Полная зачистка legacy + закрытие fix24.1/fix24.2 + обновление логотипа
This commit is contained in:
+713
@@ -0,0 +1,713 @@
|
||||
Да, это логичное продолжение: `Account Info` в текущей архитектуре надо не “улучшать”, а удалить как концепцию. После разделения `admin_user` и `peer` эта страница теряет смысл: админ — это оператор панели, а не клиентский peer с квотой, Node URL и QR. Правильная замена — единый стартовый `Dashboard`, куда переезжают системный мониторинг, состояние Hysteria2, агрегированная статистика peer’ов, трафик и security notices.
|
||||
|
||||
Ниже — дополнение к плану выше.
|
||||
|
||||
---
|
||||
|
||||
## 9. Заменить `Account Info` + `System Monitor` на полноценный Dashboard
|
||||
|
||||
Сейчас есть две слабые страницы:
|
||||
|
||||
`views/info/account/index.vue` — фактически профиль текущего `account`, но там одновременно greeting, create time, quota/download/upload/expire time, subscription buttons, QR и security notifications.
|
||||
|
||||
`views/monitor/system/index.vue` — набор карточек без истории: версия панели, CPU, RAM, disk, версия Hysteria2, статус, online users/devices.
|
||||
|
||||
После разделения админов и peer’ов это должно стать так:
|
||||
|
||||
```text
|
||||
/dashboard
|
||||
├─ общая сводка панели
|
||||
├─ состояние Hysteria2
|
||||
├─ общий трафик всех peer’ов
|
||||
├─ графики CPU/RAM/Disk
|
||||
├─ online peers/devices
|
||||
├─ top peers by traffic
|
||||
├─ security warnings
|
||||
└─ быстрые действия оператора
|
||||
```
|
||||
|
||||
`Account Info` удалить из меню. Профиль админа оставить в dropdown справа сверху: “Profile”, “Change password”, “Logout”. Peer-информация, Node URL и QR должны жить только в `Peer Detail / Peer Drawer`, а не в профиле администратора.
|
||||
|
||||
---
|
||||
|
||||
## 10. Что именно не так в текущей реализации
|
||||
|
||||
Текущий `Account Info` вызывает `getAccountApi({ id: accountStore.id })`. Пока админ и peer лежат в одной таблице, это случайно работает. После нормального разделения это станет архитектурной ошибкой: admin ID не должен использоваться как peer ID.
|
||||
|
||||
Кнопки `Subscribe`, `Subscribe QR`, `Node URL`, `Node QR Code` на странице админа надо убрать полностью. Они допустимы только для конкретного peer’а. Сейчас из-за смешанной модели админ может выглядеть как клиент подключения.
|
||||
|
||||
`System Monitor` сейчас делает два независимых запроса: `/monitor/monitorSystem` и `/monitor/monitorHysteria2`. Для дашборда это лучше заменить одним агрегирующим endpoint’ом, иначе UI будет собирать бизнес-смысл из разных API.
|
||||
|
||||
`MonitorSystem()` каждый раз делает live-запросы к CPU/RAM/Disk. CPU-сэмпл через `cpu.Percent(time.Second, false)` блокирует примерно на секунду. Для интерактивного Dashboard это плохая модель: страницу могут открыть несколько админов, и каждый начнёт дергать системные метрики.
|
||||
|
||||
`GetDiskPercent()` берёт первый partition из `disk.Partitions(true)`. Это ненадёжно: первым может оказаться не root/data partition. Для панели нужно мониторить `/`, data-dir панели и, если нужно, mountpoint с Hysteria2 config/logs.
|
||||
|
||||
`MonitorHysteria2()` каждый раз выполняет `hysteria2 version`. Версию бинарника не надо получать на каждый render дашборда. Её можно кешировать и обновлять редко: при старте, после смены версии, по ручному refresh или раз в несколько минут.
|
||||
|
||||
---
|
||||
|
||||
## 11. Новый backend-модуль: `dashboard`
|
||||
|
||||
Добавить отдельный модуль, а не расширять `monitor.go`.
|
||||
|
||||
```text
|
||||
controller/dashboard.go
|
||||
service/dashboard.go
|
||||
service/metrics_collector.go
|
||||
dao/dashboard.go
|
||||
model/vo/dashboard.go
|
||||
router/dashboard.go
|
||||
```
|
||||
|
||||
Минимальный набор API:
|
||||
|
||||
```text
|
||||
GET /hui/dashboard/summary
|
||||
GET /hui/dashboard/timeseries?range=1h&step=1m
|
||||
GET /hui/dashboard/top-peers?range=24h&limit=10
|
||||
GET /hui/dashboard/security
|
||||
```
|
||||
|
||||
Можно начать с одного endpoint’а:
|
||||
|
||||
```text
|
||||
GET /hui/dashboard
|
||||
```
|
||||
|
||||
Но лучше сразу разделить summary и timeseries. Summary обновляется часто, графики можно обновлять реже.
|
||||
|
||||
Целевой response для summary:
|
||||
|
||||
```go
|
||||
type DashboardSummaryVo struct {
|
||||
CollectedAt int64 `json:"collectedAt"`
|
||||
|
||||
System SystemSummaryVo `json:"system"`
|
||||
Hysteria HysteriaSummaryVo `json:"hysteria"`
|
||||
Peers PeerSummaryVo `json:"peers"`
|
||||
Traffic TrafficSummaryVo `json:"traffic"`
|
||||
|
||||
SecurityRisks []SecurityRiskVo `json:"securityRisks"`
|
||||
}
|
||||
|
||||
type SystemSummaryVo struct {
|
||||
CpuPercent float64 `json:"cpuPercent"`
|
||||
MemUsedBytes uint64 `json:"memUsedBytes"`
|
||||
MemTotalBytes uint64 `json:"memTotalBytes"`
|
||||
MemPercent float64 `json:"memPercent"`
|
||||
DiskUsedBytes uint64 `json:"diskUsedBytes"`
|
||||
DiskTotalBytes uint64 `json:"diskTotalBytes"`
|
||||
DiskPercent float64 `json:"diskPercent"`
|
||||
UptimeSeconds uint64 `json:"uptimeSeconds"`
|
||||
Load1 float64 `json:"load1"`
|
||||
}
|
||||
|
||||
type HysteriaSummaryVo struct {
|
||||
Version string `json:"version"`
|
||||
Running bool `json:"running"`
|
||||
ApiReachable bool `json:"apiReachable"`
|
||||
LastApiError string `json:"lastApiError,omitempty"`
|
||||
}
|
||||
|
||||
type PeerSummaryVo struct {
|
||||
Total int64 `json:"total"`
|
||||
Enabled int64 `json:"enabled"`
|
||||
Disabled int64 `json:"disabled"`
|
||||
Expired int64 `json:"expired"`
|
||||
OnlinePeers int64 `json:"onlinePeers"`
|
||||
OnlineDevices int64 `json:"onlineDevices"`
|
||||
}
|
||||
|
||||
type TrafficSummaryVo struct {
|
||||
DownloadBytes int64 `json:"downloadBytes"`
|
||||
UploadBytes int64 `json:"uploadBytes"`
|
||||
TotalBytes int64 `json:"totalBytes"`
|
||||
TodayDownloadBytes int64 `json:"todayDownloadBytes"`
|
||||
TodayUploadBytes int64 `json:"todayUploadBytes"`
|
||||
SinceResetDownloadBytes int64 `json:"sinceResetDownloadBytes"`
|
||||
SinceResetUploadBytes int64 `json:"sinceResetUploadBytes"`
|
||||
}
|
||||
```
|
||||
|
||||
Важно: после разделения таблиц все peer-агрегации идут только из `peer`, не из `admin_user`.
|
||||
|
||||
---
|
||||
|
||||
## 12. Отдельный collector вместо live-сбора на каждый HTTP-запрос
|
||||
|
||||
Dashboard не должен каждый раз сам опрашивать систему и Hysteria2 API. Нужен background collector.
|
||||
|
||||
```text
|
||||
MetricsCollector
|
||||
├─ каждые 5–10 секунд: CPU/RAM/load/uptime
|
||||
├─ каждые 30–60 секунд: disk
|
||||
├─ каждые HYSTERIA2_TRAFFIC_TIME секунд: Hysteria2 traffic
|
||||
├─ каждые 5–15 секунд: Hysteria2 online
|
||||
└─ редко: Hysteria2 version
|
||||
```
|
||||
|
||||
HTTP endpoint читает готовый snapshot из памяти и, если нужно, последние точки из SQLite. Это решает сразу три проблемы: быстрый UI, меньше нагрузки, стабильная история для графиков.
|
||||
|
||||
Пример внутренней модели:
|
||||
|
||||
```go
|
||||
type MetricsSnapshot struct {
|
||||
CollectedAt time.Time
|
||||
System SystemMetrics
|
||||
Hysteria HysteriaRuntimeMetrics
|
||||
Online map[string]int64
|
||||
}
|
||||
```
|
||||
|
||||
Для защиты от зависаний все внешние операции — с timeout и context. Hysteria2 API уже вызывается с timeout, это надо сохранить.
|
||||
|
||||
---
|
||||
|
||||
## 13. Исправить учёт трафика перед графиками
|
||||
|
||||
Это критично.
|
||||
|
||||
В текущем `saveAccountTraffic()` берётся `/traffic?clear=1`, после чего `rx/tx` умножаются на `HYSTERIA2_TRAFFIC_TIME`. По документации Hysteria2 `/traffic` возвращает traffic statistics по клиентам, а параметр `clear=1` обнуляет статистику после возврата. То есть эти значения надо трактовать как дельту с прошлого clear, а не как rate, который нужно умножать на интервал. Иначе трафик будет завышаться. ([v2.hysteria.network][1])
|
||||
|
||||
Нормальный pipeline:
|
||||
|
||||
```text
|
||||
1. collector вызывает /traffic?clear=1
|
||||
2. получает rx/tx bytes по auth/client id
|
||||
3. сохраняет immutable traffic_sample
|
||||
4. атомарно инкрементит peer.download_bytes / peer.upload_bytes
|
||||
5. обновляет aggregate таблицы для dashboard
|
||||
```
|
||||
|
||||
Новая таблица:
|
||||
|
||||
```sql
|
||||
CREATE TABLE traffic_sample (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
peer_id INTEGER NOT NULL,
|
||||
auth_id TEXT NOT NULL,
|
||||
rx_bytes INTEGER NOT NULL DEFAULT 0,
|
||||
tx_bytes INTEGER NOT NULL DEFAULT 0,
|
||||
sampled_at INTEGER NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_traffic_sample_time ON traffic_sample(sampled_at);
|
||||
CREATE INDEX idx_traffic_sample_peer_time ON traffic_sample(peer_id, sampled_at);
|
||||
```
|
||||
|
||||
Для быстрых графиков и top peers добавить агрегаты:
|
||||
|
||||
```sql
|
||||
CREATE TABLE traffic_aggregate_hourly (
|
||||
peer_id INTEGER NOT NULL,
|
||||
hour_start INTEGER NOT NULL,
|
||||
rx_bytes INTEGER NOT NULL DEFAULT 0,
|
||||
tx_bytes INTEGER NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(peer_id, hour_start)
|
||||
);
|
||||
|
||||
CREATE TABLE traffic_aggregate_daily (
|
||||
peer_id INTEGER NOT NULL,
|
||||
day_start INTEGER NOT NULL,
|
||||
rx_bytes INTEGER NOT NULL DEFAULT 0,
|
||||
tx_bytes INTEGER NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(peer_id, day_start)
|
||||
);
|
||||
```
|
||||
|
||||
Сброс трафика peer’а не должен удалять историю. Он должен сбрасывать только счётчики quota-period:
|
||||
|
||||
```text
|
||||
peer.download_bytes = 0
|
||||
peer.upload_bytes = 0
|
||||
peer.traffic_reset_at = now
|
||||
```
|
||||
|
||||
Иначе после reset dashboard потеряет исторические графики. В UI надо явно различать:
|
||||
|
||||
```text
|
||||
Traffic since reset
|
||||
Traffic today
|
||||
Traffic last 24h
|
||||
Traffic total tracked
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 14. Online users/devices: переименовать и использовать корректно
|
||||
|
||||
Hysteria2 `/online` возвращает map client ID → количество client instances. В документации отдельно указано, что это именно количество клиентских экземпляров, то есть “devices”, а не количество активных proxy-соединений. ([v2.hysteria.network][1])
|
||||
|
||||
Поэтому в Dashboard надо писать:
|
||||
|
||||
```text
|
||||
Online peers
|
||||
Online devices
|
||||
```
|
||||
|
||||
Не “online users”, если в новой модели сущность называется `peer`.
|
||||
|
||||
Расчёт:
|
||||
|
||||
```text
|
||||
onlinePeers = len(onlineMap)
|
||||
onlineDevices = sum(onlineMap values)
|
||||
```
|
||||
|
||||
После миграции ключом должен быть `peer.auth_id` или `peer.name`, но не admin username. Главное — не завязываться на таблицу админов.
|
||||
|
||||
---
|
||||
|
||||
## 15. Security warnings перенести из уведомлений в Dashboard
|
||||
|
||||
Сейчас `Account Info` показывает всплывающие `ElNotification`: default password и no HTTPS. Это раздражает, перекрывает интерфейс и не масштабируется.
|
||||
|
||||
На Dashboard сделать постоянный блок сверху:
|
||||
|
||||
```text
|
||||
Security Center
|
||||
⚠ Default admin password / force password change
|
||||
⚠ Panel is served without HTTPS
|
||||
⚠ Hysteria2 Traffic Stats API is not protected
|
||||
⚠ Hysteria2 is stopped
|
||||
⚠ Public endpoint env is not configured
|
||||
```
|
||||
|
||||
Для Traffic Stats API это особенно важно: Hysteria2 docs прямо рекомендуют задавать `secret`, иначе любой, кто имеет доступ к API listen address, сможет смотреть traffic stats и kick users. ([v2.hysteria.network][2])
|
||||
|
||||
Backend должен отдавать structured warnings:
|
||||
|
||||
```go
|
||||
type SecurityRiskVo struct {
|
||||
Key string `json:"key"` // dashboard.security.noHttps
|
||||
Severity string `json:"severity"` // info | warning | critical
|
||||
ActionRoute string `json:"actionRoute,omitempty"`
|
||||
Dismissible bool `json:"dismissible"`
|
||||
}
|
||||
```
|
||||
|
||||
Frontend переводит `key` через i18n. Никаких HTML-строк из backend. Сейчас `dangerouslyUseHTMLString` лучше убрать.
|
||||
|
||||
---
|
||||
|
||||
## 16. Новый frontend layout Dashboard
|
||||
|
||||
Создать:
|
||||
|
||||
```text
|
||||
src/views/dashboard/index.vue
|
||||
src/api/dashboard/index.ts
|
||||
src/api/dashboard/types.ts
|
||||
src/views/dashboard/components/MetricCard.vue
|
||||
src/views/dashboard/components/SecurityAlerts.vue
|
||||
src/views/dashboard/components/SystemChart.vue
|
||||
src/views/dashboard/components/TrafficChart.vue
|
||||
src/views/dashboard/components/TopPeers.vue
|
||||
src/views/dashboard/components/HysteriaStatus.vue
|
||||
```
|
||||
|
||||
Структура страницы:
|
||||
|
||||
```text
|
||||
[Security alerts, если есть]
|
||||
|
||||
[Hysteria2 status] [Online peers] [Online devices] [Traffic today]
|
||||
[CPU] [Memory] [Disk] [Total peers]
|
||||
|
||||
[Traffic chart: download/upload]
|
||||
[System chart: CPU/RAM]
|
||||
[Top peers by traffic]
|
||||
[Recent peer activity / last connections]
|
||||
```
|
||||
|
||||
Первый экран должен отвечать на вопросы оператора:
|
||||
|
||||
```text
|
||||
Сервис работает?
|
||||
Есть ли пользователи онлайн?
|
||||
Сколько трафика прошло?
|
||||
Есть ли перегруз CPU/RAM/Disk?
|
||||
Кто больше всего потребляет?
|
||||
Есть ли security/config warnings?
|
||||
```
|
||||
|
||||
То, что сейчас на `System Monitor`, можно сохранить как часть Dashboard, но карточки надо сделать полезнее:
|
||||
|
||||
```text
|
||||
CPU: 14.2%, load1 0.23
|
||||
Memory: 1.2 GB / 13.7 GB, 8.7%
|
||||
Disk: 11.5 GB / 40 GB, 28.8%
|
||||
Hysteria2: Running, v2.8.2
|
||||
```
|
||||
|
||||
Не только проценты.
|
||||
|
||||
---
|
||||
|
||||
## 17. Графики: как сделать без костылей
|
||||
|
||||
В проекте сейчас нет chart-библиотеки. Есть два нормальных варианта.
|
||||
|
||||
Вариант A, практичный: добавить ECharts. Для админ-панелей это стандартное решение: line/area charts, tooltip, resize, нормальная работа с time-series. Компоненты графиков лучше lazy-load’ить, чтобы не раздувать initial bundle.
|
||||
|
||||
Вариант B, минимальный: написать `Sparkline.vue` на SVG для CPU/RAM и traffic. Меньше зависимостей, но хуже tooltip, zoom, диапазоны и легенды.
|
||||
|
||||
Для твоего кейса я бы выбрал ECharts, но не тащил бы его во все страницы. Только Dashboard chunk.
|
||||
|
||||
Графики:
|
||||
|
||||
```text
|
||||
TrafficChart:
|
||||
series: download, upload
|
||||
ranges: 1h / 24h / 7d / 30d
|
||||
source: traffic_aggregate_hourly/daily
|
||||
|
||||
SystemChart:
|
||||
series: cpuPercent, memPercent
|
||||
ranges: 1h / 6h / 24h
|
||||
source: metric_sample
|
||||
```
|
||||
|
||||
Disk не нужно рисовать как частый line chart. Disk меняется медленно; достаточно карточки и, максимум, daily trend.
|
||||
|
||||
---
|
||||
|
||||
## 18. Таблица системных метрик
|
||||
|
||||
Добавить таблицу:
|
||||
|
||||
```sql
|
||||
CREATE TABLE metric_sample (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
sampled_at INTEGER NOT NULL,
|
||||
|
||||
cpu_percent REAL NOT NULL DEFAULT 0,
|
||||
load1 REAL NOT NULL DEFAULT 0,
|
||||
|
||||
mem_used_bytes INTEGER NOT NULL DEFAULT 0,
|
||||
mem_total_bytes INTEGER NOT NULL DEFAULT 0,
|
||||
mem_percent REAL NOT NULL DEFAULT 0,
|
||||
|
||||
disk_path TEXT NOT NULL DEFAULT '/',
|
||||
disk_used_bytes INTEGER NOT NULL DEFAULT 0,
|
||||
disk_total_bytes INTEGER NOT NULL DEFAULT 0,
|
||||
disk_percent REAL NOT NULL DEFAULT 0,
|
||||
|
||||
hysteria_running INTEGER NOT NULL DEFAULT 0,
|
||||
online_peers INTEGER NOT NULL DEFAULT 0,
|
||||
online_devices INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE INDEX idx_metric_sample_time ON metric_sample(sampled_at);
|
||||
```
|
||||
|
||||
Retention:
|
||||
|
||||
```text
|
||||
raw metric_sample: 24–72 часа
|
||||
minute aggregate: 7 дней
|
||||
hour aggregate: 30–90 дней
|
||||
daily aggregate: можно хранить дольше
|
||||
```
|
||||
|
||||
SQLite нормально выдержит такие объёмы, если не писать каждую секунду и не хранить бесконечную raw-историю.
|
||||
|
||||
---
|
||||
|
||||
## 19. Роутинг и меню
|
||||
|
||||
Текущее:
|
||||
|
||||
```text
|
||||
/ -> /info/account
|
||||
/info/account
|
||||
/monitor/system
|
||||
```
|
||||
|
||||
Целевое:
|
||||
|
||||
```text
|
||||
/ -> /dashboard
|
||||
/dashboard
|
||||
/peers
|
||||
/hysteria
|
||||
/config
|
||||
/log/system
|
||||
/log/hysteria
|
||||
```
|
||||
|
||||
В `router/index.ts`:
|
||||
|
||||
```ts
|
||||
{
|
||||
path: "/",
|
||||
component: Layout,
|
||||
redirect: "/dashboard",
|
||||
children: [...]
|
||||
}
|
||||
```
|
||||
|
||||
Новый route:
|
||||
|
||||
```ts
|
||||
{
|
||||
path: "/dashboard",
|
||||
component: "Layout",
|
||||
redirect: "/dashboard/index",
|
||||
name: "Dashboard",
|
||||
meta: {
|
||||
title: "dashboard",
|
||||
icon: "dashboard",
|
||||
roles: ["admin"],
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "index",
|
||||
component: "dashboard/index",
|
||||
name: "DashboardIndex",
|
||||
meta: {
|
||||
title: "dashboard",
|
||||
icon: "dashboard",
|
||||
roles: ["admin"],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
`Info` route удалить. `Monitor` route либо удалить, либо временно оставить redirect на `/dashboard/index` на один релиз.
|
||||
|
||||
---
|
||||
|
||||
## 20. Что делать с бывшим `Account Info`
|
||||
|
||||
Разложить по новым местам.
|
||||
|
||||
Greeting и create time админа:
|
||||
|
||||
```text
|
||||
navbar dropdown / admin profile
|
||||
```
|
||||
|
||||
Security warnings:
|
||||
|
||||
```text
|
||||
dashboard Security Center
|
||||
```
|
||||
|
||||
Quota/download/upload/expire:
|
||||
|
||||
```text
|
||||
peer drawer
|
||||
peer list compact cards
|
||||
dashboard aggregate traffic
|
||||
```
|
||||
|
||||
Subscribe/QR/Node URL:
|
||||
|
||||
```text
|
||||
peer detail только для конкретного peer
|
||||
```
|
||||
|
||||
Default password warning:
|
||||
|
||||
```text
|
||||
dashboard + forced change-password flow
|
||||
```
|
||||
|
||||
HTTPS warning:
|
||||
|
||||
```text
|
||||
dashboard security warning
|
||||
```
|
||||
|
||||
Так исчезает главный conceptual bug: админ больше не выглядит как peer.
|
||||
|
||||
---
|
||||
|
||||
## 21. Быстрые действия на Dashboard
|
||||
|
||||
Добавить только operator actions, не peer actions:
|
||||
|
||||
```text
|
||||
Restart Hysteria2
|
||||
Open Hysteria config
|
||||
Open peers
|
||||
Open logs
|
||||
Refresh metrics
|
||||
```
|
||||
|
||||
Не добавлять на dashboard “Create peer” как главную кнопку, если страница перегружена. Лучше маленькая secondary action в блоке peers.
|
||||
|
||||
Для dangerous actions — только confirm dialog:
|
||||
|
||||
```text
|
||||
Restart Hysteria2
|
||||
Reset all traffic
|
||||
```
|
||||
|
||||
`Reset all traffic` я бы вообще не держал на Dashboard. Это административная операция, ей место в settings или peer management.
|
||||
|
||||
---
|
||||
|
||||
## 22. Backend queries для агрегатов peer’ов
|
||||
|
||||
После split-модели нужны DAO-методы:
|
||||
|
||||
```go
|
||||
CountPeersByStatus(now int64) (PeerSummary, error)
|
||||
SumPeerTrafficSinceReset() (TrafficSummary, error)
|
||||
TopPeersByTraffic(from, to int64, limit int) ([]TopPeerVo, error)
|
||||
ListRecentPeerActivity(limit int) ([]PeerActivityVo, error)
|
||||
```
|
||||
|
||||
SQL-логика:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
COUNT(*) AS total,
|
||||
SUM(CASE WHEN disabled = 0 THEN 1 ELSE 0 END) AS enabled,
|
||||
SUM(CASE WHEN disabled = 1 THEN 1 ELSE 0 END) AS disabled,
|
||||
SUM(CASE WHEN expires_at > 0 AND expires_at < ? THEN 1 ELSE 0 END) AS expired
|
||||
FROM peer;
|
||||
```
|
||||
|
||||
Traffic current period:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
COALESCE(SUM(download_bytes), 0) AS download,
|
||||
COALESCE(SUM(upload_bytes), 0) AS upload
|
||||
FROM peer
|
||||
WHERE disabled = 0;
|
||||
```
|
||||
|
||||
Top peers из history:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
peer_id,
|
||||
SUM(rx_bytes) AS download,
|
||||
SUM(tx_bytes) AS upload
|
||||
FROM traffic_sample
|
||||
WHERE sampled_at BETWEEN ? AND ?
|
||||
GROUP BY peer_id
|
||||
ORDER BY download + upload DESC
|
||||
LIMIT ?;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 23. Polling на frontend
|
||||
|
||||
Первый production-safe вариант без WebSocket/SSE:
|
||||
|
||||
```text
|
||||
summary: каждые 5 секунд
|
||||
timeseries: каждые 30 секунд
|
||||
top peers: каждые 60 секунд
|
||||
```
|
||||
|
||||
Использовать `useIntervalFn` из `@vueuse/core`, он уже есть в dependencies.
|
||||
|
||||
Обязательно:
|
||||
|
||||
```text
|
||||
pause polling on unmount
|
||||
pause polling when tab hidden
|
||||
show stale badge if collectedAt older than N seconds
|
||||
manual refresh button
|
||||
```
|
||||
|
||||
Если позже захочешь realtime без polling — добавить SSE:
|
||||
|
||||
```text
|
||||
GET /hui/dashboard/events
|
||||
```
|
||||
|
||||
Но я бы не начинал с SSE. Для такой панели polling проще, надёжнее и легче тестируется.
|
||||
|
||||
---
|
||||
|
||||
## 24. Состояния ошибок
|
||||
|
||||
Dashboard должен показывать не просто `-`, а причину.
|
||||
|
||||
Примеры:
|
||||
|
||||
```text
|
||||
Hysteria2 stopped
|
||||
Traffic API unreachable
|
||||
Traffic API unauthorized
|
||||
No traffic samples yet
|
||||
Metrics collector stale
|
||||
Disk path unavailable
|
||||
```
|
||||
|
||||
Backend response:
|
||||
|
||||
```go
|
||||
type DataHealthVo struct {
|
||||
Status string `json:"status"` // ok | stale | error
|
||||
MessageKey string `json:"messageKey,omitempty"`
|
||||
LastSuccessAt int64 `json:"lastSuccessAt,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
Например:
|
||||
|
||||
```json
|
||||
{
|
||||
"hysteria": {
|
||||
"running": true,
|
||||
"apiReachable": false,
|
||||
"lastApiError": "dashboard.error.trafficApiUnauthorized"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Frontend переводит через i18n.
|
||||
|
||||
---
|
||||
|
||||
## 25. Acceptance criteria для Dashboard
|
||||
|
||||
Готовность можно проверять так:
|
||||
|
||||
```text
|
||||
/ открывает /dashboard, а не /info/account.
|
||||
В меню нет Account Info.
|
||||
Admin profile не содержит quota, Node URL, Subscribe, QR.
|
||||
Dashboard показывает Hysteria2 status, version, online peers/devices.
|
||||
Dashboard показывает общий download/upload всех peer’ов.
|
||||
Dashboard показывает traffic today и traffic since reset.
|
||||
Dashboard показывает CPU/RAM/Disk не только процентом, но и used/total.
|
||||
CPU/RAM графики строятся из history, а не из разовых live-запросов.
|
||||
Traffic график не ломается после reset traffic.
|
||||
Traffic accounting не умножает /traffic?clear=1 на polling interval.
|
||||
Security warnings отображаются внутри Dashboard, не всплывающими HTML notification.
|
||||
Если Hysteria2 API недоступен, Dashboard показывает degraded state, а не пустые карточки.
|
||||
На 1366px первый экран читается без горизонтального скролла.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 26. Приоритет внедрения
|
||||
|
||||
Я бы добавил этот блок в общий roadmap так:
|
||||
|
||||
```text
|
||||
A. Security hotfix текущей account/peer модели.
|
||||
B. Split admin_user / peer.
|
||||
C. Исправить traffic accounting: убрать ошибочное умножение, добавить traffic_sample.
|
||||
D. Создать Dashboard backend summary endpoint.
|
||||
E. Перенести /info/account и /monitor/system в /dashboard.
|
||||
F. Добавить графики и history retention.
|
||||
G. Удалить legacy routes /info/account и /monitor/system.
|
||||
```
|
||||
|
||||
Главное: не делать красивый Dashboard поверх старой таблицы `account`. Сначала развести admin и peer, затем строить дашборд на правильной доменной модели. Иначе ты просто перенесёшь старую путаницу в более красивый интерфейс.
|
||||
|
||||
[1]: https://v2.hysteria.network/docs/advanced/Traffic-Stats-API/?utm_source=chatgpt.com "Traffic Stats API"
|
||||
[2]: https://v2.hysteria.network/docs/advanced/Full-Server-Config/?utm_source=chatgpt.com "Full Server Config"
|
||||
Reference in New Issue
Block a user