From 8648a45fe7c8455780bf04fc74e3a2affcbbdb86 Mon Sep 17 00:00:00 2001 From: Tim Bradgate Date: Mon, 23 Feb 2026 21:39:43 +0000 Subject: [PATCH] Organise system settings into categories --- client/src/main.js | 3 + client/src/store/modules/system.js | 16 ++ .../vue_components/config/ConfigSettings.vue | 190 ++++++++++++------ server/controllers/api/settings.py | 7 + server/digi_server/settings.py | 42 +++- server/main.py | 4 + 6 files changed, 195 insertions(+), 67 deletions(-) diff --git a/client/src/main.js b/client/src/main.js index c3e1d153..091e524d 100644 --- a/client/src/main.js +++ b/client/src/main.js @@ -13,6 +13,7 @@ import router from './router'; import setupHttpInterceptor from './js/http-interceptor'; import { getWebSocketURL, isElectron } from '@/js/platform'; import { initRemoteLogging } from '@/js/logger'; +import log from 'loglevel'; import './assets/styles/dark.scss'; import 'vue-toast-notification/dist/theme-sugar.css'; @@ -22,6 +23,8 @@ import 'splitpanes/dist/splitpanes.css'; setupHttpInterceptor(); initRemoteLogging(); +log.info(`Running in ${import.meta.env.MODE} mode.`); + Vue.use(BootstrapVue); Vue.use(IconsPlugin); Vue.component('MultiSelect', Multiselect); diff --git a/client/src/store/modules/system.js b/client/src/store/modules/system.js index b0c7bec6..2cb19096 100644 --- a/client/src/store/modules/system.js +++ b/client/src/store/modules/system.js @@ -9,6 +9,7 @@ export default { availableShows: [], rawSettings: {}, rbacRoles: [], + settingsCategories: {}, }, mutations: { UPDATE_SETTINGS(state, settings) { @@ -23,6 +24,9 @@ export default { UPDATE_RBAC_ROLES(state, rbac) { state.rbacRoles = rbac; }, + UPDATE_SETTINGS_CATEGORIES(state, categories) { + state.settingsCategories = categories; + }, }, actions: { async GET_AVAILABLE_SHOWS(context) { @@ -88,6 +92,15 @@ export default { log.error('Unable to fetch RBAC roles'); } }, + async GET_SETTINGS_CATEGORIES(context) { + const response = await fetch(makeURL('/api/v1/settings/categories')); + if (response.ok) { + const categories = await response.json(); + await context.commit('UPDATE_SETTINGS_CATEGORIES', categories.categories); + } else { + log.error('Unable to fetch settings categories'); + } + }, }, getters: { AVAILABLE_SHOWS(state) { @@ -102,5 +115,8 @@ export default { RBAC_ROLES(state) { return state.rbacRoles; }, + SETTINGS_CATEGORIES(state) { + return state.settingsCategories; + }, }, }; diff --git a/client/src/vue_components/config/ConfigSettings.vue b/client/src/vue_components/config/ConfigSettings.vue index fbf7f9af..b8b917e7 100644 --- a/client/src/vue_components/config/ConfigSettings.vue +++ b/client/src/vue_components/config/ConfigSettings.vue @@ -9,62 +9,96 @@ @reset.stop.prevent="resetForm(true)" >
- + +
+ + {{ category }} + + {{ Object.keys(settings).length - dirtySettingsByCategory[category] }} + + + {{ dirtySettingsByCategory[category] }} + + + + +
+
+ + + + + + + + + + Unknown setting type {{ setting.type }} for setting {{ key }}. + + + + + + - - - - - - - Unknown setting type {{ setting.type }} for setting {{ key }}. - -
- Reset Submit @@ -80,7 +114,7 @@ diff --git a/server/controllers/api/settings.py b/server/controllers/api/settings.py index 4a0fefc3..4a203eeb 100644 --- a/server/controllers/api/settings.py +++ b/server/controllers/api/settings.py @@ -41,6 +41,13 @@ async def patch(self): self.write({"message": "Settings updated"}) +@ApiRoute("settings/categories", ApiVersion.V1) +class SettingsCategoriesController(BaseAPIController): + @allow_when_password_required + async def get(self): + await self.finish({"categories": self.application.digi_settings.categories}) + + @ApiRoute("settings/raw", ApiVersion.V1) class RawSettingsController(BaseAPIController): async def get(self): diff --git a/server/digi_server/settings.py b/server/digi_server/settings.py index 2f582c50..8d1274f8 100644 --- a/server/digi_server/settings.py +++ b/server/digi_server/settings.py @@ -4,7 +4,7 @@ import os import tomllib from pathlib import Path -from typing import TYPE_CHECKING, Dict, Optional +from typing import TYPE_CHECKING, Dict, List, Optional from tornado.locks import Lock @@ -167,8 +167,17 @@ def __init__(self, application: DigiScriptServer, settings_path=None): ) os.makedirs(os.path.dirname(self.settings_path)) + self.categories: Dict[str : List[str]] = {"General": []} self.settings: Dict[str, SettingsObject] = {} + self.init_settings() + self._load(spawn_callbacks=False) + self._file_watcher = IOLoopFileWatcher( + self.settings_path, self.auto_reload_changes, 100 + ) + self._file_watcher.add_error_callback(self.file_deleted) + self._file_watcher.watch() + def init_settings(self): db_default = f"sqlite:///{os.path.join(os.path.dirname(__file__), '../conf/digiscript.sqlite')}" self.define( "has_admin_user", @@ -205,6 +214,7 @@ def __init__(self, application: DigiScriptServer, settings_path=None): self._application.regen_logging, display_name="Log Level", choice_options=get_level_names_by_order(), + category="Logging", ) self.define( "log_path", @@ -213,6 +223,7 @@ def __init__(self, application: DigiScriptServer, settings_path=None): True, self._application.regen_logging, display_name="Application Log Path", + category="Logging", ) self.define( "max_log_mb", @@ -221,6 +232,7 @@ def __init__(self, application: DigiScriptServer, settings_path=None): True, self._application.regen_logging, display_name="Max Log Size (MB)", + category="Logging", ) self.define( "log_backups", @@ -229,6 +241,7 @@ def __init__(self, application: DigiScriptServer, settings_path=None): True, self._application.regen_logging, display_name="Log Backups", + category="Logging", ) self.define( "log_redaction", @@ -237,6 +250,7 @@ def __init__(self, application: DigiScriptServer, settings_path=None): True, display_name="Enable Log Redaction", help_text="When enabled, potentially sensitive information will be redacted from logs.", + category="Logging", ) self.define( "db_log_enabled", @@ -245,6 +259,7 @@ def __init__(self, application: DigiScriptServer, settings_path=None): True, self._application.regen_logging, display_name="Enable Database Log", + category="DB Logging", ) self.define( "db_log_path", @@ -253,6 +268,7 @@ def __init__(self, application: DigiScriptServer, settings_path=None): True, self._application.regen_logging, display_name="Database Log Path", + category="DB Logging", ) self.define( "db_max_log_mb", @@ -261,6 +277,7 @@ def __init__(self, application: DigiScriptServer, settings_path=None): True, self._application.regen_logging, display_name="Max Database Log Size (MB)", + category="DB Logging", ) self.define( "db_log_backups", @@ -269,6 +286,7 @@ def __init__(self, application: DigiScriptServer, settings_path=None): True, self._application.regen_logging, display_name="Database Log Backups", + category="DB Logging", ) self.define( "compiled_script_path", @@ -294,6 +312,7 @@ def __init__(self, application: DigiScriptServer, settings_path=None): True, display_name="Enable Client Log Forwarding", help_text="When enabled, client browsers will forward their logs to the server.", + category="Client Logging", ) self.define( "client_log_level", @@ -304,6 +323,7 @@ def __init__(self, application: DigiScriptServer, settings_path=None): display_name="Client Log Level", help_text="Minimum log level that clients will forward to the server.", choice_options=["TRACE", "DEBUG", "INFO", "WARN", "ERROR"], + category="Client Logging", ) self.define( "client_log_path", @@ -313,6 +333,7 @@ def __init__(self, application: DigiScriptServer, settings_path=None): self._application.regen_logging, display_name="Client Log Path", help_text="Path to the log file for client-side log messages.", + category="Client Logging", ) self.define( "client_max_log_mb", @@ -322,6 +343,7 @@ def __init__(self, application: DigiScriptServer, settings_path=None): self._application.regen_logging, display_name="Max Client Log Size (MB)", help_text="Maximum size in MB of the client log file before it is rotated.", + category="Client Logging", ) self.define( "client_log_backups", @@ -331,6 +353,7 @@ def __init__(self, application: DigiScriptServer, settings_path=None): self._application.regen_logging, display_name="Client Log Backups", help_text="Number of rotated client log file backups to retain.", + category="Client Logging", ) self.define( "log_buffer_size", @@ -341,16 +364,9 @@ def __init__(self, application: DigiScriptServer, settings_path=None): display_name="Log Buffer Size", help_text="Number of recent log entries to keep in memory for the log viewer. " "Larger values use more memory. Changes take effect after restart.", + category="Client Logging", ) - self._load(spawn_callbacks=False) - - self._file_watcher = IOLoopFileWatcher( - self.settings_path, self.auto_reload_changes, 100 - ) - self._file_watcher.add_error_callback(self.file_deleted) - self._file_watcher.watch() - def define( self, key, @@ -363,7 +379,11 @@ def define( help_text: str = "", hide_from_ui: bool = False, choice_options: Optional[list] = None, + category: str = "General", ): + if key in self.settings: + raise KeyError(f"Setting {key} is already defined") + self.settings[key] = SettingsObject( key, val_type, @@ -376,6 +396,10 @@ def define( hide_from_ui, choice_options, ) + if category not in self.categories: + self.categories[category] = [key] + else: + self.categories[category].append(key) def file_deleted(self): get_logger().info("Settings file deleted; recreating from in memory settings") diff --git a/server/main.py b/server/main.py index 3da787f7..80e8994d 100755 --- a/server/main.py +++ b/server/main.py @@ -81,6 +81,8 @@ def patched_define( display_name="", help_text="", hide_from_ui=False, + choice_options=None, + category="General", ): # For database path, adjust to a writable location if needed if key == "db_path" and default and isinstance(default, str): @@ -106,6 +108,8 @@ def patched_define( display_name, help_text, hide_from_ui, + choice_options, + category, ) # Apply the patch