852 lines
60 KiB
HTML
852 lines
60 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Hermes Hub — Панель управления</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@500;600;700;800&family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||
<link rel="stylesheet" href="style.css">
|
||
<link rel="stylesheet" href="static/workflow.css">
|
||
<link rel="icon" href="static/hermes-logo.png">
|
||
</head>
|
||
<body data-view="overview" data-workflow-mode="live">
|
||
<div id="app" class="app-layout">
|
||
<!-- Seven workspaces share the same navigation. -->
|
||
<aside class="sidebar">
|
||
<div class="brand">
|
||
<img class="brand-logo" src="static/hermes-logo.png" alt="Логотип Hermes Hub">
|
||
<div class="brand-text">
|
||
<span class="brand-title">HERMES HUB</span>
|
||
<span class="brand-subtitle">Крона · Бизнес-экосистема</span>
|
||
</div>
|
||
</div>
|
||
|
||
<nav class="nav-menu">
|
||
<button class="nav-item active" data-view="overview">
|
||
<svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M3 10 12 3l9 7v11h-6v-7H9v7H3Z"/></svg>
|
||
<span class="nav-label">Обзор</span>
|
||
</button>
|
||
<button class="nav-item" data-view="accounts">
|
||
<svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M16 21v-3a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v3 M9 10a4 4 0 1 0 0-8 4 4 0 0 0 0 8 M17 3a4 4 0 0 1 0 8 M22 21v-3a4 4 0 0 0-3-4"/></svg>
|
||
<span class="nav-label">Аккаунты</span>
|
||
<span class="nav-badge" id="nav-accounts-count">—</span>
|
||
</button>
|
||
<button class="nav-item" data-view="routing">
|
||
<svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 4v16 M5 8h14 M5 16h14 M16 5l3 3-3 3 M16 13l3 3-3 3"/></svg>
|
||
<span class="nav-label">Маршрутизация</span>
|
||
</button>
|
||
<button class="nav-item" data-view="skills">
|
||
<svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>
|
||
<span class="nav-label">Скиллы</span>
|
||
<span class="nav-badge" id="nav-skills-count">—</span>
|
||
</button>
|
||
<button class="nav-item" data-view="analytics">
|
||
<svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M3 3v18h18 M7 16v-5 M12 16V7 M17 16v-8"/></svg>
|
||
<span class="nav-label">Аналитика</span>
|
||
</button>
|
||
<button class="nav-item" data-view="health">
|
||
<svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2 3 6v6c0 5 9 10 9 10s9-5 9-10V6Z M8 12l3 3 5-6"/></svg>
|
||
<span class="nav-label">Состояние системы</span>
|
||
</button>
|
||
<button class="nav-item" data-view="logs">
|
||
<svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 2h9l4 4v16H5V2Z M8 10h8 M8 14h8 M8 18h5"/></svg>
|
||
<span class="nav-label">Журнал событий</span>
|
||
</button>
|
||
<button class="nav-item" data-view="settings">
|
||
<svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M9 3h6l1 3 3 1 2 5-2 5-3 1-1 3H9l-1-3-3-1-2-5 2-5 3-1Z M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8"/></svg>
|
||
<span class="nav-label">Настройки</span>
|
||
</button>
|
||
</nav>
|
||
|
||
<div class="brand-panel"><img src="static/hermes-logo.png" alt=""><p>Рабочее пространство<br>вашей агентной системы</p><span>Hermes Hub · Крона</span></div>
|
||
<div class="sidebar-footer">
|
||
<div class="source-indicator" id="source-indicator">
|
||
<span class="status-dot healthy"></span>
|
||
<span class="source-text" id="source-text">Загрузка данных...</span>
|
||
</div>
|
||
<div class="version-tag" id="version-tag">Hermes Hub Web</div>
|
||
<div class="commit-tag" id="commit-tag" style="font-size:11px; color:var(--text-muted); font-family:var(--font-mono); margin-top:2px;">—</div>
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- Main Content Container -->
|
||
<main class="main-wrapper">
|
||
<!-- Top Header -->
|
||
<header class="top-header">
|
||
<div class="header-left">
|
||
<h1 class="header-title" id="page-title">Обзор системы</h1>
|
||
<span class="header-readiness-badge" id="header-readiness-badge">
|
||
<span class="status-dot"></span>
|
||
<span id="header-readiness-text">Инициализация...</span>
|
||
</span>
|
||
<span class="header-hermes-config-badge hidden" id="header-hermes-config-badge" style="display:inline-flex; align-items:center; font-size:12px; color:var(--text-muted); background:var(--surface-muted); padding:3px 8px; border-radius:var(--radius-sm); border:1px solid var(--border-subtle); margin-left:6px;"></span>
|
||
<button class="header-update-badge hidden" id="header-update-badge" title="Нажмите, чтобы посмотреть детали обновления">
|
||
<span class="status-dot warning"></span>
|
||
<span id="header-update-text">Доступно обновление</span>
|
||
</button>
|
||
</div>
|
||
<div class="workflow-mode" role="group" aria-label="Режим workflow">
|
||
<button class="workflow-mode-btn active" id="workflow-mode-live" data-mode="live">LIVE</button>
|
||
<button class="workflow-mode-btn" id="workflow-mode-edit" data-mode="edit">EDIT</button>
|
||
</div>
|
||
<div class="header-actions"><button class="header-search" id="hub-search-open" aria-label="Поиск по хабу"><span>⌕ Поиск</span><kbd>Ctrl K</kbd></button><button class="header-icon" id="hub-notifications" aria-label="Уведомления" title="Последние события"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 8a6 6 0 0 1 12 0v6l2 3H4l2-3Z M9 20h6"/></svg></button><button class="header-icon" id="hub-settings" aria-label="Открыть настройки" title="Настройки"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 3h6l1 3 3 1 2 5-2 5-3 1-1 3H9l-1-3-3-1-2-5 2-5 3-1Z M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8"/></svg></button><div class="hub-user" title="Н/Д: API не передаёт профиль пользователя"><span>H</span><div>Локальный хаб<small>Профиль пользователя: Н/Д</small></div></div></div><div class="header-page-actions">
|
||
<button class="btn btn-secondary" id="btn-refresh-all" title="Обновить квоты всех профилей">
|
||
<span class="btn-icon">↻</span>
|
||
<span>Обновить всё</span>
|
||
</button>
|
||
<button class="btn btn-primary" id="btn-add-account">
|
||
<span class="btn-icon">+</span>
|
||
<span>Добавить аккаунт</span>
|
||
</button>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Dynamic Views (7 Control Sections) -->
|
||
<div class="content-scroll">
|
||
<!-- 1. OVERVIEW VIEW -->
|
||
<section id="view-overview" class="view-pane active">
|
||
<div class="workflow-toolbar">
|
||
|
||
<div class="workflow-toolbar-actions">
|
||
<span class="workflow-save-state" id="workflow-save-state">Сохранено</span>
|
||
<button class="btn btn-secondary" id="btn-workflow-save">Сохранить workflow</button>
|
||
<button class="btn btn-primary" id="btn-agent-add">+ Добавить агента</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="workflow-kpis" aria-label="Оперативные показатели">
|
||
<article class="workflow-kpi"><span>Активные задачи</span><strong id="workflow-kpi-active">Загрузка…</strong><small id="workflow-kpi-active-reason">Получение снапшота</small></article>
|
||
<article class="workflow-kpi"><span>Готовность ролей</span><strong id="workflow-kpi-online">Загрузка…</strong><small id="workflow-kpi-online-reason">Получение снапшота</small></article>
|
||
<article class="workflow-kpi"><span>Ответ модели · p50</span><strong id="workflow-kpi-latency">Загрузка…</strong><small id="workflow-kpi-latency-reason">Получение телеметрии</small></article>
|
||
<article class="workflow-kpi"><span>Использование токенов</span><strong id="workflow-kpi-tokens">Загрузка…</strong><small id="workflow-kpi-tokens-reason">Получение телеметрии</small></article>
|
||
<article class="workflow-kpi"><span>Успешность вызовов</span><strong id="workflow-kpi-success">Загрузка…</strong><small id="workflow-kpi-success-reason">Получение телеметрии</small></article>
|
||
<article class="workflow-kpi"><span>Статус системы</span><strong id="workflow-kpi-status">Загрузка…</strong><small id="workflow-kpi-status-reason">Проверка готовности</small></article>
|
||
</div>
|
||
|
||
<div class="workflow-main-layout">
|
||
<section class="workflow-board-card">
|
||
<header class="workflow-board-header">
|
||
<div><strong id="workflow-title">Workflow</strong> <span id="workflow-run-state" class="workflow-run-state">Загрузка…</span></div>
|
||
<label>Итерация: <strong id="workflow-iteration">Н/Д</strong> / <input id="workflow-max-iterations" type="number" min="1" max="100" value="5"></label>
|
||
<div class="workflow-zoom"><button id="workflow-zoom-out" title="Уменьшить">−</button><span id="workflow-zoom-value">100%</span><button id="workflow-zoom-in" title="Увеличить">+</button><button id="workflow-fit" title="Вписать граф">⛶</button></div>
|
||
</header>
|
||
<div class="workflow-canvas" id="workflow-canvas" tabindex="0" aria-label="Редактор графа workflow">
|
||
<div class="canvas-tools" role="toolbar" aria-label="Инструменты холста"><button id="canvas-select" title="Выбор агента" aria-label="Выбор агента">↖</button><button id="canvas-add" title="Добавить агента" aria-label="Добавить агента на холст">+</button><button id="canvas-connect" title="Соединить агентов в EDIT" aria-label="Создать связь агентов">⇄</button><button id="canvas-fit" title="Вписать граф" aria-label="Вписать весь граф">⛶</button><button id="canvas-arrange" title="Разнести узлы (черновик)" aria-label="Разнести узлы">▦</button></div><div id="workflow-viewport" class="workflow-viewport">
|
||
<svg id="workflow-edges" class="workflow-edges" aria-hidden="true"><defs><marker id="wf-arrow-success" class="edge-success" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto" markerUnits="userSpaceOnUse"><path d="M1,1 L8,4.5 L1,8 L3,4.5 Z"/></marker><marker id="wf-arrow-return" class="edge-return" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto" markerUnits="userSpaceOnUse"><path d="M1,1 L8,4.5 L1,8 L3,4.5 Z"/></marker><marker id="wf-arrow-next" class="edge-next" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto" markerUnits="userSpaceOnUse"><path d="M1,1 L8,4.5 L1,8 L3,4.5 Z"/></marker></defs><g id="workflow-edge-layer"></g></svg>
|
||
<div id="workflow-node-layer" class="workflow-node-layer"></div><svg class="workflow-labels" aria-hidden="true"><g id="workflow-label-layer"></g></svg>
|
||
</div>
|
||
<div id="workflow-empty" class="workflow-empty hidden"><strong>В workflow пока нет агентов</strong><span>Добавьте агента, затем переключитесь в EDIT и соедините узлы.</span></div>
|
||
<div class="workflow-minimap" id="workflow-minimap" aria-label="Мини-карта"></div>
|
||
</div>
|
||
<footer class="workflow-legend">
|
||
<span><i class="state-waiting"></i>Ожидает</span><span><i class="state-working"></i>Работает</span><span><i class="state-reviewing"></i>Проверяет</span><span><i class="state-error"></i>Ошибка</span><span><i class="state-completed"></i>Завершено</span>
|
||
<span class="edge-legend success">→ Успех</span><span class="edge-legend return">⇢ Возврат / ошибка</span><span class="edge-legend next">→ Следующий шаг</span>
|
||
</footer>
|
||
</section>
|
||
|
||
<aside class="workflow-inspector" id="workflow-inspector">
|
||
<div class="workflow-inspector-empty"><strong>INSPECTOR</strong><span>Выберите агента на графе</span></div>
|
||
</aside>
|
||
|
||
<div class="workflow-bottom-grid">
|
||
<section class="workflow-events"><header><strong>Последние события</strong><button id="workflow-events-all">Журнал событий →</button></header><div id="workflow-events-list" class="workflow-events-list"><p>Загрузка событий…</p></div></section>
|
||
<section class="workflow-statistics"><header><strong>Статистика workflow</strong></header><div id="workflow-statistics-content">Н/Д: снапшот ещё не получен</div></section><section class="workflow-run-panel"><header><strong>Активные задачи · LIVE</strong></header><div id="workflow-active-task" class="inspector-value"></div><textarea aria-label="Задача workflow" id="workflow-task" placeholder="Опишите реальную задачу для workflow"></textarea><div><button class="btn btn-primary" id="btn-workflow-start">Запустить workflow</button><button class="btn btn-secondary" id="btn-workflow-stop">Остановить</button></div><p id="workflow-run-error" class="workflow-run-error"></p></section>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 2. ACCOUNTS VIEW -->
|
||
<section id="view-accounts" class="view-pane">
|
||
<div class="toolbar">
|
||
<div class="search-box">
|
||
<span class="search-icon">🔍</span>
|
||
<input type="text" id="accounts-search" placeholder="Поиск по аккаунту, почте, роли или модели...">
|
||
</div>
|
||
<div class="filters-row">
|
||
<select id="filter-provider" class="select-filter">
|
||
<option value="all">Все провайдеры</option>
|
||
<option value="antigravity">Google Antigravity</option>
|
||
<option value="openai-codex">OpenAI Codex</option>
|
||
<option value="opencode-go">OpenCode Go</option>
|
||
<option value="claude">Claude (Anthropic)</option>
|
||
<option value="grok">Grok (xAI)</option>
|
||
<option value="ollama">Ollama</option>
|
||
<option value="local">Локальные (Local LLM)</option>
|
||
<option value="openrouter">OpenRouter</option>
|
||
<option value="nvidia">NVIDIA NIM</option>
|
||
</select>
|
||
<select id="filter-health" class="select-filter">
|
||
<option value="all">Все состояния</option>
|
||
<option value="healthy">Работает</option>
|
||
<option value="warning">Предупреждение</option>
|
||
<option value="quota_exhausted">Квота исчерпана</option>
|
||
<option value="auth_required">Требуется вход</option>
|
||
<option value="disabled">Отключён / Резерв</option>
|
||
</select>
|
||
<button class="btn btn-secondary btn-sm" id="btn-export-quotas" onclick="openExportQuotasModal()" title="Выгрузить отчет о лимитах и квотах в формате JSON или CSV">
|
||
<span>📥 Экспорт лимитов (JSON / CSV)</span>
|
||
</button>
|
||
<div class="toolbar-stats" id="accounts-stats-summary">
|
||
Список аккаунтов ещё не получен
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="account-summary" id="account-summary"></div><div id="accounts-container" class="accounts-groups-container">
|
||
<!-- Rendered by app.js -->
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 3. ROUTING VIEW (Main Control Center) -->
|
||
|
||
<section id="view-routing" class="view-pane">
|
||
<div class="routing-toolbar-actions">
|
||
<div style="display:flex; align-items:center; gap:8px;">
|
||
<label for="routing-default-role-select" style="font-weight:600; font-size:12px;">Роль по умолчанию:</label>
|
||
<select id="routing-default-role-select" class="select-filter" style="font-size:12px;">
|
||
<option value="manager">Главный менеджер (manager)</option>
|
||
<option value="developer-1">Кодер 1 (developer-1)</option>
|
||
<option value="developer-2">Кодер 2 (developer-2)</option>
|
||
<option value="code-reviewer">Ревьюер кода (code-reviewer)</option>
|
||
<option value="researcher">Исследователь (researcher)</option>
|
||
<option value="tester">Тестировщик (tester)</option>
|
||
<option value="integration-expert">Интегратор (integration-expert)</option>
|
||
<option value="security-expert">Эксперт по безопасности (security-expert)</option>
|
||
<option value="tech-writer">Технический писатель (tech-writer)</option>
|
||
<option value="analyst">Аналитик требований (analyst)</option>
|
||
<option value="dependency-agent">Агент зависимостей (dependency-agent)</option>
|
||
</select>
|
||
</div>
|
||
<button class="btn btn-secondary btn-sm" id="btn-routing-auto-assign" onclick="openAutoAssignPreviewModal()" title="Автоматическое распределение подключенных аккаунтов по ролям">
|
||
<span>⚡ Авто-распределение</span>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="view-header-note" style="display:flex; justify-content: space-between; align-items: center;">
|
||
<div>
|
||
<i class="fa-solid fa-circle-info" style="color:var(--status-warning);"></i>
|
||
Перетащите аккаунт из правой панели в нужную роль и установите порядок (приоритет) использования.
|
||
<br>
|
||
Система будет использовать аккаунты сверху вниз по списку при выполнении задач с учётом квот и состояния.
|
||
</div>
|
||
</div>
|
||
|
||
<div class="routing-grid" style="display: flex; gap: 24px; height: calc(100vh - 150px);">
|
||
<!-- LEFT COLUMN: Roles -->
|
||
<div class="routing-left-col" id="routing-roles-container" style="flex: 2; overflow-y: auto; padding-right: 8px;">
|
||
<!-- Rendered by app.js -->
|
||
</div>
|
||
|
||
<!-- RIGHT COLUMN: Available Accounts -->
|
||
<div class="routing-right-col" style="flex: 1; display: flex; flex-direction: column; border: 1px solid var(--border); border-radius: var(--radius-md); background-color: var(--surface-muted);">
|
||
<div style="padding: 16px; border-bottom: 1px solid var(--border-subtle);">
|
||
<div style="display: flex; justify-content: space-between; margin-bottom: 12px;">
|
||
<h3 style="font-family: var(--font-title); font-size: 14px; text-transform: uppercase;">Доступные аккаунты</h3>
|
||
<span id="available-accounts-count" class="text-muted" style="font-size: 12px;">Список ещё не получен</span>
|
||
</div>
|
||
<div class="input-group">
|
||
<input type="text" id="routing-account-search" placeholder="Поиск по аккаунтам, ролям или моделям..." style="width: 100%;">
|
||
</div>
|
||
<div style="display: flex; gap: 8px; margin-top: 8px;">
|
||
<select id="routing-filter-provider" style="flex:1;"><option value="all">Все провайдеры</option></select>
|
||
<select id="routing-filter-status" style="flex:1;"><option value="all">Все статусы</option></select>
|
||
</div>
|
||
</div>
|
||
<div class="available-accounts-list" id="routing-available-container" style="flex: 1; overflow-y: auto; padding: 12px; display: flex; flex-direction: column; gap: 8px;">
|
||
<!-- Rendered by app.js -->
|
||
</div>
|
||
<div style="padding: 16px; text-align: center; font-size: 12px; color: var(--text-muted); border-top: 1px dashed var(--border-subtle);">
|
||
<i class="fa-solid fa-arrows-up-down-left-right"></i> Перетащите аккаунт в нужную роль или нажмите «+» для добавления
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
|
||
<!-- SKILLS VIEW -->
|
||
<section id="view-skills" class="view-pane">
|
||
<div class="toolbar">
|
||
<div class="search-box">
|
||
<span class="search-icon">🔍</span>
|
||
<input type="text" id="skills-search" placeholder="Поиск по скиллам, тегам, описанию или агентам...">
|
||
</div>
|
||
<div class="filters-row">
|
||
<select id="filter-skills-source" class="select-filter">
|
||
<option value="all">Все источники</option>
|
||
</select>
|
||
<select id="filter-skills-status" class="select-filter">
|
||
<option value="all">Все статусы</option>
|
||
<option value="valid">Валидные (Doctor OK)</option>
|
||
<option value="invalid">С ошибками</option>
|
||
<option value="assigned">Назначенные субагентам</option>
|
||
<option value="unassigned">Не назначенные</option>
|
||
</select>
|
||
<button class="btn btn-secondary btn-sm" id="btn-refresh-skills">↻ Обновить скиллы</button>
|
||
<div class="toolbar-stats" id="skills-stats-summary">
|
||
Поиск скиллов...
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="section-card" style="margin-top:12px;">
|
||
<div class="section-card-header" style="display:flex; justify-content:space-between; align-items:center;">
|
||
<div>
|
||
<div class="section-card-title">Реестр навыков (Agent Skills · SKILL.md)</div>
|
||
<div class="section-card-subtitle">Обнаруженные файлы SKILL.md, валидация триггеров и назначение субагентам</div>
|
||
</div>
|
||
<div id="skills-usage-summary-badge" class="badge">Н/Д: вызовы со скиллами ещё не регистрировались</div>
|
||
</div>
|
||
<div id="skills-cards-container" class="skills-grid" style="display:grid; grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); gap:16px; padding-top:12px;">
|
||
<!-- Rendered by app.js -->
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
|
||
<!-- 4. ANALYTICS VIEW (P0-1 & P0-5) -->
|
||
<section id="view-analytics" class="view-pane">
|
||
<div class="view-header-note">
|
||
Телеметрия вызовов и отказоустойчивости за 24 часа. Метрики отражают реальные замеры задержки и частоты ошибок при маршрутизации.
|
||
</div>
|
||
<div class="overview-grid">
|
||
<div class="kpi-card">
|
||
<div class="kpi-label">Всего вызовов (24ч)</div>
|
||
<div class="kpi-value text-accent" id="analytics-total-calls">—</div>
|
||
<div class="kpi-sub" id="analytics-calls-breakdown">—</div>
|
||
</div>
|
||
<div class="kpi-card">
|
||
<div class="kpi-label">Доля отказов / ошибок</div>
|
||
<div class="kpi-value" id="analytics-error-rate">—</div>
|
||
<div class="kpi-sub" id="analytics-error-rate-sub">—</div>
|
||
</div>
|
||
<div class="kpi-card">
|
||
<div class="kpi-label">Латентность (p50 / p95 / max)</div>
|
||
<div class="kpi-value text-info" id="analytics-latency-p50">—</div>
|
||
<div class="kpi-sub" id="analytics-latency-sub">—</div>
|
||
</div>
|
||
<div class="kpi-card">
|
||
<div class="kpi-label">Расход токенов</div>
|
||
<div class="kpi-value text-muted" id="analytics-tokens-total">Н/Д</div>
|
||
<div class="kpi-sub" id="analytics-tokens-sub">Н/Д: провайдеры не отдают токены</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="analytics-workspace"><section class="section-card"><h2 class="section-card-title">Использование токенов</h2><div id="analytics-token-chart" class="metric-chart">Н/Д: снапшот ещё не получен</div></section><section class="section-card"><h2 class="section-card-title">Запросы по ролям</h2><div id="analytics-role-chart" class="metric-chart">Н/Д: снапшот ещё не получен</div></section><aside class="section-card"><h2 class="section-card-title">Окно измерений</h2><p class="inspector-value">Последние 24 часа</p><p class="inspector-value">Источник: telemetry из /api/snapshot. Произвольный период API не предоставляет.</p><button class="btn btn-secondary" id="analytics-refresh">Обновить</button></aside></div><div class="analytics-tables"><div class="section-card">
|
||
<div class="section-card-header">
|
||
<div class="section-card-title">Телеметрия по провайдерам ИИ</div>
|
||
<div class="section-card-subtitle">Распределение запросов, отказов и задержек в окне измерений</div>
|
||
</div>
|
||
<div class="table-responsive" id="analytics-providers-table">
|
||
<!-- Rendered by app.js -->
|
||
</div>
|
||
</div>
|
||
|
||
<div class="section-card">
|
||
<div class="section-card-header">
|
||
<div class="section-card-title">Телеметрия по ролям агентов</div>
|
||
<div class="section-card-subtitle">Нагрузка по ролям команды, частота сбоев и тайминги</div>
|
||
</div>
|
||
<div class="table-responsive" id="analytics-roles-table">
|
||
<!-- Rendered by app.js -->
|
||
</div>
|
||
</div>
|
||
</div></section>
|
||
|
||
<!-- 7. HEALTH VIEW (P0-2) -->
|
||
<section id="view-health" class="view-pane">
|
||
<div class="readiness-banner" id="health-readiness-banner">
|
||
<!-- Readiness state banner rendered by app.js -->
|
||
</div>
|
||
|
||
<div class="section-card" id="health-preflight-card">
|
||
<div class="section-card-header" style="display:flex; justify-content:space-between; align-items:center;">
|
||
<div>
|
||
<div class="section-card-title">Проверка готовности (Preflight / Dependency Agent)</div>
|
||
<div class="section-card-subtitle">Zero-quota аудит окружения, зависимостей, прав доступа и локальных серверов</div>
|
||
</div>
|
||
<button class="btn btn-secondary btn-sm" id="btn-run-preflight">Запустить проверку готовности</button>
|
||
</div>
|
||
<div id="preflight-results-container" style="padding-top:12px;">
|
||
<!-- Rendered by app.js -->
|
||
</div>
|
||
</div>
|
||
|
||
<div class="section-card">
|
||
<div class="section-card-header">
|
||
<div class="section-card-title">Ресурсы хост-системы</div>
|
||
|
||
<div class="section-card-subtitle">Мониторинг CPU, памяти, диска и сетевых параметров</div>
|
||
</div>
|
||
<div class="host-resources-grid" id="health-host-resources">
|
||
<!-- Rendered by app.js -->
|
||
</div>
|
||
</div>
|
||
|
||
<div class="section-card">
|
||
<div class="section-card-header">
|
||
<div class="section-card-title">Предупреждения и диагностика</div>
|
||
<div class="section-card-subtitle">Анализ рисков, отсутствие резервов и деградация маршрутов</div>
|
||
</div>
|
||
<div class="warnings-list" id="health-warnings-list">
|
||
<!-- Rendered by app.js -->
|
||
</div>
|
||
</div>
|
||
<div id="health-workspace" class="health-workspace"></div>
|
||
</section>
|
||
|
||
<!-- 8. LOGS / EVENTS VIEW (P0-3) -->
|
||
<section id="view-logs" class="view-pane">
|
||
<div class="toolbar">
|
||
<div class="search-box">
|
||
<span class="search-icon">🔍</span>
|
||
<input type="text" id="logs-search" placeholder="Поиск по событиям или деталям...">
|
||
</div>
|
||
<div class="filters-row">
|
||
<select id="logs-filter-level" class="select-filter">
|
||
<option value="all">Все уровни</option>
|
||
<option value="info">INFO</option>
|
||
<option value="success">SUCCESS</option>
|
||
<option value="warning">WARNING</option>
|
||
<option value="error">ERROR</option>
|
||
</select>
|
||
<select id="logs-filter-category" class="select-filter">
|
||
<option value="all">Все категории</option>
|
||
<option value="workflow_started">Запуск workflow</option><option value="agent_started">Запуск агента</option><option value="agent_completed">Завершение агента</option><option value="workflow_failed">Ошибка workflow</option>
|
||
</select>
|
||
<button class="btn btn-secondary btn-sm" id="btn-refresh-logs">↻ Обновить журнал</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="section-card" style="margin-top:12px;">
|
||
<div class="section-card-header">
|
||
<div class="section-card-title">Журнал событий Hermes Hub</div>
|
||
<div class="section-card-subtitle">События workflow из текущего снапшота · до 60 последних записей</div>
|
||
</div>
|
||
<div class="logs-layout"><div class="logs-container" id="logs-container">
|
||
<!-- Workflow events from HubSnapshot -->
|
||
</div><aside id="log-detail" class="log-detail"><h2>Детали события</h2><p>Выберите событие в таблице.</p></aside></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 9. SETTINGS VIEW (P0-4) -->
|
||
<section id="view-settings" class="view-pane">
|
||
<!-- Hub Server Settings -->
|
||
<div class="settings-card">
|
||
<h2 class="settings-group-title">Параметры сервера Hermes Hub</h2>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Хост и порт веб-сервера</div>
|
||
<div class="setting-desc">Сетевой адрес и порт для HTTP/REST API и веб-интерфейса</div>
|
||
</div>
|
||
<div class="setting-control" style="display:flex; gap:8px;">
|
||
<input type="text" id="setting-server-host" class="input-text" style="width:140px;" placeholder="127.0.0.1">
|
||
<input type="number" id="setting-server-port" class="input-text" style="width:90px;" placeholder="5800">
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Токен безопасности сервера</div>
|
||
<div class="setting-desc">Обязателен при внешнем подключении (host != 127.0.0.1)</div>
|
||
</div>
|
||
<div class="setting-control" style="display:flex; align-items:center; gap:8px;">
|
||
<span id="setting-token-status-badge" class="badge">Токен не задан</span>
|
||
<input type="password" id="setting-server-token-input" class="input-text" placeholder="Задать новый токен...">
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<label class="setting-label" for="setting-provider-proxy-url">Прокси / VPN для провайдеров</label>
|
||
<div class="setting-desc">Google отказывает по местоположению: «not currently available in your location». Выход через разрешённую страну снимает отказ. Пример: <code>socks5://127.0.0.1:1080</code>. Пусто — без прокси. Отдельному аккаунту можно задать свой адрес.</div>
|
||
</div>
|
||
<div class="setting-control"><input id="setting-provider-proxy-url" class="input-text" type="text" placeholder="Н/Д: загрузка" aria-label="Адрес прокси для обращений провайдеров"></div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<label class="setting-label" for="setting-account-check-interval">Проверка аккаунтов и моделей</label>
|
||
<div class="setting-desc">Каждые 300 секунд по умолчанию. Проверка отправляет короткий запрос модели и может расходовать квоту.</div>
|
||
</div>
|
||
<div class="setting-control"><input id="setting-account-check-interval" class="input-text" type="number" min="60" disabled placeholder="Н/Д: загрузка" aria-label="Интервал проверки аккаунтов, секунды"></div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Интервал обновления квот</div>
|
||
<div class="setting-desc">Период фонового опроса квот провайдеров (секунды)</div>
|
||
</div>
|
||
<div class="setting-control">
|
||
<select id="setting-quota-interval" class="select-filter">
|
||
<option value="60">60 секунд (1 мин)</option>
|
||
<option value="120">120 секунд (2 мин)</option>
|
||
<option value="300">300 секунд (5 мин — стандарт)</option>
|
||
<option value="600">600 секунд (10 мин)</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Порог остатка квоты</div>
|
||
<div class="setting-desc">Уровень остатка квоты аккаунта, при котором срабатывает действие</div>
|
||
</div>
|
||
<div class="setting-control">
|
||
<select id="setting-quota-threshold-percent" class="select-filter">
|
||
<option value="5">5%</option>
|
||
<option value="10" selected>10% (по умолчанию)</option>
|
||
<option value="15">15%</option>
|
||
<option value="20">20%</option>
|
||
<option value="25">25%</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Действие при низком остатке квоты</div>
|
||
<div class="setting-desc">Оповещение в журнале и готовности или автоматическое переключение на резервный аккаунт</div>
|
||
</div>
|
||
<div class="setting-control">
|
||
<select id="setting-quota-threshold-action" class="select-filter">
|
||
<option value="notify" selected>Оповещение (notify)</option>
|
||
<option value="switch">Переключение на резерв (switch)</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Маскирование почты (PII)</div>
|
||
<div class="setting-desc">Уровень маскирования адресов электронной почты в снапшотах и логах</div>
|
||
</div>
|
||
<div class="setting-control">
|
||
<select id="setting-email-masking-mode" class="select-filter">
|
||
<option value="none" selected>Без маскирования (по умолчанию)</option>
|
||
<option value="partial">Частичное (v***@gmail.com)</option>
|
||
<option value="full">Полное (***@***.***)</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Роль по умолчанию (Default Role)</div>
|
||
<div class="setting-desc">Целевая роль при отсутствии явного указания роли в запросе Hermes</div>
|
||
</div>
|
||
<div class="setting-control">
|
||
<select id="setting-default-role" class="select-filter">
|
||
<option value="manager" selected>Главный менеджер (manager)</option>
|
||
<option value="developer-1">Кодер 1 (developer-1)</option>
|
||
<option value="developer-2">Кодер 2 (developer-2)</option>
|
||
<option value="code-reviewer">Ревьюер кода (code-reviewer)</option>
|
||
<option value="researcher">Исследователь (researcher)</option>
|
||
<option value="tester">Тестировщик (tester)</option>
|
||
<option value="integration-expert">Интегратор (integration-expert)</option>
|
||
<option value="security-expert">Эксперт по безопасности (security-expert)</option>
|
||
<option value="tech-writer">Технический писатель (tech-writer)</option>
|
||
<option value="analyst">Аналитик требований (analyst)</option>
|
||
<option value="dependency-agent">Агент зависимостей (dependency-agent)</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Тема оформления</div>
|
||
<div class="setting-desc">Цветовая схема интерфейса Hub</div>
|
||
</div>
|
||
<div class="setting-control">
|
||
<select id="setting-theme" class="select-filter">
|
||
<option value="system">Системная (по умолчанию)</option>
|
||
<option value="dark">Тёмная (Dark)</option>
|
||
<!-- Medium была полностью реализована в style.css, но в этот
|
||
список не попала, и выбрать её было нельзя. Брендбук
|
||
требует три темы, причём Medium — отдельная, а не
|
||
осветлённая Dark: тёмно-зелёный холст #1A2A1F со
|
||
светлыми кремовыми карточками #F7F1E3. Проверено
|
||
подстановкой data-theme вручную: тема отрисовывается
|
||
верно и отличается от Dark и по фону, и по карточкам. -->
|
||
<option value="medium">Средняя (Medium)</option>
|
||
<option value="light">Светлая (Light)</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="settings-actions">
|
||
<button class="btn btn-primary" id="btn-save-hub-settings">Сохранить настройки сервера</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Obsidian Shared Memory Settings -->
|
||
<div class="settings-card" style="margin-top:16px;" id="settings-obsidian-card">
|
||
<div class="section-card-header" style="display:flex; justify-content:space-between; align-items:center; margin-bottom:12px;">
|
||
<div>
|
||
<h2 class="settings-group-title" style="margin:0;">Единая память (Obsidian Vault)</h2>
|
||
<div class="setting-desc">Централизованная база знаний и долгосрочная память агентов сервера (/srv/projects/AI-Memory)</div>
|
||
</div>
|
||
<span id="obsidian-vault-status-badge" class="badge">Проверка...</span>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Путь к хранилищу Obsidian</div>
|
||
<div class="setting-desc">Каталог Markdown-хранилища, содержащий папку <code>.obsidian</code> (/srv/projects/AI-Memory)</div>
|
||
</div>
|
||
<div class="setting-control" style="display:flex; gap:8px; width:45%;">
|
||
<input type="text" id="setting-obsidian-vault-path" class="input-text" style="flex:1;" placeholder="/srv/projects/AI-Memory">
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Каноническая структура памяти</div>
|
||
<div class="setting-desc" id="obsidian-vault-status-desc">00_SYSTEM, 01_PROJECTS/hermes-hub, 03_LESSONS, 04_PATTERNS, 05_AGENTS, worklog/</div>
|
||
</div>
|
||
<div class="setting-control" style="display:flex; gap:8px;">
|
||
<button class="btn btn-secondary btn-sm" id="btn-check-obsidian-vault">Проверить хранилище</button>
|
||
<button class="btn btn-primary btn-sm" id="btn-setup-memory-structure">Настроить структуру памяти</button>
|
||
</div>
|
||
</div>
|
||
<div id="obsidian-vault-details" style="font-size:12px; color:var(--text-muted); margin-top:8px; font-family:var(--font-mono);"></div>
|
||
</div>
|
||
|
||
<!-- Context Compression Settings (A56) -->
|
||
<div class="settings-card" style="margin-top:16px;" id="settings-compression-card">
|
||
<div class="section-card-header" style="display:flex; justify-content:space-between; align-items:center; margin-bottom:12px;">
|
||
<div>
|
||
<h2 class="settings-group-title" style="margin:0;">Сжатие контекста (Context Compression)</h2>
|
||
<div class="setting-desc">Автоматическое сжатие длинного контекста с сохранением 100% технических фактов и сущностей</div>
|
||
</div>
|
||
<span id="compression-status-badge" class="badge">Н/Д: модель для сжатия не выбрана</span>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Профиль модели для сжатия</div>
|
||
<div class="setting-desc">Служебная модель-компрессор (например, Qwen3-4B на порту 8082). Не участвует в маршрутизации Hermes.</div>
|
||
</div>
|
||
<div class="setting-control">
|
||
<select id="setting-compressor-profile" class="select-filter">
|
||
<option value="none">Н/Д: модель для сжатия не выбрана (отключено)</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Порог заполнения контекста для запуска сжатия</div>
|
||
<div class="setting-desc">Сжатие начинается при превышении процента от измеренного контекста модели (/props)</div>
|
||
</div>
|
||
<div class="setting-control">
|
||
<select id="setting-compression-threshold-percent" class="select-filter">
|
||
<option value="50">50% контекста (агрессивное сжатие)</option>
|
||
<option value="60">60% контекста</option>
|
||
<option value="70">70% контекста</option>
|
||
<option value="75" selected>75% контекста (рекомендуется — около 3/4)</option>
|
||
<option value="80">80% контекста</option>
|
||
<option value="85">85% контекста</option>
|
||
<option value="90">90% контекста (позднее сжатие)</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Количество свежих сообщений (Fresh Window)</div>
|
||
<div class="setting-desc">Последние сообщения, которые всегда остаются 100% дословными и не подлежат сжатию</div>
|
||
</div>
|
||
<div class="setting-control">
|
||
<select id="setting-compression-keep-recent" class="select-filter">
|
||
<option value="2">2 последних сообщения</option>
|
||
<option value="3" selected>3 последних сообщения (рекомендуется)</option>
|
||
<option value="4">4 последних сообщения</option>
|
||
<option value="5">5 последних сообщений</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Диагностика и проверка сжатия</div>
|
||
<div class="setting-desc" id="compression-diagnostic-desc">Проверить отклик компрессора и 100% удержание путей, портов, IP и хэшей</div>
|
||
</div>
|
||
<div class="setting-control" style="display:flex; gap:8px;">
|
||
<button class="btn btn-secondary btn-sm" id="btn-check-compression-status">Статус компрессора</button>
|
||
<button class="btn btn-primary btn-sm" id="btn-test-compression">Тестовое сжатие (100% фактов)</button>
|
||
</div>
|
||
</div>
|
||
<div id="compression-details-box" style="font-size:12px; color:var(--text-muted); margin-top:8px; font-family:var(--font-mono); white-space:pre-wrap;"></div>
|
||
</div>
|
||
|
||
<!-- Antigravity CLI & Eligibility Settings (A58) -->
|
||
<div class="settings-card" style="margin-top:16px;" id="settings-agy-card">
|
||
<div class="section-card-header" style="display:flex; justify-content:space-between; align-items:center; margin-bottom:12px;">
|
||
<div>
|
||
<h2 class="settings-group-title" style="margin:0;">Параметры Antigravity CLI</h2>
|
||
<div class="setting-desc">Версия утилиты, путь к исполняемому файлу и состояние проверки доступности Google</div>
|
||
</div>
|
||
<span id="agy-eligibility-badge" class="badge">Проверка...</span>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Версия и исполняемый файл agy</div>
|
||
<div class="setting-desc" id="agy-version-path-desc">Н/Д: определение...</div>
|
||
</div>
|
||
<div class="setting-control" style="display:flex; gap:8px;">
|
||
<button class="btn btn-secondary btn-sm" id="btn-refresh-agy-eligibility">↻ Проверить состояние</button>
|
||
<button class="btn btn-primary btn-sm" id="btn-run-agy-update" title="Обновление agy перезаписывает исполняемый файл и возвращает проверку доступности: сначала выполните обновление, затем повторно примените патч.">⬆ Обновить agy (agy update)</button>
|
||
</div>
|
||
</div>
|
||
<div class="setting-row" style="background:var(--surface-muted); padding:10px 12px; border-radius:var(--radius-sm); border:1px solid var(--border-subtle); margin-top:8px;">
|
||
<div class="setting-desc" style="font-size:12px; color:var(--text-secondary); margin:0;">
|
||
ℹ️ <strong>Порядок действий:</strong> <em>Обновление agy перезаписывает исполняемый файл и возвращает проверку доступности: сначала выполните обновление, затем повторно примените патч.</em>
|
||
</div>
|
||
</div>
|
||
<div class="setting-row" style="margin-top:12px;">
|
||
<div class="setting-info">
|
||
<label class="setting-label" for="setting-agy-patch-script-path">Путь к сценарию патча владельца</label>
|
||
<div class="setting-desc">Собственный скрипт владельца для снятия проверки доступности (P0-4). Оставьте пустым, если не используется.</div>
|
||
</div>
|
||
<div class="setting-control" style="display:flex; gap:8px; width:55%;">
|
||
<input type="text" id="setting-agy-patch-script-path" class="input-text" style="flex:1;" placeholder="Н/Д: сценарий не указан">
|
||
<button class="btn btn-secondary btn-sm" id="btn-run-agy-patch-script">⚡ Запустить сценарий патча</button>
|
||
</div>
|
||
</div>
|
||
<div id="agy-eligibility-details" style="font-size:12px; color:var(--text-muted); margin-top:8px; font-family:var(--font-mono);"></div>
|
||
</div>
|
||
|
||
<!-- Hub Updates -->
|
||
<div class="settings-card" style="margin-top:16px;" id="settings-updates-card">
|
||
<h2 class="settings-group-title">Обновление Hermes Hub</h2>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Текущая версия и сборка</div>
|
||
<div class="setting-desc" id="update-installed-info">Hermes Hub</div>
|
||
</div>
|
||
<div class="setting-control" style="display:flex; align-items:center; gap:8px;">
|
||
<span id="update-status-badge" class="badge">Не проверено</span>
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Проверка обновлений</div>
|
||
<div class="setting-desc" id="update-last-checked-desc">Последняя проверка: еще не выполнялась</div>
|
||
</div>
|
||
<div class="setting-control" style="display:flex; gap:8px;">
|
||
<button class="btn btn-secondary btn-sm" id="btn-check-updates">Проверить обновления</button>
|
||
<button class="btn btn-primary btn-sm" id="btn-apply-update" disabled>Установить обновление</button>
|
||
</div>
|
||
</div>
|
||
<div id="update-details-block" class="update-details-box hidden" style="margin-top:12px; padding:12px; background:var(--surface-muted); border-radius:var(--radius-sm); border:1px solid var(--border-subtle);">
|
||
<div id="update-release-title" style="font-weight:600; font-size:13px; margin-bottom:4px;"></div>
|
||
<div id="update-release-meta" style="font-size:12px; color:var(--text-muted); margin-bottom:8px;"></div>
|
||
<div id="update-release-notes" style="font-size:12px; max-height:160px; overflow-y:auto; white-space:pre-wrap; background:var(--surface-card); padding:8px; border-radius:4px; border:1px solid var(--border-subtle); font-family:var(--font-mono);"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Read-Only System Paths -->
|
||
<div class="settings-card" style="margin-top:16px;">
|
||
<h2 class="settings-group-title">Системные пути и окружение</h2>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Каталог данных Hermes</div>
|
||
<div class="setting-desc mono-path" id="path-hermes-home">—</div>
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Каталог конфигурации</div>
|
||
<div class="setting-desc mono-path" id="path-config-dir">—</div>
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Файл журнала событий</div>
|
||
<div class="setting-desc mono-path" id="path-log-file">—</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Client Local Settings -->
|
||
<div class="settings-card" style="margin-top:16px;">
|
||
<h2 class="settings-group-title">Параметры веб-клиента (Браузер)</h2>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Источник данных в браузере</div>
|
||
<div class="setting-desc">Автоматическое переключение между Live API и фикстурой</div>
|
||
</div>
|
||
<div class="setting-control">
|
||
<select id="setting-source-mode" class="select-filter">
|
||
<option value="auto">Авто (Live API с fallback)</option>
|
||
<option value="live">Строго Live API (/api/snapshot)</option>
|
||
<option value="fixture">Строго фикстура (snapshot.example.json)</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Интервал авто-опроса UI</div>
|
||
<div class="setting-desc">Частота обновления снимка состояния в окне браузера</div>
|
||
</div>
|
||
<div class="setting-control">
|
||
<select id="setting-poll-interval" class="select-filter">
|
||
<option value="3000">3 секунды</option>
|
||
<option value="5000" selected>5 секунд (стандарт)</option>
|
||
<option value="10000">10 секунд</option>
|
||
<option value="0">Отключить авто-опрос</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Локальный токен клиента (X-Hub-Token)</div>
|
||
<div class="setting-desc">Токен для заголовка X-Hub-Token при внешних вызовах</div>
|
||
</div>
|
||
<div class="setting-control">
|
||
<input type="password" id="setting-client-token-input" class="input-text" placeholder="Введите токен...">
|
||
</div>
|
||
</div>
|
||
<div class="settings-actions">
|
||
<button class="btn btn-secondary" id="btn-save-client-settings">Сохранить локальные параметры</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Clean Setup / Reset Configuration (P0-4) -->
|
||
<div class="settings-card" style="margin-top:16px; border:1px solid rgba(220, 53, 69, 0.3);">
|
||
<h2 class="settings-group-title" style="color:var(--status-error, #e57373);">Сброс конфигурации</h2>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Начать настройку заново</div>
|
||
<div class="setting-desc">Сбросить все цепочки ролей и список профилей в чистое начальное состояние. Все учетные данные и токены аккаунтов сохраняются.</div>
|
||
</div>
|
||
<div class="setting-control">
|
||
<button class="btn btn-danger" id="btn-reset-router-config">Начать настройку заново</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
|
||
<!-- Modal Layer -->
|
||
<div id="modal-backdrop" class="modal-backdrop hidden">
|
||
<div id="modal-container" class="modal-card">
|
||
<div class="modal-header">
|
||
<h2 id="modal-title" class="modal-title">Модальное окно</h2>
|
||
<button id="modal-close-btn" class="modal-close" title="Закрыть">✕</button>
|
||
</div>
|
||
<div id="modal-body" class="modal-body">
|
||
<!-- Injected dynamically -->
|
||
</div>
|
||
<div id="modal-footer" class="modal-footer">
|
||
<!-- Injected dynamically -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Toast Notification Container -->
|
||
<div id="toast-container" class="toast-container"></div>
|
||
|
||
<!-- Application Logic -->
|
||
<script src="app.js"></script>
|
||
<script src="static/workspace.js"></script>
|
||
<script src="static/workflow.js"></script>
|
||
<script>
|
||
const urlParams = new URLSearchParams(window.location.search);
|
||
const theme = urlParams.get('theme');
|
||
if (theme) {
|
||
document.body.setAttribute('data-theme', theme);
|
||
const sel = document.getElementById('theme-selector');
|
||
if (sel) sel.value = theme;
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|