updates
This commit is contained in:
268
html/admin.html
268
html/admin.html
@@ -178,6 +178,39 @@
|
||||
|
||||
</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 -->
|
||||
|
||||
@@ -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 = '<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>
|
||||
|
||||
|
||||
@@ -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