Test Failed
Push — master ( 372380...7cfc0c )
by Nicolas
03:32
created

GlancesRestfulApi._api_get_extended_processes()   A

Complexity

Conditions 2

Size

Total Lines 14
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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