From 9b1d7bb234cdc2b566590e7ae40b52467472fb62 Mon Sep 17 00:00:00 2001 From: kunish Date: Thu, 29 Jun 2023 00:00:59 +0800 Subject: [PATCH 01/21] feat(WIP): add a configure node modal --- package.json | 2 + pnpm-lock.yaml | 14 + src/components/DraggableResourceBadge.tsx | 1 + src/components/Header.tsx | 4 +- src/components/NodeModal.tsx | 146 ++++++++++ src/constants/default.ts | 67 +++++ src/constants/index.ts | 310 +--------------------- src/constants/misc.ts | 120 +++++++++ src/constants/schema.ts | 120 +++++++++ src/pages/Experiment.tsx | 8 + src/utils/node.ts | 38 +++ wing | 2 +- 12 files changed, 524 insertions(+), 308 deletions(-) create mode 100644 src/components/NodeModal.tsx create mode 100644 src/constants/default.ts create mode 100644 src/constants/misc.ts create mode 100644 src/constants/schema.ts create mode 100644 src/utils/node.ts diff --git a/package.json b/package.json index a6f8c732..a8f5c798 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "@types/node": "^20.3.1", "@types/react": "^18.2.13", "@types/react-dom": "^18.2.6", + "@types/urijs": "^1.19.19", "@types/uuid": "^9.0.2", "@typescript-eslint/eslint-plugin": "^5.60.0", "@typescript-eslint/parser": "^5.60.0", @@ -89,6 +90,7 @@ "react-router": "^6.13.0", "react-router-dom": "^6.13.0", "typescript": "^5.1.3", + "urijs": "^1.19.11", "vite": "^4.3.9", "zod": "^3.21.4" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8437f57e..09107ba3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -125,6 +125,9 @@ dependencies: '@types/react-dom': specifier: ^18.2.6 version: 18.2.6 + '@types/urijs': + specifier: ^1.19.19 + version: 1.19.19 '@types/uuid': specifier: ^9.0.2 version: 9.0.2 @@ -227,6 +230,9 @@ dependencies: typescript: specifier: ^5.1.3 version: 5.1.3 + urijs: + specifier: ^1.19.11 + version: 1.19.11 vite: specifier: ^4.3.9 version: 4.3.9(@types/node@20.3.1) @@ -3530,6 +3536,10 @@ packages: resolution: {integrity: sha512-5eQEtSCoESnh2FsiLTxE121IiE60hnMqcb435fShf4bpLRjEu1Eoekht23y6zXS9Ts3l+Szu3TARnTsA0GkOkQ==} dev: false + /@types/urijs@1.19.19: + resolution: {integrity: sha512-FDJNkyhmKLw7uEvTxx5tSXfPeQpO0iy73Ry+PmYZJvQy0QIWX8a7kJ4kLWRf+EbTPJEPDSgPXHaM7pzr5lmvCg==} + dev: false + /@types/uuid@9.0.2: resolution: {integrity: sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ==} dev: false @@ -8166,6 +8176,10 @@ packages: punycode: 2.3.0 dev: false + /urijs@1.19.11: + resolution: {integrity: sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==} + dev: false + /urlpattern-polyfill@8.0.2: resolution: {integrity: sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==} dev: false diff --git a/src/components/DraggableResourceBadge.tsx b/src/components/DraggableResourceBadge.tsx index 2abcd43d..0e029d2a 100644 --- a/src/components/DraggableResourceBadge.tsx +++ b/src/components/DraggableResourceBadge.tsx @@ -50,6 +50,7 @@ export const DraggableResourceBadge = ({ } style={{ zIndex: isDragging ? 10 : 0, + cursor: isDragging ? 'grabbing' : 'grab', }} opacity={isDragging ? 0.5 : undefined} > diff --git a/src/components/Header.tsx b/src/components/Header.tsx index edc59cd6..5786ef87 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -258,7 +258,9 @@ export const HeaderWithActions = () => { offLabel={} disabled={!generalQuery?.general.dae.running && runMutation.isLoading} checked={generalQuery?.general.dae.running} - onChange={(e) => runMutation.mutateAsync(!e.target.checked)} + onChange={(e) => { + runMutation.mutateAsync(!e.target.checked) + }} /> diff --git a/src/components/NodeModal.tsx b/src/components/NodeModal.tsx new file mode 100644 index 00000000..562bf1ab --- /dev/null +++ b/src/components/NodeModal.tsx @@ -0,0 +1,146 @@ +import { Checkbox, Modal, NumberInput, Select, Stack, TextInput } from '@mantine/core' +import { useForm } from '@mantine/form' + +import { FormActions } from './FormActions' + +export const NodeModal = ({ opened, onClose }: { opened: boolean; onClose: () => void }) => { + const { values, onSubmit, getInputProps, reset } = useForm({ + initialValues: { + protocol: 'vmess', + type: 'none', + tls: 'none', + net: 'tcp', + }, + }) + + return ( + +
{ + console.log(values) + })} + > + + + )} + + {values.type !== 'dtls' && ( + + )} + + {values.tls !== 'none' && ( + + )} + + + )} + + {values.net === 'kcp' && ( + + + + + + + + + + + {values.protocol === 'vmess' && } + + {values.protocol === 'vmess' && ( + + )} + + {values.tls !== 'none' && } + + {values.tls === 'xtls' && ( + + + {values.net === 'tcp' && ( + + )} + + {(values.net === 'ws' || + values.net === 'h2' || + values.tls === 'tls' || + (values.net === 'tcp' && values.type === 'http')) && } + + {values.tls === 'tls' && } + + {values.net === 'ws' || + values.net === 'h2' || + (values.net === 'tcp' && values.type === 'http' && )} + + {values.net === 'mkcp' || (values.net === 'kcp' && )} + + {values.net === 'grpc' && } + + + + ) +} + +export const NodeModal = ({ opened, onClose }: { opened: boolean; onClose: () => void }) => { return ( -
{ - console.log(values) - })} + - - - )} - - {values.type !== 'dtls' && ( - - )} - - {values.tls !== 'none' && ( - - )} - - - )} - - {values.net === 'kcp' && ( - + + + + + + + + + + {values.protocol === 'vmess' && } + + {values.protocol === 'vmess' && ( + + )} + + {values.tls !== 'none' && } + + {values.tls === 'xtls' && ( + + + {values.net === 'tcp' && ( + + )} + + {(values.net === 'ws' || + values.net === 'h2' || + values.tls === 'tls' || + (values.net === 'tcp' && values.type === 'http')) && } + + {values.tls === 'tls' && } + + {values.net === 'ws' || + values.net === 'h2' || + (values.net === 'tcp' && values.type === 'http' && )} + + {values.net === 'mkcp' || (values.net === 'kcp' && )} + + {values.net === 'grpc' && } + + + + ) +} + +const SSForm = () => { + const { values, onSubmit, getInputProps, reset } = useForm({ + initialValues: { + plugin: '', + obfs: '', + }, + }) + + return ( +
{ + console.log(values) + })} + > + + + + + + + + + + + {values.plugin === 'simple-obfs' || + (values.plugin === 'v2ray-plugin' && ( + + )} + + {values.plugin === 'v2ray-plugin' && ( + + )} + + {((values.plugin === 'simple-obfs' && (values.obfs === 'http' || values.obfs === 'tls')) || + values.plugin === 'v2ray-plugin') && } + + {(values.plugin === 'simple-obfs' && values.obfs === 'http') || + (values.plugin === 'v2ray-plugin' && )} + + + + ) +} + +const SSRForm = () => { + const { values, onSubmit, getInputProps, reset } = useForm({ + initialValues: { + protocol: 'vmess', + type: 'none', + tls: 'none', + net: 'tcp', + }, + }) + + return ( +
{ + console.log(values) + })} + > + + )} + + {values.type !== 'dtls' && ( + + )} + + {values.tls !== 'none' && ( + + )} + + + )} + + {values.net === 'kcp' && ( + + + + + + + + + + + {values.protocol === 'vmess' && } + + {values.protocol === 'vmess' && ( + + )} + + {values.tls !== 'none' && } + + {values.tls === 'xtls' && ( + + + {values.net === 'tcp' && ( + + )} + + {(values.net === 'ws' || + values.net === 'h2' || + values.tls === 'tls' || + (values.net === 'tcp' && values.type === 'http')) && } + + {values.tls === 'tls' && } + + {values.net === 'ws' || + values.net === 'h2' || + (values.net === 'tcp' && values.type === 'http' && )} + + {values.net === 'mkcp' || (values.net === 'kcp' && )} + + {values.net === 'grpc' && } + + + + ) +} + +const HTTPForm = () => { + const { values, onSubmit, getInputProps, reset } = useForm({ + initialValues: { + protocol: 'vmess', + type: 'none', + tls: 'none', + net: 'tcp', + }, + }) + + return ( +
{ + console.log(values) + })} + > + + )} + + {values.type !== 'dtls' && ( + + )} + + {values.tls !== 'none' && ( + + )} + + + )} + + {values.net === 'kcp' && ( + - - + - + - - - {values.protocol === 'vmess' && } - - {values.protocol === 'vmess' && ( - - )} - - {values.tls !== 'none' && } - - {values.tls === 'xtls' && ( - - {values.net === 'tcp' && ( - - )} - - {(values.net === 'ws' || - values.net === 'h2' || - values.tls === 'tls' || - (values.net === 'tcp' && values.type === 'http')) && } - - {values.tls === 'tls' && } + - {values.net === 'grpc' && } + {values.obfs !== 'plain' && } From fc5f134ccce7394d8649c11384bd454ab9f2ae9b Mon Sep 17 00:00:00 2001 From: kunish Date: Thu, 29 Jun 2023 01:20:28 +0800 Subject: [PATCH 05/21] feat(WIP): update trojan form tab --- src/components/NodeModal.tsx | 130 +++++++++-------------------------- 1 file changed, 34 insertions(+), 96 deletions(-) diff --git a/src/components/NodeModal.tsx b/src/components/NodeModal.tsx index 350395a3..7e838994 100644 --- a/src/components/NodeModal.tsx +++ b/src/components/NodeModal.tsx @@ -334,10 +334,8 @@ const SSRForm = () => { const TrojanForm = () => { const { values, onSubmit, getInputProps, reset } = useForm({ initialValues: { - protocol: 'vmess', - type: 'none', - tls: 'none', - net: 'tcp', + method: 'origin', + obfs: 'none', }, }) @@ -347,122 +345,62 @@ const TrojanForm = () => { console.log(values) })} > - - {values.protocol === 'vmess' && ( + {values.method === 'shadowsocks' && ( + {values.method === 'shadowsocks' && ( + )} - {values.tls !== 'none' && } - - {values.tls === 'xtls' && ( - - {values.net === 'tcp' && ( - - )} - - {(values.net === 'ws' || - values.net === 'h2' || - values.tls === 'tls' || - (values.net === 'tcp' && values.type === 'http')) && } - - {values.tls === 'tls' && } - - {values.net === 'ws' || - values.net === 'h2' || - (values.net === 'tcp' && values.type === 'http' && )} - - {values.net === 'mkcp' || (values.net === 'kcp' && )} - - {values.net === 'grpc' && } + {values.obfs === 'websocket' && } From b9e8405e6a8e88dd69e718761d32694bc4169283 Mon Sep 17 00:00:00 2001 From: kunish Date: Thu, 29 Jun 2023 01:24:23 +0800 Subject: [PATCH 06/21] feat(WIP): update http form tab --- src/components/NodeModal.tsx | 117 +++-------------------------------- 1 file changed, 8 insertions(+), 109 deletions(-) diff --git a/src/components/NodeModal.tsx b/src/components/NodeModal.tsx index 7e838994..13a9ffcc 100644 --- a/src/components/NodeModal.tsx +++ b/src/components/NodeModal.tsx @@ -410,10 +410,7 @@ const TrojanForm = () => { const HTTPForm = () => { const { values, onSubmit, getInputProps, reset } = useForm({ initialValues: { - protocol: 'vmess', - type: 'none', - tls: 'none', - net: 'tcp', + protocol: 'http', }, }) @@ -424,121 +421,23 @@ const HTTPForm = () => { })} > - )} - - {values.type !== 'dtls' && ( - - )} + - {values.tls !== 'none' && ( - - )} - - - )} - - {values.net === 'kcp' && ( - - - + - + - - - {values.protocol === 'vmess' && } - - {values.protocol === 'vmess' && ( - - )} - - {values.tls !== 'none' && } - - {values.tls === 'xtls' && ( - - - {values.net === 'tcp' && ( - - )} - - {(values.net === 'ws' || - values.net === 'h2' || - values.tls === 'tls' || - (values.net === 'tcp' && values.type === 'http')) && } - - {values.tls === 'tls' && } - - {values.net === 'ws' || - values.net === 'h2' || - (values.net === 'tcp' && values.type === 'http' && )} - - {values.net === 'mkcp' || (values.net === 'kcp' && )} + - {values.net === 'grpc' && } + From fcaa963696045b71809ee11b7adb0e2f2fd52b55 Mon Sep 17 00:00:00 2001 From: kunish Date: Thu, 29 Jun 2023 21:18:31 +0800 Subject: [PATCH 08/21] feat: finish vless & vmess protocols --- package.json | 1 + pnpm-lock.yaml | 7 +++ src/components/NodeModal.tsx | 110 ++++++++++++++++++++++++++++------- src/constants/default.ts | 22 +++++++ src/constants/schema.ts | 31 +++++----- src/utils/node.ts | 8 +-- 6 files changed, 138 insertions(+), 41 deletions(-) diff --git a/package.json b/package.json index a8f5c798..12200e83 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "i18next": "^23.2.3", "i18next-browser-languagedetector": "^7.0.2", "immer": "^10.0.2", + "js-base64": "^3.7.5", "lint-staged": "^13.2.2", "mantine-datatable": "^2.6.4", "monaco-editor": "^0.39.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 09107ba3..73c6fcde 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -191,6 +191,9 @@ dependencies: immer: specifier: ^10.0.2 version: 10.0.2 + js-base64: + specifier: ^3.7.5 + version: 3.7.5 lint-staged: specifier: ^13.2.2 version: 13.2.2 @@ -6076,6 +6079,10 @@ packages: resolution: {integrity: sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==} dev: false + /js-base64@3.7.5: + resolution: {integrity: sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==} + dev: false + /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} dev: false diff --git a/src/components/NodeModal.tsx b/src/components/NodeModal.tsx index 03f7b8e4..13650b6a 100644 --- a/src/components/NodeModal.tsx +++ b/src/components/NodeModal.tsx @@ -1,23 +1,92 @@ import { Checkbox, MantineProvider, Modal, NumberInput, Select, Stack, Tabs, TextInput } from '@mantine/core' -import { useForm } from '@mantine/form' +import { useForm, zodResolver } from '@mantine/form' +import { Base64 } from 'js-base64' +import { z } from 'zod' + +import { DEFAULT_V2RAY_VALUES, v2raySchema } from '~/constants' +import { generateURL } from '~/utils/node' import { FormActions } from './FormActions' const V2rayForm = () => { - const { values, onSubmit, getInputProps, reset } = useForm({ + const { values, onSubmit, getInputProps, reset } = useForm< + z.infer & { + protocol: 'vless' | 'vmess' + } + >({ initialValues: { protocol: 'vmess', - type: 'none', - tls: 'none', - net: 'tcp', - scy: 'auto', + ...DEFAULT_V2RAY_VALUES, }, + validate: zodResolver(v2raySchema), }) return (
{ - console.log(values) + const { protocol, net, tls, path, host, type, sni, flow, allowInsecure, alpn, id, add, port, ps } = values + + if (protocol === 'vless') { + const params: Record = { + type: net, + security: tls, + path, + host, + headerType: type, + sni, + flow, + allowInsecure, + } + + if (alpn !== '') { + params.alpn = alpn + } + + if (net === 'grpc') { + params.serviceName = path + } + + if (net === 'kcp') { + params.seed = path + } + + return generateURL({ + protocol, + username: id, + host: add, + port, + hash: ps, + params, + }) + } + + if (protocol === 'vmess') { + const body: Record = structuredClone(values) + + switch (net) { + case 'kcp': + case 'tcp': + default: + body.type = '' + } + switch (body.net) { + case 'ws': + case 'h2': + case 'grpc': + case 'kcp': + default: + if (body.net === 'tcp' && body.type === 'http') { + break + } + body.path = '' + } + + if (!(body.protocol === 'vless' && body.tls === 'xtls')) { + delete body.flow + } + + return 'vmess://' + Base64.encode(JSON.stringify(body)) + } })} > - )} + { } const SSForm = () => { - const { values, onSubmit, getInputProps, reset } = useForm({ - initialValues: { - plugin: '', - method: 'aes-128-gcm', - obfs: '', - }, + const { values, onSubmit, getInputProps, reset } = useForm>({ + initialValues: DEFAULT_SS_FORM_VALUES, + validate: zodResolver(ssSchema), + }) + + const handleSubmit = onSubmit((values) => { + /* ss://BASE64(method:password)@server:port#name */ + let link = `ss://${Base64.encode(`${values.method}:${values.password}`)}@${values.server}:${values.port}/` + + if (values.plugin) { + const plugin: string[] = [values.plugin] + + if (values.plugin === 'v2ray-plugin') { + if (values.tls) { + plugin.push('tls') + } + + if (values.mode !== 'websocket') { + plugin.push('mode=' + values.mode) + } + + if (values.host) { + plugin.push('host=' + values.host) + } + + if (values.path) { + if (!values.path.startsWith('/')) { + values.path = '/' + values.path + } + + plugin.push('path=' + values.path) + } + + if (values.impl) { + plugin.push('impl=' + values.impl) + } + } else { + plugin.push('obfs=' + values.obfs) + plugin.push('obfs-host=' + values.host) + + if (values.obfs === 'http') { + plugin.push('obfs-path=' + values.path) + } + + if (values.impl) { + plugin.push('impl=' + values.impl) + } + } + + link += `?plugin=${encodeURIComponent(plugin.join(';'))}` + } + + link += values.name.length ? `#${encodeURIComponent(values.name)}` : '' + + return link }) return ( - { - console.log(values) - })} - > + diff --git a/src/constants/default.ts b/src/constants/default.ts index ee23f723..bc0cc97a 100644 --- a/src/constants/default.ts +++ b/src/constants/default.ts @@ -3,7 +3,7 @@ import { z } from 'zod' import { GlobalInput, Policy } from '~/schemas/gql/graphql' import { DialMode, LogLevel, TLSImplementation, TcpCheckHttpMethod, UTLSImitate } from './misc' -import { v2raySchema } from './schema' +import { ssSchema, v2raySchema } from './schema' export const DEFAULT_ENDPOINT_URL = `${location.protocol}//${location.hostname}:2023/graphql` @@ -69,7 +69,7 @@ routing { } `.trim() -export const DEFAULT_V2RAY_VALUES: z.infer = { +export const DEFAULT_V2RAY_FORM_VALUES: z.infer = { type: 'none', tls: 'none', net: 'tcp', @@ -87,3 +87,18 @@ export const DEFAULT_V2RAY_VALUES: z.infer = { v: '', sni: '', } + +export const DEFAULT_SS_FORM_VALUES: z.infer = { + plugin: '', + method: 'aes-128-gcm', + obfs: 'http', + host: '', + impl: '', + mode: '', + name: '', + password: '', + path: '', + port: 0, + server: '', + tls: '', +} diff --git a/src/constants/schema.ts b/src/constants/schema.ts index 9dc14a11..96b1d995 100644 --- a/src/constants/schema.ts +++ b/src/constants/schema.ts @@ -20,20 +20,18 @@ export const v2raySchema = z.object({ }) export const ssSchema = z.object({ - method: z - .enum(['aes-128-gcm', 'aes-256-gcm', 'chacha20-poly1305', 'chacha20-ietf-poly1305', 'plain', 'none']) - .default('aes-128-gcm'), - plugin: z.enum(['', 'simple-obfs', 'v2ray-plugin']).default(''), - obfs: z.enum(['http', 'tls']).default('http'), - tls: z.enum(['', 'tls']).default(''), - path: z.string().default('/'), - mode: z.string().default('websocket'), - host: z.string().url().default(''), + method: z.enum(['aes-128-gcm', 'aes-256-gcm', 'chacha20-poly1305', 'chacha20-ietf-poly1305', 'plain', 'none']), + plugin: z.enum(['', 'simple-obfs', 'v2ray-plugin']), + obfs: z.enum(['http', 'tls']), + tls: z.enum(['', 'tls']), + path: z.string(), + mode: z.string(), + host: z.string(), password: z.string().nonempty(), - server: z.string().url().nonempty().or(z.string().ip().nonempty()), + server: z.string().nonempty(), port: z.number().min(0).max(65535), - name: z.string().default(''), - impl: z.enum(['', 'chained', 'transport']).default(''), + name: z.string(), + impl: z.enum(['', 'chained', 'transport']), }) export const ssrSchema = z.object({ diff --git a/src/pages/Experiment.tsx b/src/pages/Experiment.tsx index 47fe90a9..3e7ea145 100644 --- a/src/pages/Experiment.tsx +++ b/src/pages/Experiment.tsx @@ -251,6 +251,7 @@ export const ExperimentPage = () => { oldName: config.name, }) } + openRenameModal() }} > @@ -283,6 +284,7 @@ export const ExperimentPage = () => { oldName: dns.name, }) } + openRenameModal() }} > @@ -315,6 +317,7 @@ export const ExperimentPage = () => { oldName: routing.name, }) } + openRenameModal() }} > diff --git a/src/pages/Orchestrate/Config.tsx b/src/pages/Orchestrate/Config.tsx index 116d5603..104a6099 100644 --- a/src/pages/Orchestrate/Config.tsx +++ b/src/pages/Orchestrate/Config.tsx @@ -51,6 +51,7 @@ export const Config = () => { oldName: config.name, }) } + openRenameFormModal() }} > diff --git a/src/pages/Orchestrate/DNS.tsx b/src/pages/Orchestrate/DNS.tsx index 2db7fd28..c3ad33cd 100644 --- a/src/pages/Orchestrate/DNS.tsx +++ b/src/pages/Orchestrate/DNS.tsx @@ -56,6 +56,7 @@ export const DNS = () => { oldName: dns.name, }) } + openRenameFormModal() }} > diff --git a/src/pages/Orchestrate/Group.tsx b/src/pages/Orchestrate/Group.tsx index ed08a438..214d7c25 100644 --- a/src/pages/Orchestrate/Group.tsx +++ b/src/pages/Orchestrate/Group.tsx @@ -58,6 +58,7 @@ export const GroupResource = ({ highlight }: { highlight?: boolean }) => { oldName: name, }) } + openRenameFormModal() }} > diff --git a/src/pages/Orchestrate/Routing.tsx b/src/pages/Orchestrate/Routing.tsx index 7591ecbd..ba05def1 100644 --- a/src/pages/Orchestrate/Routing.tsx +++ b/src/pages/Orchestrate/Routing.tsx @@ -55,6 +55,7 @@ export const Routing = () => { oldName: routing.name, }) } + openRenameFormModal() }} > From 107ac2085e2ffdad1cc885155bab9c4ea5568834 Mon Sep 17 00:00:00 2001 From: kunish Date: Thu, 29 Jun 2023 21:35:49 +0800 Subject: [PATCH 10/21] feat: finish ssr protocol --- src/components/NodeModal.tsx | 35 +++++++++++++++++++++++------------ src/constants/default.ts | 14 +++++++++++++- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/components/NodeModal.tsx b/src/components/NodeModal.tsx index 0b93df26..569158a3 100644 --- a/src/components/NodeModal.tsx +++ b/src/components/NodeModal.tsx @@ -3,7 +3,14 @@ import { useForm, zodResolver } from '@mantine/form' import { Base64 } from 'js-base64' import { z } from 'zod' -import { DEFAULT_SS_FORM_VALUES, DEFAULT_V2RAY_FORM_VALUES, ssSchema, v2raySchema } from '~/constants' +import { + DEFAULT_SSR_FORM_VALUES, + DEFAULT_SS_FORM_VALUES, + DEFAULT_V2RAY_FORM_VALUES, + ssSchema, + ssrSchema, + v2raySchema, +} from '~/constants' import { generateURL } from '~/utils/node' import { FormActions } from './FormActions' @@ -344,20 +351,24 @@ const SSForm = () => { } const SSRForm = () => { - const { values, onSubmit, getInputProps, reset } = useForm({ - initialValues: { - method: 'aes-128-cfb', - proto: 'origin', - obfs: 'plain', - }, + const { values, onSubmit, getInputProps, reset } = useForm>({ + initialValues: DEFAULT_SSR_FORM_VALUES, + validate: zodResolver(ssrSchema), + }) + + const handleSubmit = onSubmit((values) => { + /* ssr://server:port:proto:method:obfs:URLBASE64(password)/?remarks=URLBASE64(remarks)&protoparam=URLBASE64(protoparam)&obfsparam=URLBASE64(obfsparam)) */ + return `ssr://${Base64.encode( + `${values.server}:${values.port}:${values.proto}:${values.method}:${values.obfs}:${Base64.encodeURI( + values.password + )}/?remarks=${Base64.encodeURI(values.name)}&protoparam=${Base64.encodeURI( + values.protoParam + )}&obfsparam=${Base64.encodeURI(values.obfsParam)}` + )}` }) return ( - { - console.log(values) - })} - > + diff --git a/src/constants/default.ts b/src/constants/default.ts index bc0cc97a..4533d38e 100644 --- a/src/constants/default.ts +++ b/src/constants/default.ts @@ -3,7 +3,7 @@ import { z } from 'zod' import { GlobalInput, Policy } from '~/schemas/gql/graphql' import { DialMode, LogLevel, TLSImplementation, TcpCheckHttpMethod, UTLSImitate } from './misc' -import { ssSchema, v2raySchema } from './schema' +import { ssSchema, ssrSchema, v2raySchema } from './schema' export const DEFAULT_ENDPOINT_URL = `${location.protocol}//${location.hostname}:2023/graphql` @@ -102,3 +102,15 @@ export const DEFAULT_SS_FORM_VALUES: z.infer = { server: '', tls: '', } + +export const DEFAULT_SSR_FORM_VALUES: z.infer = { + method: 'aes-128-cfb', + proto: 'origin', + obfs: 'plain', + name: '', + obfsParam: '', + password: '', + port: 0, + protoParam: '', + server: '', +} From e6ef5a9e591486a7f740b311814e325936f917ff Mon Sep 17 00:00:00 2001 From: kunish Date: Thu, 29 Jun 2023 21:42:03 +0800 Subject: [PATCH 11/21] feat: finish trojan protocol --- .eslintrc.yml | 3 ++ src/components/NodeModal.tsx | 53 +++++++++++++++++++++++++++++------- src/constants/default.ts | 17 +++++++++++- 3 files changed, 62 insertions(+), 11 deletions(-) diff --git a/.eslintrc.yml b/.eslintrc.yml index 340a7254..05b23a82 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -43,6 +43,9 @@ rules: - blankLine: always prev: '*' next: switch + - blankLine: always + prev: switch + next: '*' react/prop-types: off react/display-name: off react/self-closing-comp: error diff --git a/src/components/NodeModal.tsx b/src/components/NodeModal.tsx index 569158a3..f699ea5d 100644 --- a/src/components/NodeModal.tsx +++ b/src/components/NodeModal.tsx @@ -6,9 +6,11 @@ import { z } from 'zod' import { DEFAULT_SSR_FORM_VALUES, DEFAULT_SS_FORM_VALUES, + DEFAULT_TROJAN_FORM_VALUES, DEFAULT_V2RAY_FORM_VALUES, ssSchema, ssrSchema, + trojanSchema, v2raySchema, } from '~/constants' import { generateURL } from '~/utils/node' @@ -446,19 +448,50 @@ const SSRForm = () => { } const TrojanForm = () => { - const { values, onSubmit, getInputProps, reset } = useForm({ - initialValues: { - method: 'origin', - obfs: 'none', - }, + const { values, onSubmit, getInputProps, reset } = useForm>({ + initialValues: DEFAULT_TROJAN_FORM_VALUES, + validate: zodResolver(trojanSchema), + }) + + const handleSubmit = onSubmit((values) => { + const query: Record = { + allowInsecure: values.allowInsecure, + } + + if (values.peer !== '') { + query.sni = values.peer + } + + let protocol = 'trojan' + + if (values.method !== 'origin' || values.obfs !== 'none') { + protocol = 'trojan-go' + query.type = values.obfs === 'none' ? 'original' : 'ws' + + if (values.method === 'shadowsocks') { + query.encryption = `ss;${values.ssCipher};${values.ssPassword}` + } + + if (query.type === 'ws') { + query.host = values.host || '' + query.path = values.path || '/' + } + + delete query.allowInsecure + } + + return generateURL({ + protocol: protocol, + username: values.password, + host: values.server, + port: values.port, + hash: values.name, + params: query, + }) }) return ( - { - console.log(values) - })} - > + diff --git a/src/constants/default.ts b/src/constants/default.ts index 4533d38e..79da9af5 100644 --- a/src/constants/default.ts +++ b/src/constants/default.ts @@ -3,7 +3,7 @@ import { z } from 'zod' import { GlobalInput, Policy } from '~/schemas/gql/graphql' import { DialMode, LogLevel, TLSImplementation, TcpCheckHttpMethod, UTLSImitate } from './misc' -import { ssSchema, ssrSchema, v2raySchema } from './schema' +import { ssSchema, ssrSchema, trojanSchema, v2raySchema } from './schema' export const DEFAULT_ENDPOINT_URL = `${location.protocol}//${location.hostname}:2023/graphql` @@ -114,3 +114,18 @@ export const DEFAULT_SSR_FORM_VALUES: z.infer = { protoParam: '', server: '', } + +export const DEFAULT_TROJAN_FORM_VALUES: z.infer = { + method: 'origin', + obfs: 'none', + allowInsecure: false, + host: '', + name: '', + password: '', + path: '', + peer: '', + port: 0, + server: '', + ssCipher: 'aes-128-gcm', + ssPassword: '', +} From 2ef8707cfe7e95e5ce70f8691638c86b94eeac1c Mon Sep 17 00:00:00 2001 From: kunish Date: Thu, 29 Jun 2023 21:55:21 +0800 Subject: [PATCH 12/21] feat: finish http protocol --- src/components/NodeModal.tsx | 35 +++++++++++++++++++++++++---------- src/constants/default.ts | 11 ++++++++++- src/utils/node.ts | 19 ++++++------------- 3 files changed, 41 insertions(+), 24 deletions(-) diff --git a/src/components/NodeModal.tsx b/src/components/NodeModal.tsx index f699ea5d..48e6e7fb 100644 --- a/src/components/NodeModal.tsx +++ b/src/components/NodeModal.tsx @@ -4,16 +4,18 @@ import { Base64 } from 'js-base64' import { z } from 'zod' import { + DEFAULT_HTTP_FORM_VALUES, DEFAULT_SSR_FORM_VALUES, DEFAULT_SS_FORM_VALUES, DEFAULT_TROJAN_FORM_VALUES, DEFAULT_V2RAY_FORM_VALUES, + httpSchema, ssSchema, ssrSchema, trojanSchema, v2raySchema, } from '~/constants' -import { generateURL } from '~/utils/node' +import { GenerateURLParams, generateURL } from '~/utils/node' import { FormActions } from './FormActions' @@ -555,18 +557,31 @@ const TrojanForm = () => { } const HTTPForm = () => { - const { onSubmit, getInputProps, reset } = useForm({ - initialValues: { - protocol: 'http', - }, + const { onSubmit, getInputProps, reset } = useForm>({ + initialValues: DEFAULT_HTTP_FORM_VALUES, + validate: zodResolver(httpSchema), + }) + + const handleSubmit = onSubmit((values) => { + const generateURLParams: GenerateURLParams = { + protocol: `${values.protocol}-proxy`, + host: values.host, + port: values.port, + hash: values.name, + } + + if (values.username && values.password) { + Object.assign(generateURLParams, { + username: values.username, + password: values.password, + }) + } + + return generateURL(generateURLParams) }) return ( - { - console.log(values) - })} - > + - - - - - - - - - - {values.protocol === 'vmess' && } - - {values.protocol === 'vmess' && ( - - )} - - {values.tls !== 'none' && } - - - - {values.net === 'tcp' && ( - - )} - - {(values.net === 'ws' || - values.net === 'h2' || - values.tls === 'tls' || - (values.net === 'tcp' && values.type === 'http')) && } - - {values.tls === 'tls' && } - - {values.net === 'ws' || - values.net === 'h2' || - (values.net === 'tcp' && values.type === 'http' && )} - - {values.net === 'kcp' && } - - {values.net === 'grpc' && } - - - - ) -} - -const SSForm = () => { - const { values, onSubmit, getInputProps, reset } = useForm>({ - initialValues: DEFAULT_SS_FORM_VALUES, - validate: zodResolver(ssSchema), - }) - - const handleSubmit = onSubmit((values) => { - /* ss://BASE64(method:password)@server:port#name */ - let link = `ss://${Base64.encode(`${values.method}:${values.password}`)}@${values.server}:${values.port}/` - - if (values.plugin) { - const plugin: string[] = [values.plugin] - - if (values.plugin === 'v2ray-plugin') { - if (values.tls) { - plugin.push('tls') - } - - if (values.mode !== 'websocket') { - plugin.push('mode=' + values.mode) - } - - if (values.host) { - plugin.push('host=' + values.host) - } - - if (values.path) { - if (!values.path.startsWith('/')) { - values.path = '/' + values.path - } - - plugin.push('path=' + values.path) - } - - if (values.impl) { - plugin.push('impl=' + values.impl) - } - } else { - plugin.push('obfs=' + values.obfs) - plugin.push('obfs-host=' + values.host) - - if (values.obfs === 'http') { - plugin.push('obfs-path=' + values.path) - } - - if (values.impl) { - plugin.push('impl=' + values.impl) - } - } - - link += `?plugin=${encodeURIComponent(plugin.join(';'))}` - } - - link += values.name.length ? `#${encodeURIComponent(values.name)}` : '' - - return link - }) - - return ( -
- - - - - - - - - - - {values.plugin === 'simple-obfs' || - (values.plugin === 'v2ray-plugin' && ( - - )} - - {values.plugin === 'v2ray-plugin' && ( - - )} - - {((values.plugin === 'simple-obfs' && (values.obfs === 'http' || values.obfs === 'tls')) || - values.plugin === 'v2ray-plugin') && } - - {(values.plugin === 'simple-obfs' && values.obfs === 'http') || - (values.plugin === 'v2ray-plugin' && )} - - - - ) -} - -const SSRForm = () => { - const { values, onSubmit, getInputProps, reset } = useForm>({ - initialValues: DEFAULT_SSR_FORM_VALUES, - validate: zodResolver(ssrSchema), - }) - - const handleSubmit = onSubmit((values) => { - /* ssr://server:port:proto:method:obfs:URLBASE64(password)/?remarks=URLBASE64(remarks)&protoparam=URLBASE64(protoparam)&obfsparam=URLBASE64(obfsparam)) */ - return `ssr://${Base64.encode( - `${values.server}:${values.port}:${values.proto}:${values.method}:${values.obfs}:${Base64.encodeURI( - values.password - )}/?remarks=${Base64.encodeURI(values.name)}&protoparam=${Base64.encodeURI( - values.protoParam - )}&obfsparam=${Base64.encodeURI(values.obfsParam)}` - )}` - }) - - return ( -
- - - - - - - - - - - {values.proto !== 'origin' && } - - - - {values.method === 'shadowsocks' && ( - - - {values.obfs === 'websocket' && } - - {values.obfs === 'websocket' && } - - - - ) -} - -const HTTPForm = () => { - const { onSubmit, getInputProps, reset } = useForm & { protocol: 'http' | 'https' }>({ - initialValues: { - protocol: 'http', - ...DEFAULT_HTTP_FORM_VALUES, - }, - validate: zodResolver(httpSchema), - }) - - const handleSubmit = onSubmit((values) => { - const generateURLParams: GenerateURLParams = { - protocol: `${values.protocol}-proxy`, - host: values.host, - port: values.port, - hash: values.name, - } - - if (values.username && values.password) { - Object.assign(generateURLParams, { - username: values.username, - password: values.password, - }) - } - - return generateURL(generateURLParams) - }) - - return ( -
- + + + + + + + + + + + + + + ) +} diff --git a/src/components/NodeModal/SSForm.tsx b/src/components/NodeModal/SSForm.tsx new file mode 100644 index 00000000..c7d5d3c6 --- /dev/null +++ b/src/components/NodeModal/SSForm.tsx @@ -0,0 +1,149 @@ +import { NumberInput, Select, TextInput } from '@mantine/core' +import { useForm, zodResolver } from '@mantine/form' +import { Base64 } from 'js-base64' +import { z } from 'zod' + +import { FormActions } from '~/components/FormActions' +import { DEFAULT_SS_FORM_VALUES, ssSchema } from '~/constants' + +export const SSForm = () => { + const { values, onSubmit, getInputProps, reset } = useForm>({ + initialValues: DEFAULT_SS_FORM_VALUES, + validate: zodResolver(ssSchema), + }) + + const handleSubmit = onSubmit((values) => { + /* ss://BASE64(method:password)@server:port#name */ + let link = `ss://${Base64.encode(`${values.method}:${values.password}`)}@${values.server}:${values.port}/` + + if (values.plugin) { + const plugin: string[] = [values.plugin] + + if (values.plugin === 'v2ray-plugin') { + if (values.tls) { + plugin.push('tls') + } + + if (values.mode !== 'websocket') { + plugin.push('mode=' + values.mode) + } + + if (values.host) { + plugin.push('host=' + values.host) + } + + if (values.path) { + if (!values.path.startsWith('/')) { + values.path = '/' + values.path + } + + plugin.push('path=' + values.path) + } + + if (values.impl) { + plugin.push('impl=' + values.impl) + } + } else { + plugin.push('obfs=' + values.obfs) + plugin.push('obfs-host=' + values.host) + + if (values.obfs === 'http') { + plugin.push('obfs-path=' + values.path) + } + + if (values.impl) { + plugin.push('impl=' + values.impl) + } + } + + link += `?plugin=${encodeURIComponent(plugin.join(';'))}` + } + + link += values.name.length ? `#${encodeURIComponent(values.name)}` : '' + + return link + }) + + return ( +
+ + + + + + + + + + + {values.plugin === 'simple-obfs' || + (values.plugin === 'v2ray-plugin' && ( + + )} + + {values.plugin === 'v2ray-plugin' && ( + + )} + + {((values.plugin === 'simple-obfs' && (values.obfs === 'http' || values.obfs === 'tls')) || + values.plugin === 'v2ray-plugin') && } + + {(values.plugin === 'simple-obfs' && values.obfs === 'http') || + (values.plugin === 'v2ray-plugin' && )} + + + + ) +} diff --git a/src/components/NodeModal/SSRForm.tsx b/src/components/NodeModal/SSRForm.tsx new file mode 100644 index 00000000..5387c248 --- /dev/null +++ b/src/components/NodeModal/SSRForm.tsx @@ -0,0 +1,102 @@ +import { NumberInput, Select, TextInput } from '@mantine/core' +import { useForm, zodResolver } from '@mantine/form' +import { Base64 } from 'js-base64' +import { z } from 'zod' + +import { FormActions } from '~/components/FormActions' +import { DEFAULT_SSR_FORM_VALUES, ssrSchema } from '~/constants' + +export const SSRForm = () => { + const { values, onSubmit, getInputProps, reset } = useForm>({ + initialValues: DEFAULT_SSR_FORM_VALUES, + validate: zodResolver(ssrSchema), + }) + + const handleSubmit = onSubmit((values) => { + /* ssr://server:port:proto:method:obfs:URLBASE64(password)/?remarks=URLBASE64(remarks)&protoparam=URLBASE64(protoparam)&obfsparam=URLBASE64(obfsparam)) */ + return `ssr://${Base64.encode( + `${values.server}:${values.port}:${values.proto}:${values.method}:${values.obfs}:${Base64.encodeURI( + values.password + )}/?remarks=${Base64.encodeURI(values.name)}&protoparam=${Base64.encodeURI( + values.protoParam + )}&obfsparam=${Base64.encodeURI(values.obfsParam)}` + )}` + }) + + return ( +
+ + + + + + + + + + + {values.proto !== 'origin' && } + + + + {values.method === 'shadowsocks' && ( + + + {values.obfs === 'websocket' && } + + {values.obfs === 'websocket' && } + + + + ) +} diff --git a/src/components/NodeModal/V2rayForm.tsx b/src/components/NodeModal/V2rayForm.tsx new file mode 100644 index 00000000..d5406f36 --- /dev/null +++ b/src/components/NodeModal/V2rayForm.tsx @@ -0,0 +1,201 @@ +import { Checkbox, NumberInput, Select, TextInput } from '@mantine/core' +import { useForm, zodResolver } from '@mantine/form' +import { Base64 } from 'js-base64' +import { z } from 'zod' + +import { FormActions } from '~/components/FormActions' +import { DEFAULT_V2RAY_FORM_VALUES, v2raySchema } from '~/constants' +import { generateURL } from '~/utils' + +export const V2rayForm = () => { + const { values, onSubmit, getInputProps, reset } = useForm< + z.infer & { protocol: 'vless' | 'vmess' } + >({ + initialValues: { protocol: 'vmess', ...DEFAULT_V2RAY_FORM_VALUES }, + validate: zodResolver(v2raySchema), + }) + + const handleSubmit = onSubmit((values) => { + const { protocol, net, tls, path, host, type, sni, flow, allowInsecure, alpn, id, add, port, ps } = values + + if (protocol === 'vless') { + const params: Record = { + type: net, + security: tls, + path, + host, + headerType: type, + sni, + flow, + allowInsecure, + } + + if (alpn !== '') params.alpn = alpn + + if (net === 'grpc') params.serviceName = path + + if (net === 'kcp') params.seed = path + + return generateURL({ + protocol, + username: id, + host: add, + port, + hash: ps, + params, + }) + } + + if (protocol === 'vmess') { + const body: Record = structuredClone(values) + + switch (net) { + case 'kcp': + case 'tcp': + default: + body.type = '' + } + + switch (body.net) { + case 'ws': + case 'h2': + case 'grpc': + case 'kcp': + default: + if (body.net === 'tcp' && body.type === 'http') { + break + } + + body.path = '' + } + + if (!(body.protocol === 'vless' && body.tls === 'xtls')) { + delete body.flow + } + + return 'vmess://' + Base64.encode(JSON.stringify(body)) + } + }) + + return ( +
+ + )} + + {values.type !== 'dtls' && ( + + + {values.tls !== 'none' && ( + + )} + + + )} + + {values.net === 'kcp' && ( + { {...getInputProps('protocol')} /> - + - + - + - + - + diff --git a/src/components/NodeModal/SSForm.tsx b/src/components/NodeModal/SSForm.tsx index c7d5d3c6..94b456a1 100644 --- a/src/components/NodeModal/SSForm.tsx +++ b/src/components/NodeModal/SSForm.tsx @@ -1,12 +1,14 @@ import { NumberInput, Select, TextInput } from '@mantine/core' import { useForm, zodResolver } from '@mantine/form' import { Base64 } from 'js-base64' +import { useTranslation } from 'react-i18next' import { z } from 'zod' import { FormActions } from '~/components/FormActions' import { DEFAULT_SS_FORM_VALUES, ssSchema } from '~/constants' export const SSForm = () => { + const { t } = useTranslation() const { values, onSubmit, getInputProps, reset } = useForm>({ initialValues: DEFAULT_SS_FORM_VALUES, validate: zodResolver(ssSchema), @@ -66,13 +68,13 @@ export const SSForm = () => { return (
- + - + - + - + { /> { {...getInputProps('obfs')} /> - {values.obfs !== 'plain' && } + {values.obfs !== 'plain' && } diff --git a/src/components/NodeModal/Socks5Form.tsx b/src/components/NodeModal/Socks5Form.tsx index edbf1e94..d8a85dd7 100644 --- a/src/components/NodeModal/Socks5Form.tsx +++ b/src/components/NodeModal/Socks5Form.tsx @@ -1,5 +1,6 @@ import { NumberInput, TextInput } from '@mantine/core' import { useForm, zodResolver } from '@mantine/form' +import { useTranslation } from 'react-i18next' import { z } from 'zod' import { FormActions } from '~/components/FormActions' @@ -7,6 +8,7 @@ import { DEFAULT_SOCKS5_FORM_VALUES, socks5Schema } from '~/constants' import { GenerateURLParams, generateURL } from '~/utils' export const Socks5Form = () => { + const { t } = useTranslation() const { onSubmit, getInputProps, reset } = useForm>({ initialValues: DEFAULT_SOCKS5_FORM_VALUES, validate: zodResolver(socks5Schema), @@ -32,15 +34,15 @@ export const Socks5Form = () => { return (
- + - + - + - + - + diff --git a/src/components/NodeModal/TrojanForm.tsx b/src/components/NodeModal/TrojanForm.tsx index 5b0441b7..bc6a06b3 100644 --- a/src/components/NodeModal/TrojanForm.tsx +++ b/src/components/NodeModal/TrojanForm.tsx @@ -1,5 +1,6 @@ import { Checkbox, NumberInput, Select, TextInput } from '@mantine/core' import { useForm, zodResolver } from '@mantine/form' +import { useTranslation } from 'react-i18next' import { z } from 'zod' import { FormActions } from '~/components/FormActions' @@ -7,6 +8,7 @@ import { DEFAULT_TROJAN_FORM_VALUES, trojanSchema } from '~/constants' import { generateURL } from '~/utils' export const TrojanForm = () => { + const { t } = useTranslation() const { values, onSubmit, getInputProps, reset } = useForm>({ initialValues: DEFAULT_TROJAN_FORM_VALUES, validate: zodResolver(trojanSchema), @@ -51,16 +53,16 @@ export const TrojanForm = () => { return (
- + - + - + - + - {values.obfs === 'websocket' && } + {values.obfs === 'websocket' && } - {values.obfs === 'websocket' && } + {values.obfs === 'websocket' && } diff --git a/src/components/NodeModal/V2rayForm.tsx b/src/components/NodeModal/V2rayForm.tsx index d5406f36..528fd419 100644 --- a/src/components/NodeModal/V2rayForm.tsx +++ b/src/components/NodeModal/V2rayForm.tsx @@ -1,6 +1,7 @@ import { Checkbox, NumberInput, Select, TextInput } from '@mantine/core' import { useForm, zodResolver } from '@mantine/form' import { Base64 } from 'js-base64' +import { useTranslation } from 'react-i18next' import { z } from 'zod' import { FormActions } from '~/components/FormActions' @@ -8,6 +9,7 @@ import { DEFAULT_V2RAY_FORM_VALUES, v2raySchema } from '~/constants' import { generateURL } from '~/utils' export const V2rayForm = () => { + const { t } = useTranslation() const { values, onSubmit, getInputProps, reset } = useForm< z.infer & { protocol: 'vless' | 'vmess' } >({ @@ -80,7 +82,7 @@ export const V2rayForm = () => { return (
{ )} @@ -167,14 +169,14 @@ export const V2rayForm = () => { {values.net === 'kcp' && ( void }) => { +const schema = z.object({ + tag: z.string().nonempty(), +}) + +export const ConfigureNodeFormModal = ({ opened, onClose }: { opened: boolean; onClose: () => void }) => { const { t } = useTranslation() + const importNodesMutation = useImportNodesMutation() + const form = useForm>({ + initialValues: { tag: '' }, + validate: zodResolver(schema), + }) + + const onLinkGeneration = async (link: string) => { + const { hasErrors } = form.validate() + + if (hasErrors) return + + await importNodesMutation.mutateAsync([ + { + link, + tag: form.values.tag, + }, + ]) + + onClose() + } return ( + + + + - + - + - + - + - + - + diff --git a/src/components/CreateNodeFormModal.tsx b/src/components/CreateNodeFormModal.tsx deleted file mode 100644 index 9b6643c7..00000000 --- a/src/components/CreateNodeFormModal.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Modal, Stack, Tabs } from '@mantine/core' - -import { FormActions } from './FormActions' - -export const CreateNodeFormModal = ({ opened, onClose }: { opened: boolean; onClose: () => void }) => { - return ( - - - - - V2RAY - SS - SSR - Trojan - HTTP - SOCKS5 - - - - - - - ) -} diff --git a/src/components/index.ts b/src/components/index.ts new file mode 100644 index 00000000..c1bf1925 --- /dev/null +++ b/src/components/index.ts @@ -0,0 +1,18 @@ +export * from './ConfigFormModal' +export * from './ConfigureNodeFormModal' +export * from './DraggableResourceBadge' +export * from './DraggableResourceCard' +export * from './DroppableGroupCard' +export * from './ExpandedTableRow' +export * from './FormActions' +export * from './GroupFormModal' +export * from './Header' +export * from './ImportResourceFormModal' +export * from './PlainTextFormModal' +export * from './QRCodeModal' +export * from './RenameFormModal' +export * from './Section' +export * from './SelectItemWithDescription' +export * from './SimpleCard' +export * from './Table' +export * from './UpdateSubscriptionAction' diff --git a/src/pages/Experiment.tsx b/src/pages/Experiment.tsx index 9d0c01fd..63040179 100644 --- a/src/pages/Experiment.tsx +++ b/src/pages/Experiment.tsx @@ -21,7 +21,7 @@ import { import { useDisclosure } from '@mantine/hooks' import { Prism } from '@mantine/prism' import Editor from '@monaco-editor/react' -import { IconForms } from '@tabler/icons-react' +import { IconFileImport, IconForms } from '@tabler/icons-react' import dayjs from 'dayjs' import { produce } from 'immer' import { useRef, useState } from 'react' @@ -33,18 +33,20 @@ import { useImportNodesMutation, useImportSubscriptionsMutation, } from '~/apis' -import { ConfigFormDrawer } from '~/components/ConfigFormModal' -import { CreateNodeFormModal } from '~/components/CreateNodeFormModal' -import { DraggableResourceBadge } from '~/components/DraggableResourceBadge' -import { DraggableResourceCard } from '~/components/DraggableResourceCard' -import { DroppableGroupCard } from '~/components/DroppableGroupCard' -import { GroupFormModal } from '~/components/GroupFormModal' -import { ImportResourceFormModal } from '~/components/ImportResourceFormModal' -import { NodeModal } from '~/components/NodeModal' -import { PlainTextFormModal } from '~/components/PlainTextFormModal' -import { RenameFormModal, RenameFormModalRef } from '~/components/RenameFormModal' -import { Section } from '~/components/Section' -import { SimpleCard } from '~/components/SimpleCard' +import { + ConfigFormDrawer, + ConfigureNodeFormModal, + DraggableResourceBadge, + DraggableResourceCard, + DroppableGroupCard, + GroupFormModal, + ImportResourceFormModal, + PlainTextFormModal, + RenameFormModal, + RenameFormModalRef, + Section, + SimpleCard, +} from '~/components' import { DialMode, DraggableResourceType, EDITOR_OPTIONS, LogLevel, RuleType } from '~/constants' import { Policy } from '~/schemas/gql/graphql' @@ -217,6 +219,8 @@ export const ExperimentPage = () => { useDisclosure(false) const [openedCreateGroupModal, { open: openCreateGroupModal, close: closeCreateGroupModal }] = useDisclosure(false) const [openedImportNodeModal, { open: openImportNodeModal, close: closeImportNodeModal }] = useDisclosure(false) + const [openedConfigureNodeFormModal, { open: openConfigureNodeFormModal, close: closeConfigureNodeFormModal }] = + useDisclosure(false) const [openedImportSubscriptionModal, { open: openImportSubscriptionModal, close: closeImportSubscriptionModal }] = useDisclosure(false) @@ -504,7 +508,16 @@ export const ExperimentPage = () => { -
+
+ + + } + bordered + > {fakeNodes.map(({ id, name, tag, protocol, link }) => ( { }} /> - { - // - }} - /> - - { - // - }} - /> + ) } diff --git a/src/pages/Orchestrate/Node.tsx b/src/pages/Orchestrate/Node.tsx index 47ceae94..512f3c68 100644 --- a/src/pages/Orchestrate/Node.tsx +++ b/src/pages/Orchestrate/Node.tsx @@ -1,10 +1,11 @@ import { ActionIcon, Spoiler, Text, useMantineTheme } from '@mantine/core' import { useDisclosure } from '@mantine/hooks' -import { IconCloud, IconDetails } from '@tabler/icons-react' +import { IconCloud, IconDetails, IconFileImport } from '@tabler/icons-react' import { useRef } from 'react' import { useTranslation } from 'react-i18next' import { useImportNodesMutation, useNodesQuery, useRemoveNodesMutation } from '~/apis' +import { ConfigureNodeFormModal } from '~/components' import { DraggableResourceCard } from '~/components/DraggableResourceCard' import { ImportResourceFormModal } from '~/components/ImportResourceFormModal' import { QRCodeModal, QRCodeModalRef } from '~/components/QRCodeModal' @@ -18,13 +19,25 @@ export const NodeResource = () => { const [openedQRCodeModal, { open: openQRCodeModal, close: closeQRCodeModal }] = useDisclosure(false) const [openedImportNodeFormModal, { open: openImportNodeFormModal, close: closeImportNodeFormModal }] = useDisclosure(false) + const [openedConfigureNodeFormModal, { open: openConfigureNodeFormModal, close: closeConfigureNodeFormModal }] = + useDisclosure(false) const qrCodeModalRef = useRef(null) const { data: nodesQuery } = useNodesQuery() const removeNodesMutation = useRemoveNodesMutation() const importNodesMutation = useImportNodesMutation() return ( -
} onCreate={openImportNodeFormModal} bordered> +
} + onCreate={openConfigureNodeFormModal} + actions={ + + + + } + bordered + > {nodesQuery?.nodes.edges.map(({ id, name, tag, protocol, link }) => ( { await importNodesMutation.mutateAsync(values.resources.map(({ link, tag }) => ({ link, tag }))) }} /> + +
) } From bf2841e1383ee9315b93c1424ed78905a7497454 Mon Sep 17 00:00:00 2001 From: kunish Date: Sat, 1 Jul 2023 20:21:32 +0800 Subject: [PATCH 18/21] fix(configure-node): correcting trojan schema server validation --- src/constants/schema.ts | 112 +++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 59 deletions(-) diff --git a/src/constants/schema.ts b/src/constants/schema.ts index 826e2c75..24755ca2 100644 --- a/src/constants/schema.ts +++ b/src/constants/schema.ts @@ -35,81 +35,75 @@ export const ssSchema = z.object({ }) export const ssrSchema = z.object({ - method: z - .enum([ - 'aes-128-cfb', - 'aes-192-cfb', - 'aes-256-cfb', - 'aes-128-ctr', - 'aes-192-ctr', - 'aes-256-ctr', - 'aes-128-ofb', - 'aes-192-ofb', - 'aes-256-ofb', - 'des-cfb', - 'bf-cfb', - 'cast5-cfb', - 'rc4-md5', - 'chacha20-ietf', - 'salsa20', - 'camellia-128-cfb', - 'camellia-192-cfb', - 'camellia-256-cfb', - 'idea-cfb', - 'rc2-cfb', - 'seed-cfb', - 'none', - ]) - .default('aes-128-cfb'), + method: z.enum([ + 'aes-128-cfb', + 'aes-192-cfb', + 'aes-256-cfb', + 'aes-128-ctr', + 'aes-192-ctr', + 'aes-256-ctr', + 'aes-128-ofb', + 'aes-192-ofb', + 'aes-256-ofb', + 'des-cfb', + 'bf-cfb', + 'cast5-cfb', + 'rc4-md5', + 'chacha20-ietf', + 'salsa20', + 'camellia-128-cfb', + 'camellia-192-cfb', + 'camellia-256-cfb', + 'idea-cfb', + 'rc2-cfb', + 'seed-cfb', + 'none', + ]), password: z.string().nonempty(), - server: z.string().url().nonempty().or(z.string().ip().nonempty()), + server: z.string().nonempty(), port: z.number().min(0).max(65535).positive(), - name: z.string().default(''), - proto: z - .enum([ - 'origin', - 'verify_sha1', - 'auth_sha1_v4', - 'auth_aes128_md5', - 'auth_aes128_sha1', - 'auth_chain_a', - 'auth_chain_b', - ]) - .default('origin'), - protoParam: z.string().default(''), - obfs: z.enum(['plain', 'http_simple', 'http_post', 'random_head', 'tls1.2_ticket_auth']).default('plain'), - obfsParam: z.string().default(''), + name: z.string(), + proto: z.enum([ + 'origin', + 'verify_sha1', + 'auth_sha1_v4', + 'auth_aes128_md5', + 'auth_aes128_sha1', + 'auth_chain_a', + 'auth_chain_b', + ]), + protoParam: z.string(), + obfs: z.enum(['plain', 'http_simple', 'http_post', 'random_head', 'tls1.2_ticket_auth']), + obfsParam: z.string(), }) export const trojanSchema = z.object({ - name: z.string().default(''), - server: z.string().url().nonempty().or(z.string().ip().nonempty()), - peer: z.string().url().default(''), - host: z.string().url().default(''), - path: z.string().default(''), - allowInsecure: z.boolean().default(false), + name: z.string(), + server: z.string().nonempty(), + peer: z.string(), + host: z.string(), + path: z.string(), + allowInsecure: z.boolean(), port: z.number().min(0).max(65535), password: z.string().nonempty(), - method: z.enum(['origin', 'shadowsocks']).default('origin'), - ssCipher: z - .enum(['aes-128-gcm', 'aes-256-gcm', 'chacha20-poly1305', 'chacha20-ietf-poly1305']) - .default('aes-128-gcm'), + method: z.enum(['origin', 'shadowsocks']), + ssCipher: z.enum(['aes-128-gcm', 'aes-256-gcm', 'chacha20-poly1305', 'chacha20-ietf-poly1305']), ssPassword: z.string(), - obfs: z.enum(['none', 'websocket']).default('none'), + obfs: z.enum(['none', 'websocket']), }) export const httpSchema = z.object({ - username: z.string().default(''), - password: z.string().default(''), + username: z.string(), + password: z.string(), host: z.string().nonempty(), port: z.number().min(0).max(65535), - name: z.string().default(''), + name: z.string(), }) export const socks5Schema = z.object({ - username: z.string().default(''), - password: z.string().default(''), + username: z.string(), + password: z.string(), host: z.string().nonempty(), port: z.number().min(0).max(65535), - name: z.string().default(''), + name: z.string(), }) From e18b5f003eec737aea2b65c1439d46bf366d27e9 Mon Sep 17 00:00:00 2001 From: kunish Date: Sat, 1 Jul 2023 20:23:33 +0800 Subject: [PATCH 19/21] fix: correcting autoConfigKernelParameter zh-Hans translation --- src/i18n/locales/zh-Hans.json | 2 +- wing | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/locales/zh-Hans.json b/src/i18n/locales/zh-Hans.json index 23a57852..2a402a7a 100644 --- a/src/i18n/locales/zh-Hans.json +++ b/src/i18n/locales/zh-Hans.json @@ -83,7 +83,7 @@ "descriptions": { "config": { "allowInsecure": "允许使用不安全的TLS证书。除非迫不得已,否则不建议打开它。", - "autoConfigKernelParameter": "自动配置 Linux 内核参数,如 ip_forward(端口转发)和 send_redirects(发送重定向)。", + "autoConfigKernelParameter": "自动配置 Linux 内核参数,如 ip_forward(路由转发)和 send_redirects(发送重定向)。", "checkTolerance": "只有当新的延迟 <= 旧的延迟 - 公差时,群组才会切换节点。", "dialMode": { "domain": "通过嗅探使用域名拨号代理。如果 DNS 环境不纯净,这将在很大程度上缓解 DNS 污染问题。通常,这种模式会带来更快的代理响应时间,因为代理会在远程重新解析域名,从而获得更好的 IP 连接结果。此策略不影响路由。也就是说,域重写将在流量拆分后进行路由,dae 不会对其进行重新路由。", diff --git a/wing b/wing index afd2fea8..228a8e1c 160000 --- a/wing +++ b/wing @@ -1 +1 @@ -Subproject commit afd2fea8b756ccc75eb67ab278d963d8a8e86164 +Subproject commit 228a8e1cf87180a444401f0385e9a7de16d3f246 From 5806d3c53acf20bb561eaf36223650bb4baf7928 Mon Sep 17 00:00:00 2001 From: kunish Date: Sat, 1 Jul 2023 20:43:21 +0800 Subject: [PATCH 20/21] fix: reload after updating/removing subscription --- src/apis/mutation.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/apis/mutation.ts b/src/apis/mutation.ts index f83298e9..5056b414 100644 --- a/src/apis/mutation.ts +++ b/src/apis/mutation.ts @@ -683,6 +683,7 @@ export const useRemoveNodesMutation = () => { onSuccess: () => { queryClient.invalidateQueries({ queryKey: QUERY_KEY_NODE }) queryClient.invalidateQueries({ queryKey: QUERY_KEY_GROUP }) + queryClient.invalidateQueries({ queryKey: QUERY_KEY_CONFIG }) }, }) } @@ -748,6 +749,8 @@ export const useUpdateSubscriptionsMutation = () => { ), onSuccess: () => { queryClient.invalidateQueries({ queryKey: QUERY_KEY_SUBSCRIPTION }) + queryClient.invalidateQueries({ queryKey: QUERY_KEY_GROUP }) + queryClient.invalidateQueries({ queryKey: QUERY_KEY_GENERAL }) }, }) } @@ -771,6 +774,7 @@ export const useRemoveSubscriptionsMutation = () => { onSuccess: () => { queryClient.invalidateQueries({ queryKey: QUERY_KEY_SUBSCRIPTION }) queryClient.invalidateQueries({ queryKey: QUERY_KEY_GROUP }) + queryClient.invalidateQueries({ queryKey: QUERY_KEY_GENERAL }) }, }) } From 459326eec30574c662a4c24b25e79070e83b94b4 Mon Sep 17 00:00:00 2001 From: kunish Date: Sun, 2 Jul 2023 02:39:17 +0800 Subject: [PATCH 21/21] fix(configure-node): add missing onLinkGeneration call --- .../ConfigureNodeFormModal/HTTPForm.tsx | 4 ++-- .../ConfigureNodeFormModal/SSForm.tsx | 2 +- .../ConfigureNodeFormModal/SSRForm.tsx | 16 +++++++++------- .../ConfigureNodeFormModal/Socks5Form.tsx | 2 +- .../ConfigureNodeFormModal/TrojanForm.tsx | 18 ++++++++++-------- 5 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/components/ConfigureNodeFormModal/HTTPForm.tsx b/src/components/ConfigureNodeFormModal/HTTPForm.tsx index a646d59b..178634f3 100644 --- a/src/components/ConfigureNodeFormModal/HTTPForm.tsx +++ b/src/components/ConfigureNodeFormModal/HTTPForm.tsx @@ -19,7 +19,7 @@ export const HTTPForm = ({ onLinkGeneration }: { onLinkGeneration: (link: string const handleSubmit = onSubmit((values) => { const generateURLParams: GenerateURLParams = { - protocol: `${values.protocol}-proxy`, + protocol: values.protocol, host: values.host, port: values.port, hash: values.name, @@ -32,7 +32,7 @@ export const HTTPForm = ({ onLinkGeneration }: { onLinkGeneration: (link: string }) } - return generateURL(generateURLParams) + return onLinkGeneration(generateURL(generateURLParams)) }) return ( diff --git a/src/components/ConfigureNodeFormModal/SSForm.tsx b/src/components/ConfigureNodeFormModal/SSForm.tsx index 78072709..f727ac53 100644 --- a/src/components/ConfigureNodeFormModal/SSForm.tsx +++ b/src/components/ConfigureNodeFormModal/SSForm.tsx @@ -63,7 +63,7 @@ export const SSForm = ({ onLinkGeneration }: { onLinkGeneration: (link: string) link += values.name.length ? `#${encodeURIComponent(values.name)}` : '' - return link + return onLinkGeneration(link) }) return ( diff --git a/src/components/ConfigureNodeFormModal/SSRForm.tsx b/src/components/ConfigureNodeFormModal/SSRForm.tsx index ee304082..db4c5064 100644 --- a/src/components/ConfigureNodeFormModal/SSRForm.tsx +++ b/src/components/ConfigureNodeFormModal/SSRForm.tsx @@ -16,13 +16,15 @@ export const SSRForm = ({ onLinkGeneration }: { onLinkGeneration: (link: string) const handleSubmit = onSubmit((values) => { /* ssr://server:port:proto:method:obfs:URLBASE64(password)/?remarks=URLBASE64(remarks)&protoparam=URLBASE64(protoparam)&obfsparam=URLBASE64(obfsparam)) */ - return `ssr://${Base64.encode( - `${values.server}:${values.port}:${values.proto}:${values.method}:${values.obfs}:${Base64.encodeURI( - values.password - )}/?remarks=${Base64.encodeURI(values.name)}&protoparam=${Base64.encodeURI( - values.protoParam - )}&obfsparam=${Base64.encodeURI(values.obfsParam)}` - )}` + return onLinkGeneration( + `ssr://${Base64.encode( + `${values.server}:${values.port}:${values.proto}:${values.method}:${values.obfs}:${Base64.encodeURI( + values.password + )}/?remarks=${Base64.encodeURI(values.name)}&protoparam=${Base64.encodeURI( + values.protoParam + )}&obfsparam=${Base64.encodeURI(values.obfsParam)}` + )}` + ) }) return ( diff --git a/src/components/ConfigureNodeFormModal/Socks5Form.tsx b/src/components/ConfigureNodeFormModal/Socks5Form.tsx index ac9eff35..92e740ae 100644 --- a/src/components/ConfigureNodeFormModal/Socks5Form.tsx +++ b/src/components/ConfigureNodeFormModal/Socks5Form.tsx @@ -29,7 +29,7 @@ export const Socks5Form = ({ onLinkGeneration }: { onLinkGeneration: (link: stri }) } - return generateURL(generateURLParams) + return onLinkGeneration(generateURL(generateURLParams)) }) return ( diff --git a/src/components/ConfigureNodeFormModal/TrojanForm.tsx b/src/components/ConfigureNodeFormModal/TrojanForm.tsx index f218a815..1ff1bd2c 100644 --- a/src/components/ConfigureNodeFormModal/TrojanForm.tsx +++ b/src/components/ConfigureNodeFormModal/TrojanForm.tsx @@ -41,14 +41,16 @@ export const TrojanForm = ({ onLinkGeneration }: { onLinkGeneration: (link: stri delete query.allowInsecure } - return generateURL({ - protocol: protocol, - username: values.password, - host: values.server, - port: values.port, - hash: values.name, - params: query, - }) + return onLinkGeneration( + generateURL({ + protocol: protocol, + username: values.password, + host: values.server, + port: values.port, + hash: values.name, + params: query, + }) + ) }) return (