Settings: add dedicated DAV/Pushover tabs, fix CalDAV/CardDAV bugs
- Add admin DAV tab (rename from CalDAV/CardDAV) and Pushover tab
- Add per-user Pushover tab (User Key only; App Token stays admin-managed)
- Remove system-wide CalDAV/CardDAV fallback — per-user config only
- Rewrite contacts_tool.py using httpx directly (caldav 2.x dropped AddressBook)
- Fix CardDAV REPORT/PROPFIND using SOGo URL pattern
- Fix CalDAV/CardDAV test endpoints (POST method, URL scheme normalization)
- Fix Show Password button — API now returns actual credential values
- Convert Credentials tab to generic key-value store; dedicated keys
(CalDAV, Pushover, trusted_proxy) excluded via _DEDICATED_CRED_KEYS
This commit is contained in:
@@ -17,6 +17,8 @@
|
||||
<button type="button" class="tab-btn active" id="stab-general" onclick="switchSettingsTab('general')">General</button>
|
||||
<button type="button" class="tab-btn" id="stab-whitelists" onclick="switchSettingsTab('whitelists')">Whitelists</button>
|
||||
<button type="button" class="tab-btn" id="stab-credentials" onclick="switchSettingsTab('credentials')">Credentials</button>
|
||||
<button type="button" class="tab-btn" id="stab-caldav" onclick="switchSettingsTab('caldav')">DAV</button>
|
||||
<button type="button" class="tab-btn" id="stab-pushover" onclick="switchSettingsTab('pushover')">Pushover</button>
|
||||
<button type="button" class="tab-btn" id="stab-inbox" onclick="switchSettingsTab('inbox')">Inbox</button>
|
||||
<button type="button" class="tab-btn" id="stab-emailaccounts" onclick="switchSettingsTab('emailaccounts')">Email Accounts</button>
|
||||
<button type="button" class="tab-btn" id="stab-telegram" onclick="switchSettingsTab('telegram')">Telegram</button>
|
||||
@@ -25,6 +27,7 @@
|
||||
<button type="button" class="tab-btn" id="stab-mcp" onclick="switchSettingsTab('mcp')">MCP Servers</button>
|
||||
<button type="button" class="tab-btn" id="stab-security" onclick="switchSettingsTab('security')">Security</button>
|
||||
<button type="button" class="tab-btn" id="stab-branding" onclick="switchSettingsTab('branding')">Branding</button>
|
||||
<button type="button" class="tab-btn" id="stab-webhooks" onclick="switchSettingsTab('webhooks')">Webhooks</button>
|
||||
<button type="button" class="tab-btn" id="stab-mfa" onclick="switchSettingsTab('mfa')">Profile</button>
|
||||
</div>
|
||||
{% else %}
|
||||
@@ -34,10 +37,12 @@
|
||||
<button type="button" class="tab-btn" id="ustab-personality" onclick="switchUserTab('personality')">Personality</button>
|
||||
<button type="button" class="tab-btn" id="ustab-inbox" onclick="switchUserTab('inbox')">Inbox</button>
|
||||
<button type="button" class="tab-btn" id="ustab-emailaccounts" onclick="switchUserTab('emailaccounts')">Email Accounts</button>
|
||||
<button type="button" class="tab-btn" id="ustab-caldav" onclick="switchUserTab('caldav')">CalDAV</button>
|
||||
<button type="button" class="tab-btn" id="ustab-caldav" onclick="switchUserTab('caldav')">CalDAV / CardDAV</button>
|
||||
<button type="button" class="tab-btn" id="ustab-telegram" onclick="switchUserTab('telegram')">Telegram</button>
|
||||
<button type="button" class="tab-btn" id="ustab-mcp" onclick="switchUserTab('mcp')">MCP Servers</button>
|
||||
<button type="button" class="tab-btn" id="ustab-brain" onclick="switchUserTab('brain')">2nd Brain</button>
|
||||
<button type="button" class="tab-btn" id="ustab-pushover" onclick="switchUserTab('pushover')">Pushover</button>
|
||||
<button type="button" class="tab-btn" id="ustab-webhooks" onclick="switchUserTab('webhooks')">Webhooks</button>
|
||||
<button type="button" class="tab-btn" id="ustab-mfa" onclick="switchUserTab('mfa')">Profile</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -107,7 +112,7 @@
|
||||
Override the defaults from <code>.env</code>. Changes take effect immediately — no restart needed.
|
||||
Individual agents can further override <em>Max tool calls</em> on their own settings page.
|
||||
</p>
|
||||
<form id="limits-form" style="display:flex;gap:12px;flex-wrap:wrap;align-items:flex-end;max-width:520px">
|
||||
<form id="limits-form" style="display:flex;gap:12px;flex-wrap:wrap;align-items:flex-end;max-width:680px">
|
||||
<div class="form-group" style="flex:1;min-width:160px;margin-bottom:0">
|
||||
<label>Max tool calls per run</label>
|
||||
<input type="number" id="lim-tool-calls" class="form-input" min="1" max="200">
|
||||
@@ -116,6 +121,10 @@
|
||||
<label>Max autonomous runs / hour</label>
|
||||
<input type="number" id="lim-runs-per-hour" class="form-input" min="1" max="1000">
|
||||
</div>
|
||||
<div class="form-group" style="flex:1;min-width:160px;margin-bottom:0">
|
||||
<label>Max concurrent runs</label>
|
||||
<input type="number" id="lim-concurrent-runs" class="form-input" min="1" max="20">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary" style="margin-bottom:0">Save</button>
|
||||
</form>
|
||||
<div id="limits-defaults" style="font-size:11px;color:var(--text-dim);margin-top:10px"></div>
|
||||
@@ -404,17 +413,18 @@
|
||||
|
||||
<!-- Stored credentials -->
|
||||
<section style="margin-bottom:32px">
|
||||
<h2 class="settings-section-title">Encrypted Credentials</h2>
|
||||
<h2 class="settings-section-title">Encrypted Credential Store</h2>
|
||||
<p style="font-size:12px;color:var(--text-dim);margin-bottom:16px">
|
||||
Credentials for CalDAV, Email, and Pushover. All values are stored AES-256-GCM encrypted.
|
||||
Inbox, Telegram, and other integration credentials are managed in their respective Settings tabs.
|
||||
Generic key-value store for any credentials not managed in a dedicated settings tab.
|
||||
All values are stored AES-256-GCM encrypted.
|
||||
CalDAV, CardDAV, Pushover, Inbox, and Telegram credentials are managed in their own tabs.
|
||||
</p>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Credential</th>
|
||||
<th>Used By</th>
|
||||
<th>Key</th>
|
||||
<th>Description</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
@@ -428,6 +438,102 @@
|
||||
|
||||
</div><!-- /spane-credentials -->
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════
|
||||
TAB: CalDAV / CardDAV (admin system credentials)
|
||||
═══════════════════════════════════════════════════════════ -->
|
||||
<div id="spane-caldav" style="display:none">
|
||||
|
||||
<!-- CalDAV (Calendar) -->
|
||||
<section style="max-width:560px;margin-bottom:36px;padding-bottom:32px;border-bottom:1px solid var(--border)">
|
||||
<h2 class="settings-section-title">My CalDAV <span style="font-weight:400;font-size:13px;color:var(--text-dim)">(Calendar)</span></h2>
|
||||
<p style="font-size:12px;color:var(--text-dim);margin-bottom:16px">
|
||||
Your personal CalDAV credentials. Each user configures their own — there is no shared fallback.
|
||||
</p>
|
||||
<div class="form-group"><label>Mailcow Host</label>
|
||||
<input type="text" id="adm-caldav-host" class="form-input" placeholder="mail.example.com"></div>
|
||||
<div class="form-group"><label>Username</label>
|
||||
<input type="text" id="adm-caldav-username" class="form-input" placeholder="jarvis@example.com"></div>
|
||||
<div class="form-group"><label>Password</label>
|
||||
<div style="display:flex;gap:8px;align-items:center">
|
||||
<input type="password" id="adm-caldav-password" class="form-input" placeholder="Leave blank to keep existing">
|
||||
<button type="button" class="btn btn-ghost btn-small" onclick="togglePasswordVisibility('adm-caldav-password', this)">Show</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group"><label>Calendar name <span style="color:var(--text-dim);font-size:11px">(optional)</span></label>
|
||||
<input type="text" id="adm-caldav-calendar-name" class="form-input" placeholder="Leave blank to use first found"></div>
|
||||
<div style="margin-top:4px">
|
||||
<button class="btn btn-ghost btn-small" onclick="testAdminCaldav()" id="adm-caldav-test-btn">Test CalDAV</button>
|
||||
<div id="adm-caldav-test-result" style="margin-top:8px;font-size:13px"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CardDAV (Contacts) -->
|
||||
<section style="max-width:560px;margin-bottom:36px;padding-bottom:32px;border-bottom:1px solid var(--border)">
|
||||
<h2 class="settings-section-title">System CardDAV <span style="font-weight:400;font-size:13px;color:var(--text-dim)">(Contacts)</span></h2>
|
||||
<div class="form-group" style="display:flex;align-items:center;gap:10px;margin-bottom:16px">
|
||||
<input type="checkbox" id="adm-carddav-same" style="width:auto" onchange="onAdmCarddavSameChange()">
|
||||
<label for="adm-carddav-same" style="margin:0;cursor:pointer;font-size:13px">Same server as CalDAV (use CalDAV credentials)</label>
|
||||
</div>
|
||||
<div id="adm-carddav-custom-fields">
|
||||
<div class="form-group"><label>CardDAV Server URL</label>
|
||||
<input type="text" id="adm-carddav-url" class="form-input" placeholder="https://contacts.example.com"></div>
|
||||
<div class="form-group"><label>Username</label>
|
||||
<input type="text" id="adm-carddav-username" class="form-input" placeholder="user@example.com"></div>
|
||||
<div class="form-group"><label>Password</label>
|
||||
<div style="display:flex;gap:8px;align-items:center">
|
||||
<input type="password" id="adm-carddav-password" class="form-input" placeholder="Leave blank to keep existing">
|
||||
<button type="button" class="btn btn-ghost btn-small" onclick="togglePasswordVisibility('adm-carddav-password', this)">Show</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" style="display:flex;align-items:center;gap:10px;padding-top:12px;border-top:1px solid var(--border)">
|
||||
<input type="checkbox" id="adm-contacts-allow-write" style="width:auto">
|
||||
<label for="adm-contacts-allow-write" style="margin:0;cursor:pointer;font-size:13px">
|
||||
Allow contact writes <span style="color:var(--text-dim)">(agents can create/update/delete contacts)</span>
|
||||
</label>
|
||||
</div>
|
||||
<div style="margin-top:8px">
|
||||
<button class="btn btn-ghost btn-small" onclick="testAdminCarddav()" id="adm-carddav-test-btn">Test CardDAV</button>
|
||||
<div id="adm-carddav-test-result" style="margin-top:8px;font-size:13px"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div style="display:flex;gap:8px;align-items:center">
|
||||
<button class="btn btn-primary" onclick="saveAdminCaldav()">Save all</button>
|
||||
</div>
|
||||
|
||||
</div><!-- /spane-caldav -->
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════
|
||||
TAB: Pushover (admin)
|
||||
═══════════════════════════════════════════════════════════ -->
|
||||
<div id="spane-pushover" style="display:none">
|
||||
<section style="max-width:520px">
|
||||
<h2 class="settings-section-title">Pushover</h2>
|
||||
<p style="font-size:12px;color:var(--text-dim);margin-bottom:20px">
|
||||
Pushover delivers push notifications to iOS/Android devices.
|
||||
The <strong>App Token</strong> is created at <a href="https://pushover.net" target="_blank" style="color:var(--accent)">pushover.net</a>
|
||||
and is shared across all users of this installation.
|
||||
Each user sets their own <strong>User Key</strong> in their personal Settings → Pushover tab.
|
||||
</p>
|
||||
<div class="form-group"><label>App Token</label>
|
||||
<div style="display:flex;gap:8px;align-items:center">
|
||||
<input type="password" id="adm-pushover-app-token" class="form-input" placeholder="Leave blank to keep existing">
|
||||
<button type="button" class="btn btn-ghost btn-small" onclick="togglePasswordVisibility('adm-pushover-app-token', this)">Show</button>
|
||||
</div>
|
||||
<div id="adm-pushover-app-token-status" style="font-size:12px;color:var(--text-dim);margin-top:4px"></div>
|
||||
</div>
|
||||
<div class="form-group"><label>Your User Key <span style="color:var(--text-dim);font-size:11px">(admin's own notifications)</span></label>
|
||||
<div style="display:flex;gap:8px;align-items:center">
|
||||
<input type="password" id="adm-pushover-user-key" class="form-input" placeholder="Leave blank to keep existing">
|
||||
<button type="button" class="btn btn-ghost btn-small" onclick="togglePasswordVisibility('adm-pushover-user-key', this)">Show</button>
|
||||
</div>
|
||||
<div id="adm-pushover-user-key-status" style="font-size:12px;color:var(--text-dim);margin-top:4px"></div>
|
||||
</div>
|
||||
<button class="btn btn-primary" onclick="saveAdminPushover()">Save</button>
|
||||
</section>
|
||||
</div><!-- /spane-pushover -->
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════
|
||||
TAB: Inbox
|
||||
═══════════════════════════════════════════════════════════ -->
|
||||
@@ -1081,6 +1187,127 @@
|
||||
|
||||
</div><!-- /spane-mfa -->
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════
|
||||
ADMIN: Webhooks
|
||||
═══════════════════════════════════════════════════════════ -->
|
||||
<div id="spane-webhooks" style="display:none">
|
||||
|
||||
<!-- Inbound Endpoints -->
|
||||
<section style="margin-bottom:40px">
|
||||
<h2 class="settings-section-title">Inbound Webhook Triggers</h2>
|
||||
<p style="font-size:12px;color:var(--text-dim);margin-bottom:16px">
|
||||
Each endpoint has a secret token. POST <code>/webhook/{token}</code> with <code>{"message":"..."}</code>
|
||||
— or GET <code>/webhook/{token}?q=...</code> for iOS Shortcuts — to trigger the associated agent.
|
||||
</p>
|
||||
<button class="btn btn-primary btn-small" onclick="openWebhookModal(null)" style="margin-bottom:16px">+ Add Endpoint</button>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead><tr>
|
||||
<th>Name</th>
|
||||
<th>Agent</th>
|
||||
<th>Status</th>
|
||||
<th>Triggers</th>
|
||||
<th>Last triggered</th>
|
||||
<th style="text-align:right">Actions</th>
|
||||
</tr></thead>
|
||||
<tbody id="webhooks-tbody"><tr><td colspan="6" style="color:var(--text-dim)">Loading…</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Outbound Targets -->
|
||||
<section>
|
||||
<h2 class="settings-section-title">Outbound Webhook Targets</h2>
|
||||
<p style="font-size:12px;color:var(--text-dim);margin-bottom:16px">
|
||||
Named targets agents can POST to using the <code>webhook</code> tool.
|
||||
</p>
|
||||
<button class="btn btn-primary btn-small" onclick="openWebhookTargetModal(null)" style="margin-bottom:16px">+ Add Target</button>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead><tr>
|
||||
<th>Name</th>
|
||||
<th>URL</th>
|
||||
<th>Status</th>
|
||||
<th style="text-align:right">Actions</th>
|
||||
</tr></thead>
|
||||
<tbody id="webhook-targets-tbody"><tr><td colspan="4" style="color:var(--text-dim)">Loading…</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div><!-- /spane-webhooks -->
|
||||
|
||||
<!-- Webhook endpoint modal -->
|
||||
<div class="modal-overlay" id="webhook-modal" style="display:none">
|
||||
<div class="modal" style="max-width:500px;width:100%">
|
||||
<h3 id="webhook-modal-title" style="margin-bottom:20px">Add Webhook Endpoint</h3>
|
||||
<input type="hidden" id="webhook-modal-id">
|
||||
<div class="form-group">
|
||||
<label>Name</label>
|
||||
<input type="text" id="webhook-modal-name" class="form-input" placeholder="e.g. GitHub Push" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Description <span style="color:var(--text-dim)">(optional)</span></label>
|
||||
<input type="text" id="webhook-modal-desc" class="form-input" placeholder="What triggers this?">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Agent to trigger</label>
|
||||
<select id="webhook-modal-agent" class="form-input"></select>
|
||||
</div>
|
||||
<div class="form-group" style="display:flex;align-items:center;gap:10px">
|
||||
<input type="checkbox" id="webhook-modal-allow-get" checked style="width:auto">
|
||||
<label for="webhook-modal-allow-get" style="margin:0;cursor:pointer">Allow GET requests (for iOS Shortcuts)</label>
|
||||
</div>
|
||||
<div class="modal-buttons" style="margin-top:20px">
|
||||
<button type="button" class="btn btn-ghost" onclick="closeWebhookModal()">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveWebhook()">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Token reveal modal (shown once after create or rotate) -->
|
||||
<div class="modal-overlay" id="webhook-token-modal" style="display:none">
|
||||
<div class="modal" style="max-width:520px;width:100%">
|
||||
<h3 style="margin-bottom:8px">Webhook Token</h3>
|
||||
<p style="font-size:12px;color:var(--yellow);margin-bottom:16px">Copy this token now — it will not be shown again.</p>
|
||||
<div style="display:flex;gap:8px;align-items:center">
|
||||
<input type="text" id="webhook-token-value" class="form-input" readonly style="font-family:var(--mono);font-size:12px">
|
||||
<button class="btn btn-ghost btn-small" onclick="copyWebhookToken()">Copy</button>
|
||||
</div>
|
||||
<p style="font-size:12px;color:var(--text-dim);margin-top:14px">
|
||||
POST: <code id="webhook-token-url-post"></code><br>
|
||||
GET: <code id="webhook-token-url-get"></code>
|
||||
</p>
|
||||
<div class="modal-buttons" style="margin-top:20px">
|
||||
<button type="button" class="btn btn-primary" onclick="closeWebhookTokenModal()">Done</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Outbound target modal -->
|
||||
<div class="modal-overlay" id="webhook-target-modal" style="display:none">
|
||||
<div class="modal" style="max-width:480px;width:100%">
|
||||
<h3 id="webhook-target-modal-title" style="margin-bottom:20px">Add Webhook Target</h3>
|
||||
<input type="hidden" id="webhook-target-modal-id">
|
||||
<div class="form-group">
|
||||
<label>Name <span style="color:var(--text-dim)">(used by agents)</span></label>
|
||||
<input type="text" id="webhook-target-modal-name" class="form-input" placeholder="e.g. home-assistant" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>URL</label>
|
||||
<input type="text" id="webhook-target-modal-url" class="form-input" placeholder="https://...">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Secret header value <span style="color:var(--text-dim)">(optional — sent as Authorization: Bearer)</span></label>
|
||||
<input type="password" id="webhook-target-modal-secret" class="form-input" placeholder="Leave blank to omit">
|
||||
</div>
|
||||
<div class="modal-buttons" style="margin-top:20px">
|
||||
<button type="button" class="btn btn-ghost" onclick="closeWebhookTargetModal()">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveWebhookTarget()">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<!-- ══════════════════════════════════════════════════════════
|
||||
USER SETTINGS: API Keys
|
||||
@@ -1211,38 +1438,79 @@
|
||||
</section>
|
||||
</div><!-- /uspane-emailaccounts -->
|
||||
|
||||
<!-- CalDAV tab -->
|
||||
<!-- CalDAV / CardDAV tab -->
|
||||
<div id="uspane-caldav" style="display:none">
|
||||
<section>
|
||||
<h2 class="settings-section-title">My CalDAV</h2>
|
||||
|
||||
<!-- ── CalDAV (Calendar) ── -->
|
||||
<section style="max-width:560px;margin-bottom:36px;padding-bottom:32px;border-bottom:1px solid var(--border)">
|
||||
<h2 class="settings-section-title">My CalDAV <span style="font-weight:400;font-size:13px;color:var(--text-dim)">(Calendar)</span></h2>
|
||||
<p style="font-size:12px;color:var(--text-dim);margin-bottom:16px">
|
||||
Your personal CalDAV server for calendar access. Leave blank to use the system CalDAV server
|
||||
configured by the admin.
|
||||
Used by the <code>caldav</code> tool for reading and writing calendar events.
|
||||
Used by the <code>caldav</code> tool for reading and writing calendar events.
|
||||
</p>
|
||||
<div style="max-width:520px">
|
||||
<div class="form-group"><label>CalDAV Server URL</label>
|
||||
<input type="text" id="my-caldav-url" class="form-input" placeholder="https://mail.example.com"></div>
|
||||
<div class="form-group"><label>CalDAV Server URL</label>
|
||||
<input type="text" id="my-caldav-url" class="form-input" placeholder="https://mail.example.com"></div>
|
||||
<div class="form-group"><label>Username</label>
|
||||
<input type="text" id="my-caldav-username" class="form-input" placeholder="user@example.com"></div>
|
||||
<div class="form-group"><label>Password</label>
|
||||
<div style="display:flex;gap:8px;align-items:center">
|
||||
<input type="password" id="my-caldav-password" class="form-input" placeholder="Leave blank to keep existing">
|
||||
<button type="button" class="btn btn-ghost btn-small" onclick="togglePasswordVisibility('my-caldav-password', this)">Show</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group"><label>Calendar name <span style="color:var(--text-dim);font-size:11px">(optional — uses first found if blank)</span></label>
|
||||
<input type="text" id="my-caldav-calendar-name" class="form-input" placeholder="Personal"></div>
|
||||
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:4px">
|
||||
<button class="btn btn-ghost btn-small" onclick="testMyCaldavConfig()" id="caldav-test-btn">Test CalDAV</button>
|
||||
</div>
|
||||
<div id="caldav-test-result" style="margin-top:10px;font-size:13px"></div>
|
||||
</section>
|
||||
|
||||
<!-- ── CardDAV (Contacts) ── -->
|
||||
<section style="max-width:560px;margin-bottom:32px">
|
||||
<h2 class="settings-section-title">My CardDAV <span style="font-weight:400;font-size:13px;color:var(--text-dim)">(Contacts)</span></h2>
|
||||
<p style="font-size:12px;color:var(--text-dim);margin-bottom:16px">
|
||||
Used by the <code>contacts</code> tool for reading (and optionally writing) address book contacts.
|
||||
</p>
|
||||
<div class="form-group" style="display:flex;align-items:center;gap:10px;margin-bottom:16px">
|
||||
<input type="checkbox" id="my-carddav-same" style="width:auto" onchange="onCarddavSameChange()">
|
||||
<label for="my-carddav-same" style="margin:0;cursor:pointer;font-size:13px">
|
||||
Same server as CalDAV (use CalDAV credentials for contacts)
|
||||
</label>
|
||||
</div>
|
||||
<div id="carddav-custom-fields">
|
||||
<div class="form-group"><label>CardDAV Server URL</label>
|
||||
<input type="text" id="my-carddav-url" class="form-input" placeholder="https://contacts.example.com"></div>
|
||||
<div class="form-group"><label>Username</label>
|
||||
<input type="text" id="my-caldav-username" class="form-input" placeholder="user@example.com"></div>
|
||||
<input type="text" id="my-carddav-username" class="form-input" placeholder="user@example.com"></div>
|
||||
<div class="form-group"><label>Password</label>
|
||||
<div style="display:flex;gap:8px;align-items:center">
|
||||
<input type="password" id="my-caldav-password" class="form-input" placeholder="Leave blank to keep existing">
|
||||
<button type="button" class="btn btn-ghost btn-small" onclick="togglePasswordVisibility('my-caldav-password', this)">Show</button>
|
||||
<input type="password" id="my-carddav-password" class="form-input" placeholder="Leave blank to keep existing">
|
||||
<button type="button" class="btn btn-ghost btn-small" onclick="togglePasswordVisibility('my-carddav-password', this)">Show</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group"><label>Calendar name <span style="color:var(--text-dim);font-size:11px">(optional — uses first found if blank)</span></label>
|
||||
<input type="text" id="my-caldav-calendar-name" class="form-input" placeholder="Personal"></div>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
|
||||
<button class="btn btn-primary" onclick="saveMyCaldavConfig()">Save</button>
|
||||
<button class="btn btn-ghost btn-small" onclick="testMyCaldavConfig()" id="caldav-test-btn">Test connection</button>
|
||||
<button class="btn btn-danger btn-small" onclick="deleteMyCaldavConfig()">Clear / use system CalDAV</button>
|
||||
<div class="form-group" style="display:flex;align-items:center;gap:10px;margin-top:8px;padding-top:16px;border-top:1px solid var(--border)">
|
||||
<input type="checkbox" id="my-contacts-allow-write" style="width:auto">
|
||||
<label for="my-contacts-allow-write" style="margin:0;cursor:pointer;font-size:13px">
|
||||
Allow contact writes <span style="color:var(--text-dim)">(create, update, delete — requires confirmation)</span>
|
||||
</label>
|
||||
</div>
|
||||
<div id="caldav-test-result" style="margin-top:12px;font-size:13px"></div>
|
||||
<p style="margin-top:16px;font-size:12px;color:var(--text-dim)">
|
||||
If left blank, the system CalDAV server will be used (configured by the admin in Credentials).
|
||||
</p>
|
||||
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:6px">
|
||||
<button class="btn btn-ghost btn-small" onclick="testMyCarddavConfig()" id="carddav-test-btn">Test CardDAV</button>
|
||||
</div>
|
||||
<div id="carddav-test-result" style="margin-top:10px;font-size:13px"></div>
|
||||
</section>
|
||||
|
||||
<!-- ── Save / Clear ── -->
|
||||
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
|
||||
<button class="btn btn-primary" onclick="saveMyCaldavConfig()">Save all</button>
|
||||
<button class="btn btn-danger btn-small" onclick="deleteMyCaldavConfig()">Clear all</button>
|
||||
</div>
|
||||
<p style="margin-top:12px;font-size:12px;color:var(--text-dim)">
|
||||
Each user must configure their own CalDAV / CardDAV credentials. There is no shared fallback.
|
||||
</p>
|
||||
|
||||
</div><!-- /uspane-caldav -->
|
||||
|
||||
<div id="uspane-telegram" style="display:none">
|
||||
@@ -1335,6 +1603,83 @@
|
||||
</section>
|
||||
</div><!-- /uspane-brain -->
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════
|
||||
USER SETTINGS: Pushover
|
||||
═══════════════════════════════════════════════════════════ -->
|
||||
<div id="uspane-pushover" style="display:none">
|
||||
<section style="max-width:520px">
|
||||
<h2 class="settings-section-title">My Pushover Notifications</h2>
|
||||
<p style="font-size:12px;color:var(--text-dim);margin-bottom:16px">
|
||||
Set your personal Pushover <strong>User Key</strong> to receive push notifications on your devices.
|
||||
The App Token is shared and managed by the admin.
|
||||
Find your User Key at <a href="https://pushover.net" target="_blank" style="color:var(--accent)">pushover.net</a> → your user page.
|
||||
</p>
|
||||
<div id="my-pushover-status" style="font-size:12px;margin-bottom:12px"></div>
|
||||
<div class="form-group"><label>Your User Key</label>
|
||||
<div style="display:flex;gap:8px;align-items:center">
|
||||
<input type="password" id="my-pushover-user-key" class="form-input" placeholder="uXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX">
|
||||
<button type="button" class="btn btn-ghost btn-small" onclick="togglePasswordVisibility('my-pushover-user-key', this)">Show</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px">
|
||||
<button class="btn btn-primary" onclick="saveMyPushover()">Save</button>
|
||||
<button class="btn btn-danger btn-small" onclick="deleteMyPushover()">Remove</button>
|
||||
</div>
|
||||
</section>
|
||||
</div><!-- /uspane-pushover -->
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════
|
||||
USER SETTINGS: Webhooks
|
||||
═══════════════════════════════════════════════════════════ -->
|
||||
<div id="uspane-webhooks" style="display:none">
|
||||
|
||||
<!-- Inbound endpoints -->
|
||||
<section style="margin-bottom:40px">
|
||||
<h2 class="settings-section-title">Inbound Endpoints</h2>
|
||||
<p style="font-size:12px;color:var(--text-dim);margin-bottom:16px">
|
||||
Webhook endpoints that trigger your agents. Each has a secret token —
|
||||
POST <code>/webhook/{token}</code> with <code>{"message":"..."}</code>
|
||||
or GET <code>/webhook/{token}?q=...</code> for iOS Shortcuts.
|
||||
</p>
|
||||
<button class="btn btn-primary btn-small" onclick="openMyWebhookModal(null)" style="margin-bottom:16px">+ Add Endpoint</button>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead><tr>
|
||||
<th>Name</th>
|
||||
<th>Agent</th>
|
||||
<th>Status</th>
|
||||
<th>Triggers</th>
|
||||
<th>Last triggered</th>
|
||||
<th style="text-align:right">Actions</th>
|
||||
</tr></thead>
|
||||
<tbody id="my-webhooks-tbody"><tr><td colspan="6" style="color:var(--text-dim)">Loading…</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Outbound targets -->
|
||||
<section>
|
||||
<h2 class="settings-section-title">Outbound Targets</h2>
|
||||
<p style="font-size:12px;color:var(--text-dim);margin-bottom:16px">
|
||||
Named targets your agents can POST to using the <code>webhook</code> tool.
|
||||
Targets you define here are only visible to your own agents.
|
||||
</p>
|
||||
<button class="btn btn-primary btn-small" onclick="openMyWebhookTargetModal(null)" style="margin-bottom:16px">+ Add Target</button>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead><tr>
|
||||
<th>Name</th>
|
||||
<th>URL</th>
|
||||
<th>Status</th>
|
||||
<th style="text-align:right">Actions</th>
|
||||
</tr></thead>
|
||||
<tbody id="my-webhook-targets-tbody"><tr><td colspan="4" style="color:var(--text-dim)">Loading…</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div><!-- /uspane-webhooks -->
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════
|
||||
USER SETTINGS: Profile
|
||||
═══════════════════════════════════════════════════════════ -->
|
||||
@@ -1657,31 +2002,9 @@
|
||||
<div class="modal" style="max-width:480px;width:100%">
|
||||
<h3 style="margin-bottom:20px" id="cred-modal-title">Add Credential</h3>
|
||||
<div class="form-group" id="cred-modal-key-group">
|
||||
<label>Credential</label>
|
||||
<select id="cred-modal-key-select" class="form-input" onchange="onCredKeySelectChange()">
|
||||
<option value="">- choose a credential -</option>
|
||||
<optgroup label="CalDAV">
|
||||
<option value="mailcow_host">Mailcow Host</option>
|
||||
<option value="mailcow_username">Mailcow Username</option>
|
||||
<option value="mailcow_password">Mailcow Password</option>
|
||||
<option value="caldav_calendar_name">Calendar Name</option>
|
||||
</optgroup>
|
||||
<optgroup label="Email">
|
||||
<option value="mailcow_imap_host">IMAP Host</option>
|
||||
<option value="mailcow_smtp_host">SMTP Host</option>
|
||||
<option value="mailcow_smtp_port">SMTP Port</option>
|
||||
</optgroup>
|
||||
<optgroup label="Pushover">
|
||||
<option value="pushover_user_key">User Key</option>
|
||||
<option value="pushover_app_token">App Token</option>
|
||||
</optgroup>
|
||||
<option value="__custom__">Custom…</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" id="cred-modal-custom-group" style="display:none">
|
||||
<label>Custom credential name</label>
|
||||
<label>Key name</label>
|
||||
<input type="text" id="cred-modal-key-custom" class="form-input"
|
||||
placeholder="e.g. my_api_key" autocomplete="off">
|
||||
placeholder="e.g. my_api_key or service:token" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Value</label>
|
||||
@@ -1727,6 +2050,58 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- User webhook target modal -->
|
||||
<div class="modal-overlay" id="my-webhook-target-modal" style="display:none">
|
||||
<div class="modal" style="max-width:480px;width:100%">
|
||||
<h3 id="my-webhook-target-modal-title" style="margin-bottom:20px">Add Outbound Target</h3>
|
||||
<input type="hidden" id="my-webhook-target-modal-id">
|
||||
<div class="form-group">
|
||||
<label>Name <span style="color:var(--text-dim)">(used by agents)</span></label>
|
||||
<input type="text" id="my-webhook-target-modal-name" class="form-input" placeholder="e.g. home-assistant" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>URL</label>
|
||||
<input type="text" id="my-webhook-target-modal-url" class="form-input" placeholder="https://...">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Secret header value <span style="color:var(--text-dim)">(optional — sent as Authorization: Bearer)</span></label>
|
||||
<input type="password" id="my-webhook-target-modal-secret" class="form-input" placeholder="Leave blank to omit">
|
||||
</div>
|
||||
<div class="modal-buttons" style="margin-top:20px">
|
||||
<button type="button" class="btn btn-ghost" onclick="closeMyWebhookTargetModal()">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveMyWebhookTarget()">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- User webhook modal -->
|
||||
<div class="modal-overlay" id="my-webhook-modal" style="display:none">
|
||||
<div class="modal" style="max-width:500px;width:100%">
|
||||
<h3 id="my-webhook-modal-title" style="margin-bottom:20px">Add Webhook</h3>
|
||||
<input type="hidden" id="my-webhook-modal-id">
|
||||
<div class="form-group">
|
||||
<label>Name</label>
|
||||
<input type="text" id="my-webhook-modal-name" class="form-input" placeholder="e.g. iOS Shortcut" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Description <span style="color:var(--text-dim)">(optional)</span></label>
|
||||
<input type="text" id="my-webhook-modal-desc" class="form-input" placeholder="What triggers this?">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Agent to trigger</label>
|
||||
<select id="my-webhook-modal-agent" class="form-input"></select>
|
||||
</div>
|
||||
<div class="form-group" style="display:flex;align-items:center;gap:10px">
|
||||
<input type="checkbox" id="my-webhook-modal-allow-get" checked style="width:auto">
|
||||
<label for="my-webhook-modal-allow-get" style="margin:0;cursor:pointer">Allow GET requests (for iOS Shortcuts)</label>
|
||||
</div>
|
||||
<div class="modal-buttons" style="margin-top:20px">
|
||||
<button type="button" class="btn btn-ghost" onclick="closeMyWebhookModal()">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveMyWebhook()">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% block extra_scripts %}
|
||||
<script>window.IS_ADMIN = {{ current_user.is_admin | tojson }};</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user