BpApp: initial import of legacy version

This commit is contained in:
Thomas Gelf 2014-10-20 16:26:06 +02:00
commit d2e693ec2a
30 changed files with 1639 additions and 0 deletions

View file

@ -0,0 +1,87 @@
<?php
namespace Icinga\Module\Bpapp\Clicommands;
use Icinga\Cli\Command;
use Icinga\Module\Bpapp\BusinessProcess;
use Icinga\Application\Config;
use Icinga\Module\Monitoring\Backend;
class CheckCommand extends Command
{
protected $config;
protected $bpconf;
protected $bpname;
protected $views;
protected $filename;
protected $backend;
public function init()
{
$this->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';
}
}

View file

@ -0,0 +1,140 @@
<?php
use Icinga\Module\Monitoring\Backend;
use Icinga\Web\Controller\ModuleActionController;
use Icinga\Module\Bpapp\BusinessProcess;
use Icinga\Application\Config;
use Icinga\Web\Widget;
class Bpapp_ProcessController extends ModuleActionController
{
protected $backend;
protected $views;
protected $aliases;
protected $bpname;
protected $bpconf;
protected $filename;
public function init()
{
// $this->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 '<pre>' . print_r($bp, 1) . '</pre>';
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;
}
}

View file

@ -0,0 +1,94 @@
<?php
$months = array(
'Jänner',
'Februar',
'März',
'April',
'Mai',
'Juni',
'Juli',
'August',
'September',
'Oktober',
'November',
'Dezember',
);
?><table style="width: 100%;">
<tr>
<th style="width: 10%;">&nbsp;</th>
<? for ($i = 1; $i <= 12; $i++): ?>
<th style="width: 7.5%; border: 1px solid black;"><?= $months[$i - 1] ?></th>
<? endfor ?>
</tr>
</table>
<div style="overflow: auto">
<table>
<tr>
<? for ($i = 1; $i <= 12; $i++): ?>
<td style="border-right: 1px solid black;"><div style="width: 600px; height: 1px; overflow: hidden;">&nbsp;</div><?= $months[$i - 1] ?></td>
<? endfor ?>
</tr>
</table>
</div>
<div style="overflow: auto; ">
<?php
function stateColor($state)
{
switch($state) {
case 0:
$color = '#0f0';
break;
case 1:
$color = 'orange';
break;
case 2:
$color = '#f00';
break;
default: // und case 3
$color = '#ccc';
}
return $color;
}
$next_color = null;
$color = null;
$start = mktime(0, 0, 0, 0, 0, (int) date('Y'));
$current_offset = $start;
$htm = '';
$cnt = 0;
$last_host = null;
$last_service = null;
foreach ($this->history as $entry) {
if ($entry->hostname !== $last_host || $entry->service !== $last_service) {
echo '<span style="clear: both;" />' . "\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 '<div style="overflow: hidden; height: 10px; float: left; width: ' . $offset . 'px; background-color:' . $color . ';">&nbsp;</div>';
$current_offset += $duration;
$last_host = $entry->hostname;
$last_service = $entry->service;
}
?></div>

View file

@ -0,0 +1,239 @@
<?php
// TODO: This view script is pretty ugly. Clean the mess up, use existing
// view helpers and create new ones (or Widgets) where needed
use Icinga\Module\Bpapp\BpNode;
use Icinga\Module\Bpapp\ServiceNode;
if ($this->showMenu) {
if ($this->compact) {
} else {
?>
<div class="controls">
<?= $this->tabs ?>
</div>
<?php
}
}
function stateName($state)
{
$names = array('ok', 'warning', 'critical', 'unknown');
return $names[$state];
}
function showNode($self, $node, & $slas = array(), $opened_param, $id_prefix = '', $level = 0, $hidden = false)
{
$opened = true;
$htm = '';
$opened_list = array();
foreach ($opened_param as $opened_cmps) {
if (strstr($opened_cmps, '_') === false) {
$key = $opened_cmps;
$opened_cmps = false;
} else {
list($key, $opened_cmps) = preg_split('/_/', $opened_cmps, 2);
}
if (! array_key_exists($key, $opened_list)) {
$opened_list[$key] = array();
}
if ($opened_cmps) {
$opened_list[$key][] = $opened_cmps;
}
}
if ($node instanceof BpNode && $node->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 .= '<table class="'
. ($level === 0 ? 'businessprocess' : '')
. $extra
. '" id="' . md5($id) . '" data-base-target="col2">';
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 .= '<tr><th colspan="2" class="' . $state_classes . ' bptitle">'
. '<span class="collapsible">&nbsp;</span>'
. preg_replace('~^\d{2}\.\s*~', '', $node->getAlias());
/* . ': '
. $node->getState()
. '/'
. $node->getSortingState();
*/
if ($node->isInDowntime()) $htm .= ' <img src="' . $self->baseUrl('/img/bpapp/downtime.gif') . '" alt="" title="' . mt('bpapp', 'Downtime') . '" class="icon" />';
if ($node->isAcknowledged()) $htm .= ' <img src="' . $self->baseUrl('/img/bpapp/ack.gif') . '" alt="" title="' . mt('bpapp', 'Acknowledged') . '" class="icon" />';
if ($node->hasUrl()) {
$htm .= ' <a href="' . $node->getUrl() . '" title="'
. mt('bpapp', 'More information') . ': ' . $node->getUrl()
. '"><img src="'
. $self->baseUrl('/img/bpapp/help.gif')
. '" alt="" class="icon" /></a>';
}
// 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 .= '<span class="inlinepie" title="'
. $sumtxt
. '">'
. implode(',', $node->getStateSummary())
. '</span>';
}
}
// 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(' <span style="float: right; font-weight: normal;">[SLA] <span style="' . $sla_style . '">%0.3f%%</span> (Soll: %0.2f%%)</span>', $sla->value, $sla->level);
}
// Problem info:
if ($node->getState() > 0) {
$problems = array();
foreach ($node->getChildren() as $child) {
if ($child->getState() > 0) {
$problems[] = '<span class="state ' . stateName($child->getState()) . '">' . htmlspecialchars($child->getAlias()) . '</span>';
}
}
$htm .= ': <p class="problems">' . implode(', ', $problems) . '</p>';
}
// END of Problem Info
$htm .= "</th></tr>\n";
if ($node->hasChildren()) {
$htm .= '<tr class="children">'
. '<th class="' . $state_classes . ' operator"' . ($node->hasInfoCommand() ? ' rowspan="2"' : '') . '>'
. $node->getOperator()
. '</th><td>';
//$htm .= '<!--';
if ($node->hasInfoCommand()) {
$htm .= ' <b><i>' . nl2br(htmlspecialchars(strip_tags(
preg_replace('~(?:echo\s+(["\']))+(.+?)\1+~s', '$2',
preg_replace('~\<br\s*/?\>~', "\n", $node->getInfoCommand()
)
)))) . '</i></b></td></tr><tr class="children"><td>';
}
//$htm .= '-->';
foreach ($node->getChildren() as $name => $child) {
$htm .= showNode($self, $child, $slas, $opened_list, $id_prefix . $id . '_', $level + 1, ! $opened);
}
$htm .= "</td></tr>\n";
}
} else {
$htm .= '<tr><td class="service ' . $state_classes . '">';
if ($node->isInDowntime()) $htm .= ' <img src="' . $self->baseUrl('/img/bpapp/downtime.gif') . '" class="icon" alt="" />';
if ($node->isAcknowledged()) $htm .= ' <img src="' . $self->baseUrl('/img/bpapp/ack.gif') . '" class="icon" alt="" />';
$htm .= stateName($node->getState())
. '</td><td><a href="'
. $self->href('monitoring/show/host', array(
'host' => $node->getHostname(),
'backend' => $self->backend
))
. '">'
. $node->getHostname()
. '</a>'
. ($node instanceof ServiceNode
? ' / <a href="' . $self->href('monitoring/show/service', array(
'host' => $node->getHostname(),
'service' => $node->getServiceDescription(),
'backend' => $self->backend
)) . '">' . $node->getServiceDescription() . '</a>'
: '')
/* DEBUG
. ': '
. $node->getState()
. '/'
. $node->getSortingState()*/
;
$htm .= "</td></tr>\n";
}
$htm .= '</table>';
$htm .= "\n";
return $htm;
}
echo '<div class="content">';
if (! is_array($this->opened)) { $this->opened = array(); }
?><? foreach ($this->bp->getRootNodes() as $name => $node): ?>
<?= showNode($this, $node, $this->slas, $this->opened, 'bp_') ?>
<? endforeach ?>
<? if ($this->bp->hasWarnings()): ?>
<h2>Warnings</h2>
<? foreach ($this->bp->getWarnings() as $warning): ?>
<?= $this->escape($warning) ?><br />
<? endforeach ?>
<? endif ?>
</div>

View file

@ -0,0 +1,22 @@
<div class="controls">
<span style="float:right; margin-right: 1em; margin-top: 0.5em;">
<a href="<?= $this->href('bpapp/process/show', array(
'bp' => $this->bpname
)) ?>">Render</a>
</span>
<?= $this->tabs ?>
</div>
<pre>
<?php
$cnt = 0;
$lines = preg_split('~\r?\n~', $this->source);
$len = ceil(log(count($lines), 10));
foreach ($lines as $line) {
$cnt++;
printf("%0" . $len . "d: %s\n", $cnt, $this->escape($line));
}
?>
</pre>

8
configuration.php Normal file
View file

@ -0,0 +1,8 @@
<?php
$section = $this->menuSection($this->translate('Availability'), array(
'icon' => 'img/icons/servicegroup.png',
'priority' => 40
));
$section->add($this->translate('Business Processes'))->setUrl('bpapp/process/show');

187
library/Bpapp/BpNode.php Normal file
View file

@ -0,0 +1,187 @@
<?php
namespace Icinga\Module\Bpapp;
use Exception;
class BpNode extends Node
{
const OP_AND = '&';
const OP_OR = '|';
protected $operator = '&';
protected $url;
protected $info_command;
protected $display = 0;
protected $children;
protected $child_names = array();
protected $alias;
protected $counters;
public function __construct(
BusinessProcess $bp,
$object
/*
$name,
$operator,
$child_names
*/
) {
$this->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;
}
}

View file

@ -0,0 +1,309 @@
<?php
namespace Icinga\Module\Bpapp;
use Exception;
class BusinessProcess
{
const SOFT_STATE = 0;
const HARD_STATE = 1;
protected $ido;
protected $filename;
protected $parsing_line_number;
protected $bps;
protected $state_type = self::HARD_STATE;
protected $warnings = array();
protected $nodes = array();
// protected $object_ids = array();
protected $root_nodes = array();
protected $all_checks = array();
protected $hosts = array();
public function __construct()
{
}
/*
public function getObjectIds()
{
return array_keys($this->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: <var> = <num> of: <var1> + <var2> [+ <varn>]*');
}
$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
)
);
}
}

View file

@ -0,0 +1,21 @@
<?php
namespace Icinga\Module\Bpapp;
class HostNode extends Node
{
protected $hostname;
public function __construct(BusinessProcess $bp, $object)
{
$this->name = $object->hostname . ';Hoststate';
$this->hostname = $object->hostname;
$this->bp = $bp;
$this->setState($object->state);
}
public function getHostname()
{
return $this->hostname;
}
}

158
library/Bpapp/Node.php Normal file
View file

@ -0,0 +1,158 @@
<?php
namespace Icinga\Module\Bpapp;
use Exception;
abstract class Node
{
const FLAG_DOWNTIME = 1;
const FLAG_ACK = 2;
const FLAG_MISSING = 4;
const FLAG_NONE = 8;
const SHIFT_FLAGS = 4;
protected $bp;
protected $parent;
protected $name;
protected $state;
protected $description;
# protected $flags = 0;
protected $ack;
protected $downtime;
protected $recent_problems = array();
protected $duration;
protected $missing = false;
protected static $state_names = array(
'OK',
'WARNING',
'CRITICAL',
'UNKNOWN'
);
abstract public function __construct(BusinessProcess $bp, $object);
public function setMissing($missing = true)
{
$this->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;
}
}

View file

@ -0,0 +1,33 @@
<?php
namespace Icinga\Module\Bpapp;
class ServiceNode extends Node
{
protected $hostname;
protected $service;
public function __construct(BusinessProcess $bp, $object)
{
$this->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;
}
}

5
module.info Normal file
View file

@ -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

192
public/css/module.less Normal file
View file

@ -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;
}

BIN
public/img/ack.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 B

BIN
public/img/downtime.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 B

BIN
public/img/help.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

BIN
public/img/icon_expand.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 B

BIN
public/img/op_and.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 B

BIN
public/img/op_min1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 B

BIN
public/img/op_min2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 B

BIN
public/img/op_min3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 652 B

BIN
public/img/op_min4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

BIN
public/img/op_min5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 633 B

BIN
public/img/op_min6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 B

BIN
public/img/op_min7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

BIN
public/img/op_min8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 B

BIN
public/img/op_min9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 647 B

BIN
public/img/op_or.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 B

144
public/js/module.js Normal file
View file

@ -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));