diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1bb99d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.pyc +.*.swp +conf/servers.json diff --git a/README.md b/README.md index df9b82a..78a97a4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,85 @@ -# monit-dashboard - A web dashboard for rule all monit servers at a glance +# Monit Dashboard + +## Description +Python web application to get a dashboard of a bunch of [Monit][monit] +servers at a glance. + +## How does it work? +Every 300 seconds the application ask for the data served by the +Monit built-in web server in a XMl report from each configured server. +Then, thanks to the built-in web server, it is displayed in a single +HTML page. + +## Pre requisites + +### Debian GNU/Linux + +#### Web.py framework +- `apt install python-webpy` + +#### Python libraries +- `apt install python-xmltodict python-requests` + +### CentOS + +#### Python PIP +- `yum install epel-release` +- `yum install python-pip` + +#### Web.py framework +- `pip install web.py` + +#### Python libaries + +- `yum install python-requests python-xmltodict python-simplejson` + +## Requisites +- Config file `conf/servers.json` prior run. You might find a sample +file at `conf/servers.json.example`. +- Please see [Config](#config) section for further details. + +## Run +`./bin/monit-dashboard.py` + +By default, it will be reachable at http://localhost:8080. You might +change the port by adjusting `app.run(port=8080)` in +`bin/monit-dashboard.py` file. + +## References +- [web.py 0.3 tutorial][webpy-tutorial] +- [Learn Python the hard way, ex 50][lpthw] +- [A template example][template-example] +- [How to change HTTP server port][port] + +## Config +- Placed in `conf/servers.json` +- Sample settings as follows: +``` +{ + "My server to monit": { + "url": "http://example.com:2812", + "user": "monit", + "passwd": "*****" + } +} +``` + +# Credits +- [Original idea][idea] +- Frontend support: Júlia Vázquez +- [Icons][icons] +- [Accordion menu][accordion] + +# License +[AGPL][license] + +[monit]: https://mmonit.com/monit/ +[webpy]: http://webpy.org/ +[webpy-tutorial]: http://webpy.org/tutorial3.en +[port]: https://stackoverflow.com/questions/14444913/web-py-specify-address-and-port +[lpthw]: https://learnpythonthehardway.org/book/ex50.html +[template-example]: https://stackoverflow.com/questions/28508869/using-web-py-to-dynamically-output-values-from-process-initiated-by-form-submiss +[idea]: https://imil.net/blog/2016/03/16/Fetch-monit-status-in-JSON/ +[icons]: https://commons.wikimedia.org/wiki/User:House +[accordion]: http://www.w3schools.com/howto/howto_js_accordion.asp +[license]: LICENSE diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bin/monit-dashboard.py b/bin/monit-dashboard.py new file mode 100755 index 0000000..c5dc05d --- /dev/null +++ b/bin/monit-dashboard.py @@ -0,0 +1,71 @@ +#!/usr/bin/python + +import web +import requests, xmltodict, json, os, sys +import datetime + +urls = ('/', 'index', + '/help', 'help' +) + +app = web.application(urls, globals()) +render = web.template.render('templates/', base="layout") + +## Uncomment to turn debug off +web.config.debug = False + +## Variables +output = [] + +## Functions + +def getMonit(): + output = [] + server = {} + checks = {} + xmlQuery = "/_status?format=xml" + + with open('{0}/conf/servers.json'.format(os.path.expanduser('.'))) as f: + cf = json.loads(f.read()) + + for site in cf: + s = cf[site] + r = requests.get(s['url'] + xmlQuery, auth = (s['user'], s['passwd'])) + + allstat = json.loads(json.dumps(xmltodict.parse(r.text)['monit'])) + + services = allstat['service'] + status = {} + checks = {} + + for service in services: + name = service['name'] + status[name] = int(service['status']) + checks[service['name']] = status[name] + + server = dict(name = site, url = s['url'], result = checks) + + output.append(server) + + print(datetime.datetime.now()) + return(output) + +## Classes + +class monitDashboard(web.application): + def run(self, port=8080, *middleware): + func = self.wsgifunc(*middleware) + return web.httpserver.runsimple(func, ('0.0.0.0', port)) + +class index(object): + def GET(self): + return render.index(output = getMonit(), now = datetime.datetime.now()) + +class help(object): + def GET(self): + return render.help() + +## Main +if __name__ == "__main__": + app = monitDashboard(urls, globals()) + app.run(port=8080) diff --git a/conf/servers.json.example b/conf/servers.json.example new file mode 100644 index 0000000..7cbdbe4 --- /dev/null +++ b/conf/servers.json.example @@ -0,0 +1,14 @@ +{ + Server A": { + "url": "http://one.example.com:2812", + "user": "monit", + "passwd": "secret" + }, + + "Server B": { + "url": "http://two.example.com:2812", + "user": "monit", + "passwd": "secret" + } + +} diff --git a/static/img/error.png b/static/img/error.png new file mode 100644 index 0000000..5e52ae6 Binary files /dev/null and b/static/img/error.png differ diff --git a/static/img/ok.png b/static/img/ok.png new file mode 100644 index 0000000..2905a9b Binary files /dev/null and b/static/img/ok.png differ diff --git a/static/monit-dashboard.css b/static/monit-dashboard.css new file mode 100644 index 0000000..8b11e3d --- /dev/null +++ b/static/monit-dashboard.css @@ -0,0 +1,72 @@ +.server-link a { + color: #fff; + text-decoration: none; + text-transform: uppercase; + text-decoration: underline; +} + +th { + background-color: #999; + padding: 10px; +} + +td { + border-bottom: 1px solid #ccc; + padding: 15px; +} + +body { + font-family: arial; +} + +tr:nth-child(odd) { + background-color:#fff; +} + +tr:nth-child(even) { + background-color:#f2f2f2; +} + +a { + color: #cb0017; + font-weight: bolder; +} + +button.accordion { + color: #444; + cursor: pointer; + padding: 18px; + width: 100%; + text-align: left; + border: none; + outline: none; + transition: 0.4s; +} + +div.panel { + padding: 0 18px; + background-color: white; + display: none; +} + +div.panel.show { + display: block; +} + +.green { + background-color: #79bd8f; + font-size: 20px; +} + +.green:hover { + background-color: #588a68; +} + +.red { + background-color: #d9534f; + font-size: 20px; +} + +.red:hover { + background-color: #c9302c; +} diff --git a/static/monit-dashboard.js b/static/monit-dashboard.js new file mode 100644 index 0000000..f3d0746 --- /dev/null +++ b/static/monit-dashboard.js @@ -0,0 +1,9 @@ +var acc = document.getElementsByClassName("accordion"); +var i; + +for (i = 0; i < acc.length; i++) { + acc[i].onclick = function(){ + this.classList.toggle("active"); + this.nextElementSibling.classList.toggle("show"); + } +} diff --git a/templates/help.html b/templates/help.html new file mode 100644 index 0000000..ba5755f --- /dev/null +++ b/templates/help.html @@ -0,0 +1,3 @@ +
+

Please check the README file.

+
diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..28512df --- /dev/null +++ b/templates/index.html @@ -0,0 +1,82 @@ +$def with (output, now) + +$ errors = 0 +$ color = "green" +$for server in range(len(output)): + $code: + errors = 0 + color = "green" + + $for check in output[server]['result'].keys(): + $ isError = output[server]['result'].get(check) + $if isError != 0: + $code: + errors=errors+1 + + $if errors > 0: + $code: + color = "red" + +
+ +
+ + + + + $for check in output[server]['result'].keys(): + $ isError = output[server]['result'].get(check) + $if isError != 0: + + + + + $else: + + + + + +
+ $output[server]['name'] +
$check
$check
+
+ $else: + $code: + color = "green" + +
+ +
+ + + + + $for check in output[server]['result'].keys(): + $ isError = output[server]['result'].get(check) + $if isError != 0: + + + + + $else: + + + + + +
+ $output[server]['name'] +
$check
$check
+
+ +


+ +Latest update: $now.day/$now.month/$now.year, $now.hour:$now.minute:$now.second +

+ + diff --git a/templates/layout.html b/templates/layout.html new file mode 100644 index 0000000..5bb58e8 --- /dev/null +++ b/templates/layout.html @@ -0,0 +1,15 @@ +$def with (content) + + + + Monit Dashboard + + + + + +$:content + +Home | Help + + diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29