mirror of
https://github.com/crocofied/CoreControl.git
synced 2025-12-29 16:14:43 +00:00
i18n final
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user