diff --git a/fix24.1.txt b/fix24.1.txt
deleted file mode 100644
index 861e2bb..0000000
--- a/fix24.1.txt
+++ /dev/null
@@ -1,713 +0,0 @@
-Да, это логичное продолжение: `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"
diff --git a/fix24.2.txt b/fix24.2.txt
deleted file mode 100644
index 9cc2aef..0000000
--- a/fix24.2.txt
+++ /dev/null
@@ -1,243 +0,0 @@
-Да, я бы убирал подписки полностью. В текущем состоянии это не недоделанная мелочь, а мёртвый функциональный слой, который создаёт ложное ожидание у оператора и путает UX.
-
-По текущему коду видно следующее: ordinary Node URL / Node QR строятся отдельно и полезны; subscription-кнопки в UI есть, но backend endpoint `Hysteria2SubscribeUrl` фактически возвращает ошибку `subscription delivery is out of scope in HY2XS baseline`. При этом в service ещё лежит старая логика генерации подписок для Clash/Shadowrocket/v2rayN, но она не подключена нормальным публичным delivery route. То есть это уже рудимент: фронт показывает кнопки, backend говорит “не поддерживается”, а пользователи всё равно подключаются через обычный URI/QR.
-
-Я бы добавил в общий план отдельный пункт.
-
----
-
-## 27. Удалить subscription layer как неподдерживаемый рудимент
-
-### Решение
-
-Оставить только:
-
-```text
-Node URL
-Node QR Code
-Copy URI
-Rotate Secret
-```
-
-Удалить:
-
-```text
-Subscribe
-Subscribe QR Code
-Subscription URL
-Clash subscription extension
-Shadowrocket/V2rayN subscription delivery
-/hysteria2/hysteria2SubscribeUrl
-```
-
-То есть один peer — один обычный Hysteria2 URI/QR. Этого достаточно для текущей панели, особенно если цель — упрощённая, зрелая 3x-ui-like админка, а не subscription gateway.
-
----
-
-## 28. Почему лучше удалить, а не чинить сейчас
-
-Подписка — это отдельный продуктовый слой, а не просто “ещё один QR”.
-
-Чтобы она была production-grade, нужны:
-
-```text
-публичный endpoint доставки подписки;
-форматы под разные клиенты;
-нормальная авторизация или signed token;
-TTL / revoke / rotate;
-rate limit;
-логирование выдачи;
-обработка client type;
-совместимость Clash/Shadowrocket/v2rayN;
-безопасное сокрытие peer secret;
-корректные HTTP headers;
-контроль доступа после disable/expire peer;
-тесты на каждый формат клиента.
-```
-
-Сейчас этого нет. Более того, подписочный URL по старой логике строится вокруг `conPass`, то есть секрет peer’а становится частью URL. Это хуже обычного QR, потому что URL может попасть в browser history, reverse-proxy logs, access logs, Referer, скриншоты и т.д.
-
-Обычный Node QR тоже содержит секрет, но он показывается авторизованному админу в панели для конкретного peer’а. Подписка же предполагает публичную доставку конфигурации по ссылке. Это другой threat model.
-
-Поэтому чинить подписки сейчас — не “доделать кнопку”, а открывать отдельный блок безопасности и совместимости. Для текущего scope это лишнее.
-
----
-
-## 29. Что удалить на backend
-
-Удалить или пометить deprecated с последующим удалением:
-
-```go
-controller.Hysteria2SubscribeUrl
-controller.Hysteria2Subscribe
-service.Hysteria2SubscribeUrl
-service.Hysteria2Subscribe
-dto.Hysteria2SubscribeUrlDto
-vo.Hysteria2SubscribeVo
-```
-
-Из router убрать:
-
-```go
-hysteria2.GET("/hysteria2SubscribeUrl", controller.Hysteria2SubscribeUrl)
-```
-
-Если будешь делать новую REST-модель после разделения `admin_user` / `peer`, оставить только:
-
-```text
-GET /hui/peers/:id/client-uri
-GET /hui/peers/:id/client-qr
-```
-
-Или один endpoint:
-
-```text
-GET /hui/peers/:id/client-config
-```
-
-Response:
-
-```json
-{
- "uri": "hysteria2://...",
- "qrCode": "base64..."
-}
-```
-
-Важно: этот endpoint должен работать только под admin JWT. Никакой публичной выдачи по `conPass`.
-
----
-
-## 30. Что удалить на frontend
-
-Из `src/views/account/list/index.vue` убрать:
-
-```text
-Subscribe
-```
-
-Из будущего peer drawer убрать:
-
-```text
-Subscribe
-Subscribe QR
-```
-
-Из бывшего `src/views/info/account/index.vue` это всё всё равно исчезнет вместе с самой страницей `Account Info`.
-
-Из `src/api/hysteria2/index.ts` убрать:
-
-```ts
-hysteria2SubscribeUrlApi
-```
-
-Из `src/api/hysteria2/types.ts` убрать:
-
-```ts
-Hysteria2SubscribeUrlDto
-Hysteria2SubscribeVo
-```
-
-Из i18n убрать или оставить deprecated на один релиз:
-
-```text
-common.subscribe
-common.subscribeQrCode
-config.clashExtension
-```
-
-Если `CLASH_EXTENSION` используется только для подписки, убрать его из UI настроек. В базе можно не удалять сразу, чтобы не ломать существующие SQLite-файлы. Лучше сделать миграцию так:
-
-```text
-релиз N: поле скрыто, backend не использует;
-релиз N+1: config key удаляется или игнорируется окончательно.
-```
-
----
-
-## 31. Что оставить вместо подписки
-
-В peer list / peer drawer оставить понятные действия:
-
-```text
-Copy URI
-Show QR
-Rotate Secret
-Edit
-Reset Traffic
-Kick
-Disable
-Delete
-```
-
-В detail drawer можно сделать блок:
-
-```text
-Connection
- URI: hysteria2://...
- [Copy]
- [Show QR]
- [Rotate secret]
-```
-
-И короткий warning:
-
-```text
-Rotating secret invalidates existing client configs.
-```
-
-Этого достаточно. Оператор создаёт peer, копирует URI или QR, отдаёт клиенту. Без псевдоподписок.
-
----
-
-## 32. Если когда-нибудь возвращать подписки
-
-Возвращать их стоит только как отдельную feature, не как восстановление старого кода.
-
-Нормальная модель:
-
-```text
-subscription_token
- id
- peer_id
- token_digest
- expires_at
- revoked_at
- created_at
- last_used_at
-```
-
-Публичный endpoint:
-
-```text
-GET /sub/:token
-```
-
-Token должен быть random, revocable, не равен peer secret. В URL нельзя класть `conPass`.
-
-Формат:
-
-```text
-GET /sub/:token?target=clash
-GET /sub/:token?target=shadowrocket
-GET /sub/:token?target=v2rayn
-```
-
-Но я бы сейчас этого не делал. Это увеличит scope, а ценность низкая, если обычные URI/QR уже закрывают основной сценарий.
-
----
-
-## 33. Acceptance criteria для удаления подписок
-
-```text
-В UI нигде нет Subscribe / Subscribe QR.
-В peer list нет действий, ведущих к subscription endpoint.
-В API клиента нет hysteria2SubscribeUrlApi.
-Backend router не регистрирует /hysteria2/hysteria2SubscribeUrl.
-Service-код генерации подписок удалён или явно deprecated и недоступен.
-Обычный Node URL и Node QR продолжают работать.
-Админ не получает Node URL/QR для себя, только для peer’ов.
-В настройках нет Clash subscription extension, если подписки отключены.
-```
-
-Итог: да, подписку лучше удалить. Не скрыть кнопку CSS’ом, не оставить endpoint с `Fail`, а именно вырезать из публичного UI/API. Это упростит продукт, уберёт ложные кнопки и снизит риск утечки peer secret через URL.
diff --git a/fix24.txt b/fix24.txt
deleted file mode 100644
index 545834d..0000000
--- a/fix24.txt
+++ /dev/null
@@ -1,617 +0,0 @@
-Посмотрел архив `HY2XS_flamy-main.zip`. Проблема не косметическая: сейчас в проекте смешаны две разные доменные сущности — администратор панели и peer Hysteria2. Это видно прямо по схеме `account`: в одной таблице лежат `username/pass` для входа в админку, `con_pass` для подключения, `quota/download/upload/device_no`, `role`, `deleted`. Логин панели фильтрует `role = 'admin'`, а Hysteria2 auth ищет только по `con_pass` и не отсекает `role = 'admin'`. Поэтому админ технически остаётся peer’ом.
-
-Ниже план нормального продакшен-исправления без «подмазать CSS и добавить if role != admin».
-
----
-
-## 1. Развести админов панели и peer’ов Hysteria2
-
-### Текущее состояние
-
-Сейчас:
-
-`account.pass` — пароль входа в панель.
-`account.con_pass` — пароль подключения Hysteria2.
-`account.role` — попытка различать `admin/user`.
-`account.quota/download/upload/device_no/expire_time` — peer-поля.
-`login_at` — поле админа.
-`con_at` — поле peer’а.
-
-Это плохая модель. Роль не должна превращать одну таблицу в две разные сущности.
-
-### Целевая модель
-
-Сделать минимум две таблицы.
-
-`admin_user`:
-
-```sql
-id
-username
-password_hash
-status
-force_password_change
-last_login_at
-password_changed_at
-token_version
-created_at
-updated_at
-```
-
-`peer`:
-
-```sql
-id
-name
-remark
-auth_id
-secret_digest
-secret_ciphertext -- если нужно показывать URL/QR после создания
-quota_bytes
-download_bytes
-upload_bytes
-expires_at
-max_devices
-disabled
-banned_until
-last_connection_at
-created_at
-updated_at
-```
-
-`admin_user` не должен иметь `quota`, `con_pass`, `device_no`, `download`, `upload`.
-
-`peer` не должен иметь пароль входа в панель и `role`.
-
-### Как хранить peer secret
-
-Текущий `con_pass` хранится как plain text. Для продакшена лучше уйти от этого.
-
-Вариант нормальный:
-
-`auth_id` — публичный идентификатор peer’а, например короткий random/base32.
-`raw_secret` — генерируется при создании или ротации.
-`secret_digest = HMAC-SHA256(raw_secret, HY2XS_PEER_SECRET_KEY)` — используется для auth lookup.
-`secret_ciphertext` — опционально, если UI должен уметь повторно показать Node URL/QR. Шифровать ключом из data-dir/env, не хранить просто строкой в SQLite.
-
-Если не хочется вводить шифрование сейчас, допустим компромисс: хранить `secret_plain` временно, но уже в таблице `peer`, не рядом с admin password. Потом отдельной миграцией заменить на digest/encrypted secret.
-
-### Миграция
-
-Сделать версионированные миграции, а не держать огромную строку SQL внутри `dao/sqlite.go`.
-
-Сейчас есть два источника схемы: `apps/docs/sql/h_ui_db.sql` и inline `sqlInitStr` в `apps/dao/sqlite.go`. Они уже расходятся: в `sqlite.go` добавлен `force_password_change`, в SQL-доке его нет; `remark` объявлен как `INTEGER DEFAULT ''`, хотя в Go это `string`. Это надо убрать.
-
-Нужен один механизм:
-
-```sql
-schema_migrations(version, applied_at)
-```
-
-Миграции:
-
-`001_initial_legacy_snapshot.sql` — текущая схема, только для reference.
-`002_admin_peer_split.sql` — создаёт `admin_user`, `peer`.
-`003_migrate_legacy_accounts.sql` — переносит данные.
-`004_drop_or_archive_legacy_account.sql` — не сразу удалять, а переименовать в `legacy_account_backup`.
-
-Правила переноса:
-
-`role = 'admin'` → `admin_user`. Переносить `username`, `pass`, `force_password_change`, `login_at`. Не переносить `con_pass`.
-
-`role != 'admin'` → `peer`. Переносить `username` как `name`, `remark`, лимиты, трафик, `expire_time`, `kick_util_time`, `con_at`.
-
-После миграции admin credentials больше не могут пройти Hysteria2 auth даже теоретически.
-
----
-
-## 2. Переписать backend-слой по доменам
-
-### Что заменить
-
-Сейчас всё сидит в `account.go`: DAO, service, controller, DTO, VO. Надо разделить:
-
-```text
-dao/admin_user.go
-dao/peer.go
-
-service/auth.go
-service/admin_user.go
-service/peer.go
-service/hysteria2_auth.go
-
-controller/auth.go
-controller/admin_user.go
-controller/peer.go
-controller/hysteria2.go
-```
-
-### API
-
-Оставить `/hui/auth/login`, но он должен работать только с `admin_user`.
-
-Добавить:
-
-```text
-GET /hui/admin/me
-POST /hui/admin/change-password
-
-GET /hui/peers
-POST /hui/peers
-GET /hui/peers/:id
-PATCH /hui/peers/:id
-DELETE /hui/peers/:id
-
-POST /hui/peers/:id/reset-traffic
-POST /hui/peers/:id/kick
-POST /hui/peers/:id/release-kick
-POST /hui/peers/:id/rotate-secret
-GET /hui/peers/:id/client-url
-GET /hui/peers/:id/qr
-```
-
-Старые `/account/*` можно оставить только как compatibility layer на один релиз, но UI уже должен ходить в `/peers/*`.
-
-### Hysteria2 auth
-
-Сейчас `Hysteria2Auth()` делает:
-
-```go
-dao.GetAccount("con_pass = ? and deleted = 0 ...")
-```
-
-Нужно заменить на peer-auth:
-
-```go
-peer, err := peerRepo.FindBySecretDigest(digest)
-```
-
-И проверять только peer-поля:
-
-```text
-disabled = false
-now < expires_at
-quota_bytes < 0 OR quota_bytes > download_bytes + upload_bytes
-now > banned_until
-online_devices < max_devices
-```
-
-Admin-таблица здесь вообще не импортируется.
-
-### Сессии и JWT
-
-JWT сейчас содержит `AccountBo` с `Roles`. Оставить можно, но лучше переименовать в `AdminClaims`.
-
-Добавить `token_version` в `admin_user`. Тогда смена пароля, reset или принудительная инвалидизация токенов делается увеличением `token_version`.
-
-После смены пароля обязательно сбрасывать `force_password_change = 0`. Сейчас флаг возвращается из логина, но нормального dedicated flow для смены admin password не видно.
-
-### Reset command
-
-`apps/cmd/reset.go` сейчас меняет row `id=1` в `account`, печатает ещё и `Connection Password`. После разделения:
-
-```text
-hy2xs-admin reset-admin
-```
-
-Должен менять только `admin_user`.
-
-Никакого connection password для админа печатать нельзя.
-
----
-
-## 3. UI: переименовать “Account Manage” в peer management
-
-В интерфейсе сейчас название “Account” вводит в заблуждение. Там смешаны профиль админа и peer’ы. Нужна терминология:
-
-```text
-Профиль / Admin Profile
-Пиры / Peers
-Управление пирами / Peer Management
-```
-
-В форме создания peer убрать поле “Пароль входа”. Для peer нужен только generated/rotatable connection secret.
-
-Нормальная форма создания peer:
-
-```text
-Комментарий
-Имя / label
-Квота
-Срок действия
-Лимит устройств
-Статус
-[Создать]
-```
-
-После создания показать одноразовый блок:
-
-```text
-Connection URI
-QR
-Copy
-Сохраните сейчас: после закрытия секрет может быть скрыт
-```
-
-Если оставляете encrypted secret, можно показывать URL и позже.
-
----
-
-## 4. Починить русскую локаль и “плывущие” иконки правильно
-
-### Причина
-
-Это не проблема русского языка как такового. Русские строки длиннее, а sidebar сейчас завязан на дефолтный layout Element Plus плюс ручные margin’ы:
-
-```scss
-.svg-icon { margin-right: 16px; }
-.hideSidebar .el-sub-menu__title { padding: 0 !important; }
-.hideSidebar .svg-icon { margin-left: 20px; }
-```
-
-Из-за этого при длинных заголовках, collapse/open state и sub-menu иконки начинают жить отдельно от текста.
-
-### Исправление
-
-В `SidebarItem.vue` обернуть title в отдельный span:
-
-```vue
-
-```
-
-Для `el-menu-item` и `el-sub-menu__title` задать один стабильный layout:
-
-```scss
-.sidebar-container {
- .el-menu-item,
- .el-sub-menu__title {
- display: flex;
- align-items: center;
- gap: 12px;
- height: 48px;
- line-height: normal;
- padding: 0 16px !important;
- }
-
- .svg-icon {
- flex: 0 0 18px;
- width: 18px;
- height: 18px;
- margin-right: 0;
- }
-
- .menu-title {
- min-width: 0;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
-
- .el-sub-menu__icon-arrow {
- margin-left: auto;
- }
-}
-```
-
-Убрать ручные `margin-left: 20px` для collapsed state. Для collapsed меню текст скрывать штатно, а не через плавающие отступы.
-
-Ширину sidebar лучше поднять с `210px` до `240px` или `248px`. Но это вторично. Основной фикс — стабильная flex/grid-разметка.
-
-Для длинных пунктов включить tooltip с полным названием при hover. Не переносить текст на вторую строку внутри sidebar.
-
----
-
-## 5. Перевести уведомления и ошибки без хардкода
-
-Сейчас часть UI переведена через `$t`, но много строк осталось захардкоженными:
-
-```text
-"Required"
-"Username format is incorrect"
-"Are you sure to reset traffic?"
-"Are you sure to delete..."
-"Warning"
-"A week later"
-"file format not supported"
-```
-
-Плюс backend возвращает английские строки:
-
-```text
-wrong password
-system error
-permission denied
-username already exists
-admin cannot be deleted
-```
-
-### Frontend
-
-Добавить namespace:
-
-```ts
-validation: {
- required,
- usernameInvalid,
- passwordInvalid,
- peerSecretInvalid,
- integer,
- number,
- fileFormat,
- fileTooLarge,
-}
-
-confirm: {
- warning,
- deletePeer,
- resetTraffic,
- restartPanel,
-}
-
-timeShortcut: {
- hourLater,
- dayLater,
- weekLater,
- monthLater,
- yearLater,
-}
-```
-
-Все validation rules сделать через `computed`, чтобы при смене языка сообщения обновлялись:
-
-```ts
-const dataFormRules = computed(() => ({
- username: [
- { required: true, message: t("validation.required"), trigger: ["blur"] },
- ],
-}))
-```
-
-Подключить `ElConfigProvider` в `App.vue` и прокидывать locale Element Plus:
-
-```vue
-
-
-
-```
-
-Иначе встроенные компоненты Element Plus, datepicker, pagination и popconfirm будут жить своей локалью.
-
-### Backend
-
-Не возвращать UI-текст как источник истины. Возвращать стабильный error code/message key:
-
-```json
-{
- "code": 40010,
- "type": "no",
- "message": "peer.usernameAlreadyExists",
- "params": { "username": "testuser" }
-}
-```
-
-Frontend переводит `message` как i18n key. Если key неизвестен — fallback на `common.systemError`.
-
-Для machine endpoint `/hysteria2/auth` локализация не нужна, там протокольный ответ `{ ok: true/false, id }`.
-
----
-
-## 6. Сжать таблицу peer’ов до production-вида
-
-Сейчас таблица перегружена. В `account/list/index.vue` одновременно выводятся:
-
-```text
-ID
-Remark
-Username
-Role
-Quota
-Download
-Upload
-Online status
-Online devices
-Device limit
-Offline remaining time
-Expire time
-Last login time
-Last connection time
-Create time
-Status
-Operate
-```
-
-Для peer list это слишком много. На широком экране оно всё равно не будет хорошо читаться.
-
-### Основной список
-
-Оставить в таблице только:
-
-```text
-Peer
-Status
-Traffic
-Devices
-Expires
-Last connection
-Actions
-```
-
-Где:
-
-`Peer` — имя + remark + ID мелким текстом.
-`Status` — enabled/disabled + online/offline.
-`Traffic` — progress bar: used / quota, а upload/download спрятать в details.
-`Devices` — online / max.
-`Expires` — дата + “expired soon/expired” tag.
-`Last connection` — дата или `-`.
-`Actions` — 1–2 основные кнопки и dropdown.
-
-### Details drawer / overview dialog
-
-По клику “Обзор” открыть drawer:
-
-```text
-Peer overview
-- ID
-- Name
-- Remark
-- Created at
-- Updated at
-- Download
-- Upload
-- Quota
-- Expires at
-- Last connection
-- Kick until
-- Device limit
-- Current online devices
-- Node URL
-- QR
-```
-
-Это лучше, чем прятать половину в троеточие без структуры.
-
-### Actions
-
-В таблице оставить:
-
-```text
-Copy URL
-Edit
-⋯
-```
-
-В dropdown:
-
-```text
-Show QR
-Reset traffic
-Kick
-Release kick
-Rotate secret
-Disable / Enable
-Delete
-```
-
-`Subscribe` сейчас в baseline отключён, значит в UI его лучше не показывать, пока delivery layer вне scope. Иначе оператор видит кнопку, которая всегда ведёт к ошибке.
-
-### Поведение на малых экранах
-
-На desktop можно оставить `el-table`.
-
-На tablet/mobile лучше отдельный card layout, а не пытаться ужать таблицу. Например:
-
-```text
-Peer card
-name / status
-traffic progress
-devices
-expires
-actions
-```
-
----
-
-## 7. Конкретный порядок работ
-
-### Этап A. Быстрый security hotfix
-
-Это временный фикс, не финальная архитектура.
-
-1. В `Hysteria2Auth` добавить фильтр `role = 'user'`.
-2. В `PageAccount` по умолчанию показывать только `role = 'user'`.
-3. В `SaveAccount` явно ставить `role = 'user'`.
-4. Запретить выдачу Node URL/QR для `role = 'admin'`.
-5. В `reset.go` убрать вывод connection password.
-
-Это закрывает самый опасный баг до большой миграции.
-
-### Этап B. Нормальная доменная миграция
-
-1. Добавить `schema_migrations`.
-2. Создать `admin_user`.
-3. Создать `peer`.
-4. Перенести legacy data.
-5. Переписать DAO/service/controller.
-6. Оставить legacy `account` только как backup.
-7. Добавить `reset-admin`.
-8. Убрать `role` из peer flow.
-
-### Этап C. UI/i18n
-
-1. Переименовать раздел `Account Manage` → `Peer Manage`.
-2. Убрать peer login password из формы.
-3. Сделать i18n keys для всех validation/confirm/toast строк.
-4. Подключить Element Plus locale provider.
-5. Убрать backend English strings из UI-отображения.
-6. Добавить проверку отсутствующих i18n keys в CI.
-
-### Этап D. Layout/sidebar
-
-1. Переписать sidebar CSS на flex/grid.
-2. Убрать ручные margin hacks.
-3. Добавить `.menu-title` с ellipsis.
-4. Поднять sidebar width до 240–248px.
-5. Проверить RU/EN в expanded/collapsed состояниях.
-
-### Этап E. Peer table redesign
-
-1. Сделать компактные колонки.
-2. Вынести details в drawer.
-3. Перенести второстепенные действия в dropdown.
-4. Скрыть subscription actions, если delivery layer отключён.
-5. Добавить responsive card layout.
-
----
-
-## 8. Тесты и критерии готовности
-
-Backend tests:
-
-```text
-admin_user может войти в панель
-admin_user не может пройти Hysteria2 auth
-peer не может войти в панель
-peer может пройти Hysteria2 auth
-expired peer rejected
-disabled peer rejected
-quota-exceeded peer rejected
-device-limit peer rejected
-legacy migration переносит admin и peers корректно
-```
-
-Frontend checks:
-
-```text
-pnpm lint:eslint
-pnpm build:prod
-vue-tsc --noEmit
-```
-
-E2E/screenshot:
-
-```text
-RU sidebar expanded
-RU sidebar collapsed
-EN sidebar expanded
-EN sidebar collapsed
-Peer table 1366px
-Peer table 1920px
-Peer card/mobile layout
-```
-
-Acceptance criteria:
-
-```text
-Админ панели не отображается в списке peer’ов.
-У админа нет Node URL, QR и connection password.
-Peer не имеет password для входа в UI.
-Все toast/confirm/validation сообщения переводятся.
-Русская локаль не ломает sidebar.
-Peer list не требует горизонтального скролла на 1366px.
-Второстепенные peer-поля доступны через “Обзор”.
-```
-
-Главная мысль: не чинить это через `role` и CSS-отступы. Правильный продакшен-фикс — разделить admin identity и peer identity на уровне схемы, сервисов и UI. После этого локаль и таблица чинятся уже как нормальная фронтенд-задача, а не как борьба с последствиями смешанной модели.