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): ?> +
+ tabs; ?> +
+ Sort by sortControl->render($this); ?> +
+
+ +
+ + +matrix as $host_name => $serviceStates): ?> + + + + + + + + + + + + + + + +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; }