diff --git a/app/routes/inbounds.py b/app/routes/inbounds.py
@@ -25,7 +25,7 @@ def get_inbounds(db: DBDep, tag: str = Query(None)):
return paginate(db, query)
-@router.get("/hosts", response_model=Page[InboundHost])
+@router.get("/hosts", response_model=Page[InboundHostResponse])
def get_hosts(db: DBDep):
return paginate(db.query(DBInboundHost))
diff --git a/dashboard/public/locales/en.json b/dashboard/public/locales/en.json
@@ -37,6 +37,7 @@
"fingerprint": "Fingerprint",
"enable": "Enable",
"disable": "Disable",
+ "close": "Close",
"save": "Save",
"page": {
"settings": {
@@ -148,6 +149,10 @@
}
},
"hosts": {
+ "no-inbound-selection-alert": {
+ "title": "No inbound is selected",
+ "desc": "You must select an inbound inorder to create host."
+ },
"allowinsecure": "Allow insecure",
"dialogs": {
"edition": {
diff --git a/dashboard/public/locales/kur.json b/dashboard/public/locales/kur.json
@@ -30,6 +30,7 @@
"fingerprint": "Fingerprint",
"enable": "Çalakî ke",
"disable": "Naçalakî ke",
+ "close": "Dayixe",
"save": "Seyv Bike",
"page": {
"settings": {
@@ -141,6 +142,10 @@
}
},
"hosts": {
+ "no-inbound-selection-alert": {
+ "title": "Hîç înbawendêk hell nebijardirawe",
+ "desc": "Debêt înbawendêk hell bibjêrî ta we ku hostêk dirust bikey."
+ },
"allowinsecure": "Allow insecure",
"dialogs": {
"edition": {
diff --git a/dashboard/src/features/hosts/dialogs/mutation/fields/address.tsx b/dashboard/src/features/hosts/dialogs/mutation/fields/address.tsx
@@ -0,0 +1,31 @@
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+ Input
+} from "@marzneshin/components";
+import { useFormContext } from "react-hook-form";
+import { useTranslation } from "react-i18next";
+
+export const AddressField = () => {
+ const { t } = useTranslation()
+ const form = useFormContext()
+ return (
+ <FormField
+ control={form.control}
+ name="address"
+ render={({ field }) => (
+ <FormItem className="w-2/3">
+ <FormLabel>{t('address')}</FormLabel>
+ <FormControl>
+ <Input {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ )
+}
+
diff --git a/dashboard/src/features/hosts/dialogs/mutation/fields/allow-insecure.tsx b/dashboard/src/features/hosts/dialogs/mutation/fields/allow-insecure.tsx
@@ -0,0 +1,52 @@
+
+import {
+ Alert,
+ AlertDescription,
+ AlertTitle,
+ Checkbox,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@marzneshin/components";
+import { ExclamationTriangleIcon } from "@radix-ui/react-icons";
+import { useFormContext } from "react-hook-form";
+import { useTranslation } from "react-i18next";
+
+export const AllowInsecureField = () => {
+ const { t } = useTranslation()
+ const form = useFormContext()
+ return (
+ <FormField
+ control={form.control}
+ name="allowinsecure"
+ render={({ field }) => (
+ <FormItem className="my-2">
+ <FormControl>
+ <div className="flex flex-row gap-1 items-center">
+ <Checkbox
+ checked={field.value}
+ onCheckedChange={field.onChange}
+ />
+ <FormLabel>
+ {t('page.hosts.allowinsecure')}
+ </FormLabel>
+ </div>
+ </FormControl>
+ <FormMessage />
+ {form.getValues().allowinsecure &&
+ <Alert variant="destructive">
+ <ExclamationTriangleIcon className="w-4 h-4" />
+ <AlertTitle>
+ Warning
+ </AlertTitle>
+ <AlertDescription>
+ {t('page.hosts.dialogs.mutation.allowinsecure.alert')}
+ </AlertDescription>
+ </Alert>}
+ </FormItem>
+ )}
+ />
+ )
+}
diff --git a/dashboard/src/features/hosts/dialogs/mutation/fields/alpn.tsx b/dashboard/src/features/hosts/dialogs/mutation/fields/alpn.tsx
@@ -0,0 +1,45 @@
+
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@marzneshin/components";
+import { useFormContext } from "react-hook-form";
+import { useTranslation } from "react-i18next";
+
+export const AlpnField = () => {
+ const { t } = useTranslation()
+ const form = useFormContext()
+ return (
+ <FormField
+ control={form.control}
+ name="alpn"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>{t('alpn')}</FormLabel>
+ <Select onValueChange={field.onChange} defaultValue={field.value}>
+ <FormControl>
+ <SelectTrigger>
+ <SelectValue placeholder="Select ALPN" />
+ </SelectTrigger>
+ </FormControl>
+ <SelectContent>
+ <SelectItem value="none">None</SelectItem>
+ <SelectItem value="h2">h2</SelectItem>
+ <SelectItem value="http/1.1">HTTP 1.1</SelectItem>
+ <SelectItem value="h2,http/1.1"> H2 HTTP 1.1</SelectItem>
+ </SelectContent>
+ </Select>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ )
+}
diff --git a/dashboard/src/features/hosts/dialogs/mutation/fields/fingerprint.tsx b/dashboard/src/features/hosts/dialogs/mutation/fields/fingerprint.tsx
@@ -0,0 +1,53 @@
+
+
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@marzneshin/components";
+import { useFormContext } from "react-hook-form";
+import { useTranslation } from "react-i18next";
+
+export const FingerprintField = () => {
+ const { t } = useTranslation()
+ const form = useFormContext()
+ return (
+ <FormField
+ control={form.control}
+ name="fingerprint"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>{t('fingerprint')}</FormLabel>
+ <Select onValueChange={field.onChange} defaultValue={field.value}>
+ <FormControl>
+ <SelectTrigger>
+ <SelectValue placeholder="Select fingerprint" />
+ </SelectTrigger>
+ </FormControl>
+ <SelectContent>
+ <SelectItem value="none">None</SelectItem>
+ <SelectItem value="chrome">Chrome</SelectItem>
+ <SelectItem value="firefox">Firefox</SelectItem>
+ <SelectItem value="safari">Safari</SelectItem>
+ <SelectItem value="ios">iOS</SelectItem>
+ <SelectItem value="android">Android</SelectItem>
+ <SelectItem value="edge">Edge</SelectItem>
+ <SelectItem value="360">360</SelectItem>
+ <SelectItem value="qq">qq</SelectItem>
+ <SelectItem value="random">Random</SelectItem>
+ <SelectItem value="randomized">Randomized</SelectItem>
+ </SelectContent>
+ </Select>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ )
+}
diff --git a/dashboard/src/features/hosts/dialogs/mutation/fields/host.tsx b/dashboard/src/features/hosts/dialogs/mutation/fields/host.tsx
@@ -0,0 +1,30 @@
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+ Input
+} from "@marzneshin/components";
+import { useFormContext } from "react-hook-form";
+import { useTranslation } from "react-i18next";
+
+export const HostField = () => {
+ const { t } = useTranslation()
+ const form = useFormContext()
+ return (
+ <FormField
+ control={form.control}
+ name="host"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>{t('host')}</FormLabel>
+ <FormControl>
+ <Input {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ )
+}
diff --git a/dashboard/src/features/hosts/dialogs/mutation/fields/index.ts b/dashboard/src/features/hosts/dialogs/mutation/fields/index.ts
@@ -0,0 +1,10 @@
+export * from './remark'
+export * from './address'
+export * from './port'
+export * from './path'
+export * from './host'
+export * from './allow-insecure'
+export * from './security'
+export * from './sni'
+export * from './alpn'
+export * from './fingerprint'
diff --git a/dashboard/src/features/hosts/dialogs/mutation/fields/path.tsx b/dashboard/src/features/hosts/dialogs/mutation/fields/path.tsx
@@ -0,0 +1,31 @@
+
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+ Input
+} from "@marzneshin/components";
+import { useFormContext } from "react-hook-form";
+import { useTranslation } from "react-i18next";
+
+export const PathField = () => {
+ const { t } = useTranslation()
+ const form = useFormContext()
+ return (
+ <FormField
+ control={form.control}
+ name="path"
+ render={({ field }) => (
+ <FormItem >
+ <FormLabel>{t('path')}</FormLabel>
+ <FormControl>
+ <Input {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ )
+}
diff --git a/dashboard/src/features/hosts/dialogs/mutation/fields/port.tsx b/dashboard/src/features/hosts/dialogs/mutation/fields/port.tsx
@@ -0,0 +1,30 @@
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+ Input
+} from "@marzneshin/components";
+import { useFormContext } from "react-hook-form";
+import { useTranslation } from "react-i18next";
+
+export const PortField = () => {
+ const { t } = useTranslation()
+ const form = useFormContext()
+ return (
+ <FormField
+ control={form.control}
+ name="port"
+ render={({ field }) => (
+ <FormItem className="w-1/3">
+ <FormLabel>{t('port')}</FormLabel>
+ <FormControl>
+ <Input type="number" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ )
+}
diff --git a/dashboard/src/features/hosts/dialogs/mutation/fields/remark.tsx b/dashboard/src/features/hosts/dialogs/mutation/fields/remark.tsx
@@ -0,0 +1,30 @@
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+ Input
+} from "@marzneshin/components";
+import { useFormContext } from "react-hook-form";
+import { useTranslation } from "react-i18next";
+
+export const RemarkField = () => {
+ const { t } = useTranslation()
+ const form = useFormContext()
+ return (
+ <FormField
+ control={form.control}
+ name="remark"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>{t('name')}</FormLabel>
+ <FormControl>
+ <Input {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ )
+}
diff --git a/dashboard/src/features/hosts/dialogs/mutation/fields/security.tsx b/dashboard/src/features/hosts/dialogs/mutation/fields/security.tsx
@@ -0,0 +1,62 @@
+
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue
+} from "@marzneshin/components";
+import { useEffect, useState } from "react";
+import { useFormContext } from "react-hook-form";
+import { useTranslation } from "react-i18next";
+import { AllowInsecureField, AlpnField, FingerprintField, SniField } from ".";
+
+export const SecurityFields = () => {
+ const { t } = useTranslation()
+ const form = useFormContext()
+ const security = form.watch().security
+
+ const [extraSecurity, setExtraSecurity] = useState<boolean>(["tls", "inbound_default"].includes(security))
+ useEffect(() => {
+ setExtraSecurity(["tls", "inbound_default"].includes(security))
+ }, [security, setExtraSecurity])
+ return (
+ <>
+ <FormField
+ control={form.control}
+ name="security"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>{t('security')}</FormLabel>
+ <Select onValueChange={field.onChange} defaultValue={field.value}>
+ <FormControl>
+ <SelectTrigger>
+ <SelectValue placeholder="Select a verified email to display" />
+ </SelectTrigger>
+ </FormControl>
+ <SelectContent>
+ <SelectItem value="none">None</SelectItem>
+ <SelectItem value="tls">TLS</SelectItem>
+ <SelectItem value="inbound_default">Inbound Default</SelectItem>
+ </SelectContent>
+ </Select>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ {extraSecurity &&
+ <>
+ <SniField />
+ <AlpnField />
+ <FingerprintField />
+ <AllowInsecureField />
+ </>
+ }
+ </>
+ )
+}
diff --git a/dashboard/src/features/hosts/dialogs/mutation/fields/sni.tsx b/dashboard/src/features/hosts/dialogs/mutation/fields/sni.tsx
@@ -0,0 +1,30 @@
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+ Input
+} from "@marzneshin/components";
+import { useFormContext } from "react-hook-form";
+import { useTranslation } from "react-i18next";
+
+export const SniField = () => {
+ const { t } = useTranslation()
+ const form = useFormContext()
+ return (
+ <FormField
+ control={form.control}
+ name="sni"
+ render={({ field }) => (
+ <FormItem >
+ <FormLabel>{t('SNI')}</FormLabel>
+ <FormControl>
+ <Input {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ )
+}
diff --git a/dashboard/src/features/hosts/dialogs/mutation/index.tsx b/dashboard/src/features/hosts/dialogs/mutation/index.tsx
@@ -1,27 +1,11 @@
-
-import { FC, useEffect, useState } from "react";
+import { FC, useEffect } from "react";
import {
DialogTitle,
DialogContent,
Dialog,
DialogHeader,
Form,
- FormItem,
- FormControl,
- FormMessage,
- FormLabel,
- Input,
- FormField,
- Checkbox,
Button,
- SelectItem,
- SelectContent,
- SelectValue,
- Select,
- SelectTrigger,
- Alert,
- AlertTitle,
- AlertDescription
} from '@marzneshin/components';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
@@ -33,7 +17,14 @@ import {
useHostsCreationMutation,
useHostsUpdateMutation
} from "@marzneshin/features/hosts";
-import { ExclamationTriangleIcon } from "@radix-ui/react-icons";
+import { RemarkField } from "./fields/remark";
+import {
+ AddressField,
+ PortField,
+ HostField,
+ PathField,
+ SecurityFields,
+} from "./fields";
interface MutationDialogProps {
entity: HostType | null
@@ -42,6 +33,11 @@ interface MutationDialogProps {
onOpenChange: (state: boolean) => void
}
+const transformFormValue = (values: HostType) => {
+ const alpn = values.alpn === "none" ? "" : values.alpn
+ const fingerprint = values.fingerprint === "none" ? "" : values.fingerprint
+ return { ...values, alpn, fingerprint }
+}
export const HostsMutationDialog: FC<MutationDialogProps> = ({
inboundId,
@@ -56,18 +52,13 @@ export const HostsMutationDialog: FC<MutationDialogProps> = ({
const updateMutation = useHostsUpdateMutation();
const createMutation = useHostsCreationMutation();
const { t } = useTranslation();
- const security = form.watch().security
- const [extraSecurity, setExtraSecurity] = useState<boolean>(["tls", "inbound_default"].includes(security))
-
- useEffect(() => {
- setExtraSecurity(["tls", "inbound_default"].includes(security))
- }, [security, setExtraSecurity])
const submit = (values: HostType) => {
- if (entity) {
- updateMutation.mutate({ inboundId, host: values });
+ const host = transformFormValue(values)
+ if (entity && entity.id !== undefined) {
+ updateMutation.mutate({ hostId: entity.id, host });
} else {
- createMutation.mutate({ inboundId, host: values });
+ createMutation.mutate({ inboundId, host });
}
onOpenChange(false);
};
@@ -89,200 +80,14 @@ export const HostsMutationDialog: FC<MutationDialogProps> = ({
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(submit)}>
- <FormField
- control={form.control}
- name="remark"
- render={({ field }) => (
- <FormItem >
- <FormLabel>{t('name')}</FormLabel>
- <FormControl>
- <Input {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
+ <RemarkField />
<div className="flex flex-row gap-1 items-center">
- <FormField
- control={form.control}
- name="address"
- render={({ field }) => (
- <FormItem className="w-2/3">
- <FormLabel>{t('address')}</FormLabel>
- <FormControl>
- <Input {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
- <FormField
- control={form.control}
- name="port"
- render={({ field }) => (
- <FormItem className="w-1/3">
- <FormLabel>{t('port')}</FormLabel>
- <FormControl>
- <Input type="number" {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
+ <AddressField />
+ <PortField />
</div>
- <FormField
- control={form.control}
- name="path"
- render={({ field }) => (
- <FormItem >
- <FormLabel>{t('path')}</FormLabel>
- <FormControl>
- <Input {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
-
- <FormField
- control={form.control}
- name="host"
- render={({ field }) => (
- <FormItem >
- <FormLabel>{t('host')}</FormLabel>
- <FormControl>
- <Input {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
-
- <FormField
- control={form.control}
- name="security"
- render={({ field }) => (
- <FormItem>
- <FormLabel>{t('security')}</FormLabel>
- <Select onValueChange={field.onChange} defaultValue={field.value}>
- <FormControl>
- <SelectTrigger>
- <SelectValue placeholder="Select a verified email to display" />
- </SelectTrigger>
- </FormControl>
- <SelectContent>
- <SelectItem value="none">None</SelectItem>
- <SelectItem value="tls">TLS</SelectItem>
- <SelectItem value="inbound_default">Inbound Default</SelectItem>
- </SelectContent>
- </Select>
- <FormMessage />
- </FormItem>
- )}
- />
- {extraSecurity &&
- <>
- <FormField
- control={form.control}
- name="sni"
- render={({ field }) => (
- <FormItem >
- <FormLabel>{t('SNI')}</FormLabel>
- <FormControl>
- <Input {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
-
- <FormField
- control={form.control}
- name="alpn"
- render={({ field }) => (
- <FormItem>
- <FormLabel>{t('alpn')}</FormLabel>
- <Select onValueChange={field.onChange} defaultValue={field.value}>
- <FormControl>
- <SelectTrigger>
- <SelectValue placeholder="Select ALPN" />
- </SelectTrigger>
- </FormControl>
- <SelectContent>
- <SelectItem value="none">None</SelectItem>
- <SelectItem value="h2">h2</SelectItem>
- <SelectItem value="http/1.1">HTTP 1.1</SelectItem>
- <SelectItem value="h2,http/1.1"> H2 HTTP 1.1</SelectItem>
- </SelectContent>
- </Select>
- <FormMessage />
- </FormItem>
- )}
- />
-
- <FormField
- control={form.control}
- name="fingerprint"
- render={({ field }) => (
- <FormItem>
- <FormLabel>{t('fingerprint')}</FormLabel>
- <Select onValueChange={field.onChange} defaultValue={field.value}>
- <FormControl>
- <SelectTrigger>
- <SelectValue placeholder="Select fingerprint" />
- </SelectTrigger>
- </FormControl>
- <SelectContent>
- <SelectItem value="none">None</SelectItem>
- <SelectItem value="chrome">Chrome</SelectItem>
- <SelectItem value="firefox">Firefox</SelectItem>
- <SelectItem value="safari">Safari</SelectItem>
- <SelectItem value="ios">iOS</SelectItem>
- <SelectItem value="android">Android</SelectItem>
- <SelectItem value="edge">Edge</SelectItem>
- <SelectItem value="360">360</SelectItem>
- <SelectItem value="qq">qq</SelectItem>
- <SelectItem value="random">Random</SelectItem>
- <SelectItem value="randomized">Randomized</SelectItem>
- </SelectContent>
- </Select>
- <FormMessage />
- </FormItem>
- )}
- />
-
- <FormField
- control={form.control}
- name="allowinsecure"
- render={({ field }) => (
- <FormItem className="my-2">
- <FormControl>
- <div className="flex flex-row gap-1 items-center">
- <Checkbox
- checked={field.value}
- onCheckedChange={field.onChange}
- />
- <FormLabel>
- {t('page.hosts.allowinsecure')}
- </FormLabel>
- </div>
- </FormControl>
- <FormMessage />
- {form.getValues().allowinsecure &&
- <Alert variant="destructive">
- <ExclamationTriangleIcon className="w-4 h-4" />
- <AlertTitle>
- Warning
- </AlertTitle>
- <AlertDescription>
- {t('page.hosts.dialogs.mutation.allowinsecure.alert')}
- </AlertDescription>
- </Alert>}
- </FormItem>
- )}
- />
- </>
- }
+ <PathField />
+ <HostField />
+ <SecurityFields />
<Button
className="mt-3 w-full font-semibold"
type="submit"
diff --git a/dashboard/src/features/hosts/schema/get-default.ts b/dashboard/src/features/hosts/schema/get-default.ts
@@ -8,9 +8,9 @@ export const getDefaultValues = (): HostSchemaType => {
path: '',
address: '',
remark: '',
- security: 'none',
- alpn: 'none',
+ security: 'inbound_default',
+ alpn: "",
allowinsecure: false,
- fingerprint: 'none',
+ fingerprint: "",
}
};
diff --git a/dashboard/src/features/hosts/schema/hosts-schema.ts b/dashboard/src/features/hosts/schema/hosts-schema.ts
@@ -6,14 +6,15 @@ export const HostSchema = z.object({
port: z.coerce.number()
.gte(1, 'Port must be more than 1')
.lte(65535, 'Port can not be more than 65535')
- .or(z.string()),
- path: z.string(),
+ .or(z.string()).optional(),
+ path: z.string().optional(),
sni: z.string().optional(),
host: z.string().optional(),
- security: z.enum(['inbound_default', 'none', 'tls']),
- alpn: z.enum(['none', 'h2', 'http/1.1', 'h2,http/1.1']).optional(),
- allowinsecure: z.boolean().default(false),
+ security: z.enum(['inbound_default', 'none', 'tls']).default("inbound_default"),
+ alpn: z.enum(['h2', 'http/1.1', 'h2,http/1.1', 'none', '']).optional().default("none"),
+ allowinsecure: z.boolean().default(false).optional(),
fingerprint: z.enum([
+ "",
"none",
"chrome",
"firefox",
@@ -25,5 +26,5 @@ export const HostSchema = z.object({
"qq",
"random",
"randomized",
- ]).optional(),
+ ]).optional().default("none"),
});
diff --git a/dashboard/src/features/hosts/services/host-mutation.dto.ts b/dashboard/src/features/hosts/services/host-mutation.dto.ts
@@ -4,3 +4,8 @@ export interface HostRequestDto {
inboundId: number
host: HostType
}
+
+export interface HostUpdateRequestDto {
+ hostId: number
+ host: HostType
+}
diff --git a/dashboard/src/features/hosts/services/update-host.mutate.ts b/dashboard/src/features/hosts/services/update-host.mutate.ts
@@ -3,26 +3,26 @@ import { useMutation } from "@tanstack/react-query";
import { fetch, queryClient } from "@marzneshin/utils";
import { toast } from "sonner";
import i18n from "@marzneshin/features/i18n";
-import { HostRequestDto } from "./host-mutation.dto";
+import { HostUpdateRequestDto } from "./host-mutation.dto";
-export async function fetchHostsUpdateMutation({ inboundId, host }: HostRequestDto): Promise<HostRequestDto> {
- return fetch(`/inbounds/${inboundId}/hosts`,
- { method: 'put', body: host }).then((host: HostType) => ({ inboundId, host }));
+export async function fetchHostsUpdateMutation({ hostId, host }: HostUpdateRequestDto): Promise<HostUpdateRequestDto> {
+ return fetch(`/inbounds/hosts/${hostId}`,
+ { method: 'put', body: host }).then((host: HostType) => ({ hostId, host }));
}
-const handleError = (error: Error, value: HostRequestDto) => {
+const handleError = (error: Error, value: HostUpdateRequestDto) => {
toast.error(
- i18n.t('events.create.error', { name: value.host.remark }),
+ i18n.t('events.update.error', { name: value.host.remark }),
{
description: error.message
})
}
-const handleSuccess = (value: HostRequestDto) => {
+const handleSuccess = (value: HostUpdateRequestDto) => {
toast.success(
- i18n.t('events.create.success.title', { name: value.host.remark }),
+ i18n.t('events.update.success.title', { name: value.host.remark }),
{
- description: i18n.t('events.create.success.desc')
+ description: i18n.t('events.update.success.desc')
})
queryClient.invalidateQueries({ queryKey: [HostsQueryFetchKey] })
}
diff --git a/dashboard/src/features/hosts/tables/inbound-hosts/inbound-not-selected-alert-dialog.tsx b/dashboard/src/features/hosts/tables/inbound-hosts/inbound-not-selected-alert-dialog.tsx
@@ -0,0 +1,42 @@
+
+import {
+ AlertDialog,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle
+} from '@marzneshin/components'
+import { FC } from 'react'
+import { useTranslation } from 'react-i18next'
+
+interface InboundNotSelectedAlertDialogProps {
+ open: boolean;
+ onOpenChange: (s: boolean) => void;
+}
+
+export const InboundNotSelectedAlertDialog: FC<InboundNotSelectedAlertDialogProps> = (
+ { open, onOpenChange }
+) => {
+ const { t } = useTranslation()
+ return (
+ <AlertDialog open={open} onOpenChange={onOpenChange}>
+ <AlertDialogContent>
+ <AlertDialogHeader>
+ <AlertDialogTitle>
+ {t('page.hosts.no-inbound-selection-alert.title')}
+ </AlertDialogTitle>
+ <AlertDialogDescription>
+ {t('page.hosts.no-inbound-selection-alert.desc')}
+ </AlertDialogDescription>
+ </AlertDialogHeader>
+ <AlertDialogFooter>
+ <AlertDialogCancel>
+ {t('close')}
+ </AlertDialogCancel>
+ </AlertDialogFooter>
+ </AlertDialogContent>
+ </AlertDialog>
+ )
+}
diff --git a/dashboard/src/features/hosts/tables/inbound-hosts/index.tsx b/dashboard/src/features/hosts/tables/inbound-hosts/index.tsx
@@ -10,15 +10,18 @@ import { useState } from 'react'
import { useInboundsQuery } from '@marzneshin/features/inbounds'
import { columns } from './columns'
import { HostSettingsDialog } from '../../dialogs/settings'
+import { useDialog } from '@marzneshin/components'
+import { InboundNotSelectedAlertDialog } from './inbound-not-selected-alert-dialog'
export const InboundHostsTable = () => {
const { data: inbounds } = useInboundsQuery()
const [selectedInbound, setSelectedInbound] = useState<string>(String(inbounds[0]?.id))
const { data } = useHostsQuery(Number(selectedInbound))
- const [mutationDialogOpen, setMutationDialogOpen] = useState<boolean>(false);
- const [deleteDialogOpen, setDeleteDialogOpen] = useState<boolean>(false);
- const [settingsDialogOpen, setSettingsDialogOpen] = useState<boolean>(false);
+ const [mutationDialogOpen, setMutationDialogOpen] = useDialog();
+ const [deleteDialogOpen, setDeleteDialogOpen] = useDialog();
+ const [settingsDialogOpen, setSettingsDialogOpen] = useDialog();
+ const [inboundSelectionAlert, setInboundSelectionAlert] = useDialog();
const [selectedEntity, selectEntity] = useState<HostType | null>(null);
const onEdit = (entity: HostType) => {
@@ -32,8 +35,12 @@ export const InboundHostsTable = () => {
};
const onCreate = () => {
- selectEntity(null);
- setMutationDialogOpen(true);
+ if (selectedInbound) {
+ selectEntity(null);
+ setMutationDialogOpen(true);
+ } else {
+ setInboundSelectionAlert(true)
+ }
};
const onOpen = (entity: HostType) => {
@@ -43,6 +50,10 @@ export const InboundHostsTable = () => {
return (
<div>
+ <InboundNotSelectedAlertDialog
+ open={inboundSelectionAlert}
+ onOpenChange={setInboundSelectionAlert}
+ />
<HostSettingsDialog
open={settingsDialogOpen}
onOpenChange={setSettingsDialogOpen}