mirror of
https://github.com/henrygd/beszel.git
synced 2026-05-06 10:51:50 +02:00
updates
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import type { CellContext, Column, ColumnDef } from "@tanstack/react-table"
|
import type { CellContext, Column, ColumnDef } from "@tanstack/react-table"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { cn, formatMicroseconds, hourWithSeconds } from "@/lib/utils"
|
import { cn, copyToClipboard, formatMicroseconds, hourWithSeconds } from "@/lib/utils"
|
||||||
import {
|
import {
|
||||||
GlobeIcon,
|
GlobeIcon,
|
||||||
TimerIcon,
|
TimerIcon,
|
||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
PenBoxIcon,
|
PenBoxIcon,
|
||||||
PauseCircleIcon,
|
PauseCircleIcon,
|
||||||
PlayCircleIcon,
|
PlayCircleIcon,
|
||||||
|
CopyIcon,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import { t } from "@lingui/core/macro"
|
import { t } from "@lingui/core/macro"
|
||||||
import type { NetworkProbeRecord, SystemRecord } from "@/types"
|
import type { NetworkProbeRecord, SystemRecord } from "@/types"
|
||||||
@@ -31,6 +32,7 @@ import { useStore } from "@nanostores/react"
|
|||||||
import { SystemStatus } from "@/lib/enums"
|
import { SystemStatus } from "@/lib/enums"
|
||||||
import { Checkbox } from "@/components/ui/checkbox"
|
import { Checkbox } from "@/components/ui/checkbox"
|
||||||
import { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
|
import { formatBulkProbeLine } from "@/components/network-probes-table/probe-dialog"
|
||||||
|
|
||||||
const protocolColors: Record<string, string> = {
|
const protocolColors: Record<string, string> = {
|
||||||
icmp: "bg-blue-500/15 text-blue-400",
|
icmp: "bg-blue-500/15 text-blue-400",
|
||||||
@@ -256,6 +258,7 @@ export function getProbeColumns(
|
|||||||
: [row.original]
|
: [row.original]
|
||||||
const isBulkAction = actionRows.length > 1
|
const isBulkAction = actionRows.length > 1
|
||||||
const shouldPause = actionRows.some((probe) => probe.enabled)
|
const shouldPause = actionRows.some((probe) => probe.enabled)
|
||||||
|
const bulkCopyContent = actionRows.map((probe) => formatBulkProbeLine(probe)).join("\n")
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
@@ -269,8 +272,7 @@ export function getProbeColumns(
|
|||||||
<DropdownMenuContent align="end" onClick={(event) => event.stopPropagation()}>
|
<DropdownMenuContent align="end" onClick={(event) => event.stopPropagation()}>
|
||||||
{!isBulkAction && (
|
{!isBulkAction && (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={(event) => {
|
onClick={() => {
|
||||||
event.stopPropagation()
|
|
||||||
onEdit?.(row.original)
|
onEdit?.(row.original)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -279,8 +281,7 @@ export function getProbeColumns(
|
|||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={(event) => {
|
onClick={() => {
|
||||||
event.stopPropagation()
|
|
||||||
onSetEnabled?.(actionRows, !shouldPause)
|
onSetEnabled?.(actionRows, !shouldPause)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -296,10 +297,17 @@ export function getProbeColumns(
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => {
|
||||||
|
copyToClipboard(bulkCopyContent)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CopyIcon className="me-2.5 size-4" />
|
||||||
|
<Trans>Bulk copy</Trans>
|
||||||
|
</DropdownMenuItem>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={(event) => {
|
onClick={() => {
|
||||||
event.stopPropagation()
|
|
||||||
onDelete?.(actionRows)
|
onDelete?.(actionRows)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ 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"
|
||||||
import { Textarea } from "@/components/ui/textarea"
|
import { Textarea } from "@/components/ui/textarea"
|
||||||
import { ChevronDownIcon, ListIcon } from "lucide-react"
|
import { ChevronDownIcon, ListIcon, ServerIcon } from "lucide-react"
|
||||||
import { useToast } from "@/components/ui/use-toast"
|
import { useToast } from "@/components/ui/use-toast"
|
||||||
import { $systems } from "@/lib/stores"
|
import { $systems } from "@/lib/stores"
|
||||||
import type { NetworkProbeRecord } from "@/types"
|
import type { NetworkProbeRecord } from "@/types"
|
||||||
@@ -38,6 +38,8 @@ type NormalizedProbeValues = Omit<ProbeValues, "system" | "interval"> & {
|
|||||||
interval: number
|
interval: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BulkProbeLineSource = Pick<NetworkProbeRecord, "target" | "protocol" | "port" | "interval" | "name">
|
||||||
|
|
||||||
const defaultInterval = 20
|
const defaultInterval = 20
|
||||||
|
|
||||||
const ProbeProtocolSchema = v.picklist(["icmp", "tcp", "http"])
|
const ProbeProtocolSchema = v.picklist(["icmp", "tcp", "http"])
|
||||||
@@ -101,6 +103,14 @@ function normalizeHttpTarget(target: string, port: number) {
|
|||||||
return `${port === 443 ? "https" : "http"}://${target}`
|
return `${port === 443 ? "https" : "http"}://${target}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function trimTrailingEmptyFields(fields: string[]) {
|
||||||
|
let lastValueIndex = fields.length - 1
|
||||||
|
while (lastValueIndex > 0 && !fields[lastValueIndex]) {
|
||||||
|
lastValueIndex--
|
||||||
|
}
|
||||||
|
return fields.slice(0, lastValueIndex + 1)
|
||||||
|
}
|
||||||
|
|
||||||
function buildProbePayload(values: ProbeValues, enabled = true) {
|
function buildProbePayload(values: ProbeValues, enabled = true) {
|
||||||
const normalizedValues = v.safeParse(NormalizedProbeValuesSchema, values)
|
const normalizedValues = v.safeParse(NormalizedProbeValuesSchema, values)
|
||||||
if (!normalizedValues.success) {
|
if (!normalizedValues.success) {
|
||||||
@@ -154,6 +164,12 @@ function parseBulkProbeLine(line: string, lineNumber: number, system: string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatBulkProbeLine(probe: BulkProbeLineSource) {
|
||||||
|
const port = probe.protocol === "icmp" || probe.port === 443 ? "" : `${probe.port}`
|
||||||
|
const interval = probe.interval === defaultInterval ? "" : `${probe.interval}`
|
||||||
|
return trimTrailingEmptyFields([probe.target, probe.protocol, port, interval, probe.name?.trim() || ""]).join(",")
|
||||||
|
}
|
||||||
|
|
||||||
export function AddProbeDialog({ systemId, probes }: { systemId?: string; probes: NetworkProbeRecord[] }) {
|
export function AddProbeDialog({ systemId, probes }: { systemId?: string; probes: NetworkProbeRecord[] }) {
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [bulkOpen, setBulkOpen] = useState(false)
|
const [bulkOpen, setBulkOpen] = useState(false)
|
||||||
@@ -215,7 +231,7 @@ export function AddProbeDialog({ systemId, probes }: { systemId?: string; probes
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!newPayloads.length) {
|
if (!newPayloads.length) {
|
||||||
throw new Error("No new probes to add. All entries already exist.")
|
throw new Error("No new probes. All entries exist.")
|
||||||
}
|
}
|
||||||
|
|
||||||
closedForSubmit = true
|
closedForSubmit = true
|
||||||
@@ -291,19 +307,18 @@ export function AddProbeDialog({ systemId, probes }: { systemId?: string; probes
|
|||||||
<SheetTitle>
|
<SheetTitle>
|
||||||
<Trans>Bulk Add {{ foo: t`Network Probes` }}</Trans>
|
<Trans>Bulk Add {{ foo: t`Network Probes` }}</Trans>
|
||||||
</SheetTitle>
|
</SheetTitle>
|
||||||
<SheetDescription>
|
<SheetDescription>target[,protocol[,port[,interval[,name]]]]</SheetDescription>
|
||||||
target[,protocol[,port[,interval[,name]]]] - TCP/HTTP default to port 443.
|
|
||||||
</SheetDescription>
|
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<form ref={bulkFormRef} onSubmit={handleBulkSubmit} className="flex h-full flex-col overflow-hidden">
|
<form ref={bulkFormRef} onSubmit={handleBulkSubmit} className="flex h-full flex-col overflow-hidden">
|
||||||
<div className="flex-1 space-y-4 overflow-auto p-4">
|
<div className="flex-1 flex flex-col space-y-4 overflow-auto p-4">
|
||||||
{!systemId && (
|
{!systemId && (
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label>
|
<Label className="sr-only">
|
||||||
<Trans>System</Trans>
|
<Trans>System</Trans>
|
||||||
</Label>
|
</Label>
|
||||||
<Select value={bulkSelectedSystemId} onValueChange={setBulkSelectedSystemId} required>
|
<Select value={bulkSelectedSystemId} onValueChange={setBulkSelectedSystemId} required>
|
||||||
<SelectTrigger>
|
<SelectTrigger className="relative ps-10 pe-5 bg-card">
|
||||||
|
<ServerIcon className="size-3.5 absolute start-4 top-1/2 -translate-y-1/2 opacity-85" />
|
||||||
<SelectValue placeholder={t`Select a system`} />
|
<SelectValue placeholder={t`Select a system`} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@@ -316,7 +331,7 @@ export function AddProbeDialog({ systemId, probes }: { systemId?: string; probes
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="grid gap-2">
|
<div className="grow flex flex-col gap-2">
|
||||||
<Label htmlFor="bulk-probes" className="sr-only">
|
<Label htmlFor="bulk-probes" className="sr-only">
|
||||||
Entries
|
Entries
|
||||||
</Label>
|
</Label>
|
||||||
@@ -330,14 +345,11 @@ export function AddProbeDialog({ systemId, probes }: { systemId?: string; probes
|
|||||||
bulkFormRef.current?.requestSubmit()
|
bulkFormRef.current?.requestSubmit()
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="h-200 font-mono text-sm bg-muted/40"
|
className="font-mono grow text-sm bg-card"
|
||||||
style={{ maxHeight: `calc(100vh - 20rem)` }}
|
placeholder={["1.1.1.1", "example.com,tcp", "https://example.com,http,,60,Example"].join("\n")}
|
||||||
placeholder={["1.1.1.1", "example.com,tcp", "https://example.com,http,,60,Homepage"].join("\n")}
|
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">target[,protocol[,port[,interval[,name]]]]</p>
|
||||||
target[,protocol[,port[,interval[,name]]]] • TCP and HTTP default to port 443.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SheetFooter className="border-t">
|
<SheetFooter className="border-t">
|
||||||
|
|||||||
Reference in New Issue
Block a user