This commit is contained in:
Denozordec
2026-05-03 11:16:07 +07:00
parent ce00c4c671
commit bdb9b72fac
66 changed files with 9553 additions and 1547 deletions
+43 -14
View File
@@ -15,6 +15,8 @@ interface BackendServer {
useSsl: boolean; verifySsl: boolean; username: string; password: string
type: ServerType; site: string; country: string; asn: string
comment: string; enabled: boolean
lanSubnet: string
wanUplinks: WanUplink[]
status: "online" | "offline" | null; latency: number | null
os: string | null; model: string | null; uptime: string | null
cpuLoad: number | null; freeMemory: number | null; totalMemory: number | null
@@ -39,10 +41,14 @@ function toFrontend(s: BackendServer): Server {
latency: s.latency != null ? Math.round(s.latency) : null,
sessions: s.sessions ?? 0,
comment: s.comment || undefined,
lanSubnet: s.lanSubnet || undefined,
wanUplinks: Array.isArray(s.wanUplinks) && s.wanUplinks.length ? s.wanUplinks : undefined,
// carry extra fields needed for expanded view
uptime: s.uptime ?? undefined,
cpuLoad: s.cpuLoad ?? undefined,
polledAt: s.polledAt ?? undefined,
uptime: s.uptime ?? undefined,
cpuLoad: s.cpuLoad ?? undefined,
freeMemory: s.freeMemory ?? undefined,
totalMemory: s.totalMemory ?? undefined,
polledAt: s.polledAt ?? undefined,
}
}
@@ -113,10 +119,10 @@ const ROS_FEATURES: RosFeature[] = [
function RosBadge({ os }: { os: string }) {
const v = rosVer(os)
const cls = v >= 715
? "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/20"
? "bg-[var(--status-online-bg)] text-[var(--status-online-fg)] border-current/20"
: v >= 710
? "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20"
: "bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/20"
? "bg-[var(--status-degraded-bg)] text-[var(--status-degraded-fg)] border-current/20"
: "bg-[var(--status-offline-bg)] text-[var(--status-offline-fg)] border-current/20"
return (
<span className={cn("text-xs font-mono border rounded px-2 py-0.5", cls)}>
{os}
@@ -390,8 +396,10 @@ export default function ServersPage() {
useEffect(() => {
if (mode !== "live") {
setServerList(initialServers)
setBackendOk(false)
queueMicrotask(() => {
setServerList(initialServers)
setBackendOk(false)
})
return
}
apiFetch<BackendServer[]>("/api/servers")
@@ -437,6 +445,11 @@ export default function ServersPage() {
port: String(full.port ?? 443),
proto: full.useSsl ? "https" : "http",
verifySsl: full.verifySsl ?? false,
lanSubnet: full.lanSubnet ?? prev.lanSubnet,
wanUplinks:
Array.isArray(full.wanUplinks) && full.wanUplinks.length
? JSON.parse(JSON.stringify(full.wanUplinks))
: prev.wanUplinks,
}))
} catch {
// ignore — user can fill in manually
@@ -459,6 +472,8 @@ export default function ServersPage() {
asn: form.asn,
comment: form.comment,
enabled: form.enabled,
lanSubnet: form.lanSubnet.trim(),
wanUplinks: form.type === "home-router" ? form.wanUplinks : [],
}
if (isLive) {
@@ -484,9 +499,19 @@ export default function ServersPage() {
if (sheetMode === "edit" && editingId) {
setServerList(list => list.map(s =>
s.id === editingId
? { ...s, name: form.name, type: form.type, site: form.site, country: form.country,
host: form.host, asn: form.asn, comment: form.comment,
status: form.enabled ? (s.status === "offline" ? "online" : s.status) : "offline" }
? {
...s,
name: form.name,
type: form.type,
site: form.site,
country: form.country,
host: form.host,
asn: form.asn,
comment: form.comment,
lanSubnet: form.lanSubnet || undefined,
wanUplinks: form.type === "home-router" ? form.wanUplinks : undefined,
status: form.enabled ? (s.status === "offline" ? "online" : s.status) : "offline",
}
: s
))
} else {
@@ -497,6 +522,8 @@ export default function ServersPage() {
status: form.enabled ? "online" : "offline",
enabled: form.enabled, latency: null, sessions: 0,
comment: form.comment || undefined,
lanSubnet: form.lanSubnet || undefined,
wanUplinks: form.type === "home-router" ? form.wanUplinks : undefined,
}])
}
}
@@ -764,7 +791,7 @@ export default function ServersPage() {
</td>
<td className={cn("px-4 py-3 font-mono text-right text-sm",
s.latency == null ? "text-muted-foreground"
: s.latency > 60 ? "text-amber-500" : "")}>
: s.latency > 60 ? "text-[var(--status-degraded-fg)]" : "")}>
{s.latency == null ? "—" : `${s.latency} мс`}
</td>
<td className="px-4 py-3"><StatusBadge status={s.status} /></td>
@@ -1068,12 +1095,14 @@ export default function ServersPage() {
: <><WifiIcon className="size-4" />Проверить подключение</>}
</Button>
{testState === "ok" && (
<div className="flex items-start gap-2 rounded-md border border-emerald-500/30 bg-emerald-500/10 px-3 py-2 text-xs text-emerald-400">
<div className="flex items-start gap-2 rounded-md border border-current/25 px-3 py-2 text-xs"
style={{ background: "var(--status-online-bg)", color: "var(--status-online-fg)" }}>
<CheckCircleIcon className="size-3.5 shrink-0 mt-0.5" /><span>{testMsg}</span>
</div>
)}
{testState === "error" && (
<div className="flex items-start gap-2 rounded-md border border-red-500/30 bg-red-500/10 px-3 py-2 text-xs text-red-400">
<div className="flex items-start gap-2 rounded-md border border-current/25 px-3 py-2 text-xs"
style={{ background: "var(--status-offline-bg)", color: "var(--status-offline-fg)" }}>
<XCircleIcon className="size-3.5 shrink-0 mt-0.5" /><span>{testMsg}</span>
</div>
)}