summaryrefslogtreecommitdiff
path: root/web/templates/settings.html
diff options
context:
space:
mode:
Diffstat (limited to 'web/templates/settings.html')
-rw-r--r--web/templates/settings.html372
1 files changed, 114 insertions, 258 deletions
diff --git a/web/templates/settings.html b/web/templates/settings.html
index 4df4cb0..ca1d268 100644
--- a/web/templates/settings.html
+++ b/web/templates/settings.html
@@ -5,228 +5,84 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="{{.CSRFToken}}">
<title>Settings - Task Dashboard</title>
- <script src="https://unpkg.com/htmx.org@1.9.10"></script>
+ <link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
+ <link rel="stylesheet" href="/static/css/output.css">
+ <script src="/static/js/htmx.min.js"></script>
<style>
- :root {
- --bg-primary: #1a1a2e;
- --bg-secondary: #16213e;
- --bg-tertiary: #0f3460;
- --text-primary: #eee;
- --text-secondary: #888;
- --accent: #e94560;
- --success: #28a745;
- --danger: #dc3545;
- --border: #333;
- }
- body {
- font-family: system-ui, sans-serif;
- background: var(--bg-primary);
- color: var(--text-primary);
- margin: 0;
- padding: 20px;
- min-height: 100vh;
- }
- .container { max-width: 900px; margin: 0 auto; }
- h1 { color: var(--accent); margin-bottom: 8px; }
- h2 { color: var(--text-primary); font-size: 1.1em; margin: 24px 0 16px; border-bottom: 1px solid var(--border); padding-bottom: 8px; }
- .subtitle { color: var(--text-secondary); margin-bottom: 24px; }
- .back-link { color: var(--accent); text-decoration: none; display: inline-block; margin-bottom: 16px; }
- .back-link:hover { text-decoration: underline; }
-
- .card {
- background: var(--bg-secondary);
- border-radius: 8px;
- padding: 20px;
- margin-bottom: 16px;
- }
- .card-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 16px;
- }
- .card-title {
- font-size: 1.1em;
- font-weight: 600;
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .source-badge {
- display: inline-block;
- padding: 2px 8px;
- border-radius: 10px;
- font-size: 0.7em;
- font-weight: normal;
- }
- .source-trello { background: #0079bf; }
- .source-todoist { background: #e44332; }
- .source-gcal { background: #9b59b6; }
- .source-gtasks { background: #f39c12; }
-
- .btn {
- background: var(--bg-tertiary);
- color: var(--text-primary);
- border: 1px solid var(--border);
- padding: 8px 16px;
- border-radius: 4px;
- cursor: pointer;
- font-size: 0.9em;
- }
- .btn:hover { background: #1a4a80; }
- .btn:disabled { opacity: 0.5; cursor: not-allowed; }
- .btn-danger { border-color: var(--danger); }
- .btn-danger:hover { background: var(--danger); }
- .btn-sm { padding: 4px 8px; font-size: 0.8em; }
-
- .items-list { display: grid; gap: 8px; }
- .item-row {
- display: flex;
- align-items: center;
- gap: 12px;
- padding: 10px 12px;
- background: var(--bg-tertiary);
- border-radius: 4px;
- }
- .item-row:hover { background: #1a4a80; }
-
- .toggle-switch {
- position: relative;
- width: 44px;
- height: 24px;
- flex-shrink: 0;
- }
- .toggle-switch input { opacity: 0; width: 0; height: 0; }
- .toggle-slider {
- position: absolute;
- cursor: pointer;
- top: 0; left: 0; right: 0; bottom: 0;
- background-color: #555;
- transition: .3s;
- border-radius: 24px;
- }
- .toggle-slider:before {
- position: absolute;
- content: "";
- height: 18px;
- width: 18px;
- left: 3px;
- bottom: 3px;
- background-color: white;
- transition: .3s;
- border-radius: 50%;
- }
- input:checked + .toggle-slider { background-color: var(--success); }
- input:checked + .toggle-slider:before { transform: translateX(20px); }
-
- .item-name { flex: 1; }
- .item-desc { color: var(--text-secondary); font-size: 0.85em; }
- .item-id {
- font-family: monospace;
- font-size: 0.75em;
- color: var(--text-secondary);
- max-width: 200px;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .empty-state {
- color: var(--text-secondary);
- padding: 20px;
- text-align: center;
- }
-
- .add-form {
- display: flex;
- gap: 8px;
- margin-top: 12px;
- padding-top: 12px;
- border-top: 1px solid var(--border);
- }
- .add-form input {
- flex: 1;
- background: var(--bg-tertiary);
- border: 1px solid var(--border);
- color: var(--text-primary);
- padding: 8px 12px;
- border-radius: 4px;
- }
- .add-form input:focus { outline: none; border-color: var(--accent); }
-
.htmx-indicator { display: none; }
.htmx-request .htmx-indicator { display: inline; }
</style>
</head>
-<body hx-headers='{"X-CSRF-Token": "{{.CSRFToken}}"}'>
-
- <div class="container">
- <a href="/" class="back-link">&larr; Back to Dashboard</a>
- <h1>Settings</h1>
- <p class="subtitle">Configure feature toggles and data sources.</p>
+<body class="bg-slate-950 text-slate-200 min-h-screen p-4 sm:p-8" hx-headers='{"X-CSRF-Token": "{{.CSRFToken}}"}'>
+ <div class="max-w-4xl mx-auto">
+ <a href="/" class="inline-block mb-6 text-slate-400 hover:text-white transition-colors">&larr; Back to Dashboard</a>
+ <h1 class="text-4xl font-light text-white mb-2 tracking-tight">Settings</h1>
+ <p class="text-slate-400 mb-10">Configure feature toggles and data sources.</p>
<!-- Feature Toggles Section -->
- <div class="card">
- <div class="card-header">
- <div class="card-title">Feature Toggles</div>
- </div>
- <div class="items-list" id="toggles-list">
+ <section class="mb-12">
+ <h2 class="text-xl font-medium text-white mb-6 pb-2 border-b border-white/10">Feature Toggles</h2>
+ <div class="grid gap-4" id="toggles-list">
{{if .Toggles}}
{{range .Toggles}}
- <div class="item-row" id="toggle-{{.Name}}">
- <label class="toggle-switch">
- <input type="checkbox"
- {{if .Enabled}}checked{{end}}
- hx-post="/settings/features/toggle"
- hx-vals='{"name": "{{.Name}}", "enabled": "{{if .Enabled}}false{{else}}true{{end}}"}'
- hx-swap="none"
- hx-on::after-request="this.checked = !this.checked; if(event.detail.successful) this.checked = !this.checked;">
- <span class="toggle-slider"></span>
- </label>
- <div class="item-name">
- <strong>{{.Name}}</strong>
- {{if .Description}}<div class="item-desc">{{.Description}}</div>{{end}}
+ <div class="card flex items-center gap-4" id="toggle-{{.Name}}">
+ <div class="flex-1">
+ <strong class="text-white">{{.Name}}</strong>
+ {{if .Description}}<div class="text-sm text-slate-400">{{.Description}}</div>{{end}}
+ </div>
+ <div class="flex items-center gap-6">
+ <label class="relative inline-flex items-center cursor-pointer">
+ <input type="checkbox" value="" class="sr-only peer"
+ {{if .Enabled}}checked{{end}}
+ hx-post="/settings/features/toggle"
+ hx-vals='{"name": "{{.Name}}", "enabled": "{{if .Enabled}}false{{else}}true{{end}}"}'
+ hx-swap="none"
+ hx-on::after-request="this.checked = !this.checked; if(event.detail.successful) this.checked = !this.checked;">
+ <div class="w-11 h-6 bg-slate-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div>
+ </label>
+ <button class="text-xs text-red-400 hover:text-red-300 transition-colors"
+ hx-delete="/settings/features/{{.Name}}"
+ hx-target="#toggle-{{.Name}}"
+ hx-swap="outerHTML"
+ hx-confirm="Delete feature toggle '{{.Name}}'?">
+ Delete
+ </button>
</div>
- <button class="btn btn-danger btn-sm"
- hx-delete="/settings/features/{{.Name}}"
- hx-target="#toggle-{{.Name}}"
- hx-swap="outerHTML"
- hx-confirm="Delete feature toggle '{{.Name}}'?">
- Delete
- </button>
</div>
{{end}}
{{else}}
- <div class="empty-state">No feature toggles configured.</div>
+ <div class="card text-center text-slate-500 py-10">No feature toggles configured.</div>
{{end}}
</div>
- <form class="add-form" hx-post="/settings/features" hx-target="#toggles-list" hx-swap="beforeend">
- <input type="text" name="name" placeholder="Feature name (snake_case)" required pattern="[a-z_]+">
- <input type="text" name="description" placeholder="Description (optional)">
- <button type="submit" class="btn">Add Toggle</button>
+ <form class="mt-4 flex flex-wrap gap-3 p-4 bg-slate-900/40 rounded-xl border border-white/5" hx-post="/settings/features" hx-target="#toggles-list" hx-swap="beforeend">
+ <input type="text" name="name" placeholder="Feature name (snake_case)" required pattern="[a-z_]+"
+ class="flex-1 bg-slate-950 border border-white/10 rounded-lg px-4 py-2 text-sm focus:ring-1 focus:ring-blue-500 outline-none">
+ <input type="text" name="description" placeholder="Description (optional)"
+ class="flex-[2] bg-slate-950 border border-white/10 rounded-lg px-4 py-2 text-sm focus:ring-1 focus:ring-blue-500 outline-none">
+ <button type="submit" class="bg-blue-600 hover:bg-blue-500 text-white px-6 py-2 rounded-lg text-sm font-medium transition-colors">Add Toggle</button>
</form>
- </div>
+ </section>
<!-- Passkeys Section -->
{{if .WebAuthnEnabled}}
- <div class="card" id="passkeys-card">
- <div class="card-header">
- <div class="card-title">Passkeys</div>
+ <section class="mb-12" id="passkeys-card-section">
+ <h2 class="text-xl font-medium text-white mb-6 pb-2 border-b border-white/10">Passkeys</h2>
+ <div id="passkeys-list" hx-get="/settings/passkeys" hx-trigger="load" hx-swap="innerHTML" class="grid gap-4">
+ <div class="card text-center text-slate-500 py-10">Loading passkeys...</div>
</div>
- <div id="passkeys-list" hx-get="/settings/passkeys" hx-trigger="load" hx-swap="innerHTML">
- <div class="empty-state">Loading passkeys...</div>
+ <div class="mt-4 flex flex-wrap gap-3 p-4 bg-slate-900/40 rounded-xl border border-white/5" id="passkey-register-form">
+ <input type="text" id="passkey-name" placeholder="Passkey name (e.g., MacBook Touch ID)" maxlength="100"
+ class="flex-1 bg-slate-950 border border-white/10 rounded-lg px-4 py-2 text-sm focus:ring-1 focus:ring-blue-500 outline-none">
+ <button type="button" class="bg-slate-700 hover:bg-slate-600 text-white px-6 py-2 rounded-lg text-sm font-medium transition-colors" id="register-passkey-btn">Register New Passkey</button>
</div>
- <div class="add-form" id="passkey-register-form">
- <input type="text" id="passkey-name" placeholder="Passkey name (e.g., MacBook Touch ID)" maxlength="100">
- <button type="button" class="btn" id="register-passkey-btn">Register New Passkey</button>
- </div>
- <p id="passkey-status" style="color: var(--text-secondary); font-size: 0.85em; margin-top: 8px; display: none;"></p>
- </div>
- {{end}}
+ <p id="passkey-status" class="mt-3 text-sm text-slate-400 hidden"></p>
+ </section>
+
<script>
(function() {
- if (!document.getElementById('passkeys-card') || !window.PublicKeyCredential) {
- var card = document.getElementById('passkeys-card');
- if (card) card.style.display = 'none';
+ if (!window.PublicKeyCredential) {
+ var section = document.getElementById('passkeys-card-section');
+ if (section) section.style.display = 'none';
return;
}
@@ -238,7 +94,7 @@
btn.disabled = true;
statusEl.style.display = 'block';
- statusEl.style.color = 'var(--text-secondary)';
+ statusEl.className = 'mt-3 text-sm text-slate-400';
statusEl.textContent = 'Starting registration...';
try {
@@ -279,13 +135,12 @@
});
if (!finishResp.ok) throw new Error('Registration failed');
- statusEl.style.color = 'var(--success)';
+ statusEl.className = 'mt-3 text-sm text-green-400';
statusEl.textContent = 'Passkey registered successfully!';
nameInput.value = '';
- // Reload passkeys list
htmx.trigger('#passkeys-list', 'load');
} catch (e) {
- statusEl.style.color = 'var(--danger)';
+ statusEl.className = 'mt-3 text-sm text-red-400';
if (e.name === 'NotAllowedError') {
statusEl.textContent = 'Registration was cancelled.';
} else {
@@ -310,66 +165,67 @@
}
})();
</script>
+ {{end}}
- <!-- Source Configuration Section -->
- <h2>Data Sources</h2>
- <div style="display: flex; gap: 8px; align-items: center;">
- <form hx-post="/settings/sync" hx-swap="outerHTML" hx-target="#sources-container" hx-indicator=".sync-indicator">
- <button type="submit" class="btn">
- <span class="sync-indicator htmx-indicator">Syncing...</span>
- Sync Available Sources
- </button>
- </form>
- <button class="btn btn-danger"
- hx-post="/settings/clear-cache"
- hx-target="#sync-log"
- hx-swap="outerHTML"
- hx-confirm="Clear all cached data? Next page load will fetch fresh data from all sources.">
- Clear Cache
- </button>
- </div>
-
- {{template "sync-log" .SyncLog}}
-
- <div id="sources-container" style="margin-top: 16px;">
- {{range .Sources}}
- <div class="card">
- <div class="card-header">
- <div class="card-title">
- <span class="source-badge source-{{.}}">{{.}}</span>
- {{if eq . "trello"}}Trello Boards{{end}}
- {{if eq . "todoist"}}Todoist Projects{{end}}
- {{if eq . "gcal"}}Google Calendars{{end}}
- {{if eq . "gtasks"}}Google Task Lists{{end}}
- </div>
+ <!-- Data Sources Section -->
+ <section class="mb-12">
+ <div class="flex flex-wrap items-center justify-between gap-4 mb-6 pb-2 border-b border-white/10">
+ <h2 class="text-xl font-medium text-white">Data Sources</h2>
+ <div class="flex gap-3">
+ <form hx-post="/settings/sync" hx-swap="outerHTML" hx-target="#sources-container" hx-indicator=".sync-indicator">
+ <button type="submit" class="text-sm bg-slate-800 hover:bg-slate-700 text-white px-4 py-2 rounded-lg transition-colors">
+ <span class="sync-indicator htmx-indicator mr-2">Syncing...</span>
+ Sync Available Sources
+ </button>
+ </form>
+ <button class="text-sm border border-red-500/30 text-red-400 hover:bg-red-500/10 px-4 py-2 rounded-lg transition-colors"
+ hx-post="/settings/clear-cache"
+ hx-target="#sync-log"
+ hx-swap="outerHTML"
+ hx-confirm="Clear all cached data? Next page load will fetch fresh data from all sources.">
+ Clear Cache
+ </button>
</div>
- <div class="items-list">
- {{$configs := index $.Configs .}}
- {{if $configs}}
- {{range $configs}}
- <div class="item-row">
- <label class="toggle-switch">
- <input type="checkbox"
- {{if .Enabled}}checked{{end}}
- hx-post="/settings/toggle"
- hx-vals='{"source": "{{.Source}}", "item_type": "{{.ItemType}}", "item_id": "{{.ItemID}}", "enabled": "{{if .Enabled}}false{{else}}true{{end}}"}'
- hx-swap="none"
- hx-on::after-request="this.checked = !this.checked; if(event.detail.successful) this.checked = !this.checked;">
- <span class="toggle-slider"></span>
- </label>
- <span class="item-name">{{.ItemName}}</span>
- <span class="item-id" title="{{.ItemID}}">{{.ItemID}}</span>
- </div>
+ </div>
+
+ {{template "sync-log" .SyncLog}}
+
+ <div id="sources-container" class="grid gap-8 mt-8">
+ {{range .Sources}}
+ <div class="space-y-4">
+ <h3 class="text-sm font-semibold uppercase tracking-wider text-slate-500 flex items-center gap-2">
+ {{if eq . "trello"}}Trello Boards{{else if eq . "todoist"}}Todoist Projects{{else if eq . "gcal"}}Google Calendars{{else if eq . "gtasks"}}Google Task Lists{{else}}{{.}}{{end}}
+ </h3>
+ <div class="grid gap-3 sm:grid-cols-2">
+ {{$configs := index $.Configs .}}
+ {{if $configs}}
+ {{range $configs}}
+ <div class="card flex items-center gap-4">
+ <label class="relative inline-flex items-center cursor-pointer">
+ <input type="checkbox" value="" class="sr-only peer"
+ {{if .Enabled}}checked{{end}}
+ hx-post="/settings/toggle"
+ hx-vals='{"source": "{{.Source}}", "item_type": "{{.ItemType}}", "item_id": "{{.ItemID}}", "enabled": "{{if .Enabled}}false{{else}}true{{end}}"}'
+ hx-swap="none"
+ hx-on::after-request="this.checked = !this.checked; if(event.detail.successful) this.checked = !this.checked;">
+ <div class="w-11 h-6 bg-slate-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div>
+ </label>
+ <div class="flex-1 min-w-0">
+ <div class="text-white truncate" title="{{.ItemName}}">{{.ItemName}}</div>
+ <div class="text-[10px] text-slate-500 font-mono truncate" title="{{.ItemID}}">{{.ItemID}}</div>
+ </div>
+ </div>
+ {{end}}
+ {{else}}
+ <div class="card col-span-full text-center text-slate-500 py-6 text-sm italic">
+ No items found. Click "Sync Available Sources" to fetch.
+ </div>
{{end}}
- {{else}}
- <div class="empty-state">
- No items configured. Click "Sync Available Sources" to fetch.
- </div>
- {{end}}
+ </div>
</div>
+ {{end}}
</div>
- {{end}}
- </div>
+ </section>
</div>
</body>
</html>