# Dashboard Service - Full Chat Context For Next Agent Updated: 2026-04-16 Owner: naeel ## 1. Что это за сервис Cloud Dashboard - отдельный веб-сервис (backend + frontend), который показывает инстансы облака, фильтрацию, детали, историю операций и граф зависимостей. Текущая реализация: - Backend: Python + FastAPI - Frontend: статический SPA (HTML/CSS/vanilla JS) - Граф: Cytoscape - Runtime: контейнер в Kubernetes ## 2. Бизнес-цель Сделать рабочий дашборд для повседневной эксплуатации: - быстрый вход по API token - фильтрация по статусам - понятная таблица и карточки статистики - просмотр параметров/зависимостей инстанса - визуализация зависимостей - стабильная работа в PROD ## 3. Ключевые сделанные фичи ### 3.1 Аутентификация и сессия - Логин по API token. - Токен сохраняется в localStorage. - Logout очищает токен. ### 3.2 Выбор стенда (ВАЖНО) Сделано переключение окружений на экране логина: - PROD -> https://deck-api.ngcloud.ru/api/v1 - DEV -> https://deck-api-dev.ngcloud.ru/api/v1 - TEST -> https://deck-api-test.ngcloud.ru/api/v1 Как работает: - выбранный стенд сохраняется в localStorage как deck_env - frontend отправляет в backend заголовок X-Deck-Env - backend выбирает base URL по whitelist - если значение некорректное, fallback на test ### 3.3 Таблица и UX - Фильтры статусов с чекбоксами (увеличены). - Sticky header и правильный скролл области таблицы. - Сортировки по колонкам. - Статистика по статусам. - Detail row с вкладками: параметры, история, зависимости, граф. ### 3.4 Зависимости и графы Добавлены два режима: - граф для конкретного инстанса - общий граф для running Оптимизации: - lazy-load графа (не считать сразу) - кэш графа на backend с TTL - клиентская подвыборка subgraph - индикаторы загрузки - open large graph in modal/fullscreen behavior при большом числе узлов UI графа: - прямоугольные узлы - более крупный и читаемый текст - улучшенные стрелки/контраст - упрощенный layout (grid-подход) ### 3.5 Визуал и брендинг - day/night переключатель темы с сохранением в localStorage - favicon/logo из docs assets - удалены лишние декоративные элементы ## 4. Техническая архитектура ## Browser - SPA рендерит UI - вызывает backend по префиксу /dashboard/api - хранит deck_token и deck_env в localStorage ## Backend (FastAPI) Главные маршруты: - GET /api/instances - GET /api/instances/{uid} - GET /api/graph Транспорт: - принимает X-Deck-Token - принимает X-Deck-Env - проксирует запросы в Deck API выбранного окружения Граф: - собирается из списка инстансов и деталей - edges из dependencies/dependentInstances - кэш in-memory по ключу token+env+statuses ## Kubernetes - deployment/service/ingress в namespace terra - публичный доступ через /dashboard ## 5. Эксплуатационные детали - Сервис не использует БД. - Основная интеграция backend - только с Deck API. - Узкое место в производительности обычно внешний API, не сам UI. - В проекте уже обсужден путь: сначала стабилизировать логику на Python, потом при необходимости переписать backend на Go с сохранением API контрактов. ## 6. История решений (сжатая) 1) Сначала починили критичные JS ошибки и нестабильный логин. 2) Исправили фильтры статусов и прокинули корректный parsing на backend. 3) Переделали скролл + sticky элементы. 4) Добавили графы зависимостей, затем несколько итераций UX/производительности. 5) Сделали lazy загрузку и кэш. 6) Подчистили визуал (читаемость, стрелки, тема, favicon/logo). 7) Реализовали выбор стенда PROD/DEV/TEST на логине с end-to-end маршрутизацией. 8) Вынесли сервис в новую репу-папку dashboard и добавили правила агента + gitignore. ## 7. Что уже перенесено в новую репу dashboard Из старого пути cloud-dashboard в новый dashboard перенесены: - Dockerfile - main.py - requirements.txt - static/ - k8s/ Дополнительно создано: - .github/copilot-instructions.md (скопированы правила агента) - .gitignore (Python/кэши/локальные артефакты) ## 8. Что важно для нового агента 1) Не ломать контракт frontend/backend без явного запроса. 2) Заголовки X-Deck-Token и X-Deck-Env обязательны. 3) Поддерживать 3 стенда через whitelist. 4) Не тащить секреты/токены в git. 5) Для графа сохранять lazy-load и кэш (иначе будет тяжелая загрузка). ## 9. Рекомендованный план ближайших шагов 1) Сделать первичный commit в новой репе dashboard. 2) Настроить CI: - lint - build image - push image - deploy (опционально) 3) Добавить smoke tests: - /api/instances с разными env - /api/graph с фильтрами 4) Подготовить migration checklist для возможного будущего Go backend. ## 10. Definition of Done (текущее состояние) Сервис: - работает в k8s - логин и фильтры стабильны - графы и тема работают - выбор стенда реализован и сохраняется - код перенесен в новую папку/repo baseline ## 11. Текущие задачи (v9.7 — в работе) Все 4 задачи согласованы с оператором. Реализовывать в таком порядке: ### Задача 1 — Стриминговая загрузка инстансов - Backend `/api/instances/stream` уже есть (NDJSON, main.py строка ~297) - Фронт **ещё использует старый** `/api/instances` (index.html строка ~1004) - Нужно: переключить фронт на `/api/instances/stream`, строки добавлять в таблицу по одной по мере прихода - Все загруженные инстансы держать в памяти (`allInstances`). К API обращаться ТОЛЬКО по кнопке "Обновить" ### Задача 2 — Метка "Актуально на ЧЧ:ММ:СС" - Разместить **слева от кнопки "Обновить"** - Показывать время завершения последней загрузки - Обновлять при каждом refresh ### Задача 3 — Фильтрация зависимостей по чекбоксам - В блоке "Зависимости" инстанса показывать ТОЛЬКО те инстансы, статус которых выбран чекбоксами (running/suspended/etc) - При изменении чекбокса — сразу перерисовывать список зависимостей (без обращения к API, из памяти) ### Задача 4 — Переход по клику + кнопка "Назад" - Клик на инстанс в блоке "Зависимости" → открыть его строку/детали в таблице - Добавить кнопку "Назад" для возврата к исходному инстансу ### Правила разработки (напоминание): - ЧАСТО коммитить: `git add -A && git commit` на ВМ в `~/terra/dashboard` - Всё через SSH: `ssh -i ~/.ssh/naeel_vm_id_ed25519 -o StrictHostKeyChecking=no naeel@5.172.178.213` - Не ломать существующий рабочий код - Новая логика = новые функции в конце файла --- ## 12. Операционная дисциплина (КРИТИЧЕСКОЕ) Для нового агента это обязательное правило: - Все команды по проекту выполнять ТОЛЬКО через SSH на ВМ. SSH-шаблон: ssh -i ~/.ssh/naeel_vm_id_ed25519 -o StrictHostKeyChecking=no -o ConnectTimeout=10 naeel@5.172.178.213 КОМАНДА Локально ЗАПРЕЩЕНО выполнять: - git-команды проекта - docker-команды - kubectl-команды - curl к API проекта - bash/python/go/тесты проекта Почему: - так исключаются конфликты сред и ложные результаты; - единая точка выполнения (ВМ) дает предсказуемость и воспроизводимость. --- ## 13. Дисциплина загрузки таблицы (актуально) Обязательная модель работы таблицы: - В начале загрузки читаем данные инстансов и их параметры только из API. - Строка таблицы появляется сразу после чтения параметров конкретного инстанса. - Параллельные обращения к внешнему API запрещены: только последовательная обработка. - После завершения загрузки повторных обращений к API нет. - Новые обращения к API выполняются только по кнопке Обновить. Требования к UX загрузки: - В верхней строке перед кнопкой Обновить показывать прогресс в формате Загрузка X/Y. - После завершения загрузки показывать Актуально на ЧЧ:ММ:СС. - Рядом с меткой актуальности показывать время загрузки последнего обновления. Требования к таблице (операторский режим): - После чтения параметров конкретного инстанса из API сразу добавлять его строку в таблицу. - Добавить узкий столбец удаляемости сразу после статуса. - Зеленый индикатор показывать только если инстанс в suspended и прошло 14+ дней с входа в suspended (по данным API). - В раскрытой строке suspended-инстанса показывать пояснение по удаляемости. - Блоки Input/Output параметров не растягивать на всю ширину; сделать компактный читаемый layout. - Порог удаляемости, паттерны операций и пользовательские тексты хранить в конфиге фронта (без хардкода в вычислениях). ## 14. Журнал решений и хронология (без скрытых рассуждений) ### Шаг A — Режим загрузки таблицы - Цель: сделать поведение предсказуемым для оператора и убрать лишние запросы. - Входные ограничения: читать данные только из API; после загрузки работать из памяти; новые запросы только по Обновить. - Варианты: - A1: догружать детали по клику строки. - A2: читать детали сразу в стриме и больше не дергать API. - Выбранный вариант: A2. - Причина выбора: минимальная фрагментация данных, прозрачный UX, отсутствие неожиданной сетевой активности. - Проверка: поток отдает progress и done-событие; во фронте удалена догрузка деталей по клику. ### Шаг B — Последовательные вызовы API - Цель: исключить параллельные обращения к API. - Входные ограничения: запрет на параллельные запросы. - Варианты: - B1: batch-параллелизм с ограничением concurrency. - B2: строго последовательный обход uid. - Выбранный вариант: B2. - Причина выбора: полное соответствие ограничению и упрощение отладки. - Проверка: убраны gather/create_task/Semaphore для стрим-логики. ### Шаг C — Удаляемость suspended > 14 дней - Цель: добавить операторский индикатор удаления без лишней визуальной нагрузки. - Входные ограничения: считать только по API-данным. - Варианты: - C1: считать от dtState всегда. - C2: сначала искать suspend-операции, затем fallback на поля detail. - Выбранный вариант: C2. - Причина выбора: меньше ложных срабатываний при изменениях, не связанных с suspend. - Проверка: индикатор и пояснение показываются только для suspended; порог 14+ дней. ### Шаг D — Немонолитная, но умеренная структура - Цель: не превращать файл в монолит и одновременно не перегрузить архитектуру. - Входные ограничения: без чрезмерного рефакторинга. - Варианты: - D1: полный вынос JS в модули/файлы. - D2: локальная модульность внутри файла через конфиг и мелкие функции. - Выбранный вариант: D2. - Причина выбора: безопасно для текущей стадии, быстро настраивается, меньше риск поломок. - Реализация: - Конфиг политики/текстов вынесен в DASHBOARD_CFG. - Общие шаблоны строк через fmtText. - Рендер таблицы разделен на renderDataRow и renderDetailRow. - Константа TABLE_COLSPAN и состояние TABLE_STATE исключают магические значения. ### Шаг E — Backend как источник вычисляемых метаданных - Цель: убрать дублирующую бизнес-логику из фронта и сделать поведение единообразным. - Входные ограничения: без избыточного рефакторинга, только практичный шаг. - Варианты: - E1: оставить расчеты удаляемости только во фронте. - E2: считать удаляемость на backend и отдавать в stream-строке как мета-поля. - Выбранный вариант: E2. - Причина выбора: policy централизована на сервере, фронт становится проще и легче переконфигурируется. - Реализация: - В backend добавлен конфиг DELETE_POLICY и функции вычисления suspend-возраста. - В stream добавлено поле _meta.deleteCandidate. - Фронт сначала применяет backend _meta; клиентский fallback использовался временно и отключен на шаге G. ### Шаг F — Локальный snapshot таблицы в браузере - Цель: после перезагрузки страницы не обращаться к API до явного нажатия Обновить. - Входные ограничения: решение должно быть легким и не монолитным. - Варианты: - F1: только RAM (теряется при reload). - F2: локальный snapshot per user+env в browser storage. - Выбранный вариант: F2. - Причина выбора: быстрый старт UI после reload и автономная работа без нового API-вызова. - Реализация: - Добавлен snapshot storage key на основе user+env. - После успешного полного refresh snapshot сохраняется. - На init сначала пробуется восстановление snapshot; API вызывается только если snapshot отсутствует. - На logout snapshot очищается. ### Шаг G — Backend-only deleteability + snapshot schema v2 - Цель: исключить расхождение бизнес-правил между клиентом и сервером и безопасно версионировать локальный кэш. - Входные ограничения: без тяжелого рефакторинга. - Реализация: - Клиентский fallback вычисления удаляемости отключен в stream-обработке. - Поля `_canDelete`/`_suspendDays` инициализируются из backend `_meta.deleteCandidate` (или остаются нулевыми по умолчанию). - Формат snapshot переведен на `version=2` и `schema=instance-stream-v2`. - Ключ snapshot обновлен до `dashboard_snapshot_v2`, чтобы отделить новый формат от старого. ### Шаг H — Релиз v9.22 (build/push/deploy + верификация) - Цель: доставить изменения шага G в кластер и подтвердить фактическую версию рантайма. - Реализация: - Собран образ `naeel/cloud-dashboard:v9.22`. - Выполнен push в Docker Hub. - Применен `k8s/deployment.yaml` в namespace `terra`. - Rollout завершен успешно. - Проверено соответствие версии в deployment и pod. - Проверка: - `spec.image`: `naeel/cloud-dashboard:v9.22`. - `pod.image`: `naeel/cloud-dashboard:v9.22`. - `pod.imageID`: `docker.io/naeel/cloud-dashboard@sha256:18e87ff592a333cc280fd2af13390f47b111616b8da31f2e32d7ac24e3a3a7ea`. - `pod` статус: `Running`, `Ready=true`, `Restarts=0`. - Внутренний smoke-check: `GET http://127.0.0.1:8080/` -> `HTTP 200`. - Внешний smoke-check (ingress `terra.k8c.ru`): `HTTPS /` -> `200`, `HTTP /` -> `308`. ### Шаг I — Добавлен столбец UUID после Имени (v9.29) - Цель: сделать идентификацию инстанса в таблице мгновенной без раскрытия строки. - Реализация: - В таблицу добавлен новый sortable-столбец `UUID` сразу после `Имя`. - В рендере строк добавлена ячейка `instanceUid` (моноширинный стиль). - `TABLE_COLSPAN` увеличен с `8` до `9`. - Обновлены мобильные индексы скрываемых колонок (`nth-child`) после сдвига структуры. - Выполнен version bump: .naeel/cloud-dashboard:v9.29`. ### Шаг J — Фикс `renderDataRow is not defined` (v9.30) - Цель: убрать runtime-ошибку в таблице после добавления UUID-колонки. - Реализация: - `renderDataRow` и `renderDetailRow` вынесены из `toggle()` в общий scope. - Рендер таблицы (`render`) снова вызывает доступные функции без `ReferenceError`. - Выполнен version bump: .naeel/cloud-dashboard:v9.30`. ### Шаг K — Перенос светофора в колонку Изменён (v9.31) - Цель: убрать отдельный столбец индикатора и показывать только полезный сигнал. - Реализация: - Удален отдельный столбец светофора из таблицы. - Зеленая точка теперь рисуется после времени в колонке `Изменён` только для eligible (`suspended` > 14 дней, по backend-метаданным). - Серые точки полностью убраны. - `TABLE_COLSPAN` обновлен с 9 до 8. - Обновлены мобильные индексы скрываемых колонок. - Выполнен version bump: .naeel/cloud-dashboard:v9.31`. ### Шаг L — Возврат потокового вывода строк (v9.32) - Цель: показывать строки таблицы по мере загрузки stream, без ожидания `_done`. - Реализация: - Исправлен `scheduleRender()`: вместо perpetual-debounce применяется throttle (если таймер уже запущен, новый не ставится). - UI теперь обновляется регулярно в ходе потока, а не откладывается до конца непрерывного чтения. - Выполнен version bump: .naeel/cloud-dashboard:v9.32`. ### Шаг M — Левое выравнивание 4 колонок в Параметрах (v9.33) - Цель: сделать `Input/Output` читабельным в табличном стиле без центрирования. - Реализация: - `params-grid` переведен на `max-content` колонки и выравнивание влево. - `p-item` переведен на grid из 2 колонок (`key` + `value`) с увеличенным фиксированным зазором между ними. - `p-key` и `p-val` выровнены влево. - Ширина колонок определяется содержимым; при реальном переполнении включается перенос (`overflow-wrap: anywhere`). - Выполнен version bump: .naeel/cloud-dashboard:v9.33`. ### Шаг N — Ширина колонки имен: max + 10ch (v9.34) - Цель: сделать значения читаемыми по единой вертикали и увеличить отступ от имен. - Реализация: - Для каждой секции (`Input`/`Output`) вычисляется максимальная длина имени параметра. - Ширина колонки имен задается формулой: `maxKeyLen * 1ch + 10ch`. - Значения начинаются строго от единой левой границы value-колонки в каждой секции. - Выполнен version bump: .naeel/cloud-dashboard:v9.34`. ### Шаг O — Output +20 и copy-иконка параметров (v9.35) - Цель: повысить читаемость `Input/Output` и ускорить копирование значений. - Реализация: - Увеличен межсекционный отступ между `Input` и `Output` на +20. - Каждая строка параметров переведена в 3 зоны: `key | value | copy`. - Иконка копирования фиксирована в конце строки (правая зона), со вспышкой `✓` после успешного копирования. - Добавлена функция `copyParamValue` с Clipboard API и fallback через `execCommand`. - Выполнен version bump: .naeel/cloud-dashboard:v9.35`. ### Шаг P — Скрытие copy для пустых + фикс дневного режима (v9.36) - Цель: убрать визуальный шум и исправить неполное переключение в light theme. - Реализация: - `renderCopyBtnHtml` не рисует кнопку копирования для пустых значений и `—`. - Добавлены `body.light` стили для `.stat-card` и `.stat-card .lbl`, чтобы карточки статистики реально становились светлыми. - Выполнен version bump: .naeel/cloud-dashboard:v9.36`. ### Шаг Q — Light-theme для status-badge (v9.37) - Цель: убрать темные/кислотные бейджи статусов в дневном режиме. - Реализация: - Добавлены `body.light` переопределения для `.s-running`, `.s-suspended`, `.s-deleting`, `.s-deleted`, `.s-pending`, `.s-creating`, `.s-default`. - Для светлой темы введены мягкие светлые фоны и контрастный текст, плюс тонкая рамка бейджа. - Выполнен version bump: .naeel/cloud-dashboard:v9.37`. ### Шаг R — Контрастный фон блока деталей/параметров (v9.38) - Цель: убрать визуальное слияние раскрытой области деталей с основной таблицей. - Реализация: - Усилен контейнер `.detail-box`: отдельный фон, рамка, скругление, внутренний отступ от таблицы. - Добавлен фон для `.detail-row td` и контрастная подложка вкладок `.tabs`. - Увеличен контраст панелей `.panel-params`, `.panel-history`, `.panel-deps` в dark/light режимах. - Выполнен version bump: .naeel/cloud-dashboard:v9.38`. ### Шаг S — Полосатые строки (zebra) для читаемости (v9.39) - Цель: улучшить восприятие строк, чтобы данные не сливались по горизонтали. - Реализация: - Добавлена мягкая zebra-разметка в основной таблице (`tbody tr.data-row`) с разным тоном соседних строк. - Добавлена zebra-разметка в таблице истории (`.hist-table tbody tr`). - Добавлена лёгкая zebra-подложка для строк параметров (`.p-item`). - Отдельно настроены оттенки для `dark` и `light` темы. - Выполнен version bump: .naeel/cloud-dashboard:v9.39`. ### Шаг T — Полная деактивация графа + явная зебра основной таблицы (v9.40) - Цель: полностью убрать интеракции графа и сделать полосатость в основной таблице заметной. - Реализация: - Из header удалена кнопка `Граф зависимостей`. - Из деталей удалена вкладка `Граф` и панель графа. - Удалены обработчики клика по глобальному графу/модалке, чтобы граф не открывался вообще. - Полосатость основной таблицы переведена на классы `row-base/row-alt`, назначаемые в `renderDataRow` по индексу данных (не зависит от `detail-row`). - Усилен контраст тонов зебры в light/dark. - Выполнен version bump: .naeel/cloud-dashboard:v9.40`. ### Шаг U — Copy для UUID + жирные имена инстансов (v9.41) - Цель: ускорить работу оператора в таблице и усилить визуальный приоритет названий. - Реализация: - В колонке UUID добавлена иконка копирования `⧉` для каждой строки. - Кнопка UUID-копирования использует существующую логику `copyParamValue` и не триггерит раскрытие строки (`event.stopPropagation()`). - Имена инстансов в колонке `Имя` переведены в жирное начертание (`.inst-name`). - Для `uuid-copy` добавлены отдельные hover/ok стили в dark/light теме. - Выполнен version bump: .naeel/cloud-dashboard:v9.41`. ### Шаг V — Правильная логика иконки day/night (v9.42) - Цель: сделать поведение кнопки темы интуитивным (иконка показывает целевое переключение, а не текущий режим). - Реализация: - В `applyTheme` изменена логика кнопки: - при активной дневной теме показывается `🌙` (переключение на ночь), - при активной ночной теме показывается `☀` (переключение на день). - Обновлены `title`-подсказки на действие: `Переключить на ... тему`. - Выполнен version bump: naeel/cloud-dashboard:v9.42. ## 15. HANDOFF для нового чата (что делать дальше без доп. подсказок) Текущее состояние (baseline): - Таблица загружается через `/instances/stream` один раз на refresh, далее работает из памяти. - Повторных API-вызовов между refresh нет. - После reload данные восстанавливаются из browser snapshot (`version=2`, `schema=instance-stream-v2`). - Удаляемость берется только из backend `_meta.deleteCandidate`. - В кластере задеплоен образ `naeel/cloud-dashboard:v9.43`. Нельзя нарушать (инварианты): - Не добавлять параллельные обращения к внешнему API. - Не делать догрузку detail по клику строки. - Не возвращать клиентский fallback вычисления удаляемости. - Все проектные команды выполнять только через SSH на ВМ. При старте нового чата сделать в таком порядке: 1. Проверить, что в `k8s/deployment.yaml` увеличен image tag относительно предыдущего. 2. Проверить соответствие stream-контракта: `_progress`, `_detail`, `_meta`, `_done`. 3. Проверить, что `restoreSnapshot()` вызывается до `loadAllDetails()`. 4. Прогнать smoke-flow вручную: login -> refresh -> reload -> refresh. 5. Зафиксировать результат в этот файл отдельным шагом (H, I, ...). Примечание по sync-проверке путей: - Путь `/home/naeel/remote_dev/dashboard` может отсутствовать на ВМ, поэтому прямое сравнение хешей с `~/terra/dashboard` не всегда выполнимо на стороне ВМ. - Если путь отсутствует, источником истины считать `~/terra/dashboard`, а для альтернативной проверки синхронизации запросить у оператора допустимый способ сравнения. Чеклист smoke-flow (обязательный): - После login/refresh видно `Загрузка X/Y`. - После завершения видно `Актуально на ... (загрузка N c)`. - После reload видно `Актуально на ... (из локального snapshot)`. - До кнопки `Обновить` новых обращений к API нет. - Индикатор удаляемости совпадает с backend `_meta.deleteCandidate`. Definition of Done для следующего изменения: - Выполнен version bump (минимум patch) в image tag. - Изменение не ломает table-first режим и snapshot-режим. - Документация обновлена новым шагом в разделе 14 или отдельной секцией. ### Шаг W — Реальный фикс theme-toggle после валидации pod (v9.43) - Цель: гарантировать, что в рантайме действительно применяется инвертированная UX-логика иконки day/night. - Причина: после релиза v9.42 в pod обнаружилась старая логика `applyTheme`. - Реализация: - На VM подтвержденно обновлен `static/index.html`: - `light -> 🌙` (переключение на ночь), - `dark -> ☀` (переключение на день), - `title` показывает действие переключения. - Выполнен повторный version bump: `naeel/cloud-dashboard:v9.43`.