Files
internet_use_control/internal/web/templates/usage.html
Sam Rolfe b58993d994 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>
2026-10-13 16:18:56 +11:00

149 lines
6.0 KiB
HTML

{{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/&lt;device&gt;/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}}