Initial commit
I was working in a private repo and decided to switch to a public one without keeping the old commits.
This commit is contained in:
parent
d1f20bfa00
commit
0e115ee1fe
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
*.pyc
|
||||
.*.swp
|
||||
conf/servers.json
|
87
README.md
87
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
|
||||
|
|
0
__init__.py
Normal file
0
__init__.py
Normal file
71
bin/monit-dashboard.py
Executable file
71
bin/monit-dashboard.py
Executable file
|
@ -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)
|
14
conf/servers.json.example
Normal file
14
conf/servers.json.example
Normal file
|
@ -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"
|
||||
}
|
||||
|
||||
}
|
BIN
static/img/error.png
Normal file
BIN
static/img/error.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 719 B |
BIN
static/img/ok.png
Normal file
BIN
static/img/ok.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 711 B |
72
static/monit-dashboard.css
Normal file
72
static/monit-dashboard.css
Normal file
|
@ -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;
|
||||
}
|
9
static/monit-dashboard.js
Normal file
9
static/monit-dashboard.js
Normal file
|
@ -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");
|
||||
}
|
||||
}
|
3
templates/help.html
Normal file
3
templates/help.html
Normal file
|
@ -0,0 +1,3 @@
|
|||
<article>
|
||||
<p>Please check the README file.</p>
|
||||
</article>
|
82
templates/index.html
Normal file
82
templates/index.html
Normal file
|
@ -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"
|
||||
<button class="accordion ${color}">$output[server]['name']: $errors error(s)</button>
|
||||
<div class="panel">
|
||||
|
||||
<br>
|
||||
<table width=100%>
|
||||
<thead class="server-link">
|
||||
<tr><th colspan="2">
|
||||
<a href="$output[server]['url']" target="_blank">$output[server]['name']</a>
|
||||
</th></tr>
|
||||
</thead>
|
||||
$for check in output[server]['result'].keys():
|
||||
$ isError = output[server]['result'].get(check)
|
||||
$if isError != 0:
|
||||
<tr>
|
||||
<td><a href="$output[server]['url']/$check"
|
||||
target="_blank">$check</a></td>
|
||||
<td><a href="$output[server]['url']/$check"
|
||||
target="_blank"><img src="static/img/error.png"></a></td>
|
||||
</tr>
|
||||
$else:
|
||||
<tr>
|
||||
<td>$check</td>
|
||||
<td><img src="static/img/ok.png"></td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
$else:
|
||||
$code:
|
||||
color = "green"
|
||||
<button class="accordion ${color}">$output[server]['name']</button>
|
||||
<div class="panel">
|
||||
|
||||
<br>
|
||||
<table width=100%>
|
||||
<thead class="server-link">
|
||||
<tr><th colspan="2">
|
||||
<a href="$output[server]['url']" target="_blank">$output[server]['name']</a>
|
||||
</th></tr>
|
||||
</thead>
|
||||
$for check in output[server]['result'].keys():
|
||||
$ isError = output[server]['result'].get(check)
|
||||
$if isError != 0:
|
||||
<tr>
|
||||
<td><a href="$output[server]['url']/$check"
|
||||
target="_blank">$check</a></td>
|
||||
<td><a href="$output[server]['url']/$check"
|
||||
target="_blank"><img src="static/img/error.png"></a></td>
|
||||
</tr>
|
||||
$else:
|
||||
<tr>
|
||||
<td>$check</td>
|
||||
<td><img src="static/img/ok.png"></td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<br><br><br>
|
||||
|
||||
Latest update: $now.day/$now.month/$now.year, $now.hour:$now.minute:$now.second
|
||||
<br><br>
|
||||
|
||||
<script src="static/monit-dashboard.js" type="text/javascript"></script>
|
15
templates/layout.html
Normal file
15
templates/layout.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
$def with (content)
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Monit Dashboard</title>
|
||||
<link rel="stylesheet" type="text/css" href="static/monit-dashboard.css" />
|
||||
<meta http-equiv="refresh" content="300">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
$:content
|
||||
|
||||
<a href="/">Home</a> | <a href="/help">Help</a>
|
||||
</body>
|
||||
</html>
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
Loading…
Reference in a new issue