commit d2e693ec2a2b94e9f92160db2e3fe1a619fd8b30 Author: Thomas Gelf Date: Mon Oct 20 16:26:06 2014 +0200 BpApp: initial import of legacy version diff --git a/application/clicommands/CheckCommand.php b/application/clicommands/CheckCommand.php new file mode 100644 index 0000000..90d889f --- /dev/null +++ b/application/clicommands/CheckCommand.php @@ -0,0 +1,87 @@ +app->getModuleManager()->loadModule('monitoring'); + $this->config = Config::module($this->moduleName); + $this->readConfig(); + $this->prepareBackend(); + } + + /** + * Check a specific process + * + * Blabla + */ + public function processAction() + { + $bp = BusinessProcess::parse($this->filename); + $node = $bp->getNode($this->params->shift()); + if ($this->params->get('soft-states')) { + $bp->useSoftStates(); + } + $bp->retrieveStatesFromBackend($this->backend); + printf("Business Process %s: %s\n", $node->getStateName(), $node->getAlias()); + exit($node->getState()); + + } + + // TODO: Remove this + protected function prepareBackend() + { + if ($this->backend === null) { + $name = $this->config->{'global'}->get('default_backend'); + if (isset($this->bpconf->backend)) { + $name = $this->bpconf->backend; + } + $this->backend = Backend::createBackend($name); + } + return $this->backend; + } + + // TODO: Remove this + protected function readConfig() + { + $this->views = array(); + $this->aliases = array(); + foreach ($this->config as $key => $val) { + if (! preg_match('~^view-(.+)$~', $key, $match)) continue; + $this->views[$match[1]] = (object) $val->toArray(); + $this->aliases[(string) $val->title] = $match[1]; + if ($val->aliases) { + foreach (preg_split('~\s*,\s*~', $val->aliases, -1, PREG_SPLIT_NO_EMPTY) as $alias) { + $this->aliases[$alias] = $match[1]; + } + } + } + $bpname = $this->params->get('bp', key($this->views)); + + if (array_key_exists($bpname, $this->aliases)) { + $bpname = $this->aliases[$bpname]; + } + if (! array_key_exists($bpname, $this->views)) { + throw new Exception('Got invalid bp name: ' . $bpname); + } + $this->bpconf = $this->views[$bpname]; + $this->bpname = $bpname; + + $this->filename = $this->config->global->bp_config_dir + . '/' . $this->bpconf->file . '.conf'; + } +} diff --git a/application/controllers/ProcessController.php b/application/controllers/ProcessController.php new file mode 100644 index 0000000..7988cc7 --- /dev/null +++ b/application/controllers/ProcessController.php @@ -0,0 +1,140 @@ +requireJs('bpaddon.js'); + $this->config = Config::module('bpapp'); + $this->readConfig(); + $this->prepareBackend(); + $this->view->showMenu = $this->_getParam('menu', 'enabled') === 'enabled'; + $this->view->tabs = $this->createTabs(); + } + + protected function prepareBackend() + { + if ($this->backend === null) { + $name = $this->config->{'global'}->default_backend; + if (isset($this->bpconf->backend)) { + $name = $this->bpconf->backend; + } + $this->view->backend = $name; + $this->backend = Backend::createBackend($name); + } + return $this->backend; + } + + protected function createTabs() + { + // $tabs = $this->widget('tabs'); + $tabs = Widget::create('tabs'); + $action = $this->_request->getActionName(); + foreach ($this->views as $bpname => $bpconf) { + $tabs->add($bpname, array( + 'url' => 'bpapp/process/' . $action, + 'urlParams' => array('bp' => $bpname), + 'title' => $bpconf->title, + )); + } + $tabs->activate($this->bpname); + return $tabs; + } + + protected function readConfig() + { + $this->views = array(); + $this->aliases = array(); + foreach ($this->config as $key => $val) { + if (! preg_match('~^view-(.+)$~', $key, $match)) continue; + $this->views[$match[1]] = (object) $val->toArray(); + $this->aliases[(string) $val->title] = $match[1]; + if ($val->aliases) { + foreach (preg_split('~\s*,\s*~', $val->aliases, -1, PREG_SPLIT_NO_EMPTY) as $alias) { + $this->aliases[$alias] = $match[1]; + } + } + } + $this->view->views = $this->views; + $bpname = $this->_getParam('bp', key($this->views)); + + if (array_key_exists($bpname, $this->aliases)) { + $bpname = $this->aliases[$bpname]; + } + if (! array_key_exists($bpname, $this->views)) { + throw new Exception('Got invalid bp name: ' . $bpname); + } + $this->bpconf = $this->views[$bpname]; + $this->view->bpname = $bpname; + $this->bpname = $bpname; + + $this->filename = $this->config->global->bp_config_dir + . '/' . $this->bpconf->file . '.conf'; + } + + public function sourceAction() + { + $this->view->title = 'Source: ' . $this->bpconf->title; + $this->view->source = file_get_contents($this->filename); + } + + + public function historyAction() + { + $bp = BusinessProcess::parse($this->filename); + echo '
' . print_r($bp, 1) . '
'; + exit; + } + + public function showAction() + { + $this->setAutoRefreshInterval(10); + + $this->view->opened = $this->_getParam('opened'); + $this->view->compact = $this->_getParam('view') === 'compact'; + $bpconf = $this->bpconf; + $this->view->title = 'Process: ' . $bpconf->title; + + $bp = BusinessProcess::parse($this->filename); + if ($this->_getParam('state_type') === 'soft' + || (isset($bpconf->states) && $bpconf->states === 'soft')) { + $bp->useSoftStates(); + } + $bp->retrieveStatesFromBackend($this->backend); + $this->view->bp = $bp; + + if (isset($bpconf->slahosts)) { + $sla_hosts = preg_split('~\s*,s*~', $bpconf->slahosts, -1, PREG_SPLIT_NO_EMPTY); + if (isset($bpconf->sla_year)) { + $start = mktime(0, 0, 0, 1, 1, $bpconf->sla_year); + $end = mktime(23, 59, 59, 1, 0, $bpconf->sla_year + 1); + } else { + $start = mktime(0, 0, 0, 1, 1, (int) date('Y')); + $end = null; + // Bis zum Jahresende hochrechnen: + // $end = mktime(23, 59, 59, 1, 0, (int) date('Y') + 1); + } + $this->view->slas = $this->backend + ->module('BpAddon') + ->getBpSlaValues($sla_hosts, $start, $end); + } else { + $this->view->slas = array(); + } + + $this->view->available_bps = $this->views; + } +} + diff --git a/application/views/scripts/process/history.phtml b/application/views/scripts/process/history.phtml new file mode 100644 index 0000000..34a20b3 --- /dev/null +++ b/application/views/scripts/process/history.phtml @@ -0,0 +1,94 @@ + + + + + + + +
 
+ +
+ + + + + + +
 
+
+ +
+history as $entry) { +if ($entry->hostname !== $last_host || $entry->service !== $last_service) { + +echo '' . "\n"; +} +$cnt++; +if ($cnt > 10000) break; + $duration = $entry->timestamp - $current_offset; + if ($next_color === null) { + $color = stateColor($entry->last_state); + } else { + $color = $next_color; + } + $next_color = stateColor($entry->state); + + if ($entry->state == 0) { + $offset = ceil($duration / 3600 / 6); + } else { + $offset = floor($duration / 3600 / 6); + } + echo '
 
'; + $current_offset += $duration; + + $last_host = $entry->hostname; +$last_service = $entry->service; + +} + + +?>
diff --git a/application/views/scripts/process/show.phtml b/application/views/scripts/process/show.phtml new file mode 100644 index 0000000..9eb2a77 --- /dev/null +++ b/application/views/scripts/process/show.phtml @@ -0,0 +1,239 @@ +showMenu) { + + if ($this->compact) { + + } else { + +?> +
+tabs ?> +
+ +getState() == 0) { + $opened = false; + } + if ($level > 0) $opened = false; + $opened = false; + + +// Example "open three problem levels" + if ($node instanceof BpNode && $node->getState() > 0 && $level < 2) { +// $opened = true; + } + + if ($hidden) { + $opened = false; + } + + $id = $id_prefix . $node->getAlias(); + + if (array_key_exists(md5($id), $opened_list)) { + $opened_list = $opened_list[md5($id)]; + $opened = true; + } else { + $opened_list = array(); + } + if (! $opened) { + $extra = ' collapsed'; + } else { + $extra = ''; + } + + $htm .= ''; + + + + if ($node->isMissing()) { + $state_classes = 'state missing'; + } else { + $state_classes = 'state ' . stateName($node->getState()); + } + if ($node->isInDowntime() || $node->isAcknowledged()) { + $state_classes .= ' handled'; + } + + if ($node instanceof BpNode) { + $htm .= '\n"; + + if ($node->hasChildren()) { + $htm .= '' + . '\n"; + } + } else { + $htm .= '\n"; + } + + $htm .= '
' + . ' ' + . preg_replace('~^\d{2}\.\s*~', '', $node->getAlias()); +/* . ': ' + . $node->getState() + . '/' + . $node->getSortingState(); + */ + if ($node->isInDowntime()) $htm .= ' '; + if ($node->isAcknowledged()) $htm .= ' '; + + if ($node->hasUrl()) { + $htm .= ' '; + } + + // Summaries, PIE +if (! $self->compact) { + $summary = $node->getStateSummary(); + $sumtxt = array(); + foreach ($summary as $k => $v) { + if ($v > 0) { + $sumtxt[] = $v . 'x ' . stateName($k); + } + } + $sumtxt = implode(', ', $sumtxt); + if ($sumtxt === '') $sumtxt = '-'; + if ($level === 0) { + $htm .= '' + . implode(',', $node->getStateSummary()) + . ''; + } +} + // END of PIE + $alias = $node->getAlias(); + if (array_key_exists($alias, $slas)) { + $sla = $slas[$alias]; + $sla_style = ''; + if ($sla->level === null) $sla->level = 0; + if ($sla->value < $sla->level) { + $sla_style = ' color: red; font-weight: bold;'; + } elseif ($sla->value < $sla->level * 1.002) { + $sla_style = ' color: orange;'; + } + $htm .= sprintf(' [SLA] %0.3f%% (Soll: %0.2f%%)', $sla->value, $sla->level); + } + + + // Problem info: + if ($node->getState() > 0) { + $problems = array(); + foreach ($node->getChildren() as $child) { + if ($child->getState() > 0) { + $problems[] = '' . htmlspecialchars($child->getAlias()) . ''; + } + } + $htm .= ':

' . implode(', ', $problems) . '

'; + } + // END of Problem Info + + $htm .= "
hasInfoCommand() ? ' rowspan="2"' : '') . '>' + . $node->getOperator() + . ''; +//$htm .= ''; + + foreach ($node->getChildren() as $name => $child) { + $htm .= showNode($self, $child, $slas, $opened_list, $id_prefix . $id . '_', $level + 1, ! $opened); + } + $htm .= "
'; + if ($node->isInDowntime()) $htm .= ' '; + if ($node->isAcknowledged()) $htm .= ' '; + $htm .= stateName($node->getState()) + . '' + . $node->getHostname() + . '' + . ($node instanceof ServiceNode + ? ' / ' . $node->getServiceDescription() . '' + : '') +/* DEBUG + . ': ' + . $node->getState() + . '/' + . $node->getSortingState()*/ + ; + $htm .= "
'; + $htm .= "\n"; + return $htm; +} + +echo '
'; + +if (! is_array($this->opened)) { $this->opened = array(); } + +?>bp->getRootNodes() as $name => $node): ?> +slas, $this->opened, 'bp_') ?> + +bp->hasWarnings()): ?> +

Warnings

+bp->getWarnings() as $warning): ?> +escape($warning) ?>
+ + +
diff --git a/application/views/scripts/process/source.phtml b/application/views/scripts/process/source.phtml new file mode 100644 index 0000000..58066ff --- /dev/null +++ b/application/views/scripts/process/source.phtml @@ -0,0 +1,22 @@ +
+ +Render + +tabs ?> +
+
+source);
+$len = ceil(log(count($lines), 10));
+
+foreach ($lines as $line) {
+    $cnt++;
+    printf("%0" . $len . "d: %s\n", $cnt, $this->escape($line));
+}
+
+?>
+
diff --git a/configuration.php b/configuration.php new file mode 100644 index 0000000..749e561 --- /dev/null +++ b/configuration.php @@ -0,0 +1,8 @@ +menuSection($this->translate('Availability'), array( + 'icon' => 'img/icons/servicegroup.png', + 'priority' => 40 +)); +$section->add($this->translate('Business Processes'))->setUrl('bpapp/process/show'); + diff --git a/library/Bpapp/BpNode.php b/library/Bpapp/BpNode.php new file mode 100644 index 0000000..bc1b846 --- /dev/null +++ b/library/Bpapp/BpNode.php @@ -0,0 +1,187 @@ +bp = $bp; + $this->name = $object->name; + $this->operator = $object->operator; + $this->setChildNames($object->child_names); + } + + public function getStateSummary() + { + if ($this->counters === null) { + $this->getState(); + $this->counters = array(0, 0, 0, 0); + foreach ($this->children as $child) { + if ($child instanceof BpNode) { + $counters = $child->getStateSummary(); + foreach ($counters as $k => $v) { + $this->counters[$k] += $v; + } + } else { + $state = $child->getState(); + $this->counters[$state]++; + } + } + } + return $this->counters; + } + + public function getOperator() + { + return $this->operator; + } + + public function setUrl($url) + { + $this->url = $url; + } + + public function hasUrl() + { + return $this->url !== null; + } + + public function setInfoCommand($cmd) + { + $this->info_command = $cmd; + } + + public function hasInfoCommand() + { + return $this->info_command !== null; + } + + public function getUrl() + { + return $this->url; + } + + public function getInfoCommand() + { + return $this->info_command; + } + + public function getAlias() + { + return $this->alias; + } + + public function setAlias($name) + { + $this->alias = $name; + return $this; + } + + public function getState() + { + if ($this->state === null) { + $this->calculateState(); + } + return $this->state; + } + + protected function calculateState() + { + $sort_states = array(); + foreach ($this->getChildren() as $child) { + $sort_states[] = $child->getSortingState(); + } + switch ($this->operator) { + case self::OP_AND: + $state = max($sort_states); + break; + case self::OP_OR: + $state = min($sort_states); + break; + default: + // MIN: + if (! is_numeric($this->operator)) { + throw new Exception( + sprintf( + 'Got invalid operator: %s', + $this->operator + ) + ); + } + sort($sort_states); + + // default -> unknown + $state = 2 << self::SHIFT_FLAGS; + + for ($i = 1; $i <= $this->operator; $i++) { + $state = array_shift($sort_states); + } + } + if ($state & self::FLAG_DOWNTIME) { + $this->setDowntime(true); + } + if ($state & self::FLAG_ACK) { + $this->setAck(true); + } + $state = $state >> self::SHIFT_FLAGS; + + if ($state === 3) { + $this->state = 2; + } elseif ($state === 2) { + $this->state = 3; + } else { + $this->state = $state; + } + } + + public function hasChildren() + { + return count($this->children) > 0; + } + + public function setDisplay($display) + { + $this->display = $display; + return $this; + } + + public function setChildNames($names) + { + $this->child_names = $names; + $this->children = null; + return $this; + } + + public function getChildren() + { + if ($this->children === null) { + $this->children = array(); + natsort($this->child_names); + foreach ($this->child_names as $name) { + $this->children[$name] = $this->bp->getNode($name); + } + } + return $this->children; + } +} diff --git a/library/Bpapp/BusinessProcess.php b/library/Bpapp/BusinessProcess.php new file mode 100644 index 0000000..18d796f --- /dev/null +++ b/library/Bpapp/BusinessProcess.php @@ -0,0 +1,309 @@ +object_ids); + } +*/ + + public static function parse($filename) + { + $bp = new BusinessProcess(); + $bp->filename = $filename; + $bp->doParse(); + return $bp; + } + + public function useSoftStates() + { + $this->state_type = self::SOFT_STATE; + return $this; + } + + public function useHardStates() + { + $this->state_type = self::HARD_STATE; + return $this; + } + + protected function doParse() + { + $fh = @fopen($this->filename, 'r'); + if (! $fh) { + throw new Exception('Could not open ' . $this->filename); + } + + $this->parsing_line_number = 0; + while ($line = fgets($fh)) { + $line = trim($line); + + $this->parsing_line_number++; + + if (preg_match('~^#~', $line)) { + continue; + } + + if (preg_match('~^$~', $line)) { + continue; + } + + if (preg_match('~^display~', $line)) { + list($display, $name, $desc) = preg_split('~\s*;\s*~', substr($line, 8), 3); + $node = $this->getNode($name)->setAlias($desc)->setDisplay($display); + if ($display > 0) { + $this->root_nodes[$name] = $node; + } + } + + if (preg_match('~^external_info~', $line)) { + list($name, $script) = preg_split('~\s*;\s*~', substr($line, 14), 2); + $node = $this->getNode($name)->setInfoCommand($script); + } + + if (preg_match('~^info_url~', $line)) { + list($name, $url) = preg_split('~\s*;\s*~', substr($line, 9), 2); + $node = $this->getNode($name)->setUrl($url); + } + + if (strpos($line, '=') === false) { + continue; + } + + list($name, $value) = preg_split('~\s*=\s*~', $line, 2); + + if (strpos($name, ';') !== false) { + $this->parseError('No semicolon allowed in varname'); + } + + $op = '&'; + if (preg_match_all('~([\|\+&])~', $value, $m)) { + $op = implode('', $m[1]); + for ($i = 1; $i < strlen($op); $i++) { + if ($op[$i] !== $op[$i - 1]) { + $this->parseError('Mixing operators is not allowed'); + } + } + } + $op = $op[0]; + $op_name = $op; + + if ($op === '+') { + if (! preg_match('~^(\d+)\s*of:\s*(.+?)$~', $value, $m)) { + $this->parseError('syntax: = of: + [+ ]*'); + } + $op_name = $m[1]; + $value = $m[2]; + } + $cmps = preg_split('~\s*\\' . $op . '\s*~', $value); + + foreach ($cmps as & $val) { + if (strpos($val, ';') !== false) { + list($host, $service) = preg_split('~;~', $val, 2); + $this->all_checks[$val] = 1; + $this->hosts[$host] = 1; + } + } + $node = new BpNode($this, (object) array( + 'name' => $name, + 'operator' => $op_name, + 'child_names' => $cmps + )); + $this->addNode($name, $node); + } + + fclose($fh); + unset($this->parsing_line_number); + } + + public function retrieveStatesFromBackend($backend) + { + $this->backend = $backend; + // TODO: Split apart, create a dedicated function. + // Separate "parse-logic" from "retrieve-state-logic" + // Allow DB-based backend + // Use IcingaWeb2 Multi-Backend-Support + $check_results = array(); + $hostFilter = array_keys($this->hosts); + if ($this->state_type === self::HARD_STATE) { + $db_states = $this->backend/*->module('Bpapp')*/ + ->fetchHardStatesForBpHosts(array_keys($this->hosts)); + } else { +// TOM 2014 +// $db_states = $this->backend/*->module('Bpapp')*/ +// ->fetchSoftStatesForBpHosts(array_keys($this->hosts)); + $hostStatus = $backend->select()->from( + 'hostStatus', + array( + 'hostname' => 'host_name', + 'in_downtime' => 'host_in_downtime', + 'ack' => 'host_acknowledged', + 'state' => 'host_state' + ) + )->where('host_name', $hostFilter)->getQuery()->fetchAll(); + $serviceStatus = $backend->select()->from( + 'serviceStatus', + array( + 'hostname' => 'host_name', + 'service' => 'service_description', + 'in_downtime' => 'service_in_downtime', + 'ack' => 'service_acknowledged', + 'state' => 'service_state' + ) + )->where('host_name', $hostFilter)->getQuery()->fetchAll(); + + } + + foreach ($serviceStatus + $hostStatus as $row) { + $key = $row->hostname; + if ($row->service) { + $key .= ';' . $row->service; + // Ignore unused services, we are fetching more than we need + if (! array_key_exists($key, $this->all_checks)) { + continue; + } + $node = new ServiceNode($this, $row); + // $this->object_ids[$row->object_id] = 1; + } else { + $key .= ';Hoststatus'; + if (! array_key_exists($key, $this->all_checks)) { + continue; + } + $node = new HostNode($this, $row); + // $this->object_ids[$row->object_id] = 1; + } + if ($row->state === null) { + $node = new ServiceNode( + $this, + (object) array( + 'hostname' => $row->hostname, + 'service' => $row->service, + 'state' => 0 + ) + ); + $node->setMissing(); + } + if ((int) $row->in_downtime === 1) { + $node->setDowntime(true); + } + if ((int) $row->ack === 1) { + $node->setAck(true); + } + $this->addNode($key, $node); + } + ksort($this->root_nodes); + return $this; + } + + public function getRootNodes() + { + return $this->root_nodes; + } + + public function hasNode($name) + { + return array_key_exists($name, $this->nodes); + } + + public function getNode($name) + { + if (array_key_exists($name, $this->nodes)) { + return $this->nodes[$name]; + } + + // Fallback: if it is a service, create an empty one: + $this->warn(sprintf('The node "%s" doesn\'t exist', $name)); + $pos = strpos($name, ';'); + if ($pos !== false) { + $host = substr($name, 0, $pos); + $service = substr($name, $pos + 1); + $node = new ServiceNode( + $this, + (object) array( + 'hostname' => $host, + 'service' => $service, + 'state' => 0 + ) + ); + $node->setMissing(); + return $node; + } + + throw new Exception( + sprintf('The node "%s" doesn\'t exist', $name) + ); + } + + protected function addNode($name, Node $node) + { + if (array_key_exists($name, $this->nodes)) { + $this->warn( + sprintf( + 'Node "%s" has been defined twice', + $name + ) + ); + } + $this->nodes[$name] = $node; + return $this; + } + + public function hasWarnings() + { + return ! empty($this->warnings); + } + + public function getWarnings() + { + return $this->warnings; + } + + protected function warn($msg) + { + if (isset($this->parsing_line_number)) { + $this->warnings[] = sprintf( + 'Parser waring on %s:%s: %s', + $this->filename, + $this->parsing_line_number, + $msg + ); + } else { + $this->warnings[] = $msg; + } + } + + protected function parseError($msg) + { + throw new Exception( + sprintf( + 'Parse error on %s:%s: %s', + $this->filename, + $this->parsing_line_number, + $msg + ) + ); + } +} diff --git a/library/Bpapp/HostNode.php b/library/Bpapp/HostNode.php new file mode 100644 index 0000000..fe70dc4 --- /dev/null +++ b/library/Bpapp/HostNode.php @@ -0,0 +1,21 @@ +name = $object->hostname . ';Hoststate'; + $this->hostname = $object->hostname; + $this->bp = $bp; + $this->setState($object->state); + } + + public function getHostname() + { + return $this->hostname; + } +} diff --git a/library/Bpapp/Node.php b/library/Bpapp/Node.php new file mode 100644 index 0000000..38e8114 --- /dev/null +++ b/library/Bpapp/Node.php @@ -0,0 +1,158 @@ +missing = $missing; + return $this; + } + + public function isMissing() + { + return $this->missing; + } + + public function addChild(Node $node) + { + if (array_key_exists((string) $node, $this->children)) { + throw new Exception( + sprintf( + 'Node "%s" has been defined more than once', + $node + ) + ); + } + $this->childs[(string) $node] = $node; + $node->setParent($this); + return $this; + } + + public function setState($state) + { + $this->state = (int) $state; + return $this; + } + + public function setAck($ack = true) + { + $this->ack = $ack; + return $this; + } + + public function setDowntime($downtime = true) + { + $this->downtime = $downtime; + return $this; + } + + public function getStateName() + { + return self::$state_names[ $this->getState() ]; + } + + public function getState() + { + if ($this->state === null) { + throw new Exception( + sprintf( + 'Node %s is unable to retrieve it\'s state', + $this->name + ) + ); + } + return $this->state; + } + + public function getSortingState() + { + $state = $this->getState(); + if ($state === 3) { + $state = 2; + } elseif ($state === 2) { + $state = 3; + } + $state = ($state << self::SHIFT_FLAGS) + + ($this->isInDowntime() ? self::FLAG_DOWNTIME : 0) + + ($this->isAcknowledged() ? self::FLAG_ACK : 0); + if (! ($state & (self::FLAG_DOWNTIME | self::FLAG_ACK))) { + $state |= self::FLAG_NONE; + } + return $state; + } + + public function setParent(Node $parent) + { + $this->parent = $parent; + return $this; + } + + public function getDuration() + { + return $this->duration; + } + + public function isInDowntime() + { + if ($this->downtime === null) { + $this->getState(); + } + return $this->downtime; + } + + public function isAcknowledged() + { + if ($this->ack === null) { + $this->getState(); + } + return $this->ack; + } + + public function hasChildren() + { + return false; + } + + public function __destruct() + { + // required to avoid memleeks in PHP < 5.3: + $this->parent = null; + $this->children = array(); + } + + public function __toString() + { + return $this->name; + } +} diff --git a/library/Bpapp/ServiceNode.php b/library/Bpapp/ServiceNode.php new file mode 100644 index 0000000..9f93957 --- /dev/null +++ b/library/Bpapp/ServiceNode.php @@ -0,0 +1,33 @@ +name = $object->hostname . ';' . $object->service; + $this->hostname = $object->hostname; + $this->service = $object->service; + $this->bp = $bp; + $this->setState($object->state); + } + + public function getHostname() + { + return $this->hostname; + } + + public function getServiceDescription() + { + return $this->service; + } + + public function getAlias() + { + return $this->hostname . ': ' . $this->service; + } +} diff --git a/module.info b/module.info new file mode 100644 index 0000000..309beaa --- /dev/null +++ b/module.info @@ -0,0 +1,5 @@ +Name: BPapp +Version: 0.0.1 +Depends: monitoring +Description: BPapp is a viewer for BPaddon business process config files + That's it diff --git a/public/css/module.less b/public/css/module.less new file mode 100644 index 0000000..5a36ca4 --- /dev/null +++ b/public/css/module.less @@ -0,0 +1,192 @@ + +.content a { + color: #333; + text-decoration: none; +} + +.content a:hover { + text-decoration: underline; +} + +table.businessprocess, table.businessprocess table { + border-collapse: collapse; + margin: 0; + padding: 0; + width: 100%; +} + +table.businessprocess { + width: 100%; +} + +table.businessprocess tr { + margin: 0; + padding: 0; + font-family: Verdana, Helvetica, Arial, sans-serif; + font-size: 0.97em; +} + +table.businessprocess tr tr tr tr { + font-size: 1em; +} + +table.businessprocess th, table.businessprocess td { + margin: 0; + padding: 0.3em 0 0 0.3em; + text-align: left; + vertical-align: middle; + line-height: 1.7em; +} + +table.businessprocess th { +/* IE? */ + padding: 0.2em 1em 0.2em 1em; + cursor: pointer; + -moz-user-select: none; + -ms-user-select: none; + -khtml-user-select: none; + -webkit-user-select: none; +} + +table.businessprocess th.hovered.state { + text-decoration: underline; +} + +a img { + border: none; +} + +table.businessprocess th.bptitle { + border-radius: 0.5em 0.5em 0.5em 0; + -moz-border-radius: 0.5em 0.5em 0.5em 0; +} + +table.collapsed > tbody > tr > th.bptitle { + border-radius: 0.5em; + -moz-border-radius: 0.5em; +} + +table.businessprocess th.operator { + width: 1em; + padding: 0.5em; + text-align: center; + vertical-align: middle; + border-radius: 0 0 0.5em 0.5em; + -moz-border-radius: 0 0 0.5em 0.5em; + +} + +table.businessprocess td.service { + border-radius: 0.5em; + -moz-border-radius: 0.5; + width: 6em; + text-align: center; + font-weight: bold; +} + +table.businessprocess td.service img { + float: left; +} + +table.businessprocess, table.businessprocess table { + border-top: 2px solid transparent; + border-left: 2px solid transparent; +} + +table.businessprocess .state { + -moz-user-select: none; + -ms-user-select: none; + -khtml-user-select: none; + -webkit-user-select: none; +} + +.state.unknown { + background-color: @colorUnknown; +} + +.state.critical { + background-color: @colorCritical; + color: white; +} + +.state.critical.handled { + background-color: @colorCriticalHandled; + color: #000; +} + +.state.warning { + background-color: @colorWarning; +} + +.state.unknown.handled { + background-color: @colorUnknownHandled; +} + +.state.pending { + background-color: @colorPending; +} + +.state.warning.handled { + background-color: @colorWarningHandled; + color: #000; +} + +.state.ok { + background-color: @colorOk; + color: #fff; +} + +th.hovered.state.unknown { + background-color: #aac; +} + +table.businessprocess th p.problems { + font-weight: normal; + font-size: 0.7em; + margin: 0; + padding: 0; + display: none; +} + +.collapsed > tbody > tr > th > p.problems { + display: inline; +} + +table.businessprocess th p.problems span { + padding: 3px; +} + +table.businessprocess th.hovered p.problems > .state { + text-decoration: none; +} + +span.collapsible { + background-image: url("../img/bpapp/icon_collapse.png"); + background-repeat: no-repeat; + width: 1.7em; + height: 1.7em; + background-position: left center; + display: block; + float: left; +} + +.collapsed span.collapsible { + background: url("../img/bpapp/icon_expand.png"); + background-repeat: no-repeat; + background-position: left center; +} + +.collapsed tr.children { + display: none; +} + +.inlinepie { + display: none; + width: 2em; + float: right; + margin-top: 0.1em; + text-align: center; + vertical-align: middle; + color: transparent; +} + diff --git a/public/img/ack.gif b/public/img/ack.gif new file mode 100644 index 0000000..cda95a8 Binary files /dev/null and b/public/img/ack.gif differ diff --git a/public/img/downtime.gif b/public/img/downtime.gif new file mode 100644 index 0000000..1687798 Binary files /dev/null and b/public/img/downtime.gif differ diff --git a/public/img/help.gif b/public/img/help.gif new file mode 100644 index 0000000..9226497 Binary files /dev/null and b/public/img/help.gif differ diff --git a/public/img/icon_collapse.png b/public/img/icon_collapse.png new file mode 100644 index 0000000..0c7f37b Binary files /dev/null and b/public/img/icon_collapse.png differ diff --git a/public/img/icon_expand.png b/public/img/icon_expand.png new file mode 100644 index 0000000..19862cf Binary files /dev/null and b/public/img/icon_expand.png differ diff --git a/public/img/op_and.png b/public/img/op_and.png new file mode 100644 index 0000000..e4683b4 Binary files /dev/null and b/public/img/op_and.png differ diff --git a/public/img/op_min1.png b/public/img/op_min1.png new file mode 100644 index 0000000..431098a Binary files /dev/null and b/public/img/op_min1.png differ diff --git a/public/img/op_min2.png b/public/img/op_min2.png new file mode 100644 index 0000000..3088eb6 Binary files /dev/null and b/public/img/op_min2.png differ diff --git a/public/img/op_min3.png b/public/img/op_min3.png new file mode 100644 index 0000000..7c7eb9c Binary files /dev/null and b/public/img/op_min3.png differ diff --git a/public/img/op_min4.png b/public/img/op_min4.png new file mode 100644 index 0000000..a7f1165 Binary files /dev/null and b/public/img/op_min4.png differ diff --git a/public/img/op_min5.png b/public/img/op_min5.png new file mode 100644 index 0000000..95c88ff Binary files /dev/null and b/public/img/op_min5.png differ diff --git a/public/img/op_min6.png b/public/img/op_min6.png new file mode 100644 index 0000000..3424753 Binary files /dev/null and b/public/img/op_min6.png differ diff --git a/public/img/op_min7.png b/public/img/op_min7.png new file mode 100644 index 0000000..f039d5f Binary files /dev/null and b/public/img/op_min7.png differ diff --git a/public/img/op_min8.png b/public/img/op_min8.png new file mode 100644 index 0000000..4ff15e3 Binary files /dev/null and b/public/img/op_min8.png differ diff --git a/public/img/op_min9.png b/public/img/op_min9.png new file mode 100644 index 0000000..7511b89 Binary files /dev/null and b/public/img/op_min9.png differ diff --git a/public/img/op_or.png b/public/img/op_or.png new file mode 100644 index 0000000..c3c93b5 Binary files /dev/null and b/public/img/op_or.png differ diff --git a/public/js/module.js b/public/js/module.js new file mode 100644 index 0000000..66ff644 --- /dev/null +++ b/public/js/module.js @@ -0,0 +1,144 @@ + +(function(Icinga) { + + var Bp = function(module) { + /** + * YES, we need Icinga + */ + this.module = module; + + this.initialize(); + + this.module.icinga.logger.debug('BP module loaded'); + }; + + Bp.prototype = { + + initialize: function() + { + /** + * Tell Icinga about our event handlers + */ + this.module.on('mouseenter', 'table.businessprocess th.bptitle', this.titleMouseOver); + this.module.on('mouseleave', 'table.businessprocess th.bptitle', this.titleMouseOut); + this.module.on('click', 'table.businessprocess th', this.titleClicked); + this.module.on('rendered', this.fixOpenedBps); + + this.module.icinga.logger.debug('BP module loaded'); + }, + + /** + * Add 'hovered' class to hovered title elements + * + * TODO: Skip on tablets + */ + titleMouseOver: function (event) { + event.stopPropagation(); + var el = $(event.currentTarget); + el.addClass('hovered'); + }, + + /** + * Remove 'hovered' class from hovered title elements + * + * TODO: Skip on tablets + */ + titleMouseOut: function (event) { + event.stopPropagation(); + var el = $(event.currentTarget); + el.removeClass('hovered'); + }, + + /** + * Handle clicks on operator or title element + * + * Title shows subelement, operator unfolds all subelements + */ + titleClicked: function (event) { + var self = this; + event.stopPropagation(); + event.preventDefault(); + var $el = $(event.currentTarget), + affected = [] + $container = $el.closest('.container'); + if ($el.hasClass('operator')) { + $affected = $el.closest('table').children('tbody') + .children('tr.children').children('td').children('table'); + + // Only if there are child BPs + if ($affected.find('th.operator').length < 1) { + $affected = $el.closest('table'); + } + } else { + $affected = $el.closest('table'); + } + $affected.each(function (key, el) { + var $bptable = $(el).closest('table'); + $bptable.toggleClass('collapsed'); + if ($bptable.hasClass('collapsed')) { + $bptable.find('table').addClass('collapsed'); + } + }); + + $container.data('refreshParams', { + opened: self.listOpenedBps($container) + }); + }, + + fixOpenedBps: function(event) { + var $container = $(event.currentTarget); + var opened = $container.data('refreshParams'); + if (typeof opened === 'undefined' || typeof opened.opened === 'undefined') { + return; + } + + opened = opened.opened; + $.each(opened, function(idx, ids) { + $.each(ids.split('_'), function(idx, id) { + $('#' + id, $container).removeClass('collapsed'); + }); + }); + }, + + /** + * Get a list of all currently opened BPs. + * + * Only get the deepest nodes to keep requests as small as possible + */ + listOpenedBps: function ($container) { + var ids = []; + + $('.businessprocess', $container).add('.businessprocess table', $container) + .not('.collapsed').each(function (key, el) { + var $el = $(el); + if ($el.find('table').not('.collapsed').length === 0) { + var search = true, + this_id = $el.attr('id'), + cnt = 0, + current = el, + parent; + while (search && cnt < 40) { + cnt++; + parent = $(current).parent().closest('table')[0]; + if (!parent || $(parent).hasClass('bps')) { + search = false; + } else { + current = parent; + this_id = parent.id + '_' + this_id; + } + } + + if (this_id) { + ids.push(this_id); + } + } + }); + + return ids; + } + }; + + Icinga.availableModules.bpapp = Bp; + +}(Icinga)); +