{{ serverSSHKey(item)?.fingerprint || item.ssh_key_fingerprint || "—" }}
+ {{ serverSSHCommand(item) }}
+ diff --git a/frontend/src/views/Dashboard/Dashboard.css b/frontend/src/views/Dashboard/Dashboard.css index 622d4b0..a7141e4 100644 --- a/frontend/src/views/Dashboard/Dashboard.css +++ b/frontend/src/views/Dashboard/Dashboard.css @@ -429,6 +429,150 @@ code { background: #fff0f0; color: #ba5555 !important; } +.tag.neutral { + background: #eef2f7; + color: #63748c !important; +} + +.ssh-server-list { + display: grid; + gap: 14px; +} + +.ssh-server-card { + display: grid; + gap: 14px; + padding: 18px; + border: 1px solid #dfe7f1; + border-radius: 16px; + background: linear-gradient(135deg, #ffffff, #f8fbff); +} + +.ssh-server-card-heading, +.ssh-orphaned-heading, +.ssh-server-command { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; +} + +.ssh-server-meta { + display: flex; + flex-wrap: wrap; + gap: 8px 18px; + color: #718198; + font-size: 12px; +} + +.ssh-server-details { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 12px; +} + +.ssh-server-detail { + display: grid; + gap: 5px; + min-width: 0; + padding: 12px; + border: 1px solid #e4ebf4; + border-radius: 12px; + background: #fff; + color: #718198; + font-size: 12px; + line-height: 1.5; +} + +.ssh-server-detail strong { + color: #30435f; + font-size: 13px; +} + +.detail-label { + color: #8a98ab; + font-size: 11px; + letter-spacing: 0.04em; +} + +.ssh-server-detail code, +.ssh-server-command code { + overflow-wrap: anywhere; + color: #345f94; + font-family: ui-monospace, SFMono-Regular, Consolas, monospace; + font-size: 12px; +} + +.inline-error { + margin: 0; +} + +.ssh-server-command { + padding: 10px 12px; + border: 1px solid #d8e5f4; + border-radius: 12px; + background: #f3f8ff; +} + +.ssh-server-command > div { + display: grid; + gap: 5px; + min-width: 0; +} + +.ssh-orphaned-keys { + display: grid; + gap: 10px; + margin-top: 18px; + padding-top: 16px; + border-top: 1px solid #e7edf5; +} + +.ssh-orphaned-heading > div { + display: grid; + gap: 3px; +} + +.ssh-orphaned-list { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.ssh-orphaned-item { + display: inline-flex !important; + align-items: center; + gap: 8px; + padding: 7px 10px; + border: 1px solid #e1e8f1; + border-radius: 9px; + background: #fbfcfe; + color: #6f7f94; + font-size: 12px; +} + +@media (max-width: 900px) { + .ssh-server-details { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 640px) { + .ssh-server-card-heading, + .ssh-orphaned-heading, + .ssh-server-command { + align-items: stretch; + flex-direction: column; + } + + .ssh-server-details { + grid-template-columns: minmax(0, 1fr); + } + + .ssh-server-command .inline-button { + align-self: flex-start; + } +} .modal-backdrop { position: fixed; inset: 0; diff --git a/frontend/src/views/Dashboard/Dashboard.js b/frontend/src/views/Dashboard/Dashboard.js index 89b1327..6d6fb0f 100644 --- a/frontend/src/views/Dashboard/Dashboard.js +++ b/frontend/src/views/Dashboard/Dashboard.js @@ -516,6 +516,49 @@ export function useDashboard() { return labels[value] || (value ? "未知" : "—"); } + function serverSSHKey(server) { + if (!server?.private_key_ref) return null; + return sshKeys.value.find((item) => item.reference === server.private_key_ref) || null; + } + + function serverSSHKeyStatusLabel(server) { + const labels = { + not_configured: "未配置", + pending: "部署中", + deployed: "已部署并验证", + manual: "已配置,待验证", + failed: "部署失败,保留密码回退", + }; + return labels[server?.ssh_key_status] || "未配置"; + } + + function serverSSHKeyStatusClass(server) { + if (server?.ssh_key_status === "deployed") return "success"; + if (server?.ssh_key_status === "failed") return "danger"; + if (server?.ssh_key_status === "pending") return "warning"; + return "neutral"; + } + + function serverSSHCommand(server) { + if (!server?.address || !server?.username) return "—"; + const port = Number(server.port || 22); + const portOption = port === 22 ? "" : ` -p ${port}`; + return `ssh${portOption} ${server.username}@${server.address}`; + } + + function serverSSHKeyHint(server) { + if (server?.ssh_key_status === "deployed") { + return "Scert 已自动部署公钥并验证密钥登录,后续部署默认使用托管私钥。"; + } + if (server?.ssh_key_status === "failed") { + return server.ssh_key_error || "公钥部署或密钥登录验证失败,当前仍使用密码回退。"; + } + if (server?.auth_method === "private_key") { + return "当前使用已配置的私钥连接,尚未完成自动部署验证。"; + } + return "保存服务器后,Scert 会先用密码部署公钥,再自动验证密钥登录。"; + } + function formatDate(value) { if (!value) return "尚未签发"; return new Intl.DateTimeFormat("zh-CN", { @@ -1318,6 +1361,11 @@ export function useDashboard() { environmentLabels, webServerLabels, serverAuthLabel, + serverSSHKey, + serverSSHKeyStatusLabel, + serverSSHKeyStatusClass, + serverSSHCommand, + serverSSHKeyHint, isEditing, formTitle, form, diff --git a/frontend/src/views/Dashboard/Dashboard.vue b/frontend/src/views/Dashboard/Dashboard.vue index 26f7f33..e285612 100644 --- a/frontend/src/views/Dashboard/Dashboard.vue +++ b/frontend/src/views/Dashboard/Dashboard.vue @@ -58,6 +58,11 @@ const { dnsProviders, environmentLabels, webServerLabels, + serverSSHKey, + serverSSHKeyStatusLabel, + serverSSHKeyStatusClass, + serverSSHCommand, + serverSSHKeyHint, isEditing, formTitle, form, @@ -415,41 +420,92 @@ const { activeSection, activeLabel, sections, selectSection } = useDashboardNavi
安全凭据
-{{ serverSSHKey(item)?.fingerprint || item.ssh_key_fingerprint || "—" }}
+ {{ serverSSHCommand(item) }}
+