Owner accounts: auth, password reset, CRM dashboard visibility (#19)
Flask-session login scoped to one client_id (never a request param), self-service + operator-triggered password reset via single-use tokens, and an Owner accounts tab on the CRM dashboard. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -82,6 +82,7 @@
|
||||
<button class="tab active" data-e="leads">Leads<span class="n" id="n-leads"></span></button>
|
||||
<button class="tab" data-e="clients">Clients<span class="n" id="n-clients"></span></button>
|
||||
<button class="tab" data-e="credentials">Credentials<span class="n" id="n-credentials"></span></button>
|
||||
<button class="tab" data-e="owner_users">Eigentümer-Konten<span class="n" id="n-owner_users"></span></button>
|
||||
</div>
|
||||
<div class="bar">
|
||||
<input id="search" placeholder="Filtern …" />
|
||||
@@ -144,6 +145,7 @@
|
||||
}
|
||||
|
||||
function render(){
|
||||
if(current === 'owner_users'){ renderOwnerUsers(); return; }
|
||||
const cols = COLS[current];
|
||||
let rows = cache[current] || [];
|
||||
const q = document.getElementById('search').value.trim().toLowerCase();
|
||||
@@ -170,6 +172,31 @@
|
||||
document.getElementById('table').innerHTML = `<table><thead>${head}</thead><tbody>${body}</tbody></table>`;
|
||||
}
|
||||
|
||||
function renderOwnerUsers(){
|
||||
let rows = cache.owner_users || [];
|
||||
const q = document.getElementById('search').value.trim().toLowerCase();
|
||||
if(q) rows = rows.filter(r => ['client_id','email'].some(c => String(r[c]??'').toLowerCase().includes(q)));
|
||||
if(!rows.length){ document.getElementById('table').innerHTML = '<div class="empty">Keine Einträge.</div>'; return; }
|
||||
const head = '<tr><th>client_id</th><th>email</th><th>created_at</th><th></th></tr>';
|
||||
const body = rows.map(r => {
|
||||
const created = r.created_at ? String(r.created_at).replace('T',' ').slice(0,16) : '';
|
||||
return `<tr><td title="${esc(r.client_id)}">${esc(r.client_id)}</td><td>${esc(r.email)}</td>` +
|
||||
`<td>${esc(created)}</td><td class="actcol">` +
|
||||
`<button class="act" onclick="sendOwnerReset('${esc(r.user_id)}')">Reset-Link senden</button></td></tr>`;
|
||||
}).join('');
|
||||
document.getElementById('table').innerHTML = `<table><thead>${head}</thead><tbody>${body}</tbody></table>`;
|
||||
}
|
||||
|
||||
async function sendOwnerReset(userId){
|
||||
clearErr();
|
||||
try {
|
||||
const r = await fetch(`${API}/owner_users/${encodeURIComponent(userId)}/reset-password`, {
|
||||
method: 'POST', headers: { 'X-CRM-Token': TOKEN } });
|
||||
if(!r.ok) throw new Error('HTTP '+r.status);
|
||||
showOk('✓ Reset-Link gesendet');
|
||||
} catch(e){ showErr('Senden fehlgeschlagen: '+e.message); }
|
||||
}
|
||||
|
||||
function toggleSecret(id){
|
||||
if(revealed.has(id)) revealed.delete(id); else revealed.add(id);
|
||||
render();
|
||||
@@ -276,6 +303,7 @@
|
||||
document.querySelectorAll('.tab').forEach(x => x.classList.remove('active'));
|
||||
t.classList.add('active');
|
||||
current = t.dataset.e;
|
||||
document.getElementById('add').style.display = (current === 'owner_users') ? 'none' : '';
|
||||
if(cache[current]) render(); else load(current);
|
||||
});
|
||||
document.getElementById('search').oninput = render;
|
||||
|
||||
Reference in New Issue
Block a user