diff --git a/application/controllers/DashboardController.php b/application/controllers/DashboardController.php index 2200448a..7968eb4c 100644 --- a/application/controllers/DashboardController.php +++ b/application/controllers/DashboardController.php @@ -2,7 +2,10 @@ namespace Icinga\Module\Director\Controllers; +use dipl\Html\Html; +use Icinga\Module\Director\Web\Widget\HealthCheckPluginOutput; use Icinga\Module\Director\Dashboard\Dashboard; +use Icinga\Module\Director\Health; use Icinga\Module\Director\Web\Controller\ActionController; use Icinga\Module\Director\Web\Form\DbSelectorForm; @@ -43,7 +46,19 @@ class DashboardController extends ActionController $dashboard = Dashboard::loadByName($name, $this->db()); $this->tabs($dashboard->getTabs())->activate($name); } else { - $this->addSingleTab($this->translate('Overview')); + $this->tabs()->add('main', [ + 'label' => $this->translate('Overview'), + 'url' => 'director' + ])->add('health', [ + 'label' => $this->translate('Health'), + 'url' => 'director/health' + ])->activate('main'); + $state = $this->getHealthState(); + if ($state->isProblem()) { + $this->tabs()->get('health')->setTagParams([ + 'class' => 'state-' . strtolower($state->getName()) + ]); + } } $cntDashboards = 0; @@ -66,4 +81,16 @@ class DashboardController extends ActionController $this->content()->add($msg); } } + + /** + * @return \Icinga\Module\Director\CheckPlugin\PluginState + */ + protected function getHealthState() + { + $health = new Health(); + $health->setDbResourceName($this->getDbResourceName()); + $output = new HealthCheckPluginOutput($health); + + return $output->getState(); + } } diff --git a/application/controllers/HealthController.php b/application/controllers/HealthController.php new file mode 100644 index 00000000..1e2e73b1 --- /dev/null +++ b/application/controllers/HealthController.php @@ -0,0 +1,37 @@ +tabs()->add('main', [ + 'label' => $this->translate('Overview'), + 'url' => 'director' + ])->add('health', [ + 'label' => $this->translate('Health'), + 'url' => 'director/health' + ])->activate('health'); + + $this->addTitle($this->translate('Director Health')); + $health = new Health(); + $health->setDbResourceName($this->getDbResourceName()); + $output = new HealthCheckPluginOutput($health); + $this->content()->add($output); + $this->content()->add([ + Html::tag('h1', ['class' => 'icon-pin'], $this->translate('Hint: Check Plugin')), + Html::tag('p', $this->translate( + 'Did you know that you can run this entire Health Check' + . ' (or just some sections) as an Icinga Check on a regular' + . ' base?' + )) + ]); + } +} diff --git a/library/Director/CheckPlugin/CheckResults.php b/library/Director/CheckPlugin/CheckResults.php index 716cc578..7e20225f 100644 --- a/library/Director/CheckPlugin/CheckResults.php +++ b/library/Director/CheckPlugin/CheckResults.php @@ -28,6 +28,11 @@ class CheckResults $this->state = new PluginState(0); } + public function getName() + { + return $this->name; + } + public function add(CheckResult $result) { $this->results[] = $result; @@ -37,7 +42,26 @@ class CheckResults return $this; } - protected function getStateSummaryString() + public function getStateCounters() + { + return $this->stateCounters; + } + + public function getProblemSummary() + { + $summary = []; + for ($i = 1; $i <= 3; $i++) { + $count = $this->stateCounters[$i]; + if ($count === 0) { + continue; + } + $summary[PluginState::create($i)->getName()] = $count; + } + + return $summary; + } + + public function getStateSummaryString() { $summary = [sprintf( '%d tests OK', diff --git a/library/Director/CheckPlugin/PluginState.php b/library/Director/CheckPlugin/PluginState.php index 4f9ad62f..d68ec703 100644 --- a/library/Director/CheckPlugin/PluginState.php +++ b/library/Director/CheckPlugin/PluginState.php @@ -30,6 +30,11 @@ class PluginState $this->set($state); } + public function isProblem() + { + return $this->state > 0; + } + public function set($state) { $this->state = $this->getNumericStateFor($state); diff --git a/library/Director/Web/Widget/HealthCheckPluginOutput.php b/library/Director/Web/Widget/HealthCheckPluginOutput.php new file mode 100644 index 00000000..a69b67e9 --- /dev/null +++ b/library/Director/Web/Widget/HealthCheckPluginOutput.php @@ -0,0 +1,98 @@ +state = new PluginState('OK'); + $this->health = $health; + $this->process(); + } + + protected function process() + { + $checks = $this->health->getAllChecks(); + + foreach ($checks as $check) { + $this->add([ + $title = Html::tag('h2', $check->getName()), + $ul = Html::tag('ul', ['class' => 'health-check-result']) + ]); + + $problems = $check->getProblemSummary(); + if (! empty($problems)) { + $badges = Html::tag('span', ['class' => 'title-badges']); + foreach ($problems as $state => $count) { + $badges->add(Html::tag('span', [ + 'class' => ['badge', 'state-' . strtolower($state)], + 'title' => $this->translate('Critical Checks'), + ], $count)); + } + $title->add($badges); + } + + foreach ($check->getResults() as $result) { + $ul->add(Html::tag('li', [ + $this->colorizeState($result->getState()->getName()), + $this->colorizeStates($result->getOutput()) + ])->setSeparator(' ')); + } + $this->state->raise($check->getState()); + } + } + + public function getState() + { + return $this->state; + } + + protected function colorizeStates($string) + { + $string = Html::escape($string); + $string = preg_replace_callback( + "/'([^']+)'/", + [$this, 'highlightNames'], + $string + ); + + $string = preg_replace_callback( + '/(OK|WARNING|CRITICAL|UNKNOWN)/', + [$this, 'getColorized'], + $string + ); + + return new HtmlString($string); + } + + protected function colorizeState($state) + { + return Html::tag('span', ['class' => 'badge state-' . strtolower($state)], $state); + } + + protected function highlightNames($match) + { + return '"' . Html::tag('strong', $match[1]) . '"'; + } + + protected function getColorized($match) + { + return $this->colorizeState($match[1]); + } +} diff --git a/public/css/module.less b/public/css/module.less index cdd216f4..6124996b 100644 --- a/public/css/module.less +++ b/public/css/module.less @@ -599,6 +599,26 @@ a { color: @color-pending; } } +ul.tabs a.state-critical { + background-color: @colorCritical; + font-weight: bold; + color: white; +} +ul.tabs a.state-warning { + background-color: @colorWarning; + font-weight: bold; + color: white; +} +ul.tabs a.state-ok { + background-color: @colorOk; + font-weight: bold; + color: white; +} +ul.tabs a.state-unknown { + background-color: @colorUnknown; + font-weight: bold; + color: white; +} a:hover::before { text-decoration: none; @@ -1033,6 +1053,25 @@ form div.hint { /* END of Forms */ +ul.health-check-result { + list-style-type: none; + padding-left: 2em; + margin-bottom: 2em; + li { + line-height: 2em; + } + .badge { + font-weight: bold; + } +} + +.title-badges { + .badge { + font-size: 0.75em; + margin-left: 0.5em; + } +} + span.error { color: @colorCritical; @@ -1040,6 +1079,7 @@ span.error { color: inherit; } } + p.error { color: white; padding: 1em 2em;