feat: dashboard token auth + README v2/v3 payload sections split

Dashboard:
- Optional DASHBOARD_TOKEN env var (K8s secret 'snek-dashboard-secret')
- Flask before_request check via Authorization header or ?token= param
- /webhook and /healthz remain public (SNEK POST needs to reach webhook)
- Frontend prompts for token on first load, stores in localStorage
- Token forwarded on fetch() calls and EventSource URL

README decoding section:
- Split into two full sub-sections for Sens'it Discovery (v3) and Sens'it v2
- Explicit bit ordering note (MSB-LSB v3 vs LSB-MSB v2)
- Byte-by-byte tables, formulas per mode, worked examples
- Note that auto-detection uses byte 0 bits 2-0 == 0b110 marker for v3

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 18:27:33 +02:00
parent dd8e7602f5
commit 58ca57a627
4 changed files with 110 additions and 21 deletions
+32 -3
View File
@@ -73,6 +73,35 @@
</div>
<script>
// --- Token gate --------------------------------------------------------------
const TOKEN_KEY = 'snek_dashboard_token';
let token = localStorage.getItem(TOKEN_KEY) || '';
async function ensureToken() {
// Probe if the server requires a token
const probe = await fetch('/api/devices', { headers: authHeaders() });
if (probe.ok) return;
if (probe.status !== 401) return;
while (true) {
const t = window.prompt('Access token');
if (t === null) return;
const r = await fetch('/api/devices', { headers: { Authorization: 'Bearer ' + t } });
if (r.ok) {
token = t.trim();
localStorage.setItem(TOKEN_KEY, token);
return;
}
}
}
function authHeaders() {
return token ? { Authorization: 'Bearer ' + token } : {};
}
function authQuery() {
return token ? '?token=' + encodeURIComponent(token) : '';
}
const state = { messagesByDevice: {}, chart: null, selectedDevice: null };
const fmtTime = ts => new Date(ts * 1000).toLocaleString('fr-FR', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' });
@@ -286,7 +315,7 @@ function renderChart() {
}
async function bootstrap() {
const r = await fetch('/api/messages?limit=200');
const r = await fetch('/api/messages?limit=200', { headers: authHeaders() });
const msgs = await r.json();
msgs.forEach(m => {
if (!state.messagesByDevice[m.device]) state.messagesByDevice[m.device] = [];
@@ -298,7 +327,7 @@ async function bootstrap() {
}
function connectSSE() {
const es = new EventSource('/events');
const es = new EventSource('/events' + authQuery());
es.onmessage = (ev) => {
try {
const msg = JSON.parse(ev.data);
@@ -317,7 +346,7 @@ document.getElementById('chart-device').addEventListener('change', (e) => {
renderChart();
});
bootstrap().then(connectSSE);
ensureToken().then(bootstrap).then(connectSSE);
</script>
</body>
</html>