/**
* SSH 主机表单的默认值与规整。
*
* 抽成独立模块而不是写在 `HostManager.vue` 里,是因为「新建主机的默认值」
* 有两个使用场景:主机管理面板(`HostManager.vue`)与侧栏的快速新建入口。
* 若各写一份,两处的默认端口、默认超时迟早会不一致——而这种不一致
* 极难被发现,只会表现为「从这里建的主机连不上,从那里建的可以」。
*
* 默认值的选取依据:
* - `port: 22` —— SSH 标准端口,覆盖绝大多数场景。
* - `connectTimeoutMs: 15000` —— 跨公网 + 需要 DNS 解析时,10 秒常不够;
* 30 秒又让用户在网络不通时等太久。15 秒是「能容忍慢链路,又不至于
* 像卡死」的折中值。
* - `keepaliveSecs: 30` —— 小于多数家用路由/NAT 的 60 秒空闲回收阈值,
* 足以穿透常见 NAT 超时,同时心跳包本身开销可忽略。
* - `encoding: 'utf-8'` —— 现代 Linux 发行版默认 locale;GBK 只留给老系统。
*/
import type { SshHost } from '@/types/terminal'
export type { SshHost }
/**
* 构造一个空白主机配置。
*
* `id` 由调用方提供 —— 必须来自后端 `terminal_new_host_id`,
* 不能在前端自行生成。原因见 `HostManager.vue` 的文件头说明:
* 密码在系统凭据管理器里按 hostId 存取,id 必须与后端规则一致。
*/
export function blankHost(id: string): SshHost {
return {
id,
name: '',
host: '',
port: 22,
username: '',
authMethod: 'key',
keyId: '',
group: '',
note: '',
color: '',
favorited: false,
connectTimeoutMs: 15000,
keepaliveSecs: 30,
remoteCwd: '',
startupCommand: '',
useProxy: false,
jumpIds: [],
encoding: 'utf-8'
}
}
/**
* 补齐缺失字段的主机配置。
*
* 用于编辑历史配置:早先版本保存的 `SshHost` 可能缺少后来新增的字段
* (如 `encoding`、`useProxy`)。直接绑到表单上,缺失字段会是 `undefined`,
* 而 `` 拿到 `undefined` 会显示成空串并可能在
* 失焦时写回 `NaN`——反而把好配置改坏。这里统一补齐。
*/
export function normalizeHost(raw: Partial, id: string): SshHost {
const base = blankHost(raw.id || id)
return {
...base,
...raw,
// 数值字段单独兜底:`NaN` 是合法 number,不会触发 `??` 的默认值,
// 但塞进 `port` 会让后端校验失败,且表单上显示为空,用户看不出问题所在。
port: Number.isFinite(raw.port) ? (raw.port as number) : base.port,
connectTimeoutMs: Number.isFinite(raw.connectTimeoutMs)
? (raw.connectTimeoutMs as number)
: base.connectTimeoutMs,
keepaliveSecs: Number.isFinite(raw.keepaliveSecs)
? (raw.keepaliveSecs as number)
: base.keepaliveSecs,
authMethod: raw.authMethod || base.authMethod,
// 数组字段单独兜底:旧配置没有该字段时是 undefined,直接展开会盖掉 base 的 []
jumpIds: Array.isArray(raw.jumpIds) ? raw.jumpIds : base.jumpIds,
encoding: raw.encoding || base.encoding
}
}
/** 认证方式的中文展示名 */
export function authMethodLabel(method: string): string {
switch (method) {
case 'key':
return '密钥认证'
case 'password':
return '密码认证'
case 'agent':
return 'SSH Agent'
case 'keyboard':
return '键盘交互'
default:
return method || '未设置'
}
}
/** 是否是可用的认证方式(P0 只实现 key 与 password) */
export function isSupportedAuthMethod(method: string): boolean {
return method === 'key' || method === 'password'
}