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:
@@ -16,7 +16,7 @@
|
||||
<img src="{{ logo_url }}" alt="logo" class="sidebar-logo-img">
|
||||
<div class="sidebar-logo-text">
|
||||
<div class="sidebar-logo-name">{{ brand_name }}</div>
|
||||
<div class="sidebar-logo-app">oAI-Web <span class="sidebar-logo-version">v1.0</span></div>
|
||||
<div class="sidebar-logo-app">oAI-Web <span class="sidebar-logo-version">v1.2</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -40,6 +40,10 @@
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
|
||||
Files
|
||||
</a>
|
||||
<a class="nav-item" data-page="/monitors" href="/monitors">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M20.188 10.934a8.002 8.002 0 0 1 0 2.132M3.812 13.066a8.002 8.002 0 0 1 0-2.132M15.536 17.121a8 8 0 0 1-1.506.643M9.97 6.236A8 8 0 0 1 11.5 6M17.657 7.757a8 8 0 0 1 .879 1.506M6.343 16.243a8 8 0 0 1-.879-1.506M17.121 15.536a8 8 0 0 1-1.506.879M8.464 6.879A8 8 0 0 1 9.97 6.236"/></svg>
|
||||
Monitors
|
||||
</a>
|
||||
<a class="nav-item" data-page="/audit" href="/audit">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>
|
||||
Audit Log
|
||||
|
||||
@@ -128,6 +128,7 @@ function renderChats(data) {
|
||||
data-id="${c.id}" onclick="localStorage.setItem('current_session_id',this.dataset.id)">Open</a>
|
||||
<button class="btn btn-ghost btn-small"
|
||||
data-id="${c.id}" onclick="openRenameModal(this.dataset.id)">Rename</button>
|
||||
<a class="btn btn-ghost btn-small" href="/api/conversations/${c.id}/export" download>Export</a>
|
||||
<button class="btn btn-ghost btn-small" style="color:var(--danger,#dc3c3c)"
|
||||
data-id="${c.id}" onclick="deleteSingle(this.dataset.id)">Delete</button>
|
||||
</div>
|
||||
|
||||
@@ -42,14 +42,18 @@
|
||||
<li><a href="#settings-general">General</a></li>
|
||||
<li><a href="#settings-whitelists">Whitelists</a></li>
|
||||
<li><a href="#settings-credentials">Credentials</a></li>
|
||||
<li><a href="#settings-dav">DAV</a></li>
|
||||
<li><a href="#settings-pushover">Pushover</a></li>
|
||||
{% endif %}
|
||||
<li><a href="#settings-inbox">Inbox</a></li>
|
||||
<li><a href="#settings-emailaccounts">Email Accounts</a></li>
|
||||
<li><a href="#settings-telegram">Telegram</a></li>
|
||||
<li><a href="#settings-profile">Profile</a></li>
|
||||
{% if not (current_user and current_user.is_admin) %}
|
||||
<li><a href="#settings-caldav">CalDAV</a></li>
|
||||
<li><a href="#settings-caldav">CalDAV / CardDAV</a></li>
|
||||
<li><a href="#settings-pushover">Pushover</a></li>
|
||||
{% endif %}
|
||||
<li><a href="#settings-webhooks">Webhooks</a></li>
|
||||
<li><a href="#settings-profile">Profile</a></li>
|
||||
<li><a href="#settings-personality">Personality</a></li>
|
||||
<li><a href="#settings-brain">2nd Brain</a></li>
|
||||
<li><a href="#settings-mcp">MCP Servers</a></li>
|
||||
@@ -372,8 +376,28 @@ mcp = FastMCP(
|
||||
|
||||
<h2 id="settings-credentials">Credentials <small style="font-weight:400;font-size:12px;color:var(--text-dim)">(Admin)</small></h2>
|
||||
<p>
|
||||
All secrets (API keys, passwords, app settings) are stored in an AES-256-GCM encrypted PostgreSQL table. Keys use a <code>namespace:key</code> convention. See the <a href="#credentials">Credential Key Reference</a> for a full list.
|
||||
A generic AES-256-GCM encrypted key-value store for API keys and other secrets. Keys use a <code>namespace:key</code> convention. Service-specific credentials (CalDAV, CardDAV, Pushover) are managed in their own dedicated tabs — they do not appear here. See the <a href="#credentials">Credential Key Reference</a> for a full list of system keys.
|
||||
</p>
|
||||
|
||||
<h2 id="settings-dav">DAV (CalDAV & CardDAV) <small style="font-weight:400;font-size:12px;color:var(--text-dim)">(Admin)</small></h2>
|
||||
<p>
|
||||
Configure CalDAV and CardDAV for the admin user. There is no system-wide fallback — every user configures their own credentials independently via this tab (admin) or the <strong>CalDAV / CardDAV</strong> tab (regular users).
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>CalDAV</strong>: server URL, username, password, and calendar name. Bare hostnames (e.g. <code>mail.example.com</code>) are accepted — <code>https://</code> is prepended automatically.</li>
|
||||
<li><strong>CardDAV</strong>: tick <em>Same server as CalDAV</em> to reuse the same credentials, or enter a separate URL, username, and password. The SOGo URL pattern (<code>/SOGo/dav/{user}/Contacts/personal/</code>) is built automatically.</li>
|
||||
<li><strong>Allow contact writes</strong>: when enabled, agents can create, update, and delete contacts (not just read them). This is per-user — enabling it for your account does not affect other users.</li>
|
||||
<li><strong>Test buttons</strong>: verify CalDAV and CardDAV connectivity without saving.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="settings-pushover">Pushover <small style="font-weight:400;font-size:12px;color:var(--text-dim)">(Admin)</small></h2>
|
||||
<p>
|
||||
Pushover sends push notifications to iOS and Android devices.
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>App Token</strong>: registered once at <a href="https://pushover.net" target="_blank">pushover.net</a> for this oAI-Web installation. Shared by all users — they cannot see or change it.</li>
|
||||
<li><strong>User Key</strong>: the admin's personal Pushover user key, shown on your pushover.net dashboard. Each user sets their own User Key in <strong>Settings → Pushover</strong>.</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
<h2 id="settings-inbox">Inbox</h2>
|
||||
@@ -413,6 +437,36 @@ mcp = FastMCP(
|
||||
<li><strong>Trigger Rules</strong>: same keyword-matching logic as email inbox</li>
|
||||
</ul>
|
||||
|
||||
{% if not (current_user and current_user.is_admin) %}
|
||||
<h2 id="settings-caldav">CalDAV / CardDAV</h2>
|
||||
<p>
|
||||
Configure your personal CalDAV and CardDAV connection. There is no system-wide fallback — if you don't configure it, the tools are unavailable to you.
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>CalDAV</strong>: server URL, username, password, and calendar name. Bare hostnames are accepted — <code>https://</code> is added automatically.</li>
|
||||
<li><strong>CardDAV</strong>: tick <em>Same server as CalDAV</em> to reuse credentials, or enter separate details.</li>
|
||||
<li><strong>Allow contact writes</strong>: when enabled, agents can create, update, and delete contacts.</li>
|
||||
<li><strong>Test buttons</strong>: verify connectivity before saving.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="settings-pushover">Pushover</h2>
|
||||
<p>
|
||||
Set your personal <strong>User Key</strong> to receive push notifications on your Pushover-connected devices. Your User Key is shown on your <a href="https://pushover.net" target="_blank">pushover.net</a> dashboard. The App Token (the shared application credential) is managed by the admin — you only need your own User Key.
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<h2 id="settings-webhooks">Webhooks</h2>
|
||||
<p>
|
||||
Inbound webhooks let external services trigger agents via HTTP — useful for iOS Shortcuts, GitHub actions, Home Assistant automations, or any tool that can send an HTTP request.
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>Create a webhook</strong>: assign a name, description, and target agent. The secret token is shown <strong>once</strong> at creation — copy it immediately. Use <em>Rotate Token</em> to generate a new one if it is ever compromised.</li>
|
||||
<li><strong>Trigger via POST</strong>: <code>POST /webhook/{token}</code> with body <code>{"message": "..."}</code></li>
|
||||
<li><strong>Trigger via GET</strong>: <code>GET /webhook/{token}?q=your+message</code> — useful for iOS Shortcuts URL actions</li>
|
||||
<li><strong>Enable/disable</strong>: toggle a webhook on/off without deleting it</li>
|
||||
</ul>
|
||||
<p>The <strong>Outbound Targets</strong> section (same tab) manages named URLs that agents can send JSON payloads to via the <code>webhook</code> tool.</p>
|
||||
|
||||
<h2 id="settings-profile">Profile</h2>
|
||||
<p>Available to all users. Contains:</p>
|
||||
<ul>
|
||||
@@ -422,16 +476,8 @@ mcp = FastMCP(
|
||||
<li><strong>Two-Factor Authentication (TOTP)</strong>: enable/disable TOTP-based MFA. On setup, a QR code is shown to scan with any authenticator app (e.g. Aegis, Google Authenticator). Once enabled, every login requires a 6-digit code.</li>
|
||||
<li><strong>Data Folder</strong>: shows the path of your auto-provisioned personal folder (set by admin via <code>system:users_base_folder</code>). This folder is where the Files page browses and where agent memory files are stored.</li>
|
||||
<li><strong>Telegram Bot Token</strong>: per-user Telegram bot token (optional). Overrides the global token for your sessions.</li>
|
||||
<li><strong>CalDAV</strong>: per-user CalDAV server, credentials, and calendar name. Overrides the global CalDAV config.</li>
|
||||
</ul>
|
||||
|
||||
{% if not (current_user and current_user.is_admin) %}
|
||||
<h2 id="settings-caldav">CalDAV</h2>
|
||||
<p>
|
||||
Configure your personal CalDAV connection under <strong>Settings → Profile → CalDAV</strong>. This overrides the global CalDAV config set by the admin. Fields: server URL, username, password, calendar name. Leave blank to inherit the global config.
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<h2 id="settings-personality">Personality</h2>
|
||||
<p>
|
||||
Edit <strong>SOUL.md</strong> (agent identity, values, communication style) and <strong>USER.md</strong> (owner context: name, location, preferences) directly in the browser. Changes take effect immediately — no restart required.
|
||||
@@ -553,9 +599,7 @@ mcp = FastMCP(
|
||||
<tr><td><code>system:security_max_subject_chars</code></td><td>Max chars of email subject (default: 200)</td></tr>
|
||||
<tr><td><code>telegram:bot_token</code></td><td>Global Telegram bot API token</td></tr>
|
||||
<tr><td><code>telegram:default_agent_id</code></td><td>UUID of agent for unmatched Telegram messages</td></tr>
|
||||
<tr><td><code>pushover_user_key</code></td><td>Pushover user key</td></tr>
|
||||
<tr><td><code>pushover_app_token</code></td><td>Pushover application token</td></tr>
|
||||
<tr><td><code>caldav_calendar_name</code></td><td>Optional CalDAV calendar name override (global)</td></tr>
|
||||
<tr><td><code>pushover_app_token</code></td><td>Pushover App Token — managed via <strong>Settings → Pushover</strong>, not this tab</td></tr>
|
||||
<tr><td><code>brain:mcp_key</code></td><td>2nd Brain MCP authentication key</td></tr>
|
||||
<tr><td><code>system:api_key_hash</code></td><td>SHA-256 hash of the external API key (raw key never stored)</td></tr>
|
||||
<tr><td><code>system:api_key_created_at</code></td><td>Timestamp of last API key generation</td></tr>
|
||||
@@ -632,6 +676,10 @@ mcp = FastMCP(
|
||||
<tr><td><span class="http-del">DELETE</span></td><td><code>/api/settings/branding/logo</code></td><td>Reset logo to default</td></tr>
|
||||
<tr><td><span class="http-get">GET</span></td><td><code>/api/settings/audit-retention</code></td><td>Current audit retention setting</td></tr>
|
||||
<tr><td><span class="http-post">POST</span></td><td><code>/api/settings/audit-retention</code></td><td>Update <code>{days}</code></td></tr>
|
||||
<tr><td><span class="http-get">GET</span></td><td><code>/api/settings/caldav</code></td><td>Admin CalDAV & CardDAV config (same as <code>/api/my/caldav/config</code>)</td></tr>
|
||||
<tr><td><span class="http-post">POST</span></td><td><code>/api/settings/caldav</code></td><td>Save admin CalDAV & CardDAV config</td></tr>
|
||||
<tr><td><span class="http-get">GET</span></td><td><code>/api/settings/pushover</code></td><td>Current Pushover App Token and admin User Key</td></tr>
|
||||
<tr><td><span class="http-post">POST</span></td><td><code>/api/settings/pushover</code></td><td>Save App Token and admin User Key</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -768,14 +816,62 @@ mcp = FastMCP(
|
||||
<tr><td><span class="http-post">POST</span></td><td><code>/api/my/mfa/setup/begin</code></td><td>Start MFA setup — returns QR code PNG (base64) and provisioning URI</td></tr>
|
||||
<tr><td><span class="http-post">POST</span></td><td><code>/api/my/mfa/setup/confirm</code></td><td>Confirm setup with a valid TOTP code <code>{code}</code></td></tr>
|
||||
<tr><td><span class="http-del">DELETE</span></td><td><code>/api/my/mfa/disable</code></td><td>Disable MFA for the current user</td></tr>
|
||||
<tr><td><span class="http-get">GET</span></td><td><code>/api/my/caldav/config</code></td><td>Get per-user CalDAV config</td></tr>
|
||||
<tr><td><span class="http-post">POST</span></td><td><code>/api/my/caldav/config</code></td><td>Save per-user CalDAV credentials</td></tr>
|
||||
<tr><td><span class="http-del">DELETE</span></td><td><code>/api/my/caldav/config</code></td><td>Remove per-user CalDAV config (fall back to global)</td></tr>
|
||||
<tr><td><span class="http-get">GET</span></td><td><code>/api/my/caldav/config</code></td><td>Get per-user CalDAV & CardDAV config</td></tr>
|
||||
<tr><td><span class="http-post">POST</span></td><td><code>/api/my/caldav/config</code></td><td>Save per-user CalDAV & CardDAV credentials</td></tr>
|
||||
<tr><td><span class="http-del">DELETE</span></td><td><code>/api/my/caldav/config</code></td><td>Remove per-user CalDAV & CardDAV config</td></tr>
|
||||
<tr><td><span class="http-post">POST</span></td><td><code>/api/my/caldav/test</code></td><td>Test CalDAV connectivity with current saved config</td></tr>
|
||||
<tr><td><span class="http-post">POST</span></td><td><code>/api/my/caldav/test-carddav</code></td><td>Test CardDAV connectivity with current saved config</td></tr>
|
||||
<tr><td><span class="http-get">GET</span></td><td><code>/api/my/pushover</code></td><td>Get current user's Pushover User Key (masked)</td></tr>
|
||||
<tr><td><span class="http-post">POST</span></td><td><code>/api/my/pushover</code></td><td>Save personal User Key <code>{user_key}</code></td></tr>
|
||||
<tr><td><span class="http-del">DELETE</span></td><td><code>/api/my/pushover</code></td><td>Remove personal User Key</td></tr>
|
||||
<tr><td><span class="http-get">GET</span></td><td><code>/api/my/telegram/whitelisted-chats</code></td><td>List Telegram chat IDs whitelisted for the current user</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h3>Webhooks</h3>
|
||||
<div class="table-wrap">
|
||||
<table class="help-api-table">
|
||||
<thead><tr><th>Method</th><th>Path</th><th>Description</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><span class="http-get">GET</span></td><td><code>/api/webhooks</code></td><td>List inbound webhook endpoints (admin)</td></tr>
|
||||
<tr><td><span class="http-post">POST</span></td><td><code>/api/webhooks</code></td><td>Create endpoint — returns token once (admin)</td></tr>
|
||||
<tr><td><span class="http-put">PUT</span></td><td><code>/api/webhooks/{id}</code></td><td>Update name/description/agent/enabled (admin)</td></tr>
|
||||
<tr><td><span class="http-del">DELETE</span></td><td><code>/api/webhooks/{id}</code></td><td>Delete endpoint (admin)</td></tr>
|
||||
<tr><td><span class="http-post">POST</span></td><td><code>/api/webhooks/{id}/rotate</code></td><td>Regenerate token — returns new token once (admin)</td></tr>
|
||||
<tr><td><span class="http-get">GET</span></td><td><code>/api/my/webhooks</code></td><td>List current user's webhook endpoints</td></tr>
|
||||
<tr><td><span class="http-post">POST</span></td><td><code>/api/my/webhooks</code></td><td>Create personal webhook endpoint</td></tr>
|
||||
<tr><td><span class="http-put">PUT</span></td><td><code>/api/my/webhooks/{id}</code></td><td>Update personal webhook endpoint</td></tr>
|
||||
<tr><td><span class="http-del">DELETE</span></td><td><code>/api/my/webhooks/{id}</code></td><td>Delete personal webhook endpoint</td></tr>
|
||||
<tr><td><span class="http-get">GET</span></td><td><code>/webhook/{token}</code></td><td>Trigger via GET — param: <code>?q=message</code> (no auth)</td></tr>
|
||||
<tr><td><span class="http-post">POST</span></td><td><code>/webhook/{token}</code></td><td>Trigger via POST — body: <code>{"message": "...", "async": true}</code> (no auth)</td></tr>
|
||||
<tr><td><span class="http-get">GET</span></td><td><code>/api/webhook-targets</code></td><td>List outbound webhook targets (admin)</td></tr>
|
||||
<tr><td><span class="http-post">POST</span></td><td><code>/api/webhook-targets</code></td><td>Create outbound target (admin)</td></tr>
|
||||
<tr><td><span class="http-put">PUT</span></td><td><code>/api/webhook-targets/{id}</code></td><td>Update outbound target (admin)</td></tr>
|
||||
<tr><td><span class="http-del">DELETE</span></td><td><code>/api/webhook-targets/{id}</code></td><td>Delete outbound target (admin)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h3>Monitors</h3>
|
||||
<div class="table-wrap">
|
||||
<table class="help-api-table">
|
||||
<thead><tr><th>Method</th><th>Path</th><th>Description</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><span class="http-get">GET</span></td><td><code>/api/watched-pages</code></td><td>List page-change monitors</td></tr>
|
||||
<tr><td><span class="http-post">POST</span></td><td><code>/api/watched-pages</code></td><td>Create page monitor</td></tr>
|
||||
<tr><td><span class="http-put">PUT</span></td><td><code>/api/watched-pages/{id}</code></td><td>Update page monitor</td></tr>
|
||||
<tr><td><span class="http-del">DELETE</span></td><td><code>/api/watched-pages/{id}</code></td><td>Delete page monitor</td></tr>
|
||||
<tr><td><span class="http-post">POST</span></td><td><code>/api/watched-pages/{id}/check-now</code></td><td>Force an immediate check</td></tr>
|
||||
<tr><td><span class="http-get">GET</span></td><td><code>/api/rss-feeds</code></td><td>List RSS feed monitors</td></tr>
|
||||
<tr><td><span class="http-post">POST</span></td><td><code>/api/rss-feeds</code></td><td>Create RSS feed monitor</td></tr>
|
||||
<tr><td><span class="http-put">PUT</span></td><td><code>/api/rss-feeds/{id}</code></td><td>Update RSS feed monitor</td></tr>
|
||||
<tr><td><span class="http-del">DELETE</span></td><td><code>/api/rss-feeds/{id}</code></td><td>Delete RSS feed monitor</td></tr>
|
||||
<tr><td><span class="http-post">POST</span></td><td><code>/api/rss-feeds/{id}/fetch-now</code></td><td>Force an immediate fetch</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h3>User Management <small style="color:var(--text-dim)">(Admin)</small></h3>
|
||||
<div class="table-wrap">
|
||||
<table class="help-api-table">
|
||||
|
||||
138
server/web/templates/monitors.html
Normal file
138
server/web/templates/monitors.html
Normal file
@@ -0,0 +1,138 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ agent_name }} — Monitors{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page" id="monitors-container">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:24px">
|
||||
<h1>Monitors</h1>
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<div style="display:flex;gap:0;border-bottom:1px solid var(--border);margin-bottom:24px">
|
||||
<button class="tab-btn active" id="mtab-pages" type="button" onclick="switchMonitorTab('pages')">Page Watchers</button>
|
||||
<button class="tab-btn" id="mtab-rss" type="button" onclick="switchMonitorTab('rss')">RSS Feeds</button>
|
||||
</div>
|
||||
|
||||
<!-- ── Page Watchers tab ── -->
|
||||
<div id="mpane-pages">
|
||||
<div style="display:flex;justify-content:flex-end;margin-bottom:16px">
|
||||
<button class="btn btn-primary" onclick="openPageModal(null)">+ Add Page Watcher</button>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table id="pages-table">
|
||||
<thead><tr>
|
||||
<th>Name</th>
|
||||
<th>URL</th>
|
||||
<th>Schedule</th>
|
||||
<th>Status</th>
|
||||
<th>Last checked</th>
|
||||
<th>Last changed</th>
|
||||
<th style="text-align:right">Actions</th>
|
||||
</tr></thead>
|
||||
<tbody id="pages-tbody"><tr><td colspan="7" style="color:var(--text-dim)">Loading…</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── RSS Feeds tab ── -->
|
||||
<div id="mpane-rss" style="display:none">
|
||||
<div style="display:flex;justify-content:flex-end;margin-bottom:16px">
|
||||
<button class="btn btn-primary" onclick="openFeedModal(null)">+ Add RSS Feed</button>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table id="feeds-table">
|
||||
<thead><tr>
|
||||
<th>Name</th>
|
||||
<th>URL</th>
|
||||
<th>Schedule</th>
|
||||
<th>Status</th>
|
||||
<th>Last fetched</th>
|
||||
<th style="text-align:right">Actions</th>
|
||||
</tr></thead>
|
||||
<tbody id="feeds-tbody"><tr><td colspan="6" style="color:var(--text-dim)">Loading…</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Page watcher modal -->
|
||||
<div class="modal-overlay" id="page-modal" style="display:none">
|
||||
<div class="modal" style="max-width:540px;width:100%">
|
||||
<h3 id="page-modal-title" style="margin-bottom:20px">Add Page Watcher</h3>
|
||||
<input type="hidden" id="page-modal-id">
|
||||
<div class="form-group">
|
||||
<label>Name</label>
|
||||
<input type="text" id="page-modal-name" class="form-input" placeholder="e.g. Company homepage" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>URL</label>
|
||||
<input type="text" id="page-modal-url" class="form-input" placeholder="https://example.com/page">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>CSS selector <span style="color:var(--text-dim)">(optional — extract specific element)</span></label>
|
||||
<input type="text" id="page-modal-selector" class="form-input" placeholder="e.g. #main-content or .price">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Check schedule (cron)</label>
|
||||
<input type="text" id="page-modal-schedule" class="form-input" value="0 * * * *" placeholder="0 * * * *">
|
||||
<div style="font-size:11px;color:var(--text-dim);margin-top:4px">Default: every hour. Use <a href="https://crontab.guru" target="_blank" style="color:var(--accent)">crontab.guru</a> to build expressions.</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>On change: notify via</label>
|
||||
<select id="page-modal-mode" class="form-input">
|
||||
<option value="agent">Agent</option>
|
||||
<option value="pushover">Pushover</option>
|
||||
<option value="both">Both</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" id="page-modal-agent-group">
|
||||
<label>Agent to trigger</label>
|
||||
<select id="page-modal-agent" class="form-input"></select>
|
||||
</div>
|
||||
<div class="modal-buttons" style="margin-top:20px">
|
||||
<button type="button" class="btn btn-ghost" onclick="closePageModal()">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" onclick="savePage()">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RSS feed modal -->
|
||||
<div class="modal-overlay" id="feed-modal" style="display:none">
|
||||
<div class="modal" style="max-width:540px;width:100%">
|
||||
<h3 id="feed-modal-title" style="margin-bottom:20px">Add RSS Feed</h3>
|
||||
<input type="hidden" id="feed-modal-id">
|
||||
<div class="form-group">
|
||||
<label>Name</label>
|
||||
<input type="text" id="feed-modal-name" class="form-input" placeholder="e.g. Hacker News" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Feed URL (RSS or Atom)</label>
|
||||
<input type="text" id="feed-modal-url" class="form-input" placeholder="https://example.com/feed.xml">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Fetch schedule (cron)</label>
|
||||
<input type="text" id="feed-modal-schedule" class="form-input" value="0 */4 * * *" placeholder="0 */4 * * *">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Max new items per run</label>
|
||||
<input type="number" id="feed-modal-max-items" class="form-input" value="5" min="1" max="50">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>On new items: notify via</label>
|
||||
<select id="feed-modal-mode" class="form-input">
|
||||
<option value="agent">Agent</option>
|
||||
<option value="pushover">Pushover</option>
|
||||
<option value="both">Both</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" id="feed-modal-agent-group">
|
||||
<label>Agent to trigger</label>
|
||||
<select id="feed-modal-agent" class="form-input"></select>
|
||||
</div>
|
||||
<div class="modal-buttons" style="margin-top:20px">
|
||||
<button type="button" class="btn btn-ghost" onclick="closeFeedModal()">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveFeed()">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -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