diff --git a/config/modules/monitoring/menu.ini b/config/modules/monitoring/menu.ini
index 81cf36324..7713dcd40 100644
--- a/config/modules/monitoring/menu.ini
+++ b/config/modules/monitoring/menu.ini
@@ -41,6 +41,11 @@ title = "Services"
url = "monitoring/list/services"
priority = 50
+[Overview.Servicematrix]
+title = "Servicematrix"
+url = "monitoring/list/servicematrix"
+priority = 51
+
[Overview.Servicegroups]
title = "Servicegroups"
url = "monitoring/list/servicegroups"
diff --git a/library/Icinga/Data/PivotTable.php b/library/Icinga/Data/PivotTable.php
index 92878a372..fdf08dcba 100644
--- a/library/Icinga/Data/PivotTable.php
+++ b/library/Icinga/Data/PivotTable.php
@@ -1,14 +1,19 @@
query = $query;
- $this->verticalColumn = $verticalColumn;
- $this->horizontalColumn = $horizontalColumn;
+ $this->xAxisColumn = $xAxisColumn;
+ $this->yAxisColumn = $yAxisColumn;
}
public function limit($limit = null, $offset = null)
@@ -85,51 +97,59 @@ class PivotTable
}
/**
- * Fetch all columns
+ * Fetch the values to label the x axis with
*/
- public function fetchAll()
+ protected function fetchXAxis()
{
- $xcol = $this->horizontalColumn;
- $ycol = $this->verticalColumn;
- $queryX = clone($this->query);
- $queryX->columns($xcol);
- if ($this->limit !== null) {
- $queryX->limit($this->getLimit(), $this->getOffset());
- }
- $queryX->limit(40);
- $listX = $queryX->fetchColumn();
- $queryY = clone($this->query);
+ $queryClass = get_class($this->query);
+ $query = new $queryClass($this->query->getDatasource(), array($this->xAxisColumn));
+ return $query->fetchColumn();
+ }
- $queryY->columns($ycol);
- if ($this->verticalLimit !== null) {
- $queryY->limit($this->getVerticalLimit(), $this->getVerticalOffset());
- }
- $queryY->limit(50);
- $listY = $queryY->fetchColumn();
+ /**
+ * Fetch the values to label the y axis with
+ */
+ protected function fetchYAxis()
+ {
+ $queryClass = get_class($this->query);
+ $query = new $queryClass($this->query->getDatasource(), array($this->yAxisColumn));
+ return $query->fetchColumn();
+ }
+
+ /**
+ * Return the pivot table as array
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ $xAxis = $this->fetchXAxis();
+ $yAxis = $this->fetchYAxis();
+
+ $this->query->where($this->xAxisColumn, $xAxis)->where($this->yAxisColumn, $yAxis);
+ if (!$this->query->hasOrder()) {
+ $this->query->order($this->xAxisColumn)->order($this->yAxisColumn);
+ }
+
+ $emptyrow = new stdClass();
+ foreach ($this->query->getColumns() as $col) {
+ $emptyrow->{$col} = null;
+ }
- // TODO: resetOrder
- $this->query
- ->where($ycol, $listY)
- ->where($xcol, $listX)
- ->order($ycol)
- ->order($xcol);
$pivot = array();
- $emptyrow = (object) array();
- foreach ($this->query->listColumns() as $col) {
- $emptyrow->$col = null;
- }
- foreach ($listY as $y) {
- foreach ($listX as $x) {
+ foreach ($xAxis as $x) {
+ foreach ($yAxis as $y) {
$row = clone($emptyrow);
- $row->$xcol = $x;
- $row->$ycol = $y;
+ $row->{$this->xAxisColumn} = $x;
+ $row->{$this->yAxisColumn} = $y;
$pivot[$y][$x] = $row;
}
}
foreach ($this->query->fetchAll() as $row) {
- $pivot[$row->$ycol][$row->$xcol] = $row;
+ $pivot[$row->{$this->yAxisColumn}][$row->{$this->xAxisColumn}] = $row;
}
+
return $pivot;
}
}
diff --git a/modules/monitoring/application/controllers/ListController.php b/modules/monitoring/application/controllers/ListController.php
index a6b5da9b3..e8ab18d84 100644
--- a/modules/monitoring/application/controllers/ListController.php
+++ b/modules/monitoring/application/controllers/ListController.php
@@ -458,6 +458,33 @@ class Monitoring_ListController extends Controller
$this->view->history = $query->paginate();
}
+ public function servicematrixAction()
+ {
+ $this->view->title = 'Servicematrix';
+ $this->addTitleTab('servicematrix');
+ $dataview = ServiceStatusView::fromRequest(
+ $this->getRequest(),
+ array(
+ 'host_name',
+ 'service_description',
+ 'service_state',
+ 'service_output',
+ 'service_handled'
+ )
+ );
+
+ $this->setupFilterControl($dataview, 'servicematrix');
+ $this->setupSortControl(
+ array(
+ 'service_state' => 'Service status',
+ 'service_description' => 'Service description',
+ 'host_name' => 'Hostname'
+ )
+ );
+
+ $this->view->matrix = $dataview->pivot('service_description', 'host_name')->toArray();
+ }
+
/**
* Apply current users monitoring/filter restrictions to the given query
*
diff --git a/modules/monitoring/application/views/scripts/list/servicematrix.phtml b/modules/monitoring/application/views/scripts/list/servicematrix.phtml
new file mode 100644
index 000000000..78dec4517
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/servicematrix.phtml
@@ -0,0 +1,51 @@
+compact): ?>
+
+ = $this->tabs; ?>
+
+ Sort by = $this->sortControl->render($this); ?>
+
+
+
+
+
+
+matrix as $host_name => $serviceStates): ?>
+
+
+
+ |
+
+ = $service_description; ?> |
+
+
+
+
+
+
+
+
+
+ = $host_name; ?>
+
+ |
+
+service_state !== null): ?>
+
+
+
+ |
+ ·
+
+ |
+
+
+
+
+
+
diff --git a/modules/monitoring/library/Monitoring/DataView/DataView.php b/modules/monitoring/library/Monitoring/DataView/DataView.php
index b5152e4a1..3310a1d6b 100644
--- a/modules/monitoring/library/Monitoring/DataView/DataView.php
+++ b/modules/monitoring/library/Monitoring/DataView/DataView.php
@@ -30,6 +30,7 @@
namespace Icinga\Module\Monitoring\DataView;
use Icinga\Data\BaseQuery;
+use Icinga\Data\PivotTable;
use Icinga\Filter\Filterable;
use Icinga\Filter\Query\Tree;
use Icinga\Module\Monitoring\Backend;
@@ -167,6 +168,19 @@ abstract class DataView implements Filterable
return array();
}
+ /**
+ * Return a pivot table for the given columns based on the current query
+ *
+ * @param string $xAxisColumn The column to use for the x axis
+ * @param string $yAxisColumn The column to use for the y axis
+ *
+ * @return PivotTable
+ */
+ public function pivot($xAxisColumn, $yAxisColumn)
+ {
+ return new PivotTable($this->query, $xAxisColumn, $yAxisColumn);
+ }
+
/**
* Sort the rows, according to the specified sort column and order
*
diff --git a/public/css/icinga/monitoring-colors.less b/public/css/icinga/monitoring-colors.less
index 6c15489eb..fdc2a82de 100644
--- a/public/css/icinga/monitoring-colors.less
+++ b/public/css/icinga/monitoring-colors.less
@@ -501,6 +501,112 @@ div.box.contactgroup div.box.entry p {
/* End of monitoring box element styles */
+/* Monitoring pivot table styles */
+
+table.pivot {
+ width: 100%;
+ table-layout: fixed;
+ border-spacing: 0.2em;
+ border-collapse: separate;
+
+ th, td {
+ width: 0.5em;
+ }
+
+ thead {
+ th {
+ height: 6em;
+ padding: 0 0 1em 1.4em;
+ vertical-align: bottom;
+
+ &:first-of-type {
+ width: 4em;
+ }
+
+ span {
+ font-size: 0.8em;
+ line-height: 2.4em;
+ white-space: nowrap;
+ display: inline-block;
+
+ transform: rotate(-45deg);
+ transform-origin: bottom left;
+ -o-transform: rotate(-45deg);
+ -o-transform-origin: bottom left;
+ -ms-transform: rotate(-45deg);
+ -ms-transform-origin: bottom left;
+ -moz-transform: rotate(-45deg);
+ -moz-transform-origin: bottom left;
+ -webkit-transform: rotate(-45deg);
+ -webkit-transform-origin: bottom left;
+ filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
+ }
+ }
+ }
+
+ tbody {
+ th {
+ width: 4em;
+ font-size: 0.8em;
+ line-height: 2.2em;
+ padding-right: 1.2em;
+
+ a {
+ color: black;
+ text-decoration: none;
+
+ &:hover {
+ color: #666;
+ }
+ }
+ }
+
+ td {
+ padding: 0;
+ text-align: center;
+ border-radius: 0.5em;
+
+ a {
+ padding: 20% 50%;
+ }
+
+ &.state_ok {
+ background-color: @colorOk;
+ }
+
+ &.state_pending {
+ background-color: @colorPending;
+ }
+
+ &.state_warning {
+ background-color: @colorWarning;
+
+ &.handled {
+ background-color: @colorWarningHandled;
+ }
+ }
+
+ &.state_critical {
+ background-color: @colorCritical;
+
+ &.handled {
+ background-color: @colorCriticalHandled;
+ }
+ }
+
+ &.state_unknown {
+ background-color: @colorUnknown;
+
+ &.handled {
+ background-color: @colorUnknownHandled;
+ }
+ }
+ }
+ }
+}
+
+/* End of monitoring pivot table styles */
+
.controls {
font-size: 0.9em;
}