Major feature enhancement: Windows PowerShell network scanning integration

- Added Windows PowerShell network scanner with auto-detection and interactive mode
- Implemented dual scanning system (Windows + Linux fallback)
- Added computer management features (rename, delete, duplicate checking)
- Enhanced UI with modern responsive design and Romanian localization
- Added comprehensive Windows-Linux integration with WSL interop
- Improved error handling and user feedback throughout
- Added hot reload for development and comprehensive documentation

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-09-05 17:27:27 +03:00
parent 616763c603
commit acf234c600
13 changed files with 2477 additions and 644 deletions

651
app/static/css/style.css Normal file
View File

@@ -0,0 +1,651 @@
/* Classic Windows-style CSS */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Tahoma, Arial, sans-serif;
font-size: 14px;
background-color: #F5F5F5;
color: #000000;
line-height: 1.4;
}
.container {
background-color: #FFFFFF;
border: 1px solid #808080;
margin: 10px;
height: calc(100vh - 20px);
display: flex;
flex-direction: column;
}
.title-bar {
background: linear-gradient(to bottom, #0A246A 0%, #A6CAF0 100%);
color: white;
padding: 4px 8px;
border-bottom: 1px solid #808080;
font-weight: bold;
}
.title-bar h1 {
font-size: 16px;
margin: 0;
/* Live reload test comment */
}
.toolbar {
background-color: #F0F0F0;
border-bottom: 1px solid #808080;
padding: 4px;
display: flex;
align-items: center;
gap: 4px;
flex-wrap: wrap;
}
.toolbar button {
background-color: #F0F0F0;
border: 1px outset #D4D0C8;
padding: 6px 12px;
font-family: Tahoma, Arial, sans-serif;
font-size: 14px;
cursor: pointer;
min-height: 32px;
}
.toolbar button:hover {
background-color: #E8E5E2;
}
.toolbar button:active {
border: 1px inset #D4D0C8;
}
.network-selector {
display: flex;
align-items: center;
gap: 4px;
margin-left: 8px;
}
.network-selector label {
font-size: 14px;
}
.network-selector select,
.network-selector input {
font-family: Tahoma, Arial, sans-serif;
font-size: 14px;
border: 1px inset #D4D0C8;
padding: 4px;
background-color: white;
min-height: 28px;
}
.main-content {
flex: 1;
padding: 4px;
overflow: auto;
}
.computers-table {
width: 100%;
border-collapse: collapse;
border: 1px solid #808080;
background-color: white;
font-size: 14px;
}
.computers-table th {
background-color: #F0F0F0;
border: 1px solid #808080;
padding: 8px 12px;
text-align: left;
font-weight: bold;
font-size: 14px;
}
.computers-table td {
border: 1px solid #D4D0C8;
padding: 8px 12px;
vertical-align: middle;
}
.computers-table td:first-child {
display: flex;
align-items: center;
gap: 4px;
padding: 6px 8px;
}
.computers-table tr:nth-child(even) {
background-color: #F9F9F9;
}
.computers-table tr:hover {
background-color: #E0E0E0;
}
.status {
font-size: 14px;
padding: 2px 6px;
font-weight: bold;
}
.status.online {
color: #008000;
}
.status.offline {
color: #800000;
}
.status.unknown {
color: #808000;
}
.wake-btn {
background-color: #F0F0F0;
border: 1px outset #D4D0C8;
padding: 6px 8px;
font-family: Tahoma, Arial, sans-serif;
font-size: 16px;
cursor: pointer;
min-height: 32px;
min-width: 32px;
}
.wake-btn:hover {
background-color: #E8E5E2;
}
.wake-btn:active {
border: 1px inset #D4D0C8;
}
.rename-btn {
background-color: #F0F0F0;
border: 1px outset #D4D0C8;
padding: 6px 8px;
font-family: Tahoma, Arial, sans-serif;
font-size: 14px;
cursor: pointer;
min-height: 32px;
min-width: 32px;
}
.rename-btn:hover {
background-color: #E8E5E2;
}
.rename-btn:active {
border: 1px inset #D4D0C8;
}
.delete-btn {
background-color: #F0F0F0;
border: 1px outset #D4D0C8;
padding: 6px 8px;
font-family: Tahoma, Arial, sans-serif;
font-size: 14px;
cursor: pointer;
min-height: 32px;
min-width: 32px;
color: #800000;
}
.delete-btn:hover {
background-color: #F2DEDE;
color: #A94442;
}
.delete-btn:active {
border: 1px inset #D4D0C8;
}
.no-computers {
text-align: center;
padding: 20px;
color: #666;
font-size: 14px;
}
/* Modal Styles */
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
}
.modal-content {
background-color: #F0F0F0;
border: 2px outset #D4D0C8;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 450px;
font-family: Tahoma, Arial, sans-serif;
font-size: 14px;
}
.modal-header {
background: linear-gradient(to bottom, #0A246A 0%, #A6CAF0 100%);
color: white;
padding: 4px 8px;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-header h2 {
font-size: 16px;
font-weight: bold;
margin: 0;
}
.close {
background: none;
border: none;
color: white;
font-size: 16px;
font-weight: bold;
cursor: pointer;
padding: 0;
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
}
.close:hover {
background-color: rgba(255, 255, 255, 0.2);
}
.modal-body {
padding: 8px;
}
.form-group {
margin-bottom: 8px;
}
.form-group label {
display: block;
margin-bottom: 4px;
font-size: 14px;
}
.form-group input {
width: 100%;
border: 1px inset #D4D0C8;
padding: 6px;
font-family: Tahoma, Arial, sans-serif;
font-size: 14px;
background-color: white;
min-height: 28px;
box-sizing: border-box;
}
.form-actions {
background-color: #F0F0F0;
border-top: 1px solid #808080;
padding: 8px;
display: flex;
justify-content: flex-end;
gap: 8px;
}
.form-actions button {
background-color: #F0F0F0;
border: 1px outset #D4D0C8;
padding: 6px 20px;
font-family: Tahoma, Arial, sans-serif;
font-size: 14px;
cursor: pointer;
min-height: 32px;
min-width: 90px;
}
.form-actions button:hover {
background-color: #E8E5E2;
}
.form-actions button:active {
border: 1px inset #D4D0C8;
}
/* Message styles */
.message {
padding: 8px 12px;
margin: 6px;
border: 1px solid;
font-size: 14px;
}
.message.success {
background-color: #DFF0D8;
border-color: #D6E9C6;
color: #3C763D;
}
.message.error {
background-color: #F2DEDE;
border-color: #EBCCD1;
color: #A94442;
}
.message.warning {
background-color: #FCF8E3;
border-color: #FAEBCC;
color: #8A6D3B;
}
/* Scan table */
.scan-table {
width: 100%;
border-collapse: collapse;
border: 1px solid #808080;
margin-top: 8px;
font-size: 14px;
}
.scan-table th {
background-color: #F0F0F0;
border: 1px solid #808080;
padding: 8px;
text-align: left;
font-weight: bold;
}
.scan-table td {
border: 1px solid #D4D0C8;
padding: 8px;
}
.scan-table tr:nth-child(even) {
background-color: #F9F9F9;
}
.scan-table tr:hover {
background-color: #E0E0E0;
}
.add-btn {
background-color: #F0F0F0;
border: 1px outset #D4D0C8;
padding: 4px 8px;
font-family: Tahoma, Arial, sans-serif;
font-size: 14px;
cursor: pointer;
min-height: 28px;
min-width: 28px;
}
.add-btn:hover {
background-color: #E8E5E2;
}
.add-btn:active {
border: 1px inset #D4D0C8;
}
/* Loading animation */
.loading {
display: inline-block;
width: 16px;
height: 16px;
border: 2px solid #f3f3f3;
border-top: 2px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-right: 8px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
#scan-loading {
text-align: center;
padding: 20px;
}
#scan-loading p {
margin-top: 8px;
font-size: 14px;
}
/* Scan controls */
.scan-controls {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 4px;
border: 1px solid #D4D0C8;
background-color: #F9F9F9;
margin-bottom: 4px;
}
.select-all-container {
display: flex;
align-items: center;
gap: 6px;
font-size: 14px;
cursor: pointer;
}
.select-all-container input[type="checkbox"] {
margin: 0;
}
.add-selected-btn {
background-color: #F0F0F0;
border: 1px outset #D4D0C8;
padding: 6px 16px;
font-family: Tahoma, Arial, sans-serif;
font-size: 14px;
cursor: pointer;
min-height: 32px;
}
.add-selected-btn:enabled:hover {
background-color: #E8E5E2;
}
.add-selected-btn:active {
border: 1px inset #D4D0C8;
}
.add-selected-btn:disabled {
background-color: #E8E8E8;
color: #808080;
cursor: not-allowed;
border: 1px solid #D4D0C8;
}
.device-checkbox {
cursor: pointer;
margin: 0;
}
/* Wider scan modal for the additional column */
#scanModal .modal-content {
width: 700px;
}
/* Mobile responsiveness */
@media (max-width: 768px) {
body {
font-size: 16px;
}
.container {
margin: 5px;
height: calc(100vh - 10px);
}
.title-bar h1 {
font-size: 18px;
}
.toolbar {
padding: 8px;
gap: 8px;
}
.toolbar button {
font-size: 16px;
padding: 8px 12px;
min-height: 40px;
flex: 1;
min-width: 0;
}
.network-selector {
flex-direction: column;
align-items: stretch;
gap: 4px;
margin-left: 0;
width: 100%;
}
.network-selector label {
font-size: 16px;
}
.network-selector select,
.network-selector input {
font-size: 16px;
padding: 8px;
min-height: 36px;
}
.computers-table {
font-size: 16px;
}
.computers-table th {
font-size: 16px;
padding: 10px 8px;
}
.computers-table td {
padding: 10px 8px;
}
.computers-table td:first-child {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 6px;
justify-content: flex-start;
}
.status {
font-size: 16px;
padding: 4px 8px;
}
.wake-btn {
font-size: 20px;
padding: 8px 10px;
min-height: 40px;
min-width: 40px;
}
.rename-btn,
.delete-btn {
font-size: 16px;
padding: 8px 10px;
min-height: 40px;
min-width: 40px;
}
.modal-content {
width: calc(100vw - 20px);
max-width: 500px;
font-size: 16px;
}
.modal-header h2 {
font-size: 18px;
}
.form-group label {
font-size: 16px;
margin-bottom: 6px;
}
.form-group input {
font-size: 16px;
padding: 10px;
min-height: 36px;
}
.form-actions button {
font-size: 16px;
padding: 10px 20px;
min-height: 40px;
}
.message {
font-size: 16px;
padding: 10px 12px;
}
.scan-table {
font-size: 16px;
}
.scan-table th,
.scan-table td {
padding: 10px 6px;
}
.add-btn {
font-size: 16px;
padding: 6px 10px;
min-height: 36px;
min-width: 36px;
}
.select-all-container {
font-size: 16px;
}
.add-selected-btn {
font-size: 16px;
padding: 8px 16px;
min-height: 40px;
}
#scanModal .modal-content {
width: calc(100vw - 20px);
max-width: 100%;
}
.no-computers {
font-size: 16px;
padding: 30px 20px;
}
/* Make table horizontally scrollable on small screens */
.main-content {
overflow-x: auto;
}
.computers-table {
min-width: 600px;
}
}

608
app/static/js/app.js Normal file
View File

@@ -0,0 +1,608 @@
// WOL Manager JavaScript
let scanModal, addModal, renameModal;
// Initialize on page load
window.onload = function() {
scanModal = document.getElementById('scanModal');
addModal = document.getElementById('addModal');
renameModal = document.getElementById('renameModal');
refreshComputers();
};
function showMessage(message, type) {
const messageArea = document.getElementById('message-area');
messageArea.innerHTML = `<div class="message ${type}">${message}</div>`;
setTimeout(() => {
messageArea.innerHTML = '';
}, 5000);
}
function refreshComputers() {
fetch('/api/computers')
.then(response => response.json())
.then(computers => {
displayComputers(computers);
})
.catch(error => {
showMessage('Eroare la încărcarea calculatoarelor: ' + error.message, 'error');
});
}
function displayComputers(computers) {
const tbody = document.getElementById('computers-tbody');
const noComputersDiv = document.getElementById('no-computers');
if (computers.length === 0) {
tbody.innerHTML = '';
noComputersDiv.style.display = 'block';
return;
}
noComputersDiv.style.display = 'none';
tbody.innerHTML = computers.map(computer => `
<tr>
<td>
<button class="wake-btn" onclick="wakeComputer('${computer.mac}', '${computer.name}', '${computer.ip || ''}')" title="Trezește calculatorul">
</button>
<button class="rename-btn" onclick="openRenameModal('${computer.name}')" title="Redenumește calculatorul">
📝
</button>
<button class="delete-btn" onclick="deleteComputer('${computer.name}', '${computer.mac}')" title="Șterge calculatorul">
🗑️
</button>
</td>
<td>${computer.name}</td>
<td style="font-family: monospace;">${computer.mac}</td>
<td>${computer.ip || '-'}</td>
<td><span class="status ${computer.status}">${computer.status}</span></td>
</tr>
`).join('');
}
function wakeComputer(mac, name, ip) {
showMessage(`Se trimite magic packet pentru ${name}...`, 'success');
fetch('/api/wake', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({mac: mac, name: name, ip: ip})
})
.then(response => response.json())
.then(result => {
if (result.success) {
showMessage(result.message, 'success');
setTimeout(refreshComputers, 2000);
} else {
showMessage(result.message, 'error');
}
})
.catch(error => {
showMessage('Eroare la trezirea calculatorului: ' + error.message, 'error');
});
}
function wakeAllComputers() {
showMessage('Se trezesc toate calculatoarele...', 'success');
fetch('/api/wake-all', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(data => {
let message = 'Comenzi trimise:<br>';
data.results.forEach(result => {
message += `${result.name}: ${result.result.message}<br>`;
});
showMessage(message, 'success');
setTimeout(refreshComputers, 3000);
})
.catch(error => {
showMessage('Eroare la trezirea calculatoarelor: ' + error.message, 'error');
});
}
function openAddModal() {
addModal.style.display = 'block';
}
function closeAddModal() {
addModal.style.display = 'none';
document.getElementById('computerName').value = '';
document.getElementById('computerMac').value = '';
document.getElementById('computerIp').value = '';
}
function addComputer() {
const name = document.getElementById('computerName').value;
const mac = document.getElementById('computerMac').value;
const ip = document.getElementById('computerIp').value;
if (!name || !mac) {
showMessage('Numele și MAC-ul sunt obligatorii!', 'error');
return;
}
fetch('/api/add', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({name: name, mac: mac, ip: ip})
})
.then(response => response.json())
.then(result => {
if (result.success) {
showMessage(result.message, 'success');
closeAddModal();
refreshComputers();
} else {
showMessage(result.message, 'error');
}
})
.catch(error => {
showMessage('Eroare la adăugarea calculatorului: ' + error.message, 'error');
});
}
function toggleCustomNetwork() {
const select = document.getElementById('networkSelect');
const customInput = document.getElementById('customNetwork');
if (select.value === 'custom') {
customInput.style.display = 'inline';
customInput.focus();
} else {
customInput.style.display = 'none';
}
}
function getSelectedNetwork() {
const select = document.getElementById('networkSelect');
const customInput = document.getElementById('customNetwork');
if (select.value === 'custom') {
return customInput.value.trim();
} else {
return select.value;
}
}
function scanNetwork() {
scanModal.style.display = 'block';
document.getElementById('scan-loading').style.display = 'block';
document.getElementById('scan-results').innerHTML = '';
const network = getSelectedNetwork();
const requestData = network ? { network: network } : {};
fetch('/api/scan', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestData)
})
.then(response => response.json())
.then(result => {
document.getElementById('scan-loading').style.display = 'none';
if (result.success) {
if (result.computers && result.computers.length > 0) {
displayScanResults(result.computers);
if (result.message) {
// Afișează mesajul deasupra tabelului
document.getElementById('scan-results').innerHTML =
`<div class="message" style="background: #fef3c7; color: #92400e; border-color: #fbbf24;">${result.message}</div>` +
document.getElementById('scan-results').innerHTML;
}
} else if (result.message) {
document.getElementById('scan-results').innerHTML =
`<div class="message" style="background: #fef3c7; color: #92400e; border-color: #fbbf24;">${result.message}</div>`;
}
} else {
document.getElementById('scan-results').innerHTML =
`<div class="message error">${result.message}</div>`;
}
})
.catch(error => {
document.getElementById('scan-loading').style.display = 'none';
document.getElementById('scan-results').innerHTML =
`<div class="message error">Eroare la scanare: ${error.message}</div>`;
});
}
function triggerWindowsScan() {
scanModal.style.display = 'block';
document.getElementById('scan-loading').style.display = 'block';
document.getElementById('scan-results').innerHTML = '';
const network = getSelectedNetwork();
const requestData = network ? { network: network } : {};
showMessage('Declanșând scanul Windows...', 'success');
fetch('/api/scan/windows', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestData)
})
.then(response => response.json())
.then(data => {
document.getElementById('scan-loading').style.display = 'none';
if (data.success && data.computers) {
showMessage(data.message || 'Scan Windows completat cu succes!', 'success');
displayScanResults(data.computers);
} else {
let message = data.message || 'Scanul Windows a eșuat';
if (data.instructions) {
message += '<br><br><strong>Instrucțiuni:</strong><br>' + data.instructions;
if (data.commands) {
message += '<br><strong>Comenzi disponibile:</strong>';
data.commands.forEach(cmd => {
message += `<br>• <code>${cmd}</code>`;
});
}
}
showMessage(message, 'error');
document.getElementById('scan-results').innerHTML =
'<div style="padding: 20px; text-align: center; color: #666;">' +
'<h3>Scanul Windows nu poate fi executat din container</h3>' +
'<p>Pentru a scana rețeaua Windows și obține MAC addresses:</p>' +
'<ol style="text-align: left; margin: 20px 0;">' +
'<li>Deschide Command Prompt sau PowerShell ca Administrator pe Windows</li>' +
'<li>Navighează la directorul proiectului WOL Manager</li>' +
'<li>Rulează una din comenzile de mai jos:</li>' +
'</ol>' +
'<div style="background: #f5f5f5; padding: 15px; border-radius: 5px; margin: 10px 0; font-family: monospace;">' +
(data.commands ? data.commands.map(cmd => `<div style="margin: 5px 0;">${cmd}</div>`).join('') :
'scripts\\scan-network.bat') +
'</div>' +
'<p><small>După rularea comenzii, apasă "Scanează Rețeaua" pentru a vedea rezultatele.</small></p>' +
'</div>';
}
})
.catch(error => {
document.getElementById('scan-loading').style.display = 'none';
showMessage('Eroare la declanșarea scanului Windows: ' + error.message, 'error');
console.error('Error:', error);
});
}
function displayScanResults(computers) {
if (computers.length === 0) {
document.getElementById('scan-results').innerHTML =
'<div class="message error">Nu s-au găsit calculatoare în rețea</div>';
return;
}
let html = `
<div class="scan-controls">
<label class="select-all-container">
<input type="checkbox" id="selectAll" onchange="toggleSelectAll()">
<span>Selectează toate</span>
</label>
<button class="add-selected-btn" onclick="addSelectedFromScan()" disabled title="Adaugă calculatoarele selectate">
Adaugă Selectate
</button>
</div>
<table class="scan-table">
<thead>
<tr>
<th>Selectează</th>
<th>IP</th>
<th>MAC</th>
<th>Hostname</th>
<th>Status</th>
<th>Acțiune</th>
</tr>
</thead>
<tbody>
`;
computers.forEach((computer, index) => {
html += `
<tr>
<td>
<input type="checkbox" class="device-checkbox"
data-hostname="${computer.hostname}"
data-mac="${computer.mac}"
data-ip="${computer.ip}"
onchange="updateAddButton()">
</td>
<td>${computer.ip}</td>
<td style="font-family: monospace;">${computer.mac}</td>
<td>${computer.hostname}</td>
<td><span class="status ${computer.status}">${computer.status}</span></td>
<td>
<button class="add-btn" onclick="addFromScan('${computer.hostname}', '${computer.mac}', '${computer.ip}')" title="Adaugă calculatorul">
</button>
</td>
</tr>
`;
});
html += '</tbody></table>';
document.getElementById('scan-results').innerHTML = html;
}
function toggleSelectAll() {
const selectAllCheckbox = document.getElementById('selectAll');
const deviceCheckboxes = document.querySelectorAll('.device-checkbox');
deviceCheckboxes.forEach(checkbox => {
checkbox.checked = selectAllCheckbox.checked;
});
updateAddButton();
}
function updateAddButton() {
const deviceCheckboxes = document.querySelectorAll('.device-checkbox');
const checkedBoxes = document.querySelectorAll('.device-checkbox:checked');
const addButton = document.querySelector('.add-selected-btn');
const selectAllCheckbox = document.getElementById('selectAll');
// Enable/disable the "Add Selected" button
if (addButton) {
addButton.disabled = checkedBoxes.length === 0;
addButton.textContent = checkedBoxes.length > 0 ?
` Adaugă Selectate (${checkedBoxes.length})` : ' Adaugă Selectate';
}
// Update "Select All" checkbox state
if (selectAllCheckbox) {
if (checkedBoxes.length === 0) {
selectAllCheckbox.indeterminate = false;
selectAllCheckbox.checked = false;
} else if (checkedBoxes.length === deviceCheckboxes.length) {
selectAllCheckbox.indeterminate = false;
selectAllCheckbox.checked = true;
} else {
selectAllCheckbox.indeterminate = true;
}
}
}
function addSelectedFromScan() {
const checkedBoxes = document.querySelectorAll('.device-checkbox:checked');
if (checkedBoxes.length === 0) {
showMessage('Nu ai selectat niciun dispozitiv!', 'error');
return;
}
const devices = Array.from(checkedBoxes).map(checkbox => ({
name: checkbox.dataset.hostname,
mac: checkbox.dataset.mac,
ip: checkbox.dataset.ip
}));
// Show progress message
showMessage(`Se adaugă ${devices.length} dispozitive...`, 'success');
// Add devices one by one
let addedCount = 0;
let failedCount = 0;
let duplicateCount = 0;
let failedDevices = [];
let duplicateDevices = [];
const addDevice = (device, index) => {
return fetch('/api/add', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(device)
})
.then(response => response.json())
.then(result => {
if (result.success) {
addedCount++;
} else {
// Verifică dacă este o eroare de duplicat
if (result.message.includes('există deja')) {
duplicateCount++;
duplicateDevices.push(`${device.name} (${result.message})`);
} else {
failedCount++;
failedDevices.push(`${device.name}: ${result.message}`);
}
console.warn(`Failed to add ${device.name}:`, result.message);
}
})
.catch(error => {
failedCount++;
failedDevices.push(`${device.name}: ${error.message}`);
console.error(`Error adding ${device.name}:`, error);
});
};
// Add all devices in parallel
Promise.all(devices.map(addDevice))
.then(() => {
let message = '';
let messageType = 'success';
if (addedCount > 0 && failedCount === 0 && duplicateCount === 0) {
message = `${addedCount} dispozitive adăugate cu succes!`;
} else if (addedCount > 0) {
message = `${addedCount} dispozitive adăugate cu succes`;
if (duplicateCount > 0) {
message += `, ${duplicateCount} duplicate ignorate`;
}
if (failedCount > 0) {
message += `, ${failedCount} eșuate`;
}
message += '.';
if (duplicateCount > 0 || failedCount > 0) {
messageType = 'warning';
}
} else {
if (duplicateCount > 0 && failedCount === 0) {
message = `Toate ${duplicateCount} dispozitivele selectate există deja în sistem.`;
messageType = 'warning';
} else if (duplicateCount > 0) {
message = `${duplicateCount} dispozitive duplicate, ${failedCount} eșuate.`;
messageType = 'error';
} else {
message = `Toate ${failedCount} dispozitivele au eșuat să fie adăugate.`;
messageType = 'error';
}
}
// Dacă există duplicate sau erori, afișează detalii suplimentare în consolă
if (duplicateDevices.length > 0) {
console.info('Dispozitive duplicate:', duplicateDevices);
}
if (failedDevices.length > 0) {
console.warn('Dispozitive eșuate:', failedDevices);
}
showMessage(message, messageType);
if (addedCount > 0) {
closeScanModal();
refreshComputers();
}
});
}
function addFromScan(hostname, mac, ip) {
fetch('/api/add', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({name: hostname, mac: mac, ip: ip})
})
.then(response => response.json())
.then(result => {
if (result.success) {
showMessage(result.message, 'success');
closeScanModal();
refreshComputers();
} else {
showMessage(result.message, 'error');
}
})
.catch(error => {
showMessage('Eroare la adăugarea calculatorului: ' + error.message, 'error');
});
}
function closeScanModal() {
scanModal.style.display = 'none';
}
function openRenameModal(currentName) {
document.getElementById('currentName').value = currentName;
document.getElementById('newName').value = '';
renameModal.style.display = 'block';
document.getElementById('newName').focus();
}
function closeRenameModal() {
renameModal.style.display = 'none';
document.getElementById('currentName').value = '';
document.getElementById('newName').value = '';
}
function performRename() {
const oldName = document.getElementById('currentName').value;
const newName = document.getElementById('newName').value.trim();
if (!newName) {
showMessage('Numele nou nu poate fi gol!', 'error');
return;
}
if (oldName === newName) {
showMessage('Numele nou trebuie să fie diferit de cel actual!', 'error');
return;
}
fetch('/api/rename', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({old_name: oldName, new_name: newName})
})
.then(response => response.json())
.then(result => {
if (result.success) {
showMessage(result.message, 'success');
closeRenameModal();
refreshComputers();
} else {
showMessage(result.message, 'error');
}
})
.catch(error => {
showMessage('Eroare la redenumirea calculatorului: ' + error.message, 'error');
});
}
function deleteComputer(name, mac) {
const displayName = name && name.trim() ? name : `Calculator cu MAC ${mac}`;
if (!confirm(`Sigur vrei să ștergi calculatorul "${displayName}"?`)) {
return;
}
fetch('/api/delete', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({name: name, mac: mac})
})
.then(response => response.json())
.then(result => {
if (result.success) {
showMessage(result.message, 'success');
refreshComputers();
} else {
showMessage(result.message, 'error');
}
})
.catch(error => {
showMessage('Eroare la ștergerea calculatorului: ' + error.message, 'error');
});
}
// Close modals when clicking outside
window.onclick = function(event) {
if (event.target == addModal) {
closeAddModal();
}
if (event.target == scanModal) {
closeScanModal();
}
if (event.target == renameModal) {
closeRenameModal();
}
}
// Allow Enter key to perform rename
document.addEventListener('keydown', function(event) {
if (event.key === 'Enter' && renameModal.style.display === 'block') {
performRename();
}
});