web ui design updates

This commit is contained in:
Henry Dollman
2024-08-21 15:06:33 -04:00
parent 55863f849c
commit 5278805d79
6 changed files with 110 additions and 100 deletions

View File

@@ -13,12 +13,13 @@ import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '@/comp
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label' import { Label } from '@/components/ui/label'
import { $publicKey, pb } from '@/lib/stores' import { $publicKey, pb } from '@/lib/stores'
import { Copy, Plus } from 'lucide-react' import { Copy, PlusIcon } from 'lucide-react'
import { useState, useRef, MutableRefObject } from 'react' import { useState, useRef, MutableRefObject } from 'react'
import { useStore } from '@nanostores/react' import { useStore } from '@nanostores/react'
import { copyToClipboard } from '@/lib/utils' import { cn, copyToClipboard, isReadOnlyUser } from '@/lib/utils'
import { navigate } from './router'
export function AddSystemButton() { export function AddSystemButton({ className }: { className?: string }) {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const port = useRef() as MutableRefObject<HTMLInputElement> const port = useRef() as MutableRefObject<HTMLInputElement>
const publicKey = useStore($publicKey) const publicKey = useStore($publicKey)
@@ -46,6 +47,7 @@ export function AddSystemButton() {
try { try {
setOpen(false) setOpen(false)
await pb.collection('systems').create(data) await pb.collection('systems').create(data)
navigate('/')
// console.log(record) // console.log(record)
} catch (e) { } catch (e) {
console.log(e) console.log(e)
@@ -55,8 +57,11 @@ export function AddSystemButton() {
return ( return (
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="outline" className="flex gap-1"> <Button
<Plus className="h-4 w-4 mr-auto" /> variant="outline"
className={cn('flex gap-1 max-xs:h-[2.4rem]', className, isReadOnlyUser() && 'hidden')}
>
<PlusIcon className="h-4 w-4 -ml-1" />
Add <span className="hidden sm:inline">System</span> Add <span className="hidden sm:inline">System</span>
</Button> </Button>
</DialogTrigger> </DialogTrigger>

View File

@@ -43,7 +43,7 @@ export default function CommandPalette() {
return ( return (
<CommandDialog open={open} onOpenChange={setOpen}> <CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput placeholder="Type a command or search..." /> <CommandInput placeholder="Search for systems or settings..." />
<CommandList> <CommandList>
<CommandEmpty>No results found.</CommandEmpty> <CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions"> <CommandGroup heading="Suggestions">

View File

@@ -15,7 +15,7 @@ export function ModeToggle() {
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant={'ghost'} size="icon"> <Button className="max-sm:w-9" variant={'ghost'} size="icon">
<Sun className="h-[1.2rem] w-[1.2rem] dark:opacity-0" /> <Sun className="h-[1.2rem] w-[1.2rem] dark:opacity-0" />
<MoonStarIcon className="absolute h-[1.2rem] w-[1.2rem] opacity-0 dark:opacity-100" /> <MoonStarIcon className="absolute h-[1.2rem] w-[1.2rem] opacity-0 dark:opacity-100" />
<span className="sr-only">Toggle theme</span> <span className="sr-only">Toggle theme</span>

View File

@@ -1,20 +1,25 @@
import { Suspense, lazy, useEffect } from 'react' import { Suspense, lazy, useEffect, useState } from 'react'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card'
import { $alerts, $hubVersion, $systems, pb } from '@/lib/stores' import { $alerts, $hubVersion, $systems, pb } from '@/lib/stores'
import { useStore } from '@nanostores/react' import { useStore } from '@nanostores/react'
import { GithubIcon } from 'lucide-react' import { GithubIcon } from 'lucide-react'
import { Separator } from '../ui/separator' import { Separator } from '../ui/separator'
import { updateRecordList } from '@/lib/utils' import { updateRecordList, updateSystemList } from '@/lib/utils'
import { AlertRecord, SystemRecord } from '@/types' import { AlertRecord, SystemRecord } from '@/types'
import { Input } from '../ui/input'
const SystemsTable = lazy(() => import('../systems-table/systems-table')) const SystemsTable = lazy(() => import('../systems-table/systems-table'))
export default function () { export default function () {
const hubVersion = useStore($hubVersion) const hubVersion = useStore($hubVersion)
const [filter, setFilter] = useState<string>()
useEffect(() => { useEffect(() => {
document.title = 'Dashboard / Beszel' document.title = 'Dashboard / Beszel'
// make sure we have the latest list of systems
updateSystemList()
// subscribe to real time updates for systems / alerts // subscribe to real time updates for systems / alerts
pb.collection<SystemRecord>('systems').subscribe('*', (e) => { pb.collection<SystemRecord>('systems').subscribe('*', (e) => {
updateRecordList(e, $systems) updateRecordList(e, $systems)
@@ -31,19 +36,29 @@ export default function () {
return ( return (
<> <>
<Card> <Card>
<CardHeader className="pb-2 md:pb-5 px-4 sm:px-7 max-sm:pt-5"> <CardHeader className="pb-5 px-2 sm:px-6 max-sm:pt-5 max-sm:pb-1">
<CardTitle className="mb-1.5">All Systems</CardTitle> <div className="grid md:flex gap-3 w-full items-end">
<CardDescription> <div className="px-2 sm:px-1">
Updated in real time. Press{' '} <CardTitle className="mb-2.5">All Systems</CardTitle>
<kbd className="pointer-events-none inline-flex h-5 select-none items-center gap-0.5 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground opacity-100"> <CardDescription>
<span className="text-xs"></span>K Updated in real time. Press{' '}
</kbd>{' '} <kbd className="pointer-events-none inline-flex h-5 select-none items-center gap-0.5 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground opacity-100">
to open the command palette. <span className="text-xs"></span>K
</CardDescription> </kbd>{' '}
to open the command palette.
</CardDescription>
</div>
<Input
// @ts-ignore
placeholder="Filter..."
onChange={(e) => setFilter(e.target.value)}
className="w-full md:w-56 lg:w-80 ml-auto"
/>
</div>
</CardHeader> </CardHeader>
<CardContent className="max-sm:p-2"> <CardContent className="max-sm:p-2">
<Suspense> <Suspense>
<SystemsTable /> <SystemsTable filter={filter} />
</Suspense> </Suspense>
</CardContent> </CardContent>
</Card> </Card>

View File

@@ -57,10 +57,9 @@ import {
Trash2Icon, Trash2Icon,
WifiIcon, WifiIcon,
} from 'lucide-react' } from 'lucide-react'
import { useMemo, useState } from 'react' import { useEffect, useMemo, useState } from 'react'
import { $hubVersion, $systems, pb } from '@/lib/stores' import { $hubVersion, $systems, pb } from '@/lib/stores'
import { useStore } from '@nanostores/react' import { useStore } from '@nanostores/react'
import { AddSystemButton } from '../add-system'
import { cn, copyToClipboard, isReadOnlyUser } from '@/lib/utils' import { cn, copyToClipboard, isReadOnlyUser } from '@/lib/utils'
import AlertsButton from '../table-alerts' import AlertsButton from '../table-alerts'
import { navigate } from '../router' import { navigate } from '../router'
@@ -102,12 +101,18 @@ function sortableHeader(
) )
} }
export default function SystemsTable() { export default function SystemsTable({ filter }: { filter?: string }) {
const data = useStore($systems) const data = useStore($systems)
const hubVersion = useStore($hubVersion) const hubVersion = useStore($hubVersion)
const [sorting, setSorting] = useState<SortingState>([]) const [sorting, setSorting] = useState<SortingState>([])
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]) const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
useEffect(() => {
if (filter !== undefined) {
table.getColumn('name')?.setFilterValue(filter)
}
}, [filter])
const columns: ColumnDef<SystemRecord>[] = useMemo(() => { const columns: ColumnDef<SystemRecord>[] = useMemo(() => {
return [ return [
{ {
@@ -166,7 +171,7 @@ export default function SystemsTable() {
return null return null
} }
return ( return (
<span className="flex gap-2 items-center md:pr-5 tabular-nums pl-1.5"> <span className="flex gap-2 items-center md:pr-5 tabular-nums pl-1">
<span <span
className={cn( className={cn(
'w-2 h-2 left-0 rounded-full', 'w-2 h-2 left-0 rounded-full',
@@ -278,80 +283,64 @@ export default function SystemsTable() {
}) })
return ( return (
<> <div className="rounded-md border overflow-hidden">
<div className="w-full"> <Table>
<div className="flex items-center mb-4 gap-2"> <TableHeader className="bg-muted/40">
<Input {table.getHeaderGroups().map((headerGroup) => (
// @ts-ignore <TableRow key={headerGroup.id}>
placeholder="Filter..." {headerGroup.headers.map((header) => {
value={(table.getColumn('name')?.getFilterValue() as string) ?? ''} return (
onChange={(event) => table.getColumn('name')?.setFilterValue(event.target.value)} <TableHead className="px-2" key={header.id}>
className="max-w-sm" {header.isPlaceholder
/> ? null
<div className={cn('ml-auto flex gap-2', isReadOnlyUser() && 'hidden')}> : flexRender(header.column.columnDef.header, header.getContext())}
<AddSystemButton /> </TableHead>
</div> )
</div> })}
<div className="rounded-md border overflow-hidden"> </TableRow>
<Table> ))}
<TableHeader className="bg-muted/40"> </TableHeader>
{table.getHeaderGroups().map((headerGroup) => ( <TableBody>
<TableRow key={headerGroup.id}> {table.getRowModel().rows?.length ? (
{headerGroup.headers.map((header) => { table.getRowModel().rows.map((row) => (
return ( <TableRow
<TableHead className="px-2" key={header.id}> key={row.original.id}
{header.isPlaceholder data-state={row.getIsSelected() && 'selected'}
? null className={cn('cursor-pointer transition-opacity', {
: flexRender(header.column.columnDef.header, header.getContext())} 'opacity-50': row.original.status === 'paused',
</TableHead> })}
) onClick={(e) => {
})} const target = e.target as HTMLElement
</TableRow> if (!target.closest('[data-nolink]') && e.currentTarget.contains(target)) {
))} navigate(`/system/${encodeURIComponent(row.original.name)}`)
</TableHeader> }
<TableBody> }}
{table.getRowModel().rows?.length ? ( >
table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => (
<TableRow <TableCell
key={row.original.id} key={cell.id}
data-state={row.getIsSelected() && 'selected'} style={{
className={cn('cursor-pointer transition-opacity', { width:
'opacity-50': row.original.status === 'paused', cell.column.getSize() === Number.MAX_SAFE_INTEGER
})} ? 'auto'
onClick={(e) => { : cell.column.getSize(),
const target = e.target as HTMLElement
if (!target.closest('[data-nolink]') && e.currentTarget.contains(target)) {
navigate(`/system/${encodeURIComponent(row.original.name)}`)
}
}} }}
className={'overflow-hidden relative py-2.5'}
> >
{row.getVisibleCells().map((cell) => ( {flexRender(cell.column.columnDef.cell, cell.getContext())}
<TableCell
key={cell.id}
style={{
width:
cell.column.getSize() === Number.MAX_SAFE_INTEGER
? 'auto'
: cell.column.getSize(),
}}
className={'overflow-hidden relative py-2.5'}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No systems found
</TableCell> </TableCell>
</TableRow> ))}
)} </TableRow>
</TableBody> ))
</Table> ) : (
</div> <TableRow>
</div> <TableCell colSpan={columns.length} className="h-24 text-center">
</> No systems found
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
) )
} }

View File

@@ -28,9 +28,9 @@ import {
DropdownMenuTrigger, DropdownMenuTrigger,
DropdownMenuLabel, DropdownMenuLabel,
} from './components/ui/dropdown-menu.tsx' } from './components/ui/dropdown-menu.tsx'
import { AlertRecord, SystemRecord } from './types'
import { $router, Link, navigate } from './components/router.tsx' import { $router, Link, navigate } from './components/router.tsx'
import SystemDetail from './components/routes/system.tsx' import SystemDetail from './components/routes/system.tsx'
import { AddSystemButton } from './components/add-system.tsx'
// const ServerDetail = lazy(() => import('./components/routes/system.tsx')) // const ServerDetail = lazy(() => import('./components/routes/system.tsx'))
const CommandPalette = lazy(() => import('./components/command-palette.tsx')) const CommandPalette = lazy(() => import('./components/command-palette.tsx'))
@@ -101,7 +101,7 @@ const Layout = () => {
return ( return (
<> <>
<div className="container"> <div className="container">
<div className="flex items-center h-14 md:h-16 bg-card px-6 border bt-0 rounded-md my-4"> <div className="flex items-center h-14 md:h-16 bg-card px-4 pr-3 sm:px-6 border bt-0 rounded-md my-4">
<Link <Link
href="/" href="/"
aria-label="Home" aria-label="Home"
@@ -114,18 +114,18 @@ const Layout = () => {
<Logo className="h-[1.15em] fill-foreground" /> <Logo className="h-[1.15em] fill-foreground" />
</Link> </Link>
<div className={'flex ml-auto'}> <div className={'flex ml-auto items-center'}>
<ModeToggle /> <ModeToggle />
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<button <button
aria-label="User Actions" aria-label="User Actions"
className={cn('', buttonVariants({ variant: 'ghost', size: 'icon' }))} className={cn('max-sm:w-9', buttonVariants({ variant: 'ghost', size: 'icon' }))}
> >
<UserIcon className="h-[1.2rem] w-[1.2rem]" /> <UserIcon className="h-[1.2rem] w-[1.2rem]" />
</button> </button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end" className="min-w-44"> <DropdownMenuContent className="min-w-44">
<DropdownMenuLabel>{pb.authStore.model?.email}</DropdownMenuLabel> <DropdownMenuLabel>{pb.authStore.model?.email}</DropdownMenuLabel>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuGroup> <DropdownMenuGroup>
@@ -171,6 +171,7 @@ const Layout = () => {
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
<AddSystemButton className="ml-2" />
</div> </div>
</div> </div>
</div> </div>