From 50bcac4c8de09d0a9d14c516e82bed7715d01991 Mon Sep 17 00:00:00 2001 From: benya Date: Thu, 4 Jun 2026 20:15:38 +0300 Subject: [PATCH] Add device workspace tabs --- UI_CHECKLIST.md | 34 ++++++++++----------- web/app.js | 38 ++++++++++++++++++++++++ web/index.html | 74 ++++++++++++++++++++++++++------------------- web/styles.css | 79 ++++++++++++++++++++++++++++++++++++++++++------- 4 files changed, 166 insertions(+), 59 deletions(-) diff --git a/UI_CHECKLIST.md b/UI_CHECKLIST.md index 7cac3bc..e53be66 100644 --- a/UI_CHECKLIST.md +++ b/UI_CHECKLIST.md @@ -53,22 +53,22 @@ ## Этап 2. Карточка и обзор объекта -- [ ] Создать верхнюю панель объекта. -- [ ] Добавить хлебную крошку возврата к объектам. -- [ ] Добавить вкладки «Обзор», «Клиенты», «Сеть», «Операции», «Сведения», «Эксперт». -- [ ] Показать статус и время последнего контакта. +- [x] Создать верхнюю панель объекта. +- [x] Добавить хлебную крошку возврата к объектам. +- [x] Добавить вкладки «Обзор», «Клиенты и сеть», «Операции», «Сведения», «Эксперт». +- [x] Показать статус и время последнего контакта. - [ ] Показать WAN и доступность сервера. -- [ ] Показать CPU/load, память, uptime и число клиентов. -- [ ] Убрать дублирующиеся метрики. +- [x] Показать load, память, uptime и число клиентов. +- [x] Убрать дублирующиеся и технические метрики. - [ ] Показать активные предупреждения с приоритетами. - [ ] Добавить компактные графики метрик. - [ ] Добавить выбор периода графиков. -- [ ] Добавить основную кнопку открытия LuCI. -- [ ] Добавить кнопку запуска диагностики. +- [x] Добавить основную кнопку открытия LuCI. +- [x] Добавить кнопку перехода к диагностике. - [ ] Перенести перезагрузку и опасные действия в отдельное меню. -- [ ] Убрать raw inventory с обзорной страницы. -- [ ] Убрать произвольные команды с обзорной страницы. -- [ ] Убрать редактирование группы и тегов с обзорной страницы. +- [x] Убрать raw inventory с обзорной страницы. +- [x] Убрать произвольные команды с обзорной страницы. +- [x] Убрать редактирование группы и тегов с обзорной страницы. ## Этап 3. Клиенты @@ -124,14 +124,14 @@ ## Этап 5. Экспертный режим -- [ ] Перенести raw inventory. -- [ ] Перенести произвольные команды. -- [ ] Перенести raw UCI show/set/commit/revert. -- [ ] Перенести низкоуровневое управление пакетами. -- [ ] Перенести полную историю команд. +- [x] Перенести raw inventory. +- [x] Перенести произвольные команды. +- [x] Перенести raw UCI show/set/commit/revert. +- [x] Перенести низкоуровневое управление пакетами. +- [x] Перенести полную историю команд. - [ ] Сократить основные фильтры истории до «Все», «Выполняются», «Ошибка». - [ ] Перенести дополнительные фильтры в меню. -- [ ] Перенести аудит. +- [x] Перенести аудит. - [ ] Добавить предупреждение перед опасными действиями. - [ ] Подготовить экспертный режим к разграничению прав. diff --git a/web/app.js b/web/app.js index 3b0687e..98a4c7f 100644 --- a/web/app.js +++ b/web/app.js @@ -2,6 +2,7 @@ const state = { username: "", devices: [], selectedDeviceId: null, + deviceTab: "overview", filter: "all", commandFilter: "all", commands: [], @@ -33,6 +34,9 @@ const els = { emptyState: document.querySelector("#emptyState"), deviceView: document.querySelector("#deviceView"), backToFleetBtn: document.querySelector("#backToFleetBtn"), + quickDiagnosticBtn: document.querySelector("#quickDiagnosticBtn"), + openLuciBtn: document.querySelector("#openLuciBtn"), + remoteAccessPanel: document.querySelector("#remoteAccessPanel"), deviceName: document.querySelector("#deviceName"), deviceMeta: document.querySelector("#deviceMeta"), deviceBadge: document.querySelector("#deviceBadge"), @@ -381,6 +385,7 @@ function renderDeviceDetail(device) { els.fleetView.classList.add("is-hidden"); els.deviceView.classList.remove("is-hidden"); + renderDeviceTab(); const displayName = deviceDisplayName(device); els.pageTitle.textContent = displayName; els.deviceName.textContent = displayName; @@ -573,6 +578,7 @@ async function loadDevices() { async function selectDevice(id) { state.selectedDeviceId = id; + state.deviceTab = "overview"; renderDevices(); renderDeviceDetail(currentDevice()); await Promise.all([loadCommands(), loadAudit(), loadMetricsHistory(), loadAlerts(), loadRemoteSessions()]); @@ -585,6 +591,31 @@ function showFleet() { renderDeviceDetail(null); } +function selectDeviceTab(tab) { + state.deviceTab = tab; + renderDeviceTab(); +} + +function renderDeviceTab() { + for (const button of document.querySelectorAll(".device-tab")) { + button.classList.toggle("is-active", button.dataset.deviceTabTarget === state.deviceTab); + } + for (const section of document.querySelectorAll("[data-device-tab]")) { + section.classList.toggle("is-hidden", section.dataset.deviceTab !== state.deviceTab); + } +} + +function openLuciOrRemoteAccess() { + const activeSession = state.remoteSessions.find((session) => session.status === "active" && session.luci_port); + if (activeSession) { + window.open(`/luci/${encodeURIComponent(activeSession.device_id)}/${encodeURIComponent(activeSession.id)}/`, "_blank", "noopener"); + return; + } + selectDeviceTab("operations"); + els.remoteAccessPanel.scrollIntoView({ behavior: "smooth", block: "start" }); + setStatus("Создайте удаленную сессию, затем откройте LuCI"); +} + async function loadCommands() { if (!state.selectedDeviceId) return; const data = await api(`/api/devices/${encodeURIComponent(state.selectedDeviceId)}/commands?limit=50`); @@ -1014,6 +1045,7 @@ async function runAlertDiagnostics(alert) { } function scrollToCommands() { + selectDeviceTab("expert"); document.querySelector("#commandsPanel")?.scrollIntoView({ behavior: "smooth", block: "start" }); } @@ -1043,6 +1075,8 @@ els.logoutBtn.addEventListener("click", () => logout().catch((error) => setStatu els.refreshBtn.addEventListener("click", () => loadDevices().catch((error) => setStatus(error.message))); els.backToFleetBtn.addEventListener("click", showFleet); +els.quickDiagnosticBtn.addEventListener("click", () => selectDeviceTab("operations")); +els.openLuciBtn.addEventListener("click", openLuciOrRemoteAccess); els.reloadCommandsBtn.addEventListener("click", () => loadCommands().catch((error) => setStatus(error.message))); els.reloadAuditBtn.addEventListener("click", () => loadAudit().catch((error) => setStatus(error.message))); els.reloadAlertsBtn.addEventListener("click", () => loadAlerts().catch((error) => setStatus(error.message))); @@ -1080,6 +1114,10 @@ for (const button of document.querySelectorAll(".diagnostic-btn")) { }); } +for (const button of document.querySelectorAll(".device-tab")) { + button.addEventListener("click", () => selectDeviceTab(button.dataset.deviceTabTarget)); +} + for (const button of document.querySelectorAll(".filter")) { button.addEventListener("click", () => { state.filter = button.dataset.filter; diff --git a/web/index.html b/web/index.html index cc78bd9..9807312 100644 --- a/web/index.html +++ b/web/index.html @@ -145,22 +145,34 @@
-

Device

+

Объект

- +
+ + + +
-
+ + +
-

Состояние роутера

- кратко +

Состояние объекта

+ обновляется автоматически
-
+

Группа и теги

@@ -177,46 +189,46 @@
-
+
- Last seen + Последний контакт -
- Load average + Нагрузка -
- Uptime + Время работы -
-
- Default route +
+ Маршрут по умолчанию -
- WAN IP + WAN-адрес -
- Memory + Память -
-
- Disk +
+ Хранилище -
- Clients + Клиенты -
-
- Connectivity +
+ Проверки связи -
-
+

Inventory

@@ -243,7 +255,7 @@
-
+

Быстрая диагностика

без SSH @@ -257,7 +269,7 @@
-
+

Leases and Wi-Fi

@@ -273,7 +285,7 @@
-
+

Проблемы

@@ -284,7 +296,7 @@
-
+

История мониторинга

@@ -292,7 +304,7 @@
-
+

UCI configuration

@@ -336,7 +348,7 @@
-
+

Packages

@@ -353,7 +365,7 @@
-
+

Remote access

@@ -402,7 +414,7 @@
-
+

UCI presets

@@ -448,7 +460,7 @@
-
+

История команд

@@ -468,7 +480,7 @@
-