L'ancien triple-tap via finger_trace dessinait des points noirs (outil de demo FBInk), ne respawnait pas (mort definitif si le process tombait) et le tactile ne reveille pas l'appareil. Le power, lui, n'emet que des scancodes MSC_SCAN parasites (etat de charge USB). Les boutons de page emettent des EV_KEY propres (codes 193/194). reboot_watcher.sh: lit l'evdev (FD persistant, pas de perte d'evenements), declenche sur 3 press EV_KEY < 3 s, boucle de respawn. Plus de finger_trace. Refresh: full force au (re)demarrage (reset=1 cote client -> oubli de prev_image cote serveur) pour eviter un refresh partiel pose sur un ecran efface par le reboot.
184 lines
7.7 KiB
HTML
184 lines
7.7 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; }
|
|
|
|
/* 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 : météo, NAS -->
|
|
<div class="col col-left">
|
|
<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">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 appuis bouton page = redémarrer</span>
|
|
{% if kobo.ok %}<span class="{% if kobo.low %}stale{% endif %}">{% if kobo.charging %}⚡{% else %}🔋{% endif %} Kobo {{ kobo.percent }}%{% if kobo.stale %} · ?{% endif %}</span>{% endif %}
|
|
<span class="{% if stale %}stale{% endif %}">maj {{ updated }}{% if stale %} · DONNÉE PÉRIMÉE{% endif %}</span>
|
|
</footer>
|
|
|
|
</body>
|
|
</html>
|