feat: rpc url and api url settings on front

This commit is contained in:
VaalaCat
2025-06-22 06:32:39 +00:00
parent e5bb9db1ba
commit df0fa51011
17 changed files with 433 additions and 77 deletions

View File

@@ -41,12 +41,12 @@ func FRPsAuthOption(cfg Config, isDefault bool) v1.HTTPPluginOptions {
if isDefault {
port = cfg.Master.APIPort
} else {
port = cfg.Master.InternalFRPAuthServerPort
port = cfg.Server.InternalFRPAuthServerPort
}
authUrl, err := url.Parse(fmt.Sprintf("http://%s:%d%s",
cfg.Master.InternalFRPAuthServerHost,
cfg.Server.InternalFRPAuthServerHost,
port,
cfg.Master.InternalFRPAuthServerPath))
cfg.Server.InternalFRPAuthServerPath))
if err != nil {
logger.Logger(context.Background()).WithError(err).Fatalf("parse auth url error")
}

View File

@@ -29,19 +29,19 @@ type Config struct {
GithubProxyUrl string `env:"GITHUB_PROXY_URL" env-default:"https://ghfast.top/" env-description:"github proxy url"`
} `env-prefix:"APP_"`
Master struct {
APIPort int `env:"API_PORT" env-default:"9000" env-description:"master api port"`
APIHost string `env:"API_HOST" env-description:"master host, can behind proxy like cdn"`
APIScheme string `env:"API_SCHEME" env-default:"http" env-description:"master api scheme"`
CacheSize int `env:"CACHE_SIZE" env-default:"10" env-description:"cache size in MB"`
RPCHost string `env:"RPC_HOST" env-default:"127.0.0.1" env-description:"master host, is a public ip or domain"`
RPCPort int `env:"RPC_PORT" env-default:"9001" env-description:"master rpc port"`
InternalFRPServerHost string `env:"INTERNAL_FRP_SERVER_HOST" env-description:"internal frp server host, used for client connection"`
APIPort int `env:"API_PORT" env-default:"9000" env-description:"master api port"`
APIHost string `env:"API_HOST" env-description:"master host, can behind proxy like cdn"`
APIScheme string `env:"API_SCHEME" env-default:"http" env-description:"master api scheme"`
CacheSize int `env:"CACHE_SIZE" env-default:"10" env-description:"cache size in MB"`
RPCHost string `env:"RPC_HOST" env-default:"127.0.0.1" env-description:"master host, is a public ip or domain"`
RPCPort int `env:"RPC_PORT" env-default:"9001" env-description:"master rpc port"`
InternalFRPServerHost string `env:"INTERNAL_FRP_SERVER_HOST" env-description:"internal frp server host, used for client connection"`
} `env-prefix:"MASTER_"`
Server struct {
APIPort int `env:"API_PORT" env-default:"8999" env-description:"server api port"`
InternalFRPAuthServerHost string `env:"INTERNAL_FRP_AUTH_SERVER_HOST" env-default:"127.0.0.1" env-description:"internal frp auth server host"`
InternalFRPAuthServerPort int `env:"INTERNAL_FRP_AUTH_SERVER_PORT" env-default:"8999" env-description:"internal frp auth server port"`
InternalFRPAuthServerPath string `env:"INTERNAL_FRP_AUTH_SERVER_PATH" env-default:"/auth" env-description:"internal frp auth server path"`
} `env-prefix:"MASTER_"`
Server struct {
APIPort int `env:"API_PORT" env-default:"8999" env-description:"server api port"`
} `env-prefix:"SERVER_"`
DB struct {
Type string `env:"TYPE" env-default:"sqlite3" env-description:"db type, mysql or sqlite3 and so on"`

View File

@@ -4,6 +4,10 @@ Server 推荐使用 docker 部署!不推荐直接安装到服务器中
注意 ⚠client 和 server 的启动指令可能会随着项目更新而改变,虽然在项目迭代时会注意前后兼容,但仍难以完全适配,因此 client 和 server 的启动指令以 master 生成为准
> `default` 服务端禁止单独部署,直接在 webui 中配置即可。重复部署会造成 `default` 服务端无法正常工作。
> `server` 会占用 8999 端口,请确保该端口未被占用。如果冲突,请修改 `server` 的 `SERVER_API_PORT` 和 `INTERNAL_FRP_AUTH_SERVER_PORT` 环境变量,要确保两个端口一致。`default` 服务端也会占用这个端口,因此不能在同一台机器以**默认配置**部署 `server` 和 `default` 服务端,需要修改 `server` 的环境变量来避免冲突。
> 如果只有一台公网服务器需要管理,那么使用 `master` 自带的 `default` 服务端即可,无需单独部署 `server`,但要注意在 `master` 启动后要配置 `default` 服务端
## 在 Linux 上部署

View File

@@ -4,6 +4,10 @@ We recommend deploying the Server via Docker! Direct installation on the host is
**Note ⚠️:** The startup commands for `client` and `server` may change as the project evolves. Although we strive for backward compatibility, the commands generated by the Masters web UI should be treated as authoritative.
> The `default` server cannot be deployed separately. It should be configured in the webui. Repeated deployment will cause the `default` server to malfunction.
> The `server` will occupy port 8999. Please ensure that this port is not occupied. If there is a conflict, please modify the `SERVER_API_PORT` and `INTERNAL_FRP_AUTH_SERVER_PORT` environment variables of the `server`, ensuring that the two ports are the same. The `default` server will also occupy this port, so you cannot deploy the `server` and `default` server on the same machine with **default configuration**. You need to modify the environment variables of the `server` to avoid conflicts.
> If you only have one public-facing server to manage, you can use the Masters built-in `default` server without deploying a separate Server. Remember to configure the `default` server after starting the Master.
## Deploying on Linux

View File

@@ -26,7 +26,7 @@ import { useMutation, useQuery } from '@tanstack/react-query'
import { deleteClient, listClient } from '@/api/client'
import { useRouter } from 'next/router'
import { useStore } from '@nanostores/react'
import { $frontendPreference, $platformInfo, $useServerGithubProxyUrl } from '@/store/user'
import { $frontendPreference, $platformInfo } from '@/store/user'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { getClientsStatus } from '@/api/platform'
import { Client, ClientType } from '@/lib/pb/common'
@@ -125,7 +125,6 @@ export const columns: ColumnDef<ClientTableSchema>[] = [
export const ClientID = ({ client }: { client: ClientTableSchema }) => {
const { t } = useTranslation()
const platformInfo = useStore($platformInfo)
const useGithubProxyUrl = useStore($useServerGithubProxyUrl)
const frontendPreference = useStore($frontendPreference)
if (!platformInfo) {
@@ -152,7 +151,9 @@ export const ClientID = ({ client }: { client: ClientTableSchema }) => {
<div className="grid gap-2">
<div className="grid grid-cols-2 items-center gap-4 justify-items-center">
<Label>{t('client.install.use_github_proxy_url')}</Label>
<Checkbox onCheckedChange={$useServerGithubProxyUrl.set} defaultChecked={useGithubProxyUrl} />
<Checkbox onCheckedChange={(checked) => {
$frontendPreference.set({ ...frontendPreference, useServerGithubProxyUrl: checked === 'indeterminate' ? false : checked })
}} defaultChecked={frontendPreference.useServerGithubProxyUrl} />
</div>
<div className="grid grid-cols-2 items-center gap-4 justify-items-center">
<Label>{t('client.install.github_proxy_url')}</Label>
@@ -165,8 +166,8 @@ export const ClientID = ({ client }: { client: ClientTableSchema }) => {
navigator.clipboard.writeText(
WindowsInstallCommand('client', client, {
...platformInfo,
githubProxyUrl: useGithubProxyUrl && frontendPreference.githubProxyUrl ? frontendPreference.githubProxyUrl : platformInfo.githubProxyUrl,
}, useGithubProxyUrl),
githubProxyUrl: frontendPreference.useServerGithubProxyUrl && frontendPreference.githubProxyUrl ? frontendPreference.githubProxyUrl : platformInfo.githubProxyUrl,
}, frontendPreference.useServerGithubProxyUrl),
)
}
disabled={!platformInfo}
@@ -179,8 +180,8 @@ export const ClientID = ({ client }: { client: ClientTableSchema }) => {
readOnly
value={WindowsInstallCommand('client', client, {
...platformInfo,
githubProxyUrl: useGithubProxyUrl && frontendPreference.githubProxyUrl ? frontendPreference.githubProxyUrl : platformInfo.githubProxyUrl,
}, useGithubProxyUrl)}
githubProxyUrl: frontendPreference.useServerGithubProxyUrl && frontendPreference.githubProxyUrl ? frontendPreference.githubProxyUrl : platformInfo.githubProxyUrl,
}, frontendPreference.useServerGithubProxyUrl)}
className="flex-1"
/>
</div>
@@ -189,8 +190,8 @@ export const ClientID = ({ client }: { client: ClientTableSchema }) => {
onClick={() =>
navigator.clipboard.writeText(LinuxInstallCommand('client', client, {
...platformInfo,
githubProxyUrl: useGithubProxyUrl && frontendPreference.githubProxyUrl ? frontendPreference.githubProxyUrl : platformInfo.githubProxyUrl,
}, useGithubProxyUrl))
githubProxyUrl: frontendPreference.useServerGithubProxyUrl && frontendPreference.githubProxyUrl ? frontendPreference.githubProxyUrl : platformInfo.githubProxyUrl,
}, frontendPreference.useServerGithubProxyUrl))
}
disabled={!platformInfo}
size="sm"
@@ -202,8 +203,8 @@ export const ClientID = ({ client }: { client: ClientTableSchema }) => {
readOnly
value={LinuxInstallCommand('client', client, {
...platformInfo,
githubProxyUrl: useGithubProxyUrl && frontendPreference.githubProxyUrl ? frontendPreference.githubProxyUrl : platformInfo.githubProxyUrl,
}, useGithubProxyUrl)}
githubProxyUrl: frontendPreference.useServerGithubProxyUrl && frontendPreference.githubProxyUrl ? frontendPreference.githubProxyUrl : platformInfo.githubProxyUrl,
}, frontendPreference.useServerGithubProxyUrl)}
className="flex-1"
/>
</div>
@@ -344,7 +345,6 @@ export const ClientActions: React.FC<ClientItemProps> = ({ client, table }) => {
const { t } = useTranslation()
const router = useRouter()
const platformInfo = useStore($platformInfo)
const useGithubProxyUrl = useStore($useServerGithubProxyUrl)
const frontendPreference = useStore($frontendPreference)
const removeClient = useMutation({
@@ -433,8 +433,8 @@ export const ClientActions: React.FC<ClientItemProps> = ({ client, table }) => {
if (platformInfo) {
navigator.clipboard.writeText(LinuxInstallCommand('client', client, {
...platformInfo,
githubProxyUrl: useGithubProxyUrl && frontendPreference.githubProxyUrl ? frontendPreference.githubProxyUrl : platformInfo.githubProxyUrl,
}, useGithubProxyUrl))
githubProxyUrl: frontendPreference.useServerGithubProxyUrl && frontendPreference.githubProxyUrl ? frontendPreference.githubProxyUrl : platformInfo.githubProxyUrl,
}, frontendPreference.useServerGithubProxyUrl))
toast(t('client.actions_menu.copy_success'))
} else {
toast(t('client.actions_menu.copy_failed'))

View File

@@ -20,13 +20,13 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import React, { useState } from 'react'
import React from 'react'
import { ClientEnvFile, ExecCommandStr, LinuxInstallCommand, WindowsInstallCommand } from '@/lib/consts'
import { useMutation, useQuery } from '@tanstack/react-query'
import { deleteServer } from '@/api/server'
import { useRouter } from 'next/router'
import { useStore } from '@nanostores/react'
import { $frontendPreference, $platformInfo, $useServerGithubProxyUrl } from '@/store/user'
import { $frontendPreference, $platformInfo } from '@/store/user'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { getClientsStatus } from '@/api/platform'
import { ClientType } from '@/lib/pb/common'
@@ -124,7 +124,6 @@ export const columns: ColumnDef<ServerTableSchema>[] = [
export const ServerID = ({ server }: { server: ServerTableSchema }) => {
const { t } = useTranslation()
const platformInfo = useStore($platformInfo)
const useGithubProxyUrl = useStore($useServerGithubProxyUrl)
const frontendPreference = useStore($frontendPreference)
if (!platformInfo) {
@@ -151,7 +150,9 @@ export const ServerID = ({ server }: { server: ServerTableSchema }) => {
<div className="grid gap-2">
<div className="grid grid-cols-2 items-center gap-4 justify-items-center">
<Label>{t('client.install.use_github_proxy_url')}</Label>
<Checkbox onCheckedChange={$useServerGithubProxyUrl.set} defaultChecked={useGithubProxyUrl} />
<Checkbox onCheckedChange={(checked) => {
$frontendPreference.set({ ...frontendPreference, useServerGithubProxyUrl: checked === 'indeterminate' ? false : checked })
}} defaultChecked={frontendPreference.useServerGithubProxyUrl} />
</div>
<div className="grid grid-cols-2 items-center gap-4 justify-items-center">
<Label>{t('client.install.github_proxy_url')}</Label>
@@ -162,8 +163,8 @@ export const ServerID = ({ server }: { server: ServerTableSchema }) => {
<Button
onClick={() => navigator.clipboard.writeText(WindowsInstallCommand('server', server, {
...platformInfo,
githubProxyUrl: useGithubProxyUrl && frontendPreference.githubProxyUrl ? frontendPreference.githubProxyUrl : platformInfo.githubProxyUrl,
}, useGithubProxyUrl))}
githubProxyUrl: frontendPreference.useServerGithubProxyUrl && frontendPreference.githubProxyUrl ? frontendPreference.githubProxyUrl : platformInfo.githubProxyUrl,
}, frontendPreference.useServerGithubProxyUrl))}
disabled={!platformInfo}
size="sm"
variant="outline"
@@ -174,8 +175,8 @@ export const ServerID = ({ server }: { server: ServerTableSchema }) => {
readOnly
value={WindowsInstallCommand('server', server, {
...platformInfo,
githubProxyUrl: useGithubProxyUrl && frontendPreference.githubProxyUrl ? frontendPreference.githubProxyUrl : platformInfo.githubProxyUrl,
}, useGithubProxyUrl)}
githubProxyUrl: frontendPreference.useServerGithubProxyUrl && frontendPreference.githubProxyUrl ? frontendPreference.githubProxyUrl : platformInfo.githubProxyUrl,
}, frontendPreference.useServerGithubProxyUrl)}
className="flex-1"
/>
</div>
@@ -183,8 +184,8 @@ export const ServerID = ({ server }: { server: ServerTableSchema }) => {
<Button
onClick={() => navigator.clipboard.writeText(LinuxInstallCommand('server', server, {
...platformInfo,
githubProxyUrl: useGithubProxyUrl && frontendPreference.githubProxyUrl ? frontendPreference.githubProxyUrl : platformInfo.githubProxyUrl,
}, useGithubProxyUrl))}
githubProxyUrl: frontendPreference.useServerGithubProxyUrl && frontendPreference.githubProxyUrl ? frontendPreference.githubProxyUrl : platformInfo.githubProxyUrl,
}, frontendPreference.useServerGithubProxyUrl))}
disabled={!platformInfo}
size="sm"
variant="outline"
@@ -195,8 +196,8 @@ export const ServerID = ({ server }: { server: ServerTableSchema }) => {
readOnly
value={LinuxInstallCommand('server', server, {
...platformInfo,
githubProxyUrl: useGithubProxyUrl && frontendPreference.githubProxyUrl ? frontendPreference.githubProxyUrl : platformInfo.githubProxyUrl,
}, useGithubProxyUrl)}
githubProxyUrl: frontendPreference.useServerGithubProxyUrl && frontendPreference.githubProxyUrl ? frontendPreference.githubProxyUrl : platformInfo.githubProxyUrl,
}, frontendPreference.useServerGithubProxyUrl)}
className="flex-1"
/>
</div>

View File

@@ -3,6 +3,7 @@
import {
ChevronsUpDown,
LogOut,
SettingsIcon,
User as UserIcon, // 别名避免和 User 类型冲突
} from 'lucide-react'
import Link from 'next/link'
@@ -67,13 +68,18 @@ export function NavUser({ user }: NavUserProps) {
</div>
</DropdownMenuLabel>
{/* 使用 next/link 创建 “User Info” 菜单项 */}
<DropdownMenuItem asChild>
<Link href="/user-info" className="w-full flex items-center space-x-2">
<UserIcon className="h-4 w-4" />
<span>{t('common.userInfo')}</span>
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link href="/platform-settings" className="w-full flex items-center space-x-2">
<SettingsIcon className="h-4 w-4" />
<span>{t('平台设置')}</span>
</Link>
</DropdownMenuItem>
<DropdownMenuSeparator />

View File

@@ -0,0 +1,202 @@
import { toast } from 'sonner'
import { useTranslation } from 'react-i18next'
import { useEffect, useState } from 'react'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import * as z from 'zod'
import { useStore } from '@nanostores/react'
import { $platformInfo } from '@/store/user'
import { $frontendPreference, FrontendPreference } from '@/store/user'
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, FormDescription } from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import { Switch } from '@/components/ui/switch'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter as DialogFooterUI,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog'
// 表单校验 Schema
const platformSchema = z.object({
useServerGithubProxyUrl: z.boolean().default(false),
// githubProxyUrl 可选;若为空字符串则忽略,否则需为合法 URL
githubProxyUrl: z.union([z.string().trim().url('Invalid URL'), z.literal('')]).optional(),
clientApiUrl: z.union([z.string().trim().url('Invalid URL'), z.literal('')]).optional(),
clientRpcUrl: z.union([z.string().trim().url('Invalid URL'), z.literal('')]).optional(),
})
type PlatformFormValues = z.infer<typeof platformSchema>
export function PlatformSettingsForm() {
const { t } = useTranslation()
const [loading, setLoading] = useState(true)
const [initial, setInitial] = useState<PlatformFormValues | null>(null)
const [open, setOpen] = useState(false)
const platformInfo = useStore($platformInfo)
const form = useForm<PlatformFormValues>({
resolver: zodResolver(platformSchema),
defaultValues: {
useServerGithubProxyUrl: false,
githubProxyUrl: '',
clientApiUrl: '',
clientRpcUrl: '',
},
})
// 组件挂载时读取持久化设置
useEffect(() => {
const pref = ($frontendPreference.get() ?? {}) as FrontendPreference
form.reset({
useServerGithubProxyUrl: pref.useServerGithubProxyUrl ?? false,
githubProxyUrl: pref.githubProxyUrl ?? '',
clientApiUrl: pref.clientApiUrl ?? '',
clientRpcUrl: pref.clientRpcUrl ?? '',
})
setInitial(form.getValues())
setLoading(false)
}, [])
const onSubmit = (values: PlatformFormValues) => {
const pref: FrontendPreference = {
useServerGithubProxyUrl: values.useServerGithubProxyUrl,
githubProxyUrl: values.githubProxyUrl?.trim() || undefined,
clientApiUrl: values.clientApiUrl?.trim() || undefined,
clientRpcUrl: values.clientRpcUrl?.trim() || undefined,
}
$frontendPreference.set(pref)
toast.success(t('已更新平台设置'))
// 重置 initial 状态 & 清空 dirty
form.reset(values)
setInitial(values)
}
if (loading) {
return (
<div className="flex items-center justify-center h-64">
<p className="text-gray-500">{t('正在加载平台设置')}</p>
</div>
)
}
return (
<Card className="max-w-lg mx-auto">
<CardHeader className="border-b">
<CardTitle>{t('平台设置')}</CardTitle>
<CardDescription>{t('修改前端平台设置')}</CardDescription>
<p className="text-xs text-muted-foreground mt-1 italic">{t('此配置仅保存在本地')}</p>
</CardHeader>
<CardContent>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6 pt-2">
{/* 使用服务器代理开关 */}
<FormField
control={form.control}
name="useServerGithubProxyUrl"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
<div className="space-y-0.5">
<FormLabel>{t('使用服务器 Github 代理')}</FormLabel>
<FormDescription>{t('若开启,则使用后台配置的代理地址下载')}</FormDescription>
</div>
<FormControl>
<Switch checked={field.value} onCheckedChange={field.onChange} />
</FormControl>
</FormItem>
)}
/>
{/* 自定义 GitHub Proxy URL */}
<FormField
control={form.control}
name="githubProxyUrl"
render={({ field }) => (
<FormItem>
<FormLabel>{t('自定义 Github 代理地址')}</FormLabel>
<FormControl>
<Input placeholder={platformInfo?.githubProxyUrl || t('例如 https://ghproxy.com/')} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* 自定义 API URL */}
<FormField
control={form.control}
name="clientApiUrl"
render={({ field }) => (
<FormItem>
<FormLabel>{t('自定义 API URL')}</FormLabel>
<FormControl>
<Input placeholder={platformInfo?.clientApiUrl || t('例如 https://api.example.com/')} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* 自定义 RPC URL */}
<FormField
control={form.control}
name="clientRpcUrl"
render={({ field }) => (
<FormItem>
<FormLabel>{t('自定义 RPC URL')}</FormLabel>
<FormControl>
<Input placeholder={platformInfo?.clientRpcUrl || t('例如 https://rpc.example.com/')} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</CardContent>
<CardFooter className="flex justify-end">
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button
disabled={
form.formState.isSubmitting ||
JSON.stringify(form.getValues()) === JSON.stringify(initial)
}
onClick={() => setOpen(true)}
>
{t('保存更改')}
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>{t('确认保存')}</DialogTitle>
<DialogDescription>{t('确定保存修改?')}</DialogDescription>
</DialogHeader>
<DialogFooterUI>
<Button variant={'destructive'} onClick={() => setOpen(false)}>
{t('取消')}
</Button>
<Button
variant={'secondary'}
onClick={() => {
form.handleSubmit(onSubmit)()
setOpen(false)
}}
>
{t('确认')}
</Button>
</DialogFooterUI>
</DialogContent>
</Dialog>
</CardFooter>
</Card>
)
}

View File

@@ -37,16 +37,16 @@ export const getNavItems = (t: any) => [
url: "/proxies",
icon: Cable,
},
{
title: t('nav.editClient'),
url: "/clientedit",
icon: MonitorCogIcon,
},
{
title: t('nav.editServer'),
url: "/serveredit",
icon: ServerCogIcon,
},
// {
// title: t('nav.editClient'),
// url: "/clientedit",
// icon: MonitorCogIcon,
// },
// {
// title: t('nav.editServer'),
// url: "/serveredit",
// icon: ServerCogIcon,
// },
{
title: t('nav.trafficStats'),
url: "/clientstats",

View File

@@ -625,5 +625,23 @@
"save": "Save",
"save_success": "Save successful",
"save_error": "Save failed"
}
},
"平台设置": "Platform Settings",
"修改前端平台设置": "Modify frontend preferences",
"已更新平台设置": "Platform settings updated",
"使用服务器 Github 代理": "Use server GitHub proxy",
"若开启,则使用后台配置的代理地址下载": "If enabled, backend proxy URL will be used for downloads",
"自定义 Github 代理地址": "Custom GitHub proxy URL",
"例如 https://ghproxy.com/": "e.g. https://ghproxy.com/",
"保存更改": "Save changes",
"确认保存": "Confirm save",
"确定保存修改?": "Are you sure to save changes?",
"确认": "Confirm",
"正在加载平台设置": "Loading platform settings...",
"取消": "Cancel",
"自定义 API URL": "Custom API URL",
"自定义 RPC URL": "Custom RPC URL",
"例如 https://api.example.com/": "e.g. https://api.example.com/",
"例如 https://rpc.example.com/": "e.g. https://rpc.example.com/",
"此配置仅保存在本地": "These settings are stored only in this browser and will not sync across devices."
}

View File

@@ -625,5 +625,23 @@
"save": "Enregistrer",
"save_success": "Enregistrement réussi",
"save_error": "Échec de l'enregistrement"
}
},
"平台设置": "Paramètres de la plateforme",
"修改前端平台设置": "Modifier les préférences du frontal",
"已更新平台设置": "Paramètres de la plateforme mis à jour",
"使用服务器 Github 代理": "Utiliser le proxy GitHub du serveur",
"若开启,则使用后台配置的代理地址下载": "Si activé, l'URL proxy définie côté serveur sera utilisée pour le téléchargement",
"自定义 Github 代理地址": "URL proxy GitHub personnalisée",
"例如 https://ghproxy.com/": "ex. https://ghproxy.com/",
"保存更改": "Enregistrer les modifications",
"确认保存": "Confirmer l'enregistrement",
"确定保存修改?": "Êtes-vous sûr de vouloir enregistrer les modifications ?",
"确认": "Confirmer",
"正在加载平台设置": "Chargement des paramètres de la plateforme...",
"取消": "Annuler",
"自定义 API URL": "URL API personnalisée",
"自定义 RPC URL": "URL RPC personnalisée",
"例如 https://api.example.com/": "ex. https://api.example.com/",
"例如 https://rpc.example.com/": "ex. https://rpc.example.com/",
"此配置仅保存在本地": "Ces paramètres sont enregistrés uniquement dans ce navigateur et ne seront pas synchronisés entre les appareils."
}

View File

@@ -623,5 +623,23 @@
"save": "保存",
"save_success": "保存成功",
"save_error": "保存失败"
}
},
"平台设置": "平台设置",
"修改前端平台设置": "修改前端平台设置",
"已更新平台设置": "已更新平台设置",
"使用服务器 Github 代理": "使用服务器 Github 代理",
"若开启,则使用后台配置的代理地址下载": "若开启,则使用后台配置的代理地址下载",
"自定义 Github 代理地址": "自定义 Github 代理地址",
"例如 https://ghproxy.com/": "例如 https://ghproxy.com/",
"保存更改": "保存更改",
"确认保存": "确认保存",
"确定保存修改?": "确定保存修改?",
"确认": "确认",
"正在加载平台设置": "正在加载平台设置",
"取消": "取消",
"自定义 API URL": "自定义 API URL",
"自定义 RPC URL": "自定义 RPC URL",
"例如 https://api.example.com/": "例如 https://api.example.com/",
"例如 https://rpc.example.com/": "例如 https://rpc.example.com/",
"此配置仅保存在本地": "该设置仅保存在当前浏览器,不会同步到其他设备"
}

View File

@@ -1,4 +1,5 @@
{
"平台设置": "平台設置",
"app": {
"title": "FRP-Panel",
"subtitle": "FRP 隧道面板",
@@ -623,5 +624,22 @@
"save": "保存",
"save_success": "保存成功",
"save_error": "保存失敗"
}
},
"修改前端平台设置": "修改前端平台設定",
"已更新平台设置": "平台設定已更新",
"使用服务器 Github 代理": "使用伺服器 GitHub 代理",
"若开启,则使用后台配置的代理地址下载": "若開啟,將使用後端配置的代理位址下載",
"自定义 Github 代理地址": "自訂 GitHub 代理位址",
"例如 https://ghproxy.com/": "例如 https://ghproxy.com/",
"保存更改": "儲存變更",
"确认保存": "確認保存",
"确定保存修改?": "確定保存變更?",
"确认": "確認",
"正在加载平台设置": "正在載入平台設定...",
"取消": "取消",
"自定义 API URL": "自訂 API URL",
"自定义 RPC URL": "自訂 RPC URL",
"例如 https://api.example.com/": "例如 https://api.example.com/",
"例如 https://rpc.example.com/": "例如 https://rpc.example.com/",
"此配置仅保存在本地": "此設定僅儲存在此瀏覽器中,不會在裝置間同步。"
}

View File

@@ -3,6 +3,28 @@ import { Client, Server } from './pb/common'
import { GetPlatformInfoResponse } from './pb/api_user'
import { TypedProxyConfig } from '@/types/proxy'
// 延迟加载前端首选项,避免 SSR 期间引用 window/localStorage
type FrontendPreferenceLazy = {
githubProxyUrl?: string
useServerGithubProxyUrl?: boolean
clientApiUrl?: string
clientRpcUrl?: string
}
const getFrontendPreference = (): FrontendPreferenceLazy => {
if (typeof window !== 'undefined') {
try {
// 动态引入避免打包时静态依赖
// eslint-disable-next-line
const { $frontendPreference } = require('@/store/user')
return ($frontendPreference.get?.() ?? {}) as FrontendPreferenceLazy
} catch {
return {}
}
}
return {}
}
export const API_PATH = '/api/v1'
export const SET_TOKEN_HEADER = 'x-set-authorization'
export const X_CLIENT_REQUEST_ID = 'x-client-request-id'
@@ -77,17 +99,44 @@ export const ClientConfigured = (client: Client | undefined): boolean => {
// .refine((e) => e === "abcd@fg.com", "This email is not in our database")
// 获取最终 Github 代理 URL
const getGithubProxyUrl = (info: GetPlatformInfoResponse, applyPref = true): string => {
const pref = getFrontendPreference()
if (applyPref && pref.useServerGithubProxyUrl === false && pref.githubProxyUrl) {
return pref.githubProxyUrl
}
// 若前端未指定或选择使用服务器,返回后端
return info.githubProxyUrl
}
// 获取最终 API URL
const getClientApiUrl = (info: GetPlatformInfoResponse, applyPref = true): string => {
const pref = getFrontendPreference()
return applyPref && pref.clientApiUrl?.trim() ? pref.clientApiUrl.trim() : info.clientApiUrl
}
// 获取最终 RPC URL
const getClientRpcUrl = (info: GetPlatformInfoResponse, applyPref = true): string => {
const pref = getFrontendPreference()
return applyPref && pref.clientRpcUrl?.trim() ? pref.clientRpcUrl.trim() : info.clientRpcUrl
}
export const ExecCommandStr = <T extends Client | Server>(
type: 'client' | 'server',
item: T,
info: GetPlatformInfoResponse,
fileName?: string,
applyPref = true,
) => {
return `${fileName || 'frp-panel'} ${type} -s ${item.secret} -i ${item.id} --api-url ${info.clientApiUrl} --rpc-url ${info.clientRpcUrl}`
const apiUrl = getClientApiUrl(info, applyPref)
const rpcUrl = getClientRpcUrl(info, applyPref)
return `${fileName || 'frp-panel'} ${type} -s ${item.secret} -i ${item.id} --api-url ${apiUrl} --rpc-url ${rpcUrl}`
}
export const JoinCommandStr = (info: GetPlatformInfoResponse, token: string, fileName?: string, clientID?: string) => {
return `${fileName || 'frp-panel'} join${clientID ? ` -i ${clientID}` : ''} -j ${token} --api-url ${info.clientApiUrl} --rpc-url ${info.clientRpcUrl}`
export const JoinCommandStr = (info: GetPlatformInfoResponse, token: string, fileName?: string, clientID?: string, applyPref = true) => {
const apiUrl = getClientApiUrl(info, applyPref)
const rpcUrl = getClientRpcUrl(info, applyPref)
return `${fileName || 'frp-panel'} join${clientID ? ` -i ${clientID}` : ''} -j ${token} --api-url ${apiUrl} --rpc-url ${rpcUrl}`
}
export const WindowsInstallCommand = <T extends Client | Server>(
@@ -95,15 +144,17 @@ export const WindowsInstallCommand = <T extends Client | Server>(
item: T,
info: GetPlatformInfoResponse,
github_proxy?: boolean,
applyPref = true,
) => {
const proxyUrl = getGithubProxyUrl(info, applyPref)
return (
`[Net.ServicePointManager]::SecurityProtocol = ` +
`[Net.SecurityProtocolType]::Ssl3 -bor ` +
`[Net.SecurityProtocolType]::Tls -bor ` +
`[Net.SecurityProtocolType]::Tls11 -bor ` +
`[Net.SecurityProtocolType]::Tls12;set-ExecutionPolicy RemoteSigned;` +
`Invoke-WebRequest ${github_proxy ? info.githubProxyUrl : ''}https://raw.githubusercontent.com/VaalaCat/frp-panel/main/install.ps1 ` +
`-OutFile C:\install.ps1;powershell.exe C:\install.ps1 ${ExecCommandStr(type, item, info, ' ')}`
`Invoke-WebRequest ${github_proxy ? proxyUrl : ''}https://raw.githubusercontent.com/VaalaCat/frp-panel/main/install.ps1 ` +
`-OutFile C:\install.ps1;powershell.exe C:\install.ps1 ${ExecCommandStr(type, item, info, ' ', applyPref)}`
)
}
@@ -112,16 +163,18 @@ export const LinuxInstallCommand = <T extends Client | Server>(
item: T,
info: GetPlatformInfoResponse,
github_proxy?: boolean,
applyPref = true,
) => {
return `curl -fSL ${github_proxy ? info.githubProxyUrl : ''
}https://raw.githubusercontent.com/VaalaCat/frp-panel/main/install.sh | bash -s -- ${
github_proxy ? `--github-proxy ${info.githubProxyUrl}` : ''
}${ExecCommandStr(type, item, info, ' ')}`
const proxyUrl = getGithubProxyUrl(info, applyPref)
return `curl -fSL ${github_proxy ? proxyUrl : ''}https://raw.githubusercontent.com/VaalaCat/frp-panel/main/install.sh | bash -s -- ${github_proxy ? `--github-proxy ${proxyUrl}` : ''
}${ExecCommandStr(type, item, info, ' ', applyPref)}`
}
export const ClientEnvFile = <T extends Client | Server>(item: T, info: GetPlatformInfoResponse) => {
export const ClientEnvFile = <T extends Client | Server>(item: T, info: GetPlatformInfoResponse, applyPref = true) => {
const apiUrl = getClientApiUrl(info, applyPref)
const rpcUrl = getClientRpcUrl(info, applyPref)
return `CLIENT_ID=${item.id}
CLIENT_SECRET=${item.secret}
CLIENT_API_URL=${info.clientApiUrl}
CLIENT_RPC_URL=${info.clientRpcUrl}`
CLIENT_API_URL=${apiUrl}
CLIENT_RPC_URL=${rpcUrl}`
}

View File

@@ -0,0 +1,16 @@
import { Providers } from '@/components/providers'
import { RootLayout } from '@/components/layout'
import { Header } from '@/components/header'
import { PlatformSettingsForm } from '@/components/platform/settings'
export default function PlatformSettingsPage() {
return (
<Providers>
<RootLayout mainHeader={<Header />}>
<div className="w-full py-4">
<PlatformSettingsForm />
</div>
</RootLayout>
</Providers>
)
}

View File

@@ -3,7 +3,7 @@ import { RootLayout } from '@/components/layout'
import { Header } from '@/components/header'
import { UserProfileForm } from '@/components/user/user-info'
export default function ClientListPage() {
export default function UserInfoPage() {
return (
<Providers>
<RootLayout mainHeader={<Header />}>

View File

@@ -15,13 +15,11 @@ export const $language = persistentAtom<string>('user-language', 'zh', {
decode: JSON.parse,
})
export const $useServerGithubProxyUrl = persistentAtom<boolean>('use_server_github_proxy_url', false, {
encode: JSON.stringify,
decode: JSON.parse,
})
export type FrontendPreference = {
export type FrontendPreference = {
githubProxyUrl?: string
useServerGithubProxyUrl?: boolean
clientApiUrl?: string
clientRpcUrl?: string
}
export const $frontendPreference = persistentAtom<FrontendPreference>('frontend_preference', {}, {