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

This commit is contained in:
GuGuGu
2024-11-29 13:47:09 +01:00
committed by GitHub
parent 5850fe7fca
commit 47f092918e
44 changed files with 1138 additions and 366 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>
);

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>