Upload folder via GUI - src

This commit is contained in:
Git Manager GUI
2026-06-18 20:56:23 +02:00
parent c1b9bc7848
commit 1b5719d716
4 changed files with 704 additions and 41 deletions

View File

@@ -379,7 +379,6 @@ function draw() {
var isSel=selNode&&selNode.id===n.id;
var isCF=connFrom&&connFrom.id===n.id;
var isMultiSel=multiSel.indexOf(n)>=0;
var isMultiSel=multiSel.indexOf(n)>=0;
if(isSel||isMultiSel){ctx.shadowColor=n.color||'#00d4aa';ctx.shadowBlur=16;}
ctx.fillStyle='#161b22';
ctx.strokeStyle=isCF?'#ffa502':(isMultiSel?'#00d4aa':(isSel?(n.color||'#00d4aa'):'#30363d'));

View File

@@ -10,6 +10,50 @@
--accent:#00d4aa; --accent2:#0099ff; --danger:#ff4757; --warn:#ffa502;
--text:#e6edf3; --muted:#7d8590; --online:#26de81; --purple:#a78bfa;
}
var DEVICE_TYPE_OPTIONS = [
'Windows-PC',
'Linux-Server',
'NAS/Storage',
'Kamera/CCTV',
'Alarmanlage/Security',
'Router/Access Point',
'Drucker',
'IoT/Smart-Device',
'Virtualisierungs-Host',
'Konsole/Mediengeraet'
];
function getForcedDeviceType(ip) {
var p = deviceProfiles[ip];
if (!p || !p.forcedType) return 'auto';
return p.forcedType;
}
function buildDeviceTypeOptions(currentType) {
var html = '<option value="auto"'+(currentType==='auto'?' selected':'')+'>Auto</option>';
DEVICE_TYPE_OPTIONS.forEach(function(t) {
html += '<option value="'+esc(t)+'"'+(currentType===t?' selected':'')+'>'+esc(t)+'</option>';
});
return html;
}
async function setDeviceClass(ip, selectedType) {
if (!ip) return;
if (selectedType === 'auto') {
delete deviceProfiles[ip];
} else {
deviceProfiles[ip] = deviceProfiles[ip] || {};
deviceProfiles[ip].forcedType = selectedType;
}
await api.saveDeviceProfiles(deviceProfiles);
var activeItem = document.querySelector('.device-item.active');
if (activeItem) {
var d = devices.find(function(x){return x.ip===ip;});
if (d) selectDevice(d, activeItem);
}
showToast('Geräteklasse gespeichert', selectedType==='auto' ? 'Auto-Erkennung aktiv' : selectedType+' manuell gesetzt', 'info');
}
.light {
--bg:#f6f8fa; --surface:#ffffff; --surface2:#f0f2f5; --border:#d0d7de;
--text:#24292f; --muted:#57606a; --surface2:#eaeef2;
@@ -323,8 +367,8 @@ body{font-family:'Segoe UI',system-ui,sans-serif;background:var(--bg);color:var(
<div class="stat-box"><div class="stat-num" id="statOnline">0</div><div class="stat-label">Online</div></div>
</div>
<div id="liveStatusBar" style="display:none;padding:5px 12px;font-size:10px;color:var(--accent);display:flex;align-items:center;gap:6px;border-bottom:1px solid var(--border)">
<span style="animation:livepulse 2s ease-in-out infinite"></span> Live-Ping aktiv aktualisiert alle 4s
<div id="liveStatusBar" style="display:none;padding:5px 12px;font-size:10px;color:var(--accent);align-items:center;gap:6px;border-bottom:1px solid var(--border)">
<span style="animation:livepulse 2s ease-in-out infinite"></span> Live-Ping aktiv aktualisiert alle 6s
</div>
<div class="search-wrap">
@@ -415,6 +459,15 @@ body{font-family:'Segoe UI',system-ui,sans-serif;background:var(--bg);color:var(
<div><div class="setting-label">Offline-Geräte melden</div><div class="setting-sub">Benachrichtigung wenn ein Gerät nicht mehr erreichbar ist</div></div>
<label class="toggle"><input type="checkbox" id="settingNotifyOffline"><span class="toggle-slider"></span></label>
</div>
<div class="setting-row">
<div><div class="setting-label">Offline-Empfindlichkeit</div><div class="setting-sub">Jede Prüfung sendet bereits 3 Ping-Pakete (gilt nur als Fehlschlag, wenn alle 3 verloren gehen). Hier stellst du ein, wie viele solcher Prüfungen in Folge fehlschlagen müssen, bevor ein Gerät als offline gilt.</div></div>
<select class="setting-select" id="settingFailThreshold">
<option value="1">Sofort (1 Prüfung)</option>
<option value="2">Normal (2 Prüfungen)</option>
<option value="3">Tolerant (3 Prüfungen)</option>
<option value="5">Sehr tolerant (5 Prüfungen)</option>
</select>
</div>
<div class="setting-row">
<div><div class="setting-label">Beim Start automatisch scannen</div><div class="setting-sub">Startet den Scan direkt beim Öffnen</div></div>
<label class="toggle"><input type="checkbox" id="settingAutoStart"><span class="toggle-slider"></span></label>
@@ -532,6 +585,7 @@ body{font-family:'Segoe UI',system-ui,sans-serif;background:var(--bg);color:var(
<script>
var devices = [], totalPorts = 0, scanning = false;
var currentPorts = [], deviceNames = {}, favorites = [], settings = {};
var deviceProfiles = {};
var monitoredSubnets = [];
var extraSubnetDevices = {}; // { subnet: [devices] }
@@ -539,6 +593,7 @@ var extraSubnetDevices = {}; // { subnet: [devices] }
async function init() {
currentPorts = await api.getPorts();
deviceNames = await api.getDeviceNames();
deviceProfiles = await api.getDeviceProfiles();
favorites = await api.getFavorites();
settings = await api.getSettings();
applyTheme(settings.theme || 'dark');
@@ -546,6 +601,8 @@ async function init() {
document.getElementById('settingAutoScan').value = settings.autoScan || 0;
document.getElementById('settingNotifyNew').checked = settings.notifyNew !== false;
document.getElementById('settingNotifyOffline').checked = settings.notifyOffline !== false;
document.getElementById('settingFailThreshold').value = settings.failThreshold || 2;
FAIL_THRESHOLD = settings.failThreshold || 2;
document.getElementById('settingAutoStart').checked = settings.autoStart || false;
document.getElementById('settingCloseToTray').checked = settings.closeToTray || false;
if (settings.autoScan > 0) document.getElementById('autoscanBadge').classList.add('visible');
@@ -669,12 +726,15 @@ async function scanExtraSubnets() {
// ── Live Ping Loop ────────────────────────────────────────
var livePingTimer = null;
var livePingBusy = false;
var LIVE_PING_INTERVAL = 4000; // ms
var LIVE_PING_INTERVAL = 6000; // ms — increased from 4s since each check now sends 3 packets per device (takes a bit longer, but is far more reliable)
var FAIL_THRESHOLD = 2; // consecutive FAILED CHECKS before flagging offline. Note: each "check" already sends 3 ICMP packets and only fails if ALL 3 are lost, so this is now a much stronger guarantee than it sounds.
var consecutiveFails = {}; // { ip: count }
function startLivePing() {
stopLivePing();
if (!devices.length) return;
document.getElementById('liveStatusBar').style.display = 'flex';
consecutiveFails = {};
livePingTimer = setInterval(runLivePingCycle, LIVE_PING_INTERVAL);
runLivePingCycle();
}
@@ -694,16 +754,27 @@ async function runLivePingCycle() {
results.forEach(function(res) {
var d = devices.find(function(x) { return x.ip === res.ip; });
if (!d) return;
var liveMs = res.alive && res.ms !== null ? parseInt(res.ms) : null;
d.ms = liveMs;
if (res.alive) onlineCount++;
if (res.alive) {
consecutiveFails[res.ip] = 0;
} else {
consecutiveFails[res.ip] = (consecutiveFails[res.ip] || 0) + 1;
}
// Only treat as truly offline after FAIL_THRESHOLD consecutive misses —
// a single dropped ping on a healthy LAN is normal and not worth alerting on.
var confirmedOffline = consecutiveFails[res.ip] >= FAIL_THRESHOLD;
var liveMs = (res.alive && res.ms !== null) ? parseInt(res.ms) : null;
if (res.alive) { d.ms = liveMs; onlineCount++; }
else if (!confirmedOffline) { onlineCount++; } // still counted online while within tolerance
// Update list row
var item = document.getElementById('dev-' + res.ip.replace(/\./g, '-'));
if (item) {
var dot = item.querySelector('.device-dot');
var pingEl = item.querySelector('.device-ping');
var wasOffline = item.classList.contains('is-offline');
var wasConfirmedOffline = item.classList.contains('is-offline');
if (res.alive) {
item.classList.remove('is-offline');
var dotColor = liveMs<=50?'var(--online)':liveMs<=100?'var(--warn)':'var(--danger)';
@@ -713,12 +784,13 @@ async function runLivePingCycle() {
pingEl.className = 'device-ping ' + lpc;
pingEl.textContent = liveMs + ' ms';
}
if (wasOffline) showToast('Gerät wieder online', (getCustomName(res.ip)||d.hostname||res.ip), 'new');
} else {
if (!wasOffline) { item.classList.add('is-offline'); showToast('Gerät offline', (getCustomName(res.ip)||d.hostname||res.ip)+' antwortet nicht mehr', 'offline'); }
if (wasConfirmedOffline) showToast('Gerät wieder online', (getCustomName(res.ip)||d.hostname||res.ip), 'new');
} else if (confirmedOffline) {
if (!wasConfirmedOffline) { item.classList.add('is-offline'); showToast('Gerät offline', (getCustomName(res.ip)||d.hostname||res.ip)+' antwortet seit '+FAIL_THRESHOLD+' Prüfungen (je 3 Pings) nicht mehr', 'offline'); }
if (dot) dot.style.background = 'var(--danger)';
if (pingEl) { pingEl.className='device-ping slow'; pingEl.textContent='offline'; }
}
// else: single missed ping within tolerance — leave display as-is, no alert
}
// Update detail panel if this device is currently selected
@@ -730,7 +802,7 @@ async function runLivePingCycle() {
var pc = liveMs<=50?'green':liveMs<=100?'warn':'danger';
pingVal.className = 'info-card-val ' + pc;
pingVal.textContent = liveMs + ' ms';
} else {
} else if (confirmedOffline) {
pingVal.className = 'info-card-val danger';
pingVal.textContent = 'Offline';
}
@@ -805,9 +877,13 @@ function toggleFavorite(ip, btn, e) {
function renderFavorites() {
var panel = document.getElementById('panelFavorites');
var empty = document.getElementById('emptyFavs');
if (!favorites.length) { empty.style.display=''; return; }
empty.style.display = 'none';
if (!favorites.length) {
panel.innerHTML = '<div class="empty-list" id="emptyFavs">'+
'<div class="empty-icon">⭐</div>'+
'Noch keine Favoriten.<br>Klick auf ☆ beim Gerät<br>um es zu pinnen.'+
'</div>';
return;
}
var html = '';
favorites.forEach(function(ip) {
var d = devices.find(function(x){return x.ip===ip;});
@@ -832,15 +908,17 @@ function selectDeviceByIp(ip) {
// ── Device detail ─────────────────────────────────────────
async function selectDevice(d, item) {
if (watchIp && watchIp !== d.ip) stopWatchMode();
document.querySelectorAll('.device-item').forEach(function(el){el.classList.remove('active');});
if(item) item.classList.add('active');
document.getElementById('placeholder').style.display='none';
var detail = document.getElementById('deviceDetail');
detail.classList.add('visible');
var icon = getDeviceIcon(d.vendor);
var icon = getDeviceIcon(d);
var customName = getCustomName(d.ip);
var customNote = deviceNames[d.ip]&&deviceNames[d.ip].note?deviceNames[d.ip].note:null;
var displayName = customName||d.hostname||d.ip;
var forcedType = getForcedDeviceType(d.ip);
var isUnknown = !customName && (!d.vendor || d.vendor==='Unbekannt') && !confirmedDevices[d.ip];
detail.innerHTML =
@@ -853,6 +931,7 @@ async function selectDevice(d, item) {
(d.ipv6?'<div class="ipv6-badge">IPv6: '+esc(d.ipv6)+'</div>':'')+
(customNote?'<div style="font-size:11px;color:var(--muted);margin-top:2px">📝 '+esc(customNote)+'</div>':'')+
'<div class="detail-vendor" id="detailVendor">'+(d.vendor&&d.vendor!=='Unbekannt'?esc(d.vendor):'…')+'</div>'+
'<div style="font-size:11px;color:var(--muted);margin-top:2px">🧠 Klasse: <span style="color:var(--accent)">'+(forcedType!=='auto'?esc(forcedType)+' (manuell)':'Auto-Erkennung')+'</span></div>'+
(d.os?'<div class="detail-os">🖥 Erkanntes OS: '+esc(d.os)+(d.ttl?' (TTL '+d.ttl+')':'')+'</div>':'')+
'</div>'+
'</div>'+
@@ -860,7 +939,16 @@ async function selectDevice(d, item) {
'<button class="btn-action" onclick="reping(\''+d.ip+'\')">⚡ Sofort aktualisieren</button>'+
'<button class="btn-action" onclick="rescanPorts(\''+d.ip+'\')">🔍 Ports scannen</button>'+
'<button class="btn-action" onclick="quickEditDevice(\''+d.ip+'\')">✏️ Umbenennen</button>'+
'<button class="btn-action" id="btnWatch" onclick="toggleWatchMode(\''+d.ip+'\')" style="color:#a78bfa;border-color:#a78bfa">🔭 Beobachten (Stecker-Test)</button>'+
'<button class="btn-action" onclick="identifyDevice(\''+d.ip+'\',\''+(d.mac||'')+'\')" style="color:#ffa502;border-color:#ffa502">🔬 Identifizieren</button>'+
'</div>'+
'<div style="margin:-8px 0 16px;display:flex;align-items:center;gap:8px;flex-wrap:wrap">'+
'<span style="font-size:11px;color:var(--muted)">Geräteklasse:</span>'+
'<select class="setting-select" onchange="setDeviceClass(\''+d.ip+'\', this.value)">'+buildDeviceTypeOptions(forcedType)+'</select>'+
'<span style="font-size:10px;color:var(--muted)">Auto nutzt Ports, Hersteller und Hostname.</span>'+
'</div>'+
'<div id="watchPanel" style="display:none"></div>'+
'<div id="identifyPanel" style="display:none"></div>'+
'<div class="info-grid">'+
'<div class="info-card"><div class="info-card-label">IP-Adresse</div><div class="info-card-val teal">'+d.ip+'</div></div>'+
'<div class="info-card"><div class="info-card-label">MAC-Adresse</div><div class="info-card-val blue" id="detailMac">'+(d.mac||'')+'</div></div>'+
@@ -1018,6 +1106,158 @@ async function reping(ip) {
if (lpe&&liveMs!==null) { var lpc=liveMs<=50?'fast':liveMs<=100?'mid':'slow'; lpe.className='device-ping '+lpc; lpe.textContent=liveMs+' ms'; }
}
// ── Watch Mode: fast continuous ping for the unplug/replug identification test ──
var watchTimer = null;
var watchIp = null;
var watchHistory = []; // sequence of true/false results shown as a timeline
function toggleWatchMode(ip) {
if (watchTimer && watchIp === ip) { stopWatchMode(); return; }
stopWatchMode();
watchIp = ip;
watchHistory = [];
document.getElementById('watchPanel').style.display = 'block';
document.getElementById('btnWatch').textContent = '⏹ Beobachtung stoppen';
document.getElementById('btnWatch').style.background = '#a78bfa';
document.getElementById('btnWatch').style.color = '#000';
renderWatchPanel('Starte Beobachtung…', null);
watchTick(); // run immediately, then every 1.5s
watchTimer = setInterval(watchTick, 1500);
}
function stopWatchMode() {
if (watchTimer) { clearInterval(watchTimer); watchTimer = null; }
watchIp = null;
var btn = document.getElementById('btnWatch');
if (btn) { btn.textContent = '🔭 Beobachten (Stecker-Test)'; btn.style.background=''; btn.style.color='#a78bfa'; }
var panel = document.getElementById('watchPanel');
if (panel) panel.style.display = 'none';
}
async function watchTick() {
if (!watchIp) return;
var ip = watchIp;
try {
var res = await api.pingDevice(ip);
if (watchIp !== ip) return; // user switched device mid-flight, ignore stale result
watchHistory.push(res.alive);
if (watchHistory.length > 40) watchHistory.shift();
var statusText = res.alive ? ('Online '+(res.ms!=null?res.ms+' ms':'')) : 'Keine Antwort';
renderWatchPanel(statusText, res.alive);
} catch(e) {
watchHistory.push(false);
renderWatchPanel('Fehler bei der Abfrage', false);
}
}
function renderWatchPanel(statusText, alive) {
var panel = document.getElementById('watchPanel');
if (!panel) return;
var dots = watchHistory.map(function(a) {
return '<span style="display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:3px;background:'+(a?'#26de81':'#ff4757')+'"></span>';
}).join('');
var bigColor = alive===null ? 'var(--muted)' : (alive ? '#26de81' : '#ff4757');
var bigText = alive===null ? '⏳' : (alive ? '🟢 ONLINE' : '🔴 KEINE ANTWORT');
panel.innerHTML =
'<div style="background:var(--surface);border:2px solid '+bigColor+';border-radius:10px;padding:16px;margin-bottom:16px;text-align:center">'+
'<div style="font-size:11px;color:var(--muted);margin-bottom:6px">🔭 Live-Beobachtung jetzt Gerät vom Strom/Kabel trennen und beobachten</div>'+
'<div style="font-size:26px;font-weight:800;color:'+bigColor+'">'+bigText+'</div>'+
'<div style="font-size:12px;color:var(--muted);margin-top:4px">'+esc(statusText)+'</div>'+
'<div style="margin-top:10px;line-height:1.8">'+dots+'</div>'+
'<div style="font-size:10px;color:var(--muted);margin-top:6px">Jeder Punkt = 1 Prüfung (alle 1,5s) · grün = Antwort · rot = keine Antwort</div>'+
'</div>';
}
// ── Device identification: combines NetBIOS, reverse-DNS, WoL-probe, and ports ──
async function identifyDevice(ip, mac) {
var panel = document.getElementById('identifyPanel');
if (!panel) return;
panel.style.display = 'block';
panel.innerHTML =
'<div style="background:var(--surface);border:1px solid var(--warn);border-radius:10px;padding:16px;margin-bottom:16px">'+
'<div style="font-size:12px;color:var(--warn);display:flex;align-items:center;gap:8px"><div class="spinner"></div> Identifiziere Gerät prüfe NetBIOS, Hostname und Wake-on-LAN…</div>'+
'</div>';
var result;
try {
result = await api.identifyDevice(ip, mac);
} catch(e) {
panel.innerHTML = '<div style="background:var(--surface);border:1px solid var(--danger);border-radius:10px;padding:16px;margin-bottom:16px;color:var(--danger);font-size:12px">Identifikation fehlgeschlagen: '+esc(String(e.message||e))+'</div>';
return;
}
// If we resolved a fresher MAC than what was previously known (e.g. ARP
// cache had expired), update the displayed MAC and the in-memory device record.
if (result.mac && result.mac !== '') {
var macEl = document.getElementById('detailMac');
if (macEl) macEl.textContent = result.mac;
var d2 = devices.find(function(x) { return x.ip === ip; });
if (d2) d2.mac = result.mac;
}
var rows = [];
rows.push({
label: 'NetBIOS-Name',
value: result.netbiosName || 'Nicht ermittelbar',
hint: result.netbiosName ? 'Computername über NetBIOS gefunden — starker Hinweis auf einen Windows-PC oder ein NAS mit aktiviertem NetBIOS.' : 'Kein NetBIOS verfügbar (deaktiviert, kein Windows-Gerät, oder nicht im selben Subnetz erreichbar).',
ok: !!result.netbiosName
});
rows.push({
label: 'Reverse-DNS / Hostname',
value: result.reverseDns || 'Nicht ermittelbar',
hint: result.reverseDns ? 'Hostname über DNS-Auflösung gefunden.' : 'Router/DNS liefert keinen Namen für diese IP zurück.',
ok: !!result.reverseDns
});
rows.push({
label: 'Wake-on-LAN',
value: result.wakeOnLan && result.wakeOnLan.sent ? 'Paket gesendet' : 'Nicht möglich',
hint: result.wakeOnLan ? result.wakeOnLan.reason : '',
ok: result.wakeOnLan && result.wakeOnLan.sent
});
var portsHint = '';
if (result.openPorts && result.openPorts.length) {
portsHint = '<div style="margin-top:10px;font-size:12px;color:var(--text)"><strong>Offene Ports gefunden:</strong> ' +
result.openPorts.map(function(p){return esc(p.name)+' ('+p.port+')';}).join(', ') + '</div>';
} else {
portsHint = '<div style="margin-top:10px;font-size:12px;color:var(--muted)">Keine bekannten Ports offen — typisch für einfache IoT-Geräte, Smart-Plugs oder Bridges ohne eigene Web-Oberfläche.</div>';
}
var identity = result.identity || null;
var identityBlock = '';
if (identity) {
var confidenceColor = identity.confidence === 'manuell' ? 'var(--accent2)' : (identity.confidence === 'hoch' ? 'var(--online)' : (identity.confidence === 'mittel' ? 'var(--warn)' : 'var(--danger)'));
var ranking = (identity.ranking || []).map(function(r) {
return '<div style="font-size:11px;color:var(--muted)">• '+esc(r.type)+' ('+r.score+' Punkte)</div>';
}).join('');
var reasons = (identity.reasons || []).map(function(x) {
return '<div style="font-size:11px;color:var(--muted)">• '+esc(x)+'</div>';
}).join('');
identityBlock =
'<div style="margin-top:10px;padding:10px;border:1px solid var(--border);border-radius:8px;background:var(--surface2)">'+
'<div style="font-size:12px"><strong>Wahrscheinlicher Gerätetyp:</strong> '+esc(identity.likelyType)+'</div>'+
'<div style="font-size:11px;margin-top:3px;color:'+confidenceColor+'"><strong>Confidence:</strong> '+esc(identity.confidence)+'</div>'+
(ranking ? '<div style="margin-top:6px"><div style="font-size:11px;color:var(--text);font-weight:600">Top-Kandidaten</div>'+ranking+'</div>' : '')+
(reasons ? '<div style="margin-top:6px"><div style="font-size:11px;color:var(--text);font-weight:600">Signale</div>'+reasons+'</div>' : '')+
'</div>';
}
panel.innerHTML =
'<div style="background:var(--surface);border:1px solid var(--warn);border-radius:10px;padding:16px;margin-bottom:16px">'+
'<div style="font-size:12px;font-weight:700;color:var(--warn);margin-bottom:10px">🔬 Identifikations-Ergebnis</div>'+
rows.map(function(r) {
return '<div style="display:flex;align-items:flex-start;gap:8px;margin-bottom:8px">'+
'<span style="flex-shrink:0;margin-top:1px">'+(r.ok?'✅':'⚪')+'</span>'+
'<div><div style="font-size:12px"><strong>'+esc(r.label)+':</strong> '+esc(r.value)+'</div>'+
(r.hint?'<div style="font-size:11px;color:var(--muted);margin-top:1px">'+esc(r.hint)+'</div>':'')+
'</div></div>';
}).join('') +
identityBlock +
portsHint +
'<div style="font-size:10px;color:var(--muted);margin-top:10px;border-top:1px solid var(--border);padding-top:8px">Tipp: Wake-on-LAN funktioniert nur, wenn das Gerät ausgeschaltet (nicht getrennt) ist und WoL in dessen Netzwerkeinstellungen aktiviert ist. Beobachte, ob es kurz danach wieder online geht.</div>'+
'</div>';
}
async function rescanPorts(ip) {
var pa = document.getElementById('portsArea');
if (!pa) return;
@@ -1082,11 +1322,13 @@ async function saveSettings() {
autoScan: parseInt(document.getElementById('settingAutoScan').value),
notifyNew: document.getElementById('settingNotifyNew').checked,
notifyOffline: document.getElementById('settingNotifyOffline').checked,
failThreshold: parseInt(document.getElementById('settingFailThreshold').value),
autoStart: document.getElementById('settingAutoStart').checked,
closeToTray: document.getElementById('settingCloseToTray').checked
};
await api.saveSettings(settings);
applyTheme(settings.theme);
FAIL_THRESHOLD = settings.failThreshold;
document.getElementById('autoscanBadge').classList.toggle('visible', settings.autoScan > 0);
closeModal('settingsModal');
showToast('Einstellungen gespeichert','','info');
@@ -1138,7 +1380,13 @@ async function resetPorts() {
function openDeviceManager() { renderDeviceList(); document.getElementById('deviceModal').classList.add('open'); }
function renderDeviceList() {
var list=document.getElementById('deviceModalList'); list.innerHTML='';
var ips=Object.keys(deviceNames).sort(function(a,b){return a.split('.').map(Number).join('')-b.split('.').map(Number).join('');});
var ips=Object.keys(deviceNames).sort(function(a,b){
var aa=a.split('.').map(Number), bb=b.split('.').map(Number);
for (var i=0;i<4;i++) {
if ((aa[i]||0)!==(bb[i]||0)) return (aa[i]||0)-(bb[i]||0);
}
return 0;
});
ips.forEach(function(ip) {
var d=deviceNames[ip], row=document.createElement('div'); row.className='device-name-row';
row.innerHTML='<span class="dn-ip">'+ip+'</span>'+
@@ -1308,24 +1556,96 @@ async function exportDeviceList(format) {
if (result.success) showToast('Export erfolgreich', 'Geräteliste als '+format.toUpperCase()+' gespeichert', 'info');
}
var DEVICE_TYPE_OPTIONS = [
'Windows-PC',
'Linux-Server',
'NAS/Storage',
'Kamera/CCTV',
'Alarmanlage/Security',
'Router/Access Point',
'Drucker',
'IoT/Smart-Device',
'Virtualisierungs-Host',
'Konsole/Mediengeraet'
];
function esc(s){return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}
function getForcedDeviceType(ip) {
var p = deviceProfiles[ip];
if (!p || !p.forcedType) return 'auto';
return p.forcedType;
}
function buildDeviceTypeOptions(currentType) {
var html = '<option value="auto"'+(currentType==='auto'?' selected':'')+'>Auto</option>';
DEVICE_TYPE_OPTIONS.forEach(function(t) {
html += '<option value="'+esc(t)+'"'+(currentType===t?' selected':'')+'>'+esc(t)+'</option>';
});
return html;
}
async function setDeviceClass(ip, selectedType) {
if (!ip) return;
if (selectedType === 'auto') {
delete deviceProfiles[ip];
} else {
deviceProfiles[ip] = deviceProfiles[ip] || {};
deviceProfiles[ip].forcedType = selectedType;
}
await api.saveDeviceProfiles(deviceProfiles);
var activeItem = document.querySelector('.device-item.active');
if (activeItem) {
var d = devices.find(function(x){return x.ip===ip;});
if (d) selectDevice(d, activeItem);
}
showToast('Geräteklasse gespeichert', selectedType==='auto' ? 'Auto-Erkennung aktiv' : selectedType+' manuell gesetzt', 'info');
}
function esc(s){
return String(s||'')
.replace(/&/g,'&amp;')
.replace(/</g,'&lt;')
.replace(/>/g,'&gt;')
.replace(/"/g,'&quot;')
.replace(/'/g,'&#39;');
}
function getCustomName(ip){return deviceNames[ip]?deviceNames[ip].name:null;}
function getDeviceIcon(vendor) {
if (!vendor) return '🖥️';
var v=vendor.toLowerCase();
if(v.includes('apple')) return '🍎';
if(v.includes('synology')) return '🗄️';
if(v.includes('raspberry')) return '🍓';
if(v.includes('fritz')||v.includes('avm')) return '📡';
if(v.includes('tp-link')) return '📡';
if(v.includes('ubiquiti')) return '📶';
if(v.includes('vmware')||v.includes('qemu')||v.includes('proxmox')) return '💻';
if(v.includes('cisco')) return '🔀';
if(v.includes('samsung')) return '📺';
if(v.includes('nintendo')) return '🎮';
if(v.includes('brother')) return '🖨️';
if(v.includes('abus')) return '🔒';
function getDeviceIcon(deviceOrVendor) {
var vendor = '';
var nameHints = '';
var forcedType = null;
if (typeof deviceOrVendor === 'string') {
vendor = deviceOrVendor.toLowerCase();
} else if (deviceOrVendor && typeof deviceOrVendor === 'object') {
vendor = String(deviceOrVendor.vendor || '').toLowerCase();
nameHints = String(deviceOrVendor.hostname || deviceOrVendor.displayName || '').toLowerCase();
if (deviceOrVendor.ip) forcedType = getForcedDeviceType(deviceOrVendor.ip);
}
if (forcedType && forcedType !== 'auto') {
if (forcedType === 'NAS/Storage') return '🗄️';
if (forcedType === 'Kamera/CCTV') return '📷';
if (forcedType === 'Alarmanlage/Security') return '🚨';
if (forcedType === 'Drucker') return '🖨️';
if (forcedType === 'Router/Access Point') return '📡';
if (forcedType === 'Virtualisierungs-Host') return '💻';
}
if (vendor.includes('synology') || vendor.includes('qnap') || vendor.includes('asustor') || nameHints.includes('nas') || nameHints.includes('diskstation')) return '🗄️';
if (vendor.includes('hikvision') || vendor.includes('dahua') || vendor.includes('reolink') || vendor.includes('axis') || nameHints.includes('cam') || nameHints.includes('cctv') || nameHints.includes('nvr')) return '📷';
if (vendor.includes('abus') || vendor.includes('ajax') || vendor.includes('honeywell') || nameHints.includes('alarm') || nameHints.includes('security')) return '🚨';
if (vendor.includes('brother') || vendor.includes('hewlett') || vendor.includes('hp')) return '🖨️';
if (vendor.includes('apple')) return '🍎';
if (vendor.includes('raspberry')) return '🍓';
if (vendor.includes('fritz') || vendor.includes('avm')) return '📡';
if (vendor.includes('tp-link')) return '📡';
if (vendor.includes('ubiquiti')) return '📶';
if (vendor.includes('vmware') || vendor.includes('qemu') || vendor.includes('proxmox')) return '💻';
if (vendor.includes('cisco')) return '🔀';
if (vendor.includes('samsung')) return '📺';
if (vendor.includes('nintendo')) return '🎮';
return '🖥️';
}

View File

@@ -31,6 +31,54 @@ ipcMain.handle('save-ports', (e, ports) => { COMMON_PORTS = ports; return writeJ
ipcMain.handle('get-device-names', () => readJson('device-names.json', {}));
ipcMain.handle('save-device-names', (e, names) => writeJson('device-names.json', names));
// ── Device profiles (manual class override per IP) ───────
function loadDeviceProfiles() { return readJson('device-profiles.json', {}); }
function saveDeviceProfiles(p) { return writeJson('device-profiles.json', p); }
ipcMain.handle('get-device-profiles', () => loadDeviceProfiles());
ipcMain.handle('save-device-profiles', (e, profiles) => saveDeviceProfiles(profiles));
// ── Identity keyword catalog (vendor + hostname hints) ───
var DEFAULT_IDENTITY_KEYWORDS = {
vendor: {
nas: ['synology', 'qnap', 'asustor', 'terramaster', 'western digital', 'wd', 'seagate'],
camera: ['hikvision', 'dahua', 'reolink', 'axis', 'foscam', 'vivotek', 'ezviz', 'imou', 'uniview'],
alarm: ['abus', 'ajax', 'honeywell', 'bosch security', 'jablotron', 'somfy', 'satel', 'paradox', 'risco'],
router: ['avm', 'fritz', 'tp-link', 'ubiquiti', 'cisco', 'mikrotik', 'netgear'],
virtualization: ['vmware', 'proxmox', 'xen', 'qemu'],
printer: ['brother', 'hewlett', 'hp']
},
hostname: {
nas: ['nas', 'diskstation', 'storage', 'fileserver'],
camera: ['cam', 'camera', 'cctv', 'nvr', 'dvr', 'ipc', 'doorbell'],
alarm: ['alarm', 'security', 'siren', 'panel', 'zentrale'],
router: ['fritz', 'router', 'gateway', 'access point', 'ap-']
}
};
function mergeStringArrays(baseArr, extraArr) {
var out = (baseArr || []).slice();
(extraArr || []).forEach(function(v) {
var val = String(v || '').toLowerCase().trim();
if (val && out.indexOf(val) < 0) out.push(val);
});
return out;
}
function loadIdentityKeywords() {
var custom = readJson('identity-keywords.json', {});
var merged = { vendor: {}, hostname: {} };
Object.keys(DEFAULT_IDENTITY_KEYWORDS.vendor).forEach(function(k) {
merged.vendor[k] = mergeStringArrays(DEFAULT_IDENTITY_KEYWORDS.vendor[k], custom.vendor && custom.vendor[k]);
});
Object.keys(DEFAULT_IDENTITY_KEYWORDS.hostname).forEach(function(k) {
merged.hostname[k] = mergeStringArrays(DEFAULT_IDENTITY_KEYWORDS.hostname[k], custom.hostname && custom.hostname[k]);
});
return merged;
}
ipcMain.handle('get-identity-keywords', () => loadIdentityKeywords());
ipcMain.handle('save-identity-keywords', (e, customKeywords) => writeJson('identity-keywords.json', customKeywords || {}));
// ── Favorites ─────────────────────────────────────────────
ipcMain.handle('get-favorites', () => readJson('favorites.json', []));
ipcMain.handle('save-favorites', (e, favs) => writeJson('favorites.json', favs));
@@ -250,25 +298,44 @@ ipcMain.handle('get-network-info', () => {
function pingHost(ip) {
return new Promise(function(resolve) {
var start = Date.now();
var args = process.platform === 'win32' ? ['-n','1','-w','500',ip] : ['-c','1','-W','1',ip];
execFile('ping', args, { timeout: 2000 }, function(err, stdout) {
// Send 3 ICMP packets per check (like a real "ping" command does) instead
// of just 1. A single dropped packet is normal on any real network and
// does NOT mean the host is down — only treat it as unreachable if ALL
// packets in this check fail to get a reply.
var args = process.platform === 'win32' ? ['-n','3','-w','1000',ip] : ['-c','3','-W','1',ip];
execFile('ping', args, { timeout: 4000 }, function(err, stdout) {
stdout = stdout || '';
var alive = !err && (stdout.includes('TTL=') || stdout.includes('ttl=') || stdout.includes('1 received'));
// Count how many replies actually came back, rather than treating the
// whole check as binary alive/dead based on the process exit code.
var ttlMatches = stdout.match(/ttl[=:]\s*\d+/gi) || [];
var repliesReceived = ttlMatches.length;
var alive = repliesReceived > 0;
var ttl = null, os_guess = null;
var ttlMatch = stdout.match(/TTL=(\d+)/i);
var ttlMatch = stdout.match(/ttl[=:]\s*(\d+)/i);
if (ttlMatch) {
ttl = parseInt(ttlMatch[1]);
if (ttl >= 128) os_guess = 'Windows';
else if (ttl >= 64) os_guess = 'Linux/macOS';
else if (ttl >= 32) os_guess = 'Network Device';
}
// Average the RTT across all replies actually received, for a more
// representative number than a single sample.
var ms = null;
if (alive) {
var msMatch = stdout.match(/(\d+(?:\.\d+)?)\s*ms\b/i);
ms = msMatch ? Math.round(parseFloat(msMatch[1])) : (Date.now() - start);
var msMatches = stdout.match(/(\d+(?:\.\d+)?)\s*ms\b/gi) || [];
var msValues = msMatches.map(function(m) { return parseFloat(m); }).filter(function(n) { return !isNaN(n); });
if (msValues.length) {
var sum = msValues.reduce(function(a,b){return a+b;}, 0);
ms = Math.round(sum / msValues.length);
} else {
ms = Date.now() - start;
}
if (DEBUG_SCAN) console.log('[pingHost]', ip, '-> alive:', alive, 'err:', err ? err.message : null, 'stdout snippet:', stdout.substring(0,120).replace(/\n/g,' | '));
resolve({ ip: ip, alive: alive, ms: ms, ttl: ttl, os: os_guess });
}
if (DEBUG_SCAN) console.log('[pingHost]', ip, '-> alive:', alive, 'replies:', repliesReceived+'/3', 'ms:', ms);
resolve({ ip: ip, alive: alive, ms: ms, ttl: ttl, os: os_guess, repliesReceived: repliesReceived });
});
});
}
@@ -314,6 +381,265 @@ function getMacFromArp(ip) {
});
}
// ── Deep device identification (on-demand, not run during normal scans) ──
// Combines three independent techniques so that even if one fails (e.g. a
// device has NetBIOS disabled), the others may still reveal something useful.
// 1. NetBIOS name via nbtstat — works for Windows PCs and many NAS/printers
// that still respond to legacy NetBIOS name queries even when mDNS/UPnP is off.
function getNetbiosName(ip) {
return new Promise(function(resolve) {
if (process.platform !== 'win32') return resolve(null);
exec('nbtstat -A ' + ip, { timeout: 3000 }, function(err, stdout) {
if (err || !stdout) return resolve(null);
// Look for the <00> UNIQUE entry, which is typically the actual computer name
var lines = stdout.split('\n');
var nameLine = lines.find(function(l) { return /<00>\s*UNIQUE/i.test(l); });
if (nameLine) {
var m = nameLine.trim().match(/^([^\s]+)/);
if (m) return resolve(m[1].trim());
}
resolve(null);
});
});
}
// 2. Reverse-DNS / mDNS-style hostname — tries both nslookup (works if router's
// DNS resolves DHCP hostnames, similar to how Fritz!Box names devices) and a
// raw PTR-style attempt for broader compatibility.
function getReverseDns(ip) {
return new Promise(function(resolve) {
exec('nslookup ' + ip, { timeout: 2500 }, function(err, stdout) {
if (!err && stdout) {
var m = stdout.match(/Name:\s+(.+)/i);
if (m) return resolve(m[1].trim());
}
resolve(null);
});
});
}
// 3. Wake-on-LAN capability probe — sends a harmless WoL magic packet and
// reports whether the device's network port is configured to listen for one.
// This itself doesn't reveal a name, but tells you whether it's a PC/server
// (WoL-capable NICs almost always belong to Windows/Linux machines, never
// to simple IoT bulbs, dryers, or basic network bridges).
function sendWakeOnLanProbe(mac) {
return new Promise(function(resolve) {
if (!mac || mac === '') return resolve({ sent: false, reason: 'Keine MAC-Adresse bekannt' });
try {
var macBytes = mac.replace(/[:-]/g, '').match(/.{2}/g).map(function(h) { return parseInt(h, 16); });
if (macBytes.length !== 6 || macBytes.some(isNaN)) return resolve({ sent: false, reason: 'Ungültige MAC-Adresse' });
var magicPacket = Buffer.alloc(102);
for (var i = 0; i < 6; i++) magicPacket[i] = 0xFF;
for (var j = 0; j < 16; j++) macBytes.forEach(function(b, k) { magicPacket[6 + j*6 + k] = b; });
var dgram = require('dgram');
var socket = dgram.createSocket('udp4');
socket.on('error', function() { try { socket.close(); } catch(e){}; resolve({ sent: false, reason: 'Senden fehlgeschlagen' }); });
socket.send(magicPacket, 0, magicPacket.length, 9, '255.255.255.255', function(err) {
socket.close();
if (err) return resolve({ sent: false, reason: 'Senden fehlgeschlagen' });
resolve({ sent: true, reason: 'Wake-on-LAN-Paket gesendet (Port 9, Broadcast)' });
});
} catch(e) {
resolve({ sent: false, reason: 'Fehler: ' + e.message });
}
});
}
ipcMain.handle('identify-device', async function(event, { ip, mac }) {
var results = { ip: ip, mac: mac };
// Ping first — this forces the OS to send an ARP request for the IP if no
// cache entry exists, which is required before "arp -a" can return anything.
// Without this, a stale/expired ARP cache entry silently makes the MAC
// lookup (and therefore Wake-on-LAN) fail even though the device is online.
try { await pingHost(ip); } catch(e) {}
// Re-resolve the MAC fresh rather than trusting whatever was passed in from
// the renderer's possibly-stale device object.
try {
var freshMac = await getMacFromArp(ip);
if (freshMac) { mac = freshMac; results.mac = freshMac; }
} catch(e) {}
try {
results.vendor = await lookupVendor(results.mac);
} catch(e) { results.vendor = 'Unbekannt'; }
try {
var netbios = await getNetbiosName(ip);
results.netbiosName = netbios;
} catch(e) { results.netbiosName = null; }
try {
var rdns = await getReverseDns(ip);
results.reverseDns = rdns;
} catch(e) { results.reverseDns = null; }
try {
var wol = await sendWakeOnLanProbe(mac);
results.wakeOnLan = wol;
} catch(e) { results.wakeOnLan = { sent: false, reason: 'Unbekannter Fehler' }; }
// Re-check ports with a slightly wider net for identification purposes —
// reuse the existing scanner, results are already useful context.
try {
results.openPorts = await scanPorts(ip);
} catch(e) { results.openPorts = []; }
var profiles = loadDeviceProfiles();
var profile = profiles[ip] || {};
var identityKeywords = loadIdentityKeywords();
results.identity = inferIdentityProfile(results, profile, identityKeywords);
return results;
});
function inferIdentityProfile(results, profile, keywords) {
var scores = {
'Windows-PC': 0,
'Linux-Server': 0,
'NAS/Storage': 0,
'Kamera/CCTV': 0,
'Alarmanlage/Security': 0,
'Router/Access Point': 0,
'Drucker': 0,
'IoT/Smart-Device': 0,
'Virtualisierungs-Host': 0,
'Konsole/Mediengeraet': 0
};
var reasons = [];
var vendor = (results.vendor || '').toLowerCase();
var nameHints = [results.netbiosName, results.reverseDns].filter(Boolean).join(' ').toLowerCase();
var ports = (results.openPorts || []).map(function(p) { return p.port; });
function hasPort(p) { return ports.indexOf(p) >= 0; }
function hasAnyPort(list) { return list.some(function(p) { return hasPort(p); }); }
function hasKeyword(haystack, words) {
return words.some(function(w) { return haystack.indexOf(w) >= 0; });
}
function hasConfiguredKeyword(scope, category, haystack) {
var list = ((keywords || {})[scope] || {})[category] || [];
return hasKeyword(haystack, list);
}
function add(category, points, reason) {
scores[category] = (scores[category] || 0) + points;
reasons.push(reason);
}
if (hasPort(3389)) add('Windows-PC', 4, 'Port 3389 (RDP) ist offen');
if (hasPort(445) || hasPort(139)) add('Windows-PC', 3, 'SMB/NetBIOS-Port ist offen');
if (hasPort(22)) add('Linux-Server', 3, 'Port 22 (SSH) ist offen');
if (hasAnyPort([9090, 19999])) add('Linux-Server', 2, 'Monitoring/Server-Port ist offen');
if (hasPort(8006)) add('Virtualisierungs-Host', 5, 'Port 8006 (Proxmox) ist offen');
if (hasAnyPort([25565, 25575])) add('Linux-Server', 2, 'Gaming-Server-Port ist offen');
if (hasPort(53)) add('Router/Access Point', 2, 'Port 53 (DNS) ist offen');
if (hasAnyPort([80, 443, 8080, 8443])) {
add('Router/Access Point', 1, 'Web-Management-Port ist offen');
}
// NAS/Storage
if (hasAnyPort([5000, 5001, 32400])) add('NAS/Storage', 4, 'Typischer NAS/Media-Port ist offen');
if (hasAnyPort([111, 2049, 873])) add('NAS/Storage', 3, 'NFS/Rsync-Port ist offen');
if (hasPort(445) && hasPort(5001)) add('NAS/Storage', 2, 'SMB + NAS-Webinterface Kombination erkannt');
// Camera / CCTV / NVR
if (hasAnyPort([554, 8554])) add('Kamera/CCTV', 5, 'RTSP-Port ist offen (Video-Stream)');
if (hasAnyPort([37777, 37778])) add('Kamera/CCTV', 4, 'Dahua-CCTV-Port ist offen');
if (hasPort(8000)) add('Kamera/CCTV', 3, 'Typischer Kamera/NVR-Port ist offen');
if (hasPort(7443)) add('Kamera/CCTV', 2, 'Video-Management Port ist offen');
// Alarm / security panels
if (hasPort(10001)) add('Alarmanlage/Security', 3, 'Typischer Alarm/Controller-Port ist offen');
if (hasPort(1883) || hasPort(5683)) add('IoT/Smart-Device', 3, 'IoT-Protokoll-Port ist offen');
if (hasPort(9100) || hasPort(515) || hasPort(631)) add('Drucker', 4, 'Typischer Drucker-Port ist offen');
if (hasConfiguredKeyword('vendor', 'router', vendor)) {
add('Router/Access Point', 3, 'Hersteller deutet auf Netzwerkhardware hin');
}
if (hasConfiguredKeyword('vendor', 'nas', vendor)) {
add('NAS/Storage', 4, 'Hersteller deutet auf NAS/Storage hin');
}
if (vendor.indexOf('supermicro') >= 0) {
add('Linux-Server', 3, 'Hersteller deutet auf Server-Hardware hin');
}
if (hasConfiguredKeyword('vendor', 'camera', vendor)) {
add('Kamera/CCTV', 5, 'Hersteller deutet auf Kamera/CCTV hin');
}
if (hasConfiguredKeyword('vendor', 'alarm', vendor)) {
add('Alarmanlage/Security', 5, 'Hersteller deutet auf Alarm/Security hin');
}
if (hasConfiguredKeyword('vendor', 'virtualization', vendor)) {
add('Virtualisierungs-Host', 4, 'Hersteller deutet auf Virtualisierung hin');
}
if (hasConfiguredKeyword('vendor', 'printer', vendor)) {
add('Drucker', 2, 'Hersteller passt zu Drucker/Office-Hardware');
}
if (vendor.indexOf('nintendo') >= 0 || vendor.indexOf('sony') >= 0) {
add('Konsole/Mediengeraet', 4, 'Hersteller passt zu Konsole/Media');
}
if (hasConfiguredKeyword('hostname', 'router', nameHints)) {
add('Router/Access Point', 2, 'Hostname deutet auf Router/AP hin');
}
if (hasConfiguredKeyword('hostname', 'nas', nameHints)) {
add('NAS/Storage', 3, 'Hostname deutet auf NAS/Storage hin');
}
if (nameHints.indexOf('server') >= 0 || nameHints.indexOf('plex') >= 0) {
add('Linux-Server', 2, 'Hostname deutet auf Server hin');
}
if (hasConfiguredKeyword('hostname', 'camera', nameHints)) {
add('Kamera/CCTV', 4, 'Hostname deutet auf Kamera/CCTV hin');
}
if (hasConfiguredKeyword('hostname', 'alarm', nameHints)) {
add('Alarmanlage/Security', 4, 'Hostname deutet auf Alarm/Security hin');
}
if (results.netbiosName) {
add('Windows-PC', 2, 'NetBIOS-Name wurde aufgeloest');
}
// Cross-signal boosters to improve confidence for your use-cases.
if (scores['Kamera/CCTV'] > 0 && (scores['Alarmanlage/Security'] > 0 || hasPort(554))) {
add('Kamera/CCTV', 1, 'Mehrere Kamera-/Security-Signale kombiniert');
}
if (scores['NAS/Storage'] > 0 && (hasPort(445) || hasPort(5001) || hasPort(32400))) {
add('NAS/Storage', 1, 'NAS-Signale aus Ports und Hersteller/Name kombiniert');
}
var top = Object.keys(scores)
.map(function(k) { return { type: k, score: scores[k] }; })
.sort(function(a, b) { return b.score - a.score; });
var best = top[0];
var second = top[1] || { score: 0 };
var confidence;
if (!best || best.score <= 0) confidence = 'niedrig';
else if (best.score >= 8 || (best.score >= 6 && (best.score - second.score) >= 3)) confidence = 'hoch';
else if (best.score >= 4) confidence = 'mittel';
else confidence = 'niedrig';
var identity = {
likelyType: (best && best.score > 0) ? best.type : 'Unbekannt',
confidence: confidence,
ranking: top.filter(function(x) { return x.score > 0; }).slice(0, 3),
reasons: reasons.slice(0, 8)
};
if (profile && profile.forcedType && profile.forcedType !== 'auto') {
identity.likelyType = profile.forcedType;
identity.confidence = 'manuell';
identity.ranking = [{ type: profile.forcedType, score: 999 }].concat(identity.ranking).slice(0, 3);
identity.reasons = ['Manuelle Geräteklasse gesetzt'].concat(identity.reasons).slice(0, 8);
identity.manualOverride = true;
}
return identity;
}
// ── OUI lookup ────────────────────────────────────────────
var OUI = {
'00:50:56':'VMware','00:0C:29':'VMware','00:1A:4B':'VMware','00:05:69':'VMware',
@@ -392,7 +718,12 @@ var DEFAULT_PORTS = [
{ port:22,name:'SSH',desc:'Sichere Fernsteuerung / Terminal-Zugriff' },
{ port:23,name:'Telnet',desc:'Unverschlüsselter Fernzugriff (veraltet)' },
{ port:25,name:'SMTP',desc:'E-Mail-Versand (Simple Mail Transfer Protocol)' },
{ port:111,name:'RPCbind',desc:'NFS/RPC Dienst (NAS/Linux)' },
{ port:53,name:'DNS',desc:'Domain Name System Namensauflösung' },
{ port:515,name:'LPD',desc:'Legacy Druckerwarteschlange (Line Printer Daemon)' },
{ port:554,name:'RTSP',desc:'Video-Stream (IP-Kamera/NVR)' },
{ port:631,name:'IPP',desc:'Internet Printing Protocol (Drucker)' },
{ port:873,name:'Rsync',desc:'Dateisynchronisierung (NAS/Server)' },
{ port:80,name:'HTTP',desc:'Unverschlüsselter Web-Server' },
{ port:110,name:'POP3',desc:'E-Mail-Empfang (Post Office Protocol)' },
{ port:135,name:'RPC',desc:'Windows Remote Procedure Call' },
@@ -400,18 +731,26 @@ var DEFAULT_PORTS = [
{ port:143,name:'IMAP',desc:'E-Mail-Empfang (Internet Message Access Protocol)' },
{ port:443,name:'HTTPS',desc:'Verschlüsselter Web-Server (SSL/TLS)' },
{ port:445,name:'SMB',desc:'Windows Dateifreigabe / Netzlaufwerke' },
{ port:5683,name:'CoAP',desc:'IoT-Protokoll (Constrained Application Protocol)' },
{ port:1883,name:'MQTT',desc:'Smart-Home Nachrichtenprotokoll (IoT)' },
{ port:2049,name:'NFS',desc:'Network File System (NAS/Linux)' },
{ port:3306,name:'MySQL',desc:'MySQL/MariaDB Datenbank-Server' },
{ port:3389,name:'RDP',desc:'Windows Remote Desktop Fernsteuerung' },
{ port:37777,name:'Dahua',desc:'Dahua CCTV Service Port' },
{ port:37778,name:'Dahua-Alt',desc:'Dahua CCTV Service Port (Alternative)' },
{ port:5000,name:'DSM HTTP',desc:'Synology DiskStation Manager (HTTP)' },
{ port:5001,name:'DSM HTTPS',desc:'Synology DiskStation Manager (HTTPS)' },
{ port:5900,name:'VNC',desc:'Virtual Network Computing Desktop-Fernzugriff' },
{ port:7443,name:'Video-Management',desc:'Video-Management / NVR Web-UI' },
{ port:8000,name:'CCTV-API',desc:'Hikvision/NVR Service Port' },
{ port:8006,name:'Proxmox',desc:'Proxmox VE Web-Oberfläche' },
{ port:8080,name:'HTTP-Alt',desc:'Alternativer HTTP-Port (oft Web-Apps)' },
{ port:8554,name:'RTSP-Alt',desc:'Alternativer RTSP Video-Stream Port' },
{ port:8443,name:'HTTPS-Alt',desc:'Alternativer HTTPS-Port (oft Web-Apps)' },
{ port:9000,name:'Portainer',desc:'Portainer Docker-Verwaltung' },
{ port:9090,name:'Cockpit',desc:'Linux Cockpit Server-Verwaltung' },
{ port:9100,name:'Prometheus',desc:'Prometheus Node Exporter (Monitoring)' },
{ port:10001,name:'Alarm-Controller',desc:'Haeufig bei Alarm-/Security-Steuergeraeten' },
{ port:19999,name:'Netdata',desc:'Netdata Echtzeit-Monitoring Dashboard' },
{ port:25565,name:'Minecraft',desc:'Minecraft Java Edition Server' },
{ port:25575,name:'MC RCON',desc:'Minecraft Remote Console (RCON)' },

View File

@@ -13,8 +13,12 @@ contextBridge.exposeInMainWorld('api', {
savePorts: (p) => ipcRenderer.invoke('save-ports', p),
getDeviceNames: () => ipcRenderer.invoke('get-device-names'),
saveDeviceNames: (n) => ipcRenderer.invoke('save-device-names', n),
getDeviceProfiles: () => ipcRenderer.invoke('get-device-profiles'),
saveDeviceProfiles: (p) => ipcRenderer.invoke('save-device-profiles', p),
getFavorites: () => ipcRenderer.invoke('get-favorites'),
saveFavorites: (f) => ipcRenderer.invoke('save-favorites', f),
getIdentityKeywords: () => ipcRenderer.invoke('get-identity-keywords'),
saveIdentityKeywords:(k) => ipcRenderer.invoke('save-identity-keywords', k),
getSettings: () => ipcRenderer.invoke('get-settings'),
saveSettings: (s) => ipcRenderer.invoke('save-settings', s),
getHistory: () => ipcRenderer.invoke('get-history'),
@@ -29,6 +33,7 @@ contextBridge.exposeInMainWorld('api', {
saveMonitoredSubnets:(s) => ipcRenderer.invoke('save-monitored-subnets', s),
getIpv6Neighbors: () => ipcRenderer.invoke('get-ipv6-neighbors'),
exportDeviceList: (d,f) => ipcRenderer.invoke('export-device-list', { devices:d, format:f }),
identifyDevice: (ip,mac)=> ipcRenderer.invoke('identify-device', { ip:ip, mac:mac }),
openEditor: (l) => ipcRenderer.invoke('open-editor', l),
exportPdf: (h) => ipcRenderer.invoke('export-pdf', h),
notify: (d) => ipcRenderer.send('notify', d),