mirror of
https://github.com/henrygd/beszel.git
synced 2026-03-27 07:56:19 +01:00
Compare commits
4 Commits
quiet-hour
...
4d05bfdff0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d05bfdff0 | ||
|
|
0388401a9e | ||
|
|
162c548010 | ||
|
|
888b4a57e5 |
@@ -48,7 +48,8 @@
|
|||||||
},
|
},
|
||||||
"suspicious": {
|
"suspicious": {
|
||||||
"useAwait": "error",
|
"useAwait": "error",
|
||||||
"noEvolvingTypes": "error"
|
"noEvolvingTypes": "error",
|
||||||
|
"noArrayIndexKey": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<html lang="en" dir="ltr">
|
<html lang="en" dir="ltr">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="manifest" href="./static/manifest.json" />
|
<link rel="manifest" href="./static/manifest.json" crossorigin="use-credentials" />
|
||||||
<link rel="icon" type="image/svg+xml" href="./static/icon.svg" />
|
<link rel="icon" type="image/svg+xml" href="./static/icon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
||||||
<meta name="robots" content="noindex, nofollow" />
|
<meta name="robots" content="noindex, nofollow" />
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
import { t } from "@lingui/core/macro"
|
import { t } from "@lingui/core/macro"
|
||||||
import { Trans } from "@lingui/react/macro"
|
import { Trans } from "@lingui/react/macro"
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
import { MoreHorizontalIcon, PlusIcon, Trash2Icon, ServerIcon, ClockIcon, CalendarIcon, ActivityIcon, PenSquareIcon } from "lucide-react"
|
import {
|
||||||
|
MoreHorizontalIcon,
|
||||||
|
PlusIcon,
|
||||||
|
Trash2Icon,
|
||||||
|
ServerIcon,
|
||||||
|
ClockIcon,
|
||||||
|
CalendarIcon,
|
||||||
|
ActivityIcon,
|
||||||
|
PenSquareIcon,
|
||||||
|
} from "lucide-react"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
|
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
@@ -15,12 +24,7 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog"
|
} from "@/components/ui/dialog"
|
||||||
import {
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/components/ui/dropdown-menu"
|
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { Label } from "@/components/ui/label"
|
import { Label } from "@/components/ui/label"
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||||
@@ -54,7 +58,7 @@ export function QuietHours() {
|
|||||||
.then(({ items }) => setData(items))
|
.then(({ items }) => setData(items))
|
||||||
|
|
||||||
// Subscribe to changes
|
// Subscribe to changes
|
||||||
; (async () => {
|
;(async () => {
|
||||||
unsubscribe = await pb.collection("quiet_hours").subscribe(
|
unsubscribe = await pb.collection("quiet_hours").subscribe(
|
||||||
"*",
|
"*",
|
||||||
(e) => {
|
(e) => {
|
||||||
@@ -62,9 +66,7 @@ export function QuietHours() {
|
|||||||
setData((current) => [e.record as QuietHoursRecord, ...current])
|
setData((current) => [e.record as QuietHoursRecord, ...current])
|
||||||
}
|
}
|
||||||
if (e.action === "update") {
|
if (e.action === "update") {
|
||||||
setData((current) =>
|
setData((current) => current.map((r) => (r.id === e.record.id ? (e.record as QuietHoursRecord) : r)))
|
||||||
current.map((r) => (r.id === e.record.id ? (e.record as QuietHoursRecord) : r))
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
if (e.action === "delete") {
|
if (e.action === "delete") {
|
||||||
setData((current) => current.filter((r) => r.id !== e.record.id))
|
setData((current) => current.filter((r) => r.id !== e.record.id))
|
||||||
@@ -102,8 +104,8 @@ export function QuietHours() {
|
|||||||
const formatDateTime = (record: QuietHoursRecord) => {
|
const formatDateTime = (record: QuietHoursRecord) => {
|
||||||
if (record.type === "daily") {
|
if (record.type === "daily") {
|
||||||
// For daily windows, show only time
|
// For daily windows, show only time
|
||||||
const startTime = new Date(record.start).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
|
const startTime = new Date(record.start).toLocaleTimeString([], { hour: "numeric", minute: "2-digit" })
|
||||||
const endTime = new Date(record.end).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
|
const endTime = new Date(record.end).toLocaleTimeString([], { hour: "numeric", minute: "2-digit" })
|
||||||
return `${startTime} - ${endTime}`
|
return `${startTime} - ${endTime}`
|
||||||
}
|
}
|
||||||
// For one-time windows, show full date and time
|
// For one-time windows, show full date and time
|
||||||
@@ -112,7 +114,7 @@ export function QuietHours() {
|
|||||||
return `${start} - ${end}`
|
return `${start} - ${end}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const getWindowState = (record: QuietHoursRecord): "active" | "past" | "future" => {
|
const getWindowState = (record: QuietHoursRecord): "active" | "past" | "inactive" => {
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
|
|
||||||
if (record.type === "daily") {
|
if (record.type === "daily") {
|
||||||
@@ -132,9 +134,9 @@ export function QuietHours() {
|
|||||||
|
|
||||||
// Handle cases where window spans midnight
|
// Handle cases where window spans midnight
|
||||||
if (localStartMinutes <= localEndMinutes) {
|
if (localStartMinutes <= localEndMinutes) {
|
||||||
return currentMinutes >= localStartMinutes && currentMinutes < localEndMinutes ? "active" : "future"
|
return currentMinutes >= localStartMinutes && currentMinutes < localEndMinutes ? "active" : "inactive"
|
||||||
} else {
|
} else {
|
||||||
return currentMinutes >= localStartMinutes || currentMinutes < localEndMinutes ? "active" : "future"
|
return currentMinutes >= localStartMinutes || currentMinutes < localEndMinutes ? "active" : "inactive"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// For one-time windows
|
// For one-time windows
|
||||||
@@ -146,7 +148,7 @@ export function QuietHours() {
|
|||||||
} else if (now >= endDate) {
|
} else if (now >= endDate) {
|
||||||
return "past"
|
return "past"
|
||||||
} else {
|
} else {
|
||||||
return "future"
|
return "inactive"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,7 +161,9 @@ export function QuietHours() {
|
|||||||
<Trans>Quiet hours</Trans>
|
<Trans>Quiet hours</Trans>
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||||
<Trans>Schedule quiet hours where notifications will not be sent, such as during maintenance periods.</Trans>
|
<Trans>
|
||||||
|
Schedule quiet hours where notifications will not be sent, such as during maintenance periods.
|
||||||
|
</Trans>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
||||||
@@ -171,12 +175,7 @@ export function QuietHours() {
|
|||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<QuietHoursDialog
|
<QuietHoursDialog editingRecord={editingRecord} systems={systems} onClose={closeDialog} toast={toast} />
|
||||||
editingRecord={editingRecord}
|
|
||||||
systems={systems}
|
|
||||||
onClose={closeDialog}
|
|
||||||
toast={toast}
|
|
||||||
/>
|
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
{data.length > 0 && (
|
{data.length > 0 && (
|
||||||
@@ -198,14 +197,14 @@ export function QuietHours() {
|
|||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead className="px-4">
|
<TableHead className="px-4">
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<ActivityIcon className="size-4" />
|
<CalendarIcon className="size-4" />
|
||||||
<Trans>State</Trans>
|
<Trans>Schedule</Trans>
|
||||||
</span>
|
</span>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead className="px-4">
|
<TableHead className="px-4">
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<CalendarIcon className="size-4" />
|
<ActivityIcon className="size-4" />
|
||||||
<Trans>Schedule</Trans>
|
<Trans>State</Trans>
|
||||||
</span>
|
</span>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead className="px-4 text-right sr-only">
|
<TableHead className="px-4 text-right sr-only">
|
||||||
@@ -217,33 +216,31 @@ export function QuietHours() {
|
|||||||
{data.map((record) => (
|
{data.map((record) => (
|
||||||
<TableRow key={record.id}>
|
<TableRow key={record.id}>
|
||||||
<TableCell className="px-4 py-3">
|
<TableCell className="px-4 py-3">
|
||||||
{record.system ? (record.expand?.system?.name || record.system) : <Trans>All Systems</Trans>}
|
{record.system ? record.expand?.system?.name || record.system : <Trans>All Systems</Trans>}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="px-4 py-3">
|
<TableCell className="px-4 py-3">
|
||||||
{record.type === "daily" ? <Trans>Daily</Trans> : <Trans>One-time</Trans>}
|
{record.type === "daily" ? <Trans>Daily</Trans> : <Trans>One-time</Trans>}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell className="px-4 py-3">{formatDateTime(record)}</TableCell>
|
||||||
<TableCell className="px-4 py-3">
|
<TableCell className="px-4 py-3">
|
||||||
{(() => {
|
{(() => {
|
||||||
const state = getWindowState(record)
|
const state = getWindowState(record)
|
||||||
const stateConfig = {
|
const stateConfig = {
|
||||||
active: { label: <Trans>Active</Trans>, variant: "success" as const },
|
active: { label: <Trans>Active</Trans>, variant: "success" as const },
|
||||||
past: { label: <Trans>Past</Trans>, variant: "danger" as const },
|
past: { label: <Trans>Past</Trans>, variant: "danger" as const },
|
||||||
future: { label: <Trans>Future</Trans>, variant: "default" as const },
|
inactive: { label: <Trans>Inactive</Trans>, variant: "default" as const },
|
||||||
}
|
}
|
||||||
const config = stateConfig[state]
|
const config = stateConfig[state]
|
||||||
return (
|
return <Badge variant={config.variant}>{config.label}</Badge>
|
||||||
<Badge variant={config.variant}>
|
|
||||||
{config.label}
|
|
||||||
</Badge>
|
|
||||||
)
|
|
||||||
})()}
|
})()}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="px-4 py-3">{formatDateTime(record)}</TableCell>
|
|
||||||
<TableCell className="px-4 py-3 text-right">
|
<TableCell className="px-4 py-3 text-right">
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="ghost" size="icon" className="size-8">
|
<Button variant="ghost" size="icon" className="size-8">
|
||||||
<span className="sr-only"><Trans>Open menu</Trans></span>
|
<span className="sr-only">
|
||||||
|
<Trans>Open menu</Trans>
|
||||||
|
</span>
|
||||||
<MoreHorizontalIcon className="size-4" />
|
<MoreHorizontalIcon className="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
@@ -265,8 +262,6 @@ export function QuietHours() {
|
|||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -274,10 +269,10 @@ export function QuietHours() {
|
|||||||
// Helper function to format Date as datetime-local string (YYYY-MM-DDTHH:mm) in local time
|
// Helper function to format Date as datetime-local string (YYYY-MM-DDTHH:mm) in local time
|
||||||
function formatDateTimeLocal(date: Date): string {
|
function formatDateTimeLocal(date: Date): string {
|
||||||
const year = date.getFullYear()
|
const year = date.getFullYear()
|
||||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
const month = String(date.getMonth() + 1).padStart(2, "0")
|
||||||
const day = String(date.getDate()).padStart(2, '0')
|
const day = String(date.getDate()).padStart(2, "0")
|
||||||
const hours = String(date.getHours()).padStart(2, '0')
|
const hours = String(date.getHours()).padStart(2, "0")
|
||||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
const minutes = String(date.getMinutes()).padStart(2, "0")
|
||||||
return `${year}-${month}-${day}T${hours}:${minutes}`
|
return `${year}-${month}-${day}T${hours}:${minutes}`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,7 +285,7 @@ function QuietHoursDialog({
|
|||||||
editingRecord: QuietHoursRecord | null
|
editingRecord: QuietHoursRecord | null
|
||||||
systems: SystemRecord[]
|
systems: SystemRecord[]
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
toast: any
|
toast: ReturnType<typeof useToast>["toast"]
|
||||||
}) {
|
}) {
|
||||||
const [selectedSystem, setSelectedSystem] = useState(editingRecord?.system || "")
|
const [selectedSystem, setSelectedSystem] = useState(editingRecord?.system || "")
|
||||||
const [isGlobal, setIsGlobal] = useState(!editingRecord?.system)
|
const [isGlobal, setIsGlobal] = useState(!editingRecord?.system)
|
||||||
@@ -395,9 +390,7 @@ function QuietHoursDialog({
|
|||||||
return (
|
return (
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>{editingRecord ? <Trans>Edit Quiet Hours</Trans> : <Trans>Add Quiet Hours</Trans>}</DialogTitle>
|
||||||
{editingRecord ? <Trans>Edit Quiet Hours</Trans> : <Trans>Add Quiet Hours</Trans>}
|
|
||||||
</DialogTitle>
|
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
<Trans>Configure quiet hours where notifications will not be sent.</Trans>
|
<Trans>Configure quiet hours where notifications will not be sent.</Trans>
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
@@ -437,7 +430,7 @@ function QuietHoursDialog({
|
|||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
value={selectedSystem}
|
value={selectedSystem}
|
||||||
onChange={() => { }}
|
onChange={() => {}}
|
||||||
required={!isGlobal}
|
required={!isGlobal}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -467,7 +460,7 @@ function QuietHoursDialog({
|
|||||||
<>
|
<>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label htmlFor="start-datetime">
|
<Label htmlFor="start-datetime">
|
||||||
<Trans>Start Date & Time</Trans>
|
<Trans>Start Time</Trans>
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="start-datetime"
|
id="start-datetime"
|
||||||
@@ -481,7 +474,7 @@ function QuietHoursDialog({
|
|||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label htmlFor="end-datetime">
|
<Label htmlFor="end-datetime">
|
||||||
<Trans>End Date & Time</Trans>
|
<Trans>End Time</Trans>
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="end-datetime"
|
id="end-datetime"
|
||||||
@@ -495,12 +488,13 @@ function QuietHoursDialog({
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<div className="grid gap-2 grid-cols-2">
|
||||||
<div className="grid gap-2">
|
<div>
|
||||||
<Label htmlFor="start-time">
|
<Label htmlFor="start-time">
|
||||||
<Trans>Start Time</Trans>
|
<Trans>Start Time</Trans>
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
|
className="tabular-nums tracking-tighter"
|
||||||
id="start-time"
|
id="start-time"
|
||||||
type="time"
|
type="time"
|
||||||
value={startTime}
|
value={startTime}
|
||||||
@@ -508,13 +502,20 @@ function QuietHoursDialog({
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div>
|
||||||
<Label htmlFor="end-time">
|
<Label htmlFor="end-time">
|
||||||
<Trans>End Time</Trans>
|
<Trans>End Time</Trans>
|
||||||
</Label>
|
</Label>
|
||||||
<Input id="end-time" type="time" value={endTime} onChange={(e) => setEndTime(e.target.value)} required />
|
<Input
|
||||||
|
className="tabular-nums tracking-tighter"
|
||||||
|
id="end-time"
|
||||||
|
type="time"
|
||||||
|
value={endTime}
|
||||||
|
onChange={(e) => setEndTime(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/** biome-ignore-all lint/correctness/useHookAtTopLevel: <explanation> */
|
||||||
import { t } from "@lingui/core/macro"
|
import { t } from "@lingui/core/macro"
|
||||||
import { Trans, useLingui } from "@lingui/react/macro"
|
import { Trans, useLingui } from "@lingui/react/macro"
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
@@ -218,7 +219,7 @@ export function SystemsTableColumns(viewMode: "table" | "grid"): ColumnDef<Syste
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorFn: ({ info }) => (info.bb || (info.b || 0) * 1024 * 1024) || undefined,
|
accessorFn: ({ info }) => info.bb || (info.b || 0) * 1024 * 1024 || undefined,
|
||||||
id: "net",
|
id: "net",
|
||||||
name: () => t`Net`,
|
name: () => t`Net`,
|
||||||
size: 0,
|
size: 0,
|
||||||
@@ -291,7 +292,10 @@ export function SystemsTableColumns(viewMode: "table" | "grid"): ColumnDef<Syste
|
|||||||
[STATUS_COLORS[SystemStatus.Up]]: numFailed === 0,
|
[STATUS_COLORS[SystemStatus.Up]]: numFailed === 0,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
{totalCount} <span className="text-muted-foreground text-sm -ms-0.5">({t`Failed`.toLowerCase()}: {numFailed})</span>
|
{totalCount}{" "}
|
||||||
|
<span className="text-muted-foreground text-sm -ms-0.5">
|
||||||
|
({t`Failed`.toLowerCase()}: {numFailed})
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -395,7 +399,6 @@ function DiskCellWithMultiple(info: CellContext<SystemRecord, unknown>) {
|
|||||||
const { info: sysInfo, status, id } = info.row.original
|
const { info: sysInfo, status, id } = info.row.original
|
||||||
const extraFs = Object.entries(sysInfo.efs ?? {})
|
const extraFs = Object.entries(sysInfo.efs ?? {})
|
||||||
|
|
||||||
// No extra disks - show basic meter
|
|
||||||
if (extraFs.length === 0) {
|
if (extraFs.length === 0) {
|
||||||
return TableCellWithMeter(info)
|
return TableCellWithMeter(info)
|
||||||
}
|
}
|
||||||
@@ -405,10 +408,9 @@ function DiskCellWithMultiple(info: CellContext<SystemRecord, unknown>) {
|
|||||||
// sort extra disks by percentage descending
|
// sort extra disks by percentage descending
|
||||||
extraFs.sort((a, b) => b[1] - a[1])
|
extraFs.sort((a, b) => b[1] - a[1])
|
||||||
|
|
||||||
function getMeterClass(pct: number) {
|
function getIndicatorColor(pct: number) {
|
||||||
const threshold = getMeterState(pct)
|
const threshold = getMeterState(pct)
|
||||||
return cn(
|
return (
|
||||||
"h-full",
|
|
||||||
(status !== SystemStatus.Up && STATUS_COLORS.paused) ||
|
(status !== SystemStatus.Up && STATUS_COLORS.paused) ||
|
||||||
(threshold === MeterState.Good && STATUS_COLORS.up) ||
|
(threshold === MeterState.Good && STATUS_COLORS.up) ||
|
||||||
(threshold === MeterState.Warn && STATUS_COLORS.pending) ||
|
(threshold === MeterState.Warn && STATUS_COLORS.pending) ||
|
||||||
@@ -416,28 +418,43 @@ function DiskCellWithMultiple(info: CellContext<SystemRecord, unknown>) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getMeterClass(pct: number) {
|
||||||
|
return cn("h-full", getIndicatorColor(pct))
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Link href={getPagePath($router, "system", { id })} tabIndex={-1} className="flex flex-col gap-0.5 w-full relative z-10">
|
<Link
|
||||||
|
href={getPagePath($router, "system", { id })}
|
||||||
|
tabIndex={-1}
|
||||||
|
className="flex flex-col gap-0.5 w-full relative z-10"
|
||||||
|
>
|
||||||
<div className="flex gap-2 items-center tabular-nums tracking-tight">
|
<div className="flex gap-2 items-center tabular-nums tracking-tight">
|
||||||
<span className="min-w-8 shrink-0">{decimalString(rootDiskPct, rootDiskPct >= 10 ? 1 : 2)}%</span>
|
<span className="min-w-8 shrink-0">{decimalString(rootDiskPct, rootDiskPct >= 10 ? 1 : 2)}%</span>
|
||||||
<span className="flex-1 min-w-8 grid bg-muted h-[1em] rounded-sm overflow-hidden">
|
<span className="flex-1 min-w-8 flex items-center gap-0.5 px-1 justify-end bg-muted h-[1em] rounded-sm overflow-hidden relative">
|
||||||
{/* Root disk */}
|
{/* Root disk */}
|
||||||
<span className={getMeterClass(rootDiskPct)} style={{ width: `${rootDiskPct}%` }}></span>
|
<span
|
||||||
{/* Extra disks */}
|
className={cn("absolute inset-0", getMeterClass(rootDiskPct))}
|
||||||
{extraFs.map(([_name, pct], index) => (
|
style={{ width: `${rootDiskPct}%` }}
|
||||||
<span key={index} className={getMeterClass(pct)} style={{ width: `${pct}%` }}></span>
|
></span>
|
||||||
|
{/* Extra disk indicators */}
|
||||||
|
{extraFs.map(([name, pct]) => (
|
||||||
|
<span
|
||||||
|
key={name}
|
||||||
|
className={cn("size-1.5 rounded-full shrink-0 outline-[0.5px] outline-muted", getIndicatorColor(pct))}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right" className="max-w-xs pb-2">
|
<TooltipContent side="right" className="max-w-xs pb-2">
|
||||||
<div className="grid gap-1.5">
|
<div className="grid gap-1">
|
||||||
<div className="grid gap-0.5">
|
<div className="grid gap-0.5">
|
||||||
<div className="text-[0.65rem] text-muted-foreground uppercase tracking-wide tabular-nums"><Trans context="Root disk label">Root</Trans></div>
|
<div className="text-[0.65rem] text-muted-foreground uppercase tracking-wide tabular-nums">
|
||||||
|
<Trans context="Root disk label">Root</Trans>
|
||||||
|
</div>
|
||||||
<div className="flex gap-2 items-center tabular-nums text-xs">
|
<div className="flex gap-2 items-center tabular-nums text-xs">
|
||||||
<span className="min-w-7">{decimalString(rootDiskPct, rootDiskPct >= 10 ? 1 : 2)}%</span>
|
<span className="min-w-7">{decimalString(rootDiskPct, rootDiskPct >= 10 ? 1 : 2)}%</span>
|
||||||
<span className="flex-1 min-w-12 grid bg-muted h-2.5 rounded-sm overflow-hidden">
|
<span className="flex-1 min-w-12 grid bg-muted h-2.5 rounded-sm overflow-hidden">
|
||||||
@@ -448,7 +465,9 @@ function DiskCellWithMultiple(info: CellContext<SystemRecord, unknown>) {
|
|||||||
{extraFs.map(([name, pct]) => {
|
{extraFs.map(([name, pct]) => {
|
||||||
return (
|
return (
|
||||||
<div key={name} className="grid gap-0.5">
|
<div key={name} className="grid gap-0.5">
|
||||||
<div className="text-[0.65rem] max-w-40 text-muted-foreground uppercase tracking-wide truncate">{name}</div>
|
<div className="text-[0.65rem] max-w-40 text-muted-foreground uppercase tracking-wide truncate">
|
||||||
|
{name}
|
||||||
|
</div>
|
||||||
<div className="flex gap-2 items-center tabular-nums text-xs">
|
<div className="flex gap-2 items-center tabular-nums text-xs">
|
||||||
<span className="min-w-7">{decimalString(pct, pct >= 10 ? 1 : 2)}%</span>
|
<span className="min-w-7">{decimalString(pct, pct >= 10 ? 1 : 2)}%</span>
|
||||||
<span className="flex-1 min-w-12 grid bg-muted h-2.5 rounded-sm overflow-hidden">
|
<span className="flex-1 min-w-12 grid bg-muted h-2.5 rounded-sm overflow-hidden">
|
||||||
|
|||||||
Reference in New Issue
Block a user