fix(render,store): frpc user 支持 + 修正 useEncryption/useCompression 渲染位置

- store.Remote 新增 user 字段(DB 列名 usr,避开保留字;migrate() 自动加列),
  渲染为 frpc 全局 user。面板型 frps(如 ChhChongGou 实例)强制要求 user 登录,
  缺失时服务端返回 panel authentication unavailable: panel HTTP status 403
- ★ 修复渲染 bug:useEncryption/useCompression/bandwidthLimit 必须内嵌在 proxy 的
  transport 对象(frpc ProxyTransport)。此前渲染在 proxy 顶层,frpc 默认
  --strict-config 直接拒载:unknown field "useEncryption"。任何启用加密/压缩
  的转发都无法启动。poolCount 无 proxy 级表达(官方 ProxyTransport 无此字段),
  保留在 store 但不再渲染
- 前端:RemoteNode 画布节点与 StatusView 表单加 user 输入(画布此前会静默
  清掉未声明的字段),带『面板型 frps 必填』提示
- 测试:user 往返(含迁移路径)、渲染含/不含 user、transport 内嵌与顶层
  非法键回归守卫
This commit is contained in:
JianFeeeee
2026-10-12 19:16:28 +08:00
parent b29cd0d186
commit d83e5d8e70
7 changed files with 214 additions and 36 deletions

View File

@ -51,6 +51,12 @@
<label class="adv-sep">HTTP 访问端口 (frps vhostHTTPPort)</label>
<label>vhostHTTPPort <input v-model.number="vhostHttpPortField" type="number" min="0" /></label>
<label class="adv-sep">用户标识 (frpc user)</label>
<label>user <input v-model="userField" :placeholder="userHint" /></label>
<div v-if="needsUser" class="user-warn">
面板型 frps 必须填 user,否则登录被拒(panel HTTP status 403);留空则该项不出现在生成的配置里。
</div>
<label class="adv-sep">本地 frpc 管理 API (M3)</label>
<label class="adv-check">启用后状态页按真实代理状态展示
<span class="hint-txt">(adminPort &gt; 0)</span>
@ -96,6 +102,7 @@ const portField = field('port')
const tokenField = field('token')
const urlField = field('url')
const enabledField = field('enabled')
const userField = field('user')
const transportProtocolField = field('transportProtocol')
const transportTlsField = field('transportTls')
const transportTlsServerNameField = field('transportTlsServerName')
@ -106,6 +113,13 @@ const adminPortField = field('adminPort')
const adminUserField = field('adminUser')
const adminPasswordField = field('adminPassword')
const showAdv = ref(false)
// A panel-managed frps rejects password-only logins, so surface the field with
// a hint instead of leaving the failure to a frpc log line nobody reads.
const needsUser = computed(() => !String(userField.value || '').trim())
const userHint = computed(() =>
needsUser.value ? '必填(面板型 frps)' : '与 frps 侧一致',
)
</script>
<style scoped lang="scss">
@ -299,6 +313,14 @@ const showAdv = ref(false)
border-top: 1px dashed var(--w4f-warn-border);
padding-top: 4px;
}
// warning shown while the required `user` field is still empty
.user-warn {
grid-column: 1 / -1;
font-size: 11px;
line-height: 1.4;
color: var(--w4f-warn-text);
}
}
}
</style>

View File

@ -54,8 +54,14 @@ export interface Remote {
// M1 transport section
transportProtocol?: string; // tcp | quic | kcp | websocket
transportTls?: boolean;
transportPool?: number;
transportTlsServerName?: string;
transportPool?: number;
transportTlsServerName?: string;
// user: frpc's global `user` field. frps prefixes every proxy name with it,
// and panel-managed frps requires it for login (otherwise the server answers
// `panel authentication unavailable: panel HTTP status 403`).
// Empty = omitted from the rendered config (plain frps).
user?: string;
// M2: frps vhostHTTPPort
vhostHttpPort?: number;

View File

@ -187,6 +187,10 @@
<label>Token</label>
<el-input v-model="dialog.remote.token" :disabled="!canWrite" placeholder="可选" />
</div>
<div class="form-row">
<label>user</label>
<el-input v-model="dialog.remote.user" :disabled="!canWrite" placeholder="面板型 frps 必填,普通 frps 可留空" />
</div>
<div class="form-row">
<label>URL</label>
<el-input v-model="dialog.remote.url" :disabled="!canWrite" placeholder="可选" />
@ -226,6 +230,7 @@ const newRemote = (): Remote => ({
port: 7000,
token: '',
url: '',
user: '',
enabled: true,
})
@ -246,7 +251,7 @@ const openEdit = (p: { name: string }) => {
dialog.isNew = false
dialog.remote = found
? { ...found }
: { name: p.name, ip: '', port: 7000, token: '', url: '', enabled: true }
: { name: p.name, ip: '', port: 7000, token: '', url: '', user: '', enabled: true }
dialog.visible = true
}