mirror of
https://github.com/fankes/beszel.git
synced 2025-10-19 17:59:28 +08:00
i18n tweaks / layout fixes
This commit is contained in:
@@ -9,12 +9,7 @@ import {
|
||||
DialogTrigger,
|
||||
} from '@/components/ui/dialog'
|
||||
import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip'
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@/components/ui/tabs"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
@@ -51,7 +46,9 @@ export function AddSystemButton({ className }: { className?: string }) {
|
||||
}
|
||||
|
||||
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}"`)
|
||||
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) {
|
||||
@@ -77,10 +74,11 @@ export function AddSystemButton({ className }: { className?: string }) {
|
||||
className={cn('flex gap-1 max-xs:h-[2.4rem]', className, isReadOnlyUser() && 'hidden')}
|
||||
>
|
||||
<PlusIcon className="h-4 w-4 -ml-1" />
|
||||
{t('add')}<span className="hidden xs:inline">{t('system')}</span>
|
||||
{t('add')}
|
||||
<span className="hidden sm:inline">{t('system')}</span>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="w-[90%] sm:max-w-[425px] rounded-lg">
|
||||
<DialogContent className="w-[90%] sm:max-w-[440px] rounded-lg">
|
||||
<Tabs defaultValue="docker">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="mb-2">{t('add_system.add_new_system')}</DialogTitle>
|
||||
@@ -93,14 +91,16 @@ export function AddSystemButton({ className }: { className?: string }) {
|
||||
<TabsContent value="docker">
|
||||
<DialogDescription className={'mb-4'}>
|
||||
{t('add_system.dialog_des_1')}{' '}
|
||||
<code className="bg-muted px-1 rounded-sm">docker-compose.yml</code> {t('add_system.dialog_des_2')}
|
||||
<code className="bg-muted px-1 rounded-sm">docker-compose.yml</code>{' '}
|
||||
{t('add_system.dialog_des_2')}
|
||||
</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')}
|
||||
<code className="bg-muted px-1 rounded-sm">install command</code>{' '}
|
||||
{t('add_system.dialog_des_2')}
|
||||
</DialogDescription>
|
||||
</TabsContent>
|
||||
<form onSubmit={handleSubmit as any}>
|
||||
@@ -161,7 +161,7 @@ export function AddSystemButton({ className }: { className?: string }) {
|
||||
</div>
|
||||
{/* Docker */}
|
||||
<TabsContent value="docker">
|
||||
<DialogFooter className="flex justify-end gap-2">
|
||||
<DialogFooter className="flex justify-end gap-2 sm:w-[calc(100%+20px)] sm:-ml-[20px]">
|
||||
<Button
|
||||
type="button"
|
||||
variant={'ghost'}
|
||||
@@ -174,7 +174,7 @@ export function AddSystemButton({ className }: { className?: string }) {
|
||||
</TabsContent>
|
||||
{/* Binary */}
|
||||
<TabsContent value="binary">
|
||||
<DialogFooter className="flex justify-end gap-2">
|
||||
<DialogFooter className="flex justify-end gap-2 sm:w-[calc(100%+20px)] sm:-ml-[20px]">
|
||||
<Button
|
||||
type="button"
|
||||
variant={'ghost'}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { useEffect } from 'react'
|
||||
import { GlobeIcon, Languages } from 'lucide-react'
|
||||
import { LanguagesIcon } from 'lucide-react'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
@@ -10,19 +10,20 @@ import {
|
||||
} 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();
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
useEffect(() => {
|
||||
document.documentElement.lang = i18n.language;
|
||||
}, [i18n.language]);
|
||||
document.documentElement.lang = i18n.language
|
||||
}, [i18n.language])
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant={'ghost'} size="icon">
|
||||
<GlobeIcon className="absolute h-[1.2rem] w-[1.2rem]" />
|
||||
<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>
|
||||
@@ -30,7 +31,7 @@ export function LangToggle() {
|
||||
{languages.map(({ lang, label }) => (
|
||||
<DropdownMenuItem
|
||||
key={lang}
|
||||
className={lang === i18n.language ? 'font-bold' : ''}
|
||||
className={cn('pl-4', lang === i18n.language ? 'font-bold' : '')}
|
||||
onClick={() => i18n.changeLanguage(lang)}
|
||||
>
|
||||
{label}
|
||||
|
@@ -13,7 +13,7 @@ import { useTranslation } from 'react-i18next'
|
||||
|
||||
const SystemsTable = lazy(() => import('../systems-table/systems-table'))
|
||||
|
||||
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
|
||||
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0
|
||||
|
||||
export default function () {
|
||||
const { t } = useTranslation()
|
||||
@@ -78,13 +78,13 @@ export default function () {
|
||||
>
|
||||
<info.icon className="h-4 w-4" />
|
||||
<AlertTitle>
|
||||
{alert.sysname} {info.name}
|
||||
{alert.sysname} {t(info.name)}
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
{t('active_des', {
|
||||
{t('home.active_des', {
|
||||
value: alert.value,
|
||||
unit: info.unit,
|
||||
minutes: alert.min
|
||||
minutes: alert.min,
|
||||
})}
|
||||
</AlertDescription>
|
||||
<Link
|
||||
@@ -108,7 +108,7 @@ export default function () {
|
||||
<CardDescription>
|
||||
{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">
|
||||
<span className="text-xs">{isMac ? '⌘' : "Ctrl"}</span>K
|
||||
<span className="text-xs">{isMac ? '⌘' : 'Ctrl'}</span>K
|
||||
</kbd>{' '}
|
||||
{t('home.subtitle_2')}
|
||||
</CardDescription>
|
||||
|
@@ -9,7 +9,7 @@ import {
|
||||
} from '@/components/ui/select'
|
||||
import { chartTimeData } from '@/lib/utils'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { LoaderCircleIcon, SaveIcon } from 'lucide-react'
|
||||
import { LanguagesIcon, LoaderCircleIcon, SaveIcon } from 'lucide-react'
|
||||
import { UserSettings } from '@/types'
|
||||
import { saveSettings } from './layout'
|
||||
import { useState, useEffect } from 'react'
|
||||
@@ -21,8 +21,8 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
|
||||
const { t, i18n } = useTranslation()
|
||||
|
||||
useEffect(() => {
|
||||
document.documentElement.lang = i18n.language;
|
||||
}, [i18n.language]);
|
||||
document.documentElement.lang = i18n.language
|
||||
}, [i18n.language])
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
@@ -47,7 +47,10 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
|
||||
<form onSubmit={handleSubmit} className="space-y-5">
|
||||
<div className="space-y-2">
|
||||
<div className="mb-4">
|
||||
<h3 className="mb-1 text-lg font-medium">{t('settings.general.language.title')}</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">
|
||||
{t('settings.general.language.subtitle_1')}{' '}
|
||||
<a href="https://crowdin.com/project/beszel" className="link" target="_blank">
|
||||
@@ -59,7 +62,7 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
|
||||
<Label className="block" htmlFor="lang">
|
||||
{t('settings.general.language.preferred_language')}
|
||||
</Label>
|
||||
<Select defaultValue={i18n.language} onValueChange={(lang: string) => i18n.changeLanguage(lang)}>
|
||||
<Select value={i18n.language} onValueChange={(lang: string) => i18n.changeLanguage(lang)}>
|
||||
<SelectTrigger id="lang">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
@@ -72,9 +75,12 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="space-y-2">
|
||||
<div className="mb-4">
|
||||
<h3 className="mb-1 text-lg font-medium">{t('settings.general.chart_options.title')}</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">
|
||||
{t('settings.general.chart_options.subtitle')}
|
||||
</p>
|
||||
|
@@ -9,7 +9,7 @@
|
||||
"continue": "Continue",
|
||||
"home": {
|
||||
"active_alerts": "Active Alerts",
|
||||
"active_des": "Exceeds {{value}}{{unit}} average in last {{minutes}} minutes",
|
||||
"active_des": "Exceeds {{value}}{{unit}} average in last {{minutes}} min",
|
||||
"subtitle_1": "Updated in real time. Press",
|
||||
"subtitle_2": "to open the command palette."
|
||||
},
|
||||
|
@@ -51,6 +51,7 @@ 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 CommandPalette = lazy(() => import('./components/command-palette.tsx'))
|
||||
@@ -114,6 +115,84 @@ 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 { t } = useTranslation()
|
||||
|
||||
@@ -130,81 +209,7 @@ const Layout = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="container">
|
||||
<div className="flex items-center h-14 md:h-16 bg-card px-4 pr-3 sm:px-6 border bt-0 rounded-md my-4">
|
||||
<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>
|
||||
</div>
|
||||
<div className="container">{Navbar(t)}</div>
|
||||
<div className="container mb-14 relative">
|
||||
<App />
|
||||
<Suspense>
|
||||
|
@@ -24,6 +24,7 @@ module.exports = {
|
||||
},
|
||||
screens: {
|
||||
xs: '425px',
|
||||
450: '450px',
|
||||
},
|
||||
colors: {
|
||||
green: {
|
||||
|
Reference in New Issue
Block a user