fix one machine only
This commit is contained in:
+124
-4
@@ -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">Có thể đổi record trong `mediamtx.yml` và 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>
|
||||
|
||||
@@ -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
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user