mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-09-28 07:36:38 +08:00
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
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
# PROJECT KNOWLEDGE BASE
|
# PROJECT KNOWLEDGE BASE
|
||||||
|
|
||||||
**Generated:** Sat Mar 21 2026
|
**Generated:** Sat Mar 21 2026
|
||||||
**Commit:** 433c8aa
|
**Commit:** 0767c9c
|
||||||
**Branch:** main
|
**Branch:** main
|
||||||
**Tag:** 2.1.9-beta12
|
**Tag:** 2.1.9-beta13
|
||||||
|
|
||||||
## 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.
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ type monitorNodeListItem struct {
|
|||||||
Inx int `json:"inx"`
|
Inx int `json:"inx"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Status int `json:"status"`
|
Status int `json:"status"`
|
||||||
|
Version string `json:"version"`
|
||||||
UpdatedTime int64 `json:"updatedTime"`
|
UpdatedTime int64 `json:"updatedTime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,6 +101,7 @@ func (h *Handler) monitorNodeListHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
Inx: n.Inx,
|
Inx: n.Inx,
|
||||||
Name: n.Name,
|
Name: n.Name,
|
||||||
Status: n.Status,
|
Status: n.Status,
|
||||||
|
Version: n.Version.String,
|
||||||
UpdatedTime: updated,
|
UpdatedTime: updated,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ func (r *Repository) ListMonitorNodes() ([]model.Node, error) {
|
|||||||
return nil, errors.New("repository not initialized")
|
return nil, errors.New("repository not initialized")
|
||||||
}
|
}
|
||||||
var nodes []model.Node
|
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).
|
Where("is_remote = ?", 0).
|
||||||
Order("inx ASC, id ASC").
|
Order("inx ASC, id ASC").
|
||||||
Find(&nodes).Error
|
Find(&nodes).Error
|
||||||
|
|||||||
+3
-1
@@ -119,7 +119,9 @@ func main() {
|
|||||||
log := xlogger.NewLogger()
|
log := xlogger.NewLogger()
|
||||||
logger.SetDefault(log)
|
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()
|
defer wsReporter.Stop()
|
||||||
service.SetHTTPReportURL(config.Addr, config.Secret)
|
service.SetHTTPReportURL(config.Addr, config.Secret)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -348,7 +348,7 @@ func buildWebSocketCandidates(addr string, secret string, version string, http i
|
|||||||
normalizedAddr = strings.TrimSpace(addr)
|
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)
|
"&http=" + strconv.Itoa(http) + "&tls=" + strconv.Itoa(tls) + "&socks=" + strconv.Itoa(socks)
|
||||||
|
|
||||||
schemes := []string{"wss", "ws"}
|
schemes := []string{"wss", "ws"}
|
||||||
|
|||||||
@@ -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`).
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -466,6 +466,7 @@ export interface MonitorNodeApiItem {
|
|||||||
inx: number;
|
inx: number;
|
||||||
name: string;
|
name: string;
|
||||||
status: number;
|
status: number;
|
||||||
|
version?: string;
|
||||||
updatedTime: number;
|
updatedTime: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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<string, string> = {
|
||||||
|
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<SVGSVGElement>) {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor" {...props}>
|
||||||
|
<circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" strokeWidth="1.5" />
|
||||||
|
<circle cx="12" cy="5" r="2" />
|
||||||
|
<circle cx="5.5" cy="15.5" r="2" />
|
||||||
|
<circle cx="18.5" cy="15.5" r="2" />
|
||||||
|
<circle cx="12" cy="12" r="3" fill="none" stroke="currentColor" strokeWidth="1.5" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DebianIcon(props: SVGProps<SVGSVGElement>) {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor" {...props}>
|
||||||
|
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm3.5 14.5c-1.5 1.2-3 1.5-4.5 1.3-2-.3-3.5-1.5-4.2-3.3-.8-2-.5-4 .8-5.6 1-1.2 2.3-1.9 3.9-2 1.2 0 2 .3 2 .3l-.1 1s-.8-.4-1.7-.3c-2 .1-3.5 1.7-3.4 3.8.1 1.8 1.3 3.2 3 3.5 1.2.2 2.3-.1 3.2-.8l1 2.1z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CentOSIcon(props: SVGProps<SVGSVGElement>) {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor" {...props}>
|
||||||
|
<rect x="3" y="3" width="8" height="8" rx="1" opacity="0.9" />
|
||||||
|
<rect x="13" y="3" width="8" height="8" rx="1" opacity="0.7" />
|
||||||
|
<rect x="3" y="13" width="8" height="8" rx="1" opacity="0.7" />
|
||||||
|
<rect x="13" y="13" width="8" height="8" rx="1" opacity="0.5" />
|
||||||
|
<circle cx="12" cy="12" r="3" fill="none" stroke="currentColor" strokeWidth="1.5" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlpineIcon(props: SVGProps<SVGSVGElement>) {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor" {...props}>
|
||||||
|
<path d="M12 3L2 19h20L12 3zm0 4l6 10H6l6-10z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function FedoraIcon(props: SVGProps<SVGSVGElement>) {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor" {...props}>
|
||||||
|
<circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" strokeWidth="1.5" />
|
||||||
|
<path d="M12 6v6h6" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
|
||||||
|
<text x="8" y="17" fontSize="6" fontWeight="bold" fill="currentColor">f</text>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ArchIcon(props: SVGProps<SVGSVGElement>) {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor" {...props}>
|
||||||
|
<path d="M12 2l-2 5c1 1.5 1.5 3 2 5 .5-2 1-3.5 2-5L12 2zM8.5 10C6.5 14 4 17 2 20c2.5-1.5 4.5-2.5 7-3-1-2-1.5-4.5-0.5-7zM15.5 10c1 2.5.5 5-.5 7 2.5.5 4.5 1.5 7 3-2-3-4.5-6-6.5-10z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DefaultLinuxIcon(props: SVGProps<SVGSVGElement>) {
|
||||||
|
// Tux-inspired simple penguin outline
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor" {...props}>
|
||||||
|
<path d="M12 2C9.8 2 8 4.1 8 6.5c0 1.2.4 2.2 1 3l-1.5 4.5c-.3 1-.5 2-.5 3 0 2.2 2.2 4 5 4s5-1.8 5-4c0-1-.2-2-.5-3L15 9.5c.6-.8 1-1.8 1-3C16 4.1 14.2 2 12 2zm-1.5 6a1 1 0 110-2 1 1 0 010 2zm3 0a1 1 0 110-2 1 1 0 010 2zm-3 3h3l.5 1h-4l.5-1z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type DistroIconProps = SVGProps<SVGSVGElement> & {
|
||||||
|
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 <UbuntuIcon {...props} />;
|
||||||
|
if (key.includes("debian")) return <DebianIcon {...props} />;
|
||||||
|
if (key.includes("centos") || key.includes("rocky") || key.includes("alma")) return <CentOSIcon {...props} />;
|
||||||
|
if (key.includes("alpine")) return <AlpineIcon {...props} />;
|
||||||
|
if (key.includes("fedora")) return <FedoraIcon {...props} />;
|
||||||
|
if (key.includes("arch") || key.includes("manjaro")) return <ArchIcon {...props} />;
|
||||||
|
|
||||||
|
return <DefaultLinuxIcon {...props} />;
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ type MonitorNode = {
|
|||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
connectionStatus: "online" | "offline";
|
connectionStatus: "online" | "offline";
|
||||||
|
version?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type MonitorTab = "nodes" | "tunnels";
|
type MonitorTab = "nodes" | "tunnels";
|
||||||
@@ -73,6 +74,7 @@ export default function MonitorPage() {
|
|||||||
id: Number(n.id),
|
id: Number(n.id),
|
||||||
name: String(n.name ?? ""),
|
name: String(n.name ?? ""),
|
||||||
connectionStatus: n.status === 1 ? "online" : "offline",
|
connectionStatus: n.status === 1 ? "online" : "offline",
|
||||||
|
version: n.version,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return new Map<number, MonitorNode>(list.map((n) => [n.id, n]));
|
return new Map<number, MonitorNode>(list.map((n) => [n.id, n]));
|
||||||
|
|||||||
@@ -27,9 +27,13 @@ import {
|
|||||||
Server,
|
Server,
|
||||||
Clock,
|
Clock,
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
|
ArrowUp,
|
||||||
|
ArrowDown,
|
||||||
Eye,
|
Eye,
|
||||||
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
import { DistroIcon, parseDistroFromVersion, getDistroColor } from "@/components/distro-icon";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getNodeMetrics,
|
getNodeMetrics,
|
||||||
@@ -73,7 +77,7 @@ import { Progress } from "@/shadcn-bridge/heroui/progress";
|
|||||||
import { useNodeRealtime } from "@/pages/node/use-node-realtime";
|
import { useNodeRealtime } from "@/pages/node/use-node-realtime";
|
||||||
|
|
||||||
interface MonitorViewProps {
|
interface MonitorViewProps {
|
||||||
nodeMap: Map<number, { id: number; name: string; connectionStatus: string }>;
|
nodeMap: Map<number, { id: number; name: string; connectionStatus: string; version?: string }>;
|
||||||
viewMode?: "list" | "grid";
|
viewMode?: "list" | "grid";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,6 +169,8 @@ const getColorByUsage = (usage?: number) => {
|
|||||||
|
|
||||||
function ServerCard({ node, metric, onPress }: { node: any; metric: RealtimeNodeMetric | null; onPress?: () => void }) {
|
function ServerCard({ node, metric, onPress }: { node: any; metric: RealtimeNodeMetric | null; onPress?: () => void }) {
|
||||||
const isOnline = node.connectionStatus === "online";
|
const isOnline = node.connectionStatus === "online";
|
||||||
|
const distro = parseDistroFromVersion(node.version);
|
||||||
|
const distroColor = getDistroColor(distro);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
@@ -181,7 +187,7 @@ function ServerCard({ node, metric, onPress }: { node: any; metric: RealtimeNode
|
|||||||
<div className="flex items-center gap-3 min-w-0">
|
<div className="flex items-center gap-3 min-w-0">
|
||||||
<div className="relative flex-shrink-0">
|
<div className="relative flex-shrink-0">
|
||||||
<div className="w-10 h-10 rounded-xl bg-default-100 dark:bg-default-50/10 flex items-center justify-center border border-divider">
|
<div className="w-10 h-10 rounded-xl bg-default-100 dark:bg-default-50/10 flex items-center justify-center border border-divider">
|
||||||
<Server className={`w-5 h-5 ${isOnline ? "text-success" : "text-danger"}`} />
|
<DistroIcon distro={distro} className="w-5 h-5" style={{ color: isOnline ? distroColor : undefined }} />
|
||||||
</div>
|
</div>
|
||||||
<span className={`absolute -bottom-0.5 -right-0.5 w-3 h-3 rounded-full border-2 border-background ${isOnline ? "bg-success" : "bg-danger"}`} />
|
<span className={`absolute -bottom-0.5 -right-0.5 w-3 h-3 rounded-full border-2 border-background ${isOnline ? "bg-success" : "bg-danger"}`} />
|
||||||
</div>
|
</div>
|
||||||
@@ -1104,25 +1110,40 @@ export function MonitorView({ nodeMap, viewMode = "grid" }: MonitorViewProps) {
|
|||||||
<div className={`w-2 h-2 rounded-full ml-1 ${isOnline ? "bg-success" : "bg-danger"}`} />
|
<div className={`w-2 h-2 rounded-full ml-1 ${isOnline ? "bg-success" : "bg-danger"}`} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<span className="font-semibold text-sm whitespace-nowrap">{node.name}</span>
|
<div className="flex items-center gap-2">
|
||||||
|
<DistroIcon distro={parseDistroFromVersion(node.version)} className="w-4 h-4 flex-shrink-0" style={{ color: isOnline ? getDistroColor(parseDistroFromVersion(node.version)) : undefined }} />
|
||||||
|
<span className="font-semibold text-sm whitespace-nowrap">{node.name}</span>
|
||||||
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="flex flex-col gap-1.5 text-xs whitespace-nowrap">
|
<div className="flex flex-col gap-2 py-1 text-xs whitespace-nowrap">
|
||||||
<div className="flex items-center gap-1 font-mono text-success-500">
|
<div className="flex items-center gap-1.5 font-mono text-success-500">
|
||||||
<span className="w-[60px] text-right">{isOnline && metric ? formatBytesPerSecond(metric.netOutSpeed) : "-"}</span> ↑
|
<span className="w-[86px] text-right inline-block">{isOnline && metric ? formatBytesPerSecond(metric.netOutSpeed) : "-"}</span>
|
||||||
|
<div className="flex items-center justify-center p-[3px] rounded-full bg-success-50 dark:bg-success-500/10 text-success-500">
|
||||||
|
<ArrowUp className="w-3 h-3" strokeWidth={2.5} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1 font-mono text-primary-500">
|
<div className="flex items-center gap-1.5 font-mono text-primary-500">
|
||||||
<span className="w-[60px] text-right">{isOnline && metric ? formatBytesPerSecond(metric.netInSpeed) : "-"}</span> ↓
|
<span className="w-[86px] text-right inline-block">{isOnline && metric ? formatBytesPerSecond(metric.netInSpeed) : "-"}</span>
|
||||||
|
<div className="flex items-center justify-center p-[3px] rounded-full bg-primary-50 dark:bg-primary-500/10 text-primary-500">
|
||||||
|
<ArrowDown className="w-3 h-3" strokeWidth={2.5} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="flex flex-col gap-1.5 text-xs whitespace-nowrap">
|
<div className="flex flex-col gap-2 py-1 text-xs whitespace-nowrap">
|
||||||
<div className="flex items-center gap-1 font-mono text-default-600">
|
<div className="flex items-center gap-1.5 font-mono text-default-600">
|
||||||
<span className="w-[60px] text-right">{isOnline && metric ? formatBytes(metric.netOutBytes) : "-"}</span> ↑
|
<span className="w-[86px] text-right inline-block">{isOnline && metric ? formatBytes(metric.netOutBytes) : "-"}</span>
|
||||||
|
<div className="flex items-center justify-center p-[3px] rounded-full bg-default-100 text-default-500 dark:bg-default-100/50">
|
||||||
|
<ArrowUp className="w-3 h-3" strokeWidth={2.5} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1 font-mono text-default-600">
|
<div className="flex items-center gap-1.5 font-mono text-default-600">
|
||||||
<span className="w-[60px] text-right">{isOnline && metric ? formatBytes(metric.netInBytes) : "-"}</span> ↓
|
<span className="w-[86px] text-right inline-block">{isOnline && metric ? formatBytes(metric.netInBytes) : "-"}</span>
|
||||||
|
<div className="flex items-center justify-center p-[3px] rounded-full bg-default-100 text-default-500 dark:bg-default-100/50">
|
||||||
|
<ArrowDown className="w-3 h-3" strokeWidth={2.5} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|||||||
Reference in New Issue
Block a user