feat(v1): settings camera connection test and preview

Settings tab gains a camera group (host/port/channel/user/masked password),
a background connection-test worker that probes one frame off the GUI
thread and shows it in a preview, and a save button that writes the
structured source to the untracked config.local.json. 60 tests pass;
Qt shell compiles and needs a Windows smoke.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ila
2026-07-21 22:44:09 +08:00
co-authored by Claude Opus 4.8
parent edf8a9a94e
commit 2603a10fb1
8 changed files with 218 additions and 11 deletions
+32
View File
@@ -114,6 +114,38 @@ def _resolve_path(config_path: Path, value: Any, field_name: str) -> Path:
return (config_path.parent / raw_path).resolve()
def write_local_camera_source(
config_path: Path,
source_id: str,
host: str,
port: int,
channel: str,
username: str,
password: str,
mode: str = "stream",
) -> Path:
"""Persist a structured camera source into an untracked local config file.
Only ever call this on ``config.local.json`` (git-ignored). Credentials are
written in plaintext by design of the chosen policy; the public example must
never receive them.
"""
path = Path(config_path)
data = json.loads(path.read_text(encoding="utf-8")) if path.exists() else {}
data["source"] = {
"id": str(source_id),
"host": str(host),
"port": int(port),
"channel": str(channel),
"username": str(username),
"password": str(password),
"mode": mode,
}
path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
return path
def load_config(path: Path) -> AppConfig:
"""Load one public/local configuration pair without persisting credentials.