mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-09-26 19:41:29 +08:00

* Translation module init * Add more i18n keys * fix: fix string wrong * refactor: use namespace translation file * chore: add more translation key * fix: fix some page name error * refactor: change Trans tag for t function * chore: fix some key not work * chore: fix SearchFilterDialog i18n key error Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * chore: fix en i18n file filter missing some keys * chore: add some i18n keys * chore: add more i18n keys again * feat: add search page i18n * feat: add explore model i18n keys * Update web/src/components/menu/GeneralSettings.tsx Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * Update web/src/components/menu/GeneralSettings.tsx Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * Update web/src/components/menu/GeneralSettings.tsx Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * feat: add more live i18n keys * feat: add more search setting i18n keys * fix: remove some comment * fix: fix some setting page url error * Update web/src/views/settings/SearchSettingsView.tsx Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * fix: add system missing keys * fix: update password update i18n keys * chore: remove outdate translation.json file * fix: fix exploreSettings error * chore: add object setting i18n keys * Update web/src/views/recording/RecordingView.tsx Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * Update web/public/locales/en/components/filter.json Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * Update web/src/components/overlay/ExportDialog.tsx Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * feat: add more i18n keys * fix: fix motionDetectionTuner html node * feat: add more page i18n keys * fix: cameraStream i18n keys error * feat: add Player i18n keys * feat: add more toast i18n keys * feat: change explore setting name * feat: add more document title i18n keys * feat: add more search i18n keys * fix: fix accessDenied i18n keys error * chore: add objectType i18n * chore: add inputWithTags i18n * chore: add SearchFilterDialog i18n * Update web/src/views/settings/ObjectSettingsView.tsx Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * Update web/src/views/settings/ObjectSettingsView.tsx Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * Update web/src/views/settings/ObjectSettingsView.tsx Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * Update web/src/views/settings/ObjectSettingsView.tsx Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * Update web/src/views/settings/ObjectSettingsView.tsx Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * chore: add some missing i18n keys * chore: remove most import { t } from "i18next"; --------- Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
243 lines
6.7 KiB
TypeScript
243 lines
6.7 KiB
TypeScript
import { useTheme } from "@/context/theme-provider";
|
|
import { generateColors } from "@/utils/colorUtil";
|
|
import { useEffect, useMemo } from "react";
|
|
import Chart from "react-apexcharts";
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from "@/components/ui/table";
|
|
import {
|
|
Popover,
|
|
PopoverContent,
|
|
PopoverTrigger,
|
|
} from "@/components/ui/popover";
|
|
import { getUnitSize } from "@/utils/storageUtil";
|
|
|
|
import { CiCircleAlert } from "react-icons/ci";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
type CameraStorage = {
|
|
[key: string]: {
|
|
bandwidth: number;
|
|
usage: number;
|
|
usage_percent: number;
|
|
};
|
|
};
|
|
|
|
type TotalStorage = {
|
|
used: number;
|
|
total: number;
|
|
};
|
|
|
|
type CombinedStorageGraphProps = {
|
|
graphId: string;
|
|
cameraStorage: CameraStorage;
|
|
totalStorage: TotalStorage;
|
|
};
|
|
export function CombinedStorageGraph({
|
|
graphId,
|
|
cameraStorage,
|
|
totalStorage,
|
|
}: CombinedStorageGraphProps) {
|
|
const { t } = useTranslation(["views/system"]);
|
|
|
|
const { theme, systemTheme } = useTheme();
|
|
|
|
const entities = Object.keys(cameraStorage);
|
|
const colors = generateColors(entities.length);
|
|
|
|
const series = entities.map((entity, index) => ({
|
|
name: entity,
|
|
data: [(cameraStorage[entity].usage / totalStorage.total) * 100],
|
|
usage: cameraStorage[entity].usage,
|
|
bandwidth: cameraStorage[entity].bandwidth,
|
|
color: colors[index], // Assign the corresponding color
|
|
}));
|
|
|
|
// Add the unused percentage to the series
|
|
series.push({
|
|
name: "Unused",
|
|
data: [
|
|
((totalStorage.total - totalStorage.used) / totalStorage.total) * 100,
|
|
],
|
|
usage: totalStorage.total - totalStorage.used,
|
|
bandwidth: 0,
|
|
color: (systemTheme || theme) == "dark" ? "#404040" : "#E5E5E5",
|
|
});
|
|
|
|
const options = useMemo(() => {
|
|
return {
|
|
chart: {
|
|
id: graphId,
|
|
background: (systemTheme || theme) == "dark" ? "#404040" : "#E5E5E5",
|
|
selection: {
|
|
enabled: false,
|
|
},
|
|
toolbar: {
|
|
show: false,
|
|
},
|
|
zoom: {
|
|
enabled: false,
|
|
},
|
|
stacked: true,
|
|
stackType: "100%",
|
|
},
|
|
grid: {
|
|
show: false,
|
|
padding: {
|
|
bottom: -45,
|
|
top: -40,
|
|
left: -20,
|
|
right: -20,
|
|
},
|
|
},
|
|
legend: {
|
|
show: false,
|
|
},
|
|
dataLabels: {
|
|
enabled: false,
|
|
},
|
|
plotOptions: {
|
|
bar: {
|
|
horizontal: true,
|
|
},
|
|
},
|
|
states: {
|
|
active: {
|
|
filter: {
|
|
type: "none",
|
|
},
|
|
},
|
|
hover: {
|
|
filter: {
|
|
type: "none",
|
|
},
|
|
},
|
|
},
|
|
tooltip: {
|
|
enabled: false,
|
|
x: {
|
|
show: false,
|
|
},
|
|
y: {
|
|
formatter: function (val, { seriesIndex }) {
|
|
if (series[seriesIndex]) {
|
|
const usage = series[seriesIndex].usage;
|
|
return `${getUnitSize(usage)} (${val.toFixed(2)}%)`;
|
|
}
|
|
},
|
|
},
|
|
theme: systemTheme || theme,
|
|
},
|
|
xaxis: {
|
|
axisBorder: {
|
|
show: false,
|
|
},
|
|
axisTicks: {
|
|
show: false,
|
|
},
|
|
labels: {
|
|
formatter: function (val) {
|
|
return val + "%";
|
|
},
|
|
},
|
|
min: 0,
|
|
max: 100,
|
|
},
|
|
yaxis: {
|
|
show: false,
|
|
min: 0,
|
|
max: 100,
|
|
},
|
|
} as ApexCharts.ApexOptions;
|
|
}, [graphId, systemTheme, theme, series]);
|
|
|
|
useEffect(() => {
|
|
ApexCharts.exec(graphId, "updateOptions", options, true, true);
|
|
}, [graphId, options]);
|
|
|
|
return (
|
|
<div className="flex w-full flex-col gap-2.5">
|
|
<div className="flex w-full items-center justify-between gap-1">
|
|
<div className="flex items-center gap-1">
|
|
<div className="text-xs text-primary">
|
|
{getUnitSize(totalStorage.used)}
|
|
</div>
|
|
<div className="text-xs text-primary">/</div>
|
|
<div className="text-xs text-muted-foreground">
|
|
{getUnitSize(totalStorage.total)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="h-5 overflow-hidden rounded-md">
|
|
<Chart type="bar" options={options} series={series} height="100%" />
|
|
</div>
|
|
<div className="custom-legend">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>{t("storage.cameraStorage.camera")}</TableHead>
|
|
<TableHead>{t("storage.cameraStorage.storageUsed")}</TableHead>
|
|
<TableHead>
|
|
{t("storage.cameraStorage.percentageOfTotalUsed")}
|
|
</TableHead>
|
|
<TableHead>{t("storage.cameraStorage.bandwidth")}</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{series.map((item) => (
|
|
<TableRow key={item.name}>
|
|
<TableCell className="flex flex-row items-center gap-2 font-medium capitalize">
|
|
{" "}
|
|
<div
|
|
className="size-3 rounded-md"
|
|
style={{ backgroundColor: item.color }}
|
|
></div>
|
|
{item.name === "Unused"
|
|
? t("storage.cameraStorage.unused")
|
|
: item.name.replaceAll("_", " ")}
|
|
{item.name === "Unused" && (
|
|
<Popover>
|
|
<PopoverTrigger asChild>
|
|
<button
|
|
className="focus:outline-none"
|
|
aria-label={t(
|
|
"storage.cameraStorage.unusedStorageInformation",
|
|
)}
|
|
>
|
|
<CiCircleAlert
|
|
className="size-5"
|
|
aria-label={t(
|
|
"storage.cameraStorage.unusedStorageInformation",
|
|
)}
|
|
/>
|
|
</button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="w-80">
|
|
<div className="space-y-2">
|
|
{t("storage.cameraStorage.unused.tips")}
|
|
</div>
|
|
</PopoverContent>
|
|
</Popover>
|
|
)}
|
|
</TableCell>
|
|
<TableCell>{getUnitSize(item.usage ?? 0)}</TableCell>
|
|
<TableCell>{item.data[0].toFixed(2)}%</TableCell>
|
|
<TableCell>
|
|
{item.name === "Unused"
|
|
? "—"
|
|
: `${getUnitSize(item.bandwidth)} / hour`}
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|