Files
LEDMatrix/web_interface/templates/v3/partials/general.html
ChuckandClaude Opus 5.5 0e9e2cabba fix(web): widget cache-busting, dead frontend code, and dependency pins (#656)
- Plugin-supplied widgets load as /static/plugin-widgets/...js?v=<plugin
  version>, so an update isn't hidden behind the year-long immutable cache.
- Fire-and-forget loadInstalledPlugins() calls catch the rejection it has
  already reported, so the global handler no longer adds a second toast.
- Timezone picker renders again when the General partial is re-injected.
- Remove dead code: executePluginAction's six plugin-id fallbacks and
  [DEBUG] logging, window.currentPluginConfig and every read of it, the
  file-upload JSON delete branch, unused PluginAPI / PluginInstallManager /
  PluginStateManager helpers, loadPluginWidgetsFromManifest, the stale
  install_manager.js and LEDVisibility fallbacks, error_handler.js's global
  escapeHtml, 13 unused CSS rules, and stale comments/no-op returns.
- pytz < 2027, psutil < 7 in requirements-test.txt, pytest-cov < 8.
- Pin anthropics/claude-code-action to the commit v1 resolves to.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 10:40:52 -04:00

180 lines
11 KiB
HTML

{% import 'v3/partials/_macros.html' as ui %}
<div class="bg-white rounded-lg shadow p-6">
<div class="border-b border-gray-200 pb-4 mb-6">
<h2 class="text-lg font-semibold text-gray-900">General Settings</h2>
<p class="mt-1 text-sm text-gray-600">Configure general system settings and location information.</p>
</div>
{{ ui.settings_filter() }}
<form hx-post="/api/v3/config/main"
hx-ext="json-enc"
hx-headers='{"Content-Type": "application/json"}'
hx-swap="none"
hx-on:htmx:after-request="showSaveResult(event.detail.xhr, 'Settings saved', 'Failed to save settings')"
class="space-y-6">
{# Marks this post as the whole form, so an unchecked box saves as
false. Without it the save endpoint treats absent keys as unchanged
(FORM_SECTION_FIELD in api_v3/config.py). #}
<input type="hidden" name="__form_section" value="general">
<!-- Web Display Autostart -->
<div class="form-group" id="setting-general-web_display_autostart" data-setting-key="web_display_autostart">
<label class="flex items-center">
<input type="checkbox"
name="web_display_autostart"
value="true"
{% if main_config.web_display_autostart %}checked{% endif %}
class="form-control 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">Web Display Autostart</span>
{{ ui.help_tip('Automatically start the web interface when the device boots.\nDefault: on. Turn off if you launch the web UI manually or run headless.', 'Web Display Autostart') }}
</label>
</div>
<!-- Weekly Automatic Updates -->
<div class="form-group" id="setting-general-auto_update" data-setting-key="auto_update.enabled">
<label class="flex items-center">
<input type="checkbox"
name="auto_update_enabled"
value="true"
{% if (main_config.auto_update or {}).enabled %}checked{% endif %}
class="form-control 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">Automatically check for and install updates once a week</span>
{{ ui.help_tip('Once a week, update LEDMatrix and every installed plugin that has a newer version.\nSkipped (and reported) if you have local changes, low disk space, or the rollback service is missing. After updating, the services are restarted and checked; if they do not stay healthy, the update is rolled back automatically.\nRuns overnight (2-5 AM in your timezone) when possible. Problems show as a banner on Overview. A stopped display is not started. Default: off.', 'Automatic Updates') }}
</label>
{% if auto_update_status and (main_config.auto_update or {}).enabled and auto_update_status.verifier_installed is sameas false %}
<p class="mt-1 ml-6 text-xs text-amber-700">
{% if auto_update_status.setup_status == 'failed' %}
Setting up the update health check failed: {{ auto_update_status.setup_message }}
LEDMatrix code updates are paused until it is fixed; plugin updates still run.
{% else %}
Setting up the update health check. The display service does this when it starts, so it is
ready shortly after saving (or once the display is started). LEDMatrix code updates begin after that.
{% endif %}
</p>
{% endif %}
{% if auto_update_status and auto_update_status.verifying %}
<p class="mt-1 ml-6 text-xs text-blue-700">An update was just installed and its health check is running.</p>
{% endif %}
{% if auto_update_status and (auto_update_status.last_run or auto_update_status.next_due) %}
<div class="mt-1 ml-6 text-xs text-gray-500 space-y-1">
{% if auto_update_status.last_run %}
<p>Last automatic update: {{ auto_update_status.last_run }}
<span class="{{ 'text-red-600' if auto_update_status.status == 'error' else '' }}">— {{ auto_update_status.summary }}</span></p>
{% endif %}
{% if auto_update_status.next_due %}
<p>Next check: {{ auto_update_status.next_due }} or the first overnight window after it</p>
{% endif %}
</div>
{% endif %}
</div>
<!-- Timezone -->
<div class="form-group" id="setting-general-timezone" data-setting-key="timezone">
<label for="timezone" class="block text-sm font-medium text-gray-700">Timezone{{ ui.help_tip('Time zone used for clocks, schedules, and time-based content.\nChoose the zone where the display physically lives so on/off schedules fire at the correct local time.', 'Timezone') }}</label>
<div id="timezone_container" class="mt-1"></div>
</div>
<!-- Legacy scroll frame rate (device-wide). Core scrolling no longer reads it. -->
<div class="form-group" id="setting-general-target-fps" data-setting-key="target_fps">
<label for="target_fps" class="block text-sm font-medium text-gray-700">Scroll Frame Rate (legacy){{ ui.help_tip('Kept for older plugins that read it. It no longer changes scrolling speed or smoothness.\nScrolling runs at the panel refresh rate (Display settings), and each plugin sets its own scroll speed.\nRange 30-200. Default: 100.', 'Scroll Frame Rate (legacy)') }}</label>
<input type="number"
id="target_fps"
name="target_fps"
value="{{ main_config.target_fps or 100 }}"
min="30"
max="200"
class="form-control">
</div>
<script>
(function() {
function initTimezoneWidget() {
if (!window.LEDMatrixWidgets) { setTimeout(initTimezoneWidget, 50); return; }
var widget = window.LEDMatrixWidgets.get('timezone-selector');
if (!widget) { setTimeout(initTimezoneWidget, 50); return; }
var container = document.getElementById('timezone_container');
if (!container) return;
// Only render if container is empty (not already rendered)
if (container.children.length > 0) return;
widget.render(container, {
'x-options': { showOffset: true, placeholder: 'Select your timezone...' }
}, {{ (main_config.timezone or "America/Chicago")|tojson }}, {
fieldId: 'timezone',
name: 'timezone'
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initTimezoneWidget);
} else {
setTimeout(initTimezoneWidget, 50);
}
})();
</script>
<!-- Location Information -->
<div class="grid grid-cols-1 md:grid-cols-3 xl:grid-cols-3 2xl:grid-cols-3 gap-4">
<div class="form-group" id="setting-general-city" data-setting-key="location.city">
<label for="city" class="block text-sm font-medium text-gray-700">City{{ ui.help_tip('City used for weather, sunrise/sunset, radar, and other location-based content.\nExample: Kansas City.\nUsed as the default for the location_city setting on plugins that have one; a value saved on the plugin itself overrides it.', 'City') }}</label>
<input type="text"
id="city"
name="city"
value="{{ main_config.location.city or 'Dallas' }}"
class="form-control">
</div>
<div class="form-group" id="setting-general-state" data-setting-key="location.state">
<label for="state" class="block text-sm font-medium text-gray-700">State{{ ui.help_tip('State or region for your location.\nExample: Missouri. Improves location-lookup accuracy.\nUsed as the default for the location_state setting on plugins that have one.', 'State') }}</label>
<input type="text"
id="state"
name="state"
value="{{ main_config.location.state or 'Texas' }}"
class="form-control">
</div>
<div class="form-group" id="setting-general-country" data-setting-key="location.country">
<label for="country" class="block text-sm font-medium text-gray-700">Country{{ ui.help_tip('Country code or name for your location.\nExample: US. Used with City and State for weather, radar, and geolocation.\nUsed as the default for the location_country setting on plugins that have one.', 'Country') }}</label>
<input type="text"
id="country"
name="country"
value="{{ main_config.location.country or 'US' }}"
class="form-control">
</div>
</div>
<!-- Plugin System Settings -->
<div class="border-t border-gray-200 pt-6 mt-6">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Plugin System Settings</h3>
<p class="text-sm text-gray-600 mb-4">Configure the core plugin system behavior.</p>
<div class="space-y-4">
<!-- plugin_system.auto_discover, auto_load_enabled and
development_mode used to be toggles here. Nothing reads
them: plugins are always discovered and every enabled
plugin is loaded. Stored values are left alone. -->
<!-- Plugins Directory -->
<div class="form-group" id="setting-general-plugins_directory" data-setting-key="plugin_system.plugins_directory">
<label for="plugins_directory" class="block text-sm font-medium text-gray-700">Plugins Directory{{ ui.help_tip('Folder (relative to the project root) where plugins are stored.\nDefault: plugin-repos. Only change this if you keep plugins in a custom location.', 'Plugins Directory') }}</label>
<input type="text"
id="plugins_directory"
name="plugins_directory"
value="{{ main_config.get('plugin_system', {}).get('plugins_directory', 'plugin-repos') }}"
placeholder="plugin-repos"
class="form-control">
</div>
</div>
</div>
<!-- Submit Button -->
<div class="flex justify-end mt-6">
<button type="submit"
class="btn bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md">
<i class="fas fa-save mr-2"></i>
Save General Settings
</button>
</div>
</form>
</div>