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 interface class.""" |
||
21 | from __future__ import unicode_literals |
||
22 | |||
23 | import re |
||
24 | import sys |
||
25 | |||
26 | from glances.compat import to_ascii, nativestr, b, u, itervalues |
||
27 | from glances.globals import MACOS, WINDOWS |
||
28 | from glances.logger import logger |
||
29 | from glances.events import glances_events |
||
30 | from glances.processes import glances_processes |
||
31 | from glances.timer import Timer |
||
32 | |||
33 | # Import curses library for "normal" operating system |
||
34 | if not WINDOWS: |
||
35 | try: |
||
36 | import curses |
||
37 | import curses.panel |
||
38 | from curses.textpad import Textbox |
||
39 | except ImportError: |
||
40 | logger.critical("Curses module not found. Glances cannot start in standalone mode.") |
||
41 | sys.exit(1) |
||
42 | |||
43 | |||
44 | class _GlancesCurses(object): |
||
0 ignored issues
–
show
best-practice
introduced
by
Loading history...
|
|||
45 | |||
46 | """This class manages the curses display (and key pressed). |
||
47 | |||
48 | Note: It is a private class, use GlancesCursesClient or GlancesCursesBrowser. |
||
49 | """ |
||
50 | |||
51 | _hotkeys = { |
||
52 | '0': {'switch': 'disable_irix'}, |
||
53 | '1': {'switch': 'percpu'}, |
||
54 | '2': {'switch': 'disable_left_sidebar'}, |
||
55 | '3': {'switch': 'disable_quicklook'}, |
||
56 | '6': {'switch': 'meangpu'}, |
||
57 | '/': {'switch': 'process_short_name'}, |
||
58 | 'A': {'switch': 'disable_amps'}, |
||
59 | 'b': {'switch': 'byte'}, |
||
60 | 'B': {'switch': 'diskio_iops'}, |
||
61 | 'C': {'switch': 'disable_cloud'}, |
||
62 | 'D': {'switch': 'disable_docker'}, |
||
63 | 'd': {'switch': 'disable_diskio'}, |
||
64 | 'F': {'switch': 'fs_free_space'}, |
||
65 | 'g': {'switch': 'generate_graph'}, |
||
66 | 'G': {'switch': 'disable_gpu'}, |
||
67 | 'h': {'switch': 'help_tag'}, |
||
68 | 'I': {'switch': 'disable_ip'}, |
||
69 | 'l': {'switch': 'disable_alert'}, |
||
70 | 'M': {'switch': 'reset_minmax_tag'}, |
||
71 | 'n': {'switch': 'disable_network'}, |
||
72 | 'N': {'switch': 'disable_now'}, |
||
73 | 'P': {'switch': 'disable_ports'}, |
||
74 | 'Q': {'switch': 'enable_irq'}, |
||
75 | 'R': {'switch': 'disable_raid'}, |
||
76 | 's': {'switch': 'disable_sensors'}, |
||
77 | 'S': {'switch': 'sparkline'}, |
||
78 | 'T': {'switch': 'network_sum'}, |
||
79 | 'U': {'switch': 'network_cumul'}, |
||
80 | 'W': {'switch': 'disable_wifi'}, |
||
81 | # Processes sort hotkeys |
||
82 | 'a': {'auto_sort': True, 'sort_key': 'cpu_percent'}, |
||
83 | 'c': {'auto_sort': False, 'sort_key': 'cpu_percent'}, |
||
84 | 'i': {'auto_sort': False, 'sort_key': 'io_counters'}, |
||
85 | 'm': {'auto_sort': False, 'sort_key': 'memory_percent'}, |
||
86 | 'p': {'auto_sort': False, 'sort_key': 'name'}, |
||
87 | 't': {'auto_sort': False, 'sort_key': 'cpu_times'}, |
||
88 | 'u': {'auto_sort': False, 'sort_key': 'username'}, |
||
89 | } |
||
90 | |||
91 | _sort_loop = ['cpu_percent', 'memory_percent', 'username', |
||
92 | 'cpu_times', 'io_counters', 'name'] |
||
93 | |||
94 | # Define top menu |
||
95 | _top = ['quicklook', 'cpu', 'percpu', 'gpu', 'mem', 'memswap', 'load'] |
||
96 | _quicklook_max_width = 68 |
||
97 | |||
98 | # Define left sidebar |
||
99 | _left_sidebar = ['network', 'wifi', 'ports', 'diskio', 'fs', |
||
100 | 'irq', 'folders', 'raid', 'sensors', 'now'] |
||
101 | _left_sidebar_min_width = 23 |
||
102 | _left_sidebar_max_width = 34 |
||
103 | |||
104 | # Define right sidebar |
||
105 | _right_sidebar = ['docker', 'processcount', 'amps', 'processlist', 'alert'] |
||
106 | |||
107 | def __init__(self, config=None, args=None): |
||
108 | # Init |
||
109 | self.config = config |
||
110 | self.args = args |
||
111 | |||
112 | # Init windows positions |
||
113 | self.term_w = 80 |
||
114 | self.term_h = 24 |
||
115 | |||
116 | # Space between stats |
||
117 | self.space_between_column = 3 |
||
118 | self.space_between_line = 2 |
||
119 | |||
120 | # Init the curses screen |
||
121 | self.screen = curses.initscr() |
||
122 | if not self.screen: |
||
123 | logger.critical("Cannot init the curses library.\n") |
||
124 | sys.exit(1) |
||
125 | |||
126 | # Load the 'outputs' section of the configuration file |
||
127 | # - Init the theme (default is black) |
||
128 | self.theme = {'name': 'black'} |
||
129 | |||
130 | # Load configuration file |
||
131 | self.load_config(config) |
||
132 | |||
133 | # Init cursor |
||
134 | self._init_cursor() |
||
135 | |||
136 | # Init the colors |
||
137 | self._init_colors() |
||
138 | |||
139 | # Init main window |
||
140 | self.term_window = self.screen.subwin(0, 0) |
||
141 | |||
142 | # Init edit filter tag |
||
143 | self.edit_filter = False |
||
144 | |||
145 | # Init the process min/max reset |
||
146 | self.args.reset_minmax_tag = False |
||
147 | |||
148 | # Catch key pressed with non blocking mode |
||
149 | self.term_window.keypad(1) |
||
150 | self.term_window.nodelay(1) |
||
151 | self.pressedkey = -1 |
||
152 | |||
153 | # History tag |
||
154 | self._init_history() |
||
155 | |||
156 | def load_config(self, config): |
||
157 | """Load the outputs section of the configuration file.""" |
||
158 | # Load the theme |
||
159 | if config is not None and config.has_section('outputs'): |
||
160 | logger.debug('Read the outputs section in the configuration file') |
||
161 | self.theme['name'] = config.get_value('outputs', 'curse_theme', default='black') |
||
162 | logger.debug('Theme for the curse interface: {}'.format(self.theme['name'])) |
||
163 | |||
164 | def is_theme(self, name): |
||
165 | """Return True if the theme *name* should be used.""" |
||
166 | return getattr(self.args, 'theme_' + name) or self.theme['name'] == name |
||
167 | |||
168 | def _init_history(self): |
||
169 | """Init the history option.""" |
||
170 | |||
171 | self.reset_history_tag = False |
||
172 | |||
173 | def _init_cursor(self): |
||
174 | """Init cursors.""" |
||
175 | |||
176 | if hasattr(curses, 'noecho'): |
||
177 | curses.noecho() |
||
178 | if hasattr(curses, 'cbreak'): |
||
179 | curses.cbreak() |
||
180 | self.set_cursor(0) |
||
181 | |||
182 | def _init_colors(self): |
||
183 | """Init the Curses color layout.""" |
||
184 | |||
185 | # Set curses options |
||
186 | try: |
||
187 | if hasattr(curses, 'start_color'): |
||
188 | curses.start_color() |
||
189 | if hasattr(curses, 'use_default_colors'): |
||
190 | curses.use_default_colors() |
||
191 | except Exception as e: |
||
0 ignored issues
–
show
Catching very general exceptions such as
Exception is usually not recommended.
Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed. So, unless you specifically plan to handle any error, consider adding a more specific exception.
Loading history...
|
|||
192 | logger.warning('Error initializing terminal color ({})'.format(e)) |
||
193 | |||
194 | # Init colors |
||
195 | if self.args.disable_bold: |
||
196 | A_BOLD = 0 |
||
197 | self.args.disable_bg = True |
||
198 | else: |
||
199 | A_BOLD = curses.A_BOLD |
||
200 | |||
201 | self.title_color = A_BOLD |
||
202 | self.title_underline_color = A_BOLD | curses.A_UNDERLINE |
||
203 | self.help_color = A_BOLD |
||
204 | |||
205 | if curses.has_colors(): |
||
206 | # The screen is compatible with a colored design |
||
207 | if self.is_theme('white'): |
||
208 | # White theme: black ==> white |
||
209 | curses.init_pair(1, curses.COLOR_BLACK, -1) |
||
210 | else: |
||
211 | curses.init_pair(1, curses.COLOR_WHITE, -1) |
||
212 | if self.args.disable_bg: |
||
213 | curses.init_pair(2, curses.COLOR_RED, -1) |
||
214 | curses.init_pair(3, curses.COLOR_GREEN, -1) |
||
215 | curses.init_pair(4, curses.COLOR_BLUE, -1) |
||
216 | curses.init_pair(5, curses.COLOR_MAGENTA, -1) |
||
217 | else: |
||
218 | curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_RED) |
||
219 | curses.init_pair(3, curses.COLOR_WHITE, curses.COLOR_GREEN) |
||
220 | curses.init_pair(4, curses.COLOR_WHITE, curses.COLOR_BLUE) |
||
221 | curses.init_pair(5, curses.COLOR_WHITE, curses.COLOR_MAGENTA) |
||
222 | curses.init_pair(6, curses.COLOR_RED, -1) |
||
223 | curses.init_pair(7, curses.COLOR_GREEN, -1) |
||
224 | curses.init_pair(8, curses.COLOR_BLUE, -1) |
||
225 | |||
226 | # Colors text styles |
||
227 | if curses.COLOR_PAIRS > 8: |
||
228 | try: |
||
229 | curses.init_pair(9, curses.COLOR_MAGENTA, -1) |
||
230 | except Exception: |
||
0 ignored issues
–
show
Catching very general exceptions such as
Exception is usually not recommended.
Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed. So, unless you specifically plan to handle any error, consider adding a more specific exception.
Loading history...
|
|||
231 | if self.is_theme('white'): |
||
232 | curses.init_pair(9, curses.COLOR_BLACK, -1) |
||
233 | else: |
||
234 | curses.init_pair(9, curses.COLOR_WHITE, -1) |
||
235 | try: |
||
236 | curses.init_pair(10, curses.COLOR_CYAN, -1) |
||
237 | except Exception: |
||
0 ignored issues
–
show
Catching very general exceptions such as
Exception is usually not recommended.
Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed. So, unless you specifically plan to handle any error, consider adding a more specific exception.
Loading history...
|
|||
238 | if self.is_theme('white'): |
||
239 | curses.init_pair(10, curses.COLOR_BLACK, -1) |
||
240 | else: |
||
241 | curses.init_pair(10, curses.COLOR_WHITE, -1) |
||
242 | |||
243 | self.ifWARNING_color2 = curses.color_pair(9) | A_BOLD |
||
244 | self.ifCRITICAL_color2 = curses.color_pair(6) | A_BOLD |
||
245 | self.filter_color = curses.color_pair(10) | A_BOLD |
||
246 | |||
247 | self.no_color = curses.color_pair(1) |
||
248 | self.default_color = curses.color_pair(3) | A_BOLD |
||
249 | self.nice_color = curses.color_pair(9) |
||
250 | self.cpu_time_color = curses.color_pair(9) |
||
251 | self.ifCAREFUL_color = curses.color_pair(4) | A_BOLD |
||
252 | self.ifWARNING_color = curses.color_pair(5) | A_BOLD |
||
253 | self.ifCRITICAL_color = curses.color_pair(2) | A_BOLD |
||
254 | self.default_color2 = curses.color_pair(7) |
||
255 | self.ifCAREFUL_color2 = curses.color_pair(8) | A_BOLD |
||
256 | |||
257 | else: |
||
258 | # The screen is NOT compatible with a colored design |
||
259 | # switch to B&W text styles |
||
260 | self.no_color = curses.A_NORMAL |
||
261 | self.default_color = curses.A_NORMAL |
||
262 | self.nice_color = A_BOLD |
||
263 | self.cpu_time_color = A_BOLD |
||
264 | self.ifCAREFUL_color = curses.A_UNDERLINE |
||
265 | self.ifWARNING_color = A_BOLD |
||
266 | self.ifCRITICAL_color = curses.A_REVERSE |
||
267 | self.default_color2 = curses.A_NORMAL |
||
268 | self.ifCAREFUL_color2 = curses.A_UNDERLINE |
||
269 | self.ifWARNING_color2 = A_BOLD |
||
270 | self.ifCRITICAL_color2 = curses.A_REVERSE |
||
271 | self.filter_color = A_BOLD |
||
272 | |||
273 | # Define the colors list (hash table) for stats |
||
274 | self.colors_list = { |
||
275 | 'DEFAULT': self.no_color, |
||
276 | 'UNDERLINE': curses.A_UNDERLINE, |
||
277 | 'BOLD': A_BOLD, |
||
278 | 'SORT': A_BOLD, |
||
279 | 'OK': self.default_color2, |
||
280 | 'MAX': self.default_color2 | curses.A_BOLD, |
||
281 | 'FILTER': self.filter_color, |
||
282 | 'TITLE': self.title_color, |
||
283 | 'PROCESS': self.default_color2, |
||
284 | 'STATUS': self.default_color2, |
||
285 | 'NICE': self.nice_color, |
||
286 | 'CPU_TIME': self.cpu_time_color, |
||
287 | 'CAREFUL': self.ifCAREFUL_color2, |
||
288 | 'WARNING': self.ifWARNING_color2, |
||
289 | 'CRITICAL': self.ifCRITICAL_color2, |
||
290 | 'OK_LOG': self.default_color, |
||
291 | 'CAREFUL_LOG': self.ifCAREFUL_color, |
||
292 | 'WARNING_LOG': self.ifWARNING_color, |
||
293 | 'CRITICAL_LOG': self.ifCRITICAL_color, |
||
294 | 'PASSWORD': curses.A_PROTECT |
||
295 | } |
||
296 | |||
297 | def set_cursor(self, value): |
||
298 | """Configure the curse cursor apparence. |
||
299 | |||
300 | 0: invisible |
||
301 | 1: visible |
||
302 | 2: very visible |
||
303 | """ |
||
304 | if hasattr(curses, 'curs_set'): |
||
305 | try: |
||
306 | curses.curs_set(value) |
||
307 | except Exception: |
||
0 ignored issues
–
show
Catching very general exceptions such as
Exception is usually not recommended.
Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed. So, unless you specifically plan to handle any error, consider adding a more specific exception.
Loading history...
|
|||
308 | pass |
||
309 | |||
310 | # def get_key(self, window): |
||
311 | # # Catch ESC key AND numlock key (issue #163) |
||
312 | # keycode = [0, 0] |
||
313 | # keycode[0] = window.getch() |
||
314 | # keycode[1] = window.getch() |
||
315 | # |
||
316 | # if keycode != [-1, -1]: |
||
317 | # logger.debug("Keypressed (code: %s)" % keycode) |
||
318 | # |
||
319 | # if keycode[0] == 27 and keycode[1] != -1: |
||
320 | # # Do not escape on specials keys |
||
321 | # return -1 |
||
322 | # else: |
||
323 | # return keycode[0] |
||
324 | |||
325 | def get_key(self, window): |
||
326 | # @TODO: Check issue #163 |
||
327 | ret = window.getch() |
||
328 | logger.debug("Keypressed (code: %s)" % ret) |
||
0 ignored issues
–
show
|
|||
329 | return ret |
||
330 | |||
331 | def __catch_key(self, return_to_browser=False): |
||
332 | # Catch the pressed key |
||
333 | self.pressedkey = self.get_key(self.term_window) |
||
334 | |||
335 | # Actions (available in the global hotkey dict)... |
||
336 | for hotkey in self._hotkeys: |
||
337 | if self.pressedkey == ord(hotkey) and 'switch' in self._hotkeys[hotkey]: |
||
338 | setattr(self.args, |
||
339 | self._hotkeys[hotkey]['switch'], |
||
340 | not getattr(self.args, |
||
341 | self._hotkeys[hotkey]['switch'])) |
||
342 | if self.pressedkey == ord(hotkey) and 'auto_sort' in self._hotkeys[hotkey]: |
||
343 | setattr(glances_processes, |
||
344 | 'auto_sort', |
||
345 | self._hotkeys[hotkey]['auto_sort']) |
||
346 | if self.pressedkey == ord(hotkey) and 'sort_key' in self._hotkeys[hotkey]: |
||
347 | setattr(glances_processes, |
||
348 | 'sort_key', |
||
349 | self._hotkeys[hotkey]['sort_key']) |
||
350 | |||
351 | # Other actions... |
||
352 | if self.pressedkey == ord('\x1b') or self.pressedkey == ord('q'): |
||
353 | # 'ESC'|'q' > Quit |
||
354 | if return_to_browser: |
||
355 | logger.info("Stop Glances client and return to the browser") |
||
356 | else: |
||
357 | logger.info("Stop Glances (keypressed: {})".format(self.pressedkey)) |
||
358 | elif self.pressedkey == ord('\n'): |
||
359 | # 'ENTER' > Edit the process filter |
||
360 | self.edit_filter = not self.edit_filter |
||
361 | elif self.pressedkey == ord('4'): |
||
362 | self.args.full_quicklook = not self.args.full_quicklook |
||
363 | if self.args.full_quicklook: |
||
364 | self.enable_fullquicklook() |
||
365 | else: |
||
366 | self.disable_fullquicklook() |
||
367 | elif self.pressedkey == ord('5'): |
||
368 | self.args.disable_top = not self.args.disable_top |
||
369 | if self.args.disable_top: |
||
370 | self.disable_top() |
||
371 | else: |
||
372 | self.enable_top() |
||
373 | elif self.pressedkey == ord('e'): |
||
374 | # 'e' > Enable/Disable process extended |
||
375 | self.args.enable_process_extended = not self.args.enable_process_extended |
||
376 | if not self.args.enable_process_extended: |
||
377 | glances_processes.disable_extended() |
||
378 | else: |
||
379 | glances_processes.enable_extended() |
||
380 | elif self.pressedkey == ord('E'): |
||
381 | # 'E' > Erase the process filter |
||
382 | glances_processes.process_filter = None |
||
383 | elif self.pressedkey == ord('f'): |
||
384 | # 'f' > Show/hide fs / folder stats |
||
385 | self.args.disable_fs = not self.args.disable_fs |
||
386 | self.args.disable_folders = not self.args.disable_folders |
||
387 | elif self.pressedkey == ord('w'): |
||
388 | # 'w' > Delete finished warning logs |
||
389 | glances_events.clean() |
||
390 | elif self.pressedkey == ord('x'): |
||
391 | # 'x' > Delete finished warning and critical logs |
||
392 | glances_events.clean(critical=True) |
||
393 | elif self.pressedkey == ord('z'): |
||
394 | # 'z' > Enable or disable processes |
||
395 | self.args.disable_process = not self.args.disable_process |
||
396 | if self.args.disable_process: |
||
397 | glances_processes.disable() |
||
398 | else: |
||
399 | glances_processes.enable() |
||
400 | elif self.pressedkey == curses.KEY_LEFT: |
||
401 | # "<" (left arrow) navigation through process sort |
||
402 | setattr(glances_processes, 'auto_sort', False) |
||
403 | next_sort = (self.loop_position() - 1) % len(self._sort_loop) |
||
404 | glances_processes.sort_key = self._sort_loop[next_sort] |
||
405 | elif self.pressedkey == curses.KEY_RIGHT: |
||
406 | # ">" (right arrow) navigation through process sort |
||
407 | setattr(glances_processes, 'auto_sort', False) |
||
408 | next_sort = (self.loop_position() + 1) % len(self._sort_loop) |
||
409 | glances_processes.sort_key = self._sort_loop[next_sort] |
||
410 | |||
411 | # Return the key code |
||
412 | return self.pressedkey |
||
413 | |||
414 | def loop_position(self): |
||
415 | """Return the current sort in the loop""" |
||
416 | for i, v in enumerate(self._sort_loop): |
||
417 | if v == glances_processes.sort_key: |
||
418 | return i |
||
419 | return 0 |
||
420 | |||
421 | def disable_top(self): |
||
422 | """Disable the top panel""" |
||
423 | for p in ['quicklook', 'cpu', 'gpu', 'mem', 'memswap', 'load']: |
||
424 | setattr(self.args, 'disable_' + p, True) |
||
425 | |||
426 | def enable_top(self): |
||
427 | """Enable the top panel""" |
||
428 | for p in ['quicklook', 'cpu', 'gpu', 'mem', 'memswap', 'load']: |
||
429 | setattr(self.args, 'disable_' + p, False) |
||
430 | |||
431 | def disable_fullquicklook(self): |
||
432 | """Disable the full quicklook mode""" |
||
433 | for p in ['quicklook', 'cpu', 'gpu', 'mem', 'memswap']: |
||
434 | setattr(self.args, 'disable_' + p, False) |
||
435 | |||
436 | def enable_fullquicklook(self): |
||
437 | """Disable the full quicklook mode""" |
||
438 | self.args.disable_quicklook = False |
||
439 | for p in ['cpu', 'gpu', 'mem', 'memswap']: |
||
440 | setattr(self.args, 'disable_' + p, True) |
||
441 | |||
442 | def end(self): |
||
443 | """Shutdown the curses window.""" |
||
444 | if hasattr(curses, 'echo'): |
||
445 | curses.echo() |
||
446 | if hasattr(curses, 'nocbreak'): |
||
447 | curses.nocbreak() |
||
448 | if hasattr(curses, 'curs_set'): |
||
449 | try: |
||
450 | curses.curs_set(1) |
||
451 | except Exception: |
||
0 ignored issues
–
show
Catching very general exceptions such as
Exception is usually not recommended.
Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed. So, unless you specifically plan to handle any error, consider adding a more specific exception.
Loading history...
|
|||
452 | pass |
||
453 | curses.endwin() |
||
454 | |||
455 | def init_line_column(self): |
||
456 | """Init the line and column position for the curses interface.""" |
||
457 | self.init_line() |
||
458 | self.init_column() |
||
459 | |||
460 | def init_line(self): |
||
461 | """Init the line position for the curses interface.""" |
||
462 | self.line = 0 |
||
463 | self.next_line = 0 |
||
464 | |||
465 | def init_column(self): |
||
466 | """Init the column position for the curses interface.""" |
||
467 | self.column = 0 |
||
468 | self.next_column = 0 |
||
469 | |||
470 | def new_line(self): |
||
471 | """New line in the curses interface.""" |
||
472 | self.line = self.next_line |
||
473 | |||
474 | def new_column(self): |
||
475 | """New column in the curses interface.""" |
||
476 | self.column = self.next_column |
||
477 | |||
478 | def __get_stat_display(self, stats, layer): |
||
479 | """Return a dict of dict with all the stats display. |
||
480 | stats: Global stats dict |
||
481 | layer: ~ cs_status |
||
482 | "None": standalone or server mode |
||
483 | "Connected": Client is connected to a Glances server |
||
484 | "SNMP": Client is connected to a SNMP server |
||
485 | "Disconnected": Client is disconnected from the server |
||
486 | |||
487 | :returns: dict of dict |
||
488 | * key: plugin name |
||
489 | * value: dict returned by the get_stats_display Plugin method |
||
490 | """ |
||
491 | ret = {} |
||
492 | |||
493 | for p in stats.getPluginsList(enable=False): |
||
494 | if p == 'quicklook' or p == 'processlist': |
||
495 | # processlist is done later |
||
496 | # because we need to know how many processes could be displayed |
||
497 | continue |
||
498 | |||
499 | # Compute the plugin max size |
||
500 | plugin_max_width = None |
||
501 | if p in self._left_sidebar: |
||
502 | plugin_max_width = max(self._left_sidebar_min_width, |
||
503 | self.screen.getmaxyx()[1] - 105) |
||
504 | plugin_max_width = min(self._left_sidebar_max_width, |
||
505 | plugin_max_width) |
||
506 | |||
507 | # Get the view |
||
508 | ret[p] = stats.get_plugin(p).get_stats_display(args=self.args, |
||
509 | max_width=plugin_max_width) |
||
510 | |||
511 | return ret |
||
512 | |||
513 | def display(self, stats, cs_status=None): |
||
514 | """Display stats on the screen. |
||
515 | |||
516 | stats: Stats database to display |
||
517 | cs_status: |
||
518 | "None": standalone or server mode |
||
519 | "Connected": Client is connected to a Glances server |
||
520 | "SNMP": Client is connected to a SNMP server |
||
521 | "Disconnected": Client is disconnected from the server |
||
522 | |||
523 | Return: |
||
524 | True if the stats have been displayed |
||
525 | False if the help have been displayed |
||
526 | """ |
||
527 | # Init the internal line/column for Glances Curses |
||
528 | self.init_line_column() |
||
529 | |||
530 | # Update the stats messages |
||
531 | ########################### |
||
532 | |||
533 | # Get all the plugins but quicklook and proceslist |
||
534 | self.args.cs_status = cs_status |
||
535 | __stat_display = self.__get_stat_display(stats, layer=cs_status) |
||
536 | |||
537 | # Adapt number of processes to the available space |
||
538 | max_processes_displayed = ( |
||
539 | self.screen.getmaxyx()[0] - 11 - |
||
540 | (0 if 'docker' not in __stat_display else |
||
541 | self.get_stats_display_height(__stat_display["docker"])) - |
||
542 | (0 if 'processcount' not in __stat_display else |
||
543 | self.get_stats_display_height(__stat_display["processcount"])) - |
||
544 | (0 if 'amps' not in __stat_display else |
||
545 | self.get_stats_display_height(__stat_display["amps"])) - |
||
546 | (0 if 'alert' not in __stat_display else |
||
547 | self.get_stats_display_height(__stat_display["alert"]))) |
||
548 | |||
549 | try: |
||
550 | if self.args.enable_process_extended: |
||
551 | max_processes_displayed -= 4 |
||
552 | except AttributeError: |
||
553 | pass |
||
554 | if max_processes_displayed < 0: |
||
555 | max_processes_displayed = 0 |
||
556 | if (glances_processes.max_processes is None or |
||
557 | glances_processes.max_processes != max_processes_displayed): |
||
558 | logger.debug("Set number of displayed processes to {}".format(max_processes_displayed)) |
||
559 | glances_processes.max_processes = max_processes_displayed |
||
560 | |||
561 | # Get the processlist |
||
562 | __stat_display["processlist"] = stats.get_plugin( |
||
563 | 'processlist').get_stats_display(args=self.args) |
||
564 | |||
565 | # Display the stats on the curses interface |
||
566 | ########################################### |
||
567 | |||
568 | # Help screen (on top of the other stats) |
||
569 | if self.args.help_tag: |
||
570 | # Display the stats... |
||
571 | self.display_plugin( |
||
572 | stats.get_plugin('help').get_stats_display(args=self.args)) |
||
573 | # ... and exit |
||
574 | return False |
||
575 | |||
576 | # ===================================== |
||
577 | # Display first line (system+ip+uptime) |
||
578 | # Optionnaly: Cloud on second line |
||
579 | # ===================================== |
||
580 | self.__display_header(__stat_display) |
||
581 | |||
582 | # ============================================================== |
||
583 | # Display second line (<SUMMARY>+CPU|PERCPU+<GPU>+LOAD+MEM+SWAP) |
||
584 | # ============================================================== |
||
585 | self.__display_top(__stat_display, stats) |
||
586 | |||
587 | # ================================================================== |
||
588 | # Display left sidebar (NETWORK+PORTS+DISKIO+FS+SENSORS+Current time) |
||
589 | # ================================================================== |
||
590 | self.__display_left(__stat_display) |
||
591 | |||
592 | # ==================================== |
||
593 | # Display right stats (process and co) |
||
594 | # ==================================== |
||
595 | self.__display_right(__stat_display) |
||
596 | |||
597 | # ===================== |
||
598 | # Others popup messages |
||
599 | # ===================== |
||
600 | |||
601 | # Display edit filter popup |
||
602 | # Only in standalone mode (cs_status is None) |
||
603 | if self.edit_filter and cs_status is None: |
||
604 | new_filter = self.display_popup( |
||
605 | 'Process filter pattern: \n\n' + |
||
606 | 'Examples:\n' + |
||
607 | '- python\n' + |
||
608 | '- .*python.*\n' + |
||
609 | '- /usr/lib.*\n' + |
||
610 | '- name:.*nautilus.*\n' + |
||
611 | '- cmdline:.*glances.*\n' + |
||
612 | '- username:nicolargo\n' + |
||
613 | '- username:^root ', |
||
614 | is_input=True, |
||
615 | input_value=glances_processes.process_filter_input) |
||
616 | glances_processes.process_filter = new_filter |
||
617 | elif self.edit_filter and cs_status is not None: |
||
618 | self.display_popup('Process filter only available in standalone mode') |
||
619 | self.edit_filter = False |
||
620 | |||
621 | # Display graph generation popup |
||
622 | if self.args.generate_graph: |
||
623 | self.display_popup('Generate graph in {}'.format(self.args.export_graph_path)) |
||
624 | |||
625 | return True |
||
626 | |||
627 | def __display_header(self, stat_display): |
||
628 | """Display the firsts lines (header) in the Curses interface. |
||
629 | |||
630 | system + ip + uptime |
||
631 | (cloud) |
||
632 | """ |
||
633 | # First line |
||
634 | self.new_line() |
||
635 | self.space_between_column = 0 |
||
636 | l_uptime = 1 |
||
637 | for i in ['system', 'ip', 'uptime']: |
||
638 | if i in stat_display: |
||
639 | l_uptime += self.get_stats_display_width(stat_display[i]) |
||
640 | self.display_plugin( |
||
641 | stat_display["system"], |
||
642 | display_optional=(self.screen.getmaxyx()[1] >= l_uptime)) |
||
643 | self.space_between_column = 3 |
||
644 | self.new_column() |
||
645 | self.display_plugin(stat_display["ip"]) |
||
646 | self.new_column() |
||
647 | self.display_plugin( |
||
648 | stat_display["uptime"], |
||
649 | add_space=-(self.get_stats_display_width(stat_display["cloud"]) != 0)) |
||
650 | # Second line (optional) |
||
651 | self.init_column() |
||
652 | self.new_line() |
||
653 | self.display_plugin(stat_display["cloud"]) |
||
654 | |||
655 | def __display_top(self, stat_display, stats): |
||
656 | """Display the second line in the Curses interface. |
||
657 | |||
658 | <QUICKLOOK> + CPU|PERCPU + <GPU> + MEM + SWAP + LOAD |
||
659 | """ |
||
660 | self.init_column() |
||
661 | self.new_line() |
||
662 | |||
663 | # Init quicklook |
||
664 | stat_display['quicklook'] = {'msgdict': []} |
||
665 | |||
666 | # Dict for plugins width |
||
667 | plugin_widths = {} |
||
668 | for p in self._top: |
||
669 | plugin_widths[p] = self.get_stats_display_width(stat_display.get(p, 0)) if hasattr(self.args, 'disable_' + p) else 0 |
||
670 | |||
671 | # Width of all plugins |
||
672 | stats_width = sum(itervalues(plugin_widths)) |
||
673 | |||
674 | # Number of plugin but quicklook |
||
675 | stats_number = sum([int(stat_display[p]['msgdict'] != []) for p in self._top if not getattr(self.args, 'disable_' + p)]) |
||
676 | |||
677 | if not self.args.disable_quicklook: |
||
678 | # Quick look is in the place ! |
||
679 | if self.args.full_quicklook: |
||
680 | quicklook_width = self.screen.getmaxyx()[1] - (stats_width + 8 + stats_number * self.space_between_column) |
||
681 | else: |
||
682 | quicklook_width = min(self.screen.getmaxyx()[1] - (stats_width + 8 + stats_number * self.space_between_column), |
||
683 | self._quicklook_max_width - 5) |
||
684 | try: |
||
685 | stat_display["quicklook"] = stats.get_plugin( |
||
686 | 'quicklook').get_stats_display(max_width=quicklook_width, args=self.args) |
||
687 | except AttributeError as e: |
||
688 | logger.debug("Quicklook plugin not available (%s)" % e) |
||
0 ignored issues
–
show
|
|||
689 | else: |
||
690 | plugin_widths['quicklook'] = self.get_stats_display_width(stat_display["quicklook"]) |
||
691 | stats_width = sum(itervalues(plugin_widths)) + 1 |
||
692 | self.space_between_column = 1 |
||
693 | self.display_plugin(stat_display["quicklook"]) |
||
694 | self.new_column() |
||
695 | |||
696 | # Compute spaces between plugins |
||
697 | # Note: Only one space between Quicklook and others |
||
698 | plugin_display_optional = {} |
||
699 | for p in self._top: |
||
700 | plugin_display_optional[p] = True |
||
701 | if stats_number > 1: |
||
702 | self.space_between_column = max(1, int((self.screen.getmaxyx()[1] - stats_width) / (stats_number - 1))) |
||
703 | for p in ['mem', 'cpu']: |
||
704 | # No space ? Remove optional stats |
||
705 | if self.space_between_column < 3: |
||
706 | plugin_display_optional[p] = False |
||
707 | plugin_widths[p] = self.get_stats_display_width(stat_display[p], without_option=True) if hasattr(self.args, 'disable_' + p) else 0 |
||
708 | stats_width = sum(itervalues(plugin_widths)) + 1 |
||
709 | self.space_between_column = max(1, int((self.screen.getmaxyx()[1] - stats_width) / (stats_number - 1))) |
||
710 | else: |
||
711 | self.space_between_column = 0 |
||
712 | |||
713 | # Display CPU, MEM, SWAP and LOAD |
||
714 | for p in self._top: |
||
715 | if p == 'quicklook': |
||
716 | continue |
||
717 | if p in stat_display: |
||
718 | self.display_plugin(stat_display[p], |
||
719 | display_optional=plugin_display_optional[p]) |
||
720 | if p is not 'load': |
||
721 | # Skip last column |
||
722 | self.new_column() |
||
723 | |||
724 | # Space between column |
||
725 | self.space_between_column = 3 |
||
726 | |||
727 | # Backup line position |
||
728 | self.saved_line = self.next_line |
||
729 | |||
730 | def __display_left(self, stat_display): |
||
731 | """Display the left sidebar in the Curses interface.""" |
||
732 | self.init_column() |
||
733 | |||
734 | if self.args.disable_left_sidebar: |
||
735 | return |
||
736 | |||
737 | for p in self._left_sidebar: |
||
738 | if ((hasattr(self.args, 'enable_' + p) or |
||
739 | hasattr(self.args, 'disable_' + p)) and p in stat_display): |
||
740 | self.new_line() |
||
741 | self.display_plugin(stat_display[p]) |
||
742 | |||
743 | def __display_right(self, stat_display): |
||
744 | """Display the right sidebar in the Curses interface. |
||
745 | |||
746 | docker + processcount + amps + processlist + alert |
||
747 | """ |
||
748 | # Do not display anything if space is not available... |
||
749 | if self.screen.getmaxyx()[1] < self._left_sidebar_min_width: |
||
750 | return |
||
751 | |||
752 | # Restore line position |
||
753 | self.next_line = self.saved_line |
||
754 | |||
755 | # Display right sidebar |
||
756 | self.new_column() |
||
757 | for p in self._right_sidebar: |
||
758 | if ((hasattr(self.args, 'enable_' + p) or |
||
759 | hasattr(self.args, 'disable_' + p)) and p in stat_display): |
||
760 | if p not in p: |
||
761 | # Catch for issue #1470 |
||
762 | continue |
||
763 | self.new_line() |
||
764 | if p == 'processlist': |
||
765 | self.display_plugin(stat_display['processlist'], |
||
766 | display_optional=(self.screen.getmaxyx()[1] > 102), |
||
767 | display_additional=(not MACOS), |
||
768 | max_y=(self.screen.getmaxyx()[0] - self.get_stats_display_height(stat_display['alert']) - 2)) |
||
769 | else: |
||
770 | self.display_plugin(stat_display[p]) |
||
771 | |||
772 | def display_popup(self, message, |
||
0 ignored issues
–
show
|
|||
773 | size_x=None, size_y=None, |
||
774 | duration=3, |
||
775 | is_input=False, |
||
776 | input_size=30, |
||
777 | input_value=None): |
||
778 | """ |
||
779 | Display a centered popup. |
||
780 | |||
781 | If is_input is False: |
||
782 | Display a centered popup with the given message during duration seconds |
||
783 | If size_x and size_y: set the popup size |
||
784 | else set it automatically |
||
785 | Return True if the popup could be displayed |
||
786 | |||
787 | If is_input is True: |
||
788 | Display a centered popup with the given message and a input field |
||
789 | If size_x and size_y: set the popup size |
||
790 | else set it automatically |
||
791 | Return the input string or None if the field is empty |
||
792 | """ |
||
793 | # Center the popup |
||
794 | sentence_list = message.split('\n') |
||
795 | if size_x is None: |
||
796 | size_x = len(max(sentence_list, key=len)) + 4 |
||
797 | # Add space for the input field |
||
798 | if is_input: |
||
799 | size_x += input_size |
||
800 | if size_y is None: |
||
801 | size_y = len(sentence_list) + 4 |
||
802 | screen_x = self.screen.getmaxyx()[1] |
||
803 | screen_y = self.screen.getmaxyx()[0] |
||
804 | if size_x > screen_x or size_y > screen_y: |
||
805 | # No size to display the popup => abord |
||
806 | return False |
||
807 | pos_x = int((screen_x - size_x) / 2) |
||
808 | pos_y = int((screen_y - size_y) / 2) |
||
809 | |||
810 | # Create the popup |
||
811 | popup = curses.newwin(size_y, size_x, pos_y, pos_x) |
||
812 | |||
813 | # Fill the popup |
||
814 | popup.border() |
||
815 | |||
816 | # Add the message |
||
817 | for y, m in enumerate(message.split('\n')): |
||
818 | popup.addnstr(2 + y, 2, m, len(m)) |
||
819 | |||
820 | if is_input and not WINDOWS: |
||
821 | # Create a subwindow for the text field |
||
822 | subpop = popup.derwin(1, input_size, 2, 2 + len(m)) |
||
823 | subpop.attron(self.colors_list['FILTER']) |
||
824 | # Init the field with the current value |
||
825 | if input_value is not None: |
||
826 | subpop.addnstr(0, 0, input_value, len(input_value)) |
||
827 | # Display the popup |
||
828 | popup.refresh() |
||
829 | subpop.refresh() |
||
830 | # Create the textbox inside the subwindows |
||
831 | self.set_cursor(2) |
||
832 | self.term_window.keypad(1) |
||
833 | textbox = GlancesTextbox(subpop, insert_mode=False) |
||
834 | textbox.edit() |
||
835 | self.set_cursor(0) |
||
836 | self.term_window.keypad(0) |
||
837 | if textbox.gather() != '': |
||
838 | logger.debug( |
||
0 ignored issues
–
show
|
|||
839 | "User enters the following string: %s" % textbox.gather()) |
||
840 | return textbox.gather()[:-1] |
||
841 | else: |
||
842 | logger.debug("User centers an empty string") |
||
843 | return None |
||
844 | else: |
||
845 | # Display the popup |
||
846 | popup.refresh() |
||
847 | self.wait(duration * 1000) |
||
848 | return True |
||
849 | |||
850 | def display_plugin(self, plugin_stats, |
||
0 ignored issues
–
show
|
|||
851 | display_optional=True, |
||
852 | display_additional=True, |
||
853 | max_y=65535, |
||
854 | add_space=0): |
||
855 | """Display the plugin_stats on the screen. |
||
856 | |||
857 | If display_optional=True display the optional stats |
||
858 | If display_additional=True display additionnal stats |
||
859 | max_y: do not display line > max_y |
||
860 | add_space: add x space (line) after the plugin |
||
861 | """ |
||
862 | # Exit if: |
||
863 | # - the plugin_stats message is empty |
||
864 | # - the display tag = False |
||
865 | if plugin_stats is None or not plugin_stats['msgdict'] or not plugin_stats['display']: |
||
866 | # Exit |
||
867 | return 0 |
||
868 | |||
869 | # Get the screen size |
||
870 | screen_x = self.screen.getmaxyx()[1] |
||
871 | screen_y = self.screen.getmaxyx()[0] |
||
872 | |||
873 | # Set the upper/left position of the message |
||
874 | if plugin_stats['align'] == 'right': |
||
875 | # Right align (last column) |
||
876 | display_x = screen_x - self.get_stats_display_width(plugin_stats) |
||
877 | else: |
||
878 | display_x = self.column |
||
879 | if plugin_stats['align'] == 'bottom': |
||
880 | # Bottom (last line) |
||
881 | display_y = screen_y - self.get_stats_display_height(plugin_stats) |
||
882 | else: |
||
883 | display_y = self.line |
||
884 | |||
885 | # Display |
||
886 | x = display_x |
||
887 | x_max = x |
||
888 | y = display_y |
||
889 | for m in plugin_stats['msgdict']: |
||
890 | # New line |
||
891 | if m['msg'].startswith('\n'): |
||
892 | # Go to the next line |
||
893 | y += 1 |
||
894 | # Return to the first column |
||
895 | x = display_x |
||
896 | continue |
||
897 | # Do not display outside the screen |
||
898 | if x < 0: |
||
899 | continue |
||
900 | if not m['splittable'] and (x + len(m['msg']) > screen_x): |
||
901 | continue |
||
902 | if y < 0 or (y + 1 > screen_y) or (y > max_y): |
||
903 | break |
||
904 | # If display_optional = False do not display optional stats |
||
905 | if not display_optional and m['optional']: |
||
906 | continue |
||
907 | # If display_additional = False do not display additional stats |
||
908 | if not display_additional and m['additional']: |
||
909 | continue |
||
910 | # Is it possible to display the stat with the current screen size |
||
911 | # !!! Crach if not try/except... Why ??? |
||
912 | try: |
||
913 | self.term_window.addnstr(y, x, |
||
914 | m['msg'], |
||
915 | # Do not disply outside the screen |
||
916 | screen_x - x, |
||
917 | self.colors_list[m['decoration']]) |
||
918 | except Exception: |
||
0 ignored issues
–
show
Catching very general exceptions such as
Exception is usually not recommended.
Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed. So, unless you specifically plan to handle any error, consider adding a more specific exception.
Loading history...
|
|||
919 | pass |
||
920 | else: |
||
921 | # New column |
||
922 | # Python 2: we need to decode to get real screen size because |
||
923 | # UTF-8 special tree chars occupy several bytes. |
||
924 | # Python 3: strings are strings and bytes are bytes, all is |
||
925 | # good. |
||
926 | try: |
||
927 | x += len(u(m['msg'])) |
||
928 | except UnicodeDecodeError: |
||
929 | # Quick and dirty hack for issue #745 |
||
930 | pass |
||
931 | if x > x_max: |
||
932 | x_max = x |
||
933 | |||
934 | # Compute the next Glances column/line position |
||
935 | self.next_column = max( |
||
936 | self.next_column, x_max + self.space_between_column) |
||
937 | self.next_line = max(self.next_line, y + self.space_between_line) |
||
938 | |||
939 | # Have empty lines after the plugins |
||
940 | self.next_line += add_space |
||
941 | |||
942 | def erase(self): |
||
943 | """Erase the content of the screen.""" |
||
944 | self.term_window.erase() |
||
945 | |||
946 | def flush(self, stats, cs_status=None): |
||
947 | """Clear and update the screen. |
||
948 | |||
949 | stats: Stats database to display |
||
950 | cs_status: |
||
951 | "None": standalone or server mode |
||
952 | "Connected": Client is connected to the server |
||
953 | "Disconnected": Client is disconnected from the server |
||
954 | """ |
||
955 | self.erase() |
||
956 | self.display(stats, cs_status=cs_status) |
||
957 | |||
958 | def update(self, |
||
959 | stats, |
||
960 | duration=3, |
||
961 | cs_status=None, |
||
962 | return_to_browser=False): |
||
963 | """Update the screen. |
||
964 | |||
965 | INPUT |
||
966 | stats: Stats database to display |
||
967 | duration: duration of the loop |
||
968 | cs_status: |
||
969 | "None": standalone or server mode |
||
970 | "Connected": Client is connected to the server |
||
971 | "Disconnected": Client is disconnected from the server |
||
972 | return_to_browser: |
||
973 | True: Do not exist, return to the browser list |
||
974 | False: Exit and return to the shell |
||
975 | |||
976 | OUTPUT |
||
977 | True: Exit key has been pressed |
||
978 | False: Others cases... |
||
979 | """ |
||
980 | # Flush display |
||
981 | self.flush(stats, cs_status=cs_status) |
||
982 | |||
983 | # If the duration is < 0 (update + export time > refresh_time) |
||
984 | # Then display the interface and log a message |
||
985 | if duration <= 0: |
||
986 | logger.warning('Update and export time higher than refresh_time.') |
||
987 | duration = 0.1 |
||
988 | |||
989 | # Wait duration (in s) time |
||
990 | exitkey = False |
||
991 | countdown = Timer(duration) |
||
992 | # Set the default timeout (in ms) for the getch method |
||
993 | self.term_window.timeout(int(duration * 1000)) |
||
994 | while not countdown.finished() and not exitkey: |
||
995 | # Getkey |
||
996 | pressedkey = self.__catch_key(return_to_browser=return_to_browser) |
||
997 | # Is it an exit key ? |
||
998 | exitkey = (pressedkey == ord('\x1b') or pressedkey == ord('q')) |
||
999 | if not exitkey and pressedkey > -1: |
||
1000 | # Redraw display |
||
1001 | self.flush(stats, cs_status=cs_status) |
||
1002 | # Overwrite the timeout with the countdown |
||
1003 | self.term_window.timeout(int(countdown.get() * 1000)) |
||
1004 | |||
1005 | return exitkey |
||
1006 | |||
1007 | def wait(self, delay=100): |
||
1008 | """Wait delay in ms""" |
||
1009 | curses.napms(100) |
||
1010 | |||
1011 | def get_stats_display_width(self, curse_msg, without_option=False): |
||
1012 | """Return the width of the formatted curses message.""" |
||
1013 | try: |
||
1014 | if without_option: |
||
1015 | # Size without options |
||
1016 | c = len(max(''.join([(u(u(nativestr(i['msg'])).encode('ascii', 'replace')) if not i['optional'] else "") |
||
1017 | for i in curse_msg['msgdict']]).split('\n'), key=len)) |
||
1018 | else: |
||
1019 | # Size with all options |
||
1020 | c = len(max(''.join([u(u(nativestr(i['msg'])).encode('ascii', 'replace')) |
||
1021 | for i in curse_msg['msgdict']]).split('\n'), key=len)) |
||
1022 | except Exception as e: |
||
0 ignored issues
–
show
Catching very general exceptions such as
Exception is usually not recommended.
Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed. So, unless you specifically plan to handle any error, consider adding a more specific exception.
Loading history...
|
|||
1023 | logger.debug('ERROR: Can not compute plugin width ({})'.format(e)) |
||
1024 | return 0 |
||
1025 | else: |
||
1026 | return c |
||
1027 | |||
1028 | def get_stats_display_height(self, curse_msg): |
||
1029 | r"""Return the height of the formatted curses message. |
||
1030 | |||
1031 | The height is defined by the number of '\n' (new line). |
||
1032 | """ |
||
1033 | try: |
||
1034 | c = [i['msg'] for i in curse_msg['msgdict']].count('\n') |
||
1035 | except Exception as e: |
||
0 ignored issues
–
show
Catching very general exceptions such as
Exception is usually not recommended.
Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed. So, unless you specifically plan to handle any error, consider adding a more specific exception.
Loading history...
|
|||
1036 | logger.debug('ERROR: Can not compute plugin height ({})'.format(e)) |
||
1037 | return 0 |
||
1038 | else: |
||
1039 | return c + 1 |
||
1040 | |||
1041 | |||
1042 | class GlancesCursesStandalone(_GlancesCurses): |
||
1043 | |||
1044 | """Class for the Glances curse standalone.""" |
||
1045 | |||
1046 | pass |
||
1047 | |||
1048 | |||
1049 | class GlancesCursesClient(_GlancesCurses): |
||
1050 | |||
1051 | """Class for the Glances curse client.""" |
||
1052 | |||
1053 | pass |
||
1054 | |||
1055 | |||
1056 | if not WINDOWS: |
||
1057 | class GlancesTextbox(Textbox, object): |
||
1058 | |||
1059 | def __init__(self, *args, **kwargs): |
||
1060 | super(GlancesTextbox, self).__init__(*args, **kwargs) |
||
1061 | |||
1062 | def do_command(self, ch): |
||
1063 | if ch == 10: # Enter |
||
1064 | return 0 |
||
1065 | if ch == 127: # Back |
||
1066 | return 8 |
||
1067 | return super(GlancesTextbox, self).do_command(ch) |
||
1068 |