whereNotNull('ip_address') ->select(DB::raw("CONCAT(SUBSTRING_INDEX(ip_address, '.', 3), '.0/24') as subnet")) ->distinct() ->orderBy('subnet') ->pluck('subnet') ->toArray(); return response()->json($subnets); } // --- Dashboard --- public function dashboard(): View { $segments = NetworkSegment::withCount('scans')->orderBy('name')->get(); $totalDevices = NetworkDevice::count(); $onlineDevices = NetworkDevice::where('status', 'online')->count(); // IP-Wechsel separat → prominenter Block oben $ipChangeEvents = NetworkDeviceEvent::with('device') ->where('documented', false) ->where('event_type', 'ip_changed') ->latest() ->get(); // Alle anderen undokumentierten Ereignisse $recentEvents = NetworkDeviceEvent::with('device') ->where('documented', false) ->where('event_type', '!=', 'ip_changed') ->latest() ->take(15) ->get(); // Letzten Scan pro Segment $latestScans = NetworkScan::select('network_scans.*') ->orderByDesc('created_at') ->get() ->groupBy('segment_id'); return view('network.dashboard', compact( 'segments', 'totalDevices', 'onlineDevices', 'recentEvents', 'ipChangeEvents', 'latestScans' )); } // --- Chronologischer IP-Verlauf --- public function history(Request $request): View { $query = \App\Models\NetworkHost::query() ->join('network_scans', 'network_scans.id', '=', 'network_hosts.scan_id') ->leftJoin('network_segments', 'network_segments.id', '=', 'network_scans.segment_id') ->leftJoin('network_devices', 'network_devices.id', '=', 'network_hosts.device_id') ->select([ 'network_hosts.*', 'network_scans.created_at as scan_time', 'network_scans.segment_id', 'network_scans.scanner', 'network_segments.name as segment_name', 'network_devices.label as device_label', ]); if ($request->filled('ip')) { $query->where('network_hosts.ip_address', 'like', '%' . $request->ip . '%'); } if ($request->filled('mac')) { $query->where('network_hosts.mac_address', 'like', '%' . $request->mac . '%'); } if ($request->filled('hostname')) { $query->where(function ($q) use ($request) { $q->where('network_hosts.hostname', 'like', '%' . $request->hostname . '%') ->orWhere('network_devices.label', 'like', '%' . $request->hostname . '%'); }); } if ($request->filled('segment')) { $query->where('network_scans.segment_id', $request->segment); } if ($request->filled('status')) { $query->where('network_hosts.status', $request->status); } if ($request->filled('from')) { $query->where('network_scans.created_at', '>=', $request->from . ' 00:00:00'); } if ($request->filled('to')) { $query->where('network_scans.created_at', '<=', $request->to . ' 23:59:59'); } $entries = $query->orderByDesc('network_scans.created_at') ->orderByRaw('INET_ATON(network_hosts.ip_address)') ->paginate(100) ->withQueryString(); $segments = \App\Models\NetworkSegment::orderBy('name')->get(); return view('network.history', compact('entries', 'segments')); } // --- Globale Suche --- public function search(Request $request): View { $q = trim($request->get('q', '')); $devices = collect(); $hostResults = collect(); if (strlen($q) >= 2) { // Suche in network_devices (MAC-basiert / bereits getracked) $devices = NetworkDevice::with('events') ->where(function ($query) use ($q) { $query->where('current_ip', 'like', "%{$q}%") ->orWhere('mac_address', 'like', "%{$q}%") ->orWhere('hostname', 'like', "%{$q}%") ->orWhere('netbios_name','like', "%{$q}%") ->orWhere('label', 'like', "%{$q}%") ->orWhere('mac_vendor', 'like', "%{$q}%"); }) ->orderBy('current_ip') ->paginate(50) ->withQueryString(); // Zusätzlich: direkte Suche in network_hosts (auch ohne Device-Eintrag) // Zeigt IPs/Hosts die in Scans gefunden wurden, aber noch kein Device-Record haben $hostResults = NetworkHost::query() ->join('network_scans', 'network_scans.id', '=', 'network_hosts.scan_id') ->leftJoin('network_segments', 'network_segments.id', '=', 'network_scans.segment_id') ->whereNull('network_hosts.device_id') ->where(function ($query) use ($q) { $query->where('network_hosts.ip_address', 'like', "%{$q}%") ->orWhere('network_hosts.mac_address', 'like', "%{$q}%") ->orWhere('network_hosts.hostname', 'like', "%{$q}%"); }) ->select([ 'network_hosts.ip_address', 'network_hosts.mac_address', 'network_hosts.hostname', 'network_hosts.mac_vendor', 'network_hosts.status', 'network_hosts.ping_ms', 'network_scans.created_at as scan_time', 'network_segments.name as segment_name', DB::raw('network_scans.id as scan_id'), ]) ->orderByDesc('network_scans.created_at') ->limit(50) ->get() ->unique('ip_address'); // Jede IP nur einmal (neuester Fund) } return view('network.search', compact('q', 'devices', 'hostResults')); } // --- Alte index-Route umleiten --- public function index(): \Illuminate\Http\RedirectResponse { return redirect()->route('network.dashboard'); } // --- Alle Geräte --- public function devices(Request $request): View { $query = NetworkDevice::with('events') ->orderBy('current_ip'); if ($request->filled('status')) { $query->where('status', $request->status); } if ($request->filled('search')) { $s = $request->search; $query->where(function ($q) use ($s) { $q->where('current_ip', 'like', "%{$s}%") ->orWhere('mac_address', 'like', "%{$s}%") ->orWhere('hostname', 'like', "%{$s}%") ->orWhere('label', 'like', "%{$s}%") ->orWhere('mac_vendor', 'like', "%{$s}%"); }); } $devices = $query->paginate(50)->withQueryString(); return view('network.devices', compact('devices')); } // --- Geräte-Detail --- public function device(NetworkDevice $device): View { $device->load(['events.documentedBy', 'hosts.scan']); $ipHistory = $device->hosts() ->select('ip_address', 'status', 'ping_ms', 'created_at') ->join('network_scans', 'network_scans.id', '=', 'network_hosts.scan_id') ->orderByDesc('network_hosts.created_at') ->take(50) ->get(); return view('network.device', compact('device', 'ipHistory')); } // --- Gerät: Label/Notiz speichern --- public function updateDevice(Request $request, NetworkDevice $device): RedirectResponse { $validated = $request->validate([ 'label' => ['nullable', 'string', 'max:100'], 'notes' => ['nullable', 'string', 'max:1000'], ]); $oldLabel = $device->label; $device->update($validated); if ($oldLabel !== $validated['label']) { NetworkDeviceEvent::create([ 'device_id' => $device->id, 'event_type' => 'label_changed', 'old_value' => $oldLabel, 'new_value' => $validated['label'], 'description'=> 'Bezeichnung manuell geändert', 'documented' => true, 'documented_by' => auth()->id(), 'documented_at' => now(), ]); } return redirect()->route('network.device', $device) ->with('success', 'Gerät aktualisiert.'); } // --- Ereignis dokumentieren --- public function documentEvent(Request $request, NetworkDeviceEvent $event): RedirectResponse { $request->validate([ 'description' => ['nullable', 'string', 'max:500'], ]); $event->update([ 'documented' => true, 'documented_by' => auth()->id(), 'documented_at' => now(), 'description' => $request->description ?? $event->description, ]); return back()->with('success', 'Ereignis dokumentiert.'); } // --- Manuelle Notiz hinzufügen --- public function addNote(Request $request, NetworkDevice $device): RedirectResponse { $request->validate([ 'description' => ['required', 'string', 'max:500'], ]); NetworkDeviceEvent::create([ 'device_id' => $device->id, 'event_type' => 'manual_note', 'description' => $request->description, 'documented' => true, 'documented_by' => auth()->id(), 'documented_at' => now(), ]); return back()->with('success', 'Notiz hinzugefügt.'); } // --- Import --- public function showImport(): View { $segments = NetworkSegment::where('active', true)->orderBy('name')->get(); return view('network.import', compact('segments')); } public function import(Request $request, NetworkScanImporter $importer): RedirectResponse { $request->validate([ 'segment_id' => ['nullable', 'exists:network_segments,id'], 'scan_file' => ['required', 'file', 'mimes:txt,csv', 'max:10240'], ]); $path = $request->file('scan_file')->store('imports', 'local'); $fullPath = Storage::disk('local')->path($path); $scan = $importer->importAngryIpScannerFile( $fullPath, auth()->id(), $request->input('segment_id') ); return redirect()->route('network.scan', $scan) ->with('success', "Import abgeschlossen: {$scan->online_hosts} online, {$scan->new_devices} neue Geräte."); } // --- Scan-Detail --- public function scan(NetworkScan $scan): View { $hosts = $scan->hosts() ->orderByRaw("INET_ATON(ip_address)") ->get(); $notes = collect(); if ($scan->segment_id) { $notes = \App\Models\NetworkIpNote::where('segment_id', $scan->segment_id) ->pluck('note', 'ip_address'); } return view('network.scan', compact('scan', 'hosts', 'notes')); } }