mirror of
https://github.com/fankes/beszel.git
synced 2025-10-19 01:39:34 +08:00
updates
This commit is contained in:
4
main.go
4
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
|
||||
|
BIN
site/bun.lockb
BIN
site/bun.lockb
Binary file not shown.
@@ -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"
|
||||
}
|
||||
}
|
||||
|
@@ -52,15 +52,18 @@ export function AddServerButton() {
|
||||
e.preventDefault()
|
||||
const formData = new FormData(e.target as HTMLFormElement)
|
||||
const data = Object.fromEntries(formData) as Record<string, any>
|
||||
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)
|
||||
|
@@ -51,7 +51,7 @@ export default function CpuChart({ chartData }: { chartData: { time: string; cpu
|
||||
/>
|
||||
<Area
|
||||
dataKey="cpu"
|
||||
type="natural"
|
||||
type="monotone"
|
||||
fill="var(--color-cpu)"
|
||||
fillOpacity={0.4}
|
||||
stroke="var(--color-cpu)"
|
||||
|
93
site/src/components/charts/radial.tsx
Normal file
93
site/src/components/charts/radial.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Label, PolarRadiusAxis, RadialBar, RadialBarChart } from 'recharts'
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card'
|
||||
import {
|
||||
ChartConfig,
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from '@/components/ui/chart'
|
||||
const chartData = [{ month: 'january', desktop: 1260, mobile: 570 }]
|
||||
|
||||
const chartConfig = {
|
||||
mobile: {
|
||||
label: 'Mobile',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export function RadialChart() {
|
||||
const totalVisitors = chartData[0].desktop + chartData[0].mobile
|
||||
|
||||
return (
|
||||
<Card className="flex flex-col">
|
||||
<CardHeader className="items-center pb-0">
|
||||
<CardTitle>Radial Chart - Stacked</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-1 items-center pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square w-full max-w-[250px]">
|
||||
<RadialBarChart data={chartData} endAngle={180} innerRadius={80} outerRadius={130}>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<PolarRadiusAxis tick={false} tickLine={false} axisLine={false}>
|
||||
<Label
|
||||
content={({ viewBox }) => {
|
||||
if (viewBox && 'cx' in viewBox && 'cy' in viewBox) {
|
||||
return (
|
||||
<text x={viewBox.cx} y={viewBox.cy} textAnchor="middle">
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={(viewBox.cy || 0) - 16}
|
||||
className="fill-foreground text-2xl font-bold"
|
||||
>
|
||||
{totalVisitors.toLocaleString()}
|
||||
</tspan>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={(viewBox.cy || 0) + 4}
|
||||
className="fill-muted-foreground"
|
||||
>
|
||||
Visitors
|
||||
</tspan>
|
||||
</text>
|
||||
)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</PolarRadiusAxis>
|
||||
<RadialBar
|
||||
dataKey="desktop"
|
||||
stackId="a"
|
||||
cornerRadius={5}
|
||||
fill="var(--color-desktop)"
|
||||
className="stroke-transparent stroke-2"
|
||||
/>
|
||||
<RadialBar
|
||||
dataKey="mobile"
|
||||
fill="var(--color-mobile)"
|
||||
stackId="a"
|
||||
cornerRadius={5}
|
||||
className="stroke-transparent stroke-2"
|
||||
/>
|
||||
</RadialBarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
@@ -97,7 +97,6 @@ export default function CommandPalette() {
|
||||
<CommandShortcut>Admin</CommandShortcut>
|
||||
</CommandItem>
|
||||
<CommandItem
|
||||
keywords={['email']}
|
||||
onSelect={() => {
|
||||
window.location.href = '/_/#/settings/backups'
|
||||
}}
|
||||
|
@@ -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 (
|
||||
<ChartContainer config={chartConfig} className="min-h-[200px] w-full">
|
||||
<BarChart accessibilityLayer data={chartData}>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
tickMargin={10}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip content={<ChartTooltipContent />} />
|
||||
<ChartLegend content={<ChartLegendContent />} />
|
||||
<Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
|
||||
<Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
)
|
||||
}
|
@@ -1,8 +1,8 @@
|
||||
export function Logo({ className }: { className?: string }) {
|
||||
return (
|
||||
// audiowide
|
||||
<svg viewBox="0 0 300 82" className={className}>
|
||||
<path d="M202.1 36v35h-12.6V36a7.5 7.5 0 0 0-.2-1.5q-.1-.8-.5-1.4a3.9 3.9 0 0 0-.7-.9q-1-1-3-1.3a9.2 9.2 0 0 0-.9 0h-16.5V71H155V24.5a6.3 6.3 0 0 1 .4-2.2 6 6 0 0 1 .1-.3q.5-1.1 1.4-2 .9-.8 2-1.3a6.4 6.4 0 0 1 2.5-.5h23a17.2 17.2 0 0 1 3 .3 22.8 22.8 0 0 1 2.6.6q3 1 5.8 3v-3.9h17.1a17.7 17.7 0 0 1 2.6.2 21.3 21.3 0 0 1 1.2.2 19.5 19.5 0 0 1 3.9 1.3 21.5 21.5 0 0 1 .1 0q2 .9 3.8 2.3a15 15 0 0 1 3.2 3.3 16.3 16.3 0 0 1 0 0 16.3 16.3 0 0 1 1.6 3 19.7 19.7 0 0 1 .6 1.6q.8 2.6.8 5.9v35H218V36a8 8 0 0 0-.1-1.5q-.3-1.4-1.1-2.3-1.3-1.3-3.9-1.3h-11.4q.6 2.4.6 5Zm94.8-.3v17.8a17.3 17.3 0 0 1-.3 3 23.1 23.1 0 0 1-.7 2.7 17 17 0 0 1-3 5.7 15.9 15.9 0 0 1-3.6 3.3 19.6 19.6 0 0 1-1.8 1Q284.4 71 280 71a25.4 25.4 0 0 1-.6 0h-22.9a17.3 17.3 0 0 1-3-.3 23.1 23.1 0 0 1-2.7-.6 17 17 0 0 1-5.7-3 15.9 15.9 0 0 1-3.3-3.7 19.6 19.6 0 0 1-1-1.8q-1.7-3.1-1.8-7.5a25.4 25.4 0 0 1 0-.6 17.3 17.3 0 0 1 .3-3 23.4 23.4 0 0 1 .6-2.7q1-3 3-5.7a15.9 15.9 0 0 1 3.7-3.3 19.6 19.6 0 0 1 1.8-1q3.1-1.7 7.5-1.8a25.4 25.4 0 0 1 .6 0h22.9V48h-22.9a6.8 6.8 0 0 0-1.6.2 4.6 4.6 0 0 0-2.4 1.4A5.5 5.5 0 0 0 251 53a7.3 7.3 0 0 0 0 .6 6 6 0 0 0 .2 1.7 4.4 4.4 0 0 0 1.4 2.2 5.8 5.8 0 0 0 3.9 1.4h22.8a6.9 6.9 0 0 0 1.6-.2 4.6 4.6 0 0 0 2.4-1.4 5.4 5.4 0 0 0 1.4-3.2 7.2 7.2 0 0 0 0-.7V35.7a6.5 6.5 0 0 0-.2-1.7 4.8 4.8 0 0 0-1.3-2.3q-1.6-1.4-4-1.4h-27.8v-12h27.9a17.3 17.3 0 0 1 3 .2 23.1 23.1 0 0 1 2.7.6 17 17 0 0 1 5.6 3 15.9 15.9 0 0 1 3.4 3.7 19.6 19.6 0 0 1 1 1.8q1.7 3.1 1.8 7.5a25.4 25.4 0 0 1 0 .6ZM75.7 29.3v13.4a32.8 32.8 0 0 1-.8 7.1 29.5 29.5 0 0 1-.5 2 29 29 0 0 1-3.1 7 26.9 26.9 0 0 1-.6 1 27 27 0 0 1-5.7 6 27 27 0 0 1-7.4 4.2l14.8 11.8H54L41.8 72H29.3a32.1 32.1 0 0 1-8.2-1 28.7 28.7 0 0 1-3.5-1.2q-5.3-2.2-9.3-6a28.2 28.2 0 0 1-6-9.4A29.6 29.6 0 0 1 0 45.2a35.1 35.1 0 0 1-.1-2.5V29.3A31.8 31.8 0 0 1 1 21a28.5 28.5 0 0 1 1.2-3.4 28.1 28.1 0 0 1 5.2-8.3 26.7 26.7 0 0 1 1-1 28 28 0 0 1 9.1-6 31.8 31.8 0 0 1 .1-.1A29.9 29.9 0 0 1 27.4.1a35 35 0 0 1 1.9-.1h17.2a31.6 31.6 0 0 1 8.2 1 28.2 28.2 0 0 1 3.4 1.2 28.1 28.1 0 0 1 9.3 6 27.5 27.5 0 0 1 6 9 31.3 31.3 0 0 1 0 .4 30.1 30.1 0 0 1 2.2 9.6 35.4 35.4 0 0 1 0 2.1Zm68.5 6.7v17.2a22.2 22.2 0 0 1-.2 3 17 17 0 0 1-.6 2.9 18.6 18.6 0 0 1-1.2 2.8 15.3 15.3 0 0 1-1 1.7 15 15 0 0 1-3.1 3.4 14.2 14.2 0 0 1 0 0 18.5 18.5 0 0 1-3.8 2.3 19.5 19.5 0 0 1-4 1.3 20.8 20.8 0 0 1-2.4.3 17 17 0 0 1-1.5.1h-22.9q-2.6 0-5.7-1-3.2-.9-5.8-3a16.3 16.3 0 0 1-3.4-3.7 20 20 0 0 1-1-1.8 14.7 14.7 0 0 1-1.4-3.8q-.4-1.7-.4-3.6a26.1 26.1 0 0 1 0-1V36q0-4.4 1.4-7.6a13.2 13.2 0 0 1 .3-.7 18.6 18.6 0 0 1 2.4-3.5 15.6 15.6 0 0 1 2-2q2.7-2.1 5.9-3a24.2 24.2 0 0 1 2.8-.7q1.5-.3 3-.3h22.8a22.7 22.7 0 0 1 3.7.3q2.2.4 4 1.2a13.5 13.5 0 0 1 .6.3 18 18 0 0 1 3.4 2.2 15 15 0 0 1 2.1 2.2q2.1 2.7 3 5.8a23.3 23.3 0 0 1 .8 3 17.3 17.3 0 0 1 .2 2.8ZM63 42.7V29.3a20.4 20.4 0 0 0-.4-4 16.6 16.6 0 0 0-.8-2.8 15.4 15.4 0 0 0-2.5-4.2 14.3 14.3 0 0 0-.9-1 15 15 0 0 0-4.8-3.2 17.2 17.2 0 0 0-.4-.2 17.5 17.5 0 0 0-4.9-1.1 21 21 0 0 0-1.8-.1H29.3a20 20 0 0 0-4 .4 16.6 16.6 0 0 0-2.8.8q-3 1.2-5.2 3.4a14.8 14.8 0 0 0-3.3 5 17 17 0 0 0-.1.2 17.3 17.3 0 0 0-1 4.4 21.6 21.6 0 0 0-.2 2.4v13.4a20.4 20.4 0 0 0 .4 4 16.6 16.6 0 0 0 .8 2.8 15 15 0 0 0 2.7 4.5 14.3 14.3 0 0 0 .7.7 15.2 15.2 0 0 0 5 3.3 17.5 17.5 0 0 0 .2 0 17.4 17.4 0 0 0 4.7 1.2 21.3 21.3 0 0 0 2.1 0h17a20 20 0 0 0 4.2-.3A16.6 16.6 0 0 0 53 58q3.1-1.2 5.3-3.4a14.8 14.8 0 0 0 3.3-5 17 17 0 0 0 0-.2A17.3 17.3 0 0 0 63 45a21.6 21.6 0 0 0 0-2.4Zm68.5 10.5V36a8.4 8.4 0 0 0 0-1.5l-.5-1.4a3.7 3.7 0 0 0-.8-1 4.2 4.2 0 0 0-1.7-1l-1.4-.3a8.9 8.9 0 0 0-.7 0h-22.8q-1.8 0-3 .6a4 4 0 0 0-.8.7q-1.2 1.1-1.3 3.2a8.7 8.7 0 0 0 0 .6v17.2q0 1.8.7 3a4 4 0 0 0 .6.8 4.2 4.2 0 0 0 1.7 1l1.5.3a8.9 8.9 0 0 0 .6 0h22.8a8 8 0 0 0 1.5-.1l1.4-.5a3.7 3.7 0 0 0 1-.7 4.2 4.2 0 0 0 1-1.7q.2-.7.2-1.5a8.9 8.9 0 0 0 0-.6Z" />
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 353.3 75.1" className={className}>
|
||||
<path d="M118.9 57.3H96v-12h22.9a6.8 6.8 0 0 0 1.6-.3 4.6 4.6 0 0 0 2.4-1.4 5.5 5.5 0 0 0 1.4-3.3 7.3 7.3 0 0 0 0-.6 6.3 6.3 0 0 0-.2-1.7 4.5 4.5 0 0 0-1.4-2.2 5.5 5.5 0 0 0-3.7-1.4 7 7 0 0 0-.1 0H96a6.8 6.8 0 0 0-1.6.2A4.6 4.6 0 0 0 92 36a5.5 5.5 0 0 0-1.4 3.3 7.3 7.3 0 0 0 0 .6v17.7a6.5 6.5 0 0 0 .2 1.7 4.6 4.6 0 0 0 1.4 2.3 5.5 5.5 0 0 0 3.3 1.4 7.3 7.3 0 0 0 .6 0h22.8V75H96a17.3 17.3 0 0 1-3-.3 23.1 23.1 0 0 1-2.7-.6 17 17 0 0 1-5.7-3 15.9 15.9 0 0 1-3.3-3.7 19.6 19.6 0 0 1-1-1.8q-1.7-3.1-1.8-7.5a25.4 25.4 0 0 1 0-.6V39.8a17.3 17.3 0 0 1 .3-3 23.1 23.1 0 0 1 .6-2.7 17 17 0 0 1 3-5.7 15.9 15.9 0 0 1 3.7-3.3 19.6 19.6 0 0 1 1.8-1q3.1-1.7 7.5-1.8a25.4 25.4 0 0 1 .6 0h22.9a17.3 17.3 0 0 1 3 .3 23.1 23.1 0 0 1 2.7.6 17 17 0 0 1 5.6 3 15.9 15.9 0 0 1 3.4 3.7 19.6 19.6 0 0 1 1 1.8q1.7 3.1 1.8 7.5a25.4 25.4 0 0 1 0 .6 17.3 17.3 0 0 1-.3 3 23.1 23.1 0 0 1-.7 2.7 17 17 0 0 1-3 5.6 15.9 15.9 0 0 1-3.6 3.4 19.6 19.6 0 0 1-1.8 1q-3.1 1.7-7.5 1.8a25.4 25.4 0 0 1-.6 0Zm185.2 0h-23v-12h23a6.8 6.8 0 0 0 1.6-.3 4.6 4.6 0 0 0 2.4-1.4 5.5 5.5 0 0 0 1.3-3.3 7.3 7.3 0 0 0 0-.6 6.3 6.3 0 0 0-.1-1.7 4.5 4.5 0 0 0-1.4-2.2 5.5 5.5 0 0 0-3.7-1.4 7 7 0 0 0-.1 0h-23a6.8 6.8 0 0 0-1.6.2 4.6 4.6 0 0 0-2.4 1.4 5.5 5.5 0 0 0-1.3 3.3 7.3 7.3 0 0 0 0 .6v17.7a6.5 6.5 0 0 0 .1 1.7 4.6 4.6 0 0 0 1.4 2.3 5.5 5.5 0 0 0 3.3 1.4 7.3 7.3 0 0 0 .6 0h22.9V75h-23a17.3 17.3 0 0 1-3-.3 23.1 23.1 0 0 1-2.6-.6 17 17 0 0 1-5.7-3 15.9 15.9 0 0 1-3.3-3.7 19.6 19.6 0 0 1-1-1.8q-1.7-3.1-1.8-7.5a25.4 25.4 0 0 1 0-.6V39.8a17.3 17.3 0 0 1 .3-3 23.1 23.1 0 0 1 .6-2.7 17 17 0 0 1 3-5.7 15.9 15.9 0 0 1 3.6-3.3 19.6 19.6 0 0 1 1.8-1q3.2-1.7 7.6-1.8a25.4 25.4 0 0 1 .6 0H304a17.3 17.3 0 0 1 3 .3 23.1 23.1 0 0 1 2.6.6 17 17 0 0 1 5.7 3 15.9 15.9 0 0 1 3.3 3.7 19.6 19.6 0 0 1 1 1.8q1.7 3.1 1.8 7.5a25.4 25.4 0 0 1 0 .6 17.3 17.3 0 0 1-.3 3 23.1 23.1 0 0 1-.6 2.7 17 17 0 0 1-3 5.6 15.9 15.9 0 0 1-3.6 3.4 19.6 19.6 0 0 1-1.8 1q-3.2 1.7-7.6 1.8a25.4 25.4 0 0 1-.5 0ZM178 75h-34.3V62.4H178a8 8 0 0 0 1.5-.1l1.5-.5a4 4 0 0 0 1-.7 4.2 4.2 0 0 0 1-1.8q.2-.7.2-1.5a8.7 8.7 0 0 0 0-.5 7.7 7.7 0 0 0-.3-2q-.8-2.4-3.1-3a7.2 7.2 0 0 0-1.7-.1h-19.7a20 20 0 0 1-3.2-.2q-1.7-.3-3.2-1a11.7 11.7 0 0 1-.7-.3 16 16 0 0 1-2.9-1.8 13.1 13.1 0 0 1-1.8-1.8 14.3 14.3 0 0 1-2.2-3.5 13.2 13.2 0 0 1-.5-1.3q-.8-2.5-.8-4.7a19.8 19.8 0 0 1 .3-3.2q.2-1.7.9-3.2a11.3 11.3 0 0 1 .3-.8 15.4 15.4 0 0 1 2-3 13 13 0 0 1 1.8-1.7 15 15 0 0 1 5-2.6 20.2 20.2 0 0 1 2.6-.6 15.4 15.4 0 0 1 2.4-.2h31.2V35h-31q-1 0-1.6.2a2 2 0 0 0 0 0 2.5 2.5 0 0 0-.4.2l-.3.3a1.3 1.3 0 0 0-.1.1 1.7 1.7 0 0 0-.3.5 1.5 1.5 0 0 0 0 .3v.8a4.6 4.6 0 0 0 0 .5v.4a1.9 1.9 0 0 0 .2.3 1.9 1.9 0 0 0 .2.4 1.4 1.4 0 0 0 .4.3 1.9 1.9 0 0 0 .6.2 2.3 2.3 0 0 0 .2 0 17.2 17.2 0 0 0 1 0 16 16 0 0 0 0 0H178a22.7 22.7 0 0 1 3.8.3q2.1.4 3.9 1.1a13.5 13.5 0 0 1 .6.4 18 18 0 0 1 3.4 2.2 15 15 0 0 1 2.1 2.2q2.1 2.6 3 5.8a23.3 23.3 0 0 1 .8 3 17.3 17.3 0 0 1 .2 2.8 22.2 22.2 0 0 1-.2 3 17 17 0 0 1-.6 2.9A18.6 18.6 0 0 1 194 66a15.3 15.3 0 0 1-1 1.7 15 15 0 0 1-3.1 3.4 14.2 14.2 0 0 1 0 0 18.5 18.5 0 0 1-3.8 2.3 19.5 19.5 0 0 1-4 1.3 20.8 20.8 0 0 1-2.4.3 17 17 0 0 1-1.5.1Zm75.5-42-29.4 29.3h30.5v12.7H209q-2 0-3.5-1.1a6.8 6.8 0 0 1-2.4-2.8 6.4 6.4 0 0 1-.5-2.5 6.5 6.5 0 0 1 .2-1.2 6.3 6.3 0 0 1 1.7-3.3L233.6 35h-30.5V22.3h46a6.3 6.3 0 0 1 3.4 1q1.6 1 2.3 3 .6 1.2.6 2.4a6 6 0 0 1-.2 1.2q-.3 1.8-1.6 3.2ZM70 57.3v2.4a15.6 15.6 0 0 1-.3 2.8 20.5 20.5 0 0 1-.5 2.2q-.8 2.7-2.7 5a14 14 0 0 1-3.2 3 17.2 17.2 0 0 1-1.5.9q-3 1.5-7.2 1.5H6.4a6.7 6.7 0 0 1-2-.3 6.1 6.1 0 0 1-.5-.2 6.2 6.2 0 0 1-2-1.3 6.2 6.2 0 0 1-1.4-2A6.4 6.4 0 0 1 0 69a7.5 7.5 0 0 1 0-.3V11.5A6.4 6.4 0 0 1 .5 9Q1 7.8 1.8 7a6 6 0 0 1 2-1.4A6.4 6.4 0 0 1 6.2 5a7.5 7.5 0 0 1 .3 0h42.5a15.2 15.2 0 0 1 2.7.2A20 20 0 0 1 54 6q2.7.8 5 2.7a14 14 0 0 1 3 3.3 17 17 0 0 1 .9 1.4q1.5 3 1.5 7.2V23a23.2 23.2 0 0 1-.3 4 30.7 30.7 0 0 1-.8 3.3 23.9 23.9 0 0 1-3.5 7 26.8 26.8 0 0 1-.1.3 22.4 22.4 0 0 1 4 3.2 20.1 20.1 0 0 1 3 3.8 22.6 22.6 0 0 1 .3.5 21.5 21.5 0 0 1 1.7 3.6 26 26 0 0 1 .5 1.9 23.8 23.8 0 0 1 .7 4 30 30 0 0 1 .2 2.8Zm-12.7 2.3v-2.3a13.6 13.6 0 0 0-.2-2.5 10.7 10.7 0 0 0-.6-2 10 10 0 0 0-1.8-3 9.4 9.4 0 0 0-.4-.4 9.4 9.4 0 0 0-3.1-2 11 11 0 0 0-.3-.2 11.6 11.6 0 0 0-2.7-.6 14.7 14.7 0 0 0-1.8-.1H17.8V33.7h22.9a14.1 14.1 0 0 0 2.5-.2 11.2 11.2 0 0 0 2-.6 9.7 9.7 0 0 0 2.7-1.5 9 9 0 0 0 .7-.6 9.5 9.5 0 0 0 2.1-3.3 11 11 0 0 0 0-.1 11.3 11.3 0 0 0 .7-2.7 14.6 14.6 0 0 0 .1-1.8v-2.4q0-2.7-2.6-2.7H12.7v44.6h41.9a6 6 0 0 0 .3 0h.5a2 2 0 0 0 .7-.2 2 2 0 0 0 .2-.1 1.5 1.5 0 0 0 .3-.3l.4-.6.3-1a6 6 0 0 0 0-.6Zm296 2.8v12.7h-5.7a18.4 18.4 0 0 1-3.3-.3 23.8 23.8 0 0 1-2.5-.6 17.2 17.2 0 0 1-5.6-3 19.5 19.5 0 0 1-.2 0 16 16 0 0 1-3.5-4 19.4 19.4 0 0 1-1-1.6q-1.6-3-1.7-7.4a26.5 26.5 0 0 1 0-1V0h12.7v57.3a6 6 0 0 0 .2 1.6 4.5 4.5 0 0 0 1.2 2.1 5 5 0 0 0 3.3 1.4 6.6 6.6 0 0 0 .4 0h5.7Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
@@ -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 (
|
||||
<>
|
||||
<div className="grid gap-6 mb-10">
|
||||
<div className="p-6">
|
||||
<h1 className="text-3xl font-semibold">{name}</h1>
|
||||
</div>
|
||||
<Card className="pb-2">
|
||||
<div className="grid gap-6 mb-10 grid-cols-3">
|
||||
<Card className="col-span-full">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex gap-2 items-center text-3xl">{name}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex items-center justify-between gap-6">
|
||||
<p>{server.status}</p>
|
||||
<p>Uptime {(server.info?.u / 86400).toLocaleString()} days</p>
|
||||
<p>
|
||||
{server.info?.m} ({server.info?.c} cores / {server.info?.t} threads)
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<RadialChart />
|
||||
<RadialChart />
|
||||
<RadialChart />
|
||||
|
||||
<Card className="pb-3 col-span-full">
|
||||
<CardHeader className="pb-5">
|
||||
<CardTitle className="flex gap-2 justify-between">
|
||||
<span>Total CPU Usage</span>
|
||||
<CpuIcon className="opacity-70" />
|
||||
@@ -162,8 +185,8 @@ export default function ServerDetail({ name }: { name: string }) {
|
||||
</CardContent>
|
||||
</Card>
|
||||
{containerCpuChartData.length > 0 && (
|
||||
<Card className="pb-2">
|
||||
<CardHeader>
|
||||
<Card className="pb-3 col-span-full">
|
||||
<CardHeader className="pb-5">
|
||||
<CardTitle className="flex gap-2 justify-between">
|
||||
<span>Docker CPU Usage</span>
|
||||
<CpuIcon className="opacity-70" />
|
||||
@@ -177,8 +200,8 @@ export default function ServerDetail({ name }: { name: string }) {
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
<Card className="pb-2">
|
||||
<CardHeader>
|
||||
<Card className="pb-3 col-span-full">
|
||||
<CardHeader className="pb-5">
|
||||
<CardTitle>Total Memory Usage</CardTitle>
|
||||
<CardDescription>Precise utilization at the recorded time</CardDescription>
|
||||
</CardHeader>
|
||||
@@ -189,8 +212,8 @@ export default function ServerDetail({ name }: { name: string }) {
|
||||
</CardContent>
|
||||
</Card>
|
||||
{containerMemChartData.length > 0 && (
|
||||
<Card className="pb-2">
|
||||
<CardHeader>
|
||||
<Card className="pb-3 col-span-full">
|
||||
<CardHeader className="pb-5">
|
||||
<CardTitle className="flex gap-2 justify-between">
|
||||
<span>Docker Memory Usage</span>
|
||||
<MemoryStickIcon className="opacity-70" />
|
||||
@@ -199,13 +222,13 @@ export default function ServerDetail({ name }: { name: string }) {
|
||||
</CardHeader>
|
||||
<CardContent className={'pl-1 w-[calc(100%-2em)] h-52 relative'}>
|
||||
<Suspense fallback={<Spinner />}>
|
||||
{server?.stats?.m && <ContainerMemChart chartData={containerMemChartData} />}
|
||||
<ContainerMemChart chartData={containerMemChartData} />
|
||||
</Suspense>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
<Card className="pb-2">
|
||||
<CardHeader>
|
||||
<Card className="pb-3 col-span-full">
|
||||
<CardHeader className="pb-5">
|
||||
<CardTitle>Disk Usage</CardTitle>
|
||||
<CardDescription>Precise usage at the recorded time</CardDescription>
|
||||
</CardHeader>
|
||||
|
@@ -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<SystemRecord, unknown>) {
|
||||
const val = info.getValue() as number
|
||||
@@ -105,10 +107,13 @@ function sortableHeader(column: Column<SystemRecord, unknown>, name: string, Ico
|
||||
|
||||
export default function SystemsTable() {
|
||||
const data = useStore($servers)
|
||||
// const [deleteServer, setDeleteServer] = useState({} as SystemRecord)
|
||||
const [sorting, setSorting] = useState<SortingState>([])
|
||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
||||
|
||||
// useEffect(() => {
|
||||
// console.log('servers', data)
|
||||
// }, [data])
|
||||
|
||||
const columns: ColumnDef<SystemRecord>[] = 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' }}
|
||||
></span>
|
||||
@@ -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() {
|
||||
<div className={'flex justify-end items-center gap-1'}>
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="ghost" size={'icon'} aria-label="Notifications" data-nolink>
|
||||
<Button variant="ghost" size={'icon'} aria-label="Alerts" data-nolink>
|
||||
<BellIcon className="h-[1.2em] w-[1.2em] pointer-events-none" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Notifications</DialogTitle>
|
||||
<DialogTitle className="mb-1">Alerts for {name}</DialogTitle>
|
||||
{isAdmin() && (
|
||||
<DialogDescription>
|
||||
Please{' '}
|
||||
<a
|
||||
href="/_/#/settings/mail"
|
||||
className="font-medium text-primary opacity-80 hover:opacity-100 duration-100"
|
||||
>
|
||||
configure an SMTP server
|
||||
</a>{' '}
|
||||
to ensure alerts are delivered.
|
||||
</DialogDescription>
|
||||
)}
|
||||
</DialogHeader>
|
||||
<DialogDescription>
|
||||
The agent must be running on the server to connect. Copy the{' '}
|
||||
<code className="bg-muted px-1 rounded-sm">docker-compose.yml</code> for the
|
||||
agent below.
|
||||
<div className="space-y-2 flex flex-row items-center justify-between rounded-lg border p-4">
|
||||
<div className="space-y-0.5">
|
||||
<label
|
||||
className="font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-base"
|
||||
htmlFor=":r3m:-form-item"
|
||||
>
|
||||
Status change
|
||||
</label>
|
||||
<p
|
||||
id=":r3m:-form-item-description"
|
||||
className="text-[0.8rem] text-muted-foreground"
|
||||
>
|
||||
Triggers when system status switches between up and down.
|
||||
</p>
|
||||
</div>
|
||||
<Switch />
|
||||
</div>
|
||||
</DialogDescription>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
@@ -202,7 +234,7 @@ export default function SystemsTable() {
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
pb.collection('systems').update(id, {
|
||||
status: status === 'paused' ? 'up' : 'paused',
|
||||
status: status === 'paused' ? 'pending' : 'paused',
|
||||
})
|
||||
}}
|
||||
>
|
||||
|
158
site/src/components/ui/select.tsx
Normal file
158
site/src/components/ui/select.tsx
Normal file
@@ -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<typeof SelectPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<ChevronDown className="h-4 w-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
))
|
||||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
||||
|
||||
const SelectScrollUpButton = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollUpButton
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUp className="h-4 w-4" />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
))
|
||||
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
|
||||
|
||||
const SelectScrollDownButton = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollDownButton
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
))
|
||||
SelectScrollDownButton.displayName =
|
||||
SelectPrimitive.ScrollDownButton.displayName
|
||||
|
||||
const SelectContent = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||
>(({ className, children, position = "popper", ...props }, ref) => (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className
|
||||
)}
|
||||
position={position}
|
||||
{...props}
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn(
|
||||
"p-1",
|
||||
position === "popper" &&
|
||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
))
|
||||
SelectContent.displayName = SelectPrimitive.Content.displayName
|
||||
|
||||
const SelectLabel = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
||||
|
||||
const SelectItem = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<Check className="h-4 w-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
))
|
||||
SelectItem.displayName = SelectPrimitive.Item.displayName
|
||||
|
||||
const SelectSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
||||
|
||||
export {
|
||||
Select,
|
||||
SelectGroup,
|
||||
SelectValue,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectLabel,
|
||||
SelectItem,
|
||||
SelectSeparator,
|
||||
SelectScrollUpButton,
|
||||
SelectScrollDownButton,
|
||||
}
|
29
site/src/components/ui/separator.tsx
Normal file
29
site/src/components/ui/separator.tsx
Normal file
@@ -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<typeof SeparatorPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
||||
>(
|
||||
(
|
||||
{ className, orientation = "horizontal", decorative = true, ...props },
|
||||
ref
|
||||
) => (
|
||||
<SeparatorPrimitive.Root
|
||||
ref={ref}
|
||||
decorative={decorative}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"shrink-0 bg-border",
|
||||
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
)
|
||||
Separator.displayName = SeparatorPrimitive.Root.displayName
|
||||
|
||||
export { Separator }
|
27
site/src/components/ui/switch.tsx
Normal file
27
site/src/components/ui/switch.tsx
Normal file
@@ -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<typeof SwitchPrimitives.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SwitchPrimitives.Root
|
||||
className={cn(
|
||||
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
>
|
||||
<SwitchPrimitives.Thumb
|
||||
className={cn(
|
||||
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0"
|
||||
)}
|
||||
/>
|
||||
</SwitchPrimitives.Root>
|
||||
))
|
||||
Switch.displayName = SwitchPrimitives.Root.displayName
|
||||
|
||||
export { Switch }
|
@@ -124,7 +124,7 @@ const Layout = () => {
|
||||
<a
|
||||
href="/"
|
||||
aria-label="Home"
|
||||
className={'p-2 pl-0 -mb-1'}
|
||||
className={'p-2 pl-0'}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
navigate('/')
|
||||
|
47
site/src/types.d.ts
vendored
47
site/src/types.d.ts
vendored
@@ -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
|
||||
}
|
||||
|
Reference in New Issue
Block a user