diff --git a/doc/SSH密钥配置指南.md b/doc/SSH密钥配置指南.md new file mode 100644 index 0000000..dd2c038 --- /dev/null +++ b/doc/SSH密钥配置指南.md @@ -0,0 +1,171 @@ +# SSH 密钥配置指南 + +本文用于帮助不熟悉 SSH 的用户完成 Scert 远程服务器部署配置。Scert 只使用 SSH 密钥认证,不保存或使用远程服务器登录密码。 + +## 一、配置流程 + +SSH 配置分为三步: + +1. 在自己的电脑上生成一对 SSH 密钥。 +2. 将公钥安装到远程服务器的授权文件中。 +3. 将本机私钥录入 Scert,Scert 会在保存时加密存储。 + +密钥分为两部分: + +- **私钥**:只保存在自己的电脑或 Scert 的加密敏感信息存储中,不能发送给他人,不能提交到 Git。 +- **公钥**:可以安装到远程服务器,用于验证私钥对应的客户端。 + +## 二、Windows 生成密钥 + +Windows 10、Windows 11 和较新的 Windows Server 通常已经包含 OpenSSH 客户端。打开 PowerShell,复制并执行: + +```powershell +ssh-keygen -t ed25519 -C "scert-deploy" -f "$HOME\.ssh\scert_deploy_ed25519" +``` + +执行过程中: + +1. 如果提示创建目录,输入 `Y` 或按回车确认。 +2. 如果提示输入私钥口令,可以设置一个口令;如果希望配置更简单,也可以直接按两次回车留空。 +3. 私钥默认保存为:`%USERPROFILE%\.ssh\scert_deploy_ed25519`。 +4. 公钥默认保存为:`%USERPROFILE%\.ssh\scert_deploy_ed25519.pub`。 + +查看公钥内容: + +```powershell +Get-Content "$HOME\.ssh\scert_deploy_ed25519.pub" +``` + +复制公钥内容时,要从 `ssh-ed25519` 开始复制到末尾的注释,必须保持为一整行。 + +## 三、Linux 或 macOS 生成密钥 + +打开终端,复制并执行: + +```bash +ssh-keygen -t ed25519 -C "scert-deploy" -f ~/.ssh/scert_deploy_ed25519 +``` + +查看公钥内容: + +```bash +cat ~/.ssh/scert_deploy_ed25519.pub +``` + +默认文件位置: + +- 私钥:`~/.ssh/scert_deploy_ed25519` +- 公钥:`~/.ssh/scert_deploy_ed25519.pub` + +建议设置本地私钥权限: + +```bash +chmod 700 ~/.ssh +chmod 600 ~/.ssh/scert_deploy_ed25519 +chmod 644 ~/.ssh/scert_deploy_ed25519.pub +``` + +## 四、把公钥安装到 Linux 服务器 + +如果本机安装了 `ssh-copy-id`,可以执行: + +```bash +ssh-copy-id -i ~/.ssh/scert_deploy_ed25519.pub 用户名@服务器地址 +``` + +命令会要求输入一次服务器登录密码。密码只用于把公钥安装到服务器,不会提交给 Scert。 + +如果没有 `ssh-copy-id`,先查看公钥: + +```bash +cat ~/.ssh/scert_deploy_ed25519.pub +``` + +然后使用服务器控制台或已有登录方式,在远程服务器执行: + +```bash +umask 077 +mkdir -p ~/.ssh +cat >> ~/.ssh/authorized_keys +``` + +执行后将公钥整行粘贴进去,按回车,再按 `Ctrl+D` 结束输入。最后设置权限: + +```bash +chmod 700 ~/.ssh +chmod 600 ~/.ssh/authorized_keys +``` + +如果远程服务器使用的是其他登录用户,必须把公钥放到该用户自己的 `~/.ssh/authorized_keys`,不能误放到 root 用户目录。 + +## 五、把公钥安装到 Windows OpenSSH 服务器 + +Windows 服务器需要先确认 OpenSSH Server 已安装并运行。对于普通用户,将公钥追加到该用户的 `.ssh\authorized_keys` 文件;对于管理员账户,Windows OpenSSH 通常使用系统级管理员授权文件,具体位置以服务器 OpenSSH 配置为准。 + +建议优先使用服务器管理员提供的 OpenSSH 配置方式,不要直接覆盖已有授权文件。追加公钥时只能新增一行,不能删除其他用户已经配置的公钥。 + +## 六、在 Scert 中录入私钥 + +在 Scert 的服务器配置页面: + +1. 点击“新建或导入”。 +2. 选择自己的操作系统标签,查看对应的密钥生成和公钥安装命令。 +3. 在“私钥名称”中填写容易识别的名称,例如“测试服务器部署密钥”。 +4. 可以直接把私钥内容粘贴到“私钥内容”,也可以点击文件选择框读取本机私钥文件。 +5. 如果生成密钥时设置了口令,在“私钥口令”中填写相同口令;未设置口令时留空。 +6. 确认内容无误后点击“加密保存”。 +7. 返回服务器配置,选择刚刚保存的 SSH 私钥。 + +读取本机私钥文件只会把内容放入当前页面内存,仍需点击“加密保存”才会提交。关闭窗口或刷新页面前,请确认已经完成保存。 + +## 七、测试连接前检查 + +配置服务器时需要填写: + +- 服务器地址和 SSH 端口,默认端口为 `22`。 +- SSH 用户名。 +- 已加密保存的 SSH 私钥。 +- SSH 主机 `SHA256` 指纹。 +- 远程证书、完整证书链和私钥路径。 + +首次连接前,请通过可信的服务器控制台或管理员渠道核对主机指纹。不要为了绕过连接失败而随意删除指纹校验。 + +测试环境当前包括: + +- `192.168.2.3` +- `192.168.2.4` + +上述地址仅用于授权测试,不应直接写入生产配置。生产环境应由用户自行填写实际服务器地址,并重新核对主机指纹和远程路径。 + +## 八、常见问题 + +### 1. 提示权限不足 + +检查远程用户是否有权读取或替换证书目录,以及是否有权执行 Nginx、Caddy 或 Apache 的重载命令。不要直接给服务账号授予不必要的全盘权限。 + +### 2. 提示私钥格式错误 + +请确认录入的是私钥文件,而不是 `.pub` 公钥文件。私钥内容通常以 `BEGIN` 开头;公钥通常以 `ssh-ed25519`、`ssh-rsa` 或类似标识开头。 + +### 3. 设置了私钥口令但连接失败 + +检查 Scert 中填写的私钥口令是否与生成密钥时设置的口令完全一致。配置页面允许明文显示口令,便于确认输入;保存后由后端加密存储,接口不返回明文。 + +### 4. 不知道怎么操作 + +最简单的方式是: + +1. 在 Scert 弹窗中复制密钥生成命令。 +2. 在自己的电脑上执行命令。 +3. 将公钥安装命令中的服务器地址和用户名替换为实际值并执行。 +4. 回到 Scert,选择刚生成的私钥文件。 +5. 点击“加密保存”,再配置服务器。 + +## 九、安全注意事项 + +- 不要把私钥发送给任何人。 +- 不要把私钥、公钥安装密码或 DNS 密钥提交到 Git。 +- 不要把私钥粘贴到远程服务器;远程服务器只需要公钥。 +- 不要覆盖远程服务器已有的 `authorized_keys` 文件。 +- 服务器地址、端口、用户和主机指纹应按环境分别配置。 +- 测试机的配置不能直接复制到生产环境。 \ No newline at end of file diff --git a/doc/SSL服务程序需求确认文档.md b/doc/SSL服务程序需求确认文档.md index 0b96e13..4b7f080 100644 --- a/doc/SSL服务程序需求确认文档.md +++ b/doc/SSL服务程序需求确认文档.md @@ -996,3 +996,28 @@ imported 外部导入 - `[x]` 本机防火墙自动配置已完成代码和自动化测试;安装器提权与各目标系统实机复审待完成。 - `[!]` 云安全组自动配置不纳入当前自动修改范围,继续由初始化界面提示用户手动放行。 - `[!]` 公网 IP 证书在目标 CA 和 `lego` 版本上的兼容性验证。 + +## 三十、本轮 DNS 授权范围与 SSH 推送测试范围补充(2026-08-29) + +本节记录本轮用户补充内容,保留前文历史测试记录,便于对照追踪。 + +### 30.1 阿里云 RAM DNS 授权范围 + +- **用户补充:**本轮测试使用的阿里云 RAM 账号只允许控制以下 DNS 根域名: + - `epwn.uno` + - `zedyj.cn` + - `szqh.uno` +- **实现说明:**测试代码、示例配置和文档不得保存 AccessKeyId、AccessKeySecret 或其他敏感凭据;DNS 测试应验证授权边界,禁止将操作扩展到未授权域名。 +- **状态:**`[x]` 已记录最新授权域名范围,真实 API 读取验证待在授权环境执行。 + +### 30.2 SSH 推送测试服务器 + +- **用户补充:**在已有 `192.168.2.3` 测试机基础上,增加 `192.168.2.4` 作为证书推送测试机。 +- **实现说明:**测试范围同时覆盖 `192.168.2.3` 和 `192.168.2.4`;测试配置不得硬编码到生产配置,且每台服务器必须单独配置 SSH 用户、端口、私钥引用、主机指纹和远程证书路径。 +- **状态:**`[x]` 已更新测试范围说明,实际连接、证书推送和服务重载验证待执行。 + +### 30.3 SSH 密钥配置引导 + +- **用户补充:**为不熟悉 SSH 密钥生成的用户提供从生成密钥到粘贴公钥的完整指导,并支持更省事的操作方式。 +- **实现说明:**服务器配置页面增加 Windows、Linux/macOS 命令切换;支持复制密钥生成命令、复制公钥安装命令、选择本机私钥文件导入、私钥口令显示/隐藏,以及 Linux 和 Windows OpenSSH 授权位置说明。私钥只在页面内存中读取,保存时由后端加密存储。 +- **状态:**`[x]` 已完成前端引导和中文说明文档,待前端构建和真实浏览器页面复审。 \ No newline at end of file diff --git a/doc/Scert开发任务清单.md b/doc/Scert开发任务清单.md index 63f5dd7..ca5000f 100644 --- a/doc/Scert开发任务清单.md +++ b/doc/Scert开发任务清单.md @@ -2582,4 +2582,131 @@ Linux Ubuntu 22.04 上的 `Scert-admin` 手动启停、无开机自启动、无 ### 59.3 当前状态 -本轮代码调整和自动化检查已完成,相关任务标记为“已完成,等待复审”。浏览器 MCP 视觉验收属于外部运行环境阻塞,不影响前端构建和后端自动化检查结果;待独立浏览器窗口可用后,再对桌面宽屏、中等窗口和低高度窗口进行实际显示复审。 \ No newline at end of file +本轮代码调整和自动化检查已完成,相关任务标记为“已完成,等待复审”。浏览器 MCP 视觉验收属于外部运行环境阻塞,不影响前端构建和后端自动化检查结果;待独立浏览器窗口可用后,再对桌面宽屏、中等窗口和低高度窗口进行实际显示复审。 +## 六十、SSH 密钥配置引导与最新测试范围更新(2026-08-29) + +### 60.1 需求清单 + +- [x] 记录最新阿里云 RAM 测试授权域名:`epwn.uno`、`zedyj.cn`、`szqh.uno`。 +- [x] 将 `192.168.2.4` 加入 SSH 推送测试范围,并保留 `192.168.2.3`。 +- [x] 为 SSH 密钥配置增加 Windows、Linux/macOS 生成命令引导。 +- [x] 增加公钥安装位置和安装命令说明。 +- [x] 增加复制密钥生成命令和公钥安装命令功能。 +- [x] 增加本机私钥文件导入功能,导入内容只进入当前页面内存。 +- [x] 增加私钥口令显示/隐藏功能,并保持后端加密存储约束。 +- [x] 保持 SSH 主机 SHA256 指纹校验和私钥不回显策略。 + +### 60.2 实现文件 + +- `frontend/src/views/Dashboard/Dashboard.js` + - 增加 SSH 平台切换、命令模板、剪贴板复制和本机私钥文件读取逻辑。 +- `frontend/src/views/Dashboard/Dashboard.vue` + - 增加 SSH 三步配置引导、平台选择、命令复制、私钥文件导入和口令显示控制。 +- `frontend/src/views/Dashboard/Dashboard.css` + - 增加引导卡片、步骤布局、命令卡片、平台标签和文件选择控件样式。 +- `doc/SSH密钥配置指南.md` + - 增加用户可直接照做的 Windows、Linux/macOS、Linux 服务器和 Windows OpenSSH 配置说明。 + +### 60.3 待验证任务 + +- [ ] 使用最新授权域名完成阿里云 DNS API 读取边界验证,不记录或输出敏感凭据。 +- [ ] 在 `192.168.2.3` 上完成 SSH 连接、证书文件推送和网站服务重载测试。 +- [ ] 在 `192.168.2.4` 上完成 SSH 连接、证书文件推送和网站服务重载测试。 +- [ ] 完成前端生产构建、后端竞态测试、静态检查和模块完整性检查。 +- [ ] 在独立浏览器 MCP 窗口中完成桌面端页面截图复审。 +- [!] 移动端视觉复审按规则使用 Mobile Preview 插件,不在本轮启动浏览器 MCP 移动端预览。 + +### 60.4 安全边界 + +- 不把阿里云 RAM 凭据、SSH 私钥、服务器密码写入代码、文档、普通配置或 Git。 +- 不把测试服务器地址硬编码到生产配置。 +- 不覆盖远程服务器已有授权公钥,只允许追加或由用户明确维护授权文件。 +- SSH 连接仍必须校验主机 SHA256 指纹。 +### 60.5 本轮验证结果 + +- [x] `npm.cmd run build` 通过,前端生产构建成功。 +- [x] `go test -race ./...` 通过。 +- [x] `go vet ./...` 通过。 +- [x] `go mod verify` 通过,所有模块已验证。 +- [x] `git diff --check` 通过。 +- [x] 本机前端 `127.0.0.1:5273` 返回 HTTP 200。 +- [x] Scert 后端 `127.0.0.1:5200/healthz` 返回 HTTP 200,健康状态正常。 +- [x] `192.168.2.3:22` 和 `192.168.2.4:22` TCP 可达。 +- [!] 尚未执行两台测试机的 SSH 登录、证书推送和网站服务重载;需要分别配置并核对私钥和主机指纹。 +- [!] 浏览器 MCP 默认共享 Profile 仍被其他浏览器进程占用,本轮未强制关闭现有浏览器,未完成真实截图复审。 + +### 60.6 复审状态 + +- `[x]` 源码和文档修改完成,等待用户复审。 +- `[x]` 自动化构建和后端检查完成,等待用户复审。 +- `[!]` 真实 SSH 推送、DNS API 在线验证和浏览器截图复审仍待独立测试条件满足后执行。 +## 六十一、最新授权域名 DNS API 在线测试(2026-08-29) + +### 61.1 测试范围 + +- [x] 使用当前源码启动独立测试实例,监听 `127.0.0.1:5201`,避免影响仍在运行的 `5200` 旧版本实例。 +- [x] 使用阿里云 RAM 测试账号验证 `epwn.uno` 的 DNS API 操作能力。 +- [x] 使用阿里云 RAM 测试账号验证 `zedyj.cn` 的 DNS API 操作能力。 +- [x] 使用阿里云 RAM 测试账号验证 `szqh.uno` 的 DNS API 操作能力。 +- [x] 不申请证书,仅通过测试接口创建并清理临时 DNS-01 验证记录。 + +### 61.2 测试结果 + +| 测试域名 | 测试接口结果 | 结论 | +|---|---:|---| +| `epwn.uno` | HTTP 200,验证通过 | 阿里云 DNS API 可用 | +| `zedyj.cn` | HTTP 200,验证通过 | 阿里云 DNS API 可用 | +| `szqh.uno` | HTTP 200,验证通过 | 阿里云 DNS API 可用 | + +- [x] 当前源码 DNS Provider 列表中,阿里云 DNS 显示为可用。 +- [x] 三个域名均完成临时记录创建和自动清理流程。 +- [x] 测试实例配置文件中未发现明文 AccessKeySecret。 +- [x] 未将任何 AccessKey、Secret 或测试凭据写入代码、文档或 Git。 + +### 61.3 其他运行状态 + +- [x] `http://127.0.0.1:5201/healthz` 返回 HTTP 200。 +- [x] 原有 `http://127.0.0.1:5200/healthz` 返回 HTTP 200;该端口当前仍运行旧构建,不用于本轮源码 Provider 结论。 +- [x] 前端 `http://127.0.0.1:5273/` 返回 HTTP 200。 +- [x] `192.168.2.3:22` TCP 可达。 +- [x] `192.168.2.4:22` TCP 可达。 + +### 61.4 尚未执行项目 + +- [!] 尚未进行两台服务器的 SSH 私钥登录;当前缺少可用于测试的私钥和已核对的主机指纹。 +- [!] 尚未向两台服务器推送证书或执行 Nginx、Caddy、Apache 服务重载。 +- [!] 浏览器 MCP 共享 Chrome Profile 仍被其他浏览器进程占用,尚未完成桌面端真实截图复审。 +- [!] 移动端视觉测试仍按规则保留给 Mobile Preview 插件,本轮未启动浏览器 MCP 移动端预览。 + +### 61.5 本轮复审结论 + +- `[x]` 阿里云 RAM 对三个最新授权根域名的 DNS-01 临时记录操作已完成真实验证。 +- `[x]` 当前源码中的阿里云 Provider 已确认不是旧版本的“未实现”状态。 +- `[!]` DNS API 验证通过不等同于生产证书签发、远程部署和线上 HTTPS 校验完成;后续仍需在明确 SSH 私钥和主机指纹后执行分阶段部署测试。 + +## 六十二、绑定邮箱完整地址识别修复(2026-08-29) + +### 62.1 用户反馈 + +- [x] 绑定邮箱输入框不再显示具体邮箱用户名示例。 +- [x] 输入完整邮箱地址时,自动识别 `@` 后的邮箱域名。 +- [x] 输入框只显示 `@` 前的邮箱名称,下拉列表显示对应常用邮箱后缀。 +- [x] 输入到 `@` 但尚未输入域名时,暂时保留当前输入,避免受控输入框重绘导致光标跳动和内容错乱。 + +### 62.2 修改文件 + +- `frontend/src/views/SetupWizard/SetupWizard.vue` + - 将绑定邮箱占位文字改为通用提示“请输入邮箱名称”。 +- `frontend/src/views/SetupWizard/SetupWizard.js` + - 完整邮箱输入时拆分本地部分和域名。 + - 常用域名自动选中对应下拉项。 + - 非常用域名自动归入“自定义后缀”。 + - 保留完整邮箱到提交数据中的 `admin_email` 字段。 + +### 62.3 验证结果 + +- [x] 前端生产构建通过。 +- [x] 源码中未发现 `likb_blue` 等具体邮箱示例。 +- [x] 源码中未发现 HTML 数字编码中文字符。 +- [x] `git diff --check` 通过。 +- [!] 浏览器 MCP 当前因共享 Chrome Profile 被其他浏览器进程占用,暂未完成本次实际输入交互截图复审。 diff --git a/frontend/src/views/Dashboard/Dashboard.css b/frontend/src/views/Dashboard/Dashboard.css index 32ac9c6..d6950e7 100644 --- a/frontend/src/views/Dashboard/Dashboard.css +++ b/frontend/src/views/Dashboard/Dashboard.css @@ -472,3 +472,218 @@ code { gap: 0; } } + +.field-label-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.field-label-row > span:first-child { + min-width: 0; +} + +.inline-button { + flex: 0 0 auto; + border: 0; + padding: 4px 8px; + border-radius: 6px; + color: #2557a7; + background: #edf4ff; + font-size: 12px; + line-height: 1.4; + cursor: pointer; +} + +.inline-button:hover { + color: #17458f; + background: #dceaff; +} + +.ssh-key-guide { + margin: 0 0 18px; + padding: 16px; + border: 1px solid #dbe7f7; + border-radius: 12px; + background: linear-gradient(135deg, #f7fbff, #f3f7fc); +} + +.ssh-key-guide-heading { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 14px; + color: #183b68; + font-size: 14px; + font-weight: 700; +} + +.ssh-key-guide-heading::before { + content: "K"; + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border-radius: 7px; + color: #ffffff; + background: #3677d8; + font-size: 12px; +} + +.ssh-key-steps { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 10px; + margin: 0; +} + +.ssh-key-steps article { + display: flex; + gap: 9px; + min-width: 0; + padding: 11px; + border-radius: 9px; + background: #ffffff; +} + +.ssh-key-steps b { + display: inline-flex; + flex: 0 0 22px; + align-items: center; + justify-content: center; + width: 22px; + height: 22px; + border-radius: 50%; + color: #ffffff; + background: #5f93dc; + font-size: 12px; +} + +.ssh-key-steps div { + display: grid; + gap: 3px; + min-width: 0; +} + +.ssh-key-steps strong { + color: #23466f; + font-size: 12px; +} + +.ssh-key-steps span { + color: #687b94; + font-size: 11px; + line-height: 1.55; +} + +.ssh-key-platforms { + display: flex; + gap: 6px; + margin: 16px 0 10px; + padding: 4px; + border-radius: 8px; + background: #e8eff9; +} + +.platform-tab { + flex: 1; + border: 0; + padding: 8px 10px; + border-radius: 6px; + color: #55708e; + background: transparent; + font-size: 12px; + cursor: pointer; +} + +.platform-tab.active { + color: #214f98; + background: #ffffff; + box-shadow: 0 2px 8px rgba(48, 83, 132, 0.12); + font-weight: 700; +} + +.ssh-command-card { + margin-top: 10px; + padding: 11px 12px; + border: 1px solid #dfe8f3; + border-radius: 9px; + background: #ffffff; +} + +.ssh-command-heading { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + margin-bottom: 7px; + color: #3b5778; + font-size: 12px; + font-weight: 700; +} + +.ssh-command-card > code { + display: block; + overflow-wrap: anywhere; + color: #294765; + font-family: Consolas, "SFMono-Regular", monospace; + font-size: 11px; + line-height: 1.65; + white-space: pre-wrap; +} + +.ssh-command-card small { + display: block; + margin-top: 8px; + color: #71839a; + font-size: 11px; + line-height: 1.55; +} + +.ssh-key-guide-note { + margin: 12px 0 0; + color: #637895; + font-size: 11px; + line-height: 1.6; +} + +.field input[type="file"] { + padding: 9px; + color: #5d6f86; + background: #f8fafc; + font-size: 12px; +} + +@media (max-width: 700px) { + .ssh-key-steps { + grid-template-columns: 1fr; + } + + .field-label-row { + align-items: flex-start; + } +} +.ssh-key-guide-heading > div:first-child { + display: grid; + gap: 3px; + min-width: 0; +} + +.ssh-key-guide-heading > div:first-child span { + color: #6d819b; + font-size: 11px; + font-weight: 400; + line-height: 1.55; +} + +.guide-badge { + flex: 0 0 auto; + padding: 4px 8px; + border-radius: 999px; + color: #2c68ba; + background: #e2efff; + font-size: 11px; + white-space: nowrap; +} \ No newline at end of file diff --git a/frontend/src/views/Dashboard/Dashboard.js b/frontend/src/views/Dashboard/Dashboard.js index 32ca58f..7c3c46f 100644 --- a/frontend/src/views/Dashboard/Dashboard.js +++ b/frontend/src/views/Dashboard/Dashboard.js @@ -22,6 +22,21 @@ export function useDashboard() { const showSSHKeyForm = ref(false); const sshKeySaving = ref(false); const sshKeyFormError = ref(""); + const sshKeyPlatform = ref("windows"); + const sshKeyPassphraseVisible = ref(false); + + const sshKeyCommands = { + windows: + 'ssh-keygen -t ed25519 -C "scert-deploy" -f "$HOME\\.ssh\\scert_deploy_ed25519"', + linux: + 'ssh-keygen -t ed25519 -C "scert-deploy" -f ~/.ssh/scert_deploy_ed25519', + }; + const sshPublicKeyCommands = { + windows: + 'Get-Content "$HOME\\.ssh\\scert_deploy_ed25519.pub" | ssh root@服务器地址 "umask 077; mkdir -p ~/.ssh; cat >> ~/.ssh/authorized_keys"', + linux: + 'ssh-copy-id -i ~/.ssh/scert_deploy_ed25519.pub 用户名@服务器地址', + }; const certificateTypeLabels = { single: "单域名", @@ -178,6 +193,8 @@ export function useDashboard() { function openSSHKeyCreate() { sshKeyForm.value = createEmptySSHKeyForm(); sshKeyFormError.value = ""; + sshKeyPlatform.value = "windows"; + sshKeyPassphraseVisible.value = false; showSSHKeyForm.value = true; } @@ -185,9 +202,34 @@ export function useDashboard() { if (sshKeySaving.value) return; sshKeyForm.value = createEmptySSHKeyForm(); sshKeyFormError.value = ""; + sshKeyPassphraseVisible.value = false; showSSHKeyForm.value = false; } + async function importSSHKeyFile(event) { + const file = event.target.files?.[0]; + event.target.value = ""; + if (!file) return; + try { + const content = await file.text(); + if (!content.trim()) throw new Error("选择的私钥文件为空"); + sshKeyForm.value.private_key = content; + sshKeyFormError.value = ""; + notice.value = `已读取私钥文件:${file.name}`; + } catch (err) { + sshKeyFormError.value = err.message || "读取私钥文件失败"; + } + } + + async function copySSHKeyCommand(value, message) { + try { + await navigator.clipboard.writeText(value); + notice.value = message; + } catch { + sshKeyFormError.value = "浏览器未允许访问剪贴板,请手动复制命令"; + } + } + async function saveSSHKey() { sshKeyFormError.value = ""; sshKeySaving.value = true; @@ -456,6 +498,10 @@ export function useDashboard() { form, serverForm, sshKeyForm, + sshKeyPlatform, + sshKeyPassphraseVisible, + sshKeyCommands, + sshPublicKeyCommands, defaultCertificatePath, defaultCertificateChainPath, defaultPrivateKeyPath, @@ -467,6 +513,8 @@ export function useDashboard() { closeServerForm, openSSHKeyCreate, closeSSHKeyForm, + importSSHKeyFile, + copySSHKeyCommand, saveSSHKey, deleteSSHKey, saveServer, diff --git a/frontend/src/views/Dashboard/Dashboard.vue b/frontend/src/views/Dashboard/Dashboard.vue index 6010fcf..bb0354e 100644 --- a/frontend/src/views/Dashboard/Dashboard.vue +++ b/frontend/src/views/Dashboard/Dashboard.vue @@ -32,6 +32,10 @@ const { form, serverForm, sshKeyForm, + sshKeyPlatform, + sshKeyPassphraseVisible, + sshKeyCommands, + sshPublicKeyCommands, defaultCertificatePath, defaultCertificateChainPath, defaultPrivateKeyPath, @@ -43,6 +47,8 @@ const { closeServerForm, openSSHKeyCreate, closeSSHKeyForm, + importSSHKeyFile, + copySSHKeyCommand, saveSSHKey, deleteSSHKey, saveServer, @@ -489,7 +495,15 @@ const { required type="text" /> @@ -597,6 +611,104 @@ const {