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..5011f8e9 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.marginal, .ch-info-health.warn, .ch-info-health.warning { color: var(--warn); } +.ch-info-health.critical, .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..0118f615 100644 --- a/app/static/js/channels.js +++ b/app/static/js/channels.js @@ -150,11 +150,13 @@ function switchChannelMode() { var comparePanel = document.getElementById('channel-panel-compare'); var timelineControls = document.getElementById('channel-timeline-controls'); var compareControls = document.getElementById('channel-compare-controls'); + var infoBar = document.getElementById('channel-info-bar'); if (mode === 'compare') { timelinePanel.style.display = 'none'; comparePanel.style.display = ''; if (timelineControls) timelineControls.style.display = 'none'; if (compareControls) compareControls.style.display = 'contents'; + if (infoBar) infoBar.style.display = 'none'; loadCompareChannelList(); if (_compareChannels.length === 0) { var emptyEl = document.getElementById('compare-empty'); @@ -168,9 +170,12 @@ function switchChannelMode() { if (compareControls) compareControls.style.display = 'none'; var sel = document.getElementById('channel-select'); if (!sel || !sel.value) { - var chEmpty = document.getElementById('channel-empty'); - chEmpty.textContent = T.channel_select_prompt || 'Select a channel above to view its signal history.'; - chEmpty.style.display = ''; + document.getElementById('channel-empty').style.display = ''; + document.getElementById('channel-no-data').style.display = 'none'; + } else { + // Restore info bar for already-selected channel + var cparts = sel.value.split('-'); + _updateChannelInfoBar(cparts[0], cparts[1]); } } writeChannelHash(); @@ -213,22 +218,91 @@ 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) { + var freqStr = String(ch.frequency); + bar.appendChild(_makeInfoItem(freqStr.indexOf('MHz') === -1 ? freqStr + ' MHz' : freqStr)); + } + bar.appendChild(_makeInfoItem('DOCSIS ' + (ch.docsis_version || '3.0'))); + bar.appendChild(_makeInfoSep()); + if (ch.power != null) bar.appendChild(_makeInfoItemWithLabel('Power', ch.power, 'dBmV')); + if (ch.snr != null) bar.appendChild(_makeInfoItemWithLabel('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 noDataEl = document.getElementById('channel-no-data'); var loadingEl = document.getElementById('channel-loading'); + var infoBar = document.getElementById('channel-info-bar'); if (!val) { chartsEl.style.display = 'none'; + noDataEl.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,14 +318,16 @@ function loadChannelTimeline() { loadingEl.style.display = ''; chartsEl.style.display = 'none'; emptyEl.style.display = 'none'; + noDataEl.style.display = 'none'; + _updateChannelInfoBar(direction, channelId); fetch('/api/channel-history?channel_id=' + channelId + '&direction=' + direction + '&days=' + days) .then(function(r) { return r.json(); }) .then(function(data) { loadingEl.style.display = 'none'; if (!data || data.length === 0) { - emptyEl.textContent = T.no_channel_data || 'No data available for this channel.'; - emptyEl.style.display = ''; + noDataEl.textContent = T.no_channel_data || 'No data available for this channel.'; + noDataEl.style.display = ''; return; } chartsEl.style.display = ''; @@ -321,8 +397,8 @@ function loadChannelTimeline() { }) .catch(function() { loadingEl.style.display = 'none'; - emptyEl.textContent = T.trend_error || 'Error loading data.'; - emptyEl.style.display = ''; + noDataEl.textContent = T.trend_error || 'Error loading data.'; + noDataEl.style.display = ''; }); } window.loadChannelTimeline = loadChannelTimeline; diff --git a/app/templates/index.html b/app/templates/index.html index ff0c84cb..a8e8ff17 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -1338,10 +1338,7 @@

{{ t.bqm_import_title }}

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

{{ t.bqm_import_title }}

+ + +
- + +