summaryrefslogtreecommitdiffstats
path: root/modules/monitoring/library/Monitoring/Web
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:46:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:46:43 +0000
commit3e02d5aff85babc3ffbfcf52313f2108e313aa23 (patch)
treeb01f3923360c20a6a504aff42d45670c58af3ec5 /modules/monitoring/library/Monitoring/Web
parentInitial commit. (diff)
downloadicingaweb2-3e02d5aff85babc3ffbfcf52313f2108e313aa23.tar.xz
icingaweb2-3e02d5aff85babc3ffbfcf52313f2108e313aa23.zip
Adding upstream version 2.12.1.upstream/2.12.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'modules/monitoring/library/Monitoring/Web')
-rw-r--r--modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php339
-rw-r--r--modules/monitoring/library/Monitoring/Web/Helper/PluginOutputHookRenderer.php105
-rw-r--r--modules/monitoring/library/Monitoring/Web/Hook/HostActionsHook.php15
-rw-r--r--modules/monitoring/library/Monitoring/Web/Hook/ServiceActionsHook.php15
-rw-r--r--modules/monitoring/library/Monitoring/Web/Hook/TimelineProviderHook.php15
-rw-r--r--modules/monitoring/library/Monitoring/Web/Navigation/Action.php123
-rw-r--r--modules/monitoring/library/Monitoring/Web/Navigation/HostAction.php11
-rw-r--r--modules/monitoring/library/Monitoring/Web/Navigation/HostNote.php11
-rw-r--r--modules/monitoring/library/Monitoring/Web/Navigation/Renderer/MonitoringBadgeNavigationItemRenderer.php171
-rw-r--r--modules/monitoring/library/Monitoring/Web/Navigation/ServiceAction.php11
-rw-r--r--modules/monitoring/library/Monitoring/Web/Navigation/ServiceNote.php11
-rw-r--r--modules/monitoring/library/Monitoring/Web/Rest/RestRequest.php297
-rw-r--r--modules/monitoring/library/Monitoring/Web/Widget/CustomVarTable.php272
-rw-r--r--modules/monitoring/library/Monitoring/Web/Widget/SelectBox.php120
-rw-r--r--modules/monitoring/library/Monitoring/Web/Widget/StateBadges.php341
15 files changed, 1857 insertions, 0 deletions
diff --git a/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php b/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php
new file mode 100644
index 0000000..b001ca8
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php
@@ -0,0 +1,339 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Controller;
+
+use Exception;
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Module\Monitoring\Forms\Command\Object\CheckNowCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\DeleteCommentCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\DeleteDowntimeCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ObjectsCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\RemoveAcknowledgementCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ToggleObjectFeaturesCommandForm;
+use Icinga\Module\Monitoring\Hook\DetailviewExtensionHook;
+use Icinga\Module\Monitoring\Hook\ObjectDetailsTabHook;
+use Icinga\Module\Monitoring\Object\Service;
+use Icinga\Web\Hook;
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
+
+/**
+ * Base class for the host and service controller
+ */
+abstract class MonitoredObjectController extends Controller
+{
+ /**
+ * The requested host or service
+ *
+ * @var \Icinga\Module\Monitoring\Object\Host|\Icinga\Module\Monitoring\Object\Host
+ */
+ protected $object;
+
+ /**
+ * URL to redirect to after a command was handled
+ *
+ * @var string
+ */
+ protected $commandRedirectUrl;
+
+ /**
+ * List of visible hooked tabs
+ *
+ * @var ObjectDetailsTabHook[]
+ */
+ protected $tabHooks = [];
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Controller\ActionController For the method documentation.
+ */
+ public function prepareInit()
+ {
+ parent::prepareInit();
+ if (Hook::has('ticket')) {
+ $this->view->tickets = Hook::first('ticket');
+ }
+ if (Hook::has('grapher')) {
+ $this->view->graphers = Hook::all('grapher');
+ }
+ }
+
+ /**
+ * Show a host or service
+ */
+ public function showAction()
+ {
+ $this->setAutorefreshInterval(10);
+ $this->setupQuickActionForms();
+ $auth = $this->Auth();
+ $this->object->populate();
+ $this->handleFormatRequest();
+ $toggleFeaturesForm = new ToggleObjectFeaturesCommandForm(array(
+ 'backend' => $this->backend,
+ 'objects' => $this->object
+ ));
+ $toggleFeaturesForm
+ ->load($this->object)
+ ->handleRequest();
+ $this->view->toggleFeaturesForm = $toggleFeaturesForm;
+ if (! empty($this->object->comments) && $auth->hasPermission('monitoring/command/comment/delete')) {
+ $delCommentForm = new DeleteCommentCommandForm();
+ $delCommentForm->handleRequest();
+ $this->view->delCommentForm = $delCommentForm;
+ }
+ if (! empty($this->object->downtimes) && $auth->hasPermission('monitoring/command/downtime/delete')) {
+ $delDowntimeForm = new DeleteDowntimeCommandForm();
+ $delDowntimeForm->handleRequest();
+ $this->view->delDowntimeForm = $delDowntimeForm;
+ }
+ $this->view->showInstance = $this->backend->select()->from('instance')->count() > 1;
+ $this->view->object = $this->object;
+
+ $this->view->extensionsHtml = array();
+ foreach (Hook::all('Monitoring\DetailviewExtension') as $hook) {
+ /** @var DetailviewExtensionHook $hook */
+
+ try {
+ $html = $hook->setView($this->view)->getHtmlForObject($this->object);
+ } catch (Exception $e) {
+ $html = $this->view->escape($e->getMessage());
+ }
+
+ if ($html) {
+ $module = $this->view->escape($hook->getModule()->getName());
+ $this->view->extensionsHtml[] =
+ '<div class="icinga-module module-' . $module . '" data-icinga-module="' . $module . '">'
+ . $html
+ . '</div>';
+ }
+ }
+ }
+
+ /**
+ * Show the history for a host or service
+ */
+ public function historyAction()
+ {
+ $this->getTabs()->activate('history');
+ $this->view->history = $this->object->fetchEventhistory()->eventhistory;
+ $this->applyRestriction('monitoring/filter/objects', $this->view->history);
+
+ $this->setupLimitControl(50);
+ $this->setupPaginationControl($this->view->history, 50);
+ $this->view->object = $this->object;
+ $this->render('object/detail-history', null, true);
+ }
+
+ /**
+ * Show the content of a custom tab
+ */
+ public function tabhookAction()
+ {
+ $hookName = $this->params->get('hook');
+ $this->getTabs()->activate($hookName);
+
+ $hook = $this->tabHooks[$hookName];
+
+ $this->view->header = $hook->getHeader($this->object, $this->getRequest());
+ $this->view->content = $hook->getContent($this->object, $this->getRequest());
+ $this->view->object = $this->object;
+ $this->render('object/detail-tabhook', null, true);
+ }
+
+ /**
+ * Handle a command form
+ *
+ * @param ObjectsCommandForm $form
+ *
+ * @return ObjectsCommandForm
+ */
+ protected function handleCommandForm(ObjectsCommandForm $form)
+ {
+ $form
+ ->setBackend($this->backend)
+ ->setObjects($this->object)
+ ->setRedirectUrl(Url::fromPath($this->commandRedirectUrl)->setParams($this->params))
+ ->handleRequest();
+ $this->view->form = $form;
+ $this->view->object = $this->object;
+ $this->view->tabs->remove('dashboard');
+ $this->view->tabs->remove('menu-entry');
+ $this->_helper->viewRenderer('partials/command/object-command-form', null, true);
+ $this->setupQuickActionForms();
+ return $form;
+ }
+
+ /**
+ * Export to JSON if requested
+ */
+ protected function handleFormatRequest($query = null)
+ {
+ if ($this->params->get('format') === 'json'
+ || $this->getRequest()->getHeader('Accept') === 'application/json'
+ ) {
+ $payload = (array) $this->object->properties;
+ $payload['vars'] = $this->object->customvars;
+
+ if ($this->hasPermission('*') || ! $this->hasPermission('no-monitoring/contacts')) {
+ $payload['contacts'] = $this->object->contacts->fetchPairs();
+ $payload['contact_groups'] = $this->object->contactgroups->fetchPairs();
+ } else {
+ $payload['contacts'] = [];
+ $payload['contact_groups'] = [];
+ }
+
+ $groupName = $this->object->getType() . 'groups';
+ $payload[$groupName] = $this->object->$groupName;
+ $this->getResponse()->json()
+ ->setSuccessData($payload)
+ ->setAutoSanitize()
+ ->sendResponse();
+ }
+ }
+
+ /**
+ * Acknowledge a problem
+ */
+ abstract public function acknowledgeProblemAction();
+
+ /**
+ * Add a comment
+ */
+ abstract public function addCommentAction();
+
+ /**
+ * Reschedule a check
+ */
+ abstract public function rescheduleCheckAction();
+
+ /**
+ * Schedule a downtime
+ */
+ abstract public function scheduleDowntimeAction();
+
+ /**
+ * Create tabs
+ */
+ protected function createTabs()
+ {
+ $tabs = $this->getTabs();
+ $object = $this->object;
+ if ($object->getType() === $object::TYPE_HOST) {
+ $isService = false;
+ $params = array(
+ 'host' => $object->getName()
+ );
+ if ($this->params->has('service')) {
+ $params['service'] = $this->params->get('service');
+ }
+ } else {
+ $isService = true;
+ /** @var Service $object */
+ $params = array(
+ 'host' => $object->getHost()->getName(),
+ 'service' => $object->getName()
+ );
+ }
+ $tabs->add(
+ 'host',
+ array(
+ 'title' => sprintf(
+ $this->translate('Show detailed information for host %s'),
+ $isService ? $object->getHost()->getName() : $object->getName()
+ ),
+ 'label' => $this->translate('Host'),
+ 'url' => 'monitoring/host/show',
+ 'urlParams' => $params
+ )
+ );
+ if ($isService || $this->params->has('service')) {
+ $tabs->add(
+ 'service',
+ array(
+ 'title' => sprintf(
+ $this->translate('Show detailed information for service %s on host %s'),
+ $isService ? $object->getName() : $this->params->get('service'),
+ $isService ? $object->getHost()->getName() : $object->getName()
+ ),
+ 'label' => $this->translate('Service'),
+ 'url' => 'monitoring/service/show',
+ 'urlParams' => $params
+ )
+ );
+ }
+ $tabs->add(
+ 'services',
+ array(
+ 'title' => sprintf(
+ $this->translate('List all services on host %s'),
+ $isService ? $object->getHost()->getName() : $object->getName()
+ ),
+ 'label' => $this->translate('Services'),
+ 'url' => 'monitoring/host/services',
+ 'urlParams' => $params
+ )
+ );
+ if ($this->backend->hasQuery('eventhistory')) {
+ $tabs->add(
+ 'history',
+ array(
+ 'title' => $isService
+ ? sprintf(
+ $this->translate('Show all event records of service %s on host %s'),
+ $object->getName(),
+ $object->getHost()->getName()
+ )
+ : sprintf($this->translate('Show all event records of host %s'), $object->getName())
+ ,
+ 'label' => $this->translate('History'),
+ 'url' => $isService ? 'monitoring/service/history' : 'monitoring/host/history',
+ 'urlParams' => $params
+ )
+ );
+ }
+
+ /** @var ObjectDetailsTabHook $hook */
+ foreach (Hook::all('Monitoring\\ObjectDetailsTab') as $hook) {
+ $hookName = $hook->getName();
+ if ($hook->shouldBeShown($object, $this->Auth())) {
+ $this->tabHooks[$hookName] = $hook;
+ $tabs->add($hookName, [
+ 'label' => $hook->getLabel(),
+ 'url' => $isService ? 'monitoring/service/tabhook' : 'monitoring/host/tabhook',
+ 'urlParams' => $params + [ 'hook' => $hookName ]
+ ]);
+ }
+ }
+
+ $tabs->extend(new DashboardAction())->extend(new MenuAction());
+ }
+
+ /**
+ * Create quick action forms and pass them to the view
+ */
+ protected function setupQuickActionForms()
+ {
+ $auth = $this->Auth();
+ if ($auth->hasPermission('monitoring/command/schedule-check')
+ || ($auth->hasPermission('monitoring/command/schedule-check/active-only')
+ && $this->object->active_checks_enabled
+ )
+ ) {
+ $this->view->checkNowForm = $checkNowForm = new CheckNowCommandForm();
+ $checkNowForm
+ ->setObjects($this->object)
+ ->handleRequest();
+ }
+ if (! in_array((int) $this->object->state, array(0, 99))
+ && $this->object->acknowledged
+ && $auth->hasPermission('monitoring/command/remove-acknowledgement')
+ ) {
+ $this->view->removeAckForm = $removeAckForm = new RemoveAcknowledgementCommandForm();
+ $removeAckForm
+ ->setObjects($this->object)
+ ->handleRequest();
+ }
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Helper/PluginOutputHookRenderer.php b/modules/monitoring/library/Monitoring/Web/Helper/PluginOutputHookRenderer.php
new file mode 100644
index 0000000..50b6c65
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Helper/PluginOutputHookRenderer.php
@@ -0,0 +1,105 @@
+<?php
+/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Helper;
+
+use Icinga\Application\Logger;
+use Icinga\Web\Hook;
+
+/**
+ * Renderer for plugin output based on hooks
+ */
+class PluginOutputHookRenderer
+{
+ /** @var array */
+ protected $commandMap = [];
+
+ /**
+ * Register PluginOutput hooks
+ *
+ * Map PluginOutput hooks to their responsible commands.
+ *
+ * @return $this
+ */
+ public function registerHooks()
+ {
+ if (! Hook::has('monitoring/PluginOutput')) {
+ return $this;
+ }
+
+ foreach (Hook::all('monitoring/PluginOutput') as $hook) {
+ /** @var \Icinga\Module\Monitoring\Hook\PluginOutputHook $hook */
+ try {
+ $commands = $hook->getCommands();
+ } catch (\Exception $e) {
+ Logger::error(
+ 'Failed to get applicable commands from hook "%s". An error occurred: %s',
+ get_class($hook),
+ $e
+ );
+
+ continue;
+ }
+
+ if (! is_array($commands)) {
+ $commands = [$commands];
+ }
+
+ foreach ($commands as $command) {
+ if (! isset($this->commandMap[$command])) {
+ $this->commandMap[$command] = [];
+ }
+
+ $this->commandMap[$command][] = $hook;
+ }
+ }
+
+ return $this;
+ }
+
+ protected function renderCommand($command, $output, $detail)
+ {
+ if (isset($this->commandMap[$command])) {
+ foreach ($this->commandMap[$command] as $hook) {
+ /** @var \Icinga\Module\Monitoring\Hook\PluginOutputHook $hook */
+
+ try {
+ $output = $hook->render($command, $output, $detail);
+ } catch (\Exception $e) {
+ Logger::error(
+ 'Failed to render plugin output from hook "%s". An error occurred: %s',
+ get_class($hook),
+ $e
+ );
+
+ continue;
+ }
+ }
+ }
+
+ return $output;
+ }
+
+ /**
+ * Render the given plugin output based on the specified check command
+ *
+ * Traverse all hooks which are responsible for the specified check command and call their `render()` methods.
+ *
+ * @param string $command Check command
+ * @param string $output Plugin output
+ * @param bool $detail Whether the output is requested from the detail area
+ *
+ * @return string
+ */
+ public function render($command, $output, $detail)
+ {
+ if (empty($this->commandMap)) {
+ return $output;
+ }
+
+ $output = $this->renderCommand('*', $output, $detail);
+ $output = $this->renderCommand($command, $output, $detail);
+
+ return $output;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Hook/HostActionsHook.php b/modules/monitoring/library/Monitoring/Web/Hook/HostActionsHook.php
new file mode 100644
index 0000000..fdfe18f
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Hook/HostActionsHook.php
@@ -0,0 +1,15 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Hook;
+
+use Icinga\Module\Monitoring\Hook\HostActionsHook as BaseHook;
+
+/**
+ * Compat only
+ *
+ * Please implement hooks in our Hook direcory
+ */
+abstract class HostActionsHook extends BaseHook
+{
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Hook/ServiceActionsHook.php b/modules/monitoring/library/Monitoring/Web/Hook/ServiceActionsHook.php
new file mode 100644
index 0000000..0ffbf45
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Hook/ServiceActionsHook.php
@@ -0,0 +1,15 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Hook;
+
+use Icinga\Module\Monitoring\Hook\ServiceActionsHook as BaseHook;
+
+/**
+ * Compat only
+ *
+ * Please implement hooks in our Hook direcory
+ */
+abstract class ServiceActionsHook extends BaseHook
+{
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Hook/TimelineProviderHook.php b/modules/monitoring/library/Monitoring/Web/Hook/TimelineProviderHook.php
new file mode 100644
index 0000000..f6f110f
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Hook/TimelineProviderHook.php
@@ -0,0 +1,15 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Hook;
+
+use Icinga\Module\Monitoring\Hook\TimelineProviderHook as BaseHook;
+
+/**
+ * Compat only
+ *
+ * Please implement hooks in our Hook direcory
+ */
+abstract class TimelineProviderHook extends BaseHook
+{
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Navigation/Action.php b/modules/monitoring/library/Monitoring/Web/Navigation/Action.php
new file mode 100644
index 0000000..7e4ffe3
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Navigation/Action.php
@@ -0,0 +1,123 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Navigation;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Web\Navigation\NavigationItem;
+use Icinga\Module\Monitoring\Object\Macro;
+use Icinga\Module\Monitoring\Object\MonitoredObject;
+use Icinga\Web\Url;
+
+/**
+ * Action for monitored objects
+ */
+class Action extends NavigationItem
+{
+ /**
+ * Whether this action's macros were already resolved
+ *
+ * @var bool
+ */
+ protected $resolved = false;
+
+ /**
+ * This action's object
+ *
+ * @var MonitoredObject
+ */
+ protected $object;
+
+ /**
+ * The filter to use when being asked whether to render this action
+ *
+ * @var string
+ */
+ protected $filter;
+
+ /**
+ * This action's raw url attribute
+ *
+ * @var string
+ */
+ protected $rawUrl;
+
+ /**
+ * Set this action's object
+ *
+ * @param MonitoredObject $object
+ *
+ * @return $this
+ */
+ public function setObject(MonitoredObject $object)
+ {
+ $this->object = $object;
+ return $this;
+ }
+
+ /**
+ * Return this action's object
+ *
+ * @return MonitoredObject
+ */
+ public function getObject()
+ {
+ return $this->object;
+ }
+
+ /**
+ * Set the filter to use when being asked whether to render this action
+ *
+ * @param string $filter
+ *
+ * @return $this
+ */
+ public function setFilter($filter)
+ {
+ $this->filter = $filter;
+ return $this;
+ }
+
+ /**
+ * Return the filter to use when being asked whether to render this action
+ *
+ * @return string
+ */
+ public function getFilter()
+ {
+ return $this->filter;
+ }
+
+ public function setUrl($url)
+ {
+ if (is_string($url)) {
+ $this->rawUrl = $url;
+ } else {
+ parent::setUrl($url);
+ }
+
+ return $this;
+ }
+
+ public function getUrl()
+ {
+ $url = parent::getUrl();
+ if (! $this->resolved && $url === null && $this->rawUrl !== null) {
+ $this->setUrl(Url::fromPath(Macro::resolveMacros($this->rawUrl, $this->getObject())));
+ $this->resolved = true;
+ return parent::getUrl();
+ } else {
+ return $url;
+ }
+ }
+
+ public function getRender()
+ {
+ if ($this->render === null) {
+ $filter = $this->getFilter();
+ $this->render = $filter ? Filter::fromQueryString($filter)->matches($this->getObject()) : true;
+ }
+
+ return $this->render;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Navigation/HostAction.php b/modules/monitoring/library/Monitoring/Web/Navigation/HostAction.php
new file mode 100644
index 0000000..2e950f1
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Navigation/HostAction.php
@@ -0,0 +1,11 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Navigation;
+
+/**
+ * A host action
+ */
+class HostAction extends Action
+{
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Navigation/HostNote.php b/modules/monitoring/library/Monitoring/Web/Navigation/HostNote.php
new file mode 100644
index 0000000..2cf0cdf
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Navigation/HostNote.php
@@ -0,0 +1,11 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Navigation;
+
+/**
+ * A host note
+ */
+class HostNote extends Action
+{
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Navigation/Renderer/MonitoringBadgeNavigationItemRenderer.php b/modules/monitoring/library/Monitoring/Web/Navigation/Renderer/MonitoringBadgeNavigationItemRenderer.php
new file mode 100644
index 0000000..054e387
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Navigation/Renderer/MonitoringBadgeNavigationItemRenderer.php
@@ -0,0 +1,171 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Navigation\Renderer;
+
+use Exception;
+use Icinga\Application\Logger;
+use Icinga\Authentication\Auth;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filterable;
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+use Icinga\Web\Navigation\Renderer\BadgeNavigationItemRenderer;
+
+/**
+ * Render generic DataView columns as badges in menu items
+ *
+ * It is possible to configure the class of the rendered badge as option 'class', the
+ * columns to fetch using the option 'columns' and the DataView from which the columns
+ * will be fetched using the option 'dataview'.
+ */
+class MonitoringBadgeNavigationItemRenderer extends BadgeNavigationItemRenderer
+{
+ /**
+ * Cached count
+ *
+ * @var int
+ */
+ protected $count;
+
+ /**
+ * Caches the responses for all executed summaries
+ *
+ * @var array
+ */
+ protected static $summaries = array();
+
+ /**
+ * Accumulates all needed columns for a view to allow fetching the needed columns in
+ * one single query
+ *
+ * @var array
+ */
+ protected static $dataViews = array();
+
+ /**
+ * The dataview referred to by the navigation item
+ *
+ * @var string
+ */
+ protected $dataView;
+
+ /**
+ * The columns and titles displayed in the badge
+ *
+ * @var array
+ */
+ protected $columns;
+
+ /**
+ * Set the dataview referred to by the navigation item
+ *
+ * @param string $dataView
+ *
+ * @return $this
+ */
+ public function setDataView($dataView)
+ {
+ $this->dataView = $dataView;
+ return $this;
+ }
+
+ /**
+ * Return the dataview referred to by the navigation item
+ *
+ * @return string
+ */
+ public function getDataView()
+ {
+ return $this->dataView;
+ }
+
+ /**
+ * Set the columns and titles displayed in the badge
+ *
+ * @param array $columns
+ *
+ * @return $this
+ */
+ public function setColumns(array $columns)
+ {
+ $this->columns = $columns;
+ return $this;
+ }
+
+ /**
+ * Return the columns and titles displayed in the badge
+ *
+ * @return array
+ */
+ public function getColumns()
+ {
+ return $this->columns;
+ }
+
+ /**
+ * Apply a restriction on the given data view
+ *
+ * @param string $restriction The name of restriction
+ * @param Filterable $filterable The filterable to restrict
+ *
+ * @return Filterable The filterable
+ */
+ protected static function applyRestriction($restriction, Filterable $filterable)
+ {
+ $restrictions = Filter::matchAny();
+ foreach (Auth::getInstance()->getRestrictions($restriction) as $filter) {
+ if ($filter === '*') {
+ $filterable->addFilter(Filter::matchAll());
+ return $filterable;
+ }
+ $restrictions->addFilter(Filter::fromQueryString($filter));
+ }
+ $filterable->applyFilter($restrictions);
+ return $filterable;
+ }
+
+ /**
+ * Fetch the dataview from the database
+ *
+ * @return object
+ */
+ protected function fetchDataView()
+ {
+ $summary = MonitoringBackend::instance()->select()->from(
+ $this->getDataView(),
+ array_keys($this->getColumns())
+ );
+ static::applyRestriction('monitoring/filter/objects', $summary);
+ return $summary->fetchRow();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCount()
+ {
+ if ($this->count === null) {
+ try {
+ $summary = $this->fetchDataView();
+ } catch (Exception $e) {
+ Logger::debug($e);
+ $this->count = 1;
+ $this->state = static::STATE_UNKNOWN;
+ $this->title = $e->getMessage();
+ return $this->count;
+ }
+ $count = 0;
+ $titles = array();
+ foreach ($this->getColumns() as $column => $title) {
+ if (isset($summary->$column) && $summary->$column > 0) {
+ $titles[] = sprintf($title, $summary->$column);
+ $count += $summary->$column;
+ }
+ }
+ $this->count = $count;
+ $this->title = implode('. ', $titles);
+ }
+
+ return $this->count;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Navigation/ServiceAction.php b/modules/monitoring/library/Monitoring/Web/Navigation/ServiceAction.php
new file mode 100644
index 0000000..a88e94f
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Navigation/ServiceAction.php
@@ -0,0 +1,11 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Navigation;
+
+/**
+ * A service action
+ */
+class ServiceAction extends Action
+{
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Navigation/ServiceNote.php b/modules/monitoring/library/Monitoring/Web/Navigation/ServiceNote.php
new file mode 100644
index 0000000..4858bf5
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Navigation/ServiceNote.php
@@ -0,0 +1,11 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Navigation;
+
+/**
+ * A service note
+ */
+class ServiceNote extends Action
+{
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Rest/RestRequest.php b/modules/monitoring/library/Monitoring/Web/Rest/RestRequest.php
new file mode 100644
index 0000000..fcbe0ca
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Rest/RestRequest.php
@@ -0,0 +1,297 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Rest;
+
+use Exception;
+use Icinga\Application\Logger;
+use Icinga\Util\Json;
+use Icinga\Module\Monitoring\Exception\CurlException;
+
+/**
+ * REST Request
+ */
+class RestRequest
+{
+ /**
+ * Request URI
+ *
+ * @var string
+ */
+ protected $uri;
+
+ /**
+ * Request method
+ *
+ * @var string
+ */
+ protected $method;
+
+ /**
+ * Request content type
+ *
+ * @var string
+ */
+ protected $contentType;
+
+ /**
+ * Whether to authenticate with basic auth
+ *
+ * @var bool
+ */
+ protected $hasBasicAuth;
+
+ /**
+ * Auth username
+ *
+ * @var string
+ */
+ protected $username;
+
+ /**
+ * Auth password
+ *
+ * @var string
+ */
+ protected $password;
+
+ /**
+ * Request payload
+ *
+ * @var mixed
+ */
+ protected $payload;
+
+ /**
+ * Whether strict SSL is enabled
+ *
+ * @var bool
+ */
+ protected $strictSsl = true;
+
+ /**
+ * Request timeout
+ *
+ * @var int
+ */
+ protected $timeout = 30;
+
+ /**
+ * Create a GET REST request
+ *
+ * @param string $uri
+ *
+ * @return static
+ */
+ public static function get($uri)
+ {
+ $request = new static;
+ $request->uri = $uri;
+ $request->method = 'GET';
+ return $request;
+ }
+
+ /**
+ * Create a POST REST request
+ *
+ * @param string $uri
+ *
+ * @return static
+ */
+ public static function post($uri)
+ {
+ $request = new static;
+ $request->uri = $uri;
+ $request->method = 'POST';
+ return $request;
+ }
+
+ /**
+ * Send content type JSON
+ *
+ * @return $this
+ */
+ public function sendJson()
+ {
+ $this->contentType = 'application/json';
+
+ return $this;
+ }
+
+ /**
+ * Set basic auth credentials
+ *
+ * @param string $username
+ * @param string $password
+ *
+ * @return $this
+ */
+ public function authenticateWith($username, $password)
+ {
+ $this->hasBasicAuth = true;
+ $this->username = $username;
+ $this->password = $password;
+
+ return $this;
+ }
+
+ /**
+ * Set request payload
+ *
+ * @param mixed $payload
+ *
+ * @return $this
+ */
+ public function setPayload($payload)
+ {
+ $this->payload = $payload;
+
+ return $this;
+ }
+
+ /**
+ * Disable strict SSL
+ *
+ * @return $this
+ */
+ public function noStrictSsl()
+ {
+ $this->strictSsl = false;
+
+ return $this;
+ }
+
+ /**
+ * Serialize payload according to content type
+ *
+ * @param mixed $payload
+ * @param string $contentType
+ *
+ * @return string
+ */
+ public function serializePayload($payload, $contentType)
+ {
+ switch ($contentType) {
+ case 'application/json':
+ $payload = Json::encode($payload);
+ break;
+ }
+
+ return $payload;
+ }
+
+ /**
+ * Send the request
+ *
+ * @return mixed
+ *
+ * @throws Exception
+ */
+ public function send()
+ {
+ $defaults = array(
+ 'host' => 'localhost',
+ 'path' => '/'
+ );
+
+ $url = array_merge($defaults, parse_url($this->uri));
+
+ if (isset($url['port'])) {
+ $url['host'] .= sprintf(':%u', $url['port']);
+ }
+
+ if (isset($url['query'])) {
+ $url['path'] .= sprintf('?%s', $url['query']);
+ }
+
+ $headers = array(
+ "{$this->method} {$url['path']} HTTP/1.1",
+ "Host: {$url['host']}",
+ "Content-Type: {$this->contentType}",
+ 'Accept: application/json',
+ // Bypass "Expect: 100-continue" timeouts
+ 'Expect:'
+ );
+
+ $options = array(
+ CURLOPT_URL => $this->uri,
+ CURLOPT_TIMEOUT => $this->timeout,
+ // Ignore proxy settings
+ CURLOPT_PROXY => '',
+ CURLOPT_CUSTOMREQUEST => $this->method
+ );
+
+ // Record cURL command line for debugging
+ $curlCmd = array('curl', '-s', '-X', $this->method, '-H', escapeshellarg('Accept: application/json'));
+
+ if ($this->strictSsl) {
+ $options[CURLOPT_SSL_VERIFYHOST] = 2;
+ $options[CURLOPT_SSL_VERIFYPEER] = true;
+ } else {
+ $options[CURLOPT_SSL_VERIFYHOST] = false;
+ $options[CURLOPT_SSL_VERIFYPEER] = false;
+ $curlCmd[] = '-k';
+ }
+
+ if ($this->hasBasicAuth) {
+ $options[CURLOPT_USERPWD] = sprintf('%s:%s', $this->username, $this->password);
+ $curlCmd[] = sprintf('-u %s:%s', escapeshellarg($this->username), escapeshellarg($this->password));
+ }
+
+ if (! empty($this->payload)) {
+ $payload = $this->serializePayload($this->payload, $this->contentType);
+ $options[CURLOPT_POSTFIELDS] = $payload;
+ $curlCmd[] = sprintf('-d %s', escapeshellarg($payload));
+ }
+
+ $options[CURLOPT_HTTPHEADER] = $headers;
+
+ $stream = null;
+ $logger = Logger::getInstance();
+ if ($logger !== null && $logger->getLevel() === Logger::DEBUG) {
+ $stream = fopen('php://temp', 'w');
+ $options[CURLOPT_VERBOSE] = true;
+ $options[CURLOPT_STDERR] = $stream;
+ }
+
+ Logger::debug(
+ 'Executing %s %s',
+ implode(' ', $curlCmd),
+ escapeshellarg($this->uri)
+ );
+
+ $result = $this->curlExec($options);
+
+ if (is_resource($stream)) {
+ rewind($stream);
+ Logger::debug(stream_get_contents($stream));
+ fclose($stream);
+ }
+
+ return Json::decode($result, true);
+ }
+
+ /**
+ * Set up a new cURL handle with the given options and call {@link curl_exec()}
+ *
+ * @param array $options
+ *
+ * @return string The response
+ *
+ * @throws CurlException
+ */
+ protected function curlExec(array $options)
+ {
+ $ch = curl_init();
+ $options[CURLOPT_RETURNTRANSFER] = true;
+ curl_setopt_array($ch, $options);
+ $result = curl_exec($ch);
+
+ if ($result === false) {
+ throw new CurlException('%s', curl_error($ch));
+ }
+
+ curl_close($ch);
+ return $result;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Widget/CustomVarTable.php b/modules/monitoring/library/Monitoring/Web/Widget/CustomVarTable.php
new file mode 100644
index 0000000..b4273b5
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Widget/CustomVarTable.php
@@ -0,0 +1,272 @@
+<?php
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Widget;
+
+use Icinga\Module\Monitoring\Hook\CustomVarRendererHook;
+use Icinga\Module\Monitoring\Object\MonitoredObject;
+use ipl\Html\Attributes;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Html;
+use ipl\Html\HtmlDocument;
+use ipl\Html\HtmlElement;
+use ipl\Html\Text;
+use ipl\Web\Widget\Icon;
+use Closure;
+
+class CustomVarTable extends BaseHtmlElement
+{
+ /** @var iterable The variables */
+ protected $data;
+
+ /** @var ?MonitoredObject The object the variables are bound to */
+ protected $object;
+
+ /** @var Closure Callback to apply hooks */
+ protected $hookApplier;
+
+ /** @var array The groups as identified by hooks */
+ protected $groups = [];
+
+ /** @var string Header title */
+ protected $headerTitle;
+
+ /** @var int The nesting level */
+ protected $level = 0;
+
+ protected $tag = 'table';
+
+ /** @var HtmlElement The table body */
+ protected $body;
+
+ protected $defaultAttributes = [
+ 'class' => ['custom-var-table', 'name-value-table']
+ ];
+
+ /**
+ * Create a new CustomVarTable
+ *
+ * @param iterable $data
+ * @param ?MonitoredObject $object
+ */
+ public function __construct($data, MonitoredObject $object = null)
+ {
+ $this->data = $data;
+ $this->object = $object;
+ $this->body = new HtmlElement('tbody');
+ }
+
+ /**
+ * Set the header to show
+ *
+ * @param string $title
+ *
+ * @return $this
+ */
+ protected function setHeader($title)
+ {
+ $this->headerTitle = (string) $title;
+
+ return $this;
+ }
+
+ /**
+ * Add a new row to the body
+ *
+ * @param mixed $name
+ * @param mixed $value
+ *
+ * @return void
+ */
+ protected function addRow($name, $value)
+ {
+ $this->body->addHtml(new HtmlElement(
+ 'tr',
+ Attributes::create(['class' => "level-{$this->level}"]),
+ new HtmlElement('th', null, Html::wantHtml($name)),
+ new HtmlElement('td', null, Html::wantHtml($value))
+ ));
+ }
+
+ /**
+ * Render a variable
+ *
+ * @param mixed $name
+ * @param mixed $value
+ *
+ * @return void
+ */
+ protected function renderVar($name, $value)
+ {
+ if ($this->object !== null && $this->level === 0) {
+ list($name, $value, $group) = call_user_func($this->hookApplier, $name, $value);
+ if ($group !== null) {
+ $this->groups[$group][] = [$name, $value];
+ return;
+ }
+ }
+
+ $isArray = is_array($value);
+ if (! $isArray && $value instanceof \stdClass) {
+ $value = (array) $value;
+ $isArray = true;
+ }
+
+ switch (true) {
+ case $isArray && is_int(key($value)):
+ $this->renderArray($name, $value);
+ break;
+ case $isArray:
+ $this->renderObject($name, $value);
+ break;
+ default:
+ $this->renderScalar($name, $value);
+ }
+ }
+
+ /**
+ * Render an array
+ *
+ * @param mixed $name
+ * @param array $array
+ *
+ * @return void
+ */
+ protected function renderArray($name, array $array)
+ {
+ $numItems = count($array);
+ $name = (new HtmlDocument())->addHtml(
+ Html::wantHtml($name),
+ Text::create(' (Array)')
+ );
+
+ $this->addRow($name, sprintf(tp('%d item', '%d items', $numItems), $numItems));
+
+ ++$this->level;
+
+ ksort($array);
+ foreach ($array as $key => $value) {
+ $this->renderVar("[$key]", $value);
+ }
+
+ --$this->level;
+ }
+
+ /**
+ * Render an object (associative array)
+ *
+ * @param mixed $name
+ * @param array $object
+ *
+ * @return void
+ */
+ protected function renderObject($name, array $object)
+ {
+ $numItems = count($object);
+ $this->addRow($name, sprintf(tp('%d item', '%d items', $numItems), $numItems));
+
+ ++$this->level;
+
+ ksort($object);
+ foreach ($object as $key => $value) {
+ $this->renderVar($key, $value);
+ }
+
+ --$this->level;
+ }
+
+ /**
+ * Render a scalar
+ *
+ * @param mixed $name
+ * @param mixed $value
+ *
+ * @return void
+ */
+ protected function renderScalar($name, $value)
+ {
+ if ($value === '') {
+ $value = new HtmlElement('span', Attributes::create(['class' => 'empty']), Text::create(t('empty string')));
+ }
+
+ $this->addRow($name, $value);
+ }
+
+ /**
+ * Render a group
+ *
+ * @param string $name
+ * @param iterable $entries
+ *
+ * @return void
+ */
+ protected function renderGroup($name, $entries)
+ {
+ $table = new self($entries);
+
+ /** @var HtmlDocument $wrapper */
+ $wrapper = $this->getWrapper();
+ if ($wrapper === null) {
+ $wrapper = new HtmlDocument();
+ $wrapper->addHtml($this);
+ $this->prependWrapper($wrapper);
+ }
+
+ $wrapper->addHtml($table->setHeader($name));
+ }
+
+ protected function assemble()
+ {
+ if ($this->object !== null) {
+ $this->hookApplier = CustomVarRendererHook::prepareForObject($this->object);
+ }
+
+ if ($this->headerTitle !== null) {
+ $this->getAttributes()
+ ->add('class', 'collapsible')
+ ->add('data-visible-height', 100)
+ ->add('data-toggle-element', 'thead')
+ ->add(
+ 'id',
+ preg_replace('/\s+/', '-', strtolower($this->headerTitle)) . '-customvars'
+ );
+
+ $this->addHtml(new HtmlElement('thead', null, new HtmlElement(
+ 'tr',
+ null,
+ new HtmlElement(
+ 'th',
+ Attributes::create(['colspan' => 2]),
+ new HtmlElement(
+ 'span',
+ null,
+ new Icon('angle-right'),
+ new Icon('angle-down')
+ ),
+ Text::create($this->headerTitle)
+ )
+ )));
+ }
+
+ if (is_array($this->data)) {
+ ksort($this->data);
+ }
+
+ foreach ($this->data as $name => $value) {
+ $this->renderVar($name, $value);
+ }
+
+ $this->addHtml($this->body);
+
+ // Hooks can return objects as replacement for keys, hence a generator is needed for group entries
+ $genGenerator = function ($entries) {
+ foreach ($entries as list($key, $value)) {
+ yield $key => $value;
+ }
+ };
+
+ foreach ($this->groups as $group => $entries) {
+ $this->renderGroup($group, $genGenerator($entries));
+ }
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Widget/SelectBox.php b/modules/monitoring/library/Monitoring/Web/Widget/SelectBox.php
new file mode 100644
index 0000000..48b98ac
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Widget/SelectBox.php
@@ -0,0 +1,120 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Widget;
+
+use Icinga\Web\Form;
+use Icinga\Web\Request;
+use Icinga\Web\Widget\AbstractWidget;
+
+class SelectBox extends AbstractWidget
+{
+ /**
+ * The name of the form that will be created
+ *
+ * @var string
+ */
+ private $name;
+
+ /**
+ * An array containing all intervals with their associated labels
+ *
+ * @var array
+ */
+ private $values;
+
+ /**
+ * The label displayed next to the select box
+ *
+ * @var string
+ */
+ private $label;
+
+ /**
+ * The name of the url parameter to set
+ *
+ * @var string
+ */
+ private $parameter;
+
+ /**
+ * A request object used for initial form population
+ *
+ * @var Request
+ */
+ private $request;
+
+ /**
+ * Create a TimelineIntervalBox
+ *
+ * @param string $name The name of the form that will be created
+ * @param array $values An array containing all intervals with their associated labels
+ * @param string $label The label displayed next to the select box
+ * @param string $param The request parameter name to set
+ */
+ public function __construct($name, array $values, $label = 'Select', $param = 'selection')
+ {
+ $this->name = $name;
+ $this->values = $values;
+ $this->label = $label;
+ $this->parameter = $param;
+ }
+
+ /**
+ * Apply the parameters from the given request on this widget
+ *
+ * @param Request $request The request to use for populating the form
+ */
+ public function applyRequest(Request $request)
+ {
+ $this->request = $request;
+ }
+
+ /**
+ * Return the chosen interval value or null
+ *
+ * @param Request $request The request to fetch the value from
+ *
+ * @return string|null
+ */
+ public function getInterval(Request $request = null)
+ {
+ if ($request === null && $this->request) {
+ $request = $this->request;
+ }
+
+ if ($request) {
+ return $request->getParam('interval');
+ }
+ }
+
+ /**
+ * Renders this widget and returns the HTML as a string
+ *
+ * @return string
+ */
+ public function render()
+ {
+ $form = new Form();
+ $form->setAttrib('class', Form::DEFAULT_CLASSES . ' inline');
+ $form->setMethod('GET');
+ $form->setUidDisabled();
+ $form->setTokenDisabled();
+ $form->setName($this->name);
+ $form->addElement(
+ 'select',
+ $this->parameter,
+ array(
+ 'label' => $this->label,
+ 'multiOptions' => $this->values,
+ 'autosubmit' => true
+ )
+ );
+
+ if ($this->request) {
+ $form->populate($this->request->getParams());
+ }
+
+ return $form;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Widget/StateBadges.php b/modules/monitoring/library/Monitoring/Web/Widget/StateBadges.php
new file mode 100644
index 0000000..fdaac51
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Widget/StateBadges.php
@@ -0,0 +1,341 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Widget;
+
+use Icinga\Web\Form;
+use Icinga\Web\Navigation\Navigation;
+use Icinga\Web\Navigation\NavigationItem;
+use Icinga\Web\Url;
+use Icinga\Web\Widget\AbstractWidget;
+use Icinga\Data\Filter\Filter;
+
+class StateBadges extends AbstractWidget
+{
+ /**
+ * CSS class for the widget
+ *
+ * @var string
+ */
+ const CSS_CLASS = 'state-badges';
+
+ /**
+ * State critical
+ *
+ * @var string
+ */
+ const STATE_CRITICAL = 'state-critical';
+
+ /**
+ * State critical handled
+ *
+ * @var string
+ */
+ const STATE_CRITICAL_HANDLED = 'state-critical handled';
+
+ /**
+ * State down
+ *
+ * @var string
+ */
+ const STATE_DOWN = 'state-down';
+
+ /**
+ * State down handled
+ *
+ * @var string
+ */
+ const STATE_DOWN_HANDLED = 'state-down handled';
+
+ /**
+ * State ok
+ *
+ * @var string
+ */
+ const STATE_OK = 'state-ok';
+
+ /**
+ * State pending
+ *
+ * @var string
+ */
+ const STATE_PENDING = 'state-pending';
+
+ /**
+ * State unknown
+ *
+ * @var string
+ */
+ const STATE_UNKNOWN = 'state-unknown';
+
+ /**
+ * State unknown handled
+ *
+ * @var string
+ */
+ const STATE_UNKNOWN_HANDLED = 'state-unknown handled';
+
+ /**
+ * State unreachable
+ *
+ * @var string
+ */
+ const STATE_UNREACHABLE = 'state-unreachable';
+
+ /**
+ * State unreachable handled
+ *
+ * @var string
+ */
+ const STATE_UNREACHABLE_HANDLED = 'state-unreachable handled';
+
+ /**
+ * State up
+ *
+ * @var string
+ */
+ const STATE_UP = 'state-up';
+
+ /**
+ * State warning
+ *
+ * @var string
+ */
+ const STATE_WARNING = 'state-warning';
+
+ /**
+ * State warning handled
+ *
+ * @var string
+ */
+ const STATE_WARNING_HANDLED = 'state-warning handled';
+
+ /**
+ * State badges
+ *
+ * @var object[]
+ */
+ protected $badges = array();
+
+ /**
+ * Internal counter for badge priorities
+ *
+ * @var int
+ */
+ protected $priority = 1;
+
+ /**
+ * The base filter applied to any badge link
+ *
+ * @var Filter
+ */
+ protected $baseFilter;
+
+ /**
+ * Base URL
+ *
+ * @var Url
+ */
+ protected $url;
+
+ /**
+ * Get the base URL
+ *
+ * @return Url
+ */
+ public function getUrl()
+ {
+ return $this->url;
+ }
+
+ /**
+ * Set the base URL
+ *
+ * @param Url|string $url
+ *
+ * @return $this
+ */
+ public function setUrl($url)
+ {
+ if (! $url instanceof $url) {
+ $url = Url::fromPath($url);
+ }
+ $this->url = $url;
+ return $this;
+ }
+
+ /**
+ * Get the base filter
+ *
+ * @return Filter
+ */
+ public function getBaseFilter()
+ {
+ return $this->baseFilter;
+ }
+
+ /**
+ * Set the base filter
+ *
+ * @param Filter $baseFilter
+ *
+ * @return $this
+ */
+ public function setBaseFilter($baseFilter)
+ {
+ $this->baseFilter = $baseFilter;
+ return $this;
+ }
+
+ /**
+ * Add a state badge
+ *
+ * @param string $state
+ * @param int $count
+ * @param array $filter
+ * @param string $translateSingular
+ * @param string $translatePlural
+ * @param array $translateArgs
+ *
+ * @return $this
+ */
+ public function add(
+ $state,
+ $count,
+ array $filter,
+ $translateSingular,
+ $translatePlural,
+ array $translateArgs = array()
+ ) {
+ $this->badges[$state] = (object) array(
+ 'count' => (int) $count,
+ 'filter' => $filter,
+ 'translateArgs' => $translateArgs,
+ 'translatePlural' => $translatePlural,
+ 'translateSingular' => $translateSingular
+ );
+ return $this;
+ }
+
+ /**
+ * Create a badge
+ *
+ * @param string $state
+ * @param Navigation $badges
+ *
+ * @return $this
+ */
+ public function createBadge($state, Navigation $badges)
+ {
+ if ($this->has($state)) {
+ $badge = $this->get($state);
+ $url = clone $this->url->setParams($badge->filter);
+ if (isset($this->baseFilter)) {
+ $url->addFilter($this->baseFilter);
+ }
+ $badges->addItem(new NavigationItem($state, array(
+ 'attributes' => array('class' => 'badge ' . $state),
+ 'label' => $badge->count,
+ 'priority' => $this->priority++,
+ 'title' => vsprintf(
+ mtp('monitoring', $badge->translateSingular, $badge->translatePlural, $badge->count),
+ $badge->translateArgs
+ ),
+ 'url' => $url
+ )));
+ }
+ return $this;
+ }
+
+ /**
+ * Create a badge group
+ *
+ * @param array $states
+ * @param Navigation $badges
+ *
+ * @return $this
+ */
+ public function createBadgeGroup(array $states, Navigation $badges)
+ {
+ $group = array_intersect_key($this->badges, array_flip($states));
+ if (! empty($group)) {
+ $groupItem = new NavigationItem(
+ uniqid(),
+ array(
+ 'cssClass' => 'state-badge-group',
+ 'label' => '',
+ 'priority' => $this->priority++
+ )
+ );
+ $groupBadges = new Navigation();
+ $groupBadges->setLayout(Navigation::LAYOUT_TABS);
+ foreach (array_keys($group) as $state) {
+ $this->createBadge($state, $groupBadges);
+ }
+ $groupItem->setChildren($groupBadges);
+ $badges->addItem($groupItem);
+ }
+ return $this;
+ }
+
+ /**
+ * Get whether a badge for the given state has been added
+ *
+ * @param string $state
+ *
+ * @return bool
+ */
+ public function has($state)
+ {
+ return isset($this->badges[$state]) && $this->badges[$state]->count;
+ }
+
+ /**
+ * Get the badge for the given state
+ *
+ * @param string $state
+ *
+ * @return object
+ */
+ public function get($state)
+ {
+ return $this->badges[$state];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function render()
+ {
+ $badges = new Navigation();
+ $badges->setLayout(Navigation::LAYOUT_TABS);
+ $this
+ ->createBadgeGroup(
+ array(static::STATE_CRITICAL, static::STATE_CRITICAL_HANDLED),
+ $badges
+ )
+ ->createBadgeGroup(
+ array(static::STATE_DOWN, static::STATE_DOWN_HANDLED),
+ $badges
+ )
+ ->createBadgeGroup(
+ array(static::STATE_WARNING, static::STATE_WARNING_HANDLED),
+ $badges
+ )
+ ->createBadgeGroup(
+ array(static::STATE_UNREACHABLE, static::STATE_UNREACHABLE_HANDLED),
+ $badges
+ )
+ ->createBadgeGroup(
+ array(static::STATE_UNKNOWN, static::STATE_UNKNOWN_HANDLED),
+ $badges
+ )
+ ->createBadge(static::STATE_OK, $badges)
+ ->createBadge(static::STATE_UP, $badges)
+ ->createBadge(static::STATE_PENDING, $badges);
+ return $badges
+ ->getRenderer()
+ ->setCssClass(static::CSS_CLASS)
+ ->render();
+ }
+}