- 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.
252 lines
10 KiB
PHP
252 lines
10 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/api/protocols.php
|
|
* \ingroup netdiag
|
|
* \brief API-Endpunkt: Protokolle lesen (GET ?id=) und synchronisieren
|
|
* (POST action=sync). Der Sync ist idempotent über client_uuid.
|
|
*/
|
|
|
|
require_once __DIR__.'/netdiag_api.lib.php';
|
|
|
|
netdiag_api_bootstrap();
|
|
|
|
/**
|
|
* @var Conf $conf
|
|
* @var DoliDB $db
|
|
* @var Translate $langs
|
|
*/
|
|
|
|
$user = netdiag_api_authenticate($db);
|
|
|
|
require_once __DIR__.'/../class/netdiagprotocol.class.php';
|
|
require_once __DIR__.'/../class/netdiagdevice.class.php';
|
|
require_once __DIR__.'/../class/netdiagmeasurement.class.php';
|
|
|
|
// =========================================================================
|
|
// GET: einzelnes Protokoll mit Geräten und Messungen
|
|
// =========================================================================
|
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
|
$id = isset($_GET['id']) ? (int) $_GET['id'] : 0;
|
|
if ($id <= 0) {
|
|
netdiag_api_error('Parameter id fehlt', 400);
|
|
}
|
|
$protocol = new NetDiagProtocol($db);
|
|
if ($protocol->fetch($id) <= 0) {
|
|
netdiag_api_error('Protokoll nicht gefunden', 404);
|
|
}
|
|
|
|
$devObj = new NetDiagDevice($db);
|
|
$devices = array();
|
|
foreach ($devObj->fetchAllByProtocol($protocol->id) as $d) {
|
|
$devices[] = array(
|
|
'id' => (int) $d->id,
|
|
'ip' => $d->ip,
|
|
'mac' => $d->mac,
|
|
'hostname' => $d->hostname,
|
|
'netbiosName' => $d->netbios_name,
|
|
'mdnsName' => $d->mdns_name,
|
|
'mdnsServices' => netdiag_split_list($d->mdns_services),
|
|
'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);
|
|
$measurements = array();
|
|
foreach ($measObj->fetchAllByProtocol($protocol->id) as $m) {
|
|
$measurements[] = array(
|
|
'id' => (int) $m->id,
|
|
'deviceId' => $m->fk_device ? (int) $m->fk_device : null,
|
|
'tool' => $m->tool,
|
|
'category' => $m->category,
|
|
'label' => $m->label,
|
|
'params' => $m->params ? json_decode($m->params, true) : null,
|
|
'result' => $m->result ? json_decode($m->result, true) : null,
|
|
'measureStatus' => (int) $m->measure_status,
|
|
'dateMeasure' => $db->jdate($m->date_measure),
|
|
);
|
|
}
|
|
|
|
netdiag_api_respond(array(
|
|
'protocol' => array(
|
|
'id' => (int) $protocol->id,
|
|
'ref' => $protocol->ref,
|
|
'label' => $protocol->label,
|
|
'clientUuid' => $protocol->client_uuid,
|
|
'socId' => $protocol->fk_soc ? (int) $protocol->fk_soc : null,
|
|
'orderId' => $protocol->fk_commande ? (int) $protocol->fk_commande : null,
|
|
'dateDiag' => $db->jdate($protocol->date_diag),
|
|
'location' => $protocol->standort,
|
|
'subnet' => $protocol->subnet,
|
|
'status' => (int) $protocol->status,
|
|
'note' => $protocol->note,
|
|
),
|
|
'devices' => $devices,
|
|
'measurements' => $measurements,
|
|
));
|
|
}
|
|
|
|
// =========================================================================
|
|
// POST: Protokoll synchronisieren (anlegen oder aktualisieren)
|
|
// =========================================================================
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
netdiag_api_error('Methode nicht erlaubt', 405);
|
|
}
|
|
if (!$user->hasRight('netdiag', 'protocol', 'write')) {
|
|
netdiag_api_error('Keine Schreibberechtigung', 403);
|
|
}
|
|
|
|
$body = netdiag_api_read_body();
|
|
if (($body['action'] ?? '') !== 'sync' || empty($body['protocol']) || !is_array($body['protocol'])) {
|
|
netdiag_api_error('Erwartet: { action: "sync", protocol: {...} }', 400);
|
|
}
|
|
$p = $body['protocol'];
|
|
|
|
$uuid = isset($p['clientUuid']) ? trim((string) $p['clientUuid']) : '';
|
|
if ($uuid === '') {
|
|
netdiag_api_error('protocol.clientUuid erforderlich', 400);
|
|
}
|
|
|
|
$db->begin();
|
|
|
|
// Vorhandenes Protokoll über UUID suchen (idempotent)
|
|
$protocol = new NetDiagProtocol($db);
|
|
$exists = $protocol->fetchByClientUuid($uuid);
|
|
if ($exists < 0) {
|
|
$db->rollback();
|
|
netdiag_api_error('Datenbankfehler beim Laden', 500);
|
|
}
|
|
|
|
$protocol->client_uuid = $uuid;
|
|
$protocol->label = isset($p['label']) ? (string) $p['label'] : '';
|
|
$protocol->fk_soc = !empty($p['socId']) ? (int) $p['socId'] : null;
|
|
$protocol->fk_commande = !empty($p['orderId']) ? (int) $p['orderId'] : null;
|
|
$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'] : '';
|
|
// tms explizit setzen — die Spalte ist NOT NULL ohne brauchbaren NULL-Default
|
|
// (explicit_defaults_for_timestamp=1), createCommon würde sonst NULL einfügen.
|
|
$protocol->tms = dol_now();
|
|
|
|
if ($exists > 0) {
|
|
$result = $protocol->update($user, 1);
|
|
} else {
|
|
$result = $protocol->create($user, 1);
|
|
}
|
|
if ($result <= 0) {
|
|
$db->rollback();
|
|
netdiag_api_error('Protokoll speichern fehlgeschlagen: '.$protocol->errorsToString(), 500);
|
|
}
|
|
$protocolId = (int) $protocol->id;
|
|
|
|
// Alte Geräte und Messungen entfernen (Sync ersetzt komplett)
|
|
$db->query("DELETE FROM ".$db->prefix()."netdiag_measurement WHERE fk_protocol = ".$protocolId);
|
|
$db->query("DELETE FROM ".$db->prefix()."netdiag_device WHERE fk_protocol = ".$protocolId);
|
|
|
|
// Geräte einfügen, dabei clientId -> serverRowid merken
|
|
$deviceIdMap = array();
|
|
$devicesIn = (!empty($p['devices']) && is_array($p['devices'])) ? $p['devices'] : array();
|
|
foreach ($devicesIn as $d) {
|
|
$dev = new NetDiagDevice($db);
|
|
$dev->fk_protocol = $protocolId;
|
|
$dev->ip = isset($d['ip']) ? (string) $d['ip'] : '';
|
|
$dev->mac = isset($d['mac']) ? (string) $d['mac'] : '';
|
|
$dev->hostname = isset($d['hostname']) ? (string) $d['hostname'] : '';
|
|
$dev->vendor = isset($d['vendor']) ? (string) $d['vendor'] : '';
|
|
$dev->devicetype = isset($d['deviceType']) ? (string) $d['deviceType'] : '';
|
|
$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();
|
|
if ($dev->create($user, 1) <= 0) {
|
|
$db->rollback();
|
|
netdiag_api_error('Gerät speichern fehlgeschlagen: '.$dev->errorsToString(), 500);
|
|
}
|
|
if (isset($d['clientId'])) {
|
|
$deviceIdMap[(string) $d['clientId']] = (int) $dev->id;
|
|
}
|
|
}
|
|
|
|
// Messungen einfügen, deviceClientId auf serverRowid abbilden
|
|
$measIn = (!empty($p['measurements']) && is_array($p['measurements'])) ? $p['measurements'] : array();
|
|
foreach ($measIn as $m) {
|
|
$meas = new NetDiagMeasurement($db);
|
|
$meas->fk_protocol = $protocolId;
|
|
$dcid = isset($m['deviceClientId']) ? (string) $m['deviceClientId'] : '';
|
|
$meas->fk_device = ($dcid !== '' && isset($deviceIdMap[$dcid])) ? $deviceIdMap[$dcid] : null;
|
|
$meas->tool = isset($m['tool']) ? (string) $m['tool'] : 'unknown';
|
|
$meas->category = isset($m['category']) ? (string) $m['category'] : '';
|
|
$meas->label = isset($m['label']) ? (string) $m['label'] : '';
|
|
$meas->params = isset($m['params']) ? json_encode($m['params'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : null;
|
|
$meas->result = isset($m['result']) ? json_encode($m['result'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : null;
|
|
// Nur bekannte Bewertungen uebernehmen. Ein unbekannter Wert landete sonst
|
|
// als Array-Index in Karte und PDF und lief dort ins Leere.
|
|
$st = isset($m['measureStatus']) ? (int) $m['measureStatus'] : 0;
|
|
$meas->measure_status = ($st >= 0 && $st <= 3) ? $st : 1;
|
|
$meas->date_measure = netdiag_api_timestamp($m['dateMeasure'] ?? 0);
|
|
$meas->tms = dol_now();
|
|
if ($meas->create($user, 1) <= 0) {
|
|
$db->rollback();
|
|
netdiag_api_error('Messung speichern fehlgeschlagen: '.$meas->errorsToString(), 500);
|
|
}
|
|
}
|
|
|
|
$db->commit();
|
|
|
|
// PDF neu erzeugen, wenn das Protokoll abgeschlossen ist
|
|
$pdfgenerated = false;
|
|
if ($protocol->status == NetDiagProtocol::STATUS_DONE) {
|
|
require_once __DIR__.'/../lib/netdiag_pdf.lib.php';
|
|
$pdfgenerated = (netdiagGeneratePdf($db, $protocol, $langs) > 0);
|
|
}
|
|
|
|
netdiag_api_respond(array(
|
|
'ok' => true,
|
|
'protocolId' => $protocolId,
|
|
'ref' => $protocol->ref,
|
|
'created' => ($exists == 0),
|
|
'pdfGenerated' => $pdfgenerated,
|
|
));
|