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
+
+
+
+
+
+ | Service |
+ Status |
+ Enabled |
+ Actions |
+
+
+
+
+ | 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()
+ ]);
+ }
+}