progress on edit dialog

This commit is contained in:
henrygd
2025-02-08 22:02:20 -05:00
parent aa3de511b9
commit 3d9ea465e4
2 changed files with 225 additions and 164 deletions

View File

@@ -18,13 +18,62 @@ import { cn, copyToClipboard, isReadOnlyUser } from "@/lib/utils"
import { i18n } from "@lingui/core"
import { Trans } from "@lingui/macro"
import { useStore } from "@nanostores/react"
import { ChevronDownIcon, Copy, PlusIcon } from "lucide-react"
import { MutableRefObject, useRef, useState } from "react"
import { ChevronDownIcon, Copy, PenBoxIcon, PlusIcon } from "lucide-react"
import { memo, MutableRefObject, useRef, useState } from "react"
import { basePath, navigate } from "./router"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "./ui/dropdown-menu"
export function AddSystemButton({ className }: { className?: string }) {
const [opened, setOpened] = useState(false)
const [open, setOpen] = useState(false)
return (
<Dialog
open={open}
onOpenChange={(open) => {
setOpened(true)
setOpen(open)
}}
>
<DialogTrigger asChild>
<Button
variant="outline"
className={cn("flex gap-1 max-xs:h-[2.4rem]", className, isReadOnlyUser() && "hidden")}
>
<PlusIcon className="h-4 w-4 -ms-1" />
<Trans>
Add <span className="hidden sm:inline">System</span>
</Trans>
</Button>
</DialogTrigger>
{opened && <SystemDialog setOpen={setOpen} />}
</Dialog>
)
}
export function EditSystemButton() {
const [opened, setOpened] = useState(false)
const [open, setOpen] = useState(false)
return (
<Dialog
open={open}
onOpenChange={(open) => {
setOpened(true)
setOpen(open)
}}
>
<DialogTrigger asChild>
<DropdownMenuItem onSelect={(e) => e.preventDefault()}>
<PenBoxIcon className="me-2.5 size-4" />
<Trans>Edit</Trans>
</DropdownMenuItem>
</DialogTrigger>
{opened && <SystemDialog setOpen={setOpen} />}
</Dialog>
)
}
export const SystemDialog = memo(({ setOpen }: { setOpen: (open: boolean) => void }) => {
const port = useRef() as MutableRefObject<HTMLInputElement>
const publicKey = useStore($publicKey)
@@ -43,6 +92,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`
@@ -72,140 +122,127 @@ export function AddSystemButton({ className }: { className?: string }) {
console.log(e)
}
}
console.log("hello")
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button
variant="outline"
className={cn("flex gap-1 max-xs:h-[2.4rem]", className, isReadOnlyUser() && "hidden")}
>
<PlusIcon className="h-4 w-4 -ms-1" />
<Trans>
Add <span className="hidden sm:inline">System</span>
</Trans>
</Button>
</DialogTrigger>
<DialogContent className="w-[90%] sm:w-auto sm:ns-dialog max-w-full rounded-lg">
<Tabs defaultValue="docker">
<DialogHeader>
<DialogTitle className="mb-2">
<Trans>Add New System</Trans>
</DialogTitle>
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="docker">Docker</TabsTrigger>
<TabsTrigger value="binary">
<Trans>Binary</Trans>
</TabsTrigger>
</TabsList>
</DialogHeader>
<DialogContent className="w-[90%] sm:w-auto sm:ns-dialog max-w-full rounded-lg">
<Tabs defaultValue="docker">
<DialogHeader>
<DialogTitle className="mb-2">
<Trans>Add New System</Trans>
</DialogTitle>
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="docker">Docker</TabsTrigger>
<TabsTrigger value="binary">
<Trans>Binary</Trans>
</TabsTrigger>
</TabsList>
</DialogHeader>
{/* Docker (set tab index to prevent auto focusing content in edit system dialog) */}
<TabsContent value="docker" tabIndex={-1}>
<DialogDescription className="mb-4 leading-normal w-0 min-w-full">
<Trans>
The agent must be running on the system to connect. Copy the
<code className="bg-muted px-1 rounded-sm leading-3">docker-compose.yml</code> for the agent below.
</Trans>
</DialogDescription>
</TabsContent>
{/* Binary */}
<TabsContent value="binary">
<DialogDescription className="mb-4 leading-normal w-0 min-w-full">
<Trans>
The agent must be running on the system to connect. Copy the installation command for the agent below.
</Trans>
</DialogDescription>
</TabsContent>
<form onSubmit={handleSubmit as any}>
<div className="grid xs:grid-cols-[auto_1fr] gap-y-3 gap-x-4 items-center mt-1 mb-4">
<Label htmlFor="name" className="xs:text-end">
<Trans>Name</Trans>
</Label>
<Input id="name" name="name" className="" required />
<Label htmlFor="host" className="xs:text-end">
<Trans>Host / IP</Trans>
</Label>
<Input id="host" name="host" className="" required />
<Label htmlFor="port" className="xs:text-end">
<Trans>Port</Trans>
</Label>
<Input ref={port} name="port" id="port" defaultValue="45876" className="" required />
<Label htmlFor="pkey" className="xs:text-end whitespace-pre">
<Trans comment="Use 'Key' if your language requires many more characters">Public Key</Trans>
</Label>
<div className="relative">
<Input readOnly id="pkey" value={publicKey} className="" required></Input>
<div
className={
"h-6 w-24 bg-gradient-to-r rtl:bg-gradient-to-l from-transparent to-background to-65% absolute top-2 end-1 pointer-events-none"
}
></div>
<TooltipProvider delayDuration={100}>
<Tooltip>
<TooltipTrigger asChild>
<Button
type="button"
variant={"link"}
className="absolute end-0 top-0"
onClick={() => copyToClipboard(publicKey)}
>
<Copy className="h-4 w-4 " />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>
<Trans>Click to copy</Trans>
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</div>
{/* Docker */}
<TabsContent value="docker">
<DialogDescription className="mb-4 leading-normal w-0 min-w-full">
<Trans>
The agent must be running on the system to connect. Copy the
<code className="bg-muted px-1 rounded-sm leading-3">docker-compose.yml</code> for the agent below.
</Trans>
</DialogDescription>
<DialogFooter className="flex justify-end gap-x-2 gap-y-3 flex-col">
<div className="flex gap-0 rounded-lg">
<Button
type="button"
variant="outline"
onClick={() => copyDockerCompose(port.current.value)}
className="rounded-e-none dark:border-e-0 grow"
>
<Trans>Copy</Trans> docker compose
</Button>
<div className="w-px h-full bg-muted"></div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className={"px-2 rounded-s-none border-s-0"}>
<ChevronDownIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => copyDockerRun(port.current.value)}>
<Trans>Copy</Trans> docker run
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<Button>
<Trans>Add system</Trans>
</Button>
</DialogFooter>
</TabsContent>
{/* Binary */}
<TabsContent value="binary">
<DialogDescription className="mb-4 leading-normal w-0 min-w-full">
<Trans>
The agent must be running on the system to connect. Copy the installation command for the agent below.
</Trans>
</DialogDescription>
<DialogFooter className="flex justify-end gap-x-2 gap-y-3 flex-col">
<Button type="button" variant="outline" onClick={() => copyInstallCommand(port.current.value)}>
<Trans>Copy Linux command</Trans>
</Button>
<Button>
<Trans>Add system</Trans>
</Button>
</DialogFooter>
</TabsContent>
<form onSubmit={handleSubmit as any}>
<div className="grid xs:grid-cols-[auto_1fr] gap-y-3 gap-x-4 items-center mt-1 mb-4">
<Label htmlFor="name" className="xs:text-end">
<Trans>Name</Trans>
</Label>
<Input id="name" name="name" className="" required />
<Label htmlFor="host" className="xs:text-end">
<Trans>Host / IP</Trans>
</Label>
<Input id="host" name="host" className="" required />
<Label htmlFor="port" className="xs:text-end">
<Trans>Port</Trans>
</Label>
<Input ref={port} name="port" id="port" defaultValue="45876" className="" required />
<Label htmlFor="pkey" className="xs:text-end whitespace-pre">
<Trans comment="Use 'Key' if your language requires many more characters">Public Key</Trans>
</Label>
<div className="relative">
<Input readOnly id="pkey" value={publicKey} className="" required></Input>
<div
className={
"h-6 w-24 bg-gradient-to-r rtl:bg-gradient-to-l from-transparent to-background to-65% absolute top-2 end-1 pointer-events-none"
}
></div>
<TooltipProvider delayDuration={100}>
<Tooltip>
<TooltipTrigger asChild>
<Button
type="button"
variant={"link"}
className="absolute end-0 top-0"
onClick={() => copyToClipboard(publicKey)}
>
<Copy className="h-4 w-4 " />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>
<Trans>Click to copy</Trans>
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</div>
{/* Docker */}
<TabsContent value="docker">
<DialogFooter className="flex justify-end gap-x-2 gap-y-3 flex-col">
<div className="flex gap-0 rounded-lg">
<Button
type="button"
variant="outline"
onClick={() => copyDockerCompose(port.current.value)}
className="rounded-e-none dark:border-e-0 grow"
>
<Trans>Copy</Trans> docker compose
</Button>
<div className="w-px h-full bg-muted"></div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className={"px-2 rounded-s-none border-s-0"}>
<ChevronDownIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => copyDockerRun(port.current.value)}>
<Trans>Copy</Trans> docker run
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<Button>
<Trans>Add system</Trans>
</Button>
</DialogFooter>
</TabsContent>
{/* Binary */}
<TabsContent value="binary">
<DialogFooter className="flex justify-end gap-x-2 gap-y-3 flex-col">
<Button type="button" variant="outline" onClick={() => copyInstallCommand(port.current.value)}>
<Trans>Copy Linux command</Trans>
</Button>
<Button>
<Trans>Add system</Trans>
</Button>
</DialogFooter>
</TabsContent>
</form>
</Tabs>
</DialogContent>
</Dialog>
</form>
</Tabs>
</DialogContent>
)
}
})

View File

@@ -59,6 +59,7 @@ import {
ArrowUpIcon,
Settings2Icon,
EyeIcon,
PenBoxIcon,
} from "lucide-react"
import { useEffect, useMemo, useState } from "react"
import { $hubVersion, $systems, pb } from "@/lib/stores"
@@ -73,6 +74,8 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui
import { Input } from "../ui/input"
import { ClassValue } from "clsx"
import { getPagePath } from "@nanostores/router"
import { EditSystemButton, SystemDialog } from "../add-system"
import { Dialog, DialogContent } from "../ui/dialog"
type ViewMode = "table" | "grid"
@@ -560,10 +563,14 @@ function IndicatorDot({ system, className }: { system: SystemRecord; className?:
}
function ActionsButton({ system }: { system: SystemRecord }) {
// const [opened, setOpened] = useState(false)
const [deleteOpen, setDeleteOpen] = useState(false)
const [editOpen, setEditOpen] = useState(false)
const [editOpened, setEditOpened] = useState(false)
const { id, status, host, name } = system
return (
<AlertDialog>
<>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size={"icon"} data-nolink>
@@ -574,6 +581,16 @@ function ActionsButton({ system }: { system: SystemRecord }) {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{/* <EditSystemButton /> */}
<DropdownMenuItem
onSelect={() => {
setEditOpened(true)
setEditOpen(true)
}}
>
<PenBoxIcon className="me-2.5 size-4" />
<Trans>Edit</Trans>
</DropdownMenuItem>
<DropdownMenuItem
className={cn(isReadOnlyUser() && "hidden")}
onClick={() => {
@@ -599,38 +616,45 @@ function ActionsButton({ system }: { system: SystemRecord }) {
<Trans>Copy host</Trans>
</DropdownMenuItem>
<DropdownMenuSeparator className={cn(isReadOnlyUser() && "hidden")} />
<AlertDialogTrigger asChild>
<DropdownMenuItem className={cn(isReadOnlyUser() && "hidden")}>
<Trash2Icon className="me-2.5 size-4" />
<Trans>Delete</Trans>
</DropdownMenuItem>
</AlertDialogTrigger>
{/* <AlertDialogTrigger asChild> */}
<DropdownMenuItem className={cn(isReadOnlyUser() && "hidden")} onSelect={() => setDeleteOpen(true)}>
<Trash2Icon className="me-2.5 size-4" />
<Trans>Delete</Trans>
</DropdownMenuItem>
{/* </AlertDialogTrigger> */}
</DropdownMenuContent>
</DropdownMenu>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
<Trans>Are you sure you want to delete {name}?</Trans>
</AlertDialogTitle>
<AlertDialogDescription>
<Trans>
This action cannot be undone. This will permanently delete all current records for {name} from the
database.
</Trans>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>
<Trans>Cancel</Trans>
</AlertDialogCancel>
<AlertDialogAction
className={cn(buttonVariants({ variant: "destructive" }))}
onClick={() => pb.collection("systems").delete(id)}
>
<Trans>Continue</Trans>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{/* edit dialog */}
<Dialog open={editOpen} onOpenChange={setEditOpen}>
{editOpened && <SystemDialog setOpen={setEditOpen} />}
</Dialog>
{/* deletion dialog */}
<AlertDialog open={deleteOpen} onOpenChange={(open) => setDeleteOpen(open)}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
<Trans>Are you sure you want to delete {name}?</Trans>
</AlertDialogTitle>
<AlertDialogDescription>
<Trans>
This action cannot be undone. This will permanently delete all current records for {name} from the
database.
</Trans>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>
<Trans>Cancel</Trans>
</AlertDialogCancel>
<AlertDialogAction
className={cn(buttonVariants({ variant: "destructive" }))}
onClick={() => pb.collection("systems").delete(id)}
>
<Trans>Continue</Trans>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
)
}