/* =================================================================== Automationen. Die Kachel trug lange "Phase 4", obwohl die Automationen laengst liefen -- sie waren nur verteilt und hatten keinen Ort. Diese Seite ist dieser Ort. Sie fuehrt bewusst NICHTS Eigenes: Was hier steht, kommt aus den Bereichen selbst. Eine Seite, die Automationen doppelt abbildet, waere die naechste Stelle, die irgendwann etwas anderes behauptet als die Wirklichkeit. =================================================================== */ (() => { 'use strict'; const $ = (id) => document.getElementById(id); const el = (tag, klasse, text) => { const k = document.createElement(tag); if (klasse) k.className = klasse; if (text !== undefined) k.textContent = text; return k; }; const melde = (t) => { $('fehler').textContent = t || ''; }; async function hole(pfad, optionen = {}) { const a = await fetch(pfad, { credentials: 'same-origin', ...optionen }); if (a.status === 401) { location.assign('/workspace/'); throw new Error('abgemeldet'); } return a; } /* Was dauerhaft laeuft. Steht hier als Text, weil es Verhalten ist und keine Daten -- aber jede Zeile beschreibt etwas, das tatsaechlich im Code steht, nicht etwas Gewuenschtes. */ const REGELN = [ { name: 'Hinweise auf der Übersicht', text: 'Überfälliges, heute Fälliges, Gespräche ohne Protokoll, fällige Follow-ups und dringende Punkte werden bei jedem Aufruf neu ermittelt. Sie führen keine eigene Liste – ein Hinweis verschwindet von selbst, sobald die Sache erledigt ist.' }, { name: '„Neu" in der Bibliothek', text: 'Eine frisch eingestellte Anleitung steht 48 Stunden unter „Neu & wichtig“ und ist danach nur noch in ihrer Kategorie zu finden. Angepinntes bleibt, bis es jemand abschaltet.' }, { name: 'Zuständigkeit bei der Übergabe', text: 'Wird aus einem übergebenen Lead ein Creator angelegt, betreut ihn automatisch der Scout, der ihn gefunden hat. Die Angaben aus der Pipeline wandern dabei ins Creator-Profil.' }, { name: 'Aufgaben aus Gesprächen', text: 'To-dos aus einem Protokoll, Entscheidungen aus einem Review und Befunde aus dem Start-Check werden zu echten Aufgaben im Brett – in einem Zug, nicht als Text, den man später abtippt.' }, { name: 'Sitzungen laufen ab', text: 'Eine Anmeldung gilt zwölf Stunden. Nach einem neuen Zugangscode oder einer Sperre enden alle offenen Sitzungen der Person sofort.' }, { name: 'Schutz vor Rateversuchen', text: 'Acht Fehlversuche je Anschluss in zehn Minuten, danach ist zu. Gezählt wird die echte Adresse, nicht die von Cloudflare.' }, { name: 'Sicherung vor Umbauten', text: 'Bevor die Datenbank umgestellt wird, schreibt der Server eine vollständige Sicherung. Ohne Sicherung wird nicht umgestellt.' }, ]; const ART = { ki_gefragt: 'KI um einen Vorschlag gebeten', einstellung_geaendert: 'Einstellung geändert', betreuung_gesetzt: 'Zuständigkeit gesetzt', onboarding_gestartet: 'Creator aus einem Lead angelegt', protokoll_geschrieben: 'Gesprächsprotokoll festgehalten', startcheck_aufgabe: 'Aufgabe aus dem Start-Check', review_entscheidung: 'Aufgabe aus dem Review', anmeldung_gesperrt: 'Anmeldung wegen zu vieler Versuche gesperrt', }; const zeitpunkt = (iso) => { if (!iso) return ''; const d = new Date(iso); const p = (n) => String(n).padStart(2, '0'); return `${p(d.getDate())}.${p(d.getMonth() + 1)}. · ${p(d.getHours())}:${p(d.getMinutes())}`; }; /* ---------- KI ---------------------------------------------------------- */ async function kiZustand() { let d; try { d = await (await hole('/workspace/api/ki/status')).json(); } catch { return; } $('ki-schalter-block').hidden = false; $('ki-schalter').checked = d.angeschaltet !== false; $('ki-schalter-text').textContent = d.angeschaltet !== false ? 'Angeschaltet' : 'Abgeschaltet'; $('ki-kasten').dataset.zustand = d.da ? 'laeuft' : (d.angeschaltet === false ? 'aus' : 'weg'); $('ki-text').textContent = d.angeschaltet === false ? 'Abgeschaltet. Die Vorschlags-Knöpfe erscheinen nirgends, und der Dienst wird nicht gefragt.' : d.da ? 'Läuft. Die Vorschlags-Knöpfe erscheinen in Calls, Start-Check und Report.' : 'Angeschaltet, aber gerade nicht erreichbar. Alles andere funktioniert normal weiter.'; const daten = $('ki-daten'); daten.textContent = ''; for (const [name, wert] of [ ['Modell', 'Qwen3.5-4B, Apache 2.0'], ['Ort', 'auf diesem Server, nur über 127.0.0.1'], ['Kosten', '0 € – nichts verlässt die Maschine'], ['Grenzen', 'zwei von vier Kernen, eine Anfrage gleichzeitig, fünf je Person in zehn Minuten'], ['Zustand', d.beschaeftigt ? 'denkt gerade' : (d.da ? 'bereit' : '–')], ]) { const li = el('li'); li.append(el('span', 'ki-kasten__schild', name), el('span', null, wert)); daten.append(li); } } $('ki-schalter').addEventListener('change', async () => { const an = $('ki-schalter').checked; $('ki-schalter').disabled = true; try { const a = await hole('/workspace/api/ki/schalter', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ an }), }); if (!a.ok) { melde((await a.json().catch(() => ({}))).fehler || 'Ging nicht.'); $('ki-schalter').checked = !an; return; } melde(''); await kiZustand(); } catch { /* umgeleitet */ } finally { $('ki-schalter').disabled = false; } }); /* ---------- Hinweise ----------------------------------------------------- */ const HINWEISTEXT = { aufgaben_ueberfaellig: 'überfällige Aufgaben', aufgaben_heute: 'heute fällige Aufgaben', aufgaben_review: 'Aufgaben warten auf Freigabe', termine_heute: 'Termine heute', protokoll_fehlt: 'Gespräche ohne Protokoll', dateien_review: 'Dateien im Review', bereiche_dringend: 'dringende Punkte in den Bereichen', startcheck_handlung: 'Punkte im Start-Check', followup_faellig: 'fällige Follow-ups', uebergabe_offen: 'übergebene Leads ohne Creator', review_faellig: 'überfällige Review-Termine', review_bald: 'Reviews in den nächsten 7 Tagen', ohne_betreuung: 'Creator ohne zuständige Person', ohne_profil: 'leere Creator-Profile', ohne_startcheck: 'Creator ohne Start-Check', }; async function hinweise() { let d; try { d = await (await hole('/workspace/api/hinweise')).json(); } catch { return; } const ziel = $('hinweise'); ziel.textContent = ''; $('hinweis-zahl').textContent = String(d.hinweise.length); if (!d.hinweise.length) { ziel.append(el('p', 'leer-hinweis', 'Nichts offen. Alles läuft.')); return; } const liste = el('div', 'hinweise'); for (const h of d.hinweise) { const a = el('a', 'hinweis'); a.href = h.ziel; a.dataset.stufe = h.stufe; a.append(el('span', 'hinweis__zahl', String(h.anzahl))); a.append(el('span', 'hinweis__text', HINWEISTEXT[h.art] || h.text)); a.append(el('span', 'hinweis__pfeil', '→')); liste.append(a); } ziel.append(liste); } /* ---------- Verlauf ------------------------------------------------------ */ async function verlauf() { const ziel = $('verlauf'); let d; try { const a = await hole('/workspace/api/verwaltung/protokoll?n=40'); if (!a.ok) { ziel.textContent = ''; ziel.append(el('p', 'leise', 'Kein Zugriff auf das Protokoll.')); return; } d = await a.json(); } catch { return; } /* Nur die Eintraege, die tatsaechlich aus einer Automation stammen -- Anmeldungen und Handarbeit gehoeren hier nicht hin. */ const eintraege = (d.eintraege || d.protokoll || []).filter((e) => ART[e.aktion]).slice(0, 12); ziel.textContent = ''; if (!eintraege.length) { ziel.append(el('p', 'leer-hinweis', 'Noch nichts passiert, was von allein lief.')); return; } const liste = el('div', 'verlauf'); for (const e of eintraege) { const z = el('div', 'verlauf__zeile'); z.append(el('span', 'verlauf__zeit', zeitpunkt(e.zeitpunkt))); z.append(el('span', 'verlauf__was', ART[e.aktion])); z.append(el('span', 'verlauf__wer', [e.wer, e.detail].filter(Boolean).join(' · '))); liste.append(z); } ziel.append(liste); /* Geklappt wird die INNERE Liste, nicht #verlauf -- dessen einziges Kind ist diese Liste, da gaebe es nichts zu verbergen. */ window.klappe?.(liste, $('verlauf-schalter'), $('verlauf-schalter-text')); } /* ---------- Start --------------------------------------------------------- */ const regeln = $('regeln'); for (const r of REGELN) { const k = el('div', 'regel'); k.append(el('h3', 'regel__name', r.name)); k.append(el('p', 'regel__text', r.text)); regeln.append(k); } (async () => { let ich; try { const a = await hole('/workspace/api/ich'); if (!a.ok) { location.assign('/workspace/'); return; } ich = await a.json(); } catch { return; } $('wer').textContent = ich.name + ' · ' + (ich.rolle_name || ich.rolle); await Promise.all([kiZustand(), hinweise(), verlauf()]); })(); })();