mirror of
https://github.com/nicolargo/glances.git
synced 2024-11-24 05:15:47 +03:00
Unit tests are ok BUT the WebUI has a lot of issue: plugin disapear, traceback... Perhaps issue when stats are refreched ?
This commit is contained in:
parent
5d054e12e1
commit
cbb2facc87
@ -20,19 +20,10 @@ from urllib.parse import urljoin
|
||||
# from typing import Annotated
|
||||
from typing_extensions import Annotated
|
||||
|
||||
from glances.globals import json_dumps
|
||||
from glances.timer import Timer
|
||||
from glances.logger import logger
|
||||
|
||||
# FastAPI import
|
||||
|
||||
# TODO: not sure import is needed
|
||||
try:
|
||||
import jinja2
|
||||
except ImportError:
|
||||
logger.critical('Jinja2 import error. Glances cannot start in web server mode.')
|
||||
sys.exit(2)
|
||||
|
||||
try:
|
||||
from fastapi import FastAPI, Depends, HTTPException, status, APIRouter, Request
|
||||
from fastapi.security import HTTPBasic, HTTPBasicCredentials
|
||||
@ -78,7 +69,7 @@ class GlancesRestfulApi(object):
|
||||
# Load configuration file
|
||||
self.load_config(config)
|
||||
|
||||
# Set the bind URL (only used for log information purpose)
|
||||
# Set the bind URL
|
||||
self.bind_url = urljoin('http://{}:{}/'.format(self.args.bind_address,
|
||||
self.args.port),
|
||||
self.url_prefix)
|
||||
@ -95,7 +86,6 @@ class GlancesRestfulApi(object):
|
||||
|
||||
# Set path for WebUI
|
||||
self.STATIC_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'static/public')
|
||||
# TEMPLATE_PATH.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), 'static/templates'))
|
||||
self.TEMPLATE_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'static/templates')
|
||||
self._templates = Jinja2Templates(directory=self.TEMPLATE_PATH)
|
||||
|
||||
@ -103,7 +93,10 @@ class GlancesRestfulApi(object):
|
||||
# https://fastapi.tiangolo.com/tutorial/cors/
|
||||
self._app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
# allow_origins=["*"],
|
||||
allow_origins=[
|
||||
self.bind_url
|
||||
],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
@ -140,7 +133,7 @@ class GlancesRestfulApi(object):
|
||||
|
||||
# TODO: the password comparaison is not working for the moment.
|
||||
# if the password is wrong, authentication is working...
|
||||
# Perahps because the password is hashed in the GlancesPassword class
|
||||
# Perhaps because the password is hashed in the GlancesPassword class
|
||||
# and the one given by creds.password is not hashed ?
|
||||
def authentication(self, creds: Annotated[HTTPBasicCredentials, Depends(security)]):
|
||||
"""Check if a username/password combination is valid."""
|
||||
@ -168,21 +161,24 @@ class GlancesRestfulApi(object):
|
||||
status_code=status.HTTP_200_OK,
|
||||
response_class=ORJSONResponse,
|
||||
endpoint=self._api_status)
|
||||
|
||||
router.add_api_route('/api/%s/config' % self.API_VERSION,
|
||||
response_class=ORJSONResponse,
|
||||
endpoint=self._api_config)
|
||||
router.add_api_route('/api/%s/config/{item}' % self.API_VERSION,
|
||||
router.add_api_route('/api/%s/config/{section}' % self.API_VERSION,
|
||||
response_class=ORJSONResponse,
|
||||
endpoint=self._api_config_item)
|
||||
endpoint=self._api_config_section)
|
||||
router.add_api_route('/api/%s/config/{section}/{item}' % self.API_VERSION,
|
||||
response_class=ORJSONResponse,
|
||||
endpoint=self._api_config_section_item)
|
||||
|
||||
router.add_api_route('/api/%s/args' % self.API_VERSION,
|
||||
response_class=ORJSONResponse,
|
||||
endpoint=self._api_args)
|
||||
router.add_api_route('/api/%s/args/{item}' % self.API_VERSION,
|
||||
response_class=ORJSONResponse,
|
||||
endpoint=self._api_args_item)
|
||||
router.add_api_route('/api/%s/help' % self.API_VERSION,
|
||||
response_class=ORJSONResponse,
|
||||
endpoint=self._api_help)
|
||||
|
||||
router.add_api_route('/api/%s/pluginslist' % self.API_VERSION,
|
||||
response_class=ORJSONResponse,
|
||||
endpoint=self._api_plugins)
|
||||
@ -195,6 +191,10 @@ class GlancesRestfulApi(object):
|
||||
router.add_api_route('/api/%s/all/views' % self.API_VERSION,
|
||||
response_class=ORJSONResponse,
|
||||
endpoint=self._api_all_views)
|
||||
|
||||
router.add_api_route('/api/%s/help' % self.API_VERSION,
|
||||
response_class=ORJSONResponse,
|
||||
endpoint=self._api_help)
|
||||
router.add_api_route('/api/%s/{plugin}' % self.API_VERSION,
|
||||
response_class=ORJSONResponse,
|
||||
endpoint=self._api)
|
||||
@ -204,7 +204,7 @@ class GlancesRestfulApi(object):
|
||||
router.add_api_route('/api/%s/{plugin}/history/{nb}' % self.API_VERSION,
|
||||
response_class=ORJSONResponse,
|
||||
endpoint=self._api_history)
|
||||
router.add_api_route('/api/%s/{plugin}/top/<nb:int>' % self.API_VERSION,
|
||||
router.add_api_route('/api/%s/{plugin}/top/{nb}' % self.API_VERSION,
|
||||
response_class=ORJSONResponse,
|
||||
endpoint=self._api_top)
|
||||
router.add_api_route('/api/%s/{plugin}/limits' % self.API_VERSION,
|
||||
@ -232,18 +232,13 @@ class GlancesRestfulApi(object):
|
||||
|
||||
# WEB UI
|
||||
if not self.args.disable_webui:
|
||||
# Template
|
||||
# Template for the root index.html file
|
||||
router.add_api_route('/',
|
||||
response_class=HTMLResponse,
|
||||
endpoint=self._index)
|
||||
|
||||
# TODO: to be migrated to another route
|
||||
# router.add_api_route('/{refresh_time}',
|
||||
# endpoint=self._index)
|
||||
|
||||
# Statics files
|
||||
# self._app.mount("/static", StaticFiles(directory=self.STATIC_PATH), name="static")
|
||||
self._app.mount("/",
|
||||
self._app.mount("/static",
|
||||
StaticFiles(directory=self.STATIC_PATH),
|
||||
name="static")
|
||||
|
||||
@ -282,30 +277,27 @@ class GlancesRestfulApi(object):
|
||||
logger.critical('Error: Can not ran Glances Web server ({})'.format(e))
|
||||
|
||||
def end(self):
|
||||
"""End the bottle."""
|
||||
"""End the Web server"""
|
||||
logger.info("Close the Web server")
|
||||
# TODO: close FastAPI instance gracefully
|
||||
# self._app.close()
|
||||
# if self.url_prefix != '/':
|
||||
# self.main_app.close()
|
||||
|
||||
# Example from FastAPI documentation
|
||||
# @app.get("/", response_class=HTMLResponse)
|
||||
# def home(request: Request):
|
||||
# return templates.TemplateResponse("index.html", {"request": request})
|
||||
def _index(self, request: Request):
|
||||
"""Return main index.html (/) file.
|
||||
Parameters are available through the request object.
|
||||
Example: http://localhost:61208/?refresh=5
|
||||
"""
|
||||
|
||||
def _index(self, refresh_time=None):
|
||||
"""Return main index.html (/) file."""
|
||||
|
||||
if refresh_time is None or refresh_time < 1:
|
||||
refresh_time = int(self.args.time)
|
||||
refresh_time = request.query_params.get('refresh',
|
||||
default=max(1, int(self.args.time)))
|
||||
|
||||
# Update the stat
|
||||
self.__update__()
|
||||
|
||||
# Display
|
||||
# return template("index.html", refresh_time=refresh_time)
|
||||
return self.templates.TemplateResponse("index.html")
|
||||
return self._templates.TemplateResponse("index.html",
|
||||
{
|
||||
"request": request,
|
||||
"refresh_time": refresh_time,
|
||||
})
|
||||
|
||||
def _api_status(self):
|
||||
"""Glances API RESTful implementation.
|
||||
@ -662,29 +654,61 @@ class GlancesRestfulApi(object):
|
||||
|
||||
return ORJSONResponse(args_json)
|
||||
|
||||
def _api_config_item(self, item):
|
||||
def _api_config_section(self, section):
|
||||
"""Glances API RESTful implementation.
|
||||
|
||||
Return the JSON representation of the Glances configuration item
|
||||
Return the JSON representation of the Glances configuration section
|
||||
HTTP/200 if OK
|
||||
HTTP/400 if item is not found
|
||||
HTTP/404 if others error
|
||||
"""
|
||||
config_dict = self.config.as_dict()
|
||||
if item not in config_dict:
|
||||
if section not in config_dict:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Unknown configuration item %s" % item)
|
||||
detail="Unknown configuration item %s" % section)
|
||||
|
||||
try:
|
||||
# Get the RAW value of the config' dict
|
||||
args_json = config_dict[item]
|
||||
ret_section = config_dict[section]
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Cannot get config item (%s)" % str(e))
|
||||
detail="Cannot get config section %s (%s)" % (section, str(e)))
|
||||
|
||||
return ORJSONResponse(args_json)
|
||||
return ORJSONResponse(ret_section)
|
||||
|
||||
def _api_config_section_item(self, section, item):
|
||||
"""Glances API RESTful implementation.
|
||||
|
||||
Return the JSON representation of the Glances configuration section/item
|
||||
HTTP/200 if OK
|
||||
HTTP/400 if item is not found
|
||||
HTTP/404 if others error
|
||||
"""
|
||||
config_dict = self.config.as_dict()
|
||||
if section not in config_dict:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Unknown configuration item %s" % section)
|
||||
|
||||
try:
|
||||
# Get the RAW value of the config' dict section
|
||||
ret_section = config_dict[section]
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Cannot get config section %s (%s)" % (section, str(e)))
|
||||
|
||||
try:
|
||||
# Get the RAW value of the config' dict item
|
||||
ret_item = ret_section[item]
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Cannot get item %s in config section %s (%s)" % (item, section, str(e)))
|
||||
|
||||
return ORJSONResponse(ret_item)
|
||||
|
||||
def _api_args(self):
|
||||
"""Glances API RESTful implementation.
|
||||
|
@ -64,13 +64,13 @@ export default {
|
||||
}
|
||||
function getColumnLabel(value) {
|
||||
const labels = {
|
||||
io_counters: 'disk IO',
|
||||
cpu_percent: 'CPU consumption',
|
||||
memory_percent: 'memory consumption',
|
||||
cpu_times: 'process time',
|
||||
username: 'user name',
|
||||
name: 'process name',
|
||||
timemillis: 'process time',
|
||||
cpu_times: 'process time',
|
||||
io_counters: 'disk IO',
|
||||
name: 'process name',
|
||||
None: 'None'
|
||||
};
|
||||
return labels[value] || value;
|
||||
|
@ -78,10 +78,10 @@
|
||||
<div class="table-cell" :class="getMemoryPercentAlert(process)">
|
||||
{{ process.memory_percent == -1 ? '?' : $filters.number(process.memory_percent, 1) }}
|
||||
</div>
|
||||
<div class="table-cell hidden-xs hidden-sm">
|
||||
<div class="table-cell">
|
||||
{{ $filters.bytes(process.memvirt) }}
|
||||
</div>
|
||||
<div class="table-cell hidden-xs hidden-sm">
|
||||
<div class="table-cell">
|
||||
{{ $filters.bytes(process.memres) }}
|
||||
</div>
|
||||
<div class="table-cell">
|
||||
@ -159,8 +159,8 @@ export default {
|
||||
process.memvirt = '?';
|
||||
process.memres = '?';
|
||||
if (process.memory_info) {
|
||||
process.memvirt = process.memory_info[1];
|
||||
process.memres = process.memory_info[0];
|
||||
process.memvirt = process.memory_info.vms;
|
||||
process.memres = process.memory_info.rss;
|
||||
}
|
||||
|
||||
process.timeplus = '?';
|
||||
|
2
glances/outputs/static/public/glances.js
vendored
2
glances/outputs/static/public/glances.js
vendored
File diff suppressed because one or more lines are too long
22
glances/outputs/static/templates/index.html
Normal file
22
glances/outputs/static/templates/index.html
Normal file
@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Glances</title>
|
||||
|
||||
<link rel="icon" type="image/x-icon" href="static/favicon.ico" />
|
||||
<script>
|
||||
window.__GLANCES__ = {
|
||||
'refresh-time': '{{ refresh_time }}'
|
||||
}
|
||||
</script>
|
||||
<script src="static/glances.js" defer></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,22 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Glances</title>
|
||||
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
<script>
|
||||
window.__GLANCES__ = {
|
||||
'refresh-time': '{{ refresh_time }}'
|
||||
}
|
||||
</script>
|
||||
<script src="glances.js" defer></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
@ -443,7 +443,7 @@ class GlancesProcesses(object):
|
||||
# If io_tag = 0 > Access denied or first time (display "?")
|
||||
# If io_tag = 1 > No access denied (display the IO rate)
|
||||
if 'io_counters' in proc and proc['io_counters'] is not None:
|
||||
io_new = [proc['io_counters'].read_bytes, proc['io_counters'].write_bytes]
|
||||
io_new = [proc['io_counters'][2], proc['io_counters'][3]]
|
||||
# For IO rate computation
|
||||
# Append saved IO r/w bytes
|
||||
try:
|
||||
|
@ -59,7 +59,7 @@ class TestGlances(unittest.TestCase):
|
||||
cmdline = "./venv/bin/python"
|
||||
else:
|
||||
cmdline = "python"
|
||||
cmdline += " -m glances -B 0.0.0.0 -w -p %s --disable-webui" % SERVER_PORT
|
||||
cmdline += " -m glances -B 0.0.0.0 -w -p %s --disable-webui -C ./conf/glances.conf" % SERVER_PORT
|
||||
print("Run the Glances Web Server on port %s" % SERVER_PORT)
|
||||
args = shlex.split(cmdline)
|
||||
pid = subprocess.Popen(args)
|
||||
@ -243,6 +243,19 @@ class TestGlances(unittest.TestCase):
|
||||
self.assertIsInstance(req.json(), list)
|
||||
self.assertEqual(len(req.json()), 2)
|
||||
|
||||
def test_014_config(self):
|
||||
"""Test API request to get Glances configuration."""
|
||||
method = "config"
|
||||
print('INFO: [TEST_014] Get config')
|
||||
|
||||
req = self.http_get("%s/%s" % (URL, method))
|
||||
self.assertTrue(req.ok)
|
||||
self.assertIsInstance(req.json(), dict)
|
||||
|
||||
req = self.http_get("%s/%s/global/refresh" % (URL, method))
|
||||
self.assertTrue(req.ok)
|
||||
self.assertEqual(req.json(), "2")
|
||||
|
||||
def test_999_stop_server(self):
|
||||
"""Stop the Glances Web Server."""
|
||||
print('INFO: [TEST_999] Stop the Glances Web Server')
|
||||
|
Loading…
Reference in New Issue
Block a user