feat(I18n): Add multiple languages ​​(zh-CN/zh-TW/en/it) (#8)

This commit is contained in:
GuGuGu
2024-11-29 13:47:09 +01:00
committed by GitHub
parent 5850fe7fca
commit 47f092918e
44 changed files with 1138 additions and 366 deletions
+17 -12
View File
@@ -19,7 +19,11 @@ import { deleteAlertRules } from "@/api/alert-rule";
import { NotificationTab } from "@/components/notification-tab";
import { AlertRuleCard } from "@/components/alert-rule";
import { useTranslation } from "react-i18next";
export default function AlertRulePage() {
const { t } = useTranslation();
const { data, mutate, error, isLoading } = useSWR<ModelAlertRule[]>(
"/api/v1/alert-rule",
swrFetcher
@@ -27,9 +31,10 @@ export default function AlertRulePage() {
useEffect(() => {
if (error)
toast("Error", {
description: `Error fetching resource: ${error.message}.`,
toast(t("Error"), {
description: t("Results.ErrorFetchingResource", { error: error.message }),
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [error]);
const columns: ColumnDef<ModelAlertRule>[] = [
@@ -61,7 +66,7 @@ export default function AlertRulePage() {
accessorFn: (row) => row.id,
},
{
header: "Name",
header: t("Name"),
accessorKey: "name",
accessorFn: (row) => row.name,
cell: ({ row }) => {
@@ -70,38 +75,38 @@ export default function AlertRulePage() {
},
},
{
header: "Notifier Group",
header: t("NotifierGroup"),
accessorKey: "ngroup",
accessorFn: (row) => row.notification_group_id,
},
{
header: "Trigger Mode",
header: t("TriggerMode"),
accessorKey: "trigger Mode",
accessorFn: (row) => triggerModes[row.trigger_mode] || "",
},
{
header: "Rules",
header: t("Rules"),
accessorKey: "rules",
accessorFn: (row) => JSON.stringify(row.rules),
},
{
header: "Tasks to trigger on alert",
header: t("TasksToTriggerOnAlert"),
accessorKey: "failTriggerTasks",
accessorFn: (row) => row.fail_trigger_tasks,
},
{
header: "Tasks to trigger after recovery",
header: t("TasksToTriggerAfterRecovery"),
accessorKey: "recoverTriggerTasks",
accessorFn: (row) => row.recover_trigger_tasks,
},
{
header: "Enable",
header: t("Enable"),
accessorKey: "enable",
accessorFn: (row) => row.enable,
},
{
id: "actions",
header: "Actions",
header: t("Actions"),
cell: ({ row }) => {
const s = row.original;
return (
@@ -168,7 +173,7 @@ export default function AlertRulePage() {
{isLoading ? (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
Loading ...
{t("Loading")}...
</TableCell>
</TableRow>
) : table.getRowModel().rows?.length ? (
@@ -184,7 +189,7 @@ export default function AlertRulePage() {
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
{t("NoResults")}
</TableCell>
</TableRow>
)}
+24 -20
View File
@@ -20,14 +20,18 @@ import { CronCard } from "@/components/cron";
import { cronTypes } from "@/types";
import { IconButton } from "@/components/xui/icon-button";
import { useTranslation } from "react-i18next";
export default function CronPage() {
const { t } = useTranslation();
const { data, mutate, error, isLoading } = useSWR<ModelCron[]>("/api/v1/cron", swrFetcher);
useEffect(() => {
if (error)
toast("Error", {
description: `Error fetching resource: ${error.message}.`,
toast(t("Error"), {
description: t("Results.ErrorFetchingResource", { error: error.message }),
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [error]);
const columns: ColumnDef<ModelCron>[] = [
@@ -59,7 +63,7 @@ export default function CronPage() {
accessorFn: (row) => row.id,
},
{
header: "Name",
header: t("Name"),
accessorKey: "name",
cell: ({ row }) => {
const s = row.original;
@@ -67,17 +71,17 @@ export default function CronPage() {
},
},
{
header: "Task Type",
header: t("Type"),
accessorKey: "taskType",
accessorFn: (row) => cronTypes[row.task_type] || "",
},
{
header: "Cron Expression",
header: t("CronExpression"),
accessorKey: "scheduler",
accessorFn: (row) => row.scheduler,
},
{
header: "Command",
header: t("Command"),
accessorKey: "command",
cell: ({ row }) => {
const s = row.original;
@@ -85,17 +89,17 @@ export default function CronPage() {
},
},
{
header: "Notifier Group",
header: t("NotifierGroup"),
accessorKey: "ngroup",
accessorFn: (row) => row.notification_group_id,
},
{
header: "Send Success Notification",
header: t("SendSuccessNotification"),
accessorKey: "pushSuccessful",
accessorFn: (row) => row.push_successful ?? false,
},
{
header: "Coverage",
header: t("Coverage"),
accessorKey: "cover",
accessorFn: (row) => row.cover,
cell: ({ row }) => {
@@ -120,12 +124,12 @@ export default function CronPage() {
},
},
{
header: "Specific Servers",
header: t("SpecificServers"),
accessorKey: "servers",
accessorFn: (row) => row.servers,
},
{
header: "Last Execution",
header: t("LastExecution"),
accessorKey: "lastExecution",
accessorFn: (row) => row.last_executed_at,
cell: ({ row }) => {
@@ -134,13 +138,13 @@ export default function CronPage() {
},
},
{
header: "Last Result",
header: t("Result"),
accessorKey: "lastResult",
accessorFn: (row) => row.last_result ?? false,
},
{
id: "actions",
header: "Actions",
header: t("Actions"),
cell: ({ row }) => {
const s = row.original;
return (
@@ -157,14 +161,14 @@ export default function CronPage() {
await runCron(s.id);
} catch (e) {
console.error(e);
toast("Error executing task", {
description: "Please see the console for details.",
toast(t("Error"), {
description: t("Results.UnExpectedError"),
});
await mutate();
return;
}
toast("Success", {
description: "The task triggered successfully.",
toast(t("Success"), {
description: t("Results.TaskTriggeredSuccessfully"),
});
await mutate();
}}
@@ -192,7 +196,7 @@ export default function CronPage() {
return (
<div className="px-8">
<div className="flex mt-6 mb-4">
<h1 className="flex-1 text-3xl font-bold tracking-tight">Task</h1>
<h1 className="flex-1 text-3xl font-bold tracking-tight">{t("Task")}</h1>
<HeaderButtonGroup
className="flex-2 flex ml-auto gap-2"
delete={{
@@ -225,7 +229,7 @@ export default function CronPage() {
{isLoading ? (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
Loading ...
{t("Loading")}...
</TableCell>
</TableRow>
) : table.getRowModel().rows?.length ? (
@@ -241,7 +245,7 @@ export default function CronPage() {
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
{t("NoResults")}
</TableCell>
</TableRow>
)}
+16 -12
View File
@@ -18,7 +18,10 @@ import { HeaderButtonGroup } from "@/components/header-button-group";
import { toast } from "sonner";
import { deleteDDNSProfiles, getDDNSProviders } from "@/api/ddns";
import { useTranslation } from "react-i18next";
export default function DDNSPage() {
const { t } = useTranslation();
const { data, mutate, error, isLoading } = useSWR<ModelDDNSProfile[]>("/api/v1/ddns", swrFetcher);
const [providers, setProviders] = useState<string[]>([]);
@@ -32,9 +35,10 @@ export default function DDNSPage() {
useEffect(() => {
if (error)
toast("Error", {
description: `Error fetching resource: ${error.message}.`,
toast(t("Error"), {
description: t("Results.ErrorFetchingResource", { error: error.message }),
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [error]);
const columns: ColumnDef<ModelDDNSProfile>[] = [
@@ -66,7 +70,7 @@ export default function DDNSPage() {
accessorFn: (row) => row.id,
},
{
header: "Name",
header: t("Name"),
accessorKey: "name",
accessorFn: (row) => row.name,
cell: ({ row }) => {
@@ -75,22 +79,22 @@ export default function DDNSPage() {
},
},
{
header: "IPv4 Enabled",
header: "IPv4",
accessorKey: "enableIPv4",
accessorFn: (row) => row.enable_ipv4 ?? false,
},
{
header: "IPv6 Enabled",
header: "IPv6",
accessorKey: "enableIPv6",
accessorFn: (row) => row.enable_ipv6 ?? false,
},
{
header: "DDNS Provider",
header: t("Provider"),
accessorKey: "provider",
accessorFn: (row) => row.provider,
},
{
header: "Domains",
header: t('Domains'),
accessorKey: "domains",
accessorFn: (row) => row.domains,
cell: ({ row }) => {
@@ -99,13 +103,13 @@ export default function DDNSPage() {
},
},
{
header: "Maximum retry attempts",
header: t("MaximumRetryAttempts"),
accessorKey: "maxRetries",
accessorFn: (row) => row.max_retries,
},
{
id: "actions",
header: "Actions",
header: t("Actions"),
cell: ({ row }) => {
const s = row.original;
return (
@@ -135,7 +139,7 @@ export default function DDNSPage() {
return (
<div className="px-8">
<div className="flex mt-6 mb-4">
<h1 className="flex-1 text-3xl font-bold tracking-tight">Dynamic DNS</h1>
<h1 className="flex-1 text-3xl font-bold tracking-tight">{t("DDNS")}</h1>
<HeaderButtonGroup
className="flex-2 flex ml-auto gap-2"
delete={{
@@ -168,7 +172,7 @@ export default function DDNSPage() {
{isLoading ? (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
Loading ...
{t("Loading")}...
</TableCell>
</TableRow>
) : table.getRowModel().rows?.length ? (
@@ -184,7 +188,7 @@ export default function DDNSPage() {
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
{t("NoResults")}
</TableCell>
</TableRow>
)}
+16 -15
View File
@@ -6,7 +6,6 @@ import { Button } from "@/components/ui/button"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
@@ -15,16 +14,20 @@ import {
import { Input } from "@/components/ui/input"
import { useAuth } from "@/hooks/useAuth"
import { useTranslation } from "react-i18next"
import i18next from "i18next";
const formSchema = z.object({
username: z.string().min(2, {
message: "Username must be at least 2 characters.",
message: i18next.t("Results.UsernameMin", { number: 2 }),
}),
password: z.string().min(1, {
message: "Password cannot be empty.",
message: i18next.t("Results.PasswordRequired"),
})
})
export default () => {
function Login() {
const { login } = useAuth()
const form = useForm<z.infer<typeof formSchema>>({
@@ -39,6 +42,8 @@ export default () => {
login(values.username, values.password)
}
const { t } = useTranslation();
return (
<div className="my-8 max-w-xl m-auto">
<Form {...form}>
@@ -48,13 +53,10 @@ export default () => {
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormLabel>{t("Username")}</FormLabel>
<FormControl>
<Input placeholder="shadcn" autoComplete="username" {...field} />
<Input placeholder="admin" autoComplete="username" {...field} />
</FormControl>
<FormDescription>
This is your public display name.
</FormDescription>
<FormMessage />
</FormItem>
)}
@@ -64,20 +66,19 @@ export default () => {
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormLabel>{t("Password")}</FormLabel>
<FormControl>
<Input type="password" placeholder="shadcn" autoComplete="current-password" {...field} />
<Input type="password" placeholder="admin" autoComplete="current-password" {...field} />
</FormControl>
<FormDescription>
This is your public display name.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Login</Button>
<Button type="submit">{t("Login")}</Button>
</form>
</Form>
</div>
)
}
export default Login;
+14 -10
View File
@@ -18,14 +18,18 @@ import { HeaderButtonGroup } from "@/components/header-button-group";
import { toast } from "sonner";
import { deleteNAT } from "@/api/nat";
import { useTranslation } from "react-i18next";
export default function NATPage() {
const { t } = useTranslation();
const { data, mutate, error, isLoading } = useSWR<ModelNAT[]>("/api/v1/nat", swrFetcher);
useEffect(() => {
if (error)
toast("Error", {
description: `Error fetching resource: ${error.message}.`,
toast(t("Error"), {
description: t("Results.ErrorFetchingResource", { error: error.message }),
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [error]);
const columns: ColumnDef<ModelNAT>[] = [
@@ -57,7 +61,7 @@ export default function NATPage() {
accessorFn: (row) => row.id,
},
{
header: "Name",
header: t("Name"),
accessorKey: "name",
accessorFn: (row) => row.name,
cell: ({ row }) => {
@@ -66,12 +70,12 @@ export default function NATPage() {
},
},
{
header: "Server ID",
header: t("Server")+" ID",
accessorKey: "serverID",
accessorFn: (row) => row.server_id,
},
{
header: "Local service",
header: t("LocalService"),
accessorKey: "host",
accessorFn: (row) => row.host,
cell: ({ row }) => {
@@ -80,7 +84,7 @@ export default function NATPage() {
},
},
{
header: "Bind hostname",
header: t("BindHostname"),
accessorKey: "domain",
accessorFn: (row) => row.domain,
cell: ({ row }) => {
@@ -90,7 +94,7 @@ export default function NATPage() {
},
{
id: "actions",
header: "Actions",
header: t("Actions"),
cell: ({ row }) => {
const s = row.original;
return (
@@ -120,7 +124,7 @@ export default function NATPage() {
return (
<div className="px-8">
<div className="flex mt-6 mb-4">
<h1 className="flex-1 text-3xl font-bold tracking-tight">NAT Traversal</h1>
<h1 className="flex-1 text-3xl font-bold tracking-tight"> {t("NATT")}</h1>
<HeaderButtonGroup
className="flex-2 flex ml-auto gap-2"
delete={{
@@ -153,7 +157,7 @@ export default function NATPage() {
{isLoading ? (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
Loading ...
{t("Loading")}...
</TableCell>
</TableRow>
) : table.getRowModel().rows?.length ? (
@@ -169,7 +173,7 @@ export default function NATPage() {
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
{t("NoResults")}
</TableCell>
</TableRow>
)}
+11 -7
View File
@@ -19,7 +19,10 @@ import { deleteNotificationGroups } from "@/api/notification-group";
import { GroupTab } from "@/components/group-tab";
import { NotificationGroupCard } from "@/components/notification-group";
import { useTranslation } from "react-i18next";
export default function NotificationGroupPage() {
const { t } = useTranslation();
const { data, mutate, error, isLoading } = useSWR<ModelNotificationGroupResponseItem[]>(
"/api/v1/notification-group",
swrFetcher
@@ -27,9 +30,10 @@ export default function NotificationGroupPage() {
useEffect(() => {
if (error)
toast("Error", {
description: `Error fetching resource: ${error.message}.`,
toast(t("Error"), {
description: t("Results.ErrorFetchingResource", { error: error.message }),
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [error]);
const columns: ColumnDef<ModelNotificationGroupResponseItem>[] = [
@@ -61,7 +65,7 @@ export default function NotificationGroupPage() {
accessorFn: (row) => row.group.id,
},
{
header: "Name",
header: t("Name"),
accessorKey: "name",
accessorFn: (row) => row.group.name,
cell: ({ row }) => {
@@ -70,13 +74,13 @@ export default function NotificationGroupPage() {
},
},
{
header: "Notifiers (ID)",
header: t("Notifier")+"(ID)",
accessorKey: "notifications",
accessorFn: (row) => row.notifications,
},
{
id: "actions",
header: "Actions",
header: t("Actions"),
cell: ({ row }) => {
const s = row.original;
return (
@@ -143,7 +147,7 @@ export default function NotificationGroupPage() {
{isLoading ? (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
Loading ...
{t("Loading")}...
</TableCell>
</TableRow>
) : table.getRowModel().rows?.length ? (
@@ -159,7 +163,7 @@ export default function NotificationGroupPage() {
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
{t("NoResults")}
</TableCell>
</TableRow>
)}
+13 -8
View File
@@ -20,7 +20,11 @@ import { NotificationTab } from "@/components/notification-tab";
import { NotifierCard } from "@/components/notifier";
import { useNotification } from "@/hooks/useNotfication";
import { useTranslation } from "react-i18next";
export default function NotificationPage() {
const { t } = useTranslation();
const { data, mutate, error, isLoading } = useSWR<ModelNotification[]>(
"/api/v1/notification",
swrFetcher
@@ -29,9 +33,10 @@ export default function NotificationPage() {
useEffect(() => {
if (error)
toast("Error", {
description: `Error fetching resource: ${error.message}.`,
toast(t("Error"), {
description: t("Results.ErrorFetchingResource", { error: error.message }),
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [error]);
const columns: ColumnDef<ModelNotification>[] = [
@@ -63,7 +68,7 @@ export default function NotificationPage() {
accessorFn: (row) => row.id,
},
{
header: "Name",
header: t("Name"),
accessorKey: "name",
accessorFn: (row) => row.name,
cell: ({ row }) => {
@@ -72,7 +77,7 @@ export default function NotificationPage() {
},
},
{
header: "Groups",
header: t("Group"),
accessorKey: "groups",
accessorFn: (row) => {
return (
@@ -92,13 +97,13 @@ export default function NotificationPage() {
},
},
{
header: "Verify TLS",
header: t("VerifyTLS"),
accessorKey: "verify_tls",
accessorFn: (row) => row.verify_tls,
},
{
id: "actions",
header: "Actions",
header: t("Actions"),
cell: ({ row }) => {
const s = row.original;
return (
@@ -165,7 +170,7 @@ export default function NotificationPage() {
{isLoading ? (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
Loading ...
{t("Loading")}...
</TableCell>
</TableRow>
) : table.getRowModel().rows?.length ? (
@@ -181,7 +186,7 @@ export default function NotificationPage() {
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
{t("NoResults")}
</TableCell>
</TableRow>
)}
+4 -1
View File
@@ -5,7 +5,10 @@ import { ThemeProvider } from "@/components/theme-provider";
import Header from "@/components/header";
import { Toaster } from "@/components/ui/sonner";
import { useTranslation } from "react-i18next";
export default function Root() {
const { t } = useTranslation();
return (
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
<Card className="text-sm max-w-7xl mx-auto mt-5 min-h-[90%] flex flex-col justify-between">
@@ -14,7 +17,7 @@ export default function Root() {
<Outlet />
</div>
<footer className="mx-5 pb-5 text-foreground/60 font-thin text-center">
&copy; 2019-2024 Nezha Monitoring
&copy; 2019-2024 {t('nezha')}
</footer>
</Card>
<Toaster />
+11 -7
View File
@@ -19,7 +19,10 @@ import { deleteServerGroups } from "@/api/server-group";
import { GroupTab } from "@/components/group-tab";
import { ServerGroupCard } from "@/components/server-group";
import { useTranslation } from "react-i18next";
export default function ServerGroupPage() {
const { t } = useTranslation();
const { data, mutate, error, isLoading } = useSWR<ModelServerGroupResponseItem[]>(
"/api/v1/server-group",
swrFetcher
@@ -27,9 +30,10 @@ export default function ServerGroupPage() {
useEffect(() => {
if (error)
toast("Error", {
description: `Error fetching resource: ${error.message}.`,
toast(t("Error"), {
description: t("Results.ErrorFetchingResource", { error: error.message }),
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [error]);
const columns: ColumnDef<ModelServerGroupResponseItem>[] = [
@@ -61,7 +65,7 @@ export default function ServerGroupPage() {
accessorFn: (row) => row.group.id,
},
{
header: "Name",
header: t("Name"),
accessorKey: "name",
accessorFn: (row) => row.group.name,
cell: ({ row }) => {
@@ -70,13 +74,13 @@ export default function ServerGroupPage() {
},
},
{
header: "Servers (ID)",
header: t("Server")+"(ID)",
accessorKey: "servers",
accessorFn: (row) => row.servers,
},
{
id: "actions",
header: "Actions",
header: t("Actions"),
cell: ({ row }) => {
const s = row.original;
return (
@@ -142,7 +146,7 @@ export default function ServerGroupPage() {
{isLoading ? (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
Loading ...
{t("Loading")}...
</TableCell>
</TableRow>
) : table.getRowModel().rows?.length ? (
@@ -158,7 +162,7 @@ export default function ServerGroupPage() {
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
{t("NoResults")}
</TableCell>
</TableRow>
)}
+27 -25
View File
@@ -24,15 +24,19 @@ import { TerminalButton } from "@/components/terminal";
import { useServer } from "@/hooks/useServer";
import { joinIP } from "@/lib/utils";
import { useTranslation } from "react-i18next";
export default function ServerPage() {
const { t } = useTranslation();
const { data, mutate, error, isLoading } = useSWR<Server[]>("/api/v1/server", swrFetcher);
const { serverGroups } = useServer();
useEffect(() => {
if (error)
toast("Error", {
description: `Error fetching resource: ${error.message}.`,
toast(t("Error"), {
description: t("Results.ErrorFetchingResource", { error: error.message }),
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [error]);
const columns: ColumnDef<Server>[] = [
@@ -64,7 +68,7 @@ export default function ServerPage() {
accessorFn: (row) => `${row.id}(${row.display_index})`,
},
{
header: "Name",
header: t("Name"),
accessorKey: "name",
accessorFn: (row) => row.name,
cell: ({ row }) => {
@@ -73,7 +77,7 @@ export default function ServerPage() {
},
},
{
header: "Groups",
header: t("Group"),
accessorKey: "groups",
accessorFn: (row) => {
return (
@@ -90,28 +94,28 @@ export default function ServerPage() {
},
},
{
header: "Version",
header: t("Version"),
accessorKey: "host.version",
accessorFn: (row) => row.host.version || "Unknown",
accessorFn: (row) => row.host.version || t("Unknown"),
},
{
header: "Enable DDNS",
header: t("Enable") + t("DDNS"),
accessorKey: "enableDDNS",
accessorFn: (row) => row.enable_ddns ?? false,
},
{
header: "Hide from Guest",
header: t("HideForGuest"),
accessorKey: "hideForGuest",
accessorFn: (row) => row.hide_for_guest ?? false,
},
{
id: "installCommands",
header: "Install commands",
header: t("InstallCommands"),
cell: () => <InstallCommandsMenu />,
},
{
id: "note",
header: "Note",
header: t("Note"),
cell: ({ row }) => {
const s = row.original;
return <NoteMenu note={{ private: s.note, public: s.public_note }} />;
@@ -119,7 +123,7 @@ export default function ServerPage() {
},
{
id: "actions",
header: "Actions",
header: t("Actions"),
cell: ({ row }) => {
const s = row.original;
return (
@@ -152,7 +156,7 @@ export default function ServerPage() {
return (
<div className="px-8">
<div className="flex mt-6 mb-4">
<h1 className="text-3xl font-bold tracking-tight">Server</h1>
<h1 className="text-3xl font-bold tracking-tight">{t("Server")}</h1>
<HeaderButtonGroup
className="flex-2 flex ml-auto gap-2"
delete={{
@@ -166,8 +170,8 @@ export default function ServerPage() {
onClick={async () => {
const id = selectedRows.map((r) => r.original.id);
if (id.length < 1) {
toast("Error", {
description: "No rows are selected.",
toast(t("Error"), {
description: t("Results.SelectAtLeastOneServer"),
});
return;
}
@@ -177,23 +181,21 @@ export default function ServerPage() {
resp = await forceUpdateServer(id);
} catch (e) {
console.error(e);
toast("Error executing task", {
description: "Please see the console for details.",
toast(t("Error"), {
description: t("Results.UnExpectedError"),
});
return;
}
toast("Task executed successfully", {
description: `Result (Server ID):
${resp.success?.length ? `Success: ${resp.success.join(",")}, ` : ""}
${resp.failure?.length ? `Failure: ${resp.failure.join(",")}, ` : ""}
${resp.offline?.length ? `Offline: ${resp.offline.join(",")}` : ""}
`,
toast(t("Done"), {
description: t("Results.ForceUpdate")
+ (resp.success?.length ? t(`Success`) + ` [${resp.success.join(",")}]` : "")
+ (resp.failure?.length ? t(`Failure`) + ` [${resp.failure.join(",")}]` : "")
+ (resp.offline?.length ? t(`Offline`) + ` [${resp.offline.join(",")}]` : "")
});
}}
/>
</HeaderButtonGroup>
</div>
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
@@ -214,7 +216,7 @@ export default function ServerPage() {
{isLoading ? (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
Loading ...
{t("Loading")}...
</TableCell>
</TableRow>
) : table.getRowModel().rows?.length ? (
@@ -230,7 +232,7 @@ export default function ServerPage() {
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
{t("NoResults")}
</TableCell>
</TableRow>
)}
+22 -18
View File
@@ -20,7 +20,10 @@ import { deleteService } from "@/api/service";
import { HeaderButtonGroup } from "@/components/header-button-group";
import { toast } from "sonner";
import { useTranslation } from "react-i18next";
export default function ServicePage() {
const { t } = useTranslation();
const { data, mutate, error, isLoading } = useSWR<ModelServiceResponse>(
"/api/v1/service",
swrFetcher
@@ -28,9 +31,10 @@ export default function ServicePage() {
useEffect(() => {
if (error)
toast("Error", {
description: `Error fetching resource: ${error.message}.`,
toast(t("Error"), {
description: t("Results.ErrorFetchingResource", { error: error.message }),
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [error]);
const columns: ColumnDef<Service>[] = [
@@ -62,7 +66,7 @@ export default function ServicePage() {
accessorFn: (row) => row.service.id,
},
{
header: "Name",
header: t("Name"),
accessorFn: (row) => row.service.name,
accessorKey: "service.name",
cell: ({ row }) => {
@@ -71,7 +75,7 @@ export default function ServicePage() {
},
},
{
header: "Target",
header: t("Target"),
accessorFn: (row) => row.service.target,
accessorKey: "service.target",
cell: ({ row }) => {
@@ -80,7 +84,7 @@ export default function ServicePage() {
},
},
{
header: "Coverage",
header: t("Coverage"),
accessorKey: "service.cover",
accessorFn: (row) => row.service.cover,
cell: ({ row }) => {
@@ -90,10 +94,10 @@ export default function ServicePage() {
{(() => {
switch (s.cover) {
case 0: {
return <span>Cover All</span>;
return <span>{t("CoverAll")}</span>;
}
case 1: {
return <span>Ignore All</span>;
return <span>{t("IgnoreAll")}</span>;
}
}
})()}
@@ -102,44 +106,44 @@ export default function ServicePage() {
},
},
{
header: "Specific Servers",
header: t("SpecificServers"),
accessorKey: "service.skipServers",
accessorFn: (row) => Object.keys(row.service.skip_servers ?? {}),
},
{
header: "Type",
header: t("Type"),
accessorKey: "service.type",
accessorFn: (row) => row.service.type,
cell: ({ row }) => serviceTypes[row.original.service.type] || "",
},
{
header: "Interval",
header: t("Interval"),
accessorKey: "service.duration",
accessorFn: (row) => row.service.duration,
},
{
header: "Notifier Group ID",
header: t("NotifierGroupID"),
accessorKey: "service.ngroup",
accessorFn: (row) => row.service.notification_group_id,
},
{
header: "On Trigger",
header: t("Trigger"),
accessorKey: "service.triggerTask",
accessorFn: (row) => row.service.enable_trigger_task ?? false,
},
{
header: "Tasks to trigger on alert",
header: t("TasksToTriggerOnAlert"),
accessorKey: "service.failTriggerTasks",
accessorFn: (row) => row.service.fail_trigger_tasks,
},
{
header: "Tasks to trigger after recovery",
header: t("TasksToTriggerAfterRecovery"),
accessorKey: "service.recoverTriggerTasks",
accessorFn: (row) => row.service.recover_trigger_tasks,
},
{
id: "actions",
header: "Actions",
header: t("Actions"),
cell: ({ row }) => {
const s = row.original;
return (
@@ -169,7 +173,7 @@ export default function ServicePage() {
return (
<div className="px-8">
<div className="flex mt-6 mb-4">
<h1 className="flex-1 text-3xl font-bold tracking-tight">Service</h1>
<h1 className="flex-1 text-3xl font-bold tracking-tight">{t("Services")}</h1>
<HeaderButtonGroup
className="flex-2 flex ml-auto gap-2"
delete={{
@@ -202,7 +206,7 @@ export default function ServicePage() {
{isLoading ? (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
Loading ...
{t("Loading")}...
</TableCell>
</TableRow>
) : table.getRowModel().rows?.length ? (
@@ -218,7 +222,7 @@ export default function ServicePage() {
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
{t("NoResults")}
</TableCell>
</TableRow>
)}
+20 -18
View File
@@ -29,6 +29,8 @@ import {
SelectValue,
} from "@/components/ui/select";
import { useTranslation } from "react-i18next";
const settingFormSchema = z.object({
custom_nameservers: asOptionalField(z.string()),
ignored_ip_notification: asOptionalField(z.string()),
@@ -46,14 +48,16 @@ const settingFormSchema = z.object({
});
export default function SettingsPage() {
const { t } = useTranslation();
const [config, setConfig] = useState<ModelConfig>();
const [error, setError] = useState<Error>();
useEffect(() => {
if (error)
toast("Error", {
description: `Error fetching resource: ${error.message}.`,
toast(t("Error"), {
description: t("Results.ErrorFetchingResource", { error: error.message }),
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [error]);
useEffect(() => {
@@ -98,9 +102,7 @@ export default function SettingsPage() {
if (e instanceof Error) setError(e);
return;
} finally {
toast("Success", {
description: "Config updated successfully.",
});
toast(t("Success"));
}
};
@@ -115,7 +117,7 @@ export default function SettingsPage() {
name="site_name"
render={({ field }) => (
<FormItem>
<FormLabel>Site Name</FormLabel>
<FormLabel>{t("SiteName")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -128,7 +130,7 @@ export default function SettingsPage() {
name="language"
render={({ field }) => (
<FormItem>
<FormLabel>Language</FormLabel>
<FormLabel>{t("Language")}</FormLabel>
<FormControl>
<Select onValueChange={field.onChange} value={field.value}>
<FormControl>
@@ -154,7 +156,7 @@ export default function SettingsPage() {
name="custom_code"
render={({ field }) => (
<FormItem>
<FormLabel>Custom Codes (Style and Script)</FormLabel>
<FormLabel>{t("CustomCodes")}</FormLabel>
<FormControl>
<Textarea className="resize-y min-h-48" {...field} />
</FormControl>
@@ -167,7 +169,7 @@ export default function SettingsPage() {
name="custom_code_dashboard"
render={({ field }) => (
<FormItem>
<FormLabel>Custom Codes for Dashboard</FormLabel>
<FormLabel>{t("CustomCodesDashboard")}</FormLabel>
<FormControl>
<Textarea className="resize-y min-h-48" {...field} />
</FormControl>
@@ -180,7 +182,7 @@ export default function SettingsPage() {
name="install_host"
render={({ field }) => (
<FormItem>
<FormLabel>Dashboard Server Domain/IP without CDN</FormLabel>
<FormLabel>{t("DashboardOriginalHost")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -194,7 +196,7 @@ export default function SettingsPage() {
render={({ field }) => (
<FormItem>
<FormLabel>
Custom Public DNS Nameservers for DDNS (separate with comma)
{t("CustomPublicDNSNameserversforDDNS")+" " + t("SeparateWithComma")}
</FormLabel>
<FormControl>
<Input {...field} />
@@ -208,7 +210,7 @@ export default function SettingsPage() {
name="real_ip_header"
render={({ field }) => (
<FormItem>
<FormLabel>Real IP Header</FormLabel>
<FormLabel>{t("RealIPHeader")}</FormLabel>
<FormControl>
<Input placeholder="NZ::Use-Peer-IP" {...field} />
</FormControl>
@@ -217,7 +219,7 @@ export default function SettingsPage() {
)}
/>
<FormItem>
<FormLabel>IP Change Notification</FormLabel>
<FormLabel>{t("IPChangeNotification")}</FormLabel>
<Card className="w-full">
<CardContent>
<div className="flex flex-col space-y-4 mt-4">
@@ -226,7 +228,7 @@ export default function SettingsPage() {
name="cover"
render={({ field }) => (
<FormItem>
<FormLabel>Coverage</FormLabel>
<FormLabel>{t("Coverage")}</FormLabel>
<Select onValueChange={field.onChange} value={`${field.value}`}>
<FormControl>
<SelectTrigger>
@@ -250,7 +252,7 @@ export default function SettingsPage() {
name="ignored_ip_notification"
render={({ field }) => (
<FormItem>
<FormLabel>Specific Servers (separate with comma)</FormLabel>
<FormLabel>{t("SpecificServers")+" " + t("SeparateWithComma")}</FormLabel>
<FormControl>
<Input placeholder="1,2,3" {...field} />
</FormControl>
@@ -266,7 +268,7 @@ export default function SettingsPage() {
<FormControl>
<div className="flex items-center gap-2">
<Checkbox checked={field.value} onCheckedChange={field.onChange} />
<Label className="text-sm">Enable</Label>
<Label className="text-sm">{t("Enable")}</Label>
</div>
</FormControl>
<FormMessage />
@@ -286,7 +288,7 @@ export default function SettingsPage() {
<div className="flex items-center gap-2">
<Checkbox checked={field.value} onCheckedChange={field.onChange} />
<Label className="text-sm">
Show Full IP Address in Notification Messages
{t("FullIPNotification")}
</Label>
</div>
</FormControl>
@@ -294,7 +296,7 @@ export default function SettingsPage() {
</FormItem>
)}
/>
<Button type="submit">Save</Button>
<Button type="submit">{t("Confirm")}</Button>
</form>
</Form>
</div>
+10 -6
View File
@@ -19,14 +19,18 @@ import { deleteUser } from "@/api/user";
import { SettingsTab } from "@/components/settings-tab";
import { UserCard } from "@/components/user";
import { useTranslation } from "react-i18next";
export default function UserPage() {
const { t } = useTranslation();
const { data, mutate, error, isLoading } = useSWR<ModelUser[]>("/api/v1/user", swrFetcher);
useEffect(() => {
if (error)
toast("Error", {
description: `Error fetching resource: ${error.message}.`,
toast(t("Error"), {
description: t("Results.UnExpectedError", { error: error.message }),
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [error]);
const columns: ColumnDef<ModelUser>[] = [
@@ -58,13 +62,13 @@ export default function UserPage() {
accessorFn: (row) => row.id,
},
{
header: "Username",
header: t("Username"),
accessorKey: "username",
accessorFn: (row) => row.username,
},
{
id: "actions",
header: "Actions",
header: t("Actions"),
cell: ({ row }) => {
const s = row.original;
return (
@@ -131,7 +135,7 @@ export default function UserPage() {
{isLoading ? (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
Loading ...
{t("Loading")}...
</TableCell>
</TableRow>
) : table.getRowModel().rows?.length ? (
@@ -147,7 +151,7 @@ export default function UserPage() {
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
{t("NoResults")}
</TableCell>
</TableRow>
)}
+11 -7
View File
@@ -19,14 +19,18 @@ import { deleteWAF } from "@/api/waf";
import { ip16Str } from "@/lib/utils";
import { SettingsTab } from "@/components/settings-tab";
import { useTranslation } from "react-i18next";
export default function WAFPage() {
const { t } = useTranslation();
const { data, mutate, error, isLoading } = useSWR<ModelWAF[]>("/api/v1/waf", swrFetcher);
useEffect(() => {
if (error)
toast("Error", {
description: `Error fetching resource: ${error.message}.`,
toast(t("Error"), {
description: t(`Error fetching resource: ${error.message}.`),
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [error]);
const columns: ColumnDef<ModelWAF>[] = [
@@ -58,18 +62,18 @@ export default function WAFPage() {
accessorFn: (row) => ip16Str(row.ip ?? []),
},
{
header: "Count",
header: t("Count"),
accessorKey: "count",
accessorFn: (row) => row.count,
},
{
header: "Last Block Reason",
header: t("LastBlockReason"),
accessorKey: "lastBlockReason",
accessorFn: (row) => row.last_block_reason,
cell: ({ row }) => <span>{wafBlockReasons[row.original.last_block_reason] || ""}</span>,
},
{
header: "Last Block Time",
header: t("LastBlockTime"),
accessorKey: "lastBlockTime",
accessorFn: (row) => row.last_block_timestamp,
cell: ({ row }) => {
@@ -146,7 +150,7 @@ export default function WAFPage() {
{isLoading ? (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
Loading ...
{t("Loading")}...
</TableCell>
</TableRow>
) : table.getRowModel().rows?.length ? (
@@ -162,7 +166,7 @@ export default function WAFPage() {
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
{t("NoResults")}
</TableCell>
</TableRow>
)}