From 9f39660f50004ca7c49ea171e2a6f199487cd667 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 14 Apr 2024 15:18:42 +0200 Subject: Adding upstream version 1.3.0. Signed-off-by: Daniel Baumann --- .mailmap | 6 + .travis.yml | 28 ++ AUTHORS | 4 + CHANGELOG.md | 61 +++ COPYING | 339 +++++++++++++++++ README.md | 59 +++ RELEASE.md | 63 ++++ application/controllers/CommentsController.php | 17 + application/controllers/ConfigController.php | 45 +++ application/controllers/EventController.php | 216 +++++++++++ application/controllers/EventsController.php | 186 ++++++++++ application/controllers/IndexController.php | 14 + application/forms/Config/BackendConfigForm.php | 112 ++++++ application/forms/Config/GlobalConfigForm.php | 32 ++ application/forms/Config/MonitoringConfigForm.php | 72 ++++ application/forms/Event/EventCommentForm.php | 144 +++++++ application/forms/Events/AckFilterForm.php | 80 ++++ application/forms/Events/SeverityFilterForm.php | 224 +++++++++++ application/locale/de_DE/LC_MESSAGES/eventdb.mo | Bin 0 -> 6301 bytes application/locale/de_DE/LC_MESSAGES/eventdb.po | 413 +++++++++++++++++++++ application/views/helpers/Column.php | 51 +++ application/views/helpers/ColumnHeader.php | 17 + application/views/helpers/Event.php | 12 + application/views/helpers/EventMessage.php | 70 ++++ application/views/scripts/config/index.phtml | 21 ++ application/views/scripts/config/monitoring.phtml | 7 + application/views/scripts/event/index-plain.phtml | 62 ++++ application/views/scripts/event/index.phtml | 174 +++++++++ .../views/scripts/events/details-plain.phtml | 18 + application/views/scripts/events/details.phtml | 50 +++ application/views/scripts/events/index-plain.phtml | 19 + application/views/scripts/events/index.phtml | 93 +++++ application/views/scripts/format/text.phtml | 13 + configuration.php | 46 +++ doc/02-Configuration.md | 53 +++ doc/03-CustomVars.md | 50 +++ doc/09-Security.md | 26 ++ doc/10-Screenshots.md | 24 ++ doc/screenshots/configuration-backend.png | Bin 0 -> 14171 bytes doc/screenshots/configuration-monitoring.png | Bin 0 -> 20968 bytes doc/screenshots/monitoring-actions.png | Bin 0 -> 26487 bytes doc/screenshots/monitoring-detailview.png | Bin 0 -> 70118 bytes doc/screenshots/overview-filtered.png | Bin 0 -> 122651 bytes doc/screenshots/overview-with-details.png | Bin 0 -> 166770 bytes doc/screenshots/overview.png | Bin 0 -> 149458 bytes examples/legacy-filter/column-integration.json | 50 +++ examples/legacy-filter/examples.md | 9 + examples/legacy-filter/full-filter.json | 77 ++++ library/Eventdb/Data/LegacyFilterParser.php | 153 ++++++++ library/Eventdb/Event.php | 120 ++++++ library/Eventdb/Eventdb.php | 184 +++++++++ library/Eventdb/EventdbController.php | 181 +++++++++ library/Eventdb/Hook/DetailviewExtensionHook.php | 124 +++++++ .../Monitoring/DetailviewExtension.php | 81 ++++ .../ProvidedHook/Monitoring/EventdbActionHook.php | 182 +++++++++ .../ProvidedHook/Monitoring/HostActions.php | 15 + .../ProvidedHook/Monitoring/ServiceActions.php | 15 + library/Eventdb/Test/BaseTestCase.php | 27 ++ library/Eventdb/Test/Bootstrap.php | 35 ++ library/Eventdb/Test/PseudoHost.php | 15 + library/Eventdb/Test/PseudoMonitoringBackend.php | 14 + library/Eventdb/Test/PseudoService.php | 16 + library/Eventdb/Web/EventdbOutputFormat.php | 66 ++++ module.info | 8 + phpunit.xml | 18 + public/css/module.less | 285 ++++++++++++++ public/js/module.js | 97 +++++ run.php | 9 + test/bootstrap.php | 16 + test/config/authentication.ini | 0 test/config/config.ini | 0 test/config/resources.ini | 0 .../Eventdb/Data/LegacyFilterParserTest.php | 77 ++++ .../Monitoring/EventdbActionHookTest.php | 186 ++++++++++ test/phpunit-compat.php | 10 + test/setup_vendor.sh | 69 ++++ 76 files changed, 5060 insertions(+) create mode 100644 .mailmap create mode 100644 .travis.yml create mode 100644 AUTHORS create mode 100644 CHANGELOG.md create mode 100644 COPYING create mode 100644 README.md create mode 100644 RELEASE.md create mode 100644 application/controllers/CommentsController.php create mode 100644 application/controllers/ConfigController.php create mode 100644 application/controllers/EventController.php create mode 100644 application/controllers/EventsController.php create mode 100644 application/controllers/IndexController.php create mode 100644 application/forms/Config/BackendConfigForm.php create mode 100644 application/forms/Config/GlobalConfigForm.php create mode 100644 application/forms/Config/MonitoringConfigForm.php create mode 100644 application/forms/Event/EventCommentForm.php create mode 100644 application/forms/Events/AckFilterForm.php create mode 100644 application/forms/Events/SeverityFilterForm.php create mode 100644 application/locale/de_DE/LC_MESSAGES/eventdb.mo create mode 100644 application/locale/de_DE/LC_MESSAGES/eventdb.po create mode 100644 application/views/helpers/Column.php create mode 100644 application/views/helpers/ColumnHeader.php create mode 100644 application/views/helpers/Event.php create mode 100644 application/views/helpers/EventMessage.php create mode 100644 application/views/scripts/config/index.phtml create mode 100644 application/views/scripts/config/monitoring.phtml create mode 100644 application/views/scripts/event/index-plain.phtml create mode 100644 application/views/scripts/event/index.phtml create mode 100644 application/views/scripts/events/details-plain.phtml create mode 100644 application/views/scripts/events/details.phtml create mode 100644 application/views/scripts/events/index-plain.phtml create mode 100644 application/views/scripts/events/index.phtml create mode 100644 application/views/scripts/format/text.phtml create mode 100644 configuration.php create mode 100644 doc/02-Configuration.md create mode 100644 doc/03-CustomVars.md create mode 100644 doc/09-Security.md create mode 100644 doc/10-Screenshots.md create mode 100644 doc/screenshots/configuration-backend.png create mode 100644 doc/screenshots/configuration-monitoring.png create mode 100644 doc/screenshots/monitoring-actions.png create mode 100644 doc/screenshots/monitoring-detailview.png create mode 100644 doc/screenshots/overview-filtered.png create mode 100644 doc/screenshots/overview-with-details.png create mode 100644 doc/screenshots/overview.png create mode 100644 examples/legacy-filter/column-integration.json create mode 100644 examples/legacy-filter/examples.md create mode 100644 examples/legacy-filter/full-filter.json create mode 100644 library/Eventdb/Data/LegacyFilterParser.php create mode 100644 library/Eventdb/Event.php create mode 100644 library/Eventdb/Eventdb.php create mode 100644 library/Eventdb/EventdbController.php create mode 100644 library/Eventdb/Hook/DetailviewExtensionHook.php create mode 100644 library/Eventdb/ProvidedHook/Monitoring/DetailviewExtension.php create mode 100644 library/Eventdb/ProvidedHook/Monitoring/EventdbActionHook.php create mode 100644 library/Eventdb/ProvidedHook/Monitoring/HostActions.php create mode 100644 library/Eventdb/ProvidedHook/Monitoring/ServiceActions.php create mode 100644 library/Eventdb/Test/BaseTestCase.php create mode 100644 library/Eventdb/Test/Bootstrap.php create mode 100644 library/Eventdb/Test/PseudoHost.php create mode 100644 library/Eventdb/Test/PseudoMonitoringBackend.php create mode 100644 library/Eventdb/Test/PseudoService.php create mode 100644 library/Eventdb/Web/EventdbOutputFormat.php create mode 100644 module.info create mode 100644 phpunit.xml create mode 100644 public/css/module.less create mode 100644 public/js/module.js create mode 100644 run.php create mode 100644 test/bootstrap.php create mode 100644 test/config/authentication.ini create mode 100644 test/config/config.ini create mode 100644 test/config/resources.ini create mode 100644 test/php/library/Eventdb/Data/LegacyFilterParserTest.php create mode 100644 test/php/library/Eventdb/ProvidedHook/Monitoring/EventdbActionHookTest.php create mode 100644 test/phpunit-compat.php create mode 100755 test/setup_vendor.sh diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..eedb019 --- /dev/null +++ b/.mailmap @@ -0,0 +1,6 @@ + + + +Eric Lippmann +Eric Lippmann + diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..e0b0b51 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,28 @@ +language: php +php: + - '5.4' + - '5.5' + - '5.6' + - '7.0' + - '7.1' + - nightly + +cache: + directories: + - vendor + +matrix: + fast_finish: true + allow_failures: + - php: nightly + +branches: + only: + - master + - /^v\d/ + +before_script: +- ./test/setup_vendor.sh + +script: + - php vendor/phpunit.phar diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..263d5c2 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,4 @@ +Alexander A. Klimov +Eric Lippmann +Markus Frosch +Michael Friedrich diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0302b1f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,61 @@ +# Change Log + +## [v1.3.0](https://github.com/Icinga/icingaweb2-module-eventdb/tree/v1.3.0) (2018-01-25) +[Full Changelog](https://github.com/Icinga/icingaweb2-module-eventdb/compare/v1.2.0...v1.3.0) + +**Implemented enhancements:** + +- Links in Event text [\#24](https://github.com/Icinga/icingaweb2-module-eventdb/issues/24) +- Severity Widget for fast clicking users [\#23](https://github.com/Icinga/icingaweb2-module-eventdb/issues/23) +- Add default\_filter config option for the menu url [\#27](https://github.com/Icinga/icingaweb2-module-eventdb/pull/27) ([lazyfrosch](https://github.com/lazyfrosch)) +- Add DetailviewExtensionHook for detail and multi-select [\#26](https://github.com/Icinga/icingaweb2-module-eventdb/pull/26) ([lazyfrosch](https://github.com/lazyfrosch)) + +**Fixed bugs:** + +- Detailview extension shown despite customvar not present [\#25](https://github.com/Icinga/icingaweb2-module-eventdb/issues/25) +- Pagination issue [\#22](https://github.com/Icinga/icingaweb2-module-eventdb/issues/22) + +## [v1.2.0](https://github.com/Icinga/icingaweb2-module-eventdb/tree/v1.2.0) (2017-08-17) +[Full Changelog](https://github.com/Icinga/icingaweb2-module-eventdb/compare/v1.1.0...v1.2.0) + +**Implemented enhancements:** + +- Add more output / format options [\#21](https://github.com/Icinga/icingaweb2-module-eventdb/pull/21) ([lazyfrosch](https://github.com/lazyfrosch)) + +## [v1.1.0](https://github.com/Icinga/icingaweb2-module-eventdb/tree/v1.1.0) (2017-08-16) +[Full Changelog](https://github.com/Icinga/icingaweb2-module-eventdb/compare/v1.0.0...v1.1.0) + +**Implemented enhancements:** + +- Add screenshot to README.md [\#1](https://github.com/Icinga/icingaweb2-module-eventdb/issues/1) +- Support EDBC extensions [\#15](https://github.com/Icinga/icingaweb2-module-eventdb/issues/15) +- Event list should be auto-reloading [\#13](https://github.com/Icinga/icingaweb2-module-eventdb/issues/13) +- Add integration to host and service [\#12](https://github.com/Icinga/icingaweb2-module-eventdb/issues/12) +- Add German translation [\#9](https://github.com/Icinga/icingaweb2-module-eventdb/issues/9) +- Popup Tool Tips for Action Buttons in Interface missing [\#7](https://github.com/Icinga/icingaweb2-module-eventdb/issues/7) +- Fuzzy link to hostnames [\#5](https://github.com/Icinga/icingaweb2-module-eventdb/issues/5) +- Add EDBC extensions [\#20](https://github.com/Icinga/icingaweb2-module-eventdb/pull/20) ([lazyfrosch](https://github.com/lazyfrosch)) +- Add detailview extension [\#19](https://github.com/Icinga/icingaweb2-module-eventdb/pull/19) ([lazyfrosch](https://github.com/lazyfrosch)) +- Integration into monitoring [\#16](https://github.com/Icinga/icingaweb2-module-eventdb/pull/16) ([lazyfrosch](https://github.com/lazyfrosch)) +- Improve list and detail UI [\#14](https://github.com/Icinga/icingaweb2-module-eventdb/pull/14) ([lazyfrosch](https://github.com/lazyfrosch)) +- Improve usability of priority/severity widget [\#11](https://github.com/Icinga/icingaweb2-module-eventdb/pull/11) ([lazyfrosch](https://github.com/lazyfrosch)) +- DB repository: add convert rules for event.host\_address [\#6](https://github.com/Icinga/icingaweb2-module-eventdb/pull/6) ([Al2Klimov](https://github.com/Al2Klimov)) + +**Fixed bugs:** + +- Acknowledgement only available in multi selection [\#8](https://github.com/Icinga/icingaweb2-module-eventdb/issues/8) + +**Closed issues:** + +- Configuration for extra types [\#18](https://github.com/Icinga/icingaweb2-module-eventdb/issues/18) +- Add Host Column to Overview [\#2](https://github.com/Icinga/icingaweb2-module-eventdb/issues/2) +- The content of 'Host Address' is not readable [\#4](https://github.com/Icinga/icingaweb2-module-eventdb/issues/4) + +**Merged pull requests:** + +- Add basic phpunit environment [\#17](https://github.com/Icinga/icingaweb2-module-eventdb/pull/17) ([lazyfrosch](https://github.com/lazyfrosch)) +- Add GitHub issue template [\#10](https://github.com/Icinga/icingaweb2-module-eventdb/pull/10) ([dnsmichi](https://github.com/dnsmichi)) + + + +\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* \ No newline at end of file diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..ecbc059 --- /dev/null +++ b/COPYING @@ -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. + + + Copyright (C) + + 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. + + , 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/README.md b/README.md new file mode 100644 index 0000000..4edb9e1 --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# EventDB Module for Icinga Web 2 + +[![Build Status](https://travis-ci.org/Icinga/icingaweb2-module-eventdb.svg?branch=master)](https://travis-ci.org/Icinga/icingaweb2-module-eventdb) +[![Github Tag](https://img.shields.io/github/tag/Icinga/icingaweb2-module-eventdb.svg)](https://github.com/Icinga/icingaweb2-module-eventdb) + +![Icinga Logo](https://www.icinga.com/wp-content/uploads/2014/06/icinga_logo.png) + +* [About](#about) +* [Requirements](#requirements) +* [Getting Started](#getting-started) +* [Documentation](#documentation) +* [License](#license) + +## About + +With the EventDB Module you can browse, comment and acknowledge events collected +by [EventDB](https://git.netways.org/eventdb/eventdb) easily in +[Icinga Web 2](https://www.icinga.org/products/icinga-web-2/). + +![screenshot](doc/screenshots/overview.png) + +Also see [Screenshots](doc/10-Screenshots.md) in documentation. + +## Requirements + +* Icinga Web 2 +* A database with events collected by EventDB + +## Getting started + +Install and enable the module, then go to [Configuration](doc/02-Configuration.md) +to set up the essentials. + +## Documentation + +* [Configuration](doc/02-Configuration.md) +* [Custom variables](doc/03-CustomVars.md) +* [Security](doc/09-Security.md) +* [Screenshots](doc/10-Screenshots.md) + +## License + + Copyright (C) 2016-2017 Icinga Development Team + 2016-2017 Eric Lippmann + 2017 Markus Frosch + + 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. diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000..57cd2ea --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,63 @@ +# Release Workflow + +Specify the release version. + +``` +VERSION=1.3.0 +``` + +## Issues + +Check issues at https://github.com/Icinga/icingaweb2-module-eventdb + +## Authors + +Update the [.mailmap](.mailmap) and [AUTHORS](AUTHORS) files: + +``` +git checkout master +git log --use-mailmap | grep ^Author: | cut -f2- -d' ' | sort | uniq > AUTHORS +``` + +## Update metadata + +Edit and update [module.info](module.info). + +## Changelog + +Update the [CHANGELOG.md](CHANGELOG.md) file. + +Uses [github_changelog_generator](https://github.com/skywinder/github-changelog-generator) + +``` +export CHANGELOG_GITHUB_TOKEN=xxx +github_changelog_generator --future-release v$VERSION +``` + +Check if the file has been updated correctly. + +## Git Tag + +Commit these changes to the "master" branch: + +``` +git commit -v -a -m "Release version $VERSION" +git push origin master +``` + +And tag it with a signed tag: + +``` +git tag -s -m "Version $VERSION" v$VERSION +``` + +Push the tag. + +``` +git push --tags +``` + +## GitHub Release + +Create a new release for the newly created Git tag. +https://github.com/Icinga/icingaweb2-module-eventdb/releases diff --git a/application/controllers/CommentsController.php b/application/controllers/CommentsController.php new file mode 100644 index 0000000..a371c7b --- /dev/null +++ b/application/controllers/CommentsController.php @@ -0,0 +1,17 @@ +redirectNow($this->getRequest()->getUrl()->setPath('eventdb/events/details')); + } +} diff --git a/application/controllers/ConfigController.php b/application/controllers/ConfigController.php new file mode 100644 index 0000000..d812918 --- /dev/null +++ b/application/controllers/ConfigController.php @@ -0,0 +1,45 @@ +assertPermission('config/modules'); + parent::init(); + } + + public function indexAction() + { + $backendConfig = new BackendConfigForm(); + $backendConfig + ->setIniConfig($this->Config()) + ->handleRequest(); + $this->view->backendConfig = $backendConfig; + + $globalConfig = new GlobalConfigForm(); + $globalConfig + ->setIniConfig($this->Config()) + ->handleRequest(); + $this->view->globalConfig = $globalConfig; + + $this->view->tabs = $this->Module()->getConfigTabs()->activate('config'); + } + + public function monitoringAction() + { + $monitoringConfig = new MonitoringConfigForm(); + $monitoringConfig + ->setIniConfig($this->Config()) + ->handleRequest(); + $this->view->form = $monitoringConfig; + $this->view->tabs = $this->Module()->getConfigTabs()->activate('monitoring'); + } +} diff --git a/application/controllers/EventController.php b/application/controllers/EventController.php new file mode 100644 index 0000000..f6a323c --- /dev/null +++ b/application/controllers/EventController.php @@ -0,0 +1,216 @@ +params->getRequired('id'); + + $url = Url::fromRequest(); + + $this->getTabs()->add('event', array( + 'active' => ! $this->isFormatRequest(), + 'title' => $this->translate('Event'), + 'url' => $url->without(array('format')) + ))->extend(new EventdbOutputFormat(array(), array(EventdbOutputFormat::TYPE_TEXT))); + + $columnConfig = $this->Config('columns'); + if (! $columnConfig->isEmpty()) { + $additionalColumns = $columnConfig->keys(); + } else { + $additionalColumns = array(); + } + + $event = $this->getDb() + ->select() + ->from('event'); + + $columns = array_merge($event->getColumns(), $additionalColumns); + + $event->from('event', $columns); + $event->where('id', $eventId); + + $event->applyFilter(Filter::matchAny(array_map( + '\Icinga\Data\Filter\Filter::fromQueryString', + $this->getRestrictions('eventdb/events/filter', 'eventdb/events') + ))); + + $eventData = $event->fetchRow(); + if (! $eventData) { + throw new NotFoundError('Could not find event with id %d', $eventId); + } + + $eventObj = Event::fromData($eventData); + + $groupedEvents = null; + if ($this->getDb()->hasCorrelatorExtensions()) { + $group_leader = (int) $eventObj->group_leader; + if ($group_leader > 0) { + // redirect to group leader + $this->redirectNow(Url::fromPath('eventdb/event', array('id' => $group_leader))); + } + + if ($group_leader === -1) { + // load grouped events, if any + $groupedEvents = $this->getDb() + ->select() + ->from('event') + ->where('group_leader', $eventObj->id) + ->order('ack', 'ASC') + ->order('created', 'DESC'); + } + } + + $comments = null; + $commentForm = null; + if ($this->hasPermission('eventdb/comments')) { + $comments = $this->getDb() + ->select() + ->from('comment', array( + 'id', + 'type', + 'message', + 'created', + 'modified', + 'user' + )) + ->where('event_id', $eventId) + ->order('created', 'DESC'); + + if ($this->hasPermission('eventdb/interact')) { + $commentForm = new EventCommentForm(); + $commentForm + ->setDb($this->getDb()) + ->setFilter(Filter::expression('id', '=', $eventId)); + } + } + + $format = $this->params->get('format'); + if ($format === 'sql') { + $this->sendSqlSummary(array($event, $comments, $groupedEvents)); + } elseif ($this->isApiRequest()) { + $data = new \stdClass; + $data->event = $eventData; + if ($comments !== null) { + $data->comments = $comments; + } + if ($groupedEvents !== null) { + $data->groupedEvents = $groupedEvents; + } + $this->sendJson($data); + } elseif ($this->isTextRequest()) { + $this->view->event = $eventObj; + $this->view->columnConfig = $columnConfig; + $this->view->additionalColumns = $additionalColumns; + $this->view->groupedEvents = $groupedEvents; + $this->view->comments = $comments; + + $this->sendText(null, 'event/index-plain'); + } else { + if ($commentForm !== null) { + $commentForm->handleRequest(); + } + + $this->view->event = $eventObj; + $this->view->columnConfig = $columnConfig; + $this->view->additionalColumns = $additionalColumns; + $this->view->groupedEvents = $groupedEvents; + $this->view->comments = $comments; + $this->view->commentForm = $commentForm; + + $this->view->extensionsHtml = array(); + foreach (Hook::all('Eventdb\DetailviewExtension') as $hook) { + /** @var DetailviewExtensionHook $hook */ + $module = $this->view->escape($hook->getModule()->getName()); + $this->view->extensionsHtml[] = + '
' + . $hook->setView($this->view)->getHtmlForEvent($eventObj) + . '
'; + } + } + } + + /** + * @deprecated redirects to index view now + */ + public function commentsAction() + { + $this->redirectNow( + Url::fromPath( + 'eventdb/event', + array('id' => $this->params->getRequired('id')) + ) + ); + } + + /** + * Action allowing you to be forwarded to host in Icinga monitoring + * + * **But** case insensitive! + */ + public function hostAction() + { + $host = $this->params->getRequired('host'); + + $backend = $this->monitoringBackend(); + + $query = $backend->select() + ->from('hoststatus', array('host_name')) + ->where('host', $host); + + $realHostname = $query->fetchOne(); + + if ($realHostname !== null && $realHostname !== false) { + $this->redirectNow(Url::fromPath('monitoring/host/services', array('host' => $realHostname))); + } else { + throw new NotFoundError('Could not find a hostname matching: %s', $host); + } + } + + /** + * Action allowing you to be forwarded to host in Icinga monitoring + * + * **But** case insensitive! + */ + public function serviceAction() + { + $host = $this->params->getRequired('host'); + $service = $this->params->getRequired('service'); + + $backend = $this->monitoringBackend(); + + $query = $backend->select() + ->from('servicestatus', array('host_name', 'service')) + ->where('host', $host) + ->where('service', $service); + + $realService = $query->fetchRow(); + + if ($realService !== null && $realService !== false) { + $this->redirectNow( + Url::fromPath( + 'monitoring/service/show', + array( + 'host' => $realService->host_name, + 'service' => $realService->service + ) + ) + ); + } else { + throw new NotFoundError('Could not find a service "%s" for host "%s"', $service, $host); + } + } +} diff --git a/application/controllers/EventsController.php b/application/controllers/EventsController.php new file mode 100644 index 0000000..09e5a8d --- /dev/null +++ b/application/controllers/EventsController.php @@ -0,0 +1,186 @@ +view->title = 'EventDB: ' . $this->translate('Events'); + } + + public function indexAction() + { + $this->assertPermission('eventdb/events'); + + $this->getTabs()->add('events', array( + 'active' => ! $this->isFormatRequest(), + 'title' => $this->translate('Events'), + 'url' => Url::fromRequest()->without(array('format')) + ))->extend(new EventdbOutputFormat(array(), array(EventdbOutputFormat::TYPE_TEXT))); + + $columnConfig = $this->Config('columns'); + if ($this->params->has('columns')) { + $additionalColumns = StringHelper::trimSplit($this->params->get('columns')); + } elseif (! $columnConfig->isEmpty()) { + $additionalColumns = $columnConfig->keys(); + } else { + $additionalColumns = array(); + } + + $events = $this->getDb()->select() + ->from('event'); + + $columns = array_merge($events->getColumns(), $additionalColumns); + $events->columns($columns); + + $events->applyFilter(Filter::matchAny(array_map( + '\Icinga\Data\Filter\Filter::fromQueryString', + $this->getRestrictions('eventdb/events/filter', 'eventdb/events') + ))); + + $this->getDb()->filterGroups($events); + + $this->setupPaginationControl($events); + + $this->setupFilterControl( + $events, + array( + 'host_name' => $this->translate('Host'), + 'host_address' => $this->translate('Host Address'), + 'type' => $this->translate('Type'), + 'program' => $this->translate('Program'), + 'facility' => $this->translate('Facility'), + 'priority' => $this->translate('Priority'), + 'message' => $this->translate('Message'), + 'ack' => $this->translate('Acknowledged'), + 'created' => $this->translate('Created') + ), + array('host_name'), + array('columns', 'format') + ); + + $this->setupLimitControl(); + + $this->setupSortControl( + array( + 'host_name' => $this->translate('Host'), + 'host_address' => $this->translate('Host Address'), + 'type' => $this->translate('Type'), + 'program' => $this->translate('Program'), + 'facility' => $this->translate('Facility'), + 'priority' => $this->translate('Priority'), + 'message' => $this->translate('Message'), + 'ack' => $this->translate('Acknowledged'), + 'created' => $this->translate('Created') + ), + $events, + array('created' => 'desc') + ); + + if ($this->view->compact) { + $events->peekAhead(); + } + + if ($this->params->get('format') === 'sql') { + $this->sendSqlSummary($events); + } elseif ($this->isApiRequest()) { + $data = new \stdClass; + $data->events = $events->fetchAll(); + $this->sendJson($data); + exit; + } elseif ($this->isTextRequest()) { + $this->view->columnConfig = $this->Config('columns'); + $this->view->additionalColumns = $additionalColumns; + $this->view->events = $events; + + $this->sendText(null, 'events/index-plain'); + } else { + $this->setAutorefreshInterval(15); + + $severityFilterForm = new SeverityFilterForm(); + $severityFilterForm->handleRequest(); + + $ackFilterForm = new AckFilterForm(); + $ackFilterForm->handleRequest(); + + $this->view->ackFilterForm = $ackFilterForm; + $this->view->columnConfig = $this->Config('columns'); + $this->view->additionalColumns = $additionalColumns; + $this->view->events = $events; + $this->view->severityFilterForm = $severityFilterForm; + } + } + + public function detailsAction() + { + $this->assertPermission('eventdb/events'); + + $url = Url::fromRequest()->without(array('format')); + + $this->getTabs()->add('events', array( + 'active' => ! $this->isFormatRequest(), + 'title' => $this->translate('Events'), + 'url' => $url + ))->extend(new EventdbOutputFormat(array(), array(EventdbOutputFormat::TYPE_TEXT))); + + $events = $this->getDb() + ->select() + ->from('event'); + + $this->getDb()->filterGroups($events); + + $filter = Filter::fromQueryString($url->getQueryString()); + $events->applyFilter($filter); + + $events->applyFilter(Filter::matchAny(array_map( + '\Icinga\Data\Filter\Filter::fromQueryString', + $this->getRestrictions('eventdb/events/filter', 'eventdb/events') + ))); + + if ($this->isApiRequest()) { + $this->sendJson($events->fetchAll()); + } elseif ($this->isTextRequest()) { + $this->view->events = $events->fetchAll(); + $this->view->columnConfig = $this->Config('columns'); + + $this->sendText(null, 'events/details-plain'); + } else { + $commentForm = null; + if ($this->hasPermission('eventdb/interact')) { + $commentForm = new EventCommentForm(); + $commentForm + ->setDb($this->getDb()) + ->setFilter($filter) + ->handleRequest(); + $this->view->commentForm = $commentForm; + } + + $this->view->events = $events->fetchAll(); + $this->view->columnConfig = $this->Config('columns'); + + $this->view->extensionsHtml = array(); + foreach (Hook::all('Eventdb\DetailviewExtension') as $hook) { + /** @var DetailviewExtensionHook $hook */ + $module = $this->view->escape($hook->getModule()->getName()); + $this->view->extensionsHtml[] = + '
' + . $hook->setView($this->view)->getHtmlForEvents($this->view->events) + . '
'; + } + } + } +} diff --git a/application/controllers/IndexController.php b/application/controllers/IndexController.php new file mode 100644 index 0000000..5322936 --- /dev/null +++ b/application/controllers/IndexController.php @@ -0,0 +1,14 @@ +redirectNow('eventdb/events'); + } +} diff --git a/application/forms/Config/BackendConfigForm.php b/application/forms/Config/BackendConfigForm.php new file mode 100644 index 0000000..a9c85c1 --- /dev/null +++ b/application/forms/Config/BackendConfigForm.php @@ -0,0 +1,112 @@ +setSubmitLabel($this->translate('Save')); + } + + /** + * {@inheritdoc} + */ + public function createElements(array $formData) + { + $resources = array(); + foreach (ResourceFactory::getResourceConfigs() as $name => $config) { + if ($config->type === 'db') { + $resources[] = $name; + } + } + + $this->addElement( + 'select', + 'backend_resource', + array( + 'description' => $this->translate('The resource to use'), + 'label' => $this->translate('Resource'), + 'multiOptions' => array_combine($resources, $resources), + 'required' => true + ) + ); + + if (isset($formData['skip_validation']) && $formData['skip_validation']) { + $this->addSkipValidationCheckbox(); + } + } + + /** + * Return whether the given values are valid + * + * @param array $formData The data to validate + * + * @return bool + */ + public function isValid($formData) + { + if (! parent::isValid($formData)) { + return false; + } + + if (($el = $this->getElement('skip_validation')) === null || ! $el->isChecked()) { + $resourceConfig = ResourceFactory::getResourceConfig($this->getValue('backend_resource')); + + if (! $this->isValidEventDbSchema($resourceConfig)) { + if ($el === null) { + $this->addSkipValidationCheckbox(); + } + + return false; + } + } + + return true; + } + + public function isValidEventDbSchema($resourceConfig) + { + try { + $db = ResourceFactory::createResource($resourceConfig); + $db->select()->from('event', array('id'))->fetchOne(); + } catch (Exception $_) { + $this->error($this->translate( + 'Cannot find the EventDB schema. Please verify that the given database ' + . 'contains the schema and that the configured user has access to it.' + )); + return false; + } + return true; + } + + /** + * Add a checkbox to the form by which the user can skip the schema validation + */ + protected function addSkipValidationCheckbox() + { + $this->addElement( + 'checkbox', + 'skip_validation', + array( + 'description' => $this->translate( + 'Check this to not to validate the EventDB schema of the chosen resource' + ), + 'ignore' => true, + 'label' => $this->translate('Skip Validation'), + 'order' => 0 + ) + ); + } +} diff --git a/application/forms/Config/GlobalConfigForm.php b/application/forms/Config/GlobalConfigForm.php new file mode 100644 index 0000000..5b4bf2a --- /dev/null +++ b/application/forms/Config/GlobalConfigForm.php @@ -0,0 +1,32 @@ +setSubmitLabel($this->translate('Save')); + } + + /** + * {@inheritdoc} + */ + public function createElements(array $formData) + { + $this->addElement( + 'text', + 'global_default_filter', + array( + 'description' => $this->translate('Filter to be used by the menu link for EventDB by default'), + 'label' => $this->translate('Default Filter') + ) + ); + } +} diff --git a/application/forms/Config/MonitoringConfigForm.php b/application/forms/Config/MonitoringConfigForm.php new file mode 100644 index 0000000..e1c7638 --- /dev/null +++ b/application/forms/Config/MonitoringConfigForm.php @@ -0,0 +1,72 @@ +setSubmitLabel($this->translate('Save')); + } + + /** + * {@inheritdoc} + */ + public function createElements(array $formData) + { + $this->addElement( + 'text', + 'monitoring_custom_var', + array( + 'description' => $this->translate('Name of the custom variable to enable EventDB integration for (usually "edb")'), + 'label' => $this->translate('Custom Variable') + ) + ); + + $this->addElement( + 'checkbox', + 'monitoring_always_on_host', + array( + 'description' => $this->translate('Always enable the integration on hosts, even when the custom variable is not set'), + 'label' => $this->translate('Always enable for hosts') + ) + ); + + $this->addElement( + 'checkbox', + 'monitoring_always_on_service', + array( + 'description' => $this->translate('Always enable the integration on services, even when the custom variable is not set'), + 'label' => $this->translate('Always enable for services') + ) + ); + + $this->addElement( + 'checkbox', + 'monitoring_detailview_disable', + array( + 'description' => $this->translate('Disable the detail view inside the monitoring module'), + 'label' => $this->translate('Disable the detail view') + ) + ); + + $this->addElement( + 'text', + 'monitoring_detailview_filter', + array( + 'description' => $this->translate('Filter events in the detail view area inside the monitoring module'), + 'label' => $this->translate('Filter the detail view'), + 'value' => 'ack=0', // Also see DetailviewExtension + ) + ); + } +} diff --git a/application/forms/Event/EventCommentForm.php b/application/forms/Event/EventCommentForm.php new file mode 100644 index 0000000..130910c --- /dev/null +++ b/application/forms/Event/EventCommentForm.php @@ -0,0 +1,144 @@ + '')), + array('Errors', array('separator' => '')), + ); + + /** + * {@inheritdoc} + */ + public function init() + { + $this->setSubmitLabel($this->translate('Submit')); + } + + public function setDb(Eventdb $db) + { + $this->db = $db; + return $this; + } + + public function setFilter(Filter $filter) + { + $this->filter = $filter; + return $this; + } + + /** + * {@inheritdoc} + */ + public function createElements(array $formData) + { + $this->addElement( + 'select', + 'type', + array( + 'label' => $this->translate('Type'), + 'multiOptions' => array( + 0 => $this->translate('Comment'), + 1 => $this->translate('Acknowledge'), + 2 => $this->translate('Revoke') + ), + 'required' => true, + 'value' => 1, + ) + ); + $this->addElement( + 'text', + 'comment', + array( + 'label' => $this->translate('Comment'), + 'required' => true + ) + ); + } + + public function onSuccess() + { + $type = $this->getValue('type'); + $comment = $this->getValue('comment'); + $username = $this->Auth()->getUser()->getUsername(); + + $events = $this->db->select()->from('event', array('id'))->applyFilter($this->filter); + + $dbAdapter = $this->db->getDataSource()->getDbAdapter(); + + $dbAdapter->beginTransaction(); + try { + foreach ($events as $event) { + $this->db->insert('comment', array( + 'event_id' => $event->id, + 'type' => $type, + 'message' => $comment, + 'created' => date(Eventdb::DATETIME_FORMAT), + 'modified' => date(Eventdb::DATETIME_FORMAT), + 'user' => $username + )); + + if ($type !== '0') { + $ackFilter = Filter::expression('id', '=', $event->id); + if ($this->db->hasCorrelatorExtensions()) { + $ackFilter = Filter::matchAny($ackFilter, Filter::where('group_leader', $event->id)); + } + $this->db->update('event', array( + 'ack' => $type === '1' ? 1 : 0 + ), $ackFilter); + } + } + $dbAdapter->commit(); + return true; + } catch (Exception $e) { + $dbAdapter->rollback(); + $this->error($e->getMessage()); + return false; + } + } + + /** + * {@inheritdoc} + */ + public function loadDefaultDecorators() + { + parent::loadDefaultDecorators(); + + $this->removeDecorator('FormHints'); + } + + public function addSubmitButton() + { + parent::addSubmitButton(); + + $btn = $this->getElement('btn_submit'); + $btn->removeDecorator('HtmlTag'); + } +} diff --git a/application/forms/Events/AckFilterForm.php b/application/forms/Events/AckFilterForm.php new file mode 100644 index 0000000..829c6ce --- /dev/null +++ b/application/forms/Events/AckFilterForm.php @@ -0,0 +1,80 @@ +setAttrib('class', 'inline ack-filter-form'); + } + + /** + * {@inheritdoc} + */ + public function addSubmitButton() + { + if ((bool) $this->getRequest()->getUrl()->getParams()->get('ack', true)) { + $icon = 'ok'; + $title = $this->translate('Hide acknowledged events'); + } else { + $icon = 'cancel'; + $title = $this->translate('Show also acknowledged events'); + } + + $this->addElements(array( + array( + 'button', + 'btn_submit', + array( + 'class' => 'link-button spinner', + 'decorators' => array( + 'ViewHelper', + array('HtmlTag', array('tag' => 'div', 'class' => 'control-group form-controls')) + ), + 'escape' => false, + 'ignore' => true, + 'label' => $this->getView()->icon($icon) . $this->translate('Ack'), + 'type' => 'submit', + 'title' => $title, + 'value' => $this->translate('Ack') + ) + ) + )); + + return $this; + } + + public function onSuccess() + { + $redirect = clone $this->getRequest()->getUrl(); + $params = $redirect->getParams(); + $modifyFilter = $params->shift('modifyFilter'); + $columns = $params->shift('columns'); + if (! (bool) $this->getRequest()->getUrl()->getParams()->get('ack', true)) { + $params->remove('ack'); + } else { + $redirect->setQueryString( + Filter::fromQueryString($redirect->getQueryString()) + ->andFilter(Filter::expression('ack', '=', 0)) + ->toQueryString() + ); + } + $params = $redirect->getParams(); + if ($modifyFilter) { + $params->add('modifyFilter'); + } + if ($columns) { + $params->add('columns', $columns); + } + $this->setRedirectUrl($redirect); + return true; + } +} diff --git a/application/forms/Events/SeverityFilterForm.php b/application/forms/Events/SeverityFilterForm.php new file mode 100644 index 0000000..bd1ec5e --- /dev/null +++ b/application/forms/Events/SeverityFilterForm.php @@ -0,0 +1,224 @@ +setAttrib('class', 'inline severity-filter-form'); + } + + protected function findPriorities(Filter $filter, $sign, &$target) + { + if ($filter->isEmpty()) { + return; + } + + if ($filter->isChain()) { + /** @var FilterChain $filter */ + foreach ($filter->filters() as $part) { + /** @var Filter $part */ + if (! $part->isEmpty() && $part->isExpression()) { + /** @var FilterMatch $part */ + if (strtolower($part->getColumn()) === 'priority' && $part->getSign() === $sign) { + $expression = $part->getExpression(); + if (is_array($expression)) { + foreach ($expression as $priority) { + $target[(int) $priority] = $part; + } + } else { + $target[(int) $expression] = $part; + } + } + } else { + /** @var FilterChain $part */ + foreach ($part->filters() as $or) { + /** @var FilterExpression $or */ + if (strtolower($or->getColumn()) === 'priority' && $or->getSign() === $sign) { + $expression = $or->getExpression(); + if (is_array($expression)) { + foreach ($expression as $priority) { + $target[(int) $priority] = $or; + } + } else { + $target[(int) $expression] = $or; + } + } + } + } + } + } else { + /** @var FilterMatch $filter */ + if (strtolower($filter->getColumn()) === 'priority' && $filter->getSign() === $sign) { + $expression = $filter->getExpression(); + if (is_array($expression)) { + foreach ($expression as $priority) { + $target[(int) $priority] = $filter; + } + } else { + $target[(int) $expression] = $filter; + } + } + } + } + + /** + * {@inheritdoc} + */ + public function createElements(array $formData) + { + $includedPriorities = array(); + $excludedPriorities = array(); + $params = $this->getRequest()->getUrl()->getParams() + ->without($this->filterEditorParams) + ->without('columns') + ->without('page'); + + $filter = Filter::fromQueryString((string) $params); + + $this->findPriorities($filter, '=', $includedPriorities); + $this->findPriorities($filter, '!=', $excludedPriorities); + + foreach (Event::$priorities as $id => $priority) { + $class = $priority; + if ( + (empty($includedPriorities) or isset($includedPriorities[$id])) + && ! isset($excludedPriorities[$id]) + ) { + $class .= ' active'; + $title = $this->translate('Filter out %s'); + } else { + $title = $this->translate('Filter in %s'); + } + + $label = ucfirst(substr($priority, 0, 1)); + if ($id === 3) { + $label .= substr($priority, 1, 1); + } + $this->addElement( + 'submit', + $priority, + array( + 'class' => $class, + 'label' => $label, + 'title' => sprintf($title, ucfirst($priority)), + ) + ); + } + + $this->includedPriorities = $includedPriorities; + $this->excludedPriorities = $excludedPriorities; + + $this->filter = $filter; + } + + public function onSuccess() + { + $postData = $this->getRequest()->getPost(); + unset($postData['formUID']); + unset($postData['CSRFToken']); + reset($postData); + $priority = Event::getPriorityId(key($postData)); + $redirect = clone $this->getRequest()->getUrl(); + + // convert inclusion to exclusion + if (! empty($this->includedPriorities)) { + if (empty($this->excludedPriorities)) { + $this->excludedPriorities = array(); + } + // set exclusion with for all not included values + foreach (array_keys(Event::$priorities) as $id) { + if (! isset($this->includedPriorities[$id]) + && ! isset($this->excludedPriorities[$id]) + ) { + $this->excludedPriorities[$id] = true; + } + } + // purge from inclusions from filter + if ($this->filter instanceof FilterChain) { + foreach ($this->includedPriorities as $filter) { + if ($filter instanceof Filter) { + /** @var Filter $filter */ + $this->filter = $this->filter->removeId($filter->getId()); + } + } + } + } + + if ($this->filter instanceof FilterChain) { + // purge existing exclusions from a complex filter + foreach ($this->excludedPriorities as $filter) { + if ($filter instanceof Filter) { + /** @var Filter $filter */ + $this->filter = $this->filter->removeId($filter->getId()); + } + } + } elseif (! empty($this->excludedPriorities)) { + // empty the filter - because it only was a simple exclusion + $this->filter = new FilterAnd; + } + + // toggle exclusion + if (isset($this->excludedPriorities[$priority])) { + // in exclusion: just remove + unset($this->excludedPriorities[$priority]); + } else { + // not set: add to exclusion + $this->excludedPriorities[$priority] = true; + } + + $priorityFilter = Filter::matchAll(); + foreach (array_keys($this->excludedPriorities) as $id) { + $priorityFilter->andFilter(Filter::expression('priority', '!=', $id)); + } + + if ($this->filter->isEmpty()) { + // set the Filter + $this->filter = $priorityFilter; + } else { + // append our filter to the rest of the existing Filter + $this->filter = $this->filter->andFilter($priorityFilter); + } + + $redirect->setQueryString($this->filter->toQueryString()); + + $requestParams = $this->getRequest()->getUrl()->getParams(); + $redirectParams = $redirect->getParams(); + foreach ($this->filterEditorParams as $filterEditorParam) { + if ($requestParams->has($filterEditorParam)) { + $redirectParams->add($filterEditorParam); + } + } + if ($requestParams->has('columns')) { + $redirectParams->add('columns', $this->getRequest()->getUrl()->getParam('columns')); + } + $this->setRedirectUrl($redirect); + return true; + } +} diff --git a/application/locale/de_DE/LC_MESSAGES/eventdb.mo b/application/locale/de_DE/LC_MESSAGES/eventdb.mo new file mode 100644 index 0000000..0bcec3c Binary files /dev/null and b/application/locale/de_DE/LC_MESSAGES/eventdb.mo differ diff --git a/application/locale/de_DE/LC_MESSAGES/eventdb.po b/application/locale/de_DE/LC_MESSAGES/eventdb.po new file mode 100644 index 0000000..29b432a --- /dev/null +++ b/application/locale/de_DE/LC_MESSAGES/eventdb.po @@ -0,0 +1,413 @@ +# Icinga Web 2 - Head for multiple monitoring backends. +# Copyright (C) 2018 Icinga Development Team +# This file is distributed under the same license as Eventdb Module. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: Eventdb Module (1.1.0)\n" +"Report-Msgid-Bugs-To: dev@icinga.com\n" +"POT-Creation-Date: 2018-01-25 14:31+0000\n" +"PO-Revision-Date: 2018-01-25 15:32+0100\n" +"Last-Translator: Markus Frosch \n" +"Language: de_DE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Poedit-Basepath: .\n" +"Language-Team: \n" +"X-Generator: Poedit 2.0.5\n" +"X-Poedit-SearchPath-0: .\n" + +#: ../../../../modules/eventdb/application/views/scripts/events/index.phtml:15 +#, php-format +msgctxt "Multi-selection count" +msgid "%s row(s) selected" +msgstr "%s Zeilen ausgewählt" + +#: ../../../../modules/eventdb/application/forms/Events/AckFilterForm.php:44 +#: ../../../../modules/eventdb/application/forms/Events/AckFilterForm.php:47 +msgid "Ack" +msgstr "Bestätigt" + +#: ../../../../modules/eventdb/application/forms/Event/EventCommentForm.php:69 +msgid "Acknowledge" +msgstr "Bestätigen" + +#: ../../../../modules/eventdb/application/views/helpers/Column.php:23 +#: ../../../../modules/eventdb/application/views/scripts/events/index-plain.phtml:10 +#: ../../../../modules/eventdb/application/views/scripts/events/details-plain.phtml:9 +#: ../../../../modules/eventdb/application/views/scripts/events/details.phtml:24 +#: ../../../../modules/eventdb/application/views/scripts/events/index.phtml:72 +#: ../../../../modules/eventdb/application/views/scripts/event/index-plain.phtml:18 +#: ../../../../modules/eventdb/application/views/scripts/event/index-plain.phtml:51 +#: ../../../../modules/eventdb/application/views/scripts/event/index.phtml:27 +#: ../../../../modules/eventdb/application/views/scripts/event/index.phtml:161 +#: ../../../../modules/eventdb/application/controllers/EventsController.php:69 +#: ../../../../modules/eventdb/application/controllers/EventsController.php:87 +msgid "Acknowledged" +msgstr "Bestätigt" + +#: ../../../../modules/eventdb/application/views/scripts/event/index-plain.phtml:9 +#: ../../../../modules/eventdb/application/views/scripts/event/index.phtml:9 +msgid "Acknowledgement" +msgstr "Bestätigung" + +#: ../../../../modules/eventdb/application/views/scripts/event/index.phtml:60 +msgid "Actions" +msgstr "Aktionen" + +#: ../../../../modules/eventdb/application/views/scripts/events/details.phtml:41 +#: ../../../../modules/eventdb/application/views/scripts/event/index.phtml:118 +msgid "Add comment / acknowledge" +msgstr "Kommentar / Bestätigung hinzufügen" + +#: ../../../../modules/eventdb/library/Eventdb/ProvidedHook/Monitoring/EventdbActionHook.php:151 +msgid "All events for host" +msgstr "Alle Ereignisse für Host" + +#: ../../../../modules/eventdb/configuration.php:40 +msgid "Allow to acknowledge and comment events" +msgstr "Erlauben Ereignisse zu Bestätigen und zu Kommentieren" + +#: ../../../../modules/eventdb/configuration.php:35 +msgid "Allow to view comments" +msgstr "Erlauben Kommentare zu sehen" + +#: ../../../../modules/eventdb/configuration.php:30 +msgid "Allow to view events" +msgstr "Erlauben Ereignisse zu sehen" + +#: ../../../../modules/eventdb/application/forms/Config/MonitoringConfigForm.php:40 +msgid "Always enable for hosts" +msgstr "Immer für Hosts aktivieren" + +#: ../../../../modules/eventdb/application/forms/Config/MonitoringConfigForm.php:49 +msgid "Always enable for services" +msgstr "Immer für Services aktivieren" + +#: ../../../../modules/eventdb/application/forms/Config/MonitoringConfigForm.php:39 +msgid "" +"Always enable the integration on hosts, even when the custom variable is not " +"set" +msgstr "" +"Immer bei Interaktionen mit Hosts anzeigen, auch wenn die angepasste " +"Variable nicht gesetzt ist" + +#: ../../../../modules/eventdb/application/forms/Config/MonitoringConfigForm.php:48 +msgid "" +"Always enable the integration on services, even when the custom variable is " +"not set" +msgstr "" +"Immer bei Interaktionen mit Services anzeigen, auch wenn die angepasste " +"Variable nicht gesetzt ist" + +#: ../../../../modules/eventdb/application/views/scripts/events/details.phtml:44 +msgid "At least one event is set to auto-clear." +msgstr "Mindestens ein Ereignis ist für automatische Bestätigung gesetzt." + +#: ../../../../modules/eventdb/application/views/scripts/events/details.phtml:25 +#: ../../../../modules/eventdb/application/views/scripts/events/index.phtml:73 +#: ../../../../modules/eventdb/application/views/scripts/event/index.phtml:28 +#: ../../../../modules/eventdb/application/views/scripts/event/index.phtml:162 +msgid "Auto-Clear" +msgstr "Automatische Bestätigung" + +#: ../../../../modules/eventdb/application/forms/Config/BackendConfigForm.php:86 +msgid "" +"Cannot find the EventDB schema. Please verify that the given database " +"contains the schema and that the configured user has access to it." +msgstr "" +"Kann das EventDB-Schema nicht finden. Bitte stellen Sie sicher, dass die " +"angegebene Datenbankverbindung das Schema enthält und dass der konfigurierte " +"Benutzer Zugriff darauf hat." + +#: ../../../../modules/eventdb/application/forms/Config/BackendConfigForm.php:104 +msgid "Check this to not to validate the EventDB schema of the chosen resource" +msgstr "" +"Häkchen setzen um das EventDB Schema der gewählten Ressource nicht zu " +"überprüfen." + +#: ../../../../modules/eventdb/application/views/scripts/event/index-plain.phtml:8 +#: ../../../../modules/eventdb/application/views/scripts/event/index.phtml:8 +#: ../../../../modules/eventdb/application/forms/Event/EventCommentForm.php:68 +#: ../../../../modules/eventdb/application/forms/Event/EventCommentForm.php:80 +msgid "Comment" +msgstr "Kommentar" + +#: ../../../../modules/eventdb/application/views/scripts/event/index-plain.phtml:28 +#: ../../../../modules/eventdb/application/views/scripts/event/index.phtml:128 +msgid "Comments" +msgstr "Kommentare" + +#: ../../../../modules/eventdb/configuration.php:19 +msgid "Config" +msgstr "Konfiguration" + +#: ../../../../modules/eventdb/configuration.php:18 +#, fuzzy +msgid "Configure EventDB" +msgstr "EventDB Datenbankverbindung einrichten" + +#: ../../../../modules/eventdb/configuration.php:23 +#: ../../../../modules/eventdb/application/views/scripts/config/monitoring.phtml:5 +msgid "Configure integration into the monitoring module" +msgstr "Interaktionen im Monitoring Modul" + +#: ../../../../modules/eventdb/application/views/scripts/config/index.phtml:19 +msgid "Configure module" +msgstr "Modul konfigurieren" + +#: ../../../../modules/eventdb/application/views/scripts/config/index.phtml:7 +msgid "Create a New Resource" +msgstr "Neue Ressource erstellen" + +#: ../../../../modules/eventdb/application/views/scripts/config/index.phtml:14 +msgid "Create a new resource" +msgstr "Neue Ressource erstellen" + +#: ../../../../modules/eventdb/application/controllers/EventsController.php:70 +#: ../../../../modules/eventdb/application/controllers/EventsController.php:88 +msgid "Created" +msgstr "Erstellt" + +#: ../../../../modules/eventdb/application/forms/Config/MonitoringConfigForm.php:31 +msgid "Custom Variable" +msgstr "Angepasste Variable" + +#: ../../../../modules/eventdb/application/views/scripts/config/index.phtml:5 +#, fuzzy +msgid "Database backend" +msgstr "EventDB Datenbankverbindung einrichten" + +#: ../../../../modules/eventdb/application/forms/Config/GlobalConfigForm.php:28 +msgid "Default Filter" +msgstr "Standard Filter" + +#: ../../../../modules/eventdb/application/views/scripts/event/index.phtml:36 +msgid "Details" +msgstr "Details" + +#: ../../../../modules/eventdb/application/forms/Config/MonitoringConfigForm.php:58 +msgid "Disable the detail view" +msgstr "Detailansicht deaktivieren" + +#: ../../../../modules/eventdb/application/forms/Config/MonitoringConfigForm.php:57 +msgid "Disable the detail view inside the monitoring module" +msgstr "Die Detailansicht innerhalb des Monitoring Moduls deaktivieren" + +#: ../../../../modules/eventdb/application/controllers/EventController.php:26 +msgid "Event" +msgstr "Ereignis" + +#: ../../../../modules/eventdb/application/controllers/EventsController.php:22 +#: ../../../../modules/eventdb/application/controllers/EventsController.php:31 +#: ../../../../modules/eventdb/application/controllers/EventsController.php:136 +msgid "Events" +msgstr "Ereignisse" + +# Spezifische Syslog Bezeichnung +#: ../../../../modules/eventdb/application/controllers/EventsController.php:66 +#: ../../../../modules/eventdb/application/controllers/EventsController.php:84 +msgid "Facility" +msgstr "Facility" + +#: ../../../../modules/eventdb/application/forms/Config/MonitoringConfigForm.php:66 +msgid "Filter events in the detail view area inside the monitoring module" +msgstr "Ereignisse der Detailansicht im Monitoring Modul filtern" + +#: ../../../../modules/eventdb/application/forms/Events/SeverityFilterForm.php:117 +#, php-format +msgid "Filter in %s" +msgstr "Filtern nach %s" + +#: ../../../../modules/eventdb/application/forms/Events/SeverityFilterForm.php:115 +#, php-format +msgid "Filter out %s" +msgstr "Filtern ohne %s" + +#: ../../../../modules/eventdb/application/forms/Config/MonitoringConfigForm.php:67 +msgid "Filter the detail view" +msgstr "Detailansicht filtern" + +#: ../../../../modules/eventdb/application/forms/Config/GlobalConfigForm.php:27 +msgid "Filter to be used by the menu link for EventDB by default" +msgstr "Filter für die URL des Menü Eintrages der EventDB" + +#: ../../../../modules/eventdb/library/Eventdb/ProvidedHook/Monitoring/EventdbActionHook.php:134 +msgid "Filtered events" +msgstr "Gefilterte Ereignisse" + +#: ../../../../modules/eventdb/application/views/scripts/event/index-plain.phtml:42 +#: ../../../../modules/eventdb/application/views/scripts/event/index.phtml:147 +msgid "Grouped Events" +msgstr "Gruppierte Ereignisse" + +#: ../../../../modules/eventdb/application/forms/Events/AckFilterForm.php:26 +msgid "Hide acknowledged events" +msgstr "Bestätigte Ereignisse verstecken" + +#: ../../../../modules/eventdb/application/views/scripts/event/index.phtml:65 +#: ../../../../modules/eventdb/application/controllers/EventsController.php:62 +#: ../../../../modules/eventdb/application/controllers/EventsController.php:80 +msgid "Host" +msgstr "Host" + +#: ../../../../modules/eventdb/application/controllers/EventsController.php:63 +#: ../../../../modules/eventdb/application/controllers/EventsController.php:81 +msgid "Host Address" +msgstr "Host Adresse" + +#: ../../../../modules/eventdb/application/views/scripts/event/index.phtml:105 +msgid "Host service status" +msgstr "Host Service Status" + +#: ../../../../modules/eventdb/library/Eventdb/ProvidedHook/Monitoring/DetailviewExtension.php:66 +msgid "Loading" +msgstr "Lade" + +#: ../../../../modules/eventdb/application/views/scripts/event/index-plain.phtml:35 +#: ../../../../modules/eventdb/application/views/scripts/event/index.phtml:50 +#: ../../../../modules/eventdb/application/controllers/EventsController.php:68 +#: ../../../../modules/eventdb/application/controllers/EventsController.php:86 +msgid "Message" +msgstr "Meldung" + +#: ../../../../modules/eventdb/configuration.php:24 +#: ../../../../modules/eventdb/application/views/scripts/event/index.phtml:102 +msgid "Monitoring" +msgstr "Monitoring" + +#: ../../../../modules/eventdb/application/forms/Config/MonitoringConfigForm.php:30 +msgid "" +"Name of the custom variable to enable EventDB integration for (usually \"edb" +"\")" +msgstr "" +"Name der angepassten Variable um die EventDB Integration zu aktivieren " +"(normalerweise \"edb\")" + +#: ../../../../modules/eventdb/application/views/scripts/event/index.phtml:130 +msgid "No comments recorded for this event yet." +msgstr "Keine Kommentare für dieses Ereignis." + +#: ../../../../modules/eventdb/application/views/scripts/events/index-plain.phtml:3 +#: ../../../../modules/eventdb/application/views/scripts/events/index.phtml:33 +msgid "No events recorded yet." +msgstr "Keine Ereignisse gespeichert." + +#: ../../../../modules/eventdb/application/views/scripts/event/index.phtml:63 +msgid "Other events for" +msgstr "Andere Ereignisse für" + +#: ../../../../modules/eventdb/application/views/scripts/events/details.phtml:45 +#: ../../../../modules/eventdb/application/views/scripts/event/index.phtml:122 +msgid "Please only acknowledge manually, if you know what that means." +msgstr "Bitte nur manuell Bestätigen wenn Sie wissen was das bedeutet." + +#: ../../../../modules/eventdb/application/views/scripts/events/index.phtml:6 +msgctxt "Multi-selection help" +msgid "" +"Press and hold the Ctrl key while clicking on rows to select multiple rows " +"or press and hold the Shift key to select a range of rows" +msgstr "" +"Drücken und halten Sie STRG während Sie auf Spalten klicken, um mehrere " +"auszuwählen. Mit der Umschalttaste können Sie Bereiche von Spalten auswählen" + +#: ../../../../modules/eventdb/application/views/scripts/events/index-plain.phtml:9 +#: ../../../../modules/eventdb/application/views/scripts/events/details-plain.phtml:8 +#: ../../../../modules/eventdb/application/views/scripts/event/index-plain.phtml:17 +#: ../../../../modules/eventdb/application/views/scripts/event/index-plain.phtml:50 +#: ../../../../modules/eventdb/application/controllers/EventsController.php:67 +#: ../../../../modules/eventdb/application/controllers/EventsController.php:85 +msgid "Priority" +msgstr "Priorität" + +#: ../../../../modules/eventdb/application/views/scripts/event/index.phtml:75 +#: ../../../../modules/eventdb/application/controllers/EventsController.php:65 +#: ../../../../modules/eventdb/application/controllers/EventsController.php:83 +msgid "Program" +msgstr "Programm" + +#: ../../../../modules/eventdb/application/views/scripts/event/index.phtml:86 +msgid "Program and Host" +msgstr "Programm und Host" + +#: ../../../../modules/eventdb/application/forms/Config/BackendConfigForm.php:40 +msgid "Resource" +msgstr "Ressource" + +#: ../../../../modules/eventdb/configuration.php:45 +msgid "Restrict views to the events that match the filter" +msgstr "Ereignisse mit einem Filter einschränken" + +#: ../../../../modules/eventdb/application/views/scripts/event/index-plain.phtml:10 +#: ../../../../modules/eventdb/application/views/scripts/event/index.phtml:10 +msgid "Revocation" +msgstr "Widerruf" + +#: ../../../../modules/eventdb/application/forms/Event/EventCommentForm.php:70 +msgid "Revoke" +msgstr "Wiederrufen" + +#: ../../../../modules/eventdb/application/forms/Config/BackendConfigForm.php:20 +#: ../../../../modules/eventdb/application/forms/Config/MonitoringConfigForm.php:18 +#: ../../../../modules/eventdb/application/forms/Config/GlobalConfigForm.php:15 +msgid "Save" +msgstr "Speichern" + +#: ../../../../modules/eventdb/application/views/scripts/events/index.phtml:84 +msgid "Show More" +msgstr "Mehr zeigen" + +#: ../../../../modules/eventdb/application/forms/Events/AckFilterForm.php:29 +msgid "Show also acknowledged events" +msgstr "Auch bestätigte Ereignisse anzeigen" + +#: ../../../../modules/eventdb/application/forms/Config/BackendConfigForm.php:107 +msgid "Skip Validation" +msgstr "Überprüfung überspringen" + +#: ../../../../modules/eventdb/application/forms/Event/EventCommentForm.php:42 +msgid "Submit" +msgstr "Absenden" + +#: ../../../../modules/eventdb/library/Eventdb/Web/EventdbOutputFormat.php:49 +msgid "Text" +msgstr "Text" + +#: ../../../../modules/eventdb/application/forms/Config/BackendConfigForm.php:39 +msgid "The resource to use" +msgstr "Die zu benutzenden Ressource" + +#: ../../../../modules/eventdb/application/views/scripts/event/index.phtml:121 +msgid "This event is set to auto-clear." +msgstr "Das Ereignis ist auf automatische Bestätigung gesetzt." + +#: ../../../../modules/eventdb/application/views/scripts/events/index-plain.phtml:7 +#: ../../../../modules/eventdb/application/views/scripts/events/details-plain.phtml:6 +#: ../../../../modules/eventdb/application/views/scripts/event/index-plain.phtml:15 +#: ../../../../modules/eventdb/application/views/scripts/event/index-plain.phtml:48 +msgid "Timestamp" +msgstr "Zeitstempel" + +#: ../../../../modules/eventdb/application/views/scripts/events/index-plain.phtml:12 +#: ../../../../modules/eventdb/application/views/scripts/events/details-plain.phtml:11 +#: ../../../../modules/eventdb/application/views/scripts/event/index-plain.phtml:20 +#: ../../../../modules/eventdb/application/views/scripts/event/index-plain.phtml:53 +#: ../../../../modules/eventdb/application/forms/Event/EventCommentForm.php:66 +#: ../../../../modules/eventdb/application/controllers/EventsController.php:64 +#: ../../../../modules/eventdb/application/controllers/EventsController.php:82 +msgid "Type" +msgstr "Typ" + +#: ../../../../modules/eventdb/application/views/scripts/event/index-plain.phtml:33 +msgid "User" +msgstr "Benutzername" + +#: ../../../../modules/eventdb/library/Eventdb/Eventdb.php:141 +msgid "You need to configure a resource to access the EventDB database first" +msgstr "Sie müssen zuerst die Datenbank der EventDB konfigurieren" + +#~ msgid "Backend" +#~ msgstr "Datenbank" diff --git a/application/views/helpers/Column.php b/application/views/helpers/Column.php new file mode 100644 index 0000000..b343de4 --- /dev/null +++ b/application/views/helpers/Column.php @@ -0,0 +1,51 @@ +$column ? $this->view->icon('ok', $this->view->translate('Acknowledged')) : '-'; + } else { + $renderer = $this->view->columnConfig->get($column, 'renderer', $default); + + switch ($renderer) { + case 'host_url': + $html = $this->view->qlink($event->$column, 'eventdb/event/host', + array('host' => $event->$column)); + break; + case 'service_url': + $html = $this->view->qlink($event->$column, 'eventdb/event/service', + array('service' => $event->$column, 'host' => $event->host_name)); + break; + case 'url': + $html = $this->view->qlink($event->$column, $event->$column); + break; + case 'message': + $html = $this->view->eventMessage($event->$column); + break; + default: + $html = $this->view->escape($event->$column); + break; + } + } + + return '' . $html . ''; + } +} diff --git a/application/views/helpers/ColumnHeader.php b/application/views/helpers/ColumnHeader.php new file mode 100644 index 0000000..3c7f7cd --- /dev/null +++ b/application/views/helpers/ColumnHeader.php @@ -0,0 +1,17 @@ +view->columnConfig->get($columnHeader, 'label', ucwords(str_replace('_', ' ', $columnHeader))); + if ($plain) { + return $header; + } + $htm = ''; + $htm .= $this->view->escape($header); + $htm .= ''; + return $htm; + } +} diff --git a/application/views/helpers/Event.php b/application/views/helpers/Event.php new file mode 100644 index 0000000..f61a793 --- /dev/null +++ b/application/views/helpers/Event.php @@ -0,0 +1,12 @@ +getPurifier()->purify($message); + + // search for URLs and make them a link + $htm = preg_replace_callback( + static::URL_REGEX, + function ($match) { + return sprintf( + '%s', + htmlspecialchars($match[0]), + htmlspecialchars($match[0]) + ); + }, + $htm + ); + + return $htm; + } + + /** + * Get the purifier instance + * + * @return HTMLPurifier + */ + protected function getPurifier() + { + if (self::$purifier === null) { + require_once 'HTMLPurifier/Bootstrap.php'; + require_once 'HTMLPurifier.php'; + require_once 'HTMLPurifier.autoload.php'; + + $config = HTMLPurifier_Config::createDefault(); + $config->set('Core.EscapeNonASCIICharacters', true); + $config->set('HTML.Allowed', Config::module('eventdb')->get( + 'frontend', + 'allowed_html', + 'p,br,b,a[href|target],i,table,tr,td[colspan],div,*[class]' + )); + $config->set('Attr.AllowedFrameTargets', array('_blank')); + $config->set('Cache.DefinitionImpl', null); + self::$purifier = new HTMLPurifier($config); + } + return self::$purifier; + } +} diff --git a/application/views/scripts/config/index.phtml b/application/views/scripts/config/index.phtml new file mode 100644 index 0000000..2c47663 --- /dev/null +++ b/application/views/scripts/config/index.phtml @@ -0,0 +1,21 @@ +
+ +
+
+

translate('Database backend') ?>

+ qlink( + $this->translate('Create a New Resource'), + 'config/createresource', + null, + array( + 'class' => 'button-link', + 'data-base-target' => '_next', + 'icon' => 'plus', + 'title' => $this->translate('Create a new resource'), + ) + ) ?> + + +

translate('Configure module') ?>

+ +
diff --git a/application/views/scripts/config/monitoring.phtml b/application/views/scripts/config/monitoring.phtml new file mode 100644 index 0000000..1f6bbde --- /dev/null +++ b/application/views/scripts/config/monitoring.phtml @@ -0,0 +1,7 @@ +
+ +
+
+

translate('Configure integration into the monitoring module') ?>

+ +
diff --git a/application/views/scripts/event/index-plain.phtml b/application/views/scripts/event/index-plain.phtml new file mode 100644 index 0000000..5b6ea8d --- /dev/null +++ b/application/views/scripts/event/index-plain.phtml @@ -0,0 +1,62 @@ +translate('Comment'), + $this->translate('Acknowledgement'), + $this->translate('Revocation'), +); + +$displayColumns = array_merge(array('program', 'message', 'facility'), $additionalColumns); +?> +translate('Timestamp') ?>: created ?> + +translate('Priority') ?>: getPriority()) ?> +ack ? sprintf(' (%s)', $this->translate('Acknowledged')) : '' ?> + +translate('Type') ?>: getType() ?> + + +columnHeader($col, null, true) ?>: offsetGet($col)) ?> + + + +hasResult()): ?> +[ translate('Comments') ?> ] + + +type] ?>: created ?> + +translate('User') ?>: user ?> + +translate('Message') ?>: message ?> + + + + + +hasResult()): ?> +[ translate('Grouped Events') ?> ] + +event($groupedEventData); +?> +translate('Timestamp') ?>: created ?> + +translate('Priority') ?>: getPriority()) ?> +ack ? sprintf(' (%s)', $this->translate('Acknowledged')) : '' ?> + +translate('Type') ?>: getType() ?> + + +columnHeader($col, null, true) ?>: offsetGet($col)) ?> + + + + + + diff --git a/application/views/scripts/event/index.phtml b/application/views/scripts/event/index.phtml new file mode 100644 index 0000000..83dcf01 --- /dev/null +++ b/application/views/scripts/event/index.phtml @@ -0,0 +1,174 @@ +icon('comment', $this->translate('Comment')), + $this->icon('ok', $this->translate('Acknowledgement')), + $this->icon('cancel', $this->translate('Revocation')) +); + +if (! $this->compact): ?> +
+ tabs ?> +
+ +
+ + + + + column('host_name', $event, array('selectable')) ?> + + + column('host_address', $event, array('selectable')) ?> + +
+
getPriority()) ?>
+
timeAgo(strtotime($event->created)) ?>
+
+ icon($event->getTypeIcon(), $event->getType()) ?> + ack) { echo $this->icon('ok', $this->translate('Acknowledged')); } ?> + group_autoclear) { echo $this->icon('reschedule', $this->translate('Auto-Clear')); } ?> +
+

translate('Details') ?>

+ + + + columnHeader($column) ?> + column($column, $event) ?> + + +
+ message): ?> +

translate('Message') ?>

+
eventMessage($event->message) ?>
+ + + + +

translate('Actions') ?>

+ + + + + + + + + + +
translate('Other events for') ?>qlink( + $this->translate('Host'), + 'eventdb/events', + array('host_name' => $event->host_name), + array( + 'icon' => 'search', + 'class' => 'action-link' + ) + ) ?> + program): ?> + qlink( + $this->translate('Program'), + 'eventdb/events', + array( + 'program' => $event->program, + ), + array( + 'icon' => 'search', + 'class' => 'action-link' + ) + ) ?> + qlink( + $this->translate('Program and Host'), + 'eventdb/events', + array( + 'host_name' => $event->host_name, + 'program' => $event->program, + ), + array( + 'icon' => 'search', + 'class' => 'action-link' + ) + ) ?> + +
translate('Monitoring') ?> + qlink( + $this->translate('Host service status'), + 'eventdb/event/host', + array('host' => $event->host_name), + array( + 'icon' => 'search', + 'class' => 'action-link' + ) + ) ?> +
+ + +

translate('Add comment / acknowledge') ?>

+ group_autoclear): ?> +
+ translate('This event is set to auto-clear.') ?> + translate('Please only acknowledge manually, if you know what that means.') ?> +
+ +
+ + +

translate('Comments') ?>

+ hasResult()): ?> +

translate('No comments recorded for this event yet.') ?>

+ + + + + + + + + + + + +
timeAgo(strtotime($comment->created)) ?>type] ?>escape($comment->user) ?>escape($comment->message) ?>
+ + + hasResult()): ?> +

translate('Grouped Events') ?>

+ + + event($groupedEventData); + ?> + + + + column('host_name', $groupedEvent) ?> + column('program', $groupedEvent) ?> + column('message', $groupedEvent) ?> + + + +
+
getPriority()) ?>
+
timeAgo(strtotime($groupedEvent->created)) ?>
+
+ icon($groupedEvent->getTypeIcon(), $groupedEvent->getType()) ?> + ack) { echo $this->icon('ok', $this->translate('Acknowledged')); } ?> + group_autoclear) { $autoClear = true; echo $this->icon('reschedule', $this->translate('Auto-Clear')); } ?> +
+ + +
+ diff --git a/application/views/scripts/events/details-plain.phtml b/application/views/scripts/events/details-plain.phtml new file mode 100644 index 0000000..d5eb724 --- /dev/null +++ b/application/views/scripts/events/details-plain.phtml @@ -0,0 +1,18 @@ +event($eventData); + $url = $this->url('eventdb/event', array('id' => $event->id)); +?> +translate('Timestamp') ?>: created ?> + +translate('Priority') ?>: getPriority()) ?> +ack ? sprintf(' (%s)', $this->translate('Acknowledged')) : '' ?> + +translate('Type') ?>: getType() ?> + + +columnHeader($col, null, true) ?>: offsetGet($col)) ?> + + + + diff --git a/application/views/scripts/events/details.phtml b/application/views/scripts/events/details.phtml new file mode 100644 index 0000000..ee9f3cd --- /dev/null +++ b/application/views/scripts/events/details.phtml @@ -0,0 +1,50 @@ +compact): +?> +
+ tabs ?> +
+ +
+ + event($eventData); + $url = $this->url('eventdb/event', array('id' => $event->id)); + ?> + + + + column('host_name', $event) ?> + column('program', $event) ?> + column('message', $event) ?> + + +
+
getPriority()) ?>
+
timeAgo(strtotime($event->created)) ?>
+
+ icon($event->getTypeIcon(), $event->getType()) ?> + ack) { echo $this->icon('ok', $this->translate('Acknowledged')); } ?> + group_autoclear) { $autoClear = true; echo $this->icon('reschedule', $this->translate('Auto-Clear')); } ?> +
+ + + + +

translate('Add comment / acknowledge') ?>

+ +
+ translate('At least one event is set to auto-clear.') ?> + translate('Please only acknowledge manually, if you know what that means.') ?> +
+ +
+ +
diff --git a/application/views/scripts/events/index-plain.phtml b/application/views/scripts/events/index-plain.phtml new file mode 100644 index 0000000..55f4c2e --- /dev/null +++ b/application/views/scripts/events/index-plain.phtml @@ -0,0 +1,19 @@ +hasResult()): ?> +translate('No events recorded yet.') ?> +event($eventData); ?> +translate('Timestamp') ?>: created ?> + +translate('Priority') ?>: getPriority()) ?> +ack ? sprintf(' (%s)', $this->translate('Acknowledged')) : '' ?> + +translate('Type') ?>: getType() ?> + + +columnHeader($col, null, true) ?>: offsetGet($col)) ?> + + + + diff --git a/application/views/scripts/events/index.phtml b/application/views/scripts/events/index.phtml new file mode 100644 index 0000000..f1e4da7 --- /dev/null +++ b/application/views/scripts/events/index.phtml @@ -0,0 +1,93 @@ +compact): ?> +
+ tabs ?> + translate( + 'Press and hold the Ctrl key while clicking on rows to select multiple rows or press and hold the Shift key to' + . ' select a range of rows', + 'Multi-selection help' + ); + ?> +
+ translate('%s row(s) selected', 'Multi-selection count'), + '0' + ) ?> +
+ paginator ?> +
+ limiter ?> + sortBox ?> +
+ filterEditor ?> +
+ severityFilterForm ?> + ackFilterForm ?> +
+
+ +
+hasResult()): ?> +

translate('No events recorded yet.') ?>

+
+ + + compact): ?> + + + + + + columnHeader($displayColumn) ?> + + + + + + event($eventData); + $created = $event->created; + $createdTs = strtotime($created); + $url = $this->url('eventdb/event', array('id' => $event->id)); + $classes = array('priority-col', $event->getPriority()); + if ($event->ack) { + $classes[] = 'ack'; + } + ?> + + + + + column($displayColumn, $event) ?> + + + + +
+
getPriority()) ?>
+ +
+ icon($event->getTypeIcon(), $event->getType()) ?> + ack) { echo $this->icon('ok', $this->translate('Acknowledged')); } ?> + group_autoclear) { echo $this->icon('reschedule', $this->translate('Auto-Clear')); } ?> +
+compact && $events->hasMore()): ?> + qlink( + $this->translate('Show More'), + $this->url()->without(array('view', 'limit')), + null, + array( + 'data-base-target' => '_next', + 'class' => 'action-link' + ) + ) ?> + + diff --git a/application/views/scripts/format/text.phtml b/application/views/scripts/format/text.phtml new file mode 100644 index 0000000..647914b --- /dev/null +++ b/application/views/scripts/format/text.phtml @@ -0,0 +1,13 @@ +compact): ?> +
tabs ?>
+ +
+ +
partial($partial . '.phtml', null, $this) ?>
+ +
+ +
diff --git a/configuration.php b/configuration.php new file mode 100644 index 0000000..0147b89 --- /dev/null +++ b/configuration.php @@ -0,0 +1,46 @@ +getConfig(); + +$url = 'eventdb/events'; +if (($default_filter = $config->get('global', 'default_filter')) !== null) { + $url .= '?' . $default_filter; +} + +$section = $this->menuSection('EventDB', array( + 'icon' => 'tasks', + 'priority' => 200, + 'url' => $url, +)); + +$this->provideConfigTab('config', array( + 'title' => $this->translate('Configure EventDB'), + 'label' => $this->translate('Config'), + 'url' => 'config' +)); +$this->provideConfigTab('monitoring', array( + 'title' => $this->translate('Configure integration into the monitoring module'), + 'label' => $this->translate('Monitoring'), + 'url' => 'config/monitoring' +)); + +$this->providePermission( + 'eventdb/events', + $this->translate('Allow to view events') +); + +$this->providePermission( + 'eventdb/comments', + $this->translate('Allow to view comments') +); + +$this->providePermission( + 'eventdb/interact', + $this->translate('Allow to acknowledge and comment events') +); + +$this->provideRestriction( + 'eventdb/events/filter', + $this->translate('Restrict views to the events that match the filter') +); diff --git a/doc/02-Configuration.md b/doc/02-Configuration.md new file mode 100644 index 0000000..0f6b04e --- /dev/null +++ b/doc/02-Configuration.md @@ -0,0 +1,53 @@ +Configuration +============= + +## Database Resource + +To let the module know where the events are stored you have to create an SQL +database resource, with the EventDB schema, and events collected by EventDB. + +Access to the EventDB for the module is handled with normal Icingaweb2 resources. + +Create the resource and go to the config area to select it: + + Configuration -> Modules -> Eventdb -> Backend + +![Configuration Backend](screenshots/configuration-backend.png) + +## Monitoring Integration + +The EventDB module integrates into Icinga Web 2's monitoring module by default, +offering action links in host and service detail views. + +![Configuration Monitoring](screenshots/configuration-monitoring.png) + +### Default actions + +By default, every host and services shows an action link to the event list, +filtered by host name. + +### Custom Variable + +You can configure a custom variable that enables the integration selectively. + +* `_edb` or `vars.edb` will enable the actions only on objects that have the custom var +* `_edb_filter` or `vars.edb_filter` allows you to pre-filter the linked events + +The name of the customvar (`edb`) needs to be configured in the config area of the module. + +Also see [Custom Variables](03-CustomVars.md) documentation. + +### Always show actions + +There are options to always show actions on host or service, even if the custom variable +is not set. + +### Detail view + +Icingaweb adds a new feature in 2.5.0 to allow extra content inside the detail views +of hosts and services. + +By default the view filters for not acknowledged events, and shows you the last, most +critical events first. One can jump to events list or a detail view immediately. + +Custom filters can be added in the config area, also the view can be disabled there. \ No newline at end of file diff --git a/doc/03-CustomVars.md b/doc/03-CustomVars.md new file mode 100644 index 0000000..cfd4b74 --- /dev/null +++ b/doc/03-CustomVars.md @@ -0,0 +1,50 @@ +Custom Variables +================ + +The monitoring integration of this module can use custom variables from the +Icinga context to control display and filtering of the integration. + +Also see [Configuration](02-Configuration.md) on how to configure the features. + +Custom variables control: + +* If the EventDB integration and actions are shown for a host or service +* How the linked results should be filtered + +## Examples + +For Icinga 2: + +```icinga2 +object Host "test" { + import "generic-host" + + address = "127.0.0.1" + + vars.edb = "1" + vars.edb_filter = "priority!=7&priority!=5&priority!=6&ack=0" + // ... +} +``` + +For Icinga 1.x: + +```nagios +define host { + use generic-host + host_name test + + address 127.0.0.1 + + _edb 1 + _edb_filter priority!=7&priority!=5&priority!=6&ack=0 +} +``` + +**Note:** A filter by `host_name` will always be added, unless you have `host_name` as part of your filter. + +## Legacy filters + +The module also supports legacy JSON filters from the icinga-web 1.x EventDB module. + +Please see the `examples` directory of this module for some supported filters. diff --git a/doc/09-Security.md b/doc/09-Security.md new file mode 100644 index 0000000..9aab433 --- /dev/null +++ b/doc/09-Security.md @@ -0,0 +1,26 @@ +Security +============================ + +The EventDB Module provides permissions and restrictions as described below. + +## Permissions + +| Name | Description | +| ---------------- | ----------- | +| eventdb/events | Allow to view events | +| eventdb/comments | Allow to view comments | +| eventdb/interact | Allow to acknowledge and comment events | + +## Restrictions + +| Name | Description | +| ----------------------- | ----------- | +| eventdb/events/filter | Restrict views to the events that match the filter | +| eventdb/comments/filter | Restrict views to the comments that match the filter | + +## Examples + +| eventdb/events/filter | Description | +| -------------------------- | ----------- | +| type!=syslog | Hide the Syslog events from a role | +| type=syslog&program=icinga | Show only Icinga-related Syslog events to a role | diff --git a/doc/10-Screenshots.md b/doc/10-Screenshots.md new file mode 100644 index 0000000..22905d3 --- /dev/null +++ b/doc/10-Screenshots.md @@ -0,0 +1,24 @@ +Screenshots +=========== + +**Overview of Events** + +![Screenshot](screenshots/overview.png) + +**Filtered overview** + +![Screenshot](screenshots/overview-filtered.png) + +**Detail area** + +![Screenshot](screenshots/overview-with-details.png) + +**Monitoring actions** + +![Screenshot](screenshots/monitoring-actions.png) + +**Monitoring detail view** + +For Icinga Web 2 >= 2.5.0 + +![Screenshot](screenshots/monitoring-detailview.png) \ No newline at end of file diff --git a/doc/screenshots/configuration-backend.png b/doc/screenshots/configuration-backend.png new file mode 100644 index 0000000..6de9fba Binary files /dev/null and b/doc/screenshots/configuration-backend.png differ diff --git a/doc/screenshots/configuration-monitoring.png b/doc/screenshots/configuration-monitoring.png new file mode 100644 index 0000000..d6c9e8d Binary files /dev/null and b/doc/screenshots/configuration-monitoring.png differ diff --git a/doc/screenshots/monitoring-actions.png b/doc/screenshots/monitoring-actions.png new file mode 100644 index 0000000..724e0a0 Binary files /dev/null and b/doc/screenshots/monitoring-actions.png differ diff --git a/doc/screenshots/monitoring-detailview.png b/doc/screenshots/monitoring-detailview.png new file mode 100644 index 0000000..e7ceb83 Binary files /dev/null and b/doc/screenshots/monitoring-detailview.png differ diff --git a/doc/screenshots/overview-filtered.png b/doc/screenshots/overview-filtered.png new file mode 100644 index 0000000..d183cd8 Binary files /dev/null and b/doc/screenshots/overview-filtered.png differ diff --git a/doc/screenshots/overview-with-details.png b/doc/screenshots/overview-with-details.png new file mode 100644 index 0000000..53a6008 Binary files /dev/null and b/doc/screenshots/overview-with-details.png differ diff --git a/doc/screenshots/overview.png b/doc/screenshots/overview.png new file mode 100644 index 0000000..99cc82d Binary files /dev/null and b/doc/screenshots/overview.png differ diff --git a/examples/legacy-filter/column-integration.json b/examples/legacy-filter/column-integration.json new file mode 100644 index 0000000..474031f --- /dev/null +++ b/examples/legacy-filter/column-integration.json @@ -0,0 +1,50 @@ +/* + NOTE: this is a JSON file with comments. You might not be able to parse it, depending on the implementation! + + These are the filter features mapped by the edbColumn.js integration. + + See https://git.netways.org/eventdb/eventdb/blob/master/icinga-cronk/EventDB/lib/js/edbColumn.js +*/ +{ + "hostFilter": { + // Note: `data.host` is by default the hostname, unless set in edb_filter + "include_pattern": "{{mapped from data.host}}", + "include_pattern_type": "regexp", + "exclude_pattern_type": "disabled", + "exclude_pattern": false, + "include_set": [], + "exclude_set": [] + }, + "programFilter": { + "include_pattern": false, + "include_pattern_type": "disabled", + "exclude_pattern": false, + "exclude_pattern_type": "disabled", + "include_set": [ /* mapped from data.programInclusion */ ], + "exclude_set": [ /* mapped from data.programExclusion */ ] + }, + "messageFilter": { + "items": [ /* mapped from data.msg */ ] + }, + "misc": { + "hideAck": false + }, + "sourceExclusion": [ /* mapped from data.sourceExclusion */ ], + "priorityExclusion": [ /* mapped from data.priorityExclusion */ ], + "facilityExclusion": [ /* mapped from data.facilityExclusion */ ], + "timespan": { + "from": "{{ mapped from data.startTime }}", + "to": -1 + }, + "display": { + "order": { + "field": "created", + "dir": "desc" + }, + "group": { + "field": null + }, + "count": "id", + "limit": 50 + } +} diff --git a/examples/legacy-filter/examples.md b/examples/legacy-filter/examples.md new file mode 100644 index 0000000..fdcba03 --- /dev/null +++ b/examples/legacy-filter/examples.md @@ -0,0 +1,9 @@ +Legacy Filter JSON examples +=========================== + +Here are some JSON examples, each line is a single filter used in `edb_filter` custom variable. + + { host: 'otherhostname' } + { host: 'specialhostname', priorityExclusion: [] } + { "host": ".*", "programInclusion": ["cloud-monitoring"] } + { programInclusion: ['test-program'] } diff --git a/examples/legacy-filter/full-filter.json b/examples/legacy-filter/full-filter.json new file mode 100644 index 0000000..5d89277 --- /dev/null +++ b/examples/legacy-filter/full-filter.json @@ -0,0 +1,77 @@ +/* + NOTE: this is a JSON file with comments. You might not be able to parse it, depending on the implementation! + + This is the full filter style, that the old EventDB Cronk supported. + + The old monitoring integration *did not* support the exact filter. +*/ +{ + "hostFilter": { + "include_pattern": "test", + "include_pattern_type": "exact", + "exclude_pattern_type": "regexp", + "exclude_pattern": "aa.*b", + "include_set": [ + "test" + ], + "exclude_set": [] + }, + "programFilter": { + "include_pattern": false, + "include_pattern_type": "disabled", + "exclude_pattern": "test", + "exclude_pattern_type": "contains", + "include_set": [ + "" + ], + "exclude_set": [] + }, + "messageFilter": { + "items": [ + { + "type": "exc", + "message": "onlydebug", + "isRegexp": false + }, + { + "type": "inc", + "message": "needthis.*", + "isRegexp": true + } + ] + }, + "misc": { + "hideAck": true + }, + "sourceExclusion": [ + "2", + "3", + "4" + ], + "priorityExclusion": [ + "5", + "6", + "7" + ], + "facilityExclusion": [ + "8", + "9", + "10", + "11" + ], + "timespan": { + "from": 1502362200, + "to": 1502362200 + }, + "display": { + "order": { + "field": "created", + "dir": "desc" + }, + "group": { + "field": null + }, + "count": "id", + "limit": 200 + } +} \ No newline at end of file diff --git a/library/Eventdb/Data/LegacyFilterParser.php b/library/Eventdb/Data/LegacyFilterParser.php new file mode 100644 index 0000000..2907202 --- /dev/null +++ b/library/Eventdb/Data/LegacyFilterParser.php @@ -0,0 +1,153 @@ +host = str_replace('.*', '*', $data->host); + if ($data->host === '*') { + $data->host = null; + } + } else { + $data->host = $host; + } + if ($data->host !== null) { + $filter->andFilter(Filter::expression('host_name', '=', $data->host)); + } + + static::handleArray($filter, $data, 'programInclusion', 'program'); + static::handleArray($filter, $data, 'programExclusion', 'program', '!='); + + static::handleArray($filter, $data, 'priorityExclusion', 'priority', '!='); + static::handleArray($filter, $data, 'sourceExclusion', 'source', '!='); + static::handleArray($filter, $data, 'facilityExclusion', 'facility', '!='); + + // TODO: msg - when really needed + // TODO: startTime - when really needed + + // Note: any other field or data part gets ignored... + + return $filter; + } + + protected static function handleArray(Filter $filter, $data, $property, $filterAttr, $op = '=') + { + if (property_exists($data, $property) && ! empty($data->$property)) { + if ($op === '!=') { + $subFilter = $filter; + } else { + $subFilter = new FilterOr; + } + + /* + if (is_array($data->$property) && count($data->$property) === 1) { + $data->$property = current($data->$property); + } + */ + if (! is_array($data->$property)) { + $data->$property = array($data->$property); + } + foreach ($data->$property as $val) { + $subFilter->addFilter(Filter::expression($filterAttr, $op, $val)); + } + + if ($subFilter !== $filter) { + $filters = $subFilter->filters(); + if ($filter->isChain() && count($filters) > 1) { + $filter->andFilter($subFilter); + } else { + $filter->andFilter(current($filters)); + } + } + } + } + + /** + * @author partially by NikiC https://stackoverflow.com/users/385378/nikic + * @source partially from https://stackoverflow.com/a/20440596/449813 + * + * @param $json string + * + * @return string + */ + public static function fixJSONQuotes($json) + { + // fix unquoted identifiers + $json = preg_replace('/([{,]+)(\s*)([^"]+?)\s*:/', '$1"$3":', $json); + + $regex = <<<'REGEX' +~ + "[^"\\]*(?:\\.|[^"\\]*)*" + (*SKIP)(*F) + | '([^'\\]*(?:\\.|[^'\\]*)*)' +~x +REGEX; + + return preg_replace_callback($regex, function ($matches) { + return '"' . preg_replace('~\\\\.(*SKIP)(*F)|"~', '\\"', $matches[1]) . '"'; + }, $json); + } + + /** + * Basic check if it looks like a JSON filter + * + * @param $string + * + * @return bool + */ + static public function isJsonFilter($string) + { + if (! $string || ! is_string($string)) { + return false; + } + + $string = trim($string); + if (empty($string)) { + return false; + } + + if (preg_match('/^\{.*\}$/s', $string)) { + // looks like JSON data + return true; + } + return false; + } +} diff --git a/library/Eventdb/Event.php b/library/Eventdb/Event.php new file mode 100644 index 0000000..a3bba1a --- /dev/null +++ b/library/Eventdb/Event.php @@ -0,0 +1,120 @@ + 'kernel messages', + 1 => 'user-level messages', + 2 => 'mail system', + 3 => 'system daemons', + 4 => 'security/authorization messages', + 5 => 'messages generated internally by syslogd', + 6 => 'line printer subsystem', + 7 => 'network news subsystem', + 8 => 'UUCP subsystem', + 9 => 'clock daemon', + 10 => 'security/authorization messages', + 11 => 'FTP daemon', + 12 => 'NTP subsystem', + 13 => 'log audit', + 14 => 'log alert', + 15 => 'clock daemon', + 16 => 'local use 0', + 17 => 'local use 1', + 18 => 'local use 2', + 19 => 'local use 3', + 20 => 'local use 4', + 21 => 'local use 5', + 22 => 'local use 6', + 23 => 'local use 7' + ); + + public static $priorities = array( + 0 => 'emergency', + 1 => 'alert', + 2 => 'critical', + 3 => 'error', + 4 => 'warning', + 5 => 'notice', + 6 => 'info', + 7 => 'debug' + ); + + public static $types = array( + 0 => 'syslog', + 1 => 'snmp', + 2 => 'mail' + ); + + public static $typeIcons = array( + '_default' => 'help', + 'syslog' => 'doc-text', + 'snmp' => 'plug', + 'mail' => 'bell', + ); + + public function __construct($data) + { + parent::__construct($data, ArrayObject::ARRAY_AS_PROPS); + } + + public function offsetGet($index) + { + if (! $this->offsetExists($index)) { + return null; + } + $getter = 'get' . ucfirst($index); + if (method_exists($this, $getter)) { + return $this->$getter(); + } + return parent::offsetGet($index); + } + + public function getAck() + { + return (bool) parent::offsetGet('ack'); + } + + public function getFacility() + { + $facility = (int) parent::offsetGet('facility'); + return array_key_exists($facility, static::$facilities) ? static::$facilities[$facility] : $facility; + } + + public function getPriority() + { + $priority = (int) parent::offsetGet('priority'); + return array_key_exists($priority, static::$priorities) ? static::$priorities[$priority] : $priority; + } + + public function getType() + { + $type = (int) parent::offsetGet('type'); + return array_key_exists($type, static::$types) ? static::$types[$type] : $type; + } + + public function getTypeIcon() + { + if (array_key_exists($type = $this->getType(), static::$typeIcons)) { + return static::$typeIcons[$type]; + } else { + return static::$typeIcons['_default']; + } + } + + public static function fromData($data) + { + return new static($data); + } + + public static function getPriorityId($priorityName) + { + $priorities = array_flip(static::$priorities); + return $priorities[$priorityName]; + } +} diff --git a/library/Eventdb/Eventdb.php b/library/Eventdb/Eventdb.php new file mode 100644 index 0000000..b885258 --- /dev/null +++ b/library/Eventdb/Eventdb.php @@ -0,0 +1,184 @@ + 'c', + 'event' => 'e', + ); + + /** + * Default query columns + * + * @var array + */ + protected static $defaultQueryColumns = array( + 'event' => array( + 'id', + 'host_name', + 'host_address', + 'type', + 'facility', + 'priority', + 'program', + 'message', + 'alternative_message', + 'ack', + 'created', + 'modified', + ), + 'comment' => array( + 'id', + 'event_id', + 'type', + 'message', + 'created', + 'modified', + 'user' + ) + ); + + protected static $edbcQueryColumns = array( + 'event' => array( + 'group_active', + 'group_id', + 'group_count', + 'group_leader', + 'group_autoclear', + 'flags', + 'alternative_message' + ) + ); + + /** @var bool */ + protected $hasCorrelatorExtensions = null; + + /** + * Checks if Event repository has EDBC columns + * + * @return bool + */ + public function hasCorrelatorExtensions() + { + if ($this->hasCorrelatorExtensions === null) { + $dba = $this->getDataSource()->getDbAdapter(); + $result = $dba->fetchRow("SHOW COLUMNS FROM `event` LIKE 'group_leader'"); + + $this->hasCorrelatorExtensions = ! ! $result; + } + return $this->hasCorrelatorExtensions; + } + + public function filterGroups(RepositoryQuery $query) + { + if ($this->hasCorrelatorExtensions()) { + $query->addFilter(Filter::matchAny( + Filter::expression('group_leader', '=', -1), + new FilterExpression('group_leader', 'IS', new \Zend_Db_Expr('NULL')) + )); + } + return $this; + } + + /** + * {@inheritdoc} + */ + protected function initializeQueryColumns() + { + $additionalColumns = Config::module('eventdb', 'columns')->keys(); + $queryColumns = static::$defaultQueryColumns; + if ($this->hasCorrelatorExtensions()) { + foreach (static::$edbcQueryColumns as $table => $fields) { + if (array_key_exists($table, $queryColumns)) { + $queryColumns[$table] = array_merge($queryColumns[$table], $fields); + } else { + $queryColumns[$table] = $fields; + } + } + } + if ($additionalColumns !== null) { + $eventColumns = $queryColumns['event']; + $queryColumns['event'] = array_merge($eventColumns, array_diff($additionalColumns, $eventColumns)); + } + return $queryColumns; + } + + /** + * Create and return a new instance of the Eventdb + * + * @param ConfigObject $config The configuration to use, otherwise the module's configuration + * + * @return static + * + * @throws ConfigurationError In case no resource has been configured in the module's configuration + */ + public static function fromConfig(ConfigObject $config = null) + { + if ($config === null) { + $moduleConfig = Config::module('eventdb'); + if (($resourceName = $moduleConfig->get('backend', 'resource')) === null) { + throw new ConfigurationError( + mt('eventdb', 'You need to configure a resource to access the EventDB database first') + ); + } + + $resource = ResourceFactory::create($resourceName); + } else { + $resource = ResourceFactory::createResource($config); + } + + return new static($resource); + } + + /** + * {@inheritdoc} + */ + protected function initializeConversionRules() + { + return array('event' => array('host_address' => 'ip_address')); + } + + /** + * Convert an IP address into its human-readable form + * + * @param string $rawAddress + * + * @return string + */ + protected function retrieveIpAddress($rawAddress) + { + return $rawAddress === null ? null : inet_ntop($rawAddress); + } + + /** + * Convert an IP address into its binary form + * + * @param string $address + * + * @return string + */ + protected function persistIpAddress($address) + { + return $address === null ? null : inet_pton($address); + } +} diff --git a/library/Eventdb/EventdbController.php b/library/Eventdb/EventdbController.php new file mode 100644 index 0000000..538553e --- /dev/null +++ b/library/Eventdb/EventdbController.php @@ -0,0 +1,181 @@ +Auth()->isAuthenticated()) { + foreach ($this->Auth()->getUser()->getRoles() as $role) { + if ($permission !== null && ! in_array($permission, $role->getPermissions())) { + continue; + } + $restrictionsFromRole = $role->getRestrictions($name); + if (empty($restrictionsFromRole)) { + $restrictions = array(); + break; + } else { + if (! is_array($restrictionsFromRole)) { + $restrictionsFromRole = array($restrictionsFromRole); + } + $restrictions = array_merge($restrictions, array_values($restrictionsFromRole)); + } + } + } + return $restrictions; + } + + /** + * Retrieves the Icinga MonitoringBackend + * + * @param string|null $name + * + * @return MonitoringBackend + * @throws IcingaException When monitoring is not enabled + */ + protected function monitoringBackend($name = null) + { + if ($this->monitoringBackend === null) { + if (! Icinga::app()->getModuleManager()->hasEnabled('monitoring')) { + throw new IcingaException('The module "monitoring" must be enabled and configured!'); + } + $this->monitoringBackend = MonitoringBackend::instance($name); + } + return $this->monitoringBackend; + } + + protected function setViewScript($name) + { + $this->_helper->viewRenderer->setNoController(true); + $this->_helper->viewRenderer->setScriptAction($name); + } + + protected function isFormatRequest() + { + return $this->hasParam('format'); + } + + protected function isApiRequest() + { + $format = $this->getParam('format'); + $header = $this->getRequest()->getHeader('Accept'); + + if ($format === 'json' || preg_match('#application/json(;.+)?#', $header)) { + return true; + } else { + return false; + } + } + + protected function isTextRequest() + { + $format = $this->getParam('format'); + if ($format === 'text' || $this->isPlainTextRequest()) { + return true; + } else { + return false; + } + } + + protected function isPlainTextRequest() + { + $header = $this->getRequest()->getHeader('Accept'); + if ($header !== null && preg_match('#text/plain#', $header)) { + return true; + } else { + return false; + } + } + + /** + * Send the user a summary of SQL queries + * + * @param array|QueryInterface $queries + */ + protected function sendSqlSummary($queries) + { + if (! is_array($queries)) { + $queries = array($queries); + } + + $str = ''; + foreach ($queries as $query) { + if ($query !== null) { + $str .= wordwrap($query) . "\n\n"; + } + } + + $this->sendText($str); + } + + /** + * Output JSON data to the requester + * + * @param mixed $data + * @param int $options + * @param int $depth + * + * @throws JsonEncodeException + */ + protected function sendJson($data, $options = 0, $depth = 100) + { + header('Content-Type: application/json'); + + if (defined('JSON_PARTIAL_OUTPUT_ON_ERROR')) { + $options |= JSON_PARTIAL_OUTPUT_ON_ERROR; + } + + $output = json_encode($data, $options, $depth); + if (! $output && json_last_error() !== null) { + throw new JsonEncodeException('JSON error: ' . json_last_error_msg()); + } + echo $output; + exit; + } + + protected function sendText($str, $script = null) + { + if ($this->isPlainTextRequest()) { + if ($script !== null) { + echo $this->view->render($this->getViewScript($script, true)); + } else { + echo $str; + } + exit; + } else { + $this->view->text = $str; + $this->view->partial = $script; + $this->setViewScript('format/text'); + } + } +} diff --git a/library/Eventdb/Hook/DetailviewExtensionHook.php b/library/Eventdb/Hook/DetailviewExtensionHook.php new file mode 100644 index 0000000..9fee702 --- /dev/null +++ b/library/Eventdb/Hook/DetailviewExtensionHook.php @@ -0,0 +1,124 @@ +init(); + } + + /** + * Overwrite this function for hook initialization, e.g. loading the hook's config + */ + protected function init() + { + } + + /** + * Shall return valid HTML to include in the detail view + * + * @param Event $event The event to generate HTML for + * + * @return string + */ + abstract public function getHtmlForEvent(Event $event); + + /** + * Shall return valid HTML to include in the multi-select view for events + * + * @param Event[] $events The events to generate HTML for + * + * @return string + */ + public function getHtmlForEvents($events) + { + return ''; + } + + /** + * Get {@link view} + * + * @return View + */ + public function getView() + { + return $this->view; + } + + /** + * Set {@link view} + * + * @param View $view + * + * @return $this + */ + public function setView($view) + { + $this->view = $view; + return $this; + } + + /** + * Get the module of the derived class + * + * @return Module + */ + public function getModule() + { + if ($this->module === null) { + $class = get_class($this); + if (ClassLoader::classBelongsToModule($class)) { + $this->module = Icinga::app()->getModuleManager()->getModule(ClassLoader::extractModuleName($class)); + } + } + + return $this->module; + } + + /** + * Set the module of the derived class + * + * @param Module $module + * + * @return $this + */ + public function setModule(Module $module) + { + $this->module = $module; + + return $this; + } +} diff --git a/library/Eventdb/ProvidedHook/Monitoring/DetailviewExtension.php b/library/Eventdb/ProvidedHook/Monitoring/DetailviewExtension.php new file mode 100644 index 0000000..e03f401 --- /dev/null +++ b/library/Eventdb/ProvidedHook/Monitoring/DetailviewExtension.php @@ -0,0 +1,81 @@ +hasPermission('eventdb/events')) { + return ''; + } + + $config = static::config(); + + if ($config->get('detailview_disable') === '1') { + return ''; + } + + $actions = clone EventdbActionHook::getActions($object); + if (! $actions->hasRenderableItems()) { + // no actions -> no EventDB + return ''; + } + + $htm = '

EventDB

'; + + $htm .= '
'; + $actions->setLayout(Navigation::LAYOUT_TABS); + $htm .= $actions->render(); + $htm .= '
'; + + $url = Url::fromPath('eventdb/events', array('host_name' => $object->host_name)); + + $customFilter = EventdbActionHook::getCustomFilter($object); + if ($customFilter === null) { + $customFilter = new FilterAnd; + } + $detailview_filter = $config->get('detailview_filter', 'ack=0'); + if ($detailview_filter !== null) { + $customFilter = $customFilter->andFilter(Filter::fromQueryString($detailview_filter)); + } + + $htm .= sprintf( + '
', + $url->with(array( + 'sort' => 'priority', + 'dir' => 'asc', + 'view' => 'compact', + 'limit' => 5, + ))->addFilter($customFilter) + ); + $htm .= '

' . mt('eventdb', 'Loading') . '...

'; + $htm .= '
'; + + return $htm; + } + + protected function eventDb() + { + return Eventdb::fromConfig(); + } + + protected static function config() + { + return Config::module('eventdb')->getSection('monitoring'); + } +} diff --git a/library/Eventdb/ProvidedHook/Monitoring/EventdbActionHook.php b/library/Eventdb/ProvidedHook/Monitoring/EventdbActionHook.php new file mode 100644 index 0000000..1eb968d --- /dev/null +++ b/library/Eventdb/ProvidedHook/Monitoring/EventdbActionHook.php @@ -0,0 +1,182 @@ +hasPermission('eventdb/events')) { + return null; + } + + $objectKey = static::getObjectKey($object); + + // check cache if the filter already have been rendered + if (static::$wantCache && array_key_exists($objectKey, self::$customFilters)) { + return self::$customFilters[$objectKey]; + } + + $config = static::config(); + $custom_var = $config->get('custom_var', null); + + $service = null; + $edb_filter = null; + if ($custom_var !== null && $object instanceof Service) { + $edb_filter = $object->{'_service_' . $custom_var . '_filter'}; + $service = $object->service_description; + } elseif ($custom_var !== null && $object instanceof Host) { + $edb_filter = $object->{'_host_' . $custom_var . '_filter'}; + } + + $customFilter = null; + if ($edb_filter !== null) { + if (LegacyFilterParser::isJsonFilter($edb_filter)) { + try { + $customFilter = LegacyFilterParser::parse( + $edb_filter, + $object->host_name, + $service + ); + } catch (InvalidPropertyException $e) { + Logger::warning($e->getMessage()); + } + } else { + try { + $customFilter = Filter::fromQueryString($edb_filter); + + if (! in_array('host_name', $customFilter->listFilteredColumns())) { + $customFilter = $customFilter->andFilter(Filter::expression('host_name', '=', $object->host_name)); + } + } catch (FilterParseException $e) { + Logger::warning('Could not parse custom EventDB filter: %s (%s)', $edb_filter, $e->getMessage()); + } + } + } + + return self::$customFilters[$objectKey] = $customFilter; + } + + /** + * @param MonitoredObject $object Host or Service to render for + * @param bool $no_cache Only for testing - to avoid caching + * + * @return array|Navigation + */ + public static function getActions(MonitoredObject $object) + { + if (! Auth::getInstance()->hasPermission('eventdb/events')) { + return array(); + } + + $objectKey = static::getObjectKey($object); + + // check cache if the buttons already have been rendered + if (static::$wantCache && array_key_exists($objectKey, self::$cachedNav)) { + return self::$cachedNav[$objectKey]; + } + + $nav = new Navigation(); + + $config = static::config(); + + $custom_var = $config->get('custom_var', null); + + $edb_cv = null; + $always_on = null; + + if ($custom_var !== null && $object instanceof Service) { + $edb_cv = $object->{'_service_' . $custom_var}; + $always_on = $config->get('always_on_service', 0); + } elseif ($custom_var !== null && $object instanceof Host) { + $edb_cv = $object->{'_host_' . $custom_var}; + $always_on = $config->get('always_on_host', 0); + } + + $customFilter = static::getCustomFilter($object); + if ($customFilter !== null) { + $params = UrlParams::fromQueryString($customFilter->toQueryString()); + $nav->addItem( + 'events_filtered', + array( + 'label' => mt('eventdb', 'Filtered events'), + 'url' => Url::fromPath('eventdb/events')->setParams($params), + 'icon' => 'tasks', + 'class' => 'action-link', + 'priority' => 1 + ) + ); + } + + // show access to all events, if (or) + // - custom_var is not configured + // - always_on is configured + // - custom_var is configured and set on object (to any value) + if ($custom_var === null || ! empty($edb_cv) || ! empty($always_on)) { + $nav->addItem( + 'events', + array( + 'label' => mt('eventdb', 'All events for host'), + 'url' => Url::fromPath( + 'eventdb/events', + array( + 'host_name' => $object->host_name, + ) + ), + 'icon' => 'tasks', + 'class' => 'action-link', + 'priority' => 99 + ) + ); + } + + return self::$cachedNav[$objectKey] = $nav; + } + + protected static function getObjectKey(MonitoredObject $object) + { + $type = $object->getType(); + $objectKey = sprintf('%!%', $type, $object->host_name); + if ($type === 'service') { + $objectKey .= '!' . $object->service_description; + } + return $objectKey; + } + + protected static function config() + { + return Config::module('eventdb')->getSection('monitoring'); + } +} diff --git a/library/Eventdb/ProvidedHook/Monitoring/HostActions.php b/library/Eventdb/ProvidedHook/Monitoring/HostActions.php new file mode 100644 index 0000000..caf33e3 --- /dev/null +++ b/library/Eventdb/ProvidedHook/Monitoring/HostActions.php @@ -0,0 +1,15 @@ +app(); + } + + protected function app() + { + if (self::$app === null) { + self::$app = Icinga::app(); + } + + return self::$app; + } +} diff --git a/library/Eventdb/Test/Bootstrap.php b/library/Eventdb/Test/Bootstrap.php new file mode 100644 index 0000000..848b360 --- /dev/null +++ b/library/Eventdb/Test/Bootstrap.php @@ -0,0 +1,35 @@ +getModuleManager() + ->loadModule('eventdb', $basedir) + ->loadModule('monitoring', $basedir . '/vendor/icingaweb2/modules/monitoring'); + + $user = new User('icingaadmin'); + $user->setPermissions(array('*')); + Auth::getInstance()->setAuthenticated($user); + } +} diff --git a/library/Eventdb/Test/PseudoHost.php b/library/Eventdb/Test/PseudoHost.php new file mode 100644 index 0000000..7ebcd8c --- /dev/null +++ b/library/Eventdb/Test/PseudoHost.php @@ -0,0 +1,15 @@ +properties = new \stdClass; + $this->hostVariables = $this->customvars = $vars; + return $this; + } +} diff --git a/library/Eventdb/Test/PseudoMonitoringBackend.php b/library/Eventdb/Test/PseudoMonitoringBackend.php new file mode 100644 index 0000000..3ad6de5 --- /dev/null +++ b/library/Eventdb/Test/PseudoMonitoringBackend.php @@ -0,0 +1,14 @@ +properties = new \stdClass; + $this->serviceVariables = $this->customvars = $vars; + $this->hostVariables = array(); + return $this; + } +} diff --git a/library/Eventdb/Web/EventdbOutputFormat.php b/library/Eventdb/Web/EventdbOutputFormat.php new file mode 100644 index 0000000..ceec1ce --- /dev/null +++ b/library/Eventdb/Web/EventdbOutputFormat.php @@ -0,0 +1,66 @@ +enable = $enable; + $disabled = array_merge(static::$disabledTypes, $disabled); + parent::__construct($disabled); + } + + /** + * {@inheritdoc} + */ + public function getSupportedTypes() + { + $supported = parent::getSupportedTypes(); + + if (in_array(self::TYPE_TEXT, $this->enable)) { + $supported[self::TYPE_TEXT] = array( + 'name' => 'text', + 'label' => mt('eventdb', 'Text'), + 'icon' => 'doc-text', + 'urlParams' => array('format' => 'text'), + ); + } + + return $supported; + } + + public function apply(Tabs $tabs) + { + parent::apply($tabs); + + if ($textTab = $tabs->get(self::TYPE_TEXT)) { + $textTab->setTargetBlank(false); + } + } +} diff --git a/module.info b/module.info new file mode 100644 index 0000000..4461db0 --- /dev/null +++ b/module.info @@ -0,0 +1,8 @@ +Module: eventdb +Version: 1.3.0 +Description: EventDB module +Depends: monitoring>=2.5.0 +Description: Integration to browse and acknowledge event in the EventDB + EventDB is a event storage and search addon to Icinga. + + This module provides a simple access and integration with monitoring. diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..f9b092b --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,18 @@ + + + + + test/php + + + diff --git a/public/css/module.less b/public/css/module.less new file mode 100644 index 0000000..9c86a8f --- /dev/null +++ b/public/css/module.less @@ -0,0 +1,285 @@ +@color-priority-emergency: #ff2200; +@color-priority-emergency-fg: white; +@color-priority-alert: #ff4400; +@color-priority-alert-fg: white; +@color-priority-critical: #ff6600; +@color-priority-critical-fg: white; +@color-priority-error: #ff8800; +@color-priority-error-fg: white; +@color-priority-warning: #ffaa44; // same as @color-warning +@color-priority-warning-fg: #6d572b; +@color-priority-notice: #44bb77; // same as @color-ok; +@color-priority-notice-fg: white; +@color-priority-information: #aaaaff; +@color-priority-information-fg: #32486d; +@color-priority-debug: #ccc; +@color-priority-debug-fg: #333; + +h2 { + margin-top: 1em; +} + +.events-table, .event-summary-table { + border-collapse: separate; + border-spacing: 0 1px; + padding: 0; +} + +.ack-col { + color: @icinga-blue; +} + +.priority-col { + width: 8em; + border-left: 5px solid; + white-space: nowrap; + text-align: center; + margin-right: 1em; + padding: 0.33em 1em; + + &.emergency { + border-left-color: @color-priority-emergency; + background-color: @color-priority-emergency; + color: @color-priority-emergency-fg; + + &.ack { + color: inherit; + background-color: inherit; + } + } + + &.critical { + border-left-color: @color-priority-critical; + background-color: @color-priority-critical; + color: @color-priority-critical-fg; + + &.ack { + color: inherit; + background-color: inherit; + } + } + + &.alert { + border-left-color: @color-priority-alert; + background-color: @color-priority-alert; + color: @color-priority-alert-fg; + + &.ack { + color: inherit; + background-color: inherit; + } + } + + &.error { + border-left-color: @color-priority-error; + background-color: @color-priority-error; + color: @color-priority-error-fg; + + &.ack { + color: inherit; + background-color: inherit; + } + } + + &.warning { + border-left-color: @color-priority-warning; + background-color: @color-priority-warning; + color: @color-priority-warning-fg; + + &.ack { + color: inherit; + background-color: inherit; + } + } + + &.notice { + border-left-color: @color-priority-notice; + } + + &.information, &.info { + border-left-color: @color-priority-information; + } + + &.debug { + border-left-color: @color-priority-debug; + } +} + +.comment-link { + .button-link(); + + margin-top: 0.5em; +} + +.quick-filter-controls { + margin-top: 0.5em; + text-align: center; +} + +.ack-filter-form { + margin-left: 0.5em; +} + +.severity-filter-form { + .control-label-group { + display: none; + } + + .control-group { + display: inline-block; + padding: 0; + } + + .active { + font-weight: bold; + } + + .emergency.active { + background-color: @color-priority-emergency; + border-color: @color-priority-emergency; + color: @color-priority-emergency-fg; + } + + .alert.active { + background-color: @color-priority-alert; + border-color: @color-priority-alert; + color: @color-priority-alert-fg; + } + + .critical.active { + background-color: @color-priority-critical; + border-color: @color-priority-critical; + color: @color-priority-critical-fg; + } + + .error.active { + background-color: @color-priority-error; + border-color: @color-priority-error; + color: @color-priority-error-fg; + } + + .warning.active { + background-color: @color-priority-warning; + border-color: @color-priority-warning; + color: @color-priority-warning-fg; + } + + .notice.active { + background-color: @color-priority-notice; + border-color: @color-priority-notice; + color: @color-priority-notice-fg; + } + + .information.active, .info.active { + background-color: @color-priority-information; + border-color: @color-priority-information; + color: @color-priority-information-fg; + } + + .debug.active { + background-color: @color-priority-debug; + border-color: @color-priority-debug; + color: @color-priority-debug-fg; + } +} + +.events-table, .event-summary-table { + .event-message { + max-width: 60em; + font-family: @font-family-fixed; + } +} + +.event-message { + &.detail { + font-size: 1.2em; + border-left: 5px solid #eee; + padding: 0.66em 0.33em; + } + + a { + font-weight: bold; + } +} + +.event-summary-table { + .event-host_address { + font-family: @font-family-fixed; + font-size: 0.9em; + color: @gray; + } +} + +a.action-link { + color: @icinga-blue !important; + margin-right: 1em; +} + +.name-value-table { + th { + width: 8em; + } +} + +.comments-table { + width: auto; + + tr { + border-bottom: none; + } + + td { + padding: 0.5em 0.5em; + } + + .comment-created { + white-space: nowrap; + } + .comment-type { + max-width: 2em; + } + .comment-message { + min-width: 30em; + font-family: @font-family-fixed; + } +} + +.comment-form { + select { + width: auto; + } + + input[name=comment] { + width: 25em; + } + + input[type=submit] { + padding: 0.2em 0.5em; + } +} + +div.warning { + display: inline-block; + font-size: 1.2em; + font-weight: bold; + border: 1px solid @color-priority-warning; + border-left: 5px solid @color-priority-warning; + margin: 0.5em 0; + padding: 0.3em 0.5em; +} + +.copyable-actions { + margin-bottom: 0.5em; +} + +input[type=submit].disabled, +button.disabled { + border-color: @gray-light !important; + background-color: white !important; + color: @gray-light !important; + + &:hover { + background: @gray-light !important; + color: white; + } +} diff --git a/public/js/module.js b/public/js/module.js new file mode 100644 index 0000000..0678421 --- /dev/null +++ b/public/js/module.js @@ -0,0 +1,97 @@ +/*! Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */ + +;(function(Icinga) { + + var EventDB = function(module) { + this.module = module; + this.initialize(); + }; + + EventDB.prototype = { + initialize: function() { + this.module.on('rendered', this.enableCopyable); + this.module.on('submit', 'form.severity-filter-form', this.severitySubmit); + + var addCSSRule = function(sheet, selector, rules, index) { + if('insertRule' in sheet) { + sheet.insertRule(selector + '{' + rules + '}', index); + } else if('addRule' in sheet) { + sheet.addRule(selector, rules, index); + } else { + this.module.icinga.logger.debug('Can\'t insert CSS rule'); + } + }; + + var sheet = (function() { + var style = document.createElement('style'); + // WebKit hack + style.appendChild(document.createTextNode('')); + document.head.appendChild(style); + return style.sheet; + })(); + + addCSSRule( + sheet, + '#layout.twocols.wide-layout #col1.module-eventdb, #layout.twocols.wide-layout #col1.module-eventdb ~ #col2', + 'width: 50%', + 0 + ); + }, + enableCopyable: function() { + var e = this; + $('.copyable').each(function() { + var $button = $('') + .attr('href', '#') + .addClass('action-link icon icon-globe copyable-button') + .text('Copy text'); + + $button.on('click', function() { + var $el = $(this).parent().siblings('.copyable'); + if ($el) { + e.selectText($el[0]); + document.execCommand('copy'); + setTimeout(function () { + e.clearSelection() + }, 500); + if (icinga) { + icinga.loader.createNotice('info', 'Text copied to clipboard') + } + } + }); + + var $div = $('
').addClass('copyable-actions').append($button); + + $(this).before($div); + }); + }, + selectText: function (text) { + var doc = document, range, selection; + if (doc.body.createTextRange) { + range = document.body.createTextRange(); + range.moveToElementText(text); + range.select(); + } else if (window.getSelection) { + selection = window.getSelection(); + range = document.createRange(); + range.selectNodeContents(text); + selection.removeAllRanges(); + selection.addRange(range); + } + }, + clearSelection: function() { + if (document.selection) { + document.selection.empty(); + } else if (window.getSelection) { + window.getSelection().removeAllRanges(); + } + }, + severitySubmit: function(ev) { + $(ev.currentTarget) + .find('input[type=submit]') + .prop('disabled', true) + .addClass('disabled'); + } + }; + + Icinga.availableModules.eventdb = EventDB; +}(Icinga)); diff --git a/run.php b/run.php new file mode 100644 index 0000000..322b34b --- /dev/null +++ b/run.php @@ -0,0 +1,9 @@ +provideHook('monitoring/HostActions'); +$this->provideHook('monitoring/ServiceActions'); + +$this->provideHook('monitoring/DetailviewExtension'); diff --git a/test/bootstrap.php b/test/bootstrap.php new file mode 100644 index 0000000..0253904 --- /dev/null +++ b/test/bootstrap.php @@ -0,0 +1,16 @@ + 'host_name=testhost', // default filter + '{ host: "test" }' => 'host_name=test', + "{ host: 'test' }" => 'host_name=test', + "{ host: 'otherhostname' }" => 'host_name=otherhostname', + "{ host: 'specialhostname', priorityExclusion: [] }" => 'host_name=specialhostname', + "{ host: 'specialhostname', priorityExclusion: [6,7,8] }" => 'host_name=specialhostname&priority!=6&priority!=7&priority!=8', + '{ "host": "*" }' => '', // doesn't make much sense, but well... + '{ "host": "*", "programInclusion": ["cloud-monitoring"] }' => 'program=cloud-monitoring', + '{ "host": ".*", "programInclusion": ["cloud-monitoring"] }' => 'program=cloud-monitoring', + '{ "host": "myhost.*.example.com" }' => 'host_name=myhost%2A.example.com', + "{ programInclusion: ['test1', 'test2'] }" => 'host_name=testhost&(program=test1|program=test2)', + "{ programInclusion: ['test'] }" => 'host_name=testhost&program=test', + "{ programExclusion: ['test'] }" => 'host_name=testhost&program!=test', + "{ programExclusion: ['test1', 'test2'] }" => 'host_name=testhost&program!=test1&program!=test2', + ); + + public function testFiltersThatContainSomeJson() + { + $filters = array( + ' { host: "test" } ', + ' {} ', + '{}', + "{\n\"multiline\": 1\n}", + ); + foreach ($filters as $filter) { + $this->assertTrue(LegacyFilterParser::isJsonFilter($filter)); + } + } + + public function testFiltersThatDoNotContainJson() + { + $filters = array( + ' {xxxx ', + 1337, + 'sometext', + "{\nbrokenjson\n", + ); + foreach ($filters as $filter) { + $this->assertFalse(LegacyFilterParser::isJsonFilter($filter), 'Filter: ' . $filter); + } + + } + + public function testParsingFilters() + { + foreach ($this->validFilters as $json => $result) { + $this->assertTrue( + LegacyFilterParser::isJsonFilter($json), + 'Should be recognized as JSON filter by isJsonFilter' + ); + + $filter = LegacyFilterParser::parse($json, 'testhost'); + $this->assertEquals( + $result, + $filter->toQueryString(), + 'Resulting URL filter should match for json: ' . $json + ); + } + } +} diff --git a/test/php/library/Eventdb/ProvidedHook/Monitoring/EventdbActionHookTest.php b/test/php/library/Eventdb/ProvidedHook/Monitoring/EventdbActionHookTest.php new file mode 100644 index 0000000..d833d73 --- /dev/null +++ b/test/php/library/Eventdb/ProvidedHook/Monitoring/EventdbActionHookTest.php @@ -0,0 +1,186 @@ +setupConfiguration(null, null, null); + + $nav = EventdbActionHook::getActions($this->buildHost(null, null)); + + $items = $nav->getItems(); + $this->assertCount(1, $items); + + /** @var NavigationItem $navObj */ + $navObj = current($items); + + $this->assertEquals('host_name=testhost', $navObj->getUrl()->getQueryString()); + } + + public function testHostWithoutVarsAndNormalConfig() + { + $this->setupConfiguration(); + + $nav = EventdbActionHook::getActions($this->buildHost(null, null)); + + $this->assertCount(0, $nav->getItems()); + } + + public function testHostWithVars() + { + $this->setupConfiguration(); + + $nav = EventdbActionHook::getActions($this->buildHost()); + + $items = $nav->getItems(); + $this->assertCount(1, $items); + + /** @var NavigationItem $navObj */ + $navObj = current($items); + + $this->assertEquals('host_name=testhost', $navObj->getUrl()->getQueryString()); + } + + public function testHostWithVarsAlwaysOn() + { + $this->setupConfiguration('edb', '1'); + + $nav = EventdbActionHook::getActions($this->buildHost(null, 'othervar')); + + $this->assertCount(1, $nav->getItems()); + } + + public function testServiceWithVarsAlwaysOn() + { + $this->setupConfiguration('edb', null, '1'); + + $nav = EventdbActionHook::getActions($this->buildService(null, 'othervar')); + + $this->assertCount(1, $nav->getItems()); + } + + public function testHostWithLegacyFilter() + { + $this->setupConfiguration(); + + $nav = EventdbActionHook::getActions($this->buildHost("{ host: 'test2', programInclusion: 'test2' }")); + + $items = $nav->getItems(); + $this->assertCount(2, $items); + + /** @var NavigationItem $navObj */ + $navObj = current($items); + + $this->assertEquals('host_name=test2&program=test2', $navObj->getUrl()->getQueryString()); + } + + public function testHostWithFilter() + { + $this->setupConfiguration(); + + $nav = EventdbActionHook::getActions($this->buildHost("program=test3")); + + $items = $nav->getItems(); + $this->assertCount(2, $items); + + /** @var NavigationItem $navObj */ + $navObj = current($items); + + $this->assertEquals("program=test3&host_name=testhost", $navObj->getUrl()->getQueryString()); + } + + public function testHostWithFilterThatFiltersHost() + { + $this->setupConfiguration(); + + $nav = EventdbActionHook::getActions($this->buildHost("host_name=test3&program=test3")); + + $items = $nav->getItems(); + $this->assertCount(2, $items); + + /** @var NavigationItem $navObj */ + $navObj = current($items); + + $this->assertEquals("host_name=test3&program=test3", $navObj->getUrl()->getQueryString()); + } + + protected function configure($settings = array()) + { + $config = Config::module('eventdb'); + $section = $config->getSection('monitoring'); + foreach ($settings as $key => $val) { + $section->$key = $val; + } + $config->setSection('monitoring', $section); + + // NOTE: we need to save here, because Config::module always load config from disk + $config->saveIni(); + + return $this; + } + + protected function setupConfiguration($custom_var = 'edb', $always_host = null, $always_service = null) + { + $this->configure(array( + 'custom_var' => $custom_var, + 'always_on_host' => $always_host, + 'always_on_service' => $always_service, + )); + } + + protected function monitoringBackend() + { + return PseudoMonitoringBackend::dummy(); + } + + protected function buildHost($plainFilter = null, $custom_var = 'edb') + { + $host = new PseudoHost($this->monitoringBackend(), 'testhost'); + $host->host_name = 'testhost'; + + $vars = array(); + if ($custom_var !== null) { + $vars[$custom_var] = '1'; + } + if ($plainFilter !== null) { + $vars[$custom_var . '_filter'] = $plainFilter; + } + $host->provideCustomVars($vars); + + return $host; + } + + protected function buildService($plainFilter = null, $custom_var = 'edb') + { + $service = new PseudoService($this->monitoringBackend(), 'testhost', 'test'); + $service->host_name = 'testhost'; + $service->service_description = 'testhost'; + + $vars = array(); + if ($custom_var !== null) { + $vars[$custom_var] = '1'; + } + if ($plainFilter !== null) { + $vars[$custom_var . '_filter'] = $plainFilter; + } + $service->provideCustomVars($vars); + + return $service; + } +} diff --git a/test/phpunit-compat.php b/test/phpunit-compat.php new file mode 100644 index 0000000..2b1be3a --- /dev/null +++ b/test/phpunit-compat.php @@ -0,0 +1,10 @@ +