diff options
Diffstat (limited to 'glances/outputs')
32 files changed, 43438 insertions, 681 deletions
diff --git a/glances/outputs/glances_bars.py b/glances/outputs/glances_bars.py index f6535368..6f8e7d21 100644 --- a/glances/outputs/glances_bars.py +++ b/glances/outputs/glances_bars.py @@ -45,6 +45,9 @@ class Bar(object): self.__size = size # Bar current percent self.__percent = 0 + # Min and max value + self.min_value = 0 + self.max_value = 100 # Char used for the decoration self.__pre_char = pre_char self.__post_char = post_char @@ -69,9 +72,10 @@ class Bar(object): @percent.setter def percent(self, value): - if value < 0 or value > 100: - raise AssertionError('The percent must be between 0 and 100.') - + if value <= self.min_value: + value = self.min_value + if value >= self.max_value: + value = self.max_value self.__percent = value @property diff --git a/glances/outputs/glances_bottle.py b/glances/outputs/glances_bottle.py index 94de21b3..053a1175 100644 --- a/glances/outputs/glances_bottle.py +++ b/glances/outputs/glances_bottle.py @@ -23,8 +23,9 @@ import json import os import sys import tempfile +from io import open -from glances.core.glances_logging import logger +from glances.logger import logger try: from bottle import Bottle, static_file, abort, response, request, auth_basic @@ -61,7 +62,7 @@ class GlancesBottle(object): def check_auth(self, username, password): """Check if a username/password combination is valid.""" if username == self.args.username: - from glances.core.glances_password import GlancesPassword + from glances.password import GlancesPassword pwd = GlancesPassword() return pwd.check_password(self.args.password, pwd.sha256_hash(password)) else: @@ -70,7 +71,7 @@ class GlancesBottle(object): def _route(self): """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('/<refresh_time:int>', method=["GET"], 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) @@ -81,6 +82,8 @@ class GlancesBottle(object): self._app.route('/favicon.ico', method="GET", callback=self._favicon) # REST API + self._app.route('/api/2/args', method="GET", callback=self._api_args) + self._app.route('/api/2/args/:item', method="GET", callback=self._api_args_item) 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) @@ -112,10 +115,6 @@ class GlancesBottle(object): def _index(self, refresh_time=None): """Bottle callback for index.html (/) file.""" - # Manage parameter - if refresh_time is None: - refresh_time = self.args.time - # Update the stat self.stats.update() @@ -215,8 +214,11 @@ class GlancesBottle(object): if self.args.debug: fname = os.path.join(tempfile.gettempdir(), 'glances-debug.json') - with open(fname) as f: - return f.read() + try: + with open(fname) as f: + return f.read() + except IOError: + logger.debug("Debug file (%s) not found" % fname) # Update the stat self.stats.update() @@ -228,7 +230,6 @@ class GlancesBottle(object): abort(404, "Cannot get stats (%s)" % str(e)) return statval - def _api_all_limits(self): """Glances API RESTFul implementation. @@ -379,6 +380,46 @@ class GlancesBottle(object): else: return pdict + def _api_args(self): + """Glances API RESTFul implementation. + + Return the JSON representation of the Glances command line arguments + HTTP/200 if OK + HTTP/404 if others error + """ + response.content_type = 'application/json' + + try: + # Get the JSON value of the args' dict + # Use vars to convert namespace to dict + # Source: https://docs.python.org/2/library/functions.html#vars + args_json = json.dumps(vars(self.args)) + except Exception as e: + abort(404, "Cannot get args (%s)" % str(e)) + return args_json + + def _api_args_item(self, item): + """Glances API RESTFul implementation. + + Return the JSON representation of the Glances command line arguments item + HTTP/200 if OK + HTTP/400 if item is not found + HTTP/404 if others error + """ + response.content_type = 'application/json' + + if item not in self.args: + abort(400, "Unknown item %s" % item) + + try: + # Get the JSON value of the args' dict + # Use vars to convert namespace to dict + # Source: https://docs.python.org/2/library/functions.html#vars + args_json = json.dumps(vars(self.args)[item]) + except Exception as e: + abort(404, "Cannot get args item (%s)" % str(e)) + return args_json + class EnableCors(object): name = 'enable_cors' diff --git a/glances/outputs/glances_colorconsole.py b/glances/outputs/glances_colorconsole.py index 9f581dc3..cf785112 100644 --- a/glances/outputs/glances_colorconsole.py +++ b/glances/outputs/glances_colorconsole.py @@ -23,7 +23,8 @@ import time import msvcrt -from glances.core.glances_logging import logger +from glances.compat import queue +from glances.logger import logger try: import colorconsole @@ -32,16 +33,11 @@ except ImportError: logger.critical("Colorconsole module not found. Glances cannot start in standalone mode.") sys.exit(1) -try: - import queue -except ImportError: # Python 2 - import Queue as queue - class ListenGetch(threading.Thread): def __init__(self, nom=''): - threading.Thread.__init__(self) + super(ListenGetch, self).__init__() self.Terminated = False self.q = queue.Queue() diff --git a/glances/outputs/glances_curses.py b/glances/outputs/glances_curses.py index 92bd839f..4d5934c8 100644 --- a/glances/outputs/glances_curses.py +++ b/glances/outputs/glances_curses.py @@ -19,19 +19,18 @@ """Curses interface class.""" -# Import system lib import re import sys -# Import Glances lib -from glances.core.glances_globals import is_mac, is_windows -from glances.core.glances_logging import logger -from glances.core.glances_logs import glances_logs -from glances.core.glances_processes import glances_processes -from glances.core.glances_timer import Timer +from glances.compat import u +from glances.globals import OSX, WINDOWS +from glances.logger import logger +from glances.logs import glances_logs +from glances.processes import glances_processes +from glances.timer import Timer # Import curses lib for "normal" operating system and consolelog for Windows -if not is_windows: +if not WINDOWS: try: import curses import curses.panel @@ -70,61 +69,118 @@ class _GlancesCurses(object): logger.critical("Cannot init the curses library.\n") sys.exit(1) - # Set curses options - if hasattr(curses, 'start_color'): - curses.start_color() - if hasattr(curses, 'use_default_colors'): - curses.use_default_colors() + # Init cursor + self._init_cursor() + + # Init the colors + self._init_colors() + + # Init main window + self.term_window = self.screen.subwin(0, 0) + + # Init refresh time + self.__refresh_time = args.time + + # Init edit filter tag + self.edit_filter = False + + # Init the process min/max reset + self.args.reset_minmax_tag = False + + # Catch key pressed with non blocking mode + self.no_flash_cursor() + self.term_window.nodelay(1) + self.pressedkey = -1 + + # History tag + self._init_history() + + def _init_history(self): + '''Init the history option''' + + self.reset_history_tag = False + self.history_tag = False + if self.args.enable_history: + logger.info('Stats history enabled with output path %s' % + self.args.path_history) + from glances.exports.glances_history import GlancesHistory + self.glances_history = GlancesHistory(self.args.path_history) + if not self.glances_history.graph_enabled(): + self.args.enable_history = False + logger.error( + 'Stats history disabled because MatPlotLib is not installed') + + def _init_cursor(self): + '''Init cursors''' + if hasattr(curses, 'noecho'): curses.noecho() if hasattr(curses, 'cbreak'): curses.cbreak() self.set_cursor(0) + def _init_colors(self): + '''Init the Curses color layout''' + + # Set curses options + if hasattr(curses, 'start_color'): + curses.start_color() + if hasattr(curses, 'use_default_colors'): + curses.use_default_colors() + # Init colors - self.hascolors = False - if curses.has_colors() and curses.COLOR_PAIRS > 8: - self.hascolors = True - # FG color, BG color - if args.theme_white: + if self.args.disable_bold: + A_BOLD = 0 + self.args.disable_bg = True + else: + A_BOLD = curses.A_BOLD + + self.title_color = A_BOLD + self.title_underline_color = A_BOLD | curses.A_UNDERLINE + self.help_color = A_BOLD + + if curses.has_colors(): + # The screen is compatible with a colored design + if self.args.theme_white: + # White theme: black ==> white curses.init_pair(1, curses.COLOR_BLACK, -1) else: curses.init_pair(1, curses.COLOR_WHITE, -1) - curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_RED) - curses.init_pair(3, curses.COLOR_WHITE, curses.COLOR_GREEN) - curses.init_pair(4, curses.COLOR_WHITE, curses.COLOR_BLUE) - curses.init_pair(5, curses.COLOR_WHITE, curses.COLOR_MAGENTA) + if self.args.disable_bg: + curses.init_pair(2, curses.COLOR_RED, -1) + curses.init_pair(3, curses.COLOR_GREEN, -1) + curses.init_pair(4, curses.COLOR_BLUE, -1) + curses.init_pair(5, curses.COLOR_MAGENTA, -1) + else: + curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_RED) + curses.init_pair(3, curses.COLOR_WHITE, curses.COLOR_GREEN) + curses.init_pair(4, curses.COLOR_WHITE, curses.COLOR_BLUE) + curses.init_pair(5, curses.COLOR_WHITE, curses.COLOR_MAGENTA) curses.init_pair(6, curses.COLOR_RED, -1) curses.init_pair(7, curses.COLOR_GREEN, -1) curses.init_pair(8, curses.COLOR_BLUE, -1) - try: - curses.init_pair(9, curses.COLOR_MAGENTA, -1) - except Exception: - if args.theme_white: - curses.init_pair(9, curses.COLOR_BLACK, -1) - else: - curses.init_pair(9, curses.COLOR_WHITE, -1) - try: - curses.init_pair(10, curses.COLOR_CYAN, -1) - except Exception: - if args.theme_white: - curses.init_pair(10, curses.COLOR_BLACK, -1) - else: - curses.init_pair(10, curses.COLOR_WHITE, -1) - else: - self.hascolors = False + # Colors text styles + if curses.COLOR_PAIRS > 8: + try: + curses.init_pair(9, curses.COLOR_MAGENTA, -1) + except Exception: + if self.args.theme_white: + curses.init_pair(9, curses.COLOR_BLACK, -1) + else: + curses.init_pair(9, curses.COLOR_WHITE, -1) + try: + curses.init_pair(10, curses.COLOR_CYAN, -1) + except Exception: + if self.args.theme_white: + curses.init_pair(10, curses.COLOR_BLACK, -1) + else: + curses.init_pair(10, curses.COLOR_WHITE, -1) - if args.disable_bold: - A_BOLD = curses.A_BOLD - else: - A_BOLD = 0 + self.ifWARNING_color2 = curses.color_pair(9) | A_BOLD + self.ifCRITICAL_color2 = curses.color_pair(6) | A_BOLD + self.filter_color = curses.color_pair(10) | A_BOLD - self.title_color = A_BOLD - self.title_underline_color = A_BOLD | curses.A_UNDERLINE - self.help_color = A_BOLD - if self.hascolors: - # Colors text styles self.no_color = curses.color_pair(1) self.default_color = curses.color_pair(3) | A_BOLD self.nice_color = curses.color_pair(9) | A_BOLD @@ -134,11 +190,10 @@ class _GlancesCurses(object): self.ifCRITICAL_color = curses.color_pair(2) | A_BOLD self.default_color2 = curses.color_pair(7) | A_BOLD self.ifCAREFUL_color2 = curses.color_pair(8) | A_BOLD - self.ifWARNING_color2 = curses.color_pair(9) | A_BOLD - self.ifCRITICAL_color2 = curses.color_pair(6) | A_BOLD - self.filter_color = curses.color_pair(10) | A_BOLD + else: - # B&W text styles + # The screen is NOT compatible with a colored design + # switch to B&W text styles self.no_color = curses.A_NORMAL self.default_color = curses.A_NORMAL self.nice_color = A_BOLD @@ -175,33 +230,6 @@ class _GlancesCurses(object): 'PASSWORD': curses.A_PROTECT } - # Init main window - self.term_window = self.screen.subwin(0, 0) - - # Init refresh time - self.__refresh_time = args.time - - # Init edit filter tag - self.edit_filter = False - - # Catch key pressed with non blocking mode - self.no_flash_cursor() - self.term_window.nodelay(1) - self.pressedkey = -1 - - # History tag - self.reset_history_tag = False - self.history_tag = False - if args.enable_history: - logger.info('Stats history enabled with output path %s' % - args.path_history) - from glances.exports.glances_history import GlancesHistory - self.glances_history = GlancesHistory(args.path_history) - if not self.glances_history.graph_enabled(): - args.enable_history = False - logger.error( - 'Stats history disabled because MatPlotLib is not installed') - def flash_cursor(self): self.term_window.keypad(1) @@ -277,6 +305,22 @@ class _GlancesCurses(object): self.args.disable_cpu = False self.args.disable_mem = False self.args.disable_swap = False + elif self.pressedkey == ord('5'): + # '5' > Enable/disable top menu + logger.info(self.args.disable_top) + self.args.disable_top = not self.args.disable_top + if self.args.disable_top: + self.args.disable_quicklook = True + self.args.disable_cpu = True + self.args.disable_mem = True + self.args.disable_swap = True + self.args.disable_load = True + else: + self.args.disable_quicklook = False + self.args.disable_cpu = False + self.args.disable_mem = False + self.args.disable_swap = False + self.args.disable_load = False elif self.pressedkey == ord('/'): # '/' > Switch between short/long name for processes self.args.process_short_name = not self.args.process_short_name @@ -286,8 +330,10 @@ class _GlancesCurses(object): glances_processes.sort_key = 'cpu_percent' elif self.pressedkey == ord('b'): # 'b' > Switch between bit/s and Byte/s for network IO - # self.net_byteps_tag = not self.net_byteps_tag self.args.byte = not self.args.byte + elif self.pressedkey == ord('B'): + # 'B' > Switch between bit/s and IO/s for Disk IO + self.args.diskio_iops = not self.args.diskio_iops elif self.pressedkey == ord('c'): # 'c' > Sort processes by CPU usage glances_processes.auto_sort = False @@ -305,12 +351,17 @@ class _GlancesCurses(object): glances_processes.disable_extended() else: glances_processes.enable_extended() + elif self.pressedkey == ord('E'): + # 'E' > Erase the process filter + logger.info("Erase process filter") + glances_processes.process_filter = None elif self.pressedkey == ord('F'): # 'F' > Switch between FS available and free space self.args.fs_free_space = not self.args.fs_free_space elif self.pressedkey == ord('f'): - # 'f' > Show/hide fs stats + # 'f' > Show/hide fs / folder stats self.args.disable_fs = not self.args.disable_fs + self.args.disable_folder = not self.args.disable_folder elif self.pressedkey == ord('g'): # 'g' > History self.history_tag = not self.history_tag @@ -331,6 +382,9 @@ class _GlancesCurses(object): # 'm' > Sort processes by MEM usage glances_processes.auto_sort = False glances_processes.sort_key = 'memory_percent' + elif self.pressedkey == ord('M'): + # 'M' > Reset processes summary min/max + self.args.reset_minmax_tag = not self.args.reset_minmax_tag elif self.pressedkey == ord('n'): # 'n' > Show/hide network stats self.args.disable_network = not self.args.disable_network @@ -468,6 +522,8 @@ class _GlancesCurses(object): 'diskio').get_stats_display(args=self.args) stats_fs = stats.get_plugin('fs').get_stats_display( args=self.args, max_width=plugin_max_width) + stats_folders = stats.get_plugin('folders').get_stats_display( + args=self.args, max_width=plugin_max_width) stats_raid = stats.get_plugin('raid').get_stats_display( args=self.args) stats_sensors = stats.get_plugin( @@ -631,8 +687,11 @@ class _GlancesCurses(object): # Display left sidebar (NETWORK+DISKIO+FS+SENSORS+Current time) # ================================================================== self.init_column() - if not (self.args.disable_network and self.args.disable_diskio and - self.args.disable_fs and self.args.disable_raid and + if not (self.args.disable_network and + self.args.disable_diskio and + self.args.disable_fs and + self.args.disable_folder and + self.args.disable_raid and self.args.disable_sensors) and not self.args.disable_left_sidebar: self.new_line() self.display_plugin(stats_network) @@ -641,6 +700,8 @@ class _GlancesCurses(object): self.new_line() self.display_plugin(stats_fs) self.new_line() + self.display_plugin(stats_folders) + self.new_line() self.display_plugin(stats_raid) self.new_line() self.display_plugin(stats_sensors) @@ -669,7 +730,7 @@ class _GlancesCurses(object): self.new_line() self.display_plugin(stats_processlist, display_optional=(screen_x > 102), - display_additional=(not is_mac), + display_additional=(not OSX), max_y=(screen_y - self.get_stats_display_height(stats_alert) - 2)) self.new_line() self.display_plugin(stats_alert) @@ -758,7 +819,7 @@ class _GlancesCurses(object): for y, m in enumerate(message.split('\n')): popup.addnstr(2 + y, 2, m, len(m)) - if is_input and not is_windows: + if is_input and not WINDOWS: # Create a subwindow for the text field subpop = popup.derwin(1, input_size, 2, 2 + len(m)) subpop.attron(self.colors_list['FILTER']) @@ -858,15 +919,15 @@ class _GlancesCurses(object): pass else: # New column + # Python 2: we need to decode to get real screen size because + # UTF-8 special tree chars occupy several bytes. + # Python 3: strings are strings and bytes are bytes, all is + # good. try: - # Python 2: we need to decode to get real screen size because utf-8 special tree chars - # occupy several bytes - offset = len(m['msg'].decode("utf-8", "replace")) - except AttributeError: - # Python 3: strings are strings and bytes are bytes, all is - # good - offset = len(m['msg']) - x += offset + x += len(u(m['msg'])) + except UnicodeDecodeError: + # Quick and dirty hack for issue #745 + pass if x > x_max: x_max = x @@ -981,7 +1042,7 @@ class GlancesCursesBrowser(_GlancesCurses): def __init__(self, args=None): # Init the father class - _GlancesCurses.__init__(self, args=args) + super(GlancesCursesBrowser, self).__init__(args=args) _colors_list = { 'UNKNOWN': self.no_color, @@ -1217,16 +1278,15 @@ class GlancesCursesBrowser(_GlancesCurses): return True +if not WINDOWS: + class GlancesTextbox(Textbox, object): -if not is_windows: - class GlancesTextbox(Textbox): - - def __init__(*args, **kwargs): - Textbox.__init__(*args, **kwargs) + def __init__(self, *args, **kwargs): + super(GlancesTextbox, self).__init__(*args, **kwargs) def do_command(self, ch): if ch == 10: # Enter return 0 if ch == 127: # Back return 8 - return Textbox.do_command(self, ch) + return super(GlancesTextbox, self).do_command(ch) diff --git a/glances/outputs/static/css/style.css b/glances/outputs/static/css/style.css index 084a5dd0..cf253470 100644 --- a/glances/outputs/static/css/style.css +++ b/glances/outputs/static/css/style.css @@ -36,6 +36,9 @@ body { font-weight: bold; color: white; } +.sortable { + cursor: pointer; +} .text-right { text-align: right; } diff --git a/glances/outputs/static/html/help.html b/glances/outputs/static/html/help.html index 5919ba16..814b2a76 100644 --- a/glances/outputs/static/html/help.html +++ b/glances/outputs/static/html/help.html @@ -20,35 +20,35 @@ </div> <div class="row"> <div class="col-sm-12 col-lg-6">{{help.sort_user}}</div> - <div class="col-sm-12 col-lg-6">{{help.enable_disable_docker}}</div> + <div class="col-sm-12 col-lg-6">{{help.show_hide_ip}}</div> </div> <div class="row"> <div class="col-sm-12 col-lg-6">{{help.sort_proc}}</div> - <div class="col-sm-12 col-lg-6">{{help.view_network_io_combination}}</div> + <div class="col-sm-12 col-lg-6">{{help.enable_disable_docker}}</div> </div> <div class="row"> <div class="col-sm-12 col-lg-6">{{help.sort_io}}</div> - <div class="col-sm-12 col-lg-6">{{help.view_cumulative_network}}</div> + <div class="col-sm-12 col-lg-6">{{help.view_network_io_combination}}</div> </div> <div class="row"> <div class="col-sm-12 col-lg-6">{{help.sort_cpu_times}}</div> - <div class="col-sm-12 col-lg-6">{{help.show_hide_filesytem_freespace}}</div> + <div class="col-sm-12 col-lg-6">{{help.view_cumulative_network}}</div> </div> <div class="row"> <div class="col-sm-12 col-lg-6">{{help.show_hide_diskio}}</div> - <div class="col-sm-12 col-lg-6">{{help.show_hide_help}}</div> + <div class="col-sm-12 col-lg-6">{{help.show_hide_filesytem_freespace}}</div> </div> <div class="row"> <div class="col-sm-12 col-lg-6">{{help.show_hide_filesystem}}</div> - <div class="col-sm-12 col-lg-6"></div> + <div class="col-sm-12 col-lg-6">{{help.show_hide_help}}</div> </div> <div class="row"> <div class="col-sm-12 col-lg-6">{{help.show_hide_network}}</div> - <div class="col-sm-12 col-lg-6"></div> + <div class="col-sm-12 col-lg-6">{{help.diskio_iops}}</div> </div> <div class="row"> <div class="col-sm-12 col-lg-6">{{help.show_hide_sensors}}</div> - <div class="col-sm-12 col-lg-6"></div> + <div class="col-sm-12 col-lg-6">{{help.show_hide_top_menu}}</div> </div> <div class="row"> <div class="col-sm-12 col-lg-6">{{help.show_hide_left_sidebar}}</div> diff --git a/glances/outputs/static/html/index.html b/glances/outputs/static/html/index.html index 46493c8c..fb90052f 100644 --- a/glances/outputs/static/html/index.html +++ b/glances/outputs/static/html/index.html @@ -4,16 +4,17 @@ <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> + <title ng-bind="title">Glances</title> + <base href="/"> <link rel="icon" type="image/x-icon" href="favicon.ico" /> |