mirror of
https://github.com/fankes/beszel.git
synced 2025-10-20 02:09:28 +08:00
progress on settings page
This commit is contained in:
@@ -4,7 +4,7 @@ export const $router = createRouter(
|
|||||||
{
|
{
|
||||||
home: '/',
|
home: '/',
|
||||||
server: '/system/:name',
|
server: '/system/:name',
|
||||||
'forgot-password': '/forgot-password',
|
settings: '/settings/:name?',
|
||||||
},
|
},
|
||||||
{ links: false }
|
{ links: false }
|
||||||
)
|
)
|
||||||
@@ -16,7 +16,7 @@ export const navigate = (urlString: string) => {
|
|||||||
|
|
||||||
function onClick(e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) {
|
function onClick(e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
$router.open(new URL((e.target as HTMLAnchorElement).href).pathname)
|
$router.open(new URL((e.currentTarget as HTMLAnchorElement).href).pathname)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Link = (props: React.AnchorHTMLAttributes<HTMLAnchorElement>) => {
|
export const Link = (props: React.AnchorHTMLAttributes<HTMLAnchorElement>) => {
|
||||||
|
46
beszel/site/src/components/routes/settings/general.tsx
Normal file
46
beszel/site/src/components/routes/settings/general.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Label } from '@/components/ui/label'
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select'
|
||||||
|
import { chartTimeData } from '@/lib/utils'
|
||||||
|
import { Separator } from '@/components/ui/separator'
|
||||||
|
|
||||||
|
export default function SettingsProfilePage() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{/* <div>
|
||||||
|
<h3 className="text-lg font-medium mb-1">General</h3>
|
||||||
|
<p className="text-sm text-muted-foreground">Set your preferred language and timezone.</p>
|
||||||
|
</div>
|
||||||
|
<Separator className="mt-6 mb-5" /> */}
|
||||||
|
<div className="space-y-5">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Default chart period</Label>
|
||||||
|
{/* <Input placeholder="Username" /> */}
|
||||||
|
<Select defaultValue="1h">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{Object.entries(chartTimeData).map(([value, { label }]) => (
|
||||||
|
<SelectItem key={label} value={value}>
|
||||||
|
{label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<p className="text-[0.8rem] text-muted-foreground">
|
||||||
|
Sets the default time range for charts.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<Button type="submit">Save settings</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
71
beszel/site/src/components/routes/settings/layout.tsx
Normal file
71
beszel/site/src/components/routes/settings/layout.tsx
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import { Suspense, lazy, useEffect } from 'react'
|
||||||
|
import { Separator } from '../../ui/separator'
|
||||||
|
import { SidebarNav } from './sidebar-nav.tsx'
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card.tsx'
|
||||||
|
import { useStore } from '@nanostores/react'
|
||||||
|
import { $router } from '@/components/router.tsx'
|
||||||
|
import { redirectPage } from '@nanostores/router'
|
||||||
|
import { BellIcon, SettingsIcon } from 'lucide-react'
|
||||||
|
|
||||||
|
const General = lazy(() => import('./general.tsx'))
|
||||||
|
const Notifications = lazy(() => import('./notifications.tsx'))
|
||||||
|
|
||||||
|
const sidebarNavItems = [
|
||||||
|
{
|
||||||
|
title: 'General',
|
||||||
|
href: '/settings/general',
|
||||||
|
icon: SettingsIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Notifications',
|
||||||
|
href: '/settings/notifications',
|
||||||
|
icon: BellIcon,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function SettingsLayout() {
|
||||||
|
const page = useStore($router)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.title = 'Settings / Beszel'
|
||||||
|
// redirect to account page if no page is specified
|
||||||
|
if (page?.path === '/settings') {
|
||||||
|
redirectPage($router, 'settings', { name: 'general' })
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="pt-5 px-4 pb-9 sm:pt-6 sm:px-7">
|
||||||
|
<CardHeader className="p-0">
|
||||||
|
<CardTitle className="mb-1">Settings</CardTitle>
|
||||||
|
<CardDescription>Manage your account settings and set e-mail preferences.</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-0">
|
||||||
|
<Separator className="my-5" />
|
||||||
|
<div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
|
||||||
|
<aside className="lg:w-48 w-full overflow-auto">
|
||||||
|
<SidebarNav items={sidebarNavItems} />
|
||||||
|
</aside>
|
||||||
|
<div className="flex-1">
|
||||||
|
<Suspense>
|
||||||
|
{/* @ts-ignore */}
|
||||||
|
<SettingsContent name={page?.params?.name ?? 'general'} />
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SettingsContent({ name }: { name: string }) {
|
||||||
|
switch (name) {
|
||||||
|
case 'general':
|
||||||
|
return <General />
|
||||||
|
// case 'display':
|
||||||
|
// return <Display />
|
||||||
|
case 'notifications':
|
||||||
|
return <Notifications />
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
98
beszel/site/src/components/routes/settings/notifications.tsx
Normal file
98
beszel/site/src/components/routes/settings/notifications.tsx
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
import { Label } from '@/components/ui/label'
|
||||||
|
import { pb } from '@/lib/stores'
|
||||||
|
import { Separator } from '@/components/ui/separator'
|
||||||
|
import { Card } from '@/components/ui/card'
|
||||||
|
// import { Switch } from '@/components/ui/switch'
|
||||||
|
import { Trash2Icon } from 'lucide-react'
|
||||||
|
|
||||||
|
export default function SettingsNotificationsPage() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{/* <div>
|
||||||
|
<h3 className="text-xl font-medium mb-1">Notifications</h3>
|
||||||
|
<p className="text-sm text-muted-foreground">Configure how you receive notifications.</p>
|
||||||
|
</div>
|
||||||
|
<Separator className="my-6" /> */}
|
||||||
|
<div className="space-y-5">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="mb-4">
|
||||||
|
<h3 className="mb-1 text-lg font-medium">Email notifications</h3>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Get notified when new alerts are created.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Label className="block">To email(s)</Label>
|
||||||
|
<Input placeholder="name@example.com" defaultValue={pb.authStore.model?.email} />
|
||||||
|
<p className="text-[0.8rem] text-muted-foreground">
|
||||||
|
Separate multiple emails with commas.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-1 text-lg font-medium">Webhook / Push notifications</h3>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Beszel uses{' '}
|
||||||
|
<a
|
||||||
|
href="https://containrrr.dev/shoutrrr/v0.8/services/overview/"
|
||||||
|
target="_blank"
|
||||||
|
className="font-medium text-primary opacity-80 hover:opacity-100 duration-100"
|
||||||
|
>
|
||||||
|
Shoutrrr
|
||||||
|
</a>{' '}
|
||||||
|
to integrate with popular notification services.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Card className="bg-muted/30 p-3.5">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Label htmlFor="name" className="sr-only">
|
||||||
|
URL
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
className="light:bg-card"
|
||||||
|
required
|
||||||
|
placeholder="generic://example.com?@header=value"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
className=""
|
||||||
|
// onClick={() => append({ value: '' })}
|
||||||
|
>
|
||||||
|
Test
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
className="shrink-0"
|
||||||
|
// onClick={() => append({ value: '' })}
|
||||||
|
>
|
||||||
|
<Trash2Icon className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
{/* <Label htmlFor="enabled-01" className="sr-only">
|
||||||
|
Enabled
|
||||||
|
</Label>
|
||||||
|
<Switch defaultChecked id="enabled-01" className="ml-2" /> */}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="mt-2"
|
||||||
|
// onClick={() => append({ value: '' })}
|
||||||
|
>
|
||||||
|
Add URL
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<Button type="submit">Save settings</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
40
beszel/site/src/components/routes/settings/sidebar-nav.tsx
Normal file
40
beszel/site/src/components/routes/settings/sidebar-nav.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { buttonVariants } from '../../ui/button'
|
||||||
|
import { $router, Link } from '../../router'
|
||||||
|
import { useStore } from '@nanostores/react'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
interface SidebarNavProps extends React.HTMLAttributes<HTMLElement> {
|
||||||
|
items: {
|
||||||
|
href: string
|
||||||
|
title: string
|
||||||
|
icon?: React.FC<React.SVGProps<SVGSVGElement>>
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SidebarNav({ className, items, ...props }: SidebarNavProps) {
|
||||||
|
const page = useStore($router)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav
|
||||||
|
className={cn('flex space-x-2 lg:flex-col lg:space-x-0 lg:space-y-1', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{items.map((item) => (
|
||||||
|
<Link
|
||||||
|
key={item.href}
|
||||||
|
href={item.href}
|
||||||
|
className={cn(
|
||||||
|
buttonVariants({ variant: 'ghost' }),
|
||||||
|
'flex items-center gap-3',
|
||||||
|
page?.path === item.href ? 'bg-muted hover:bg-muted' : 'hover:bg-muted/50',
|
||||||
|
'justify-start'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{item.icon && <item.icon className="h-4 w-4" />}
|
||||||
|
{item.title}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
)
|
||||||
|
}
|
@@ -1,5 +1,5 @@
|
|||||||
import './index.css'
|
import './index.css'
|
||||||
import React, { Suspense, lazy, useEffect } from 'react'
|
import { Suspense, lazy, useEffect } from 'react'
|
||||||
import ReactDOM from 'react-dom/client'
|
import ReactDOM from 'react-dom/client'
|
||||||
import Home from './components/routes/home.tsx'
|
import Home from './components/routes/home.tsx'
|
||||||
import { ThemeProvider } from './components/theme-provider.tsx'
|
import { ThemeProvider } from './components/theme-provider.tsx'
|
||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
LogOutIcon,
|
LogOutIcon,
|
||||||
LogsIcon,
|
LogsIcon,
|
||||||
ServerIcon,
|
ServerIcon,
|
||||||
|
SettingsIcon,
|
||||||
UserIcon,
|
UserIcon,
|
||||||
UsersIcon,
|
UsersIcon,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
@@ -42,7 +43,7 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
DropdownMenuLabel,
|
DropdownMenuLabel,
|
||||||
} from './components/ui/dropdown-menu.tsx'
|
} from './components/ui/dropdown-menu.tsx'
|
||||||
import { $router, Link, navigate } from './components/router.tsx'
|
import { $router, Link } 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'
|
import { AddSystemButton } from './components/add-system.tsx'
|
||||||
|
|
||||||
@@ -50,6 +51,7 @@ import { AddSystemButton } from './components/add-system.tsx'
|
|||||||
const CommandPalette = lazy(() => import('./components/command-palette.tsx'))
|
const CommandPalette = lazy(() => import('./components/command-palette.tsx'))
|
||||||
const LoginPage = lazy(() => import('./components/login/login.tsx'))
|
const LoginPage = lazy(() => import('./components/login/login.tsx'))
|
||||||
const CopyToClipboardDialog = lazy(() => import('./components/copy-to-clipboard.tsx'))
|
const CopyToClipboardDialog = lazy(() => import('./components/copy-to-clipboard.tsx'))
|
||||||
|
const Settings = lazy(() => import('./components/routes/settings/layout.tsx'))
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const page = useStore($router)
|
const page = useStore($router)
|
||||||
@@ -99,6 +101,12 @@ const App = () => {
|
|||||||
return <Home />
|
return <Home />
|
||||||
} else if (page.route === 'server') {
|
} else if (page.route === 'server') {
|
||||||
return <SystemDetail name={page.params.name} />
|
return <SystemDetail name={page.params.name} />
|
||||||
|
} else if (page.path.startsWith('/settings')) {
|
||||||
|
return (
|
||||||
|
<Suspense>
|
||||||
|
<Settings />
|
||||||
|
</Suspense>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,20 +126,19 @@ const Layout = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<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">
|
<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="/" aria-label="Home" className={'p-2 pl-0'}>
|
||||||
href="/"
|
|
||||||
aria-label="Home"
|
|
||||||
className={'p-2 pl-0'}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
navigate('/')
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Logo className="h-[1.15em] fill-foreground" />
|
<Logo className="h-[1.15em] fill-foreground" />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className={'flex ml-auto items-center'}>
|
<div className={'flex ml-auto items-center'}>
|
||||||
<ModeToggle />
|
<ModeToggle />
|
||||||
|
<Link
|
||||||
|
href="/settings/general"
|
||||||
|
aria-label="Settings"
|
||||||
|
className={cn('', buttonVariants({ variant: 'ghost', size: 'icon' }))}
|
||||||
|
>
|
||||||
|
<SettingsIcon className="h-[1.2rem] w-[1.2rem]" />
|
||||||
|
</Link>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<button
|
<button
|
||||||
|
@@ -94,5 +94,10 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [require('tailwindcss-animate')],
|
plugins: [
|
||||||
|
require('tailwindcss-animate'),
|
||||||
|
function ({ addVariant }) {
|
||||||
|
addVariant('light', '.light &')
|
||||||
|
},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user