mirror of
https://github.com/Buriburizaem0n/admin-frontend-domain.git
synced 2026-02-04 04:30:06 +00:00
feat(I18n): Add multiple languages (zh-CN/zh-TW/en/it) (#8)
This commit is contained in:
@@ -32,12 +32,14 @@
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
"i18next": "^24.0.2",
|
||||
"jotai-zustand": "^0.6.0",
|
||||
"lucide-react": "^0.454.0",
|
||||
"next-themes": "^0.3.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-hook-form": "^7.53.1",
|
||||
"react-i18next": "^15.1.2",
|
||||
"react-router-dom": "^6.27.0",
|
||||
"react-use-websocket": "^4.10.1",
|
||||
"react-virtuoso": "^4.12.0",
|
||||
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
import { KeyedMutator } from "swr";
|
||||
import { buttonVariants } from "@/components/ui/button"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface ButtonGroupProps<E, U> {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
@@ -20,6 +22,7 @@ interface ButtonGroupProps<E, U> {
|
||||
}
|
||||
|
||||
export function ActionButtonGroup<E, U>({ className, children, delete: { fn, id, mutate } }: ButtonGroupProps<E, U>) {
|
||||
const { t } = useTranslation();
|
||||
const handleDelete = async () => {
|
||||
await fn([id]);
|
||||
await mutate();
|
||||
@@ -30,18 +33,18 @@ export function ActionButtonGroup<E, U>({ className, children, delete: { fn, id,
|
||||
{children}
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<IconButton variant="outline" icon="trash" />
|
||||
<IconButton variant="destructive" icon="trash" />
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent className="sm:max-w-lg">
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Confirm Deletion?</AlertDialogTitle>
|
||||
<AlertDialogTitle>{t("ConfirmDeletion")}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This operation is unrecoverable!
|
||||
{t("Results.ThisOperationIsUnrecoverable")}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction className={buttonVariants({ variant: "destructive" })} onClick={handleDelete}>Confirm</AlertDialogAction>
|
||||
<AlertDialogCancel>{t("Close")}</AlertDialogCancel>
|
||||
<AlertDialogAction className={buttonVariants({ variant: "destructive" })} onClick={handleDelete}>{t("Confirm")}</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
@@ -43,6 +43,8 @@ import { Textarea } from "./ui/textarea"
|
||||
import { useNotification } from "@/hooks/useNotfication"
|
||||
import { Combobox } from "./ui/combobox"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface AlertRuleCardProps {
|
||||
data?: ModelAlertRule;
|
||||
mutate: KeyedMutator<ModelAlertRule[]>;
|
||||
@@ -83,6 +85,7 @@ const alertRuleFormSchema = z.object({
|
||||
});
|
||||
|
||||
export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) => {
|
||||
const { t } = useTranslation();
|
||||
const form = useForm<z.infer<typeof alertRuleFormSchema>>({
|
||||
resolver: zodResolver(alertRuleFormSchema),
|
||||
defaultValues: data ? {
|
||||
@@ -133,7 +136,7 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
|
||||
<ScrollArea className="max-h-[calc(100dvh-5rem)] p-3">
|
||||
<div className="items-center mx-1">
|
||||
<DialogHeader>
|
||||
<DialogTitle>New Alert Rule</DialogTitle>
|
||||
<DialogTitle>{data ? t("EditAlertRule") : t("CreateAlertRule")}</DialogTitle>
|
||||
<DialogDescription />
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
@@ -143,7 +146,7 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormLabel>{t("Name")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
@@ -158,7 +161,7 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
|
||||
name="rules_raw"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Rules</FormLabel>
|
||||
<FormLabel>{t("Rules")}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
className="resize-y"
|
||||
@@ -174,7 +177,7 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
|
||||
name="notification_group_id"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Notifier Group</FormLabel>
|
||||
<FormLabel>{t("NotifierGroup")}</FormLabel>
|
||||
<FormControl>
|
||||
<Combobox
|
||||
placeholder="Search..."
|
||||
@@ -192,7 +195,7 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
|
||||
name="trigger_mode"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Trigger Mode</FormLabel>
|
||||
<FormLabel>{t("TriggerMode")}</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={`${field.value}`}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
@@ -214,7 +217,7 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
|
||||
name="fail_trigger_tasks"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Tasks to trigger on an alarm (Separate with comma)</FormLabel>
|
||||
<FormLabel>{t("TasksToTriggerOnAlert") + t("SeparateWithComma")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="1,2,3"
|
||||
@@ -235,7 +238,7 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
|
||||
name="recover_trigger_tasks"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Tasks to trigger after recovery (Separate with comma)</FormLabel>
|
||||
<FormLabel>{t("TasksToTriggerAfterRecovery") + t("SeparateWithComma")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="1,2,3"
|
||||
@@ -262,7 +265,7 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<Label className="text-sm">Enable</Label>
|
||||
<Label className="text-sm">{t("Enable")}</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -272,10 +275,10 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
|
||||
<DialogFooter className="justify-end">
|
||||
<DialogClose asChild>
|
||||
<Button type="button" className="my-2" variant="secondary">
|
||||
Close
|
||||
{t("Close")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit" className="my-2">Submit</Button>
|
||||
<Button type="submit" className="my-2">{t("Confirm")}</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -42,6 +42,8 @@ import { useNotification } from "@/hooks/useNotfication"
|
||||
import { MultiSelect } from "./xui/multi-select"
|
||||
import { Combobox } from "./ui/combobox"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface CronCardProps {
|
||||
data?: ModelCron;
|
||||
mutate: KeyedMutator<ModelCron[]>;
|
||||
@@ -59,6 +61,7 @@ const cronFormSchema = z.object({
|
||||
});
|
||||
|
||||
export const CronCard: React.FC<CronCardProps> = ({ data, mutate }) => {
|
||||
const { t } = useTranslation();
|
||||
const form = useForm<z.infer<typeof cronFormSchema>>({
|
||||
resolver: zodResolver(cronFormSchema),
|
||||
defaultValues: data ? data : {
|
||||
@@ -109,7 +112,7 @@ export const CronCard: React.FC<CronCardProps> = ({ data, mutate }) => {
|
||||
<ScrollArea className="max-h-[calc(100dvh-5rem)] p-3">
|
||||
<div className="items-center mx-1">
|
||||
<DialogHeader>
|
||||
<DialogTitle>New Task</DialogTitle>
|
||||
<DialogTitle>{data?t("EditTask"):t("CreateTask")}</DialogTitle>
|
||||
<DialogDescription />
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
@@ -119,7 +122,7 @@ export const CronCard: React.FC<CronCardProps> = ({ data, mutate }) => {
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormLabel>{t("Name")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="My Task"
|
||||
@@ -135,7 +138,7 @@ export const CronCard: React.FC<CronCardProps> = ({ data, mutate }) => {
|
||||
name="task_type"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Task Type</FormLabel>
|
||||
<FormLabel>{t("Type")}</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={`${field.value}`}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
@@ -157,7 +160,7 @@ export const CronCard: React.FC<CronCardProps> = ({ data, mutate }) => {
|
||||
name="scheduler"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Cron expression</FormLabel>
|
||||
<FormLabel>{t("CronExpression") }</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="0 0 0 3 * * (At 3 AM)"
|
||||
@@ -173,7 +176,7 @@ export const CronCard: React.FC<CronCardProps> = ({ data, mutate }) => {
|
||||
name="command"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Command</FormLabel>
|
||||
<FormLabel>{t("Command")}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
className="resize-y"
|
||||
@@ -189,7 +192,7 @@ export const CronCard: React.FC<CronCardProps> = ({ data, mutate }) => {
|
||||
name="cover"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Coverage</FormLabel>
|
||||
<FormLabel>{t("Coverage")}</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={`${field.value}`}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
@@ -211,7 +214,7 @@ export const CronCard: React.FC<CronCardProps> = ({ data, mutate }) => {
|
||||
name="servers"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Specific Servers</FormLabel>
|
||||
<FormLabel>{t("SpecificServers")}</FormLabel>
|
||||
<FormControl>
|
||||
<MultiSelect
|
||||
options={serverList}
|
||||
@@ -231,7 +234,7 @@ export const CronCard: React.FC<CronCardProps> = ({ data, mutate }) => {
|
||||
name="notification_group_id"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Notifier Group ID</FormLabel>
|
||||
<FormLabel>{t("NotifierGroup")}</FormLabel>
|
||||
<FormControl>
|
||||
<Combobox
|
||||
placeholder="Search..."
|
||||
@@ -247,10 +250,10 @@ export const CronCard: React.FC<CronCardProps> = ({ data, mutate }) => {
|
||||
<DialogFooter className="justify-end">
|
||||
<DialogClose asChild>
|
||||
<Button type="button" className="my-2" variant="secondary">
|
||||
Close
|
||||
{t("Close")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit" className="my-2">Submit</Button>
|
||||
<Button type="submit" className="my-2">{t("Confirm")}</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -41,6 +41,8 @@ import { ddnsTypes, ddnsRequestTypes } from "@/types"
|
||||
import { createDDNSProfile, updateDDNSProfile } from "@/api/ddns"
|
||||
import { Textarea } from "./ui/textarea"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface DDNSCardProps {
|
||||
data?: ModelDDNSProfile;
|
||||
providers: string[];
|
||||
@@ -64,6 +66,7 @@ const ddnsFormSchema = z.object({
|
||||
});
|
||||
|
||||
export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) => {
|
||||
const { t } = useTranslation();
|
||||
const form = useForm<z.infer<typeof ddnsFormSchema>>({
|
||||
resolver: zodResolver(ddnsFormSchema),
|
||||
defaultValues: data ? data : {
|
||||
@@ -100,7 +103,7 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
<ScrollArea className="max-h-[calc(100dvh-5rem)] p-3">
|
||||
<div className="items-center mx-1">
|
||||
<DialogHeader>
|
||||
<DialogTitle>New DDNS Profile</DialogTitle>
|
||||
<DialogTitle>{data?t("EditDDNS"):t("CreateDDNS")}</DialogTitle>
|
||||
<DialogDescription />
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
@@ -110,7 +113,7 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormLabel>{t("Name")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="My DDNS Profile"
|
||||
@@ -126,7 +129,7 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
name="provider"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Provider</FormLabel>
|
||||
<FormLabel>{t("Provider")}</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={`${field.value}`}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
@@ -148,7 +151,7 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
name="domains"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Domains (separate with comma)</FormLabel>
|
||||
<FormLabel>{t("Domains") + t("SeparateWithComma")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="www.example.com"
|
||||
@@ -169,7 +172,7 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
name="access_id"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Credential 1</FormLabel>
|
||||
<FormLabel>{t("Credential")} 1</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Token ID"
|
||||
@@ -185,7 +188,7 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
name="access_secret"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Credential 2</FormLabel>
|
||||
<FormLabel>{t("Credential")} 2</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Token Secret"
|
||||
@@ -201,7 +204,7 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
name="max_retries"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Maximum retry attempts</FormLabel>
|
||||
<FormLabel>{t("MaximumRetryAttempts")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
@@ -234,7 +237,7 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
name="webhook_method"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Webhook Request Method</FormLabel>
|
||||
<FormLabel>Webhook {t("RequestMethod")}</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={`${field.value}`}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
@@ -256,7 +259,7 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
name="webhook_request_type"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Webhook Request Type</FormLabel>
|
||||
<FormLabel>Webhook {t("RequestType")}</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={`${field.value}`}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
@@ -278,7 +281,7 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
name="webhook_headers"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Webhook Request Headers</FormLabel>
|
||||
<FormLabel>Webhook {t("RequestHeader")}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
className="resize-y"
|
||||
@@ -295,7 +298,7 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
name="webhook_request_body"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Webhook Request Body</FormLabel>
|
||||
<FormLabel>Webhook {t("RequestBody")}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
className="resize-y"
|
||||
@@ -318,7 +321,7 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<Label className="text-sm">Enable IPv4</Label>
|
||||
<Label className="text-sm">{t("Enable")} IPv4</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -336,7 +339,7 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<Label className="text-sm">Enable IPv6</Label>
|
||||
<Label className="text-sm">{t("Enable")} IPv6</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -346,10 +349,10 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
<DialogFooter className="justify-end">
|
||||
<DialogClose asChild>
|
||||
<Button type="button" className="my-2" variant="secondary">
|
||||
Close
|
||||
{t("Close")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit" className="my-2">Submit</Button>
|
||||
<Button type="submit" className="my-2">{t("Confirm")}</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -46,6 +46,9 @@ import {
|
||||
DrawerTrigger,
|
||||
} from "@/components/ui/drawer"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
|
||||
interface FMProps {
|
||||
wsUrl: string;
|
||||
}
|
||||
@@ -59,6 +62,7 @@ const arraysEqual = (a: Uint8Array, b: Uint8Array) => {
|
||||
}
|
||||
|
||||
const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl, ...props }) => {
|
||||
const { t } = useTranslation();
|
||||
const fmRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [dOpen, setdOpen] = useState(false);
|
||||
@@ -67,14 +71,14 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
|
||||
const columns: ColumnDef<FMEntry>[] = [
|
||||
{
|
||||
id: "type",
|
||||
header: () => <span>Type</span>,
|
||||
header: () => <span>{t("Type")}</span>,
|
||||
accessorFn: row => row.type,
|
||||
cell: ({ row }) => (
|
||||
row.original.type == 0 ? <File size={24} /> : <Folder size={24} />
|
||||
),
|
||||
},
|
||||
{
|
||||
header: () => <span>Name</span>,
|
||||
header: () => <span>{t("Name")}</span>,
|
||||
id: "name",
|
||||
accessorFn: row => row.name,
|
||||
cell: ({ row }) => (
|
||||
@@ -85,7 +89,7 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
|
||||
size: 5000,
|
||||
},
|
||||
{
|
||||
header: () => <span>Action</span>,
|
||||
header: () => <span>{t("Actions")}</span>,
|
||||
id: "download",
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
@@ -143,30 +147,30 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
|
||||
|
||||
worker.onmessage = async (event: MessageEvent<FMWorkerData>) => {
|
||||
switch (event.data.type) {
|
||||
case FMWorkerOpcode.Error: {
|
||||
console.error('Error from worker', event.data.error);
|
||||
break;
|
||||
}
|
||||
case FMWorkerOpcode.Progress: {
|
||||
handleReady.current = true;
|
||||
break;
|
||||
}
|
||||
case FMWorkerOpcode.Result: {
|
||||
handleReady.current = false;
|
||||
case FMWorkerOpcode.Error: {
|
||||
console.error('Error from worker', event.data.error);
|
||||
break;
|
||||
}
|
||||
case FMWorkerOpcode.Progress: {
|
||||
handleReady.current = true;
|
||||
break;
|
||||
}
|
||||
case FMWorkerOpcode.Result: {
|
||||
handleReady.current = false;
|
||||
|
||||
if (event.data.blob && event.data.fileName) {
|
||||
const url = URL.createObjectURL(event.data.blob);
|
||||
const anchor = document.createElement('a');
|
||||
anchor.href = url;
|
||||
anchor.download = event.data.fileName;
|
||||
anchor.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
firstChunk.current = true;
|
||||
if (dOpen) setdOpen(false);
|
||||
break;
|
||||
if (event.data.blob && event.data.fileName) {
|
||||
const url = URL.createObjectURL(event.data.blob);
|
||||
const anchor = document.createElement('a');
|
||||
anchor.href = url;
|
||||
anchor.download = event.data.fileName;
|
||||
anchor.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
firstChunk.current = true;
|
||||
if (dOpen) setdOpen(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,8 +184,8 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
|
||||
},
|
||||
onError: (e) => {
|
||||
console.error(e);
|
||||
toast("Websocket error", {
|
||||
description: "View console for details.",
|
||||
toast("Websocket" + " " + t("Error"), {
|
||||
description: t("Results.UnExpectedError"),
|
||||
})
|
||||
},
|
||||
onMessage: async (e) => {
|
||||
@@ -214,8 +218,8 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error processing received data:', error);
|
||||
toast("FM error", {
|
||||
description: "View console for details.",
|
||||
toast("FM" + " " + t("Error"), {
|
||||
description: t("Results.UnExpectedError"),
|
||||
})
|
||||
if (dOpen) setdOpen(false);
|
||||
if (uOpen) setuOpen(false);
|
||||
@@ -286,30 +290,30 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
|
||||
<IconButton variant="ghost" icon="menu" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem onClick={listFile}>Refresh</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={listFile}>{t('Refresh')}</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={
|
||||
async () => {
|
||||
await navigator.clipboard.writeText(formatPath(currentPath));
|
||||
}
|
||||
}>Copy path</DropdownMenuItem>
|
||||
}>{t("CopyPath")}</DropdownMenuItem>
|
||||
<AlertDialogTrigger asChild>
|
||||
<DropdownMenuItem>Goto</DropdownMenuItem>
|
||||
<DropdownMenuItem>{t('Goto')}</DropdownMenuItem>
|
||||
</AlertDialogTrigger>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Goto</AlertDialogTitle>
|
||||
<AlertDialogTitle>{t('Goto')}</AlertDialogTitle>
|
||||
<AlertDialogDescription />
|
||||
</AlertDialogHeader>
|
||||
<Input className="mb-1" placeholder="Path" value={gotoPath} onChange={(e) => { setGotoPath(e.target.value) }} />
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={() => { setPath(gotoPath) }}>Confirm</AlertDialogAction>
|
||||
<AlertDialogCancel>{t("Close")}</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={() => { setPath(gotoPath) }}>{t("Confirm")}</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
<h1 className="text-base">Pseudo File Manager</h1>
|
||||
<h1 className="text-base">{t("FileManager")}</h1>
|
||||
<div className="ml-auto">
|
||||
<input ref={fileInputRef} type="file" className="hidden" onChange={
|
||||
async (e) => {
|
||||
@@ -331,7 +335,7 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
|
||||
<AlertDialog open={dOpen}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Downloading...</AlertDialogTitle>
|
||||
<AlertDialogTitle>{t("Downloading")}...</AlertDialogTitle>
|
||||
<AlertDialogDescription />
|
||||
</AlertDialogHeader>
|
||||
</AlertDialogContent>
|
||||
@@ -339,7 +343,7 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
|
||||
<AlertDialog open={uOpen}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Uploading...</AlertDialogTitle>
|
||||
<AlertDialogTitle>{t("Uploading")}...</AlertDialogTitle>
|
||||
<AlertDialogDescription />
|
||||
</AlertDialogHeader>
|
||||
</AlertDialogContent>
|
||||
@@ -350,6 +354,7 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
|
||||
}
|
||||
|
||||
export const FMCard = ({ id }: { id?: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [fm, setFM] = useState<ModelCreateFMResponse | null>(null);
|
||||
const [init, setInit] = useState(false);
|
||||
@@ -363,8 +368,8 @@ export const FMCard = ({ id }: { id?: string }) => {
|
||||
const createdFM = await createFM(id);
|
||||
setFM(createdFM);
|
||||
} catch (e) {
|
||||
toast("FM API Error", {
|
||||
description: "View console for details.",
|
||||
toast(t("Error"), {
|
||||
description: t("Results.UnExpectedError"),
|
||||
})
|
||||
console.error("fetch error", e);
|
||||
return;
|
||||
@@ -396,7 +401,7 @@ export const FMCard = ({ id }: { id?: string }) => {
|
||||
?
|
||||
<FMComponent className="p-1 space-y-5" wsUrl={`/api/v1/ws/file/${fm.session_id}`} />
|
||||
:
|
||||
<p>The server does not exist, or have not been connected yet.</p>
|
||||
<p>{t("Results.TheServerDoesNotOnline")}</p>
|
||||
}
|
||||
</div>
|
||||
</SheetContent>
|
||||
@@ -417,7 +422,7 @@ export const FMCard = ({ id }: { id?: string }) => {
|
||||
?
|
||||
<FMComponent className="p-1 space-y-5" wsUrl={`/api/v1/ws/file/${fm.session_id}`} />
|
||||
:
|
||||
<p>The server does not exist, or have not been connected yet.</p>
|
||||
<p>{t("Results.TheServerDoesNotOnline")}</p>
|
||||
}
|
||||
</div>
|
||||
</DrawerContent>
|
||||
|
||||
@@ -5,17 +5,20 @@ import {
|
||||
} from "@/components/ui/tabs"
|
||||
import { Link, useLocation } from "react-router-dom"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const GroupTab = ({ className }: { className?: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
|
||||
return (
|
||||
<Tabs defaultValue={location.pathname} className={className}>
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="/dashboard/server-group" asChild>
|
||||
<Link to="/dashboard/server-group">Server</Link>
|
||||
<Link to="/dashboard/server-group">{t("Server")}</Link>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="/dashboard/notification-group" asChild>
|
||||
<Link to="/dashboard/notification-group">Notification</Link>
|
||||
<Link to="/dashboard/notification-group">{t("Notification")}</Link>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
|
||||
@@ -14,6 +14,8 @@ import {
|
||||
import { KeyedMutator } from "swr";
|
||||
import { toast } from "sonner"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface ButtonGroupProps<E, U> {
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
@@ -26,13 +28,15 @@ export function HeaderButtonGroup<E, U>({ className, children, delete: { fn, id,
|
||||
await mutate();
|
||||
}
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{id.length < 1 ? (
|
||||
<>
|
||||
<IconButton variant="destructive" icon="trash" onClick={() => {
|
||||
toast("Error", {
|
||||
description: "No rows are selected."
|
||||
toast(t("Error"), {
|
||||
description: t("Results.NoRowsAreSelected")
|
||||
});
|
||||
}} />
|
||||
{children}
|
||||
@@ -45,14 +49,14 @@ export function HeaderButtonGroup<E, U>({ className, children, delete: { fn, id,
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent className="sm:max-w-lg">
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Confirm Deletion?</AlertDialogTitle>
|
||||
<AlertDialogTitle>{t("ConfirmDeletion")}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This operation is unrecoverable!
|
||||
{t("Results.ThisOperationIsUnrecoverable")}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction className={buttonVariants({ variant: "destructive" })} onClick={handleDelete}>Confirm</AlertDialogAction>
|
||||
<AlertDialogCancel>{t("Close")}</AlertDialogCancel>
|
||||
<AlertDialogAction className={buttonVariants({ variant: "destructive" })} onClick={handleDelete}>{t("Confirm")}</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
@@ -29,17 +29,21 @@ import { Button } from "./ui/button";
|
||||
import { IconButton } from "./xui/icon-button";
|
||||
import { useState } from "react";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import i18next from "i18next";
|
||||
const pages = [
|
||||
{ href: "/dashboard", label: "Server" },
|
||||
{ href: "/dashboard/service", label: "Service" },
|
||||
{ href: "/dashboard/cron", label: "Task" },
|
||||
{ href: "/dashboard/notification", label: "Notification" },
|
||||
{ href: "/dashboard/ddns", label: "Dynamic DNS" },
|
||||
{ href: "/dashboard/nat", label: "NAT Traversal" },
|
||||
{ href: "/dashboard/server-group", label: "Group" },
|
||||
{ href: "/dashboard", label: i18next.t("Server") },
|
||||
{ href: "/dashboard/service", label: i18next.t("Service") },
|
||||
{ href: "/dashboard/cron", label: i18next.t("Task") },
|
||||
{ href: "/dashboard/notification", label: i18next.t("Notification") },
|
||||
{ href: "/dashboard/ddns", label: i18next.t("DDNS") },
|
||||
{ href: "/dashboard/nat", label: i18next.t("NATT") },
|
||||
{ href: "/dashboard/server-group", label: i18next.t("Group") },
|
||||
]
|
||||
|
||||
export default function Header() {
|
||||
const { t } = useTranslation();
|
||||
const { logout } = useAuth();
|
||||
const profile = useMainStore(store => store.profile);
|
||||
|
||||
@@ -56,7 +60,7 @@ export default function Header() {
|
||||
<NavigationMenuList>
|
||||
<Card className="mr-1">
|
||||
<NavigationMenuLink asChild className={navigationMenuTriggerStyle() + ' !text-foreground'}>
|
||||
<Link to={profile ? "/dashboard" : '#'}><img className="h-7 mr-1" src='/dashboard/logo.svg' /> NEZHA</Link>
|
||||
<Link to={profile ? "/dashboard" : '#'}><img className="h-7 mr-1" src='/dashboard/logo.svg' /> {t("nezha")}</Link>
|
||||
</NavigationMenuLink>
|
||||
</Card>
|
||||
|
||||
@@ -65,37 +69,37 @@ export default function Header() {
|
||||
<>
|
||||
<NavigationMenuItem>
|
||||
<NzNavigationMenuLink asChild active={location.pathname === "/dashboard"} className={navigationMenuTriggerStyle()}>
|
||||
<Link to="/dashboard">Server</Link>
|
||||
<Link to="/dashboard">{t("Server")}</Link>
|
||||
</NzNavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NzNavigationMenuLink asChild active={location.pathname === "/dashboard/service"} className={navigationMenuTriggerStyle()}>
|
||||
<Link to="/dashboard/service">Service</Link>
|
||||
<Link to="/dashboard/service">{t("Service")}</Link>
|
||||
</NzNavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NzNavigationMenuLink asChild active={location.pathname === "/dashboard/cron"} className={navigationMenuTriggerStyle()}>
|
||||
<Link to="/dashboard/cron">Task</Link>
|
||||
<Link to="/dashboard/cron">{t('Task')}</Link>
|
||||
</NzNavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NzNavigationMenuLink asChild active={location.pathname === "/dashboard/notification" || location.pathname === "/dashboard/alert-rule"} className={navigationMenuTriggerStyle()}>
|
||||
<Link to="/dashboard/notification">Notification</Link>
|
||||
<Link to="/dashboard/notification">{t('Notification')}</Link>
|
||||
</NzNavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NzNavigationMenuLink asChild active={location.pathname === "/dashboard/ddns"} className={navigationMenuTriggerStyle()}>
|
||||
<Link to="/dashboard/ddns">Dynamic DNS</Link>
|
||||
<Link to="/dashboard/ddns">{t('DDNS')}</Link>
|
||||
</NzNavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NzNavigationMenuLink asChild active={location.pathname === "/dashboard/nat"} className={navigationMenuTriggerStyle()}>
|
||||
<Link to="/dashboard/nat">NAT Traversal</Link>
|
||||
<Link to="/dashboard/nat">{t('NATT')}</Link>
|
||||
</NzNavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NzNavigationMenuLink asChild active={location.pathname === "/dashboard/server-group" || location.pathname === "/dashboard/notification-group"} className={navigationMenuTriggerStyle()}>
|
||||
<Link to="/dashboard/server-group">Group</Link>
|
||||
<Link to="/dashboard/server-group">{t('Group')}</Link>
|
||||
</NzNavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
</>
|
||||
@@ -120,14 +124,14 @@ export default function Header() {
|
||||
<DropdownMenuItem onClick={() => { setDropdownOpen(false) }}>
|
||||
<Link to="/dashboard/profile" className="flex items-center gap-2 w-full">
|
||||
<User2 />
|
||||
Profile
|
||||
{t('Profile')}
|
||||
<DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => { setDropdownOpen(false) }}>
|
||||
<Link to="/dashboard/settings" className="flex items-center gap-2 w-full">
|
||||
<Settings />
|
||||
Settings
|
||||
{t('Settings')}
|
||||
<DropdownMenuShortcut>⇧⌘S</DropdownMenuShortcut>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
@@ -135,7 +139,7 @@ export default function Header() {
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={logout} className="cursor-pointer">
|
||||
<LogOut />
|
||||
<span>Log out</span>
|
||||
{t('Logout')}
|
||||
<DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
@@ -156,8 +160,8 @@ export default function Header() {
|
||||
</DrawerTrigger>
|
||||
<DrawerContent>
|
||||
<DrawerHeader className="text-left">
|
||||
<DrawerTitle>Navigate to</DrawerTitle>
|
||||
<DrawerDescription>Select a page to navigate to.</DrawerDescription>
|
||||
<DrawerTitle>{t('NavigateTo')}</DrawerTitle>
|
||||
<DrawerDescription>{t('SelectAPageToNavigateTo')}</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<div className="grid gap-1 px-4">
|
||||
{pages.slice(0).map((item, index) => (
|
||||
@@ -173,7 +177,7 @@ export default function Header() {
|
||||
</div>
|
||||
<DrawerFooter>
|
||||
<DrawerClose asChild>
|
||||
<Button variant="outline">Close</Button>
|
||||
<Button variant="outline">{t('Close')}</Button>
|
||||
</DrawerClose>
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
@@ -201,14 +205,14 @@ export default function Header() {
|
||||
<DropdownMenuItem onClick={() => { setDropdownOpen(false) }}>
|
||||
<Link to="/dashboard/profile" className="flex items-center gap-2 w-full">
|
||||
<User2 />
|
||||
Profile
|
||||
{t('Profile')}
|
||||
<DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => { setDropdownOpen(false) }}>
|
||||
<Link to="/dashboard/settings" className="flex items-center gap-2 w-full">
|
||||
<Settings />
|
||||
Settings
|
||||
{t('Settings')}
|
||||
<DropdownMenuShortcut>⇧⌘S</DropdownMenuShortcut>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
@@ -216,7 +220,7 @@ export default function Header() {
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={logout} className="cursor-pointer">
|
||||
<LogOut />
|
||||
<span>Log out</span>
|
||||
{t('Logout')}
|
||||
<DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
|
||||
@@ -9,7 +9,10 @@ import {
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import { Theme, useTheme } from "@/components/theme-provider"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export function ModeToggle() {
|
||||
const { t } = useTranslation();
|
||||
const { setTheme } = useTheme()
|
||||
|
||||
const toggleTheme = (theme: Theme) => {
|
||||
@@ -27,13 +30,13 @@ export function ModeToggle() {
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => toggleTheme("light")}>
|
||||
Light
|
||||
{t("theme.light")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => toggleTheme("dark")}>
|
||||
Dark
|
||||
{t("theme.dark")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => toggleTheme("system")}>
|
||||
System
|
||||
{t("theme.system")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
@@ -28,6 +28,8 @@ import { KeyedMutator } from "swr"
|
||||
import { IconButton } from "@/components/xui/icon-button"
|
||||
import { createNAT, updateNAT } from "@/api/nat"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface NATCardProps {
|
||||
data?: ModelNAT;
|
||||
mutate: KeyedMutator<ModelNAT[]>;
|
||||
@@ -41,6 +43,7 @@ const natFormSchema = z.object({
|
||||
});
|
||||
|
||||
export const NATCard: React.FC<NATCardProps> = ({ data, mutate }) => {
|
||||
const { t } = useTranslation();
|
||||
const form = useForm<z.infer<typeof natFormSchema>>({
|
||||
resolver: zodResolver(natFormSchema),
|
||||
defaultValues: data ? data : {
|
||||
@@ -77,7 +80,7 @@ export const NATCard: React.FC<NATCardProps> = ({ data, mutate }) => {
|
||||
<ScrollArea className="max-h-[calc(100dvh-5rem)] p-3">
|
||||
<div className="items-center mx-1">
|
||||
<DialogHeader>
|
||||
<DialogTitle>New NAT Profile</DialogTitle>
|
||||
<DialogTitle>{data?t("EditNAT"):t("CreateNAT")}</DialogTitle>
|
||||
<DialogDescription />
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
@@ -87,7 +90,7 @@ export const NATCard: React.FC<NATCardProps> = ({ data, mutate }) => {
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormLabel>{t("Name")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="My NAT Profile"
|
||||
@@ -103,7 +106,7 @@ export const NATCard: React.FC<NATCardProps> = ({ data, mutate }) => {
|
||||
name="server_id"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Server ID</FormLabel>
|
||||
<FormLabel>{t("Server")} ID</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
@@ -120,7 +123,7 @@ export const NATCard: React.FC<NATCardProps> = ({ data, mutate }) => {
|
||||
name="host"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Local Service</FormLabel>
|
||||
<FormLabel>{t("LocalService")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="192.168.1.1:80 (with port)"
|
||||
@@ -136,7 +139,7 @@ export const NATCard: React.FC<NATCardProps> = ({ data, mutate }) => {
|
||||
name="domain"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Bind hostname</FormLabel>
|
||||
<FormLabel>{t("BindHostname")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="router.app.yourdomain.com"
|
||||
@@ -150,10 +153,10 @@ export const NATCard: React.FC<NATCardProps> = ({ data, mutate }) => {
|
||||
<DialogFooter className="justify-end">
|
||||
<DialogClose asChild>
|
||||
<Button type="button" className="my-2" variant="secondary">
|
||||
Close
|
||||
{t("Close")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit" className="my-2">Submit</Button>
|
||||
<Button type="submit" className="my-2">{t("Confirm")}</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -9,11 +9,14 @@ import { forwardRef, useState } from "react"
|
||||
import { IconButton } from "./xui/icon-button"
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface NoteMenuProps extends ButtonProps {
|
||||
note: { private?: string, public?: string };
|
||||
}
|
||||
|
||||
export const NoteMenu = forwardRef<HTMLButtonElement, NoteMenuProps>((props, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const [copy, setCopy] = useState(false);
|
||||
|
||||
const switchState = async (text?: string) => {
|
||||
@@ -41,8 +44,8 @@ export const NoteMenu = forwardRef<HTMLButtonElement, NoteMenuProps>((props, ref
|
||||
} />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem onClick={() => { switchState(props.note.private) }}>Private</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => { switchState(props.note.public) }}>Public</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => { switchState(props.note.private) }}>{t("Private")}</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => { switchState(props.note.public) }}>{t("Public")}</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
|
||||
@@ -30,6 +30,8 @@ import { createNotificationGroup, updateNotificationGroup } from "@/api/notifica
|
||||
import { MultiSelect } from "@/components/xui/multi-select"
|
||||
import { useNotification } from "@/hooks/useNotfication"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface NotificationGroupCardProps {
|
||||
data?: ModelNotificationGroupResponseItem;
|
||||
mutate: KeyedMutator<ModelNotificationGroupResponseItem[]>;
|
||||
@@ -41,6 +43,7 @@ const notificationGroupFormSchema = z.object({
|
||||
});
|
||||
|
||||
export const NotificationGroupCard: React.FC<NotificationGroupCardProps> = ({ data, mutate }) => {
|
||||
const { t } = useTranslation();
|
||||
const form = useForm<z.infer<typeof notificationGroupFormSchema>>({
|
||||
resolver: zodResolver(notificationGroupFormSchema),
|
||||
defaultValues: data ? {
|
||||
@@ -84,7 +87,7 @@ export const NotificationGroupCard: React.FC<NotificationGroupCardProps> = ({ da
|
||||
<ScrollArea className="max-h-[calc(100dvh-5rem)] p-3">
|
||||
<div className="items-center mx-1">
|
||||
<DialogHeader>
|
||||
<DialogTitle>New Notifier Group</DialogTitle>
|
||||
<DialogTitle>{data ? t("EditNotifierGroup") : t("CreateNotifierGroup")}</DialogTitle>
|
||||
<DialogDescription />
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
@@ -94,7 +97,7 @@ export const NotificationGroupCard: React.FC<NotificationGroupCardProps> = ({ da
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormLabel>{t("Name")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Group Name"
|
||||
@@ -110,7 +113,7 @@ export const NotificationGroupCard: React.FC<NotificationGroupCardProps> = ({ da
|
||||
name="notifications"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Notifiers</FormLabel>
|
||||
<FormLabel>{t("Notification")}</FormLabel>
|
||||
<MultiSelect
|
||||
options={notifierList}
|
||||
onValueChange={e => {
|
||||
@@ -126,10 +129,10 @@ export const NotificationGroupCard: React.FC<NotificationGroupCardProps> = ({ da
|
||||
<DialogFooter className="justify-end">
|
||||
<DialogClose asChild>
|
||||
<Button type="button" className="my-2" variant="secondary">
|
||||
Close
|
||||
{t("Close")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit" className="my-2">Submit</Button>
|
||||
<Button type="submit" className="my-2">{t("Confirm")}</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -5,17 +5,21 @@ import {
|
||||
} from "@/components/ui/tabs"
|
||||
import { Link, useLocation } from "react-router-dom"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
|
||||
export const NotificationTab = ({ className }: { className?: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
|
||||
return (
|
||||
<Tabs defaultValue={location.pathname} className={className}>
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="/dashboard/notification" asChild>
|
||||
<Link to="/dashboard/notification">Notifier</Link>
|
||||
<Link to="/dashboard/notification">{t("Notifier")}</Link>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="/dashboard/alert-rule" asChild>
|
||||
<Link to="/dashboard/alert-rule">Alert Rule</Link>
|
||||
<Link to="/dashboard/alert-rule">{t("AlertRule")}</Link>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
|
||||
@@ -40,6 +40,8 @@ import { nrequestTypes, nrequestMethods } from "@/types"
|
||||
import { createNotification, updateNotification } from "@/api/notification"
|
||||
import { Textarea } from "./ui/textarea"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface NotifierCardProps {
|
||||
data?: ModelNotification;
|
||||
mutate: KeyedMutator<ModelNotification[]>;
|
||||
@@ -57,6 +59,7 @@ const notificationFormSchema = z.object({
|
||||
});
|
||||
|
||||
export const NotifierCard: React.FC<NotifierCardProps> = ({ data, mutate }) => {
|
||||
const { t } = useTranslation();
|
||||
const form = useForm<z.infer<typeof notificationFormSchema>>({
|
||||
resolver: zodResolver(notificationFormSchema),
|
||||
defaultValues: data ? data : {
|
||||
@@ -95,7 +98,7 @@ export const NotifierCard: React.FC<NotifierCardProps> = ({ data, mutate }) => {
|
||||
<ScrollArea className="max-h-[calc(100dvh-5rem)] p-3">
|
||||
<div className="items-center mx-1">
|
||||
<DialogHeader>
|
||||
<DialogTitle>New Notifier</DialogTitle>
|
||||
<DialogTitle>{data?t("EditNotifier"):t("CreateNotifier")}</DialogTitle>
|
||||
<DialogDescription />
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
@@ -105,7 +108,7 @@ export const NotifierCard: React.FC<NotifierCardProps> = ({ data, mutate }) => {
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormLabel>{t("Name")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="My Notifier"
|
||||
@@ -136,7 +139,7 @@ export const NotifierCard: React.FC<NotifierCardProps> = ({ data, mutate }) => {
|
||||
name="request_method"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Request Method</FormLabel>
|
||||
<FormLabel>{t("RequestMethod")}</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={`${field.value}`}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
@@ -158,7 +161,7 @@ export const NotifierCard: React.FC<NotifierCardProps> = ({ data, mutate }) => {
|
||||
name="request_type"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Request Type</FormLabel>
|
||||
<FormLabel>{t("Type")}</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={`${field.value}`}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
@@ -180,7 +183,7 @@ export const NotifierCard: React.FC<NotifierCardProps> = ({ data, mutate }) => {
|
||||
name="request_header"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Header</FormLabel>
|
||||
<FormLabel>{t("RequestHeader")}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
className="resize-y"
|
||||
@@ -197,7 +200,7 @@ export const NotifierCard: React.FC<NotifierCardProps> = ({ data, mutate }) => {
|
||||
name="request_body"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Body</FormLabel>
|
||||
<FormLabel>{t("RequestBody")}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
className="resize-y h-[240px]"
|
||||
@@ -220,7 +223,7 @@ export const NotifierCard: React.FC<NotifierCardProps> = ({ data, mutate }) => {
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<Label className="text-sm">Verify TLS</Label>
|
||||
<Label className="text-sm">{t("VerifyTLS")}</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -238,7 +241,7 @@ export const NotifierCard: React.FC<NotifierCardProps> = ({ data, mutate }) => {
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<Label className="text-sm">Do Not Send Test Message</Label>
|
||||
<Label className="text-sm">{t("DoNotSendTestMessage")}</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -248,10 +251,10 @@ export const NotifierCard: React.FC<NotifierCardProps> = ({ data, mutate }) => {
|
||||
<DialogFooter className="justify-end">
|
||||
<DialogClose asChild>
|
||||
<Button type="button" className="my-2" variant="secondary">
|
||||
Close
|
||||
{t("Close")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit" className="my-2">Submit</Button>
|
||||
<Button type="submit" className="my-2">{t("Confirm")}</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -27,12 +27,15 @@ import { useState } from "react"
|
||||
import { useMainStore } from "@/hooks/useMainStore"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const profileFormSchema = z.object({
|
||||
original_password: z.string().min(5).max(72),
|
||||
new_password: z.string().min(8).max(72),
|
||||
});
|
||||
|
||||
export const ProfileCard = ({ className }: { className: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const form = useForm<z.infer<typeof profileFormSchema>>({
|
||||
resolver: zodResolver(profileFormSchema),
|
||||
defaultValues: {
|
||||
@@ -51,7 +54,7 @@ export const ProfileCard = ({ className }: { className: string }) => {
|
||||
try {
|
||||
await updateProfile(values);
|
||||
} catch (e) {
|
||||
toast("Update failed", {
|
||||
toast(t("Error"), {
|
||||
description: `${e}`,
|
||||
})
|
||||
return;
|
||||
@@ -66,14 +69,14 @@ export const ProfileCard = ({ className }: { className: string }) => {
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline" className={className}>
|
||||
Update Password
|
||||
{t("UpdatePassword")}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-xl">
|
||||
<ScrollArea className="max-h-[calc(100dvh-5rem)] p-3">
|
||||
<div className="items-center mx-1">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Update Server</DialogTitle>
|
||||
<DialogTitle>{t("UpdatePassword")}</DialogTitle>
|
||||
<DialogDescription />
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
@@ -83,7 +86,7 @@ export const ProfileCard = ({ className }: { className: string }) => {
|
||||
name="original_password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Original Password</FormLabel>
|
||||
<FormLabel>{t("OriginalPassword")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
@@ -98,7 +101,7 @@ export const ProfileCard = ({ className }: { className: string }) => {
|
||||
name="new_password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>New Password</FormLabel>
|
||||
<FormLabel>{t("NewPassword")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
@@ -112,10 +115,10 @@ export const ProfileCard = ({ className }: { className: string }) => {
|
||||
<DialogFooter className="justify-end">
|
||||
<DialogClose asChild>
|
||||
<Button type="button" className="my-2" variant="secondary">
|
||||
Close
|
||||
{t("Close")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit" className="my-2">Submit</Button>
|
||||
<Button type="submit" className="my-2">{t("Confirm")}</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -30,6 +30,8 @@ import { createServerGroup, updateServerGroup } from "@/api/server-group"
|
||||
import { MultiSelect } from "@/components/xui/multi-select"
|
||||
import { useServer } from "@/hooks/useServer"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface ServerGroupCardProps {
|
||||
data?: ModelServerGroupResponseItem;
|
||||
mutate: KeyedMutator<ModelServerGroupResponseItem[]>;
|
||||
@@ -41,6 +43,7 @@ const serverGroupFormSchema = z.object({
|
||||
});
|
||||
|
||||
export const ServerGroupCard: React.FC<ServerGroupCardProps> = ({ data, mutate }) => {
|
||||
const { t } = useTranslation();
|
||||
const form = useForm<z.infer<typeof serverGroupFormSchema>>({
|
||||
resolver: zodResolver(serverGroupFormSchema),
|
||||
defaultValues: data ? {
|
||||
@@ -84,7 +87,7 @@ export const ServerGroupCard: React.FC<ServerGroupCardProps> = ({ data, mutate }
|
||||
<ScrollArea className="max-h-[calc(100dvh-5rem)] p-3">
|
||||
<div className="items-center mx-1">
|
||||
<DialogHeader>
|
||||
<DialogTitle>New Server Group</DialogTitle>
|
||||
<DialogTitle>{data? t("EditServerGroup"):t("CreateServerGroup")}</DialogTitle>
|
||||
<DialogDescription />
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
@@ -94,7 +97,7 @@ export const ServerGroupCard: React.FC<ServerGroupCardProps> = ({ data, mutate }
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormLabel>{t("Name")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Group Name"
|
||||
@@ -110,7 +113,7 @@ export const ServerGroupCard: React.FC<ServerGroupCardProps> = ({ data, mutate }
|
||||
name="servers"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Servers</FormLabel>
|
||||
<FormLabel>{t("Server")}</FormLabel>
|
||||
<FormControl>
|
||||
<MultiSelect
|
||||
options={serverList}
|
||||
@@ -128,10 +131,10 @@ export const ServerGroupCard: React.FC<ServerGroupCardProps> = ({ data, mutate }
|
||||
<DialogFooter className="justify-end">
|
||||
<DialogClose asChild>
|
||||
<Button type="button" className="my-2" variant="secondary">
|
||||
Close
|
||||
{t("Close")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit" className="my-2">Submit</Button>
|
||||
<Button type="submit" className="my-2">{t("Confirm")}</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -32,6 +32,7 @@ import { KeyedMutator } from "swr"
|
||||
import { asOptionalField } from "@/lib/utils"
|
||||
import { IconButton } from "@/components/xui/icon-button"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
interface ServerCardProps {
|
||||
data: ModelServer;
|
||||
@@ -49,6 +50,7 @@ const serverFormSchema = z.object({
|
||||
});
|
||||
|
||||
export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
||||
const { t } = useTranslation();
|
||||
const form = useForm<z.infer<typeof serverFormSchema>>({
|
||||
resolver: zodResolver(serverFormSchema),
|
||||
defaultValues: data,
|
||||
@@ -75,7 +77,7 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
||||
<ScrollArea className="max-h-[calc(100dvh-5rem)] p-3">
|
||||
<div className="items-center mx-1">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Update Server</DialogTitle>
|
||||
<DialogTitle>{t("EditServer") }</DialogTitle>
|
||||
<DialogDescription />
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
@@ -85,7 +87,7 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormLabel>{t("Name")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="My Server"
|
||||
@@ -101,7 +103,7 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
||||
name="display_index"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Display Index</FormLabel>
|
||||
<FormLabel>{t("Weight")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
@@ -118,7 +120,7 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
||||
name="ddns_profiles"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>DDNS Profile IDs (Separate with comma)</FormLabel>
|
||||
<FormLabel>{t("DDNSProfiles") + t("SeparateWithComma")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="1,2,3"
|
||||
@@ -146,7 +148,7 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<Label className="text-sm">Enable DDNS</Label>
|
||||
<Label className="text-sm">{t("Enable") + t("DDNS") }</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -164,7 +166,7 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<Label className="text-sm">Hide from Guest</Label>
|
||||
<Label className="text-sm">{t("HideForGuest")}</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -176,7 +178,7 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
||||
name="note"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Note</FormLabel>
|
||||
<FormLabel>{t("Private") + t("Note")}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
className="resize-none"
|
||||
@@ -192,7 +194,7 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
||||
name="public_note"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Public Note</FormLabel>
|
||||
<FormLabel>{t("Public") + t("Note")}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
className="resize-y"
|
||||
@@ -206,10 +208,10 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
||||
<DialogFooter className="justify-end">
|
||||
<DialogClose asChild>
|
||||
<Button type="button" className="my-2" variant="secondary">
|
||||
Close
|
||||
{t("Close")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit" className="my-2">Submit</Button>
|
||||
<Button type="submit" className="my-2">{t("Submit")}</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -44,6 +44,8 @@ import { Combobox } from "./ui/combobox"
|
||||
import { useServer } from "@/hooks/useServer"
|
||||
import { useNotification } from "@/hooks/useNotfication"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface ServiceCardProps {
|
||||
data?: ModelService;
|
||||
mutate: KeyedMutator<ModelServiceResponse>;
|
||||
@@ -69,6 +71,7 @@ const serviceFormSchema = z.object({
|
||||
});
|
||||
|
||||
export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
const { t } = useTranslation();
|
||||
const form = useForm<z.infer<typeof serviceFormSchema>>({
|
||||
resolver: zodResolver(serviceFormSchema),
|
||||
defaultValues: data ? {
|
||||
@@ -130,7 +133,7 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
<ScrollArea className="max-h-[calc(100dvh-5rem)] p-3">
|
||||
<div className="items-center mx-1">
|
||||
<DialogHeader>
|
||||
<DialogTitle>New Service</DialogTitle>
|
||||
<DialogTitle>{data?t("EditService"):t("CreateService")}</DialogTitle>
|
||||
<DialogDescription />
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
@@ -140,7 +143,7 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Service Name</FormLabel>
|
||||
<FormLabel>{t("Name")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="My Service Monitor"
|
||||
@@ -156,7 +159,7 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
name="target"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Target</FormLabel>
|
||||
<FormLabel>{t("Target")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="HTTP (https://t.tt)|Ping (t.tt)|TCP (t.tt:80)"
|
||||
@@ -172,7 +175,7 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
name="type"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Type</FormLabel>
|
||||
<FormLabel>{t("Type")}</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={`${field.value}`}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
@@ -200,7 +203,7 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<Label className="text-sm">Show in Service</Label>
|
||||
<Label className="text-sm">{t("ShowInService")}</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -212,7 +215,7 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
name="duration"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Interval (s)</FormLabel>
|
||||
<FormLabel>{t("Interval")} (s)</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
@@ -229,7 +232,7 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
name="cover"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Coverage</FormLabel>
|
||||
<FormLabel>{t("Coverage")}</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={`${field.value}`}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
@@ -251,7 +254,7 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
name="skip_servers_raw"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Specific Servers</FormLabel>
|
||||
<FormLabel>{t("SpecificServers")}</FormLabel>
|
||||
<FormControl>
|
||||
<MultiSelect
|
||||
options={serverList}
|
||||
@@ -268,7 +271,7 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
name="notification_group_id"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Notifier Group</FormLabel>
|
||||
<FormLabel>{t("NotifierGroupID")}</FormLabel>
|
||||
<FormControl>
|
||||
<Combobox
|
||||
placeholder="Search..."
|
||||
@@ -292,7 +295,7 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<Label className="text-sm">Enable Failure Notification</Label>
|
||||
<Label className="text-sm">{t("EnableFailureNotification")}</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -304,7 +307,7 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
name="max_latency"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Maximum Latency Time (ms)</FormLabel>
|
||||
<FormLabel>{t("MaximumLatency")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
@@ -321,7 +324,7 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
name="min_latency"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Minimum Latency Time (ms)</FormLabel>
|
||||
<FormLabel>{t("MinimumLatency")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
@@ -344,7 +347,7 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<Label className="text-sm">Enable Latency Notification</Label>
|
||||
<Label className="text-sm">{t("EnableLatencyNotification")}</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -362,7 +365,7 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<Label className="text-sm">Enable Trigger Task</Label>
|
||||
<Label className="text-sm">{t("EnableTriggerTask")}</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -374,7 +377,7 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
name="fail_trigger_tasks"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Tasks to trigger on an alarm (Separate with comma)</FormLabel>
|
||||
<FormLabel>{t("TasksToTriggerOnAlert") + t("SeparateWithComma")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="1,2,3"
|
||||
@@ -395,7 +398,7 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
name="recover_trigger_tasks"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Tasks to trigger after recovery (Separate with comma)</FormLabel>
|
||||
<FormLabel>{t("TasksToTriggerAfterRecovery") + t("SeparateWithComma")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="1,2,3"
|
||||
@@ -414,10 +417,10 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
<DialogFooter className="justify-end">
|
||||
<DialogClose asChild>
|
||||
<Button type="button" className="my-2" variant="secondary">
|
||||
Close
|
||||
{t("Close")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit" className="my-2">Submit</Button>
|
||||
<Button type="submit" className="my-2">{t("Submit")}</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -5,20 +5,23 @@ import {
|
||||
} from "@/components/ui/tabs"
|
||||
import { Link, useLocation } from "react-router-dom"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const SettingsTab = ({ className }: { className?: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
|
||||
return (
|
||||
<Tabs defaultValue={location.pathname} className={className}>
|
||||
<TabsList className="grid w-full grid-cols-3">
|
||||
<TabsTrigger value="/dashboard/settings" asChild>
|
||||
<Link to="/dashboard/settings">Config</Link>
|
||||
<Link to="/dashboard/settings">{t("Settings")}</Link>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="/dashboard/settings/user" asChild>
|
||||
<Link to="/dashboard/settings/user">User</Link>
|
||||
<Link to="/dashboard/settings/user">{t("User")}</Link>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="/dashboard/settings/waf" asChild>
|
||||
<Link to="/dashboard/settings/waf">WAF</Link>
|
||||
<Link to="/dashboard/settings/waf">{t("WAF")}</Link>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
|
||||
@@ -28,6 +28,8 @@ import { KeyedMutator } from "swr"
|
||||
import { IconButton } from "@/components/xui/icon-button"
|
||||
import { createUser } from "@/api/user"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface UserCardProps {
|
||||
mutate: KeyedMutator<ModelUser[]>;
|
||||
}
|
||||
@@ -38,6 +40,7 @@ const userFormSchema = z.object({
|
||||
});
|
||||
|
||||
export const UserCard: React.FC<UserCardProps> = ({ mutate }) => {
|
||||
const { t } = useTranslation();
|
||||
const form = useForm<z.infer<typeof userFormSchema>>({
|
||||
resolver: zodResolver(userFormSchema),
|
||||
defaultValues: {
|
||||
@@ -67,7 +70,7 @@ export const UserCard: React.FC<UserCardProps> = ({ mutate }) => {
|
||||
<ScrollArea className="max-h-[calc(100dvh-5rem)] p-3">
|
||||
<div className="items-center mx-1">
|
||||
<DialogHeader>
|
||||
<DialogTitle>New User</DialogTitle>
|
||||
<DialogTitle>{t("NewUser")}</DialogTitle>
|
||||
<DialogDescription />
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
@@ -77,7 +80,7 @@ export const UserCard: React.FC<UserCardProps> = ({ mutate }) => {
|
||||
name="username"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormLabel>{t("Username")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
@@ -92,7 +95,7 @@ export const UserCard: React.FC<UserCardProps> = ({ mutate }) => {
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormLabel>{t("Password")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
@@ -105,10 +108,10 @@ export const UserCard: React.FC<UserCardProps> = ({ mutate }) => {
|
||||
<DialogFooter className="justify-end">
|
||||
<DialogClose asChild>
|
||||
<Button type="button" className="my-2" variant="secondary">
|
||||
Close
|
||||
{t("Close")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit" className="my-2">Submit</Button>
|
||||
<Button type="submit" className="my-2">{t("Confirm")}</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
42
src/lib/i18n.ts
Normal file
42
src/lib/i18n.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import i18n from "i18next";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
|
||||
import enTranslation from "../locales/en/translation.json";
|
||||
import itTranslation from "../locales/it/translation.json";
|
||||
import zhCNTranslation from "../locales/zh-CN/translation.json";
|
||||
import zhTWTranslation from "../locales/zh-TW/translation.json";
|
||||
|
||||
const resources = {
|
||||
en: {
|
||||
translation: enTranslation,
|
||||
},
|
||||
it: {
|
||||
translation: itTranslation,
|
||||
},
|
||||
"zh-CN": {
|
||||
translation: zhCNTranslation,
|
||||
},
|
||||
"zh-TW": {
|
||||
translation: zhTWTranslation,
|
||||
},
|
||||
};
|
||||
|
||||
const getStoredLanguage = () => {
|
||||
return localStorage.getItem("language") || "zh-CN";
|
||||
};
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
resources,
|
||||
lng: getStoredLanguage(), // 使用localStorage中存储的语言或默认值
|
||||
fallbackLng: "en", // 当前语言的翻译没有找到时,使用的备选语言
|
||||
interpolation: {
|
||||
escapeValue: false, // react已经安全地转义
|
||||
},
|
||||
});
|
||||
|
||||
// 添加语言改变时的处理函数
|
||||
i18n.on("languageChanged", (lng) => {
|
||||
localStorage.setItem("language", lng);
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
152
src/locales/en/translation.json
Normal file
152
src/locales/en/translation.json
Normal file
@@ -0,0 +1,152 @@
|
||||
{
|
||||
"nezha": "Nezha Monitoring",
|
||||
"theme": {
|
||||
"light": "Light",
|
||||
"dark": "Dark",
|
||||
"system": "Follow System"
|
||||
},
|
||||
"Username": "Username",
|
||||
"Password": "Password",
|
||||
"Results": {
|
||||
"UsernameMin": "Username must be at least {{number}} characters.",
|
||||
"PasswordRequired": "Password cannot be empty.",
|
||||
"ErrorFetchingResource": "Error Fetching Resource : {{error}}",
|
||||
"SelectAtLeastOneServer": "Please select at least one server.",
|
||||
"UnExpectedError": "UnExpected Error, Please see the console for details.",
|
||||
"ForceUpdate": "Forced upgrade:",
|
||||
"NoRowsAreSelected": "No rows are selected",
|
||||
"ThisOperationIsUnrecoverable": "This operation cannot be undone!",
|
||||
"TaskTriggeredSuccessfully": "The task triggered successfully",
|
||||
"TheServerDoesNotOnline": "The server does not exist or has not been connected yet"
|
||||
},
|
||||
"Login": "Log in",
|
||||
"Server": "Server",
|
||||
"Service": "Serve",
|
||||
"Task": "Task",
|
||||
"Notification": "Notification",
|
||||
"DDNS": "Dynamic DNS",
|
||||
"NATT": "NAT Traversal",
|
||||
"Group": "Group",
|
||||
"Profile": "personal information",
|
||||
"Settings": "System settings",
|
||||
"Logout": "Log out",
|
||||
"NavigateTo": "Navigate to",
|
||||
"SelectAPageToNavigateTo": "Choose a page to jump to",
|
||||
"Close": "Close",
|
||||
"Error": "Error",
|
||||
"Name": "Name",
|
||||
"Version": "Version",
|
||||
"Unknown": "unknown",
|
||||
"Enable": "Enable",
|
||||
"HideForGuest": "Hidden from visitors",
|
||||
"InstallCommands": "Installation command",
|
||||
"Note": "Note",
|
||||
"Success": "Success",
|
||||
"Done": "Finish",
|
||||
"Offline": "Offline",
|
||||
"Failure": "Fail",
|
||||
"Loading": "loading",
|
||||
"NoResults": "no content",
|
||||
"Actions": "Actions",
|
||||
"EditServer": "Edit server",
|
||||
"Weight": "Weight (the larger the number, the higher it is displayed)",
|
||||
"DDNSProfiles": "DDNS Profile IDs",
|
||||
"SeparateWithComma": "(Separate with comma)",
|
||||
"Public": "Public",
|
||||
"Private": "private",
|
||||
"Submit": "submit",
|
||||
"Target": "Target",
|
||||
"Coverage": "Coverage",
|
||||
"CoverAll": "Cover all",
|
||||
"IgnoreAll": "Ignore all",
|
||||
"SpecificServers": "Specific server",
|
||||
"Type": "Type",
|
||||
"Interval": "interval",
|
||||
"NotifierGroupID": "Notification group ID",
|
||||
"Trigger": "On Trigger",
|
||||
"TasksToTriggerOnAlert": "The task that triggered the alert",
|
||||
"TasksToTriggerAfterRecovery": "Tasks to be triggered after recovery",
|
||||
"Confirm": "Confirm",
|
||||
"ConfirmDeletion": "Confirm deletion?",
|
||||
"Services": "Services",
|
||||
"ShowInService": "Show in Service",
|
||||
"Coverages": {
|
||||
"Excludes": "Excludes specific servers",
|
||||
"Only": "Only specific servers",
|
||||
"Alarmed": "Executed on the server that triggered the alarm"
|
||||
},
|
||||
"EnableFailureNotification": "Enable Failure Notification",
|
||||
"MaximumLatency": "Maximum Latency Time (ms)",
|
||||
"MinimumLatency": "Minimum delay time (milliseconds)",
|
||||
"EnableLatencyNotification": "Enable delayed notifications",
|
||||
"EnableTriggerTask": "Enable Trigger Task",
|
||||
"CronExpression": "Cron expression",
|
||||
"Command": "Order",
|
||||
"NotifierGroup": "Notification group",
|
||||
"SendSuccessNotification": "Send success notification",
|
||||
"LastExecution": "Last Execution",
|
||||
"Result": "Result",
|
||||
"Scheduled": "Scheduled tasks",
|
||||
"Notifier": "Notifier",
|
||||
"AlertRule": "Alert rules",
|
||||
"VerifyTLS": "Verify TLS",
|
||||
"TriggerMode": "Trigger mode",
|
||||
"Rules": "Rules",
|
||||
"RequestMethod": "Request method",
|
||||
"RequestHeader": "Request header",
|
||||
"DoNotSendTestMessage": "Do Not Send Test Message",
|
||||
"Always": "Always",
|
||||
"Once": "Once",
|
||||
"Provider": "Provider",
|
||||
"Domains": "domain name",
|
||||
"MaximumRetryAttempts": "Maximum number of retries",
|
||||
"Refresh": "Refresh",
|
||||
"CopyPath": "copy path",
|
||||
"Goto": "Go to",
|
||||
"UpdatePassword": "change password",
|
||||
"OriginalPassword": "original password",
|
||||
"NewPassword": "New Password",
|
||||
"EditDDNS": "Edit DDNS",
|
||||
"CreateDDNS": "Create DDNS",
|
||||
"Credential": "Credential",
|
||||
"RequestType": "Request type",
|
||||
"RequestBody": "Request body",
|
||||
"FileManager": "File manager",
|
||||
"Downloading": "Downloading",
|
||||
"Uploading": "Uploading",
|
||||
"EditNAT": "Edit intranet penetration",
|
||||
"CreateNAT": "Create intranet penetration",
|
||||
"LocalService": "local service",
|
||||
"BindHostname": "Bind domain name",
|
||||
"EditServerGroup": "Edit server group",
|
||||
"CreateServerGroup": "Create server group",
|
||||
"User": "User",
|
||||
"WAF": "Web application firewall",
|
||||
"SiteName": "Site name",
|
||||
"DashboardOriginalHost": "Dashboard Server Domain/IP without CDN",
|
||||
"Auto": "automatic recognition",
|
||||
"LoginFailed": "Login failed",
|
||||
"BruteForceAttackingToken": "Brute Force Attacking Token",
|
||||
"BruteForceAttackingAgentSecret": "Brute Force Attacking Agent Secret",
|
||||
"Language": "language",
|
||||
"CustomCodes": "Custom Codes (Style and Script)",
|
||||
"CustomCodesDashboard": "Custom Codes for Dashboard",
|
||||
"CustomPublicDNSNameserversforDDNS": "Custom Public DNS Nameservers for DDNS",
|
||||
"RealIPHeader": "Real IP request header",
|
||||
"IPChangeNotification": "IP Change notification",
|
||||
"FullIPNotification": "Show Full IP Address in Notification Messages",
|
||||
"EditService": "Editing services",
|
||||
"CreateService": "Create service",
|
||||
"EditTask": "Edit task",
|
||||
"CreateTask": "Create task",
|
||||
"CreateNotifier": "Create notification",
|
||||
"EditNotifier": "Edit notification",
|
||||
"EditAlertRule": "Edit alarm rules",
|
||||
"CreateAlertRule": "Create alert rules",
|
||||
"EditNotifierGroup": "Edit notification group",
|
||||
"CreateNotifierGroup": "Create notification group",
|
||||
"NewUser": "new user",
|
||||
"Count": "Count",
|
||||
"LastBlockReason": "Last Block Reason",
|
||||
"LastBlockTime": "Last ban time"
|
||||
}
|
||||
152
src/locales/it/translation.json
Normal file
152
src/locales/it/translation.json
Normal file
@@ -0,0 +1,152 @@
|
||||
{
|
||||
"nezha": "Monitoraggio Nezha",
|
||||
"theme": {
|
||||
"light": "Chiaro",
|
||||
"dark": "Scuro",
|
||||
"system": "Segui il sistema"
|
||||
},
|
||||
"Username": "Nome utente",
|
||||
"Password": "Password",
|
||||
"Results": {
|
||||
"UsernameMin": "Il nome utente deve contenere almeno {{number}} caratteri.",
|
||||
"PasswordRequired": "La password non può essere vuota.",
|
||||
"ErrorFetchingResource": "Errore nel recupero della risorsa: {{error}}",
|
||||
"SelectAtLeastOneServer": "Seleziona almeno un server.",
|
||||
"UnExpectedError": "Errore imprevisto. Controlla la console per i dettagli.",
|
||||
"ForceUpdate": "Aggiornamento forzato:",
|
||||
"NoRowsAreSelected": "Nessuna riga selezionata",
|
||||
"ThisOperationIsUnrecoverable": "Questa operazione non può essere annullata!",
|
||||
"TaskTriggeredSuccessfully": "Attività avviata correttamente",
|
||||
"TheServerDoesNotOnline": "Il server non esiste o non è stato ancora connesso"
|
||||
},
|
||||
"Login": "Accedi",
|
||||
"Server": "Server",
|
||||
"Service": "Servizio",
|
||||
"Task": "Compito",
|
||||
"Notification": "Notifica",
|
||||
"DDNS": "DNS Dinamico",
|
||||
"NATT": "Traversata NAT",
|
||||
"Group": "Gruppo",
|
||||
"Profile": "Informazioni personali",
|
||||
"Settings": "Impostazioni di sistema",
|
||||
"Logout": "Esci",
|
||||
"NavigateTo": "Vai a",
|
||||
"SelectAPageToNavigateTo": "Scegli una pagina da visitare",
|
||||
"Close": "Chiudi",
|
||||
"Error": "Errore",
|
||||
"Name": "Nome",
|
||||
"Version": "Versione",
|
||||
"Unknown": "Sconosciuto",
|
||||
"Enable": "Abilita",
|
||||
"HideForGuest": "Nascosto ai visitatori",
|
||||
"InstallCommands": "Comando di installazione",
|
||||
"Note": "Osservazione",
|
||||
"Success": "Successo",
|
||||
"Done": "Fine",
|
||||
"Offline": "Non in linea",
|
||||
"Failure": "Fallire",
|
||||
"Loading": "caricamento",
|
||||
"NoResults": "nessun contenuto",
|
||||
"Actions": "Azione",
|
||||
"EditServer": "Modifica server",
|
||||
"Weight": "Peso (più grande è il numero, più alto sarà visualizzato)",
|
||||
"DDNSProfiles": "ID profilo DDNS",
|
||||
"SeparateWithComma": "(separati da virgole)",
|
||||
"Public": "Pubblico",
|
||||
"Private": "privato",
|
||||
"Submit": "invia",
|
||||
"Target": "Bersaglio",
|
||||
"Coverage": "Copertura",
|
||||
"CoverAll": "Copri tutto",
|
||||
"IgnoreAll": "Ignorare tutto",
|
||||
"SpecificServers": "Server specifico",
|
||||
"Type": "Tipo",
|
||||
"Interval": "intervallo",
|
||||
"NotifierGroupID": "ID del gruppo di notifiche",
|
||||
"Trigger": "Grilletto",
|
||||
"TasksToTriggerOnAlert": "L'attività che ha attivato l'avviso",
|
||||
"TasksToTriggerAfterRecovery": "Attività da attivare dopo il ripristino",
|
||||
"Confirm": "Confermo",
|
||||
"ConfirmDeletion": "Confermi l'eliminazione?",
|
||||
"NewService": "Nuovo servizio",
|
||||
"Services": "Servizi",
|
||||
"ShowInService": "Mostra in servizio",
|
||||
"Coverages": {
|
||||
"Only": "Solo server specifici",
|
||||
"Excludes": "Escludi server specifici",
|
||||
"Alarmed": "Eseguito sul server che ha attivato l'allarme"
|
||||
},
|
||||
"EnableFailureNotification": "Abilita la notifica di errore",
|
||||
"MaximumLatency": "Tempo di ritardo massimo (millisecondi)",
|
||||
"MinimumLatency": "Tempo di ritardo minimo (millisecondi)",
|
||||
"EnableLatencyNotification": "Abilita le notifiche ritardate",
|
||||
"EnableTriggerTask": "Abilita attività di attivazione",
|
||||
"CronExpression": "Espressione cron",
|
||||
"Command": "Ordine",
|
||||
"NotifierGroup": "gruppo di notifica",
|
||||
"SendSuccessNotification": "Invia notifica di successo",
|
||||
"LastExecution": "Ultimo giustiziato",
|
||||
"Result": "Risultato",
|
||||
"Scheduled": "Attività pianificate",
|
||||
"Notifier": "Notifica",
|
||||
"AlertRule": "Regole di allerta",
|
||||
"VerifyTLS": "Verifica TLS",
|
||||
"TriggerMode": "Modalità di attivazione",
|
||||
"Rules": "Regola",
|
||||
"RequestMethod": "Metodo di richiesta",
|
||||
"RequestHeader": "Intestazione della richiesta",
|
||||
"DoNotSendTestMessage": "Non inviare messaggi di prova",
|
||||
"Always": "Sempre",
|
||||
"Once": "solo una volta",
|
||||
"Provider": "fornitore",
|
||||
"Domains": "nome di dominio",
|
||||
"MaximumRetryAttempts": "Numero massimo di tentativi",
|
||||
"Refresh": "aggiornare",
|
||||
"CopyPath": "percorso di copia",
|
||||
"Goto": "Vai a",
|
||||
"UpdatePassword": "cambiare la password",
|
||||
"OriginalPassword": "password originale",
|
||||
"NewPassword": "Nuova parola d'ordine",
|
||||
"EditDDNS": "Modifica DDNS",
|
||||
"CreateDDNS": "Crea DDNS",
|
||||
"Credential": "Credenziale",
|
||||
"RequestType": "Tipo di richiesta",
|
||||
"RequestBody": "Richiedi corpo",
|
||||
"FileManager": "Gestore di file",
|
||||
"Downloading": "Download in corso",
|
||||
"Uploading": "Caricamento",
|
||||
"EditNAT": "Modifica la penetrazione della intranet",
|
||||
"CreateNAT": "Creare penetrazione intranet",
|
||||
"LocalService": "servizio locale",
|
||||
"BindHostname": "Associa il nome di dominio",
|
||||
"EditServerGroup": "Modifica gruppo di server",
|
||||
"CreateServerGroup": "Crea gruppo di server",
|
||||
"EditService": "Servizi di editing",
|
||||
"CreateService": "Crea servizio",
|
||||
"EditTask": "Modifica attività",
|
||||
"CreateTask": "Crea attività",
|
||||
"CreateNotifier": "Crea notifica",
|
||||
"EditNotifier": "Modifica notifica",
|
||||
"EditAlertRule": "Modifica le regole degli allarmi",
|
||||
"CreateAlertRule": "Crea regole di avviso",
|
||||
"EditNotifierGroup": "Modifica gruppo di notifiche",
|
||||
"CreateNotifierGroup": "Crea gruppo di notifica",
|
||||
"User": "Utente",
|
||||
"WAF": "Firewall dell'applicazione Web",
|
||||
"SiteName": "Nome del sito",
|
||||
"Language": "lingua",
|
||||
"CustomCodes": "Codice personalizzato (stili e script)",
|
||||
"CustomCodesDashboard": "Codice personalizzato per dashboard",
|
||||
"DashboardOriginalHost": "Nome di dominio/IP del server Dashboard (no CDN)",
|
||||
"CustomPublicDNSNameserversforDDNS": "Server dei nomi DNS pubblici personalizzati per DDNS",
|
||||
"RealIPHeader": "Intestazione della richiesta IP reale",
|
||||
"IPChangeNotification": "Notifica di modifica IP",
|
||||
"FullIPNotification": "Mostra l'indirizzo IP completo nei messaggi di notifica",
|
||||
"LoginFailed": "Accesso non riuscito",
|
||||
"BruteForceAttackingToken": "Segnalino di attacco di forza bruta",
|
||||
"BruteForceAttackingAgentSecret": "Segreti proxy dell'attacco di forza bruta",
|
||||
"NewUser": "nuovo utente",
|
||||
"Count": "contare",
|
||||
"LastBlockReason": "Motivo dell'ultimo divieto",
|
||||
"LastBlockTime": "L'ultima volta che è stato vietato"
|
||||
}
|
||||
152
src/locales/zh-CN/translation.json
Normal file
152
src/locales/zh-CN/translation.json
Normal file
@@ -0,0 +1,152 @@
|
||||
{
|
||||
"nezha": "哪吒监控",
|
||||
"theme": {
|
||||
"light": "亮色",
|
||||
"dark": "暗色",
|
||||
"system": "跟随系统"
|
||||
},
|
||||
"Username": "用户名",
|
||||
"Password": "密码",
|
||||
"Results": {
|
||||
"UsernameMin": "用户名必须至少有 2 个字符。",
|
||||
"PasswordRequired": "密码不能为空。",
|
||||
"ErrorFetchingResource": "获取资源时出错:{{error}}",
|
||||
"SelectAtLeastOneServer": "请至少选择一台服务器。",
|
||||
"UnExpectedError": "意外错误,请查看控制台了解详细信息。",
|
||||
"ForceUpdate": "强制升级:",
|
||||
"NoRowsAreSelected": "未选择任何行",
|
||||
"ThisOperationIsUnrecoverable": "这个操作将无法恢复!",
|
||||
"TaskTriggeredSuccessfully": "任务触发成功",
|
||||
"TheServerDoesNotOnline": "服务器不存在或者还未连接"
|
||||
},
|
||||
"Login": "登录",
|
||||
"Server": "服务器",
|
||||
"Service": "服务",
|
||||
"Task": "任务",
|
||||
"Notification": "通知",
|
||||
"DDNS": "动态域名解析",
|
||||
"NATT": "内网穿透",
|
||||
"Group": "分组",
|
||||
"Profile": "个人信息",
|
||||
"Settings": "系统设置",
|
||||
"Logout": "登出",
|
||||
"NavigateTo": "导航至",
|
||||
"SelectAPageToNavigateTo": "选择一个页面跳转",
|
||||
"Close": "关闭",
|
||||
"Error": "错误",
|
||||
"Name": "名称",
|
||||
"Version": "版本",
|
||||
"Unknown": "未知",
|
||||
"Enable": "启用",
|
||||
"HideForGuest": "对游客隐藏",
|
||||
"InstallCommands": "安装命令",
|
||||
"Note": "备注",
|
||||
"Success": "成功",
|
||||
"Done": "完成",
|
||||
"Offline": "离线",
|
||||
"Failure": "失败",
|
||||
"Loading": "加载中",
|
||||
"NoResults": "没有内容",
|
||||
"Actions": "操作",
|
||||
"EditServer": "编辑服务器",
|
||||
"Weight": "权重(数字越大,显示越靠前)",
|
||||
"DDNSProfiles": "DDNS 配置文件 ID",
|
||||
"SeparateWithComma": "(以英文逗号分隔)",
|
||||
"Public": "公开",
|
||||
"Private": "私有",
|
||||
"Submit": "提交",
|
||||
"Target": "目标",
|
||||
"Coverage": "覆盖范围",
|
||||
"CoverAll": "覆盖全部",
|
||||
"IgnoreAll": "忽略全部",
|
||||
"SpecificServers": "特定服务器",
|
||||
"Type": "类型",
|
||||
"Interval": "间隔",
|
||||
"NotifierGroupID": "通知组ID",
|
||||
"Trigger": "触发",
|
||||
"TasksToTriggerOnAlert": "触发警报的任务",
|
||||
"TasksToTriggerAfterRecovery": "恢复后要触发的任务",
|
||||
"Confirm": "确认",
|
||||
"ConfirmDeletion": "确认删除?",
|
||||
"NewService": "新服务",
|
||||
"Services": "服务",
|
||||
"ShowInService": "服务中显示",
|
||||
"Coverages": {
|
||||
"Excludes": "排除特定服务器",
|
||||
"Only": "仅特定服务器",
|
||||
"Alarmed": "在触发报警的服务器上执行"
|
||||
},
|
||||
"EnableFailureNotification": "启用失败通知",
|
||||
"MaximumLatency": "最大延迟时间(毫秒)",
|
||||
"MinimumLatency": "最小延迟时间(毫秒)",
|
||||
"EnableLatencyNotification": "启用延迟通知",
|
||||
"EnableTriggerTask": "启用触发任务",
|
||||
"CronExpression": "Cron表达式",
|
||||
"Command": "命令",
|
||||
"NotifierGroup": "通知组",
|
||||
"SendSuccessNotification": "发送成功通知",
|
||||
"LastExecution": "最后执行",
|
||||
"Result": "结果",
|
||||
"Scheduled": "计划任务",
|
||||
"AlertRule": "警报规则",
|
||||
"Notifier": "通知",
|
||||
"VerifyTLS": "验证 TLS",
|
||||
"TriggerMode": "触发模式",
|
||||
"Rules": "规则",
|
||||
"RequestMethod": "请求方式",
|
||||
"RequestHeader": "请求头",
|
||||
"DoNotSendTestMessage": "不发送测试消息",
|
||||
"Always": "总是",
|
||||
"Once": "仅一次",
|
||||
"Provider": "提供商",
|
||||
"Domains": "域名",
|
||||
"MaximumRetryAttempts": "最大重试次数",
|
||||
"Refresh": "刷新",
|
||||
"CopyPath": "复制路径",
|
||||
"Goto": "前往",
|
||||
"UpdatePassword": "更改密码",
|
||||
"OriginalPassword": "原始密码",
|
||||
"NewPassword": "新密码",
|
||||
"EditDDNS": "编辑DDNS",
|
||||
"CreateDDNS": "创建DDNS",
|
||||
"Credential": "凭据",
|
||||
"RequestType": "请求类型",
|
||||
"RequestBody": "请求主体",
|
||||
"FileManager": "文件管理器",
|
||||
"Downloading": "下载中",
|
||||
"Uploading": "上传中",
|
||||
"EditNAT": "编辑内网穿透",
|
||||
"CreateNAT": "创建内网穿透",
|
||||
"LocalService": "本地服务",
|
||||
"BindHostname": "绑定域名",
|
||||
"EditServerGroup": "编辑服务器分组",
|
||||
"CreateServerGroup": "创建服务器分组",
|
||||
"EditService": "编辑服务",
|
||||
"CreateService": "创建服务",
|
||||
"EditTask": "编辑任务",
|
||||
"CreateTask": "创建任务",
|
||||
"EditNotifier": "编辑通知",
|
||||
"CreateNotifier": "创建通知",
|
||||
"EditAlertRule": "编辑报警规则",
|
||||
"CreateAlertRule": "创建报警规则",
|
||||
"EditNotifierGroup": "编辑通知分组",
|
||||
"CreateNotifierGroup": "创建通知分组",
|
||||
"User": "用户",
|
||||
"WAF": "Web应用防火墙",
|
||||
"SiteName": "站点名称",
|
||||
"Language": "语言",
|
||||
"CustomCodes": "自定义代码(样式和脚本)",
|
||||
"CustomCodesDashboard": "仪表板的自定义代码",
|
||||
"DashboardOriginalHost": "仪表板服务器域名/IP(无 CDN)",
|
||||
"CustomPublicDNSNameserversforDDNS": "DDNS 的自定义公共 DNS 名称服务器",
|
||||
"RealIPHeader": "真实IP请求头",
|
||||
"IPChangeNotification": "IP变更通知",
|
||||
"FullIPNotification": "在通知消息中显示完整的 IP 地址",
|
||||
"LoginFailed": "登录失败",
|
||||
"BruteForceAttackingToken": "暴力攻击令牌",
|
||||
"BruteForceAttackingAgentSecret": "暴力攻击代理秘密",
|
||||
"NewUser": "新用户",
|
||||
"Count": "计数",
|
||||
"LastBlockReason": "最后封禁原因",
|
||||
"LastBlockTime": "最后封禁时间"
|
||||
}
|
||||
152
src/locales/zh-TW/translation.json
Normal file
152
src/locales/zh-TW/translation.json
Normal file
@@ -0,0 +1,152 @@
|
||||
{
|
||||
"nezha": "哪吒監控",
|
||||
"theme": {
|
||||
"light": "亮色",
|
||||
"dark": "暗色",
|
||||
"system": "跟隨系統"
|
||||
},
|
||||
"Username": "用戶名",
|
||||
"Password": "密碼",
|
||||
"Results": {
|
||||
"UsernameMin": "使用者名稱必須至少有 2 個字元。",
|
||||
"PasswordRequired": "密碼不能為空。",
|
||||
"ErrorFetchingResource": "取得資源時發生錯誤:{{error}}",
|
||||
"SelectAtLeastOneServer": "請至少選擇一台伺服器。",
|
||||
"UnExpectedError": "意外錯誤,請查看控制台以了解詳細資訊。",
|
||||
"ForceUpdate": "強制升級:",
|
||||
"NoRowsAreSelected": "未選擇任何行",
|
||||
"ThisOperationIsUnrecoverable": "這個操作將無法恢復!",
|
||||
"TaskTriggeredSuccessfully": "任務觸發成功",
|
||||
"TheServerDoesNotOnline": "伺服器不存在或尚未連接"
|
||||
},
|
||||
"Login": "登入",
|
||||
"Server": "伺服器",
|
||||
"Service": "服務",
|
||||
"Task": "任務",
|
||||
"Notification": "通知",
|
||||
"DDNS": "動態網域解析",
|
||||
"NATT": "內網穿透",
|
||||
"Group": "分組",
|
||||
"Profile": "個人資訊",
|
||||
"Settings": "系統設定",
|
||||
"Logout": "登出",
|
||||
"NavigateTo": "導航至",
|
||||
"SelectAPageToNavigateTo": "選擇一個頁面跳轉",
|
||||
"Close": "關閉",
|
||||
"Error": "錯誤",
|
||||
"Name": "名稱",
|
||||
"Version": "版本",
|
||||
"Unknown": "未知",
|
||||
"Enable": "啟用",
|
||||
"HideForGuest": "對遊客隱藏",
|
||||
"InstallCommands": "安裝命令",
|
||||
"Note": "備註",
|
||||
"Success": "成功",
|
||||
"Done": "完成",
|
||||
"Offline": "離線",
|
||||
"Failure": "失敗",
|
||||
"NoResults": "沒有內容",
|
||||
"Loading": "載入中",
|
||||
"Actions": "操作",
|
||||
"EditServer": "編輯伺服器",
|
||||
"Weight": "權重(數字越大,顯示越前)",
|
||||
"DDNSProfiles": "DDNS 設定檔 ID",
|
||||
"SeparateWithComma": "(以英文逗號分隔)",
|
||||
"Public": "公開",
|
||||
"Private": "私人",
|
||||
"Submit": "提交",
|
||||
"Target": "目標",
|
||||
"Coverage": "覆蓋範圍",
|
||||
"CoverAll": "覆蓋全部",
|
||||
"IgnoreAll": "忽略全部",
|
||||
"SpecificServers": "特定伺服器",
|
||||
"Type": "類型",
|
||||
"Interval": "間隔",
|
||||
"NotifierGroupID": "通知群組ID",
|
||||
"Trigger": "觸發",
|
||||
"TasksToTriggerOnAlert": "觸發警報的任務",
|
||||
"TasksToTriggerAfterRecovery": "恢復後要觸發的任務",
|
||||
"Confirm": "確認",
|
||||
"ConfirmDeletion": "確認刪除?",
|
||||
"NewService": "新服務",
|
||||
"Services": "服務",
|
||||
"ShowInService": "服務中顯示",
|
||||
"Coverages": {
|
||||
"Only": "僅特定伺服器",
|
||||
"Excludes": "排除特定伺服器",
|
||||
"Alarmed": "在觸發警報的伺服器上執行"
|
||||
},
|
||||
"EnableFailureNotification": "啟用失敗通知",
|
||||
"MaximumLatency": "最大延遲時間(毫秒)",
|
||||
"MinimumLatency": "最小延遲時間(毫秒)",
|
||||
"EnableLatencyNotification": "啟用延遲通知",
|
||||
"EnableTriggerTask": "啟用觸發任務",
|
||||
"CronExpression": "Cron表達式",
|
||||
"Command": "命令",
|
||||
"NotifierGroup": "通知群組",
|
||||
"SendSuccessNotification": "發送成功通知",
|
||||
"LastExecution": "最後執行",
|
||||
"Result": "結果",
|
||||
"Scheduled": "計劃任務",
|
||||
"AlertRule": "警報規則",
|
||||
"Notifier": "通知",
|
||||
"VerifyTLS": "驗證 TLS",
|
||||
"TriggerMode": "觸發模式",
|
||||
"Rules": "規則",
|
||||
"RequestMethod": "請求方式",
|
||||
"RequestHeader": "請求頭",
|
||||
"DoNotSendTestMessage": "不發送測試訊息",
|
||||
"Always": "總是",
|
||||
"Once": "僅一次",
|
||||
"Provider": "提供者",
|
||||
"Domains": "網域",
|
||||
"MaximumRetryAttempts": "最大重試次數",
|
||||
"Refresh": "刷新",
|
||||
"CopyPath": "複製路徑",
|
||||
"Goto": "前往",
|
||||
"UpdatePassword": "更改密碼",
|
||||
"OriginalPassword": "原始密碼",
|
||||
"NewPassword": "新密碼",
|
||||
"EditDDNS": "編輯DDNS",
|
||||
"CreateDDNS": "建立DDNS",
|
||||
"Credential": "憑證",
|
||||
"RequestType": "請求類型",
|
||||
"RequestBody": "請求主體",
|
||||
"FileManager": "文件管理器",
|
||||
"Downloading": "下載中",
|
||||
"Uploading": "上傳中",
|
||||
"EditNAT": "編輯內網穿透",
|
||||
"CreateNAT": "創建內網穿透",
|
||||
"LocalService": "本地服務",
|
||||
"BindHostname": "綁定域名",
|
||||
"EditServerGroup": "編輯伺服器分組",
|
||||
"CreateServerGroup": "建立伺服器分組",
|
||||
"EditService": "編輯服務",
|
||||
"CreateService": "創建服務",
|
||||
"EditTask": "編輯任務",
|
||||
"CreateTask": "創建任務",
|
||||
"CreateNotifier": "建立通知",
|
||||
"EditNotifier": "編輯通知",
|
||||
"EditAlertRule": "編輯警報規則",
|
||||
"CreateAlertRule": "建立警報規則",
|
||||
"EditNotifierGroup": "編輯通知分組",
|
||||
"CreateNotifierGroup": "建立通知分組",
|
||||
"User": "使用者",
|
||||
"WAF": "Web應用防火牆",
|
||||
"SiteName": "網站名稱",
|
||||
"Language": "語言",
|
||||
"CustomCodes": "自訂程式碼(樣式和腳本)",
|
||||
"CustomCodesDashboard": "儀表板的自訂程式碼",
|
||||
"DashboardOriginalHost": "儀表板伺服器網域/IP(無 CDN)",
|
||||
"CustomPublicDNSNameserversforDDNS": "DDNS 的自訂公共 DNS 名稱伺服器",
|
||||
"RealIPHeader": "真實IP請求頭",
|
||||
"IPChangeNotification": "IP變更通知",
|
||||
"FullIPNotification": "在通知訊息中顯示完整的 IP 位址",
|
||||
"LoginFailed": "登入失敗",
|
||||
"BruteForceAttackingToken": "暴力攻擊令牌",
|
||||
"BruteForceAttackingAgentSecret": "暴力攻擊代理秘密",
|
||||
"NewUser": "新用戶",
|
||||
"Count": "計數",
|
||||
"LastBlockReason": "最後封鎖原因",
|
||||
"LastBlockTime": "最後封鎖時間"
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
} from "react-router-dom";
|
||||
|
||||
import './index.css'
|
||||
import './lib/i18n';
|
||||
|
||||
import Root from "./routes/root";
|
||||
import ErrorPage from "./error-page";
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import i18next from "i18next";
|
||||
export const triggerModes: Record<number, string> = {
|
||||
0: "Always",
|
||||
1: "Once",
|
||||
0: i18next.t("Always"),
|
||||
1: i18next.t("Once"),
|
||||
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import i18next from "i18next";
|
||||
export const cronTypes: Record<number, string> = {
|
||||
0: "Scheduled",
|
||||
1: "Trigger",
|
||||
0: i18next.t("Scheduled"),
|
||||
1: i18next.t("Trigger"),
|
||||
}
|
||||
|
||||
export const cronCoverageTypes: Record<number, string> = {
|
||||
0: "Only specific servers",
|
||||
1: "All excludes specific servers",
|
||||
2: "The alarmed servers"
|
||||
0: i18next.t("Coverages.Only"),
|
||||
1: i18next.t("Coverages.Excludes"),
|
||||
2: i18next.t("Coverages.Alarmed"),
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import i18next from "i18next";
|
||||
export const serviceTypes: Record<number, string> = {
|
||||
1: "HTTP GET",
|
||||
2: "ICMP Ping",
|
||||
@@ -5,6 +6,6 @@ export const serviceTypes: Record<number, string> = {
|
||||
}
|
||||
|
||||
export const serviceCoverageTypes: Record<number, string> = {
|
||||
0: "All excludes specific servers",
|
||||
1: "Only specific servers",
|
||||
0: i18next.t("Coverages.Excludes"),
|
||||
1: i18next.t("Coverages.Only"),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user