diff --git a/application/controllers/ListController.php b/application/controllers/ListController.php new file mode 100644 index 0000000..586ce2e --- /dev/null +++ b/application/controllers/ListController.php @@ -0,0 +1,101 @@ +getTabs()->extend(new DashboardAction())->extend(new MenuAction()); + } + + public function hostsAction() + { + $this->addTitleTab( + 'hosts', + mt('monitoring', 'Hosts'), + mt('monitoring', 'List hosts') + ); + + $this->view->hosts = $hosts = $this->backend->select()->from('hoststatus', ['host_name', 'host_display_name']); + $this->applyRestriction('monitoring/filter/objects', $hosts); + $this->filterQuery($hosts); + $this->setupPaginationControl($hosts); + $this->setupLimitControl(); + $this->setupSortControl(['host_display_name' => mt('monitoring', 'Hostname')], $hosts); + + $this->handleTimeRangePickerRequest(); + $this->view->timeRangePicker = $this->renderTimeRangePicker($this->view); + } + + public function servicesAction() + { + $this->addTitleTab( + 'services', + mt('monitoring', 'Services'), + mt('monitoring', 'List services') + ); + + $this->view->services = $services = $this->backend->select()->from('servicestatus', [ + 'host_name', + 'host_display_name', + 'service_description', + 'service_display_name' + ]); + $this->applyRestriction('monitoring/filter/objects', $services); + $this->filterQuery($services); + $this->setupPaginationControl($services); + $this->setupLimitControl(); + $this->setupSortControl([ + 'service_display_name' => mt('monitoring', 'Service Name'), + 'host_display_name' => mt('monitoring', 'Hostname') + ], $services); + + $this->handleTimeRangePickerRequest(); + $this->view->timeRangePicker = $this->renderTimeRangePicker($this->view); + } + + /** + * Apply filters on a DataView + * + * @param DataView $dataView The DataView to apply filters on + */ + protected function filterQuery(DataView $dataView) + { + $this->setupFilterControl( + $dataView, + null, + null, + array_merge(['format', 'stateType', 'addColumns', 'problems'], TimeRangePicker::getAllParameters()) + ); + $this->handleFormatRequest($dataView); + } + + /** + * Add title tab + * + * @param string $action + * @param string $title + * @param string $tip + */ + protected function addTitleTab($action, $title, $tip) + { + $this->getTabs()->add($action, [ + 'title' => $tip, + 'label' => $title, + 'url' => Url::fromRequest(), + 'active' => true + ]); + } +} diff --git a/application/controllers/ShowController.php b/application/controllers/ShowController.php index 849f8c6..3b85366 100644 --- a/application/controllers/ShowController.php +++ b/application/controllers/ShowController.php @@ -4,6 +4,7 @@ namespace Icinga\Module\Graphite\Controllers; use DirectoryIterator; use Icinga\Exception\NotFoundError; +use Icinga\Module\Graphite\Forms\TimeRangePicker\TimeRangePickerTrait; use Icinga\Module\Graphite\GraphiteChart; use Icinga\Module\Graphite\GraphiteUtil; use Icinga\Module\Graphite\GraphiteWeb; @@ -11,6 +12,7 @@ use Icinga\Module\Graphite\GraphiteWebClient; use Icinga\Module\Graphite\GraphTemplate; use Icinga\Module\Graphite\TemplateStore; use Icinga\Web\Controller; +use Icinga\Web\UrlParams; use Icinga\Web\Widget; class ShowController extends Controller @@ -168,15 +170,14 @@ class ShowController extends Controller foreach ($set->loadTemplates() as $key => $template) { if (strpos($template->getFilterString(), '$service') !== false) continue; - $imgParams = array( - 'template' => $key, - 'start' => $view->start, - 'width' => $view->width, - 'height' => $view->height - ); + $imgParams = (new UrlParams()) + ->set('template', $key) + ->set('start', $view->start) + ->set('width', $view->width) + ->set('height', $view->height); if ($this->view->disabledDatasources) { - $imgParams['disabled'] = $this->view->disabledDatasources; + $imgParams->set('disabled', $this->view->disabledDatasources); foreach ($this->view->disabledDatasources as $dis) { if ($template->hasDatasource($dis)) { $template->getDatasource($dis)->disable(); @@ -190,7 +191,7 @@ class ShowController extends Controller ->select() ->from($template->getFilterString()) ->where('hostname', $hostname) - ->getWrappedImageLinks($template, $imgParams); + ->getWrappedImageLinks($template, TimeRangePickerTrait::copyAllRangeParameters($imgParams)); } } @@ -228,15 +229,14 @@ class ShowController extends Controller foreach ($set->loadTemplates() as $key => $template) { if (strpos($template->getFilterString(), '$service') === false) continue; - $imgParams = array( - 'template' => $key, - 'start' => $view->start, - 'width' => $view->width, - 'height' => $view->height - ); + $imgParams = (new UrlParams()) + ->set('template', $key) + ->set('start', $view->start) + ->set('width', $view->width) + ->set('height', $view->height);; if ($this->view->disabledDatasources) { - $imgParams['disabled'] = $this->view->disabledDatasources; + $imgParams->set('disabled', $this->view->disabledDatasources); foreach ($this->view->disabledDatasources as $dis) { if ($template->hasDatasource($dis)) { $template->getDatasource($dis)->disable(); @@ -252,7 +252,7 @@ class ShowController extends Controller ->from($template->getFilterString()) ->where('hostname', $hostname) ->where('service', $service) - ->getWrappedImageLinks($template, $imgParams); + ->getWrappedImageLinks($template, TimeRangePickerTrait::copyAllRangeParameters($imgParams)); } } @@ -329,12 +329,29 @@ class ShowController extends Controller $this->view->disabledDatasources = $this->params->getValues('disabled'); } + /** + * Get time range parameters for Graphite from the URL + * + * @return string[] + */ + protected function getRangeFromTimeRangePicker() + { + $params = $this->getRequest()->getUrl()->getParams(); + $relative = $params->get(TimeRangePickerTrait::getRelativeRangeParameter()); + if ($relative !== null) { + return ["-{$relative}s", null]; + } + + $absolute = TimeRangePickerTrait::getAbsoluteRangeParameters(); + return [$params->get($absolute['start'], '-1hours'), $params->get($absolute['end'])]; + } + protected function handleGraphParams() { if ($this->handledGraphParams === false) { $this->handledGraphParams = true; $view = $this->view; - $view->start = $this->params->shift('start', '-1hours'); + list($view->start, $view->end) = $this->getRangeFromTimeRangePicker(); $view->width = $this->params->shift('width', '300'); $view->height = $this->params->shift('height', '150'); } @@ -347,6 +364,7 @@ class ShowController extends Controller $this->handleGraphParams(); $view = $this->view; $chart->setStart($view->start) + ->setUntil($view->end) ->setWidth($view->width) ->setHeight($view->height) // TODO: handle before diff --git a/application/forms/TimeRangePicker/CommonForm.php b/application/forms/TimeRangePicker/CommonForm.php index c00ce65..2934de3 100644 --- a/application/forms/TimeRangePicker/CommonForm.php +++ b/application/forms/TimeRangePicker/CommonForm.php @@ -36,6 +36,7 @@ class CommonForm extends Form public function init() { $this->setName('form_timerangepickercommon_graphite'); + $this->setAttrib('data-base-target', '_self'); } public function createElements(array $formData) diff --git a/application/forms/TimeRangePicker/CustomForm.php b/application/forms/TimeRangePicker/CustomForm.php index a90834c..ab5aacc 100644 --- a/application/forms/TimeRangePicker/CustomForm.php +++ b/application/forms/TimeRangePicker/CustomForm.php @@ -40,6 +40,7 @@ class CustomForm extends Form public function init() { $this->setName('form_timerangepickercustom_graphite'); + $this->setAttrib('data-base-target', '_self'); } public function createElements(array $formData) diff --git a/application/forms/TimeRangePicker/TimeRangePickerTrait.php b/application/forms/TimeRangePicker/TimeRangePickerTrait.php index 26577a5..cb4f0bd 100644 --- a/application/forms/TimeRangePicker/TimeRangePickerTrait.php +++ b/application/forms/TimeRangePicker/TimeRangePickerTrait.php @@ -2,6 +2,7 @@ namespace Icinga\Module\Graphite\Forms\TimeRangePicker; +use Icinga\Web\Url; use Icinga\Web\UrlParams; trait TimeRangePickerTrait @@ -46,6 +47,33 @@ trait TimeRangePickerTrait return array_values(array_merge(static::getAllRangeParameters(), [static::getRangeCustomizationParameter()])); } + /** + * Copy {@link getAllRangeParameters()} from one {@link UrlParams} instance to another + * + * @param UrlParams|null $copy Defaults to a new instance + * @param UrlParams|null $origin Defaults to the current request's params + * + * @return UrlParams The copy + */ + public static function copyAllRangeParameters(UrlParams $copy = null, UrlParams $origin = null) + { + if ($origin === null) { + $origin = Url::fromRequest()->getParams(); + } + if ($copy === null) { + $copy = new UrlParams(); + } + + foreach (TimeRangePickerTrait::getAllRangeParameters() as $param) { + $value = $origin->get($param); + if ($value !== null) { + $copy->set($param, $value); + } + } + + return $copy; + } + /** * Extract the relative time range (if any) from the given URL parameters * diff --git a/application/views/scripts/list/hosts.phtml b/application/views/scripts/list/hosts.phtml new file mode 100644 index 0000000..73b9938 --- /dev/null +++ b/application/views/scripts/list/hosts.phtml @@ -0,0 +1,64 @@ + +
+ + +
+ + +
+ + +
+ +
+getFilter()->isEmpty()) { + echo '

' . $this->escape($this->translate('Please specify a filter')) . '

'; +} elseif ($hosts->hasResult()) { + foreach ($hosts->peekAhead($compact) as $host) { + echo '
'; + + if (! $compact) { + echo '

' + . $this->qlink( + $host->host_name === $host->host_display_name + ? $host->host_display_name + : $host->host_display_name . ' (' . $this->escape($host->host_name) . ')', + Url::fromPath('monitoring/host/show', ['host' => $host->host_name]), + null, + ['data-base-target' => '_next'] + ) + . '

'; + } + echo EmbedGraphs::host($host->host_name); + + echo '
'; + } + + if (! $compact && $hosts->hasMore()) { + echo ''; + } +} else { + echo '

' . $this->escape(mt('monitoring', 'No hosts found matching the filter.')) . '

'; +} +?> +
diff --git a/application/views/scripts/list/services.phtml b/application/views/scripts/list/services.phtml new file mode 100644 index 0000000..1f108c8 --- /dev/null +++ b/application/views/scripts/list/services.phtml @@ -0,0 +1,72 @@ + +
+ + +
+ + +
+ + +
+ +
+getFilter()->isEmpty()) { + echo '

' . $this->escape($this->translate('Please specify a filter')) . '

'; +} elseif ($services->hasResult()) { + foreach ($services->peekAhead($compact) as $service) { + if (! $compact) { + echo '

' + . $this->qlink( + $service->host_name === $service->host_display_name + ? $service->host_display_name + : $service->host_display_name . ' (' . $this->escape($service->host_name) . ')', + Url::fromPath('monitoring/host/show', ['host' => $service->host_name]), + null, + ['data-base-target' => '_next'] + ) + . ': ' + . $this->qlink( + $service->service_description === $service->service_display_name + ? $service->service_display_name + : $service->service_display_name . ' (' . $this->escape($service->service_description) . ')', + Url::fromPath('monitoring/service/show', [ + 'host' => $service->host_name, + 'service' => $service->service_description + ]), + null, + ['data-base-target' => '_next'] + ) + . '

'; + } + echo EmbedGraphs::service($service->host_name, $service->service_description); + } + + if (! $compact && $services->hasMore()) { + echo ''; + } +} else { + echo '

' . $this->escape(mt('monitoring', 'No services found matching the filter.')) . '

'; +} +?> +
diff --git a/application/views/scripts/show/host.phtml b/application/views/scripts/show/host.phtml index d6dc97c..712cded 100644 --- a/application/views/scripts/show/host.phtml +++ b/application/views/scripts/show/host.phtml @@ -1,21 +1,30 @@ +
tabs ?>

hostname ?>

+
images as $type => $imgs): ?> 0): ?> -

escape(ucfirst($type)) ?>

-templates[$type] ?> -partial( - 'show/legend.phtml', - array( - 'template' => $template, - 'disabledDatasources' => $this->disabledDatasources - ) -) ?> +
+{$this->escape(ucfirst($type))}"; + echo $this->partial( + 'show/legend.phtml', + array( + 'template' => $this->templates[$type], + 'disabledDatasources' => $this->disabledDatasources + ) + ); +} +?> $url): ?> @@ -23,4 +32,9 @@ + + +

translate('No graphs found') ?>

+ +
diff --git a/application/views/scripts/show/service.phtml b/application/views/scripts/show/service.phtml index f072481..ddabecd 100644 --- a/application/views/scripts/show/service.phtml +++ b/application/views/scripts/show/service.phtml @@ -1,21 +1,30 @@ +
tabs ?>

hostname ?>: service ?>

+
images as $type => $imgs): ?> 0): ?> -

escape(ucfirst($type)) ?>

-templates[$type] ?> -partial( - 'show/legend.phtml', - array( - 'template' => $template, - 'disabledDatasources' => $this->disabledDatasources - ) -) ?> +
+{$this->escape(ucfirst($type))}"; + echo $this->partial( + 'show/legend.phtml', + array( + 'template' => $this->templates[$type], + 'disabledDatasources' => $this->disabledDatasources + ) + ); +} +?> $url): ?> @@ -23,4 +32,9 @@ + + +

translate('No graphs found') ?>

+ +
diff --git a/configuration.php b/configuration.php index 52470fd..6d39c75 100644 --- a/configuration.php +++ b/configuration.php @@ -2,7 +2,10 @@ /** @var \Icinga\Application\Modules\Module $this */ -$this->menuSection(N_('Graphite'), ['icon' => 'chart-area'])->setUrl('graphite/show/overview'); +/** @var \Icinga\Application\Modules\MenuItemContainer $section */ +$section = $this->menuSection(N_('Graphite'), ['icon' => 'chart-area'])->setUrl('graphite/show/overview'); +$section->add(N_('Hosts'), ['url' => 'graphite/list/hosts']); +$section->add(N_('Services'), ['url' => 'graphite/list/services']); $this->provideConfigTab('backend', array( 'title' => $this->translate('Configure the Graphite Web backend'), diff --git a/library/Graphite/EmbedGraphs.php b/library/Graphite/EmbedGraphs.php new file mode 100644 index 0000000..66212fc --- /dev/null +++ b/library/Graphite/EmbedGraphs.php @@ -0,0 +1,66 @@ +href('graphite/show/host', ['host' => $host])); + } + + /** + * Embed all graphs of the given service of the given host + * + * @param string $host + * @param string $service + * + * @return string + */ + public static function service($host, $service) + { + return static::url(static::getView()->href('graphite/show/service', [ + 'host' => $host, + 'service' => $service + ])); + } + + /** + * Return a
which causes the framework JS to embed the given URL + * + * @param Url $url + * + * @return string + */ + protected static function url(Url $url) + { + TimeRangePickerTrait::copyAllRangeParameters($url->getParams()); + + // TODO(ak): EL says "
is enough", + // but this seems not to work for me + return '
'; + } + + /** + * Get the current response view + * + * @return View + */ + protected static function getView() + { + return Icinga::app()->getViewRenderer()->view; + } +} diff --git a/library/Graphite/GraphiteChart.php b/library/Graphite/GraphiteChart.php index 13d2f1a..d09e00a 100644 --- a/library/Graphite/GraphiteChart.php +++ b/library/Graphite/GraphiteChart.php @@ -12,6 +12,11 @@ class GraphiteChart protected $from = '-4hours'; + /** + * @var string + */ + protected $until; + protected $showLegend = true; protected $height = 200; @@ -70,9 +75,32 @@ class GraphiteChart return $this->from; } + /** + * Get {@link until} + * + * @return string + */ + public function getUntil() + { + return $this->until; + } + + /** + * Set {@link until} + * + * @param string $until + * + * @return $this + */ + public function setUntil($until) + { + $this->until = $until; + return $this; + } + protected function getParams() { - return array( + $params = [ 'height' => $this->height, 'width' => $this->width, '_salt' => time() . '.000', @@ -90,7 +118,13 @@ class GraphiteChart // 'hideYAxis' => 'true', // 'format' => 'svg', // 'pieMode' => 'average', - ); + ]; + + if ($this->until !== null) { + $params['until'] = $this->until; + } + + return $params; } public function getUrl() diff --git a/library/Graphite/ProvidedHook/Monitoring/DetailviewExtension.php b/library/Graphite/ProvidedHook/Monitoring/DetailviewExtension.php new file mode 100644 index 0000000..541b8b0 --- /dev/null +++ b/library/Graphite/ProvidedHook/Monitoring/DetailviewExtension.php @@ -0,0 +1,38 @@ +getType()) { + case 'host': + /** @var Host $object */ + return $this->getGeneric() . EmbedGraphs::host($object->getName()); + case 'service': + /** @var Service $object */ + return $this->getGeneric() . EmbedGraphs::service($object->getHost()->getName(), $object->getName()); + } + } + + /** + * Get monitored object type independend HTML to use + * + * @return string + */ + protected function getGeneric() + { + $this->handleTimeRangePickerRequest(); + return '

' . mt('graphite', 'Graphs') . '

' . $this->renderTimeRangePicker($this->getView()); + } +} diff --git a/library/Graphite/ProvidedHook/Monitoring/HostActions.php b/library/Graphite/ProvidedHook/Monitoring/HostActions.php deleted file mode 100644 index cdd63a3..0000000 --- a/library/Graphite/ProvidedHook/Monitoring/HostActions.php +++ /dev/null @@ -1,17 +0,0 @@ - Url::fromPath('graphite/show/host', array('host' => $host->host_name)) - ); - } -} diff --git a/library/Graphite/ProvidedHook/Monitoring/ServiceActions.php b/library/Graphite/ProvidedHook/Monitoring/ServiceActions.php deleted file mode 100644 index 29214e1..0000000 --- a/library/Graphite/ProvidedHook/Monitoring/ServiceActions.php +++ /dev/null @@ -1,22 +0,0 @@ - Url::fromPath( - 'graphite/show/service', - array( - 'host' => $service->host_name, - 'service' => $service->service_description, - )) - ); - } -} diff --git a/library/Graphite/Web/Controller/TimeRangePickerTrait.php b/library/Graphite/Web/Controller/TimeRangePickerTrait.php index 6f67a4e..807785d 100644 --- a/library/Graphite/Web/Controller/TimeRangePickerTrait.php +++ b/library/Graphite/Web/Controller/TimeRangePickerTrait.php @@ -53,7 +53,10 @@ trait TimeRangePickerTrait $view->translate('Custom', 'TimeRangePicker'), $url->with(TimeRangePicker::getRangeCustomizationParameter(), '1'), null, - ['class' => 'button-link'] + [ + 'class' => 'button-link', + 'data-base-target' => '_self' + ] ); } diff --git a/public/css/module.less b/public/css/module.less index 751c10f..89a8e94 100644 --- a/public/css/module.less +++ b/public/css/module.less @@ -50,6 +50,16 @@ ul.legend { } } +div.graphs-host { + display: inline-block; + vertical-align: top; + margin-right: 2em; + + > div.container[data-icinga-url] { + width: 300px; + } +} + form[name=form_timerangepickercommon_graphite] select { width: 7.5em; margin-right: 0.25em; diff --git a/run.php b/run.php index e4a8474..c64206b 100644 --- a/run.php +++ b/run.php @@ -1,5 +1,4 @@ provideHook('monitoring/HostActions'); -$this->provideHook('monitoring/ServiceActions'); - +/** @var \Icinga\Application\Modules\Module $this */ +$this->provideHook('monitoring/DetailviewExtension');