diff --git a/site/bun.lockb b/site/bun.lockb index 961a586..5fde890 100755 Binary files a/site/bun.lockb and b/site/bun.lockb differ diff --git a/site/package.json b/site/package.json index bb13a32..f483f2c 100644 --- a/site/package.json +++ b/site/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@nanostores/react": "^0.7.2", + "@nanostores/router": "^0.15.0", "@radix-ui/react-alert-dialog": "^1.1.1", "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dropdown-menu": "^2.1.1", @@ -22,15 +23,15 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cmdk": "^1.0.0", - "lucide-react": "^0.401.0", + "lucide-react": "^0.407.0", "nanostores": "^0.10.3", "pocketbase": "^0.21.3", "react": "^18.3.1", "react-dom": "^18.3.1", + "recharts": "^2.12.7", "tailwind-merge": "^2.4.0", "tailwindcss-animate": "^1.0.7", - "valibot": "^0.36.0", - "wouter": "^3.3.1" + "valibot": "^0.36.0" }, "devDependencies": { "@types/bun": "^1.1.6", diff --git a/site/src/components/command-palette.tsx b/site/src/components/command-palette.tsx index 0513b2a..c1b6254 100644 --- a/site/src/components/command-palette.tsx +++ b/site/src/components/command-palette.tsx @@ -13,11 +13,10 @@ import { CommandShortcut, } from '@/components/ui/command' import { useEffect, useState } from 'react' -import { navigate } from 'wouter/use-browser-location' import { useStore } from '@nanostores/react' -import { $servers } from '@/lib/stores' +import { $servers, navigate } from '@/lib/stores' -export function CommandPalette() { +export default function () { const [open, setOpen] = useState(false) const servers = useStore($servers) diff --git a/site/src/components/cpu-chart.tsx b/site/src/components/cpu-chart.tsx new file mode 100644 index 0000000..cb66075 --- /dev/null +++ b/site/src/components/cpu-chart.tsx @@ -0,0 +1,117 @@ +import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts' + +import { + ChartConfig, + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from '@/components/ui/chart' +import { formatDateShort } from '@/lib/utils' + +const chartData = [ + { month: '2024-07-09 23:29:08.976Z', cpu: 6.2 }, + { month: '2024-07-09 23:28:08.976Z', cpu: 2.8 }, + { month: '2024-07-09 23:27:08.976Z', cpu: 9.5 }, + { month: '2024-07-09 23:26:08.976Z', cpu: 23.4 }, + { month: '2024-07-09 23:25:08.976Z', cpu: 4.3 }, + { month: '2024-07-09 23:24:08.976Z', cpu: 9.1 }, + { month: '2024-07-09 23:29:08.976Z', cpu: 6.2 }, + { month: '2024-07-09 23:28:08.976Z', cpu: 2.8 }, + { month: '2024-07-09 23:27:08.976Z', cpu: 9.5 }, + { month: '2024-07-09 23:26:08.976Z', cpu: 23.4 }, + { month: '2024-07-09 23:25:08.976Z', cpu: 4.3 }, + { month: '2024-07-09 23:24:08.976Z', cpu: 9.1 }, + { month: '2024-07-09 23:29:08.976Z', cpu: 6.2 }, + { month: '2024-07-09 23:28:08.976Z', cpu: 2.8 }, + { month: '2024-07-09 23:27:08.976Z', cpu: 9.5 }, + { month: '2024-07-09 23:26:08.976Z', cpu: 23.4 }, + { month: '2024-07-09 23:25:08.976Z', cpu: 4.3 }, + { month: '2024-07-09 23:24:08.976Z', cpu: 9.1 }, + { month: '2024-07-09 23:25:08.976Z', cpu: 4.3 }, + { month: '2024-07-09 23:24:08.976Z', cpu: 9.1 }, + { month: '2024-07-09 23:29:08.976Z', cpu: 6.2 }, + { month: '2024-07-09 23:28:08.976Z', cpu: 2.8 }, + { month: '2024-07-09 23:27:08.976Z', cpu: 9.5 }, + { month: '2024-07-09 23:26:08.976Z', cpu: 23.4 }, + { month: '2024-07-09 23:25:08.976Z', cpu: 4.3 }, + { month: '2024-07-09 23:24:08.976Z', cpu: 9.1 }, + { month: '2024-07-09 23:25:08.976Z', cpu: 4.3 }, + { month: '2024-07-09 23:24:08.976Z', cpu: 9.1 }, + { month: '2024-07-09 23:29:08.976Z', cpu: 6.2 }, + { month: '2024-07-09 23:28:08.976Z', cpu: 2.8 }, + { month: '2024-07-09 23:27:08.976Z', cpu: 9.5 }, + { month: '2024-07-09 23:26:08.976Z', cpu: 23.4 }, + { month: '2024-07-09 23:25:08.976Z', cpu: 4.3 }, + { month: '2024-07-09 23:24:08.976Z', cpu: 9.1 }, + { month: '2024-07-09 23:25:08.976Z', cpu: 4.3 }, + { month: '2024-07-09 23:24:08.976Z', cpu: 9.1 }, + { month: '2024-07-09 23:29:08.976Z', cpu: 6.2 }, + { month: '2024-07-09 23:28:08.976Z', cpu: 2.8 }, + { month: '2024-07-09 23:27:08.976Z', cpu: 9.5 }, + { month: '2024-07-09 23:26:08.976Z', cpu: 23.4 }, + { month: '2024-07-09 23:25:08.976Z', cpu: 4.3 }, + { month: '2024-07-09 23:24:08.976Z', cpu: 9.1 }, + { month: '2024-07-09 23:25:08.976Z', cpu: 4.3 }, + { month: '2024-07-09 23:24:08.976Z', cpu: 9.1 }, + { month: '2024-07-09 23:29:08.976Z', cpu: 6.2 }, + { month: '2024-07-09 23:28:08.976Z', cpu: 2.8 }, + { month: '2024-07-09 23:27:08.976Z', cpu: 9.5 }, + { month: '2024-07-09 23:26:08.976Z', cpu: 23.4 }, + { month: '2024-07-09 23:25:08.976Z', cpu: 4.3 }, + { month: '2024-07-09 23:24:08.976Z', cpu: 9.1 }, + { month: '2024-07-09 23:25:08.976Z', cpu: 4.3 }, + { month: '2024-07-09 23:24:08.976Z', cpu: 9.1 }, + { month: '2024-07-09 23:29:08.976Z', cpu: 6.2 }, + { month: '2024-07-09 23:28:08.976Z', cpu: 2.8 }, + { month: '2024-07-09 23:27:08.976Z', cpu: 9.5 }, + { month: '2024-07-09 23:26:08.976Z', cpu: 23.4 }, + { month: '2024-07-09 23:25:08.976Z', cpu: 4.3 }, + { month: '2024-07-09 23:24:08.976Z', cpu: 9.1 }, +] + +// for (const data of chartData) { +// data.month = formatDateShort(data.month) +// } + +const chartConfig = { + cpu: { + label: 'cpu', + color: 'hsl(var(--chart-1))', + }, +} satisfies ChartConfig + +export default function () { + return ( + + + + + formatDateShort(value)} + /> + } /> + + + + ) +} diff --git a/site/src/components/example-chart.tsx b/site/src/components/example-chart.tsx new file mode 100644 index 0000000..5fbb4a6 --- /dev/null +++ b/site/src/components/example-chart.tsx @@ -0,0 +1,53 @@ +'use client' + +import { Bar, BarChart, CartesianGrid, XAxis } from 'recharts' + +import { + ChartConfig, + ChartContainer, + ChartLegend, + ChartLegendContent, + ChartTooltip, + ChartTooltipContent, +} from '@/components/ui/chart' + +const chartData = [ + { month: 'January', desktop: 186, mobile: 80 }, + { month: 'February', desktop: 305, mobile: 200 }, + { month: 'March', desktop: 237, mobile: 120 }, + { month: 'April', desktop: 73, mobile: 190 }, + { month: 'May', desktop: 209, mobile: 130 }, + { month: 'June', desktop: 214, mobile: 140 }, +] + +const chartConfig = { + desktop: { + label: 'Desktop', + color: '#2563eb', + }, + mobile: { + label: 'Mobile', + color: '#60a5fa', + }, +} satisfies ChartConfig + +export function Component() { + return ( + + + + value.slice(0, 3)} + /> + } /> + } /> + + + + + ) +} diff --git a/site/src/components/loader.tsx b/site/src/components/loader.tsx new file mode 100644 index 0000000..e69de29 diff --git a/site/src/components/login.tsx b/site/src/components/login.tsx index 996df0d..4aa4b2f 100644 --- a/site/src/components/login.tsx +++ b/site/src/components/login.tsx @@ -1,11 +1,9 @@ -import { Link } from 'wouter' - import { cn } from '@/lib/utils' import { buttonVariants } from '@/components/ui/button' import { UserAuthForm } from '@/components/user-auth-form' import { ChevronLeft } from 'lucide-react' -export default function LoginPage() { +export default function () { return (
@@ -23,9 +21,9 @@ export default function LoginPage() {

- + Don't have an account? Sign Up - +

diff --git a/site/src/components/routes/home.tsx b/site/src/components/routes/home.tsx index 68645bb..3a9a46c 100644 --- a/site/src/components/routes/home.tsx +++ b/site/src/components/routes/home.tsx @@ -1,11 +1,13 @@ -import { useEffect } from 'react' +import { Suspense, lazy, useEffect } from 'react' import { $servers, pb } from '@/lib/stores' -import { DataTable } from '../server-table/data-table' +// import { DataTable } from '../server-table/data-table' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card' import { SystemRecord } from '@/types' import { updateServerList } from '@/lib/utils' -export function Home() { +const DataTable = lazy(() => import('../server-table/data-table')) + +export default function () { useEffect(() => { document.title = 'Home' }, []) @@ -57,7 +59,9 @@ export function Home() { - + + + diff --git a/site/src/components/routes/server.tsx b/site/src/components/routes/server.tsx index 85cb68b..7cc0e20 100644 --- a/site/src/components/routes/server.tsx +++ b/site/src/components/routes/server.tsx @@ -1,24 +1,32 @@ import { $servers, pb } from '@/lib/stores' import { ContainerStatsRecord, SystemRecord } from '@/types' -import { useEffect, useState } from 'react' -import { useRoute } from 'wouter' +import { Suspense, lazy, useEffect, useState } from 'react' import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '../ui/card' import { useStore } from '@nanostores/react' +import Spinner from '../spinner' +// import { CpuChart } from '../cpu-chart' + +const CpuChart = lazy(() => import('../cpu-chart')) function timestampToBrowserTime(timestamp: string) { const date = new Date(timestamp) return date.toLocaleString() } -export function ServerDetail() { +// function addColors(objects: Record[]) { +// objects.forEach((obj, index) => { +// const hue = ((index * 360) / objects.length) % 360 // Distribute hues evenly +// obj.fill = `hsl(${hue}, 100%, 50%)` // Set fill to HSL color with full saturation and 50% lightness +// }) +// } + +export default function ServerDetail({ name }: { name: string }) { const servers = useStore($servers) - const [_, params] = useRoute('/server/:name') const [server, setServer] = useState({} as SystemRecord) const [containers, setContainers] = useState([] as ContainerStatsRecord[]) - // const [serverId, setServerId] = useState('') useEffect(() => { - document.title = params!.name + document.title = name }, []) useEffect(() => { @@ -27,7 +35,7 @@ export function ServerDetail() { return } console.log('running') - const matchingServer = servers.find((s) => s.name === params!.name) as SystemRecord + const matchingServer = servers.find((s) => s.name === name) as SystemRecord setServer(matchingServer) @@ -52,6 +60,20 @@ export function ServerDetail() { return ( <> +
+ + + CPU Usage + Showing total visitors for the last 30 minutes + + + }> + + + + +
+ {server.name} diff --git a/site/src/components/server-table/data-table.tsx b/site/src/components/server-table/data-table.tsx index 8653e54..2c65265 100644 --- a/site/src/components/server-table/data-table.tsx +++ b/site/src/components/server-table/data-table.tsx @@ -48,18 +48,15 @@ import { MoreHorizontal, ArrowUpDown, Copy, - RefreshCcw, Server, Cpu, MemoryStick, HardDrive, } from 'lucide-react' import { useMemo, useState } from 'react' -import { navigate } from 'wouter/use-browser-location' -import { $servers, pb } from '@/lib/stores' +import { $servers, pb, navigate } from '@/lib/stores' import { useStore } from '@nanostores/react' import { AddServerButton } from '../add-server' -import clsx from 'clsx' import { cn, copyToClipboard } from '@/lib/utils' function CellFormatter(info: CellContext) { @@ -74,7 +71,7 @@ function CellFormatter(info: CellContext) {
@@ -97,7 +94,7 @@ function sortableHeader(column: Column, name: string, Ico ) } -export function DataTable() { +export default function () { const data = useStore($servers) const [deleteServer, setDeleteServer] = useState({} as SystemRecord) const [sorting, setSorting] = useState([]) @@ -111,7 +108,7 @@ export function DataTable() { cell: (info) => ( + +
+ ) +} diff --git a/site/src/components/ui/chart.tsx b/site/src/components/ui/chart.tsx new file mode 100644 index 0000000..1b7cbc8 --- /dev/null +++ b/site/src/components/ui/chart.tsx @@ -0,0 +1,361 @@ +import * as React from "react" +import * as RechartsPrimitive from "recharts" + +import { cn } from "@/lib/utils" + +// Format: { THEME_NAME: CSS_SELECTOR } +const THEMES = { light: "", dark: ".dark" } as const + +export type ChartConfig = { + [k in string]: { + label?: React.ReactNode + icon?: React.ComponentType + } & ( + | { color?: string; theme?: never } + | { color?: never; theme: Record } + ) +} + +type ChartContextProps = { + config: ChartConfig +} + +const ChartContext = React.createContext(null) + +function useChart() { + const context = React.useContext(ChartContext) + + if (!context) { + throw new Error("useChart must be used within a ") + } + + return context +} + +const ChartContainer = React.forwardRef< + HTMLDivElement, + React.ComponentProps<"div"> & { + config: ChartConfig + children: React.ComponentProps< + typeof RechartsPrimitive.ResponsiveContainer + >["children"] + } +>(({ id, className, children, config, ...props }, ref) => { + const uniqueId = React.useId() + const chartId = `chart-${id || uniqueId.replace(/:/g, "")}` + + return ( + +
+ + + {children} + +
+
+ ) +}) +ChartContainer.displayName = "Chart" + +const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { + const colorConfig = Object.entries(config).filter( + ([_, config]) => config.theme || config.color + ) + + if (!colorConfig.length) { + return null + } + + return ( +