commit c576726a26477f13782540545b70bd8cfb27af8f Author: Eduard Wisch Date: Tue May 19 12:12:11 2026 +0200 Initiales Commit — Dolibarr-Modul NetDiag [deploy] Netzwerk-Diagnose-Modul mit JSON-API für die NetDiag-App: - 3 Tabellen (protocol/device/measurement), generisches JSON-result - JSON-API: auth, customers, orders, protocols (idempotenter Sync), pdf - JWT-Auth (HS256), CORS für die Capacitor-App - Tabs an Thirdparty + Auftrag, Protokoll-Card, PDF-Generator - QR-Code zum App-Download in der Modul-Konfiguration - de_DE + en_US, Rechtesystem netdiag->protocol read/write/delete Co-Authored-By: Claude Opus 4.7 (1M context) diff --git a/.forgejo/workflows/deploy.yml b/.forgejo/workflows/deploy.yml new file mode 100644 index 0000000..b693d1f --- /dev/null +++ b/.forgejo/workflows/deploy.yml @@ -0,0 +1,70 @@ +name: Deploy netdiag + +on: + push: + tags: + - 'v*' + branches: + - main + +jobs: + deploy: + runs-on: docker + if: startsWith(github.ref, 'refs/tags/v') || contains(github.event.head_commit.message, '[deploy]') + steps: + - name: Notify Start + uses: https://git.data-it-solution.de/data/ntfy-action@main + with: + status: start + project: NetDiag + ntfy_auth: ${{ secrets.NTFY_AUTH }} + run_number: ${{ github.run_number }} + message: ${{ github.event.head_commit.message }} + + - name: Checkout + run: | + git clone --depth 1 --branch "${GITHUB_REF_NAME}" \ + "https://token:${{ secrets.GIT_TOKEN }}@git.data-it-solution.de/${GITHUB_REPOSITORY}.git" . + + - name: Deploy nach Dolibarr + run: | + DEPLOY_PATH="/mnt/appdata/firma/dolibarr-202509/modules/netdiag" + REF="${GITHUB_REF#refs/*/}" + + echo "Deploye ${REF} nach ${DEPLOY_PATH} ..." + + if [ -d "$DEPLOY_PATH" ]; then + find "$DEPLOY_PATH" -mindepth 1 -not -path '*/.git/*' -not -name '.git' -delete 2>/dev/null || true + else + mkdir -p "$DEPLOY_PATH" + fi + + rsync -a \ + --exclude='.git' \ + --exclude='.forgejo' \ + --exclude='.gitignore' \ + --exclude='CLAUDE.md' \ + --exclude='test/' \ + ./ "$DEPLOY_PATH/" + + echo "Deployment erfolgreich: ${REF} -> ${DEPLOY_PATH}" + ls -la "$DEPLOY_PATH/core/modules/" + + - name: Notify Success + if: success() + uses: https://git.data-it-solution.de/data/ntfy-action@main + with: + status: success + project: NetDiag + ntfy_auth: ${{ secrets.NTFY_AUTH }} + run_number: ${{ github.run_number }} + + - name: Notify Failure + if: failure() + uses: https://git.data-it-solution.de/data/ntfy-action@main + with: + status: failure + project: NetDiag + ntfy_auth: ${{ secrets.NTFY_AUTH }} + run_number: ${{ github.run_number }} + click_url: https://git.data-it-solution.de/${{ github.repository }}/actions diff --git a/ChangeLog.md b/ChangeLog.md new file mode 100644 index 0000000..9c59f8d --- /dev/null +++ b/ChangeLog.md @@ -0,0 +1,11 @@ +# Changelog NetDiag + +## 1.0.0 + +- Erstversion +- Datenmodell: Protokoll, Gerät, Messung +- JSON-API: auth, customers, orders, protocols (Sync), pdf +- Tab "Netzwerk-Diagnose" an Kunde und Auftrag +- PDF-Protokoll-Generator + ECM-Ablage +- Backend: Protokoll-Liste und Detailansicht +- Admin: Token-Einstellung, QR-Code zum App-Download diff --git a/README.md b/README.md new file mode 100644 index 0000000..b9566b6 --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# NetDiag — Netzwerk-Diagnose für Dolibarr + +Dolibarr-Modul für die Ablage von Netzwerk-Diagnose-Protokollen. Erfasst per +mobiler App (siehe Projekt `NetzwerkDiagnose/app`) gefundene Geräte, Ports und +Messungen und hängt die Protokolle dauerhaft an **Kunde** und **Auftrag**. + +## Funktionen + +- Datenmodell: Protokoll → Geräte → Messungen (`llx_netdiag_*`) +- Tab **Netzwerk-Diagnose** an Kunde (thirdparty) und Auftrag (commande) +- JSON-API unter `/custom/netdiag/api/` für die mobile App (JWT-Auth) +- PDF-Protokoll, wird im Dokumentenarchiv (ECM) abgelegt +- Rechtesystem: `netdiag → protocol → read/write/delete` +- Mehrsprachig (de_DE, en_US) +- QR-Code zum App-Download in der Modul-Einrichtung + +## Installation + +1. Verzeichnis `netdiag/` nach `htdocs/custom/` auf den Dolibarr-Server kopieren. +2. In Dolibarr: **Einrichtung → Module → NetDiag** aktivieren. +3. Beim Aktivieren werden die Tabellen `llx_netdiag_protocol`, + `llx_netdiag_device`, `llx_netdiag_measurement` angelegt und ein + JWT-Schlüssel erzeugt. +4. Benutzern das Recht **NetDiag → Protokolle lesen/schreiben** geben. + +## API-Endpunkte + +Alle unter `https:///custom/netdiag/api/`: + +| Endpunkt | Methode | Zweck | +|----------|---------|-------| +| `auth.php` | POST `{login,password}` | Anmeldung → `{token,expiresIn,user}` | +| `customers.php` | GET `?q=` / `?id=` | Kundensuche / Kundendetail | +| `orders.php` | GET `?open=1&q=` / `?id=` | Auftragsliste / Auftragsdetail | +| `protocols.php` | GET `?id=` | Protokoll mit Geräten + Messungen | +| `protocols.php` | POST `{action:"sync",protocol:{…}}` | Protokoll anlegen/aktualisieren (idempotent über `clientUuid`) | +| `pdf.php` | GET `?id=&jwt=` | Protokoll-PDF streamen | + +Authentifizierung per `Authorization: Bearer ` oder `?jwt=`. + +## Einrichtung + +**Einrichtung → Module → NetDiag → Einstellungen:** +- Token-Gültigkeit (Sekunden) +- App-Download-URL (APK) — wird als QR-Code angezeigt + +## Lizenz + +GPLv3 diff --git a/admin/about.php b/admin/about.php new file mode 100644 index 0000000..c0c55f8 --- /dev/null +++ b/admin/about.php @@ -0,0 +1,87 @@ + + * + * 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 . + */ + +/** + * \file netdiag/admin/about.php + * \ingroup netdiag + * \brief Info-Seite des Moduls NetDiag + */ + +// 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/lib/admin.lib.php"; +require_once '../lib/netdiag.lib.php'; + +/** + * @var DoliDB $db + * @var Translate $langs + * @var User $user + */ + +$langs->loadLangs(array("admin", "netdiag@netdiag")); + +if (!$user->admin) { + accessforbidden(); +} + +$title = $langs->trans("NetDiagAbout"); +llxHeader('', $title, '', '', 0, 0, '', '', '', 'mod-netdiag page-admin'); + +$linkback = ''.$langs->trans("BackToModuleList").''; +print load_fiche_titre($title, $linkback, 'title_setup'); + +$head = netdiagAdminPrepareHead(); +print dol_get_fiche_head($head, 'about', $langs->trans("ModuleNetDiagName"), -1, 'fa-network-wired'); + +print '
'; +print ''.$langs->trans("ModuleNetDiagName").' — '.$langs->trans("ModuleNetDiagDesc").'

'; +print 'Version: 1.0.0
'; +print 'Autor: Alles Watt läuft (Eduard Wisch)
'; +print 'Lizenz: GPLv3
'; +print '
'; + +print dol_get_fiche_end(); + +llxFooter(); +$db->close(); diff --git a/admin/setup.php b/admin/setup.php new file mode 100644 index 0000000..e3fc74c --- /dev/null +++ b/admin/setup.php @@ -0,0 +1,147 @@ + + * + * 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 . + */ + +/** + * \file netdiag/admin/setup.php + * \ingroup netdiag + * \brief Einrichtungsseite des Moduls NetDiag + */ + +// 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/lib/admin.lib.php"; +require_once '../lib/netdiag.lib.php'; + +/** + * @var Conf $conf + * @var DoliDB $db + * @var Translate $langs + * @var User $user + */ + +$langs->loadLangs(array("admin", "netdiag@netdiag")); + +if (!$user->admin) { + accessforbidden(); +} + +$action = GETPOST('action', 'aZ09'); + +// Konstanten speichern +if ($action == 'updateconst') { + $ttl = GETPOSTINT('NETDIAG_API_TOKEN_TTL'); + if ($ttl < 60) { + $ttl = 604800; + } + dolibarr_set_const($db, 'NETDIAG_API_TOKEN_TTL', $ttl, 'chaine', 0, '', $conf->entity); + dolibarr_set_const($db, 'NETDIAG_APK_URL', GETPOST('NETDIAG_APK_URL', 'alpha'), 'chaine', 0, '', $conf->entity); + setEventMessages($langs->trans("SetupSaved"), null, 'mesgs'); + header("Location: ".$_SERVER["PHP_SELF"]); + exit; +} + +/* + * Ansicht + */ + +$form = new Form($db); +$help_url = ''; +$title = $langs->trans("NetDiagSetup"); + +llxHeader('', $title, $help_url, '', 0, 0, '', '', '', 'mod-netdiag page-admin'); + +$linkback = ''.$langs->trans("BackToModuleList").''; +print load_fiche_titre($title, $linkback, 'title_setup'); + +$head = netdiagAdminPrepareHead(); +print dol_get_fiche_head($head, 'settings', $langs->trans("ModuleNetDiagName"), -1, 'fa-network-wired'); + +print '
'; +print ''; +print ''; + +print ''; +print ''; + +print ''; +print ''; + +print ''; +print ''; + +print '
'.$langs->trans("Parameter").''.$langs->trans("Value").'
'.$langs->trans("NETDIAG_API_TOKEN_TTL"); +print ' '.$form->textwithpicto('', $langs->trans("NETDIAG_API_TOKEN_TTLTooltip")).'
'.$langs->trans("NETDIAG_APK_URL"); +print ' '.$form->textwithpicto('', $langs->trans("NETDIAG_APK_URLTooltip")).'
'; + +print '
'; +print '
'; + +print '
'.$langs->trans("NetDiagApiSecretInfo").'
'; + +// QR-Code zum App-Download +$apkurl = getDolGlobalString('NETDIAG_APK_URL'); +if (!empty($apkurl)) { + print '
'; + print ''.$langs->trans("NetDiagAppDownload").'
'; + print '
'.$langs->trans("NetDiagAppDownloadHint").'
'; + + $qrfile = DOL_DOCUMENT_ROOT.'/includes/tecnickcom/tcpdf/tcpdf_barcodes_2d.php'; + if (file_exists($qrfile)) { + require_once $qrfile; + $barcode = new TCPDF2DBarcode($apkurl, 'QRCODE,M'); + // SVG-Ausgabe -> komplett lokal, kein externer Dienst + print '
'; + print $barcode->getBarcodeSVGcode(5, 5, 'black'); + print '
'; + } else { + print '
QR-Bibliothek (TCPDF) nicht gefunden.
'; + } + print ''; + print '
'; +} + +print dol_get_fiche_end(); + +llxFooter(); +$db->close(); diff --git a/api/auth.php b/api/auth.php new file mode 100644 index 0000000..189ee0d --- /dev/null +++ b/api/auth.php @@ -0,0 +1,91 @@ + + * + * 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 . + */ + +/** + * \file netdiag/api/auth.php + * \ingroup netdiag + * \brief API-Endpunkt: Anmeldung der mobilen App, liefert JWT. + * + * POST {login, password} -> {token, expiresIn, user} + */ + +require_once __DIR__.'/netdiag_api.lib.php'; + +netdiag_api_bootstrap(); + +/** @var DoliDB $db */ + +if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + netdiag_api_error('Nur POST erlaubt', 405); +} + +$body = netdiag_api_read_body(); +$login = isset($body['login']) ? trim((string) $body['login']) : ''; +$password = isset($body['password']) ? (string) $body['password'] : ''; + +if ($login === '' || $password === '') { + netdiag_api_error('Login und Passwort erforderlich', 400); +} + +require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php'; + +// Zugangsdaten gegen Dolibarr prüfen (Standard-Login-Backend) +$entitytocheck = (int) $conf->entity; +$authmode = (getDolGlobalString('MAIN_AUTHENTICATION_MODE') ? getDolGlobalString('MAIN_AUTHENTICATION_MODE') : 'dolibarr'); +$resultlogin = checkLoginPassEntity($login, $password, $entitytocheck, explode(',', $authmode)); + +if (empty($resultlogin)) { + // Kurze Verzögerung gegen Brute-Force + sleep(1); + netdiag_api_error('Login fehlgeschlagen', 401); +} + +$user = new User($db); +if ($user->fetch('', $resultlogin, '', 0, $entitytocheck) <= 0 || empty($user->id)) { + netdiag_api_error('Benutzer nicht gefunden', 401); +} +if (!empty($user->statut) && $user->statut == 0) { + netdiag_api_error('Benutzer deaktiviert', 403); +} +$user->loadRights(); + +if (!$user->hasRight('netdiag', 'protocol', 'read')) { + netdiag_api_error('Keine Berechtigung für NetDiag', 403); +} + +$ttl = (int) getDolGlobalString('NETDIAG_API_TOKEN_TTL', '604800'); +if ($ttl < 60) { + $ttl = 604800; +} + +$token = netdiag_jwt_encode(array( + 'sub' => (int) $user->id, + 'name' => $user->getFullName($langs), +), $ttl); + +netdiag_api_respond(array( + 'token' => $token, + 'expiresIn' => $ttl, + 'user' => array( + 'id' => (int) $user->id, + 'login' => $user->login, + 'name' => $user->getFullName($langs), + 'email' => $user->email, + 'canWrite' => (bool) $user->hasRight('netdiag', 'protocol', 'write'), + ), +)); diff --git a/api/customers.php b/api/customers.php new file mode 100644 index 0000000..16dc62b --- /dev/null +++ b/api/customers.php @@ -0,0 +1,119 @@ + + * + * 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 . + */ + +/** + * \file netdiag/api/customers.php + * \ingroup netdiag + * \brief API-Endpunkt: Kunden suchen (?q=) oder Kundendetail (?id=). + */ + +require_once __DIR__.'/netdiag_api.lib.php'; + +netdiag_api_bootstrap(); + +/** @var DoliDB $db */ + +$user = netdiag_api_authenticate($db); + +$id = isset($_GET['id']) ? (int) $_GET['id'] : 0; +$q = isset($_GET['q']) ? trim((string) $_GET['q']) : ''; +$limit = isset($_GET['limit']) ? min(200, max(1, (int) $_GET['limit'])) : 50; + +$prefix = $db->prefix(); + +// ---- Kundendetail ---- +if ($id > 0) { + $sql = "SELECT s.rowid, s.nom, s.code_client, s.address, s.zip, s.town, s.phone, s.email, s.tva_intra"; + $sql .= " FROM ".$prefix."societe as s"; + $sql .= " WHERE s.rowid = ".((int) $id); + $sql .= " AND s.entity IN (".getEntity('societe').")"; + $resql = $db->query($sql); + if (!$resql || !($obj = $db->fetch_object($resql))) { + netdiag_api_error('Kunde nicht gefunden', 404); + } + $customer = array( + 'id' => (int) $obj->rowid, + 'name' => $obj->nom, + 'code' => $obj->code_client, + 'address' => $obj->address, + 'zip' => $obj->zip, + 'town' => $obj->town, + 'phone' => $obj->phone, + 'email' => $obj->email, + 'vat' => $obj->tva_intra, + ); + + // Aufträge des Kunden + $orders = array(); + $sqlo = "SELECT rowid, ref, ref_client, date_commande, fk_statut FROM ".$prefix."commande"; + $sqlo .= " WHERE fk_soc = ".((int) $id)." AND entity IN (".getEntity('commande').")"; + $sqlo .= " ORDER BY date_commande DESC, rowid DESC"; + $reso = $db->query($sqlo); + if ($reso) { + while ($o = $db->fetch_object($reso)) { + $orders[] = array( + 'id' => (int) $o->rowid, + 'ref' => $o->ref, + 'refClient' => $o->ref_client, + 'date' => $db->jdate($o->date_commande), + 'status' => (int) $o->fk_statut, + 'open' => ((int) $o->fk_statut >= 0 && (int) $o->fk_statut < 3), + ); + } + } + + // Diagnose-Protokolle des Kunden + $protocols = netdiag_api_protocol_list($db, ' AND p.fk_soc = '.((int) $id)); + + netdiag_api_respond(array( + 'customer' => $customer, + 'orders' => $orders, + 'protocols' => $protocols, + )); +} + +// ---- Kundensuche ---- +$sql = "SELECT s.rowid, s.nom, s.code_client, s.zip, s.town, s.phone,"; +$sql .= " (SELECT COUNT(*) FROM ".$prefix."netdiag_protocol p WHERE p.fk_soc = s.rowid) as protocolcount"; +$sql .= " FROM ".$prefix."societe as s"; +$sql .= " WHERE s.entity IN (".getEntity('societe').")"; +$sql .= " AND s.client IN (1, 2, 3)"; +$sql .= " AND s.status = 1"; +if ($q !== '') { + $sql .= " AND (".natural_search(array('s.nom', 's.name_alias', 's.code_client', 's.town', 's.zip'), $q, 0, 1).")"; +} +$sql .= " ORDER BY s.nom ASC"; +$sql .= $db->plimit($limit, 0); + +$resql = $db->query($sql); +if (!$resql) { + netdiag_api_error('Datenbankfehler: '.$db->lasterror(), 500); +} +$customers = array(); +while ($obj = $db->fetch_object($resql)) { + $customers[] = array( + 'id' => (int) $obj->rowid, + 'name' => $obj->nom, + 'code' => $obj->code_client, + 'zip' => $obj->zip, + 'town' => $obj->town, + 'phone' => $obj->phone, + 'protocolCount' => (int) $obj->protocolcount, + ); +} + +netdiag_api_respond(array('customers' => $customers)); diff --git a/api/netdiag_api.lib.php b/api/netdiag_api.lib.php new file mode 100644 index 0000000..106acc0 --- /dev/null +++ b/api/netdiag_api.lib.php @@ -0,0 +1,330 @@ + + * + * 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 . + */ + +/** + * \file netdiag/api/netdiag_api.lib.php + * \ingroup netdiag + * \brief Gemeinsame Funktionen der JSON-API: Bootstrap, JWT, Antworten. + * + * Wird von jedem API-Endpunkt eingebunden. Lädt die Dolibarr-Umgebung + * ohne Web-Session und authentifiziert die mobile App per JWT. + */ + +// Konstanten setzen BEVOR Dolibarr geladen wird (kein Menü, kein HTML, kein Login) +if (!defined('NOLOGIN')) { + define('NOLOGIN', '1'); +} +if (!defined('NOCSRFCHECK')) { + define('NOCSRFCHECK', '1'); +} +if (!defined('NOTOKENRENEWAL')) { + define('NOTOKENRENEWAL', '1'); +} +if (!defined('NOREQUIREMENU')) { + define('NOREQUIREMENU', '1'); +} +if (!defined('NOREQUIREHTML')) { + define('NOREQUIREHTML', '1'); +} +if (!defined('NOREQUIREAJAX')) { + define('NOREQUIREAJAX', '1'); +} +if (!defined('NOREQUIRESOC')) { + define('NOREQUIRESOC', '1'); +} + +/** + * Dolibarr-Umgebung laden (master.inc.php) und CORS-Header setzen. + * + * @return void + */ +function netdiag_api_bootstrap() +{ + // master.inc.php aus dem Webroot finden + $res = 0; + $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))."/master.inc.php")) { + $res = @include substr($tmp, 0, ($i + 1))."/master.inc.php"; + } + if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/master.inc.php")) { + $res = @include dirname(substr($tmp, 0, ($i + 1)))."/master.inc.php"; + } + if (!$res && file_exists("../../../master.inc.php")) { + $res = @include "../../../master.inc.php"; + } + if (!$res && file_exists("../../../../master.inc.php")) { + $res = @include "../../../../master.inc.php"; + } + if (!$res) { + header('Content-Type: application/json; charset=utf-8'); + http_response_code(500); + echo json_encode(array('error' => 'Dolibarr environment not found')); + exit; + } + + // CORS: Bearer-Token-Auth, daher Wildcard-Origin erlaubt (keine Cookies) + header('Access-Control-Allow-Origin: *'); + header('Access-Control-Allow-Methods: GET, POST, OPTIONS'); + header('Access-Control-Allow-Headers: Content-Type, Authorization'); + header('Access-Control-Max-Age: 86400'); + + // Preflight sofort beantworten + if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { + http_response_code(204); + exit; + } +} + +/** + * JSON-Antwort senden und Skript beenden. + * + * @param mixed $data Antwortdaten + * @param int $httpstatus HTTP-Statuscode + * @return void + */ +function netdiag_api_respond($data, $httpstatus = 200) +{ + header('Content-Type: application/json; charset=utf-8'); + http_response_code($httpstatus); + echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + exit; +} + +/** + * Fehler-Antwort senden und Skript beenden. + * + * @param string $message Fehlermeldung + * @param int $httpstatus HTTP-Statuscode + * @return void + */ +function netdiag_api_error($message, $httpstatus = 400) +{ + netdiag_api_respond(array('error' => $message), $httpstatus); +} + +/** + * Base64-URL-kodieren (JWT-konform, ohne Padding). + * + * @param string $data Rohdaten + * @return string Kodierter String + */ +function netdiag_base64url_encode($data) +{ + return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); +} + +/** + * Base64-URL-dekodieren. + * + * @param string $data Kodierter String + * @return string Rohdaten + */ +function netdiag_base64url_decode($data) +{ + return base64_decode(strtr($data, '-_', '+/')); +} + +/** + * Geheimen JWT-Schlüssel des Moduls holen. + * + * @return string Schlüssel + */ +function netdiag_jwt_secret() +{ + $secret = getDolGlobalString('NETDIAG_API_JWT_SECRET'); + if (empty($secret)) { + // Fallback: Instanz-eindeutiger Wert (sollte nach Modulaktivierung nicht eintreten) + $secret = md5(DOL_DOCUMENT_ROOT.getDolGlobalString('MAIN_INFO_SOCIETE_NOM')); + } + return $secret; +} + +/** + * JWT (HS256) erzeugen. + * + * @param array $payload Nutzdaten (sub, name, exp werden ergänzt) + * @param int $ttl Gültigkeit in Sekunden + * @return string Signiertes Token + */ +function netdiag_jwt_encode($payload, $ttl) +{ + $header = array('alg' => 'HS256', 'typ' => 'JWT'); + $now = dol_now(); + $payload['iat'] = $now; + $payload['exp'] = $now + $ttl; + + $seg = array(); + $seg[] = netdiag_base64url_encode(json_encode($header)); + $seg[] = netdiag_base64url_encode(json_encode($payload)); + $signinginput = implode('.', $seg); + $signature = hash_hmac('sha256', $signinginput, netdiag_jwt_secret(), true); + $seg[] = netdiag_base64url_encode($signature); + + return implode('.', $seg); +} + +/** + * JWT prüfen und Nutzdaten zurückgeben. + * + * @param string $token JWT + * @return array|null Nutzdaten oder null bei ungültig/abgelaufen + */ +function netdiag_jwt_decode($token) +{ + $parts = explode('.', (string) $token); + if (count($parts) !== 3) { + return null; + } + list($h, $p, $s) = $parts; + $expected = hash_hmac('sha256', $h.'.'.$p, netdiag_jwt_secret(), true); + $given = netdiag_base64url_decode($s); + if (!hash_equals($expected, $given)) { + return null; + } + $payload = json_decode(netdiag_base64url_decode($p), true); + if (!is_array($payload)) { + return null; + } + if (empty($payload['exp']) || $payload['exp'] < dol_now()) { + return null; + } + return $payload; +} + +/** + * Token aus Request lesen (Authorization-Header oder ?jwt=). + * + * @return string Token oder leerer String + */ +function netdiag_api_read_token() +{ + $auth = ''; + if (!empty($_SERVER['HTTP_AUTHORIZATION'])) { + $auth = $_SERVER['HTTP_AUTHORIZATION']; + } elseif (!empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) { + $auth = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; + } elseif (function_exists('apache_request_headers')) { + $headers = apache_request_headers(); + if (!empty($headers['Authorization'])) { + $auth = $headers['Authorization']; + } + } + if (stripos($auth, 'Bearer ') === 0) { + return trim(substr($auth, 7)); + } + if (!empty($_GET['jwt'])) { + return (string) $_GET['jwt']; + } + return ''; +} + +/** + * Aktuellen Request authentifizieren. Bricht mit 401 ab, wenn ungültig. + * + * @param DoliDB $db Datenbank-Handler + * @return User Geladenes Benutzer-Objekt + */ +function netdiag_api_authenticate($db) +{ + $token = netdiag_api_read_token(); + if (empty($token)) { + netdiag_api_error('Kein Token übermittelt', 401); + } + $payload = netdiag_jwt_decode($token); + if ($payload === null || empty($payload['sub'])) { + netdiag_api_error('Token ungültig oder abgelaufen', 401); + } + + require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php'; + $user = new User($db); + if ($user->fetch((int) $payload['sub']) <= 0 || empty($user->id)) { + netdiag_api_error('Benutzer nicht gefunden', 401); + } + if (!empty($user->statut) && $user->statut == 0) { + netdiag_api_error('Benutzer deaktiviert', 403); + } + $user->loadRights(); + + if (!$user->hasRight('netdiag', 'protocol', 'read')) { + netdiag_api_error('Keine Berechtigung für NetDiag', 403); + } + return $user; +} + +/** + * JSON-Body eines POST-Requests einlesen. + * + * @return array Dekodierte Daten (leer bei Fehler) + */ +function netdiag_api_read_body() +{ + $raw = file_get_contents('php://input'); + if (empty($raw)) { + return array(); + } + $data = json_decode($raw, true); + return is_array($data) ? $data : array(); +} + +/** + * Liste von Diagnose-Protokollen als Array zurückgeben (für API-Antworten). + * + * @param DoliDB $db Datenbank-Handler + * @param string $filtersql Zusätzlicher SQL-Filter, beginnend mit ' AND ...' + * @return array> Liste der Protokolle + */ +function netdiag_api_protocol_list($db, $filtersql = '') +{ + $prefix = $db->prefix(); + $sql = "SELECT p.rowid, p.ref, p.label, p.client_uuid, p.fk_soc, p.fk_commande,"; + $sql .= " p.date_diag, p.standort, p.subnet, p.status,"; + $sql .= " (SELECT COUNT(*) FROM ".$prefix."netdiag_device d WHERE d.fk_protocol = p.rowid) as devcount,"; + $sql .= " (SELECT COUNT(*) FROM ".$prefix."netdiag_measurement m WHERE m.fk_protocol = p.rowid) as meascount"; + $sql .= " FROM ".$prefix."netdiag_protocol as p"; + $sql .= " WHERE p.entity IN (".getEntity('netdiagprotocol').")"; + $sql .= $filtersql; + $sql .= " ORDER BY p.date_diag DESC, p.rowid DESC"; + + $list = array(); + $resql = $db->query($sql); + if ($resql) { + while ($obj = $db->fetch_object($resql)) { + $list[] = array( + 'id' => (int) $obj->rowid, + 'ref' => $obj->ref, + 'label' => $obj->label, + 'clientUuid' => $obj->client_uuid, + 'socId' => $obj->fk_soc ? (int) $obj->fk_soc : null, + 'orderId' => $obj->fk_commande ? (int) $obj->fk_commande : null, + 'dateDiag' => $db->jdate($obj->date_diag), + 'location' => $obj->standort, + 'subnet' => $obj->subnet, + 'status' => (int) $obj->status, + 'deviceCount' => (int) $obj->devcount, + 'measureCount' => (int) $obj->meascount, + ); + } + } + return $list; +} diff --git a/api/orders.php b/api/orders.php new file mode 100644 index 0000000..f4269b4 --- /dev/null +++ b/api/orders.php @@ -0,0 +1,117 @@ + + * + * 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 . + */ + +/** + * \file netdiag/api/orders.php + * \ingroup netdiag + * \brief API-Endpunkt: Aufträge listen (?open=1&q=) oder Auftragsdetail (?id=). + * + * Auftragsstatus (Dolibarr commande.fk_statut): + * -1 = storniert, 0 = Entwurf, 1 = validiert, 2 = in Bearbeitung, + * 3 = abgeschlossen. "Aktiv" = Status 0/1/2. + */ + +require_once __DIR__.'/netdiag_api.lib.php'; + +netdiag_api_bootstrap(); + +/** @var DoliDB $db */ + +$user = netdiag_api_authenticate($db); + +$id = isset($_GET['id']) ? (int) $_GET['id'] : 0; +$q = isset($_GET['q']) ? trim((string) $_GET['q']) : ''; +$onlyopen = !empty($_GET['open']); +$limit = isset($_GET['limit']) ? min(200, max(1, (int) $_GET['limit'])) : 80; + +$prefix = $db->prefix(); + +// ---- Auftragsdetail ---- +if ($id > 0) { + $sql = "SELECT c.rowid, c.ref, c.ref_client, c.date_commande, c.fk_statut, c.note_public,"; + $sql .= " s.rowid as socid, s.nom as socname, s.address, s.zip, s.town, s.phone, s.email"; + $sql .= " FROM ".$prefix."commande as c"; + $sql .= " LEFT JOIN ".$prefix."societe as s ON s.rowid = c.fk_soc"; + $sql .= " WHERE c.rowid = ".((int) $id)." AND c.entity IN (".getEntity('commande').")"; + $resql = $db->query($sql); + if (!$resql || !($obj = $db->fetch_object($resql))) { + netdiag_api_error('Auftrag nicht gefunden', 404); + } + $order = array( + 'id' => (int) $obj->rowid, + 'ref' => $obj->ref, + 'refClient' => $obj->ref_client, + 'date' => $db->jdate($obj->date_commande), + 'status' => (int) $obj->fk_statut, + 'open' => ((int) $obj->fk_statut >= 0 && (int) $obj->fk_statut < 3), + 'note' => $obj->note_public, + 'customer' => array( + 'id' => (int) $obj->socid, + 'name' => $obj->socname, + 'address' => $obj->address, + 'zip' => $obj->zip, + 'town' => $obj->town, + 'phone' => $obj->phone, + 'email' => $obj->email, + ), + ); + $protocols = netdiag_api_protocol_list($db, ' AND p.fk_commande = '.((int) $id)); + + netdiag_api_respond(array('order' => $order, 'protocols' => $protocols)); +} + +// ---- Auftragsliste ---- +$sql = "SELECT c.rowid, c.ref, c.ref_client, c.date_commande, c.fk_statut,"; +$sql .= " s.rowid as socid, s.nom as socname, s.zip, s.town,"; +$sql .= " (SELECT COUNT(*) FROM ".$prefix."netdiag_protocol p WHERE p.fk_commande = c.rowid) as protocolcount"; +$sql .= " FROM ".$prefix."commande as c"; +$sql .= " LEFT JOIN ".$prefix."societe as s ON s.rowid = c.fk_soc"; +$sql .= " WHERE c.entity IN (".getEntity('commande').")"; +if ($onlyopen) { + // Aktive Aufträge: Entwurf / validiert / in Bearbeitung + $sql .= " AND c.fk_statut IN (0, 1, 2)"; +} +if ($q !== '') { + $sql .= " AND (".natural_search(array('c.ref', 'c.ref_client', 's.nom', 's.town'), $q, 0, 1).")"; +} +$sql .= " ORDER BY c.date_commande DESC, c.rowid DESC"; +$sql .= $db->plimit($limit, 0); + +$resql = $db->query($sql); +if (!$resql) { + netdiag_api_error('Datenbankfehler: '.$db->lasterror(), 500); +} +$orders = array(); +while ($obj = $db->fetch_object($resql)) { + $orders[] = array( + 'id' => (int) $obj->rowid, + 'ref' => $obj->ref, + 'refClient' => $obj->ref_client, + 'date' => $db->jdate($obj->date_commande), + 'status' => (int) $obj->fk_statut, + 'open' => ((int) $obj->fk_statut >= 0 && (int) $obj->fk_statut < 3), + 'protocolCount' => (int) $obj->protocolcount, + 'customer' => array( + 'id' => $obj->socid ? (int) $obj->socid : null, + 'name' => $obj->socname, + 'zip' => $obj->zip, + 'town' => $obj->town, + ), + ); +} + +netdiag_api_respond(array('orders' => $orders)); diff --git a/api/pdf.php b/api/pdf.php new file mode 100644 index 0000000..742877d --- /dev/null +++ b/api/pdf.php @@ -0,0 +1,67 @@ + + * + * 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 . + */ + +/** + * \file netdiag/api/pdf.php + * \ingroup netdiag + * \brief API-Endpunkt: Protokoll-PDF streamen (GET ?id=&jwt=). + */ + +require_once __DIR__.'/netdiag_api.lib.php'; + +netdiag_api_bootstrap(); + +/** + * @var DoliDB $db + * @var Translate $langs + */ + +$user = netdiag_api_authenticate($db); + +require_once __DIR__.'/../class/netdiagprotocol.class.php'; +require_once __DIR__.'/../lib/netdiag.lib.php'; +require_once __DIR__.'/../lib/netdiag_pdf.lib.php'; + +$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); +} + +$file = netdiagGetOutputDir().'/'.dol_sanitizeFileName($protocol->ref).'/'.dol_sanitizeFileName($protocol->ref).'.pdf'; + +// PDF erzeugen, wenn noch nicht vorhanden oder Neuerzeugung verlangt +if (!dol_is_file($file) || !empty($_GET['regenerate'])) { + if (netdiagGeneratePdf($db, $protocol, $langs) <= 0) { + netdiag_api_error('PDF-Erzeugung fehlgeschlagen', 500); + } +} +if (!dol_is_file($file)) { + netdiag_api_error('PDF nicht gefunden', 404); +} + +// PDF ausliefern +header('Content-Type: application/pdf'); +header('Content-Disposition: inline; filename="'.dol_sanitizeFileName($protocol->ref).'.pdf"'); +header('Content-Length: '.dol_filesize($file)); +header('Cache-Control: private, max-age=0'); +readfile($file); +exit; diff --git a/api/protocols.php b/api/protocols.php new file mode 100644 index 0000000..224f193 --- /dev/null +++ b/api/protocols.php @@ -0,0 +1,215 @@ + + * + * 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 . + */ + +/** + * \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, + 'vendor' => $d->vendor, + 'deviceType' => $d->devicetype, + '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 = !empty($p['dateDiag']) ? (int) $p['dateDiag'] : dol_now(); +$protocol->fk_user_techniker = (int) $user->id; +$protocol->standort = isset($p['location']) ? (string) $p['location'] : ''; +$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'] : ''; + +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->error, 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'] : ''; + if ($dev->create($user, 1) <= 0) { + $db->rollback(); + netdiag_api_error('Gerät speichern fehlgeschlagen: '.$dev->error, 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; + $meas->measure_status = isset($m['measureStatus']) ? (int) $m['measureStatus'] : 0; + $meas->date_measure = !empty($m['dateMeasure']) ? (int) $m['dateMeasure'] : dol_now(); + if ($meas->create($user, 1) <= 0) { + $db->rollback(); + netdiag_api_error('Messung speichern fehlgeschlagen: '.$meas->error, 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, +)); diff --git a/class/netdiagdevice.class.php b/class/netdiagdevice.class.php new file mode 100644 index 0000000..3ea9c88 --- /dev/null +++ b/class/netdiagdevice.class.php @@ -0,0 +1,156 @@ + + * + * 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 . + */ + +/** + * \file htdocs/custom/netdiag/class/netdiagdevice.class.php + * \ingroup netdiag + * \brief Klasse für ein im Diagnose-Protokoll gefundenes Gerät + */ + +require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php'; + +/** + * Klasse NetDiagDevice — ein im Netzwerk gefundenes Gerät. + */ +class NetDiagDevice extends CommonObject +{ + /** @var string Modul-Name */ + public $module = 'netdiag'; + + /** @var string Element-Typ */ + public $element = 'netdiagdevice'; + + /** @var string Datenbanktabelle (ohne Präfix) */ + public $table_element = 'netdiag_device'; + + /** @var string Icon */ + public $picto = 'fa-desktop'; + + /** @var int 1 = Tabelle hat ein 'entity'-Feld */ + public $ismultientitymanaged = 1; + + /** + * @var array> Felddefinitionen + */ + public $fields = array( + 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'index' => 1, 'position' => 1), + 'entity' => array('type' => 'integer', 'label' => 'Entity', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'default' => 1, 'index' => 1, 'position' => 5), + 'fk_protocol' => array('type' => 'integer', 'label' => 'Protocol', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'index' => 1, 'position' => 10), + '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), + 'hostname' => array('type' => 'varchar(255)', 'label' => 'Hostname', 'enabled' => 1, 'visible' => 1, 'position' => 30), + '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), + '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), + 'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'position' => 501), + ); + + public $rowid; + public $entity; + public $fk_protocol; + public $ip; + public $mac; + public $hostname; + public $vendor; + public $devicetype; + public $note; + public $date_creation; + public $tms; + + /** + * Konstruktor + * + * @param DoliDB $db Datenbank-Handler + */ + public function __construct(DoliDB $db) + { + $this->db = $db; + } + + /** + * Datensatz anlegen + * + * @param User $user Benutzer + * @param int $notrigger 1 = Trigger unterdrücken + * @return int >0 wenn OK, <=0 bei Fehler + */ + public function create(User $user, $notrigger = 0) + { + return $this->createCommon($user, $notrigger); + } + + /** + * Datensatz laden + * + * @param int $id Rowid + * @param string $ref Referenz + * @return int >0 wenn OK, 0 wenn nicht gefunden, <0 bei Fehler + */ + public function fetch($id, $ref = null) + { + return $this->fetchCommon($id, $ref); + } + + /** + * Alle Geräte eines Protokolls laden + * + * @param int $fk_protocol Protokoll-Rowid + * @return NetDiagDevice[] Liste der Geräte + */ + public function fetchAllByProtocol($fk_protocol) + { + $result = array(); + $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element; + $sql .= " WHERE fk_protocol = ".((int) $fk_protocol); + $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; + } + } + } + return $result; + } + + /** + * Datensatz aktualisieren + * + * @param User $user Benutzer + * @param int $notrigger 1 = Trigger unterdrücken + * @return int >0 wenn OK, <=0 bei Fehler + */ + public function update(User $user, $notrigger = 0) + { + return $this->updateCommon($user, $notrigger); + } + + /** + * Datensatz löschen + * + * @param User $user Benutzer + * @param int $notrigger 1 = Trigger unterdrücken + * @return int >0 wenn OK, <=0 bei Fehler + */ + public function delete(User $user, $notrigger = 0) + { + return $this->deleteCommon($user, $notrigger); + } +} diff --git a/class/netdiagmeasurement.class.php b/class/netdiagmeasurement.class.php new file mode 100644 index 0000000..5b939e1 --- /dev/null +++ b/class/netdiagmeasurement.class.php @@ -0,0 +1,170 @@ + + * + * 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 . + */ + +/** + * \file htdocs/custom/netdiag/class/netdiagmeasurement.class.php + * \ingroup netdiag + * \brief Klasse für eine Messung (ein Tool-Lauf) im Diagnose-Protokoll + */ + +require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php'; + +/** + * Klasse NetDiagMeasurement — Ergebnis eines Tool-Laufs. + * + * params/result sind generisches JSON: neue Tools brauchen kein Schema-Update. + */ +class NetDiagMeasurement extends CommonObject +{ + /** @var string Modul-Name */ + public $module = 'netdiag'; + + /** @var string Element-Typ */ + public $element = 'netdiagmeasurement'; + + /** @var string Datenbanktabelle (ohne Präfix) */ + public $table_element = 'netdiag_measurement'; + + /** @var string Icon */ + public $picto = 'fa-wave-square'; + + /** @var int 1 = Tabelle hat ein 'entity'-Feld */ + public $ismultientitymanaged = 1; + + /** Ampel-Status der Messung */ + const RESULT_OK = 0; + const RESULT_WARN = 1; + const RESULT_FAIL = 2; + + /** + * @var array> Felddefinitionen + */ + public $fields = array( + 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'index' => 1, 'position' => 1), + 'entity' => array('type' => 'integer', 'label' => 'Entity', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'default' => 1, 'index' => 1, 'position' => 5), + 'fk_protocol' => array('type' => 'integer', 'label' => 'Protocol', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'index' => 1, 'position' => 10), + 'fk_device' => array('type' => 'integer', 'label' => 'Device', 'enabled' => 1, 'visible' => 0, 'index' => 1, 'position' => 15), + 'tool' => array('type' => 'varchar(64)', 'label' => 'Tool', 'enabled' => 1, 'visible' => 1, 'notnull' => 1, 'position' => 20), + 'category' => array('type' => 'varchar(32)', 'label' => 'ToolCategory', 'enabled' => 1, 'visible' => 1, 'position' => 25), + 'label' => array('type' => 'varchar(255)', 'label' => 'Label', 'enabled' => 1, 'visible' => 1, 'position' => 30), + 'params' => array('type' => 'text', 'label' => 'Params', 'enabled' => 1, 'visible' => 0, 'position' => 40), + 'result' => array('type' => 'text', 'label' => 'Result', 'enabled' => 1, 'visible' => 0, 'position' => 45), + 'measure_status' => array('type' => 'smallint', 'label' => 'MeasureStatus', 'enabled' => 1, 'visible' => 1, 'notnull' => 1, 'default' => 0, 'position' => 50), + 'date_measure' => array('type' => 'datetime', 'label' => 'DateMeasure', 'enabled' => 1, 'visible' => 1, 'position' => 55), + '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' => 1, 'position' => 501), + ); + + public $rowid; + public $entity; + public $fk_protocol; + public $fk_device; + public $tool; + public $category; + public $label; + public $params; + public $result; + public $measure_status; + public $date_measure; + public $date_creation; + public $tms; + + /** + * Konstruktor + * + * @param DoliDB $db Datenbank-Handler + */ + public function __construct(DoliDB $db) + { + $this->db = $db; + } + + /** + * Datensatz anlegen + * + * @param User $user Benutzer + * @param int $notrigger 1 = Trigger unterdrücken + * @return int >0 wenn OK, <=0 bei Fehler + */ + public function create(User $user, $notrigger = 0) + { + if (empty($this->date_measure)) { + $this->date_measure = dol_now(); + } + return $this->createCommon($user, $notrigger); + } + + /** + * Datensatz laden + * + * @param int $id Rowid + * @param string $ref Referenz + * @return int >0 wenn OK, 0 wenn nicht gefunden, <0 bei Fehler + */ + public function fetch($id, $ref = null) + { + return $this->fetchCommon($id, $ref); + } + + /** + * Alle Messungen eines Protokolls laden + * + * @param int $fk_protocol Protokoll-Rowid + * @return NetDiagMeasurement[] Liste der Messungen + */ + public function fetchAllByProtocol($fk_protocol) + { + $result = array(); + $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element; + $sql .= " WHERE fk_protocol = ".((int) $fk_protocol); + $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; + } + } + } + return $result; + } + + /** + * Datensatz aktualisieren + * + * @param User $user Benutzer + * @param int $notrigger 1 = Trigger unterdrücken + * @return int >0 wenn OK, <=0 bei Fehler + */ + public function update(User $user, $notrigger = 0) + { + return $this->updateCommon($user, $notrigger); + } + + /** + * Datensatz löschen + * + * @param User $user Benutzer + * @param int $notrigger 1 = Trigger unterdrücken + * @return int >0 wenn OK, <=0 bei Fehler + */ + public function delete(User $user, $notrigger = 0) + { + return $this->deleteCommon($user, $notrigger); + } +} diff --git a/class/netdiagprotocol.class.php b/class/netdiagprotocol.class.php new file mode 100644 index 0000000..9e4acad --- /dev/null +++ b/class/netdiagprotocol.class.php @@ -0,0 +1,233 @@ + + * + * 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 . + */ + +/** + * \file htdocs/custom/netdiag/class/netdiagprotocol.class.php + * \ingroup netdiag + * \brief Klasse für das Diagnose-Protokoll (Kopfdatensatz) + */ + +require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php'; + +/** + * Klasse NetDiagProtocol — ein Netzwerk-Diagnose-Vorgang beim Kunden. + * + * Bündelt gefundene Geräte (NetDiagDevice) und Messungen (NetDiagMeasurement) + * und ist mit Kunde (fk_soc) sowie optional Auftrag (fk_commande) verknüpft. + */ +class NetDiagProtocol extends CommonObject +{ + /** @var string Modul-Name */ + public $module = 'netdiag'; + + /** @var string Element-Typ */ + public $element = 'netdiagprotocol'; + + /** @var string Datenbanktabelle (ohne Präfix) */ + public $table_element = 'netdiag_protocol'; + + /** @var string Icon */ + public $picto = 'fa-network-wired'; + + /** @var int 1 = Tabelle hat ein 'entity'-Feld für Multicompany */ + public $ismultientitymanaged = 1; + + /** Status-Konstanten */ + const STATUS_DRAFT = 0; + const STATUS_DONE = 1; + + /** + * @var array> Felddefinitionen für die generischen CRUD-Methoden + */ + public $fields = array( + 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'index' => 1, 'position' => 1), + 'ref' => array('type' => 'varchar(128)', 'label' => 'Ref', 'enabled' => 1, 'visible' => 1, 'notnull' => 1, 'showoncombobox' => 1, 'position' => 10), + 'entity' => array('type' => 'integer', 'label' => 'Entity', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'default' => 1, 'index' => 1, 'position' => 5), + 'label' => array('type' => 'varchar(255)', 'label' => 'Label', 'enabled' => 1, 'visible' => 1, 'position' => 20), + 'client_uuid' => array('type' => 'varchar(64)', 'label' => 'ClientUuid', 'enabled' => 1, 'visible' => 0, 'position' => 25), + 'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'enabled' => 1, 'visible' => 1, 'index' => 1, 'position' => 30), + 'fk_commande' => array('type' => 'integer:Commande:commande/class/commande.class.php', 'label' => 'Order', 'enabled' => 1, 'visible' => 1, 'index' => 1, 'position' => 35), + 'date_diag' => array('type' => 'datetime', 'label' => 'DateDiag', 'enabled' => 1, 'visible' => 1, 'position' => 40), + 'fk_user_techniker' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'Technician', 'enabled' => 1, 'visible' => 1, 'position' => 45), + 'standort' => array('type' => 'varchar(255)', 'label' => 'Location', 'enabled' => 1, 'visible' => 1, 'position' => 50), + 'subnet' => array('type' => 'varchar(64)', 'label' => 'Subnet', 'enabled' => 1, 'visible' => 1, 'position' => 55), + 'status' => array('type' => 'smallint', 'label' => 'Status', 'enabled' => 1, 'visible' => 1, 'notnull' => 1, 'default' => 0, 'index' => 1, 'position' => 60, 'arrayofkeyval' => array(0 => 'Draft', 1 => 'Done')), + 'note' => array('type' => 'text', 'label' => 'Note', 'enabled' => 1, 'visible' => 3, 'position' => 70), + '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' => 1, 'position' => 501), + 'fk_user_creat' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'position' => 510), + 'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => 0, 'position' => 511), + ); + + public $rowid; + public $ref; + public $entity; + public $label; + public $client_uuid; + public $fk_soc; + public $fk_commande; + public $date_diag; + public $fk_user_techniker; + public $standort; + public $subnet; + public $status; + public $note; + public $date_creation; + public $tms; + public $fk_user_creat; + public $fk_user_modif; + + /** + * Konstruktor + * + * @param DoliDB $db Datenbank-Handler + */ + public function __construct(DoliDB $db) + { + $this->db = $db; + } + + /** + * Datensatz anlegen + * + * @param User $user Benutzer + * @param int $notrigger 1 = Trigger unterdrücken + * @return int >0 wenn OK (rowid), <=0 bei Fehler + */ + public function create(User $user, $notrigger = 0) + { + if (empty($this->ref) || $this->ref == '(PROV)') { + $this->ref = $this->getNextNumRef(); + } + if (empty($this->date_diag)) { + $this->date_diag = dol_now(); + } + return $this->createCommon($user, $notrigger); + } + + /** + * Datensatz laden + * + * @param int $id Rowid + * @param string $ref Referenz statt Rowid + * @return int >0 wenn OK, 0 wenn nicht gefunden, <0 bei Fehler + */ + public function fetch($id, $ref = null) + { + return $this->fetchCommon($id, $ref); + } + + /** + * Protokoll anhand der Client-UUID laden (für idempotenten App-Sync) + * + * @param string $uuid Client-UUID + * @return int >0 wenn OK, 0 wenn nicht gefunden, <0 bei Fehler + */ + public function fetchByClientUuid($uuid) + { + $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element; + $sql .= " WHERE client_uuid = '".$this->db->escape($uuid)."'"; + $sql .= " AND entity IN (".getEntity($this->element).")"; + $resql = $this->db->query($sql); + if (!$resql) { + $this->error = $this->db->lasterror(); + return -1; + } + if ($obj = $this->db->fetch_object($resql)) { + return $this->fetch((int) $obj->rowid); + } + return 0; + } + + /** + * Datensatz aktualisieren + * + * @param User $user Benutzer + * @param int $notrigger 1 = Trigger unterdrücken + * @return int >0 wenn OK, <=0 bei Fehler + */ + public function update(User $user, $notrigger = 0) + { + return $this->updateCommon($user, $notrigger); + } + + /** + * Datensatz löschen (inkl. zugehöriger Geräte und Messungen) + * + * @param User $user Benutzer + * @param int $notrigger 1 = Trigger unterdrücken + * @return int >0 wenn OK, <=0 bei Fehler + */ + public function delete(User $user, $notrigger = 0) + { + $this->db->begin(); + $tables = array('netdiag_measurement', 'netdiag_device'); + foreach ($tables as $table) { + $sql = "DELETE FROM ".$this->db->prefix().$table." WHERE fk_protocol = ".((int) $this->id); + if (!$this->db->query($sql)) { + $this->error = $this->db->lasterror(); + $this->db->rollback(); + return -1; + } + } + $result = $this->deleteCommon($user, $notrigger); + if ($result <= 0) { + $this->db->rollback(); + return -1; + } + $this->db->commit(); + return 1; + } + + /** + * Nächste freie Referenz ermitteln (NDjjjj-nnnn) + * + * @return string Neue Referenz + */ + public function getNextNumRef() + { + $prefix = 'ND'.dol_print_date(dol_now(), '%Y').'-'; + $sql = "SELECT MAX(CAST(SUBSTRING(ref, ".(strlen($prefix) + 1).") AS UNSIGNED)) AS maxnum"; + $sql .= " FROM ".$this->db->prefix().$this->table_element; + $sql .= " WHERE ref LIKE '".$this->db->escape($prefix)."%'"; + $sql .= " AND entity IN (".getEntity($this->element).")"; + $resql = $this->db->query($sql); + $num = 0; + if ($resql && ($obj = $this->db->fetch_object($resql))) { + $num = (int) $obj->maxnum; + } + return $prefix.sprintf('%04d', $num + 1); + } + + /** + * Beschriftung des Status zurückgeben + * + * @param int $mode 0=Langtext, 1=Kurztext, 3=Picto, ... + * @return string HTML-Label + */ + public function getLibStatut($mode = 0) + { + global $langs; + $labels = array( + self::STATUS_DRAFT => $langs->trans('Draft'), + self::STATUS_DONE => $langs->trans('NetDiagStatusDone'), + ); + $label = isset($labels[$this->status]) ? $labels[$this->status] : ''; + $statustype = ($this->status == self::STATUS_DONE) ? 'status4' : 'status0'; + return dolGetStatus($label, $label, '', $statustype, $mode); + } +} diff --git a/core/modules/modNetDiag.class.php b/core/modules/modNetDiag.class.php new file mode 100644 index 0000000..d584e82 --- /dev/null +++ b/core/modules/modNetDiag.class.php @@ -0,0 +1,240 @@ + + * + * 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 . + */ + +/** + * \defgroup netdiag Modul NetDiag + * \brief NetDiag Modul-Descriptor — Netzwerk-Diagnose-Protokolle. + * \file htdocs/custom/netdiag/core/modules/modNetDiag.class.php + * \ingroup netdiag + * \brief Beschreibungs- und Aktivierungsdatei für das Modul NetDiag + */ +include_once DOL_DOCUMENT_ROOT.'/core/modules/DolibarrModules.class.php'; + + +/** + * Beschreibungs- und Aktivierungsklasse für das Modul NetDiag + * + * NetDiag speichert Netzwerk-Diagnose-Protokolle (IP-Scan, Port-Scan, Ping, + * WLAN, DHCP, SNMP, Durchsatz, Stresstest) und hängt sie an Kunde und Auftrag. + * Die JSON-API unter /custom/netdiag/api/ versorgt die mobile Diagnose-App. + */ +class modNetDiag extends DolibarrModules +{ + /** + * Konstruktor. Definiert Namen, Konstanten, Verzeichnisse, Rechte, Menüs. + * + * @param DoliDB $db Datenbank-Handler + */ + public function __construct($db) + { + global $conf, $langs; + + $this->db = $db; + + // Eindeutige Modul-ID (freier Bereich, siehe Wiki Liste der Modul-IDs) + $this->numero = 500100; + + // Schlüsseltext zur Identifikation (Rechte, Menüs, ...) + $this->rights_class = 'netdiag'; + + // Familie: 'technic' = transversales/technisches Modul + $this->family = "technic"; + $this->module_position = '90'; + + // Modul-Label (ohne Leerzeichen), Fallback wenn Übersetzung 'ModuleNetDiagName' fehlt + $this->name = preg_replace('/^mod/i', '', get_class($this)); + + // Modulbeschreibung, Fallback wenn Übersetzung 'ModuleNetDiagDesc' fehlt + $this->description = "ModuleNetDiagDesc"; + $this->descriptionlong = "Netzwerk-Diagnose-Protokolle: erfasst per mobiler App Geräte, Ports, Messungen und hängt sie an Kunde und Auftrag."; + + // Autor + $this->editor_name = 'Alles Watt läuft'; + $this->editor_url = ''; + $this->editor_squarred_logo = ''; + + $this->version = '1.0.0'; + + // Konstanten-Name in llx_const für Modul-Status + $this->const_name = 'MAIN_MODULE_'.strtoupper($this->name); + + // Icon (Font-Awesome) + $this->picto = 'fa-network-wired'; + + // Vom Modul unterstützte Funktionen + $this->module_parts = array( + 'triggers' => 0, + 'login' => 0, + 'substitutions' => 0, + 'menus' => 0, + 'tpl' => 0, + 'barcode' => 0, + 'models' => 1, + 'printing' => 0, + 'theme' => 0, + 'css' => array(), + 'js' => array(), + 'hooks' => array(), + 'moduleforexternal' => 0, + 'websitetemplates' => 0, + 'captcha' => 0, + ); + + // Datenverzeichnisse, die beim Aktivieren angelegt werden + $this->dirs = array("/netdiag/temp"); + + // Konfigurationsseiten (in netdiag/admin) + $this->config_page_url = array("setup.php@netdiag"); + + // Abhängigkeiten + $this->hidden = false; + $this->depends = array(); + $this->requiredby = array(); + $this->conflictwith = array(); + + // Sprachdatei des Moduls + $this->langfiles = array("netdiag@netdiag"); + + // Voraussetzungen + $this->phpmin = array(7, 4); + $this->need_dolibarr_version = array(19, -3); + $this->need_javascript_ajax = 0; + + $this->warnings_activation = array(); + $this->warnings_activation_ext = array(); + + // Konstanten beim Aktivieren anlegen + // (Key, Typ, Wert, Beschreibung, sichtbar, current/allentities, deleteonunactive) + $this->const = array( + 1 => array('NETDIAG_API_JWT_SECRET', 'chaine', dol_hash(dol_print_date(dol_now(), 'dayhourrfc').mt_rand(), 'md5'), 'Geheimer Schlüssel zum Signieren der API-JWT', 0, 'current', 1), + 2 => array('NETDIAG_API_TOKEN_TTL', 'chaine', '604800', 'Gültigkeit des API-Tokens in Sekunden (Standard 7 Tage)', 1, 'current', 0), + 3 => array('NETDIAG_APK_URL', 'chaine', 'https://git.data-it-solution.de/api/packages/data-it/generic/netdiag-apk/latest/NetDiag.apk', 'Download-URL der Android-App (für QR-Code im Admin)', 1, 'current', 0), + ); + + if (!isModEnabled("netdiag")) { + $conf->netdiag = new stdClass(); + $conf->netdiag->enabled = 0; + } + + // Neue Tabs an bestehenden Objekten + // Tab "Netzwerk-Diagnose" an Kunde (thirdparty) und Auftrag (order) + $this->tabs = array( + 'thirdparty:+netdiag:NetDiagTab:netdiag@netdiag:$user->hasRight(\'netdiag\', \'protocol\', \'read\'):/custom/netdiag/netdiag_object_tab.php?socid=__ID__&objecttype=thirdparty', + 'order:+netdiag:NetDiagTab:netdiag@netdiag:$user->hasRight(\'netdiag\', \'protocol\', \'read\'):/custom/netdiag/netdiag_object_tab.php?id=__ID__&objecttype=order', + ); + + $this->dictionaries = array(); + $this->boxes = array(); + $this->cronjobs = array(); + + // Rechte + $this->rights = array(); + $r = 0; + $o = 1; + + $this->rights[$r][0] = $this->numero.sprintf("%02d", ($o * 10) + 1); + $this->rights[$r][1] = 'Diagnose-Protokolle lesen'; + $this->rights[$r][4] = 'protocol'; + $this->rights[$r][5] = 'read'; + $r++; + + $this->rights[$r][0] = $this->numero.sprintf("%02d", ($o * 10) + 2); + $this->rights[$r][1] = 'Diagnose-Protokolle anlegen/ändern'; + $this->rights[$r][4] = 'protocol'; + $this->rights[$r][5] = 'write'; + $r++; + + $this->rights[$r][0] = $this->numero.sprintf("%02d", ($o * 10) + 3); + $this->rights[$r][1] = 'Diagnose-Protokolle löschen'; + $this->rights[$r][4] = 'protocol'; + $this->rights[$r][5] = 'delete'; + $r++; + + // Top-Menü + $this->menu = array(); + $r = 0; + + $this->menu[$r++] = array( + 'fk_menu' => '', + 'type' => 'top', + 'titre' => 'ModuleNetDiagName', + 'prefix' => img_picto('', $this->picto, 'class="pictofixedwidth valignmiddle"'), + 'mainmenu' => 'netdiag', + 'leftmenu' => '', + 'url' => '/custom/netdiag/netdiagindex.php', + 'langs' => 'netdiag@netdiag', + 'position' => 1000 + $r, + 'enabled' => 'isModEnabled("netdiag")', + 'perms' => '$user->hasRight("netdiag", "protocol", "read")', + 'target' => '', + 'user' => 2, + ); + + $this->menu[$r++] = array( + 'fk_menu' => 'fk_mainmenu=netdiag', + 'type' => 'left', + 'titre' => 'NetDiagProtocolList', + 'prefix' => img_picto('', $this->picto, 'class="pictofixedwidth valignmiddle paddingright"'), + 'mainmenu' => 'netdiag', + 'leftmenu' => 'netdiag_protocol_list', + 'url' => '/custom/netdiag/netdiagindex.php', + 'langs' => 'netdiag@netdiag', + 'position' => 1000 + $r, + 'enabled' => 'isModEnabled("netdiag")', + 'perms' => '$user->hasRight("netdiag", "protocol", "read")', + 'target' => '', + 'user' => 2, + ); + } + + /** + * Wird beim Aktivieren des Moduls aufgerufen. + * Legt Konstanten, Rechte, Menüs und Datenbanktabellen an. + * + * @param string $options Optionen ('', 'noboxes') + * @return int<-1,1> 1 wenn OK, <=0 bei Fehler + */ + public function init($options = '') + { + global $conf, $langs; + + // SQL-Tabellen beim Aktivieren laden + $result = $this->_load_tables('/netdiag/sql/'); + if ($result < 0) { + return -1; + } + + // Rechte entfernen/neu setzen + $this->remove($options); + + $sql = array(); + + return $this->_init($sql, $options); + } + + /** + * Wird beim Deaktivieren des Moduls aufgerufen. + * + * @param string $options Optionen ('', 'noboxes') + * @return int<-1,1> 1 wenn OK, <=0 bei Fehler + */ + public function remove($options = '') + { + $sql = array(); + return $this->_remove($sql, $options); + } +} diff --git a/langs/de_DE/netdiag.lang b/langs/de_DE/netdiag.lang new file mode 100644 index 0000000..36c26df --- /dev/null +++ b/langs/de_DE/netdiag.lang @@ -0,0 +1,61 @@ +# Copyright (C) 2026 Eduard Wisch +# Sprachdatei NetDiag - Deutsch + +# +# Allgemein +# +ModuleNetDiagName = NetDiag +ModuleNetDiagDesc = Netzwerk-Diagnose: erfasst per mobiler App Geräte, Ports und Messungen und hängt die Protokolle an Kunde und Auftrag. +NetDiagArea = NetDiag - Übersicht +NetDiagTab = Netzwerk-Diagnose + +# +# Admin-Seite +# +NetDiagSetup = NetDiag Einrichtung +NetDiagSetupPage = NetDiag Einrichtungsseite +NetDiagAbout = Über NetDiag +NetDiagAboutPage = NetDiag Info-Seite +NETDIAG_API_TOKEN_TTL = Token-Gültigkeit (Sekunden) +NETDIAG_API_TOKEN_TTLTooltip = Wie lange ein API-Token der mobilen App gültig bleibt. Standard 604800 (7 Tage). +NETDIAG_APK_URL = Download-URL der App (APK) +NETDIAG_APK_URLTooltip = Adresse, unter der die Android-App (APK) liegt. Wird als QR-Code angezeigt. +NetDiagApiSecretInfo = Der JWT-Schlüssel wurde beim Aktivieren automatisch erzeugt und ist nicht editierbar. +NetDiagAppDownload = NetDiag-App herunterladen +NetDiagAppDownloadHint = Mit dem Handy scannen, um die Diagnose-App zu installieren. + +# +# Protokoll +# +NetDiagProtocol = Diagnose-Protokoll +NetDiagProtocols = Diagnose-Protokolle +NetDiagProtocolList = Protokoll-Liste +NewNetDiagProtocol = Neues Diagnose-Protokoll +NetDiagStatusDraft = Entwurf +NetDiagStatusDone = Abgeschlossen +NetDiagNoProtocol = Noch kein Diagnose-Protokoll für dieses Objekt vorhanden + +# +# Felder +# +DateDiag = Aufnahmedatum +DateMeasure = Messzeitpunkt +Technician = Techniker +Location = Standort +Subnet = Netzbereich +DeviceType = Gerätetyp +Protocol = Protokoll +Device = Gerät +Tool = Werkzeug +ToolCategory = Kategorie +MeasureStatus = Bewertung +ClientUuid = App-Kennung +IpAddress = IP-Adresse +MacAddress = MAC-Adresse +Hostname = Hostname +Vendor = Hersteller +NetDiagDevices = Gefundene Geräte +NetDiagMeasurements = Messungen +NetDiagMeasureOk = OK +NetDiagMeasureWarn = Warnung +NetDiagMeasureFail = Fehler diff --git a/langs/en_US/netdiag.lang b/langs/en_US/netdiag.lang new file mode 100644 index 0000000..26a28a4 --- /dev/null +++ b/langs/en_US/netdiag.lang @@ -0,0 +1,61 @@ +# Copyright (C) 2026 Eduard Wisch +# Translation file NetDiag - English + +# +# Generic +# +ModuleNetDiagName = NetDiag +ModuleNetDiagDesc = Network diagnostics: capture devices, ports and measurements with a mobile app and attach the protocols to customer and order. +NetDiagArea = NetDiag - Overview +NetDiagTab = Network diagnostics + +# +# Admin page +# +NetDiagSetup = NetDiag setup +NetDiagSetupPage = NetDiag setup page +NetDiagAbout = About NetDiag +NetDiagAboutPage = NetDiag about page +NETDIAG_API_TOKEN_TTL = Token validity (seconds) +NETDIAG_API_TOKEN_TTLTooltip = How long a mobile app API token stays valid. Default 604800 (7 days). +NETDIAG_APK_URL = App download URL (APK) +NETDIAG_APK_URLTooltip = Address where the Android app (APK) is hosted. Shown as a QR code. +NetDiagApiSecretInfo = The JWT secret was generated automatically on activation and is not editable. +NetDiagAppDownload = Download NetDiag app +NetDiagAppDownloadHint = Scan with your phone to install the diagnostics app. + +# +# Protocol +# +NetDiagProtocol = Diagnostic protocol +NetDiagProtocols = Diagnostic protocols +NetDiagProtocolList = Protocol list +NewNetDiagProtocol = New diagnostic protocol +NetDiagStatusDraft = Draft +NetDiagStatusDone = Completed +NetDiagNoProtocol = No diagnostic protocol for this object yet + +# +# Fields +# +DateDiag = Diagnostic date +DateMeasure = Measurement time +Technician = Technician +Location = Location +Subnet = Subnet +DeviceType = Device type +Protocol = Protocol +Device = Device +Tool = Tool +ToolCategory = Category +MeasureStatus = Rating +ClientUuid = App identifier +IpAddress = IP address +MacAddress = MAC address +Hostname = Hostname +Vendor = Vendor +NetDiagDevices = Discovered devices +NetDiagMeasurements = Measurements +NetDiagMeasureOk = OK +NetDiagMeasureWarn = Warning +NetDiagMeasureFail = Fail diff --git a/lib/netdiag.lib.php b/lib/netdiag.lib.php new file mode 100644 index 0000000..2272722 --- /dev/null +++ b/lib/netdiag.lib.php @@ -0,0 +1,134 @@ + + * + * 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 . + */ + +/** + * \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> 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> 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. + * + * @param string $json JSON-String des Ergebnisses + * @return string HTML-Schnipsel + */ +function netdiagFormatResult($json) +{ + if (empty($json)) { + return '-'; + } + $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); + } + + $out = '
'; + 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 .= ''.$label.': '.dol_escape_htmltag(dol_trunc($valstr, 200)).' '; + } + $out .= '
'; + return $out; +} diff --git a/lib/netdiag_pdf.lib.php b/lib/netdiag_pdf.lib.php new file mode 100644 index 0000000..3ffea2a --- /dev/null +++ b/lib/netdiag_pdf.lib.php @@ -0,0 +1,199 @@ + + * + * 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 . + */ + +/** + * \file netdiag/lib/netdiag_pdf.lib.php + * \ingroup netdiag + * \brief PDF-Generator für Diagnose-Protokolle + */ + +require_once DOL_DOCUMENT_ROOT.'/core/lib/pdf.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php'; +require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php'; +require_once __DIR__.'/netdiag.lib.php'; +require_once __DIR__.'/../class/netdiagdevice.class.php'; +require_once __DIR__.'/../class/netdiagmeasurement.class.php'; + +/** + * Ein Diagnose-Protokoll als PDF erzeugen und im Dokumentenverzeichnis ablegen. + * + * @param DoliDB $db Datenbank-Handler + * @param NetDiagProtocol $protocol Protokoll-Objekt (geladen) + * @param Translate $outputlangs Sprache der Ausgabe + * @return int >0 wenn OK, <=0 bei Fehler + */ +function netdiagGeneratePdf($db, $protocol, $outputlangs) +{ + global $conf, $mysoc, $langs; + + $outputlangs->loadLangs(array("netdiag@netdiag", "main", "companies", "orders")); + + $dir = netdiagGetOutputDir().'/'.dol_sanitizeFileName($protocol->ref); + if (!dol_is_dir($dir)) { + if (dol_mkdir($dir) < 0) { + $protocol->error = 'Konnte Verzeichnis nicht anlegen: '.$dir; + return -1; + } + } + $file = $dir.'/'.dol_sanitizeFileName($protocol->ref).'.pdf'; + + // Daten laden + $soc = new Societe($db); + if ($protocol->fk_soc > 0) { + $soc->fetch($protocol->fk_soc); + } + $devObj = new NetDiagDevice($db); + $devices = $devObj->fetchAllByProtocol($protocol->id); + $measObj = new NetDiagMeasurement($db); + $measurements = $measObj->fetchAllByProtocol($protocol->id); + + // PDF aufbauen + $pdf = pdf_getInstance('A4', 'mm', 'P'); + $default_font_size = pdf_getPDFFontSize($outputlangs); + $pdf->SetAutoPageBreak(true, 20); + $pdf->SetMargins(15, 15, 15); + $pdf->SetTitle($protocol->ref); + $pdf->SetSubject($outputlangs->transnoentities("NetDiagProtocol")); + $pdf->SetCreator("Dolibarr ".DOL_VERSION." - NetDiag"); + $pdf->SetAuthor($mysoc->name); + $pdf->AddPage(); + + // Kopf + $pdf->SetFont('', 'B', 16); + $pdf->SetTextColor(0, 70, 130); + $pdf->Cell(0, 8, $outputlangs->transnoentities("NetDiagProtocol").' '.$protocol->ref, 0, 1, 'L'); + $pdf->SetTextColor(0, 0, 0); + $pdf->SetFont('', '', 9); + $pdf->Cell(0, 5, $mysoc->name, 0, 1, 'L'); + $pdf->Ln(3); + + // Stammdaten + $pdf->SetFont('', '', 10); + $infos = array( + $outputlangs->transnoentities("ThirdParty") => ($soc->id > 0 ? $soc->name : '-'), + $outputlangs->transnoentities("DateDiag") => dol_print_date($protocol->date_diag, 'dayhour', false, $outputlangs), + $outputlangs->transnoentities("Location") => ($protocol->standort ? $protocol->standort : '-'), + $outputlangs->transnoentities("Subnet") => ($protocol->subnet ? $protocol->subnet : '-'), + ); + foreach ($infos as $k => $v) { + $pdf->SetFont('', 'B', 10); + $pdf->Cell(40, 6, $k.':', 0, 0, 'L'); + $pdf->SetFont('', '', 10); + $pdf->Cell(0, 6, $v, 0, 1, 'L'); + } + if (!empty($protocol->note)) { + $pdf->Ln(1); + $pdf->SetFont('', 'I', 9); + $pdf->MultiCell(0, 5, $outputlangs->transnoentities("Note").': '.$protocol->note, 0, 'L'); + } + $pdf->Ln(4); + + // Geräteliste + $pdf->SetFont('', 'B', 12); + $pdf->Cell(0, 7, $outputlangs->transnoentities("NetDiagDevices").' ('.count($devices).')', 0, 1, 'L'); + $pdf->SetFillColor(0, 70, 130); + $pdf->SetTextColor(255, 255, 255); + $pdf->SetFont('', 'B', 8); + $cols = array(35, 40, 50, 40, 15); + $heads = array("IpAddress", "MacAddress", "Hostname", "Vendor", "DeviceType"); + 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); + foreach ($devices as $dev) { + $pdf->Cell($cols[0], 5, dol_trunc($dev->ip, 22), 1, 0, 'L'); + $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'); + $pdf->Cell($cols[3], 5, dol_trunc($dev->vendor, 26), 1, 0, 'L'); + $pdf->Cell($cols[4], 5, dol_trunc($dev->devicetype, 10), 1, 1, 'L'); + } + if (empty($devices)) { + $pdf->Cell(array_sum($cols), 5, '-', 1, 1, 'C'); + } + $pdf->Ln(4); + + // Messungen + $pdf->SetFont('', 'B', 12); + $pdf->Cell(0, 7, $outputlangs->transnoentities("NetDiagMeasurements").' ('.count($measurements).')', 0, 1, 'L'); + $statuslabels = array(0 => 'OK', 1 => $outputlangs->transnoentities("NetDiagMeasureWarn"), 2 => $outputlangs->transnoentities("NetDiagMeasureFail")); + foreach ($measurements as $m) { + $st = (int) $m->measure_status; + if ($st == 2) { + $pdf->SetFillColor(230, 130, 130); + } elseif ($st == 1) { + $pdf->SetFillColor(245, 215, 130); + } else { + $pdf->SetFillColor(170, 215, 170); + } + $pdf->SetFont('', 'B', 9); + $title = ($m->category ? '['.$m->category.'] ' : '').$m->tool.($m->label ? ' — '.$m->label : ''); + $pdf->Cell(150, 6, dol_trunc($title, 80), 1, 0, 'L'); + $pdf->Cell(30, 6, $statuslabels[$st], 1, 1, 'C', true); + $pdf->SetFont('', '', 8); + $pdf->MultiCell(180, 5, netdiagPdfFlattenResult($m->result), 1, 'L'); + } + if (empty($measurements)) { + $pdf->SetFont('', '', 9); + $pdf->Cell(180, 5, '-', 1, 1, 'C'); + } + + // Fuß + $pdf->Ln(6); + $pdf->SetFont('', 'I', 8); + $pdf->SetTextColor(120, 120, 120); + $pdf->Cell(0, 5, $outputlangs->transnoentities("NetDiagProtocol").' '.$protocol->ref.' — '.dol_print_date(dol_now(), 'dayhour', false, $outputlangs), 0, 1, 'L'); + + // Speichern + $pdf->Output($file, 'F'); + dolChmod($file); + + return 1; +} + +/** + * Mess-Ergebnis (JSON) als einzeiligen Text für PDF-Ausgabe. + * + * @param string $json JSON-String + * @return string Lesbarer Text + */ +function netdiagPdfFlattenResult($json) +{ + if (empty($json)) { + return '-'; + } + $data = json_decode($json, true); + if (!is_array($data)) { + return (string) $json; + } + $parts = array(); + foreach ($data as $key => $val) { + 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'; + } + $parts[] = ucfirst((string) $key).': '.$val; + } + return implode(' | ', $parts); +} diff --git a/netdiag_object_tab.php b/netdiag_object_tab.php new file mode 100644 index 0000000..fa63f3d --- /dev/null +++ b/netdiag_object_tab.php @@ -0,0 +1,170 @@ + + * + * 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 . + */ + +/** + * \file netdiag/netdiag_object_tab.php + * \ingroup netdiag + * \brief Tab "Netzwerk-Diagnose" an Kunde (thirdparty) und Auftrag (order) + */ + +// 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.'/societe/class/societe.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.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(); +} + +$objecttype = GETPOST('objecttype', 'aZ09'); +$id = GETPOSTINT('id'); +$socid = GETPOSTINT('socid'); + +// Parent-Objekt laden und passenden Filter aufbauen +$parent = null; +$head = array(); +$picto = 'company'; +$filtersql = ''; + +if ($objecttype == 'order') { + require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php'; + require_once DOL_DOCUMENT_ROOT.'/core/lib/order.lib.php'; + $parent = new Commande($db); + if ($id <= 0 || $parent->fetch($id) <= 0) { + accessforbidden('Order not found'); + } + $parent->fetch_thirdparty(); + $head = commande_prepare_head($parent); + $picto = 'order'; + $filtersql = " AND p.fk_commande = ".((int) $parent->id); +} else { + $parent = new Societe($db); + if ($socid <= 0 || $parent->fetch($socid) <= 0) { + accessforbidden('ThirdParty not found'); + } + $head = societe_prepare_head($parent); + $picto = 'company'; + $filtersql = " AND p.fk_soc = ".((int) $parent->id); +} + +/* + * Ansicht + */ + +llxHeader('', $langs->trans("NetDiagTab"), '', '', 0, 0, '', '', '', 'mod-netdiag'); + +print dol_get_fiche_head($head, 'netdiag', $langs->trans($objecttype == 'order' ? 'CustomerOrder' : 'ThirdParty'), -1, $picto); + +if ($objecttype == 'order') { + $linkback = ''.$langs->trans("BackToList").''; + dol_banner_tab($parent, 'ref', $linkback, 1, 'ref', 'ref'); +} else { + $linkback = ''.$langs->trans("BackToList").''; + dol_banner_tab($parent, 'socid', $linkback, ($user->socid ? 0 : 1), 'rowid', 'nom'); +} + +print dol_get_fiche_end(); + +// Protokolle des Objekts auflisten +$sql = "SELECT p.rowid, p.ref, p.label, p.date_diag, p.standort, p.subnet, p.status,"; +$sql .= " (SELECT COUNT(*) FROM ".$db->prefix()."netdiag_device d WHERE d.fk_protocol = p.rowid) as devcount,"; +$sql .= " (SELECT COUNT(*) FROM ".$db->prefix()."netdiag_measurement m WHERE m.fk_protocol = p.rowid) as meascount"; +$sql .= " FROM ".$db->prefix()."netdiag_protocol as p"; +$sql .= " WHERE p.entity IN (".getEntity('netdiagprotocol').")"; +$sql .= $filtersql; +$sql .= " ORDER BY p.date_diag DESC, p.rowid DESC"; + +$resql = $db->query($sql); + +print '

'; +print load_fiche_titre($langs->trans("NetDiagProtocols"), '', 'fa-network-wired'); + +print '
'; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; + +$proto = new NetDiagProtocol($db); +$nb = 0; +if ($resql) { + while ($obj = $db->fetch_object($resql)) { + $nb++; + $proto->status = $obj->status; + $cardurl = dol_buildpath('/netdiag/netdiagprotocol_card.php', 1).'?id='.$obj->rowid; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + } +} +if (!$nb) { + print ''; +} +print '
'.$langs->trans("Ref").''.$langs->trans("DateDiag").''.$langs->trans("Location").''.$langs->trans("Subnet").''.$langs->trans("NetDiagDevices").''.$langs->trans("NetDiagMeasurements").''.$langs->trans("Status").'
'.img_picto('', 'fa-network-wired').' '.dol_escape_htmltag($obj->ref).''.dol_print_date($db->jdate($obj->date_diag), 'dayhour').''.dol_escape_htmltag($obj->standort).''.dol_escape_htmltag($obj->subnet).''.((int) $obj->devcount).''.((int) $obj->meascount).''.$proto->getLibStatut(3).'
'.$langs->trans("NetDiagNoProtocol").'
'; +print '
'; +print '
'; + +llxFooter(); +$db->close(); diff --git a/netdiagindex.php b/netdiagindex.php new file mode 100644 index 0000000..36634e2 --- /dev/null +++ b/netdiagindex.php @@ -0,0 +1,158 @@ + + * + * 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 . + */ + +/** + * \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'); +$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"; +$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); +} +$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 '
'; +print '
'; +print ''; + +print ''; +print ''; +print ''; +print ''; +print ''; + +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; + +$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 ''; + print ''; + if ($obj->socid) { + print ''; + } else { + print ''; + } + print ''; + print ''; + print ''; + print ''; + print ''; +} +if (empty($rows)) { + print ''; +} + +print '
'.$langs->trans("Ref").''.$langs->trans("ThirdParty").''.$langs->trans("DateDiag").''.$langs->trans("Location").''.$langs->trans("NetDiagDevices").''.$langs->trans("Status").'
'.img_picto('', 'fa-network-wired').' '.dol_escape_htmltag($obj->ref).''.dol_escape_htmltag($obj->socname).''.dol_print_date($db->jdate($obj->date_diag), 'dayhour').''.dol_escape_htmltag($obj->standort).''.((int) $obj->devcount).''.$proto->getLibStatut(3).'
'.$langs->trans("NoRecordFound").'
'; +print '
'; +print '
'; + +llxFooter(); +$db->close(); diff --git a/netdiagprotocol_card.php b/netdiagprotocol_card.php new file mode 100644 index 0000000..a4314ef --- /dev/null +++ b/netdiagprotocol_card.php @@ -0,0 +1,231 @@ + + * + * 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 . + */ + +/** + * \file netdiag/netdiagprotocol_card.php + * \ingroup netdiag + * \brief Detailansicht eines Diagnose-Protokolls (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.'/societe/class/societe.class.php'; +require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php'; +require_once __DIR__.'/class/netdiagprotocol.class.php'; +require_once __DIR__.'/class/netdiagdevice.class.php'; +require_once __DIR__.'/class/netdiagmeasurement.class.php'; +require_once __DIR__.'/lib/netdiag.lib.php'; +require_once __DIR__.'/lib/netdiag_pdf.lib.php'; + +/** + * @var Conf $conf + * @var DoliDB $db + * @var Translate $langs + * @var User $user + */ + +$langs->loadLangs(array("netdiag@netdiag", "companies", "orders", "other")); + +if (!$user->hasRight('netdiag', 'protocol', 'read')) { + accessforbidden(); +} + +$id = GETPOSTINT('id'); +$action = GETPOST('action', 'aZ09'); + +$object = new NetDiagProtocol($db); +if ($id <= 0 || $object->fetch($id) <= 0) { + accessforbidden('Protocol not found'); +} + +// Aktion: PDF generieren +if ($action == 'builddoc' && $user->hasRight('netdiag', 'protocol', 'read')) { + $result = netdiagGeneratePdf($db, $object, $langs); + if ($result > 0) { + setEventMessages($langs->trans("FileGenerated"), null, 'mesgs'); + } else { + setEventMessages($object->error, $object->errors, 'errors'); + } + header("Location: ".$_SERVER["PHP_SELF"]."?id=".$object->id); + exit; +} + +// Aktion: löschen +if ($action == 'confirm_delete' && GETPOST('confirm') == 'yes' && $user->hasRight('netdiag', 'protocol', 'delete')) { + if ($object->delete($user) > 0) { + setEventMessages($langs->trans("RecordDeleted"), null, 'mesgs'); + header("Location: ".dol_buildpath('/netdiag/netdiagindex.php', 1)); + exit; + } + setEventMessages($object->error, $object->errors, 'errors'); +} + +/* + * Ansicht + */ + +$form = new Form($db); +llxHeader('', $object->ref, '', '', 0, 0, '', '', '', 'mod-netdiag page-card'); + +$head = netdiagProtocolPrepareHead($object); +print dol_get_fiche_head($head, 'card', $langs->trans("NetDiagProtocol"), -1, 'fa-network-wired'); + +// Lösch-Bestätigung +if ($action == 'delete') { + print $form->formconfirm($_SERVER["PHP_SELF"]."?id=".$object->id, $langs->trans("Delete"), $langs->trans("ConfirmDeleteObject"), "confirm_delete", '', '', 1); +} + +$linkback = ''.$langs->trans("BackToList").''; +dol_banner_tab($object, 'id', $linkback, 1, 'rowid', 'ref'); + +print '
'; +print '
'; +print ''; + +print ''; + +$soc = new Societe($db); +print ''; + +print ''; + +print ''; +print ''; +print ''; +print ''; +print ''; + +print '
'.$langs->trans("Label").''.dol_escape_htmltag($object->label).'
'.$langs->trans("ThirdParty").''; +if ($object->fk_soc > 0 && $soc->fetch($object->fk_soc) > 0) { + print $soc->getNomUrl(1); +} +print '
'.$langs->trans("Order").''; +if ($object->fk_commande > 0) { + $cmd = new Commande($db); + if ($cmd->fetch($object->fk_commande) > 0) { + print $cmd->getNomUrl(1); + } +} +print '
'.$langs->trans("DateDiag").''.dol_print_date($object->date_diag, 'dayhour').'
'.$langs->trans("Location").''.dol_escape_htmltag($object->standort).'
'.$langs->trans("Subnet").''.dol_escape_htmltag($object->subnet).'
'.$langs->trans("Status").''.$object->getLibStatut(4).'
'.$langs->trans("Note").''.dol_nl2br(dol_escape_htmltag($object->note)).'
'; +print '
'; + +print dol_get_fiche_end(); + +// Aktionsknöpfe +print '
'; +print ''.$langs->trans("BuildDoc").''; +if ($user->hasRight('netdiag', 'protocol', 'delete')) { + print ''.$langs->trans("Delete").''; +} +print '
'; + +// Vorhandene PDF-Dokumente +$upload_dir = netdiagGetOutputDir().'/'.dol_sanitizeFileName($object->ref); +if (is_dir($upload_dir)) { + $files = dol_dir_list($upload_dir, 'files', 0, '\.pdf$'); + if (!empty($files)) { + print '
'; + foreach ($files as $f) { + $durl = DOL_URL_ROOT.'/document.php?modulepart=netdiag&file='.urlencode(dol_sanitizeFileName($object->ref).'/'.$f['name']); + print ''; + } + print '
'.$langs->trans("Documents").'
'.img_mime($f['name']).' '.dol_escape_htmltag($f['name']).'
'; + } +} + +// Geräteliste +$devObj = new NetDiagDevice($db); +$devices = $devObj->fetchAllByProtocol($object->id); +print '
'; +print load_fiche_titre($langs->trans("NetDiagDevices").' ('.count($devices).')', '', 'fa-desktop'); +print '
'; +print ''; +print ''; +print ''; +print ''; +foreach ($devices as $dev) { + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; +} +if (empty($devices)) { + print ''; +} +print '
'.$langs->trans("IpAddress").''.$langs->trans("MacAddress").''.$langs->trans("Hostname").''.$langs->trans("Vendor").''.$langs->trans("DeviceType").'
'.dol_escape_htmltag($dev->ip).''.dol_escape_htmltag($dev->mac).''.dol_escape_htmltag($dev->hostname).''.dol_escape_htmltag($dev->vendor).''.dol_escape_htmltag($dev->devicetype).'
-
'; + +// Messungen +$measObj = new NetDiagMeasurement($db); +$measurements = $measObj->fetchAllByProtocol($object->id); +$statuslabels = array(0 => 'NetDiagMeasureOk', 1 => 'NetDiagMeasureWarn', 2 => 'NetDiagMeasureFail'); +$statuscss = array(0 => 'badge-status4', 1 => 'badge-status1', 2 => 'badge-status8'); +print '
'; +print load_fiche_titre($langs->trans("NetDiagMeasurements").' ('.count($measurements).')', '', 'fa-wave-square'); +print '
'; +print ''; +print ''; +print ''; +print ''; +print ''; +foreach ($measurements as $m) { + $st = (int) $m->measure_status; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; +} +if (empty($measurements)) { + print ''; +} +print '
'.$langs->trans("DateMeasure").''.$langs->trans("ToolCategory").''.$langs->trans("Tool").''.$langs->trans("Label").''.$langs->trans("Result").''.$langs->trans("MeasureStatus").'
'.dol_print_date($m->date_measure, 'dayhour').''.dol_escape_htmltag($m->category).''.dol_escape_htmltag($m->tool).''.dol_escape_htmltag($m->label).''.netdiagFormatResult($m->result).''.$langs->trans($statuslabels[$st]).'
-
'; + +llxFooter(); +$db->close(); diff --git a/sql/llx_netdiag_device.key.sql b/sql/llx_netdiag_device.key.sql new file mode 100644 index 0000000..931287a --- /dev/null +++ b/sql/llx_netdiag_device.key.sql @@ -0,0 +1,6 @@ +-- Copyright (C) 2026 Eduard Wisch +-- +-- Schlüssel und Indizes für llx_netdiag_device + +ALTER TABLE llx_netdiag_device ADD INDEX idx_netdiag_device_fk_protocol(fk_protocol); +ALTER TABLE llx_netdiag_device ADD CONSTRAINT fk_netdiag_device_protocol FOREIGN KEY (fk_protocol) REFERENCES llx_netdiag_protocol(rowid); diff --git a/sql/llx_netdiag_device.sql b/sql/llx_netdiag_device.sql new file mode 100644 index 0000000..b2cfab9 --- /dev/null +++ b/sql/llx_netdiag_device.sql @@ -0,0 +1,22 @@ +-- Copyright (C) 2026 Eduard Wisch +-- +-- 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. +-- +-- Tabelle: gefundenes Gerät innerhalb eines Diagnose-Protokolls + +CREATE TABLE llx_netdiag_device( + rowid integer AUTO_INCREMENT PRIMARY KEY NOT NULL, + entity integer DEFAULT 1 NOT NULL, + fk_protocol integer NOT NULL, + ip varchar(45), + mac varchar(17), + hostname varchar(255), + vendor varchar(128), + devicetype varchar(64), + note text, + date_creation datetime NOT NULL, + tms timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL +) ENGINE=innodb; diff --git a/sql/llx_netdiag_measurement.key.sql b/sql/llx_netdiag_measurement.key.sql new file mode 100644 index 0000000..bf53b09 --- /dev/null +++ b/sql/llx_netdiag_measurement.key.sql @@ -0,0 +1,8 @@ +-- Copyright (C) 2026 Eduard Wisch +-- +-- Schlüssel und Indizes für llx_netdiag_measurement + +ALTER TABLE llx_netdiag_measurement ADD INDEX idx_netdiag_measurement_fk_protocol(fk_protocol); +ALTER TABLE llx_netdiag_measurement ADD INDEX idx_netdiag_measurement_fk_device(fk_device); +ALTER TABLE llx_netdiag_measurement ADD INDEX idx_netdiag_measurement_tool(tool); +ALTER TABLE llx_netdiag_measurement ADD CONSTRAINT fk_netdiag_measurement_protocol FOREIGN KEY (fk_protocol) REFERENCES llx_netdiag_protocol(rowid); diff --git a/sql/llx_netdiag_measurement.sql b/sql/llx_netdiag_measurement.sql new file mode 100644 index 0000000..322fa5a --- /dev/null +++ b/sql/llx_netdiag_measurement.sql @@ -0,0 +1,25 @@ +-- Copyright (C) 2026 Eduard Wisch +-- +-- 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. +-- +-- Tabelle: ein Tool-Lauf (Messung) innerhalb eines Diagnose-Protokolls. +-- params/result sind generisches JSON -> neue Tools brauchen kein Schema-Update. + +CREATE TABLE llx_netdiag_measurement( + rowid integer AUTO_INCREMENT PRIMARY KEY NOT NULL, + entity integer DEFAULT 1 NOT NULL, + fk_protocol integer NOT NULL, + fk_device integer, + tool varchar(64) NOT NULL, + category varchar(32), + label varchar(255), + params text, + result longtext, + measure_status smallint DEFAULT 0 NOT NULL, + date_measure datetime, + date_creation datetime NOT NULL, + tms timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL +) ENGINE=innodb; diff --git a/sql/llx_netdiag_protocol.key.sql b/sql/llx_netdiag_protocol.key.sql new file mode 100644 index 0000000..f490d87 --- /dev/null +++ b/sql/llx_netdiag_protocol.key.sql @@ -0,0 +1,8 @@ +-- Copyright (C) 2026 Eduard Wisch +-- +-- Schlüssel und Indizes für llx_netdiag_protocol + +ALTER TABLE llx_netdiag_protocol ADD UNIQUE INDEX uk_netdiag_protocol_ref(ref, entity); +ALTER TABLE llx_netdiag_protocol ADD UNIQUE INDEX uk_netdiag_protocol_uuid(client_uuid); +ALTER TABLE llx_netdiag_protocol ADD INDEX idx_netdiag_protocol_fk_soc(fk_soc); +ALTER TABLE llx_netdiag_protocol ADD INDEX idx_netdiag_protocol_fk_commande(fk_commande); diff --git a/sql/llx_netdiag_protocol.sql b/sql/llx_netdiag_protocol.sql new file mode 100644 index 0000000..5399c66 --- /dev/null +++ b/sql/llx_netdiag_protocol.sql @@ -0,0 +1,28 @@ +-- Copyright (C) 2026 Eduard Wisch +-- +-- 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. +-- +-- Tabelle: Diagnose-Protokoll (Kopfdatensatz eines Netzwerk-Einsatzes) + +CREATE TABLE llx_netdiag_protocol( + rowid integer AUTO_INCREMENT PRIMARY KEY NOT NULL, + ref varchar(128) NOT NULL, + entity integer DEFAULT 1 NOT NULL, + label varchar(255), + client_uuid varchar(64), + fk_soc integer, + fk_commande integer, + date_diag datetime, + fk_user_techniker integer, + standort varchar(255), + subnet varchar(64), + status smallint DEFAULT 0 NOT NULL, + note text, + date_creation datetime NOT NULL, + tms timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL, + fk_user_creat integer NOT NULL, + fk_user_modif integer +) ENGINE=innodb;