Test Failed
Push — develop ( 66c9ff...e21229 )
by Nicolas
05:06
created

glances/outputs/glances_curses_browser.py (39 issues)

1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of Glances.
4
#
5
# Copyright (C) 2019 Nicolargo <[email protected]>
6
#
7
# Glances is free software; you can redistribute it and/or modify
8
# it under the terms of the GNU Lesser General Public License as published by
9
# the Free Software Foundation, either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Glances is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU Lesser General Public License for more details.
16
#
17
# You should have received a copy of the GNU Lesser General Public License
18
# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20
"""Curses browser interface class ."""
21
22
import sys
23
import math
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
24
import curses
25
from glances.outputs.glances_curses import _GlancesCurses
26
27
from glances.logger import logger
28
from glances.timer import Timer
29
30
31
class GlancesCursesBrowser(_GlancesCurses):
32
    """Class for the Glances curse client browser."""
33
34
    def __init__(self, args=None):
35
        """Init the father class."""
36
        super(GlancesCursesBrowser, self).__init__(args=args)
37
38
        _colors_list = {
39
            'UNKNOWN': self.no_color,
40
            'SNMP': self.default_color2,
41
            'ONLINE': self.default_color2,
42
            'OFFLINE': self.ifCRITICAL_color2,
43
            'PROTECTED': self.ifWARNING_color2,
44
        }
45
        self.colors_list.update(_colors_list)
46
47
        # First time scan tag
48
        # Used to display a specific message when the browser is started
49
        self.first_scan = True
50
51
        # Init refresh time
52
        self.__refresh_time = args.time
53
54
        # Init the cursor position for the client browser
55
        self.cursor_position = 0
56
57
        # Active Glances server number
58
        self._active_server = None
59
60
        self._current_page = 0
61
        self._page_max = 0
62
        self._page_max_lines = 0
63
64
        self.is_end = False
65
        self._revesed_sorting = False
66
        self._stats_list = None
67
68
    @property
69
    def active_server(self):
70
        """Return the active server or None if it's the browser list."""
71
        return self._active_server
72
73
    @active_server.setter
74
    def active_server(self, index):
75
        """Set the active server or None if no server selected."""
76
        self._active_server = index
77
78
    @property
79
    def cursor(self):
80
        """Get the cursor position."""
81
        return self.cursor_position
82
83
    @cursor.setter
84
    def cursor(self, position):
85
        """Set the cursor position."""
86
        self.cursor_position = position
87
88
    def get_pagelines(self, stats):
0 ignored issues
show
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
89
        if self._current_page == self._page_max - 1:
90
            page_lines = len(stats) % self._page_max_lines
91
        else:
92
            page_lines = self._page_max_lines
93
        return page_lines
94
95
    def _get_status_count(self, stats):
0 ignored issues
show
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
96
        counts = {}
97
        for item in stats:
98
            color = item['status']
99
            counts[color] = counts.get(color, 0) + 1
100
    
0 ignored issues
show
Trailing whitespace
Loading history...
101
        result = ''
102
        for key in counts.keys():
0 ignored issues
show
Consider iterating the dictionary directly instead of calling .keys()
Loading history...
103
            result += key + ': ' + str(counts[key]) + ' '
104
   
0 ignored issues
show
Trailing whitespace
Loading history...
105
        return result
106
107
    def _get_stats(self, stats):
108
        stats_list = None
109
        if self._stats_list is not None:
110
            stats_list = self._stats_list
111
            stats_list.sort(reverse = self._revesed_sorting, 
0 ignored issues
show
Trailing whitespace
Loading history...
No space allowed around keyword argument assignment
Loading history...
112
                            key = lambda x: { 'UNKNOWN' : 0,
0 ignored issues
show
No space allowed around keyword argument assignment
Loading history...
No space allowed after bracket
Loading history...
113
                                              'OFFLINE' : 1,
114
                                              'PROTECTED' : 2,
115
                                              'SNMP' : 3,
116
                                              'ONLINE': 4 }.get(x['status'], 99))
0 ignored issues
show
This line is too long as per the coding-style (81/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
No space allowed before bracket
Loading history...
117
        else:
118
            stats_list = stats
119
        
0 ignored issues
show
Trailing whitespace
Loading history...
120
        return stats_list
121
        
0 ignored issues
show
Trailing whitespace
Loading history...
122
    def cursor_up(self, stats):
123
        """Set the cursor to position N-1 in the list."""
124
        if 0 <= self.cursor_position - 1:
0 ignored issues
show
Comparison should be (self.cursor_position) - (1) >= 0
Loading history...
125
            self.cursor_position -= 1
126
        else:
127
            if self._current_page - 1 < 0 :
0 ignored issues
show
No space allowed before :
Loading history...
128
                self._current_page = self._page_max - 1
129
                self.cursor_position = (len(stats) - 1) % self._page_max_lines 
0 ignored issues
show
Trailing whitespace
Loading history...
130
            else:
131
                self._current_page -= 1
132
                self.cursor_position = self._page_max_lines - 1
133
134
    def cursor_down(self, stats):
135
        """Set the cursor to position N-1 in the list."""
136
        
0 ignored issues
show
Trailing whitespace
Loading history...
137
        if self.cursor_position + 1 < self.get_pagelines(stats):
138
            self.cursor_position += 1
139
        else:
140
            if self._current_page + 1 < self._page_max:
141
                self._current_page += 1
142
            else:
143
                self._current_page = 0
144
            self.cursor_position = 0
145
146
    def cursor_pageup(self, stats):
0 ignored issues
show
The argument stats seems to be unused.
Loading history...
147
        """Set prev page."""
148
        if self._current_page - 1 < 0:
149
            self._current_page = self._page_max - 1
150
        else:
151
            self._current_page -= 1
152
        self.cursor_position = 0
153
154
    def cursor_pagedown(self, stats):
0 ignored issues
show
The argument stats seems to be unused.
Loading history...
155
        """Set next page."""
156
        if self._current_page + 1 < self._page_max:
157
            self._current_page += 1
158
        else:
159
            self._current_page = 0
160
        self.cursor_position = 0
161
162
    def __catch_key(self, stats):
163
        # Catch the browser pressed key
164
        self.pressedkey = self.get_key(self.term_window)
165
        refresh = False
166
        if self.pressedkey != -1:
167
            logger.debug("Key pressed. Code=%s" % self.pressedkey)
168
169
        # Actions...
170
        if self.pressedkey == ord('\x1b') or self.pressedkey == ord('q'):
171
            # 'ESC'|'q' > Quit
172
            self.end()
173
            logger.info("Stop Glances client browser")
174
            # sys.exit(0)
175
            self.is_end = True
176
        elif self.pressedkey == 10:
177
            # 'ENTER' > Run Glances on the selected server
178
            self.active_server =  self._current_page * self._page_max_lines + self.cursor_position
0 ignored issues
show
This line is too long as per the coding-style (98/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
Exactly one space required after assignment
Loading history...
179
            logger.debug("Server {}/{} selected".format(self.active_server, len(stats)))
180
        elif self.pressedkey == curses.KEY_UP or self.pressedkey == 65:
181
            # 'UP' > Up in the server list
182
            self.cursor_up(stats)
183
            logger.debug("Server {}/{} selected".format(self.cursor + 1, len(stats)))
184
        elif self.pressedkey == curses.KEY_DOWN or self.pressedkey == 66:
185
            # 'DOWN' > Down in the server list
186
            self.cursor_down(stats)
187
            logger.debug("Server {}/{} selected".format(self.cursor + 1, len(stats)))
188
        elif self.pressedkey == curses.KEY_PPAGE:
189
             # 'Page UP' > Prev page in the server list
190
            self.cursor_pageup(stats)
191
            logger.debug("PageUP: Server ({}/{}) pages.".format(self._current_page + 1, self._page_max))
0 ignored issues
show
This line is too long as per the coding-style (104/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
Use formatting in logging functions and pass the parameters as arguments
Loading history...
192
        elif self.pressedkey == curses.KEY_NPAGE:
193
            # 'Page Down' > Next page in the server list
194
            self.cursor_pagedown(stats)
195
            logger.debug("PageDown: Server {}/{} pages".format(self._current_page + 1, self._page_max))
0 ignored issues
show
This line is too long as per the coding-style (103/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
Use formatting in logging functions and pass the parameters as arguments
Loading history...
196
        elif self.pressedkey == ord('1'):
197
            self._stats_list = None
198
            refresh = True
199
        elif self.pressedkey == ord('2'):
200
            self._revesed_sorting = False            
0 ignored issues
show
Trailing whitespace
Loading history...
201
            self._stats_list = stats.copy()
202
            refresh = True
203
        elif self.pressedkey == ord('3'):
204
            self._revesed_sorting = True
205
            self._stats_list = stats.copy()
206
            refresh = True
207
208
        if refresh:
209
            self._current_page = 0
210
            self.cursor_position = 0
211
            self.flush(stats)
212
213
        # Return the key code
214
        return self.pressedkey
215
216
    def update(self,
217
               stats,
218
               duration=3,
219
               cs_status=None,
220
               return_to_browser=False):
221
        """Update the servers' list screen.
222
223
        Wait for __refresh_time sec / catch key every 100 ms.
224
225
        stats: Dict of dict with servers stats
226
        """
227
        # Flush display
228
        logger.debug('Servers list: {}'.format(stats))
229
        self.flush(stats)
230
231
        # Wait
232
        exitkey = False
233
        countdown = Timer(self.__refresh_time)
234
        while not countdown.finished() and not exitkey:
235
            # Getkey
236
            pressedkey = self.__catch_key(stats)
237
            # Is it an exit or select server key ?
238
            exitkey = (
239
                pressedkey == ord('\x1b') or pressedkey == ord('q') or pressedkey == 10)
240
            if not exitkey and pressedkey > -1:
241
                # Redraw display
242
                self.flush(stats)
243
            # Wait 100ms...
244
            self.wait()
245
246
        return self.active_server
247
248
    def flush(self, stats):
249
        """Update the servers' list screen.
250
251
        stats: List of dict with servers stats
252
        """
253
        self.erase()
254
        self.display(stats)
255
256
    def display(self, stats, cs_status=None):
257
        """Display the servers list.
258
259
        Return:
260
            True if the stats have been displayed
261
            False if the stats have not been displayed (no server available)
262
        """
263
        # Init the internal line/column for Glances Curses
264
        self.init_line_column()
265
266
        # Get the current screen size
267
        screen_x = self.screen.getmaxyx()[1]
268
        screen_y = self.screen.getmaxyx()[0]
269
        stats_max = screen_y - 3
270
        stats_len = len(stats)
271
272
        self._page_max_lines = stats_max
273
        self._page_max = int(math.ceil(stats_len / stats_max))
0 ignored issues
show
division w/o __future__ statement
Loading history...
274
        # Init position
275
        x = 0
276
        y = 0
277
278
        # Display top header
279
        if stats_len == 0:
280
            if self.first_scan and not self.args.disable_autodiscover:
281
                msg = 'Glances is scanning your network. Please wait...'
282
                self.first_scan = False
283
            else:
284
                msg = 'No Glances server available'
285
        elif len(stats) == 1:
286
            msg = 'One Glances server available'
287
        else:
288
            msg = '{} Glances servers available'.format(stats_len)
289
        if self.args.disable_autodiscover:
290
            msg += ' (auto discover is disabled)'
291
        if screen_y > 1:
292
            self.term_window.addnstr(y, x,
293
                                     msg,
294
                                     screen_x - x,
295
                                     self.colors_list['TITLE'])
296
297
            msg = '{}'.format(self._get_status_count(stats))
298
            self.term_window.addnstr(y + 1, x,
299
                                     msg,
300
                                     screen_x - x)
301
302
        if stats_len > stats_max and screen_y > 2:
303
            msg = '{} servers displayed.({}/{}) {}'.format(self.get_pagelines(stats),
0 ignored issues
show
This line is too long as per the coding-style (85/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
304
                                                            self._current_page + 1, 
0 ignored issues
show
Wrong continued indentation (remove 1 space).
Loading history...
Trailing whitespace
Loading history...
This line is too long as per the coding-style (83/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
305
                                                            self._page_max, 
0 ignored issues
show
Wrong continued indentation (remove 1 space).
Loading history...
Trailing whitespace
Loading history...
306
                                                            self._get_status_count(stats))
0 ignored issues
show
Wrong continued indentation (remove 1 space).
Loading history...
This line is too long as per the coding-style (90/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
307
            self.term_window.addnstr(y + 1, x,
308
                                     msg,
309
                                     screen_x - x)
310
        
0 ignored issues
show
Trailing whitespace
Loading history...
311
        if stats_len == 0:
312
            return False
313
314
        # Display the Glances server list
315
        # ================================
316
317
        # Table of table
318
        # Item description: [stats_id, column name, column size]
319
        column_def = [
320
            ['name', 'Name', 16],
321
            ['alias', None, None],
322
            ['load_min5', 'LOAD', 6],
323
            ['cpu_percent', 'CPU%', 5],
324
            ['mem_percent', 'MEM%', 5],
325
            ['status', 'STATUS', 9],
326
            ['ip', 'IP', 15],
327
            # ['port', 'PORT', 5],
328
            ['hr_name', 'OS', 16],
329
        ]
330
        y = 2
331
332
        # Display table header
333
        xc = x + 2
334
        for cpt, c in enumerate(column_def):
335
            if xc < screen_x and y < screen_y and c[1] is not None:
336
                self.term_window.addnstr(y, xc,
337
                                         c[1],
338
                                         screen_x - x,
339
                                         self.colors_list['BOLD'])
340
                xc += c[2] + self.space_between_column
341
        y += 1
342
343
        # If a servers has been deleted from the list...
344
        # ... and if the cursor is in the latest position
345
        if self.cursor > len(stats) - 1:
346
            # Set the cursor position to the latest item
347
            self.cursor = len(stats) - 1
348
349
        stats_list = self._get_stats(stats)
350
        start_line = self._page_max_lines * self._current_page 
0 ignored issues
show
Trailing whitespace
Loading history...
351
        end_line = start_line + self.get_pagelines(stats_list)
352
        current_page = stats_list[start_line:end_line]
353
        
0 ignored issues
show
Trailing whitespace
Loading history...
354
        # Display table
355
        line = 0
356
        for v in current_page:
357
            # Limit the number of displayed server (see issue #1256)
358
            if line >= stats_max:
359
                continue
360
            # Get server stats
361
            server_stat = {}
362
            for c in column_def:
363
                try:
364
                    server_stat[c[0]] = v[c[0]]
365
                except KeyError as e:
366
                    logger.debug(
367
                        "Cannot grab stats {} from server (KeyError: {})".format(c[0], e))
368
                    server_stat[c[0]] = '?'
369
                # Display alias instead of name
370
                try:
371
                    if c[0] == 'alias' and v[c[0]] is not None:
372
                        server_stat['name'] = v[c[0]]
373
                except KeyError:
374
                    pass
375
376
            # Display line for server stats
377
            cpt = 0
378
            xc = x
379
380
            # Is the line selected ?
381
            if line == self.cursor:
382
                # Display cursor
383
                self.term_window.addnstr(
384
                    y, xc, ">", screen_x - xc, self.colors_list['BOLD'])
385
386
            # Display the line
387
            xc += 2
388
            for c in column_def:
389
                if xc < screen_x and y < screen_y and c[1] is not None:
390
                    # Display server stats
391
                    self.term_window.addnstr(
392
                        y, xc, format(server_stat[c[0]]), c[2], self.colors_list[v['status']])
393
                    xc += c[2] + self.space_between_column
394
                cpt += 1
395
            # Next line, next server...
396
            y += 1
397
            line += 1
398
399
        return True
400