feat: add support change protocol from client to server

This commit is contained in:
VaalaCat
2024-12-22 15:42:38 +00:00
parent e116a5ad17
commit c2dcab76c4
10 changed files with 233 additions and 261 deletions

View File

@@ -82,7 +82,16 @@ func UpdateFrpcHander(c context.Context, req *pb.UpdateFRPCRequest) (*pb.UpdateF
}
cliCfg.ServerAddr = srv.ServerIP
cliCfg.ServerPort = srvConf.BindPort
switch cliCfg.Transport.Protocol {
case "tcp":
cliCfg.ServerPort = srvConf.BindPort
case "kcp":
cliCfg.ServerPort = srvConf.KCPBindPort
case "quic":
cliCfg.ServerPort = srvConf.QUICBindPort
default:
cliCfg.ServerPort = srvConf.BindPort
}
cliCfg.User = userInfo.GetUserName()
cliCfg.Auth = v1.AuthClientConfig{}
cliCfg.Metadatas = map[string]string{

View File

@@ -0,0 +1,164 @@
import React from 'react'
import { Control } from 'react-hook-form'
import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import { useTranslation } from 'react-i18next'
import StringListInput from './list-input'
export const HostField = ({
control,
name,
label,
placeholder,
defaultValue,
}: {
control: Control<any>
name: string
label: string
placeholder?: string
defaultValue?: string
}) => {
const { t } = useTranslation()
return (
<FormField
name={name}
control={control}
render={({ field }) => (
<FormItem>
<FormLabel>{t(label)}</FormLabel>
<FormControl>
<Input className='text-sm' placeholder={placeholder || '127.0.0.1'} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
defaultValue={defaultValue}
/>
)
}
export const PortField = ({
control,
name,
label,
placeholder,
defaultValue,
}: {
control: Control<any>
name: string
label: string
placeholder?: string
defaultValue?: number
}) => {
const { t } = useTranslation()
return (
<FormField
name={name}
control={control}
render={({ field }) => (
<FormItem>
<FormLabel>{t(label)}</FormLabel>
<FormControl>
<Input className='text-sm' placeholder={placeholder || '1234'} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
defaultValue={defaultValue}
/>
)
}
export const SecretStringField = ({
control,
name,
label,
placeholder,
defaultValue,
}: {
control: Control<any>
name: string
label: string
placeholder?: string
defaultValue?: string
}) => {
const { t } = useTranslation()
return (
<FormField
name={name}
control={control}
render={({ field }) => (
<FormItem>
<FormLabel>{t(label)}</FormLabel>
<FormControl>
<Input className='text-sm' placeholder={placeholder || "secret"} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
defaultValue={defaultValue}
/>
)
}
export const StringField = ({
control,
name,
label,
placeholder,
defaultValue,
}: {
control: Control<any>
name: string
label: string
placeholder?: string
defaultValue?: string
}) => {
const { t } = useTranslation()
return (
<FormField
name={name}
control={control}
render={({ field }) => (
<FormItem>
<FormLabel>{t(label)}</FormLabel>
<FormControl>
<Input className='text-sm' placeholder={placeholder || '127.0.0.1'} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
defaultValue={defaultValue}
/>
)
}
export const StringArrayField = ({
control,
name,
label,
placeholder,
defaultValue,
}: {
control: Control<any>
name: string
label: string
placeholder?: string
defaultValue?: string[]
}) => {
const { t } = useTranslation()
return (
<FormField
name={name}
control={control}
render={({ field }) => (
<FormItem>
<FormLabel>{t(label)}</FormLabel>
<FormControl>
<StringListInput placeholder={placeholder || '/path'} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
defaultValue={defaultValue}
/>
)
}

View File

@@ -15,7 +15,6 @@ import { TypedProxyConfig } from '@/types/proxy'
import { ClientSelector } from '../base/client-selector'
import { ServerSelector } from '../base/server-selector'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
export interface FRPCFormCardProps {
clientID?: string

View File

@@ -3,7 +3,7 @@ import React, { useEffect } from 'react'
import { useState } from 'react'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Label } from '@radix-ui/react-label'
import { HTTPProxyForm, STCPProxyForm, TCPProxyForm, TypedProxyForm, UDPProxyForm } from './proxy_form'
import { TypedProxyForm } from './proxy_form'
import { Button } from '@/components/ui/button'
import { Client, RespCode } from '@/lib/pb/common'
import { ClientConfig } from '@/types/client'
@@ -13,10 +13,11 @@ import { Input } from '@/components/ui/input'
import { AccordionHeader } from '@radix-ui/react-accordion'
import { QueryObserverResult, RefetchOptions, useMutation } from '@tanstack/react-query'
import { updateFRPC } from '@/api/frp'
import { Card, CardContent } from '@/components/ui/card'
import { GetClientResponse } from '@/lib/pb/api_client'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { BaseSelector } from '../base/selector'
import { ConnectionProtocols } from '@/lib/consts'
export interface FRPCFormProps {
clientID: string
@@ -28,10 +29,17 @@ export interface FRPCFormProps {
setClientProxyConfigs: React.Dispatch<React.SetStateAction<TypedProxyConfig[]>>
}
export const FRPCForm: React.FC<FRPCFormProps> = ({ clientID, serverID, client, refetchClient, clientProxyConfigs, setClientProxyConfigs }) => {
export const FRPCForm: React.FC<FRPCFormProps> = ({ clientID, serverID, clientConfig, client, refetchClient, clientProxyConfigs, setClientProxyConfigs }) => {
const { t } = useTranslation()
const [proxyType, setProxyType] = useState<ProxyType>('http')
const [proxyName, setProxyName] = useState<string | undefined>()
const [protocol, setProtocol] = useState<string | undefined>("tcp")
useEffect(() => {
if (clientConfig.transport?.protocol) {
setProtocol(clientConfig.transport?.protocol)
}
}, [clientConfig])
const handleTypeChange = (value: string) => {
setProxyType(value as ProxyType)
@@ -68,6 +76,10 @@ export const FRPCForm: React.FC<FRPCFormProps> = ({ clientID, serverID, client,
config: Buffer.from(
JSON.stringify({
proxies: clientProxyConfigs,
transport: {
...clientConfig.transport,
protocol,
}
} as ClientConfig),
),
serverId: serverID,
@@ -86,7 +98,7 @@ export const FRPCForm: React.FC<FRPCFormProps> = ({ clientID, serverID, client,
}
return (
<>
<div className='flex flex-col space-y-2'>
<Popover>
<PopoverTrigger asChild>
<Button className="my-2">{t('proxy.form.add')}</Button>
@@ -115,6 +127,11 @@ export const FRPCForm: React.FC<FRPCFormProps> = ({ clientID, serverID, client,
</Button>
</PopoverContent>
</Popover>
<Label className="text-sm font-medium">{t('proxy.form.protocol')}</Label>
<BaseSelector value={protocol} setValue={setProtocol}
dataList={ConnectionProtocols.map((item) => { return { label: item, value: item } })}
placeholder={t('proxy.form.protocol')}
label={t('proxy.form.protocol')} />
<Accordion type="single" defaultValue="proxies" collapsible key={clientID + serverID + client}>
<AccordionItem value="proxies">
<AccordionTrigger>
@@ -165,6 +182,6 @@ export const FRPCForm: React.FC<FRPCFormProps> = ({ clientID, serverID, client,
>
{t('proxy.form.submit')}
</Button>
</>
</div>
)
}

View File

@@ -4,10 +4,9 @@ import React from 'react'
import { ZodPortSchema, ZodStringOptionalSchema, ZodStringSchema } from '@/lib/consts'
import { useEffect, useState } from 'react'
import { zodResolver } from '@hookform/resolvers/zod'
import { Control, useForm } from 'react-hook-form'
import { useForm } from 'react-hook-form'
import { Button } from '@/components/ui/button'
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import { Form, FormDescription } from '@/components/ui/form'
import { Label } from '@/components/ui/label'
import { YesIcon } from '@/components/ui/icon'
import { useTranslation } from 'react-i18next'
@@ -15,7 +14,13 @@ import { useQuery } from '@tanstack/react-query'
import { getServer } from '@/api/server'
import { Switch } from "@/components/ui/switch"
import { VisitPreview } from '../base/visit-preview'
import StringListInput from '../base/list-input'
import {
HostField,
PortField,
SecretStringField,
StringArrayField,
StringField
} from '../base/form-field'
export const TCPConfigSchema = z.object({
remotePort: ZodPortSchema,
@@ -55,163 +60,6 @@ export interface ProxyFormProps {
setClientProxyConfigs: React.Dispatch<React.SetStateAction<TypedProxyConfig[]>>
}
const HostField = ({
control,
name,
label,
placeholder,
defaultValue,
}: {
control: Control<any>
name: string
label: string
placeholder?: string
defaultValue?: string
}) => {
const { t } = useTranslation()
return (
<FormField
name={name}
control={control}
render={({ field }) => (
<FormItem>
<FormLabel>{t(label)}</FormLabel>
<FormControl>
<Input className='text-sm' placeholder={placeholder || '127.0.0.1'} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
defaultValue={defaultValue}
/>
)
}
const PortField = ({
control,
name,
label,
placeholder,
defaultValue,
}: {
control: Control<any>
name: string
label: string
placeholder?: string
defaultValue?: number
}) => {
const { t } = useTranslation()
return (
<FormField
name={name}
control={control}
render={({ field }) => (
<FormItem>
<FormLabel>{t(label)}</FormLabel>
<FormControl>
<Input className='text-sm' placeholder={placeholder || '1234'} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
defaultValue={defaultValue}
/>
)
}
const SecretStringField = ({
control,
name,
label,
placeholder,
defaultValue,
}: {
control: Control<any>
name: string
label: string
placeholder?: string
defaultValue?: string
}) => {
const { t } = useTranslation()
return (
<FormField
name={name}
control={control}
render={({ field }) => (
<FormItem>
<FormLabel>{t(label)}</FormLabel>
<FormControl>
<Input className='text-sm' placeholder={placeholder || "secret"} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
defaultValue={defaultValue}
/>
)
}
const StringField = ({
control,
name,
label,
placeholder,
defaultValue,
}: {
control: Control<any>
name: string
label: string
placeholder?: string
defaultValue?: string
}) => {
const { t } = useTranslation()
return (
<FormField
name={name}
control={control}
render={({ field }) => (
<FormItem>
<FormLabel>{t(label)}</FormLabel>
<FormControl>
<Input className='text-sm' placeholder={placeholder || '127.0.0.1'} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
defaultValue={defaultValue}
/>
)
}
const StringArrayField = ({
control,
name,
label,
placeholder,
defaultValue,
}: {
control: Control<any>
name: string
label: string
placeholder?: string
defaultValue?: string[]
}) => {
const { t } = useTranslation()
return (
<FormField
name={name}
control={control}
render={({ field }) => (
<FormItem>
<FormLabel>{t(label)}</FormLabel>
<FormControl>
<StringListInput placeholder={placeholder || '/path'} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
defaultValue={defaultValue}
/>
)
}
export const TypedProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, defaultProxyConfig, proxyName, clientProxyConfigs, setClientProxyConfigs, enablePreview }) => {
if (!defaultProxyConfig) {

View File

@@ -1,11 +1,10 @@
import { ServerConfig } from '@/types/server'
import { useEffect, useState } from 'react'
import { useEffect } from 'react'
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import * as z from 'zod'
import { Button } from '@/components/ui/button'
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import { Form } from '@/components/ui/form'
import { ZodIPSchema, ZodPortSchema, ZodStringSchema } from '@/lib/consts'
import { RespCode, Server } from '@/lib/pb/common'
import { updateFRPS } from '@/api/frp'
@@ -13,6 +12,7 @@ import { useMutation } from '@tanstack/react-query'
import { Label } from '@radix-ui/react-label'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { HostField, PortField } from '../base/form-field'
const ServerConfigSchema = z.object({
bindAddr: ZodIPSchema.default('0.0.0.0').optional(),
@@ -21,6 +21,8 @@ const ServerConfigSchema = z.object({
vhostHTTPPort: ZodPortSchema.optional(),
subDomainHost: ZodStringSchema.optional(),
publicHost: ZodStringSchema.optional(),
quicBindPort: ZodPortSchema.optional(),
kcpBindPort: ZodPortSchema.optional(),
})
export const ServerConfigZodSchema = ServerConfigSchema
@@ -48,7 +50,7 @@ const FRPSForm: React.FC<FRPSFormProps> = ({ serverID, server }) => {
const onSubmit = async (values: z.infer<typeof ServerConfigZodSchema>) => {
try {
const {publicHost, ...rest} = values
const { publicHost, ...rest } = values
let resp = await updateFrps.mutateAsync({
serverIp: publicHost,
serverId: serverID,
@@ -59,7 +61,7 @@ const FRPSForm: React.FC<FRPSFormProps> = ({ serverID, server }) => {
} as ServerConfig),
),
})
toast(resp.status?.code === RespCode.SUCCESS ? t('server.operation.update_success') : t('server.operation.update_failed'),{
toast(resp.status?.code === RespCode.SUCCESS ? t('server.operation.update_success') : t('server.operation.update_failed'), {
description: resp.status?.message,
})
} catch (error) {
@@ -80,87 +82,14 @@ const FRPSForm: React.FC<FRPSFormProps> = ({ serverID, server }) => {
{serverID && (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 px-0.5">
<FormField
control={form.control}
name="publicHost"
render={({ field }) => (
<FormItem>
<FormLabel>{t('server.form.public_host')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
defaultValue={server?.ip}
/>
<FormField
control={form.control}
name="bindPort"
render={({ field }) => (
<FormItem>
<FormLabel>{t('server.form.bind_port')}</FormLabel>
<FormControl>
<Input type="number" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
defaultValue={7000}
/>
<FormField
control={form.control}
name="bindAddr"
render={({ field }) => (
<FormItem>
<FormLabel>{t('server.form.bind_addr')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
defaultValue="0.0.0.0"
/>
<FormField
control={form.control}
name="proxyBindAddr"
render={({ field }) => (
<FormItem>
<FormLabel>{t('server.form.proxy_bind_addr')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="vhostHTTPPort"
render={({ field }) => (
<FormItem>
<FormLabel>{t('server.form.vhost_http_port')}</FormLabel>
<FormControl>
<Input type="number" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="subDomainHost"
render={({ field }) => (
<FormItem>
<FormLabel>{t('server.form.subdomain_host')}</FormLabel>
<FormControl>
<Input placeholder="example.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<HostField name="publicHost" label={t('server.form.public_host')} placeholder='8.8.8.8' control={form.control} defaultValue={server?.ip}/>
<PortField name="bindPort" label={t('server.form.bind_port')} control={form.control} />
<HostField name="bindAddr" label={t('server.form.bind_addr')} control={form.control} />
<HostField name="proxyBindAddr" label={t('server.form.proxy_bind_addr')} control={form.control} />
<PortField name="vhostHTTPPort" label={t('server.form.vhost_http_port')} control={form.control} />
<HostField name="subDomainHost" label={t('server.form.subdomain_host')} control={form.control} />
<PortField name="quicBindPort" label={t('server.form.quic_bind_port')} control={form.control} />
<PortField name="kcpBindPort" label={t('server.form.kcp_bind_port')} control={form.control} />
<Button type="submit">{t('common.submit')}</Button>
</form>
</Form>

View File

@@ -200,7 +200,9 @@
"bind_addr": "FRPs Listen Address",
"proxy_bind_addr": "Proxy Listen Address",
"vhost_http_port": "HTTP Listen Port",
"subdomain_host": "Subdomain Host"
"subdomain_host": "Subdomain Host",
"quic_bind_port": "Quic Bind Port",
"kcp_bind_port":"KCP Bind Port"
},
"editor": {
"comment": "Node {{id}} Comment",

View File

@@ -207,7 +207,9 @@
"bind_addr": "FRPs 监听地址",
"proxy_bind_addr": "代理监听地址",
"vhost_http_port": "HTTP 监听端口",
"subdomain_host": "域名后缀"
"subdomain_host": "域名后缀",
"quic_bind_port": "Quic 监听端口",
"kcp_bind_port":"KCP 监听端口"
},
"create": {
"button": "新建",

View File

@@ -22,11 +22,13 @@ export const ZodEmailSchema = z.string({ required_error: 'validation.required' }
.min(1, { message: 'validation.required' })
.email({ message: 'auth.email.invalid' })
export const ConnectionProtocols = ["tcp", "kcp", "quic", "websocket", "wss"]
export const TypedProxyConfigValid = (typedProxyCfg: TypedProxyConfig | undefined): boolean => {
return (typedProxyCfg?.localPort && typedProxyCfg.localIP && typedProxyCfg.name && typedProxyCfg.type) ? true : false
}
export const IsIDValid = (clientID: string|undefined): boolean => {
export const IsIDValid = (clientID: string | undefined): boolean => {
if (clientID == undefined) {
return false
}

View File

@@ -3,10 +3,10 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build && rm -rf ../cmd/frpp/out && cp -r out ../cmd/frpp",
"start": "next start",
"lint": "next lint"
"dev": "bunx next dev",
"build": "bunx next build && rm -rf ../cmd/frpp/out && cp -r out ../cmd/frpp",
"start": "bunx next start",
"lint": "bunx next lint"
},
"dependencies": {
"@hookform/resolvers": "^3.3.4",