dolibarr.netdiag/netdiagindex.php
Eduard Wisch d079431cdf Phase 5 abgeschlossen: Klarnamen, Parameter, Listen-Ampel, N+1, Standort
- 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.
2026-08-16 11:09:28 +02:00

199 lines
7.7 KiB
PHP

<?php
/* Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* \file netdiag/netdiagindex.php
* \ingroup netdiag
* \brief Liste aller Netzwerk-Diagnose-Protokolle (Backend)
*/
// Dolibarr-Umgebung laden
$res = 0;
if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) {
$res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
}
$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME'];
$tmp2 = realpath(__FILE__);
$i = strlen($tmp) - 1;
$j = strlen($tmp2) - 1;
while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) {
$i--;
$j--;
}
if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) {
$res = @include substr($tmp, 0, ($i + 1))."/main.inc.php";
}
if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) {
$res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php";
}
if (!$res && file_exists("../main.inc.php")) {
$res = @include "../main.inc.php";
}
if (!$res && file_exists("../../main.inc.php")) {
$res = @include "../../main.inc.php";
}
if (!$res) {
die("Include of main fails");
}
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php';
require_once __DIR__.'/class/netdiagprotocol.class.php';
/**
* @var DoliDB $db
* @var Translate $langs
* @var User $user
*/
$langs->loadLangs(array("netdiag@netdiag", "companies", "orders"));
if (!$user->hasRight('netdiag', 'protocol', 'read')) {
accessforbidden();
}
$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) {
$page = 0;
}
$offset = $limit * $page;
/*
* Ansicht
*/
$form = new Form($db);
$title = $langs->trans("NetDiagProtocols");
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,";
// 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').")";
if ($search_ref) {
$sql .= natural_search('p.ref', $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);
$resql = $db->query($sql);
$rows = array();
if ($resql) {
while ($obj = $db->fetch_object($resql)) {
$rows[] = $obj;
}
}
print load_fiche_titre($title, '', 'fa-network-wired');
print '<form method="GET" action="'.$_SERVER["PHP_SELF"].'">';
print '<div class="div-table-responsive">';
print '<table class="tagtable liste">';
print '<tr class="liste_titre">';
print '<td><input type="text" name="search_ref" class="maxwidth100" value="'.dol_escape_htmltag($search_ref).'" placeholder="'.$langs->trans("Ref").'"></td>';
print '<td><input type="text" name="search_soc" class="maxwidth150" value="'.dol_escape_htmltag($search_soc).'" placeholder="'.$langs->trans("ThirdParty").'"></td>';
print '<td colspan="3" class="right"><label class="opacitymedium small"><input type="checkbox" name="search_problem" value="1"'.($search_problem ? ' checked' : '').'> '.$langs->trans("NetDiagOnlyWithFindings").'</label></td>';
print '<td class="right"><input type="submit" class="button small" value="'.$langs->trans("Search").'"></td>';
print '</tr>';
print '<tr class="liste_titre">';
print '<th>'.$langs->trans("Ref").'</th>';
print '<th>'.$langs->trans("ThirdParty").'</th>';
print '<th>'.$langs->trans("DateDiag").'</th>';
print '<th>'.$langs->trans("Location").'</th>';
print '<th class="right">'.$langs->trans("NetDiagDevices").'</th>';
print '<th class="center">'.$langs->trans("NetDiagMeasurements").'</th>';
print '<th class="center">'.$langs->trans("Status").'</th>';
print '</tr>';
$proto = new NetDiagProtocol($db);
foreach ($rows as $obj) {
$proto->id = $obj->rowid;
$proto->status = $obj->status;
$cardurl = dol_buildpath('/netdiag/netdiagprotocol_card.php', 1).'?id='.$obj->rowid;
print '<tr class="oddeven">';
print '<td><a href="'.$cardurl.'">'.img_picto('', 'fa-network-wired').' '.dol_escape_htmltag($obj->ref).'</a></td>';
if ($obj->socid) {
print '<td><a href="'.DOL_URL_ROOT.'/societe/card.php?socid='.$obj->socid.'">'.dol_escape_htmltag($obj->socname).'</a></td>';
} else {
print '<td></td>';
}
print '<td>'.dol_print_date($db->jdate($obj->date_diag), 'dayhour').'</td>';
print '<td>'.dol_escape_htmltag($obj->standort).'</td>';
print '<td class="right">'.((int) $obj->devcount).'</td>';
// 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 '<td class="center">';
$meascount = (int) $obj->meascount;
if ($meascount === 0) {
print '<span class="opacitymedium">-</span>';
} 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 '<span class="badge '.$ampelcss[$worst].'">'.$langs->trans($ampellabels[$worst]).'</span>';
print ' <span class="opacitymedium small">'.$meascount;
if ((int) $obj->unmesscount > 0) {
print ', '.((int) $obj->unmesscount).' n.m.';
}
print '</span>';
}
print '</td>';
print '<td class="center">'.$proto->getLibStatut(3).'</td>';
print '</tr>';
}
if (empty($rows)) {
print '<tr><td colspan="7" class="opacitymedium center">'.$langs->trans("NoRecordFound").'</td></tr>';
}
print '</table>';
print '</div>';
print '</form>';
llxFooter();
$db->close();