From 910e83be50f44a44a0dc37c158e8cf2e286e9d26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=B5=D1=80=D0=B3=D0=B5=D0=B9=20=D0=90=D0=BD=D1=82?= =?UTF-8?q?=D1=80=D0=BE=D0=BF=D0=BE=D0=B2?= Date: Wed, 20 Aug 2025 15:27:59 +0300 Subject: [PATCH] refactor: move templates to app/ and extract CSS/JS to static files --- app/static/css/error.css | 211 ++ app/static/css/index.css | 2584 +++++++++++++++++++++++ app/static/css/login.css | 279 +++ app/static/js/login.js | 105 + {templates => app/templates}/error.html | 0 {templates => app/templates}/index.html | 0 {templates => app/templates}/login.html | 0 7 files changed, 3179 insertions(+) create mode 100644 app/static/css/error.css create mode 100644 app/static/css/index.css create mode 100644 app/static/css/login.css create mode 100644 app/static/js/login.js rename {templates => app/templates}/error.html (100%) rename {templates => app/templates}/index.html (100%) rename {templates => app/templates}/login.html (100%) diff --git a/app/static/css/error.css b/app/static/css/error.css new file mode 100644 index 0000000..3b87eb6 --- /dev/null +++ b/app/static/css/error.css @@ -0,0 +1,211 @@ + :root { + --bg: #1a1b26; + --fg: #c0caf5; + --panel: #24283b; + --border: #414868; + --accent: #7aa2f7; + --muted: #565a6e; + --ok: #9ece6a; + --warn: #e0af68; + --err: #f7768e; + --chip: #414868; + } + + [data-theme="light"] { + --bg: #d5d6db; + --fg: #343b58; + --panel: #e1e2e7; + --border: #9699a3; + --accent: #34548a; + --muted: #9699a3; + --ok: #485e30; + --warn: #8f5e15; + --err: #8c4351; + --chip: #d5d6db; + } + + * { + margin: 0; + padding: 0; + box-sizing: border-box; + } + + body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + background: var(--bg); + color: var(--fg); + line-height: 1.6; + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + transition: background-color 0.3s ease, color 0.3s ease; + } + + .error-container { + max-width: 600px; + padding: 2rem; + text-align: center; + transition: all 0.3s ease; + } + + .error-icon { + font-size: 4rem; + margin-bottom: 1rem; + color: var(--err); + } + + .error-code { + font-size: 3rem; + font-weight: bold; + color: var(--accent); + margin-bottom: 0.5rem; + } + + .error-title { + font-size: 1.5rem; + font-weight: 600; + margin-bottom: 1rem; + color: var(--fg); + } + + .error-message { + font-size: 1rem; + color: var(--muted); + margin-bottom: 2rem; + line-height: 1.6; + } + + .error-details { + background: var(--panel); + border: 1px solid var(--border); + border-radius: 8px; + padding: 1rem; + margin-bottom: 2rem; + text-align: left; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: 0.9rem; + color: var(--muted); + } + + .btn { + display: inline-block; + padding: 0.75rem 1.5rem; + background: var(--accent); + color: white; + text-decoration: none; + border-radius: 6px; + font-weight: 500; + transition: all 0.2s ease; + border: none; + cursor: pointer; + font-size: 1rem; + } + + .btn:hover { + background: var(--accent); + opacity: 0.9; + transform: translateY(-1px); + } + + .btn-secondary { + background: var(--chip); + color: var(--fg); + border: 1px solid var(--border); + } + + .btn-secondary:hover { + background: var(--border); + } + + .btn-group { + display: flex; + gap: 1rem; + justify-content: center; + flex-wrap: wrap; + } + + .theme-toggle { + position: fixed; + top: 1rem; + right: 1rem; + background: var(--panel); + border: 1px solid var(--border); + border-radius: 50%; + width: 48px; + height: 48px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.2s ease; + font-size: 1.2rem; + color: var(--fg); + z-index: 1000; + } + + .theme-toggle:hover { + background: var(--border); + transform: scale(1.1); + } + + .theme-toggle:active { + transform: scale(0.95); + } + + .auth-notice { + background: var(--warn); + color: var(--bg); + padding: 1rem; + border-radius: 6px; + margin-bottom: 1rem; + font-weight: 500; + } + + .footer { + margin-top: 2rem; + padding-top: 1rem; + border-top: 1px solid var(--border); + color: var(--muted); + font-size: 0.9rem; + } + + .footer a { + color: var(--accent); + text-decoration: none; + } + + .footer a:hover { + text-decoration: underline; + } + + @media (max-width: 768px) { + .error-container { + padding: 1rem; + } + + .error-code { + font-size: 2rem; + } + + .error-title { + font-size: 1.25rem; + } + + .btn-group { + flex-direction: column; + align-items: center; + } + + .btn { + width: 100%; + max-width: 300px; + } + + .theme-toggle { + top: 0.5rem; + right: 0.5rem; + width: 40px; + height: 40px; + font-size: 1rem; + } diff --git a/app/static/css/index.css b/app/static/css/index.css new file mode 100644 index 0000000..dabb3f9 --- /dev/null +++ b/app/static/css/index.css @@ -0,0 +1,2584 @@ +/* THEME TOKENS */ +:root{ + --bg:#0e0f13; --panel:#151821; --muted:#8b94a8; --accent:#7aa2f7; --ok:#9ece6a; --warn:#e0af68; --err:#f7768e; --fg:#e5e9f0; + --border:#2a2f3a; --tab:#1b2030; --tab-active:#22283a; --chip:#2b3142; --link:#9ab8ff; + --sidebar-width: 280px; --header-height: 60px; +} +:root[data-theme="light"]{ + --bg:#f7f9fc; --panel:#ffffff; --muted:#667085; --accent:#3b82f6; --ok:#15803d; --warn:#b45309; --err:#b91c1c; --fg:#0f172a; + --border:#e5e7eb; --tab:#eef2ff; --tab-active:#dbeafe; --chip:#eef2f7; --link:#1d4ed8; +} + +*{box-sizing:border-box} +html,body{height:100%; margin: 0; padding: 0;} +body{background:var(--bg);color:var(--fg);font:13px/1.45 ui-monospace,Menlo,Consolas,monospace; overflow: hidden;} +a{color:var(--link)} + +/* Modern Layout */ +.app-container { + display: flex; + height: 100vh; + overflow: hidden; +} + +/* Sidebar */ +.sidebar { + width: var(--sidebar-width); + background: var(--panel); + border-right: 1px solid var(--border); + display: flex; + flex-direction: column; + overflow: hidden; + transition: width 0.3s ease, transform 0.3s ease; +} + +.sidebar.collapsed { + width: 42px; +} + +.sidebar.collapsed .sidebar-header h1, +.sidebar.collapsed .sidebar-header .subtitle, +.sidebar.collapsed .sidebar-controls, +.sidebar.collapsed .container-list { + display: none; +} + +/* Миникарточки контейнеров для свернутого sidebar */ +.sidebar.collapsed .mini-container-list { + display: flex; + flex-direction: column; + gap: 4px; + padding: 8px 4px; + overflow-y: auto; + flex: 1; + scrollbar-width: thin; + scrollbar-color: var(--border) transparent; +} + +/* Всплывающие подсказки для миникарточек */ +.mini-container-tooltip { + position: absolute; + background: var(--panel); + border: 1px solid var(--border); + border-radius: 8px; + padding: 12px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + z-index: 1000; + pointer-events: auto; + opacity: 0; + visibility: hidden; + transition: opacity 0.2s ease, visibility 0.2s ease; + max-width: 280px; + min-width: 200px; + font-size: 12px; + line-height: 1.4; +} + +.mini-container-tooltip.show { + opacity: 1; + visibility: visible; +} + +.mini-container-tooltip-header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; + font-weight: 600; + color: var(--fg); +} + +.mini-container-tooltip-icon { + font-size: 14px; + color: var(--accent); +} + +.mini-container-tooltip-name { + font-size: 13px; + font-weight: 600; + color: var(--fg); + margin-bottom: 4px; +} + +.mini-container-tooltip-service { + font-size: 11px; + color: var(--muted); + margin-bottom: 6px; +} + +.mini-container-tooltip-status { + display: flex; + align-items: center; + gap: 6px; + font-size: 10px; + margin-bottom: 4px; +} + +.mini-container-tooltip-status-indicator { + width: 6px; + height: 6px; + border-radius: 50%; +} + +.mini-container-tooltip-status-indicator.running { background: var(--ok); } +.mini-container-tooltip-status-indicator.stopped { background: var(--err); } +.mini-container-tooltip-status-indicator.paused { background: var(--warn); } + +.mini-container-tooltip-port { + font-size: 10px; + color: var(--muted); + margin-bottom: 4px; +} + +.mini-container-tooltip-url { + font-size: 10px; + color: var(--accent); + text-decoration: none; + display: flex; + align-items: center; + gap: 4px; + padding: 2px 4px; + border-radius: 4px; + transition: all 0.2s ease; + cursor: pointer; +} + +.mini-container-tooltip-url:hover { + text-decoration: underline; + color: var(--link); + background: var(--chip); +} + + + +/* Дополнительные стили для светлой темы */ +:root[data-theme="light"] .mini-container-tooltip { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + border-color: var(--border); +} + +/* Анимация появления подсказки */ +@keyframes tooltipFadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +.mini-container-tooltip.show { + opacity: 1; + visibility: visible; + animation: tooltipFadeIn 0.2s ease; +} + +/* Позиционирование подсказки */ +.mini-container-tooltip.top { + bottom: calc(100% + 8px); + left: 50%; + transform: translateX(-50%); +} + +.mini-container-tooltip.bottom { + top: calc(100% + 8px); + left: 50%; + transform: translateX(-50%); +} + +.mini-container-tooltip.left { + right: calc(100% + 8px); + top: 50%; + transform: translateY(-50%); +} + +.mini-container-tooltip.right { + left: calc(100% + 8px); + top: 50%; + transform: translateY(-50%); +} + +/* Стрелка подсказки */ +.mini-container-tooltip::before { + content: ''; + position: absolute; + width: 0; + height: 0; + border: 4px solid transparent; +} + +.mini-container-tooltip.top::before { + bottom: -8px; + left: 50%; + transform: translateX(-50%); + border-top-color: var(--panel); +} + +.mini-container-tooltip.bottom::before { + top: -8px; + left: 50%; + transform: translateX(-50%); + border-bottom-color: var(--panel); +} + +.mini-container-tooltip.left::before { + right: -8px; + top: 50%; + transform: translateY(-50%); + border-left-color: var(--panel); +} + +.mini-container-tooltip.right::before { + left: -8px; + top: 50%; + transform: translateY(-50%); + border-right-color: var(--panel); +} + +.sidebar.collapsed .mini-container-list::-webkit-scrollbar { + width: 4px; +} + +.sidebar.collapsed .mini-container-list::-webkit-scrollbar-track { + background: transparent; +} + +.sidebar.collapsed .mini-container-list::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 2px; +} + +.sidebar.collapsed .mini-container-list::-webkit-scrollbar-thumb:hover { + background: var(--muted); +} + +.sidebar:not(.collapsed) .mini-container-list { + display: none; +} + +.mini-container-item { + background: var(--chip); + border: 1px solid var(--border); + border-radius: 6px; + padding: 6px; + cursor: pointer; + transition: all 0.2s ease; + position: relative; + min-height: 32px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.mini-container-item:hover { + background: var(--tab-active); + border-color: var(--accent); + transform: scale(1.05); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); +} + + + +.mini-container-item.active { + background: var(--tab-active); + border-color: var(--accent); + color: var(--accent); + box-shadow: 0 0 0 1px var(--accent); +} + +.mini-container-item.active:hover { + transform: scale(1.05); + box-shadow: 0 0 0 1px var(--accent), 0 2px 8px rgba(0, 0, 0, 0.2); +} + +.mini-container-item.active::before { + content: ""; + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 2px; + background: var(--accent); + border-radius: 0 1px 1px 0; +} + +.mini-container-item.selected { + border-color: var(--ok); + box-shadow: 0 0 0 1px var(--ok); + background: var(--tab-active); +} + +.mini-container-item.selected .mini-container-icon { + color: var(--ok); +} + +.mini-container-item.selected .mini-container-name { + color: var(--ok); +} + +.mini-container-item.selected:hover { + transform: scale(1.05); + box-shadow: 0 0 0 1px var(--ok), 0 2px 8px rgba(0, 0, 0, 0.2); +} + +/* Индикатор выбора для миникарточек */ +.mini-container-item.selected::after { + content: ''; + position: absolute; + top: -2px; + right: -2px; + width: 8px; + height: 8px; + background: var(--ok); + border-radius: 50%; + border: 1px solid var(--panel); + z-index: 10; + animation: selectionPulse 0.3s ease; +} + +@keyframes selectionPulse { + 0% { + transform: scale(0); + opacity: 0; + } + 50% { + transform: scale(1.2); + opacity: 1; + } + 100% { + transform: scale(1); + opacity: 1; + } +} + +.mini-container-icon { + font-size: 12px; + margin-bottom: 2px; + color: var(--muted); +} + +.mini-container-item.active .mini-container-icon { + color: var(--accent); +} + +.mini-container-name { + font-size: 8px; + font-weight: 500; + text-align: center; + line-height: 1.2; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: var(--fg); +} + +.mini-container-item.active .mini-container-name { + color: var(--accent); +} + +.mini-container-status { + position: absolute; + top: 2px; + right: 2px; + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--ok); +} + +.mini-container-status.running { background: var(--ok); } +.mini-container-status.stopped { background: var(--err); } +.mini-container-status.paused { background: var(--warn); } + +/* Плейсхолдер для миникарточек */ +.mini-container-item.placeholder { + opacity: 0.5; + cursor: default; + background: var(--chip); + border-color: var(--border); +} + +.mini-container-item.placeholder:hover { + transform: none; + background: var(--chip); + border-color: var(--border); + box-shadow: none; +} + +.mini-container-item.placeholder .mini-container-icon, +.mini-container-item.placeholder .mini-container-name { + color: var(--muted); +} + +.sidebar.collapsed .sidebar-header { + display: flex; + align-items: center; + justify-content: center; + padding: 10px; +} + +.sidebar.collapsed .header-buttons { + flex-direction: column; + gap: 4px; + align-items: center; + justify-content: center; + width: 100%; +} + +/* Логотип в свернутом состоянии */ +.sidebar.collapsed .sidebar-logo { + display: flex; + align-items: center; + justify-content: center; + height: 40px; + font-size: 18px; + color: var(--accent); + font-weight: bold; + margin-bottom: 10px; + border-bottom: 1px solid var(--border); +} + +.sidebar:not(.collapsed) .sidebar-logo { + display: none; +} + +/* Сворачивание header вместе с sidebar */ +.sidebar.collapsed ~ .main-content .header { + height: 40px; + padding: 0 20px; + display: flex; + align-items: center; + gap: 10px; +} + +.sidebar.collapsed ~ .main-content .header .header-title, +.sidebar.collapsed ~ .main-content .header .header-badge, +.sidebar.collapsed ~ .main-content .header .mobile-toggle { + display: none; +} + +.sidebar.collapsed ~ .main-content .header .header-filter { + flex: 1; + max-width: none; + margin: 0 8px; + min-width: 150px; +} + +.sidebar.collapsed ~ .main-content .header .header-controls { display: flex; align-items: center; gap: 8px; margin-left: auto; } + +/* Скрыть кнопки loglevels в компактном header */ +.sidebar.collapsed ~ .main-content .header .header-compact-controls { display: none; } + +/* Скрыть весь log-header */ +.log-header { display: none; } + +/* Минимальный padding для log-content в свернутом состоянии */ +.sidebar.collapsed ~ .main-content .log-content { + padding: 0; +} + +/* Обеспечиваем правильное отображение логов при свернутом sidebar */ +.sidebar.collapsed ~ .main-content .log-area { + height: calc(100vh - var(--header-height)) !important; + overflow: hidden !important; +} + +.sidebar.collapsed ~ .main-content .log-content { + overflow: visible !important; + height: 100% !important; + padding: 0 !important; +} + +.sidebar.collapsed ~ .main-content .single-view-content .log { + height: calc(100vh - var(--header-height)) !important; + overflow: auto !important; + display: block !important; + max-height: none !important; + min-height: 200px !important; + position: relative !important; +} + +/* Исправляем скролл для multi-view в свернутом состоянии */ +.sidebar.collapsed ~ .main-content .multi-view-content { + height: calc(100vh - var(--header-height) - 60px) !important; + overflow: hidden !important; +} + +.sidebar.collapsed ~ .main-content .multi-view-log { + height: 100% !important; + overflow: auto !important; + display: block !important; + max-height: none !important; + min-height: 200px !important; + position: relative !important; +} + +/* Обеспечиваем правильное отображение логов при развернутом sidebar */ +.sidebar:not(.collapsed) ~ .main-content .single-view-content .log, +.sidebar:not(.collapsed) ~ .main-content .multi-view-log { + height: 100% !important; + overflow: auto !important; + display: block !important; + min-height: 200px !important; + position: relative !important; +} + +/* Стили для multi-view-content контейнеров */ +.sidebar.collapsed ~ .main-content .multi-view-content { + height: calc(100vh - var(--header-height) - 60px) !important; + overflow: hidden !important; + display: flex !important; + flex-direction: column !important; +} + +.sidebar:not(.collapsed) ~ .main-content .multi-view-content { + height: 100% !important; + overflow: hidden !important; + display: flex !important; + flex-direction: column !important; +} + +/* Дополнительные стили для multi-view-log в свернутом состоянии */ +.sidebar.collapsed ~ .main-content .multi-view-log { + height: 100% !important; + overflow: auto !important; + display: block !important; + max-height: none !important; + min-height: 200px !important; + position: relative !important; + flex: 1 !important; + min-height: 0 !important; +} + +/* Более специфичные правила для multi-view логов */ +.sidebar.collapsed ~ .main-content .multi-view-content .multi-view-log { + height: 100% !important; + overflow: auto !important; + display: block !important; + max-height: none !important; + min-height: 200px !important; + position: relative !important; + flex: 1 !important; + min-height: 0 !important; +} + +.sidebar.collapsed ~ .main-content .multi-view-grid .multi-view-panel .multi-view-content .multi-view-log { + height: 100% !important; + overflow: auto !important; + display: block !important; + max-height: none !important; + min-height: 200px !important; + position: relative !important; + flex: 1 !important; + min-height: 0 !important; +} + +/* Multi-view panel для Single View режима */ +.multi-view-panel { + display: none; + background: var(--panel); + border-bottom: 1px solid var(--border); + padding: 8px 16px; + font-size: 14px; + font-weight: 500; + color: var(--accent); +} + +.sidebar.collapsed ~ .main-content .multi-view-panel { + display: block; +} + +/* Дополнительные стили для обеспечения правильного скролла в multi-view */ +.multi-view-grid { + display: grid; + gap: 2px; + height: 100%; + padding: 0px; + /* Равная высота строк для нескольких рядов (3+ окон) */ + grid-auto-rows: 1fr; + align-items: stretch; +} + +.multi-view-panel { + background: var(--panel); + border: 1px solid var(--border); + border-radius: 8px; + display: flex; + flex-direction: column; + overflow: hidden; + padding: 2px; + height: 100%; +} + +.multi-view-content { + flex: 1; + overflow: hidden; + display: flex; + flex-direction: column; +} + +.multi-view-log { + height: 100%; + margin: 0; + padding: 12px; + font-size: 11px; + line-height: 1.4; + white-space: pre; + word-break: break-word; + overflow: auto; + background: var(--bg); + color: var(--fg); + font-family: ui-monospace, Menlo, Consolas, monospace; + flex: 1; + min-height: 0; +} + +/* Принудительный сброс стилей для multi-view логов */ +.multi-view-log { + height: 100% !important; + overflow: auto !important; + display: block !important; + max-height: none !important; + min-height: 200px !important; + position: relative !important; + flex: 1 !important; + min-height: 0 !important; +} + +/* Универсальные стили для всех multi-view логов */ +.multi-view-content .multi-view-log { + height: 100% !important; + overflow: auto !important; + display: block !important; + max-height: none !important; + min-height: 200px !important; + position: relative !important; + flex: 1 !important; + min-height: 0 !important; + width: 100% !important; + box-sizing: border-box !important; +} + +/* Универсальные стили для всех multi-view логов в свернутом состоянии */ +.sidebar.collapsed ~ .main-content .multi-view-content .multi-view-log { + height: 100% !important; + overflow: auto !important; + display: block !important; + max-height: none !important; + min-height: 200px !important; + position: relative !important; + flex: 1 !important; + min-height: 0 !important; + width: 100% !important; + box-sizing: border-box !important; +} + +/* Всегда скрываем одиночный блок multi-view панели в Single View */ +#multiViewPanel { + display: none !important; +} + +/* Приводим Single View лог к виду multi-view-log */ +.log-area .log-content .log { + height: 100%; + margin: 0; + padding: 12px; + font-size: 11px; + line-height: 1.4; + white-space: pre; /* по умолчанию как у multi-view; переключается applyWrapSettings() */ + word-break: break-word; + overflow: auto; + background: var(--bg); + color: var(--fg); + font-family: ui-monospace, Menlo, Consolas, monospace; +} + + + +.sidebar-toggle { + position: fixed; + top: 50vh; + left: var(--sidebar-width); + transform: translate(-50%, -50%); + width: 30px; + height: 30px; + background: var(--accent); + border: 2px solid var(--panel); + border-radius: 50%; + color: #0b0d12; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + z-index: 1001; + transition: all 0.2s ease; +} + +.sidebar.collapsed + .sidebar-toggle { + left: 42px; +} + +.sidebar-toggle:hover { + background: var(--ok); +} + + + +.sidebar-header { + padding: 20px; + border-bottom: 1px solid var(--border); + background: var(--panel); +} + +.header-top { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 0px; +} + +.header-buttons { + display: flex; + gap: 8px; + align-items: center; +} + +.sidebar-header h1 { + font-size: 18px; + margin: 0; + color: var(--accent); + font-weight: 600; +} + +.sidebar-header .subtitle { + font-size: 12px; + color: var(--muted); + margin: 0; +} + +.options-btn, +.help-btn, +.logout-btn { + background: var(--chip); + border: 1px solid var(--border); + color: var(--muted); + padding: 6px 8px; + border-radius: 6px; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; +} + +/* Компактные контролы в header: по умолчанию скрыты */ +.header-compact-controls { display: none; align-items: center; gap: 6px; } + +/* Модальное окно для кнопки помощи */ +.help-tooltip { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: var(--bg); + z-index: 10000; + opacity: 0; + visibility: hidden; + transition: all 0.3s ease; + pointer-events: none; + display: flex; + flex-direction: column; + overflow-y: auto; +} + +.help-tooltip.show { + opacity: 1; + visibility: visible; + pointer-events: auto; +} + +.help-tooltip-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 20px 30px; + border-bottom: 1px solid var(--border); + background: var(--panel); + flex-shrink: 0; +} + +.help-tooltip-header-content { + display: flex; + align-items: center; + gap: 12px; +} + +.help-tooltip-close { + background: var(--chip); + border: 1px solid var(--border); + border-radius: 6px; + padding: 8px 12px; + color: var(--muted); + cursor: pointer; + transition: all 0.2s ease; + font-size: 16px; +} + +.help-tooltip-close:hover { + background: var(--tab-active); + color: var(--fg); + border-color: var(--accent); +} + +.help-tooltip-logo { + width: 24px; + height: 24px; + background: var(--accent); + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + color: #0b0d12; + font-weight: bold; + font-size: 12px; +} + +.help-tooltip-title { + font-weight: 600; + color: var(--fg); + font-size: 18px; +} + +.help-tooltip-section { + margin-bottom: 24px; + padding: 20px; + background: var(--panel); + border: 1px solid var(--border); + border-radius: 8px; +} + +.help-tooltip-section:last-child { + margin-bottom: 0; +} + +.help-tooltip-section-title { + font-weight: 600; + color: var(--accent); + margin-bottom: 12px; + font-size: 14px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.help-tooltip-content { + color: var(--muted); + font-size: 12px; + line-height: 1.4; +} + +.help-tooltip-body { + flex: 1; + padding: 30px; + overflow-y: auto; +} + +.help-tooltip-content-wrapper { + display: flex; + gap: 40px; + max-width: 1200px; + margin: 0 auto; +} + +.help-tooltip-left-column, +.help-tooltip-right-column { + flex: 1; + min-width: 0; +} + +.help-tooltip-hotkeys { + display: grid; + grid-template-columns: auto 1fr; + gap: 12px 20px; + font-size: 12px; + margin-top: 8px; +} + +.help-tooltip-hotkey { + background: var(--chip); + border: 1px solid var(--border); + border-radius: 6px; + padding: 6px 12px; + font-family: 'Courier New', monospace; + font-weight: 600; + color: var(--fg); + text-align: center; + min-width: 60px; + font-size: 11px; +} + +.help-tooltip-description { + color: var(--muted); + font-size: 12px; + line-height: 1.4; +} + +.help-tooltip-footer { + display: flex; + align-items: center; + justify-content: space-between; + padding: 20px 30px; + border-top: 1px solid var(--border); + background: var(--panel); + flex-shrink: 0; +} + +.help-tooltip-author { + font-size: 12px; + color: var(--muted); +} + +.help-tooltip-version { + font-size: 12px; + color: var(--accent); + font-weight: 500; +} + +.help-tooltip-author a { + color: var(--accent); + text-decoration: none; +} + +.help-tooltip-author a:hover { + text-decoration: underline; +} + + + + + +.help-btn:hover { + background: #a78bfa; + color: #0b0d12; + border-color: #a78bfa; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(187, 154, 247, 0.5); +} + +.logout-btn:hover { + background: var(--tab-active); + color: var(--fg); + border-color: var(--accent); +} + + + + + +/* Специальный hover эффект для кнопки options с цветом accent */ +.options-btn:hover { + background: var(--accent) !important; /* Цвет логотипа */ + color: #0b0d12 !important; + border-color: var(--accent) !important; +} + +/* Кнопка options когда меню открыто (неактивное состояние) */ +.options-btn:not(.active) { + background: #e0a800; /* Цвет как у кнопки warning */ + color: #0b0d12; + border-color: #e0a800; +} + +.options-btn.active { + background: var(--chip); + color: var(--muted); + border-color: var(--border); +} + +.logout-btn:hover { + background: var(--err); + color: #fff; + border-color: var(--err); +} + +.options-btn i, +.logout-btn i { + font-size: 12px; +} + +/* Кнопка состояния WebSocket */ +.ws-status-btn { + background: var(--chip); + color: var(--muted); + border: 1px solid var(--border); + border-radius: 6px; + padding: 6px 12px; + font-size: 11px; + font-weight: 500; + cursor: default; + transition: all 0.3s ease; + font-family: inherit; + min-width: 60px; + text-align: center; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +.ws-status-btn.ws-on { + background: #7ea855; /* Темнее на 20% */ + color: white; + border-color: #7ea855; +} + +.ws-status-btn.ws-off { + background: #f7768e; + color: white; + border-color: #f7768e; +} + +.ws-status-btn.ws-err { + background: #e0af68; + color: white; + border-color: #e0af68; +} + +.ws-status-btn.ws-available { + background: #7aa2f7; + color: white; + border-color: #7aa2f7; +} + +/* Кнопка состояния AJAX Update */ +.ajax-update-btn { + background: var(--chip); + color: var(--muted); + border: 1px solid var(--border); + border-radius: 6px; + padding: 6px 12px; + font-size: 11px; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + font-family: inherit; + min-width: 60px; + text-align: center; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +.ajax-update-btn.ajax-on { + background: #7ea855; /* Зеленый цвет */ + color: white; + border-color: #7ea855; +} + +.ajax-update-btn.ajax-off { + background: #f7768e; /* Красный цвет */ + color: white; + border-color: #f7768e; +} + +.ajax-update-btn:hover { + opacity: 0.8; + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0,0,0,0.2); +} + +/* Sidebar Controls */ +.sidebar-controls { + padding: 16px; + border-bottom: 1px solid var(--border); +} + +.control-group { + margin-bottom: 16px; +} + +.control-group:last-child { + margin-bottom: 0; +} + +/* Collapsible sections */ +.control-group.collapsible { + border: 1px solid var(--border); + border-radius: 8px; + overflow: hidden; +} + +.control-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; + background: var(--chip); + cursor: pointer; + user-select: none; + transition: background-color 0.2s ease; +} + +.control-header:hover { + background: var(--tab-active); +} + +.control-header label { + display: block; + font-size: 11px; + color: var(--muted); + margin: 0; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.collapse-btn { + background: none; + border: none; + color: var(--muted); + cursor: pointer; + padding: 4px; + border-radius: 4px; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; +} + +.collapse-btn:hover { + background: var(--border); + color: var(--fg); +} + +.collapse-btn i { + font-size: 12px; + transition: transform 0.2s ease; +} + +.control-group.collapsed .collapse-btn i { + transform: rotate(-90deg); +} + +.control-content { + padding: 16px; + transition: all 0.3s ease; + overflow: hidden; +} + +.control-group.collapsed .control-content { + padding: 0 16px; + max-height: 0; + opacity: 0; +} + +/* Полностью свернутое состояние - только заголовки */ +.control-group.minimized { + margin-bottom: 4px; +} + +.control-group.minimized .control-header { + padding: 8px 12px; + font-size: 10px; +} + +.control-group.minimized .control-header label { + font-size: 10px; + text-transform: none; + letter-spacing: 0; +} + +.control-group.minimized .control-content { + display: none; +} + +/* Скрытие всех секций при активной кнопке Options */ +.sidebar-controls.hidden { + display: none; +} + +.control-group label { + display: block; + font-size: 11px; + color: var(--muted); + margin-bottom: 6px; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.control-group select, +.control-group input[type="text"] { + width: 100%; + background: var(--chip); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 6px; + padding: 8px 12px; + font-size: 12px; + transition: border-color 0.2s ease; +} + +.control-group select:focus, +.control-group input[type="text"]:focus { + outline: none; + border-color: var(--accent); +} + +/* Checkbox Groups */ +.checkbox-group { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.checkbox-group.levels-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px 16px; + width: 100%; + align-items: center; +} + +.checkbox-group.options-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px 16px; + width: 100%; + align-items: center; +} + +.checkbox-item { + display: flex; + align-items: center; + gap: 6px; + font-size: 11px; + color: var(--muted); +} + +.levels-grid .checkbox-item { + min-height: 20px; + justify-content: flex-start; +} + +.options-grid .checkbox-item { + min-height: 20px; + justify-content: flex-start; +} + +.checkbox-item input[type="checkbox"] { + margin: 0; +} + +/* Секция исключенных контейнеров */ +.excluded-containers-list { + max-height: 150px; + overflow-y: auto; + margin-bottom: 12px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--bg); +} + +.excluded-container-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 8px 12px; + border-bottom: 1px solid var(--border); + font-size: 12px; +} + +.excluded-container-item:last-child { + border-bottom: none; +} + +.excluded-container-item:first-child { + border-top-left-radius: 6px; + border-top-right-radius: 6px; +} + +.excluded-container-item:last-child { + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; +} + +.excluded-container-name { + color: var(--fg); + flex: 1; + word-break: break-word; +} + +.remove-excluded-btn { + background: var(--err); + color: white; + border: none; + border-radius: 4px; + padding: 4px 8px; + font-size: 11px; + cursor: pointer; + transition: all 0.2s ease; + margin-left: 8px; + min-width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; +} + +.remove-excluded-btn:hover { + opacity: 0.8; + transform: scale(1.05); +} + +.excluded-containers-controls { + display: flex; + gap: 8px; + align-items: center; +} + +.excluded-input { + flex: 1; + padding: 6px 10px; + border: 1px solid var(--border); + border-radius: 4px; + background: var(--bg); + color: var(--fg); + font-size: 12px; + transition: border-color 0.2s ease; +} + +.excluded-input:focus { + outline: none; + border-color: var(--accent); +} + +.excluded-input::placeholder { + color: var(--muted); +} + +/* Buttons */ +.btn { + background: var(--chip); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 6px; + padding: 8px 12px; + font-size: 12px; + cursor: pointer; + transition: all 0.2s ease; + display: inline-flex; + align-items: center; + gap: 6px; +} + +.btn:hover { + background: var(--tab-active); + border-color: var(--accent); +} + +.btn-primary { + background: var(--accent); + color: #0b0d12; + border-color: var(--accent); + font-weight: 500; +} + +.btn-primary:hover { + background: #6b8fd8; +} + +.btn-small { + padding: 4px 8px; + font-size: 10px; + min-width: auto; +} + +.btn-full-width { + width: 100%; + justify-content: center; +} + +/* Стили для кнопки refresh */ +.log-refresh-btn { + background: var(--accent); + color: white; + border: 1px solid var(--accent); + border-radius: 6px; + transition: all 0.2s ease; + padding: 6px 12px; + font-size: 11px; + font-weight: 500; + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 60px; + text-align: center; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +.log-refresh-btn:hover { + background: var(--accent); + opacity: 0.8; + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0,0,0,0.2); +} + +/* Стили для кнопки помощи в header */ +.help-btn { + background: var(--accent); + color: #0b0d12; + border: 1px solid var(--accent); + border-radius: 6px; + padding: 6px 8px; + font-size: 11px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + min-width: 32px; + text-align: center; + box-shadow: 0 2px 6px rgba(187, 154, 247, 0.3); +} + +.help-btn i { + font-size: 12px; + animation: help-pulse 2s ease-in-out infinite; +} + +@keyframes help-pulse { + 0%, 100% { + transform: scale(1); + } + 50% { + transform: scale(1.1); + } +} + +/* Стили для счетчиков-кнопок */ +.counter-btn { + display: inline-flex; + flex-direction: row; + align-items: center; + justify-content: center; + padding: 6px 12px; + margin: 0 4px; + border: none; + border-radius: 6px; + font-size: 11px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + min-width: 70px; +} + +.counter-btn:hover { + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0,0,0,0.2); +} + +/* Убираем стиль hidden, так как счетчики больше не скрываются */ + +.counter-btn.disabled { + opacity: 0.5; + background: var(--muted) !important; + cursor: pointer; +} + +.counter-btn.disabled:hover { + opacity: 0.7; +} + +.counter-label { + font-size: 10px; + opacity: 0.9; + margin-right: 4px; +} + +.counter-value { + font-size: 12px; + font-weight: bold; +} + +/* Цвета для разных уровней логов */ +.debug-btn { + background: #6c757d; /* Серый */ + color: white; +} + +.debug-btn:hover { + background: #5a6268; /* Затемненный серый */ +} + +.info-btn { + background: var(--ok); /* Зеленый цвет */ + color: white; + border: 1px solid var(--ok); +} + +.info-btn:hover { + background: #7ea855; /* Затемненный зеленый */ +} + +.warn-btn { + background: #ffc107; /* Оставляем текущий цвет */ + color: #212529; +} + +.warn-btn:hover { + background: #e0a800; /* Затемненный желтый */ +} + +.error-btn { + background: #dc3545; /* Оставляем текущий цвет */ + color: white; +} + +.error-btn:hover { + background: #c82333; /* Затемненный красный */ +} + +.other-btn { + background: #f8f9fa; /* Самый светлый серый */ + color: #212529; +} + +.other-btn:hover { + background: #dee2e6; /* Затемненный серый */ +} + +.btn-group { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +.btn-group.actions-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px 16px; + width: 100%; +} + +.btn-group.actions-grid .btn-full-width { + grid-column: 1 / -1; +} + +/* Main Content */ +.main-content { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; +} + +/* Header */ +.header { + height: var(--header-height); + background: var(--panel); + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + padding: 0 20px; + gap: 16px; + flex-wrap: wrap; +} + +.header-title { + font-size: 16px; + font-weight: 600; + color: var(--fg); + margin: 0; + display: flex; + align-items: center; + gap: 8px; +} + + + +.header-badge { + color: var(--muted); + padding: 8px 12px; + font-size: 12px; + display: flex; + align-items: center; + gap: 8px; + height: fit-content; +} + +.header-badge select { + background: var(--chip); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 6px; + padding: 8px 12px; + font-size: 12px; + cursor: pointer; + outline: none; + transition: border-color 0.2s ease; + min-width: 120px; +} + +.header-badge select:focus { + border-color: var(--accent); +} + +.header-badge select option { + background: var(--bg); + color: var(--fg); +} + +/* Мультивыбор проектов */ +.multi-select-container { + position: relative; + min-width: 120px; +} + +.multi-select-display { + background: var(--chip); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 6px; + padding: 8px 12px; + font-size: 12px; + cursor: pointer; + outline: none; + transition: border-color 0.2s ease; + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.multi-select-display:hover { + border-color: var(--accent); +} + +.multi-select-display.active { + border-color: var(--accent); + background: var(--tab-active); +} + +.multi-select-text { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.multi-select-dropdown { + position: absolute; + top: 100%; + left: 0; + right: 0; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 6px; + box-shadow: 0 4px 12px rgba(0,0,0,0.15); + z-index: 1000; + max-height: 200px; + overflow-y: auto; + margin-top: 4px; +} + +.multi-select-option { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + cursor: pointer; + transition: background-color 0.2s ease; +} + +.multi-select-option:hover { + background: var(--chip); +} + +.multi-select-option input[type="checkbox"] { + margin: 0; +} + +.multi-select-option label { + cursor: pointer; + font-size: 12px; + color: var(--fg); + flex: 1; +} + +.header-controls { + margin-left: auto; + display: flex; + align-items: center; + gap: 12px; +} + +.header-filter { + flex: 1; + min-width: 200px; + max-width: none; + padding: 8px 12px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--bg); + color: var(--fg); + font-size: 12px; + transition: border-color 0.2s ease; + margin: 0 16px; + width: 100%; +} + +.header-filter:focus { + outline: none; + border-color: var(--accent); +} + +.header-filter::placeholder { + color: var(--muted); +} + +/* Адаптивность для header-filter */ +@media (max-width: 1200px) { + .header-filter { + min-width: 150px; + margin: 0 12px; + } + + .sidebar.collapsed ~ .main-content .header .header-filter { + min-width: 120px; + margin: 0 6px; + } +} + +@media (max-width: 768px) { + .header-filter { + min-width: 120px; + margin: 0 8px; + padding: 6px 10px; + font-size: 11px; + } + + .header { + gap: 12px; + padding: 0 16px; + } + + .sidebar.collapsed ~ .main-content .header .header-filter { + min-width: 100px; + margin: 0 4px; + padding: 4px 8px; + font-size: 10px; + } +} + +@media (max-width: 480px) { + .header-filter { + min-width: 100px; + margin: 0 4px; + padding: 4px 8px; + font-size: 10px; + } + + .header { + gap: 8px; + padding: 0 12px; + } + + .header-controls { + gap: 8px; + } + + /* Адаптивность для свернутого sidebar */ + .sidebar.collapsed ~ .main-content .header .header-filter { + min-width: 100px; + margin: 0 4px; + } +} + +.header-project-select { + padding: 6px 12px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--bg); + color: var(--fg); + font-size: 12px; + cursor: pointer; + transition: border-color 0.2s ease; + min-width: 120px; +} + +.header-project-select:focus { + outline: none; + border-color: var(--accent); +} + +.header-project-select option { + background: var(--bg); + color: var(--fg); +} + +/* Theme Toggle */ +.theme-toggle { + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; + color: var(--muted); +} + +.theme-toggle input { + appearance: none; + width: 40px; + height: 20px; + border-radius: 999px; + position: relative; + background: var(--chip); + border: 1px solid var(--border); + cursor: pointer; + transition: background 0.2s ease; +} + +.theme-toggle input::after { + content: ""; + position: absolute; + top: 2px; + left: 2px; + width: 14px; + height: 14px; + border-radius: 50%; + background: var(--fg); + transition: transform 0.2s ease; +} + +.theme-toggle input:checked::after { + transform: translateX(20px); +} + +.theme-toggle input:checked { + background: var(--accent); +} + +/* Container List */ +.container-list { + flex: 1; + overflow-y: auto; + padding: 16px; +} + +.container-item { + background: var(--chip); + border: 1px solid var(--border); + border-radius: 8px; + padding: 12px; + margin-bottom: 8px; + cursor: pointer; + transition: all 0.2s ease; + position: relative; +} + +.container-item:hover { + background: var(--tab-active); + border-color: var(--accent); +} + +.container-item.active { + background: var(--tab-active); + border-color: var(--accent); + color: var(--accent); +} + +.container-item.active::before { + content: ""; + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 3px; + background: var(--accent); + border-radius: 0 2px 2px 0; +} + +.container-name { + font-size: 13px; + font-weight: 500; + margin-bottom: 4px; + display: flex; + align-items: center; + gap: 8px; +} + +.container-service { + font-size: 11px; + color: var(--muted); + margin-bottom: 4px; +} + +.container-status { + display: flex; + align-items: center; + gap: 8px; + font-size: 10px; +} + +.status-indicator { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--ok); +} + +.status-indicator.running { background: var(--ok); } +.status-indicator.stopped { background: var(--err); } +.status-indicator.paused { background: var(--warn); } + +.container-link { + color: var(--accent); + text-decoration: none; + opacity: 0.7; + transition: opacity 0.2s ease; + margin-left: 4px; +} + +.container-link:hover { + opacity: 1; + color: var(--accent); +} + +/* Чекбоксы для мультивыбора контейнеров */ +.container-select { + position: absolute; + bottom: 8px; + right: 8px; + z-index: 10; +} + +.container-checkbox { + display: none; +} + +.container-checkbox-label { + display: flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + border: 2px solid var(--border); + border-radius: 4px; + background: var(--bg); + cursor: pointer; + transition: all 0.2s ease; + position: relative; +} + +.container-checkbox-label:hover { + border-color: var(--accent); + background: var(--chip); +} + +.container-checkbox:checked + .container-checkbox-label { + background: var(--accent); + border-color: var(--accent); +} + +.container-checkbox:checked + .container-checkbox-label::after { + content: "✓"; + color: white; + font-size: 12px; + font-weight: bold; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.container-item.selected { + border-color: var(--accent); + background: var(--tab-active); +} + +/* Мультипросмотр */ +.multi-view-grid { + display: grid; + gap: 2px; + height: 100%; + padding: 0px; + /* Равная высота строк для нескольких рядов (3+ окон) */ + grid-auto-rows: 1fr; + align-items: stretch; +} + +.multi-view-panel { + background: var(--panel); + border: 1px solid var(--border); + border-radius: 8px; + display: flex; + flex-direction: column; + overflow: hidden; + padding: 2px; +} + +.multi-view-header { + padding: 5px 16px; + background: var(--chip); + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + min-height: 16px; +} + +.multi-view-title { + font-size: 13px; + font-weight: 500; + color: var(--fg); + margin: 0; + flex: 1; + min-width: 0; /* Позволяет flex-элементу сжиматься */ + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + + + +.multi-view-content { + flex: 1; + overflow: hidden; +} + +/* Single View Panel - аналогично Multi View */ +.single-view-panel { + background: var(--panel); + border: 1px solid var(--border); + border-radius: 8px; + display: flex; + flex-direction: column; + overflow: hidden; + padding: 2px; + height: 100%; +} + +.single-view-header { + padding: 5px 16px; + background: var(--chip); + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + min-height: 16px; +} + +.single-view-title { + font-size: 13px; + font-weight: 500; + color: var(--fg); + margin: 0; + flex: 1; + min-width: 0; /* Позволяет flex-элементу сжиматься */ + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* Кнопки уровней логирования для заголовков */ +.single-view-levels, +.multi-view-levels { + display: flex; + gap: 4px; + align-items: center; + flex-shrink: 0; /* Предотвращает сжатие кнопок */ +} + +.level-btn { + display: flex; + flex-direction: column; + align-items: center; + gap: 2px; + padding: 4px 6px; + border: 1px solid var(--border); + border-radius: 4px; + background: var(--panel); + color: var(--fg); + cursor: pointer; + transition: all 0.2s ease; + font-size: 10px; + min-width: 40px; + max-width: 50px; + position: relative; + flex-shrink: 0; /* Предотвращает сжатие кнопок */ +} + +.level-btn:hover { + background: var(--chip); + border-color: var(--accent); +} + +.level-btn.active { + background: var(--accent); + color: #0b0d12; + border-color: var(--accent); +} + +.level-btn.disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.level-btn.disabled:hover { + background: var(--panel); + border-color: var(--border); +} + +.level-label { + font-weight: 500; + font-size: 9px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.level-value { + font-weight: 600; + font-size: 11px; +} + +/* Цвета для разных уровней */ +.level-btn.debug-btn { + border-color: #6c757d; /* Серый */ + color: #6c757d; +} + +.level-btn.debug-btn:hover, +.level-btn.debug-btn.active { + background: #5a6268; /* Затемненный серый */ + color: white; +} + +.level-btn.info-btn { + border-color: var(--ok); /* Зеленый цвет */ + color: var(--ok); +} + +.level-btn.info-btn:hover, +.level-btn.info-btn.active { + background: #7ea855; /* Затемненный зеленый */ + color: white; +} + +.level-btn.warn-btn { + border-color: var(--warn); /* Оставляем текущий цвет */ + color: var(--warn); +} + +.level-btn.warn-btn:hover, +.level-btn.warn-btn.active { + background: #e0a800; /* Затемненный желтый */ + color: #0b0d12; +} + +.level-btn.error-btn { + border-color: var(--err); /* Оставляем текущий цвет */ + color: var(--err); +} + +.level-btn.error-btn:hover, +.level-btn.error-btn.active { + background: #c82333; /* Затемненный красный */ + color: white; +} + +.level-btn.other-btn { + border-color: #f8f9fa; /* Самый светлый серый */ + color: #f8f9fa; +} + +.level-btn.other-btn:hover, +.level-btn.other-btn.active { + background: #dee2e6; /* Затемненный серый */ + color: #212529; +} + +.single-view-content { + flex: 1; + overflow: hidden; +} + +.single-view-content .log { + height: 100%; + margin: 0; + padding: 12px; + font-size: 11px; + line-height: 1.4; + white-space: pre; + word-break: break-word; + overflow: auto; + background: var(--bg); + color: var(--fg); + font-family: ui-monospace, Menlo, Consolas, monospace; +} + +.multi-view-log { + height: 100%; + margin: 0; + padding: 12px; + font-size: 11px; + line-height: 1.4; + white-space: pre; + word-break: break-word; + overflow: auto; + background: var(--bg); + color: var(--fg); + font-family: ui-monospace, Menlo, Consolas, monospace; +} + +/* Log Area */ +.log-area { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.log-header { + padding: 12px 20px; + background: var(--panel); + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; +} + +.log-title { + font-size: 14px; + font-weight: 500; + color: var(--fg); + margin: 0; +} + +.log-controls { + margin-left: auto; + display: flex; + align-items: center; + gap: 8px; +} + +.log-content { + flex: 1; + overflow: hidden; + padding: 0; + background: var(--bg); +} + +.log { + white-space: pre-wrap; + word-break: break-word; + font-size: 12px; + line-height: 1.5; + margin: 0; + tab-size: 2; + height: 100%; + overflow: auto; +} + +/* Responsive */ +@media (max-width: 768px) { + .sidebar { + width: 100%; + position: absolute; + z-index: 1000; + height: 100%; + transform: translateX(-100%); + transition: transform 0.3s ease; + } + + .sidebar.open { + transform: translateX(0); + } + + .main-content { + width: 100%; + } + + .mobile-toggle { + display: block; + } +} + +.mobile-toggle { + display: none; + background: none; + border: none; + color: var(--fg); + font-size: 18px; + cursor: pointer; + padding: 8px; +} + +/* Legacy styles for compatibility */ +#tabs{display:none} +main{display:none} +.grid-1{grid-template-columns:1fr} +.grid-2{grid-template-columns:1fr 1fr} +.grid-3{grid-template-columns:1fr 1fr 1fr} +.grid-4{grid-template-columns:1fr 1fr;grid-auto-rows:45vh} +.panel{border:1px solid var(--border);border-radius:10px;background:color-mix(in oklab, var(--panel) 96%, var(--bg));display:flex;flex-direction:column;min-height:0} +.panel .title{padding:6px 10px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px;color:var(--muted);font-size:12px} +.badge{padding:2px 8px;border-radius:999px;background:var(--chip);border:1px solid var(--border);margin-left:6px;color:var(--muted)} +.controls .badge{margin-left:0} +.toolbar{display:flex;gap:6px;margin-left:auto} +.counter{font-size:11px;color:var(--muted)} +.logwrap{flex:1;overflow:auto;padding:10px} +.log{white-space:pre-wrap;word-break:break-word;font-size:12px;line-height:1.5;margin:0;tab-size:2} +.line{color:var(--fg); display:block; margin:0; padding:0; line-height:1.4} .ok{color:var(--ok)} .warn{color:var(--warn)} .err{color:var(--err)} .dbg{color:#7dcfff} .ts{color:var(--muted)} +/* Цвета для multi-view логов */ +.multi-view-log .line{color:var(--fg) !important; display:block; margin:0; padding:0; line-height:1.4} +.multi-view-log .ok{color:var(--ok) !important} +.multi-view-log .warn{color:var(--warn) !important} +.multi-view-log .err{color:var(--err) !important} +.multi-view-log .dbg{color:#7dcfff !important} +.multi-view-log .ts{color:var(--muted) !important} + +/* Дополнительные стили для multi-view логов */ +.multi-view-log span.line{color:var(--fg) !important; display:block; margin:0; padding:0; line-height:1.4} +.multi-view-log span.ok{color:var(--ok) !important} +.multi-view-log span.warn{color:var(--warn) !important} +.multi-view-log span.err{color:var(--err) !important} +.multi-view-log span.dbg{color:#7dcfff !important} +.multi-view-log span.ts{color:var(--muted) !important} + +/* Стили для ANSI цветов в multi-view */ +.multi-view-log .ansi-red{color:#f7768e !important} +.multi-view-log .ansi-green{color:#22c55e !important} +.multi-view-log .ansi-yellow{color:#eab308 !important} +.multi-view-log .ansi-blue{color:#3b82f6 !important} +.multi-view-log .ansi-magenta{color:#a855f7 !important} +.multi-view-log .ansi-cyan{color:#06b6d4 !important} +.multi-view-log .ansi-white{color:var(--fg) !important} +.multi-view-log .ansi-black{color:#79808f !important} +footer{position:fixed;right:10px;bottom:10px;opacity:.6;font-size:11px} +.filterlvl{display:flex;gap:6px;align-items:center} +/* Instance tag */ +.inst-tag{display:inline-block;padding:0 6px;margin-right:6px;border-radius:6px;border:1px solid var(--border);opacity:.9} +/* ANSI */ +.ansi-black{color:#79808f} .ansi-red{color:#f7768e} .ansi-green{color:#22c55e} .ansi-yellow{color:#eab308} +.ansi-blue{color:#3b82f6} .ansi-magenta{color:#a855f7} .ansi-cyan{color:#06b6d4} .ansi-white{color:var(--fg)} +.ansi-bold{font-weight:bold} .ansi-italic{font-style:italic} .ansi-underline{text-decoration:underline} + +/* Theme toggle */ +.theme-toggle{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--muted)} +.theme-toggle input{appearance:none;width:36px;height:20px;border-radius:999px;position:relative;background:var(--chip);border:1px solid var(--border);cursor:pointer} +.theme-toggle input::after{content:"";position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;background:var(--fg);transition:transform .2s ease} +.theme-toggle input:checked::after{transform:translateX(16px)} + +/* Floating copy button */ +.copy-fab{ + position:fixed; z-index:9999; display:none; padding:6px 10px; border-radius:8px; + background:var(--accent); color:#0b0d12; border:none; box-shadow:0 6px 20px rgba(0,0,0,.25); + font-size:12px; +} +.copy-fab.show{display:block} +.copy-fab:active{transform:translateY(1px)} + +/* Модальное окно с горячими клавишами */ +.hotkeys-modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + z-index: 10000; + display: none; + align-items: center; + justify-content: center; +} + +.hotkeys-modal.show { + display: flex; +} + +.hotkeys-modal-content { + background: var(--panel); + border: 1px solid var(--border); + border-radius: 12px; + box-shadow: 0 16px 64px rgba(0,0,0,0.4); + max-width: 500px; + width: 90%; + max-height: 80vh; + overflow-y: auto; + animation: modalSlideIn 0.3s ease; +} + +@keyframes modalSlideIn { + from { + opacity: 0; + transform: scale(0.9) translateY(-20px); + } + to { + opacity: 1; + transform: scale(1) translateY(0); + } +} + +.hotkeys-modal-header { + padding: 20px 24px 16px; + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + justify-content: space-between; +} + +.hotkeys-modal-title { + font-size: 18px; + font-weight: 600; + color: var(--accent); + margin: 0; + display: flex; + align-items: center; + gap: 8px; +} + +.hotkeys-modal-close { + background: none; + border: none; + color: var(--muted); + cursor: pointer; + padding: 8px; + border-radius: 6px; + transition: all 0.2s ease; + font-size: 16px; +} + +.hotkeys-modal-close:hover { + background: var(--chip); + color: var(--fg); +} + +.hotkeys-modal-body { + padding: 20px 24px; +} + +.hotkeys-section { + margin-bottom: 24px; +} + +.hotkeys-section:last-child { + margin-bottom: 0; +} + +.hotkeys-section-title { + font-size: 14px; + font-weight: 600; + color: var(--fg); + margin: 0 0 12px 0; + padding-bottom: 8px; + border-bottom: 1px solid var(--border); +} + +.hotkeys-list { + list-style: none; + padding: 0; + margin: 0; +} + +.hotkeys-list li { + display: flex; + align-items: center; + gap: 12px; + margin: 8px 0; + font-size: 13px; + color: var(--fg); +} + +.hotkeys-list kbd { + background: var(--chip); + border: 1px solid var(--border); + border-radius: 4px; + padding: 4px 8px; + font-size: 11px; + font-family: monospace; + color: var(--accent); + min-width: 20px; + text-align: center; +} + +/* Hotkeys notification */ +.hotkeys-notification { + position: fixed; + top: 20px; + right: 20px; + z-index: 10000; + background: var(--panel); + border: 1px solid var(--border); + border-radius: 8px; + box-shadow: 0 8px 32px rgba(0,0,0,0.3); + max-width: 350px; + animation: slideInRight 0.3s ease; +} + +@keyframes slideInRight { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +.notification-content { + padding: 16px; + position: relative; +} + +.notification-content h4 { + margin: 0 0 12px 0; + color: var(--accent); + font-size: 14px; + display: flex; + align-items: center; + gap: 8px; +} + +.notification-content ul { + margin: 0; + padding: 0; + list-style: none; +} + +.notification-content li { + margin: 8px 0; + font-size: 12px; + color: var(--fg); + display: flex; + align-items: center; + gap: 8px; +} + +.notification-content kbd { + background: var(--chip); + border: 1px solid var(--border); + border-radius: 4px; + padding: 2px 6px; + font-size: 10px; + font-family: monospace; + color: var(--accent); +} + +.notification-close { + position: absolute; + top: 8px; + right: 8px; + background: none; + border: none; + color: var(--muted); + cursor: pointer; + padding: 4px; + border-radius: 4px; + transition: all 0.2s ease; +} + +.notification-close:hover { + background: var(--chip); + color: var(--fg); diff --git a/app/static/css/login.css b/app/static/css/login.css new file mode 100644 index 0000000..52e8d40 --- /dev/null +++ b/app/static/css/login.css @@ -0,0 +1,279 @@ + /* THEME TOKENS */ + :root{ + --bg:#0e0f13; --panel:#151821; --muted:#8b94a8; --accent:#7aa2f7; --ok:#9ece6a; --warn:#e0af68; --err:#f7768e; --fg:#e5e9f0; + --border:#2a2f3a; --tab:#1b2030; --tab-active:#22283a; --chip:#2b3142; --link:#9ab8ff; + } + :root[data-theme="light"]{ + --bg:#f7f9fc; --panel:#ffffff; --muted:#667085; --accent:#3b82f6; --ok:#15803d; --warn:#b45309; --err:#b91c1c; --fg:#0f172a; + --border:#e5e7eb; --tab:#eef2ff; --tab-active:#dbeafe; --chip:#eef2f7; --link:#1d4ed8; + } + + *{box-sizing:border-box} + html,body{height:100%; margin: 0; padding: 0;} + body{background:var(--bg);color:var(--fg);font:13px/1.45 ui-monospace,Menlo,Consolas,monospace;} + + .login-container { + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + padding: 20px; + } + + .login-card { + background: var(--panel); + border: 1px solid var(--border); + border-radius: 12px; + padding: 40px; + width: 100%; + max-width: 400px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); + } + + .login-header { + text-align: center; + margin-bottom: 32px; + } + + .login-logo { + font-size: 32px; + color: var(--accent); + margin-bottom: 16px; + } + + .login-title { + font-size: 24px; + font-weight: 600; + color: var(--fg); + margin: 0 0 8px 0; + } + + .login-subtitle { + font-size: 14px; + color: var(--muted); + margin: 0; + } + + .login-form { + display: flex; + flex-direction: column; + gap: 20px; + } + + .form-group { + display: flex; + flex-direction: column; + gap: 8px; + } + + .form-label { + font-size: 12px; + font-weight: 500; + color: var(--muted); + text-transform: uppercase; + letter-spacing: 0.5px; + } + + .form-input { + background: var(--chip); + border: 1px solid var(--border); + border-radius: 8px; + padding: 12px 16px; + font-size: 14px; + color: var(--fg); + transition: all 0.2s ease; + font-family: inherit; + } + + .form-input:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 3px rgba(122, 162, 247, 0.1); + } + + .form-input::placeholder { + color: var(--muted); + } + + .password-input-wrapper { + position: relative; + width: 100%; + } + + .password-toggle { + position: absolute; + right: 8px; /* Ближе к краю для всех устройств */ + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + color: var(--muted); + cursor: pointer; + padding: 6px; + border-radius: 4px; + transition: all 0.2s ease; + z-index: 10; + display: flex; + align-items: center; + justify-content: center; + min-width: 24px; + min-height: 24px; + } + + .password-toggle:hover { + color: var(--fg); + background: var(--chip); + } + + .password-toggle:active { + transform: translateY(-50%) scale(0.95); + } + + .password-input-wrapper .form-input { + padding-right: 40px; /* Место для кнопки */ + width: 100%; /* Поле на всю ширину */ + } + + .login-button { + background: var(--accent); + color: #0b0d12; + border: none; + border-radius: 8px; + padding: 14px 24px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + font-family: inherit; + margin-top: 8px; + } + + .login-button:hover { + background: #6b8fd8; + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(122, 162, 247, 0.3); + } + + .login-button:disabled { + background: var(--muted); + cursor: not-allowed; + transform: none; + box-shadow: none; + } + + .error-message { + background: rgba(247, 118, 142, 0.1); + border: 1px solid var(--err); + border-radius: 8px; + padding: 12px 16px; + font-size: 14px; + color: var(--err); + display: none; + } + + .error-message.show { + display: block; + } + + .loading-spinner { + display: none; + width: 16px; + height: 16px; + border: 2px solid transparent; + border-top: 2px solid currentColor; + border-radius: 50%; + animation: spin 1s linear infinite; + margin-right: 8px; + } + + .loading-spinner.show { + display: inline-block; + } + + @keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } + } + + .footer { + text-align: center; + margin-top: 32px; + padding-top: 24px; + border-top: 1px solid var(--border); + font-size: 12px; + color: var(--muted); + } + + .footer a { + color: var(--link); + text-decoration: none; + } + + .footer a:hover { + text-decoration: underline; + } + + /* Theme toggle */ + .theme-toggle { + position: fixed; + top: 20px; + right: 20px; + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; + color: var(--muted); + background: var(--panel); + border: 1px solid var(--border); + border-radius: 8px; + padding: 8px 12px; + } + + .theme-toggle input { + appearance: none; + width: 36px; + height: 20px; + border-radius: 999px; + position: relative; + background: var(--chip); + border: 1px solid var(--border); + cursor: pointer; + transition: background 0.2s ease; + } + + .theme-toggle input::after { + content: ""; + position: absolute; + top: 2px; + left: 2px; + width: 14px; + height: 14px; + border-radius: 50%; + background: var(--fg); + transition: transform 0.2s ease; + } + + .theme-toggle input:checked::after { + transform: translateX(16px); + } + + .theme-toggle input:checked { + background: var(--accent); + } + + @media (max-width: 480px) { + .login-card { + padding: 24px; + margin: 16px; + } + + .login-title { + font-size: 20px; + } + + .theme-toggle { + top: 16px; + right: 16px; + padding: 6px 10px; + font-size: 11px; + } diff --git a/app/static/js/login.js b/app/static/js/login.js new file mode 100644 index 0000000..5051a3c --- /dev/null +++ b/app/static/js/login.js @@ -0,0 +1,105 @@ + flex-direction: column; + gap: 8px; + } + + .form-label { + font-size: 12px; + font-weight: 500; + color: var(--muted); + text-transform: uppercase; + letter-spacing: 0.5px; + } + + .form-input { + background: var(--chip); + border: 1px solid var(--border); + border-radius: 8px; + padding: 12px 16px; + font-size: 14px; + color: var(--fg); + transition: all 0.2s ease; + font-family: inherit; + } + + .form-input:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 3px rgba(122, 162, 247, 0.1); + } + + .form-input::placeholder { + color: var(--muted); + } + + .password-input-wrapper { + position: relative; + width: 100%; + } + + .password-toggle { + position: absolute; + right: 8px; /* Ближе к краю для всех устройств */ + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + color: var(--muted); + cursor: pointer; + padding: 6px; + border-radius: 4px; + transition: all 0.2s ease; + z-index: 10; + display: flex; + align-items: center; + justify-content: center; + min-width: 24px; + min-height: 24px; + } + + .password-toggle:hover { + color: var(--fg); + background: var(--chip); + } + + .password-toggle:active { + transform: translateY(-50%) scale(0.95); + } + + .password-input-wrapper .form-input { + padding-right: 40px; /* Место для кнопки */ + width: 100%; /* Поле на всю ширину */ + } + + .login-button { + background: var(--accent); + color: #0b0d12; + border: none; + border-radius: 8px; + padding: 14px 24px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + font-family: inherit; + margin-top: 8px; + } + + .login-button:hover { + background: #6b8fd8; + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(122, 162, 247, 0.3); + } + + .login-button:disabled { + background: var(--muted); + cursor: not-allowed; + transform: none; + box-shadow: none; + } + + .error-message { + background: rgba(247, 118, 142, 0.1); + border: 1px solid var(--err); + border-radius: 8px; + padding: 12px 16px; + font-size: 14px; diff --git a/templates/error.html b/app/templates/error.html similarity index 100% rename from templates/error.html rename to app/templates/error.html diff --git a/templates/index.html b/app/templates/index.html similarity index 100% rename from templates/index.html rename to app/templates/index.html diff --git a/templates/login.html b/app/templates/login.html similarity index 100% rename from templates/login.html rename to app/templates/login.html