Test Failed
Push — master ( ce0fc3...e09530 )
by Nicolas
03:36
created

GlancesRestfulApi.__init__()   B

Complexity

Conditions 5

Size

Total Lines 72
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 35
nop 3
dl 0
loc 72
rs 8.5733
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
#
2
# This file is part of Glances.
3
#
4
# SPDX-FileCopyrightText: 2024 Nicolas Hennion <[email protected]>
5
#
6
# SPDX-License-Identifier: LGPL-3.0-only
7
#
8
9
"""RestFull API interface class."""
10
11
import os
12
import socket
13
import sys
14
import tempfile
15
import webbrowser
16
from typing import Annotated, Any, Union
17
from urllib.parse import urljoin
18
19
from glances import __apiversion__, __version__
20
from glances.events_list import glances_events
21
from glances.globals import json_dumps
22
from glances.logger import logger
23
from glances.password import GlancesPassword
24
from glances.servers_list import GlancesServersList
25
from glances.servers_list_dynamic import GlancesAutoDiscoverClient
26
from glances.stats import GlancesStats
27
from glances.timer import Timer
28
29
# FastAPI import
30
try:
31
    from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request, status
32
    from fastapi.middleware.cors import CORSMiddleware
33
    from fastapi.middleware.gzip import GZipMiddleware
34
    from fastapi.responses import HTMLResponse, JSONResponse
35
    from fastapi.security import HTTPBasic, HTTPBasicCredentials
36
    from fastapi.staticfiles import StaticFiles
37
    from fastapi.templating import Jinja2Templates
38
except ImportError as e:
39
    logger.critical(f'FastAPI import error: {e}')
40
    logger.critical('Glances cannot start in web server mode.')
41
    sys.exit(2)
42
43
try:
44
    import uvicorn
45
except ImportError:
46
    logger.critical('Uvicorn import error. Glances cannot start in web server mode.')
47
    sys.exit(2)
48
import builtins
49
import contextlib
50
import threading
51
import time
52
53
security = HTTPBasic()
54
55
56
class GlancesJSONResponse(JSONResponse):
57
    """
58
    Glances impl of fastapi's JSONResponse to use internal JSON Serialization features
59
60
    Ref: https://fastapi.tiangolo.com/advanced/custom-response/
61
    """
62
63
    def render(self, content: Any) -> bytes:
64
        return json_dumps(content)
65
66
67
class GlancesUvicornServer(uvicorn.Server):
68
    def install_signal_handlers(self):
69
        pass
70
71
    @contextlib.contextmanager
72
    def run_in_thread(self, timeout=3):
73
        thread = threading.Thread(target=self.run)
74
        thread.start()
75
        try:
76
            chrono = Timer(timeout)
77
            while not self.started and not chrono.finished():
78
                time.sleep(0.5)
79
            # Timeout reached
80
            # Something go wrong...
81
            # The Uvicorn server should be stopped
82
            if not self.started:
83
                self.should_exit = True
84
                thread.join()
85
            yield
86
        finally:
87
            self.should_exit = True
88
            thread.join()
89
90
91
class GlancesRestfulApi:
92
    """This class manages the Restful API server."""
93
94
    API_VERSION = __apiversion__
95
96
    def __init__(self, config=None, args=None):
97
        # Init config
98
        self.config = config
99
100
        # Init args
101
        self.args = args
102
103
        # Init stats
104
        # Will be updated within route
105
        self.stats = None
106
107
        # Init servers list (only for the browser mode)
108
        if self.args.browser:
109
            self.servers_list = GlancesServersList(config=config, args=args)
110
        else:
111
            self.servers_list = None
112
113
        # cached_time is the minimum time interval between stats updates
114
        # i.e. HTTP/RESTful calls will not retrieve updated info until the time
115
        # since last update is passed (will retrieve old cached info instead)
116
        self.timer = Timer(0)
117
118
        # Load configuration file
119
        self.load_config(config)
120
121
        # Set the bind URL
122
        self.bind_url = urljoin(f'http://{self.args.bind_address}:{self.args.port}/', self.url_prefix)
123
124
        # FastAPI Init
125
        if self.args.password:
126
            self._app = FastAPI(default_response_class=GlancesJSONResponse, dependencies=[Depends(self.authentication)])
127
            self._password = GlancesPassword(username=args.username, config=config)
128
129
        else:
130
            self._app = FastAPI(default_response_class=GlancesJSONResponse)
131
            self._password = None
132
133
        # Set path for WebUI
134
        webui_root_path = config.get_value(
135
            'outputs', 'webui_root_path', default=os.path.dirname(os.path.realpath(__file__))
136
        )
137
        if webui_root_path == '':
138
            webui_root_path = os.path.dirname(os.path.realpath(__file__))
139
        self.STATIC_PATH = os.path.join(webui_root_path, 'static/public')
140
        self.TEMPLATE_PATH = os.path.join(webui_root_path, 'static/templates')
141
        self._templates = Jinja2Templates(directory=self.TEMPLATE_PATH)
142
143
        # FastAPI Enable CORS
144
        # https://fastapi.tiangolo.com/tutorial/cors/
145
        self._app.add_middleware(
146
            CORSMiddleware,
147
            # Related to https://github.com/nicolargo/glances/issues/2812
148
            allow_origins=config.get_list_value('outputs', 'cors_origins', default=["*"]),
149
            allow_credentials=config.get_bool_value('outputs', 'cors_credentials', default=True),
150
            allow_methods=config.get_list_value('outputs', 'cors_methods', default=["*"]),
151
            allow_headers=config.get_list_value('outputs', 'cors_headers', default=["*"]),
152
        )
153
154
        # FastAPI Enable GZIP compression
155
        # https://fastapi.tiangolo.com/advanced/middleware/
156
        self._app.add_middleware(GZipMiddleware, minimum_size=1000)
157
158
        # FastAPI Define routes
159
        self._app.include_router(self._router())
160
161
        # Enable auto discovering of the service
162
        self.autodiscover_client = None
163
        if not self.args.disable_autodiscover:
164
            logger.info('Autodiscover is enabled with service name {}'.format(socket.gethostname().split('.', 1)[0]))
165
            self.autodiscover_client = GlancesAutoDiscoverClient(socket.gethostname().split('.', 1)[0], self.args)
166
        else:
167
            logger.info("Glances autodiscover announce is disabled")
168
169
    def load_config(self, config):
170
        """Load the outputs section of the configuration file."""
171
        # Limit the number of processes to display in the WebUI
172
        self.url_prefix = ''
173
        if config is not None and config.has_section('outputs'):
174
            # Max process to display in the WebUI
175
            n = config.get_value('outputs', 'max_processes_display', default=None)
176
            logger.debug(f'Number of processes to display in the WebUI: {n}')
177
            # URL prefix
178
            self.url_prefix = config.get_value('outputs', 'url_prefix', default='')
179
            if self.url_prefix != '':
180
                self.url_prefix = self.url_prefix.rstrip('/')
181
            logger.debug(f'URL prefix: {self.url_prefix}')
182
183
    def __update_stats(self):
184
        # Never update more than 1 time per cached_time
185
        if self.timer.finished():
186
            self.stats.update()
187
            self.timer = Timer(self.args.cached_time)
188
189
    def __update_servers_list(self):
190
        # Never update more than 1 time per cached_time
191
        if self.timer.finished() and self.servers_list is not None:
192
            self.servers_list.update_servers_stats()
193
            self.timer = Timer(self.args.cached_time)
194
195
    def authentication(self, creds: Annotated[HTTPBasicCredentials, Depends(security)]):
196
        """Check if a username/password combination is valid."""
197
        if creds.username == self.args.username:
198
            # check_password and get_hash are (lru) cached to optimize the requests
199
            if self._password.check_password(self.args.password, self._password.get_hash(creds.password)):
200
                return creds.username
201
202
        # If the username/password combination is invalid, return an HTTP 401
203
        raise HTTPException(
204
            status.HTTP_401_UNAUTHORIZED, "Incorrect username or password", {"WWW-Authenticate": "Basic"}
205
        )
206
207
    def _router(self) -> APIRouter:
208
        """Define a custom router for Glances path."""
209
        base_path = f'/api/{self.API_VERSION}'
210
        plugin_path = f"{base_path}/{{plugin}}"
211
212
        # Create the main router
213
        router = APIRouter(prefix=self.url_prefix)
214
215
        # REST API route definition
216
        # ==========================
217
218
        # HEAD
219
        router.add_api_route(f'{base_path}/status', self._api_status, methods=['HEAD', 'GET'])
220
221
        # POST
222
        router.add_api_route(f'{base_path}/events/clear/warning', self._events_clear_warning, methods=['POST'])
223
        router.add_api_route(f'{base_path}/events/clear/all', self._events_clear_all, methods=['POST'])
224
225
        # GET
226
        route_mapping = {
227
            f'{base_path}/config': self._api_config,
228
            f'{base_path}/config/{{section}}': self._api_config_section,
229
            f'{base_path}/config/{{section}}/{{item}}': self._api_config_section_item,
230
            f'{base_path}/args': self._api_args,
231
            f'{base_path}/args/{{item}}': self._api_args_item,
232
            f'{base_path}/help': self._api_help,
233
            f'{base_path}/all': self._api_all,
234
            f'{base_path}/all/limits': self._api_all_limits,
235
            f'{base_path}/all/views': self._api_all_views,
236
            f'{base_path}/pluginslist': self._api_plugins,
237
            f'{base_path}/serverslist': self._api_servers_list,
238
            f'{plugin_path}': self._api,
239
            f'{plugin_path}/history': self._api_history,
240
            f'{plugin_path}/history/{{nb}}': self._api_history,
241
            f'{plugin_path}/top/{{nb}}': self._api_top,
242
            f'{plugin_path}/limits': self._api_limits,
243
            f'{plugin_path}/views': self._api_views,
244
            f'{plugin_path}/{{item}}': self._api_item,
245
            f'{plugin_path}/{{item}}/views': self._api_item_views,
246
            f'{plugin_path}/{{item}}/history': self._api_item_history,
247
            f'{plugin_path}/{{item}}/history/{{nb}}': self._api_item_history,
248
            f'{plugin_path}/{{item}}/description': self._api_item_description,
249
            f'{plugin_path}/{{item}}/unit': self._api_item_unit,
250
            f'{plugin_path}/{{item}}/value/{{value:path}}': self._api_value,
251
            f'{plugin_path}/{{item}}/{{key}}': self._api_key,
252
            f'{plugin_path}/{{item}}/{{key}}/views': self._api_key_views,
253
        }
254
        for path, endpoint in route_mapping.items():
255
            router.add_api_route(path, endpoint)
256
257
        # Browser WEBUI
258
        if self.args.browser:
259
            # Template for the root browser.html file
260
            router.add_api_route('/browser', self._browser, response_class=HTMLResponse)
261
262
            # Statics files
263
            self._app.mount(self.url_prefix + '/static', StaticFiles(directory=self.STATIC_PATH), name="static")
264
            logger.debug(f"The Browser WebUI is enable and got statics files in {self.STATIC_PATH}")
265
266
            bindmsg = f'Glances Browser Web User Interface started on {self.bind_url}browser'
267
            logger.info(bindmsg)
268
            print(bindmsg)
269
270
        # WEBUI
271
        if not self.args.disable_webui:
272
            # Template for the root index.html file
273
            router.add_api_route('/', self._index, response_class=HTMLResponse)
274
275
            # Statics files
276
            self._app.mount(self.url_prefix + '/static', StaticFiles(directory=self.STATIC_PATH), name="static")
277
            logger.debug(f"The WebUI is enable and got statics files in {self.STATIC_PATH}")
278
279
            bindmsg = f'Glances Web User Interface started on {self.bind_url}'
280
            logger.info(bindmsg)
281
            print(bindmsg)
282
        else:
283
            logger.info('The WebUI is disable (--disable-webui)')
284
285
        # Restful API
286
        bindmsg = f'Glances RESTful API Server started on {self.bind_url}api/{self.API_VERSION}'
287
        logger.info(bindmsg)
288
        print(bindmsg)
289
290
        return router
291
292
    def start(self, stats: GlancesStats) -> None:
293
        """Start the bottle."""
294
        # Init stats
295
        self.stats = stats
296
297
        # Init plugin list
298
        self.plugins_list = self.stats.getPluginsList()
299
300
        if self.args.open_web_browser:
301
            # Implementation of the issue #946
302
            # Try to open the Glances Web UI in the default Web browser if:
303
            # 1) --open-web-browser option is used
304
            # 2) Glances standalone mode is running on Windows OS
305
            webbrowser.open(self.bind_url, new=2, autoraise=1)
306
307
        # Start Uvicorn server
308
        self._start_uvicorn()
309
310
    def _start_uvicorn(self):
311
        # Run the Uvicorn Web server
312
        uvicorn_config = uvicorn.Config(
313
            self._app, host=self.args.bind_address, port=self.args.port, access_log=self.args.debug
314
        )
315
        try:
316
            self.uvicorn_server = GlancesUvicornServer(config=uvicorn_config)
317
        except Exception as e:
318
            logger.critical(f'Error: Can not ran Glances Web server ({e})')
319
            self.uvicorn_server = None
320
        else:
321
            with self.uvicorn_server.run_in_thread():
322
                while not self.uvicorn_server.should_exit:
323
                    time.sleep(1)
324
325
    def end(self):
326
        """End the Web server"""
327
        if not self.args.disable_autodiscover and self.autodiscover_client:
328
            self.autodiscover_client.close()
329
        logger.info("Close the Web server")
330
331
    def _index(self, request: Request):
332
        """Return main index.html (/) file.
333
334
        Parameters are available through the request object.
335
        Example: http://localhost:61208/?refresh=5
336
337
        Note: This function is only called the first time the page is loaded.
338
        """
339
        refresh_time = request.query_params.get('refresh', default=max(1, int(self.args.time)))
340
341
        # Update the stat
342
        self.__update_stats()
343
344
        # Display
345
        return self._templates.TemplateResponse("index.html", {"request": request, "refresh_time": refresh_time})
346
347
    def _browser(self, request: Request):
348
        """Return main browser.html (/browser) file.
349
350
        Note: This function is only called the first time the page is loaded.
351
        """
352
        refresh_time = request.query_params.get('refresh', default=max(1, int(self.args.time)))
353
354
        # Display
355
        return self._templates.TemplateResponse("browser.html", {"request": request, "refresh_time": refresh_time})
356
357
    def _api_status(self):
358
        """Glances API RESTful implementation.
359
360
        Return a 200 status code.
361
        This entry point should be used to check the API health.
362
363
        See related issue:  Web server health check endpoint #1988
364
        """
365
366
        return GlancesJSONResponse({'version': __version__})
367
368
    def _events_clear_warning(self):
369
        """Glances API RESTful implementation.
370
371
        Return a 200 status code.
372
373
        It's a post message to clean warning events
374
        """
375
        glances_events.clean()
376
        return GlancesJSONResponse({})
377
378
    def _events_clear_all(self):
379
        """Glances API RESTful implementation.
380
381
        Return a 200 status code.
382
383
        It's a post message to clean all events
384
        """
385
        glances_events.clean(critical=True)
386
        return GlancesJSONResponse({})
387
388
    def _api_help(self):
389
        """Glances API RESTful implementation.
390
391
        Return the help data or 404 error.
392
        """
393
        try:
394
            plist = self.stats.get_plugin("help").get_view_data()
395
        except Exception as e:
396
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get help view data ({str(e)})")
397
398
        return GlancesJSONResponse(plist)
399
400
    def _api_plugins(self):
401
        """Glances API RESTFul implementation.
402
403
        @api {get} /api/%s/pluginslist Get plugins list
404
        @apiVersion 2.0
405
        @apiName pluginslist
406
        @apiGroup plugin
407
408
        @apiSuccess {String[]} Plugins list.
409
410
        @apiSuccessExample Success-Response:
411
            HTTP/1.1 200 OK
412
            [
413
               "load",
414
               "help",
415
               "ip",
416
               "memswap",
417
               "processlist",
418
               ...
419
            ]
420
421
         @apiError Cannot get plugin list.
422
423
         @apiErrorExample Error-Response:
424
            HTTP/1.1 404 Not Found
425
        """
426
        # Update the stat
427
        self.__update_stats()
428
429
        try:
430
            plist = self.plugins_list
431
        except Exception as e:
432
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get plugin list ({str(e)})")
433
434
        return GlancesJSONResponse(plist)
435
436
    def _api_servers_list(self):
437
        """Glances API RESTful implementation.
438
439
        Return the JSON representation of the servers list (for browser mode)
440
        HTTP/200 if OK
441
        """
442
        # Update the servers list (and the stats for all the servers)
443
        self.__update_servers_list()
444
445
        return GlancesJSONResponse(self.servers_list.get_servers_list() if self.servers_list else [])
446
447
    def _api_all(self):
448
        """Glances API RESTful implementation.
449
450
        Return the JSON representation of all the plugins
451
        HTTP/200 if OK
452
        HTTP/400 if plugin is not found
453
        HTTP/404 if others error
454
        """
455
        if self.args.debug:
456
            fname = os.path.join(tempfile.gettempdir(), 'glances-debug.json')
457
            try:
458
                with builtins.open(fname) as f:
459
                    return f.read()
460
            except OSError:
461
                logger.debug(f"Debug file ({fname}) not found")
462
463
        # Update the stat
464
        self.__update_stats()
465
466
        try:
467
            # Get the RAW value of the stat ID
468
            statval = self.stats.getAllAsDict()
469
        except Exception as e:
470
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get stats ({str(e)})")
471
472
        return GlancesJSONResponse(statval)
473
474
    def _api_all_limits(self):
475
        """Glances API RESTful implementation.
476
477
        Return the JSON representation of all the plugins limits
478
        HTTP/200 if OK
479
        HTTP/400 if plugin is not found
480
        HTTP/404 if others error
481
        """
482
        try:
483
            # Get the RAW value of the stat limits
484
            limits = self.stats.getAllLimitsAsDict()
485
        except Exception as e:
486
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get limits ({str(e)})")
487
488
        return GlancesJSONResponse(limits)
489
490
    def _api_all_views(self):
491
        """Glances API RESTful implementation.
492
493
        Return the JSON representation of all the plugins views
494
        HTTP/200 if OK
495
        HTTP/400 if plugin is not found
496
        HTTP/404 if others error
497
        """
498
        try:
499
            # Get the RAW value of the stat view
500
            limits = self.stats.getAllViewsAsDict()
501
        except Exception as e:
502
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get views ({str(e)})")
503
504
        return GlancesJSONResponse(limits)
505
506
    def _api(self, plugin: str):
507
        """Glances API RESTful implementation.
508
509
        Return the JSON representation of a given plugin
510
        HTTP/200 if OK
511
        HTTP/400 if plugin is not found
512
        HTTP/404 if others error
513
        """
514
        self._check_if_plugin_available(plugin)
515
516
        # Update the stat
517
        self.__update_stats()
518
519
        try:
520
            # Get the RAW value of the stat ID
521
            statval = self.stats.get_plugin(plugin).get_raw()
522
        except Exception as e:
523
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get plugin {plugin} ({str(e)})")
524
525
        return GlancesJSONResponse(statval)
526
527
    def _check_if_plugin_available(self, plugin: str) -> None:
528
        if plugin in self.plugins_list:
529
            return
530
531
        raise HTTPException(
532
            status.HTTP_400_BAD_REQUEST, f"Unknown plugin {plugin} (available plugins: {self.plugins_list})"
533
        )
534
535
    def _api_top(self, plugin: str, nb: int = 0):
536
        """Glances API RESTful implementation.
537
538
        Return the JSON representation of a given plugin limited to the top nb items.
539
        It is used to reduce the payload of the HTTP response (example: processlist).
540
541
        HTTP/200 if OK
542
        HTTP/400 if plugin is not found
543
        HTTP/404 if others error
544
        """
545
        self._check_if_plugin_available(plugin)
546
547
        # Update the stat
548
        self.__update_stats()
549
550
        try:
551
            # Get the RAW value of the stat ID
552
            statval = self.stats.get_plugin(plugin).get_raw()
553
        except Exception as e:
554
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get plugin {plugin} ({str(e)})")
555
556
        print(statval)
557
558
        if isinstance(statval, list):
559
            statval = statval[:nb]
560
561
        return GlancesJSONResponse(statval)
562
563
    def _api_history(self, plugin: str, nb: int = 0):
564
        """Glances API RESTful implementation.
565
566
        Return the JSON representation of a given plugin history
567
        Limit to the last nb items (all if nb=0)
568
        HTTP/200 if OK
569
        HTTP/400 if plugin is not found
570
        HTTP/404 if others error
571
        """
572
        self._check_if_plugin_available(plugin)
573
574
        # Update the stat
575
        self.__update_stats()
576
577
        try:
578
            # Get the RAW value of the stat ID
579
            statval = self.stats.get_plugin(plugin).get_raw_history(nb=int(nb))
580
        except Exception as e:
581
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get plugin history {plugin} ({str(e)})")
582
583
        return statval
584
585
    def _api_limits(self, plugin: str):
586
        """Glances API RESTful implementation.
587
588
        Return the JSON limits of a given plugin
589
        HTTP/200 if OK
590
        HTTP/400 if plugin is not found
591
        HTTP/404 if others error
592
        """
593
        self._check_if_plugin_available(plugin)
594
595
        try:
596
            # Get the RAW value of the stat limits
597
            ret = self.stats.get_plugin(plugin).limits
598
        except Exception as e:
599
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get limits for plugin {plugin} ({str(e)})")
600
601
        return GlancesJSONResponse(ret)
602
603
    def _api_views(self, plugin: str):
604
        """Glances API RESTful implementation.
605
606
        Return the JSON views of a given plugin
607
        HTTP/200 if OK
608
        HTTP/400 if plugin is not found
609
        HTTP/404 if others error
610
        """
611
        if plugin not in self.plugins_list:
612
            raise HTTPException(
613
                status.HTTP_400_BAD_REQUEST, f"Unknown plugin {plugin} (available plugins: {self.plugins_list})"
614
            )
615
616
        try:
617
            # Get the RAW value of the stat views
618
            ret = self.stats.get_plugin(plugin).get_views()
619
        except Exception as e:
620
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get views for plugin {plugin} ({str(e)})")
621
622
        return GlancesJSONResponse(ret)
623
624
    def _api_item(self, plugin: str, item: str):
625
        """Glances API RESTful implementation.
626
627
        Return the JSON representation of the couple plugin/item
628
        HTTP/200 if OK
629
        HTTP/400 if plugin is not found
630
        HTTP/404 if others error
631
        """
632
        self._check_if_plugin_available(plugin)
633
634
        # Update the stat
635
        self.__update_stats()
636
637
        try:
638
            # Get the RAW value of the stat views
639
            ret = self.stats.get_plugin(plugin).get_raw_stats_item(item)
640
        except Exception as e:
641
            raise HTTPException(
642
                status.HTTP_404_NOT_FOUND,
643
                f"Cannot get item {item} in plugin {plugin} ({str(e)})",
644
            )
645
646
        return GlancesJSONResponse(ret)
647
648
    def _api_key(self, plugin: str, item: str, key: str):
649
        """Glances API RESTful implementation.
650
651
        Return the JSON representation of  plugin/item/key
652
        HTTP/200 if OK
653
        HTTP/400 if plugin is not found
654
        HTTP/404 if others error
655
        """
656
        self._check_if_plugin_available(plugin)
657
658
        # Update the stat
659
        self.__update_stats()
660
661
        try:
662
            # Get the RAW value of the stat views
663
            ret = self.stats.get_plugin(plugin).get_raw_stats_key(item, key)
664
        except Exception as e:
665
            raise HTTPException(
666
                status.HTTP_404_NOT_FOUND,
667
                f"Cannot get item {item} for key {key} in plugin {plugin} ({str(e)})",
668
            )
669
670
        return GlancesJSONResponse(ret)
671
672
    def _api_item_views(self, plugin: str, item: str):
673
        """Glances API RESTful implementation.
674
675
        Return the JSON view representation of the couple plugin/item
676
        HTTP/200 if OK
677
        HTTP/400 if plugin is not found
678
        HTTP/404 if others error
679
        """
680
        self._check_if_plugin_available(plugin)
681
682
        # Update the stat
683
        self.__update_stats()
684
685
        try:
686
            # Get the RAW value of the stat views
687
            ret = self.stats.get_plugin(plugin).get_views().get(item)
688
        except Exception as e:
689
            raise HTTPException(
690
                status.HTTP_404_NOT_FOUND,
691
                f"Cannot get item {item} in plugin view {plugin} ({str(e)})",
692
            )
693
694
        return GlancesJSONResponse(ret)
695
696
    def _api_key_views(self, plugin: str, item: str, key: str):
697
        """Glances API RESTful implementation.
698
699
        Return the JSON view representation of plugin/item/key
700
        HTTP/200 if OK
701
        HTTP/400 if plugin is not found
702
        HTTP/404 if others error
703
        """
704
        self._check_if_plugin_available(plugin)
705
706
        # Update the stat
707
        self.__update_stats()
708
709
        try:
710
            # Get the RAW value of the stat views
711
            ret = self.stats.get_plugin(plugin).get_views().get(key).get(item)
712
        except Exception as e:
713
            raise HTTPException(
714
                status.HTTP_404_NOT_FOUND,
715
                f"Cannot get item {item} for key {key} in plugin view {plugin} ({str(e)})",
716
            )
717
718
        return GlancesJSONResponse(ret)
719
720
    def _api_item_history(self, plugin: str, item: str, nb: int = 0):
721
        """Glances API RESTful implementation.
722
723
        Return the JSON representation of the couple plugin/history of item
724
        HTTP/200 if OK
725
        HTTP/400 if plugin is not found
726
        HTTP/404 if others error
727
728
        """
729
        self._check_if_plugin_available(plugin)
730
731
        # Update the stat
732
        self.__update_stats()
733
734
        try:
735
            # Get the RAW value of the stat history
736
            ret = self.stats.get_plugin(plugin).get_raw_history(item, nb=nb)
737
        except Exception as e:
738
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get history for plugin {plugin} ({str(e)})")
739
        else:
740
            return GlancesJSONResponse(ret)
741
742
    def _api_item_description(self, plugin: str, item: str):
743
        """Glances API RESTful implementation.
744
745
        Return the JSON representation of the couple plugin/item description
746
        HTTP/200 if OK
747
        HTTP/400 if plugin is not found
748
        HTTP/404 if others error
749
        """
750
        self._check_if_plugin_available(plugin)
751
752
        try:
753
            # Get the description
754
            ret = self.stats.get_plugin(plugin).get_item_info(item, 'description')
755
        except Exception as e:
756
            raise HTTPException(
757
                status.HTTP_404_NOT_FOUND, f"Cannot get {item} description for plugin {plugin} ({str(e)})"
758
            )
759
        else:
760
            return GlancesJSONResponse(ret)
761
762
    def _api_item_unit(self, plugin: str, item: str):
763
        """Glances API RESTful implementation.
764
765
        Return the JSON representation of the couple plugin/item unit
766
        HTTP/200 if OK
767
        HTTP/400 if plugin is not found
768
        HTTP/404 if others error
769
        """
770
        self._check_if_plugin_available(plugin)
771
772
        try:
773
            # Get the unit
774
            ret = self.stats.get_plugin(plugin).get_item_info(item, 'unit')
775
        except Exception as e:
776
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get {item} unit for plugin {plugin} ({str(e)})")
777
        else:
778
            return GlancesJSONResponse(ret)
779
780
    def _api_value(self, plugin: str, item: str, value: Union[str, int, float]):
781
        """Glances API RESTful implementation.
782
783
        Return the process stats (dict) for the given item=value
784
        HTTP/200 if OK
785
        HTTP/400 if plugin is not found
786
        HTTP/404 if others error
787
        """
788
        self._check_if_plugin_available(plugin)
789
790
        # Update the stat
791
        self.__update_stats()
792
793
        try:
794
            # Get the RAW value
795
            ret = self.stats.get_plugin(plugin).get_raw_stats_value(item, value)
796
        except Exception as e:
797
            raise HTTPException(
798
                status.HTTP_404_NOT_FOUND, f"Cannot get {item} = {value} for plugin {plugin} ({str(e)})"
799
            )
800
        else:
801
            return GlancesJSONResponse(ret)
802
803
    def _api_config(self):
804
        """Glances API RESTful implementation.
805
806
        Return the JSON representation of the Glances configuration file
807
        HTTP/200 if OK
808
        HTTP/404 if others error
809
        """
810
        try:
811
            # Get the RAW value of the config' dict
812
            args_json = self.config.as_dict()
813
        except Exception as e:
814
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get config ({str(e)})")
815
        else:
816
            return GlancesJSONResponse(args_json)
817
818
    def _api_config_section(self, section: str):
819
        """Glances API RESTful implementation.
820
821
        Return the JSON representation of the Glances configuration section
822
        HTTP/200 if OK
823
        HTTP/400 if item is not found
824
        HTTP/404 if others error
825
        """
826
        config_dict = self.config.as_dict()
827
        if section not in config_dict:
828
            raise HTTPException(status.HTTP_400_BAD_REQUEST, f"Unknown configuration item {section}")
829
830
        try:
831
            # Get the RAW value of the config' dict
832
            ret_section = config_dict[section]
833
        except Exception as e:
834
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get config section {section} ({str(e)})")
835
836
        return GlancesJSONResponse(ret_section)
837
838
    def _api_config_section_item(self, section: str, item: str):
839
        """Glances API RESTful implementation.
840
841
        Return the JSON representation of the Glances configuration section/item
842
        HTTP/200 if OK
843
        HTTP/400 if item is not found
844
        HTTP/404 if others error
845
        """
846
        config_dict = self.config.as_dict()
847
        if section not in config_dict:
848
            raise HTTPException(status.HTTP_400_BAD_REQUEST, f"Unknown configuration item {section}")
849
850
        try:
851
            # Get the RAW value of the config' dict section
852
            ret_section = config_dict[section]
853
        except Exception as e:
854
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get config section {section} ({str(e)})")
855
856
        try:
857
            # Get the RAW value of the config' dict item
858
            ret_item = ret_section[item]
859
        except Exception as e:
860
            raise HTTPException(
861
                status.HTTP_404_NOT_FOUND, f"Cannot get item {item} in config section {section} ({str(e)})"
862
            )
863
864
        return GlancesJSONResponse(ret_item)
865
866
    def _api_args(self):
867
        """Glances API RESTful implementation.
868
869
        Return the JSON representation of the Glances command line arguments
870
        HTTP/200 if OK
871
        HTTP/404 if others error
872
        """
873
        try:
874
            # Get the RAW value of the args' dict
875
            # Use vars to convert namespace to dict
876
            # Source: https://docs.python.org/%s/library/functions.html#vars
877
            args_json = vars(self.args)
878
        except Exception as e:
879
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get args ({str(e)})")
880
881
        return GlancesJSONResponse(args_json)
882
883
    def _api_args_item(self, item: str):
884
        """Glances API RESTful implementation.
885
886
        Return the JSON representation of the Glances command line arguments item
887
        HTTP/200 if OK
888
        HTTP/400 if item is not found
889
        HTTP/404 if others error
890
        """
891
        if item not in self.args:
892
            raise HTTPException(status.HTTP_400_BAD_REQUEST, f"Unknown argument item {item}")
893
894
        try:
895
            # Get the RAW value of the args' dict
896
            # Use vars to convert namespace to dict
897
            # Source: https://docs.python.org/%s/library/functions.html#vars
898
            args_json = vars(self.args)[item]
899
        except Exception as e:
900
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get args item ({str(e)})")
901
902
        return GlancesJSONResponse(args_json)
903