合并服务器配置与部署命令流程
将服务器连接、证书路径、校验命令和重载命令统一到同一表单,并优化 SSH 引导布局。
This commit is contained in:
parent
6e43d5e3a2
commit
7b9329433a
@ -596,7 +596,7 @@ test("serverModal.compilesAndPreservesDeploymentFields", async () => {
|
||||
assert.match(html, /value="22"/);
|
||||
assert.match(html, /min="1"/);
|
||||
assert.match(html, /max="65535"/);
|
||||
assert.equal((html.match(/class="test-help-tip"/g) || []).length, 4);
|
||||
assert.equal((html.match(/class="test-help-tip"/g) || []).length, 5);
|
||||
for (const path of ["/test/cert.pem", "/test/fullchain.pem", "/test/key.pem"]) {
|
||||
assert.ok(html.includes(path));
|
||||
}
|
||||
|
||||
@ -17,24 +17,62 @@
|
||||
.server-modal form {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 0 18px;
|
||||
}
|
||||
|
||||
.server-modal form > .form-grid,
|
||||
.server-modal form > .field {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.server-modal .form-grid {
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.server-modal .server-form-section {
|
||||
display: grid;
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 0 18px;
|
||||
min-width: 0;
|
||||
padding: 14px 0 4px;
|
||||
border-top: 1px solid #dce6f1;
|
||||
}
|
||||
|
||||
.server-modal .server-form-section:first-child {
|
||||
padding-top: 2px;
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.server-modal .server-form-section-heading,
|
||||
.server-modal .server-form-grid,
|
||||
.server-modal .server-paths,
|
||||
.server-modal .form-error,
|
||||
.server-modal .modal-actions {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.server-modal .server-form-section-heading {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.server-modal .server-form-section-heading h3 {
|
||||
margin: 2px 0 0;
|
||||
color: #214d86;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.server-modal .server-form-heading-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.server-modal .server-form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 0 18px;
|
||||
}
|
||||
|
||||
.server-modal .server-form-field-wide {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.server-modal .server-paths {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
@ -75,29 +113,13 @@
|
||||
}
|
||||
|
||||
.server-modal .server-ssh-key-guide {
|
||||
grid-column: 1 / -1;
|
||||
min-width: 0;
|
||||
margin: 2px 0 8px;
|
||||
margin: 0;
|
||||
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));
|
||||
@ -229,13 +251,17 @@
|
||||
border-top: 1px solid #d1e0f1;
|
||||
}
|
||||
|
||||
.server-modal .server-command-section textarea {
|
||||
min-height: 82px;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.server-modal form {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.server-modal form > .field {
|
||||
grid-column: span 1;
|
||||
.server-modal .server-form-section {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.server-modal .server-paths {
|
||||
@ -253,12 +279,15 @@
|
||||
}
|
||||
|
||||
.server-modal form,
|
||||
.server-modal .form-grid {
|
||||
.server-modal .server-form-section,
|
||||
.server-modal .server-form-grid {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.server-modal form > .form-grid,
|
||||
.server-modal form > .field {
|
||||
.server-modal .server-form-section,
|
||||
.server-modal .server-form-section-heading,
|
||||
.server-modal .server-form-grid,
|
||||
.server-modal .server-form-field-wide {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
|
||||
@ -61,103 +61,130 @@ const {
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<form @submit.prevent="saveServer">
|
||||
<div class="form-grid">
|
||||
<label class="field"
|
||||
><span>服务器名称</span
|
||||
><input
|
||||
v-model="serverForm.name"
|
||||
required
|
||||
type="text"
|
||||
placeholder="例如生产 Nginx" /></label
|
||||
><label class="field"
|
||||
><span>网站服务</span
|
||||
><select v-model="serverForm.web_server">
|
||||
<option value="nginx">Nginx</option>
|
||||
<option value="caddy">Caddy</option>
|
||||
<option value="apache">Apache</option>
|
||||
<option value="custom">自定义</option>
|
||||
</select></label
|
||||
>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<label class="field"
|
||||
><span>服务器地址</span
|
||||
><input
|
||||
v-model="serverForm.address"
|
||||
required
|
||||
type="text"
|
||||
placeholder="IP 或主机名" /></label
|
||||
><label class="field"
|
||||
><span>SSH 端口</span
|
||||
><input
|
||||
v-model.number="serverForm.port"
|
||||
required
|
||||
type="number"
|
||||
min="1"
|
||||
max="65535"
|
||||
/></label>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<label class="field"
|
||||
><span>SSH 用户</span
|
||||
><input
|
||||
v-model="serverForm.username"
|
||||
required
|
||||
type="text" /></label
|
||||
><label class="field"
|
||||
><span>认证方式</span
|
||||
><select v-model="serverForm.auth_method">
|
||||
<option value="password">SSH 密码</option>
|
||||
<option value="private_key">SSH 私钥</option>
|
||||
</select></label
|
||||
>
|
||||
</div>
|
||||
<label v-if="serverForm.auth_method === 'password'" class="field">
|
||||
<span>SSH 密码</span>
|
||||
<input
|
||||
v-model="serverForm.password"
|
||||
type="password"
|
||||
:required="!serverEditingId || !serverForm.passwordConfigured"
|
||||
autocomplete="new-password"
|
||||
placeholder="编辑时留空表示保留原密码"
|
||||
/>
|
||||
</label>
|
||||
<div v-else class="form-grid">
|
||||
<label class="field"
|
||||
><span class="field-label-row"
|
||||
><span>SSH 私钥</span
|
||||
><button
|
||||
class="inline-button"
|
||||
type="button"
|
||||
@click="openSSHKeyCreate({ embedded: true })"
|
||||
>
|
||||
新建或导入
|
||||
</button></span
|
||||
><select v-model="serverForm.private_key_ref" required>
|
||||
<option value="">请选择已加密保存的私钥</option>
|
||||
<option
|
||||
v-for="key in sshKeys"
|
||||
:key="key.id"
|
||||
:value="key.reference"
|
||||
>
|
||||
{{ key.name }} · {{ key.algorithm }}
|
||||
</option></select
|
||||
><HelpTip v-if="sshKeys.length === 0"
|
||||
>还没有私钥?点击“新建或导入”,按照引导生成、粘贴或读取私钥文件。</HelpTip
|
||||
></label
|
||||
>
|
||||
</div>
|
||||
<section v-if="showEmbeddedSSHKeyGuide" class="server-ssh-key-guide">
|
||||
<div class="server-ssh-key-heading">
|
||||
<form class="server-form" @submit.prevent="saveServer">
|
||||
<section class="server-form-section">
|
||||
<div class="server-form-section-heading">
|
||||
<div>
|
||||
<p class="eyebrow">服务器配置</p>
|
||||
<h3>基本信息与连接方式</h3>
|
||||
</div>
|
||||
<HelpTip>
|
||||
这里的内容、SSH 认证方式、证书路径和服务命令会作为同一台部署服务器的完整配置保存。
|
||||
</HelpTip>
|
||||
</div>
|
||||
<div class="server-form-grid">
|
||||
<label class="field">
|
||||
<span>服务器名称</span>
|
||||
<input
|
||||
v-model="serverForm.name"
|
||||
required
|
||||
type="text"
|
||||
placeholder="例如生产 Nginx"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>网站服务</span>
|
||||
<select v-model="serverForm.web_server">
|
||||
<option value="nginx">Nginx</option>
|
||||
<option value="caddy">Caddy</option>
|
||||
<option value="apache">Apache</option>
|
||||
<option value="custom">自定义</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>服务器地址</span>
|
||||
<input
|
||||
v-model="serverForm.address"
|
||||
required
|
||||
type="text"
|
||||
placeholder="IP 或主机名"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>SSH 端口</span>
|
||||
<input
|
||||
v-model.number="serverForm.port"
|
||||
required
|
||||
type="number"
|
||||
min="1"
|
||||
max="65535"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>SSH 用户</span>
|
||||
<input v-model="serverForm.username" required type="text" />
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>认证方式</span>
|
||||
<select v-model="serverForm.auth_method">
|
||||
<option value="password">SSH 密码</option>
|
||||
<option value="private_key">SSH 私钥</option>
|
||||
</select>
|
||||
</label>
|
||||
<label v-if="serverForm.auth_method === 'password'" class="field server-form-field-wide">
|
||||
<span>SSH 密码</span>
|
||||
<input
|
||||
v-model="serverForm.password"
|
||||
type="password"
|
||||
:required="!serverEditingId || !serverForm.passwordConfigured"
|
||||
autocomplete="new-password"
|
||||
placeholder="编辑时留空表示保留原密码"
|
||||
/>
|
||||
</label>
|
||||
<label v-else class="field server-form-field-wide">
|
||||
<span class="field-label-row">
|
||||
<span>SSH 私钥</span>
|
||||
<button
|
||||
class="inline-button"
|
||||
type="button"
|
||||
@click="openSSHKeyCreate({ embedded: true })"
|
||||
>
|
||||
新建或导入
|
||||
</button>
|
||||
</span>
|
||||
<select v-model="serverForm.private_key_ref" required>
|
||||
<option value="">请选择已加密保存的私钥</option>
|
||||
<option
|
||||
v-for="key in sshKeys"
|
||||
:key="key.id"
|
||||
:value="key.reference"
|
||||
>
|
||||
{{ key.name }} · {{ key.algorithm }}
|
||||
</option>
|
||||
</select>
|
||||
<HelpTip v-if="sshKeys.length === 0">
|
||||
还没有私钥?点击“新建或导入”,按照引导生成、粘贴或读取私钥文件。
|
||||
</HelpTip>
|
||||
</label>
|
||||
<label class="field server-form-field-wide">
|
||||
<span>
|
||||
SSH 主机 SHA256 指纹
|
||||
<HelpTip>请填写通过可信渠道核对过的服务器主机指纹,避免连接到错误的服务器。</HelpTip>
|
||||
</span>
|
||||
<input
|
||||
v-model="serverForm.host_key_fingerprint"
|
||||
required
|
||||
type="text"
|
||||
placeholder="SHA256:..."
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-if="showEmbeddedSSHKeyGuide" class="server-form-section server-ssh-key-guide">
|
||||
<div class="server-form-section-heading">
|
||||
<div>
|
||||
<p class="eyebrow">当前服务器</p>
|
||||
<h3>在这里准备 SSH 私钥</h3>
|
||||
<span class="muted">准备完成后会自动选中,不需要离开服务器配置。</span>
|
||||
<h3>准备 SSH 私钥</h3>
|
||||
</div>
|
||||
<div class="server-form-heading-actions">
|
||||
<HelpTip>
|
||||
按顺序执行命令后读取本机私钥,保存后会自动选中到当前服务器配置。
|
||||
</HelpTip>
|
||||
<button class="inline-button" type="button" @click="closeSSHKeyForm">
|
||||
收起引导
|
||||
</button>
|
||||
</div>
|
||||
<button class="inline-button" type="button" @click="closeSSHKeyForm">
|
||||
收起引导
|
||||
</button>
|
||||
</div>
|
||||
<div class="ssh-key-steps">
|
||||
<article>
|
||||
@ -275,72 +302,84 @@ const {
|
||||
</div>
|
||||
<p v-if="sshKeyFormError" class="form-error">{{ sshKeyFormError }}</p>
|
||||
<div class="server-ssh-key-actions">
|
||||
<span class="muted">私钥只会加密保存,服务器配置仅保存安全引用。</span>
|
||||
<HelpTip>私钥会加密保存,服务器配置中只保存安全引用。</HelpTip>
|
||||
<button class="primary-button" type="button" :disabled="sshKeySaving" @click="saveSSHKey">
|
||||
{{ sshKeySaving ? "加密保存中……" : "加密保存并用于当前服务器" }}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
<label class="field"
|
||||
><span>SSH 主机 SHA256 指纹</span
|
||||
><input
|
||||
v-model="serverForm.host_key_fingerprint"
|
||||
required
|
||||
type="text"
|
||||
placeholder="SHA256:..."
|
||||
/></label>
|
||||
<div class="server-paths">
|
||||
<label class="field"
|
||||
><span>远程证书路径</span
|
||||
><input
|
||||
v-model="serverForm.certificate_path"
|
||||
required
|
||||
type="text"
|
||||
:placeholder="defaultCertificatePath"
|
||||
/></label>
|
||||
<label class="field"
|
||||
><span>远程完整证书链路径</span
|
||||
><input
|
||||
v-model="serverForm.certificate_chain_path"
|
||||
required
|
||||
type="text"
|
||||
:placeholder="defaultCertificateChainPath"
|
||||
/></label>
|
||||
<label class="field"
|
||||
><span>远程私钥路径 <HelpTip>
|
||||
保存的是路径模板,部署时会将 {域名} 和 {证书类型}
|
||||
替换为实际值。证书类型目录固定为 single、wildcard 或 san。
|
||||
</HelpTip></span
|
||||
><input
|
||||
v-model="serverForm.private_key_path"
|
||||
required
|
||||
type="text"
|
||||
:placeholder="defaultPrivateKeyPath"
|
||||
/></label>
|
||||
</div>
|
||||
|
||||
<label class="field"
|
||||
><span
|
||||
>配置校验命令
|
||||
<HelpTip
|
||||
>每行一个参数;Nginx、Caddy、Apache 留空使用默认命令,自定义服务必须填写</HelpTip
|
||||
></span
|
||||
><textarea
|
||||
v-model="serverForm.validation_command"
|
||||
rows="2"
|
||||
placeholder="nginx -t"
|
||||
></textarea>
|
||||
</label>
|
||||
<label class="field"
|
||||
><span
|
||||
>重载命令
|
||||
<HelpTip>每行一个参数;留空使用网站服务默认命令</HelpTip></span
|
||||
><textarea
|
||||
v-model="serverForm.reload_command"
|
||||
rows="2"
|
||||
placeholder="nginx -s reload"
|
||||
></textarea>
|
||||
</label>
|
||||
<section class="server-form-section">
|
||||
<div class="server-form-section-heading">
|
||||
<div>
|
||||
<p class="eyebrow">证书部署</p>
|
||||
<h3>远程文件路径</h3>
|
||||
</div>
|
||||
<HelpTip>
|
||||
路径支持 {域名} 和 {证书类型} 占位符,部署时会替换为实际目录。
|
||||
</HelpTip>
|
||||
</div>
|
||||
<div class="server-paths">
|
||||
<label class="field">
|
||||
<span>远程证书路径</span>
|
||||
<input
|
||||
v-model="serverForm.certificate_path"
|
||||
required
|
||||
type="text"
|
||||
:placeholder="defaultCertificatePath"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>远程完整证书链路径</span>
|
||||
<input
|
||||
v-model="serverForm.certificate_chain_path"
|
||||
required
|
||||
type="text"
|
||||
:placeholder="defaultCertificateChainPath"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>远程私钥路径</span>
|
||||
<input
|
||||
v-model="serverForm.private_key_path"
|
||||
required
|
||||
type="text"
|
||||
:placeholder="defaultPrivateKeyPath"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="server-form-section server-command-section">
|
||||
<div class="server-form-section-heading">
|
||||
<div>
|
||||
<p class="eyebrow">部署命令</p>
|
||||
<h3>服务校验与重载</h3>
|
||||
</div>
|
||||
<HelpTip>
|
||||
每行填写一个命令参数;标准 Nginx、Caddy、Apache 可以留空使用默认命令,自定义服务需要填写。
|
||||
</HelpTip>
|
||||
</div>
|
||||
<div class="server-form-grid">
|
||||
<label class="field">
|
||||
<span>配置校验命令</span>
|
||||
<textarea
|
||||
v-model="serverForm.validation_command"
|
||||
rows="2"
|
||||
placeholder="nginx -t"
|
||||
></textarea>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>重载命令</span>
|
||||
<textarea
|
||||
v-model="serverForm.reload_command"
|
||||
rows="2"
|
||||
placeholder="nginx -s reload"
|
||||
></textarea>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<p v-if="serverFormError" class="form-error">{{ serverFormError }}</p>
|
||||
<div class="modal-actions">
|
||||
<button
|
||||
@ -349,13 +388,10 @@ const {
|
||||
:disabled="serverSaving"
|
||||
@click="closeServerForm"
|
||||
>
|
||||
取消</button
|
||||
><button
|
||||
class="primary-button"
|
||||
type="submit"
|
||||
:disabled="serverSaving"
|
||||
>
|
||||
{{ serverSaving ? "保存中……" : "保存服务器" }}
|
||||
取消
|
||||
</button>
|
||||
<button class="primary-button" type="submit" :disabled="serverSaving">
|
||||
{{ serverSaving ? "保存中……" : "保存服务器配置" }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user