Skip to content

Commit fbeb9e0

Browse files
itsDNNSclaude
andcommitted
Fix card expand bug, add range indicators, clarify DS/US, DOCSIS 3.1 warning
- Fix cards stretching together (align-items: start on grid) - Move onclick to header only, not entire card - Replace gauge bars with colored range indicators showing good/warn/crit zones and a marker for the current value (DS Power, SNR, US Power) - Rename "Signal Quality" to "Downstream" with arrow icons for clarity - Add DOCSIS 3.1 upstream warning when no OFDMA channel found - Add card_no_docsis31 i18n key in EN/DE/FR/ES Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 23d6730 commit fbeb9e0

File tree

6 files changed

+94
-23
lines changed

6 files changed

+94
-23
lines changed

app/i18n/de.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,10 @@
162162
"bqm_url_hint": "ThinkBroadband BQM Share-Link einfuegen (.png)",
163163
"bqm_no_data": "Kein BQM-Graph fuer dieses Datum.",
164164

165-
"card_signal_quality": "Signalqualitaet",
165+
"card_signal_quality": "Downstream",
166166
"card_upstream": "Upstream",
167167
"card_errors": "Fehler",
168+
"card_no_docsis31": "Kein DOCSIS 3.1 Upstream-Kanal gefunden. Kann vom Vertrag oder Geraet abhaengen.",
168169
"card_ds_power": "Signalpegel",
169170
"card_snr": "Signalklarheit",
170171
"card_transmit_power": "Sendeleistung",

app/i18n/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,10 @@
162162
"bqm_url_hint": "Paste your ThinkBroadband BQM share link (.png)",
163163
"bqm_no_data": "No BQM graph for this date.",
164164

165-
"card_signal_quality": "Signal Quality",
165+
"card_signal_quality": "Downstream",
166166
"card_upstream": "Upstream",
167167
"card_errors": "Errors",
168+
"card_no_docsis31": "No DOCSIS 3.1 upstream channel found. May depend on your plan or equipment.",
168169
"card_ds_power": "Signal Level",
169170
"card_snr": "Signal Clarity",
170171
"card_transmit_power": "Transmit Power",

app/i18n/es.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,10 @@
162162
"bqm_url_hint": "Pega tu enlace de compartir ThinkBroadband BQM (.png)",
163163
"bqm_no_data": "No hay gráfico BQM para esta fecha.",
164164

165-
"card_signal_quality": "Calidad de señal",
165+
"card_signal_quality": "Descendente",
166166
"card_upstream": "Ascendente",
167167
"card_errors": "Errores",
168+
"card_no_docsis31": "No se encontró canal DOCSIS 3.1 ascendente. Puede depender de tu plan o equipo.",
168169
"card_ds_power": "Nivel de señal",
169170
"card_snr": "Claridad de señal",
170171
"card_transmit_power": "Potencia de transmisión",

app/i18n/fr.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,10 @@
162162
"bqm_url_hint": "Collez votre lien de partage ThinkBroadband BQM (.png)",
163163
"bqm_no_data": "Pas de graphique BQM pour cette date.",
164164

165-
"card_signal_quality": "Qualité du signal",
165+
"card_signal_quality": "Descendant",
166166
"card_upstream": "Montant",
167167
"card_errors": "Erreurs",
168+
"card_no_docsis31": "Aucun canal DOCSIS 3.1 montant trouvé. Peut dépendre de votre forfait ou équipement.",
168169
"card_ds_power": "Niveau du signal",
169170
"card_snr": "Clarté du signal",
170171
"card_transmit_power": "Puissance d'émission",

app/templates/index.html

Lines changed: 75 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@
208208
/* ── Metric Cards Grid ── */
209209
.metric-cards {
210210
display: grid; grid-template-columns: repeat(3, 1fr);
211-
gap: 12px; margin-bottom: 16px;
211+
gap: 12px; margin-bottom: 16px; align-items: start;
212212
}
213213

214214
/* ── Metric Card ── */
@@ -253,7 +253,31 @@
253253
.metric-unit { font-size: 0.7em; font-weight: normal; color: var(--muted); margin-left: 2px; }
254254
.metric-label { font-size: 0.78em; color: var(--muted); margin-top: 2px; }
255255

256-
/* ── Gauge Bar ── */
256+
/* ── Range Indicator ── */
257+
.range-indicator {
258+
position: relative; height: 6px; border-radius: 3px;
259+
margin-top: 8px; display: flex; overflow: visible;
260+
}
261+
.range-zone { height: 100%; }
262+
.range-zone:first-child { border-radius: 3px 0 0 3px; }
263+
.range-zone:last-of-type { border-radius: 0 3px 3px 0; }
264+
.range-good { background: var(--good); opacity: 0.25; }
265+
.range-warn { background: var(--warn); opacity: 0.25; }
266+
.range-crit { background: var(--crit); opacity: 0.25; }
267+
.range-marker {
268+
position: absolute; top: -4px; width: 3px; height: 14px;
269+
border-radius: 1.5px; transform: translateX(-50%);
270+
transition: left 0.4s ease;
271+
}
272+
.range-marker.val-good { background: var(--good); }
273+
.range-marker.val-warn { background: var(--warn); }
274+
.range-marker.val-crit { background: var(--crit); }
275+
.range-labels {
276+
display: flex; justify-content: space-between;
277+
font-size: 0.6em; color: var(--muted); margin-top: 3px;
278+
}
279+
280+
/* ── Gauge Bar (errors only) ── */
257281
.gauge-bar {
258282
height: 4px; background: rgba(255,255,255,0.08); border-radius: 2px;
259283
margin-top: 6px; overflow: hidden;
@@ -287,6 +311,13 @@
287311
font-size: 0.78em; color: var(--muted); font-style: italic;
288312
padding-bottom: 12px;
289313
}
314+
.card-notice {
315+
font-size: 0.78em; padding: 6px 10px; margin-top: 8px;
316+
border-radius: 4px; display: flex; align-items: center; gap: 6px;
317+
}
318+
.card-notice-warn {
319+
background: rgba(255,152,0,0.1); color: var(--warn);
320+
}
290321

291322
/* ── Tables ── */
292323
table { width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 0.9em; }
@@ -587,35 +618,49 @@
587618
else ('warn' if 'us_power_warn' in s.health_issues else 'good') %}
588619
{% set error_health = 'crit' if 'uncorr_errors_high' in s.health_issues else 'good' %}
589620

590-
{# ── Gauge percentages ── #}
591-
{% set ds_power_pct = [0, [100, (s.ds_power_avg|abs / 12 * 100)|int]|min]|max %}
592-
{% set snr_pct = [0, [100, ((s.ds_snr_avg - 20) / 25 * 100)|int]|min]|max %}
593-
{% set us_power_pct = [0, [100, ((s.us_power_avg - 30) / 30 * 100)|int]|min]|max %}
594-
595621
{# ── DS Power health ── #}
596622
{% set ds_pwr_health = 'crit' if 'ds_power_critical' in s.health_issues else 'good' %}
597623
{# ── SNR health ── #}
598624
{% set snr_health = 'crit' if 'snr_critical' in s.health_issues else ('warn' if 'snr_warn' in s.health_issues else 'good') %}
599625

626+
{# ── Range marker positions (clamped 2-98%) ── #}
627+
{% set ds_pwr_marker = [2, [98, ((s.ds_power_avg + 12) / 24 * 100)|round|int]|min]|max %}
628+
{% set snr_marker = [2, [98, ((s.ds_snr_avg - 15) / 30 * 100)|round|int]|min]|max %}
629+
{% set us_pwr_marker = [2, [98, ((s.us_power_avg - 25) / 35 * 100)|round|int]|min]|max %}
630+
600631
<div class="metric-cards">
601-
{# ── Card 1: Signal Quality ── #}
602-
<div class="metric-card health-{{ signal_health }}" onclick="toggleCard(this)">
603-
<div class="metric-card-header">
632+
{# ── Card 1: Downstream ── #}
633+
<div class="metric-card health-{{ signal_health }}">
634+
<div class="metric-card-header" onclick="toggleCard(this.parentNode)">
604635
<span class="status-dot"></span>
605-
<span class="card-title">{{ t.card_signal_quality }}</span>
636+
<span class="card-title">&#8595; {{ t.card_signal_quality }}</span>
606637
<span class="chevron">&#9654;</span>
607638
</div>
608639
<div class="metric-card-body">
609640
<div class="metric-row">
610641
<div class="metric-item">
611642
<div class="metric-value val-{{ ds_pwr_health }}">{{ s.ds_power_avg }}<span class="metric-unit">dBmV</span></div>
612643
<div class="metric-label">{{ t.card_ds_power }}</div>
613-
<div class="gauge-bar"><div class="gauge-fill val-{{ ds_pwr_health }}" style="width:{{ ds_power_pct }}%"></div></div>
644+
<div class="range-indicator">
645+
<div class="range-zone range-crit" style="width:8.3%"></div>
646+
<div class="range-zone range-warn" style="width:12.5%"></div>
647+
<div class="range-zone range-good" style="width:58.3%"></div>
648+
<div class="range-zone range-warn" style="width:12.5%"></div>
649+
<div class="range-zone range-crit" style="width:8.4%"></div>
650+
<div class="range-marker val-{{ ds_pwr_health }}" style="left:{{ ds_pwr_marker }}%"></div>
651+
</div>
652+
<div class="range-labels"><span>-12</span><span>0</span><span>+12</span></div>
614653
</div>
615654
<div class="metric-item">
616655
<div class="metric-value val-{{ snr_health }}">{{ s.ds_snr_avg }}<span class="metric-unit">dB</span></div>
617656
<div class="metric-label">{{ t.card_snr }}</div>
618-
<div class="gauge-bar"><div class="gauge-fill val-{{ snr_health }}" style="width:{{ snr_pct }}%"></div></div>
657+
<div class="range-indicator">
658+
<div class="range-zone range-crit" style="width:33.3%"></div>
659+
<div class="range-zone range-warn" style="width:16.7%"></div>
660+
<div class="range-zone range-good" style="width:50%"></div>
661+
<div class="range-marker val-{{ snr_health }}" style="left:{{ snr_marker }}%"></div>
662+
</div>
663+
<div class="range-labels"><span>15</span><span>25</span><span>30</span><span>45 dB</span></div>
619664
</div>
620665
</div>
621666
</div>
@@ -630,20 +675,31 @@
630675
</div>
631676

632677
{# ── Card 2: Upstream ── #}
633-
<div class="metric-card health-{{ us_health }}" onclick="toggleCard(this)">
634-
<div class="metric-card-header">
678+
<div class="metric-card health-{{ us_health }}">
679+
<div class="metric-card-header" onclick="toggleCard(this.parentNode)">
635680
<span class="status-dot"></span>
636-
<span class="card-title">{{ t.card_upstream }}</span>
681+
<span class="card-title">&#8593; {{ t.card_upstream }}</span>
637682
<span class="chevron">&#9654;</span>
638683
</div>
639684
<div class="metric-card-body">
640685
<div class="metric-row">
641686
<div class="metric-item">
642687
<div class="metric-value val-{{ us_health }}">{{ s.us_power_avg }}<span class="metric-unit">dBmV</span></div>
643688
<div class="metric-label">{{ t.card_transmit_power }}</div>
644-
<div class="gauge-bar"><div class="gauge-fill val-{{ us_health }}" style="width:{{ us_power_pct }}%"></div></div>
689+
<div class="range-indicator">
690+
<div class="range-zone range-good" style="width:68.6%"></div>
691+
<div class="range-zone range-warn" style="width:14.3%"></div>
692+
<div class="range-zone range-crit" style="width:17.1%"></div>
693+
<div class="range-marker val-{{ us_health }}" style="left:{{ us_pwr_marker }}%"></div>
694+
</div>
695+
<div class="range-labels"><span>25</span><span>35</span><span>49</span><span>54</span><span>60</span></div>
645696
</div>
646697
</div>
698+
{% if not has_us_ofdma %}
699+
<div class="card-notice card-notice-warn">
700+
<span>&#9888;</span> {{ t.card_no_docsis31 }}
701+
</div>
702+
{% endif %}
647703
</div>
648704
<div class="metric-card-detail">
649705
<table class="detail-table">
@@ -655,8 +711,8 @@
655711
</div>
656712

657713
{# ── Card 3: Errors ── #}
658-
<div class="metric-card health-{{ error_health }}" onclick="toggleCard(this)">
659-
<div class="metric-card-header">
714+
<div class="metric-card health-{{ error_health }}">
715+
<div class="metric-card-header" onclick="toggleCard(this.parentNode)">
660716
<span class="status-dot"></span>
661717
<span class="card-title">{{ t.card_errors }}</span>
662718
<span class="chevron">&#9654;</span>

app/web.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,15 @@ def _compute_uncorr_pct(analysis):
193193
uncorr = analysis.get("summary", {}).get("ds_uncorrectable_errors", 0)
194194
return min(100, math.log10(max(1, uncorr)) / 5 * 100)
195195

196+
def _has_us_ofdma(analysis):
197+
"""Check if any upstream channel uses DOCSIS 3.1+ (OFDMA)."""
198+
if not analysis:
199+
return True # don't warn when no data yet
200+
for ch in analysis.get("us_channels", []):
201+
if str(ch.get("docsis_version", "")) in ("3.1", "4.0"):
202+
return True
203+
return False
204+
196205
ts = request.args.get("t")
197206
if ts and not _TS_RE.match(ts):
198207
return redirect("/")
@@ -211,6 +220,7 @@ def _compute_uncorr_pct(analysis):
211220
isp_name=isp_name, connection_info=conn_info,
212221
bqm_configured=bqm_configured,
213222
uncorr_pct=_compute_uncorr_pct(snapshot),
223+
has_us_ofdma=_has_us_ofdma(snapshot),
214224
t=t, lang=lang, languages=LANGUAGES,
215225
)
216226
return render_template(
@@ -225,6 +235,7 @@ def _compute_uncorr_pct(analysis):
225235
isp_name=isp_name, connection_info=conn_info,
226236
bqm_configured=bqm_configured,
227237
uncorr_pct=_compute_uncorr_pct(_state["analysis"]),
238+
has_us_ofdma=_has_us_ofdma(_state["analysis"]),
228239
t=t, lang=lang, languages=LANGUAGES,
229240
)
230241

0 commit comments

Comments
 (0)