mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-12 18:26:36 +08:00
feat(frontend): 控制台 next-intl 中英双语
接入无 URL 前缀的 zh-CN/en,顶栏与外观设置可切换语言;选择写入 cookie 后刷新生效。
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Copy } from 'lucide-react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -28,38 +29,6 @@ import {
|
||||
|
||||
type InstallVariant = 'edge' | 'relay' | 'tunnel';
|
||||
|
||||
const variantMeta: Record<
|
||||
InstallVariant,
|
||||
{
|
||||
title: string;
|
||||
description: string;
|
||||
tokenLabel: string;
|
||||
scriptLabel?: string;
|
||||
dockerLabel: string;
|
||||
}
|
||||
> = {
|
||||
edge: {
|
||||
title: '边缘节点部署',
|
||||
description: '使用 Agent Token 将边缘节点接入控制端。',
|
||||
tokenLabel: 'Agent Token',
|
||||
dockerLabel: 'Docker 容器部署',
|
||||
},
|
||||
relay: {
|
||||
title: '中继部署命令',
|
||||
description: '使用 Discovery Token 将 frps 中继节点接入控制端。',
|
||||
tokenLabel: 'Discovery Token',
|
||||
scriptLabel: '一键脚本部署 (Linux / macOS)',
|
||||
dockerLabel: 'Docker 容器部署',
|
||||
},
|
||||
tunnel: {
|
||||
title: '隧道部署命令',
|
||||
description: '使用 Tunnel Token 将 openflared 客户端接入控制端。',
|
||||
tokenLabel: 'Tunnel Token',
|
||||
scriptLabel: '一键脚本部署 (Linux / macOS)',
|
||||
dockerLabel: 'Docker 容器部署',
|
||||
},
|
||||
};
|
||||
|
||||
export function InstallCommand({
|
||||
node,
|
||||
variant,
|
||||
@@ -67,7 +36,40 @@ export function InstallCommand({
|
||||
node: NodeItem;
|
||||
variant: InstallVariant;
|
||||
}) {
|
||||
const t = useTranslations('nodes.install');
|
||||
const tn = useTranslations('nodes');
|
||||
const [serverUrl, setServerUrl] = useState('');
|
||||
const variantMeta: Record<
|
||||
InstallVariant,
|
||||
{
|
||||
title: string;
|
||||
description: string;
|
||||
tokenLabel: string;
|
||||
scriptLabel?: string;
|
||||
dockerLabel: string;
|
||||
}
|
||||
> = {
|
||||
edge: {
|
||||
title: t('edgeTitle'),
|
||||
description: t('edgeDesc'),
|
||||
tokenLabel: t('agentToken'),
|
||||
dockerLabel: t('dockerLabel'),
|
||||
},
|
||||
relay: {
|
||||
title: t('relayTitle'),
|
||||
description: t('relayDesc'),
|
||||
tokenLabel: t('discoveryToken'),
|
||||
scriptLabel: t('scriptLabel'),
|
||||
dockerLabel: t('dockerLabel'),
|
||||
},
|
||||
tunnel: {
|
||||
title: t('tunnelTitle'),
|
||||
description: t('tunnelDesc'),
|
||||
tokenLabel: t('tunnelToken'),
|
||||
scriptLabel: t('scriptLabel'),
|
||||
dockerLabel: t('dockerLabel'),
|
||||
},
|
||||
};
|
||||
const meta = variantMeta[variant];
|
||||
|
||||
const statusQuery = useQuery({
|
||||
@@ -123,7 +125,7 @@ export function InstallCommand({
|
||||
await navigator.clipboard.writeText(value);
|
||||
toast.success(message);
|
||||
} catch {
|
||||
toast.error('复制失败,请手动选择命令文本。');
|
||||
toast.error(t('copyFailed'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -143,11 +145,11 @@ export function InstallCommand({
|
||||
size='sm'
|
||||
className='h-7 text-xs'
|
||||
onClick={() =>
|
||||
void handleCopy(scriptCommand, '部署脚本命令已复制')
|
||||
void handleCopy(scriptCommand, t('copiedScript'))
|
||||
}
|
||||
>
|
||||
<Copy className='size-3.5 mr-1' />
|
||||
复制脚本
|
||||
{t('copyScript')}
|
||||
</Button>
|
||||
) : null}
|
||||
{dockerCommand ? (
|
||||
@@ -156,11 +158,11 @@ export function InstallCommand({
|
||||
size='sm'
|
||||
className='h-7 text-xs'
|
||||
onClick={() =>
|
||||
void handleCopy(dockerCommand, 'Docker 部署命令已复制')
|
||||
void handleCopy(dockerCommand, t('copiedDocker'))
|
||||
}
|
||||
>
|
||||
<Copy className='size-3.5 mr-1' />
|
||||
复制 Docker
|
||||
{t('copyDocker')}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -168,20 +170,20 @@ export function InstallCommand({
|
||||
<CardContent className='space-y-4'>
|
||||
<div className='grid gap-4 md:grid-cols-2'>
|
||||
<div className='rounded-lg border px-3 py-3'>
|
||||
<p className='text-xs text-muted-foreground'>Node ID</p>
|
||||
<p className='text-xs text-muted-foreground'>{t('nodeId')}</p>
|
||||
<p className='mt-1 text-sm break-all font-medium'>{node.node_id}</p>
|
||||
</div>
|
||||
<div className='rounded-lg border px-3 py-3'>
|
||||
<p className='text-xs text-muted-foreground'>{meta.tokenLabel}</p>
|
||||
<p className='mt-1 text-sm break-all font-medium'>
|
||||
{node.access_token || '暂无'}
|
||||
{node.access_token || tn('na')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='space-y-2'>
|
||||
<Label htmlFor={`server-url-${variant}`}>
|
||||
Server URL(控制端地址)
|
||||
{t('serverUrl')}
|
||||
</Label>
|
||||
<Input
|
||||
id={`server-url-${variant}`}
|
||||
@@ -190,17 +192,17 @@ export function InstallCommand({
|
||||
placeholder='https://openflare.example.com'
|
||||
/>
|
||||
<p className='text-xs text-muted-foreground'>
|
||||
部署脚本和容器将使用此地址连接 OpenFlare 控制端。
|
||||
{t('serverUrlHint')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{!node.access_token ? (
|
||||
<p className='text-sm text-muted-foreground'>
|
||||
节点尚未生成接入 Token,请稍后刷新。
|
||||
{t('noToken')}
|
||||
</p>
|
||||
) : !normalizedServerUrl ? (
|
||||
<p className='text-sm text-muted-foreground'>
|
||||
请填写有效的 Server URL 以生成部署命令。
|
||||
{t('needServerUrl')}
|
||||
</p>
|
||||
) : (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user