diff options
Diffstat (limited to 'application')
-rw-r--r-- | application/clicommands/Icinga2Command.php | 206 | ||||
-rw-r--r-- | application/controllers/ConfigController.php | 30 | ||||
-rw-r--r-- | application/controllers/GraphController.php | 172 | ||||
-rw-r--r-- | application/controllers/HostsController.php | 112 | ||||
-rw-r--r-- | application/controllers/ListController.php | 192 | ||||
-rw-r--r-- | application/controllers/MonitoringGraphController.php | 155 | ||||
-rw-r--r-- | application/controllers/ServicesController.php | 115 | ||||
-rw-r--r-- | application/forms/Config/AdvancedForm.php | 95 | ||||
-rw-r--r-- | application/forms/Config/BackendForm.php | 59 | ||||
-rw-r--r-- | application/forms/TimeRangePicker/CommonForm.php | 196 | ||||
-rw-r--r-- | application/forms/TimeRangePicker/CustomForm.php | 246 | ||||
-rw-r--r-- | application/views/scripts/config/advanced.phtml | 7 | ||||
-rw-r--r-- | application/views/scripts/config/backend.phtml | 7 | ||||
-rw-r--r-- | application/views/scripts/list/hosts.phtml | 67 | ||||
-rw-r--r-- | application/views/scripts/list/services.phtml | 77 | ||||
-rw-r--r-- | application/views/scripts/test/apache.phtml | 12 | ||||
-rw-r--r-- | application/views/scripts/test/cpu.phtml | 28 |
17 files changed, 1776 insertions, 0 deletions
diff --git a/application/clicommands/Icinga2Command.php b/application/clicommands/Icinga2Command.php new file mode 100644 index 0000000..816e063 --- /dev/null +++ b/application/clicommands/Icinga2Command.php @@ -0,0 +1,206 @@ +<?php + +/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Graphite\Clicommands; + +use Icinga\Cli\Command; +use Icinga\Module\Graphite\Graphing\GraphingTrait; +use Icinga\Module\Graphite\Graphing\Template; +use Icinga\Module\Graphite\Util\MacroTemplate; +use Icinga\Module\Graphite\Web\Widget\Graphs; + +class Icinga2Command extends Command +{ + use GraphingTrait; + + /** + * Generate Icinga 2 host and service config based on the present graph templates + * + * The generated (fictive) monitored objects' checks yield random perfdata to be + * written to Graphite as expected by the present graph templates of this module. + * The generated Icinga 2 config can be used to simulate graphs generated based + * on the graph templates. + * + * icingacli graphite icinga2 config + */ + public function configAction() + { + $icinga2CfgObjPrefix = 'IW2_graphite_demo'; + $obscuredCheckCommandCustomVar = Graphs::getObscuredCheckCommandCustomVar(); + + $result = [ + <<<EOT +object CheckCommand "$icinga2CfgObjPrefix" { + command = [ "/usr/bin/printf" ] + arguments = { + "%s" = {{ + var res = " |" + for (label => max in macro("\$$icinga2CfgObjPrefix\$")) { + res += " '" + label + "'=" + (random() % max) + ";" + (max * 0.8) + ";" + (max * 0.9) + ";0;" + max + } + res + }} + } +} +EOT + , + <<<EOT +object HostGroup "$icinga2CfgObjPrefix" { + assign where host.vars.$icinga2CfgObjPrefix +} +EOT + , + <<<EOT +object ServiceGroup "$icinga2CfgObjPrefix" { + assign where service.vars.$icinga2CfgObjPrefix +} +EOT + , + <<<EOT +object Host "{$icinga2CfgObjPrefix}_doesntmatchanycheckcommand" { + check_command = "$icinga2CfgObjPrefix" + check_interval = 30s + vars.$obscuredCheckCommandCustomVar = "doesntmatchanycheckcommand" + vars.$icinga2CfgObjPrefix = { + "dummy1" = 100 + "dummy2" = 100 + "dummy3" = 100 + "dummy4" = 100 + } +} +EOT + , + <<<EOT +apply Service "{$icinga2CfgObjPrefix}_doesntmatchanycheckcommand" { + assign where host.vars.$icinga2CfgObjPrefix + check_command = "$icinga2CfgObjPrefix" + check_interval = 30s + vars.$obscuredCheckCommandCustomVar = "doesntmatchanycheckcommand" + vars.$icinga2CfgObjPrefix = { + "dummy1" = 100 + "dummy2" = 100 + "dummy3" = 100 + "dummy4" = 100 + } +} +EOT + ]; + + foreach (static::getAllTemplates()->getAllTemplates() as $checkCommand => $templates) { + $perfdata = []; + + foreach ($templates as $templateName => $template) { + /** @var Template $template */ + + $urlParams = $template->getUrlParams(); + + switch (isset($urlParams['yUnitSystem']) ? $urlParams['yUnitSystem']->resolve([]) : 'none') { + case 'si': + case 'binary': + $max = 42000000; + break; + + case 'sec': + case 'msec': + $max = 82800; + break; + + default: + $max = 100; + } + + foreach ($template->getCurves() as $curveName => $curve) { + /** @var MacroTemplate $metricFilter */ + $metricFilter = $curve[0]; + + $macros = array_flip($metricFilter->getMacros()); + $service = isset($macros['service_name_template']); + + foreach ($macros as & $macro) { + $macro = ['dummy1', 'dummy2', 'dummy3', 'dummy4']; + } + + $macros['host_name_template'] = ['']; + $macros['service_name_template'] = ['']; + + foreach ($this->cartesianProduct($macros) as $macroValues) { + if ( + preg_match( + '/\A\.[^.]+\.(.+)\.[^.]+\z/', + $metricFilter->resolve($macroValues), + $match + ) + ) { + $perfdata[$match[1]] = $max; + } + } + } + } + + $monObj = $service + ? [ + "apply Service \"{$icinga2CfgObjPrefix}_{$checkCommand}\" {", + " assign where host.vars.$icinga2CfgObjPrefix" + ] + : ["object Host \"{$icinga2CfgObjPrefix}_{$checkCommand}\" {"]; + + $monObj[] = " check_command = \"$icinga2CfgObjPrefix\""; + $monObj[] = ' check_interval = 30s'; + $monObj[] = " vars.$obscuredCheckCommandCustomVar = \"$checkCommand\""; + $monObj[] = " vars.$icinga2CfgObjPrefix = {"; + + foreach ($perfdata as $label => $max) { + $monObj[] = " \"$label\" = $max"; + } + + $monObj[] = ' }'; + $monObj[] = '}'; + + $result[] = implode("\n", $monObj); + } + + echo implode("\n\n", $result) . "\n"; + } + + /** + * Generate the cartesian product of the given array + * + * [ + * 'a' => ['b', 'c'], + * 'd' => ['e', 'f'] + * ] + * + * [ + * ['a' => 'b', 'd' => 'e'], + * ['a' => 'b', 'd' => 'f'], + * ['a' => 'c', 'd' => 'e'], + * ['a' => 'c', 'd' => 'f'] + * ] + * + * @param array[] $input + * + * @return array[] + */ + protected function cartesianProduct(array &$input) + { + $results = [[]]; + + foreach ($input as $key => & $values) { + $nextStep = []; + + foreach ($results as & $result) { + foreach ($values as $value) { + $nextStep[] = array_merge($result, [$key => $value]); + } + } + unset($result); + + $results = & $nextStep; + unset($nextStep); + } + unset($values); + + return $results; + } +} diff --git a/application/controllers/ConfigController.php b/application/controllers/ConfigController.php new file mode 100644 index 0000000..f627e36 --- /dev/null +++ b/application/controllers/ConfigController.php @@ -0,0 +1,30 @@ +<?php + +namespace Icinga\Module\Graphite\Controllers; + +use Icinga\Module\Graphite\Forms\Config\AdvancedForm; +use Icinga\Module\Graphite\Forms\Config\BackendForm; +use Icinga\Web\Controller; + +class ConfigController extends Controller +{ + public function init() + { + $this->assertPermission('config/modules'); + parent::init(); + } + + public function backendAction() + { + $this->view->form = $form = new BackendForm(); + $form->setIniConfig($this->Config())->handleRequest(); + $this->view->tabs = $this->Module()->getConfigTabs()->activate('backend'); + } + + public function advancedAction() + { + $this->view->form = $form = new AdvancedForm(); + $form->setIniConfig($this->Config())->handleRequest(); + $this->view->tabs = $this->Module()->getConfigTabs()->activate('advanced'); + } +} diff --git a/application/controllers/GraphController.php b/application/controllers/GraphController.php new file mode 100644 index 0000000..c8dc7db --- /dev/null +++ b/application/controllers/GraphController.php @@ -0,0 +1,172 @@ +<?php + +namespace Icinga\Module\Graphite\Controllers; + +use Icinga\Exception\Http\HttpBadRequestException; +use Icinga\Exception\Http\HttpNotFoundException; +use Icinga\Module\Graphite\Graphing\GraphingTrait; +use Icinga\Module\Graphite\Util\IcingadbUtils; +use Icinga\Module\Graphite\Web\Widget\Graphs; +use Icinga\Module\Icingadb\Model\Host; +use Icinga\Module\Icingadb\Model\Service; +use Icinga\Web\Controller; +use Icinga\Web\UrlParams; +use ipl\Orm\Model; +use ipl\Stdlib\Filter; + +class GraphController extends Controller +{ + use GraphingTrait; + + /** + * The URL parameters for the graph + * + * @var string[] + */ + protected $graphParamsNames = [ + 'start', 'end', + 'width', 'height', + 'legend', + 'template', 'default_template', + 'bgcolor', 'fgcolor', + 'majorGridLineColor', 'minorGridLineColor' + ]; + + /** + * The URL parameters for metrics filtering + * + * @var UrlParams + */ + protected $filterParams; + + /** + * The URL parameters for the graph + * + * @var string[] + */ + protected $graphParams = []; + + public function init() + { + parent::init(); + + $this->filterParams = clone $this->getRequest()->getUrl()->getParams(); + + foreach ($this->graphParamsNames as $paramName) { + $this->graphParams[$paramName] = $this->filterParams->shift($paramName); + } + } + + public function serviceAction() + { + $hostName = $this->filterParams->getRequired('host.name'); + $serviceName = $this->filterParams->getRequired('service.name'); + $icingadbUtils = IcingadbUtils::getInstance(); + $query = Service::on($icingadbUtils->getDb()) + ->with('state') + ->with('host'); + + $query->filter(Filter::all( + Filter::equal('service.name', $serviceName), + Filter::equal('service.host.name', $hostName) + )); + + $icingadbUtils->applyRestrictions($query); + + /** @var Service $service */ + $service = $query->first(); + + if ($service === null) { + throw new HttpNotFoundException($this->translate('No such service')); + } + + $checkCommandColumn = $service->vars[Graphs::getObscuredCheckCommandCustomVar()] ?? null; + + $this->supplyImage( + $service, + $service->checkcommand_name, + $checkCommandColumn + ); + } + + public function hostAction() + { + $hostName = $this->filterParams->getRequired('host.name'); + $icingadbUtils = IcingadbUtils::getInstance(); + $query = Host::on($icingadbUtils->getDb())->with('state'); + $query->filter(Filter::equal('host.name', $hostName)); + + $icingadbUtils->applyRestrictions($query); + + /** @var Host $host */ + $host = $query->first(); + + if ($host === null) { + throw new HttpNotFoundException($this->translate('No such host')); + } + + $checkCommandColumn = $host->vars[Graphs::getObscuredCheckCommandCustomVar()] ?? null; + + $this->supplyImage( + $host, + $host->checkcommand_name, + $checkCommandColumn + ); + } + + /** + * Do all monitored object type independent actions + * + * @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) + { + 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/application/controllers/HostsController.php b/application/controllers/HostsController.php new file mode 100644 index 0000000..f77281a --- /dev/null +++ b/application/controllers/HostsController.php @@ -0,0 +1,112 @@ +<?php + +/* Icinga Graphite Web | (c) 2022 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Graphite\Controllers; + +use GuzzleHttp\Psr7\ServerRequest; +use Icinga\Module\Graphite\Web\Controller\IcingadbGraphiteController; +use Icinga\Module\Graphite\Web\Controller\TimeRangePickerTrait; +use Icinga\Module\Graphite\Web\Widget\IcingadbGraphs; +use Icinga\Module\Icingadb\Model\Host; +use Icinga\Module\Icingadb\Web\Control\SearchBar\ObjectSuggestions; +use Icinga\Web\Url; +use ipl\Html\HtmlString; +use ipl\Stdlib\Filter; +use ipl\Web\Control\LimitControl; +use ipl\Web\Control\SortControl; + +class HostsController extends IcingadbGraphiteController +{ + use TimeRangePickerTrait; + + public function indexAction() + { + if (! $this->useIcingadbAsBackend) { + $params = urldecode($this->params->get('legacyParams')); + $this->redirectNow(Url::fromPath('graphite/list/hosts')->setQueryString($params)); + } + + // shift graph params to avoid exception + $graphRange = $this->params->shift('graph_range'); + $baseFilter = $graphRange ? Filter::equal('graph_range', $graphRange) : null; + foreach ($this->graphParams as $param) { + $this->params->shift($param); + } + + $this->addTitleTab(t('Hosts')); + + $db = $this->getDb(); + + $hosts = Host::on($db)->with('state'); + $hosts->filter(Filter::like('state.performance_data', '*')); + + $this->applyRestrictions($hosts); + + $limitControl = $this->createLimitControl(); + $paginationControl = $this->createPaginationControl($hosts); + $sortControl = $this->createSortControl($hosts, ['host.display_name' => t('Hostname')]); + + $searchBar = $this->createSearchBar( + $hosts, + array_merge( + [$limitControl->getLimitParam(), $sortControl->getSortParam()], + $this->graphParams + ) + ); + + if ($searchBar->hasBeenSent() && ! $searchBar->isValid()) { + if ($searchBar->hasBeenSubmitted()) { + $filter = $this->getFilter(); + } else { + $this->addControl($searchBar); + $this->sendMultipartUpdate(); + return; + } + } else { + $filter = $searchBar->getFilter(); + } + + $hosts->filter($filter); + + $this->addControl($paginationControl); + $this->addControl($sortControl); + $this->addControl($limitControl); + $this->addControl($searchBar); + $this->handleTimeRangePickerRequest(); + $this->addControl(HtmlString::create($this->renderTimeRangePicker($this->view))); + + $this->addContent( + (new IcingadbGraphs($hosts->execute())) + ->setBaseFilter($baseFilter) + ); + + if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) { + $this->sendMultipartUpdate(); + } + + $this->setAutorefreshInterval(30); + } + + public function completeAction() + { + $suggestions = new ObjectSuggestions(); + $suggestions->setModel(Host::class); + $suggestions->forRequest(ServerRequest::fromGlobals()); + $this->getDocument()->add($suggestions); + } + + public function searchEditorAction() + { + $editor = $this->createSearchEditor( + Host::on($this->getDb()), + array_merge( + [LimitControl::DEFAULT_LIMIT_PARAM, SortControl::DEFAULT_SORT_PARAM], + $this->graphParams + ) + ); + + $this->getDocument()->add($editor); + $this->setTitle(t('Adjust Filter')); + } +} diff --git a/application/controllers/ListController.php b/application/controllers/ListController.php new file mode 100644 index 0000000..46d8321 --- /dev/null +++ b/application/controllers/ListController.php @@ -0,0 +1,192 @@ +<?php + +namespace Icinga\Module\Graphite\Controllers; + +use Icinga\Data\Filter\Filter; +use Icinga\Module\Graphite\Util\TimeRangePickerTools; +use Icinga\Module\Graphite\Web\Controller\MonitoringAwareController; +use Icinga\Module\Graphite\Web\Controller\TimeRangePickerTrait; +use Icinga\Module\Icingadb\Compat\UrlMigrator; +use Icinga\Module\Monitoring\DataView\DataView; +use Icinga\Module\Monitoring\Object\Host; +use Icinga\Module\Monitoring\Object\Service; +use Icinga\Web\Url; +use Icinga\Web\Widget\Tabextension\DashboardAction; +use Icinga\Web\Widget\Tabextension\MenuAction; +use Icinga\Web\Widget\Tabextension\OutputFormat; +use ipl\Web\Filter\QueryString; + +class ListController extends MonitoringAwareController +{ + use TimeRangePickerTrait; + + public function init() + { + parent::init(); + $this->getTabs() + ->extend(new OutputFormat([OutputFormat::TYPE_CSV, OutputFormat::TYPE_JSON])) + ->extend(new DashboardAction()) + ->extend(new MenuAction()); + } + + public function hostsAction() + { + if ($this->useIcingadbAsBackend) { + $legacyParams = urlencode($this->params->toString()); + $params = QueryString::render( + UrlMigrator::transformFilter( + QueryString::parse($this->params->toString()) + ) + ); + + $url = Url::fromPath('graphite/hosts') + ->setQueryString($params); + + if ($legacyParams) { + $url->setParam('legacyParams', $legacyParams); + } + + $this->redirectNow($url); + } + + $this->addTitleTab( + 'hosts', + mt('monitoring', 'Hosts'), + mt('monitoring', 'List hosts') + ); + + $hostsQuery = $this->applyMonitoringRestriction( + $this->backend->select()->from('hoststatus', ['host_name']) + ); + + $hostsQuery->applyFilter(Filter::expression('host_perfdata', '!=', '')); + + $this->view->baseUrl = $baseUrl = Url::fromPath('monitoring/host/show'); + TimeRangePickerTools::copyAllRangeParameters( + $baseUrl->getParams(), + $this->getRequest()->getUrl()->getParams() + ); + + $this->filterQuery($hostsQuery); + $this->setupPaginationControl($hostsQuery); + $this->setupLimitControl(); + $this->setupSortControl(['host_display_name' => mt('monitoring', 'Hostname')], $hostsQuery); + + $hosts = []; + foreach ($hostsQuery->peekAhead($this->view->compact) as $host) { + $host = new Host($this->backend, $host->host_name); + $host->fetch(); + $hosts[] = $host; + } + + $this->handleTimeRangePickerRequest(); + $this->view->timeRangePicker = $this->renderTimeRangePicker($this->view); + $this->view->hosts = $hosts; + $this->view->hasMoreHosts = ! $this->view->compact && $hostsQuery->hasMore(); + + $this->setAutorefreshInterval(30); + } + + public function servicesAction() + { + if ($this->useIcingadbAsBackend) { + $legacyParams = urlencode($this->params->toString()); + $params = QueryString::render( + UrlMigrator::transformFilter( + QueryString::parse($this->params->toString()) + ) + ); + + $url = Url::fromPath('graphite/services') + ->setQueryString($params); + + if ($legacyParams) { + $url->setParam('legacyParams', $legacyParams); + } + + $this->redirectNow($url); + } + + $this->addTitleTab( + 'services', + mt('monitoring', 'Services'), + mt('monitoring', 'List services') + ); + + $servicesQuery = $this->applyMonitoringRestriction( + $this->backend->select()->from('servicestatus', ['host_name', 'service_description']) + ); + + $servicesQuery->applyFilter(Filter::expression('service_perfdata', '!=', '')); + + $this->view->hostBaseUrl = $hostBaseUrl = Url::fromPath('monitoring/host/show'); + TimeRangePickerTools::copyAllRangeParameters( + $hostBaseUrl->getParams(), + $this->getRequest()->getUrl()->getParams() + ); + + $this->view->serviceBaseUrl = $serviceBaseUrl = Url::fromPath('monitoring/service/show'); + TimeRangePickerTools::copyAllRangeParameters( + $serviceBaseUrl->getParams(), + $this->getRequest()->getUrl()->getParams() + ); + + $this->filterQuery($servicesQuery); + $this->setupPaginationControl($servicesQuery); + $this->setupLimitControl(); + $this->setupSortControl([ + 'service_display_name' => mt('monitoring', 'Service Name'), + 'host_display_name' => mt('monitoring', 'Hostname') + ], $servicesQuery); + + $services = []; + foreach ($servicesQuery->peekAhead($this->view->compact) as $service) { + $service = new Service($this->backend, $service->host_name, $service->service_description); + $service->fetch(); + $services[] = $service; + } + + $this->handleTimeRangePickerRequest(); + $this->view->timeRangePicker = $this->renderTimeRangePicker($this->view); + $this->view->services = $services; + $this->view->hasMoreServices = ! $this->view->compact && $servicesQuery->hasMore(); + + $this->setAutorefreshInterval(30); + } + + /** + * 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', 'graphs_limit'], + TimeRangePickerTools::getAllRangeParameters() + ) + ); + $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/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 @@ +<?php + +namespace Icinga\Module\Graphite\Controllers; + +use Icinga\Exception\Http\HttpBadRequestException; +use Icinga\Exception\Http\HttpNotFoundException; +use Icinga\Module\Graphite\Graphing\GraphingTrait; +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\Web\UrlParams; + +class MonitoringGraphController extends MonitoringAwareController +{ + use GraphingTrait; + + /** + * The URL parameters for the graph + * + * @var string[] + */ + protected $graphParamsNames = [ + 'start', 'end', + 'width', 'height', + 'legend', + 'template', 'default_template', + 'bgcolor', 'fgcolor', + 'majorGridLineColor', 'minorGridLineColor' + ]; + + /** + * The URL parameters for metrics filtering + * + * @var UrlParams + */ + protected $filterParams; + + /** + * The URL parameters for the graph + * + * @var string[] + */ + protected $graphParams = []; + + public function init() + { + parent::init(); + + $this->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/application/controllers/ServicesController.php b/application/controllers/ServicesController.php new file mode 100644 index 0000000..212ad1f --- /dev/null +++ b/application/controllers/ServicesController.php @@ -0,0 +1,115 @@ +<?php + +namespace Icinga\Module\Graphite\Controllers; + +use GuzzleHttp\Psr7\ServerRequest; +use Icinga\Module\Graphite\Web\Controller\IcingadbGraphiteController; +use Icinga\Module\Graphite\Web\Controller\TimeRangePickerTrait; +use Icinga\Module\Graphite\Web\Widget\IcingadbGraphs; +use Icinga\Module\Icingadb\Model\Service; +use Icinga\Module\Icingadb\Web\Control\SearchBar\ObjectSuggestions; +use Icinga\Web\Url; +use ipl\Html\HtmlString; +use ipl\Stdlib\Filter; +use ipl\Web\Control\LimitControl; +use ipl\Web\Control\SortControl; + +class ServicesController extends IcingadbGraphiteController +{ + use TimeRangePickerTrait; + + public function indexAction() + { + if (! $this->useIcingadbAsBackend) { + $params = urldecode($this->params->get('legacyParams')); + $this->redirectNow(Url::fromPath('graphite/list/services')->setQueryString($params)); + } + + // shift graph params to avoid exception + $graphRange = $this->params->shift('graph_range'); + $baseFilter = $graphRange ? Filter::equal('graph_range', $graphRange) : null; + foreach ($this->graphParams as $param) { + $this->params->shift($param); + } + + $this->addTitleTab(t('Services')); + + $db = $this->getDb(); + + $services = Service::on($db) + ->with('state') + ->with('host'); + $services->filter(Filter::like('state.performance_data', '*')); + + $this->applyRestrictions($services); + + $limitControl = $this->createLimitControl(); + $paginationControl = $this->createPaginationControl($services); + $sortControl = $this->createSortControl($services, [ + 'service.display_name' => t('Servicename'), + 'host.display_name' => t('Hostname') + ]); + + $searchBar = $this->createSearchBar( + $services, + array_merge( + [$limitControl->getLimitParam(), $sortControl->getSortParam()], + $this->graphParams + ) + ); + + if ($searchBar->hasBeenSent() && ! $searchBar->isValid()) { + if ($searchBar->hasBeenSubmitted()) { + $filter = $this->getFilter(); + } else { + $this->addControl($searchBar); + $this->sendMultipartUpdate(); + return; + } + } else { + $filter = $searchBar->getFilter(); + } + + $services->filter($filter); + + $this->addControl($paginationControl); + $this->addControl($sortControl); + $this->addControl($limitControl); + $this->addControl($searchBar); + $this->handleTimeRangePickerRequest(); + $this->addControl(HtmlString::create($this->renderTimeRangePicker($this->view))); + + $this->addContent( + (new IcingadbGraphs($services->execute())) + ->setBaseFilter($baseFilter) + ); + + if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) { + $this->sendMultipartUpdate(); + } + + $this->setAutorefreshInterval(30); + } + + public function completeAction() + { + $suggestions = new ObjectSuggestions(); + $suggestions->setModel(Service::class); + $suggestions->forRequest(ServerRequest::fromGlobals()); + $this->getDocument()->add($suggestions); + } + + public function searchEditorAction() + { + $editor = $this->createSearchEditor( + Service::on($this->getDb()), + array_merge( + [LimitControl::DEFAULT_LIMIT_PARAM, SortControl::DEFAULT_SORT_PARAM], + $this->graphParams + ) + ); + + $this->getDocument()->add($editor); + $this->setTitle(t('Adjust Filter')); + } +} diff --git a/application/forms/Config/AdvancedForm.php b/application/forms/Config/AdvancedForm.php new file mode 100644 index 0000000..1fc196b --- /dev/null +++ b/application/forms/Config/AdvancedForm.php @@ -0,0 +1,95 @@ +<?php + +namespace Icinga\Module\Graphite\Forms\Config; + +use Icinga\Forms\ConfigForm; +use Icinga\Module\Graphite\Web\Form\Validator\MacroTemplateValidator; +use Zend_Validate_Regex; + +class AdvancedForm extends ConfigForm +{ + public function init() + { + $this->setName('form_config_graphite_advanced'); + $this->setSubmitLabel($this->translate('Save Changes')); + } + + public function createElements(array $formData) + { + $this->addElements([ + [ + 'number', + 'ui_default_time_range', + [ + 'label' => $this->translate('Default time range'), + 'description' => $this->translate('The default time range for graphs'), + 'min' => 0, + 'value' => 1 + ] + ], + [ + 'select', + 'ui_default_time_range_unit', + [ + 'label' => $this->translate('Default time range unit'), + 'description' => $this->translate('The above range\'s unit'), + 'multiOptions' => [ + 'minutes' => $this->translate('Minutes'), + 'hours' => $this->translate('Hours'), + 'days' => $this->translate('Days'), + 'weeks' => $this->translate('Weeks'), + 'months' => $this->translate('Months'), + 'years' => $this->translate('Years') + ], + 'value' => 'hours' + ] + ], + [ + 'checkbox', + 'ui_disable_no_graphs_found', + [ + 'label' => $this->translate('Disable "no graphs found"'), + 'description' => $this->translate( + 'If no graphs were found for a monitored object, just display nothing at all' + ), + ] + ], + [ + 'text', + 'icinga_graphite_writer_host_name_template', + [ + 'label' => $this->translate('Host name template'), + 'description' => $this->translate( + 'The value of your Icinga 2 GraphiteWriter\'s' + . ' attribute host_name_template (if specified)' + ), + 'validators' => [new MacroTemplateValidator()] + ] + ], + [ + 'text', + 'icinga_graphite_writer_service_name_template', + [ + 'label' => $this->translate('Service name template'), + 'description' => $this->translate( + 'The value of your Icinga 2 GraphiteWriter\'s' + . ' attribute service_name_template (if specified)' + ), + 'validators' => [new MacroTemplateValidator()] + ] + ], + [ + 'text', + 'icinga_customvar_obscured_check_command', + [ + 'label' => $this->translate('Obscured check command custom variable'), + 'description' => $this->translate( + 'The Icinga custom variable with the "actual" check command obscured' + . ' by e.g. check_by_ssh (defaults to check_command)' + ), + 'validators' => [new Zend_Validate_Regex('/\A\w*\z/')] + ] + ] + ]); + } +} diff --git a/application/forms/Config/BackendForm.php b/application/forms/Config/BackendForm.php new file mode 100644 index 0000000..90e0af2 --- /dev/null +++ b/application/forms/Config/BackendForm.php @@ -0,0 +1,59 @@ +<?php + +namespace Icinga\Module\Graphite\Forms\Config; + +use Icinga\Forms\ConfigForm; +use Icinga\Module\Graphite\Web\Form\Validator\HttpUserValidator; + +class BackendForm extends ConfigForm +{ + public function init() + { + $this->setName('form_config_graphite_backend'); + $this->setSubmitLabel($this->translate('Save Changes')); + } + + public function createElements(array $formData) + { + $this->addElements([ + [ + 'text', + 'graphite_url', + [ + 'required' => true, + 'label' => $this->translate('Graphite Web URL'), + 'description' => $this->translate('URL to your Graphite Web'), + 'validators' => ['UrlValidator'] + ] + ], + [ + 'text', + 'graphite_user', + [ + 'label' => $this->translate('Graphite Web user'), + 'description' => $this->translate( + 'A user with access to your Graphite Web via HTTP basic authentication' + ), + 'validators' => [new HttpUserValidator()] + ] + ], + [ + 'password', + 'graphite_password', + [ + 'renderPassword' => true, + 'label' => $this->translate('Graphite Web password'), + 'description' => $this->translate('The above user\'s password') + ] + ], + [ + 'checkbox', + 'graphite_insecure', + [ + 'label' => $this->translate('Connect insecurely'), + 'description' => $this->translate('Check this to not verify the remote\'s TLS certificate') + ] + ] + ]); + } +} diff --git a/application/forms/TimeRangePicker/CommonForm.php b/application/forms/TimeRangePicker/CommonForm.php new file mode 100644 index 0000000..21e2096 --- /dev/null +++ b/application/forms/TimeRangePicker/CommonForm.php @@ -0,0 +1,196 @@ +<?php + +namespace Icinga\Module\Graphite\Forms\TimeRangePicker; + +use Icinga\Module\Graphite\Util\TimeRangePickerTools; +use Icinga\Web\Form; +use Zend_Form_Element_Select; + +class CommonForm extends Form +{ + /** + * The selectable units with themselves in seconds + * + * One month equals 30 days and one year equals 365.25 days. This should cover enough cases. + * + * @var int[] + */ + protected $rangeFactors = [ + 'minutes' => 60, + 'hours' => 3600, + 'days' => 86400, + 'weeks' => 604800, + 'months' => 2592000, + 'years' => 31557600 + ]; + + /** + * The elements' default values + * + * @var string[]|null + */ + protected $defaultFormData; + + public function init() + { + $this->setName('form_timerangepickercommon_graphite'); + $this->setAttrib('data-base-target', '_self'); + $this->setAttrib('class', 'icinga-form icinga-controls inline'); + } + + public function createElements(array $formData) + { + $this->addElements([ + $this->createSelect( + 'minutes', + $this->translate('Minutes'), + $this->translate('Show the last … minutes'), + [5, 10, 15, 30, 45], + $this->translate('%d minute'), + $this->translate('%d minutes') + ), + $this->createSelect( + 'hours', + $this->translate('Hours'), + $this->translate('Show the last … hours'), + [1, 2, 3, 6, 12, 18], + $this->translate('%d hour'), + $this->translate('%d hours') + ), + $this->createSelect( + 'days', + $this->translate('Days'), + $this->translate('Show the last … days'), + range(1, 6), + $this->translate('%d day'), + $this->translate('%d days') + ), + $this->createSelect( + 'weeks', + $this->translate('Weeks'), + $this->translate('Show the last … weeks'), + range(1, 4), + $this->translate('%d week'), + $this->translate('%d weeks') + ), + $this->createSelect( + 'months', + $this->translate('Months'), + $this->translate('Show the last … months'), + [1, 2, 3, 6, 9], + $this->translate('%d month'), + $this->translate('%d months') + ), + $this->createSelect( + 'years', + $this->translate('Years'), + $this->translate('Show the last … years'), + range(1, 3), + $this->translate('%d year'), + $this->translate('%d years') + ) + ]); + + $this->urlToForm(); + + $this->defaultFormData = $this->getValues(); + } + + public function onSuccess() + { + $this->formToUrl(); + $this->getRedirectUrl()->remove(array_values(TimeRangePickerTools::getAbsoluteRangeParameters())); + } + + /** + * Create a common range picker for a specific time unit + * + * @param string $name + * @param string $label + * @param string $description + * @param int[] $options + * @param string $singular + * @param string $plural + * + * @return Zend_Form_Element_Select + */ + protected function createSelect($name, $label, $description, array $options, $singular, $plural) + { + $multiOptions = ['' => $label]; + foreach ($options as $option) { + $multiOptions[$option] = sprintf($option === 1 ? $singular : $plural, $option); + } + + $element = $this->createElement('select', $name, [ + 'label' => $label, + 'description' => $description, + 'multiOptions' => $multiOptions, + 'title' => $description, + 'autosubmit' => true + ]); + + $decorators = $element->getDecorators(); + $element->setDecorators([ + 'Zend_Form_Decorator_ViewHelper' => $decorators['Zend_Form_Decorator_ViewHelper'] + ]); + + return $element; + } + + /** + * Set this form's elements' default values based on the redirect URL's parameters + */ + protected function urlToForm() + { + $params = $this->getRedirectUrl()->getParams(); + $seconds = TimeRangePickerTools::getRelativeSeconds($params); + + if ( + $seconds === null + && count(array_intersect_key( + $params->toArray(false), + array_keys(TimeRangePickerTools::getAllRangeParameters()) + )) === 0 + ) { + $seconds = TimeRangePickerTools::getDefaultRelativeTimeRange(); + } + + if ($seconds !== null) { + if ($seconds !== false) { + foreach ($this->rangeFactors as $unit => $factor) { + /** @var Zend_Form_Element_Select $element */ + $element = $this->getElement($unit); + + $options = $element->getMultiOptions(); + unset($options['']); + + foreach ($options as $option => $_) { + if ($seconds === $option * $factor) { + $element->setValue((string) $option); + return; + } + } + } + } + + $params->remove(TimeRangePickerTools::getRelativeRangeParameter()); + } + } + + /** + * Change the redirect URL's parameters based on this form's elements' values + */ + protected function formToUrl() + { + $formData = $this->getValues(); + foreach ($this->rangeFactors as $unit => $factor) { + if ($formData[$unit] !== '' && $formData[$unit] !== $this->defaultFormData[$unit]) { + $this->getRedirectUrl()->setParam( + TimeRangePickerTools::getRelativeRangeParameter(), + (string) ((int) $formData[$unit] * $factor) + ); + return; + } + } + } +} diff --git a/application/forms/TimeRangePicker/CustomForm.php b/application/forms/TimeRangePicker/CustomForm.php new file mode 100644 index 0000000..89b5833 --- /dev/null +++ b/application/forms/TimeRangePicker/CustomForm.php @@ -0,0 +1,246 @@ +<?php + +namespace Icinga\Module\Graphite\Forms\TimeRangePicker; + +use DateInterval; +use DateTime; +use DateTimeZone; +use Icinga\Module\Graphite\Util\TimeRangePickerTools; +use Icinga\Module\Graphite\Web\Form\Decorator\Proxy; +use Icinga\Web\Form; + +class CustomForm extends Form +{ + /** + * @var string + */ + protected $dateTimeFormat = 'Y-m-d\TH:i'; + + /** + * @var string + */ + protected $timestamp = '/^(?:0|-?[1-9]\d*)$/'; + + /** + * Right now + * + * @var DateTime + */ + protected $now; + + public function init() + { + $this->setName('form_timerangepickercustom_graphite'); + $this->setAttrib('data-base-target', '_self'); + } + + public function createElements(array $formData) + { + $this->addElements([ + [ + 'date', + 'start_date', + [ + 'placeholder' => 'YYYY-MM-DD', + 'label' => $this->translate('Start'), + 'description' => $this->translate('Start of the date/time range') + ] + ], + [ + 'time', + 'start_time', + [ + 'placeholder' => 'HH:MM', + 'label' => $this->translate('Start'), + 'description' => $this->translate('Start of the date/time range') + ] + ], + [ + 'date', + 'end_date', + [ + 'placeholder' => 'YYYY-MM-DD', + 'label' => $this->translate('End'), + 'description' => $this->translate('End of the date/time range') + ] + ], + [ + 'time', + 'end_time', + [ + 'placeholder' => 'HH:MM', + 'label' => $this->translate('End'), + 'description' => $this->translate('End of the date/time range') + ] + ] + ]); + + $this->groupDateTime('start'); + $this->groupDateTime('end'); + + $this->setSubmitLabel($this->translate('Update')); + + $this->urlToForm('start', $this->getRelativeTimestamp()); + $this->urlToForm('end'); + } + + public function addSubmitButton() + { + $result = parent::addSubmitButton(); + + $this->getElement('btn_submit')->class = 'flyover-toggle'; + + return $result; + } + + public function onSuccess() + { + $start = $this->formToUrl('start', '00:00'); + $end = $this->formToUrl('end', '23:59', 'PT59S'); + if ($start > $end) { + $absoluteRangeParameters = TimeRangePickerTools::getAbsoluteRangeParameters(); + $this->getRedirectUrl()->getParams() + ->set($absoluteRangeParameters['start'], $end) + ->set($absoluteRangeParameters['end'], $start); + } + + $this->getRedirectUrl()->remove(TimeRangePickerTools::getRelativeRangeParameter()); + } + + /** + * Add display group for a date and a time input belonging together + * + * @param string $part Either 'start' or 'end' + */ + protected function groupDateTime($part) + { + $this->addDisplayGroup(["{$part}_date", "{$part}_time"], $part); + $group = $this->getDisplayGroup($part); + + foreach ($group->getElements() as $element) { + /** @var \Zend_Form_Element $element */ + + $elementDecorators = $element->getDecorators(); + $element->setDecorators([ + 'Zend_Form_Decorator_ViewHelper' => $elementDecorators['Zend_Form_Decorator_ViewHelper'] + ]); + } + + $decorators = []; + foreach ($elementDecorators as $key => $decorator) { + if ($key === 'Zend_Form_Decorator_ViewHelper') { + $decorators['Zend_Form_Decorator_FormElements'] = + $group->getDecorators()['Zend_Form_Decorator_FormElements']; + } else { + $decorators[$key] = (new Proxy())->setActualDecorator($decorator->setElement($element)); + } + } + + $group->setDecorators($decorators); + } + + /** + * Set this form's elements' default values based on the redirect URL's parameters + * + * @param string $part Either 'start' or 'end' + * @param int $defaultTimestamp Fallback + */ + protected function urlToForm($part, $defaultTimestamp = null) + { + $params = $this->getRedirectUrl()->getParams(); + $absoluteRangeParameters = TimeRangePickerTools::getAbsoluteRangeParameters(); + $timestamp = $params->get($absoluteRangeParameters[$part], $defaultTimestamp); + + if ($timestamp !== null) { + if (preg_match($this->timestamp, $timestamp)) { + list($date, $time) = explode( + 'T', + DateTime::createFromFormat('U', $timestamp) + ->setTimezone(new DateTimeZone(date_default_timezone_get())) + ->format($this->dateTimeFormat) + ); + + $this->getElement("{$part}_date")->setValue($date); + $this->getElement("{$part}_time")->setValue($time); + } else { + $params->remove($absoluteRangeParameters[$part]); + } + } + } + + /** + * Get the relative range start (if any) set by {@link CommonForm} + * + * @return int|null + */ + protected function getRelativeTimestamp() + { + $seconds = TimeRangePickerTools::getRelativeSeconds($this->getRedirectUrl()->getParams()); + return is_int($seconds) ? $this->getNow()->getTimestamp() - $seconds : null; + } + + /** + * Change the redirect URL's parameters based on this form's elements' values + * + * @param string $part Either 'start' or 'end' + * @param string $defaultTime Default if no time given + * @param string $addInterval Add this interval to the result + * + * @return int|null The updated timestamp (if any) + */ + protected function formToUrl($part, $defaultTime, $addInterval = null) + { + $date = $this->getValue("{$part}_date"); + $time = $this->getValue("{$part}_time"); + $params = $this->getRedirectUrl()->getParams(); + $absoluteRangeParameters = TimeRangePickerTools::getAbsoluteRangeParameters(); + + if ($date === '' && $time === '') { + $params->remove($absoluteRangeParameters[$part]); + } else { + $dateTime = DateTime::createFromFormat( + $this->dateTimeFormat, + ($date === '' ? $this->getNow()->format('Y-m-d') : $date) + . 'T' . ($time === '' ? $defaultTime : $time) + ); + + if ($dateTime === false) { + $params->remove($absoluteRangeParameters[$part]); + } else { + if ($addInterval !== null) { + $dateTime->add(new DateInterval($addInterval)); + } + + $params->set($absoluteRangeParameters[$part], $dateTime->format('U')); + return $dateTime->getTimestamp(); + } + } + } + + /** + * Get {@link now} + * + * @return DateTime + */ + public function getNow() + { + if ($this->now === null) { + $this->now = new DateTime(); + } + + return $this->now; + } + + /** + * Set {@link now} + * + * @param DateTime $now + * + * @return $this + */ + public function setNow($now) + { + $this->now = $now; + return $this; + } +} diff --git a/application/views/scripts/config/advanced.phtml b/application/views/scripts/config/advanced.phtml new file mode 100644 index 0000000..ab47cdb --- /dev/null +++ b/application/views/scripts/config/advanced.phtml @@ -0,0 +1,7 @@ +<div class="controls"> + <?= /** @var \Icinga\Web\Widget\Tabs $tabs */ $tabs ?> +</div> + +<div class="content"> + <?= /** @var \Icinga\Module\Graphite\Forms\Config\AdvancedForm $form */ $form ?> +</div> diff --git a/application/views/scripts/config/backend.phtml b/application/views/scripts/config/backend.phtml new file mode 100644 index 0000000..7750f3c --- /dev/null +++ b/application/views/scripts/config/backend.phtml @@ -0,0 +1,7 @@ +<div class="controls"> + <?= /** @var \Icinga\Web\Widget\Tabs $tabs */ $tabs ?> +</div> + +<div class="content"> + <?= /** @var \Icinga\Module\Graphite\Forms\Config\BackendForm $form */ $form ?> +</div> diff --git a/application/views/scripts/list/hosts.phtml b/application/views/scripts/list/hosts.phtml new file mode 100644 index 0000000..ce0e37c --- /dev/null +++ b/application/views/scripts/list/hosts.phtml @@ -0,0 +1,67 @@ +<?php + +use Icinga\Module\Graphite\Web\Widget\Graphs\Host; +use Icinga\Web\Url; + +/** @var \Icinga\Web\View $this */ +/** @var \Icinga\Web\Widget\FilterEditor $filterEditor */ +/** @var \Icinga\Module\Monitoring\Object\Host[] $hosts */ +/** @var bool $hasMoreHosts */ +/** @var \Icinga\Web\Url $baseUrl */ + +if (! $compact): ?> +<div class="controls"> + <?= $tabs ?> + <?= $paginator ?> + <div class="sort-controls-container"> + <?= $limiter ?> + <?= $sortBox ?> + </div> + <?= $filterEditor ?> + <?= $timeRangePicker ?> +</div> +<?php endif ?> +<div class="content"> +<?php +if (! empty($hosts)) { + echo '<div class="graphite-graph-color-registry"></div>'; + echo '<div class="grid">'; + foreach ($hosts as $host) { + $hostGraphs = (string) (new Host($host))->setPreloadDummy()->handleRequest(); + + if ($hostGraphs !== '') { + echo '<div class="grid-item">' + . '<h2>' + . $this->qlink( + $host->host_name === $host->host_display_name + ? $host->host_display_name + : $host->host_display_name . ' (' . $this->escape($host->host_name) . ')', + $baseUrl->with(['host' => $host->host_name]), + null, + ['data-base-target' => '_next'] + ) + . '</h2>' + . $hostGraphs + . '</div>'; + } + } + + if ($hasMoreHosts) { + echo '<div class="action-links">' + . $this->qlink( + mt('monitoring', 'Show More'), + $this->url()->without(array('view', 'limit')), + null, + [ + 'class' => 'action-link', + 'data-base-target' => '_next' + ] + ) + . '</div>'; + } + echo '</div>'; +} else { + echo '<p>' . $this->escape(mt('monitoring', 'No hosts found matching the filter.')) . '</p>'; +} +?> +</div> diff --git a/application/views/scripts/list/services.phtml b/application/views/scripts/list/services.phtml new file mode 100644 index 0000000..90ca03c --- /dev/null +++ b/application/views/scripts/list/services.phtml @@ -0,0 +1,77 @@ +<?php + +use Icinga\Module\Graphite\Web\Widget\Graphs\Service; +use Icinga\Web\Url; + +/** @var \Icinga\Web\View $this */ +/** @var \Icinga\Web\Widget\FilterEditor $filterEditor */ +/** @var \Icinga\Module\Monitoring\Object\Service[] $services */ +/** @var bool $hasMoreServices */ +/** @var \Icinga\Web\Url $hostBaseUrl */ +/** @var \Icinga\Web\Url $serviceBaseUrl */ + +if (! $compact): ?> +<div class="controls"> + <?= $tabs ?> + <?= $paginator ?> + <div class="sort-controls-container"> + <?= $limiter ?> + <?= $sortBox ?> + </div> + <?= $filterEditor ?> + <?= $timeRangePicker ?> +</div> +<?php endif ?> +<div class="content"> +<?php +if (! empty($services)) { + echo '<div class="graphite-graph-color-registry"></div>'; + echo '<div class="grid">'; + foreach ($services as $service) { + echo '<div class="grid-item">' + . '<h2>' + . $this->qlink( + $service->host_name === $service->host_display_name + ? $service->host_display_name + : $service->host_display_name . ' (' . $this->escape($service->host_name) . ')', + $hostBaseUrl->with(['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) . ')', + $serviceBaseUrl->with([ + 'host' => $service->host_name, + 'service' => $service->service_description + ]), + null, + ['data-base-target' => '_next'] + ) + . '</h2>'; + + echo (new Service($service))->setPreloadDummy()->handleRequest(); + echo '</div>'; + } + + if ($hasMoreServices) { + echo '<div class="action-links">' + . $this->qlink( + mt('monitoring', 'Show More'), + $this->url()->without(array('view', 'limit')), + null, + [ + 'class' => 'action-link', + 'data-base-target' => '_next' + ] + ) + . '</div>'; + } + echo '</div>'; +} else { + echo '<p>' . $this->escape(mt('monitoring', 'No services found matching the filter.')) . '</p>'; +} +?> +</div> diff --git a/application/views/scripts/test/apache.phtml b/application/views/scripts/test/apache.phtml new file mode 100644 index 0000000..069ccbe --- /dev/null +++ b/application/views/scripts/test/apache.phtml @@ -0,0 +1,12 @@ +<div class="controls"> +<?= $this->tabs ?> +</div> + +<div class="content"> +<?php foreach ($this->images as $base => $img): ?> +<div style="width: 260px; float: left; margin-right: 5px;"> +<h3><?= $this->escape($base) ?></h3> +<img src="<?= $img ?>" /> +</div> +<?php endforeach ?> +</div> diff --git a/application/views/scripts/test/cpu.phtml b/application/views/scripts/test/cpu.phtml new file mode 100644 index 0000000..495e315 --- /dev/null +++ b/application/views/scripts/test/cpu.phtml @@ -0,0 +1,28 @@ +<?php +$maxCnt = 0; +foreach ($this->images as $base => $cpus) { + $maxCnt = max($maxCnt, count($cpus)); +} +?> +<div class="controls"> +<?= $this->tabs ?> +<h1>CPUs</h1> +</div> +<div class="content"> +<table style="width: 100%;"> +<tr> + <th style="width: 15em;"> </th> + <th>CPUs</th> +</tr> +<?php foreach ($this->images as $base => $cpus): ?> +<tr> +<th style="vertical-align: top; text-align: right; padding-right: 2em;"><?= $this->escape($base) ?></th> +<td> +<?php foreach ($cpus as $num => $img): ?> +<div style="width: 53px; float: left;"><img src="<?= $img ?>" /><!--<br />CPU <?= $num ?>--></div> +<?php endforeach ?> +</td> +</tr> +<?php endforeach ?> +</table> +</div> |