From d079431cdf326116866e6d2e6c696e5b40d0a369 Mon Sep 17 00:00:00 2001 From: Eduard Wisch Date: Sun, 16 Aug 2026 11:09:28 +0200 Subject: [PATCH] Phase 5 abgeschlossen: Klarnamen, Parameter, Listen-Ampel, N+1, Standort MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Werkzeug-Klarnamen statt interner IDs: "IP-Scanner — 46 Geräte im Netz …" statt "[netzwerk] ipscan — …". Bewusst eine kurze Zuordnung in netdiagToolName() statt eines zweiten Satzes Sprachschlüssel — die kanonischen Namen stehen in der App, doppelte Pflege wäre eine Fehlerquelle. Enthält auch dhcpcheck/wifiscan: in der App gibt es sie nicht mehr, in der PRODUKTIONSDATENBANK stehen dazu aber noch Messungen (4 bzw. 2). Die Gegenprüfung hielt den Punkt für gegenstandslos, hatte dabei aber nur die Testdatenbank angesehen. - Messparameter anzeigen (Prod-Messung #126): unter jeder Messung steht jetzt "Ziel: 192.168.1.1 · Dauer (s): 300" in Karte und PDF. Vorher stand das Ergebnis ohne Bezugspunkt da — man sah nicht, wogegen gemessen wurde. - Listenseite: Ampel je Protokoll (schlechteste Einzelmessung) plus Anzahl, dazu ein Filter "nur mit Befund". Status 3 "nicht messbar" geht bewusst NICHT ins Maximum ein — er ist keine Aussage über das Kundennetz — sondern wird separat als "n.m." ausgewiesen. Im Browser geprüft: der Filter liefert ausschließlich Protokolle mit Warnung oder Fehler. - N+1-Queries behoben: fetchAllByProtocol() las nur die rowids und setzte je Zeile ein eigenes fetch() ab. Jetzt eine Abfrage mit setVarsFromFetchObj(), zusätzlich mit Entity-Filter (fehlte bisher ganz). Nachgemessen über SHOW SESSION STATUS: 46 Geräte + 7 Messungen brauchen statt 55 Abfragen noch eine. - Standort aus der Kundenadresse vorbelegen, wenn der Techniker nichts eingetragen hat; eine vorhandene Angabe wird nie überschrieben. Über die echte API geprüft. Offen bleibt aus Phase 5 nur der Vergleich zweier Protokolle (Geräte-Diff nach MAC) — eigenes Feature mit eigener Ansicht. --- api/protocols.php | 12 +++++ class/netdiagdevice.class.php | 14 ++++-- class/netdiagmeasurement.class.php | 11 +++-- langs/de_DE/netdiag.lang | 1 + langs/en_US/netdiag.lang | 1 + lib/netdiag.lib.php | 79 ++++++++++++++++++++++++++++++ lib/netdiag_pdf.lib.php | 11 ++++- netdiagindex.php | 47 ++++++++++++++++-- netdiagprotocol_card.php | 10 +++- 9 files changed, 172 insertions(+), 14 deletions(-) diff --git a/api/protocols.php b/api/protocols.php index 6e4da79..5652d25 100644 --- a/api/protocols.php +++ b/api/protocols.php @@ -144,6 +144,18 @@ $protocol->fk_commande = !empty($p['orderId']) ? (int) $p['orderId'] : nul $protocol->date_diag = netdiag_api_timestamp($p['dateDiag'] ?? 0); $protocol->fk_user_techniker = (int) $user->id; $protocol->standort = isset($p['location']) ? (string) $p['location'] : ''; +// Standort leer? Dann aus der Kundenadresse vorbelegen. Der Techniker tippt +// vor Ort selten etwas ein, im Protokoll stand der Ort deshalb fast immer +// leer — obwohl er in Dolibarr am Kunden hinterlegt ist. Eine vom Techniker +// eingetragene Angabe wird NIE überschrieben. +if (trim($protocol->standort) === '' && !empty($protocol->fk_soc)) { + require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php'; + $socTmp = new Societe($db); + if ($socTmp->fetch((int) $protocol->fk_soc) > 0) { + $teile = array_filter(array(trim($socTmp->address), trim($socTmp->zip.' '.$socTmp->town))); + $protocol->standort = substr(implode(', ', $teile), 0, 255); + } +} $protocol->subnet = isset($p['subnet']) ? (string) $p['subnet'] : ''; $protocol->status = isset($p['status']) ? (int) $p['status'] : NetDiagProtocol::STATUS_DRAFT; $protocol->note = isset($p['note']) ? (string) $p['note'] : ''; diff --git a/class/netdiagdevice.class.php b/class/netdiagdevice.class.php index 5aefba5..c5efd10 100644 --- a/class/netdiagdevice.class.php +++ b/class/netdiagdevice.class.php @@ -135,18 +135,24 @@ class NetDiagDevice extends CommonObject */ public function fetchAllByProtocol($fk_protocol) { + // Alle Spalten in EINER Abfrage holen. Vorher wurden nur die rowids + // gelesen und je Zeile ein eigenes fetch() abgesetzt — bei 46 Geräten + // also 47 Abfragen statt einer. Die Kartenansicht und der + // PDF-Generator laden diese Liste jeweils komplett. $result = array(); - $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element; + $sql = "SELECT * FROM ".$this->db->prefix().$this->table_element; $sql .= " WHERE fk_protocol = ".((int) $fk_protocol); + $sql .= " AND entity IN (".getEntity($this->element).")"; $sql .= " ORDER BY INET_ATON(ip), rowid"; $resql = $this->db->query($sql); if ($resql) { while ($obj = $this->db->fetch_object($resql)) { $dev = new self($this->db); - if ($dev->fetch((int) $obj->rowid) > 0) { - $result[] = $dev; - } + $dev->setVarsFromFetchObj($obj); + $dev->id = (int) $obj->rowid; + $result[] = $dev; } + $this->db->free($resql); } return $result; } diff --git a/class/netdiagmeasurement.class.php b/class/netdiagmeasurement.class.php index 85d388b..27c9632 100644 --- a/class/netdiagmeasurement.class.php +++ b/class/netdiagmeasurement.class.php @@ -128,18 +128,21 @@ class NetDiagMeasurement extends CommonObject */ public function fetchAllByProtocol($fk_protocol) { + // Eine Abfrage statt einer je Messung (siehe NetDiagDevice::fetchAllByProtocol) $result = array(); - $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element; + $sql = "SELECT * FROM ".$this->db->prefix().$this->table_element; $sql .= " WHERE fk_protocol = ".((int) $fk_protocol); + $sql .= " AND entity IN (".getEntity($this->element).")"; $sql .= " ORDER BY date_measure, rowid"; $resql = $this->db->query($sql); if ($resql) { while ($obj = $this->db->fetch_object($resql)) { $m = new self($this->db); - if ($m->fetch((int) $obj->rowid) > 0) { - $result[] = $m; - } + $m->setVarsFromFetchObj($obj); + $m->id = (int) $obj->rowid; + $result[] = $m; } + $this->db->free($resql); } return $result; } diff --git a/langs/de_DE/netdiag.lang b/langs/de_DE/netdiag.lang index 5b09fa4..1c2713e 100644 --- a/langs/de_DE/netdiag.lang +++ b/langs/de_DE/netdiag.lang @@ -74,3 +74,4 @@ NetDiagFoundViaPort=Port NetDiagFoundViaArp=ARP NetDiagFoundViaMdns=mDNS NetDiagLastSeen=Zuletzt gesehen +NetDiagOnlyWithFindings=nur mit Befund diff --git a/langs/en_US/netdiag.lang b/langs/en_US/netdiag.lang index 489148e..967956d 100644 --- a/langs/en_US/netdiag.lang +++ b/langs/en_US/netdiag.lang @@ -74,3 +74,4 @@ NetDiagFoundViaPort=Port NetDiagFoundViaArp=ARP NetDiagFoundViaMdns=mDNS NetDiagLastSeen=Last seen +NetDiagOnlyWithFindings=only with findings diff --git a/lib/netdiag.lib.php b/lib/netdiag.lib.php index 49b9c9a..4fa8d38 100644 --- a/lib/netdiag.lib.php +++ b/lib/netdiag.lib.php @@ -430,3 +430,82 @@ function netdiagKundentext($text) } return $text; } + +/** + * Klarname eines Werkzeugs für die Anzeige. + * + * Die kanonischen Namen stehen eigentlich in der App (`Tool.name`), das + * Dolibarr-Modul kann darauf aber nicht zugreifen. Deshalb hier eine bewusst + * kurze Zuordnung statt eines zweiten Satzes Sprachschlüssel — mit Rückfall + * auf die ID, damit ein neues Werkzeug nie einen leeren Titel bekommt. + * + * Enthält auch Werkzeuge, die es in der App nicht mehr gibt („dhcpcheck", + * „wifiscan"): in der Produktionsdatenbank stehen dazu noch Messungen aus + * älteren Protokollen, und die sollen weiterhin lesbar sein. + * + * @param string $tool Werkzeug-ID aus der Messung + * @return string Klarname, oder die ID selbst wenn unbekannt + */ +function netdiagToolName($tool) +{ + $map = array( + 'ipscan' => 'IP-Scanner', + 'portscan' => 'Port-Scan', + 'ping' => 'Ping / Qualität', + 'ipconflict' => 'IP-Konflikt', + 'snmp' => 'SNMP-Switch', + 'traceroute' => 'Traceroute', + 'stresstest' => 'Dauer-/Stresstest', + 'iperf' => 'Durchsatz-Test', + 'wifikanal' => 'WLAN-Kanalanalyse', + 'monitor' => 'Geräte-Monitor', + 'iptest' => 'IP-Test (Dose)', + 'wifitrack' => 'WLAN-Empfang', + // nicht mehr vorhandene Werkzeuge, aber in Altprotokollen enthalten + 'dhcpcheck' => 'DHCP-Prüfung', + 'wifiscan' => 'WLAN-Scan', + ); + return $map[$tool] ?? $tool; +} + +/** + * Messparameter für die Anzeige aufbereiten („Ziel: 192.168.1.1"). + * + * Ohne sie steht im Protokoll zwar das Ergebnis, aber nicht, worauf es sich + * bezieht — bei Prod-Messung #126 war deshalb nicht erkennbar, gegen welches + * Ziel der Dauertest lief. Nur ausgefüllte Parameter erscheinen. + * + * @param string $json JSON-String der Parameter + * @return string lesbarer Text, leer wenn nichts anzuzeigen ist + */ +function netdiagFormatParams($json) +{ + if (empty($json)) { + return ''; + } + $data = json_decode($json, true); + if (!is_array($data) || empty($data)) { + return ''; + } + $labels = array( + 'host' => 'Ziel', + 'subnet' => 'Netzbereich', + 'ip' => 'Gerät', + 'ports' => 'Ports', + 'port' => 'Port', + 'count' => 'Anzahl Proben', + 'durationSec' => 'Dauer (s)', + 'intervalSec' => 'Messabstand (s)', + 'community' => 'SNMP-Community', + 'rounds' => 'Runden', + 'timeoutMs' => 'Zeitgrenze (ms)', + ); + $teile = array(); + foreach ($data as $k => $v) { + if ($v === null || $v === '' || is_array($v)) { + continue; + } + $teile[] = ($labels[$k] ?? ucfirst((string) $k)).': '.$v; + } + return implode(' · ', $teile); +} diff --git a/lib/netdiag_pdf.lib.php b/lib/netdiag_pdf.lib.php index a1de688..674a6f2 100644 --- a/lib/netdiag_pdf.lib.php +++ b/lib/netdiag_pdf.lib.php @@ -181,10 +181,19 @@ function netdiagGeneratePdf($db, $protocol, $outputlangs) // wie ein abgebrochenes Dokument. netdiagPdfEnsureSpace($pdf, 11); $pdf->SetFont('', 'B', 9); - $title = ($m->category ? '['.$m->category.'] ' : '').$m->tool.($m->label ? ' — '.$m->label : ''); + // Klarname statt interner ID — „ipscan" sagt dem Kunden nichts + $title = netdiagToolName($m->tool).($m->label ? ' — '.$m->label : ''); $pdf->Cell(150, 6, dol_trunc($title, 80), 1, 0, 'L'); $pdf->Cell(30, 6, $statusmarks[$st].$statuslabels[$st], 1, 1, 'C', true); $pdf->SetFont('', '', 8); + // Messparameter: ohne sie steht das Ergebnis ohne Bezug da — man sieht + // nicht, gegen welches Ziel gemessen wurde (Prod-Messung #126). + $params = netdiagFormatParams($m->params); + if ($params !== '') { + $pdf->SetFont('', 'I', 7); + $pdf->MultiCell(180, 4, $params, 1, 'L'); + $pdf->SetFont('', '', 8); + } if ($m->tool === 'stresstest') { netdiagPdfStressTest($pdf, $m->result); } elseif ($m->tool === 'wifikanal') { diff --git a/netdiagindex.php b/netdiagindex.php index 36634e2..a87573f 100644 --- a/netdiagindex.php +++ b/netdiagindex.php @@ -67,6 +67,10 @@ if (!$user->hasRight('netdiag', 'protocol', 'read')) { $search_ref = GETPOST('search_ref', 'alpha'); $search_soc = GETPOST('search_soc', 'alpha'); +// „nur mit Befund": zeigt Protokolle, in denen mindestens eine Messung +// Warnung oder Fehler ist — beim Durchsehen vieler Protokolle die eigentliche +// Frage („wo muss ich nochmal ran?") +$search_problem = GETPOST('search_problem', 'int'); $limit = GETPOSTINT('limit') ? GETPOSTINT('limit') : $conf->liste_limit; $page = GETPOSTINT('page'); if ($page < 0) { @@ -85,7 +89,16 @@ llxHeader('', $title, '', '', 0, 0, '', '', '', 'mod-netdiag page-list'); // Datensätze laden $sql = "SELECT p.rowid, p.ref, p.label, p.date_diag, p.standort, p.subnet, p.status,"; $sql .= " s.rowid as socid, s.nom as socname,"; -$sql .= " (SELECT COUNT(*) FROM ".$db->prefix()."netdiag_device d WHERE d.fk_protocol = p.rowid) as devcount"; +$sql .= " (SELECT COUNT(*) FROM ".$db->prefix()."netdiag_device d WHERE d.fk_protocol = p.rowid) as devcount,"; +// Schlechteste Einzelmessung als Protokoll-Ampel. Status 3 („nicht messbar") +// darf dabei NICHT als schlechtester Wert gelten — er ist keine Aussage über +// das Kundennetz; deshalb wird er separat gezählt statt in MAX() einzugehen. +$sql .= " (SELECT MAX(m.measure_status) FROM ".$db->prefix()."netdiag_measurement m + WHERE m.fk_protocol = p.rowid AND m.measure_status IN (0,1,2)) as worststatus,"; +$sql .= " (SELECT COUNT(*) FROM ".$db->prefix()."netdiag_measurement m + WHERE m.fk_protocol = p.rowid) as meascount,"; +$sql .= " (SELECT COUNT(*) FROM ".$db->prefix()."netdiag_measurement m + WHERE m.fk_protocol = p.rowid AND m.measure_status = 3) as unmesscount"; $sql .= " FROM ".$db->prefix()."netdiag_protocol as p"; $sql .= " LEFT JOIN ".$db->prefix()."societe as s ON s.rowid = p.fk_soc"; $sql .= " WHERE p.entity IN (".getEntity('netdiagprotocol').")"; @@ -95,6 +108,10 @@ if ($search_ref) { if ($search_soc) { $sql .= natural_search('s.nom', $search_soc); } +if ($search_problem) { + $sql .= " AND EXISTS (SELECT 1 FROM ".$db->prefix()."netdiag_measurement m2 + WHERE m2.fk_protocol = p.rowid AND m2.measure_status IN (1,2))"; +} $sql .= " ORDER BY p.date_diag DESC, p.rowid DESC"; $sql .= $db->plimit($limit, $offset); @@ -116,7 +133,8 @@ print ''; print ''; print ''; print ''; -print ''; +print ''; +print ''; print ''; print ''; @@ -125,6 +143,7 @@ print ''; print ''; print ''; print ''; +print ''; print ''; print ''; @@ -143,11 +162,33 @@ foreach ($rows as $obj) { print ''; print ''; print ''; + // Messungs-Ampel: schlechteste Einzelmessung, plus Hinweis auf nicht + // messbare Punkte. So sieht man in der Liste, wo noch etwas zu tun ist, + // ohne jedes Protokoll einzeln zu öffnen. + print ''; print ''; print ''; } if (empty($rows)) { - print ''; + print ''; } print '
'.$langs->trans("ThirdParty").''.$langs->trans("DateDiag").''.$langs->trans("Location").''.$langs->trans("NetDiagDevices").''.$langs->trans("NetDiagMeasurements").''.$langs->trans("Status").'
'.dol_print_date($db->jdate($obj->date_diag), 'dayhour').''.dol_escape_htmltag($obj->standort).''.((int) $obj->devcount).''; + $meascount = (int) $obj->meascount; + if ($meascount === 0) { + print '-'; + } else { + $worst = ($obj->worststatus === null) ? 3 : (int) $obj->worststatus; + if ($worst < 0 || $worst > 3) { + $worst = 1; + } + $ampellabels = array(0 => 'NetDiagMeasureOk', 1 => 'NetDiagMeasureWarn', 2 => 'NetDiagMeasureFail', 3 => 'NetDiagMeasureUnmeasurable'); + $ampelcss = array(0 => 'badge-status4', 1 => 'badge-status1', 2 => 'badge-status8', 3 => 'badge-status0'); + print ''.$langs->trans($ampellabels[$worst]).''; + print ' '.$meascount; + if ((int) $obj->unmesscount > 0) { + print ', '.((int) $obj->unmesscount).' n.m.'; + } + print ''; + } + print ''.$proto->getLibStatut(3).'
'.$langs->trans("NoRecordFound").'
'.$langs->trans("NoRecordFound").'
'; diff --git a/netdiagprotocol_card.php b/netdiagprotocol_card.php index 3b9c6b7..90df87d 100644 --- a/netdiagprotocol_card.php +++ b/netdiagprotocol_card.php @@ -260,8 +260,14 @@ foreach ($measurements as $m) { print ''; print ''.dol_print_date($m->date_measure, 'dayhour').''; print ''.dol_escape_htmltag($m->category).''; - print ''.dol_escape_htmltag($m->tool).''; - print ''.dol_escape_htmltag($m->label).''; + print ''.dol_escape_htmltag(netdiagToolName($m->tool)).''; + print ''.dol_escape_htmltag($m->label); + // Messparameter darunter: sonst ist nicht erkennbar, wogegen gemessen wurde + $params = netdiagFormatParams($m->params); + if ($params !== '') { + print '
'.dol_escape_htmltag($params).''; + } + print ''; print ''.netdiagFormatResult($m->result, $m->tool).''; print ''.$langs->trans($statuslabels[$st]).''; print '';