Compare commits

..
Author SHA1 Message Date
Nicolas Mowen 463e93d36f Uppsercase model name 2026-01-09 08:51:57 -07:00
Josh Hawkins 495ee0161b prevent react Suspense crash during auth redirect
add redirect-check guards to stop rendering lazy routes while navigation is pending (fixes some users seeing React error #426 when auth expires)
2026-01-09 06:46:20 -06:00
Nicolas Mowen ad1365a46d Add preferred language to review docs 2026-01-08 09:28:24 -07:00
Josh Hawkins 33eb5d031b icon improvements
add type to getIconForLabel
provide default icon for audio events
2026-01-08 10:11:05 -06:00
11 changed files with 28 additions and 51 deletions
+2 -2
View File
@@ -39,14 +39,14 @@ jobs:
STABLE_TAG=${BASE}:stable
PULL_TAG=${BASE}:${BUILD_TAG}
docker run --rm -v $HOME/.docker/config.json:/config.json quay.io/skopeo/stable:latest copy --authfile /config.json --multi-arch all docker://${PULL_TAG} docker://${VERSION_TAG}
for variant in standard-arm64 tensorrt tensorrt-jp6 rk rocm synaptics; do
for variant in standard-arm64 tensorrt tensorrt-jp6 rk rocm; do
docker run --rm -v $HOME/.docker/config.json:/config.json quay.io/skopeo/stable:latest copy --authfile /config.json --multi-arch all docker://${PULL_TAG}-${variant} docker://${VERSION_TAG}-${variant}
done
# stable tag
if [[ "${BUILD_TYPE}" == "stable" ]]; then
docker run --rm -v $HOME/.docker/config.json:/config.json quay.io/skopeo/stable:latest copy --authfile /config.json --multi-arch all docker://${PULL_TAG} docker://${STABLE_TAG}
for variant in standard-arm64 tensorrt tensorrt-jp6 rk rocm synaptics; do
for variant in standard-arm64 tensorrt tensorrt-jp6 rk rocm; do
docker run --rm -v $HOME/.docker/config.json:/config.json quay.io/skopeo/stable:latest copy --authfile /config.json --multi-arch all docker://${PULL_TAG}-${variant} docker://${STABLE_TAG}-${variant}
done
fi
+1 -2
View File
@@ -101,8 +101,7 @@
"show": "Show {{item}}",
"ID": "ID",
"none": "None",
"all": "All",
"other": "Other"
"all": "All"
},
"list": {
"two": "{{0}} and {{1}}",
+1 -8
View File
@@ -86,14 +86,7 @@
"otherProcesses": {
"title": "Other Processes",
"processCpuUsage": "Process CPU Usage",
"processMemoryUsage": "Process Memory Usage",
"series": {
"go2rtc": "go2rtc",
"recording": "recording",
"review_segment": "review segment",
"embeddings": "embeddings",
"audio_detector": "audio detector"
}
"processMemoryUsage": "Process Memory Usage"
}
},
"storage": {
@@ -166,7 +166,7 @@ export const ClassificationCard = forwardRef<
<div className="break-all smart-capitalize">
{data.name == "unknown"
? t("details.unknown")
: data.name.toLowerCase() == "none"
: data.name == "none"
? t("details.none")
: data.name}
</div>
@@ -22,7 +22,6 @@ import { useTranslation } from "react-i18next";
import { useDocDomain } from "@/hooks/use-doc-domain";
import useSWR from "swr";
import { formatSecondsToDuration } from "@/utils/dateUtil";
import { useDateLocale } from "@/hooks/use-date-locale";
import ActivityIndicator from "../indicators/activity-indicator";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
@@ -49,13 +48,12 @@ export default function SetPasswordDialog({
const { t } = useTranslation(["views/settings", "common"]);
const { getLocaleDocUrl } = useDocDomain();
const isAdmin = useIsAdmin();
const dateLocale = useDateLocale();
const { data: config } = useSWR("config");
const refreshSeconds: number | undefined =
config?.auth?.refresh_time ?? undefined;
const refreshTimeLabel = refreshSeconds
? formatSecondsToDuration(refreshSeconds, dateLocale)
? formatSecondsToDuration(refreshSeconds)
: t("time.30minutes", { ns: "common" });
// visibility toggles for password fields
@@ -266,7 +266,7 @@ export function TrackingDetails({
const label = event.sub_label
? event.sub_label
: getTranslatedLabel(event.label, event.data.type);
: getTranslatedLabel(event.label);
const getZoneColor = useCallback(
(zoneName: string) => {
@@ -999,7 +999,7 @@ function LifecycleIconRow({
<div className="ml-3 flex-shrink-0 px-1 text-right text-xs text-primary-variant">
<div className="flex flex-row items-center gap-3">
<div className="whitespace-nowrap">{formattedEventTimestamp}</div>
{isAdmin && config?.plus?.enabled && item.data.box && (
{((isAdmin && config?.plus?.enabled) || item.data.box) && (
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
<DropdownMenuTrigger>
<div className="rounded p-1 pr-2" role="button">
+15 -18
View File
@@ -16,6 +16,7 @@ import {
} from "@/types/live";
import { getIconForLabel } from "@/utils/iconUtil";
import Chip from "../indicators/Chip";
import { capitalizeFirstLetter } from "@/utils/stringUtil";
import { cn } from "@/lib/utils";
import { TbExclamationCircle } from "react-icons/tb";
import { TooltipPortal } from "@radix-ui/react-tooltip";
@@ -25,8 +26,6 @@ import { LuVideoOff } from "react-icons/lu";
import { Trans, useTranslation } from "react-i18next";
import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name";
import { ImageShadowOverlay } from "../overlay/ImageShadowOverlay";
import { getTranslatedLabel } from "@/utils/i18n";
import { formatList } from "@/utils/stringUtil";
type LivePlayerProps = {
cameraRef?: (ref: HTMLDivElement | null) => void;
@@ -372,22 +371,20 @@ export default function LivePlayer({
</div>
<TooltipPortal>
<TooltipContent className="smart-capitalize">
{formatList(
[
...new Set([
...(objects || []).map(({ label, sub_label }) =>
label.endsWith("verified")
? sub_label
: label.replaceAll("_", " "),
),
]),
]
.filter((label) => label?.includes("-verified") == false)
.map((label) =>
getTranslatedLabel(label.replace("-verified", "")),
)
.sort(),
)}
{[
...new Set([
...(objects || []).map(({ label, sub_label }) =>
label.endsWith("verified")
? sub_label
: label.replaceAll("_", " "),
),
]),
]
.filter((label) => label?.includes("-verified") == false)
.map((label) => capitalizeFirstLetter(label))
.sort()
.join(", ")
.replaceAll("-verified", "")}
</TooltipContent>
</TooltipPortal>
</Tooltip>
@@ -417,9 +417,7 @@ export default function Step1NameCamera({
<SelectContent>
{CAMERA_BRANDS.map((brand) => (
<SelectItem key={brand.value} value={brand.value}>
{brand.label.toLowerCase() === "other"
? t("label.other", { ns: "common" })
: brand.label}
{brand.label}
</SelectItem>
))}
</SelectContent>
+2 -7
View File
@@ -1,5 +1,5 @@
import { fromUnixTime, intervalToDuration, formatDuration } from "date-fns";
import { enUS, Locale } from "date-fns/locale";
import { Locale } from "date-fns/locale";
import { formatInTimeZone } from "date-fns-tz";
import i18n from "@/utils/i18n";
export const longToDate = (long: number): Date => new Date(long * 1000);
@@ -293,13 +293,9 @@ export const getDurationFromTimestamps = (
/**
*
* @param seconds - number of seconds to convert into hours, minutes and seconds
* @param locale - the date-fns locale to use for formatting
* @returns string - formatted duration in hours, minutes and seconds
*/
export const formatSecondsToDuration = (
seconds: number,
locale?: Locale,
): string => {
export const formatSecondsToDuration = (seconds: number): string => {
if (isNaN(seconds) || seconds < 0) {
return "Invalid duration";
}
@@ -308,7 +304,6 @@ export const formatSecondsToDuration = (
return formatDuration(duration, {
format: ["hours", "minutes", "seconds"],
delimiter: ", ",
locale: locale ?? enUS,
});
};
+1 -4
View File
@@ -12,10 +12,7 @@ export function getLifecycleItemDescription(
const label = lifecycleItem.data.sub_label
? capitalizeFirstLetter(rawLabel)
: getTranslatedLabel(
rawLabel,
lifecycleItem.class_type === "heard" ? "audio" : "object",
);
: getTranslatedLabel(rawLabel);
switch (lifecycleItem.class_type) {
case "visible":
+1 -1
View File
@@ -855,7 +855,7 @@ export default function GeneralMetrics({
<ThresholdBarGraph
key={series.name}
graphId={`${series.name}-cpu`}
name={t(`general.otherProcesses.series.${series.name}`)}
name={series.name.replaceAll("_", " ")}
unit="%"
threshold={DetectorCpuThreshold}
updateTimes={updateTimes}