Phase 7: telemetry and usage (daily rollup + week dashboard)
Build on Phases 1–6. Telemetry already arrived on
restrict/<device>/telemetry; this phase stores it in a daily rollup and
shows it.
- store: migration 003 adds telemetry_daily (device, day, app seconds,
site seconds). InsertTelemetry maintains the affected days in the same
transaction; RebuildRollups backfills and repairs it. UsageRange and
TopUsage serve the week grid and top lists. CapAudit enforces the
size-capped append-only audit log from AGENTS.md §7.
- web: GET /devices/{id}/usage — Monday-to-Sunday totals plus the top 10
apps and top 10 sites, using CSP-safe native <progress> bars.
- theme.js: light/dark from the OS preference, with a nav toggle, loaded
as an external script so the strict CSP still holds.
- cmd/control: `rollup` subcommand; rollup rebuild and audit cap at
start-up and every 15 minutes.
- Version 0.5.0-phase7 (compose, Makefile, docs).
Tests: go test ./... and go vet ./... pass in golang:1.25-alpine.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
148
internal/web/templates/usage.html
Normal file
148
internal/web/templates/usage.html
Normal file
@@ -0,0 +1,148 @@
|
||||
{{define "usage.html"}}<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<title>{{.Title}} — Internet Use Control</title>
|
||||
<link rel="stylesheet" href="/static/tokens.css">
|
||||
<link rel="stylesheet" href="/static/app.css">
|
||||
<script src="/static/theme.js" defer></script>
|
||||
</head>
|
||||
<body class="app">
|
||||
<nav class="nav-bar">
|
||||
<a class="nav-bar__brand" href="/">
|
||||
<span class="nav-bar__mark" aria-hidden="true"></span>
|
||||
Internet Use Control
|
||||
</a>
|
||||
<div class="nav-bar__right">
|
||||
<span class="nav-bar__user">{{.Username}}</span>
|
||||
<button class="button-utility" type="button" data-theme-toggle>Dark</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="container">
|
||||
<header class="page-head">
|
||||
<p class="page-head__eyebrow"><a href="/">← Dashboard</a></p>
|
||||
<h1 class="heading-1">Usage</h1>
|
||||
<p class="page-head__sub">{{.Sub}}</p>
|
||||
</header>
|
||||
|
||||
{{if .Flash}}<p class="flash flash--ok" role="status">{{.Flash}}</p>{{end}}
|
||||
{{if .Error}}<p class="flash flash--error" role="alert">{{.Error}}</p>{{end}}
|
||||
|
||||
<section class="section" aria-labelledby="week-title">
|
||||
<div class="section__head">
|
||||
<h2 id="week-title" class="heading-2">Week of {{.WeekLabel}}</h2>
|
||||
<div class="section__actions">
|
||||
<a class="button-utility" href="/devices/{{.DeviceID}}/usage?week={{.PrevWeek}}">← Previous</a>
|
||||
{{if not .IsThisWeek}}<a class="button-utility" href="/devices/{{.DeviceID}}/usage">This week</a>{{end}}
|
||||
<a class="button-utility" href="/devices/{{.DeviceID}}/usage?week={{.NextWeek}}">Next →</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-grid">
|
||||
<article class="tool-card">
|
||||
<div class="tool-card__icon tool-card__icon--sky" aria-hidden="true">▤</div>
|
||||
<h3 class="tool-card__title">App time</h3>
|
||||
<p class="tool-card__desc">{{.WeekAppsTxt}} this week</p>
|
||||
<span class="status-pill status-pill--muted">screen time</span>
|
||||
</article>
|
||||
<article class="tool-card">
|
||||
<div class="tool-card__icon tool-card__icon--purple" aria-hidden="true">⇄</div>
|
||||
<h3 class="tool-card__title">Site time</h3>
|
||||
<p class="tool-card__desc">{{.WeekSiteTxt}} this week</p>
|
||||
<span class="status-pill status-pill--muted">by hostname</span>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
{{if .HasUsage}}
|
||||
<div class="schedule-scroll">
|
||||
<table class="usage-table">
|
||||
<caption class="visually-hidden">Daily usage for the selected week</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Day</th>
|
||||
<th scope="col">Apps</th>
|
||||
<th scope="col">Sites</th>
|
||||
<th scope="col">Screen time</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Days}}
|
||||
<tr{{if .IsToday}} class="usage-table__today"{{end}}>
|
||||
<th scope="row">{{.Weekday}} <span class="usage-table__date">{{.Date}}{{if .IsToday}} · today{{end}}</span></th>
|
||||
<td class="usage-table__num">{{.AppLabel}}</td>
|
||||
<td class="usage-table__num">{{.DomainLabel}}</td>
|
||||
<td class="usage-table__bar">
|
||||
<progress class="usage-bar" value="{{.AppSeconds}}" max="{{$.MaxApp}}"
|
||||
aria-label="App time {{.AppLabel}} on {{.Date}}"></progress>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="empty-state">
|
||||
<p class="empty-state__title">No usage for this week</p>
|
||||
<p class="empty-state__body">No device has reported telemetry for this week. Reports arrive on
|
||||
<code>restrict/<device>/telemetry</code> and are rolled up per day.</p>
|
||||
</div>
|
||||
{{end}}
|
||||
</section>
|
||||
|
||||
{{if .HasUsage}}
|
||||
<div class="list-grid">
|
||||
<section class="card-panel list-panel" aria-labelledby="top-apps-title">
|
||||
<h2 id="top-apps-title" class="heading-2">Top apps</h2>
|
||||
<p class="list-panel__sub">Total time this week, biggest first.</p>
|
||||
<ul class="usage-list">
|
||||
{{range .TopApps}}
|
||||
<li class="usage-item">
|
||||
<span class="usage-item__name" title="{{.Name}}">{{.Name}}</span>
|
||||
<span class="usage-item__time">{{.Label}}</span>
|
||||
<progress class="usage-bar usage-bar--full" value="{{.Percent}}" max="100"
|
||||
aria-label="{{.Name}}: {{.Label}}"></progress>
|
||||
</li>
|
||||
{{else}}
|
||||
<li class="usage-item usage-item--empty">No apps reported.</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="card-panel list-panel" aria-labelledby="top-domains-title">
|
||||
<h2 id="top-domains-title" class="heading-2">Top sites</h2>
|
||||
<p class="list-panel__sub">Total time this week, biggest first.</p>
|
||||
<ul class="usage-list">
|
||||
{{range .TopDomains}}
|
||||
<li class="usage-item">
|
||||
<span class="usage-item__name" title="{{.Name}}">{{.Name}}</span>
|
||||
<span class="usage-item__time">{{.Label}}</span>
|
||||
<progress class="usage-bar usage-bar--full" value="{{.Percent}}" max="100"
|
||||
aria-label="{{.Name}}: {{.Label}}"></progress>
|
||||
</li>
|
||||
{{else}}
|
||||
<li class="usage-item usage-item--empty">No sites reported.</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<p class="schedule-note__hint">{{.Retention}}</p>
|
||||
|
||||
<div class="form-actions">
|
||||
<a class="button-utility" href="/devices/{{.DeviceID}}/schedule">Weekly schedule</a>
|
||||
<a class="button-utility" href="/devices/{{.DeviceID}}/lists">Blocklist and allowlist</a>
|
||||
<a class="button-utility" href="/">Done</a>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="app-foot">
|
||||
<span>Control Service · {{.Version}} · {{.AppEnv}}</span>
|
||||
<span class="app-foot__sep">·</span>
|
||||
<span>{{year}} Family Home Lab</span>
|
||||
</footer>
|
||||
</body>
|
||||
</html>{{end}}
|
||||
Reference in New Issue
Block a user