197 lines
8.1 KiB
HTML
197 lines
8.1 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<style>
|
|
/* Dashboard e-ink PAYSAGE 1680x1264 — noir & blanc, fort contraste, sans couleur.
|
|
Le PNG est pivoté de 90° côté backend pour le panneau portrait de la Kobo. */
|
|
* { 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;
|
|
}
|
|
/* Deux colonnes + pied de page pleine largeur. */
|
|
body {
|
|
padding: 44px 52px;
|
|
display: grid;
|
|
grid-template-columns: 600px 1fr;
|
|
grid-template-rows: 1fr auto;
|
|
grid-template-areas: "left right" "footer footer";
|
|
column-gap: 56px;
|
|
row-gap: 28px;
|
|
}
|
|
.col {
|
|
display: flex; flex-direction: column; min-width: 0;
|
|
}
|
|
.col-left { grid-area: left; }
|
|
.col-right { grid-area: right; padding-left: 56px; border-left: 5px solid var(--line); }
|
|
.section { margin-bottom: 40px; }
|
|
.section:last-child { margin-bottom: 0; }
|
|
.rule { border: 0; border-top: 4px solid var(--line); margin: 0 0 32px; }
|
|
|
|
/* En-tête : heure au-dessus, date dessous (alignées à gauche). */
|
|
header { display: flex; flex-direction: column; gap: 10px; }
|
|
.clock { font-size: 210px; font-weight: 800; line-height: 0.82; letter-spacing: -6px; }
|
|
.date { font-size: 46px; font-weight: 600; }
|
|
.date .dow { font-size: 68px; font-weight: 800; text-transform: capitalize; }
|
|
|
|
/* Météo */
|
|
.weather .top { display: flex; align-items: center; gap: 32px; }
|
|
.weather .icon { font-size: 140px; line-height: 1; }
|
|
.weather .temp { font-size: 130px; font-weight: 800; line-height: 1; }
|
|
.weather .meta { font-size: 40px; color: var(--muted); margin-top: 18px; }
|
|
.weather .meta b { color: var(--ink); }
|
|
|
|
/* Titre de section */
|
|
.title { font-size: 36px; font-weight: 800; text-transform: uppercase;
|
|
letter-spacing: 3px; margin-bottom: 24px; }
|
|
|
|
/* Jauges Claude */
|
|
.gauge { margin-bottom: 36px; }
|
|
.gauge .row { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 12px; }
|
|
.gauge .name { font-size: 42px; font-weight: 700; }
|
|
.gauge .pct { font-size: 66px; font-weight: 800; }
|
|
.gauge .pct small { font-size: 32px; font-weight: 600; }
|
|
.bar { position: relative; height: 50px; 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); }
|
|
|
|
/* Liste NAS (valeurs larges -> une colonne pleine largeur, sans soulignements) */
|
|
.nas-list { display: flex; flex-direction: column; gap: 18px; }
|
|
.nas-list .ha-item { border-bottom: 0; padding-bottom: 0; }
|
|
.bad { font-weight: 800; }
|
|
|
|
/* Grille Home Assistant */
|
|
.ha-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 18px 44px; }
|
|
.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 { grid-area: footer; 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>
|
|
|
|
<!-- Colonne gauche : heure, date, météo -->
|
|
<div class="col col-left">
|
|
<header class="section">
|
|
<div class="clock">{{ time }}</div>
|
|
<div class="date">
|
|
<span class="dow">{{ dow }}</span> · {{ date }}
|
|
</div>
|
|
</header>
|
|
<hr class="rule">
|
|
|
|
<div class="section weather">
|
|
{% if weather.ok %}
|
|
<div class="top">
|
|
<div class="icon">{{ weather.icon }}</div>
|
|
<div class="temp">{{ weather.temp | round | int }}°</div>
|
|
</div>
|
|
<div class="meta">
|
|
{{ weather.label }} · ressenti <b>{{ weather.feels_like | round | int }}°</b>
|
|
</div>
|
|
<div class="meta">
|
|
min <b>{{ weather.temp_min | round | int }}°</b> / max <b>{{ weather.temp_max | round | int }}°</b>{% if weather.precip_prob is not none %} · pluie <b>{{ weather.precip_prob }}%</b>{% endif %}
|
|
</div>
|
|
{% else %}
|
|
<div class="meta">Météo indisponible</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if nas.ok %}
|
|
<hr class="rule">
|
|
<div class="section">
|
|
<div class="title">NAS</div>
|
|
<div class="nas-list">
|
|
{% for d in nas.disks %}
|
|
<div class="ha-item"><span class="k">{{ d.label }}</span><span class="v">{{ d.percent | round | int }}% · {{ d.free_human }} libre</span></div>
|
|
{% endfor %}
|
|
<div class="ha-item"><span class="k">Docker</span><span class="v">{{ nas.docker_running }}/{{ nas.docker_total }}{% if nas.docker_unhealthy %} · <span class="bad">{{ nas.docker_unhealthy }} KO</span>{% else %} ✓{% endif %}</span></div>
|
|
<div class="ha-item"><span class="k">Port VPN</span><span class="v">{% if nas.vpn_ok %}OK{% if nas.vpn_port %} · {{ nas.vpn_port }}{% endif %}{% else %}<span class="bad">✗ désync</span>{% endif %}</span></div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Colonne droite : abonnement Claude + maison -->
|
|
<div class="col col-right">
|
|
<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: {{ (100 - g.remaining) | round(1) }}%;"></div>
|
|
</div>
|
|
<div class="sub">{{ (100 - g.remaining) | round | int }}% utilisé · reset dans {{ g.resets_in }}{% if g.extra %} · {{ g.extra }}{% endif %}</div>
|
|
</div>
|
|
{% endfor %}
|
|
{% if claude.extra %}
|
|
<div class="sub" style="font-size:32px;">{{ claude.extra.label }}</div>
|
|
{% endif %}
|
|
{% 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 codex.ok %}
|
|
<div class="section">
|
|
<div class="title">Codex{% if codex.plan_type %} · {{ codex.plan_type | capitalize }}{% endif %}{% if codex.limited %} · <span class="bad">⚠ limite atteinte</span>{% endif %}</div>
|
|
{% for g in codex.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: {{ (100 - g.remaining) | round(1) }}%;"></div>
|
|
</div>
|
|
<div class="sub">{{ (100 - g.remaining) | round | int }}% utilisé · reset dans {{ g.resets_in }}</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if ha_states %}
|
|
<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 %}
|
|
</div>
|
|
|
|
<footer>
|
|
<span>Monitorink · 3 taps = redémarrer</span>
|
|
<span class="{% if stale %}stale{% endif %}">maj {{ updated }}{% if stale %} · DONNÉE PÉRIMÉE{% endif %}</span>
|
|
</footer>
|
|
|
|
</body>
|
|
</html>
|