needlogin = true; $me = new User(); $me->loadMe(); $this->layout()->set("me", $me); $this->me = $me; if (!$this->me->is("Admin")) { http_response_code(403); self::returnJson(['success' => false, 'message' => 'Permission denied']); die(); } if (defined('ZABBIX_API_URL') && defined('ZABBIX_API_KEY') && ZABBIX_API_URL && ZABBIX_API_KEY) { $this->zabbix = new Zabbix($this->ZABBIX_API_URL, $this->ZABBIX_API_KEY); } else { http_response_code(500); self::returnJson(['error' => 'Zabbix API is not configured on the server.']); die(); } $this->postData = json_decode(file_get_contents('php://input'), true) ?? []; } protected function listInterfacesAction() { $hostId = $this->request->hostId; $items = $this->zabbix->getHostInterfaceItems($hostId); $interfaces = []; foreach ($items as $item) { $baseName = preg_replace('/:\s*Bits\s*(sent|received)$/i', '', $item['name']); $direction = str_contains(strtolower($item['name']), 'received') ? 'rx' : 'tx'; if (!isset($interfaces[$baseName])) { $interfaces[$baseName] = ['name' => $baseName, 'rx' => null, 'tx' => null]; } $interfaces[$baseName][$direction] = $item; } $sortedInterfaces = array_values($interfaces); usort($sortedInterfaces, fn($a, $b) => strcmp($a['name'], $b['name'])); self::returnJson($sortedInterfaces); } protected function interfaceDataAction() { $itemIds = $this->postData['itemIds'] ?? []; if (empty($itemIds)) { self::returnJson([]); return; } $timeRange = $this->postData['timeRange'] ?? '24h'; $time_from = strtotime('-' . str_replace(['m', 'h', 'd'], [' minutes', ' hours', ' days'], $timeRange)); $params = [ 'itemids' => $itemIds, 'output' => 'extend', 'history' => 3, // Type of history: float 'sortfield' => 'clock', 'sortorder' => 'ASC', 'time_from' => $time_from, ]; $history = $this->zabbix->zabbixRequest('history.get', $params)['result'] ?? []; $historyByItemId = []; foreach ($history as $point) { $historyByItemId[$point['itemid']][] = [ 'x' => intval($point['clock']) * 1000, 'y' => round(floatval($point['value']) / 1000000, 2) // Mbps ]; } self::returnJson($historyByItemId); } protected function generalDataAction() { $hostId = $this->request->hostId; $data = $this->zabbix->getOverviewData($hostId); self::returnJson($data); } protected function getProblemsAction() { $hostId = $this->request->hostId; $currentProblems = $this->zabbix->zabbixRequest('problem.get', [ 'hostids' => $hostId, 'output' => 'extend', 'recent' => true, 'sortfield' => ['eventid'], 'sortorder' => 'DESC' ])['result'] ?? []; $resolvedProblems = $this->zabbix->getResolvedProblems($hostId, strtotime('-7 days')); self::returnJson([ 'current' => $currentProblems, 'resolved' => $resolvedProblems ]); } protected function getConfigurationDataAction() { $hostId = $this->request->hostId; $host = $this->zabbix->getHostWithInterfaces($hostId); if (!$host) { self::returnJson(['error' => 'Host not found.']); return; } $snmpInterface = null; foreach ($host['interfaces'] as $iface) { if ($iface['type'] == '2') { // SNMP type $snmpInterface = $iface; break; } } $opStatusItems = $this->zabbix->getInterfaceOperationalStatusItems($hostId); $allTriggers = $this->zabbix->getTriggersForHostByDescription($hostId, "Interface "); $triggerMap = []; foreach ($allTriggers as $trigger) { $triggerMap[$trigger['description']] = $trigger; } $interfaceAlarms = []; foreach ($opStatusItems as $item) { $expectedDescription = "Interface " . $item['name'] . " is down on " . $host['name']; $trigger = $triggerMap[$expectedDescription] ?? null; $interfaceAlarms[] = [ 'itemid' => $item['itemid'], 'name' => $item['name'], 'key' => $item['key_'], 'isAlarmed' => !is_null($trigger), 'triggerId' => $trigger['triggerid'] ?? null ]; } self::returnJson([ 'snmp' => $snmpInterface, 'interfaces' => $interfaceAlarms ]); } protected function updateSnmpAction() { $interfaceId = $this->postData['interfaceId'] ?? null; $details = $this->postData['details'] ?? null; if (!$interfaceId || !$details) { http_response_code(400); self::returnJson(['error' => 'Missing required parameters.']); return; } $result = $this->zabbix->updateHostInterface($interfaceId, $details); self::returnJson($result); } protected function updateInterfaceAlarmAction() { $hostId = $this->postData['hostId']; $item = $this->postData['item']; $enabled = $this->postData['enabled']; $host = $this->zabbix->getHostById($hostId)[0] ?? null; if (!$host) { self::returnJson(['error' => 'Host not found.']); return; } $description = "Interface " . $item['name'] . " is down on " . $host['name']; if ($enabled) { $expression = "last(/".$host['host']."/".$item['key'].")=2"; $result = $this->zabbix->createInterfaceLinkDownTrigger($expression, $description); } else { $triggers = $this->zabbix->getTriggersForHostByDescription($hostId, $description); $triggerIds = array_column($triggers, 'triggerid'); $result = $this->zabbix->deleteTriggers($triggerIds); } self::returnJson($result); } protected function getReportDataAction() { $hostId = $this->request->hostId; $timeRange = $this->request->timeRange ?? '7d'; $time_from = strtotime('-' . str_replace(['d'], [' days'], $timeRange)); // Step 1: Fetch all interface-related items (traffic and speed) in a single API call. // We include 'value_type' to handle different history types correctly. $items = $this->zabbix->zabbixRequest('item.get', [ 'hostids' => $hostId, 'output' => ['itemid', 'name', 'key_', 'value_type'], 'search' => ['key_' => ['net.if.in', 'net.if.out', 'net.if.speed']], 'searchByAny' => true, 'sortfield' => 'name' ])['result'] ?? []; // Step 2: Organize items and group them for efficient processing. $interfaces = []; $trafficItems = []; // Will hold item info for both rx and tx. $speedItemsByType = []; $speedItemMap = []; foreach ($items as $item) { $key = $item['key_']; if (str_contains($key, 'net.if.in') || str_contains($key, 'net.if.out')) { $baseName = preg_replace('/:\s*Bits\s*(sent|received)$/i', '', $item['name']); $direction = str_contains($key, 'net.if.in') ? 'rx' : 'tx'; if (!isset($interfaces[$baseName])) { $interfaces[$baseName] = ['name' => $baseName, 'rx_item' => null, 'tx_item' => null, 'speed' => null]; } $interfaces[$baseName][$direction . '_item'] = $item; $trafficItems[$item['itemid']] = $item; } elseif (str_contains($key, 'net.if.speed')) { $baseName = preg_replace('/:\s*Interface\s*|\s*speed$/i', '', $item['name']); $value_type = (int)$item['value_type']; $speedItemsByType[$value_type][] = $item['itemid']; $speedItemMap[$item['itemid']] = $baseName; } } // Step 3: Aggressively fetch the last known speed for all interfaces. // We query history with a larger limit to find the value even if it's not recent. foreach ($speedItemsByType as $type => $itemIds) { $historyResult = $this->zabbix->zabbixRequest('history.get', [ 'itemids' => $itemIds, 'history' => $type, 'output' => ['itemid', 'value'], 'sortfield' => 'clock', 'sortorder' => 'DESC', 'limit' => count($itemIds) * 5 // Increase limit to better ensure finding a value for each item ])['result'] ?? []; $latestForType = []; foreach ($historyResult as $point) { if (!isset($latestForType[$point['itemid']])) { $latestForType[$point['itemid']] = $point; $baseName = $speedItemMap[$point['itemid']] ?? null; if ($baseName && isset($interfaces[$baseName])) { $interfaces[$baseName]['speed'] = (float)$point['value']; } } } } // Step 4: Attempt to fetch trend data for all traffic items at once. $trafficItemIds = array_keys($trafficItems); $trends = $this->zabbix->getTrends($trafficItemIds, $time_from); $trendsByItemId = []; foreach ($trends as $trend) { $trendsByItemId[$trend['itemid']][] = $trend; } // Step 5: Build the report, using trends first and falling back to raw history if trends are unavailable. $report = []; foreach ($interfaces as $iface) { $rx_item = $iface['rx_item']; $tx_item = $iface['tx_item']; $speed = $iface['speed']; // This function calculates statistics from either trend data or raw history data. $calcStats = function($item, $speed, $trendData) use ($time_from) { if (!$item) return ['avg' => 0, 'max' => 0, 'usage' => 0]; $values = []; $avg = 0; $max = 0; if (!empty($trendData)) { // Method 1: Use efficient trend data if available. $avg = array_sum(array_column($trendData, 'value_avg')) / count($trendData); $max = max(array_column($trendData, 'value_max')); } else { // Method 2 (Fallback): Fetch raw history if trends are missing. $history = $this->zabbix->zabbixRequest('history.get', [ 'itemids' => [$item['itemid']], 'history' => (int)$item['value_type'], 'time_from' => $time_from, 'output' => ['value'] ])['result'] ?? []; if (!empty($history)) { $values = array_column($history, 'value'); $avg = array_sum($values) / count($values); $max = max($values); } } $usage = ($speed > 0) ? ($avg / $speed) * 100 : 0; return [ 'avg' => round($avg / 1000000, 2), // bps to Mbps 'max' => round($max / 1000000, 2), // bps to Mbps 'usage' => round($usage, 2) ]; }; $report[] = [ 'name' => $iface['name'], 'speed' => $speed !== null ? round($speed / 1000000) : 'N/A', // bps to Mbps 'rx' => $calcStats($rx_item, $speed, $trendsByItemId[$rx_item['itemid']] ?? []), 'tx' => $calcStats($tx_item, $speed, $trendsByItemId[$tx_item['itemid']] ?? []) ]; } usort($report, fn($a, $b) => strnatcmp($a['name'], $b['name'])); self::returnJson($report); } protected function liveDataAction() { $itemId = $this->request->itemId; if(empty($itemId)) { http_response_code(400); self::returnJson(['error' => 'Item ID is required.']); return; } $this->zabbix->createTask($itemId); sleep(3); $history = $this->zabbix->getItemValues([$itemId], 1); if(empty($history)) { self::returnJson(null); return; } $point = $history[0]; $formattedPoint = [ 'x' => intval($point['clock']) * 1000, 'y' => round(floatval($point['value']) / 1000000, 2) ]; self::returnJson($formattedPoint); } public function liveGraphPageAction() { $this->layout(false); $this->layout()->set('API_BASE_URL', self::getUrl("DeviceMonitoring")); $this->layout()->setTemplate('Device/LiveGraph'); } }