From 5f4d524b9836d89f5a9ce2cfbaa75546d588b1f9 Mon Sep 17 00:00:00 2001 From: xuans Date: Sat, 26 Sep 2026 13:10:04 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=88=E5=B9=B6=E6=9C=8D=E5=8A=A1=E5=99=A8?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E4=B8=8ESSH=E5=AF=86=E9=92=A5=E5=BC=95?= =?UTF-8?q?=E5=AF=BC=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/views/Dashboard/Dashboard.js | 26 ++- .../src/views/Dashboard/Dashboard.test.js | 17 ++ frontend/src/views/Dashboard/ServerModal.css | 178 ++++++++++++++++++ frontend/src/views/Dashboard/ServerModal.vue | 152 ++++++++++++++- 4 files changed, 369 insertions(+), 4 deletions(-) diff --git a/frontend/src/views/Dashboard/Dashboard.js b/frontend/src/views/Dashboard/Dashboard.js index 61a9a5c..3e9913e 100644 --- a/frontend/src/views/Dashboard/Dashboard.js +++ b/frontend/src/views/Dashboard/Dashboard.js @@ -38,6 +38,7 @@ export function useDashboard() { const serverSaving = ref(false); const serverFormError = ref(""); const showSSHKeyForm = ref(false); + const showEmbeddedSSHKeyGuide = ref(false); const sshKeySaving = ref(false); const sshKeyFormError = ref(""); const sshKeyPlatform = ref("windows"); @@ -310,6 +311,15 @@ export function useDashboard() { const serverForm = ref(createEmptyServerForm()); const sshKeyForm = ref(createEmptySSHKeyForm()); + watch( + () => [serverForm.value.username, serverForm.value.address], + ([username, address]) => { + if (!showEmbeddedSSHKeyGuide.value) return; + sshKeyCommandUsername.value = String(username || "").trim() || "用户名"; + sshKeyCommandHost.value = String(address || "").trim() || "服务器地址"; + }, + ); + async function fetchAdmin(path, options = {}) { const response = await fetch(path, { ...options, @@ -583,10 +593,13 @@ export function useDashboard() { } function closeServerForm() { - if (!serverSaving.value) showServerForm.value = false; + if (!serverSaving.value) { + showEmbeddedSSHKeyGuide.value = false; + showServerForm.value = false; + } } - function openSSHKeyCreate() { + function openSSHKeyCreate(options = {}) { sshKeyForm.value = createEmptySSHKeyForm(); sshKeyFormError.value = ""; sshKeyPlatform.value = "windows"; @@ -595,7 +608,8 @@ export function useDashboard() { sshKeyCommandHost.value = serverForm.value.address.trim() || "服务器地址"; sshKeyRandomSuffix.value = createSSHKeyRandomSuffix(); sshKeyForm.value.name = sshKeyFileName.value; - showSSHKeyForm.value = true; + showEmbeddedSSHKeyGuide.value = options.embedded === true; + showSSHKeyForm.value = options.embedded !== true; } function closeSSHKeyForm() { @@ -603,6 +617,7 @@ export function useDashboard() { sshKeyForm.value = createEmptySSHKeyForm(); sshKeyFormError.value = ""; sshKeyPassphraseVisible.value = false; + showEmbeddedSSHKeyGuide.value = false; showSSHKeyForm.value = false; } @@ -641,7 +656,11 @@ export function useDashboard() { }); const result = await response.json().catch(() => ({})); if (!response.ok) throw new Error(result.error || "保存 SSH 私钥失败"); + if (showEmbeddedSSHKeyGuide.value && result.reference) { + serverForm.value.private_key_ref = result.reference; + } sshKeyForm.value = createEmptySSHKeyForm(); + showEmbeddedSSHKeyGuide.value = false; showSSHKeyForm.value = false; notice.value = "SSH 私钥已加密保存"; await loadData(); @@ -1273,6 +1292,7 @@ export function useDashboard() { serverSaving, serverFormError, showSSHKeyForm, + showEmbeddedSSHKeyGuide, sshKeySaving, sshKeyFormError, certificateTypeLabels, diff --git a/frontend/src/views/Dashboard/Dashboard.test.js b/frontend/src/views/Dashboard/Dashboard.test.js index b59d13c..9aa1114 100644 --- a/frontend/src/views/Dashboard/Dashboard.test.js +++ b/frontend/src/views/Dashboard/Dashboard.test.js @@ -539,6 +539,23 @@ test("serverModal.compilesAndPreservesDeploymentFields", async () => { }, sshKeys: [], openSSHKeyCreate: () => {}, + showEmbeddedSSHKeyGuide: false, + closeSSHKeyForm: () => {}, + sshKeySaving: false, + sshKeyFormError: "", + sshKeyPlatform: "windows", + sshKeyFileName: "test-key", + sshKeyPrivatePath: "/test/private", + sshKeyPublicPath: "/test/public", + sshKeyCommandUsername: "deploy", + sshKeyCommandHost: "example.test", + sshKeyCommands: { windows: "generate-windows", linux: "generate-linux" }, + sshPublicKeyCommands: { windows: "install-windows", linux: "install-linux" }, + sshKeyForm: { name: "", private_key: "", passphrase: "" }, + importSSHKeyFile: () => {}, + sshKeyPassphraseVisible: false, + copySSHKeyCommand: () => {}, + saveSSHKey: () => {}, defaultCertificatePath: "", defaultCertificateChainPath: "", defaultPrivateKeyPath: "", diff --git a/frontend/src/views/Dashboard/ServerModal.css b/frontend/src/views/Dashboard/ServerModal.css index 8828dc5..471da4d 100644 --- a/frontend/src/views/Dashboard/ServerModal.css +++ b/frontend/src/views/Dashboard/ServerModal.css @@ -74,6 +74,161 @@ border-top: 1px solid #dce6f1; } +.server-modal .server-ssh-key-guide { + grid-column: 1 / -1; + min-width: 0; + margin: 2px 0 8px; + padding: 14px; + border: 1px solid #c7daf1; + border-radius: 14px; + background: #f5f9ff; +} + +.server-modal .server-ssh-key-heading { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; + margin-bottom: 10px; +} + +.server-modal .server-ssh-key-heading h3 { + margin: 2px 0 4px; + color: #214d86; + font-size: 18px; +} + +.server-modal .server-ssh-key-guide .ssh-key-steps { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 8px; + margin-bottom: 10px; +} + +.server-modal .server-ssh-key-guide .ssh-key-steps article { + display: flex; + align-items: center; + gap: 8px; + min-width: 0; + padding: 9px 10px; + border: 1px solid #d1e0f1; + border-radius: 10px; + background: #fff; + color: #294a73; +} + +.server-modal .server-ssh-key-guide .ssh-key-steps b { + display: inline-grid; + place-items: center; + flex: 0 0 24px; + width: 24px; + height: 24px; + border-radius: 50%; + background: #5b91dc; + color: #fff; +} + +.server-modal .server-ssh-key-guide .ssh-key-platforms { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 4px; + margin-bottom: 8px; + padding: 4px; + border-radius: 10px; + background: #e7effa; +} + +.server-modal .server-ssh-key-guide .platform-tab { + min-height: 34px; + border: 0; + border-radius: 8px; + background: transparent; + color: #5d7390; + cursor: pointer; + font: inherit; +} + +.server-modal .server-ssh-key-guide .platform-tab.active { + background: #fff; + color: #24579a; + box-shadow: 0 2px 8px rgb(42 80 125 / 12%); +} + +.server-modal .server-ssh-key-guide .ssh-key-path-line, +.server-modal .server-ssh-key-guide .ssh-key-path-summary { + display: grid; + gap: 8px; + margin-bottom: 8px; + padding: 8px 10px; + border: 1px solid #d1e0f1; + border-radius: 10px; + background: #fff; +} + +.server-modal .server-ssh-key-guide .ssh-key-path-line { + grid-template-columns: auto minmax(0, 1fr) auto; + align-items: center; +} + +.server-modal .server-ssh-key-guide .ssh-key-path-summary { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.server-modal .server-ssh-key-guide .ssh-key-path-summary > div { + display: grid; + gap: 4px; + min-width: 0; +} + +.server-modal .server-ssh-key-guide code { + overflow-wrap: anywhere; + color: #294a73; + font-family: ui-monospace, SFMono-Regular, Consolas, monospace; + font-size: 12px; +} + +.server-modal .server-ssh-key-guide .ssh-command-card { + margin-bottom: 8px; + padding: 9px 10px; + border: 1px solid #d1e0f1; + border-radius: 10px; + background: #fff; +} + +.server-modal .server-ssh-key-guide .ssh-command-heading, +.server-modal .server-ssh-key-actions { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.server-modal .server-ssh-key-guide .ssh-command-heading { + margin-bottom: 5px; + color: #294a73; + font-weight: 700; +} + +.server-modal .server-ssh-key-fields { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0 14px; + padding-top: 2px; +} + +.server-modal .server-ssh-key-fields .field:nth-child(3) { + grid-column: 1 / -1; +} + +.server-modal .server-ssh-key-fields textarea { + min-height: 96px; +} + +.server-modal .server-ssh-key-actions { + padding-top: 8px; + border-top: 1px solid #d1e0f1; +} + @media (max-width: 900px) { .server-modal form { grid-template-columns: repeat(2, minmax(0, 1fr)); @@ -86,6 +241,10 @@ .server-modal .server-paths { grid-template-columns: minmax(0, 1fr); } + + .server-modal .server-ssh-key-guide .ssh-key-path-summary { + grid-template-columns: minmax(0, 1fr); + } } @media (max-width: 640px) { @@ -102,4 +261,23 @@ .server-modal form > .field { grid-column: 1 / -1; } + + .server-modal .server-ssh-key-guide .ssh-key-steps, + .server-modal .server-ssh-key-fields { + grid-template-columns: minmax(0, 1fr); + } + + .server-modal .server-ssh-key-fields .field:nth-child(3) { + grid-column: auto; + } + + .server-modal .server-ssh-key-guide .ssh-key-path-line { + grid-template-columns: minmax(0, 1fr); + } + + .server-modal .server-ssh-key-heading, + .server-modal .server-ssh-key-actions { + align-items: stretch; + flex-direction: column; + } } diff --git a/frontend/src/views/Dashboard/ServerModal.vue b/frontend/src/views/Dashboard/ServerModal.vue index 6b852be..52736f0 100644 --- a/frontend/src/views/Dashboard/ServerModal.vue +++ b/frontend/src/views/Dashboard/ServerModal.vue @@ -9,6 +9,23 @@ const { serverForm, sshKeys, openSSHKeyCreate, + closeSSHKeyForm, + showEmbeddedSSHKeyGuide, + sshKeySaving, + sshKeyFormError, + sshKeyPlatform, + sshKeyFileName, + sshKeyPrivatePath, + sshKeyPublicPath, + sshKeyCommandUsername, + sshKeyCommandHost, + sshKeyCommands, + sshPublicKeyCommands, + sshKeyForm, + importSSHKeyFile, + sshKeyPassphraseVisible, + copySSHKeyCommand, + saveSSHKey, defaultCertificatePath, defaultCertificateChainPath, defaultPrivateKeyPath, @@ -113,7 +130,7 @@ const { > +
+
+
+

当前服务器

+

在这里准备 SSH 私钥

+ 准备完成后会自动选中,不需要离开服务器配置。 +
+ +
+
+
+ 1 + 本机生成密钥 +
+
+ 2 + 把公钥放到当前服务器 +
+
+ 3 + 读取私钥并保存 +
+
+
+ + +
+
+ 本次密钥文件名 + {{ sshKeyFileName }} + 每次随机生成 +
+
+
+ 本地私钥位置 + {{ sshKeyPrivatePath }} +
+
+ 本地公钥位置 + {{ sshKeyPublicPath }} +
+
+ 远程登录目标 + {{ sshKeyCommandUsername }}@{{ sshKeyCommandHost }} +
+
+
+
+ 生成命令 + +
+ {{ sshKeyCommands[sshKeyPlatform] }} +
+
+
+ 安装公钥命令 + +
+ {{ sshPublicKeyCommands[sshKeyPlatform] }} +
+
+ + + + +
+

{{ sshKeyFormError }}

+
+ 私钥只会加密保存,服务器配置仅保存安全引用。 + +
+