import { computed, onMounted, onUnmounted, ref, watch } from "vue"; const commonEmailDomains = [ { label: "常用邮箱", value: "" }, { label: "@163.com", value: "163.com" }, { label: "@qq.com", value: "qq.com" }, { label: "@126.com", value: "126.com" }, { label: "@outlook.com", value: "outlook.com" }, { label: "@gmail.com", value: "gmail.com" }, { label: "@foxmail.com", value: "foxmail.com" }, { label: "@aliyun.com", value: "aliyun.com" }, { label: "@139.com", value: "139.com" }, { label: "@189.cn", value: "189.cn" }, { label: "自定义后缀", value: "custom" }, ]; export function useSetupWizard(props, emit) { const scertLogo = "/Scert.png"; const submitting = ref(false); const error = ref(""); const resultMessage = ref(""); const dnsCatalogLoading = ref(true); const dnsCatalogError = ref(""); const dnsTesting = ref(false); const dnsTestError = ref(""); const dnsProviders = ref([]); const dnsCredentials = ref({}); const dnsTestDomain = ref(props.status.web_hostname || ""); const dnsVerification = ref(null); const verificationClock = ref(Date.now()); const currentStep = ref(1); const selectedEmailDomain = ref(""); const emailLocalPart = ref(""); let verificationTimer; const targetType = ref(props.status.web_public_ip ? "ip" : "hostname"); const form = ref({ admin_username: "", admin_email: "", admin_password: "", confirm_password: "", server_port: props.status.server_port || 8080, email_http_enabled: 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, email_include_management_url: true, email_sender_name: "Scert", email_sender_email: "", email_timeout_secs: 10, email_retry_count: 3, email_retry_intervals_secs: "30,120,600", recipients: "", dns_provider: props.status.dns_provider || "cloudflare", certificate_root: props.status.certificate_root || "", web_hostname: props.status.web_hostname || "", web_public_ip: props.status.web_public_ip || "", web_enabled: props.status.web_enabled !== false, https_mode: props.status.https_mode || "direct", }); const targetLabel = computed(() => targetType.value === "hostname" ? "管理页面域名" : "公网 IP 地址", ); const resetURLPreview = computed(() => { const rawHost = targetType.value === "hostname" ? form.value.web_hostname : form.value.web_public_ip; const host = String(rawHost || "") .trim() .replace(/^\[|\]$/g, ""); if (!host) return "完成域名或公网 IP 配置后自动生成"; const displayHost = host.includes(":") ? `[${host}]` : host; return `https://${displayHost}/reset`; }); const selectedDNSProvider = computed( () => dnsProviders.value.find( (provider) => provider.id === form.value.dns_provider, ) || null, ); const dnsVerificationValid = computed(() => { verificationClock.value; if ( !dnsVerification.value?.verification_token || !dnsVerification.value?.credential_ref ) return false; return new Date(dnsVerification.value.expires_at).getTime() > Date.now(); }); function invalidateDNSVerification() { dnsVerification.value = null; dnsTestError.value = ""; } function formatVerificationExpiry(value) { if (!value) return ""; return new Intl.DateTimeFormat("zh-CN", { dateStyle: "medium", timeStyle: "medium", timeZone: "Asia/Shanghai", }).format(new Date(value)); } function handleEmailInput(value) { error.value = ""; const rawValue = String(value || "").trim(); if (!rawValue) { emailLocalPart.value = ""; selectedEmailDomain.value = ""; form.value.admin_email = ""; return; } const atIndex = rawValue.indexOf("@"); if (atIndex <= 0) { emailLocalPart.value = rawValue; form.value.admin_email = selectedEmailDomain.value && selectedEmailDomain.value !== "custom" ? `${rawValue}@${selectedEmailDomain.value}` : rawValue; return; } const localPart = rawValue.slice(0, atIndex).trim(); const domain = rawValue.slice(atIndex + 1).trim().toLowerCase(); emailLocalPart.value = localPart; selectedEmailDomain.value = commonEmailDomains.some( (option) => option.value === domain, ) ? domain : "custom"; form.value.admin_email = `${localPart}@${domain}`; } function applyEmailDomain(domain) { error.value = ""; if (!domain || domain === "custom") return; const currentEmail = form.value.admin_email.trim(); const localPart = emailLocalPart.value.trim() || currentEmail.split("@")[0].trim(); if (!localPart) { selectedEmailDomain.value = ""; error.value = "请先输入邮箱名称,再选择常用邮箱。"; return; } emailLocalPart.value = localPart; form.value.admin_email = `${localPart}@${domain}`; } async function loadDNSProviders() { dnsCatalogLoading.value = true; dnsCatalogError.value = ""; try { const response = await fetch("/api/v1/setup/dns/providers"); const result = await response.json().catch(() => ({})); if (!response.ok) throw new Error(result.error || "读取 DNS 服务商列表失败"); dnsProviders.value = Array.isArray(result.items) ? result.items : []; if ( !dnsProviders.value.some( (provider) => provider.id === form.value.dns_provider, ) ) { form.value.dns_provider = dnsProviders.value.find((provider) => provider.available)?.id || ""; } } catch (err) { dnsCatalogError.value = err.message || "读取 DNS 服务商列表失败"; } finally { dnsCatalogLoading.value = false; } } function credentialPayload() { const payload = {}; for (const field of selectedDNSProvider.value?.credential_fields || []) { const value = String(dnsCredentials.value[field.name] || "").trim(); if (value) payload[field.name] = value; } return payload; } async function testDNS() { dnsTestError.value = ""; resultMessage.value = ""; invalidateDNSVerification(); const provider = selectedDNSProvider.value; if (!provider) { dnsTestError.value = "请选择 DNS 服务商。"; return; } if (!provider.available) { dnsTestError.value = "该 DNS 服务商在线验证适配器尚未开放。"; return; } const testDomain = dnsTestDomain.value.trim(); if (!testDomain) { dnsTestError.value = "请填写 DNS 权限测试域名。"; return; } for (const field of provider.credential_fields || []) { if ( field.required && !String(dnsCredentials.value[field.name] || "").trim() ) { dnsTestError.value = `请填写${field.label}。`; return; } } dnsTesting.value = true; try { const response = await fetch("/api/v1/setup/dns/test", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ provider: provider.id, test_domain: testDomain, credentials: credentialPayload(), }), }); const result = await response.json().catch(() => ({})); if (!response.ok) throw new Error(result.error || "DNS API 在线验证失败"); dnsVerification.value = result; } catch (err) { dnsTestError.value = err.message || "DNS API 在线验证失败"; } finally { dnsTesting.value = false; } } function validateCurrentStep() { error.value = ""; resultMessage.value = ""; if (currentStep.value === 1) { if (!form.value.admin_username.trim()) { error.value = "请输入超级管理员用户名。"; return false; } if (!form.value.admin_email.trim()) { error.value = "请输入超级管理员绑定邮箱。"; return false; } if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.value.admin_email.trim())) { error.value = "请输入合法的超级管理员绑定邮箱。"; return false; } if (form.value.admin_password.length < 8) { error.value = "超级管理员密码至少需要 8 个字符。"; return false; } if (form.value.admin_password !== form.value.confirm_password) { error.value = "两次输入的超级管理员密码不一致。"; return false; } } if (currentStep.value === 2) { const port = Number(form.value.server_port); if (!Number.isInteger(port) || port < 1 || port > 65535) { error.value = "请输入 1 至 65535 之间的主服务端口。"; 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" && !form.value.email_http_credential_ref.trim() ) { error.value = "启用 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) { const provider = selectedDNSProvider.value; if (!provider) { error.value = "请选择 DNS 服务商。"; return false; } if (!provider.available) { error.value = "该 DNS 服务商在线验证适配器尚未开放。"; return false; } if (!dnsTestDomain.value.trim()) { error.value = "请填写 DNS 权限测试域名。"; return false; } for (const field of provider.credential_fields || []) { if ( field.required && !String(dnsCredentials.value[field.name] || "").trim() ) { error.value = `请填写${field.label}。`; return false; } } if (!dnsVerificationValid.value) { error.value = "请先完成 DNS API 在线验证。"; return false; } } if (currentStep.value === 4) { const hostname = targetType.value === "hostname" ? form.value.web_hostname.trim() : ""; const publicIP = targetType.value === "ip" ? form.value.web_public_ip.trim() : ""; if (!hostname && !publicIP) { error.value = `请填写${targetLabel.value}。`; return false; } if (!form.value.certificate_root.trim()) { error.value = "请填写证书部署根目录。"; return false; } } return true; } function nextStep() { if (!validateCurrentStep()) return; if (currentStep.value < 4) currentStep.value += 1; } function previousStep() { error.value = ""; resultMessage.value = ""; if (currentStep.value > 1) currentStep.value -= 1; } function handleSubmit() { if (currentStep.value < 4) { nextStep(); return; } submit(); } function recipients() { return form.value.recipients .split(/[\n,,]/) .map((item) => item.trim()) .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(/[,,]/) .map((item) => Number(item.trim())) .filter((item) => Number.isInteger(item) && item > 0); } async function submit() { error.value = ""; resultMessage.value = ""; if (form.value.admin_password.length < 8) { error.value = "超级管理员密码至少需要 8 个字符。"; return; } if (form.value.admin_password !== form.value.confirm_password) { error.value = "两次输入的超级管理员密码不一致。"; return; } if (!dnsVerificationValid.value) { error.value = "必须先完成 DNS API 在线验证;验证结果已过期时请重新测试。"; return; } const hostname = targetType.value === "hostname" ? form.value.web_hostname.trim() : ""; const publicIP = targetType.value === "ip" ? form.value.web_public_ip.trim() : ""; if (!hostname && !publicIP) { error.value = `请填写${targetLabel.value}。`; return; } submitting.value = true; try { const response = await fetch("/api/v1/setup", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ admin_username: form.value.admin_username, 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_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_headers: emailHeaders(), email_event_mode: form.value.email_event_mode, email_include_deployment_details: form.value.email_include_deployment_details, email_include_certificate_serial: form.value.email_include_certificate_serial, email_include_management_url: form.value.email_include_management_url, email_sender_name: form.value.email_sender_name, email_sender_email: form.value.email_sender_email, email_timeout_secs: Number(form.value.email_timeout_secs), email_retry_count: Number(form.value.email_retry_count), email_retry_intervals_secs: retryIntervals(), recipients: recipients(), dns_provider: form.value.dns_provider, dns_credential_ref: dnsVerification.value.credential_ref, dns_verification_token: dnsVerification.value.verification_token, dns_test_domain: dnsTestDomain.value.trim(), certificate_root: form.value.certificate_root, web_hostname: hostname, web_public_ip: publicIP, web_enabled: form.value.web_enabled, https_mode: form.value.https_mode, }), }); const result = await response.json().catch(() => ({})); if (!response.ok) throw new Error(result.error || "保存初始化配置失败"); resultMessage.value = result.message || "初始化配置已保存。"; emit("initialized", result); } catch (err) { error.value = err.message || "保存初始化配置失败"; } finally { submitting.value = false; } } watch( () => form.value.dns_provider, () => { dnsCredentials.value = {}; invalidateDNSVerification(); }, ); watch(dnsCredentials, invalidateDNSVerification, { deep: true }); watch(dnsTestDomain, invalidateDNSVerification); onMounted(() => { loadDNSProviders(); verificationTimer = window.setInterval(() => { verificationClock.value = Date.now(); }, 30000); }); onUnmounted(() => { if (verificationTimer) window.clearInterval(verificationTimer); }); return { scertLogo, submitting, error, resultMessage, dnsCatalogLoading, dnsCatalogError, dnsTesting, dnsTestError, dnsProviders, dnsCredentials, dnsTestDomain, dnsVerification, currentStep, targetType, form, commonEmailDomains, selectedEmailDomain, emailLocalPart, targetLabel, resetURLPreview, selectedDNSProvider, dnsVerificationValid, formatVerificationExpiry, applyEmailDomain, handleEmailInput, testDNS, handleSubmit, previousStep, }; }