Files
Server_Monitorizare/templates/device_management.html
ske087 42989aa8df Initial server monitoring system with port 80 support
Features:
- Device management and monitoring dashboard
- Remote command execution on devices via port 80
- Auto-update coordination for multiple devices
- Database reset functionality with safety confirmations
- Server logs filtering and dedicated logging interface
- Device status monitoring and management
- SQLite database for comprehensive logging
- Web interface with Bootstrap styling
- Comprehensive error handling and logging

Key components:
- server.py: Main Flask application with all routes
- templates/: Complete web interface templates
- data/database.db: SQLite database for device logs
- UPDATE_SUMMARY.md: Development progress documentation
2025-08-14 15:53:00 +03:00

506 lines
22 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Device Management</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
body {
background-color: #f8f9fa;
font-family: Arial, sans-serif;
}
h1 {
text-align: center;
color: #343a40;
}
.card {
margin-bottom: 20px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.device-card {
border-left: 4px solid #007bff;
}
.status-online {
color: #28a745;
}
.status-offline {
color: #dc3545;
}
.command-buttons {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 15px;
}
.back-button {
margin-bottom: 20px;
text-align: center;
}
.search-container {
margin-bottom: 20px;
}
.search-input {
max-width: 400px;
margin: 0 auto;
}
.loading {
display: none;
}
.result-container {
margin-top: 15px;
padding: 10px;
border-radius: 5px;
display: none;
}
.result-success {
background-color: #d4edda;
border: 1px solid #c3e6cb;
color: #155724;
}
.result-error {
background-color: #f8d7da;
border: 1px solid #f5c6cb;
color: #721c24;
}
</style>
</head>
<body>
<div class="container mt-5">
<h1 class="mb-4">Device Management</h1>
<div class="back-button">
<a href="/dashboard" class="btn btn-primary">Back to Dashboard</a>
<a href="/unique_devices" class="btn btn-secondary">View Unique Devices</a>
<a href="/server_logs" class="btn btn-info" title="View server operations and system logs">
<i class="fas fa-server"></i> Server Logs
</a>
</div>
<!-- Search Filter -->
<div class="search-container">
<div class="search-input">
<input type="text" id="searchInput" class="form-control" placeholder="Search devices by hostname or IP...">
</div>
</div>
<!-- Bulk Operations -->
<div class="card">
<div class="card-header">
<h5>Bulk Operations</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<label for="bulkCommand" class="form-label">Select Command:</label>
<select class="form-select" id="bulkCommand">
<option value="">Select a command...</option>
<option value="sudo apt update">Update Package Lists</option>
<option value="sudo apt upgrade -y">Upgrade Packages</option>
<option value="sudo apt autoremove -y">Remove Unused Packages</option>
<option value="df -h">Check Disk Space</option>
<option value="free -m">Check Memory Usage</option>
<option value="uptime">Check Uptime</option>
<option value="sudo systemctl restart networking">Restart Networking</option>
<option value="sudo reboot">Reboot Device</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label">&nbsp;</label>
<div>
<button class="btn btn-warning" onclick="executeOnAllDevices()">Execute on All Devices</button>
<button class="btn btn-info" onclick="executeOnSelectedDevices()">Execute on Selected</button>
<button class="btn btn-danger" onclick="autoUpdateAllDevices()" title="Auto-update all devices to latest app.py version">
Auto Update All
</button>
<button class="btn btn-dark" onclick="autoUpdateSelectedDevices()" title="Auto-update selected devices">
Auto Update Selected
</button>
</div>
</div>
</div>
<div class="result-container" id="bulkResult"></div>
</div>
</div>
<!-- Device List -->
<div id="deviceContainer">
{% for device in devices %}
<div class="card device-card" data-hostname="{{ device[0] }}" data-ip="{{ device[1] }}">
<div class="card-header">
<div class="row align-items-center">
<div class="col-md-6">
<h6 class="mb-0">
<input type="checkbox" class="device-checkbox me-2" value="{{ device[1] }}">
<strong>{{ device[0] }}</strong> ({{ device[1] }})
</h6>
</div>
<div class="col-md-3">
<small class="text-muted">Last seen: {{ device[2] }}</small>
</div>
<div class="col-md-3 text-end">
<span class="badge bg-secondary status" id="status-{{ device[1] }}">Checking...</span>
<button class="btn btn-sm btn-outline-info" onclick="checkDeviceStatus('{{ device[1] }}')">
Refresh Status
</button>
</div>
</div>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-8">
<select class="form-select command-select" id="command-{{ device[1] }}">
<option value="">Select a command...</option>
<option value="sudo apt update">Update Package Lists</option>
<option value="sudo apt upgrade -y">Upgrade Packages</option>
<option value="sudo apt autoremove -y">Remove Unused Packages</option>
<option value="df -h">Check Disk Space</option>
<option value="free -m">Check Memory Usage</option>
<option value="uptime">Check Uptime</option>
<option value="sudo systemctl restart networking">Restart Networking</option>
<option value="sudo reboot">Reboot Device</option>
</select>
</div>
<div class="col-md-4">
<button class="btn btn-success" onclick="executeCommand('{{ device[1] }}')">
Execute Command
</button>
<button class="btn btn-warning" onclick="autoUpdateDevice('{{ device[1] }}')" title="Auto-update app.py to latest version">
Auto Update
</button>
</div>
</div>
<div class="result-container" id="result-{{ device[1] }}"></div>
<div class="loading" id="loading-{{ device[1] }}">
<div class="spinner-border spinner-border-sm" role="status">
<span class="visually-hidden">Loading...</span>
</div>
Executing command...
</div>
</div>
</div>
{% endfor %}
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Search functionality
document.getElementById('searchInput').addEventListener('keyup', function() {
const filter = this.value.toLowerCase();
const devices = document.querySelectorAll('.device-card');
devices.forEach(device => {
const hostname = device.dataset.hostname.toLowerCase();
const ip = device.dataset.ip.toLowerCase();
if (hostname.includes(filter) || ip.includes(filter)) {
device.style.display = '';
} else {
device.style.display = 'none';
}
});
});
// Check device status
async function checkDeviceStatus(deviceIp) {
const statusElement = document.getElementById(`status-${deviceIp}`);
statusElement.textContent = 'Checking...';
statusElement.className = 'badge bg-secondary';
try {
const response = await fetch(`/device_status/${deviceIp}`);
const result = await response.json();
if (result.success) {
statusElement.textContent = 'Online';
statusElement.className = 'badge bg-success';
} else {
statusElement.textContent = 'Offline';
statusElement.className = 'badge bg-danger';
}
} catch (error) {
statusElement.textContent = 'Error';
statusElement.className = 'badge bg-danger';
}
}
// Execute command on single device
async function executeCommand(deviceIp) {
const commandSelect = document.getElementById(`command-${deviceIp}`);
const command = commandSelect.value;
if (!command) {
alert('Please select a command first');
return;
}
const loadingElement = document.getElementById(`loading-${deviceIp}`);
const resultElement = document.getElementById(`result-${deviceIp}`);
loadingElement.style.display = 'block';
resultElement.style.display = 'none';
try {
const response = await fetch('/execute_command', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
device_ip: deviceIp,
command: command
})
});
const result = await response.json();
loadingElement.style.display = 'none';
resultElement.style.display = 'block';
if (result.success) {
resultElement.className = 'result-container result-success';
resultElement.innerHTML = `
<strong>Success:</strong> ${result.result.message}<br>
<small><strong>Output:</strong><br><pre>${result.result.output}</pre></small>
`;
} else {
resultElement.className = 'result-container result-error';
resultElement.innerHTML = `<strong>Error:</strong> ${result.error}`;
}
} catch (error) {
loadingElement.style.display = 'none';
resultElement.style.display = 'block';
resultElement.className = 'result-container result-error';
resultElement.innerHTML = `<strong>Network Error:</strong> ${error.message}`;
}
}
// Execute command on all devices
async function executeOnAllDevices() {
const command = document.getElementById('bulkCommand').value;
if (!command) {
alert('Please select a command first');
return;
}
const deviceIps = Array.from(document.querySelectorAll('.device-card')).map(card => card.dataset.ip);
await executeBulkCommand(deviceIps, command);
}
// Execute command on selected devices
async function executeOnSelectedDevices() {
const command = document.getElementById('bulkCommand').value;
if (!command) {
alert('Please select a command first');
return;
}
const selectedIps = Array.from(document.querySelectorAll('.device-checkbox:checked')).map(cb => cb.value);
if (selectedIps.length === 0) {
alert('Please select at least one device');
return;
}
await executeBulkCommand(selectedIps, command);
}
// Execute bulk command
async function executeBulkCommand(deviceIps, command) {
const resultElement = document.getElementById('bulkResult');
resultElement.style.display = 'block';
resultElement.className = 'result-container';
resultElement.innerHTML = '<div class="spinner-border spinner-border-sm" role="status"></div> Executing commands...';
try {
const response = await fetch('/execute_command_bulk', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
device_ips: deviceIps,
command: command
})
});
const result = await response.json();
let html = '<h6>Bulk Execution Results:</h6>';
let successCount = 0;
for (const [ip, deviceResult] of Object.entries(result.results)) {
if (deviceResult.success) {
successCount++;
html += `<div class="alert alert-success alert-sm">✓ ${ip}: ${deviceResult.result.message}</div>`;
} else {
html += `<div class="alert alert-danger alert-sm">✗ ${ip}: ${deviceResult.error}</div>`;
}
}
html += `<div class="mt-2"><strong>Summary:</strong> ${successCount}/${deviceIps.length} devices succeeded</div>`;
resultElement.className = 'result-container result-success';
resultElement.innerHTML = html;
} catch (error) {
resultElement.className = 'result-container result-error';
resultElement.innerHTML = `<strong>Network Error:</strong> ${error.message}`;
}
}
// Auto-update functionality
async function autoUpdateDevice(deviceIp) {
const resultElement = document.getElementById(`result-${deviceIp}`);
const loadingElement = document.getElementById(`loading-${deviceIp}`);
try {
// Show loading
loadingElement.style.display = 'block';
resultElement.className = 'result-container';
resultElement.innerHTML = '';
const response = await fetch('/auto_update_devices', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
device_ips: [deviceIp]
})
});
const result = await response.json();
loadingElement.style.display = 'none';
if (result.results && result.results.length > 0) {
const deviceResult = result.results[0];
if (deviceResult.success) {
if (deviceResult.status === 'no_update_needed') {
resultElement.className = 'result-container result-success';
resultElement.innerHTML = `<strong>No Update Needed:</strong> Device is already running version ${deviceResult.new_version || 'latest'}`;
} else {
resultElement.className = 'result-container result-success';
resultElement.innerHTML = `<strong>Update Success:</strong> ${deviceResult.message}<br>
<small>Updated from v${deviceResult.old_version} to v${deviceResult.new_version}</small><br>
<small class="text-warning">Device is restarting...</small>`;
}
} else {
resultElement.className = 'result-container result-error';
resultElement.innerHTML = `<strong>Update Failed:</strong> ${deviceResult.error}`;
}
} else {
resultElement.className = 'result-container result-error';
resultElement.innerHTML = '<strong>Error:</strong> No response from server';
}
} catch (error) {
loadingElement.style.display = 'none';
resultElement.className = 'result-container result-error';
resultElement.innerHTML = `<strong>Network Error:</strong> ${error.message}`;
}
}
async function autoUpdateAllDevices() {
if (!confirm('Are you sure you want to auto-update ALL devices? This will restart all devices.')) {
return;
}
await performBulkAutoUpdate('all');
}
async function autoUpdateSelectedDevices() {
const selectedDevices = Array.from(document.querySelectorAll('.device-checkbox:checked'))
.map(cb => cb.value);
if (selectedDevices.length === 0) {
alert('Please select at least one device');
return;
}
if (!confirm(`Are you sure you want to auto-update ${selectedDevices.length} selected device(s)? This will restart the selected devices.`)) {
return;
}
await performBulkAutoUpdate('selected');
}
async function performBulkAutoUpdate(mode) {
const resultElement = document.getElementById('bulkResult');
try {
// Determine which devices to update
let deviceIps;
if (mode === 'all') {
deviceIps = Array.from(document.querySelectorAll('.device-card'))
.map(card => card.dataset.ip);
} else {
deviceIps = Array.from(document.querySelectorAll('.device-checkbox:checked'))
.map(cb => cb.value);
}
// Show loading state
resultElement.className = 'result-container';
resultElement.innerHTML = `<div class="alert alert-info">
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
Auto-updating ${deviceIps.length} device(s)... This may take several minutes.
</div>`;
const response = await fetch('/auto_update_devices', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
device_ips: deviceIps
})
});
const result = await response.json();
let html = '<h6>Auto-Update Results:</h6>';
let successCount = 0;
for (const deviceResult of result.results) {
if (deviceResult.success) {
successCount++;
if (deviceResult.status === 'no_update_needed') {
html += `<div class="alert alert-info alert-sm"> ${deviceResult.device_ip}: Already up to date</div>`;
} else {
html += `<div class="alert alert-success alert-sm">✓ ${deviceResult.device_ip}: ${deviceResult.message}</div>`;
}
} else {
html += `<div class="alert alert-danger alert-sm">✗ ${deviceResult.device_ip}: ${deviceResult.error}</div>`;
}
}
html += `<div class="mt-2"><strong>Summary:</strong> ${successCount}/${deviceIps.length} devices updated successfully</div>`;
if (successCount > 0) {
html += `<div class="alert alert-warning mt-2"><small>Note: Updated devices are restarting and may be temporarily unavailable.</small></div>`;
}
resultElement.className = 'result-container result-success';
resultElement.innerHTML = html;
} catch (error) {
resultElement.className = 'result-container result-error';
resultElement.innerHTML = `<strong>Network Error:</strong> ${error.message}`;
}
}
// Check status of all devices on page load
document.addEventListener('DOMContentLoaded', function() {
const devices = document.querySelectorAll('.device-card');
devices.forEach(device => {
const ip = device.dataset.ip;
checkDeviceStatus(ip);
});
});
</script>
</body>
</html>