[优化] 添加节点 IP 手动覆盖功能,更新相关文档和测试用例

This commit is contained in:
ryan
2026-05-31 14:09:53 +08:00
parent 8894620b92
commit 4ae6c2718f
20 changed files with 351 additions and 7 deletions
@@ -1654,7 +1654,10 @@ export function NodeDetailPage({ nodeId }: { nodeId: string }) {
<p className="font-medium text-[var(--foreground-primary)]">
IP 地址
</p>
<p className="mt-1">{node.ip || '暂无'}</p>
<p className="mt-1">
{node.ip || '暂无'}
{node.ip_manual_override ? '(管理端锁定)' : ''}
</p>
</div>
<div>
<p className="font-medium text-[var(--foreground-primary)]">
@@ -43,6 +43,7 @@ const nodeEditorSchema = z
.min(1, '请输入节点名')
.max(128, '节点名不能超过 128 个字符'),
ip: z.string().trim().max(64, '节点 IP 不能超过 64 个字符'),
ip_manual_override: z.boolean(),
auto_update_enabled: z.boolean(),
geo_manual_override: z.boolean(),
geo_region: z.string(),
@@ -51,6 +52,14 @@ const nodeEditorSchema = z
geo_longitude: z.string().trim(),
})
.superRefine((values, ctx) => {
if (values.ip_manual_override && values.ip.trim() === '') {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ['ip'],
message: '锁定节点 IP 时必须填写节点 IP',
});
}
if (!values.geo_manual_override) {
return;
}
@@ -100,6 +109,7 @@ type NodeEditorValues = z.infer<typeof nodeEditorSchema>;
const defaultValues: NodeEditorValues = {
name: '',
ip: '',
ip_manual_override: false,
auto_update_enabled: false,
geo_manual_override: false,
geo_region: '',
@@ -184,6 +194,7 @@ function buildFormValues(node?: Partial<NodeItem> | null): NodeEditorValues {
return {
name: node.name ?? '',
ip: node.ip ?? '',
ip_manual_override: node.ip_manual_override ?? false,
auto_update_enabled: node.auto_update_enabled ?? false,
geo_manual_override: node.geo_manual_override ?? false,
geo_region: node.geo_manual_override ? (node.geo_name ?? '') : '',
@@ -204,6 +215,7 @@ function toPayload(values: NodeEditorValues): NodeMutationPayload {
return {
name: values.name.trim(),
ip: values.ip.trim(),
ip_manual_override: values.ip_manual_override,
auto_update_enabled: values.auto_update_enabled,
geo_manual_override: false,
geo_name: '',
@@ -215,6 +227,7 @@ function toPayload(values: NodeEditorValues): NodeMutationPayload {
return {
name: values.name.trim(),
ip: values.ip.trim(),
ip_manual_override: values.ip_manual_override,
auto_update_enabled: values.auto_update_enabled,
geo_manual_override: true,
geo_name: values.geo_name.trim(),
@@ -253,6 +266,10 @@ export function NodeEditorModal({
control: form.control,
name: 'auto_update_enabled',
});
const watchedIPManualOverride = useWatch({
control: form.control,
name: 'ip_manual_override',
});
const watchedGeoManualOverride = useWatch({
control: form.control,
name: 'geo_manual_override',
@@ -266,6 +283,7 @@ export function NodeEditorModal({
form.reset(buildFormValues(node));
}, [form, isOpen, node]);
const ipField = form.register('ip');
const handleSubmit = form.handleSubmit((values) => {
onSubmit(toPayload(values));
});
@@ -309,12 +327,42 @@ export function NodeEditorModal({
<ResourceField
label="节点 IP"
hint="可手动维护节点当前对外 IP;留空则等待 Agent 注册或心跳自动回填。"
hint={
watchedIPManualOverride
? '已锁定为指定值,不会被更新。'
: '留空或关闭锁定时,等待 Agent 自动更新。'
}
error={form.formState.errors.ip?.message}
>
<ResourceInput placeholder="203.0.113.10" {...form.register('ip')} />
<ResourceInput
placeholder="203.0.113.10"
{...ipField}
onChange={(event) => {
ipField.onChange(event);
const nextIP = event.target.value.trim();
const previousIP = (node?.ip ?? '').trim();
if (nextIP !== '' && nextIP !== previousIP) {
form.setValue('ip_manual_override', true, {
shouldDirty: true,
shouldValidate: true,
});
}
}}
/>
</ResourceField>
<ToggleField
label="锁定节点 IP"
description="开启后以管理端填写的 IP 为准,Agent 后续上报不会覆盖该字段。"
checked={watchedIPManualOverride}
onChange={(checked) =>
form.setValue('ip_manual_override', checked, {
shouldDirty: true,
shouldValidate: true,
})
}
/>
<ToggleField
label="启用自动更新"
description="开启后 Agent 心跳返回会提示节点自动执行自更新。"
@@ -309,6 +309,7 @@ export function NodesPage() {
</p>
<p className="text-xs text-[var(--foreground-secondary)]">
IP:{node.ip || 'null'}
{node.ip_manual_override ? '(已锁定)' : ''}
</p>
<p className="text-xs text-[var(--foreground-secondary)]">
位置:{node.geo_name || '未配置地图点位'}
@@ -5,6 +5,7 @@ export interface NodeItem {
node_id: string;
name: string;
ip: string;
ip_manual_override: boolean;
geo_name: string;
geo_latitude?: number | null;
geo_longitude?: number | null;
@@ -41,6 +42,7 @@ export interface NodeBootstrapToken {
export interface NodeMutationPayload {
name: string;
ip: string;
ip_manual_override: boolean;
auto_update_enabled: boolean;
geo_name: string;
geo_latitude?: number | null;