diff --git a/application/controllers/GraphController.php b/application/controllers/GraphController.php index ce90f05..c8dc7db 100644 --- a/application/controllers/GraphController.php +++ b/application/controllers/GraphController.php @@ -2,24 +2,19 @@ namespace Icinga\Module\Graphite\Controllers; -use Icinga\Application\Modules\Module; use Icinga\Exception\Http\HttpBadRequestException; use Icinga\Exception\Http\HttpNotFoundException; use Icinga\Module\Graphite\Graphing\GraphingTrait; -use Icinga\Module\Graphite\ProvidedHook\Icingadb\IcingadbSupport; use Icinga\Module\Graphite\Util\IcingadbUtils; -use Icinga\Module\Graphite\Web\Controller\MonitoringAwareController; use Icinga\Module\Graphite\Web\Widget\Graphs; -use Icinga\Module\Monitoring\Object\Host; -use Icinga\Module\Monitoring\Object\MonitoredObject; -use Icinga\Module\Monitoring\Object\Service; +use Icinga\Module\Icingadb\Model\Host; +use Icinga\Module\Icingadb\Model\Service; +use Icinga\Web\Controller; use Icinga\Web\UrlParams; -use Icinga\Module\Icingadb\Model\Service as IcingadbService; -use Icinga\Module\Icingadb\Model\Host as IcingadbHost; use ipl\Orm\Model; use ipl\Stdlib\Filter; -class GraphController extends MonitoringAwareController +class GraphController extends Controller { use GraphingTrait; @@ -62,64 +57,12 @@ class GraphController extends MonitoringAwareController } } - public function hostAction() - { - if (Module::exists('icingadb') && IcingadbSupport::useIcingaDbAsBackend()) { - $this->icingadbHost(); - return; - } - - $hostName = $this->filterParams->getRequired('host.name'); - $checkCommandColumn = '_host_' . Graphs::getObscuredCheckCommandCustomVar(); - $host = $this->applyMonitoringRestriction( - $this->backend->select()->from('hoststatus', ['host_check_command', $checkCommandColumn]) - ) - ->where('host_name', $hostName) - ->limit(1) // just to be sure to save a few CPU cycles - ->fetchRow(); - - if ($host === false) { - throw new HttpNotFoundException('%s', $this->translate('No such host')); - } - - $this->supplyImage(new Host($this->backend, $hostName), $host->host_check_command, $host->$checkCommandColumn); - } - public function serviceAction() - { - if (Module::exists('icingadb') && IcingadbSupport::useIcingaDbAsBackend()) { - $this->icingadbService(); - return; - } - - $hostName = $this->filterParams->getRequired('host.name'); - $serviceName = $this->filterParams->getRequired('service.name'); - $checkCommandColumn = '_service_' . Graphs::getObscuredCheckCommandCustomVar(); - $service = $this->applyMonitoringRestriction( - $this->backend->select()->from('servicestatus', ['service_check_command', $checkCommandColumn]) - ) - ->where('host_name', $hostName) - ->where('service_description', $serviceName) - ->limit(1) // just to be sure to save a few CPU cycles - ->fetchRow(); - - if ($service === false) { - throw new HttpNotFoundException('%s', $this->translate('No such service')); - } - - $this->supplyImage( - new Service($this->backend, $hostName, $serviceName), - $service->service_check_command, - $service->$checkCommandColumn - ); - } - - private function icingadbService() { $hostName = $this->filterParams->getRequired('host.name'); $serviceName = $this->filterParams->getRequired('service.name'); $icingadbUtils = IcingadbUtils::getInstance(); - $query = IcingadbService::on($icingadbUtils->getDb()) + $query = Service::on($icingadbUtils->getDb()) ->with('state') ->with('host'); @@ -130,7 +73,7 @@ class GraphController extends MonitoringAwareController $icingadbUtils->applyRestrictions($query); - /** @var IcingadbService $service */ + /** @var Service $service */ $service = $query->first(); if ($service === null) { @@ -146,16 +89,16 @@ class GraphController extends MonitoringAwareController ); } - private function icingadbHost() + public function hostAction() { $hostName = $this->filterParams->getRequired('host.name'); $icingadbUtils = IcingadbUtils::getInstance(); - $query = IcingadbHost::on($icingadbUtils->getDb())->with('state'); + $query = Host::on($icingadbUtils->getDb())->with('state'); $query->filter(Filter::equal('host.name', $hostName)); $icingadbUtils->applyRestrictions($query); - /** @var IcingadbHost $host */ + /** @var Host $host */ $host = $query->first(); if ($host === null) { @@ -172,12 +115,12 @@ class GraphController extends MonitoringAwareController } /** - * Do all monitored object type independend actions + * Do all monitored object type independent actions * - * @param MonitoredObject|Model $object The object to render the graphs for - * @param string $checkCommand The check command of the object we supply an image for - * @param string|null $obscuredCheckCommand The "real" check command (if any) of the object we - * display graphs for + * @param Model $object The object to render the graphs for + * @param string $checkCommand The check command of the object we supply an image for + * @param string|null $obscuredCheckCommand The "real" check command (if any) of the object we + * display graphs for */ protected function supplyImage($object, $checkCommand, $obscuredCheckCommand) { diff --git a/application/controllers/MonitoringGraphController.php b/application/controllers/MonitoringGraphController.php new file mode 100644 index 0000000..583c859 --- /dev/null +++ b/application/controllers/MonitoringGraphController.php @@ -0,0 +1,155 @@ +filterParams = clone $this->getRequest()->getUrl()->getParams(); + + foreach ($this->graphParamsNames as $paramName) { + $this->graphParams[$paramName] = $this->filterParams->shift($paramName); + } + } + + public function hostAction() + { + $hostName = $this->filterParams->getRequired('host.name'); + $checkCommandColumn = '_host_' . Graphs::getObscuredCheckCommandCustomVar(); + $host = $this->applyMonitoringRestriction( + $this->backend->select()->from('hoststatus', ['host_check_command', $checkCommandColumn]) + ) + ->where('host_name', $hostName) + ->limit(1) // just to be sure to save a few CPU cycles + ->fetchRow(); + + if ($host === false) { + throw new HttpNotFoundException('%s', $this->translate('No such host')); + } + + $this->supplyImage(new Host($this->backend, $hostName), $host->host_check_command, $host->$checkCommandColumn); + } + + public function serviceAction() + { + $hostName = $this->filterParams->getRequired('host.name'); + $serviceName = $this->filterParams->getRequired('service.name'); + $checkCommandColumn = '_service_' . Graphs::getObscuredCheckCommandCustomVar(); + $service = $this->applyMonitoringRestriction( + $this->backend->select()->from('servicestatus', ['service_check_command', $checkCommandColumn]) + ) + ->where('host_name', $hostName) + ->where('service_description', $serviceName) + ->limit(1) // just to be sure to save a few CPU cycles + ->fetchRow(); + + if ($service === false) { + throw new HttpNotFoundException('%s', $this->translate('No such service')); + } + + $this->supplyImage( + new Service($this->backend, $hostName, $serviceName), + $service->service_check_command, + $service->$checkCommandColumn + ); + } + + /** + * Do all monitored object type independend actions + * + * @param MonitoredObject $object The object to render the graphs for + * @param string $checkCommand The check command of the object we supply an image for + * @param string|null $obscuredCheckCommand The "real" check command (if any) of the object we + * display graphs for + */ + protected function supplyImage($object, $checkCommand, $obscuredCheckCommand) + { + if (isset($this->graphParams['default_template'])) { + $urlParam = 'default_template'; + $templates = $this->getAllTemplates()->getDefaultTemplates(); + } else { + $urlParam = 'template'; + $templates = $this->getAllTemplates()->getTemplates( + $obscuredCheckCommand === null ? $checkCommand : $obscuredCheckCommand + ); + } + + if (! isset($templates[$this->graphParams[$urlParam]])) { + throw new HttpNotFoundException($this->translate('No such template')); + } + + $charts = $templates[$this->graphParams[$urlParam]]->getCharts( + static::getMetricsDataSource(), + $object, + array_map('rawurldecode', $this->filterParams->toArray(false)) + ); + + switch (count($charts)) { + case 0: + throw new HttpNotFoundException($this->translate('No such graph')); + + case 1: + $charts[0] + ->setFrom($this->graphParams['start']) + ->setUntil($this->graphParams['end']) + ->setWidth($this->graphParams['width']) + ->setHeight($this->graphParams['height']) + ->setBackgroundColor($this->graphParams['bgcolor']) + ->setForegroundColor($this->graphParams['fgcolor']) + ->setMajorGridLineColor($this->graphParams['majorGridLineColor']) + ->setMinorGridLineColor($this->graphParams['minorGridLineColor']) + ->setShowLegend((bool) $this->graphParams['legend']) + ->serveImage($this->getResponse()); + + // not falling through, serveImage exits + default: + throw new HttpBadRequestException('%s', $this->translate( + 'Graphite Web yields more than one metric for the given filter.' + . ' Please specify a more precise filter.' + )); + } + } +} diff --git a/library/Graphite/Web/Controller/MonitoringAwareController.php b/library/Graphite/Web/Controller/MonitoringAwareController.php index 59beaba..dca2ebd 100644 --- a/library/Graphite/Web/Controller/MonitoringAwareController.php +++ b/library/Graphite/Web/Controller/MonitoringAwareController.php @@ -2,10 +2,20 @@ namespace Icinga\Module\Graphite\Web\Controller; +use ArrayIterator; use Icinga\Application\Modules\Module; +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filterable; +use Icinga\Exception\ConfigurationError; +use Icinga\Exception\QueryException; use Icinga\Module\Graphite\ProvidedHook\Icingadb\IcingadbSupport; -use Icinga\Module\Monitoring\Controller; +use Icinga\Module\Monitoring\Backend\MonitoringBackend; +use Icinga\Module\Monitoring\Data\CustomvarProtectionIterator; use Icinga\Module\Monitoring\DataView\DataView; +use Icinga\Util\Json; +use Icinga\File\Csv; +use Icinga\Web\Controller; +use Icinga\Web\Url; abstract class MonitoringAwareController extends Controller { @@ -34,6 +44,132 @@ abstract class MonitoringAwareController extends Controller return; } - parent::moduleInit(); + $this->backend = MonitoringBackend::instance($this->_getParam('backend')); + $this->view->url = Url::fromRequest(); + } + + + protected function handleFormatRequest($query) + { + $desiredContentType = $this->getRequest()->getHeader('Accept'); + if ($desiredContentType === 'application/json') { + $desiredFormat = 'json'; + } elseif ($desiredContentType === 'text/csv') { + $desiredFormat = 'csv'; + } else { + $desiredFormat = strtolower($this->params->get('format', 'html')); + } + + if ($desiredFormat !== 'html' && ! $this->params->has('limit')) { + $query->limit(); // Resets any default limit and offset + } + + switch ($desiredFormat) { + case 'sql': + echo '
'
+                    . htmlspecialchars(wordwrap($query->dump()))
+                    . '
'; + exit; + case 'json': + $response = $this->getResponse(); + $response + ->setHeader('Content-Type', 'application/json') + ->setHeader('Cache-Control', 'no-store') + ->setHeader( + 'Content-Disposition', + 'inline; filename=' . $this->getRequest()->getActionName() . '.json' + ) + ->appendBody( + Json::sanitize( + iterator_to_array( + new CustomvarProtectionIterator( + new ArrayIterator($query->fetchAll()) + ) + ) + ) + ) + ->sendResponse(); + exit; + case 'csv': + $response = $this->getResponse(); + $response + ->setHeader('Content-Type', 'text/csv') + ->setHeader('Cache-Control', 'no-store') + ->setHeader( + 'Content-Disposition', + 'attachment; filename=' . $this->getRequest()->getActionName() . '.csv' + ) + ->appendBody((string) Csv::fromQuery(new CustomvarProtectionIterator($query))) + ->sendResponse(); + exit; + } + } + + /** + * Apply a restriction of the authenticated on the given filterable + * + * @param string $name Name of the restriction + * @param Filterable $filterable Filterable to restrict + * + * @return Filterable The filterable having the restriction applied + */ + protected function applyRestriction($name, Filterable $filterable) + { + $filterable->applyFilter($this->getRestriction($name)); + return $filterable; + } + + /** + * Get a restriction of the authenticated + * + * @param string $name Name of the restriction + * + * @return Filter Filter object + * @throws ConfigurationError If the restriction contains invalid filter columns + */ + protected function getRestriction($name) + { + $restriction = Filter::matchAny(); + $restriction->setAllowedFilterColumns(array( + 'host_name', + 'hostgroup_name', + 'instance_name', + 'service_description', + 'servicegroup_name', + function ($c) { + return preg_match('/^_(?:host|service)_/i', $c); + } + )); + foreach ($this->getRestrictions($name) as $filter) { + if ($filter === '*') { + return Filter::matchAll(); + } + try { + $restriction->addFilter(Filter::fromQueryString($filter)); + } catch (QueryException $e) { + throw new ConfigurationError( + $this->translate( + 'Cannot apply restriction %s using the filter %s. You can only use the following columns: %s' + ), + $name, + $filter, + implode(', ', array( + 'instance_name', + 'host_name', + 'hostgroup_name', + 'service_description', + 'servicegroup_name', + '_(host|service)_' + )), + $e + ); + } + } + + if ($restriction->isEmpty()) { + return Filter::matchAll(); + } + + return $restriction; } } diff --git a/run.php b/run.php index 9bda699..6c69bc9 100644 --- a/run.php +++ b/run.php @@ -2,6 +2,8 @@ /** @var \Icinga\Application\Modules\Module $this */ +use Icinga\Module\Graphite\ProvidedHook\Icingadb\IcingadbSupport; + require_once $this->getLibDir() . '/vendor/Psr/Loader.php'; require_once $this->getLibDir() . '/vendor/iplx/Loader.php'; @@ -9,3 +11,22 @@ $this->provideHook('monitoring/DetailviewExtension'); $this->provideHook('icingadb/IcingadbSupport'); $this->provideHook('icingadb/HostDetailExtension'); $this->provideHook('icingadb/ServiceDetailExtension'); + +if (! $this->exists('icingadb') || ! IcingadbSupport::useIcingaDbAsBackend()) { + $this->addRoute('graphite/monitoring-graph/host', new Zend_Controller_Router_Route( + 'graphite/graph/host', + [ + 'controller' => 'monitoring-graph', + 'action' => 'host', + 'module' => 'graphite' + ] + )); + $this->addRoute('graphite/monitoring-graph/service', new Zend_Controller_Router_Route( + 'graphite/graph/service', + [ + 'controller' => 'monitoring-graph', + 'action' => 'service', + 'module' => 'graphite' + ] + )); +}