diff --git a/public/api/license/v1/index.php b/public/api/license/v1/index.php index 8534aa0..0dcb86f 100644 --- a/public/api/license/v1/index.php +++ b/public/api/license/v1/index.php @@ -5,12 +5,14 @@ declare(strict_types=1); header('Content-Type: application/json; charset=utf-8'); require_once __DIR__ . '/../../../../src/Core/Db.php'; +require_once __DIR__ . '/../../../../src/Core/Auth.php'; require_once __DIR__ . '/../../../../src/Modules/License/Audit.php'; require_once __DIR__ . '/../../../../src/Modules/License/KeyGen.php'; require_once __DIR__ . '/../../../../src/Modules/License/RateLimiter.php'; require_once __DIR__ . '/../../../../src/Modules/License/LicenseService.php'; use Deploymentcenter\Core\Db; +use Deploymentcenter\Core\Auth; use Deploymentcenter\Modules\License\LicenseService; use Deploymentcenter\Modules\License\RateLimiter; @@ -38,18 +40,36 @@ try { $licenseService = new LicenseService($pdo); + // Validate Endpoint (Public API for clients) if (str_ends_with($uri, '/validate') && $method === 'POST') { $res = $licenseService->validate($inputData, $ip); sendResponse($res); } + // Deactivate Endpoint (AUTHENTICATED ONLY - Security Protection) if (str_ends_with($uri, '/deactivate') && $method === 'POST') { + $authHeader = $_SERVER['HTTP_X_WATCHDOG_KEY'] ?? $_SERVER['HTTP_AUTHORIZATION'] ?? $_SERVER['HTTP_X_LICENSE_KEY'] ?? null; + if ($authHeader && str_starts_with($authHeader, 'Bearer ')) { + $authHeader = substr($authHeader, 7); + } + + $sharedKey = $config['security']['shared_key'] ?? ''; + $isAuthenticated = ($authHeader && hash_equals($sharedKey, $authHeader)) || Auth::isLoggedIn(); + + if (!$isAuthenticated) { + sendResponse([ + 'error' => 'Unauthorized', + 'message' => 'Authentication required for license deactivation. Pass Bearer token or master key.' + ], 401); + } + $res = $licenseService->deactivate($inputData, $ip); sendResponse($res); } + // Status Endpoint if (str_ends_with($uri, '/status') && $method === 'GET') { - sendResponse(['status' => 'ok', 'module' => 'LicenseLabrador', 'version' => '1.0']); + sendResponse(['status' => 'ok', 'module' => 'Lizenzen', 'version' => '1.0']); } sendResponse(['error' => 'Not Found', 'message' => 'Endpoint not found'], 404); diff --git a/public/index.php b/public/index.php index 9cc017b..8e0f198 100644 --- a/public/index.php +++ b/public/index.php @@ -108,16 +108,20 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $ttl = (int)($_POST['ttl'] ?? 168); $notes = trim($_POST['notes'] ?? ''); - if ($slug && $name) { + if ($name) { try { if ($id > 0) { - $stmt = $pdo->prepare('UPDATE dc_projects SET slug = :s, name = :n, default_cache_ttl_hours = :t, notes = :notes WHERE id = :id'); - $stmt->execute([':s' => $slug, ':n' => $name, ':t' => $ttl, ':notes' => $notes, ':id' => $id]); + // Update: Slug is immutable! + $stmt = $pdo->prepare('UPDATE dc_projects SET name = :n, default_cache_ttl_hours = :t, notes = :notes WHERE id = :id'); + $stmt->execute([':n' => $name, ':t' => $ttl, ':notes' => $notes, ':id' => $id]); $msg = "Projekt '{$name}' wurde aktualisiert."; } else { - $stmt = $pdo->prepare('INSERT INTO dc_projects (slug, name, default_cache_ttl_hours, notes) VALUES (:s, :n, :t, :notes)'); - $stmt->execute([':s' => $slug, ':n' => $name, ':t' => $ttl, ':notes' => $notes]); - $msg = "Neues Projekt '{$name}' angelegt."; + // New Project + if ($slug) { + $stmt = $pdo->prepare('INSERT INTO dc_projects (slug, name, default_cache_ttl_hours, notes) VALUES (:s, :n, :t, :notes)'); + $stmt->execute([':s' => $slug, ':n' => $name, ':t' => $ttl, ':notes' => $notes]); + $msg = "Neues Projekt '{$name}' angelegt."; + } } } catch (Throwable $e) { $msg = "Fehler beim Speichern des Projekts: " . $e->getMessage(); @@ -126,6 +130,27 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } } + // Delete Project (with Safety Confirmation) + if ($action === 'delete_project') { + $id = (int)($_POST['project_id'] ?? 0); + $confirmSlug = trim($_POST['confirm_slug'] ?? ''); + + if ($id > 0) { + $stmt = $pdo->prepare('SELECT slug, name FROM dc_projects WHERE id = :id'); + $stmt->execute([':id' => $id]); + $proj = $stmt->fetch(); + + if ($proj && hash_equals($proj['slug'], $confirmSlug)) { + $delStmt = $pdo->prepare('DELETE FROM dc_projects WHERE id = :id'); + $delStmt->execute([':id' => $id]); + $msg = "Projekt '{$proj['name']}' ({$proj['slug']}) wurde unwiderruflich gelöscht."; + } else { + $msg = "Sicherheitsbestätigung fehlgeschlagen! Der eingegebene Slug stimmte nicht überein."; + $msgType = 'danger'; + } + } + } + // Create License Key if ($action === 'create_license') { $productId = (int)($_POST['product_id'] ?? 0); @@ -225,14 +250,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $group = trim($_POST['group'] ?? 'Default'); $os = trim($_POST['os'] ?? 'Linux'); $interval = (int)($_POST['interval'] ?? 60); + $parent = trim($_POST['parent_source'] ?? ''); if ($source) { $stmt = $pdo->prepare(' - INSERT INTO watchdog_monitors (source, instance, type, state, expected_interval_sec, group_key, os, created_utc, updated_utc) - VALUES (:source, "default", :type, "stopped", :interval, :group, :os, NOW(), NOW()) - ON DUPLICATE KEY UPDATE expected_interval_sec = VALUES(expected_interval_sec), group_key = VALUES(group_key) + INSERT INTO watchdog_monitors (source, instance, type, state, expected_interval_sec, group_key, parent_source, os, created_utc, updated_utc) + VALUES (:source, "default", :type, "stopped", :interval, :group, :parent, :os, NOW(), NOW()) + ON DUPLICATE KEY UPDATE expected_interval_sec = VALUES(expected_interval_sec), group_key = VALUES(group_key), parent_source = VALUES(parent_source) '); - $stmt->execute([':source' => $source, ':type' => $type, ':interval' => $interval, ':group' => $group, ':os' => $os]); + $stmt->execute([':source' => $source, ':type' => $type, ':interval' => $interval, ':group' => $group, ':parent' => !empty($parent)?$parent:null, ':os' => $os]); $tokMgr = new TokenManager($pdo); $tok = $tokMgr->createToken($source, "Token for {$source}"); @@ -240,6 +266,43 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } } + // Edit Watchdog Monitor + if ($action === 'edit_watchdog_monitor') { + $oldSource = trim($_POST['old_source'] ?? ''); + $newSource = trim($_POST['new_source'] ?? $oldSource); + $group = trim($_POST['group_key'] ?? 'Default'); + $parent = trim($_POST['parent_source'] ?? ''); + $interval = (int)($_POST['interval'] ?? 60); + $notes = trim($_POST['notes'] ?? ''); + + if ($oldSource && $newSource) { + $monRepo = new MonitorRepo($pdo); + $monRepo->updateMonitor($oldSource, $newSource, 'default', $group, $parent, $notes, null, $interval); + $msg = "Monitor '{$newSource}' wurde aktualisiert."; + } + } + + // Delete Watchdog Monitor + if ($action === 'delete_watchdog_monitor') { + $source = trim($_POST['source'] ?? ''); + if ($source) { + $monRepo = new MonitorRepo($pdo); + $monRepo->deleteMonitor($source, 'default'); + $msg = "Monitor '{$source}' wurde gelöscht."; + } + } + + // Link Entity Parent (Watchdog Hierarchy) + if ($action === 'link_entity') { + $source = trim($_POST['source'] ?? ''); + $parent = trim($_POST['parent_source'] ?? ''); + if ($source) { + $monRepo = new MonitorRepo($pdo); + $monRepo->setParentSource($source, $parent); + $msg = "Hierarchie gespeichert: {$source} → " . ($parent ?: 'Keine (Top Level)'); + } + } + // Create Agent Token (Watchdog) if ($action === 'create_agent_token') { $source = trim($_POST['token_source'] ?? ''); @@ -306,6 +369,47 @@ $auditLogs = $pdo->query('SELECT * FROM license_audit_log ORDER BY created_at DE $monitorRepo = new MonitorRepo($pdo); $monitors = $monitorRepo->getAllMonitors(); +// Flatten Tree for Hierarchy Rendering +function buildMonitorTree(array $monitors): array { + $bySource = []; + foreach ($monitors as $m) { + $bySource[$m['source']] = $m; + $bySource[$m['source']]['children'] = []; + } + + $tree = []; + foreach ($bySource as $source => &$m) { + $parent = $m['parent_source'] ?? null; + if (!empty($parent) && isset($bySource[$parent])) { + $bySource[$parent]['children'][] = &$m; + } else { + $tree[] = &$m; + } + } + unset($m); + + $flat = []; + $walk = function(array $nodes, int $depth = 0) use (&$walk, &$flat) { + $count = count($nodes); + for ($i = 0; $i < $count; $i++) { + $node = $nodes[$i]; + $node['depth'] = $depth; + $node['is_last'] = ($i === $count - 1); + $children = $node['children'] ?? []; + unset($node['children']); + $flat[] = $node; + if (!empty($children)) { + $walk($children, $depth + 1); + } + } + }; + $walk($tree, 0); + + return $flat; +} + +$hierarchicalMonitors = buildMonitorTree($monitors); + $monitorsUp = 0; $monitorsWarning = 0; $monitorsDown = 0; foreach ($monitors as $m) { if ($m['state'] === 'up') $monitorsUp++; @@ -325,61 +429,62 @@ $releases = $updateMgr->getReleases(); $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http'; $host = $_SERVER['HTTP_HOST'] ?? 'dc.mhdf.de'; $baseUrl = $protocol . '://' . $host; -$pubKeyB64 = 'Fehlt (Ed25519 Key Server Default)'; ?> - Deploymentcenter - Unified Operations Center + Deploymentcenter - Operations Hub - +