From bc9a453452a4ae02a365394898ae209d0af481c7 Mon Sep 17 00:00:00 2001 From: Hein Erik Date: Thu, 2 Apr 2026 16:09:55 +0200 Subject: [PATCH] Initial commit --- WebRDM/api.php | 298 ++++++++++++++++++++++++++++++++++++++++++ WebRDM/css/style.css | 71 ++++++++++ WebRDM/db/rdm.db | Bin 0 -> 61440 bytes WebRDM/db/secret.key | Bin 0 -> 32 bytes WebRDM/index.html | 64 +++++++++ WebRDM/js/app.js | 302 +++++++++++++++++++++++++++++++++++++++++++ WebRDM/map.html | 50 +++++++ 7 files changed, 785 insertions(+) create mode 100644 WebRDM/api.php create mode 100644 WebRDM/css/style.css create mode 100644 WebRDM/db/rdm.db create mode 100644 WebRDM/db/secret.key create mode 100644 WebRDM/index.html create mode 100644 WebRDM/js/app.js create mode 100644 WebRDM/map.html diff --git a/WebRDM/api.php b/WebRDM/api.php new file mode 100644 index 0000000..e70007e --- /dev/null +++ b/WebRDM/api.php @@ -0,0 +1,298 @@ +prepare("INSERT INTO audit_logs (user, action, details) VALUES (?, ?, ?)"); + $stmt->execute([$user, $action, $details]); +} + +function generateTOTPSecret() { $b32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; $s = ""; for ($i = 0; $i < 16; $i++) $s .= $b32[random_int(0, 31)]; return $s; } +function verifyTOTP($secret, $code) { + if (empty($secret) || strlen($code) != 6) return false; + $b32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; $secret = strtoupper($secret); $dec = ""; + for ($i = 0; $i < strlen($secret); $i++) { $n = strpos($b32, $secret[$i]); if ($n === false) return false; $dec .= str_pad(base_convert($n, 10, 2), 5, '0', STR_PAD_LEFT); } + $dec = str_split($dec, 8); $bin = ""; foreach ($dec as $b) $bin .= chr(bindec($b)); + $time = floor(time() / 30); $timeBin = str_pad(pack('N', $time), 8, "\x00", STR_PAD_LEFT); + $hmac = hash_hmac('sha1', $timeBin, $bin, true); $offset = ord(substr($hmac, -1)) & 0x0F; + $value = unpack('N', substr($hmac, $offset, 4))[1] & 0x7FFFFFFF; + $calcCode = str_pad($value % 1000000, 6, '0', STR_PAD_LEFT); return hash_equals($calcCode, $code); +} + +try { + $pdo = new PDO("sqlite:" . $db_file); + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $pdo->exec("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE, password TEXT, role TEXT, totp_secret TEXT, totp_enabled INTEGER DEFAULT 0)"); + $pdo->exec("CREATE TABLE IF NOT EXISTS folders (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE)"); + $pdo->exec("CREATE TABLE IF NOT EXISTS connections (id INTEGER PRIMARY KEY AUTOINCREMENT, folder_name TEXT, parent_id INTEGER DEFAULT NULL, name TEXT, type TEXT, host TEXT, port TEXT, username TEXT, password TEXT, kuma_url TEXT, tags TEXT, asset_type TEXT, os TEXT, hardware TEXT, serial_number TEXT, mac_address TEXT, location TEXT, warranty TEXT, uplink_id INTEGER, uplink_port TEXT, local_port TEXT, map_x REAL, map_y REAL, map_locked INTEGER DEFAULT 0)"); + $pdo->exec("CREATE TABLE IF NOT EXISTS protocols (id INTEGER PRIMARY KEY AUTOINCREMENT, proto_key TEXT UNIQUE, name TEXT, exec_path TEXT, args TEXT, icon TEXT)"); + $pdo->exec("CREATE TABLE IF NOT EXISTS templates (id INTEGER PRIMARY KEY AUTOINCREMENT, category TEXT, name TEXT, type TEXT, host TEXT, port TEXT, username TEXT, password TEXT, kuma_url TEXT, tags TEXT)"); + $pdo->exec("CREATE TABLE IF NOT EXISTS settings (id INTEGER PRIMARY KEY AUTOINCREMENT, key_name TEXT UNIQUE, value TEXT)"); + $pdo->exec("CREATE TABLE IF NOT EXISTS scripts (id INTEGER PRIMARY KEY AUTOINCREMENT, folder_name TEXT, name TEXT, language TEXT, script_body TEXT)"); + $pdo->exec("CREATE TABLE IF NOT EXISTS audit_logs (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, user TEXT, action TEXT, details TEXT)"); + $pdo->exec("CREATE TABLE IF NOT EXISTS password_history (id INTEGER PRIMARY KEY AUTOINCREMENT, connection_id INTEGER, password TEXT, changed_at DATETIME DEFAULT CURRENT_TIMESTAMP)"); + + try { $pdo->exec("ALTER TABLE connections ADD COLUMN local_port TEXT"); } catch (Exception $e) {} + try { $pdo->exec("ALTER TABLE connections ADD COLUMN map_x REAL"); } catch (Exception $e) {} + try { $pdo->exec("ALTER TABLE connections ADD COLUMN map_y REAL"); } catch (Exception $e) {} + try { $pdo->exec("ALTER TABLE connections ADD COLUMN map_locked INTEGER DEFAULT 0"); } catch (Exception $e) {} + + if ($pdo->query("SELECT COUNT(*) FROM users")->fetchColumn() == 0) { + $pdo->prepare("INSERT INTO users (username, password, role) VALUES (?, ?, ?)")->execute(['admin', password_hash('admin', PASSWORD_DEFAULT), 'admin']); + logAudit($pdo, 'System', 'INSTALL', 'Admin user created.'); + } + + if ($pdo->query("SELECT COUNT(*) FROM protocols")->fetchColumn() == 0) { + $pdo->exec("INSERT INTO protocols (proto_key, name, exec_path, args, icon) VALUES ('rdp', 'RDP (Windows)', 'mstsc.exe', '/v:[HOST]', 'bi-windows'), ('ssh', 'SSH', 'putty.exe', '-ssh [USER]@[HOST]', 'bi-terminal'), ('web', 'Website', '', '', 'bi-globe')"); + } + + $pdo->exec("INSERT OR IGNORE INTO settings (key_name, value) VALUES ('dashboard_url', 'https://threatmap.fortiguard.com/')"); + $pdo->exec("INSERT OR IGNORE INTO settings (key_name, value) VALUES ('pwd_exclude_chars', 'lI1O0')"); + + $action = $_GET['action'] ?? ''; $method = $_SERVER['REQUEST_METHOD']; $input = json_decode(file_get_contents('php://input'), true); + + if ($action === 'login' && $method === 'POST') { + $stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?"); $stmt->execute([$input['username']]); $user = $stmt->fetch(PDO::FETCH_ASSOC); + if ($user && password_verify($input['password'], $user['password'])) { + if ($user['totp_enabled']) { + $_SESSION['pending_2fa'] = $user['id']; echo json_encode(["status" => "2fa_required"]); + } else { + $_SESSION['user_id'] = $user['id']; $_SESSION['role'] = $user['role']; $_SESSION['username'] = $user['username']; + logAudit($pdo, $user['username'], 'LOGIN', 'Erfolgreicher Login.'); + echo json_encode(["status" => "success", "role" => $user['role'], "username" => $user['username']]); + } + } else { + logAudit($pdo, $input['username'] ?? 'Unknown', 'LOGIN_FAILED', 'Falsche Zugangsdaten.'); + http_response_code(401); echo json_encode(["error" => "Falsche Zugangsdaten"]); + } + exit; + } + elseif ($action === 'verify_2fa' && $method === 'POST') { + if (!isset($_SESSION['pending_2fa'])) { http_response_code(401); die(json_encode(["error" => "Session invalid"])); } + $stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?"); $stmt->execute([$_SESSION['pending_2fa']]); $user = $stmt->fetch(PDO::FETCH_ASSOC); + if (verifyTOTP($user['totp_secret'], $input['code'])) { + unset($_SESSION['pending_2fa']); + $_SESSION['user_id'] = $user['id']; $_SESSION['role'] = $user['role']; $_SESSION['username'] = $user['username']; + logAudit($pdo, $user['username'], 'LOGIN_2FA', 'Erfolgreicher 2FA Login.'); + echo json_encode(["status" => "success", "role" => $user['role'], "username" => $user['username']]); + } else { + logAudit($pdo, $user['username'], 'LOGIN_2FA_FAILED', 'Falscher 2FA Code.'); + http_response_code(401); echo json_encode(["error" => "Falscher 2FA Code"]); + } + exit; + } + elseif ($action === 'logout') { + if(isset($_SESSION['username'])) logAudit($pdo, $_SESSION['username'], 'LOGOUT', 'Benutzer abgemeldet.'); + session_destroy(); echo json_encode(["status" => "success"]); exit; + } + elseif ($action === 'session') { + if(isset($_SESSION['user_id'])) echo json_encode(["status" => "active", "role" => $_SESSION['role'], "username" => $_SESSION['username']]); + else echo json_encode(["status" => "inactive"]); + exit; + } + + if (!isset($_SESSION['user_id'])) { http_response_code(401); die(json_encode(["error" => "Nicht eingeloggt"])); } + $isAdmin = ($_SESSION['role'] === 'admin'); + $currentUser = $_SESSION['username']; + + if ($action === 'categories' && $method === 'GET') { + $img_dir = __DIR__ . '/img/DeviceCategories'; if (!is_dir($img_dir)) { @mkdir($img_dir, 0755, true); } + $categories = []; + if (is_dir($img_dir)) { + $files = scandir($img_dir); + foreach ($files as $file) { + if ($file !== '.' && $file !== '..' && in_array(strtolower(pathinfo($file, PATHINFO_EXTENSION)), ['png', 'jpg', 'jpeg', 'gif', 'svg'])) { + $categories[] = ['name' => pathinfo($file, PATHINFO_FILENAME), 'url' => 'img/DeviceCategories/' . $file]; + } + } + } + echo json_encode($categories); exit; + } + + if ($action === 'users') { + if ($method === 'GET') { + if (!$isAdmin) die(json_encode(["error" => "Access denied"])); + echo json_encode($pdo->query("SELECT id, username, role, totp_enabled FROM users")->fetchAll(PDO::FETCH_ASSOC)); + } elseif ($method === 'POST') { + if (!$isAdmin) die(); + if (!empty($input['id'])) { + if(!empty($input['password'])) $pdo->prepare("UPDATE users SET username=?, role=?, password=? WHERE id=?")->execute([$input['username'], $input['role'], password_hash($input['password'], PASSWORD_DEFAULT), $input['id']]); + else $pdo->prepare("UPDATE users SET username=?, role=? WHERE id=?")->execute([$input['username'], $input['role'], $input['id']]); + logAudit($pdo, $currentUser, 'USER_UPDATE', "Benutzer {$input['username']} aktualisiert."); + } else { + $pdo->prepare("INSERT INTO users (username, password, role) VALUES (?, ?, ?)")->execute([$input['username'], password_hash($input['password'], PASSWORD_DEFAULT), $input['role']]); + logAudit($pdo, $currentUser, 'USER_CREATE', "Benutzer {$input['username']} erstellt."); + } + echo json_encode(["status" => "success"]); + } elseif ($method === 'DELETE') { + if (!$isAdmin || $input['id'] == $_SESSION['user_id']) die(); + $pdo->prepare("DELETE FROM users WHERE id = ?")->execute([$input['id']]); + logAudit($pdo, $currentUser, 'USER_DELETE', "Benutzer ID {$input['id']} gelöscht."); + echo json_encode(["status" => "success"]); + } + } + elseif ($action === 'audit' && $method === 'GET' && $isAdmin) { + echo json_encode($pdo->query("SELECT * FROM audit_logs ORDER BY timestamp DESC LIMIT 200")->fetchAll(PDO::FETCH_ASSOC)); + } + elseif ($action === 'password_history' && $method === 'GET') { + $stmt = $pdo->prepare("SELECT password, changed_at FROM password_history WHERE connection_id = ? ORDER BY changed_at DESC"); + $stmt->execute([$_GET['id']]); + $hist = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach($hist as &$h) { $h['password'] = decryptData($h['password'], $crypto_key); } + echo json_encode($hist); + } + elseif ($action === 'my_2fa') { + if ($method === 'GET') { + $stmt = $pdo->prepare("SELECT totp_secret, totp_enabled FROM users WHERE id = ?"); $stmt->execute([$_SESSION['user_id']]); $u = $stmt->fetch(PDO::FETCH_ASSOC); + $secret = $u['totp_secret']; + if (!$secret) { $secret = generateTOTPSecret(); $pdo->prepare("UPDATE users SET totp_secret = ? WHERE id = ?")->execute([$secret, $_SESSION['user_id']]); } + $url = "otpauth://totp/WebRDM:" . urlencode($_SESSION['username']) . "?secret=" . $secret . "&issuer=WebRDM"; + echo json_encode(["secret" => $secret, "enabled" => $u['totp_enabled'], "qr" => "https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=" . urlencode($url)]); + } elseif ($method === 'POST') { + if (verifyTOTP($input['secret'], $input['code'])) { + $pdo->prepare("UPDATE users SET totp_enabled = 1 WHERE id = ?")->execute([$_SESSION['user_id']]); + logAudit($pdo, $currentUser, '2FA_ENABLED', '2FA aktiviert.'); + echo json_encode(["status" => "success"]); + } else { http_response_code(400); echo json_encode(["error" => "Code inkorrekt"]); } + } elseif ($method === 'DELETE') { + $pdo->prepare("UPDATE users SET totp_enabled = 0, totp_secret = NULL WHERE id = ?")->execute([$_SESSION['user_id']]); + logAudit($pdo, $currentUser, '2FA_DISABLED', '2FA deaktiviert.'); + echo json_encode(["status" => "success"]); + } + } + elseif ($action === 'folders') { + if ($method === 'GET') echo json_encode($pdo->query("SELECT name FROM folders ORDER BY name")->fetchAll(PDO::FETCH_COLUMN)); + elseif ($method === 'POST' && $isAdmin) { + $pdo->prepare("INSERT OR IGNORE INTO folders (name) VALUES (:name)")->execute(['name' => $input['name']]); + logAudit($pdo, $currentUser, 'FOLDER_CREATE', "Ordner '{$input['name']}' erstellt."); + echo json_encode(["status" => "success"]); + } + } + elseif ($action === 'connections') { + if ($method === 'GET') { + $conns = $pdo->query("SELECT * FROM connections ORDER BY folder_name, name")->fetchAll(PDO::FETCH_ASSOC); + foreach($conns as &$c) { $c['password'] = decryptData($c['password'], $crypto_key); } + echo json_encode($conns); + } + elseif ($method === 'POST' && $isAdmin) { + $raw_pw = $input['password'] ?? ''; + $old_pw_enc = ''; + + if (!empty($input['id'])) { + $stmt = $pdo->prepare("SELECT password FROM connections WHERE id = ?"); $stmt->execute([$input['id']]); + $old_conn = $stmt->fetch(PDO::FETCH_ASSOC); + $old_pw_enc = $old_conn['password'] ?? ''; + $old_pw_decrypted = decryptData($old_pw_enc, $crypto_key); + + if ($old_pw_decrypted !== '' && $old_pw_decrypted !== $raw_pw) { + $pdo->prepare("INSERT INTO password_history (connection_id, password) VALUES (?, ?)")->execute([$input['id'], $old_pw_enc]); + $pdo->prepare("DELETE FROM password_history WHERE id NOT IN (SELECT id FROM password_history WHERE connection_id = ? ORDER BY changed_at DESC LIMIT 10)")->execute([$input['id']]); + } + } + + if(isset($input['password'])) $input['password'] = encryptData($input['password'], $crypto_key); + + if (!empty($input['id'])) { + $pdo->prepare("UPDATE connections SET folder_name=:folder_name, parent_id=:parent_id, name=:name, type=:type, host=:host, port=:port, username=:username, password=:password, kuma_url=:kuma_url, tags=:tags, asset_type=:asset_type, os=:os, hardware=:hardware, serial_number=:serial_number, mac_address=:mac_address, location=:location, warranty=:warranty, uplink_id=:uplink_id, uplink_port=:uplink_port, local_port=:local_port WHERE id=:id")->execute($input); + logAudit($pdo, $currentUser, 'ASSET_UPDATE', "Verbindung '{$input['name']}' aktualisiert."); + } else { + $stmt = $pdo->prepare("INSERT INTO connections (folder_name, parent_id, name, type, host, port, username, password, kuma_url, tags, asset_type, os, hardware, serial_number, mac_address, location, warranty, uplink_id, uplink_port, local_port) VALUES (:folder_name, :parent_id, :name, :type, :host, :port, :username, :password, :kuma_url, :tags, :asset_type, :os, :hardware, :serial_number, :mac_address, :location, :warranty, :uplink_id, :uplink_port, :local_port)"); + unset($input['id']); $stmt->execute($input); + logAudit($pdo, $currentUser, 'ASSET_CREATE', "Verbindung '{$input['name']}' erstellt."); + } + echo json_encode(["status" => "success"]); + } + } + elseif ($action === 'delete_connection' && $method === 'POST' && $isAdmin) { + $pdo->prepare("DELETE FROM connections WHERE id = :id")->execute(['id' => $input['id']]); + $pdo->prepare("DELETE FROM password_history WHERE connection_id = :id")->execute(['id' => $input['id']]); + logAudit($pdo, $currentUser, 'ASSET_DELETE', "Verbindung ID {$input['id']} gelöscht."); + echo json_encode(["status" => "success"]); + } + elseif ($action === 'scripts') { + if ($method === 'GET') { echo json_encode($pdo->query("SELECT * FROM scripts ORDER BY folder_name, name")->fetchAll(PDO::FETCH_ASSOC)); } + elseif ($method === 'POST' && $isAdmin) { + $data = ['folder_name' => $input['folder_name'] ?: null, 'name' => $input['name'], 'language' => $input['language'], 'script_body' => $input['script_body']]; + if (!empty($input['id'])) { + $data['id'] = $input['id']; + $pdo->prepare("UPDATE scripts SET folder_name=:folder_name, name=:name, language=:language, script_body=:script_body WHERE id=:id")->execute($data); + logAudit($pdo, $currentUser, 'SCRIPT_UPDATE', "Skript '{$input['name']}' aktualisiert."); + } else { + $pdo->prepare("INSERT INTO scripts (folder_name, name, language, script_body) VALUES (:folder_name, :name, :language, :script_body)")->execute($data); + logAudit($pdo, $currentUser, 'SCRIPT_CREATE', "Skript '{$input['name']}' erstellt."); + } + echo json_encode(["status" => "success"]); + } + } + elseif ($action === 'delete_script' && $method === 'POST' && $isAdmin) { + $pdo->prepare("DELETE FROM scripts WHERE id = :id")->execute(['id' => $input['id']]); + logAudit($pdo, $currentUser, 'SCRIPT_DELETE', "Skript ID {$input['id']} gelöscht."); + echo json_encode(["status" => "success"]); + } + elseif ($action === 'save_map_pos' && $method === 'POST' && $isAdmin) { + $pdo->prepare("UPDATE connections SET map_x = ?, map_y = ? WHERE id = ?")->execute([$input['x'], $input['y'], $input['id']]); echo json_encode(["status" => "success"]); + } + elseif ($action === 'toggle_map_lock' && $method === 'POST' && $isAdmin) { + $pdo->prepare("UPDATE connections SET map_locked = ? WHERE id = ?")->execute([$input['locked'], $input['id']]); echo json_encode(["status" => "success"]); + } + elseif ($action === 'reset_map' && $method === 'POST' && $isAdmin) { + $pdo->exec("UPDATE connections SET map_x = NULL, map_y = NULL, map_locked = 0"); + logAudit($pdo, $currentUser, 'MAP_RESET', "Kartenphysik und Layout zurückgesetzt."); + echo json_encode(["status" => "success"]); + } + elseif ($action === 'protocols' && $method === 'GET') echo json_encode($pdo->query("SELECT * FROM protocols")->fetchAll(PDO::FETCH_ASSOC)); + elseif ($action === 'settings' && $method === 'GET') { $stmt = $pdo->query("SELECT * FROM settings"); $s = []; foreach($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) $s[$r['key_name']] = $r['value']; echo json_encode($s); } + elseif ($isAdmin) { + if ($action === 'protocols') { + if ($method === 'POST') { $pdo->prepare("INSERT INTO protocols (proto_key, name, exec_path, args, icon) VALUES (:key, :name, :path, :args, :icon)")->execute(['key' => strtolower($input['proto_key']), 'name' => $input['name'], 'path' => $input['exec_path'], 'args' => $input['args'], 'icon' => $input['icon'] ?? 'bi-gear']); echo json_encode(["status" => "success"]); } + elseif ($method === 'DELETE') { $pdo->prepare("DELETE FROM protocols WHERE id = :id AND proto_key != 'web'")->execute(['id' => $input['id']]); echo json_encode(["status" => "success"]); } + } + elseif ($action === 'templates') { + if ($method === 'POST') { $pdo->prepare("INSERT INTO templates (category, name, type, host, port, username, password, kuma_url, tags) VALUES (:category, :name, :type, :host, :port, :username, :password, :kuma_url, :tags)")->execute(['category' => $input['category'], 'name' => $input['name'], 'type' => $input['type'] ?? 'rdp', 'host' => $input['host'] ?? '', 'port' => $input['port'] ?? '', 'username' => $input['username'] ?? '', 'password' => $input['password'] ?? '', 'kuma_url' => $input['kuma_url'] ?? '', 'tags' => $input['tags'] ?? '']); echo json_encode(["status" => "success"]); } + elseif ($method === 'DELETE') { $pdo->prepare("DELETE FROM templates WHERE id = :id")->execute(['id' => $input['id']]); echo json_encode(["status" => "success"]); } + } + elseif ($action === 'settings' && $method === 'POST') { + // Robustes UPSERT, funktioniert immer in SQLite + $stmt_check = $pdo->prepare("SELECT COUNT(*) FROM settings WHERE key_name = ?"); + $stmt_insert = $pdo->prepare("INSERT INTO settings (key_name, value) VALUES (?, ?)"); + $stmt_update = $pdo->prepare("UPDATE settings SET value = ? WHERE key_name = ?"); + + foreach($input as $key => $val) { + $stmt_check->execute([$key]); + if($stmt_check->fetchColumn() > 0) $stmt_update->execute([$val, $key]); + else $stmt_insert->execute([$key, $val]); + } + logAudit($pdo, $currentUser, 'SETTINGS_UPDATE', "Globale Einstellungen geändert."); + echo json_encode(["status" => "success"]); + } + } else { http_response_code(403); echo json_encode(["error" => "Berechtigung fehlt"]); } + +} catch (Exception $e) { http_response_code(500); echo json_encode(["error" => $e->getMessage()]); } +?> \ No newline at end of file diff --git a/WebRDM/css/style.css b/WebRDM/css/style.css new file mode 100644 index 0000000..9d32ac2 --- /dev/null +++ b/WebRDM/css/style.css @@ -0,0 +1,71 @@ +:root { + --bg-body: #f3f3f3; --bg-sidebar: #ffffff; --bg-card: #ffffff; + --text-main: #333333; --text-muted: #666666; --border-color: #d8d8d8; + --primary-color: #0078d7; --primary-hover: #005a9e; + --item-hover: #e5f1fb; --item-active: #cce4f7; --label-color: #0066cc; +} + +[data-theme="dark"] { + --bg-body: #1e1e1e; --bg-sidebar: #252526; --bg-card: #2d2d30; + --text-main: #f1f1f1; --text-muted: #aaaaaa; --border-color: #3e3e42; + --primary-color: #3f98e7; --primary-hover: #60a5fa; + --item-hover: #333333; --item-active: #094771; --label-color: #569cd6; +} + +body { height: 100vh; display: flex; flex-direction: column; overflow: hidden; background-color: var(--bg-body); color: var(--text-main); font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } +.navbar { background-color: var(--bg-sidebar) !important; border-bottom: 1px solid var(--border-color); } +.navbar-brand { color: var(--text-main) !important; font-weight: 600; cursor: pointer; text-decoration: none; } +.sidebar { height: 100%; overflow-y: auto; background-color: var(--bg-sidebar); border-right: 1px solid var(--border-color); display: flex; flex-direction: column; } +.main-content { height: 100%; padding: 0; overflow-y: auto; background-color: var(--bg-body); } + +.form-control, .form-select, .form-control[type="file"]::file-selector-button { background-color: var(--bg-sidebar); color: var(--text-main); border: 1px solid var(--border-color); border-radius: 4px; } +.form-control:focus, .form-select:focus { background-color: var(--bg-sidebar); color: var(--text-main); border-color: var(--primary-color); box-shadow: none; } +.btn-primary { background-color: var(--primary-color); border-color: var(--primary-color); color: white;} +.btn-outline-secondary { color: var(--text-main); border-color: var(--border-color); } +.btn-outline-secondary:hover { background-color: var(--item-hover); color: var(--text-main); } +.btn-nav-icon { background: none; border: 1px solid var(--border-color); color: var(--text-main); border-radius: 4px; width: 35px; height: 35px; display: flex; align-items: center; justify-content: center; margin-left: 10px; transition: 0.2s; } +.btn-nav-icon:hover { background-color: var(--item-hover); } + +.folder-header { font-weight: 600; margin-top: 1rem; margin-bottom: 0.5rem; color: var(--text-muted); font-size: 0.85rem; text-transform: uppercase; } +.connection-item { cursor: pointer; padding: 6px 10px; display: flex; align-items: center; gap: 8px; font-size: 0.9rem; } +.connection-item:hover { background-color: var(--item-hover); } +.connection-item.active { background-color: var(--item-active); border-left: 3px solid var(--primary-color); padding-left: 7px;} +.depth-0 { margin-left: 0px; } .depth-1 { margin-left: 20px; } .depth-2 { margin-left: 40px; } + +/* Devolutions UI Layout */ +.dev-action-sidebar { background-color: var(--bg-sidebar); border-right: 1px solid var(--border-color); padding: 15px; height: 100%; width: 200px; flex-shrink: 0; } +.dev-action-group { margin-bottom: 25px; } +.dev-action-title { font-weight: bold; font-size: 0.85rem; border-bottom: 1px solid var(--border-color); padding-bottom: 5px; margin-bottom: 15px; color: var(--text-main); } +.dev-action-btn { display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; cursor: pointer; padding: 10px; border-radius: 4px; color: var(--text-main); text-decoration: none; } +.dev-action-btn:hover { background-color: var(--item-hover); } +.dev-action-btn i { font-size: 1.8rem; margin-bottom: 5px; } +.dev-main-view { flex-grow: 1; display: flex; flex-direction: column; overflow: hidden; } +.dev-header { background-color: var(--bg-card); padding: 15px 20px; border-bottom: 1px solid var(--border-color); display: flex; align-items: center; gap: 15px; } +.dev-tabs { background-color: var(--bg-card); padding: 0 20px; border-bottom: 1px solid var(--border-color); display: flex; gap: 20px; font-size: 0.9rem; } +.dev-tab { padding: 10px 0; cursor: pointer; color: var(--text-main); border-bottom: 2px solid transparent; } +.dev-tab.active { border-bottom-color: var(--primary-color); font-weight: bold; } +.dev-content { padding: 20px; overflow-y: auto; flex-grow: 1; } +.dev-row { background-color: var(--bg-card); border: 1px solid var(--border-color); border-bottom: none; padding: 10px 20px; display: flex; justify-content: space-between; align-items: center; } +.dev-row:last-child { border-bottom: 1px solid var(--border-color); } +.dev-row-label { color: var(--label-color); font-size: 0.85rem; margin-bottom: 2px; } +.dev-row-value { font-size: 0.95rem; color: var(--text-main); } +.dev-row-tools { display: flex; gap: 15px; color: var(--text-muted); font-size: 1.1rem; } +.dev-row-tools i { cursor: pointer; transition: color 0.2s; } +.dev-row-tools i:hover { color: var(--primary-color); } +.dashboard-card { background-color: var(--bg-card); border: 1px solid var(--border-color); border-radius: 4px; padding: 2rem; } +.text-muted-custom { color: var(--text-muted); } +.tag-badge { background-color: var(--item-hover); color: var(--primary-color); border: 1px solid var(--border-color); padding: 2px 8px; border-radius: 12px; font-size: 0.75rem; margin-right: 5px; } + +/* Map Specifics */ +#mynetwork { flex-grow: 1; width: 100%; height: 100%; background-color: var(--bg-body); } +div.vis-tooltip { background-color: var(--bg-sidebar); border: 1px solid var(--border-color); color: var(--text-main); border-radius: 6px; padding: 10px; font-family: 'Segoe UI', Tahoma, sans-serif; font-size: 0.85rem; box-shadow: 0 4px 6px rgba(0,0,0,0.1); } + +/* Login Overlay */ +#login-overlay { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background-color: var(--bg-body); z-index: 9999; display: flex; align-items: center; justify-content: center; } +.login-box { background-color: var(--bg-card); padding: 3rem; border-radius: 8px; border: 1px solid var(--border-color); box-shadow: 0 10px 25px rgba(0,0,0,0.1); width: 100%; max-width: 400px; text-align: center; } + +/* Toasts */ +#toast { visibility: hidden; min-width: 250px; background-color: #333; color: #fff; text-align: center; border-radius: 4px; padding: 10px; position: fixed; z-index: 10000; left: 50%; bottom: 30px; font-size: 0.9rem; transform: translateX(-50%); } +#toast.show { visibility: visible; animation: fadein 0.5s, fadeout 0.5s 2.5s; } +@keyframes fadein { from {bottom: 0; opacity: 0;} to {bottom: 30px; opacity: 1;} } +@keyframes fadeout { from {bottom: 30px; opacity: 1;} to {bottom: 0; opacity: 0;} } diff --git a/WebRDM/db/rdm.db b/WebRDM/db/rdm.db new file mode 100644 index 0000000000000000000000000000000000000000..5af31c657e403e4978f516eb1e356c284ae68eed GIT binary patch literal 61440 zcmeI*Pi!Ms9S86kPi*JUIP_s#yNT`iPu%^}jwYTL zd+M>rJ2Ot=Aq3nILR?$+0^&ji65`a-1Mp`2Cr);kMr_rx z?RS)T-u(N`d!P5l&dhjT^4pu5O~pOKtShz{)<#A<1Gae)o@oK%E=+f1M*T`3(sSZ=U;JU}fxAW9P@dGa4KH%lLlamuw*h2tWV= z5P-mEOW-Iu?hl5;{DUvs%C1hEie()brYcu8%QnpR>1nc>OT-Hau@Jw$nGjDe73Vco zOr;Bn^+Zn0=2Bbn+%55D;+7aM7BZrtVI<@fSo}saV~`tSYH35A4I}EvYJ|dcd;VU6JiX zQ*^n}s_#;>JFl*kWkpp@YV|hKjk4lYS$74ty{R)h5 z31zXEPHh(xkF-Y9(0g9CVK-%qmQC8LCwC5QI7iUqG94Q81-}&JIjy16L+hT-{^*sJ zmTkDxvYS^~a!2RBGVBjdP4P!5w|J}qZLLvpD$*T~oG4^(?pe!OqwV7c+b`$7qPIFF z3q?kJ!PTiJ<+HatS?Z-mk1~FLaC(}5P}{yq zs0I#&foA>Ght9xJ6FR!+?UX3!mZ)hOwox{8tDo?lC{cg*Sxe+@AhViqi`1`#fs)Zf zT9%uNUF{ZHG5bff#@?~qxgp8t3nr$YR8#$2$x=V*C+~ClkNC6gC>HPsXJ+^Zl8dFZ zRLz!kqhcMW#!qk^&p)e|t<_m`QtHh!d|Fi@{L`!s7QmEN03C$zk4sGX(|$_l>V<;(o7 zPNq6<5*EGJqKz``zGw6%hfZelyG{pe>704er@*eps8)1L|1Rwwb2tWV=5P$##AOHaf zK;W4YAcDYM+Stk$BKeYLm-nlq9)X+8AFy@5AqZ8 z4*4_rIr$y2$PWPs0SG_<0uX=z1Rwwb2tWV=5P-l_30&|CVZQeqS??)74-0wWblCA> zVTM0`V8ave2veTklRuts6R$AK4?IEV8}$j3Bi#okh6N!w;y%zcHpI^R```Ny@WM3T zehP&o%_Hy{7x|F1#Aw3NJ~f+EmoK*2v@wv8%O;bY&*ax{)wx^c9I0 z|NXh6;h`5qhKWeW#k)NVa#P*AB~xowZ^zy_qgnZ=~0kZxoi& zg?uq_EgF@GmrFc#wWI_`CiJKbKTw){DV$knHkMUAWz)IE9F?x{Uwbp{d0eQ83I0^5 zn@*^n%`tN)l$6gVC2Ox4eeYe4+vc5!Q|D*hYMbh4bAbuZV8mpJWlOpiU6ig|U6dB3 zQ#nj(COuGeoyC;?^?m0n@Z1;vN429B??Bz(>s9_^>OQmHqjKs#wN|s&Tdk5+R*jqY zH`c4E%gJr&dMfjpv3$F#&i-yO zv83q9TDq3FR<-S>wGxTcjfTd04Prf(EV3?TYE`N=78}%#C`~Q0tEd$kc_qR+bFf=h zG$ye!JJ0`n$A7?)-;x@6e*70Ki2(u-fB*y_009U<00Izz00bcLF$#R%>*vmg1A(an zx?7^VmeWoBd@5AJGW z`f_9o*_rX3Vm^_3edSIzp3lGj>YWXCmBE?Hzz|IhyE9|j0O00Izz00bZa0SG_<0uX=z1U|(A&iTKG{Fo!ZW-k~Z009U< z00Izz00bZa0SG_<0ucD*1YZ2cOG8{F*gQ~WdRW$5DwWGs#kBO4lv(;BKg7+IRmH0A z8j7jPEmQxn&Hy?c07R-rojTwD_mHw)DdV%Mq=Y+YD6PF5{`~N)o5yyToKmY;|fB*y_009U<00Izz00bcL2?@Nx4|BmI zZ!kRU{tttT(e_0tdhyNGtC8hR`sZCmY{LIQRc~@*c;2FhBqT5P$##AOHafKmY;| zfB*y_@aYm57J?)0^?=U(|A!oT_~|MjN&^81KmY;|fB*y_009U<00I#Bj0nu~K5lYi zW-5Q6+2ty=sbx0}_Rj>ZD%Evo%A&T_>HqI8M}A7~enwnSOb9>#0uX=z1Rwwb2tWV= z5P-l(Dv{wpN_gqz@KRKiqAM>it-QSK-g}Sd^NE67%&x@?i5+V0Y7Mp3 zsEBhLTlqpR9M6U`>vN(~vs;R;SsFF%#Vk9ee{96qm9s^>c{*Z%-T3FpTO4_dy`*b#c=*#g)vy0QQ>?1^@s6 literal 0 HcmV?d00001 diff --git a/WebRDM/index.html b/WebRDM/index.html new file mode 100644 index 0000000..63cb231 --- /dev/null +++ b/WebRDM/index.html @@ -0,0 +1,64 @@ + + + + + Web RDM - Professional SOC + + + + + + + +
+
+

Web RDM

+
+ +
+ + + +
+ +
+

Bitte gib deinen 6-stelligen Google Authenticator Code ein.

+ + +
+
+
+ +
+
+ Web RDM +
+ + Karte + + + +
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+ +
Nachricht
+ + + + + diff --git a/WebRDM/js/app.js b/WebRDM/js/app.js new file mode 100644 index 0000000..6995e62 --- /dev/null +++ b/WebRDM/js/app.js @@ -0,0 +1,302 @@ +window.folders = []; window.connections = []; window.protocols = []; window.templates = []; window.users = []; +window.appSettings = {}; window.securityDashboardUrl = "https://threatmap.fortiguard.com/"; +window.currentUser = { role: 'viewer', username: '' }; +window.categories = []; window.scripts = []; window.auditLogs = []; +window.networkInstance = null; window.mapLabelMode = window.mapLabelMode || 'name'; + +window.isMapPage = document.getElementById('mynetwork') !== null; + +// --- UTILS & UI --- +window.initTheme = function() { + const t = localStorage.getItem('rdm_theme') || 'light'; document.documentElement.setAttribute('data-theme', t); + if(document.getElementById('theme-icon')) { document.getElementById('theme-icon').className = t === 'dark' ? 'bi bi-sun' : 'bi bi-moon'; } + return t; +}; +window.toggleTheme = function() { + const t = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark'; document.documentElement.setAttribute('data-theme', t); localStorage.setItem('rdm_theme', t); + if(document.getElementById('theme-icon')) { document.getElementById('theme-icon').className = t === 'dark' ? 'bi bi-sun' : 'bi bi-moon'; } + if (window.isMapPage) window.drawMap(); +}; +window.showToast = function(msg) { const toast = document.getElementById("toast"); if(!toast) return; toast.innerText = msg; toast.className = "show"; setTimeout(() => { toast.className = toast.className.replace("show", ""); }, 3000); }; +window.copyToClipboard = function(text) { if(!text) return; navigator.clipboard.writeText(text).then(() => window.showToast("In die Zwischenablage kopiert!")); }; +window.togglePwd = function(id, realPwd) { const el = document.getElementById(id); const icon = document.getElementById('icon-' + id); if(el.innerText === '••••••••') { el.innerText = realPwd; icon.classList.replace('bi-eye', 'bi-eye-slash'); } else { el.innerText = '••••••••'; icon.classList.replace('bi-eye-slash', 'bi-eye'); } }; +window.switchTab = function(tabId) { document.querySelectorAll('.tab-view').forEach(el => el.style.display = 'none'); document.querySelectorAll('.dev-tab').forEach(el => el.classList.remove('active')); document.getElementById('view-' + tabId).style.display = 'block'; document.getElementById('tab-btn-' + tabId).classList.add('active'); }; +window.getProtoInfo = function(type) { let p = window.protocols.find(x => x.proto_key === type); return p ? p : {icon: 'bi-link', name: type}; }; + +// NEU: Erweiterter Generator mit Filter +window.generatePassword = function() { + let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+~|}{[]:;?><,./-="; + const exclude = window.appSettings.pwd_exclude_chars || ""; + + if(exclude) { + for(let i=0; i `${h.changed_at}••••••••`).join(''); + + const modalHtml = `
Passwort-Historie (Letzte 10)
${rows}
Geändert amPasswortAktion
`; + const oldModal = document.getElementById('historyModal'); if(oldModal) oldModal.remove(); document.body.insertAdjacentHTML('beforeend', modalHtml); new bootstrap.Modal(document.getElementById('historyModal')).show(); +}; + +// --- AUTHENTICATION --- +window.checkSession = async function() { + try { + const res = await (await fetch('api.php?action=session')).json(); + if(res.status === 'active') { window.currentUser.role = res.role; window.currentUser.username = res.username; if(document.getElementById('user-badge')) document.getElementById('user-badge').innerText = res.username; document.getElementById('login-overlay').style.display = 'none'; window.loadData(); } + } catch(e) { console.error("Session Check Error:", e); } +}; +window.doLogin = async function() { + const u = document.getElementById('l_user').value; const p = document.getElementById('l_pass').value; + try { + const res = await fetch('api.php?action=login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({username: u, password: p}) }); + if(res.ok) { + const data = await res.json(); + if(data.status === '2fa_required') { document.getElementById('step-login').classList.add('d-none'); document.getElementById('step-2fa').classList.remove('d-none'); } + else { window.currentUser.role = data.role; window.currentUser.username = data.username; if(document.getElementById('user-badge')) document.getElementById('user-badge').innerText = data.username; document.getElementById('login-overlay').style.display = 'none'; window.loadData(); } + } else { document.getElementById('login-error').innerText = "Zugangsdaten falsch."; document.getElementById('login-error').classList.remove('d-none'); } + } catch (e) { document.getElementById('login-error').innerText = "Verbindungsfehler zur API."; document.getElementById('login-error').classList.remove('d-none'); } +}; +window.verify2FA = async function() { + const code = document.getElementById('l_2fa').value; const res = await fetch('api.php?action=verify_2fa', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({code: code}) }); + if(res.ok) { const data = await res.json(); window.currentUser.role = data.role; window.currentUser.username = data.username; if(document.getElementById('user-badge')) document.getElementById('user-badge').innerText = data.username; document.getElementById('login-overlay').style.display = 'none'; window.loadData(); } + else { document.getElementById('login-error').innerText = "Code falsch."; document.getElementById('login-error').classList.remove('d-none'); } +}; +window.doLogout = async function() { await fetch('api.php?action=logout'); location.reload(); }; + +// --- DATA LOADING & ROUTING --- +window.loadData = async function() { + try { + window.folders = await (await fetch('api.php?action=folders')).json(); window.connections = await (await fetch('api.php?action=connections')).json(); + window.protocols = await (await fetch('api.php?action=protocols')).json(); window.appSettings = await (await fetch('api.php?action=settings')).json(); + window.categories = await (await fetch('api.php?action=categories')).json(); window.scripts = await (await fetch('api.php?action=scripts')).json(); + if(window.currentUser.role === 'admin') { window.users = await (await fetch('api.php?action=users')).json(); window.auditLogs = await (await fetch('api.php?action=audit')).json(); } + window.securityDashboardUrl = window.appSettings.dashboard_url || "https://threatmap.fortiguard.com/"; + if(window.isMapPage) { window.drawMap(); } else { window.renderSidebar(); window.showHomeDashboard(); } + } catch(e) { console.error("Load Data Error", e); } +}; + +window.showHomeDashboard = function() { + if(window.isMapPage) return; + document.querySelectorAll('.connection-item').forEach(el => el.classList.remove('active')); + let totalAssets = window.connections ? window.connections.length : 0; let totalScripts = window.scripts ? window.scripts.length : 0; let foldersCount = window.folders ? window.folders.length : 0; + let assetCounts = {}; if (window.connections) { window.connections.forEach(c => { if(c.asset_type) assetCounts[c.asset_type] = (assetCounts[c.asset_type] || 0) + 1; }); } + let breakdownHtml = Object.entries(assetCounts).map(([k,v]) => ` ${k}: ${v}`).join(''); + let kumaItems = window.connections ? window.connections.filter(c => c.kuma_url) : []; let kumaHtml = ''; + if(kumaItems.length > 0) { + kumaHtml = `

Wall of Health

`; + kumaItems.forEach(c => { let iframeOrImg = c.kuma_url.includes('/badge/') ? `` : ``; kumaHtml += `
${c.name}
${iframeOrImg}
`; }); + kumaHtml += `
`; + } + document.getElementById('main-area').innerHTML = `

System Dashboard

${totalAssets}
Verbindungen / Assets
${totalScripts}
Skripte / Snippets
${foldersCount}
Ordnerstrukturen
${breakdownHtml ? `
Hardware-Inventar
${breakdownHtml}
` : ''}${kumaHtml}
`; +}; + +window.renderSidebar = function() { + if(window.isMapPage) return; + const sidebar = document.getElementById('sidebar-content'); sidebar.innerHTML = ''; + if(window.currentUser.role === 'admin') { + sidebar.innerHTML += `
  • Verbindung / Asset
  • Skript / Snippet
`; + } + const tree = {}; window.folders.forEach(f => { const parts = f.split('/'); let current = tree; let path = ""; parts.forEach(p => { path = path ? path + "/" + p : p; if(!current[p]) current[p] = { fullPath: path, children: {} }; current = current[p].children; }); }); + for(let rootName in tree) { window.renderFolderNode(rootName, tree[rootName], sidebar, 0); } +}; + +window.renderFolderNode = function(name, node, container, depth) { + const wrapper = document.createElement('div'); const folderDiv = document.createElement('div'); + folderDiv.className = `folder-header px-2 search-item depth-${depth > 2 ? 2 : depth}`; + folderDiv.innerHTML = ` ${name}`; + wrapper.appendChild(folderDiv); + const childrenContainer = document.createElement('div'); childrenContainer.className = "folder-children"; + folderDiv.querySelector('.toggle-icon').onclick = (e) => { e.stopPropagation(); childrenContainer.classList.toggle('d-none'); e.target.classList.toggle('bi-chevron-down'); e.target.classList.toggle('bi-chevron-right'); }; + for(let childName in node.children) { window.renderFolderNode(childName, node.children[childName], childrenContainer, depth + 1); } + window.connections.filter(c => c.folder_name === node.fullPath && !c.parent_id).forEach(item => { window.renderConnectionItem(item, childrenContainer, depth + 1); }); + window.scripts.filter(s => s.folder_name === node.fullPath).forEach(script => { window.renderScriptItem(script, childrenContainer, depth + 1); }); + wrapper.appendChild(childrenContainer); container.appendChild(wrapper); +}; + +window.renderConnectionItem = function(item, container, depth) { + const itemDiv = document.createElement('div'); itemDiv.setAttribute('data-search', `${item.name} ${item.host} ${item.tags} ${item.asset_type} ${item.hardware}`.toLowerCase()); itemDiv.className = `connection-item depth-${depth > 2 ? 2 : depth} search-item`; itemDiv.innerHTML = ` ${item.name}`; itemDiv.onclick = () => { document.querySelectorAll('.connection-item').forEach(el => el.classList.remove('active')); itemDiv.classList.add('active'); window.showConnectionDetails(item); }; container.appendChild(itemDiv); window.connections.filter(c => c.parent_id == item.id).forEach(child => window.renderConnectionItem(child, container, depth + 1)); +}; + +window.renderScriptItem = function(script, container, depth) { + const itemDiv = document.createElement('div'); itemDiv.setAttribute('data-search', `${script.name} ${script.language} script`.toLowerCase()); itemDiv.className = `connection-item depth-${depth > 2 ? 2 : depth} search-item`; + let icon = 'bi-file-earmark-text'; if(script.language === 'bash') icon = 'bi-terminal'; if(script.language === 'powershell') icon = 'bi-windows'; if(script.language === 'python') icon = 'bi-filetype-py'; if(script.language === 'sql') icon = 'bi-filetype-sql'; + itemDiv.innerHTML = ` ${script.name}`; itemDiv.onclick = () => { document.querySelectorAll('.connection-item').forEach(el => el.classList.remove('active')); itemDiv.classList.add('active'); window.showScriptDetails(script); }; container.appendChild(itemDiv); +}; + +window.filterSidebar = function() { const term = document.getElementById('searchInput').value.toLowerCase(); document.querySelectorAll('.search-item').forEach(item => { if(item.classList.contains('folder-header')) { item.style.display = item.innerText.toLowerCase().includes(term) ? '' : 'none'; } else { item.style.display = item.getAttribute('data-search').includes(term) ? '' : 'none'; } }); }; + +window.openNewFolderModal = function() { + if(window.currentUser.role !== 'admin') return; let folderOptions = ''; window.folders.forEach(f => folderOptions += ``); + const modalHtml = `
Neuer Ordner
`; + const oldModal = document.getElementById('folderModal'); if(oldModal) oldModal.remove(); document.body.insertAdjacentHTML('beforeend', modalHtml); new bootstrap.Modal(document.getElementById('folderModal')).show(); +}; +window.saveNewFolder = async function() { const parent = document.getElementById('new_folder_parent').value; const name = document.getElementById('new_folder_name').value.trim(); if(!name) return; const fullName = parent ? `${parent}/${name}` : name; await fetch('api.php?action=folders', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({name: fullName}) }); bootstrap.Modal.getInstance(document.getElementById('folderModal')).hide(); await window.loadData(); }; + +window.showScriptForm = function(editItem = null) { + if(window.currentUser.role !== 'admin' || window.isMapPage) return; + const isEdit = editItem !== null; let folderOptions = ''; window.folders.forEach(f => folderOptions += ``); + const langs = ['bash', 'powershell', 'python', 'sql', 'text']; let langOptions = langs.map(l => ``).join(''); + document.getElementById('main-area').innerHTML = `

${isEdit ? 'Skript bearbeiten' : 'Neues Skript'}

${isEdit ? `` : ''}
`; +}; +window.saveScript = async function() { if(window.currentUser.role !== 'admin') return; const data = { id: document.getElementById('f_script_id').value || null, folder_name: document.getElementById('f_script_folder').value || null, name: document.getElementById('f_script_name').value, language: document.getElementById('f_script_lang').value, script_body: document.getElementById('f_script_body').value }; if(!data.name) return alert("Bitte einen Namen vergeben!"); await fetch('api.php?action=scripts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); await window.loadData(); window.showHomeDashboard(); }; +window.showScriptDetails = function(item) { + if(window.isMapPage) return; let adminActions = ''; if (window.currentUser.role === 'admin') { adminActions = `
Bearbeiten
Editieren
Löschen
`; } + const safeContent = String(item.script_body).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); + document.getElementById('main-area').innerHTML = `
Aktionen
Code Kopieren
${adminActions}

${item.name}

Snippet: ${item.language.toUpperCase()}
${safeContent}
`; +}; +window.deleteScript = async function(id) { if(window.currentUser.role !== 'admin') return; if(!confirm("Skript wirklich löschen?")) return; await fetch('api.php?action=delete_script', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({id: id}) }); await window.loadData(); window.showHomeDashboard(); }; + +window.showConnectionForm = function(editItem = null) { + if(window.currentUser.role !== 'admin' || window.isMapPage) return; if(window.folders.length === 0 && !editItem) { alert("Bitte erstelle zuerst einen Ordner!"); return; } + const isEdit = editItem !== null; let protoOptions = window.protocols.map(p => ``).join(''); let folderOptions = window.folders.map(f => ``).join(''); let connOptions = window.connections.filter(c => !isEdit || c.id !== editItem.id).map(c => ``).join(''); let uplinkOptions = ``; uplinkOptions += window.connections.filter(c => !isEdit || c.id !== editItem.id).map(c => ``).join(''); let catOptions = ``; window.categories.forEach(cat => { catOptions += ``; }); + + document.getElementById('main-area').innerHTML = `

${isEdit ? 'Eigenschaften bearbeiten' : 'Neuer Eintrag'}

Verbindung & System
+
+
Topologie & Port-Mapping
Asset Details
${isEdit ? `` : ''}
`; +}; + +window.showConnectionDetails = function(item) { + if(window.isMapPage) return; const protoInfo = window.getProtoInfo(item.type); const children = window.connections.filter(c => c.parent_id == item.id); let tagsHtml = ''; if (item.tags) tagsHtml = item.tags.split(/[\s,]+/).filter(t=>t).map(t => `${t.startsWith('#')?t:'#'+t}`).join(''); + let uplinkName = '-'; if(item.uplink_id) { const up = window.connections.find(c => c.id == item.uplink_id); let portInfo = ''; if(item.local_port && item.uplink_port) portInfo = `(${item.local_port} ➔ ${item.uplink_port})`; else if(item.local_port) portInfo = `(Lokaler Port: ${item.local_port})`; else if(item.uplink_port) portInfo = `(Zielport: ${item.uplink_port})`; if(up) uplinkName = `${up.name} ${portInfo}`; } + let childRows = ''; if (children.length > 0) { childRows = `
Unterverbindungen (${children.length})
`; children.forEach(c => { childRows += `
${c.name}
${c.username || '-'}
`; }); childRows += `
`; } + let adminActions = ''; if (window.currentUser.role === 'admin') { const isLocked = item.map_locked == 1; const lockIcon = isLocked ? 'bi-pin-angle-fill text-danger' : 'bi-pin-angle text-secondary'; const lockText = isLocked ? 'Fixierung lösen' : 'Auf Map fixieren'; adminActions = `
Bearbeiten
Neu
Edit
${lockText}
Löschen
`; } + + document.getElementById('main-area').innerHTML = `
Aktionen
Sitzung öffnen
${item.type === 'web' ? `
Neuer Tab
` : ''}${adminActions}

${item.name}

${protoInfo.name}
Übersicht
Asset Info
Unterverbindungen (${children.length})
${item.type === 'web' ? `
` : ''}
Uplink (hängt an)
${uplinkName}
Host
${item.host}${item.port ? ':'+item.port : ''}
Benutzername
${item.username || '-'}
+ +
Passwort
••••••••
+ + ${tagsHtml ? `
Tags
${tagsHtml}
` : ''}
${item.kuma_url ? `
Systemstatus (Uptime Kuma)
${item.kuma_url.includes('/badge/') ? `` : `
`}
` : ''}
Kategorie
${item.asset_type || '-'}
Betriebssystem
${item.os || '-'}
Hardware / Modell
${item.hardware || '-'}
Seriennummer
${item.serial_number || '-'}
MAC-Adresse
${item.mac_address || '-'}
Standort (Rack / HE)
${item.location || '-'}
Garantie-Ablauf
${item.warranty || '-'}
${children.length > 0 ? childRows : '
Keine Unterverbindungen vorhanden.
'}
`; +}; + +window.saveConnection = async function() { if(window.currentUser.role !== 'admin') return; const t = document.getElementById('f_target').value; const data = { id: document.getElementById('f_conn_id').value || null, folder_name: t.startsWith('folder:') ? t.substring(7) : null, parent_id: t.startsWith('conn:') ? t.substring(5) : null, name: document.getElementById('f_conn_name').value, type: document.getElementById('f_type').value, host: document.getElementById('f_host').value, port: document.getElementById('f_port').value, username: document.getElementById('f_user').value, password: document.getElementById('f_pass').value, kuma_url: document.getElementById('f_kuma').value, tags: document.getElementById('f_tags').value, asset_type: document.getElementById('f_asset_type').value, os: document.getElementById('f_os').value, hardware: document.getElementById('f_hardware').value, serial_number: document.getElementById('f_serial').value, mac_address: document.getElementById('f_mac').value, location: document.getElementById('f_location').value, warranty: document.getElementById('f_warranty').value, uplink_id: document.getElementById('f_uplink_id').value || null, uplink_port: document.getElementById('f_uplink_port').value, local_port: document.getElementById('f_local_port').value }; await fetch('api.php?action=connections', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); await window.loadData(); window.showHomeDashboard(); }; +window.executeConnection = function(type, host, port, user, pass) { if(type === 'web') return; const p = port || "-"; const u = user || "-"; const pwd = pass || "-"; window.location.href = `my${type}://${host};${p};${u};${pwd}`; }; +window.deleteConnection = async function(id) { if(window.currentUser.role !== 'admin') return; if(!confirm("Wirklich löschen?")) return; await fetch('api.php?action=delete_connection', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({id: id}) }); await window.loadData(); window.showHomeDashboard(); }; +window.toggleMapLock = async function(id, lockState) { if(window.currentUser.role !== 'admin') return; await fetch('api.php?action=toggle_map_lock', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({id: id, locked: lockState}) }); await window.loadData(); const item = window.connections.find(c => c.id == id); if (item && !window.isMapPage) window.showConnectionDetails(item); }; + +window.loadMy2FA = async function() { const data = await (await fetch('api.php?action=my_2fa')).json(); const container = document.getElementById('2fa-container'); if (data.enabled) { container.innerHTML = `
2FA ist aktiv.
`; } else { container.innerHTML = `

Scanne diesen Code mit dem Google Authenticator.
Secret (Manuell): ${data.secret}

`; } }; +window.enable2FA = async function(secret) { const code = document.getElementById('setup_2fa_code').value; const res = await fetch('api.php?action=my_2fa', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({secret: secret, code: code}) }); if(res.ok) { window.showToast("2FA aktiviert!"); window.loadMy2FA(); } else { alert("Code falsch!"); } }; +window.disable2FA = async function() { if(!confirm("2FA wirklich deaktivieren?")) return; await fetch('api.php?action=my_2fa', { method: 'DELETE' }); window.showToast("2FA deaktiviert!"); window.loadMy2FA(); }; + +window.showSettings = function() { + if(window.isMapPage) return; document.querySelectorAll('.connection-item').forEach(el => el.classList.remove('active')); const mainArea = document.getElementById('main-area'); let adminTabs = ''; let adminContent = ''; + + if (window.currentUser.role === 'admin') { + let userRows = window.users.map(u => `${u.username}${u.role}${u.totp_enabled ? '' : '-'}`).join(''); + let protoRows = window.protocols.map(p => ` ${p.name}${p.proto_key}${p.exec_path}${p.proto_key !== 'web' ? `` : ''}`).join(''); + + let auditRows = ''; + if(window.auditLogs && window.auditLogs.length > 0) { + auditRows = window.auditLogs.map(l => `${l.timestamp}${l.user}${l.action}${l.details}`).join(''); + } else { auditRows = `Keine Einträge vorhanden.`; } + + adminTabs = `
  • System & Daten
  • Benutzer
  • Dienste
  • Audit-Log
  • `; + + // NEU: Eingabefeld für ausgeschlossene Passwort-Zeichen im Tab "System & Daten" + adminContent = ` +
    +
    Startseite / Dashboard
    +
    + +
    Passwort-Generator
    +
    +
    +
    +
    + +
    Daten & Import
    +
    |
    +
    +
    ${userRows}
    BenutzernameRolle2FA
    Benutzer anlegen/bearbeiten
    +
    Dienste
    Setup laden
    ${protoRows}
    DienstKürzelPfad
    +
    Audit-Log
    Zeigt die letzten 200 Aktionen
    ${auditRows}
    ZeitstempelBenutzerAktionDetails
    + `; + } else { adminContent = `
    Nur Administratoren können globale Einstellungen ändern. Lade dein lokales PC Setup-Skript hier herunter.
    `; } + mainArea.innerHTML = `

    Profil & Einstellungen

    • Mein Profil
    • ${adminTabs}
    Zwei-Faktor-Authentifizierung (2FA)

    Schütze deinen Account zusätzlich mit einem TOTP-Code.

    Lade 2FA Daten...
    ${adminContent}
    `; + window.loadMy2FA(); +}; + +window.saveUser = async function() { const data = { id: document.getElementById('u_id').value, username: document.getElementById('u_name').value, password: document.getElementById('u_pass').value, role: document.getElementById('u_role').value }; if(!data.username) return alert("Benutzername fehlt"); await fetch('api.php?action=users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); await window.loadData(); window.showSettings(); }; +window.editUser = function(id, name, role) { document.getElementById('u_id').value = id; document.getElementById('u_name').value = name; document.getElementById('u_role').value = role; }; +window.deleteUser = async function(id) { if(!confirm("User löschen?")) return; await fetch('api.php?action=users', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({id: id}) }); await window.loadData(); window.showSettings(); }; + +window.saveGeneralSettings = async function() { + const data = { + dashboard_url: document.getElementById('s_dashboard_url').value, + pwd_exclude_chars: document.getElementById('s_pwd_exclude').value + }; + await fetch('api.php?action=settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); + await window.loadData(); + window.showToast("Einstellungen gespeichert!"); +}; + +window.addProtocol = async function() { const data = { proto_key: document.getElementById('p_key').value, name: document.getElementById('p_name').value, exec_path: document.getElementById('p_path').value, args: '', icon: 'bi-terminal' }; await fetch('api.php?action=protocols', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); await window.loadData(); window.showSettings(); }; +window.deleteProtocol = async function(id) { await fetch('api.php?action=protocols', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({id: id}) }); await window.loadData(); window.showSettings(); }; +window.exportCSV = function() { const esc = (s) => '"' + String(s || '').replace(/"/g, '""') + '"'; let csv = "folder,name,type,host,port,username,password,kuma_url,tags,asset_type,os,hardware,serial_number,mac_address,location,warranty,uplink_id,uplink_port,local_port\n"; window.connections.forEach(c => { csv += [esc(c.folder_name), esc(c.name), esc(c.type), esc(c.host), esc(c.port), esc(c.username), esc(c.password), esc(c.kuma_url), esc(c.tags), esc(c.asset_type), esc(c.os), esc(c.hardware), esc(c.serial_number), esc(c.mac_address), esc(c.location), esc(c.warranty), esc(c.uplink_id), esc(c.uplink_port), esc(c.local_port)].join(',') + "\n"; }); const a = document.createElement('a'); a.href = window.URL.createObjectURL(new Blob([csv], { type: 'text/csv;charset=utf-8;' })); a.download = 'rdm_export.csv'; a.click(); }; +window.importCSV = async function() { const f = document.getElementById('csvFileInput').files[0]; if (!f) return alert("Datei wählen."); const reader = new FileReader(); reader.onload = async (e) => { const lines = e.target.result.split(/\r?\n/).filter(line => line.trim() !== ''); const delimiter = lines[0].includes(';') ? ';' : ','; const headers = lines[0].split(delimiter).map(h => h.trim().toLowerCase().replace(/^"|"$/g, '')); const data = []; for (let i = 1; i < lines.length; i++) { const regex = new RegExp(`(\\${delimiter}|\\r?\\n|\\r|^)(?:"([^"]*(?:""[^"]*)*)"|([^"\\${delimiter}\\r\\n]*))`, 'gi'); let values = []; let match; while (match = regex.exec(lines[i])) { values.push(match[2] ? match[2].replace(/""/g, '"') : match[3]); } let rowObj = {}; headers.forEach((h, idx) => rowObj[h] = values[idx] || ''); if(rowObj['name'] && rowObj['host']) data.push(rowObj); } if(data.length === 0) return alert("Keine gültigen Zeilen."); const res = await (await fetch('api.php?action=import_connections', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) })).json(); if (res.status === "success") { window.showToast("Import erfolgreich!"); await window.loadData(); window.showSettings(); } }; reader.readAsText(f); }; +window.exportMapPNG = function() { const canvas = document.querySelector('#mynetwork canvas'); if(!canvas) { alert("Fehler: Karte konnte nicht im Browser gefunden werden."); return; } const dataUrl = canvas.toDataURL("image/png"); const a = document.createElement('a'); a.href = dataUrl; a.download = 'Netzwerk-Topologie.png'; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.showToast("Export erfolgreich!"); }; +window.toggleFullScreen = function() { const elem = document.getElementById('mynetwork').parentElement; if (!document.fullscreenElement) { elem.requestFullscreen().catch(err => { alert("Fullscreen API wird nicht unterstützt."); }); } else { document.exitFullscreen(); } }; +window.filterMap = function(category) { if (!window.networkInstance) return; const allNodes = window.networkInstance.body.data.nodes.get(); const updates = []; allNodes.forEach(n => { if (n.id.startsWith('folder_')) return; const connId = n.id.replace('conn_', ''); const conn = window.connections.find(c => c.id == connId); let isMatch = !category || (conn && conn.asset_type === category); updates.push({ id: n.id, opacity: isMatch ? 1 : 0.15 }); }); window.networkInstance.body.data.nodes.update(updates); }; +window.resetMapPhysics = async function() { if(window.currentUser.role !== 'admin') return; if(!confirm("⚠️ ACHTUNG: Möchtest du wirklich alle fixierten Koordinaten löschen und die Schwerkraft neu berechnen lassen?")) return; await fetch('api.php?action=reset_map', { method: 'POST' }); window.showToast("Schwerkraft reaktiviert!"); await window.loadData(); }; + +window.drawMap = function() { + if (!window.isMapPage) return; + let containerWrapper = document.getElementById('mynetwork').parentElement; + if(!document.getElementById('map-controls') && containerWrapper) { + containerWrapper.style.position = 'relative'; const controls = document.createElement('div'); controls.id = 'map-controls'; + controls.style.cssText = "position:absolute; top:20px; right:20px; z-index:1000; background:var(--bg-card); padding:10px; border-radius:8px; border:1px solid var(--border-color); box-shadow:0 4px 10px rgba(0,0,0,0.3); display:flex; gap:10px; align-items:center;"; + let filterOptions = ``; window.categories.forEach(c => filterOptions += ``); + controls.innerHTML = `
    `; + containerWrapper.appendChild(controls); + } + + const currentTheme = document.documentElement.getAttribute('data-theme') || 'light'; const isDark = currentTheme === 'dark'; const textColor = isDark ? '#f1f1f1' : '#333333'; const folderColor = isDark ? '#333333' : '#e2e8f0'; const highlightEdgeColor = isDark ? '#3f98e7' : '#0078d7'; + const nodes = new vis.DataSet(); const edges = new vis.DataSet(); const folderSet = new Set(); + const groupColors = { 'rdp': { background: '#0078d7', border: '#005a9e' }, 'ssh': { background: '#2b2b2b', border: '#000000' }, 'web': { background: '#107c41', border: '#0b592f' }, 'default': { background: '#6c757d', border: '#495057' } }; + + window.connections.forEach(c => { + if (c.folder_name && !c.uplink_id && !folderSet.has(c.folder_name)) { folderSet.add(c.folder_name); const shortName = c.folder_name.split('/').pop(); nodes.add({ id: 'folder_' + c.folder_name, label: shortName, shape: 'box', color: { background: folderColor, border: '#aaaaaa' }, font: { color: textColor, size: 16, bold: true }, margin: 10 }); } + let tooltip = `${c.name}
    ${c.host}${c.port ? ':'+c.port : ''}
    `; if(c.asset_type) tooltip += `Typ: ${c.asset_type}
    `; if(c.hardware) tooltip += `Hardware: ${c.hardware}
    `; if(c.local_port && c.uplink_port) tooltip += `Ports: ${c.local_port} ➔ ${c.uplink_port}`; + let nodeShape = c.uplink_id ? 'box' : 'dot'; let nodeImage = undefined; let nodeFont = { color: c.uplink_id ? '#fff' : textColor, size: c.parent_id ? 12 : 14 }; + let labelText = ''; let typeBrackets = c.asset_type ? '\n[' + c.asset_type + ']' : ''; + if (window.mapLabelMode === 'name') { labelText = c.name + typeBrackets; } else if (window.mapLabelMode === 'ip') { labelText = (c.host || 'Keine IP') + typeBrackets; } else if (window.mapLabelMode === 'both') { labelText = c.name + '\n' + (c.host || 'Keine IP') + typeBrackets; } + if (c.asset_type) { const catInfo = window.categories.find(cat => cat.name === c.asset_type); if (catInfo) { nodeShape = 'image'; nodeImage = catInfo.url; nodeFont.color = textColor; nodeFont.background = isDark ? 'rgba(30,30,30,0.7)' : 'rgba(255,255,255,0.7)'; } } + let nodeOptions = { id: 'conn_' + c.id, label: c.map_locked == 1 ? "📌 " + labelText : labelText, shape: nodeShape, image: nodeImage, size: nodeImage ? 25 : (c.parent_id ? 10 : 18), color: groupColors[c.type] || groupColors['default'], font: nodeFont, title: tooltip }; + if (c.map_x !== null && c.map_y !== null) { nodeOptions.x = parseFloat(c.map_x); nodeOptions.y = parseFloat(c.map_y); nodeOptions.physics = false; } + if (c.map_locked == 1) nodeOptions.fixed = true; + const filterVal = document.getElementById('map-filter-select') ? document.getElementById('map-filter-select').value : ''; if(filterVal && c.asset_type !== filterVal) { nodeOptions.opacity = 0.15; } + nodes.add(nodeOptions); + if (c.uplink_id) { let edgeLabel = ''; if (c.local_port && c.uplink_port) edgeLabel = `${c.local_port} ➔ ${c.uplink_port}`; else if (c.local_port) edgeLabel = `${c.local_port} ➔`; else if (c.uplink_port) edgeLabel = `➔ ${c.uplink_port}`; edges.add({ from: 'conn_' + c.uplink_id, to: 'conn_' + c.id, label: edgeLabel, arrows: { to: { enabled: true, scaleFactor: 0.6 } }, font: { align: 'middle', size: 11, color: textColor, background: isDark ? '#1e1e1e' : '#ffffff', strokeWidth: 0 }, color: { color: highlightEdgeColor, highlight: highlightEdgeColor }, width: 2, length: 180 }); } else if (c.parent_id) { edges.add({ from: 'conn_' + c.parent_id, to: 'conn_' + c.id, dashes: true, color: { color: '#aaaaaa' }, length: 80 }); } else if (c.folder_name) { edges.add({ from: 'folder_' + c.folder_name, to: 'conn_' + c.id, color: { color: '#aaaaaa', opacity: 0.4 }, length: 250 }); } + }); + const container = document.getElementById('mynetwork'); const data = { nodes: nodes, edges: edges }; const options = { physics: { stabilization: true, barnesHut: { gravitationalConstant: -2500, springLength: 180, springConstant: 0.04 } }, edges: { smooth: false }, interaction: { hover: true, zoomView: true, dragView: true } }; + window.networkInstance = new vis.Network(container, data, options); + window.networkInstance.on("dragEnd", async function (params) { if (window.currentUser.role !== 'admin') return; if (params.nodes.length > 0) { const nodeId = params.nodes[0]; if(nodeId.startsWith('conn_')) { const id = nodeId.replace('conn_', ''); const pos = window.networkInstance.getPositions([nodeId])[nodeId]; await fetch('api.php?action=save_map_pos', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({id: id, x: pos.x, y: pos.y}) }); const conn = window.connections.find(c => c.id == id); if(conn) { conn.map_x = pos.x; conn.map_y = pos.y; } } } }); +}; + +window.spawnAsteroidsShip = function() { + if(!window.isMapPage || document.getElementById('asteroids-ship')) return; const mapContainer = document.getElementById('mynetwork'); if(!mapContainer) return; + const ship = document.createElement('div'); ship.id = 'asteroids-ship'; ship.innerHTML = ``; + const startY = Math.random() * mapContainer.offsetHeight; ship.style.cssText = `position: absolute; top: ${startY}px; left: -50px; z-index: 10; pointer-events: none; transition: transform 10s linear; transform: rotate(90deg);`; + mapContainer.appendChild(ship); + setTimeout(() => { const endY = Math.random() * mapContainer.offsetHeight; const endX = mapContainer.offsetWidth + 50; const angle = Math.atan2(endY - startY, endX - (-50)) * 180 / Math.PI; ship.style.transform = `translate(${endX}px, ${endY - startY}px) rotate(${angle + 90}deg)`; }, 100); + setTimeout(() => { if(ship.parentNode) ship.parentNode.removeChild(ship); }, 10500); +}; +setInterval(() => { if(Math.random() > 0.8) window.spawnAsteroidsShip(); }, 30000); + +window.initTheme(); window.checkSession(); \ No newline at end of file diff --git a/WebRDM/map.html b/WebRDM/map.html new file mode 100644 index 0000000..00381c4 --- /dev/null +++ b/WebRDM/map.html @@ -0,0 +1,50 @@ + + + + + Netzwerk Topologie - Web RDM + + + + + + + + +
    +
    +

    Web RDM Map

    +
    +
    + + + +
    +
    +

    Bitte gib deinen 6-stelligen Google Authenticator Code ein.

    + + +
    +
    +
    + +
    +
    + + Zurück zum RDM + +
    + + Physische Layer-1/2 Map. Knoten ziehen zum Sortieren. + + +
    +
    +
    + +
    + + + + +