Test Failed
Push — develop ( d0629e...eebab0 )
by Nicolas
03:11
created

GlancesRestfulApi._api_item_views()   A

Complexity

Conditions 2

Size

Total Lines 23
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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