Compare commits

..

No commits in common. "221145736fdfdfda78f5dd2cdeeb3b34958c0b31" and "8b433726f326dcd78ae54ecacf27489bba8a6a8f" have entirely different histories.

10 changed files with 37 additions and 432 deletions

View file

@ -438,55 +438,3 @@ function netdiag_api_protocol_list($db, $filtersql = '')
} }
return $list; return $list;
} }
/**
* Liste (Array aus der App) für die Datenbank zu einer Komma-Zeichenkette
* zusammenfassen.
*
* Ports und mDNS-Dienste sind in der App Arrays; in der Tabelle liegen sie als
* Textspalte. Bewusst mit Längenbegrenzung: ein Gerät mit sehr vielen Diensten
* darf den INSERT nicht scheitern lassen (MariaDB kürzt im strict mode nicht,
* sondern lehnt ab).
*
* @param mixed $val Array oder Skalar aus dem JSON-Payload
* @param int $maxLen Maximale Länge der Zielspalte
* @return string Komma-Liste, ggf. gekürzt
*/
function netdiag_join_list($val, $maxLen)
{
if ($val === null || $val === '') {
return '';
}
if (!is_array($val)) {
$val = array($val);
}
$teile = array();
foreach ($val as $v) {
if (is_array($v)) {
continue; // verschachtelte Strukturen gehören nicht in eine Textspalte
}
$s = trim((string) $v);
if ($s !== '') {
$teile[] = str_replace(',', ' ', $s);
}
}
$out = implode(',', $teile);
return strlen($out) > $maxLen ? substr($out, 0, $maxLen) : $out;
}
/**
* Komma-Zeichenkette aus der Datenbank wieder als Liste liefern.
*
* @param string|null $val Spaltenwert
* @return array<int,string>
*/
function netdiag_split_list($val)
{
if ($val === null || trim((string) $val) === '') {
return array();
}
$teile = array_map('trim', explode(',', (string) $val));
return array_values(array_filter($teile, static function ($t) {
return $t !== '';
}));
}

View file

@ -55,20 +55,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$devices = array(); $devices = array();
foreach ($devObj->fetchAllByProtocol($protocol->id) as $d) { foreach ($devObj->fetchAllByProtocol($protocol->id) as $d) {
$devices[] = array( $devices[] = array(
'id' => (int) $d->id, 'id' => (int) $d->id,
'ip' => $d->ip, 'ip' => $d->ip,
'mac' => $d->mac, 'mac' => $d->mac,
'hostname' => $d->hostname, 'hostname' => $d->hostname,
'netbiosName' => $d->netbios_name, 'vendor' => $d->vendor,
'mdnsName' => $d->mdns_name, 'deviceType' => $d->devicetype,
'mdnsServices' => netdiag_split_list($d->mdns_services), 'note' => $d->note,
'customName' => $d->custom_name,
'vendor' => $d->vendor,
'deviceType' => $d->devicetype,
'openPorts' => array_map('intval', netdiag_split_list($d->open_ports)),
'foundVia' => $d->found_via,
'lastSeen' => !empty($d->last_seen) ? (int) $db->jdate($d->last_seen) * 1000 : null,
'note' => $d->note,
); );
} }
$measObj = new NetDiagMeasurement($db); $measObj = new NetDiagMeasurement($db);
@ -178,16 +171,6 @@ foreach ($devicesIn as $d) {
$dev->vendor = isset($d['vendor']) ? (string) $d['vendor'] : ''; $dev->vendor = isset($d['vendor']) ? (string) $d['vendor'] : '';
$dev->devicetype = isset($d['deviceType']) ? (string) $d['deviceType'] : ''; $dev->devicetype = isset($d['deviceType']) ? (string) $d['deviceType'] : '';
$dev->note = isset($d['note']) ? (string) $d['note'] : ''; $dev->note = isset($d['note']) ? (string) $d['note'] : '';
// Gerätemerkmale aus dem IP-Scan. Ohne sie steht im Kundenprotokoll nur
// die IP, während der Techniker in der App „Drucker HP, Port 9100" sieht.
$dev->netbios_name = isset($d['netbiosName']) ? (string) $d['netbiosName'] : '';
$dev->mdns_name = isset($d['mdnsName']) ? (string) $d['mdnsName'] : '';
$dev->mdns_services = netdiag_join_list($d['mdnsServices'] ?? null, 512);
$dev->custom_name = isset($d['customName']) ? (string) $d['customName'] : '';
$dev->open_ports = netdiag_join_list($d['openPorts'] ?? null, 255);
$dev->found_via = isset($d['foundVia']) ? substr((string) $d['foundVia'], 0, 16) : '';
// lastSeen kommt als Unix-Zeit in Millisekunden aus der App
$dev->last_seen = !empty($d['lastSeen']) ? $db->idate((int) round(((float) $d['lastSeen']) / 1000)) : null;
$dev->tms = dol_now(); $dev->tms = dol_now();
if ($dev->create($user, 1) <= 0) { if ($dev->create($user, 1) <= 0) {
$db->rollback(); $db->rollback();

View file

@ -53,15 +53,8 @@ class NetDiagDevice extends CommonObject
'ip' => array('type' => 'varchar(45)', 'label' => 'IpAddress', 'enabled' => 1, 'visible' => 1, 'position' => 20), 'ip' => array('type' => 'varchar(45)', 'label' => 'IpAddress', 'enabled' => 1, 'visible' => 1, 'position' => 20),
'mac' => array('type' => 'varchar(17)', 'label' => 'MacAddress', 'enabled' => 1, 'visible' => 1, 'position' => 25), 'mac' => array('type' => 'varchar(17)', 'label' => 'MacAddress', 'enabled' => 1, 'visible' => 1, 'position' => 25),
'hostname' => array('type' => 'varchar(255)', 'label' => 'Hostname', 'enabled' => 1, 'visible' => 1, 'position' => 30), 'hostname' => array('type' => 'varchar(255)', 'label' => 'Hostname', 'enabled' => 1, 'visible' => 1, 'position' => 30),
'netbios_name' => array('type' => 'varchar(64)', 'label' => 'NetDiagNetbiosName', 'enabled' => 1, 'visible' => 1, 'position' => 31),
'mdns_name' => array('type' => 'varchar(255)', 'label' => 'NetDiagMdnsName', 'enabled' => 1, 'visible' => 1, 'position' => 32),
'mdns_services' => array('type' => 'varchar(512)', 'label' => 'NetDiagMdnsServices', 'enabled' => 1, 'visible' => 3, 'position' => 33),
'custom_name' => array('type' => 'varchar(255)', 'label' => 'NetDiagCustomName', 'enabled' => 1, 'visible' => 1, 'position' => 34),
'vendor' => array('type' => 'varchar(128)', 'label' => 'Vendor', 'enabled' => 1, 'visible' => 1, 'position' => 35), 'vendor' => array('type' => 'varchar(128)', 'label' => 'Vendor', 'enabled' => 1, 'visible' => 1, 'position' => 35),
'devicetype' => array('type' => 'varchar(64)', 'label' => 'DeviceType', 'enabled' => 1, 'visible' => 1, 'position' => 40), 'devicetype' => array('type' => 'varchar(64)', 'label' => 'DeviceType', 'enabled' => 1, 'visible' => 1, 'position' => 40),
'open_ports' => array('type' => 'varchar(255)', 'label' => 'NetDiagOpenPorts', 'enabled' => 1, 'visible' => 1, 'position' => 41),
'found_via' => array('type' => 'varchar(16)', 'label' => 'NetDiagFoundVia', 'enabled' => 1, 'visible' => 1, 'position' => 42),
'last_seen' => array('type' => 'datetime', 'label' => 'NetDiagLastSeen', 'enabled' => 1, 'visible' => 1, 'position' => 43),
'note' => array('type' => 'text', 'label' => 'Note', 'enabled' => 1, 'visible' => 3, 'position' => 50), 'note' => array('type' => 'text', 'label' => 'Note', 'enabled' => 1, 'visible' => 3, 'position' => 50),
'date_creation' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'position' => 500), 'date_creation' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'position' => 500),
'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => 0, 'notnull' => 0, 'position' => 501), 'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => 0, 'notnull' => 0, 'position' => 501),
@ -73,22 +66,8 @@ class NetDiagDevice extends CommonObject
public $ip; public $ip;
public $mac; public $mac;
public $hostname; public $hostname;
/** @var string NetBIOS-Name (UDP-137-Abfrage) */
public $netbios_name;
/** @var string mDNS-/Bonjour-Anzeigename */
public $mdns_name;
/** @var string angebotene mDNS-Dienste, kommagetrennt */
public $mdns_services;
/** @var string vom Techniker vergebener Name */
public $custom_name;
public $vendor; public $vendor;
public $devicetype; public $devicetype;
/** @var string offene Ports, kommagetrennt (z.B. "80,443,9100") */
public $open_ports;
/** @var string Fundweg: ping | port | arp | mdns */
public $found_via;
/** @var int|string Unix-Zeit/Datum der letzten Sichtung */
public $last_seen;
public $note; public $note;
public $date_creation; public $date_creation;
public $tms; public $tms;

View file

@ -60,17 +60,3 @@ NetDiagMeasureOk = OK
NetDiagMeasureWarn = Warnung NetDiagMeasureWarn = Warnung
NetDiagMeasureFail = Fehler NetDiagMeasureFail = Fehler
NetDiagMeasureUnmeasurable = Nicht messbar NetDiagMeasureUnmeasurable = Nicht messbar
# Gerätemerkmale aus dem IP-Scan (seit Phase 5 synchronisiert)
NetDiagVendor=Hersteller
NetDiagNetbiosName=NetBIOS-Name
NetDiagMdnsName=mDNS-Name
NetDiagMdnsServices=mDNS-Dienste
NetDiagCustomName=Eigener Name
NetDiagOpenPorts=Offene Ports
NetDiagFoundVia=Gefunden über
NetDiagFoundViaPing=Ping
NetDiagFoundViaPort=Port
NetDiagFoundViaArp=ARP
NetDiagFoundViaMdns=mDNS
NetDiagLastSeen=Zuletzt gesehen

View file

@ -60,17 +60,3 @@ NetDiagMeasureOk = OK
NetDiagMeasureWarn = Warning NetDiagMeasureWarn = Warning
NetDiagMeasureFail = Fail NetDiagMeasureFail = Fail
NetDiagMeasureUnmeasurable = Not measurable NetDiagMeasureUnmeasurable = Not measurable
# Device details from the IP scan (synchronised since phase 5)
NetDiagVendor=Manufacturer
NetDiagNetbiosName=NetBIOS name
NetDiagMdnsName=mDNS name
NetDiagMdnsServices=mDNS services
NetDiagCustomName=Custom name
NetDiagOpenPorts=Open ports
NetDiagFoundVia=Found via
NetDiagFoundViaPing=Ping
NetDiagFoundViaPort=Port
NetDiagFoundViaArp=ARP
NetDiagFoundViaMdns=mDNS
NetDiagLastSeen=Last seen

View file

@ -289,144 +289,3 @@ function netdiagFormatWifiKanal($data)
$out .= '</div>'; $out .= '</div>';
return $out; return $out;
} }
/**
* Klartext-Bezeichnung und Einheit für einen Ergebnis-Schlüssel.
*
* Vorher wurde der rohe JSON-Schlüssel nur großgeschrieben ausgegeben das
* Kundendokument las sich wie ein Datenbank-Auszug: „VerlustProzent: 0 |
* MinMs: 4.4 | ArpAvailable: nein | MdnsOk: ja". Kennzahlen standen ohne
* Einheit da („UptimeSek: 8123456"), und interne Messwerkzeug-Details, die den
* Kunden nichts angehen, standen mitten im Dokument.
*
* Bewusst eine WHITELIST: was hier nicht steht, erscheint nicht im
* Kundendokument. Ein neues Werkzeug muss also einmalig hier eingetragen
* werden dafür kann nie versehentlich ein internes Feld durchrutschen.
* Die Technikeransicht in Dolibarr zeigt weiterhin alle Felder.
*
* @return array<string,array{0:string,1:string}> Schlüssel => [Bezeichnung, Einheit]
*/
function netdiagKundenfelder()
{
return array(
// gemeinsam
'host' => array('Ziel', ''),
'subnet' => array('Netzbereich', ''),
'count' => array('Gefundene Geräte', ''),
// Ping / Laufzeit
'gesendet' => array('Gesendet', 'Pakete'),
'empfangen' => array('Empfangen', 'Pakete'),
'verlustProzent' => array('Paketverlust', '%'),
'minMs' => array('Kürzeste Antwortzeit', 'ms'),
'avgMs' => array('Mittlere Antwortzeit', 'ms'),
'medianMs' => array('Typische Antwortzeit', 'ms'),
'maxMs' => array('Längste Antwortzeit', 'ms'),
'p95Ms' => array('Antwortzeit (95 %)', 'ms'),
'jitterMs' => array('Schwankung', 'ms'),
'verfahren' => array('Messverfahren', ''),
// Durchsatz
'downMbps' => array('Download', 'Mbit/s'),
'upMbps' => array('Upload', 'Mbit/s'),
'mbitProSekunde' => array('Durchsatz', 'Mbit/s'),
// Dauertest
'dauerSekunden' => array('Messdauer', 's'),
'intervallSek' => array('Messabstand', 's'),
'laengsterAusfallSek' => array('Längster Ausfall', 's'),
// Netz/DHCP
'server' => array('DHCP-Server', ''),
'lease' => array('Lease-Dauer', 's'),
'gateway' => array('Gateway', ''),
'dns' => array('DNS-Server', ''),
// SNMP / Switch
'sysDescr' => array('Gerätebeschreibung', ''),
'uptimeSek' => array('Betriebszeit', 's'),
// Traceroute
'reachedTarget' => array('Ziel erreicht', ''),
// WLAN
'anzahlNetze' => array('Sichtbare WLAN-Netze', ''),
// Fehler/Hinweise — immer zeigen, sonst bliebe eine gelbe oder rote Ampel
// im Kundendokument ohne jede Erklärung stehen. Der Text läuft durch
// netdiagKundentext() und wird dort von Messgeräte-Interna befreit.
'fehler' => array('Fehler', ''),
'hinweis' => array('Hinweis', ''),
'kundenhinweis' => array('Hinweis', ''),
);
}
/**
* Sekundenwert lesbar machen (aus „8123456" wird „94 Tage 0 Std").
*
* @param int|float $sek Sekunden
* @return string lesbare Dauer
*/
function netdiagDauerLesbar($sek)
{
$sek = (int) $sek;
if ($sek < 60) {
return $sek.' s';
}
if ($sek < 3600) {
return round($sek / 60).' min';
}
if ($sek < 86400) {
return floor($sek / 3600).' Std '.round(($sek % 3600) / 60).' min';
}
return floor($sek / 86400).' Tage '.round(($sek % 86400) / 3600).' Std';
}
/**
* Ein Ergebnisfeld für das Kundendokument aufbereiten.
*
* @param string $key Schlüssel aus dem Ergebnis-JSON
* @param mixed $val Wert
* @return string|null „Bezeichnung: Wert Einheit" oder null, wenn das Feld
* nicht ins Kundendokument gehört
*/
function netdiagFeldFuerKunde($key, $val)
{
$felder = netdiagKundenfelder();
if (!isset($felder[$key])) {
return null; // internes Feld — bewusst nicht im Kundendokument
}
list($label, $einheit) = $felder[$key];
if (is_array($val)) {
$flat = array();
foreach ($val as $item) {
$flat[] = is_array($item) ? json_encode($item, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : (string) $item;
}
$val = implode(', ', $flat);
} elseif (is_bool($val)) {
$val = $val ? 'ja' : 'nein';
} elseif ($val === null || $val === '') {
return null; // nicht ermittelt — lieber weglassen als „: " drucken
}
// Sekundenwerte lesbar machen statt sechsstellige Zahlen zu drucken
if ($einheit === 's' && is_numeric($val) && $val >= 3600) {
return $label.': '.netdiagDauerLesbar($val);
}
$text = netdiagKundentext((string) $val);
return $label.': '.$text.($einheit !== '' ? ' '.$einheit : '');
}
/**
* Interne Formulierungen aus Alt-Datensätzen für das Kundendokument
* entschärfen.
*
* Beispiel aus einem echten Protokoll: „ARP-Tabelle nicht lesbar
* (/proc/net/arp) braucht Root". Das ist eine Aussage über das Messgerät,
* nicht über das Kundennetz, und gehört nicht ins Abnahmedokument. Neue
* Messungen liefern gleich einen Kundentext; für die bereits gespeicherten
* Ergebnisse hilft nur ein Ersetzen beim Drucken.
*
* @param string $text Rohtext aus dem Ergebnis
* @return string kundentauglicher Text
*/
function netdiagKundentext($text)
{
if (stripos($text, '/proc/net/arp') !== false || stripos($text, 'braucht root') !== false) {
return 'Mit dem eingesetzten Messgerät nicht prüfbar — die Aussage bleibt offen.';
}
return $text;
}

View file

@ -106,38 +106,23 @@ function netdiagGeneratePdf($db, $protocol, $outputlangs)
// Geräteliste // Geräteliste
$pdf->SetFont('', 'B', 12); $pdf->SetFont('', 'B', 12);
$pdf->Cell(0, 7, $outputlangs->transnoentities("NetDiagDevices").' ('.count($devices).')', 0, 1, 'L'); $pdf->Cell(0, 7, $outputlangs->transnoentities("NetDiagDevices").' ('.count($devices).')', 0, 1, 'L');
/* $pdf->SetFillColor(0, 70, 130);
* Spaltenbreiten (Summe muss 180 mm bleiben, sonst passt die Leerzeile $pdf->SetTextColor(255, 255, 255);
* unten nicht mehr zum Kopf): Gerätetyp hatte 15 mm, der Text durfte aber $pdf->SetFont('', 'B', 8);
* 10 Zeichen lang sein „Chromecast/TV" lief dadurch über den rechten $cols = array(35, 40, 50, 40, 15);
* Tabellenrahmen HINAUS in den Druckrand (nachgemessen an ND2026-0015: $heads = array("IpAddress", "MacAddress", "Hostname", "Vendor", "DeviceType");
* 199,0 mm bei 195 mm Tabellenkante). Cell() schneidet nicht ab, es foreach ($heads as $idx => $h) {
* schreibt einfach weiter. MAC und Hostname sind in der Praxis oft leer $pdf->Cell($cols[$idx], 6, $outputlangs->transnoentities($h), 1, 0, 'L', true);
* (ARP ist ab Android 10 gesperrt), von dort kommt der Platz. }
*/ $pdf->Ln();
$cols = array(30, 34, 44, 28, 24, 20); $pdf->SetTextColor(0, 0, 0);
$heads = array("IpAddress", "MacAddress", "Hostname", "NetDiagVendor", "DeviceType", "NetDiagOpenPorts"); $pdf->SetFont('', '', 8);
netdiagPdfTableHead($pdf, $cols, $heads, $outputlangs);
foreach ($devices as $dev) { foreach ($devices as $dev) {
netdiagPdfEnsureSpace($pdf, 5, function ($p) use ($cols, $heads, $outputlangs) { $pdf->Cell($cols[0], 5, dol_trunc($dev->ip, 22), 1, 0, 'L');
netdiagPdfTableHead($p, $cols, $heads, $outputlangs); $pdf->Cell($cols[1], 5, dol_trunc($dev->mac, 24), 1, 0, 'L');
}); $pdf->Cell($cols[2], 5, dol_trunc($dev->hostname, 32), 1, 0, 'L');
// Anzeigename wie in der App: eigener Name vor mDNS-/Host-/NetBIOS-Name. $pdf->Cell($cols[3], 5, dol_trunc($dev->vendor, 26), 1, 0, 'L');
// Ein Drucker ist per DNS oft namenlos, meldet sich per mDNS aber als $pdf->Cell($cols[4], 5, dol_trunc($dev->devicetype, 10), 1, 1, 'L');
// "Brother HL-L2350DW" — im Kundenprotokoll stand bisher nur die IP.
$anzeige = '';
foreach (array($dev->custom_name, $dev->mdns_name, $dev->hostname, $dev->netbios_name) as $kandidat) {
if (!empty($kandidat)) {
$anzeige = $kandidat;
break;
}
}
$pdf->Cell($cols[0], 5, dol_trunc($dev->ip, 18), 1, 0, 'L');
$pdf->Cell($cols[1], 5, dol_trunc($dev->mac, 20), 1, 0, 'L');
$pdf->Cell($cols[2], 5, dol_trunc($anzeige, 26), 1, 0, 'L');
$pdf->Cell($cols[3], 5, dol_trunc($dev->vendor, 16), 1, 0, 'L');
$pdf->Cell($cols[4], 5, dol_trunc($dev->devicetype, 14), 1, 0, 'L');
$pdf->Cell($cols[5], 5, dol_trunc((string) $dev->open_ports, 12), 1, 1, 'L');
} }
if (empty($devices)) { if (empty($devices)) {
$pdf->Cell(array_sum($cols), 5, '-', 1, 1, 'C'); $pdf->Cell(array_sum($cols), 5, '-', 1, 1, 'C');
@ -174,12 +159,6 @@ function netdiagGeneratePdf($db, $protocol, $outputlangs)
} else { } else {
$pdf->SetFillColor(170, 215, 170); $pdf->SetFillColor(170, 215, 170);
} }
// Titelzeile und Ergebnis zusammenhalten: sonst steht am Seitenende die
// Überschrift samt Ampel allein und darunter ein leerer, unten offener
// Rahmen — das Ergebnis beginnt erst auf der nächsten Seite. Bei einem
// Testlauf über 61 Umbruchlagen trat das in 5 Fällen auf und liest sich
// wie ein abgebrochenes Dokument.
netdiagPdfEnsureSpace($pdf, 11);
$pdf->SetFont('', 'B', 9); $pdf->SetFont('', 'B', 9);
$title = ($m->category ? '['.$m->category.'] ' : '').$m->tool.($m->label ? ' — '.$m->label : ''); $title = ($m->category ? '['.$m->category.'] ' : '').$m->tool.($m->label ? ' — '.$m->label : '');
$pdf->Cell(150, 6, dol_trunc($title, 80), 1, 0, 'L'); $pdf->Cell(150, 6, dol_trunc($title, 80), 1, 0, 'L');
@ -224,68 +203,22 @@ function netdiagPdfFlattenResult($json)
} }
$data = json_decode($json, true); $data = json_decode($json, true);
if (!is_array($data)) { if (!is_array($data)) {
return netdiagKundentext((string) $json); return (string) $json;
} }
$parts = array(); $parts = array();
foreach ($data as $key => $val) { foreach ($data as $key => $val) {
$feld = netdiagFeldFuerKunde((string) $key, $val); if (is_array($val)) {
if ($feld !== null) { $flat = array();
$parts[] = $feld; foreach ($val as $item) {
$flat[] = is_array($item) ? json_encode($item, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : (string) $item;
}
$val = implode(', ', $flat);
} elseif (is_bool($val)) {
$val = $val ? 'ja' : 'nein';
} }
$parts[] = ucfirst((string) $key).': '.$val;
} }
return empty($parts) ? '-' : implode(' | ', $parts); return implode(' | ', $parts);
}
/**
* Sorgt dafür, dass die nächsten `$hoeheMm` Millimeter noch auf die aktuelle
* Seite passen sonst wird vorher umgebrochen und optional eine
* Wiederholungszeile (z.B. der Tabellenkopf) ausgegeben.
*
* Der automatische Seitenumbruch von TCPDF reicht dafür nicht: er greift erst,
* wenn die Zelle schon geschrieben wird, und zerreißt dabei zusammengehörige
* Blöcke (Tabellenkopf/Datenzeilen, Messungstitel/Messergebnis).
*
* @param TCPDF $pdf PDF-Objekt
* @param float $hoeheMm benötigte Resthöhe in mm
* @param callable|null $nachUmbruch wird nach einem Seitenwechsel aufgerufen
* @return bool true, wenn umgebrochen wurde
*/
function netdiagPdfEnsureSpace($pdf, $hoeheMm, $nachUmbruch = null)
{
$rest = ($pdf->getPageHeight() - $pdf->getBreakMargin()) - $pdf->GetY();
if ($rest >= $hoeheMm) {
return false;
}
$pdf->AddPage();
if (is_callable($nachUmbruch)) {
$nachUmbruch($pdf);
}
return true;
}
/**
* Kopfzeile einer Tabelle ausgeben (blau hinterlegt) und danach Schrift/Farbe
* für die Datenzeilen wiederherstellen. Als eigene Funktion, damit dieselbe
* Zeile nach einem Seitenumbruch wiederholt werden kann vorher standen die
* Spalten ab Seite 2 ohne jede Beschriftung da.
*
* @param TCPDF $pdf PDF-Objekt
* @param array<int,int> $cols Spaltenbreiten in mm
* @param array<int,string> $heads Sprachschlüssel der Überschriften
* @param Translate $outputlangs Sprache
* @return void
*/
function netdiagPdfTableHead($pdf, $cols, $heads, $outputlangs)
{
$pdf->SetFillColor(0, 70, 130);
$pdf->SetTextColor(255, 255, 255);
$pdf->SetFont('', 'B', 8);
foreach ($heads as $idx => $h) {
$pdf->Cell($cols[$idx], 6, $outputlangs->transnoentities($h), 1, 0, 'L', true);
}
$pdf->Ln();
$pdf->SetTextColor(0, 0, 0);
$pdf->SetFont('', '', 8);
} }
/** /**

View file

@ -182,49 +182,19 @@ print load_fiche_titre($langs->trans("NetDiagDevices").' ('.count($devices).')',
print '<div class="div-table-responsive-no-min"><table class="noborder centpercent">'; print '<div class="div-table-responsive-no-min"><table class="noborder centpercent">';
print '<tr class="liste_titre">'; print '<tr class="liste_titre">';
print '<th>'.$langs->trans("IpAddress").'</th><th>'.$langs->trans("MacAddress").'</th>'; print '<th>'.$langs->trans("IpAddress").'</th><th>'.$langs->trans("MacAddress").'</th>';
print '<th>'.$langs->trans("Hostname").'</th><th>'.$langs->trans("NetDiagVendor").'</th><th>'.$langs->trans("DeviceType").'</th>'; print '<th>'.$langs->trans("Hostname").'</th><th>'.$langs->trans("Vendor").'</th><th>'.$langs->trans("DeviceType").'</th>';
print '<th>'.$langs->trans("NetDiagOpenPorts").'</th><th>'.$langs->trans("NetDiagFoundVia").'</th>';
print '</tr>'; print '</tr>';
// Fundweg als Klartext — "ping"/"port"/"arp"/"mdns" sagt sonst nur dem Entwickler etwas
$foundvialabels = array(
'ping' => $langs->trans("NetDiagFoundViaPing"),
'port' => $langs->trans("NetDiagFoundViaPort"),
'arp' => $langs->trans("NetDiagFoundViaArp"),
'mdns' => $langs->trans("NetDiagFoundViaMdns"),
);
foreach ($devices as $dev) { foreach ($devices as $dev) {
// Anzeigename: eigener Name vor mDNS-/Host-/NetBIOS-Name (wie in der App)
$anzeige = '';
foreach (array($dev->custom_name, $dev->mdns_name, $dev->hostname, $dev->netbios_name) as $kandidat) {
if (!empty($kandidat)) {
$anzeige = $kandidat;
break;
}
}
print '<tr class="oddeven">'; print '<tr class="oddeven">';
print '<td>'.dol_escape_htmltag($dev->ip).'</td>'; print '<td>'.dol_escape_htmltag($dev->ip).'</td>';
print '<td>'.dol_escape_htmltag($dev->mac).'</td>'; print '<td>'.dol_escape_htmltag($dev->mac).'</td>';
print '<td>'.dol_escape_htmltag($anzeige); print '<td>'.dol_escape_htmltag($dev->hostname).'</td>';
// Weitere Bezeichner nur zeigen, wenn sie vom Anzeigenamen abweichen
$weitere = array();
if (!empty($dev->netbios_name) && $dev->netbios_name !== $anzeige) {
$weitere[] = 'NB: '.$dev->netbios_name;
}
if (!empty($dev->mdns_name) && $dev->mdns_name !== $anzeige) {
$weitere[] = 'mDNS: '.$dev->mdns_name;
}
if (!empty($weitere)) {
print '<br><span class="opacitymedium small">'.dol_escape_htmltag(implode(' · ', $weitere)).'</span>';
}
print '</td>';
print '<td>'.dol_escape_htmltag($dev->vendor).'</td>'; print '<td>'.dol_escape_htmltag($dev->vendor).'</td>';
print '<td>'.dol_escape_htmltag($dev->devicetype).'</td>'; print '<td>'.dol_escape_htmltag($dev->devicetype).'</td>';
print '<td>'.dol_escape_htmltag($dev->open_ports).'</td>';
print '<td>'.dol_escape_htmltag($foundvialabels[$dev->found_via] ?? $dev->found_via).'</td>';
print '</tr>'; print '</tr>';
} }
if (empty($devices)) { if (empty($devices)) {
print '<tr><td colspan="7" class="opacitymedium center">-</td></tr>'; print '<tr><td colspan="5" class="opacitymedium center">-</td></tr>';
} }
print '</table></div>'; print '</table></div>';

View file

@ -14,21 +14,8 @@ CREATE TABLE llx_netdiag_device(
ip varchar(45), ip varchar(45),
mac varchar(17), mac varchar(17),
hostname varchar(255), hostname varchar(255),
-- Weitere Bezeichner, unter denen sich ein Gerät meldet. Ein Drucker ist
-- per DNS oft namenlos, meldet sich aber per mDNS als "Brother HL-L2350DW".
netbios_name varchar(64),
mdns_name varchar(255),
mdns_services varchar(512),
-- vom Techniker vergebener Name; überschreibt in der Anzeige alles andere
custom_name varchar(255),
vendor varchar(128), vendor varchar(128),
devicetype varchar(64), devicetype varchar(64),
-- offene Ports als Komma-Liste (z.B. "80,443,9100") — die Portliste ist die
-- Begründung für die Geräteart und gehört deshalb ins Protokoll
open_ports varchar(255),
-- wie das Gerät gefunden wurde: ping | port | arp | mdns
found_via varchar(16),
last_seen datetime,
note text, note text,
date_creation datetime NOT NULL, date_creation datetime NOT NULL,
tms timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL tms timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL

View file

@ -1,26 +0,0 @@
-- 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.
--
-- Nachtrag zu llx_netdiag_device: Gerätemerkmale, die die App seit dem
-- IP-Scanner-Umbau ermittelt, die aber bisher nirgends ankamen.
--
-- Der Techniker sah in der App „Drucker HP, Port 9100" — im Kundenprotokoll
-- stand nur die IP. Die Felder fehlten dabei nicht etwa in der Übertragung,
-- sondern durchgängig: in dieser Tabelle, in NetDiagDevice::$fields, im
-- App-Payload und in der Anzeige. Diese Datei ergänzt die Tabelle.
--
-- Läuft bei jedem Modul-Update mit (DolibarrModules::_load_tables führt alle
-- llx_*.sql aus, alphabetisch — „.sql" vor „_v2.sql"). „IF NOT EXISTS" macht
-- den Lauf wiederholbar, ohne dass ein Update mit Fehler abbricht.
ALTER TABLE llx_netdiag_device ADD COLUMN IF NOT EXISTS netbios_name varchar(64) AFTER hostname;
ALTER TABLE llx_netdiag_device ADD COLUMN IF NOT EXISTS mdns_name varchar(255) AFTER netbios_name;
ALTER TABLE llx_netdiag_device ADD COLUMN IF NOT EXISTS mdns_services varchar(512) AFTER mdns_name;
ALTER TABLE llx_netdiag_device ADD COLUMN IF NOT EXISTS custom_name varchar(255) AFTER mdns_services;
ALTER TABLE llx_netdiag_device ADD COLUMN IF NOT EXISTS open_ports varchar(255) AFTER devicetype;
ALTER TABLE llx_netdiag_device ADD COLUMN IF NOT EXISTS found_via varchar(16) AFTER open_ports;
ALTER TABLE llx_netdiag_device ADD COLUMN IF NOT EXISTS last_seen datetime AFTER found_via;