fix one machine only

This commit is contained in:
2026-04-30 21:29:11 +07:00
parent 5e1f529ed7
commit 176e6bdb8f
8 changed files with 185 additions and 149 deletions
+124 -4
View File
@@ -2,7 +2,7 @@ import { Plus, Trash2 } from "lucide-react";
import { useEffect, useMemo, useState } from "react";
import type { FormEvent } from "react";
import { useConfigStore } from "@/stores/configStore";
import type { MediaMtxConfigView } from "@/types/api";
import type { AppConfigUpdate, MediaMtxConfigView } from "@/types/api";
import { apiJson } from "@/utils/api";
export default function Settings() {
@@ -24,6 +24,12 @@ export default function Settings() {
const [weekdaysFrom, setWeekdaysFrom] = useState("18:00");
const [weekdaysTo, setWeekdaysTo] = useState("08:00");
const [weekendAllDay, setWeekendAllDay] = useState(true);
const [mediamtxApiUrl, setMediamtxApiUrl] = useState("http://127.0.0.1:9997");
const [mediamtxWebrtcUrl, setMediamtxWebrtcUrl] = useState("http://127.0.0.1:8889");
const [mediamtxApiUser, setMediamtxApiUser] = useState("");
const [mediamtxApiPass, setMediamtxApiPass] = useState("");
const [recordingsDir, setRecordingsDir] = useState("");
const [apiPort, setApiPort] = useState("8008");
const loadMtx = async () => {
setMtxBusy(true);
@@ -55,6 +61,16 @@ export default function Settings() {
setWeekendAllDay(schedule.weekend_all_day);
}, [schedule]);
useEffect(() => {
if (!config) return;
setMediamtxApiUrl(config.mediamtx_api_url);
setMediamtxWebrtcUrl(config.mediamtx_webrtc_url);
setMediamtxApiUser(config.mediamtx_api_user ?? "");
setMediamtxApiPass(config.mediamtx_api_pass ?? "");
setRecordingsDir(config.recordings_dir);
setApiPort(String(config.api_port ?? 8008));
}, [config]);
const canAdd = useMemo(() => rtspUrl.trim().length > 0, [rtspUrl]);
const onAdd = async (e: FormEvent) => {
@@ -90,6 +106,38 @@ export default function Settings() {
});
};
const onSaveBasicConfig = async () => {
setMtxBusy(true);
setMtxError(null);
setRestartMsg(null);
const payload: AppConfigUpdate = {
mediamtx_api_url: mediamtxApiUrl.trim(),
mediamtx_webrtc_url: mediamtxWebrtcUrl.trim(),
mediamtx_api_user: mediamtxApiUser.trim() || null,
mediamtx_api_pass: mediamtxApiPass.trim() || null,
recordings_dir: recordingsDir.trim(),
api_port: Number(apiPort) || 8008,
};
try {
await apiJson("/config/basic", {
method: "POST",
body: JSON.stringify(payload),
});
await load();
await loadMtx();
setRestartMsg("Lưu config.json thành công");
} catch (e) {
if (typeof e === "object" && e && "status" in e) {
const ex = e as { status: number; bodyText?: string };
setMtxError(`http_${ex.status}${ex.bodyText ? `: ${ex.bodyText}` : ""}`);
} else {
setMtxError("failed_to_save_config");
}
} finally {
setMtxBusy(false);
}
};
const onDelete = async (name: string) => {
setMtxBusy(true);
setMtxError(null);
@@ -232,9 +280,81 @@ export default function Settings() {
<div className="rounded-lg border border-zinc-800 bg-zinc-900/20 p-4">
<div className="text-xs font-semibold text-zinc-200">Recording & Scheduler</div>
<div className="mt-1 text-xs text-zinc-400"> thể đi record trong `mediamtx.yml` scheduler backend</div>
<div className="mt-1 text-xs text-zinc-400">Camera lấy từ `mediamtx.yml`, các URL/credentials lấy từ `config.json`</div>
<div className="mt-4 space-y-3">
<div className="grid gap-2">
<div>
<label className="text-xs text-zinc-400">MediaMTX API URL</label>
<input
value={mediamtxApiUrl}
onChange={(e) => setMediamtxApiUrl(e.target.value)}
placeholder="http://127.0.0.1:9997"
className="mt-1 h-9 w-full rounded-md border border-zinc-800 bg-zinc-950 px-3 text-sm text-zinc-100"
/>
</div>
<div>
<label className="text-xs text-zinc-400">MediaMTX WebRTC URL</label>
<input
value={mediamtxWebrtcUrl}
onChange={(e) => setMediamtxWebrtcUrl(e.target.value)}
placeholder="http://127.0.0.1:8889"
className="mt-1 h-9 w-full rounded-md border border-zinc-800 bg-zinc-950 px-3 text-sm text-zinc-100"
/>
</div>
<div className="grid gap-2 md:grid-cols-2">
<div>
<label className="text-xs text-zinc-400">MediaMTX API User</label>
<input
value={mediamtxApiUser}
onChange={(e) => setMediamtxApiUser(e.target.value)}
placeholder="dashboard"
className="mt-1 h-9 w-full rounded-md border border-zinc-800 bg-zinc-950 px-3 text-sm text-zinc-100"
/>
</div>
<div>
<label className="text-xs text-zinc-400">MediaMTX API Pass</label>
<input
type="password"
value={mediamtxApiPass}
onChange={(e) => setMediamtxApiPass(e.target.value)}
placeholder="password"
className="mt-1 h-9 w-full rounded-md border border-zinc-800 bg-zinc-950 px-3 text-sm text-zinc-100"
/>
</div>
</div>
<div className="grid gap-2 md:grid-cols-2">
<div>
<label className="text-xs text-zinc-400">Recordings Dir</label>
<input
value={recordingsDir}
onChange={(e) => setRecordingsDir(e.target.value)}
placeholder="/mnt/ssd/IPCam_OrangePi_Dashboard/mediamtx/recordings"
className="mt-1 h-9 w-full rounded-md border border-zinc-800 bg-zinc-950 px-3 text-sm text-zinc-100"
/>
</div>
<div>
<label className="text-xs text-zinc-400">API Port</label>
<input
type="number"
min={1}
max={65535}
value={apiPort}
onChange={(e) => setApiPort(e.target.value)}
className="mt-1 h-9 w-full rounded-md border border-zinc-800 bg-zinc-950 px-3 text-sm text-zinc-100"
/>
</div>
</div>
<button
type="button"
onClick={() => void onSaveBasicConfig()}
disabled={mtxBusy}
className="inline-flex items-center gap-2 rounded-md border border-zinc-700 bg-zinc-950/40 px-3 py-2 text-sm text-zinc-200 transition hover:bg-zinc-900 disabled:cursor-not-allowed disabled:opacity-60"
>
Save Config
</button>
</div>
<label className="flex items-center gap-2 text-sm text-zinc-200">
<input
type="checkbox"
@@ -304,9 +424,9 @@ export default function Settings() {
</button>
<div className="rounded-md border border-zinc-800 bg-zinc-950/10 px-3 py-2 text-xs text-zinc-400">
MediaMTX API: <span className="text-zinc-200">{mtx?.api_url ?? config?.mediamtx_api_url ?? "-"}</span>
MediaMTX API: <span className="text-zinc-200">{config?.mediamtx_api_url ?? "-"}</span>
<br />
WebRTC: <span className="text-zinc-200">{mtx?.webrtc_url ?? config?.mediamtx_webrtc_url ?? "-"}</span>
WebRTC: <span className="text-zinc-200">{config?.mediamtx_webrtc_url ?? "-"}</span>
<br />
Recordings dir: <span className="text-zinc-200">{config?.recordings_dir ?? "-"}</span>
</div>
+9
View File
@@ -21,6 +21,15 @@ export type AppConfig = {
schedule: Schedule;
};
export type AppConfigUpdate = {
mediamtx_api_url: string;
mediamtx_webrtc_url: string;
mediamtx_api_user?: string | null;
mediamtx_api_pass?: string | null;
recordings_dir: string;
api_port: number;
};
export type RecordingItem = {
camera: string;
filename: string;
+1 -2
View File
@@ -44,7 +44,6 @@ function normalizeLoopbackHost(rawUrl: string): string {
}
export function getMediamtxWebrtcBaseUrl(configUrl?: string) {
const env = import.meta.env.VITE_MEDIAMTX_WEBRTC_URL as string | undefined;
return normalizeLoopbackHost(env ?? configUrl ?? "http://localhost:8889");
return normalizeLoopbackHost(configUrl ?? "http://localhost:8889");
}