Compare commits

...

3 Commits

Author SHA1 Message Date
f7964d4fc0 - added download button
- added reads for number plate sightings
2025-12-03 10:46:36 +00:00
2a4afc7eae - organised file structure for dashboard 2025-12-02 14:10:06 +00:00
1810fc04b5 Merge pull request 'feature/settingsPage' (#10) from feature/settingsPage into develop
Reviewed-on: #10
2025-12-02 13:54:56 +00:00
17 changed files with 179 additions and 36 deletions

View File

@@ -1,8 +1,8 @@
import type { SystemHealthStatus } from "../../../types/types";
import { useGetSystemHealth } from "../hooks/useGetSystemHealth";
import CameraStatus from "./CameraStatus";
import SystemOverview from "./SystemOverview";
import SystemStatusCard from "./SystemStatusCard";
import CameraStatus from "./cameraStatus/CameraStatus";
import SystemHealthCard from "./systemHealth/SystemHealthCard";
import SystemStatusCard from "./systemStatus/SystemStatusCard";
const DashboardGrid = () => {
const { query } = useGetSystemHealth();
@@ -37,7 +37,7 @@ const DashboardGrid = () => {
return (
<div className="grid grid-cols-1 md:grid-rows-2 md:grid-cols-2">
<SystemStatusCard />
<SystemOverview
<SystemHealthCard
startTime={startTime}
uptime={uptime}
statuses={statuses}

View File

@@ -1,7 +1,7 @@
import type { SystemHealthStatus } from "../../../types/types";
import Card from "../../../ui/Card";
import StatusIndicators from "../../../ui/StatusIndicators";
import { capitalize } from "../../../utils/utils";
import type { SystemHealthStatus } from "../../../../types/types";
import Card from "../../../../ui/Card";
import StatusIndicators from "../../../../ui/StatusIndicators";
import { capitalize } from "../../../../utils/utils";
import CameraStatusGridItem from "./CameraStatusGridItem";
type CameraStatusProps = {

View File

@@ -1,7 +1,7 @@
import { useState } from "react";
import type { SystemHealthStatus } from "../../../types/types";
import { capitalize } from "../../../utils/utils";
import SystemHealthModal from "./systemHealthModal/SystemHealthModal";
import type { SystemHealthStatus } from "../../../../types/types";
import { capitalize } from "../../../../utils/utils";
import SystemHealthModal from "../systemHealth/systemHealthModal/SystemHealthModal";
type CameraStatusGridItemProps = {
title: string;

View File

@@ -2,7 +2,7 @@ import { useState } from "react";
import type { SystemHealthStatus } from "../../../../types/types";
import StatusIndicators from "../../../../ui/StatusIndicators";
import { capitalize } from "../../../../utils/utils";
import SystemHealthModal from "../systemHealthModal/SystemHealthModal";
import SystemHealthModal from "../systemHealth/systemHealthModal/SystemHealthModal";
type StatusGridItemProps = {
title: string;

View File

@@ -1,5 +1,5 @@
import type { SystemHealthStatus } from "../../../types/types";
import StatusGridItem from "./statusGridItem/StatusGridItem";
import type { SystemHealthStatus } from "../../../../types/types";
import StatusGridItem from "../statusGridItem/StatusGridItem";
type SystemHealthProps = {
startTime: string;

View File

@@ -1,9 +1,9 @@
import { faArrowsRotate } from "@fortawesome/free-solid-svg-icons";
import Card from "../../../ui/Card";
import CardHeader from "../../../ui/CardHeader";
import Card from "../../../../ui/Card";
import CardHeader from "../../../../ui/CardHeader";
import SystemHealth from "./SystemHealth";
import type { SystemHealthStatus } from "../../../types/types";
import type { SystemHealthStatus } from "../../../../types/types";
type SystemOverviewProps = {
startTime: string;
@@ -15,7 +15,7 @@ type SystemOverviewProps = {
refetch: () => void;
};
const SystemOverview = ({
const SystemHealthCard = ({
startTime,
uptime,
statuses,
@@ -39,4 +39,4 @@ const SystemOverview = ({
);
};
export default SystemOverview;
export default SystemHealthCard;

View File

@@ -1,8 +1,8 @@
import type { SystemHealthStatus } from "../../../../types/types";
import Badge from "../../../../ui/Badge";
import ModalComponent from "../../../../ui/ModalComponent";
import StatusIndicators from "../../../../ui/StatusIndicators";
import { capitalize } from "../../../../utils/utils";
import type { SystemHealthStatus } from "../../../../../types/types";
import Badge from "../../../../../ui/Badge";
import ModalComponent from "../../../../../ui/ModalComponent";
import StatusIndicators from "../../../../../ui/StatusIndicators";
import { capitalize } from "../../../../../utils/utils";
type SystemHealthModalProps = {
isSystemHealthModalOpen: boolean;

View File

@@ -0,0 +1,50 @@
import { faDownload } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useDownloadLogFiles } from "../../../hooks/useDownloadLogFiles";
import { toast } from "sonner";
const DownloadLogButton = () => {
const { downloadLogFilesQuery } = useDownloadLogFiles();
const isLoading = downloadLogFilesQuery?.isFetching;
const handleDownloadClick = async () => {
try {
const blob = await downloadLogFilesQuery?.refetch().then((res) => res.data);
if (!blob) {
throw new Error("No log file data received");
}
const url = window.URL.createObjectURL(new Blob([blob]));
const link = document.createElement("a");
if (!link) {
throw new Error("Failed to create download link");
} else {
link.href = url;
link.setAttribute("download", "FlexiAI-0.log");
document.body.appendChild(link);
link.click();
link.parentNode?.removeChild(link);
window.URL.revokeObjectURL(url);
}
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
toast.error(errorMessage);
}
};
return (
<button
className="p-3 border border-gray-700 rounded-lg hover:bg-[#233241] hover:cursor-pointer"
onClick={handleDownloadClick}
>
<div className="flex flex-row gap-2 items-center">
<span className="font-bold text-xl bg-slate-700 p-1 px-2 rounded-md">
<FontAwesomeIcon icon={faDownload} />
</span>
<p className="text-lg">{"Download Log Files"}</p>
</div>
<p className="text-slate-400 italic text-start">{isLoading ? "Downloading..." : "View logs"}</p>
</button>
);
};
export default DownloadLogButton;

View File

@@ -0,0 +1,48 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faChartSimple } from "@fortawesome/free-solid-svg-icons";
type StatusReadsProps = {
reads: {
totalPending: number;
totalActive: number;
totalSent: number;
totalReceived: number;
totalLost: number;
sanityCheck: boolean;
sanityCheckFormula: string;
};
isReadsLoading?: boolean;
};
const StatusReads = ({ reads, isReadsLoading }: StatusReadsProps) => {
const totalPending = reads?.totalPending ?? 0;
const totalActive = reads?.totalActive ?? 0;
const totalSent = reads?.totalSent ?? 0;
const totalLost = reads?.totalLost ?? 0;
const totalReceived = reads?.totalReceived ?? 0;
if (isReadsLoading) {
return <p className="text-slate-400">Loading reads</p>;
}
return (
<div className="p-3 border border-gray-700 rounded-lg hover:bg-[#233241]">
<div className="flex flex-row gap-2 items-center">
<span className="font-bold text-xl bg-slate-700 p-1 px-2 rounded-md">
<FontAwesomeIcon icon={faChartSimple} />
</span>
<p className="text-lg">Reads</p>
</div>
<div className="text-slate-400 mt-1">
Pending: <span className="text-yellow-500">{totalPending}</span> | Active:{" "}
<span className="text-cyan-500">{totalActive}</span> | Lost: <span className="text-red-500">{totalLost}</span>
<br />
Sent / Received: <span className="text-blue-500">{totalSent}</span> |{" "}
<span className="text-green-500">{totalReceived}</span>
</div>
</div>
);
};
export default StatusReads;

View File

@@ -1,13 +1,23 @@
import { useInfoSocket } from "../../../app/context/WebSocketContext";
import Card from "../../../ui/Card";
import CardHeader from "../../../ui/CardHeader";
import StatusItemCPU from "./StatusItems/StatusItemCPU";
import { useEffect } from "react";
import { useInfoSocket } from "../../../../app/context/WebSocketContext";
import Card from "../../../../ui/Card";
import CardHeader from "../../../../ui/CardHeader";
import DownloadLogButton from "./StatusItems/DownloadLogButton";
import StatusItemLocal from "./StatusItems/StatusItemLocal";
import StatusItemThreads from "./StatusItems/StatusItemThreads";
import StatusItemUTC from "./StatusItems/StatusItemUTC";
import StatusReads from "./StatusItems/StatusReads";
import { useGetStore } from "../../hooks/useGetStore";
const SystemStatusCard = () => {
const { data: stats } = useInfoSocket();
const { storeQuery } = useGetStore();
const reads = storeQuery?.data;
const isReadsLoading = storeQuery.isFetching;
useEffect(() => {
storeQuery.refetch();
}, [reads]);
return (
<Card className="p-4">
@@ -16,8 +26,8 @@ const SystemStatusCard = () => {
<div className="grid grid-cols-2 grid-rows-2 gap-4 col-span-2">
<StatusItemUTC statusInfoItem={stats["system-clock-utc"]} description={"UTC Time"} />
<StatusItemLocal statusInfoItem={stats["system-clock-local"]} description={"Local Time"} />
<StatusItemCPU statusInfoItem={stats["memory-cpu-status"]} description={"CPU"} />
<StatusItemThreads statusInfoItem={stats["thread-count"]} description={"Threads"} />
<DownloadLogButton />
<StatusReads reads={reads} isReadsLoading={isReadsLoading} />
</div>
) : (
<span className="text-slate-500">Loading system status</span>

View File

@@ -0,0 +1,20 @@
import { useQuery } from "@tanstack/react-query";
import { CAMBASE } from "../../../utils/config";
const getDownloadLogFiles = async () => {
const response = await fetch(`${CAMBASE}/LogView/download?filename=FlexiAI-0.log`);
if (!response.ok) {
throw new Error("Failed to download log files");
}
return response.blob();
};
export const useDownloadLogFiles = () => {
const downloadLogFilesQuery = useQuery({
queryKey: ["downloadLogFiles"],
queryFn: getDownloadLogFiles,
enabled: false,
});
return { downloadLogFilesQuery };
};

View File

@@ -0,0 +1,19 @@
import { useQuery } from "@tanstack/react-query";
import { CAMBASE } from "../../../utils/config";
const getStoreData = async () => {
const response = await fetch(`${CAMBASE}/Store0/diagnostics-json`);
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json();
};
export const useGetStore = () => {
const storeQuery = useQuery({
queryKey: ["storeData"],
queryFn: getStoreData,
// refetchInterval: 10 * 60 * 1000,
});
return { storeQuery };
};

View File

@@ -6,9 +6,5 @@ export const Route = createFileRoute("/")({
});
function HomePage() {
return (
<div>
<DashboardGrid />
</div>
);
return <DashboardGrid />;
}