mirror of
https://github.com/fankes/beszel.git
synced 2025-10-19 17:59:28 +08:00
move alerts ui to sheet component
This commit is contained in:
@@ -17,7 +17,7 @@ import { $publicKey, pb } from "@/lib/stores"
|
|||||||
import { cn, generateToken, isReadOnlyUser, tokenMap, useLocalStorage } from "@/lib/utils"
|
import { cn, generateToken, isReadOnlyUser, tokenMap, useLocalStorage } from "@/lib/utils"
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
import { ChevronDownIcon, ExternalLinkIcon, PlusIcon } from "lucide-react"
|
import { ChevronDownIcon, ExternalLinkIcon, PlusIcon } from "lucide-react"
|
||||||
import { memo, useEffect, useRef, useState } from "react"
|
import { memo, useEffect, useMemo, useRef, useState } from "react"
|
||||||
import { $router, basePath, Link, navigate } from "./router"
|
import { $router, basePath, Link, navigate } from "./router"
|
||||||
import { SystemRecord } from "@/types"
|
import { SystemRecord } from "@/types"
|
||||||
import { AppleIcon, DockerIcon, TuxIcon, WindowsIcon } from "./ui/icons"
|
import { AppleIcon, DockerIcon, TuxIcon, WindowsIcon } from "./ui/icons"
|
||||||
@@ -122,7 +122,8 @@ export const SystemDialog = ({ setOpen, system }: { setOpen: (open: boolean) =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return useMemo(
|
||||||
|
() => (
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="w-[90%] sm:w-auto sm:ns-dialog max-w-full rounded-lg"
|
className="w-[90%] sm:w-auto sm:ns-dialog max-w-full rounded-lg"
|
||||||
onCloseAutoFocus={() => {
|
onCloseAutoFocus={() => {
|
||||||
@@ -165,9 +166,7 @@ export const SystemDialog = ({ setOpen, system }: { setOpen: (open: boolean) =>
|
|||||||
<Trans>
|
<Trans>
|
||||||
Copy the installation command for the agent below, or register agents automatically with a{" "}
|
Copy the installation command for the agent below, or register agents automatically with a{" "}
|
||||||
<Link
|
<Link
|
||||||
onClick={() => {
|
onClick={() => setOpen(false)}
|
||||||
setOpen(false)
|
|
||||||
}}
|
|
||||||
href={getPagePath($router, "settings", { name: "tokens" })}
|
href={getPagePath($router, "settings", { name: "tokens" })}
|
||||||
className="link"
|
className="link"
|
||||||
>
|
>
|
||||||
@@ -239,7 +238,9 @@ export const SystemDialog = ({ setOpen, system }: { setOpen: (open: boolean) =>
|
|||||||
<CopyButton
|
<CopyButton
|
||||||
text={t`Copy Linux command`}
|
text={t`Copy Linux command`}
|
||||||
icon={<TuxIcon className="size-4" />}
|
icon={<TuxIcon className="size-4" />}
|
||||||
onClick={async () => copyLinuxCommand(isUnixSocket ? hostValue : port.current?.value, publicKey, token)}
|
onClick={async () =>
|
||||||
|
copyLinuxCommand(isUnixSocket ? hostValue : port.current?.value, publicKey, token)
|
||||||
|
}
|
||||||
dropdownItems={[
|
dropdownItems={[
|
||||||
{
|
{
|
||||||
text: t({ message: "Homebrew command", context: "Button to copy install command" }),
|
text: t({ message: "Homebrew command", context: "Button to copy install command" }),
|
||||||
@@ -267,6 +268,8 @@ export const SystemDialog = ({ setOpen, system }: { setOpen: (open: boolean) =>
|
|||||||
</form>
|
</form>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
),
|
||||||
|
[]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,56 +2,35 @@ import { t } from "@lingui/core/macro"
|
|||||||
import { memo, useMemo, useState } from "react"
|
import { memo, useMemo, useState } from "react"
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
import { $alerts } from "@/lib/stores"
|
import { $alerts } from "@/lib/stores"
|
||||||
import { Dialog, DialogTrigger, DialogContent } from "@/components/ui/dialog"
|
|
||||||
import { BellIcon } from "lucide-react"
|
import { BellIcon } from "lucide-react"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { SystemRecord } from "@/types"
|
import { SystemRecord } from "@/types"
|
||||||
import { AlertDialogContent } from "./alerts-dialog"
|
import { AlertDialogContent } from "./alerts-sheet"
|
||||||
|
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"
|
||||||
|
|
||||||
export default memo(function AlertsButton({ system }: { system: SystemRecord }) {
|
export default memo(function AlertsButton({ system }: { system: SystemRecord }) {
|
||||||
const [opened, setOpened] = useState(false)
|
const [opened, setOpened] = useState(false)
|
||||||
const alerts = useStore($alerts)
|
const alerts = useStore($alerts)
|
||||||
|
|
||||||
const hasSystemAlert = alerts[system.id]?.size > 0
|
const hasSystemAlert = alerts[system.id]?.size > 0
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => (
|
() => (
|
||||||
<Dialog>
|
<Sheet>
|
||||||
<DialogTrigger asChild>
|
<SheetTrigger asChild>
|
||||||
<Button variant="ghost" size="icon" aria-label={t`Alerts`} onClick={() => setOpened(true)}>
|
<Button variant="ghost" size="icon" aria-label={t`Alerts`} data-nolink onClick={() => setOpened(true)}>
|
||||||
<BellIcon
|
<BellIcon
|
||||||
className={cn("h-[1.2em] w-[1.2em]", {
|
className={cn("h-[1.2em] w-[1.2em] pointer-events-none", {
|
||||||
"fill-primary": hasSystemAlert,
|
"fill-primary": hasSystemAlert,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</SheetTrigger>
|
||||||
<DialogContent className="max-h-full sm:max-h-[95svh] overflow-auto max-w-148">
|
<SheetContent className="max-h-full overflow-auto w-145 !max-w-full p-4 sm:p-6">
|
||||||
{opened && <AlertDialogContent system={system} />}
|
{opened && <AlertDialogContent system={system} />}
|
||||||
</DialogContent>
|
</SheetContent>
|
||||||
</Dialog>
|
</Sheet>
|
||||||
),
|
),
|
||||||
[opened, hasSystemAlert]
|
[opened, hasSystemAlert]
|
||||||
)
|
)
|
||||||
|
|
||||||
// return useMemo(
|
|
||||||
// () => (
|
|
||||||
// <Sheet>
|
|
||||||
// <SheetTrigger asChild>
|
|
||||||
// <Button variant="ghost" size="icon" aria-label={t`Alerts`} data-nolink onClick={() => setOpened(true)}>
|
|
||||||
// <BellIcon
|
|
||||||
// className={cn("h-[1.2em] w-[1.2em] pointer-events-none", {
|
|
||||||
// "fill-primary": hasAlert,
|
|
||||||
// })}
|
|
||||||
// />
|
|
||||||
// </Button>
|
|
||||||
// </SheetTrigger>
|
|
||||||
// <SheetContent className="max-h-full overflow-auto w-[35em] p-4 sm:p-5">
|
|
||||||
// {opened && <AlertDialogContent system={system} />}
|
|
||||||
// </SheetContent>
|
|
||||||
// </Sheet>
|
|
||||||
// ),
|
|
||||||
// [opened, hasAlert]
|
|
||||||
// )
|
|
||||||
})
|
})
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Trans } from "@lingui/react/macro";
|
import { Trans } from "@lingui/react/macro"
|
||||||
import { useState, lazy, Suspense } from "react"
|
import { useState, lazy, Suspense } from "react"
|
||||||
import { Button, buttonVariants } from "@/components/ui/button"
|
import { Button, buttonVariants } from "@/components/ui/button"
|
||||||
import {
|
import {
|
||||||
|
@@ -45,7 +45,7 @@ export function Link(props: React.AnchorHTMLAttributes<HTMLAnchorElement>) {
|
|||||||
if (e.ctrlKey || e.metaKey) {
|
if (e.ctrlKey || e.metaKey) {
|
||||||
window.open(href, "_blank")
|
window.open(href, "_blank")
|
||||||
} else {
|
} else {
|
||||||
$router.open(href)
|
navigate(href)
|
||||||
props.onClick?.(e)
|
props.onClick?.(e)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
@@ -111,8 +111,7 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
|
|||||||
Icon: ServerIcon,
|
Icon: ServerIcon,
|
||||||
cell: (info) => {
|
cell: (info) => {
|
||||||
const { name } = info.row.original
|
const { name } = info.row.original
|
||||||
return useMemo(
|
return (
|
||||||
() => (
|
|
||||||
<>
|
<>
|
||||||
<span className="flex gap-2 items-center font-medium text-sm text-nowrap md:ps-1 md:pe-5">
|
<span className="flex gap-2 items-center font-medium text-sm text-nowrap md:ps-1 md:pe-5">
|
||||||
<IndicatorDot system={info.row.original} />
|
<IndicatorDot system={info.row.original} />
|
||||||
@@ -124,8 +123,6 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
|
|||||||
aria-label={name}
|
aria-label={name}
|
||||||
></Link>
|
></Link>
|
||||||
</>
|
</>
|
||||||
),
|
|
||||||
[name]
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
header: sortableHeader,
|
header: sortableHeader,
|
||||||
|
101
beszel/site/src/components/ui/sheet.tsx
Normal file
101
beszel/site/src/components/ui/sheet.tsx
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
||||||
|
import { XIcon } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
|
||||||
|
return <SheetPrimitive.Root data-slot="sheet" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetTrigger({ ...props }: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
|
||||||
|
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetClose({ ...props }: React.ComponentProps<typeof SheetPrimitive.Close>) {
|
||||||
|
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetPortal({ ...props }: React.ComponentProps<typeof SheetPrimitive.Portal>) {
|
||||||
|
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetOverlay({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
|
||||||
|
return (
|
||||||
|
<SheetPrimitive.Overlay
|
||||||
|
data-slot="sheet-overlay"
|
||||||
|
className={cn(
|
||||||
|
"data-[state=open]:animate-in duration-500 isolate data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/40",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetContent({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
side = "right",
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
|
||||||
|
side?: "top" | "right" | "bottom" | "left"
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<SheetPortal>
|
||||||
|
<SheetOverlay />
|
||||||
|
<SheetPrimitive.Content
|
||||||
|
data-slot="sheet-content"
|
||||||
|
className={cn(
|
||||||
|
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-[400ms]",
|
||||||
|
side === "right" &&
|
||||||
|
"data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
|
||||||
|
side === "left" &&
|
||||||
|
"data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
|
||||||
|
side === "top" &&
|
||||||
|
"data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b",
|
||||||
|
side === "bottom" &&
|
||||||
|
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
|
||||||
|
<XIcon className="size-4" />
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
</SheetPrimitive.Close>
|
||||||
|
</SheetPrimitive.Content>
|
||||||
|
</SheetPortal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return <div data-slot="sheet-header" className={cn("flex flex-col gap-1.5 p-4", className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return <div data-slot="sheet-footer" className={cn("mt-auto flex flex-col gap-2 p-4", className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetTitle({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Title>) {
|
||||||
|
return (
|
||||||
|
<SheetPrimitive.Title
|
||||||
|
data-slot="sheet-title"
|
||||||
|
className={cn("text-foreground font-semibold", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetDescription({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Description>) {
|
||||||
|
return (
|
||||||
|
<SheetPrimitive.Description
|
||||||
|
data-slot="sheet-description"
|
||||||
|
className={cn("text-muted-foreground text-sm", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Sheet, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription }
|
@@ -27,7 +27,7 @@ const TabsTrigger = React.forwardRef<
|
|||||||
<TabsPrimitive.Trigger
|
<TabsPrimitive.Trigger
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-xs",
|
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-xs cursor-pointer",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
Reference in New Issue
Block a user