Build and push server image / build-and-push (push) Failing after 1m10s
New third mode alongside photos/calendar: fetches a .whiteboard file over plain WebDAV (Basic auth -- generic, not Nextcloud-specific) and renders it via a small Node.js sidecar using Excalidraw's own real export code (@excalidraw/utils + @resvg/resvg-js, no headless browser), since a .whiteboard file turns out to be Excalidraw scene JSON, not an image. The sidecar runs as a second process inside this same container (Dockerfile installs Node, start.sh backgrounds it before exec'ing uvicorn) rather than a separate docker-compose service -- lightweight, stateless, reachable only at 127.0.0.1 from the Python process, nothing worth independently scaling. The rendered PNG is treated exactly like a photo from there on -- composed/quantized through the existing image_pipeline (letterboxed, never cropped) rather than a second parallel rendering pipeline. WebDAV credentials support the common "it's actually the same Nextcloud account as my CalDAV" case (an explicit opt-in checkbox, not silently inferred) while still working with any WebDAV server generically. Frame-level source (URL + owning account) follows the same owner- controls-their-own-data permission split as calendar sources and the week view's task list: only the account owner can point a frame at it, anyone linked can clear it. Honest limitation: this environment has no Node.js/npm, so render-service/ is written carefully against each library's documented API (verified via the npm registry, including transitive dependency licenses after the CalDAV/AGPL surprise earlier this session) but has never actually been executed. First real docker build is the first true test -- see render-service/README.md.
120 lines
5.8 KiB
HTML
120 lines
5.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>Email
|
|
<input type="email" name="email" value="{{ user.email }}" placeholder="[email protected]">
|
|
</label>
|
|
<p class="sub" style="margin-top: 8px;">Used for password-reset links
|
|
and, for frames you own, battery-low alerts (set a threshold in a
|
|
frame's Configuration tab).</p>
|
|
<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;">Calendar</h2>
|
|
<label>Calendar URL (iCal .ics feed)
|
|
<input type="text" name="calendar_ics_url" placeholder="https://calendar.example.com/you.ics"
|
|
value="{{ user.calendar_ics_url }}">
|
|
</label>
|
|
<p class="sub" style="margin-top: 8px;">A single subscription link
|
|
(no login needed) -- e.g. Google Calendar's Settings →
|
|
"Secret address in iCal format", or Apple/Outlook's equivalent.</p>
|
|
|
|
<label style="margin-top: 20px;">CalDAV server URL
|
|
<input type="text" name="calendar_caldav_url" placeholder="https://cloud.example.com/remote.php/dav/"
|
|
value="{{ user.calendar_caldav_url }}">
|
|
</label>
|
|
<label>CalDAV username
|
|
<input type="text" name="calendar_caldav_username" autocomplete="off"
|
|
value="{{ user.calendar_caldav_username }}">
|
|
</label>
|
|
<label>CalDAV password
|
|
<input type="password" name="calendar_caldav_password" autocomplete="off"
|
|
placeholder="{% if user.calendar_caldav_password %}(unchanged -- enter a new password to replace){% else %}app password or account password{% endif %}">
|
|
</label>
|
|
<p class="sub" style="margin-top: 8px;">An account (Nextcloud,
|
|
Fastmail, iCloud, ...) that can expose more than one calendar --
|
|
e.g. Nextcloud's is usually
|
|
<code>https://your-server/remote.php/dav/</code>. Save this
|
|
section first, then use "Discover calendars" below to list what's
|
|
in the account.</p>
|
|
<button type="button" class="secondary" id="caldav-discover" style="margin-top: 12px;">Discover calendars</button>
|
|
<ul class="sub" id="caldav-calendar-list" style="margin-top: 8px; padding-left: 18px;">
|
|
{% if user.calendar_caldav_calendars %}
|
|
{% for c in user.calendar_caldav_calendars %}<li>{{ c.display_name }}</li>{% endfor %}
|
|
{% endif %}
|
|
</ul>
|
|
|
|
<p class="sub" style="margin-top: 8px;">Neither of these shows up
|
|
anywhere by itself -- add individual calendars to any frame
|
|
you're linked to from that frame's Calendar tab, so a frame only
|
|
shows calendars people have actually chosen to share with it.</p>
|
|
|
|
<h2 class="card-title" style="margin-top: 24px;">Whiteboard (WebDAV)</h2>
|
|
<p class="sub">Credentials for whiteboard frame mode -- fetching a
|
|
specific file (e.g. a Nextcloud Whiteboard board) over WebDAV.
|
|
Any WebDAV server works, not just Nextcloud.</p>
|
|
<div class="checkbox-row">
|
|
<input type="checkbox" id="webdav_reuse_caldav_creds" name="webdav_reuse_caldav_creds"
|
|
value="true" {% if user.webdav_reuse_caldav_creds %}checked{% endif %}>
|
|
<label for="webdav_reuse_caldav_creds" style="margin: 0; font-weight: normal;">
|
|
Reuse my CalDAV username/password above (only works if it's the
|
|
same account -- e.g. Nextcloud's CalDAV and its regular file
|
|
storage share one login)</label>
|
|
</div>
|
|
<div id="webdav-creds-fields">
|
|
<label>WebDAV username
|
|
<input type="text" id="webdav_username" name="webdav_username" autocomplete="off"
|
|
value="{{ user.webdav_username }}">
|
|
</label>
|
|
<label>WebDAV password
|
|
<input type="password" id="webdav_password" name="webdav_password" autocomplete="off"
|
|
placeholder="{% if user.webdav_password %}(unchanged -- enter a new password to replace){% else %}app password or account password{% endif %}">
|
|
</label>
|
|
</div>
|
|
<p class="sub" style="margin-top: 8px;">Doesn't show up anywhere by
|
|
itself -- point a specific frame's Whiteboard tab at a file URL
|
|
using this account.</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>
|
|
|
|
<div id="result"></div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script src="/static/settings.js"></script>
|
|
{% endblock %}
|