diff options
43 files changed, 1506 insertions, 184 deletions
diff --git a/MANIFEST.in b/MANIFEST.in index 74ab5f46..859466ca 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -7,6 +7,7 @@ include glances/outputs/bottle/*.tpl include glances/outputs/static/*.ico include glances/outputs/static/css/*.css include glances/outputs/static/js/*.js +include glances/outputs/static/js/*.js.map include man/glances.1 recursive-include docs images/*.png glances-doc.html recursive-include glances *.py @@ -13,7 +13,7 @@ Glances - An eye on your system .. image:: https://pypip.in/d/Glances/badge.png :target: https://pypi.python.org/pypi/Glances/ :alt: Downloads - + Follow Glances on Twitter: `@nicolargo`_ or `@glances_system`_ diff --git a/glances/outputs/bottle/plugin_table.tpl b/glances/outputs/bottle/plugin_table.tpl deleted file mode 100644 index f98e5497..00000000 --- a/glances/outputs/bottle/plugin_table.tpl +++ /dev/null @@ -1,22 +0,0 @@ -% if stats['msgdict'] != []: -<section id="{{ plugin_name }}" class="plugin"> - <table class="table"> - <tbody> - <tr> - % for msg in stats['msgdict']: - % if msg['msg'].startswith('\n'): - </tr> - <tr> - % else: - % if stats['display']: - <td class="{{ msg['decoration'].lower() }} {{ 'hidden-xs hidden-sm' if msg['optional'] else '' }}"> - {{ msg['msg'] }} - </td> - % end - % end - % end - </tr> - </tbody> - </table> -</section> -% end
\ No newline at end of file diff --git a/glances/outputs/bottle/plugin_text.tpl b/glances/outputs/bottle/plugin_text.tpl deleted file mode 100644 index 990e7478..00000000 --- a/glances/outputs/bottle/plugin_text.tpl +++ /dev/null @@ -1,11 +0,0 @@ -% if stats['msgdict'] != []: -<section id="{{ plugin_name }}" class="plugin"> -% for msg in stats['msgdict']: - % if stats['display']: - <span class="{{ msg['decoration'].lower() }} {{ 'hidden-xs hidden-sm' if msg['optional'] else '' }}"> - {{ msg['msg'] }} - </span> - % end -% end -</section> -% end
\ No newline at end of file diff --git a/glances/outputs/glances_bottle.py b/glances/outputs/glances_bottle.py index a64272d8..c5529685 100644 --- a/glances/outputs/glances_bottle.py +++ b/glances/outputs/glances_bottle.py @@ -16,19 +16,20 @@ # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. - """Web interface class.""" +# Import Glances libs +# Import mandatory Bottle lib import json import os import sys -# Import Glances libs +from glances.core.glances_globals import is_windows from glances.core.glances_logging import logger -# Import mandatory Bottle lib + try: - from bottle import Bottle, template, static_file, TEMPLATE_PATH, abort, response, request + from bottle import Bottle, static_file, abort, response, request except ImportError: logger.critical('Bottle module not found. Glances cannot start in web server mode.') sys.exit(2) @@ -53,9 +54,6 @@ class GlancesBottle(object): # Define routes self._route() - # Update the template path (glances/outputs/bottle) - TEMPLATE_PATH.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'bottle')) - # Path where the statics files are stored self.STATIC_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'static') @@ -63,10 +61,17 @@ class GlancesBottle(object): """Define route.""" self._app.route('/', method="GET", callback=self._index) self._app.route('/<refresh_time:int>', method=["GET", "POST"], callback=self._index) + self._app.route('/<filename:re:.*\.css>', method="GET", callback=self._css) self._app.route('/<filename:re:.*\.js>', method="GET", callback=self._js) + self._app.route('/<filename:re:.*\.js.map>', method="GET", callback=self._js_map) + self._app.route('/<filename:re:.*\.html>', method="GET", callback=self._html) + + self._app.route('/<filename:re:.*\.png>', method="GET", callback=self._images) self._app.route('/favicon.ico', method="GET", callback=self._favicon) + # REST API + self._app.route('/api/2/help', method="GET", callback=self._api_help) self._app.route('/api/2/pluginslist', method="GET", callback=self._api_plugins) self._app.route('/api/2/all', method="GET", callback=self._api_all) self._app.route('/api/2/all/limits', method="GET", callback=self._api_all_limits) @@ -105,8 +110,13 @@ class GlancesBottle(object): self.stats.update() # Display - return self.display(self.stats, refresh_time=refresh_time) + return static_file("index.html", root=os.path.join(self.STATIC_PATH, 'html')) + def _html(self, filename): + """Bottle callback for *.html files.""" + # Return the static file + return static_file(filename, root=os.path.join(self.STATIC_PATH, 'html')) + def _css(self, filename): """Bottle callback for *.css files.""" # Return the static file @@ -117,11 +127,37 @@ class GlancesBottle(object): # Return the static file return static_file(filename, root=os.path.join(self.STATIC_PATH, 'js')) + def _js_map(self, filename): + """Bottle callback for *.js.map files.""" + # Return the static file + return static_file(filename, root=os.path.join(self.STATIC_PATH, 'js')) + + def _images(self, filename): + """Bottle callback for *.png files.""" + # Return the static file + return static_file(filename, root=os.path.join(self.STATIC_PATH, 'images')) + def _favicon(self): """Bottle callback for favicon.""" # Return the static file return static_file('favicon.ico', root=self.STATIC_PATH) + def _api_help(self): + """ + Glances API RESTFul implementation + Return the help data + or 404 error + """ + response.content_type = 'application/json' + + # Update the stat + view_data = self.stats.get_plugin("help").get_view_data() + try: + plist = json.dumps(view_data, sort_keys=True) + except Exception as e: + abort(404, "Cannot get help view data (%s)" % str(e)) + return plist + def _api_plugins(self): """ @api {get} /api/2/pluginslist Get plugins list @@ -168,15 +204,24 @@ class GlancesBottle(object): """ response.content_type = 'application/json' - # Update the stat - self.stats.update() - - try: - # Get the JSON value of the stat value - statval = json.dumps(self.stats.getAllAsDict()) - except Exception as e: - abort(404, "Cannot get stats (%s)" % str(e)) - return statval + if not self.args.debug: + # Update the stat + self.stats.update() + + try: + # Get the JSON value of the stat ID + statval = json.dumps(self.stats.getAllAsDict()) + except Exception as e: + abort(404, "Cannot get stats (%s)" % str(e)) + return statval + else: + path = "~/glances/" + if is_windows: + path = "D:\\glances\\" + filepath = path + "debug.json" + + f = open(filepath) + return f.read() def _api_all_limits(self): """ @@ -328,33 +373,6 @@ class GlancesBottle(object): else: return pdict - def display(self, stats, refresh_time=None): - """Display stats on the web page. - - stats: Stats database to display - """ - - stats = { - 'system': self.stats.get_plugin('system').get_stats_display(args=self.args), - 'uptime': self.stats.get_plugin('uptime').get_stats_display(args=self.args), - 'cpu': self.stats.get_plugin('cpu').get_stats_display(args=self.args), - 'load': self.stats.get_plugin('load').get_stats_display(args=self.args), - 'mem': self.stats.get_plugin('mem').get_stats_display(args=self.args), - 'memswap': self.stats.get_plugin('memswap').get_stats_display(args=self.args), - 'network': self.stats.get_plugin('network').get_stats_display(args=self.args), - 'diskio': self.stats.get_plugin('diskio').get_stats_display(args=self.args), - 'fs': self.stats.get_plugin('fs').get_stats_display(args=self.args), - 'raid': self.stats.get_plugin('raid').get_stats_display(args=self.args), - 'sensors': self.stats.get_plugin('sensors').get_stats_display(args=self.args), - 'alert': self.stats.get_plugin('alert').get_stats_display(args=self.args), - 'processcount': self.stats.get_plugin('processcount').get_stats_display(args=self.args), - 'monitor': self.stats.get_plugin('monitor').get_stats_display(args=self.args), - 'processlist': self.stats.get_plugin('processlist').get_stats_display(args=self.args), - 'docker': self.stats.get_plugin('docker').get_stats_display(args=self.args) - } - - return template('base', refresh_time=refresh_time, stats=stats) - class EnableCors(object): name = 'enable_cors' diff --git a/glances/outputs/static/css/style.css b/glances/outputs/static/css/style.css index 3ac50123..4bb9e6f8 100644 --- a/glances/outputs/static/css/style.css +++ b/glances/outputs/static/css/style.css @@ -3,31 +3,45 @@ body { color: #BBB; font-family: "Lucida Sans Typewriter", "Lucida Console", Monaco, "Bitstream Vera Sans Mono", monospace; } -.plugin { - margin-bottom: 20px; -} -.plugin table { + +.table { + display: table; width: 100%; } -.plugin table tr td:not(:first-child) { +.table-row-group { + display: table-row-group +} +.table-row { + display: table-row; +} +.table-cell { + display: table-cell; text-align: right; } -.underline{ +.plugin { + margin-bottom: 20px; +} +.plugin.table-row-group .table-row:last-child .table-cell { + padding-bottom: 20px; +} + +.underline { text-decoration: underline } -.bold{ +.bold { font-weight: bold; } -.sort{ +.sort { font-weight: bold; -} -.sort:after{ - content: '\25BC' + color: white; } .text-right { text-align: right; } +.text-left { + text-align: left; +} /* Theme */ @@ -77,25 +91,85 @@ body { } /* Plugins */ -#cpu table tr td:nth-child(3), -#mem table tr td:nth-child(3), -#monitor table tr td:nth-child(3) { - text-align: left; - padding-left: 20px; -} -#processlist table tr td { - text-align: right; -} -#processlist table tr td, -#docker table tr td { +#processlist .table-cell, #containers .table-cell { padding: 0px 5px 0px 5px; white-space: nowrap; } -#processlist table tr td:nth-child(6), -#processlist table tr td:nth-child(12) { - text-align: left; +gl-monitor-list { + display: block; } -#docker table tr td:nth-child(2), -#docker table tr td:nth-child(6) { +gl-monitor-list .table-cell { text-align: left; -}
\ No newline at end of file +} +/* Loading page */ + +#loading-page .glances-logo { + background: url('glances.png') no-repeat center center; + background-size: contain; +} + +@media (max-width: 750px) { + #loading-page .glances-logo { + height: 400px; + } +} +@media (min-width: 750px) { + #loading-page .glances-logo { + height: 500px; + } +} + + +/* +Loading animation +source : https://github.com/lukehaas/css-loaders +*/ +#loading-page .loader:before, +#loading-page .loader:after, +#loading-page .loader { + border-radius: 50%; + width: 1em; + height: 1em; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; + -webkit-animation: loader 1.8s infinite ease-in-out; + animation: loader 1.8s infinite ease-in-out; +} +#loading-page .loader { + margin: auto; + font-size: 10px; + position: relative; + text-indent: -9999em; + -webkit-animation-delay: 0.16s; + animation-delay: 0.16s; +} +#loading-page .loader:before { + left: -3.5em; +} +#loading-page .loader:after { + left: 3.5em; + -webkit-animation-delay: 0.32s; + animation-delay: 0.32s; +} +#loading-page .loader:before, +#loading-page .loader:after { + content: ''; + position: absolute; + top: 0; +} +@-webkit-keyframes loader { + 0%, 80%, 100% { + box-shadow: 0 2.5em 0 -1.3em #56CA69; + } + 40% { + box-shadow: 0 2.5em 0 0 #56CA69; + } +} +@keyframes loader { + 0%, 80%, 100% { + box-shadow: 0 2.5em 0 -1.3em #56CA69; + } + 40% { + box-shadow: 0 2.5em 0 0 #56CA69; + } +} diff --git a/glances/outputs/static/html/components/monitor_process.html b/glances/outputs/static/html/components/monitor_process.html new file mode 100644 index 00000000..ec883040 --- /dev/null +++ b/glances/outputs/static/html/components/monitor_process.html @@ -0,0 +1,4 @@ +<div class="table-cell {{ descriptionClass }}">{{ process.description }}</div> +<div class="table-cell">{{ process.count > 1 ? process.count : '' }}</div> +<div class="table-cell">{{ process.count > 0 ? 'RUNNING' : 'NOT RUNNING' }}</div> +<div class="table-cell">{{ process.result }}</div> diff --git a/glances/outputs/static/html/help.html b/glances/outputs/static/html/help.html new file mode 100644 index 00000000..b193d08e --- /dev/null +++ b/glances/outputs/static/html/help.html @@ -0,0 +1,63 @@ +<div class="row"> + <div class="col-sm-2 col-lg-10">{{help.version}} {{help.psutil_version}}</div> +</div> +<div class="row"> </div> +<div class="row"> + <div class="col-sm-2 col-lg-10">{{help.configuration_file}}</div> +</div> +<div class="row"> </div> +<div class="row"> + <div class="col-sm-2 col-lg-3">{{help.sort_auto}}</div> + <div class="col-sm-2 col-lg-3">{{help.sort_network}}</div> +</div> +<div class="row"> + <div class="col-sm-2 col-lg-3">{{help.sort_cpu}}</div> + <div class="col-sm-2 col-lg-3">{{help.show_hide_alert}}</div> +</div> +<div class="row"> + <div class="col-sm-2 col-lg-3">{{help.show_mem}}</div> + <div class="col-sm-2 col-lg-3">{{help.delete_warning_alerts}}</div> +</div> +<div class="row"> + <div class="col-sm-2 col-lg-3">{{help.sort_proc}}</div> + <div class="col-sm-2 col-lg-3">{{help.delete_warning_critical_alerts}}</div> +</div> +<div class="row"> + <div class="col-sm-2 col-lg-3">{{help.sort_io}}</div> + <div class="col-sm-2 col-lg-3">{{help.percpu}}</div> +</div> +<div class="row"> + <div class="col-sm-2 col-lg-3">{{help.show_hide_help}}</div> + <div class="col-sm-2 col-lg-3">{{help.show_hide_diskio}}</div> +</div> +<div class="row"> + <div class="col-sm-2 col-lg-3">{{help.view_network_io_combination}}</div> + <div class="col-sm-2 col-lg-3">{{help.view_cumulative_network}}</div> +</div> +<div class="row"> + <div class="col-sm-2 col-lg-3">{{help.show_hide_network}}</div> + <div class="col-sm-2 col-lg-3">{{help.show_hide_filesytem_freespace}}</div> +</div> +<div class="row"> + <div class="col-sm-2 col-lg-3">{{help.show_hide_sensors}}</div> + <div class="col-sm-2 col-lg-3">{{help.generate_graphs}}</div> +</div> +<div class="row"> + <div class="col-sm-2 col-lg-3">{{help.show_hide_left_sidebar}}</div> + <div class="col-sm-2 col-lg-3">{{help.reset_history}}</div> +</div> +<div class="row"> + <div class="col-sm-2 col-lg-3">{{help.enable_disable_process_stats}}</div> + <div class="col-sm-2 col-lg-3">{{help.quit}}</div> +</div> +<div class="row"> + <div class="col-sm-2 col-lg-3">{{help.enable_disable_top_extends_stats}}</div> + <div class="col-sm-2 col-lg-3">{{help.enable_disable_short_processname}}</div> +</div> +<div class="row"> + <div class="col-sm-2 col-lg-3">{{help.enable_disable_docker}}</div> +</div> +<div class="row"> </div> +<div class="row"> + <div class="col-sm-2 col-lg-3">{{help.edit_pattern_filter}}</div> +</div> diff --git a/glances/outputs/static/html/index.html b/glances/outputs/static/html/index.html new file mode 100644 index 00000000..f9b20a70 --- /dev/null +++ b/glances/outputs/static/html/index.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<html ng-app="glancesApp"> + +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>Glances</title> + + <link rel="icon" type="image/x-icon" href="favicon.ico" /> + <link rel="stylesheet" type="text/css" href="normalize.css" /> + <link rel="stylesheet" type="text/css" href="bootstrap.min.css" /> + <link rel="stylesheet" type="text/css" href="style.css" /> + + <script type="text/javascript" src="vendors/angular.min.js"></script> + <script type="text/javascript" src="vendors/angular-route.min.js"></script> + <script type="text/javascript" src="vendors/lodash.min.js"></script> + <script type="text/javascript" src="app.js"></script> + <script type="text/javascript" src="filters.js"></script> + <script type="text/javascript" src="variables.js"></script> + <script type="text/javascript" src="directives.js"></script> + + <script type="text/javascript" src="stats_controller.js"></script> +</head> + +<body ng-view="" ng-keydown="onKeyDown($event)"> + +</body> +</html> diff --git a/glances/outputs/static/html/plugins/alert.html b/glances/outputs/static/html/plugins/alert.html new file mode 100644 index 00000000..e8011851 --- /dev/null +++ b/glances/outputs/static/html/plugins/alert.html @@ -0,0 +1,7 @@ +<div class="table"> + <div class="table-row" ng-repeat="alert in result['alert']"> + <div class="table-cell text-left"> +{{alert.begin}} ({{(alert[1]-alert[0]) | date : 'h:mm:ss'}}) - {{alert[2]}} on {{alert[3]}} ({{alert[4]}}) + </div> + </div> +</div> diff --git a/glances/outputs/static/html/plugins/alerts.html b/glances/outputs/static/html/plugins/alerts.html new file mode 100644 index 00000000..a03db0fc --- /dev/null +++ b/glances/outputs/static/html/plugins/alerts.html @@ -0,0 +1,2 @@ +<span class="title" ng-show="!result['alert'].length">No warning or critical alert detected</span> +<span class="title" ng-show="result['alert'].length">Warning or critical alerts (lasts {{result['alert'].length}} entries)</span> diff --git a/glances/outputs/static/html/plugins/cpu.html b/glances/outputs/static/html/plugins/cpu.html new file mode 100644 index 00000000..3e78b419 --- /dev/null +++ b/glances/outputs/static/html/plugins/cpu.html @@ -0,0 +1,44 @@ +<div class="table" ng-show="!show.per_cpu"> + <div class="table-row"> + <div class="table-cell text-left title">CPU</div> + <div class="table-cell">{{result["cpu"].total}}%</div> + </div> + <div class="table-row"> + <div class="table-cell text-left">user:</div> + <div class="table-cell" ng-class="getClass('cpu', 'cpu_user_', result['cpu'].user, 1)"> + {{result["cpu"].user}}% |