diff --git a/app/routes/inbounds.py b/app/routes/inbounds.py index da88aca..691c033 100644 --- 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 index 6633029..4900019 100644 --- 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 index 9998075..2b1be22 100644 --- 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 new file mode 100644 index 0000000..1d98324 --- /dev/null +++ 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 ( + ( + + {t('address')} + + + + + + )} + /> + ) +} + diff --git a/dashboard/src/features/hosts/dialogs/mutation/fields/allow-insecure.tsx b/dashboard/src/features/hosts/dialogs/mutation/fields/allow-insecure.tsx new file mode 100644 index 0000000..5b3ba1c --- /dev/null +++ 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 ( + ( + + +
+ + + {t('page.hosts.allowinsecure')} + +
+
+ + {form.getValues().allowinsecure && + + + + Warning + + + {t('page.hosts.dialogs.mutation.allowinsecure.alert')} + + } +
+ )} + /> + ) +} diff --git a/dashboard/src/features/hosts/dialogs/mutation/fields/alpn.tsx b/dashboard/src/features/hosts/dialogs/mutation/fields/alpn.tsx new file mode 100644 index 0000000..793eb7a --- /dev/null +++ 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 ( + ( + + {t('alpn')} + + + + )} + /> + ) +} diff --git a/dashboard/src/features/hosts/dialogs/mutation/fields/fingerprint.tsx b/dashboard/src/features/hosts/dialogs/mutation/fields/fingerprint.tsx new file mode 100644 index 0000000..2686855 --- /dev/null +++ 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 ( + ( + + {t('fingerprint')} + + + + )} + /> + ) +} diff --git a/dashboard/src/features/hosts/dialogs/mutation/fields/host.tsx b/dashboard/src/features/hosts/dialogs/mutation/fields/host.tsx new file mode 100644 index 0000000..2bbfba0 --- /dev/null +++ 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 ( + ( + + {t('host')} + + + + + + )} + /> + ) +} diff --git a/dashboard/src/features/hosts/dialogs/mutation/fields/index.ts b/dashboard/src/features/hosts/dialogs/mutation/fields/index.ts new file mode 100644 index 0000000..26e6b32 --- /dev/null +++ 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 new file mode 100644 index 0000000..2033bfb --- /dev/null +++ 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 ( + ( + + {t('path')} + + + + + + )} + /> + ) +} diff --git a/dashboard/src/features/hosts/dialogs/mutation/fields/port.tsx b/dashboard/src/features/hosts/dialogs/mutation/fields/port.tsx new file mode 100644 index 0000000..d961168 --- /dev/null +++ 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 ( + ( + + {t('port')} + + + + + + )} + /> + ) +} diff --git a/dashboard/src/features/hosts/dialogs/mutation/fields/remark.tsx b/dashboard/src/features/hosts/dialogs/mutation/fields/remark.tsx new file mode 100644 index 0000000..2950f5d --- /dev/null +++ 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 ( + ( + + {t('name')} + + + + + + )} + /> + ) +} diff --git a/dashboard/src/features/hosts/dialogs/mutation/fields/security.tsx b/dashboard/src/features/hosts/dialogs/mutation/fields/security.tsx new file mode 100644 index 0000000..beae0b7 --- /dev/null +++ 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(["tls", "inbound_default"].includes(security)) + useEffect(() => { + setExtraSecurity(["tls", "inbound_default"].includes(security)) + }, [security, setExtraSecurity]) + return ( + <> + ( + + {t('security')} + + + + )} + /> + {extraSecurity && + <> + + + + + + } + + ) +} diff --git a/dashboard/src/features/hosts/dialogs/mutation/fields/sni.tsx b/dashboard/src/features/hosts/dialogs/mutation/fields/sni.tsx new file mode 100644 index 0000000..0eb0387 --- /dev/null +++ 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 ( + ( + + {t('SNI')} + + + + + + )} + /> + ) +} diff --git a/dashboard/src/features/hosts/dialogs/mutation/index.tsx b/dashboard/src/features/hosts/dialogs/mutation/index.tsx index 734a2d7..2579b74 100644 --- 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 = ({ inboundId, @@ -56,18 +52,13 @@ export const HostsMutationDialog: FC = ({ const updateMutation = useHostsUpdateMutation(); const createMutation = useHostsCreationMutation(); const { t } = useTranslation(); - const security = form.watch().security - const [extraSecurity, setExtraSecurity] = useState(["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 = ({
- ( - - {t('name')} - - - - - - )} - /> +
- ( - - {t('address')} - - - - - - )} - /> - ( - - {t('port')} - - - - - - )} - /> + +
- ( - - {t('path')} - - - - - - )} - /> - - ( - - {t('host')} - - - - - - )} - /> - - ( - - {t('security')} - - - - )} - /> - {extraSecurity && - <> - ( - - {t('SNI')} - - - - - - )} - /> - - ( - - {t('alpn')} - - - - )} - /> - - ( - - {t('fingerprint')} - - - - )} - /> - - ( - - -
- - - {t('page.hosts.allowinsecure')} - -
-
- - {form.getValues().allowinsecure && - - - - Warning - - - {t('page.hosts.dialogs.mutation.allowinsecure.alert')} - - } -
- )} - /> - - } + + +