feat: full channels.json UI + http_allowlist settings

Notifications page:
- NC Talk section expanded: url, bot_secret, notification_room,
  nc_username, nc_app_password — all fields from channels.json now editable
- Per-channel sections use <details>/<summary> collapsibles; auto-open
  when values are present
- Secrets use type=password with "leave blank to keep" semantics
- Google Chat outbound webhook in its own collapsible section

Account settings:
- HTTP POST Allowlist section added (same textarea pattern as email allowlist)
- POST /settings/http-allowlist route saves home/{user}/http_allowlist.json
- Example placeholder shows ha.dgrzone.com and n8n patterns

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-05-09 13:57:18 -04:00
parent 7b443b40a4
commit 348ca120c1
3 changed files with 229 additions and 33 deletions

View File

@@ -46,7 +46,7 @@
border-radius: 12px;
padding: 2.5rem 2rem;
width: 100%;
max-width: 480px;
max-width: 520px;
}
.page-nav {
@@ -109,6 +109,7 @@
transition: border-color 0.15s;
}
input:focus, select:focus { border-color: #7c3aed; }
input[type="password"] { font-family: monospace; letter-spacing: 0.05em; }
.field { margin-bottom: 1rem; }
@@ -157,7 +158,49 @@
}
.test-result.ok { background: rgba(74, 222, 128, 0.1); color: #4ade80; border: 1px solid rgba(74, 222, 128, 0.25); }
.test-result.err { background: rgba(248, 113, 113, 0.1); color: #f87171; border: 1px solid rgba(248, 113, 113, 0.25); }
.hint { font-size: 0.78rem; color: var(--pg-dim); margin-top: 0.35rem; line-height: 1.5; }
/* Channel config blocks */
details.channel-block {
border: 1px solid var(--pg-border);
border-radius: 8px;
margin-bottom: 0.75rem;
overflow: hidden;
}
details.channel-block summary {
padding: 0.75rem 1rem;
font-size: 0.85rem;
font-weight: 600;
color: var(--pg-muted);
cursor: pointer;
list-style: none;
display: flex;
align-items: center;
gap: 0.5rem;
user-select: none;
background: var(--pg-bg);
}
details.channel-block summary::-webkit-details-marker { display: none; }
details.channel-block summary::before {
content: '▶';
font-size: 0.65rem;
color: var(--pg-dimmer);
transition: transform 0.15s;
flex-shrink: 0;
}
details.channel-block[open] summary::before { transform: rotate(90deg); }
details.channel-block[open] summary { border-bottom: 1px solid var(--pg-border); }
.channel-block-body {
padding: 1rem 1rem 0.25rem;
}
.channel-hint {
font-size: 0.75rem;
color: var(--pg-dimmer);
margin-top: -0.6rem;
margin-bottom: 1rem;
line-height: 1.5;
}
</style>
</head>
<body>
@@ -179,12 +222,13 @@
<!-- SUCCESS -->
<!-- ERROR -->
<!-- Channel config -->
<div class="section">
<h2>Channel</h2>
<form method="POST" action="/settings/notifications">
<form method="POST" action="/settings/notifications">
<!-- Channel selector -->
<div class="section">
<h2>Channel</h2>
<div class="field">
<label for="notification_channel">Notification channel</label>
<label for="notification_channel">Default outbound channel</label>
<select id="notification_channel" name="notification_channel"
data-value="{{ notify_channel }}">
<option value="">None (disabled)</option>
@@ -193,36 +237,118 @@
<option value="nextcloud">Nextcloud Talk</option>
<option value="google_chat">Google Chat</option>
</select>
<p class="hint">Used for reminder alerts, distillation summaries, and cron job notifications.</p>
</div>
<div class="field">
<label for="notification_email">Email override
<label for="notification_email">Email address override
<span style="color:var(--pg-dim); font-weight:400;">(optional)</span>
</label>
<input type="email" id="notification_email" name="notification_email"
value="{{ notify_email_override }}"
placeholder="Leave blank to use login email"
placeholder="Leave blank to use your login email"
autocomplete="off">
</div>
<div class="field">
<label for="nc_notification_room">Nextcloud Talk room token</label>
<input type="text" id="nc_notification_room" name="nc_notification_room"
value="{{ nc_notify_room }}"
placeholder="Token from the Talk room URL"
autocomplete="off" spellcheck="false">
</div>
<div class="field">
<label for="gc_outbound_webhook">Google Chat webhook URL</label>
<input type="url" id="gc_outbound_webhook" name="gc_outbound_webhook"
value="{{ gc_webhook }}"
placeholder="https://chat.googleapis.com/v1/spaces/…"
autocomplete="off" spellcheck="false">
</div>
<button type="submit">Save notification settings</button>
</form>
</div>
</div>
<!-- Nextcloud Talk -->
<div class="section">
<h2>Nextcloud Talk</h2>
<p class="hint" style="margin-bottom:1rem;">
Configure to send and receive messages via your Nextcloud Talk bot.
<strong>Sending</strong> requires the bot URL, secret, and notification room.
<strong>Reading history</strong> (<code>nc_talk_history</code> tool) additionally
requires a Nextcloud username and app password.
</p>
<details class="channel-block" {{ nc_url and 'open' or '' }}>
<summary>Bot credentials (sending)</summary>
<div class="channel-block-body">
<p class="channel-hint">
Set these up in your Nextcloud Talk room → Bot settings.
See the <a href="/help" style="color:#a78bfa;">setup guide</a> for step-by-step instructions.
</p>
<div class="field">
<label for="nc_url">Nextcloud URL</label>
<input type="url" id="nc_url" name="nc_url"
value="{{ nc_url }}"
placeholder="https://cloud.example.com"
autocomplete="off" spellcheck="false">
</div>
<div class="field">
<label for="nc_bot_secret">Bot secret</label>
<input type="password" id="nc_bot_secret" name="nc_bot_secret"
value="{{ nc_bot_secret }}"
placeholder="Leave blank to keep existing value"
autocomplete="new-password" spellcheck="false">
<p class="hint">Generated when you registered the bot in Nextcloud Talk.</p>
</div>
<div class="field">
<label for="nc_notification_room">Notification room token</label>
<input type="text" id="nc_notification_room" name="nc_notification_room"
value="{{ nc_notify_room }}"
placeholder="Token from the Talk room URL"
autocomplete="off" spellcheck="false">
<p class="hint">The token at the end of the Talk room URL — e.g. <code>abc123def</code>.</p>
</div>
</div>
</details>
<details class="channel-block" {{ nc_username and 'open' or '' }}>
<summary>API credentials (reading history)</summary>
<div class="channel-block-body">
<p class="channel-hint">
Required for the <code>nc_talk_history</code> orchestrator tool.
Generate an app password in Nextcloud → Settings → Security → App passwords.
</p>
<div class="field">
<label for="nc_username">Nextcloud username</label>
<input type="text" id="nc_username" name="nc_username"
value="{{ nc_username }}"
placeholder="Your Nextcloud login username"
autocomplete="off" spellcheck="false">
</div>
<div class="field">
<label for="nc_app_password">App password</label>
<input type="password" id="nc_app_password" name="nc_app_password"
value="{{ nc_app_password }}"
placeholder="Leave blank to keep existing value"
autocomplete="new-password" spellcheck="false">
</div>
</div>
</details>
</div>
<!-- Google Chat -->
<div class="section">
<h2>Google Chat</h2>
<p class="hint" style="margin-bottom:1rem;">
Outbound webhook for proactive messages to a Google Chat space.
Incoming messages are handled separately via the Google Chat Add-on.
</p>
<details class="channel-block" {{ gc_webhook and 'open' or '' }}>
<summary>Outbound webhook</summary>
<div class="channel-block-body">
<p class="channel-hint">
Create a webhook in your Google Chat space → Manage webhooks.
Paste the full URL here.
</p>
<div class="field">
<label for="gc_outbound_webhook">Webhook URL</label>
<input type="url" id="gc_outbound_webhook" name="gc_outbound_webhook"
value="{{ gc_webhook }}"
placeholder="https://chat.googleapis.com/v1/spaces/…"
autocomplete="off" spellcheck="false">
</div>
</div>
</details>
</div>
<button type="submit">Save notification settings</button>
</form>
<!-- Test -->
<div class="section">
<div class="section" style="margin-top:2rem;">
<h2>Test</h2>
<p class="hint" style="margin-bottom:0.85rem">
Fire a notification via your configured channel or run the reminder check

View File

@@ -345,6 +345,25 @@
</form>
</div>
<!-- HTTP POST Allowlist -->
<div class="section">
<h2>HTTP POST Allowlist</h2>
<p style="font-size:0.8rem; color:var(--pg-muted); margin-bottom:0.85rem; line-height:1.55;">
One URL prefix per line. The <code style="font-size:0.82rem; background:var(--pg-bg); padding:0.1rem 0.35rem; border-radius:4px;">http_post</code>
tool will only POST to URLs that start with a listed prefix.
Leave blank to block all outbound POST requests.
</p>
<form method="POST" action="/settings/http-allowlist">
<div class="field">
<label for="http_allowlist_ta">Allowed URL prefixes</label>
<textarea id="http_allowlist_ta" name="prefixes" rows="5"
placeholder="https://ha.dgrzone.com/api/webhook/&#10;https://n8n.dgrzone.com/webhook/"
spellcheck="false">{{ http_allowlist }}</textarea>
</div>
<button type="submit">Save allowlist</button>
</form>
</div>
<!-- Notifications -->
<div class="section">
<h2>Notifications</h2>