SmartestHome/identity/frontend/admin.html

287 lines
12 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Household admin</title>
<link rel="stylesheet" href="style.css">
</head>
<body class="admin">
<!--
identity's admin panel — managing people, guests, rights and history, as opposed to
register.html (the door-panel kiosk's own self-service flow) and dashboard.html (the
ambient at-a-glance screen).
THIS IS THE ONE PAGE HERE NOT DESIGNED FOR A KIOSK. The other two are big-touch-
target, glanceable, and running unattended on a wall panel; this one is dense, has
destructive buttons on it, and is meant for a phone or laptop where you sat down
intending to administer something. That's why it opts into `body.admin`'s tighter
scale rather than inheriting the 64px-button kiosk defaults — a wall panel anyone
can walk up to should not have a "Prune 6 people" button on it.
Config comes from the URL exactly like the sibling pages: ?api=&token=.
-->
<div id="tabs">
<button type="button" class="tab active" data-panel="people">👥<span>People</span></button>
<button type="button" class="tab" data-panel="prune">🧹<span>Prune</span></button>
<button type="button" class="tab" data-panel="history">🕘<span>History</span></button>
<button type="button" class="tab" data-panel="floorplan">🗺️<span>Floorplan</span></button>
<button type="button" class="tab" data-panel="access">🔑<span>Access log</span></button>
</div>
<main>
<!-- ---------------------------------------------------------------- People -->
<section class="panel active" id="panel-people">
<section class="block">
<h2>People</h2>
<p class="hint">Tap a person to edit every field, their devices, door rights and chores.</p>
<div id="people-list" class="card-list"><p class="hint">Loading…</p></div>
</section>
</section>
<!-- ----------------------------------------------------------------- Prune -->
<section class="panel" id="panel-prune">
<section class="block">
<h2>Prune old records</h2>
<p class="hint">
Select everyone whose last visit was before a date — for clearing out
one-off guests. Nothing is deleted until you confirm the exact list below.
</p>
<div class="row">
<label class="field">
Last visited before
<input id="prune-date" type="date">
</label>
<button type="button" id="prune-select" class="btn">Select all</button>
</div>
<p id="prune-summary" class="hint"></p>
<div id="prune-list" class="card-list"></div>
<div class="row" style="margin-top:12px">
<button type="button" id="prune-delete" class="btn danger" disabled>Delete selected</button>
<span id="prune-status" class="hint"></span>
</div>
</section>
</section>
<!-- --------------------------------------------------------------- History -->
<section class="panel" id="panel-history">
<section class="block">
<h2>Who was home, when</h2>
<div class="row">
<label class="field">
Person
<select id="history-person"><option value="">Everyone</option></select>
</label>
<label class="field">
Since
<input id="history-since" type="date">
</label>
<button type="button" id="history-load" class="btn">Load</button>
</div>
<div id="visit-list" class="card-list"><p class="hint">Pick a range and load.</p></div>
</section>
<section class="block">
<h2>…and with whom</h2>
<p class="hint">Overlapping visits, worked out from the history above.</p>
<div id="copresence-list" class="card-list"></div>
</section>
</section>
<!-- ------------------------------------------------------------- Floorplan -->
<!--
The half that was missing from Phase 6 (project-plan open decision #22): /presence
has reported a best-effort `room` all along, but there was nothing to plot it on.
Rooms are DRAWN HERE BY HAND — nothing in this project knows the shape of this
flat, and the alternative to drawing it was inventing a coordinate format against
a guess, which is why this stayed deferred rather than half-built.
-->
<section class="panel" id="panel-floorplan">
<section class="block">
<div class="row">
<label class="field">
Level
<select id="fp-level"></select>
</label>
<button type="button" id="fp-add-level" class="btn">Add level</button>
<button type="button" id="fp-rename-level" class="btn">Rename</button>
<button type="button" id="fp-delete-level" class="btn danger">Delete level</button>
<label class="btn" style="cursor:pointer">
Background image
<input id="fp-image" type="file" accept="image/*" hidden>
</label>
<span class="grow"></span>
<label class="check">
<input id="fp-live" type="checkbox" checked>
Show who's home
</label>
</div>
<p class="hint" id="fp-hint">
Click on the plan to drop points, then <b>Finish</b> (or double-click) to close
the room. Drag a point to adjust it. Click a finished room to edit it.
</p>
</section>
<section class="block fp-layout">
<div class="fp-canvas-wrap">
<svg id="fp-canvas" viewBox="0 0 1000 700" preserveAspectRatio="xMidYMid meet">
<image id="fp-bg" x="0" y="0" width="1000" height="700" preserveAspectRatio="none"></image>
<g id="fp-rooms"></g>
<g id="fp-draft"></g>
<g id="fp-handles"></g>
</svg>
<div class="row" style="margin-top:8px">
<button type="button" id="fp-draw" class="btn primary">Draw a room</button>
<button type="button" id="fp-finish" class="btn" disabled>Finish</button>
<button type="button" id="fp-cancel" class="btn" disabled>Cancel</button>
<span id="fp-status" class="hint"></span>
</div>
</div>
<aside class="fp-side">
<h3 id="fp-editor-title">Rooms</h3>
<div id="fp-room-list" class="card-list"></div>
<fieldset class="field-group" id="fp-room-editor" hidden>
<legend>Selected room</legend>
<label class="field">Name
<input id="fp-room-name" type="text" autocomplete="off">
</label>
<label class="field">
Home Assistant area
<input id="fp-room-area" type="text" list="fp-areas" placeholder="e.g. kitchen" autocomplete="off">
</label>
<datalist id="fp-areas"></datalist>
<p class="hint" id="fp-area-hint"></p>
<label class="field">Colour
<input id="fp-room-color" type="color" value="#6ea8fe">
</label>
<div class="row">
<button type="button" id="fp-room-save" class="btn primary">Save room</button>
<button type="button" id="fp-room-delete" class="btn danger">Delete</button>
</div>
</fieldset>
<div id="fp-unplaced" class="hint" style="margin-top:14px"></div>
</aside>
</section>
</section>
<!-- ------------------------------------------------------------ Access log -->
<section class="panel" id="panel-access">
<section class="block">
<h2>Device access checks</h2>
<p class="hint">
Every time something asked whether a person may operate a device — allowed and
denied alike. identity only ever answers these; Home Assistant is what actually
moves a lock.
</p>
<div id="access-list" class="card-list"><p class="hint">Loading…</p></div>
</section>
</section>
</main>
<!-- The person editor. One dialog reused for whoever's selected, rather than an
always-rendered form per person — the list can get long and only one is ever
being edited. -->
<dialog id="editor">
<form method="dialog" id="editor-form">
<header class="editor-head">
<span class="avatar" id="edit-avatar">👤</span>
<h3 id="edit-title">Edit</h3>
<button type="button" class="btn ghost" id="edit-close">Close</button>
</header>
<div class="editor-body">
<label class="field">Name <span class="hint">— what the assistant says out loud</span>
<input id="edit-name" type="text" autocomplete="off">
</label>
<label class="field">Nickname <span class="hint">— what people call them; the assistant never uses it</span>
<input id="edit-nickname" type="text" autocomplete="off" placeholder="none">
</label>
<label class="field">Note
<input id="edit-note" type="text" autocomplete="off" placeholder="e.g. cousin, visits at Christmas">
</label>
<fieldset class="field-group">
<legend>Chores</legend>
<label class="check">
<input id="edit-chore-exempt" type="checkbox">
Exempt from chores
<span class="hint">— litter is always the exception; everyone cleans up what they left out</span>
</label>
<label class="field">Reminder style
<input id="edit-reminder-style" type="text" placeholder="e.g. be gentle, give me a few minutes">
</label>
<div class="field">
Assigned chores
<div id="edit-chore-types" class="chip-row"></div>
<p class="hint">
A strong preference, not a lock: if the assignee isn't home, whoever is
gets nudged instead. Litter ignores assignment entirely.
</p>
</div>
</fieldset>
<fieldset class="field-group">
<legend>Arrival notifications</legend>
<label class="check">
<input id="edit-notify-on-arrival" type="checkbox">
Push me when someone gets home
</label>
<label class="check">
<input id="edit-announce-arrivals" type="checkbox">
Announce <em>my</em> arrivals to others
<span class="hint">— untick to keep your comings and goings private</span>
</label>
<div class="row">
<label class="field grow">
ntfy topic
<input id="edit-notify-topic" type="text" placeholder="(shared household topic)">
</label>
<button type="button" id="edit-test-notify" class="btn">Send test</button>
</div>
<p id="notify-hint" class="hint"></p>
</fieldset>
<fieldset class="field-group">
<legend>Devices used for presence</legend>
<div id="edit-identifiers" class="card-list"></div>
<div class="row">
<input id="edit-new-identifier" type="text" placeholder="device_tracker.pble_…" class="grow">
<button type="button" id="edit-add-identifier" class="btn">Add</button>
</div>
<p class="hint">
Must match TRUSTED_ENTITY_PREFIXES — only IRK-resolved or fixed-tag entities
can identify a person. For provisioning a tag that isn't in range yet.
</p>
</fieldset>
<fieldset class="field-group">
<legend>Device rights</legend>
<div id="edit-grants" class="card-list"></div>
<div class="row">
<input id="grant-entity" type="text" placeholder="lock.front_door" class="grow">
<input id="grant-permission" type="text" placeholder="operate" style="max-width:110px">
<input id="grant-expires" type="date" title="Optional expiry">
<button type="button" id="grant-add" class="btn">Grant</button>
</div>
<p class="hint">
Lets this person operate that entity — e.g. unlocking the front door for
themselves. An expiry date makes it a temporary key.
</p>
</fieldset>
<div class="row editor-footer">
<button type="button" id="edit-save" class="btn primary">Save changes</button>
<button type="button" id="edit-delete" class="btn danger">Delete person</button>
<span id="edit-status" class="hint"></span>
</div>
</div>
</form>
</dialog>
<script src="admin.js"></script>
</body>
</html>