diff --git a/web/config.js b/web/config.js index 70d378f..aa3cc4b 100644 --- a/web/config.js +++ b/web/config.js @@ -147,8 +147,6 @@ "privacy.body": "Your settings are encrypted before they are saved on the server. Email contents are not shown here.", "common.required": "Required", "common.optional": "Optional", - "common.buildVersion": "Build version", - "common.buildVersionUnknown": "Build version unavailable", "common.atLeastOne": "At least one", "common.ms": "ms", "common.times": "times", @@ -210,11 +208,11 @@ "login.fail": "Unable to connect to the admin service; check the network and retry.", "callback.eyebrow": "External callbacks", "callback.title": "Register Telegram and Stalwart callbacks", - "callback.copy": "Save the business configuration first. Enter the public HTTPS origin that receives callbacks, then register both services from this page.", + "callback.copy": "Save the business configuration first. This page registers callbacks with the Cloudflare Worker front proxy when it is enabled, or with the backend directly when it is not. Enter the public HTTPS origin that receives callbacks, then register both services from this page.", "callback.originLabel": "Public HTTPS origin", "callback.originPlaceholder": "https://mail.example.com", - "callback.originHint": "Use the Worker URL when enabled; otherwise use the backend URL. Enter only the origin, without a path.", - "callback.state": "Registration is an explicit action and can be repeated safely.", + "callback.originHint": "Callbacks hit the Cloudflare Worker front proxy, or the backend directly when no proxy is enabled. Use the Worker URL when the proxy is enabled, otherwise the backend URL. Enter only the origin, without a path.", + "callback.state": "Registration is an explicit action and can be repeated safely. Registering a new address cancels the previous subscription, so the old address stops receiving pushes.", "callback.saveFirst": "Save the current business configuration before registering callbacks.", "callback.register": "Register callbacks", "callback.registerBusy": "Registering…", @@ -360,8 +358,6 @@ "privacy.body": "你的设置会加密后保存在服务器上。这里不会显示邮件内容。", "common.required": "必填", "common.optional": "可选", - "common.buildVersion": "构建版本", - "common.buildVersionUnknown": "构建版本未知", "common.atLeastOne": "至少一项", "common.ms": "毫秒", "common.times": "次", @@ -425,11 +421,11 @@ "timezone.desc": "Telegram 通知中的收件时间按此时区渲染。", "callback.eyebrow": "外部回调", "callback.title": "注册 Telegram 和 Stalwart 回调", - "callback.copy": "先保存业务配置,再输入接收回调的公开 HTTPS 地址。本页会向两个服务注册回调。", + "callback.copy": "先保存业务配置。本页会向 Cloudflare Worker 前置代理(启用时)或后端本身注册回调。请输入接收回调的公开 HTTPS 地址,然后在本页注册两个服务。", "callback.originLabel": "公开 HTTPS 地址", "callback.originPlaceholder": "https://mail.example.com", - "callback.originHint": "启用 Worker 时填写 Worker 地址,否则填写后端地址。只填写站点 origin,不带路径。", - "callback.state": "注册需要显式操作,可以安全地重复执行。", + "callback.originHint": "回调会打到 Cloudflare Worker 前置代理或直接打到后端。启用前置代理时填 Worker 地址,否则填后端地址;只填站点 origin,不带路径。", + "callback.state": "注册需要显式操作,可以安全地重复执行。改用新地址注册会先注销旧订阅,旧地址不会再收到推送。", "callback.saveFirst": "请先保存当前业务配置,再注册回调。", "callback.register": "注册回调", "callback.registerBusy": "正在注册…", @@ -619,9 +615,6 @@ const llmModel = document.querySelector("#llm-model"); const notice = document.querySelector("#notice"); const preflightButton = document.querySelector("#business-preflight-button"); - const versionFooter = document.querySelector("#build-version"); - const versionTop = document.querySelector("#build-version-top"); - const versionSetup = document.querySelector("#build-version-setup"); // C-NO-LOCAL-WRITE / C-REDIS-ONLY-STATE: credentials and configuration stay in page memory. let adminSession = ""; @@ -716,24 +709,12 @@ return parseResponseText(responseText, response.status); } - // The build fingerprint comes from /api/status so the SPA never hard-codes one: the binary - // supplies it at compile time and the page just echoes it. Absent means the backend is older - // than the version endpoint, which is itself useful information for an operator. - function renderVersion(version) { - const value = typeof version === "string" && version.trim() ? version.trim() : ""; - const label = t("common.buildVersion"); - if (versionFooter) versionFooter.textContent = value ? `${label} ${value}` : t("common.buildVersionUnknown"); - if (versionTop) versionTop.textContent = value; - if (versionSetup) versionSetup.textContent = value ? `${label} ${value}` : ""; - } - async function showSetupStatus(options = {}) { showSetupView(t("setup.checking")); statusRetryButton.disabled = true; statusRetryButton.textContent = t("setup.checkingBtn"); try { const status = await request("/api/status", "GET", undefined, ""); - renderVersion(status && status.version); if (status && status.ready === true) { setupMode = false; setupCard.hidden = true; diff --git a/web/index.html b/web/index.html index 1161790..a15b2a3 100644 --- a/web/index.html +++ b/web/index.html @@ -19,7 +19,6 @@ MessageWeaveService configuration Secure configuration channel -
@@ -43,7 +42,6 @@

Startup secrets must be configured in the deployment environment. Secrets are never saved or echoed in the page.

- -
+

External callbacks

Register Telegram and Stalwart callbacks

-

Save the business configuration first. Enter the public HTTPS origin that receives callbacks, then register both services from this page.

-
Use the Worker URL when enabled; otherwise use the backend URL. Enter only the origin, without a path.
- +

Save the business configuration first. This page registers callbacks with the Cloudflare Worker front proxy when it is enabled, or with the backend directly when it is not. Enter the public HTTPS origin that receives callbacks, then register both services from this page.

+
+
Callbacks hit the Cloudflare Worker front proxy, or the backend directly when no proxy is enabled. Use the Worker URL when the proxy is enabled, otherwise the backend URL. Enter only the origin, without a path.
+
+
@@ -172,7 +172,5 @@
- - diff --git a/web/styles.css b/web/styles.css index 8d3d584..b476dcd 100644 --- a/web/styles.css +++ b/web/styles.css @@ -31,8 +31,6 @@ button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus- .brand-copy strong { font-size: 14px; letter-spacing: -.2px; } .brand-copy > span { color: #85869a; font-size: 11px; letter-spacing: .05em; } .topbar-note { display: inline-flex; align-items: center; gap: 8px; margin-left: auto; color: #77798e; font-size: 12px; } -.topbar-version { display: none; color: #a1a1b0; font-size: 10px; padding: 4px 8px; border: 1px solid #e6e6ef; border-radius: 999px; background: rgba(255, 255, 255, .8); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; } -@media (min-width: 900px) { .topbar-version { display: inline-block; } } .secure-dot, .persist-dot { width: 7px; height: 7px; border-radius: 50%; background: #48ae86; box-shadow: 0 0 0 3px rgba(72, 174, 134, .12); } .layout { width: min(960px, calc(100% - 40px)); margin: 0 auto; padding: 54px 0 56px; } @@ -65,7 +63,6 @@ h3 { margin-bottom: 4px; color: #35364e; font-size: 15px; } .setup-panel .panel-copy { margin-bottom: 14px; } .setup-missing { margin: 0 0 17px; padding: 12px 14px; border: 1px solid #f2e9c8; border-radius: 8px; background: #fffbef; color: #796132; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; line-height: 1.6; overflow-wrap: anywhere; } .setup-privacy { margin: 0 0 17px; color: #858697; font-size: 12px; line-height: 1.65; } -.build-version { margin: 20px 0 0; padding-top: 15px; border-top: 1px solid #efeff5; color: #a1a1b0; font-size: 10px; line-height: 1.5; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; overflow-wrap: anywhere; } code { padding: 2px 5px; border-radius: 4px; background: #f1f0fa; color: #534a9c; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .92em; } .auth-form > label, .field > label, .setting-card > label { display: block; margin-bottom: 7px; color: #47485f; font-size: 12px; font-weight: 700; } .token-row { display: flex; gap: 10px; } @@ -144,6 +141,9 @@ fieldset:disabled .field input, fieldset:disabled .field textarea, fieldset:disa .save-state.is-dirty { color: #9c712e; } .form-actions { display: flex; gap: 8px; } .runtime-section .settings-grid { padding: 0 25px 22px; } +/* The callback card has no `.form-section` wrapper, so its grid needs the + section padding the other sections get for free. */ +.callback-section .field-grid { padding: 0 25px 22px; } .settings-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; } .setting-card { min-width: 0; padding: 16px; border: 1px solid #eeeef4; border-radius: 11px; background: #fff; } .setting-card > label { margin-bottom: 5px; color: #35364e; font-size: 13px; } @@ -158,9 +158,6 @@ fieldset:disabled .field input, fieldset:disabled .field textarea, fieldset:disa .privacy-icon svg { width: 18px; height: 18px; } .privacy-note p { margin: 0; font-size: 11px; line-height: 1.65; } .privacy-note strong { color: #64657a; font-weight: 680; } -.footer { width: min(1100px, calc(100% - 48px)); display: flex; justify-content: center; margin: 0 auto; padding: 18px 0 23px; border-top: 1px solid rgba(224, 224, 238, .72); color: #a5a5b2; font-size: 9px; letter-spacing: .08em; } -.footer span:last-child { letter-spacing: 0; } -.footer-version { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0; } .visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; clip-path: inset(50%); border: 0; } @media (max-width: 720px) { @@ -188,7 +185,7 @@ fieldset:disabled .field input, fieldset:disabled .field textarea, fieldset:disa .form-actions .button { flex: 1; } .runtime-section .settings-grid { padding: 0 14px 17px; } .section-copy { margin: -3px 17px 16px; } - .footer { width: calc(100% - 34px); } + .callback-section .field-grid { padding: 0 17px 17px; } } @media (max-width: 420px) {