diff --git a/web_interface/blueprints/api_v3.py b/web_interface/blueprints/api_v3.py index ec12be1b..0aca34cb 100644 --- a/web_interface/blueprints/api_v3.py +++ b/web_interface/blueprints/api_v3.py @@ -989,6 +989,25 @@ def save_main_config(): if field in data: current_config['display']['display_durations'][field] = int(data[field]) + # Per-mode durations from the Rotation & Durations page, posted as + # duration__ (mode keys are arbitrary plugin mode names, so + # they can't use the suffix convention above) + mode_duration_fields = [k for k in data.keys() if k.startswith('duration__')] + if mode_duration_fields: + if 'display' not in current_config: + current_config['display'] = {} + if 'display_durations' not in current_config['display']: + current_config['display']['display_durations'] = {} + + for field in mode_duration_fields: + mode_key = field[len('duration__'):] + if not mode_key: + continue + try: + current_config['display']['display_durations'][mode_key] = int(data[field]) + except (ValueError, TypeError): + logger.warning("Ignoring non-integer duration for %s", mode_key) + # Handle plugin configurations dynamically # Any key that matches a plugin ID should be saved as plugin config # This includes proper secret field handling from schema diff --git a/web_interface/blueprints/pages_v3.py b/web_interface/blueprints/pages_v3.py index 8c3c1bbc..f25752ce 100644 --- a/web_interface/blueprints/pages_v3.py +++ b/web_interface/blueprints/pages_v3.py @@ -397,12 +397,51 @@ def _load_display_partial(): return "Error loading partial", 500 def _load_durations_partial(): - """Load display durations partial""" + """Load rotation & durations partial. + + Builds one duration entry per display mode of every enabled plugin + (falling back to the display controller's 30s default), overlaid with any + values saved in display.display_durations. Historically the template only + looped over saved keys, and nothing ever populated them, so the page + rendered empty. + """ try: if pages_v3.config_manager: main_config = pages_v3.config_manager.load_config() + duration_groups = [] + covered_keys = set() + if pages_v3.plugin_manager: + try: + pages_v3.plugin_manager.discover_plugins() + saved = (main_config.get('display', {}) or {}).get('display_durations', {}) or {} + infos = sorted(pages_v3.plugin_manager.get_all_plugin_info(), + key=lambda i: (i.get('name') or i.get('id') or '').lower()) + for info in infos: + pid = info.get('id') + if not pid or not (main_config.get(pid, {}) or {}).get('enabled', False): + continue + modes = pages_v3.plugin_manager.get_plugin_display_modes(pid) or [pid] + covered_keys.update(modes) + duration_groups.append({ + 'plugin_id': pid, + 'plugin_name': info.get('name') or pid, + 'modes': [{'key': m, 'value': saved.get(m, 30)} for m in modes], + }) + # Saved keys not owned by any enabled plugin (disabled or + # uninstalled plugins) stay visible rather than vanishing. + leftovers = [{'key': k, 'value': v} for k, v in saved.items() + if k not in covered_keys] + if leftovers: + duration_groups.append({ + 'plugin_id': '', + 'plugin_name': 'Other saved entries', + 'modes': leftovers, + }) + except Exception: + logger.warning("durations: could not enumerate plugin modes", exc_info=True) return render_template('v3/partials/durations.html', - main_config=main_config) + main_config=main_config, + duration_groups=duration_groups) except Exception as e: logger.error("Error loading partial", exc_info=True) return "Error loading partial", 500 diff --git a/web_interface/templates/v3/partials/durations.html b/web_interface/templates/v3/partials/durations.html index d980744c..6c1515a5 100644 --- a/web_interface/templates/v3/partials/durations.html +++ b/web_interface/templates/v3/partials/durations.html @@ -30,22 +30,39 @@ value='{{ main_config.display.get("plugin_rotation_order", [])|tojson }}'> -
- {% for key, value in main_config.display.display_durations.items() %} -
- - + {% if duration_groups %} +
+
+

Screen Durations

+

How long each screen stays on before rotating to the next one, in seconds (5–600, default 30).

+
+ {% for group in duration_groups %} +
+

{{ group.plugin_name }}

+
+ {% for mode in group.modes %} +
+ + +
+ {% endfor %} +
{% endfor %}
+ {% else %} +
+

No enabled plugins found — enable a plugin in the Plugin Manager to set its screen durations here.

+
+ {% endif %}