修改前检查点:保留初始化通知精简进度
保存仅保留 SMTP 的在途修改;旧校验测试待更新,尚未完成实机验收。
This commit is contained in:
parent
d8e0bf0409
commit
01232e4c68
@ -45,8 +45,6 @@ export function useSetupWizard(props, emit) {
|
||||
const dnsProviders = ref([]);
|
||||
const dnsCredentials = ref({});
|
||||
const dnsCredentialsVisible = ref(true);
|
||||
const emailCredentials = ref("");
|
||||
const emailCredentialsVisible = ref(true);
|
||||
const dnsTestDomain = ref(props.status.web_hostname || "");
|
||||
const dnsVerification = ref(null);
|
||||
const verificationClock = ref(Date.now());
|
||||
@ -75,7 +73,6 @@ export function useSetupWizard(props, emit) {
|
||||
admin_password: "",
|
||||
confirm_password: "",
|
||||
server_port: props.status.mode === "bootstrap" ? 80 : (props.status.server_port || 80),
|
||||
email_http_enabled: false,
|
||||
email_smtp_enabled: false,
|
||||
email_smtp_host: "",
|
||||
email_smtp_port: 587,
|
||||
@ -83,13 +80,6 @@ export function useSetupWizard(props, emit) {
|
||||
email_smtp_password: "",
|
||||
email_smtp_mode: "starttls",
|
||||
email_smtp_allow_plaintext_auth: false,
|
||||
email_provider: "",
|
||||
email_endpoint: "",
|
||||
email_http_path: "/api/email/send",
|
||||
email_http_login_path: "/api/login",
|
||||
email_http_auth_mode: "none",
|
||||
email_http_credential_ref: "",
|
||||
email_http_headers: "",
|
||||
email_event_mode: "detailed",
|
||||
email_include_deployment_details: true,
|
||||
email_include_certificate_serial: true,
|
||||
@ -116,30 +106,6 @@ export function useSetupWizard(props, emit) {
|
||||
const targetLabel = computed(() =>
|
||||
targetType.value === "hostname" ? "管理页面域名" : "公网 IP 地址",
|
||||
);
|
||||
const emailCredentialLabel = computed(() => {
|
||||
switch (form.value.email_http_auth_mode) {
|
||||
case "bearer":
|
||||
return "Bearer Token 凭据(JSON)";
|
||||
case "hmac_sha256":
|
||||
return "HMAC-SHA256 凭据(JSON)";
|
||||
case "session":
|
||||
return "登录会话凭据(JSON)";
|
||||
default:
|
||||
return "HTTP 通知凭据";
|
||||
}
|
||||
});
|
||||
const emailCredentialPlaceholder = computed(() => {
|
||||
switch (form.value.email_http_auth_mode) {
|
||||
case "bearer":
|
||||
return '{"token":"请输入 Bearer Token"}';
|
||||
case "hmac_sha256":
|
||||
return '{"secret":"请输入签名密钥"}';
|
||||
case "session":
|
||||
return '{"account":"请输入账号","password":"请输入密码"}';
|
||||
default:
|
||||
return "无需填写凭据";
|
||||
}
|
||||
});
|
||||
const resetURLPreview = computed(() => {
|
||||
const rawHost =
|
||||
targetType.value === "hostname"
|
||||
@ -428,27 +394,6 @@ export function useSetupWizard(props, emit) {
|
||||
return "•".repeat(Math.min(Math.max(text.length, 8), 64));
|
||||
}
|
||||
|
||||
function emailCredentialPayload() {
|
||||
if (
|
||||
!form.value.email_http_enabled ||
|
||||
form.value.email_http_auth_mode === "none"
|
||||
) {
|
||||
return {};
|
||||
}
|
||||
const text = emailCredentials.value.trim();
|
||||
if (!text) return {};
|
||||
const parsed = JSON.parse(text);
|
||||
if (!parsed || Array.isArray(parsed) || typeof parsed !== "object") {
|
||||
throw new Error("HTTP 通知凭据必须是 JSON 对象。");
|
||||
}
|
||||
for (const [key, value] of Object.entries(parsed)) {
|
||||
if (!key.trim() || typeof value !== "string" || !value.trim()) {
|
||||
throw new Error("HTTP 通知凭据的字段和值必须是非空文本。");
|
||||
}
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
async function testDNS() {
|
||||
dnsTestError.value = "";
|
||||
resultMessage.value = "";
|
||||
@ -586,42 +531,6 @@ export function useSetupWizard(props, emit) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (form.value.email_http_enabled && !form.value.email_endpoint.trim()) {
|
||||
error.value = "启用 HTTP 邮件通知后必须填写 API 地址。";
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
form.value.email_http_enabled &&
|
||||
form.value.email_http_auth_mode !== "none" &&
|
||||
(isBootstrap || !form.value.email_http_credential_ref.trim()) &&
|
||||
!emailCredentials.value.trim()
|
||||
) {
|
||||
error.value = "启用 HTTP 通知认证后必须填写有效凭据。";
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
form.value.email_http_enabled &&
|
||||
form.value.email_http_auth_mode !== "none" &&
|
||||
emailCredentials.value.trim()
|
||||
) {
|
||||
try {
|
||||
emailCredentialPayload();
|
||||
} catch (err) {
|
||||
error.value = err.message || "HTTP 通知凭据格式无效。";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (form.value.email_http_headers.trim()) {
|
||||
try {
|
||||
const headers = JSON.parse(form.value.email_http_headers);
|
||||
if (!headers || Array.isArray(headers) || typeof headers !== "object") {
|
||||
throw new Error("not-object");
|
||||
}
|
||||
} catch {
|
||||
error.value = "HTTP 自定义请求头必须是 JSON 对象。";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentStep.value === 3) {
|
||||
@ -717,11 +626,6 @@ export function useSetupWizard(props, emit) {
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function emailHeaders() {
|
||||
if (!form.value.email_http_headers.trim()) return {};
|
||||
return JSON.parse(form.value.email_http_headers);
|
||||
}
|
||||
|
||||
function retryIntervals() {
|
||||
return form.value.email_retry_intervals_secs
|
||||
.split(/[,,]/)
|
||||
@ -769,7 +673,7 @@ export function useSetupWizard(props, emit) {
|
||||
admin_email: form.value.admin_email,
|
||||
admin_password: form.value.admin_password,
|
||||
server_port: Number(form.value.server_port),
|
||||
email_http_enabled: form.value.email_http_enabled,
|
||||
email_http_enabled: false,
|
||||
email_smtp_enabled: form.value.email_smtp_enabled,
|
||||
email_smtp_host: form.value.email_smtp_host,
|
||||
email_smtp_port: Number(form.value.email_smtp_port),
|
||||
@ -777,14 +681,6 @@ export function useSetupWizard(props, emit) {
|
||||
email_smtp_password: form.value.email_smtp_password,
|
||||
email_smtp_mode: form.value.email_smtp_mode,
|
||||
email_smtp_allow_plaintext_auth: form.value.email_smtp_allow_plaintext_auth,
|
||||
email_provider: form.value.email_provider,
|
||||
email_endpoint: form.value.email_endpoint,
|
||||
email_http_path: form.value.email_http_path,
|
||||
email_http_login_path: form.value.email_http_login_path,
|
||||
email_http_auth_mode: form.value.email_http_auth_mode,
|
||||
email_http_credential_ref: form.value.email_http_credential_ref,
|
||||
email_http_credentials: emailCredentialPayload(),
|
||||
email_http_headers: emailHeaders(),
|
||||
email_event_mode: form.value.email_event_mode,
|
||||
email_include_deployment_details:
|
||||
form.value.email_include_deployment_details,
|
||||
@ -898,8 +794,6 @@ export function useSetupWizard(props, emit) {
|
||||
dnsCredentials,
|
||||
dnsCredentialsVisible,
|
||||
maskCredential,
|
||||
emailCredentials,
|
||||
emailCredentialsVisible,
|
||||
dnsTestDomain,
|
||||
dnsVerification,
|
||||
currentStep,
|
||||
@ -913,8 +807,6 @@ export function useSetupWizard(props, emit) {
|
||||
emailAutofillElement,
|
||||
handleEmailAutofill,
|
||||
targetLabel,
|
||||
emailCredentialLabel,
|
||||
emailCredentialPlaceholder,
|
||||
resetURLPreview,
|
||||
selectedDNSProvider,
|
||||
dnsVerificationValid,
|
||||
|
||||
@ -35,8 +35,6 @@ const {
|
||||
dnsCredentials,
|
||||
dnsCredentialsVisible,
|
||||
maskCredential,
|
||||
emailCredentials,
|
||||
emailCredentialsVisible,
|
||||
dnsTestDomain,
|
||||
dnsVerification,
|
||||
currentStep,
|
||||
@ -51,8 +49,6 @@ const {
|
||||
handleEmailAutofill,
|
||||
targetLabel,
|
||||
resetURLPreview,
|
||||
emailCredentialLabel,
|
||||
emailCredentialPlaceholder,
|
||||
selectedDNSProvider,
|
||||
dnsVerificationValid,
|
||||
formatVerificationExpiry,
|
||||
@ -314,31 +310,7 @@ const {
|
||||
<legend><span>02</span>服务与通知</legend>
|
||||
|
||||
<div class="notification-layout">
|
||||
<div class="notification-guide">
|
||||
<strong>
|
||||
先完成基础配置
|
||||
<HelpTip>
|
||||
填写邮件服务地址即可继续,其他项目已经提供合理默认值。
|
||||
</HelpTip>
|
||||
</strong>
|
||||
</div>
|
||||
|
||||
<div class="notification-row notification-service-row">
|
||||
<label class="confirm-row compact-row">
|
||||
<input
|
||||
id="email-http-enabled"
|
||||
v-model="form.email_http_enabled"
|
||||
type="checkbox"
|
||||
/>
|
||||
<span>
|
||||
<strong>
|
||||
启用 HTTP 邮件通知
|
||||
<HelpTip>
|
||||
通过第三方邮件 API 发送任务通知。
|
||||
</HelpTip>
|
||||
</strong>
|
||||
</span>
|
||||
</label>
|
||||
<label class="confirm-row compact-row">
|
||||
<input
|
||||
id="email-smtp-enabled"
|
||||
@ -437,56 +409,6 @@ const {
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="notification-smtp-tip">
|
||||
SMTP 密码只会写入加密敏感信息存储,不会回显到配置文件、接口响应或日志。建议优先使用 STARTTLS 或隐式 TLS。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="form.email_http_enabled" class="notification-basic-panel">
|
||||
<div class="notification-section-heading">
|
||||
<div>
|
||||
<strong>
|
||||
邮件通知基础配置
|
||||
<HelpTip>
|
||||
填写服务地址、发件人和收件人即可,技术参数无需修改。
|
||||
</HelpTip>
|
||||
</strong>
|
||||
</div>
|
||||
<span class="notification-recommended-badge">推荐配置</span>
|
||||
</div>
|
||||
|
||||
<div class="notification-row notification-basic-row">
|
||||
<label>
|
||||
<span>
|
||||
邮件服务地址
|
||||
<HelpTip>
|
||||
填写邮件服务提供的地址,不需要自行拼接接口路径。
|
||||
</HelpTip>
|
||||
</span>
|
||||
<input
|
||||
id="email-endpoint"
|
||||
name="email_endpoint"
|
||||
v-model.trim="form.email_endpoint"
|
||||
type="url"
|
||||
placeholder="https://mail.example.com"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
<span>
|
||||
邮件服务名称(可选)
|
||||
<HelpTip>
|
||||
仅用于日志和通知内容中的识别。
|
||||
</HelpTip>
|
||||
</span>
|
||||
<input
|
||||
id="email-provider"
|
||||
name="email_provider"
|
||||
v-model.trim="form.email_provider"
|
||||
placeholder="例如:Smail"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="notification-row notification-sender-row">
|
||||
<label>
|
||||
<span>发件人名称</span>
|
||||
@ -528,7 +450,7 @@ const {
|
||||
<span>只有手动启动 Scert-admin 后,此地址才会临时可用。</span>
|
||||
</div>
|
||||
|
||||
<div v-if="form.email_http_enabled" class="notification-advanced-panel">
|
||||
<div v-if="form.email_smtp_enabled" class="notification-advanced-panel">
|
||||
<button
|
||||
class="notification-advanced-toggle"
|
||||
type="button"
|
||||
@ -537,7 +459,6 @@ const {
|
||||
>
|
||||
<span>
|
||||
<strong>高级配置</strong>
|
||||
<small>认证、请求路径、重试策略和通知内容选项</small>
|
||||
</span>
|
||||
<span class="notification-advanced-action">
|
||||
{{ notificationAdvancedVisible ? "收起配置" : "展开配置" }}
|
||||
@ -545,101 +466,6 @@ const {
|
||||
</button>
|
||||
|
||||
<div v-if="notificationAdvancedVisible" class="notification-advanced-content">
|
||||
<div class="notification-advanced-tip">
|
||||
默认值适用于常见 HTTP 邮件服务。只有服务方提供了特殊要求时,才需要修改这里的内容。
|
||||
</div>
|
||||
|
||||
<div class="notification-row notification-api-row">
|
||||
<label>
|
||||
<span>HTTP 请求路径</span>
|
||||
<input
|
||||
id="email-http-path"
|
||||
v-model.trim="form.email_http_path"
|
||||
placeholder="/api/email/send"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
<span>HTTP 认证方式</span>
|
||||
<select
|
||||
id="email-http-auth-mode"
|
||||
v-model="form.email_http_auth_mode"
|
||||
>
|
||||
<option value="none">无认证</option>
|
||||
<option value="bearer">Bearer Token</option>
|
||||
<option value="hmac_sha256">HMAC-SHA256</option>
|
||||
<option value="session">账号登录会话</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-if="form.email_http_auth_mode === 'session'" class="notification-row notification-auth-row">
|
||||
<label>
|
||||
<span>登录请求路径</span>
|
||||
<input
|
||||
id="email-http-login-path"
|
||||
v-model.trim="form.email_http_login_path"
|
||||
placeholder="/api/login"
|
||||
/>
|
||||
</label>
|
||||
<label v-if="!isBootstrap">
|
||||
<span>已有凭据引用(可选)</span>
|
||||
<input
|
||||
id="email-http-credential-ref"
|
||||
v-model.trim="form.email_http_credential_ref"
|
||||
placeholder="已有安全凭据引用"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-else class="notification-row notification-auth-row">
|
||||
<label v-if="!isBootstrap">
|
||||
<span>已有凭据引用(可选)</span>
|
||||
<input
|
||||
id="email-http-credential-ref"
|
||||
v-model.trim="form.email_http_credential_ref"
|
||||
placeholder="已有安全凭据引用"
|
||||
/>
|
||||
</label>
|
||||
<div v-if="form.email_http_auth_mode === 'none'" class="notification-auth-note">
|
||||
当前接口不启用认证,不需要填写凭据。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="form.email_http_auth_mode !== 'none'"
|
||||
class="notification-credential-field"
|
||||
>
|
||||
<div class="credential-visibility-row">
|
||||
<span>{{ emailCredentialLabel }}</span>
|
||||
<label class="credential-visibility-control">
|
||||
<input
|
||||
v-model="emailCredentialsVisible"
|
||||
type="checkbox"
|
||||
/>
|
||||
<span>显示凭据明文</span>
|
||||
</label>
|
||||
</div>
|
||||
<textarea
|
||||
v-if="emailCredentialsVisible"
|
||||
id="email-http-credentials-visible"
|
||||
v-model="emailCredentials"
|
||||
rows="3"
|
||||
autocomplete="off"
|
||||
:placeholder="emailCredentialPlaceholder"
|
||||
></textarea>
|
||||
<textarea
|
||||
v-else
|
||||
id="email-http-credentials-masked"
|
||||
:value="maskCredential(emailCredentials)"
|
||||
rows="3"
|
||||
readonly
|
||||
autocomplete="off"
|
||||
></textarea>
|
||||
<small>
|
||||
配置时允许明文显示;提交后仅保存加密凭据引用,普通配置文件不会保存密码或令牌。
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="notification-row notification-delivery-row">
|
||||
<label>
|
||||
<span>通知事件模式</span>
|
||||
@ -674,14 +500,6 @@ const {
|
||||
</div>
|
||||
|
||||
<div class="notification-bottom-row">
|
||||
<label class="email-headers-field">
|
||||
<span>HTTP 自定义请求头(JSON,可选)</span>
|
||||
<textarea
|
||||
v-model.trim="form.email_http_headers"
|
||||
rows="2"
|
||||
placeholder='例如:{"X-Tenant":"scert"}'
|
||||
></textarea>
|
||||
</label>
|
||||
<div class="setup-grid three-columns compact-options email-option-grid">
|
||||
<label class="confirm-row compact-row">
|
||||
<input
|
||||
|
||||
@ -110,11 +110,14 @@ for (const driver of ["sqlite", "mysql", "postgres"]) {
|
||||
email_http_enabled: true,
|
||||
email_endpoint: "https://mail.example.test",
|
||||
email_http_auth_mode: "bearer",
|
||||
email_smtp_enabled: true,
|
||||
email_smtp_host: "smtp.example.test",
|
||||
email_smtp_username: "sender@example.test",
|
||||
email_smtp_password: "smtp-test-only",
|
||||
email_retry_count: 0,
|
||||
recipients: "first@example.test,second@example.test",
|
||||
email_http_credential_ref: "secret://client/not-trusted",
|
||||
});
|
||||
state.emailCredentials.value = JSON.stringify({ token: "test-notification-token" });
|
||||
for (let step = 1; step < 4; step += 1) {
|
||||
assert.equal(state.currentStep.value, step);
|
||||
await state.handleSubmit();
|
||||
@ -128,10 +131,18 @@ for (const driver of ["sqlite", "mysql", "postgres"]) {
|
||||
assert.equal(submitted[0].database_verification_token, "database-token");
|
||||
assert.equal(submitted[0].dns_verification_token, "dns-token");
|
||||
assert.equal(submitted[0].web_public_ip, "203.0.113.20");
|
||||
assert.equal(submitted[0].notification.http_enabled, true);
|
||||
assert.equal(submitted[0].notification.http_enabled, false);
|
||||
assert.equal(submitted[0].notification.smtp_enabled, true);
|
||||
assert.equal(submitted[0].notification.smtp_host, "smtp.example.test");
|
||||
assert.equal(submitted[0].notification.smtp_username, "sender@example.test");
|
||||
assert.equal(submitted[0].notification.smtp_password, "smtp-test-only");
|
||||
assert.equal(submitted[0].notification.smtp_port, 587);
|
||||
assert.equal(submitted[0].notification.smtp_mode, "starttls");
|
||||
assert.equal(submitted[0].notification.retry_count, 0);
|
||||
assert.deepEqual(submitted[0].notification.recipients, ["first@example.test", "second@example.test"]);
|
||||
assert.deepEqual(submitted[0].notification.http_credentials, { token: "test-notification-token" });
|
||||
for (const key of ["endpoint", "http_auth_mode", "http_credentials", "http_path", "http_headers"]) {
|
||||
assert.equal(Object.hasOwn(submitted[0].notification, key), false);
|
||||
}
|
||||
for (const key of ["dns_credential_ref", "certificate_root", "database", "confirm_password"]) {
|
||||
assert.equal(Object.hasOwn(submitted[0], key), false);
|
||||
}
|
||||
|
||||
@ -294,7 +294,7 @@
|
||||
}
|
||||
|
||||
.access-shell--setup .notification-layout:has(.smtp-basic-panel):has(.notification-advanced-content) {
|
||||
grid-template-columns: minmax(0, 0.95fr) minmax(0, 0.95fr) minmax(0, 1.3fr);
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.access-shell--setup .notification-layout:has(.smtp-basic-panel):has(.notification-advanced-content) .notification-basic-row {
|
||||
|
||||
@ -22,6 +22,25 @@ function hasClass(name) {
|
||||
&& prop.value?.content.split(/\s+/).includes(name));
|
||||
}
|
||||
|
||||
test("notification step exposes SMTP only and keeps sender fields in its panel", () => {
|
||||
const root = template("./SetupWizard.vue");
|
||||
const step = find(root, hasClass("setup-notification-step"));
|
||||
const panel = find(step, hasClass("smtp-basic-panel"));
|
||||
const id = (value) => (node) => node.props?.some((prop) =>
|
||||
prop.name === "id" && prop.value?.content === value);
|
||||
assert.ok(find(step, id("email-smtp-enabled")));
|
||||
for (const value of ["email-http-enabled", "email-endpoint", "email-http-path", "email-http-auth-mode"]) {
|
||||
assert.equal(find(step, id(value)), undefined);
|
||||
}
|
||||
for (const value of ["email-smtp-host", "email-smtp-password", "email-sender-name", "email-sender-email", "recipients"]) {
|
||||
assert.ok(find(panel, id(value)), value);
|
||||
}
|
||||
const advanced = find(step, hasClass("notification-advanced-panel"));
|
||||
assert.equal(advanced.props.find((prop) => prop.name === "if").exp.content, "form.email_smtp_enabled");
|
||||
assert.ok(find(advanced, hasClass("notification-delivery-row")));
|
||||
assert.ok(find(advanced, hasClass("email-option-grid")));
|
||||
});
|
||||
|
||||
test("account heading precedes the two-column account fields", () => {
|
||||
const root = template("./SetupWizard.vue");
|
||||
const card = find(root, (node) => node.tag === "fieldset"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user