mirror of
https://github.com/henrygd/beszel.git
synced 2026-04-21 12:11:49 +02:00
add probes page
This commit is contained in:
@@ -319,18 +319,18 @@ func updateNetworkProbesRecords(app core.App, data map[string]probe.Result, syst
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
collectionName := "network_probes"
|
collectionName := "network_probes"
|
||||||
|
nowString := time.Now().UTC().Format(types.DefaultDateLayout)
|
||||||
for key := range data {
|
for key := range data {
|
||||||
probe := data[key]
|
probe := data[key]
|
||||||
id := MakeStableHashId(systemId, key)
|
id := MakeStableHashId(systemId, key)
|
||||||
params := dbx.Params{
|
params := dbx.Params{
|
||||||
// "system": systemId,
|
|
||||||
"latency": probe[0],
|
"latency": probe[0],
|
||||||
"loss": probe[3],
|
"loss": probe[3],
|
||||||
"updated": time.Now().UTC().Format(types.DefaultDateLayout),
|
"updated": nowString,
|
||||||
}
|
}
|
||||||
_, err := app.DB().Update(collectionName, params, dbx.HashExp{"id": id}).Execute()
|
_, err := app.DB().Update(collectionName, params, dbx.HashExp{"id": id}).Execute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.Logger().Warn("Failed to update network probe record", "system", systemId, "probe", key, "err", err)
|
app.Logger().Warn("Failed to update probe", "system", systemId, "probe", key, "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
LogOutIcon,
|
LogOutIcon,
|
||||||
LogsIcon,
|
LogsIcon,
|
||||||
MenuIcon,
|
MenuIcon,
|
||||||
|
NetworkIcon,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
SearchIcon,
|
SearchIcon,
|
||||||
ServerIcon,
|
ServerIcon,
|
||||||
@@ -109,6 +110,10 @@ export default function Navbar() {
|
|||||||
<HardDriveIcon className="h-4 w-4 me-2.5" strokeWidth={1.5} />
|
<HardDriveIcon className="h-4 w-4 me-2.5" strokeWidth={1.5} />
|
||||||
<span>S.M.A.R.T.</span>
|
<span>S.M.A.R.T.</span>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => navigate(getPagePath($router, "probes"))} className="flex items-center">
|
||||||
|
<NetworkIcon className="h-4 w-4 me-2.5" strokeWidth={1.5} />
|
||||||
|
<Trans>Network Probes</Trans>
|
||||||
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() => navigate(getPagePath($router, "settings", { name: "general" }))}
|
onClick={() => navigate(getPagePath($router, "settings", { name: "general" }))}
|
||||||
className="flex items-center"
|
className="flex items-center"
|
||||||
@@ -180,6 +185,21 @@ export default function Navbar() {
|
|||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>S.M.A.R.T.</TooltipContent>
|
<TooltipContent>S.M.A.R.T.</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Link
|
||||||
|
href={getPagePath($router, "probes")}
|
||||||
|
className={cn("hidden md:grid", buttonVariants({ variant: "ghost", size: "icon" }))}
|
||||||
|
aria-label="Network Probes"
|
||||||
|
onMouseEnter={() => import("@/components/routes/probes")}
|
||||||
|
>
|
||||||
|
<NetworkIcon className="h-[1.2rem] w-[1.2rem]" strokeWidth={1.5} />
|
||||||
|
</Link>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<Trans>Network Probes</Trans>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
<LangToggle />
|
<LangToggle />
|
||||||
<ModeToggle />
|
<ModeToggle />
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ export default function NetworkProbesTableNew({ systemId }: { systemId?: string
|
|||||||
className="ms-auto px-4 w-full max-w-full md:w-64"
|
className="ms-auto px-4 w-full max-w-full md:w-64"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{systemId && !isReadOnlyUser() ? <AddProbeDialog systemId={systemId} /> : null}
|
{!isReadOnlyUser() ? <AddProbeDialog systemId={systemId} /> : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import { Trans, useLingui } from "@lingui/react/macro"
|
import { Trans, useLingui } from "@lingui/react/macro"
|
||||||
|
import { useStore } from "@nanostores/react"
|
||||||
import { pb } from "@/lib/api"
|
import { pb } from "@/lib/api"
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -16,8 +17,9 @@ 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 { PlusIcon } from "lucide-react"
|
import { PlusIcon } from "lucide-react"
|
||||||
import { useToast } from "@/components/ui/use-toast"
|
import { useToast } from "@/components/ui/use-toast"
|
||||||
|
import { $systems } from "@/lib/stores"
|
||||||
|
|
||||||
export function AddProbeDialog({ systemId }: { systemId: string }) {
|
export function AddProbeDialog({ systemId }: { systemId?: string }) {
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [protocol, setProtocol] = useState<string>("icmp")
|
const [protocol, setProtocol] = useState<string>("icmp")
|
||||||
const [target, setTarget] = useState("")
|
const [target, setTarget] = useState("")
|
||||||
@@ -25,6 +27,8 @@ export function AddProbeDialog({ systemId }: { systemId: string }) {
|
|||||||
const [probeInterval, setProbeInterval] = useState("60")
|
const [probeInterval, setProbeInterval] = useState("60")
|
||||||
const [name, setName] = useState("")
|
const [name, setName] = useState("")
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [selectedSystemId, setSelectedSystemId] = useState("")
|
||||||
|
const systems = useStore($systems)
|
||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
const { t } = useLingui()
|
const { t } = useLingui()
|
||||||
|
|
||||||
@@ -34,6 +38,7 @@ export function AddProbeDialog({ systemId }: { systemId: string }) {
|
|||||||
setPort("")
|
setPort("")
|
||||||
setProbeInterval("60")
|
setProbeInterval("60")
|
||||||
setName("")
|
setName("")
|
||||||
|
setSelectedSystemId("")
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
@@ -41,7 +46,7 @@ export function AddProbeDialog({ systemId }: { systemId: string }) {
|
|||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
await pb.collection("network_probes").create({
|
await pb.collection("network_probes").create({
|
||||||
system: systemId,
|
system: systemId ?? selectedSystemId,
|
||||||
name,
|
name,
|
||||||
target,
|
target,
|
||||||
protocol,
|
protocol,
|
||||||
@@ -76,6 +81,25 @@ export function AddProbeDialog({ systemId }: { systemId: string }) {
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<form onSubmit={handleSubmit} className="grid gap-4 tabular-nums">
|
<form onSubmit={handleSubmit} className="grid gap-4 tabular-nums">
|
||||||
|
{!systemId && (
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Label>
|
||||||
|
<Trans>System</Trans>
|
||||||
|
</Label>
|
||||||
|
<Select value={selectedSystemId} onValueChange={setSelectedSystemId} required>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder={t`Select a system`} />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{systems.map((sys) => (
|
||||||
|
<SelectItem key={sys.id} value={sys.id}>
|
||||||
|
{sys.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label>
|
<Label>
|
||||||
<Trans>Target</Trans>
|
<Trans>Target</Trans>
|
||||||
@@ -142,7 +166,7 @@ export function AddProbeDialog({ systemId }: { systemId: string }) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button type="submit" disabled={loading}>
|
<Button type="submit" disabled={loading || (!systemId && !selectedSystemId)}>
|
||||||
{loading ? <Trans>Creating...</Trans> : <Trans>Add {{ foo: t`Probe` }}</Trans>}
|
{loading ? <Trans>Creating...</Trans> : <Trans>Add {{ foo: t`Probe` }}</Trans>}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const routes = {
|
|||||||
home: "/",
|
home: "/",
|
||||||
containers: "/containers",
|
containers: "/containers",
|
||||||
smart: "/smart",
|
smart: "/smart",
|
||||||
|
probes: "/probes",
|
||||||
system: `/system/:id`,
|
system: `/system/:id`,
|
||||||
settings: `/settings/:name?`,
|
settings: `/settings/:name?`,
|
||||||
forgot_password: `/forgot-password`,
|
forgot_password: `/forgot-password`,
|
||||||
|
|||||||
26
internal/site/src/components/routes/probes.tsx
Normal file
26
internal/site/src/components/routes/probes.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { useLingui } from "@lingui/react/macro"
|
||||||
|
import { memo, useEffect, useMemo } from "react"
|
||||||
|
import NetworkProbesTableNew from "@/components/network-probes-table/network-probes-table"
|
||||||
|
import { ActiveAlerts } from "@/components/active-alerts"
|
||||||
|
import { FooterRepoLink } from "@/components/footer-repo-link"
|
||||||
|
|
||||||
|
export default memo(() => {
|
||||||
|
const { t } = useLingui()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.title = `${t`Network Probes`} / Beszel`
|
||||||
|
}, [t])
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => (
|
||||||
|
<>
|
||||||
|
<div className="grid gap-4">
|
||||||
|
<ActiveAlerts />
|
||||||
|
<NetworkProbesTableNew />
|
||||||
|
</div>
|
||||||
|
<FooterRepoLink />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
})
|
||||||
@@ -30,6 +30,7 @@ const LoginPage = lazy(() => import("@/components/login/login.tsx"))
|
|||||||
const Home = lazy(() => import("@/components/routes/home.tsx"))
|
const Home = lazy(() => import("@/components/routes/home.tsx"))
|
||||||
const Containers = lazy(() => import("@/components/routes/containers.tsx"))
|
const Containers = lazy(() => import("@/components/routes/containers.tsx"))
|
||||||
const Smart = lazy(() => import("@/components/routes/smart.tsx"))
|
const Smart = lazy(() => import("@/components/routes/smart.tsx"))
|
||||||
|
const Probes = lazy(() => import("@/components/routes/probes.tsx"))
|
||||||
const SystemDetail = lazy(() => import("@/components/routes/system.tsx"))
|
const SystemDetail = lazy(() => import("@/components/routes/system.tsx"))
|
||||||
const CopyToClipboardDialog = lazy(() => import("@/components/copy-to-clipboard.tsx"))
|
const CopyToClipboardDialog = lazy(() => import("@/components/copy-to-clipboard.tsx"))
|
||||||
|
|
||||||
@@ -79,6 +80,8 @@ const App = memo(() => {
|
|||||||
return <Containers />
|
return <Containers />
|
||||||
} else if (page.route === "smart") {
|
} else if (page.route === "smart") {
|
||||||
return <Smart />
|
return <Smart />
|
||||||
|
} else if (page.route === "probes") {
|
||||||
|
return <Probes />
|
||||||
} else if (page.route === "settings") {
|
} else if (page.route === "settings") {
|
||||||
return <Settings />
|
return <Settings />
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user