拆分私钥录入弹窗并修复底部裁切及悬浮说明
This commit is contained in:
parent
ec91ebd487
commit
cfd9efa6f3
@ -454,3 +454,65 @@ test("changePassword.loadsScopedBaseStylesAndCompiles", async () => {
|
|||||||
assert.match(base, /\.modal-backdrop\s*\{[^}]*position:\s*fixed;/);
|
assert.match(base, /\.modal-backdrop\s*\{[^}]*position:\s*fixed;/);
|
||||||
assert.match(base, /\.field\s*\{/);
|
assert.match(base, /\.field\s*\{/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("sshKeyModal.compilesAndPreservesCredentialFields", async () => {
|
||||||
|
const { readFile } = await import("node:fs/promises");
|
||||||
|
const { parse, compileScript, compileTemplate } = await import("@vue/compiler-sfc");
|
||||||
|
const { renderToString } = await import("@vue/server-renderer");
|
||||||
|
const Vue = await import("vue");
|
||||||
|
const source = await readFile(new URL("./SSHKeyModal.vue", import.meta.url), "utf8");
|
||||||
|
const { descriptor, errors } = parse(source);
|
||||||
|
assert.deepEqual(errors, []);
|
||||||
|
assert.ok(compileScript(descriptor, { id: "ssh-key-modal" }));
|
||||||
|
const compiled = compileTemplate({
|
||||||
|
source: descriptor.template.content,
|
||||||
|
filename: "SSHKeyModal.vue",
|
||||||
|
id: "ssh-key-modal",
|
||||||
|
compilerOptions: { mode: "function" },
|
||||||
|
});
|
||||||
|
assert.deepEqual(compiled.errors, []);
|
||||||
|
assert.ok(descriptor.styles.some(style => style.scoped && style.src === "./Dashboard.css"));
|
||||||
|
assert.ok(descriptor.styles.some(style => style.scoped && style.src === "./SSHKeyModal.css"));
|
||||||
|
assert.doesNotMatch(source, /<HelpTip\s+class=/);
|
||||||
|
const render = new Function("Vue", compiled.code)(Vue);
|
||||||
|
for (const platform of ["windows", "linux"]) {
|
||||||
|
const app = Vue.createSSRApp({
|
||||||
|
render,
|
||||||
|
setup: () => ({
|
||||||
|
showSSHKeyForm: true,
|
||||||
|
closeSSHKeyForm: () => {},
|
||||||
|
saveSSHKey: () => {},
|
||||||
|
sshKeyPlatform: platform,
|
||||||
|
sshKeyFileName: "test-key",
|
||||||
|
sshKeyPrivatePath: "/test/private",
|
||||||
|
sshKeyPublicPath: "/test/public",
|
||||||
|
sshKeyCommandUsername: "deploy",
|
||||||
|
sshKeyCommandHost: "example.test",
|
||||||
|
copySSHKeyCommand: () => {},
|
||||||
|
sshKeyCommands: { windows: "generate-windows", linux: "generate-linux" },
|
||||||
|
sshPublicKeyCommands: { windows: "install-windows", linux: "install-linux" },
|
||||||
|
sshKeyForm: { name: "", private_key: "", passphrase: "" },
|
||||||
|
importSSHKeyFile: () => {},
|
||||||
|
sshKeyPassphraseVisible: false,
|
||||||
|
sshKeyFormError: "test-validation-error",
|
||||||
|
sshKeySaving: false,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
app.component("HelpTip", {
|
||||||
|
setup: (_, { slots }) => () => Vue.h("span", { class: "test-help-tip" }, slots.default?.()),
|
||||||
|
});
|
||||||
|
const html = await renderToString(app);
|
||||||
|
assert.match(html, /class="modal-card ssh-key-modal"/);
|
||||||
|
assert.match(html, /type="submit"/);
|
||||||
|
assert.match(html, /type="file"/);
|
||||||
|
assert.match(html, /type="password"/);
|
||||||
|
assert.match(html, /rows="5"/);
|
||||||
|
assert.match(html, /test-validation-error/);
|
||||||
|
assert.equal((html.match(/ required/g) || []).length, 2);
|
||||||
|
assert.ok(html.includes(`generate-${platform}`));
|
||||||
|
assert.ok(html.includes(`install-${platform}`));
|
||||||
|
assert.ok(html.includes("deploy@example.test"));
|
||||||
|
}
|
||||||
|
const css = await readFile(new URL("./SSHKeyModal.css", import.meta.url), "utf8");
|
||||||
|
assert.match(css, /\.ssh-key-modal\s*\{[^}]*overflow:\s*auto;/);
|
||||||
|
});
|
||||||
@ -4,6 +4,7 @@ import UserManagement from "../UserManagement/UserManagement.vue";
|
|||||||
import SettingsModal from "./SettingsModal.vue";
|
import SettingsModal from "./SettingsModal.vue";
|
||||||
import DomainModal from "./DomainModal.vue";
|
import DomainModal from "./DomainModal.vue";
|
||||||
import ServerModal from "./ServerModal.vue";
|
import ServerModal from "./ServerModal.vue";
|
||||||
|
import SSHKeyModal from "./SSHKeyModal.vue";
|
||||||
import { useDashboard } from "./Dashboard.js";
|
import { useDashboard } from "./Dashboard.js";
|
||||||
|
|
||||||
const dashboard = useDashboard();
|
const dashboard = useDashboard();
|
||||||
@ -474,216 +475,8 @@ const {
|
|||||||
|
|
||||||
<ServerModal v-if="showServerForm" :state="dashboard" />
|
<ServerModal v-if="showServerForm" :state="dashboard" />
|
||||||
|
|
||||||
<div
|
<SSHKeyModal v-if="showSSHKeyForm" :state="dashboard" />
|
||||||
v-if="showSSHKeyForm"
|
|
||||||
class="modal-backdrop"
|
|
||||||
@click.self="closeSSHKeyForm"
|
|
||||||
>
|
|
||||||
<section
|
|
||||||
class="modal-card ssh-key-modal"
|
|
||||||
role="dialog"
|
|
||||||
aria-modal="true"
|
|
||||||
aria-label="添加 SSH 私钥"
|
|
||||||
>
|
|
||||||
<div class="modal-heading">
|
|
||||||
<div>
|
|
||||||
<p class="eyebrow">安全凭据</p>
|
|
||||||
<h2>添加 SSH 私钥</h2>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
class="close-button"
|
|
||||||
type="button"
|
|
||||||
aria-label="关闭"
|
|
||||||
@click="closeSSHKeyForm"
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<form @submit.prevent="saveSSHKey">
|
|
||||||
<section class="ssh-key-guide">
|
|
||||||
<div class="ssh-key-guide-heading">
|
|
||||||
<div>
|
|
||||||
<strong>不会准备 SSH 密钥?</strong>
|
|
||||||
<span>按下面三步操作,完成生成、安装公钥和录入私钥。</span>
|
|
||||||
</div>
|
|
||||||
<span class="guide-badge">新手引导</span>
|
|
||||||
</div>
|
|
||||||
<div class="ssh-key-steps">
|
|
||||||
<article>
|
|
||||||
<b>1</b>
|
|
||||||
<div>
|
|
||||||
<strong>在本机生成密钥</strong>
|
|
||||||
<span>打开 PowerShell、终端或命令提示符,复制下面的命令执行。</span>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
<article>
|
|
||||||
<b>2</b>
|
|
||||||
<div>
|
|
||||||
<strong>把公钥放到服务器</strong>
|
|
||||||
<span>公钥放入服务器的授权文件,私钥不要粘贴到服务器。</span>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
<article>
|
|
||||||
<b>3</b>
|
|
||||||
<div>
|
|
||||||
<strong>录入私钥并保存</strong>
|
|
||||||
<span>可以直接粘贴,也可以选择本机私钥文件,Scert 会加密保存。</span>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
<div class="ssh-key-platforms" role="tablist" aria-label="密钥生成平台">
|
|
||||||
<button
|
|
||||||
class="platform-tab"
|
|
||||||
:class="{ active: sshKeyPlatform === 'windows' }"
|
|
||||||
type="button"
|
|
||||||
role="tab"
|
|
||||||
:aria-selected="sshKeyPlatform === 'windows'"
|
|
||||||
@click="sshKeyPlatform = 'windows'"
|
|
||||||
>
|
|
||||||
Windows
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="platform-tab"
|
|
||||||
:class="{ active: sshKeyPlatform === 'linux' }"
|
|
||||||
type="button"
|
|
||||||
role="tab"
|
|
||||||
:aria-selected="sshKeyPlatform === 'linux'"
|
|
||||||
@click="sshKeyPlatform = 'linux'"
|
|
||||||
>
|
|
||||||
Linux / macOS
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="ssh-key-path-line">
|
|
||||||
<span>本次密钥文件名</span>
|
|
||||||
<code>{{ sshKeyFileName }}</code>
|
|
||||||
<span class="guide-badge">每次随机生成</span>
|
|
||||||
</div>
|
|
||||||
<div class="ssh-key-path-summary">
|
|
||||||
<div>
|
|
||||||
<span>本地私钥位置</span>
|
|
||||||
<code>{{ sshKeyPrivatePath }}</code>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span>本地公钥位置</span>
|
|
||||||
<code>{{ sshKeyPublicPath }}</code>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span>远程登录目标</span>
|
|
||||||
<code>{{ sshKeyCommandUsername }}@{{ sshKeyCommandHost }}</code>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="ssh-command-card">
|
|
||||||
<div class="ssh-command-heading">
|
|
||||||
<span>生成命令</span>
|
|
||||||
<button
|
|
||||||
class="inline-button"
|
|
||||||
type="button"
|
|
||||||
@click="
|
|
||||||
copySSHKeyCommand(
|
|
||||||
sshKeyCommands[sshKeyPlatform],
|
|
||||||
'密钥生成命令已复制'
|
|
||||||
)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
复制命令
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<code>{{ sshKeyCommands[sshKeyPlatform] }}</code>
|
|
||||||
</div>
|
|
||||||
<div class="ssh-command-card">
|
|
||||||
<div class="ssh-command-heading">
|
|
||||||
<span>安装公钥命令</span>
|
|
||||||
<button
|
|
||||||
class="inline-button"
|
|
||||||
type="button"
|
|
||||||
@click="
|
|
||||||
copySSHKeyCommand(
|
|
||||||
sshPublicKeyCommands[sshKeyPlatform],
|
|
||||||
'公钥安装命令已复制'
|
|
||||||
)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
复制命令
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<code>{{ sshPublicKeyCommands[sshKeyPlatform] }}</code>
|
|
||||||
<small>
|
|
||||||
本地公钥文件就是上面显示的随机路径;安装后会追加到远程用户的
|
|
||||||
<code>~/.ssh/authorized_keys</code>。每台主机、每次新建配置都会生成新的文件名。
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
<p class="ssh-key-guide-note">
|
|
||||||
懒人方式:如果你能登录服务器控制台,只需执行生成命令,再把随机路径中的公钥内容追加到授权文件,最后在下方选择私钥文件即可。
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
<div class="ssh-key-form-fields">
|
|
||||||
<label class="field"
|
|
||||||
><span>私钥名称</span
|
|
||||||
><input
|
|
||||||
v-model="sshKeyForm.name"
|
|
||||||
required
|
|
||||||
type="text"
|
|
||||||
maxlength="128"
|
|
||||||
placeholder="例如生产环境部署私钥"
|
|
||||||
autocomplete="off"
|
|
||||||
/></label>
|
|
||||||
<label class="field"
|
|
||||||
><span>读取本机私钥文件 <small>可选,读取后仍需点击加密保存</small></span
|
|
||||||
><input type="file" @change="importSSHKeyFile" /></label
|
|
||||||
>
|
|
||||||
<label class="field"
|
|
||||||
><span
|
|
||||||
>私钥内容
|
|
||||||
<small>支持 RSA、ECDSA、Ed25519 和 OpenSSH 私钥</small></span
|
|
||||||
><textarea
|
|
||||||
v-model="sshKeyForm.private_key"
|
|
||||||
required
|
|
||||||
rows="11"
|
|
||||||
spellcheck="false"
|
|
||||||
placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"
|
|
||||||
></textarea>
|
|
||||||
</label>
|
|
||||||
<label class="field"
|
|
||||||
><span class="field-label-row"
|
|
||||||
><span>私钥口令 <small>未加密私钥请留空</small></span
|
|
||||||
><button
|
|
||||||
class="inline-button"
|
|
||||||
type="button"
|
|
||||||
@click="sshKeyPassphraseVisible = !sshKeyPassphraseVisible"
|
|
||||||
>
|
|
||||||
{{ sshKeyPassphraseVisible ? "隐藏口令" : "显示口令" }}
|
|
||||||
</button></span
|
|
||||||
><input
|
|
||||||
v-model="sshKeyForm.passphrase"
|
|
||||||
:type="sshKeyPassphraseVisible ? 'text' : 'password'"
|
|
||||||
maxlength="4096"
|
|
||||||
autocomplete="new-password"
|
|
||||||
/></label>
|
|
||||||
<p class="security-hint">
|
|
||||||
保存后私钥和口令只进入本机加密敏感信息存储,后续接口不会返回明文。
|
|
||||||
</p>
|
|
||||||
<p v-if="sshKeyFormError" class="form-error">{{ sshKeyFormError }}</p>
|
|
||||||
<div class="modal-actions">
|
|
||||||
<button
|
|
||||||
class="secondary-button"
|
|
||||||
type="button"
|
|
||||||
:disabled="sshKeySaving"
|
|
||||||
@click="closeSSHKeyForm"
|
|
||||||
>
|
|
||||||
取消</button
|
|
||||||
><button
|
|
||||||
class="primary-button"
|
|
||||||
type="submit"
|
|
||||||
:disabled="sshKeySaving"
|
|
||||||
>
|
|
||||||
{{ sshKeySaving ? "加密保存中……" : "加密保存" }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div v-if="showTaskDetails" class="modal-backdrop" @click.self="closeTaskDetails">
|
<div v-if="showTaskDetails" class="modal-backdrop" @click.self="closeTaskDetails">
|
||||||
<section class="modal-card task-details-modal" role="dialog" aria-modal="true" aria-label="任务详情">
|
<section class="modal-card task-details-modal" role="dialog" aria-modal="true" aria-label="任务详情">
|
||||||
<div class="modal-heading">
|
<div class="modal-heading">
|
||||||
|
|||||||
131
frontend/src/views/Dashboard/SSHKeyModal.css
Normal file
131
frontend/src/views/Dashboard/SSHKeyModal.css
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
.modal-backdrop.ssh-key-backdrop {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ssh-key-modal {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: min(100%, 1180px);
|
||||||
|
max-height: calc(100dvh - 32px);
|
||||||
|
overflow: auto;
|
||||||
|
padding: 18px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ssh-key-modal .modal-heading {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ssh-key-modal form {
|
||||||
|
grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
|
||||||
|
gap: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ssh-key-modal .ssh-key-guide,
|
||||||
|
.ssh-key-modal .ssh-key-form-fields {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ssh-key-modal .ssh-key-guide-heading {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ssh-key-modal .ssh-key-guide-heading > div:first-child,
|
||||||
|
.ssh-key-modal .ssh-key-steps article > div,
|
||||||
|
.ssh-key-modal .field > span,
|
||||||
|
.ssh-key-modal .field-label-row > span {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ssh-key-modal .ssh-key-steps {
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ssh-key-modal .ssh-key-steps article {
|
||||||
|
gap: 5px;
|
||||||
|
padding: 7px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ssh-key-modal .ssh-key-steps strong {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ssh-key-modal .ssh-key-platforms {
|
||||||
|
margin: 8px 0;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ssh-key-modal .platform-tab {
|
||||||
|
padding: 6px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ssh-key-modal .ssh-key-path-line,
|
||||||
|
.ssh-key-modal .ssh-key-path-summary {
|
||||||
|
gap: 5px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
margin: 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ssh-key-modal .ssh-key-path-summary {
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.ssh-key-modal .ssh-key-path-summary > div:last-child {
|
||||||
|
grid-column: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ssh-key-modal .ssh-command-card {
|
||||||
|
margin-top: 6px;
|
||||||
|
padding: 7px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ssh-key-modal .ssh-command-heading {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ssh-key-modal .ssh-command-card > code {
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ssh-key-modal .field {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ssh-key-modal .field input,
|
||||||
|
.ssh-key-modal .field textarea {
|
||||||
|
min-height: 36px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ssh-key-modal .ssh-key-form-fields .field textarea {
|
||||||
|
min-height: 130px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ssh-key-modal .ssh-key-form-fields .modal-actions {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.ssh-key-modal form {
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ssh-key-modal .ssh-key-form-fields {
|
||||||
|
grid-column: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.ssh-key-modal {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ssh-key-modal .ssh-key-path-summary {
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
240
frontend/src/views/Dashboard/SSHKeyModal.vue
Normal file
240
frontend/src/views/Dashboard/SSHKeyModal.vue
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
<script setup>
|
||||||
|
import HelpTip from "../components/HelpTip/HelpTip.vue";
|
||||||
|
|
||||||
|
const props = defineProps({ state: { type: Object, required: true } });
|
||||||
|
const {
|
||||||
|
showSSHKeyForm,
|
||||||
|
closeSSHKeyForm,
|
||||||
|
saveSSHKey,
|
||||||
|
sshKeyPlatform,
|
||||||
|
sshKeyFileName,
|
||||||
|
sshKeyPrivatePath,
|
||||||
|
sshKeyPublicPath,
|
||||||
|
sshKeyCommandUsername,
|
||||||
|
sshKeyCommandHost,
|
||||||
|
copySSHKeyCommand,
|
||||||
|
sshKeyCommands,
|
||||||
|
sshPublicKeyCommands,
|
||||||
|
sshKeyForm,
|
||||||
|
importSSHKeyFile,
|
||||||
|
sshKeyPassphraseVisible,
|
||||||
|
sshKeyFormError,
|
||||||
|
sshKeySaving,
|
||||||
|
} = props.state;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="showSSHKeyForm"
|
||||||
|
class="modal-backdrop ssh-key-backdrop"
|
||||||
|
@click.self="closeSSHKeyForm"
|
||||||
|
>
|
||||||
|
<section
|
||||||
|
class="modal-card ssh-key-modal"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-label="添加 SSH 私钥"
|
||||||
|
>
|
||||||
|
<div class="modal-heading">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">安全凭据</p>
|
||||||
|
<h2>添加 SSH 私钥</h2>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="close-button"
|
||||||
|
type="button"
|
||||||
|
aria-label="关闭"
|
||||||
|
@click="closeSSHKeyForm"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<form @submit.prevent="saveSSHKey">
|
||||||
|
<section class="ssh-key-guide">
|
||||||
|
<div class="ssh-key-guide-heading">
|
||||||
|
<div>
|
||||||
|
<strong>不会准备 SSH 密钥?</strong>
|
||||||
|
<HelpTip>按下面三步操作,完成生成、安装公钥和录入私钥。</HelpTip>
|
||||||
|
</div>
|
||||||
|
<span class="guide-badge">新手引导</span>
|
||||||
|
</div>
|
||||||
|
<div class="ssh-key-steps">
|
||||||
|
<article>
|
||||||
|
<b>1</b>
|
||||||
|
<div>
|
||||||
|
<strong>在本机生成密钥</strong>
|
||||||
|
<HelpTip>打开 PowerShell、终端或命令提示符,复制下面的命令执行。</HelpTip>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<article>
|
||||||
|
<b>2</b>
|
||||||
|
<div>
|
||||||
|
<strong>把公钥放到服务器</strong>
|
||||||
|
<HelpTip>公钥放入服务器的授权文件,私钥不要粘贴到服务器。</HelpTip>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<article>
|
||||||
|
<b>3</b>
|
||||||
|
<div>
|
||||||
|
<strong>录入私钥并保存</strong>
|
||||||
|
<HelpTip>可以直接粘贴,也可以选择本机私钥文件,Scert 会加密保存。</HelpTip>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
<div class="ssh-key-platforms" role="tablist" aria-label="密钥生成平台">
|
||||||
|
<button
|
||||||
|
class="platform-tab"
|
||||||
|
:class="{ active: sshKeyPlatform === 'windows' }"
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
:aria-selected="sshKeyPlatform === 'windows'"
|
||||||
|
@click="sshKeyPlatform = 'windows'"
|
||||||
|
>
|
||||||
|
Windows
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="platform-tab"
|
||||||
|
:class="{ active: sshKeyPlatform === 'linux' }"
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
:aria-selected="sshKeyPlatform === 'linux'"
|
||||||
|
@click="sshKeyPlatform = 'linux'"
|
||||||
|
>
|
||||||
|
Linux / macOS
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="ssh-key-path-line">
|
||||||
|
<span>本次密钥文件名</span>
|
||||||
|
<code>{{ sshKeyFileName }}</code>
|
||||||
|
<span class="guide-badge">每次随机生成</span>
|
||||||
|
</div>
|
||||||
|
<div class="ssh-key-path-summary">
|
||||||
|
<div>
|
||||||
|
<span>本地私钥位置</span>
|
||||||
|
<code>{{ sshKeyPrivatePath }}</code>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>本地公钥位置</span>
|
||||||
|
<code>{{ sshKeyPublicPath }}</code>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>远程登录目标</span>
|
||||||
|
<code>{{ sshKeyCommandUsername }}@{{ sshKeyCommandHost }}</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ssh-command-card">
|
||||||
|
<div class="ssh-command-heading">
|
||||||
|
<span>生成命令</span>
|
||||||
|
<button
|
||||||
|
class="inline-button"
|
||||||
|
type="button"
|
||||||
|
@click="
|
||||||
|
copySSHKeyCommand(
|
||||||
|
sshKeyCommands[sshKeyPlatform],
|
||||||
|
'密钥生成命令已复制'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
复制命令
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<code>{{ sshKeyCommands[sshKeyPlatform] }}</code>
|
||||||
|
</div>
|
||||||
|
<div class="ssh-command-card">
|
||||||
|
<div class="ssh-command-heading">
|
||||||
|
<span>安装公钥命令</span>
|
||||||
|
<button
|
||||||
|
class="inline-button"
|
||||||
|
type="button"
|
||||||
|
@click="
|
||||||
|
copySSHKeyCommand(
|
||||||
|
sshPublicKeyCommands[sshKeyPlatform],
|
||||||
|
'公钥安装命令已复制'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
复制命令
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<code>{{ sshPublicKeyCommands[sshKeyPlatform] }}</code>
|
||||||
|
<HelpTip>
|
||||||
|
本地公钥文件就是上面显示的随机路径;安装后会追加到远程用户的
|
||||||
|
<code>~/.ssh/authorized_keys</code>。每台主机、每次新建配置都会生成新的文件名。
|
||||||
|
</HelpTip>
|
||||||
|
</div>
|
||||||
|
<HelpTip>
|
||||||
|
懒人方式:如果你能登录服务器控制台,只需执行生成命令,再把随机路径中的公钥内容追加到授权文件,最后在下方选择私钥文件即可。
|
||||||
|
</HelpTip>
|
||||||
|
</section>
|
||||||
|
<div class="ssh-key-form-fields">
|
||||||
|
<label class="field"
|
||||||
|
><span>私钥名称</span
|
||||||
|
><input
|
||||||
|
v-model="sshKeyForm.name"
|
||||||
|
required
|
||||||
|
type="text"
|
||||||
|
maxlength="128"
|
||||||
|
placeholder="例如生产环境部署私钥"
|
||||||
|
autocomplete="off"
|
||||||
|
/></label>
|
||||||
|
<label class="field"
|
||||||
|
><span>读取本机私钥文件 <HelpTip>可选,读取后仍需点击加密保存</HelpTip></span
|
||||||
|
><input type="file" @change="importSSHKeyFile" /></label
|
||||||
|
>
|
||||||
|
<label class="field"
|
||||||
|
><span
|
||||||
|
>私钥内容
|
||||||
|
<HelpTip>支持 RSA、ECDSA、Ed25519 和 OpenSSH 私钥</HelpTip></span
|
||||||
|
><textarea
|
||||||
|
v-model="sshKeyForm.private_key"
|
||||||
|
required
|
||||||
|
rows="5"
|
||||||
|
spellcheck="false"
|
||||||
|
placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"
|
||||||
|
></textarea>
|
||||||
|
</label>
|
||||||
|
<label class="field"
|
||||||
|
><span class="field-label-row"
|
||||||
|
><span>私钥口令 <HelpTip>未加密私钥请留空</HelpTip></span
|
||||||
|
><button
|
||||||
|
class="inline-button"
|
||||||
|
type="button"
|
||||||
|
@click="sshKeyPassphraseVisible = !sshKeyPassphraseVisible"
|
||||||
|
>
|
||||||
|
{{ sshKeyPassphraseVisible ? "隐藏口令" : "显示口令" }}
|
||||||
|
</button></span
|
||||||
|
><input
|
||||||
|
v-model="sshKeyForm.passphrase"
|
||||||
|
:type="sshKeyPassphraseVisible ? 'text' : 'password'"
|
||||||
|
maxlength="4096"
|
||||||
|
autocomplete="new-password"
|
||||||
|
/></label>
|
||||||
|
<HelpTip>
|
||||||
|
保存后私钥和口令只进入本机加密敏感信息存储,后续接口不会返回明文。
|
||||||
|
</HelpTip>
|
||||||
|
<p v-if="sshKeyFormError" class="form-error">{{ sshKeyFormError }}</p>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button
|
||||||
|
class="secondary-button"
|
||||||
|
type="button"
|
||||||
|
:disabled="sshKeySaving"
|
||||||
|
@click="closeSSHKeyForm"
|
||||||
|
>
|
||||||
|
取消</button
|
||||||
|
><button
|
||||||
|
class="primary-button"
|
||||||
|
type="submit"
|
||||||
|
:disabled="sshKeySaving"
|
||||||
|
>
|
||||||
|
{{ sshKeySaving ? "加密保存中……" : "加密保存" }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped src="./Dashboard.css"></style>
|
||||||
|
<style scoped src="./SSHKeyModal.css"></style>
|
||||||
Loading…
Reference in New Issue
Block a user