feat: M1 高级传输参数(P0)

- store: Local 增加 useEncryption/useCompression/bandwidthLimit/poolCount/metadatas/annotations;Remote 增加 transportProtocol/transportTls/transportPool/transportTlsServerName;新增 migrate() ALTER TABLE 迁移旧库

- render: 输出 transport 段(protocol/tls/poolCount)与 proxy 高级字段;补 TestRenderTransportAdvanced/OmittedWhenEmpty

- main: renderRemote 闭包透传 M1 字段到 render.Proxy

- web: LocalNode/RemoteNode 折叠高级区表单,types.ts 接口扩展

- 验证:go test 全绿、vue-tsc/build 通过、端到端 PUT canvas→frpc JSON 输出完整
This commit is contained in:
2026-08-17 12:28:54 +08:00
parent e73bd834a1
commit 07f25ea067
11 changed files with 460 additions and 62 deletions

View File

@ -28,12 +28,32 @@
</select>
</label>
</div>
<div class="adv">
<button class="adv-toggle" @click.stop="showAdv = !showAdv">
{{ showAdv ? ' 收起高级' : ' 高级' }}
</button>
<div v-if="showAdv" class="adv-body">
<label class="adv-check">
<input v-model="useEncryptionField" type="checkbox" /> 加密
(useEncryption)
</label>
<label class="adv-check">
<input v-model="useCompressionField" type="checkbox" /> 压缩
(useCompression)
</label>
<label>限速 <input v-model="bandwidthLimitField" placeholder="如 1MB" /></label>
<label>连接池 <input v-model.number="poolCountField" type="number" min="0" /></label>
<label>元数据(JSON) <input v-model="metadatasField" placeholder='{"env":"prod"}' /></label>
<label>注解(JSON) <input v-model="annotationsField" placeholder='{"owner":"ops"}' /></label>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { Handle, Position } from '@vue-flow/core'
import { computed } from 'vue'
import { computed, ref } from 'vue'
import type { CanvasLocal } from '../types'
const props = defineProps<{
@ -59,6 +79,41 @@ const nameField = field('name')
const ipField = field('ip')
const portField = field('port')
const protocolField = field('protocol')
const useEncryptionField = field('useEncryption')
const useCompressionField = field('useCompression')
const bandwidthLimitField = field('bandwidthLimit')
const poolCountField = field('poolCount')
// map fields shown as JSON strings in the UI
const showAdv = ref(false)
const metadatasField = computed({
get: () => JSON.stringify(props.data.metadatas ?? {}),
set: (v: string) => {
let parsed: Record<string, string> = {}
if (v.trim()) {
try {
parsed = JSON.parse(v)
} catch {
return // keep old value on invalid JSON
}
}
emit('update:data', { ...props.data, metadatas: parsed })
},
})
const annotationsField = computed({
get: () => JSON.stringify(props.data.annotations ?? {}),
set: (v: string) => {
let parsed: Record<string, string> = {}
if (v.trim()) {
try {
parsed = JSON.parse(v)
} catch {
return
}
}
emit('update:data', { ...props.data, annotations: parsed })
},
})
</script>
<style scoped lang="scss">
@ -162,5 +217,54 @@ const protocolField = field('protocol')
flex: 0 0 64px;
}
}
.adv {
margin-top: 6px;
.adv-toggle {
border: 1px dashed #a5d6a7;
background: transparent;
color: #558b2f;
font-size: 11px;
border-radius: 6px;
padding: 2px 8px;
cursor: pointer;
}
.adv-body {
display: flex;
flex-direction: column;
gap: 4px;
margin-top: 6px;
padding: 6px;
background: rgba(76, 175, 80, 0.06);
border-radius: 8px;
label {
display: flex;
align-items: center;
gap: 4px;
font-size: 11px;
color: #558b2f;
input,
select {
flex: 1;
min-width: 0;
border: 1px solid #a5d6a7;
border-radius: 6px;
padding: 2px 6px;
font-size: 12px;
background: #fff;
color: #333;
&:focus {
outline: none;
border-color: #4caf50;
}
}
}
.adv-check {
font-size: 11px;
}
}
}
}
</style>