Servers.tsx i18n 1/2

This commit is contained in:
headlessdev 2025-04-28 23:50:18 +02:00
parent eff7901b67
commit 58e2466875
2 changed files with 114 additions and 68 deletions

View File

@ -66,6 +66,7 @@ import NextLink from "next/link"
import { Toaster } from "@/components/ui/sonner" import { Toaster } from "@/components/ui/sonner"
import { toast } from "sonner" import { toast } from "sonner"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { useTranslations } from "next-intl"
interface ServerHistory { interface ServerHistory {
labels: string[]; labels: string[];
@ -122,7 +123,8 @@ interface MonitoringData {
temp?: number temp?: number
} }
export default function Dashboard() { export default function Servers() {
const t = useTranslations('Servers')
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>("")
@ -465,18 +467,14 @@ export default function Dashboard() {
}; };
}, []); }, []);
// Handler für benutzerdefinierte Zahleneingaben mit Verzögerung
const handleItemsPerPageChange = (value: string) => { const handleItemsPerPageChange = (value: string) => {
// Bestehenden Timer löschen
if (debounceTimerRef.current) { if (debounceTimerRef.current) {
clearTimeout(debounceTimerRef.current); clearTimeout(debounceTimerRef.current);
} }
// Neuen Timer setzen
debounceTimerRef.current = setTimeout(() => { debounceTimerRef.current = setTimeout(() => {
const newItemsPerPage = parseInt(value); const newItemsPerPage = parseInt(value);
// Sicherstellen, dass der Wert im gültigen Bereich liegt
if (isNaN(newItemsPerPage) || newItemsPerPage < 1) { if (isNaN(newItemsPerPage) || newItemsPerPage < 1) {
toast.error("Bitte eine Zahl zwischen 1 und 100 eingeben"); toast.error("Bitte eine Zahl zwischen 1 und 100 eingeben");
return; return;
@ -485,37 +483,31 @@ export default function Dashboard() {
const validatedValue = Math.min(Math.max(newItemsPerPage, 1), 100); const validatedValue = Math.min(Math.max(newItemsPerPage, 1), 100);
setItemsPerPage(validatedValue); setItemsPerPage(validatedValue);
setCurrentPage(1); // Zurück zur ersten Seite setCurrentPage(1);
Cookies.set("itemsPerPage-servers", String(validatedValue), { Cookies.set("itemsPerPage-servers", String(validatedValue), {
expires: 365, expires: 365,
path: "/", path: "/",
sameSite: "strict", sameSite: "strict",
}); });
// Daten mit neuer Paginierung abrufen
getServers(); getServers();
}, 600); // 600ms Verzögerung für bessere Eingabe mehrziffriger Zahlen }, 600);
}; };
// Handler für voreingestellte Werte aus dem Dropdown
const handlePresetItemsPerPageChange = (value: string) => { const handlePresetItemsPerPageChange = (value: string) => {
// Für voreingestellte Werte sofort anwenden
const newItemsPerPage = parseInt(value); const newItemsPerPage = parseInt(value);
// Nur Standardwerte hier verarbeiten
if ([4, 6, 10, 15, 20, 25].includes(newItemsPerPage)) { if ([4, 6, 10, 15, 20, 25].includes(newItemsPerPage)) {
setItemsPerPage(newItemsPerPage); setItemsPerPage(newItemsPerPage);
setCurrentPage(1); // Zurück zur ersten Seite setCurrentPage(1);
Cookies.set("itemsPerPage-servers", String(newItemsPerPage), { Cookies.set("itemsPerPage-servers", String(newItemsPerPage), {
expires: 365, expires: 365,
path: "/", path: "/",
sameSite: "strict", sameSite: "strict",
}); });
// Daten mit neuer Paginierung abrufen
getServers(); getServers();
} else { } else {
// Für benutzerdefinierte Werte den verzögerten Handler verwenden
handleItemsPerPageChange(value); handleItemsPerPageChange(value);
} }
}; };
@ -535,11 +527,11 @@ export default function Dashboard() {
</BreadcrumbItem> </BreadcrumbItem>
<BreadcrumbSeparator className="hidden md:block" /> <BreadcrumbSeparator className="hidden md:block" />
<BreadcrumbItem> <BreadcrumbItem>
<BreadcrumbPage>My Infrastructure</BreadcrumbPage> <BreadcrumbPage>{t('MyInfrastructure')}</BreadcrumbPage>
</BreadcrumbItem> </BreadcrumbItem>
<BreadcrumbSeparator className="hidden md:block" /> <BreadcrumbSeparator className="hidden md:block" />
<BreadcrumbItem> <BreadcrumbItem>
<BreadcrumbPage>Servers</BreadcrumbPage> <BreadcrumbPage>{t('Servers')}</BreadcrumbPage>
</BreadcrumbItem> </BreadcrumbItem>
</BreadcrumbList> </BreadcrumbList>
</Breadcrumb> </Breadcrumb>
@ -548,11 +540,11 @@ export default function Dashboard() {
<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">Your Servers</span> <span className="text-3xl font-bold">{t('YourServers')}</span>
<div className="flex gap-2"> <div className="flex gap-2">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="outline" size="icon" title="Change view"> <Button variant="outline" size="icon" title={t('ChangeView')}>
{isGridLayout ? ( {isGridLayout ? (
<LayoutGrid className="h-4 w-4" /> <LayoutGrid className="h-4 w-4" />
) : ( ) : (
@ -562,10 +554,10 @@ export default function Dashboard() {
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => toggleLayout(false)}> <DropdownMenuItem onClick={() => toggleLayout(false)}>
<List className="h-4 w-4 mr-2" /> List View <List className="h-4 w-4 mr-2" /> {t('ListView')}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => toggleLayout(true)}> <DropdownMenuItem onClick={() => toggleLayout(true)}>
<LayoutGrid className="h-4 w-4 mr-2" /> Grid View <LayoutGrid className="h-4 w-4 mr-2" /> {t('GridView')}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
@ -581,23 +573,23 @@ export default function Dashboard() {
> >
<SelectTrigger className="w-[140px]"> <SelectTrigger className="w-[140px]">
<SelectValue> <SelectValue>
{itemsPerPage} {itemsPerPage === 1 ? 'item' : 'items'} {itemsPerPage} {itemsPerPage === 1 ? t('ItemsPerPage.item') : t('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 ? 'item' : 'items'} (custom) {itemsPerPage} {itemsPerPage === 1 ? t('ItemsPerPage.item') : t('ItemsPerPage.items')} ({t('ItemsPerPage.Custom')})
</SelectItem> </SelectItem>
) : null} ) : null}
<SelectItem value="4">4 items</SelectItem> <SelectItem value="4">{t('4')}</SelectItem>
<SelectItem value="6">6 items</SelectItem> <SelectItem value="6">{t('6')}</SelectItem>
<SelectItem value="10">10 items</SelectItem> <SelectItem value="10">{t('10')}</SelectItem>
<SelectItem value="15">15 items</SelectItem> <SelectItem value="15">{t('15')}</SelectItem>
<SelectItem value="20">20 items</SelectItem> <SelectItem value="20">{t('20')}</SelectItem>
<SelectItem value="25">25 items</SelectItem> <SelectItem value="25">{t('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">Custom (1-100)</Label> <Label htmlFor="custom-items" className="text-xs font-medium">{t('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"
@ -608,8 +600,6 @@ export default function Dashboard() {
className="h-8" className="h-8"
defaultValue={itemsPerPage} defaultValue={itemsPerPage}
onChange={(e) => { onChange={(e) => {
// Änderung nicht sofort anwenden während des Tippens
// Nur visuelles Feedback für die Validierung
const value = parseInt(e.target.value); const value = parseInt(e.target.value);
if (isNaN(value) || value < 1 || value > 100) { if (isNaN(value) || value < 1 || value > 100) {
e.target.classList.add("border-red-500"); e.target.classList.add("border-red-500");
@ -618,7 +608,6 @@ export default function Dashboard() {
} }
}} }}
onBlur={(e) => { onBlur={(e) => {
// Änderung anwenden, wenn das Input den Fokus verliert
const value = parseInt(e.target.value); const value = parseInt(e.target.value);
if (value >= 1 && value <= 100) { if (value >= 1 && value <= 100) {
handleItemsPerPageChange(e.target.value); handleItemsPerPageChange(e.target.value);
@ -626,7 +615,6 @@ export default function Dashboard() {
}} }}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
// Bestehenden Debounce-Timer löschen, um sofort anzuwenden
if (debounceTimerRef.current) { if (debounceTimerRef.current) {
clearTimeout(debounceTimerRef.current); clearTimeout(debounceTimerRef.current);
debounceTimerRef.current = null; debounceTimerRef.current = null;
@ -634,7 +622,6 @@ export default function Dashboard() {
const value = parseInt((e.target as HTMLInputElement).value); const value = parseInt((e.target as HTMLInputElement).value);
if (value >= 1 && value <= 100) { if (value >= 1 && value <= 100) {
// Änderung sofort bei Enter anwenden
const validatedValue = Math.min(Math.max(value, 1), 100); const validatedValue = Math.min(Math.max(value, 1), 100);
setItemsPerPage(validatedValue); setItemsPerPage(validatedValue);
setCurrentPage(1); setCurrentPage(1);
@ -644,11 +631,8 @@ export default function Dashboard() {
sameSite: "strict", sameSite: "strict",
}); });
// Kurze Verzögerung hinzufügen für bessere Reaktionsfähigkeit
setTimeout(() => { setTimeout(() => {
getServers(); getServers();
// Dropdown schließen
document.body.click(); document.body.click();
}, 50); }, 50);
} }
@ -656,7 +640,7 @@ export default function Dashboard() {
}} }}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
/> />
<span className="text-xs text-muted-foreground whitespace-nowrap">items</span> <span className="text-xs text-muted-foreground whitespace-nowrap">{t('ItemsPerPage.items')}</span>
</div> </div>
</div> </div>
</SelectContent> </SelectContent>
@ -671,7 +655,7 @@ export default function Dashboard() {
<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>Add a server</span> <span>{t('AddServer.Title')}</span>
<Select <Select
onValueChange={(value) => { onValueChange={(value) => {
if (!value) return; if (!value) return;
@ -727,20 +711,20 @@ export default function Dashboard() {
<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('Tabs.General')}</TabsTrigger>
<TabsTrigger value="hardware">Hardware</TabsTrigger> <TabsTrigger value="hardware">{t('Tabs.Hardware')}</TabsTrigger>
<TabsTrigger value="virtualization">Host</TabsTrigger> <TabsTrigger value="virtualization">{t('Tabs.Host')}</TabsTrigger>
<TabsTrigger value="monitoring">Monitoring</TabsTrigger> <TabsTrigger value="monitoring">{t('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">Icon</Label> <Label htmlFor="icon">{t('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="Select an icon"> <SelectValue placeholder={t('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} />
@ -751,7 +735,7 @@ export default function Dashboard() {
</SelectTrigger> </SelectTrigger>
<SelectContent className="max-h-[300px]"> <SelectContent className="max-h-[300px]">
<Input <Input
placeholder="Search icons..." placeholder={t('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]")
@ -792,14 +776,14 @@ export default function Dashboard() {
</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">Preview</Label> <Label htmlFor="icon">{t('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">Name</Label> <Label htmlFor="name">{t('AddServer.General.Name')}</Label>
<Input <Input
id="name" id="name"
type="text" type="text"
@ -810,11 +794,11 @@ export default function Dashboard() {
</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">
Operating System <span className="text-stone-600">(optional)</span> {t('AddServer.General.OperatingSystem')} <span className="text-stone-600">({t('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="Select OS" /> <SelectValue placeholder={t('AddServer.General.OperatingSystemPlaceholder')} />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="Windows">Windows</SelectItem> <SelectItem value="Windows">Windows</SelectItem>
@ -825,7 +809,7 @@ export default function Dashboard() {
</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">
IP Adress <span className="text-stone-600">(optional)</span> {t('AddServer.General.IPAdress')} <span className="text-stone-600">({t('optional')})</span>
</Label> </Label>
<Input <Input
id="ip" id="ip"
@ -840,12 +824,11 @@ export default function Dashboard() {
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger>
<Label htmlFor="publicURL"> <Label htmlFor="publicURL">
Management URL <span className="text-stone-600">(optional)</span> {t('AddServer.General.ManagementURL')} <span className="text-stone-600">({t('optional')})</span>
</Label> </Label>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
Link to a web interface (e.g. Proxmox or Portainer) with which the server can be {t('AddServer.General.ManagementURLTooltip')}
managed
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
@ -863,7 +846,7 @@ export default function Dashboard() {
<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">
CPU <span className="text-stone-600">(optional)</span> {t('AddServer.Hardware.CPU')} <span className="text-stone-600">({t('optional')})</span>
</Label> </Label>
<Input <Input
id="cpu" id="cpu"
@ -875,7 +858,7 @@ export default function Dashboard() {
</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">
GPU <span className="text-stone-600">(optional)</span> {t('AddServer.Hardware.GPU')} <span className="text-stone-600">({t('optional')})</span>
</Label> </Label>
<Input <Input
id="gpu" id="gpu"
@ -887,7 +870,7 @@ export default function Dashboard() {
</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">
RAM <span className="text-stone-600">(optional)</span> {t('AddServer.Hardware.RAM')} <span className="text-stone-600">({t('optional')})</span>
</Label> </Label>
<Input <Input
id="ram" id="ram"
@ -899,7 +882,7 @@ export default function Dashboard() {
</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">
Disk <span className="text-stone-600">(optional)</span> {t('AddServer.Hardware.Disk')} <span className="text-stone-600">({t('optional')})</span>
</Label> </Label>
<Input <Input
id="disk" id="disk"
@ -919,11 +902,11 @@ export default function Dashboard() {
checked={host} checked={host}
onCheckedChange={(checked) => setHost(checked === true)} onCheckedChange={(checked) => setHost(checked === true)}
/> />
<Label htmlFor="hostCheckbox">Mark as host server</Label> <Label htmlFor="hostCheckbox">{t('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>Host Server</Label> <Label>{t('AddServer.Host.SelectHostServer')}</Label>
<Select <Select
value={hostServer?.toString()} value={hostServer?.toString()}
onValueChange={(value) => { onValueChange={(value) => {
@ -935,10 +918,10 @@ export default function Dashboard() {
}} }}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Select a host server" /> <SelectValue placeholder={t('AddServer.Host.SelectHostServerPlaceholder')} />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="0">No host server</SelectItem> <SelectItem value="0">{t('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}
@ -958,12 +941,12 @@ export default function Dashboard() {
checked={monitoring} checked={monitoring}
onCheckedChange={(checked) => setMonitoring(checked === true)} onCheckedChange={(checked) => setMonitoring(checked === true)}
/> />
<Label htmlFor="monitoringCheckbox">Enable monitoring</Label> <Label htmlFor="monitoringCheckbox">{t('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">Monitoring URL</Label> <Label htmlFor="monitoringURL">{t('AddServer.Monitoring.URL')}</Label>
<Input <Input
id="monitoringURL" id="monitoringURL"
type="text" type="text"
@ -973,9 +956,9 @@ export default function Dashboard() {
/> />
</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('AddServer.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('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:
@ -1000,8 +983,8 @@ export default function Dashboard() {
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogCancel>{t('cancel')}</AlertDialogCancel>
<AlertDialogAction onClick={add}>Add</AlertDialogAction> <AlertDialogAction onClick={add}>{t('add')}</AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>

View File

@ -47,5 +47,68 @@
"ActiveConnections": "Active Connections", "ActiveConnections": "Active Connections",
"ViewNetworkDetails": "View network details" "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": {
"General": "General",
"Hardware": "Hardware",
"Host": "Host",
"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:"
}
}
} }
} }