From 8ebde9dca9888ea681a5d5ea264b0f4deb8bf378 Mon Sep 17 00:00:00 2001 From: sagit <36596628+Sagit-chu@users.noreply.github.com> Date: Sun, 22 Mar 2026 13:03:04 +0800 Subject: [PATCH] feat: node OS logo and UI fixes (#367) * chore: update AGENTS.md with next release info * feat: node OS logo, UI rate fix, and monitor trend updates --- AGENTS.md | 4 +- .../internal/http/handler/monitoring.go | 2 + .../store/repo/repository_monitor_nodes.go | 2 +- go-gost/main.go | 4 +- go-gost/x/socket/distro.go | 21 +++ go-gost/x/socket/websocket_reporter.go | 2 +- plans/061-node-os-logo.md | 15 ++ plans/062-release-beta14.md | 49 ++++++ vite-frontend/src/api/types.ts | 1 + vite-frontend/src/components/distro-icon.tsx | 154 ++++++++++++++++++ vite-frontend/src/pages/monitor.tsx | 2 + vite-frontend/src/pages/node/monitor-view.tsx | 47 ++++-- 12 files changed, 285 insertions(+), 18 deletions(-) create mode 100644 go-gost/x/socket/distro.go create mode 100644 plans/061-node-os-logo.md create mode 100644 plans/062-release-beta14.md create mode 100644 vite-frontend/src/components/distro-icon.tsx diff --git a/AGENTS.md b/AGENTS.md index 80a99e7..c86fc52 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,9 +1,9 @@ # PROJECT KNOWLEDGE BASE **Generated:** Sat Mar 21 2026 -**Commit:** 433c8aa +**Commit:** 0767c9c **Branch:** main -**Tag:** 2.1.9-beta12 +**Tag:** 2.1.9-beta13 ## 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. diff --git a/go-backend/internal/http/handler/monitoring.go b/go-backend/internal/http/handler/monitoring.go index e7c8c21..5ca8a34 100644 --- a/go-backend/internal/http/handler/monitoring.go +++ b/go-backend/internal/http/handler/monitoring.go @@ -71,6 +71,7 @@ type monitorNodeListItem struct { Inx int `json:"inx"` Name string `json:"name"` Status int `json:"status"` + Version string `json:"version"` UpdatedTime int64 `json:"updatedTime"` } @@ -100,6 +101,7 @@ func (h *Handler) monitorNodeListHandler(w http.ResponseWriter, r *http.Request) Inx: n.Inx, Name: n.Name, Status: n.Status, + Version: n.Version.String, UpdatedTime: updated, }) } diff --git a/go-backend/internal/store/repo/repository_monitor_nodes.go b/go-backend/internal/store/repo/repository_monitor_nodes.go index e05cd3e..064d845 100644 --- a/go-backend/internal/store/repo/repository_monitor_nodes.go +++ b/go-backend/internal/store/repo/repository_monitor_nodes.go @@ -11,7 +11,7 @@ func (r *Repository) ListMonitorNodes() ([]model.Node, error) { return nil, errors.New("repository not initialized") } var nodes []model.Node - err := r.db.Select("id", "inx", "name", "status", "updated_time"). + err := r.db.Select("id", "inx", "name", "status", "version", "updated_time"). Where("is_remote = ?", 0). Order("inx ASC, id ASC"). Find(&nodes).Error diff --git a/go-gost/main.go b/go-gost/main.go index 42dc9ec..88405e9 100644 --- a/go-gost/main.go +++ b/go-gost/main.go @@ -119,7 +119,9 @@ func main() { log := xlogger.NewLogger() logger.SetDefault(log) - wsReporter := socket.StartWebSocketReporterWithConfig(config.Addr, config.Secret, config.Http, config.Tls, config.Socks, version) + distro := socket.DetectDistro() + fullVersion := fmt.Sprintf("%s (%s/%s)", version, distro, runtime.GOARCH) + wsReporter := socket.StartWebSocketReporterWithConfig(config.Addr, config.Secret, config.Http, config.Tls, config.Socks, fullVersion) defer wsReporter.Stop() service.SetHTTPReportURL(config.Addr, config.Secret) diff --git a/go-gost/x/socket/distro.go b/go-gost/x/socket/distro.go new file mode 100644 index 0000000..d6fdef4 --- /dev/null +++ b/go-gost/x/socket/distro.go @@ -0,0 +1,21 @@ +package socket + +import ( + "strings" + + "github.com/shirou/gopsutil/v3/host" +) + +// DetectDistro returns the Linux distribution name (e.g. "ubuntu", "centos", +// "debian"). Falls back to "linux" when detection fails. +func DetectDistro() string { + info, err := host.Info() + if err != nil || info == nil { + return "linux" + } + platform := strings.ToLower(strings.TrimSpace(info.Platform)) + if platform == "" { + return "linux" + } + return platform +} diff --git a/go-gost/x/socket/websocket_reporter.go b/go-gost/x/socket/websocket_reporter.go index 33b353d..2032145 100644 --- a/go-gost/x/socket/websocket_reporter.go +++ b/go-gost/x/socket/websocket_reporter.go @@ -348,7 +348,7 @@ func buildWebSocketCandidates(addr string, secret string, version string, http i normalizedAddr = strings.TrimSpace(addr) } - query := "/system-info?type=1&secret=" + secret + "&version=" + version + + query := "/system-info?type=1&secret=" + url.QueryEscape(secret) + "&version=" + url.QueryEscape(version) + "&http=" + strconv.Itoa(http) + "&tls=" + strconv.Itoa(tls) + "&socks=" + strconv.Itoa(socks) schemes := []string{"wss", "ws"} diff --git a/plans/061-node-os-logo.md b/plans/061-node-os-logo.md new file mode 100644 index 0000000..ab6da6d --- /dev/null +++ b/plans/061-node-os-logo.md @@ -0,0 +1,15 @@ +# Plan 061: Node Logo By OS Type (Linux Distro) + +## Objective +Display different logos for nodes in the 'Monitor - Node - Card/List View' based on their Linux distribution (Ubuntu, Debian, CentOS, Alpine, etc.). + +## Tasks +- [x] Agent: Use `gopsutil/v3/host.Info().Platform` to detect the Linux distro and include it in the version string (`distro.go`). +- [x] Agent: Update `main.go` to call `socket.DetectDistro()` instead of `runtime.GOOS`. +- [x] Agent: Ensure version parameter is URL-escaped since it now includes distro info with special chars. +- [x] Backend: Select `version` column in `ListMonitorNodes`. +- [x] Backend: Include `version` in `monitorNodeListItem` JSON response. +- [x] Frontend: Add `version` to TypeScript interfaces (`MonitorNodeApiItem`, `MonitorNode`, `MonitorViewProps`). +- [x] Frontend: Create `distro-icon.tsx` component with SVG logos for Ubuntu, Debian, CentOS/Rocky/Alma, Alpine, Fedora, Arch/Manjaro, and a default Linux (Tux) fallback. +- [x] Frontend: Use `DistroIcon` in `ServerCard` (card view) and list view name column with branded colors per distro. +- [x] All three projects compile cleanly (`go build`, `tsc --noEmit`). diff --git a/plans/062-release-beta14.md b/plans/062-release-beta14.md new file mode 100644 index 0000000..b16854c --- /dev/null +++ b/plans/062-release-beta14.md @@ -0,0 +1,49 @@ +# Plan 062: Commit, PR, Merge and Tag + +## Overview +This plan outlines the steps to commit all changes, create a PR, merge it, and then publish a new tag for the FLVX project. + +## Checklist +- [ ] Check current git status for any unexpected changes +- [ ] Create a feature branch `feat-node-os-logo-release` +- [ ] Stage and commit all modifications and untracked files +- [ ] Push the feature branch to origin +- [ ] Create a Pull Request (PR) from the feature branch to `main` +- [ ] Merge the PR to `main` +- [ ] Update `AGENTS.md` with the new tag and commit hash +- [ ] Create and push new tag `2.1.9-beta14` + +## Detailed Steps + +### 1. Create Feature Branch +```bash +git checkout -b feat-node-os-logo-release +``` + +### 2. Commit all changes +Add all modified and untracked files: +```bash +git add . +git commit -m "feat: node OS logo support, UI rate overlap fix and tunnel monitoring updates" +``` + +### 3. Push and PR +Push to `origin`: +```bash +git push origin feat-node-os-logo-release +``` +Create PR via `gh pr create` if possible. + +### 4. Merge to Main +```bash +git checkout main +git merge feat-node-os-logo-release +git push origin main +``` + +### 5. Create Tag +Increment the current tag `2.1.9-beta13` to `2.1.9-beta14`. +```bash +git tag 2.1.9-beta14 +git push origin 2.1.9-beta14 +``` diff --git a/vite-frontend/src/api/types.ts b/vite-frontend/src/api/types.ts index 25db1b7..77330d7 100644 --- a/vite-frontend/src/api/types.ts +++ b/vite-frontend/src/api/types.ts @@ -466,6 +466,7 @@ export interface MonitorNodeApiItem { inx: number; name: string; status: number; + version?: string; updatedTime: number; } diff --git a/vite-frontend/src/components/distro-icon.tsx b/vite-frontend/src/components/distro-icon.tsx new file mode 100644 index 0000000..de0d151 --- /dev/null +++ b/vite-frontend/src/components/distro-icon.tsx @@ -0,0 +1,154 @@ +import type { SVGProps } from "react"; + +// Map of Linux distribution names to their SVG logo components. +// Version strings from the agent look like "v1.0.0 (ubuntu/amd64)". + +const distroColors: Record = { + ubuntu: "#E95420", + debian: "#A81D33", + centos: "#262577", + rocky: "#10B981", + alma: "#0F4266", + alpine: "#0D597F", + fedora: "#51A2DA", + arch: "#1793D1", + opensuse: "#73BA25", + suse: "#73BA25", + rhel: "#EE0000", + redhat: "#EE0000", + oracle: "#F80000", + amazon: "#FF9900", + amzn: "#FF9900", + kali: "#557C94", + mint: "#87CF3E", + gentoo: "#54487A", + void: "#478061", + nixos: "#7EBAE4", + manjaro: "#35BF5C", +}; + +function UbuntuIcon(props: SVGProps) { + return ( + + + + + + + + ); +} + +function DebianIcon(props: SVGProps) { + return ( + + + + ); +} + +function CentOSIcon(props: SVGProps) { + return ( + + + + + + + + ); +} + +function AlpineIcon(props: SVGProps) { + return ( + + + + ); +} + +function FedoraIcon(props: SVGProps) { + return ( + + + + f + + ); +} + +function ArchIcon(props: SVGProps) { + return ( + + + + ); +} + +function DefaultLinuxIcon(props: SVGProps) { + // Tux-inspired simple penguin outline + return ( + + + + ); +} + +type DistroIconProps = SVGProps & { + distro: string; +}; + +/** + * Detect the Linux distro from a version string like "v1.0.0 (ubuntu/amd64)" + * and return the normalized distro key. + */ +export function parseDistroFromVersion(version?: string): string { + if (!version) return "linux"; + const lower = version.toLowerCase(); + + // Try to extract from parenthetical format: (distro/arch) + const match = lower.match(/\(([^/)\s]+)/); + if (match) { + return match[1].trim(); + } + + // Fallback: check for known distro names anywhere in the string + const knownDistros = [ + "ubuntu", "debian", "centos", "rocky", "alma", "alpine", + "fedora", "arch", "opensuse", "suse", "rhel", "redhat", + "oracle", "amazon", "amzn", "kali", "mint", "gentoo", + "void", "nixos", "manjaro", + ]; + for (const d of knownDistros) { + if (lower.includes(d)) return d; + } + + return "linux"; +} + +/** + * Returns a branded color for the detected distro. + */ +export function getDistroColor(distro: string): string { + const key = distro.toLowerCase(); + for (const [name, color] of Object.entries(distroColors)) { + if (key.includes(name)) return color; + } + return "#6366F1"; // default indigo for unknown linux +} + +/** + * Render an SVG icon for the given Linux distribution. + */ +export function DistroIcon({ distro, ...props }: DistroIconProps) { + const key = distro.toLowerCase(); + + if (key.includes("ubuntu")) return ; + if (key.includes("debian")) return ; + if (key.includes("centos") || key.includes("rocky") || key.includes("alma")) return ; + if (key.includes("alpine")) return ; + if (key.includes("fedora")) return ; + if (key.includes("arch") || key.includes("manjaro")) return ; + + return ; +} diff --git a/vite-frontend/src/pages/monitor.tsx b/vite-frontend/src/pages/monitor.tsx index 91add49..1b4cee0 100644 --- a/vite-frontend/src/pages/monitor.tsx +++ b/vite-frontend/src/pages/monitor.tsx @@ -15,6 +15,7 @@ type MonitorNode = { id: number; name: string; connectionStatus: "online" | "offline"; + version?: string; }; type MonitorTab = "nodes" | "tunnels"; @@ -73,6 +74,7 @@ export default function MonitorPage() { id: Number(n.id), name: String(n.name ?? ""), connectionStatus: n.status === 1 ? "online" : "offline", + version: n.version, })); return new Map(list.map((n) => [n.id, n])); diff --git a/vite-frontend/src/pages/node/monitor-view.tsx b/vite-frontend/src/pages/node/monitor-view.tsx index 3bd128f..5928be3 100644 --- a/vite-frontend/src/pages/node/monitor-view.tsx +++ b/vite-frontend/src/pages/node/monitor-view.tsx @@ -27,9 +27,13 @@ import { Server, Clock, ArrowLeft, + ArrowUp, + ArrowDown, Eye, + } from "lucide-react"; import toast from "react-hot-toast"; +import { DistroIcon, parseDistroFromVersion, getDistroColor } from "@/components/distro-icon"; import { getNodeMetrics, @@ -73,7 +77,7 @@ import { Progress } from "@/shadcn-bridge/heroui/progress"; import { useNodeRealtime } from "@/pages/node/use-node-realtime"; interface MonitorViewProps { - nodeMap: Map; + nodeMap: Map; viewMode?: "list" | "grid"; } @@ -165,6 +169,8 @@ const getColorByUsage = (usage?: number) => { function ServerCard({ node, metric, onPress }: { node: any; metric: RealtimeNodeMetric | null; onPress?: () => void }) { const isOnline = node.connectionStatus === "online"; + const distro = parseDistroFromVersion(node.version); + const distroColor = getDistroColor(distro); return (
- +
@@ -1104,25 +1110,40 @@ export function MonitorView({ nodeMap, viewMode = "grid" }: MonitorViewProps) {
- {node.name} +
+ + {node.name} +
-
-
- {isOnline && metric ? formatBytesPerSecond(metric.netOutSpeed) : "-"} ↑ +
+
+ {isOnline && metric ? formatBytesPerSecond(metric.netOutSpeed) : "-"} +
+ +
-
- {isOnline && metric ? formatBytesPerSecond(metric.netInSpeed) : "-"} ↓ +
+ {isOnline && metric ? formatBytesPerSecond(metric.netInSpeed) : "-"} +
+ +
-
-
- {isOnline && metric ? formatBytes(metric.netOutBytes) : "-"} ↑ +
+
+ {isOnline && metric ? formatBytes(metric.netOutBytes) : "-"} +
+ +
-
- {isOnline && metric ? formatBytes(metric.netInBytes) : "-"} ↓ +
+ {isOnline && metric ? formatBytes(metric.netInBytes) : "-"} +
+ +