Compare commits
6 Commits
acf234c600
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 3fb712ae0b | |||
| 43e997ba47 | |||
| 3c14094650 | |||
| 072553953e | |||
| 86931a091e | |||
| 48db3a11d0 |
1
.env
1
.env
@@ -1,4 +1,5 @@
|
||||
# WOL Manager Environment Configuration
|
||||
WOL_NETWORK_MODE=bridge
|
||||
WOL_EXTERNAL_PORT=5000
|
||||
FLASK_PORT=5000
|
||||
FLASK_DEBUG=true
|
||||
@@ -10,6 +10,9 @@ WOL_NETWORK_MODE=bridge
|
||||
# For Linux with host mode: this setting is ignored
|
||||
WOL_EXTERNAL_PORT=5000
|
||||
|
||||
# Flask internal port (port on which Flask app runs inside container)
|
||||
FLASK_PORT=5000
|
||||
|
||||
# Examples:
|
||||
# Windows Docker Desktop:
|
||||
# WOL_NETWORK_MODE=bridge
|
||||
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
data/network-scan-results.json
|
||||
data/wol-computers.conf
|
||||
102
app/app.py
102
app/app.py
@@ -198,6 +198,76 @@ class WOLManager:
|
||||
|
||||
return {'success': True, 'message': f'Computerul {deleted_name} a fost șters!'}
|
||||
|
||||
def edit_computer(self, old_name, new_name, new_mac, new_ip=None):
|
||||
"""Editează un calculator existent - permite modificarea numelui, MAC-ului și IP-ului"""
|
||||
if not new_name.strip():
|
||||
return {'success': False, 'message': 'Numele nou nu poate fi gol!'}
|
||||
|
||||
if not new_mac.strip():
|
||||
return {'success': False, 'message': 'Adresa MAC nu poate fi goală!'}
|
||||
|
||||
# Validează formatul MAC-ului (XX:XX:XX:XX:XX:XX)
|
||||
import re
|
||||
mac_pattern = r'^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$'
|
||||
if not re.match(mac_pattern, new_mac):
|
||||
return {'success': False, 'message': 'Formatul MAC-ului este invalid! Folosește formatul XX:XX:XX:XX:XX:XX'}
|
||||
|
||||
# Normalizează MAC-ul (lowercase și cu :)
|
||||
new_mac = new_mac.lower().replace('-', ':')
|
||||
|
||||
computers = []
|
||||
found = False
|
||||
old_mac = None
|
||||
|
||||
# Citește toate computerele
|
||||
if os.path.exists(CONFIG_FILE):
|
||||
with open(CONFIG_FILE, 'r') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line and not line.startswith('#'):
|
||||
parts = line.split('|')
|
||||
if len(parts) >= 2:
|
||||
if parts[0] == old_name:
|
||||
old_mac = parts[1]
|
||||
found = True
|
||||
# Verifică dacă noul MAC este diferit și nu există deja
|
||||
if new_mac != old_mac.lower():
|
||||
# Verifică dacă noul MAC există deja la alt computer
|
||||
for check_line in computers:
|
||||
if not check_line.startswith('#') and check_line.strip():
|
||||
check_parts = check_line.split('|')
|
||||
if len(check_parts) >= 2 and check_parts[1].lower() == new_mac:
|
||||
return {'success': False, 'message': f'Adresa MAC {new_mac} este deja folosită de alt computer!'}
|
||||
|
||||
# Actualizează computerul cu noile valori
|
||||
new_ip_value = new_ip.strip() if new_ip else ''
|
||||
computers.append(f"{new_name}|{new_mac}|{new_ip_value}")
|
||||
else:
|
||||
# Verifică dacă noul MAC este folosit de alt computer
|
||||
if parts[1].lower() == new_mac and parts[0] != old_name:
|
||||
return {'success': False, 'message': f'Adresa MAC {new_mac} este deja folosită de computerul "{parts[0]}"!'}
|
||||
computers.append(line)
|
||||
else:
|
||||
computers.append(line)
|
||||
|
||||
if not found:
|
||||
return {'success': False, 'message': f'Computerul "{old_name}" nu a fost găsit!'}
|
||||
|
||||
# Verifică dacă noul nume există deja (doar dacă s-a schimbat numele)
|
||||
if new_name != old_name:
|
||||
for computer_line in computers:
|
||||
if not computer_line.startswith('#') and computer_line.strip():
|
||||
parts = computer_line.split('|')
|
||||
if len(parts) >= 2 and parts[0] == new_name and not computer_line.startswith(f"{new_name}|{new_mac}|"):
|
||||
return {'success': False, 'message': f'Numele "{new_name}" este deja folosit de alt computer!'}
|
||||
|
||||
# Rescrie fișierul
|
||||
with open(CONFIG_FILE, 'w') as f:
|
||||
for computer_line in computers:
|
||||
f.write(computer_line + '\n')
|
||||
|
||||
return {'success': True, 'message': f'Computerul a fost actualizat cu succes!'}
|
||||
|
||||
def scan_network(self, custom_network=None):
|
||||
try:
|
||||
# Încearcă să citească rezultatele scanului Windows mai întâi
|
||||
@@ -290,6 +360,9 @@ class WOLManager:
|
||||
parts = line.split()
|
||||
if len(parts) >= 2:
|
||||
gateway = parts[1]
|
||||
# Skip Docker bridge networks
|
||||
if gateway.startswith('172.17.') or gateway.startswith('172.18.') or gateway.startswith('172.19.') or gateway.startswith('172.20.'):
|
||||
continue
|
||||
# Construiește rețeaua bazată pe gateway
|
||||
network_parts = gateway.split('.')
|
||||
network = f"{network_parts[0]}.{network_parts[1]}.{network_parts[2]}.0/24"
|
||||
@@ -307,6 +380,10 @@ class WOLManager:
|
||||
mac = match.group(2)
|
||||
hostname = line.split()[0] if line.split() else '?'
|
||||
|
||||
# Skip Docker bridge networks
|
||||
if ip.startswith('172.17.') or ip.startswith('172.18.') or ip.startswith('172.19.') or ip.startswith('172.20.'):
|
||||
continue
|
||||
|
||||
# Verifică dacă IP-ul este în rețeaua specificată
|
||||
if custom_network:
|
||||
import ipaddress
|
||||
@@ -322,7 +399,7 @@ class WOLManager:
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
# Autodetectare - adaugă toate intrările ARP
|
||||
# Autodetectare - adaugă toate intrările ARP (fără Docker bridge)
|
||||
scanned.append({
|
||||
'ip': ip,
|
||||
'mac': mac,
|
||||
@@ -338,6 +415,14 @@ class WOLManager:
|
||||
'message': f'Nu s-au găsit dispozitive cu MAC addresses în rețeaua {custom_network}. În Docker Desktop Windows, scanarea automată este limitată. Rulează scanul Windows sau folosește butonul "➕ Adaugă Calculator" pentru a adăuga manual dispozitivele cu IP și MAC cunoscute.'
|
||||
}
|
||||
|
||||
# Dacă nu s-au găsit dispozitive în autodetectare, probabil rulează în Docker
|
||||
if not custom_network and not scanned:
|
||||
return {
|
||||
'success': True,
|
||||
'computers': [],
|
||||
'message': 'Nu s-au găsit dispozitive în rețeaua locală. Aplicația rulează în Docker cu acces limitat la rețea. Pentru rezultate complete, rulează scanul Windows din sistemul host sau specifică manual o rețea CIDR.'
|
||||
}
|
||||
|
||||
return {'success': True, 'computers': scanned}
|
||||
|
||||
wol_manager = WOLManager()
|
||||
@@ -398,6 +483,17 @@ def rename_computer():
|
||||
)
|
||||
return jsonify(result)
|
||||
|
||||
@app.route('/api/edit', methods=['POST'])
|
||||
def edit_computer():
|
||||
data = request.get_json()
|
||||
result = wol_manager.edit_computer(
|
||||
data.get('old_name'),
|
||||
data.get('new_name'),
|
||||
data.get('new_mac'),
|
||||
data.get('new_ip')
|
||||
)
|
||||
return jsonify(result)
|
||||
|
||||
@app.route('/api/delete', methods=['POST'])
|
||||
def delete_computer():
|
||||
data = request.get_json()
|
||||
@@ -485,5 +581,7 @@ def trigger_windows_scan():
|
||||
})
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Get port from environment variable or default to 5000
|
||||
port = int(os.environ.get('FLASK_PORT', 5000))
|
||||
# Enable debug mode for development with hot reload
|
||||
app.run(host='0.0.0.0', port=5000, debug=True, use_reloader=True)
|
||||
app.run(host='0.0.0.0', port=port, debug=True, use_reloader=True)
|
||||
@@ -129,6 +129,23 @@ body {
|
||||
background-color: #E0E0E0;
|
||||
}
|
||||
|
||||
.computer-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.computer-name.online {
|
||||
color: #008000;
|
||||
}
|
||||
|
||||
.computer-name.offline {
|
||||
color: #800000;
|
||||
}
|
||||
|
||||
.computer-name.unknown {
|
||||
color: #808000;
|
||||
}
|
||||
|
||||
/* Keep status class for scan modal compatibility */
|
||||
.status {
|
||||
font-size: 14px;
|
||||
padding: 2px 6px;
|
||||
@@ -166,7 +183,7 @@ body {
|
||||
border: 1px inset #D4D0C8;
|
||||
}
|
||||
|
||||
.rename-btn {
|
||||
.edit-btn {
|
||||
background-color: #F0F0F0;
|
||||
border: 1px outset #D4D0C8;
|
||||
padding: 6px 8px;
|
||||
@@ -177,11 +194,11 @@ body {
|
||||
min-width: 32px;
|
||||
}
|
||||
|
||||
.rename-btn:hover {
|
||||
.edit-btn:hover {
|
||||
background-color: #E8E5E2;
|
||||
}
|
||||
|
||||
.rename-btn:active {
|
||||
.edit-btn:active {
|
||||
border: 1px inset #D4D0C8;
|
||||
}
|
||||
|
||||
@@ -479,9 +496,45 @@ body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Wider scan modal for the additional column */
|
||||
.device-checkbox:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Styles for existing/disabled devices in scan modal */
|
||||
.scan-table tr.device-exists {
|
||||
background-color: #f5f5f5;
|
||||
color: #999;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.scan-table tr.device-exists td {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.scan-table tr.device-exists:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.scan-table tr.device-exists .add-btn {
|
||||
background-color: #e8e8e8;
|
||||
color: #999;
|
||||
cursor: not-allowed;
|
||||
border: 1px solid #d4d4d4;
|
||||
}
|
||||
|
||||
/* Wider scan modal for the additional column with proper scrolling */
|
||||
#scanModal .modal-content {
|
||||
width: 700px;
|
||||
max-height: 90vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#scanModal .modal-body {
|
||||
overflow-y: auto;
|
||||
max-height: 70vh;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Mobile responsiveness */
|
||||
@@ -564,7 +617,7 @@ body {
|
||||
min-width: 40px;
|
||||
}
|
||||
|
||||
.rename-btn,
|
||||
.edit-btn,
|
||||
.delete-btn {
|
||||
font-size: 16px;
|
||||
padding: 8px 10px;
|
||||
@@ -633,6 +686,12 @@ body {
|
||||
#scanModal .modal-content {
|
||||
width: calc(100vw - 20px);
|
||||
max-width: 100%;
|
||||
max-height: 90vh;
|
||||
}
|
||||
|
||||
#scanModal .modal-body {
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.no-computers {
|
||||
@@ -646,6 +705,23 @@ body {
|
||||
}
|
||||
|
||||
.computers-table {
|
||||
min-width: 600px;
|
||||
min-width: 500px;
|
||||
}
|
||||
|
||||
.computer-name {
|
||||
font-size: 15px;
|
||||
max-width: 140px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.computers-table td {
|
||||
padding: 6px 4px;
|
||||
}
|
||||
|
||||
.computers-table td:first-child {
|
||||
padding: 4px 2px;
|
||||
gap: 2px;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
// WOL Manager JavaScript
|
||||
let scanModal, addModal, renameModal;
|
||||
let scanModal, addModal, editModal;
|
||||
|
||||
// Initialize on page load
|
||||
window.onload = function() {
|
||||
scanModal = document.getElementById('scanModal');
|
||||
addModal = document.getElementById('addModal');
|
||||
renameModal = document.getElementById('renameModal');
|
||||
editModal = document.getElementById('editModal');
|
||||
refreshComputers();
|
||||
};
|
||||
|
||||
@@ -40,23 +40,38 @@ function displayComputers(computers) {
|
||||
|
||||
noComputersDiv.style.display = 'none';
|
||||
|
||||
tbody.innerHTML = computers.map(computer => `
|
||||
// Sort computers by IP address
|
||||
const sortedComputers = computers.slice().sort((a, b) => {
|
||||
const ipA = a.ip || '';
|
||||
const ipB = b.ip || '';
|
||||
|
||||
if (!ipA && !ipB) return 0;
|
||||
if (!ipA) return 1;
|
||||
if (!ipB) return -1;
|
||||
|
||||
const parseIP = (ip) => {
|
||||
return ip.split('.').map(num => parseInt(num, 10).toString().padStart(3, '0')).join('.');
|
||||
};
|
||||
|
||||
return parseIP(ipA).localeCompare(parseIP(ipB));
|
||||
});
|
||||
|
||||
tbody.innerHTML = sortedComputers.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 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 class="computer-name ${computer.status}">${computer.name}</td>
|
||||
<td>${computer.ip || '-'}</td>
|
||||
<td><span class="status ${computer.status}">${computer.status}</span></td>
|
||||
<td style="font-family: monospace;">${computer.mac}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
@@ -182,20 +197,23 @@ function scanNetwork() {
|
||||
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 => {
|
||||
// Get current computers list to compare with scan results
|
||||
Promise.all([
|
||||
fetch('/api/computers').then(response => response.json()),
|
||||
fetch('/api/scan', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(requestData)
|
||||
}).then(response => response.json())
|
||||
])
|
||||
.then(([existingComputers, result]) => {
|
||||
document.getElementById('scan-loading').style.display = 'none';
|
||||
|
||||
if (result.success) {
|
||||
if (result.computers && result.computers.length > 0) {
|
||||
displayScanResults(result.computers);
|
||||
displayScanResults(result.computers, existingComputers);
|
||||
if (result.message) {
|
||||
// Afișează mesajul deasupra tabelului
|
||||
document.getElementById('scan-results').innerHTML =
|
||||
@@ -228,20 +246,23 @@ function triggerWindowsScan() {
|
||||
|
||||
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 => {
|
||||
// Get current computers list to compare with scan results
|
||||
Promise.all([
|
||||
fetch('/api/computers').then(response => response.json()),
|
||||
fetch('/api/scan/windows', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(requestData)
|
||||
}).then(response => response.json())
|
||||
])
|
||||
.then(([existingComputers, 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);
|
||||
displayScanResults(data.computers, existingComputers);
|
||||
} else {
|
||||
let message = data.message || 'Scanul Windows a eșuat';
|
||||
|
||||
@@ -281,13 +302,32 @@ function triggerWindowsScan() {
|
||||
});
|
||||
}
|
||||
|
||||
function displayScanResults(computers) {
|
||||
function displayScanResults(computers, existingComputers = []) {
|
||||
if (computers.length === 0) {
|
||||
document.getElementById('scan-results').innerHTML =
|
||||
'<div class="message error">Nu s-au găsit calculatoare în rețea</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort computers by IP address
|
||||
const sortedComputers = computers.slice().sort((a, b) => {
|
||||
const ipA = a.ip || '';
|
||||
const ipB = b.ip || '';
|
||||
|
||||
if (!ipA && !ipB) return 0;
|
||||
if (!ipA) return 1;
|
||||
if (!ipB) return -1;
|
||||
|
||||
const parseIP = (ip) => {
|
||||
return ip.split('.').map(num => parseInt(num, 10).toString().padStart(3, '0')).join('.');
|
||||
};
|
||||
|
||||
return parseIP(ipA).localeCompare(parseIP(ipB));
|
||||
});
|
||||
|
||||
// Create a set of existing MAC addresses for quick lookup (normalize to lowercase)
|
||||
const existingMACs = new Set(existingComputers.map(comp => comp.mac.toLowerCase()));
|
||||
|
||||
let html = `
|
||||
<div class="scan-controls">
|
||||
<label class="select-all-container">
|
||||
@@ -312,23 +352,32 @@ function displayScanResults(computers) {
|
||||
<tbody>
|
||||
`;
|
||||
|
||||
computers.forEach((computer, index) => {
|
||||
sortedComputers.forEach((computer, index) => {
|
||||
const computerMAC = computer.mac.toLowerCase();
|
||||
const deviceExists = existingMACs.has(computerMAC);
|
||||
const rowClass = deviceExists ? 'device-exists' : '';
|
||||
const checkboxDisabled = deviceExists ? 'disabled' : '';
|
||||
const buttonDisabled = deviceExists ? 'disabled' : '';
|
||||
const buttonTitle = deviceExists ? 'Device-ul există deja în sistem' : 'Adaugă calculatorul';
|
||||
|
||||
html += `
|
||||
<tr>
|
||||
<tr class="${rowClass}">
|
||||
<td>
|
||||
<input type="checkbox" class="device-checkbox"
|
||||
data-hostname="${computer.hostname}"
|
||||
data-mac="${computer.mac}"
|
||||
data-ip="${computer.ip}"
|
||||
onchange="updateAddButton()">
|
||||
onchange="updateAddButton()"
|
||||
${checkboxDisabled}>
|
||||
</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 class="add-btn" onclick="addFromScan('${computer.hostname}', '${computer.mac}', '${computer.ip}')"
|
||||
title="${buttonTitle}" ${buttonDisabled}>
|
||||
${deviceExists ? '✓' : '➕'}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -341,7 +390,7 @@ function displayScanResults(computers) {
|
||||
|
||||
function toggleSelectAll() {
|
||||
const selectAllCheckbox = document.getElementById('selectAll');
|
||||
const deviceCheckboxes = document.querySelectorAll('.device-checkbox');
|
||||
const deviceCheckboxes = document.querySelectorAll('.device-checkbox:not(:disabled)');
|
||||
|
||||
deviceCheckboxes.forEach(checkbox => {
|
||||
checkbox.checked = selectAllCheckbox.checked;
|
||||
@@ -351,8 +400,8 @@ function toggleSelectAll() {
|
||||
}
|
||||
|
||||
function updateAddButton() {
|
||||
const deviceCheckboxes = document.querySelectorAll('.device-checkbox');
|
||||
const checkedBoxes = document.querySelectorAll('.device-checkbox:checked');
|
||||
const deviceCheckboxes = document.querySelectorAll('.device-checkbox:not(:disabled)');
|
||||
const checkedBoxes = document.querySelectorAll('.device-checkbox:checked:not(:disabled)');
|
||||
const addButton = document.querySelector('.add-selected-btn');
|
||||
const selectAllCheckbox = document.getElementById('selectAll');
|
||||
|
||||
@@ -363,7 +412,7 @@ function updateAddButton() {
|
||||
`➕ Adaugă Selectate (${checkedBoxes.length})` : '➕ Adaugă Selectate';
|
||||
}
|
||||
|
||||
// Update "Select All" checkbox state
|
||||
// Update "Select All" checkbox state (only consider enabled checkboxes)
|
||||
if (selectAllCheckbox) {
|
||||
if (checkedBoxes.length === 0) {
|
||||
selectAllCheckbox.indeterminate = false;
|
||||
@@ -378,7 +427,7 @@ function updateAddButton() {
|
||||
}
|
||||
|
||||
function addSelectedFromScan() {
|
||||
const checkedBoxes = document.querySelectorAll('.device-checkbox:checked');
|
||||
const checkedBoxes = document.querySelectorAll('.device-checkbox:checked:not(:disabled)');
|
||||
|
||||
if (checkedBoxes.length === 0) {
|
||||
showMessage('Nu ai selectat niciun dispozitiv!', 'error');
|
||||
@@ -484,6 +533,12 @@ function addSelectedFromScan() {
|
||||
}
|
||||
|
||||
function addFromScan(hostname, mac, ip) {
|
||||
// Check if this is called from a disabled button (device already exists)
|
||||
if (event && event.target && event.target.hasAttribute('disabled')) {
|
||||
showMessage('Device-ul există deja în sistem!', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('/api/add', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -510,52 +565,69 @@ 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 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 closeRenameModal() {
|
||||
renameModal.style.display = 'none';
|
||||
document.getElementById('currentName').value = '';
|
||||
document.getElementById('newName').value = '';
|
||||
function closeEditModal() {
|
||||
editModal.style.display = 'none';
|
||||
document.getElementById('editName').value = '';
|
||||
document.getElementById('editMac').value = '';
|
||||
document.getElementById('editIp').value = '';
|
||||
}
|
||||
|
||||
function performRename() {
|
||||
const oldName = document.getElementById('currentName').value;
|
||||
const newName = document.getElementById('newName').value.trim();
|
||||
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 nou nu poate fi gol!', 'error');
|
||||
showMessage('Numele nu poate fi gol!', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (oldName === newName) {
|
||||
showMessage('Numele nou trebuie să fie diferit de cel actual!', 'error');
|
||||
if (!newMac) {
|
||||
showMessage('Adresa MAC nu poate fi goală!', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('/api/rename', {
|
||||
// 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})
|
||||
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');
|
||||
closeRenameModal();
|
||||
closeEditModal();
|
||||
refreshComputers();
|
||||
} else {
|
||||
showMessage(result.message, 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showMessage('Eroare la redenumirea calculatorului: ' + error.message, 'error');
|
||||
showMessage('Eroare la editarea calculatorului: ' + error.message, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -595,14 +667,14 @@ window.onclick = function(event) {
|
||||
if (event.target == scanModal) {
|
||||
closeScanModal();
|
||||
}
|
||||
if (event.target == renameModal) {
|
||||
closeRenameModal();
|
||||
if (event.target == editModal) {
|
||||
closeEditModal();
|
||||
}
|
||||
}
|
||||
|
||||
// Allow Enter key to perform rename
|
||||
// Allow Enter key to perform edit
|
||||
document.addEventListener('keydown', function(event) {
|
||||
if (event.key === 'Enter' && renameModal.style.display === 'block') {
|
||||
performRename();
|
||||
if (event.key === 'Enter' && editModal.style.display === 'block') {
|
||||
performEdit();
|
||||
}
|
||||
});
|
||||
@@ -40,9 +40,8 @@
|
||||
<tr>
|
||||
<th>Acțiuni</th>
|
||||
<th>Nume Calculator</th>
|
||||
<th>Adresa MAC</th>
|
||||
<th>Adresa IP</th>
|
||||
<th>Status</th>
|
||||
<th>Adresa MAC</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="computers-tbody">
|
||||
@@ -100,26 +99,30 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal pentru redenumirea calculatoarelor -->
|
||||
<div id="renameModal" class="modal">
|
||||
<!-- Modal pentru editarea calculatoarelor -->
|
||||
<div id="editModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2>Redenumește Calculator</h2>
|
||||
<span class="close" onclick="closeRenameModal()">×</span>
|
||||
<h2>Editează Calculator</h2>
|
||||
<span class="close" onclick="closeEditModal()">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="currentName">Nume Actual:</label>
|
||||
<input type="text" id="currentName" readonly>
|
||||
<label for="editName">Nume Calculator:</label>
|
||||
<input type="text" id="editName" placeholder="ex: PC Birou">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="newName">Nume Nou:</label>
|
||||
<input type="text" id="newName" placeholder="Introdu noul nume">
|
||||
<label for="editMac">Adresa MAC:</label>
|
||||
<input type="text" id="editMac" placeholder="ex: 00:11:22:33:44:55">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="editIp">IP (opțional):</label>
|
||||
<input type="text" id="editIp" placeholder="ex: 192.168.1.100">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="button" onclick="closeRenameModal()">Anulează</button>
|
||||
<button type="button" onclick="performRename()">Redenumește</button>
|
||||
<button type="button" onclick="closeEditModal()">Anulează</button>
|
||||
<button type="button" onclick="performEdit()">Salvează</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
{
|
||||
"message": "Scanare completata cu succes. Gasite 13 dispozitive.",
|
||||
"networks_scanned": "10.0.20.0/24",
|
||||
"timestamp": "2025-09-05T17:03:30.367Z",
|
||||
"computers": [
|
||||
{
|
||||
"status": "online",
|
||||
"ip": "10.0.20.1",
|
||||
"mac": "6c:5a:b0:20:ff:7c",
|
||||
"hostname": ""
|
||||
},
|
||||
{
|
||||
"status": "online",
|
||||
"ip": "10.0.20.36",
|
||||
"mac": "9c:6b:00:18:5f:23",
|
||||
"hostname": ""
|
||||
},
|
||||
{
|
||||
"status": "online",
|
||||
"ip": "10.0.20.122",
|
||||
"mac": "d4:3d:7e:de:05:f7",
|
||||
"hostname": "svnroa"
|
||||
},
|
||||
{
|
||||
"status": "online",
|
||||
"ip": "10.0.20.144",
|
||||
"mac": "",
|
||||
"hostname": "host.docker.internal"
|
||||
},
|
||||
{
|
||||
"status": "online",
|
||||
"ip": "10.0.20.161",
|
||||
"mac": "de:ad:be:ef:10:04",
|
||||
"hostname": ""
|
||||
},
|
||||
{
|
||||
"status": "online",
|
||||
"ip": "10.0.20.162",
|
||||
"mac": "bc:24:11:05:58:5c",
|
||||
"hostname": ""
|
||||
},
|
||||
{
|
||||
"status": "online",
|
||||
"ip": "10.0.20.163",
|
||||
"mac": "bc:24:11:3e:9d:70",
|
||||
"hostname": ""
|
||||
},
|
||||
{
|
||||
"status": "online",
|
||||
"ip": "10.0.20.164",
|
||||
"mac": "bc:24:11:90:b4:04",
|
||||
"hostname": ""
|
||||
},
|
||||
{
|
||||
"status": "online",
|
||||
"ip": "10.0.20.165",
|
||||
"mac": "bc:24:11:da:c2:63",
|
||||
"hostname": ""
|
||||
},
|
||||
{
|
||||
"status": "online",
|
||||
"ip": "10.0.20.170",
|
||||
"mac": "de:ad:be:ef:10:00",
|
||||
"hostname": ""
|
||||
},
|
||||
{
|
||||
"status": "online",
|
||||
"ip": "10.0.20.200",
|
||||
"mac": "fc:3f:db:0a:0d:d8",
|
||||
"hostname": ""
|
||||
},
|
||||
{
|
||||
"status": "online",
|
||||
"ip": "10.0.20.201",
|
||||
"mac": "58:47:ca:7d:51:3b",
|
||||
"hostname": ""
|
||||
},
|
||||
{
|
||||
"status": "online",
|
||||
"ip": "10.0.20.210",
|
||||
"mac": "b0:a7:b9:4c:28:2c",
|
||||
"hostname": ""
|
||||
}
|
||||
],
|
||||
"success": true
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
# Format: name|mac|ip
|
||||
ROUTER|6c:5a:b0:20:ff:7c|10.0.20.1
|
||||
ROA|9c:6b:00:18:5f:23|10.0.20.36
|
||||
SVNROA|d4:3d:7e:de:05:f7|10.0.20.122
|
||||
PVEMINI-1|de:ad:be:ef:10:04|10.0.20.161
|
||||
PVEMINI-2|bc:24:11:3e:9d:70|10.0.20.163
|
||||
PVEMINI-3|bc:24:11:05:58:5c|10.0.20.162
|
||||
PVEMINI-4|bc:24:11:da:c2:63|10.0.20.165
|
||||
PVEMINI-5|bc:24:11:90:b4:04|10.0.20.164
|
||||
PVEMINI-PORTAINER|de:ad:be:ef:10:00|10.0.20.170
|
||||
PVE1|fc:3f:db:0a:0d:d8|10.0.20.200
|
||||
PVEMINI|74:38:b7:fd:0b:b6|10.0.20.221
|
||||
|c2:f5:a3:17:a2:dc|10.0.20.229
|
||||
@@ -15,6 +15,7 @@ services:
|
||||
- PYTHONUNBUFFERED=1
|
||||
- WSL_INTEROP=${WSL_INTEROP}
|
||||
- FLASK_DEBUG=${FLASK_DEBUG:-false}
|
||||
- FLASK_PORT=${FLASK_PORT:-5000}
|
||||
network_mode: "${WOL_NETWORK_MODE:-bridge}"
|
||||
privileged: true
|
||||
cap_add:
|
||||
|
||||
@@ -30,25 +30,34 @@ function Get-LocalNetworks {
|
||||
$networkAddr = $adapter.IPAddress
|
||||
$prefixLength = $adapter.PrefixLength
|
||||
|
||||
# Simple network calculation for /24 networks (most common)
|
||||
if ($prefixLength -eq 24) {
|
||||
$parts = $networkAddr.Split('.')
|
||||
if ($parts.Length -eq 4) {
|
||||
# Network calculation for different prefix lengths
|
||||
$parts = $networkAddr.Split('.')
|
||||
if ($parts.Length -eq 4) {
|
||||
if ($prefixLength -eq 24) {
|
||||
$networkAddress = "$($parts[0]).$($parts[1]).$($parts[2]).0"
|
||||
$networks += "$networkAddress/24"
|
||||
Write-Log "Found network: $networkAddress/24 from adapter $($adapter.IPAddress)"
|
||||
}
|
||||
}
|
||||
elseif ($prefixLength -eq 16) {
|
||||
$parts = $networkAddr.Split('.')
|
||||
if ($parts.Length -eq 4) {
|
||||
elseif ($prefixLength -eq 20) {
|
||||
# For /20, calculate the network base (e.g., 10.0.20.144/20 -> 10.0.16.0/20)
|
||||
$thirdOctet = [int]$parts[2]
|
||||
$networkThird = $thirdOctet - ($thirdOctet % 16) # Round down to nearest /20 boundary
|
||||
$networkAddress = "$($parts[0]).$($parts[1]).$networkThird.0"
|
||||
$networks += "$networkAddress/20"
|
||||
Write-Log "Found network: $networkAddress/20 from adapter $($adapter.IPAddress)"
|
||||
}
|
||||
elseif ($prefixLength -eq 16) {
|
||||
$networkAddress = "$($parts[0]).$($parts[1]).0.0"
|
||||
$networks += "$networkAddress/16"
|
||||
Write-Log "Found network: $networkAddress/16 from adapter $($adapter.IPAddress)"
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Log "Skipping unsupported prefix length /$prefixLength for $networkAddr"
|
||||
else {
|
||||
Write-Log "Unsupported prefix length /$prefixLength for $networkAddr, trying /24"
|
||||
# Fallback to /24 for unknown prefix lengths
|
||||
$networkAddress = "$($parts[0]).$($parts[1]).$($parts[2]).0"
|
||||
$networks += "$networkAddress/24"
|
||||
Write-Log "Fallback network: $networkAddress/24 from adapter $($adapter.IPAddress)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +83,21 @@ function Show-NetworkSelectionMenu {
|
||||
foreach ($adapter in $adapters) {
|
||||
$parts = $adapter.IPAddress.Split('.')
|
||||
if ($parts.Length -eq 4) {
|
||||
$networkAddr = "$($parts[0]).$($parts[1]).$($parts[2]).0/24"
|
||||
$prefixLength = $adapter.PrefixLength
|
||||
|
||||
if ($prefixLength -eq 24) {
|
||||
$networkAddr = "$($parts[0]).$($parts[1]).$($parts[2]).0/24"
|
||||
} elseif ($prefixLength -eq 20) {
|
||||
$thirdOctet = [int]$parts[2]
|
||||
$networkThird = $thirdOctet - ($thirdOctet % 16)
|
||||
$networkAddr = "$($parts[0]).$($parts[1]).$networkThird.0/20"
|
||||
} elseif ($prefixLength -eq 16) {
|
||||
$networkAddr = "$($parts[0]).$($parts[1]).0.0/16"
|
||||
} else {
|
||||
# Fallback to /24
|
||||
$networkAddr = "$($parts[0]).$($parts[1]).$($parts[2]).0/24"
|
||||
}
|
||||
|
||||
if ($detectedNetworks -notcontains $networkAddr) {
|
||||
$detectedNetworks += $networkAddr
|
||||
}
|
||||
@@ -170,6 +193,81 @@ function Test-NetworkAddress {
|
||||
}
|
||||
}
|
||||
|
||||
function Get-LocalHostMacAddress {
|
||||
param([string]$IPAddress)
|
||||
|
||||
Write-Log "Attempting to find MAC address for potential local host: $IPAddress"
|
||||
|
||||
try {
|
||||
# Method 1: Check if this IP matches any of the local network adapters
|
||||
$localAdapters = Get-NetIPAddress -AddressFamily IPv4 | Where-Object {
|
||||
$_.IPAddress -ne "127.0.0.1" -and ($_.PrefixOrigin -eq "Dhcp" -or $_.PrefixOrigin -eq "Manual")
|
||||
}
|
||||
|
||||
foreach ($adapter in $localAdapters) {
|
||||
if ($adapter.IPAddress -eq $IPAddress) {
|
||||
Write-Log "Found matching local IP address: $IPAddress on interface $($adapter.InterfaceIndex)"
|
||||
|
||||
# Get the corresponding network adapter
|
||||
$netAdapter = Get-NetAdapter -InterfaceIndex $adapter.InterfaceIndex -ErrorAction SilentlyContinue
|
||||
if ($netAdapter -and $netAdapter.MacAddress) {
|
||||
$cleanMac = $netAdapter.MacAddress.Replace('-', ':').ToLower()
|
||||
Write-Log "Found local MAC address: $cleanMac for IP $IPAddress"
|
||||
return $cleanMac
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Method 2: Use WMI to find network adapters with the matching IP
|
||||
$wmiAdapters = Get-WmiObject -Class Win32_NetworkAdapterConfiguration | Where-Object {
|
||||
$_.IPEnabled -eq $true -and $_.IPAddress
|
||||
}
|
||||
|
||||
foreach ($wmiAdapter in $wmiAdapters) {
|
||||
if ($wmiAdapter.IPAddress -contains $IPAddress) {
|
||||
if ($wmiAdapter.MACAddress) {
|
||||
$cleanMac = $wmiAdapter.MACAddress.Replace('-', ':').ToLower()
|
||||
Write-Log "Found local MAC via WMI: $cleanMac for IP $IPAddress"
|
||||
return $cleanMac
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Method 3: Check if IP is in the same subnet and try to find the default adapter
|
||||
$localIPs = $localAdapters | ForEach-Object { $_.IPAddress }
|
||||
foreach ($localIP in $localIPs) {
|
||||
$localParts = $localIP -split '\.'
|
||||
$targetParts = $IPAddress -split '\.'
|
||||
|
||||
# Simple check for same /24 network
|
||||
if ($localParts.Length -eq 4 -and $targetParts.Length -eq 4) {
|
||||
if ($localParts[0] -eq $targetParts[0] -and
|
||||
$localParts[1] -eq $targetParts[1] -and
|
||||
$localParts[2] -eq $targetParts[2]) {
|
||||
|
||||
# This IP is in the same subnet, find the adapter for the local IP
|
||||
$matchingAdapter = $localAdapters | Where-Object { $_.IPAddress -eq $localIP }
|
||||
if ($matchingAdapter) {
|
||||
$netAdapter = Get-NetAdapter -InterfaceIndex $matchingAdapter.InterfaceIndex -ErrorAction SilentlyContinue
|
||||
if ($netAdapter -and $netAdapter.MacAddress) {
|
||||
$cleanMac = $netAdapter.MacAddress.Replace('-', ':').ToLower()
|
||||
Write-Log "Found MAC for same subnet adapter: $cleanMac (local IP: $localIP, target: $IPAddress)"
|
||||
return $cleanMac
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Log "Could not determine MAC address for IP: $IPAddress"
|
||||
return ""
|
||||
}
|
||||
catch {
|
||||
Write-Log "Error finding local MAC address: $($_.Exception.Message)"
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
function Get-NetworkDevices {
|
||||
param([string]$NetworkCIDR)
|
||||
|
||||
@@ -188,22 +286,107 @@ function Get-NetworkDevices {
|
||||
if ($prefixLength -eq 24) {
|
||||
$startIP = 1
|
||||
$endIP = 254
|
||||
} elseif ($prefixLength -eq 20) {
|
||||
# For /20 networks (like 10.0.20.0/20), scan the specific /24 subnet where the network is
|
||||
$thirdOctet = [int]$networkParts[2]
|
||||
$baseIP = "$($networkParts[0]).$($networkParts[1]).$thirdOctet"
|
||||
$startIP = 1
|
||||
$endIP = 254
|
||||
Write-Log "Scanning /20 network as /24 subnet: $baseIP.0/24"
|
||||
} elseif ($prefixLength -eq 16) {
|
||||
# For /16 networks, we need to scan multiple /24 subnets
|
||||
# For now, just scan the specific /24 subnet where the network address is
|
||||
$thirdOctet = [int]$networkParts[2]
|
||||
$baseIP = "$($networkParts[0]).$($networkParts[1]).$thirdOctet"
|
||||
$startIP = 1
|
||||
$endIP = 254
|
||||
Write-Log "Scanning /16 network as /24 subnet: $baseIP.0/24"
|
||||
} else {
|
||||
Write-Log "Only /24 networks are supported in this version"
|
||||
Write-Log "Unsupported prefix length /$prefixLength. Supported: /16, /20, /24"
|
||||
return @()
|
||||
}
|
||||
|
||||
$devices = @()
|
||||
$aliveIPs = @()
|
||||
|
||||
# Simplified ping sweep with progress indication
|
||||
Write-Log "Starting ping sweep for $($endIP - $startIP + 1) addresses..."
|
||||
|
||||
# Use the configurable batch size parameter
|
||||
$totalIPs = $endIP - $startIP + 1
|
||||
$processed = 0
|
||||
|
||||
Write-Log "Scanning $totalIPs addresses in batches of $BatchSize..."
|
||||
# STEP 1: Get ARP table first to find devices that might not respond to ping
|
||||
Write-Log "Step 1: Retrieving devices from ARP table..."
|
||||
$arpEntries = @{}
|
||||
$arpDevices = @{}
|
||||
|
||||
try {
|
||||
# Use Get-NetNeighbor for Windows 8/Server 2012 and later
|
||||
$neighbors = Get-NetNeighbor -AddressFamily IPv4 | Where-Object { $_.State -ne "Unreachable" }
|
||||
foreach ($neighbor in $neighbors) {
|
||||
if ($neighbor.LinkLayerAddress -and $neighbor.LinkLayerAddress -ne "00-00-00-00-00-00") {
|
||||
$cleanMac = $neighbor.LinkLayerAddress.Replace('-', ':').ToLower()
|
||||
$arpEntries[$neighbor.IPAddress] = $cleanMac
|
||||
|
||||
# Check if this IP is in our scan range
|
||||
$ipParts = $neighbor.IPAddress -split '\.'
|
||||
if ($ipParts.Length -eq 4) {
|
||||
$currentBaseIP = "$($ipParts[0]).$($ipParts[1]).$($ipParts[2])"
|
||||
$lastOctet = [int]$ipParts[3]
|
||||
|
||||
if ($currentBaseIP -eq $baseIP -and $lastOctet -ge $startIP -and $lastOctet -le $endIP) {
|
||||
$arpDevices[$neighbor.IPAddress] = @{
|
||||
ip = $neighbor.IPAddress
|
||||
mac = $cleanMac
|
||||
hostname = ""
|
||||
status = "arp"
|
||||
}
|
||||
Write-Log "Found in ARP: $($neighbor.IPAddress) -> $cleanMac"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log "Get-NetNeighbor failed, trying arp command..."
|
||||
|
||||
# Fallback to arp command
|
||||
try {
|
||||
$arpOutput = arp -a
|
||||
foreach ($line in $arpOutput) {
|
||||
if ($line -match '(\d+\.\d+\.\d+\.\d+)\s+([0-9a-fA-F-]{17})\s+\w+') {
|
||||
$ip = $matches[1]
|
||||
$mac = $matches[2].Replace('-', ':').ToLower()
|
||||
if ($mac -ne "00:00:00:00:00:00") {
|
||||
$arpEntries[$ip] = $mac
|
||||
|
||||
# Check if this IP is in our scan range
|
||||
$ipParts = $ip -split '\.'
|
||||
if ($ipParts.Length -eq 4) {
|
||||
$currentBaseIP = "$($ipParts[0]).$($ipParts[1]).$($ipParts[2])"
|
||||
$lastOctet = [int]$ipParts[3]
|
||||
|
||||
if ($currentBaseIP -eq $baseIP -and $lastOctet -ge $startIP -and $lastOctet -le $endIP) {
|
||||
$arpDevices[$ip] = @{
|
||||
ip = $ip
|
||||
mac = $mac
|
||||
hostname = ""
|
||||
status = "arp"
|
||||
}
|
||||
Write-Log "Found in ARP: $ip -> $mac"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log "Warning: Could not retrieve ARP table"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Log "Found $($arpDevices.Count) devices in ARP table for this network"
|
||||
|
||||
# STEP 2: Ping sweep for remaining IPs not found in ARP
|
||||
Write-Log "Step 2: Starting ping sweep for remaining addresses..."
|
||||
|
||||
$aliveIPs = @()
|
||||
$processed = 0
|
||||
$skippedCount = 0
|
||||
|
||||
# Process IPs in smaller batches to avoid overwhelming the system
|
||||
for ($batch = $startIP; $batch -le $endIP; $batch += $BatchSize) {
|
||||
@@ -212,9 +395,16 @@ function Get-NetworkDevices {
|
||||
|
||||
Write-Log "Batch progress: $([Math]::Floor(($batch - $startIP) * 100 / $totalIPs))% - Scanning IPs $batch to $batchEnd"
|
||||
|
||||
# Create jobs for current batch
|
||||
# Create jobs for current batch (skip IPs already found in ARP)
|
||||
for ($i = $batch; $i -le $batchEnd; $i++) {
|
||||
$ip = "$baseIP.$i"
|
||||
|
||||
if ($arpDevices.ContainsKey($ip)) {
|
||||
$processed++
|
||||
$skippedCount++
|
||||
continue # Skip IPs already found in ARP
|
||||
}
|
||||
|
||||
$job = Start-Job -ScriptBlock {
|
||||
param($IPAddress, $TimeoutMs)
|
||||
try {
|
||||
@@ -246,7 +436,7 @@ function Get-NetworkDevices {
|
||||
|
||||
if ($result -and $result.Success) {
|
||||
$aliveIPs += $result.IP
|
||||
Write-Log "Found alive host: $($result.IP) ($($result.ResponseTime)ms)"
|
||||
Write-Log "Found alive host via ping: $($result.IP) ($($result.ResponseTime)ms)"
|
||||
}
|
||||
|
||||
$processed++
|
||||
@@ -254,49 +444,24 @@ function Get-NetworkDevices {
|
||||
|
||||
# Show progress more frequently
|
||||
$progressPercent = [Math]::Floor($processed * 100 / $totalIPs)
|
||||
Write-Log "Progress: $processed/$totalIPs addresses scanned ($progressPercent%)"
|
||||
Write-Log "Progress: $processed/$totalIPs addresses scanned ($progressPercent%) - Skipped $skippedCount (already in ARP)"
|
||||
|
||||
# Small delay between batches to prevent system overload
|
||||
Start-Sleep -Milliseconds 100
|
||||
}
|
||||
|
||||
Write-Log "Found $($aliveIPs.Count) alive hosts"
|
||||
Write-Log "Found $($aliveIPs.Count) additional hosts via ping"
|
||||
|
||||
# Get ARP table to find MAC addresses
|
||||
Write-Log "Retrieving MAC addresses from ARP table..."
|
||||
$arpEntries = @{}
|
||||
# STEP 3: Combine results and resolve hostnames
|
||||
Write-Log "Step 3: Building final device list..."
|
||||
|
||||
try {
|
||||
# Use Get-NetNeighbor for Windows 8/Server 2012 and later
|
||||
$neighbors = Get-NetNeighbor -AddressFamily IPv4 | Where-Object { $_.State -ne "Unreachable" }
|
||||
foreach ($neighbor in $neighbors) {
|
||||
if ($neighbor.LinkLayerAddress -and $neighbor.LinkLayerAddress -ne "00-00-00-00-00-00") {
|
||||
$arpEntries[$neighbor.IPAddress] = $neighbor.LinkLayerAddress.Replace('-', ':').ToLower()
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log "Get-NetNeighbor failed, trying arp command..."
|
||||
|
||||
# Fallback to arp command
|
||||
try {
|
||||
$arpOutput = arp -a
|
||||
foreach ($line in $arpOutput) {
|
||||
if ($line -match '(\d+\.\d+\.\d+\.\d+)\s+([0-9a-fA-F-]{17})\s+\w+') {
|
||||
$ip = $matches[1]
|
||||
$mac = $matches[2].Replace('-', ':').ToLower()
|
||||
if ($mac -ne "00:00:00:00:00:00") {
|
||||
$arpEntries[$ip] = $mac
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log "Warning: Could not retrieve ARP table"
|
||||
}
|
||||
# Add devices found in ARP
|
||||
foreach ($arpDevice in $arpDevices.Values) {
|
||||
$arpDevice.status = "online" # Change from "arp" to "online"
|
||||
$devices += $arpDevice
|
||||
}
|
||||
|
||||
# Build device list
|
||||
# Add devices found via ping (and try to get their MAC from ARP if available)
|
||||
foreach ($ip in $aliveIPs) {
|
||||
$device = @{
|
||||
ip = $ip
|
||||
@@ -304,11 +469,14 @@ function Get-NetworkDevices {
|
||||
hostname = ""
|
||||
status = "online"
|
||||
}
|
||||
|
||||
# Try to resolve hostname
|
||||
$devices += $device
|
||||
}
|
||||
|
||||
# Resolve hostnames and fix MAC addresses for local host
|
||||
foreach ($device in $devices) {
|
||||
try {
|
||||
$hostname = [System.Net.Dns]::GetHostEntry($ip).HostName
|
||||
if ($hostname -and $hostname -ne $ip) {
|
||||
$hostname = [System.Net.Dns]::GetHostEntry($device.ip).HostName
|
||||
if ($hostname -and $hostname -ne $device.ip) {
|
||||
$device.hostname = $hostname
|
||||
}
|
||||
}
|
||||
@@ -316,12 +484,18 @@ function Get-NetworkDevices {
|
||||
# Hostname resolution failed, leave empty
|
||||
}
|
||||
|
||||
$devices += $device
|
||||
# Special handling for local host (Windows machine running the script)
|
||||
if (-not $device.mac -or $device.mac -eq "") {
|
||||
$device.mac = Get-LocalHostMacAddress -IPAddress $device.ip
|
||||
if ($device.mac) {
|
||||
Write-Log "Found local host MAC: $($device.ip) -> $($device.mac)"
|
||||
}
|
||||
}
|
||||
|
||||
if ($device.mac) {
|
||||
Write-Log "Device found: $ip -> $($device.mac) ($($device.hostname))"
|
||||
Write-Log "Final device: $($device.ip) -> $($device.mac) ($($device.hostname))"
|
||||
} else {
|
||||
Write-Log "Device found: $ip (no MAC address available)"
|
||||
Write-Log "Final device: $($device.ip) (no MAC address available) ($($device.hostname))"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user