From 0d25a54149d42edd9d9c379f4270fabc48fc4c51 Mon Sep 17 00:00:00 2001 From: Dennis Braun Date: Mon, 23 Mar 2026 19:20:00 +0100 Subject: [PATCH 1/5] Polish channels view: inline controls, info bar, onboarding, full-width modulation 1. Remove centered title, controls are now the first row (ch-controls) 2. Add channel info bar showing current Power, SNR, DOCSIS version, frequency, and health status after channel selection 3. Add onboarding empty state with icon and guidance text (replaces blank page before channel selection) 4. Make modulation chart full-width (grid-column: 1/-1) to fill the unbalanced empty space 5. Extract inline styles to CSS classes 6. Add channel_onboarding_title/desc i18n keys in all 4 languages --- app/i18n/de.json | 2 + app/i18n/en.json | 2 + app/i18n/es.json | 2 + app/i18n/fr.json | 2 + app/i18n/template.json | 2 + app/static/css/views.css | 82 +++++++++++++++++++++++++++++++++++++++ app/static/js/channels.js | 67 +++++++++++++++++++++++++++++++- app/templates/index.html | 18 ++++++--- 8 files changed, 170 insertions(+), 7 deletions(-) diff --git a/app/i18n/de.json b/app/i18n/de.json index 237d03b5..91d4fba7 100644 --- a/app/i18n/de.json +++ b/app/i18n/de.json @@ -339,6 +339,8 @@ "signal_ds_channels": "DS Kanäle", "signal_us_channels": "US Kanäle", "signal_snapshot_time": "Snapshot", + "channel_onboarding_title": "Kanal-Verlauf", + "channel_onboarding_desc": "Waehle einen Kanal aus dem Dropdown um den Signalverlauf zu sehen.", "channel_timeline": "Kanalverlauf", "select_channel": "Kanal wählen", "downstream_channels": "Downstream Kanäle", diff --git a/app/i18n/en.json b/app/i18n/en.json index 11c458e3..40758804 100644 --- a/app/i18n/en.json +++ b/app/i18n/en.json @@ -339,6 +339,8 @@ "signal_ds_channels": "DS Channels", "signal_us_channels": "US Channels", "signal_snapshot_time": "Snapshot", + "channel_onboarding_title": "Channel Timeline", + "channel_onboarding_desc": "Select a channel from the dropdown to view its signal history over time.", "channel_timeline": "Channel Timeline", "select_channel": "Select Channel", "downstream_channels": "Downstream Channels", diff --git a/app/i18n/es.json b/app/i18n/es.json index e26972fc..c90b4809 100644 --- a/app/i18n/es.json +++ b/app/i18n/es.json @@ -334,6 +334,8 @@ "signal_ds_channels": "Canales DS", "signal_us_channels": "Canales US", "signal_snapshot_time": "Snapshot", + "channel_onboarding_title": "Historial del canal", + "channel_onboarding_desc": "Selecciona un canal del menu para ver su historial de senal.", "channel_timeline": "Historial de canal", "select_channel": "Seleccionar canal", "downstream_channels": "Canales descendentes", diff --git a/app/i18n/fr.json b/app/i18n/fr.json index 4ec0a90a..7ca75a35 100644 --- a/app/i18n/fr.json +++ b/app/i18n/fr.json @@ -331,6 +331,8 @@ "signal_ds_channels": "Canaux DS", "signal_us_channels": "Canaux US", "signal_snapshot_time": "Snapshot", + "channel_onboarding_title": "Historique du canal", + "channel_onboarding_desc": "Selectionnez un canal dans la liste pour voir son historique.", "channel_timeline": "Historique du canal", "select_channel": "Choisir un canal", "downstream_channels": "Canaux descendants", diff --git a/app/i18n/template.json b/app/i18n/template.json index 4d939b23..de926dd7 100644 --- a/app/i18n/template.json +++ b/app/i18n/template.json @@ -319,6 +319,8 @@ "signal_ds_channels": "", "signal_us_channels": "", "signal_snapshot_time": "", + "channel_onboarding_title": "", + "channel_onboarding_desc": "", "channel_timeline": "", "select_channel": "", "downstream_channels": "", diff --git a/app/static/css/views.css b/app/static/css/views.css index ea4e3acb..e897b26b 100644 --- a/app/static/css/views.css +++ b/app/static/css/views.css @@ -1232,6 +1232,88 @@ .btn-ghost:hover { color: var(--text); } +/* ── Channel View Controls ── */ +.ch-controls { + display: flex; + gap: 12px; + margin-bottom: var(--space-lg, 20px); + flex-wrap: wrap; + align-items: center; +} + +/* Channel info bar */ +.ch-info-bar { + display: flex; + gap: var(--space-md, 16px); + padding: var(--space-sm, 8px) var(--space-md, 16px); + margin-bottom: var(--space-md, 16px); + background: var(--tint-subtle, rgba(255,255,255,0.02)); + border: 1px solid var(--glass-border, var(--card-border)); + border-radius: var(--radius-sm, 8px); + font-size: 0.82em; + flex-wrap: wrap; + align-items: center; +} +.ch-info-item { + display: flex; + align-items: center; + gap: 6px; + color: var(--text-secondary); +} +.ch-info-item strong { + color: var(--text-primary); +} +.ch-info-sep { + width: 1px; + height: 16px; + background: var(--glass-border); + flex-shrink: 0; +} +.ch-info-health { + display: inline-flex; + align-items: center; + gap: 4px; + font-weight: 600; +} +.ch-info-health.good { color: var(--good); } +.ch-info-health.tolerated { color: var(--tolerated, var(--warn)); } +.ch-info-health.warn { color: var(--warn); } +.ch-info-health.crit { color: var(--crit); } + +/* Channel onboarding (empty state) */ +.ch-onboarding { + display: flex; + align-items: center; + gap: var(--space-md, 16px); + padding: var(--space-xl, 32px); + background: var(--tint-subtle, rgba(255,255,255,0.02)); + border: 1px dashed var(--glass-border); + border-radius: var(--radius-md, 12px); + color: var(--text-secondary); +} +.ch-onboarding > i, .ch-onboarding > svg { + width: 32px; height: 32px; + color: var(--text-muted); + flex-shrink: 0; +} +.ch-onboarding-text { + display: flex; + flex-direction: column; + gap: 4px; +} +.ch-onboarding-text strong { + color: var(--text-primary); + font-size: 0.95em; +} +.ch-onboarding-text span { + font-size: 0.85em; +} + +/* Full-width chart card (modulation) */ +.ch-full-width { + grid-column: 1 / -1; +} + /* ── Channel Compare ── */ .compare-controls { display: flex; diff --git a/app/static/js/channels.js b/app/static/js/channels.js index 0708e03a..a5f0d962 100644 --- a/app/static/js/channels.js +++ b/app/static/js/channels.js @@ -213,22 +213,86 @@ function loadChannelList(callback) { }); sel.appendChild(grp2); } + _cachedChannelData = data; _channelsLoaded = true; if (callback) callback(); }) .catch(function() { if (callback) callback(); }); } +function _makeInfoItem(text, bold) { + var el = document.createElement('span'); + el.className = 'ch-info-item'; + if (bold) { + var b = document.createElement('strong'); + b.textContent = text; + el.appendChild(b); + } else { + el.textContent = text; + } + return el; +} +function _makeInfoItemWithLabel(label, value, unit) { + var el = document.createElement('span'); + el.className = 'ch-info-item'; + el.textContent = label + ' '; + var b = document.createElement('strong'); + b.textContent = value; + el.appendChild(b); + if (unit) el.appendChild(document.createTextNode(' ' + unit)); + return el; +} +function _makeInfoSep() { + var el = document.createElement('span'); + el.className = 'ch-info-sep'; + return el; +} + +function _updateChannelInfoBar(direction, channelId) { + var bar = document.getElementById('channel-info-bar'); + if (!bar) return; + if (!_cachedChannelData) { bar.style.display = 'none'; return; } + var channels = direction === 'ds' + ? (_cachedChannelData.ds_channels || []) + : (_cachedChannelData.us_channels || []); + var ch = null; + for (var i = 0; i < channels.length; i++) { + if (String(channels[i].channel_id) === String(channelId)) { ch = channels[i]; break; } + } + if (!ch) { bar.style.display = 'none'; return; } + while (bar.firstChild) bar.removeChild(bar.firstChild); + var dir = direction.toUpperCase(); + var health = ch.health || 'unknown'; + var healthLabel = T['health_' + health] || health; + + bar.appendChild(_makeInfoItem(dir + ' ' + channelId, true)); + bar.appendChild(_makeInfoSep()); + if (ch.frequency) bar.appendChild(_makeInfoItem(ch.frequency + ' MHz')); + bar.appendChild(_makeInfoItem('DOCSIS ' + (ch.docsis_version || '3.0'))); + bar.appendChild(_makeInfoSep()); + if (ch.power != null) bar.appendChild(_makeInfoItemWithLabel(T.power_dbmv || 'Power', ch.power, 'dBmV')); + if (ch.snr != null) bar.appendChild(_makeInfoItemWithLabel(T.snr_db || 'SNR', ch.snr, 'dB')); + bar.appendChild(_makeInfoSep()); + var healthEl = document.createElement('span'); + healthEl.className = 'ch-info-health ' + health; + healthEl.textContent = healthLabel; + bar.appendChild(healthEl); + bar.style.display = ''; +} + +var _cachedChannelData = null; + function loadChannelTimeline() { var sel = document.getElementById('channel-select'); var val = sel.value; var chartsEl = document.getElementById('channel-charts'); var emptyEl = document.getElementById('channel-empty'); var loadingEl = document.getElementById('channel-loading'); + var infoBar = document.getElementById('channel-info-bar'); if (!val) { chartsEl.style.display = 'none'; loadingEl.style.display = 'none'; - emptyEl.textContent = T.channel_select_prompt || 'Select a channel above to view its signal history.'; + if (infoBar) infoBar.style.display = 'none'; emptyEl.style.display = ''; writeChannelHash(); return; @@ -244,6 +308,7 @@ function loadChannelTimeline() { loadingEl.style.display = ''; chartsEl.style.display = 'none'; emptyEl.style.display = 'none'; + _updateChannelInfoBar(direction, channelId); fetch('/api/channel-history?channel_id=' + channelId + '&direction=' + direction + '&days=' + days) .then(function(r) { return r.json(); }) diff --git a/app/templates/index.html b/app/templates/index.html index ff0c84cb..4066be1a 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -1338,10 +1338,7 @@

{{ t.bqm_import_title }}

-
- {{ t.channels }} -
-
+
@@ -1375,12 +1372,21 @@

{{ t.bqm_import_title }}

+ + +
- +