拆分域名配置弹窗并修复多域名单屏布局与提示换行
This commit is contained in:
parent
eb9181d04b
commit
c654334436
@ -305,3 +305,62 @@ test("auditStatusRendering", async () => {
|
||||
assert.doesNotMatch(html, /danger/);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
test("domainModal.compilesAndRendersSanFieldsWithoutLiteralNewline", 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("./DomainModal.vue", import.meta.url), "utf8");
|
||||
const { descriptor, errors } = parse(source);
|
||||
assert.deepEqual(errors, []);
|
||||
assert.ok(compileScript(descriptor, { id: "domain-modal" }));
|
||||
const compiled = compileTemplate({
|
||||
source: descriptor.template.content,
|
||||
filename: "DomainModal.vue",
|
||||
id: "domain-modal",
|
||||
compilerOptions: { mode: "function" },
|
||||
});
|
||||
assert.deepEqual(compiled.errors, []);
|
||||
const render = new Function("Vue", compiled.code)(Vue);
|
||||
for (const certificateType of ["single", "wildcard", "san"]) {
|
||||
const app = Vue.createSSRApp({
|
||||
render,
|
||||
setup: () => ({
|
||||
showForm: true,
|
||||
formTitle: "test-domain",
|
||||
closeForm: () => {},
|
||||
saveDomain: () => {},
|
||||
form: {
|
||||
name: "example.test",
|
||||
certificate_type: certificateType,
|
||||
sans: "",
|
||||
acme_environment: "staging",
|
||||
dns_provider: "aliyun",
|
||||
auto_renew: true,
|
||||
server_ids: [],
|
||||
},
|
||||
dnsProviders: [],
|
||||
servers: [],
|
||||
displayLabel: (_, value) => value,
|
||||
webServerLabels: {},
|
||||
formError: "",
|
||||
saving: 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 domain-modal"/);
|
||||
assert.match(html, /type="submit"/);
|
||||
assert.match(html, /class="test-help-tip"/);
|
||||
assert.doesNotMatch(html, /www\.example\.com\\napi/);
|
||||
assert.equal(html.includes('id="domain-sans"'), certificateType === "san");
|
||||
if (certificateType === "san") {
|
||||
assert.match(html, /rows="2"/);
|
||||
assert.ok(html.includes("www.example.com\napi.example.com"));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
import ChangePassword from "../components/ChangePassword/ChangePassword.vue";
|
||||
import UserManagement from "../UserManagement/UserManagement.vue";
|
||||
import SettingsModal from "./SettingsModal.vue";
|
||||
import DomainModal from "./DomainModal.vue";
|
||||
import { useDashboard } from "./Dashboard.js";
|
||||
|
||||
const dashboard = useDashboard();
|
||||
@ -466,143 +467,10 @@ const {
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div> <div v-if="showForm" class="modal-backdrop" @click.self="closeForm">
|
||||
<section
|
||||
class="modal-card"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
:aria-label="formTitle"
|
||||
>
|
||||
<div class="modal-heading">
|
||||
<div>
|
||||
<p class="eyebrow">域名配置</p>
|
||||
<h2>{{ formTitle }}</h2>
|
||||
</div>
|
||||
<button
|
||||
class="close-button"
|
||||
type="button"
|
||||
aria-label="关闭"
|
||||
@click="closeForm"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<form @submit.prevent="saveDomain">
|
||||
<label class="field" for="domain-name"
|
||||
><span>主域名</span
|
||||
><input
|
||||
id="domain-name"
|
||||
name="name"
|
||||
v-model="form.name"
|
||||
type="text"
|
||||
placeholder="例如 example.com"
|
||||
autocomplete="off"
|
||||
/></label>
|
||||
<label class="field" for="certificate-type"
|
||||
><span>证书类型</span
|
||||
><select
|
||||
id="certificate-type"
|
||||
name="certificate_type"
|
||||
v-model="form.certificate_type"
|
||||
>
|
||||
<option value="single">单域名</option>
|
||||
<option value="wildcard">泛域名</option>
|
||||
<option value="san">SAN 多域名</option>
|
||||
</select></label
|
||||
>
|
||||
<label
|
||||
v-if="form.certificate_type === 'san'"
|
||||
class="field"
|
||||
for="domain-sans"
|
||||
><span>附加域名 <small>每行一个,也支持逗号分隔</small></span
|
||||
><textarea
|
||||
id="domain-sans"
|
||||
name="sans"
|
||||
v-model="form.sans"
|
||||
rows="4"
|
||||
placeholder="www.example.com\napi.example.com"
|
||||
></textarea>
|
||||
</label>
|
||||
<div class="form-grid">
|
||||
<label class="field" for="acme-environment"
|
||||
><span>ACME 环境</span
|
||||
><select
|
||||
id="acme-environment"
|
||||
name="acme_environment"
|
||||
v-model="form.acme_environment"
|
||||
>
|
||||
<option value="staging">测试环境</option>
|
||||
<option value="production">生产环境</option>
|
||||
</select></label
|
||||
>
|
||||
<label class="field" for="dns-provider"
|
||||
><span>DNS 验证服务商</span
|
||||
><select
|
||||
id="dns-provider"
|
||||
name="dns_provider"
|
||||
v-model="form.dns_provider"
|
||||
>
|
||||
<option value="">暂不选择</option>
|
||||
<option
|
||||
v-for="provider in dnsProviders"
|
||||
:key="provider.value"
|
||||
:value="provider.value"
|
||||
>
|
||||
{{ provider.label }}
|
||||
</option>
|
||||
</select></label
|
||||
>
|
||||
</div>
|
||||
<label class="switch-field" for="auto-renew"
|
||||
><input
|
||||
id="auto-renew"
|
||||
name="auto_renew"
|
||||
v-model="form.auto_renew"
|
||||
type="checkbox"
|
||||
/><span
|
||||
><strong>启用自动续期</strong
|
||||
><small>证书剩余有效期达到阈值时自动创建续期任务</small></span
|
||||
></label
|
||||
>
|
||||
<fieldset class="server-picker">
|
||||
<legend>关联部署服务器</legend>
|
||||
<label
|
||||
v-for="server in servers"
|
||||
:key="server.id"
|
||||
class="server-option"
|
||||
><input
|
||||
v-model="form.server_ids"
|
||||
type="checkbox"
|
||||
:value="server.id"
|
||||
/><span
|
||||
><strong>{{ server.name }}</strong
|
||||
><small
|
||||
>{{ server.address }} ·
|
||||
{{ displayLabel(webServerLabels, server.web_server) }}</small
|
||||
></span
|
||||
></label
|
||||
>
|
||||
<p v-if="servers.length === 0" class="muted">
|
||||
暂无服务器配置,可先保存域名后添加服务器。
|
||||
</p>
|
||||
</fieldset>
|
||||
<p v-if="formError" class="form-error">{{ formError }}</p>
|
||||
<div class="modal-actions">
|
||||
<button
|
||||
class="secondary-button"
|
||||
type="button"
|
||||
:disabled="saving"
|
||||
@click="closeForm"
|
||||
>
|
||||
取消</button
|
||||
><button class="primary-button" type="submit" :disabled="saving">
|
||||
{{ saving ? "保存中……" : "保存配置" }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<DomainModal v-if="showForm" :state="dashboard" />
|
||||
|
||||
<div
|
||||
v-if="showServerForm"
|
||||
class="modal-backdrop"
|
||||
|
||||
91
frontend/src/views/Dashboard/DomainModal.css
Normal file
91
frontend/src/views/Dashboard/DomainModal.css
Normal file
@ -0,0 +1,91 @@
|
||||
.modal-backdrop.domain-backdrop {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.domain-modal {
|
||||
box-sizing: border-box;
|
||||
width: min(100%, 1060px);
|
||||
max-height: calc(100dvh - 32px);
|
||||
padding: 22px 28px;
|
||||
}
|
||||
|
||||
.domain-modal .modal-heading {
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.domain-modal form {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 0 18px;
|
||||
}
|
||||
|
||||
.domain-modal .domain-sans-field,
|
||||
.domain-modal .form-grid,
|
||||
.domain-modal .switch-field,
|
||||
.domain-modal .server-picker,
|
||||
.domain-modal .form-error,
|
||||
.domain-modal .modal-actions {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.domain-modal .field {
|
||||
min-width: 0;
|
||||
margin-bottom: 9px;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.domain-modal .field input,
|
||||
.domain-modal .field select,
|
||||
.domain-modal .field textarea {
|
||||
min-height: 36px;
|
||||
padding: 8px 11px;
|
||||
}
|
||||
|
||||
.domain-modal .field textarea {
|
||||
min-height: 72px;
|
||||
}
|
||||
|
||||
.domain-modal .field > span,
|
||||
.domain-modal .switch-field > span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.domain-modal .switch-field {
|
||||
align-items: center;
|
||||
margin: 4px 0 10px;
|
||||
}
|
||||
|
||||
.domain-modal .server-picker {
|
||||
min-width: 0;
|
||||
max-height: 130px;
|
||||
overflow-y: auto;
|
||||
margin: 4px 0 12px;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.domain-modal .server-picker .muted {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.domain-modal .form-error {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.domain-modal .modal-actions {
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid #dce6f1;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.domain-modal {
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.domain-modal form,
|
||||
.domain-modal .form-grid {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
}
|
||||
160
frontend/src/views/Dashboard/DomainModal.vue
Normal file
160
frontend/src/views/Dashboard/DomainModal.vue
Normal file
@ -0,0 +1,160 @@
|
||||
<script setup>
|
||||
import HelpTip from "../components/HelpTip/HelpTip.vue";
|
||||
|
||||
const props = defineProps({ state: { type: Object, required: true } });
|
||||
const {
|
||||
showForm,
|
||||
formTitle,
|
||||
closeForm,
|
||||
saveDomain,
|
||||
form,
|
||||
dnsProviders,
|
||||
servers,
|
||||
displayLabel,
|
||||
webServerLabels,
|
||||
formError,
|
||||
saving,
|
||||
} = props.state;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="showForm" class="modal-backdrop domain-backdrop" @click.self="closeForm">
|
||||
<section
|
||||
class="modal-card domain-modal"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
:aria-label="formTitle"
|
||||
>
|
||||
<div class="modal-heading">
|
||||
<div>
|
||||
<p class="eyebrow">域名配置</p>
|
||||
<h2>{{ formTitle }}</h2>
|
||||
</div>
|
||||
<button
|
||||
class="close-button"
|
||||
type="button"
|
||||
aria-label="关闭"
|
||||
@click="closeForm"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<form @submit.prevent="saveDomain">
|
||||
<label class="field" for="domain-name"
|
||||
><span>主域名</span
|
||||
><input
|
||||
id="domain-name"
|
||||
name="name"
|
||||
v-model="form.name"
|
||||
type="text"
|
||||
placeholder="例如 example.com"
|
||||
autocomplete="off"
|
||||
/></label>
|
||||
<label class="field" for="certificate-type"
|
||||
><span>证书类型</span
|
||||
><select
|
||||
id="certificate-type"
|
||||
name="certificate_type"
|
||||
v-model="form.certificate_type"
|
||||
>
|
||||
<option value="single">单域名</option>
|
||||
<option value="wildcard">泛域名</option>
|
||||
<option value="san">SAN 多域名</option>
|
||||
</select></label
|
||||
>
|
||||
<label
|
||||
v-if="form.certificate_type === 'san'"
|
||||
class="field domain-sans-field"
|
||||
for="domain-sans"
|
||||
><span>附加域名 <HelpTip>每行一个,也支持逗号分隔</HelpTip></span
|
||||
><textarea
|
||||
id="domain-sans"
|
||||
name="sans"
|
||||
v-model="form.sans"
|
||||
rows="2"
|
||||
:placeholder="'www.example.com\napi.example.com'"
|
||||
></textarea>
|
||||
</label>
|
||||
<div class="form-grid">
|
||||
<label class="field" for="acme-environment"
|
||||
><span>ACME 环境</span
|
||||
><select
|
||||
id="acme-environment"
|
||||
name="acme_environment"
|
||||
v-model="form.acme_environment"
|
||||
>
|
||||
<option value="staging">测试环境</option>
|
||||
<option value="production">生产环境</option>
|
||||
</select></label
|
||||
>
|
||||
<label class="field" for="dns-provider"
|
||||
><span>DNS 验证服务商</span
|
||||
><select
|
||||
id="dns-provider"
|
||||
name="dns_provider"
|
||||
v-model="form.dns_provider"
|
||||
>
|
||||
<option value="">暂不选择</option>
|
||||
<option
|
||||
v-for="provider in dnsProviders"
|
||||
:key="provider.value"
|
||||
:value="provider.value"
|
||||
>
|
||||
{{ provider.label }}
|
||||
</option>
|
||||
</select></label
|
||||
>
|
||||
</div>
|
||||
<label class="switch-field" for="auto-renew"
|
||||
><input
|
||||
id="auto-renew"
|
||||
name="auto_renew"
|
||||
v-model="form.auto_renew"
|
||||
type="checkbox"
|
||||
/><span
|
||||
><strong>启用自动续期</strong
|
||||
><HelpTip>证书剩余有效期达到阈值时自动创建续期任务</HelpTip></span
|
||||
></label
|
||||
>
|
||||
<fieldset class="server-picker">
|
||||
<legend>关联部署服务器</legend>
|
||||
<label
|
||||
v-for="server in servers"
|
||||
:key="server.id"
|
||||
class="server-option"
|
||||
><input
|
||||
v-model="form.server_ids"
|
||||
type="checkbox"
|
||||
:value="server.id"
|
||||
/><span
|
||||
><strong>{{ server.name }}</strong
|
||||
><small
|
||||
>{{ server.address }} ·
|
||||
{{ displayLabel(webServerLabels, server.web_server) }}</small
|
||||
></span
|
||||
></label
|
||||
>
|
||||
<p v-if="servers.length === 0" class="muted">
|
||||
暂无服务器配置,可先保存域名后添加服务器。
|
||||
</p>
|
||||
</fieldset>
|
||||
<p v-if="formError" class="form-error">{{ formError }}</p>
|
||||
<div class="modal-actions">
|
||||
<button
|
||||
class="secondary-button"
|
||||
type="button"
|
||||
:disabled="saving"
|
||||
@click="closeForm"
|
||||
>
|
||||
取消</button
|
||||
><button class="primary-button" type="submit" :disabled="saving">
|
||||
{{ saving ? "保存中……" : "保存配置" }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped src="./Dashboard.css"></style>
|
||||
<style scoped src="./DomainModal.css"></style>
|
||||
Loading…
Reference in New Issue
Block a user