Test Failed
Push — master ( cc9054...a8608f )
by Nicolas
03:40
created

  A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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