|
72 | 72 | white-space: nowrap; overflow: hidden; text-overflow: ellipsis; |
73 | 73 | } |
74 | 74 | .topbar-meta { font-size: 0.8em; color: var(--muted); white-space: nowrap; } |
75 | | - .settings-link { |
76 | | - color: var(--muted); text-decoration: none; |
77 | | - font-size: 1.2em; padding: 4px; line-height: 1; |
78 | | - } |
79 | | - .settings-link:hover { color: var(--accent); } |
80 | | - .settings-link svg { width: 20px; height: 20px; vertical-align: middle; } |
| 75 | + |
| 76 | + /* ── Layout ── */ |
| 77 | + .app-layout { display: flex; min-height: 100vh; } |
81 | 78 |
|
82 | 79 | /* ── Sidebar ── */ |
83 | | - .sidebar-overlay { |
84 | | - position: fixed; inset: 0; background: var(--overlay); |
85 | | - z-index: 90; opacity: 0; pointer-events: none; transition: opacity 0.25s; |
86 | | - } |
87 | | - .sidebar-overlay.open { opacity: 1; pointer-events: auto; } |
88 | 80 | .sidebar { |
89 | | - position: fixed; top: 0; left: 0; bottom: 0; width: 260px; |
90 | | - background: var(--surface); z-index: 100; |
91 | | - transform: translateX(-100%); transition: transform 0.25s ease; |
| 81 | + width: 240px; min-width: 240px; |
| 82 | + background: var(--surface); |
92 | 83 | display: flex; flex-direction: column; |
93 | 84 | border-right: 1px solid var(--input-border); |
| 85 | + transition: width 0.25s ease, min-width 0.25s ease; |
| 86 | + overflow: hidden; |
94 | 87 | } |
95 | | - .sidebar.open { transform: translateX(0); } |
| 88 | + .sidebar.collapsed { width: 0; min-width: 0; } |
96 | 89 | .sidebar-content { flex: 1; overflow-y: auto; } |
97 | 90 | .sidebar-header { |
98 | 91 | display: flex; justify-content: space-between; align-items: center; |
99 | | - padding: 16px 20px; border-bottom: 1px solid var(--input-border); |
100 | | - font-weight: bold; color: var(--accent); |
| 92 | + padding: 14px 20px; border-bottom: 1px solid var(--input-border); |
| 93 | + font-weight: bold; color: var(--accent); white-space: nowrap; |
101 | 94 | } |
102 | | - .sidebar-close { |
| 95 | + .sidebar-toggle { |
103 | 96 | background: none; border: none; color: var(--muted); |
104 | | - font-size: 1.4em; cursor: pointer; line-height: 1; |
| 97 | + font-size: 1.2em; cursor: pointer; line-height: 1; padding: 2px 4px; |
105 | 98 | } |
106 | | - .sidebar-close:hover { color: var(--text); } |
| 99 | + .sidebar-toggle:hover { color: var(--accent); } |
| 100 | + .app-main { flex: 1; min-width: 0; display: flex; flex-direction: column; } |
107 | 101 | .sidebar-link { |
108 | 102 | display: flex; align-items: center; gap: 10px; |
109 | 103 | padding: 12px 20px; color: var(--text); text-decoration: none; |
|
309 | 303 | .btn-muted:hover { background: var(--accent); color: #000; } |
310 | 304 |
|
311 | 305 | /* ── Responsive ── */ |
312 | | - @media (max-width: 900px) { .charts-grid { grid-template-columns: 1fr; } } |
| 306 | + @media (max-width: 900px) { |
| 307 | + .charts-grid { grid-template-columns: 1fr; } |
| 308 | + .sidebar { width: 0; min-width: 0; } |
| 309 | + .sidebar.collapsed { width: 0; min-width: 0; } |
| 310 | + } |
313 | 311 | @media (max-width: 600px) { |
314 | 312 | body { font-size: 14px; } |
315 | 313 | .topbar { gap: 6px; padding: 8px 10px; } |
|
322 | 320 | </style> |
323 | 321 | </head> |
324 | 322 | <body> |
325 | | - |
326 | | -<!-- Sidebar Overlay --> |
327 | | -<div class="sidebar-overlay" id="sidebar-overlay"></div> |
| 323 | +<div class="app-layout"> |
328 | 324 |
|
329 | 325 | <!-- Sidebar --> |
330 | 326 | <nav class="sidebar" id="sidebar"> |
331 | 327 | <div class="sidebar-header"> |
332 | | - <span>{{ t.nav }}</span> |
333 | | - <button class="sidebar-close" id="sidebar-close">×</button> |
| 328 | + <span>DOCSIS Monitor</span> |
| 329 | + <button class="sidebar-toggle" id="sidebar-collapse" title="Collapse">❮</button> |
334 | 330 | </div> |
335 | 331 | <div class="sidebar-content"> |
336 | 332 | <a class="sidebar-link active" data-view="live"> |
|
368 | 364 | </div> |
369 | 365 | </nav> |
370 | 366 |
|
| 367 | +<!-- Main Area --> |
| 368 | +<div class="app-main"> |
| 369 | + |
371 | 370 | <!-- Topbar --> |
372 | 371 | <div class="topbar"> |
373 | | - <button class="hamburger-btn" id="hamburger">☰</button> |
| 372 | + <button class="hamburger-btn" id="hamburger" title="Menu">☰</button> |
374 | 373 | <div class="date-nav" id="date-nav"> |
375 | 374 | <button id="date-prev">‹</button> |
376 | 375 | <span class="date-label" id="date-label" title="{{ t.open_calendar }}">{{ t.live }}</span> |
|
380 | 379 | <span class="topbar-meta" id="topbar-meta"> |
381 | 380 | {% if last_update %}{{ t.last_update }}: {{ last_update }} | {{ poll_interval }}s{% endif %} |
382 | 381 | </span> |
383 | | - <a href="/settings" class="settings-link" title="{{ t.settings }}"> |
384 | | - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
385 | | - <circle cx="12" cy="12" r="3"></circle> |
386 | | - <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"></path> |
387 | | - </svg> |
388 | | - </a> |
389 | 382 | </div> |
390 | 383 |
|
391 | 384 | <!-- Calendar Popup --> |
|
433 | 426 | {% endif %} |
434 | 427 |
|
435 | 428 | <div class="summary-grid"> |
| 429 | + {% if isp_name or connection_info %} |
| 430 | + <div class="summary-card"> |
| 431 | + <div class="label">{{ t.tariff }}</div> |
| 432 | + <div class="value">{% if isp_name %}{{ isp_name }}{% endif %}{% if connection_info.max_downstream_kbps %} {{ connection_info.max_downstream_kbps // 1000 }}/{{ connection_info.max_upstream_kbps // 1000 }} Mbit/s{% endif %}</div> |
| 433 | + </div> |
| 434 | + {% endif %} |
436 | 435 | <div class="summary-card"> |
437 | 436 | <div class="label">{{ t.ds_channels }}</div> |
438 | 437 | <div class="value">{{ s.ds_total }}</div> |
@@ -553,6 +552,8 @@ <h2 class="section-title">{{ t.upstream }} ({{ us|length }} {{ t.channels }})</h |
553 | 552 | </div> |
554 | 553 | </div> |
555 | 554 | </div><!-- /main-content --> |
| 555 | +</div><!-- /app-main --> |
| 556 | +</div><!-- /app-layout --> |
556 | 557 |
|
557 | 558 | <!-- Export Modal --> |
558 | 559 | <div class="modal-overlay" id="export-modal"> |
@@ -641,21 +642,19 @@ <h2>{{ t.export_title }}</h2> |
641 | 642 |
|
642 | 643 | /* ── Sidebar ── */ |
643 | 644 | var sidebar = document.getElementById('sidebar'); |
644 | | - var overlay = document.getElementById('sidebar-overlay'); |
645 | 645 |
|
646 | | - function toggleSidebar() { |
647 | | - sidebar.classList.toggle('open'); |
648 | | - overlay.classList.toggle('open'); |
649 | | - } |
650 | | - document.getElementById('hamburger').addEventListener('click', toggleSidebar); |
651 | | - document.getElementById('sidebar-close').addEventListener('click', toggleSidebar); |
652 | | - overlay.addEventListener('click', toggleSidebar); |
| 646 | + function collapseSidebar() { sidebar.classList.add('collapsed'); } |
| 647 | + function expandSidebar() { sidebar.classList.remove('collapsed'); } |
| 648 | + |
| 649 | + document.getElementById('sidebar-collapse').addEventListener('click', collapseSidebar); |
| 650 | + document.getElementById('hamburger').addEventListener('click', function() { |
| 651 | + sidebar.classList.toggle('collapsed'); |
| 652 | + }); |
653 | 653 |
|
654 | 654 | var sidebarLinks = document.querySelectorAll('.sidebar-link[data-view]'); |
655 | 655 | sidebarLinks.forEach(function(link) { |
656 | 656 | link.addEventListener('click', function() { |
657 | 657 | switchView(this.getAttribute('data-view')); |
658 | | - toggleSidebar(); |
659 | 658 | }); |
660 | 659 | }); |
661 | 660 |
|
@@ -903,10 +902,6 @@ <h2>{{ t.export_title }}</h2> |
903 | 902 | var T = {{ t|tojson }}; |
904 | 903 | var modal = document.getElementById('export-modal'); |
905 | 904 | var textarea = document.getElementById('export-text'); |
906 | | - var sidebar = document.getElementById('sidebar'); |
907 | | - var overlay = document.getElementById('sidebar-overlay'); |
908 | | - sidebar.classList.remove('open'); |
909 | | - overlay.classList.remove('open'); |
910 | 905 | textarea.value = T.export_no_data; |
911 | 906 | modal.classList.add('open'); |
912 | 907 | fetch('/api/export') |
|
0 commit comments