diff options
126 files changed, 12026 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..710cc82 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,17 @@ +# Graphite module for Icinga Web 2 - changelog + +## Version 1.1.0 + +This release fixes a few bugs in monitored object attributes considering, +special character handling, PHP 7 compatibility and UI performance. +It also adds a [graph assembly debugger](doc/05-Troubleshooting.md). + +## Version 1.0.1 + +This release just fixes a mismatch between the Git version tag and the version +in module.info. + +## Version 1.0.0 + +First stable release. You can find its features on +[the Icinga blog](https://www.icinga.com/2018/03/21/graphite-module-for-icinga-web-2-released/). @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..54a1206 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# Icinga Web Graphite Integration + +[![PHP Support](https://img.shields.io/badge/php-%3E%3D%207.2-777BB4?logo=PHP)](https://php.net/) +![Build Status](https://github.com/icinga/icingaweb2-module-graphite/workflows/PHP%20Tests/badge.svg?branch=master) +[![Github Tag](https://img.shields.io/github/tag/Icinga/icingaweb2-module-graphite.svg)](https://github.com/Icinga/icingaweb2-module-graphite) + +![Icinga Logo](https://icinga.com/wp-content/uploads/2014/06/icinga_logo.png) + +This module integrates an existing [Graphite](https://graphite.readthedocs.io/en/latest/) +installation in your [Icinga Web 2](https://icinga.com/products/infrastructure-monitoring/) +frontend. + +![Service List](doc/img/service-list.png) +![Detail View](doc/img/service-detail-view.png) + +It provides a new menu section with two general overviews for hosts and +services as well as an extension to the host and service detail view of +the monitoring module. + +## Documentation + +* [Installation](https://icinga.com/docs/icinga-web-graphite-integration/latest/doc/02-Installation/) +* [Configuration](https://icinga.com/docs/icinga-web-graphite-integration/latest/doc/03-Configuration/) +* [Templates](https://icinga.com/docs/icinga-web-graphite-integration/latest/doc/04-Templates/) +* [Development](https://icinga.com/docs/icinga-web-graphite-integration/latest/doc/06-Development/) 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> diff --git a/configuration.php b/configuration.php new file mode 100644 index 0000000..1ac05b3 --- /dev/null +++ b/configuration.php @@ -0,0 +1,31 @@ +<?php + +/** @var \Icinga\Application\Modules\Module $this */ + +/** @var \Icinga\Application\Modules\MenuItemContainer $section */ + +use Icinga\Module\Graphite\ProvidedHook\Icingadb\IcingadbSupport; + +$section = $this->menuSection(N_('Graphite'), ['icon' => 'chart-area']); + +if ($this::exists('icingadb') && IcingadbSupport::useIcingaDbAsBackend()) { + $section->add(N_('Hosts'), ['url' => 'graphite/hosts']); + $section->add(N_('Services'), ['url' => 'graphite/services']); +} else { + $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'), + 'label' => $this->translate('Backend'), + 'url' => 'config/backend' +)); + +$this->provideConfigTab('advanced', array( + 'title' => $this->translate('Advanced configuration'), + 'label' => $this->translate('Advanced'), + 'url' => 'config/advanced' +)); + +$this->providePermission('graphite/debug', $this->translate('Allow debugging directly via the web UI')); diff --git a/doc/01-About.md b/doc/01-About.md new file mode 100644 index 0000000..01e012f --- /dev/null +++ b/doc/01-About.md @@ -0,0 +1,19 @@ +# Icinga Web Graphite Integration + +This module integrates an existing [Graphite](https://graphite.readthedocs.io/en/latest/) +installation in your [Icinga Web](https://icinga.com/products/infrastructure-monitoring/) +frontend. + +![Service List](img/service-list.png) | ![Detail View](img/service-detail-view.png) +--------------------------------------|-------------------------------------------- + +It provides a new menu section with two general overviews for hosts and +services as well as an extension to the host and service detail view of +the monitoring module. + +## Documentation + +* [Installation](02-Installation.md) +* [Configuration](03-Configuration.md) +* [Templates](04-Templates.md) +* [Troubleshooting](05-Troubleshooting.md) diff --git a/doc/02-Installation.md b/doc/02-Installation.md new file mode 100644 index 0000000..6850ecd --- /dev/null +++ b/doc/02-Installation.md @@ -0,0 +1,48 @@ +<!-- {% if index %} --> +# Installing Icinga Web Graphite Integration + +It is recommended to use prebuilt packages +for all supported platforms from our official release repository. +Of course [Icinga Web](https://icinga.com/docs/icinga-web) itself +is required to run its Graphite integration. +The latter uses Graphite Web, so that is required as well. +If they are not already set up, it is best to do this first. + +The following steps will guide you through installing +and setting up Icinga Web Graphite Integration. +<!-- {% else %} --> +<!-- {% if not icingaDocs %} --> + +## Installing the Package + +If the [repository](https://packages.icinga.com) is not configured yet, please add it first. +Then use your distribution's package manager to install the `icinga-graphite` package +or install [from source](02-Installation.md.d/From-Source.md). +<!-- {% endif %} --><!-- {# end if not icingaDocs #} --> + +## Prepare Icinga 2 + +Enable the graphite feature: + +``` +# icinga2 feature enable graphite +``` + +Adjust its configuration in `/etc/icinga2/features-enabled/graphite.conf`: + +``` +object GraphiteWriter "graphite" { + host = "192.0.2.42" + port = 2003 + enable_send_thresholds = true +} +``` + +And then restart Icinga2. Enabling thresholds is not a hard requirement. +However, some templates look better if they are able to render a max +value or similar. + +## Configuring the Icinga Web Graphite Integration + +For required additional steps see the [Configuration](03-Configuration.md) chapter. +<!-- {% endif %} --><!-- {# end else if index #} --> diff --git a/doc/02-Installation.md.d/From-Source.md b/doc/02-Installation.md.d/From-Source.md new file mode 100644 index 0000000..1433743 --- /dev/null +++ b/doc/02-Installation.md.d/From-Source.md @@ -0,0 +1,14 @@ +# Installing Icinga Web Graphite Integration from Source + +Please see the Icinga Web documentation on +[how to install modules](https://icinga.com/docs/icinga-web-2/latest/doc/08-Modules/#installation) from source. +Make sure you use `graphite` as the module name. The following requirements must also be met. + +## Requirements + +* PHP (≥7.2) +* [Icinga Web](https://github.com/Icinga/icingaweb2) (≥2.9) +* [Icinga DB Web](https://github.com/Icinga/icingadb-web) (≥1.0) +* [Icinga PHP Library (ipl)](https://github.com/Icinga/icinga-php-library) (≥0.9) +* [Icinga PHP Thirdparty](https://github.com/Icinga/icinga-php-thirdparty) (≥0.11) +<!-- {% include "02-Installation.md" %} --> diff --git a/doc/03-Configuration.md b/doc/03-Configuration.md new file mode 100644 index 0000000..0b2a38d --- /dev/null +++ b/doc/03-Configuration.md @@ -0,0 +1,65 @@ +# <a id="Configuration"></a>Configuration + +## Basics + +Open up the Icinga Web frontend and navigate to: + +`Configuration > Modules > graphite > Backend` + +Enter the Graphite Web URL. (e.g. `https://192.0.2.42:8003/`) + +The HTTP basic authentication credentials are only required +if your Graphite Web is protected by such a mechanism. + +## Advanced + +Open up the Icinga Web frontend and navigate to: + +`Configuration > Modules > graphite > Advanced` + +### UI + +The settings *Default time range* and *Default time range unit* set the default +time range for displayed graphs both in the graphs lists and in monitored +objects' detail views. + +If you'd like to suppress the *No graphs found* messages, check *Disable "no +graphs found"*. (This may cause unexpected blank pages in the graphs lists.) + +### Icinga 2 (Core) + +The settings *Host name template* and *Service name template* both are only +required if you are using a different naming schema than the default Icinga 2 +is using. (As outlined [here](https://www.icinga.com/docs/icinga2/latest/doc/14-features/#current-graphite-schema)) + +The setting *Obscured check command custom variable* is only required if there +are wrapped check commands (see below) and the "actual" check command is stored +in another custom variable than `check_command`. + +## Wrapped check commands + +If a monitored object is checked remotely and not via an Icinga 2 agent, but +e.g. by check_by_ssh or check_nrpe, the monitored object's effective check +command becomes by_ssh or nrpe respectively. This breaks the respective +monitored objects' graphs as graph templates are applied to monitored objects +via their check commands. (They fall back to the default template.) + +To make the respective graphs working as expected you have to tell the +monitored object's "actual" check command by setting its custom variable +`check_command`, e.g.: + +``` +apply Service "by_ssh-disk" { + import "generic-service" + check_command = "by_ssh" + vars.by_ssh_address = "192.0.2.1" + vars.by_ssh_command = "/usr/lib64/nagios/plugins/check_disk -w 20 -c 10" + vars.check_command = "disk" // <= HERE + assign where host.name == NodeName +} +``` + +## Further reading + +* [Templates](04-Templates.md) +* [Troubleshooting](05-Troubleshooting.md) diff --git a/doc/04-Templates.md b/doc/04-Templates.md new file mode 100644 index 0000000..5acbae1 --- /dev/null +++ b/doc/04-Templates.md @@ -0,0 +1,226 @@ +# Templates <a id="templates"></a> + +A template defines what kind of data a graph visualizes, which kind of graph to +use and its style. Essentially the Icinga Web Graphite Integration is using +templates to tell Graphite how to render which graphs. + +* [Location](04-Templates.md#templates-location) +* [Structure](04-Templates.md#templates-structure) + * [graph](04-Templates.md#templates-structure-graph) + * [metric_filters](04-Templates.md#templates-structure-metric-filters) + * [urlparams](04-Templates.md#templates-structure-urlparams) + * [functions](04-Templates.md#templates-structure-functions) +* [Example](04-Templates.md#templates-example) +* [Default Template Settings](04-Templates.md#templates-default-settings) + +## Template Location <a id="templates-location"></a> + +There are a bunch of templates already included, located in +the installation path. (e.g. `/usr/share/icingaweb2/modules/graphite`) + +To add additional/customized templates, place them in its configuration path. +(e.g. `/etc/icingaweb2/modules/graphite/templates`) These will either extend +the available templates or override some of them. Subfolders placed here will +also be included in the same way, while additionally extending or overriding +templates of its parent folders. + +> **Note:** +> +> Hidden files and directories (with a leading dot) are ignored. + +## Template Structure <a id="templates-structure"></a> + +Templates are organized within simple INI files. However, it is perfectly valid +to define multiple templates in a single file. + +The name of a section consists of two parts separated by a dot: + + [hostalive-rta.graph] + +The first part is the name of the template and the second part the name of one +of the following configuration topics: + +> **Note:** +> +> Template file will be ignored if the [graph] or [metric_filters] section is missing. + +### Template Structure: graph <a id="templates-structure-graph"></a> + +Supports a single option called `check_command` and should be set to the name +of a Icinga 2 [check-command](https://www.icinga.com/docs/icinga2/latest/doc/03-monitoring-basics/#check-commands). +To get multiple graphs for hosts and services with this check-command, multiple +templates can reference the same check-command. + +If multiple check commands do effectively the same thing and yield the same +perfdata, all of them may be specified separated by comma. E.g.: + +```ini +[ping-rta.graph] +check_command = "ping, ping4, ping6" +``` + +### Template Structure: metric_filters <a id="templates-structure-metric-filters"></a> + +Define what metric to use and how many curves to display in the resulting graph. +Each option's key represents the name of a curve. Its value the path to the +metric in Icinga 2's [graphite naming schema](https://www.icinga.com/docs/icinga2/latest/doc/14-features/#current-graphite-schema). + +Curve names are used to map Graphite functions to metrics. (More on this below) +However, they are fully arbitrary and have no further meaning outside template +configurations. + +A curve's metric path must begin with either the macro `$host_name_template$` +or `$service_name_template$` and is substituted with Icinga 2's prefix label. +The rest of the path is arbitrary, but to get meaningful results use a valid +path to one of the performance data metrics: + + <prefix-label>.perfdata.<perfdata-label>.<metric> + +An example path which points to the metric `value` of the `rta` perfdata-label +looks as follows: + + $host_name_template$.perfdata.rta.value + +To dynamically render a graph for each performance data label found, define a +macro in place for the actual perfdata-label: + + $host_name_template$.perfdata.$perfdata_label$.value + +You can also use wildcards. To define a wildcard, please use the following syntax: + + $macro:wildcard syntax here$ + + Some Examples: + + $perfdata_label:{abc,def}$ + $perfdata_label:{a*c,de*}$ + $perfdata_label:{a[vbn]c,def}$ + +> **Note:** +> +> The name of the macro for the perfdata-label is also arbitrary. You may as +> well use a more descriptive name such as `$disk$` for the disk check. `$disk$` +> is the same as `$disk:*$`. + +### Template Structure: urlparams <a id="templates-structure-urlparams"></a> + +Allows to define additional URL parameters to be passed to Graphite's render +API. + +Each option represents a single parameter's name and value. A list of all +supported parameters can be found [here](https://graphite.readthedocs.io/en/latest/render_api.html#graph-parameters). + +If you have used a macro for the curve's perfdata-label you may utilize it +here as well: + + title = "Disk usage on $disk$" + +You may also define URL parameters once for all templates +(including the shipped ones) in the `default_url_params` section in +`/etc/icingaweb2/modules/graphite/config.ini`: + + [default_url_params] + yUnitSystem = "none" + +These may be overridden in the template itself: + + yUnitSystem = "binary" + +### Template Structure: functions <a id="templates-structure-functions"></a> + +Allows to define Graphite functions which are applied to the metric of a +specific curve on the graph. + +Each option's key must match a curve's name in order to apply the function +to the curve's metric. A list of all supported functions can be found [here](https://graphite.readthedocs.io/en/latest/functions.html#functions). + +The metric in question can be referenced in the function call using the macro +`$metric$` as shown in the following example: + + alias(color(scale($metric$, 1000), '#1a7dd7'), 'Round trip time (ms)') + +In addition you may utilize all other macros here as well: + + alias(color(scale(divideSeries($metric$, $service_name_template$.perfdata.$disk$.max), 100), '#1a7dd7'), 'Used (%)') + +## Template Example <a id="templates-example"></a> + +The configuration examples used in this document are borrowed from the template +for the `hostalive` check-command: + +```ini +[hostalive-rta.graph] +check_command = "hostalive" + +[hostalive-rta.metrics_filters] +rta.value = "$host_name_template$.perfdata.rta.value" + +[hostalive-rta.urlparams] +areaAlpha = "0.5" +areaMode = "all" +min = "0" +yUnitSystem = "none" + +[hostalive-rta.functions] +rta.value = "alias(color(scale($metric$, 1000), '#1a7dd7'), 'Round trip time (ms)')" + + +[hostalive-pl.graph] +check_command = "hostalive" + +[hostalive-pl.metrics_filters] +pl.value = "$host_name_template$.perfdata.pl.value" + +[hostalive-pl.urlparams] +areaAlpha = "0.5" +areaMode = "all" +min = "0" +yUnitSystem = "none" + +[hostalive-pl.functions] +pl.value = "alias(color($metric$, '#1a7dd7'), 'Packet loss (%)')" +``` + +## Default Template Settings <a id="templates-default-settings"></a> + +Next to maintaining templates for specific commands, you can +specify the default template settings in the [default.ini](https://github.com/Icinga/icingaweb2-module-graphite/blob/master/templates/default.ini) +configuration file. + +The following example adjusts the background and foreground colors +to setup the "dark mode" for graphs. + +First, copy the package provided configuration into the configuration +path. Then add the `bgcolor` and `fgcolor` settings into the [urlparams](04-Templates.md#templates-structure-urlparams) +sections for `default-host` and `default-service`. + +``` +cp /usr/share/icingaweb2/modules/graphite/templates/default.ini /etc/icingaweb2/modules/graphite/templates/default.ini + +vim /etc/icingaweb2/modules/graphite/templates/default.ini + +[default-host.urlparams] + +bgcolor = "black" +fgcolor = "white" + +[default-service.urlparams] + +bgcolor = "black" +fgcolor = "white" +``` + +The settings make use the `urlparams` section which adds the +parameters to the render API. + + +> **Note** +> +> Instead of modifying the color settings in the default template, +> you can also change the Graphite configuration explained in +> [this community topic](https://community.icinga.com/t/how-to-adjust-the-graphite-background-color/3172/3). + + +## Further reading + +* [Troubleshooting](06-Troubleshooting.md) diff --git a/doc/05-Troubleshooting.md b/doc/05-Troubleshooting.md new file mode 100644 index 0000000..11ef711 --- /dev/null +++ b/doc/05-Troubleshooting.md @@ -0,0 +1,85 @@ +# <a id="Troubleshooting"></a>Troubleshooting + +## Graphs missing or not shown as expected + +If too less or too many graphs are shown for a host/service or the graphs don't +look as expected, debugging becomes harder if there's no obvious error message +like "Could not resolve host: example.com". + +In such cases the "graphs assembling debugger" may help: + +1. Navigate to the respective host/service as usual +2. Add `&graph_debug=1` to the URL +3. Inspect the log displayed under "Graphs assembling process record" + +### Example + +Example debug log for the host "icinga.com": + +``` ++ Icinga check command: 'hostalive' ++ Obscured check command: NULL ++ Applying templates for check command 'hostalive' +++ Applying template 'hostalive-rta' ++++ Fetched 1 metric(s) from 'https://example.com/metrics/expand?query=icinga2.icinga_com.host.hostalive.perfdata.rta.value' ++++ Excluded 0 metric(s) ++++ Combined 1 metric(s) to 1 chart(s) +++ Applying template 'hostalive-pl' ++++ Fetched 1 metric(s) from 'https://example.com/metrics/expand?query=icinga2.icinga_com.host.hostalive.perfdata.pl.value' ++++ Excluded 0 metric(s) ++++ Combined 1 metric(s) to 1 chart(s) ++ Applying default templates, excluding previously used metrics +++ Applying template 'default-host' ++++ Fetched 2 metric(s) from 'https://example.com/metrics/expand?query=icinga2.icinga_com.host.hostalive.perfdata.%2A.value' ++++ Excluded 2 metric(s) ++++ Combined 0 metric(s) to 0 chart(s) +++ Not applying template 'default-service' +``` + +The log describes how the Icinga Web Graphite Integration assembled the +displayed graphs (or why no graphs could be assembled). The plus signs indent +the performed actions to visualize their hierarchy, e.g. all actions below +`Applying templates for check command 'hostalive'` indented with more than one +plus sign (until `Applying default templates, (...)`) are sub-actions of the +above one. + +#### Details + +At first the host's check command is being determined. Then all templates made +for that check command are applied. Finally, the default template is applied. + +For each template the available Graphite metrics are fetched and combined to +graphs if possible. (See also [Templates](04-Templates.md).) The actual metrics +are not shown not to make the log too large. But they can be viewed at the shown +URLs. + +Example result of the first URL: + +``` +{"results": ["icinga2.icinga_com.host.hostalive.perfdata.rta.value"]} +``` + +## Special chars in host or service name + +Graphite cannot work with special characters. The host and service name should +therefore only contain Latin characters. If you want to use special characters +in host and service names, please set a `display_name` for the object. + +### Example + +``` +object Host "Only latin chars here" { + display_name = "Special chars are welcome" + ... +} + +object Service "Only latin chars here" { + display_name = "Special chars are welcome" + ... +} + +apply Service "Only latin chars here" { + display_name = "Special chars are welcome" + ... +} +``` diff --git a/doc/06-Development.md b/doc/06-Development.md new file mode 100644 index 0000000..bfa2a57 --- /dev/null +++ b/doc/06-Development.md @@ -0,0 +1,15 @@ +# Development + +There is a CLI command for demonstrating +graph templates (useful for developing them): + +```bash +icingacli graphite icinga2 config +``` + +It generates Icinga 2 config based on the present graph templates. +With this config Icinga will (also) "monitor" dummy services yielding random +perfdata as expected by the graph templates. + +I. e.: If that Icinga is also writing to the Graphite that is read by your +Icinga Web Graphite Integration, you'll get dummy graphs for all templates. diff --git a/doc/img/service-detail-view.png b/doc/img/service-detail-view.png Binary files differnew file mode 100644 index 0000000..807524b --- /dev/null +++ b/doc/img/service-detail-view.png diff --git a/doc/img/service-list.png b/doc/img/service-list.png Binary files differnew file mode 100644 index 0000000..b234068 --- /dev/null +++ b/doc/img/service-list.png diff --git a/library/Graphite/Graphing/Chart.php b/library/Graphite/Graphing/Chart.php new file mode 100644 index 0000000..ded8ae8 --- /dev/null +++ b/library/Graphite/Graphing/Chart.php @@ -0,0 +1,385 @@ +<?php + +namespace Icinga\Module\Graphite\Graphing; + +use Icinga\Module\Graphite\Util\MacroTemplate; +use Icinga\Module\Graphite\Web\Widget\GraphImage; +use Icinga\Web\Response; + +class Chart +{ + /** + * Used to render the chart + * + * @var GraphiteWebClient + */ + protected $graphiteWebClient; + + /** @var ?string This chart's background color */ + protected $bgcolor; + + /** @var ?string This chart's foreground color */ + protected $fgcolor; + + /** @var ?string This chart's major grid line color */ + protected $majorGridLineColor; + + /** @var ?string This chart's minor grid line color */ + protected $minorGridLineColor; + + /** + * This chart's base + * + * @var Template + */ + protected $template; + + /** + * Target metrics by curve name + * + * @var string[] + */ + protected $metrics; + + /** + * The chart's begin + * + * @var string + */ + protected $from = '-14400'; + + /** + * The chart's end + * + * @var string + */ + protected $until; + + /** + * The chart's width + * + * @var int + */ + protected $width = 350; + + /** + * The chart's height + * + * @var int + */ + protected $height = 200; + + /** + * Whether to show the chart's legend + * + * @var bool + */ + protected $showLegend = true; + + /** + * Constructor + * + * @param GraphiteWebClient $graphiteWebClient Used to render the chart + * @param Template $template This chart's base + * @param string[] $metrics Target metrics by curve name + */ + public function __construct(GraphiteWebClient $graphiteWebClient, Template $template, array $metrics) + { + $this->graphiteWebClient = $graphiteWebClient; + $this->template = $template; + $this->metrics = $metrics; + } + + /** + * Let Graphite Web render this chart and serve the result immediately to the user agent (via the given response) + * + * Does not return. + * + * @param Response $response + */ + public function serveImage(Response $response) + { + $image = new GraphImage($this); + + // Errors should occur now or not at all + $image->render(); + + $response + ->setHeader('Content-Type', 'image/png', true) + ->setHeader('Content-Disposition', 'inline; filename="graph.png"', true) + ->setHeader('Cache-Control', null, true) + ->setHeader('Expires', null, true) + ->setHeader('Pragma', null, true) + ->setBody($image) + ->sendResponse(); + + exit; + } + + /** + * Extract the values of the template's metrics filters' variables from the target metrics + * + * @return string[] + */ + public function getMetricVariables() + { + /** @var MacroTemplate[][] $curves */ + $curves = $this->template->getFullCurves(); + $variables = []; + + foreach ($this->metrics as $curveName => $metric) { + $vars = $curves[$curveName][0]->reverseResolve($metric); + if ($vars !== false) { + $variables = array_merge($variables, $vars); + } + } + + return $variables; + } + + /** + * Get Graphite Web client + * + * @return GraphiteWebClient + */ + public function getGraphiteWebClient() + { + return $this->graphiteWebClient; + } + + /** + * Get this chart's background color + * + * @return string|null + */ + public function getBackgroundColor(): ?string + { + return $this->bgcolor; + } + + /** + * Set this chart's background color + * + * @param string|null $color + * + * @return $this + */ + public function setBackgroundColor(?string $color): self + { + $this->bgcolor = $color; + + return $this; + } + + /** + * Get this chart's foreground color + * + * @return string|null + */ + public function getForegroundColor(): ?string + { + return $this->fgcolor; + } + + /** + * Set this chart's foreground color + * + * @param string|null $color + * + * @return $this + */ + public function setForegroundColor(?string $color): self + { + $this->fgcolor = $color; + + return $this; + } + + /** + * Get this graph's major grid line color + * + * @return string|null + */ + public function getMajorGridLineColor(): ?string + { + return $this->majorGridLineColor; + } + + /** + * Set this graph's major grid line color + * + * @param string|null $color + * + * @return $this + */ + public function setMajorGridLineColor(?string $color): self + { + $this->majorGridLineColor = $color; + + return $this; + } + + /** + * Get this graph's minor grid line color + * + * @return string|null + */ + public function getMinorGridLineColor(): ?string + { + return $this->minorGridLineColor; + } + + /** + * Set this graph's minor grid line color + * + * @param string|null $color + * + * @return $this + */ + public function setMinorGridLineColor(?string $color): self + { + $this->minorGridLineColor = $color; + + return $this; + } + + /** + * Get template + * + * @return Template + */ + public function getTemplate() + { + return $this->template; + } + + /** + * Get metrics + * + * @return string[] + */ + public function getMetrics() + { + return $this->metrics; + } + + /** + * Get begin + * + * @return string + */ + public function getFrom() + { + return $this->from; + } + + /** + * Set begin + * + * @param string $from + * + * @return $this + */ + public function setFrom($from) + { + $this->from = $from; + + return $this; + } + + /** + * Get end + * + * @return string + */ + public function getUntil() + { + return $this->until; + } + + /** + * Set end + * + * @param string $until + * + * @return $this + */ + public function setUntil($until) + { + $this->until = $until; + + return $this; + } + + /** + * Get width + * + * @return int + */ + public function getWidth() + { + return $this->width; + } + + /** + * Set width + * + * @param int $width + * + * @return $this + */ + public function setWidth($width) + { + $this->width = $width; + + return $this; + } + + /** + * Get height + * + * @return int + */ + public function getHeight() + { + return $this->height; + } + + /** + * Set height + * + * @param int $height + * + * @return $this + */ + public function setHeight($height) + { + $this->height = $height; + + return $this; + } + + /** + * Get whether to show the chart's legend + * + * @return bool + */ + public function getShowLegend() + { + return $this->showLegend; + } + + /** + * Set whether to show the chart's legend + * + * @param bool $showLegend + * + * @return $this + */ + public function setShowLegend($showLegend) + { + $this->showLegend = $showLegend; + + return $this; + } +} diff --git a/library/Graphite/Graphing/GraphingTrait.php b/library/Graphite/Graphing/GraphingTrait.php new file mode 100644 index 0000000..e32a52a --- /dev/null +++ b/library/Graphite/Graphing/GraphingTrait.php @@ -0,0 +1,79 @@ +<?php + +namespace Icinga\Module\Graphite\Graphing; + +use Icinga\Application\Config; +use Icinga\Application\Icinga; +use Icinga\Exception\ConfigurationError; +use Icinga\Module\Graphite\Web\FakeSchemeRequest; +use Icinga\Web\Url; + +trait GraphingTrait +{ + /** + * All loaded templates + * + * @var Templates + */ + protected static $allTemplates; + + /** + * Metrics data source + * + * @var MetricsDataSource + */ + protected static $metricsDataSource; + + /** + * Load and get all templates + * + * @return Templates + */ + protected static function getAllTemplates() + { + if (static::$allTemplates === null) { + $allTemplates = (new Templates())->loadDir( + Icinga::app() + ->getModuleManager() + ->getModule('graphite') + ->getBaseDir() . DIRECTORY_SEPARATOR . 'templates' + ); + + $path = Config::resolvePath('modules/graphite/templates'); + if (file_exists($path)) { + $allTemplates->loadDir($path); + } + + static::$allTemplates = $allTemplates; + } + + return static::$allTemplates; + } + + /** + * Get metrics data source + * + * @return MetricsDataSource + * + * @throws ConfigurationError + */ + public static function getMetricsDataSource() + { + if (static::$metricsDataSource === null) { + $config = Config::module('graphite'); + $graphite = $config->getSection('graphite'); + if (! isset($graphite->url)) { + throw new ConfigurationError('Missing "graphite.url" in "%s"', $config->getConfigFile()); + } + + static::$metricsDataSource = new MetricsDataSource( + (new GraphiteWebClient(Url::fromPath($graphite->url, [], new FakeSchemeRequest()))) + ->setUser($graphite->user) + ->setPassword($graphite->password) + ->setInsecure($graphite->insecure) + ); + } + + return static::$metricsDataSource; + } +} diff --git a/library/Graphite/Graphing/GraphiteWebClient.php b/library/Graphite/Graphing/GraphiteWebClient.php new file mode 100644 index 0000000..b06b6ce --- /dev/null +++ b/library/Graphite/Graphing/GraphiteWebClient.php @@ -0,0 +1,198 @@ +<?php + +namespace Icinga\Module\Graphite\Graphing; + +use Icinga\Web\Url; +use iplx\Http\Client; +use iplx\Http\ClientInterface; +use iplx\Http\Request; + +/** + * HTTP interface to Graphite Web + */ +class GraphiteWebClient +{ + /** + * Base URL of every Graphite Web HTTP request + * + * @var Url + */ + protected $baseUrl; + + /** + * HTTP basic auth user for every Graphite Web HTTP request + * + * @var string|null + */ + protected $user; + + /** + * The above user's password + * + * @var string|null + */ + protected $password; + + /** + * Don't verify the remote's TLS certificate + * + * @var bool + */ + protected $insecure = false; + + /** + * HTTP client + * + * @var ClientInterface + */ + protected $httpClient; + + /** + * Constructor + * + * @param Url $baseUrl Base URL of every Graphite Web HTTP request + */ + public function __construct(Url $baseUrl) + { + $this->httpClient = new Client(); + + $this->setBaseUrl($baseUrl); + } + + /** + * Send an HTTP request to the configured Graphite Web and return the response's body + * + * @param Url $url + * @param string $method + * @param string[] $headers + * @param string $body + * + * @return string + */ + public function request(Url $url, $method = 'GET', array $headers = [], $body = null) + { + $headers['User-Agent'] = 'icingaweb2-module-graphite'; + if ($this->user !== null) { + $headers['Authorization'] = 'Basic ' . base64_encode("{$this->user}:{$this->password}"); + } + + // TODO(ak): keep connections alive (TCP handshakes are a bit expensive and TLS handshakes are very expensive) + return (string) $this->httpClient->send( + new Request($method, $this->completeUrl($url)->getAbsoluteUrl(), $headers, $body), + ['curl' => [ + CURLOPT_SSL_VERIFYPEER => ! $this->insecure + ]] + )->getBody(); + } + + /** + * Complete the given relative URL according to the base URL + * + * @param Url $url + * + * @return Url + */ + public function completeUrl(Url $url) + { + $completeUrl = clone $this->baseUrl; + return $completeUrl + ->setPath(ltrim(rtrim($completeUrl->getPath(), '/') . '/' . ltrim($url->getPath(), '/'), '/')) + ->setParams($url->getParams()); + } + + /** + * Get the base URL of every Graphite Web HTTP request + * + * @return Url + */ + public function getBaseUrl() + { + return $this->baseUrl; + } + + /** + * Set the base URL of every Graphite Web HTTP request + * + * @param Url $baseUrl + * + * @return $this + */ + public function setBaseUrl(Url $baseUrl) + { + $this->baseUrl = $baseUrl; + + return $this; + } + + /** + * Get the HTTP basic auth user + * + * @return null|string + */ + public function getUser() + { + return $this->user; + } + + /** + * Set the HTTP basic auth user + * + * @param null|string $user + * + * @return $this + */ + public function setUser($user) + { + $this->user = $user; + + return $this; + } + + /** + * Get the HTTP basic auth password + * + * @return null|string + */ + public function getPassword() + { + return $this->password; + } + + /** + * Set the HTTP basic auth password + * + * @param null|string $password + * + * @return $this + */ + public function setPassword($password) + { + $this->password = $password; + + return $this; + } + + /** + * Get whether not to verify the remote's TLS certificate + * + * @return bool + */ + public function getInsecure() + { + return $this->insecure; + } + + /** + * Set whether not to verify the remote's TLS certificate + * + * @param bool $insecure + * + * @return $this + */ + public function setInsecure($insecure = true) + { + $this->insecure = $insecure; + + return $this; + } +} diff --git a/library/Graphite/Graphing/MetricsDataSource.php b/library/Graphite/Graphing/MetricsDataSource.php new file mode 100644 index 0000000..19787da --- /dev/null +++ b/library/Graphite/Graphing/MetricsDataSource.php @@ -0,0 +1,48 @@ +<?php + +namespace Icinga\Module\Graphite\Graphing; + +use Icinga\Data\Selectable; + +/** + * Provides an interface to Graphite Web's metrics list + */ +class MetricsDataSource implements Selectable +{ + /** + * HTTP interface to Graphite Web + * + * @var GraphiteWebClient + */ + private $client; + + /** + * Constructor + * + * @param GraphiteWebClient $client HTTP interface to Graphite Web + */ + public function __construct(GraphiteWebClient $client) + { + $this->client = $client; + } + + /** + * Initiate a new query + * + * @return MetricsQuery + */ + public function select() + { + return new MetricsQuery($this); + } + + /** + * Get the client passed to the constructor + * + * @return GraphiteWebClient + */ + public function getClient() + { + return $this->client; + } +} diff --git a/library/Graphite/Graphing/MetricsQuery.php b/library/Graphite/Graphing/MetricsQuery.php new file mode 100644 index 0000000..da05c17 --- /dev/null +++ b/library/Graphite/Graphing/MetricsQuery.php @@ -0,0 +1,219 @@ +<?php + +namespace Icinga\Module\Graphite\Graphing; + +use Icinga\Data\Fetchable; +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filterable; +use Icinga\Data\Queryable; +use Icinga\Exception\NotImplementedError; +use Icinga\Module\Graphite\GraphiteUtil; +use Icinga\Module\Graphite\Util\IcingadbUtils; +use Icinga\Module\Graphite\Util\MacroTemplate; +use Icinga\Module\Graphite\Util\InternalProcessTracker as IPT; +use Icinga\Module\Icingadb\Compat\UrlMigrator; +use Icinga\Module\Icingadb\Model\Host; +use Icinga\Module\Monitoring\Object\Macro; +use Icinga\Module\Monitoring\Object\MonitoredObject; +use Icinga\Util\Json; +use Icinga\Web\Url; +use InvalidArgumentException; +use ipl\Orm\Model; +use ipl\Stdlib\Filter as IplFilter; + +/** + * Queries a {@link MetricsDataSource} + */ +class MetricsQuery implements Queryable, Filterable, Fetchable +{ + /** + * @var MetricsDataSource + */ + protected $dataSource; + + /** + * The base metrics pattern + * + * @var MacroTemplate + */ + protected $base; + + /** + * Extension of {@link base} + * + * @var string[] + */ + protected $filter = []; + + /** + * The object to render the graphs for + * + * @var MonitoredObject|Model + */ + protected $object; + + /** + * Constructor + * + * @param MetricsDataSource $dataSource + */ + public function __construct(MetricsDataSource $dataSource) + { + $this->dataSource = $dataSource; + } + + public function from($target, array $fields = null) + { + if ($fields !== null) { + throw new InvalidArgumentException('Fields are not applicable to this kind of query'); + } + + try { + $this->base = $target instanceof MacroTemplate ? $target : new MacroTemplate((string) $target); + } catch (InvalidArgumentException $e) { + throw new InvalidArgumentException('Bad target', $e); + } + + return $this; + } + + public function applyFilter(Filter $filter) + { + throw new NotImplementedError(__METHOD__); + } + + public function setFilter(Filter $filter) + { + throw new NotImplementedError(__METHOD__); + } + + public function getFilter() + { + throw new NotImplementedError(__METHOD__); + } + + public function addFilter(Filter $filter) + { + throw new NotImplementedError(__METHOD__); + } + + public function where($condition, $value = null) + { + $this->filter[$condition] = $this->escapeMetricStep($value); + + return $this; + } + + public function fetchAll() + { + $result = []; + foreach ($this->fetchColumn() as $metric) { + $result[] = (object) ['metric' => $metric]; + } + + return $result; + } + + public function fetchRow() + { + $result = $this->fetchColumn(); + return empty($result) ? false : (object) ['metric' => $result[0]]; + } + + public function fetchColumn() + { + $filter = []; + foreach ($this->base->getMacros() as $macro) { + if (isset($this->filter[$macro])) { + $filter[$macro] = $this->filter[$macro]; + continue; + } + + if (strpos($macro, '.') === false) { + continue; + } + + $workaroundMacro = str_replace('.', '_', $macro); + if ($this->object instanceof Model) { + // icingadb macro + $tranformFilter = UrlMigrator::transformFilter( + IplFilter::equal($workaroundMacro, ''), + $this->object instanceof Host ? 'hosts' : 'services' + ); + + if ($tranformFilter === false) { + continue; + } + + $migratedMacro = $tranformFilter->getColumn(); + + if ($migratedMacro === $workaroundMacro) { + $workaroundMacro = $macro; + } else { + $workaroundMacro = $migratedMacro; + } + + $icingadbMacros = IcingadbUtils::getInstance(); + $result = $icingadbMacros->resolveMacro($workaroundMacro, $this->object); + } else { + if ($workaroundMacro === 'service_name') { + $workaroundMacro = 'service_description'; + } + + $result = Macro::resolveMacro($workaroundMacro, $this->object); + } + + if ($result !== $workaroundMacro) { + $filter[$macro] = $this->escapeMetricStep($result); + } + } + + $client = $this->dataSource->getClient(); + $url = Url::fromPath('metrics/expand', [ + 'query' => $this->base->resolve($filter, '*') + ]); + $res = Json::decode($client->request($url)); + natsort($res->results); + + IPT::recordf('Fetched %s metric(s) from %s', count($res->results), (string) $client->completeUrl($url)); + + return array_values($res->results); + } + + public function fetchOne() + { + $result = $this->fetchColumn(); + return empty($result) ? false : $result[0]; + } + + public function fetchPairs() + { + throw new NotImplementedError(__METHOD__); + } + + /** + * Set the object to render the graphs for + * + * @param MonitoredObject|Model $object + * + * @return $this + */ + public function setObject($object) + { + $this->object = $object; + + return $this; + } + + /** + * Escapes a string for usage in a Graphite metric path between two dots + * + * @param string $step + * + * @return string + */ + protected function escapeMetricStep($step) + { + return preg_replace('/[^a-zA-Z0-9\*\-:^[\]$#%\']/', '_', $step); + } +} diff --git a/library/Graphite/Graphing/Template.php b/library/Graphite/Graphing/Template.php new file mode 100644 index 0000000..a030fb7 --- /dev/null +++ b/library/Graphite/Graphing/Template.php @@ -0,0 +1,364 @@ +<?php + +namespace Icinga\Module\Graphite\Graphing; + +use Icinga\Application\Config; +use Icinga\Exception\ConfigurationError; +use Icinga\Module\Graphite\Util\MacroTemplate; +use Icinga\Module\Graphite\Util\InternalProcessTracker as IPT; +use Icinga\Module\Monitoring\Object\MonitoredObject; +use InvalidArgumentException; +use ipl\Orm\Model; + +class Template +{ + /** + * The configured icinga.graphite_writer_host_name_template + * + * @var MacroTemplate + */ + protected static $hostNameTemplate; + + /** + * The configured icinga.graphite_writer_service_name_template + * + * @var MacroTemplate + */ + protected static $serviceNameTemplate; + + /** + * All curves to show in a chart by name with Graphite Web metric filters and Graphite functions + * + * [$curve => [$metricFilter, $function], ...] + * + * @var MacroTemplate[][] + */ + protected $curves = []; + + /** + * All curves to show in a chart by name with full Graphite Web metric filters and Graphite functions + * + * [$curve => [$metricFilter, $function], ...] + * + * @var MacroTemplate[][] + */ + protected $fullCurves; + + /** + * Additional URL parameters for rendering via Graphite Web + * + * [$key => $value, ...] + * + * @var MacroTemplate[] + */ + protected $urlParams = []; + + /** + * Constructor + */ + public function __construct() + { + } + + /** + * Get all charts based on this template and applicable to the metrics + * from the given data source restricted by the given filter + * + * @param MetricsDataSource $dataSource + * @param MonitoredObject|Model $object The object to render the graphs for + * @param string[] $filter + * @param MacroTemplate[] $excludeMetrics + * + * @return Chart[] + */ + public function getCharts( + MetricsDataSource $dataSource, + $object, + array $filter, + array &$excludeMetrics = [] + ) { + $metrics = []; + $metricsUsed = 0; + $metricsExcluded = 0; + + foreach ($this->getFullCurves() as $curveName => $curve) { + $fullMetricTemplate = $curve[0]; + + $query = $dataSource->select()->setObject($object)->from($fullMetricTemplate); + + foreach ($filter as $key => $value) { + $query->where($key, $value); + } + + foreach ($query->fetchColumn() as $metric) { + foreach ($excludeMetrics as $excludeMetric) { + if ($excludeMetric->reverseResolve($metric) !== false) { + ++$metricsExcluded; + continue 2; + } + } + + $vars = $curve[0]->reverseResolve($metric); + if ($vars !== false) { + $metrics[$curveName][$metric] = $vars; + ++$metricsUsed; + } + } + } + + switch (count($metrics)) { + case 0: + $metricsCombinations = []; + break; + + case 1: + $metricsCombinations = []; + + foreach ($metrics as $curveName => & $curveMetrics) { + foreach ($curveMetrics as $metric => & $_) { + $metricsCombinations[] = [$curveName => $metric]; + } + unset($_); + } + unset($curveMetrics); + + break; + + default: + $possibleCombinations = []; + $handledCurves = []; + foreach ($metrics as $curveName1 => & $metrics1) { + $handledCurves[$curveName1] = true; + + foreach ($metrics as $curveName2 => & $metrics2) { + if (! isset($handledCurves[$curveName2])) { + foreach ($metrics1 as $metric1 => & $vars1) { + foreach ($metrics2 as $metric2 => & $vars2) { + if ( + count(array_intersect_assoc($vars1, $vars2)) + === count(array_intersect_key($vars1, $vars2)) + ) { + $possibleCombinations[$curveName1][$curveName2][$metric1][$metric2] = true; + } + } + unset($vars2); + } + unset($vars1); + } + } + unset($metrics2); + } + unset($metrics1); + + $metricsCombinations = []; + $this->combineMetrics($metrics, $possibleCombinations, $metricsCombinations); + } + + $charts = []; + foreach ($metricsCombinations as $metricsCombination) { + $charts[] = new Chart($dataSource->getClient(), $this, $metricsCombination); + } + + IPT::recordf('Excluded %s metric(s)', $metricsExcluded); + IPT::recordf('Combined %s metric(s) to %s chart(s)', $metricsUsed, count($charts)); + + return $charts; + } + + /** + * Fill the given metrics combinations from the given metrics as restricted by the given possible combinations + * + * @param string[][][] $metrics + * @param bool[][][][] $possibleCombinations + * @param string[][] $metricsCombinations + * @param string[] $currentCombination + */ + protected function combineMetrics( + array &$metrics, + array &$possibleCombinations, + array &$metricsCombinations, + array $currentCombination = [] + ) { + if (empty($currentCombination)) { + foreach ($metrics as $curveName => & $curveMetrics) { + foreach ($curveMetrics as $metric => & $_) { + $this->combineMetrics( + $metrics, + $possibleCombinations, + $metricsCombinations, + [$curveName => $metric] + ); + } + unset($_); + + break; + } + unset($curveMetrics); + } elseif (count($currentCombination) === count($metrics)) { + $metricsCombinations[] = $currentCombination; + } else { + foreach ($metrics as $nextCurveName => & $_) { + if (! isset($currentCombination[$nextCurveName])) { + break; + } + } + unset($_); + + $allowedNextCurveMetricsPerCurrentCurveName = []; + foreach ($currentCombination as $currentCurveName => $currentCurveMetric) { + $allowedNextCurveMetricsPerCurrentCurveName[$currentCurveName] + = $possibleCombinations[$currentCurveName][$nextCurveName][$currentCurveMetric]; + } + + $allowedNextCurveMetrics = $allowedNextCurveMetricsPerCurrentCurveName[$currentCurveName]; + unset($allowedNextCurveMetricsPerCurrentCurveName[$currentCurveName]); + + foreach ($allowedNextCurveMetricsPerCurrentCurveName as & $allowedMetrics) { + $allowedNextCurveMetrics = array_intersect_key($allowedNextCurveMetrics, $allowedMetrics); + } + unset($allowedMetrics); + + foreach ($allowedNextCurveMetrics as $allowedNextCurveMetric => $_) { + $nextCombination = $currentCombination; + $nextCombination[$nextCurveName] = $allowedNextCurveMetric; + + $this->combineMetrics($metrics, $possibleCombinations, $metricsCombinations, $nextCombination); + } + } + } + + /** + * Get curves to show in a chart by name with Graphite Web metric filters and Graphite functions + * + * @return MacroTemplate[][] + */ + public function getCurves() + { + return $this->curves; + } + + /** + * Get curves to show in a chart by name with full Graphite Web metric filters and Graphite functions + * + * @return MacroTemplate[][] + */ + public function getFullCurves() + { + if ($this->fullCurves === null) { + $curves = $this->curves; + + foreach ($curves as &$curve) { + $curve[0] = new MacroTemplate($curve[0]->resolve([ + 'host_name_template' => static::getHostNameTemplate(), + 'service_name_template' => static::getServiceNameTemplate(), + '' => '$$' + ])); + } + unset($curve); + + $this->fullCurves = $curves; + } + + return $this->fullCurves; + } + + /** + * Set curves to show in a chart by name with Graphite Web metric filters and Graphite functions + * + * @param MacroTemplate[][] $curves + * + * @return $this + */ + public function setCurves(array $curves) + { + $this->curves = $curves; + + return $this; + } + + /** + * Get additional URL parameters for Graphite Web + * + * @return MacroTemplate[] + */ + public function getUrlParams() + { + return $this->urlParams; + } + + /** + * Set additional URL parameters for Graphite Web + * + * @param MacroTemplate[] $urlParams + * + * @return $this + */ + public function setUrlParams(array $urlParams) + { + $this->urlParams = $urlParams; + + return $this; + } + + /** + * Get {@link hostNameTemplate} + * + * @return MacroTemplate + * + * @throws ConfigurationError If the configuration is invalid + */ + protected static function getHostNameTemplate() + { + if (static::$hostNameTemplate === null) { + $config = Config::module('graphite'); + $template = $config->get( + 'icinga', + 'graphite_writer_host_name_template', + 'icinga2.$host.name$.host.$host.check_command$' + ); + + try { + static::$hostNameTemplate = new MacroTemplate($template); + } catch (InvalidArgumentException $e) { + throw new ConfigurationError( + 'Bad icinga.graphite_writer_host_name_template in "%s": %s', + $config->getConfigFile(), + $e->getMessage() + ); + } + } + + return static::$hostNameTemplate; + } + + /** + * Get {@link serviceNameTemplate} + * + * @return MacroTemplate + * + * @throws ConfigurationError If the configuration is invalid + */ + protected static function getServiceNameTemplate() + { + if (static::$serviceNameTemplate === null) { + $config = Config::module('graphite'); + $template = $config->get( + 'icinga', + 'graphite_writer_service_name_template', + 'icinga2.$host.name$.services.$service.name$.$service.check_command$' + ); + + try { + static::$serviceNameTemplate = new MacroTemplate($template); + } catch (InvalidArgumentException $e) { + throw new ConfigurationError( + 'Bad icinga.graphite_writer_service_name_template in "%s": %s', + $config->getConfigFile(), + $e->getMessage() + ); + } + } + + return static::$serviceNameTemplate; + } +} diff --git a/library/Graphite/Graphing/Templates.php b/library/Graphite/Graphing/Templates.php new file mode 100644 index 0000000..0765e46 --- /dev/null +++ b/library/Graphite/Graphing/Templates.php @@ -0,0 +1,321 @@ +<?php + +namespace Icinga\Module\Graphite\Graphing; + +use Icinga\Application\Config; +use Icinga\Data\ConfigObject; +use Icinga\Exception\ConfigurationError; +use Icinga\Module\Graphite\Util\MacroTemplate; +use Icinga\Web\UrlParams; +use InvalidArgumentException; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use SplFileInfo; + +/** + * Templates collection + */ +class Templates +{ + /** + * All templates by their check command and name + * + * @var Template[][] + */ + protected $templates = []; + + /** + * All default templates by their name + * + * @var Template[] + */ + protected $defaultTemplates = []; + + /** + * Default URL params for all templates + * + * @var string[] + */ + protected $defaultUrlParams = []; + + /** + * Constructor + */ + public function __construct() + { + $config = Config::module('graphite'); + + foreach ($config->getSection('default_url_params') as $param => $value) { + try { + $this->defaultUrlParams[$param] = new MacroTemplate($value); + } catch (InvalidArgumentException $e) { + throw new ConfigurationError( + 'Invalid URL parameter "%s" ("%s") in file "%s"', + $param, + $value, + $config->getConfigFile(), + $e + ); + } + } + } + + /** + * Load templates as configured inside the given directory + * + * @param string $path + * + * @return $this + * + * @throws ConfigurationError If the configuration is invalid + */ + public function loadDir($path) + { + foreach ( + new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( + $path, + RecursiveDirectoryIterator::KEY_AS_PATHNAME | RecursiveDirectoryIterator::CURRENT_AS_FILEINFO + | RecursiveDirectoryIterator::SKIP_DOTS | RecursiveDirectoryIterator::FOLLOW_SYMLINKS + ), + RecursiveIteratorIterator::LEAVES_ONLY + ) as $filepath => $fileinfo + ) { + /** @var SplFileInfo $fileinfo */ + + if ($fileinfo->isFile() && preg_match('/\A[^.].*\.ini\z/si', $fileinfo->getFilename())) { + $this->loadIni($filepath); + } + } + + return $this; + } + + /** + * Load templates as configured in the given INI file + * + * @param string $path + * + * @return $this + * + * @throws ConfigurationError If the configuration is invalid + */ + public function loadIni($path) + { + /** @var string[][][] $templates */ + $templates = []; + + foreach (Config::fromIni($path) as $section => $options) { + /** @var ConfigObject $options */ + + $matches = []; + if (! preg_match('/\A(.+)\.(graph|metrics_filters|urlparams|functions)\z/', $section, $matches)) { + throw new ConfigurationError('Bad section name "%s" in file "%s"', $section, $path); + } + + $templates[$matches[1]][$matches[2]] = $options->toArray(); + } + + $checkCommands = []; + + foreach ($templates as $templateName => $template) { + $checkCommands[$templateName] = isset($template['graph']['check_command']) + ? array_unique(preg_split('/\s*,\s*/', $template['graph']['check_command'], -1, PREG_SPLIT_NO_EMPTY)) + : []; + unset($template['graph']['check_command']); + + if (isset($template['graph'])) { + switch (count($template['graph'])) { + case 0: + break; + + case 1: + throw new ConfigurationError( + 'Bad option for template "%s" in file "%s": "graph.%s"', + $templateName, + $path, + array_keys($template['graph'])[0] + ); + + default: + $standalone = array_keys($template['graph']); + sort($standalone); + + throw new ConfigurationError( + 'Bad options for template "%s" in file "%s": %s', + $templateName, + $path, + implode(', ', array_map( + function ($option) { + return "\"graph.$option\""; + }, + $standalone + )) + ); + } + } + + /** @var MacroTemplate[][] $curves */ + $curves = []; + + if (isset($template['metrics_filters'])) { + foreach ($template['metrics_filters'] as $curve => $metricsFilter) { + try { + $curves[$curve][0] = new MacroTemplate($metricsFilter); + } catch (InvalidArgumentException $e) { + throw new ConfigurationError( + 'Bad metrics filter "%s" for curve "%s" of template "%s" in file "%s": %s', + $metricsFilter, + $curve, + $templateName, + $path, + $e->getMessage() + ); + } + + if ( + count(array_intersect( + $curves[$curve][0]->getMacros(), + ['host_name_template', 'service_name_template'] + )) !== 1 + ) { + throw new ConfigurationError( + 'Bad metrics filter "%s" for curve "%s" of template "%s" in file "%s": must include' + . ' either the macro $host_name_template$ or $service_name_template$, but not both', + $metricsFilter, + $curve, + $templateName, + $path + ); + } + + if (isset($template['functions'][$curve])) { + try { + $curves[$curve][1] = new MacroTemplate($template['functions'][$curve]); + } catch (InvalidArgumentException $e) { + throw new ConfigurationError( + 'Bad function "%s" for curve "%s" of template "%s" in file "%s": %s', + $template['functions'][$curve], + $curve, + $templateName, + $path, + $e->getMessage() + ); + } + + unset($template['functions'][$curve]); + } else { + $curves[$curve][1] = new MacroTemplate('$metric$'); + } + } + } + + if (isset($template['functions'])) { + switch (count($template['functions'])) { + case 0: + break; + + case 1: + throw new ConfigurationError( + 'Metrics filter for curve "%s" of template "%s" in file "%s" missing', + array_keys($template['functions'])[0], + $templateName, + $path + ); + + default: + $standalone = array_keys($template['functions']); + sort($standalone); + + throw new ConfigurationError( + 'Metrics filter for curves of template "%s" in file "%s" missing: "%s"', + $templateName, + $path, + implode('", "', $standalone) + ); + } + } + + $urlParams = $this->defaultUrlParams; + + if (isset($template['urlparams'])) { + foreach ($template['urlparams'] as $key => $value) { + try { + $urlParams[$key] = new MacroTemplate($value); + } catch (InvalidArgumentException $e) { + throw new ConfigurationError( + 'Invalid URL parameter "%s" ("%s") for template "%s" in file "%s": %s', + $key, + $value, + $templateName, + $path, + $e->getMessage() + ); + } + } + } + + $templates[$templateName] = empty($curves) ? null : (new Template()) + ->setCurves($curves) + ->setUrlParams($urlParams); + } + + foreach ($templates as $templateName => $template) { + if ($template === null) { + if (empty($checkCommands[$templateName])) { + unset($this->defaultTemplates[$templateName]); + } else { + foreach ($checkCommands[$templateName] as $checkCommand) { + unset($this->templates[$checkCommand][$templateName]); + + if (empty($this->templates[$checkCommand])) { + unset($this->templates[$checkCommand]); + } + } + } + } else { + if (empty($checkCommands[$templateName])) { + $this->defaultTemplates[$templateName] = $template; + } else { + foreach ($checkCommands[$templateName] as $checkCommand) { + $this->templates[$checkCommand][$templateName] = $template; + } + } + } + } + + return $this; + } + + /** + * Get all loaded templates for the given check command by their names + * + * @param string $checkCommand + * + * @return Template[] + */ + public function getTemplates($checkCommand) + { + return isset($this->templates[$checkCommand]) ? $this->templates[$checkCommand] : []; + } + + /** + * Get all loaded templates for all check commands + * + * @return Template[][] + */ + public function getAllTemplates() + { + return $this->templates; + } + + /** + * Get all loaded default templates by their names + * + * @return Template[] + */ + public function getDefaultTemplates() + { + return $this->defaultTemplates; + } +} diff --git a/library/Graphite/ProvidedHook/Icingadb/HostDetailExtension.php b/library/Graphite/ProvidedHook/Icingadb/HostDetailExtension.php new file mode 100644 index 0000000..31e4e6c --- /dev/null +++ b/library/Graphite/ProvidedHook/Icingadb/HostDetailExtension.php @@ -0,0 +1,46 @@ +<?php + +namespace Icinga\Module\Graphite\ProvidedHook\Icingadb; + +use Icinga\Application\Icinga; +use Icinga\Module\Graphite\Util\InternalProcessTracker as IPT; +use Icinga\Module\Graphite\Web\Controller\TimeRangePickerTrait; +use Icinga\Module\Graphite\Web\Widget\Graphs; +use Icinga\Module\Icingadb\Hook\HostDetailExtensionHook; +use Icinga\Module\Icingadb\Model\Host; +use ipl\Html\Html; +use ipl\Html\HtmlString; +use ipl\Html\ValidHtml; + +class HostDetailExtension extends HostDetailExtensionHook +{ + use TimeRangePickerTrait; + + public function getHtmlForObject(Host $host): ValidHtml + { + if (Icinga::app()->getRequest()->getUrl()->getParam('graph_debug')) { + IPT::enable(); + } + + $graphs = (string) Graphs::forIcingadbObject($host) + ->setWidth(440) + ->setHeight(220) + ->setClasses(['object-detail-view']) + ->setPreloadDummy() + ->setShowNoGraphsFound(false) + ->handleRequest(); + + if (! empty($graphs)) { + $this->handleTimeRangePickerRequest(); + + $header = Html::tag('h2', [], 'Graphs'); + $timepicker = HtmlString::create($this->renderTimeRangePicker(Icinga::app()->getViewRenderer()->view)); + $graphColorRegistry = Html::tag('div', ['class' => 'graphite-graph-color-registry']); + $graphs = HtmlString::create($graphs); + + return HtmlString::create($header . $timepicker . $graphColorRegistry . $graphs); + } + + return HtmlString::create(''); + } +} diff --git a/library/Graphite/ProvidedHook/Icingadb/IcingadbSupport.php b/library/Graphite/ProvidedHook/Icingadb/IcingadbSupport.php new file mode 100644 index 0000000..8f0f38e --- /dev/null +++ b/library/Graphite/ProvidedHook/Icingadb/IcingadbSupport.php @@ -0,0 +1,9 @@ +<?php + +namespace Icinga\Module\Graphite\ProvidedHook\Icingadb; + +use Icinga\Module\Icingadb\Hook\IcingadbSupportHook; + +class IcingadbSupport extends IcingadbSupportHook +{ +} diff --git a/library/Graphite/ProvidedHook/Icingadb/ServiceDetailExtension.php b/library/Graphite/ProvidedHook/Icingadb/ServiceDetailExtension.php new file mode 100644 index 0000000..63c2b79 --- /dev/null +++ b/library/Graphite/ProvidedHook/Icingadb/ServiceDetailExtension.php @@ -0,0 +1,46 @@ +<?php + +namespace Icinga\Module\Graphite\ProvidedHook\Icingadb; + +use Icinga\Application\Icinga; +use Icinga\Module\Graphite\Util\InternalProcessTracker as IPT; +use Icinga\Module\Graphite\Web\Controller\TimeRangePickerTrait; +use Icinga\Module\Graphite\Web\Widget\Graphs; +use Icinga\Module\Icingadb\Hook\ServiceDetailExtensionHook; +use Icinga\Module\Icingadb\Model\Service; +use ipl\Html\Html; +use ipl\Html\HtmlString; +use ipl\Html\ValidHtml; + +class ServiceDetailExtension extends ServiceDetailExtensionHook +{ + use TimeRangePickerTrait; + + public function getHtmlForObject(Service $service): ValidHtml + { + if (Icinga::app()->getRequest()->getUrl()->getParam('graph_debug')) { + IPT::enable(); + } + + $graphs = (string) Graphs::forIcingadbObject($service) + ->setWidth(440) + ->setHeight(220) + ->setClasses(['object-detail-view']) + ->setPreloadDummy() + ->setShowNoGraphsFound(false) + ->handleRequest(); + + if (! empty($graphs)) { + $this->handleTimeRangePickerRequest(); + + $header = Html::tag('h2', [], 'Graphs'); + $timepicker = HtmlString::create($this->renderTimeRangePicker(Icinga::app()->getViewRenderer()->view)); + $graphColorRegistry = Html::tag('div', ['class' => 'graphite-graph-color-registry']); + $graphs = HtmlString::create($graphs); + + return HtmlString::create($header . $timepicker . $graphColorRegistry . $graphs); + } + + return HtmlString::create(''); + } +} diff --git a/library/Graphite/ProvidedHook/Monitoring/DetailviewExtension.php b/library/Graphite/ProvidedHook/Monitoring/DetailviewExtension.php new file mode 100644 index 0000000..d6a4673 --- /dev/null +++ b/library/Graphite/ProvidedHook/Monitoring/DetailviewExtension.php @@ -0,0 +1,40 @@ +<?php + +namespace Icinga\Module\Graphite\ProvidedHook\Monitoring; + +use Icinga\Application\Icinga; +use Icinga\Module\Graphite\Util\InternalProcessTracker as IPT; +use Icinga\Module\Graphite\Web\Controller\TimeRangePickerTrait; +use Icinga\Module\Graphite\Web\Widget\Graphs; +use Icinga\Module\Monitoring\Hook\DetailviewExtensionHook; +use Icinga\Module\Monitoring\Object\MonitoredObject; + +class DetailviewExtension extends DetailviewExtensionHook +{ + use TimeRangePickerTrait; + + public function getHtmlForObject(MonitoredObject $object) + { + if (Icinga::app()->getRequest()->getUrl()->getParam('graph_debug')) { + IPT::enable(); + } + + $graphs = (string) Graphs::forMonitoredObject($object) + ->setWidth(440) + ->setHeight(220) + ->setClasses(['object-detail-view']) + ->setPreloadDummy() + ->setShowNoGraphsFound(false) + ->handleRequest(); + + if ($graphs !== '') { + $this->handleTimeRangePickerRequest(); + return '<h2>' . mt('graphite', 'Graphs') . '</h2>' + . $this->renderTimeRangePicker($this->getView()) + . '<div class="graphite-graph-color-registry"></div>' + . $graphs; + } + + return ''; + } +} diff --git a/library/Graphite/Util/IcingadbUtils.php b/library/Graphite/Util/IcingadbUtils.php new file mode 100644 index 0000000..43334e5 --- /dev/null +++ b/library/Graphite/Util/IcingadbUtils.php @@ -0,0 +1,49 @@ +<?php + +/* Icinga Graphite Web | (c) 2022 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Graphite\Util; + +use Icinga\Module\Icingadb\Common\Auth; +use Icinga\Module\Icingadb\Common\Database; +use Icinga\Module\Icingadb\Common\Macros; + +/** +* Class for initialising icingadb utils + */ +class IcingadbUtils +{ + use Macros; + use Database; + use Auth; + + protected static $instance; + + /** + * @see getInstance() + */ + private function __construct() + { + } + + /** + * Get the IcingadbUtils instance + * + * @return IcingadbUtils + */ + public static function getInstance(): IcingadbUtils + { + if (self::$instance === null) { + self::$instance = new self(); + } + + return static::$instance; + } + + /** + * Prevent the instance from being cloned (which would create a second instance of it) + */ + private function __clone() + { + } +} diff --git a/library/Graphite/Util/InternalProcessTracker.php b/library/Graphite/Util/InternalProcessTracker.php new file mode 100644 index 0000000..f7f2df6 --- /dev/null +++ b/library/Graphite/Util/InternalProcessTracker.php @@ -0,0 +1,126 @@ +<?php + +namespace Icinga\Module\Graphite\Util; + +use Icinga\Authentication\Auth; +use Icinga\Security\SecurityException; + +/** + * A record about what happened during a specific action + */ +class InternalProcessTracker +{ + /** + * Whether to record anything + * + * @var bool + */ + private static $enabled = false; + + /** + * How many '+'es to prepend to each new record + * + * @var int + */ + private static $indentation = 1; + + /** + * The recorded happenings + * + * @var string[] + */ + private static $records = []; + + /** + * Get whether recording is enabled + * + * @return bool + */ + public static function enabled() + { + return self::$enabled; + } + + /** + * Enable recording + * + * @throws SecurityException + */ + public static function enable() + { + if (! Auth::getInstance()->hasPermission('graphite/debug')) { + throw new SecurityException('No permission for graphite/debug'); + } + + self::$enabled = true; + } + + /** + * Introduce a "sub-process" + */ + public static function indent() + { + if (self::$enabled) { + ++self::$indentation; + } + } + + /** + * Record a happening + * + * Behaves like {@link sprintf()} if additional arguments given, but {@link var_export()}s the arguments first + * (so always use %s instead of e.g. %d). + * + * @param string $format + */ + public static function recordf($format) + { + if (self::$enabled) { + if (func_num_args() > 1) { + $args = []; + foreach (array_slice(func_get_args(), 1) as $arg) { + $args[] = var_export($arg, true); + } + + $format = vsprintf($format, $args); + } + + self::$records[] = str_repeat('+', self::$indentation) . " $format"; + } + } + + /** + * Terminate a "sub-process" + */ + public static function unindent() + { + if (self::$enabled) { + --self::$indentation; + } + } + + /** + * Dump everything recorded as plain text + * + * @return string + */ + public static function dump() + { + return implode("\n", self::$records); + } + + /** + * Reset records + */ + public static function clear() + { + if (self::$enabled) { + self::$indentation = 1; + self::$records = []; + } + } + + final private function __construct() + { + } +} diff --git a/library/Graphite/Util/MacroTemplate.php b/library/Graphite/Util/MacroTemplate.php new file mode 100644 index 0000000..23a171a --- /dev/null +++ b/library/Graphite/Util/MacroTemplate.php @@ -0,0 +1,239 @@ +<?php + +namespace Icinga\Module\Graphite\Util; + +use InvalidArgumentException; + +/** + * A macro-based template for strings + */ +class MacroTemplate +{ + /** + * Macros' start and end character + * + * @var string + */ + protected $macroCharacter; + + /** + * The parsed template + * + * @var string[] + */ + protected $template; + + /** + * Regex for reverse resolving patterns + * + * @var string + */ + protected $reverseResolvePattern; + + /** + * Wildcards + * + * @var string[] + */ + protected $wildCards; + + /** + * The original raw template + * + * @var string + */ + protected $orgTemplate; + + /** + * Constructor + * + * @param string $template The raw template + * @param string $macroCharacter Macros' start and end character + */ + public function __construct($template, $macroCharacter = '$') + { + $this->orgTemplate = $template; + $this->macroCharacter = $macroCharacter; + $this->template = explode($macroCharacter, $template); + foreach ($this->template as $key => $value) { + if (preg_match('/([^:]+):(.+)/', $value, $match)) { + $wildCardKey = $match[1]; + $this->template[$key] = $wildCardKey; + $this->wildCards[$wildCardKey] = $match[2]; + } + } + + if (! (count($this->template) % 2)) { + throw new InvalidArgumentException( + 'template contains odd number of ' . var_export($macroCharacter, true) + . 's: ' . var_export($template, true) + ); + } + } + + /** + * Return a string based on this template with the macros resolved from the given variables + * + * @param string[] $variables + * @param string $default The default value for missing variables. + * By default the macro just isn't replaced. + * + * @return string + */ + public function resolve(array $variables, $default = null) + { + $macro = false; + $result = []; // kind of string builder + + foreach ($this->template as $part) { + if ($macro) { + if (isset($variables[$part])) { + $result[] = $variables[$part]; + } elseif ($part === '') { + $result[] = $this->macroCharacter; + } elseif ($default === null) { + $result[] = $this->macroCharacter; + $result[] = $part; + // add wildcards to result before they are + // overwritten from Template::getFullCurves() + if (isset($this->wildCards[$part])) { + $result[] = ':' . $this->wildCards[$part]; + } + + $result[] = $this->macroCharacter; + } else { + if (isset($this->wildCards[$part])) { + $result[] = $this->wildCards[$part]; + } else { + $result[] = $default; + } + } + } else { + $result[] = $part; + } + + $macro = ! $macro; + } + + return implode($result); + } + + /** + * Try to reverse-resolve the given string + * + * @param string $resolved A result of {@link resolve()} + * + * @return string[]|false Variables as passed to {@link resolve()} if successful + */ + public function reverseResolve($resolved) + { + $matches = []; + if (! preg_match($this->getReverseResolvePattern(), $resolved, $matches)) { + return false; + } + + $macro = false; + $macros = []; + $currentCapturedSubPatternIndex = 0; + + foreach ($this->template as $part) { + if ($macro && ! isset($macros[$part])) { + $macros[$part] = ++$currentCapturedSubPatternIndex; + } + + $macro = ! $macro; + } + + $macros = array_flip($macros); + + $result = []; + foreach ($matches as $index => $match) { + if ($index > 0) { + $result[$macros[$index]] = $match; + } + } + + return $result; + } + + /** + * Return the raw template string this instance was constructed from + * + * @return string + */ + public function __toString() + { + return $this->orgTemplate; + } + + /** + * Return the macros of this template + * + * @return string[] + */ + public function getMacros() + { + $macro = false; + $macros = []; + + foreach ($this->template as $part) { + if ($macro) { + $macros[$part] = null; + } + + $macro = ! $macro; + } + + unset($macros['']); + + return array_keys($macros); + } + + /** + * Get macros' start and end character + * + * @return string + */ + public function getMacroCharacter() + { + return $this->macroCharacter; + } + + /** + * Get {@link reverseResolvePattern} + * + * @return string + */ + protected function getReverseResolvePattern() + { + if ($this->reverseResolvePattern === null) { + $result = ['/\A']; // kind of string builder + $macro = false; + $macros = []; + $currentCapturedSubPatternIndex = 0; + + foreach ($this->template as $part) { + if ($macro) { + if (isset($macros[$part])) { + $result[] = '\g{'; + $result[] = $macros[$part]; + $result[] = '}'; + } else { + $macros[$part] = ++$currentCapturedSubPatternIndex; + $result[] = '(.*)'; + } + } else { + $result[] = preg_quote($part, '/'); + } + + $macro = ! $macro; + } + + $result[] = '\z/s'; + + $this->reverseResolvePattern = implode($result); + } + + return $this->reverseResolvePattern; + } +} diff --git a/library/Graphite/Util/TimeRangePickerTools.php b/library/Graphite/Util/TimeRangePickerTools.php new file mode 100644 index 0000000..d1ebc75 --- /dev/null +++ b/library/Graphite/Util/TimeRangePickerTools.php @@ -0,0 +1,111 @@ +<?php + +namespace Icinga\Module\Graphite\Util; + +use Icinga\Application\Config; +use Icinga\Exception\ConfigurationError; +use Icinga\Web\Url; +use Icinga\Web\UrlParams; + +final class TimeRangePickerTools +{ + /** + * @return string + */ + public static function getRelativeRangeParameter() + { + return 'graph_range'; + } + + /** + * @return string[] + */ + public static function getAbsoluteRangeParameters() + { + return ['start' => 'graph_start', 'end' => 'graph_end']; + } + + /** + * @return string[] + */ + public static function getAllRangeParameters() + { + return array_values(array_merge([static::getRelativeRangeParameter()], static::getAbsoluteRangeParameters())); + } + + /** + * 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 (self::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 + * + * @param UrlParams $params + * + * @return bool|int|null + */ + public static function getRelativeSeconds(UrlParams $params) + { + $seconds = $params->get(self::getRelativeRangeParameter()); + if ($seconds === null) { + return null; + } + + return preg_match('/^(?:0|[1-9]\d*)$/', $seconds) ? (int) $seconds : false; + } + + /** + * Get the default relative time range for graphs + * + * @return int + * + * @throws ConfigurationError + */ + public static function getDefaultRelativeTimeRange() + { + $rangeFactors = [ + 'minutes' => 60, + 'hours' => 3600, + 'days' => 86400, + 'weeks' => 604800, + 'months' => 2592000, + 'years' => 31557600 + ]; + + $config = Config::module('graphite'); + $unit = $config->get('ui', 'default_time_range_unit', 'hours'); + + if (! isset($rangeFactors[$unit])) { + throw new ConfigurationError( + 'Bad ui.default_time_range_unit %s in file %s', + var_export($unit, true), + var_export($config->getConfigFile(), true) + ); + } + + return (int) $config->get('ui', 'default_time_range', 1) * $rangeFactors[$unit]; + } +} diff --git a/library/Graphite/Web/Controller/IcingadbGraphiteController.php b/library/Graphite/Web/Controller/IcingadbGraphiteController.php new file mode 100644 index 0000000..36bc026 --- /dev/null +++ b/library/Graphite/Web/Controller/IcingadbGraphiteController.php @@ -0,0 +1,110 @@ +<?php + +/* Icinga Graphite Web | (c) 2022 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Graphite\Web\Controller; + +use Icinga\Application\Modules\Module; +use Icinga\Module\Graphite\ProvidedHook\Icingadb\IcingadbSupport; +use Icinga\Module\Icingadb\Common\Auth; +use Icinga\Module\Icingadb\Common\Database; +use Icinga\Module\Icingadb\Common\SearchControls; +use ipl\Orm\Query; +use ipl\Stdlib\Contract\Paginatable; +use ipl\Web\Compat\CompatController; +use ipl\Web\Control\LimitControl; +use ipl\Web\Control\PaginationControl; +use ipl\Web\Control\SortControl; +use ipl\Web\Filter\QueryString; +use ipl\Stdlib\Filter; +use ipl\Web\Url; + +class IcingadbGraphiteController extends CompatController +{ + use Auth; + use Database; + use SearchControls; + + /** @var bool Whether to use icingadb as the backend */ + protected $useIcingadbAsBackend; + + /** @var string[] Graph parameters */ + protected $graphParams = ['graphs_limit', 'graph_range', 'graph_start', 'graph_end', 'legacyParams']; + + /** @var Filter\Rule Filter from query string parameters */ + private $filter; + + protected function moduleInit() + { + $this->useIcingadbAsBackend = Module::exists('icingadb') && IcingadbSupport::useIcingaDbAsBackend(); + } + + /** + * Get the filter created from query string parameters + * + * @return Filter\Rule + */ + public function getFilter(): Filter\Rule + { + if ($this->filter === null) { + $this->filter = QueryString::parse((string) $this->params); + } + + return $this->filter; + } + + /** + * Create and return the LimitControl + * + * This automatically shifts the limit URL parameter from {@link $params}. + * + * @return LimitControl + */ + public function createLimitControl(): LimitControl + { + $limitControl = new LimitControl(Url::fromRequest()); + $limitControl->setDefaultLimit($this->getPageSize(null)); + + $this->params->shift($limitControl->getLimitParam()); + + return $limitControl; + } + + /** + * Create and return the PaginationControl + * + * This automatically shifts the pagination URL parameters from {@link $params}. + * + * @return PaginationControl + */ + public function createPaginationControl(Paginatable $paginatable): PaginationControl + { + $paginationControl = new PaginationControl($paginatable, Url::fromRequest()); + $paginationControl->setDefaultPageSize($this->getPageSize(null)); + $paginationControl->setAttribute('id', $this->getRequest()->protectId('pagination-control')); + + $this->params->shift($paginationControl->getPageParam()); + $this->params->shift($paginationControl->getPageSizeParam()); + + return $paginationControl->apply(); + } + + /** + * Create and return the SortControl + * + * This automatically shifts the sort URL parameter from {@link $params}. + * + * @param Query $query + * @param array $columns Possible sort columns as sort string-label pairs + * + * @return SortControl + */ + public function createSortControl(Query $query, array $columns): SortControl + { + $sortControl = SortControl::create($columns); + + $this->params->shift($sortControl->getSortParam()); + + return $sortControl->apply($query); + } +} diff --git a/library/Graphite/Web/Controller/MonitoringAwareController.php b/library/Graphite/Web/Controller/MonitoringAwareController.php new file mode 100644 index 0000000..dca2ebd --- /dev/null +++ b/library/Graphite/Web/Controller/MonitoringAwareController.php @@ -0,0 +1,175 @@ +<?php + +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\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 +{ + /** @var bool Whether to use icingadb as the backend */ + protected $useIcingadbAsBackend = false; + + /** + * Restrict the given monitored object query for the currently authenticated user + * + * @param DataView $dataView + * + * @return DataView The given data view + */ + protected function applyMonitoringRestriction(DataView $dataView) + { + $this->applyRestriction('monitoring/filter/objects', $dataView); + + return $dataView; + } + + protected function moduleInit() + { + if (Module::exists('icingadb') && IcingadbSupport::useIcingaDbAsBackend()) { + $this->useIcingadbAsBackend = true; + + return; + } + + $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 '<pre>' + . htmlspecialchars(wordwrap($query->dump())) + . '</pre>'; + 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)_<customvar-name>' + )), + $e + ); + } + } + + if ($restriction->isEmpty()) { + return Filter::matchAll(); + } + + return $restriction; + } +} diff --git a/library/Graphite/Web/Controller/TimeRangePickerTrait.php b/library/Graphite/Web/Controller/TimeRangePickerTrait.php new file mode 100644 index 0000000..7352b1b --- /dev/null +++ b/library/Graphite/Web/Controller/TimeRangePickerTrait.php @@ -0,0 +1,115 @@ +<?php + +namespace Icinga\Module\Graphite\Web\Controller; + +use Icinga\Module\Graphite\Forms\TimeRangePicker\CommonForm; +use Icinga\Module\Graphite\Forms\TimeRangePicker\CustomForm; +use Icinga\Web\Request; +use Icinga\Web\Url; +use Icinga\Web\View; + +trait TimeRangePickerTrait +{ + /** + * @var CommonForm + */ + protected $timeRangePickerCommonForm; + + /** + * @var CustomForm + */ + protected $timeRangePickerCustomForm; + + /** + * Process the given request using the forms + * + * @param Request $request The request to be processed + * + * @return Request The request supposed to be processed + */ + protected function handleTimeRangePickerRequest(Request $request = null) + { + $this->getTimeRangePickerCommonForm()->handleRequest($request); + return $this->getTimeRangePickerCustomForm()->handleRequest($request); + } + + /** + * Render all needed forms and links + * + * @param View $view + * + * @return string + */ + protected function renderTimeRangePicker(View $view) + { + $url = Url::fromRequest()->getAbsoluteUrl(); + + return '<div class="timerangepicker-container">' + . $this->getTimeRangePickerCommonForm() + . '<div class="flyover flyover-arrow-top" data-flyover-suspends-auto-refresh id="' + . $view->protectId('graphite-customrange') + . '">' + . $view->qlink(null, '#', null, [ + 'title' => $view->translate('Specify custom time range'), + 'class' => 'button-link flyover-toggle', + 'icon' => 'service' + ]) + . '<div class="flyover-content">' . $this->getTimeRangePickerCustomForm() . '</div>' + . '</div>' + . '</div>'; + } + + /** + * Get {@link timeRangePickerCommonForm} + * + * @return CommonForm + */ + public function getTimeRangePickerCommonForm() + { + if ($this->timeRangePickerCommonForm === null) { + $this->timeRangePickerCommonForm = new CommonForm(); + } + + return $this->timeRangePickerCommonForm; + } + + /** + * Set {@link timeRangePickerCommonForm} + * + * @param CommonForm $timeRangePickerCommonForm + * + * @return $this + */ + public function setTimeRangePickerCommonForm(CommonForm $timeRangePickerCommonForm) + { + $this->timeRangePickerCommonForm = $timeRangePickerCommonForm; + return $this; + } + + /** + * Get {@link timeRangePickerCustomForm} + * + * @return CustomForm + */ + public function getTimeRangePickerCustomForm() + { + if ($this->timeRangePickerCustomForm === null) { + $this->timeRangePickerCustomForm = new CustomForm(); + } + + return $this->timeRangePickerCustomForm; + } + + /** + * Set {@link timeRangePickerCustomForm} + * + * @param CustomForm $timeRangePickerCustomForm + * + * @return $this + */ + public function setTimeRangePickerCustomForm(CustomForm $timeRangePickerCustomForm) + { + $this->timeRangePickerCustomForm = $timeRangePickerCustomForm; + return $this; + } +} diff --git a/library/Graphite/Web/FakeSchemeRequest.php b/library/Graphite/Web/FakeSchemeRequest.php new file mode 100644 index 0000000..dc415cd --- /dev/null +++ b/library/Graphite/Web/FakeSchemeRequest.php @@ -0,0 +1,18 @@ +<?php + +namespace Icinga\Module\Graphite\Web; + +use Icinga\Web\Request; + +/** + * Rationale: + * + * {@link Url::fromPath()} doesn't preserve URLs which seem to be internal as they are. + */ +class FakeSchemeRequest extends Request +{ + public function getScheme() + { + return 'a random url scheme which always differs from the current request\'s one'; + } +} diff --git a/library/Graphite/Web/Form/Decorator/Proxy.php b/library/Graphite/Web/Form/Decorator/Proxy.php new file mode 100644 index 0000000..63d339c --- /dev/null +++ b/library/Graphite/Web/Form/Decorator/Proxy.php @@ -0,0 +1,47 @@ +<?php + +namespace Icinga\Module\Graphite\Web\Form\Decorator; + +use Zend_Form_Decorator_Abstract; +use Zend_Form_Decorator_Interface; + +/** + * Wrap a decorator and use it only for rendering + */ +class Proxy extends Zend_Form_Decorator_Abstract +{ + /** + * The actual decorator being proxied + * + * @var Zend_Form_Decorator_Interface + */ + protected $actualDecorator; + + public function render($content) + { + return $this->actualDecorator->render($content); + } + + /** + * Get {@link actualDecorator} + * + * @return Zend_Form_Decorator_Interface + */ + public function getActualDecorator() + { + return $this->actualDecorator; + } + + /** + * Set {@link actualDecorator} + * + * @param Zend_Form_Decorator_Interface $actualDecorator + * + * @return $this + */ + public function setActualDecorator($actualDecorator) + { + $this->actualDecorator = $actualDecorator; + return $this; + } +} diff --git a/library/Graphite/Web/Form/Validator/CustomErrorMessagesValidator.php b/library/Graphite/Web/Form/Validator/CustomErrorMessagesValidator.php new file mode 100644 index 0000000..893a5b7 --- /dev/null +++ b/library/Graphite/Web/Form/Validator/CustomErrorMessagesValidator.php @@ -0,0 +1,42 @@ +<?php + +namespace Icinga\Module\Graphite\Web\Form\Validator; + +use Zend_Validate_Abstract; + +/** + * Provides an easy way to implement validators with custom error messages + * + * TODO(ak): move to framework(?) + */ +abstract class CustomErrorMessagesValidator extends Zend_Validate_Abstract +{ + /** + * Constructor + */ + public function __construct() + { + $this->_messageTemplates = ['CUSTOM_ERROR' => '']; + } + + public function isValid($value) + { + $errorMessage = $this->validate($value); + if ($errorMessage === null) { + return true; + } + + $this->setMessage($errorMessage, 'CUSTOM_ERROR'); + $this->_error('CUSTOM_ERROR'); + return false; + } + + /** + * Validate the given value and return an error message if it's invalid + * + * @param string $value + * + * @return string|null + */ + abstract protected function validate($value); +} diff --git a/library/Graphite/Web/Form/Validator/HttpUserValidator.php b/library/Graphite/Web/Form/Validator/HttpUserValidator.php new file mode 100644 index 0000000..d5f4a86 --- /dev/null +++ b/library/Graphite/Web/Form/Validator/HttpUserValidator.php @@ -0,0 +1,30 @@ +<?php + +namespace Icinga\Module\Graphite\Web\Form\Validator; + +use Zend_Validate_Abstract; + +/** + * Validates http basic authn user names + * + * TODO(ak): move to Icinga Web 2 + */ +class HttpUserValidator extends Zend_Validate_Abstract +{ + /** + * Constructor + */ + public function __construct() + { + $this->_messageTemplates = ['HAS_COLON' => mt('graphite', 'The username must not contain colons.')]; + } + + public function isValid($value) + { + $hasColon = false !== strpos($value, ':'); + if ($hasColon) { + $this->_error('HAS_COLON'); + } + return ! $hasColon; + } +} diff --git a/library/Graphite/Web/Form/Validator/MacroTemplateValidator.php b/library/Graphite/Web/Form/Validator/MacroTemplateValidator.php new file mode 100644 index 0000000..8ff4e3c --- /dev/null +++ b/library/Graphite/Web/Form/Validator/MacroTemplateValidator.php @@ -0,0 +1,21 @@ +<?php + +namespace Icinga\Module\Graphite\Web\Form\Validator; + +use Icinga\Module\Graphite\Util\MacroTemplate; +use InvalidArgumentException; + +/** + * Validates Icinga-style macro templates + */ +class MacroTemplateValidator extends CustomErrorMessagesValidator +{ + protected function validate($value) + { + try { + new MacroTemplate($value); + } catch (InvalidArgumentException $e) { + return $e->getMessage(); + } + } +} diff --git a/library/Graphite/Web/Widget/GraphImage.php b/library/Graphite/Web/Widget/GraphImage.php new file mode 100644 index 0000000..af64e69 --- /dev/null +++ b/library/Graphite/Web/Widget/GraphImage.php @@ -0,0 +1,143 @@ +<?php + +namespace Icinga\Module\Graphite\Web\Widget; + +use Icinga\Module\Graphite\Graphing\Chart; +use Icinga\Web\Url; +use Icinga\Web\UrlParams; +use Icinga\Web\Widget\AbstractWidget; +use RuntimeException; + +class GraphImage extends AbstractWidget +{ + /** + * The chart to be rendered + * + * @var Chart + */ + protected $chart; + + /** + * The rendered PNG image + * + * @var string|null + */ + protected $rendered; + + /** + * Constructor + * + * @param Chart $chart The chart to be rendered + */ + public function __construct(Chart $chart) + { + $this->chart = $chart; + } + + /** + * Render the graph lazily + * + * @return string + */ + public function render() + { + if ($this->rendered === null) { + $now = time(); + + $from = (int) $this->chart->getFrom(); + if ($from < 0) { + $from += $now; + } + + $until = (string) $this->chart->getUntil(); + + if ($until === '') { + $until = $now; + } else { + $until = (int) $until; + if ($until < 0) { + $until += $now; + } + } + + $variables = $this->chart->getMetricVariables(); + $template = $this->chart->getTemplate(); + $graphiteWebClient = $this->chart->getGraphiteWebClient(); + $params = (new UrlParams())->addValues([ + 'from' => $from, + 'until' => $until, + 'bgcolor' => $this->chart->getBackgroundColor() ?? 'black', + 'fgcolor' => $this->chart->getForegroundColor() ?? 'white', + 'majorGridLineColor' => $this->chart->getMajorGridLineColor() ?? '0000003F', + 'minorGridLineColor' => $this->chart->getMinorGridLineColor() ?? 'black', + 'width' => $this->chart->getWidth(), + 'height' => $this->chart->getHeight(), + 'hideLegend' => (string) ! $this->chart->getShowLegend(), + 'tz' => date_default_timezone_get(), + '_salt' => "$now.000", + 'vTitle' => 'Percent', + 'lineMode' => 'connected', + 'drawNullAsZero' => 'false', + 'graphType' => 'line', + '_ext' => 'whatever.svg' + ]); + + foreach ($template->getUrlParams() as $key => $value) { + $params->set($key, $value->resolve($variables)); + } + + $metrics = $this->chart->getMetrics(); + $allVars = []; + + foreach ($template->getCurves() as $curveName => $curve) { + if (!isset($metrics[$curveName])) { + continue; + } + + $vars = $curve[0]->reverseResolve($metrics[$curveName]); + + if ($vars !== false) { + $allVars = array_merge($allVars, $vars); + } + } + + foreach ($metrics as $curveName => $metric) { + $allVars['metric'] = $metric; + $params->add('target', $template->getCurves()[$curveName][1]->resolve($allVars)); + } + + $url = Url::fromPath('/render')->setParams($params); + $headers = [ + 'Accept-language' => 'en', + 'Content-type' => 'application/x-www-form-urlencoded' + ]; + + for (;;) { + try { + $this->rendered = $graphiteWebClient->request($url, 'GET', $headers); + } catch (RuntimeException $e) { + if (preg_match('/\b500\b/', $e->getMessage())) { + // A 500 Internal Server Error, probably because of + // a division by zero because of a too low time range to render. + + $until = (int) $url->getParam('until'); + $diff = $until - (int) $url->getParam('from'); + + // Try to render a higher time range, but give up + // once our default (1h) has been reached (non successfully). + if ($diff < 3600) { + $url->setParam('from', $until - $diff * 2); + continue; + } + } + + throw $e; + } + + break; + } + } + + return $this->rendered; + } +} diff --git a/library/Graphite/Web/Widget/Graphs.php b/library/Graphite/Web/Widget/Graphs.php new file mode 100644 index 0000000..e18b8da --- /dev/null +++ b/library/Graphite/Web/Widget/Graphs.php @@ -0,0 +1,688 @@ +<?php + +namespace Icinga\Module\Graphite\Web\Widget; + +use Icinga\Application\Config; +use Icinga\Application\Icinga; +use Icinga\Authentication\Auth; +use Icinga\Exception\ConfigurationError; +use Icinga\Module\Graphite\Graphing\Chart; +use Icinga\Module\Graphite\Graphing\GraphingTrait; +use Icinga\Module\Graphite\Graphing\Template; +use Icinga\Module\Graphite\Util\InternalProcessTracker as IPT; +use Icinga\Module\Graphite\Util\TimeRangePickerTools; +use Icinga\Module\Graphite\Web\Widget\Graphs\Host as HostGraphs; +use Icinga\Module\Graphite\Web\Widget\Graphs\Service as ServiceGraphs; +use Icinga\Module\Monitoring\Object\Host; +use Icinga\Module\Monitoring\Object\MonitoredObject; +use Icinga\Module\Monitoring\Object\Service; +use Icinga\Web\Request; +use Icinga\Web\Url; +use Icinga\Web\View; +use Icinga\Web\Widget\AbstractWidget; +use ipl\Orm\Model; +use Icinga\Module\Icingadb\Model\Host as IcingadbHost; +use Icinga\Module\Graphite\Web\Widget\Graphs\Icingadb\IcingadbHost as IcingadbHostGraphs; +use Icinga\Module\Graphite\Web\Widget\Graphs\Icingadb\IcingadbService as IcingadbServiceGraphs; + +abstract class Graphs extends AbstractWidget +{ + use GraphingTrait; + + /** + * The Icinga custom variable with the "real" check command (if any) of objects we display graphs for + * + * @var string + */ + protected static $obscuredCheckCommandCustomVar; + + /** + * The type of the object to render the graphs for + * + * @var string + */ + protected $objectType; + + /** + * The object to render the graphs for + * + * @var MonitoredObject|Model + */ + protected $object; + + /** + * Graph image width + * + * @var string + */ + protected $width = '350'; + + /** + * Graph image height + * + * @var string + */ + protected $height = '200'; + + /** + * Graph range start + * + * @var string + */ + protected $start; + + /** + * Graph range end + * + * @var string + */ + protected $end; + + /** + * Whether to render as compact as possible + * + * @var bool + */ + protected $compact = false; + + /** + * The check command of the monitored object we display graphs for + * + * @var string + */ + protected $checkCommand; + + /** + * The "real" check command (if any) of the monitored object we display graphs for + * + * E.g. the command executed remotely via check_by_ssh + * + * @var string|null + */ + protected $obscuredCheckCommand; + + /** + * Additional CSS classes for the <div/>s around the images + * + * @var string[] + */ + protected $classes = []; + + /** + * Whether to serve a transparent dummy image first and let the JS code load the actual graph + * + * @var bool + */ + protected $preloadDummy = false; + + /** + * Whether to render the graphs inline + * + * @var bool + */ + protected $renderInline; + + /** + * Whether to explicitly display that no graphs were found + * + * @var bool|null + */ + protected $showNoGraphsFound; + + /** + * Factory, based on the given monitoring object + * + * @param MonitoredObject $object + * + * @return static + */ + public static function forMonitoredObject(MonitoredObject $object) + { + switch ($object->getType()) { + case 'host': + /** @var Host $object */ + return new HostGraphs($object); + + case 'service': + /** @var Service $object */ + return new ServiceGraphs($object); + } + } + + /** + * Factory, based on the given icingadb object + * + * @param Model $object + * + * @return static + */ + public static function forIcingadbObject(Model $object) + { + if ($object instanceof IcingadbHost) { + return new IcingadbHostGraphs($object); + } + + return new IcingadbServiceGraphs($object); + } + + /** + * Get the Icinga custom variable with the "real" check command (if any) of monitored objects we display graphs for + * + * @return string + */ + public static function getObscuredCheckCommandCustomVar() + { + if (static::$obscuredCheckCommandCustomVar === null) { + static::$obscuredCheckCommandCustomVar = Config::module('graphite') + ->get('icinga', 'customvar_obscured_check_command', 'check_command'); + } + + return static::$obscuredCheckCommandCustomVar; + } + + /** + * Constructor + * + * @param MonitoredObject|Model $object The object to render the graphs for + */ + public function __construct($object) + { + $this->object = $object; + $this->renderInline = Url::fromRequest()->getParam('format') === 'pdf'; + + if ($object instanceof Model) { + $this->checkCommand = $object->checkcommand_name; + $this->obscuredCheckCommand = $object->vars[Graphs::getObscuredCheckCommandCustomVar()] ?? null; + } else { + $this->checkCommand = $object->{"{$this->objectType}_check_command"}; + $this->obscuredCheckCommand = $object->{ + "_{$this->objectType}_" . Graphs::getObscuredCheckCommandCustomVar() + }; + } + } + + /** + * Process the given request using this widget + * + * @param Request $request The request to be processed + * + * @return $this + */ + public function handleRequest(Request $request = null) + { + if ($request === null) { + $request = Icinga::app()->getRequest(); + } + + $params = $request->getUrl()->getParams(); + list($this->start, $this->end) = $this->getRangeFromTimeRangePicker($request); + $this->width = $params->shift('width', $this->width); + $this->height = $params->shift('height', $this->height); + + return $this; + } + + /** + * Render the graphs list + * + * @return string + */ + protected function getGraphsList() + { + $result = []; // kind of string builder + $imageBaseUrl = $this->getImageBaseUrl(); + $allTemplates = $this->getAllTemplates(); + $actualCheckCommand = $this->obscuredCheckCommand === null ? $this->checkCommand : $this->obscuredCheckCommand; + $concreteTemplates = $allTemplates->getTemplates($actualCheckCommand); + + $excludedMetrics = []; + + foreach ($concreteTemplates as $concreteTemplate) { + foreach ($concreteTemplate->getCurves() as $curve) { + $excludedMetrics[] = $curve[0]; + } + } + + IPT::recordf("Icinga check command: %s", $this->checkCommand); + IPT::recordf("Obscured check command: %s", $this->obscuredCheckCommand); + + foreach ( + [ + ['template', $concreteTemplates, []], + ['default_template', $allTemplates->getDefaultTemplates(), $excludedMetrics], + ] as $templateSet + ) { + list($urlParam, $templates, $excludeMetrics) = $templateSet; + + if ($urlParam === 'template') { + IPT::recordf('Applying templates for check command %s', $actualCheckCommand); + } else { + IPT::recordf('Applying default templates, excluding previously used metrics'); + } + + IPT::indent(); + + foreach ($templates as $templateName => $template) { + if ($this->designedForObjectType($template)) { + IPT::recordf('Applying template %s', $templateName); + IPT::indent(); + + $charts = $template->getCharts( + static::getMetricsDataSource(), + $this->object, + [], + $excludeMetrics + ); + + if (! empty($charts)) { + $currentGraphs = []; + + foreach ($charts as $chart) { + /** @var Chart $chart */ + + $metricVariables = $chart->getMetricVariables(); + $bestIntersect = -1; + $bestPos = count($result); + + foreach ($result as $graphPos => & $graph) { + $currentIntersect = count(array_intersect_assoc($graph[1], $metricVariables)); + + if ($currentIntersect >= $bestIntersect) { + $bestIntersect = $currentIntersect; + $bestPos = $graphPos + 1; + } + } + unset($graph); + + $urlParams = $template->getUrlParams(); + if (array_key_exists("height", $urlParams)) { + $actheight = $urlParams["height"]->resolve(['height']); + if ($actheight < $this->height) { + $actheight = $this->height; + } + } else { + $actheight = $this->height; + } + $actwidth = $this->width; + $actwidthfix = ""; + if (array_key_exists("width", $urlParams)) { + $actwidth = $urlParams["width"]->resolve(['width']); + $actwidthfix = "width: {$actwidth}px; "; + } + + if ($this->renderInline) { + $chart->setFrom($this->start) + ->setUntil($this->end) + ->setWidth($actwidth) + ->setHeight($actheight) + ->setBackgroundColor('white') + ->setForegroundColor('black') + ->setMajorGridLineColor('grey') + ->setMinorGridLineColor('white') + ->setShowLegend(! $this->compact); + + $img = new InlineGraphImage($chart); + } else { + $imageUrl = $this->filterImageUrl($imageBaseUrl->with($metricVariables)) + ->setParam($urlParam, $templateName) + ->setParam('start', $this->start) + ->setParam('end', $this->end) + ->setParam('width', $actwidth) + ->setParam('height', $actheight); + + if (! $this->compact) { + $imageUrl->setParam('legend', 1); + } + + if ($this->preloadDummy) { + $src = 'data:image/png;base64,' // 1x1 dummy + . 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRS' + . 'TlMAQObYZgAAAApJREFUeAFjZAAAAAQAAhq+CAMAAAAASUVORK5CYII='; + } else { + $src = $imageUrl; + } + + $img = '<img id="graphiteImg-' . md5((string) $imageUrl) . '"' + . " src=\"$src\" data-actualimageurl=\"$imageUrl\" class=\"detach graphiteImg\"" + . " alt=\"\" width=\"$actwidth\" height=\"$actheight\"" + . " style=\"min-width: {$actwidth}px; $actwidthfix min-height: {$actheight}px;\">"; + } + + $currentGraphs[] = [$img, $metricVariables, $bestPos]; + } + + foreach (array_reverse($currentGraphs) as $graph) { + list($img, $metricVariables, $bestPos) = $graph; + array_splice($result, $bestPos, 0, [[$img, $metricVariables]]); + } + } + + IPT::unindent(); + } else { + IPT::recordf('Not applying template %s', $templateName); + } + } + + IPT::unindent(); + } + + if (! empty($result)) { + foreach ($result as & $graph) { + $graph = $graph[0]; + } + unset($graph); + + $currentUrl = Icinga::app()->getRequest()->getUrl(); + $limit = (int) $currentUrl->getParam('graphs_limit', 2); + $total = count($result); + + if ($limit < 1) { + $limit = -1; + } + + if ($limit !== -1 && $total > $limit) { + $result = array_slice($result, 0, $limit); + + if (! $this->compact) { + /** @var View $view */ + $view = $this->view(); + + $url = $this->getGraphsListBaseUrl(); + TimeRangePickerTools::copyAllRangeParameters($url->getParams(), $currentUrl->getParams()); + + $result[] = "<p class='load-more'>{$view->qlink( + sprintf($view->translate('Load all %d graphs'), $total), + $url->setParam('graphs_limit', '-1'), + null, + ['class' => 'action-link'] + )}</p>"; + } + } + + $classes = $this->classes; + $classes[] = 'images'; + + array_unshift($result, '<div class="' . implode(' ', $classes) . '">'); + $result[] = '</div>'; + } + + if ($this->renderInline) { + foreach ($result as $html) { + if ($html instanceof InlineGraphImage) { + // Errors should occur now or not at all + $html->render(); + } + } + } + + return implode($result); + } + + public function render() + { + IPT::clear(); + + try { + $result = $this->getGraphsList(); + } catch (ConfigurationError $e) { + $view = $this->view(); + + return "<p>{$view->escape($e->getMessage())}</p>" + . '<p>' . vsprintf( + $view->escape($view->translate('Please %scorrect%s the configuration of the Graphite module.')), + Auth::getInstance()->hasPermission('config/modules') + ? explode( + '$LINK_TEXT$', + $view->qlink('$LINK_TEXT$', 'graphite/config/backend', null, ['class' => 'action-link']) + ) + : ['', ''] + ) . '</p>'; + } + + $view = $this->view(); + + if ($result === '' && $this->getShowNoGraphsFound()) { + $result = "<p>{$view->escape($view->translate('No graphs found'))}</p>"; + } + + if (IPT::enabled()) { + $result .= "<h3>{$view->escape($view->translate('Graphs assembling process record'))}</h3>" + . "<pre>{$view->escape(IPT::dump())}</pre>"; + } + + return $result; + } + + /** + * Get time range parameters for Graphite from the URL + * + * @param Request $request The request to be used + * + * @return string[] + */ + protected function getRangeFromTimeRangePicker(Request $request) + { + $params = $request->getUrl()->getParams(); + $relative = $params->get(TimeRangePickerTools::getRelativeRangeParameter()); + if ($relative !== null) { + return ["-$relative", null]; + } + + $absolute = TimeRangePickerTools::getAbsoluteRangeParameters(); + $start = $params->get($absolute['start']); + return [ + $start === null ? -TimeRangePickerTools::getDefaultRelativeTimeRange() : $start, + $params->get($absolute['end']) + ]; + } + + /** + * Return a identifier specifying the monitored object we display graphs for + * + * @return string + */ + abstract protected function getMonitoredObjectIdentifier(); + + /** + * Get the base URL to a graph specifying just the monitored object kind + * + * @return Url + */ + abstract protected function getImageBaseUrl(); + + /** + * Get the base URL to the monitored object's graphs list + * + * @return Url + */ + abstract protected function getGraphsListBaseUrl(); + + /** + * Extend the {@link getImageBaseUrl()}'s result's parameters with the concrete monitored object + * + * @param Url $url The URL to extend + * + * @return Url The given URL + */ + abstract protected function filterImageUrl(Url $url); + + /** + * Return whether the given template is designed for the type of the object we display graphs for + * + * @param Template $template + * + * @return bool + */ + abstract protected function designedForObjectType(Template $template); + + /** + * Get {@link compact} + * + * @return bool + */ + public function getCompact() + { + return $this->compact; + } + + /** + * Set {@link compact} + * + * @param bool $compact + * + * @return $this + */ + public function setCompact($compact = true) + { + $this->compact = $compact; + return $this; + } + + /** + * Get the graph image width + * + * @return string + */ + public function getWidth() + { + return $this->width; + } + + /** + * Set the graph image width + * + * @param string $width + * + * @return $this + */ + public function setWidth($width) + { + $this->width = $width; + + return $this; + } + + /** + * Get the graph image height + * + * @return string + */ + public function getHeight() + { + return $this->height; + } + + /** + * Set the graph image height + * + * @param string $height + * + * @return $this + */ + public function setHeight($height) + { + $this->height = $height; + + return $this; + } + + /** + * Get additional CSS classes for the <div/>s around the images + * + * @return string[] + */ + public function getClasses() + { + return $this->classes; + } + + /** + * Set additional CSS classes for the <div/>s around the images + * + * @param string[] $classes + * + * @return $this + */ + public function setClasses($classes) + { + $this->classes = $classes; + + return $this; + } + + /** + * Get whether to serve a transparent dummy image first and let the JS code load the actual graph + * + * @return bool + */ + public function getPreloadDummy() + { + return $this->preloadDummy; + } + + /** + * Set whether to serve a transparent dummy image first and let the JS code load the actual graph + * + * @param bool $preloadDummy + * + * @return $this + */ + public function setPreloadDummy($preloadDummy = true) + { + $this->preloadDummy = $preloadDummy; + + return $this; + } + + /** + * Get whether to render the graphs inline + * + * @return bool + */ + public function getRenderInline() + { + return $this->renderInline; + } + + /** + * Set whether to render the graphs inline + * + * @param bool $renderInline + * + * @return $this + */ + public function setRenderInline($renderInline = true) + { + $this->renderInline = $renderInline; + + return $this; + } + + /** + * Get whether to explicitly display that no graphs were found + * + * @return bool + */ + public function getShowNoGraphsFound() + { + if ($this->showNoGraphsFound === null) { + $this->showNoGraphsFound = ! Config::module('graphite')->get('ui', 'disable_no_graphs_found'); + } + + return $this->showNoGraphsFound; + } + + /** + * Set whether to explicitly display that no graphs were found + * + * @param bool $showNoGraphsFound + * + * @return $this + */ + public function setShowNoGraphsFound($showNoGraphsFound = true) + { + $this->showNoGraphsFound = $showNoGraphsFound; + + return $this; + } +} diff --git a/library/Graphite/Web/Widget/Graphs/Host.php b/library/Graphite/Web/Widget/Graphs/Host.php new file mode 100644 index 0000000..2247bcc --- /dev/null +++ b/library/Graphite/Web/Widget/Graphs/Host.php @@ -0,0 +1,51 @@ +<?php + +namespace Icinga\Module\Graphite\Web\Widget\Graphs; + +use Icinga\Module\Graphite\Graphing\Template; +use Icinga\Module\Graphite\Web\Widget\Graphs; +use Icinga\Module\Monitoring\Object\Host as MonitoredHost; +use Icinga\Web\Url; + +class Host extends Graphs +{ + protected $objectType = 'host'; + + /** + * The host to render the graphs of + * + * @var MonitoredHost + */ + protected $object; + + protected function getImageBaseUrl() + { + return Url::fromPath('graphite/graph/host'); + } + + protected function getGraphsListBaseUrl() + { + return Url::fromPath('graphite/list/hosts', ['host' => $this->object->getName()]); + } + + protected function filterImageUrl(Url $url) + { + return $url->setParam('host.name', $this->object->getName()); + } + + protected function getMonitoredObjectIdentifier() + { + return $this->object->getName(); + } + + protected function designedForObjectType(Template $template) + { + foreach ($template->getCurves() as $curve) { + if (in_array('host_name_template', $curve[0]->getMacros())) { + return true; + } + } + + return false; + } +} diff --git a/library/Graphite/Web/Widget/Graphs/Icingadb/IcingadbHost.php b/library/Graphite/Web/Widget/Graphs/Icingadb/IcingadbHost.php new file mode 100644 index 0000000..2b0a614 --- /dev/null +++ b/library/Graphite/Web/Widget/Graphs/Icingadb/IcingadbHost.php @@ -0,0 +1,61 @@ +<?php + +namespace Icinga\Module\Graphite\Web\Widget\Graphs\Icingadb; + +use Icinga\Module\Graphite\Graphing\Template; +use Icinga\Module\Graphite\Web\Widget\Graphs; +use Icinga\Web\Url; +use Icinga\Module\Icingadb\Model\Host; + +class IcingadbHost extends Graphs +{ + protected $objectType = 'host'; + + /** + * The Icingadb host to render the graphs for + * + * @var Host + */ + protected $object; + + protected function getGraphsListBaseUrl() + { + return Url::fromPath('graphite/hosts', ['host.name' => $this->object->name]); + } + + protected function filterImageUrl(Url $url) + { + return $url->setParam('host.name', $this->object->name); + } + + public function createHostTitle() + { + return $this->object->name; + } + + public function getObjectType() + { + return $this->objectType; + } + + protected function getMonitoredObjectIdentifier() + { + return $this->object->name; + } + + protected function getImageBaseUrl() + { + return Url::fromPath('graphite/graph/host'); + } + + protected function designedForObjectType(Template $template) + { + foreach ($template->getCurves() as $curve) { + if (in_array('host_name_template', $curve[0]->getMacros())) { + return true; + } + } + + return false; + } +} diff --git a/library/Graphite/Web/Widget/Graphs/Icingadb/IcingadbService.php b/library/Graphite/Web/Widget/Graphs/Icingadb/IcingadbService.php new file mode 100644 index 0000000..7827e86 --- /dev/null +++ b/library/Graphite/Web/Widget/Graphs/Icingadb/IcingadbService.php @@ -0,0 +1,71 @@ +<?php + +namespace Icinga\Module\Graphite\Web\Widget\Graphs\Icingadb; + +use Icinga\Module\Graphite\Graphing\Template; +use Icinga\Module\Graphite\Web\Widget\Graphs; +use Icinga\Web\Url; +use Icinga\Module\Icingadb\Model\Service; + +class IcingadbService extends Graphs +{ + protected $objectType = 'service'; + + /** + * The icingadb service to render the graphs for + * + * @var Service + */ + protected $object; + + protected function getGraphsListBaseUrl() + { + return Url::fromPath( + 'graphite/services', + ['service.name' => $this->object->name, 'host.name' => $this->object->host->name] + ); + } + + protected function filterImageUrl(Url $url) + { + return $url + ->setParam('host.name', $this->object->host->name) + ->setParam('service.name', $this->object->name); + } + + public function createHostTitle() + { + return $this->object->host->name; + } + + public function createServiceTitle() + { + return ' : ' . $this->object->name; + } + + public function getObjectType() + { + return $this->objectType; + } + + protected function getMonitoredObjectIdentifier() + { + return $this->object->host->name . ':' . $this->object->name; + } + + protected function getImageBaseUrl() + { + return Url::fromPath('graphite/graph/service'); + } + + protected function designedforObjectType(Template $template) + { + foreach ($template->getCurves() as $curve) { + if (in_array('service_name_template', $curve[0]->getMacros())) { + return true; + } + } + + return false; + } +} diff --git a/library/Graphite/Web/Widget/Graphs/Service.php b/library/Graphite/Web/Widget/Graphs/Service.php new file mode 100644 index 0000000..5fc0143 --- /dev/null +++ b/library/Graphite/Web/Widget/Graphs/Service.php @@ -0,0 +1,56 @@ +<?php + +namespace Icinga\Module\Graphite\Web\Widget\Graphs; + +use Icinga\Module\Graphite\Graphing\Template; +use Icinga\Module\Graphite\Web\Widget\Graphs; +use Icinga\Module\Monitoring\Object\Service as MonitoredService; +use Icinga\Web\Url; + +class Service extends Graphs +{ + protected $objectType = 'service'; + + /** + * The service to render the graphs for + * + * @var MonitoredService + */ + protected $object; + + protected function getImageBaseUrl() + { + return Url::fromPath('graphite/graph/service'); + } + + protected function getGraphsListBaseUrl() + { + return Url::fromPath( + 'graphite/list/services', + ['host' => $this->object->getHost()->getName(), 'service' => $this->object->getName()] + ); + } + + protected function filterImageUrl(Url $url) + { + return $url + ->setParam('host.name', $this->object->getHost()->getName()) + ->setParam('service.name', $this->object->getName()); + } + + protected function getMonitoredObjectIdentifier() + { + return $this->object->getHost()->getName() . ':' . $this->object->getName(); + } + + protected function designedForObjectType(Template $template) + { + foreach ($template->getCurves() as $curve) { + if (in_array('service_name_template', $curve[0]->getMacros())) { + return true; + } + } + + return false; + } +} diff --git a/library/Graphite/Web/Widget/IcingadbGraphs.php b/library/Graphite/Web/Widget/IcingadbGraphs.php new file mode 100644 index 0000000..e038e92 --- /dev/null +++ b/library/Graphite/Web/Widget/IcingadbGraphs.php @@ -0,0 +1,106 @@ +<?php + +/* Icinga Graphite Web | (c) 2022 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Graphite\Web\Widget; + +use Icinga\Data\Filter\Filter; +use Icinga\Module\Graphite\Web\Widget\Graphs\Icingadb\IcingadbHost; +use Icinga\Module\Graphite\Web\Widget\Graphs\Icingadb\IcingadbService; +use Icinga\Module\Icingadb\Common\Links; +use Icinga\Module\Icingadb\Widget\EmptyState; +use Icinga\Module\Icingadb\Model\Host; +use ipl\Html\BaseHtmlElement; +use ipl\Html\Html; +use ipl\Html\HtmlDocument; +use ipl\Html\HtmlString; +use ipl\Orm\ResultSet; +use ipl\Stdlib\BaseFilter; +use ipl\Web\Filter\QueryString; +use ipl\Web\Widget\Link; + +/** +* Class for creating graphs of icingadb objects +*/ +class IcingadbGraphs extends BaseHtmlElement +{ + use BaseFilter; + + protected $defaultAttributes = ['class' => 'grid']; + + /** @var Iterable */ + protected $objects; + + protected $tag = 'div'; + + /** + * Create a new Graph item + * + * @param ResultSet $objects + */ + public function __construct(ResultSet $objects) + { + $this->objects = $objects; + } + + protected function assemble() + { + if (! $this->objects->hasResult()) { + $this->add(new EmptyState(t('No items found.'))); + } + + foreach ($this->objects as $object) { + $this->add($this->createGridItem($object)); + } + + $document = new HtmlDocument(); + $document->addHtml(Html::tag('div', ['class' => 'graphite-graph-color-registry']), $this); + $this->prependWrapper($document); + } + + protected function createGridItem($object) + { + if ($object instanceof Host) { + $graph = new IcingadbHost($object); + $hostObj = $object; + } else { + $graph = new IcingadbService($object); + $hostObj = $object->host; + } + + $hostUrl = Links::host($hostObj); + + if ($this->hasBaseFilter()) { + $hostUrl->addFilter(Filter::fromQueryString(QueryString::render($this->getBaseFilter()))); + } + + $hostLink = new Link( + $graph->createHostTitle(), + $hostUrl, + ['data-base-target' => '_next'] + ); + + $serviceLink = null; + if ($graph->getObjectType() === 'service') { + $serviceUrl = Links::service($object, $hostObj); + + if ($this->hasBaseFilter()) { + $serviceUrl->addFilter(Filter::fromQueryString(QueryString::render($this->getBaseFilter()))); + } + + $serviceLink = new Link( + $graph->createServiceTitle(), + $serviceUrl, + ['data-base-target' => '_next'] + ); + } + + $gridItem = Html::tag('div', ['class' => 'grid-item']); + $header = Html::tag('h2'); + + $header->add([$hostLink, $serviceLink]); + $gridItem->add($header); + + return $gridItem->add(HtmlString::create($graph->setPreloadDummy()->handleRequest())); + } +} diff --git a/library/Graphite/Web/Widget/InlineGraphImage.php b/library/Graphite/Web/Widget/InlineGraphImage.php new file mode 100644 index 0000000..881384d --- /dev/null +++ b/library/Graphite/Web/Widget/InlineGraphImage.php @@ -0,0 +1,49 @@ +<?php + +namespace Icinga\Module\Graphite\Web\Widget; + +use Icinga\Module\Graphite\Graphing\Chart; +use Icinga\Web\Widget\AbstractWidget; + +class InlineGraphImage extends AbstractWidget +{ + /** + * The image to be rendered + * + * @var GraphImage + */ + protected $image; + + /** + * The rendered <img> + * + * @var string|null + */ + protected $rendered; + + /** + * Constructor + * + * @param Chart $chart The chart to be rendered + */ + public function __construct(Chart $chart) + { + $this->image = new GraphImage($chart); + } + + /** + * Render the graph lazily + * + * @return string + */ + public function render() + { + if ($this->rendered === null) { + $this->rendered = '<img src="data:image/png;base64,' + . implode("\n", str_split(base64_encode($this->image->render()), 76)) + . '">'; + } + + return $this->rendered; + } +} diff --git a/library/vendor/Psr/Http/Message/MessageInterface.php b/library/vendor/Psr/Http/Message/MessageInterface.php new file mode 100644 index 0000000..8f67a05 --- /dev/null +++ b/library/vendor/Psr/Http/Message/MessageInterface.php @@ -0,0 +1,187 @@ +<?php + +namespace Psr\Http\Message; + +/** + * HTTP messages consist of requests from a client to a server and responses + * from a server to a client. This interface defines the methods common to + * each. + * + * Messages are considered immutable; all methods that might change state MUST + * be implemented such that they retain the internal state of the current + * message and return an instance that contains the changed state. + * + * @link http://www.ietf.org/rfc/rfc7230.txt + * @link http://www.ietf.org/rfc/rfc7231.txt + */ +interface MessageInterface +{ + /** + * Retrieves the HTTP protocol version as a string. + * + * The string MUST contain only the HTTP version number (e.g., "1.1", "1.0"). + * + * @return string HTTP protocol version. + */ + public function getProtocolVersion(); + + /** + * Return an instance with the specified HTTP protocol version. + * + * The version string MUST contain only the HTTP version number (e.g., + * "1.1", "1.0"). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new protocol version. + * + * @param string $version HTTP protocol version + * @return self + */ + public function withProtocolVersion($version); + + /** + * Retrieves all message header values. + * + * The keys represent the header name as it will be sent over the wire, and + * each value is an array of strings associated with the header. + * + * // Represent the headers as a string + * foreach ($message->getHeaders() as $name => $values) { + * echo $name . ": " . implode(", ", $values); + * } + * + * // Emit headers iteratively: + * foreach ($message->getHeaders() as $name => $values) { + * foreach ($values as $value) { + * header(sprintf('%s: %s', $name, $value), false); + * } + * } + * + * While header names are not case-sensitive, getHeaders() will preserve the + * exact case in which headers were originally specified. + * + * @return array Returns an associative array of the message's headers. Each + * key MUST be a header name, and each value MUST be an array of strings + * for that header. + */ + public function getHeaders(); + + /** + * Checks if a header exists by the given case-insensitive name. + * + * @param string $name Case-insensitive header field name. + * @return bool Returns true if any header names match the given header + * name using a case-insensitive string comparison. Returns false if + * no matching header name is found in the message. + */ + public function hasHeader($name); + + /** + * Retrieves a message header value by the given case-insensitive name. + * + * This method returns an array of all the header values of the given + * case-insensitive header name. + * + * If the header does not appear in the message, this method MUST return an + * empty array. + * + * @param string $name Case-insensitive header field name. + * @return string[] An array of string values as provided for the given + * header. If the header does not appear in the message, this method MUST + * return an empty array. + */ + public function getHeader($name); + + /** + * Retrieves a comma-separated string of the values for a single header. + * + * This method returns all of the header values of the given + * case-insensitive header name as a string concatenated together using + * a comma. + * + * NOTE: Not all header values may be appropriately represented using + * comma concatenation. For such headers, use getHeader() instead + * and supply your own delimiter when concatenating. + * + * If the header does not appear in the message, this method MUST return + * an empty string. + * + * @param string $name Case-insensitive header field name. + * @return string A string of values as provided for the given header + * concatenated together using a comma. If the header does not appear in + * the message, this method MUST return an empty string. + */ + public function getHeaderLine($name); + + /** + * Return an instance with the provided value replacing the specified header. + * + * While header names are case-insensitive, the casing of the header will + * be preserved by this function, and returned from getHeaders(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new and/or updated header and value. + * + * @param string $name Case-insensitive header field name. + * @param string|string[] $value Header value(s). + * @return self + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withHeader($name, $value); + + /** + * Return an instance with the specified header appended with the given value. + * + * Existing values for the specified header will be maintained. The new + * value(s) will be appended to the existing list. If the header did not + * exist previously, it will be added. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new header and/or value. + * + * @param string $name Case-insensitive header field name to add. + * @param string|string[] $value Header value(s). + * @return self + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withAddedHeader($name, $value); + + /** + * Return an instance without the specified header. + * + * Header resolution MUST be done without case-sensitivity. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the named header. + * + * @param string $name Case-insensitive header field name to remove. + * @return self + */ + public function withoutHeader($name); + + /** + * Gets the body of the message. + * + * @return StreamInterface Returns the body as a stream. + */ + public function getBody(); + + /** + * Return an instance with the specified message body. + * + * The body MUST be a StreamInterface object. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return a new instance that has the + * new body stream. + * + * @param StreamInterface $body Body. + * @return self + * @throws \InvalidArgumentException When the body is not valid. + */ + public function withBody(StreamInterface $body); +} diff --git a/library/vendor/Psr/Http/Message/RequestInterface.php b/library/vendor/Psr/Http/Message/RequestInterface.php new file mode 100644 index 0000000..75c802e --- /dev/null +++ b/library/vendor/Psr/Http/Message/RequestInterface.php @@ -0,0 +1,129 @@ +<?php + +namespace Psr\Http\Message; + +/** + * Representation of an outgoing, client-side request. + * + * Per the HTTP specification, this interface includes properties for + * each of the following: + * + * - Protocol version + * - HTTP method + * - URI + * - Headers + * - Message body + * + * During construction, implementations MUST attempt to set the Host header from + * a provided URI if no Host header is provided. + * + * Requests are considered immutable; all methods that might change state MUST + * be implemented such that they retain the internal state of the current + * message and return an instance that contains the changed state. + */ +interface RequestInterface extends MessageInterface +{ + /** + * Retrieves the message's request target. + * + * Retrieves the message's request-target either as it will appear (for + * clients), as it appeared at request (for servers), or as it was + * specified for the instance (see withRequestTarget()). + * + * In most cases, this will be the origin-form of the composed URI, + * unless a value was provided to the concrete implementation (see + * withRequestTarget() below). + * + * If no URI is available, and no request-target has been specifically + * provided, this method MUST return the string "/". + * + * @return string + */ + public function getRequestTarget(); + + /** + * Return an instance with the specific request-target. + * + * If the request needs a non-origin-form request-target — e.g., for + * specifying an absolute-form, authority-form, or asterisk-form — + * this method may be used to create an instance with the specified + * request-target, verbatim. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * changed request target. + * + * @link http://tools.ietf.org/html/rfc7230#section-2.7 (for the various + * request-target forms allowed in request messages) + * @param mixed $requestTarget + * @return self + */ + public function withRequestTarget($requestTarget); + + /** + * Retrieves the HTTP method of the request. + * + * @return string Returns the request method. + */ + public function getMethod(); + + /** + * Return an instance with the provided HTTP method. + * + * While HTTP method names are typically all uppercase characters, HTTP + * method names are case-sensitive and thus implementations SHOULD NOT + * modify the given string. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * changed request method. + * + * @param string $method Case-sensitive method. + * @return self + * @throws \InvalidArgumentException for invalid HTTP methods. + */ + public function withMethod($method); + + /** + * Retrieves the URI instance. + * + * This method MUST return a UriInterface instance. + * + * @link http://tools.ietf.org/html/rfc3986#section-4.3 + * @return UriInterface Returns a UriInterface instance + * representing the URI of the request. + */ + public function getUri(); + + /** + * Returns an instance with the provided URI. + * + * This method MUST update the Host header of the returned request by + * default if the URI contains a host component. If the URI does not + * contain a host component, any pre-existing Host header MUST be carried + * over to the returned request. + * + * You can opt-in to preserving the original state of the Host header by + * setting `$preserveHost` to `true`. When `$preserveHost` is set to + * `true`, this method interacts with the Host header in the following ways: + * + * - If the the Host header is missing or empty, and the new URI contains + * a host component, this method MUST update the Host header in the returned + * request. + * - If the Host header is missing or empty, and the new URI does not contain a + * host component, this method MUST NOT update the Host header in the returned + * request. + * - If a Host header is present and non-empty, this method MUST NOT update + * the Host header in the returned request. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new UriInterface instance. + * + * @link http://tools.ietf.org/html/rfc3986#section-4.3 + * @param UriInterface $uri New request URI to use. + * @param bool $preserveHost Preserve the original state of the Host header. + * @return self + */ + public function withUri(UriInterface $uri, $preserveHost = false); +} diff --git a/library/vendor/Psr/Http/Message/ResponseInterface.php b/library/vendor/Psr/Http/Message/ResponseInterface.php new file mode 100644 index 0000000..6724809 --- /dev/null +++ b/library/vendor/Psr/Http/Message/ResponseInterface.php @@ -0,0 +1,68 @@ +<?php + +namespace Psr\Http\Message; + +/** + * Representation of an outgoing, server-side response. + * + * Per the HTTP specification, this interface includes properties for + * each of the following: + * + * - Protocol version + * - Status code and reason phrase + * - Headers + * - Message body + * + * Responses are considered immutable; all methods that might change state MUST + * be implemented such that they retain the internal state of the current + * message and return an instance that contains the changed state. + */ +interface ResponseInterface extends MessageInterface +{ + /** + * Gets the response status code. + * + * The status code is a 3-digit integer result code of the server's attempt + * to understand and satisfy the request. + * + * @return int Status code. + */ + public function getStatusCode(); + + /** + * Return an instance with the specified status code and, optionally, reason phrase. + * + * If no reason phrase is specified, implementations MAY choose to default + * to the RFC 7231 or IANA recommended reason phrase for the response's + * status code. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated status and reason phrase. + * + * @link http://tools.ietf.org/html/rfc7231#section-6 + * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml + * @param int $code The 3-digit integer result code to set. + * @param string $reasonPhrase The reason phrase to use with the + * provided status code; if none is provided, implementations MAY + * use the defaults as suggested in the HTTP specification. + * @return self + * @throws \InvalidArgumentException For invalid status code arguments. + */ + public function withStatus($code, $reasonPhrase = ''); + + /** + * Gets the response reason phrase associated with the status code. + * + * Because a reason phrase is not a required element in a response + * status line, the reason phrase value MAY be null. Implementations MAY + * choose to return the default RFC 7231 recommended reason phrase (or those + * listed in the IANA HTTP Status Code Registry) for the response's + * status code. + * + * @link http://tools.ietf.org/html/rfc7231#section-6 + * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml + * @return string Reason phrase; must return an empty string if none present. + */ + public function getReasonPhrase(); +} diff --git a/library/vendor/Psr/Http/Message/ServerRequestInterface.php b/library/vendor/Psr/Http/Message/ServerRequestInterface.php new file mode 100644 index 0000000..916e065 --- /dev/null +++ b/library/vendor/Psr/Http/Message/ServerRequestInterface.php @@ -0,0 +1,261 @@ +<?php + +namespace Psr\Http\Message; + +/** + * Representation of an incoming, server-side HTTP request. + * + * Per the HTTP specification, this interface includes properties for + * each of the following: + * + * - Protocol version + * - HTTP method + * - URI + * - Headers + * - Message body + * + * Additionally, it encapsulates all data as it has arrived to the + * application from the CGI and/or PHP environment, including: + * + * - The values represented in $_SERVER. + * - Any cookies provided (generally via $_COOKIE) + * - Query string arguments (generally via $_GET, or as parsed via parse_str()) + * - Upload files, if any (as represented by $_FILES) + * - Deserialized body parameters (generally from $_POST) + * + * $_SERVER values MUST be treated as immutable, as they represent application + * state at the time of request; as such, no methods are provided to allow + * modification of those values. The other values provide such methods, as they + * can be restored from $_SERVER or the request body, and may need treatment + * during the application (e.g., body parameters may be deserialized based on + * content type). + * + * Additionally, this interface recognizes the utility of introspecting a + * request to derive and match additional parameters (e.g., via URI path + * matching, decrypting cookie values, deserializing non-form-encoded body + * content, matching authorization headers to users, etc). These parameters + * are stored in an "attributes" property. + * + * Requests are considered immutable; all methods that might change state MUST + * be implemented such that they retain the internal state of the current + * message and return an instance that contains the changed state. + */ +interface ServerRequestInterface extends RequestInterface +{ + /** + * Retrieve server parameters. + * + * Retrieves data related to the incoming request environment, + * typically derived from PHP's $_SERVER superglobal. The data IS NOT + * REQUIRED to originate from $_SERVER. + * + * @return array + */ + public function getServerParams(); + + /** + * Retrieve cookies. + * + * Retrieves cookies sent by the client to the server. + * + * The data MUST be compatible with the structure of the $_COOKIE + * superglobal. + * + * @return array + */ + public function getCookieParams(); + + /** + * Return an instance with the specified cookies. + * + * The data IS NOT REQUIRED to come from the $_COOKIE superglobal, but MUST + * be compatible with the structure of $_COOKIE. Typically, this data will + * be injected at instantiation. + * + * This method MUST NOT update the related Cookie header of the request + * instance, nor related values in the server params. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated cookie values. + * + * @param array $cookies Array of key/value pairs representing cookies. + * @return self + */ + public function withCookieParams(array $cookies); + + /** + * Retrieve query string arguments. + * + * Retrieves the deserialized query string arguments, if any. + * + * Note: the query params might not be in sync with the URI or server + * params. If you need to ensure you are only getting the original + * values, you may need to parse the query string from `getUri()->getQuery()` + * or from the `QUERY_STRING` server param. + * + * @return array + */ + public function getQueryParams(); + + /** + * Return an instance with the specified query string arguments. + * + * These values SHOULD remain immutable over the course of the incoming + * request. They MAY be injected during instantiation, such as from PHP's + * $_GET superglobal, or MAY be derived from some other value such as the + * URI. In cases where the arguments are parsed from the URI, the data + * MUST be compatible with what PHP's parse_str() would return for + * purposes of how duplicate query parameters are handled, and how nested + * sets are handled. + * + * Setting query string arguments MUST NOT change the URI stored by the + * request, nor the values in the server params. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated query string arguments. + * + * @param array $query Array of query string arguments, typically from + * $_GET. + * @return self + */ + public function withQueryParams(array $query); + + /** + * Retrieve normalized file upload data. + * + * This method returns upload metadata in a normalized tree, with each leaf + * an instance of Psr\Http\Message\UploadedFileInterface. + * + * These values MAY be prepared from $_FILES or the message body during + * instantiation, or MAY be injected via withUploadedFiles(). + * + * @return array An array tree of UploadedFileInterface instances; an empty + * array MUST be returned if no data is present. + */ + public function getUploadedFiles(); + + /** + * Create a new instance with the specified uploaded files. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param array An array tree of UploadedFileInterface instances. + * @return self + * @throws \InvalidArgumentException if an invalid structure is provided. + */ + public function withUploadedFiles(array $uploadedFiles); + + /** + * Retrieve any parameters provided in the request body. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, this method MUST + * return the contents of $_POST. + * + * Otherwise, this method may return any results of deserializing + * the request body content; as parsing returns structured content, the + * potential types MUST be arrays or objects only. A null value indicates + * the absence of body content. + * + * @return null|array|object The deserialized body parameters, if any. + * These will typically be an array or object. + */ + public function getParsedBody(); + + /** + * Return an instance with the specified body parameters. + * + * These MAY be injected during instantiation. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, use this method + * ONLY to inject the contents of $_POST. + * + * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of + * deserializing the request body content. Deserialization/parsing returns + * structured data, and, as such, this method ONLY accepts arrays or objects, + * or a null value if nothing was available to parse. + * + * As an example, if content negotiation determines that the request data + * is a JSON payload, this method could be used to create a request + * instance with the deserialized parameters. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param null|array|object $data The deserialized body data. This will + * typically be in an array or object. + * @return self + * @throws \InvalidArgumentException if an unsupported argument type is + * provided. + */ + public function withParsedBody($data); + + /** + * Retrieve attributes derived from the request. + * + * The request "attributes" may be used to allow injection of any + * parameters derived from the request: e.g., the results of path + * match operations; the results of decrypting cookies; the results of + * deserializing non-form-encoded message bodies; etc. Attributes + * will be application and request specific, and CAN be mutable. + * + * @return array Attributes derived from the request. + */ + public function getAttributes(); + + /** + * Retrieve a single derived request attribute. + * + * Retrieves a single derived request attribute as described in + * getAttributes(). If the attribute has not been previously set, returns + * the default value as provided. + * + * This method obviates the need for a hasAttribute() method, as it allows + * specifying a default value to return if the attribute is not found. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $default Default value to return if the attribute does not exist. + * @return mixed + */ + public function getAttribute($name, $default = null); + + /** + * Return an instance with the specified derived request attribute. + * + * This method allows setting a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $value The value of the attribute. + * @return self + */ + public function withAttribute($name, $value); + + /** + * Return an instance that removes the specified derived request attribute. + * + * This method allows removing a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @return self + */ + public function withoutAttribute($name); +} diff --git a/library/vendor/Psr/Http/Message/StreamInterface.php b/library/vendor/Psr/Http/Message/StreamInterface.php new file mode 100644 index 0000000..f68f391 --- /dev/null +++ b/library/vendor/Psr/Http/Message/StreamInterface.php @@ -0,0 +1,158 @@ +<?php + +namespace Psr\Http\Message; + +/** + * Describes a data stream. + * + * Typically, an instance will wrap a PHP stream; this interface provides + * a wrapper around the most common operations, including serialization of + * the entire stream to a string. + */ +interface StreamInterface +{ + /** + * Reads all data from the stream into a string, from the beginning to end. + * + * This method MUST attempt to seek to the beginning of the stream before + * reading data and read the stream until the end is reached. + * + * Warning: This could attempt to load a large amount of data into memory. + * + * This method MUST NOT raise an exception in order to conform with PHP's + * string casting operations. + * + * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring + * @return string + */ + public function __toString(); + + /** + * Closes the stream and any underlying resources. + * + * @return void + */ + public function close(); + + /** + * Separates any underlying resources from the stream. + * + * After the stream has been detached, the stream is in an unusable state. + * + * @return resource|null Underlying PHP stream, if any + */ + public function detach(); + + /** + * Get the size of the stream if known. + * + * @return int|null Returns the size in bytes if known, or null if unknown. + */ + public function getSize(); + + /** + * Returns the current position of the file read/write pointer + * + * @return int Position of the file pointer + * @throws \RuntimeException on error. + */ + public function tell(); + + /** + * Returns true if the stream is at the end of the stream. + * + * @return bool + */ + public function eof(); + + /** + * Returns whether or not the stream is seekable. + * + * @return bool + */ + public function isSeekable(); + + /** + * Seek to a position in the stream. + * + * @link http://www.php.net/manual/en/function.fseek.php + * @param int $offset Stream offset + * @param int $whence Specifies how the cursor position will be calculated + * based on the seek offset. Valid values are identical to the built-in + * PHP $whence values for `fseek()`. SEEK_SET: Set position equal to + * offset bytes SEEK_CUR: Set position to current location plus offset + * SEEK_END: Set position to end-of-stream plus offset. + * @throws \RuntimeException on failure. + */ + public function seek($offset, $whence = SEEK_SET); + + /** + * Seek to the beginning of the stream. + * + * If the stream is not seekable, this method will raise an exception; + * otherwise, it will perform a seek(0). + * + * @see seek() + * @link http://www.php.net/manual/en/function.fseek.php + * @throws \RuntimeException on failure. + */ + public function rewind(); + + /** + * Returns whether or not the stream is writable. + * + * @return bool + */ + public function isWritable(); + + /** + * Write data to the stream. + * + * @param string $string The string that is to be written. + * @return int Returns the number of bytes written to the stream. + * @throws \RuntimeException on failure. + */ + public function write($string); + + /** + * Returns whether or not the stream is readable. + * + * @return bool + */ + public function isReadable(); + + /** + * Read data from the stream. + * + * @param int $length Read up to $length bytes from the object and return + * them. Fewer than $length bytes may be returned if underlying stream + * call returns fewer bytes. + * @return string Returns the data read from the stream, or an empty string + * if no bytes are available. + * @throws \RuntimeException if an error occurs. + */ + public function read($length); + + /** + * Returns the remaining contents in a string + * + * @return string + * @throws \RuntimeException if unable to read or an error occurs while + * reading. + */ + public function getContents(); + + /** + * Get stream metadata as an associative array or retrieve a specific key. + * + * The keys returned are identical to the keys returned from PHP's + * stream_get_meta_data() function. + * + * @link http://php.net/manual/en/function.stream-get-meta-data.php + * @param string $key Specific metadata to retrieve. + * @return array|mixed|null Returns an associative array if no key is + * provided. Returns a specific key value if a key is provided and the + * value is found, or null if the key is not found. + */ + public function getMetadata($key = null); +} diff --git a/library/vendor/Psr/Http/Message/UploadedFileInterface.php b/library/vendor/Psr/Http/Message/UploadedFileInterface.php new file mode 100644 index 0000000..5ad288d --- /dev/null +++ b/library/vendor/Psr/Http/Message/UploadedFileInterface.php @@ -0,0 +1,123 @@ +<?php + +namespace Psr\Http\Message; + +/** + * Value object representing a file uploaded through an HTTP request. + * + * Instances of this interface are considered immutable; all methods that + * might change state MUST be implemented such that they retain the internal + * state of the current instance and return an instance that contains the + * changed state. + */ +interface UploadedFileInterface +{ + /** + * Retrieve a stream representing the uploaded file. + * + * This method MUST return a StreamInterface instance, representing the + * uploaded file. The purpose of this method is to allow utilizing native PHP + * stream functionality to manipulate the file upload, such as + * stream_copy_to_stream() (though the result will need to be decorated in a + * native PHP stream wrapper to work with such functions). + * + * If the moveTo() method has been called previously, this method MUST raise + * an exception. + * + * @return StreamInterface Stream representation of the uploaded file. + * @throws \RuntimeException in cases when no stream is available or can be + * created. + */ + public function getStream(); + + /** + * Move the uploaded file to a new location. + * + * Use this method as an alternative to move_uploaded_file(). This method is + * guaranteed to work in both SAPI and non-SAPI environments. + * Implementations must determine which environment they are in, and use the + * appropriate method (move_uploaded_file(), rename(), or a stream + * operation) to perform the operation. + * + * $targetPath may be an absolute path, or a relative path. If it is a + * relative path, resolution should be the same as used by PHP's rename() + * function. + * + * The original file or stream MUST be removed on completion. + * + * If this method is called more than once, any subsequent calls MUST raise + * an exception. + * + * When used in an SAPI environment where $_FILES is populated, when writing + * files via moveTo(), is_uploaded_file() and move_uploaded_file() SHOULD be + * used to ensure permissions and upload status are verified correctly. + * + * If you wish to move to a stream, use getStream(), as SAPI operations + * cannot guarantee writing to stream destinations. + * + * @see http://php.net/is_uploaded_file + * @see http://php.net/move_uploaded_file + * @param string $targetPath Path to which to move the uploaded file. + * @throws \InvalidArgumentException if the $path specified is invalid. + * @throws \RuntimeException on any error during the move operation, or on + * the second or subsequent call to the method. + */ + public function moveTo($targetPath); + + /** + * Retrieve the file size. + * + * Implementations SHOULD return the value stored in the "size" key of + * the file in the $_FILES array if available, as PHP calculates this based + * on the actual size transmitted. + * + * @return int|null The file size in bytes or null if unknown. + */ + public function getSize(); + + /** + * Retrieve the error associated with the uploaded file. + * + * The return value MUST be one of PHP's UPLOAD_ERR_XXX constants. + * + * If the file was uploaded successfully, this method MUST return + * UPLOAD_ERR_OK. + * + * Implementations SHOULD return the value stored in the "error" key of + * the file in the $_FILES array. + * + * @see http://php.net/manual/en/features.file-upload.errors.php + * @return int One of PHP's UPLOAD_ERR_XXX constants. + */ + public function getError(); + + /** + * Retrieve the filename sent by the client. + * + * Do not trust the value returned by this method. A client could send + * a malicious filename with the intention to corrupt or hack your + * application. + * + * Implementations SHOULD return the value stored in the "name" key of + * the file in the $_FILES array. + * + * @return string|null The filename sent by the client or null if none + * was provided. + */ + public function getClientFilename(); + + /** + * Retrieve the media type sent by the client. + * + * Do not trust the value returned by this method. A client could send + * a malicious media type with the intention to corrupt or hack your + * application. + * + * Implementations SHOULD return the value stored in the "type" key of + * the file in the $_FILES array. + * + * @return string|null The media type sent by the client or null if none + * was provided. + */ + public function getClientMediaType(); +} diff --git a/library/vendor/Psr/Http/Message/UriInterface.php b/library/vendor/Psr/Http/Message/UriInterface.php new file mode 100644 index 0000000..c51a64b --- /dev/null +++ b/library/vendor/Psr/Http/Message/UriInterface.php @@ -0,0 +1,324 @@ +<?php + +namespace Psr\Http\Message; + +/** + * Value object representing a URI. + * + * This interface is meant to represent URIs according to RFC 3986 and to + * provide methods for most common operations. Additional functionality for + * working with URIs can be provided on top of the interface or externally. + * Its primary use is for HTTP requests, but may also be used in other + * contexts. + * + * Instances of this interface are considered immutable; all methods that + * might change state MUST be implemented such that they retain the internal + * state of the current instance and return an instance that contains the + * changed state. + * + * Typically the Host header will be also be present in the request message. + * For server-side requests, the scheme will typically be discoverable in the + * server parameters. + * + * @link http://tools.ietf.org/html/rfc3986 (the URI specification) + */ +interface UriInterface +{ + /** + * Retrieve the scheme component of the URI. + * + * If no scheme is present, this method MUST return an empty string. + * + * The value returned MUST be normalized to lowercase, per RFC 3986 + * Section 3.1. + * + * The trailing ":" character is not part of the scheme and MUST NOT be + * added. + * + * @see https://tools.ietf.org/html/rfc3986#section-3.1 + * @return string The URI scheme. + */ + public function getScheme(); + + /** + * Retrieve the authority component of the URI. + * + * If no authority information is present, this method MUST return an empty + * string. + * + * The authority syntax of the URI is: + * + * <pre> + * [user-info@]host[:port] + * </pre> + * + * If the port component is not set or is the standard port for the current + * scheme, it SHOULD NOT be included. + * + * @see https://tools.ietf.org/html/rfc3986#section-3.2 + * @return string The URI authority, in "[user-info@]host[:port]" format. + */ + public function getAuthority(); + + /** + * Retrieve the user information component of the URI. + * + * If no user information is present, this method MUST return an empty + * string. + * + * If a user is present in the URI, this will return that value; + * additionally, if the password is also present, it will be appended to the + * user value, with a colon (":") separating the values. + * + * The trailing "@" character is not part of the user information and MUST + * NOT be added. + * + * @return string The URI user information, in "username[:password]" format. + */ + public function getUserInfo(); + + /** + * Retrieve the host component of the URI. + * + * If no host is present, this method MUST return an empty string. + * + * The value returned MUST be normalized to lowercase, per RFC 3986 + * Section 3.2.2. + * + * @see http://tools.ietf.org/html/rfc3986#section-3.2.2 + * @return string The URI host. + */ + public function getHost(); + + /** + * Retrieve the port component of the URI. + * + * If a port is present, and it is non-standard for the current scheme, + * this method MUST return it as an integer. If the port is the standard port + * used with the current scheme, this method SHOULD return null. + * + * If no port is present, and no scheme is present, this method MUST return + * a null value. + * + * If no port is present, but a scheme is present, this method MAY return + * the standard port for that scheme, but SHOULD return null. + * + * @return null|int The URI port. + */ + public function getPort(); + + /** + * Retrieve the path component of the URI. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * Normally, the empty path "" and absolute path "/" are considered equal as + * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically + * do this normalization because in contexts with a trimmed base path, e.g. + * the front controller, this difference becomes significant. It's the task + * of the user to handle both "" and "/". + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.3. + * + * As an example, if the value should include a slash ("/") not intended as + * delimiter between path segments, that value MUST be passed in encoded + * form (e.g., "%2F") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.3 + * @return string The URI path. + */ + public function getPath(); + + /** + * Retrieve the query string of the URI. + * + * If no query string is present, this method MUST return an empty string. + * + * The leading "?" character is not part of the query and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.4. + * + * As an example, if a value in a key/value pair of the query string should + * include an ampersand ("&") not intended as a delimiter between values, + * that value MUST be passed in encoded form (e.g., "%26") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.4 + * @return string The URI query string. + */ + public function getQuery(); + + /** + * Retrieve the fragment component of the URI. + * + * If no fragment is present, this method MUST return an empty string. + * + * The leading "#" character is not part of the fragment and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.5. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.5 + * @return string The URI fragment. + */ + public function getFragment(); + + /** + * Return an instance with the specified scheme. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified scheme. + * + * Implementations MUST support the schemes "http" and "https" case + * insensitively, and MAY accommodate other schemes if required. + * + * An empty scheme is equivalent to removing the scheme. + * + * @param string $scheme The scheme to use with the new instance. + * @return self A new instance with the specified scheme. + * @throws \InvalidArgumentException for invalid or unsupported schemes. + */ + public function withScheme($scheme); + + /** + * Return an instance with the specified user information. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified user information. + * + * Password is optional, but the user information MUST include the + * user; an empty string for the user is equivalent to removing user + * information. + * + * @param string $user The user name to use for authority. + * @param null|string $password The password associated with $user. + * @return self A new instance with the specified user information. + */ + public function withUserInfo($user, $password = null); + + /** + * Return an instance with the specified host. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified host. + * + * An empty host value is equivalent to removing the host. + * + * @param string $host The hostname to use with the new instance. + * @return self A new instance with the specified host. + * @throws \InvalidArgumentException for invalid hostnames. + */ + public function withHost($host); + + /** + * Return an instance with the specified port. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified port. + * + * Implementations MUST raise an exception for ports outside the + * established TCP and UDP port ranges. + * + * A null value provided for the port is equivalent to removing the port + * information. + * + * @param null|int $port The port to use with the new instance; a null value + * removes the port information. + * @return self A new instance with the specified port. + * @throws \InvalidArgumentException for invalid ports. + */ + public function withPort($port); + + /** + * Return an instance with the specified path. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified path. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * If the path is intended to be domain-relative rather than path relative then + * it must begin with a slash ("/"). Paths not starting with a slash ("/") + * are assumed to be relative to some base path known to the application or + * consumer. + * + * Users can provide both encoded and decoded path characters. + * Implementations ensure the correct encoding as outlined in getPath(). + * + * @param string $path The path to use with the new instance. + * @return self A new instance with the specified path. + * @throws \InvalidArgumentException for invalid paths. + */ + public function withPath($path); + + /** + * Return an instance with the specified query string. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified query string. + * + * Users can provide both encoded and decoded query characters. + * Implementations ensure the correct encoding as outlined in getQuery(). + * + * An empty query string value is equivalent to removing the query string. + * + * @param string $query The query string to use with the new instance. + * @return self A new instance with the specified query string. + * @throws \InvalidArgumentException for invalid query strings. + */ + public function withQuery($query); + + /** + * Return an instance with the specified URI fragment. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified URI fragment. + * + * Users can provide both encoded and decoded fragment characters. + * Implementations ensure the correct encoding as outlined in getFragment(). + * + * An empty fragment value is equivalent to removing the fragment. + * + * @param string $fragment The fragment to use with the new instance. + * @return self A new instance with the specified fragment. + */ + public function withFragment($fragment); + + /** + * Return the string representation as a URI reference. + * + * Depending on which components of the URI are present, the resulting + * string is either a full URI or relative reference according to RFC 3986, + * Section 4.1. The method concatenates the various components of the URI, + * using the appropriate delimiters: + * + * - If a scheme is present, it MUST be suffixed by ":". + * - If an authority is present, it MUST be prefixed by "//". + * - The path can be concatenated without delimiters. But there are two + * cases where the path has to be adjusted to make the URI reference + * valid as PHP does not allow to throw an exception in __toString(): + * - If the path is rootless and an authority is present, the path MUST + * be prefixed by "/". + * - If the path is starting with more than one "/" and no authority is + * present, the starting slashes MUST be reduced to one. + * - If a query is present, it MUST be prefixed by "?". + * - If a fragment is present, it MUST be prefixed by "#". + * + * @see http://tools.ietf.org/html/rfc3986#section-4.1 + * @return string + */ + public function __toString(); +} diff --git a/library/vendor/Psr/LICENSE b/library/vendor/Psr/LICENSE new file mode 100644 index 0000000..c2d8e45 --- /dev/null +++ b/library/vendor/Psr/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 PHP Framework Interoperability Group + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/library/vendor/Psr/Loader.php b/library/vendor/Psr/Loader.php new file mode 100644 index 0000000..45c78af --- /dev/null +++ b/library/vendor/Psr/Loader.php @@ -0,0 +1,21 @@ +<?php +/* Icinga Web 2 Elasticsearch Module (c) 2017 Icinga Development Team | GPLv2+ */ + +spl_autoload_register(function ($class) { + $prefix = 'Psr\\'; + $len = strlen($prefix); + + $baseDir = __DIR__ . '/'; + + if (strncmp($prefix, $class, $len) !== 0) { + return; + } + + $relative = substr($class, $len); + + $file = $baseDir . str_replace('\\', '/', $relative) . '.php'; + + if (file_exists($file)) { + require $file; + } +}); diff --git a/library/vendor/iplx/Http/Client.php b/library/vendor/iplx/Http/Client.php new file mode 100644 index 0000000..39d8905 --- /dev/null +++ b/library/vendor/iplx/Http/Client.php @@ -0,0 +1,199 @@ +<?php + +namespace iplx\Http; + +use RuntimeException; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; + +/** + * HTTP client that uses cURL + */ +class Client implements ClientInterface +{ + /** + * Client version + * + * @var string + */ + const VERSION = '1.0.0'; + + /** + * Maximum number of internal cURL handles + * + * @var int + */ + const MAX_HANDLES = 4; + + /** + * Internal cURL handles + * + * @var array + */ + protected $handles = []; + + /** + * Return user agent + * + * @return string + */ + protected function getAgent() + { + $defaultAgent = 'ipl/' . self::VERSION; + $defaultAgent .= ' curl/' . curl_version()['version']; + $defaultAgent .= ' PHP/' . PHP_VERSION; + + return $defaultAgent; + } + + /** + * Create and return a cURL handle based on the given request + * + * @param RequestInterface $request + * @param array $options + * + * @return Handle + * + * @throws RuntimeException + */ + protected function createHandle(RequestInterface $request, array $options) + { + $headers = []; + foreach ($request->getHeaders() as $name => $values) { + $headers[] = $name . ': ' . implode(', ', $values); + } + + $curlOptions = [ + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_FAILONERROR => true, + CURLOPT_USERAGENT => $this->getAgent() + ]; + + if (isset($options['curl'])) { + $curlOptions += $options['curl']; + } + + $curlOptions += [ + CURLOPT_CUSTOMREQUEST => $request->getMethod(), + CURLOPT_HTTPHEADER => $headers, + CURLOPT_RETURNTRANSFER => false, + CURLOPT_URL => (string) $request->getUri()->withFragment('') + ]; + + if (! $request->hasHeader('Accept')) { + $curlOptions[CURLOPT_HTTPHEADER][] = 'Accept:'; + } + + if (! $request->hasHeader('Content-Type')) { + $curlOptions[CURLOPT_HTTPHEADER][] = 'Content-Type:'; + } + + if (! $request->hasHeader('Expect')) { + $curlOptions[CURLOPT_HTTPHEADER][] = 'Expect:'; + } + + if ($request->getBody()->getSize() !== 0) { + $curlOptions[CURLOPT_UPLOAD] = true; + + $body = $request->getBody(); + if ($body->isSeekable()) { + $body->seek(0); + } + + $curlOptions[CURLOPT_READFUNCTION] = function ($ch, $infile, $length) use ($body) { + return $body->read($length); + }; + } + + if ($request->getProtocolVersion()) { + $protocolVersion = null; + switch ($request->getProtocolVersion()) { + case '2.0': + if (version_compare(phpversion(), '7.0.7', '<')) { + throw new RuntimeException('You need at least PHP 7.0.7 to use HTTP 2.0'); + } + $protocolVersion = CURL_HTTP_VERSION_2; + break; + case '1.1': + $protocolVersion = CURL_HTTP_VERSION_1_1; + break; + default: + $protocolVersion = CURL_HTTP_VERSION_1_0; + } + + $curlOptions[CURLOPT_HTTP_VERSION] = $protocolVersion; + } + + $handle = new Handle(); + + $curlOptions[CURLOPT_HEADERFUNCTION] = function($ch, $header) use ($handle) { + $size = strlen($header); + + if (! trim($header) || strpos($header, 'HTTP/') === 0) { + return $size; + } + + list($key, $value) = explode(': ', $header, 2); + $handle->responseHeaders[$key] = rtrim($value, "\r\n"); + + return $size; + }; + + $handle->responseBody = Stream::open(); + + $curlOptions[CURLOPT_WRITEFUNCTION] = function ($ch, $string) use ($handle) { + return $handle->responseBody->write($string); + }; + + $ch = ! empty($this->handles) ? array_pop($this->handles) : curl_init(); + + curl_setopt_array($ch, $curlOptions); + + $handle->handle = $ch; + + return $handle; + } + + /** + * Execute a cURL handle and return the response + * + * @param Handle $handle + * + * @return ResponseInterface + * + * @throws RuntimeException + */ + protected function executeHandle(Handle $handle) + { + $ch = $handle->handle; + + $success = curl_exec($ch); + + if ($success === false) { + throw new RuntimeException(curl_error($ch)); + } + + $response = new Response( + curl_getinfo($ch, CURLINFO_HTTP_CODE), $handle->responseHeaders, $handle->responseBody + ); + + if (count($this->handles) >= self::MAX_HANDLES) { + curl_close($ch); + } else { + curl_reset($ch); + + $this->handles[] = $ch; + } + + return $response; + } + + public function send(RequestInterface $request, array $options = []) + { + $handle = $this->createHandle($request, $options); + + $response = $this->executeHandle($handle); + + return $response; + } +} diff --git a/library/vendor/iplx/Http/ClientInterface.php b/library/vendor/iplx/Http/ClientInterface.php new file mode 100644 index 0000000..e7765a7 --- /dev/null +++ b/library/vendor/iplx/Http/ClientInterface.php @@ -0,0 +1,22 @@ +<?php + +namespace iplx\Http; + +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; + +/** + * Interface for HTTP clients which send HTTP requests + */ +interface ClientInterface +{ + /** + * Send a HTTP request + * + * @param RequestInterface $request Request to send + * @param array $options Request options + * + * @return ResponseInterface The response + */ + public function send(RequestInterface $request, array $options = []); +} diff --git a/library/vendor/iplx/Http/Handle.php b/library/vendor/iplx/Http/Handle.php new file mode 100644 index 0000000..490b5c5 --- /dev/null +++ b/library/vendor/iplx/Http/Handle.php @@ -0,0 +1,32 @@ +<?php + +namespace iplx\Http; + +use Psr\Http\Message\StreamInterface; + +/** + * Internal cURL handle representation + */ +class Handle +{ + /** + * cURL handle + * + * @var resource + */ + public $handle; + + /** + * Response body + * + * @var StreamInterface + */ + public $responseBody; + + /** + * Received response headers + * + * @var array + */ + public $responseHeaders = []; +} diff --git a/library/vendor/iplx/Http/MessageTrait.php b/library/vendor/iplx/Http/MessageTrait.php new file mode 100644 index 0000000..c8dc9b3 --- /dev/null +++ b/library/vendor/iplx/Http/MessageTrait.php @@ -0,0 +1,174 @@ +<?php + +namespace iplx\Http; + +use Psr\Http\Message\StreamInterface; + +trait MessageTrait +{ + /** + * Case sensitive header names with lowercase header names as keys + * + * @var array + */ + protected $headerNames = []; + + /** + * Header values with lowercase header names as keys + * + * @var array + */ + protected $headerValues = []; + + /** + * The body of this request + * + * @var StreamInterface + */ + protected $body; + + /** + * Protocol version + * + * @var string + */ + protected $protocolVersion; + + public function getProtocolVersion() + { + return $this->protocolVersion; + } + + public function withProtocolVersion($version) + { + $message = clone $this; + $message->protocolVersion = $version; + + return $message; + } + + public function getHeaders() + { + return array_combine($this->headerNames, $this->headerValues); + } + + public function hasHeader($header) + { + return isset($this->headerValues[strtolower($header)]); + } + + public function getHeader($header) + { + $header = strtolower($header); + + if (! isset($this->headerValues[$header])) { + return []; + } + + return $this->headerValues[$header]; + } + + public function getHeaderLine($name) + { + $name = strtolower($name); + + if (! isset($this->headerValues[$name])) { + return ''; + } + + return implode(', ', $this->headerValues[$name]); + } + + public function withHeader($name, $value) + { + $name = rtrim($name); + + $value = $this->normalizeHeaderValues($value); + + $normalized = strtolower($name); + + $message = clone $this; + $message->headerNames[$normalized] = $name; + $message->headerValues[$normalized] = $value; + + return $message; + } + + public function withAddedHeader($name, $value) + { + $name = rtrim($name); + + $value = $this->normalizeHeaderValues($value); + + $normalized = strtolower($name); + + $message = clone $this; + if (isset($message->headerNames[$normalized])) { + $message->headerValues[$normalized] = array_merge($message->headerValues[$normalized], $value); + } else { + $message->headerNames[$normalized] = $name; + $message->headerValues[$normalized] = $value; + } + + return $message; + } + + public function withoutHeader($name) + { + $normalized = strtolower(rtrim($name)); + + $message = clone $this; + unset($message->headerNames[$normalized]); + unset($message->headerValues[$normalized]); + + return $message; + } + + public function getBody() + { + return $this->body; + } + + public function withBody(StreamInterface $body) + { + $message = clone $this; + $message->body = $body; + + return $message; + } + + protected function setHeaders(array $headers) + { + // Prepare header field names and header field values according to + // https://tools.ietf.org/html/rfc7230#section-3.2.4 + $names = array_map('rtrim', array_keys($headers)); + $values = $this->normalizeHeaderValues($headers); + + $normalized = array_map('strtolower', $names); + + $this->headerNames = array_combine( + $normalized, + $names + ); + + $this->headerValues = array_combine( + $normalized, + $values + ); + } + + protected function normalizeHeaderValues(array $values) + { + // Prepare header field names and header field values according to + // https://tools.ietf.org/html/rfc7230#section-3.2.4 + return array_map(function ($value) { + if (! is_array($value)) { + $value = [$value]; + } + + return array_map(function ($value) { + return trim($value, " \t"); + }, $value); + }, $values); + } +} diff --git a/library/vendor/iplx/Http/Request.php b/library/vendor/iplx/Http/Request.php new file mode 100644 index 0000000..b9fae7d --- /dev/null +++ b/library/vendor/iplx/Http/Request.php @@ -0,0 +1,143 @@ +<?php + +namespace iplx\Http; + +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\UriInterface; + +/** + * A HTTP request + */ +class Request implements RequestInterface +{ + use MessageTrait; + + /** + * HTTP method of the request + * + * @var string + */ + protected $method; + + /** + * The request target + * + * @var string|null + */ + protected $requestTarget; + + /** + * URI of the request + * + * @var UriInterface + */ + protected $uri; + + /** + * Create a new HTTP request + * + * @param string $method HTTP method + * @param string $uri URI + * @param array $headers Request headers + * @param string $body Request body + * @param string $protocolVersion Protocol version + */ + public function __construct($method, $uri, array $headers = [], $body = null, $protocolVersion = '1.1') + { + $this->method = $method; + $this->uri = new Uri($uri); + $this->setHeaders($headers); + $this->body = Stream::create($body); + $this->protocolVersion = $protocolVersion; + + $this->provideHostHeader(); + } + + public function getRequestTarget() + { + if ($this->requestTarget !== null) { + return $this->requestTarget; + } + + $requestTarget = $this->uri->getPath(); + + // Weak type checks to also check null + + if ($requestTarget == '') { + $requestTarget = '/'; + } + + if ($this->uri->getQuery() != '') { + $requestTarget .= '?' . $this->uri->getQuery(); + } + + return $requestTarget; + } + + public function withRequestTarget($requestTarget) + { + $request = clone $this; + $request->requestTarget = $requestTarget; + + return $request; + } + + public function getMethod() + { + return $this->method; + } + + public function withMethod($method) + { + $request = clone $this; + $request->method = $method; + + return $this; + } + + public function getUri() + { + return $this->uri; + } + + public function withUri(UriInterface $uri, $preserveHost = false) + { + $request = clone $this; + $request->uri = $uri; + + if (! $preserveHost) { + $this->provideHostHeader(true); + } + + return $this; + } + + protected function provideHostHeader($force = false) + { + if ($this->hasHeader('host')) { + if (! $force) { + return; + } + + $header = $this->headerNames['host']; + } else { + $header = 'Host'; + } + + $host = $this->uri->getHost(); + + // Weak type check to also check null + if ($host == '') { + $host = ''; + } else { + $port = $this->uri->getPort(); + + if ($port !== null) { + $host .= ":$port"; + } + } + + $this->headerNames['host'] = $header; + $this->headerValues['host'] = [$host]; + } +} diff --git a/library/vendor/iplx/Http/Response.php b/library/vendor/iplx/Http/Response.php new file mode 100644 index 0000000..25448b1 --- /dev/null +++ b/library/vendor/iplx/Http/Response.php @@ -0,0 +1,64 @@ +<?php + +namespace iplx\Http; + +use Psr\Http\Message\ResponseInterface; + +/** + * A HTTP response + */ +class Response implements ResponseInterface +{ + use MessageTrait; + + /** + * Status code of the response + * + * @var int + */ + protected $statusCode; + + /** + * Response status reason phrase + * + * @var string + */ + protected $reasonPhrase = ''; + + /** + * Create a new HTTP response + * + * @param int $statusCode Response status code + * @param array $headers Response headers + * @param string $body Response body + * @param string $protocolVersion Protocol version + * @param string $reasonPhrase Response status reason phrase + */ + public function __construct($statusCode = 200, array $headers = [], $body = null, $protocolVersion = '1.1', $reasonPhrase = '') + { + $this->statusCode = $statusCode; + $this->setHeaders($headers); + $this->body = Stream::create($body); + $this->protocolVersion = $protocolVersion; + $this->reasonPhrase = $reasonPhrase; + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function withStatus($code, $reasonPhrase = '') + { + $response = clone $this; + $response->statusCode = $code; + $response->reasonPhrase = $reasonPhrase; + + return $response; + } + + public function getReasonPhrase() + { + return $this->reasonPhrase; + } +} diff --git a/library/vendor/iplx/Http/Stream.php b/library/vendor/iplx/Http/Stream.php new file mode 100644 index 0000000..a113312 --- /dev/null +++ b/library/vendor/iplx/Http/Stream.php @@ -0,0 +1,283 @@ +<?php + +namespace iplx\Http; + +use Exception; +use InvalidArgumentException; +use RuntimeException; +use Psr\Http\Message\StreamInterface; + +class Stream implements StreamInterface +{ + protected $stream; + + protected $size; + + protected $seekable; + + protected $readable; + + protected $writable; + + public function __construct($stream) + { + if (! is_resource($stream)) { + throw new InvalidArgumentException('Invalid stream resource'); + } + + $this->stream = $stream; + + $meta = stream_get_meta_data($this->stream); + $this->seekable = $meta['seekable']; + $this->readable = preg_match('/[r+]/', $meta['mode']) === 1; + $this->writable = preg_match('/[waxc+]/', $meta['mode']) === 1; + } + + public function __destruct() + { + $this->close(); + } + + public function __toString() + { + try { + $this->seek(0); + $contents = stream_get_contents($this->stream); + } catch (Exception $e) { + $contents = ''; + } + + return $contents; + } + + public function close() + { + if (isset($this->stream)) { + if (is_resource($this->stream)) { + fclose($this->stream); + } + $this->detach(); + } + } + + public function detach() + { + if (! isset($this->stream)) { + return null; + } + + $stream = $this->stream; + + $this->stream = null; + $this->size = null; + $this->seekable = false; + $this->readable = false; + $this->writable = false; + + return $stream; + } + + public function getSize() + { + if ($this->size !== null) { + return $this->size; + } + + if (! isset($this->stream)) { + return null; + } + + $stats = fstat($this->stream); + $this->size = $stats['size']; + + return $this->size; + } + + public function tell() + { + $this->assertAttached(); + + $position = ftell($this->stream); + + if ($position === false) { + throw new RuntimeException('Unable to determine stream position'); + } + + return $position; + } + + public function eof() + { + $this->assertAttached(); + + return feof($this->stream); + } + + public function isSeekable() + { + return $this->seekable; + } + + public function seek($offset, $whence = SEEK_SET) + { + $this->assertSeekable(); + + if (fseek($this->stream, $offset, $whence) === -1) { + throw new RuntimeException('Unable to seek to stream position'); + } + } + + public function rewind() + { + $this->seek(0); + } + + public function isWritable() + { + return $this->writable; + } + + public function write($string) + { + $this->assertWritable(); + + $written = fwrite($this->stream, $string); + + if ($written === false) { + throw new RuntimeException('Unable to write to stream'); + } + + return $written; + } + + public function isReadable() + { + return $this->readable; + } + + public function read($length) + { + $this->assertReadable(); + + $data = fread($this->stream, $length); + + if ($data === false) { + throw new RuntimeException('Unable to read from stream'); + } + + return $data; + } + + public function getContents() + { + $this->assertReadable(); + + $contents = stream_get_contents($this->stream); + + if ($contents === false) { + throw new RuntimeException('Unable to read stream contents'); + } + + return $contents; + } + + public function getMetadata($key = null) + { + if (! isset($this->stream)) { + return $key === null ? [] : null; + } + + $meta = stream_get_meta_data($this->stream); + + if ($key === null) { + return $meta; + } + + if (isset($meta[$key])) { + return $meta[$key]; + } + + return null; + } + + public function assertAttached() + { + if (! isset($this->stream)) { + throw new RuntimeException('Stream is detached'); + } + } + + public function assertSeekable() + { + $this->assertAttached(); + + if (! $this->isSeekable()) { + throw new RuntimeException('Stream is not seekable'); + } + } + + public function assertReadable() + { + $this->assertAttached(); + + if (! $this->isReadable()) { + throw new RuntimeException('Stream is not readable'); + } + } + + public function assertWritable() + { + $this->assertAttached(); + + if (! $this->isWritable()) { + throw new RuntimeException('Stream is not writable'); + } + } + + /** + * Open a stream + * + * @param string $filename + * @param string $mode + * + * @return static + */ + public static function open($filename = 'php://temp', $mode = 'r+') + { + $stream = fopen($filename, $mode); + + return new static($stream); + } + + /** + * Create a stream + * + * @param StreamInterface|string|resource $resource + * + * @return StreamInterface + */ + public static function create($resource) + { + if ($resource instanceof StreamInterface) { + return $resource; + } + + if (is_scalar($resource)) { + $stream = fopen('php://temp', 'r+'); + + if ($resource !== '') { + fwrite($stream, $resource); + fseek($stream, 0); + } + + return new static($stream); + } + + if (is_resource($resource)) { + return new static($resource); + } + + return static::open(); + } + +} diff --git a/library/vendor/iplx/Http/Uri.php b/library/vendor/iplx/Http/Uri.php new file mode 100644 index 0000000..044fb17 --- /dev/null +++ b/library/vendor/iplx/Http/Uri.php @@ -0,0 +1,202 @@ +<?php + +namespace iplx\Http; + +use InvalidArgumentException; +use Psr\Http\Message\UriInterface; + +class Uri implements UriInterface +{ + protected $scheme; + + protected $host; + + protected $port; + + protected $user; + + protected $pass; + + protected $path; + + protected $query; + + protected $fragment; + + public function __construct($uri = null) + { + $parts = parse_url($uri); + + if ($parts === false) { + throw new InvalidArgumentException(); + } + + foreach ($parts as $component => $value) { + $this->$component = $value; + } + } + + public function getScheme() + { + return $this->scheme; + } + + public function getAuthority() + { + // Weak type check to also check null + if ($this->host == '') { + return ''; + } + + $authority = $this->host; + + $userInfo = $this->getUserInfo(); + $port = $this->getPort(); + + if ($userInfo) { + $authority = "$userInfo@$authority"; + } + + if ($port !== null) { + $authority .= ":$port"; + } + + return $authority; + } + + public function getUserInfo() + { + $userInfo = $this->user; + + if ($this->pass !== null) { + $userInfo .= ":{$this->pass}"; + } + + return $userInfo; + } + + public function getHost() + { + return $this->host; + } + + public function getPort() + { + return $this->port; + } + + public function getPath() + { + return $this->path; + } + + public function getQuery() + { + return $this->query; + } + + public function getFragment() + { + return $this->fragment; + } + + public function withScheme($scheme) + { + $uri = clone $this; + $uri->scheme = $scheme; + + return $uri; + } + + public function withUserInfo($user, $password = null) + { + $uri = clone $this; + $uri->user = $user; + $uri->pass = $password; + + return $uri; + } + + public function withHost($host) + { + $uri = clone $this; + $uri->host = $host; + + return $uri; + } + + public function withPort($port) + { + $uri = clone $this; + $uri->port = $port; + + return $uri; + } + + public function withPath($path) + { + $uri = clone $this; + $uri->path = $path; + + return $uri; + } + + public function withQuery($query) + { + $uri = clone $this; + $uri->query = $query; + + return $uri; + } + + public function withFragment($fragment) + { + $uri = clone $this; + $uri->fragment = $fragment; + + return $uri; + } + + public function __toString() + { + $scheme = $this->getScheme(); + $authority = $this->getAuthority(); + $path = $this->getPath(); + $query = $this->getQuery(); + $fragment = $this->getFragment(); + + $uri = ''; + + // Weak type checks to also check null + + if ($scheme != '') { + $uri = "$scheme:"; + } + + if ($authority != '') { + $uri .= "//$authority"; + } + + if ($path != '') { + if ($path[0] === '/') { + if ($authority == '') { + $path = ltrim($path, '/'); + } + } else { + $path = "/$path"; + } + + $uri .= $path; + } + + if ($query != '') { + $uri .= "?$query"; + } + + if ($fragment != '') { + $uri .= "#$fragment"; + } + + return $uri; + } +} diff --git a/library/vendor/iplx/LICENSE b/library/vendor/iplx/LICENSE new file mode 100644 index 0000000..ecbc059 --- /dev/null +++ b/library/vendor/iplx/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License.
\ No newline at end of file diff --git a/library/vendor/iplx/Loader.php b/library/vendor/iplx/Loader.php new file mode 100644 index 0000000..5a6c9c0 --- /dev/null +++ b/library/vendor/iplx/Loader.php @@ -0,0 +1,20 @@ +<?php + +spl_autoload_register(function ($class) { + $prefix = 'iplx\\'; + $len = strlen($prefix); + + $baseDir = __DIR__ . '/'; + + if (strncmp($prefix, $class, $len) !== 0) { + return; + } + + $relative = substr($class, $len); + + $file = $baseDir . str_replace('\\', '/', $relative) . '.php'; + + if (file_exists($file)) { + require $file; + } +}); diff --git a/library/vendor/iplx/README b/library/vendor/iplx/README new file mode 100644 index 0000000..aa0b11e --- /dev/null +++ b/library/vendor/iplx/README @@ -0,0 +1 @@ +Experimental version of the not released Icinga PHP Library (ipl). Do not use. diff --git a/module.info b/module.info new file mode 100644 index 0000000..110c851 --- /dev/null +++ b/module.info @@ -0,0 +1,8 @@ +Module: graphite +Version: 1.2.2 +Requires: + Libraries: icinga-php-library (>=0.9.0), icinga-php-thirdparty (>=0.10.0) + Modules: monitoring (>=2.9.0), icingadb (>=1.0.0) +Description: Icinga Web Graphite Integration + This module integrates an existing Graphite installation in your Icinga Web + frontend. diff --git a/public/css/module.less b/public/css/module.less new file mode 100644 index 0000000..bb651e7 --- /dev/null +++ b/public/css/module.less @@ -0,0 +1,213 @@ +div.images { + h3 { + clear: both; + } + + img.svg { + float: left; + border: none; + } + +} + +div.images.object-detail-view { + display: block; + + img.graphiteImg { + width: 100%; + display: block; + } +} + +.timerangepicker-container { + display: flex; + padding: .25em 0; + + .button-link { + line-height: 1.75; + width: 2.25em; + + i { + display: inline-block; + width: 100%; + text-align: center; + } + + i:before { + margin-right: 0; + } + } +} + +form[name=form_timerangepickercommon_graphite] { + display: inline-flex; + flex: 1 1 auto; + width: 0; + max-width: 43.25em; + + &:not(:last-child) { + margin-right: .25em; + } + + select { + min-width: 3.5em; + max-width: 7em; + flex: 1 1 auto; + width: auto; + + &:not(:last-of-type) { + margin-right: .25em; + } + } +} + +form[name=form_timerangepickercustom_graphite] { + min-width: 21em; + + .control-label-group { + text-align: left; + } + + input[type=date] { + min-width: 11em; + } + + input[type=time] { + min-width: 5em; + } +} + +.flyover[id^="graphite-customrange-"] { + display: inline-block; +} + +.grid { + margin-right: -1em; + margin-bottom: -1em; + + .empty-state { + .rounded-corners(); + background-color: @gray-lightest; + margin-right: 1em; + padding: 1em; + text-align: center; + } +} + +.grid-item { + display: inline-block; + border-radius: .25em; + border: 1px solid @gray-lighter; + min-width: 302px; + vertical-align: top; + margin-right: 1em; + margin-bottom: 1em; +} + +.grid-item h2 { + margin-top: 0; + background: @gray-lightest; + border-bottom: 1px solid @gray-lighter; + padding:.25em .5em; + max-width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.grid-item > .images:not(:last-child) { + margin-right: 1em; + display: inline-block; +} + +.grid-item .graphiteImg { + margin-right: 1px; +} + +.grid-item > p { + padding: .5em; + margin-bottom: 0; +} + +p.load-more { + margin-right: 1em; + text-align: right; +} + +// TODO: Remove once ipl-web v0.7.0 is required +.controls:not(.default-layout) { + .box-shadow(0, 0, 0, 1px, @gray-lighter); + + .pagination-control { + float: left; + } + + .sort-control, + .limit-control { + float: right; + margin-left: .5em; + } + + .sort-control { + display: flex; + justify-content: flex-end; + + :not(.form-element) > label { + margin-right: 0; + } + + .control-button { + margin: 0; + } + } + + > :not(:only-child) { + margin-bottom: 0.5em; + } + + .search-suggestions { + margin-bottom: 2.5em; + } + + .search-controls, + .filter { + clear: both; + display: flex; + min-width: 100%; + + .search-bar { + flex: 1 1 auto; + + & ~ .control-button:last-child { + margin-right: -.5em; + } + + & ~ .control-button { + margin-left: .5em; + } + } + } +} + +/* Graph colors */ + +@graphite-graph-bg-color: @body-bg-color; +@graphite-graph-fg-color: @text-color; +@graphite-graph-major-grid-color: @gray-light; +@graphite-graph-minor-grid-color: @graphite-graph-bg-color; + +@light-mode: { + --graphite-graph-bg-color: var(--body-bg-color); + --graphite-graph-fg-color: var(--text-color); + --graphite-graph-major-grid-color: var(--gray-light); + --graphite-graph-minor-grid-color: var(--graphite-graph-bg-color); +}; + +.graphite-graph-color-registry { + display: none; + + background-color: @graphite-graph-bg-color; + color: @graphite-graph-fg-color; + border-top-color: @graphite-graph-major-grid-color; + border-bottom-color: @graphite-graph-minor-grid-color; +} diff --git a/public/js/module.js b/public/js/module.js new file mode 100644 index 0000000..a2a32f2 --- /dev/null +++ b/public/js/module.js @@ -0,0 +1,112 @@ +/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */ + +(function(Icinga) { + + "use strict"; + + class Graphite extends Icinga.EventListener { + constructor(icinga) { + super(icinga); + + this._colorParams = null; + this._resizeTimer = null; + this._onResizeBound = this.onResize.bind(this); + this._onModeChangeBound = this.onModeChange.bind(this); + this._mediaQueryList = window.matchMedia('(prefers-color-scheme: light)'); + + this.on('css-reloaded', 'head', this.onCssReloaded, this); + this.on('rendered', '#main > .container', this.onRendered, this); + window.addEventListener('resize', this._onResizeBound, { passive: true }); + this._mediaQueryList.addEventListener('change', this._onModeChangeBound, { passive: true }); + } + + get colorParams() { + if (this._colorParams === null) { + let colorRegistry = document.querySelector('.graphite-graph-color-registry'); + let registryStyle = window.getComputedStyle(colorRegistry); + + this._colorParams = { + bgcolor: this.rgbToHex(registryStyle.backgroundColor, 'black'), + fgcolor: this.rgbToHex(registryStyle.color, 'white'), + majorGridLineColor: this.rgbToHex(registryStyle.borderTopColor, '0000003F'), + minorGridLineColor: this.rgbToHex(registryStyle.borderBottomColor, 'black') + }; + } + + return this._colorParams; + } + + unbind(emitter) { + super.unbind(emitter); + + window.removeEventListener('resize', this._onResizeBound); + this._mediaQueryList.removeEventListener('change', this._onModeChangeBound); + + this._onResizeBound = null; + this._onModeChangeBound = null; + this._mediaQueryList = null; + } + + onCssReloaded(event) { + let _this = event.data.self; + + _this._colorParams = null; + _this.updateImages(document); + } + + onRendered(event, autorefresh, scripted, autosubmit) { + let _this = event.data.self; + let container = event.target; + + _this.updateImages(container); + } + + onResize() { + // Images are not updated instantly, the user might not yet be finished resizing the window + if (this._resizeTimer !== null) { + clearTimeout(this._resizeTimer); + } + + this._resizeTimer = setTimeout(() => this.updateImages(document), 200); + } + + onModeChange() { + this._colorParams = null; + this.updateImages(document); + } + + updateImages(container) { + container.querySelectorAll('img.graphiteImg[data-actualimageurl]').forEach(img => { + let params = { ...this.colorParams }; // Theming ftw! + params.r = (new Date()).getTime(); // To bypass the browser cache + params.width = img.scrollWidth; // It's either fixed or dependent on parent width + + img.src = this.icinga.utils.addUrlParams(img.dataset.actualimageurl, params); + }); + } + + rgbToHex(rgb, def) { + if (! rgb) { + return def; + } + + let match = rgb.match(/rgba?\((\d+), (\d+), (\d+)(?:, ([\d.]+))?\)/); + if (match === null) { + return def; + } + + let alpha = ''; + if (typeof match[4] !== 'undefined') { + alpha = Math.round(parseFloat(match[4]) * 255).toString(16); + } + + return parseInt(match[1], 10).toString(16).padStart(2, '0') + + parseInt(match[2], 10).toString(16).padStart(2, '0') + + parseInt(match[3], 10).toString(16).padStart(2, '0') + + alpha; + } + } + + Icinga.Behaviors.Graphite = Graphite; + +})(Icinga); @@ -0,0 +1,32 @@ +<?php + +/** @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'; + +$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' + ] + )); +} diff --git a/templates/cpu_windows_powershell_framework.ini b/templates/cpu_windows_powershell_framework.ini new file mode 100644 index 0000000..f156851 --- /dev/null +++ b/templates/cpu_windows_powershell_framework.ini @@ -0,0 +1,19 @@ +[load-windows.graph] +check_command = "Invoke-IcingaCheckCPU" + +[load-windows.metrics_filters] +load.value = "$service_name_template$.perfdata.$load$.value" +crit.value = "$service_name_template$.perfdata.$load$.crit" +warn.value = "$service_name_template$.perfdata.$load$.warn" + +[load-windows.urlparams] +areaAlpha = "0.5" +lineWidth = "2" +min = "0" +title = "CPU $load$ %" +yUnitSystem = "none" + +[load-windows.functions] +load.value = "alias(color($metric$, '#1a7dd7'), 'CPU usage(%)')" +crit.value = "alias(color($metric$, '#ff0000'), 'Crit (%)')" +warn.value = "alias(color($metric$, '#ff8d00'), 'Warn (%)')" diff --git a/templates/default.ini b/templates/default.ini new file mode 100644 index 0000000..5d6921d --- /dev/null +++ b/templates/default.ini @@ -0,0 +1,26 @@ +[default-host.metrics_filters] +value = "$host_name_template$.perfdata.$perfdata$.value" + +[default-host.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +title = "$perfdata$" +yUnitSystem = "none" + +[default-host.functions] +value = "alias(color($metric$, '#1a7dd7'), 'value')" + + +[default-service.metrics_filters] +value = "$service_name_template$.perfdata.$perfdata$.value" + +[default-service.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +title = "$perfdata$" +yUnitSystem = "none" + +[default-service.functions] +value = "alias(color($metric$, '#1a7dd7'), 'value')" diff --git a/templates/disk.ini b/templates/disk.ini new file mode 100644 index 0000000..f1f97ca --- /dev/null +++ b/templates/disk.ini @@ -0,0 +1,22 @@ +[disk.graph] +check_command = "disk, disk-windows" + +[disk.metrics_filters] +value = "$service_name_template$.perfdata.$disk$.value" +max = "$service_name_template$.perfdata.$disk$.max" +crit = "$service_name_template$.perfdata.$disk$.crit" +warn = "$service_name_template$.perfdata.$disk$.warn" + +[disk.urlparams] +areaAlpha = "0.5" +areaMode = "first" +lineWidth = "2" +min = "0" +title = "Disk $disk$" +yUnitSystem = "binary" + +[disk.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Used (bytes)')" +max = "alias(color($metric$, '#cfd7e6'), 'Size (bytes)')" +crit = "alias(color($metric$, '#ff0000'), 'Crit (bytes)')" +warn = "alias(color($metric$, '#ff8d00'), 'Warn (bytes)')" diff --git a/templates/disk_windows_powershell_framework.ini b/templates/disk_windows_powershell_framework.ini new file mode 100644 index 0000000..1b8e827 --- /dev/null +++ b/templates/disk_windows_powershell_framework.ini @@ -0,0 +1,40 @@ +[disk.graph] +check_command = "Invoke-IcingaCheckUsedPartitionSpace" + +[disk.metrics_filters] +value = "$service_name_template$.perfdata.used_space_$disk$.value" +max = "$service_name_template$.perfdata.used_space_$disk$.max" +crit = "$service_name_template$.perfdata.used_space_$disk$.crit" +warn = "$service_name_template$.perfdata.used_space_$disk$.warn" +[disk.urlparams] +areaAlpha = "0.5" +areaMode = "first" +lineWidth = "2" +min = "0" +title = "Disk $disk$" +yUnitSystem = "binary" + +[disk.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Used (bytes)')" +max = "alias(color($metric$, '#cfd7e6'), 'Size (bytes)')" +crit = "alias(color($metric$, '#ff0000'), 'Crit (bytes)')" +warn = "alias(color($metric$, '#ff8d00'), 'Warn (bytes)')" + +[disk-percent.graph] +check_command = "Invoke-IcingaCheckUsedPartitionSpace" + +[disk-percent.metrics_filters] +value = "$service_name_template$.perfdata.$disk$.value" +crit = "$service_name_template$.perfdata.$disk$.crit" +warn = "$service_name_template$.perfdata.$disk$.warn" +[disk-percent.urlparams] +areaAlpha = "0.5" +areaMode = "first" +lineWidth = "2" +min = "0" +title = "Disk $disk$" + +[disk-percent.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Used (%)')" +crit = "alias(color($metric$, '#ff0000'), 'Crit (%)')" +warn = "alias(color($metric$, '#ff8d00'), 'Warn (%)')" diff --git a/templates/ethMon.ini b/templates/ethMon.ini new file mode 100644 index 0000000..41de344 --- /dev/null +++ b/templates/ethMon.ini @@ -0,0 +1,34 @@ +[ethMon-inb.graph] +check_command = "ethMon" + +[ethMon-inb.metrics_filters] +in.value = "$service_name_template$.perfdata.rx.value" + +[ethMon-inb.urlparams] +areaAlpha = "0.3" +areaMode = "all" +lineWidth = "1" +min = "0" +title = "Inbound" +yUnitSystem = "binary" + +[ethMon-inb.functions] +in.value = "alias(color($metric$, '#7CE52D'), 'In (bytes/s)')" + + +[ethMon-out.graph] +check_command = "ethMon" + +[ethMon-out.metrics_filters] +out.value = "$service_name_template$.perfdata.tx.value" + +[ethMon-out.urlparams] +areaAlpha = "0.3" +areaMode = "all" +lineWidth = "1" +min = "0" +title = "Outbound" +yUnitSystem = "binary" + +[ethMon-out.functions] +out.value = "alias(color($metric$, '#ff5566'), 'Out (bytes/s)')" diff --git a/templates/file_age.ini b/templates/file_age.ini new file mode 100644 index 0000000..7be72ad --- /dev/null +++ b/templates/file_age.ini @@ -0,0 +1,32 @@ +[file_age-age.graph] +check_command = "file_age" + +[file_age-age.metrics_filters] +value = "$service_name_template$.perfdata.age.value" + +[file_age-age.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "sec" + +[file_age-age.functions] +value = "alias(color($metric$, '#1a7dd7'), 'File age (s)')" + + +[file_age-size.graph] +check_command = "file_age" + +[file_age-size.metrics_filters] +value = "$service_name_template$.perfdata.size.value" + +[file_age-size.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "binary" + +[file_age-size.functions] +value = "alias(color($metric$, '#1a7dd7'), 'File size (bytes)')" diff --git a/templates/fping.ini b/templates/fping.ini new file mode 100644 index 0000000..0b2b41d --- /dev/null +++ b/templates/fping.ini @@ -0,0 +1,32 @@ +[fping-rta.graph] +check_command = "fping, fping4, fping6" + +[fping-rta.metrics_filters] +rta.value = "$service_name_template$.perfdata.rta.value" + +[fping-rta.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[fping-rta.functions] +rta.value = "alias(color(scale($metric$, 1000), '#1a7dd7'), 'Round trip time (ms)')" + + +[fping-loss.graph] +check_command = "fping, fping4, fping6" + +[fping-loss.metrics_filters] +loss.value = "$service_name_template$.perfdata.loss.value" + +[fping-loss.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[fping-loss.functions] +loss.value = "alias(color($metric$, '#1a7dd7'), 'Packet loss (%)')" diff --git a/templates/graphite-template memory-linux-local.ini b/templates/graphite-template memory-linux-local.ini new file mode 100644 index 0000000..8868bc0 --- /dev/null +++ b/templates/graphite-template memory-linux-local.ini @@ -0,0 +1,28 @@ +[memory.graph] +check_command = "mem" + +[memory.metrics_filters] +value = "$service_name_template$.perfdata.USED.value" +max = "$service_name_template$.perfdata.USED.max" +crit = "$service_name_template$.perfdata.USED.crit" +warn = "$service_name_template$.perfdata.USED.warn" +caches = "$service_name_template$.perfdata.CACHES.value" +free = "$service_name_template$.perfdata.FREE.value" +total = "$service_name_template$.perfdata.TOTAL.value" + +[memory.urlparams] +areaAlpha = "0.5" +areaMode = "first" +lineWidth = "2" +min = "0" +title = "Memory USED" +yUnitSystem = "binary" + +[memory.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Used (bytes)')" +max = "alias(color($metric$, '#cfd7e6'), 'Size (bytes)')" +crit = "alias(color($metric$, '#ff0000'), 'Crit (bytes)')" +warn = "alias(color($metric$, '#ff8d00'), 'Warn (bytes)')" +free = "alias(color($metric$, '#41D2A2'), 'Free (bytes)')" +total = "alias(color($metric$, '#8000FF'), 'Total (bytes)')" +caches = "alias(color($metric$, '#ABABAB'), 'Caches (bytes)')" diff --git a/templates/hostalive.ini b/templates/hostalive.ini new file mode 100644 index 0000000..eda252a --- /dev/null +++ b/templates/hostalive.ini @@ -0,0 +1,32 @@ +[hostalive-rta.graph] +check_command = "hostalive" + +[hostalive-rta.metrics_filters] +rta.value = "$host_name_template$.perfdata.rta.value" + +[hostalive-rta.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[hostalive-rta.functions] +rta.value = "alias(color(scale($metric$, 1000), '#1a7dd7'), 'Round trip time (ms)')" + + +[hostalive-pl.graph] +check_command = "hostalive" + +[hostalive-pl.metrics_filters] +pl.value = "$host_name_template$.perfdata.pl.value" + +[hostalive-pl.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[hostalive-pl.functions] +pl.value = "alias(color($metric$, '#1a7dd7'), 'Packet loss (%)')" diff --git a/templates/icinga.ini b/templates/icinga.ini new file mode 100644 index 0000000..b178fb2 --- /dev/null +++ b/templates/icinga.ini @@ -0,0 +1,302 @@ +[icinga-uptime.graph] +check_command = "icinga" + +[icinga-uptime.metrics_filters] +uptime.value = "$service_name_template$.perfdata.uptime.value" + +[icinga-uptime.urlparams] +title = "Uptime" +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "sec" + +[icinga-uptime.functions] +uptime.value = "alias(color($metric$, '#1a7dd7'), 'Uptime (s)')" + + +[icinga-host-checks.graph] +check_command = "icinga" + +[icinga-host-checks.metrics_filters] +active_host_checks.value = "$service_name_template$.perfdata.active_host_checks.value" +passive_host_checks.value = "$service_name_template$.perfdata.passive_host_checks.value" + +[icinga-host-checks.urlparams] +title = "Host Checks" +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[icinga-host-checks.functions] +active_host_checks.value = "alias(color($metric$, '#1a7dd7'), 'Active/s')" +passive_host_checks.value = "alias(color($metric$, '#0b3c68'), 'Passive/s')" + + +[icinga-service-checks.graph] +check_command = "icinga" + +[icinga-service-checks.metrics_filters] +active_service_checks.value = "$service_name_template$.perfdata.active_service_checks.value" +passive_service_checks.value = "$service_name_template$.perfdata.passive_service_checks.value" + +[icinga-service-checks.urlparams] +title = "Service Checks" +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[icinga-service-checks.functions] +active_service_checks.value = "alias(color($metric$, '#1a7dd7'), 'Active/s')" +passive_service_checks.value = "alias(color($metric$, '#0b3c68'), 'Passive/s')" + + +[icinga-hosts-state.graph] +check_command = "icinga" + +[icinga-hosts-state.metrics_filters] +num_hosts_up.value = "$service_name_template$.perfdata.num_hosts_up.value" +num_hosts_down.value = "$service_name_template$.perfdata.num_hosts_down.value" +num_hosts_unreachable.value = "$service_name_template$.perfdata.num_hosts_unreachable.value" +num_hosts_pending.value = "$service_name_template$.perfdata.num_hosts_pending.value" + +[icinga-hosts-state.urlparams] +title = "Host States" +areaMode = "none" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[icinga-hosts-state.functions] +num_hosts_up.value = "alias(color($metric$, '#44bb77'), 'Up')" +num_hosts_down.value = "alias(color($metric$, '#ff5566'), 'Down')" +num_hosts_unreachable.value = "alias(color($metric$, '#c70fff'), 'Unreachable')" +num_hosts_pending.value = "alias(color($metric$, '#1a7dd7'), 'Pending')" + + +[icinga-hosts-condition.graph] +check_command = "icinga" + +[icinga-hosts-condition.metrics_filters] +num_hosts_flapping.value = "$service_name_template$.perfdata.num_hosts_flapping.value" +num_hosts_in_downtime.value = "$service_name_template$.perfdata.num_hosts_in_downtime.value" +num_hosts_acknowledged.value = "$service_name_template$.perfdata.num_hosts_acknowledged.value" + +[icinga-hosts-condition.urlparams] +title = "Host Conditions" +areaMode = "none" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[icinga-hosts-condition.functions] +num_hosts_flapping.value = "alias(color($metric$, '#c70fff'), 'Flapping')" +num_hosts_in_downtime.value = "alias(color($metric$, '#1a7dd7'), 'In Downtime')" +num_hosts_acknowledged.value = "alias(color($metric$, '#0b3c68'), 'Acknowledged')" + + +[icinga-services-state.graph] +check_command = "icinga" + +[icinga-services-state.metrics_filters] +num_services_ok.value = "$service_name_template$.perfdata.num_services_ok.value" +num_services_warning.value = "$service_name_template$.perfdata.num_services_warning.value" +num_services_critical.value = "$service_name_template$.perfdata.num_services_critical.value" +num_services_unknown.value = "$service_name_template$.perfdata.num_services_unknown.value" +num_services_pending.value = "$service_name_template$.perfdata.num_services_pending.value" +num_services_unreachable.value = "$service_name_template$.perfdata.num_services_unreachable.value" + +[icinga-services-state.urlparams] +title = "Service States" +areaMode = "none" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[icinga-services-state.functions] +num_services_ok.value = "alias(color($metric$, '#44bb77'), 'Ok')" +num_services_warning.value = "alias(color($metric$, '#ffaa44'), 'Warning')" +num_services_critical.value = "alias(color($metric$, '#ff5566'), 'Critical')" +num_services_unknown.value = "alias(color($metric$, '#595959'), 'Unknown')" +num_services_pending.value = "alias(color($metric$, '#1a7dd7'), 'Pending')" +num_services_unreachable.value = "alias(color($metric$, '#c70fff'), 'Unreachable')" + + +[icinga-services-condition.graph] +check_command = "icinga" + +[icinga-services-condition.metrics_filters] +num_services_flapping.value = "$service_name_template$.perfdata.num_services_flapping.value" +num_services_in_downtime.value = "$service_name_template$.perfdata.num_services_in_downtime.value" +num_services_acknowledged.value = "$service_name_template$.perfdata.num_services_acknowledged.value" + +[icinga-services-condition.urlparams] +title = "Service Conditions" +areaMode = "none" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[icinga-services-condition.functions] +num_services_flapping.value = "alias(color($metric$, '#c70fff'), 'Flapping')" +num_services_in_downtime.value = "alias(color($metric$, '#1a7dd7'), 'In Downtime')" +num_services_acknowledged.value = "alias(color($metric$, '#0b3c68'), 'Acknowledged')" + + +[icinga-latency.graph] +check_command = "icinga" + +[icinga-latency.metrics_filters] +min_latency.value = "$service_name_template$.perfdata.min_latency.value" +avg_latency.value = "$service_name_template$.perfdata.avg_latency.value" +max_latency.value = "$service_name_template$.perfdata.max_latency.value" + +[icinga-latency.urlparams] +title = "Check Latency" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "sec" + +[icinga-latency.functions] +min_latency.value = "alias(color($metric$, '#61aaed'), 'Min (s)')" +avg_latency.value = "alias(color($metric$, '#1a7dd7'), 'Avg (s)')" +max_latency.value = "alias(color($metric$, '#0b3c68'), 'Max (s)')" + + +[icinga-execution-time.graph] +check_command = "icinga" + +[icinga-execution-time.metrics_filters] +min_execution_time.value = "$service_name_template$.perfdata.min_execution_time.value" +avg_execution_time.value = "$service_name_template$.perfdata.avg_execution_time.value" +max_execution_time.value = "$service_name_template$.perfdata.max_execution_time.value" + +[icinga-execution-time.urlparams] +title = "Check Execution Time" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "sec" + +[icinga-execution-time.functions] +min_execution_time.value = "alias(color($metric$, '#61aaed'), 'Min (s)')" +avg_execution_time.value = "alias(color($metric$, '#1a7dd7'), 'Avg (s)')" +max_execution_time.value = "alias(color($metric$, '#0b3c68'), 'Max (s)')" + + +[icinga-api-endpoints.graph] +check_command = "icinga" + +[icinga-api-endpoints.metrics_filters] +api_num_endpoints.value = "$service_name_template$.perfdata.api_num_endpoints.value" +api_num_conn_endpoints.value = "$service_name_template$.perfdata.api_num_conn_endpoints.value" +api_num_not_conn_endpoints.value = "$service_name_template$.perfdata.api_num_not_conn_endpoints.value" + +[icinga-api-endpoints.urlparams] +title = "API Endpoints" +areaMode = "none" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[icinga-api-endpoints.functions] +api_num_endpoints.value = "alias(color($metric$, '#61aaed'), 'All')" +api_num_conn_endpoints.value = "alias(color($metric$, '#1a7dd7'), 'Connected')" +api_num_not_conn_endpoints.value = "alias(color($metric$, '#0b3c68'), 'Not Connected')" + + +[icinga-api-http-clients.graph] +check_command = "icinga" + +[icinga-api-http-clients.metrics_filters] +api_num_http_clients.value = "$service_name_template$.perfdata.api_num_http_clients.value" + +[icinga-api-http-clients.urlparams] +title = "API" +areaMode = "none" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[icinga-api-http-clients.functions] +api_num_http_clients.value = "alias(color($metric$, '#1a7dd7'), 'HTTP Clients')" + + +[icinga-checker.graph] +check_command = "icinga" + +[icinga-checker.metrics_filters] +checkercomponent_checker_idle.value = "$service_name_template$.perfdata.checkercomponent_checker_idle.value" +checkercomponent_checker_pending.value = "$service_name_template$.perfdata.checkercomponent_checker_pending.value" + +[icinga-checker.urlparams] +title = "Checker" +areaMode = "none" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[icinga-checker.functions] +checkercomponent_checker_idle.value = "alias(color($metric$, '#61aaed'), 'Idle')" +checkercomponent_checker_pending.value = "alias(color($metric$, '#1a7dd7'), 'Pending')" + + +[icinga-ido-mysql-queries.graph] +check_command = "icinga" + +[icinga-ido-mysql-queries.metrics_filters] +idomysqlconnection_ido-mysql_queries_rate.value = "$service_name_template$.perfdata.idomysqlconnection_ido-mysql_queries_rate.value" + +[icinga-ido-mysql-queries.urlparams] +title = "IDO MySQL" +areaMode = "none" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[icinga-ido-mysql-queries.functions] +idomysqlconnection_ido-mysql_queries_rate.value = "alias(color($metric$, '#1a7dd7'), 'Queries/s')" + + +[icinga-ido-mysql-queue.graph] +check_command = "icinga" + +[icinga-ido-mysql-queue.metrics_filters] +idomysqlconnection_ido-mysql_query_queue_items.value = "$service_name_template$.perfdata.idomysqlconnection_ido-mysql_query_queue_items.value" +idomysqlconnection_ido-mysql_query_queue_item_rate.value = "$service_name_template$.perfdata.idomysqlconnection_ido-mysql_query_queue_item_rate.value" + +[icinga-ido-mysql-queue.urlparams] +title = "IDO MySQL Queue" +areaMode = "none" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[icinga-ido-mysql-queue.functions] +idomysqlconnection_ido-mysql_query_queue_items.value = "alias(color($metric$, '#1a7dd7'), 'Items')" +idomysqlconnection_ido-mysql_query_queue_item_rate.value = "alias(color($metric$, '#0b3c68'), 'Items/s')" + + +[icinga-graphite-queue.graph] +check_command = "icinga" + +[icinga-graphite-queue.metrics_filters] +graphitewriter_graphite_work_queue_items.value = "$service_name_template$.perfdata.graphitewriter_graphite_work_queue_items.value" +graphitewriter_graphite_work_queue_item_rate.value = "$service_name_template$.perfdata.graphitewriter_graphite_work_queue_item_rate.value" + +[icinga-graphite-queue.urlparams] +title = "Graphite Queue" +areaMode = "none" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[icinga-graphite-queue.functions] +graphitewriter_graphite_work_queue_items.value = "alias(color($metric$, '#1a7dd7'), 'Items')" +graphitewriter_graphite_work_queue_item_rate.value = "alias(color($metric$, '#0b3c68'), 'Items/s')" diff --git a/templates/icmp-hosts.ini b/templates/icmp-hosts.ini new file mode 100644 index 0000000..e6122a3 --- /dev/null +++ b/templates/icmp-hosts.ini @@ -0,0 +1,38 @@ +[icmp-rt.graph] +check_command = "icmp-host" + +[icmp-rt.metrics_filters] +rtmin.value = "$host_name_template$.perfdata.rtmin.value" +rta.value = "$host_name_template$.perfdata.rta.value" +rtmax.value = "$host_name_template$.perfdata.rtmax.value" + +[icmp-rt.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[icmp-rt.functions] +rtmin.value = "alias(color(scale($metric$, 1000), '#44bb77'), 'Min. round trip time (ms)')" +rta.value = "alias(color(scale($metric$, 1000), '#ffaa44'), 'Avg. round trip time (ms)')" +rtmax.value = "alias(color(scale($metric$, 1000), '#ff5566'), 'Max. round trip time (ms)')" + + +[icmp-pl.graph] +check_command = "icmp-host" + +[icmp-pl.metrics_filters] +pl.value = "$host_name_template$.perfdata.pl.value" + +[icmp-pl.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[icmp-pl.functions] +pl.value = "alias(color($metric$, '#1a7dd7'), 'Packet loss (%)')" + + diff --git a/templates/icmp.ini b/templates/icmp.ini new file mode 100644 index 0000000..cb60655 --- /dev/null +++ b/templates/icmp.ini @@ -0,0 +1,36 @@ +[icmp-rt.graph] +check_command = "icmp" + +[icmp-rt.metrics_filters] +rtmin.value = "$service_name_template$.perfdata.rtmin.value" +rta.value = "$service_name_template$.perfdata.rta.value" +rtmax.value = "$service_name_template$.perfdata.rtmax.value" + +[icmp-rt.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[icmp-rt.functions] +rtmin.value = "alias(color(scale($metric$, 1000), '#44bb77'), 'Min. round trip time (ms)')" +rta.value = "alias(color(scale($metric$, 1000), '#ffaa44'), 'Avg. round trip time (ms)')" +rtmax.value = "alias(color(scale($metric$, 1000), '#ff5566'), 'Max. round trip time (ms)')" + + +[icmp-pl.graph] +check_command = "icmp" + +[icmp-pl.metrics_filters] +pl.value = "$service_name_template$.perfdata.pl.value" + +[icmp-pl.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[icmp-pl.functions] +pl.value = "alias(color($metric$, '#1a7dd7'), 'Packet loss (%)')" diff --git a/templates/interfacetable.ini b/templates/interfacetable.ini new file mode 100644 index 0000000..359d201 --- /dev/null +++ b/templates/interfacetable.ini @@ -0,0 +1,52 @@ +[interfacetable-traffic.graph] +check_command = "interfacetable" + +[interfacetable-traffic.metrics_filters] +bpsin = "$service_name_template$.perfdata.If_$interface$.check_interface_table_port.BpsIn.value" +bpsout = "$service_name_template$.perfdata.If_$interface$.check_interface_table_port.BpsOut.value" + +[interfacetable-traffic.urlparams] +min = "0" +title = "Interface $interface$" +lineWidth = "2" +yUnitSystem = "si" + +[interfacetable-traffic.functions] +bpsin = "alias(color($metric$, '#1a7dd7'), 'Traffic in (B/s)')" +bpsout = "alias(color($metric$, '#0b3c68'), 'Traffic out (B/s)')" + + +[interfacetable-discard.graph] +check_command = "interfacetable" + +[interfacetable-discard.metrics_filters] +ppsindiscard = "$service_name_template$.perfdata.If_$interface$.check_interface_table_port.PpsInDiscard.value" +ppsoutdiscard = "$service_name_template$.perfdata.If_$interface$.check_interface_table_port.PpsOutDiscard.value" + +[interfacetable-discard.urlparams] +min = "0" +title = "Interface $interface$" +lineWidth = "2" +yUnitSystem = "si" + +[interfacetable-discard.functions] +ppsindiscard = "alias(color($metric$, '#edb017'), 'Discard in (B/s)')" +ppsoutdiscard = "alias(color($metric$, '#ad7d05'), 'Discard out (B/s)')" + + +[interfacetable-error.graph] +check_command = "interfacetable" + +[interfacetable-error.metrics_filters] +ppsinerr = "$service_name_template$.perfdata.If_$interface$.check_interface_table_port.PpsInErr.value" +ppsouterr = "$service_name_template$.perfdata.If_$interface$.check_interface_table_port.PpsOutErr.value" + +[interfacetable-error.urlparams] +min = "0" +title = "Interface $interface$" +lineWidth = "2" +yUnitSystem = "si" + +[interfacetable-error.functions] +ppsinerr = "alias(color($metric$, '#ff5566'), 'Error in (B/s)')" +ppsouterr = "alias(color($metric$, '#a80000'), 'Error out (B/s)')" diff --git a/templates/load.ini b/templates/load.ini new file mode 100644 index 0000000..1a4de4d --- /dev/null +++ b/templates/load.ini @@ -0,0 +1,35 @@ +[load.graph] +check_command = "load" + +[load.metrics_filters] +load15.value = "$service_name_template$.perfdata.load15.value" +load5.value = "$service_name_template$.perfdata.load5.value" +load1.value = "$service_name_template$.perfdata.load1.value" + +[load.urlparams] +areaAlpha = "0.5" +min = "0" +yUnitSystem = "none" +lineWidth = "2" + +[load.functions] +load15.value = "alias(color($metric$, '#ff5566'), 'Load 15')" +load5.value = "alias(color($metric$, '#ffaa44'), 'Load 5')" +load1.value = "alias(color($metric$, '#44bb77'), 'Load 1')" + + +[load-windows.graph] +check_command = "load-windows" + +[load-windows.metrics_filters] +value = "$service_name_template$.perfdata.load.value" + +[load-windows.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[load-windows.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Load (%)')" diff --git a/templates/mailq.ini b/templates/mailq.ini new file mode 100644 index 0000000..76b8a94 --- /dev/null +++ b/templates/mailq.ini @@ -0,0 +1,15 @@ +[mailq.graph] +check_command = "mailq" + +[mailq.metrics_filters] +unsent = "$service_name_template$.perfdata.unsent.value" + +[mailq.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mailq.functions] +unsent = "alias(color($metric$, '#1a7dd7'), 'Unsent mails')" diff --git a/templates/memory.ini b/templates/memory.ini new file mode 100644 index 0000000..daba4b4 --- /dev/null +++ b/templates/memory.ini @@ -0,0 +1,17 @@ +[memory.graph] +check_command = "memory-windows" + +[memory.metrics_filters] +value = "$service_name_template$.perfdata.memory.value" +max = "$service_name_template$.perfdata.memory.max" + +[memory.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "binary" + +[memory.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Used (bytes)')" +max = "alias(color($metric$, '#cfd7e6'), 'Size (bytes)')" diff --git a/templates/memory_windows_powershell_framework.ini b/templates/memory_windows_powershell_framework.ini new file mode 100644 index 0000000..53f89f4 --- /dev/null +++ b/templates/memory_windows_powershell_framework.ini @@ -0,0 +1,42 @@ +[memory.graph] +check_command = "Invoke-IcingaCheckMemory" + +[memory.metrics_filters] +value = "$service_name_template$.perfdata.used_bytes.value" +max = "$service_name_template$.perfdata.used_bytes.max" +crit = "$service_name_template$.perfdata.used_bytes.crit" +warn = "$service_name_template$.perfdata.used_bytes.warn" + +[memory.urlparams] +areaAlpha = "0.5" +areaMode = "first" +lineWidth = "2" +min = "0" +title = "Memory used" +yUnitSystem = "binary" + +[memory.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Used (bytes)')" +max = "alias(color($metric$, '#cfd7e6'), 'Size (bytes)')" +crit = "alias(color($metric$, '#ff0000'), 'Crit (bytes)')" +warn = "alias(color($metric$, '#ff8d00'), 'Warn (bytes)')" + +[memory-percent.graph] +check_command = "Invoke-IcingaCheckMemory" + +[memory-percent.metrics_filters] +value = "$service_name_template$.perfdata.memory_percent_used.value" +crit = "$service_name_template$.perfdata.memory_percent_used.crit" +warn = "$service_name_template$.perfdata.memory_percent_used.warn" + +[memory-percent.urlparams] +areaAlpha = "0.5" +areaMode = "first" +lineWidth = "2" +min = "0" +title = "Memory % used" + +[memory-percent.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Used (%)')" +crit = "alias(color($metric$, '#ff0000'), 'Crit (%)')" +warn = "alias(color($metric$, '#ff8d00'), 'Warn (%)')" diff --git a/templates/multi.ini b/templates/multi.ini new file mode 100644 index 0000000..2f35658 --- /dev/null +++ b/templates/multi.ini @@ -0,0 +1,54 @@ +[multi2-host.metrics_filters] +value = "$host_name_template$.perfdata.$multi1$.$multi2$.value" + +[multi2-host.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +title = "$multi1$::$multi2$" +yUnitSystem = "none" + +[multi2-host.functions] +value = "alias(color($metric$, '#1a7dd7'), 'value')" + + +[multi2-service.metrics_filters] +value = "$service_name_template$.perfdata.$multi1$.$multi2$.value" + +[multi2-service.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +title = "$multi1$::$multi2$" +yUnitSystem = "none" + +[multi2-service.functions] +value = "alias(color($metric$, '#1a7dd7'), 'value')" + + +[multi3-host.metrics_filters] +value = "$host_name_template$.perfdata.$multi1$.$multi2$.$multi3$.value" + +[multi3-host.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +title = "$multi1$::$multi2$::$multi3$" +yUnitSystem = "none" + +[multi3-host.functions] +value = "alias(color($metric$, '#1a7dd7'), 'value')" + + +[multi3-service.metrics_filters] +value = "$service_name_template$.perfdata.$multi1$.$multi2$.$multi3$.value" + +[multi3-service.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +title = "$multi1$::$multi2$::$multi3$" +yUnitSystem = "none" + +[multi3-service.functions] +value = "alias(color($metric$, '#1a7dd7'), 'value')" diff --git a/templates/mysql.ini b/templates/mysql.ini new file mode 100644 index 0000000..f39d15a --- /dev/null +++ b/templates/mysql.ini @@ -0,0 +1,270 @@ +[mysql-Connections.graph] +check_command = "mysql" + +[mysql-Connections.metrics_filters] +value = "$service_name_template$.perfdata.Connections.value" + +[mysql-Connections.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql-Connections.functions] +value = "alias(color(nonNegativeDerivative($metric$), '#1a7dd7'), 'Connections')" + + +[mysql-Open_files.graph] +check_command = "mysql" + +[mysql-Open_files.metrics_filters] +value = "$service_name_template$.perfdata.Open_files.value" + +[mysql-Open_files.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql-Open_files.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Open files')" + + +[mysql-Open_tables.graph] +check_command = "mysql" + +[mysql-Open_tables.metrics_filters] +value = "$service_name_template$.perfdata.Open_tables.value" + +[mysql-Open_tables.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql-Open_tables.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Open tables')" + + +[mysql-Qcache_free_memory.graph] +check_command = "mysql" + +[mysql-Qcache_free_memory.metrics_filters] +value = "$service_name_template$.perfdata.Qcache_free_memory.value" + +[mysql-Qcache_free_memory.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "binary" + +[mysql-Qcache_free_memory.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Query cache free memory (Bytes)')" + + +[mysql-Qcache_hits.graph] +check_command = "mysql" + +[mysql-Qcache_hits.metrics_filters] +value = "$service_name_template$.perfdata.Qcache_hits.value" + +[mysql-Qcache_hits.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql-Qcache_hits.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Query cache hits')" + + +[mysql-Qcache_inserts.graph] +check_command = "mysql" + +[mysql-Qcache_inserts.metrics_filters] +value = "$service_name_template$.perfdata.Qcache_inserts.value" + +[mysql-Qcache_inserts.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql-Qcache_inserts.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Query cache inserts')" + + +[mysql-Qcache_lowmem_prunes.graph] +check_command = "mysql" + +[mysql-Qcache_lowmem_prunes.metrics_filters] +value = "$service_name_template$.perfdata.Qcache_lowmem_prunes.value" + +[mysql-Qcache_lowmem_prunes.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql-Qcache_lowmem_prunes.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Query cache lowmem prunes')" + + +[mysql-Qcache_not_cached.graph] +check_command = "mysql" + +[mysql-Qcache_not_cached.metrics_filters] +value = "$service_name_template$.perfdata.Qcache_not_cached.value" + +[mysql-Qcache_not_cached.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql-Qcache_not_cached.functions] +value = "alias(color(nonNegativeDerivative($metric$), '#1a7dd7'), 'Not cached queries')" + + +[mysql-Qcache_queries_in_cache.graph] +check_command = "mysql" + +[mysql-Qcache_queries_in_cache.metrics_filters] +value = "$service_name_template$.perfdata.Qcache_queries_in_cache.value" + +[mysql-Qcache_queries_in_cache.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql-Qcache_queries_in_cache.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Queries in cache')" + + +[mysql-Queries.graph] +check_command = "mysql" + +[mysql-Queries.metrics_filters] +value = "$service_name_template$.perfdata.Queries.value" + +[mysql-Queries.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql-Queries.functions] +value = "alias(color(nonNegativeDerivative($metric$), '#1a7dd7'), 'Queries')" + + +[mysql-Questions.graph] +check_command = "mysql" + +[mysql-Questions.metrics_filters] +value = "$service_name_template$.perfdata.Questions.value" + +[mysql-Questions.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql-Questions.functions] +value = "alias(color(nonNegativeDerivative($metric$), '#1a7dd7'), 'Questions')" + + +[mysql-Table_locks_waited.graph] +check_command = "mysql" + +[mysql-Table_locks_waited.metrics_filters] +value = "$service_name_template$.perfdata.Table_locks_waited.value" + +[mysql-Table_locks_waited.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql-Table_locks_waited.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Table locks waited for')" + + +[mysql-Threads_connected.graph] +check_command = "mysql" + +[mysql-Threads_connected.metrics_filters] +value = "$service_name_template$.perfdata.Threads_connected.value" + +[mysql-Threads_connected.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql-Threads_connected.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Threads connected')" + + +[mysql-Threads_running.graph] +check_command = "mysql" + +[mysql-Threads_running.metrics_filters] +value = "$service_name_template$.perfdata.Threads_running.value" + +[mysql-Threads_running.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql-Threads_running.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Threads running')" + + +[mysql-Uptime.graph] +check_command = "mysql" + +[mysql-Uptime.metrics_filters] +value = "$service_name_template$.perfdata.Uptime.value" + +[mysql-Uptime.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "sec" + +[mysql-Uptime.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Uptime (s)')" + + +[mysql-seconds_behind_master.graph] +check_command = "mysql" + +[mysql-seconds_behind_master.metrics_filters] +value = "$service_name_template$.perfdata.seconds_behind_master.value" + +[mysql-seconds_behind_master.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "sec" + +[mysql-seconds_behind_master.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Seconds behind master')" diff --git a/templates/mysql_health.ini b/templates/mysql_health.ini new file mode 100644 index 0000000..711338c --- /dev/null +++ b/templates/mysql_health.ini @@ -0,0 +1,456 @@ +[mysql_health-bufferpool_hitrate.graph] +check_command = "mysql_health" + +[mysql_health-bufferpool_hitrate.metrics_filters] +value = "$service_name_template$.perfdata.bufferpool_hitrate.value" +now.value = "$service_name_template$.perfdata.bufferpool_hitrate_now.value" + +[mysql_health-bufferpool_hitrate.urlparams] +areaAlpha = "0.5" +areaMode = "first" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql_health-bufferpool_hitrate.functions] +value = "alias(color($metric$, '#1a7dd7'), 'InnoDB buffer pool hitrate (%)')" +now.value = "alias(color($metric$, '#ff5566'), 'InnoDB buffer pool hitrate (now, %)')" + + +[mysql_health-bufferpool_wait_free.graph] +check_command = "mysql_health" + +[mysql_health-bufferpool_wait_free.metrics_filters] +value = "$service_name_template$.perfdata.bufferpool_free_waits_rate.value" + +[mysql_health-bufferpool_wait_free.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql_health-bufferpool_wait_free.functions] +value = "alias(color($metric$, '#1a7dd7'), 'InnoDB buffer pool waits/s')" + + +[mysql_health-clients_aborted.graph] +check_command = "mysql_health" + +[mysql_health-clients_aborted.metrics_filters] +value = "$service_name_template$.perfdata.clients_aborted_per_sec.value" + +[mysql_health-clients_aborted.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql_health-clients_aborted.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Clients aborted/s')" + + +[mysql_health-cluster_ndbd_running.graph] +check_command = "mysql_health" + +[mysql_health-cluster_ndbd_running.metrics_filters] +ndbd_nodes.value = "$service_name_template$.perfdata.ndbd_nodes.value" +ndb_mgmd_nodes.value = "$service_name_template$.perfdata.ndb_mgmd_nodes.value" + +[mysql_health-cluster_ndbd_running.urlparams] +areaAlpha = "0.5" +areaMode = "first" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql_health-cluster_ndbd_running.functions] +ndbd_nodes.value = "alias(color($metric$, '#1a7dd7'), 'Ndbd nodes')" +ndb_mgmd_nodes.value = "alias(color($metric$, '#ff5566'), 'Ndb_mgmd nodes')" + + +[mysql_health-mysqld_nodes.graph] +check_command = "mysql_health" + +[mysql_health-mysqld_nodes.metrics_filters] +value = "$service_name_template$.perfdata.mysqld_nodes.value" + +[mysql_health-mysqld_nodes.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql_health-mysqld_nodes.functions] +value = "alias(color($metric$, '#1a7dd7'), 'MySQLd nodes')" + + +[mysql_health-connection_time.graph] +check_command = "mysql_health" + +[mysql_health-connection_time.metrics_filters] +value = "$service_name_template$.perfdata.connection_time.value" + +[mysql_health-connection_time.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql_health-connection_time.functions] +value = "alias(color(scale($metric$, 1000), '#1a7dd7'), 'Connection time (ms)')" + + +[mysql_health-connects_aborted.graph] +check_command = "mysql_health" + +[mysql_health-connects_aborted.metrics_filters] +value = "$service_name_template$.perfdata.connects_aborted_per_sec.value" + +[mysql_health-connects_aborted.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql_health-connects_aborted.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Connects aborted/s')" + + +[mysql_health-index_usage.graph] +check_command = "mysql_health" + +[mysql_health-index_usage.metrics_filters] +value = "$service_name_template$.perfdata.index_usage.value" +now.value = "$service_name_template$.perfdata.index_usage_now.value" + +[mysql_health-index_usage.urlparams] +areaAlpha = "0.5" +areaMode = "first" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql_health-index_usage.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Index usage (%)')" +now.value = "alias(color($metric$, '#ff5566'), 'Index usage (now, %)')" + + +[mysql_health-keycache_hitrate.graph] +check_command = "mysql_health" + +[mysql_health-keycache_hitrate.metrics_filters] +value = "$service_name_template$.perfdata.keycache_hitrate.value" +now.value = "$service_name_template$.perfdata.keycache_hitrate_now.value" + +[mysql_health-keycache_hitrate.urlparams] +areaAlpha = "0.5" +areaMode = "first" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql_health-keycache_hitrate.functions] +value = "alias(color($metric$, '#1a7dd7'), 'MyISAM keycache hitrate (%)')" +now.value = "alias(color($metric$, '#ff5566'), 'MyISAM keycache hitrate (now, %)')" + + +[mysql_health-log_waits.graph] +check_command = "mysql_health" + +[mysql_health-log_waits.metrics_filters] +value = "$service_name_template$.perfdata.innodb_log_waits_rate.value" + +[mysql_health-log_waits.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql_health-log_waits.functions] +value = "alias(color($metric$, '#1a7dd7'), 'InnoDB log waits/s')" + + +[mysql_health-long_running_procs.graph] +check_command = "mysql_health" + +[mysql_health-long_running_procs.metrics_filters] +value = "$service_name_template$.perfdata.long_running_procs.value" + +[mysql_health-long_running_procs.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql_health-long_running_procs.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Long running processes')" + + +[mysql_health-open_files.graph] +check_command = "mysql_health" + +[mysql_health-open_files.metrics_filters] +value = "$service_name_template$.perfdata.open_files.value" + +[mysql_health-open_files.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql_health-open_files.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Open files')" + + +[mysql_health-qcache_hitrate.graph] +check_command = "mysql_health" + +[mysql_health-qcache_hitrate.metrics_filters] +value = "$service_name_template$.perfdata.qcache_hitrate.value" +now.value = "$service_name_template$.perfdata.qcache_hitrate_now.value" + +[mysql_health-qcache_hitrate.urlparams] +areaAlpha = "0.5" +areaMode = "first" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql_health-qcache_hitrate.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Query cache hitrate (%)')" +now.value = "alias(color($metric$, '#ff5566'), 'Query cache hitrate (now, %)')" + + +[mysql_health-selects_per_sec.graph] +check_command = "mysql_health" + +[mysql_health-selects_per_sec.metrics_filters] +value = "$service_name_template$.perfdata.selects_per_sec.value" + +[mysql_health-selects_per_sec.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql_health-selects_per_sec.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Selects/s')" + + +[mysql_health-qcache_lowmem_prunes.graph] +check_command = "mysql_health" + +[mysql_health-qcache_lowmem_prunes.metrics_filters] +value = "$service_name_template$.perfdata.qcache_lowmem_prunes_rate.value" + +[mysql_health-qcache_lowmem_prunes.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql_health-qcache_lowmem_prunes.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Query cache lowmem prunes/s')" + + +[mysql_health-slave_lag.graph] +check_command = "mysql_health" + +[mysql_health-slave_lag.metrics_filters] +value = "$service_name_template$.perfdata.slave_lag.value" + +[mysql_health-slave_lag.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "sec" + +[mysql_health-slave_lag.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Seconds slave is behind master')" + + +[mysql_health-slow_queries.graph] +check_command = "mysql_health" + +[mysql_health-slow_queries.metrics_filters] +value = "$service_name_template$.perfdata.slow_queries_rate.value" + +[mysql_health-slow_queries.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql_health-slow_queries.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Slow queries/s')" + + +[mysql_health-table_lock_contention.graph] +check_command = "mysql_health" + +[mysql_health-table_lock_contention.metrics_filters] +value = "$service_name_template$.perfdata.tablelock_contention.value" +now.value = "$service_name_template$.perfdata.tablelock_contention_now.value" + +[mysql_health-table_lock_contention.urlparams] +areaAlpha = "0.5" +areaMode = "first" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql_health-table_lock_contention.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Table lock contention (%)')" +now.value = "alias(color($metric$, '#ff5566'), 'Table lock contention (now, %)')" + + +[mysql_health-tablecache_hitrate.graph] +check_command = "mysql_health" + +[mysql_health-tablecache_hitrate.metrics_filters] +value = "$service_name_template$.perfdata.tablecache_hitrate.value" + +[mysql_health-tablecache_hitrate.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql_health-tablecache_hitrate.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Table cache hitrate (%)')" + + +[mysql_health-tablecache_fillrate.graph] +check_command = "mysql_health" + +[mysql_health-tablecache_fillrate.metrics_filters] +value = "$service_name_template$.perfdata.tablecache_fillrate.value" + +[mysql_health-tablecache_fillrate.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql_health-tablecache_fillrate.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Table cache fillrate (%)')" + + +[mysql_health-threadcache_hitrate.graph] +check_command = "mysql_health" + +[mysql_health-threadcache_hitrate.metrics_filters] +value = "$service_name_template$.perfdata.thread_cache_hitrate.value" +now.value = "$service_name_template$.perfdata.thread_cache_hitrate_now.value" + +[mysql_health-threadcache_hitrate.urlparams] +areaAlpha = "0.5" +areaMode = "first" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql_health-threadcache_hitrate.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Thread cache hitrate (%)')" +now.value = "alias(color($metric$, '#ff5566'), 'Thread cache hitrate (now, %)')" + + +[mysql_health-threads_cached.graph] +check_command = "mysql_health" + +[mysql_health-threads_cached.metrics_filters] +value = "$service_name_template$.perfdata.threads_cached.value" + +[mysql_health-threads_cached.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql_health-threads_cached.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Threads cached')" + + +[mysql_health-threads_connected.graph] +check_command = "mysql_health" + +[mysql_health-threads_connected.metrics_filters] +value = "$service_name_template$.perfdata.threads_connected.value" + +[mysql_health-threads_connected.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql_health-threads_connected.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Threads connected')" + + +[mysql_health-threads_created.graph] +check_command = "mysql_health" + +[mysql_health-threads_created.metrics_filters] +value = "$service_name_template$.perfdata.threads_created_per_sec.value" + +[mysql_health-threads_created.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql_health-threads_created.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Threads created/s')" + + +[mysql_health-threads_running.graph] +check_command = "mysql_health" + +[mysql_health-threads_running.metrics_filters] +value = "$service_name_template$.perfdata.threads_running.value" + +[mysql_health-threads_running.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql_health-threads_running.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Threads running')" + + +[mysql_health-tmp_disk_tables.graph] +check_command = "mysql_health" + +[mysql_health-tmp_disk_tables.metrics_filters] +value = "$service_name_template$.perfdata.pct_tmp_table_on_disk.value" +now.value = "$service_name_template$.perfdata.pct_tmp_table_on_disk_now.value" + +[mysql_health-tmp_disk_tables.urlparams] +areaAlpha = "0.5" +areaMode = "first" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[mysql_health-tmp_disk_tables.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Temp tables created on disk (%)')" +now.value = "alias(color($metric$, '#ff5566'), 'Temp tables created on disk (now, %)')" diff --git a/templates/netapp_cdot_aggregates.ini b/templates/netapp_cdot_aggregates.ini new file mode 100644 index 0000000..5e26e6f --- /dev/null +++ b/templates/netapp_cdot_aggregates.ini @@ -0,0 +1,22 @@ +[disk.graph] +check_command = "netapp_cdot_aggr_status" + +[disk.metrics_filters] +value = "$service_name_template$.perfdata.$disk$.value" +max = "$service_name_template$.perfdata.$disk$.max" +crit = "$service_name_template$.perfdata.$disk$.crit" +warn = "$service_name_template$.perfdata.$disk$.warn" +[disk.urlparams] +areaAlpha = "0.5" +areaMode = "first" +lineWidth = "2" +min = "0" +title = "Disk $disk$" +yUnitSystem = "binary" + +[disk.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Used (bytes)')" +max = "alias(color($metric$, '#cfd7e6'), 'Size (bytes)')" +crit = "alias(color($metric$, '#ff0000'), 'Crit (bytes)')" +warn = "alias(color($metric$, '#ff8d00'), 'Warn (bytes)')" + diff --git a/templates/netapp_cdot_volumes.ini b/templates/netapp_cdot_volumes.ini new file mode 100644 index 0000000..92cc686 --- /dev/null +++ b/templates/netapp_cdot_volumes.ini @@ -0,0 +1,44 @@ +[vol-space.graph] +check_command = "netapp_cdot_volume_status" + +[vol-space.metrics_filters] +value = "$service_name_template$.perfdata.$vol$.check_cdot_volume_usage.space_used.value" +max = "$service_name_template$.perfdata.$vol$.check_cdot_volume_usage.space_used.max" +crit = "$service_name_template$.perfdata.$vol$.check_cdot_volume_usage.space_used.crit" +warn = "$service_name_template$.perfdata.$vol$.check_cdot_volume_usage.space_used.warn" +[vol-space.urlparams] +areaAlpha = "0.5" +areaMode = "first" +lineWidth = "2" +min = "0" +title = "Disk $vol$ Space used" +yUnitSystem = "binary" + +[vol-space.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Used (bytes)')" +max = "alias(color($metric$, '#cfd7e6'), 'Size (bytes)')" +crit = "alias(color($metric$, '#ff0000'), 'Crit (bytes)')" +warn = "alias(color($metric$, '#ff8d00'), 'Warn (bytes)')" + +[disk-inode.graph] +check_command = "netapp_cdot_volume_status" + +[disk-inode.metrics_filters] +value = "$service_name_template$.perfdata.$disk$.check_cdot_volume_usage.inode_used.value" +max = "$service_name_template$.perfdata.$disk$.check_cdot_volume_usage.inode_used.max" +crit = "$service_name_template$.perfdata.$disk$.check_cdot_volume_usage.inode_used.crit" +warn = "$service_name_template$.perfdata.$disk$.check_cdot_volume_usage.inode_used.warn" + +[disk-inode.urlparams] +areaAlpha = "0.5" +areaMode = "first" +lineWidth = "2" +min = "0" +title = "Disk $disk$ Inodes used" + +[disk-inode.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Used Inodes')" +max = "alias(color($metric$, '#cfd7e6'), 'Max Inodes')" +crit = "alias(color($metric$, '#ff0000'), 'Crit Inodes')" +warn = "alias(color($metric$, '#ff8d00'), 'Warn Inodes')" + diff --git a/templates/network.ini b/templates/network.ini new file mode 100644 index 0000000..e7801e6 --- /dev/null +++ b/templates/network.ini @@ -0,0 +1,35 @@ +[network-windows-total.graph] +check_command = "network-windows" + +[network-windows-total.metrics_filters] +value = "$service_name_template$.perfdata.network.value" + +[network-windows-total.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +title = "Total" +yUnitSystem = "binary" + +[network-windows-total.functions] +value = "alias(color($metric$, '#1a7dd7'), 'I/O (bytes/s)')" + + +[network-windows-nic.graph] +check_command = "network-windows" + +[network-windows-nic.metrics_filters] +in.value = "$service_name_template$.perfdata.$nic$_in.value" +out.value = "$service_name_template$.perfdata.$nic$_out.value" + +[network-windows-nic.urlparams] +areaAlpha = "0.5" +lineWidth = "2" +min = "0" +title = "$nic$" +yUnitSystem = "binary" + +[network-windows-nic.functions] +in.value = "alias(color($metric$, '#1a7dd7'), 'In (bytes/s)')" +out.value = "alias(color($metric$, '#ff5566'), 'Out (bytes/s)')" diff --git a/templates/ntp.ini b/templates/ntp.ini new file mode 100644 index 0000000..be91992 --- /dev/null +++ b/templates/ntp.ini @@ -0,0 +1,65 @@ +[ntp-offset.graph] +check_command = "ntp_time, ntp_peer" + +[ntp-offset.metrics_filters] +value = "$service_name_template$.perfdata.offset.value" + +[ntp-offset.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +yUnitSystem = "none" + +[ntp-offset.functions] +value = "alias(color(scale($metric$, 1000), '#1a7dd7'), 'Offset (ms)')" + + +[ntp_peer-jitter.graph] +check_command = "ntp_peer" + +[ntp_peer-jitter.metrics_filters] +value = "$service_name_template$.perfdata.jitter.value" + +[ntp_peer-jitter.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[ntp_peer-jitter.functions] +value = "alias(color(scale($metric$, 1000), '#1a7dd7'), 'Jitter (ms)')" + + +[ntp_peer-stratum.graph] +check_command = "ntp_peer" + +[ntp_peer-stratum.metrics_filters] +value = "$service_name_template$.perfdata.stratum.value" + +[ntp_peer-stratum.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[ntp_peer-stratum.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Stratum')" + + +[ntp_peer-truechimers.graph] +check_command = "ntp_peer" + +[ntp_peer-truechimers.metrics_filters] +value = "$service_name_template$.perfdata.truechimers.value" + +[ntp_peer-truechimers.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[ntp_peer-truechimers.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Truechimers')" diff --git a/templates/nwc-interface-usage.ini b/templates/nwc-interface-usage.ini new file mode 100644 index 0000000..8d9cf7a --- /dev/null +++ b/templates/nwc-interface-usage.ini @@ -0,0 +1,58 @@ +[interface-traffic.graph] +check_command = "interface-usage" + +[interface-traffic.metrics_filters] +bpsin = "$service_name_template$.perfdata.$interface$_traffic_in.value" +bpsmaxin = "$service_name_template$.perfdata.$interface$_traffic_in.max" +bpscritin = "$service_name_template$.perfdata.$interface$_traffic_in.crit" +bpswarnin = "$service_name_template$.perfdata.$interface$_traffic_in.warn" + +bpsout = "$service_name_template$.perfdata.$interface$_traffic_out.value" +bpsmaxout = "$service_name_template$.perfdata.$interface$_traffic_out.max" +bpscritout = "$service_name_template$.perfdata.$interface$_traffic_out.crit" +bpswarnout = "$service_name_template$.perfdata.$interface$_traffic_out.warn" + +[interface-traffic.urlparams] +min = "0" +title = "$interface$ traffic" +lineWidth = "2" +yUnitSystem = "si" + +[interface-traffic.functions] +bpsin = "alias(color($metric$, '#1a7dd7'), 'Traffic in (B/s)')" +bpsmaxin = "alias(color($metric$, '#7d7f81'), 'Traffic in max (B/s)')" +bpswarnin = "alias(color($metric$, '#ff8d00'), 'Traffic in warn (B/s)')" +bpscritin = "alias(color($metric$, '#ff0000'), 'Traffic in crit (B/s)')" + +bpsout = "alias(color($metric$, '#0b3c68'), 'Traffic out (B/s)')" +bpsmaxout = "alias(color($metric$, '#45008d'), 'Traffic out max (B/s)')" +bpswarnout = "alias(color($metric$, '#ff8d00'), 'Traffic out warn (B/s)')" +bpscritout = "alias(color($metric$, '#ff0000'), 'Traffic out crit (B/s)')" + + +[interface-usage.graph] +check_command = "interface-usage" + +[interface-usage.metrics_filters] +usagein = "$service_name_template$.perfdata.$interface$_usage_in.value" +usagecritin = "$service_name_template$.perfdata.$interface$_usage_in.crit" +usagewarnin = "$service_name_template$.perfdata.$interface$_usage_in.warn" + +usageout = "$service_name_template$.perfdata.$interface$_usage_out.value" +usagecritout = "$service_name_template$.perfdata.$interface$_usage_out.crit" +usagewarnout = "$service_name_template$.perfdata.$interface$_usage_out.warn" + +[interface-usage.urlparams] +min = "0" +title = "$interface$ usage" +lineWidth = "2" +yUnitSystem = "none" + +[interface-usage.functions] +usagein = "alias(color($metric$, '#1a7dd7'), 'Usage in (%)')" +usagewarnin = "alias(color($metric$, '#ff8d00'), 'Usage in warn (%)')" +usagecritin = "alias(color($metric$, '#ff0000'), 'Usage in crit (%)')" + +usageout = "alias(color($metric$, '#0b3c68'), 'Usage out (%)')" +usagewarnout = "alias(color($metric$, '#ff8d00'), 'Usage out warn (%)')" +usagecritout = "alias(color($metric$, '#ff0000'), 'Usage out crit (%)')" diff --git a/templates/nwc-load.ini b/templates/nwc-load.ini new file mode 100644 index 0000000..83d080f --- /dev/null +++ b/templates/nwc-load.ini @@ -0,0 +1,19 @@ +[load.graph] +check_command = "nwc-load" + +[load.metrics_filters] +value = "$service_name_template$.nwc_health.perfdata.$load$.value" +crit = "$service_name_template$.nwc_health.perfdata.$load$.crit" +warn = "$service_name_template$.nwc_health.perfdata.$load$.warn" + +[load.urlparams] +areaAlpha = "0.5" +lineWidth = "2" +min = "0" +title = "$load$ %" +yUnitSystem = "none" + +[load.functions] +value = "alias(color($metric$, '#1a7dd7'), 'CPU usage (%)')" +crit = "alias(color($metric$, '#ff0000'), 'Crit (%)')" +warn = "alias(color($metric$, '#ff8d00'), 'Warn (%)')" diff --git a/templates/ping.ini b/templates/ping.ini new file mode 100644 index 0000000..7d281d0 --- /dev/null +++ b/templates/ping.ini @@ -0,0 +1,32 @@ +[ping-rta.graph] +check_command = "ping, ping4, ping6, ping-windows" + +[ping-rta.metrics_filters] +rta.value = "$service_name_template$.perfdata.rta.value" + +[ping-rta.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[ping-rta.functions] +rta.value = "alias(color(scale($metric$, 1000), '#1a7dd7'), 'Round trip time (ms)')" + + +[ping-pl.graph] +check_command = "ping, ping4, ping6" + +[ping-pl.metrics_filters] +pl.value = "$service_name_template$.perfdata.pl.value" + +[ping-pl.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[ping-pl.functions] +pl.value = "alias(color($metric$, '#1a7dd7'), 'Packet loss (%)')" diff --git a/templates/procs.ini b/templates/procs.ini new file mode 100644 index 0000000..2fcc374 --- /dev/null +++ b/templates/procs.ini @@ -0,0 +1,15 @@ +[procs.graph] +check_command = "procs, procs-windows" + +[procs.metrics_filters] +value = "$service_name_template$.perfdata.procs.value" + +[procs.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[procs.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Process count')" diff --git a/templates/response-size-hosts.ini b/templates/response-size-hosts.ini new file mode 100644 index 0000000..e8c80cc --- /dev/null +++ b/templates/response-size-hosts.ini @@ -0,0 +1,16 @@ +[response-size.graph] +check_command = "http-host" + +[response-size.metrics_filters] +value = "$host_name_template$.perfdata.size.value" + +[response-size.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "binary" + +[response-size.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Response size (bytes)')" + diff --git a/templates/response-size.ini b/templates/response-size.ini new file mode 100644 index 0000000..7171d07 --- /dev/null +++ b/templates/response-size.ini @@ -0,0 +1,15 @@ +[response-size.graph] +check_command = "http" + +[response-size.metrics_filters] +value = "$service_name_template$.perfdata.size.value" + +[response-size.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "binary" + +[response-size.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Response size (bytes)')" diff --git a/templates/response-time-hosts.ini b/templates/response-time-hosts.ini new file mode 100644 index 0000000..7986733 --- /dev/null +++ b/templates/response-time-hosts.ini @@ -0,0 +1,16 @@ +[response-time.graph] +check_command = "dig-host, dns-host, ftp-host, http-host, imap-host, ldap-host, pgsql-host, smtp-host, ssh-host, tcp-host, udp-host" + +[response-time.metrics_filters] +value = "$host_name_template$.perfdata.time.value" + +[response-time.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[response-time.functions] +value = "alias(color(scale($metric$, 1000), '#1a7dd7'), 'Response time (ms)')" + diff --git a/templates/response-time.ini b/templates/response-time.ini new file mode 100644 index 0000000..222492f --- /dev/null +++ b/templates/response-time.ini @@ -0,0 +1,15 @@ +[response-time.graph] +check_command = "dig, dns, ftp, http, imap, ldap, pgsql, smtp, ssh, tcp, udp" + +[response-time.metrics_filters] +value = "$service_name_template$.perfdata.time.value" + +[response-time.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[response-time.functions] +value = "alias(color(scale($metric$, 1000), '#1a7dd7'), 'Response time (ms)')" diff --git a/templates/snmp-int.ini b/templates/snmp-int.ini new file mode 100644 index 0000000..32a87ad --- /dev/null +++ b/templates/snmp-int.ini @@ -0,0 +1,54 @@ +[interfacetable-traffic.graph] +check_command = "snmp-interface" + +[interfacetable-traffic.metrics_filters] +bpsin = "$service_name_template$.perfdata.$interface$_in_bps.value" +bpsout = "$service_name_template$.perfdata.$interface$_out_bps.value" + +[interfacetable-traffic.urlparams] +min = "0" +title = "Interface $interface$ Traffic" +lineWidth = "2" +yUnitSystem = "si" + +[interfacetable-traffic.functions] +bpsin = "alias(color($metric$, '#1a7dd7'), 'Traffic in (B/s)')" +bpsout = "alias(color($metric$, '#0b3c68'), 'Traffic out (B/s)')" + + +[interfacetable-discard.graph] +check_command = "snmp-interface" + +[interfacetable-discard.metrics_filters] +ppsindiscard = "$service_name_template$.perfdata.$interface$_in_discard.value" +ppsoutdiscard = "$service_name_template$.perfdata.$interface$_out_discard.value" + +[interfacetable-discard.urlparams] +min = "0" +title = "Interface $interface$ Discards" +lineWidth = "2" +yUnitSystem = "si" + +[interfacetable-discard.functions] +ppsindiscard = "alias(color($metric$, '#edb017'), 'Discard in (B/s)')" +ppsoutdiscard = "alias(color($metric$, '#ad7d05'), 'Discard out (B/s)')" + + +[interfacetable-error.graph] +check_command = "snmp-interface" + +[interfacetable-error.metrics_filters] +ppsinerr = "$service_name_template$.perfdata.$interface$_in_error.value" +ppsouterr = "$service_name_template$.perfdata.$interface$_out_error.value" + +[interfacetable-error.urlparams] +min = "0" +title = "Interface $interface$ Errors" +lineWidth = "2" +yUnitSystem = "si" + +[interfacetable-error.functions] +ppsinerr = "alias(color($metric$, '#ff5566'), 'Error in (B/s)')" +ppsouterr = "alias(color($metric$, '#a80000'), 'Error out (B/s)')" + + diff --git a/templates/snmp-load-netsl.ini b/templates/snmp-load-netsl.ini new file mode 100644 index 0000000..85e6df6 --- /dev/null +++ b/templates/snmp-load-netsl.ini @@ -0,0 +1,19 @@ +[load-snmp.graph] +check_command = "snmp-load" + +[load-snmp.metrics_filters] +load15.value = "$service_name_template$.perfdata.load_15_min.value" +load5.value = "$service_name_template$.perfdata.load_5_min.value" +load1.value = "$service_name_template$.perfdata.load_1_min.value" + +[load-snmp.urlparams] +areaAlpha = "0.5" +min = "0" +yUnitSystem = "none" +lineWidth = "2" + +[load-snmp.functions] +load15.value = "alias(color($metric$, '#ff5566'), 'Load 15')" +load5.value = "alias(color($metric$, '#ffaa44'), 'Load 5')" +load1.value = "alias(color($metric$, '#44bb77'), 'Load 1')" + diff --git a/templates/snmp-load-stand.ini b/templates/snmp-load-stand.ini new file mode 100644 index 0000000..f7b11e8 --- /dev/null +++ b/templates/snmp-load-stand.ini @@ -0,0 +1,20 @@ +[load-windows.graph] +check_command = "load-windows" + +[load-windows.metrics_filters] +load.value = "$service_name_template$.perfdata.cpu_prct_used.value" +crit.value = "$service_name_template$.perfdata.cpu_prct_used.crit" +warn.value = "$service_name_template$.perfdata.cpu_prct_used.warn" + +[load-windows.urlparams] +areaAlpha = "0.5" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[load-windows.functions] +load.value = "alias(color($metric$, '#1a7dd7'), 'CPU usage(%)')" +crit.value = "alias(color($metric$, '#ff0000'), 'Crit (%)')" +warn.value = "alias(color($metric$, '#ff8d00'), 'Warn (%)')" + + diff --git a/templates/snmp-memory.ini b/templates/snmp-memory.ini new file mode 100644 index 0000000..8b154da --- /dev/null +++ b/templates/snmp-memory.ini @@ -0,0 +1,23 @@ +[memory.graph] +check_command = "snmp-memory" + +[memory.metrics_filters] +value = "$service_name_template$.perfdata.$mem$.value" +max = "$service_name_template$.perfdata.$mem$.max" +crit = "$service_name_template$.perfdata.$mem$.crit" +warn = "$service_name_template$.perfdata.$mem$.warn" + +[memory.urlparams] +areaAlpha = "0.5" +lineWidth = "2" +min = "0" +title = "Memory $mem$" +yUnitSystem = "binary" + +[memory.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Used (bytes)')" +max = "alias(color($metric$, '#cfd7e6'), 'Size (bytes)')" +crit = "alias(color($metric$, '#ff0000'), 'Crit (bytes)')" +warn = "alias(color($metric$, '#ff8d00'), 'Warn (bytes)')" + + diff --git a/templates/snmp-storage.ini b/templates/snmp-storage.ini new file mode 100644 index 0000000..4b4505f --- /dev/null +++ b/templates/snmp-storage.ini @@ -0,0 +1,21 @@ +[disk.graph] +check_command = "snmp-storage" + +[disk.metrics_filters] +value = "$service_name_template$.perfdata.$disk$.value" +max = "$service_name_template$.perfdata.$disk$.max" +crit = "$service_name_template$.perfdata.$disk$.crit" +warn = "$service_name_template$.perfdata.$disk$.warn" +[disk.urlparams] +areaAlpha = "0.5" +areaMode = "first" +lineWidth = "2" +min = "0" +title = "Disk $disk$" +yUnitSystem = "binary" + +[disk.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Used (bytes)')" +max = "alias(color($metric$, '#cfd7e6'), 'Size (bytes)')" +crit = "alias(color($metric$, '#ff0000'), 'Crit (bytes)')" +warn = "alias(color($metric$, '#ff8d00'), 'Warn (bytes)')" diff --git a/templates/swap.ini b/templates/swap.ini new file mode 100644 index 0000000..535bc69 --- /dev/null +++ b/templates/swap.ini @@ -0,0 +1,17 @@ +[swap.graph] +check_command = "swap, swap-windows" + +[swap.metrics_filters] +value = "$service_name_template$.perfdata.swap.value" +max = "$service_name_template$.perfdata.swap.max" + +[swap.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "binary" + +[swap.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Used (bytes)')" +max = "alias(color($metric$, '#cfd7e6'), 'Size (bytes)')" diff --git a/templates/update.ini b/templates/update.ini new file mode 100644 index 0000000..3b87ea7 --- /dev/null +++ b/templates/update.ini @@ -0,0 +1,34 @@ +[apt.graph] +check_command = "apt" + +[apt.metrics_filters] +critical_updates.value = "$service_name_template$.perfdata.critical_updates.value" +available_upgrades.value = "$service_name_template$.perfdata.available_upgrades.value" + +[apt.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[apt.functions] +critical_updates.value = "alias(color($metric$, '#ff5566'), 'Critical Updates')" +available_upgrades.value = "alias(color($metric$, '#ffaa44'), 'Available Upgrades')" + + +[update-windows.graph] +check_command = "update-windows" + +[update-windows.metrics_filters] +value = "$service_name_template$.perfdata.update.value" + +[update-windows.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[update-windows.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Available Updates')" diff --git a/templates/uptime.ini b/templates/uptime.ini new file mode 100644 index 0000000..588ab2c --- /dev/null +++ b/templates/uptime.ini @@ -0,0 +1,32 @@ +[uptime.graph] +check_command = "mysql_health, uptime-windows" + +[uptime.metrics_filters] +value = "$service_name_template$.perfdata.uptime.value" + +[uptime.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "sec" + +[uptime.functions] +value = "alias(color($metric$, '#1a7dd7'), 'Uptime (s)')" + + +[uptime-snmp.graph] +check_command = "snmp-uptime" + +[uptime-snmp.metrics_filters] +value = "$service_name_template$.perfdata.uptime.value" + +[uptime-snmp.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "sec" + +[uptime-snmp.functions] +value = "alias(color(scale($metric$, 86400), '#1a7dd7'), 'Uptime (s)')" diff --git a/templates/users.ini b/templates/users.ini new file mode 100644 index 0000000..64172b8 --- /dev/null +++ b/templates/users.ini @@ -0,0 +1,15 @@ +[users.graph] +check_command = "users, users-windows" + +[users.metrics_filters] +users = "$service_name_template$.perfdata.users.value" + +[users.urlparams] +areaAlpha = "0.5" +areaMode = "all" +lineWidth = "2" +min = "0" +yUnitSystem = "none" + +[users.functions] +users = "alias(color($metric$, '#1a7dd7'), 'Logged in Users')" |