mirror of
https://github.com/Buriburizaem0n/admin-frontend-domain.git
synced 2026-05-06 05:38:51 +00:00
feat(I18n): Add multiple languages (zh-CN/zh-TW/en/it) (#8)
This commit is contained in:
+17
-12
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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
@@ -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">
|
||||
© 2019-2024 哪吒监控 Nezha Monitoring
|
||||
© 2019-2024 {t('nezha')}
|
||||
</footer>
|
||||
</Card>
|
||||
<Toaster />
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user