This commit is contained in:
Josh Hawkins
2026-08-22 11:51:08 -05:00
parent d02811d2b4
commit b8cfe68d26
11 changed files with 36 additions and 74 deletions
+3 -5
View File
@@ -25,8 +25,8 @@ export default function SetupCamera({ onNext, onBack }: SetupCameraProps) {
const handleClose = useCallback(() => {
setShowWizard(false);
// Delay to let CameraWizardDialog's config save and go2rtc setup complete,
// then force a fresh fetch from the server (bypassing SWR cache)
// the dialog's config save and go2rtc setup need a moment to land, and
// the SWR cache would serve the pre-save config
setTimeout(() => {
fetch(`${window.baseUrl || ""}api/config`)
.then((res) => res.json())
@@ -37,9 +37,7 @@ export default function SetupCamera({ onNext, onBack }: SetupCameraProps) {
setAddedCameras(cameraNames);
}
})
.catch(() => {
// Fetch failed, stay on this step
});
.catch(() => {});
}, 1000);
}, [mutateConfig, addedCameras]);
+2 -7
View File
@@ -87,7 +87,6 @@ export default function SetupComplete({
const handleFinish = useCallback(async () => {
setFinishing(true);
try {
// Mark setup as complete
await axios.put("config/set", {
config_data: {
onboarding: { setup_complete: true },
@@ -95,8 +94,7 @@ export default function SetupComplete({
requires_restart: 0,
});
// camera adds were applied live, so with no pending requires_restart
// saves the wizard can hand off without restarting
// camera adds were applied live, so nothing is waiting on a restart
if (!restartRequired) {
window.location.href = window.baseUrl || "/";
return;
@@ -104,10 +102,8 @@ export default function SetupComplete({
setRestarting(true);
// Trigger restart
await axios.post("restart");
// Poll for server availability with max retries
let retries = 0;
const maxRetries = 60; // 2 minutes max
pollRef.current = setInterval(async () => {
@@ -116,7 +112,6 @@ export default function SetupComplete({
if (pollRef.current) {
clearInterval(pollRef.current);
}
// Give up polling, redirect anyway
window.location.href = window.baseUrl || "/";
return;
}
@@ -129,7 +124,7 @@ export default function SetupComplete({
window.location.href = window.baseUrl || "/";
}
} catch {
// Server not ready yet
// not back yet
}
}, 2000);
} catch {
+3 -4
View File
@@ -20,8 +20,8 @@ import { LuExternalLink } from "react-icons/lu";
import { toast } from "sonner";
import useSWR from "swr";
// detectors that ship no default model; configuring them without one leaves
// the detector unable to start
// these ship no default model, so configuring one without a model leaves the
// detector unable to start
const MODEL_REQUIRED_DETECTORS = ["onnx", "tensorrt"];
const CPU_FALLBACK: DetectionHardware[] = [
@@ -64,7 +64,7 @@ export default function SetupDetector({
});
const plusEnabled = Boolean(config?.plus?.enabled);
// the cpu is always probed, so an empty or failed probe leaves a fallback
// the cpu is always probed, so an empty list means the probe failed
const options = useMemo(
() => (hardware && hardware.length > 0 ? hardware : CPU_FALLBACK),
[hardware],
@@ -121,7 +121,6 @@ export default function SetupDetector({
const handleSave = useCallback(async () => {
if (needsModel && !plusModelId) {
// no model to run on this hardware yet, so leave the config untouched
onSkip(selected.key);
return;
}
+7 -15
View File
@@ -11,7 +11,6 @@ import useSWR from "swr";
const AUTO = "auto";
const NONE = "none";
// the codec key of a preset that decodes anything
const ANY_CODEC = "any";
// ffprobe names h265 streams hevc
@@ -24,11 +23,9 @@ function normalizeCodec(codec: string): string {
type SetupHwAccelProps = {
detectorHardwareKey?: string;
// detect stream codec of each camera added in the wizard, keyed by camera
// name. hwaccel only applies to the detect stream.
// camera name -> detect stream codec, the only stream hwaccel applies to
detectCodecs: Record<string, string>;
// saved reports whether a config write happened, so the wizard knows
// whether finishing requires a restart
// saved tells the wizard whether finishing needs a restart
onNext: (saved: boolean) => void;
onBack: () => void;
onSkip: () => void;
@@ -99,14 +96,11 @@ export default function SetupHwAccel({
return { ffmpeg: { hwaccel_args: shared } };
}
// this family decodes one codec per preset, so each camera needs the
// preset matching its own detect stream
const perCamera = cameraCodecs
.map((entry) => ({ ...entry, preset: family.presets[entry.codec] }))
.filter((entry) => entry.preset);
if (perCamera.length === 0) {
// no camera to match, so fall back to the family's first preset
const fallback = Object.values(family.presets)[0];
return fallback ? { ffmpeg: { hwaccel_args: fallback } } : null;
}
@@ -114,12 +108,11 @@ export default function SetupHwAccel({
const presets = new Set(perCamera.map((entry) => entry.preset));
if (presets.size === 1 && perCamera.length === cameraCodecs.length) {
// every camera wants the same preset, so one global value says it
return { ffmpeg: { hwaccel_args: [...presets][0] } };
}
// the global stays on auto, so cameras added later still get resolved
// at startup rather than inheriting one camera's codec
// the global stays on auto so cameras added later resolve at startup
// instead of inheriting one camera's codec
return {
cameras: Object.fromEntries(
perCamera.map((entry) => [
@@ -137,13 +130,12 @@ export default function SetupHwAccel({
const configData =
selected === NONE
? // an empty string would make config/set delete the key (reviving the
// "auto" default), so an explicit no-hwaccel is an empty list
? // an empty string would make config/set delete the key, reviving
// the "auto" default
{ ffmpeg: { hwaccel_args: [] } }
: configFor(families.find((family) => family.key === key));
// Auto with nothing derived writes nothing: the config default of "auto"
// stays in place and the backend decides at startup
// nothing to write leaves the config default of "auto" in place
if (!configData) {
onNext(false);
return;
+2 -5
View File
@@ -35,7 +35,6 @@ export default function SetupRecording({
const { data: stats } = useSWR("stats", { revalidateOnFocus: false });
// Calculate storage estimate
const storageInfo = stats?.service?.storage?.["/tmp/frigate/recordings"];
const freeGb = storageInfo ? Math.round(storageInfo.free / 1024) : null;
const cameraCount = cameraNames.length;
@@ -53,8 +52,7 @@ export default function SetupRecording({
if (enabled) {
record.alerts = { retain: { days: retentionDays } };
record.detections = { retain: { days: retentionDays } };
// continuous keeps every segment, so it is the one that needs turning
// on. Its default of 0 already means "only what was detected".
// written even when off, so switching modes back turns it off again
record.continuous = { days: mode === CONTINUOUS ? retentionDays : 0 };
}
@@ -140,8 +138,7 @@ export default function SetupRecording({
min={1}
max={365}
value={retentionDays}
// the spinner arrows are noise at this size, and the field is
// still typeable and arrow-key steppable without them
// drop the spinner arrows; typing and arrow keys still work
className="[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
onChange={(e) =>
setRetentionDays(Math.max(1, parseInt(e.target.value) || 1))