addValidationRules([ 'name' => 'string', 'widgetid' => 'db widget.widgetid', 'fields' => 'array' ]); } protected function doAction(): void { // Get list of sysmapids. $sysmapids = []; $navtree_items = []; foreach ($this->fields_values['navtree'] as $id => $navtree_item) { $sysmapid = array_key_exists('sysmapid', $navtree_item) ? $navtree_item['sysmapid'] : 0; if ($sysmapid != 0) { $sysmapids[$sysmapid] = true; } $navtree_items[$id] = [ 'parent' => $navtree_item['parent'], 'sysmapid' => $sysmapid, 'child_sysmapids' => [] ]; } // Propagate item mapids to all its parent items. foreach ($navtree_items as $navtree_item) { $parent = $navtree_item['parent']; while (array_key_exists($parent, $navtree_items)) { if ($navtree_item['sysmapid'] != 0) { $navtree_items[$parent]['child_sysmapids'][$navtree_item['sysmapid']] = true; } $parent = $navtree_items[$parent]['parent']; } } // Get severity levels and colors and select list of sysmapids to count problems per maps. $this->problems_per_severity_tpl = []; $severity_config = []; $maps_accessible = $sysmapids ? API::Map()->get([ 'output' => [], 'sysmapids' => array_keys($sysmapids), 'preservekeys' => true ]) : []; for ($severity = TRIGGER_SEVERITY_NOT_CLASSIFIED; $severity < TRIGGER_SEVERITY_COUNT; $severity++) { $this->problems_per_severity_tpl[$severity] = 0; $severity_config[$severity] = [ 'name' => CSeverityHelper::getName($severity), 'style_class' => CSeverityHelper::getStatusStyle($severity) ]; } $widgetid = $this->getInput('widgetid', 0); $navtree_item_selected = 0; $navtree_items_opened = []; if ($widgetid) { $pattern = 'web.dashboard.widget.navtree.item-%.toggle'; $discard_from_start = strpos($pattern, '%'); $discard_from_end = strlen($pattern) - $discard_from_start - 1; foreach (CProfile::findByIdxPattern($pattern, $widgetid) as $item_opened) { $navtree_items_opened[] = substr($item_opened, $discard_from_start, -$discard_from_end); } $navtree_item_selected = CProfile::get('web.dashboard.widget.navtree.item.selected', 0, $widgetid); } $this->setResponse(new CControllerResponseData([ 'name' => $this->getInput('name', $this->widget->getDefaultName()), 'navtree' => $this->fields_values['navtree'], 'navtree_item_selected' => $navtree_item_selected, 'navtree_items_opened' => $navtree_items_opened, 'problems' => $this->getNumberOfProblemsBySysmap($navtree_items), 'show_unavailable' => $this->fields_values['show_unavailable'], 'maps_accessible' => array_keys($maps_accessible), 'severity_config' => $severity_config, 'initial_load' => $this->getInput('initial_load', 0), 'user' => [ 'debug_mode' => $this->getDebugMode() ] ])); } private function getNumberOfProblemsBySysmap(array $navtree_items = []): array { $response = []; $sysmapids = []; foreach ($navtree_items as $navtree_item) { $sysmapids[$navtree_item['sysmapid']] = true; } unset($sysmapids[0]); $sysmaps = $sysmapids ? API::Map()->get([ 'output' => ['sysmapid', 'severity_min'], 'selectLinks' => ['linktriggers', 'permission'], 'selectSelements' => ['elements', 'elementtype', 'permission'], 'sysmapids' => array_keys($sysmapids), 'preservekeys' => true ]) : []; if ($sysmaps) { $triggers_per_hosts = []; $triggers_per_host_groups = []; $problems_per_trigger = []; $submaps_relations = []; $submaps_found = []; $host_groups = []; $hosts = []; // Gather submaps from all selected maps. foreach ($sysmaps as $map) { foreach ($map['selements'] as $selement) { if ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_MAP) { if (($element = reset($selement['elements'])) !== false) { $submaps_relations[$map['sysmapid']][] = $element['sysmapid']; $submaps_found[] = $element['sysmapid']; } } } } // Gather maps added as submaps for each of map in any depth. $sysmaps_resolved = array_keys($sysmaps); while ($diff = array_diff($submaps_found, $sysmaps_resolved)) { $submaps = API::Map()->get([ 'output' => ['sysmapid', 'severity_min'], 'selectLinks' => ['linktriggers', 'permission'], 'selectSelements' => ['elements', 'elementtype', 'permission'], 'sysmapids' => $diff, 'preservekeys' => true ]); $sysmaps_resolved = array_merge($sysmaps_resolved, $diff); foreach ($submaps as $submap) { $sysmaps[$submap['sysmapid']] = $submap; foreach ($submap['selements'] as $selement) { if ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_MAP) { $element = reset($selement['elements']); if ($element) { $submaps_relations[$submap['sysmapid']][] = $element['sysmapid']; $submaps_found[] = $element['sysmapid']; } } } } } // Gather elements from all maps selected. foreach ($sysmaps as $map) { // Collect triggers from map links. foreach ($map['links'] as $link) { foreach ($link['linktriggers'] as $linktrigger) { $problems_per_trigger[$linktrigger['triggerid']] = $this->problems_per_severity_tpl; } } // Collect map elements. foreach ($map['selements'] as $selement) { switch ($selement['elementtype']) { case SYSMAP_ELEMENT_TYPE_HOST_GROUP: if (($element = reset($selement['elements'])) !== false) { $host_groups[$element['groupid']] = true; } break; case SYSMAP_ELEMENT_TYPE_TRIGGER: foreach (array_column($selement['elements'], 'triggerid') as $triggerid) { $problems_per_trigger[$triggerid] = $this->problems_per_severity_tpl; } break; case SYSMAP_ELEMENT_TYPE_HOST: if (($element = reset($selement['elements'])) !== false) { $hosts[$element['hostid']] = true; } break; } } } // Drop all disabled and inaccessible triggers. if ($problems_per_trigger) { $triggers = API::Trigger()->get([ 'output' => [], 'triggerids' => array_keys($problems_per_trigger), 'monitored' => true, 'preservekeys' => true ]); $problems_per_trigger = array_intersect_key($problems_per_trigger, $triggers); unset($triggers); } // Select lowest severity to reduce amount of data returned by API. $severity_min = min(array_column($sysmaps, 'severity_min')); // Get triggers related to host groups. if ($host_groups) { $triggers = API::Trigger()->get([ 'output' => ['triggerid'], 'selectHostGroups' => ['groupid'], 'groupids' => array_keys($host_groups), 'preservekeys' => true ]); foreach ($triggers as $trigger) { foreach ($trigger['hostgroups'] as $host_group) { $triggers_per_host_groups[$host_group['groupid']][$trigger['triggerid']] = true; } $problems_per_trigger[$trigger['triggerid']] = $this->problems_per_severity_tpl; } unset($host_groups); } // Get triggers related to hosts. if ($hosts) { $triggers = API::Trigger()->get([ 'output' => ['triggerid'], 'selectHosts' => ['hostid'], 'hostids' => array_keys($hosts), 'preservekeys' => true, 'monitored' => true ]); foreach ($triggers as $trigger) { if (($host = reset($trigger['hosts'])) !== false) { $triggers_per_hosts[$host['hostid']][$trigger['triggerid']] = true; $problems_per_trigger[$trigger['triggerid']] = $this->problems_per_severity_tpl; } } unset($hosts); } // Count problems per trigger. if ($problems_per_trigger) { $problems = API::Problem()->get([ 'output' => ['objectid', 'severity'], 'source' => EVENT_SOURCE_TRIGGERS, 'object' => EVENT_OBJECT_TRIGGER, 'objectids' => array_keys($problems_per_trigger), 'severities' => range($severity_min, TRIGGER_SEVERITY_COUNT - 1), 'symptom' => false, 'preservekeys' => true ]); if ($problems) { foreach ($problems as $problem) { $problems_per_trigger[$problem['objectid']][$problem['severity']]++; } } } // Count problems in each submap included in navigation tree: foreach ($navtree_items as $id => $navtree_item) { $maps_need_to_count_in = $navtree_item['child_sysmapids']; if ($navtree_item['sysmapid'] != 0) { $maps_need_to_count_in[$navtree_item['sysmapid']] = true; } $response[$id] = $this->problems_per_severity_tpl; $problems_counted = []; foreach (array_keys($maps_need_to_count_in) as $sysmapid) { if (array_key_exists($sysmapid, $sysmaps)) { $map = $sysmaps[$sysmapid]; // Count problems occurred in linked elements. foreach ($map['selements'] as $selement) { if ($selement['permission'] >= PERM_READ) { $problems = $this->getElementProblems($selement, $problems_per_trigger, $sysmaps, $submaps_relations, $map['severity_min'], $problems_counted, $triggers_per_hosts, $triggers_per_host_groups ); if ($problems !== null) { $response[$id] = self::sumArrayValues($response[$id], $problems); } } } // Count problems occurred in triggers which are related to the links. foreach ($map['links'] as $link) { $uncounted_problem_triggers = array_diff_key( array_flip(array_column($link['linktriggers'], 'triggerid')), $problems_counted ); foreach ($uncounted_problem_triggers as $triggerid => $var) { $problems_counted[$triggerid] = true; // Skip disabled and inaccessible triggers. if (!array_key_exists($triggerid, $problems_per_trigger)) { continue; } $problems_to_add = $problems_per_trigger[$triggerid]; // Remove problems which are less important than map's min-severity. if ($map['severity_min'] > 0) { foreach (array_keys($problems_to_add) as $severity) { if ($map['severity_min'] > $severity) { $problems_to_add[$severity] = 0; } } } $response[$id] = self::sumArrayValues($response[$id], $problems_to_add); } unset($uncounted_problem_triggers); } } } } } foreach ($response as &$row) { // Reduce the amount of data transferred over Ajax. if ($row === $this->problems_per_severity_tpl) { $row = 0; } } unset($row); return $response; } private function getElementProblems(array $selement, array $problems_per_trigger, array $sysmaps, array $submaps_relations, $severity_min = 0, array &$problems_counted = [], array $triggers_per_hosts = [], array $triggers_per_host_groups = []): ?array { $problems = null; switch ($selement['elementtype']) { case SYSMAP_ELEMENT_TYPE_HOST_GROUP: $problems = $this->problems_per_severity_tpl; if (($element = reset($selement['elements'])) !== false) { if (array_key_exists($element['groupid'], $triggers_per_host_groups)) { $uncounted_problem_triggers = array_diff_key($triggers_per_host_groups[$element['groupid']], $problems_counted ); foreach ($uncounted_problem_triggers as $triggerid => $var) { $problems_counted[$triggerid] = true; // Skip disabled and inaccessible triggers. if (array_key_exists($triggerid, $problems_per_trigger)) { $problems = self::sumArrayValues($problems, $problems_per_trigger[$triggerid]); } } unset($uncounted_problem_triggers); } } break; case SYSMAP_ELEMENT_TYPE_TRIGGER: $problems = $this->problems_per_severity_tpl; $uncounted_problem_triggers = array_diff_key( array_flip(array_column($selement['elements'], 'triggerid')), $problems_counted ); foreach ($uncounted_problem_triggers as $triggerid => $var) { $problems_counted[$triggerid] = true; // Skip disabled and inaccessible triggers. if (array_key_exists($triggerid, $problems_per_trigger)) { $problems = self::sumArrayValues($problems, $problems_per_trigger[$triggerid]); } } unset($uncounted_problem_triggers); break; case SYSMAP_ELEMENT_TYPE_HOST: $problems = $this->problems_per_severity_tpl; if (($element = reset($selement['elements'])) !== false) { if (array_key_exists($element['hostid'], $triggers_per_hosts)) { $uncounted_problem_triggers = array_diff_key($triggers_per_hosts[$element['hostid']], $problems_counted ); foreach ($uncounted_problem_triggers as $triggerid => $var) { $problems_counted[$triggerid] = true; // Skip disabled and inaccessible triggers. if (array_key_exists($triggerid, $problems_per_trigger)) { $problems = self::sumArrayValues($problems, $problems_per_trigger[$triggerid]); } } unset($uncounted_problem_triggers); } } break; case SYSMAP_ELEMENT_TYPE_MAP: $problems = $this->problems_per_severity_tpl; if (($submap_element = reset($selement['elements'])) !== false) { // Recursively find all submaps in any depth and put them into an array. $maps_to_process[$submap_element['sysmapid']] = false; while (array_filter($maps_to_process, static function($item) {return !$item;})) { foreach ($maps_to_process as $linked_map => $val) { $maps_to_process[$linked_map] = true; if (array_key_exists($linked_map, $submaps_relations)) { foreach ($submaps_relations[$linked_map] as $submap) { if (!array_key_exists($submap, $maps_to_process)) { $maps_to_process[$submap] = false; } } } } } // Count problems in each of selected submap. foreach ($maps_to_process as $sysmapid => $val) { // Count problems in elements assigned to selements. if (array_key_exists($sysmapid, $sysmaps)) { foreach ($sysmaps[$sysmapid]['selements'] as $submap_selement) { if ($submap_selement['permission'] >= PERM_READ) { $problems_in_submap = $this->getElementProblems($submap_selement, $problems_per_trigger, $sysmaps, $submaps_relations, $sysmaps[$sysmapid]['severity_min'], $problems_counted, $triggers_per_hosts, $triggers_per_host_groups ); if ($problems_in_submap !== null) { $problems = self::sumArrayValues($problems, $problems_in_submap); } } } } // Count problems in triggers assigned to linked. if (array_key_exists($sysmapid, $sysmaps)) { foreach ($sysmaps[$sysmapid]['links'] as $link) { if ($link['permission'] >= PERM_READ) { $uncounted_problem_triggers = array_diff_key( array_flip(array_column($link['linktriggers'], 'triggerid')), $problems_counted ); foreach ($uncounted_problem_triggers as $triggerid => $var) { $problems_counted[$triggerid] = true; // Skip disabled and inaccessible triggers. if (array_key_exists($triggerid, $problems_per_trigger)) { $problems = self::sumArrayValues($problems, $problems_per_trigger[$triggerid] ); } } unset($uncounted_problem_triggers); } } } } } break; } // Remove problems which are less important than $severity_min. if ($problems !== null && $severity_min > 0) { foreach (array_keys($problems) as $severity) { if ($severity_min > $severity) { $problems[$severity] = 0; } } } return $problems; } /** * Function is used to sum problems in 2 arrays. * * Example: * $a1 = [1 => 0, 2 => 5, 3 => 10]; * $a2 = [1 => 1, 2 => 2, 3 => 3]; * self::sumArrayValues($a1, $a2); // returns [1 => 1, 2 => 7, 3 => 13] * * @param array $a1 Array containing severity as key and number of problems as value. * @param array $a2 Array containing severity as key and number of problems as value. * * @return array Array containing problems in both arrays summed. */ private static function sumArrayValues(array $a1, array $a2): array { foreach ($a1 as $key => &$value) { $value += $a2[$key]; } unset($value); return $a1; } }