From 8ca6cc32b2c789a3149861159ad258f2cb9491e3 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 14:39:39 +0200 Subject: Adding upstream version 2.11.4. Signed-off-by: Daniel Baumann --- .../Web/Controller/MonitoredObjectController.php | 337 ++++++++++++++++++++ .../Web/Helper/PluginOutputHookRenderer.php | 105 +++++++ .../Monitoring/Web/Hook/HostActionsHook.php | 15 + .../Monitoring/Web/Hook/ServiceActionsHook.php | 15 + .../Monitoring/Web/Hook/TimelineProviderHook.php | 15 + .../library/Monitoring/Web/Navigation/Action.php | 123 ++++++++ .../Monitoring/Web/Navigation/HostAction.php | 11 + .../library/Monitoring/Web/Navigation/HostNote.php | 11 + .../MonitoringBadgeNavigationItemRenderer.php | 167 ++++++++++ .../Monitoring/Web/Navigation/ServiceAction.php | 11 + .../Monitoring/Web/Navigation/ServiceNote.php | 11 + .../library/Monitoring/Web/Rest/RestRequest.php | 297 ++++++++++++++++++ .../Monitoring/Web/Widget/CustomVarTable.php | 270 ++++++++++++++++ .../library/Monitoring/Web/Widget/SelectBox.php | 120 ++++++++ .../library/Monitoring/Web/Widget/StateBadges.php | 341 +++++++++++++++++++++ 15 files changed, 1849 insertions(+) create mode 100644 modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php create mode 100644 modules/monitoring/library/Monitoring/Web/Helper/PluginOutputHookRenderer.php create mode 100644 modules/monitoring/library/Monitoring/Web/Hook/HostActionsHook.php create mode 100644 modules/monitoring/library/Monitoring/Web/Hook/ServiceActionsHook.php create mode 100644 modules/monitoring/library/Monitoring/Web/Hook/TimelineProviderHook.php create mode 100644 modules/monitoring/library/Monitoring/Web/Navigation/Action.php create mode 100644 modules/monitoring/library/Monitoring/Web/Navigation/HostAction.php create mode 100644 modules/monitoring/library/Monitoring/Web/Navigation/HostNote.php create mode 100644 modules/monitoring/library/Monitoring/Web/Navigation/Renderer/MonitoringBadgeNavigationItemRenderer.php create mode 100644 modules/monitoring/library/Monitoring/Web/Navigation/ServiceAction.php create mode 100644 modules/monitoring/library/Monitoring/Web/Navigation/ServiceNote.php create mode 100644 modules/monitoring/library/Monitoring/Web/Rest/RestRequest.php create mode 100644 modules/monitoring/library/Monitoring/Web/Widget/CustomVarTable.php create mode 100644 modules/monitoring/library/Monitoring/Web/Widget/SelectBox.php create mode 100644 modules/monitoring/library/Monitoring/Web/Widget/StateBadges.php (limited to 'modules/monitoring/library/Monitoring/Web') 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..014ac43 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php @@ -0,0 +1,337 @@ +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[] = + '
' + . $html + . '
'; + } + } + } + + /** + * 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; + $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 @@ +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 @@ +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 @@ +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) { + $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 @@ +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..4cbdad5 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Web/Widget/CustomVarTable.php @@ -0,0 +1,270 @@ + ['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); + + $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 @@ +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 @@ +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(); + } +} -- cgit v1.2.3