Files
wol/app/static/js/app.js
Marius Mutu 072553953e Transform rename button to full edit functionality
- Replace rename modal with comprehensive edit modal supporting name, MAC, and IP changes
- Add edit_computer() method with full validation (MAC format, duplicates)
- Create new /api/edit endpoint accepting all computer attributes
- Update frontend JavaScript for multi-field editing with client-side validation
- Rename functions from openRenameModal/performRename to openEditModal/performEdit
- Pre-populate edit form with current values and validate MAC address format

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-07 00:10:21 +03:00

625 lines
22 KiB
JavaScript
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.

// WOL Manager JavaScript
let scanModal, addModal, editModal;
// Initialize on page load
window.onload = function() {
scanModal = document.getElementById('scanModal');
addModal = document.getElementById('addModal');
editModal = document.getElementById('editModal');
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="edit-btn" onclick="openEditModal('${computer.name}', '${computer.mac}', '${computer.ip || ''}')" title="Editează 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 openEditModal(currentName, currentMac, currentIp) {
document.getElementById('editName').value = currentName;
document.getElementById('editName').dataset.originalName = currentName;
document.getElementById('editMac').value = currentMac;
document.getElementById('editIp').value = currentIp || '';
editModal.style.display = 'block';
document.getElementById('editName').focus();
}
function closeEditModal() {
editModal.style.display = 'none';
document.getElementById('editName').value = '';
document.getElementById('editMac').value = '';
document.getElementById('editIp').value = '';
}
function performEdit() {
const oldName = document.getElementById('editName').dataset.originalName || document.getElementById('editName').value;
const newName = document.getElementById('editName').value.trim();
const newMac = document.getElementById('editMac').value.trim();
const newIp = document.getElementById('editIp').value.trim();
if (!newName) {
showMessage('Numele nu poate fi gol!', 'error');
return;
}
if (!newMac) {
showMessage('Adresa MAC nu poate fi goală!', 'error');
return;
}
// Validare simplă pentru formatul MAC-ului
const macPattern = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
if (!macPattern.test(newMac)) {
showMessage('Formatul MAC-ului este invalid! Folosește formatul XX:XX:XX:XX:XX:XX', 'error');
return;
}
fetch('/api/edit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
old_name: oldName,
new_name: newName,
new_mac: newMac,
new_ip: newIp
})
})
.then(response => response.json())
.then(result => {
if (result.success) {
showMessage(result.message, 'success');
closeEditModal();
refreshComputers();
} else {
showMessage(result.message, 'error');
}
})
.catch(error => {
showMessage('Eroare la editarea 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 == editModal) {
closeEditModal();
}
}
// Allow Enter key to perform edit
document.addEventListener('keydown', function(event) {
if (event.key === 'Enter' && editModal.style.display === 'block') {
performEdit();
}
});