This PR breaks the large web and player bootstrap files into smaller modules with clearer ownership.

Web changes:

Split shared helpers, bootstrap logic, route groups, and upload-sync behavior out of web.js.
Kept web.js focused on wiring and server startup.
Fixed screen playlist reassignment so changing a screen’s playlist now triggers a refresh.
Fixed single-slide playlist refresh behavior so updates do not get stuck behind the current slide.
Player changes:

Split websocket/runtime handling into runtime.js.
Split playlist assembly and revision hashing into playlist.js.
Split onboarding and player HTTP routes into dedicated modules.
Split render utilities and template loading into render-helpers.js.
Kept player.js mostly as startup/orchestration.
Validation:

Rebuilt both services with Docker Compose.
Smoke-checked web and player routes after the refactor.
Verified get_errors was clean on the touched modules.
This commit is contained in:
2026-07-20 23:58:27 +01:00
parent 480ccdbe9c
commit 2ea8d389fa
321 changed files with 12687 additions and 7080 deletions
+28 -31
View File
@@ -5,39 +5,36 @@
</div>
</div>
<div class="card user-create-card">
<form method="post" action="/admin/users" class="user-create-form">
<section class="user-create-section">
<h3>Account details</h3>
<div class="user-create-fields">
<label>
Username
<input type="text" name="username" autocomplete="username" required />
</label>
<label>
Name
<input type="text" name="name" autocomplete="name" required />
</label>
<div class="card card-outline card-primary admin-form-card">
<div class="card-header">
<h3 class="card-title">User details</h3>
</div>
<form id="user-form" method="post" action="/admin/users">
<div class="card-body">
<div class="row g-3">
<div class="col-12 col-md-6">
<label for="user-username" class="form-label">Username</label>
<input id="user-username" type="text" name="username" class="form-control" autocomplete="username" required />
</div>
<div class="col-12 col-md-6">
<label for="user-name" class="form-label">Name</label>
<input id="user-name" type="text" name="name" class="form-control" autocomplete="name" required />
</div>
<div class="col-12 col-md-6">
<label for="user-password" class="form-label">Password</label>
<input id="user-password" type="password" name="password" class="form-control" autocomplete="new-password" minlength="8" required />
</div>
<div class="col-12 col-md-6">
<label for="user-confirm-password" class="form-label">Confirm password</label>
<input id="user-confirm-password" type="password" name="confirm_password" class="form-control" autocomplete="new-password" minlength="8" required />
</div>
</div>
</section>
<section class="user-create-section">
<h3>Set password</h3>
<div class="user-create-fields">
<label>
Password
<input type="password" name="password" autocomplete="new-password" minlength="8" required />
</label>
<label>
Confirm password
<input type="password" name="confirm_password" autocomplete="new-password" minlength="8" required />
</label>
</div>
<div class="card-footer d-flex justify-content-end">
<div class="btn-group" role="group" aria-label="User actions">
{{{saveActionButtons formId="user-form" saveLabel="Save" showSaveAndClose=false showSaveAndNew=false}}}
<a class="btn btn-warning" href="/admin/users" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
</div>
</section>
<div class="actions user-create-actions">
<button type="submit" class="user-create-button">Create user</button>
<a class="button-link secondary" href="/admin/users">Back to users</a>
</div>
</form>
</div>
+57 -39
View File
@@ -1,49 +1,67 @@
<div class="page-header">
<div>
<h2>Edit user</h2>
<h2>Edit user - {{user.name}}</h2>
<p>Update the name or password for {{user.username}}.</p>
</div>
</div>
<div class="card user-edit-card">
<div class="user-edit-header">
<div class="user-cell">
<div class="user-avatar" aria-hidden="true">{{usernameInitial user.username}}</div>
<div>
<strong>{{user.username}}</strong>
{{#if user.isCurrentUser}}
<div class="subtle">Current signed-in user</div>
{{/if}}
<div class="card card-outline card-primary admin-form-card">
<div class="card-body">
<div class="row g-4">
<div class="col-12 col-xl-6">
<div class="card card-outline card-secondary admin-form-card mb-0">
<div class="card-header">
<h3 class="card-title">Profile</h3>
</div>
<form id="user-profile-form" method="post" action="/admin/users/{{user.id}}/username" data-async-save data-async-save-close-url="/admin/users">
<div class="card-body">
<div class="mb-3">
<label for="edit-user-username" class="form-label">Username</label>
<input id="edit-user-username" type="text" name="username" value="{{user.username}}" class="form-control" autocomplete="username" required disabled />
</div>
<div class="mb-0">
<label for="edit-user-name" class="form-label">Name</label>
<input id="edit-user-name" type="text" name="name" value="{{user.name}}" class="form-control" autocomplete="name" required />
</div>
</div>
<div class="card-footer d-flex justify-content-end">
<div class="btn-group" role="group" aria-label="Profile actions">
{{{saveActionButtons formId="user-profile-form" saveLabel="Save" saveAndCloseLabel="Save and Close" saveAndCloseValue="close" showSaveAndNew=false}}}
<a class="btn btn-warning" href="/admin/users" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
<button type="submit" class="btn btn-danger" form="delete-user-form">Delete</button>
</div>
</div>
</form>
</div>
</div>
<div class="col-12 col-xl-6">
<div class="card card-outline card-warning admin-form-card h-100 mb-0">
<div class="card-header">
<h3 class="card-title">Reset password</h3>
</div>
<form id="user-password-form" method="post" action="/admin/users/{{user.id}}/password" data-async-save data-async-save-close-url="/admin/users">
<div class="card-body">
<div class="mb-3">
<label for="edit-user-password" class="form-label">New password</label>
<input id="edit-user-password" type="password" name="password" class="form-control" autocomplete="new-password" minlength="8" required />
</div>
<div class="mb-0">
<label for="edit-user-confirm-password" class="form-label">Confirm password</label>
<input id="edit-user-confirm-password" type="password" name="confirm_password" class="form-control" autocomplete="new-password" minlength="8" required />
</div>
</div>
<div class="card-footer d-flex justify-content-end">
<div class="btn-group" role="group" aria-label="Password actions">
{{{saveActionButtons formId="user-password-form" saveLabel="Save" saveAndCloseLabel="Save and Close" saveAndCloseValue="close" showSaveAndNew=false}}}
</div>
</div>
</form>
</div>
</div>
</div>
<a class="button-link secondary" href="/admin/users">Back to users</a>
</div>
</div>
<div class="user-edit-grid">
<form method="post" action="/admin/users/{{user.id}}/username" class="card user-edit-panel">
<h3>Edit user</h3>
<label>
Username
<input type="text" name="username" value="{{user.username}}" autocomplete="username" required disabled />
</label>
<label>
Name
<input type="text" name="name" value="{{user.name}}" autocomplete="name" required />
</label>
<button type="submit" class="user-edit-button">Save changes</button>
</form>
<form method="post" action="/admin/users/{{user.id}}/password" class="card user-edit-panel">
<h3>Reset password</h3>
<label>
New password
<input type="password" name="password" autocomplete="new-password" minlength="8" required />
</label>
<label>
Confirm password
<input type="password" name="confirm_password" autocomplete="new-password" minlength="8" required />
</label>
<button type="submit" class="user-edit-button">Save password</button>
</form>
</div>
</div>
<form id="delete-user-form" method="post" action="/admin/users/{{user.id}}/delete" data-confirm-message="Delete {{user.username}}?"></form>
+12 -9
View File
@@ -3,14 +3,17 @@
<h2>Users</h2>
<p>Create accounts, rename users, reset passwords, and remove unused logins.</p>
</div>
<div class="page-toolbar">
<a class="button-link" href="/admin/users/new">Add user</a>
</div>
</div>
<div class="card">
<h3>Existing users</h3>
<table class="table-cards users-table">
<div class="card card-outline card-primary">
<div class="card-header">
<h3 class="card-title">Existing users</h3>
<div class="card-tools">
<a class="btn btn-primary btn-sm" href="/admin/users/new">Add user</a>
</div>
</div>
<div class="card-body table-responsive p-0">
<table class="table table-striped w-100 mb-0 users-table">
<thead>
<tr>
<th>User</th>
@@ -24,7 +27,6 @@
<tr>
<td data-label="User">
<div class="user-cell">
<div class="user-avatar user-avatar--small" aria-hidden="true">{{usernameInitial name}}</div>
<div>
<strong>{{username}}</strong>
{{#if isCurrentUser}}
@@ -43,9 +45,9 @@
<td data-label="Actions">
<div class="actions users-row-actions">
{{#unless isCurrentUser}}
<a class="button-link list-action" href="/admin/users/{{id}}/edit">Edit</a>
<a class="btn btn-sm btn-primary" href="/admin/users/{{id}}/edit">Edit</a>
<form method="post" action="/admin/users/{{id}}/delete" class="inline-form" data-confirm-message="Delete {{username}}?">
<button type="submit" class="button-link list-action is-danger">Delete</button>
<button type="submit" class="btn btn-sm btn-danger">Delete</button>
</form>
{{else}}
<span class="empty">Protected</span>
@@ -59,4 +61,5 @@
{{/if}}
</tbody>
</table>
</div>
</div>