207 lines
8.4 KiB
HTML
207 lines
8.4 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="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>
|
|
|
|
<!-- ------------------------------------------------------------ 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>
|