mirror of
https://github.com/crocofied/CoreControl.git
synced 2025-12-24 02:46:28 +00:00
i18n final
This commit is contained in:
parent
58dd396241
commit
3e4f7c3641
@ -85,6 +85,7 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
interface Application {
|
||||
id: number;
|
||||
@ -112,6 +113,7 @@ interface ApplicationsResponse {
|
||||
}
|
||||
|
||||
export default function Dashboard() {
|
||||
const t = useTranslations();
|
||||
const [name, setName] = useState<string>("");
|
||||
const [description, setDescription] = useState<string>("");
|
||||
const [icon, setIcon] = useState<string>("");
|
||||
@ -182,31 +184,28 @@ export default function Dashboard() {
|
||||
};
|
||||
|
||||
const handleItemsPerPageChange = (value: string) => {
|
||||
// Clear any existing timer
|
||||
if (debounceTimerRef.current) {
|
||||
clearTimeout(debounceTimerRef.current);
|
||||
}
|
||||
|
||||
// Set a new timer
|
||||
debounceTimerRef.current = setTimeout(() => {
|
||||
const newItemsPerPage = parseInt(value);
|
||||
|
||||
// Ensure the value is within the valid range
|
||||
if (isNaN(newItemsPerPage) || newItemsPerPage < 1) {
|
||||
toast.error("Please enter a number between 1 and 100");
|
||||
toast.error(t('Applications.Messages.NumberValidation'));
|
||||
return;
|
||||
}
|
||||
|
||||
const validatedValue = Math.min(Math.max(newItemsPerPage, 1), 100);
|
||||
|
||||
setItemsPerPage(validatedValue);
|
||||
setCurrentPage(1); // Reset to first page when changing items per page
|
||||
setCurrentPage(1);
|
||||
Cookies.set("itemsPerPage-app", String(validatedValue), {
|
||||
expires: 365,
|
||||
path: "/",
|
||||
sameSite: "strict",
|
||||
});
|
||||
}, 300); // 300ms delay
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const add = async () => {
|
||||
@ -221,10 +220,10 @@ export default function Dashboard() {
|
||||
uptimecheckUrl: customUptimeCheck ? uptimecheckUrl : "",
|
||||
});
|
||||
getApplications();
|
||||
toast.success("Application added successfully");
|
||||
toast.success(t('Applications.Messages.AddSuccess'));
|
||||
} catch (error: any) {
|
||||
console.log(error.response?.data);
|
||||
toast.error("Failed to add application");
|
||||
toast.error(t('Applications.Messages.AddError'));
|
||||
}
|
||||
};
|
||||
|
||||
@ -244,7 +243,7 @@ export default function Dashboard() {
|
||||
setLoading(false);
|
||||
} catch (error: any) {
|
||||
console.log(error.response?.data);
|
||||
toast.error("Failed to get applications");
|
||||
toast.error(t('Applications.Messages.GetError'));
|
||||
}
|
||||
};
|
||||
|
||||
@ -265,10 +264,10 @@ export default function Dashboard() {
|
||||
try {
|
||||
await axios.post("/api/applications/delete", { id });
|
||||
getApplications();
|
||||
toast.success("Application deleted successfully");
|
||||
toast.success(t('Applications.Messages.DeleteSuccess'));
|
||||
} catch (error: any) {
|
||||
console.log(error.response?.data);
|
||||
toast.error("Failed to delete application");
|
||||
toast.error(t('Applications.Messages.DeleteError'));
|
||||
}
|
||||
};
|
||||
|
||||
@ -306,10 +305,10 @@ export default function Dashboard() {
|
||||
});
|
||||
getApplications();
|
||||
setEditId(null);
|
||||
toast.success("Application edited successfully");
|
||||
toast.success(t('Applications.Messages.EditSuccess'));
|
||||
} catch (error: any) {
|
||||
console.log(error.response.data);
|
||||
toast.error("Failed to edit application");
|
||||
toast.error(t('Applications.Messages.EditError'));
|
||||
}
|
||||
};
|
||||
|
||||
@ -363,11 +362,11 @@ export default function Dashboard() {
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator className="hidden md:block" />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>My Infrastructure</BreadcrumbPage>
|
||||
<BreadcrumbPage>{t('Applications.Breadcrumb.MyInfrastructure')}</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator className="hidden md:block" />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Applications</BreadcrumbPage>
|
||||
<BreadcrumbPage>{t('Applications.Breadcrumb.Applications')}</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
@ -376,11 +375,11 @@ export default function Dashboard() {
|
||||
<Toaster />
|
||||
<div className="p-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-3xl font-bold">Your Applications</span>
|
||||
<span className="text-3xl font-bold">{t('Applications.Title')}</span>
|
||||
<div className="flex gap-2">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="icon" title="Change view">
|
||||
<Button variant="outline" size="icon" title={t('Applications.Views.ChangeView')}>
|
||||
{isCompactLayout ? (
|
||||
<Grid3X3 className="h-4 w-4" />
|
||||
) : isGridLayout ? (
|
||||
@ -392,13 +391,13 @@ export default function Dashboard() {
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => toggleLayout("standard")}>
|
||||
<List className="h-4 w-4 mr-2" /> List View
|
||||
<List className="h-4 w-4 mr-2" /> {t('Applications.Views.ListView')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => toggleLayout("grid")}>
|
||||
<LayoutGrid className="h-4 w-4 mr-2" /> Grid View
|
||||
<LayoutGrid className="h-4 w-4 mr-2" /> {t('Applications.Views.GridView')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => toggleLayout("compact")}>
|
||||
<Grid3X3 className="h-4 w-4 mr-2" /> Compact View
|
||||
<Grid3X3 className="h-4 w-4 mr-2" /> {t('Applications.Views.CompactView')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@ -489,7 +488,7 @@ export default function Dashboard() {
|
||||
</Select>
|
||||
{servers.length === 0 ? (
|
||||
<p className="text-muted-foreground">
|
||||
You must first add a server.
|
||||
{t('Applications.Messages.AddServerFirst')}
|
||||
</p>
|
||||
) : (
|
||||
<AlertDialog>
|
||||
@ -500,24 +499,24 @@ export default function Dashboard() {
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent className="max-w-[90vw] w-[600px] max-h-[90vh] overflow-y-auto">
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Add an application</AlertDialogTitle>
|
||||
<AlertDialogTitle>{t('Applications.Add.Title')}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
<div className="space-y-4 pt-4">
|
||||
<div className="grid w-full items-center gap-1.5">
|
||||
<Label>Name</Label>
|
||||
<Label>{t('Applications.Add.Name')}</Label>
|
||||
<Input
|
||||
placeholder="e.g. Portainer"
|
||||
placeholder={t('Applications.Add.NamePlaceholder')}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid w-full items-center gap-1.5">
|
||||
<Label>Server</Label>
|
||||
<Label>{t('Applications.Add.Server')}</Label>
|
||||
<Select
|
||||
onValueChange={(v) => setServerId(Number(v))}
|
||||
required
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Select server" />
|
||||
<SelectValue placeholder={t('Applications.Add.SelectServer')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{servers.map((server) => (
|
||||
@ -533,53 +532,35 @@ export default function Dashboard() {
|
||||
</div>
|
||||
<div className="grid w-full items-center gap-1.5">
|
||||
<Label>
|
||||
Description{" "}
|
||||
<span className="text-stone-600">(optional)</span>
|
||||
{t('Applications.Add.Description')}{" "}
|
||||
<span className="text-stone-600">{t('Common.optional')}</span>
|
||||
</Label>
|
||||
<Textarea
|
||||
placeholder="Application description"
|
||||
placeholder={t('Applications.Add.DescriptionPlaceholder')}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid w-full items-center gap-1.5">
|
||||
<Label>
|
||||
Icon URL{" "}
|
||||
<span className="text-stone-600">(optional)</span>
|
||||
</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
value={icon}
|
||||
placeholder="https://example.com/icon.png"
|
||||
onChange={(e) => setIcon(e.target.value)}
|
||||
/>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Button variant="outline" size="icon" onClick={generateIconURL}>
|
||||
<Zap />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Generate Icon URL
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<Label>{t('Applications.Add.IconURL')}</Label>
|
||||
<Input
|
||||
placeholder={t('Applications.Add.IconURLPlaceholder')}
|
||||
onChange={(e) => setIcon(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid w-full items-center gap-1.5">
|
||||
<Label>Public URL</Label>
|
||||
<Label>{t('Applications.Add.PublicURL')}</Label>
|
||||
<Input
|
||||
placeholder="https://example.com"
|
||||
placeholder={t('Applications.Add.PublicURLPlaceholder')}
|
||||
onChange={(e) => setPublicURL(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid w-full items-center gap-1.5">
|
||||
<Label>
|
||||
Local URL{" "}
|
||||
<span className="text-stone-600">(optional)</span>
|
||||
{t('Applications.Add.LocalURL')}{" "}
|
||||
<span className="text-stone-600">{t('Common.optional')}</span>
|
||||
</Label>
|
||||
<Input
|
||||
placeholder="http://localhost:3000"
|
||||
placeholder={t('Applications.Add.LocalURLPlaceholder')}
|
||||
onChange={(e) => setLocalURL(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
@ -591,23 +572,23 @@ export default function Dashboard() {
|
||||
onChange={(e) => setCustomUptimeCheck(e.target.checked)}
|
||||
className="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
|
||||
/>
|
||||
<Label htmlFor="custom-uptime-check">Custom Uptime Check URL</Label>
|
||||
<Label htmlFor="custom-uptime-check">{t('Applications.Add.CustomUptimeCheck')}</Label>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
When enabled, this URL replaces the Public URL for uptime monitoring checks
|
||||
{t('Applications.Add.CustomUptimeCheckTooltip')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
{customUptimeCheck && (
|
||||
<div className="grid w-full items-center gap-1.5">
|
||||
<Label>Uptime Check URL</Label>
|
||||
<Label>{t('Applications.Add.UptimeCheckURL')}</Label>
|
||||
<Input
|
||||
placeholder="https://example.com/status"
|
||||
placeholder={t('Applications.Add.UptimeCheckURLPlaceholder')}
|
||||
value={uptimecheckUrl}
|
||||
onChange={(e) => setUptimecheckUrl(e.target.value)}
|
||||
/>
|
||||
@ -617,12 +598,12 @@ export default function Dashboard() {
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogCancel>{t('Common.cancel')}</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={add}
|
||||
disabled={!name || !publicURL || !serverId}
|
||||
>
|
||||
Add
|
||||
{t('Common.add')}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
@ -633,7 +614,7 @@ export default function Dashboard() {
|
||||
<div className="flex flex-col gap-2 mb-4 pt-2">
|
||||
<Input
|
||||
id="application-search"
|
||||
placeholder="Type to search..."
|
||||
placeholder={t('Applications.Search.Placeholder')}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
@ -668,7 +649,7 @@ export default function Dashboard() {
|
||||
className="w-full h-full object-contain rounded-md"
|
||||
/>
|
||||
) : (
|
||||
<span className="text-gray-500 text-xs">Icon</span>
|
||||
<span className="text-gray-500 text-xs">{t('Applications.Card.Icon')}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-center mt-2">
|
||||
@ -698,7 +679,7 @@ export default function Dashboard() {
|
||||
className="w-full h-full object-contain rounded-md"
|
||||
/>
|
||||
) : (
|
||||
<span className="text-gray-500 text-xs">Image</span>
|
||||
<span className="text-gray-500 text-xs">{t('Applications.Card.Image')}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
@ -710,7 +691,7 @@ export default function Dashboard() {
|
||||
{app.description && (
|
||||
<br className="hidden md:block" />
|
||||
)}
|
||||
Server: {app.server || "No server"}
|
||||
{t('Applications.Card.Server')}: {app.server || t('Applications.Card.NoServer')}
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
@ -727,7 +708,7 @@ export default function Dashboard() {
|
||||
}
|
||||
>
|
||||
<Link className="h-4 w-4" />
|
||||
Public URL
|
||||
{t('Applications.Card.PublicURL')}
|
||||
</Button>
|
||||
{app.localURL && (
|
||||
<Button
|
||||
@ -738,7 +719,7 @@ export default function Dashboard() {
|
||||
}
|
||||
>
|
||||
<Home className="h-4 w-4" />
|
||||
Local URL
|
||||
{t('Applications.Card.LocalURL')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@ -929,7 +910,7 @@ export default function Dashboard() {
|
||||
onClick={() => window.open(app.publicURL, "_blank")}
|
||||
>
|
||||
<Link className="h-4 w-4" />
|
||||
Public URL
|
||||
{t('Applications.Card.PublicURL')}
|
||||
</Button>
|
||||
{app.localURL && (
|
||||
<Button
|
||||
@ -938,7 +919,7 @@ export default function Dashboard() {
|
||||
onClick={() => window.open(app.localURL, "_blank")}
|
||||
>
|
||||
<Home className="h-4 w-4" />
|
||||
Local URL
|
||||
{t('Applications.Card.LocalURL')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@ -1124,7 +1105,7 @@ export default function Dashboard() {
|
||||
<div className="flex items-center justify-center">
|
||||
<div className="inline-block" role="status" aria-label="loading">
|
||||
<svg
|
||||
className="w-6 h-6 stroke-white animate-spin "
|
||||
className="w-6 h-6 stroke-white animate-spin"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@ -1144,14 +1125,16 @@ export default function Dashboard() {
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
<span className="sr-only">Loading...</span>
|
||||
<span className="sr-only">{t('Common.Loading')}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="pt-4 pb-4">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{totalItems > 0 ? `Showing ${startItem}-${endItem} of ${totalItems} applications` : "No applications found"}
|
||||
{totalItems > 0
|
||||
? t('Applications.Pagination.Showing', { start: startItem, end: endItem, total: totalItems })
|
||||
: t('Applications.Pagination.NoApplications')}
|
||||
</div>
|
||||
</div>
|
||||
<Pagination>
|
||||
|
||||
@ -16,8 +16,10 @@ import {
|
||||
import { ReactFlow, Controls, Background, ConnectionLineType } from "@xyflow/react";
|
||||
import "@xyflow/react/dist/style.css";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export default function Dashboard() {
|
||||
const t = useTranslations();
|
||||
const [nodes, setNodes] = useState<any[]>([]);
|
||||
const [edges, setEdges] = useState<any[]>([]);
|
||||
|
||||
@ -58,13 +60,13 @@ export default function Dashboard() {
|
||||
<BreadcrumbSeparator className="hidden md:block dark:text-slate-500" />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage className="dark:text-slate-300">
|
||||
My Infrastructure
|
||||
{t('Network.Breadcrumb.MyInfrastructure')}
|
||||
</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator className="hidden md:block dark:text-slate-500" />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage className="dark:text-slate-300">
|
||||
Network
|
||||
{t('Network.Breadcrumb.Network')}
|
||||
</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
|
||||
@ -36,6 +36,7 @@ import {
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { useTranslations } from "next-intl"
|
||||
|
||||
interface NotificationsResponse {
|
||||
notifications: any[]
|
||||
@ -46,6 +47,7 @@ interface NotificationResponse {
|
||||
}
|
||||
|
||||
export default function Settings() {
|
||||
const t = useTranslations()
|
||||
const { theme, setTheme } = useTheme()
|
||||
|
||||
const [email, setEmail] = useState<string>("")
|
||||
@ -92,7 +94,7 @@ export default function Settings() {
|
||||
setEmailError("")
|
||||
|
||||
if (!email) {
|
||||
setEmailError("Email is required")
|
||||
setEmailError(t('Settings.UserSettings.ChangeEmail.EmailRequired'))
|
||||
setEmailErrorVisible(true)
|
||||
setTimeout(() => {
|
||||
setEmailErrorVisible(false)
|
||||
@ -123,7 +125,7 @@ export default function Settings() {
|
||||
const changePassword = async () => {
|
||||
try {
|
||||
if (password !== confirmPassword) {
|
||||
setPasswordError("Passwords do not match")
|
||||
setPasswordError(t('Settings.UserSettings.ChangePassword.PasswordsDontMatch'))
|
||||
setPasswordErrorVisible(true)
|
||||
setTimeout(() => {
|
||||
setPasswordErrorVisible(false)
|
||||
@ -132,7 +134,7 @@ export default function Settings() {
|
||||
return
|
||||
}
|
||||
if (!oldPassword || !password || !confirmPassword) {
|
||||
setPasswordError("All fields are required")
|
||||
setPasswordError(t('Settings.UserSettings.ChangePassword.AllFieldsRequired'))
|
||||
setPasswordErrorVisible(true)
|
||||
setTimeout(() => {
|
||||
setPasswordErrorVisible(false)
|
||||
@ -263,7 +265,7 @@ export default function Settings() {
|
||||
const response = await axios.post("/api/notifications/test", {
|
||||
notificationId: id,
|
||||
})
|
||||
toast.success("Notification will be sent in a few seconds.")
|
||||
toast.success(t('Settings.Notifications.TestSuccess'))
|
||||
} catch (error: any) {
|
||||
toast.error(error.response.data.error)
|
||||
}
|
||||
@ -280,15 +282,11 @@ export default function Settings() {
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem className="hidden md:block">
|
||||
<BreadcrumbPage>/</BreadcrumbPage>
|
||||
<BreadcrumbPage>{t('Settings.Breadcrumb.Dashboard')}</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator className="hidden md:block" />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Dashboard</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator className="hidden md:block" />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Settings</BreadcrumbPage>
|
||||
<BreadcrumbPage>{t('Settings.Breadcrumb.Settings')}</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
@ -296,31 +294,31 @@ export default function Settings() {
|
||||
</header>
|
||||
<div className="p-6">
|
||||
<div className="pb-4">
|
||||
<span className="text-3xl font-bold">Settings</span>
|
||||
<span className="text-3xl font-bold">{t('Settings.Title')}</span>
|
||||
</div>
|
||||
<div className="grid gap-6">
|
||||
<Card className="overflow-hidden border-2 border-muted/20 shadow-sm">
|
||||
<CardHeader className="bg-muted/10 px-6 py-4 border-b">
|
||||
<div className="flex items-center gap-2">
|
||||
<User className="h-5 w-5 text-primary" />
|
||||
<h2 className="text-xl font-semibold">User Settings</h2>
|
||||
<h2 className="text-xl font-semibold">{t('Settings.UserSettings.Title')}</h2>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pb-6">
|
||||
<div className="text-sm text-muted-foreground mb-6">
|
||||
Manage your user settings here. You can change your email, password, and other account settings.
|
||||
{t('Settings.UserSettings.Description')}
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-8">
|
||||
<div className="space-y-4">
|
||||
<div className="border-b pb-2">
|
||||
<h3 className="font-semibold text-lg">Change Email</h3>
|
||||
<h3 className="font-semibold text-lg">{t('Settings.UserSettings.ChangeEmail.Title')}</h3>
|
||||
</div>
|
||||
|
||||
{emailErrorVisible && (
|
||||
<Alert variant="destructive" className="animate-in fade-in-50">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertTitle>Error</AlertTitle>
|
||||
<AlertTitle>{t('Common.Error')}</AlertTitle>
|
||||
<AlertDescription>{emailError}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
@ -328,34 +326,33 @@ export default function Settings() {
|
||||
{emailSuccess && (
|
||||
<Alert className="border-green-200 bg-green-50 text-green-800 dark:border-green-800 dark:bg-green-950 dark:text-green-300 animate-in fade-in-50">
|
||||
<Check className="h-4 w-4" />
|
||||
<AlertTitle>Success</AlertTitle>
|
||||
<AlertDescription>Email changed successfully.</AlertDescription>
|
||||
<AlertTitle>{t('Settings.UserSettings.ChangeEmail.Success')}</AlertTitle>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<div className="space-y-3">
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Enter new email"
|
||||
placeholder={t('Settings.UserSettings.ChangeEmail.Placeholder')}
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className="h-11"
|
||||
/>
|
||||
<Button onClick={changeEmail} className="w-full h-11">
|
||||
Change Email
|
||||
{t('Settings.UserSettings.ChangeEmail.Button')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="border-b pb-2">
|
||||
<h3 className="font-semibold text-lg">Change Password</h3>
|
||||
<h3 className="font-semibold text-lg">{t('Settings.UserSettings.ChangePassword.Title')}</h3>
|
||||
</div>
|
||||
|
||||
{passwordErrorVisible && (
|
||||
<Alert variant="destructive" className="animate-in fade-in-50">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertTitle>Error</AlertTitle>
|
||||
<AlertTitle>{t('Common.Error')}</AlertTitle>
|
||||
<AlertDescription>{passwordError}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
@ -363,35 +360,34 @@ export default function Settings() {
|
||||
{passwordSuccess && (
|
||||
<Alert className="border-green-200 bg-green-50 text-green-800 dark:border-green-800 dark:bg-green-950 dark:text-green-300 animate-in fade-in-50">
|
||||
<Check className="h-4 w-4" />
|
||||
<AlertTitle>Success</AlertTitle>
|
||||
<AlertDescription>Password changed successfully.</AlertDescription>
|
||||
<AlertTitle>{t('Settings.UserSettings.ChangePassword.Success')}</AlertTitle>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<div className="space-y-3">
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Enter old password"
|
||||
placeholder={t('Settings.UserSettings.ChangePassword.OldPassword')}
|
||||
value={oldPassword}
|
||||
onChange={(e) => setOldPassword(e.target.value)}
|
||||
className="h-11"
|
||||
/>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Enter new password"
|
||||
placeholder={t('Settings.UserSettings.ChangePassword.NewPassword')}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className="h-11"
|
||||
/>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Confirm new password"
|
||||
placeholder={t('Settings.UserSettings.ChangePassword.ConfirmPassword')}
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
className="h-11"
|
||||
/>
|
||||
<Button onClick={changePassword} className="w-full h-11">
|
||||
Change Password
|
||||
{t('Settings.UserSettings.ChangePassword.Button')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@ -403,25 +399,25 @@ export default function Settings() {
|
||||
<CardHeader className="bg-muted/10 px-6 py-4 border-b">
|
||||
<div className="flex items-center gap-2">
|
||||
<Palette className="h-5 w-5 text-primary" />
|
||||
<h2 className="text-xl font-semibold">Theme Settings</h2>
|
||||
<h2 className="text-xl font-semibold">{t('Settings.ThemeSettings.Title')}</h2>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pb-6">
|
||||
<div className="text-sm text-muted-foreground mb-6">
|
||||
Select a theme for the application. You can choose between light, dark, or system theme.
|
||||
{t('Settings.ThemeSettings.Description')}
|
||||
</div>
|
||||
|
||||
<div className="max-w-md">
|
||||
<Select value={theme} onValueChange={(value: string) => setTheme(value)}>
|
||||
<SelectTrigger className="w-full h-11">
|
||||
<SelectValue>
|
||||
{(theme ?? "system").charAt(0).toUpperCase() + (theme ?? "system").slice(1)}
|
||||
{t(`Settings.ThemeSettings.${(theme ?? "system").charAt(0).toUpperCase() + (theme ?? "system").slice(1)}`)}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="light">Light</SelectItem>
|
||||
<SelectItem value="dark">Dark</SelectItem>
|
||||
<SelectItem value="system">System</SelectItem>
|
||||
<SelectItem value="light">{t('Settings.ThemeSettings.Light')}</SelectItem>
|
||||
<SelectItem value="dark">{t('Settings.ThemeSettings.Dark')}</SelectItem>
|
||||
<SelectItem value="system">{t('Settings.ThemeSettings.System')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
@ -434,61 +430,59 @@ export default function Settings() {
|
||||
<div className="bg-muted/20 p-2 rounded-full">
|
||||
<Bell className="h-5 w-5 text-primary" />
|
||||
</div>
|
||||
<h2 className="text-xl font-semibold">Notifications</h2>
|
||||
<h2 className="text-xl font-semibold">{t('Settings.Notifications.Title')}</h2>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="p-6">
|
||||
<div className="text-sm text-muted-foreground mb-6">
|
||||
Set up notifications to get instantly alerted when an application changes status.
|
||||
{t('Settings.Notifications.Description')}
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button className="w-full h-11 flex items-center gap-2">
|
||||
Add Notification Channel
|
||||
{t('Settings.Notifications.AddChannel')}
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogTitle>Add Notification</AlertDialogTitle>
|
||||
<AlertDialogTitle>{t('Settings.Notifications.AddNotification.Title')}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
<div className="space-y-4">
|
||||
<Input
|
||||
type="text"
|
||||
id="notificationName"
|
||||
placeholder="Notification Name (optional)"
|
||||
placeholder={t('Settings.Notifications.AddNotification.Name')}
|
||||
onChange={(e) => setNotificationName(e.target.value)}
|
||||
/>
|
||||
<Select value={notificationType} onValueChange={(value: string) => setNotificationType(value)}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Notification Type" />
|
||||
<SelectValue placeholder={t('Settings.Notifications.AddNotification.Type')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="smtp">SMTP</SelectItem>
|
||||
<SelectItem value="telegram">Telegram</SelectItem>
|
||||
<SelectItem value="discord">Discord</SelectItem>
|
||||
<SelectItem value="gotify">Gotify</SelectItem>
|
||||
<SelectItem value="ntfy">Ntfy</SelectItem>
|
||||
<SelectItem value="pushover">Pushover</SelectItem>
|
||||
<SelectItem value="smtp">{t('Settings.Notifications.AddNotification.SMTP.Title')}</SelectItem>
|
||||
<SelectItem value="telegram">{t('Settings.Notifications.AddNotification.Telegram.Title')}</SelectItem>
|
||||
<SelectItem value="discord">{t('Settings.Notifications.AddNotification.Discord.Title')}</SelectItem>
|
||||
<SelectItem value="gotify">{t('Settings.Notifications.AddNotification.Gotify.Title')}</SelectItem>
|
||||
<SelectItem value="ntfy">{t('Settings.Notifications.AddNotification.Ntfy.Title')}</SelectItem>
|
||||
<SelectItem value="pushover">{t('Settings.Notifications.AddNotification.Pushover.Title')}</SelectItem>
|
||||
</SelectContent>
|
||||
|
||||
{notificationType === "smtp" && (
|
||||
<div className="mt-4 space-y-4">
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="smtpHost">SMTP Host</Label>
|
||||
<Label>{t('Settings.Notifications.AddNotification.SMTP.Host')}</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="smtpHost"
|
||||
placeholder="smtp.example.com"
|
||||
onChange={(e) => setSmtpHost(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="smtpPort">SMTP Port</Label>
|
||||
<Label>{t('Settings.Notifications.AddNotification.SMTP.Port')}</Label>
|
||||
<Input
|
||||
type="number"
|
||||
id="smtpPort"
|
||||
placeholder="587"
|
||||
onChange={(e) => setSmtpPort(Number(e.target.value))}
|
||||
/>
|
||||
@ -498,26 +492,24 @@ export default function Settings() {
|
||||
<div className="flex items-center space-x-2 pt-2 pb-4">
|
||||
<Checkbox id="smtpSecure" onCheckedChange={(checked: any) => setSmtpSecure(checked)} />
|
||||
<Label htmlFor="smtpSecure" className="text-sm font-medium leading-none">
|
||||
Secure Connection (TLS/SSL)
|
||||
{t('Settings.Notifications.AddNotification.SMTP.Secure')}
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4">
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="smtpUser">SMTP Username</Label>
|
||||
<Label>{t('Settings.Notifications.AddNotification.SMTP.User')}</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="smtpUser"
|
||||
placeholder="user@example.com"
|
||||
onChange={(e) => setSmtpUsername(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="smtpPass">SMTP Password</Label>
|
||||
<Label>{t('Settings.Notifications.AddNotification.SMTP.Pass')}</Label>
|
||||
<Input
|
||||
type="password"
|
||||
id="smtpPass"
|
||||
placeholder="••••••••"
|
||||
onChange={(e) => setSmtpPassword(e.target.value)}
|
||||
/>
|
||||
@ -525,20 +517,18 @@ export default function Settings() {
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="smtpFrom">From Address</Label>
|
||||
<Label>{t('Settings.Notifications.AddNotification.SMTP.From')}</Label>
|
||||
<Input
|
||||
type="email"
|
||||
id="smtpFrom"
|
||||
placeholder="noreply@example.com"
|
||||
onChange={(e) => setSmtpFrom(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="smtpTo">To Address</Label>
|
||||
<Label>{t('Settings.Notifications.AddNotification.SMTP.To')}</Label>
|
||||
<Input
|
||||
type="email"
|
||||
id="smtpTo"
|
||||
placeholder="admin@example.com"
|
||||
onChange={(e) => setSmtpTo(e.target.value)}
|
||||
/>
|
||||
@ -551,20 +541,16 @@ export default function Settings() {
|
||||
{notificationType === "telegram" && (
|
||||
<div className="mt-4 space-y-2">
|
||||
<div className="grid w-full items-center gap-1.5">
|
||||
<Label htmlFor="telegramToken">Bot Token</Label>
|
||||
<Label>{t('Settings.Notifications.AddNotification.Telegram.Token')}</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="telegramToken"
|
||||
placeholder=""
|
||||
onChange={(e) => setTelegramToken(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid w-full items-center gap-1.5">
|
||||
<Label htmlFor="telegramChatId">Chat ID</Label>
|
||||
<Label>{t('Settings.Notifications.AddNotification.Telegram.ChatId')}</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="telegramChatId"
|
||||
placeholder=""
|
||||
onChange={(e) => setTelegramChatId(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
@ -574,11 +560,9 @@ export default function Settings() {
|
||||
{notificationType === "discord" && (
|
||||
<div className="mt-4">
|
||||
<div className="grid w-full items-center gap-1.5">
|
||||
<Label htmlFor="discordWebhook">Webhook URL</Label>
|
||||
<Label>{t('Settings.Notifications.AddNotification.Discord.Webhook')}</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="discordWebhook"
|
||||
placeholder=""
|
||||
onChange={(e) => setDiscordWebhook(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
@ -588,19 +572,15 @@ export default function Settings() {
|
||||
{notificationType === "gotify" && (
|
||||
<div className="mt-4">
|
||||
<div className="grid w-full items-center gap-1.5">
|
||||
<Label htmlFor="gotifyUrl">Gotify URL</Label>
|
||||
<Label>{t('Settings.Notifications.AddNotification.Gotify.Url')}</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="gotifyUrl"
|
||||
placeholder=""
|
||||
onChange={(e) => setGotifyUrl(e.target.value)}
|
||||
/>
|
||||
<div className="grid w-full items-center gap-1.5">
|
||||
<Label htmlFor="gotifyToken">Gotify Token</Label>
|
||||
<Label>{t('Settings.Notifications.AddNotification.Gotify.Token')}</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="gotifyToken"
|
||||
placeholder=""
|
||||
onChange={(e) => setGotifyToken(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
@ -611,19 +591,15 @@ export default function Settings() {
|
||||
{notificationType === "ntfy" && (
|
||||
<div className="mt-4">
|
||||
<div className="grid w-full items-center gap-1.5">
|
||||
<Label htmlFor="ntfyUrl">Ntfy URL</Label>
|
||||
<Label>{t('Settings.Notifications.AddNotification.Ntfy.Url')}</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="ntfyUrl"
|
||||
placeholder=""
|
||||
onChange={(e) => setNtfyUrl(e.target.value)}
|
||||
/>
|
||||
<div className="grid w-full items-center gap-1.5">
|
||||
<Label htmlFor="ntfyToken">Ntfy Token</Label>
|
||||
<Label>{t('Settings.Notifications.AddNotification.Ntfy.Token')}</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="ntfyToken"
|
||||
placeholder=""
|
||||
onChange={(e) => setNtfyToken(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
@ -634,31 +610,28 @@ export default function Settings() {
|
||||
{notificationType === "pushover" && (
|
||||
<div className="mt-4 flex flex-col gap-2">
|
||||
<div className="grid w-full items-center gap-1.5">
|
||||
<Label htmlFor="pushoverUrl">Pushover URL</Label>
|
||||
<Label>{t('Settings.Notifications.AddNotification.Pushover.Url')}</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="pushoverUrl"
|
||||
placeholder="e.g. https://api.pushover.net/1/messages.json"
|
||||
placeholder={t('Settings.Notifications.AddNotification.Pushover.UrlPlaceholder')}
|
||||
onChange={(e) => setPushoverUrl(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid w-full items-center gap-1.5">
|
||||
<Label htmlFor="pushoverToken">Pushover Token</Label>
|
||||
<Label>{t('Settings.Notifications.AddNotification.Pushover.Token')}</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="pushoverToken"
|
||||
placeholder="e.g. 1234567890"
|
||||
placeholder={t('Settings.Notifications.AddNotification.Pushover.TokenPlaceholder')}
|
||||
onChange={(e) => setPushoverToken(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid w-full items-center gap-1.5">
|
||||
<Label htmlFor="pushoverUser">Pushover User</Label>
|
||||
<Label>{t('Settings.Notifications.AddNotification.Pushover.User')}</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="pushoverUser"
|
||||
placeholder="e.g. 1234567890"
|
||||
placeholder={t('Settings.Notifications.AddNotification.Pushover.UserPlaceholder')}
|
||||
onChange={(e) => setPushoverUser(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
@ -668,8 +641,8 @@ export default function Settings() {
|
||||
</div>
|
||||
</AlertDialogDescription>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={addNotification}>Add</AlertDialogAction>
|
||||
<AlertDialogCancel>{t('Common.cancel')}</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={addNotification}>{t('Common.add')}</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
@ -677,65 +650,63 @@ export default function Settings() {
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button className="w-full h-11" variant="outline">
|
||||
Customize Notification Text
|
||||
{t('Settings.Notifications.CustomizeText.Display')}
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogTitle>Customize Notification Text</AlertDialogTitle>
|
||||
<AlertDialogTitle>{t('Settings.Notifications.CustomizeText.Title')}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="text_application">Notification Text for Applications</Label>
|
||||
<Label>{t('Settings.Notifications.CustomizeText.Application')}</Label>
|
||||
<Textarea
|
||||
id="text_application"
|
||||
placeholder="Type here..."
|
||||
value={notificationTextApplication}
|
||||
onChange={(e) => setNotificationTextApplication(e.target.value)}
|
||||
rows={4}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="text_server">Notification Text for Servers</Label>
|
||||
<Label>{t('Settings.Notifications.CustomizeText.Server')}</Label>
|
||||
<Textarea
|
||||
id="text_server"
|
||||
placeholder="Type here..."
|
||||
value={notificationTextServer}
|
||||
onChange={(e) => setNotificationTextServer(e.target.value)}
|
||||
rows={4}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pt-4 text-sm text-muted-foreground">
|
||||
You can use the following placeholders in the text:
|
||||
<ul className="list-disc list-inside space-y-1 pt-2">
|
||||
<li>
|
||||
<b>Server related:</b>
|
||||
<div className="pt-4 text-sm text-muted-foreground">
|
||||
{t('Settings.Notifications.CustomizeText.Placeholders.Title')}
|
||||
<ul className="list-disc list-inside space-y-1 pt-2">
|
||||
<li>
|
||||
<b>{t('Settings.Notifications.CustomizeText.Placeholders.Server.Title')}</b>
|
||||
<ul className="list-disc list-inside ml-4 space-y-1 pt-1 text-muted-foreground">
|
||||
<li>!name - The name of the server</li>
|
||||
<li>!status - The current status of the server (online/offline)</li>
|
||||
<li>{t('Settings.Notifications.CustomizeText.Placeholders.Server.Name')}</li>
|
||||
<li>{t('Settings.Notifications.CustomizeText.Placeholders.Server.Status')}</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<b>Application related:</b>
|
||||
</li>
|
||||
<li>
|
||||
<b>{t('Settings.Notifications.CustomizeText.Placeholders.Application.Title')}</b>
|
||||
<ul className="list-disc list-inside ml-4 space-y-1 pt-1 text-muted-foreground">
|
||||
<li>!name - The name of the application</li>
|
||||
<li>!url - The URL where the application is hosted</li>
|
||||
<li>!status - The current status of the application (online/offline)</li>
|
||||
<li>{t('Settings.Notifications.CustomizeText.Placeholders.Application.Name')}</li>
|
||||
<li>{t('Settings.Notifications.CustomizeText.Placeholders.Application.Url')}</li>
|
||||
<li>{t('Settings.Notifications.CustomizeText.Placeholders.Application.Status')}</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</AlertDialogDescription>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={editNotificationText}>Save</AlertDialogAction>
|
||||
<AlertDialogCancel>{t('Common.cancel')}</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={editNotificationText}>
|
||||
{t('Common.Save')}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
|
||||
<div className="mt-8">
|
||||
<h3 className="text-lg font-medium mb-4">Active Notification Channels</h3>
|
||||
<h3 className="text-lg font-medium mb-4">{t('Settings.Notifications.ActiveChannels')}</h3>
|
||||
<div className="space-y-3">
|
||||
{notifications.length > 0 ? (
|
||||
notifications.map((notification) => (
|
||||
@ -775,14 +746,12 @@ export default function Settings() {
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-1">
|
||||
<h3 className="font-medium capitalize">{notification.name && notification.name !== "" ? notification.name : notification.type}</h3>
|
||||
<h3 className="font-medium capitalize">
|
||||
{notification.name ||
|
||||
t(`Settings.Notifications.AddNotification.${notification.type.charAt(0).toUpperCase() + notification.type.slice(1)}.Title`)}
|
||||
</h3>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{notification.type === "smtp" && "Email notifications"}
|
||||
{notification.type === "telegram" && "Telegram bot alerts"}
|
||||
{notification.type === "discord" && "Discord webhook alerts"}
|
||||
{notification.type === "gotify" && "Gotify notifications"}
|
||||
{notification.type === "ntfy" && "Ntfy notifications"}
|
||||
{notification.type === "pushover" && "Pushover notifications"}
|
||||
{t(`Settings.Notifications.AddNotification.${notification.type.charAt(0).toUpperCase() + notification.type.slice(1)}.Description`)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -794,7 +763,7 @@ export default function Settings() {
|
||||
onClick={() => testNotification(notification.id)}
|
||||
>
|
||||
<Play className="h-4 w-4 mr-1" />
|
||||
Test
|
||||
{t('Settings.Notifications.Test')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@ -803,6 +772,7 @@ export default function Settings() {
|
||||
onClick={() => deleteNotification(notification.id)}
|
||||
>
|
||||
<Trash2 className="h-4 w-4 mr-1" />
|
||||
{t('Settings.Notifications.Delete')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@ -814,9 +784,11 @@ export default function Settings() {
|
||||
<Bell className="h-6 w-6 text-muted-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="text-lg font-medium mb-1">No notifications configured</h3>
|
||||
<h3 className="text-lg font-medium mb-1">
|
||||
{t('Settings.Notifications.NoNotifications')}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground max-w-md mx-auto">
|
||||
Add a notification channel to get alerted when your applications change status.
|
||||
{t('Settings.Notifications.NoNotificationsDescription')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@ -830,4 +802,4 @@ export default function Settings() {
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -31,6 +31,7 @@ import { Label } from "@/components/ui/label";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { toast } from "sonner";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const timeFormats = {
|
||||
1: (timestamp: string) =>
|
||||
@ -81,6 +82,7 @@ interface PaginationData {
|
||||
}
|
||||
|
||||
export default function Uptime() {
|
||||
const t = useTranslations();
|
||||
const [data, setData] = useState<UptimeData[]>([]);
|
||||
const [timespan, setTimespan] = useState<1 | 2 | 3 | 4>(1);
|
||||
const [pagination, setPagination] = useState<PaginationData>({
|
||||
@ -138,34 +140,30 @@ export default function Uptime() {
|
||||
};
|
||||
|
||||
const handleItemsPerPageChange = (value: string) => {
|
||||
// Clear any existing timer
|
||||
if (debounceTimerRef.current) {
|
||||
clearTimeout(debounceTimerRef.current);
|
||||
}
|
||||
|
||||
// Set a new timer
|
||||
debounceTimerRef.current = setTimeout(() => {
|
||||
const newItemsPerPage = parseInt(value);
|
||||
|
||||
// Ensure the value is within the valid range
|
||||
if (isNaN(newItemsPerPage) || newItemsPerPage < 1) {
|
||||
toast.error("Please enter a number between 1 and 100");
|
||||
toast.error(t('Uptime.Messages.NumberValidation'));
|
||||
return;
|
||||
}
|
||||
|
||||
const validatedValue = Math.min(Math.max(newItemsPerPage, 1), 100);
|
||||
|
||||
setItemsPerPage(validatedValue);
|
||||
setPagination(prev => ({...prev, currentPage: 1})); // Reset to first page
|
||||
setPagination(prev => ({...prev, currentPage: 1}));
|
||||
Cookies.set("itemsPerPage-uptime", String(validatedValue), {
|
||||
expires: 365,
|
||||
path: "/",
|
||||
sameSite: "strict",
|
||||
});
|
||||
|
||||
// Fetch data with new pagination
|
||||
getData(timespan, 1, validatedValue);
|
||||
}, 300); // 300ms delay
|
||||
}, 300);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@ -187,11 +185,11 @@ export default function Uptime() {
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator className="hidden md:block" />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>My Infrastructure</BreadcrumbPage>
|
||||
<BreadcrumbPage>{t('Uptime.Breadcrumb.MyInfrastructure')}</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator className="hidden md:block" />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Uptime</BreadcrumbPage>
|
||||
<BreadcrumbPage>{t('Uptime.Breadcrumb.Uptime')}</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
@ -200,7 +198,7 @@ export default function Uptime() {
|
||||
<Toaster />
|
||||
<div className="p-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-3xl font-bold">Uptime</span>
|
||||
<span className="text-3xl font-bold">{t('Uptime.Title')}</span>
|
||||
<div className="flex gap-2">
|
||||
<Select
|
||||
value={String(itemsPerPage)}
|
||||
@ -213,22 +211,22 @@ export default function Uptime() {
|
||||
>
|
||||
<SelectTrigger className="w-[140px]">
|
||||
<SelectValue>
|
||||
{itemsPerPage} {itemsPerPage === 1 ? 'item' : 'items'}
|
||||
{itemsPerPage} {itemsPerPage === 1 ? t('Common.ItemsPerPage.item') : t('Common.ItemsPerPage.items')}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{![5, 10, 15, 20, 25].includes(itemsPerPage) ? (
|
||||
<SelectItem value={String(itemsPerPage)}>
|
||||
{itemsPerPage} {itemsPerPage === 1 ? 'item' : 'items'} (custom)
|
||||
{itemsPerPage} {itemsPerPage === 1 ? t('Common.ItemsPerPage.item') : t('Common.ItemsPerPage.items')} (custom)
|
||||
</SelectItem>
|
||||
) : null}
|
||||
<SelectItem value="5">5 items</SelectItem>
|
||||
<SelectItem value="10">10 items</SelectItem>
|
||||
<SelectItem value="15">15 items</SelectItem>
|
||||
<SelectItem value="20">20 items</SelectItem>
|
||||
<SelectItem value="25">25 items</SelectItem>
|
||||
<SelectItem value="5">{t('Common.ItemsPerPage.5')}</SelectItem>
|
||||
<SelectItem value="10">{t('Common.ItemsPerPage.10')}</SelectItem>
|
||||
<SelectItem value="15">{t('Common.ItemsPerPage.15')}</SelectItem>
|
||||
<SelectItem value="20">{t('Common.ItemsPerPage.20')}</SelectItem>
|
||||
<SelectItem value="25">{t('Common.ItemsPerPage.25')}</SelectItem>
|
||||
<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('Common.ItemsPerPage.Custom')}</Label>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<Input
|
||||
id="custom-items"
|
||||
@ -239,8 +237,6 @@ export default function Uptime() {
|
||||
className="h-8"
|
||||
defaultValue={itemsPerPage}
|
||||
onChange={(e) => {
|
||||
// Don't immediately apply the change while typing
|
||||
// Just validate the input for visual feedback
|
||||
const value = parseInt(e.target.value);
|
||||
if (isNaN(value) || value < 1 || value > 100) {
|
||||
e.target.classList.add("border-red-500");
|
||||
@ -249,7 +245,6 @@ export default function Uptime() {
|
||||
}
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
// Apply the change when the input loses focus
|
||||
const value = parseInt(e.target.value);
|
||||
if (value >= 1 && value <= 100) {
|
||||
handleItemsPerPageChange(e.target.value);
|
||||
@ -257,7 +252,6 @@ export default function Uptime() {
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
// Clear any existing debounce timer to apply immediately
|
||||
if (debounceTimerRef.current) {
|
||||
clearTimeout(debounceTimerRef.current);
|
||||
debounceTimerRef.current = null;
|
||||
@ -265,7 +259,6 @@ export default function Uptime() {
|
||||
|
||||
const value = parseInt((e.target as HTMLInputElement).value);
|
||||
if (value >= 1 && value <= 100) {
|
||||
// Apply change immediately on Enter
|
||||
const validatedValue = Math.min(Math.max(value, 1), 100);
|
||||
setItemsPerPage(validatedValue);
|
||||
setPagination(prev => ({...prev, currentPage: 1}));
|
||||
@ -275,15 +268,13 @@ export default function Uptime() {
|
||||
sameSite: "strict",
|
||||
});
|
||||
getData(timespan, 1, validatedValue);
|
||||
|
||||
// Close the dropdown
|
||||
document.body.click();
|
||||
}
|
||||
}
|
||||
}}
|
||||
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('Common.ItemsPerPage.items')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</SelectContent>
|
||||
@ -297,13 +288,13 @@ export default function Uptime() {
|
||||
disabled={isLoading}
|
||||
>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder="Select timespan" />
|
||||
<SelectValue placeholder={t('Uptime.TimeRange.Select')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1">Last 1 hour</SelectItem>
|
||||
<SelectItem value="2">Last 1 day</SelectItem>
|
||||
<SelectItem value="3">Last 7 days</SelectItem>
|
||||
<SelectItem value="4">Last 30 days</SelectItem>
|
||||
<SelectItem value="1">{t('Uptime.TimeRange.LastHour')}</SelectItem>
|
||||
<SelectItem value="2">{t('Uptime.TimeRange.LastDay')}</SelectItem>
|
||||
<SelectItem value="3">{t('Uptime.TimeRange.Last7Days')}</SelectItem>
|
||||
<SelectItem value="4">{t('Uptime.TimeRange.Last30Days')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
@ -311,7 +302,7 @@ export default function Uptime() {
|
||||
|
||||
<div className="pt-4 space-y-4">
|
||||
{isLoading ? (
|
||||
<div className="text-center py-8">Loading...</div>
|
||||
<div className="text-center py-8">{t('Uptime.Messages.Loading')}</div>
|
||||
) : (
|
||||
data.map((app) => {
|
||||
const reversedSummary = [...app.uptimeSummary].reverse();
|
||||
@ -370,10 +361,10 @@ export default function Uptime() {
|
||||
</p>
|
||||
<p>
|
||||
{entry.missing
|
||||
? "No data"
|
||||
? t('Uptime.Status.NoData')
|
||||
: entry.online
|
||||
? "Online"
|
||||
: "Offline"}
|
||||
? t('Uptime.Status.Online')
|
||||
: t('Uptime.Status.Offline')}
|
||||
</p>
|
||||
</div>
|
||||
<Tooltip.Arrow className="fill-gray-900" />
|
||||
@ -397,8 +388,12 @@ export default function Uptime() {
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{pagination.totalItems > 0
|
||||
? `Showing ${((pagination.currentPage - 1) * itemsPerPage) + 1}-${Math.min(pagination.currentPage * itemsPerPage, pagination.totalItems)} of ${pagination.totalItems} items`
|
||||
: "No items found"}
|
||||
? t('Uptime.Pagination.Showing', {
|
||||
start: ((pagination.currentPage - 1) * itemsPerPage) + 1,
|
||||
end: Math.min(pagination.currentPage * itemsPerPage, pagination.totalItems),
|
||||
total: pagination.totalItems
|
||||
})
|
||||
: t('Uptime.Messages.NoItems')}
|
||||
</div>
|
||||
</div>
|
||||
<Pagination>
|
||||
|
||||
403
i18n/languages/de.json
Normal file
403
i18n/languages/de.json
Normal file
@ -0,0 +1,403 @@
|
||||
{
|
||||
"Common": {
|
||||
"ChangeView": "Ansicht ändern",
|
||||
"ListView": "Listenansicht",
|
||||
"GridView": "Rasteransicht",
|
||||
"optional": "optional",
|
||||
"cancel": "Abbrechen",
|
||||
"add": "Hinzufügen",
|
||||
"since": "seit {date}",
|
||||
"notSet": "Nicht festgelegt",
|
||||
"noData": "Keine Daten",
|
||||
"Loading": "Lade...",
|
||||
"Refresh": "Aktualisieren",
|
||||
"Server": {
|
||||
"CPU": "CPU",
|
||||
"GPU": "GPU",
|
||||
"RAM": "RAM",
|
||||
"Disk": "Festplatte",
|
||||
"OS": "Betriebssystem",
|
||||
"IP": "IP",
|
||||
"Host": "Host",
|
||||
"Temperature": "Temperatur",
|
||||
"Usage": "Auslastung",
|
||||
"Tabs": {
|
||||
"General": "Allgemein",
|
||||
"Hardware": "Hardware",
|
||||
"Host": "Host",
|
||||
"Monitoring": "Überwachung"
|
||||
}
|
||||
},
|
||||
"ItemsPerPage": {
|
||||
"items": "Einträge",
|
||||
"item": "Eintrag",
|
||||
"4": "4 Einträge",
|
||||
"6": "6 Einträge",
|
||||
"10": "10 Einträge",
|
||||
"15": "15 Einträge",
|
||||
"20": "20 Einträge",
|
||||
"25": "25 Einträge",
|
||||
"Custom": "Benutzerdefiniert (1-100)"
|
||||
}
|
||||
},
|
||||
"Server": {
|
||||
"Hardware": "Hardware",
|
||||
"Network": "Netzwerk",
|
||||
"CurrentUsage": "Aktuelle Auslastung",
|
||||
"UsageHistory": "Nutzungsverlauf",
|
||||
"ResourceUsageHistory": "Ressourcennutzungsverlauf",
|
||||
"TimeRange": {
|
||||
"Select": "Zeitraum auswählen",
|
||||
"LastHour": "Letzte Stunde",
|
||||
"Last24Hours": "Letzte 24 Stunden",
|
||||
"Last7Days": "Letzte 7 Tage",
|
||||
"Last30Days": "Letzte 30 Tage"
|
||||
},
|
||||
"VirtualMachines": "Virtuelle Maschinen",
|
||||
"VirtualMachinesDescription": "Virtuelle Maschinen auf diesem Server",
|
||||
"HardwareInformation": "Hardware-Informationen",
|
||||
"ManagementURL": "Management-URL",
|
||||
"VM": "VM",
|
||||
"Physical": "Physisch",
|
||||
"HostedOn": "Gehostet auf",
|
||||
"NotFound": "Server nicht gefunden",
|
||||
"NotFoundDescription": "Der gesuchte Server konnte nicht gefunden werden."
|
||||
},
|
||||
"Sidebar": {
|
||||
"Main Navigation": "Hauptnavigation",
|
||||
"Dashboard": "Dashboard",
|
||||
"My Infrastructure": "Meine Infrastruktur",
|
||||
"Servers": "Server",
|
||||
"Applications": "Anwendungen",
|
||||
"Uptime": "Uptime",
|
||||
"Network": "Netzwerk",
|
||||
"Settings": "Einstellungen",
|
||||
"Logout": "Abmelden"
|
||||
},
|
||||
"Home": {
|
||||
"TitleUnder": "Dashboard zur Verwaltung Ihrer Serverinfrastruktur",
|
||||
"LoginCardTitle": "Anmeldung",
|
||||
"LoginCardDescription": "Anmeldedaten eingeben",
|
||||
"AuthenticationError": "Authentifizierungsfehler",
|
||||
"Email": "E-Mail",
|
||||
"Password": "Passwort",
|
||||
"SigninButton": "Anmelden",
|
||||
"SigninButtonSigningIn": "Anmeldung läuft..."
|
||||
},
|
||||
"Dashboard": {
|
||||
"Title": "Dashboard",
|
||||
"Servers": {
|
||||
"Title": "Server",
|
||||
"Description": "Übersicht physischer und virtueller Server",
|
||||
"PhysicalServers": "Physische Server",
|
||||
"VirtualServers": "Virtuelle Server",
|
||||
"ManageServers": "Server verwalten"
|
||||
},
|
||||
"Applications": {
|
||||
"Title": "Anwendungen",
|
||||
"Description": "Verwalten Sie Ihre Anwendungen",
|
||||
"OnlineApplications": "Aktive Anwendungen",
|
||||
"ViewAllApplications": "Alle Anwendungen anzeigen"
|
||||
},
|
||||
"Uptime": {
|
||||
"Title": "Uptime",
|
||||
"Description": "Überwachung der Verfügbarkeit",
|
||||
"OnlineApplications": "Online-Anwendungen",
|
||||
"ViewUptimeMetrics": "Uptime-Statistiken anzeigen"
|
||||
},
|
||||
"Network": {
|
||||
"Title": "Netzwerk",
|
||||
"Description": "Netzwerkkonfiguration verwalten",
|
||||
"ActiveConnections": "Aktive Verbindungen",
|
||||
"ViewNetworkDetails": "Netzwerkdetails anzeigen"
|
||||
}
|
||||
},
|
||||
"Servers": {
|
||||
"Title": "Server",
|
||||
"MyInfrastructure": "Meine Infrastruktur",
|
||||
"YourServers": "Ihre Server",
|
||||
"AddServer": {
|
||||
"Title": "Server hinzufügen",
|
||||
"General": {
|
||||
"Title": "Server hinzufügen",
|
||||
"CopyServer": "Server kopieren",
|
||||
"Icon": "Symbol",
|
||||
"IconPlaceholder": "Symbol auswählen",
|
||||
"IconSearchPlaceholder": "Symbole suchen...",
|
||||
"Preview": "Vorschau",
|
||||
"Name": "Name",
|
||||
"OperatingSystem": "Betriebssystem",
|
||||
"OperatingSystemPlaceholder": "OS auswählen",
|
||||
"IPAdress": "IP-Adresse",
|
||||
"ManagementURL": "Management-URL",
|
||||
"ManagementURLTooltip": "Link zur Verwaltungsoberfläche (z.B. Proxmox oder Portainer)"
|
||||
},
|
||||
"Host": {
|
||||
"MarkAsHostServer": "Als Host-Server markieren",
|
||||
"SelectHostServer": "Host-Server auswählen",
|
||||
"SelectHostServerPlaceholder": "Host-Server auswählen",
|
||||
"NoHostServer": "Kein Host-Server"
|
||||
},
|
||||
"Monitoring": {
|
||||
"Enable": "Überwachung aktivieren",
|
||||
"URL": "Monitoring-URL",
|
||||
"SetupTitle": "Erforderliche Serverkonfiguration",
|
||||
"SetupDescription": "Zur Aktivierung benötigen Sie Glances auf dem Server. Beispiel-Docker-Compose-Konfiguration:"
|
||||
}
|
||||
},
|
||||
"ServerCard": {
|
||||
"HardwareInformation": "Hardware-Informationen",
|
||||
"ViewDetails": "Details anzeigen",
|
||||
"OpenManagementURL": "Management-URL öffnen",
|
||||
"EditServer": "Server bearbeiten",
|
||||
"DeleteServer": "Server löschen",
|
||||
"HostedVMs": "Gehostete VMs",
|
||||
"ResourceUsage": "Ressourcennutzung",
|
||||
"DeleteConfirmation": {
|
||||
"Title": "{name} löschen",
|
||||
"Description": "Sind Sie sicher? Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"Cancel": "Abbrechen",
|
||||
"Delete": "Löschen"
|
||||
}
|
||||
},
|
||||
"EditServer": {
|
||||
"Title": "{name} bearbeiten",
|
||||
"Save": "Speichern",
|
||||
"General": {
|
||||
"Title": "Allgemein",
|
||||
"Icon": "Symbol",
|
||||
"Name": "Name",
|
||||
"OperatingSystem": "Betriebssystem",
|
||||
"IPAddress": "IP-Adresse",
|
||||
"ManagementURL": "Management-URL"
|
||||
},
|
||||
"Hardware": {
|
||||
"Title": "Hardware",
|
||||
"CPU": "CPU",
|
||||
"GPU": "GPU",
|
||||
"RAM": "RAM",
|
||||
"Disk": "Festplatte"
|
||||
},
|
||||
"Host": {
|
||||
"Title": "Host",
|
||||
"MarkAsHostServer": "Als Host-Server markieren",
|
||||
"CannotDisableHost": "Kann nicht deaktiviert werden, solange VMs gehostet werden",
|
||||
"SelectHostServer": "Host-Server auswählen"
|
||||
},
|
||||
"Monitoring": {
|
||||
"Title": "Überwachung",
|
||||
"Enable": "Überwachung aktivieren",
|
||||
"URL": "Monitoring-URL",
|
||||
"SetupTitle": "Erforderliche Serverkonfiguration",
|
||||
"SetupDescription": "Beispiel-Docker-Compose-Konfiguration für Glances:"
|
||||
}
|
||||
},
|
||||
"Pagination": {
|
||||
"Showing": "Zeige {start}-{end} von {total} Servern",
|
||||
"NoServers": "Keine Server gefunden"
|
||||
},
|
||||
"Search": {
|
||||
"Placeholder": "Suche..."
|
||||
}
|
||||
},
|
||||
"Applications": {
|
||||
"Title": "Ihre Anwendungen",
|
||||
"Breadcrumb": {
|
||||
"MyInfrastructure": "Meine Infrastruktur",
|
||||
"Applications": "Anwendungen"
|
||||
},
|
||||
"Views": {
|
||||
"ChangeView": "Ansicht ändern",
|
||||
"ListView": "Listenansicht",
|
||||
"GridView": "Rasteransicht",
|
||||
"CompactView": "Kompaktansicht"
|
||||
},
|
||||
"Add": {
|
||||
"Title": "Anwendung hinzufügen",
|
||||
"Name": "Name",
|
||||
"NamePlaceholder": "z.B. Portainer",
|
||||
"Server": "Server",
|
||||
"SelectServer": "Server auswählen",
|
||||
"Description": "Beschreibung",
|
||||
"DescriptionPlaceholder": "Anwendungsbeschreibung",
|
||||
"IconURL": "Symbol-URL",
|
||||
"IconURLPlaceholder": "https://example.com/icon.png",
|
||||
"PublicURL": "Öffentliche URL",
|
||||
"PublicURLPlaceholder": "https://example.com",
|
||||
"LocalURL": "Lokale URL",
|
||||
"LocalURLPlaceholder": "http://localhost:3000",
|
||||
"CustomUptimeCheck": "Benutzerdefinierte Uptime-URL",
|
||||
"CustomUptimeCheckTooltip": "Ersetzt die öffentliche URL für Uptime-Checks",
|
||||
"UptimeCheckURL": "Uptime-Prüf-URL",
|
||||
"UptimeCheckURLPlaceholder": "https://example.com/status"
|
||||
},
|
||||
"Edit": {
|
||||
"Title": "Anwendung bearbeiten",
|
||||
"SaveButton": "Änderungen speichern"
|
||||
},
|
||||
"Card": {
|
||||
"NoServer": "Kein Server",
|
||||
"PublicURL": "Öffentliche URL",
|
||||
"LocalURL": "Lokale URL",
|
||||
"Icon": "Symbol",
|
||||
"Image": "Bild"
|
||||
},
|
||||
"Messages": {
|
||||
"AddServerFirst": "Bitte zuerst Server hinzufügen",
|
||||
"AddSuccess": "Anwendung erfolgreich hinzugefügt",
|
||||
"EditSuccess": "Änderungen gespeichert",
|
||||
"DeleteSuccess": "Anwendung gelöscht",
|
||||
"GetError": "Fehler beim Laden",
|
||||
"EditError": "Speichern fehlgeschlagen",
|
||||
"DeleteError": "Löschen fehlgeschlagen",
|
||||
"NumberValidation": "Bitte Zahl zwischen 1-100 eingeben"
|
||||
},
|
||||
"Pagination": {
|
||||
"Showing": "Zeige {start}-{end} von {total} Anwendungen",
|
||||
"NoApplications": "Keine Anwendungen gefunden"
|
||||
},
|
||||
"Search": {
|
||||
"Placeholder": "Suche..."
|
||||
}
|
||||
},
|
||||
"Uptime": {
|
||||
"Title": "Uptime",
|
||||
"Breadcrumb": {
|
||||
"MyInfrastructure": "Meine Infrastruktur",
|
||||
"Uptime": "Uptime"
|
||||
},
|
||||
"TimeRange": {
|
||||
"Select": "Zeitraum wählen",
|
||||
"LastHour": "Letzte Stunde",
|
||||
"LastDay": "Letzter Tag",
|
||||
"Last7Days": "Letzte 7 Tage",
|
||||
"Last30Days": "Letzte 30 Tage"
|
||||
},
|
||||
"Status": {
|
||||
"Online": "Online",
|
||||
"Offline": "Offline",
|
||||
"NoData": "Keine Daten"
|
||||
},
|
||||
"Messages": {
|
||||
"NumberValidation": "Bitte Zahl zwischen 1-100",
|
||||
"NoItems": "Keine Einträge",
|
||||
"Loading": "Lade..."
|
||||
},
|
||||
"Pagination": {
|
||||
"Showing": "Zeige {start}-{end} von {total} Einträgen"
|
||||
}
|
||||
},
|
||||
"Network": {
|
||||
"Title": "Netzwerk",
|
||||
"Breadcrumb": {
|
||||
"MyInfrastructure": "Meine Infrastruktur",
|
||||
"Network": "Netzwerk"
|
||||
}
|
||||
},
|
||||
"Settings": {
|
||||
"Title": "Einstellungen",
|
||||
"Breadcrumb": {
|
||||
"Dashboard": "Dashboard",
|
||||
"Settings": "Einstellungen"
|
||||
},
|
||||
"UserSettings": {
|
||||
"Title": "Benutzereinstellungen",
|
||||
"Description": "E-Mail, Passwort und Kontoeinstellungen verwalten",
|
||||
"ChangeEmail": {
|
||||
"Title": "E-Mail ändern",
|
||||
"Placeholder": "Neue E-Mail",
|
||||
"Button": "E-Mail ändern",
|
||||
"Success": "E-Mail geändert",
|
||||
"Error": "Fehler"
|
||||
},
|
||||
"ChangePassword": {
|
||||
"Title": "Passwort ändern",
|
||||
"OldPassword": "Altes Passwort",
|
||||
"NewPassword": "Neues Passwort",
|
||||
"ConfirmPassword": "Passwort bestätigen",
|
||||
"Button": "Passwort ändern",
|
||||
"Success": "Passwort geändert",
|
||||
"Error": "Fehler",
|
||||
"PasswordsDontMatch": "Passwörter stimmen nicht überein",
|
||||
"AllFieldsRequired": "Alle Felder benötigt"
|
||||
}
|
||||
},
|
||||
"ThemeSettings": {
|
||||
"Title": "Design-Einstellungen",
|
||||
"Description": "Wählen Sie zwischen Hell, Dunkel oder System",
|
||||
"Light": "Hell",
|
||||
"Dark": "Dunkel",
|
||||
"System": "System"
|
||||
},
|
||||
"Notifications": {
|
||||
"Title": "Benachrichtigungen",
|
||||
"Description": "Erhalten Sie Warnungen bei Statusänderungen",
|
||||
"AddChannel": "Kanal hinzufügen",
|
||||
"NoNotifications": "Keine Benachrichtigungen",
|
||||
"NoNotificationsDescription": "Fügen Sie einen Benachrichtigungskanal hinzu",
|
||||
"AddNotification": {
|
||||
"Title": "Benachrichtigung hinzufügen",
|
||||
"Name": "Name (optional)",
|
||||
"Type": "Typ",
|
||||
"SMTP": {
|
||||
"Title": "SMTP",
|
||||
"Host": "SMTP-Host",
|
||||
"Port": "SMTP-Port",
|
||||
"Secure": "Sichere Verbindung (TLS/SSL)",
|
||||
"Username": "SMTP-Benutzername",
|
||||
"Password": "SMTP-Passwort",
|
||||
"From": "Absenderadresse",
|
||||
"To": "Empfängeradresse"
|
||||
},
|
||||
"Telegram": {
|
||||
"Title": "Telegram",
|
||||
"Token": "Bot-Token",
|
||||
"ChatId": "Chat-ID"
|
||||
},
|
||||
"Discord": {
|
||||
"Title": "Discord",
|
||||
"Webhook": "Webhook-URL"
|
||||
},
|
||||
"Gotify": {
|
||||
"Title": "Gotify",
|
||||
"Url": "Gotify-URL",
|
||||
"Token": "Gotify-Token"
|
||||
},
|
||||
"Ntfy": {
|
||||
"Title": "Ntfy",
|
||||
"Url": "Ntfy-URL",
|
||||
"Token": "Ntfy-Token"
|
||||
},
|
||||
"Pushover": {
|
||||
"Title": "Pushover",
|
||||
"Url": "Pushover-URL",
|
||||
"Token": "Pushover-Token",
|
||||
"User": "Pushover-Benutzer"
|
||||
}
|
||||
},
|
||||
"CustomizeText": {
|
||||
"Display": "Benachrichtigungstext anpassen",
|
||||
"Title": "Textanpassung",
|
||||
"Application": "Text für Anwendungen",
|
||||
"Server": "Text für Server",
|
||||
"Placeholders": {
|
||||
"Title": "Verfügbare Platzhalter:",
|
||||
"Server": {
|
||||
"Title": "Server:",
|
||||
"Name": "!name - Servername",
|
||||
"Status": "!status - Status (online/offline)"
|
||||
},
|
||||
"Application": {
|
||||
"Title": "Anwendung:",
|
||||
"Name": "!name - Anwendungsname",
|
||||
"Url": "!url - Anwendungs-URL",
|
||||
"Status": "!status - Status (online/offline)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ActiveChannels": "Aktive Kanäle",
|
||||
"Test": "Testen",
|
||||
"Delete": "Löschen"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -199,6 +199,207 @@
|
||||
"Search": {
|
||||
"Placeholder": "Type to search..."
|
||||
}
|
||||
},
|
||||
"Applications": {
|
||||
"Title": "Your Applications",
|
||||
"Breadcrumb": {
|
||||
"MyInfrastructure": "My Infrastructure",
|
||||
"Applications": "Applications"
|
||||
},
|
||||
"Views": {
|
||||
"ChangeView": "Change view",
|
||||
"ListView": "List View",
|
||||
"GridView": "Grid View",
|
||||
"CompactView": "Compact View"
|
||||
},
|
||||
"Add": {
|
||||
"Title": "Add an application",
|
||||
"Name": "Name",
|
||||
"NamePlaceholder": "e.g. Portainer",
|
||||
"Server": "Server",
|
||||
"SelectServer": "Select server",
|
||||
"Description": "Description",
|
||||
"DescriptionPlaceholder": "Application description",
|
||||
"IconURL": "Icon URL",
|
||||
"IconURLPlaceholder": "https://example.com/icon.png",
|
||||
"PublicURL": "Public URL",
|
||||
"PublicURLPlaceholder": "https://example.com",
|
||||
"LocalURL": "Local URL",
|
||||
"LocalURLPlaceholder": "http://localhost:3000",
|
||||
"CustomUptimeCheck": "Custom Uptime Check URL",
|
||||
"CustomUptimeCheckTooltip": "When enabled, this URL replaces the Public URL for uptime monitoring checks",
|
||||
"UptimeCheckURL": "Uptime Check URL",
|
||||
"UptimeCheckURLPlaceholder": "https://example.com/status"
|
||||
},
|
||||
"Edit": {
|
||||
"Title": "Edit Application",
|
||||
"SaveButton": "Save Changes"
|
||||
},
|
||||
"Card": {
|
||||
"Server": "Server",
|
||||
"NoServer": "No server",
|
||||
"PublicURL": "Public URL",
|
||||
"LocalURL": "Local URL",
|
||||
"Icon": "Icon",
|
||||
"Image": "Image"
|
||||
},
|
||||
"Messages": {
|
||||
"AddServerFirst": "You must first add a server.",
|
||||
"AddSuccess": "Application added successfully",
|
||||
"EditSuccess": "Application edited successfully",
|
||||
"DeleteSuccess": "Application deleted successfully",
|
||||
"GetError": "Failed to get applications",
|
||||
"EditError": "Failed to edit application",
|
||||
"DeleteError": "Failed to delete application",
|
||||
"NumberValidation": "Please enter a number between 1 and 100"
|
||||
},
|
||||
"Pagination": {
|
||||
"Showing": "Showing {start}-{end} of {total} applications",
|
||||
"NoApplications": "No applications found"
|
||||
},
|
||||
"Search": {
|
||||
"Placeholder": "Type to search..."
|
||||
}
|
||||
},
|
||||
"Uptime": {
|
||||
"Title": "Uptime",
|
||||
"Breadcrumb": {
|
||||
"MyInfrastructure": "My Infrastructure",
|
||||
"Uptime": "Uptime"
|
||||
},
|
||||
"TimeRange": {
|
||||
"Select": "Select timespan",
|
||||
"LastHour": "Last 1 hour",
|
||||
"LastDay": "Last 1 day",
|
||||
"Last7Days": "Last 7 days",
|
||||
"Last30Days": "Last 30 days"
|
||||
},
|
||||
"Status": {
|
||||
"Online": "Online",
|
||||
"Offline": "Offline",
|
||||
"NoData": "No data"
|
||||
},
|
||||
"Messages": {
|
||||
"NumberValidation": "Please enter a number between 1 and 100",
|
||||
"NoItems": "No items found",
|
||||
"Loading": "Loading..."
|
||||
},
|
||||
"Pagination": {
|
||||
"Showing": "Showing {start}-{end} of {total} items"
|
||||
}
|
||||
},
|
||||
"Network": {
|
||||
"Title": "Network",
|
||||
"Breadcrumb": {
|
||||
"MyInfrastructure": "My Infrastructure",
|
||||
"Network": "Network"
|
||||
}
|
||||
},
|
||||
"Settings": {
|
||||
"Title": "Settings",
|
||||
"Breadcrumb": {
|
||||
"Dashboard": "Dashboard",
|
||||
"Settings": "Settings"
|
||||
},
|
||||
"UserSettings": {
|
||||
"Title": "User Settings",
|
||||
"Description": "Manage your user settings here. You can change your email, password, and other account settings.",
|
||||
"ChangeEmail": {
|
||||
"Title": "Change Email",
|
||||
"Placeholder": "Enter new email",
|
||||
"Button": "Change Email",
|
||||
"Success": "Email changed successfully.",
|
||||
"Error": "Error"
|
||||
},
|
||||
"ChangePassword": {
|
||||
"Title": "Change Password",
|
||||
"OldPassword": "Enter old password",
|
||||
"NewPassword": "Enter new password",
|
||||
"ConfirmPassword": "Confirm new password",
|
||||
"Button": "Change Password",
|
||||
"Success": "Password changed successfully.",
|
||||
"Error": "Error",
|
||||
"PasswordsDontMatch": "Passwords do not match",
|
||||
"AllFieldsRequired": "All fields are required"
|
||||
}
|
||||
},
|
||||
"ThemeSettings": {
|
||||
"Title": "Theme Settings",
|
||||
"Description": "Select a theme for the application. You can choose between light, dark, or system theme.",
|
||||
"Light": "Light",
|
||||
"Dark": "Dark",
|
||||
"System": "System"
|
||||
},
|
||||
"Notifications": {
|
||||
"Title": "Notifications",
|
||||
"Description": "Set up notifications to get instantly alerted when an application changes status.",
|
||||
"AddChannel": "Add Notification Channel",
|
||||
"NoNotifications": "No notifications configured",
|
||||
"NoNotificationsDescription": "Add a notification channel to get alerted when your applications change status.",
|
||||
"AddNotification": {
|
||||
"Title": "Add Notification",
|
||||
"Name": "Notification Name (optional)",
|
||||
"Type": "Notification Type",
|
||||
"SMTP": {
|
||||
"Title": "SMTP",
|
||||
"Host": "SMTP Host",
|
||||
"Port": "SMTP Port",
|
||||
"Secure": "Secure Connection (TLS/SSL)",
|
||||
"Username": "SMTP Username",
|
||||
"Password": "SMTP Password",
|
||||
"From": "From Address",
|
||||
"To": "To Address"
|
||||
},
|
||||
"Telegram": {
|
||||
"Title": "Telegram",
|
||||
"Token": "Bot Token",
|
||||
"ChatId": "Chat ID"
|
||||
},
|
||||
"Discord": {
|
||||
"Title": "Discord",
|
||||
"Webhook": "Webhook URL"
|
||||
},
|
||||
"Gotify": {
|
||||
"Title": "Gotify",
|
||||
"Url": "Gotify URL",
|
||||
"Token": "Gotify Token"
|
||||
},
|
||||
"Ntfy": {
|
||||
"Title": "Ntfy",
|
||||
"Url": "Ntfy URL",
|
||||
"Token": "Ntfy Token"
|
||||
},
|
||||
"Pushover": {
|
||||
"Title": "Pushover",
|
||||
"Url": "Pushover URL",
|
||||
"Token": "Pushover Token",
|
||||
"User": "Pushover User"
|
||||
}
|
||||
},
|
||||
"CustomizeText": {
|
||||
"Display": "Customize Notification Text",
|
||||
"Title": "Customize Notification Text",
|
||||
"Application": "Notification Text for Applications",
|
||||
"Server": "Notification Text for Servers",
|
||||
"Placeholders": {
|
||||
"Title": "You can use the following placeholders in the text:",
|
||||
"Server": {
|
||||
"Title": "Server related:",
|
||||
"Name": "!name - The name of the server",
|
||||
"Status": "!status - The current status of the server (online/offline)"
|
||||
},
|
||||
"Application": {
|
||||
"Title": "Application related:",
|
||||
"Name": "!name - The name of the application",
|
||||
"Url": "!url - The URL where the application is hosted",
|
||||
"Status": "!status - The current status of the application (online/offline)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ActiveChannels": "Active Notification Channels",
|
||||
"Test": "Test",
|
||||
"Delete": "Delete"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user