Add timeline graph component

This commit is contained in:
Nick Mowen 2024-01-02 10:43:35 -07:00
parent 9aee0f625b
commit 248a045034
11 changed files with 212 additions and 16 deletions

131
web/package-lock.json generated
View File

@ -24,6 +24,7 @@
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7",
"apexcharts": "^3.45.1",
"axios": "^1.6.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
@ -34,6 +35,7 @@
"lucide-react": "^0.294.0",
"monaco-yaml": "^5.1.0",
"react": "^18.2.0",
"react-apexcharts": "^1.4.1",
"react-day-picker": "^8.9.1",
"react-dom": "^18.2.0",
"react-hook-form": "^7.48.2",
@ -2782,6 +2784,11 @@
"node": ">=10.0.0"
}
},
"node_modules/@yr/monotone-cubic-spline": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz",
"integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA=="
},
"node_modules/acorn": {
"version": "8.11.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
@ -2933,6 +2940,20 @@
"node": ">= 8"
}
},
"node_modules/apexcharts": {
"version": "3.45.1",
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.45.1.tgz",
"integrity": "sha512-pPjj/SA6dfPvR/IKRZF0STdfBGpBh3WRt7K0DFuW9P8erypYkX17EHu3/molPRfo2zSiQwTVpshHC5ncysqfkA==",
"dependencies": {
"@yr/monotone-cubic-spline": "^1.0.3",
"svg.draggable.js": "^2.2.2",
"svg.easing.js": "^2.0.0",
"svg.filter.js": "^2.0.2",
"svg.pathmorphing.js": "^0.1.3",
"svg.resize.js": "^1.4.3",
"svg.select.js": "^3.0.1"
}
},
"node_modules/arg": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
@ -6341,6 +6362,21 @@
"node": ">= 0.6.0"
}
},
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.13.1"
}
},
"node_modules/prop-types/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/propagating-hammerjs": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/propagating-hammerjs/-/propagating-hammerjs-2.0.1.tgz",
@ -6406,6 +6442,18 @@
"node": ">=0.10.0"
}
},
"node_modules/react-apexcharts": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/react-apexcharts/-/react-apexcharts-1.4.1.tgz",
"integrity": "sha512-G14nVaD64Bnbgy8tYxkjuXEUp/7h30Q0U33xc3AwtGFijJB9nHqOt1a6eG0WBn055RgRg+NwqbKGtqPxy15d0Q==",
"dependencies": {
"prop-types": "^15.8.1"
},
"peerDependencies": {
"apexcharts": "^3.41.0",
"react": ">=0.13"
}
},
"node_modules/react-day-picker": {
"version": "8.9.1",
"resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.9.1.tgz",
@ -7270,6 +7318,89 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/svg.draggable.js": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz",
"integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==",
"dependencies": {
"svg.js": "^2.0.1"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/svg.easing.js": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz",
"integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==",
"dependencies": {
"svg.js": ">=2.3.x"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/svg.filter.js": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz",
"integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==",
"dependencies": {
"svg.js": "^2.2.5"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/svg.js": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz",
"integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA=="
},
"node_modules/svg.pathmorphing.js": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz",
"integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==",
"dependencies": {
"svg.js": "^2.4.0"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/svg.resize.js": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz",
"integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==",
"dependencies": {
"svg.js": "^2.6.5",
"svg.select.js": "^2.1.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/svg.resize.js/node_modules/svg.select.js": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz",
"integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==",
"dependencies": {
"svg.js": "^2.2.5"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/svg.select.js": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz",
"integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==",
"dependencies": {
"svg.js": "^2.6.5"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/swr": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/swr/-/swr-2.2.4.tgz",

View File

@ -29,6 +29,7 @@
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7",
"apexcharts": "^3.45.1",
"axios": "^1.6.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
@ -39,6 +40,7 @@
"lucide-react": "^0.294.0",
"monaco-yaml": "^5.1.0",
"react": "^18.2.0",
"react-apexcharts": "^1.4.1",
"react-day-picker": "^8.9.1",
"react-dom": "^18.2.0",
"react-hook-form": "^7.48.2",

View File

@ -59,7 +59,7 @@ export default function HistoryCard({
</div>
<Button className="px-2 py-2" variant="ghost" size="xs">
<LuTrash
className="w-5 h-5 stroke-red-500"
className="w-5 h-5 stroke-danger"
onClick={(e: Event) => {
e.stopPropagation();

View File

@ -0,0 +1,43 @@
import { GraphData } from "@/types/graph";
import Chart from "react-apexcharts";
type TimelineGraphProps = {
id: string;
data: GraphData[];
};
/**
* A graph meant to be overlaid on top of a timeline
*/
export default function TimelineGraph({ id, data }: TimelineGraphProps) {
return (
<Chart
type="bar"
options={{
chart: {
id: id,
toolbar: {
show: false,
},
},
dataLabels: { enabled: false },
grid: {
show: false,
},
xaxis: {
type: "datetime",
labels: {
show: false,
},
},
yaxis: {
labels: {
show: false,
},
},
}}
series={data}
height={100}
/>
);
}

View File

@ -150,9 +150,9 @@ function ConfigEditor() {
</div>
</div>
{success && <div className="max-h-20 text-green-500">{success}</div>}
{success && <div className="max-h-20 text-success">{success}</div>}
{error && (
<div className="p-4 overflow-scroll text-red-500 whitespace-pre-wrap">
<div className="p-4 overflow-scroll text-danger whitespace-pre-wrap">
{error}
</div>
)}

View File

@ -123,7 +123,7 @@ function Camera({ camera }: { camera: CameraConfig }) {
? recordValue == "ON"
? "text-primary"
: "text-gray-400"
: "text-red-500"
: "text-danger"
}
onClick={(e) => {
e.stopPropagation();

View File

@ -160,7 +160,7 @@ function Export() {
{message.text && (
<div
className={`max-h-20 ${
message.error ? "text-red-500" : "text-green-500"
message.error ? "text-danger" : "text-success"
}`}
>
{message.text}

View File

@ -212,7 +212,7 @@ function History() {
Cancel
</AlertDialogCancel>
<AlertDialogAction
className="bg-red-500"
className="bg-danger"
onClick={() => onDeleteMulti()}
>
Delete

9
web/src/types/graph.ts Normal file
View File

@ -0,0 +1,9 @@
export type GraphDataPoint = {
x: Date;
y: number;
};
export type GraphData = {
name?: string;
data: GraphDataPoint[];
};

View File

@ -1,11 +1,20 @@
type Recording = {
id: string,
camera: string,
start_time: number,
end_time: number,
path: string,
segment_size: number,
motion: number,
objects: number,
dBFS: number,
}
id: string;
camera: string;
start_time: number;
end_time: number;
path: string;
segment_size: number;
motion: number;
objects: number;
dBFS: number;
};
type RecordingSegment = {
id: string;
start_time: number;
end_time: number;
motion: number;
objects: number;
segment_size: number;
};

View File

@ -20,6 +20,8 @@ module.exports = {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
danger: "#ef4444",
success: "#22c55e",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {