Servers.tsx i18n

This commit is contained in:
headlessdev 2025-04-29 16:20:34 +02:00
parent 58e2466875
commit c4f3b47fc7
2 changed files with 342 additions and 272 deletions

View File

@ -28,6 +28,8 @@ import {
Copy, Copy,
History, History,
Thermometer, Thermometer,
ChevronLeft,
ChevronRight,
} from "lucide-react" } from "lucide-react"
import { Card, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Card, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { import {
@ -124,7 +126,7 @@ interface MonitoringData {
} }
export default function Servers() { export default function Servers() {
const t = useTranslations('Servers') const t = useTranslations()
const [host, setHost] = useState<boolean>(false) const [host, setHost] = useState<boolean>(false)
const [hostServer, setHostServer] = useState<number>(0) const [hostServer, setHostServer] = useState<number>(0)
const [name, setName] = useState<string>("") const [name, setName] = useState<string>("")
@ -527,11 +529,11 @@ export default function Servers() {
</BreadcrumbItem> </BreadcrumbItem>
<BreadcrumbSeparator className="hidden md:block" /> <BreadcrumbSeparator className="hidden md:block" />
<BreadcrumbItem> <BreadcrumbItem>
<BreadcrumbPage>{t('MyInfrastructure')}</BreadcrumbPage> <BreadcrumbPage>{t('Servers.MyInfrastructure')}</BreadcrumbPage>
</BreadcrumbItem> </BreadcrumbItem>
<BreadcrumbSeparator className="hidden md:block" /> <BreadcrumbSeparator className="hidden md:block" />
<BreadcrumbItem> <BreadcrumbItem>
<BreadcrumbPage>{t('Servers')}</BreadcrumbPage> <BreadcrumbPage>{t('Servers.Title')}</BreadcrumbPage>
</BreadcrumbItem> </BreadcrumbItem>
</BreadcrumbList> </BreadcrumbList>
</Breadcrumb> </Breadcrumb>
@ -540,11 +542,11 @@ export default function Servers() {
<Toaster /> <Toaster />
<div className="p-6"> <div className="p-6">
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<span className="text-3xl font-bold">{t('YourServers')}</span> <span className="text-3xl font-bold">{t('Servers.YourServers')}</span>
<div className="flex gap-2"> <div className="flex gap-2">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="outline" size="icon" title={t('ChangeView')}> <Button variant="outline" size="icon" title={t('Common.ChangeView')}>
{isGridLayout ? ( {isGridLayout ? (
<LayoutGrid className="h-4 w-4" /> <LayoutGrid className="h-4 w-4" />
) : ( ) : (
@ -554,10 +556,10 @@ export default function Servers() {
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => toggleLayout(false)}> <DropdownMenuItem onClick={() => toggleLayout(false)}>
<List className="h-4 w-4 mr-2" /> {t('ListView')} <List className="h-4 w-4 mr-2" /> {t('Common.ListView')}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => toggleLayout(true)}> <DropdownMenuItem onClick={() => toggleLayout(true)}>
<LayoutGrid className="h-4 w-4 mr-2" /> {t('GridView')} <LayoutGrid className="h-4 w-4 mr-2" /> {t('Common.GridView')}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
@ -573,23 +575,23 @@ export default function Servers() {
> >
<SelectTrigger className="w-[140px]"> <SelectTrigger className="w-[140px]">
<SelectValue> <SelectValue>
{itemsPerPage} {itemsPerPage === 1 ? t('ItemsPerPage.item') : t('ItemsPerPage.items')} {itemsPerPage} {itemsPerPage === 1 ? t('Common.ItemsPerPage.item') : t('Common.ItemsPerPage.items')}
</SelectValue> </SelectValue>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{![4, 6, 10, 15, 20, 25].includes(itemsPerPage) ? ( {![4, 6, 10, 15, 20, 25].includes(itemsPerPage) ? (
<SelectItem value={String(itemsPerPage)}> <SelectItem value={String(itemsPerPage)}>
{itemsPerPage} {itemsPerPage === 1 ? t('ItemsPerPage.item') : t('ItemsPerPage.items')} ({t('ItemsPerPage.Custom')}) {itemsPerPage} {itemsPerPage === 1 ? t('Common.ItemsPerPage.item') : t('Common.ItemsPerPage.items')} ({t('Common.ItemsPerPage.Custom')})
</SelectItem> </SelectItem>
) : null} ) : null}
<SelectItem value="4">{t('4')}</SelectItem> <SelectItem value="4">{t('Common.ItemsPerPage.4')}</SelectItem>
<SelectItem value="6">{t('6')}</SelectItem> <SelectItem value="6">{t('Common.ItemsPerPage.6')}</SelectItem>
<SelectItem value="10">{t('10')}</SelectItem> <SelectItem value="10">{t('Common.ItemsPerPage.10')}</SelectItem>
<SelectItem value="15">{t('15')}</SelectItem> <SelectItem value="15">{t('Common.ItemsPerPage.15')}</SelectItem>
<SelectItem value="20">{t('20')}</SelectItem> <SelectItem value="20">{t('Common.ItemsPerPage.20')}</SelectItem>
<SelectItem value="25">{t('25')}</SelectItem> <SelectItem value="25">{t('Common.ItemsPerPage.25')}</SelectItem>
<div className="p-2 border-t mt-1"> <div className="p-2 border-t mt-1">
<Label htmlFor="custom-items" className="text-xs font-medium">{t('ItemsPerPage.Custom')}</Label> <Label htmlFor="custom-items" className="text-xs font-medium">{t('Common.ItemsPerPage.Custom')}</Label>
<div className="flex items-center gap-2 mt-1"> <div className="flex items-center gap-2 mt-1">
<Input <Input
id="custom-items" id="custom-items"
@ -640,7 +642,7 @@ export default function Servers() {
}} }}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
/> />
<span className="text-xs text-muted-foreground whitespace-nowrap">{t('ItemsPerPage.items')}</span> <span className="text-xs text-muted-foreground whitespace-nowrap">{t('Common.ItemsPerPage.items')}</span>
</div> </div>
</div> </div>
</SelectContent> </SelectContent>
@ -655,7 +657,7 @@ export default function Servers() {
<AlertDialogContent className="max-w-[95vw] w-[600px] max-h-[90vh] overflow-y-auto"> <AlertDialogContent className="max-w-[95vw] w-[600px] max-h-[90vh] overflow-y-auto">
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle className="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4"> <AlertDialogTitle className="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4">
<span>{t('AddServer.Title')}</span> <span>{t('Servers.AddServer.Title')}</span>
<Select <Select
onValueChange={(value) => { onValueChange={(value) => {
if (!value) return; if (!value) return;
@ -696,7 +698,7 @@ export default function Servers() {
<SelectTrigger className="w-[140px] h-8 text-xs"> <SelectTrigger className="w-[140px] h-8 text-xs">
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<Copy className="h-3 w-3 text-muted-foreground" /> <Copy className="h-3 w-3 text-muted-foreground" />
<SelectValue placeholder="Copy server" /> <SelectValue placeholder={t('Servers.AddServer.General.CopyServer')} />
</div> </div>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@ -711,20 +713,20 @@ export default function Servers() {
<AlertDialogDescription> <AlertDialogDescription>
<Tabs defaultValue="general" className="w-full"> <Tabs defaultValue="general" className="w-full">
<TabsList className="w-full"> <TabsList className="w-full">
<TabsTrigger value="general">{t('Tabs.General')}</TabsTrigger> <TabsTrigger value="general">{t('Common.Server.Tabs.General')}</TabsTrigger>
<TabsTrigger value="hardware">{t('Tabs.Hardware')}</TabsTrigger> <TabsTrigger value="hardware">{t('Common.Server.Tabs.Hardware')}</TabsTrigger>
<TabsTrigger value="virtualization">{t('Tabs.Host')}</TabsTrigger> <TabsTrigger value="virtualization">{t('Common.Server.Tabs.Host')}</TabsTrigger>
<TabsTrigger value="monitoring">{t('Tabs.Monitoring')}</TabsTrigger> <TabsTrigger value="monitoring">{t('Common.Server.Tabs.Monitoring')}</TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="general"> <TabsContent value="general">
<div className="space-y-4 pt-4"> <div className="space-y-4 pt-4">
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4"> <div className="flex flex-col sm:flex-row items-start sm:items-center gap-4">
<div className="grid w-full sm:w-[calc(100%-52px)] items-center gap-1.5"> <div className="grid w-full sm:w-[calc(100%-52px)] items-center gap-1.5">
<Label htmlFor="icon">{t('AddServer.General.Icon')}</Label> <Label htmlFor="icon">{t('Servers.AddServer.General.Icon')}</Label>
<div className="space-y-2"> <div className="space-y-2">
<Select value={icon} onValueChange={(value) => setIcon(value)}> <Select value={icon} onValueChange={(value) => setIcon(value)}>
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">
<SelectValue placeholder={t('AddServer.General.IconPlaceholder')}> <SelectValue placeholder={t('Servers.AddServer.General.IconPlaceholder')}>
{icon && ( {icon && (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<DynamicIcon name={icon as any} size={18} /> <DynamicIcon name={icon as any} size={18} />
@ -735,7 +737,7 @@ export default function Servers() {
</SelectTrigger> </SelectTrigger>
<SelectContent className="max-h-[300px]"> <SelectContent className="max-h-[300px]">
<Input <Input
placeholder={t('AddServer.General.IconSearchPlaceholder')} placeholder={t('Servers.AddServer.General.IconSearchPlaceholder')}
className="mb-2" className="mb-2"
onChange={(e) => { onChange={(e) => {
const iconElements = document.querySelectorAll("[data-icon-item]") const iconElements = document.querySelectorAll("[data-icon-item]")
@ -776,14 +778,14 @@ export default function Servers() {
</div> </div>
</div> </div>
<div className="grid w-[52px] items-center gap-1.5"> <div className="grid w-[52px] items-center gap-1.5">
<Label htmlFor="icon">{t('AddServer.General.Preview')}</Label> <Label htmlFor="icon">{t('Servers.AddServer.General.Preview')}</Label>
<div className="flex items-center justify-center"> <div className="flex items-center justify-center">
{icon && <DynamicIcon name={icon as any} size={36} />} {icon && <DynamicIcon name={icon as any} size={36} />}
</div> </div>
</div> </div>
</div> </div>
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="name">{t('AddServer.General.Name')}</Label> <Label htmlFor="name">{t('Servers.AddServer.General.Name')}</Label>
<Input <Input
id="name" id="name"
type="text" type="text"
@ -794,11 +796,11 @@ export default function Servers() {
</div> </div>
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="description"> <Label htmlFor="description">
{t('AddServer.General.OperatingSystem')} <span className="text-stone-600">({t('optional')})</span> {t('Servers.AddServer.General.OperatingSystem')} <span className="text-stone-600">({t('Common.optional')})</span>
</Label> </Label>
<Select value={os} onValueChange={(value) => setOs(value)}> <Select value={os} onValueChange={(value) => setOs(value)}>
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">
<SelectValue placeholder={t('AddServer.General.OperatingSystemPlaceholder')} /> <SelectValue placeholder={t('Servers.AddServer.General.OperatingSystemPlaceholder')} />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="Windows">Windows</SelectItem> <SelectItem value="Windows">Windows</SelectItem>
@ -809,7 +811,7 @@ export default function Servers() {
</div> </div>
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="ip"> <Label htmlFor="ip">
{t('AddServer.General.IPAdress')} <span className="text-stone-600">({t('optional')})</span> {t('Servers.AddServer.General.IPAdress')} <span className="text-stone-600">({t('Common.optional')})</span>
</Label> </Label>
<Input <Input
id="ip" id="ip"
@ -824,11 +826,11 @@ export default function Servers() {
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger>
<Label htmlFor="publicURL"> <Label htmlFor="publicURL">
{t('AddServer.General.ManagementURL')} <span className="text-stone-600">({t('optional')})</span> {t('Servers.AddServer.General.ManagementURL')} <span className="text-stone-600">({t('Common.optional')})</span>
</Label> </Label>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
{t('AddServer.General.ManagementURLTooltip')} {t('Servers.AddServer.General.ManagementURLTooltip')}
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
@ -846,7 +848,7 @@ export default function Servers() {
<div className="space-y-4 pt-4"> <div className="space-y-4 pt-4">
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="cpu"> <Label htmlFor="cpu">
{t('AddServer.Hardware.CPU')} <span className="text-stone-600">({t('optional')})</span> {t('Common.Server.CPU')} <span className="text-stone-600">({t('Common.optional')})</span>
</Label> </Label>
<Input <Input
id="cpu" id="cpu"
@ -858,7 +860,7 @@ export default function Servers() {
</div> </div>
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="gpu"> <Label htmlFor="gpu">
{t('AddServer.Hardware.GPU')} <span className="text-stone-600">({t('optional')})</span> {t('Common.Server.GPU')} <span className="text-stone-600">({t('Common.optional')})</span>
</Label> </Label>
<Input <Input
id="gpu" id="gpu"
@ -870,7 +872,7 @@ export default function Servers() {
</div> </div>
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="ram"> <Label htmlFor="ram">
{t('AddServer.Hardware.RAM')} <span className="text-stone-600">({t('optional')})</span> {t('Common.Server.RAM')} <span className="text-stone-600">({t('Common.optional')})</span>
</Label> </Label>
<Input <Input
id="ram" id="ram"
@ -882,7 +884,7 @@ export default function Servers() {
</div> </div>
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="disk"> <Label htmlFor="disk">
{t('AddServer.Hardware.Disk')} <span className="text-stone-600">({t('optional')})</span> {t('Common.Server.Disk')} <span className="text-stone-600">({t('Common.optional')})</span>
</Label> </Label>
<Input <Input
id="disk" id="disk"
@ -902,11 +904,11 @@ export default function Servers() {
checked={host} checked={host}
onCheckedChange={(checked) => setHost(checked === true)} onCheckedChange={(checked) => setHost(checked === true)}
/> />
<Label htmlFor="hostCheckbox">{t('AddServer.Host.MarkAsHostServer')}</Label> <Label htmlFor="hostCheckbox">{t('Servers.AddServer.Host.MarkAsHostServer')}</Label>
</div> </div>
{!host && ( {!host && (
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label>{t('AddServer.Host.SelectHostServer')}</Label> <Label>{t('Servers.AddServer.Host.SelectHostServer')}</Label>
<Select <Select
value={hostServer?.toString()} value={hostServer?.toString()}
onValueChange={(value) => { onValueChange={(value) => {
@ -918,10 +920,10 @@ export default function Servers() {
}} }}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder={t('AddServer.Host.SelectHostServerPlaceholder')} /> <SelectValue placeholder={t('Servers.AddServer.Host.SelectHostServerPlaceholder')} />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="0">{t('AddServer.Host.NoHostServer')}</SelectItem> <SelectItem value="0">{t('Servers.AddServer.Host.NoHostServer')}</SelectItem>
{hostServers.map((server) => ( {hostServers.map((server) => (
<SelectItem key={server.id} value={server.id.toString()}> <SelectItem key={server.id} value={server.id.toString()}>
{server.name} {server.name}
@ -941,12 +943,12 @@ export default function Servers() {
checked={monitoring} checked={monitoring}
onCheckedChange={(checked) => setMonitoring(checked === true)} onCheckedChange={(checked) => setMonitoring(checked === true)}
/> />
<Label htmlFor="monitoringCheckbox">{t('AddServer.Monitoring.Enable')}</Label> <Label htmlFor="monitoringCheckbox">{t('Servers.AddServer.Monitoring.Enable')}</Label>
</div> </div>
{monitoring && ( {monitoring && (
<> <>
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="monitoringURL">{t('AddServer.Monitoring.URL')}</Label> <Label htmlFor="monitoringURL">{t('Servers.AddServer.Monitoring.URL')}</Label>
<Input <Input
id="monitoringURL" id="monitoringURL"
type="text" type="text"
@ -956,9 +958,9 @@ export default function Servers() {
/> />
</div> </div>
<div className="mt-4 p-4 border rounded-lg bg-muted"> <div className="mt-4 p-4 border rounded-lg bg-muted">
<h4 className="text-sm font-semibold mb-2">{t('AddServer.Monitoring.SetupTitle')}</h4> <h4 className="text-sm font-semibold mb-2">{t('Servers.AddServer.Monitoring.SetupTitle')}</h4>
<p className="text-sm text-muted-foreground mb-3"> <p className="text-sm text-muted-foreground mb-3">
{t('AddServer.Monitoring.SetupDescription')} {t('Servers.AddServer.Monitoring.SetupDescription')}
</p> </p>
<pre className="bg-background p-4 rounded-md text-sm overflow-x-auto"> <pre className="bg-background p-4 rounded-md text-sm overflow-x-auto">
<code>{`services: <code>{`services:
@ -983,8 +985,8 @@ export default function Servers() {
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>{t('cancel')}</AlertDialogCancel> <AlertDialogCancel>{t('Common.cancel')}</AlertDialogCancel>
<AlertDialogAction onClick={add}>{t('add')}</AlertDialogAction> <AlertDialogAction onClick={add}>{t('Common.add')}</AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>
@ -993,7 +995,7 @@ export default function Servers() {
<div className="flex flex-col gap-2 mb-4 pt-2"> <div className="flex flex-col gap-2 mb-4 pt-2">
<Input <Input
id="application-search" id="application-search"
placeholder="Type to search..." placeholder={t('Servers.Search.Placeholder')}
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
/> />
@ -1016,7 +1018,7 @@ export default function Servers() {
<StatusIndicator isOnline={server.online} /> <StatusIndicator isOnline={server.online} />
{server.online && server.uptime && ( {server.online && server.uptime && (
<span className="text-xs text-muted-foreground mt-1"> <span className="text-xs text-muted-foreground mt-1">
since {server.uptime} {t('Common.since', { date: server.uptime })}
</span> </span>
)} )}
</div> </div>
@ -1045,13 +1047,13 @@ export default function Servers() {
<div className="flex items-center gap-2 text-foreground/80"> <div className="flex items-center gap-2 text-foreground/80">
<MonitorCog className="h-4 w-4 text-muted-foreground" /> <MonitorCog className="h-4 w-4 text-muted-foreground" />
<span> <span>
<b>OS:</b> {server.os || "-"} <b>{t('Common.Server.OS')}:</b> {server.os || "-"}
</span> </span>
</div> </div>
<div className="flex items-center gap-2 text-foreground/80"> <div className="flex items-center gap-2 text-foreground/80">
<FileDigit className="h-4 w-4 text-muted-foreground" /> <FileDigit className="h-4 w-4 text-muted-foreground" />
<span> <span>
<b>IP:</b> {server.ip || "Not set"} <b>{t('Common.Server.IP')}:</b> {server.ip || t('Common.notSet')}
</span> </span>
</div> </div>
@ -1059,7 +1061,7 @@ export default function Servers() {
<div className="flex items-center gap-2 text-foreground/80"> <div className="flex items-center gap-2 text-foreground/80">
<LucideServer className="h-4 w-4 text-muted-foreground" /> <LucideServer className="h-4 w-4 text-muted-foreground" />
<span> <span>
<b>Host:</b> {getHostServerName(server.hostServer)} <b>{t('Common.Server.Host')}:</b> {getHostServerName(server.hostServer)}
</span> </span>
</div> </div>
)} )}
@ -1069,31 +1071,31 @@ export default function Servers() {
</div> </div>
<div className="col-span-full mb-2"> <div className="col-span-full mb-2">
<h4 className="text-sm font-semibold">Hardware Information</h4> <h4 className="text-sm font-semibold">{t('Servers.ServerCard.HardwareInformation')}</h4>
</div> </div>
<div className="flex items-center gap-2 text-foreground/80"> <div className="flex items-center gap-2 text-foreground/80">
<Cpu className="h-4 w-4 text-muted-foreground" /> <Cpu className="h-4 w-4 text-muted-foreground" />
<span> <span>
<b>CPU:</b> {server.cpu || "-"} <b>{t('Common.Server.CPU')}:</b> {server.cpu || "-"}
</span> </span>
</div> </div>
<div className="flex items-center gap-2 text-foreground/80"> <div className="flex items-center gap-2 text-foreground/80">
<Microchip className="h-4 w-4 text-muted-foreground" /> <Microchip className="h-4 w-4 text-muted-foreground" />
<span> <span>
<b>GPU:</b> {server.gpu || "-"} <b>{t('Common.Server.GPU')}:</b> {server.gpu || "-"}
</span> </span>
</div> </div>
<div className="flex items-center gap-2 text-foreground/80"> <div className="flex items-center gap-2 text-foreground/80">
<MemoryStick className="h-4 w-4 text-muted-foreground" /> <MemoryStick className="h-4 w-4 text-muted-foreground" />
<span> <span>
<b>RAM:</b> {server.ram || "-"} <b>{t('Common.Server.RAM')}:</b> {server.ram || "-"}
</span> </span>
</div> </div>
<div className="flex items-center gap-2 text-foreground/80"> <div className="flex items-center gap-2 text-foreground/80">
<HardDrive className="h-4 w-4 text-muted-foreground" /> <HardDrive className="h-4 w-4 text-muted-foreground" />
<span> <span>
<b>Disk:</b> {server.disk || "-"} <b>{t('Common.Server.Disk')}:</b> {server.disk || "-"}
</span> </span>
</div> </div>
@ -1104,15 +1106,15 @@ export default function Servers() {
</div> </div>
<div className="col-span-full"> <div className="col-span-full">
<h4 className="text-sm font-semibold mb-3">Resource Usage</h4> <h4 className="text-sm font-semibold mb-3">{t('Servers.ServerCard.ResourceUsage')}</h4>
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Cpu className="h-4 w-4 text-muted-foreground" /> <Cpu className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium">CPU</span> <span className="text-sm font-medium">{t('Common.Server.CPU')}</span>
</div> </div>
<span className="text-xs font-medium">{server.cpuUsage !== null && server.cpuUsage !== undefined ? `${server.cpuUsage}%` : "NO DATA"}</span> <span className="text-xs font-medium">{server.cpuUsage !== null && server.cpuUsage !== undefined ? `${server.cpuUsage}%` : t('Common.noData')}</span>
</div> </div>
<div className="h-2 w-full overflow-hidden rounded-full bg-secondary mt-1"> <div className="h-2 w-full overflow-hidden rounded-full bg-secondary mt-1">
<div <div
@ -1126,9 +1128,9 @@ export default function Servers() {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<MemoryStick className="h-4 w-4 text-muted-foreground" /> <MemoryStick className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium">RAM</span> <span className="text-sm font-medium">{t('Common.Server.RAM')}</span>
</div> </div>
<span className="text-xs font-medium">{server.ramUsage !== null && server.ramUsage !== undefined ? `${server.ramUsage}%` : "NO DATA"}</span> <span className="text-xs font-medium">{server.ramUsage !== null && server.ramUsage !== undefined ? `${server.ramUsage}%` : t('Common.noData')}</span>
</div> </div>
<div className="h-2 w-full overflow-hidden rounded-full bg-secondary mt-1"> <div className="h-2 w-full overflow-hidden rounded-full bg-secondary mt-1">
<div <div
@ -1142,9 +1144,9 @@ export default function Servers() {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<HardDrive className="h-4 w-4 text-muted-foreground" /> <HardDrive className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium">Disk</span> <span className="text-sm font-medium">{t('Common.Server.Disk')}</span>
</div> </div>
<span className="text-xs font-medium">{server.diskUsage !== null && server.diskUsage !== undefined ? `${server.diskUsage}%` : "NO DATA"}</span> <span className="text-xs font-medium">{server.diskUsage !== null && server.diskUsage !== undefined ? `${server.diskUsage}%` : t('Common.noData')}</span>
</div> </div>
<div className="h-2 w-full overflow-hidden rounded-full bg-secondary mt-1"> <div className="h-2 w-full overflow-hidden rounded-full bg-secondary mt-1">
<div <div
@ -1155,21 +1157,21 @@ export default function Servers() {
</div> </div>
<div> <div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Microchip className="h-4 w-4 text-muted-foreground" /> <Microchip className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium">GPU</span> <span className="text-sm font-medium">{t('Common.Server.GPU')}</span>
</div>
<span className="text-xs font-medium">
{server.online &&
server.gpuUsage &&
server.gpuUsage !== null &&
server.gpuUsage !== undefined &&
server.gpuUsage.toString() !== "0"
? `${server.gpuUsage}%`
: t('Common.noData')}
</span>
</div> </div>
<span className="text-xs font-medium">
{server.online &&
server.gpuUsage &&
server.gpuUsage !== null &&
server.gpuUsage !== undefined &&
server.gpuUsage.toString() !== "0"
? `${server.gpuUsage}%`
: "NO DATA"}
</span>
</div>
<div className="h-2 w-full overflow-hidden rounded-full bg-secondary mt-1"> <div className="h-2 w-full overflow-hidden rounded-full bg-secondary mt-1">
<div <div
className={`h-full ${server.gpuUsage && server.gpuUsage > 80 ? "bg-destructive" : server.gpuUsage && server.gpuUsage > 60 ? "bg-amber-500" : "bg-emerald-500"}`} className={`h-full ${server.gpuUsage && server.gpuUsage > 80 ? "bg-destructive" : server.gpuUsage && server.gpuUsage > 60 ? "bg-amber-500" : "bg-emerald-500"}`}
@ -1180,20 +1182,20 @@ export default function Servers() {
</div> </div>
<div className="mt-4"> <div className="mt-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Thermometer className="h-4 w-4 text-muted-foreground" /> <Thermometer className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium">Temp</span> <span className="text-sm font-medium">{t('Common.Server.Temperature')}</span>
</div>
<span className="text-xs font-medium">
{server.online &&
server.temp !== null &&
server.temp !== undefined &&
server.temp.toString() !== "0"
? `${server.temp}°C`
: t('Common.noData')}
</span>
</div> </div>
<span className="text-xs font-medium">
{server.online &&
server.temp !== null &&
server.temp !== undefined &&
server.temp.toString() !== "0"
? `${server.temp}°C`
: "NO DATA"}
</span>
</div>
<div className="h-2 w-full overflow-hidden rounded-full bg-secondary mt-1"> <div className="h-2 w-full overflow-hidden rounded-full bg-secondary mt-1">
<div <div
className={`h-full ${server.temp && server.temp > 80 ? "bg-destructive" : server.temp && server.temp > 60 ? "bg-amber-500" : "bg-emerald-500"}`} className={`h-full ${server.temp && server.temp > 80 ? "bg-destructive" : server.temp && server.temp > 60 ? "bg-amber-500" : "bg-emerald-500"}`}
@ -1214,7 +1216,7 @@ export default function Servers() {
<NextLink href={`/dashboard/servers/${server.id}`} className="flex-1"> <NextLink href={`/dashboard/servers/${server.id}`} className="flex-1">
<Button variant="outline" className="w-full"> <Button variant="outline" className="w-full">
<History className="h-4 w-4 mr-2" /> <History className="h-4 w-4 mr-2" />
View Details {t('Servers.ServerCard.ViewDetails')}
</Button> </Button>
</NextLink> </NextLink>
@ -1230,7 +1232,7 @@ export default function Servers() {
<LinkIcon className="h-4 w-4" /> <LinkIcon className="h-4 w-4" />
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent>Open Management URL</TooltipContent> <TooltipContent>{t('Servers.ServerCard.OpenManagementURL')}</TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
)} )}
@ -1254,7 +1256,7 @@ export default function Servers() {
<Pencil className="h-4 w-4" /> <Pencil className="h-4 w-4" />
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent>Edit server</TooltipContent> <TooltipContent>{t('Servers.ServerCard.EditServer')}</TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
@ -1270,7 +1272,7 @@ export default function Servers() {
</AlertDialogTrigger> </AlertDialogTrigger>
<AlertDialogContent> <AlertDialogContent>
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle>Hosted VMs</AlertDialogTitle> <AlertDialogTitle>{t('Servers.ServerCard.HostedVMs')}</AlertDialogTitle>
<AlertDialogDescription> <AlertDialogDescription>
{server.host && ( {server.host && (
<div className="mt-4"> <div className="mt-4">
@ -1325,21 +1327,21 @@ export default function Servers() {
</AlertDialogTrigger> </AlertDialogTrigger>
<AlertDialogContent className="max-w-[95vw] w-[600px] max-h-[90vh] overflow-y-auto"> <AlertDialogContent className="max-w-[95vw] w-[600px] max-h-[90vh] overflow-y-auto">
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle>Edit VM</AlertDialogTitle> <AlertDialogTitle>{t('Servers.EditServer.Title', { name: hostedVM.name })}</AlertDialogTitle>
<AlertDialogDescription> <AlertDialogDescription>
<Tabs defaultValue="general" className="w-full"> <Tabs defaultValue="general" className="w-full">
<TabsList className="w-full"> <TabsList className="w-full">
<TabsTrigger value="general">General</TabsTrigger> <TabsTrigger value="general">{t('Common.Server.Tabs.General')}</TabsTrigger>
<TabsTrigger value="hardware">Hardware</TabsTrigger> <TabsTrigger value="hardware">{t('Common.Server.Tabs.Hardware')}</TabsTrigger>
<TabsTrigger value="virtualization"> <TabsTrigger value="virtualization">
Host {t('Common.Server.Tabs.Host')}
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="general"> <TabsContent value="general">
<div className="space-y-4 pt-4"> <div className="space-y-4 pt-4">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="grid w-[calc(100%-52px)] items-center gap-1.5"> <div className="grid w-[calc(100%-52px)] items-center gap-1.5">
<Label htmlFor="editIcon">Icon</Label> <Label htmlFor="editIcon">{t('Servers.EditServer.General.Icon')}</Label>
<div className="space-y-2"> <div className="space-y-2">
<Select <Select
value={editIcon} value={editIcon}
@ -1348,7 +1350,7 @@ export default function Servers() {
} }
> >
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">
<SelectValue placeholder="Select an icon"> <SelectValue placeholder={t('Servers.AddServer.General.IconPlaceholder')}>
{editIcon && ( {editIcon && (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<DynamicIcon <DynamicIcon
@ -1362,7 +1364,7 @@ export default function Servers() {
</SelectTrigger> </SelectTrigger>
<SelectContent className="max-h-[300px]"> <SelectContent className="max-h-[300px]">
<Input <Input
placeholder="Search icons..." placeholder={t('Servers.AddServer.General.IconSearchPlaceholder')}
className="mb-2" className="mb-2"
onChange={(e) => { onChange={(e) => {
const iconElements = const iconElements =
@ -1426,7 +1428,7 @@ export default function Servers() {
</div> </div>
</div> </div>
<div className="grid w-[52px] items-center gap-1.5"> <div className="grid w-[52px] items-center gap-1.5">
<Label htmlFor="editIcon">Preview</Label> <Label htmlFor="editIcon">{t('Servers.AddServer.General.Preview')}</Label>
<div className="flex items-center justify-center"> <div className="flex items-center justify-center">
{editIcon && ( {editIcon && (
<DynamicIcon <DynamicIcon
@ -1438,7 +1440,7 @@ export default function Servers() {
</div> </div>
</div> </div>
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="editName">Name</Label> <Label htmlFor="editName">{t('Servers.EditServer.General.Name')}</Label>
<Input <Input
id="editName" id="editName"
type="text" type="text"
@ -1448,13 +1450,13 @@ export default function Servers() {
/> />
</div> </div>
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="editOs">Operating System</Label> <Label htmlFor="editOs">{t('Servers.EditServer.General.OperatingSystem')}</Label>
<Select <Select
value={editOs} value={editOs}
onValueChange={setEditOs} onValueChange={setEditOs}
> >
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">
<SelectValue placeholder="Select OS" /> <SelectValue placeholder={t('Servers.AddServer.General.OperatingSystemPlaceholder')} />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="Windows"> <SelectItem value="Windows">
@ -1466,7 +1468,7 @@ export default function Servers() {
</Select> </Select>
</div> </div>
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="editIp">IP Adress</Label> <Label htmlFor="editIp">{t('Servers.EditServer.General.IPAddress')}</Label>
<Input <Input
id="editIp" id="editIp"
type="text" type="text"
@ -1476,7 +1478,7 @@ export default function Servers() {
/> />
</div> </div>
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="editUrl">Management URL</Label> <Label htmlFor="editUrl">{t('Servers.EditServer.General.ManagementURL')}</Label>
<Input <Input
id="editUrl" id="editUrl"
type="text" type="text"
@ -1491,7 +1493,7 @@ export default function Servers() {
<TabsContent value="hardware"> <TabsContent value="hardware">
<div className="space-y-4 pt-4"> <div className="space-y-4 pt-4">
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="editCpu">CPU</Label> <Label htmlFor="editCpu">{t('Servers.EditServer.Hardware.CPU')}</Label>
<Input <Input
id="editCpu" id="editCpu"
value={editCpu} value={editCpu}
@ -1499,7 +1501,7 @@ export default function Servers() {
/> />
</div> </div>
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="editGpu">GPU</Label> <Label htmlFor="editGpu">{t('Servers.EditServer.Hardware.GPU')}</Label>
<Input <Input
id="editGpu" id="editGpu"
value={editGpu} value={editGpu}
@ -1507,7 +1509,7 @@ export default function Servers() {
/> />
</div> </div>
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="editRam">RAM</Label> <Label htmlFor="editRam">{t('Servers.EditServer.Hardware.RAM')}</Label>
<Input <Input
id="editRam" id="editRam"
value={editRam} value={editRam}
@ -1515,7 +1517,7 @@ export default function Servers() {
/> />
</div> </div>
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="editDisk">Disk</Label> <Label htmlFor="editDisk">{t('Servers.EditServer.Hardware.Disk')}</Label>
<Input <Input
id="editDisk" id="editDisk"
value={editDisk} value={editDisk}
@ -1539,18 +1541,18 @@ export default function Servers() {
} }
/> />
<Label htmlFor="editHostCheckbox"> <Label htmlFor="editHostCheckbox">
Mark as host server {t('Servers.EditServer.Host.MarkAsHostServer')}
{server.hostedVMs && {server.hostedVMs &&
server.hostedVMs.length > 0 && ( server.hostedVMs.length > 0 && (
<span className="text-muted-foreground text-sm ml-2"> <span className="text-muted-foreground text-sm ml-2">
(Cannot be disabled while hosting VMs) ({t('Servers.EditServer.Host.CannotDisableHost')})
</span> </span>
)} )}
</Label> </Label>
</div> </div>
{!editHost && ( {!editHost && (
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label>Host Server</Label> <Label>{t('Servers.EditServer.Host.SelectHostServer')}</Label>
<Select <Select
value={editHostServer?.toString()} value={editHostServer?.toString()}
onValueChange={(value) => { onValueChange={(value) => {
@ -1562,10 +1564,10 @@ export default function Servers() {
}} }}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Select a host server" /> <SelectValue placeholder={t('Servers.AddServer.Host.SelectHostServerPlaceholder')} />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="0">No host server</SelectItem> <SelectItem value="0">{t('Servers.AddServer.Host.NoHostServer')}</SelectItem>
{hostServers {hostServers
.filter( .filter(
(server) => server.id !== editId, (server) => server.id !== editId,
@ -1585,8 +1587,8 @@ export default function Servers() {
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogCancel>{t('Common.cancel')}</AlertDialogCancel>
<Button onClick={edit}>Save</Button> <Button onClick={edit}>{t('Servers.EditServer.Save')}</Button>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>
@ -1601,43 +1603,43 @@ export default function Servers() {
<div className="flex items-center gap-2 text-foreground/80"> <div className="flex items-center gap-2 text-foreground/80">
<MonitorCog className="h-4 w-4 text-muted-foreground" /> <MonitorCog className="h-4 w-4 text-muted-foreground" />
<span> <span>
<b>OS:</b> {hostedVM.os || "-"} <b>{t('Common.Server.OS')}:</b> {hostedVM.os || "-"}
</span> </span>
</div> </div>
<div className="flex items-center gap-2 text-foreground/80"> <div className="flex items-center gap-2 text-foreground/80">
<FileDigit className="h-4 w-4 text-muted-foreground" /> <FileDigit className="h-4 w-4 text-muted-foreground" />
<span> <span>
<b>IP:</b> {hostedVM.ip || "Not set"} <b>{t('Common.Server.IP')}:</b> {hostedVM.ip || t('Common.notSet')}
</span> </span>
</div> </div>
</div> </div>
<div className="col-span-full mb-2"> <div className="col-span-full mb-2">
<h4 className="text-sm font-semibold">Hardware Information</h4> <h4 className="text-sm font-semibold">{t('Servers.ServerCard.HardwareInformation')}</h4>
</div> </div>
<div className="flex items-center gap-2 text-foreground/80"> <div className="flex items-center gap-2 text-foreground/80">
<Cpu className="h-4 w-4 text-muted-foreground" /> <Cpu className="h-4 w-4 text-muted-foreground" />
<span> <span>
<b>CPU:</b> {hostedVM.cpu || "-"} <b>{t('Common.Server.CPU')}:</b> {hostedVM.cpu || "-"}
</span> </span>
</div> </div>
<div className="flex items-center gap-2 text-foreground/80"> <div className="flex items-center gap-2 text-foreground/80">
<Microchip className="h-4 w-4 text-muted-foreground" /> <Microchip className="h-4 w-4 text-muted-foreground" />
<span> <span>
<b>GPU:</b> {hostedVM.gpu || "-"} <b>{t('Common.Server.GPU')}:</b> {hostedVM.gpu || "-"}
</span> </span>
</div> </div>
<div className="flex items-center gap-2 text-foreground/80"> <div className="flex items-center gap-2 text-foreground/80">
<MemoryStick className="h-4 w-4 text-muted-foreground" /> <MemoryStick className="h-4 w-4 text-muted-foreground" />
<span> <span>
<b>RAM:</b> {hostedVM.ram || "-"} <b>{t('Common.Server.RAM')}:</b> {hostedVM.ram || "-"}
</span> </span>
</div> </div>
<div className="flex items-center gap-2 text-foreground/80"> <div className="flex items-center gap-2 text-foreground/80">
<HardDrive className="h-4 w-4 text-muted-foreground" /> <HardDrive className="h-4 w-4 text-muted-foreground" />
<span> <span>
<b>Disk:</b> {hostedVM.disk || "-"} <b>{t('Common.Server.Disk')}:</b> {hostedVM.disk || "-"}
</span> </span>
</div> </div>
@ -1652,7 +1654,7 @@ export default function Servers() {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Cpu className="h-4 w-4 text-muted-foreground" /> <Cpu className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium">CPU</span> <span className="text-sm font-medium">{t('Common.Server.CPU')}</span>
</div> </div>
<span className="text-xs font-medium"> <span className="text-xs font-medium">
{hostedVM.cpuUsage || 0}% {hostedVM.cpuUsage || 0}%
@ -1670,7 +1672,7 @@ export default function Servers() {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<MemoryStick className="h-4 w-4 text-muted-foreground" /> <MemoryStick className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium">RAM</span> <span className="text-sm font-medium">{t('Common.Server.RAM')}</span>
</div> </div>
<span className="text-xs font-medium"> <span className="text-xs font-medium">
{hostedVM.ramUsage || 0}% {hostedVM.ramUsage || 0}%
@ -1688,7 +1690,7 @@ export default function Servers() {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<HardDrive className="h-4 w-4 text-muted-foreground" /> <HardDrive className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium">Disk</span> <span className="text-sm font-medium">{t('Common.Server.Disk')}</span>
</div> </div>
<span className="text-xs font-medium"> <span className="text-xs font-medium">
{hostedVM.diskUsage || 0}% {hostedVM.diskUsage || 0}%
@ -1713,12 +1715,12 @@ export default function Servers() {
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>Close</AlertDialogCancel> <AlertDialogCancel>{t('Common.cancel')}</AlertDialogCancel>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent>View VMs ({server.hostedVMs.length})</TooltipContent> <TooltipContent>{t('Servers.ServerCard.HostedVMs')} ({server.hostedVMs.length})</TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
)} )}
@ -1734,27 +1736,27 @@ export default function Servers() {
</AlertDialogTrigger> </AlertDialogTrigger>
<AlertDialogContent className="max-w-[95vw] w-[600px] max-h-[90vh] overflow-y-auto"> <AlertDialogContent className="max-w-[95vw] w-[600px] max-h-[90vh] overflow-y-auto">
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle>Edit {server.name}</AlertDialogTitle> <AlertDialogTitle>{t('Servers.EditServer.Title', { name: server.name })}</AlertDialogTitle>
<AlertDialogDescription> <AlertDialogDescription>
<Tabs defaultValue="general" className="w-full"> <Tabs defaultValue="general" className="w-full">
<TabsList className="w-full"> <TabsList className="w-full">
<TabsTrigger value="general">General</TabsTrigger> <TabsTrigger value="general">{t('Common.Server.Tabs.General')}</TabsTrigger>
<TabsTrigger value="hardware">Hardware</TabsTrigger> <TabsTrigger value="hardware">{t('Common.Server.Tabs.Hardware')}</TabsTrigger>
<TabsTrigger value="virtualization">Host</TabsTrigger> <TabsTrigger value="virtualization">{t('Common.Server.Tabs.Host')}</TabsTrigger>
<TabsTrigger value="monitoring">Monitoring</TabsTrigger> <TabsTrigger value="monitoring">{t('Common.Server.Tabs.Monitoring')}</TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="general"> <TabsContent value="general">
<div className="space-y-4 pt-4"> <div className="space-y-4 pt-4">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="grid w-[calc(100%-52px)] items-center gap-1.5"> <div className="grid w-[calc(100%-52px)] items-center gap-1.5">
<Label htmlFor="editIcon">Icon</Label> <Label htmlFor="editIcon">{t('Servers.EditServer.General.Icon')}</Label>
<div className="space-y-2"> <div className="space-y-2">
<Select <Select
value={editIcon} value={editIcon}
onValueChange={(value) => setEditIcon(value)} onValueChange={(value) => setEditIcon(value)}
> >
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">
<SelectValue placeholder="Select an icon"> <SelectValue placeholder={t('Servers.AddServer.General.IconPlaceholder')}>
{editIcon && ( {editIcon && (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<DynamicIcon name={editIcon as any} size={18} /> <DynamicIcon name={editIcon as any} size={18} />
@ -1765,7 +1767,7 @@ export default function Servers() {
</SelectTrigger> </SelectTrigger>
<SelectContent className="max-h-[300px]"> <SelectContent className="max-h-[300px]">
<Input <Input
placeholder="Search icons..." placeholder={t('Servers.AddServer.General.IconSearchPlaceholder')}
className="mb-2" className="mb-2"
onChange={(e) => { onChange={(e) => {
const iconElements = document.querySelectorAll( const iconElements = document.querySelectorAll(
@ -1811,14 +1813,14 @@ export default function Servers() {
</div> </div>
</div> </div>
<div className="grid w-[52px] items-center gap-1.5"> <div className="grid w-[52px] items-center gap-1.5">
<Label htmlFor="editIcon">Preview</Label> <Label htmlFor="editIcon">{t('Servers.AddServer.General.Preview')}</Label>
<div className="flex items-center justify-center"> <div className="flex items-center justify-center">
{editIcon && <DynamicIcon name={editIcon as any} size={36} />} {editIcon && <DynamicIcon name={editIcon as any} size={36} />}
</div> </div>
</div> </div>
</div> </div>
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="editName">Name</Label> <Label htmlFor="editName">{t('Servers.EditServer.General.Name')}</Label>
<Input <Input
id="editName" id="editName"
type="text" type="text"
@ -1828,11 +1830,11 @@ export default function Servers() {
</div> </div>
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="editOs"> <Label htmlFor="editOs">
Operating System <span className="text-stone-600">(optional)</span> {t('Servers.EditServer.General.OperatingSystem')} <span className="text-stone-600">({t('Common.optional')})</span>
</Label> </Label>
<Select value={editOs} onValueChange={(value) => setEditOs(value)}> <Select value={editOs} onValueChange={(value) => setEditOs(value)}>
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">
<SelectValue placeholder="Select OS" /> <SelectValue placeholder={t('Servers.AddServer.General.OperatingSystemPlaceholder')} />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="Windows">Windows</SelectItem> <SelectItem value="Windows">Windows</SelectItem>
@ -1843,7 +1845,7 @@ export default function Servers() {
</div> </div>
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="editIp"> <Label htmlFor="editIp">
IP Address <span className="text-stone-600">(optional)</span> {t('Servers.EditServer.General.IPAddress')} <span className="text-stone-600">({t('Common.optional')})</span>
</Label> </Label>
<Input <Input
id="editIp" id="editIp"
@ -1854,7 +1856,7 @@ export default function Servers() {
</div> </div>
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="editUrl"> <Label htmlFor="editUrl">
Management URL <span className="text-stone-600">(optional)</span> {t('Servers.EditServer.General.ManagementURL')} <span className="text-stone-600">({t('Common.optional')})</span>
</Label> </Label>
<Input <Input
id="editUrl" id="editUrl"
@ -1868,7 +1870,7 @@ export default function Servers() {
<TabsContent value="hardware"> <TabsContent value="hardware">
<div className="space-y-4 pt-4"> <div className="space-y-4 pt-4">
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="editCpu">CPU</Label> <Label htmlFor="editCpu">{t('Servers.EditServer.Hardware.CPU')}</Label>
<Input <Input
id="editCpu" id="editCpu"
value={editCpu} value={editCpu}
@ -1876,7 +1878,7 @@ export default function Servers() {
/> />
</div> </div>
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="editGpu">GPU</Label> <Label htmlFor="editGpu">{t('Servers.EditServer.Hardware.GPU')}</Label>
<Input <Input
id="editGpu" id="editGpu"
value={editGpu} value={editGpu}
@ -1884,7 +1886,7 @@ export default function Servers() {
/> />
</div> </div>
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="editRam">RAM</Label> <Label htmlFor="editRam">{t('Servers.EditServer.Hardware.RAM')}</Label>
<Input <Input
id="editRam" id="editRam"
value={editRam} value={editRam}
@ -1892,7 +1894,7 @@ export default function Servers() {
/> />
</div> </div>
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="editDisk">Disk</Label> <Label htmlFor="editDisk">{t('Servers.EditServer.Hardware.Disk')}</Label>
<Input <Input
id="editDisk" id="editDisk"
value={editDisk} value={editDisk}
@ -1911,11 +1913,11 @@ export default function Servers() {
setEditHost(checked === true) setEditHost(checked === true)
} }
/> />
<Label htmlFor="editHostCheckbox">Mark as host server</Label> <Label htmlFor="editHostCheckbox">{t('Servers.EditServer.Host.MarkAsHostServer')}</Label>
</div> </div>
{!editHost && ( {!editHost && (
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label>Host Server</Label> <Label>{t('Servers.EditServer.Host.SelectHostServer')}</Label>
<Select <Select
value={editHostServer?.toString()} value={editHostServer?.toString()}
onValueChange={(value) => { onValueChange={(value) => {
@ -1927,10 +1929,10 @@ export default function Servers() {
}} }}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Select a host server" /> <SelectValue placeholder={t('Servers.AddServer.Host.SelectHostServerPlaceholder')} />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="0">No host server</SelectItem> <SelectItem value="0">{t('Servers.AddServer.Host.NoHostServer')}</SelectItem>
{hostServers {hostServers
.filter( .filter(
(server) => server.id !== editId, (server) => server.id !== editId,
@ -1954,12 +1956,12 @@ export default function Servers() {
checked={editMonitoring} checked={editMonitoring}
onCheckedChange={(checked) => setEditMonitoring(checked === true)} onCheckedChange={(checked) => setEditMonitoring(checked === true)}
/> />
<Label htmlFor="editMonitoringCheckbox">Enable monitoring</Label> <Label htmlFor="editMonitoringCheckbox">{t('Servers.EditServer.Monitoring.Enable')}</Label>
</div> </div>
{editMonitoring && ( {editMonitoring && (
<> <>
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="editMonitoringURL">Monitoring URL</Label> <Label htmlFor="editMonitoringURL">{t('Servers.EditServer.Monitoring.URL')}</Label>
<Input <Input
id="editMonitoringURL" id="editMonitoringURL"
type="text" type="text"
@ -1969,9 +1971,9 @@ export default function Servers() {
/> />
</div> </div>
<div className="mt-4 p-4 border rounded-lg bg-muted"> <div className="mt-4 p-4 border rounded-lg bg-muted">
<h4 className="text-sm font-semibold mb-2">Required Server Setup</h4> <h4 className="text-sm font-semibold mb-2">{t('Servers.EditServer.Monitoring.SetupTitle')}</h4>
<p className="text-sm text-muted-foreground mb-3"> <p className="text-sm text-muted-foreground mb-3">
To enable monitoring, you need to install Glances on your server. Here's an example Docker Compose configuration: {t('Servers.EditServer.Monitoring.SetupDescription')}
</p> </p>
<pre className="bg-background p-4 rounded-md text-sm"> <pre className="bg-background p-4 rounded-md text-sm">
<code>{`services: <code>{`services:
@ -1996,8 +1998,8 @@ export default function Servers() {
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogCancel>{t('Common.cancel')}</AlertDialogCancel>
<Button onClick={edit}>Save</Button> <Button onClick={edit}>{t('Servers.EditServer.Save')}</Button>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>
@ -2013,24 +2015,24 @@ export default function Servers() {
</AlertDialogTrigger> </AlertDialogTrigger>
<AlertDialogContent> <AlertDialogContent>
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle>Delete {server.name}</AlertDialogTitle> <AlertDialogTitle>{t('Servers.ServerCard.DeleteConfirmation.Title', { name: server.name })}</AlertDialogTitle>
<AlertDialogDescription> <AlertDialogDescription>
Are you sure you want to delete this server? This action cannot be undone. {t('Servers.ServerCard.DeleteConfirmation.Description')}
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogCancel>{t('Servers.ServerCard.DeleteConfirmation.Cancel')}</AlertDialogCancel>
<AlertDialogAction <AlertDialogAction
className="bg-destructive text-destructive-foreground hover:bg-destructive/90" className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
onClick={() => deleteApplication(server.id)} onClick={() => deleteApplication(server.id)}
> >
Delete {t('Servers.ServerCard.DeleteConfirmation.Delete')}
</AlertDialogAction> </AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent>Delete server</TooltipContent> <TooltipContent>{t('Servers.ServerCard.DeleteServer')}</TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
</div> </div>
@ -2071,8 +2073,12 @@ export default function Servers() {
<div className="flex justify-between items-center mb-2"> <div className="flex justify-between items-center mb-2">
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground">
{totalItems > 0 {totalItems > 0
? `Showing ${((currentPage - 1) * itemsPerPage) + 1}-${Math.min(currentPage * itemsPerPage, totalItems)} of ${totalItems} servers` ? t('Servers.Pagination.Showing', {
: "No servers found"} start: ((currentPage - 1) * itemsPerPage) + 1,
end: Math.min(currentPage * itemsPerPage, totalItems),
total: totalItems
})
: t('Servers.Pagination.NoServers')}
</div> </div>
</div> </div>
<Pagination> <Pagination>

View File

@ -1,114 +1,178 @@
{ {
"Sidebar": { "Common": {
"Main Navigation": "Main Navigation", "ChangeView": "Change View",
"Dashboard": "Dashboard", "ListView": "List View",
"My Infrastructure": "My Infrastructure", "GridView": "Grid View",
"Servers": "Servers", "optional": "optional",
"Applications": "Applications", "cancel": "Cancel",
"Uptime": "Uptime", "add": "Add",
"Network": "Network", "since": "since {date}",
"Settings": "Settings", "notSet": "Not set",
"Logout": "Logout" "noData": "No data",
}, "ItemsPerPage": {
"Home": { "items": "items",
"TitleUnder": "Dashboard to manage your entire server infrastructure", "item": "item",
"LoginCardTitle": "Login", "4": "4 items",
"LoginCardDescription": "Enter your credentials to continue", "6": "6 items",
"AuthenticationError": "Authentication Error", "10": "10 items",
"Email": "Email", "15": "15 items",
"Password": "Password", "20": "20 items",
"SigninButton": "Sign in", "25": "25 items",
"SigninButtonSigningIn": "Signing in..." "Custom": "Custom (1-100)"
}, },
"Dashboard": { "Server": {
"Title": "Dashboard", "CPU": "CPU",
"Servers": { "GPU": "GPU",
"Title": "Servers", "RAM": "RAM",
"Description": "Physical and virtual servers overview", "Disk": "Disk",
"PhysicalServers": "Physical Servers", "OS": "OS",
"VirtualServers": "Virtual Servers", "IP": "IP",
"ManageServers": "Manage Servers" "Host": "Host",
}, "Temperature": "Temperature",
"Applications": {
"Title": "Applications",
"Description": "Manage your deployed applications",
"OnlineApplications": "Running Applications",
"ViewAllApplications": "View all applications"
},
"Uptime": {
"Title": "Uptime",
"Description": "Monitor your service availability",
"OnlineApplications": "Online Applications",
"ViewUptimeMetrics": "View uptime metrics"
},
"Network": {
"Title": "Network",
"Description": "Manage network configuration",
"ActiveConnections": "Active Connections",
"ViewNetworkDetails": "View network details"
}
},
"Servers": {
"MyInfrastructure": "My Infrastructure",
"Title": "Servers",
"YourServers": "Your Servers",
"ChangeView": "Change View",
"ListView": "List View",
"GridView": "Grid View",
"ItemsPerPage": {
"items": "items",
"item": "item",
"4": "4 items",
"6": "6 items",
"10": "10 items",
"15": "15 items",
"20": "20 items",
"25": "25 items",
"Custom": "Custom (1-100)"
},
"Tabs": { "Tabs": {
"General": "General", "General": "General",
"Hardware": "Hardware", "Hardware": "Hardware",
"Host": "Host", "Host": "Host",
"Monitoring": "Monitoring" "Monitoring": "Monitoring"
},
"optional": "optional",
"cancel": "Cancel",
"add": "Add",
"AddServer": {
"Title": "Add a server",
"General": {
"Title": "Add a server",
"CopyServer": "Copy server",
"Icon": "Icon",
"IconPlaceholder": "Select an icon",
"IconSearchPlaceholder": "Search icons...",
"Preview": "Preview",
"Name": "Name",
"OperatingSystem": "Operating System",
"OperatingSystemPlaceholder": "Select OS",
"IPAdress": "IP Adress",
"ManagementURL": "Management URL",
"ManagementURLTooltip": "Link to a web interface (e.g. Proxmox or Portainer) with which the server can be managed"
},
"Hardware": {
"CPU": "CPU",
"GPU": "GPU",
"RAM": "RAM",
"Storage": "Storage"
},
"Host": {
"MarkAsHostServer": "Mark as host server",
"SelectHostServer": "Select a host server",
"SelectHostServerPlaceholder": "Select a host server",
"NoHostServer": "No host server"
},
"Monitoring": {
"Enable": "Enable monitoring",
"URL": "Monitoring URL",
"SetupTitle": "Required Server Setup",
"SetupDescription": "To enable monitoring, you need to install Glances on your server. Here's an example Docker Compose configuration:"
}
} }
}
},
"Sidebar": {
"Main Navigation": "Main Navigation",
"Dashboard": "Dashboard",
"My Infrastructure": "My Infrastructure",
"Servers": "Servers",
"Applications": "Applications",
"Uptime": "Uptime",
"Network": "Network",
"Settings": "Settings",
"Logout": "Logout"
},
"Home": {
"TitleUnder": "Dashboard to manage your entire server infrastructure",
"LoginCardTitle": "Login",
"LoginCardDescription": "Enter your credentials to continue",
"AuthenticationError": "Authentication Error",
"Email": "Email",
"Password": "Password",
"SigninButton": "Sign in",
"SigninButtonSigningIn": "Signing in..."
},
"Dashboard": {
"Title": "Dashboard",
"Servers": {
"Title": "Servers",
"Description": "Physical and virtual servers overview",
"PhysicalServers": "Physical Servers",
"VirtualServers": "Virtual Servers",
"ManageServers": "Manage Servers"
},
"Applications": {
"Title": "Applications",
"Description": "Manage your deployed applications",
"OnlineApplications": "Running Applications",
"ViewAllApplications": "View all applications"
},
"Uptime": {
"Title": "Uptime",
"Description": "Monitor your service availability",
"OnlineApplications": "Online Applications",
"ViewUptimeMetrics": "View uptime metrics"
},
"Network": {
"Title": "Network",
"Description": "Manage network configuration",
"ActiveConnections": "Active Connections",
"ViewNetworkDetails": "View network details"
}
},
"Servers": {
"Title": "Servers",
"MyInfrastructure": "My Infrastructure",
"YourServers": "Your Servers",
"AddServer": {
"Title": "Add a server",
"General": {
"Title": "Add a server",
"CopyServer": "Copy server",
"Icon": "Icon",
"IconPlaceholder": "Select an icon",
"IconSearchPlaceholder": "Search icons...",
"Preview": "Preview",
"Name": "Name",
"OperatingSystem": "Operating System",
"OperatingSystemPlaceholder": "Select OS",
"IPAdress": "IP Adress",
"ManagementURL": "Management URL",
"ManagementURLTooltip": "Link to a web interface (e.g. Proxmox or Portainer) with which the server can be managed"
},
"Host": {
"MarkAsHostServer": "Mark as host server",
"SelectHostServer": "Select a host server",
"SelectHostServerPlaceholder": "Select a host server",
"NoHostServer": "No host server"
},
"Monitoring": {
"Enable": "Enable monitoring",
"URL": "Monitoring URL",
"SetupTitle": "Required Server Setup",
"SetupDescription": "To enable monitoring, you need to install Glances on your server. Here's an example Docker Compose configuration:"
}
},
"ServerCard": {
"HardwareInformation": "Hardware Information",
"ViewDetails": "View Details",
"OpenManagementURL": "Open Management URL",
"EditServer": "Edit Server",
"DeleteServer": "Delete Server",
"HostedVMs": "Hosted VMs",
"ResourceUsage": "Resource Usage",
"DeleteConfirmation": {
"Title": "Delete {name}",
"Description": "Are you sure you want to delete this server? This action cannot be undone.",
"Cancel": "Cancel",
"Delete": "Delete"
}
},
"EditServer": {
"Title": "Edit {name}",
"Save": "Save",
"General": {
"Title": "General",
"Icon": "Icon",
"Name": "Name",
"OperatingSystem": "Operating System",
"IPAddress": "IP Address",
"ManagementURL": "Management URL"
},
"Hardware": {
"Title": "Hardware",
"CPU": "CPU",
"GPU": "GPU",
"RAM": "RAM",
"Disk": "Disk"
},
"Host": {
"Title": "Host",
"MarkAsHostServer": "Mark as host server",
"CannotDisableHost": "Cannot be disabled while hosting VMs",
"SelectHostServer": "Select a host server"
},
"Monitoring": {
"Title": "Monitoring",
"Enable": "Enable monitoring",
"URL": "Monitoring URL",
"SetupTitle": "Required Server Setup",
"SetupDescription": "To enable monitoring, you need to install Glances on your server. Here's an example Docker Compose configuration:"
}
},
"Pagination": {
"Showing": "Showing {start}-{end} of {total} servers",
"NoServers": "No servers found"
},
"Search": {
"Placeholder": "Type to search..."
}
} }
} }