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

glances/outputs/glances_curses_browser.py (1 issue)

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
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):
0 ignored issues
show
Too many instance attributes (13/7)
Loading history...
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):
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):
96
        counts = {}
97
        for item in stats:
98
            color = item['status']
99
            counts[color] = counts.get(color, 0) + 1
100
    
101
        result = ''
102
        for key in counts.keys():
103
            result += key + ': ' + str(counts[key]) + ' '
104
   
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, 
112
                            key = lambda x: { 'UNKNOWN' : 0,
113
                                              'OFFLINE' : 1,
114
                                              'PROTECTED' : 2,
115
                                              'SNMP' : 3,
116
                                              'ONLINE': 4 }.get(x['status'], 99))
117
        else:
118
            stats_list = stats
119
        
120
        return stats_list
121
        
122
    def cursor_up(self, stats):
123
        """Set the cursor to position N-1 in the list."""
124
        if 0 <= self.cursor_position - 1:
125
            self.cursor_position -= 1
126
        else:
127
            if self._current_page - 1 < 0 :
128
                self._current_page = self._page_max - 1
129
                self.cursor_position = (len(stats) - 1) % self._page_max_lines 
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
        
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):
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):
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
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))
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))
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            
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))
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),
304
                                                            self._current_page + 1, 
305
                                                            self._page_max, 
306
                                                            self._get_status_count(stats))
307
            self.term_window.addnstr(y + 1, x,
308
                                     msg,
309
                                     screen_x - x)
310
        
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 
351
        end_line = start_line + self.get_pagelines(stats_list)
352
        current_page = stats_list[start_line:end_line]
353
        
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