fix(admin-frontend): 修复节点权限组保存与协议默认值

统一将节点编辑和批量修改的 group_ids、route_ids
序列化为字符串 ID,避免保存权限组后订阅侧无法命中节点

后端新增 whereGroupId 兼容历史字符串与数字 JSON 值,
并补齐 TUIC 版本、ALPN 选项及 AnyTLS 默认 Padding 配置

docs: 新增 HelloAGENTS 通用与工作流避坑指南
This commit is contained in:
yinjianm
2026-04-27 23:03:57 +08:00
parent c64badfc23
commit 30c2f655e7
16 changed files with 1096 additions and 33 deletions
+3 -3
View File
@@ -909,7 +909,7 @@ export interface AdminNodeBatchUpdatePayload {
ids: number[]
host?: string
rate?: number
group_ids?: number[]
group_ids?: string[]
}
export interface AdminNodeSavePayload {
@@ -917,8 +917,8 @@ export interface AdminNodeSavePayload {
type: AdminNodeType
code?: string
name: string
group_ids?: number[]
route_ids?: number[]
group_ids?: string[]
route_ids?: string[]
parent_id?: number | null
enabled?: boolean
host: string
+13 -2
View File
@@ -55,6 +55,14 @@ function toNumberArray(value: unknown): number[] {
)]
}
function toJsonIdArray(value: unknown[]): string[] {
return [...new Set(
value
.map((item) => String(item ?? '').trim())
.filter(Boolean),
)]
}
function splitMultiline(value: string): string[] {
return [...new Set(
value
@@ -154,6 +162,9 @@ export function toNodeFormModel(node?: AdminNodeItem | null): NodeFormModel {
: createEmptyNodeForm().rateTimeRanges
form.tags = toStringArray(node.tags)
form.groupIds = toNumberArray(node.group_ids)
if (form.groupIds.length === 0 && Array.isArray(node.groups)) {
form.groupIds = toNumberArray(node.groups.map((group) => group.id))
}
form.routeIds = toNumberArray(node.route_ids)
form.host = toStringValue(node.host)
form.port = toStringValue(node.port)
@@ -467,8 +478,8 @@ export function toNodeSavePayload(form: NodeFormModel): AdminNodeSavePayload {
type: form.type as AdminNodeType,
code: form.code.trim() || undefined,
name: form.name.trim(),
group_ids: [...new Set(form.groupIds)],
route_ids: [...new Set(form.routeIds)],
group_ids: toJsonIdArray(form.groupIds),
route_ids: toJsonIdArray(form.routeIds),
parent_id: form.parentId ?? undefined,
enabled: form.enabled,
host: form.host.trim(),
@@ -197,6 +197,17 @@ export const NODE_UDP_RELAY_MODE_OPTIONS: Array<NodeOption> = [
{ value: 'quic', label: 'quic' },
]
export const NODE_TUIC_VERSION_OPTIONS: Array<NodeOption<number>> = [
{ value: 5, label: 'V5' },
{ value: 4, label: 'V4' },
]
export const NODE_ALPN_OPTIONS: Array<NodeOption> = [
{ value: 'h3', label: 'HTTP/3' },
{ value: 'h2', label: 'HTTP/2' },
{ value: 'http/1.1', label: 'HTTP/1.1' },
]
export const NODE_MUX_PROTOCOL_OPTIONS: Array<NodeOption> = [
{ value: 'yamux', label: 'yamux' },
{ value: 'smux', label: 'smux' },
@@ -290,6 +301,11 @@ export function createEmptyNodeForm(): NodeFormModel {
'0=30-30',
'1=100-400',
'2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000',
'3=9-9,500-1000',
'4=500-1000',
'5=500-1000',
'6=500-1000',
'7=500-1000',
].join('\n'),
multiplexEnabled: false,
multiplexProtocol: 'yamux',
@@ -6,7 +6,7 @@ import type { AdminServerGroupItem } from '@/types/api'
interface NodeBatchEditPayload {
host?: string
rate?: number
group_ids?: number[]
group_ids?: string[]
}
const props = defineProps<{
@@ -64,7 +64,7 @@ function handleSubmit() {
emit('submit', {
host: form.updateHost ? form.host.trim() : undefined,
rate: form.updateRate ? Number(form.rate) : undefined,
group_ids: form.updateGroups ? [...form.groupIds] : undefined,
group_ids: form.updateGroups ? [...new Set(form.groupIds.map((item) => String(item)))] : undefined,
})
}
@@ -2,12 +2,14 @@
import { computed } from 'vue'
import {
getNodeProtocolHint,
NODE_ALPN_OPTIONS,
NODE_CONGESTION_CONTROL_OPTIONS,
NODE_MUX_PROTOCOL_OPTIONS,
NODE_SHADOWSOCKS_CIPHER_OPTIONS,
NODE_SHADOWSOCKS_OBFS_OPTIONS,
NODE_TCP_HEADER_OPTIONS,
NODE_TLS_FINGERPRINT_OPTIONS,
NODE_TUIC_VERSION_OPTIONS,
NODE_UDP_RELAY_MODE_OPTIONS,
NODE_VLESS_FLOW_OPTIONS,
shouldShowRealitySettings,
@@ -58,13 +60,6 @@ const currentProtocolHint = computed(() => getNodeProtocolHint(props.form.type))
</ElSelect>
</ElFormItem>
<ElFormItem
v-if="['hysteria', 'tuic', 'anytls'].includes(props.form.type)"
label="服务器名称(SNI"
>
<ElInput v-model="props.form.tlsServerName" placeholder="example.com" />
</ElFormItem>
<ElFormItem v-if="showTlsSection" label="服务器名称(SNI">
<ElInput v-model="props.form.tlsServerName" placeholder="example.com" />
</ElFormItem>
@@ -366,7 +361,14 @@ const currentProtocolHint = computed(() => getNodeProtocolHint(props.form.type))
<template v-else-if="props.form.type === 'tuic'">
<ElFormItem label="协议版本">
<ElInputNumber v-model="props.form.tuicVersion" :min="1" :controls="false" class="full-width" />
<ElSelect v-model="props.form.tuicVersion" placeholder="请选择版本">
<ElOption
v-for="option in NODE_TUIC_VERSION_OPTIONS"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</ElSelect>
</ElFormItem>
<ElFormItem label="拥塞控制">
<ElSelect v-model="props.form.tuicCongestionControl" placeholder="请选择拥塞控制">
@@ -396,7 +398,14 @@ const currentProtocolHint = computed(() => getNodeProtocolHint(props.form.type))
allow-create
default-first-option
placeholder="输入后回车添加 ALPN"
/>
>
<ElOption
v-for="option in NODE_ALPN_OPTIONS"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</ElSelect>
</ElFormItem>
</template>