mirror of
https://github.com/zvx-echo6/recon.git
synced 2026-05-20 06:34:40 +02:00
Add contacts/phone book system with per-user scoping
New files: - lib/auth.py: Authentik forward-auth helpers (get_user_id, @require_auth) - lib/contacts.py: ContactsDB with CRUD, soft delete, restore, purge, find_nearby - lib/contacts_api.py: Flask Blueprint with 9 API endpoints at /api/contacts - templates/knowledge/deleted_contacts.html: Dashboard recovery page Modified: - lib/api.py: Register contacts_bp, add KNOWLEDGE_SUBNAV entry, /deleted-contacts route - config/profiles: has_contacts feature flag (true for home, false for pi profiles) Separate SQLite DB at data/contacts.db. Per-user isolation via X-Authentik-Username. Home/Work labels enforced unique per user. Haversine proximity queries (75m default). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
095bf8c2af
commit
a4288c0cd8
8 changed files with 423 additions and 0 deletions
56
templates/knowledge/deleted_contacts.html
Normal file
56
templates/knowledge/deleted_contacts.html
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<h3 style="color:#ffa500;margin-bottom:16px;">Deleted Contacts</h3>
|
||||
{% if not contacts %}
|
||||
<p class="text-dim">No deleted contacts.</p>
|
||||
{% else %}
|
||||
<table>
|
||||
<tr><th>Label</th><th>Name</th><th>Category</th><th>Phone</th><th>Deleted At</th><th>Actions</th></tr>
|
||||
{% for c in contacts %}
|
||||
<tr id="row-{{ c.id }}">
|
||||
<td>{{ c.label }}</td>
|
||||
<td>{{ c.name or '' }}</td>
|
||||
<td class="text-dim">{{ c.category or '' }}</td>
|
||||
<td class="text-dim text-xs">{{ c.phone or '' }}</td>
|
||||
<td class="text-dim text-xs">{{ c.deleted_at or '' }}</td>
|
||||
<td>
|
||||
<button class="btn" onclick="restoreContact({{ c.id }})">Restore</button>
|
||||
<button class="btn" style="margin-left:4px;color:#ff4444;" onclick="purgeContact({{ c.id }})">Purge</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
<script>
|
||||
async function restoreContact(id) {
|
||||
try {
|
||||
var resp = await fetch('/api/contacts/' + id + '/restore', {method: 'POST'});
|
||||
if (resp.ok) {
|
||||
location.reload();
|
||||
} else {
|
||||
var data = await resp.json();
|
||||
alert(data.error || 'Restore failed');
|
||||
}
|
||||
} catch(e) {
|
||||
alert('Error: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function purgeContact(id) {
|
||||
if (!confirm('Permanently delete this contact? This cannot be undone.')) return;
|
||||
try {
|
||||
var resp = await fetch('/api/contacts/' + id + '/purge', {method: 'DELETE'});
|
||||
if (resp.ok) {
|
||||
location.reload();
|
||||
} else {
|
||||
var data = await resp.json();
|
||||
alert(data.error || 'Purge failed');
|
||||
}
|
||||
} catch(e) {
|
||||
alert('Error: ' + e.message);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue