<?php declare(strict_types=1); if (!defined('DASHBOARD_ROOT')) { http_response_code(403); exit('Direct access denied.'); } header('Content-Type: application/json'); if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); exit(json_encode(['success' => false, 'error' => 'POST required.'])); } if (!AuthChain::isAuthenticated()) { http_response_code(401); exit(json_encode(['success' => false, 'error' => 'Not authenticated.'])); } CSRF::enforce(); RBAC::enforce('fleet.provision'); $action = $_POST['action'] ?? ''; $data = $_POST['data'] ?? []; if (!is_array($data)) $data = json_decode($data, true) ?? []; $adminId = $_SESSION['admin_id']; switch ($action) { case 'provision': $nodeId = $data['node_id'] ?? ''; $hostname = $data['hostname'] ?? ''; $wgIp = $data['wg_ip'] ?? ''; $nodeRole = $data['node_role'] ?? ''; if (!$nodeId || !$hostname || !$wgIp || !$nodeRole) { exit(json_encode(['success' => false, 'error' => 'All fields required (node_id, hostname, wg_ip, node_role).'])); } $id = Database::insert( "INSERT INTO nodes (node_id, hostname, wg_ip, node_role, status, created_at, updated_at) VALUES (:nid, :host, :wg, :role, 'provisioning', NOW(), NOW())", [':nid' => $nodeId, ':host' => $hostname, ':wg' => $wgIp, ':role' => $nodeRole] ); Events::record(Events::CAT_FLEET, 'node.provisioned', 'fleet_node', $id, ['node_id' => $nodeId, 'node_role' => $nodeRole], $adminId); AuditLogger::log('node.provisioned', 'fleet_node', $id); echo json_encode(['success' => true, 'id' => $id]); break; case 'decommission': $id = (int)($data['id'] ?? 0); Database::execute("UPDATE nodes SET status = 'manual_offline', updated_at = NOW() WHERE id = :id", [':id' => $id]); Events::record(Events::CAT_FLEET, 'node.decommissioned', 'fleet_node', $id, [], $adminId); AuditLogger::log('node.decommissioned', 'fleet_node', $id); echo json_encode(['success' => true]); break; default: echo json_encode(['success' => false, 'error' => 'Unknown license action.']); } exit; 