GlancesCursesBrowser.__init__()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 33
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 19
nop 2
dl 0
loc 33
rs 9.45
c 0
b 0
f 0
1
#
2
# This file is part of Glances.
3
#
4
# SPDX-FileCopyrightText: 2022 Nicolas Hennion <[email protected]>
5
#
6
# SPDX-License-Identifier: LGPL-3.0-only
7
#
8
9
"""Curses browser interface class ."""
10
11
import curses
12
import math
13
14
from glances.logger import logger
15
from glances.outputs.glances_curses import _GlancesCurses
16
from glances.timer import Timer
17
18
19
class GlancesCursesBrowser(_GlancesCurses):
20
    """Class for the Glances curse client browser."""
21
22
    def __init__(self, args=None):
23
        """Init the father class."""
24
        super().__init__(args=args)
25
26
        _colors_list = {
27
            'UNKNOWN': self.no_color,
28
            'SNMP': self.default_color2,
29
            'ONLINE': self.default_color2,
30
            'OFFLINE': self.ifCRITICAL_color2,
31
            'PROTECTED': self.ifWARNING_color2,
32
        }
33
        self.colors_list.update(_colors_list)
34
35
        # First time scan tag
36
        # Used to display a specific message when the browser is started
37
        self.first_scan = True
38
39
        # Init refresh time
40
        self.__refresh_time = args.time
41
42
        # Init the cursor position for the client browser
43
        self.cursor_position = 0
44
45
        # Active Glances server number
46
        self._active_server = None
47
48
        self._current_page = 0
49
        self._page_max = 0
50
        self._page_max_lines = 0
51
52
        self.is_end = False
53
        self._revesed_sorting = False
54
        self._stats_list = None
55
56
    @property
57
    def active_server(self):
58
        """Return the active server or None if it's the browser list."""
59
        return self._active_server
60
61
    @active_server.setter
62
    def active_server(self, index):
63
        """Set the active server or None if no server selected."""
64
        self._active_server = index
65
66
    @property
67
    def cursor(self):
68
        """Get the cursor position."""
69
        return self.cursor_position
70
71
    @cursor.setter
72
    def cursor(self, position):
73
        """Set the cursor position."""
74
        self.cursor_position = position
75
76
    def get_pagelines(self, stats):
77
        if self._current_page == self._page_max - 1:
78
            page_lines = len(stats) % self._page_max_lines
79
        else:
80
            page_lines = self._page_max_lines
81
        return page_lines
82
83
    def _get_status_count(self, stats):
84
        counts = {}
85
        for item in stats:
86
            color = item['status']
87
            counts[color] = counts.get(color, 0) + 1
88
89
        result = ''
90
        for key in counts.keys():
91
            result += key + ': ' + str(counts[key]) + ' '
92
93
        return result
94
95
    def _get_stats(self, stats):
96
        stats_list = None
97
        if self._stats_list is not None:
98
            stats_list = self._stats_list
99
            stats_list.sort(
100
                reverse=self._revesed_sorting,
101
                key=lambda x: {'UNKNOWN': 0, 'OFFLINE': 1, 'PROTECTED': 2, 'SNMP': 3, 'ONLINE': 4}.get(x['status'], 99),
102
            )
103
        else:
104
            stats_list = stats
105
106
        return stats_list
107
108
    def cursor_up(self, stats):
109
        """Set the cursor to position N-1 in the list."""
110
        if 0 <= self.cursor_position - 1:
111
            self.cursor_position -= 1
112
        else:
113
            if self._current_page - 1 < 0:
114
                self._current_page = self._page_max - 1
115
                self.cursor_position = (len(stats) - 1) % self._page_max_lines
116
            else:
117
                self._current_page -= 1
118
                self.cursor_position = self._page_max_lines - 1
119
120
    def cursor_down(self, stats):
121
        """Set the cursor to position N-1 in the list."""
122
123
        if self.cursor_position + 1 < self.get_pagelines(stats):
124
            self.cursor_position += 1
125
        else:
126
            if self._current_page + 1 < self._page_max:
127
                self._current_page += 1
128
            else:
129
                self._current_page = 0
130
            self.cursor_position = 0
131
132
    def cursor_pageup(self, stats):
133
        """Set prev page."""
134
        if self._current_page - 1 < 0:
135
            self._current_page = self._page_max - 1
136
        else:
137
            self._current_page -= 1
138
        self.cursor_position = 0
139
140
    def cursor_pagedown(self, stats):
141
        """Set next page."""
142
        if self._current_page + 1 < self._page_max:
143
            self._current_page += 1
144
        else:
145
            self._current_page = 0
146
        self.cursor_position = 0
147
148
    def __catch_key(self, stats):
149
        # Catch the browser pressed key
150
        self.pressedkey = self.get_key(self.term_window)
151
        refresh = False
152
        if self.pressedkey != -1:
153
            logger.debug(f"Key pressed. Code={self.pressedkey}")
154
155
        # Actions...
156
        if self.pressedkey == ord('\x1b') or self.pressedkey == ord('q'):
157
            # 'ESC'|'q' > Quit
158
            self.end()
159
            logger.info("Stop Glances client browser")
160
            # sys.exit(0)
161
            self.is_end = True
162
        elif self.pressedkey == 10:
163
            # 'ENTER' > Run Glances on the selected server
164
            self.active_server = self._current_page * self._page_max_lines + self.cursor_position
165
            logger.debug(f"Server {self.active_server}/{len(stats)} selected")
166
        elif self.pressedkey == curses.KEY_UP or self.pressedkey == 65:
167
            # 'UP' > Up in the server list
168
            self.cursor_up(stats)
169
            logger.debug(f"Server {self.cursor + 1}/{len(stats)} selected")
170
        elif self.pressedkey == curses.KEY_DOWN or self.pressedkey == 66:
171
            # 'DOWN' > Down in the server list
172
            self.cursor_down(stats)
173
            logger.debug(f"Server {self.cursor + 1}/{len(stats)} selected")
174
        elif self.pressedkey == curses.KEY_PPAGE:
175
            # 'Page UP' > Prev page in the server list
176
            self.cursor_pageup(stats)
177
            logger.debug(f"PageUP: Server ({self._current_page + 1}/{self._page_max}) pages.")
178
        elif self.pressedkey == curses.KEY_NPAGE:
179
            # 'Page Down' > Next page in the server list
180
            self.cursor_pagedown(stats)
181
            logger.debug(f"PageDown: Server {self._current_page + 1}/{self._page_max} pages")
182
        elif self.pressedkey == ord('1'):
183
            self._stats_list = None
184
            refresh = True
185
        elif self.pressedkey == ord('2'):
186
            self._revesed_sorting = False
187
            self._stats_list = stats.copy()
188
            refresh = True
189
        elif self.pressedkey == ord('3'):
190
            self._revesed_sorting = True
191
            self._stats_list = stats.copy()
192
            refresh = True
193
194
        if refresh:
195
            self._current_page = 0
196
            self.cursor_position = 0
197
            self.flush(stats)
198
199
        # Return the key code
200
        return self.pressedkey
201
202
    def update(self, stats, duration=3, cs_status=None, return_to_browser=False):
203
        """Update the servers' list screen.
204
205
        Wait for __refresh_time sec / catch key every 100 ms.
206
207
        :param stats: Dict of dict with servers stats
208
        :param cs_status:
209
        :param duration:
210
        :param return_to_browser:
211
        """
212
        # Flush display
213
        logger.debug(f'Servers list: {stats}')
214
        self.flush(stats)
215
216
        # Wait
217
        exitkey = False
218
        countdown = Timer(self.__refresh_time)
219
        while not countdown.finished() and not exitkey:
220
            # Getkey
221
            pressedkey = self.__catch_key(stats)
222
            # Is it an exit or select server key ?
223
            exitkey = pressedkey == ord('\x1b') or pressedkey == ord('q') or pressedkey == 10
224
            if not exitkey and pressedkey > -1:
225
                # Redraw display
226
                self.flush(stats)
227
            # Wait 100ms...
228
            self.wait()
229
230
        return self.active_server
231
232
    def flush(self, stats):
233
        """Update the servers' list screen.
234
235
        :param stats: List of dict with servers stats
236
        """
237
        self.erase()
238
        self.display(stats)
239
240
    def display(self, stats, cs_status=None):
241
        """Display the servers list.
242
243
        :return: True if the stats have been displayed else False (no server available)
244
        """
245
        # Init the internal line/column for Glances Curses
246
        self.init_line_column()
247
248
        # Get the current screen size
249
        screen_x = self.screen.getmaxyx()[1]
250
        screen_y = self.screen.getmaxyx()[0]
251
        stats_max = screen_y - 3
252
        self._page_max_lines = stats_max
253
        self._page_max = int(math.ceil(len(stats) / stats_max))
254
255
        # Display header
256
        x, y = self.__display_header(stats, 0, 0, screen_x, screen_y)
257
258
        # Display Glances server list
259
        # ================================
260
        return self.__display_server_list(stats, x, y, screen_x, screen_y)
261
262
    def __display_header(self, stats, x, y, screen_x, screen_y):
263
        stats_len = len(stats)
264
        stats_max = screen_y - 3
265
        if stats_len == 0:
266
            if self.first_scan and not self.args.disable_autodiscover:
267
                msg = 'Glances is scanning your network. Please wait...'
268
                self.first_scan = False
269
            else:
270
                msg = 'No Glances server available'
271
        elif len(stats) == 1:
272
            msg = 'One Glances server available'
273
        else:
274
            msg = f'{stats_len} Glances servers available'
275
        if self.args.disable_autodiscover:
276
            msg += ' (auto discover is disabled)'
277
        if screen_y > 1:
278
            self.term_window.addnstr(y, x, msg, screen_x - x, self.colors_list['TITLE'])
279
280
            msg = f'{self._get_status_count(stats)}'
281
            self.term_window.addnstr(y + 1, x, msg, screen_x - x)
282
283
        if stats_len > stats_max and screen_y > 2:
284
            page_lines = self.get_pagelines(stats)
285
            status_count = self._get_status_count(stats)
286
            msg = f'{page_lines} servers displayed.({self._current_page + 1}/{self._page_max}) {status_count}'
287
            self.term_window.addnstr(y + 1, x, msg, screen_x - x)
288
289
        return x, y
290
291
    def __display_server_list(self, stats, x, y, screen_x, screen_y):
292
        if len(stats) == 0:
293
            # No server to display
294
            return False
295
296
        stats_max = screen_y - 3
297
298
        # Table of table
299
        # Item description: [stats_id, column name, column size]
300
        column_def = [
301
            ['name', 'Name', 16],
302
            ['alias', None, None],
303
            ['load_min5', 'LOAD', 6],
304
            ['cpu_percent', 'CPU%', 5],
305
            ['mem_percent', 'MEM%', 5],
306
            ['status', 'STATUS', 9],
307
            ['ip', 'IP', 15],
308
            # ['port', 'PORT', 5],
309
            ['hr_name', 'OS', 16],
310
        ]
311
        y = 2
312
313
        # Display table header
314
        xc = x + 2
315
        for cpt, c in enumerate(column_def):
316
            if xc < screen_x and y < screen_y and c[1] is not None:
317
                self.term_window.addnstr(y, xc, c[1], screen_x - x, self.colors_list['BOLD'])
318
                xc += c[2] + self.space_between_column
319
        y += 1
320
321
        # If a servers has been deleted from the list...
322
        # ... and if the cursor is in the latest position
323
        if self.cursor > len(stats) - 1:
324
            # Set the cursor position to the latest item
325
            self.cursor = len(stats) - 1
326
327
        stats_list = self._get_stats(stats)
328
        start_line = self._page_max_lines * self._current_page
329
        end_line = start_line + self.get_pagelines(stats_list)
330
        current_page = stats_list[start_line:end_line]
331
332
        # Display table
333
        line = 0
334
        for v in current_page:
335
            # Limit the number of displayed server (see issue #1256)
336
            if line >= stats_max:
337
                continue
338
            # Get server stats
339
            server_stat = {}
340
            for c in column_def:
341
                try:
342
                    server_stat[c[0]] = v[c[0]]
343
                except KeyError as e:
344
                    logger.debug(f"Cannot grab stats {c[0]} from server (KeyError: {e})")
345
                    server_stat[c[0]] = '?'
346
                # Display alias instead of name
347
                try:
348
                    if c[0] == 'alias' and v[c[0]] is not None:
349
                        server_stat['name'] = v[c[0]]
350
                except KeyError:
351
                    pass
352
353
            # Display line for server stats
354
            cpt = 0
355
            xc = x
356
357
            # Is the line selected ?
358
            if line == self.cursor:
359
                # Display cursor
360
                self.term_window.addnstr(y, xc, ">", screen_x - xc, self.colors_list['BOLD'])
361
362
            # Display the line
363
            xc += 2
364
            for c in column_def:
365
                if xc < screen_x and y < screen_y and c[1] is not None:
366
                    # Display server stats
367
                    self.term_window.addnstr(y, xc, format(server_stat[c[0]]), c[2], self.colors_list[v['status']])
368
                    xc += c[2] + self.space_between_column
369
                cpt += 1
370
            # Next line, next server...
371
            y += 1
372
            line += 1
373
374
        return True
375