Add shaping, Prometheus, monitor tab and peer audit

Baseline TypeScript version before the Go rewrite: CAKE fair sharing with
automatic deprioritisation of heavy devices, per-key priority, /metrics with
a metrics token, the built-in Prometheus install/remove, the Monitor tab,
grouped sidebar tabs and the dead-peer audit.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 15:27:05 +03:00
co-authored by Claude Sonnet 5.5
parent 7fbff82e76
commit e9df363dd2
33 changed files with 2708 additions and 49 deletions
+27 -9
View File
@@ -11,7 +11,8 @@ import {
import { IcoMenu, IcoLogout, IcoSun, IcoMoon, IcoRefresh } from './components/icons';
import ServerBar from './components/ServerBar';
import AddServerModal from './components/AddServerModal';
import { TABS } from './tabs';
import ServerSettingsModal from './components/ServerSettingsModal';
import { TABS, TABS_BY_GROUP } from './tabs';
// Оболочка приложения: логин, тема, drawer/сайдбар, общая сессия (token,
// список серверов и выбранный сервер, снэкбар) и переключение вкладок. Контент каждой вкладки приходит
@@ -26,6 +27,7 @@ export default function App() {
const [panelVersion, setPanelVersion] = useState<string | null>(null);
const [serverId, setServerIdState] = useState<number | null>(null);
const [modal, setModal] = useState<{ node?: NodeInfo } | null>(null);
const [settingsFor, setSettingsFor] = useState<NodeInfo | null>(null);
const [msg, setMsg] = useState('');
const [drawerOpen, setDrawerOpen] = useState(false);
const [activeTab, setActiveTab] = useState(TABS[0].id);
@@ -288,14 +290,19 @@ export default function App() {
</span>
</div>
<hr className="drawer-divider" />
{TABS.map(t => (
<div
key={t.id}
className={`nav-item${t.id === activeTab ? ' nav-item--active' : ''}`}
onClick={() => { setActiveTab(t.id); setDrawerOpen(false); }}
>
<t.Icon /> {t.label}
</div>
{TABS_BY_GROUP.map(g => (
<nav key={g.id} className="nav-group" aria-label={g.label}>
<span className="nav-group-title">{g.label}</span>
{g.tabs.map(t => (
<div
key={t.id}
className={`nav-item${t.id === activeTab ? ' nav-item--active' : ''}`}
onClick={() => { setActiveTab(t.id); setDrawerOpen(false); }}
>
<t.Icon /> {t.label}
</div>
))}
</nav>
))}
<div className="sidebar-spacer" />
@@ -318,6 +325,8 @@ export default function App() {
onAdd={() => setModal({})}
onJoin={n => setModal({ node: n })}
onDelete={deleteNode}
// Сначала выбираем сервер: запросы модалки идут с X-Server-Id выбранного
onSettings={n => { pickServer(n.id); setSettingsFor(n); }}
onRestartAwg={restartAwg}
restarting={restarting}
showRestart={showRestart}
@@ -346,6 +355,15 @@ export default function App() {
/>
)}
{settingsFor && (
<ServerSettingsModal
token={token}
node={settingsFor}
onClose={() => setSettingsFor(null)}
showMsg={showMsg}
/>
)}
{msg && <div className="snack">{msg}</div>}
</div>
);