Build and push server image / build-and-push (push) Successful in 43s
The web UI grows into the multi-frame world: a left sidebar lists the
user's frames (with an online dot driven by the same overdue math as
the Device panel; collapsible off-canvas with a hamburger on mobile),
and each frame gets three tabs -- Photos (album picker, now displaying,
the drag-to-reorder upcoming grid), Configuration (name/order/
orientation/refresh/quiet hours/timezone/smart crop + the firmware
card), and Stats (device telemetry, lifetime counters, battery chart).
Settings and Admin adopt the same shell. / becomes a routing hub:
first frame, empty-state onboarding page, setup/login, or the
manage-QR redirect.
The JSON API moves to /api/frames/{id}/... behind require_frame_view /
require_frame_control: any linked user (admins see all) can view; 404
for frames outside your view so ids aren't confirmed; mutations 409
with the holder's name unless you hold the soft control lock, and
POST take-control always flips it to you. Config saves are now partial
updates -- each tab posts only its own fields (checkboxes always sent
explicitly), so the split forms can't clobber each other.
All CSS moves to static/theme.css and the old 680-line inline script
block splits into static/*.js -- the Pointer Events drag-drop state
machine and the canvas battery chart ported intact, not rewritten. The
CSRF fetch wrapper now reads a <meta> tag. No build step, still vanilla.
Verified end-to-end: page/static/API suites, control-lock handoff in
both directions, partial-save field preservation, non-admin frame
isolation, and the legacy-device curl suite (still byte-identical
responses for the deployed frame).
43 lines
1.8 KiB
HTML
43 lines
1.8 KiB
HTML
{% extends "app_base.html" %}
|
|
|
|
{% block title %}Settings{% endblock %}
|
|
{% block page_title %}Your account{% endblock %}
|
|
|
|
{% block content %}
|
|
{% if saved %}<div class="status ok">Saved.</div>{% endif %}
|
|
{% if error %}<div class="status err">{{ error }}</div>{% endif %}
|
|
|
|
<section class="card">
|
|
<h2 class="card-title">Profile & photo library</h2>
|
|
<form method="post" action="/settings">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<label>Display name
|
|
<input type="text" name="display_name" maxlength="64" value="{{ user.display_name }}">
|
|
</label>
|
|
<label>Immich URL
|
|
<input type="text" name="immich_url" placeholder="http://your-immich-host:2283"
|
|
value="{{ user.immich_url }}">
|
|
</label>
|
|
<label>Immich API key
|
|
<input type="password" name="immich_api_key" autocomplete="off"
|
|
placeholder="{% if user.immich_api_key %}(unchanged -- enter a new key to replace){% else %}your-immich-api-key{% endif %}">
|
|
</label>
|
|
<p class="sub" style="margin-top: 8px;">Frames you own pull photos from
|
|
this Immich library. The key needs read access to albums/assets/faces
|
|
plus <code>sharedLink.create</code> for the on-frame share QR.</p>
|
|
|
|
<h2 class="card-title" style="margin-top: 24px;">Change password</h2>
|
|
<label>Current password
|
|
<input type="password" name="current_password" autocomplete="current-password">
|
|
</label>
|
|
<label>New password
|
|
<input type="password" name="new_password" minlength="8" autocomplete="new-password">
|
|
</label>
|
|
<p class="sub" style="margin-top: 8px;">Leave both blank to keep your
|
|
current password.</p>
|
|
|
|
<button type="submit">Save</button>
|
|
</form>
|
|
</section>
|
|
{% endblock %}
|