glances_curses.py 47.5 KB
Newer Older
1 2
# -*- coding: utf-8 -*-
#
3
# This file is part of Glances.
4
#
5
# Copyright (C) 2015 Nicolargo <nicolas@nicolargo.com>
6 7 8 9 10 11 12 13 14 15 16 17 18 19
#
# Glances is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Glances 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 Lesser General Public License for more details.
#
# 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/>.

A
PEP 257  
Alessio Sergi 已提交
20 21
"""Curses interface class."""

N
Nicolargo 已提交
22
import re
A
flake8  
Alessio Sergi 已提交
23
import sys
24

25
from glances.compat import u
A
Alessio Sergi 已提交
26
from glances.globals import OSX, WINDOWS
27 28 29 30
from glances.logger import logger
from glances.logs import glances_logs
from glances.processes import glances_processes
from glances.timer import Timer
31 32

# Import curses lib for "normal" operating system and consolelog for Windows
A
Alessio Sergi 已提交
33
if not WINDOWS:
34 35 36
    try:
        import curses
        import curses.panel
37
        from curses.textpad import Textbox
38
    except ImportError:
N
Nicolargo 已提交
39 40
        logger.critical(
            "Curses module not found. Glances cannot start in standalone mode.")
41 42
        sys.exit(1)
else:
43 44
    from glances.outputs.glances_colorconsole import WCurseLight
    curses = WCurseLight()
45 46


47
class _GlancesCurses(object):
48

A
PEP 257  
Alessio Sergi 已提交
49 50 51
    """This class manages the curses display (and key pressed).

    Note: It is a private class, use GlancesCursesClient or GlancesCursesBrowser.
52
    """
53

A
PEP 257  
Alessio Sergi 已提交
54
    def __init__(self, args=None):
55 56
        # Init args
        self.args = args
N
Nicolas Hennion 已提交
57

58 59 60 61 62 63 64 65 66 67 68
        # Init windows positions
        self.term_w = 80
        self.term_h = 24

        # Space between stats
        self.space_between_column = 3
        self.space_between_line = 2

        # Init the curses screen
        self.screen = curses.initscr()
        if not self.screen:
A
Alessio Sergi 已提交
69
            logger.critical("Cannot init the curses library.\n")
N
Nicolas Hennion 已提交
70
            sys.exit(1)
71

72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
        # 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

87 88 89
        # Init the process min/max reset
        self.args.reset_minmax_tag = False

90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
        # 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'''

116 117 118 119
        if hasattr(curses, 'noecho'):
            curses.noecho()
        if hasattr(curses, 'cbreak'):
            curses.cbreak()
N
Nicolargo 已提交
120
        self.set_cursor(0)
121

122 123 124 125 126 127 128 129 130
    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()

131
        # Init colors
132 133 134 135 136 137 138 139 140 141 142 143 144
        if self.args.disable_bold:
            A_BOLD = curses.A_BOLD
        else:
            A_BOLD = 0

        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
N
Nicolargo 已提交
145 146 147
                curses.init_pair(1, curses.COLOR_BLACK, -1)
            else:
                curses.init_pair(1, curses.COLOR_WHITE, -1)
148
            if self.args.disable_bg:
149 150 151 152 153 154 155 156 157
                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)
            else:
                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)
158 159 160 161 162
            curses.init_pair(6, curses.COLOR_RED, -1)
            curses.init_pair(7, curses.COLOR_GREEN, -1)
            curses.init_pair(8, curses.COLOR_BLUE, -1)

            # Colors text styles
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
            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)

                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

183
            self.no_color = curses.color_pair(1)
A
Alessio Sergi 已提交
184
            self.default_color = curses.color_pair(3) | A_BOLD
185
            self.nice_color = curses.color_pair(9) | A_BOLD
186
            self.cpu_time_color = curses.color_pair(9) | A_BOLD
187 188 189 190 191
            self.ifCAREFUL_color = curses.color_pair(4) | A_BOLD
            self.ifWARNING_color = curses.color_pair(5) | A_BOLD
            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
192

193
        else:
194 195
            # The screen is NOT compatible with a colored design
            # switch to B&W text styles
196 197
            self.no_color = curses.A_NORMAL
            self.default_color = curses.A_NORMAL
198
            self.nice_color = A_BOLD
199
            self.cpu_time_color = A_BOLD
200 201 202 203 204 205 206
            self.ifCAREFUL_color = curses.A_UNDERLINE
            self.ifWARNING_color = A_BOLD
            self.ifCRITICAL_color = curses.A_REVERSE
            self.default_color2 = curses.A_NORMAL
            self.ifCAREFUL_color2 = curses.A_UNDERLINE
            self.ifWARNING_color2 = A_BOLD
            self.ifCRITICAL_color2 = curses.A_REVERSE
N
Nicolargo 已提交
207
            self.filter_color = A_BOLD
208 209

        # Define the colors list (hash table) for stats
210
        self.colors_list = {
211 212 213
            'DEFAULT': self.no_color,
            'UNDERLINE': curses.A_UNDERLINE,
            'BOLD': A_BOLD,
214
            'SORT': A_BOLD,
215
            'OK': self.default_color2,
N
Nicolargo 已提交
216
            'FILTER': self.filter_color,
217
            'TITLE': self.title_color,
218 219
            'PROCESS': self.default_color2,
            'STATUS': self.default_color2,
220
            'NICE': self.nice_color,
221
            'CPU_TIME': self.cpu_time_color,
222 223 224 225 226 227
            'CAREFUL': self.ifCAREFUL_color2,
            'WARNING': self.ifWARNING_color2,
            'CRITICAL': self.ifCRITICAL_color2,
            'OK_LOG': self.default_color,
            'CAREFUL_LOG': self.ifCAREFUL_color,
            'WARNING_LOG': self.ifWARNING_color,
228 229
            'CRITICAL_LOG': self.ifCRITICAL_color,
            'PASSWORD': curses.A_PROTECT
230 231
        }

232 233 234 235 236 237
    def flash_cursor(self):
        self.term_window.keypad(1)

    def no_flash_cursor(self):
        self.term_window.keypad(0)

N
Nicolargo 已提交
238
    def set_cursor(self, value):
A
PEP 257  
Alessio Sergi 已提交
239 240 241 242 243 244
        """Configure the curse cursor apparence.

        0: invisible
        1: visible
        2: very visible
        """
N
Nicolargo 已提交
245 246 247 248 249 250
        if hasattr(curses, 'curs_set'):
            try:
                curses.curs_set(value)
            except Exception:
                pass

251
    def get_key(self, window):
A
PEP 257  
Alessio Sergi 已提交
252
        # Catch ESC key AND numlock key (issue #163)
253 254 255 256
        keycode = [0, 0]
        keycode[0] = window.getch()
        keycode[1] = window.getch()

N
Nicolargo 已提交
257
        if keycode != [-1, -1]:
N
Nicolargo 已提交
258
            logger.debug("Keypressed (code: %s)" % keycode)
N
Nicolargo 已提交
259

260 261 262 263 264 265
        if keycode[0] == 27 and keycode[1] != -1:
            # Do not escape on specials keys
            return -1
        else:
            return keycode[0]

N
Nicolargo 已提交
266
    def __catch_key(self, return_to_browser=False):
A
PEP 257  
Alessio Sergi 已提交
267
        # Catch the pressed key
268
        self.pressedkey = self.get_key(self.term_window)
269 270 271 272

        # Actions...
        if self.pressedkey == ord('\x1b') or self.pressedkey == ord('q'):
            # 'ESC'|'q' > Quit
N
Nicolargo 已提交
273 274 275 276 277 278
            if return_to_browser:
                logger.info("Stop Glances client and return to the browser")
            else:
                self.end()
                logger.info("Stop Glances")
                sys.exit(0)
N
Nicolargo 已提交
279 280 281
        elif self.pressedkey == 10:
            # 'ENTER' > Edit the process filter
            self.edit_filter = not self.edit_filter
282 283 284
        elif self.pressedkey == ord('0'):
            # '0' > Switch between IRIX and Solaris mode
            self.args.disable_irix = not self.args.disable_irix
285 286 287
        elif self.pressedkey == ord('1'):
            # '1' > Switch between CPU and PerCPU information
            self.args.percpu = not self.args.percpu
288 289
        elif self.pressedkey == ord('2'):
            # '2' > Enable/disable left sidebar
290
            self.args.disable_left_sidebar = not self.args.disable_left_sidebar
N
nicolargo 已提交
291 292 293
        elif self.pressedkey == ord('3'):
            # '3' > Enable/disable quicklook
            self.args.disable_quicklook = not self.args.disable_quicklook
N
Nicolargo 已提交
294 295 296 297 298 299 300 301 302 303 304 305 306
        elif self.pressedkey == ord('4'):
            # '4' > Enable/disable all but quick look and load
            self.args.full_quicklook = not self.args.full_quicklook
            if self.args.full_quicklook:
                self.args.disable_quicklook = False
                self.args.disable_cpu = True
                self.args.disable_mem = True
                self.args.disable_swap = True
            else:
                self.args.disable_quicklook = False
                self.args.disable_cpu = False
                self.args.disable_mem = False
                self.args.disable_swap = False
307 308 309
        elif self.pressedkey == ord('/'):
            # '/' > Switch between short/long name for processes
            self.args.process_short_name = not self.args.process_short_name
310
        elif self.pressedkey == ord('a'):
311 312 313
            # 'a' > Sort processes automatically and reset to 'cpu_percent'
            glances_processes.auto_sort = True
            glances_processes.sort_key = 'cpu_percent'
314 315 316 317 318 319
        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('c'):
            # 'c' > Sort processes by CPU usage
320 321
            glances_processes.auto_sort = False
            glances_processes.sort_key = 'cpu_percent'
322
        elif self.pressedkey == ord('d'):
323
            # 'd' > Show/hide disk I/O stats
324
            self.args.disable_diskio = not self.args.disable_diskio
N
Nicolargo 已提交
325 326 327
        elif self.pressedkey == ord('D'):
            # 'D' > Show/hide Docker stats
            self.args.disable_docker = not self.args.disable_docker
N
Nicolargo 已提交
328 329
        elif self.pressedkey == ord('e'):
            # 'e' > Enable/Disable extended stats for top process
330 331
            self.args.enable_process_extended = not self.args.enable_process_extended
            if not self.args.enable_process_extended:
N
Nicolargo 已提交
332 333 334
                glances_processes.disable_extended()
            else:
                glances_processes.enable_extended()
335 336 337 338
        elif self.pressedkey == ord('E'):
            # 'E' > Erase the process filter
            logger.info("Erase process filter")
            glances_processes.process_filter = None
339 340 341
        elif self.pressedkey == ord('F'):
            # 'F' > Switch between FS available and free space
            self.args.fs_free_space = not self.args.fs_free_space
342
        elif self.pressedkey == ord('f'):
N
nicolargo 已提交
343
            # 'f' > Show/hide fs / folder stats
344
            self.args.disable_fs = not self.args.disable_fs
N
nicolargo 已提交
345
            self.args.disable_folder = not self.args.disable_folder
346 347 348
        elif self.pressedkey == ord('g'):
            # 'g' > History
            self.history_tag = not self.history_tag
349 350
        elif self.pressedkey == ord('h'):
            # 'h' > Show/hide help
351
            self.args.help_tag = not self.args.help_tag
352
        elif self.pressedkey == ord('i'):
353
            # 'i' > Sort processes by IO rate (not available on OS X)
354 355
            glances_processes.auto_sort = False
            glances_processes.sort_key = 'io_counters'
356 357 358
        elif self.pressedkey == ord('I'):
            # 'I' > Show/hide IP module
            self.args.disable_ip = not self.args.disable_ip
359 360
        elif self.pressedkey == ord('l'):
            # 'l' > Show/hide log messages
361
            self.args.disable_log = not self.args.disable_log
362 363
        elif self.pressedkey == ord('m'):
            # 'm' > Sort processes by MEM usage
364 365
            glances_processes.auto_sort = False
            glances_processes.sort_key = 'memory_percent'
366 367 368
        elif self.pressedkey == ord('M'):
            # 'M' > Reset processes summary min/max
            self.args.reset_minmax_tag = not self.args.reset_minmax_tag
369
        elif self.pressedkey == ord('n'):
370
            # 'n' > Show/hide network stats
371
            self.args.disable_network = not self.args.disable_network
372 373
        elif self.pressedkey == ord('p'):
            # 'p' > Sort processes by name
374 375
            glances_processes.auto_sort = False
            glances_processes.sort_key = 'name'
376 377
        elif self.pressedkey == ord('r'):
            # 'r' > Reset history
378
            self.reset_history_tag = not self.reset_history_tag
N
Nicolargo 已提交
379 380 381
        elif self.pressedkey == ord('R'):
            # 'R' > Hide RAID plugins
            self.args.disable_raid = not self.args.disable_raid
382 383
        elif self.pressedkey == ord('s'):
            # 's' > Show/hide sensors stats (Linux-only)
384
            self.args.disable_sensors = not self.args.disable_sensors
385
        elif self.pressedkey == ord('t'):
386
            # 't' > Sort processes by TIME usage
387 388
            glances_processes.auto_sort = False
            glances_processes.sort_key = 'cpu_times'
389 390
        elif self.pressedkey == ord('T'):
            # 'T' > View network traffic as sum Rx+Tx
391
            self.args.network_sum = not self.args.network_sum
392
        elif self.pressedkey == ord('u'):
A
Alessio Sergi 已提交
393 394 395 396 397
            # 'u' > Sort processes by USER
            glances_processes.auto_sort = False
            glances_processes.sort_key = 'username'
        elif self.pressedkey == ord('U'):
            # 'U' > View cumulative network I/O (instead of bitrate)
398
            self.args.network_cumul = not self.args.network_cumul
399 400 401 402 403 404
        elif self.pressedkey == ord('w'):
            # 'w' > Delete finished warning logs
            glances_logs.clean()
        elif self.pressedkey == ord('x'):
            # 'x' > Delete finished warning and critical logs
            glances_logs.clean(critical=True)
405 406 407 408 409
        elif self.pressedkey == ord('z'):
            # 'z' > Enable/Disable processes stats (count + list + monitor)
            # Enable/Disable display
            self.args.disable_process = not self.args.disable_process
            # Enable/Disable update
410
            if self.args.disable_process:
411 412 413
                glances_processes.disable()
            else:
                glances_processes.enable()
414 415 416 417
        # Return the key code
        return self.pressedkey

    def end(self):
418
        """Shutdown the curses window."""
N
Nicolas Hennion 已提交
419 420 421 422 423 424 425 426 427
        if hasattr(curses, 'echo'):
            curses.echo()
        if hasattr(curses, 'nocbreak'):
            curses.nocbreak()
        if hasattr(curses, 'curs_set'):
            try:
                curses.curs_set(1)
            except Exception:
                pass
428
        curses.endwin()
429

430
    def init_line_column(self):
A
PEP 257  
Alessio Sergi 已提交
431
        """Init the line and column position for the curses inteface."""
432 433
        self.init_line()
        self.init_column()
434 435

    def init_line(self):
A
PEP 257  
Alessio Sergi 已提交
436
        """Init the line position for the curses inteface."""
437 438 439 440
        self.line = 0
        self.next_line = 0

    def init_column(self):
A
PEP 257  
Alessio Sergi 已提交
441
        """Init the column position for the curses inteface."""
442 443 444 445
        self.column = 0
        self.next_column = 0

    def new_line(self):
A
PEP 257  
Alessio Sergi 已提交
446
        """New line in the curses interface."""
447 448 449
        self.line = self.next_line

    def new_column(self):
A
PEP 257  
Alessio Sergi 已提交
450
        """New column in the curses interface."""
451 452
        self.column = self.next_column

453
    def display(self, stats, cs_status=None):
A
PEP 257  
Alessio Sergi 已提交
454
        """Display stats on the screen.
455

456 457 458
        stats: Stats database to display
        cs_status:
            "None": standalone or server mode
459 460
            "Connected": Client is connected to a Glances server
            "SNMP": Client is connected to a SNMP server
461
            "Disconnected": Client is disconnected from the server
462 463 464 465

        Return:
            True if the stats have been displayed
            False if the help have been displayed
466
        """
467 468
        # Init the internal line/column for Glances Curses
        self.init_line_column()
469 470 471 472 473

        # Get the screen size
        screen_x = self.screen.getmaxyx()[1]
        screen_y = self.screen.getmaxyx()[0]

474 475 476 477 478 479 480 481 482 483 484 485
        # No processes list in SNMP mode
        if cs_status == 'SNMP':
            # so... more space for others plugins
            plugin_max_width = 43
        else:
            plugin_max_width = None

        # Update the stats messages
        ###########################

        # Update the client server status
        self.args.cs_status = cs_status
486 487
        stats_system = stats.get_plugin(
            'system').get_stats_display(args=self.args)
488 489
        stats_uptime = stats.get_plugin('uptime').get_stats_display()
        if self.args.percpu:
490
            stats_cpu = stats.get_plugin('percpu').get_stats_display(args=self.args)
491
        else:
492 493 494 495
            stats_cpu = stats.get_plugin('cpu').get_stats_display(args=self.args)
        stats_load = stats.get_plugin('load').get_stats_display(args=self.args)
        stats_mem = stats.get_plugin('mem').get_stats_display(args=self.args)
        stats_memswap = stats.get_plugin('memswap').get_stats_display(args=self.args)
496 497
        stats_network = stats.get_plugin('network').get_stats_display(
            args=self.args, max_width=plugin_max_width)
498 499 500 501
        try:
            stats_ip = stats.get_plugin('ip').get_stats_display(args=self.args)
        except AttributeError:
            stats_ip = None
502 503 504 505
        stats_diskio = stats.get_plugin(
            'diskio').get_stats_display(args=self.args)
        stats_fs = stats.get_plugin('fs').get_stats_display(
            args=self.args, max_width=plugin_max_width)
506
        stats_folders = stats.get_plugin('folders').get_stats_display(
N
nicolargo 已提交
507
            args=self.args, max_width=plugin_max_width)
N
Nicolargo 已提交
508
        stats_raid = stats.get_plugin('raid').get_stats_display(
509
            args=self.args)
510 511
        stats_sensors = stats.get_plugin(
            'sensors').get_stats_display(args=self.args)
512
        stats_now = stats.get_plugin('now').get_stats_display()
N
Nicolargo 已提交
513 514
        stats_docker = stats.get_plugin('docker').get_stats_display(
            args=self.args)
515 516 517 518 519 520
        stats_processcount = stats.get_plugin(
            'processcount').get_stats_display(args=self.args)
        stats_monitor = stats.get_plugin(
            'monitor').get_stats_display(args=self.args)
        stats_alert = stats.get_plugin(
            'alert').get_stats_display(args=self.args)
521

522
        # Adapt number of processes to the available space
523
        max_processes_displayed = screen_y - 11 - \
N
Nicolargo 已提交
524 525
            self.get_stats_display_height(stats_alert) - \
            self.get_stats_display_height(stats_docker)
526 527 528 529 530
        try:
            if self.args.enable_process_extended and not self.args.process_tree:
                max_processes_displayed -= 4
        except AttributeError:
            pass
531
        if max_processes_displayed < 0:
532
            max_processes_displayed = 0
533 534 535 536
        if (glances_processes.max_processes is None or
                glances_processes.max_processes != max_processes_displayed):
            logger.debug("Set number of displayed processes to {0}".format(max_processes_displayed))
            glances_processes.max_processes = max_processes_displayed
537

538 539
        stats_processlist = stats.get_plugin(
            'processlist').get_stats_display(args=self.args)
540

541 542 543 544
        # Display the stats on the curses interface
        ###########################################

        # Help screen (on top of the other stats)
545
        if self.args.help_tag:
546
            # Display the stats...
547 548
            self.display_plugin(
                stats.get_plugin('help').get_stats_display(args=self.args))
549 550 551
            # ... and exit
            return False

N
Nicolargo 已提交
552
        # ==================================
553
        # Display first line (system+uptime)
N
Nicolargo 已提交
554
        # ==================================
555 556
        # Space between column
        self.space_between_column = 0
557
        self.new_line()
558
        l_uptime = self.get_stats_display_width(
N
Nicolargo 已提交
559
            stats_system) + self.space_between_column + self.get_stats_display_width(stats_ip) + 3 + self.get_stats_display_width(stats_uptime)
560 561 562 563 564 565
        self.display_plugin(
            stats_system, display_optional=(screen_x >= l_uptime))
        self.new_column()
        self.display_plugin(stats_ip)
        # Space between column
        self.space_between_column = 3
566
        self.new_column()
567
        self.display_plugin(stats_uptime)
A
Alessio Sergi 已提交
568

N
Nicolargo 已提交
569
        # ========================================================
N
Nicolargo 已提交
570
        # Display second line (<SUMMARY>+CPU|PERCPU+LOAD+MEM+SWAP)
N
Nicolargo 已提交
571
        # ========================================================
572
        self.init_column()
573
        self.new_line()
574

N
Nicolargo 已提交
575 576
        # Init quicklook
        stats_quicklook = {'msgdict': []}
577 578 579 580 581
        quicklook_width = 0

        # Get stats for CPU, MEM, SWAP and LOAD (if needed)
        if self.args.disable_cpu:
            cpu_width = 0
582
        else:
583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600
            cpu_width = self.get_stats_display_width(stats_cpu)
        if self.args.disable_mem:
            mem_width = 0
        else:
            mem_width = self.get_stats_display_width(stats_mem)
        if self.args.disable_swap:
            swap_width = 0
        else:
            swap_width = self.get_stats_display_width(stats_memswap)
        if self.args.disable_load:
            load_width = 0
        else:
            load_width = self.get_stats_display_width(stats_load)

        # Size of plugins but quicklook
        stats_width = cpu_width + mem_width + swap_width + load_width

        # Number of plugin but quicklook
601 602 603 604 605
        stats_number = (
            int(not self.args.disable_cpu and stats_cpu['msgdict'] != []) +
            int(not self.args.disable_mem and stats_mem['msgdict'] != []) +
            int(not self.args.disable_swap and stats_memswap['msgdict'] != []) +
            int(not self.args.disable_load and stats_load['msgdict'] != []))
606 607 608

        if not self.args.disable_quicklook:
            # Quick look is in the place !
609 610 611 612
            if self.args.full_quicklook:
                quicklook_width = screen_x - (stats_width + 8 + stats_number * self.space_between_column)
            else:
                quicklook_width = min(screen_x - (stats_width + 8 + stats_number * self.space_between_column), 79)
N
Nicolargo 已提交
613 614 615 616 617 618
            try:
                stats_quicklook = stats.get_plugin(
                    'quicklook').get_stats_display(max_width=quicklook_width, args=self.args)
            except AttributeError as e:
                logger.debug("Quicklook plugin not available (%s)" % e)
            else:
619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652
                quicklook_width = self.get_stats_display_width(stats_quicklook)
                stats_width += quicklook_width + 1
            self.space_between_column = 1
            self.display_plugin(stats_quicklook)
            self.new_column()

        # Compute spaces between plugins
        # Note: Only one space between Quicklook and others
        display_optional_cpu = True
        display_optional_mem = True
        if stats_number > 1:
            self.space_between_column = max(1, int((screen_x - stats_width) / (stats_number - 1)))
            # No space ? Remove optionnal MEM stats
            if self.space_between_column < 3:
                display_optional_mem = False
                if self.args.disable_mem:
                    mem_width = 0
                else:
                    mem_width = self.get_stats_display_width(stats_mem, without_option=True)
                stats_width = quicklook_width + 1 + cpu_width + mem_width + swap_width + load_width
                self.space_between_column = max(1, int((screen_x - stats_width) / (stats_number - 1)))
            # No space again ? Remove optionnal CPU stats
            if self.space_between_column < 3:
                display_optional_cpu = False
                if self.args.disable_cpu:
                    cpu_width = 0
                else:
                    cpu_width = self.get_stats_display_width(stats_cpu, without_option=True)
                stats_width = quicklook_width + 1 + cpu_width + mem_width + swap_width + load_width
                self.space_between_column = max(1, int((screen_x - stats_width) / (stats_number - 1)))
        else:
            self.space_between_column = 0

        # Display CPU, MEM, SWAP and LOAD
653
        self.display_plugin(stats_cpu, display_optional=display_optional_cpu)
654
        self.new_column()
655
        self.display_plugin(stats_mem, display_optional=display_optional_mem)
656
        self.new_column()
657
        self.display_plugin(stats_memswap)
N
Nicolargo 已提交
658 659
        self.new_column()
        self.display_plugin(stats_load)
660

661 662 663
        # Space between column
        self.space_between_column = 3

664 665 666
        # Backup line position
        self.saved_line = self.next_line

667
        # ==================================================================
668
        # Display left sidebar (NETWORK+DISKIO+FS+SENSORS+Current time)
669
        # ==================================================================
670
        self.init_column()
N
nicolargo 已提交
671 672 673 674 675
        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
A
flake8  
Alessio Sergi 已提交
676
                self.args.disable_sensors) and not self.args.disable_left_sidebar:
677 678 679 680 681 682 683
            self.new_line()
            self.display_plugin(stats_network)
            self.new_line()
            self.display_plugin(stats_diskio)
            self.new_line()
            self.display_plugin(stats_fs)
            self.new_line()
684
            self.display_plugin(stats_folders)
N
nicolargo 已提交
685
            self.new_line()
N
Nicolargo 已提交
686 687
            self.display_plugin(stats_raid)
            self.new_line()
688 689 690
            self.display_plugin(stats_sensors)
            self.new_line()
            self.display_plugin(stats_now)
691

N
Nicolargo 已提交
692 693 694
        # ====================================
        # Display right stats (process and co)
        # ====================================
695
        # If space available...
696
        if screen_x > 52:
697 698 699
            # Restore line position
            self.next_line = self.saved_line

700
            # Display right sidebar
N
Nicolargo 已提交
701
            # ((DOCKER)+PROCESS_COUNT+(MONITORED)+PROCESS_LIST+ALERT)
702 703
            self.new_column()
            self.new_line()
N
Nicolargo 已提交
704 705
            self.display_plugin(stats_docker)
            self.new_line()
706
            self.display_plugin(stats_processcount)
707
            if glances_processes.process_filter is None and cs_status is None:
N
Nicolargo 已提交
708 709 710
                # Do not display stats monitor list if a filter exist
                self.new_line()
                self.display_plugin(stats_monitor)
711
            self.new_line()
A
Alessio Sergi 已提交
712
            self.display_plugin(stats_processlist,
713
                                display_optional=(screen_x > 102),
A
Alessio Sergi 已提交
714
                                display_additional=(not OSX),
A
Alessio Sergi 已提交
715
                                max_y=(screen_y - self.get_stats_display_height(stats_alert) - 2))
716
            self.new_line()
717 718
            self.display_plugin(stats_alert)

719 720 721
        # History option
        # Generate history graph
        if self.history_tag and self.args.enable_history:
722
            self.display_popup(
A
Alessio Sergi 已提交
723 724
                'Generate graphs history in {0}\nPlease wait...'.format(
                    self.glances_history.get_output_folder()))
N
Nicolargo 已提交
725
            self.display_popup(
A
Alessio Sergi 已提交
726 727 728
                'Generate graphs history in {0}\nDone: {1} graphs generated'.format(
                    self.glances_history.get_output_folder(),
                    self.glances_history.generate_graph(stats)))
729
        elif self.reset_history_tag and self.args.enable_history:
A
Alessio Sergi 已提交
730
            self.display_popup('Reset history')
731
            self.glances_history.reset(stats)
732
        elif (self.history_tag or self.reset_history_tag) and not self.args.enable_history:
733 734
            try:
                self.glances_history.graph_enabled()
A
Alessio Sergi 已提交
735
            except Exception:
A
Alessio Sergi 已提交
736
                self.display_popup('History disabled\nEnable it using --enable-history')
737
            else:
A
Alessio Sergi 已提交
738
                self.display_popup('History disabled\nPlease install matplotlib')
739 740 741
        self.history_tag = False
        self.reset_history_tag = False

N
Nicolargo 已提交
742
        # Display edit filter popup
743 744
        # Only in standalone mode (cs_status is None)
        if self.edit_filter and cs_status is None:
A
Alessio Sergi 已提交
745 746 747
            new_filter = self.display_popup(
                'Process filter pattern: ', is_input=True,
                input_value=glances_processes.process_filter)
748
            glances_processes.process_filter = new_filter
749
        elif self.edit_filter and cs_status != 'None':
A
Alessio Sergi 已提交
750
            self.display_popup('Process filter only available in standalone mode')
N
Nicolargo 已提交
751 752
        self.edit_filter = False

753 754
        return True

755 756
    def display_popup(self, message,
                      size_x=None, size_y=None,
N
Nicolargo 已提交
757 758
                      duration=3,
                      is_input=False,
N
Nicolargo 已提交
759
                      input_size=30,
N
Nicolargo 已提交
760
                      input_value=None):
761
        """
A
PEP 257  
Alessio Sergi 已提交
762 763
        Display a centered popup.

N
Nicolargo 已提交
764 765 766 767 768
        If is_input is False:
         Display a centered popup with the given message during duration seconds
         If size_x and size_y: set the popup size
         else set it automatically
         Return True if the popup could be displayed
A
PEP 257  
Alessio Sergi 已提交
769

N
Nicolargo 已提交
770 771 772 773
        If is_input is True:
         Display a centered popup with the given message and a input field
         If size_x and size_y: set the popup size
         else set it automatically
774
         Return the input string or None if the field is empty
775 776
        """
        # Center the popup
N
Nicolargo 已提交
777
        sentence_list = message.split('\n')
778
        if size_x is None:
N
Nicolargo 已提交
779
            size_x = len(max(sentence_list, key=len)) + 4
N
Nicolargo 已提交
780 781 782
            # Add space for the input field
            if is_input:
                size_x += input_size
783
        if size_y is None:
N
Nicolargo 已提交
784
            size_y = len(sentence_list) + 4
785 786 787 788
        screen_x = self.screen.getmaxyx()[1]
        screen_y = self.screen.getmaxyx()[0]
        if size_x > screen_x or size_y > screen_y:
            # No size to display the popup => abord
789
            return False
790 791
        pos_x = int((screen_x - size_x) / 2)
        pos_y = int((screen_y - size_y) / 2)
792 793 794

        # Create the popup
        popup = curses.newwin(size_y, size_x, pos_y, pos_x)
795

796 797 798 799
        # Fill the popup
        popup.border()

        # Add the message
N
nicolargo 已提交
800
        for y, m in enumerate(message.split('\n')):
801 802
            popup.addnstr(2 + y, 2, m, len(m))

A
Alessio Sergi 已提交
803
        if is_input and not WINDOWS:
N
Nicolargo 已提交
804 805
            # Create a subwindow for the text field
            subpop = popup.derwin(1, input_size, 2, 2 + len(m))
806
            subpop.attron(self.colors_list['FILTER'])
N
Nicolargo 已提交
807 808 809 810 811 812 813 814
            # Init the field with the current value
            if input_value is not None:
                subpop.addnstr(0, 0, input_value, len(input_value))
            # Display the popup
            popup.refresh()
            subpop.refresh()
            # Create the textbox inside the subwindows
            self.set_cursor(2)
815
            self.flash_cursor()
A
Alessio Sergi 已提交
816
            textbox = GlancesTextbox(subpop, insert_mode=False)
N
Nicolargo 已提交
817 818
            textbox.edit()
            self.set_cursor(0)
819
            self.no_flash_cursor()
N
Nicolargo 已提交
820
            if textbox.gather() != '':
N
Nicolargo 已提交
821
                logger.debug(
822
                    "User enters the following string: %s" % textbox.gather())
N
Nicolargo 已提交
823 824
                return textbox.gather()[:-1]
            else:
825
                logger.debug("User centers an empty string")
N
Nicolargo 已提交
826 827 828 829 830 831
                return None
        else:
            # Display the popup
            popup.refresh()
            curses.napms(duration * 1000)
            return True
832

833
    def display_plugin(self, plugin_stats,
834
                       display_optional=True,
835
                       display_additional=True,
836
                       max_y=65535):
A
PEP 257  
Alessio Sergi 已提交
837 838
        """Display the plugin_stats on the screen.

839 840
        If display_optional=True display the optional stats
        If display_additional=True display additionnal stats
841 842
        max_y do not display line > max_y
        """
843 844 845
        # Exit if:
        # - the plugin_stats message is empty
        # - the display tag = False
846
        if plugin_stats is None or not plugin_stats['msgdict'] or not plugin_stats['display']:
847
            # Exit
848 849 850 851 852 853 854
            return 0

        # Get the screen size
        screen_x = self.screen.getmaxyx()[1]
        screen_y = self.screen.getmaxyx()[0]

        # Set the upper/left position of the message
855
        if plugin_stats['align'] == 'right':
856
            # Right align (last column)
857
            display_x = screen_x - self.get_stats_display_width(plugin_stats)
858
        else:
859
            display_x = self.column
860
        if plugin_stats['align'] == 'bottom':
861
            # Bottom (last line)
862
            display_y = screen_y - self.get_stats_display_height(plugin_stats)
863
        else:
864
            display_y = self.line
865

866 867
        # Display
        x = display_x
868
        x_max = x
869 870 871
        y = display_y
        for m in plugin_stats['msgdict']:
            # New line
872
            if m['msg'].startswith('\n'):
873
                # Go to the next line
874
                y += 1
875 876 877 878
                # Return to the first column
                x = display_x
                continue
            # Do not display outside the screen
879
            if x < 0:
880
                continue
881
            if not m['splittable'] and (x + len(m['msg']) > screen_x):
882
                continue
883
            if y < 0 or (y + 1 > screen_y) or (y > max_y):
884 885
                break
            # If display_optional = False do not display optional stats
886
            if not display_optional and m['optional']:
887
                continue
888 889 890
            # If display_additional = False do not display additional stats
            if not display_additional and m['additional']:
                continue
891 892 893
            # Is it possible to display the stat with the current screen size
            # !!! Crach if not try/except... Why ???
            try:
A
Alessio Sergi 已提交
894 895
                self.term_window.addnstr(y, x,
                                         m['msg'],
896 897
                                         # Do not disply outside the screen
                                         screen_x - x,
898
                                         self.colors_list[m['decoration']])
A
Alessio Sergi 已提交
899
            except Exception:
900 901 902
                pass
            else:
                # New column
A
Alessio Sergi 已提交
903 904 905 906 907
                # 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.
                offset = len(u(m['msg']))
908
                x += offset
909 910
                if x > x_max:
                    x_max = x
911 912

        # Compute the next Glances column/line position
N
Nicolargo 已提交
913 914
        self.next_column = max(
            self.next_column, x_max + self.space_between_column)
915
        self.next_line = max(self.next_line, y + self.space_between_line)
916 917

    def erase(self):
A
PEP 257  
Alessio Sergi 已提交
918
        """Erase the content of the screen."""
919 920
        self.term_window.erase()

921
    def flush(self, stats, cs_status=None):
A
PEP 257  
Alessio Sergi 已提交
922 923
        """Clear and update the screen.

924 925 926 927 928 929 930
        stats: Stats database to display
        cs_status:
            "None": standalone or server mode
            "Connected": Client is connected to the server
            "Disconnected": Client is disconnected from the server
        """
        self.erase()
931
        self.display(stats, cs_status=cs_status)
932

933
    def update(self, stats, cs_status=None, return_to_browser=False):
A
PEP 257  
Alessio Sergi 已提交
934 935 936 937
        """Update the screen.

        Wait for __refresh_time sec / catch key every 100 ms.

N
Nicolargo 已提交
938
        INPUT
939 940 941 942 943
        stats: Stats database to display
        cs_status:
            "None": standalone or server mode
            "Connected": Client is connected to the server
            "Disconnected": Client is disconnected from the server
N
Nicolargo 已提交
944 945 946 947 948 949 950
        return_to_browser:
            True: Do not exist, return to the browser list
            False: Exit and return to the shell

        OUPUT
        True: Exit key has been pressed
        False: Others cases...
951 952
        """
        # Flush display
953
        self.flush(stats, cs_status=cs_status)
954 955

        # Wait
N
Nicolargo 已提交
956
        exitkey = False
957
        countdown = Timer(self.__refresh_time)
N
Nicolargo 已提交
958
        while not countdown.finished() and not exitkey:
959
            # Getkey
N
Nicolargo 已提交
960 961 962 963
            pressedkey = self.__catch_key(return_to_browser=return_to_browser)
            # Is it an exit key ?
            exitkey = (pressedkey == ord('\x1b') or pressedkey == ord('q'))
            if not exitkey and pressedkey > -1:
964
                # Redraw display
965
                self.flush(stats, cs_status=cs_status)
966 967 968
            # Wait 100ms...
            curses.napms(100)

N
Nicolargo 已提交
969 970
        return exitkey

971
    def get_stats_display_width(self, curse_msg, without_option=False):
A
PEP 257  
Alessio Sergi 已提交
972
        """Return the width of the formatted curses message.
973

A
PEP 257  
Alessio Sergi 已提交
974 975
        The height is defined by the maximum line.
        """
976
        try:
977
            if without_option:
978
                # Size without options
A
flake8  
Alessio Sergi 已提交
979
                c = len(max(''.join([(re.sub(r'[^\x00-\x7F]+', ' ', i['msg']) if not i['optional'] else "")
980
                                     for i in curse_msg['msgdict']]).split('\n'), key=len))
981 982
            else:
                # Size with all options
A
flake8  
Alessio Sergi 已提交
983
                c = len(max(''.join([re.sub(r'[^\x00-\x7F]+', ' ', i['msg'])
984
                                     for i in curse_msg['msgdict']]).split('\n'), key=len))
A
Alessio Sergi 已提交
985
        except Exception:
986 987 988 989
            return 0
        else:
            return c

990
    def get_stats_display_height(self, curse_msg):
A
PEP 257  
Alessio Sergi 已提交
991
        r"""Return the height of the formatted curses message.
992

A
PEP 257  
Alessio Sergi 已提交
993 994
        The height is defined by the number of '\n' (new line).
        """
995
        try:
A
Alessio Sergi 已提交
996
            c = [i['msg'] for i in curse_msg['msgdict']].count('\n')
A
Alessio Sergi 已提交
997
        except Exception:
998 999 1000
            return 0
        else:
            return c + 1
N
Nicolargo 已提交
1001

1002

1003 1004
class GlancesCursesStandalone(_GlancesCurses):

A
PEP 257  
Alessio Sergi 已提交
1005
    """Class for the Glances curse standalone."""
1006 1007 1008 1009

    pass


1010 1011
class GlancesCursesClient(_GlancesCurses):

A
PEP 257  
Alessio Sergi 已提交
1012
    """Class for the Glances curse client."""
1013 1014 1015 1016 1017 1018

    pass


class GlancesCursesBrowser(_GlancesCurses):

A
PEP 257  
Alessio Sergi 已提交
1019
    """Class for the Glances curse client browser."""
1020 1021 1022

    def __init__(self, args=None):
        # Init the father class
A
Alessio Sergi 已提交
1023
        super(GlancesCursesBrowser, self).__init__(args=args)
1024

N
Nicolargo 已提交
1025 1026
        _colors_list = {
            'UNKNOWN': self.no_color,
N
Nicolargo 已提交
1027
            'SNMP': self.default_color2,
N
Nicolargo 已提交
1028 1029 1030 1031
            'ONLINE': self.default_color2,
            'OFFLINE': self.ifCRITICAL_color2,
            'PROTECTED': self.ifWARNING_color2,
        }
1032
        self.colors_list.update(_colors_list)
N
Nicolargo 已提交
1033

1034 1035 1036 1037
        # First time scan tag
        # Used to display a specific message when the browser is started
        self.first_scan = True

1038 1039 1040 1041
        # Init refresh time
        self.__refresh_time = args.time

        # Init the cursor position for the client browser
1042
        self.cursor_position = 0
1043

N
Nicolargo 已提交
1044
        # Active Glances server number
1045
        self._active_server = None
N
Nicolargo 已提交
1046

1047 1048 1049 1050
    @property
    def active_server(self):
        """Return the active server or None if it's the browser list."""
        return self._active_server
N
Nicolargo 已提交
1051

1052 1053 1054 1055
    @active_server.setter
    def active_server(self, index):
        """Set the active server or None if no server selected."""
        self._active_server = index
1056

1057 1058 1059
    @property
    def cursor(self):
        """Get the cursor position."""
1060 1061
        return self.cursor_position

1062 1063 1064 1065 1066
    @cursor.setter
    def cursor(self, position):
        """Set the cursor position."""
        self.cursor_position = position

N
Nicolargo 已提交
1067
    def cursor_up(self, servers_list):
A
PEP 257  
Alessio Sergi 已提交
1068
        """Set the cursor to position N-1 in the list."""
1069 1070
        if self.cursor_position > 0:
            self.cursor_position -= 1
N
Nicolargo 已提交
1071 1072
        else:
            self.cursor_position = len(servers_list) - 1
1073 1074

    def cursor_down(self, servers_list):
A
PEP 257  
Alessio Sergi 已提交
1075
        """Set the cursor to position N-1 in the list."""
1076 1077
        if self.cursor_position < len(servers_list) - 1:
            self.cursor_position += 1
N
Nicolargo 已提交
1078 1079
        else:
            self.cursor_position = 0
1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092

    def __catch_key(self, servers_list):
        # Catch the browser pressed key
        self.pressedkey = self.get_key(self.term_window)

        # Actions...
        if self.pressedkey == ord('\x1b') or self.pressedkey == ord('q'):
            # 'ESC'|'q' > Quit
            self.end()
            logger.info("Stop Glances client browser")
            sys.exit(0)
        elif self.pressedkey == 10:
            # 'ENTER' > Run Glances on the selected server
1093 1094
            logger.debug("Server number {0} selected".format(self.cursor + 1))
            self.active_server = self.cursor
1095 1096
        elif self.pressedkey == 259:
            # 'UP' > Up in the server list
N
Nicolargo 已提交
1097
            self.cursor_up(servers_list)
1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112
        elif self.pressedkey == 258:
            # 'DOWN' > Down in the server list
            self.cursor_down(servers_list)

        # Return the key code
        return self.pressedkey

    def update(self, servers_list):
        """Update the servers' list screen.

        Wait for __refresh_time sec / catch key every 100 ms.

        servers_list: Dict of dict with servers stats
        """
        # Flush display
1113
        logger.debug('Servers list: {0}'.format(servers_list))
1114 1115 1116
        self.flush(servers_list)

        # Wait
N
Nicolargo 已提交
1117
        exitkey = False
1118
        countdown = Timer(self.__refresh_time)
N
Nicolargo 已提交
1119
        while not countdown.finished() and not exitkey:
1120
            # Getkey
N
Nicolargo 已提交
1121 1122
            pressedkey = self.__catch_key(servers_list)
            # Is it an exit or select server key ?
N
Nicolargo 已提交
1123 1124
            exitkey = (
                pressedkey == ord('\x1b') or pressedkey == ord('q') or pressedkey == 10)
N
Nicolargo 已提交
1125
            if not exitkey and pressedkey > -1:
1126 1127 1128 1129 1130
                # Redraw display
                self.flush(servers_list)
            # Wait 100ms...
            curses.napms(100)

1131
        return self.active_server
N
Nicolargo 已提交
1132

1133 1134
    def flush(self, servers_list):
        """Update the servers' list screen.
A
PEP 257  
Alessio Sergi 已提交
1135

1136 1137 1138 1139 1140 1141
        servers_list: List of dict with servers stats
        """
        self.erase()
        self.display(servers_list)

    def display(self, servers_list):
A
PEP 257  
Alessio Sergi 已提交
1142 1143
        """Display the servers list.

1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160
        Return:
            True if the stats have been displayed
            False if the stats have not been displayed (no server available)
        """
        # Init the internal line/column for Glances Curses
        self.init_line_column()

        # Get the current screen size
        screen_x = self.screen.getmaxyx()[1]
        screen_y = self.screen.getmaxyx()[0]

        # Init position
        x = 0
        y = 0

        # Display top header
        if len(servers_list) == 0:
N
Nicolargo 已提交
1161
            if self.first_scan and not self.args.disable_autodiscover:
A
Alessio Sergi 已提交
1162
                msg = 'Glances is scanning your network. Please wait...'
N
Nicolargo 已提交
1163
                self.first_scan = False
1164
            else:
A
Alessio Sergi 已提交
1165
                msg = 'No Glances server available'
1166
        elif len(servers_list) == 1:
A
Alessio Sergi 已提交
1167
            msg = 'One Glances server available'
1168
        else:
A
Alessio Sergi 已提交
1169
            msg = '{0} Glances servers available'.format(len(servers_list))
1170
        if self.args.disable_autodiscover:
A
Alessio Sergi 已提交
1171
            msg += ' ' + '(auto discover is disabled)'
1172 1173 1174 1175 1176 1177 1178 1179 1180
        self.term_window.addnstr(y, x,
                                 msg,
                                 screen_x - x,
                                 self.colors_list['TITLE'])

        if len(servers_list) == 0:
            return False

        # Display the Glances server list
A
flake8  
Alessio Sergi 已提交
1181
        # ================================
1182 1183 1184 1185

        # Table of table
        # Item description: [stats_id, column name, column size]
        column_def = [
A
Alessio Sergi 已提交
1186
            ['name', 'Name', 16],
1187
            ['alias', None, None],
A
Alessio Sergi 已提交
1188 1189 1190
            ['load_min5', 'LOAD', 6],
            ['cpu_percent', 'CPU%', 5],
            ['mem_percent', 'MEM%', 5],
1191
            ['status', 'STATUS', 9],
A
Alessio Sergi 已提交
1192 1193 1194
            ['ip', 'IP', 15],
            # ['port', 'PORT', 5],
            ['hr_name', 'OS', 16],
1195 1196 1197 1198 1199
        ]
        y = 2

        # Display table header
        xc = x + 2
N
nicolargo 已提交
1200
        for cpt, c in enumerate(column_def):
1201
            if xc < screen_x and y < screen_y and c[1] is not None:
N
Nicolargo 已提交
1202 1203 1204 1205 1206
                self.term_window.addnstr(y, xc,
                                         c[1],
                                         screen_x - x,
                                         self.colors_list['BOLD'])
                xc += c[2] + self.space_between_column
1207 1208 1209 1210
        y += 1

        # If a servers has been deleted from the list...
        # ... and if the cursor is in the latest position
1211
        if self.cursor > len(servers_list) - 1:
1212
            # Set the cursor position to the latest item
1213
            self.cursor = len(servers_list) - 1
1214 1215 1216 1217 1218

        # Display table
        line = 0
        for v in servers_list:
            # Get server stats
1219 1220 1221
            server_stat = {}
            for c in column_def:
                try:
1222
                    server_stat[c[0]] = v[c[0]]
1223 1224
                except KeyError as e:
                    logger.debug(
A
Alessio Sergi 已提交
1225
                        "Cannot grab stats {0} from server (KeyError: {1})".format(c[0], e))
1226
                    server_stat[c[0]] = '?'
1227
                # Display alias instead of name
N
Nicolargo 已提交
1228 1229 1230
                try:
                    if c[0] == 'alias' and v[c[0]] is not None:
                        server_stat['name'] = v[c[0]]
A
Alessio Sergi 已提交
1231
                except KeyError:
N
Nicolargo 已提交
1232
                    pass
1233 1234 1235 1236 1237 1238

            # Display line for server stats
            cpt = 0
            xc = x

            # Is the line selected ?
1239
            if line == self.cursor:
1240
                # Display cursor
1241 1242
                self.term_window.addnstr(
                    y, xc, ">", screen_x - xc, self.colors_list['BOLD'])
1243

1244
            # Display the line
1245 1246
            xc += 2
            for c in column_def:
1247
                if xc < screen_x and y < screen_y and c[1] is not None:
N
Nicolargo 已提交
1248
                    # Display server stats
1249 1250
                    self.term_window.addnstr(
                        y, xc, format(server_stat[c[0]]), c[2], self.colors_list[v['status']])
N
Nicolargo 已提交
1251
                    xc += c[2] + self.space_between_column
1252 1253 1254 1255 1256 1257 1258
                cpt += 1
            # Next line, next server...
            y += 1
            line += 1

        return True

A
Alessio Sergi 已提交
1259
if not WINDOWS:
1260
    class GlancesTextbox(Textbox, object):
1261

1262 1263
        def __init__(self, *args, **kwargs):
            super(GlancesTextbox, self).__init__(*args, **kwargs)
1264

N
Nicolargo 已提交
1265
        def do_command(self, ch):
1266
            if ch == 10:  # Enter
N
Nicolargo 已提交
1267
                return 0
1268
            if ch == 127:  # Back
N
Nicolargo 已提交
1269
                return 8
1270
            return super(GlancesTextbox, self).do_command(ch)