mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-07 08:06:37 +08:00
feat(relay): support configurable frps webui port and fix node detail integration
- Implement configurable FRPS WebUI switch and custom port setting (relay_frps_web_ui_port) in system configs. - Integrate settings into Relay Node detail manage page instead of global settings. - Dynamically query server version to select matching Docker image tag for Relay installation. - Clean up legacy code and fix backend linter/test warnings.
This commit is contained in:
@@ -2,14 +2,15 @@
|
||||
|
||||
import Link from 'next/link';
|
||||
import {useRouter} from 'next/navigation';
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query';
|
||||
import {useState} from 'react';
|
||||
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query';
|
||||
import {useEffect, useMemo, useState} from 'react';
|
||||
import {
|
||||
Activity,
|
||||
Copy,
|
||||
ExternalLink,
|
||||
FileText,
|
||||
Fingerprint,
|
||||
Loader2,
|
||||
Network,
|
||||
RefreshCw,
|
||||
RotateCcw,
|
||||
@@ -30,11 +31,14 @@ import {
|
||||
AlertDialogTitle,
|
||||
} from '@/components/ui/alert-dialog';
|
||||
import {Button} from '@/components/ui/button';
|
||||
import {Input} from '@/components/ui/input';
|
||||
import {Label} from '@/components/ui/label';
|
||||
import {Switch} from '@/components/ui/switch';
|
||||
import {formatDateTime} from '@/lib/utils';
|
||||
import type {NodeAgentReleaseInfo, NodeItem, ReleaseChannel} from '@/lib/services/openflare';
|
||||
import {NodeService} from '@/lib/services/openflare';
|
||||
import services from '@/lib/services';
|
||||
import type {SystemConfig} from '@/lib/services/admin';
|
||||
|
||||
import {AgentUpdateDialog} from './agent-update-dialog';
|
||||
import {InstallCommand} from './install-command';
|
||||
@@ -58,6 +62,13 @@ import {
|
||||
|
||||
const nodesQueryKey = ['openflare', 'nodes'];
|
||||
|
||||
function systemConfigMap(configs: SystemConfig[]) {
|
||||
return configs.reduce<Record<string, SystemConfig>>((accumulator, config) => {
|
||||
accumulator[config.key] = config;
|
||||
return accumulator;
|
||||
}, {});
|
||||
}
|
||||
|
||||
export function RelayNodeDetail({ node }: { node: NodeItem }) {
|
||||
const router = useRouter();
|
||||
const queryClient = useQueryClient();
|
||||
@@ -65,6 +76,43 @@ export function RelayNodeDetail({ node }: { node: NodeItem }) {
|
||||
const [upgradeOpen, setUpgradeOpen] = useState(false);
|
||||
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||
|
||||
const [webServerPort, setWebServerPort] = useState('17500');
|
||||
|
||||
const systemConfigsQuery = useQuery({
|
||||
queryKey: ['admin', 'system-configs'],
|
||||
queryFn: () => services.adminSystemConfig.listSystemConfigs(),
|
||||
});
|
||||
|
||||
const configs = useMemo(
|
||||
() => systemConfigMap(systemConfigsQuery.data ?? []),
|
||||
[systemConfigsQuery.data]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (systemConfigsQuery.data) {
|
||||
setWebServerPort(configs['relay_frps_web_ui_port']?.value || '17500');
|
||||
}
|
||||
}, [systemConfigsQuery.data, configs]);
|
||||
|
||||
const savePortMutation = useMutation({
|
||||
mutationFn: async (port: string) => {
|
||||
const portNum = parseInt(port, 10);
|
||||
if (isNaN(portNum) || portNum < 1 || portNum > 65535) {
|
||||
throw new Error('端口号必须在 1 ~ 65535 范围内');
|
||||
}
|
||||
const portCfg = configs['relay_frps_web_ui_port'];
|
||||
await services.adminSystemConfig.updateSystemConfig('relay_frps_web_ui_port', {
|
||||
value: String(portNum),
|
||||
description: portCfg?.description || 'FRPS 内置 Web 管理界面端口',
|
||||
});
|
||||
},
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: ['admin', 'system-configs'] });
|
||||
toast.success('FRPS WebUI 端口配置已更新');
|
||||
},
|
||||
onError: (error) => toast.error(error instanceof Error ? error.message : '更新端口失败'),
|
||||
});
|
||||
|
||||
const saveMutation = useMutation({
|
||||
mutationFn: (payload: Parameters<typeof NodeService.updateNode>[1]) =>
|
||||
NodeService.updateNode(node.id, payload),
|
||||
@@ -147,9 +195,14 @@ export function RelayNodeDetail({ node }: { node: NodeItem }) {
|
||||
]);
|
||||
};
|
||||
|
||||
const webServerPortNum = parseInt(configs['relay_frps_web_ui_port']?.value || '', 10);
|
||||
const resolvedPort = !isNaN(webServerPortNum) && webServerPortNum > 0
|
||||
? webServerPortNum
|
||||
: (node.relay_bind_port || 7000) + 500;
|
||||
|
||||
const webUiUrl =
|
||||
node.relay_web_server_enabled && node.relay_bind_port
|
||||
? `http://${node.ip || '127.0.0.1'}:${node.relay_bind_port + 500}`
|
||||
? `http://${node.ip || '127.0.0.1'}:${resolvedPort}`
|
||||
: null;
|
||||
|
||||
const headerActions = (
|
||||
@@ -257,14 +310,13 @@ export function RelayNodeDetail({ node }: { node: NodeItem }) {
|
||||
|
||||
const manageTab = (
|
||||
<div className="space-y-6">
|
||||
<NodeSectionCard title="FRPS WebUI" description="控制 frps 内置 Web 管理界面是否启用">
|
||||
<NodeSectionCard title="FRPS WebUI" description="控制 frps 内置 Web 管理界面是否启用及其监听端口">
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between rounded-xl border px-4 py-4">
|
||||
<div className="space-y-1">
|
||||
<Label>启用 Web 管理界面</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
默认监听绑定端口 + 500,例如 {node.relay_bind_port || 7000} →{' '}
|
||||
{(node.relay_bind_port || 7000) + 500}
|
||||
开启后,节点将启动 frps 的内置管理服务。
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
@@ -274,6 +326,38 @@ export function RelayNodeDetail({ node }: { node: NodeItem }) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border p-4 space-y-3">
|
||||
<Label htmlFor="web_server_port" className="text-xs font-semibold">Web 管理界面端口</Label>
|
||||
<div className="flex max-w-sm items-center gap-2">
|
||||
<Input
|
||||
id="web_server_port"
|
||||
type="number"
|
||||
min={1}
|
||||
max={65535}
|
||||
value={webServerPort}
|
||||
onChange={(e) => setWebServerPort(e.target.value)}
|
||||
placeholder="例如: 17500"
|
||||
className="bg-card text-xs h-9"
|
||||
disabled={savePortMutation.isPending || systemConfigsQuery.isLoading}
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
className="h-9"
|
||||
onClick={() => savePortMutation.mutate(webServerPort)}
|
||||
disabled={savePortMutation.isPending || systemConfigsQuery.isLoading}
|
||||
>
|
||||
{savePortMutation.isPending ? (
|
||||
<Loader2 className="size-3.5 animate-spin" />
|
||||
) : (
|
||||
'保存'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-[11px] text-muted-foreground">
|
||||
当前实际监听端口:{resolvedPort} (修改后在下次节点心跳同步时生效,默认为绑定端口 + 500)。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{webUiUrl ? (
|
||||
<div className="flex flex-wrap items-center gap-3 text-sm">
|
||||
<a
|
||||
|
||||
Reference in New Issue
Block a user