Changeset 0.20.0 (#85)
This commit is contained in:
163
src/js/notification-prefs.js
Normal file
163
src/js/notification-prefs.js
Normal file
@@ -0,0 +1,163 @@
|
||||
/* ─────────────────────────────────────────────
|
||||
Notification Preferences (v0.20.0 Phase 3)
|
||||
Settings → Notifications tab
|
||||
───────────────────────────────────────────── */
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const NotifPrefs = {
|
||||
_loaded: false,
|
||||
_prefs: [],
|
||||
|
||||
// Known notification types for the UI
|
||||
_types: [
|
||||
{ key: 'kb.ready', label: 'Knowledge base ready' },
|
||||
{ key: 'kb.error', label: 'Knowledge base error' },
|
||||
{ key: 'role.fallback', label: 'Model fallback' },
|
||||
{ key: 'grant.changed', label: 'Group membership change' },
|
||||
{ key: '*', label: 'Default (all others)' },
|
||||
],
|
||||
|
||||
async load() {
|
||||
try {
|
||||
this._prefs = await API.listNotifPrefs() || [];
|
||||
} catch (e) {
|
||||
console.warn('[notif-prefs] failed to load:', e.message);
|
||||
this._prefs = [];
|
||||
}
|
||||
this._loaded = true;
|
||||
this.render();
|
||||
},
|
||||
|
||||
render() {
|
||||
const container = document.getElementById('notifPrefsBody');
|
||||
if (!container) return;
|
||||
|
||||
const prefMap = {};
|
||||
for (const p of this._prefs) {
|
||||
prefMap[p.type] = p;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
for (const t of this._types) {
|
||||
const p = prefMap[t.key];
|
||||
const inApp = p ? p.in_app : true;
|
||||
const email = p ? p.email : false;
|
||||
|
||||
html += `<tr class="notif-pref-row" data-type="${esc(t.key)}">
|
||||
<td class="notif-pref-label">${esc(t.label)}</td>
|
||||
<td class="notif-pref-toggle">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" class="notif-pref-inapp" ${inApp ? 'checked' : ''}>
|
||||
</label>
|
||||
</td>
|
||||
<td class="notif-pref-toggle">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" class="notif-pref-email" ${email ? 'checked' : ''}>
|
||||
</label>
|
||||
</td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
container.innerHTML = `<table class="notif-pref-table">
|
||||
<thead><tr>
|
||||
<th>Notification Type</th>
|
||||
<th>In-App</th>
|
||||
<th>Email</th>
|
||||
</tr></thead>
|
||||
<tbody>${html}</tbody>
|
||||
</table>`;
|
||||
|
||||
// Attach change listeners
|
||||
container.querySelectorAll('.notif-pref-row').forEach(row => {
|
||||
const type = row.dataset.type;
|
||||
row.querySelector('.notif-pref-inapp').addEventListener('change', (e) => {
|
||||
this._save(type, { in_app: e.target.checked });
|
||||
});
|
||||
row.querySelector('.notif-pref-email').addEventListener('change', (e) => {
|
||||
this._save(type, { email: e.target.checked });
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
async _save(type, patch) {
|
||||
try {
|
||||
await API.setNotifPref(type, patch);
|
||||
// Update cache
|
||||
const existing = this._prefs.find(p => p.type === type);
|
||||
if (existing) {
|
||||
Object.assign(existing, patch);
|
||||
} else {
|
||||
this._prefs.push({ type, in_app: true, email: false, ...patch });
|
||||
}
|
||||
} catch (e) {
|
||||
UI.toast('Failed to save preference', 'error');
|
||||
this.render(); // revert checkboxes
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// ── Admin SMTP UI helpers ───────────────────
|
||||
|
||||
// Called from index.html onclick
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
NotifPrefs._testEmail = async function() {
|
||||
const status = document.getElementById('smtpTestStatus');
|
||||
const btn = document.getElementById('adminSmtpTestBtn');
|
||||
if (!status || !btn) return;
|
||||
|
||||
btn.disabled = true;
|
||||
status.textContent = 'Sending…';
|
||||
status.className = 'smtp-test-status';
|
||||
|
||||
try {
|
||||
const result = await API.adminTestEmail();
|
||||
status.textContent = `✓ Sent to ${result.sent_to}`;
|
||||
status.className = 'smtp-test-status success';
|
||||
} catch (e) {
|
||||
status.textContent = '✗ ' + (e.message || 'Failed');
|
||||
status.className = 'smtp-test-status error';
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
};
|
||||
|
||||
NotifPrefs._initAdminSmtp = function() {
|
||||
const toggle = document.getElementById('adminEmailEnabled');
|
||||
const fields = document.getElementById('smtpConfigFields');
|
||||
if (!toggle || !fields) return;
|
||||
toggle.addEventListener('change', () => {
|
||||
fields.style.display = toggle.checked ? '' : 'none';
|
||||
});
|
||||
};
|
||||
|
||||
NotifPrefs._loadAdminSmtp = function(cfg) {
|
||||
if (!cfg) return;
|
||||
const el = (id) => document.getElementById(id);
|
||||
if (cfg.email_enabled) el('adminEmailEnabled').checked = true;
|
||||
if (cfg.smtp_host) el('adminSmtpHost').value = cfg.smtp_host;
|
||||
if (cfg.smtp_port) el('adminSmtpPort').value = cfg.smtp_port;
|
||||
if (cfg.smtp_user) el('adminSmtpUser').value = cfg.smtp_user;
|
||||
// Don't populate password (security)
|
||||
if (cfg.smtp_from) el('adminSmtpFrom').value = cfg.smtp_from;
|
||||
if (cfg.smtp_tls != null) el('adminSmtpTls').value = String(cfg.smtp_tls);
|
||||
|
||||
// Show/hide SMTP fields
|
||||
const fields = document.getElementById('smtpConfigFields');
|
||||
if (fields) fields.style.display = cfg.email_enabled ? '' : 'none';
|
||||
};
|
||||
|
||||
NotifPrefs._getAdminSmtp = function() {
|
||||
const el = (id) => document.getElementById(id);
|
||||
const config = {
|
||||
email_enabled: el('adminEmailEnabled')?.checked || false,
|
||||
smtp_host: el('adminSmtpHost')?.value?.trim() || '',
|
||||
smtp_port: parseInt(el('adminSmtpPort')?.value) || 587,
|
||||
smtp_user: el('adminSmtpUser')?.value?.trim() || '',
|
||||
smtp_from: el('adminSmtpFrom')?.value?.trim() || '',
|
||||
smtp_tls: el('adminSmtpTls')?.value === 'true',
|
||||
};
|
||||
// Only include password if user typed something new
|
||||
const pw = el('adminSmtpPassword')?.value;
|
||||
if (pw) config.smtp_password = pw;
|
||||
return config;
|
||||
};
|
||||
Reference in New Issue
Block a user