diff --git a/dashboard/README.md b/dashboard/README.md new file mode 100644 index 0000000..b95b366 --- /dev/null +++ b/dashboard/README.md @@ -0,0 +1,3 @@ +# Dashboard + +Allows using a tablet, smartphone, ebook reader or any other low-power internet-connected hardware as system monitor for an host on the same network. diff --git a/dashboard/dashboard.cfg.example b/dashboard/dashboard.cfg.example new file mode 100644 index 0000000..93b9cda --- /dev/null +++ b/dashboard/dashboard.cfg.example @@ -0,0 +1,95 @@ +[DEFAULT] + +# The webpage will be available at http://this.host.ip.address:PORT +PORT=8080 + +# The webpage will be updated every REFRESH_SECONDS. Set to 0 to disable autorefresh. +REFRESH_SECONDS=1 + +#### SENSORS #### +# Every sensor value is obtained on a command being executed, its result being parsed with a regexp +# to extract (as a single group) the numeric or string value, and the value being used to plot the +# graph. This sensor definitions are ready to be used, just enable the ones you need. +# You can add your own declaring another section like this: +# +# [my_sensor] +# DISABLED=False +# COMMAND=/my/custom/binary --with parameters +# REGEXP=my regex to parse (awesome|disappointing) command output +# TYPE=TIMEGRAPH # May also be GRAPH or ERROR + +[system_load_1min] +# The system load average in the last minute +DISABLED=True +COMMAND=uptime +REGEXP=.*load average: (\d+[,.]\d+), \d+[,.]\d+, \d+[,.]\d+ +TYPE=TIMEGRAPH + +[system_load_5min] +# The system load average in the last 5 minutes +DISABLED=True +COMMAND=uptime +REGEXP=.*load average: \d+[,.]\d+, (\d+[,.]\d+), \d+[,.]\d+ +TYPE=TIMEGRAPH + +[system_load_15min] +# The system load average in the last 15 minutes +DISABLED=True +COMMAND=uptime +REGEXP=.*load average: \d+[,.]\d+, \d+[,.]\d+, (\d+[,.]\d+) +TYPE=TIMEGRAPH + +[used_disk_space] +# Used disk space in percent +DISABLED=True +COMMAND=df -h /dev/sda1 +REGEXP=(\d{1,3})% +TYPE=GRAPH + +[raid_status] +# Raid status +DISABLED=True +COMMAND=cat /proc/mdstat +REGEXP=.*\] \[([U_]+)\]\n +TYPE=ERROR + +[laptop_charger_disconnected] +# Laptop charger disconnected +# For laptops used as servers, apparently common among the self hosters. Requires acpi package installed. +DISABLED=True +COMMAND=acpi -a +REGEXP=Adapter \d: (.+) +TYPE=ERROR + +[free_ram] +# Free ram in % +# Shows another approach: does all the computation in the command and picks up +# all the output (by not declaring a regexp). +DISABLED=True +COMMAND=free | grep Mem | awk '{print int($4/$2 * 100.0)}' +TYPE=TIMEGRAPH + +[available_ram] +# Like Free ram, but shows available instead of free. You may want to use this if you use a memcache. +DISABLED=True +COMMAND=free | grep Mem | awk '{print int($7/$2 * 100.0)}' +TYPE=TIMEGRAPH + +[cpu_temperature] +# CPU Temperature alarm: requires lm-sensors installed and configured (check your distribution's guide) +# The regexp must be adapted to your configuration: run `sensors` in the command line +# to find the name of the temperature sensor in your system. In this case is `Core 0`, +# but may be called Tdie or a lot of different names, there is no standard. +DISABLED=True +COMMAND=sensors +REGEXP=Core 0: +\+?(-?\d{1,3}).\d°[CF] +TYPE=TIMEGRAPH + +[fan_speed] +# Fan speed alarm: requires lm-sensors installed and configured (check your distribution's guide) +# The regexp must be adapted to your configuration: run `sensors` in the command line +# to find the name of the fan speed sensor in your system. +DISABLED=True +COMMAND=sensors +REGEXP=cpu_fan: +(\d) RPM +TYPE=TIMEGRAPH diff --git a/dashboard/dashboard.py b/dashboard/dashboard.py new file mode 100755 index 0000000..069f7ab --- /dev/null +++ b/dashboard/dashboard.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 + +import os +import sys +import time +from http.server import BaseHTTPRequestHandler, HTTPServer +import logging +import traceback +import re +import locale +import subprocess +import configparser + + +""" @package docstring +Resources dashboard + +Starts a webserver on a specific port of the monitored server and serves a simple webpage containing +the monitored sensors graphs. + +Installation: +- Copy dashboard.cfg in /usr/local/etc/dashboard.cfg and customize it +- Copy dashboard.py in /usr/local/bin/dashboard.py + +Usage: +Start the server: +/usr/local/bin/dashboard.py /usr/local/etc/dashboard.cfg + +@author Daniele Verducci + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + + +NAME = 'dashboard' +VERSION = '0.1' +DESCRIPTION = 'A simple system resources dashboard' + + +class WebServer(BaseHTTPRequestHandler): + + def __init__(self, configPath): + ''' Sets up locale (needed for parsing numbers) ''' + # Get system locale from $LANG (i.e. "en_GB.UTF-8") + systemLocale = os.getenv('LANG') + if not systemLocale: + raise ValueError('System environment variabile $LANG is not set!') + + locale.setlocale(locale.LC_ALL, systemLocale) + + ''' Reads the config ''' + self._log = logging.getLogger('main') + + if not os.path.exists(configPath) or not os.path.isfile(configPath): + raise ValueError('configPath must be a file') + + self.config = configparser.ConfigParser(interpolation=None) # Disable interpolation because contains regexp + self.config.read(configPath) + self.hostname = os.uname()[1] + + def do_GET(self): + self.readSensors() + + self.send_response(200) + self.send_header("Content-type", "text/html") + self.end_headers() + self.wfile.write(bytes("https://pythonbasics.org", "utf-8")) + self.wfile.write(bytes("

Request: %s

" % self.path, "utf-8")) + self.wfile.write(bytes("", "utf-8")) + self.wfile.write(bytes("

This is an example web server.

", "utf-8")) + self.wfile.write(bytes("", "utf-8")) + + def readSensors(): + return + +if __name__ == '__main__': + import argparse + + parser = argparse.ArgumentParser( + prog = NAME + '.py', + description = NAME + ' ' + VERSION + '\n' + DESCRIPTION, + formatter_class = argparse.RawTextHelpFormatter + ) + parser.add_argument('configFile', help="configuration file path") + parser.add_argument('-q', '--quiet', action='store_true', help="suppress non-essential output") + args = parser.parse_args() + + if args.quiet: + level = logging.WARNING + else: + level = logging.INFO + logging.basicConfig(level=level) + + port = + httpd = HTTPServer(('localhost', port), Server) + logging.info('Serving on port {}'.format(port)) + try: + httpd.serve_forever() + except KeyboardInterrupt: + pass + except Exception as e: + logging.critical(traceback.format_exc()) + print('ERROR: {}'.format(e)) + sys.exit(1) + finally: + httpd.server_close() + + sys.exit(0) \ No newline at end of file