Show each server's Forgetting version in the panel
- awg-agent sends its install's VERSION (from the CLI's env, else the project's .env) in the auth frame; the hub keeps it in nodes.version (added by migration), so an offline node still shows the last one. - GET /ui/nodes returns version per server (0 is the panel's own) and the panel's version at the top. - ServerBar: a vX.Y.Z chip next to the AWG generation. A node older or newer than the panel, or one that sends no version (agent before 0.3.4, shown as v?.?.?), gets a warm chip with an "update it" tooltip. - App.css: tertiary-container tokens from design.md (light) and a warm dark pair; they did not exist yet. - Mock server: panel 0.3.3, Frankfurt behind on 0.3.2, Amsterdam with no version.
This commit is contained in:
@@ -25,6 +25,8 @@ html, body, #root { height: 100%; }
|
||||
--neutral-bg: #e4dac9; --neutral-text: #7a6a55; --neutral-dot: #a99a85;
|
||||
--error-bg: #ffdad6; --error-text: #410002; --error-dot: #ba1a1a;
|
||||
--warn-dot: #e08a2c;
|
||||
--tertiary-container: #ffdcad; /* design.md: tertiary-container */
|
||||
--on-tertiary-container: #7a4a14;
|
||||
|
||||
--danger-border: #e6c4bc;
|
||||
--danger-text: #b34a3a;
|
||||
@@ -67,6 +69,8 @@ html, body, #root { height: 100%; }
|
||||
--neutral-bg: #34291c; --neutral-text: #d0c0a4; --neutral-dot: #b0a085;
|
||||
--error-bg: #5c1414; --error-text: #ffb4ab; --error-dot: #ff5449;
|
||||
--warn-dot: #ffb76b;
|
||||
--tertiary-container: #4a3218; /* тёплый тёмный, как у статусов в тёмной теме */
|
||||
--on-tertiary-container: #ffdcad;
|
||||
|
||||
--danger-border: #6e4540;
|
||||
--danger-text: #ffb4ab;
|
||||
@@ -449,6 +453,16 @@ body {
|
||||
line-height: 1.2;
|
||||
}
|
||||
.server-gen-wrap { align-self: flex-start; margin-top: 2px; }
|
||||
.server-chips { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 2px; }
|
||||
.server-chips .server-gen-wrap, .server-chips .server-gen-chip { margin-top: 0; }
|
||||
.server-ver-chip {
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
padding: 2px 8px;
|
||||
background: var(--neutral-bg);
|
||||
color: var(--neutral-text);
|
||||
}
|
||||
.server-ver-chip--warn { background: var(--tertiary-container); color: var(--on-tertiary-container); }
|
||||
.server-gen-chip {
|
||||
align-self: flex-start;
|
||||
margin-top: 2px;
|
||||
|
||||
@@ -23,6 +23,7 @@ export default function App() {
|
||||
const [statusText, setStatusText] = useState('');
|
||||
const [nodes, setNodes] = useState<NodeInfo[]>([]);
|
||||
const [hubEnabled, setHubEnabled] = useState(false);
|
||||
const [panelVersion, setPanelVersion] = useState<string | null>(null);
|
||||
const [serverId, setServerIdState] = useState<number | null>(null);
|
||||
const [modal, setModal] = useState<{ node?: NodeInfo } | null>(null);
|
||||
const [msg, setMsg] = useState('');
|
||||
@@ -55,6 +56,7 @@ export default function App() {
|
||||
const list: NodeInfo[] = data.nodes ?? [];
|
||||
setNodes(list);
|
||||
setHubEnabled(!!data.hub);
|
||||
setPanelVersion(data.version ?? null);
|
||||
const cur = getServerId();
|
||||
if (cur !== null && list.some(n => n.id === cur)) return;
|
||||
let saved: number | null = null;
|
||||
@@ -311,6 +313,7 @@ export default function App() {
|
||||
nodes={nodes}
|
||||
serverId={serverId}
|
||||
hubEnabled={hubEnabled}
|
||||
panelVersion={panelVersion}
|
||||
onSelect={pickServer}
|
||||
onAdd={() => setModal({})}
|
||||
onJoin={n => setModal({ node: n })}
|
||||
|
||||
@@ -1,17 +1,39 @@
|
||||
// Copyright (c) 2026 Ivan Vasilev
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
import { type NodeInfo, genLabel } from '../lib/shared';
|
||||
import { type NodeInfo, genLabel, cmpVersion } from '../lib/shared';
|
||||
import { IcoRefresh, IcoPlus, IcoTrash, IcoCopy } from './icons';
|
||||
|
||||
// Сервер-бар: карточки серверов (core-локальный и ноды) + «Добавить сервер» и
|
||||
// (на мобильной) перезапуск AWG под ними. Общий для вкладок — показывает
|
||||
// серверы, между которыми переключается содержимое вкладки (пиры / абоненты).
|
||||
// Версия Forgetting на сервере. Нода, которая отстаёт от панели или не сообщает версию
|
||||
// (агент старше 0.3.4), подсвечена: её стоит обновить — install.sh, «Обновить».
|
||||
function versionChip(n: NodeInfo, panel: string | null) {
|
||||
if (n.pending) return null;
|
||||
let tip = '', warn = false;
|
||||
if (!n.version) {
|
||||
warn = true;
|
||||
tip = 'Версия неизвестна: нода старше 0.3.4 — обнови её (install.sh → «Обновить»)';
|
||||
} else if (panel && !n.local) {
|
||||
const d = cmpVersion(n.version, panel);
|
||||
if (d < 0) { warn = true; tip = `Панель на ${panel} — обнови ноду (install.sh → «Обновить»)`; }
|
||||
else if (d > 0) { warn = true; tip = `Нода новее панели (${panel}) — обнови панель`; }
|
||||
}
|
||||
const chip = (
|
||||
<span className={`chip server-ver-chip${warn ? ' server-ver-chip--warn' : ''}`}>
|
||||
{n.version ? `v${n.version}` : 'v?.?.?'}
|
||||
</span>
|
||||
);
|
||||
return tip ? <span className="tip-wrap server-gen-wrap" data-tip={tip}>{chip}</span> : chip;
|
||||
}
|
||||
|
||||
export default function ServerBar({
|
||||
nodes, serverId, hubEnabled, onSelect, onAdd, onJoin, onDelete,
|
||||
nodes, serverId, hubEnabled, panelVersion, onSelect, onAdd, onJoin, onDelete,
|
||||
onRestartAwg, restarting, showRestart = true,
|
||||
}: {
|
||||
nodes: NodeInfo[];
|
||||
panelVersion: string | null;
|
||||
serverId: number | null;
|
||||
hubEnabled: boolean;
|
||||
onSelect: (id: number) => void;
|
||||
@@ -52,9 +74,12 @@ export default function ServerBar({
|
||||
) : !n.online ? (
|
||||
<span className="server-card-ip">нет связи</span>
|
||||
) : h && (
|
||||
<>
|
||||
<span className="server-card-ip">{h.ip}</span>
|
||||
{(h.module || h.tools) ? (
|
||||
<span className="server-card-ip">{h.ip}</span>
|
||||
)}
|
||||
{/* Плашки: поколение AWG (только на связи) и версия Forgetting (известна и без связи) */}
|
||||
{!n.pending && (
|
||||
<span className="server-chips">
|
||||
{h && n.online && ((h.module || h.tools) ? (
|
||||
<span
|
||||
className="tip-wrap server-gen-wrap"
|
||||
data-tip={`модуль ${h.module || '?'} · tools ${h.tools || '?'}`}
|
||||
@@ -63,8 +88,9 @@ export default function ServerBar({
|
||||
</span>
|
||||
) : (
|
||||
<span className="chip chip--offline server-gen-chip">{genLabel(h.gen)}</span>
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
{versionChip(n, panelVersion)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{h && n.online && <span className="server-card-peers">{h.peers} peers</span>}
|
||||
|
||||
@@ -60,9 +60,20 @@ export interface NodeInfo {
|
||||
online: boolean;
|
||||
pending: boolean; // нода создана, но ещё ни разу не подключалась
|
||||
last_seen: number | null;
|
||||
version: string | null; // версия Forgetting на сервере; null — нода ещё не подключалась или агент старше 0.3.4
|
||||
health: { server: string; ip: string; gen: AwgGen; peers: number; awg_up: boolean; module: string; tools: string } | null;
|
||||
}
|
||||
|
||||
// Сравнение версий вида 0.3.3 (хвост после «-» не учитывается): <0 — a старше b.
|
||||
export function cmpVersion(a: string, b: string): number {
|
||||
const pa = a.split('-')[0].split('.').map(Number), pb = b.split('-')[0].split('.').map(Number);
|
||||
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
||||
const d = (pa[i] || 0) - (pb[i] || 0);
|
||||
if (d) return d;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Выбранный сервер. Вкладки зовут apiFetch без явного id — заголовок X-Server-Id
|
||||
// подставляется здесь; App ставит значение до смены key у вкладки, поэтому та
|
||||
// перечитывает данные уже с новым сервером.
|
||||
|
||||
Reference in New Issue
Block a user