From c1b9bc78488e3bba11537132f0701fb77f9939a0 Mon Sep 17 00:00:00 2001 From: Git Manager GUI Date: Wed, 17 Jun 2026 22:00:49 +0200 Subject: [PATCH] Upload folder via GUI - src --- src/editor-preload.js | 8 + src/editor.html | 1013 +++++++++++++++++++++++++++++++ src/index.html | 1335 +++++++++++++++++++++++++++++++++++++++++ src/main.js | 625 +++++++++++++++++++ src/preload.js | 44 ++ 5 files changed, 3025 insertions(+) create mode 100644 src/editor-preload.js create mode 100644 src/editor.html create mode 100644 src/index.html create mode 100644 src/main.js create mode 100644 src/preload.js diff --git a/src/editor-preload.js b/src/editor-preload.js new file mode 100644 index 0000000..6c9f0f4 --- /dev/null +++ b/src/editor-preload.js @@ -0,0 +1,8 @@ +'use strict'; +const { contextBridge, ipcRenderer } = require('electron'); + +contextBridge.exposeInMainWorld('editorApi', { + exportPdf: (html) => ipcRenderer.invoke('export-pdf', html), + pingDevice: (ip) => ipcRenderer.invoke('ping-device', ip), + onPingResult: (cb) => ipcRenderer.on('ping-result', (e, d) => cb(d)) +}); diff --git a/src/editor.html b/src/editor.html new file mode 100644 index 0000000..2cc80ff --- /dev/null +++ b/src/editor.html @@ -0,0 +1,1013 @@ + + + + +Netzwerk-Editor + + + +
+ ๐Ÿ”Œ Netzwerk-Editor +
+ + + + + + +
+ + + + +
+ + +
+ + + +
+ + + + + +
+ + + โœ“ Gespeichert +
+ +
+ +
Ziel-Gerรคt klicken ยท ESC = Abbrechen
+ + +
+ + + + diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..c09963f --- /dev/null +++ b/src/index.html @@ -0,0 +1,1335 @@ + + + + + +NetScanner + + + + + +
+
+ + NetScanner + Netzwerk-Analyse + โŸณ Auto-Scan aktiv +
+
+ + + + +
+
+ + +
+ + +
+ + + + + +
+
+ + + + Gerรคt aus der Liste auswรคhlen +
+
+
+
+ + + + + + + + + + + + + + + + + + + diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..9811887 --- /dev/null +++ b/src/main.js @@ -0,0 +1,625 @@ +'use strict'; + +const { app, BrowserWindow, ipcMain, Notification, shell, Tray, Menu, nativeImage } = require('electron'); +const path = require('path'); +const { execFile, exec } = require('child_process'); +const os = require('os'); +const net = require('net'); +const fs = require('fs'); + +let mainWindow; +var DEBUG_SCAN = false; // verbose console logging for scan diagnostics โ€” set to true if troubleshooting again +let tray = null; +var autoScanTimer = null; +var autoScanInterval = 0; // minutes, 0 = off +var isQuitting = false; + +// โ”€โ”€ Helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +function dataPath(file) { return path.join(app.getPath('userData'), file); } +function readJson(file, fallback) { + try { var d = fs.readFileSync(dataPath(file), 'utf8'); return JSON.parse(d); } catch(e) { return fallback; } +} +function writeJson(file, data) { + try { fs.writeFileSync(dataPath(file), JSON.stringify(data, null, 2), 'utf8'); return true; } catch(e) { return false; } +} + +// โ”€โ”€ Port config โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +ipcMain.handle('get-ports', () => readJson('custom-ports.json', null) || DEFAULT_PORTS); +ipcMain.handle('save-ports', (e, ports) => { COMMON_PORTS = ports; return writeJson('custom-ports.json', ports); }); + +// โ”€โ”€ Device names โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +ipcMain.handle('get-device-names', () => readJson('device-names.json', {})); +ipcMain.handle('save-device-names', (e, names) => writeJson('device-names.json', names)); + +// โ”€โ”€ Favorites โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +ipcMain.handle('get-favorites', () => readJson('favorites.json', [])); +ipcMain.handle('save-favorites', (e, favs) => writeJson('favorites.json', favs)); + +// โ”€โ”€ Settings โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +ipcMain.handle('get-settings', () => readJson('settings.json', { theme: 'dark', autoScan: 0, notifyNew: true, notifyOffline: true })); +ipcMain.handle('save-settings', (e, s) => { + writeJson('settings.json', s); + autoScanInterval = s.autoScan || 0; + resetAutoScan(); + return true; +}); + +// โ”€โ”€ Scan history โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +function loadHistory() { return readJson('scan-history.json', []); } +function saveHistory(h) { writeJson('scan-history.json', h.slice(0, 200)); } + +ipcMain.handle('get-history', () => loadHistory()); +ipcMain.handle('clear-history', () => { writeJson('scan-history.json', []); return true; }); + +function addHistoryEntry(entry) { + var h = loadHistory(); + h.unshift(entry); + saveHistory(h); +} + +// โ”€โ”€ Uptime tracking โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +// Records per-IP: { onlineSince: timestamp|null, lastSeen: timestamp } +function loadUptimeData() { return readJson('uptime.json', {}); } +function saveUptimeData(d) { writeJson('uptime.json', d); } + +function updateUptime(ip, isOnline) { + var data = loadUptimeData(); + var now = Date.now(); + if (!data[ip]) data[ip] = { onlineSince: null, lastSeen: null, lastOffline: null }; + if (isOnline) { + if (!data[ip].onlineSince) data[ip].onlineSince = now; + data[ip].lastSeen = now; + } else { + data[ip].onlineSince = null; + data[ip].lastOffline = now; + } + saveUptimeData(data); + return data[ip]; +} + +ipcMain.handle('get-uptime', (e, ip) => { + var data = loadUptimeData(); + return data[ip] || { onlineSince: null, lastSeen: null, lastOffline: null }; +}); + +// โ”€โ”€ Ping history for sparkline graphs โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +// In-memory only (resets on restart) โ€“ keeps last N points per IP +var pingHistory = {}; // { ip: [{t,ms}, ...] } +var PING_HISTORY_MAX = 40; + +function recordPingHistory(ip, ms) { + if (!pingHistory[ip]) pingHistory[ip] = []; + pingHistory[ip].push({ t: Date.now(), ms: ms }); + if (pingHistory[ip].length > PING_HISTORY_MAX) pingHistory[ip].shift(); +} + +ipcMain.handle('get-ping-history', (e, ip) => pingHistory[ip] || []); + +// โ”€โ”€ Named network plans (multiple saved schaltplans) โ”€โ”€โ”€โ”€โ”€โ”€ +ipcMain.handle('get-plans', () => readJson('plans.json', {})); +ipcMain.handle('save-plan', (e, { name, data }) => { + var plans = readJson('plans.json', {}); + plans[name] = { data: data, savedAt: Date.now() }; + return writeJson('plans.json', plans); +}); +ipcMain.handle('delete-plan', (e, name) => { + var plans = readJson('plans.json', {}); + delete plans[name]; + return writeJson('plans.json', plans); +}); + +// โ”€โ”€ Multi-subnet monitoring โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +ipcMain.handle('get-monitored-subnets', () => readJson('monitored-subnets.json', [])); +ipcMain.handle('save-monitored-subnets', (e, subnets) => writeJson('monitored-subnets.json', subnets)); + +// โ”€โ”€ Window โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +function createWindow() { + mainWindow = new BrowserWindow({ + width: 1200, height: 780, minWidth: 900, minHeight: 600, + frame: false, backgroundColor: '#0d1117', + webPreferences: { preload: path.join(__dirname, 'preload.js'), contextIsolation: true, nodeIntegration: false }, + icon: path.join(__dirname, '../assets/icon.png') + }); + mainWindow.loadFile(path.join(__dirname, 'index.html')); + + // F12 opens DevTools even though the window is frameless (no menu bar) + mainWindow.webContents.on('before-input-event', function(event, input) { + if (input.key === 'F12') mainWindow.webContents.toggleDevTools(); + }); + + // Mirror every renderer console.log/error/warn into this terminal so + // frontend errors are visible without needing to open DevTools manually. + mainWindow.webContents.on('console-message', function(event, level, message, line, sourceId) { + var levelNames = ['LOG','WARN','ERROR','DEBUG']; + console.log('[renderer:' + (levelNames[level] || level) + ']', message, '(line ' + line + ')'); + }); + + mainWindow.on('close', function(e) { + var settings = readJson('settings.json', {}); + if (settings.closeToTray && !isQuitting) { + e.preventDefault(); + mainWindow.hide(); + } + }); +} + +function createTray() { + try { + var iconPath = path.join(__dirname, '../assets/icon.png'); + var img = fs.existsSync(iconPath) ? nativeImage.createFromPath(iconPath) : nativeImage.createEmpty(); + tray = new Tray(img.isEmpty() ? nativeImage.createEmpty() : img.resize({ width: 16, height: 16 })); + tray.setToolTip('NetScanner โ€“ lรคuft im Hintergrund'); + var contextMenu = Menu.buildFromTemplate([ + { label: 'ร–ffnen', click: function() { mainWindow.show(); } }, + { label: 'Jetzt scannen', click: function() { mainWindow.show(); mainWindow.webContents.send('auto-scan-trigger'); } }, + { type: 'separator' }, + { label: 'Beenden', click: function() { isQuitting = true; app.quit(); } } + ]); + tray.setContextMenu(contextMenu); + tray.on('click', function() { mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show(); }); + } catch(e) { /* tray not supported on this platform/config */ } +} + +process.on('uncaughtException', function(err) { + console.error('[UNCAUGHT EXCEPTION]', err); +}); +process.on('unhandledRejection', function(reason) { + console.error('[UNHANDLED REJECTION]', reason); +}); + +app.whenReady().then(function() { + var saved = readJson('custom-ports.json', null); + if (saved) COMMON_PORTS = saved; + var settings = readJson('settings.json', { autoScan: 0 }); + autoScanInterval = settings.autoScan || 0; + createWindow(); + createTray(); + resetAutoScan(); +}); +app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); }); +app.on('before-quit', () => { isQuitting = true; }); + +// โ”€โ”€ Window controls โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +ipcMain.on('win-minimize', () => mainWindow.minimize()); +ipcMain.on('win-maximize', () => { mainWindow.isMaximized() ? mainWindow.unmaximize() : mainWindow.maximize(); }); +ipcMain.on('win-close', () => mainWindow.close()); +ipcMain.on('quit-app', () => { isQuitting = true; app.quit(); }); + +// โ”€โ”€ IPv6 neighbor discovery โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +function getIPv6Neighbors() { + return new Promise(function(resolve) { + var cmd = process.platform === 'win32' ? 'netsh interface ipv6 show neighbors' : 'ip -6 neigh show'; + exec(cmd, { timeout: 3000 }, function(err, stdout) { + if (err || !stdout) return resolve([]); + var results = []; + var lines = stdout.split('\n'); + lines.forEach(function(line) { + // Match IPv6 address (not link-local fe80:: unless specifically wanted) + MAC + var ipMatch = line.match(/([0-9a-f]{0,4}:[0-9a-f:]+:[0-9a-f]{0,4})/i); + var macMatch = line.match(/([0-9a-f]{2}[:-]){5}[0-9a-f]{2}/i); + if (ipMatch && macMatch && !ipMatch[1].startsWith('fe80')) { + results.push({ ip: ipMatch[1], mac: macMatch[0].toUpperCase() }); + } + }); + resolve(results); + }); + }); +} + +ipcMain.handle('get-ipv6-neighbors', async () => await getIPv6Neighbors()); + +// โ”€โ”€ Auto scan โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +function resetAutoScan() { + if (autoScanTimer) { clearInterval(autoScanTimer); autoScanTimer = null; } + if (autoScanInterval > 0) { + autoScanTimer = setInterval(function() { + if (mainWindow) mainWindow.webContents.send('auto-scan-trigger'); + }, autoScanInterval * 60 * 1000); + } +} + +// โ”€โ”€ Notifications โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +ipcMain.on('notify', (e, { title, body, type }) => { + var settings = readJson('settings.json', { notifyNew: true, notifyOffline: true }); + if (type === 'new' && !settings.notifyNew) return; + if (type === 'offline' && !settings.notifyOffline) return; + if (Notification.isSupported()) { + new Notification({ title: title, body: body, silent: false }).show(); + } +}); + +// โ”€โ”€ Vendor lookup โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +ipcMain.handle('lookup-vendor', async (e, mac) => await lookupVendor(mac)); + +// โ”€โ”€ Network info โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +ipcMain.handle('get-network-info', () => { + var ifaces = os.networkInterfaces(); + var results = []; + Object.keys(ifaces).forEach(function(name) { + ifaces[name].forEach(function(iface) { + if (iface.family === 'IPv4' && !iface.internal) { + var parts = iface.address.split('.'); + results.push({ name: name, address: iface.address, subnet: parts[0]+'.'+parts[1]+'.'+parts[2]+'.', mac: iface.mac }); + } + }); + }); + return results; +}); + +// โ”€โ”€ Ping โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +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) { + stdout = stdout || ''; + var alive = !err && (stdout.includes('TTL=') || stdout.includes('ttl=') || stdout.includes('1 received')); + var ttl = null, os_guess = null; + var ttlMatch = stdout.match(/TTL=(\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'; + } + 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); + } + 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 }); + }); + }); +} + +ipcMain.handle('ping-device', async (e, ip) => await pingHost(ip)); + +// โ”€โ”€ Ping multiple devices at once (for live updates) โ”€โ”€โ”€โ”€โ”€โ”€ +// Pings are chunked instead of all-at-once: spawning 30+ ping processes +// simultaneously creates OS scheduling contention that inflates wall-clock +// timings across the board. Chunks of 12 keep this from happening. +ipcMain.handle('ping-devices-batch', async function(event, ips) { + var results = []; + var chunkSize = 12; + for (var i = 0; i < ips.length; i += chunkSize) { + var chunk = ips.slice(i, i + chunkSize); + var chunkResults = await Promise.all(chunk.map(function(ip) { return pingHost(ip); })); + results = results.concat(chunkResults); + } + results.forEach(function(r) { + recordPingHistory(r.ip, r.alive ? r.ms : null); + updateUptime(r.ip, r.alive); + }); + return results; +}); + +// โ”€โ”€ Hostname โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +function getHostname(ip) { + return new Promise(function(resolve) { + exec('nslookup ' + ip, { timeout: 2000 }, function(err, stdout) { + if (!err && stdout) { var m = stdout.match(/Name:\s+(.+)/i); if (m) return resolve(m[1].trim().split('.')[0]); } + resolve(null); + }); + }); +} + +// โ”€โ”€ MAC from ARP โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +function getMacFromArp(ip) { + return new Promise(function(resolve) { + exec('arp -a ' + ip, { timeout: 2000 }, function(err, stdout) { + if (!err && stdout) { var m = stdout.match(/([0-9a-f]{2}[:-]){5}[0-9a-f]{2}/i); if (m) return resolve(m[0].toUpperCase()); } + resolve(null); + }); + }); +} + +// โ”€โ”€ OUI lookup โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +var OUI = { + '00:50:56':'VMware','00:0C:29':'VMware','00:1A:4B':'VMware','00:05:69':'VMware', + '52:54:00':'QEMU/KVM','00:16:3E':'Xen', + 'B8:27:EB':'Raspberry Pi','DC:A6:32':'Raspberry Pi','E4:5F:01':'Raspberry Pi','28:CD:C1':'Raspberry Pi', + '00:1B:21':'Intel','8C:8D:28':'Intel','00:21:6A':'Intel','A4:C3:F0':'Intel','00:1F:3C':'Intel','A0:36:9F':'Intel', + 'AC:22:05':'ASUS','04:D4:C4':'ASUS','14:DA:E9':'ASUS','2C:4D:54':'ASUS','BC:AE:C5':'ASUS','74:D0:2B':'ASUS', + '18:31:BF':'Apple','3C:06:30':'Apple','F0:18:98':'Apple','50:1A:C5':'Apple','A4:CF:99':'Apple', + 'AC:BC:32':'Apple','F4:F1:5A':'Apple','DC:2B:2A':'Apple','A8:51:AB':'Apple','3C:22:FB':'Apple', + 'AC:E2:D3':'Hewlett Packard','00:17:08':'Hewlett Packard','00:1F:29':'Hewlett Packard', + '3C:D9:2B':'Hewlett Packard','94:57:A5':'Hewlett Packard','B4:B5:2F':'Hewlett Packard', + '00:14:22':'Dell','F8:DB:88':'Dell','BC:30:5B':'Dell','18:FB:7B':'Dell','B8:AC:6F':'Dell', + '00:23:AE':'Lenovo','54:EE:75':'Lenovo','E8:6A:64':'Lenovo','28:D2:44':'Lenovo', + 'FC:AA:14':'Synology','00:11:32':'Synology','BC:5F:F4':'Synology', + 'CC:9F:06':'TP-Link','F4:F2:6D':'TP-Link','18:D6:C7':'TP-Link','50:C7:BF':'TP-Link','AC:84:C6':'TP-Link', + 'B4:FB:E4':'AVM (Fritz!Box)','00:04:0E':'AVM (Fritz!Box)','C4:86:E9':'AVM (Fritz!Box)', + 'AC:16:2D':'AVM (Fritz!Box)','3C:37:12':'AVM (Fritz!Box)','A0:63:91':'AVM (Fritz!Box)', + '08:96:D7':'AVM (Fritz!Box)','1C:4B:D6':'AVM (Fritz!Box)','34:31:C4':'AVM (Fritz!Box)', + '7C:FF:4D':'AVM (Fritz!Box)','9C:C7:A6':'AVM (Fritz!Box)','D4:21:22':'AVM (Fritz!Box)', + 'E0:14:C8':'AVM (Fritz!Box)','F8:1A:67':'AVM (Fritz!Box)','BC:05:43':'AVM (Fritz!Box)', + 'D8:61:62':'Ubiquiti','24:A4:3C':'Ubiquiti','80:2A:A8':'Ubiquiti','F0:9F:C2':'Ubiquiti', + '00:1E:67':'Cisco','00:50:0F':'Cisco','AC:F2:C5':'Cisco','00:00:0C':'Cisco', + 'C8:D3:A3':'Huawei','00:E0:FC':'Huawei','28:6E:D4':'Huawei', + '00:12:47':'Samsung','08:D4:0C':'Samsung','F4:7B:5E':'Samsung','CC:07:AB':'Samsung','8C:77:12':'Samsung', + '98:DA:C4':'Xiaomi','64:09:80':'Xiaomi','50:64:2B':'Xiaomi', + '00:E0:4C':'Realtek', + '00:17:9A':'D-Link','1C:7E:E5':'D-Link','C8:BE:19':'D-Link', + '00:14:6C':'Netgear','20:E5:2A':'Netgear','A0:21:B7':'Netgear', + '4C:5E:0C':'MikroTik','B8:69:F4':'MikroTik','CC:2D:E0':'MikroTik', + 'AC:1F:6B':'Supermicro','0C:C4:7A':'Supermicro', + '00:08:9B':'QNAP','24:5E:BE':'QNAP', + '00:09:BF':'Nintendo','00:17:AB':'Nintendo','58:BD:A3':'Nintendo', + '00:13:A9':'Sony','F8:DA:0C':'Sony','30:17:C8':'Sony', + 'BC:24:11':'Proxmox Server Solutions GmbH','00:23:24':'G-PRO COMPUTER', + '8C:11:CB':'ABUS Security-Center GmbH','94:B3:F7':'Hui Zhou Gaoshengda Technology', + 'E0:03:6B':'Samsung Electronics Co.,Ltd','F4:B8:5E':'Texas Instruments', + '00:1B:A9':'Brother Industries','E0:D3:62':'TP-Link Systems Inc.', + '12:C5:74':'Private/Unknown' +}; + +var vendorCache = {}; +function lookupVendorApi(mac) { + return new Promise(function(resolve) { + var normalized = mac.replace(/[:\-]/g,'').toUpperCase().match(/.{2}/g).join(':'); + var oui = normalized.substring(0,8); + if (vendorCache[oui]) return resolve(vendorCache[oui]); + var url = 'https://api.maclookup.app/v2/macs/' + normalized; + var https = require('https'); + var req = https.get(url, { timeout: 3000 }, function(res) { + var data = ''; + res.on('data', function(c) { data += c; }); + res.on('end', function() { + try { + var json = JSON.parse(data); + var vendor = (json.company && json.company !== 'Private') ? json.company : 'Unbekannt'; + vendorCache[oui] = vendor; resolve(vendor); + } catch(e) { resolve('Unbekannt'); } + }); + }); + req.on('error', () => resolve('Unbekannt')); + req.on('timeout', () => { req.destroy(); resolve('Unbekannt'); }); + }); +} + +async function lookupVendor(mac) { + if (!mac || mac === 'โ€“') return 'Unbekannt'; + var normalized = mac.replace(/-/g,':').toUpperCase(); + var oui = normalized.substring(0,8); + if (OUI[oui]) return OUI[oui]; + return await lookupVendorApi(normalized); +} + +// โ”€โ”€ Ports โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +var DEFAULT_PORTS = [ + { port:21,name:'FTP',desc:'Dateiรผbertragung (File Transfer Protocol)' }, + { 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:53,name:'DNS',desc:'Domain Name System โ€“ Namensauflรถsung' }, + { 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' }, + { port:139,name:'NetBIOS',desc:'Windows Netzwerkfreigabe (NetBIOS)' }, + { 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:1883,name:'MQTT',desc:'Smart-Home Nachrichtenprotokoll (IoT)' }, + { port:3306,name:'MySQL',desc:'MySQL/MariaDB Datenbank-Server' }, + { port:3389,name:'RDP',desc:'Windows Remote Desktop โ€“ Fernsteuerung' }, + { 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:8006,name:'Proxmox',desc:'Proxmox VE Web-Oberflรคche' }, + { port:8080,name:'HTTP-Alt',desc:'Alternativer HTTP-Port (oft Web-Apps)' }, + { 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: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)' }, + { port:32400,name:'Plex',desc:'Plex Media Server' } +]; +var COMMON_PORTS = DEFAULT_PORTS; + +function scanPort(ip, port) { + return new Promise(function(resolve) { + var socket = new net.Socket(); var done = false; socket.setTimeout(600); + socket.on('connect', function() { done=true; socket.destroy(); resolve(true); }); + socket.on('timeout', function() { if(!done){done=true;socket.destroy();resolve(false);} }); + socket.on('error', function() { if(!done){done=true;resolve(false);} }); + socket.connect(port, ip); + }); +} + +async function scanPorts(ip) { + var results = []; var batch = 5; + for (var i = 0; i < COMMON_PORTS.length; i += batch) { + var slice = COMMON_PORTS.slice(i, i+batch); + var checks = await Promise.all(slice.map(function(p) { return scanPort(ip,p.port).then(function(open){return open?p:null;}); })); + checks.forEach(function(r) { if(r) results.push(r); }); + } + return results; +} + +// โ”€โ”€ Security warnings: flag insecure/risky open ports โ”€โ”€โ”€โ”€โ”€ +var INSECURE_PORTS = { + 21: 'FTP รผbermittelt Zugangsdaten unverschlรผsselt', + 23: 'Telnet ist komplett unverschlรผsselt โ€“ Fernzugriff vermeiden', + 139: 'NetBIOS ist ein hรคufiges Angriffsziel im LAN', + 445: 'SMB war Ziel diverser Wรผrmer (z.B. WannaCry) โ€“ nur intern erlauben', + 3389: 'RDP sollte nie direkt ins Internet exponiert werden', + 5900: 'VNC ist oft unverschlรผsselt โ€“ Passwortschutz prรผfen' +}; + +function getSecurityWarnings(openPorts) { + var warnings = []; + openPorts.forEach(function(p) { + if (INSECURE_PORTS[p.port]) warnings.push({ port: p.port, name: p.name, warning: INSECURE_PORTS[p.port] }); + }); + return warnings; +} + +ipcMain.handle('get-security-warnings', (e, openPorts) => getSecurityWarnings(openPorts)); + +// โ”€โ”€ Full scan โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +var lastScanResults = []; + +async function performScan(subnet, sendProgress) { + if (DEBUG_SCAN) console.log('[performScan] START subnet:', subnet, 'sendProgress:', sendProgress); + var found = []; var batch = 50; var ips = []; + for (var i=1;i<=254;i++) ips.push(subnet+i); + + for (var j=0;j 0 && prevIps.indexOf(d.ip) < 0) { + mainWindow.webContents.send('device-new', d); + if (settings.notifyNew && Notification.isSupported()) { + new Notification({ title:'NetScanner โ€“ Neues Gerรคt', body: (d.hostname||d.ip) + ' (' + d.ip + ') ist dem Netzwerk beigetreten.', silent:false }).show(); + } + } + }); + + lastScanResults.forEach(function(d) { + if (currIps.indexOf(d.ip) < 0) { + mainWindow.webContents.send('device-offline', d); + updateUptime(d.ip, false); + if (settings.notifyOffline && Notification.isSupported()) { + new Notification({ title:'NetScanner โ€“ Gerรคt offline', body: (d.hostname||d.ip) + ' (' + d.ip + ') ist nicht mehr erreichbar.', silent:false }).show(); + } + } + }); + + lastScanResults = enriched; + addHistoryEntry({ time: Date.now(), subnet: subnet, count: enriched.length, devices: enriched.map(function(d){return {ip:d.ip,hostname:d.hostname,ms:d.ms,os:d.os};}) }); + } catch(e) { + console.error('scan-network error:', e); + } + try { mainWindow.webContents.send('scan-progress', 100); } catch(e2) {} + if (DEBUG_SCAN) console.log('[scan-network] returning', enriched.length, 'devices to renderer'); + return enriched; +}); + +// โ”€โ”€ Scan an additional subnet (multi-subnet monitoring) โ”€โ”€โ”€ +ipcMain.handle('scan-subnet-silent', async function(event, subnet) { + return await performScan(subnet, false); +}); + +ipcMain.handle('scan-ports', async (e,ip) => await scanPorts(ip)); + +// โ”€โ”€ Export device list as CSV/JSON โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +ipcMain.handle('export-device-list', async function(event, { devices, format }) { + var { dialog } = require('electron'); + var ext = format === 'json' ? 'json' : 'csv'; + var result = await dialog.showSaveDialog(mainWindow, { + title: 'Gerรคteliste exportieren', + defaultPath: 'NetScanner-Geraete-' + new Date().toISOString().slice(0,10) + '.' + ext, + filters: [{ name: ext.toUpperCase(), extensions: [ext] }] + }); + if (result.canceled || !result.filePath) return { success: false }; + + var content; + if (format === 'json') { + content = JSON.stringify(devices, null, 2); + } else { + var header = 'IP,Name,Hostname,MAC,Hersteller,Ping(ms),OS,IPv6\n'; + var rows = devices.map(function(d) { + return [d.ip, (d.displayName||''), (d.hostname||''), (d.mac||''), (d.vendor||''), (d.ms!=null?d.ms:''), (d.os||''), (d.ipv6||'')] + .map(function(v){ return '"' + String(v).replace(/"/g,'""') + '"'; }).join(','); + }); + content = header + rows.join('\n'); + } + fs.writeFileSync(result.filePath, content, 'utf8'); + shell.showItemInFolder(result.filePath); + return { success: true }; +}); +ipcMain.handle('open-editor', async function(event, layoutData) { + var { BrowserWindow: BW } = require('electron'); + var editorWin = new BW({ width:1300, height:800, title:'Netzwerk-Editor', frame:true, backgroundColor:'#0d1117', + webPreferences:{ nodeIntegration:false, contextIsolation:true, preload:path.join(__dirname,'editor-preload.js') } + }); + editorWin.loadFile(path.join(__dirname,'editor.html')); + editorWin.webContents.on('did-finish-load', function() { + editorWin.webContents.executeJavaScript('window.postMessage('+JSON.stringify({type:'init',nodes:layoutData.nodes,connections:layoutData.connections,groups:layoutData.groups||[]})+', "*")'); + }); + return { success:true }; +}); + +// โ”€โ”€ PDF Export โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +ipcMain.handle('export-pdf', async function(event, htmlContent) { + var { BrowserWindow: BW, dialog } = require('electron'); + var result = await dialog.showSaveDialog(mainWindow, { + title:'Netzwerk-Schaltplan speichern', + defaultPath:'NetScanner-Schaltplan-'+new Date().toISOString().slice(0,10)+'.pdf', + filters:[{name:'PDF',extensions:['pdf']}] + }); + if (result.canceled || !result.filePath) return { success:false }; + var tmpHtml = path.join(os.tmpdir(),'netscanner-export-'+Date.now()+'.html'); + fs.writeFileSync(tmpHtml, htmlContent, 'utf8'); + var win = new BW({ show:false, width:1200, height:900, webPreferences:{nodeIntegration:false,contextIsolation:true} }); + await win.loadFile(tmpHtml); + await new Promise(function(r){setTimeout(r,2000);}); + var pdfData = await win.webContents.printToPDF({ printBackground:true, pageSize:'A4', landscape:true, margins:{top:0.4,bottom:0.4,left:0.4,right:0.4} }); + win.close(); + try { fs.unlinkSync(tmpHtml); } catch(e) {} + fs.writeFileSync(result.filePath, pdfData); + shell.openPath(result.filePath); + return { success:true }; +}); diff --git a/src/preload.js b/src/preload.js new file mode 100644 index 0000000..e91debd --- /dev/null +++ b/src/preload.js @@ -0,0 +1,44 @@ +'use strict'; +const { contextBridge, ipcRenderer } = require('electron'); + +contextBridge.exposeInMainWorld('api', { + getNetworkInfo: () => ipcRenderer.invoke('get-network-info'), + scanNetwork: (s) => ipcRenderer.invoke('scan-network', s), + scanSubnetSilent: (s) => ipcRenderer.invoke('scan-subnet-silent', s), + scanPorts: (ip) => ipcRenderer.invoke('scan-ports', ip), + pingDevice: (ip) => ipcRenderer.invoke('ping-device', ip), + pingDevicesBatch: (ips) => ipcRenderer.invoke('ping-devices-batch', ips), + lookupVendor: (mac) => ipcRenderer.invoke('lookup-vendor', mac), + getPorts: () => ipcRenderer.invoke('get-ports'), + savePorts: (p) => ipcRenderer.invoke('save-ports', p), + getDeviceNames: () => ipcRenderer.invoke('get-device-names'), + saveDeviceNames: (n) => ipcRenderer.invoke('save-device-names', n), + getFavorites: () => ipcRenderer.invoke('get-favorites'), + saveFavorites: (f) => ipcRenderer.invoke('save-favorites', f), + getSettings: () => ipcRenderer.invoke('get-settings'), + saveSettings: (s) => ipcRenderer.invoke('save-settings', s), + getHistory: () => ipcRenderer.invoke('get-history'), + clearHistory: () => ipcRenderer.invoke('clear-history'), + getUptime: (ip) => ipcRenderer.invoke('get-uptime', ip), + getPingHistory: (ip) => ipcRenderer.invoke('get-ping-history', ip), + getSecurityWarnings: (ports) => ipcRenderer.invoke('get-security-warnings', ports), + getPlans: () => ipcRenderer.invoke('get-plans'), + savePlan: (n,d) => ipcRenderer.invoke('save-plan', { name:n, data:d }), + deletePlan: (n) => ipcRenderer.invoke('delete-plan', n), + getMonitoredSubnets: () => ipcRenderer.invoke('get-monitored-subnets'), + 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 }), + openEditor: (l) => ipcRenderer.invoke('open-editor', l), + exportPdf: (h) => ipcRenderer.invoke('export-pdf', h), + notify: (d) => ipcRenderer.send('notify', d), + onDeviceFound: (cb) => ipcRenderer.on('device-found', (e,d) => cb(d)), + onScanProgress: (cb) => ipcRenderer.on('scan-progress', (e,p) => cb(p)), + onDeviceNew: (cb) => ipcRenderer.on('device-new', (e,d) => cb(d)), + onDeviceOffline: (cb) => ipcRenderer.on('device-offline', (e,d) => cb(d)), + onAutoScan: (cb) => ipcRenderer.on('auto-scan-trigger', () => cb()), + winMinimize: () => ipcRenderer.send('win-minimize'), + winMaximize: () => ipcRenderer.send('win-maximize'), + winClose: () => ipcRenderer.send('win-close'), + quitApp: () => ipcRenderer.send('quit-app') +});