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>
);
}

View File

@@ -9,12 +9,14 @@
"@deno/vite-plugin": "npm:@deno/vite-plugin@^1.0.0",
"@oak/oak": "jsr:@oak/oak@^17.1.3",
"@std/assert": "jsr:@std/assert@1",
"@tailwindcss/vite": "npm:@tailwindcss/vite@^4.1.4",
"@tajpouria/cors": "jsr:@tajpouria/cors@^1.2.1",
"@types/react": "npm:@types/react@^18.3.12",
"@vitejs/plugin-react": "npm:@vitejs/plugin-react@^4.3.3",
"react": "npm:react@^18.3.1",
"react-dom": "npm:react-dom@^18.3.1",
"react-router-dom": "npm:react-router-dom@^7.5.1",
"tailwindcss": "npm:tailwindcss@^4.1.4",
"vite": "npm:vite@^5.4.11"
},
"compilerOptions": {

187
deno.lock generated
View File

@@ -21,6 +21,7 @@
"jsr:@std/testing@*": "1.0.5",
"jsr:@tajpouria/cors@^1.2.1": "1.2.1",
"npm:@deno/vite-plugin@1": "1.0.0_vite@5.4.11__@types+node@22.5.4_@types+node@22.5.4",
"npm:@tailwindcss/vite@^4.1.4": "4.1.4_vite@5.4.11__@types+node@22.5.4_@types+node@22.5.4",
"npm:@types/node@*": "22.5.4",
"npm:@types/react@^18.3.12": "18.3.12",
"npm:@vitejs/plugin-react@^4.3.3": "4.3.3_vite@5.4.11__@types+node@22.5.4_@babel+core@7.26.0_@types+node@22.5.4",
@@ -28,6 +29,7 @@
"npm:react-dom@^18.3.1": "18.3.1_react@18.3.1",
"npm:react-router-dom@^7.5.1": "7.5.1_react@18.3.1_react-dom@18.3.1__react@18.3.1",
"npm:react@^18.3.1": "18.3.1",
"npm:tailwindcss@^4.1.4": "4.1.4",
"npm:vite@*": "5.4.11_@types+node@22.5.4",
"npm:vite@^5.4.11": "5.4.11_@types+node@22.5.4"
},
@@ -258,6 +260,25 @@
"vite"
]
},
"@emnapi/core@1.4.3": {
"integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==",
"dependencies": [
"@emnapi/wasi-threads",
"tslib"
]
},
"@emnapi/runtime@1.4.3": {
"integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==",
"dependencies": [
"tslib"
]
},
"@emnapi/wasi-threads@1.0.2": {
"integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==",
"dependencies": [
"tslib"
]
},
"@esbuild/aix-ppc64@0.21.5": {
"integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="
},
@@ -351,6 +372,14 @@
"@jridgewell/sourcemap-codec"
]
},
"@napi-rs/wasm-runtime@0.2.9": {
"integrity": "sha512-OKRBiajrrxB9ATokgEQoG87Z25c67pCpYcCwmXYX8PBftC9pBfN18gnm/fh1wurSLEKIAt+QRFLFCQISrb66Jg==",
"dependencies": [
"@emnapi/core",
"@emnapi/runtime",
"@tybys/wasm-util"
]
},
"@rollup/rollup-android-arm-eabi@4.27.3": {
"integrity": "sha512-EzxVSkIvCFxUd4Mgm4xR9YXrcp976qVaHnqom/Tgm+vU79k4vV4eYTjmRvGfeoW8m9LVcsAy/lGjcgVegKEhLQ=="
},
@@ -405,6 +434,91 @@
"@rollup/rollup-win32-x64-msvc@4.27.3": {
"integrity": "sha512-vliZLrDmYKyaUoMzEbMTg2JkerfBjn03KmAw9CykO0Zzkzoyd7o3iZNam/TpyWNjNT+Cz2iO3P9Smv2wgrR+Eg=="
},
"@tailwindcss/node@4.1.4": {
"integrity": "sha512-MT5118zaiO6x6hNA04OWInuAiP1YISXql8Z+/Y8iisV5nuhM8VXlyhRuqc2PEviPszcXI66W44bCIk500Oolhw==",
"dependencies": [
"enhanced-resolve",
"jiti",
"lightningcss",
"tailwindcss"
]
},
"@tailwindcss/oxide-android-arm64@4.1.4": {
"integrity": "sha512-xMMAe/SaCN/vHfQYui3fqaBDEXMu22BVwQ33veLc8ep+DNy7CWN52L+TTG9y1K397w9nkzv+Mw+mZWISiqhmlA=="
},
"@tailwindcss/oxide-darwin-arm64@4.1.4": {
"integrity": "sha512-JGRj0SYFuDuAGilWFBlshcexev2hOKfNkoX+0QTksKYq2zgF9VY/vVMq9m8IObYnLna0Xlg+ytCi2FN2rOL0Sg=="
},
"@tailwindcss/oxide-darwin-x64@4.1.4": {
"integrity": "sha512-sdDeLNvs3cYeWsEJ4H1DvjOzaGios4QbBTNLVLVs0XQ0V95bffT3+scptzYGPMjm7xv4+qMhCDrkHwhnUySEzA=="
},
"@tailwindcss/oxide-freebsd-x64@4.1.4": {
"integrity": "sha512-VHxAqxqdghM83HslPhRsNhHo91McsxRJaEnShJOMu8mHmEj9Ig7ToHJtDukkuLWLzLboh2XSjq/0zO6wgvykNA=="
},
"@tailwindcss/oxide-linux-arm-gnueabihf@4.1.4": {
"integrity": "sha512-OTU/m/eV4gQKxy9r5acuesqaymyeSCnsx1cFto/I1WhPmi5HDxX1nkzb8KYBiwkHIGg7CTfo/AcGzoXAJBxLfg=="
},
"@tailwindcss/oxide-linux-arm64-gnu@4.1.4": {
"integrity": "sha512-hKlLNvbmUC6z5g/J4H+Zx7f7w15whSVImokLPmP6ff1QqTVE+TxUM9PGuNsjHvkvlHUtGTdDnOvGNSEUiXI1Ww=="
},
"@tailwindcss/oxide-linux-arm64-musl@4.1.4": {
"integrity": "sha512-X3As2xhtgPTY/m5edUtddmZ8rCruvBvtxYLMw9OsZdH01L2gS2icsHRwxdU0dMItNfVmrBezueXZCHxVeeb7Aw=="
},
"@tailwindcss/oxide-linux-x64-gnu@4.1.4": {
"integrity": "sha512-2VG4DqhGaDSmYIu6C4ua2vSLXnJsb/C9liej7TuSO04NK+JJJgJucDUgmX6sn7Gw3Cs5ZJ9ZLrnI0QRDOjLfNQ=="
},
"@tailwindcss/oxide-linux-x64-musl@4.1.4": {
"integrity": "sha512-v+mxVgH2kmur/X5Mdrz9m7TsoVjbdYQT0b4Z+dr+I4RvreCNXyCFELZL/DO0M1RsidZTrm6O1eMnV6zlgEzTMQ=="
},
"@tailwindcss/oxide-wasm32-wasi@4.1.4": {
"integrity": "sha512-2TLe9ir+9esCf6Wm+lLWTMbgklIjiF0pbmDnwmhR9MksVOq+e8aP3TSsXySnBDDvTTVd/vKu1aNttEGj3P6l8Q==",
"dependencies": [
"@emnapi/core",
"@emnapi/runtime",
"@emnapi/wasi-threads",
"@napi-rs/wasm-runtime",
"@tybys/wasm-util",
"tslib"
]
},
"@tailwindcss/oxide-win32-arm64-msvc@4.1.4": {
"integrity": "sha512-VlnhfilPlO0ltxW9/BgfLI5547PYzqBMPIzRrk4W7uupgCt8z6Trw/tAj6QUtF2om+1MH281Pg+HHUJoLesmng=="
},
"@tailwindcss/oxide-win32-x64-msvc@4.1.4": {
"integrity": "sha512-+7S63t5zhYjslUGb8NcgLpFXD+Kq1F/zt5Xv5qTv7HaFTG/DHyHD9GA6ieNAxhgyA4IcKa/zy7Xx4Oad2/wuhw=="
},
"@tailwindcss/oxide@4.1.4": {
"integrity": "sha512-p5wOpXyOJx7mKh5MXh5oKk+kqcz8T+bA3z/5VWWeQwFrmuBItGwz8Y2CHk/sJ+dNb9B0nYFfn0rj/cKHZyjahQ==",
"dependencies": [
"@tailwindcss/oxide-android-arm64",
"@tailwindcss/oxide-darwin-arm64",
"@tailwindcss/oxide-darwin-x64",
"@tailwindcss/oxide-freebsd-x64",
"@tailwindcss/oxide-linux-arm-gnueabihf",
"@tailwindcss/oxide-linux-arm64-gnu",
"@tailwindcss/oxide-linux-arm64-musl",
"@tailwindcss/oxide-linux-x64-gnu",
"@tailwindcss/oxide-linux-x64-musl",
"@tailwindcss/oxide-wasm32-wasi",
"@tailwindcss/oxide-win32-arm64-msvc",
"@tailwindcss/oxide-win32-x64-msvc"
]
},
"@tailwindcss/vite@4.1.4_vite@5.4.11__@types+node@22.5.4_@types+node@22.5.4": {
"integrity": "sha512-4UQeMrONbvrsXKXXp/uxmdEN5JIJ9RkH7YVzs6AMxC/KC1+Np7WZBaNIco7TEjlkthqxZbt8pU/ipD+hKjm80A==",
"dependencies": [
"@tailwindcss/node",
"@tailwindcss/oxide",
"tailwindcss",
"vite"
]
},
"@tybys/wasm-util@0.9.0": {
"integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==",
"dependencies": [
"tslib"
]
},
"@types/babel__core@7.20.5": {
"integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
"dependencies": [
@@ -491,9 +605,19 @@
"ms"
]
},
"detect-libc@2.0.3": {
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="
},
"electron-to-chromium@1.5.63": {
"integrity": "sha512-ddeXKuY9BHo/mw145axlyWjlJ1UBt4WK3AlvkT7W2AbqfRQoacVoRUCF6wL3uIx/8wT9oLKXzI+rFqHHscByaA=="
},
"enhanced-resolve@5.18.1": {
"integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==",
"dependencies": [
"graceful-fs",
"tapable"
]
},
"esbuild@0.21.5": {
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
"dependencies": [
@@ -534,6 +658,12 @@
"globals@11.12.0": {
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
},
"graceful-fs@4.2.11": {
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
},
"jiti@2.4.2": {
"integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="
},
"js-tokens@4.0.0": {
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
@@ -543,6 +673,52 @@
"json5@2.2.3": {
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="
},
"lightningcss-darwin-arm64@1.29.2": {
"integrity": "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA=="
},
"lightningcss-darwin-x64@1.29.2": {
"integrity": "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w=="
},
"lightningcss-freebsd-x64@1.29.2": {
"integrity": "sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg=="
},
"lightningcss-linux-arm-gnueabihf@1.29.2": {
"integrity": "sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg=="
},
"lightningcss-linux-arm64-gnu@1.29.2": {
"integrity": "sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ=="
},
"lightningcss-linux-arm64-musl@1.29.2": {
"integrity": "sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ=="
},
"lightningcss-linux-x64-gnu@1.29.2": {
"integrity": "sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg=="
},
"lightningcss-linux-x64-musl@1.29.2": {
"integrity": "sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w=="
},
"lightningcss-win32-arm64-msvc@1.29.2": {
"integrity": "sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw=="
},
"lightningcss-win32-x64-msvc@1.29.2": {
"integrity": "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA=="
},
"lightningcss@1.29.2": {
"integrity": "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==",
"dependencies": [
"detect-libc",
"lightningcss-darwin-arm64",
"lightningcss-darwin-x64",
"lightningcss-freebsd-x64",
"lightningcss-linux-arm-gnueabihf",
"lightningcss-linux-arm64-gnu",
"lightningcss-linux-arm64-musl",
"lightningcss-linux-x64-gnu",
"lightningcss-linux-x64-musl",
"lightningcss-win32-arm64-msvc",
"lightningcss-win32-x64-msvc"
]
},
"loose-envify@1.4.0": {
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dependencies": [
@@ -653,6 +829,15 @@
"source-map-js@1.2.1": {
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
},
"tailwindcss@4.1.4": {
"integrity": "sha512-1ZIUqtPITFbv/DxRmDr5/agPqJwF69d24m9qmM1939TJehgY539CtzeZRjbLt5G6fSy/7YqqYsfvoTEw9xUI2A=="
},
"tapable@2.2.1": {
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="
},
"tslib@2.8.1": {
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
"turbo-stream@2.4.0": {
"integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g=="
},
@@ -687,11 +872,13 @@
"jsr:@std/assert@1",
"jsr:@tajpouria/cors@^1.2.1",
"npm:@deno/vite-plugin@1",
"npm:@tailwindcss/vite@^4.1.4",
"npm:@types/react@^18.3.12",
"npm:@vitejs/plugin-react@^4.3.3",
"npm:react-dom@^18.3.1",
"npm:react-router-dom@^7.5.1",
"npm:react@^18.3.1",
"npm:tailwindcss@^4.1.4",
"npm:vite@^5.4.11"
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -7,23 +7,11 @@ import data from "./api/data.json" with { type: "json" };
export const app = new Application();
const router = new Router();
router.get("/api/dinosaurs", (context) => {
context.response.body = data;
router.get("/api/snapshots", (context) => {
context.response.headers.set("Content-Type", "application/json");
context.response.body = JSON.stringify(data);
});
router.get("/api/dinosaurs/:dinosaur", (context) => {
if (!context?.params?.dinosaur) {
context.response.body = "No dinosaur name provided.";
}
const dinosaur = data.find((item) =>
item.name.toLowerCase() === context.params.dinosaur.toLowerCase()
);
context.response.body = dinosaur ?? "No dinosaur found.";
});
app.use(oakCors());
app.use(router.routes());
app.use(router.allowedMethods());

View File

@@ -1,6 +1,7 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import deno from "@deno/vite-plugin";
import tailwindcss from '@tailwindcss/vite'
import "react";
import "react-dom";
@@ -18,6 +19,7 @@ export default defineConfig({
},
plugins: [
react(),
tailwindcss(),
deno(),
],
optimizeDeps: {