mirror of
https://github.com/fankes/beszel.git
synced 2025-10-19 17:59:28 +08:00
Merge branch 'ArsFy-main'
This commit is contained in:
Binary file not shown.
@@ -31,11 +31,14 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.0.0",
|
"cmdk": "^1.0.0",
|
||||||
"d3-time": "^3.1.0",
|
"d3-time": "^3.1.0",
|
||||||
|
"i18next": "^23.16.4",
|
||||||
|
"i18next-browser-languagedetector": "^8.0.0",
|
||||||
"lucide-react": "^0.452.0",
|
"lucide-react": "^0.452.0",
|
||||||
"nanostores": "^0.11.3",
|
"nanostores": "^0.11.3",
|
||||||
"pocketbase": "^0.21.5",
|
"pocketbase": "^0.21.5",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
"react-i18next": "^15.1.0",
|
||||||
"recharts": "^2.13.0",
|
"recharts": "^2.13.0",
|
||||||
"tailwind-merge": "^2.5.4",
|
"tailwind-merge": "^2.5.4",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
@@ -9,6 +9,7 @@ import {
|
|||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from '@/components/ui/dialog'
|
} from '@/components/ui/dialog'
|
||||||
import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip'
|
import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip'
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||||
|
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
import { Label } from '@/components/ui/label'
|
import { Label } from '@/components/ui/label'
|
||||||
@@ -18,8 +19,11 @@ import { useState, useRef, MutableRefObject } from 'react'
|
|||||||
import { useStore } from '@nanostores/react'
|
import { useStore } from '@nanostores/react'
|
||||||
import { cn, copyToClipboard, isReadOnlyUser } from '@/lib/utils'
|
import { cn, copyToClipboard, isReadOnlyUser } from '@/lib/utils'
|
||||||
import { navigate } from './router'
|
import { navigate } from './router'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export function AddSystemButton({ className }: { className?: string }) {
|
export function AddSystemButton({ className }: { className?: string }) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
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)
|
||||||
@@ -41,6 +45,12 @@ export function AddSystemButton({ className }: { className?: string }) {
|
|||||||
# FILESYSTEM: /dev/sda1 # override the root partition / device for disk I/O stats`)
|
# FILESYSTEM: /dev/sda1 # override the root partition / device for disk I/O stats`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function copyInstallCommand(port: string) {
|
||||||
|
copyToClipboard(
|
||||||
|
`curl -sL https://raw.githubusercontent.com/henrygd/beszel/main/supplemental/scripts/install-agent.sh -o install-agent.sh && chmod +x install-agent.sh && ./install-agent.sh -p ${port} -k "${publicKey}"`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
async function handleSubmit(e: SubmitEvent) {
|
async function handleSubmit(e: SubmitEvent) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
const formData = new FormData(e.target as HTMLFormElement)
|
const formData = new FormData(e.target as HTMLFormElement)
|
||||||
@@ -64,85 +74,119 @@ export function AddSystemButton({ className }: { className?: string }) {
|
|||||||
className={cn('flex gap-1 max-xs:h-[2.4rem]', className, isReadOnlyUser() && 'hidden')}
|
className={cn('flex gap-1 max-xs:h-[2.4rem]', className, isReadOnlyUser() && 'hidden')}
|
||||||
>
|
>
|
||||||
<PlusIcon className="h-4 w-4 -ml-1" />
|
<PlusIcon className="h-4 w-4 -ml-1" />
|
||||||
Add <span className="hidden xs:inline">System</span>
|
{t('add')}
|
||||||
|
<span className="hidden sm:inline">{t('system')}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="w-[90%] sm:max-w-[425px] rounded-lg">
|
<DialogContent className="w-[90%] sm:max-w-[440px] rounded-lg">
|
||||||
<DialogHeader>
|
<Tabs defaultValue="docker">
|
||||||
<DialogTitle className="mb-2">Add New System</DialogTitle>
|
<DialogHeader>
|
||||||
<DialogDescription>
|
<DialogTitle className="mb-2">{t('add_system.add_new_system')}</DialogTitle>
|
||||||
The agent must be running on the system to connect. Copy the{' '}
|
<TabsList className="grid w-full grid-cols-2">
|
||||||
<code className="bg-muted px-1 rounded-sm">docker-compose.yml</code> for the agent
|
<TabsTrigger value="docker">Docker</TabsTrigger>
|
||||||
below.
|
<TabsTrigger value="binary">{t('add_system.binary')}</TabsTrigger>
|
||||||
</DialogDescription>
|
</TabsList>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<form onSubmit={handleSubmit as any}>
|
{/* Docker */}
|
||||||
<div className="grid gap-3 mt-1 mb-4">
|
<TabsContent value="docker">
|
||||||
<div className="grid grid-cols-4 items-center gap-4">
|
<DialogDescription className={'mb-4'}>
|
||||||
<Label htmlFor="name" className="text-right">
|
{t('add_system.dialog_des_1')}{' '}
|
||||||
Name
|
<code className="bg-muted px-1 rounded-sm">docker-compose.yml</code>{' '}
|
||||||
</Label>
|
{t('add_system.dialog_des_2')}
|
||||||
<Input id="name" name="name" className="col-span-3" required />
|
</DialogDescription>
|
||||||
|
</TabsContent>
|
||||||
|
{/* Binary */}
|
||||||
|
<TabsContent value="binary">
|
||||||
|
<DialogDescription className={'mb-4'}>
|
||||||
|
{t('add_system.dialog_des_1')}{' '}
|
||||||
|
<code className="bg-muted px-1 rounded-sm">install command</code>{' '}
|
||||||
|
{t('add_system.dialog_des_2')}
|
||||||
|
</DialogDescription>
|
||||||
|
</TabsContent>
|
||||||
|
<form onSubmit={handleSubmit as any}>
|
||||||
|
<div className="grid gap-3 mt-1 mb-4">
|
||||||
|
<div className="grid grid-cols-4 items-center gap-4">
|
||||||
|
<Label htmlFor="name" className="text-right">
|
||||||
|
{t('add_system.name')}
|
||||||
|
</Label>
|
||||||
|
<Input id="name" name="name" className="col-span-3" required />
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-4 items-center gap-4">
|
||||||
|
<Label htmlFor="host" className="text-right">
|
||||||
|
{t('add_system.host_ip')}
|
||||||
|
</Label>
|
||||||
|
<Input id="host" name="host" className="col-span-3" required />
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-4 items-center gap-4">
|
||||||
|
<Label htmlFor="port" className="text-right">
|
||||||
|
{t('add_system.port')}
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
ref={port}
|
||||||
|
name="port"
|
||||||
|
id="port"
|
||||||
|
defaultValue="45876"
|
||||||
|
className="col-span-3"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-4 items-center gap-4 relative">
|
||||||
|
<Label htmlFor="pkey" className="text-right whitespace-pre">
|
||||||
|
{t('add_system.public_key')}
|
||||||
|
</Label>
|
||||||
|
<Input readOnly id="pkey" value={publicKey} className="col-span-3" required></Input>
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'h-6 w-24 bg-gradient-to-r from-transparent to-background to-65% absolute right-1 pointer-events-none'
|
||||||
|
}
|
||||||
|
></div>
|
||||||
|
<TooltipProvider delayDuration={100}>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant={'link'}
|
||||||
|
className="absolute right-0"
|
||||||
|
onClick={() => copyToClipboard(publicKey)}
|
||||||
|
>
|
||||||
|
<Copy className="h-4 w-4 " />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>{t('add_system.click_to_copy')}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-4 items-center gap-4">
|
{/* Docker */}
|
||||||
<Label htmlFor="host" className="text-right">
|
<TabsContent value="docker">
|
||||||
Host / IP
|
<DialogFooter className="flex justify-end gap-2 sm:w-[calc(100%+20px)] sm:-ml-[20px]">
|
||||||
</Label>
|
<Button
|
||||||
<Input id="host" name="host" className="col-span-3" required />
|
type="button"
|
||||||
</div>
|
variant={'ghost'}
|
||||||
<div className="grid grid-cols-4 items-center gap-4">
|
onClick={() => copyDockerCompose(port.current.value)}
|
||||||
<Label htmlFor="port" className="text-right">
|
>
|
||||||
Port
|
{t('copy')} docker compose
|
||||||
</Label>
|
</Button>
|
||||||
<Input
|
<Button>{t('add_system.add_system')}</Button>
|
||||||
ref={port}
|
</DialogFooter>
|
||||||
name="port"
|
</TabsContent>
|
||||||
id="port"
|
{/* Binary */}
|
||||||
defaultValue="45876"
|
<TabsContent value="binary">
|
||||||
className="col-span-3"
|
<DialogFooter className="flex justify-end gap-2 sm:w-[calc(100%+20px)] sm:-ml-[20px]">
|
||||||
required
|
<Button
|
||||||
/>
|
type="button"
|
||||||
</div>
|
variant={'ghost'}
|
||||||
<div className="grid grid-cols-4 items-center gap-4 relative">
|
onClick={() => copyInstallCommand(port.current.value)}
|
||||||
<Label htmlFor="pkey" className="text-right whitespace-pre">
|
>
|
||||||
Public Key
|
{t('copy')} linux {t('add_system.command')}
|
||||||
</Label>
|
</Button>
|
||||||
<Input readOnly id="pkey" value={publicKey} className="col-span-3" required></Input>
|
<Button>{t('add_system.add_system')}</Button>
|
||||||
<div
|
</DialogFooter>
|
||||||
className={
|
</TabsContent>
|
||||||
'h-6 w-24 bg-gradient-to-r from-transparent to-background to-65% absolute right-1 pointer-events-none'
|
</form>
|
||||||
}
|
</Tabs>
|
||||||
></div>
|
|
||||||
<TooltipProvider delayDuration={100}>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant={'link'}
|
|
||||||
className="absolute right-0"
|
|
||||||
onClick={() => copyToClipboard(publicKey)}
|
|
||||||
>
|
|
||||||
<Copy className="h-4 w-4 " />
|
|
||||||
</Button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>Click to copy</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<DialogFooter className="flex justify-end gap-2">
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant={'ghost'}
|
|
||||||
onClick={() => copyDockerCompose(port.current.value)}
|
|
||||||
>
|
|
||||||
Copy docker compose
|
|
||||||
</Button>
|
|
||||||
<Button>Add system</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</form>
|
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
|
@@ -17,6 +17,7 @@ import { Link } from '../router'
|
|||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||||
import { Checkbox } from '../ui/checkbox'
|
import { Checkbox } from '../ui/checkbox'
|
||||||
import { SystemAlert, SystemAlertGlobal } from './alerts-system'
|
import { SystemAlert, SystemAlertGlobal } from './alerts-system'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export default memo(function AlertsButton({ system }: { system: SystemRecord }) {
|
export default memo(function AlertsButton({ system }: { system: SystemRecord }) {
|
||||||
const alerts = useStore($alerts)
|
const alerts = useStore($alerts)
|
||||||
@@ -54,6 +55,8 @@ function TheContent({
|
|||||||
}: {
|
}: {
|
||||||
data: { system: SystemRecord; alerts: AlertRecord[]; systemAlerts: AlertRecord[] }
|
data: { system: SystemRecord; alerts: AlertRecord[]; systemAlerts: AlertRecord[] }
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const [overwriteExisting, setOverwriteExisting] = useState<boolean | 'indeterminate'>(false)
|
const [overwriteExisting, setOverwriteExisting] = useState<boolean | 'indeterminate'>(false)
|
||||||
const systems = $systems.get()
|
const systems = $systems.get()
|
||||||
|
|
||||||
@@ -69,13 +72,13 @@ function TheContent({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="text-xl">Alerts</DialogTitle>
|
<DialogTitle className="text-xl">{t('alerts.title')}</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
See{' '}
|
{t('alerts.subtitle_1')}{' '}
|
||||||
<Link href="/settings/notifications" className="link">
|
<Link href="/settings/notifications" className="link">
|
||||||
notification settings
|
{t('alerts.notification_settings')}
|
||||||
</Link>{' '}
|
</Link>{' '}
|
||||||
to configure how you receive alerts.
|
{t('alerts.subtitle_2')}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<Tabs defaultValue="system">
|
<Tabs defaultValue="system">
|
||||||
@@ -86,7 +89,7 @@ function TheContent({
|
|||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="global">
|
<TabsTrigger value="global">
|
||||||
<GlobeIcon className="mr-1.5 h-3.5 w-3.5" />
|
<GlobeIcon className="mr-1.5 h-3.5 w-3.5" />
|
||||||
All systems
|
{t('all_systems')}
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent value="system">
|
<TabsContent value="system">
|
||||||
@@ -107,7 +110,7 @@ function TheContent({
|
|||||||
checked={overwriteExisting}
|
checked={overwriteExisting}
|
||||||
onCheckedChange={setOverwriteExisting}
|
onCheckedChange={setOverwriteExisting}
|
||||||
/>
|
/>
|
||||||
Overwrite existing alerts
|
{t('alerts.overwrite_existing_alerts')}
|
||||||
</label>
|
</label>
|
||||||
<div className="grid gap-3">
|
<div className="grid gap-3">
|
||||||
{data.map((d) => (
|
{data.map((d) => (
|
||||||
|
@@ -6,6 +6,7 @@ import { lazy, Suspense, useRef, useState } from 'react'
|
|||||||
import { toast } from '../ui/use-toast'
|
import { toast } from '../ui/use-toast'
|
||||||
import { RecordOptions } from 'pocketbase'
|
import { RecordOptions } from 'pocketbase'
|
||||||
import { newQueue, Queue } from '@henrygd/queue'
|
import { newQueue, Queue } from '@henrygd/queue'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
interface AlertData {
|
interface AlertData {
|
||||||
checked?: boolean
|
checked?: boolean
|
||||||
@@ -157,6 +158,8 @@ export function SystemAlertGlobal({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function AlertContent({ data }: { data: AlertData }) {
|
function AlertContent({ data }: { data: AlertData }) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const { key } = data
|
const { key } = data
|
||||||
|
|
||||||
const hasSliders = !('single' in data.alert)
|
const hasSliders = !('single' in data.alert)
|
||||||
@@ -185,10 +188,10 @@ function AlertContent({ data }: { data: AlertData }) {
|
|||||||
>
|
>
|
||||||
<div className="grid gap-1 select-none">
|
<div className="grid gap-1 select-none">
|
||||||
<p className="font-semibold flex gap-3 items-center capitalize">
|
<p className="font-semibold flex gap-3 items-center capitalize">
|
||||||
<Icon className="h-4 w-4 opacity-85" /> {data.alert.name}
|
<Icon className="h-4 w-4 opacity-85" /> {t(data.alert.name)}
|
||||||
</p>
|
</p>
|
||||||
{!showSliders && (
|
{!showSliders && (
|
||||||
<span className="block text-sm text-muted-foreground">{data.alert.desc}</span>
|
<span className="block text-sm text-muted-foreground">{t(data.alert.desc)}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Switch
|
<Switch
|
||||||
@@ -205,7 +208,7 @@ function AlertContent({ data }: { data: AlertData }) {
|
|||||||
<Suspense fallback={<div className="h-10" />}>
|
<Suspense fallback={<div className="h-10" />}>
|
||||||
<div>
|
<div>
|
||||||
<p id={`v${key}`} className="text-sm block h-8">
|
<p id={`v${key}`} className="text-sm block h-8">
|
||||||
Average exceeds{' '}
|
{t('alerts.average_exceeds')}{' '}
|
||||||
<strong className="text-foreground">
|
<strong className="text-foreground">
|
||||||
{value}
|
{value}
|
||||||
{data.alert.unit}
|
{data.alert.unit}
|
||||||
@@ -224,8 +227,7 @@ function AlertContent({ data }: { data: AlertData }) {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p id={`t${key}`} className="text-sm block h-8">
|
<p id={`t${key}`} className="text-sm block h-8">
|
||||||
For <strong className="text-foreground">{min}</strong> minute
|
{t('alerts.for')} <strong className="text-foreground">{min}</strong> {min > 1 ? t('alerts.minutes') : t('alerts.minute')}
|
||||||
{min > 1 && 's'}
|
|
||||||
</p>
|
</p>
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<Slider
|
<Slider
|
||||||
|
@@ -25,8 +25,11 @@ import { useStore } from '@nanostores/react'
|
|||||||
import { $systems } from '@/lib/stores'
|
import { $systems } from '@/lib/stores'
|
||||||
import { isAdmin } from '@/lib/utils'
|
import { isAdmin } from '@/lib/utils'
|
||||||
import { navigate } from './router'
|
import { navigate } from './router'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export default function CommandPalette() {
|
export default function CommandPalette() {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const systems = useStore($systems)
|
const systems = useStore($systems)
|
||||||
|
|
||||||
@@ -44,7 +47,7 @@ export default function CommandPalette() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<CommandDialog open={open} onOpenChange={setOpen}>
|
<CommandDialog open={open} onOpenChange={setOpen}>
|
||||||
<CommandInput placeholder="Search for systems or settings..." />
|
<CommandInput placeholder={t('command.search')} />
|
||||||
<CommandList>
|
<CommandList>
|
||||||
<CommandEmpty>No results found.</CommandEmpty>
|
<CommandEmpty>No results found.</CommandEmpty>
|
||||||
{systems.length > 0 && (
|
{systems.length > 0 && (
|
||||||
@@ -67,7 +70,7 @@ export default function CommandPalette() {
|
|||||||
<CommandSeparator className="mb-1.5" />
|
<CommandSeparator className="mb-1.5" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<CommandGroup heading="Pages / Settings">
|
<CommandGroup heading={t('command.pages_settings')}>
|
||||||
<CommandItem
|
<CommandItem
|
||||||
keywords={['home']}
|
keywords={['home']}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
@@ -76,8 +79,8 @@ export default function CommandPalette() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<LayoutDashboard className="mr-2 h-4 w-4" />
|
<LayoutDashboard className="mr-2 h-4 w-4" />
|
||||||
<span>Dashboard</span>
|
<span>{t('command.dashboard')}</span>
|
||||||
<CommandShortcut>Page</CommandShortcut>
|
<CommandShortcut>{t('command.page')}</CommandShortcut>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem
|
<CommandItem
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
@@ -86,8 +89,8 @@ export default function CommandPalette() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SettingsIcon className="mr-2 h-4 w-4" />
|
<SettingsIcon className="mr-2 h-4 w-4" />
|
||||||
<span>Settings</span>
|
<span>{t('settings.settings')}</span>
|
||||||
<CommandShortcut>Settings</CommandShortcut>
|
<CommandShortcut>{t('settings.settings')}</CommandShortcut>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem
|
<CommandItem
|
||||||
keywords={['alerts']}
|
keywords={['alerts']}
|
||||||
@@ -97,8 +100,8 @@ export default function CommandPalette() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MailIcon className="mr-2 h-4 w-4" />
|
<MailIcon className="mr-2 h-4 w-4" />
|
||||||
<span>Notification settings</span>
|
<span>{t('settings.notifications.title')}</span>
|
||||||
<CommandShortcut>Settings</CommandShortcut>
|
<CommandShortcut>{t('settings.settings')}</CommandShortcut>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem
|
<CommandItem
|
||||||
keywords={['github']}
|
keywords={['github']}
|
||||||
@@ -107,14 +110,14 @@ export default function CommandPalette() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Github className="mr-2 h-4 w-4" />
|
<Github className="mr-2 h-4 w-4" />
|
||||||
<span>Documentation</span>
|
<span>{t('command.documentation')}</span>
|
||||||
<CommandShortcut>GitHub</CommandShortcut>
|
<CommandShortcut>GitHub</CommandShortcut>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
{isAdmin() && (
|
{isAdmin() && (
|
||||||
<>
|
<>
|
||||||
<CommandSeparator className="mb-1.5" />
|
<CommandSeparator className="mb-1.5" />
|
||||||
<CommandGroup heading="Admin">
|
<CommandGroup heading={t("command.admin")}>
|
||||||
<CommandItem
|
<CommandItem
|
||||||
keywords={['pocketbase']}
|
keywords={['pocketbase']}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
@@ -123,8 +126,8 @@ export default function CommandPalette() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<UsersIcon className="mr-2 h-4 w-4" />
|
<UsersIcon className="mr-2 h-4 w-4" />
|
||||||
<span>Users</span>
|
<span>{t('user_dm.users')}</span>
|
||||||
<CommandShortcut>Admin</CommandShortcut>
|
<CommandShortcut>{t("command.admin")}</CommandShortcut>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem
|
<CommandItem
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
@@ -133,8 +136,8 @@ export default function CommandPalette() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<LogsIcon className="mr-2 h-4 w-4" />
|
<LogsIcon className="mr-2 h-4 w-4" />
|
||||||
<span>Logs</span>
|
<span>{t('user_dm.logs')}</span>
|
||||||
<CommandShortcut>Admin</CommandShortcut>
|
<CommandShortcut>{t("command.admin")}</CommandShortcut>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem
|
<CommandItem
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
@@ -143,8 +146,8 @@ export default function CommandPalette() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DatabaseBackupIcon className="mr-2 h-4 w-4" />
|
<DatabaseBackupIcon className="mr-2 h-4 w-4" />
|
||||||
<span>Backups</span>
|
<span>{t('user_dm.backups')}</span>
|
||||||
<CommandShortcut>Admin</CommandShortcut>
|
<CommandShortcut>{t("command.admin")}</CommandShortcut>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem
|
<CommandItem
|
||||||
keywords={['oauth', 'oicd']}
|
keywords={['oauth', 'oicd']}
|
||||||
@@ -154,8 +157,8 @@ export default function CommandPalette() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<LockKeyholeIcon className="mr-2 h-4 w-4" />
|
<LockKeyholeIcon className="mr-2 h-4 w-4" />
|
||||||
<span>Auth Providers</span>
|
<span>{t('user_dm.auth_providers')}</span>
|
||||||
<CommandShortcut>Admin</CommandShortcut>
|
<CommandShortcut>{t("command.admin")}</CommandShortcut>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem
|
<CommandItem
|
||||||
keywords={['email']}
|
keywords={['email']}
|
||||||
@@ -165,8 +168,8 @@ export default function CommandPalette() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MailIcon className="mr-2 h-4 w-4" />
|
<MailIcon className="mr-2 h-4 w-4" />
|
||||||
<span>SMTP settings</span>
|
<span>{t('command.SMTP_settings')}</span>
|
||||||
<CommandShortcut>Admin</CommandShortcut>
|
<CommandShortcut>{t("command.admin")}</CommandShortcut>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
</>
|
</>
|
||||||
|
43
beszel/site/src/components/lang-toggle.tsx
Normal file
43
beszel/site/src/components/lang-toggle.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { useEffect } from 'react'
|
||||||
|
import { LanguagesIcon } from 'lucide-react'
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from '@/components/ui/dropdown-menu'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import languages from '../lib/languages.json'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
export function LangToggle() {
|
||||||
|
const { i18n } = useTranslation()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.documentElement.lang = i18n.language
|
||||||
|
}, [i18n.language])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant={'ghost'} size="icon" className="hidden 450:flex">
|
||||||
|
<LanguagesIcon className="absolute h-[1.2rem] w-[1.2rem]" />
|
||||||
|
<span className="sr-only">Language</span>
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent>
|
||||||
|
{languages.map(({ lang, label }) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
key={lang}
|
||||||
|
className={cn('pl-4', lang === i18n.language ? 'font-bold' : '')}
|
||||||
|
onClick={() => i18n.changeLanguage(lang)}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
)
|
||||||
|
}
|
@@ -8,8 +8,10 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@/components/ui/dropdown-menu'
|
} from '@/components/ui/dropdown-menu'
|
||||||
import { useTheme } from '@/components/theme-provider'
|
import { useTheme } from '@/components/theme-provider'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export function ModeToggle() {
|
export function ModeToggle() {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { setTheme } = useTheme()
|
const { setTheme } = useTheme()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -18,21 +20,21 @@ export function ModeToggle() {
|
|||||||
<Button variant={'ghost'} size="icon">
|
<Button variant={'ghost'} size="icon">
|
||||||
<SunIcon className="h-[1.2rem] w-[1.2rem] dark:opacity-0" />
|
<SunIcon 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">{t('themes.toggle_theme')}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
<DropdownMenuItem onClick={() => setTheme('light')}>
|
<DropdownMenuItem onClick={() => setTheme('light')}>
|
||||||
<SunIcon className="mr-2.5 h-4 w-4" />
|
<SunIcon className="mr-2.5 h-4 w-4" />
|
||||||
Light
|
{t('themes.light')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={() => setTheme('dark')}>
|
<DropdownMenuItem onClick={() => setTheme('dark')}>
|
||||||
<MoonStarIcon className="mr-2.5 h-4 w-4" />
|
<MoonStarIcon className="mr-2.5 h-4 w-4" />
|
||||||
Dark
|
{t('themes.dark')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={() => setTheme('system')}>
|
<DropdownMenuItem onClick={() => setTheme('system')}>
|
||||||
<LaptopIcon className="mr-2.5 h-4 w-4" />
|
<LaptopIcon className="mr-2.5 h-4 w-4" />
|
||||||
System
|
{t('themes.system')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
@@ -9,10 +9,15 @@ import { AlertRecord, SystemRecord } from '@/types'
|
|||||||
import { Input } from '../ui/input'
|
import { Input } from '../ui/input'
|
||||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
|
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
|
||||||
import { Link } from '../router'
|
import { Link } from '../router'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
const SystemsTable = lazy(() => import('../systems-table/systems-table'))
|
const SystemsTable = lazy(() => import('../systems-table/systems-table'))
|
||||||
|
|
||||||
|
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const hubVersion = useStore($hubVersion)
|
const hubVersion = useStore($hubVersion)
|
||||||
const [filter, setFilter] = useState<string>()
|
const [filter, setFilter] = useState<string>()
|
||||||
const alerts = useStore($alerts)
|
const alerts = useStore($alerts)
|
||||||
@@ -58,7 +63,7 @@ export default function () {
|
|||||||
<Card className="mb-4">
|
<Card className="mb-4">
|
||||||
<CardHeader className="pb-4 px-2 sm:px-6 max-sm:pt-5 max-sm:pb-1">
|
<CardHeader className="pb-4 px-2 sm:px-6 max-sm:pt-5 max-sm:pb-1">
|
||||||
<div className="px-2 sm:px-1">
|
<div className="px-2 sm:px-1">
|
||||||
<CardTitle>Active Alerts</CardTitle>
|
<CardTitle>{t('home.active_alerts')}</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="max-sm:p-2">
|
<CardContent className="max-sm:p-2">
|
||||||
@@ -73,11 +78,14 @@ export default function () {
|
|||||||
>
|
>
|
||||||
<info.icon className="h-4 w-4" />
|
<info.icon className="h-4 w-4" />
|
||||||
<AlertTitle>
|
<AlertTitle>
|
||||||
{alert.sysname} {info.name}
|
{alert.sysname} {t(info.name)}
|
||||||
</AlertTitle>
|
</AlertTitle>
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
Exceeds {alert.value}
|
{t('home.active_des', {
|
||||||
{info.unit} average in last {alert.min} min
|
value: alert.value,
|
||||||
|
unit: info.unit,
|
||||||
|
minutes: alert.min,
|
||||||
|
})}
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
<Link
|
<Link
|
||||||
href={`/system/${encodeURIComponent(alert.sysname!)}`}
|
href={`/system/${encodeURIComponent(alert.sysname!)}`}
|
||||||
@@ -96,17 +104,17 @@ export default function () {
|
|||||||
<CardHeader className="pb-5 px-2 sm:px-6 max-sm:pt-5 max-sm:pb-1">
|
<CardHeader className="pb-5 px-2 sm:px-6 max-sm:pt-5 max-sm:pb-1">
|
||||||
<div className="grid md:flex gap-3 w-full items-end">
|
<div className="grid md:flex gap-3 w-full items-end">
|
||||||
<div className="px-2 sm:px-1">
|
<div className="px-2 sm:px-1">
|
||||||
<CardTitle className="mb-2.5">All Systems</CardTitle>
|
<CardTitle className="mb-2.5">{t('all_systems')}</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Updated in real time. Press{' '}
|
{t('home.subtitle_1')}{' '}
|
||||||
<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">
|
<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">
|
||||||
<span className="text-xs">⌘</span>K
|
<span className="text-xs">{isMac ? '⌘' : 'Ctrl'}</span>K
|
||||||
</kbd>{' '}
|
</kbd>{' '}
|
||||||
to open the command palette.
|
{t('home.subtitle_2')}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Filter..."
|
placeholder={t('filter')}
|
||||||
onChange={(e) => setFilter(e.target.value)}
|
onChange={(e) => setFilter(e.target.value)}
|
||||||
className="w-full md:w-56 lg:w-80 ml-auto px-4"
|
className="w-full md:w-56 lg:w-80 ml-auto px-4"
|
||||||
/>
|
/>
|
||||||
|
@@ -10,8 +10,11 @@ import { useState } from 'react'
|
|||||||
import { Textarea } from '@/components/ui/textarea'
|
import { Textarea } from '@/components/ui/textarea'
|
||||||
import { toast } from '@/components/ui/use-toast'
|
import { toast } from '@/components/ui/use-toast'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export default function ConfigYaml() {
|
export default function ConfigYaml() {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const [configContent, setConfigContent] = useState<string>('')
|
const [configContent, setConfigContent] = useState<string>('')
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
@@ -40,30 +43,27 @@ export default function ConfigYaml() {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-medium mb-2">YAML Configuration</h3>
|
<h3 className="text-xl font-medium mb-2">{t('settings.yaml_config.title')}</h3>
|
||||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||||
Export your current systems configuration.
|
{t('settings.yaml_config.subtitle')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Separator className="my-4" />
|
<Separator className="my-4" />
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<p className="text-sm text-muted-foreground leading-relaxed my-1">
|
<p className="text-sm text-muted-foreground leading-relaxed my-1">
|
||||||
Systems may be managed in a{' '}
|
{t('settings.yaml_config.des_1')}{' '}
|
||||||
<code className="bg-muted rounded-sm px-1 text-primary">config.yml</code> file inside
|
<code className="bg-muted rounded-sm px-1 text-primary">config.yml</code> {t('settings.yaml_config.des_2')}
|
||||||
your data directory.
|
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||||
On each restart, systems in the database will be updated to match the systems defined in
|
{t('settings.yaml_config.des_3')}
|
||||||
the file.
|
|
||||||
</p>
|
</p>
|
||||||
<Alert className="my-4 border-destructive text-destructive w-auto table md:pr-6">
|
<Alert className="my-4 border-destructive text-destructive w-auto table md:pr-6">
|
||||||
<AlertCircleIcon className="h-4 w-4 stroke-destructive" />
|
<AlertCircleIcon className="h-4 w-4 stroke-destructive" />
|
||||||
<AlertTitle>Caution - potential data loss</AlertTitle>
|
<AlertTitle>{t('settings.yaml_config.alert.title')}</AlertTitle>
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
<p>
|
<p>
|
||||||
Existing systems not defined in <code>config.yml</code> will be deleted. Please make
|
{t('settings.yaml_config.alert.des_1')} <code>config.yml</code> {t('settings.yaml_config.alert.des_2')}
|
||||||
regular backups.
|
|
||||||
</p>
|
</p>
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
@@ -86,7 +86,7 @@ export default function ConfigYaml() {
|
|||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
<ButtonIcon className={clsx('h-4 w-4 mr-0.5', isLoading && 'animate-spin')} />
|
<ButtonIcon className={clsx('h-4 w-4 mr-0.5', isLoading && 'animate-spin')} />
|
||||||
Export configuration
|
{t('settings.export_configuration')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@@ -9,13 +9,21 @@ import {
|
|||||||
} from '@/components/ui/select'
|
} from '@/components/ui/select'
|
||||||
import { chartTimeData } from '@/lib/utils'
|
import { chartTimeData } from '@/lib/utils'
|
||||||
import { Separator } from '@/components/ui/separator'
|
import { Separator } from '@/components/ui/separator'
|
||||||
import { LoaderCircleIcon, SaveIcon } from 'lucide-react'
|
import { LanguagesIcon, LoaderCircleIcon, SaveIcon } from 'lucide-react'
|
||||||
import { UserSettings } from '@/types'
|
import { UserSettings } from '@/types'
|
||||||
import { saveSettings } from './layout'
|
import { saveSettings } from './layout'
|
||||||
import { useState } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
// import { Input } from '@/components/ui/input'
|
// import { Input } from '@/components/ui/input'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import languages from '../../../lib/languages.json'
|
||||||
|
|
||||||
export default function SettingsProfilePage({ userSettings }: { userSettings: UserSettings }) {
|
export default function SettingsProfilePage({ userSettings }: { userSettings: UserSettings }) {
|
||||||
|
const { t, i18n } = useTranslation()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.documentElement.lang = i18n.language
|
||||||
|
}, [i18n.language])
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||||
@@ -30,46 +38,55 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-medium mb-2">General</h3>
|
<h3 className="text-xl font-medium mb-2">{t('settings.general.title')}</h3>
|
||||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||||
Change general application options.
|
{t('settings.general.subtitle')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Separator className="my-4" />
|
<Separator className="my-4" />
|
||||||
<form onSubmit={handleSubmit} className="space-y-5">
|
<form onSubmit={handleSubmit} className="space-y-5">
|
||||||
{/* <Separator />
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h3 className="mb-1 text-lg font-medium">Language</h3>
|
<h3 className="mb-1 text-lg font-medium flex items-center gap-2">
|
||||||
|
<LanguagesIcon className="h-4 w-4" />
|
||||||
|
{t('settings.general.language.title')}
|
||||||
|
</h3>
|
||||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||||
Internationalization will be added in a future release. Please see the{' '}
|
{t('settings.general.language.subtitle_1')}{' '}
|
||||||
<a href="#" className="link" target="_blank">
|
<a href="https://crowdin.com/project/beszel" className="link" target="_blank">
|
||||||
discussion on GitHub
|
Crowdin
|
||||||
</a>{' '}
|
</a>{' '}
|
||||||
for more details.
|
{t('settings.general.language.subtitle_2')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Label className="block" htmlFor="lang">
|
<Label className="block" htmlFor="lang">
|
||||||
Preferred language
|
{t('settings.general.language.preferred_language')}
|
||||||
</Label>
|
</Label>
|
||||||
<Select defaultValue="en">
|
<Select value={i18n.language} onValueChange={(lang: string) => i18n.changeLanguage(lang)}>
|
||||||
<SelectTrigger id="lang">
|
<SelectTrigger id="lang">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="en">English</SelectItem>
|
{languages.map((lang) => (
|
||||||
|
<SelectItem key={lang.lang} value={lang.lang}>
|
||||||
|
{lang.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div> */}
|
</div>
|
||||||
|
<Separator />
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h3 className="mb-1 text-lg font-medium">Chart options</h3>
|
<h3 className="mb-1 text-lg font-medium">
|
||||||
|
{t('settings.general.chart_options.title')}
|
||||||
|
</h3>
|
||||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||||
Adjust display options for charts.
|
{t('settings.general.chart_options.subtitle')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Label className="block" htmlFor="chartTime">
|
<Label className="block" htmlFor="chartTime">
|
||||||
Default time period
|
{t('settings.general.chart_options.default_time_period')}
|
||||||
</Label>
|
</Label>
|
||||||
<Select
|
<Select
|
||||||
name="chartTime"
|
name="chartTime"
|
||||||
@@ -88,7 +105,7 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<p className="text-[0.8rem] text-muted-foreground">
|
<p className="text-[0.8rem] text-muted-foreground">
|
||||||
Sets the default time range for charts when a system is viewed.
|
{t('settings.general.chart_options.default_time_period_des')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Separator />
|
<Separator />
|
||||||
@@ -102,7 +119,7 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
|
|||||||
) : (
|
) : (
|
||||||
<SaveIcon className="h-4 w-4" />
|
<SaveIcon className="h-4 w-4" />
|
||||||
)}
|
)}
|
||||||
Save settings
|
{t('settings.save_settings')}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -13,27 +13,7 @@ import General from './general.tsx'
|
|||||||
import Notifications from './notifications.tsx'
|
import Notifications from './notifications.tsx'
|
||||||
import ConfigYaml from './config-yaml.tsx'
|
import ConfigYaml from './config-yaml.tsx'
|
||||||
import { isAdmin } from '@/lib/utils.ts'
|
import { isAdmin } from '@/lib/utils.ts'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
const sidebarNavItems = [
|
|
||||||
{
|
|
||||||
title: 'General',
|
|
||||||
href: '/settings/general',
|
|
||||||
icon: SettingsIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Notifications',
|
|
||||||
href: '/settings/notifications',
|
|
||||||
icon: BellIcon,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
if (isAdmin()) {
|
|
||||||
sidebarNavItems.push({
|
|
||||||
title: 'YAML Config',
|
|
||||||
href: '/settings/config',
|
|
||||||
icon: FileSlidersIcon,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function saveSettings(newSettings: Partial<UserSettings>) {
|
export async function saveSettings(newSettings: Partial<UserSettings>) {
|
||||||
try {
|
try {
|
||||||
@@ -64,6 +44,29 @@ export async function saveSettings(newSettings: Partial<UserSettings>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function SettingsLayout() {
|
export default function SettingsLayout() {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const sidebarNavItems = [
|
||||||
|
{
|
||||||
|
title: t('settings.general.title'),
|
||||||
|
href: '/settings/general',
|
||||||
|
icon: SettingsIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('settings.notifications.title'),
|
||||||
|
href: '/settings/notifications',
|
||||||
|
icon: BellIcon,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
if (isAdmin()) {
|
||||||
|
sidebarNavItems.push({
|
||||||
|
title: t('settings.yaml_config.short_title'),
|
||||||
|
href: '/settings/config',
|
||||||
|
icon: FileSlidersIcon,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const page = useStore($router)
|
const page = useStore($router)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -77,8 +80,8 @@ export default function SettingsLayout() {
|
|||||||
return (
|
return (
|
||||||
<Card className="pt-5 px-4 pb-8 sm:pt-6 sm:px-7">
|
<Card className="pt-5 px-4 pb-8 sm:pt-6 sm:px-7">
|
||||||
<CardHeader className="p-0">
|
<CardHeader className="p-0">
|
||||||
<CardTitle className="mb-1">Settings</CardTitle>
|
<CardTitle className="mb-1">{t('settings.settings')}</CardTitle>
|
||||||
<CardDescription>Manage display and notification preferences.</CardDescription>
|
<CardDescription>{t('settings.subtitle')}</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="p-0">
|
<CardContent className="p-0">
|
||||||
<Separator className="hidden md:block my-5" />
|
<Separator className="hidden md:block my-5" />
|
||||||
|
@@ -12,6 +12,7 @@ import { UserSettings } from '@/types'
|
|||||||
import { saveSettings } from './layout'
|
import { saveSettings } from './layout'
|
||||||
import * as v from 'valibot'
|
import * as v from 'valibot'
|
||||||
import { isAdmin } from '@/lib/utils'
|
import { isAdmin } from '@/lib/utils'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
interface ShoutrrrUrlCardProps {
|
interface ShoutrrrUrlCardProps {
|
||||||
url: string
|
url: string
|
||||||
@@ -25,6 +26,8 @@ const NotificationSchema = v.object({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const SettingsNotificationsPage = ({ userSettings }: { userSettings: UserSettings }) => {
|
const SettingsNotificationsPage = ({ userSettings }: { userSettings: UserSettings }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const [webhooks, setWebhooks] = useState(userSettings.webhooks ?? [])
|
const [webhooks, setWebhooks] = useState(userSettings.webhooks ?? [])
|
||||||
const [emails, setEmails] = useState<string[]>(userSettings.emails ?? [])
|
const [emails, setEmails] = useState<string[]>(userSettings.emails ?? [])
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
@@ -69,51 +72,55 @@ const SettingsNotificationsPage = ({ userSettings }: { userSettings: UserSetting
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-medium mb-2">Notifications</h3>
|
<h3 className="text-xl font-medium mb-2">{t('settings.notifications.title')}</h3>
|
||||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||||
Configure how you receive alert notifications.
|
{t('settings.notifications.subtitle_1')}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-muted-foreground mt-1.5 leading-relaxed">
|
<p className="text-sm text-muted-foreground mt-1.5 leading-relaxed">
|
||||||
Looking instead for where to create alerts? Click the bell{' '}
|
{t('settings.notifications.subtitle_2')}{' '}
|
||||||
<BellIcon className="inline h-4 w-4" /> icons in the systems table.
|
<BellIcon className="inline h-4 w-4" /> {t('settings.notifications.subtitle_3')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Separator className="my-4" />
|
<Separator className="my-4" />
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h3 className="mb-1 text-lg font-medium">Email notifications</h3>
|
<h3 className="mb-1 text-lg font-medium">
|
||||||
|
{t('settings.notifications.email.title')}
|
||||||
|
</h3>
|
||||||
{isAdmin() && (
|
{isAdmin() && (
|
||||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||||
Please{' '}
|
{t('settings.notifications.email.please')}{' '}
|
||||||
<a href="/_/#/settings/mail" className="link" target="_blank">
|
<a href="/_/#/settings/mail" className="link" target="_blank">
|
||||||
configure an SMTP server
|
{t('settings.notifications.email.configure_an_SMTP_server')}
|
||||||
</a>{' '}
|
</a>{' '}
|
||||||
to ensure alerts are delivered.{' '}
|
{t('settings.notifications.email.to_ensure_alerts_are_delivered')}{' '}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Label className="block" htmlFor="email">
|
<Label className="block" htmlFor="email">
|
||||||
To email(s)
|
{t('settings.notifications.email.to_email_s')}
|
||||||
</Label>
|
</Label>
|
||||||
<InputTags
|
<InputTags
|
||||||
value={emails}
|
value={emails}
|
||||||
onChange={setEmails}
|
onChange={setEmails}
|
||||||
placeholder="Enter email address..."
|
placeholder={t('settings.notifications.email.enter_email_address')}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
type="email"
|
type="email"
|
||||||
id="email"
|
id="email"
|
||||||
/>
|
/>
|
||||||
<p className="text-[0.8rem] text-muted-foreground">
|
<p className="text-[0.8rem] text-muted-foreground">
|
||||||
Save address using enter key or comma. Leave blank to disable email notifications.
|
{t('settings.notifications.email.des')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Separator />
|
<Separator />
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="mb-1 text-lg font-medium">Webhook / Push notifications</h3>
|
<h3 className="mb-1 text-lg font-medium">
|
||||||
|
{t('settings.notifications.webhook_push.title')}
|
||||||
|
</h3>
|
||||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||||
Beszel uses{' '}
|
{t('settings.notifications.webhook_push.des_1')}{' '}
|
||||||
<a
|
<a
|
||||||
href="https://containrrr.dev/shoutrrr/services/overview/"
|
href="https://containrrr.dev/shoutrrr/services/overview/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -121,7 +128,7 @@ const SettingsNotificationsPage = ({ userSettings }: { userSettings: UserSetting
|
|||||||
>
|
>
|
||||||
Shoutrrr
|
Shoutrrr
|
||||||
</a>{' '}
|
</a>{' '}
|
||||||
to integrate with popular notification services.
|
{t('settings.notifications.webhook_push.des_2')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{webhooks.length > 0 && (
|
{webhooks.length > 0 && (
|
||||||
@@ -146,7 +153,7 @@ const SettingsNotificationsPage = ({ userSettings }: { userSettings: UserSetting
|
|||||||
onClick={addWebhook}
|
onClick={addWebhook}
|
||||||
>
|
>
|
||||||
<PlusIcon className="h-4 w-4 -ml-0.5" />
|
<PlusIcon className="h-4 w-4 -ml-0.5" />
|
||||||
Add URL
|
{t('settings.notifications.webhook_push.add_url')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Separator />
|
<Separator />
|
||||||
@@ -161,7 +168,7 @@ const SettingsNotificationsPage = ({ userSettings }: { userSettings: UserSetting
|
|||||||
) : (
|
) : (
|
||||||
<SaveIcon className="h-4 w-4" />
|
<SaveIcon className="h-4 w-4" />
|
||||||
)}
|
)}
|
||||||
Save settings
|
{t('settings.save_settings')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -21,6 +21,7 @@ import { ChartAverage, ChartMax, Rows, TuxIcon } from '../ui/icons'
|
|||||||
import { useIntersectionObserver } from '@/lib/use-intersection-observer'
|
import { useIntersectionObserver } from '@/lib/use-intersection-observer'
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'
|
||||||
import { timeTicks } from 'd3-time'
|
import { timeTicks } from 'd3-time'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
const AreaChartDefault = lazy(() => import('../charts/area-chart'))
|
const AreaChartDefault = lazy(() => import('../charts/area-chart'))
|
||||||
const ContainerChart = lazy(() => import('../charts/container-chart'))
|
const ContainerChart = lazy(() => import('../charts/container-chart'))
|
||||||
@@ -96,6 +97,8 @@ async function getStats<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function SystemDetail({ name }: { name: string }) {
|
export default function SystemDetail({ name }: { name: string }) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const systems = useStore($systems)
|
const systems = useStore($systems)
|
||||||
const chartTime = useStore($chartTime)
|
const chartTime = useStore($chartTime)
|
||||||
/** Max CPU toggle value */
|
/** Max CPU toggle value */
|
||||||
@@ -349,7 +352,7 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
aria-label="Toggle grid"
|
aria-label={t('monitor.toggle_grid')}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="hidden lg:flex p-0 text-primary"
|
className="hidden lg:flex p-0 text-primary"
|
||||||
@@ -362,7 +365,7 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>Toggle grid</TooltipContent>
|
<TooltipContent>{t('monitor.toggle_grid')}</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</div>
|
</div>
|
||||||
@@ -373,10 +376,8 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
<div className="grid lg:grid-cols-2 gap-4">
|
<div className="grid lg:grid-cols-2 gap-4">
|
||||||
<ChartCard
|
<ChartCard
|
||||||
grid={grid}
|
grid={grid}
|
||||||
title="Total CPU Usage"
|
title={t('monitor.total_cpu_usage')}
|
||||||
description={`${
|
description={`${cpuMaxStore[0] && isLongerChart ? t('monitor.max_1_min') : t('monitor.average') } ${t('monitor.cpu_des')}`}
|
||||||
cpuMaxStore[0] && isLongerChart ? 'Max 1 min ' : 'Average'
|
|
||||||
} system-wide CPU utilization`}
|
|
||||||
cornerEl={isLongerChart ? <SelectAvgMax store={cpuMaxStore} /> : null}
|
cornerEl={isLongerChart ? <SelectAvgMax store={cpuMaxStore} /> : null}
|
||||||
>
|
>
|
||||||
<AreaChartDefault
|
<AreaChartDefault
|
||||||
@@ -390,8 +391,8 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
{containerFilterBar && (
|
{containerFilterBar && (
|
||||||
<ChartCard
|
<ChartCard
|
||||||
grid={grid}
|
grid={grid}
|
||||||
title="Docker CPU Usage"
|
title={t('monitor.docker_cpu_usage')}
|
||||||
description="Average CPU utilization of containers"
|
description={t('monitor.docker_cpu_des')}
|
||||||
cornerEl={containerFilterBar}
|
cornerEl={containerFilterBar}
|
||||||
>
|
>
|
||||||
<ContainerChart chartData={chartData} dataKey="c" chartName="cpu" />
|
<ContainerChart chartData={chartData} dataKey="c" chartName="cpu" />
|
||||||
@@ -400,8 +401,8 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
|
|
||||||
<ChartCard
|
<ChartCard
|
||||||
grid={grid}
|
grid={grid}
|
||||||
title="Total Memory Usage"
|
title={t('monitor.total_memory_usage')}
|
||||||
description="Precise utilization at the recorded time"
|
description={t('monitor.memory_des')}
|
||||||
>
|
>
|
||||||
<MemChart chartData={chartData} />
|
<MemChart chartData={chartData} />
|
||||||
</ChartCard>
|
</ChartCard>
|
||||||
@@ -409,15 +410,15 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
{containerFilterBar && (
|
{containerFilterBar && (
|
||||||
<ChartCard
|
<ChartCard
|
||||||
grid={grid}
|
grid={grid}
|
||||||
title="Docker Memory Usage"
|
title={t('monitor.docker_memory_usage')}
|
||||||
description="Memory usage of docker containers"
|
description={t('monitor.docker_memory_des')}
|
||||||
cornerEl={containerFilterBar}
|
cornerEl={containerFilterBar}
|
||||||
>
|
>
|
||||||
<ContainerChart chartData={chartData} chartName="mem" dataKey="m" unit=" MB" />
|
<ContainerChart chartData={chartData} chartName="mem" dataKey="m" unit=" MB" />
|
||||||
</ChartCard>
|
</ChartCard>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ChartCard grid={grid} title="Disk Space" description="Usage of root partition">
|
<ChartCard grid={grid} title={t('monitor.disk_space')} description={t('monitor.disk_des')}>
|
||||||
<DiskChart
|
<DiskChart
|
||||||
chartData={chartData}
|
chartData={chartData}
|
||||||
dataKey="stats.du"
|
dataKey="stats.du"
|
||||||
@@ -427,8 +428,8 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
|
|
||||||
<ChartCard
|
<ChartCard
|
||||||
grid={grid}
|
grid={grid}
|
||||||
title="Disk I/O"
|
title={t('monitor.disk_io')}
|
||||||
description="Throughput of root filesystem"
|
description={t('monitor.disk_io_des')}
|
||||||
cornerEl={isLongerChart ? <SelectAvgMax store={diskIoMaxStore} /> : null}
|
cornerEl={isLongerChart ? <SelectAvgMax store={diskIoMaxStore} /> : null}
|
||||||
>
|
>
|
||||||
<AreaChartDefault
|
<AreaChartDefault
|
||||||
@@ -440,9 +441,9 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
|
|
||||||
<ChartCard
|
<ChartCard
|
||||||
grid={grid}
|
grid={grid}
|
||||||
title="Bandwidth"
|
title={t('monitor.bandwidth')}
|
||||||
cornerEl={isLongerChart ? <SelectAvgMax store={bandwidthMaxStore} /> : null}
|
cornerEl={isLongerChart ? <SelectAvgMax store={bandwidthMaxStore} /> : null}
|
||||||
description="Network traffic of public interfaces"
|
description={t('monitor.bandwidth_des')}
|
||||||
>
|
>
|
||||||
<AreaChartDefault
|
<AreaChartDefault
|
||||||
chartData={chartData}
|
chartData={chartData}
|
||||||
@@ -459,8 +460,8 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<ChartCard
|
<ChartCard
|
||||||
title="Docker Network I/O"
|
title={t('monitor.docker_network_io')}
|
||||||
description="Includes traffic between internal services"
|
description={t('monitor.docker_network_io_des')}
|
||||||
cornerEl={containerFilterBar}
|
cornerEl={containerFilterBar}
|
||||||
>
|
>
|
||||||
{/* @ts-ignore */}
|
{/* @ts-ignore */}
|
||||||
@@ -470,13 +471,13 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{(systemStats.at(-1)?.stats.su ?? 0) > 0 && (
|
{(systemStats.at(-1)?.stats.su ?? 0) > 0 && (
|
||||||
<ChartCard grid={grid} title="Swap Usage" description="Swap space used by the system">
|
<ChartCard grid={grid} title={t('monitor.swap_usage')} description={t('monitor.swap_des')}>
|
||||||
<SwapChart chartData={chartData} />
|
<SwapChart chartData={chartData} />
|
||||||
</ChartCard>
|
</ChartCard>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{systemStats.at(-1)?.stats.t && (
|
{systemStats.at(-1)?.stats.t && (
|
||||||
<ChartCard grid={grid} title="Temperature" description="Temperatures of system sensors">
|
<ChartCard grid={grid} title={t('monitor.temperature')} description={t('monitor.temperature_des')}>
|
||||||
<TemperatureChart chartData={chartData} />
|
<TemperatureChart chartData={chartData} />
|
||||||
</ChartCard>
|
</ChartCard>
|
||||||
)}
|
)}
|
||||||
@@ -490,8 +491,8 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
<div key={extraFsName} className="contents">
|
<div key={extraFsName} className="contents">
|
||||||
<ChartCard
|
<ChartCard
|
||||||
grid={grid}
|
grid={grid}
|
||||||
title={`${extraFsName} Usage`}
|
title={`${extraFsName} ${t('monitor.usage')}`}
|
||||||
description={`Disk usage of ${extraFsName}`}
|
description={`${t('monitor.disk_usage_of')} ${extraFsName}`}
|
||||||
>
|
>
|
||||||
<DiskChart
|
<DiskChart
|
||||||
chartData={chartData}
|
chartData={chartData}
|
||||||
@@ -502,7 +503,7 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
<ChartCard
|
<ChartCard
|
||||||
grid={grid}
|
grid={grid}
|
||||||
title={`${extraFsName} I/O`}
|
title={`${extraFsName} I/O`}
|
||||||
description={`Throughput of ${extraFsName}`}
|
description={`${t('monitor.throughput_of')} ${extraFsName}`}
|
||||||
cornerEl={isLongerChart ? <SelectAvgMax store={diskIoMaxStore} /> : null}
|
cornerEl={isLongerChart ? <SelectAvgMax store={diskIoMaxStore} /> : null}
|
||||||
>
|
>
|
||||||
<AreaChartDefault
|
<AreaChartDefault
|
||||||
@@ -525,6 +526,8 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ContainerFilterBar() {
|
function ContainerFilterBar() {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const containerFilter = useStore($containerFilter)
|
const containerFilter = useStore($containerFilter)
|
||||||
|
|
||||||
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
@@ -534,7 +537,7 @@ function ContainerFilterBar() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Filter..."
|
placeholder={t('filter')}
|
||||||
className="pl-4 pr-8"
|
className="pl-4 pr-8"
|
||||||
value={containerFilter}
|
value={containerFilter}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
@@ -560,6 +563,8 @@ function SelectAvgMax({
|
|||||||
}: {
|
}: {
|
||||||
store: [boolean, React.Dispatch<React.SetStateAction<boolean>>]
|
store: [boolean, React.Dispatch<React.SetStateAction<boolean>>]
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const [max, setMax] = store
|
const [max, setMax] = store
|
||||||
const Icon = max ? ChartMax : ChartAverage
|
const Icon = max ? ChartMax : ChartAverage
|
||||||
|
|
||||||
@@ -571,10 +576,10 @@ function SelectAvgMax({
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem key="avg" value="avg">
|
<SelectItem key="avg" value="avg">
|
||||||
Average
|
{t('monitor.average')}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
<SelectItem key="max" value="max">
|
<SelectItem key="max" value="max">
|
||||||
Max 1 min
|
{t('monitor.max_1_min')}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
@@ -63,6 +63,7 @@ import { cn, copyToClipboard, decimalString, isReadOnlyUser } from '@/lib/utils'
|
|||||||
import AlertsButton from '../alerts/alert-button'
|
import AlertsButton from '../alerts/alert-button'
|
||||||
import { navigate } from '../router'
|
import { navigate } from '../router'
|
||||||
import { EthernetIcon } from '../ui/icons'
|
import { EthernetIcon } from '../ui/icons'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
function CellFormatter(info: CellContext<SystemRecord, unknown>) {
|
function CellFormatter(info: CellContext<SystemRecord, unknown>) {
|
||||||
const val = info.getValue() as number
|
const val = info.getValue() as number
|
||||||
@@ -102,6 +103,8 @@ function sortableHeader(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function SystemsTable({ filter }: { filter?: string }) {
|
export default function SystemsTable({ filter }: { filter?: string }) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
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>([])
|
||||||
@@ -145,32 +148,32 @@ export default function SystemsTable({ filter }: { filter?: string }) {
|
|||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
header: ({ column }) => sortableHeader(column, 'System', ServerIcon),
|
header: ({ column }) => sortableHeader(column, t('systems_table.system'), ServerIcon),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'info.cpu',
|
accessorKey: 'info.cpu',
|
||||||
invertSorting: true,
|
invertSorting: true,
|
||||||
cell: CellFormatter,
|
cell: CellFormatter,
|
||||||
header: ({ column }) => sortableHeader(column, 'CPU', CpuIcon),
|
header: ({ column }) => sortableHeader(column, t('systems_table.cpu'), CpuIcon),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'info.mp',
|
accessorKey: 'info.mp',
|
||||||
invertSorting: true,
|
invertSorting: true,
|
||||||
cell: CellFormatter,
|
cell: CellFormatter,
|
||||||
header: ({ column }) => sortableHeader(column, 'Memory', MemoryStickIcon),
|
header: ({ column }) => sortableHeader(column, t('systems_table.memory'), MemoryStickIcon),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'info.dp',
|
accessorKey: 'info.dp',
|
||||||
invertSorting: true,
|
invertSorting: true,
|
||||||
cell: CellFormatter,
|
cell: CellFormatter,
|
||||||
header: ({ column }) => sortableHeader(column, 'Disk', HardDriveIcon),
|
header: ({ column }) => sortableHeader(column, t('systems_table.disk'), HardDriveIcon),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorFn: (originalRow) => originalRow.info.b || 0,
|
accessorFn: (originalRow) => originalRow.info.b || 0,
|
||||||
id: 'n',
|
id: 'n',
|
||||||
invertSorting: true,
|
invertSorting: true,
|
||||||
size: 115,
|
size: 115,
|
||||||
header: ({ column }) => sortableHeader(column, 'Net', EthernetIcon),
|
header: ({ column }) => sortableHeader(column, t('systems_table.net'), EthernetIcon),
|
||||||
cell: (info) => {
|
cell: (info) => {
|
||||||
const val = info.getValue() as number
|
const val = info.getValue() as number
|
||||||
return (
|
return (
|
||||||
@@ -184,7 +187,7 @@ export default function SystemsTable({ filter }: { filter?: string }) {
|
|||||||
accessorKey: 'info.v',
|
accessorKey: 'info.v',
|
||||||
invertSorting: true,
|
invertSorting: true,
|
||||||
size: 50,
|
size: 50,
|
||||||
header: ({ column }) => sortableHeader(column, 'Agent', WifiIcon, true),
|
header: ({ column }) => sortableHeader(column, t('systems_table.agent'), WifiIcon, true),
|
||||||
cell: (info) => {
|
cell: (info) => {
|
||||||
const version = info.getValue() as string
|
const version = info.getValue() as string
|
||||||
if (!version || !hubVersion) {
|
if (!version || !hubVersion) {
|
||||||
@@ -217,7 +220,7 @@ export default function SystemsTable({ filter }: { filter?: string }) {
|
|||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="ghost" size={'icon'} data-nolink>
|
<Button variant="ghost" size={'icon'} data-nolink>
|
||||||
<span className="sr-only">Open menu</span>
|
<span className="sr-only">{t('systems_table.open_menu')}</span>
|
||||||
<MoreHorizontalIcon className="w-5" />
|
<MoreHorizontalIcon className="w-5" />
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
@@ -233,44 +236,42 @@ export default function SystemsTable({ filter }: { filter?: string }) {
|
|||||||
{status === 'paused' ? (
|
{status === 'paused' ? (
|
||||||
<>
|
<>
|
||||||
<PlayCircleIcon className="mr-2.5 h-4 w-4" />
|
<PlayCircleIcon className="mr-2.5 h-4 w-4" />
|
||||||
Resume
|
{t('systems_table.resume')}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<PauseCircleIcon className="mr-2.5 h-4 w-4" />
|
<PauseCircleIcon className="mr-2.5 h-4 w-4" />
|
||||||
Pause
|
{t('systems_table.pause')}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={() => copyToClipboard(host)}>
|
<DropdownMenuItem onClick={() => copyToClipboard(host)}>
|
||||||
<CopyIcon className="mr-2.5 h-4 w-4" />
|
<CopyIcon className="mr-2.5 h-4 w-4" />
|
||||||
Copy host
|
{t('systems_table.copy_host')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuSeparator className={cn(isReadOnlyUser() && 'hidden')} />
|
<DropdownMenuSeparator className={cn(isReadOnlyUser() && 'hidden')} />
|
||||||
<AlertDialogTrigger asChild>
|
<AlertDialogTrigger asChild>
|
||||||
<DropdownMenuItem className={cn(isReadOnlyUser() && 'hidden')}>
|
<DropdownMenuItem className={cn(isReadOnlyUser() && 'hidden')}>
|
||||||
<Trash2Icon className="mr-2.5 h-4 w-4" />
|
<Trash2Icon className="mr-2.5 h-4 w-4" />
|
||||||
Delete
|
{t('systems_table.delete')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</AlertDialogTrigger>
|
</AlertDialogTrigger>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>Are you sure you want to delete {name}?</AlertDialogTitle>
|
<AlertDialogTitle>{t('systems_table.delete_confirm', { name })}</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
This action cannot be undone. This will permanently delete all current records
|
{t('systems_table.delete_confirm_des_1')} <code className="bg-muted rounded-sm px-1">{name}</code> {t('systems_table.delete_confirm_des_2')}
|
||||||
for <code className="bg-muted rounded-sm px-1">{name}</code> from the
|
|
||||||
database.
|
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
<AlertDialogCancel>{t('cancel')}</AlertDialogCancel>
|
||||||
<AlertDialogAction
|
<AlertDialogAction
|
||||||
className={cn(buttonVariants({ variant: 'destructive' }))}
|
className={cn(buttonVariants({ variant: 'destructive' }))}
|
||||||
onClick={() => pb.collection('systems').delete(id)}
|
onClick={() => pb.collection('systems').delete(id)}
|
||||||
>
|
>
|
||||||
Continue
|
{t('continue')}
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
@@ -354,7 +355,7 @@ export default function SystemsTable({ filter }: { filter?: string }) {
|
|||||||
) : (
|
) : (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||||
No systems found
|
{t('systems_table.no_systems_found')}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
)}
|
)}
|
||||||
|
34
beszel/site/src/lib/i18n.ts
Normal file
34
beszel/site/src/lib/i18n.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import i18n from 'i18next';
|
||||||
|
import { initReactI18next } from 'react-i18next';
|
||||||
|
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||||
|
|
||||||
|
import en from '../locales/en/translation.json';
|
||||||
|
import es from '../locales/es/translation.json';
|
||||||
|
import fr from '../locales/fr/translation.json';
|
||||||
|
import de from '../locales/de/translation.json';
|
||||||
|
import ru from '../locales/ru/translation.json';
|
||||||
|
import zhHans from '../locales/zh-CN/translation.json';
|
||||||
|
import zhHant from '../locales/zh-HK/translation.json';
|
||||||
|
|
||||||
|
i18n
|
||||||
|
.use(LanguageDetector)
|
||||||
|
.use(initReactI18next)
|
||||||
|
.init({
|
||||||
|
resources: {
|
||||||
|
en: { translation: en },
|
||||||
|
es: { translation: es },
|
||||||
|
fr: { translation: fr },
|
||||||
|
de: { translation: de },
|
||||||
|
ru: { translation: ru },
|
||||||
|
// Chinese (Simplified)
|
||||||
|
'zh-CN': { translation: zhHans },
|
||||||
|
// Chinese (Traditional)
|
||||||
|
'zh-HK': { translation: zhHant },
|
||||||
|
},
|
||||||
|
fallbackLng: 'en',
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export { i18n };
|
30
beszel/site/src/lib/languages.json
Normal file
30
beszel/site/src/lib/languages.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"lang": "en",
|
||||||
|
"label": "English"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"lang": "es",
|
||||||
|
"label": "Español"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"lang": "fr",
|
||||||
|
"label": "Français"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"lang": "de",
|
||||||
|
"label": "Deutsch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"lang": "ru",
|
||||||
|
"label": "Русский"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"lang": "zh-CN",
|
||||||
|
"label": "简体中文"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"lang": "zh-HK",
|
||||||
|
"label": "繁體中文"
|
||||||
|
}
|
||||||
|
]
|
@@ -301,40 +301,40 @@ export const chartMargin = { top: 12 }
|
|||||||
|
|
||||||
export const alertInfo = {
|
export const alertInfo = {
|
||||||
Status: {
|
Status: {
|
||||||
name: 'Status',
|
name: 'alerts.info.status',
|
||||||
unit: '',
|
unit: '',
|
||||||
icon: ServerIcon,
|
icon: ServerIcon,
|
||||||
desc: 'Triggers when status switches between up and down.',
|
desc: 'alerts.info.status_des',
|
||||||
single: true,
|
single: true,
|
||||||
},
|
},
|
||||||
CPU: {
|
CPU: {
|
||||||
name: 'CPU usage',
|
name: 'alerts.info.cpu_usage',
|
||||||
unit: '%',
|
unit: '%',
|
||||||
icon: CpuIcon,
|
icon: CpuIcon,
|
||||||
desc: 'Triggers when CPU usage exceeds a threshold.',
|
desc: 'alerts.info.cpu_usage_des',
|
||||||
},
|
},
|
||||||
Memory: {
|
Memory: {
|
||||||
name: 'memory usage',
|
name: 'alerts.info.memory_usage',
|
||||||
unit: '%',
|
unit: '%',
|
||||||
icon: MemoryStickIcon,
|
icon: MemoryStickIcon,
|
||||||
desc: 'Triggers when memory usage exceeds a threshold.',
|
desc: 'alerts.info.memory_usage_des',
|
||||||
},
|
},
|
||||||
Disk: {
|
Disk: {
|
||||||
name: 'disk usage',
|
name: 'alerts.info.disk_usage',
|
||||||
unit: '%',
|
unit: '%',
|
||||||
icon: HardDriveIcon,
|
icon: HardDriveIcon,
|
||||||
desc: 'Triggers when usage of any disk exceeds a threshold.',
|
desc: 'alerts.info.disk_usage_des',
|
||||||
},
|
},
|
||||||
Bandwidth: {
|
Bandwidth: {
|
||||||
name: 'bandwidth',
|
name: 'alerts.info.bandwidth',
|
||||||
unit: ' MB/s',
|
unit: ' MB/s',
|
||||||
icon: EthernetIcon,
|
icon: EthernetIcon,
|
||||||
desc: 'Triggers when combined up/down exceeds a threshold.',
|
desc: 'alerts.info.bandwidth_des',
|
||||||
},
|
},
|
||||||
Temperature: {
|
Temperature: {
|
||||||
name: 'temperature',
|
name: 'alerts.info.temperature',
|
||||||
unit: '°C',
|
unit: '°C',
|
||||||
icon: ThermometerIcon,
|
icon: ThermometerIcon,
|
||||||
desc: 'Triggers when any sensor exceeds a threshold.',
|
desc: 'alerts.info.temperature_des',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
178
beszel/site/src/locales/de/translation.json
Normal file
178
beszel/site/src/locales/de/translation.json
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
{
|
||||||
|
"all_systems": "Alle Systeme",
|
||||||
|
"filter": "Filtern...",
|
||||||
|
"copy": "Kopieren",
|
||||||
|
"add": "Hinzufügen",
|
||||||
|
"system": "System",
|
||||||
|
"systems": "Systeme",
|
||||||
|
"cancel": "Abbrechen",
|
||||||
|
"continue": "Fortsetzen",
|
||||||
|
"home": {
|
||||||
|
"active_alerts": "Aktive Warnungen",
|
||||||
|
"active_des": "Überschreitet {{value}}{{unit}} Durchschnitt in den letzten {{minutes}} Minuten",
|
||||||
|
"subtitle_1": "In Echtzeit aktualisiert. Drücken Sie",
|
||||||
|
"subtitle_2": "um die Befehlsübersicht zu öffnen."
|
||||||
|
},
|
||||||
|
"systems_table": {
|
||||||
|
"system": "System",
|
||||||
|
"memory": "Speicher",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"disk": "Festplatte",
|
||||||
|
"net": "Netzwerk",
|
||||||
|
"agent": "Agent",
|
||||||
|
"no_systems_found": "Keine Systeme gefunden.",
|
||||||
|
"open_menu": "Menü öffnen",
|
||||||
|
"resume": "Fortsetzen",
|
||||||
|
"pause": "Pause",
|
||||||
|
"copy_host": "Host kopieren",
|
||||||
|
"delete": "Löschen",
|
||||||
|
"delete_confirm": "Sind Sie sicher, dass Sie {{name}} löschen möchten?",
|
||||||
|
"delete_confirm_des_1": "Diese Aktion kann nicht rückgängig gemacht werden. Dies wird alle aktuellen Aufzeichnungen für",
|
||||||
|
"delete_confirm_des_2": "dauerhaft aus der Datenbank löschen."
|
||||||
|
},
|
||||||
|
"alerts": {
|
||||||
|
"title": "Warnungen",
|
||||||
|
"subtitle_1": "Siehe",
|
||||||
|
"notification_settings": "Benachrichtigungseinstellungen",
|
||||||
|
"subtitle_2": "um zu konfigurieren, wie Sie Warnungen erhalten.",
|
||||||
|
"overwrite_existing_alerts": "Bestehende Warnungen überschreiben",
|
||||||
|
"info": {
|
||||||
|
"status": "Status",
|
||||||
|
"status_des": "Löst aus, wenn der Status zwischen oben und unten wechselt.",
|
||||||
|
"cpu_usage": "CPU-Auslastung",
|
||||||
|
"cpu_usage_des": "Löst aus, wenn die CPU-Auslastung einen Schwellenwert überschreitet.",
|
||||||
|
"memory_usage": "Speicherauslastung",
|
||||||
|
"memory_usage_des": "Löst aus, wenn die Speicherauslastung einen Schwellenwert überschreitet.",
|
||||||
|
"disk_usage": "Festplattennutzung",
|
||||||
|
"disk_usage_des": "Löst aus, wenn die Nutzung einer Festplatte einen Schwellenwert überschreitet.",
|
||||||
|
"bandwidth": "Bandbreite",
|
||||||
|
"bandwidth_des": "Löst aus, wenn die kombinierte Auf-/Abwärtsbandbreite einen Schwellenwert überschreitet.",
|
||||||
|
"temperature": "Temperatur",
|
||||||
|
"temperature_des": "Löst aus, wenn ein Sensor einen Schwellenwert überschreitet."
|
||||||
|
},
|
||||||
|
"average_exceeds": "Durchschnitt überschreitet",
|
||||||
|
"for": "Für",
|
||||||
|
"minute": "Minute",
|
||||||
|
"minutes": "Minuten"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"settings": "Einstellungen",
|
||||||
|
"subtitle": "Anzeige- und Benachrichtigungseinstellungen verwalten.",
|
||||||
|
"save_settings": "Einstellungen speichern",
|
||||||
|
"export_configuration": "Konfiguration exportieren",
|
||||||
|
"general": {
|
||||||
|
"title": "Allgemein",
|
||||||
|
"subtitle": "Allgemeine Anwendungsoptionen ändern.",
|
||||||
|
"language": {
|
||||||
|
"title": "Sprache",
|
||||||
|
"subtitle_1": "Möchten Sie uns helfen, unsere Übersetzungen noch besser zu machen? Schauen Sie sich",
|
||||||
|
"subtitle_2": "für weitere Details an.",
|
||||||
|
"preferred_language": "Bevorzugte Sprache"
|
||||||
|
},
|
||||||
|
"chart_options": {
|
||||||
|
"title": "Diagrammoptionen",
|
||||||
|
"subtitle": "Anzeigeoptionen für Diagramme anpassen.",
|
||||||
|
"default_time_period": "Standardzeitraum",
|
||||||
|
"default_time_period_des": "Legt den Standardzeitraum für Diagramme fest, wenn ein System angezeigt wird."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"title": "Benachrichtigungen",
|
||||||
|
"subtitle_1": "Konfigurieren Sie, wie Sie Warnbenachrichtigungen erhalten.",
|
||||||
|
"subtitle_2": "Suchen Sie stattdessen nach dem Ort, an dem Sie Warnungen erstellen können? Klicken Sie auf die Glocke",
|
||||||
|
"subtitle_3": "Symbole in der Systemtabelle.",
|
||||||
|
"email": {
|
||||||
|
"title": "E-Mail-Benachrichtigungen",
|
||||||
|
"please": "Bitte",
|
||||||
|
"configure_an_SMTP_server": "konfigurieren Sie einen SMTP-Server",
|
||||||
|
"to_ensure_alerts_are_delivered": "um sicherzustellen, dass Warnungen zugestellt werden.",
|
||||||
|
"to_email_s": "An E-Mail(s)",
|
||||||
|
"enter_email_address": "E-Mail-Adresse eingeben...",
|
||||||
|
"des": "Adresse mit der Eingabetaste oder dem Komma speichern. Leer lassen, um E-Mail-Benachrichtigungen zu deaktivieren."
|
||||||
|
},
|
||||||
|
"webhook_push": {
|
||||||
|
"title": "Webhook / Push-Benachrichtigungen",
|
||||||
|
"des_1": "Beszel verwendet",
|
||||||
|
"des_2": "um sich mit beliebten Benachrichtigungsdiensten zu integrieren.",
|
||||||
|
"add_url": "URL hinzufügen"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"yaml_config": {
|
||||||
|
"short_title": "YAML-Konfig",
|
||||||
|
"title": "YAML-Konfiguration",
|
||||||
|
"subtitle": "Aktuelle Systemkonfiguration exportieren.",
|
||||||
|
"des_1": "Systeme können in einer",
|
||||||
|
"des_2": "Datei im Datenverzeichnis verwaltet werden.",
|
||||||
|
"des_3": "Bei jedem Neustart werden die Systeme in der Datenbank aktualisiert, um den in der Datei definierten Systemen zu entsprechen.",
|
||||||
|
"alert": {
|
||||||
|
"title": "Achtung - potenzieller Datenverlust",
|
||||||
|
"des_1": "Bestehende Systeme, die nicht in",
|
||||||
|
"des_2": "definiert sind, werden gelöscht. Bitte machen Sie regelmäßige Backups."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": "Sprache"
|
||||||
|
},
|
||||||
|
"user_dm": {
|
||||||
|
"users": "Benutzer",
|
||||||
|
"logs": "Protokolle",
|
||||||
|
"backups": "Backups",
|
||||||
|
"auth_providers": "Authentifizierungsanbieter",
|
||||||
|
"log_out": "Abmelden"
|
||||||
|
},
|
||||||
|
"themes": {
|
||||||
|
"toggle_theme": "Thema wechseln",
|
||||||
|
"light": "Hell",
|
||||||
|
"dark": "Dunkel",
|
||||||
|
"system": "System"
|
||||||
|
},
|
||||||
|
"add_system": {
|
||||||
|
"add_new_system": "Neues System hinzufügen",
|
||||||
|
"binary": "Binär",
|
||||||
|
"dialog_des_1": "Der Agent muss auf dem System laufen, um eine Verbindung herzustellen. Kopieren Sie den",
|
||||||
|
"dialog_des_2": "für den Agenten unten.",
|
||||||
|
"name": "Name",
|
||||||
|
"host_ip": "Host / IP",
|
||||||
|
"port": "Port",
|
||||||
|
"public_key": "Öffentlicher Schlüssel",
|
||||||
|
"click_to_copy": "Zum Kopieren klicken",
|
||||||
|
"command": "Befehl",
|
||||||
|
"add_system": "System hinzufügen"
|
||||||
|
},
|
||||||
|
"command": {
|
||||||
|
"search": "Nach Systemen oder Einstellungen suchen...",
|
||||||
|
"pages_settings": "Seiten / Einstellungen",
|
||||||
|
"dashboard": "Dashboard",
|
||||||
|
"documentation": "Dokumentation",
|
||||||
|
"SMTP_settings": "SMTP-Einstellungen",
|
||||||
|
"page": "Seite",
|
||||||
|
"admin": "Admin"
|
||||||
|
},
|
||||||
|
"monitor": {
|
||||||
|
"toggle_grid": "Raster umschalten",
|
||||||
|
"average": "Durchschnitt",
|
||||||
|
"max_1_min": "Max 1 Min",
|
||||||
|
"total_cpu_usage": "Gesamte CPU-Auslastung",
|
||||||
|
"cpu_des": "Systemweite CPU-Auslastung",
|
||||||
|
"docker_cpu_usage": "Docker-CPU-Auslastung",
|
||||||
|
"docker_cpu_des": "Durchschnittliche CPU-Auslastung der Container",
|
||||||
|
"total_memory_usage": "Gesamte Speicherauslastung",
|
||||||
|
"memory_des": "Genaue Nutzung zum aufgezeichneten Zeitpunkt",
|
||||||
|
"docker_memory_usage": "Docker-Speicherauslastung",
|
||||||
|
"docker_memory_des": "Speichernutzung der Docker-Container",
|
||||||
|
"disk_space": "Festplattenspeicher",
|
||||||
|
"disk_des": "Nutzung der Root-Partition",
|
||||||
|
"disk_io": "Festplatten-I/O",
|
||||||
|
"disk_io_des": "Durchsatz des Root-Dateisystems",
|
||||||
|
"bandwidth": "Bandbreite",
|
||||||
|
"bandwidth_des": "Netzwerkverkehr der öffentlichen Schnittstellen",
|
||||||
|
"docker_network_io": "Docker-Netzwerk-I/O",
|
||||||
|
"docker_network_io_des": "Netzwerkverkehr der Docker-Container",
|
||||||
|
"swap_usage": "Swap-Nutzung",
|
||||||
|
"swap_des": "Vom System genutzter Swap-Speicher",
|
||||||
|
"temperature": "Temperatur",
|
||||||
|
"temperature_des": "Temperaturen der System-Sensoren",
|
||||||
|
"usage": "Nutzung",
|
||||||
|
"disk_usage_of": "Festplattennutzung von",
|
||||||
|
"throughput_of": "Durchsatz von"
|
||||||
|
}
|
||||||
|
}
|
178
beszel/site/src/locales/en/translation.json
Normal file
178
beszel/site/src/locales/en/translation.json
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
{
|
||||||
|
"all_systems": "All Systems",
|
||||||
|
"filter": "Filter...",
|
||||||
|
"copy": "Copy",
|
||||||
|
"add": "Add",
|
||||||
|
"system": "System",
|
||||||
|
"systems": "Systems",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"continue": "Continue",
|
||||||
|
"home": {
|
||||||
|
"active_alerts": "Active Alerts",
|
||||||
|
"active_des": "Exceeds {{value}}{{unit}} average in last {{minutes}} min",
|
||||||
|
"subtitle_1": "Updated in real time. Press",
|
||||||
|
"subtitle_2": "to open the command palette."
|
||||||
|
},
|
||||||
|
"systems_table": {
|
||||||
|
"system": "System",
|
||||||
|
"memory": "Memory",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"disk": "Disk",
|
||||||
|
"net": "Net",
|
||||||
|
"agent": "Agent",
|
||||||
|
"no_systems_found": "No systems found.",
|
||||||
|
"open_menu": "Open menu",
|
||||||
|
"resume": "Resume",
|
||||||
|
"pause": "Pause",
|
||||||
|
"copy_host": "Copy host",
|
||||||
|
"delete": "Delete",
|
||||||
|
"delete_confirm": "Are you sure you want to delete {{name}}?",
|
||||||
|
"delete_confirm_des_1": "This action cannot be undone. This will permanently delete all current records for",
|
||||||
|
"delete_confirm_des_2": "from the database."
|
||||||
|
},
|
||||||
|
"alerts": {
|
||||||
|
"title": "Alerts",
|
||||||
|
"subtitle_1": "See",
|
||||||
|
"notification_settings": "notification settings",
|
||||||
|
"subtitle_2": "to configure how you receive alerts.",
|
||||||
|
"overwrite_existing_alerts": "Overwrite existing alerts",
|
||||||
|
"info": {
|
||||||
|
"status": "Status",
|
||||||
|
"status_des": "Triggers when status switches between up and down.",
|
||||||
|
"cpu_usage": "CPU Usage",
|
||||||
|
"cpu_usage_des": "Triggers when CPU usage exceeds a threshold.",
|
||||||
|
"memory_usage": "Memory Usage",
|
||||||
|
"memory_usage_des": "Triggers when memory usage exceeds a threshold.",
|
||||||
|
"disk_usage": "Disk Usage",
|
||||||
|
"disk_usage_des": "Triggers when usage of any disk exceeds a threshold.",
|
||||||
|
"bandwidth": "Bandwidth",
|
||||||
|
"bandwidth_des": "Triggers when combined up/down exceeds a threshold.",
|
||||||
|
"temperature": "Temperature",
|
||||||
|
"temperature_des": "Triggers when any sensor exceeds a threshold."
|
||||||
|
},
|
||||||
|
"average_exceeds": "Average exceeds",
|
||||||
|
"for": "For",
|
||||||
|
"minute": "minute",
|
||||||
|
"minutes": "minutes"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"settings": "Settings",
|
||||||
|
"subtitle": "Manage display and notification preferences.",
|
||||||
|
"save_settings": "Save Settings",
|
||||||
|
"export_configuration": "Export configuration",
|
||||||
|
"general": {
|
||||||
|
"title": "General",
|
||||||
|
"subtitle": "Change general application options.",
|
||||||
|
"language": {
|
||||||
|
"title": "Language",
|
||||||
|
"subtitle_1": "Want to help us make our translations even better? Check out",
|
||||||
|
"subtitle_2": "for more details.",
|
||||||
|
"preferred_language": "Preferred Language"
|
||||||
|
},
|
||||||
|
"chart_options": {
|
||||||
|
"title": "Chart options",
|
||||||
|
"subtitle": "Adjust display options for charts.",
|
||||||
|
"default_time_period": "Default time period",
|
||||||
|
"default_time_period_des": "Sets the default time range for charts when a system is viewed."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"title": "Notifications",
|
||||||
|
"subtitle_1": "Configure how you receive alert notifications.",
|
||||||
|
"subtitle_2": "Looking instead for where to create alerts? Click the bell",
|
||||||
|
"subtitle_3": "icons in the systems table.",
|
||||||
|
"email": {
|
||||||
|
"title": "Email notifications",
|
||||||
|
"please": "Please",
|
||||||
|
"configure_an_SMTP_server": "configure an SMTP server",
|
||||||
|
"to_ensure_alerts_are_delivered": "to ensure alerts are delivered.",
|
||||||
|
"to_email_s": "To email(s)",
|
||||||
|
"enter_email_address": "Enter email address...",
|
||||||
|
"des": "Save address using enter key or comma. Leave blank to disable email notifications."
|
||||||
|
},
|
||||||
|
"webhook_push": {
|
||||||
|
"title": "Webhook / Push notifications",
|
||||||
|
"des_1": "Beszel uses",
|
||||||
|
"des_2": "to integrate with popular notification services.",
|
||||||
|
"add_url": "Add URL"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"yaml_config": {
|
||||||
|
"short_title": "YAML Config",
|
||||||
|
"title": "YAML Configuration",
|
||||||
|
"subtitle": "Export your current systems configuration.",
|
||||||
|
"des_1": "Systems may be managed in a",
|
||||||
|
"des_2": "file inside your data directory.",
|
||||||
|
"des_3": "On each restart, systems in the database will be updated to match the systems defined in the file.",
|
||||||
|
"alert": {
|
||||||
|
"title": "Caution - potential data loss",
|
||||||
|
"des_1": "Existing systems not defined in",
|
||||||
|
"des_2": "will be deleted. Please make regular backups."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": "Language"
|
||||||
|
},
|
||||||
|
"user_dm": {
|
||||||
|
"users": "Users",
|
||||||
|
"logs": "Logs",
|
||||||
|
"backups": "Backups",
|
||||||
|
"auth_providers": "Auth Providers",
|
||||||
|
"log_out": "Log Out"
|
||||||
|
},
|
||||||
|
"themes": {
|
||||||
|
"toggle_theme": "Toggle theme",
|
||||||
|
"light": "Light",
|
||||||
|
"dark": "Dark",
|
||||||
|
"system": "System"
|
||||||
|
},
|
||||||
|
"add_system": {
|
||||||
|
"add_new_system": "Add New System",
|
||||||
|
"binary": "Binary",
|
||||||
|
"dialog_des_1": "The agent must be running on the system to connect. Copy the",
|
||||||
|
"dialog_des_2": "for the agent below.",
|
||||||
|
"name": "Name",
|
||||||
|
"host_ip": "Host / IP",
|
||||||
|
"port": "Port",
|
||||||
|
"public_key": "Public Key",
|
||||||
|
"click_to_copy": "Click to copy",
|
||||||
|
"command": "command",
|
||||||
|
"add_system": "Add system"
|
||||||
|
},
|
||||||
|
"command": {
|
||||||
|
"search": "Search for systems or settings...",
|
||||||
|
"pages_settings": "Pages / Settings",
|
||||||
|
"dashboard": "Dashboard",
|
||||||
|
"documentation": "Documentation",
|
||||||
|
"SMTP_settings": "SMTP settings",
|
||||||
|
"page": "Page",
|
||||||
|
"admin": "Admin"
|
||||||
|
},
|
||||||
|
"monitor": {
|
||||||
|
"toggle_grid": "Toggle grid",
|
||||||
|
"average": "Average",
|
||||||
|
"max_1_min": "Max 1 min ",
|
||||||
|
"total_cpu_usage": "Total CPU Usage",
|
||||||
|
"cpu_des": "system-wide CPU utilization",
|
||||||
|
"docker_cpu_usage": "Docker CPU Usage",
|
||||||
|
"docker_cpu_des": "Average CPU utilization of containers",
|
||||||
|
"total_memory_usage": "Total Memory Usage",
|
||||||
|
"memory_des": "Precise utilization at the recorded time",
|
||||||
|
"docker_memory_usage": "Docker Memory Usage",
|
||||||
|
"docker_memory_des": "Memory usage of docker containers",
|
||||||
|
"disk_space": "Disk Space",
|
||||||
|
"disk_des": "Usage of root partition",
|
||||||
|
"disk_io": "Disk I/O",
|
||||||
|
"disk_io_des": "Throughput of root filesystem",
|
||||||
|
"bandwidth": "Bandwidth",
|
||||||
|
"bandwidth_des": "Network traffic of public interfaces",
|
||||||
|
"docker_network_io": "Docker Network I/O",
|
||||||
|
"docker_network_io_des": "Network traffic of docker containers",
|
||||||
|
"swap_usage": "Swap Usage",
|
||||||
|
"swap_des": "Swap space used by the system",
|
||||||
|
"temperature": "Temperature",
|
||||||
|
"temperature_des": "Temperatures of system sensors",
|
||||||
|
"usage": "Usage",
|
||||||
|
"disk_usage_of": "Disk usage of",
|
||||||
|
"throughput_of": "Throughput of"
|
||||||
|
}
|
||||||
|
}
|
178
beszel/site/src/locales/es/translation.json
Normal file
178
beszel/site/src/locales/es/translation.json
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
{
|
||||||
|
"all_systems": "Todos los sistemas",
|
||||||
|
"filter": "Filtrar...",
|
||||||
|
"copy": "Copiar",
|
||||||
|
"add": "Agregar",
|
||||||
|
"system": "Sistema",
|
||||||
|
"systems": "Sistemas",
|
||||||
|
"cancel": "Cancelar",
|
||||||
|
"continue": "Continuar",
|
||||||
|
"home": {
|
||||||
|
"active_alerts": "Alertas activas",
|
||||||
|
"active_des": "Excede el promedio de {{value}}{{unit}} en los últimos {{minutes}} minutos",
|
||||||
|
"subtitle_1": "Actualizado en tiempo real. Presione",
|
||||||
|
"subtitle_2": "para abrir la paleta de comandos."
|
||||||
|
},
|
||||||
|
"systems_table": {
|
||||||
|
"system": "Sistema",
|
||||||
|
"memory": "Memoria",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"disk": "Disco",
|
||||||
|
"net": "Red",
|
||||||
|
"agent": "Agente",
|
||||||
|
"no_systems_found": "No se encontraron sistemas.",
|
||||||
|
"open_menu": "Abrir menú",
|
||||||
|
"resume": "Reanudar",
|
||||||
|
"pause": "Pausar",
|
||||||
|
"copy_host": "Copiar host",
|
||||||
|
"delete": "Eliminar",
|
||||||
|
"delete_confirm": "¿Estás seguro de que quieres eliminar {{name}}?",
|
||||||
|
"delete_confirm_des_1": "Esta acción no se puede deshacer. Esto eliminará permanentemente todos los registros actuales para",
|
||||||
|
"delete_confirm_des_2": "de la base de datos."
|
||||||
|
},
|
||||||
|
"alerts": {
|
||||||
|
"title": "Alertas",
|
||||||
|
"subtitle_1": "Ver",
|
||||||
|
"notification_settings": "configuraciones de notificación",
|
||||||
|
"subtitle_2": "para configurar cómo recibes las alertas.",
|
||||||
|
"overwrite_existing_alerts": "Sobrescribir alertas existentes",
|
||||||
|
"info": {
|
||||||
|
"status": "Estado",
|
||||||
|
"status_des": "Se activa cuando el estado cambia entre arriba y abajo.",
|
||||||
|
"cpu_usage": "Uso de CPU",
|
||||||
|
"cpu_usage_des": "Se activa cuando el uso de la CPU supera un umbral.",
|
||||||
|
"memory_usage": "Uso de memoria",
|
||||||
|
"memory_usage_des": "Se activa cuando el uso de la memoria supera un umbral.",
|
||||||
|
"disk_usage": "Uso de disco",
|
||||||
|
"disk_usage_des": "Se activa cuando el uso de cualquier disco supera un umbral.",
|
||||||
|
"bandwidth": "Ancho de banda",
|
||||||
|
"bandwidth_des": "Se activa cuando el combinado arriba/abajo supera un umbral.",
|
||||||
|
"temperature": "Temperatura",
|
||||||
|
"temperature_des": "Se activa cuando cualquier sensor supera un umbral."
|
||||||
|
},
|
||||||
|
"average_exceeds": "Promedio excede",
|
||||||
|
"for": "Por",
|
||||||
|
"minute": "minuto",
|
||||||
|
"minutes": "minutos"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"settings": "Configuraciones",
|
||||||
|
"subtitle": "Administre las preferencias de visualización y notificación.",
|
||||||
|
"save_settings": "Guardar configuraciones",
|
||||||
|
"export_configuration": "Exportar configuración",
|
||||||
|
"general": {
|
||||||
|
"title": "General",
|
||||||
|
"subtitle": "Cambie las opciones generales de la aplicación.",
|
||||||
|
"language": {
|
||||||
|
"title": "Idioma",
|
||||||
|
"subtitle_1": "¿Quiere ayudarnos a mejorar nuestras traducciones? Consulte",
|
||||||
|
"subtitle_2": "para más detalles.",
|
||||||
|
"preferred_language": "Idioma preferido"
|
||||||
|
},
|
||||||
|
"chart_options": {
|
||||||
|
"title": "Opciones de gráficos",
|
||||||
|
"subtitle": "Ajuste las opciones de visualización para los gráficos.",
|
||||||
|
"default_time_period": "Período de tiempo predeterminado",
|
||||||
|
"default_time_period_des": "Establezca el rango de tiempo predeterminado para los gráficos cuando se visualiza un sistema."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"title": "Notificaciones",
|
||||||
|
"subtitle_1": "Configure cómo recibe las notificaciones de alerta.",
|
||||||
|
"subtitle_2": "¿Busca en su lugar dónde crear alertas? Haga clic en el icono de campana",
|
||||||
|
"subtitle_3": "en la tabla de sistemas.",
|
||||||
|
"email": {
|
||||||
|
"title": "Notificaciones por correo electrónico",
|
||||||
|
"please": "Por favor",
|
||||||
|
"configure_an_SMTP_server": "configure un servidor SMTP",
|
||||||
|
"to_ensure_alerts_are_delivered": "para asegurarse de que se entreguen las alertas.",
|
||||||
|
"to_email_s": "A correo electrónico(s)",
|
||||||
|
"enter_email_address": "Ingrese la dirección de correo electrónico...",
|
||||||
|
"des": "Guarde la dirección presionando Enter o usando una coma. Deje en blanco para desactivar las notificaciones por correo electrónico."
|
||||||
|
},
|
||||||
|
"webhook_push": {
|
||||||
|
"title": "Notificaciones Webhook/Push",
|
||||||
|
"des_1": "Beszel utiliza",
|
||||||
|
"des_2": "para integrarse con populares servicios de notificación.",
|
||||||
|
"add_url": "Agregar URL"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"yaml_config": {
|
||||||
|
"short_title": "Configuración YAML",
|
||||||
|
"title": "Configuración YAML",
|
||||||
|
"subtitle": "Exporta tu configuración actual de sistemas.",
|
||||||
|
"des_1": "Los sistemas pueden gestionarse en un",
|
||||||
|
"des_2": "archivo dentro de tu directorio de datos.",
|
||||||
|
"des_3": "En cada reinicio, los sistemas de la base de datos se actualizarán para coincidir con los sistemas definidos en el archivo.",
|
||||||
|
"alert": {
|
||||||
|
"title": "Advertencia - posible pérdida de datos",
|
||||||
|
"des_1": "Los sistemas existentes no definidos en",
|
||||||
|
"des_2": "serán eliminados. Por favor, haz copias de seguridad regulares."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": "Idioma"
|
||||||
|
},
|
||||||
|
"user_dm": {
|
||||||
|
"users": "Usuarios",
|
||||||
|
"logs": "Registros",
|
||||||
|
"backups": "Respaldos",
|
||||||
|
"auth_providers": "Proveedores de autenticación",
|
||||||
|
"log_out": "Cerrar sesión"
|
||||||
|
},
|
||||||
|
"themes": {
|
||||||
|
"toggle_theme": "Alternar tema",
|
||||||
|
"light": "Claro",
|
||||||
|
"dark": "Oscuro",
|
||||||
|
"system": "Sistema"
|
||||||
|
},
|
||||||
|
"add_system": {
|
||||||
|
"add_new_system": "Agregar nuevo sistema",
|
||||||
|
"binary": "Binario",
|
||||||
|
"dialog_des_1": "El agente debe estar ejecutándose en el sistema para conectarse. Copia el",
|
||||||
|
"dialog_des_2": "para el agente a continuación.",
|
||||||
|
"name": "Nombre",
|
||||||
|
"host_ip": "Host/IP",
|
||||||
|
"port": "Puerto",
|
||||||
|
"public_key": "Clave pública",
|
||||||
|
"click_to_copy": "Haz clic para copiar",
|
||||||
|
"command": "comando",
|
||||||
|
"add_system": "Agregar sistema"
|
||||||
|
},
|
||||||
|
"command": {
|
||||||
|
"search": "Buscar sistemas o configuraciones...",
|
||||||
|
"pages_settings": "Páginas/Configuraciones",
|
||||||
|
"dashboard": "Panel de control",
|
||||||
|
"documentation": "Documentación",
|
||||||
|
"SMTP_settings": "Configuraciones SMTP",
|
||||||
|
"page": "Página",
|
||||||
|
"admin": "Administrador"
|
||||||
|
},
|
||||||
|
"monitor": {
|
||||||
|
"toggle_grid": "Alternar cuadrícula",
|
||||||
|
"average": "Promedio",
|
||||||
|
"max_1_min": "Máx. 1 min ",
|
||||||
|
"total_cpu_usage": "Uso total de CPU",
|
||||||
|
"cpu_des": "Utilización de CPU de todo el sistema",
|
||||||
|
"docker_cpu_usage": "Uso de CPU de Docker",
|
||||||
|
"docker_cpu_des": "Uso promedio de CPU de los contenedores",
|
||||||
|
"total_memory_usage": "Uso total de memoria",
|
||||||
|
"memory_des": "Utilización precisa en el momento registrado",
|
||||||
|
"docker_memory_usage": "Uso de memoria de Docker",
|
||||||
|
"docker_memory_des": "Uso de memoria de los contenedores de Docker",
|
||||||
|
"disk_space": "Espacio en disco",
|
||||||
|
"disk_des": "Uso de la partición raíz",
|
||||||
|
"disk_io": "E/S de disco",
|
||||||
|
"disk_io_des": "Rendimiento de la raíz del sistema de archivos",
|
||||||
|
"bandwidth": "Ancho de banda",
|
||||||
|
"bandwidth_des": "Tráfico de red de interfaces públicas",
|
||||||
|
"docker_network_io": "E/S de red de Docker",
|
||||||
|
"docker_network_io_des": "Tráfico de red de los contenedores de Docker",
|
||||||
|
"swap_usage": "Uso de intercambio",
|
||||||
|
"swap_des": "Espacio de intercambio utilizado por el sistema",
|
||||||
|
"temperature": "Temperatura",
|
||||||
|
"temperature_des": "Temperaturas de los sensores del sistema",
|
||||||
|
"usage": "Uso",
|
||||||
|
"disk_usage_of": "Uso de disco de",
|
||||||
|
"throughput_of": "Rendimiento de"
|
||||||
|
}
|
||||||
|
}
|
178
beszel/site/src/locales/fr/translation.json
Normal file
178
beszel/site/src/locales/fr/translation.json
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
{
|
||||||
|
"all_systems": "Tous les systèmes",
|
||||||
|
"filter": "Filtrer...",
|
||||||
|
"copy": "Copier",
|
||||||
|
"add": "Ajouter",
|
||||||
|
"system": "Système",
|
||||||
|
"systems": "Systèmes",
|
||||||
|
"cancel": "Annuler",
|
||||||
|
"continue": "Continuer",
|
||||||
|
"home": {
|
||||||
|
"active_alerts": "Alertes actives",
|
||||||
|
"active_des": "Dépasse {{value}}{{unit}} en moyenne au cours des {{minutes}} dernières minutes",
|
||||||
|
"subtitle_1": "Mis à jour en temps réel. Appuyez sur",
|
||||||
|
"subtitle_2": "pour ouvrir la palette de commandes."
|
||||||
|
},
|
||||||
|
"systems_table": {
|
||||||
|
"system": "Système",
|
||||||
|
"memory": "Mémoire",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"disk": "Disque",
|
||||||
|
"net": "Réseau",
|
||||||
|
"agent": "Agent",
|
||||||
|
"no_systems_found": "Aucun système trouvé.",
|
||||||
|
"open_menu": "Ouvrir le menu",
|
||||||
|
"resume": "Reprendre",
|
||||||
|
"pause": "Pause",
|
||||||
|
"copy_host": "Copier l'hôte",
|
||||||
|
"delete": "Supprimer",
|
||||||
|
"delete_confirm": "Êtes-vous sûr de vouloir supprimer {{name}}?",
|
||||||
|
"delete_confirm_des_1": "Cette action est irréversible. Cela supprimera définitivement tous les enregistrements actuels de",
|
||||||
|
"delete_confirm_des_2": "de la base de données."
|
||||||
|
},
|
||||||
|
"alerts": {
|
||||||
|
"title": "Alertes",
|
||||||
|
"subtitle_1": "Voir",
|
||||||
|
"notification_settings": "paramètres de notification",
|
||||||
|
"subtitle_2": "pour configurer comment vous recevez les alertes.",
|
||||||
|
"overwrite_existing_alerts": "Écraser les alertes existantes",
|
||||||
|
"info": {
|
||||||
|
"status": "Statut",
|
||||||
|
"status_des": "Déclenchement lorsque le statut passe de haut en bas.",
|
||||||
|
"cpu_usage": "Utilisation du CPU",
|
||||||
|
"cpu_usage_des": "Déclenchement lorsque l'utilisation du CPU dépasse un seuil.",
|
||||||
|
"memory_usage": "Utilisation de la mémoire",
|
||||||
|
"memory_usage_des": "Déclenchement lorsque l'utilisation de la mémoire dépasse un seuil.",
|
||||||
|
"disk_usage": "Utilisation du disque",
|
||||||
|
"disk_usage_des": "Déclenchement lorsque l'utilisation de n'importe quel disque dépasse un seuil.",
|
||||||
|
"bandwidth": "Bande passante",
|
||||||
|
"bandwidth_des": "Déclenchement lorsque le total montant/descendant dépasse un seuil.",
|
||||||
|
"temperature": "Température",
|
||||||
|
"temperature_des": "Déclenchement lorsque n'importe quel capteur dépasse un seuil."
|
||||||
|
},
|
||||||
|
"average_exceeds": "La moyenne dépasse",
|
||||||
|
"for": "Pour",
|
||||||
|
"minute": "minute",
|
||||||
|
"minutes": "minutes"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"settings": "Paramètres",
|
||||||
|
"subtitle": "Gérer les préférences d'affichage et de notification.",
|
||||||
|
"save_settings": "Enregistrer les paramètres",
|
||||||
|
"export_configuration": "Exporter la configuration",
|
||||||
|
"general": {
|
||||||
|
"title": "Général",
|
||||||
|
"subtitle": "Modifier les options générales de l'application.",
|
||||||
|
"language": {
|
||||||
|
"title": "Langue",
|
||||||
|
"subtitle_1": "Vous voulez nous aider à améliorer nos traductions? Consultez",
|
||||||
|
"subtitle_2": "pour plus de détails.",
|
||||||
|
"preferred_language": "Langue préférée"
|
||||||
|
},
|
||||||
|
"chart_options": {
|
||||||
|
"title": "Options de graphique",
|
||||||
|
"subtitle": "Ajuster les options d'affichage pour les graphiques.",
|
||||||
|
"default_time_period": "Période de temps par défaut",
|
||||||
|
"default_time_period_des": "Définit la plage de temps par défaut pour les graphiques lorsqu'un système est consulté."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"title": "Notifications",
|
||||||
|
"subtitle_1": "Configurer comment vous recevez les notifications d'alerte.",
|
||||||
|
"subtitle_2": "Vous cherchez plutôt où créer des alertes? Cliquez sur la cloche",
|
||||||
|
"subtitle_3": "icônes dans le tableau des systèmes.",
|
||||||
|
"email": {
|
||||||
|
"title": "Notifications par email",
|
||||||
|
"please": "Veuillez",
|
||||||
|
"configure_an_SMTP_server": "configurer un serveur SMTP",
|
||||||
|
"to_ensure_alerts_are_delivered": "pour garantir la livraison des alertes.",
|
||||||
|
"to_email_s": "À email(s)",
|
||||||
|
"enter_email_address": "Entrez l'adresse email...",
|
||||||
|
"des": "Enregistrez l'adresse en utilisant la touche entrée ou la virgule. Laissez vide pour désactiver les notifications par email."
|
||||||
|
},
|
||||||
|
"webhook_push": {
|
||||||
|
"title": "Notifications Webhook / Push",
|
||||||
|
"des_1": "Beszel utilise",
|
||||||
|
"des_2": "pour s'intégrer avec des services de notification populaires.",
|
||||||
|
"add_url": "Ajouter une URL"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"yaml_config": {
|
||||||
|
"short_title": "Config YAML",
|
||||||
|
"title": "Configuration YAML",
|
||||||
|
"subtitle": "Exporter la configuration actuelle de vos systèmes.",
|
||||||
|
"des_1": "Les systèmes peuvent être gérés dans un fichier",
|
||||||
|
"des_2": "à l'intérieur de votre répertoire de données.",
|
||||||
|
"des_3": "À chaque redémarrage, les systèmes dans la base de données seront mis à jour pour correspondre aux systèmes définis dans le fichier.",
|
||||||
|
"alert": {
|
||||||
|
"title": "Attention - perte de données potentielle",
|
||||||
|
"des_1": "Les systèmes existants non définis dans",
|
||||||
|
"des_2": "seront supprimés. Veuillez faire des sauvegardes régulières."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": "Langue"
|
||||||
|
},
|
||||||
|
"user_dm": {
|
||||||
|
"users": "Utilisateurs",
|
||||||
|
"logs": "Journaux",
|
||||||
|
"backups": "Sauvegardes",
|
||||||
|
"auth_providers": "Fournisseurs d'authentification",
|
||||||
|
"log_out": "Déconnexion"
|
||||||
|
},
|
||||||
|
"themes": {
|
||||||
|
"toggle_theme": "Changer de thème",
|
||||||
|
"light": "Clair",
|
||||||
|
"dark": "Sombre",
|
||||||
|
"system": "Système"
|
||||||
|
},
|
||||||
|
"add_system": {
|
||||||
|
"add_new_system": "Ajouter un nouveau système",
|
||||||
|
"binary": "Binaire",
|
||||||
|
"dialog_des_1": "L'agent doit être en cours d'exécution sur le système pour se connecter. Copiez le",
|
||||||
|
"dialog_des_2": "pour l'agent ci-dessous.",
|
||||||
|
"name": "Nom",
|
||||||
|
"host_ip": "Hôte / IP",
|
||||||
|
"port": "Port",
|
||||||
|
"public_key": "Clé publique",
|
||||||
|
"click_to_copy": "Cliquez pour copier",
|
||||||
|
"command": "commande",
|
||||||
|
"add_system": "Ajouter un système"
|
||||||
|
},
|
||||||
|
"command": {
|
||||||
|
"search": "Rechercher des systèmes ou des paramètres...",
|
||||||
|
"pages_settings": "Pages / Paramètres",
|
||||||
|
"dashboard": "Tableau de bord",
|
||||||
|
"documentation": "Documentation",
|
||||||
|
"SMTP_settings": "Paramètres SMTP",
|
||||||
|
"page": "Page",
|
||||||
|
"admin": "Admin"
|
||||||
|
},
|
||||||
|
"monitor": {
|
||||||
|
"toggle_grid": "Changer de grille",
|
||||||
|
"average": "Moyenne",
|
||||||
|
"max_1_min": "Max 1 min",
|
||||||
|
"total_cpu_usage": "Utilisation totale du CPU",
|
||||||
|
"cpu_des": "utilisation du CPU à l'échelle du système",
|
||||||
|
"docker_cpu_usage": "Utilisation du CPU Docker",
|
||||||
|
"docker_cpu_des": "Utilisation moyenne du CPU des conteneurs",
|
||||||
|
"total_memory_usage": "Utilisation totale de la mémoire",
|
||||||
|
"memory_des": "Utilisation précise au moment enregistré",
|
||||||
|
"docker_memory_usage": "Utilisation de la mémoire Docker",
|
||||||
|
"docker_memory_des": "Utilisation de la mémoire des conteneurs Docker",
|
||||||
|
"disk_space": "Espace disque",
|
||||||
|
"disk_des": "Utilisation de la partition racine",
|
||||||
|
"disk_io": "E/S disque",
|
||||||
|
"disk_io_des": "Débit du système de fichiers racine",
|
||||||
|
"bandwidth": "Bande passante",
|
||||||
|
"bandwidth_des": "Trafic réseau des interfaces publiques",
|
||||||
|
"docker_network_io": "E/S réseau Docker",
|
||||||
|
"docker_network_io_des": "Trafic réseau des conteneurs Docker",
|
||||||
|
"swap_usage": "Utilisation du swap",
|
||||||
|
"swap_des": "Espace swap utilisé par le système",
|
||||||
|
"temperature": "Température",
|
||||||
|
"temperature_des": "Températures des capteurs du système",
|
||||||
|
"usage": "Utilisation",
|
||||||
|
"disk_usage_of": "Utilisation du disque de",
|
||||||
|
"throughput_of": "Débit de"
|
||||||
|
}
|
||||||
|
}
|
178
beszel/site/src/locales/ru/translation.json
Normal file
178
beszel/site/src/locales/ru/translation.json
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
{
|
||||||
|
"all_systems": "Все системы",
|
||||||
|
"filter": "Фильтр...",
|
||||||
|
"copy": "Копировать",
|
||||||
|
"add": "Добавить",
|
||||||
|
"system": "Система",
|
||||||
|
"systems": "Системы",
|
||||||
|
"cancel": "Отмена",
|
||||||
|
"continue": "Продолжить",
|
||||||
|
"home": {
|
||||||
|
"active_alerts": "Активные предупреждения",
|
||||||
|
"active_des": "Превышает {{value}}{{unit}} в среднем за последние {{minutes}} минут",
|
||||||
|
"subtitle_1": "Обновляется в реальном времени. Нажмите",
|
||||||
|
"subtitle_2": "чтобы открыть палитру команд."
|
||||||
|
},
|
||||||
|
"systems_table": {
|
||||||
|
"system": "Система",
|
||||||
|
"memory": "Память",
|
||||||
|
"cpu": "ЦП",
|
||||||
|
"disk": "Диск",
|
||||||
|
"net": "Сеть",
|
||||||
|
"agent": "Агент",
|
||||||
|
"no_systems_found": "Систем не найдено.",
|
||||||
|
"open_menu": "Открыть меню",
|
||||||
|
"resume": "Возобновить",
|
||||||
|
"pause": "Пауза",
|
||||||
|
"copy_host": "Копировать хост",
|
||||||
|
"delete": "Удалить",
|
||||||
|
"delete_confirm": "Вы уверены, что хотите удалить {{name}}?",
|
||||||
|
"delete_confirm_des_1": "Это действие нельзя отменить. Это навсегда удалит все текущие записи для",
|
||||||
|
"delete_confirm_des_2": "из базы данных."
|
||||||
|
},
|
||||||
|
"alerts": {
|
||||||
|
"title": "Предупреждения",
|
||||||
|
"subtitle_1": "См.",
|
||||||
|
"notification_settings": "настройки уведомлений",
|
||||||
|
"subtitle_2": "чтобы настроить, как вы получаете предупреждения.",
|
||||||
|
"overwrite_existing_alerts": "Перезаписать существующие предупреждения",
|
||||||
|
"info": {
|
||||||
|
"status": "Статус",
|
||||||
|
"status_des": "Срабатывает, когда статус переключается между вверх и вниз.",
|
||||||
|
"cpu_usage": "Использование ЦП",
|
||||||
|
"cpu_usage_des": "Срабатывает, когда использование ЦП превышает порог.",
|
||||||
|
"memory_usage": "Использование памяти",
|
||||||
|
"memory_usage_des": "Срабатывает, когда использование памяти превышает порог.",
|
||||||
|
"disk_usage": "Использование диска",
|
||||||
|
"disk_usage_des": "Срабатывает, когда использование любого диска превышает порог.",
|
||||||
|
"bandwidth": "Пропускная способность",
|
||||||
|
"bandwidth_des": "Срабатывает, когда суммарная загрузка/выгрузка превышает порог.",
|
||||||
|
"temperature": "Температура",
|
||||||
|
"temperature_des": "Срабатывает, когда любой датчик превышает порог."
|
||||||
|
},
|
||||||
|
"average_exceeds": "Среднее значение превышает",
|
||||||
|
"for": "За",
|
||||||
|
"minute": "минуту",
|
||||||
|
"minutes": "минут"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"settings": "Настройки",
|
||||||
|
"subtitle": "Управление предпочтениями отображения и уведомлений.",
|
||||||
|
"save_settings": "Сохранить настройки",
|
||||||
|
"export_configuration": "Экспорт конфигурации",
|
||||||
|
"general": {
|
||||||
|
"title": "Общие",
|
||||||
|
"subtitle": "Изменить общие параметры приложения.",
|
||||||
|
"language": {
|
||||||
|
"title": "Язык",
|
||||||
|
"subtitle_1": "Хотите помочь нам улучшить наши переводы? Ознакомьтесь с",
|
||||||
|
"subtitle_2": "для получения дополнительной информации.",
|
||||||
|
"preferred_language": "Предпочитаемый язык"
|
||||||
|
},
|
||||||
|
"chart_options": {
|
||||||
|
"title": "Параметры диаграммы",
|
||||||
|
"subtitle": "Настроить параметры отображения для диаграмм.",
|
||||||
|
"default_time_period": "Период по умолчанию",
|
||||||
|
"default_time_period_des": "Устанавливает диапазон времени по умолчанию для диаграмм при просмотре системы."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"title": "Уведомления",
|
||||||
|
"subtitle_1": "Настройте, как вы получаете уведомления о предупреждениях.",
|
||||||
|
"subtitle_2": "Ищете, где создать предупреждения? Нажмите на колокольчик",
|
||||||
|
"subtitle_3": "значки в таблице систем.",
|
||||||
|
"email": {
|
||||||
|
"title": "Уведомления по электронной почте",
|
||||||
|
"please": "Пожалуйста",
|
||||||
|
"configure_an_SMTP_server": "настройте SMTP-сервер",
|
||||||
|
"to_ensure_alerts_are_delivered": "чтобы гарантировать доставку предупреждений.",
|
||||||
|
"to_email_s": "На электронную почту(ы)",
|
||||||
|
"enter_email_address": "Введите адрес электронной почты...",
|
||||||
|
"des": "Сохраните адрес, используя клавишу ввода или запятую. Оставьте пустым, чтобы отключить уведомления по электронной почте."
|
||||||
|
},
|
||||||
|
"webhook_push": {
|
||||||
|
"title": "Webhook / Push уведомления",
|
||||||
|
"des_1": "Beszel использует",
|
||||||
|
"des_2": "для интеграции с популярными сервисами уведомлений.",
|
||||||
|
"add_url": "Добавить URL"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"yaml_config": {
|
||||||
|
"short_title": "YAML Конфиг",
|
||||||
|
"title": "YAML Конфигурация",
|
||||||
|
"subtitle": "Экспорт текущей конфигурации ваших систем.",
|
||||||
|
"des_1": "Системы могут управляться в",
|
||||||
|
"des_2": "файле в вашем каталоге данных.",
|
||||||
|
"des_3": "При каждом перезапуске системы в базе данных будут обновлены, чтобы соответствовать системам, определенным в файле.",
|
||||||
|
"alert": {
|
||||||
|
"title": "Внимание - возможная потеря данных",
|
||||||
|
"des_1": "Существующие системы, не определенные в",
|
||||||
|
"des_2": "будут удалены. Пожалуйста, делайте регулярные резервные копии."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": "Язык"
|
||||||
|
},
|
||||||
|
"user_dm": {
|
||||||
|
"users": "Пользователи",
|
||||||
|
"logs": "Журналы",
|
||||||
|
"backups": "Резервные копии",
|
||||||
|
"auth_providers": "Поставщики аутентификации",
|
||||||
|
"log_out": "Выйти"
|
||||||
|
},
|
||||||
|
"themes": {
|
||||||
|
"toggle_theme": "Переключить тему",
|
||||||
|
"light": "Светлая",
|
||||||
|
"dark": "Темная",
|
||||||
|
"system": "Система"
|
||||||
|
},
|
||||||
|
"add_system": {
|
||||||
|
"add_new_system": "Добавить новую систему",
|
||||||
|
"binary": "Бинарный",
|
||||||
|
"dialog_des_1": "Агент должен работать на системе для подключения. Скопируйте",
|
||||||
|
"dialog_des_2": "для агента ниже.",
|
||||||
|
"name": "Имя",
|
||||||
|
"host_ip": "Хост / IP",
|
||||||
|
"port": "Порт",
|
||||||
|
"public_key": "Публичный ключ",
|
||||||
|
"click_to_copy": "Нажмите, чтобы скопировать",
|
||||||
|
"command": "команда",
|
||||||
|
"add_system": "Добавить систему"
|
||||||
|
},
|
||||||
|
"command": {
|
||||||
|
"search": "Поиск систем или настроек...",
|
||||||
|
"pages_settings": "Страницы / Настройки",
|
||||||
|
"dashboard": "Панель управления",
|
||||||
|
"documentation": "Документация",
|
||||||
|
"SMTP_settings": "Настройки SMTP",
|
||||||
|
"page": "Страница",
|
||||||
|
"admin": "Админ"
|
||||||
|
},
|
||||||
|
"monitor": {
|
||||||
|
"toggle_grid": "Переключить сетку",
|
||||||
|
"average": "Среднее",
|
||||||
|
"max_1_min": "Макс 1 мин",
|
||||||
|
"total_cpu_usage": "Общее использование ЦП",
|
||||||
|
"cpu_des": "системное использование ЦП",
|
||||||
|
"docker_cpu_usage": "Использование ЦП Docker",
|
||||||
|
"docker_cpu_des": "Среднее использование ЦП контейнеров",
|
||||||
|
"total_memory_usage": "Общее использование памяти",
|
||||||
|
"memory_des": "Точное использование на момент записи",
|
||||||
|
"docker_memory_usage": "Использование памяти Docker",
|
||||||
|
"docker_memory_des": "Использование памяти контейнеров Docker",
|
||||||
|
"disk_space": "Место на диске",
|
||||||
|
"disk_des": "Использование корневого раздела",
|
||||||
|
"disk_io": "Дисковый ввод/вывод",
|
||||||
|
"disk_io_des": "Пропускная способность корневой файловой системы",
|
||||||
|
"bandwidth": "Пропускная способность",
|
||||||
|
"bandwidth_des": "Сетевой трафик публичных интерфейсов",
|
||||||
|
"docker_network_io": "Сетевой ввод/вывод Docker",
|
||||||
|
"docker_network_io_des": "Сетевой трафик контейнеров Docker",
|
||||||
|
"swap_usage": "Использование swap",
|
||||||
|
"swap_des": "Использование swap пространства системой",
|
||||||
|
"temperature": "Температура",
|
||||||
|
"temperature_des": "Температуры датчиков системы",
|
||||||
|
"usage": "Использование",
|
||||||
|
"disk_usage_of": "Использование диска",
|
||||||
|
"throughput_of": "Пропускная способность"
|
||||||
|
}
|
||||||
|
}
|
178
beszel/site/src/locales/zh-CN/translation.json
Normal file
178
beszel/site/src/locales/zh-CN/translation.json
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
{
|
||||||
|
"all_systems": "所有服务器",
|
||||||
|
"filter": "筛选...",
|
||||||
|
"copy": "复制",
|
||||||
|
"add": "添加",
|
||||||
|
"system": "服务器",
|
||||||
|
"systems": "服务器",
|
||||||
|
"cancel": "取消",
|
||||||
|
"continue": "继续",
|
||||||
|
"home": {
|
||||||
|
"active_alerts": "活动警报",
|
||||||
|
"active_des": "在过去 {{minutes}} 分钟内超过 {{value}}{{unit}} 平均值",
|
||||||
|
"subtitle_1": "实时更新。按",
|
||||||
|
"subtitle_2": "打开命令面板。"
|
||||||
|
},
|
||||||
|
"systems_table": {
|
||||||
|
"system": "服务器",
|
||||||
|
"memory": "内存",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"disk": "磁盘",
|
||||||
|
"net": "网络",
|
||||||
|
"agent": "客户端",
|
||||||
|
"no_systems_found": "未找到服务器。",
|
||||||
|
"open_menu": "打开菜单",
|
||||||
|
"resume": "恢复",
|
||||||
|
"pause": "暂停",
|
||||||
|
"copy_host": "复制主机",
|
||||||
|
"delete": "删除",
|
||||||
|
"delete_confirm": "您确定要删除 {{name}} 吗?",
|
||||||
|
"delete_confirm_des_1": "此操作无法撤消。这将永久从数据库中删除",
|
||||||
|
"delete_confirm_des_2": "的所有记录。"
|
||||||
|
},
|
||||||
|
"alerts": {
|
||||||
|
"title": "警报",
|
||||||
|
"subtitle_1": "查看",
|
||||||
|
"notification_settings": "通知设置",
|
||||||
|
"subtitle_2": "配置如何接收警报。",
|
||||||
|
"overwrite_existing_alerts": "覆盖现有警报",
|
||||||
|
"info": {
|
||||||
|
"status": "状态",
|
||||||
|
"status_des": "状态在 在线/离线 之间切换时触发。",
|
||||||
|
"cpu_usage": "CPU 使用率",
|
||||||
|
"cpu_usage_des": "当 CPU 使用率超过阈值时触发。",
|
||||||
|
"memory_usage": "内存使用率",
|
||||||
|
"memory_usage_des": "当内存使用率超过阈值时触发。",
|
||||||
|
"disk_usage": "磁盘使用率",
|
||||||
|
"disk_usage_des": "当任何磁盘的使用率超过阈值时触发。",
|
||||||
|
"bandwidth": "带宽",
|
||||||
|
"bandwidth_des": "当组合的 上行/下行 流量超过阈值时触发。",
|
||||||
|
"temperature": "温度",
|
||||||
|
"temperature_des": "当任何传感器超过阈值时触发。"
|
||||||
|
},
|
||||||
|
"average_exceeds": "平均值超过",
|
||||||
|
"for": "持续",
|
||||||
|
"minute": "分钟",
|
||||||
|
"minutes": "分钟"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"settings": "设置",
|
||||||
|
"subtitle": "管理显示和通知偏好。",
|
||||||
|
"save_settings": "保存设置",
|
||||||
|
"export_configuration": "导出配置",
|
||||||
|
"general": {
|
||||||
|
"title": "常规",
|
||||||
|
"subtitle": "更改通用应用程序选项。",
|
||||||
|
"language": {
|
||||||
|
"title": "语言",
|
||||||
|
"subtitle_1": "想帮助我们改进翻译吗?查看",
|
||||||
|
"subtitle_2": "了解更多详情。",
|
||||||
|
"preferred_language": "首选语言"
|
||||||
|
},
|
||||||
|
"chart_options": {
|
||||||
|
"title": "图表选项",
|
||||||
|
"subtitle": "调整图表的显示选项。",
|
||||||
|
"default_time_period": "默认时间段",
|
||||||
|
"default_time_period_des": "设置查看服务器时图表的默认时间范围。"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"title": "通知",
|
||||||
|
"subtitle_1": "设置如何接收警报通知。",
|
||||||
|
"subtitle_2": "正在寻找创建警报的位置?点击服务器列表中的",
|
||||||
|
"subtitle_3": "图标。",
|
||||||
|
"email": {
|
||||||
|
"title": "电子邮件通知",
|
||||||
|
"please": "配置",
|
||||||
|
"configure_an_SMTP_server": "SMTP 服务器",
|
||||||
|
"to_ensure_alerts_are_delivered": "以确保邮件可以成功发送。",
|
||||||
|
"to_email_s": "收件人邮箱",
|
||||||
|
"enter_email_address": "输入邮箱地址...",
|
||||||
|
"des": "使用 Enter 键或逗号保存地址。留空以禁用电子邮件通知。"
|
||||||
|
},
|
||||||
|
"webhook_push": {
|
||||||
|
"title": "Webhook / 推送通知",
|
||||||
|
"des_1": "Beszel 使用",
|
||||||
|
"des_2": "与流行的通知服务集成。",
|
||||||
|
"add_url": "添加 URL"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"yaml_config": {
|
||||||
|
"short_title": "YAML 配置",
|
||||||
|
"title": "YAML 配置",
|
||||||
|
"subtitle": "导出当前系统配置。",
|
||||||
|
"des_1": "系统使用",
|
||||||
|
"des_2": "配置文件储存管理数据",
|
||||||
|
"des_3": "每次重启时,数据库中的系统将更新以匹配文件中定义的系统。",
|
||||||
|
"alert": {
|
||||||
|
"title": "警告 - 潜在数据丢失",
|
||||||
|
"des_1": "未在",
|
||||||
|
"des_2": "中定义的现有系统将被删除。请定期备份。"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": "语言"
|
||||||
|
},
|
||||||
|
"user_dm": {
|
||||||
|
"users": "用户",
|
||||||
|
"logs": "日志",
|
||||||
|
"backups": "备份",
|
||||||
|
"auth_providers": "身份验证提供者",
|
||||||
|
"log_out": "登出"
|
||||||
|
},
|
||||||
|
"themes": {
|
||||||
|
"toggle_theme": "切换主题",
|
||||||
|
"light": "浅色",
|
||||||
|
"dark": "深色",
|
||||||
|
"system": "系统"
|
||||||
|
},
|
||||||
|
"add_system": {
|
||||||
|
"add_new_system": "添加新服务器",
|
||||||
|
"binary": "二进制文件",
|
||||||
|
"dialog_des_1": "客户端必须在服务器上运行才能连接。复制",
|
||||||
|
"dialog_des_2": "到目标服务器以安装。",
|
||||||
|
"name": "名称",
|
||||||
|
"host_ip": "主机/IP",
|
||||||
|
"port": "端口",
|
||||||
|
"public_key": "公钥",
|
||||||
|
"click_to_copy": "点击复制",
|
||||||
|
"command": "命令",
|
||||||
|
"add_system": "添加服务器"
|
||||||
|
},
|
||||||
|
"command": {
|
||||||
|
"search": "在设置或系统中搜索...",
|
||||||
|
"pages_settings": "页面 / 设置",
|
||||||
|
"dashboard": "仪表盘",
|
||||||
|
"documentation": "文档",
|
||||||
|
"SMTP_settings": "SMTP 设置",
|
||||||
|
"page": "页面",
|
||||||
|
"admin": "管理员"
|
||||||
|
},
|
||||||
|
"monitor": {
|
||||||
|
"toggle_grid": "切换布局",
|
||||||
|
"average": "平均值",
|
||||||
|
"max_1_min": "1 分钟最大",
|
||||||
|
"total_cpu_usage": "总 CPU 使用率",
|
||||||
|
"cpu_des": "系统范围的 CPU 利用率",
|
||||||
|
"docker_cpu_usage": "Docker CPU 使用率",
|
||||||
|
"docker_cpu_des": "Docker 的平均 CPU 利用率",
|
||||||
|
"total_memory_usage": "总内存使用率",
|
||||||
|
"memory_des": "记录时间点的精确利用率",
|
||||||
|
"docker_memory_usage": "Docker 内存使用率",
|
||||||
|
"docker_memory_des": "Docker 容器的内存使用率",
|
||||||
|
"disk_space": "磁盘空间",
|
||||||
|
"disk_des": "根分区的使用情况",
|
||||||
|
"disk_io": "磁盘 I/O",
|
||||||
|
"disk_io_des": "根文件系统的吞吐量",
|
||||||
|
"bandwidth": "带宽",
|
||||||
|
"bandwidth_des": "公共接口的网络流量",
|
||||||
|
"docker_network_io": "Docker 网络 I/O",
|
||||||
|
"docker_network_io_des": "Docker 容器的网络流量",
|
||||||
|
"swap_usage": "交换空间使用率",
|
||||||
|
"swap_des": "系统使用的交换空间",
|
||||||
|
"temperature": "温度",
|
||||||
|
"temperature_des": "系统传感器的温度",
|
||||||
|
"usage": "使用率",
|
||||||
|
"disk_usage_of": "的磁盘使用率",
|
||||||
|
"throughput_of": "的吞吐量"
|
||||||
|
}
|
||||||
|
}
|
178
beszel/site/src/locales/zh-HK/translation.json
Normal file
178
beszel/site/src/locales/zh-HK/translation.json
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
{
|
||||||
|
"all_systems": "所有伺服器",
|
||||||
|
"filter": "篩選...",
|
||||||
|
"copy": "複製",
|
||||||
|
"add": "新增",
|
||||||
|
"system": "伺服器",
|
||||||
|
"systems": "伺服器",
|
||||||
|
"cancel": "取消",
|
||||||
|
"continue": "繼續",
|
||||||
|
"home": {
|
||||||
|
"active_alerts": "活動警報",
|
||||||
|
"active_des": "在過去 {{minutes}} 分鐘內超過 {{value}}{{unit}} 平均值",
|
||||||
|
"subtitle_1": "即時更新。按",
|
||||||
|
"subtitle_2": "打開指令面板。"
|
||||||
|
},
|
||||||
|
"systems_table": {
|
||||||
|
"system": "伺服器",
|
||||||
|
"memory": "記憶體",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"disk": "磁碟",
|
||||||
|
"net": "網絡",
|
||||||
|
"agent": "客戶端",
|
||||||
|
"no_systems_found": "未找到伺服器。",
|
||||||
|
"open_menu": "打開選單",
|
||||||
|
"resume": "恢復",
|
||||||
|
"pause": "暫停",
|
||||||
|
"copy_host": "複製主機",
|
||||||
|
"delete": "刪除",
|
||||||
|
"delete_confirm": "您確定要刪除 {{name}} 嗎?",
|
||||||
|
"delete_confirm_des_1": "此操作無法撤銷。這將永久從資料庫中刪除",
|
||||||
|
"delete_confirm_des_2": "的所有記錄。"
|
||||||
|
},
|
||||||
|
"alerts": {
|
||||||
|
"title": "警報",
|
||||||
|
"subtitle_1": "查看",
|
||||||
|
"notification_settings": "通知設定",
|
||||||
|
"subtitle_2": "配置如何接收警報。",
|
||||||
|
"overwrite_existing_alerts": "覆蓋現有警報",
|
||||||
|
"info": {
|
||||||
|
"status": "狀態",
|
||||||
|
"status_des": "狀態在 在線/離線 之間切換時觸發。",
|
||||||
|
"cpu_usage": "CPU 使用率",
|
||||||
|
"cpu_usage_des": "當 CPU 使用率超過閾值時觸發。",
|
||||||
|
"memory_usage": "記憶體使用率",
|
||||||
|
"memory_usage_des": "當記憶體使用率超過閾值時觸發。",
|
||||||
|
"disk_usage": "磁碟使用率",
|
||||||
|
"disk_usage_des": "當任何磁碟的使用率超過閾值時觸發。",
|
||||||
|
"bandwidth": "頻寬",
|
||||||
|
"bandwidth_des": "當組合的 上行/下行 流量超過閾值時觸發。",
|
||||||
|
"temperature": "溫度",
|
||||||
|
"temperature_des": "當任何感應器超過閾值時觸發。"
|
||||||
|
},
|
||||||
|
"average_exceeds": "平均值超過",
|
||||||
|
"for": "持續",
|
||||||
|
"minute": "分鐘",
|
||||||
|
"minutes": "分鐘"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"settings": "設定",
|
||||||
|
"subtitle": "管理顯示和通知偏好。",
|
||||||
|
"save_settings": "儲存設定",
|
||||||
|
"export_configuration": "匯出配置",
|
||||||
|
"general": {
|
||||||
|
"title": "一般",
|
||||||
|
"subtitle": "更改通用應用程式選項。",
|
||||||
|
"language": {
|
||||||
|
"title": "語言",
|
||||||
|
"subtitle_1": "想幫助我們改進翻譯嗎?查看",
|
||||||
|
"subtitle_2": "了解更多詳情。",
|
||||||
|
"preferred_language": "首選語言"
|
||||||
|
},
|
||||||
|
"chart_options": {
|
||||||
|
"title": "圖表選項",
|
||||||
|
"subtitle": "調整圖表的顯示選項。",
|
||||||
|
"default_time_period": "預設時間段",
|
||||||
|
"default_time_period_des": "設定查看伺服器時圖表的預設時間範圍。"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"title": "通知",
|
||||||
|
"subtitle_1": "設定如何接收警報通知。",
|
||||||
|
"subtitle_2": "正在尋找建立警報的位置?點擊伺服器列表中的",
|
||||||
|
"subtitle_3": "圖示。",
|
||||||
|
"email": {
|
||||||
|
"title": "電郵通知",
|
||||||
|
"please": "配置",
|
||||||
|
"configure_an_SMTP_server": "SMTP 伺服器",
|
||||||
|
"to_ensure_alerts_are_delivered": "以確保郵件可以成功發送。",
|
||||||
|
"to_email_s": "收件人電郵",
|
||||||
|
"enter_email_address": "輸入電郵地址...",
|
||||||
|
"des": "使用 Enter 鍵或逗號儲存地址。留空以禁用電郵通知。"
|
||||||
|
},
|
||||||
|
"webhook_push": {
|
||||||
|
"title": "Webhook / 推送通知",
|
||||||
|
"des_1": "Beszel 使用",
|
||||||
|
"des_2": "與流行的通知服務整合。",
|
||||||
|
"add_url": "新增 URL"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"yaml_config": {
|
||||||
|
"short_title": "YAML 配置",
|
||||||
|
"title": "YAML 配置",
|
||||||
|
"subtitle": "匯出當前系統配置。",
|
||||||
|
"des_1": "系統使用",
|
||||||
|
"des_2": "配置檔案儲存管理數據",
|
||||||
|
"des_3": "每次重啟時,資料庫中的系統將更新以匹配檔案中定義的系統。",
|
||||||
|
"alert": {
|
||||||
|
"title": "警告 - 潛在數據丟失",
|
||||||
|
"des_1": "未在",
|
||||||
|
"des_2": "中定義的現有系統將被刪除。請定期備份。"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": "語言"
|
||||||
|
},
|
||||||
|
"user_dm": {
|
||||||
|
"users": "用戶",
|
||||||
|
"logs": "日誌",
|
||||||
|
"backups": "備份",
|
||||||
|
"auth_providers": "身份驗證提供者",
|
||||||
|
"log_out": "登出"
|
||||||
|
},
|
||||||
|
"themes": {
|
||||||
|
"toggle_theme": "切換主題",
|
||||||
|
"light": "淺色",
|
||||||
|
"dark": "深色",
|
||||||
|
"system": "系統"
|
||||||
|
},
|
||||||
|
"add_system": {
|
||||||
|
"add_new_system": "新增新伺服器",
|
||||||
|
"binary": "二進制檔案",
|
||||||
|
"dialog_des_1": "客戶端必須在伺服器上運行才能連接。複製",
|
||||||
|
"dialog_des_2": "到目標伺服器以安裝。",
|
||||||
|
"name": "名稱",
|
||||||
|
"host_ip": "主機/IP",
|
||||||
|
"port": "端口",
|
||||||
|
"public_key": "公鑰",
|
||||||
|
"click_to_copy": "點擊複製",
|
||||||
|
"command": "指令",
|
||||||
|
"add_system": "新增伺服器"
|
||||||
|
},
|
||||||
|
"command": {
|
||||||
|
"search": "在設定或系統中搜尋...",
|
||||||
|
"pages_settings": "頁面 / 設定",
|
||||||
|
"dashboard": "儀表板",
|
||||||
|
"documentation": "文件",
|
||||||
|
"SMTP_settings": "SMTP 設定",
|
||||||
|
"page": "頁面",
|
||||||
|
"admin": "管理員"
|
||||||
|
},
|
||||||
|
"monitor": {
|
||||||
|
"toggle_grid": "切換佈局",
|
||||||
|
"average": "平均值",
|
||||||
|
"max_1_min": "1 分鐘最大",
|
||||||
|
"total_cpu_usage": "總 CPU 使用率",
|
||||||
|
"cpu_des": "系統範圍的 CPU 利用率",
|
||||||
|
"docker_cpu_usage": "Docker CPU 使用率",
|
||||||
|
"docker_cpu_des": "Docker 的平均 CPU 利用率",
|
||||||
|
"total_memory_usage": "總記憶體使用率",
|
||||||
|
"memory_des": "記錄時間點的精確利用率",
|
||||||
|
"docker_memory_usage": "Docker 記憶體使用率",
|
||||||
|
"docker_memory_des": "Docker 容器的記憶體使用率",
|
||||||
|
"disk_space": "磁碟空間",
|
||||||
|
"disk_des": "根分區的使用情況",
|
||||||
|
"disk_io": "磁碟 I/O",
|
||||||
|
"disk_io_des": "根檔案系統的吞吐量",
|
||||||
|
"bandwidth": "頻寬",
|
||||||
|
"bandwidth_des": "公共介面的網絡流量",
|
||||||
|
"docker_network_io": "Docker 網絡 I/O",
|
||||||
|
"docker_network_io_des": "Docker 容器的網絡流量",
|
||||||
|
"swap_usage": "交換空間使用率",
|
||||||
|
"swap_des": "系統使用的交換空間",
|
||||||
|
"temperature": "溫度",
|
||||||
|
"temperature_des": "系統感應器的溫度",
|
||||||
|
"usage": "使用率",
|
||||||
|
"disk_usage_of": "的磁碟使用率",
|
||||||
|
"throughput_of": "的吞吐量"
|
||||||
|
}
|
||||||
|
}
|
@@ -11,6 +11,7 @@ import {
|
|||||||
$hubVersion,
|
$hubVersion,
|
||||||
$copyContent,
|
$copyContent,
|
||||||
} from './lib/stores.ts'
|
} from './lib/stores.ts'
|
||||||
|
import { LangToggle } from './components/lang-toggle.tsx'
|
||||||
import { ModeToggle } from './components/mode-toggle.tsx'
|
import { ModeToggle } from './components/mode-toggle.tsx'
|
||||||
import {
|
import {
|
||||||
cn,
|
cn,
|
||||||
@@ -48,6 +49,10 @@ 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'
|
||||||
|
|
||||||
|
import './lib/i18n.ts'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { TFunction } from 'i18next'
|
||||||
|
|
||||||
// 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'))
|
||||||
const LoginPage = lazy(() => import('./components/login/login.tsx'))
|
const LoginPage = lazy(() => import('./components/login/login.tsx'))
|
||||||
@@ -110,7 +115,87 @@ const App = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Navbar = (t: TFunction<'translation', undefined>) => {
|
||||||
|
return (
|
||||||
|
<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 href="/" aria-label="Home" className={'p-2 pl-0'}>
|
||||||
|
<Logo className="h-[1.15em] fill-foreground" />
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<div className={'flex ml-auto items-center'}>
|
||||||
|
<LangToggle />
|
||||||
|
<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>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<button
|
||||||
|
aria-label="User Actions"
|
||||||
|
className={cn('', buttonVariants({ variant: 'ghost', size: 'icon' }))}
|
||||||
|
>
|
||||||
|
<UserIcon className="h-[1.2rem] w-[1.2rem]" />
|
||||||
|
</button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align={isReadOnlyUser() ? 'end' : 'center'} className="min-w-44">
|
||||||
|
<DropdownMenuLabel>{pb.authStore.model?.email}</DropdownMenuLabel>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
{isAdmin() && (
|
||||||
|
<>
|
||||||
|
<DropdownMenuItem asChild>
|
||||||
|
<a href="/_/" target="_blank">
|
||||||
|
<UsersIcon className="mr-2.5 h-4 w-4" />
|
||||||
|
<span>{t('user_dm.users')}</span>
|
||||||
|
</a>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem asChild>
|
||||||
|
<a href="/_/#/collections?collectionId=2hz5ncl8tizk5nx" target="_blank">
|
||||||
|
<ServerIcon className="mr-2.5 h-4 w-4" />
|
||||||
|
<span>{t('systems')}</span>
|
||||||
|
</a>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem asChild>
|
||||||
|
<a href="/_/#/logs" target="_blank">
|
||||||
|
<LogsIcon className="mr-2.5 h-4 w-4" />
|
||||||
|
<span>{t('user_dm.logs')}</span>
|
||||||
|
</a>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem asChild>
|
||||||
|
<a href="/_/#/settings/backups" target="_blank">
|
||||||
|
<DatabaseBackupIcon className="mr-2.5 h-4 w-4" />
|
||||||
|
<span>{t('user_dm.backups')}</span>
|
||||||
|
</a>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem asChild>
|
||||||
|
<a href="/_/#/settings/auth-providers" target="_blank">
|
||||||
|
<LockKeyholeIcon className="mr-2.5 h-4 w-4" />
|
||||||
|
<span>{t('user_dm.auth_providers')}</span>
|
||||||
|
</a>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
<DropdownMenuItem onSelect={() => pb.authStore.clear()}>
|
||||||
|
<LogOutIcon className="mr-2.5 h-4 w-4" />
|
||||||
|
<span>{t('user_dm.log_out')}</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
<AddSystemButton className="ml-2" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const Layout = () => {
|
const Layout = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const authenticated = useStore($authenticated)
|
const authenticated = useStore($authenticated)
|
||||||
const copyContent = useStore($copyContent)
|
const copyContent = useStore($copyContent)
|
||||||
|
|
||||||
@@ -124,80 +209,7 @@ const Layout = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="container">
|
<div className="container">{Navbar(t)}</div>
|
||||||
<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 href="/" aria-label="Home" className={'p-2 pl-0'}>
|
|
||||||
<Logo className="h-[1.15em] fill-foreground" />
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<div className={'flex ml-auto items-center'}>
|
|
||||||
<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>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<button
|
|
||||||
aria-label="User Actions"
|
|
||||||
className={cn('', buttonVariants({ variant: 'ghost', size: 'icon' }))}
|
|
||||||
>
|
|
||||||
<UserIcon className="h-[1.2rem] w-[1.2rem]" />
|
|
||||||
</button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align={isReadOnlyUser() ? 'end' : 'center'} className="min-w-44">
|
|
||||||
<DropdownMenuLabel>{pb.authStore.model?.email}</DropdownMenuLabel>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuGroup>
|
|
||||||
{isAdmin() && (
|
|
||||||
<>
|
|
||||||
<DropdownMenuItem asChild>
|
|
||||||
<a href="/_/" target="_blank">
|
|
||||||
<UsersIcon className="mr-2.5 h-4 w-4" />
|
|
||||||
<span>Users</span>
|
|
||||||
</a>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem asChild>
|
|
||||||
<a href="/_/#/collections?collectionId=2hz5ncl8tizk5nx" target="_blank">
|
|
||||||
<ServerIcon className="mr-2.5 h-4 w-4" />
|
|
||||||
<span>Systems</span>
|
|
||||||
</a>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem asChild>
|
|
||||||
<a href="/_/#/logs" target="_blank">
|
|
||||||
<LogsIcon className="mr-2.5 h-4 w-4" />
|
|
||||||
<span>Logs</span>
|
|
||||||
</a>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem asChild>
|
|
||||||
<a href="/_/#/settings/backups" target="_blank">
|
|
||||||
<DatabaseBackupIcon className="mr-2.5 h-4 w-4" />
|
|
||||||
<span>Backups</span>
|
|
||||||
</a>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem asChild>
|
|
||||||
<a href="/_/#/settings/auth-providers" target="_blank">
|
|
||||||
<LockKeyholeIcon className="mr-2.5 h-4 w-4" />
|
|
||||||
<span>Auth providers</span>
|
|
||||||
</a>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</DropdownMenuGroup>
|
|
||||||
<DropdownMenuItem onSelect={() => pb.authStore.clear()}>
|
|
||||||
<LogOutIcon className="mr-2.5 h-4 w-4" />
|
|
||||||
<span>Log out</span>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
<AddSystemButton className="ml-2" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="container mb-14 relative">
|
<div className="container mb-14 relative">
|
||||||
<App />
|
<App />
|
||||||
<Suspense>
|
<Suspense>
|
||||||
|
@@ -24,6 +24,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
screens: {
|
screens: {
|
||||||
xs: '425px',
|
xs: '425px',
|
||||||
|
450: '450px',
|
||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
green: {
|
green: {
|
||||||
|
Reference in New Issue
Block a user