Test Failed
Push — develop ( a9e382...15d993 )
by Nicolas
02:28
created

GlancesRestfulApi._api_config_section_item()   A

Complexity

Conditions 4

Size

Total Lines 27
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 14
nop 3
dl 0
loc 27
rs 9.7
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() and self.servers_list is not None:
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}}/{{key}}': self._api_key,
250
            f'{plugin_path}/{{item}}/{{key}}/views': self._api_key_views,
251
            f'{plugin_path}/{{item}}/{{value:path}}': self._api_value,
252
        }
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 _api_help(self):
369
        """Glances API RESTful implementation.
370
371
        Return the help data or 404 error.
372
        """
373
        try:
374
            plist = self.stats.get_plugin("help").get_view_data()
375
        except Exception as e:
376
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get help view data ({str(e)})")
377
378
        return GlancesJSONResponse(plist)
379
380
    def _api_plugins(self):
381
        """Glances API RESTFul implementation.
382
383
        @api {get} /api/%s/pluginslist Get plugins list
384
        @apiVersion 2.0
385
        @apiName pluginslist
386
        @apiGroup plugin
387
388
        @apiSuccess {String[]} Plugins list.
389
390
        @apiSuccessExample Success-Response:
391
            HTTP/1.1 200 OK
392
            [
393
               "load",
394
               "help",
395
               "ip",
396
               "memswap",
397
               "processlist",
398
               ...
399
            ]
400
401
         @apiError Cannot get plugin list.
402
403
         @apiErrorExample Error-Response:
404
            HTTP/1.1 404 Not Found
405
        """
406
        # Update the stat
407
        self.__update_stats()
408
409
        try:
410
            plist = self.plugins_list
411
        except Exception as e:
412
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get plugin list ({str(e)})")
413
414
        return GlancesJSONResponse(plist)
415
416
    def _api_servers_list(self):
417
        """Glances API RESTful implementation.
418
419
        Return the JSON representation of the servers list (for browser mode)
420
        HTTP/200 if OK
421
        """
422
        # Update the servers list (and the stats for all the servers)
423
        self.__update_servers_list()
424
425
        return GlancesJSONResponse(self.servers_list.get_servers_list() if self.servers_list else [])
426
427
    def _api_all(self):
428
        """Glances API RESTful implementation.
429
430
        Return the JSON representation of all the plugins
431
        HTTP/200 if OK
432
        HTTP/400 if plugin is not found
433
        HTTP/404 if others error
434
        """
435
        if self.args.debug:
436
            fname = os.path.join(tempfile.gettempdir(), 'glances-debug.json')
437
            try:
438
                with builtins.open(fname) as f:
439
                    return f.read()
440
            except OSError:
441
                logger.debug(f"Debug file ({fname}) not found")
442
443
        # Update the stat
444
        self.__update_stats()
445
446
        try:
447
            # Get the RAW value of the stat ID
448
            statval = self.stats.getAllAsDict()
449
        except Exception as e:
450
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get stats ({str(e)})")
451
452
        return GlancesJSONResponse(statval)
453
454
    def _api_all_limits(self):
455
        """Glances API RESTful implementation.
456
457
        Return the JSON representation of all the plugins limits
458
        HTTP/200 if OK
459
        HTTP/400 if plugin is not found
460
        HTTP/404 if others error
461
        """
462
        try:
463
            # Get the RAW value of the stat limits
464
            limits = self.stats.getAllLimitsAsDict()
465
        except Exception as e:
466
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get limits ({str(e)})")
467
468
        return GlancesJSONResponse(limits)
469
470
    def _api_all_views(self):
471
        """Glances API RESTful implementation.
472
473
        Return the JSON representation of all the plugins views
474
        HTTP/200 if OK
475
        HTTP/400 if plugin is not found
476
        HTTP/404 if others error
477
        """
478
        try:
479
            # Get the RAW value of the stat view
480
            limits = self.stats.getAllViewsAsDict()
481
        except Exception as e:
482
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get views ({str(e)})")
483
484
        return GlancesJSONResponse(limits)
485
486
    def _api(self, plugin: str):
487
        """Glances API RESTful implementation.
488
489
        Return the JSON representation of a given plugin
490
        HTTP/200 if OK
491
        HTTP/400 if plugin is not found
492
        HTTP/404 if others error
493
        """
494
        self._check_if_plugin_available(plugin)
495
496
        # Update the stat
497
        self.__update_stats()
498
499
        try:
500
            # Get the RAW value of the stat ID
501
            statval = self.stats.get_plugin(plugin).get_raw()
502
        except Exception as e:
503
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get plugin {plugin} ({str(e)})")
504
505
        return GlancesJSONResponse(statval)
506
507
    def _check_if_plugin_available(self, plugin: str) -> None:
508
        if plugin in self.plugins_list:
509
            return
510
511
        raise HTTPException(
512
            status.HTTP_400_BAD_REQUEST, f"Unknown plugin {plugin} (available plugins: {self.plugins_list})"
513
        )
514
515
    def _api_top(self, plugin: str, nb: int = 0):
516
        """Glances API RESTful implementation.
517
518
        Return the JSON representation of a given plugin limited to the top nb items.
519
        It is used to reduce the payload of the HTTP response (example: processlist).
520
521
        HTTP/200 if OK
522
        HTTP/400 if plugin is not found
523
        HTTP/404 if others error
524
        """
525
        self._check_if_plugin_available(plugin)
526
527
        # Update the stat
528
        self.__update_stats()
529
530
        try:
531
            # Get the RAW value of the stat ID
532
            statval = self.stats.get_plugin(plugin).get_raw()
533
        except Exception as e:
534
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get plugin {plugin} ({str(e)})")
535
536
        print(statval)
537
538
        if isinstance(statval, list):
539
            statval = statval[:nb]
540
541
        return GlancesJSONResponse(statval)
542
543
    def _api_history(self, plugin: str, nb: int = 0):
544
        """Glances API RESTful implementation.
545
546
        Return the JSON representation of a given plugin history
547
        Limit to the last nb items (all if nb=0)
548
        HTTP/200 if OK
549
        HTTP/400 if plugin is not found
550
        HTTP/404 if others error
551
        """
552
        self._check_if_plugin_available(plugin)
553
554
        # Update the stat
555
        self.__update_stats()
556
557
        try:
558
            # Get the RAW value of the stat ID
559
            statval = self.stats.get_plugin(plugin).get_raw_history(nb=int(nb))
560
        except Exception as e:
561
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get plugin history {plugin} ({str(e)})")
562
563
        return statval
564
565
    def _api_limits(self, plugin: str):
566
        """Glances API RESTful implementation.
567
568
        Return the JSON limits of a given plugin
569
        HTTP/200 if OK
570
        HTTP/400 if plugin is not found
571
        HTTP/404 if others error
572
        """
573
        self._check_if_plugin_available(plugin)
574
575
        try:
576
            # Get the RAW value of the stat limits
577
            ret = self.stats.get_plugin(plugin).limits
578
        except Exception as e:
579
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get limits for plugin {plugin} ({str(e)})")
580
581
        return GlancesJSONResponse(ret)
582
583
    def _api_views(self, plugin: str):
584
        """Glances API RESTful implementation.
585
586
        Return the JSON views of a given plugin
587
        HTTP/200 if OK
588
        HTTP/400 if plugin is not found
589
        HTTP/404 if others error
590
        """
591
        if plugin not in self.plugins_list:
592
            raise HTTPException(
593
                status.HTTP_400_BAD_REQUEST, f"Unknown plugin {plugin} (available plugins: {self.plugins_list})"
594
            )
595
596
        try:
597
            # Get the RAW value of the stat views
598
            ret = self.stats.get_plugin(plugin).get_views()
599
        except Exception as e:
600
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get views for plugin {plugin} ({str(e)})")
601
602
        return GlancesJSONResponse(ret)
603
604
    def _api_item(self, plugin: str, item: str):
605
        """Glances API RESTful implementation.
606
607
        Return the JSON representation of the couple plugin/item
608
        HTTP/200 if OK
609
        HTTP/400 if plugin is not found
610
        HTTP/404 if others error
611
        """
612
        self._check_if_plugin_available(plugin)
613
614
        # Update the stat
615
        self.__update_stats()
616
617
        try:
618
            # Get the RAW value of the stat views
619
            ret = self.stats.get_plugin(plugin).get_raw_stats_item(item)
620
        except Exception as e:
621
            raise HTTPException(
622
                status.HTTP_404_NOT_FOUND,
623
                f"Cannot get item {item} in plugin {plugin} ({str(e)})",
624
            )
625
626
        return GlancesJSONResponse(ret)
627
628
    def _api_key(self, plugin: str, item: str, key: str):
629
        """Glances API RESTful implementation.
630
631
        Return the JSON representation of  plugin/item/key
632
        HTTP/200 if OK
633
        HTTP/400 if plugin is not found
634
        HTTP/404 if others error
635
        """
636
        self._check_if_plugin_available(plugin)
637
638
        # Update the stat
639
        self.__update_stats()
640
641
        try:
642
            # Get the RAW value of the stat views
643
            ret = self.stats.get_plugin(plugin).get_raw_stats_key(item, key)
644
        except Exception as e:
645
            raise HTTPException(
646
                status.HTTP_404_NOT_FOUND,
647
                f"Cannot get item {item} in plugin {plugin} ({str(e)})",
648
            )
649
650
        return GlancesJSONResponse(ret)
651
652
    def _api_item_views(self, plugin: str, item: str):
653
        """Glances API RESTful implementation.
654
655
        Return the JSON view representation of the couple plugin/item
656
        HTTP/200 if OK
657
        HTTP/400 if plugin is not found
658
        HTTP/404 if others error
659
        """
660
        self._check_if_plugin_available(plugin)
661
662
        # Update the stat
663
        self.__update_stats()
664
665
        try:
666
            # Get the RAW value of the stat views
667
            ret = self.stats.get_plugin(plugin).get_views().get(item)
668
        except Exception as e:
669
            raise HTTPException(
670
                status.HTTP_404_NOT_FOUND,
671
                f"Cannot get item {item} in plugin view {plugin} ({str(e)})",
672
            )
673
674
        return GlancesJSONResponse(ret)
675
676
    def _api_key_views(self, plugin: str, item: str, key: str):
677
        """Glances API RESTful implementation.
678
679
        Return the JSON view representation of plugin/item/key
680
        HTTP/200 if OK
681
        HTTP/400 if plugin is not found
682
        HTTP/404 if others error
683
        """
684
        self._check_if_plugin_available(plugin)
685
686
        # Update the stat
687
        self.__update_stats()
688
689
        try:
690
            # Get the RAW value of the stat views
691
            ret = self.stats.get_plugin(plugin).get_views().get(key).get(item)
692
        except Exception as e:
693
            raise HTTPException(
694
                status.HTTP_404_NOT_FOUND,
695
                f"Cannot get item {item} in plugin view {plugin} ({str(e)})",
696
            )
697
698
        return GlancesJSONResponse(ret)
699
700
    def _api_item_history(self, plugin: str, item: str, nb: int = 0):
701
        """Glances API RESTful implementation.
702
703
        Return the JSON representation of the couple plugin/history of item
704
        HTTP/200 if OK
705
        HTTP/400 if plugin is not found
706
        HTTP/404 if others error
707
708
        """
709
        self._check_if_plugin_available(plugin)
710
711
        # Update the stat
712
        self.__update_stats()
713
714
        try:
715
            # Get the RAW value of the stat history
716
            ret = self.stats.get_plugin(plugin).get_raw_history(item, nb=nb)
717
        except Exception as e:
718
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get history for plugin {plugin} ({str(e)})")
719
        else:
720
            return GlancesJSONResponse(ret)
721
722
    def _api_item_description(self, plugin: str, item: str):
723
        """Glances API RESTful implementation.
724
725
        Return the JSON representation of the couple plugin/item description
726
        HTTP/200 if OK
727
        HTTP/400 if plugin is not found
728
        HTTP/404 if others error
729
        """
730
        self._check_if_plugin_available(plugin)
731
732
        try:
733
            # Get the description
734
            ret = self.stats.get_plugin(plugin).get_item_info(item, 'description')
735
        except Exception as e:
736
            raise HTTPException(
737
                status.HTTP_404_NOT_FOUND, f"Cannot get {item} description for plugin {plugin} ({str(e)})"
738
            )
739
        else:
740
            return GlancesJSONResponse(ret)
741
742
    def _api_item_unit(self, plugin: str, item: str):
743
        """Glances API RESTful implementation.
744
745
        Return the JSON representation of the couple plugin/item unit
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 unit
754
            ret = self.stats.get_plugin(plugin).get_item_info(item, 'unit')
755
        except Exception as e:
756
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get {item} unit for plugin {plugin} ({str(e)})")
757
        else:
758
            return GlancesJSONResponse(ret)
759
760
    def _api_value(self, plugin: str, item: str, value: Union[str, int, float]):
761
        """Glances API RESTful implementation.
762
763
        Return the process stats (dict) for the given item=value
764
        HTTP/200 if OK
765
        HTTP/400 if plugin is not found
766
        HTTP/404 if others error
767
        """
768
        self._check_if_plugin_available(plugin)
769
770
        # Update the stat
771
        self.__update_stats()
772
773
        try:
774
            # Get the RAW value
775
            ret = self.stats.get_plugin(plugin).get_raw_stats_value(item, value)
776
        except Exception as e:
777
            raise HTTPException(
778
                status.HTTP_404_NOT_FOUND, f"Cannot get {item} = {value} for plugin {plugin} ({str(e)})"
779
            )
780
        else:
781
            return GlancesJSONResponse(ret)
782
783
    def _api_config(self):
784
        """Glances API RESTful implementation.
785
786
        Return the JSON representation of the Glances configuration file
787
        HTTP/200 if OK
788
        HTTP/404 if others error
789
        """
790
        try:
791
            # Get the RAW value of the config' dict
792
            args_json = self.config.as_dict()
793
        except Exception as e:
794
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get config ({str(e)})")
795
        else:
796
            return GlancesJSONResponse(args_json)
797
798
    def _api_config_section(self, section: str):
799
        """Glances API RESTful implementation.
800
801
        Return the JSON representation of the Glances configuration section
802
        HTTP/200 if OK
803
        HTTP/400 if item is not found
804
        HTTP/404 if others error
805
        """
806
        config_dict = self.config.as_dict()
807
        if section not in config_dict:
808
            raise HTTPException(status.HTTP_400_BAD_REQUEST, f"Unknown configuration item {section}")
809
810
        try:
811
            # Get the RAW value of the config' dict
812
            ret_section = config_dict[section]
813
        except Exception as e:
814
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get config section {section} ({str(e)})")
815
816
        return GlancesJSONResponse(ret_section)
817
818
    def _api_config_section_item(self, section: str, item: str):
819
        """Glances API RESTful implementation.
820
821
        Return the JSON representation of the Glances configuration section/item
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 section
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
        try:
837
            # Get the RAW value of the config' dict item
838
            ret_item = ret_section[item]
839
        except Exception as e:
840
            raise HTTPException(
841
                status.HTTP_404_NOT_FOUND, f"Cannot get item {item} in config section {section} ({str(e)})"
842
            )
843
844
        return GlancesJSONResponse(ret_item)
845
846
    def _api_args(self):
847
        """Glances API RESTful implementation.
848
849
        Return the JSON representation of the Glances command line arguments
850
        HTTP/200 if OK
851
        HTTP/404 if others error
852
        """
853
        try:
854
            # Get the RAW value of the args' dict
855
            # Use vars to convert namespace to dict
856
            # Source: https://docs.python.org/%s/library/functions.html#vars
857
            args_json = vars(self.args)
858
        except Exception as e:
859
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get args ({str(e)})")
860
861
        return GlancesJSONResponse(args_json)
862
863
    def _api_args_item(self, item: str):
864
        """Glances API RESTful implementation.
865
866
        Return the JSON representation of the Glances command line arguments item
867
        HTTP/200 if OK
868
        HTTP/400 if item is not found
869
        HTTP/404 if others error
870
        """
871
        if item not in self.args:
872
            raise HTTPException(status.HTTP_400_BAD_REQUEST, f"Unknown argument item {item}")
873
874
        try:
875
            # Get the RAW value of the args' dict
876
            # Use vars to convert namespace to dict
877
            # Source: https://docs.python.org/%s/library/functions.html#vars
878
            args_json = vars(self.args)[item]
879
        except Exception as e:
880
            raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get args item ({str(e)})")
881
882
        return GlancesJSONResponse(args_json)
883