-
+ +
+ + + + +
+
+ ⚠️ Watchdog-Evaluator läuft nicht. + Die Monitor-Zustände sind derzeit nicht verlässlich – ein ausgefallenes System bleibt „up“, bis es sich wieder meldet. + + Letzter Lauf: (). + + Der Evaluator lief noch nie. + +
+ Cron einrichten: * * * * * curl -fsS -H "Authorization: Bearer <SHARED_KEY>" /api/watchdog/v1/evaluate > /dev/null +
+
+
+ + + +
+
@@ -1036,6 +1557,7 @@ $baseUrl = $protocol . '://' . $host;

📁 Neues Projekt anlegen / Bearbeiten

+
@@ -1112,6 +1634,7 @@ $baseUrl = $protocol . '://' . $host; Bitte geben Sie zur Sicherheitsbestätigung den Projekt-Slug ein:

+ @@ -1132,6 +1655,7 @@ $baseUrl = $protocol . '://' . $host;
+ @@ -1186,12 +1710,36 @@ $baseUrl = $protocol . '://' . $host; - +
+ +
+ + + + + + + + + + + + + +
+
@@ -1229,6 +1777,7 @@ $baseUrl = $protocol . '://' . $host; + @@ -1259,6 +1808,7 @@ $baseUrl = $protocol . '://' . $host;

Neuen Lizenzschlüssel generieren

+
@@ -1329,6 +1879,7 @@ $baseUrl = $protocol . '://' . $host;

✏️ Lizenz Vollständig Bearbeiten

+
@@ -1408,6 +1959,7 @@ $baseUrl = $protocol . '://' . $host; + @@ -1416,6 +1968,7 @@ $baseUrl = $protocol . '://' . $host;
+ @@ -1825,6 +2381,7 @@ $baseUrl = $protocol . '://' . $host;

Neues Software-Release veröffentlichen

+
@@ -1879,7 +2436,7 @@ $baseUrl = $protocol . '://' . $host;
-
+
Dev Bugs (Entwicklung)
@@ -1893,65 +2450,150 @@ $baseUrl = $protocol . '://' . $host;
-
💡 Ideen & Gedanken
+
💡 Ideen & Gedanken
+
+
🤖 Von Agenten belegt
+
+
Gelöst / Umgesetzt
-
-
-

🐛 Bugs, Features & Ideen

-
- - - - - - + +
+
+

🤖 Aktive Agenten

+
+
+ +
+ + + Item(s) · Lease bis + +
+
+ + +
+
+

+ 🐛 Bugs, Features & Ideen + Treffer +

+ + + + + + + + + + + + + + + + + + + + Zurücksetzen + + +
+ - - + + @@ -1959,63 +2601,84 @@ $baseUrl = $protocol . '://' . $host; + + + + + + - 💡 FEATURE' : '🐛 BUG'; - $envBadge = match($item['environment']) { + $envBadge = match ((string)$item['environment']) { 'development' => '🔵 DEV', 'production' => '🔴 PROD', - default => '' . strtoupper($item['environment']) . '' + default => '' . e(strtoupper((string)$item['environment'])) . '', }; - $statusBadge = match($item['status']) { + $statusBadge = match ((string)$item['status']) { 'open' => 'OFFEN', 'planned' => 'GEPLANT', 'in_progress' => 'IN BEARBEITUNG', - 'resolved' => 'GELÖST / UMGESETZT', + 'resolved' => 'GELÖST', 'rejected' => 'ABGELEHNT', - default => '' . strtoupper($item['status']) . '' + default => '' . e(strtoupper((string)$item['status'])) . '', }; - $sevBadge = match($item['severity']) { + $sevBadge = match ((string)$item['severity']) { 'idea' => '💡 IDEE', - 'wishlist' => '⭐ WUNSCHLISTE', + 'wishlist' => '⭐ WUNSCH', 'critical' => '🔥 KRITISCH', 'high' => 'HOCH', 'medium' => 'MITTEL', - default => 'NIEDRIG' + default => 'NIEDRIG', }; - $jsonItem = htmlspecialchars(json_encode($item), ENT_QUOTES, 'UTF-8'); + // json_encode liefert eine gueltige JS-Literaldarstellung; das + // vorherige addslashes() konnte an Zeilenumbruechen zerbrechen. + $jsonItem = e((string)json_encode($item, JSON_INVALID_UTF8_SUBSTITUTE)); + $jsTitle = e((string)json_encode((string)$item['title'], JSON_INVALID_UTF8_SUBSTITUTE)); + + $isClaimed = !empty($item['claimed_by']) + && !empty($item['lease_until']) + && strtotime((string)$item['lease_until'] . ' UTC') > time(); ?> - - + + - + @@ -2036,6 +2708,23 @@ $baseUrl = $protocol . '://' . $host;
ID / Typ UmgebungProjekt & TitelBuild / VersionProjekt & TitelBuild Schweregrad Anzahl Status
+ Keine Einträge für diese Filterkombination. +
#
#
- +
- Projekt: - Von: + Projekt: + Von: + + + + 🔒 + + - 📲 Push: + 📲 - 🤖 + 🤖 + + + + 📄 + - 🏷️ + 🏷️
@@ -2024,11 +2687,20 @@ $baseUrl = $protocol . '://' . $host; -
- - - - +
+ + + + + + +
+ + + + +
+ + 1): ?> +
+ + Seite von + ( Einträge) + +
+ 1): ?> + ← Zurück + + + Weiter → + +
+
+
@@ -2044,6 +2733,7 @@ $baseUrl = $protocol . '://' . $host;

➕ Bug, Feature Request oder Idee Erfassen

+
@@ -2179,6 +2869,7 @@ $baseUrl = $protocol . '://' . $host;
+ @@ -2187,6 +2878,7 @@ $baseUrl = $protocol . '://' . $host; WIDERUFEN + @@ -2250,12 +2942,14 @@ $baseUrl = $protocol . '://' . $host;
+
+ @@ -2273,6 +2967,7 @@ $baseUrl = $protocol . '://' . $host;

➕ Neuen Master-Token Erstellen

+
@@ -2313,26 +3008,45 @@ $baseUrl = $protocol . '://' . $host;
- -
- - - - - + +

+ Stärkere Rechte schließen die schwächeren ein: bugtracker:manage + umfasst Lesen und Melden, updateservice:publish auch Lesen. + Für einen Coding-Agenten genügt daher Bugtracker verwalten. +

+
+ ['🌟 Alle Rechte', 'Uneingeschränkter Zugriff'], + 'bugtracker:manage' => ['⚙️ Bugtracker verwalten', 'Übernehmen, kommentieren, schließen — schließt Lesen und Melden ein'], + 'bugtracker:report' => ['🐛 Bugtracker melden', 'Nur Einträge anlegen — schließt Lesen ein'], + 'bugtracker:read' => ['👁️ Bugtracker lesen', 'Nur Abfragen'], + 'watchdog:ping' => ['🛡️ Watchdog Heartbeat', 'Statusmeldungen senden'], + 'watchdog:read' => ['📊 Watchdog lesen', 'Monitore und Ereignisse abfragen'], + 'updateservice:read' => ['📦 Updates prüfen', 'Auf neue Releases prüfen'], + 'updateservice:publish' => ['🚀 Releases veröffentlichen', 'Neue Releases einstellen — schließt Prüfen ein'], + ]; + foreach ($scopeChoices as $value => $labels): ?> + +
+
+ + + + Leer lassen für unbegrenzte Gültigkeit. Abgelaufene Tokens werden ab sofort + tatsächlich abgewiesen — die Spalte existierte zuvor, wurde aber nie geprüft. + +
+
@@ -2345,61 +3059,135 @@ $baseUrl = $protocol . '://' . $host;
-

⚙️ Server & Umgebung Information

+

⚙️ Server & Umgebung

- - + +
- - + +
- - + + +
+
+ + + +
+
+ + +
+
+ +
+ +
+

🩺 Systemzustand

+ + + + + + + + + + + + + + + + + + + + + + +
PrüfungZustandHinweis
Watchdog-Evaluator + + + + + + Letzter Lauf: () + + Noch nie gelaufen — ohne ihn bleiben ausgefallene Monitore „up“. + +
Log-Verzeichnis beschreibbarvar/log/ — Fehlerprotokolle
Konfiguration ausserhalb GitOKconfig/config.php ist per .gitignore ausgeschlossen
+
-

📖 API Documentation (Swagger UI)

+

📖 API-Dokumentation

- Die API-Dokumentation liegt geschützt hinter der Plattform-Authentifizierung. + Die Schnittstelle beschreibt sich selbst. Der frühere fest im Quelltext + hinterlegte Textblock ist durch eine maschinenlesbare OpenAPI-Beschreibung + ersetzt — ein Agent kann sie abrufen und sich daran orientieren, ohne dass + eine Prompt-Vorlage gepflegt werden muss.

-
-=== DEPLOYMENTCENTER REST API SPECIFICATION (OpenAPI 3.0) === - -1. LIZENZEN MODULE: - - POST /api/license/v1/validate (Public) - Body: { "product": "myapp", "license_key": "XXXXX-...", "hardware_id": "HWID-..." } - - - POST /api/license/v1/deactivate (Geschützt - Requires Bearer / Auth) - Body: { "product": "myapp", "license_key": "XXXXX-...", "hardware_id": "HWID-..." } - -2. WATCHDOG MODULE: - - POST /api/watchdog/v1/ping (Header: X-Agent-Token) - Body: { "source": "srv-db-01", "status": "ok", "message": "Heartbeat", "interval": 60 } - - - GET /api/watchdog/v1/status - - GET /api/watchdog/v1/events?limit=50 - -3. UPDATESERVICE MODULE: - - GET /api/updateservice/v1/check?product=myapp&version=1.0.0 - - GET /api/updateservice/v1/releases?product=myapp + +
Kernendpunkte im Überblick + +BUGTRACKER (Token mit bugtracker:report / :read / :manage) + POST /api/bugtracker/v1/report Bug, Feature oder Idee melden + GET /api/bugtracker/v1/projects Projekt-Discovery + GET /api/bugtracker/v1/manage?action=list Filtern, Pagination, updated_since + POST /api/bugtracker/v1/manage?action=next Nächstes Item holen UND übernehmen + POST /api/bugtracker/v1/manage?action=comment&id=42 + POST /api/bugtracker/v1/manage?action=resolve&id=42 + +UPDATESERVICE + GET /api/updateservice/v1/check?product=myapp&version=1.0.0 + POST /api/updateservice/v1/publish (Token mit updateservice:publish) + schliesst automatisch alle Items mit passendem resolved_in_build + +WATCHDOG + POST /api/watchdog/v1/ping Heartbeat (watchdog:ping) + GET /api/watchdog/v1/evaluate Zustandsbewertung (Cron, Shared Key) + +TOKENS + POST /api/tokens/v1/provision Sub-Token aus Master-Token + +SYSTEM + GET /api/health Verfügbarkeit & Schema-Status + GET /api/openapi.json Vollständige Spezifikation
-

🗄️ Datenbank-Migration & Testdaten-Re-Seed

-

- Führt das vollständige DB-Schema aus und stellt sicher, dass alle Tabellen angelegt und migriert sind. +

🗄️ Datenbank-Migration

+

+ Führt ausstehende Migrationen aus. Bereits angewendete Versionen werden in + dc_migrations vermerkt und übersprungen.

- Datenbank-Migration jetzt ausführen (install_db.php) +
+ Hinweis zur Sicherheit: Dieser Endpunkt war zuvor ohne + Anmeldung erreichbar und setzte bei jedem Aufruf das Administrator-Passwort + auf einen fest im Quelltext stehenden Wert zurück. Er verlangt jetzt eine + angemeldete Sitzung oder den Shared Key und legt ein Konto nur an, wenn noch + keines existiert. +
+ Migration jetzt ausführen
@@ -2526,31 +3314,11 @@ $baseUrl = $protocol . '://' . $host; location.hash = 'tab-' + moduleName; } - // Bugtracker Table Filter JS - function filterBugtrackerTable() { - const project = document.getElementById('btFilterProject').value; - const env = document.getElementById('btFilterEnv').value; - const type = document.getElementById('btFilterType').value; - const severity = document.getElementById('btFilterSeverity').value; - const status = document.getElementById('btFilterStatus').value; - const query = (document.getElementById('btFilterSearch').value || '').toLowerCase().trim(); - - document.querySelectorAll('#btTableBody .bt-row').forEach(row => { - const matchProject = (project === 'all' || row.getAttribute('data-project') === project); - const matchEnv = (env === 'all' || row.getAttribute('data-env') === env); - const matchType = (type === 'all' || row.getAttribute('data-type') === type); - const matchSeverity = (severity === 'all' || row.getAttribute('data-severity') === severity); - const matchStatus = (status === 'all' || row.getAttribute('data-status') === status); - const textContent = row.innerText.toLowerCase(); - const matchQuery = (!query || textContent.includes(query)); - - if (matchProject && matchEnv && matchType && matchSeverity && matchStatus && matchQuery) { - row.style.display = ''; - } else { - row.style.display = 'none'; - } - }); - } + // Die frueher hier stehende Funktion filterBugtrackerTable() ist entfallen. + // Sie blendete Zeilen im Browser aus, waehrend der Server ohnehin alle + // Datensaetze gerendert hatte. Gefiltert wird jetzt serverseitig ueber + // GET-Parameter - das skaliert, ueberlebt den Auto-Refresh und laesst + // sich als Link teilen. // Open Bugtracker Edit Modal function openEditBugtrackerModal(item) { @@ -2570,6 +3338,27 @@ $baseUrl = $protocol . '://' . $host; document.getElementById('btEditModal').style.display = 'none'; } + /** + * Maskiert Text fuer die Einbettung in HTML. + * + * Ohne diese Funktion wurden Titel, Beschreibung, Fehlermeldung, + * Stacktrace und Kommentare ungefiltert per innerHTML eingesetzt. + * Da der Report-Endpunkt zudem ohne Token erreichbar war, konnte + * jeder ein Item mit eingebettetem Skript einliefern, das beim + * Oeffnen der Detailansicht in der Admin-Sitzung ausgefuehrt wurde. + */ + function esc(value) { + if (value === null || value === undefined) { + return ''; + } + return String(value) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } + // Open Bugtracker Item Details & Timeline Modal function openBugtrackerModal(itemId) { const modal = document.getElementById('btDetailModal'); @@ -2577,13 +3366,18 @@ $baseUrl = $protocol . '://' . $host; modal.style.display = 'flex'; content.innerHTML = '
⏳ Lade Details & Timeline...
'; - fetch(`api/bugtracker/v1/manage/index.php?id=${itemId}`) + fetch(`api/bugtracker/v1/manage?action=get&id=${encodeURIComponent(itemId)}`, { + headers: { 'Accept': 'application/json' }, + credentials: 'same-origin' + }) .then(r => r.json()) .then(res => { - if (!res.item) { - content.innerHTML = '
Fehler beim Laden des Items.
'; + if (!res || res.status !== 'success' || !res.item) { + const reason = res && res.error ? res.error.message : 'Unbekannter Fehler'; + content.innerHTML = '
Item konnte nicht geladen werden: ' + esc(reason) + '
'; return; } + const item = res.item; const comments = item.comments || []; @@ -2595,46 +3389,76 @@ $baseUrl = $protocol . '://' . $host; commentsHtml += `
- ${c.author} - ${c.created_at} + ${esc(c.author)} + ${esc(c.created_at)} UTC
-
${c.comment}
+ ${c.action_taken ? `
[${esc(c.action_taken)}]
` : ''} +
${esc(c.comment)}
`; }); } + const claimBanner = (item.claimed_by && item.lease_until) + ? `
+ 🤖 In Bearbeitung durch ${esc(item.claimed_by)} bis ${esc(item.lease_until)} UTC +
` + : ''; + + const contextRows = [ + ['Repository', item.repo_url], + ['Branch', item.git_branch], + ['Commit', item.commit_sha], + ['Datei', item.file_path ? item.file_path + (item.line_no ? ':' + item.line_no : '') : null] + ].filter(row => row[1]); + + const contextHtml = contextRows.length === 0 ? '' : ` +
+ Code-Kontext: +
+ ${contextRows.map(row => `
${esc(row[0])}: ${esc(row[1])}
`).join('')} +
+
`; + content.innerHTML = `
- ${item.type.toUpperCase()} - ${item.environment.toUpperCase()} -

#${item.id}: ${item.title}

+ ${esc(item.type).toUpperCase()} + ${esc(item.environment).toUpperCase()} + ${esc(item.status).toUpperCase()} +

#${esc(item.id)}: ${esc(item.title)}

- Projekt: ${item.project_slug} | Build: ${item.build_version || 'v1.0.0'} | Gemeldet von: ${item.created_by} + Projekt: ${esc(item.project_slug)} + | Build: ${esc(item.build_version || 'unbekannt')} + | Gemeldet von: ${esc(item.created_by)} + | ${esc(item.occurrence_count)}x aufgetreten
+ ${claimBanner} + ${item.description ? `
Beschreibung: -
${item.description}
+
${esc(item.description)}
` : ''} + ${contextHtml} + ${item.error_message ? `
Fehlermeldung: -
${item.error_message}
+
${esc(item.error_message)}
` : ''} ${item.stack_trace ? `
Stacktrace: -
${item.stack_trace}
+
${esc(item.stack_trace)}
` : ''} @@ -2644,8 +3468,9 @@ $baseUrl = $protocol . '://' . $host;
${commentsHtml}
+ - +
@@ -2655,7 +3480,7 @@ $baseUrl = $protocol . '://' . $host; `; }) .catch(err => { - content.innerHTML = `
Fehler beim Laden: ${err.message}
`; + content.innerHTML = '
Fehler beim Laden: ' + esc(err.message) + '
'; }); } @@ -2744,6 +3569,7 @@ $baseUrl = $protocol . '://' . $host; setVal('monEditNewSource', m.source || ''); setVal('monEditType', m.type || 'host'); setVal('monEditParent', m.parent_source || ''); + setVal('monEditOs', m.os || ''); setVal('monEditGroup', m.group_key || 'Applications'); setVal('monEditInterval', m.expected_interval_sec || 60); setVal('monEditNotes', m.notes || ''); @@ -2878,6 +3704,54 @@ $baseUrl = $protocol . '://' . $host;
+ + +