diff --git a/beszel/internal/hub/hub.go b/beszel/internal/hub/hub.go index 189cae4..208e798 100644 --- a/beszel/internal/hub/hub.go +++ b/beszel/internal/hub/hub.go @@ -237,8 +237,8 @@ func (h *Hub) Run() { oldRecord := newRecord.Original() newStatus := newRecord.GetString("status") - // if system is disconnected and connection exists, remove it - if newStatus == "down" || newStatus == "paused" { + // if system is not up and connection exists, remove it + if newStatus != "up" { h.deleteSystemConnection(newRecord) } diff --git a/beszel/site/src/components/add-system.tsx b/beszel/site/src/components/add-system.tsx index 9653d5f..239787a 100644 --- a/beszel/site/src/components/add-system.tsx +++ b/beszel/site/src/components/add-system.tsx @@ -16,15 +16,46 @@ import { Label } from "@/components/ui/label" import { $publicKey, pb } from "@/lib/stores" import { cn, copyToClipboard, isReadOnlyUser } from "@/lib/utils" import { i18n } from "@lingui/core" -import { Trans } from "@lingui/macro" +import { t, Trans } from "@lingui/macro" import { useStore } from "@nanostores/react" import { ChevronDownIcon, Copy, PlusIcon } from "lucide-react" -import { MutableRefObject, useRef, useState } from "react" +import { memo, MutableRefObject, useRef, useState } from "react" import { basePath, navigate } from "./router" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "./ui/dropdown-menu" +import { SystemRecord } from "@/types" export function AddSystemButton({ className }: { className?: string }) { const [open, setOpen] = useState(false) + let opened = useRef(false) + if (open) { + opened.current = true + } + + return ( + + + + + {opened.current && } + + ) +} + +/** + * SystemDialog component for adding or editing a system. + * @param {Object} props - The component props. + * @param {function} props.setOpen - Function to set the open state of the dialog. + * @param {SystemRecord} [props.system] - Optional system record for editing an existing system. + */ +export const SystemDialog = memo(({ setOpen, system }: { setOpen: (open: boolean) => void; system?: SystemRecord }) => { const port = useRef() as MutableRefObject const publicKey = useStore($publicKey) @@ -43,6 +74,7 @@ export function AddSystemButton({ className }: { className?: string }) { PORT: ${port} KEY: "${publicKey}"`) } + function copyDockerRun(port: string) { copyToClipboard( `docker run -d --name beszel-agent --network host --restart unless-stopped -v /var/run/docker.sock:/var/run/docker.sock:ro -e KEY="${publicKey}" -e PORT=${port} henrygd/beszel-agent:latest` @@ -65,7 +97,11 @@ export function AddSystemButton({ className }: { className?: string }) { data.users = pb.authStore.record!.id try { setOpen(false) - await pb.collection("systems").create(data) + if (system) { + await pb.collection("systems").update(system.id, { ...data, status: "pending" }) + } else { + await pb.collection("systems").create(data) + } navigate(basePath) // console.log(record) } catch (e) { @@ -74,138 +110,119 @@ export function AddSystemButton({ className }: { className?: string }) { } return ( - - - - - - - - - Add New System - - - Docker - - Binary - - - - {/* Docker */} - - - - The agent must be running on the system to connect. Copy the - docker-compose.yml for the agent below. - - - - {/* Binary */} - - - - The agent must be running on the system to connect. Copy the installation command for the agent below. - - - -
-
- - - - - - - -
- -
- - - - - - -

- Click to copy -

-
-
-
-
+ + + + + {system ? `${t`Edit`} ${system?.name}` : Add New System} + + + Docker + + Binary + + + + {/* Docker (set tab index to prevent auto focusing content in edit system dialog) */} + + + + The agent must be running on the system to connect. Copy the + docker-compose.yml for the agent below. + + + + {/* Binary */} + + + + The agent must be running on the system to connect. Copy the installation command for the agent below. + + + + +
+ + + + + + + +
+ +
+ + + + + + +

+ Click to copy +

+
+
+
+
+ {/* Docker */} - - -
- -
- - - - - - copyDockerRun(port.current.value)}> - Copy docker run - - - -
- -
+
+ + + + + + copyDockerRun(port.current.value)}> + Copy docker run + + + +
{/* Binary */} - - - - - + + - -
-
-
+ {/* Save */} + + + + + ) -} +}) diff --git a/beszel/site/src/components/systems-table/systems-table.tsx b/beszel/site/src/components/systems-table/systems-table.tsx index 175756e..8f90367 100644 --- a/beszel/site/src/components/systems-table/systems-table.tsx +++ b/beszel/site/src/components/systems-table/systems-table.tsx @@ -37,7 +37,6 @@ import { AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, - AlertDialogTrigger, } from "@/components/ui/alert-dialog" import { SystemRecord } from "@/types" @@ -59,8 +58,9 @@ import { ArrowUpIcon, Settings2Icon, EyeIcon, + PenBoxIcon, } from "lucide-react" -import { useEffect, useMemo, useState } from "react" +import { memo, useEffect, useMemo, useRef, useState } from "react" import { $hubVersion, $systems, pb } from "@/lib/stores" import { useStore } from "@nanostores/react" import { cn, copyToClipboard, decimalString, isReadOnlyUser, useLocalStorage } from "@/lib/utils" @@ -73,6 +73,8 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui import { Input } from "../ui/input" import { ClassValue } from "clsx" import { getPagePath } from "@nanostores/router" +import { SystemDialog } from "../add-system" +import { Dialog } from "../ui/dialog" type ViewMode = "table" | "grid" @@ -559,11 +561,15 @@ function IndicatorDot({ system, className }: { system: SystemRecord; className?: ) } -function ActionsButton({ system }: { system: SystemRecord }) { - // const [opened, setOpened] = useState(false) +const ActionsButton = memo(({ system }: { system: SystemRecord }) => { + const [deleteOpen, setDeleteOpen] = useState(false) + const [editOpen, setEditOpen] = useState(false) + let editOpened = useRef(false) + const { id, status, host, name } = system + return ( - + <> + {!isReadOnlyUser() && ( + { + editOpened.current = true + setEditOpen(true) + }} + > + + Edit + + )} { @@ -599,38 +616,43 @@ function ActionsButton({ system }: { system: SystemRecord }) { Copy host - - - - Delete - - + setDeleteOpen(true)}> + + Delete + - - - - Are you sure you want to delete {name}? - - - - This action cannot be undone. This will permanently delete all current records for {name} from the - database. - - - - - - Cancel - - pb.collection("systems").delete(id)} - > - Continue - - - - + {/* edit dialog */} + + {editOpened.current && } + + {/* deletion dialog */} + setDeleteOpen(open)}> + + + + Are you sure you want to delete {name}? + + + + This action cannot be undone. This will permanently delete all current records for {name} from the + database. + + + + + + Cancel + + pb.collection("systems").delete(id)} + > + Continue + + + + + ) -} +}) diff --git a/beszel/site/src/lib/utils.ts b/beszel/site/src/lib/utils.ts index 37f08ba..3302846 100644 --- a/beszel/site/src/lib/utils.ts +++ b/beszel/site/src/lib/utils.ts @@ -53,7 +53,7 @@ export const updateSystemList = (() => { try { const records = await pb .collection("systems") - .getFullList({ sort: "+name", fields: "id,name,host,info,status" }) + .getFullList({ sort: "+name", fields: "id,name,host,port,info,status" }) if (records.length) { $systems.set(records) @@ -107,7 +107,7 @@ export const formatDay = (timestamp: string) => { } export const updateFavicon = (newIcon: string) => { - ;(document.querySelector("link[rel='icon']") as HTMLLinkElement).href = prependBasePath( `/static/${newIcon}` ) + ;(document.querySelector("link[rel='icon']") as HTMLLinkElement).href = prependBasePath(`/static/${newIcon}`) } export const isAdmin = () => pb.authStore.record?.role === "admin"