diff --git a/CHANGELOG.md b/CHANGELOG.md index 85148bc..b29eefb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,20 @@ Dieses Projekt verwendet [Semantic Versioning](https://semver.org/lang/de/). --- +## [0.6.0] - 2026-07-01 + +### Added +- Netzwerk-Segmentverwaltung: Subnetze mit Name, VLAN-ID und Aktiv/Inaktiv-Flag definieren +- Neue Tabelle `network_segments` als organisatorische Einheit für Scan-Durchläufe +- Dashboard-Ansicht „Netzwerk" mit Übersicht aller Segmente, KPI-Karten und offenen Ereignissen +- Globale Suche über alle Segmente nach IP, MAC, Hostname und Bezeichnung +- Navigation „Netzwerk" als Dropdown: Dashboard, Segmente, Alle Geräte, Suche, Import +- Import-Seite: Segment-Auswahl beim Upload eines Angry IP Scanner Exports +- Segment-Detailseite mit Scan-Historie +- `segment_id` FK in `network_scans` Tabelle + +--- + ## [0.5.0] - 2026-06-29 ### Added @@ -81,7 +95,8 @@ Dieses Projekt verwendet [Semantic Versioning](https://semver.org/lang/de/). - Grundlegende PHP-Projektstruktur (public/, src/, config/) - composer.json, .gitignore, README.md -[Unreleased]: http://localhost:3000/admin/Network-MGMT/compare/v0.5.0...HEAD +[Unreleased]: http://localhost:3000/admin/Network-MGMT/compare/v0.6.0...HEAD +[0.6.0]: http://localhost:3000/admin/Network-MGMT/compare/v0.5.0...v0.6.0 [0.5.0]: http://localhost:3000/admin/Network-MGMT/compare/v0.4.0...v0.5.0 [0.4.0]: http://localhost:3000/admin/Network-MGMT/compare/v0.3.0...v0.4.0 [0.3.0]: http://localhost:3000/admin/Network-MGMT/compare/v0.2.0...v0.3.0 diff --git a/app/Http/Controllers/NetworkController.php b/app/Http/Controllers/NetworkController.php index e0a2921..a5234f0 100644 --- a/app/Http/Controllers/NetworkController.php +++ b/app/Http/Controllers/NetworkController.php @@ -5,31 +5,67 @@ namespace App\Http\Controllers; use App\Models\NetworkDevice; use App\Models\NetworkDeviceEvent; use App\Models\NetworkScan; +use App\Models\NetworkSegment; use App\Services\NetworkScanImporter; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Storage; use Illuminate\View\View; class NetworkController extends Controller { - // --- Übersicht --- - public function index(): View + // --- Dashboard --- + public function dashboard(): View { - $latestScan = NetworkScan::latest()->first(); - $totalDevices = NetworkDevice::count(); + $segments = NetworkSegment::withCount('scans')->orderBy('name')->get(); + $totalDevices = NetworkDevice::count(); $onlineDevices = NetworkDevice::where('status', 'online')->count(); - $recentEvents = NetworkDeviceEvent::with('device') + $recentEvents = NetworkDeviceEvent::with('device') ->where('documented', false) ->latest() ->take(10) ->get(); - $scans = NetworkScan::latest()->take(10)->get(); - return view('network.index', compact( - 'latestScan', 'totalDevices', 'onlineDevices', 'recentEvents', 'scans' + // 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', 'latestScans' )); } + // --- Globale Suche --- + public function search(Request $request): View + { + $q = trim($request->get('q', '')); + $devices = collect(); + + if (strlen($q) >= 2) { + $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('label', 'like', "%{$q}%") + ->orWhere('mac_vendor', 'like', "%{$q}%"); + }) + ->orderBy('current_ip') + ->paginate(50) + ->withQueryString(); + } + + return view('network.search', compact('q', 'devices')); + } + + // --- Alte index-Route umleiten --- + public function index(): \Illuminate\Http\RedirectResponse + { + return redirect()->route('network.dashboard'); + } + // --- Alle Geräte --- public function devices(Request $request): View { @@ -136,19 +172,25 @@ class NetworkController extends Controller // --- Import --- public function showImport(): View { - return view('network.import'); + $segments = NetworkSegment::where('active', true)->orderBy('name')->get(); + return view('network.import', compact('segments')); } public function import(Request $request, NetworkScanImporter $importer): RedirectResponse { $request->validate([ - 'scan_file' => ['required', 'file', 'mimes:txt,csv', 'max:5120'], + '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_path('app/' . $path); + $path = $request->file('scan_file')->store('imports', 'local'); + $fullPath = Storage::disk('local')->path($path); - $scan = $importer->importAngryIpScannerFile($fullPath, auth()->id()); + $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."); diff --git a/app/Http/Controllers/NetworkSegmentController.php b/app/Http/Controllers/NetworkSegmentController.php new file mode 100644 index 0000000..cab113f --- /dev/null +++ b/app/Http/Controllers/NetworkSegmentController.php @@ -0,0 +1,83 @@ +orderBy('name') + ->get(); + + return view('network.segments.index', compact('segments')); + } + + public function create(): View + { + return view('network.segments.create'); + } + + public function store(Request $request): RedirectResponse + { + $validated = $request->validate([ + 'name' => ['required', 'string', 'max:100'], + 'subnet' => ['required', 'string', 'max:50'], + 'vlan_id' => ['nullable', 'integer', 'min:1', 'max:4094'], + 'active' => ['boolean'], + 'description' => ['nullable', 'string', 'max:500'], + ]); + + $validated['active'] = $request->boolean('active', true); + $validated['created_by'] = auth()->id(); + + NetworkSegment::create($validated); + + return redirect()->route('network.segments.index') + ->with('success', "Segment \"{$validated['name']}\" angelegt."); + } + + public function show(NetworkSegment $segment): View + { + $scans = $segment->scans()->latest()->paginate(20); + $latestScan = $segment->scans()->latest()->first(); + + return view('network.segments.show', compact('segment', 'scans', 'latestScan')); + } + + public function edit(NetworkSegment $segment): View + { + return view('network.segments.edit', compact('segment')); + } + + public function update(Request $request, NetworkSegment $segment): RedirectResponse + { + $validated = $request->validate([ + 'name' => ['required', 'string', 'max:100'], + 'subnet' => ['required', 'string', 'max:50'], + 'vlan_id' => ['nullable', 'integer', 'min:1', 'max:4094'], + 'active' => ['boolean'], + 'description' => ['nullable', 'string', 'max:500'], + ]); + + $validated['active'] = $request->boolean('active', true); + $segment->update($validated); + + return redirect()->route('network.segments.index') + ->with('success', "Segment \"{$segment->name}\" aktualisiert."); + } + + public function destroy(NetworkSegment $segment): RedirectResponse + { + $name = $segment->name; + $segment->delete(); + + return redirect()->route('network.segments.index') + ->with('success', "Segment \"{$name}\" gelöscht."); + } +} diff --git a/app/Models/NetworkScan.php b/app/Models/NetworkScan.php index 3445613..d9b9d57 100644 --- a/app/Models/NetworkScan.php +++ b/app/Models/NetworkScan.php @@ -9,7 +9,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany; class NetworkScan extends Model { protected $fillable = [ - 'subnet', 'source', 'scanner', 'total_hosts', + 'segment_id', 'subnet', 'source', 'scanner', 'total_hosts', 'online_hosts', 'new_devices', 'changed_devices', 'notes', 'created_by', ]; @@ -27,4 +27,9 @@ class NetworkScan extends Model { return $this->belongsTo(User::class, 'created_by'); } + + public function segment(): BelongsTo + { + return $this->belongsTo(NetworkSegment::class, 'segment_id'); + } } diff --git a/app/Models/NetworkSegment.php b/app/Models/NetworkSegment.php new file mode 100644 index 0000000..eb7232f --- /dev/null +++ b/app/Models/NetworkSegment.php @@ -0,0 +1,59 @@ + 'boolean', + 'vlan_id' => 'integer', + ]; + + public function createdBy(): BelongsTo + { + return $this->belongsTo(User::class, 'created_by'); + } + + public function scans(): HasMany + { + return $this->hasMany(NetworkScan::class, 'segment_id'); + } + + public function latestScan(): HasMany + { + return $this->hasMany(NetworkScan::class, 'segment_id')->latestOfMany(); + } + + /** Alle Hosts dieses Segments (über Scans) */ + public function hosts(): HasManyThrough + { + return $this->hasManyThrough(NetworkHost::class, NetworkScan::class, 'segment_id', 'scan_id'); + } + + /** Anzahl online-Hosts im letzten Scan */ + public function getOnlineCountAttribute(): int + { + return $this->scans()->latest()->first()?->online_hosts ?? 0; + } + + /** Anzahl Geräte gesamt im letzten Scan */ + public function getTotalCountAttribute(): int + { + return $this->scans()->latest()->first()?->total_hosts ?? 0; + } + + /** Letzter Scan-Zeitpunkt */ + public function getLastScannedAtAttribute(): ?string + { + return $this->scans()->latest()->first()?->created_at?->format('d.m.Y H:i'); + } +} diff --git a/app/Services/NetworkScanImporter.php b/app/Services/NetworkScanImporter.php index b722f23..1db1055 100644 --- a/app/Services/NetworkScanImporter.php +++ b/app/Services/NetworkScanImporter.php @@ -8,6 +8,7 @@ use App\Models\NetworkHost; use App\Models\NetworkScan; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Log; class NetworkScanImporter { @@ -19,47 +20,84 @@ class NetworkScanImporter /** * Importiert eine Angry IP Scanner .txt Exportdatei. */ - public function importAngryIpScannerFile(string $filePath, int $createdBy): NetworkScan + public function importAngryIpScannerFile(string $filePath, int $createdBy, ?int $segmentId = null): NetworkScan { - $lines = file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); - $scanner = ''; - $subnet = ''; - $headers = []; - $rows = []; + // Datei einlesen und UTF-8 BOM entfernen + $content = file_get_contents($filePath); + $content = preg_replace('/^\xEF\xBB\xBF/', '', $content); // UTF-8 BOM + $content = str_replace("\r\n", "\n", $content); // Windows CRLF → LF + $content = str_replace("\r", "\n", $content); // altes Mac CR → LF + + $lines = array_filter(explode("\n", $content), fn($l) => trim($l) !== ''); + $lines = array_values($lines); + + $scanner = ''; + $subnet = ''; + $headers = []; + $rows = []; + $splitPattern = null; // wird beim ersten Header-Treffer gesetzt foreach ($lines as $line) { - if (str_starts_with($line, 'Erstellt von') || str_starts_with($line, 'Created by')) { - $scanner = trim(str_replace(['Erstellt von ', 'Created by '], '', $line)); + $trimmed = trim($line); + + if (str_starts_with($trimmed, 'Erstellt von') || str_starts_with($trimmed, 'Created by')) { + $scanner = trim(str_replace(['Erstellt von ', 'Created by '], '', $trimmed)); continue; } - if (str_starts_with($line, 'Gescannt') || str_starts_with($line, 'Scanned')) { - // "Gescannt 192.168.86.0 - 192.168.86.255" - preg_match('/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/', $line, $m); + if (str_starts_with($trimmed, 'Gescannt') || str_starts_with($trimmed, 'Scanned')) { + preg_match('/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/', $trimmed, $m); $subnet = $m[1] ?? '0.0.0.0'; continue; } - if (str_starts_with($line, 'http') || str_starts_with($line, 'https')) { - continue; // URL-Zeile überspringen - } - // Header-Zeile (beginnt mit "IP") - if (str_starts_with($line, 'IP') && empty($headers)) { - $headers = preg_split('/\t+/', $line); - $headers = array_map('trim', $headers); + if (str_starts_with($trimmed, 'http')) { continue; } - // Datenzeile - if (!empty($headers) && str_starts_with(trim($line), '') && preg_match('/^\d{1,3}\./', trim($line))) { - $cols = preg_split('/\t+/', $line); - $row = []; + + // Header-Zeile erkennen (beginnt mit "IP") + if (str_starts_with($trimmed, 'IP') && empty($headers)) { + // Trennzeichen auto-detektieren + if (substr_count($trimmed, "\t") >= 2) { + $splitPattern = '/\t/'; + } elseif (substr_count($trimmed, ';') >= 2) { + $splitPattern = '/;/'; + } elseif (substr_count($trimmed, ',') >= 2) { + $splitPattern = '/,/'; + } else { + // Angry IP Scanner: mehrere normale Leerzeichen als Trennzeichen + // Non-Breaking Spaces (0xC2 0xA0) bleiben innerhalb von Werten erhalten + $splitPattern = '/ {2,}/'; + } + $headers = array_map('trim', preg_split($splitPattern, $trimmed)); + continue; + } + + // Datenzeile: muss mit IP-Adresse beginnen + if (!empty($headers) && $splitPattern !== null + && preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/', $trimmed)) { + $cols = array_map( + fn($v) => trim(str_replace("\xc2\xa0", ' ', $v)), // Non-Breaking Spaces → normale Spaces + preg_split($splitPattern, $line) + ); + $row = []; foreach ($headers as $i => $h) { - $row[$h] = trim($cols[$i] ?? ''); + $row[$h] = $cols[$i] ?? ''; } $rows[] = $row; } } - return DB::transaction(function () use ($rows, $scanner, $subnet, $createdBy) { + // DEBUG: Log parse results + Log::info('NetworkScanImporter: scanner=' . $scanner . ', subnet=' . $subnet); + Log::info('NetworkScanImporter: separator hex=' . bin2hex($separator)); + Log::info('NetworkScanImporter: headers=' . json_encode($headers)); + Log::info('NetworkScanImporter: rows found=' . count($rows)); + if (!empty($rows)) { + Log::info('NetworkScanImporter: first row=' . json_encode($rows[0])); + } + + return DB::transaction(function () use ($rows, $scanner, $subnet, $createdBy, $segmentId) { $this->scan = NetworkScan::create([ + 'segment_id' => $segmentId, 'subnet' => $subnet, 'source' => 'import', 'scanner' => $scanner, @@ -83,23 +121,23 @@ class NetworkScanImporter private function processRow(array $row): void { - $ip = $this->extractColumn($row, ['IP']); - $ping = $this->extractColumn($row, ['Ping']); - $host = $this->extractColumn($row, ['Hostname']); - $mac = $this->normalizeMAC($this->extractColumn($row, ['MAC Addresse', 'MAC Address'])); - $vendor = $this->extractColumn($row, ['MAC Hersteller', 'MAC Vendor']); - $ttl = (int) $this->extractColumn($row, ['TTL']) ?: null; - $ports = $this->extractColumn($row, ['Ports']); - $netbios= $this->extractColumn($row, ['NetBIOS Info']); - $http = $this->extractColumn($row, ['HTTP Sender']); - $web = $this->extractColumn($row, ['Web Erkennung', 'Web Detection']); + $ip = trim($this->extractColumn($row, ['IP', 'IP-Adresse', 'IP Address', 'IP Adresse'])); + $ping = trim($this->extractColumn($row, ['Ping', 'Ping (ms)'])); + $host = $this->cleanValue($this->extractColumn($row, ['Hostname', 'Host Name', 'DNS-Hostname'])); + $mac = $this->normalizeMAC($this->extractColumn($row, ['MAC Adresse', 'MAC Addresse', 'MAC Address', 'MAC-Adresse', 'MAC'])); + $vendor = $this->cleanValue($this->extractColumn($row, ['MAC Hersteller', 'MAC Vendor', 'Hersteller', 'Vendor'])); + $ttl = (int) $this->cleanValue($this->extractColumn($row, ['TTL'])) ?: null; + $ports = $this->cleanValue($this->extractColumn($row, ['Ports', 'Gefilterte Ports', 'Offene Ports'])); + $netbios= $this->cleanValue($this->extractColumn($row, ['NetBIOS Info', 'NetBIOS-Info', 'NetBIOS'])); + $http = $this->cleanValue($this->extractColumn($row, ['HTTP Sender', 'HTTP'])); + $web = $this->cleanValue($this->extractColumn($row, ['Web Erkennung', 'Web Detection', 'Webserver'])); $pingMs = null; $status = 'offline'; - if (!empty($ping) && $ping !== 'n/a' && $ping !== '-') { - preg_match('/(\d+)/', $ping, $m); - $pingMs = isset($m[1]) ? (int)$m[1] : null; + // Online-Erkennung: Ping enthält eine Zahl gefolgt von "ms" + if (preg_match('/(\d+)\s*ms/i', $ping, $m)) { + $pingMs = (int) $m[1]; $status = 'online'; $this->onlineHosts++; } @@ -210,6 +248,17 @@ class NetworkScanImporter return ''; } + /** + * Bereinigt Angry IP Scanner Platzhalterwerte wie [n/a], [n/s], [n/d] zu leerem String. + */ + private function cleanValue(string $value): string + { + if (preg_match('/^\[n\/[asd]\]$/i', trim($value))) { + return ''; + } + return trim($value); + } + private function normalizeMAC(string $mac): string { // Verschiedene MAC-Formate normalisieren zu XX:XX:XX:XX:XX:XX diff --git a/database/migrations/2026_07_01_100001_create_network_segments_table.php b/database/migrations/2026_07_01_100001_create_network_segments_table.php new file mode 100644 index 0000000..31f12e8 --- /dev/null +++ b/database/migrations/2026_07_01_100001_create_network_segments_table.php @@ -0,0 +1,27 @@ +id(); + $table->string('name'); // z.B. "Büro", "Server-VLAN" + $table->string('subnet'); // z.B. "192.168.86.0/24" + $table->unsignedSmallInteger('vlan_id')->nullable(); // z.B. 10, 20 + $table->boolean('active')->default(true); // aktiv überwachen + $table->text('description')->nullable(); // optionale Beschreibung + $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete(); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('network_segments'); + } +}; diff --git a/database/migrations/2026_07_01_100002_add_segment_id_to_network_scans.php b/database/migrations/2026_07_01_100002_add_segment_id_to_network_scans.php new file mode 100644 index 0000000..debc80b --- /dev/null +++ b/database/migrations/2026_07_01_100002_add_segment_id_to_network_scans.php @@ -0,0 +1,27 @@ +foreignId('segment_id') + ->nullable() + ->after('id') + ->constrained('network_segments') + ->nullOnDelete(); + }); + } + + public function down(): void + { + Schema::table('network_scans', function (Blueprint $table) { + $table->dropForeign(['segment_id']); + $table->dropColumn('segment_id'); + }); + } +}; diff --git a/resources/views/layouts/navigation.blade.php b/resources/views/layouts/navigation.blade.php index 6d41dbe..65637af 100644 --- a/resources/views/layouts/navigation.blade.php +++ b/resources/views/layouts/navigation.blade.php @@ -39,10 +39,35 @@ @endrole - {{-- Netzwerk-Link --}} - - 🌐 Netzwerk - + {{-- Netzwerk-Dropdown --}} + + + + + + + 📊 Dashboard + + + 🗂️ Segmente + + + 💻 Alle Geräte + + + 🔍 Suche + + + ⬆️ Scan importieren + + + {{-- Hilfe-Dropdown --}} @@ -133,8 +158,21 @@ @endrole
- - 🌐 Netzwerk +
Netzwerk
+ +   📊 Dashboard + + +   🗂️ Segmente + + +   💻 Alle Geräte + + +   🔍 Suche + + +   ⬆️ Scan importieren
diff --git a/resources/views/network/dashboard.blade.php b/resources/views/network/dashboard.blade.php new file mode 100644 index 0000000..cbcbae4 --- /dev/null +++ b/resources/views/network/dashboard.blade.php @@ -0,0 +1,158 @@ + + + + + +
+
+ + {{-- KPI-Karten --}} +
+
+

Segmente

+

{{ $segments->count() }}

+
+
+

Geräte gesamt

+

{{ $totalDevices }}

+
+
+

Aktuell online

+

{{ $onlineDevices }}

+
+
+

Offene Ereignisse

+

{{ $recentEvents->count() }}

+
+
+ + {{-- Globale Suche --}} +
+
+ + +
+
+ + {{-- Segmente --}} +
+
+

Netzwerk-Segmente

+ Alle verwalten → +
+ + @if($segments->isEmpty()) +
+

Noch keine Segmente definiert.

+ + Erstes Segment anlegen + +
+ @else + + + + + + + + + + + + + + @foreach($segments as $segment) + @php + $lastScan = $latestScans->get($segment->id)?->first(); + @endphp + + + + + + + + + + @endforeach + +
StatusNameSubnetzVLANLetzter ScanOnline / GesamtScans
+ + + + {{ $segment->name }} + + {{ $segment->subnet }} + {{ $segment->vlan_id ? 'VLAN ' . $segment->vlan_id : '—' }} + + {{ $lastScan?->created_at->format('d.m.Y H:i') ?? '—' }} + + @if($lastScan) + {{ $lastScan->online_hosts }} + / {{ $lastScan->total_hosts }} + @else + + @endif + + {{ $segment->scans_count }} +
+ @endif +
+ + {{-- Undokumentierte Ereignisse --}} + @if($recentEvents->count() > 0) +
+
+

+ Undokumentierte Ereignisse + {{ $recentEvents->count() }} +

+
+
+ @foreach($recentEvents as $event) +
+
+ + +
+ {{ $event->event_label }} + + + {{ $event->device->display_name }} + + · {{ $event->created_at->format('d.m.Y H:i') }} + +
+
+ Detail → +
+ @endforeach +
+
+ @endif + +
+
+
diff --git a/resources/views/network/import.blade.php b/resources/views/network/import.blade.php index d8daefb..8000d18 100644 --- a/resources/views/network/import.blade.php +++ b/resources/views/network/import.blade.php @@ -1,7 +1,7 @@
- Netzwerk + Netzwerk /

Scan importieren

@@ -23,6 +23,21 @@
@csrf +
+ + + +
+
+ +

Globale Suche

+
+ +
+
+ + + + + @if($q) + + Zurücksetzen + + @endif + + + @if($q && strlen($q) < 2) +

Mindestens 2 Zeichen eingeben.

+ @elseif($q) +

+ {{ $devices->total() }} Ergebnis(se) für „{{ $q }}" über alle Segmente +

+ +
+ + + + + + + + + + + + + + @forelse($devices as $device) + + + + + + + + + + @empty + + + + @endforelse + +
StatusIP-AdresseMAC-AdresseHostname / BezeichnungHerstellerZuletzt gesehen
+ + {{ $device->current_ip }}{{ $device->mac_address }} + @if($device->label) + {{ $device->label }} + ({{ $device->hostname }}) + @else + {{ $device->hostname ?? '—' }} + @endif + {{ $device->mac_vendor ?? '—' }} + {{ $device->last_seen_at?->format('d.m.Y H:i') ?? '—' }} + + Detail → +
Keine Geräte gefunden.
+ + @if($devices->hasPages()) +
+ {{ $devices->links() }} +
+ @endif +
+ @endif + +
+
+ diff --git a/resources/views/network/segments/create.blade.php b/resources/views/network/segments/create.blade.php new file mode 100644 index 0000000..2216331 --- /dev/null +++ b/resources/views/network/segments/create.blade.php @@ -0,0 +1,65 @@ + + +
+ Segmente + / +

Neues Segment

+
+
+ +
+
+
+
+ @csrf + +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + +
+ +
+ + +
+ +
+ + Abbrechen + + +
+
+
+
+
+
diff --git a/resources/views/network/segments/edit.blade.php b/resources/views/network/segments/edit.blade.php new file mode 100644 index 0000000..d45ecb4 --- /dev/null +++ b/resources/views/network/segments/edit.blade.php @@ -0,0 +1,64 @@ + + +
+ Segmente + / +

{{ $segment->name }} bearbeiten

+
+
+ +
+
+
+
+ @csrf @method('PUT') + +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + +
+ +
+ active) ? 'checked' : '' }} + class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500" /> + +
+ +
+ + Abbrechen + + +
+
+
+
+
+
diff --git a/resources/views/network/segments/index.blade.php b/resources/views/network/segments/index.blade.php new file mode 100644 index 0000000..ee8335f --- /dev/null +++ b/resources/views/network/segments/index.blade.php @@ -0,0 +1,77 @@ + + +
+
+ Netzwerk + / +

Segmente

+
+ + + Segment anlegen + +
+
+ +
+
+ + @if(session('success')) +
{{ session('success') }}
+ @endif + +
+ + + + + + + + + + + + + + @forelse($segments as $segment) + + + + + + + + + + @empty + + + + @endforelse + +
AktivNameSubnetzVLANScansBeschreibung
+ + + + {{ $segment->name }} + + {{ $segment->subnet }} + {{ $segment->vlan_id ? 'VLAN ' . $segment->vlan_id : '—' }} + {{ $segment->scans_count }} + {{ $segment->description ?? '—' }} + + Bearbeiten +
+ @csrf @method('DELETE') + +
+
+ Noch keine Segmente. Jetzt anlegen +
+
+
+
+
diff --git a/resources/views/network/segments/show.blade.php b/resources/views/network/segments/show.blade.php new file mode 100644 index 0000000..2fc0630 --- /dev/null +++ b/resources/views/network/segments/show.blade.php @@ -0,0 +1,115 @@ + + +
+
+ Segmente + / +

{{ $segment->name }}

+
+ +
+
+ +
+
+ + {{-- Segment-Info --}} +
+
+

Subnetz

+

{{ $segment->subnet }}

+
+
+

VLAN

+

{{ $segment->vlan_id ? 'VLAN ' . $segment->vlan_id : '—' }}

+
+
+

Status

+

+ {{ $segment->active ? 'Aktiv' : 'Inaktiv' }} +

+
+
+

Scans gesamt

+

{{ $scans->total() }}

+
+
+ + @if($latestScan) +
+
+

Letzter Scan

+

{{ $latestScan->created_at->format('d.m.Y H:i') }}

+
+
+

Online / Gesamt

+

+ {{ $latestScan->online_hosts }} + / {{ $latestScan->total_hosts }} +

+
+
+

Neue Geräte

+

{{ $latestScan->new_devices }}

+
+
+ @endif + + {{-- Scan-Historie --}} +
+
+

Scan-Historie

+
+ + + + + + + + + + + + + + @forelse($scans as $scan) + + + + + + + + + + @empty + + + + @endforelse + +
DatumScannerGesamtOnlineNeuGeändert
{{ $scan->created_at->format('d.m.Y H:i') }}{{ $scan->scanner ?? '—' }}{{ $scan->total_hosts }}{{ $scan->online_hosts }}{{ $scan->new_devices }}{{ $scan->changed_devices }} + Details → +
Noch keine Scans für dieses Segment.
+ @if($scans->hasPages()) +
+ {{ $scans->links() }} +
+ @endif +
+ +
+
+
diff --git a/routes/web.php b/routes/web.php index efe739f..f0bac02 100644 --- a/routes/web.php +++ b/routes/web.php @@ -3,6 +3,7 @@ use App\Http\Controllers\ProfileController; use App\Http\Controllers\HelpController; use App\Http\Controllers\NetworkController; +use App\Http\Controllers\NetworkSegmentController; use App\Http\Controllers\Admin\UserController as AdminUserController; use App\Http\Controllers\Admin\LayoutController as AdminLayoutController; use Illuminate\Support\Facades\Route; @@ -38,14 +39,30 @@ Route::prefix('network') ->name('network.') ->middleware(['auth']) ->group(function () { - Route::get('/', [NetworkController::class, 'index'])->name('index'); + // Dashboard + Route::get('/', [NetworkController::class, 'dashboard'])->name('dashboard'); + + // Globale Suche + Route::get('/search', [NetworkController::class, 'search'])->name('search'); + + // Segmente (CRUD) + Route::resource('segments', NetworkSegmentController::class) + ->names('segments'); + + // Geräte Route::get('/devices', [NetworkController::class, 'devices'])->name('devices'); Route::get('/devices/{device}', [NetworkController::class, 'device'])->name('device'); Route::put('/devices/{device}', [NetworkController::class, 'updateDevice'])->name('device.update'); Route::post('/devices/{device}/note', [NetworkController::class, 'addNote'])->name('device.note'); + + // Ereignisse Route::post('/events/{event}/document', [NetworkController::class, 'documentEvent'])->name('document'); + + // Import Route::get('/import', [NetworkController::class, 'showImport'])->name('import'); Route::post('/import', [NetworkController::class, 'import'])->name('import'); + + // Scan-Detail Route::get('/scans/{scan}', [NetworkController::class, 'scan'])->name('scan'); });