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 @@
+
+
+ = $tabs ?>
+ = $paginator ?>
+
+ = $limiter ?>
+ = $sortBox ?>
+
+ = $filterEditor ?>
+ = $timeRangePicker ?>
+
+
+
+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 '
'
+ . $this->qlink(
+ mt('monitoring', 'Show More'),
+ $this->url()->without(array('view', 'limit')),
+ null,
+ [
+ 'class' => 'action-link',
+ 'data-base-target' => '_next'
+ ]
+ )
+ . '
';
+ }
+} 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 @@
+
+
+ = $tabs ?>
+ = $paginator ?>
+
+ = $limiter ?>
+ = $sortBox ?>
+
+ = $filterEditor ?>
+ = $timeRangePicker ?>
+
+
+
+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 '
'
+ . $this->qlink(
+ mt('monitoring', 'Show More'),
+ $this->url()->without(array('view', 'limit')),
+ null,
+ [
+ 'class' => 'action-link',
+ 'data-base-target' => '_next'
+ ]
+ )
+ . '
';
+ }
+} 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 @@
+
= $this->tabs ?>
= $this->hostname ?>
+
images as $type => $imgs): ?>
0): ?>
-
= $this->escape(ucfirst($type)) ?>
-templates[$type] ?>
-= $this->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 @@
+
+
+
= $this->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 @@
+
= $this->tabs ?>
= $this->hostname ?>: = $this->service ?>
+
images as $type => $imgs): ?>
0): ?>
-
= $this->escape(ucfirst($type)) ?>
-templates[$type] ?>
-= $this->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 @@
+
+
+
= $this->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');