Vorab: drei Punkte der Roadmap-Liste waren Fehlannahmen. Eine Analyse mit anschließender Gegenprüfung (jeder Befund musste einen Widerlegungsversuch überstehen) hat sie ausgeräumt, bevor Code geändert wurde: - "ab Seite 2 alles nach rechts verschoben" existiert nicht. Nachgemessen am Prod-PDF ND2026-0015 mit pdftotext -bbox: Seite 1 und Seite 2 beginnen beide bei 16,0 mm. Die echten Umbruchfehler waren andere. - measure_status validieren war seit Phase 1 erledigt. - Werkzeug-IDs / Teilnetz-Gruppierung / TCPDF-Fußzeile: verworfen, die vorgeschlagenen Änderungen hätten das PDF verschlechtert. PDF (alle Punkte am mehrseitigen Dokument nachgeprüft): - Tabellenkopf der Geräteliste wird auf Folgeseiten wiederholt. Vorher standen ab Seite 2 unbeschriftete Spalten — bei leeren MAC/Hostname-Feldern vier namenlose Spalten. - Messungs-Titelzeile und Ergebnis werden zusammengehalten. Vorher blieb die Überschrift samt Ampel am Seitenende allein zurück, darunter ein leerer, unten offener Rahmen; in einem Testlauf über 61 Umbruchlagen 5-mal (~8 %). - Spalte "Gerätetyp" hatte 15 mm, ließ aber 10 Zeichen zu — "Chromecast/TV" lief bis 199,0 mm bei 195 mm Tabellenkante über den Rahmen in den Druckrand. - Deutsche Bezeichnungen mit Einheiten statt roher JSON-Schlüssel: aus "VerlustProzent: 0 | MinMs: 4.4 | UptimeSek: 8123456" wird "Paketverlust: 0 % | Kürzeste Antwortzeit: 4.4 ms | Betriebszeit: 94 Tage 1 Std". Als Whitelist (netdiagKundenfelder(), gemeinsam für Karte und PDF) — interne Felder wie arpAvailable, mdnsOk, probed, answered fallen damit automatisch heraus. - "ARP-Tabelle nicht lesbar (/proc/net/arp) — braucht Root" wird beim Drucken zu einem kundentauglichen Satz. Altdaten stehen so in der DB, deshalb Ersetzung beim Drucken statt nur in der App. Gerätemerkmale (der eigentliche Roadmap-Punkt): Der Techniker sah in der App "Drucker HP, Port 9100", im Kundenprotokoll stand nur die IP. Die Felder fehlten dabei nicht in der Übertragung, sondern durchgängig — ein Fix allein in der API wäre folgenlos geblieben, weil Dolibarrs setSaveQuery() nur deklarierte $fields schreibt. Ergänzt über die ganze Kette: sql/llx_netdiag_device.sql + neue Migration llx_netdiag_device_v2.sql (ADD COLUMN IF NOT EXISTS, wiederholbar, läuft bei jedem Modul-Update), NetDiagDevice::$fields + Properties, api/protocols.php POST und GET, Kartenansicht und PDF. Neu: netbios_name, mdns_name, mdns_services, custom_name, open_ports, found_via, last_seen. Im PDF steht jetzt statt "192.168.178.20" die Zeile "Brother HL-L2350DW · Brother · Drucker · 80,443,9100" — der Name kommt aus mDNS, obwohl der Hostname leer ist. Sprachschlüssel Vendor -> NetDiagVendor: Die Gegenprüfung hielt den Punkt für falsch (Translate::load() ist first-wins, im CLI-Test kam "Hersteller"), im Browser stand in der Kartenansicht aber "Lieferant" — im HTTP-Kontext lädt Dolibarr vorher andere Sprachdateien als im CLI. Statt der Ursache nachzugehen jetzt ein eigener, kollisionsfreier Schlüssel; im Browser gegengeprüft. Nebenbei: doppeltes "OK OK" beim Status 0 im PDF. Gegen das Test-Dolibarr geprüft: Sync über die echte API (Login, POST, GET), Felder in der DB kontrolliert, Kartenansicht im Browser, mehrseitiges PDF gerendert und angesehen, Migration zweimal ausgeführt (idempotent).
432 lines
15 KiB
PHP
432 lines
15 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 htdocs/custom/netdiag/lib/netdiag.lib.php
|
|
* \ingroup netdiag
|
|
* \brief Hilfsfunktionen für das Modul NetDiag
|
|
*/
|
|
|
|
/**
|
|
* Tabs für die Admin-Seiten des Moduls vorbereiten
|
|
*
|
|
* @return array<int,array<int,string>> Tab-Array
|
|
*/
|
|
function netdiagAdminPrepareHead()
|
|
{
|
|
global $langs, $conf;
|
|
|
|
$langs->load("netdiag@netdiag");
|
|
|
|
$h = 0;
|
|
$head = array();
|
|
|
|
$head[$h][0] = dol_buildpath("/netdiag/admin/setup.php", 1);
|
|
$head[$h][1] = $langs->trans("Settings");
|
|
$head[$h][2] = 'settings';
|
|
$h++;
|
|
|
|
$head[$h][0] = dol_buildpath("/netdiag/admin/about.php", 1);
|
|
$head[$h][1] = $langs->trans("About");
|
|
$head[$h][2] = 'about';
|
|
$h++;
|
|
|
|
complete_head_from_modules($conf, $langs, null, $head, $h, 'netdiag@netdiag');
|
|
complete_head_from_modules($conf, $langs, null, $head, $h, 'netdiag@netdiag', 'remove');
|
|
|
|
return $head;
|
|
}
|
|
|
|
/**
|
|
* Tabs für die Detailansicht eines Diagnose-Protokolls vorbereiten
|
|
*
|
|
* @param NetDiagProtocol $object Protokoll-Objekt
|
|
* @return array<int,array<int,string>> Tab-Array
|
|
*/
|
|
function netdiagProtocolPrepareHead($object)
|
|
{
|
|
global $langs, $conf;
|
|
|
|
$langs->load("netdiag@netdiag");
|
|
|
|
$h = 0;
|
|
$head = array();
|
|
|
|
$head[$h][0] = dol_buildpath("/netdiag/netdiagprotocol_card.php", 1).'?id='.$object->id;
|
|
$head[$h][1] = $langs->trans("NetDiagProtocol");
|
|
$head[$h][2] = 'card';
|
|
$h++;
|
|
|
|
complete_head_from_modules($conf, $langs, $object, $head, $h, 'netdiagprotocol@netdiag');
|
|
complete_head_from_modules($conf, $langs, $object, $head, $h, 'netdiagprotocol@netdiag', 'remove');
|
|
|
|
return $head;
|
|
}
|
|
|
|
/**
|
|
* Ausgabeverzeichnis des Moduls ermitteln (für PDF-Dokumente)
|
|
*
|
|
* @return string Absoluter Pfad zum Dokumentenverzeichnis
|
|
*/
|
|
function netdiagGetOutputDir()
|
|
{
|
|
global $conf;
|
|
if (!empty($conf->netdiag->dir_output)) {
|
|
return $conf->netdiag->dir_output;
|
|
}
|
|
return DOL_DATA_ROOT.'/netdiag';
|
|
}
|
|
|
|
/**
|
|
* Ein Mess-Ergebnis (JSON) lesbar als HTML aufbereiten.
|
|
*
|
|
* Generisch: rendert flache Schlüssel/Wert-Paare und einfache Listen,
|
|
* damit auch künftige Tools ohne Code-Änderung dargestellt werden. Jeder Wert
|
|
* wird auf 200 Zeichen gekürzt (`dol_trunc`) — für Skalare unschädlich, aber
|
|
* ein Dauertest mit vielen Ausfällen/Minuten-Buckets würde dabei mitten im
|
|
* Satz abgeschnitten. Für `$tool === 'stresstest'` deshalb eigener, nicht
|
|
* gekürzter Zweig (siehe netdiagFormatStressTest) — dieselbe Notlösung wie
|
|
* netdiagPdfStressTest() im PDF-Generator.
|
|
*
|
|
* @param string $json JSON-String des Ergebnisses
|
|
* @param string $tool Werkzeug-ID der Messung (optional, für Sonderfälle)
|
|
* @return string HTML-Schnipsel
|
|
*/
|
|
function netdiagFormatResult($json, $tool = '')
|
|
{
|
|
if (empty($json)) {
|
|
return '<span class="opacitymedium">-</span>';
|
|
}
|
|
$data = json_decode($json, true);
|
|
if ($data === null) {
|
|
return dol_escape_htmltag(dol_trunc($json, 120));
|
|
}
|
|
if (!is_array($data)) {
|
|
return dol_escape_htmltag((string) $data);
|
|
}
|
|
if ($tool === 'stresstest') {
|
|
return netdiagFormatStressTest($data);
|
|
}
|
|
if ($tool === 'wifikanal') {
|
|
return netdiagFormatWifiKanal($data);
|
|
}
|
|
|
|
$out = '<div class="netdiag-result">';
|
|
foreach ($data as $key => $val) {
|
|
$label = dol_escape_htmltag(ucfirst((string) $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;
|
|
}
|
|
$valstr = implode(', ', $flat);
|
|
} elseif (is_bool($val)) {
|
|
$valstr = $val ? 'ja' : 'nein';
|
|
} else {
|
|
$valstr = (string) $val;
|
|
}
|
|
$out .= '<span class="netdiag-kv"><strong>'.$label.':</strong> '.dol_escape_htmltag(dol_trunc($valstr, 200)).'</span> ';
|
|
}
|
|
$out .= '</div>';
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* Dauer-/Stresstest-Ergebnis strukturiert als HTML aufbereiten (Kennzahlen +
|
|
* Ausfallliste, ungekürzt) statt es wie ein generisches Werkzeug zu
|
|
* behandeln — sonst schneidet dol_trunc(...,200) die Ausfallliste eines
|
|
* längeren Laufs mitten im Satz ab.
|
|
*
|
|
* @param array<string,mixed> $data bereits dekodiertes result-JSON
|
|
* @return string HTML-Schnipsel
|
|
*/
|
|
function netdiagFormatStressTest($data)
|
|
{
|
|
if (!empty($data['hinweis']) || !empty($data['fehler'])) {
|
|
return '<span class="opacitymedium">'.dol_escape_htmltag((string) ($data['hinweis'] ?? $data['fehler'])).'</span>';
|
|
}
|
|
|
|
$sum = array();
|
|
if (isset($data['host'])) {
|
|
$sum[] = 'Ziel: '.$data['host'];
|
|
}
|
|
if (isset($data['dauerSekunden'])) {
|
|
$sum[] = 'Dauer: '.round($data['dauerSekunden'] / 60).' min';
|
|
}
|
|
if (isset($data['intervallSek'])) {
|
|
$sum[] = 'Takt: '.$data['intervallSek'].' s';
|
|
}
|
|
if (isset($data['gesendet']) && isset($data['empfangen'])) {
|
|
$sum[] = 'Proben: '.$data['empfangen'].'/'.$data['gesendet'];
|
|
}
|
|
if (isset($data['verlustProzent'])) {
|
|
$sum[] = 'Verlust: '.$data['verlustProzent'].' %';
|
|
}
|
|
if (isset($data['avgMs']) && $data['avgMs'] !== null) {
|
|
$sum[] = 'Ø '.$data['avgMs'].' ms';
|
|
}
|
|
if (isset($data['minMs']) && isset($data['maxMs']) && $data['minMs'] !== null) {
|
|
$sum[] = 'Min/Max '.$data['minMs'].'/'.$data['maxMs'].' ms';
|
|
}
|
|
if (isset($data['p95Ms']) && $data['p95Ms'] !== null) {
|
|
$sum[] = 'p95 '.$data['p95Ms'].' ms';
|
|
}
|
|
if (isset($data['laengsterAusfallSek']) && $data['laengsterAusfallSek'] !== null) {
|
|
$sum[] = 'längster Ausfall '.$data['laengsterAusfallSek'].' s';
|
|
}
|
|
|
|
$out = '<div class="netdiag-result"><div class="netdiag-kv">'.dol_escape_htmltag(implode(' | ', $sum)).'</div>';
|
|
|
|
$ausfaelle = (isset($data['ausfaelle']) && is_array($data['ausfaelle'])) ? $data['ausfaelle'] : array();
|
|
if (empty($ausfaelle)) {
|
|
$out .= '<div class="opacitymedium">Kein Ausfall während des Laufs.</div>';
|
|
} else {
|
|
$out .= '<div><strong>Ausfälle ('.count($ausfaelle).'):</strong></div><ul class="netdiag-outages">';
|
|
foreach ($ausfaelle as $line) {
|
|
$out .= '<li>'.dol_escape_htmltag((string) $line).'</li>';
|
|
}
|
|
$out .= '</ul>';
|
|
}
|
|
$out .= '</div>';
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* WLAN-Kanal-Momentaufnahme strukturiert als HTML aufbereiten.
|
|
*
|
|
* Wie beim Dauertest ein eigener Zweig statt der generischen Darstellung:
|
|
* `netze` ist eine Liste von Objekten und würde dort als JSON-Text mit
|
|
* dol_trunc(…,200) mitten im Satz abgeschnitten.
|
|
*
|
|
* @param array<string,mixed> $data bereits dekodiertes result-JSON
|
|
* @return string HTML-Schnipsel
|
|
*/
|
|
function netdiagFormatWifiKanal($data)
|
|
{
|
|
$out = '<div class="netdiag-result">';
|
|
|
|
// Aus dem Demomodus der App: erfundene Netze zum Ansehen/Vorführen. Muss
|
|
// deutlich sichtbar bleiben, sonst wird eine Demo später für eine echte
|
|
// Messung am Kundenstandort gehalten.
|
|
if (!empty($data['demodaten'])) {
|
|
$out .= '<div class="netdiag-kv"><strong style="color:#b45309">'
|
|
.dol_escape_htmltag('DEMODATEN — keine echte Messung').'</strong></div>';
|
|
}
|
|
|
|
$sum = array();
|
|
if (isset($data['anzahlNetze'])) {
|
|
$sum[] = $data['anzahlNetze'].' Netze sichtbar';
|
|
}
|
|
if (!empty($data['eigenesNetz']) && is_array($data['eigenesNetz'])) {
|
|
$e = $data['eigenesNetz'];
|
|
$teil = 'Eigenes Netz: '.($e['ssid'] ?? '?').' — Kanal '.($e['kanal'] ?? '?');
|
|
if (!empty($e['band'])) {
|
|
$teil .= ' ('.$e['band'].')';
|
|
}
|
|
if (isset($e['rssi'])) {
|
|
$teil .= ', '.$e['rssi'].' dBm';
|
|
}
|
|
if (!empty($e['bewertung'])) {
|
|
$teil .= ' — '.$e['bewertung'];
|
|
}
|
|
$sum[] = $teil;
|
|
}
|
|
if (!empty($data['empfehlung24GHz']) && is_array($data['empfehlung24GHz'])) {
|
|
$best = reset($data['empfehlung24GHz']);
|
|
if (is_array($best) && isset($best['kanal'])) {
|
|
$sum[] = 'Störungsärmster 2,4-GHz-Kanal: '.$best['kanal'];
|
|
}
|
|
}
|
|
if (!empty($sum)) {
|
|
$out .= '<div class="netdiag-kv">'.dol_escape_htmltag(implode(' | ', $sum)).'</div>';
|
|
}
|
|
|
|
if (!empty($data['warnungen']) && is_array($data['warnungen'])) {
|
|
$out .= '<div><strong>Hinweise:</strong></div><ul class="netdiag-outages">';
|
|
foreach ($data['warnungen'] as $w) {
|
|
$out .= '<li>'.dol_escape_htmltag((string) $w).'</li>';
|
|
}
|
|
$out .= '</ul>';
|
|
}
|
|
|
|
$netze = (isset($data['netze']) && is_array($data['netze'])) ? $data['netze'] : array();
|
|
if (!empty($netze)) {
|
|
$out .= '<table class="noborder centpercent"><tr class="liste_titre">';
|
|
$out .= '<th>SSID</th><th class="center">Kanal</th><th>Band</th>';
|
|
$out .= '<th class="center">Breite</th><th>Standard</th><th>Sicherheit</th><th class="right">Pegel</th>';
|
|
$out .= '</tr>';
|
|
foreach ($netze as $n) {
|
|
if (!is_array($n)) {
|
|
continue;
|
|
}
|
|
$out .= '<tr class="oddeven">';
|
|
$out .= '<td>'.dol_escape_htmltag((string) ($n['ssid'] ?? '')).'</td>';
|
|
$out .= '<td class="center">'.dol_escape_htmltag((string) ($n['kanal'] ?? '')).'</td>';
|
|
$out .= '<td>'.dol_escape_htmltag((string) ($n['band'] ?? '')).'</td>';
|
|
$out .= '<td class="center">'.(isset($n['breiteMhz']) && $n['breiteMhz'] !== null ? dol_escape_htmltag($n['breiteMhz'].' MHz') : '-').'</td>';
|
|
$out .= '<td>'.dol_escape_htmltag((string) ($n['standard'] ?? '-')).'</td>';
|
|
$out .= '<td>'.dol_escape_htmltag((string) ($n['sicherheit'] ?? '-')).'</td>';
|
|
$out .= '<td class="right">'.dol_escape_htmltag((string) ($n['rssi'] ?? '')).' dBm</td>';
|
|
$out .= '</tr>';
|
|
}
|
|
$out .= '</table>';
|
|
}
|
|
|
|
$out .= '</div>';
|
|
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;
|
|
}
|