102 lines
3.6 KiB
TypeScript
102 lines
3.6 KiB
TypeScript
/**
|
||
* 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`,
|
||
* 而 `<Input v-model.number>` 拿到 `undefined` 会显示成空串并可能在
|
||
* 失焦时写回 `NaN`——反而把好配置改坏。这里统一补齐。
|
||
*/
|
||
export function normalizeHost(raw: Partial<SshHost>, 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'
|
||
}
|