diff --git a/adk-rust/RUNBOOK.md b/adk-rust/RUNBOOK.md index 464fe68..68a0522 100644 --- a/adk-rust/RUNBOOK.md +++ b/adk-rust/RUNBOOK.md @@ -1549,6 +1549,9 @@ systemctl is-active tsj-guardian-bot tsj-guardian-watchdog gost-tg Отложить: +- новый этап: сделать `detmir-portal` - единый read-only Rust web portal для + оператора, руководителя и владельца. Детальный план: + `docs/DETMIR_PORTAL_GUI_PLAN_RU.md`; - перенос Telegram bot runtime снят с плана: Python остается постоянным runtime, Rust используется только для backend helpers; - перенос оставшихся install/runtime scripts на Rust; diff --git a/docs/DETMIR_PORTAL_GUI_PLAN_RU.md b/docs/DETMIR_PORTAL_GUI_PLAN_RU.md new file mode 100644 index 0000000..169d123 --- /dev/null +++ b/docs/DETMIR_PORTAL_GUI_PLAN_RU.md @@ -0,0 +1,802 @@ +# DetMir Portal GUI Plan + +Документ предназначен для следующего агента, в том числе менее сильного. Не +надо угадывать архитектуру: идти по фазам, проверять каждый слой, не ломать +текущий контур. + +## Цель + +Сделать единый web GUI для работы с контуром DetMir: + +- оператор видит техническое состояние и последние проблемы; +- менеджер видит работу сотрудников и отклонения без технических терминов; +- владелец видит короткую управленческую картину: работа, риски, 1С, ИБ, + проблемные зоны; +- текущие Grafana/AW/DLP/1C экраны не удаляются, а становятся источниками и + deep links; +- первый production MVP только read-only. + +## Жесткие ограничения + +- pfSense не трогать. +- Telegram runtime остается Python. +- Не менять маршрутизацию, DNS, VPN, NAT и default routes. +- Не удалять Grafana dashboards, AW DB, SQLite DB, backups. +- Не добавлять write/action endpoints в первом MVP. +- Не выводить секреты в HTML, JSON, journald, git diff. +- Любая кнопка с мутацией только после отдельной фазы и отдельной проверки. +- Портал должен работать автономно на сервере, без зависимости от ноутбука. + +## Текущий фундамент + +Уже есть зеленые источники, которые портал должен использовать: + +- `detmir-status --json` на Proxmox; +- `detmir-check --json` на Proxmox, включая `grafana-data`; +- `detmir-grafana-check` в Grafana CT 201; +- AW Worktime API на `10.10.10.13:5610`; +- ActivityWatch API на `10.10.10.13:5600`; +- DLP health/case/policy services на AW server; +- 1C analytics API на `10.10.10.2:8710`; +- внешний gateway `https://dm.iri1968.dpdns.org/`; +- nginx Basic Auth на gateway. + +## Целевая архитектура MVP + +Новый Rust crate: + +```text +adk-rust/crates/detmir-portal/ +``` + +Production binary: + +```text +/usr/local/bin/detmir-portal +``` + +Production service на Proxmox host: + +```text +detmir-portal.service +``` + +Bind: + +```text +127.0.0.1:8720 +``` + +External route через существующий nginx gateway: + +```text +https://dm.iri1968.dpdns.org/portal/ +``` + +Почему Proxmox host: + +- там уже живут `detmir-status`, `detmir-check`, `detmir-auto`; +- там есть доступ к `pct exec 201` для Grafana-check artifact; +- там уже стоит nginx gateway; +- не нужна новая CT/platform операция. + +## Rust Stack + +Предпочтение: использовать текущий стиль проекта. + +Минимальный MVP: + +- `tiny_http` для HTTP server, как в `worktime-api`, `dlp-policy-engine`, + `dlp-case-management`; +- `reqwest blocking` для HTTP к AW/Grafana/1C/DLP; +- `serde`/`serde_json` для typed models; +- static HTML/CSS/JS встроить в binary через `include_str!`; +- без npm/build pipeline на первом этапе. + +Не использовать на MVP: + +- React/Vite/Next; +- отдельный frontend build; +- database для портала; +- websocket; +- сложный RBAC; +- write actions. + +## Основные URL Портала + +HTML: + +```text +GET /portal/ +GET /portal/operator +GET /portal/manager +GET /portal/owner +GET /portal/incidents +``` + +JSON API: + +```text +GET /portal/api/health +GET /portal/api/summary +GET /portal/api/operator +GET /portal/api/manager +GET /portal/api/owner +GET /portal/api/incidents +GET /portal/api/links +``` + +Service-local direct URLs: + +```text +GET / +GET /operator +GET /manager +GET /owner +GET /incidents +GET /api/health +GET /api/summary +``` + +Портал должен корректно работать за prefix `/portal/`. Не хардкодить абсолютные +пути вида `/api/...` в JS; использовать относительные `api/...` или вычислять +base path. + +## Data Contract + +### `/api/health` + +Минимальный JSON: + +```json +{ + "ok": true, + "generated_at_utc": "2026-06-02T18:00:00Z", + "version": "0.1.0", + "sources": { + "detmir_status": true, + "detmir_check": true, + "grafana_check": true, + "worktime_api": true, + "dlp_health": true, + "one_c": true + } +} +``` + +### `/api/summary` + +Единый верхнеуровневый статус: + +```json +{ + "severity": "OK", + "operator_ok": true, + "headline": "Контур работает штатно", + "generated_at_utc": "2026-06-02T18:00:00Z", + "blocks": { + "collection": {"status": "OK", "text": "Данные свежие"}, + "grafana": {"status": "OK", "text": "7 панелей, данные актуальны"}, + "dlp": {"status": "OK", "text": "22 проверки OK"}, + "worktime": {"status": "OK", "text": "Есть данные за сегодня"}, + "one_c": {"status": "OK", "text": "API 1C analytics отвечает"} + } +} +``` + +### `/api/operator` + +Для оператора: + +- `detmir_status`; +- `detmir_check.summary`; +- `grafana_data`; +- failed units count; +- freshness по buckets; +- последние WARN/FAIL; +- ссылки на Grafana/AW/worktime. + +### `/api/manager` + +Для менеджера: + +- сотрудники за сегодня; +- активное время; +- доказанная работа; +- приложения; +- последние действия; +- простые отклонения: + - данных нет; + - данные устарели; + - активность ниже ожидаемой; + - слишком много событий DLP. + +Источник: сначала AW Worktime API `/reports/worktime/today`. Не ходить напрямую +в SQLite. + +### `/api/owner` + +Для владельца: + +- 4-6 крупных карточек: + - "Работа сегодня"; + - "Риски ИБ"; + - "1С / финансы"; + - "Сбор данных"; + - "Инциденты"; + - "Что требует внимания"; +- короткие выводы в человеческом языке; +- links на глубокие Grafana/1C/AW страницы. + +### `/api/incidents` + +MVP read-only список: + +- DLP incidents/cases; +- DetMir health failures; +- Grafana data failures; +- stale collectors; +- 1C analytics warnings. + +Пока без кнопок "закрыть", "назначить", "эскалировать". + +## Источники Данных + +### Proxmox local commands + +```bash +detmir-status --json +detmir-check --json +systemctl --failed --no-pager +``` + +Правило: + +- command timeout максимум 10 секунд; +- ошибка источника не должна валить HTTP server; +- ошибка источника должна попасть в JSON как `status: "FAIL"` или + `source_error`. + +### Grafana check artifact + +Через `detmir-check` уже есть агрегированный `grafana-data`. + +Для подробного operator view можно дополнительно читать: + +```bash +sudo -n /usr/sbin/pct exec 201 -- cat /var/lib/detmir-grafana-check/latest.json +``` + +Если это не работает, не делать repair. Просто показать ошибку. + +### AW Worktime API + +Основной URL: + +```text +http://10.10.10.13:5610/reports/worktime/today +``` + +Правило: + +- `Connection: close`; +- timeout 10-15 секунд; +- не кешировать пустой отчет как успешный; +- если API временно не ответил, показать stale/source error. + +### DLP + +Минимум: + +```bash +ssh aw-server 'sudo -n /usr/local/bin/dlp-health-check --json' +``` + +Лучше после MVP сделать HTTP client к case/policy APIs, но не обязательно в +первом проходе. + +### 1C Analytics + +Минимум: + +```text +http://10.10.10.2:8710/api/health +http://10.10.10.2:8710/manager/brief +http://10.10.10.2:8710/manager/actions +``` + +Если `/manager/brief` HTML, для MVP не парсить его глубоко. Дать link и health +card. + +## UI Правила + +Это рабочий портал, не landing page. + +- Первый экран сразу показывает состояние контура. +- Без маркетингового hero. +- Без декоративных gradient/orb/background. +- Не делать карточки внутри карточек. +- Dense, спокойный, операционный интерфейс. +- Использовать 8px radius или меньше для cards/panels. +- Текст не должен вылезать из блоков. +- Цвета статусов: + - OK: зеленый; + - WARN: желтый/янтарный; + - FAIL: красный; + - UNKNOWN: серый. +- Не использовать одну сплошную синюю/фиолетовую палитру. +- Главные роли должны быть tabs/segmented control: + - Оператор; + - Руководитель; + - Владелец; + - Инциденты. +- Иконки можно использовать inline SVG только если нет frontend dependency. + Если позже появится frontend dependency, использовать lucide icons. + +## Экран 1: Operator Console + +Цель: за 10 секунд понять, жив ли контур. + +Блоки: + +1. Верхняя строка: + - общий статус; + - время последней проверки; + - `ok_for_operator`; + - кнопка-ссылка "Открыть Grafana"; + - кнопка-ссылка "Открыть AW". + +2. Health grid: + - DetMir; + - ActivityWatch; + - Worktime API; + - Grafana Data; + - DLP; + - 1C. + +3. Data freshness: + - buckets OK/STALE/DEAD; + - Grafana freshness; + - DLP counters. + +4. Problems: + - список WARN/FAIL; + - для каждого: источник, текст, время, ссылка. + +5. Deep links: + - DetMir ActivityWatch dashboard; + - Worktime report; + - AW UI; + - Grafana dashboards; + - 1C brief. + +MVP без кнопок restart/heal. + +## Экран 2: Manager View + +Цель: понять работу сотрудников без технических терминов. + +Блоки: + +1. Сегодня: + - всего активного времени; + - число пользователей; + - последний сбор данных. + +2. Сотрудники: + - имя; + - активное время; + - последнее действие; + - основные приложения; + - статус данных. + +3. Приложения: + - 1С; + - браузер; + - Проводник; + - прочие. + +4. Отклонения: + - нет данных; + - неактивен; + - слишком старый сбор; + - DLP signal. + +Не показывать: + +- bucket ids; +- raw JSON; +- systemd unit names; +- stack traces. + +## Экран 3: Owner View + +Цель: дать владельцу управленческий ответ, а не технический dashboard. + +Блоки: + +1. "Компания сегодня": + - работа идет / есть сбои / есть риски. + +2. "Люди и работа": + - активность; + - заметные отклонения; + - кто требует внимания. + +3. "Безопасность": + - DLP OK/WARN/FAIL; + - открытые кейсы; + - критичные сработки. + +4. "1С и финансы": + - health 1C analytics; + - ссылка на financial/reporting board; + - ссылка на actions. + +5. "Что сделать": + - 3-5 коротких рекомендаций. + +Рекомендации в MVP должны быть rule-based, не AI: + +- если `ok_for_operator=false`: "Проверить технический контур"; +- если Grafana stale: "Обновить/проверить Grafana data pipeline"; +- если DLP fail: "Открыть DLP обзор"; +- если worktime rows empty: "Проверить RDP collectors". + +## Экран 4: Incidents + +MVP read-only. + +Таблица: + +- статус; +- тип; +- источник; +- краткое описание; +- время; +- ссылка. + +Типы: + +- `health`; +- `grafana`; +- `dlp`; +- `worktime`; +- `one_c`; +- `collector`. + +Нельзя делать в MVP: + +- закрытие инцидента; +- изменение severity; +- отправка в Telegram; +- запуск heal. + +## Phase 0: Baseline Before Coding + +Команды: + +```bash +cd /mnt/usb_hdd2/Projects/ActivityWatch-Russian +git status --short +export CARGO_TARGET_DIR=/home/igor/.cache/detmir-adk-rust-target +cd ansible +export no_proxy='localhost,127.0.0.1,192.168.100.18,10.10.10.13,10.10.10.2,10.10.10.0/24,192.168.100.0/24' +export NO_PROXY="$no_proxy" +ansible proxmox -i inventory.ini -m shell -a 'detmir-status --json' +ansible proxmox -i inventory.ini -m shell -a 'detmir-check --json' +``` + +Acceptance: + +- рабочее дерево понятно; +- текущий DetMir не сломан; +- нет попытки чинить unrelated проблемы. + +## Phase 1: Create Rust Crate + +Files: + +```text +adk-rust/Cargo.toml +adk-rust/crates/detmir-portal/Cargo.toml +adk-rust/crates/detmir-portal/src/main.rs +adk-rust/crates/detmir-portal/src/static/index.html +adk-rust/crates/detmir-portal/src/static/app.css +adk-rust/crates/detmir-portal/src/static/app.js +``` + +Minimum CLI: + +```text +detmir-portal --bind 127.0.0.1:8720 +detmir-portal --bind 127.0.0.1:8720 --json-smoke +detmir-portal --help +``` + +Cargo deps: + +```toml +anyhow.workspace = true +chrono.workspace = true +clap.workspace = true +reqwest.workspace = true +serde.workspace = true +serde_json.workspace = true +tiny_http.workspace = true +``` + +Acceptance: + +```bash +cd adk-rust +cargo fmt --all -- --check +CARGO_TARGET_DIR=/home/igor/.cache/detmir-adk-rust-target cargo test -p detmir-portal +CARGO_TARGET_DIR=/home/igor/.cache/detmir-adk-rust-target cargo clippy -p detmir-portal --all-targets -- -D warnings +CARGO_TARGET_DIR=/home/igor/.cache/detmir-adk-rust-target cargo build --release -p detmir-portal +``` + +## Phase 2: Backend Aggregation + +Implement typed structs: + +```text +PortalSummary +PortalBlock +OperatorView +ManagerView +OwnerView +IncidentItem +SourceStatus +``` + +Implement source functions: + +```text +read_detmir_status() +read_detmir_check() +read_grafana_check() +fetch_worktime_today() +fetch_one_c_health() +fetch_dlp_health() +build_incidents() +``` + +Rules: + +- every source has timeout; +- every source returns typed `SourceStatus`; +- never panic on bad source JSON; +- never expose credentials; +- if one source fails, portal still responds with degraded status. + +Acceptance: + +```bash +detmir-portal --json-smoke +curl -fsS http://127.0.0.1:8720/api/health | jq . +curl -fsS http://127.0.0.1:8720/api/summary | jq . +curl -fsS http://127.0.0.1:8720/api/operator | jq . +``` + +## Phase 3: Static UI MVP + +Implement one HTML app with tabs. + +Required visible text: + +- "Оператор"; +- "Руководитель"; +- "Владелец"; +- "Инциденты"; +- "Контур"; +- "Данные"; +- "Риски"; +- "Работа сегодня". + +Required behavior: + +- load `/api/summary`; +- load active tab API; +- show loading state; +- show source error state; +- refresh every 60 seconds; +- links open existing systems. + +No build step. + +Acceptance: + +```bash +curl -fsS http://127.0.0.1:8720/ | grep -F 'Оператор' +curl -fsS http://127.0.0.1:8720/ | grep -F 'Руководитель' +curl -fsS http://127.0.0.1:8720/ | grep -F 'Владелец' +``` + +## Phase 4: Browser Verification + +Use Playwright only after service works by curl. + +Desktop viewport: + +```text +1440x1000 +``` + +Mobile viewport: + +```text +390x844 +``` + +Check: + +- page is nonblank; +- no text overlap; +- tabs work; +- all API requests return 200; +- no console errors; +- status and cards visible; +- external links are present. + +Save screenshots under: + +```text +.playwright-cli/ +``` + +Do not commit screenshots unless explicitly requested. + +## Phase 5: Deployment + +Add playbook: + +```text +ansible/deploy_detmir_portal.yml +``` + +Deploy to Proxmox host: + +```text +/usr/local/bin/detmir-portal +/etc/systemd/system/detmir-portal.service +``` + +Service: + +```ini +[Unit] +Description=DetMir Operator Portal +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +EnvironmentFile=-/etc/detmir-portal.env +ExecStart=/usr/local/bin/detmir-portal --bind 127.0.0.1:8720 +Restart=on-failure +RestartSec=5s + +[Install] +WantedBy=multi-user.target +``` + +Nginx route: + +```text +location /portal/ { + proxy_pass http://127.0.0.1:8720/; +} +``` + +Important: + +- preserve existing Basic Auth; +- do not bypass gateway auth; +- do not expose raw internal ports publicly. + +Acceptance: + +```bash +systemctl is-active detmir-portal +curl -fsS http://127.0.0.1:8720/api/health +curl -k -I -H 'Host: dm.iri1968.dpdns.org' https://127.0.0.1/portal/ +``` + +External: + +```text +https://dm.iri1968.dpdns.org/portal/ +``` + +## Phase 6: Integrate Into Health Gates + +Add `detmir-portal-check` only after MVP is stable, or add a mode inside +`detmir-portal`: + +```bash +detmir-portal --check +``` + +It should verify: + +- service can aggregate all required sources; +- `/api/health` is OK; +- HTML includes role tabs; +- gateway route returns 401 without auth or 200 with auth. + +Then add artifact requirement: + +```text +scripts/check_detmir_rust_release_artifacts.sh -> detmir-portal +``` + +Add required service check to `detmir-check` only after production service is +stable for at least one run. + +## Phase 7: Rollback + +Rollback must be simple: + +```bash +sudo systemctl disable --now detmir-portal.service +sudo rm -f /usr/local/bin/detmir-portal +sudo rm -f /etc/systemd/system/detmir-portal.service +sudo systemctl daemon-reload +``` + +If nginx was changed: + +- keep backup before edit; +- restore previous gateway config; +- `nginx -t`; +- `systemctl reload nginx`. + +Never rollback by deleting unrelated gateway routes. + +## Phase 8: Post-MVP Enhancements + +Only after read-only portal is stable: + +1. role-aware views based on gateway username; +2. incident comments; +3. acknowledge/assign incident; +4. safe "run check now"; +5. safe "open Telegram status"; +6. PDF/HTML daily owner report; +7. historical trends; +8. AI summary with strict source citations; +9. action buttons with explicit allowlist and audit log. + +## Definition Of Done For MVP + +MVP is done only when all are true: + +- `detmir-portal` crate exists and builds in release; +- unit tests pass; +- clippy passes with `-D warnings`; +- portal serves HTML and JSON locally; +- portal deployed as systemd service on Proxmox; +- gateway URL works: + `https://dm.iri1968.dpdns.org/portal/`; +- browser screenshots checked desktop and mobile; +- no secrets in HTML/JSON/journald; +- `detmir-status` stays OK after deployment; +- runbook updated; +- git commit pushed. + +## Recommended First Implementation Slice + +Do not start with all screens. First slice: + +1. create `detmir-portal` crate; +2. implement `/api/health`; +3. implement `/api/summary`; +4. implement one static `/` page with four tabs but only Operator tab filled; +5. run local curl tests; +6. deploy service internally on Proxmox; +7. add `/portal/` gateway route; +8. browser-test external URL; +9. only then fill Manager and Owner views. + +This keeps risk low and gives a visible result quickly.