diff --git a/main.go b/main.go index c5c8e87..4e99ab9 100644 --- a/main.go +++ b/main.go @@ -390,8 +390,8 @@ func handleStatusAlerts(newStatus string, oldRecord *models.Record) error { // send alert sendAlert(EmailData{ to: user.Get("email").(string), - subj: fmt.Sprintf("Server %s is %s %v", systemName, alertStatus, emoji), - body: fmt.Sprintf("Server %s is %s %v", systemName, alertStatus, emoji), + subj: fmt.Sprintf("Connection to %s is %s %v", systemName, alertStatus, emoji), + body: fmt.Sprintf("Connection to %s is %s %v\n\n- Qoma", systemName, alertStatus, emoji), }) } return nil diff --git a/site/bun.lockb b/site/bun.lockb index eb6ce56..1f21930 100755 Binary files a/site/bun.lockb and b/site/bun.lockb differ diff --git a/site/package.json b/site/package.json index 16b5a3a..0874a10 100644 --- a/site/package.json +++ b/site/package.json @@ -1,46 +1,49 @@ { - "name": "site", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview" - }, - "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", - "@radix-ui/react-label": "^2.1.0", - "@radix-ui/react-slot": "^1.1.0", - "@radix-ui/react-toast": "^1.2.1", - "@radix-ui/react-tooltip": "^1.1.2", - "@tanstack/react-table": "^8.19.2", - "@vitejs/plugin-react": "^4.3.1", - "class-variance-authority": "^0.7.0", - "clsx": "^2.1.1", - "cmdk": "^1.0.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.13.0-alpha.1", - "tailwind-merge": "^2.4.0", - "tailwindcss-animate": "^1.0.7", - "valibot": "^0.36.0" - }, - "devDependencies": { - "@types/bun": "^1.1.6", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "autoprefixer": "^10.4.19", - "postcss": "^8.4.39", - "tailwindcss": "^3.4.4", - "typescript": "^5.5.3", - "vite": "^5.3.3" - } + "name": "site", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "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", + "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-select": "^2.1.1", + "@radix-ui/react-separator": "^1.1.0", + "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-switch": "^1.1.0", + "@radix-ui/react-toast": "^1.2.1", + "@radix-ui/react-tooltip": "^1.1.2", + "@tanstack/react-table": "^8.19.2", + "@vitejs/plugin-react": "^4.3.1", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "cmdk": "^1.0.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.13.0-alpha.1", + "tailwind-merge": "^2.4.0", + "tailwindcss-animate": "^1.0.7", + "valibot": "^0.36.0" + }, + "devDependencies": { + "@types/bun": "^1.1.6", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "autoprefixer": "^10.4.19", + "postcss": "^8.4.39", + "tailwindcss": "^3.4.4", + "typescript": "^5.5.3", + "vite": "^5.3.3" + } } diff --git a/site/src/components/add-server.tsx b/site/src/components/add-server.tsx index bf4dbb7..e233e6e 100644 --- a/site/src/components/add-server.tsx +++ b/site/src/components/add-server.tsx @@ -52,15 +52,18 @@ export function AddServerButton() { e.preventDefault() const formData = new FormData(e.target as HTMLFormElement) const data = Object.fromEntries(formData) as Record - data.status = 'down' - data.stats = { - c: 0, - d: 0, - dp: 0, - du: 0, + data.status = 'pending' + data.info = { + cpu: 0, m: 0, - mp: 0, mu: 0, + mp: 0, + mb: 0, + d: 0, + du: 0, + dp: 0, + dr: 0, + dw: 0, } as SystemStats try { setOpen(false) diff --git a/site/src/components/charts/cpu-chart.tsx b/site/src/components/charts/cpu-chart.tsx index 7319a25..6ee3ac3 100644 --- a/site/src/components/charts/cpu-chart.tsx +++ b/site/src/components/charts/cpu-chart.tsx @@ -51,7 +51,7 @@ export default function CpuChart({ chartData }: { chartData: { time: string; cpu /> + + Radial Chart - Stacked + January - June 2024 + + + + + } /> + + + + + + + + +
+ Trending up by 5.2% this month +
+
+ Showing total visitors for the last 6 months +
+
+ + ) +} diff --git a/site/src/components/command-palette.tsx b/site/src/components/command-palette.tsx index b756609..925c993 100644 --- a/site/src/components/command-palette.tsx +++ b/site/src/components/command-palette.tsx @@ -97,7 +97,6 @@ export default function CommandPalette() { Admin { window.location.href = '/_/#/settings/backups' }} diff --git a/site/src/components/example-chart.tsx b/site/src/components/example-chart.tsx deleted file mode 100644 index 5fbb4a6..0000000 --- a/site/src/components/example-chart.tsx +++ /dev/null @@ -1,53 +0,0 @@ -'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 deleted file mode 100644 index e69de29..0000000 diff --git a/site/src/components/logo.tsx b/site/src/components/logo.tsx index a0bc5fe..b9ea42e 100644 --- a/site/src/components/logo.tsx +++ b/site/src/components/logo.tsx @@ -1,8 +1,8 @@ export function Logo({ className }: { className?: string }) { return ( // audiowide - - + + ) } diff --git a/site/src/components/routes/server.tsx b/site/src/components/routes/server.tsx index 7345bcd..097e941 100644 --- a/site/src/components/routes/server.tsx +++ b/site/src/components/routes/server.tsx @@ -9,7 +9,16 @@ import Spinner from '../spinner' // import DiskChart from '../charts/disk-chart' // import ContainerCpuChart from '../charts/container-cpu-chart' // import ContainerMemChart from '../charts/container-mem-chart' -import { CpuIcon, MemoryStickIcon } from 'lucide-react' +import { CpuIcon, MemoryStickIcon, ServerIcon } from 'lucide-react' +import { RadialChart } from '../charts/radial' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' +import { Separator } from '@/components/ui/separator' const CpuChart = lazy(() => import('../charts/cpu-chart')) const ContainerCpuChart = lazy(() => import('../charts/container-cpu-chart')) @@ -87,7 +96,7 @@ export default function ServerDetail({ name }: { name: string }) { const memData = [] as { time: string; mem: number; memUsed: number; memCache: number }[] const diskData = [] as { time: string; disk: number; diskUsed: number }[] for (let { created, stats } of serverStats) { - cpuData.push({ time: created, cpu: stats.c }) + cpuData.push({ time: created, cpu: stats.cpu }) // maxCpu = Math.max(maxCpu, stats.c) memData.push({ time: created, mem: stats.m, memUsed: stats.mu, memCache: stats.mb }) diskData.push({ time: created, disk: stats.d, diskUsed: stats.du }) @@ -135,18 +144,32 @@ export default function ServerDetail({ name }: { name: string }) { containerCpuData.push(cpuData) containerMemData.push(memData) } + // console.log('containerMemData', containerMemData) setContainerCpuChartData(containerCpuData.reverse()) setContainerMemChartData(containerMemData.reverse()) }, [containers]) return ( <> -
-
-

{name}

-
- +
+ + {name} + + +

{server.status}

+

Uptime {(server.info?.u / 86400).toLocaleString()} days

+

+ {server.info?.m} ({server.info?.c} cores / {server.info?.t} threads) +

+
+
+ + + + + + Total CPU Usage @@ -162,8 +185,8 @@ export default function ServerDetail({ name }: { name: string }) { {containerCpuChartData.length > 0 && ( - - + + Docker CPU Usage @@ -177,8 +200,8 @@ export default function ServerDetail({ name }: { name: string }) { )} - - + + Total Memory Usage Precise utilization at the recorded time @@ -189,8 +212,8 @@ export default function ServerDetail({ name }: { name: string }) { {containerMemChartData.length > 0 && ( - - + + Docker Memory Usage @@ -199,13 +222,13 @@ export default function ServerDetail({ name }: { name: string }) { }> - {server?.stats?.m && } + )} - - + + Disk Usage Precise usage at the recorded time diff --git a/site/src/components/server-table/systems-table.tsx b/site/src/components/server-table/systems-table.tsx index 7bd04de..d8f3e62 100644 --- a/site/src/components/server-table/systems-table.tsx +++ b/site/src/components/server-table/systems-table.tsx @@ -57,7 +57,7 @@ import { Trash2Icon, BellIcon, } from 'lucide-react' -import { useMemo, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { $servers, pb, navigate } from '@/lib/stores' import { useStore } from '@nanostores/react' import { AddServerButton } from '../add-server' @@ -70,6 +70,8 @@ import { DialogTitle, DialogHeader, } from '@/components/ui/dialog' +import { Switch } from '@/components/ui/switch' +import { Separator } from '../ui/separator' function CellFormatter(info: CellContext) { const val = info.getValue() as number @@ -105,10 +107,13 @@ function sortableHeader(column: Column, name: string, Ico export default function SystemsTable() { const data = useStore($servers) - // const [deleteServer, setDeleteServer] = useState({} as SystemRecord) const [sorting, setSorting] = useState([]) const [columnFilters, setColumnFilters] = useState([]) + // useEffect(() => { + // console.log('servers', data) + // }, [data]) + const columns: ColumnDef[] = useMemo(() => { return [ { @@ -124,7 +129,8 @@ export default function SystemsTable() { className={cn('w-2 h-2 left-0 rounded-full', { 'bg-green-500': status === 'up', 'bg-red-500': status === 'down', - 'bg-yellow-500': status === 'paused', + 'bg-primary/40': status === 'paused', + 'bg-yellow-500': status === 'pending', })} style={{ marginBottom: '-1px' }} > @@ -143,17 +149,17 @@ export default function SystemsTable() { header: ({ column }) => sortableHeader(column, 'Server', Server), }, { - accessorKey: 'stats.c', + accessorKey: 'info.cpu', cell: CellFormatter, header: ({ column }) => sortableHeader(column, 'CPU', Cpu), }, { - accessorKey: 'stats.mp', + accessorKey: 'info.mp', cell: CellFormatter, header: ({ column }) => sortableHeader(column, 'Memory', MemoryStick), }, { - accessorKey: 'stats.dp', + accessorKey: 'info.dp', cell: CellFormatter, header: ({ column }) => sortableHeader(column, 'Disk', HardDrive), }, @@ -167,18 +173,44 @@ export default function SystemsTable() {
- - Notifications + Alerts for {name} + {isAdmin() && ( + + Please{' '} + + configure an SMTP server + {' '} + to ensure alerts are delivered. + + )} - The agent must be running on the server to connect. Copy the{' '} - docker-compose.yml for the - agent below. +
+
+ +

+ Triggers when system status switches between up and down. +

+
+ +
@@ -202,7 +234,7 @@ export default function SystemsTable() { { pb.collection('systems').update(id, { - status: status === 'paused' ? 'up' : 'paused', + status: status === 'paused' ? 'pending' : 'paused', }) }} > diff --git a/site/src/components/ui/select.tsx b/site/src/components/ui/select.tsx new file mode 100644 index 0000000..fe56d4d --- /dev/null +++ b/site/src/components/ui/select.tsx @@ -0,0 +1,158 @@ +import * as React from "react" +import * as SelectPrimitive from "@radix-ui/react-select" +import { Check, ChevronDown, ChevronUp } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Select = SelectPrimitive.Root + +const SelectGroup = SelectPrimitive.Group + +const SelectValue = SelectPrimitive.Value + +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + span]:line-clamp-1", + className + )} + {...props} + > + {children} + + + + +)) +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName + +const SelectScrollUpButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName + +const SelectScrollDownButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollDownButton.displayName = + SelectPrimitive.ScrollDownButton.displayName + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = "popper", ...props }, ref) => ( + + + + + {children} + + + + +)) +SelectContent.displayName = SelectPrimitive.Content.displayName + +const SelectLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectLabel.displayName = SelectPrimitive.Label.displayName + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + + {children} + +)) +SelectItem.displayName = SelectPrimitive.Item.displayName + +const SelectSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectSeparator.displayName = SelectPrimitive.Separator.displayName + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, +} diff --git a/site/src/components/ui/separator.tsx b/site/src/components/ui/separator.tsx new file mode 100644 index 0000000..6d7f122 --- /dev/null +++ b/site/src/components/ui/separator.tsx @@ -0,0 +1,29 @@ +import * as React from "react" +import * as SeparatorPrimitive from "@radix-ui/react-separator" + +import { cn } from "@/lib/utils" + +const Separator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>( + ( + { className, orientation = "horizontal", decorative = true, ...props }, + ref + ) => ( + + ) +) +Separator.displayName = SeparatorPrimitive.Root.displayName + +export { Separator } diff --git a/site/src/components/ui/switch.tsx b/site/src/components/ui/switch.tsx new file mode 100644 index 0000000..aa58baa --- /dev/null +++ b/site/src/components/ui/switch.tsx @@ -0,0 +1,27 @@ +import * as React from "react" +import * as SwitchPrimitives from "@radix-ui/react-switch" + +import { cn } from "@/lib/utils" + +const Switch = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +Switch.displayName = SwitchPrimitives.Root.displayName + +export { Switch } diff --git a/site/src/main.tsx b/site/src/main.tsx index 112ceb7..50678d8 100644 --- a/site/src/main.tsx +++ b/site/src/main.tsx @@ -124,7 +124,7 @@ const Layout = () => { { e.preventDefault() navigate('/') diff --git a/site/src/types.d.ts b/site/src/types.d.ts index fab340d..65c5d4d 100644 --- a/site/src/types.d.ts +++ b/site/src/types.d.ts @@ -3,28 +3,51 @@ import { RecordModel } from 'pocketbase' export interface SystemRecord extends RecordModel { name: string host: string - status: 'up' | 'down' | 'paused' + status: 'up' | 'down' | 'paused' | 'pending' port: string - stats: SystemStats + info: SystemInfo +} + +export interface SystemInfo { + /** cpu percent */ + cpu: number + /** cpu threads */ + t: number + /** cpu cores */ + c: number + /** cpu model */ + m: string + /** operating system */ + o?: string + /** uptime */ + u: number + /** memory percent */ + mp: number + /** disk percent */ + dp: number } export interface SystemStats { /** cpu percent */ - c: number - /** disk size (gb) */ - d: number - /** disk percent */ - dp: number - /** disk used (gb) */ - du: number + cpu: number /** total memory (gb) */ m: number + /** memory used (gb) */ + mu: number /** memory percent */ mp: number /** memory buffer + cache (gb) */ mb: number - /** memory used (gb) */ - mu: number + /** disk size (gb) */ + d: number + /** disk used (gb) */ + du: number + /** disk percent */ + dp: number + /** disk read (mb) */ + dr: number + /** disk write (mb) */ + dw: number } export interface ContainerStatsRecord extends RecordModel { @@ -43,5 +66,5 @@ interface ContainerStats { export interface SystemStatsRecord extends RecordModel { system: string - stats: SystemStats + info: SystemStats }