35 KiB
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. История решений (сжатая)
- Сначала починили критичные JS ошибки и нестабильный логин.
- Исправили фильтры статусов и прокинули корректный parsing на backend.
- Переделали скролл + sticky элементы.
- Добавили графы зависимостей, затем несколько итераций UX/производительности.
- Сделали lazy загрузку и кэш.
- Подчистили визуал (читаемость, стрелки, тема, favicon/logo).
- Реализовали выбор стенда PROD/DEV/TEST на логине с end-to-end маршрутизацией.
- Вынесли сервис в новую репу-папку dashboard и добавили правила агента + gitignore.
7. Что уже перенесено в новую репу dashboard
Из старого пути cloud-dashboard в новый dashboard перенесены:
- Dockerfile
- main.py
- requirements.txt
- static/
- k8s/
Дополнительно создано:
- .github/copilot-instructions.md (скопированы правила агента)
- .gitignore (Python/кэши/локальные артефакты)
8. Что важно для нового агента
- Не ломать контракт frontend/backend без явного запроса.
- Заголовки X-Deck-Token и X-Deck-Env обязательны.
- Поддерживать 3 стенда через whitelist.
- Не тащить секреты/токены в git.
- Для графа сохранять lazy-load и кэш (иначе будет тяжелая загрузка).
9. Рекомендованный план ближайших шагов
- Сделать первичный commit в новой репе dashboard.
- Настроить CI:
- lint
- build image
- push image
- deploy (опционально)
- Добавить smoke tests:
- /api/instances с разными env
- /api/graph с фильтрами
- Подготовить 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в namespaceterra. - 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`.
- В таблицу добавлен новый sortable-столбец
Шаг 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`.
- Добавлена мягкая zebra-разметка в основной таблице (
Шаг T — Полная деактивация графа + явная зебра основной таблицы (v9.40)
- Цель: полностью убрать интеракции графа и сделать полосатость в основной таблице заметной.
- Реализация:
- Из header удалена кнопка
Граф зависимостей. - Из деталей удалена вкладка
Графи панель графа. - Удалены обработчики клика по глобальному графу/модалке, чтобы граф не открывался вообще.
- Полосатость основной таблицы переведена на классы
row-base/row-alt, назначаемые вrenderDataRowпо индексу данных (не зависит отdetail-row). - Усилен контраст тонов зебры в light/dark.
- Выполнен version bump: .naeel/cloud-dashboard:v9.40`.
- Из header удалена кнопка
Шаг 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`.
- В колонке UUID добавлена иконка копирования
Шаг 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 на ВМ.
При старте нового чата сделать в таком порядке:
- Проверить, что в
k8s/deployment.yamlувеличен image tag относительно предыдущего. - Проверить соответствие stream-контракта:
_progress,_detail,_meta,_done. - Проверить, что
restoreSnapshot()вызывается доloadAllDetails(). - Прогнать smoke-flow вручную: login -> refresh -> reload -> refresh.
- Зафиксировать результат в этот файл отдельным шагом (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.
- На VM подтвержденно обновлен