Skip to content

Commit 22cc9a9

Browse files
itsDNNSclaude
andcommitted
Right-align channel badges, equalize card heights, add modem uptime to errors card
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6371341 commit 22cc9a9

File tree

8 files changed

+34
-7
lines changed

8 files changed

+34
-7
lines changed

app/fritzbox.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,17 @@ def get_device_info(url: str, sid: str) -> dict:
8484
r.raise_for_status()
8585
data = r.json().get("data", {})
8686
fritzos = data.get("fritzos", {})
87-
return {
87+
result = {
8888
"model": fritzos.get("Productname", "FRITZ!Box"),
8989
"sw_version": fritzos.get("nspver", ""),
9090
}
91+
uptime = fritzos.get("Uptime")
92+
if uptime is not None:
93+
try:
94+
result["uptime_seconds"] = int(uptime)
95+
except (ValueError, TypeError):
96+
pass
97+
return result
9198
except Exception:
9299
return {"model": "FRITZ!Box", "sw_version": ""}
93100

app/i18n/de.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,5 +175,6 @@
175175
"card_ideal_range": "Idealbereich",
176176
"card_uncorr_exact": "Unkorrigierbar (exakt)",
177177
"card_corr_exact": "Korrigierbar (exakt)",
178-
"card_errors_note": "Unkorrigierbare Fehler koennen nicht wiederhergestellt werden. Hohe und steigende Werte deuten auf ein Kabelproblem hin."
178+
"card_errors_note": "Unkorrigierbare Fehler koennen nicht wiederhergestellt werden. Hohe und steigende Werte deuten auf ein Kabelproblem hin.",
179+
"card_uptime": "Modem-Laufzeit"
179180
}

app/i18n/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,5 +175,6 @@
175175
"card_ideal_range": "Ideal Range",
176176
"card_uncorr_exact": "Uncorrectable (exact)",
177177
"card_corr_exact": "Correctable (exact)",
178-
"card_errors_note": "Uncorrectable errors cannot be recovered. High growing numbers indicate a cable issue."
178+
"card_errors_note": "Uncorrectable errors cannot be recovered. High growing numbers indicate a cable issue.",
179+
"card_uptime": "Modem Uptime"
179180
}

app/i18n/es.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,5 +175,6 @@
175175
"card_ideal_range": "Rango ideal",
176176
"card_uncorr_exact": "No corregibles (exacto)",
177177
"card_corr_exact": "Corregibles (exacto)",
178-
"card_errors_note": "Los errores no corregibles no se pueden recuperar. Números altos y crecientes indican un problema de cable."
178+
"card_errors_note": "Los errores no corregibles no se pueden recuperar. Números altos y crecientes indican un problema de cable.",
179+
"card_uptime": "Tiempo activo del módem"
179180
}

app/i18n/fr.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,5 +175,6 @@
175175
"card_ideal_range": "Plage idéale",
176176
"card_uncorr_exact": "Non corrigeables (exact)",
177177
"card_corr_exact": "Corrigeables (exact)",
178-
"card_errors_note": "Les erreurs non corrigeables ne peuvent pas être récupérées. Des chiffres élevés et croissants indiquent un problème de câble."
178+
"card_errors_note": "Les erreurs non corrigeables ne peuvent pas être récupérées. Des chiffres élevés et croissants indiquent un problème de câble.",
179+
"card_uptime": "Disponibilité modem"
179180
}

app/main.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ def polling_loop(config_mgr, storage, stop_event):
6868
if device_info is None:
6969
device_info = fritzbox.get_device_info(config["modem_url"], sid)
7070
log.info("FritzBox model: %s (%s)", device_info["model"], device_info["sw_version"])
71+
web.update_state(device_info=device_info)
7172

7273
if connection_info is None:
7374
connection_info = fritzbox.get_connection_info(config["modem_url"], sid)

app/templates/index.html

Lines changed: 10 additions & 1 deletion
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; align-items: start;
211+
gap: 12px; margin-bottom: 16px;
212212
}
213213

214214
/* ── Metric Card ── */
@@ -364,6 +364,7 @@
364364
color: var(--accent); list-style: none;
365365
display: flex; align-items: center; gap: 8px; user-select: none;
366366
}
367+
details.channel-group summary .badge { margin-left: auto; }
367368
details.channel-group summary::-webkit-details-marker { display: none; }
368369
details.channel-group summary::before {
369370
content: "\25B6"; font-size: 0.65em; transition: transform 0.2s;
@@ -720,6 +721,14 @@
720721
<div class="metric-secondary">{{ s.ds_correctable_errors|fmt_k }} {{ t.card_corr_label }}</div>
721722
</div>
722723
</div>
724+
{% if device_info and device_info.uptime_seconds is defined %}
725+
{% set ut = device_info.uptime_seconds %}
726+
{% set ut_d = ut // 86400 %}
727+
{% set ut_h = (ut % 86400) // 3600 %}
728+
<div class="metric-secondary" style="margin-top:10px; padding-top:10px; border-top:1px solid var(--input-border);">
729+
&#9201; {{ t.card_uptime }}: <strong>{% if ut_d %}{{ ut_d }}d {% endif %}{{ ut_h }}h</strong>
730+
</div>
731+
{% endif %}
723732
</div>
724733
<div class="metric-card-detail">
725734
<table class="detail-table">

app/web.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ def _get_lang():
6464
"poll_interval": 900,
6565
"error": None,
6666
"connection_info": None,
67+
"device_info": None,
6768
}
6869

6970
_storage = None
@@ -158,7 +159,7 @@ def inject_auth():
158159
return {"auth_enabled": auth_enabled}
159160

160161

161-
def update_state(analysis=None, error=None, poll_interval=None, connection_info=None):
162+
def update_state(analysis=None, error=None, poll_interval=None, connection_info=None, device_info=None):
162163
"""Update the shared web state from the main loop."""
163164
if analysis is not None:
164165
_state["analysis"] = analysis
@@ -170,6 +171,8 @@ def update_state(analysis=None, error=None, poll_interval=None, connection_info=
170171
_state["poll_interval"] = poll_interval
171172
if connection_info is not None:
172173
_state["connection_info"] = connection_info
174+
if device_info is not None:
175+
_state["device_info"] = device_info
173176

174177

175178
@app.route("/")
@@ -185,6 +188,7 @@ def index():
185188
isp_name = _config_manager.get("isp_name", "") if _config_manager else ""
186189
bqm_configured = _config_manager.is_bqm_configured() if _config_manager else False
187190
conn_info = _state.get("connection_info") or {}
191+
dev_info = _state.get("device_info") or {}
188192

189193
def _compute_uncorr_pct(analysis):
190194
"""Compute log-scale percentage for uncorrectable errors gauge."""
@@ -221,6 +225,7 @@ def _has_us_ofdma(analysis):
221225
bqm_configured=bqm_configured,
222226
uncorr_pct=_compute_uncorr_pct(snapshot),
223227
has_us_ofdma=_has_us_ofdma(snapshot),
228+
device_info=dev_info,
224229
t=t, lang=lang, languages=LANGUAGES,
225230
)
226231
return render_template(
@@ -236,6 +241,7 @@ def _has_us_ofdma(analysis):
236241
bqm_configured=bqm_configured,
237242
uncorr_pct=_compute_uncorr_pct(_state["analysis"]),
238243
has_us_ofdma=_has_us_ofdma(_state["analysis"]),
244+
device_info=dev_info,
239245
t=t, lang=lang, languages=LANGUAGES,
240246
)
241247

0 commit comments

Comments
 (0)