mirror of
https://github.com/Buriburizaem0n/admin-frontend-domain.git
synced 2026-02-05 13:10:08 +00:00
feat(I18n): Add multiple languages (zh-CN/zh-TW/en/it) (#8)
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user