Files
patrickandClaude Sonnet 5.5 347f55cf7c
test / pytest (3.11) (push) Canceled after 0s
test / pytest (3.13) (push) Canceled after 0s
feat: Phase 6c baseline, ack, host alarms, outage notice, send limit, privacy mode, UI link
Includes atomic migrations, per-rule reminder semantics, first-run helper script
and the note on the undecided UI chat idea.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-06 03:06:23 +01:00

110 lines
3.2 KiB
Python

import re
from dataclasses import dataclass
from typing import Awaitable, Callable
from fastapi import HTTPException
from ..constants import FLAG_OFF, FLAG_ON
URL_RE = re.compile(r"^https?://[^\s/]+")
@dataclass(frozen=True)
class Field:
key: str
label: str
kind: str = "text"
required: bool = False
secret: bool = False
placeholder: str = ""
default: str = ""
choices: tuple[tuple[str, str], ...] = ()
hint: str = ""
transform: Callable[[str], str] | None = None
@property
def name(self) -> str:
return self.key.replace(".", "_")
@dataclass(frozen=True)
class Action:
label: str
path: str
@dataclass(frozen=True)
class Section:
slug: str
title: str
fields: tuple[Field, ...]
validate: Callable[[dict], list[str]]
test: Callable[[dict], Awaitable[str]] | None = None
optional: bool = False
enabled_key: str | None = None
actions: tuple[Action, ...] = ()
context_keys: tuple[str, ...] = ()
notice: str = ""
extra_template: str = ""
extra_context: Callable[[object], dict] | None = None
def section_done(store, section: Section) -> bool:
if section.enabled_key:
return store.get(section.enabled_key) == FLAG_ON
return all(store.get(f.key) for f in section.fields if f.required)
def checkbox_default(field: Field) -> str:
return field.default or FLAG_ON
def collect(form, section: Section) -> dict[str, str | None]:
submitted: dict[str, str | None] = {}
for field in section.fields:
if field.kind == "checkbox":
submitted[field.key] = FLAG_ON if form.get(field.name) else FLAG_OFF
continue
raw = str(form.get(field.name, "")).strip()
if field.transform is not None:
raw = field.transform(raw)
submitted[field.key] = (raw or None) if field.secret else raw
return submitted
def effective(store, submitted: dict[str, str | None]) -> dict[str, str]:
return {key: (value if value is not None else store.get(key) or "") for key, value in submitted.items()}
def check(section: Section, values: dict[str, str]) -> list[str]:
missing = [f"{f.label} fehlt." for f in section.fields if f.required and not values.get(f.key)]
return missing or section.validate(values)
def form_values(section: Section, values: dict[str, str]) -> dict:
shown = {}
for field in section.fields:
if field.secret:
continue
if field.kind == "checkbox":
shown[field.name] = values.get(field.key, checkbox_default(field)) == FLAG_ON
else:
shown[field.name] = values.get(field.key, field.default)
return shown
def stored_values(store, section: Section) -> dict[str, str]:
return {f.key: store.get(f.key, checkbox_default(f) if f.kind == "checkbox" else f.default) or ""
for f in section.fields if not f.secret}
def secret_flags(store, section: Section) -> dict[str, bool]:
return {f.name: store.is_set(f.key) for f in section.fields if f.secret}
def find_section(sections: dict[str, Section], slug: str) -> Section:
section = sections.get(slug)
if section is None:
raise HTTPException(404, "Unbekannter Bereich")
return section