Miscellaneous fixes (#23648)
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions

* sort preview cameras in history by ui order

* sort cameras by UI order in various components for consistent display

* add no recordings faq

* fix link

* recording cache faq

* add link

* improve anchor naming in object detector docs

rather than #configuration-1, #configuration-2, etc

* use yaml instead of json for object detector docs

* fix anchor
This commit is contained in:
Josh Hawkins
2026-07-07 10:56:43 -06:00
committed by GitHub
parent 4ee12e6237
commit c99d6b0dcf
23 changed files with 1689 additions and 518 deletions
@@ -208,7 +208,12 @@ export default function CameraManagementView({
!config.cameras[camera].enabled_in_config &&
!isReplayCamera(camera),
)
.sort();
.sort((a, b) => {
const orderA = config.cameras[a].ui?.order ?? 0;
const orderB = config.cameras[b].ui?.order ?? 0;
if (orderA !== orderB) return orderA - orderB;
return a.localeCompare(b);
});
}
return [];
}, [config]);
@@ -217,7 +222,12 @@ export default function CameraManagementView({
if (config) {
return Object.keys(config.cameras)
.filter((camera) => !isReplayCamera(camera))
.sort();
.sort((a, b) => {
const orderA = config.cameras[a].ui?.order ?? 0;
const orderB = config.cameras[b].ui?.order ?? 0;
if (orderA !== orderB) return orderA - orderB;
return a.localeCompare(b);
});
}
return [];
}, [config]);
@@ -140,6 +140,7 @@ export default function FrigatePlusSettingsView(_props: SettingsPageProps) {
<tbody>
{Object.entries(config.cameras)
.filter(([name]) => !isReplayCamera(name))
.sort(([, a], [, b]) => a.ui.order - b.ui.order)
.map(([name, camera]) => (
<tr
key={name}
+6 -2
View File
@@ -148,7 +148,7 @@ export default function ProfilesView({
const data: Record<string, Record<string, string[]>> = {};
const cameras = Object.keys(config.cameras)
.filter((name) => !isReplayCamera(name))
.sort();
.sort((a, b) => config.cameras[a].ui.order - config.cameras[b].ui.order);
for (const profile of allProfileNames) {
data[profile] = {};
@@ -474,7 +474,11 @@ export default function ProfilesView({
const color = getProfileColor(profile, allProfileNames);
const isActive = activeProfile === profile;
const cameraData = profileOverviewData[profile] ?? {};
const cameras = Object.keys(cameraData).sort();
const cameras = Object.keys(cameraData).sort(
(a, b) =>
(config?.cameras[a]?.ui?.order ?? 0) -
(config?.cameras[b]?.ui?.order ?? 0),
);
const isExpanded = expandedProfiles.has(profile);
return (
+103 -103
View File
@@ -316,114 +316,114 @@ export default function CameraMetrics({
</div>
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
{config &&
Object.values(config.cameras).map((camera) => {
if (camera.enabled && !isReplayCamera(camera.name)) {
return (
<Fragment key={camera.name}>
{probeCameraName == camera.name && (
<CameraInfoDialog
camera={camera}
showCameraInfoDialog={showCameraInfoDialog}
setShowCameraInfoDialog={setShowCameraInfoDialog}
/>
)}
<div className="flex w-full flex-col gap-3">
<div className="flex flex-row items-center justify-between">
<div className="flex items-center gap-2">
<div className="text-sm font-medium text-muted-foreground smart-capitalize">
<CameraNameLabel camera={camera} />
</div>
{statsHistory.length > 0 &&
statsHistory[statsHistory.length - 1]?.cameras[
camera.name
] && (
<ConnectionQualityIndicator
quality={
statsHistory[statsHistory.length - 1]?.cameras[
camera.name
]?.connection_quality
}
expectedFps={
statsHistory[statsHistory.length - 1]?.cameras[
camera.name
]?.expected_fps || 0
}
reconnects={
statsHistory[statsHistory.length - 1]?.cameras[
camera.name
]?.reconnects_last_hour || 0
}
stalls={
statsHistory[statsHistory.length - 1]?.cameras[
camera.name
]?.stalls_last_hour || 0
}
/>
)}
</div>
<Tooltip>
<TooltipTrigger>
<MdInfo
className="size-5 cursor-pointer text-muted-foreground"
onClick={() => {
setShowCameraInfoDialog(true);
setProbeCameraName(camera.name);
}}
/>
</TooltipTrigger>
<TooltipContent>
{t("cameras.info.tips.title")}
</TooltipContent>
</Tooltip>
</div>
<div
key={camera.name}
className="grid gap-2 sm:grid-cols-2"
>
{Object.keys(cameraCpuSeries).includes(camera.name) ? (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">CPU</div>
<CameraLineGraph
graphId={`${camera.name}-cpu`}
unit="%"
dataLabels={["ffmpeg", "capture", "detect"]}
updateTimes={updateTimes}
data={Object.values(
cameraCpuSeries[camera.name] || {},
)}
isActive={isActive}
/>
</div>
) : (
<Skeleton className="aspect-video size-full" />
)}
{Object.keys(cameraFpsSeries).includes(camera.name) ? (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">
{t("cameras.framesAndDetections")}
Object.values(config.cameras)
.sort((a, b) => a.ui.order - b.ui.order)
.map((camera) => {
if (camera.enabled && !isReplayCamera(camera.name)) {
return (
<Fragment key={camera.name}>
{probeCameraName == camera.name && (
<CameraInfoDialog
camera={camera}
showCameraInfoDialog={showCameraInfoDialog}
setShowCameraInfoDialog={setShowCameraInfoDialog}
/>
)}
<div className="flex w-full flex-col gap-3">
<div className="flex flex-row items-center justify-between">
<div className="flex items-center gap-2">
<div className="text-sm font-medium text-muted-foreground smart-capitalize">
<CameraNameLabel camera={camera} />
</div>
<CameraLineGraph
graphId={`${camera.name}-dps`}
unit=""
dataLabels={["camera", "detect", "skipped"]}
updateTimes={updateTimes}
data={Object.values(
cameraFpsSeries[camera.name] || {},
{statsHistory.length > 0 &&
statsHistory[statsHistory.length - 1]?.cameras[
camera.name
] && (
<ConnectionQualityIndicator
quality={
statsHistory[statsHistory.length - 1]
?.cameras[camera.name]?.connection_quality
}
expectedFps={
statsHistory[statsHistory.length - 1]
?.cameras[camera.name]?.expected_fps || 0
}
reconnects={
statsHistory[statsHistory.length - 1]
?.cameras[camera.name]
?.reconnects_last_hour || 0
}
stalls={
statsHistory[statsHistory.length - 1]
?.cameras[camera.name]?.stalls_last_hour ||
0
}
/>
)}
isActive={isActive}
/>
</div>
) : (
<Skeleton className="aspect-video size-full" />
)}
<Tooltip>
<TooltipTrigger>
<MdInfo
className="size-5 cursor-pointer text-muted-foreground"
onClick={() => {
setShowCameraInfoDialog(true);
setProbeCameraName(camera.name);
}}
/>
</TooltipTrigger>
<TooltipContent>
{t("cameras.info.tips.title")}
</TooltipContent>
</Tooltip>
</div>
<div
key={camera.name}
className="grid gap-2 sm:grid-cols-2"
>
{Object.keys(cameraCpuSeries).includes(camera.name) ? (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">CPU</div>
<CameraLineGraph
graphId={`${camera.name}-cpu`}
unit="%"
dataLabels={["ffmpeg", "capture", "detect"]}
updateTimes={updateTimes}
data={Object.values(
cameraCpuSeries[camera.name] || {},
)}
isActive={isActive}
/>
</div>
) : (
<Skeleton className="aspect-video size-full" />
)}
{Object.keys(cameraFpsSeries).includes(camera.name) ? (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">
{t("cameras.framesAndDetections")}
</div>
<CameraLineGraph
graphId={`${camera.name}-dps`}
unit=""
dataLabels={["camera", "detect", "skipped"]}
updateTimes={updateTimes}
data={Object.values(
cameraFpsSeries[camera.name] || {},
)}
isActive={isActive}
/>
</div>
) : (
<Skeleton className="aspect-video size-full" />
)}
</div>
</div>
</div>
</Fragment>
);
}
</Fragment>
);
}
return null;
})}
return null;
})}
</div>
</div>
);