Backend Monitorink: serveur PNG (Claude usage + météo + HA)
This commit is contained in:
138
backend/templates/dashboard.html
Normal file
138
backend/templates/dashboard.html
Normal file
@@ -0,0 +1,138 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
/* Dashboard e-ink 1264x1680 — noir & blanc, fort contraste, pas de dépendance couleur. */
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
:root {
|
||||
--ink: #000;
|
||||
--paper: #fff;
|
||||
--muted: #555;
|
||||
--line: #000;
|
||||
--gauge-bg: #d9d9d9;
|
||||
}
|
||||
html, body {
|
||||
width: {{ width }}px; height: {{ height }}px;
|
||||
background: var(--paper); color: var(--ink);
|
||||
font-family: "DejaVu Sans", "Noto Sans", Arial, sans-serif;
|
||||
-webkit-font-smoothing: none;
|
||||
}
|
||||
body { padding: 48px 56px; display: flex; flex-direction: column; }
|
||||
.section { margin-bottom: 40px; }
|
||||
.rule { border: 0; border-top: 4px solid var(--line); margin: 0 0 28px; }
|
||||
|
||||
/* En-tête : heure + date */
|
||||
header { display: flex; justify-content: space-between; align-items: flex-end; }
|
||||
.clock { font-size: 150px; font-weight: 800; line-height: 0.9; letter-spacing: -4px; }
|
||||
.date { font-size: 40px; font-weight: 600; text-align: right; text-transform: capitalize; }
|
||||
.date .dow { font-size: 52px; font-weight: 800; }
|
||||
|
||||
/* Météo */
|
||||
.weather { display: flex; align-items: center; gap: 36px; }
|
||||
.weather .icon { font-size: 110px; line-height: 1; }
|
||||
.weather .temp { font-size: 96px; font-weight: 800; }
|
||||
.weather .meta { font-size: 36px; color: var(--muted); }
|
||||
.weather .meta b { color: var(--ink); }
|
||||
|
||||
/* Titre de section */
|
||||
.title { font-size: 34px; font-weight: 800; text-transform: uppercase;
|
||||
letter-spacing: 3px; margin-bottom: 22px; }
|
||||
|
||||
/* Jauges Claude */
|
||||
.gauge { margin-bottom: 34px; }
|
||||
.gauge .row { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 12px; }
|
||||
.gauge .name { font-size: 40px; font-weight: 700; }
|
||||
.gauge .pct { font-size: 64px; font-weight: 800; }
|
||||
.gauge .pct small { font-size: 32px; font-weight: 600; }
|
||||
.bar { position: relative; height: 46px; background: var(--gauge-bg);
|
||||
border: 4px solid var(--ink); border-radius: 6px; overflow: hidden; }
|
||||
.bar .fill { position: absolute; top: 0; left: 0; bottom: 0; background: var(--ink); }
|
||||
.bar.low .fill { background: repeating-linear-gradient(45deg, #000 0 14px, #fff 14px 22px); }
|
||||
.gauge .sub { font-size: 30px; color: var(--muted); margin-top: 10px; }
|
||||
.err { font-size: 40px; font-weight: 700; padding: 24px; border: 4px dashed var(--ink); }
|
||||
|
||||
/* Grille Home Assistant */
|
||||
.ha-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px 40px; }
|
||||
.ha-item { display: flex; justify-content: space-between; align-items: baseline;
|
||||
border-bottom: 3px solid var(--ink); padding-bottom: 12px; }
|
||||
.ha-item .k { font-size: 38px; font-weight: 600; }
|
||||
.ha-item .v { font-size: 44px; font-weight: 800; }
|
||||
|
||||
footer { margin-top: auto; display: flex; justify-content: space-between;
|
||||
font-size: 28px; color: var(--muted); padding-top: 20px; border-top: 3px solid var(--ink); }
|
||||
.stale { font-weight: 800; color: var(--ink); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="section">
|
||||
<div class="clock">{{ time }}</div>
|
||||
<div class="date">
|
||||
<div class="dow">{{ dow }}</div>
|
||||
<div>{{ date }}</div>
|
||||
</div>
|
||||
</header>
|
||||
<hr class="rule">
|
||||
|
||||
<div class="section weather">
|
||||
{% if weather.ok %}
|
||||
<div class="icon">{{ weather.icon }}</div>
|
||||
<div>
|
||||
<div class="temp">{{ weather.temp | round | int }}°</div>
|
||||
<div class="meta">
|
||||
{{ weather.label }} · ressenti <b>{{ weather.feels_like | round | int }}°</b>
|
||||
</div>
|
||||
</div>
|
||||
<div class="meta" style="margin-left:auto; text-align:right;">
|
||||
<div>min <b>{{ weather.temp_min | round | int }}°</b> / max <b>{{ weather.temp_max | round | int }}°</b></div>
|
||||
{% if weather.precip_prob is not none %}<div>pluie <b>{{ weather.precip_prob }}%</b></div>{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="meta">Météo indisponible</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<hr class="rule">
|
||||
|
||||
<div class="section">
|
||||
<div class="title">Abonnement Claude{% if claude.ok %} · Max 5x{% endif %}</div>
|
||||
{% if not claude.ok %}
|
||||
<div class="err">⚠ {{ claude.error }}</div>
|
||||
{% else %}
|
||||
{% for g in gauges %}
|
||||
<div class="gauge">
|
||||
<div class="row">
|
||||
<span class="name">{{ g.name }}</span>
|
||||
<span class="pct">{{ g.remaining | round | int }}<small>% restant</small></span>
|
||||
</div>
|
||||
<div class="bar {% if g.remaining < 20 %}low{% endif %}">
|
||||
<div class="fill" style="width: {{ g.remaining | round(1) }}%;"></div>
|
||||
</div>
|
||||
<div class="sub">reset dans {{ g.resets_in }}{% if g.extra %} · {{ g.extra }}{% endif %}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if claude.burn_rate %}
|
||||
<div class="sub" style="font-size:32px;">Burn rate : {{ claude.burn_rate | round | int }} tok/min</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if ha_states %}
|
||||
<hr class="rule">
|
||||
<div class="section">
|
||||
<div class="title">Maison</div>
|
||||
<div class="ha-grid">
|
||||
{% for s in ha_states %}
|
||||
<div class="ha-item"><span class="k">{{ s.label }}</span><span class="v">{{ s.display }}</span></div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<footer>
|
||||
<span>Monitorink</span>
|
||||
<span class="{% if stale %}stale{% endif %}">maj {{ updated }}{% if stale %} · DONNÉE PÉRIMÉE{% endif %}</span>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user