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);
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
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...
+
+
+
+
+
+
+
+
+ - Keine Abweichungen gefunden.
+
+ -
+
+
{{ host.name }} ({{ host.host }})
+
+
+
+
+
+ -
+
+
{{ item.data.name }} ({{ item.data.ip }})
+
+
+
+
+
+
+
+ -
+
+
IP: {{ item.zabbix.host }}
+
+
TheTool Name: {{ item.theTool.data.name }}
+
+
+
+
Zabbix Name: {{ item.zabbix.name }}
+
+
+
+
+
+ -
+
+
{{ host.name }} ({{ host.host }})
+
+
+
+
+ -
+
+
+
{{ item.theTool.data.name }}
+
+
+
+
TheTool: Lat: {{ item.theTool.data.pop ? item.theTool.data.pop.gps_lat : item.theTool.data.gps_lat || 'N/A' }}, Lon: {{ item.theTool.data.pop ? item.theTool.data.pop.gps_long : item.theTool.data.gps_long || 'N/A' }}
+
Zabbix: Lat: {{ item.zabbix.inventory.location_lat || 'N/A' }}, Lon: {{ item.zabbix.inventory.location_lon || 'N/A' }}
+
+
+
+
+ -
+
+
{{ item.zabbix.name }} ({{ item.zabbix.host }})
+
(Gerät in TheTool gefunden)
+
+
+
+ Koordinaten vom POP-Standort geerbt. Bitte im POP-Modul bearbeiten.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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