├── .github ├── ISSUE_TEMPLATE.md └── workflows │ ├── build-latest.yml │ ├── build-release.yml │ └── recommend-base.yml ├── .gitignore ├── CHANGELOG.md ├── Dockerfile ├── Gemfile ├── LICENSE ├── README.md ├── RELEASE.md ├── assets ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 ├── images │ ├── favicon.png │ ├── icinga-hero-background.jpg │ ├── logo.png │ ├── logo_icinga-inv.png │ ├── logo_icinga_big.png │ └── logo_icinga_big_winter.png ├── javascripts │ ├── Chart.bundle.js │ ├── Chart.bundle.min.js │ ├── application.coffee │ ├── d3-3.2.8.js │ ├── dashing.gridster.coffee │ ├── gridster │ │ ├── jquery.gridster.js │ │ ├── jquery.gridster.min.js │ │ └── jquery.leanModal.min.js │ ├── jquery.knob.js │ └── rickshaw-1.4.3.min.js └── stylesheets │ ├── application.scss │ ├── font-awesome.css │ ├── jquery.gridster.css │ └── jquery.gridster.min.css ├── config.ru ├── config ├── .gitignore └── icinga2.json ├── dashboards ├── icinga2.erb └── layout.erb ├── docker └── start.sh ├── jobs ├── buzzwords.rb ├── convergence.rb ├── icinga2.rb └── sample.rb ├── lib └── icinga2.rb ├── pki ├── README.md ├── ca.crt ├── icinga2a.crt └── icinga2a.key ├── public ├── 404.html ├── dashing_icinga2_overview.png ├── dashing_icon.png └── favicon.ico ├── restart-dashing ├── test └── icinga2.rb ├── tools ├── logrotate │ └── dashing-icinga2 └── systemd │ └── dashing-icinga2.service └── widgets ├── chartjs ├── chartjs.coffee └── chartjs.html ├── clock ├── clock.coffee ├── clock.html └── clock.scss ├── comments ├── comments.coffee ├── comments.html └── comments.scss ├── graph ├── graph.coffee ├── graph.html └── graph.scss ├── iframe ├── iframe.coffee ├── iframe.html └── iframe.scss ├── image ├── image.coffee ├── image.html └── image.scss ├── list ├── list.coffee ├── list.html └── list.scss ├── meter ├── meter.coffee ├── meter.html └── meter.scss ├── number ├── number.coffee ├── number.html └── number.scss ├── roundchartjs ├── roundchartjs.coffee └── roundchartjs.html ├── showmon ├── showmon.coffee ├── showmon.html └── showmon.scss ├── simplelist ├── simplelist.coffee ├── simplelist.html └── simplelist.scss ├── simplemon ├── simplemon.coffee ├── simplemon.html └── simplemon.scss ├── table ├── table.coffee ├── table.html └── table.scss └── text ├── text.coffee ├── text.html └── text.scss /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | ## Expected Behavior 16 | 17 | 18 | 19 | ## Current Behavior 20 | 21 | 22 | 23 | ## Possible Solution 24 | 25 | 26 | 27 | ## Steps to Reproduce (for bugs) 28 | 29 | 30 | 1. 31 | 2. 32 | 3. 33 | 4. 34 | 35 | ## Context 36 | 37 | 38 | 39 | ## Your Environment 40 | 41 | * Dashing version (`gem list --local dashing`): 42 | * Ruby version (`ruby -V`): 43 | * Version of this project (tarball name, tag name or `git show -1`): 44 | * Modifications to this project, if any (`git diff`): 45 | * Operating System and version: 46 | * Client browser and version: 47 | 48 | -------------------------------------------------------------------------------- /.github/workflows/build-latest.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | 8 | jobs: 9 | docker: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | - name: Set up QEMU 16 | uses: docker/setup-qemu-action@v1 17 | - name: Set up Docker buildx 18 | uses: docker/setup-buildx-action@v1 19 | - name: Login to DockerHub 20 | uses: docker/login-action@v1 21 | with: 22 | username: ${{ secrets.DOCKERHUB_USERNAME }} 23 | password: ${{ secrets.DOCKERHUB_TOKEN }} 24 | - name: Build and push image(s) 25 | uses: docker/build-push-action@v2 26 | with: 27 | push: true 28 | context: . 29 | platforms: linux/amd64 30 | tags: dbodky/dashing-icinga2:latest 31 | -------------------------------------------------------------------------------- /.github/workflows/build-release.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | docker: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | - name: Set up QEMU 15 | uses: docker/setup-qemu-action@v1 16 | - name: Set up Docker buildx 17 | uses: docker/setup-buildx-action@v1 18 | - name: Login to DockerHub 19 | uses: docker/login-action@v1 20 | with: 21 | username: ${{ secrets.DOCKERHUB_USERNAME }} 22 | password: ${{ secrets.DOCKERHUB_TOKEN }} 23 | - name: Build and push image(s) 24 | uses: docker/build-push-action@v2 25 | with: 26 | push: true 27 | context: . 28 | platforms: linux/amd64 29 | tags: dbodky/dashing-icinga2:${{ github.event.release.tag_name }} 30 | -------------------------------------------------------------------------------- /.github/workflows/recommend-base.yml: -------------------------------------------------------------------------------- 1 | name: Recommend Base Image Improvements 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 1 */1 *' 6 | workflow_dispatch: {} 7 | 8 | jobs: 9 | recommendations: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | 16 | - name: Docker Scout 17 | id: docker-scout 18 | uses: docker/scout-action@v0.18.1 19 | with: 20 | command: recommendations 21 | image: dbodky/dashing-icinga2:latest 22 | 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle 2 | binpaths 3 | history.yml 4 | log 5 | tmp 6 | *.lock 7 | config/icinga2.local.json 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [v3.2.0](https://github.com/mocdaniel/dashing-icinga2/tree/3.2.0) 4 | 5 | [Full Changelog](https://github.com/mocdaniel/dashing-icinga2/compare/v3.1.0...v3.2.0) 6 | 7 | **Fixed bugs:** 8 | 9 | - \[Bug\] DOWN Hosts are not displayed correctly and throw errors: "no implicit conversion of nil into string" [\#120](https://github.com/mocdaniel/dashing-icinga2/issues/120) 10 | - \[Bug\] Receiving 401 errors after Installation [\#119](https://github.com/mocdaniel/dashing-icinga2/issues/119) 11 | 12 | **Closed issues:** 13 | 14 | - Pie-chart widgets render too big [\#123](https://github.com/mocdaniel/dashing-icinga2/issues/123) 15 | - Invalid yield in layout.erb [\#121](https://github.com/mocdaniel/dashing-icinga2/issues/121) 16 | - No data on icinga2-Dashboard [\#115](https://github.com/mocdaniel/dashing-icinga2/issues/115) 17 | 18 | **Merged pull requests:** 19 | 20 | - Updated ChartJS to v3.2.1 [\#118](https://github.com/mocdaniel/dashing-icinga2/pull/118) ([mocdaniel](https://github.com/mocdaniel)) 21 | 22 | ## [v3.1.0](https://github.com/mocdaniel/dashing-icinga2/tree/v3.1.0) (2020-12-08) 23 | 24 | [Full Changelog](https://github.com/mocdaniel/dashing-icinga2/compare/3.1.0...v3.1.0) 25 | 26 | **Merged pull requests:** 27 | 28 | - Chartjs appearance [\#124](https://github.com/mocdaniel/dashing-icinga2/pull/124) ([mocdaniel](https://github.com/mocdaniel)) 29 | - no-services-for-down-hosts-show-display-name-for-services-and-hosts [\#114](https://github.com/mocdaniel/dashing-icinga2/pull/114) ([betrZHAW](https://github.com/betrZHAW)) 30 | 31 | ## [3.1.0](https://github.com/mocdaniel/dashing-icinga2/tree/3.1.0) (2020-11-07) 32 | 33 | [Full Changelog](https://github.com/mocdaniel/dashing-icinga2/compare/v3.0.0...3.1.0) 34 | 35 | **Implemented enhancements:** 36 | 37 | - Changing timezone [\#92](https://github.com/mocdaniel/dashing-icinga2/issues/92) 38 | - Scrolling of crammed widgets [\#99](https://github.com/mocdaniel/dashing-icinga2/pull/99) ([mocdaniel](https://github.com/mocdaniel)) 39 | 40 | **Fixed bugs:** 41 | 42 | - Use Chartjs update\(\) and rebuild chart only when necessary [\#97](https://github.com/mocdaniel/dashing-icinga2/pull/97) ([coderobe](https://github.com/coderobe)) 43 | 44 | **Closed issues:** 45 | 46 | - Host and Service Problems still 0 [\#105](https://github.com/mocdaniel/dashing-icinga2/issues/105) 47 | - Object not found! Error in Last two panel [\#104](https://github.com/mocdaniel/dashing-icinga2/issues/104) 48 | - Enhancement Request: numbers next to host/service dials [\#102](https://github.com/mocdaniel/dashing-icinga2/issues/102) 49 | - Wrong data at "Service Problems", "Unhandled Services" and "Problems" at the to view. [\#96](https://github.com/mocdaniel/dashing-icinga2/issues/96) 50 | - Wrong Data and Graphs Orientation on Mouse Over [\#93](https://github.com/mocdaniel/dashing-icinga2/issues/93) 51 | - Dual login windows/iFrames w/Ruby using RHSCL [\#80](https://github.com/mocdaniel/dashing-icinga2/issues/80) 52 | - Cannot install on CentOS 7 / RHEL 7 - /usr/bin/ruby too old [\#98](https://github.com/mocdaniel/dashing-icinga2/issues/98) 53 | 54 | **Merged pull requests:** 55 | 56 | - Add dashing icon [\#112](https://github.com/mocdaniel/dashing-icinga2/pull/112) ([theFeu](https://github.com/theFeu)) 57 | - Changes to README.md [\#110](https://github.com/mocdaniel/dashing-icinga2/pull/110) ([mocdaniel](https://github.com/mocdaniel)) 58 | - Update project for transfer ownership [\#109](https://github.com/mocdaniel/dashing-icinga2/pull/109) ([dnsmichi](https://github.com/dnsmichi)) 59 | - Added new Edge to supported browsers [\#100](https://github.com/mocdaniel/dashing-icinga2/pull/100) ([mocdaniel](https://github.com/mocdaniel)) 60 | - Add timezone config/ENV support for the clock widget [\#94](https://github.com/mocdaniel/dashing-icinga2/pull/94) ([dnsmichi](https://github.com/dnsmichi)) 61 | 62 | ## [v3.0.0](https://github.com/mocdaniel/dashing-icinga2/tree/v3.0.0) (2019-10-16) 63 | 64 | [Full Changelog](https://github.com/mocdaniel/dashing-icinga2/compare/v2.0.0...v3.0.0) 65 | 66 | **Implemented enhancements:** 67 | 68 | - iFrame Title [\#82](https://github.com/mocdaniel/dashing-icinga2/issues/82) 69 | - Add title support for the iframe widget for Icinga Web [\#90](https://github.com/mocdaniel/dashing-icinga2/pull/90) ([dnsmichi](https://github.com/dnsmichi)) 70 | - Support both, Dashing and Smashing as Gems [\#88](https://github.com/mocdaniel/dashing-icinga2/pull/88) ([dnsmichi](https://github.com/dnsmichi)) 71 | - Add chartjs widgets to dashboard [\#86](https://github.com/mocdaniel/dashing-icinga2/pull/86) ([dnsmichi](https://github.com/dnsmichi)) 72 | - Docker: Introduce dnsmichi/dashing-icinga2 [\#85](https://github.com/mocdaniel/dashing-icinga2/pull/85) ([dnsmichi](https://github.com/dnsmichi)) 73 | - Add timezone to clock widget [\#74](https://github.com/mocdaniel/dashing-icinga2/pull/74) ([Dambakk](https://github.com/Dambakk)) 74 | - Add option to show only hard state problems [\#73](https://github.com/mocdaniel/dashing-icinga2/pull/73) ([Dambakk](https://github.com/Dambakk)) 75 | 76 | **Fixed bugs:** 77 | 78 | - Update dashing-icinga2 [\#70](https://github.com/mocdaniel/dashing-icinga2/pull/70) ([jschanz](https://github.com/jschanz)) 79 | 80 | **Closed issues:** 81 | 82 | - Not working \(stale information, information does not updated\) after icinga2 update to version 2.11 [\#83](https://github.com/mocdaniel/dashing-icinga2/issues/83) 83 | - Service down number not matching [\#79](https://github.com/mocdaniel/dashing-icinga2/issues/79) 84 | - CentOs 7 - Cannot Start Service /usr/bin/env: ruby: No such file or directory [\#78](https://github.com/mocdaniel/dashing-icinga2/issues/78) 85 | - sample dashbaord blank [\#77](https://github.com/mocdaniel/dashing-icinga2/issues/77) 86 | - Blank dashboard with console-log "EventSource's response has a MIME type \("text/html"\) that is not "text/event-stream" [\#76](https://github.com/mocdaniel/dashing-icinga2/issues/76) 87 | - Two lower panels \(iframes\) not showing content [\#75](https://github.com/mocdaniel/dashing-icinga2/issues/75) 88 | - Communication with Certificates [\#69](https://github.com/mocdaniel/dashing-icinga2/issues/69) 89 | - Icinga2 crashes after a few seconds when starting dashing-icinga2 [\#68](https://github.com/mocdaniel/dashing-icinga2/issues/68) 90 | - setting permissions to exclude network devices. [\#66](https://github.com/mocdaniel/dashing-icinga2/issues/66) 91 | - Add a better indicator for 0 unhandled problems for Simplelist [\#65](https://github.com/mocdaniel/dashing-icinga2/issues/65) 92 | - How to add multiple Icinga2 instances [\#64](https://github.com/mocdaniel/dashing-icinga2/issues/64) 93 | - Empty values in dashing-icinga2 [\#63](https://github.com/mocdaniel/dashing-icinga2/issues/63) 94 | 95 | **Merged pull requests:** 96 | 97 | - Add support details & sponsoring [\#89](https://github.com/mocdaniel/dashing-icinga2/pull/89) ([dnsmichi](https://github.com/dnsmichi)) 98 | - Change license to MIT to comply with Dashing and assets [\#87](https://github.com/mocdaniel/dashing-icinga2/pull/87) ([dnsmichi](https://github.com/dnsmichi)) 99 | - Refactor version parsing and drop the version\_revision attribute [\#84](https://github.com/mocdaniel/dashing-icinga2/pull/84) ([dnsmichi](https://github.com/dnsmichi)) 100 | 101 | ## [v2.0.0](https://github.com/mocdaniel/dashing-icinga2/tree/v2.0.0) (2018-03-08) 102 | 103 | [Full Changelog](https://github.com/mocdaniel/dashing-icinga2/compare/v1.3.0...v2.0.0) 104 | 105 | **Implemented enhancements:** 106 | 107 | - Refine Dashing layout [\#57](https://github.com/mocdaniel/dashing-icinga2/pull/57) ([dnsmichi](https://github.com/dnsmichi)) 108 | - Allow to configure the Icinga Web 2 Iframe URL [\#54](https://github.com/mocdaniel/dashing-icinga2/pull/54) ([dnsmichi](https://github.com/dnsmichi)) 109 | - Add support for environment variables overriding local configuration settings [\#52](https://github.com/mocdaniel/dashing-icinga2/pull/52) ([dnsmichi](https://github.com/dnsmichi)) 110 | - Allow to use 'config/icinga2.local.json' for local configuration overrides [\#51](https://github.com/mocdaniel/dashing-icinga2/pull/51) ([dnsmichi](https://github.com/dnsmichi)) 111 | - Render Undhandled Problems green if count is zero [\#45](https://github.com/mocdaniel/dashing-icinga2/pull/45) ([dnsmichi](https://github.com/dnsmichi)) 112 | - colored and sorted problems by severity [\#41](https://github.com/mocdaniel/dashing-icinga2/pull/41) ([marconett](https://github.com/marconett)) 113 | - Allow Overriding of TLS Cert Names [\#40](https://github.com/mocdaniel/dashing-icinga2/pull/40) ([spjmurray](https://github.com/spjmurray)) 114 | - Move Logo [\#39](https://github.com/mocdaniel/dashing-icinga2/pull/39) ([spjmurray](https://github.com/spjmurray)) 115 | - Upgrade Presentation Layer [\#38](https://github.com/mocdaniel/dashing-icinga2/pull/38) ([spjmurray](https://github.com/spjmurray)) 116 | 117 | **Fixed bugs:** 118 | 119 | - Dashing issues with refreshing data [\#29](https://github.com/mocdaniel/dashing-icinga2/issues/29) 120 | - blank dashboard [\#15](https://github.com/mocdaniel/dashing-icinga2/issues/15) 121 | - Handle SocketError [\#34](https://github.com/mocdaniel/dashing-icinga2/pull/34) ([glauco](https://github.com/glauco)) 122 | 123 | **Closed issues:** 124 | 125 | - 2.8.0 Icinga Plugin shows Menu [\#50](https://github.com/mocdaniel/dashing-icinga2/issues/50) 126 | - Verify dashboard, Javascript and browser versions [\#19](https://github.com/mocdaniel/dashing-icinga2/issues/19) 127 | 128 | **Merged pull requests:** 129 | 130 | - Fix problem list ordering: Crit -\> Warn -\> Unknown [\#53](https://github.com/mocdaniel/dashing-icinga2/pull/53) ([dnsmichi](https://github.com/dnsmichi)) 131 | - Fix version dependencies in Gemfile for dashing and rack-test [\#49](https://github.com/mocdaniel/dashing-icinga2/pull/49) ([dnsmichi](https://github.com/dnsmichi)) 132 | 133 | ## [v1.3.0](https://github.com/mocdaniel/dashing-icinga2/tree/v1.3.0) (2017-06-29) 134 | 135 | [Full Changelog](https://github.com/mocdaniel/dashing-icinga2/compare/v1.2.0...v1.3.0) 136 | 137 | **Implemented enhancements:** 138 | 139 | - Update simplemon widget counts to only show unhandled problems \(!ack && !downtime\) [\#17](https://github.com/mocdaniel/dashing-icinga2/issues/17) 140 | - Update documentation [\#37](https://github.com/mocdaniel/dashing-icinga2/pull/37) ([dnsmichi](https://github.com/dnsmichi)) 141 | - Lower refresh interval to 10s [\#30](https://github.com/mocdaniel/dashing-icinga2/pull/30) ([dnsmichi](https://github.com/dnsmichi)) 142 | - Better layout: 5 cols, handled stats, problems 2 rows, refined titles and font size [\#27](https://github.com/mocdaniel/dashing-icinga2/pull/27) ([dnsmichi](https://github.com/dnsmichi)) 143 | - Better error handling and default values [\#25](https://github.com/mocdaniel/dashing-icinga2/pull/25) ([dnsmichi](https://github.com/dnsmichi)) 144 | - Problem dashboards should show unhandled counts \(and overall counts below\) [\#24](https://github.com/mocdaniel/dashing-icinga2/pull/24) ([dnsmichi](https://github.com/dnsmichi)) 145 | - Add WorkQueue metrics to library and dashboard [\#23](https://github.com/mocdaniel/dashing-icinga2/pull/23) ([dnsmichi](https://github.com/dnsmichi)) 146 | - Add GitHub issue template [\#20](https://github.com/mocdaniel/dashing-icinga2/pull/20) ([dnsmichi](https://github.com/dnsmichi)) 147 | 148 | **Closed issues:** 149 | 150 | - host down widget does not see downtime [\#16](https://github.com/mocdaniel/dashing-icinga2/issues/16) 151 | 152 | **Merged pull requests:** 153 | 154 | - Prepare v1.3.0 and add a Changelog [\#31](https://github.com/mocdaniel/dashing-icinga2/pull/31) ([dnsmichi](https://github.com/dnsmichi)) 155 | - Update README [\#28](https://github.com/mocdaniel/dashing-icinga2/pull/28) ([dnsmichi](https://github.com/dnsmichi)) 156 | - Update Authors in README [\#26](https://github.com/mocdaniel/dashing-icinga2/pull/26) ([dnsmichi](https://github.com/dnsmichi)) 157 | - Fix logo [\#22](https://github.com/mocdaniel/dashing-icinga2/pull/22) ([dnsmichi](https://github.com/dnsmichi)) 158 | 159 | ## [v1.2.0](https://github.com/mocdaniel/dashing-icinga2/tree/v1.2.0) (2017-04-13) 160 | 161 | [Full Changelog](https://github.com/mocdaniel/dashing-icinga2/compare/v1.1.0...v1.2.0) 162 | 163 | **Closed issues:** 164 | 165 | - Don't fetch all object data by default [\#13](https://github.com/mocdaniel/dashing-icinga2/issues/13) 166 | - dashing-icinga2 wont start [\#10](https://github.com/mocdaniel/dashing-icinga2/issues/10) 167 | 168 | **Merged pull requests:** 169 | 170 | - Silence the log level [\#14](https://github.com/mocdaniel/dashing-icinga2/pull/14) ([dnsmichi](https://github.com/dnsmichi)) 171 | - Update README.md [\#12](https://github.com/mocdaniel/dashing-icinga2/pull/12) ([tete2soja](https://github.com/tete2soja)) 172 | - Update README.md [\#11](https://github.com/mocdaniel/dashing-icinga2/pull/11) ([tete2soja](https://github.com/tete2soja)) 173 | 174 | ## [v1.1.0](https://github.com/mocdaniel/dashing-icinga2/tree/v1.1.0) (2016-12-16) 175 | 176 | [Full Changelog](https://github.com/mocdaniel/dashing-icinga2/compare/v1.0.1...v1.1.0) 177 | 178 | ## [v1.0.1](https://github.com/mocdaniel/dashing-icinga2/tree/v1.0.1) (2016-12-02) 179 | 180 | [Full Changelog](https://github.com/mocdaniel/dashing-icinga2/compare/v1.0.0...v1.0.1) 181 | 182 | ## [v1.0.0](https://github.com/mocdaniel/dashing-icinga2/tree/v1.0.0) (2016-11-27) 183 | 184 | [Full Changelog](https://github.com/mocdaniel/dashing-icinga2/compare/v0.9.0...v1.0.0) 185 | 186 | ## [v0.9.0](https://github.com/mocdaniel/dashing-icinga2/tree/v0.9.0) (2016-10-16) 187 | 188 | [Full Changelog](https://github.com/mocdaniel/dashing-icinga2/compare/fcedcb403295eaa05fbf5442cc35cfaeb6da9e96...v0.9.0) 189 | 190 | 191 | 192 | \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* 193 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.7-buster 2 | 3 | MAINTAINER Daniel Bodky 4 | 5 | ENV LANG C.UTF-8 6 | ENV DEBIAN_FRONTEND noninteractive 7 | 8 | RUN apt-get update && apt-get install -y --no-install-recommends \ 9 | libssl-dev nodejs build-essential \ 10 | && rm -rf /var/lib/apt/lists/* && mkdir -p /usr/share/dashing-icinga2 11 | 12 | RUN echo 'gem: --no-document' >> /etc/gemrc && gem install --quiet bundler 13 | 14 | WORKDIR /usr/share/dashing-icinga2 15 | ADD Gemfile /usr/share/dashing-icinga2 16 | RUN bundle update --quiet 17 | 18 | EXPOSE 8005 19 | 20 | # mimic defaults from config/icinga2.json 21 | ENV ICINGA2_API_HOST localhost 22 | ENV ICINGA2_API_PORT 5665 23 | ENV ICINGA2_API_USERNAME dashing 24 | ENV ICINGA2_API_PASSWORD icinga2ondashingr0xx 25 | ENV ICINGA2_API_CERT_PATH pki/ 26 | ENV ICINGA2_API_NODENAME localhost 27 | ENV ICINGAWEB2_URL http://localhost/icingaweb2 28 | ENV DASHBOARD_SHOW_ONLY_HARD_STATE_PROBLEMS 0 29 | ENV DASHBOARD_TIMEZONE UTC 30 | 31 | # add code 32 | ADD . /usr/share/dashing-icinga2 33 | ADD config/icinga2.json config/icinga2.local.json 34 | RUN bundle update --quiet 35 | 36 | COPY docker/start.sh /usr/local/bin 37 | 38 | CMD ["start.sh"] 39 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'smashing' 4 | 5 | # dashing server 6 | gem 'puma' 7 | gem 'eventmachine' 8 | gem 'sd_notify' 9 | 10 | # sinatra pulls rack-protection which pulls rack-test >= 0. v0.8.0 requires Ruby >= 2.2.2 (this fails on RHEL7). 11 | gem 'rack-test', '< 0.8.0' 12 | 13 | # Icinga 2 job 14 | gem 'rest-client' 15 | gem 'json' 16 | 17 | # Misc 18 | gem 'sassc', '<= 2.1.0' 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Michael Friedrich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://img.shields.io/docker/cloud/build/dbodky/dashing-icinga2) 2 | # Dashing with Icinga 2 3 | 4 | ** As of v3.3 dashing-icinga2 will actually use** `smashing` and not `dashing` anymore. 5 | 6 | #### Table of Contents 7 | 8 | 1. [Introduction](#introduction) 9 | 2. [Support](#support) 10 | 3. [License](#license) 11 | 4. [Requirements](#requirements) 12 | 4. [Installation](#installation) 13 | 5. [Configuration](#configuration) 14 | 6. [Run](#run) 15 | 7. [Authors](#authors) 16 | 8. [Troubleshooting](#troubleshooting) 17 | 9. [Development](#development) 18 | 19 | ## Introduction 20 | 21 | [Smashing](https://smashing.github.io/) is a Sinatra based framework 22 | that lets you build beautiful dashboards. 23 | 24 | You can put your important infrastructure stats and metrics on your office 25 | dashboard. Data can be pulled with custom jobs or pushed via REST API. You can re-arrange 26 | widgets via drag&drop. Possible integrations include [Icinga](https://www.icinga.com/), [Grafana](https://grafana.com/), 27 | ticket systems such as [RT](https://bestpractical.com/request-tracker/) or [OTRS](https://www.otrs.com), 28 | [sensors](https://shop.netways.de/), [weather](https://github.com/Shopify/dashing/wiki/Additional-Widgets), 29 | [schedules](https://blog.netways.de/2013/06/21/netrp-netways-resource-planner/), 30 | etc. -- literally anything which can be presented as counter or list. 31 | 32 | ### Icinga 2 Dashboard 33 | 34 | The Icinga 2 dashboard is built on top of Smashing and uses the [Icinga 2 API](https://www.icinga.com/products/icinga-2/) 35 | to visualize what's going on with your monitoring. It combines several popular widgets 36 | and provides development instructions for your own implementation. The dashboard 37 | also allows to embed the [Icinga Web 2](https://www.icinga.com/products/icinga-web-2/) 38 | host and service problem lists as Iframe. 39 | 40 | ![Dashing Icinga 2](public/dashing_icinga2_overview.png "Dashing Icinga 2") 41 | 42 | ## Support 43 | 44 | This is a demo playground with jobs, widgets and dashboards. You can use and modify them for 45 | your own needs. You can also send PRs with custom widgets and propose their use in the dashboard. 46 | 47 | In terms of implementation specific questions, please read the [development docs](#development) 48 | first. 49 | 50 | ## License 51 | 52 | * The code for jobs, dashboards and libraries is licensed under the MIT license. 53 | * Smashing is licensed under the [MIT license](https://github.com/Smashing/smashing/blob/master/MIT-LICENSE). 54 | * Chartjs is licensed under the [MIT license](https://github.com/chartjs/Chart.js/blob/master/LICENSE.md). 55 | 56 | ## Requirements 57 | 58 | * Ruby, Gems and Bundler 59 | * Smashing Ruby Gem 60 | * Icinga 2 (v2.11+) and the REST API 61 | 62 | Supported browsers and clients: 63 | 64 | * Linux, Unix, \*Pi 65 | * Chrome, Firefox, Safari 66 | * "new" Edge [v79 and above] 67 | 68 | **Windows with IE and old Edge (without Chromium) is not supported since SSE (Server Sent Events) are not implemented.** 69 | 70 | ## Installation 71 | 72 | ### Docker 73 | 74 | The Docker image is located at [dbodky/dashing-icinga2](https://hub.docker.com/r/dbodky/dashing-icinga2). 75 | You can also build your own Docker image from the provided Dockerfile. Modify it when needed. When pulling the image from DockerHub, the correct image architecture should get pulled automatically. 76 | 77 | The environment variables from this project can be used to configure the container. 78 | 79 | Example on macOS with Docker for Mac: 80 | 81 | ``` 82 | docker run -it -p 5666:5665 -p 8005:8005 -e ICINGA2_API_HOST='docker.for.mac.localhost' -e ICINGA2_API_PORT=5665 -e ICINGA2_API_USERNAME='root' -e ICINGA2_API_PASSWORD='icinga' dbodky/dashing-icinga2 83 | ``` 84 | 85 | Note that the Docker container exposes port 8005 by default. Modifying this requires you to build your own image. 86 | 87 | ### Source 88 | 89 | Either clone this repository from GitHub or download the tarball. 90 | 91 | Git clone: 92 | 93 | ``` 94 | cd /usr/share 95 | git clone https://github.com/mocdaniel/dashing-icinga2.git 96 | cd dashing-icinga2 97 | ``` 98 | 99 | Tarball download: 100 | 101 | ``` 102 | cd /usr/share 103 | wget https://github.com/mocdaniel/dashing-icinga2/archive/master.zip 104 | unzip master.zip 105 | mv dashing-icinga2-master dashing-icinga2 106 | cd dashing-icinga2 107 | ``` 108 | 109 | 110 | ### Linux 111 | 112 | #### RedHat/CentOS 7 (requires EPEL repository): 113 | 114 | ``` 115 | yum makecache 116 | yum -y install epel-release 117 | yum -y install rubygems rubygem-bundler ruby-devel openssl gcc-c++ make nodejs 118 | ``` 119 | 120 | Note: The development tools and header files are required for building the `eventmachine` gem. 121 | 122 | #### Debian/Ubuntu: 123 | 124 | ``` 125 | apt-get update 126 | apt-get -y install ruby bundler nodejs 127 | ``` 128 | 129 | Proceed with the `bundler` gem installation. 130 | 131 | ``` 132 | gem install bundler 133 | ``` 134 | 135 | In case the installation takes quite long and you do not need any documentation, 136 | add the `--no-document` flags. 137 | 138 | #### Proceed with bundling for all systems (CentOS, Ubuntu, Debian etc.) 139 | 140 | Install the dependencies using Bundler. **Do not run this as root.** 141 | 142 | ``` 143 | cd /usr/share/dashing-icinga2 144 | bundle 145 | ``` 146 | 147 | >**Note** 148 | > In case bundle complains about missing write access to `/usr/share/dashing-icinga2/Gemfile.lock`, 149 | > add write permissions accordingly 150 | 151 | Proceed to the [configuration](#configuration) section. 152 | 153 | ### Unix and macOS 154 | 155 | On macOS [OpenSSL was deprecated](https://github.com/eventmachine/eventmachine/issues/602), 156 | therefore you'll need to fix the eventmachine gem: 157 | 158 | ``` 159 | brew install openssl 160 | bundle config build.eventmachine --with-cppflags=-I/usr/local/opt/openssl/include 161 | bundle install --path binpaths 162 | ``` 163 | 164 | Proceed to the [configuration](#configuration) section. 165 | 166 | 167 | ## Configuration 168 | 169 | ### Icinga 2 API 170 | 171 | The Icinga 2 API requires either basic authentication or client certificates. 172 | 173 | Add a new ApiUser object to your Icinga 2 configuration. Choose the 174 | path depending on your setup type. Keep in mind that synced ApiUser 175 | objects expose the login information to other Icinga 2 instances. 176 | 177 | ``` 178 | vim /etc/icinga2/conf.d/api-users.conf 179 | 180 | vim /etc/icinga2/zones.d/global-templates/api-users.conf 181 | ``` 182 | 183 | ``` 184 | object ApiUser "dashing" { 185 | password = "icinga2ondashingr0xx" 186 | permissions = [ "status/query", "objects/query/*" ] 187 | } 188 | ``` 189 | 190 | Set the [ApiUser permissions](https://www.icinga.com/docs/icinga2/latest/doc/12-icinga2-api/#permissions) 191 | according to your needs. By default the Icinga 2 job fetches 192 | data from the `/v1/status` and `/v1/objects` API endpoints, but does not require write 193 | permissions. If you're extending the API queries on your own, keep in mind to add 194 | proper permissions. 195 | 196 | In case you want to use client certificates, set the `client_cn` attribute accordingly. 197 | 198 | ### Smashing Configuration 199 | 200 | #### Configuration File 201 | 202 | Copy the example configuration from `config/icinga2.json` into `config/icinga2.local.json` 203 | and adjust the settings for the Icinga 2 API credentials in the `icinga2` section. 204 | 205 | The `icingaweb2` section allows you to specify the Icinga Web 2 URL 206 | for the iframe widgets. This is read on startup once. 207 | 208 | ``` 209 | cp config/icinga2.json config/icinga2.local.json 210 | ``` 211 | 212 | ``` 213 | vim config/icinga2.local.json 214 | 215 | { 216 | "icinga2": { 217 | "api": { 218 | "host": "localhost", 219 | "port": 5665, 220 | "user": "dashing", 221 | "password": "icinga2ondashingr0xx" 222 | } 223 | }, 224 | "icingaweb2": { 225 | "url": "http://localhost/icingaweb2" 226 | }, 227 | "dashboard": { 228 | "show_only_hard_state_problems": false, 229 | "timezone": "UTC", 230 | "url_prefix": "" 231 | } 232 | } 233 | ``` 234 | 235 | The `show_only_hard_state_problems` option ignores NOT-OK states until they 236 | reach a hard NOT-OK state (off by default). The `timezone` option controls the clock widget's 237 | timezone. 238 | 239 | If you prefer to use client certificates, set the `pki_path` attribute. The Icinga 2 240 | job expects the certificate file names based on the local FQDN e.g. `pki/icinga2-master1.localdomain.crt`. 241 | You can override this behaviour by specifying the `node_name` configuration option 242 | explicitly. 243 | 244 | ``` 245 | { 246 | "icinga2": { 247 | "api": { 248 | "host": "localhost", 249 | "port": 5665, 250 | "user": "dashing", 251 | "pki_path": "pki/", 252 | "node_name": "icinga2-master1.localdomain" 253 | } 254 | }, 255 | "icingaweb2": { 256 | "url": "http://localhost/icingaweb2" 257 | }, 258 | "dashboard": { 259 | "show_only_hard_state_problems": false, 260 | "timezone": "UTC", 261 | "url_prefix": "" 262 | } 263 | } 264 | ``` 265 | 266 | > **Note:** 267 | > 268 | > If both methods are configured, the Icinga 2 job prefers client certificates. 269 | 270 | #### Environment Variables 271 | 272 | If you prefer to configure the Icinga 2 settings in environment variables, you 273 | can do so as well. This helps with containers or local development tests. 274 | 275 | Variable | Description 276 | -------------------------|------------------------- 277 | ICINGA2\_API\_HOST | **Required.** Host where the API is listening on. 278 | ICINGA2\_API\_PORT | **Required.** Port where the API is listening on. 279 | ICINGA2\_API\_USERNAME | **Optional.** Required for basic auth as username. 280 | ICINGA2\_API\_PASSWORD | **Optional.** Required for basic auth as password. 281 | ICINGA2\_API\_CERT\_PATH | **Optional.** Client certificate path. 282 | ICINGA2\_API\_NODENAME | **Optional.** If client certificates do not match the host name, override it. 283 | ICINGAWEB2\_URL | **Optional.** Set the Icinga Web 2 Url. Defaults to `http://localhost/icingaweb2`. 284 | DASHBOARD_SHOW_ONLY_HARD_STATE_PROBLEMS | **Optional.** Set `show_only_hard_state_problems` configuration option, toggle with `0|1`. 285 | DASHBOARD_TIMEZONE | **Optional.** Set the `timezone` option for the dashboard, overriding the default `UTC` value. 286 | DASHBOARD_URL_PREFIX | **Optional.** Set the url prefix e.g. when running behind a reverse proxy 287 | 288 | > **Note** 289 | > 290 | > Environment variables always override local configuration. 291 | 292 | Example: 293 | 294 | ``` 295 | ICINGA2_API_HOST=localhost ICINGA2_API_PORT=5665 ICINGA2_API_USERNAME=root ICINGA2_API_PASSWORD=icinga puma config.ru -p 8005 296 | ``` 297 | 298 | ## Run 299 | 300 | ### Systemd Service 301 | 302 | Install the provided Systemd service file from `tools/systemd`. It assumes 303 | that the working directory is `/usr/share/dashing-icinga2` and the Smashing gem 304 | is installed to `/usr/local/bin/smashing`. Adopt these paths for your own needs. 305 | 306 | #### Redhat/CentOS 307 | 308 | ``` 309 | cp tools/systemd/dashing-icinga2.service /usr/lib/systemd/system/ 310 | systemctl daemon-reload 311 | systemctl start dashing-icinga2.service 312 | systemctl status dashing-icinga2.service 313 | ``` 314 | 315 | #### Debian 316 | 317 | ```bash 318 | cp tools/systemd/dashing-icinga2.service /lib/systemd/system/ 319 | systemctl daemon-reload 320 | systemctl start dashing-icinga2.service 321 | systemctl status dashing-icinga2.service 322 | ``` 323 | 324 | ### Script 325 | 326 | You can start smashing as daemon by using this script: 327 | 328 | ``` 329 | ./restart-dashing 330 | ``` 331 | 332 | Additional options are available through `./restart-dashing -h`. 333 | 334 | Navigate to [http://localhost:8005](http://localhost:8005) 335 | 336 | 337 | ### Foreground 338 | 339 | You can run Smashing in foreground for tests and debugging too: 340 | 341 | ``` 342 | export PATH="/usr/local/bin:$PATH" 343 | puma config.ru -p 8005 344 | ``` 345 | 346 | Or with environment variables: 347 | 348 | ``` 349 | ICINGA2_API_HOST=localhost ICINGA2_API_PORT=5665 ICINGA2_API_USERNAME=root ICINGA2_API_PASSWORD=icinga puma config.ru -p 8005 350 | ``` 351 | 352 | ### Reverse Proxy / Subfolder 353 | 354 | To configure the application to run in a subfolder, e.g. for a reverse proxy, set the `DASHBOARD_URL_PREFIX` environment 355 | variable or set the corresponding value in `config/icinga2.local.json` 356 | 357 | Apache 2 reverse proxy example for `DASHBOARD_URL_PREFIX=/icinga2-dashing` 358 | 359 | ``` 360 | 361 | ProxyPass http://127.0.0.1:8005/icinga2-dashing/ 362 | ProxyPassReverse http://127.0.0.1:8005/icinga2-dashing/ 363 | 364 | ``` 365 | 366 | ### Logrotate 367 | 368 | You can install the provided logrotate script to rotate the Dashing log in `/usr/share/dashing-icinga2/log`. 369 | 370 | ``` 371 | cp tools/logrotate/dashing-icinga2 /etc/logrotate.d 372 | ``` 373 | 374 | ## Authors 375 | 376 | Original author: 377 | 378 | * [dnsmichi](https://github.com/dnsmichi) 379 | 380 | Maintainer as of May 2020: 381 | * [mocdaniel](https://github.com/mocdaniel) 382 | 383 | Thanks to all contributors! :) 384 | 385 | * [flourish86](https://github.com/flourish86) for the UX design and CSS tips. 386 | * [marconett](https://github.com/marconett) for the [colorized and sorted problem list widget](https://github.com/dnsmichi/dashing-icinga2/pull/41). 387 | * [tutabeier](https://github.com/tutabeier) for [environment variables](https://github.com/dnsmichi/dashing-icinga2/pull/35) inspiration instead of a local configuration file. 388 | * [mcktr](https://github.com/mcktr) for helping out with [unhandled problems](https://github.com/dnsmichi/dashing-icinga2/pull/18). 389 | * [tachtler](https://github.com/tachtler) for the [Systemd service and logrotate](https://github.com/dnsmichi/dashing-icinga2/pull/6) additions. 390 | * [fugstrolch](https://github.com/fugstrolch) for the [Icinga Web 2 iframe integration](https://github.com/dnsmichi/dashing-icinga2/pull/4). 391 | * [tobiasvdk](https://github.com/tobiasvdk) for check stats widget and suggestions. 392 | * [bodsch](https://github.com/bodsch) for the [job rewrite and config file support](https://github.com/dnsmichi/dashing-icinga2/pull/3) inspiration, and [better error handling](https://github.com/dnsmichi/dashing-icinga2/pull/21). 393 | * [spjmurray](https://github.com/spjmurray) for [styling and 1080p resolution](https://github.com/spjmurray/dashing-icinga2/tree/1080p). 394 | * [micke2k](https://github.com/micke2k) for [proper time formatting](https://github.com/dnsmichi/dashing-icinga2/pull/2). 395 | * [lazyfrosch](https://github.com/lazyfrosch) for ideas on [Dashing with Icinga](https://github.com/lazyfrosch/dashing-icinga). 396 | * [roidelapliue](https://github.com/roidelapluie) for the [Icinga 1.x dashing script](https://github.com/roidelapluie/dashing-scripts). 397 | * [](https://github.com/austinjhung) for reevaluating switching to Puma + Smashing instead of Thin + Dashing. 398 | 399 | ## Troubleshooting 400 | 401 | Please add these details when you are asking a question on the community channels. 402 | 403 | ### Required Information 404 | 405 | * Dashing/Smashing version (`gem list --local dashing`) 406 | * Ruby version (`ruby -V`) 407 | * Icinga 2 version (`icinga2 --version`) 408 | * Version of this project (tarball name, download date, tag name or `git show -1`) 409 | * Your own modifications to this project, if any 410 | 411 | ### Widgets are not updated 412 | 413 | * Open your browser's development console and check for errors. 414 | * Ensure that the job runner does not log any errors. 415 | * Stop the smashing daemon and run it in foreground. 416 | 417 | ### Connection Errors 418 | 419 | If the connection to the Icinga 2 API was interrupted, check for possible network issues. 420 | The Icinga 2 daemon might have been reloaded at that time. 421 | 422 | * Manually test the Icinga 2 API (check https://www.icinga.com/docs for the official documentation) 423 | * Verify that the configuration file contains the correct API details 424 | * Modify the `jobs/icinga2.rb` and add additional logging (use `puts` similar to existing examples) 425 | * Run Dashing in foreground 426 | 427 | ### Misc Errors 428 | 429 | * Port 8005 is not reachable. Ensure that the firewall rules are setup accordingly. 430 | 431 | ## Development 432 | 433 | Fork the repository on GitHub, commit your changes and send a PR please :) 434 | 435 | The Icinga 2 dashboard mainly depends on the following files: 436 | 437 | * `dashboards/icinga2.erb` 438 | * `jobs/icinga2.rb` 439 | * `lib/icinga2.rb` 440 | * `config/icinga2.json` 441 | 442 | Additional changes are inside the widgets. `simplemon` and `simplelist` have been added. `meter` was modified to update the 443 | maximum value at runtime. `list` was updated to highlight colors and change font sizes. 444 | 445 | ### Configuration 446 | 447 | You can use environment variables to quickly set the required configuration settings: 448 | 449 | ``` 450 | ICINGA2_API_HOST=localhost ICINGA2_API_PORT=5665 ICINGA2_API_USERNAME=root ICINGA2_API_PASSWORD=icinga puma config.ru -p 8005 451 | ``` 452 | 453 | ### Icinga 2 Library 454 | 455 | `lib/icinga2.rb` provides a class `icinga` which is responsible 456 | for reading the configuration file, initializing the api connection 457 | and fetching data. 458 | 459 | Several public attributes are exposed by this class already. You'll 460 | need to instantiate a new object and then call the `run` method. 461 | 462 | Then you are able to access these attributes and public functions 463 | such as `getHostobjects` and `getServiceObjects`. These two functions 464 | can be called by passing 465 | 466 | * attrs as an array of attribute strings 467 | * filter as Icinga 2 API filter string 468 | * joins as an array of joined objects and/or attributes 469 | 470 | A simple test runner for testing own modifications has been added 471 | into `test/icinga2.rb`. You can find additional examples over there as 472 | well. 473 | 474 | > **Note** 475 | > 476 | > These code parts may change. Keep this in mind on updates. 477 | 478 | 479 | ### Icinga 2 Job 480 | 481 | Widgets are updated by calling `send_event` inside the `jobs/icinga2.rb` file 482 | in the event scheduler. 483 | 484 | The widget data is calculated from the `Icinga2` object class. 485 | 486 | Include the Icinga 2 library: 487 | 488 | ```ruby 489 | require './lib/icinga2' 490 | ``` 491 | 492 | Instantiate a new object called `icinga` from the `Icinga2` class. Add the 493 | path to the configuration file. 494 | 495 | ```ruby 496 | # initialize data provider 497 | icinga = Icinga2.new('config/icinga2.json') # fixed path 498 | ``` 499 | 500 | Run the scheduler every five seconds and start it now. 501 | 502 | ```ruby 503 | SCHEDULER.every '5s', :first_in => 0 do |job| 504 | ``` 505 | 506 | Then call the `run` method to fetch the current data into the `icinga` object 507 | 508 | ```ruby 509 | # run data provider 510 | icinga.run 511 | ``` 512 | 513 | Now you are able to access the exported object attributes and call available 514 | object methods. Please check `libs/icinga2.rb` for specific options. If you 515 | require more attributes and/or methods please send a PR! 516 | 517 | ### Icinga 2 Dashboard 518 | 519 | The dashboard is located in the `dashboards/icinga2.erb` file and mostly 520 | consists of an HTML list. It can be used as template where variables are 521 | read from the `config.ru` file but that's for advanced usage. 522 | 523 | Example: 524 | 525 | ```html 526 | vim dashboards/icinga2.erb 527 | 528 |
  • 529 |
    530 |
  • 531 | ``` 532 | 533 | The following attributes are important: 534 | 535 | * `data-row` and `data-col` specify the location of the widget on screen. 536 | * `data-sizex` and `data-sizey` specify the width and height of a widget by tiles. 537 | * `data-view` defines the name of the widget to use 538 | * `data-id` specifies the name of the data source for the used widget (important for `send_event` later) 539 | * `data-title` defines the widget's title on top 540 | * `data-min` and `data-max` are widget specific in this example. They are referenced inside the Coffee script file inside the widget code. 541 | * `style` can be used to specify certain CSS to make the widget look more beautiful if not already. 542 | * `class=scrollable` allows for scrolling of crammed widgets. Works for most widgets but is mostly meant to be used with `List` and `Simplelist`. 543 | 544 | ### Dashboard Widgets 545 | 546 | The widgets are located inside the `widgets` directory. Each widget consists of three files: 547 | 548 | * `widget.html` defines the basic layout 549 | * `widget.scss` specifies required styling 550 | * `widget.coffee` implements the event handlng for the widget, e.g. `OnData` when `send_event` pushes new data. 551 | 552 | #### Meter 553 | 554 | This widget is used to display host and service problem counts. The maximum value is updated 555 | at runtime too because of API-created objects. 556 | 557 | Example: 558 | 559 | ```ruby 560 | vim jobs/icinga2.rb 561 | 562 | send_event('icinga-host-meter', { 563 | value: host_meter, 564 | max: host_meter_max, 565 | moreinfo: "Total hosts: " + host_meter_max.to_s, 566 | color: 'blue' }) 567 | ``` 568 | 569 | `icinga-host-meter` is the value of the `data-id` field in the `dashboards/icinga2.erb` file. 570 | 571 | ``` 572 | vim dashboards/icinga2.erb 573 | 574 |
  • 575 |
    576 |
  • 577 | ``` 578 | 579 | In order to update the widget you'll need to send a hash which contains the following keys 580 | and values: 581 | 582 | * `value` containing the current problem count 583 | * `max` specifying the current object count 584 | * `moreinfo` creating a string which is displayed below the meter as legend 585 | * `color` for specifying the widget's color 586 | 587 | #### List 588 | 589 | Used to print the average checks per minute and list service problems by severity. 590 | 591 | Example for check statistics: 592 | 593 | Create a new array containing a hash for each table row. The `label` key is required, 594 | `value` is optional. 595 | 596 | ```ruby 597 | vim jobs/icinga2.rb 598 | 599 | icinga_stats = [ 600 | {"label" => "Host checks/min", "value" => icinga.host_active_checks_1min}, 601 | {"label" => "Service checks/min", "value" => icinga.service_active_checks_1min}, 602 | ] 603 | ``` 604 | 605 | Use this array inside the `icinga-stats` event (`data-id` in the `dashboards/icinga2.erb` file) 606 | as `items` attribute. You can add `moreinfo` which provides an additional legend for this widget. 607 | `color` is optional. 608 | 609 | ```ruby 610 | vim jobs/icinga2.rb 611 | 612 | send_event('icinga-stats', { 613 | title: icinga.version + " (" + icinga.version_revision + ")", 614 | items: icinga_stats, 615 | moreinfo: "Avg latency: " + icinga.avg_latency.to_s + "s", 616 | color: 'blue' }) 617 | ``` 618 | 619 | #### Simplelist 620 | 621 | Print problem counts by state as background color in a simple list. 622 | 623 | Example: 624 | 625 | ```ruby 626 | vim jobs/icinga2.rb 627 | 628 | # Combined view of unhandled host problems (only if there are some) 629 | unhandled_host_problems = [] 630 | 631 | if (icinga.host_count_problems_down > 0) 632 | unhandled_host_problems.push( 633 | { "color" => icinga.stateToColor(1, true), "value" => icinga.host_count_problems_down }, 634 | ) 635 | end 636 | 637 | send_event('icinga-host-problems', { 638 | items: unhandled_host_problems, 639 | moreinfo: "All Problems: " + icinga.host_count_problems_down.to_s 640 | }) 641 | ``` 642 | 643 | #### Simplemon 644 | 645 | Print problem counts by state and coloring. Also add acknowledged objects and those 646 | in downtime. 647 | 648 | Example: 649 | 650 | ```ruby 651 | vim jobs/icinga2.rb 652 | 653 | send_event('icinga-service-critical', { 654 | value: icinga.service_count_critical.to_s, 655 | color: 'red' }) 656 | ``` 657 | 658 | `icinga-service-critical` is the value of `data-id` field inside the `dashboards/icinga2.erb` 659 | file. In order to update the widget you need to send a `value` and a `color` as hash values. 660 | 661 | #### IFrame 662 | 663 | You can edit `dashboards/icinga2.erb` to modify the iframe widget 664 | for Icinga Web 2. Keep in mind that you keep the template function `<%=getIcingaWeb2Url()%>` 665 | in order to read the Icinga Web 2 Host and URL from the configuration. 666 | 667 | Example URL: 668 | 669 | ``` 670 | /icingaweb2/monitoring/list/services?service_problem=1&sort=service_severity&dir=desc 671 | ``` 672 | 673 | Add the fullscreen and compact options for those views. 674 | 675 | ``` 676 | &showFullscreen&showCompact 677 | ``` 678 | 679 | Example: 680 | 681 | ```html 682 | 683 |
  • 684 |
    685 |
  • 686 |
  • 687 |
    688 |
  • 689 | ``` 690 | 691 | #### Chartjs 692 | 693 | Original from [tywhang](https://github.com/tywhang/smashing-chartjs-example). 694 | 695 | 696 | #### Roundchartjs 697 | 698 | A fork of [tywhang's](https://github.com/tywhang/smashing-chartjs-example) Chartjs widget, with minimal HTML changes in order to render round charts (specifically `doughnut` and `pie` in the correct size for dashing-icinga2's default widget settings. 699 | 700 | 701 | #### Clock 702 | 703 | Show the time in a specific timezone. Enter the timezone as found in `/usr/share/zoneinfo` on Linux. 704 | 705 | Example: 706 | 707 | ```html 708 |
  • 709 |
    710 |
  • 711 |
  • 712 |
    713 |
  • 714 | ``` 715 | 716 | ### Docker 717 | 718 | ``` 719 | docker build . -t dbodky/dashing-icinga2 720 | docker login 721 | docker push dbodky/dashing-icinga2 722 | ``` 723 | 724 | Test with Docker for Mac: 725 | 726 | ``` 727 | docker run -it -p 8005:8005 -e ICINGA2_API_HOST='docker.for.mac.localhost' -e ICINGA2_API_PORT=5665 -e ICINGA2_API_USERNAME='root' -e ICINGA2_API_PASSWORD='icinga' dbodky/dashing-icinga2 728 | ``` 729 | 730 | In order to test things, add `bash` as the last parameter. This avoids running `start.sh` and allows to test further. 731 | 732 | 733 | ### References 734 | 735 | - https://community.icinga.com/t/extend-icinga-dashing-for-your-own-needs/763 736 | - https://community.icinga.com/t/dashing-for-icinga-3-0-0/2523 737 | - https://www.icinga.com/2016/01/28/awesome-dashing-dashboards-with-icinga-2/ 738 | - https://www.icinga.com/2016/12/22/merry-xmas-dashing-with-icinga-2-v1-1-0-is-here/ 739 | - https://www.icinga.com/2017/07/13/dashing-for-icinga-2-v1-3-0-released/ 740 | 741 | - https://www.antonissen.net/2017/02/19/monitoring-your-network-with-icinga-2-final-part-6/ 742 | - https://community.spiceworks.com/how_to/147719-icinga2-dashing 743 | - https://linoxide.com/monitoring-2/setup-monitoring-dashing-icinga2/ 744 | - http://brunner-it.de/2016/08/04/icinga2-dashing-installieren/ 745 | - https://www.unixe.de/icinga2-dashing/ 746 | 747 | #### Feedback 748 | 749 | - https://twitter.com/Mikeschova/status/1229415489627181056 750 | 751 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release Workflow 2 | 3 | Specify the release version. 4 | 5 | ``` 6 | VERSION=3.0.0 7 | ``` 8 | 9 | ## Issues 10 | 11 | Check issues at https://github.com/dnsmichi/dashing-icinga2 12 | 13 | ## Changelog 14 | 15 | Write it manually. 16 | 17 | ## Release Commit 18 | 19 | ## Git Tag 20 | 21 | ``` 22 | git tag -s -m "Version $VERSION" v$VERSION 23 | ``` 24 | 25 | Push the tag. 26 | 27 | ``` 28 | git push --tags 29 | ``` 30 | 31 | ## GitHub Release 32 | 33 | Create a new release for the newly created Git tag. 34 | https://github.com/dnsmichi/dashing-icinga2/releases 35 | 36 | ## Announcement 37 | 38 | * Blogpost 39 | * Twitter 40 | * LinkedIn 41 | -------------------------------------------------------------------------------- /assets/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mocdaniel/dashing-icinga2/2fc3aa4e150651299b7df7a83aeb43ccd95ac930/assets/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /assets/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mocdaniel/dashing-icinga2/2fc3aa4e150651299b7df7a83aeb43ccd95ac930/assets/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /assets/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mocdaniel/dashing-icinga2/2fc3aa4e150651299b7df7a83aeb43ccd95ac930/assets/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /assets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mocdaniel/dashing-icinga2/2fc3aa4e150651299b7df7a83aeb43ccd95ac930/assets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /assets/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mocdaniel/dashing-icinga2/2fc3aa4e150651299b7df7a83aeb43ccd95ac930/assets/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mocdaniel/dashing-icinga2/2fc3aa4e150651299b7df7a83aeb43ccd95ac930/assets/images/favicon.png -------------------------------------------------------------------------------- /assets/images/icinga-hero-background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mocdaniel/dashing-icinga2/2fc3aa4e150651299b7df7a83aeb43ccd95ac930/assets/images/icinga-hero-background.jpg -------------------------------------------------------------------------------- /assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mocdaniel/dashing-icinga2/2fc3aa4e150651299b7df7a83aeb43ccd95ac930/assets/images/logo.png -------------------------------------------------------------------------------- /assets/images/logo_icinga-inv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mocdaniel/dashing-icinga2/2fc3aa4e150651299b7df7a83aeb43ccd95ac930/assets/images/logo_icinga-inv.png -------------------------------------------------------------------------------- /assets/images/logo_icinga_big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mocdaniel/dashing-icinga2/2fc3aa4e150651299b7df7a83aeb43ccd95ac930/assets/images/logo_icinga_big.png -------------------------------------------------------------------------------- /assets/images/logo_icinga_big_winter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mocdaniel/dashing-icinga2/2fc3aa4e150651299b7df7a83aeb43ccd95ac930/assets/images/logo_icinga_big_winter.png -------------------------------------------------------------------------------- /assets/javascripts/application.coffee: -------------------------------------------------------------------------------- 1 | # dashing.js is located in the dashing framework 2 | # It includes jquery & batman for you. 3 | #= require dashing.js 4 | 5 | #= require_directory . 6 | #= require_tree ../../widgets 7 | 8 | Batman.config.viewPrefix = document.querySelector('meta[name="dashing_url_prefix"]').content + '/views' 9 | 10 | console.log("Yeah! The dashboard has started!") 11 | 12 | Chart.defaults.color = "white" 13 | Dashing.on 'ready', -> 14 | Dashing.widget_margins ||= [5, 5] 15 | Dashing.widget_base_dimensions ||= [300, 300] 16 | Dashing.numColumns ||= 5 17 | 18 | contentWidth = (Dashing.widget_base_dimensions[0] + Dashing.widget_margins[0] * 2) * Dashing.numColumns 19 | 20 | Batman.setImmediate -> 21 | $('.gridster').width(contentWidth) 22 | $('.gridster ul:first').gridster 23 | max_size_x: Dashing.numColumns 24 | widget_margins: Dashing.widget_margins 25 | widget_base_dimensions: Dashing.widget_base_dimensions 26 | avoid_overlapped_widgets: !Dashing.customGridsterLayout 27 | draggable: 28 | stop: Dashing.showGridsterInstructions 29 | start: -> Dashing.currentWidgetPositions = Dashing.getWidgetPositions() 30 | -------------------------------------------------------------------------------- /assets/javascripts/dashing.gridster.coffee: -------------------------------------------------------------------------------- 1 | #= require_directory ./gridster 2 | 3 | # This file enables gridster integration (http://gridster.net/) 4 | # Delete it if you'd rather handle the layout yourself. 5 | # You'll miss out on a lot if you do, but we won't hold it against you. 6 | 7 | Dashing.gridsterLayout = (positions) -> 8 | Dashing.customGridsterLayout = true 9 | positions = positions.replace(/^"|"$/g, '') 10 | positions = $.parseJSON(positions) 11 | widgets = $("[data-row^=]") 12 | for widget, index in widgets 13 | $(widget).attr('data-row', positions[index].row) 14 | $(widget).attr('data-col', positions[index].col) 15 | 16 | Dashing.getWidgetPositions = -> 17 | $(".gridster ul:first").gridster().data('gridster').serialize() 18 | 19 | Dashing.showGridsterInstructions = -> 20 | newWidgetPositions = Dashing.getWidgetPositions() 21 | 22 | unless JSON.stringify(newWidgetPositions) == JSON.stringify(Dashing.currentWidgetPositions) 23 | Dashing.currentWidgetPositions = newWidgetPositions 24 | $('#save-gridster').slideDown() 25 | $('#gridster-code').text(" 26 | 31 | ") 32 | 33 | $ -> 34 | $('#save-gridster').leanModal() 35 | 36 | $('#save-gridster').click -> 37 | $('#save-gridster').slideUp() 38 | -------------------------------------------------------------------------------- /assets/javascripts/gridster/jquery.leanModal.min.js: -------------------------------------------------------------------------------- 1 | // leanModal v1.1 by Ray Stone - http://finelysliced.com.au 2 | // Dual licensed under the MIT and GPL 3 | 4 | (function($){$.fn.extend({leanModal:function(options){var defaults={top:100,overlay:0.5,closeButton:null};var overlay=$("
    ");$("body").append(overlay);options=$.extend(defaults,options);return this.each(function(){var o=options;$(this).click(function(e){var modal_id=$(this).attr("href");$("#lean_overlay").click(function(){close_modal(modal_id)});$(o.closeButton).click(function(){close_modal(modal_id)});var modal_height=$(modal_id).outerHeight();var modal_width=$(modal_id).outerWidth(); 5 | $("#lean_overlay").css({"display":"block",opacity:0});$("#lean_overlay").fadeTo(200,o.overlay);$(modal_id).css({"display":"block","position":"fixed","opacity":0,"z-index":11000,"left":50+"%","margin-left":-(modal_width/2)+"px","top":o.top+"px"});$(modal_id).fadeTo(200,1);e.preventDefault()})});function close_modal(modal_id){$("#lean_overlay").fadeOut(200);$(modal_id).css({"display":"none"})}}})})(jQuery); 6 | -------------------------------------------------------------------------------- /assets/javascripts/jquery.knob.js: -------------------------------------------------------------------------------- 1 | /*!jQuery Knob*/ 2 | /** 3 | * Downward compatible, touchable dial 4 | * 5 | * Version: 1.2.0 (15/07/2012) 6 | * Requires: jQuery v1.7+ 7 | * 8 | * Copyright (c) 2012 Anthony Terrien 9 | * Under MIT and GPL licenses: 10 | * http://www.opensource.org/licenses/mit-license.php 11 | * http://www.gnu.org/licenses/gpl.html 12 | * 13 | * Thanks to vor, eskimoblood, spiffistan, FabrizioC 14 | */ 15 | $(function () { 16 | 17 | /** 18 | * Kontrol library 19 | */ 20 | "use strict"; 21 | 22 | /** 23 | * Definition of globals and core 24 | */ 25 | var k = {}, // kontrol 26 | max = Math.max, 27 | min = Math.min; 28 | 29 | k.c = {}; 30 | k.c.d = $(document); 31 | k.c.t = function (e) { 32 | return e.originalEvent.touches.length - 1; 33 | }; 34 | 35 | /** 36 | * Kontrol Object 37 | * 38 | * Definition of an abstract UI control 39 | * 40 | * Each concrete component must call this one. 41 | * 42 | * k.o.call(this); 43 | * 44 | */ 45 | k.o = function () { 46 | var s = this; 47 | 48 | this.o = null; // array of options 49 | this.$ = null; // jQuery wrapped element 50 | this.i = null; // mixed HTMLInputElement or array of HTMLInputElement 51 | this.g = null; // 2D graphics context for 'pre-rendering' 52 | this.v = null; // value ; mixed array or integer 53 | this.cv = null; // change value ; not commited value 54 | this.x = 0; // canvas x position 55 | this.y = 0; // canvas y position 56 | this.$c = null; // jQuery canvas element 57 | this.c = null; // rendered canvas context 58 | this.t = 0; // touches index 59 | this.isInit = false; 60 | this.fgColor = null; // main color 61 | this.pColor = null; // previous color 62 | this.dH = null; // draw hook 63 | this.cH = null; // change hook 64 | this.eH = null; // cancel hook 65 | this.rH = null; // release hook 66 | 67 | this.run = function () { 68 | var cf = function (e, conf) { 69 | var k; 70 | for (k in conf) { 71 | s.o[k] = conf[k]; 72 | } 73 | s.init(); 74 | s._configure() 75 | ._draw(); 76 | }; 77 | 78 | if(this.$.data('kontroled')) return; 79 | this.$.data('kontroled', true); 80 | 81 | this.extend(); 82 | this.o = $.extend( 83 | { 84 | // Config 85 | min : this.$.data('min') || 0, 86 | max : this.$.data('max') || 100, 87 | stopper : true, 88 | readOnly : this.$.data('readonly'), 89 | 90 | // UI 91 | cursor : (this.$.data('cursor') === true && 30) 92 | || this.$.data('cursor') 93 | || 0, 94 | thickness : this.$.data('thickness') || 0.35, 95 | width : this.$.data('width') || 200, 96 | height : this.$.data('height') || 200, 97 | displayInput : this.$.data('displayinput') == null || this.$.data('displayinput'), 98 | displayPrevious : this.$.data('displayprevious'), 99 | fgColor : this.$.data('fgcolor') || '#87CEEB', 100 | inline : false, 101 | 102 | // Hooks 103 | draw : null, // function () {} 104 | change : null, // function (value) {} 105 | cancel : null, // function () {} 106 | release : null // function (value) {} 107 | }, this.o 108 | ); 109 | 110 | // routing value 111 | if(this.$.is('fieldset')) { 112 | 113 | // fieldset = array of integer 114 | this.v = {}; 115 | this.i = this.$.find('input') 116 | this.i.each(function(k) { 117 | var $this = $(this); 118 | s.i[k] = $this; 119 | s.v[k] = $this.val(); 120 | 121 | $this.bind( 122 | 'change' 123 | , function () { 124 | var val = {}; 125 | val[k] = $this.val(); 126 | s.val(val); 127 | } 128 | ); 129 | }); 130 | this.$.find('legend').remove(); 131 | 132 | } else { 133 | // input = integer 134 | this.i = this.$; 135 | this.v = this.$.val(); 136 | (this.v == '') && (this.v = this.o.min); 137 | 138 | this.$.bind( 139 | 'change' 140 | , function () { 141 | s.val(s.$.val()); 142 | } 143 | ); 144 | } 145 | 146 | (!this.o.displayInput) && this.$.hide(); 147 | 148 | this.$c = $(''); 151 | this.c = this.$c[0].getContext("2d"); 152 | 153 | this.$ 154 | .wrap($('
    ')) 157 | .before(this.$c); 158 | 159 | if (this.v instanceof Object) { 160 | this.cv = {}; 161 | this.copy(this.v, this.cv); 162 | } else { 163 | this.cv = this.v; 164 | } 165 | 166 | this.$ 167 | .bind("configure", cf) 168 | .parent() 169 | .bind("configure", cf); 170 | 171 | this._listen() 172 | ._configure() 173 | ._xy() 174 | .init(); 175 | 176 | this.isInit = true; 177 | 178 | this._draw(); 179 | 180 | return this; 181 | }; 182 | 183 | this._draw = function () { 184 | 185 | // canvas pre-rendering 186 | var d = true, 187 | c = document.createElement('canvas'); 188 | 189 | c.width = s.o.width; 190 | c.height = s.o.height; 191 | s.g = c.getContext('2d'); 192 | 193 | s.clear(); 194 | 195 | s.dH 196 | && (d = s.dH()); 197 | 198 | (d !== false) && s.draw(); 199 | 200 | s.c.drawImage(c, 0, 0); 201 | c = null; 202 | }; 203 | 204 | this._touch = function (e) { 205 | 206 | var touchMove = function (e) { 207 | 208 | var v = s.xy2val( 209 | e.originalEvent.touches[s.t].pageX, 210 | e.originalEvent.touches[s.t].pageY 211 | ); 212 | 213 | if (v == s.cv) return; 214 | 215 | if ( 216 | s.cH 217 | && (s.cH(v) === false) 218 | ) return; 219 | 220 | 221 | s.change(v); 222 | s._draw(); 223 | }; 224 | 225 | // get touches index 226 | this.t = k.c.t(e); 227 | 228 | // First touch 229 | touchMove(e); 230 | 231 | // Touch events listeners 232 | k.c.d 233 | .bind("touchmove.k", touchMove) 234 | .bind( 235 | "touchend.k" 236 | , function () { 237 | k.c.d.unbind('touchmove.k touchend.k'); 238 | 239 | if ( 240 | s.rH 241 | && (s.rH(s.cv) === false) 242 | ) return; 243 | 244 | s.val(s.cv); 245 | } 246 | ); 247 | 248 | return this; 249 | }; 250 | 251 | this._mouse = function (e) { 252 | 253 | var mouseMove = function (e) { 254 | var v = s.xy2val(e.pageX, e.pageY); 255 | if (v == s.cv) return; 256 | 257 | if ( 258 | s.cH 259 | && (s.cH(v) === false) 260 | ) return; 261 | 262 | s.change(v); 263 | s._draw(); 264 | }; 265 | 266 | // First click 267 | mouseMove(e); 268 | 269 | // Mouse events listeners 270 | k.c.d 271 | .bind("mousemove.k", mouseMove) 272 | .bind( 273 | // Escape key cancel current change 274 | "keyup.k" 275 | , function (e) { 276 | if (e.keyCode === 27) { 277 | k.c.d.unbind("mouseup.k mousemove.k keyup.k"); 278 | 279 | if ( 280 | s.eH 281 | && (s.eH() === false) 282 | ) return; 283 | 284 | s.cancel(); 285 | } 286 | } 287 | ) 288 | .bind( 289 | "mouseup.k" 290 | , function (e) { 291 | k.c.d.unbind('mousemove.k mouseup.k keyup.k'); 292 | 293 | if ( 294 | s.rH 295 | && (s.rH(s.cv) === false) 296 | ) return; 297 | 298 | s.val(s.cv); 299 | } 300 | ); 301 | 302 | return this; 303 | }; 304 | 305 | this._xy = function () { 306 | var o = this.$c.offset(); 307 | this.x = o.left; 308 | this.y = o.top; 309 | return this; 310 | }; 311 | 312 | this._listen = function () { 313 | 314 | if (!this.o.readOnly) { 315 | this.$c 316 | .bind( 317 | "mousedown" 318 | , function (e) { 319 | e.preventDefault(); 320 | s._xy()._mouse(e); 321 | } 322 | ) 323 | .bind( 324 | "touchstart" 325 | , function (e) { 326 | e.preventDefault(); 327 | s._xy()._touch(e); 328 | } 329 | ); 330 | this.listen(); 331 | } else { 332 | this.$.attr('readonly', 'readonly'); 333 | } 334 | 335 | return this; 336 | }; 337 | 338 | this._configure = function () { 339 | 340 | // Hooks 341 | if (this.o.draw) this.dH = this.o.draw; 342 | if (this.o.change) this.cH = this.o.change; 343 | if (this.o.cancel) this.eH = this.o.cancel; 344 | if (this.o.release) this.rH = this.o.release; 345 | 346 | if (this.o.displayPrevious) { 347 | this.pColor = this.h2rgba(this.o.fgColor, "0.4"); 348 | this.fgColor = this.h2rgba(this.o.fgColor, "0.6"); 349 | } else { 350 | this.fgColor = this.o.fgColor; 351 | } 352 | 353 | return this; 354 | }; 355 | 356 | this._clear = function () { 357 | this.$c[0].width = this.$c[0].width; 358 | }; 359 | 360 | // Abstract methods 361 | this.listen = function () {}; // on start, one time 362 | this.extend = function () {}; // each time configure triggered 363 | this.init = function () {}; // each time configure triggered 364 | this.change = function (v) {}; // on change 365 | this.val = function (v) {}; // on release 366 | this.xy2val = function (x, y) {}; // 367 | this.draw = function () {}; // on change / on release 368 | this.clear = function () { this._clear(); }; 369 | 370 | // Utils 371 | this.h2rgba = function (h, a) { 372 | var rgb; 373 | h = h.substring(1,7) 374 | rgb = [parseInt(h.substring(0,2),16) 375 | ,parseInt(h.substring(2,4),16) 376 | ,parseInt(h.substring(4,6),16)]; 377 | return "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "," + a + ")"; 378 | }; 379 | 380 | this.copy = function (f, t) { 381 | for (var i in f) { t[i] = f[i]; } 382 | }; 383 | }; 384 | 385 | 386 | /** 387 | * k.Dial 388 | */ 389 | k.Dial = function () { 390 | k.o.call(this); 391 | 392 | this.startAngle = null; 393 | this.xy = null; 394 | this.radius = null; 395 | this.lineWidth = null; 396 | this.cursorExt = null; 397 | this.w2 = null; 398 | this.PI2 = 2*Math.PI; 399 | 400 | this.extend = function () { 401 | this.o = $.extend( 402 | { 403 | bgColor : this.$.data('bgcolor') || '#EEEEEE', 404 | angleOffset : this.$.data('angleoffset') || 0, 405 | angleArc : this.$.data('anglearc') || 360, 406 | inline : true 407 | }, this.o 408 | ); 409 | }; 410 | 411 | this.val = function (v) { 412 | if (null != v) { 413 | this.cv = this.o.stopper ? max(min(v, this.o.max), this.o.min) : v; 414 | this.v = this.cv; 415 | this.$.val(this.v); 416 | this._draw(); 417 | } else { 418 | return this.v; 419 | } 420 | }; 421 | 422 | this.xy2val = function (x, y) { 423 | var a, ret; 424 | 425 | a = Math.atan2( 426 | x - (this.x + this.w2) 427 | , - (y - this.y - this.w2) 428 | ) - this.angleOffset; 429 | 430 | if(this.angleArc != this.PI2 && (a < 0) && (a > -0.5)) { 431 | // if isset angleArc option, set to min if .5 under min 432 | a = 0; 433 | } else if (a < 0) { 434 | a += this.PI2; 435 | } 436 | 437 | ret = ~~ (0.5 + (a * (this.o.max - this.o.min) / this.angleArc)) 438 | + this.o.min; 439 | 440 | this.o.stopper 441 | && (ret = max(min(ret, this.o.max), this.o.min)); 442 | 443 | return ret; 444 | }; 445 | 446 | this.listen = function () { 447 | // bind MouseWheel 448 | var s = this, 449 | mw = function (e) { 450 | e.preventDefault(); 451 | 452 | var ori = e.originalEvent 453 | ,deltaX = ori.detail || ori.wheelDeltaX 454 | ,deltaY = ori.detail || ori.wheelDeltaY 455 | ,v = parseInt(s.$.val()) + (deltaX>0 || deltaY>0 ? 1 : deltaX<0 || deltaY<0 ? -1 : 0); 456 | 457 | if ( 458 | s.cH 459 | && (s.cH(v) === false) 460 | ) return; 461 | 462 | s.val(v); 463 | } 464 | , kval, to, m = 1, kv = {37:-1, 38:1, 39:1, 40:-1}; 465 | 466 | this.$ 467 | .bind( 468 | "keydown" 469 | ,function (e) { 470 | var kc = e.keyCode; 471 | kval = parseInt(String.fromCharCode(kc)); 472 | 473 | if (isNaN(kval)) { 474 | 475 | (kc !== 13) // enter 476 | && (kc !== 8) // bs 477 | && (kc !== 9) // tab 478 | && (kc !== 189) // - 479 | && e.preventDefault(); 480 | 481 | // arrows 482 | if ($.inArray(kc,[37,38,39,40]) > -1) { 483 | e.preventDefault(); 484 | 485 | var v = parseInt(s.$.val()) + kv[kc] * m; 486 | 487 | s.o.stopper 488 | && (v = max(min(v, s.o.max), s.o.min)); 489 | 490 | s.change(v); 491 | s._draw(); 492 | 493 | // long time keydown speed-up 494 | to = window.setTimeout( 495 | function () { m*=2; } 496 | ,30 497 | ); 498 | } 499 | } 500 | } 501 | ) 502 | .bind( 503 | "keyup" 504 | ,function (e) { 505 | if (isNaN(kval)) { 506 | if (to) { 507 | window.clearTimeout(to); 508 | to = null; 509 | m = 1; 510 | s.val(s.$.val()); 511 | } 512 | } else { 513 | // kval postcond 514 | (s.$.val() > s.o.max && s.$.val(s.o.max)) 515 | || (s.$.val() < s.o.min && s.$.val(s.o.min)); 516 | } 517 | 518 | } 519 | ); 520 | 521 | this.$c.bind("mousewheel DOMMouseScroll", mw); 522 | this.$.bind("mousewheel DOMMouseScroll", mw) 523 | }; 524 | 525 | this.init = function () { 526 | 527 | if ( 528 | this.v < this.o.min 529 | || this.v > this.o.max 530 | ) this.v = this.o.min; 531 | 532 | this.$.val(this.v); 533 | this.w2 = this.o.width / 2; 534 | this.cursorExt = this.o.cursor / 100; 535 | this.xy = this.w2; 536 | this.lineWidth = this.xy * this.o.thickness; 537 | this.radius = this.xy - this.lineWidth / 2; 538 | 539 | this.o.angleOffset 540 | && (this.o.angleOffset = isNaN(this.o.angleOffset) ? 0 : this.o.angleOffset); 541 | 542 | this.o.angleArc 543 | && (this.o.angleArc = isNaN(this.o.angleArc) ? this.PI2 : this.o.angleArc); 544 | 545 | // deg to rad 546 | this.angleOffset = this.o.angleOffset * Math.PI / 180; 547 | this.angleArc = this.o.angleArc * Math.PI / 180; 548 | 549 | // compute start and end angles 550 | this.startAngle = 1.5 * Math.PI + this.angleOffset; 551 | this.endAngle = 1.5 * Math.PI + this.angleOffset + this.angleArc; 552 | 553 | var s = max( 554 | String(Math.abs(this.o.max)).length 555 | , String(Math.abs(this.o.min)).length 556 | , 2 557 | ) + 2; 558 | 559 | this.o.displayInput 560 | && this.i.css({ 561 | 'width' : ((this.o.width / 2 + 4) >> 0) + 'px' 562 | ,'height' : ((this.o.width / 3) >> 0) + 'px' 563 | ,'position' : 'absolute' 564 | ,'vertical-align' : 'middle' 565 | ,'margin-top' : ((this.o.width / 3) >> 0) + 'px' 566 | ,'margin-left' : '-' + ((this.o.width * 3 / 4 + 2) >> 0) + 'px' 567 | ,'border' : 0 568 | ,'background' : 'none' 569 | ,'font' : 'bold ' + ((this.o.width / s) >> 0) + 'px Arial' 570 | ,'text-align' : 'center' 571 | ,'color' : this.o.fgColor 572 | ,'padding' : '0px' 573 | ,'-webkit-appearance': 'none' 574 | }) 575 | || this.i.css({ 576 | 'width' : '0px' 577 | ,'visibility' : 'hidden' 578 | }); 579 | }; 580 | 581 | this.change = function (v) { 582 | this.cv = v; 583 | this.$.val(v); 584 | }; 585 | 586 | this.angle = function (v) { 587 | return (v - this.o.min) * this.angleArc / (this.o.max - this.o.min); 588 | }; 589 | 590 | this.draw = function () { 591 | 592 | var c = this.g, // context 593 | a = this.angle(this.cv) // Angle 594 | , sat = this.startAngle // Start angle 595 | , eat = sat + a // End angle 596 | , sa, ea // Previous angles 597 | , r = 1; 598 | 599 | c.lineWidth = this.lineWidth; 600 | 601 | this.o.cursor 602 | && (sat = eat - this.cursorExt) 603 | && (eat = eat + this.cursorExt); 604 | 605 | c.beginPath(); 606 | c.strokeStyle = this.o.bgColor; 607 | c.arc(this.xy, this.xy, this.radius, this.endAngle, this.startAngle, true); 608 | c.stroke(); 609 | 610 | if (this.o.displayPrevious) { 611 | ea = this.startAngle + this.angle(this.v); 612 | sa = this.startAngle; 613 | this.o.cursor 614 | && (sa = ea - this.cursorExt) 615 | && (ea = ea + this.cursorExt); 616 | 617 | c.beginPath(); 618 | c.strokeStyle = this.pColor; 619 | c.arc(this.xy, this.xy, this.radius, sa, ea, false); 620 | c.stroke(); 621 | r = (this.cv == this.v); 622 | } 623 | 624 | c.beginPath(); 625 | c.strokeStyle = r ? this.o.fgColor : this.fgColor ; 626 | c.arc(this.xy, this.xy, this.radius, sat, eat, false); 627 | c.stroke(); 628 | }; 629 | 630 | this.cancel = function () { 631 | this.val(this.v); 632 | }; 633 | }; 634 | 635 | $.fn.dial = $.fn.knob = function (o) { 636 | return this.each( 637 | function () { 638 | var d = new k.Dial(); 639 | d.o = o; 640 | d.$ = $(this); 641 | d.run(); 642 | } 643 | ).parent(); 644 | }; 645 | 646 | }); -------------------------------------------------------------------------------- /assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | /* 2 | //=require_directory . 3 | //=require_tree ../../widgets 4 | */ 5 | // ---------------------------------------------------------------------------- 6 | // Sass declarations 7 | // ---------------------------------------------------------------------------- 8 | $text-color: rgba(255, 255, 255, 0.97); 9 | $value-color: rgba(255, 255, 255, 0.97); 10 | 11 | $label-color: rgba(255, 255, 255, 0.7); 12 | $title-color: rgba(255, 255, 255, 0.7); 13 | $title-color-uns: rgba(0, 0, 0, 0.7); 14 | $moreinfo-color: rgba(255, 255, 255, 0.7); 15 | $updatedat-color: rgba(255, 255, 255, 0.0); //hide last updated entirely 16 | 17 | $background-color: transparent; 18 | $background-color-transparent-factor: 0.75; //change this to influence transparency factor on simplelist/list background color 19 | 20 | $background-color-green: rgba(68, 187, 119, $background-color-transparent-factor); 21 | $background-color-red: rgba(255, 85, 102, $background-color-transparent-factor); 22 | $background-color-yellow: rgba(255, 170, 68, $background-color-transparent-factor); 23 | $background-color-purple: rgba(170, 68, 255, $background-color-transparent-factor); 24 | $background-color-grey: rgba(153, 153, 153, $background-color-transparent-factor); 25 | 26 | $icinga-blue: rgba(0, 149, 191, 1); 27 | 28 | @mixin animation($animation-name, $duration, $function, $animation-iteration-count:""){ 29 | -webkit-animation: $animation-name $duration $function #{$animation-iteration-count}; 30 | -moz-animation: $animation-name $duration $function #{$animation-iteration-count}; 31 | -ms-animation: $animation-name $duration $function #{$animation-iteration-count}; 32 | } 33 | 34 | // ---------------------------------------------------------------------------- 35 | // Base styles 36 | // ---------------------------------------------------------------------------- 37 | html { 38 | background-image: url("icinga-hero-background.jpg"); 39 | background-color: $background-color; 40 | /* Stretch the background image, don't duplicate it */ 41 | -webkit-background-size: cover; /* For WebKit */ 42 | -moz-background-size: cover; /* Mozilla */ 43 | -o-background-size: cover; /* Opera */ 44 | background-size: cover; /* Generic */ 45 | 46 | font-size: 100%; 47 | -webkit-text-size-adjust: 100%; 48 | -ms-text-size-adjust: 100%; 49 | } 50 | 51 | body { 52 | margin: 0; 53 | font-size: 20px; 54 | color: $text-color; 55 | font-family: 'Open Sans', "Helvetica Neue", Helvetica, Arial, sans-serif; 56 | text-align: center; 57 | } 58 | 59 | b, strong { 60 | font-weight: bold; 61 | } 62 | 63 | a { 64 | text-decoration: none; 65 | color: inherit; 66 | } 67 | 68 | img { 69 | border: 0; 70 | -ms-interpolation-mode: bicubic; 71 | vertical-align: middle; 72 | } 73 | 74 | img, object { 75 | max-width: 100%; 76 | } 77 | 78 | iframe { 79 | max-width: 100%; 80 | } 81 | 82 | table { 83 | border-collapse: collapse; 84 | border-spacing: 0; 85 | width: 100%; 86 | } 87 | 88 | td { 89 | vertical-align: middle; 90 | } 91 | 92 | ul, ol { 93 | padding: 0; 94 | margin: 0; 95 | } 96 | 97 | h1, h2, h3, h4, h5, p { 98 | padding: 0; 99 | margin: 0; 100 | } 101 | h1 { 102 | margin-bottom: 16px; 103 | font-size: 24px; 104 | font-weight: 400; 105 | text-align: center; 106 | padding: 10px; 107 | border-bottom: 1px solid rgba(255, 255, 255, 0.25); 108 | color: rgba(255, 255, 255, 0.8); 109 | } 110 | h2 { 111 | text-transform: uppercase; 112 | font-size: 68px; 113 | font-weight: 700; 114 | color: $text-color; 115 | } 116 | h3 { 117 | font-size: 25px; 118 | font-weight: 600; 119 | color: $text-color; 120 | } 121 | 122 | #banner { 123 | text-align: left; 124 | padding: 10px; 125 | } 126 | 127 | // ---------------------------------------------------------------------------- 128 | // Base widget styles 129 | // ---------------------------------------------------------------------------- 130 | .gridster { 131 | margin: 0px auto; 132 | } 133 | 134 | .icon-background { 135 | width: 100%!important; 136 | height: 100%; 137 | position: absolute; 138 | left: 0; 139 | top: 0; 140 | opacity: 0.1; 141 | font-size: 275px; 142 | text-align: center; 143 | margin-top: 82px; 144 | } 145 | 146 | .list-nostyle { 147 | list-style: none; 148 | } 149 | 150 | .gridster ul { 151 | list-style: none; 152 | } 153 | 154 | .gs_w { 155 | width: 100%; 156 | display: table; 157 | cursor: pointer; 158 | } 159 | 160 | .widget { 161 | margin: 0; 162 | text-align: center; 163 | width: 100%; 164 | height: 100%; 165 | //background-color: $icinga-blue; 166 | background-color: $background-color; 167 | /* Hack to fill the surrounding list element */ 168 | position: absolute; 169 | top: 0; 170 | left: 0; 171 | /* Welcome to the future, way better than absolute positioning */ 172 | display: flex; 173 | flex-direction: column; 174 | /* Pimp mode */ 175 | border-top: 1px solid rgba(255, 255, 255, 0.4); 176 | border-bottom: 1px solid rgba(0, 0, 0, 0.4); 177 | border-left: 1px solid rgba(255, 255, 255, 0.4); 178 | border-right: 1px solid rgba(0, 0, 0, 0.4); 179 | box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.4); 180 | border-radius: 5px; 181 | } 182 | 183 | .header { 184 | font-size: 24px; 185 | font-weight: 600; 186 | padding: 10px; 187 | color: rgba(255, 255, 255, 0.8); 188 | } 189 | 190 | .content { 191 | height: 100%; 192 | padding: 10px; 193 | /* This prevents more-info and updated-at elements from vanishing */ 194 | /* If you update to 'overflow: auto;' you can get scroll bars */ 195 | overflow: hidden; 196 | } 197 | 198 | .more-info { 199 | padding: 5px; 200 | font-size: 15px; 201 | } 202 | 203 | .updated-at { 204 | padding: 5px; 205 | font-size: 12px; 206 | color: $updatedat-color; 207 | } 208 | 209 | #save-gridster { 210 | display: none; 211 | position: fixed; 212 | top: 0; 213 | margin: 0px auto; 214 | left: 50%; 215 | z-index: 1000; 216 | background: black; 217 | width: 190px; 218 | text-align: center; 219 | border: 1px solid white; 220 | border-top: 0px; 221 | margin-left: -95px; 222 | padding: 15px; 223 | } 224 | 225 | #save-gridster:hover { 226 | padding-top: 25px; 227 | } 228 | 229 | #saving-instructions { 230 | display: none; 231 | padding: 10px; 232 | width: 500px; 233 | height: 122px; 234 | z-index: 1000; 235 | background: white; 236 | top: 100px; 237 | color: black; 238 | font-size: 15px; 239 | padding-bottom: 4px; 240 | 241 | textarea { 242 | white-space: nowrap; 243 | width: 494px; 244 | height: 80px; 245 | } 246 | } 247 | 248 | #lean_overlay { 249 | position: fixed; 250 | z-index:100; 251 | top: 0px; 252 | left: 0px; 253 | height:100%; 254 | width:100%; 255 | background: #000; 256 | display: none; 257 | } 258 | 259 | #container { 260 | display: inline-block; 261 | } 262 | 263 | 264 | // ---------------------------------------------------------------------------- 265 | // Clearfix 266 | // ---------------------------------------------------------------------------- 267 | .clearfix:before, .clearfix:after { content: "\0020"; display: block; height: 0; overflow: hidden; } 268 | .clearfix:after { clear: both; } 269 | .clearfix { zoom: 1; } 270 | 271 | // ---------------------------------------------------------------------------- 272 | // Adding scrolling functionality to widgets for all browsers 273 | // ---------------------------------------------------------------------------- 274 | .scrollable { 275 | overflow: hidden; 276 | } 277 | 278 | .scrollable .content { 279 | height: 100%; 280 | overflow-y: scroll; 281 | margin-right: -50px; 282 | padding-right: 50px; 283 | } 284 | -------------------------------------------------------------------------------- /assets/stylesheets/jquery.gridster.css: -------------------------------------------------------------------------------- 1 | /*! gridster.js - v0.1.0 - 2012-08-14 2 | * http://gridster.net/ 3 | * Copyright (c) 2012 ducksboard; Licensed MIT */ 4 | 5 | .gridster { 6 | position:relative; 7 | } 8 | 9 | .gridster > * { 10 | margin: 0 auto; 11 | -webkit-transition: height .4s; 12 | -moz-transition: height .4s; 13 | -o-transition: height .4s; 14 | -ms-transition: height .4s; 15 | transition: height .4s; 16 | } 17 | 18 | .gridster .gs_w{ 19 | z-index: 2; 20 | position: absolute; 21 | } 22 | 23 | .ready .gs_w:not(.preview-holder) { 24 | -webkit-transition: opacity .3s, left .3s, top .3s; 25 | -moz-transition: opacity .3s, left .3s, top .3s; 26 | -o-transition: opacity .3s, left .3s, top .3s; 27 | transition: opacity .3s, left .3s, top .3s; 28 | } 29 | 30 | .gridster .preview-holder { 31 | z-index: 1; 32 | position: absolute; 33 | background-color: #fff; 34 | border-color: #fff; 35 | opacity: 0.3; 36 | } 37 | 38 | .gridster .player-revert { 39 | z-index: 10!important; 40 | -webkit-transition: left .3s, top .3s!important; 41 | -moz-transition: left .3s, top .3s!important; 42 | -o-transition: left .3s, top .3s!important; 43 | transition: left .3s, top .3s!important; 44 | } 45 | 46 | .gridster .dragging { 47 | z-index: 10!important; 48 | -webkit-transition: all 0s !important; 49 | -moz-transition: all 0s !important; 50 | -o-transition: all 0s !important; 51 | transition: all 0s !important; 52 | } 53 | 54 | /* Uncomment this if you set helper : "clone" in draggable options */ 55 | /*.gridster .player { 56 | opacity:0; 57 | }*/ -------------------------------------------------------------------------------- /assets/stylesheets/jquery.gridster.min.css: -------------------------------------------------------------------------------- 1 | /*! gridster.js - v0.5.1 - 2014-03-26 - * http://gridster.net/ - Copyright (c) 2014 ducksboard; Licensed MIT */ 2 | .gridster{position:relative}.gridster>*{margin:0 auto;-webkit-transition:height .4s,width .4s;-moz-transition:height .4s,width .4s;-o-transition:height .4s,width .4s;-ms-transition:height .4s,width .4s;transition:height .4s,width .4s}.gridster .gs-w{z-index:2;position:absolute}.ready .gs-w:not(.preview-holder){-webkit-transition:opacity .3s,left .3s,top .3s;-moz-transition:opacity .3s,left .3s,top .3s;-o-transition:opacity .3s,left .3s,top .3s;transition:opacity .3s,left .3s,top .3s}.ready .gs-w:not(.preview-holder),.ready .resize-preview-holder{-webkit-transition:opacity .3s,left .3s,top .3s,width .3s,height .3s;-moz-transition:opacity .3s,left .3s,top .3s,width .3s,height .3s;-o-transition:opacity .3s,left .3s,top .3s,width .3s,height .3s;transition:opacity .3s,left .3s,top .3s,width .3s,height .3s}.gridster .preview-holder{z-index:1;position:absolute;background-color:#fff;border-color:#fff;opacity:.3}.gridster .player-revert{z-index:10!important;-webkit-transition:left .3s,top .3s!important;-moz-transition:left .3s,top .3s!important;-o-transition:left .3s,top .3s!important;transition:left .3s,top .3s!important}.gridster .dragging,.gridster .resizing{z-index:10!important;-webkit-transition:all 0s!important;-moz-transition:all 0s!important;-o-transition:all 0s!important;transition:all 0s!important}.gs-resize-handle{position:absolute;z-index:1}.gs-resize-handle-both{width:20px;height:20px;bottom:-8px;right:-8px;background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg08IS0tIEdlbmVyYXRvcjogQWRvYmUgRmlyZXdvcmtzIENTNiwgRXhwb3J0IFNWRyBFeHRlbnNpb24gYnkgQWFyb24gQmVhbGwgKGh0dHA6Ly9maXJld29ya3MuYWJlYWxsLmNvbSkgLiBWZXJzaW9uOiAwLjYuMSAgLS0+DTwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DTxzdmcgaWQ9IlVudGl0bGVkLVBhZ2UlMjAxIiB2aWV3Qm94PSIwIDAgNiA2IiBzdHlsZT0iYmFja2dyb3VuZC1jb2xvcjojZmZmZmZmMDAiIHZlcnNpb249IjEuMSINCXhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbDpzcGFjZT0icHJlc2VydmUiDQl4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjZweCIgaGVpZ2h0PSI2cHgiDT4NCTxnIG9wYWNpdHk9IjAuMzAyIj4NCQk8cGF0aCBkPSJNIDYgNiBMIDAgNiBMIDAgNC4yIEwgNCA0LjIgTCA0LjIgNC4yIEwgNC4yIDAgTCA2IDAgTCA2IDYgTCA2IDYgWiIgZmlsbD0iIzAwMDAwMCIvPg0JPC9nPg08L3N2Zz4=);background-position:top left;background-repeat:no-repeat;cursor:se-resize;z-index:20}.gs-resize-handle-x{top:0;bottom:13px;right:-5px;width:10px;cursor:e-resize}.gs-resize-handle-y{left:0;right:13px;bottom:-5px;height:10px;cursor:s-resize}.gs-w:hover .gs-resize-handle,.resizing .gs-resize-handle{opacity:1}.gs-resize-handle,.gs-w.dragging .gs-resize-handle{opacity:0}.gs-resize-disabled .gs-resize-handle{display:none!important}[data-max-sizex="1"] .gs-resize-handle-x,[data-max-sizey="1"] .gs-resize-handle-y,[data-max-sizey="1"][data-max-sizex="1"] .gs-resize-handle{display:none!important} 3 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | require 'dashing' 2 | require './lib/icinga2' 3 | 4 | 5 | icinga = Icinga2.new('config/icinga2.json') 6 | set :icinga, icinga 7 | 8 | configure do 9 | set :auth_token, 'YOUR_AUTH_TOKEN' 10 | set :default_dashboard, '/icinga2'.prepend(icinga.url_prefix).gsub(/^\//, '') 11 | 12 | # allow iframes e.g. icingaweb2 13 | # https://github.com/Shopify/dashing/issues/199 14 | # thx Sandro Lang 15 | set :protection, :except => :frame_options 16 | 17 | helpers do 18 | def protected! 19 | # Put any authentication code you want in here. 20 | # This method is run before accessing any resource. 21 | end 22 | 23 | # Specify Icinga Web 2 URL 24 | # https://github.com/Shopify/dashing/issues/509 25 | def getIcingaWeb2Url() 26 | icinga = Sinatra::Application.icinga 27 | if icinga.icingaweb2_url != nil 28 | return icinga.icingaweb2_url 29 | else 30 | return 'http://192.168.33.5/icingaweb2' 31 | end 32 | end 33 | 34 | def getTimeZone() 35 | icinga = Sinatra::Application.icinga 36 | if icinga.time_zone != nil 37 | return icinga.time_zone 38 | else 39 | return "UTC" 40 | end 41 | end 42 | 43 | def getUrlPrefix() 44 | icinga = Sinatra::Application.icinga 45 | if icinga.url_prefix != nil 46 | return icinga.url_prefix 47 | else 48 | return "" 49 | end 50 | end 51 | end 52 | end 53 | 54 | set :assets_prefix, icinga.url_prefix + '/assets' 55 | map Sinatra::Application.assets_prefix do 56 | run Sinatra::Application.sprockets 57 | end 58 | 59 | run Rack::URLMap.new(icinga.url_prefix => Sinatra::Application) 60 | -------------------------------------------------------------------------------- /config/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mocdaniel/dashing-icinga2/2fc3aa4e150651299b7df7a83aeb43ccd95ac930/config/.gitignore -------------------------------------------------------------------------------- /config/icinga2.json: -------------------------------------------------------------------------------- 1 | { 2 | "icinga2": { 3 | "api": { 4 | "host": "localhost", 5 | "port": 5665, 6 | "user": "dashing", 7 | "password": "icinga2ondashingr0xx" 8 | } 9 | }, 10 | "icingaweb2": { 11 | "url": "http://localhost/icingaweb2" 12 | }, 13 | "dashboard": { 14 | "show_only_hard_state_problems": false, 15 | "timezone": "UTC", 16 | "url_prefix": "" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /dashboards/icinga2.erb: -------------------------------------------------------------------------------- 1 | 11 | 12 | <% content_for :title do %>Icinga 2<% end %> 13 |
    14 | 172 | 173 |
    174 | -------------------------------------------------------------------------------- /dashboards/layout.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | <%= yield_content(:title) %> 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
    24 | 27 | <%= yield %> 28 |
    29 | 30 | <% if development? %> 31 |
    32 |

    Paste the following at the top of <%= params[:dashboard] %>.erb

    33 | 34 |
    35 | Save this layout 36 | <% end %> 37 | 38 | 39 | -------------------------------------------------------------------------------- /docker/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # ensure that dashing runs with utf8 4 | export LANG=en_US.UTF-8 5 | export LC_CTYPE=en_US.UTF-8 6 | export LC_ALL=en_US.UTF-8 7 | 8 | puma config.ru -p 8005 9 | -------------------------------------------------------------------------------- /jobs/buzzwords.rb: -------------------------------------------------------------------------------- 1 | buzzwords = ['Paradigm shift', 'Leverage', 'Pivoting', 'Turn-key', 'Streamlininess', 'Exit strategy', 'Synergy', 'Enterprise', 'Web 2.0'] 2 | buzzword_counts = Hash.new({ value: 0 }) 3 | 4 | SCHEDULER.every '2s' do 5 | random_buzzword = buzzwords.sample 6 | buzzword_counts[random_buzzword] = { label: random_buzzword, value: (buzzword_counts[random_buzzword][:value] + 1) % 30 } 7 | 8 | send_event('buzzwords', { items: buzzword_counts.values }) 9 | end -------------------------------------------------------------------------------- /jobs/convergence.rb: -------------------------------------------------------------------------------- 1 | # Populate the graph with some random points 2 | points = [] 3 | (1..10).each do |i| 4 | points << { x: i, y: rand(50) } 5 | end 6 | last_x = points.last[:x] 7 | 8 | SCHEDULER.every '2s' do 9 | points.shift 10 | last_x += 1 11 | points << { x: last_x, y: rand(50) } 12 | 13 | send_event('convergence', points: points) 14 | end -------------------------------------------------------------------------------- /jobs/icinga2.rb: -------------------------------------------------------------------------------- 1 | #/****************************************************************************** 2 | # * Icinga 2 Dashing Job * 3 | # * Copyright (C) 2015-2016 Icinga Development Team (https://www.icinga.org) * 4 | # * * 5 | # * This program is free software; you can redistribute it and/or * 6 | # * modify it under the terms of the GNU General Public License * 7 | # * as published by the Free Software Foundation; either version 2 * 8 | # * of the License, or (at your option) any later version. * 9 | # * * 10 | # * This program is distributed in the hope that it will be useful, * 11 | # * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | # * GNU General Public License for more details. * 14 | # * * 15 | # * You should have received a copy of the GNU General Public License * 16 | # * along with this program; if not, write to the Free Software Foundation * 17 | # * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * 18 | # ******************************************************************************/ 19 | 20 | require './lib/icinga2' 21 | 22 | # initialize data provider 23 | icinga = Icinga2.new('config/icinga2.json') # fixed path 24 | 25 | SCHEDULER.every '10s', :first_in => 0 do |job| 26 | # run data provider 27 | icinga.run 28 | 29 | #puts "App Info: " + icinga.app_data.to_s + " Version: " + icinga.version 30 | #puts "CIB Info: " + icinga.cib_data.to_s 31 | 32 | # meter widget 33 | # we'll update the patched meter widget with absolute values (set max dynamically) 34 | host_meter = icinga.host_count_problems.to_f 35 | host_meter_max = icinga.host_count_all 36 | service_meter = icinga.service_count_problems.to_f 37 | service_meter_max = icinga.service_count_all 38 | 39 | #puts "Meter widget: Hosts " + host_meter.to_s + "/" + host_meter_max.to_s + " Services " + service_meter.to_s + "/" + service_meter_max.to_s 40 | 41 | # icinga stats 42 | icinga_stats = [ 43 | {"label" => "Host checks/min", "value" => icinga.host_active_checks_1min}, 44 | {"label" => "Service checks/min", "value" => icinga.service_active_checks_1min}, 45 | ] 46 | 47 | wqStats, clusterStats = icinga.getIcingaStats() 48 | 49 | wqStats.each do |name, value| 50 | icinga_stats.push( { "label" => name, "value" => "%0.2f" % value } ) 51 | end 52 | #clusterStats.each do |name, value| 53 | # icinga_stats.push( { "label" => name, "value" => "%0.2f" % value } ) 54 | #end 55 | 56 | #puts "Stats: " + icinga_stats.to_s 57 | 58 | ### Events 59 | send_event('icinga-host-meter', { 60 | value: host_meter, 61 | max: host_meter_max, 62 | moreinfo: "Total hosts: " + host_meter_max.to_s, 63 | color: 'blue' }) 64 | 65 | send_event('icinga-service-meter', { 66 | value: service_meter, 67 | max: service_meter_max, 68 | moreinfo: "Total services: " + service_meter_max.to_s, 69 | color: 'blue' }) 70 | 71 | send_event('icinga-stats', { 72 | title: "Icinga " + icinga.version, 73 | items: icinga_stats, 74 | moreinfo: "Avg latency: " + icinga.avg_latency.to_s + "s", 75 | color: 'blue' }) 76 | 77 | #### Dougnuts 78 | send_event('doughnut-pie-hosts', { 79 | type: "doughnut", 80 | header: "Hosts", 81 | labels: [ "UP", "Down" ], 82 | datasets: [ icinga.host_count_up, icinga.host_count_problems_down], 83 | }) 84 | 85 | send_event('doughnut-pie-services', { 86 | type: "doughnut", 87 | header: "Services", 88 | labels: [ "OK", "Warning", "Critical", "Unknown" ], 89 | datasets: [ icinga.service_count_ok, icinga.service_count_problems_warning, icinga.service_count_problems_critical, icinga.service_count_problems_unknown], 90 | }) 91 | send_event('bar-chart-endpoints', { 92 | header: "Endpoints", 93 | labels: [ "Connected", "Not Connected" ], 94 | datasets: [ clusterStats["num_conn_endpoints"], clusterStats["num_not_conn_endpoints"]], 95 | }) 96 | 97 | #### Bar Charts 98 | send_event('bar-chart-checks', { 99 | #type: "horizontalBar", 100 | type: "bar", 101 | header: "Active Checks", 102 | labels: [ "Hosts/min", "Services/min" ], 103 | datasets: [ icinga.host_active_checks_1min, icinga.service_active_checks_1min], 104 | }) 105 | send_event('bar-chart-downtimes', { 106 | #type: "horizontalBar", 107 | type: "bar", 108 | header: "Downtimes", 109 | labels: [ "Hosts", "Services" ], 110 | datasets: [ icinga.host_count_in_downtime, icinga.service_count_in_downtime], 111 | }) 112 | send_event('bar-chart-acks', { 113 | #type: "horizontalBar", 114 | type: "bar", 115 | header: "Acknowledgements", 116 | labels: [ "Hosts", "Services" ], 117 | datasets: [ icinga.host_count_acknowledged, icinga.service_count_acknowledged], 118 | }) 119 | 120 | # send_event('icinga-host-meter', { 121 | # value: host_meter, 122 | # max: host_meter_max, 123 | # moreinfo: "Total hosts: " + host_meter_max.to_s, 124 | # color: 'blue' }) 125 | # 126 | # send_event('icinga-service-meter', { 127 | # value: service_meter, 128 | # max: service_meter_max, 129 | # moreinfo: "Total services: " + service_meter_max.to_s, 130 | # color: 'blue' }) 131 | 132 | # handled stats 133 | # handled_stats = [ 134 | # {"label" => "Acknowledgements", "color" => "blue"}, 135 | # {"label" => "Hosts", "value" => icinga.host_count_acknowledged}, 136 | # {"label" => "Services", "value" => icinga.service_count_acknowledged}, 137 | # {"label" => "Downtimes", "color" => "blue"}, 138 | # {"label" => "Hosts", "value" => icinga.host_count_in_downtime}, 139 | # {"label" => "Services", "value" => icinga.service_count_in_downtime}, 140 | # ] 141 | # 142 | # send_event('handled-stats', { 143 | # items: handled_stats, 144 | # color: 'blue' }) 145 | 146 | # problem services 147 | severity_stats = [] 148 | icinga.service_problems_severity.each do |name, state| 149 | severity_stats.push({ 150 | "label" => icinga.formatService(name), 151 | "color" => icinga.stateToColor(state.to_int, false), 152 | "state" => state.to_int 153 | }) 154 | end 155 | 156 | order = [ 2,1,3 ] 157 | result = severity_stats.sort do |a, b| 158 | order.index(a['state']) <=> order.index(b['state']) 159 | end 160 | 161 | #puts "Severity: " + result.to_s 162 | 163 | send_event('icinga-severity', { 164 | items: result, 165 | color: 'blue' }) 166 | 167 | # Combined view of unhandled host problems (only if there are some) 168 | unhandled_host_problems = [] 169 | 170 | if (icinga.host_count_problems_down > 0) 171 | unhandled_host_problems.push( 172 | { "color" => icinga.stateToColor(1, true), "value" => icinga.host_count_problems_down }, 173 | ) 174 | end 175 | 176 | send_event('icinga-host-problems', { 177 | items: unhandled_host_problems, 178 | moreinfo: "All Problems: " + icinga.host_count_problems_down.to_s 179 | }) 180 | 181 | # Combined view of unhandled service problems (only if there are some) 182 | unhandled_service_problems = [] 183 | 184 | if (icinga.service_count_problems_critical > 0) 185 | unhandled_service_problems.push( 186 | { "color" => icinga.stateToColor(2, false), "value" => icinga.service_count_problems_critical }, 187 | ) 188 | end 189 | if (icinga.service_count_problems_warning > 0) 190 | unhandled_service_problems.push( 191 | { "color" => icinga.stateToColor(1, false), "value" => icinga.service_count_problems_warning }, 192 | ) 193 | end 194 | if (icinga.service_count_problems_unknown > 0) 195 | unhandled_service_problems.push( 196 | { "color" => icinga.stateToColor(3, false), "value" => icinga.service_count_problems_unknown } 197 | ) 198 | end 199 | 200 | send_event('icinga-service-problems', { 201 | items: unhandled_service_problems, 202 | moreinfo: "All Problems: " + (icinga.service_count_problems_critical + icinga.service_count_problems_warning + icinga.service_count_problems_unknown).to_s 203 | }) 204 | end 205 | 206 | -------------------------------------------------------------------------------- /jobs/sample.rb: -------------------------------------------------------------------------------- 1 | current_valuation = 0 2 | current_karma = 0 3 | 4 | SCHEDULER.every '2s' do 5 | last_valuation = current_valuation 6 | last_karma = current_karma 7 | current_valuation = rand(100) 8 | current_karma = rand(200000) 9 | 10 | send_event('valuation', { current: current_valuation, last: last_valuation }) 11 | send_event('karma', { current: current_karma, last: last_karma }) 12 | send_event('synergy', { value: rand(100) }) 13 | end -------------------------------------------------------------------------------- /lib/icinga2.rb: -------------------------------------------------------------------------------- 1 | #/****************************************************************************** 2 | # * Icinga 2 Dashing Job Library * 3 | # * Copyright (C) 2016-2017 Icinga Development Team (https://www.icinga.com) * 4 | # * * 5 | # * This program is free software; you can redistribute it and/or * 6 | # * modify it under the terms of the GNU General Public License * 7 | # * as published by the Free Software Foundation; either version 2 * 8 | # * of the License, or (at your option) any later version. * 9 | # * * 10 | # * This program is distributed in the hope that it will be useful, * 11 | # * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | # * GNU General Public License for more details. * 14 | # * * 15 | # * You should have received a copy of the GNU General Public License * 16 | # * along with this program; if not, write to the Free Software Foundation * 17 | # * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * 18 | # ******************************************************************************/ 19 | 20 | require 'json' 21 | require 'rest-client' 22 | require 'openssl' 23 | require 'logger' 24 | require 'time' 25 | 26 | class Icinga2 27 | # general info 28 | attr_reader :version 29 | attr_reader :node_name 30 | attr_reader :app_starttime 31 | attr_reader :uptime 32 | attr_reader :icingaweb2_url 33 | attr_reader :time_zone 34 | attr_reader :url_prefix 35 | 36 | # general stats 37 | attr_reader :avg_latency 38 | attr_reader :avg_execution_time 39 | attr_reader :host_active_checks_1min 40 | attr_reader :host_passive_checks_1min 41 | attr_reader :service_active_checks_1min 42 | attr_reader :service_passive_checks_1min 43 | 44 | attr_reader :service_problems_severity 45 | 46 | # host stats 47 | attr_reader :host_count_all 48 | attr_reader :host_count_problems 49 | attr_reader :host_count_problems_down 50 | attr_reader :host_count_up 51 | attr_reader :host_count_down 52 | attr_reader :host_count_in_downtime 53 | attr_reader :host_count_acknowledged 54 | 55 | # service stats 56 | attr_reader :service_count_all 57 | attr_reader :service_count_problems 58 | attr_reader :service_count_problems_warning 59 | attr_reader :service_count_problems_critical 60 | attr_reader :service_count_problems_unknown 61 | attr_reader :service_count_ok 62 | attr_reader :service_count_warning 63 | attr_reader :service_count_critical 64 | attr_reader :service_count_unknown 65 | attr_reader :service_count_in_downtime 66 | attr_reader :service_count_acknowledged 67 | 68 | # data providers 69 | attr_reader :app_data 70 | attr_reader :cib_data 71 | attr_reader :all_hosts_data 72 | attr_reader :all_services_data 73 | 74 | def initialize(configFile) 75 | # add logger 76 | file = File.open('/tmp/dashing-icinga2.log', File::WRONLY | File::APPEND | File::CREAT) 77 | @log = Logger.new(file, 'daily', 1024000) 78 | @log.level = Logger::INFO 79 | @log.datetime_format = "%Y-%m-%d %H:%M:%S" 80 | @log.formatter = proc do |severity, datetime, progname, msg| 81 | "[#{datetime.strftime(@log.datetime_format)}] #{severity.ljust(5)} : #{msg}\n" 82 | end 83 | 84 | # get configuration settings 85 | begin 86 | puts "First trying to read environment variables" 87 | getConfEnv() 88 | rescue 89 | puts "Environment variables not found, falling back to configuration file " + configFile 90 | getConfFile(configFile) 91 | end 92 | 93 | @apiVersion = "v1" 94 | @apiUrlBase = sprintf('https://%s:%d/%s', @host, @port, @apiVersion) 95 | 96 | @hasCert = false 97 | checkCert() 98 | 99 | @headers = { 100 | "Content-Type" => "application/json", 101 | "Accept" => "application/json" 102 | } 103 | end 104 | 105 | def getConfEnv() 106 | # prefer environment variables over the configuration file 107 | @host = ENV['ICINGA2_API_HOST'] 108 | @port = ENV['ICINGA2_API_PORT'] 109 | @user = ENV['ICINGA2_API_USERNAME'] 110 | @password = ENV['ICINGA2_API_PASSWORD'] 111 | @pkiPath = ENV['ICINGA2_API_CERT_PATH'] 112 | @nodeName = ENV['ICINGA2_API_NODENAME'] 113 | 114 | # external attribute 115 | @icingaweb2_url = ENV['ICINGAWEB2_URL'] 116 | 117 | # dashboards 118 | @showOnlyHardStateProblems = ENV['DASHBOARD_SHOW_ONLY_HARD_STATE_PROBLEMS'] 119 | @time_zone = ENV['DASHBOARD_TIMEZONE'] 120 | @url_prefix = ENV['DASHBOARD_URL_PREFIX'] 121 | 122 | # check for the least required variables, the rest is read later on 123 | if [@host, @port].all? {|value| value.nil? or value == ""} 124 | raise ArgumentError.new('Required environment variables not found!') 125 | end 126 | 127 | puts "Using environment variable configuration on '" + @host + ":" + @port + "'." 128 | end 129 | 130 | def getConfFile(configFile) 131 | configFile = File.expand_path(configFile) 132 | @log.debug(sprintf( ' config file : %s', configFile)) 133 | 134 | # Allow to use 'icinga2.local.json' or any other '.local.json' defined in jobs 135 | configFileLocal = File.dirname(configFile) + "/" + File.basename(configFile,File.extname(configFile)) + ".local" + File.extname(configFile) 136 | 137 | puts "Detecting local config file '" + configFileLocal + "'." 138 | 139 | if (File.exist?(configFileLocal)) 140 | realConfigFile = configFileLocal 141 | else 142 | realConfigFile = configFile 143 | end 144 | 145 | @log.info(sprintf('Using config file \'%s\'', realConfigFile)) 146 | puts "Using config file '" + realConfigFile + "'." 147 | 148 | begin 149 | if (File.exist?(realConfigFile)) 150 | file = File.read(realConfigFile) 151 | @config = JSON.parse(file) 152 | 153 | puts "Reading config" + @config.to_s 154 | 155 | if @config.key? 'icinga2' 156 | config_icinga2 = @config['icinga2'] 157 | 158 | if config_icinga2.key? 'api' 159 | @host = @config["icinga2"]["api"]["host"] 160 | @port = @config["icinga2"]["api"]["port"] 161 | @user = @config["icinga2"]["api"]["user"] 162 | @password = @config["icinga2"]["api"]["password"] 163 | @pkiPath = @config["icinga2"]["api"]["pki_path"] 164 | @nodeName = @config['icinga2']['api']['node_name'] 165 | end 166 | end 167 | 168 | if @config.key? 'dashboard' 169 | config_dashboard = @config['dashboard'] 170 | 171 | if config_dashboard.key? 'show_only_hard_state_problems' # retire this check later 172 | @showOnlyHardStateProblems = config_dashboard['show_only_hard_state_problems'] 173 | end 174 | 175 | if config_dashboard.key? 'timezone' 176 | @time_zone = config_dashboard['timezone'] 177 | end 178 | 179 | if config_dashboard.key? 'url_prefix' 180 | @url_prefix = config_dashboard['url_prefix'] 181 | end 182 | end 183 | 184 | if @config.key? 'icingaweb2' 185 | # external attribute 186 | @icingaweb2_url = @config['icingaweb2']['url'] 187 | end 188 | else 189 | @log.warn(sprintf('Config file %s not found! Using default config.', configFile)) 190 | @host = "localhost" 191 | @port = 5665 192 | @user = "dashing" 193 | @password = "icinga2ondashingr0xx" 194 | @pkiPath = "pki/" 195 | @nodeName = nil 196 | @showOnlyHardStateProblems = false 197 | @time_zone = "UTC" 198 | @url_prefix = "" 199 | 200 | # external attribute 201 | @icingaweb2_url = 'http://localhost/icingaweb2' 202 | end 203 | 204 | rescue JSON::ParserError => e 205 | @log.error('wrong result (no json)') 206 | @log.error(e) 207 | end 208 | end 209 | 210 | def checkCert() 211 | unless @nodeName 212 | begin 213 | @nodeName = Socket.gethostbyname(Socket.gethostname).first 214 | @log.debug(sprintf('node name: %s', @nodeName)) 215 | rescue SocketError => error 216 | @log.error(error) 217 | end 218 | end 219 | 220 | if File.file?(sprintf('%s/%s.crt', @pkiPath, @nodeName)) 221 | @log.debug("PKI found, using client certificates for connection to Icinga 2 API") 222 | certFile = File.read(sprintf('%s/%s.crt', @pkiPath, @nodeName)) 223 | keyFile = File.read(sprintf('%s/%s.key', @pkiPath, @nodeName)) 224 | caFile = File.read(sprintf('%s/ca.crt', @pkiPath)) 225 | 226 | cert = OpenSSL::X509::Certificate.new(certFile) 227 | key = OpenSSL::PKey::RSA.new(keyFile) 228 | 229 | @options = { 230 | :ssl_client_cert => cert, 231 | :ssl_client_key => key, 232 | :ssl_ca_file => caFile, 233 | :verify_ssl => OpenSSL::SSL::VERIFY_NONE # FIXME 234 | } 235 | 236 | @hasCert = true 237 | else 238 | @log.debug("PKI not found, using basic auth for connection to Icinga 2 API") 239 | 240 | @options = { 241 | :user => @user, 242 | :password => @password, 243 | :verify_ssl => OpenSSL::SSL::VERIFY_NONE # FIXME 244 | } 245 | 246 | @hasCert = false 247 | end 248 | end 249 | 250 | def getApiData(apiUrl, requestBody = nil) 251 | restClient = RestClient::Resource.new(apiUrl, @options) 252 | 253 | maxRetries = 30 254 | retried = 0 255 | 256 | begin 257 | if requestBody 258 | @headers["X-HTTP-Method-Override"] = "GET" 259 | payload = JSON.generate(requestBody) 260 | 261 | # debug 262 | #puts "Payload: " + payload 263 | res = restClient.post(payload, @headers) 264 | else 265 | res = restClient.get(@headers) 266 | end 267 | rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH => e 268 | if (retried < maxRetries) 269 | retried += 1 270 | $stderr.puts(format("Cannot execute request against '%s': '%s' (retry %d / %d)", apiUrl, e, retried, maxRetries)) 271 | sleep(2) 272 | retry 273 | else 274 | $stderr.puts("Maximum retries (%d) against '%s' reached. Giving up ...", maxRetries, apiUrl) 275 | return nil 276 | end 277 | end 278 | 279 | body = res.body 280 | # debug 281 | #puts "Body: " + body 282 | data = JSON.parse(body) 283 | 284 | return data 285 | end 286 | 287 | def getIcingaApplicationData() 288 | apiUrl = sprintf('%s/status/IcingaApplication', @apiUrlBase) 289 | data = getApiData(apiUrl) 290 | 291 | if not data or not data.has_key?('results') or data['results'].empty? or not data['results'][0].has_key?('status') 292 | return nil 293 | end 294 | 295 | return data['results'][0]['status'] #there's only one row 296 | end 297 | 298 | def getCIBData() 299 | apiUrl = sprintf('%s/status/CIB', @apiUrlBase) 300 | data = getApiData(apiUrl) 301 | 302 | if not data or not data.has_key?('results') or data['results'].empty? or not data['results'][0].has_key?('status') 303 | return nil 304 | end 305 | 306 | return data['results'][0]['status'] #there's only one row 307 | end 308 | 309 | def getStatusData() 310 | apiUrl = sprintf('%s/status', @apiUrlBase) 311 | data = getApiData(apiUrl) 312 | 313 | if not data or not data.has_key?('results') 314 | return nil 315 | end 316 | 317 | return data['results'] 318 | end 319 | 320 | def getHostObjects(attrs = nil, filter = nil, joins = nil) 321 | apiUrl = sprintf('%s/objects/hosts', @apiUrlBase) 322 | 323 | requestBody = {} 324 | 325 | if (attrs) 326 | requestBody["attrs"] = attrs 327 | end 328 | 329 | if (filter) 330 | requestBody["filter"] = filter 331 | end 332 | 333 | if (joins) 334 | requestBody["joins"] = joins 335 | end 336 | 337 | # fetch data with requestBody (which means X-HTTP-Method-Override: GET) 338 | data = getApiData(apiUrl, requestBody) 339 | 340 | if not data or not data.has_key?('results') 341 | return nil 342 | end 343 | 344 | return data['results'] 345 | end 346 | 347 | def getServiceObjects(attrs = nil, filter = nil, joins = nil) 348 | apiUrl = sprintf('%s/objects/services', @apiUrlBase) 349 | 350 | requestBody = {} 351 | 352 | if (attrs) 353 | requestBody["attrs"] = attrs 354 | end 355 | 356 | if (filter) 357 | requestBody["filter"] = filter 358 | end 359 | 360 | tmpJoin = [ "host" ] 361 | 362 | if (joins) 363 | requestBody["joins"] = joins 364 | end 365 | 366 | #puts "request body: " + requestBody.to_s 367 | 368 | # fetch data with requestBody (which means X-HTTP-Method-Override: GET) 369 | data = getApiData(apiUrl, requestBody) 370 | 371 | if not data or not data.has_key?('results') 372 | return nil 373 | end 374 | 375 | return data['results'] 376 | end 377 | 378 | def formatService(name) 379 | service_map = name.split('!', 2) 380 | return service_map[0].to_s 381 | end 382 | 383 | def stateFromString(stateStr) 384 | if (stateStr == "Down" or stateStr == "Warning") 385 | return 1 386 | elif (stateStr == "Up" or stateStr == "OK") 387 | return 0 388 | elif (stateStr == "Critical") 389 | return 2 390 | elif (stateStr == "Unknown") 391 | return 3 392 | end 393 | 394 | return "Undefined state. Programming error." 395 | end 396 | 397 | def stateToString(state, is_host = false) 398 | if (is_host && state >= 1) 399 | return "Down" 400 | elsif (is_host && state == 0) 401 | return "Up" 402 | elsif (state == 0) 403 | return "OK" 404 | elsif (state == 1) 405 | return "Warning" 406 | elsif (state == 2) 407 | return "Critical" 408 | elsif (state == 3) 409 | return "Unknown" 410 | end 411 | 412 | return "Undefined state. Programming error." 413 | end 414 | 415 | def stateToColor(state, is_host = false) 416 | if (is_host && state >= 1) 417 | return "red" 418 | elsif (is_host && state == 0) 419 | return "green" 420 | elsif (state == 0) 421 | return "green" 422 | elsif (state == 1) 423 | return "yellow" 424 | elsif (state == 2) 425 | return "red" 426 | elsif (state == 3) 427 | return "purple" 428 | end 429 | 430 | return "Undefined state. Programming error." 431 | end 432 | 433 | def countProblems(objects, states = nil) 434 | problems = 0 435 | 436 | compStates = [] 437 | 438 | if not states 439 | compStates = [ 1, 2, 3] 440 | end 441 | 442 | if states.is_a?(Integer) 443 | compStates.push(states) 444 | end 445 | 446 | objects.each do |item| 447 | item.each do |k, d| 448 | if (k != "attrs") 449 | next 450 | end 451 | 452 | if @showOnlyHardStateProblems 453 | if (compStates.include?(d["state"]) && d["downtime_depth"] == 0 && d["acknowledgement"] == 0 && d['state_type'] != 0.0) 454 | problems = problems + 1 455 | end 456 | else 457 | if (compStates.include?(d["state"]) && d["downtime_depth"] == 0 && d["acknowledgement"] == 0) 458 | problems = problems + 1 459 | end 460 | end 461 | end 462 | end 463 | 464 | return problems 465 | end 466 | 467 | def countProblemsServices(objects, states = nil) 468 | problems = 0 469 | 470 | compStates = [] 471 | 472 | if not states 473 | compStates = [ 1, 2, 3] 474 | end 475 | 476 | if states.is_a?(Integer) 477 | compStates.push(states) 478 | end 479 | 480 | objects.each do |item| 481 | item.each do |k, d| 482 | if (k != "attrs") 483 | next 484 | end 485 | 486 | if @showOnlyHardStateProblems 487 | if (compStates.include?(d["state"]) && d["downtime_depth"] == 0 && d["acknowledgement"] == 0 && d['state_type'] != 0.0) 488 | if (item["joins"]["host"]["state"] == 0.0) 489 | problems = problems + 1 490 | end 491 | end 492 | else 493 | if (compStates.include?(d["state"]) && d["downtime_depth"] == 0 && d["acknowledgement"] == 0) 494 | problems = problems + 1 495 | end 496 | end 497 | end 498 | end 499 | 500 | return problems 501 | end 502 | 503 | # use last_check here, takes less traffic than the entire check result 504 | def getObjectHasBeenChecked(object) 505 | return object["attrs"]["last_check"] > 0 506 | end 507 | 508 | # stolen from Icinga Web 2, ./modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatusQuery.php 509 | def getHostSeverity(host) 510 | attrs = host["attrs"] 511 | 512 | severity = 0 513 | 514 | if (attrs["state"] == 0) 515 | if (getObjectHasBeenChecked(host)) 516 | severity += 16 517 | end 518 | 519 | if (attrs["acknowledgement"] != 0) 520 | severity += 2 521 | elsif (attrs["downtime_depth"] > 0) 522 | severity += 1 523 | else 524 | severity += 4 525 | end 526 | else 527 | if (getObjectHasBeenChecked(host)) 528 | severity += 16 529 | elsif (attrs["state"] == 1) 530 | severity += 32 531 | elsif (attrs["state"] == 2) 532 | severity += 64 533 | else 534 | severity += 256 535 | end 536 | 537 | if (attrs["acknowledgement"] != 0) 538 | severity += 2 539 | elsif (attrs["downtime_depth"] > 0) 540 | severity += 1 541 | else 542 | severity += 4 543 | end 544 | end 545 | 546 | return severity 547 | end 548 | 549 | # stolen from Icinga Web 2, ./modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatusQuery.php 550 | def getServiceSeverity(service) 551 | attrs = service["attrs"] 552 | 553 | severity = 0 554 | 555 | if (attrs["state"] == 0) 556 | if (getObjectHasBeenChecked(service)) 557 | severity += 16 558 | end 559 | 560 | if (attrs["acknowledgement"] != 0) 561 | severity += 2 562 | elsif (attrs["downtime_depth"] > 0) 563 | severity += 1 564 | else 565 | severity += 4 566 | end 567 | else 568 | if (getObjectHasBeenChecked(service)) 569 | severity += 16 570 | elsif (attrs["state"] == 1) 571 | severity += 32 572 | elsif (attrs["state"] == 2) 573 | severity += 128 574 | elsif (attrs["state"] == 3) 575 | severity += 64 576 | else 577 | severity += 256 578 | end 579 | 580 | # requires joins 581 | host_attrs = service["joins"]["host"] 582 | 583 | if (host_attrs["state"] > 0) 584 | severity += 1024 585 | elsif (attrs["acknowledgement"]) 586 | severity += 512 587 | elsif (attrs["downtime_depth"] > 0) 588 | severity += 256 589 | else 590 | severity += 2048 591 | end 592 | end 593 | 594 | return severity 595 | end 596 | 597 | def getProblemServices(all_services_data, all_hosts_data, max_items = 30) 598 | service_problems = {} 599 | host_problems = {} 600 | 601 | if(all_hosts_data != nil) 602 | testw = "not nil." 603 | else 604 | testw = "nil." 605 | end 606 | 607 | all_services_data.each do |service| 608 | #puts "Severity for " + service["name"] + ": " + getServiceSeverity(service).to_s 609 | 610 | if (service["attrs"]["state"] == 0) or 611 | (service["attrs"]["downtime_depth"] > 0) or 612 | (service["attrs"]["acknowledgement"] > 0) or 613 | (service["joins"]["host"]["state"] == 1.0) 614 | next 615 | end 616 | 617 | if @showOnlyHardStateProblems and (service["attrs"]["state_type"] == 0.0) 618 | next 619 | end 620 | 621 | service_problems[service] = getServiceSeverity(service) 622 | end 623 | 624 | count = 0 625 | service_problems_severity = {} 626 | 627 | all_hosts_data.each do |host| 628 | if (host["attrs"]["state"] == 0) or 629 | (host["attrs"]["downtime_depth"] > 0) or 630 | (host["attrs"]["acknowledgement"] > 0) 631 | next 632 | end 633 | 634 | if @showOnlyHardStateProblems and (host["attrs"]["state_type"] == 0.0) 635 | next 636 | end 637 | 638 | host_display_name = host["attrs"]["display_name"] 639 | service_problems_severity[host_display_name + " is DOWN"] = 2.0 640 | end 641 | 642 | service_problems.sort_by {|k, v| v}.reverse.each do |obj, severity| 643 | if (count >= max_items) 644 | break 645 | end 646 | 647 | host_display_name = obj["joins"]["host"]["display_name"] 648 | host_display_name_clean = host_display_name.split('(')[0].split('|')[0] 649 | host_display_name_clean_split = host_display_name_clean.split(': ') 650 | host_display_name_for_display = host_display_name_clean_split[0].to_s 651 | service_display_name = obj["attrs"]["display_name"] 652 | name = host_display_name_for_display + " - " + service_display_name 653 | 654 | service_problems_severity[name] = obj["attrs"]["state"] 655 | 656 | count += 1 657 | end 658 | 659 | return service_problems, service_problems_severity 660 | end 661 | 662 | def getIcingaStats() 663 | results = getStatusData() 664 | 665 | wqStats = {} 666 | clusterStats = {} 667 | 668 | results.each do |r| 669 | status = r["status"] 670 | 671 | wqKeyList = [ "work_queue_item_rate", "query_queue_item_rate" ] 672 | clusterKeyList = [ "num_conn_endpoints", "num_not_conn_endpoints", "anonymous_clients", "clients" ] 673 | 674 | # structure is "type" - "name" 675 | # api - json_rpc 676 | # api - http 677 | # idomysqlconnection - ido-mysql 678 | status.each do |type, typeval| 679 | if not typeval.is_a?(Hash) 680 | next 681 | end 682 | 683 | typeval.each do |attr, val| 684 | # puts attr + " " + val.to_s 685 | 686 | # collect top level matches, e.g. num_conn_endpoints 687 | clusterKeyList.each do |key| 688 | # puts "Matching top level key " + key + " with attr " + attr + " val " + val.to_s 689 | if key == attr 690 | clusterStats[attr] = val 691 | end 692 | end 693 | 694 | if not val.is_a?(Hash) 695 | next 696 | end 697 | 698 | # collect inner parts, e.g. json_rpc.anonymous_clients 699 | clusterKeyList.each do |key| 700 | if val.has_key? key 701 | clusterStats[attr] = val[key] 702 | end 703 | end 704 | 705 | wqKeyList.each do |key| 706 | if val.has_key? key 707 | attrName = attr + " queue rate" 708 | wqStats[attrName] = val[key] 709 | end 710 | end 711 | 712 | end 713 | end 714 | end 715 | 716 | return wqStats, clusterStats 717 | end 718 | 719 | def fetchVersion(version) 720 | #version = "v2.4.10-504-gab4ba18" 721 | #version = "2.11.0-1" 722 | # strip v2.4.10 (default) and r2.4.10 (Debian) 723 | # icinga2/lib/base/utility.cpp - ParseVersion 724 | 725 | version_str = version[/^[vr]?(2\.\d+\.\d+).*$/,1] 726 | 727 | @version = version_str 728 | end 729 | 730 | def initializeAttributes() 731 | @version = "Not running" 732 | @node_name = "" 733 | @app_starttime = 0 734 | @uptime = 0 735 | 736 | @avg_latency = 0 737 | @avg_execution_time = 0 738 | @host_active_checks_1min = 0 739 | @host_passive_checks_1min = 0 740 | @service_active_checks_1min = 0 741 | @service_passive_checks_1min = 0 742 | 743 | @service_problems_severity = 0 744 | 745 | @host_count_all = 0 746 | @host_count_problems = 0 747 | @host_count_problems_down = 0 748 | @host_count_up = 0 749 | @host_count_down = 0 750 | @host_count_in_downtime = 0 751 | @host_count_acknowledged = 0 752 | 753 | @service_count_all = 0 754 | @service_count_problems = 0 755 | @service_count_problems_warning = 0 756 | @service_count_problems_critical = 0 757 | @service_count_problems_unknown = 0 758 | @service_count_ok = 0 759 | @service_count_warning = 0 760 | @service_count_critical = 0 761 | @service_count_unknown = 0 762 | @service_count_unknown = 0 763 | @service_count_in_downtime = 0 764 | @service_count_acknowledged = 0 765 | 766 | @app_data = nil 767 | @cib_data = nil 768 | @all_hosts_data = nil 769 | @all_services_data = nil 770 | end 771 | 772 | def run 773 | # initialize attributes to provide some semi-useful data 774 | initializeAttributes() 775 | 776 | ## App data 777 | @app_data = getIcingaApplicationData() 778 | 779 | unless(@app_data.nil?) 780 | fetchVersion(@app_data['icingaapplication']['app']['version']) 781 | @node_name = @app_data['icingaapplication']['app']['node_name'] 782 | @app_starttime = Time.at(@app_data['icingaapplication']['app']['program_start'].to_f) 783 | end 784 | 785 | ## CIB data 786 | @cib_data = getCIBData() #exported 787 | 788 | unless(@cib_data.nil?) 789 | uptimeTmp = cib_data["uptime"].round(2) 790 | @uptime = Time.at(uptimeTmp).utc.strftime("%H:%M:%S") 791 | 792 | @avg_latency = cib_data["avg_latency"].round(2) 793 | @avg_execution_time = cib_data["avg_execution_time"].round(2) 794 | 795 | @host_count_up = cib_data["num_hosts_up"].to_int 796 | @host_count_down = cib_data["num_hosts_down"].to_int 797 | @host_count_in_downtime = cib_data["num_hosts_in_downtime"].to_int 798 | @host_count_acknowledged = cib_data["num_hosts_acknowledged"].to_int 799 | 800 | @service_count_ok = cib_data["num_services_ok"].to_int 801 | @service_count_warning = cib_data["num_services_warning"].to_int 802 | @service_count_critical = cib_data["num_services_critical"].to_int 803 | @service_count_unknown = cib_data["num_services_unknown"].to_int 804 | @service_count_in_downtime = cib_data["num_services_in_downtime"].to_int 805 | @service_count_acknowledged = cib_data["num_services_acknowledged"].to_int 806 | 807 | # check stats 808 | @host_active_checks_1min = cib_data["active_host_checks_1min"] 809 | @host_passive_checks_1min = cib_data["passive_host_checks_1min"] 810 | @service_active_checks_1min = cib_data["active_service_checks_1min"] 811 | @service_passive_checks_1min = cib_data["passive_service_checks_1min"] 812 | end 813 | 814 | ## Objects data 815 | # fetch the minimal attributes for problem calculation 816 | all_hosts_data = nil 817 | all_services_data = nil 818 | 819 | if @showOnlyHardStateProblems 820 | all_hosts_data = getHostObjects([ "name", "display_name", "state", "acknowledgement", "downtime_depth", "last_check", "state_type" ], nil, nil) 821 | all_services_data = getServiceObjects([ "name", "display_name", "host_name", "state", "acknowledgement", "downtime_depth", "last_check", "state_type" ], nil, [ "host.name", "host.display_name", "host.state", "host.acknowledgement", "host.downtime_depth", "host.last_check" ]) 822 | else 823 | all_hosts_data = getHostObjects([ "name", "display_name", "state", "acknowledgement", "downtime_depth", "last_check" ], nil, nil) 824 | all_services_data = getServiceObjects([ "name", "display_name", "host_name", "state", "acknowledgement", "downtime_depth", "last_check" ], nil, [ "host.name", "host.display_name", "host.state", "host.acknowledgement", "host.downtime_depth", "host.last_check" ]) 825 | end 826 | 827 | unless(all_hosts_data.nil?) 828 | @host_count_all = all_hosts_data.size 829 | @host_count_problems = countProblems(all_hosts_data) 830 | @host_count_problems_down = countProblems(all_hosts_data, 1) 831 | end 832 | 833 | unless(all_services_data.nil?) 834 | @service_count_all = all_services_data.size 835 | @service_count_problems = countProblemsServices(all_services_data) 836 | @service_count_problems_warning = countProblemsServices(all_services_data, 1) 837 | @service_count_problems_critical = countProblemsServices(all_services_data, 2) 838 | @service_count_problems_unknown = countProblemsServices(all_services_data, 3) 839 | 840 | # severity 841 | @service_problems, @service_problems_severity = getProblemServices(all_services_data, all_hosts_data) 842 | end 843 | 844 | end 845 | end 846 | -------------------------------------------------------------------------------- /pki/README.md: -------------------------------------------------------------------------------- 1 | Put your client certificate public and private key and the ca.crt here. 2 | 3 | .crt 4 | .key 5 | ca.crt 6 | -------------------------------------------------------------------------------- /pki/ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFrzCCA5egAwIBAgIJAORBoXN7OwqRMA0GCSqGSIb3DQEBBQUAMG4xCzAJBgNV 3 | BAYTAkRFMRAwDgYDVQQIDAdCYXZhcmlhMRIwEAYDVQQHDAlOdXJlbWJlcmcxEDAO 4 | BgNVBAoMB05FVFdBWVMxEzARBgNVBAsMCk1vbml0b3JpbmcxEjAQBgNVBAMMCUlj 5 | aW5nYSBDQTAeFw0xMzEyMjAxMTA4MzVaFw0yMzEyMTgxMTA4MzVaMG4xCzAJBgNV 6 | BAYTAkRFMRAwDgYDVQQIDAdCYXZhcmlhMRIwEAYDVQQHDAlOdXJlbWJlcmcxEDAO 7 | BgNVBAoMB05FVFdBWVMxEzARBgNVBAsMCk1vbml0b3JpbmcxEjAQBgNVBAMMCUlj 8 | aW5nYSBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANYENop6VrRb 9 | a+ryVwbje2TXYy4P1HeOIMKKtkRyxi+/t6WiMlQLrIZVgVOtP64997AIgwC03sMF 10 | 4cTp63XUdq4Z4Ewgxt0sgUXmsmxOoOBybz6yrQercKujQqKdEWBKxixUi32nowV4 11 | 2GOne+PDfsYSuaDx2pe9cz8OTQ/VhBSYJUoCP4NG/IQQW84Km2cYE8k3oD6A+ru+ 12 | rmfzKR4GyvPSKsCw0Xu4I/DHAsKNIutukSPAY452mKpRisvmGxjYZJOR1DkTj+tt 13 | QQVBsNXAzD0IbXfx/lfur3v+Yx+eGpR0FQNZEhaqtAH0wmHiydaY1iMgHf4Rmje8 14 | ew+N11PfGMS3z6fJbqKGzjZUa28XoottbjG6NTcZe73VYOhx6LB70Zhx/i0Ay+J0 15 | rVPxNDkSjw43Aaa1yyq8Iz9vI8wk7APKunRTCiIDeLZIApYbQwZxhGam4javgwoh 16 | vBOi2HJzIrfuQYIXZDMtgdsDGHIpHl9xL+NFH5pPN/JuwonRZTwbcJgxsouOB5Bg 17 | bc+XOu6VvvcgmfbQbDscmbQiPplg+T/RD/dwEz+jbPWqkaOBtE6K0Tslyh0z/hc3 18 | SxPOSl13O0ze53jNQE37JFE+Kp54b5iQ9bHddBGyS9Pxmmn+SJZs+OGKsTZopLUT 19 | GizpHvk8XzQoERlxr6KReGAXF2WuEsITAgMBAAGjUDBOMB0GA1UdDgQWBBRRKIlt 20 | l5hgmB71VSFAYZeqkmBl0zAfBgNVHSMEGDAWgBRRKIltl5hgmB71VSFAYZeqkmBl 21 | 0zAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQAwRZnQ9/GFCtMv1qVc 22 | 0D3UZlDFQyYS4I48xZ2aSt9N6IhJBr6PxSOxLl7zaIj+KPvLUz1hhGlj8YE1h8j9 23 | 7PWb7ZQevqo6Su8P7k6XZjL5z3ezI57hsc14dYqF+4mMz2UuGWu51DBe+p22OeP1 24 | hhqLzwqW5G/RqqMhreRY8qTGiFgVaCiOKOH+E0ITAXqkKIrg26tUtB4zs0wkN8Of 25 | Tq1DBMbji4bfPl4fCt6xjn/163ui/NcCLszXLwGU78rvfLJ2ASfhnuLhXGy4TLLH 26 | DEkp7lvLGYYy60lw/izYeDAdgxECamk5SUchKm5y/m9vwRSZKcusdDcjHoOQ/rUT 27 | XBXy8YGy0YltxkVv06d6SpNgOs+RZxGcfVoTSjIhfj0ISSOBUhsFNeLNrUelUGTH 28 | iix4hvh8JTOy+GJYJT2IgMkbG5XZSHfQytr6Kbm8A5iz+HQfZVSNnP9WY7WyS+ci 29 | wXGnDNk2Wr9EzqP1PU3S2jvBMl80ffKXo12fzmcGZXw0LV81WND9Bo5bYVVYwava 30 | /RVwrx9jYbNtAUiJHdbqVLs2/t4Ck2Cf1dRKHYVRL9c4HsaEnWTXOIL/rMrKfBm6 31 | p1kaXLTBfAvFxtzFD0tETRQ1OalcTQYNzpZsMPjCt4pfQOExwlsAEcB/HvrNtG3E 32 | Gr5lWWq/XOK7fPjM10sucbuSFA== 33 | -----END CERTIFICATE----- 34 | -------------------------------------------------------------------------------- /pki/icinga2a.crt: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 1 (0x0) 4 | Serial Number: 2 (0x2) 5 | Signature Algorithm: sha1WithRSAEncryption 6 | Issuer: C=DE, ST=Bavaria, L=Nuremberg, O=NETWAYS, OU=Monitoring, CN=Icinga CA 7 | Validity 8 | Not Before: Dec 20 11:10:13 2013 GMT 9 | Not After : Dec 18 11:10:13 2023 GMT 10 | Subject: C=DE, ST=Bavaria, L=Nuremberg, O=NETWAYS, OU=Monitoring, CN=icinga2a 11 | Subject Public Key Info: 12 | Public Key Algorithm: rsaEncryption 13 | Public-Key: (4096 bit) 14 | Modulus: 15 | 00:f6:80:b5:53:82:e7:71:f3:c3:e2:53:e7:0b:92: 16 | c5:ab:b8:d9:ff:b6:6f:8e:4b:5b:bf:60:1a:cd:77: 17 | 4c:c6:fb:79:5e:d2:44:b9:c6:0c:ef:7a:48:15:c3: 18 | dc:50:0e:7a:9e:29:fc:59:9d:1d:9d:3c:f1:e9:42: 19 | b6:77:80:11:4d:60:e9:d2:07:ed:f4:1d:ea:26:b2: 20 | 13:75:14:88:45:4c:98:32:f5:6d:b6:ad:4c:66:48: 21 | e4:90:99:1f:0c:2f:21:f7:77:e4:d6:13:69:8a:a4: 22 | e5:cb:44:94:59:51:d1:79:97:64:41:22:58:e2:b6: 23 | 71:85:e7:2b:0a:c1:f1:2c:37:73:8d:aa:d9:42:c6: 24 | a9:36:84:57:8e:56:da:8b:25:a4:9b:17:e7:d4:8d: 25 | ae:91:ab:20:1e:3b:b2:c0:ec:26:06:80:c1:c7:81: 26 | c8:f7:85:61:d2:7d:8d:b4:c7:c0:79:ff:21:40:15: 27 | 12:ea:c9:21:25:67:6b:10:58:25:91:e6:c0:53:5f: 28 | 65:c5:00:5c:1c:5f:0e:11:27:61:15:2f:f6:27:28: 29 | 46:48:35:f3:fe:61:55:9b:3e:3b:56:8b:6a:f2:f6: 30 | d3:70:69:73:78:49:3d:c1:07:a8:9e:91:67:36:58: 31 | ac:ae:84:8a:ae:8f:9e:47:72:4c:af:fc:56:55:32: 32 | 16:3e:89:56:42:5b:9d:8d:2d:ec:29:b0:49:4b:de: 33 | d6:02:eb:11:e3:f4:ea:33:60:79:f6:dc:a3:1c:ce: 34 | 82:60:28:a0:7e:a9:7d:fe:4a:3e:81:ef:e8:3d:80: 35 | 13:79:ae:43:dc:b7:fb:19:fb:c0:49:42:92:9e:c0: 36 | 2a:8a:b4:43:38:fa:c5:b4:55:8b:f0:8f:57:32:40: 37 | f1:14:37:3b:02:ac:5f:67:82:eb:f2:bb:c6:1e:0c: 38 | ff:26:ac:b6:cc:81:b4:8c:1d:90:f3:bd:3b:bd:0f: 39 | 6c:01:32:57:e4:bb:9d:be:d5:23:73:37:e7:28:5d: 40 | 10:2d:ba:40:09:0f:4f:74:50:f1:c1:d3:15:e7:1b: 41 | be:44:3f:3e:0f:a1:f7:62:2a:b9:89:0e:e0:d1:89: 42 | ce:d1:ef:92:a9:7b:8d:c5:fd:07:d6:7e:f4:a0:ee: 43 | db:89:5a:e8:90:e5:d9:3b:e7:34:a1:4b:95:1a:90: 44 | 4a:cc:02:8d:65:2f:3a:86:63:b9:32:10:2c:c8:52: 45 | c2:56:0b:e7:d6:d7:c6:93:47:6c:42:23:88:ba:c8: 46 | d5:ba:a8:fd:e0:e2:85:00:7f:50:57:36:ce:90:8c: 47 | 91:4c:d6:7a:48:da:f9:a5:ee:e8:9d:79:76:39:19: 48 | a4:1b:28:3c:bc:45:3e:83:d2:4c:48:b2:5d:5f:e3: 49 | 68:42:85 50 | Exponent: 65537 (0x10001) 51 | Signature Algorithm: sha1WithRSAEncryption 52 | 06:9b:05:8e:35:7f:ba:9f:3d:44:62:5d:0d:0e:c5:20:55:e3: 53 | 9c:e6:17:3c:4e:59:83:2d:37:56:0c:e8:bf:e4:0f:76:0b:e7: 54 | 35:36:e4:22:0e:ca:94:2a:bb:38:51:0e:0b:5b:98:28:08:26: 55 | 49:20:ac:74:6c:5f:c0:a0:81:df:f7:7c:43:69:31:77:72:71: 56 | 19:0b:be:c3:ca:1c:83:e4:62:92:84:f2:d3:20:43:88:1f:3e: 57 | fe:a3:e5:4e:5c:e6:37:e5:a3:52:41:09:c6:f2:f5:af:23:5b: 58 | 3d:3b:cb:3d:e1:85:84:c5:05:72:dd:83:6d:eb:8d:fe:1a:66: 59 | 43:63:89:1f:aa:e3:12:7a:c8:b2:26:6b:c0:60:c9:84:7a:85: 60 | a3:15:a6:3d:7c:11:43:5f:2f:81:c5:3a:a9:0f:50:3a:33:3d: 61 | c6:b8:22:91:b4:dc:bc:bb:fa:0b:d6:ed:94:df:2f:ee:13:54: 62 | 5d:05:ab:60:e2:72:a4:14:f2:84:98:d0:af:10:dd:bb:74:98: 63 | 7c:e5:53:bb:fb:12:bf:8c:b1:7b:f9:5f:5a:5a:1b:9e:11:e9: 64 | 67:a0:df:2a:30:7c:8e:1c:cc:f8:c4:f8:bd:06:12:14:00:93: 65 | 5c:0b:26:c7:75:47:bc:d9:c5:78:00:50:a7:4c:04:50:dc:7d: 66 | 0e:56:86:05:82:cb:c0:70:d5:af:79:81:dd:6a:47:ad:82:aa: 67 | e8:ce:74:a1:d0:c2:96:1b:ec:66:24:fd:56:01:7d:97:2e:5d: 68 | 03:6e:c7:28:89:45:49:da:c1:a8:a8:a2:c6:dd:d5:23:5b:19: 69 | 2f:86:a9:0d:5f:47:81:e7:ae:43:f1:73:61:9c:56:e5:c3:c7: 70 | 0f:c4:98:bf:80:0f:64:5a:88:93:11:54:ac:57:1a:71:b3:49: 71 | 99:7a:03:42:0e:2e:df:3d:d9:69:68:87:7a:4a:2d:90:d2:c2: 72 | 21:00:2e:4b:c2:93:10:02:38:57:f2:94:b7:91:3c:8b:1e:96: 73 | 7a:cf:b1:46:02:bf:f8:fd:be:e4:b0:06:96:83:a8:2c:fc:71: 74 | 9f:f4:31:9b:9c:c4:c7:6b:7d:7b:32:56:00:0e:c8:67:90:01: 75 | f2:47:79:8c:58:89:d9:c2:73:bf:e9:63:1f:cf:29:e9:88:2f: 76 | 73:3f:5c:f4:38:35:5e:56:cd:6a:e4:e8:bd:3d:40:46:be:53: 77 | a1:12:ff:0e:92:9f:b3:08:fe:21:fb:ae:e8:e7:00:b6:2e:82: 78 | f1:2a:46:9c:22:7d:18:0e:6c:11:3b:63:b5:ed:de:2e:d2:6d: 79 | 8f:56:7c:05:08:b0:03:81:1b:31:53:ba:91:e3:ed:45:65:ca: 80 | 97:bb:8a:d0:a0:fc:90:ac 81 | -----BEGIN CERTIFICATE----- 82 | MIIFTzCCAzcCAQIwDQYJKoZIhvcNAQEFBQAwbjELMAkGA1UEBhMCREUxEDAOBgNV 83 | BAgMB0JhdmFyaWExEjAQBgNVBAcMCU51cmVtYmVyZzEQMA4GA1UECgwHTkVUV0FZ 84 | UzETMBEGA1UECwwKTW9uaXRvcmluZzESMBAGA1UEAwwJSWNpbmdhIENBMB4XDTEz 85 | MTIyMDExMTAxM1oXDTIzMTIxODExMTAxM1owbTELMAkGA1UEBhMCREUxEDAOBgNV 86 | BAgMB0JhdmFyaWExEjAQBgNVBAcMCU51cmVtYmVyZzEQMA4GA1UECgwHTkVUV0FZ 87 | UzETMBEGA1UECwwKTW9uaXRvcmluZzERMA8GA1UEAwwIaWNpbmdhMmEwggIiMA0G 88 | CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQD2gLVTgudx88PiU+cLksWruNn/tm+O 89 | S1u/YBrNd0zG+3le0kS5xgzvekgVw9xQDnqeKfxZnR2dPPHpQrZ3gBFNYOnSB+30 90 | HeomshN1FIhFTJgy9W22rUxmSOSQmR8MLyH3d+TWE2mKpOXLRJRZUdF5l2RBIlji 91 | tnGF5ysKwfEsN3ONqtlCxqk2hFeOVtqLJaSbF+fUja6RqyAeO7LA7CYGgMHHgcj3 92 | hWHSfY20x8B5/yFAFRLqySElZ2sQWCWR5sBTX2XFAFwcXw4RJ2EVL/YnKEZINfP+ 93 | YVWbPjtWi2ry9tNwaXN4ST3BB6iekWc2WKyuhIquj55Hckyv/FZVMhY+iVZCW52N 94 | LewpsElL3tYC6xHj9OozYHn23KMczoJgKKB+qX3+Sj6B7+g9gBN5rkPct/sZ+8BJ 95 | QpKewCqKtEM4+sW0VYvwj1cyQPEUNzsCrF9nguvyu8YeDP8mrLbMgbSMHZDzvTu9 96 | D2wBMlfku52+1SNzN+coXRAtukAJD090UPHB0xXnG75EPz4PofdiKrmJDuDRic7R 97 | 75Kpe43F/QfWfvSg7tuJWuiQ5dk75zShS5UakErMAo1lLzqGY7kyECzIUsJWC+fW 98 | 18aTR2xCI4i6yNW6qP3g4oUAf1BXNs6QjJFM1npI2vml7uideXY5GaQbKDy8RT6D 99 | 0kxIsl1f42hChQIDAQABMA0GCSqGSIb3DQEBBQUAA4ICAQAGmwWONX+6nz1EYl0N 100 | DsUgVeOc5hc8TlmDLTdWDOi/5A92C+c1NuQiDsqUKrs4UQ4LW5goCCZJIKx0bF/A 101 | oIHf93xDaTF3cnEZC77DyhyD5GKShPLTIEOIHz7+o+VOXOY35aNSQQnG8vWvI1s9 102 | O8s94YWExQVy3YNt643+GmZDY4kfquMSesiyJmvAYMmEeoWjFaY9fBFDXy+BxTqp 103 | D1A6Mz3GuCKRtNy8u/oL1u2U3y/uE1RdBatg4nKkFPKEmNCvEN27dJh85VO7+xK/ 104 | jLF7+V9aWhueEelnoN8qMHyOHMz4xPi9BhIUAJNcCybHdUe82cV4AFCnTARQ3H0O 105 | VoYFgsvAcNWveYHdaketgqroznSh0MKWG+xmJP1WAX2XLl0DbscoiUVJ2sGoqKLG 106 | 3dUjWxkvhqkNX0eB565D8XNhnFblw8cPxJi/gA9kWoiTEVSsVxpxs0mZegNCDi7f 107 | PdlpaId6Si2Q0sIhAC5LwpMQAjhX8pS3kTyLHpZ6z7FGAr/4/b7ksAaWg6gs/HGf 108 | 9DGbnMTHa317MlYADshnkAHyR3mMWInZwnO/6WMfzynpiC9zP1z0ODVeVs1q5Oi9 109 | PUBGvlOhEv8Okp+zCP4h+67o5wC2LoLxKkacIn0YDmwRO2O17d4u0m2PVnwFCLAD 110 | gRsxU7qR4+1FZcqXu4rQoPyQrA== 111 | -----END CERTIFICATE----- 112 | -------------------------------------------------------------------------------- /pki/icinga2a.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQD2gLVTgudx88Pi 3 | U+cLksWruNn/tm+OS1u/YBrNd0zG+3le0kS5xgzvekgVw9xQDnqeKfxZnR2dPPHp 4 | QrZ3gBFNYOnSB+30HeomshN1FIhFTJgy9W22rUxmSOSQmR8MLyH3d+TWE2mKpOXL 5 | RJRZUdF5l2RBIljitnGF5ysKwfEsN3ONqtlCxqk2hFeOVtqLJaSbF+fUja6RqyAe 6 | O7LA7CYGgMHHgcj3hWHSfY20x8B5/yFAFRLqySElZ2sQWCWR5sBTX2XFAFwcXw4R 7 | J2EVL/YnKEZINfP+YVWbPjtWi2ry9tNwaXN4ST3BB6iekWc2WKyuhIquj55Hckyv 8 | /FZVMhY+iVZCW52NLewpsElL3tYC6xHj9OozYHn23KMczoJgKKB+qX3+Sj6B7+g9 9 | gBN5rkPct/sZ+8BJQpKewCqKtEM4+sW0VYvwj1cyQPEUNzsCrF9nguvyu8YeDP8m 10 | rLbMgbSMHZDzvTu9D2wBMlfku52+1SNzN+coXRAtukAJD090UPHB0xXnG75EPz4P 11 | ofdiKrmJDuDRic7R75Kpe43F/QfWfvSg7tuJWuiQ5dk75zShS5UakErMAo1lLzqG 12 | Y7kyECzIUsJWC+fW18aTR2xCI4i6yNW6qP3g4oUAf1BXNs6QjJFM1npI2vml7uid 13 | eXY5GaQbKDy8RT6D0kxIsl1f42hChQIDAQABAoICAQCDuEsskOq0DZpCuqrfmFJg 14 | bKqw5f++fa5NGUG5QWKZgY9dh+aJjrci0KjsWHjFnrcPBUh/amGEwChUiP2P5bNZ 15 | McxAG1Sf+cxBWS7khVA8F63MrTEvSHNmxNS+H9RjYlw4LKzvZ/ghyfOxJrhO8lWJ 16 | L8i2tW/h1bPb5acuXGxPQGJS9VSbRiTtqNJQkUC+0iKfbarH/d2moVbMUQI37Ph0 17 | ySDDRyroeTHIlKbAT20ew6I06A3vwAIiRp0DYtbQnAlxg6ySFJsj3fdaWzyZYcOb 18 | MP49X/c9my1vg1WZ7W383sAldXPqdti06S7FbNVGSSQdLV4d4UimzQEFmUVYySbw 19 | v12zRKBQovxLh36gWPCxMkk3s/jx4cH+zmhccy7BHSG3AO4g0K8eXOZI8u4Tf0q/ 20 | /CkEeB31iEmWDn99buIrkpH6gBSr/pq36PhZSB/Rm0HJ3OLY/WQnkeWE0y8pevYq 21 | 4VIU7smqmXaIXBw4NS2BRUeb8SJagY78nRNH+w5g1ijoa+5Z648/LvIdkYLeTZkv 22 | kkNFRI5juqYGE6xzBD/u3VxRDIo/ZDzx+b6+KHBNkTRrhpOa2b/l8xBTEVMN72dy 23 | F/n7vKGxblSO4YQQnlrSuQNs7oE9PXmVOqxpxIrG4e7bRTR4uyfOb/VnFBSMAiCC 24 | 55FOKukNLkFiS/74U9YuEQKCAQEA/gdAiLkgl9dq6EYzjerm24rYGqy2X6VC4v1D 25 | iCH3xbA2rLBB8lrb0fBWUxYjhaksm8tyeWgWXt81PS/uYDgmHxNiLbZFNMZxRR/E 26 | qm361zt48O5n04zzRVpOcxLm3sqioJiLZPsmwoJQD9bI3M/5X/yXwqSyUdpusiZ3 27 | 2lII1X5/n8q1POhuR9q8bczIAKf+o4wGIOVJ3J3jGyTKEAMEZyxL+J1oC3wRqsS4 28 | Owbc64MBa9k1I0XtkXabedLVGTHK9I16aFFai65HsF7ojpeP6cxpRzFGY7FpaFeQ 29 | qr77pctExgcttvjyoGAGZ9RDpus4REw/VD7FbbXiUFpQsZUwkwKCAQEA+GqAzEl4 30 | SZxJFw/9vV0SvYyHafPCdXFPiAaOIV0wa86YYR6LI+3y0IjziRqln8WWxEC55KIT 31 | ylsWwrY3wMqUny/QIziAYCjnFhLZ8QdwuYq0L9M3zdcO9QveNtzLRnVCLZrSXTvA 32 | lqYiGbpb/AChvBUoadbM6vJjvhfVlVHlcK1PrJASXVIlZYb3IN1SM0D1y06KYV7m 33 | itRBFhd4qRJdj3C9Oh1pG0khDpIpRGLw1sW3hBD38MYrEoj0DcqmDEHjBs5mzerj 34 | OTDoKEwOGDTZ59nvJjh5K7Re8jvZk13zQVGHYGySPVmq26Yow1vW7knaUoZzFfbx 35 | Elg7EVQNkN3nhwKCAQAHajzHAOhjpkUXnJz6ooW0mNvwA2SbbB6Es7HZ8mviGF+w 36 | MMDsCxzphuNuL3I//40uywR/aLtmb4uFSrXhlyH9vDocbXqdrME3rFK4SteMms7P 37 | 8ZWURkP/nUA2+bFFhZKzr2A6RV5/RQCDRdi9IVD9LUlcxCP0YbTjfF35QHXDjZq6 38 | Flet/HbjoA3qApU/96dmLLstEKRUTUOI0k0XpI7nWBKroiScWTXGz1E1X+Do+vz6 39 | oe98JRlnKpglOTNBNgPfmZWeH+nNs/uhuVN+fgwUXgQgsN6GIHKcOhod8c80qrdK 40 | NIFwGtoyNjtZooeLAsi0rakXk3F931ZI1CoQ1PMDAoIBAG//PGBeA2BUmNNP/1sf 41 | mjJhKDAJmTNvyaaPJgc+x3TjaOnoZlzli6KfDAVVQTS+VZHXiLfepsOFu8Y/dvVx 42 | n/4BBXrLn307E6xtBVtYuma8dS5WsQMZoYGzPxVHCFKUzShYc91a8iI5dohY7922 43 | vzRX6aeGE08dxwBOKJowmRvq10/6iH0QaxHR/psPjV7UO7zUhSkPKyuONGXsYRcA 44 | 07O25WlNFzUZDFFQblCJtwFtlKZzLAWeoo6xti59nc1K6zsHVWwjKRyAwzpfx778 45 | HP4d0GdP8LlWkbPubdK1+dUKF9nOOUR1p8VtrVUek9FM9KReP4a4lclqg/71AUS9 46 | 9wMCggEAHXJBXheQmfzRpPx1WFVcfLhFK08Qom8zniX4mKQieuVxEB7yMeQrwF+I 47 | 98Q8913imLjJrliVRY1b1qjrJgoNXo6GAPiLyQfbeUhTgCltUv0D4/yxO5Qlntpt 48 | sm6BPQVI7q2jfGW6a89O9xDJYmZ1AdYRJxG7THRZmpB/6FoIcs+JKo02gYpl0kuS 49 | tEIRjDQJZ/UWixisSSGFXKaPrKn/mFAINcTrpWGs8FJaqqO9JzudyULqoVvqn5xD 50 | ITwqLDA7OHbVGRUhQtGN1PJfhcbyuXfMA4CSW+JoBX08+pW/aB06pYEcBjS4tMCg 51 | xailq0+t1Ki4r/Jh+5mVFBy5nHYP+Q== 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | This Dashboard doesn't exist. 5 | 17 | 18 | 19 | 20 | 21 |
    22 |

    Drats! That Dashboard doesn't exist.

    23 |

    You may have mistyped the address or the page may have moved.

    24 |
    25 | 26 | -------------------------------------------------------------------------------- /public/dashing_icinga2_overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mocdaniel/dashing-icinga2/2fc3aa4e150651299b7df7a83aeb43ccd95ac930/public/dashing_icinga2_overview.png -------------------------------------------------------------------------------- /public/dashing_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mocdaniel/dashing-icinga2/2fc3aa4e150651299b7df7a83aeb43ccd95ac930/public/dashing_icon.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mocdaniel/dashing-icinga2/2fc3aa4e150651299b7df7a83aeb43ccd95ac930/public/favicon.ico -------------------------------------------------------------------------------- /restart-dashing: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #/****************************************************************************** 4 | # * Icinga 2 Dashing Restart Script * 5 | # * Copyright (C) 2016-2017 Icinga Development Team (https://www.icinga.com) * 6 | # * * 7 | # * This program is free software; you can redistribute it and/or * 8 | # * modify it under the terms of the GNU General Public License * 9 | # * as published by the Free Software Foundation; either version 2 * 10 | # * of the License, or (at your option) any later version. * 11 | # * * 12 | # * This program is distributed in the hope that it will be useful, * 13 | # * but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | # * GNU General Public License for more details. * 16 | # * * 17 | # * You should have received a copy of the GNU General Public License * 18 | # * along with this program; if not, write to the Free Software Foundation * 19 | # * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * 20 | # ******************************************************************************/ 21 | 22 | if [ -d '/usr/share/dashing-icinga2' ]; then 23 | DASHING_PWD='/usr/share/dashing-icinga2' 24 | else 25 | DASHING_PWD=`pwd` 26 | fi 27 | 28 | DASHING_PORT=8005 29 | DASHING_PID_FILE="tmp/pids/thin.pid" 30 | DASHING_BIN=dashing 31 | 32 | usage(){ 33 | cat << EOF 34 | usage: $0 options 35 | This script restarts the Dashing daemon. 36 | OPTIONS: 37 | -h Show this message 38 | -p Port where Dashing will listen (defaults to 3030) 39 | -P PID file path (defaults to tmp/pids/thin.pid) 40 | -D Change to this directory before starting the daemon (defaults to current directory) 41 | -b Path to the dashing binary (defaults to just 'dashing' from system path) 42 | EOF 43 | } 44 | 45 | while getopts ":p:P:D:b:h" opt; do 46 | case $opt in 47 | h) 48 | usage 49 | exit 1 50 | ;; 51 | p) 52 | DASHING_PORT=$OPTARG 53 | ;; 54 | P) 55 | DASHING_PID_FILE=$OPTARG 56 | ;; 57 | D) 58 | DASHING_PWD=$OPTARG 59 | ;; 60 | b) 61 | DASHING_BIN=$OPTARG 62 | ;; 63 | \?) 64 | echo "Invalid option: -$OPTARG" >&2 65 | usage 66 | exit 1 67 | ;; 68 | :) 69 | echo "Option -$OPTARG requires an argument." >&2 70 | usage 71 | exit 1 72 | ;; 73 | esac 74 | done 75 | 76 | # ensure that dashing runs with utf8 77 | export LANG=en_US.UTF-8 78 | export LC_CTYPE=en_US.UTF-8 79 | export LC_ALL=en_US.UTF-8 80 | 81 | export PATH="/usr/local/bin:$PATH" 82 | 83 | cd $DASHING_PWD 84 | 85 | if [ -f "$DASHING_PID_FILE" ]; then 86 | kill -9 $(<"$DASHING_PID_FILE") > /dev/null 2>&1 87 | rm -f $DASHING_PID_FILE 88 | fi 89 | 90 | $DASHING_BIN start -d -p $DASHING_PORT --pid $DASHING_PID_FILE 91 | ERR=$? 92 | 93 | if [ $ERR != 0 ]; then 94 | echo "Restart failed. Bailing out." 95 | exit $ERR 96 | fi 97 | 98 | # dashing might take a while to start 99 | while [ ! -f "$DASHING_PID_FILE" ]; do 100 | sleep 1 101 | done 102 | 103 | PID=$(<"$DASHING_PID_FILE") 104 | 105 | echo "Restarted Dashing with PID $PID listening on port $DASHING_PORT." 106 | exit $ERR 107 | -------------------------------------------------------------------------------- /test/icinga2.rb: -------------------------------------------------------------------------------- 1 | #/****************************************************************************** 2 | # * Icinga 2 Dashing Library Test * 3 | # * Copyright (C) 2015-2017 Icinga Development Team (https://www.icinga.com) * 4 | # * * 5 | # * This program is free software; you can redistribute it and/or * 6 | # * modify it under the terms of the GNU General Public License * 7 | # * as published by the Free Software Foundation; either version 2 * 8 | # * of the License, or (at your option) any later version. * 9 | # * * 10 | # * This program is distributed in the hope that it will be useful, * 11 | # * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | # * GNU General Public License for more details. * 14 | # * * 15 | # * You should have received a copy of the GNU General Public License * 16 | # * along with this program; if not, write to the Free Software Foundation * 17 | # * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * 18 | # ******************************************************************************/ 19 | 20 | require './lib/icinga2' 21 | 22 | # initialize data provider 23 | icinga = Icinga2.new('config/icinga2.json') # fixed path 24 | 25 | # run data provider 26 | icinga.run 27 | 28 | ########## tests begin 29 | hostAttrs = [ "__name", "state" ] 30 | hostFilter = "host.name == NodeName" 31 | hostJoins = nil 32 | hostObjs = icinga.getHostObjects(hostAttrs, hostFilter, hostJoins) 33 | 34 | host_stats = [] 35 | hostObjs.each do |host| 36 | host_stats.push({ "label" => host["attrs"]["__name"], "value" => host["attrs"]["state"] }) 37 | end 38 | 39 | puts "Host listing: " + host_stats.to_s 40 | 41 | serviceAttrs = [ "__name", "state" ] 42 | serviceFilter = "host.name == NodeName" 43 | serviceJoins = [ "host.name", "host.state" ] 44 | serviceObjs = icinga.getServiceObjects(serviceAttrs, serviceFilter, serviceJoins) 45 | 46 | #puts "Host Objects: " + hostObjs.to_s 47 | #puts "Service Object: " + serviceObjs.to_s 48 | 49 | service_stats = [] 50 | serviceObjs.each do |service| 51 | service_stats.push({ "label" => icinga.formatService(service["attrs"]["__name"]), "value" => service["attrs"]["state"] }) 52 | end 53 | 54 | puts "Service listing: " + service_stats.to_s 55 | 56 | ########## tests end 57 | 58 | -------------------------------------------------------------------------------- /tools/logrotate/dashing-icinga2: -------------------------------------------------------------------------------- 1 | /usr/share/dashing-icinga2/log/puma.log { 2 | daily 3 | rotate 7 4 | compress 5 | delaycompress 6 | missingok 7 | notifempty 8 | postrotate 9 | /bin/kill -USR1 $(cat /var/run/puma.pid 2> /dev/null) 2> /dev/null || true 10 | endscript 11 | } 12 | -------------------------------------------------------------------------------- /tools/systemd/dashing-icinga2.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Dashing-Icinga2 host/service/network monitoring system 3 | After=syslog.target network.target postgresql.service mariadb.service carbon-cache.service icinga2.service 4 | 5 | [Service] 6 | Type=notify 7 | WatchdogSec=10 8 | WorkingDirectory=/usr/share/dashing-icinga2 9 | ExecStart=/usr/local/bin/puma -p 8005 --pid /var/run/puma.pid 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | -------------------------------------------------------------------------------- /widgets/chartjs/chartjs.coffee: -------------------------------------------------------------------------------- 1 | 2 | #`function generateLabels(chart) { 3 | # var data = chart.data; 4 | # if (data.labels.length && data.datasets.length) { 5 | # return data.labels.map(function(label, i) { 6 | # var meta = chart.getDatasetMeta(0); 7 | # var style = meta.controller.getStyle(i); 8 | # 9 | # // fetch the value 10 | # var value = chart.config.data.datasets[arc._datasetIndex].data[arc._index]; 11 | # 12 | # return { 13 | # // Add the numbers next to the label 14 | # text: label + ": " + value, 15 | # fillStyle: style.backgroundColor, 16 | # strokeStyle: style.borderColor, 17 | # lineWidth: style.borderWidth, 18 | # hidden: isNaN(data.datasets[0].data[i]) || meta.data[i].hidden, 19 | # 20 | # // Extra data used for toggling the correct item 21 | # index: i 22 | # }; 23 | # }); 24 | # } 25 | # return []; 26 | #}` 27 | 28 | class Dashing.Chartjs extends Dashing.Widget 29 | 30 | constructor: -> 31 | super 32 | @id = @get("id") 33 | @type = @get("type") 34 | @header = @get("header") 35 | @labels = @get("labels") && @get("labels").split(",") 36 | @options = @get("options") || {} 37 | 38 | if @type == "scatter" 39 | @datasets = @get("datasets") 40 | else 41 | @datasets = @get("datasets") && @get("datasets").split(",") 42 | 43 | @colorNames = @get("colornames") && @get("colornames").split(",") 44 | 45 | ready: -> 46 | Chart.defaults.global.defaultColor = 'rgb(255, 255, 255)' 47 | Chart.defaults.global.defaultFontColor = 'rgb(255, 255, 255)' 48 | Chart.defaults.global.legend.labels.fontColor = 'rgb(255, 255, 255)' 49 | Chart.defaults.global.layout.padding = { left: 10, right: 10, top: 10, bottom: 10 } 50 | Chart.defaults.global.elements.point.radius = 5 51 | Chart.defaults.global.legend.display = false 52 | Chart.defaults.global.legend.position = 'bottom' 53 | 54 | Chart.defaults.doughnut.legend.position = 'right' 55 | Chart.defaults.doughnut.legend.display = true 56 | 57 | Chart.defaults.bar.barThickness = 'flex' 58 | #Chart.defaults.bar.legend.display = false 59 | Chart.defaults.bar.backgroundColor = 'rgb(255, 255, 255)' 60 | Chart.defaults.bar.scales.yAxes = [{ 61 | ticks: { 62 | beginAtZero: true 63 | } 64 | }] 65 | 66 | # Override original legend label generator 67 | # https://github.com/chartjs/Chart.js/blob/master/src/controllers/controller.doughnut.js#L45 68 | # Chart.defaults.global.legend.labels.generateLabels = generateLabels 69 | 70 | @draw() 71 | 72 | onData: (data) -> 73 | @type = data.type || @type 74 | @header = data.header || @header 75 | @labels = data.labels || @labels 76 | @options = data.options || @options 77 | @datasets = data.datasets || @datasets 78 | @colorNames = data.colorNames || @colorNames 79 | 80 | @draw() 81 | 82 | draw: -> 83 | switch @type 84 | when "pie", "doughnut", "polarArea" 85 | @circularChart @id, 86 | type: @type, 87 | labels: @labels, 88 | colors: @colorNames, 89 | datasets: @datasets, 90 | options: @options 91 | 92 | when "line", "bar", "horizontalBar", "radar", "scatter" 93 | @linearChart @id, 94 | type: @type, 95 | header: @header, 96 | labels: @labels, 97 | colors: @colorNames, 98 | datasets: @datasets, 99 | options: @options 100 | 101 | else 102 | return 103 | 104 | circularChart: (id, { type, labels, colors, datasets, options }) -> 105 | @ensureChartExists() 106 | @chart.data = @merge labels: labels, datasets: [@merge data: datasets, @colors(colors)] 107 | @chart.update() 108 | 109 | linearChart: (id, { type, labels, header, colors, datasets, options }) -> 110 | @ensureChartExists() 111 | @chart.data = @merge labels: labels, datasets: [@merge(@colors(colors), label: header, data: datasets)] 112 | @chart.update() 113 | 114 | ensureChartExists: () -> 115 | if typeof @chart == "undefined" 116 | @chart = new Chart(document.getElementById(@id), { type: @type, data: @merge labels: @labels, datasets: [@merge data: @datasets, @colors(@colorNames)] }, @options) 117 | 118 | merge: (xs...) => 119 | if xs?.length > 0 120 | @tap {}, (m) -> m[k] = v for k, v of x for x in xs 121 | 122 | tap: (o, fn) -> fn(o); o 123 | 124 | # Keep this in sync with application.scss 125 | #$background-color-green: rgba(68, 187, 119, $background-color-transparent-factor); 126 | #$background-color-red: rgba(255, 85, 102, $background-color-transparent-factor); 127 | #$background-color-yellow: rgba(255, 170, 68, $background-color-transparent-factor); 128 | #$background-color-purple: rgba(170, 68, 255, $background-color-transparent-factor); 129 | #$background-color-grey: rgba(153, 153, 153, $background-color-transparent-factor); 130 | 131 | colorCode: -> 132 | aqua: "0, 255, 255" 133 | black: "0, 0, 0" 134 | blue: "151, 187, 205" 135 | cyan: "0, 255, 255" 136 | darkgray: "77, 83, 96" 137 | fuschia: "255, 0, 255" 138 | gray: "153, 153, 153" 139 | green: "68, 187, 119" 140 | lightgray: "220, 220, 220" 141 | lime: "0, 255, 0" 142 | magenta: "255, 0, 255" 143 | maroon: "128, 0, 0" 144 | navy: "0, 0, 128" 145 | olive: "128, 128, 0" 146 | purple: "170, 68, 255" 147 | red: "255, 85, 102" 148 | silver: "192, 192, 192" 149 | teal: "0, 128, 128" 150 | white: "255, 255, 255" 151 | yellow: "255, 170, 68" 152 | 153 | colors: (colorNames) -> 154 | backgroundColor: colorNames.map (colorName) => @backgroundColor(colorName) 155 | borderColor: colorNames.map (colorName) => @borderColor(colorName) 156 | borderWidth: colorNames.map (colorName) -> 1 157 | pointBackgroundColor: colorNames.map (colorName) => @pointBackgroundColor(colorName) 158 | pointBorderColor: colorNames.map (colorName) => @pointBorderColor(colorName) 159 | pointHoverBackgroundColor: colorNames.map (colorName) => @pointHoverBackgroundColor(colorName) 160 | pointHoverBorderColor: colorNames.map (colorName) => @pointHoverBorderColor(colorName) 161 | 162 | backgroundColor: (colorName) -> "rgba(#{ @colorCode()[colorName] }, 0.8)" 163 | borderColor: (colorName) -> "rgba(#{ @colorCode()[colorName] }, 1)" 164 | pointBackgroundColor: (colorName) -> "rgba(#{ @colorCode()[colorName] }, 1)" 165 | pointBorderColor: (colorName) -> "rgba(#{ @colorCode()[colorName] }, 1)" 166 | pointHoverBackgroundColor: -> "fff" 167 | pointHoverBorderColor: (colorName) -> "rgba(#{ @colorCode()[colorName] }, 0.8)" 168 | 169 | circleColor: (colorName) -> 170 | backgroundColor: "rgba(#{ @colorCode()[colorName] }, 0.2)" 171 | borderColor: "rgba(#{ @colorCode()[colorName] }, 1)" 172 | borderWidth: 1 173 | hoverBackgroundColor: "#fff" 174 | hoverBorderColor: "rgba(#{ @colorCode()['blue'] },0.8)" 175 | -------------------------------------------------------------------------------- /widgets/chartjs/chartjs.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | -------------------------------------------------------------------------------- /widgets/clock/clock.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Clock extends Dashing.Widget 2 | 3 | ready: -> 4 | setInterval(@startTime, 500) 5 | 6 | startTime: => 7 | zone = @get('timezone') 8 | optionsDate = { 9 | timeZone: zone, 10 | weekday: 'short', 11 | year: 'numeric', 12 | month: 'short', 13 | day: 'numeric' 14 | }; 15 | optionsTime = { 16 | timeZone: zone, 17 | hour: '2-digit', 18 | minute: '2-digit', 19 | hour12: false 20 | }; 21 | 22 | date = new Date().toLocaleDateString('en-US', optionsDate); 23 | time = new Date().toLocaleTimeString('en-US', optionsTime); 24 | 25 | @set('time', time) 26 | @set('date', date) 27 | @set('title', @get('title')) 28 | -------------------------------------------------------------------------------- /widgets/clock/clock.html: -------------------------------------------------------------------------------- 1 |

    2 |

    3 |

    4 | -------------------------------------------------------------------------------- /widgets/clock/clock.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #dc5945; 5 | 6 | // ---------------------------------------------------------------------------- 7 | // Widget-clock styles 8 | // ---------------------------------------------------------------------------- 9 | .widget-clock { 10 | 11 | background-color: $background-color; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /widgets/comments/comments.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Comments extends Dashing.Widget 2 | 3 | @accessor 'quote', -> 4 | "“#{@get('current_comment')?.body}”" 5 | 6 | ready: -> 7 | @currentIndex = 0 8 | @commentElem = $(@node).find('.comment-container') 9 | @nextComment() 10 | @startCarousel() 11 | 12 | onData: (data) -> 13 | @currentIndex = 0 14 | 15 | startCarousel: -> 16 | setInterval(@nextComment, 8000) 17 | 18 | nextComment: => 19 | comments = @get('comments') 20 | if comments 21 | @commentElem.fadeOut => 22 | @currentIndex = (@currentIndex + 1) % comments.length 23 | @set 'current_comment', comments[@currentIndex] 24 | @commentElem.fadeIn() 25 | -------------------------------------------------------------------------------- /widgets/comments/comments.html: -------------------------------------------------------------------------------- 1 |

    2 |
    3 |

    4 |

    5 |
    6 | 7 |

    8 | -------------------------------------------------------------------------------- /widgets/comments/comments.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #eb9c3c; 5 | 6 | $title-color: rgba(255, 255, 255, 0.7); 7 | $moreinfo-color: rgba(255, 255, 255, 0.7); 8 | 9 | // ---------------------------------------------------------------------------- 10 | // Widget-comment styles 11 | // ---------------------------------------------------------------------------- 12 | .widget-comments { 13 | 14 | background-color: $background-color; 15 | 16 | .title { 17 | color: $title-color; 18 | margin-bottom: 15px; 19 | } 20 | 21 | .name { 22 | padding-left: 5px; 23 | } 24 | 25 | .comment-container { 26 | display: none; 27 | } 28 | 29 | .more-info { 30 | color: $moreinfo-color; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /widgets/graph/graph.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Graph extends Dashing.Widget 2 | 3 | @accessor 'current', -> 4 | return @get('displayedValue') if @get('displayedValue') 5 | points = @get('points') 6 | if points 7 | points[points.length - 1].y 8 | 9 | ready: -> 10 | container = $(@node).parent() 11 | # Gross hacks. Let's fix this. 12 | width = (Dashing.widget_base_dimensions[0] * container.data("sizex")) + Dashing.widget_margins[0] * 2 * (container.data("sizex") - 1) 13 | height = (Dashing.widget_base_dimensions[1] * container.data("sizey")) 14 | @graph = new Rickshaw.Graph( 15 | element: @node 16 | width: width 17 | height: height 18 | renderer: @get("graphtype") 19 | series: [ 20 | { 21 | color: "#fff", 22 | data: [{x:0, y:0}] 23 | } 24 | ] 25 | ) 26 | 27 | @graph.series[0].data = @get('points') if @get('points') 28 | 29 | x_axis = new Rickshaw.Graph.Axis.Time(graph: @graph) 30 | y_axis = new Rickshaw.Graph.Axis.Y(graph: @graph, tickFormat: Rickshaw.Fixtures.Number.formatKMBT) 31 | @graph.render() 32 | 33 | onData: (data) -> 34 | if @graph 35 | @graph.series[0].data = data.points 36 | @graph.render() 37 | -------------------------------------------------------------------------------- /widgets/graph/graph.html: -------------------------------------------------------------------------------- 1 |

    2 | 3 |

    4 | 5 |

    6 | -------------------------------------------------------------------------------- /widgets/graph/graph.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #dc5945; 5 | 6 | $title-color: rgba(255, 255, 255, 0.7); 7 | $moreinfo-color: rgba(255, 255, 255, 0.3); 8 | $tick-color: rgba(0, 0, 0, 0.4); 9 | 10 | 11 | // ---------------------------------------------------------------------------- 12 | // Widget-graph styles 13 | // ---------------------------------------------------------------------------- 14 | .widget-graph { 15 | 16 | background-color: $background-color; 17 | position: relative; 18 | 19 | 20 | svg { 21 | position: absolute; 22 | opacity: 0.4; 23 | fill-opacity: 0.4; 24 | left: 0px; 25 | top: 0px; 26 | } 27 | 28 | .title, .value { 29 | position: relative; 30 | z-index: 99; 31 | } 32 | 33 | .title { 34 | color: $title-color; 35 | } 36 | 37 | .more-info { 38 | color: $moreinfo-color; 39 | font-weight: 600; 40 | font-size: 20px; 41 | margin-top: 0; 42 | } 43 | 44 | .x_tick { 45 | position: absolute; 46 | bottom: 0; 47 | .title { 48 | font-size: 20px; 49 | color: $tick-color; 50 | opacity: 0.5; 51 | padding-bottom: 3px; 52 | } 53 | } 54 | 55 | .y_ticks { 56 | font-size: 20px; 57 | fill: $tick-color; 58 | fill-opacity: 1; 59 | } 60 | 61 | .domain { 62 | display: none; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /widgets/iframe/iframe.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Iframe extends Dashing.Widget 2 | 3 | ready: -> 4 | # This is fired when the widget is done being rendered 5 | $(@node).find(".iframe").attr('src', @get('src')) 6 | 7 | onData: (data) -> 8 | # Handle incoming data 9 | # You can access the html node of this widget with `@node` 10 | # Example: $(@node).fadeOut().fadeIn() will make the node flash each time data comes in. 11 | $(@node).find(".iframe").attr('src', data.src) 12 | -------------------------------------------------------------------------------- /widgets/iframe/iframe.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | -------------------------------------------------------------------------------- /widgets/iframe/iframe.scss: -------------------------------------------------------------------------------- 1 | .widget-iframe { 2 | padding: 3px 0px 0px 0px !important; 3 | 4 | iframe { 5 | width: 100%; 6 | height: 100%; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /widgets/image/image.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Image extends Dashing.Widget 2 | 3 | ready: -> 4 | # This is fired when the widget is done being rendered 5 | 6 | onData: (data) -> 7 | # Handle incoming data 8 | # You can access the html node of this widget with `@node` 9 | # Example: $(@node).fadeOut().fadeIn() will make the node flash each time data comes in. 10 | -------------------------------------------------------------------------------- /widgets/image/image.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /widgets/image/image.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #4b4b4b; 5 | 6 | // ---------------------------------------------------------------------------- 7 | // Widget-image styles 8 | // ---------------------------------------------------------------------------- 9 | .widget-image { 10 | 11 | background-color: $background-color; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /widgets/list/list.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.List extends Dashing.Widget 2 | ready: -> 3 | if @get('unordered') 4 | $(@node).find('ol').remove() 5 | else 6 | $(@node).find('ul').remove() 7 | -------------------------------------------------------------------------------- /widgets/list/list.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
      4 |
    1. 5 | 6 | 7 |
    2. 8 |
    9 | 10 |
      11 |
    • 12 | 13 | 14 |
    • 15 |
    16 |
    17 |
    18 |
    19 | -------------------------------------------------------------------------------- /widgets/list/list.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | // Import global settings (color, size, etc.) 5 | 6 | @import "application.scss"; 7 | 8 | // ---------------------------------------------------------------------------- 9 | // Widget-list styles 10 | // ---------------------------------------------------------------------------- 11 | .widget-list { 12 | 13 | background-color: $background-color; 14 | font-size: 15px; 15 | 16 | ol, ul { 17 | text-align: left; 18 | color: $label-color; 19 | } 20 | 21 | ul { 22 | color: #ffffff; 23 | } 24 | 25 | ol { 26 | list-style-position: inside; 27 | } 28 | 29 | li { 30 | margin-bottom: 5px; 31 | } 32 | 33 | .list-nostyle { 34 | list-style: none; 35 | } 36 | 37 | .label { 38 | color: $label-color; 39 | } 40 | 41 | .value { 42 | float: right; 43 | margin-left: 10px; 44 | font-weight: 500; 45 | color: $value-color; 46 | } 47 | 48 | .green { 49 | background-color: $background-color-green; 50 | } 51 | .yellow { 52 | background-color: $background-color-yellow; 53 | } 54 | .red { 55 | background-color: $background-color-red; 56 | } 57 | .purple { 58 | background-color: $background-color-purple; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /widgets/meter/meter.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Meter extends Dashing.Widget 2 | 3 | @accessor 'value', Dashing.AnimatedValue 4 | 5 | constructor: -> 6 | super 7 | @observe 'value', (value) -> 8 | $(@node).find(".meter").val(value).trigger('change') 9 | 10 | @observe 'max', (max) -> 11 | $(@node).find(".meter").trigger('configure', {'max': max}) 12 | 13 | ready: -> 14 | meter = $(@node).find(".meter") 15 | meter.attr("data-bgcolor", meter.css("background-color")) 16 | meter.attr("data-fgcolor", meter.css("color")) 17 | meter.knob() 18 | -------------------------------------------------------------------------------- /widgets/meter/meter.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 4 |
    5 |
    6 |
    7 | -------------------------------------------------------------------------------- /widgets/meter/meter.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | // Import global settings (color, size, etc.) 5 | 6 | @import "application.scss"; 7 | 8 | $meter-background: darken($icinga-blue, 12%); 9 | $meter-color: rgba(255, 255, 255, 0.95); 10 | 11 | // ---------------------------------------------------------------------------- 12 | // Widget-meter styles 13 | // ---------------------------------------------------------------------------- 14 | .widget-meter { 15 | 16 | background-color: $background-color; 17 | 18 | input.meter { 19 | background-color: $meter-background; 20 | color: $meter-color; 21 | } 22 | 23 | .title { 24 | color: $title-color; 25 | } 26 | 27 | .more-info { 28 | color: $moreinfo-color; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /widgets/number/number.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Number extends Dashing.Widget 2 | @accessor 'current', Dashing.AnimatedValue 3 | 4 | @accessor 'difference', -> 5 | if @get('last') 6 | last = parseInt(@get('last')) 7 | current = parseInt(@get('current')) 8 | if last != 0 9 | diff = Math.abs(Math.round((current - last) / last * 100)) 10 | "#{diff}%" 11 | else 12 | "" 13 | 14 | @accessor 'arrow', -> 15 | if @get('last') 16 | if parseInt(@get('current')) > parseInt(@get('last')) then 'fa fa-arrow-up' else 'fa fa-arrow-down' 17 | 18 | onData: (data) -> 19 | if data.status 20 | # clear existing "status-*" classes 21 | $(@get('node')).attr 'class', (i,c) -> 22 | c.replace /\bstatus-\S+/g, '' 23 | # add new class 24 | $(@get('node')).addClass "status-#{data.status}" 25 | -------------------------------------------------------------------------------- /widgets/number/number.html: -------------------------------------------------------------------------------- 1 |

    2 | 3 |

    4 | 5 |

    6 | 7 |

    8 | 9 |

    10 | 11 |

    12 | -------------------------------------------------------------------------------- /widgets/number/number.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #47bbb3; 5 | $value-color: #fff; 6 | 7 | $title-color: rgba(255, 255, 255, 0.7); 8 | $moreinfo-color: rgba(255, 255, 255, 0.7); 9 | 10 | // ---------------------------------------------------------------------------- 11 | // Widget-number styles 12 | // ---------------------------------------------------------------------------- 13 | .widget-number { 14 | 15 | background-color: $background-color; 16 | 17 | .title { 18 | color: $title-color; 19 | } 20 | 21 | .value { 22 | color: $value-color; 23 | } 24 | 25 | .change-rate { 26 | font-weight: 500; 27 | font-size: 30px; 28 | color: $value-color; 29 | } 30 | 31 | .more-info { 32 | color: $moreinfo-color; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /widgets/roundchartjs/roundchartjs.coffee: -------------------------------------------------------------------------------- 1 | 2 | #`function generateLabels(chart) { 3 | # var data = chart.data; 4 | # if (data.labels.length && data.datasets.length) { 5 | # return data.labels.map(function(label, i) { 6 | # var meta = chart.getDatasetMeta(0); 7 | # var style = meta.controller.getStyle(i); 8 | # 9 | # // fetch the value 10 | # var value = chart.config.data.datasets[arc._datasetIndex].data[arc._index]; 11 | # 12 | # return { 13 | # // Add the numbers next to the label 14 | # text: label + ": " + value, 15 | # fillStyle: style.backgroundColor, 16 | # strokeStyle: style.borderColor, 17 | # lineWidth: style.borderWidth, 18 | # hidden: isNaN(data.datasets[0].data[i]) || meta.data[i].hidden, 19 | # 20 | # // Extra data used for toggling the correct item 21 | # index: i 22 | # }; 23 | # }); 24 | # } 25 | # return []; 26 | #}` 27 | 28 | class Dashing.Roundchartjs extends Dashing.Widget 29 | 30 | constructor: -> 31 | super 32 | @id = @get("id") 33 | @type = @get("type") 34 | @header = @get("header") 35 | @labels = @get("labels") && @get("labels").split(",") 36 | @options = @get("options") || {} 37 | 38 | if @type == "scatter" 39 | @datasets = @get("datasets") 40 | else 41 | @datasets = @get("datasets") && @get("datasets").split(",") 42 | 43 | @colorNames = @get("colornames") && @get("colornames").split(",") 44 | 45 | ready: -> 46 | Chart.defaults.global.defaultColor = 'rgb(255, 255, 255)' 47 | Chart.defaults.global.defaultFontColor = 'rgb(255, 255, 255)' 48 | Chart.defaults.global.legend.labels.fontColor = 'rgb(255, 255, 255)' 49 | Chart.defaults.global.layout.padding = { left: 10, right: 10, top: 10, bottom: 10 } 50 | Chart.defaults.global.elements.point.radius = 5 51 | Chart.defaults.global.legend.display = false 52 | Chart.defaults.global.legend.position = 'bottom' 53 | 54 | Chart.defaults.doughnut.legend.position = 'right' 55 | Chart.defaults.doughnut.legend.display = true 56 | 57 | Chart.defaults.bar.barThickness = 'flex' 58 | #Chart.defaults.bar.legend.display = false 59 | Chart.defaults.bar.backgroundColor = 'rgb(255, 255, 255)' 60 | Chart.defaults.bar.scales.yAxes = [{ 61 | ticks: { 62 | beginAtZero: true 63 | } 64 | }] 65 | 66 | # Override original legend label generator 67 | # https://github.com/chartjs/Chart.js/blob/master/src/controllers/controller.doughnut.js#L45 68 | # Chart.defaults.global.legend.labels.generateLabels = generateLabels 69 | 70 | @draw() 71 | 72 | onData: (data) -> 73 | @type = data.type || @type 74 | @header = data.header || @header 75 | @labels = data.labels || @labels 76 | @options = data.options || @options 77 | @datasets = data.datasets || @datasets 78 | @colorNames = data.colorNames || @colorNames 79 | 80 | @draw() 81 | 82 | draw: -> 83 | switch @type 84 | when "pie", "doughnut", "polarArea" 85 | @circularChart @id, 86 | type: @type, 87 | labels: @labels, 88 | colors: @colorNames, 89 | datasets: @datasets, 90 | options: @options 91 | 92 | when "line", "bar", "horizontalBar", "radar", "scatter" 93 | @linearChart @id, 94 | type: @type, 95 | header: @header, 96 | labels: @labels, 97 | colors: @colorNames, 98 | datasets: @datasets, 99 | options: @options 100 | 101 | else 102 | return 103 | 104 | circularChart: (id, { type, labels, colors, datasets, options }) -> 105 | @ensureChartExists() 106 | @chart.data = @merge labels: labels, datasets: [@merge data: datasets, @colors(colors)] 107 | @chart.update() 108 | 109 | linearChart: (id, { type, labels, header, colors, datasets, options }) -> 110 | @ensureChartExists() 111 | @chart.data = @merge labels: labels, datasets: [@merge(@colors(colors), label: header, data: datasets)] 112 | @chart.update() 113 | 114 | ensureChartExists: () -> 115 | if typeof @chart == "undefined" 116 | @chart = new Chart(document.getElementById(@id), { type: @type, data: @merge labels: @labels, datasets: [@merge data: @datasets, @colors(@colorNames)] }, @options) 117 | 118 | merge: (xs...) => 119 | if xs?.length > 0 120 | @tap {}, (m) -> m[k] = v for k, v of x for x in xs 121 | 122 | tap: (o, fn) -> fn(o); o 123 | 124 | # Keep this in sync with application.scss 125 | #$background-color-green: rgba(68, 187, 119, $background-color-transparent-factor); 126 | #$background-color-red: rgba(255, 85, 102, $background-color-transparent-factor); 127 | #$background-color-yellow: rgba(255, 170, 68, $background-color-transparent-factor); 128 | #$background-color-purple: rgba(170, 68, 255, $background-color-transparent-factor); 129 | #$background-color-grey: rgba(153, 153, 153, $background-color-transparent-factor); 130 | 131 | colorCode: -> 132 | aqua: "0, 255, 255" 133 | black: "0, 0, 0" 134 | blue: "151, 187, 205" 135 | cyan: "0, 255, 255" 136 | darkgray: "77, 83, 96" 137 | fuschia: "255, 0, 255" 138 | gray: "153, 153, 153" 139 | green: "68, 187, 119" 140 | lightgray: "220, 220, 220" 141 | lime: "0, 255, 0" 142 | magenta: "255, 0, 255" 143 | maroon: "128, 0, 0" 144 | navy: "0, 0, 128" 145 | olive: "128, 128, 0" 146 | purple: "170, 68, 255" 147 | red: "255, 85, 102" 148 | silver: "192, 192, 192" 149 | teal: "0, 128, 128" 150 | white: "255, 255, 255" 151 | yellow: "255, 170, 68" 152 | 153 | colors: (colorNames) -> 154 | backgroundColor: colorNames.map (colorName) => @backgroundColor(colorName) 155 | borderColor: colorNames.map (colorName) => @borderColor(colorName) 156 | borderWidth: colorNames.map (colorName) -> 1 157 | pointBackgroundColor: colorNames.map (colorName) => @pointBackgroundColor(colorName) 158 | pointBorderColor: colorNames.map (colorName) => @pointBorderColor(colorName) 159 | pointHoverBackgroundColor: colorNames.map (colorName) => @pointHoverBackgroundColor(colorName) 160 | pointHoverBorderColor: colorNames.map (colorName) => @pointHoverBorderColor(colorName) 161 | 162 | backgroundColor: (colorName) -> "rgba(#{ @colorCode()[colorName] }, 0.8)" 163 | borderColor: (colorName) -> "rgba(#{ @colorCode()[colorName] }, 1)" 164 | pointBackgroundColor: (colorName) -> "rgba(#{ @colorCode()[colorName] }, 1)" 165 | pointBorderColor: (colorName) -> "rgba(#{ @colorCode()[colorName] }, 1)" 166 | pointHoverBackgroundColor: -> "fff" 167 | pointHoverBorderColor: (colorName) -> "rgba(#{ @colorCode()[colorName] }, 0.8)" 168 | 169 | circleColor: (colorName) -> 170 | backgroundColor: "rgba(#{ @colorCode()[colorName] }, 0.2)" 171 | borderColor: "rgba(#{ @colorCode()[colorName] }, 1)" 172 | borderWidth: 1 173 | hoverBackgroundColor: "#fff" 174 | hoverBorderColor: "rgba(#{ @colorCode()['blue'] },0.8)" 175 | -------------------------------------------------------------------------------- /widgets/roundchartjs/roundchartjs.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 4 |
    5 | -------------------------------------------------------------------------------- /widgets/showmon/showmon.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Showmon extends Dashing.Widget 2 | 3 | ready: -> 4 | # This is fired when the widget is done being rendered 5 | 6 | onData: (data) -> 7 | # Handle incoming data 8 | # You can access the html node of this widget with `@node` 9 | # Example: $(@node).fadeOut().fadeIn() will make the node flash each time data comes in. -------------------------------------------------------------------------------- /widgets/showmon/showmon.html: -------------------------------------------------------------------------------- 1 |
    -------------------------------------------------------------------------------- /widgets/showmon/showmon.scss: -------------------------------------------------------------------------------- 1 | .widget-showmon { 2 | 3 | } -------------------------------------------------------------------------------- /widgets/simplelist/simplelist.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Simplelist extends Dashing.Widget 2 | ready: -> 3 | if @get('unordered') 4 | $(@node).find('ol').remove() 5 | else 6 | $(@node).find('ul').remove() 7 | -------------------------------------------------------------------------------- /widgets/simplelist/simplelist.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 |
    4 |
  • 5 |

    6 |
  • 7 |
    8 | 9 |
    10 | 11 |
    12 | -------------------------------------------------------------------------------- /widgets/simplelist/simplelist.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | // Import global settings (color, size, etc.) 5 | 6 | @import "application.scss"; 7 | 8 | // ---------------------------------------------------------------------------- 9 | // Widget-number styles 10 | // ---------------------------------------------------------------------------- 11 | .widget-simplelist { 12 | 13 | background-color: $background-color; 14 | 15 | .value { 16 | margin-top: 40px; 17 | color: $value-color; 18 | } 19 | 20 | h3 { 21 | font-size: 40px; 22 | font-weight: 600; 23 | } 24 | 25 | .green { 26 | background-color: $background-color-green; 27 | } 28 | .yellow { 29 | background-color: $background-color-yellow; 30 | } 31 | .red { 32 | background-color: $background-color-red; 33 | } 34 | .purple { 35 | background-color: $background-color-purple; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /widgets/simplemon/simplemon.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Simplemon extends Dashing.Widget 2 | @accessor 'current', Dashing.AnimatedValue 3 | 4 | ready: -> 5 | #setInterval(@checkUpdate, 100) 6 | 7 | onData: (data) -> 8 | if data.color 9 | # clear existing "color-*" classes 10 | $(@get('node')).attr 'class', (i,c) -> 11 | c.replace /\bcolor-\S+/g, '' 12 | # add new class 13 | $(@get('node')).addClass "color-#{data.color}" 14 | 15 | checkUpdate: => 16 | if updatedAt = @get('updatedAt') 17 | timestamp = new Date(updatedAt * 1000) 18 | now = new Date() 19 | diff = now.getTime() - timestamp.getTime() 20 | if diff > 30000 21 | @onData({color:'grey'}) 22 | -------------------------------------------------------------------------------- /widgets/simplemon/simplemon.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 |
    4 |

    5 |

    6 |
    7 | 8 |
    9 | 10 |
    11 | -------------------------------------------------------------------------------- /widgets/simplemon/simplemon.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | // Import global settings (color, size, etc.) 5 | 6 | @import "application.scss"; 7 | 8 | $background-color: #47bbb3; 9 | $value-color: #fff; 10 | 11 | $background-color-green: #44bb77; 12 | $background-color-green-1: #44bb77; 13 | $background-color-green-2: #44bb77; 14 | $value-color-green: #fff; 15 | 16 | $background-color-red: #ff5566; 17 | $background-color-red-1: #ff5566; 18 | $background-color-red-2: #ff5566; 19 | $value-color-red: #fff; 20 | 21 | $background-color-yellow: #ffaa44; 22 | $background-color-yellow-1: #ffaa44; 23 | $background-color-yellow-2: #ffaa44; 24 | $value-color-yellow: #fff; 25 | 26 | $background-color-purple: #aa44ff; 27 | $background-color-purple-1: #aa44ff; 28 | $background-color-purple-2: #aa44ff; 29 | $value-color-purple: #fff; 30 | 31 | $background-color-grey: #999999; 32 | $value-color-grey: #fff; 33 | 34 | $title-color: rgba(255, 255, 255, 0.7); 35 | $title-color-uns: rgba(0, 0, 0, 0.7); 36 | $moreinfo-color: rgba(255, 255, 255, 0.7); 37 | 38 | @-webkit-keyframes status-yellow-background { 39 | 0% { background-color: $background-color-yellow-1; } 40 | 50% { background-color: $background-color-yellow-2; } 41 | 100% { background-color: $background-color-yellow-1; } 42 | } 43 | @-webkit-keyframes status-red-background { 44 | 0% { background-color: $background-color-red-1; } 45 | 50% { background-color: $background-color-red-2; } 46 | 100% { background-color: $background-color-red-1; } 47 | } 48 | @-webkit-keyframes status-green-background { 49 | 0% { background-color: $background-color-green-1; } 50 | 50% { background-color: $background-color-green-2; } 51 | 100% { background-color: $background-color-green-1; } 52 | } 53 | @-webkit-keyframes status-purple-background { 54 | 0% { background-color: $background-color-purple-1; } 55 | 50% { background-color: $background-color-purple-2; } 56 | 100% { background-color: $background-color-purple-1; } 57 | } 58 | @mixin animation($animation-name, $duration, $function, $animation-iteration-count:""){ 59 | -webkit-animation: $animation-name $duration $function #{$animation-iteration-count}; 60 | -moz-animation: $animation-name $duration $function #{$animation-iteration-count}; 61 | -ms-animation: $animation-name $duration $function #{$animation-iteration-count}; 62 | } 63 | 64 | 65 | // ---------------------------------------------------------------------------- 66 | // Widget-number styles 67 | // ---------------------------------------------------------------------------- 68 | .widget-simplemon { 69 | 70 | background-color: $background-color; 71 | 72 | .value { 73 | margin-top: 40px; 74 | color: $value-color; 75 | } 76 | 77 | } 78 | .widget-simplemon.color-green { 79 | background-color: $background-color-green; 80 | .value { color: $value-color-green; } 81 | } 82 | .widget-simplemon.color-green-blink { 83 | background-color: $background-color-green; 84 | @include animation(status-green-background, 2s, ease, infinite); 85 | .value { color: $value-color-green; } 86 | } 87 | .widget-simplemon.color-red { 88 | background-color: $background-color-red; 89 | .value { color: $value-color-red; } 90 | } 91 | .widget-simplemon.color-red-blink { 92 | background-color: $background-color-red; 93 | @include animation(status-red-background, 2s, ease, infinite); 94 | .value { color: $value-color-red; } 95 | } 96 | .widget-simplemon.color-yellow { 97 | background-color: $background-color-yellow; 98 | .value { color: $value-color-yellow; } 99 | } 100 | .widget-simplemon.color-yellow-blink { 101 | @include animation(status-yellow-background, 2s, ease, infinite); 102 | background-color: $background-color-yellow; 103 | .value { color: $value-color-yellow; } 104 | } 105 | .widget-simplemon.color-purple { 106 | background-color: $background-color-purple; 107 | .value { color: $value-color-purple; } 108 | } 109 | .widget-simplemon.color-purple-blink { 110 | @include animation(status-purple-background, 2s, ease, infinite); 111 | background-color: $background-color-purple; 112 | .value { color: $value-color-purple; } 113 | } 114 | .widget-simplemon.color-grey { 115 | background-color: $background-color-grey; 116 | .value { color: $value-color-grey; } 117 | } 118 | -------------------------------------------------------------------------------- /widgets/table/table.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Table extends Dashing.Widget 2 | 3 | ready: -> 4 | # This is fired when the widget is done being rendered 5 | 6 | onData: (data) -> 7 | # Handle incoming data 8 | # You can access the html node of this widget with `@node` 9 | # Example: $(@node).fadeOut().fadeIn() will make the node flash each time data comes in. -------------------------------------------------------------------------------- /widgets/table/table.html: -------------------------------------------------------------------------------- 1 |
    2 | -------------------------------------------------------------------------------- /widgets/table/table.scss: -------------------------------------------------------------------------------- 1 | .widget-table { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /widgets/text/text.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Text extends Dashing.Widget 2 | -------------------------------------------------------------------------------- /widgets/text/text.html: -------------------------------------------------------------------------------- 1 |

    2 | 3 |

    4 | 5 |

    6 | 7 |

    8 | -------------------------------------------------------------------------------- /widgets/text/text.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #ec663c; 5 | 6 | $title-color: rgba(255, 255, 255, 0.7); 7 | $moreinfo-color: rgba(255, 255, 255, 0.7); 8 | 9 | // ---------------------------------------------------------------------------- 10 | // Widget-text styles 11 | // ---------------------------------------------------------------------------- 12 | .widget-text { 13 | 14 | background-color: $background-color; 15 | 16 | .title { 17 | color: $title-color; 18 | } 19 | 20 | .more-info { 21 | color: $moreinfo-color; 22 | } 23 | 24 | 25 | &.large h3 { 26 | font-size: 65px; 27 | } 28 | } 29 | --------------------------------------------------------------------------------