feat: Add application-level settings for CSS color management
Some checks failed
Run Tests / test (push) Failing after 1m51s

- Introduced a new table `application_setting` to store configurable application options.
- Implemented functions to manage CSS color settings, including loading, updating, and reading environment overrides.
- Added a new settings view to render and manage theme colors.
- Updated UI to include a settings page with theme color management and environment overrides display.
- Enhanced CSS styles for the settings page and sidebar navigation.
- Created unit and end-to-end tests for the new settings functionality and CSS management.
This commit is contained in:
2025-10-25 19:20:52 +02:00
parent e74ec79cc9
commit 5b1322ddbc
24 changed files with 1336 additions and 35 deletions

View File

@@ -1,17 +1,3 @@
{% set nav_links = [
("/", "Dashboard"),
("/ui/scenarios", "Scenarios"),
("/ui/parameters", "Parameters"),
("/ui/currencies", "Currencies"),
("/ui/costs", "Costs"),
("/ui/consumption", "Consumption"),
("/ui/production", "Production"),
("/ui/equipment", "Equipment"),
("/ui/maintenance", "Maintenance"),
("/ui/simulations", "Simulations"),
("/ui/reporting", "Reporting"),
] %}
<div class="sidebar-inner">
<div class="sidebar-brand">
<span class="brand-logo" aria-hidden="true">CM</span>
@@ -20,20 +6,5 @@
<span class="brand-subtitle">Mining Planner</span>
</div>
</div>
<nav class="sidebar-nav" aria-label="Primary navigation">
{% set current_path = request.url.path if request else "" %}
{% for href, label in nav_links %}
{% if href == "/" %}
{% set is_active = current_path == "/" %}
{% else %}
{% set is_active = current_path.startswith(href) %}
{% endif %}
<a
href="{{ href }}"
class="sidebar-link{% if is_active %} is-active{% endif %}"
>
{{ label }}
</a>
{% endfor %}
</nav>
{% include "partials/sidebar_nav.html" %}
</div>

View File

@@ -0,0 +1,88 @@
{% set nav_groups = [
{
"label": "Dashboard",
"links": [
{"href": "/", "label": "Dashboard"},
],
},
{
"label": "Scenarios",
"links": [
{"href": "/ui/scenarios", "label": "Overview"},
{"href": "/ui/parameters", "label": "Parameters"},
{"href": "/ui/costs", "label": "Costs"},
{"href": "/ui/consumption", "label": "Consumption"},
{"href": "/ui/production", "label": "Production"},
{
"href": "/ui/equipment",
"label": "Equipment",
"children": [
{"href": "/ui/maintenance", "label": "Maintenance"},
],
},
],
},
{
"label": "Analysis",
"links": [
{"href": "/ui/simulations", "label": "Simulations"},
{"href": "/ui/reporting", "label": "Reporting"},
],
},
{
"label": "Settings",
"links": [
{
"href": "/ui/settings",
"label": "Settings",
"children": [
{"href": "/ui/currencies", "label": "Currency Management"},
],
},
],
},
] %}
<nav class="sidebar-nav" aria-label="Primary navigation">
{% set current_path = request.url.path if request else "" %}
{% for group in nav_groups %}
<div class="sidebar-section">
<div class="sidebar-section-label">{{ group.label }}</div>
<div class="sidebar-section-links">
{% for link in group.links %}
{% set href = link.href %}
{% if href == "/" %}
{% set is_active = current_path == "/" %}
{% else %}
{% set is_active = current_path.startswith(href) %}
{% endif %}
<div class="sidebar-link-block">
<a
href="{{ href }}"
class="sidebar-link{% if is_active %} is-active{% endif %}"
>
{{ link.label }}
</a>
{% if link.children %}
<div class="sidebar-sublinks">
{% for child in link.children %}
{% if child.href == "/" %}
{% set child_active = current_path == "/" %}
{% else %}
{% set child_active = current_path.startswith(child.href) %}
{% endif %}
<a
href="{{ child.href }}"
class="sidebar-sublink{% if child_active %} is-active{% endif %}"
>
{{ child.label }}
</a>
{% endfor %}
</div>
{% endif %}
</div>
{% endfor %}
</div>
</div>
{% endfor %}
</nav>

113
templates/settings.html Normal file
View File

@@ -0,0 +1,113 @@
{% extends "base.html" %}
{% block title %}Settings · CalMiner{% endblock %}
{% block content %}
<section class="page-header">
<div>
<h1>Settings</h1>
<p class="page-subtitle">Configure platform defaults and administrative options.</p>
</div>
</section>
<section class="settings-grid">
<article class="settings-card">
<h2>Currency Management</h2>
<p>Manage available currencies, symbols, and default selections from the Currency Management page.</p>
<a class="button-link" href="/ui/currencies">Go to Currency Management</a>
</article>
<article class="settings-card">
<h2>Visual Theme</h2>
<p>Adjust CalMiner theme colors and preview changes instantly.</p>
<p class="settings-card-note">Changes save to the settings table and apply across the UI after submission. Environment overrides (if configured) remain read-only.</p>
</article>
</section>
<section class="panel" id="theme-settings" data-api="/api/settings/css">
<header class="panel-header">
<div>
<h2>Theme Colors</h2>
<p class="chart-subtitle">Update global CSS variables to customize CalMiner&apos;s appearance.</p>
</div>
</header>
<form id="theme-settings-form" class="form-grid color-form-grid" novalidate>
{% for key, value in css_variables.items() %}
{% set env_meta = css_env_override_meta.get(key) %}
<label class="color-form-field{% if env_meta %} is-env-override{% endif %}" data-variable="{{ key }}">
<span class="color-field-header">
<span class="color-field-name">{{ key }}</span>
<span class="color-field-default">Default: {{ css_defaults[key] }}</span>
</span>
<span class="color-field-helper" id="color-helper-{{ loop.index }}">Accepts hex, rgb(a), or hsl(a) values.</span>
{% if env_meta %}
<span class="color-env-flag">Managed via {{ env_meta.env_var }} (read-only)</span>
{% endif %}
<span class="color-input-row">
<input
type="text"
name="{{ key }}"
class="color-value-input"
value="{{ value }}"
autocomplete="off"
aria-describedby="color-helper-{{ loop.index }}"
{% if env_meta %}disabled aria-disabled="true" data-env-override="true"{% endif %}
/>
<span class="color-preview" aria-hidden="true" style="background: {{ value }}"></span>
</span>
</label>
{% endfor %}
<div class="button-row">
<button type="submit" class="btn primary">Save Theme</button>
<button type="button" class="btn" id="theme-settings-reset">Reset to Defaults</button>
</div>
</form>
{% from "partials/components.html" import feedback with context %}
{{ feedback("theme-settings-feedback") }}
</section>
<section class="panel" id="theme-env-overrides">
<header class="panel-header">
<div>
<h2>Environment Overrides</h2>
<p class="chart-subtitle">The following CSS variables are controlled via environment variables and take precedence over database values.</p>
</div>
</header>
{% if css_env_override_rows %}
<div class="table-container env-overrides-table">
<table aria-label="Environment-controlled theme variables">
<thead>
<tr>
<th scope="col">CSS Variable</th>
<th scope="col">Environment Variable</th>
<th scope="col">Value</th>
</tr>
</thead>
<tbody>
{% for row in css_env_override_rows %}
<tr>
<td><code>{{ row.css_key }}</code></td>
<td><code>{{ row.env_var }}</code></td>
<td><code>{{ row.value }}</code></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="empty-state">No environment overrides configured.</p>
{% endif %}
</section>
{% endblock %}
{% block scripts %}
{{ super() }}
<script id="theme-settings-data" type="application/json">
{{ {
"variables": css_variables,
"defaults": css_defaults,
"envOverrides": css_env_overrides,
"envSources": css_env_override_rows
} | tojson }}
</script>
<script src="/static/js/settings.js"></script>
{% endblock %}