163 lines
19 KiB
HTML
163 lines
19 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<meta name="color-scheme" content="light" />
|
||
<meta name="referrer" content="no-referrer" />
|
||
<title data-i18n="app.title">Service Configuration · MessageWeave</title>
|
||
<link rel="stylesheet" href="/assets/styles.css" />
|
||
<script src="/assets/config.js" defer></script>
|
||
</head>
|
||
<body>
|
||
<div class="ambient ambient-one" aria-hidden="true"></div>
|
||
<div class="ambient ambient-two" aria-hidden="true"></div>
|
||
|
||
<header class="topbar">
|
||
<a class="brand" href="/" data-i18n-aria="brand.aria" aria-label="MessageWeave configuration home">
|
||
<span class="brand-mark" aria-hidden="true">S</span>
|
||
<span class="brand-copy"><strong>MessageWeave</strong><span data-i18n="brand.tagline">Service configuration</span></span>
|
||
</a>
|
||
<span class="topbar-note"><span class="secure-dot"></span><span data-i18n="topbar.note">Secure configuration channel</span></span>
|
||
</header>
|
||
|
||
<main class="layout">
|
||
<section id="intro" class="intro" aria-labelledby="page-title" hidden>
|
||
<p class="eyebrow"><span data-i18n="intro.eyebrow">Service management</span> <span>·</span> CONFIGURATION</p>
|
||
<h1 id="page-title" data-i18n="intro.title">Manage your service connections</h1>
|
||
<p class="intro-copy" data-i18n="intro.copy">Configure JMAP, Telegram, LLM, access allowlist and runtime parameters. Business configuration is stored in external Redis and hot-reloaded immediately after a successful save.</p>
|
||
</section>
|
||
|
||
<div id="notice" class="notice notice-info" role="status" aria-live="polite" hidden data-i18n="notice.bootstrap">
|
||
Create a short-lived admin session using the CONFIG_ENCRYPTION_KEY value.
|
||
</div>
|
||
|
||
<section id="setup-card" class="panel auth-panel setup-panel" aria-labelledby="setup-title" aria-live="polite">
|
||
<div class="panel-heading">
|
||
<div class="icon-tile icon-violet" aria-hidden="true">
|
||
<svg viewBox="0 0 24 24"><path d="M12 3 4 7v5c0 4.4 3.1 7.6 8 9 4.9-1.4 8-4.6 8-9V7l-8-4Z"/><path d="M12 8v5M12 16h.01"/></svg>
|
||
</div>
|
||
<div><p class="eyebrow" data-i18n="setup.eyebrow">Setup guide</p><h2 id="setup-title" data-i18n="setup.title">Service startup configuration</h2></div>
|
||
</div>
|
||
<p id="setup-message" class="panel-copy" data-i18n="setup.checking">Checking service startup configuration…</p>
|
||
<p id="setup-missing" class="setup-missing" hidden></p>
|
||
<p class="setup-privacy" data-i18n="setup.privacy">Startup secrets must be configured in the deployment environment. Secrets are never saved or echoed in the page.</p>
|
||
<button id="status-retry-button" class="button button-quiet" type="button" data-i18n="setup.retry">Re-check</button>
|
||
</section>
|
||
|
||
<section id="auth-card" class="panel auth-panel" aria-labelledby="auth-title" hidden>
|
||
<div class="panel-heading">
|
||
<div class="icon-tile icon-violet" aria-hidden="true">
|
||
<svg viewBox="0 0 24 24"><path d="M7.5 10V7.5a4.5 4.5 0 0 1 9 0V10M6 10h12v10H6z"/><path d="M12 14v2"/></svg>
|
||
</div>
|
||
<div><p class="eyebrow" data-i18n="auth.eyebrow">Authorization required</p><h2 id="auth-title" data-i18n="auth.title">Create admin session</h2></div>
|
||
</div>
|
||
<p class="panel-copy"><span data-i18n="auth.bodyPre">Enter the value of the required startup key </span><code>CONFIG_ENCRYPTION_KEY</code><span data-i18n="auth.bodyPost">. The server issues a 15-minute admin session; the key and session exist only in the current page memory.</span></p>
|
||
<form id="auth-form" class="auth-form" autocomplete="off">
|
||
<label for="bootstrap-input" data-i18n="auth.label">Business encryption key</label>
|
||
<div class="token-row">
|
||
<input id="bootstrap-input" name="admin-credential" type="password" autocomplete="new-password" spellcheck="false" required data-i18n-placeholder="auth.placeholder" placeholder="Enter the CONFIG_ENCRYPTION_KEY value" aria-describedby="bootstrap-hint" />
|
||
<button id="connect-button" class="button button-primary" type="submit" data-i18n="auth.submit">Create session</button>
|
||
</div>
|
||
<p id="bootstrap-hint" class="field-hint" data-i18n="auth.hint">Never written to localStorage, sessionStorage or cookies. You must re-authorize after closing or refreshing the page.</p>
|
||
</form>
|
||
</section>
|
||
|
||
<section id="config-panel" class="config-panel" data-i18n-aria="config.aria" aria-label="Business configuration and runtime parameters" hidden>
|
||
<div class="panel config-toolbar">
|
||
<div class="session-state"><span class="status-pulse" aria-hidden="true"></span><div><strong data-i18n="session.active">Admin session active</strong><span id="session-expiry" data-i18n="session.expiryDefault">Session kept only in current page memory</span></div></div>
|
||
<div class="toolbar-actions">
|
||
<label class="toggle-card" for="service-enabled"><input id="service-enabled" type="checkbox" /><span><strong data-i18n="service.enable">Enable business processing</strong><small id="service-enabled-state" data-i18n="service.loading">Loading…</small></span></label>
|
||
<button id="reload-button" class="button button-quiet" type="button" data-i18n="reload.button">Reload runtime parameters</button>
|
||
<button id="logout-button" class="button button-quiet" type="button" data-i18n="logout.button">Sign out and clear session</button>
|
||
</div>
|
||
</div>
|
||
|
||
<section class="panel config-section" aria-labelledby="business-title">
|
||
<div class="section-heading">
|
||
<div><p class="eyebrow" data-i18n="business.eyebrow">Full replacement</p><h2 id="business-title" data-i18n="business.title">Business connections and auth</h2></div>
|
||
<span class="persist-label"><span class="persist-dot"></span><span data-i18n="business.persist">Encrypted in Redis</span></span>
|
||
</div>
|
||
<div class="callout callout-warning">
|
||
<strong data-i18n="callout.title">Business configuration is not read from the server.</strong>
|
||
<span data-i18n="callout.bodyPre"> To avoid secret echo, the API does not expose a business configuration GET. Every submission must re-fill the complete configuration, including all required secrets; optional account ID and LLM fields are sent as </span><code>null</code><span data-i18n="callout.bodyPost"> when left blank.</span>
|
||
</div>
|
||
|
||
<form id="business-form" novalidate>
|
||
<fieldset id="business-fields" disabled>
|
||
<legend class="visually-hidden" data-i18n="legend.business">Full business configuration</legend>
|
||
|
||
<section class="form-section" aria-labelledby="jmap-title">
|
||
<div class="form-section-heading"><span class="section-number">01</span><div><h3 id="jmap-title" data-i18n="jmap.title">JMAP mailbox</h3><p data-i18n="jmap.desc">The server must use HTTPS; the URL must not contain credentials, query parameters or fragments.</p></div></div>
|
||
<div class="field-grid">
|
||
<div class="field field-wide"><label for="jmap-session-url">JMAP Session URL <span class="required-mark" data-i18n="common.required">Required</span></label><input id="jmap-session-url" name="jmap_session_url" type="url" inputmode="url" autocomplete="url" required placeholder="https://mail.example.com/.well-known/jmap" /><small data-i18n="jmap.urlHint">HTTPS address without username, password, query parameters or #fragment.</small></div>
|
||
<div class="field"><label for="jmap-username"><span data-i18n="jmap.userLabel">Email username</span> <span class="required-mark" data-i18n="common.required">Required</span></label><input id="jmap-username" name="jmap_username" type="text" autocomplete="username" required /></div>
|
||
<div class="field"><label for="jmap-password"><span data-i18n="jmap.passLabel">App-specific password</span> <span class="required-mark" data-i18n="common.required">Required</span></label><input id="jmap-password" name="jmap_password" type="password" autocomplete="new-password" spellcheck="false" required data-i18n-placeholder="secret.reenter" placeholder="Re-enter on each full save" /></div>
|
||
<div class="field"><label for="account-id">JMAP Account ID <span class="optional-mark" data-i18n="common.optional">Optional</span></label><input id="account-id" name="account_id" type="text" autocomplete="off" data-i18n-placeholder="jmap.accountPh" placeholder="Leave blank to use the session primary account" /></div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="form-section" aria-labelledby="telegram-title">
|
||
<div class="form-section-heading"><span class="section-number">02</span><div><h3 id="telegram-title" data-i18n="telegram.title">Telegram and access auth</h3><p data-i18n="telegram.desc">Only chat IDs in the allowlist may trigger bot operations.</p></div></div>
|
||
<div class="field-grid">
|
||
<div class="field"><label for="telegram-bot-token">Telegram Bot Token <span class="required-mark" data-i18n="common.required">Required</span></label><input id="telegram-bot-token" name="bot_token" type="password" autocomplete="new-password" spellcheck="false" required data-i18n-placeholder="secret.reenter" placeholder="Re-enter on each full save" /></div>
|
||
<div class="field"><label for="telegram-chat-id"><span data-i18n="telegram.chatLabel">Target Chat ID</span> <span class="required-mark" data-i18n="common.required">Required</span></label><input id="telegram-chat-id" name="telegram_chat_id" type="text" inputmode="numeric" autocomplete="off" required data-i18n-placeholder="telegram.chatPh" placeholder="e.g. -1001234567890" /><small data-i18n="telegram.chatHint">Accepts a full signed 64-bit integer.</small></div>
|
||
<div class="field field-wide"><label for="chat-allowlist">Chat allowlist <span class="required-mark" data-i18n="common.atLeastOne">At least one</span></label><textarea id="chat-allowlist" name="chat_allowlist" rows="3" required spellcheck="false" data-i18n-placeholder="telegram.allowPh" placeholder="One chat ID per line or comma-separated"></textarea><small data-i18n="telegram.allowHint">Integer list; format and duplicates are checked before saving.</small></div>
|
||
<div class="field"><label for="telegram-webhook-secret">Telegram Webhook Secret <span class="required-mark" data-i18n="common.required">Required</span></label><input id="telegram-webhook-secret" name="telegram_webhook_secret" type="password" autocomplete="new-password" spellcheck="false" required data-i18n-placeholder="secret.reenter" placeholder="Re-enter on each full save" /></div>
|
||
<div class="field"><label for="reconcile-token">Reconcile Bearer Token <span class="required-mark" data-i18n="common.required">Required</span></label><input id="reconcile-token" name="reconcile_token" type="password" autocomplete="new-password" spellcheck="false" required data-i18n-placeholder="secret.reenter" placeholder="Re-enter on each full save" /></div>
|
||
<div class="field field-wide"><label for="worker-token">Worker Token <span class="required-mark" data-i18n="common.required">Required</span></label><input id="worker-token" name="worker_token" type="password" autocomplete="new-password" spellcheck="false" required data-i18n-placeholder="telegram.workerPh" placeholder="Used by /worker and compatible admin APIs; re-enter on each full save" /><small data-i18n="telegram.workerHint">This value differs from the current admin session. The page never reads or echoes an existing token.</small></div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="form-section" aria-labelledby="llm-title">
|
||
<div class="form-section-heading"><span class="section-number">03</span><div><h3 id="llm-title" data-i18n="llm.title">LLM service</h3><p data-i18n="llm.desc">When enabled, an HTTPS Base URL, model name and API key are required.</p></div></div>
|
||
<div class="toggle-grid">
|
||
<label class="toggle-card" for="llm-enabled"><input id="llm-enabled" name="llm_enabled" type="checkbox" /><span><strong data-i18n="llm.enableLabel">Enable LLM</strong><small data-i18n="llm.enableHint">Allow the bot to call OpenAI-compatible model services.</small></span></label>
|
||
<label class="toggle-card" for="llm-allow-net"><input id="llm-allow-net" name="llm_allow_net" type="checkbox" /><span><strong data-i18n="llm.netLabel">Allow network calls</strong><small data-i18n="llm.netHint">When off, no LLM client is created.</small></span></label>
|
||
</div>
|
||
<div class="field-grid llm-fields">
|
||
<div class="field"><label for="llm-base-url">LLM Base URL <span id="llm-url-required" class="optional-mark" data-i18n="common.optional">Optional</span></label><input id="llm-base-url" name="llm_base_url" type="url" inputmode="url" autocomplete="url" placeholder="https://api.example.com/v1" /><small data-i18n="llm.urlHint">HTTPS is required when LLM is enabled.</small></div>
|
||
<div class="field"><label for="llm-model"><span data-i18n="llm.modelLabel">Model name</span> <span id="llm-model-required" class="optional-mark" data-i18n="common.optional">Optional</span></label><input id="llm-model" name="llm_model" type="text" autocomplete="off" data-i18n-placeholder="llm.modelPh" placeholder="Leave blank for null" /></div>
|
||
<div class="field field-wide"><label for="llm-api-key">LLM API Key <span id="llm-key-required" class="optional-mark" data-i18n="common.optional">Optional</span></label><input id="llm-api-key" name="llm_api_key" type="password" autocomplete="new-password" spellcheck="false" data-i18n-placeholder="llm.keyPh" placeholder="Required when LLM is enabled; never echoed" /></div>
|
||
</div>
|
||
</section>
|
||
</fieldset>
|
||
|
||
<div class="form-footer">
|
||
<p id="business-save-state" class="save-state" aria-live="polite" data-i18n="business.saveStateInitial">You can fill in the full configuration after the admin session is verified.</p>
|
||
<button id="business-save-button" class="button button-primary" type="submit" disabled><span class="button-label" data-i18n="business.save">Save business config and hot-reload</span><span class="button-spinner" aria-hidden="true"></span></button>
|
||
</div>
|
||
</form>
|
||
</section>
|
||
|
||
<section class="panel config-section runtime-section" aria-labelledby="runtime-title">
|
||
<div class="section-heading">
|
||
<div><p class="eyebrow" data-i18n="runtime.eyebrow">Outbound requests</p><h2 id="runtime-title" data-i18n="runtime.title">Timeouts and retries</h2></div>
|
||
<span class="persist-label"><span class="persist-dot"></span><span data-i18n="runtime.persist">Stored in Redis</span></span>
|
||
</div>
|
||
<p class="section-copy"><span data-i18n="runtime.bodyPre">Runtime parameters are read by </span><code>GET /api/config</code><span data-i18n="runtime.bodyMid"> and updated by </span><code>PUT /api/config</code><span data-i18n="runtime.bodyPost">; new values apply immediately to subsequent requests after saving.</span></p>
|
||
<form id="runtime-form" novalidate>
|
||
<fieldset id="runtime-fields" disabled>
|
||
<legend class="visually-hidden" data-i18n="legend.runtime">Runtime parameters</legend>
|
||
<div class="settings-grid">
|
||
<article class="setting-card"><label for="jmap-timeout" data-i18n="runtime.jmapLabel">JMAP timeout</label><p class="setting-description" data-i18n="runtime.jmapDesc">Upper wait limit for a single JMAP request.</p><div class="number-control"><input id="jmap-timeout" name="jmap_timeout_ms" type="number" min="100" max="300000" step="1" required /><span data-i18n="common.ms">ms</span></div><small data-i18n="runtime.jmapHint">100–300,000; default 15,000.</small></article>
|
||
<article class="setting-card"><label for="telegram-timeout" data-i18n="runtime.tgLabel">Telegram timeout</label><p class="setting-description" data-i18n="runtime.tgDesc">Upper wait limit for a single Telegram request.</p><div class="number-control"><input id="telegram-timeout" name="telegram_timeout_ms" type="number" min="100" max="300000" step="1" required /><span data-i18n="common.ms">ms</span></div><small data-i18n="runtime.tgHint">100–300,000; default 10,000.</small></article>
|
||
<article class="setting-card"><label for="llm-timeout" data-i18n="runtime.llmLabel">LLM timeout</label><p class="setting-description" data-i18n="runtime.llmDesc">Upper wait limit for a single model request.</p><div class="number-control"><input id="llm-timeout" name="llm_timeout_ms" type="number" min="100" max="300000" step="1" required /><span data-i18n="common.ms">ms</span></div><small data-i18n="runtime.llmHint">100–300,000; default 30,000.</small></article>
|
||
<article class="setting-card"><label for="max-retries" data-i18n="runtime.retriesLabel">Max retries</label><p class="setting-description" data-i18n="runtime.retriesDesc">Extra attempts after a failure; 0 means no retry.</p><div class="number-control"><input id="max-retries" name="max_retries" type="number" min="0" max="5" step="1" required /><span data-i18n="common.times">times</span></div><small data-i18n="runtime.retriesHint">0–5; default 3.</small></article>
|
||
</div>
|
||
</fieldset>
|
||
<div class="form-footer">
|
||
<p id="runtime-save-state" class="save-state" aria-live="polite" data-i18n="runtime.saveStateInitial">Runtime parameters become editable after a successful read.</p>
|
||
<div class="form-actions"><button id="defaults-button" class="button button-quiet" type="button" disabled data-i18n="runtime.defaults">Restore defaults</button><button id="runtime-save-button" class="button button-primary" type="submit" disabled><span class="button-label" data-i18n="runtime.save">Save runtime parameters</span><span class="button-spinner" aria-hidden="true"></span></button></div>
|
||
</div>
|
||
</form>
|
||
</section>
|
||
</section>
|
||
|
||
<aside id="privacy-note" class="privacy-note" hidden><span class="privacy-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 3 5 6v5c0 4.5 2.8 8 7 10 4.2-2 7-5.5 7-10V6l-7-3Z"/><path d="m9 12 2 2 4-4"/></svg></span><p><strong data-i18n="privacy.title">Secrets are never echoed or persisted to the browser.</strong> <span data-i18n="privacy.body">Business configuration is submitted only to the same-origin API and encrypted into Redis by the backend; this page never reads mailbox contents and uses no local files or database.</span></p></aside>
|
||
</main>
|
||
|
||
<footer class="footer"><span>MessageWeave</span><span data-i18n="footer.note">Configuration managed by your server</span></footer>
|
||
</body>
|
||
</html>
|