fix some bug in frontend

This commit is contained in:
2026-04-30 22:44:06 +07:00
parent ae583a72de
commit df3a3cd188
3 changed files with 100 additions and 7 deletions
+38 -5
View File
@@ -57,6 +57,32 @@ def _clean_text(value: Optional[str]) -> Optional[str]:
return cleaned or None return cleaned or None
def _sanitize_cfg_fields(cfg: AppConfig) -> bool:
changed = False
clean_api = _clean_text(cfg.mediamtx_api_url)
clean_webrtc = _clean_text(cfg.mediamtx_webrtc_url)
clean_user = _clean_text(cfg.mediamtx_api_user)
clean_pass = _clean_text(cfg.mediamtx_api_pass)
clean_recordings = _clean_text(cfg.recordings_dir)
if clean_api and clean_api != cfg.mediamtx_api_url:
cfg.mediamtx_api_url = clean_api
changed = True
if clean_webrtc and clean_webrtc != cfg.mediamtx_webrtc_url:
cfg.mediamtx_webrtc_url = clean_webrtc
changed = True
if clean_user != cfg.mediamtx_api_user:
cfg.mediamtx_api_user = clean_user
changed = True
if clean_pass != cfg.mediamtx_api_pass:
cfg.mediamtx_api_pass = clean_pass
changed = True
if clean_recordings and clean_recordings != cfg.recordings_dir:
cfg.recordings_dir = clean_recordings
changed = True
return changed
def _load_mediamtx_yml() -> dict: def _load_mediamtx_yml() -> dict:
if not MEDIAMTX_YML_PATH.exists(): if not MEDIAMTX_YML_PATH.exists():
raise HTTPException(status_code=500, detail="mediamtx_yml_not_found") raise HTTPException(status_code=500, detail="mediamtx_yml_not_found")
@@ -96,6 +122,7 @@ def _build_mediamtx_view(data: dict, cfg: AppConfig) -> MediaMTXConfigView:
async def _sync_app_config_from_mediamtx() -> AppConfig: async def _sync_app_config_from_mediamtx() -> AppConfig:
cfg = await store.load() cfg = await store.load()
_sanitize_cfg_fields(cfg)
data = _load_mediamtx_yml() data = _load_mediamtx_yml()
cfg.cameras = [ cfg.cameras = [
Camera(name=c.name, rtsp_url=c.rtsp_url) Camera(name=c.name, rtsp_url=c.rtsp_url)
@@ -133,6 +160,8 @@ def _restart_mediamtx() -> dict:
async def _apply_recording(enabled: bool) -> None: async def _apply_recording(enabled: bool) -> None:
cfg = await store.load() cfg = await store.load()
if _sanitize_cfg_fields(cfg):
await store.save(cfg)
try: try:
paths = await MediaMTXClient( paths = await MediaMTXClient(
api_url=cfg.mediamtx_api_url, api_url=cfg.mediamtx_api_url,
@@ -140,8 +169,9 @@ async def _apply_recording(enabled: bool) -> None:
password=cfg.mediamtx_api_pass, password=cfg.mediamtx_api_pass,
).list_paths_status() ).list_paths_status()
names = [it.get("name") for it in (paths.get("items") or []) if isinstance(it, dict) and it.get("name")] names = [it.get("name") for it in (paths.get("items") or []) if isinstance(it, dict) and it.get("name")]
except Exception: except httpx.HTTPError as e:
names = [c.name for c in cfg.cameras] logger.warning("scheduler_skip_apply_recording: mediamtx_unreachable (%s)", type(e).__name__)
return
if not names: if not names:
return return
@@ -150,7 +180,10 @@ async def _apply_recording(enabled: bool) -> None:
username=cfg.mediamtx_api_user, username=cfg.mediamtx_api_user,
password=cfg.mediamtx_api_pass, password=cfg.mediamtx_api_pass,
) )
await client.set_recording_bulk(names, enabled) try:
await client.set_recording_bulk(names, enabled)
except httpx.HTTPError as e:
logger.warning("scheduler_apply_recording_failed: %s", type(e).__name__)
scheduler = Scheduler(apply=_apply_recording) scheduler = Scheduler(apply=_apply_recording)
@@ -169,9 +202,9 @@ async def _scheduler_loop() -> None:
"Set mediamtx_api_user/mediamtx_api_pass in api/data/config.json" "Set mediamtx_api_user/mediamtx_api_pass in api/data/config.json"
) )
else: else:
logger.exception("scheduler_tick_http_status_%s", code) logger.warning("scheduler_tick_http_status_%s", code)
except httpx.HTTPError: except httpx.HTTPError:
logger.exception("scheduler_tick_http_error") logger.warning("scheduler_tick_http_error")
except Exception: except Exception:
logger.exception("scheduler_tick_failed") logger.exception("scheduler_tick_failed")
finally: finally:
+1 -1
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Trae Project</title> <title>IPCam OrangePi Dashboard</title>
<script type="module"> <script type="module">
if (import.meta.hot?.on) { if (import.meta.hot?.on) {
import.meta.hot.on('vite:error', (error) => { import.meta.hot.on('vite:error', (error) => {
+61 -1
View File
@@ -11,4 +11,64 @@
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.light body {
background-color: rgb(244 244 245);
color: rgb(24 24 27);
}
/* Light theme overrides for existing zinc-based utility classes in pages/cards/forms. */
.light .border-zinc-800 {
border-color: rgb(212 212 216);
}
.light .border-zinc-700 {
border-color: rgb(161 161 170);
}
.light .bg-zinc-900\/20,
.light .bg-zinc-900\/30 {
background-color: rgb(255 255 255 / 0.82);
}
.light .bg-zinc-950 {
background-color: rgb(255 255 255);
}
.light .bg-zinc-950\/10 {
background-color: rgb(244 244 245 / 0.72);
}
.light .bg-zinc-950\/30,
.light .bg-zinc-950\/40 {
background-color: rgb(228 228 231 / 0.78);
}
.light .hover\:bg-zinc-900:hover {
background-color: rgb(228 228 231);
}
.light .hover\:bg-zinc-950\/30:hover {
background-color: rgb(228 228 231 / 0.9);
}
.light .text-zinc-100,
.light .text-zinc-200 {
color: rgb(24 24 27);
}
.light .text-zinc-300,
.light .text-zinc-400 {
color: rgb(82 82 91);
}
.light .text-zinc-500 {
color: rgb(113 113 122);
}
.light .from-zinc-950\/80 {
--tw-gradient-from: rgb(255 255 255 / 0.88) var(--tw-gradient-from-position);
--tw-gradient-to: rgb(255 255 255 / 0) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}