feat(web): Start Display on Boot toggle on the General tab

Turns ledmatrix.service's autostart on or off at once through the existing
enable_autostart / disable_autostart system actions (sudo grants already in
lib_sudoers.sh). The checkbox shows systemd's own state, read with
`systemctl is-enabled`, so nothing is stored in config.json and the default
stays on. Hidden when the state cannot be read; a refused change puts the
box back and shows the route's message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-10-12 17:49:10 -04:00
co-authored by Claude Opus 5.5
parent 886e199cae
commit 85d75cdd70
7 changed files with 208 additions and 1 deletions
+17
View File
@@ -4,6 +4,7 @@ from html.parser import HTMLParser
import json
import logging
import re
import subprocess
from pathlib import Path
# Strict allowlists for URL-derived values used in path and script operations.
@@ -487,9 +488,25 @@ def _load_general_partial():
main_config=main_config,
auto_update_status=auto_update_status,
update_channel_status=update_channel_status,
display_autostart=_display_autostart_state(),
web_login=_web_login_state())
def _display_autostart_state():
"""Whether ledmatrix.service starts at boot; None hides the toggle.
systemd's enablement is the setting itself (the installer enables the
unit), so nothing is stored in config.json. None when it cannot be read:
no systemctl (a dev machine) or the unit is not installed.
"""
try:
result = subprocess.run(['systemctl', 'is-enabled', 'ledmatrix.service'],
capture_output=True, text=True, timeout=5)
except (OSError, subprocess.SubprocessError):
return None
return {'enabled': True, 'disabled': False}.get(result.stdout.strip())
def _web_login_state():
"""What the General tab's Security section shows; None hides it.
@@ -191,6 +191,28 @@ export function revokeToken(ctx, id, name) {
}).catch(failed(ctx));
}
/**
* The "Start Display on Boot" checkbox: enable or disable ledmatrix.service
* at once. A refusal (e.g. the sudo rule is missing) puts the box back.
*/
export function setDisplayAutostart(ctx, box) {
const on = box.checked;
box.disabled = true;
return send(ctx, 'POST', '/api/v3/system/action', { action: on ? 'enable_autostart' : 'disable_autostart' }).then(function(res) {
if (!res) return false;
const ok = res.ok && res.d.status === 'success';
if (!ok) box.checked = !on;
ctx.notify(ok ? (on ? 'The display will start on boot' : 'The display will not start on boot')
: (res.d.message || 'Could not change display autostart'), ok ? 'success' : 'error');
return ok;
}).catch(function(err) {
box.checked = !on;
failed(ctx)(err);
}).finally(function() {
box.disabled = false;
});
}
const FORM_ACTIONS = { 'set-password': setPassword, 'disable-login': disableLogin, 'create-token': createToken };
function drawTimezone(root, ctx) {
@@ -238,6 +260,10 @@ export function init(root, ctx) {
if (row) revokeToken(ctx, row.dataset.tokenId, button.dataset.tokenName);
}
}, on);
root.addEventListener('change', function(event) {
const box = event.target;
if (box && box.dataset && box.dataset.action === 'display-autostart') setDisplayAutostart(ctx, box);
}, on);
drawTimezone(root, ctx);
active = ctx;
}
@@ -12,6 +12,22 @@
{{ ui.settings_filter() }}
{# Display autostart is systemd's enable/disable of ledmatrix.service, not a
config.json key: outside the form so Save never posts it. general.js
applies it on change. Hidden when the unit's state cannot be read. #}
{% if display_autostart is not none %}
<div class="form-group mb-6" id="setting-general-display_autostart" data-setting-key="display_autostart">
<label class="flex items-center">
<input type="checkbox"
data-action="display-autostart"
{% if display_autostart %}checked{% endif %}
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
<span class="ml-2 text-sm font-medium text-gray-900">Start Display on Boot</span>
{{ ui.help_tip('Start the LED display automatically when the device powers on.\nApplies right away; no Save needed.\nWhen off, start it with Start Display on the Overview tab. The web interface still starts on boot.\nDefault: on.', 'Start Display on Boot') }}
</label>
</div>
{% endif %}
<form hx-post="/api/v3/config/main"
hx-ext="json-enc"
hx-headers='{"Content-Type": "application/json"}'