Hacked some shit together with Github Copilot

This commit is contained in:
Max
2025-04-22 04:21:25 +02:00
parent 66bb790238
commit 368c7b3e35
14 changed files with 624 additions and 3087 deletions

View File

@@ -1,14 +1,16 @@
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Index from "./pages/Index.tsx";
import Dinosaur from "./pages/Dinosaur.tsx";
import HistoryPage from "./pages/HistoryPage.tsx";
import ServerPage from "./pages/ServerPage.tsx";
import "./App.css";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Index />} />
<Route path="/:selectedDinosaur" element={<Dinosaur />} />
<Route path="/" element={<Index />} />
<Route path="/history" element={<HistoryPage />} />
<Route path="/servers" element={<ServerPage />} />
</Routes>
</BrowserRouter>
);

View File

@@ -0,0 +1,44 @@
import React from "react";
import SnapshotCard from "./SnapshotCard.tsx";
interface Snapshot {
id: string;
timestamp: string;
progress: number;
status: "pending" | "in-progress" | "completed" | "error";
}
interface HistoryProps {
currentA: Snapshot[];
historyA: Snapshot[];
currentB: Snapshot[];
historyB: Snapshot[];
}
export default function History({ currentA, historyA, currentB, historyB }: HistoryProps) {
// Combine current and history snapshots for both servers
const allSnapshotsA = [...currentA, ...historyA];
const allSnapshotsB = [...currentB, ...historyB];
// Pair snapshots by ID
const pairedHistory = allSnapshotsA.map((snapshotA) => {
const snapshotB = allSnapshotsB.find((snap) => snap.id === snapshotA.id);
return { snapshotA, snapshotB };
});
return (
<div className="w-full mt-6 p-6 border rounded-lg shadow-md bg-gray-50 dark:bg-gray-800">
<h3 className="text-xl font-bold mb-4 text-gray-800 dark:text-gray-200">
Snapshot History
</h3>
<div className="space-y-6">
{pairedHistory.map(({ snapshotA, snapshotB }) => (
<div key={snapshotA.id} className="flex flex-row gap-4">
<SnapshotCard {...snapshotA} />
{snapshotB && <SnapshotCard {...snapshotB} />}
</div>
))}
</div>
</div>
);
}

View File

@@ -0,0 +1,33 @@
import React from "react";
import SnapshotCard from "./SnapshotCard.tsx";
interface Snapshot {
id: string;
timestamp: string;
progress: number;
status: "pending" | "in-progress" | "completed" | "error";
}
interface ServerCardProps {
serverName: string;
snapshots: Snapshot[];
}
export default function ServerCard({ serverName, snapshots }: ServerCardProps) {
return (
<div className="w-full p-6 border rounded-lg shadow-md bg-gray-50 dark:bg-gray-800">
<h2 className="text-2xl font-bold mb-6 text-gray-800 dark:text-gray-200">{serverName}</h2>
<div className="grid grid-cols-2 gap-6">
{snapshots.map((snapshot) => (
<SnapshotCard
key={snapshot.id}
id={snapshot.id}
timestamp={snapshot.timestamp}
progress={snapshot.progress}
status={snapshot.status}
/>
))}
</div>
</div>
);
}

View File

@@ -0,0 +1,36 @@
import React from "react";
interface Snapshot {
id: string;
timestamp: string;
progress: number;
status: "pending" | "in-progress" | "completed" | "error";
}
export default function SnapshotCard({ id, timestamp, progress, status }: Snapshot) {
const statusColors = {
completed: "bg-green-100 dark:bg-green-800",
"in-progress": "bg-blue-100 dark:bg-blue-800",
error: "bg-red-100 dark:bg-red-800",
pending: "bg-gray-100 dark:bg-gray-800",
};
return (
<div className={`p-4 rounded-lg shadow-sm ${statusColors[status]}`}>
<div className="text-lg font-semibold text-gray-700 dark:text-gray-200">{id}</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
{new Date(timestamp).toLocaleString()}
</div>
{status === "in-progress" && (
<div className="mt-2">
<div className="w-full bg-gray-300 dark:bg-gray-700 rounded-full h-2.5">
<div
className="bg-blue-500 dark:bg-blue-400 h-2.5 rounded-full"
style={{ width: `${progress}%` }}
></div>
</div>
</div>
)}
</div>
);
}

View File

@@ -1,3 +1,5 @@
@import "tailwindcss";
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;

View File

@@ -1,24 +0,0 @@
import { useEffect, useState } from "react";
import { Link, useParams } from "react-router-dom";
import { Dino } from "../types";
export default function Dinosaur() {
const { selectedDinosaur } = useParams();
const [dinosaur, setDino] = useState<Dino>({ name: "", description: "" });
useEffect(() => {
(async () => {
const resp = await fetch(`/api/dinosaurs/${selectedDinosaur}`);
const dino = await resp.json() as Dino;
setDino(dino);
})();
}, [selectedDinosaur]);
return (
<div>
<h1>{dinosaur.name}</h1>
<p>{dinosaur.description}</p>
<Link to="/">🠠 Back to all dinosaurs</Link>
</div>
);
}

View File

@@ -0,0 +1,70 @@
import { useEffect, useState } from "react";
import History from "../components/History.tsx";
interface Snapshot {
id: string;
timestamp: string;
progress: number;
status: "pending" | "in-progress" | "completed" | "error";
}
interface ServerData {
name: string;
current: Snapshot[];
history: Snapshot[];
}
export default function HistoryPage() {
const [serverA, setServerA] = useState<ServerData | null>(null);
const [serverB, setServerB] = useState<ServerData | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch("/api/snapshots");
const data = await response.json();
setServerA(data.serverA);
setServerB(data.serverB);
} catch (error) {
console.error("Error fetching snapshot data:", error);
} finally {
setLoading(false);
}
}
fetchData();
}, []);
if (loading) {
return <div>Loading...</div>;
}
if (!serverA || !serverB) {
return <div>Error loading server data.</div>;
}
return (
<div className="min-h-dvh flex flex-col bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-gray-200">
{/* Header */}
<header className="bg-gray-200 dark:bg-gray-800 p-4 shadow-md">
<h1 className="text-3xl font-bold text-center">Snapshot History</h1>
</header>
{/* Main Content */}
<main className="flex-1 p-6">
<History
currentA={serverA.current}
historyA={serverA.history}
currentB={serverB.current}
historyB={serverB.history}
/>
</main>
{/* Footer */}
<footer className="bg-gray-200 dark:bg-gray-800 p-4 text-center">
<p>&copy; 2023 ZFS Monitoring. All rights reserved.</p>
</footer>
</div>
);
}

View File

@@ -1,33 +1,124 @@
import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { Dino } from "../types.ts";
import { useNavigate } from "react-router-dom";
interface Snapshot {
id: string;
timestamp: string;
progress: number;
status: "pending" | "in-progress" | "completed" | "error";
}
interface ServerData {
name: string;
current: Snapshot[];
history: Snapshot[];
}
export default function Index() {
const [dinosaurs, setDinosaurs] = useState<Dino[]>([]);
const navigate = useNavigate();
const [serverA, setServerA] = useState<ServerData | null>(null);
const [serverB, setServerB] = useState<ServerData | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
(async () => {
const response = await fetch(`/api/dinosaurs/`);
const allDinosaurs = await response.json() as Dino[];
setDinosaurs(allDinosaurs);
})();
async function fetchData() {
try {
const response = await fetch("/api/snapshots");
const data = await response.json();
setServerA(data.serverA);
setServerB(data.serverB);
} catch (error) {
console.error("Error fetching snapshot data:", error);
} finally {
setLoading(false);
}
}
fetchData();
}, []);
if (loading) {
return <div>Loading...</div>;
}
if (!serverA || !serverB) {
return <div>Error loading server data.</div>;
}
const latestSnapshot = (server: ServerData) =>
server.current.length > 0 ? server.current[0] : null;
const recentHistory = (server: ServerData) =>
server.history.slice(0, 3);
return (
<main>
<h1>Welcome to the Dinosaur app</h1>
<p>Click on a dinosaur below to learn more.</p>
{dinosaurs.map((dinosaur: Dino) => {
return (
<Link
to={`/${dinosaur.name.toLowerCase()}`}
key={dinosaur.name}
className="dinosaur"
>
{dinosaur.name}
</Link>
);
})}
</main>
<div className="min-h-dvh flex flex-col bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-gray-200">
{/* Header */}
<header className="bg-gray-200 dark:bg-gray-800 p-4 shadow-md">
<h1 className="text-3xl font-bold text-center">ZFS Snapshot Monitoring Dashboard</h1>
</header>
{/* Main Content */}
<main className="flex-1 p-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Server Section */}
<div className="p-6 border rounded-lg shadow-md bg-blue-500 text-white">
<h2 className="text-2xl font-bold mb-4">Server Overview</h2>
<div className="space-y-4">
{[serverA, serverB].map((server, index) => (
<div
key={index}
className="p-4 border rounded-lg bg-blue-600 hover:bg-blue-700 transition"
onClick={() => navigate("/servers")}
>
<h3 className="text-xl font-bold">{server.name}</h3>
{latestSnapshot(server) ? (
<p className="mt-2">
Latest Snapshot:{" "}
<span className="font-mono">
{latestSnapshot(server)?.timestamp}
</span>
</p>
) : (
<p className="mt-2">No snapshots available.</p>
)}
</div>
))}
</div>
</div>
{/* History Section */}
<div className="p-6 border rounded-lg shadow-md bg-green-500 text-white">
<h2 className="text-2xl font-bold mb-4">Recent History</h2>
<div className="space-y-4">
{[serverA, serverB].map((server, index) => (
<div
key={index}
className="p-4 border rounded-lg bg-green-600 hover:bg-green-700 transition"
onClick={() => navigate("/history")}
>
<h3 className="text-xl font-bold">{server.name}</h3>
<ul className="mt-2 space-y-1">
{recentHistory(server).map((snapshot) => (
<li
key={snapshot.id}
className="font-mono text-sm"
>
{snapshot.timestamp} - {snapshot.status}
</li>
))}
</ul>
</div>
))}
</div>
</div>
</div>
</main>
{/* Footer */}
<footer className="bg-gray-200 dark:bg-gray-800 p-4 text-center">
<p>&copy; 2023 ZFS Monitoring. All rights reserved.</p>
</footer>
</div>
);
}
}

View File

@@ -0,0 +1,68 @@
import { useEffect, useState } from "react";
import ServerCard from "../components/ServerCard.tsx";
interface Snapshot {
id: string;
timestamp: string;
progress: number;
status: "pending" | "in-progress" | "completed" | "error";
}
interface ServerData {
name: string;
current: Snapshot[];
history: Snapshot[];
}
export default function ServerPage() {
const [serverA, setServerA] = useState<ServerData | null>(null);
const [serverB, setServerB] = useState<ServerData | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch("/api/snapshots");
const data = await response.json();
setServerA(data.serverA);
setServerB(data.serverB);
} catch (error) {
console.error("Error fetching snapshot data:", error);
} finally {
setLoading(false);
}
}
fetchData();
}, []);
if (loading) {
return <div>Loading...</div>;
}
if (!serverA || !serverB) {
return <div>Error loading server data.</div>;
}
return (
<div className="min-h-dvh flex flex-col bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-gray-200">
{/* Header */}
<header className="bg-gray-200 dark:bg-gray-800 p-4 shadow-md">
<h1 className="text-3xl font-bold text-center">Server Snapshots</h1>
</header>
{/* Main Content */}
<main className="flex-1 p-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<ServerCard serverName={serverA.name} snapshots={serverA.current} />
<ServerCard serverName={serverB.name} snapshots={serverB.current} />
</div>
</main>
{/* Footer */}
<footer className="bg-gray-200 dark:bg-gray-800 p-4 text-center">
<p>&copy; 2023 ZFS Monitoring. All rights reserved.</p>
</footer>
</div>
);
}