Compare commits
7 Commits
feature/ba
...
feature/da
| Author | SHA1 | Date | |
|---|---|---|---|
| 27d2c6a1b9 | |||
| 31b6bd45f5 | |||
| c9dde6b992 | |||
| d15a69e6b9 | |||
| b18e11ca6a | |||
| e984c74333 | |||
| 25a744bd8d |
@@ -1,10 +1,10 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>MAV | BayiQ</title>
|
<title>BayIQ</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import Card from "../../../ui/Card";
|
||||||
|
import CardHeader from "../../../ui/CardHeader";
|
||||||
|
|
||||||
|
type CameraStatusProps = {
|
||||||
|
title: string;
|
||||||
|
status?: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CameraStatus = ({ title, status, description }: CameraStatusProps) => {
|
||||||
|
return (
|
||||||
|
<Card className="p-4">
|
||||||
|
<CardHeader title={title} status={status} />
|
||||||
|
<p className=" text-gray-500">{description}</p>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CameraStatus;
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
|
import CameraStatus from "./CameraStatus";
|
||||||
|
import SystemOverview from "./SystemOverview";
|
||||||
import SystemStatusCard from "./SystemStatusCard";
|
import SystemStatusCard from "./SystemStatusCard";
|
||||||
|
|
||||||
const DashboardGrid = () => {
|
const DashboardGrid = () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="grid grid-cols-1 md:grid-rows-2 md:grid-cols-2">
|
||||||
<SystemStatusCard />
|
<SystemStatusCard />
|
||||||
|
<SystemOverview />
|
||||||
|
<div className="grid grid-cols-1 md:col-span-2 md:grid-cols-3">
|
||||||
|
<CameraStatus title="Camera 1" status={"bg-red-500"} description={"Camera not responding"} />
|
||||||
|
<CameraStatus title="Camera 2" status={"bg-gray-500"} description={"Camera Offline"} />
|
||||||
|
<CameraStatus title="Camera 3" status={"bg-gray-500"} description={"Camera Offline"} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { faHardDrive } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
type StatusItemProps = {
|
||||||
|
statusInfoItem: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StatusItemCPU = ({ statusInfoItem, description }: StatusItemProps) => {
|
||||||
|
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={faHardDrive} />
|
||||||
|
</span>
|
||||||
|
<p className="text-lg">{statusInfoItem}</p>
|
||||||
|
</div>
|
||||||
|
<p className="text-slate-400">{description}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StatusItemCPU;
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { faClock } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
type StatusItemProps = {
|
||||||
|
statusInfoItem: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StatusItemLocal = ({ statusInfoItem, description }: StatusItemProps) => {
|
||||||
|
const humanReadable = (string: string) => {
|
||||||
|
if (description.toLowerCase().includes("local")) {
|
||||||
|
const text = string.slice(0, statusInfoItem.length - 5);
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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={faClock} />
|
||||||
|
</span>
|
||||||
|
<p className="text-lg">{description.toLowerCase().includes("local") && humanReadable(statusInfoItem)}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-slate-400">{description}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StatusItemLocal;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { faMicrochip } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
type StatusItemProps = {
|
||||||
|
statusInfoItem: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StatusItemThreads = ({ statusInfoItem, description }: StatusItemProps) => {
|
||||||
|
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={faMicrochip} />
|
||||||
|
</span>
|
||||||
|
<p className="text-lg">{statusInfoItem}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-slate-400">{description}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StatusItemThreads;
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { faClock } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
type StatusItemProps = {
|
||||||
|
statusInfoItem: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StatusItemUTC = ({ statusInfoItem, description }: StatusItemProps) => {
|
||||||
|
const humanReadable = (string: string) => {
|
||||||
|
if (description.includes("UTC")) {
|
||||||
|
const text = string.slice(0, statusInfoItem.length - 3);
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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={faClock} />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<p className="text-lg">{description.toLowerCase().includes("utc") && humanReadable(statusInfoItem)}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-slate-400">{description}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StatusItemUTC;
|
||||||
38
src/features/dashboard/components/SystemHealth.tsx
Normal file
38
src/features/dashboard/components/SystemHealth.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import type { SystemHealthStatus } from "../../../types/types";
|
||||||
|
import Badge from "../../../ui/Badge";
|
||||||
|
|
||||||
|
type SystemHealthProps = {
|
||||||
|
startTime: string;
|
||||||
|
uptime: string;
|
||||||
|
statuses: SystemHealthStatus[];
|
||||||
|
isLoading: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SystemHealth = ({ startTime, uptime, statuses, isLoading }: SystemHealthProps) => {
|
||||||
|
if (isLoading) {
|
||||||
|
return <span className="text-slate-500">Loading system health…</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-100 md:h-70">
|
||||||
|
<div className="p-2 border-b border-gray-600 grid grid-cols-2 justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg">Start Time</h3> <span className="text-slate-300">{startTime}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg">Up Time</h3> <span className="text-slate-300">{uptime}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{statuses?.map((status: SystemHealthStatus) => (
|
||||||
|
<div className="border border-gray-700 p-4 rounded-md m-2 flex justify-between">
|
||||||
|
<span>{status.id}</span> <Badge text={status.tags[0]} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SystemHealth;
|
||||||
22
src/features/dashboard/components/SystemOverview.tsx
Normal file
22
src/features/dashboard/components/SystemOverview.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { faArrowsRotate } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import Card from "../../../ui/Card";
|
||||||
|
import CardHeader from "../../../ui/CardHeader";
|
||||||
|
import { useGetSystemHealth } from "../hooks/useGetSystemHealth";
|
||||||
|
import SystemHealth from "./SystemHealth";
|
||||||
|
|
||||||
|
const SystemOverview = () => {
|
||||||
|
const { query } = useGetSystemHealth();
|
||||||
|
|
||||||
|
const startTime = query?.data?.StartTimeHumane;
|
||||||
|
const uptime = query?.data?.UptimeHumane;
|
||||||
|
const statuses = query?.data?.Status;
|
||||||
|
const isLoading = query?.isLoading;
|
||||||
|
return (
|
||||||
|
<Card className="p-4">
|
||||||
|
<CardHeader title="System Health" refetch={query?.refetch} icon={faArrowsRotate} />
|
||||||
|
<SystemHealth startTime={startTime} uptime={uptime} statuses={statuses} isLoading={isLoading} />
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SystemOverview;
|
||||||
@@ -1,20 +1,24 @@
|
|||||||
import { useInfoSocket } from "../../../app/context/WebSocketContext";
|
import { useInfoSocket } from "../../../app/context/WebSocketContext";
|
||||||
import Card from "../../../ui/Card";
|
import Card from "../../../ui/Card";
|
||||||
import CardHeader from "../../../ui/CardHeader";
|
import CardHeader from "../../../ui/CardHeader";
|
||||||
|
import StatusItemCPU from "./StatusItems/StatusItemCPU";
|
||||||
|
import StatusItemLocal from "./StatusItems/StatusItemLocal";
|
||||||
|
import StatusItemThreads from "./StatusItems/StatusItemThreads";
|
||||||
|
import StatusItemUTC from "./StatusItems/StatusItemUTC";
|
||||||
|
|
||||||
const SystemStatusCard = () => {
|
const SystemStatusCard = () => {
|
||||||
const { data: stats } = useInfoSocket();
|
const { data: stats } = useInfoSocket();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="p-4 w-[40%]">
|
<Card className="p-4">
|
||||||
<CardHeader title="Overview" />
|
<CardHeader title="System Status" />
|
||||||
{stats ? (
|
{stats ? (
|
||||||
<>
|
<div className="grid grid-cols-2 grid-rows-2 gap-4 col-span-2">
|
||||||
<div>UTC: {stats["system-clock-utc"]}</div>
|
<StatusItemUTC statusInfoItem={stats["system-clock-utc"]} description={"UTC Time"} />
|
||||||
<span>Local: {stats["system-clock-local"]}</span>
|
<StatusItemLocal statusInfoItem={stats["system-clock-local"]} description={"Local Time"} />
|
||||||
<span>CPU: {stats["memory-cpu-status"]}</span>
|
<StatusItemCPU statusInfoItem={stats["memory-cpu-status"]} description={"CPU"} />
|
||||||
<span>Threads: {stats["thread-count"]}</span>
|
<StatusItemThreads statusInfoItem={stats["thread-count"]} description={"Threads"} />
|
||||||
</>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-slate-500">Loading system status…</span>
|
<span className="text-slate-500">Loading system status…</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
15
src/features/dashboard/hooks/useGetSystemHealth.ts
Normal file
15
src/features/dashboard/hooks/useGetSystemHealth.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
const response = await fetch(`http://100.115.148.59/api/system-health`);
|
||||||
|
if (!response.ok) throw new Error("Cannot get System overview");
|
||||||
|
return response.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetSystemHealth = () => {
|
||||||
|
const query = useQuery({
|
||||||
|
queryKey: ["fetchSystemData"],
|
||||||
|
queryFn: fetchData,
|
||||||
|
});
|
||||||
|
return { query };
|
||||||
|
};
|
||||||
@@ -9,7 +9,6 @@ export const Route = createFileRoute("/baywatch")({
|
|||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-xl font-semibold">Cameras</h2>
|
|
||||||
<CameraGrid />
|
<CameraGrid />
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ export const Route = createFileRoute("/")({
|
|||||||
function HomePage() {
|
function HomePage() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-xl font-semibold"> Dashboard</h2>
|
|
||||||
<DashboardGrid />
|
<DashboardGrid />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,7 +11,13 @@ export type InfoBarData = {
|
|||||||
"thread-count": string;
|
"thread-count": string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type StatusIndicator = "neutral-quaternary" | "dark" | "info" | "success" | "warning" | "danger";
|
||||||
export type Region = {
|
export type Region = {
|
||||||
name: string;
|
name: string;
|
||||||
brushColour: string;
|
brushColour: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SystemHealthStatus = {
|
||||||
|
id: string;
|
||||||
|
tags: string[];
|
||||||
|
};
|
||||||
|
|||||||
24
src/ui/Badge.tsx
Normal file
24
src/ui/Badge.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import type { Icon, IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { capitalize } from "../utils/utils";
|
||||||
|
|
||||||
|
type BadgeProps = {
|
||||||
|
icon?: Icon | IconDefinition;
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Badge = ({ icon, text }: BadgeProps) => {
|
||||||
|
const lowerCaseWord = text.toLowerCase();
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={`text-sm font-medium inline-flex items-center px-2 py-0.5 rounded-md me-2
|
||||||
|
border-2 space-x-2
|
||||||
|
${text.toLowerCase() === "running" ? "bg-green-800 text-green-300 border-green-900" : "bg-red-800 text-red-300 border-red-900"} `}
|
||||||
|
>
|
||||||
|
{icon && <FontAwesomeIcon icon={icon} />}
|
||||||
|
<span>{capitalize(lowerCaseWord)}</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Badge;
|
||||||
@@ -1,18 +1,27 @@
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
import StatusIndicators from "./StatusIndicators";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import type { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||||
|
|
||||||
type CameraOverviewHeaderProps = {
|
type CameraOverviewHeaderProps = {
|
||||||
title?: string;
|
title?: string;
|
||||||
|
status?: string;
|
||||||
|
refetch?: () => void;
|
||||||
|
icon?: IconProp;
|
||||||
};
|
};
|
||||||
const CardHeader = ({ title }: CameraOverviewHeaderProps) => {
|
const CardHeader = ({ title, status, icon, refetch }: CameraOverviewHeaderProps) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"w-full border-b border-gray-600 flex flex-row items-center space-x-2 mb-6 relative justify-between",
|
"w-full border-b border-gray-600 flex flex-row items-center space-x-2 mb-6 relative justify-between",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex flex-row items-center w-full justify-between">
|
||||||
{/* {icon && <FontAwesomeIcon icon={icon} className="size-4" />} */}
|
<h2 className="flex flex-row text-xl items-center">
|
||||||
<h2 className="text-xl">{title}</h2>
|
{status && <StatusIndicators status={status} />}
|
||||||
|
{title}
|
||||||
|
</h2>
|
||||||
|
{icon && <FontAwesomeIcon icon={icon} className="size-4" onClick={refetch} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
9
src/ui/StatusIndicators.tsx
Normal file
9
src/ui/StatusIndicators.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
type StatusIndicatorsProps = { status: string };
|
||||||
|
|
||||||
|
const StatusIndicators = ({ status }: StatusIndicatorsProps) => {
|
||||||
|
return <span className={clsx(`flex w-3 h-3 me-2 rounded-full`, status)}></span>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StatusIndicators;
|
||||||
3
src/utils/utils.ts
Normal file
3
src/utils/utils.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export function capitalize(s?: string) {
|
||||||
|
return s ? s.charAt(0).toUpperCase() + s.slice(1) : "";
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user