updates
This commit is contained in:
268
html/admin.html
268
html/admin.html
@@ -178,6 +178,39 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- SYSTEMD SERVICES SECTION -->
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12">
|
||||||
|
<h3 class="mt-4">SystemD Services</h3>
|
||||||
|
<div id="services-table">
|
||||||
|
<div class="d-flex justify-content-end mb-3">
|
||||||
|
<button type="button" class="btn btn-primary" onclick="refreshServices()">
|
||||||
|
<svg width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16">
|
||||||
|
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z"/>
|
||||||
|
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z"/>
|
||||||
|
</svg>
|
||||||
|
Refresh
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Service</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Enabled</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="services-tbody">
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="text-center">Loading services...</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- toast -->
|
<!-- toast -->
|
||||||
|
|
||||||
@@ -409,6 +442,8 @@ window.onload = function() {
|
|||||||
}
|
}
|
||||||
}); //end AJAx
|
}); //end AJAx
|
||||||
|
|
||||||
|
// Load services on page load
|
||||||
|
refreshServices();
|
||||||
|
|
||||||
} //end window.onload
|
} //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 = '<i class="bi bi-arrow-clockwise"></i> 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 ? '<i class="bi bi-stop"></i> Disable' : '<i class="bi bi-play"></i> 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 = `
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="text-center text-danger">
|
||||||
|
<i class="bi bi-exclamation-triangle"></i> ${message}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = `
|
||||||
|
<strong>Success!</strong><br>
|
||||||
|
Service: ${serviceName}<br>
|
||||||
|
${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 = `
|
||||||
|
<strong>Error!</strong><br>
|
||||||
|
Service: ${serviceName}<br>
|
||||||
|
${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 = `
|
||||||
|
<strong>Request Failed!</strong><br>
|
||||||
|
Service: ${serviceName}<br>
|
||||||
|
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 = `
|
||||||
|
<strong>Success!</strong><br>
|
||||||
|
Service: ${serviceName}<br>
|
||||||
|
${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 = `
|
||||||
|
<strong>Error!</strong><br>
|
||||||
|
Service: ${serviceName}<br>
|
||||||
|
${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 = `
|
||||||
|
<strong>Request Failed!</strong><br>
|
||||||
|
Service: ${serviceName}<br>
|
||||||
|
Error: ${error}
|
||||||
|
`;
|
||||||
|
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample);
|
||||||
|
toastBootstrap.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user