editor.py aktualisiert
This commit is contained in:
@@ -28,23 +28,47 @@ WEB_PORT = int(os.environ.get("WEB_PORT", "8080"))
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Branding / theme configuration
|
# Branding / theme configuration
|
||||||
|
|
||||||
SITE_NAME = os.environ.get("SITE_NAME", "Event Audio")
|
|
||||||
SITE_SUBTITLE = os.environ.get("SITE_SUBTITLE", "Audiostream-Aufzeichnungen")
|
|
||||||
PAGE_TITLE = os.environ.get("PAGE_TITLE", "Event Audio – Editor")
|
|
||||||
PAGE_HEADING = os.environ.get("PAGE_HEADING", "Event Audio – Editor")
|
|
||||||
EDIT_PAGE_TITLE = os.environ.get("EDIT_PAGE_TITLE", "Event Audio – Aufnahme bearbeiten")
|
|
||||||
EDIT_PAGE_HEADING = os.environ.get("EDIT_PAGE_HEADING", "Aufnahme bearbeiten")
|
|
||||||
THEME_FILE = Path(os.environ.get("THEME_FILE", "/config/theme.json"))
|
THEME_FILE = Path(os.environ.get("THEME_FILE", "/config/theme.json"))
|
||||||
LOGO_FILE = Path(os.environ["LOGO_FILE"]) if os.environ.get("LOGO_FILE") else None
|
SETTINGS_FILE = Path(os.environ.get("SETTINGS_FILE", "/config/settings.json"))
|
||||||
FAVICON_FILE = Path(os.environ["FAVICON_FILE"]) if os.environ.get("FAVICON_FILE") else None
|
|
||||||
GIT_REPO_URL = os.environ.get("GIT_REPO_URL", "")
|
DEFAULT_SETTINGS = {
|
||||||
USER_URL = os.environ.get("USER_URL", "")
|
"site_name": os.environ.get("SITE_NAME", "Event Audio"),
|
||||||
|
"site_subtitle": os.environ.get("SITE_SUBTITLE", "Audiostream-Aufzeichnungen"),
|
||||||
|
"page_title": os.environ.get("PAGE_TITLE", "Event Audio – Editor"),
|
||||||
|
"page_heading": os.environ.get("PAGE_HEADING", "Event Audio – Editor"),
|
||||||
|
"edit_page_title": os.environ.get("EDIT_PAGE_TITLE", "Event Audio – Aufnahme bearbeiten"),
|
||||||
|
"edit_page_heading": os.environ.get("EDIT_PAGE_HEADING", "Aufnahme bearbeiten"),
|
||||||
|
"git_repo_url": os.environ.get("GIT_REPO_URL", ""),
|
||||||
|
"user_url": os.environ.get("USER_URL", ""),
|
||||||
|
"editor_url": os.environ.get("EDITOR_URL", ""),
|
||||||
|
"logo_file": os.environ.get("LOGO_FILE", ""),
|
||||||
|
"favicon_file": os.environ.get("FAVICON_FILE", ""),
|
||||||
|
}
|
||||||
|
|
||||||
DEFAULT_THEME = {'background': '#111', 'surface': '#181818', 'surface_alt': '#222', 'surface_dark': '#0b0b0b', 'text': '#eee', 'text_secondary': '#ccc', 'text_muted': '#aaa', 'text_faint': '#777', 'border': '#333', 'border_subtle': '#2b2b2b', 'control': '#333', 'control_hover': '#444', 'primary': '#1769aa', 'primary_hover': '#1f7fc2', 'accent': '#ffcc33', 'success': '#173d24', 'success_text': '#b8f5c9', 'warning': '#6b4e00', 'danger': '#8b0000', 'danger_soft': '#4a2020', 'danger_soft_hover': '#6a2b2b', 'error': '#4a1b1b', 'error_text': '#ffd0d0', 'recording': '#b00000', 'recording_border': '#3a2222', 'selection': 'rgba(80,150,255,.16)', 'shadow': '#000', 'scroll_thumb': '#777', 'scroll_thumb_hover': '#999', 'scroll_thumb_active': '#aaa', 'live_surface': '#1d1111', 'table_header': '#252525', 'table_text': '#eee', 'footer': '#666', 'white': '#fff','surface_modal':'#202020','overlay':'rgba(0,0,0,.78)'}
|
DEFAULT_THEME = {'background': '#111', 'surface': '#181818', 'surface_alt': '#222', 'surface_dark': '#0b0b0b', 'text': '#eee', 'text_secondary': '#ccc', 'text_muted': '#aaa', 'text_faint': '#777', 'border': '#333', 'border_subtle': '#2b2b2b', 'control': '#333', 'control_hover': '#444', 'primary': '#1769aa', 'primary_hover': '#1f7fc2', 'accent': '#ffcc33', 'success': '#173d24', 'success_text': '#b8f5c9', 'warning': '#6b4e00', 'danger': '#8b0000', 'danger_soft': '#4a2020', 'danger_soft_hover': '#6a2b2b', 'error': '#4a1b1b', 'error_text': '#ffd0d0', 'recording': '#b00000', 'recording_border': '#3a2222', 'selection': 'rgba(80,150,255,.16)', 'shadow': '#000', 'scroll_thumb': '#777', 'scroll_thumb_hover': '#999', 'scroll_thumb_active': '#aaa', 'live_surface': '#1d1111', 'table_header': '#252525', 'table_text': '#eee', 'footer': '#666', 'white': '#fff','surface_modal':'#202020','overlay':'rgba(0,0,0,.78)'}
|
||||||
|
|
||||||
|
def load_settings():
|
||||||
|
"""Load UI/branding settings, falling back to environment defaults."""
|
||||||
|
settings = dict(DEFAULT_SETTINGS)
|
||||||
|
if SETTINGS_FILE.is_file():
|
||||||
|
try:
|
||||||
|
data = json.loads(SETTINGS_FILE.read_text(encoding="utf-8"))
|
||||||
|
if isinstance(data, dict):
|
||||||
|
for key in DEFAULT_SETTINGS:
|
||||||
|
if key in data and isinstance(data[key], str):
|
||||||
|
settings[key] = data[key]
|
||||||
|
if isinstance(data.get("colors"), dict):
|
||||||
|
settings["colors"] = dict(data["colors"])
|
||||||
|
except Exception as exc:
|
||||||
|
print(f"[branding] Einstellungen konnten nicht geladen werden: {exc}", flush=True)
|
||||||
|
return settings
|
||||||
|
|
||||||
|
|
||||||
def load_theme():
|
def load_theme():
|
||||||
"""Load semantic theme colors from JSON, falling back to built-in defaults."""
|
"""Load semantic theme colors from settings, then theme.json, then defaults."""
|
||||||
theme = dict(DEFAULT_THEME)
|
theme = dict(DEFAULT_THEME)
|
||||||
|
|
||||||
|
# Keep the existing theme.json as a backwards-compatible fallback.
|
||||||
if THEME_FILE.is_file():
|
if THEME_FILE.is_file():
|
||||||
try:
|
try:
|
||||||
data = json.loads(THEME_FILE.read_text(encoding="utf-8"))
|
data = json.loads(THEME_FILE.read_text(encoding="utf-8"))
|
||||||
@@ -56,29 +80,50 @@ def load_theme():
|
|||||||
theme[key] = value.strip()
|
theme[key] = value.strip()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
print(f"[branding] Theme konnte nicht geladen werden: {exc}", flush=True)
|
print(f"[branding] Theme konnte nicht geladen werden: {exc}", flush=True)
|
||||||
|
|
||||||
|
settings = load_settings()
|
||||||
|
colors = settings.get("colors") if isinstance(settings.get("colors"), dict) else {}
|
||||||
|
for key in theme:
|
||||||
|
value = colors.get(key)
|
||||||
|
if isinstance(value, str) and re.fullmatch(r"(?:#[0-9a-fA-F]{3,8}|rgba?\([^)]*\))", value.strip()):
|
||||||
|
theme[key] = value.strip()
|
||||||
return theme
|
return theme
|
||||||
|
|
||||||
|
|
||||||
def theme_css():
|
def theme_css():
|
||||||
theme = load_theme()
|
theme = load_theme()
|
||||||
return ":root {\n" + "\n".join(
|
return ":root {\n" + "\n".join(
|
||||||
f" --color-{key.replace('_', '-')}: {value};" for key, value in theme.items()
|
f" --color-{key.replace('_', '-')}: {value};" for key, value in theme.items()
|
||||||
) + "\n}"
|
) + "\n}"
|
||||||
|
|
||||||
|
|
||||||
def branding_context():
|
def branding_context():
|
||||||
|
settings = load_settings()
|
||||||
|
logo_file = Path(settings["logo_file"]) if settings.get("logo_file") else None
|
||||||
|
favicon_file = Path(settings["favicon_file"]) if settings.get("favicon_file") else None
|
||||||
return {
|
return {
|
||||||
"site_name": SITE_NAME,
|
"site_name": settings["site_name"],
|
||||||
"site_subtitle": SITE_SUBTITLE,
|
"site_subtitle": settings["site_subtitle"],
|
||||||
"page_title": PAGE_TITLE,
|
"page_title": settings["page_title"],
|
||||||
"page_heading": PAGE_HEADING,
|
"page_heading": settings["page_heading"],
|
||||||
"edit_page_title": EDIT_PAGE_TITLE,
|
"edit_page_title": settings["edit_page_title"],
|
||||||
"edit_page_heading": EDIT_PAGE_HEADING,
|
"edit_page_heading": settings["edit_page_heading"],
|
||||||
"theme_css": theme_css(),
|
"theme_css": theme_css(),
|
||||||
"logo_url": "/branding/logo" if LOGO_FILE and LOGO_FILE.is_file() else None,
|
"logo_url": "/branding/logo" if logo_file and logo_file.is_file() else None,
|
||||||
"favicon_url": "/branding/favicon" if FAVICON_FILE and FAVICON_FILE.is_file() else None,
|
"favicon_url": "/branding/favicon" if favicon_file and favicon_file.is_file() else None,
|
||||||
"git_repo_url": GIT_REPO_URL,
|
"git_repo_url": settings["git_repo_url"],
|
||||||
"user_url": USER_URL,
|
"user_url": settings["user_url"],
|
||||||
|
"editor_url": settings["editor_url"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def save_settings(data):
|
||||||
|
"""Atomically persist the editable page configuration."""
|
||||||
|
SETTINGS_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
temp = SETTINGS_FILE.with_name(SETTINGS_FILE.name + ".tmp")
|
||||||
|
temp.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||||
|
os.replace(temp, SETTINGS_FILE)
|
||||||
|
|
||||||
NORMALIZE = os.environ.get("NORMALIZE", "true").lower() not in ("0", "false", "no")
|
NORMALIZE = os.environ.get("NORMALIZE", "true").lower() not in ("0", "false", "no")
|
||||||
NORMALIZE_TARGET = os.environ.get("NORMALIZE_TARGET", "-16")
|
NORMALIZE_TARGET = os.environ.get("NORMALIZE_TARGET", "-16")
|
||||||
NORMALIZE_TRUE_PEAK = os.environ.get("NORMALIZE_TRUE_PEAK", "-1.5")
|
NORMALIZE_TRUE_PEAK = os.environ.get("NORMALIZE_TRUE_PEAK", "-1.5")
|
||||||
@@ -1367,6 +1412,7 @@ render();
|
|||||||
{% if user_url %}
|
{% if user_url %}
|
||||||
<a href="{{ user_url }}">Benutzerseite</a>
|
<a href="{{ user_url }}">Benutzerseite</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<a href="/settings">⚙ Einstellungen</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -1505,6 +1551,7 @@ th { color:var(--color-text-muted); font-weight:normal; }
|
|||||||
<div class="site-header">
|
<div class="site-header">
|
||||||
{% if logo_url %}<img class="site-logo" src="{{ logo_url }}" alt="{{ site_name }}">{% endif %}
|
{% if logo_url %}<img class="site-logo" src="{{ logo_url }}" alt="{{ site_name }}">{% endif %}
|
||||||
<h1>{{ page_heading }}</h1>
|
<h1>{{ page_heading }}</h1>
|
||||||
|
<a class="button" href="/settings">⚙ Einstellungen</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="subtitle">Fertige Aufnahmen aus {{ recordings_dir }}</div>
|
<div class="subtitle">Fertige Aufnahmen aus {{ recordings_dir }}</div>
|
||||||
|
|
||||||
@@ -1853,22 +1900,209 @@ async function deleteFile(kind, filename) {
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
SETTINGS_HTML = """
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Einstellungen – {{ site_name }}</title>
|
||||||
|
{% if favicon_url %}<link rel="icon" href="{{ favicon_url }}">{% endif %}
|
||||||
|
<style>
|
||||||
|
{{ theme_css|safe }}
|
||||||
|
* { box-sizing:border-box; }
|
||||||
|
body { margin:0; padding:24px; background:var(--color-background); color:var(--color-text); font-family:system-ui,-apple-system,sans-serif; }
|
||||||
|
.container { max-width:1100px; margin:0 auto; }
|
||||||
|
a { color:var(--color-text-secondary); }
|
||||||
|
.header { display:flex; align-items:center; gap:14px; margin-bottom:24px; flex-wrap:wrap; }
|
||||||
|
.header h1 { margin:0; flex:1; }
|
||||||
|
.logo { max-width:140px; max-height:48px; object-fit:contain; }
|
||||||
|
.back { text-decoration:none; }
|
||||||
|
.section { background:var(--color-surface); border:1px solid var(--color-border); border-radius:8px; padding:18px; margin-bottom:18px; }
|
||||||
|
.section h2 { margin:0 0 16px; font-size:20px; }
|
||||||
|
.grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:14px 18px; }
|
||||||
|
.field label { display:block; color:var(--color-text-muted); font-size:13px; margin-bottom:5px; }
|
||||||
|
.field input { width:100%; padding:9px 10px; background:var(--color-control); color:var(--color-text); border:1px solid var(--color-control-hover); border-radius:5px; font-size:15px; }
|
||||||
|
.field.full { grid-column:1/-1; }
|
||||||
|
.color-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px 18px; }
|
||||||
|
.color-field { display:grid; grid-template-columns:1fr 52px; gap:8px; align-items:end; }
|
||||||
|
.color-field .text-wrap { min-width:0; }
|
||||||
|
.color-field input[type=color] { width:52px; height:40px; padding:3px; background:var(--color-control); border:1px solid var(--color-control-hover); border-radius:5px; }
|
||||||
|
.actions { display:flex; gap:10px; flex-wrap:wrap; margin-top:20px; }
|
||||||
|
button { border:0; border-radius:5px; padding:10px 16px; background:var(--color-primary); color:var(--color-white); font-size:15px; cursor:pointer; }
|
||||||
|
button:hover { background:var(--color-primary-hover); }
|
||||||
|
.note { color:var(--color-text-muted); font-size:13px; margin-top:8px; }
|
||||||
|
.message { padding:10px 12px; border-radius:6px; margin-bottom:16px; background:var(--color-success); color:var(--color-success-text); }
|
||||||
|
@media(max-width:700px) { body { padding:12px; } .grid,.color-grid { grid-template-columns:1fr; } .field.full { grid-column:auto; } }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<a class="back" href="/">← Editor</a>
|
||||||
|
{% if logo_url %}<img class="logo" src="{{ logo_url }}" alt="{{ site_name }}">{% endif %}
|
||||||
|
<h1>Einstellungen</h1>
|
||||||
|
</div>
|
||||||
|
{% if saved %}<div class="message">Einstellungen wurden gespeichert.</div>{% endif %}
|
||||||
|
<form method="post" action="/settings">
|
||||||
|
<div class="section">
|
||||||
|
<h2>Seiten und Branding</h2>
|
||||||
|
<div class="grid">
|
||||||
|
<div class="field"><label for="site_name">Name</label><input id="site_name" name="site_name" value="{{ settings.site_name }}"></div>
|
||||||
|
<div class="field"><label for="site_subtitle">Untertitel</label><input id="site_subtitle" name="site_subtitle" value="{{ settings.site_subtitle }}"></div>
|
||||||
|
<div class="field"><label for="page_title">Seitentitel Benutzerseite</label><input id="page_title" name="page_title" value="{{ settings.page_title }}"></div>
|
||||||
|
<div class="field"><label for="page_heading">Überschrift Benutzerseite</label><input id="page_heading" name="page_heading" value="{{ settings.page_heading }}"></div>
|
||||||
|
<div class="field"><label for="edit_page_title">Seitentitel Editor</label><input id="edit_page_title" name="edit_page_title" value="{{ settings.edit_page_title }}"></div>
|
||||||
|
<div class="field"><label for="edit_page_heading">Überschrift Aufnahme-Editor</label><input id="edit_page_heading" name="edit_page_heading" value="{{ settings.edit_page_heading }}"></div>
|
||||||
|
<div class="field"><label for="git_repo_url">Git-Repository URL</label><input id="git_repo_url" name="git_repo_url" value="{{ settings.git_repo_url }}" placeholder="https://…"></div>
|
||||||
|
<div class="field"><label for="user_url">Benutzerseite URL</label><input id="user_url" name="user_url" value="{{ settings.user_url }}" placeholder="https://…"></div>
|
||||||
|
<div class="field"><label for="editor_url">Editor URL</label><input id="editor_url" name="editor_url" value="{{ settings.editor_url }}" placeholder="https://…"></div>
|
||||||
|
<div class="field"><label for="logo_file">Logo-Datei</label><input id="logo_file" name="logo_file" value="{{ settings.logo_file }}" placeholder="/config/logo.svg"></div>
|
||||||
|
<div class="field"><label for="favicon_file">Favicon-Datei</label><input id="favicon_file" name="favicon_file" value="{{ settings.favicon_file }}" placeholder="/config/favicon.svg"></div>
|
||||||
|
</div>
|
||||||
|
<div class="note">Logo und Favicon werden nicht hochgeladen, sondern als Pfad innerhalb des Containers angegeben.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>Farbschema</h2>
|
||||||
|
<div class="color-grid">
|
||||||
|
{% for key, label, value, picker in color_fields %}
|
||||||
|
<div class="color-field">
|
||||||
|
<div class="text-wrap"><label for="color_{{ key }}">{{ label }}</label><input id="color_{{ key }}" name="color_{{ key }}" value="{{ value }}"></div>
|
||||||
|
{% if picker %}<input type="color" value="{{ picker }}" data-target="color_{{ key }}" aria-label="{{ label }} Farbe">{% else %}<span></span>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="note">Erlaubt sind Hex-Farben sowie rgba()/rgb()-Werte. Änderungen werden nach dem Speichern auf Editor und Benutzerseite verwendet.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="actions"><button type="submit">Einstellungen speichern</button><a class="back" href="/">Abbrechen</a></div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.querySelectorAll('input[type=color][data-target]').forEach(picker => {
|
||||||
|
picker.addEventListener('input', () => {
|
||||||
|
const target = document.getElementById(picker.dataset.target);
|
||||||
|
if (target) target.value = picker.value;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Routes
|
# Routes
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
COLOR_LABELS = {
|
||||||
|
"background": "Hintergrund",
|
||||||
|
"surface": "Fläche",
|
||||||
|
"surface_alt": "Alternative Fläche",
|
||||||
|
"surface_dark": "Dunkle Fläche",
|
||||||
|
"text": "Text",
|
||||||
|
"text_secondary": "Sekundärtext",
|
||||||
|
"text_muted": "Gedämpfter Text",
|
||||||
|
"text_faint": "Schwacher Text",
|
||||||
|
"border": "Rahmen",
|
||||||
|
"border_subtle": "Feiner Rahmen",
|
||||||
|
"control": "Steuerelement",
|
||||||
|
"control_hover": "Steuerelement Hover",
|
||||||
|
"primary": "Primärfarbe",
|
||||||
|
"primary_hover": "Primärfarbe Hover",
|
||||||
|
"accent": "Akzent",
|
||||||
|
"success": "Erfolg",
|
||||||
|
"success_text": "Erfolgstext",
|
||||||
|
"warning": "Warnung",
|
||||||
|
"danger": "Gefahr",
|
||||||
|
"danger_soft": "Gefahr Fläche",
|
||||||
|
"danger_soft_hover": "Gefahr Fläche Hover",
|
||||||
|
"error": "Fehler",
|
||||||
|
"error_text": "Fehlertext",
|
||||||
|
"recording": "Aufnahme",
|
||||||
|
"recording_border": "Aufnahme Rahmen",
|
||||||
|
"selection": "Auswahl",
|
||||||
|
"shadow": "Schatten",
|
||||||
|
"scroll_thumb": "Scrollbalken",
|
||||||
|
"scroll_thumb_hover": "Scrollbalken Hover",
|
||||||
|
"scroll_thumb_active": "Scrollbalken aktiv",
|
||||||
|
"live_surface": "Live-Fläche",
|
||||||
|
"table_header": "Tabellenkopf",
|
||||||
|
"table_text": "Tabellentext",
|
||||||
|
"footer": "Footer",
|
||||||
|
"white": "Weiß",
|
||||||
|
"surface_modal": "Dialogfläche",
|
||||||
|
"overlay": "Overlay",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def build_color_fields(colors):
|
||||||
|
fields = []
|
||||||
|
for key in DEFAULT_THEME:
|
||||||
|
value = colors.get(key, DEFAULT_THEME[key]) if isinstance(colors, dict) else DEFAULT_THEME[key]
|
||||||
|
picker = value if re.fullmatch(r"#[0-9a-fA-F]{6}", value) else None
|
||||||
|
fields.append((key, COLOR_LABELS.get(key, key), value, picker))
|
||||||
|
return fields
|
||||||
|
|
||||||
|
@app.route("/settings", methods=["GET", "POST"])
|
||||||
|
def settings():
|
||||||
|
settings = load_settings()
|
||||||
|
saved = False
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
for key in DEFAULT_SETTINGS:
|
||||||
|
settings[key] = request.form.get(key, "").strip()
|
||||||
|
|
||||||
|
colors = {}
|
||||||
|
current_theme = load_theme()
|
||||||
|
for key in DEFAULT_THEME:
|
||||||
|
value = request.form.get(f"color_{key}", current_theme[key]).strip()
|
||||||
|
if re.fullmatch(r"(?:#[0-9a-fA-F]{3,8}|rgba?\([^)]*\))", value):
|
||||||
|
colors[key] = value
|
||||||
|
else:
|
||||||
|
colors[key] = current_theme[key]
|
||||||
|
settings["colors"] = colors
|
||||||
|
|
||||||
|
try:
|
||||||
|
save_settings(settings)
|
||||||
|
except OSError as exc:
|
||||||
|
return render_template_string(
|
||||||
|
SETTINGS_HTML,
|
||||||
|
settings=settings,
|
||||||
|
saved=False,
|
||||||
|
color_fields=build_color_fields(colors),
|
||||||
|
**branding_context(),
|
||||||
|
error=f"Einstellungen konnten nicht gespeichert werden: {exc}",
|
||||||
|
), 500
|
||||||
|
saved = True
|
||||||
|
|
||||||
|
colors = settings.get("colors") if isinstance(settings.get("colors"), dict) else load_theme()
|
||||||
|
return render_template_string(
|
||||||
|
SETTINGS_HTML,
|
||||||
|
settings=settings,
|
||||||
|
saved=saved,
|
||||||
|
color_fields=build_color_fields(colors),
|
||||||
|
**branding_context(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/branding/logo")
|
@app.route("/branding/logo")
|
||||||
def branding_logo():
|
def branding_logo():
|
||||||
if LOGO_FILE is None or not LOGO_FILE.is_file():
|
settings = load_settings()
|
||||||
|
path = Path(settings["logo_file"]) if settings.get("logo_file") else None
|
||||||
|
if path is None or not path.is_file():
|
||||||
abort(404)
|
abort(404)
|
||||||
return send_file(LOGO_FILE)
|
return send_file(path)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/branding/favicon")
|
@app.route("/branding/favicon")
|
||||||
def branding_favicon():
|
def branding_favicon():
|
||||||
if FAVICON_FILE is None or not FAVICON_FILE.is_file():
|
settings = load_settings()
|
||||||
|
path = Path(settings["favicon_file"]) if settings.get("favicon_file") else None
|
||||||
|
if path is None or not path.is_file():
|
||||||
abort(404)
|
abort(404)
|
||||||
return send_file(FAVICON_FILE)
|
return send_file(path)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
|
|||||||
Reference in New Issue
Block a user