import React, { useEffect, useState } from 'react'; import { Key, Plus, Trash2, Copy, Check, Webhook, Zap, Terminal, } from 'lucide-react'; import { APIKey, ApiKeyScope, PriorityLevel, SharedList, TaskList } from '../../lib/types'; import { api, triggerHaptic } from '../../lib/api'; interface ApiKeysPanelProps { onIngested?: () => void; } // What a key may deliver; keys never read anything const SCOPES: { id: ApiKeyScope; label: string; hint: string }[] = [ { id: 'webhook:triage', label: '📥 Triage / Inbox', hint: 'Gedanken in die Inbox' }, { id: 'webhook:task', label: '📋 Aufgaben', hint: 'Aufgaben direkt anlegen' }, { id: 'webhook:list', label: '📝 Listeneinträge', hint: 'Einträge zu Listen hinzufügen' }, ]; const scopeLabel = (id: string) => SCOPES.find((s) => s.id === id)?.label || id; type SimTarget = 'triage' | 'task' | 'list_item'; // Personal API keys with scopes, a simulator and copyable examples (settings → Private Webhooks) export const ApiKeysPanel: React.FC = ({ onIngested }) => { const [keys, setKeys] = useState([]); const [newKeyName, setNewKeyName] = useState(''); const [newKeyScopes, setNewKeyScopes] = useState(['webhook:triage']); const [newlyCreatedKey, setNewlyCreatedKey] = useState(null); const [copiedKey, setCopiedKey] = useState(false); // Live Webhook Simulator State const [simTitle, setSimTitle] = useState('Waschmaschine im Keller ist fertig!'); const [simPriority, setSimPriority] = useState('high'); const [simTarget, setSimTarget] = useState('triage'); const [simListId, setSimListId] = useState(''); const [simTaskListId, setSimTaskListId] = useState(''); const [simStatus, setSimStatus] = useState(null); const [lists, setLists] = useState([]); const [taskLists, setTaskLists] = useState([]); const loadKeys = async () => { const k = await api.getAPIKeys(); setKeys(k); }; useEffect(() => { loadKeys(); api.getAllLists().then(setLists).catch(() => {}); api.getTaskLists().then(setTaskLists).catch(() => {}); }, []); const toggleScope = (id: ApiKeyScope) => { setNewKeyScopes((prev) => { const next = prev.includes(id) ? prev.filter((s) => s !== id) : [...prev, id]; return next.length > 0 ? next : prev; // at least one }); }; const handleCreateKey = async (e: React.FormEvent) => { e.preventDefault(); triggerHaptic(15); const created = await api.createAPIKey(newKeyName.trim() || 'Home Assistant', newKeyScopes); if (created.rawKey) { setNewlyCreatedKey(created.rawKey); } setNewKeyName(''); setNewKeyScopes(['webhook:triage']); await loadKeys(); }; const handleDeleteKey = async (id: string) => { if (!window.confirm('Diesen API-Schlüssel wirklich widerrufen?')) return; triggerHaptic(20); await api.deleteAPIKey(id); await loadKeys(); }; const handleCopy = (text: string) => { navigator.clipboard.writeText(text); setCopiedKey(true); setTimeout(() => setCopiedKey(false), 2000); }; const handleSimulateWebhook = async () => { triggerHaptic(20); if (simTarget === 'list_item' && !simListId) { setSimStatus('❌ Bitte eine Liste wählen.'); return; } try { // With a freshly created key the simulator tests exactly that key (and its scopes) await api.testWebhookIngest(newlyCreatedKey, { title: simTitle, priority: simPriority, tags: simTarget === 'list_item' ? [] : ['smarthome', 'automation'], target: simTarget, taskListId: simTarget === 'task' ? simTaskListId || undefined : undefined, listId: simTarget === 'list_item' ? simListId : undefined, }); setSimStatus( simTarget === 'task' ? '✅ Webhook erfolgreich ausgelöst! Die Aufgabe ist angelegt.' : simTarget === 'list_item' ? '✅ Webhook erfolgreich ausgelöst! Der Eintrag steht in der Liste.' : '✅ Webhook erfolgreich ausgelöst! Neue Karte liegt in deiner Inbox.' ); onIngested?.(); setTimeout(() => setSimStatus(null), 4000); } catch (err: any) { setSimStatus(`❌ Fehler: ${err.message}`); } }; const exampleToken = newlyCreatedKey || 'mindsift_sec_dein_api_key'; const baseUrl = window.location.origin; const examplePayload = simTarget === 'task' ? `{ "target": "task", "title": "Filter der Lüftungsanlage wechseln", "priority": "high", "dueDate": "2026-11-15T09:00"${simTaskListId ? `,\n "taskListId": "${simTaskListId}"` : ''} }` : simTarget === 'list_item' ? `{ "target": "list_item", "listId": "${simListId || 'ID_DER_LISTE'}", "content": "Hafermilch", "quantity": "2" }` : `{ "title": "Kellerfeuchtigkeit über 70%", "priority": "urgent", "tags": ["smarthome", "keller"] }`; return (
{/* API keys */}

Webhooks & Ingestion API-SchlĂĽssel

Verbinde Home Assistant, Zigbee2MQTT, n8n oder eigene Cronjobs mit MindSift. Ein Key kann nur Daten einliefern, nie lesen; was genau, legst du pro Key fest.

setNewKeyName(e.target.value)} placeholder="Bezeichnung (z. B. Home Assistant, Proxmox Backup, Rauchmelder)…" className="w-full sm:flex-1 min-w-0 h-11 sm:h-9 px-3 input-field text-base sm:text-sm" />
Darf einliefern: {SCOPES.map((sc) => { const on = newKeyScopes.includes(sc.id); return ( ); })}
{newlyCreatedKey && (

Neuer API-SchlĂĽssel erstellt (bitte jetzt kopieren):

{newlyCreatedKey}
)} {/* Active Keys List */}
{keys.length === 0 ? (

Noch keine dedizierten API-SchlĂĽssel hinterlegt.

) : ( keys.map((k) => (
{k.name} {k.keyPrefix} {k.scopes .split(',') .filter(Boolean) .map((sc) => ( {scopeLabel(sc)} ))} {k.lastUsedAt && ( Zuletzt genutzt: {new Date(k.lastUsedAt).toLocaleDateString('de-DE')} )}
)) )}
{/* Simulator */}

Live Smarthome Webhook-Simulator

Teste den Webhook-Endpunkt direkt hier {newlyCreatedKey ? ' – mit dem eben erzeugten Key und seinen Berechtigungen.' : ' – mit deiner Anmeldung.'}

{( [ ['triage', '📥 Inbox'], ['task', '📋 Aufgabe'], ['list_item', '📝 Listeneintrag'], ] as const ).map(([value, label]) => ( ))} {simTarget === 'list_item' && ( )} {simTarget === 'task' && ( )}
{simTarget === 'triage' && (
{[ { label: '🧺 Waschmaschine fertig', text: 'Waschmaschine im Keller ist fertig – Wäsche aufhängen!', prio: 'high' as PriorityLevel }, { label: '🚨 Rauchmelder Batterie', text: 'Rauchmelder Dachgeschoss meldet niedrige Batterie (12%)', prio: 'urgent' as PriorityLevel }, { label: '💧 Luftfeuchtigkeit Keller', text: 'Kellerfeuchtigkeit über 70% – Lüften oder Entfeuchter starten', prio: 'urgent' as PriorityLevel }, ].map((preset) => ( ))}
)}
setSimTitle(e.target.value)} className="flex-1 h-11 sm:h-9 px-3 input-field text-base sm:text-sm" />
{simStatus && (

{simStatus}

)} {/* Code Example Snippets */}
cURL / Home Assistant REST Command:
{`curl -X POST "${baseUrl}/api/v1/webhooks/ingest" \\
  -H "X-API-Key: ${exampleToken}" \\
  -H "Content-Type: application/json" \\
  -d '${examplePayload}'`}
          

Der Key gehört in den Header X-API-Key, nicht in die URL, damit er nicht in Proxy-Logs landet. Fehlt dem Key die Berechtigung für das Ziel, antwortet der Server mit 403.

); };