chore: release 2.1.9-beta7 (#357)

Sync current changes to 2.1.9-beta7.
This commit is contained in:
sagit
2026-03-20 22:18:17 +08:00
committed by GitHub
20 changed files with 154 additions and 37 deletions
+3 -3
View File
@@ -1,9 +1,9 @@
# PROJECT KNOWLEDGE BASE # PROJECT KNOWLEDGE BASE
**Generated:** Thu Mar 19 2026 **Generated:** Fri Mar 20 2026
**Commit:** 6458b5a **Commit:** f45f960
**Branch:** main **Branch:** main
**Tag:** 2.1.9-alpha5 **Tag:** 2.1.9-beta6
## OVERVIEW ## OVERVIEW
FLVX (formerly Flux Panel) is a traffic forwarding management system built on a forked GOST v3 stack. It ships as a Go-based admin API (SQLite/PostgreSQL) + Vite/React UI + Go forwarding agent, with optional mobile WebView wrappers. FLVX (formerly Flux Panel) is a traffic forwarding management system built on a forked GOST v3 stack. It ships as a Go-based admin API (SQLite/PostgreSQL) + Vite/React UI + Go forwarding agent, with optional mobile WebView wrappers.
+4 -4
View File
@@ -33,13 +33,13 @@ curl -L https://raw.githubusercontent.com/Sagit-chu/flux-panel/main/install.sh -
#### 安装特定版本 #### 安装特定版本
从 [Releases](https://github.com/Sagit-chu/flux-panel/releases) 页面复制对应版本的安装命令,脚本会自动安装该版本而非最新版。 从 [Releases](https://github.com/Sagit-chu/flux-panel/releases) 页面复制对应版本的安装命令,脚本会自动安装该版本而非最新版。
面板端(以 2.1.0 为例): 面板端(以 2.1.9-beta6 为例):
```bash ```bash
curl -L https://github.com/Sagit-chu/flux-panel/releases/download/2.1.0/panel_install.sh -o panel_install.sh && chmod +x panel_install.sh && ./panel_install.sh curl -L https://github.com/Sagit-chu/flux-panel/releases/download/2.1.9-beta6/panel_install.sh -o panel_install.sh && chmod +x panel_install.sh && ./panel_install.sh
``` ```
节点端(以 2.1.0 为例): 节点端(以 2.1.9-beta6 为例):
```bash ```bash
curl -L https://github.com/Sagit-chu/flux-panel/releases/download/2.1.0/install.sh -o install.sh && chmod +x install.sh && ./install.sh curl -L https://github.com/Sagit-chu/flux-panel/releases/download/2.1.9-beta6/install.sh -o install.sh && chmod +x install.sh && ./install.sh
``` ```
#### PostgreSQL 部署(Docker Compose) #### PostgreSQL 部署(Docker Compose)
+5
View File
@@ -1,5 +1,10 @@
# GO BACKEND KNOWLEDGE BASE # GO BACKEND KNOWLEDGE BASE
**Generated:** Fri Mar 20 2026
**Commit:** f45f960
**Branch:** main
**Tag:** 2.1.9-beta6
## OVERVIEW ## OVERVIEW
Go-based Admin API for FLVX. Replaced legacy Spring Boot backend. Go-based Admin API for FLVX. Replaced legacy Spring Boot backend.
**Stack:** Go 1.24, net/http (std lib), GORM + SQLite/PostgreSQL (glebarez/sqlite - CGO-free). **Stack:** Go 1.24, net/http (std lib), GORM + SQLite/PostgreSQL (glebarez/sqlite - CGO-free).
+4 -1
View File
@@ -1,6 +1,9 @@
# BACKEND HTTP HANDLER KNOWLEDGE BASE # BACKEND HTTP HANDLER KNOWLEDGE BASE
**Generated:** Thu Feb 26 2026 **Generated:** Fri Mar 20 2026
**Commit:** f45f960
**Branch:** main
**Tag:** 2.1.9-beta6
## OVERVIEW ## OVERVIEW
HTTP request handlers for FLVX Admin API. Core business logic layer. HTTP request handlers for FLVX Admin API. Core business logic layer.
@@ -695,6 +695,30 @@ func (h *Handler) monitorServiceResultsHandler(w http.ResponseWriter, r *http.Re
return return
} }
// If start/end time range is provided, use time-based query (mirrors node metrics / tunnel quality pattern).
startStr := r.URL.Query().Get("start")
endStr := r.URL.Query().Get("end")
if startStr != "" && endStr != "" {
startMs, err1 := strconv.ParseInt(startStr, 10, 64)
endMs, err2 := strconv.ParseInt(endStr, 10, 64)
if err1 != nil || err2 != nil || startMs <= 0 || endMs <= 0 || endMs < startMs {
response.WriteJSON(w, response.ErrDefault("无效的时间范围"))
return
}
if endMs-startMs > maxMetricsRangeMs {
response.WriteJSON(w, response.ErrDefault("时间范围过大"))
return
}
results, err := h.repo.GetServiceMonitorResultsByTimeRange(monitorID, startMs, endMs)
if err != nil {
response.WriteJSON(w, response.Err(-2, err.Error()))
return
}
response.WriteJSON(w, response.OK(results))
return
}
// Fallback: count-based limit query (backward compat).
limit := 100 limit := 100
if l := r.URL.Query().Get("limit"); l != "" { if l := r.URL.Query().Get("limit"); l != "" {
if v, err := strconv.Atoi(l); err == nil && v > 0 && v <= 1000 { if v, err := strconv.Atoi(l); err == nil && v > 0 && v <= 1000 {
@@ -3568,6 +3568,25 @@ func (r *Repository) GetServiceMonitorResults(monitorID int64, limit int) ([]mod
return results, err return results, err
} }
// GetServiceMonitorResultsByTimeRange returns results for a monitor within [startMs, endMs].
// Mirrors GetNodeMetrics / GetTunnelMetrics pattern for time-range based charting.
func (r *Repository) GetServiceMonitorResultsByTimeRange(monitorID int64, startMs, endMs int64) ([]model.ServiceMonitorResult, error) {
if r == nil || r.db == nil {
return nil, nil
}
var results []model.ServiceMonitorResult
err := r.db.Where("monitor_id = ? AND timestamp >= ? AND timestamp <= ?", monitorID, startMs, endMs).
Order("timestamp DESC").
Limit(5000).
Find(&results).Error
if len(results) > 1 {
for i, j := 0, len(results)-1; i < j; i, j = i+1, j-1 {
results[i], results[j] = results[j], results[i]
}
}
return results, err
}
// GetLatestServiceMonitorResults returns the newest result per monitor_id. // GetLatestServiceMonitorResults returns the newest result per monitor_id.
// This is intended for list rendering (avoid N+1 queries). // This is intended for list rendering (avoid N+1 queries).
func (r *Repository) GetLatestServiceMonitorResults() ([]model.ServiceMonitorResult, error) { func (r *Repository) GetLatestServiceMonitorResults() ([]model.ServiceMonitorResult, error) {
+4 -1
View File
@@ -1,6 +1,9 @@
# GO-GOST SERVICE KNOWLEDGE BASE # GO-GOST SERVICE KNOWLEDGE BASE
**Generated:** Thu Feb 26 2026 **Generated:** Fri Mar 20 2026
**Commit:** f45f960
**Branch:** main
**Tag:** 2.1.9-beta6
## OVERVIEW ## OVERVIEW
Forwarding agent built on GOST v3 with a local fork of `github.com/go-gost/x` under `x/`. Forwarding agent built on GOST v3 with a local fork of `github.com/go-gost/x` under `x/`.
+5
View File
@@ -1,5 +1,10 @@
# GO-GOST/X KNOWLEDGE BASE # GO-GOST/X KNOWLEDGE BASE
**Generated:** Fri Mar 20 2026
**Commit:** f45f960
**Branch:** main
**Tag:** 2.1.9-beta6
## OVERVIEW ## OVERVIEW
Local fork of `github.com/go-gost/x` used by `go-gost/` via `replace github.com/go-gost/x => ./x`. Most protocol/runtime behavior changes happen here. 30+ top-level packages - framework-style layout. Local fork of `github.com/go-gost/x` used by `go-gost/` via `replace github.com/go-gost/x => ./x`. Most protocol/runtime behavior changes happen here. 30+ top-level packages - framework-style layout.
+5
View File
@@ -1,5 +1,10 @@
# GO-GOST/X API KNOWLEDGE BASE # GO-GOST/X API KNOWLEDGE BASE
**Generated:** Fri Mar 20 2026
**Commit:** f45f960
**Branch:** main
**Tag:** 2.1.9-beta6
## OVERVIEW ## OVERVIEW
Gin-based management API for reading/writing config and controlling services at runtime. Gin-based management API for reading/writing config and controlling services at runtime.
+5
View File
@@ -1,5 +1,10 @@
# GO-GOST/X CONFIG KNOWLEDGE BASE # GO-GOST/X CONFIG KNOWLEDGE BASE
**Generated:** Fri Mar 20 2026
**Commit:** f45f960
**Branch:** main
**Tag:** 2.1.9-beta6
## OVERVIEW ## OVERVIEW
Config model + parsing/loading pipeline for the `go-gost/x` runtime. This is the bridge between `gost.json`/`gost.yaml` and in-memory registries/services. Config model + parsing/loading pipeline for the `go-gost/x` runtime. This is the bridge between `gost.json`/`gost.yaml` and in-memory registries/services.
+4 -1
View File
@@ -1,6 +1,9 @@
# GOST CONNECTOR KNOWLEDGE BASE # GOST CONNECTOR KNOWLEDGE BASE
**Generated:** Fri Feb 13 2026 **Generated:** Fri Mar 20 2026
**Commit:** f45f960
**Branch:** main
**Tag:** 2.1.9-beta6
## OVERVIEW ## OVERVIEW
Connection initiators (clients) for various protocols in GOST forwarding. Connection initiators (clients) for various protocols in GOST forwarding.
+5
View File
@@ -1,5 +1,10 @@
# GO-GOST/X DIALERS KNOWLEDGE BASE # GO-GOST/X DIALERS KNOWLEDGE BASE
**Generated:** Fri Mar 20 2026
**Commit:** f45f960
**Branch:** main
**Tag:** 2.1.9-beta6
## OVERVIEW ## OVERVIEW
Outbound dialers (client-side connection establishment) used by connectors/handlers. Outbound dialers (client-side connection establishment) used by connectors/handlers.
+5
View File
@@ -1,5 +1,10 @@
# GO-GOST/X HANDLERS KNOWLEDGE BASE # GO-GOST/X HANDLERS KNOWLEDGE BASE
**Generated:** Fri Mar 20 2026
**Commit:** f45f960
**Branch:** main
**Tag:** 2.1.9-beta6
## OVERVIEW ## OVERVIEW
Protocol handlers (server-side request handling) used by services defined in the GOST config. Protocol handlers (server-side request handling) used by services defined in the GOST config.
+5
View File
@@ -1,5 +1,10 @@
# GO-GOST/X LISTENERS KNOWLEDGE BASE # GO-GOST/X LISTENERS KNOWLEDGE BASE
**Generated:** Fri Mar 20 2026
**Commit:** f45f960
**Branch:** main
**Tag:** 2.1.9-beta6
## OVERVIEW ## OVERVIEW
Inbound listeners (transport-level accept loops) used by services defined in the GOST config. Inbound listeners (transport-level accept loops) used by services defined in the GOST config.
+4 -1
View File
@@ -1,6 +1,9 @@
# GO-GOST REGISTRY KNOWLEDGE BASE # GO-GOST REGISTRY KNOWLEDGE BASE
**Generated:** Wed Feb 04 2026 **Generated:** Fri Mar 20 2026
**Commit:** f45f960
**Branch:** main
**Tag:** 2.1.9-beta6
## OVERVIEW ## OVERVIEW
Central registration point for all pluggable GOST components (handlers, listeners, dialers, etc.). Central registration point for all pluggable GOST components (handlers, listeners, dialers, etc.).
+4 -1
View File
@@ -1,6 +1,9 @@
# GOST SOCKET KNOWLEDGE BASE # GOST SOCKET KNOWLEDGE BASE
**Generated:** Sun Feb 15 2026 **Generated:** Fri Mar 20 2026
**Commit:** f45f960
**Branch:** main
**Tag:** 2.1.9-beta6
## OVERVIEW ## OVERVIEW
WebSocket reporter and socket utilities for panel integration. WebSocket reporter and socket utilities for panel integration.
+13
View File
@@ -0,0 +1,13 @@
# Plan 058: Sync Main and Push Tag
Sync current changes to `main`, merge to `master` (if needed) or just PR/merge on `main` (if that's the flow), and push a new tag `2.1.9-beta7`.
## Tasks
- [ ] Determine the next tag version (`2.1.9-beta7`).
- [ ] Create a feature branch `release/2.1.9-beta7`.
- [ ] Commit all current changes to the feature branch.
- [ ] Push the feature branch to remote.
- [ ] Create a PR to `main`.
- [ ] Merge the PR to `main`.
- [ ] Create and push tag `2.1.9-beta7`.
+3 -3
View File
@@ -1,9 +1,9 @@
# VITE FRONTEND KNOWLEDGE BASE # VITE FRONTEND KNOWLEDGE BASE
**Generated:** Thu Feb 26 2026 **Generated:** Fri Mar 20 2026
**Commit:** 21008cc **Commit:** f45f960
**Branch:** main **Branch:** main
**Tag:** 2.1.5-rc15 **Tag:** 2.1.9-beta6
## OVERVIEW ## OVERVIEW
Web management console for FLVX. Web management console for FLVX.
+14 -3
View File
@@ -486,11 +486,22 @@ export const updateServiceMonitor = (data: ServiceMonitorMutationPayload) =>
export const deleteServiceMonitor = (id: number) => export const deleteServiceMonitor = (id: number) =>
Network.post("/monitor/services/delete", { id }); Network.post("/monitor/services/delete", { id });
export const getServiceMonitorResults = (monitorId: number, limit = 100) => export const getServiceMonitorResults = (
Network.get<ServiceMonitorResultApiItem[]>( monitorId: number,
options?: { limit?: number; start?: number; end?: number },
) => {
const params: Record<string, string> = {};
if (options?.start != null && options?.end != null) {
params.start = String(options.start);
params.end = String(options.end);
} else if (options?.limit != null) {
params.limit = String(options.limit);
}
return Network.get<ServiceMonitorResultApiItem[]>(
`/monitor/services/${monitorId}/results`, `/monitor/services/${monitorId}/results`,
{ limit: String(limit) }, params,
); );
};
export const getServiceMonitorLatestResults = () => export const getServiceMonitorLatestResults = () =>
Network.get<ServiceMonitorResultApiItem[]>( Network.get<ServiceMonitorResultApiItem[]>(
+19 -19
View File
@@ -314,7 +314,7 @@ export function MonitorView({ nodeMap, viewMode = "grid" }: MonitorViewProps) {
}); });
const [submitLoading, setSubmitLoading] = useState(false); const [submitLoading, setSubmitLoading] = useState(false);
const [activeServiceMonitorId, setActiveServiceMonitorId] = useState<number | null>(null); const [activeServiceMonitorId, setActiveServiceMonitorId] = useState<number | null>(null);
const [serviceMonitorRangeLimit, setServiceMonitorRangeLimit] = useState(50); const [serviceMonitorRangeMs, setServiceMonitorRangeMs] = useState(60 * 60 * 1000);
const [accessDenied, setAccessDenied] = useState<string | null>(null); const [accessDenied, setAccessDenied] = useState<string | null>(null);
const [resultsModalOpen, setResultsModalOpen] = useState(false); const [resultsModalOpen, setResultsModalOpen] = useState(false);
@@ -526,9 +526,12 @@ export function MonitorView({ nodeMap, viewMode = "grid" }: MonitorViewProps) {
}, []); }, []);
const loadMonitorResults = useCallback( const loadMonitorResults = useCallback(
async (monitorId: number, limit = 20) => { async (monitorId: number, options?: { rangeMs?: number; limit?: number }) => {
try { try {
const response = await getServiceMonitorResults(monitorId, limit); const apiOptions = options?.rangeMs != null
? { start: Date.now() - options.rangeMs, end: Date.now() }
: { limit: options?.limit ?? 100 };
const response = await getServiceMonitorResults(monitorId, apiOptions);
if (response.code === 0 && response.data) { if (response.code === 0 && response.data) {
setMonitorResults((prev) => ({ setMonitorResults((prev) => ({
@@ -600,7 +603,7 @@ export function MonitorView({ nodeMap, viewMode = "grid" }: MonitorViewProps) {
if (!resultsMonitorId) return; if (!resultsMonitorId) return;
setResultsLoading(true); setResultsLoading(true);
try { try {
await loadMonitorResults(resultsMonitorId, resultsLimit); await loadMonitorResults(resultsMonitorId, { limit: resultsLimit });
} finally { } finally {
setResultsLoading(false); setResultsLoading(false);
} }
@@ -641,11 +644,11 @@ export function MonitorView({ nodeMap, viewMode = "grid" }: MonitorViewProps) {
void loadResultsForModal(); void loadResultsForModal();
}, [resultsModalOpen, resultsMonitorId, resultsLimit, loadResultsForModal]); }, [resultsModalOpen, resultsMonitorId, resultsLimit, loadResultsForModal]);
// Reload results for the active service monitor chart when range limit changes // Reload results for the active service monitor chart when time range changes
useEffect(() => { useEffect(() => {
if (!activeServiceMonitorId) return; if (!activeServiceMonitorId) return;
void loadMonitorResults(activeServiceMonitorId, serviceMonitorRangeLimit); void loadMonitorResults(activeServiceMonitorId, { rangeMs: serviceMonitorRangeMs });
}, [activeServiceMonitorId, serviceMonitorRangeLimit, loadMonitorResults]); }, [activeServiceMonitorId, serviceMonitorRangeMs, loadMonitorResults]);
const chartData = metrics.map((m) => ({ const chartData = metrics.map((m) => ({
time: formatTimestamp(m.timestamp, metricsRangeMs), time: formatTimestamp(m.timestamp, metricsRangeMs),
@@ -1322,10 +1325,8 @@ export function MonitorView({ nodeMap, viewMode = "grid" }: MonitorViewProps) {
const activeStale = resolvedActiveMonitor ? isResultStale(resolvedActiveMonitor, activeLatestResult) : false; const activeStale = resolvedActiveMonitor ? isResultStale(resolvedActiveMonitor, activeLatestResult) : false;
const activeResults = resolvedActiveMonitorId != null ? (monitorResults[resolvedActiveMonitorId] || []) : []; const activeResults = resolvedActiveMonitorId != null ? (monitorResults[resolvedActiveMonitorId] || []) : [];
const activeLatencyData = [...activeResults] const activeLatencyData = [...activeResults]
.slice(0, serviceMonitorRangeLimit)
.reverse()
.map((r) => ({ .map((r) => ({
time: formatTimestamp(r.timestamp), time: formatTimestamp(r.timestamp, serviceMonitorRangeMs),
latency: r.success === 1 ? r.latencyMs : null, latency: r.success === 1 ? r.latencyMs : null,
success: r.success, success: r.success,
})); }));
@@ -1336,24 +1337,23 @@ export function MonitorView({ nodeMap, viewMode = "grid" }: MonitorViewProps) {
<h3 className="text-lg font-semibold">服务监控图表</h3> <h3 className="text-lg font-semibold">服务监控图表</h3>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Select <Select
className="w-28" className="w-36"
selectedKeys={[String(serviceMonitorRangeLimit)]} selectedKeys={[String(serviceMonitorRangeMs)]}
onSelectionChange={(keys) => { onSelectionChange={(keys) => {
const v = Number(Array.from(keys)[0]); const v = Number(Array.from(keys)[0]);
if (v > 0) setServiceMonitorRangeLimit(v); if (v > 0) setServiceMonitorRangeMs(v);
}} }}
> >
<SelectItem key="20">20条</SelectItem> <SelectItem key={String(60 * 60 * 1000)}>1小时</SelectItem>
<SelectItem key="50">50条</SelectItem> <SelectItem key={String(6 * 60 * 60 * 1000)}>6小时</SelectItem>
<SelectItem key="100">100条</SelectItem> <SelectItem key={String(24 * 60 * 60 * 1000)}>24小时</SelectItem>
<SelectItem key="200">200条</SelectItem>
</Select> </Select>
{resolvedActiveMonitorId != null && ( {resolvedActiveMonitorId != null && (
<Button <Button
size="sm" size="sm"
variant="flat" variant="flat"
onPress={() => { onPress={() => {
void loadMonitorResults(resolvedActiveMonitorId, serviceMonitorRangeLimit); void loadMonitorResults(resolvedActiveMonitorId, { rangeMs: serviceMonitorRangeMs });
}} }}
> >
<RefreshCw className="w-4 h-4 mr-1" /> <RefreshCw className="w-4 h-4 mr-1" />
@@ -1391,7 +1391,7 @@ export function MonitorView({ nodeMap, viewMode = "grid" }: MonitorViewProps) {
setActiveServiceMonitorId(monitor.id); setActiveServiceMonitorId(monitor.id);
// Load results for this monitor if not loaded // Load results for this monitor if not loaded
if (!monitorResults[monitor.id] || monitorResults[monitor.id].length <= 1) { if (!monitorResults[monitor.id] || monitorResults[monitor.id].length <= 1) {
void loadMonitorResults(monitor.id, serviceMonitorRangeLimit); void loadMonitorResults(monitor.id, { rangeMs: serviceMonitorRangeMs });
} }
}} }}
> >