diff --git a/application/DeviceMonitoring/DeviceMonitoringController.php b/application/DeviceMonitoring/DeviceMonitoringController.php index 873c8479b..36f54c9b6 100644 --- a/application/DeviceMonitoring/DeviceMonitoringController.php +++ b/application/DeviceMonitoring/DeviceMonitoringController.php @@ -30,9 +30,6 @@ class DeviceMonitoringController extends mfBaseController $this->postData = json_decode(file_get_contents('php://input'), true) ?? []; } - /** - * Gets a list of all available interfaces, grouping Sent/Received items. - */ protected function listInterfacesAction() { $hostId = $this->request->hostId; @@ -54,9 +51,6 @@ class DeviceMonitoringController extends mfBaseController self::returnJson($sortedInterfaces); } - /** - * Gets historical data for a specific list of item IDs. - */ protected function interfaceDataAction() { $itemIds = $this->postData['itemIds'] ?? []; @@ -71,7 +65,7 @@ class DeviceMonitoringController extends mfBaseController $params = [ 'itemids' => $itemIds, 'output' => 'extend', - 'history' => 3, // Numeric (unsigned) + 'history' => 3, // Type of history: float 'sortfield' => 'clock', 'sortorder' => 'ASC', 'time_from' => $time_from, @@ -82,66 +76,246 @@ class DeviceMonitoringController extends mfBaseController foreach ($history as $point) { $historyByItemId[$point['itemid']][] = [ 'x' => intval($point['clock']) * 1000, - 'y' => round(floatval($point['value']) / 1000000, 2) + 'y' => round(floatval($point['value']) / 1000000, 2) // Mbps ]; } self::returnJson($historyByItemId); } - /** - * Gets general monitoring data (Uptime, Ping, Temp). - */ protected function generalDataAction() { $hostId = $this->request->hostId; - - $itemsToFetch = [ - 'ping' => $this->zabbix->getICMPItems($hostId), - 'uptime' => $this->zabbix->getUptimeItems($hostId), - ]; - - $itemIds = []; - $itemMap = []; - foreach ($itemsToFetch as $type => $items) { - if (!empty($items)) { - foreach($items as $item) { - $itemIds[] = $item['itemid']; - $itemMap[$item['itemid']] = ['type' => $type, 'name' => $item['name'], 'units' => $item['units']]; - } - } - } - - $values = []; - if(!empty($itemIds)) { - $history = $this->zabbix->getItemValues($itemIds, 1); - foreach($history as $h) { - $info = $itemMap[$h['itemid']]; - $values[$info['type']][] = ['name' => $info['name'], 'value' => $h['value'], 'clock' => $h['clock'], 'units' => $info['units']]; - } - } - - self::returnJson($values); + $data = $this->zabbix->getOverviewData($hostId); + self::returnJson($data); } - /** - * Gets Zabbix problems (triggers) for the host. - */ protected function getProblemsAction() { $hostId = $this->request->hostId; - $problems = $this->zabbix->zabbixRequest('problem.get', [ + $currentProblems = $this->zabbix->zabbixRequest('problem.get', [ 'hostids' => $hostId, 'output' => 'extend', - 'recent' => true, // Use boolean true + 'recent' => true, 'sortfield' => ['eventid'], 'sortorder' => 'DESC' ])['result'] ?? []; - self::returnJson($problems); + $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); } - /** - * Forces a Zabbix item check and returns the latest value for live graphs. - */ protected function liveDataAction() { $itemId = $this->request->itemId; if(empty($itemId)) { @@ -168,9 +342,6 @@ class DeviceMonitoringController extends mfBaseController self::returnJson($formattedPoint); } - /** - * Renders a dedicated HTML page for the live graph popup. - */ public function liveGraphPageAction() { $this->layout(false); $this->layout()->set('API_BASE_URL', self::getUrl("DeviceMonitoring")); diff --git a/lib/Zabbix/Zabbix.php b/lib/Zabbix/Zabbix.php index eaf19e6e2..6f055c0a0 100644 --- a/lib/Zabbix/Zabbix.php +++ b/lib/Zabbix/Zabbix.php @@ -1,32 +1,35 @@ url = $url; $this->apiKey = $apiKey; } - public function zabbixRequest($method, $params, $die = false) { + public function zabbixRequest($method, $params, $die = false) + { $data = array( 'jsonrpc' => '2.0', - 'method' => $method, - 'params' => $params, - 'id' => 1 + 'method' => $method, + 'params' => $params, + 'id' => 1 ); $options = array( 'http' => array( - 'header' => "Content-Type: application/json\r\n" . + 'header' => "Content-Type: application/json\r\n" . "Authorization: Bearer " . $this->apiKey . "\r\n", - 'method' => 'POST', - 'content' => json_encode($data) + 'method' => 'POST', + 'content' => json_encode($data), + 'timeout' => 30 ) ); - // var dump options and data and die if ($die) { var_dump($options); echo json_encode($data); @@ -39,25 +42,29 @@ class Zabbix { return json_decode($result, true); } - public function getHostById($hostId) { + public function getHostById($hostId) + { $response = $this->zabbixRequest('host.get', array( 'hostids' => $hostId )); return $response['result']; } - public function getItemValues($itemIds, $limit = 15) { + public function getItemValues($itemIds, $limit = 15) + { + if (empty($itemIds)) return []; $response = $this->zabbixRequest('history.get', array( - 'itemids' => $itemIds, - 'output' => 'extend', + 'itemids' => $itemIds, + 'output' => 'extend', 'sortfield' => 'clock', 'sortorder' => 'DESC', - 'limit' => $limit + 'limit' => $limit )); return $response['result']; } - public function getHosts($hostname = null, $ip = null) { + public function getHosts($hostname = null, $ip = null) + { if ($hostname) { $response = $this->zabbixRequest('host.get', array( 'search' => array('name' => array($hostname)) @@ -72,80 +79,84 @@ class Zabbix { return []; } - public function getHostInterfaceItems($hostId) { + public function getHostInterfaceItems($hostId) + { $response = $this->zabbixRequest('item.get', array( - 'hostids' => $hostId, - 'output' => ['itemid','name_resolved', 'key_', 'units'], - 'search' => ['name' => ["Bits received", "Bits sent"]], + 'hostids' => $hostId, + 'output' => ['itemid', 'name', 'key_', 'units'], + 'search' => ['name' => ["Bits received", "Bits sent"]], 'searchByAny' => true, - 'sortfield' => 'name' + 'sortfield' => 'name' )); return $response['result']; } - public function getICMPItems($hostId) { + public function getICMPItems($hostId) + { $response = $this->zabbixRequest('item.get', array( 'hostids' => $hostId, - 'search' => array('name' => array("ICMP")) + 'search' => array('name' => array("ICMP")) )); return $response['result']; } - public function getUptimeItems($hostId) { + public function getUptimeItems($hostId) + { $response = $this->zabbixRequest('item.get', array( 'hostids' => $hostId, - 'search' => array('name' => array("Uptime")) + 'search' => array('name' => array("Uptime")) )); return $response['result']; } - public function getHostInterfaces($hostIds) { - $response = $this->zabbixRequest('hostinterface.get', array('hostids' => $hostIds)); + public function getHostInterfaces($hostIds) + { + $response = $this->zabbixRequest('hostinterface.get', array('hostids' => $hostIds, 'output' => 'extend')); return $response['result']; } - public function createTask($itemid) { + public function createTask($itemid) + { $response = $this->zabbixRequest('task.create', array( - 'type' => 6, - 'request' => array( - 'itemid' => $itemid - ) + 'type' => 6, + 'request' => array('itemid' => $itemid) )); return $response['result']; } - public function getAllHostsWithDetails() { + public function getAllHostsWithDetails() + { $response = $this->zabbixRequest('host.get', [ - 'output' => ['hostid', 'host', 'name', 'status'], - 'selectInventory' => ['location_lat', 'location_lon'], + 'output' => ['hostid', 'host', 'name', 'status'], + 'selectInventory' => ['location_lat', 'location_lon'], 'selectParentTemplates' => ['templateid', 'name'], - 'selectHostGroups' => 'extend' // This is the new line + 'selectHostGroups' => 'extend' ]); return $response['result'] ?? []; } - public function updateHostInventory($hostId, $inventoryData) { - // First, get the current inventory to avoid overwriting existing fields + public function updateHostInventory($hostId, $inventoryData) + { $hostResponse = $this->zabbixRequest('host.get', [ - 'hostids' => $hostId, + 'hostids' => $hostId, 'selectInventory' => 'extend' ]); $currentInventory = $hostResponse['result'][0]['inventory'] ?? []; - - // Merge new coordinates into the existing inventory $newInventory = array_merge($currentInventory, $inventoryData); $params = [ - 'hostid' => $hostId, + 'hostid' => $hostId, 'inventory_mode' => 0, // Set to manual mode - 'inventory' => $newInventory + 'inventory' => $newInventory ]; $response = $this->zabbixRequest('host.update', $params); return $response['result'] ?? ['error' => $response['error'] ?? 'Unknown error']; } - public function getTemplateIdByName($templateName) { + + public function getTemplateIdByName($templateName) + { $response = $this->zabbixRequest('template.get', [ 'output' => ['templateid'], 'filter' => ['host' => [$templateName]] @@ -153,7 +164,8 @@ class Zabbix { return $response['result'][0]['templateid'] ?? null; } - public function getTemplatesByNames(array $templateNames) { + public function getTemplatesByNames(array $templateNames) + { $response = $this->zabbixRequest('template.get', [ 'output' => ['templateid', 'name'], 'filter' => ['host' => $templateNames] @@ -161,38 +173,39 @@ class Zabbix { return $response['result'] ?? []; } - - public function createHost($visibleName, $ip, $groupId, $templateIds) { - $templatesData = array_map(function($id) { + public function createHost($visibleName, $ip, $groupId, $templateIds) + { + $templatesData = array_map(function ($id) { return ['templateid' => $id]; }, $templateIds); $params = [ - 'host' => $ip, // Technical name is the IP - 'name' => $visibleName, // Visible name - 'interfaces' => [ // <-- Corrected structure + 'host' => $ip, + 'name' => $visibleName, + 'interfaces' => [ [ - 'type' => 2, // 2 for SNMP - 'main' => 1, - 'useip' => 1, - 'ip' => $ip, - 'dns' => '', - 'port' => '161', + 'type' => 2, // 2 for SNMP + 'main' => 1, + 'useip' => 1, + 'ip' => $ip, + 'dns' => '', + 'port' => '161', 'details' => [ - 'version' => 2, + 'version' => 2, 'community' => 'public_xinon' ] ] ], - 'groups' => [['groupid' => $groupId]], - 'templates' => $templatesData // Use the correctly formatted array + 'groups' => [['groupid' => $groupId]], + 'templates' => $templatesData ]; $response = $this->zabbixRequest('host.create', $params); return $response['result'] ?? ['error' => $response['error'] ?? 'Unknown error']; } - public function getHostGroupIdByName($groupName) { + public function getHostGroupIdByName($groupName) + { $response = $this->zabbixRequest('hostgroup.get', [ 'output' => ['groupid'], 'filter' => ['name' => [$groupName]] @@ -200,4 +213,133 @@ class Zabbix { return $response['result'][0]['groupid'] ?? null; } -} + public function getHostWithInterfaces($hostId) + { + $response = $this->zabbixRequest('host.get', [ + 'hostids' => $hostId, + 'output' => ['hostid', 'host', 'name'], + 'selectInterfaces' => 'extend' + ]); + return $response['result'][0] ?? null; + } + + public function getResolvedProblems($hostId, $time_from) + { + $response = $this->zabbixRequest('event.get', [ + 'hostids' => $hostId, + 'output' => 'extend', + 'select_acknowledges' => ['message'], + 'sortfield' => ['clock'], + 'sortorder' => 'DESC', + 'time_from' => $time_from, + 'object' => 0, + 'value' => 0 + ]); + return $response['result'] ?? []; + } + + public function updateHostInterface($interfaceId, $details) + { + $params = ['interfaceid' => $interfaceId, 'details' => $details]; + $response = $this->zabbixRequest('hostinterface.update', $params); + return $response['result'] ?? ['error' => $response['error'] ?? 'Unknown error']; + } + + public function getInterfaceOperationalStatusItems($hostId) + { + $response = $this->zabbixRequest('item.get', [ + 'hostids' => $hostId, + 'output' => ['itemid', 'name', 'key_'], + 'search' => ['key_' => 'net.if.status'], + 'sortfield' => 'name' + ]); + return $response['result'] ?? []; + } + + public function getTriggersForHostByDescription($hostId, $description) + { + $response = $this->zabbixRequest('trigger.get', [ + 'hostids' => $hostId, + 'output' => ['triggerid', 'description'], + 'search' => ['description' => $description], + 'searchByAny' => true + ]); + return $response['result'] ?? []; + } + + public function createInterfaceLinkDownTrigger($expression, $description, $priority = 4) + { + $params = [ + 'description' => $description, + 'expression' => $expression, + 'priority' => $priority, + ]; + $response = $this->zabbixRequest('trigger.create', $params); + return $response['result'] ?? ['error' => $response['error'] ?? 'Unknown error']; + } + + public function deleteTriggers(array $triggerIds) + { + if (empty($triggerIds)) return []; + $response = $this->zabbixRequest('trigger.delete', $triggerIds); + return $response['result'] ?? ['error' => $response['error'] ?? 'Unknown error']; + } + + public function getTrends(array $itemIds, $time_from) + { + if (empty($itemIds)) return []; + $response = $this->zabbixRequest('trend.get', [ + 'itemids' => $itemIds, + 'output' => ['itemid', 'num', 'value_min', 'value_avg', 'value_max'], + 'time_from' => $time_from + ]); + return $response['result'] ?? []; + } + + public function getOverviewData($hostId) + { + $items = $this->zabbixRequest('item.get', [ + 'hostids' => $hostId, + 'output' => ['itemid', 'name', 'units', 'key_'], + 'search' => ['key_' => ['icmpping', 'system.uptime']], + 'searchByAny' => true + ])['result'] ?? []; + + $itemIds = []; + $itemMap = []; + $data = ['ping' => [], 'uptime' => []]; + foreach ($items as $item) { + $itemIds[] = $item['itemid']; + $type = str_contains($item['key_'], 'uptime') ? 'uptime' : 'ping'; + $itemMap[$item['itemid']] = ['type' => $type, 'name' => $item['name'], 'units' => $item['units']]; + } + + if (!empty($itemIds)) { + $history = $this->getItemValues($itemIds, 1); + foreach ($history as $h) { + if (!isset($itemMap[$h['itemid']])) continue; + $info = $itemMap[$h['itemid']]; + $data[$info['type']][] = ['name' => $info['name'], 'value' => $h['value'], 'clock' => $h['clock'], 'units' => $info['units']]; + } + } + + $time30d = time() - 2592000; + $problems = $this->zabbixRequest('problem.get', [ + 'hostids' => $hostId, + 'time_from' => $time30d, + 'output' => ['clock'] + ])['result'] ?? []; + + $time7d = time() - 604800; + $time24h = time() - 86400; + $counts = ['24h' => 0, '7d' => 0, '30d' => 0]; + foreach ($problems as $p) { + $counts['30d']++; + if ($p['clock'] >= $time7d) $counts['7d']++; + if ($p['clock'] >= $time24h) $counts['24h']++; + } + $data['problemCounts'] = $counts; + + return $data; + } +} \ No newline at end of file diff --git a/public/js/pages/Device/Device.css b/public/js/pages/Device/Device.css index e7dfdb483..2548161e7 100644 --- a/public/js/pages/Device/Device.css +++ b/public/js/pages/Device/Device.css @@ -19,3 +19,11 @@ .sev-average { border-left-color: #fd7e14; } .sev-average .problem-icon { color: #fd7e14; } .sev-high { border-left-color: #dc3545; } .sev-high .problem-icon { color: #dc3545; } .sev-disaster { border-left-color: #7B014C; } .sev-disaster .problem-icon { color: #7B014C; } +.overview-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1rem; } +.problem-counts { display: flex; justify-content: space-around; text-align: center; padding: 1rem 0; } +.problem-counts .count { font-size: 1.5rem; font-weight: bold; display: block; } +.problem-counts .period { font-size: 0.8rem; color: #6c757d; } +.problems-list.resolved .problem-card { opacity: 0.8; } +.sev-resolved { border-left: 5px solid #28a745; } +.sev-resolved .problem-icon { color: #28a745; } +.c-pointer { cursor: pointer; } \ No newline at end of file diff --git a/public/js/pages/Device/DeviceMonitoring.js b/public/js/pages/Device/DeviceMonitoring.js index a82e3427d..96266313b 100644 --- a/public/js/pages/Device/DeviceMonitoring.js +++ b/public/js/pages/Device/DeviceMonitoring.js @@ -1,3 +1,28 @@ +const ttSwitchCSS = ` +.tt-switch { position: relative; display: inline-block; width: 44px; height: 24px; } +.tt-switch input { opacity: 0; width: 0; height: 0; } +.slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; } +.slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .4s; } +input:checked + .slider { background-color: #28a745; } +input:focus + .slider { box-shadow: 0 0 1px #28a745; } +input:checked + .slider:before { transform: translateX(20px); } +.slider.round { border-radius: 24px; } +.slider.round:before { border-radius: 50%; } +`; +const styleSheet = document.createElement("style"); +styleSheet.innerText = ttSwitchCSS; +document.head.appendChild(styleSheet); + +Vue.component('tt-switch', { + template: ` + + `, + props: { value: { type: Boolean, default: false } } +}); + Vue.component('device-monitoring-modal', { //language=Vue template: ` @@ -5,7 +30,8 @@ Vue.component('device-monitoring-modal', { :title="'Monitoring für ' + deviceName" @update:show="$emit('close')" :save="false" - :delete="false"> + :delete="false" + dialog-class="modal-xl">
{{ formatUptime(item.value) }}
Seit {{ moment(Date.now() - item.value * 1000).format('DD.MM.YYYY HH:mm') }}| Schnittstelle | +Speed (Mbps) | +Max In (Mbps) | +Avg In (Mbps) | +Auslastung In (%) | +Max Out (Mbps) | +Avg Out (Mbps) | +Auslastung Out (%) | +
|---|---|---|---|---|---|---|---|
| {{ d.name }} | +{{ d.speed }} | +{{ d.rx.max }} | +{{ d.rx.avg }} | +{{ d.rx.usage }}% | +{{ d.tx.max }} | +{{ d.tx.avg }} | +{{ d.tx.usage }}% | +
| Schnittstelle | Alarmierung aktiv |
|---|---|
| {{ iface.name }} | +