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 (
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 (
+