diff --git a/html/admin.html b/html/admin.html index e875ad0..253659d 100755 --- a/html/admin.html +++ b/html/admin.html @@ -178,6 +178,39 @@ + +
+
+

SystemD Services

+
+
+ +
+ + + + + + + + + + + + + + +
ServiceStatusEnabledActions
Loading services...
+
+
+
+ @@ -409,6 +442,8 @@ window.onload = function() { } }); //end AJAx + // Load services on page load + refreshServices(); } //end window.onload @@ -925,6 +960,239 @@ function updateSondeCoefficient(id, coefficient) { }); } +/* + ____ _ __ __ _ + / ___| ___ _ ____ _(_) ___ ___| \/ | __ _ _ __ __ _ __ _ ___ _ __ ___ ___ _ __ | |_ + \___ \ / _ \ '__\ \ / / |/ __/ _ \ |\/| |/ _` | '_ \ / _` |/ _` |/ _ \ '_ ` _ \ / _ \ '_ \| __| + ___) | __/ | \ V /| | (_| __/ | | | (_| | | | | (_| | (_| | __/ | | | | | __/ | | | |_ + |____/ \___|_| \_/ |_|\___\___|_| |_|\__,_|_| |_|\__,_|\__, |\___|_| |_| |_|\___|_| |_|\__| + |___/ +*/ + +function refreshServices() { + console.log("Refreshing services status"); + + $.ajax({ + url: 'launcher.php?type=get_systemd_services', + dataType: 'json', + method: 'GET', + cache: false, + success: function(response) { + console.log("Services data:", response); + + if (response.success) { + displayServices(response.services); + } else { + showServiceError("Failed to load services: " + response.error); + } + }, + error: function(xhr, status, error) { + console.error('Failed to load services:', error); + showServiceError("Failed to load services: " + error); + } + }); +} + +function displayServices(services) { + const tbody = document.getElementById('services-tbody'); + tbody.innerHTML = ''; + + services.forEach(function(service) { + const row = document.createElement('tr'); + + // Service name + const nameCell = document.createElement('td'); + nameCell.textContent = service.display_name || service.name; + row.appendChild(nameCell); + + // Status + const statusCell = document.createElement('td'); + const statusBadge = document.createElement('span'); + statusBadge.className = `badge ${service.active ? 'bg-success' : 'bg-danger'}`; + statusBadge.textContent = service.active ? 'Running' : 'Stopped'; + statusCell.appendChild(statusBadge); + row.appendChild(statusCell); + + // Enabled + const enabledCell = document.createElement('td'); + const enabledBadge = document.createElement('span'); + enabledBadge.className = `badge ${service.enabled ? 'bg-info' : 'bg-secondary'}`; + enabledBadge.textContent = service.enabled ? 'Enabled' : 'Disabled'; + enabledCell.appendChild(enabledBadge); + row.appendChild(enabledCell); + + // Actions + const actionsCell = document.createElement('td'); + + // Restart button + const restartBtn = document.createElement('button'); + restartBtn.className = 'btn btn-sm btn-warning me-2'; + restartBtn.innerHTML = ' Restart'; + restartBtn.onclick = function() { + restartService(service.name); + }; + actionsCell.appendChild(restartBtn); + + // Enable/Disable button + const toggleBtn = document.createElement('button'); + toggleBtn.className = `btn btn-sm ${service.enabled ? 'btn-danger' : 'btn-success'}`; + toggleBtn.innerHTML = service.enabled ? ' Disable' : ' Enable'; + toggleBtn.onclick = function() { + toggleService(service.name, !service.enabled); + }; + actionsCell.appendChild(toggleBtn); + + row.appendChild(actionsCell); + tbody.appendChild(row); + }); +} + +function showServiceError(message) { + const tbody = document.getElementById('services-tbody'); + tbody.innerHTML = ` + + + ${message} + + + `; +} + +function restartService(serviceName) { + console.log(`Restarting service: ${serviceName}`); + + if (!confirm(`Are you sure you want to restart ${serviceName}?`)) { + return; + } + + const toastLiveExample = document.getElementById('liveToast'); + const toastBody = toastLiveExample.querySelector('.toast-body'); + + $.ajax({ + url: 'launcher.php?type=restart_systemd_service&service=' + encodeURIComponent(serviceName), + dataType: 'json', + method: 'GET', + cache: false, + success: function(response) { + console.log('Service restart response:', response); + + let formattedMessage = ''; + + if (response.success) { + // Success message + toastLiveExample.classList.remove('text-bg-danger'); + toastLiveExample.classList.add('text-bg-success'); + + formattedMessage = ` + Success!
+ Service: ${serviceName}
+ ${response.message || 'Service restarted successfully'} + `; + + // Refresh services after a short delay + setTimeout(refreshServices, 2000); + } else { + // Error message + toastLiveExample.classList.remove('text-bg-success'); + toastLiveExample.classList.add('text-bg-danger'); + + formattedMessage = ` + Error!
+ Service: ${serviceName}
+ ${response.error || 'Unknown error occurred'} + `; + } + + // Update and show toast + toastBody.innerHTML = formattedMessage; + const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample); + toastBootstrap.show(); + }, + error: function(xhr, status, error) { + console.error('Failed to restart service:', error); + + // Show error toast + toastLiveExample.classList.remove('text-bg-success'); + toastLiveExample.classList.add('text-bg-danger'); + toastBody.innerHTML = ` + Request Failed!
+ Service: ${serviceName}
+ Error: ${error} + `; + const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample); + toastBootstrap.show(); + } + }); +} + +function toggleService(serviceName, enable) { + const action = enable ? 'enable' : 'disable'; + console.log(`${action} service: ${serviceName}`); + + if (!confirm(`Are you sure you want to ${action} ${serviceName}?`)) { + return; + } + + const toastLiveExample = document.getElementById('liveToast'); + const toastBody = toastLiveExample.querySelector('.toast-body'); + + $.ajax({ + url: 'launcher.php?type=toggle_systemd_service&service=' + encodeURIComponent(serviceName) + '&enable=' + enable, + dataType: 'json', + method: 'GET', + cache: false, + success: function(response) { + console.log('Service toggle response:', response); + + let formattedMessage = ''; + + if (response.success) { + // Success message + toastLiveExample.classList.remove('text-bg-danger'); + toastLiveExample.classList.add('text-bg-success'); + + formattedMessage = ` + Success!
+ Service: ${serviceName}
+ ${response.message || `Service ${action}d successfully`} + `; + + // Refresh services after a short delay + setTimeout(refreshServices, 2000); + } else { + // Error message + toastLiveExample.classList.remove('text-bg-success'); + toastLiveExample.classList.add('text-bg-danger'); + + formattedMessage = ` + Error!
+ Service: ${serviceName}
+ ${response.error || 'Unknown error occurred'} + `; + } + + // Update and show toast + toastBody.innerHTML = formattedMessage; + const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample); + toastBootstrap.show(); + }, + error: function(xhr, status, error) { + console.error('Failed to toggle service:', error); + + // Show error toast + toastLiveExample.classList.remove('text-bg-success'); + toastLiveExample.classList.add('text-bg-danger'); + toastBody.innerHTML = ` + Request Failed!
+ Service: ${serviceName}
+ Error: ${error} + `; + const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample); + toastBootstrap.show(); + } + }); +} + diff --git a/html/launcher.php b/html/launcher.php index 0a778fb..607d573 100755 --- a/html/launcher.php +++ b/html/launcher.php @@ -1014,3 +1014,199 @@ if ($type == "execute_command") { ]); } } + +/* + ____ _ ____ _ __ __ _ + / ___| _ _ ___| |_ ___ _ __ ___ | _ \ / ___| ___ _ ____ _(_) ___ ___| \/ | __ _ _ __ __ _ __ _ ___ _ __ ___ ___ _ __ | |_ + \___ \| | | / __| __/ _ \ '_ ` _ \| | | | \___ \ / _ \ '__\ \ / / |/ __/ _ \ |\/| |/ _` | '_ \ / _` |/ _` |/ _ \ '_ ` _ \ / _ \ '_ \| __| + ___) | |_| \__ \ || __/ | | | | | |_| | ___) | __/ | \ V /| | (_| __/ | | | (_| | | | | (_| | (_| | __/ | | | | | __/ | | | |_ + |____/ \__, |___/\__\___|_| |_| |_|____/ |____/ \___|_| \_/ |_|\___\___|_| |_|\__,_|_| |_|\__,_|\__, |\___|_| |_| |_|\___|_| |_|\__| + |___/ |___/ +*/ + +// Get systemd services status +if ($type == "get_systemd_services") { + try { + // List of NebuleAir services to monitor + $services = [ + 'nebuleair-npm-data.timer', + 'nebuleair-envea-data.timer', + 'nebuleair-sara-data.timer', + 'nebuleair-bme280-data.timer', + 'nebuleair-mppt-data.timer', + 'nebuleair-db-cleanup-data.timer' + ]; + + $serviceStatus = []; + + foreach ($services as $service) { + // Get service active status + $activeCmd = "systemctl is-active " . escapeshellarg($service) . " 2>/dev/null"; + $activeStatus = trim(shell_exec($activeCmd)); + $isActive = ($activeStatus === 'active'); + + // Get service enabled status + $enabledCmd = "systemctl is-enabled " . escapeshellarg($service) . " 2>/dev/null"; + $enabledStatus = trim(shell_exec($enabledCmd)); + $isEnabled = ($enabledStatus === 'enabled'); + + // Clean up service name for display + $displayName = str_replace(['.timer', 'nebuleair-', '-data'], '', $service); + $displayName = ucfirst(str_replace('-', ' ', $displayName)); + + $serviceStatus[] = [ + 'name' => $service, + 'display_name' => $displayName, + 'active' => $isActive, + 'enabled' => $isEnabled + ]; + } + + echo json_encode([ + 'success' => true, + 'services' => $serviceStatus + ], JSON_PRETTY_PRINT); + + } catch (Exception $e) { + echo json_encode([ + 'success' => false, + 'error' => $e->getMessage() + ]); + } +} + +// Restart a systemd service +if ($type == "restart_systemd_service") { + $service = $_GET['service'] ?? null; + + if (empty($service)) { + echo json_encode([ + 'success' => false, + 'error' => 'No service specified' + ]); + exit; + } + + // Validate service name (security check) + $allowedServices = [ + 'nebuleair-npm-data.timer', + 'nebuleair-envea-data.timer', + 'nebuleair-sara-data.timer', + 'nebuleair-bme280-data.timer', + 'nebuleair-mppt-data.timer', + 'nebuleair-db-cleanup-data.timer' + ]; + + if (!in_array($service, $allowedServices)) { + echo json_encode([ + 'success' => false, + 'error' => 'Invalid service name' + ]); + exit; + } + + try { + // Restart the service + $command = "sudo systemctl restart " . escapeshellarg($service) . " 2>&1"; + $output = shell_exec($command); + + // Check if restart was successful + $statusCmd = "systemctl is-active " . escapeshellarg($service) . " 2>/dev/null"; + $status = trim(shell_exec($statusCmd)); + + if ($status === 'active' || empty($output)) { + echo json_encode([ + 'success' => true, + 'message' => "Service $service restarted successfully" + ]); + } else { + echo json_encode([ + 'success' => false, + 'error' => "Failed to restart service: $output" + ]); + } + + } catch (Exception $e) { + echo json_encode([ + 'success' => false, + 'error' => $e->getMessage() + ]); + } +} + +// Enable/disable a systemd service +if ($type == "toggle_systemd_service") { + $service = $_GET['service'] ?? null; + $enable = $_GET['enable'] ?? null; + + if (empty($service) || $enable === null) { + echo json_encode([ + 'success' => false, + 'error' => 'Missing service name or enable parameter' + ]); + exit; + } + + // Validate service name (security check) + $allowedServices = [ + 'nebuleair-npm-data.timer', + 'nebuleair-envea-data.timer', + 'nebuleair-sara-data.timer', + 'nebuleair-bme280-data.timer', + 'nebuleair-mppt-data.timer', + 'nebuleair-db-cleanup-data.timer' + ]; + + if (!in_array($service, $allowedServices)) { + echo json_encode([ + 'success' => false, + 'error' => 'Invalid service name' + ]); + exit; + } + + try { + $enable = filter_var($enable, FILTER_VALIDATE_BOOLEAN); + $action = $enable ? 'enable' : 'disable'; + + // Enable/disable the service + $command = "sudo systemctl $action " . escapeshellarg($service) . " 2>&1"; + $output = shell_exec($command); + + // If disabling, also stop the service + if (!$enable) { + $stopCommand = "sudo systemctl stop " . escapeshellarg($service) . " 2>&1"; + $stopOutput = shell_exec($stopCommand); + } + + // If enabling, also start the service + if ($enable) { + $startCommand = "sudo systemctl start " . escapeshellarg($service) . " 2>&1"; + $startOutput = shell_exec($startCommand); + } + + // Check if the operation was successful + $statusCmd = "systemctl is-enabled " . escapeshellarg($service) . " 2>/dev/null"; + $status = trim(shell_exec($statusCmd)); + + $expectedStatus = $enable ? 'enabled' : 'disabled'; + + if ($status === $expectedStatus) { + echo json_encode([ + 'success' => true, + 'message' => "Service $service " . ($enable ? 'enabled and started' : 'disabled and stopped') . " successfully" + ]); + } else { + echo json_encode([ + 'success' => false, + 'error' => "Failed to $action service: $output" + ]); + } + + } catch (Exception $e) { + echo json_encode([ + 'success' => false, + 'error' => $e->getMessage() + ]); + } +}