diff --git a/Layout/default/Device/Form.php b/Layout/default/Device/Form.php index faa302a76..ee041a89e 100644 --- a/Layout/default/Device/Form.php +++ b/Layout/default/Device/Form.php @@ -70,7 +70,7 @@ asort($Devices);
+ value="name ?? $_GET['name'] ?? '' ?>" required="required">
@@ -167,7 +167,7 @@ asort($Devices);
+ value="ip ?? $_GET['ip'] ?? '' ?>">
diff --git a/Layout/default/menu.php b/Layout/default/menu.php index ed26aa373..5373d2882 100644 --- a/Layout/default/menu.php +++ b/Layout/default/menu.php @@ -73,6 +73,7 @@
  • "> Devices
  • is(["Admin"])): ?> +
  • "> Device Konsolidierung
  • "> Benutzer
  • Grundstammdaten
  • diff --git a/application/Device/DeviceController.php b/application/Device/DeviceController.php index 573bfc5ab..eb3326c5a 100644 --- a/application/Device/DeviceController.php +++ b/application/Device/DeviceController.php @@ -349,6 +349,12 @@ class DeviceController extends mfBaseController case "deviceoltserviceportrefresh": $this->deviceoltserviceportrefresh($ip); break; + case "getZabbixConsolidationData": + $this->getZabbixConsolidationData(); + break; + case "updateZabbixCoordinates": + $this->updateZabbixCoordinates(); + break; default: $return = false; } @@ -512,4 +518,266 @@ class DeviceController extends mfBaseController return $data ?? []; } + + protected function zabbixConsolidationAction() + { + if (!$this->me->is(["Admin"])) { + $this->layout()->setFlash("Keine Berechtigung", "error"); + $this->redirect("Dashboard"); + } + + $JSGlobals = [ + "BASE_URL" => self::getUrl(""), + "API_URL" => self::getUrl("Device", "api"), + "DEVICE_TYPES" => DevicetypeModel::getAll(), + "POPS" => PopModel::getAll(), + "PAGE_TITLE" => "Zabbix Consolidation", + "PATH" => [ + ["text" => MFAPPNAME_SLUG, "href" => self::getUrl("Dashboard")], + ["text" => "Devices", "href" => self::getUrl("Device")], + ["text" => "Zabbix Consolidation", "href" => self::getUrl("Device", "zabbixConsolidation")] + ] + ]; + + $this->layout()->set("vueViewName", "DeviceZabbixConsolidation"); + $this->layout()->set("JSGlobals", $JSGlobals); + $this->layout()->setTemplate("VueViews/Vue"); + } + + protected function getZabbixConsolidationData() { + $zabbix = new Zabbix(ZABBIX_API_URL, ZABBIX_API_KEY); + $theToolDevices = DeviceModel::getAll(); + + $excludedGroupIds = [2, 4, 6, 7]; + + $allZabbixHosts = $zabbix->getAllHostsWithDetails(); + + $zabbixHosts = array_filter($allZabbixHosts, function($host) use ($excludedGroupIds) { + $hostGroupIds = array_column($host['hostgroups'] ?? [], 'groupid'); + if (empty($hostGroupIds)) { + return true; + } + return empty(array_intersect($hostGroupIds, $excludedGroupIds)); + }); + + $theToolDevicesByName = []; + $theToolDevicesByIp = []; + foreach ($theToolDevices as $device) { + if ($device->ip === '127.0.0.1') continue; + $theToolDevicesByName[$device->name] = $device; + if (!empty($device->ip)) { + $theToolDevicesByIp[$device->ip] = $device; + } + } + + $zabbixHostsByVisibleName = []; + $zabbixHostsByIp = []; + foreach ($zabbixHosts as $host) { + if (!empty($host['name'])) { + $zabbixHostsByVisibleName[$host['name']] = $host; + } + if (!empty($host['host'])) { + $zabbixHostsByIp[$host['host']] = $host; + } + } + + $results = [ + 'mismatchedNames' => [], + 'inTheToolOnly' => [], + 'inZabbixOnly' => [], + 'noSnmp' => [], + 'noCoordsInZabbix' => [], + 'mismatchedCoords' => [], + ]; + + foreach ($theToolDevices as $ttDevice) { + if ($ttDevice->ip === '127.0.0.1') continue; + + $deviceDetails = new Device($ttDevice->id); + $theToolData = ['id' => $deviceDetails->id, 'data' => (array) $deviceDetails->data]; + if ($deviceDetails->pop && $deviceDetails->pop->id) { + $theToolData['data']['pop'] = (array) $deviceDetails->pop->data; + } + + if (!isset($zabbixHostsByVisibleName[$ttDevice->name]) && !isset($zabbixHostsByIp[$ttDevice->ip])) { + $results['inTheToolOnly'][] = $theToolData; + } else { + if (isset($zabbixHostsByVisibleName[$ttDevice->name])) { + $zbxHost = $zabbixHostsByVisibleName[$ttDevice->name]; + $ttLat = (float)($theToolData['data']['pop']['gps_lat'] ?? $theToolData['data']['gps_lat'] ?? 0); + $ttLon = (float)($theToolData['data']['pop']['gps_long'] ?? $theToolData['data']['gps_long'] ?? 0); + $zbxLat = (float)($zbxHost['inventory']['location_lat'] ?? 0); + $zbxLon = (float)($zbxHost['inventory']['location_lon'] ?? 0); + + if (($ttLat || $ttLon || $zbxLat || $zbxLon) && (abs($ttLat - $zbxLat) > 0.0001 || abs($ttLon - $zbxLon) > 0.0001)) { + $results['mismatchedCoords'][] = ['theTool' => $theToolData, 'zabbix' => $zbxHost]; + } + } + } + } + + foreach ($zabbixHosts as $zbxHost) { + if (empty($zbxHost['name'])) continue; + + if (!isset($theToolDevicesByName[$zbxHost['name']])) { + $results['inZabbixOnly'][] = $zbxHost; + } + + $hasSnmp = false; + if (!empty($zbxHost['parentTemplates'])) { + foreach ($zbxHost['parentTemplates'] as $template) { + if (stripos($template['name'], 'icmp ping') === false) { + $hasSnmp = true; + break; + } + } + } + if (!$hasSnmp) { + $results['noSnmp'][] = $zbxHost; + } + + $zbxLat = (float)($zbxHost['inventory']['location_lat'] ?? 0); + $zbxLon = (float)($zbxHost['inventory']['location_lon'] ?? 0); + + $theToolDeviceForCoords = $theToolDevicesByName[$zbxHost['name']] ?? null; + $theToolDataForCoords = null; + if($theToolDeviceForCoords){ + $deviceDetails = new Device($theToolDeviceForCoords->id); + $theToolDataForCoords = ['id' => $deviceDetails->id, 'data' => (array) $deviceDetails->data]; + if ($deviceDetails->pop && $deviceDetails->pop->id) { + $theToolDataForCoords['data']['pop'] = (array) $deviceDetails->pop->data; + } + } + + if ($zbxLat == 0 && $zbxLon == 0) { + $results['noCoordsInZabbix'][] = [ + 'zabbix' => $zbxHost, + 'theTool' => $theToolDataForCoords + ]; + } + } + + foreach ($zabbixHosts as $zbxHost) { + if(isset($theToolDevicesByIp[$zbxHost['host']])) { + $ttDeviceFromIp = new Device($theToolDevicesByIp[$zbxHost['host']]->id); + $theToolData = ['id' => $ttDeviceFromIp->id, 'data' => $ttDeviceFromIp->data]; + if ($ttDeviceFromIp->pop && $ttDeviceFromIp->pop->id) { + $theToolData['data']['pop'] = $ttDeviceFromIp->pop->data; + } + + if($ttDeviceFromIp->name !== $zbxHost['name']){ + $results['mismatchedNames'][] = ['theTool' => $theToolData, 'zabbix' => $zbxHost]; + } + } + } + + self::returnJson($results); + } + + protected function getZabbixTemplatesAction() { + $zabbix = new Zabbix(ZABBIX_API_URL, ZABBIX_API_KEY); + $templateNames = [ + "ICMP Ping", + "1_Default XINON Template", + "1_XINON Extreme EXOS by SNMP", + "1_MIKROTIK_TEMPLATE" + ]; + $templates = $zabbix->getTemplatesByNames($templateNames); + $formattedTemplates = array_map(function($template) { + return ['value' => $template['templateid'], 'text' => $template['name']]; + }, $templates); + + self::returnJson($formattedTemplates); + } + + protected function createZabbixHostAction() { + $this->postData = json_decode(file_get_contents('php://input'), true); + + $deviceId = $this->postData['deviceId'] ?? null; + $templateId = $this->postData['templateId'] ?? null; + if (!$deviceId || !$templateId) { + self::sendError("Device ID or Template ID is missing."); + } + + $device = DeviceModel::getOne($deviceId); + if (!$device || !$device->id) { + self::sendError("Device not found in TheTool."); + } + + $zabbix = new Zabbix(ZABBIX_API_URL, ZABBIX_API_KEY); + $groupName = "Discovered hosts"; + + $existingHosts = $zabbix->getHosts($device->name, $device->ip); + if (!empty($existingHosts)) { + self::sendError("Host with this name or IP already exists in Zabbix."); + } + + $groupId = $zabbix->getHostGroupIdByName($groupName); + if (!$groupId) { + self::sendError("Host group '$groupName' not found in Zabbix."); + } + + $result = $zabbix->createHost($device->name, $device->ip, $groupId, $templateId); + + if (isset($result['hostids'])) { + $device->zabbix_host_id = $result['hostids'][0]; + $device->save(); + self::returnJson(['success' => true, 'message' => 'Host successfully created in Zabbix and linked in TheTool.']); + } else { + self::sendError("Failed to create host in Zabbix: " . json_encode($result['error'] ?? 'Unknown error')); + } + } + + protected function updateTheToolCoordinatesAction() { + $deviceId = $this->postData['deviceId'] ?? null; + $lat = $this->postData['lat'] ?? null; + $lon = $this->postData['lon'] ?? null; + + if (!$deviceId || $lat === null || $lon === null) { + self::sendError("Device ID or coordinates are missing."); + } + + $device = new Device($deviceId); + if (!$device->id) { + self::sendError("Device not found."); + } + + if ($device->pop_id) { + self::sendError("Koordinaten können nicht geändert werden, da sie vom POP-Standort geerbt werden."); + } + + $device->update([ + 'gps_lat' => $lat, + 'gps_long' => $lon + ]); + $device->save(); + + self::returnJson(['success' => true, 'message' => 'TheTool coordinates updated successfully.']); + } + + protected function updateZabbixCoordinates() { + $this->postData = json_decode(file_get_contents('php://input'), true); + $hostId = $this->postData['hostId'] ?? null; + $lat = $this->postData['lat'] ?? null; + $lon = $this->postData['lon'] ?? null; + + if (!$hostId || $lat === null || $lon === null) { + self::sendError("Host ID or coordinates are missing."); + } + + $zabbix = new Zabbix(ZABBIX_API_URL, ZABBIX_API_KEY); + $inventoryData = [ + 'location_lat' => (string)$lat, + 'location_lon' => (string)$lon, + ]; + + $result = $zabbix->updateHostInventory($hostId, $inventoryData); + + if (isset($result['hostids'])) { + self::returnJson(['success' => true, 'message' => 'Coordinates updated successfully in Zabbix.']); + } else { + self::sendError("Failed to update coordinates in Zabbix: " . json_encode($result['error'] ?? 'Unknown error')); + } + } + } diff --git a/lib/Zabbix/Zabbix.php b/lib/Zabbix/Zabbix.php index cbad9c98e..cff576073 100644 --- a/lib/Zabbix/Zabbix.php +++ b/lib/Zabbix/Zabbix.php @@ -113,4 +113,83 @@ class Zabbix { )); return $response['result']; } + + public function getAllHostsWithDetails() { + $response = $this->zabbixRequest('host.get', [ + 'output' => ['hostid', 'host', 'name', 'status'], + 'selectInventory' => ['location_lat', 'location_lon'], + 'selectParentTemplates' => ['templateid', 'name'], + 'selectHostGroups' => 'extend' // This is the new line + ]); + return $response['result'] ?? []; + } + + public function updateHostInventory($hostId, $inventoryData) { + // First, get the current inventory to avoid overwriting existing fields + $hostResponse = $this->zabbixRequest('host.get', [ + 'hostids' => $hostId, + 'selectInventory' => 'extend' + ]); + + $currentInventory = $hostResponse['result'][0]['inventory'] ?? []; + + // Merge new coordinates into the existing inventory + $newInventory = array_merge($currentInventory, $inventoryData); + + $params = [ + 'hostid' => $hostId, + 'inventory_mode' => 0, // Set to manual mode + 'inventory' => $newInventory + ]; + + $response = $this->zabbixRequest('host.update', $params); + return $response['result'] ?? ['error' => $response['error'] ?? 'Unknown error']; + } + public function getTemplateIdByName($templateName) { + $response = $this->zabbixRequest('template.get', [ + 'output' => ['templateid'], + 'filter' => ['host' => [$templateName]] + ]); + return $response['result'][0]['templateid'] ?? null; + } + + public function getTemplatesByNames(array $templateNames) { + $response = $this->zabbixRequest('template.get', [ + 'output' => ['templateid', 'name'], + 'filter' => ['host' => $templateNames] + ]); + return $response['result'] ?? []; + } + + + public function createHost($visibleName, $ip, $groupId, $templateId) { + $params = [ + 'host' => $ip, // Technical name is the IP + 'name' => $visibleName, // Visible name + 'interfaces' => [ + [ + 'type' => 1, // Agent interface + 'main' => 1, + 'useip' => 1, + 'ip' => $ip, + 'dns' => '', + 'port' => '10050' + ] + ], + 'groups' => [['groupid' => $groupId]], + 'templates' => [['templateid' => $templateId]] + ]; + + $response = $this->zabbixRequest('host.create', $params); + return $response['result'] ?? ['error' => $response['error'] ?? 'Unknown error']; + } + + public function getHostGroupIdByName($groupName) { + $response = $this->zabbixRequest('hostgroup.get', [ + 'output' => ['groupid'], + 'filter' => ['name' => [$groupName]] + ]); + return $response['result'][0]['groupid'] ?? null; + } + } diff --git a/public/js/pages/DeviceZabbixConsolidation/DeviceZabbixConsolidation.css b/public/js/pages/DeviceZabbixConsolidation/DeviceZabbixConsolidation.css new file mode 100644 index 000000000..41c1246df --- /dev/null +++ b/public/js/pages/DeviceZabbixConsolidation/DeviceZabbixConsolidation.css @@ -0,0 +1,106 @@ +.consolidation-container { + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.consolidation-section .card-header { + font-weight: bold; + padding: .75rem 1.25rem; + border-bottom: 1px solid #dee2e6; + cursor: pointer; + display: flex; + justify-content: space-between; + align-items: center; +} + +.consolidation-section .card-header .fas { + transition: transform 0.2s ease-in-out; +} + +.consolidation-section .card-header.collapsed .fa-chevron-down { + transform: rotate(-90deg); +} + +.consolidation-section .card-body { + padding: 1.25rem; +} + +.consolidation-section .list-group-item { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 0.5rem; +} + +.consolidation-section .device-info { + flex-grow: 1; +} + +.consolidation-section .actions { + flex-shrink: 0; + margin-left: 1rem; + display: flex; + gap: 0.5rem; +} + +#map-modal .modal-body { + height: 60vh; + padding: 0; + position: relative; +} + +#map-modal .modal-body > .form-group { + position: absolute; + top: 10px; + left: 50px; + z-index: 1001; + background-color: white; + padding: 5px 10px; + border-radius: 5px; +} + +#leaflet-map { + width: 100%; + height: 100%; +} + +/* Leaflet GeoSearch styles */ +#map-modal .leaflet-control-geosearch a.glass, +#map-modal .leaflet-control-geosearch .results a.glass { + border-radius: 4px 0 0 4px; +} +#map-modal .leaflet-control-geosearch form input { + padding: 0 10px; + height: 30px; + border: 1px solid #ccc; +} +#map-modal .leaflet-control-geosearch .results { + max-height: 40vh; +} + +.coords-display { + position: absolute; + bottom: 10px; + left: 10px; + background: rgba(255,255,255,0.8); + padding: 5px 10px; + border-radius: 4px; + z-index: 1000; + font-family: monospace; +} + +.bg-purple { + background-color: #6f42c1 !important; +} + +.device-info a { + margin-left: 8px; + color: #6c757d; + font-size: 0.9em; +} + +.device-info a:hover { + color: #343a40; +} \ No newline at end of file diff --git a/public/js/pages/DeviceZabbixConsolidation/DeviceZabbixConsolidation.js b/public/js/pages/DeviceZabbixConsolidation/DeviceZabbixConsolidation.js new file mode 100644 index 000000000..c9d0880db --- /dev/null +++ b/public/js/pages/DeviceZabbixConsolidation/DeviceZabbixConsolidation.js @@ -0,0 +1,346 @@ +Vue.component('device-zabbix-consolidation', { + template: ` + +
    +
    +

    Lade und vergleiche Daten von TheTool und Zabbix...

    +
    +
    + + + + +
    +
    Lat: {{ mapModal.markerCoords ? mapModal.markerCoords.lat.toFixed(6) : 'N/A' }}, Lng: {{ mapModal.markerCoords ? mapModal.markerCoords.lng.toFixed(6) : 'N/A' }}
    +
    + +
    +
    + + +

    Bitte wählen Sie ein Template für das Gerät {{ zabbixCreateModal.deviceName }}.

    + +
    +
    +
    + `, + data() { + return { + loading: true, + window: window, + zabbixUrl: 'https://monitoring.xinon.at', + results: {}, + sections: { + inZabbixOnly: { title: 'Geräte in Zabbix, aber nicht in TheTool', isOpen: false, headerClass: 'bg-warning' }, + inTheToolOnly: { title: 'Geräte in TheTool, aber nicht in Zabbix', isOpen: false, headerClass: 'bg-info' }, + mismatchedNames: { title: 'Abweichende Namen (IP-basiert)', isOpen: false, headerClass: 'bg-secondary text-white' }, + noSnmp: { title: 'Geräte in Zabbix ohne SNMP Template', isOpen: false, headerClass: 'bg-light' }, + mismatchedCoords: { title: 'Abweichende Koordinaten', isOpen: false, headerClass: 'bg-danger text-white' }, + noCoordsInZabbix: { title: 'Geräte in Zabbix ohne Koordinaten', isOpen: false, headerClass: 'bg-purple text-white' } + }, + mapModal: { + show: false, + loading: false, + host: null, + theToolDevice: null, + map: null, + marker: null, + markerCoords: null, + updateTheToolCoords: false, + deviceHasPop: false + }, + zabbixCreateModal: { + show: false, + loading: false, + loadingId: null, + deviceId: null, + deviceName: '', + templates: [], + selectedTemplateId: null + } + }; + }, + async mounted() { + await this.refreshData(); + }, + methods: { + toggleSection(key) { + this.sections[key].isOpen = !this.sections[key].isOpen; + }, + createDeviceFromZabbix(host) { + const params = new URLSearchParams(); + params.append('name', host.name); + params.append('ip', host.host); + window.open(`${window.TT_CONFIG.BASE_URL}/Device/add?${params.toString()}`, '_blank'); + }, + async openCreateZabbixHostModal(deviceId) { + const device = this.results.inTheToolOnly.find(d => d.id === deviceId); + if (!device) return; + + this.zabbixCreateModal.deviceId = deviceId; + this.zabbixCreateModal.deviceName = device.data.name; + this.zabbixCreateModal.loading = true; + this.zabbixCreateModal.show = true; + + try { + const response = await axios.get(`${window.TT_CONFIG.API_URL}?do=getZabbixTemplates`); + this.zabbixCreateModal.templates = response.data; + } catch (e) { + window.notify('error', 'Fehler beim Laden der Zabbix Templates.'); + this.zabbixCreateModal.show = false; + } finally { + this.zabbixCreateModal.loading = false; + } + }, + async submitCreateZabbixHost() { + if (!this.zabbixCreateModal.selectedTemplateId) { + return window.notify('error', 'Bitte wählen Sie ein Template aus.'); + } + this.zabbixCreateModal.loading = true; + this.zabbixCreateModal.loadingId = this.zabbixCreateModal.deviceId; + try { + const response = await axios.post(`${window.TT_CONFIG.API_URL}?do=createZabbixHost`, { + deviceId: this.zabbixCreateModal.deviceId, + templateId: this.zabbixCreateModal.selectedTemplateId + }); + if (response.data.success) { + window.notify('success', response.data.message); + this.zabbixCreateModal.show = false; + await this.refreshData(); + } else { + window.notify('error', response.data.message); + } + } catch (e) { + window.notify('error', 'Ein Fehler ist aufgetreten.'); + } finally { + this.zabbixCreateModal.loading = false; + this.zabbixCreateModal.loadingId = null; + this.zabbixCreateModal.deviceId = null; + this.zabbixCreateModal.selectedTemplateId = null; + } + }, + openMapModal(host, theToolDevice = null) { + this.mapModal.host = host; + this.mapModal.theToolDevice = theToolDevice; + this.mapModal.deviceHasPop = !!(theToolDevice && theToolDevice.data && theToolDevice.data.pop_id); + this.mapModal.updateTheToolCoords = false; + this.mapModal.show = true; + this.$nextTick(this.initMap); + }, + async initMap() { + if (typeof L === 'undefined' || typeof window.GeoSearch === 'undefined') { + await this.loadScripts(); + } + if (this.mapModal.map) { + this.mapModal.map.remove(); + } + + const ttDevice = this.mapModal.theToolDevice; + const zbxHost = this.mapModal.host; + let initialLat = 47.0707; // Default to Graz + let initialLon = 15.4395; + let zoom = 13; + + const ttDeviceLat = ttDevice?.data?.pop?.gps_lat ?? ttDevice?.data?.gps_lat; + const ttDeviceLon = ttDevice?.data?.pop?.gps_long ?? ttDevice?.data?.gps_long; + + if (ttDevice && parseFloat(ttDeviceLat) && parseFloat(ttDeviceLon)) { + initialLat = parseFloat(ttDeviceLat); + initialLon = parseFloat(ttDeviceLon); + zoom = 16; + } else if (zbxHost.inventory && parseFloat(zbxHost.inventory.location_lat) && parseFloat(zbxHost.inventory.location_lon)) { + initialLat = parseFloat(zbxHost.inventory.location_lat); + initialLon = parseFloat(zbxHost.inventory.location_lon); + zoom = 16; + } + + this.mapModal.map = L.map(this.$refs.leafletMap).setView([initialLat, initialLon], zoom); + L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap contributors' + }).addTo(this.mapModal.map); + + const searchControl = new window.GeoSearch.GeoSearchControl({ + provider: new window.GeoSearch.OpenStreetMapProvider(), + style: 'bar', + showMarker: false, + autoClose: true, + }); + this.mapModal.map.addControl(searchControl); + + this.mapModal.marker = L.marker([initialLat, initialLon], { draggable: true }).addTo(this.mapModal.map); + this.mapModal.markerCoords = this.mapModal.marker.getLatLng(); + + this.mapModal.marker.on('dragend', (event) => this.mapModal.markerCoords = event.target.getLatLng()); + this.mapModal.map.on('click', (e) => { + this.mapModal.marker.setLatLng(e.latlng); + this.mapModal.markerCoords = e.latlng; + }); + this.mapModal.map.on('geosearch/showlocation', (result) => { + const latLng = L.latLng(result.location.y, result.location.x); + this.mapModal.marker.setLatLng(latLng); + this.mapModal.markerCoords = latLng; + }); + + setTimeout(() => this.mapModal.map.invalidateSize(), 100); + }, + async saveCoordinates() { + this.mapModal.loading = true; + const coords = this.mapModal.marker.getLatLng(); + let allSuccess = true; + + try { + const zabbixResponse = await axios.post(`${window.TT_CONFIG.API_URL}?do=updateZabbixCoordinates`, { + hostId: this.mapModal.host.hostid, + lat: coords.lat, + lon: coords.lng + }); + if (!zabbixResponse.data.success) { + allSuccess = false; + window.notify('error', 'Fehler beim Speichern der Zabbix-Koordinaten: ' + zabbixResponse.data.message); + } + } catch (error) { + allSuccess = false; + window.notify('error', 'Fehler beim Speichern der Zabbix-Koordinaten.'); + } + + if (this.mapModal.updateTheToolCoords && this.mapModal.theToolDevice && allSuccess) { + try { + const theToolResponse = await axios.post(`${window.TT_CONFIG.API_URL}?do=updateTheToolCoordinates`, { + deviceId: this.mapModal.theToolDevice.id, + lat: coords.lat, + lon: coords.lng + }); + if (!theToolResponse.data.success) { + allSuccess = false; + window.notify('error', 'Fehler beim Speichern der TheTool-Koordinaten: ' + theToolResponse.data.message); + } + } catch (e) { + allSuccess = false; + window.notify('error', 'Fehler beim Speichern der TheTool-Koordinaten.'); + } + } + + if(allSuccess) { + window.notify('success', 'Koordinaten erfolgreich gespeichert.'); + this.mapModal.show = false; + await this.refreshData(); + } + + this.mapModal.loading = false; + }, + async refreshData() { + this.loading = true; + try { + const response = await axios.post(`${window.TT_CONFIG.API_URL}?do=getZabbixConsolidationData`); + this.results = response.data; + } catch (error) { + window.notify('error', 'Fehler beim Aktualisieren der Daten.'); + } finally { + this.loading = false; + } + }, + loadScripts() { + const scripts = [ + { type: 'link', url: 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css' }, + { type: 'link', url: 'https://unpkg.com/leaflet-geosearch@3.0.0/dist/geosearch.css' }, + { type: 'script', url: 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js' }, + { type: 'script', url: 'https://unpkg.com/leaflet-geosearch@latest/dist/bundle.min.js' }, + ]; + + const promises = scripts.map(s => new Promise((resolve, reject) => { + let el; + if (document.querySelector(`[href="${s.url}"], [src="${s.url}"]`)) { + resolve(); return; + } + if (s.type === 'script') { + el = document.createElement('script'); + el.src = s.url; el.async = false; el.onload = resolve; el.onerror = reject; + } else { + el = document.createElement('link'); + el.rel = 'stylesheet'; el.href = s.url; resolve(); + } + if (el) document.head.appendChild(el); else reject(); + })); + return Promise.all(promises); + }, + } +}); \ No newline at end of file