Rename any reference to Bottle in doc and dep file. Ready to refactor the main glances_restful_api.py file.

This commit is contained in:
nicolargo 2023-11-26 10:02:30 +01:00
parent 36ed96b05a
commit bcf4ce45fc
17 changed files with 49 additions and 700 deletions

View File

@ -80,6 +80,9 @@ test-min: ## Run unit tests in minimal environment
test-min-with-upgrade: venv-min-upgrade ## Upgrade deps and run unit tests in minimal environment
./venv-min/bin/python ./unitest.py
test-restful-api: ## Run unit tests of the RESTful API
./venv-min/bin/python ./unitest-restful.py
# ===================================================================
# Linters and profilers
# ===================================================================

View File

@ -92,11 +92,11 @@ Optional dependencies:
- ``batinfo`` (for battery monitoring)
- ``bernhard`` (for the Riemann export module)
- ``bottle`` (for Web server mode)
- ``cassandra-driver`` (for the Cassandra export module)
- ``chevron`` (for the action script feature)
- ``docker`` (for the Containers Docker monitoring support)
- ``elasticsearch`` (for the Elastic Search export module)
- ``FastAPI`` and ``Uvicorn`` (for Web server mode)
- ``graphitesender`` (For the Graphite export module)
- ``hddtemp`` (for HDD temperature monitoring support) [Linux-only]
- ``influxdb`` (for the InfluxDB version 1 export module)
@ -207,10 +207,10 @@ Get the Glances container:
The following tags are availables:
- *latest-full* for a full Alpine Glances image (latest release) with all dependencies
- *latest* for a basic Alpine Glances (latest release) version with minimal dependencies (Bottle and Docker)
- *latest* for a basic Alpine Glances (latest release) version with minimal dependencies (FastAPI and Docker)
- *dev* for a basic Alpine Glances image (based on development branch) with all dependencies (Warning: may be instable)
- *ubuntu-latest-full* for a full Ubuntu Glances image (latest release) with all dependencies
- *ubuntu-latest* for a basic Ubuntu Glances (latest release) version with minimal dependencies (Bottle and Docker)
- *ubuntu-latest* for a basic Ubuntu Glances (latest release) version with minimal dependencies (FastAPI and Docker)
- *ubuntu-dev* for a basic Ubuntu Glances image (based on development branch) with all dependencies (Warning: may be instable)
Run last version of Glances container in *console mode*:
@ -319,7 +319,7 @@ Start Termux on your device and enter:
$ apt update
$ apt upgrade
$ apt install clang python
$ pip install bottle
$ pip install fastapi uvicorn
$ pip install glances
And start Glances:

View File

@ -172,7 +172,7 @@ Command-Line Options
.. option:: -w, --webserver
run Glances in web server mode (bottle lib needed)
run Glances in web server mode (FastAPI lib needed)
.. option:: --cached-time CACHED_TIME

View File

@ -28,7 +28,7 @@ Available tags (all images are based on both Alpine and Ubuntu Operating System)
* - `latest`
- Alpine
- Latest Release
- Minimal + (Bottle & Docker)
- Minimal + (FastAPI & Docker)
* - `dev`
- Alpine
- develop
@ -40,7 +40,7 @@ Available tags (all images are based on both Alpine and Ubuntu Operating System)
* - `ubuntu-latest`
- Ubuntu
- Latest Release
- Minimal + (Bottle & Docker)
- Minimal + (FastAPI & Docker)
* - `ubuntu-dev`
- Ubuntu
- develop

View File

@ -254,7 +254,7 @@ set refresh time in seconds [default: 3 sec]
.INDENT 0.0
.TP
.B \-w, \-\-webserver
run Glances in web server mode (bottle lib needed)
run Glances in web server mode (FastAPI lib needed)
.UNINDENT
.INDENT 0.0
.TP

View File

@ -12,7 +12,7 @@ globals.py Share variables upon modules
main.py Main script to rule them up...
client.py Glances client
server.py Glances server
webserver.py Glances web server (Bottle-based)
webserver.py Glances web server (Based on FastAPI)
autodiscover.py Glances autodiscover module (via zeroconf)
standalone.py Glances standalone (curses interface)
password.py Manage password for Glances client/server
@ -27,7 +27,7 @@ plugins
outputs
=> Glances UI
glances_curses.py The curses interface
glances_bottle.py The web interface
glances_restful-api.py The HTTP/API & Web based interface
...
exports
=> Glances exports

View File

@ -363,7 +363,7 @@ Examples of use:
action='store_true',
default=False,
dest='webserver',
help='run Glances in web server mode (bottle needed)',
help='run Glances in web server mode (FastAPI and Uvicorn lib needed)',
)
parser.add_argument(
'--cached-time',

View File

@ -1,668 +0,0 @@
# -*- coding: utf-8 -*-
#
# This file is part of Glances.
#
# SPDX-FileCopyrightText: 2023 Nicolas Hennion <nicolas@nicolargo.com>
#
# SPDX-License-Identifier: LGPL-3.0-only
#
"""RestFull API interface class."""
import os
import sys
import tempfile
from io import open
import webbrowser
import zlib
import socket
from urllib.parse import urljoin
from glances.globals import b, json_dumps
from glances.timer import Timer
from glances.logger import logger
try:
from bottle import Bottle, static_file, abort, response, request, auth_basic, template, TEMPLATE_PATH
except ImportError:
logger.critical('Bottle module not found. Glances cannot start in web server mode.')
sys.exit(2)
def compress(func):
"""Compress result with deflate algorithm if the client ask for it."""
def wrapper(*args, **kwargs):
"""Wrapper that take one function and return the compressed result."""
ret = func(*args, **kwargs)
logger.debug(
'Receive {} {} request with header: {}'.format(
request.method,
request.url,
['{}: {}'.format(h, request.headers.get(h)) for h in request.headers.keys()],
)
)
if 'deflate' in request.headers.get('Accept-Encoding', ''):
response.headers['Content-Encoding'] = 'deflate'
ret = deflate_compress(ret)
else:
response.headers['Content-Encoding'] = 'identity'
return ret
def deflate_compress(data, compress_level=6):
"""Compress given data using the DEFLATE algorithm"""
# Init compression
zobj = zlib.compressobj(
compress_level, zlib.DEFLATED, zlib.MAX_WBITS, zlib.DEF_MEM_LEVEL, zlib.Z_DEFAULT_STRATEGY
)
# Return compressed object
return zobj.compress(b(data)) + zobj.flush()
return wrapper
class GlancesBottle(object):
"""This class manages the Bottle Web server."""
API_VERSION = '3'
def __init__(self, config=None, args=None):
# Init config
self.config = config
# Init args
self.args = args
# Init stats
# Will be updated within Bottle route
self.stats = None
# cached_time is the minimum time interval between stats updates
# i.e. HTTP/RESTful calls will not retrieve updated info until the time
# since last update is passed (will retrieve old cached info instead)
self.timer = Timer(0)
# Load configuration file
self.load_config(config)
# Set the bind URL (only used for log information purpose)
self.bind_url = urljoin('http://{}:{}/'.format(self.args.bind_address, self.args.port), self.url_prefix)
# Init Bottle
self._app = Bottle()
# Enable CORS (issue #479)
self._app.install(EnableCors())
# Password
if args.password != '':
self._app.install(auth_basic(self.check_auth))
# Define routes
self._route()
# Path where the statics files are stored
self.STATIC_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'static/public')
# Paths for templates
TEMPLATE_PATH.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), 'static/templates'))
def load_config(self, config):
"""Load the outputs section of the configuration file."""
# Limit the number of processes to display in the WebUI
self.url_prefix = '/'
if config is not None and config.has_section('outputs'):
n = config.get_value('outputs', 'max_processes_display', default=None)
logger.debug('Number of processes to display in the WebUI: {}'.format(n))
self.url_prefix = config.get_value('outputs', 'url_prefix', default='/')
logger.debug('URL prefix: {}'.format(self.url_prefix))
def __update__(self):
# Never update more than 1 time per cached_time
if self.timer.finished():
self.stats.update()
self.timer = Timer(self.args.cached_time)
def app(self):
return self._app()
def check_auth(self, username, password):
"""Check if a username/password combination is valid."""
if username == self.args.username:
from glances.password import GlancesPassword
pwd = GlancesPassword(username=username, config=self.config)
return pwd.check_password(self.args.password, pwd.get_hash(password))
else:
return False
def _route(self):
"""Define route."""
# REST API
self._app.route('/api/%s/status' % self.API_VERSION, method="GET", callback=self._api_status)
self._app.route('/api/%s/config' % self.API_VERSION, method="GET", callback=self._api_config)
self._app.route('/api/%s/config/<item>' % self.API_VERSION, method="GET", callback=self._api_config_item)
self._app.route('/api/%s/args' % self.API_VERSION, method="GET", callback=self._api_args)
self._app.route('/api/%s/args/<item>' % self.API_VERSION, method="GET", callback=self._api_args_item)
self._app.route('/api/%s/help' % self.API_VERSION, method="GET", callback=self._api_help)
self._app.route('/api/%s/pluginslist' % self.API_VERSION, method="GET", callback=self._api_plugins)
self._app.route('/api/%s/all' % self.API_VERSION, method="GET", callback=self._api_all)
self._app.route('/api/%s/all/limits' % self.API_VERSION, method="GET", callback=self._api_all_limits)
self._app.route('/api/%s/all/views' % self.API_VERSION, method="GET", callback=self._api_all_views)
self._app.route('/api/%s/<plugin>' % self.API_VERSION, method="GET", callback=self._api)
self._app.route('/api/%s/<plugin>/history' % self.API_VERSION, method="GET", callback=self._api_history)
self._app.route(
'/api/%s/<plugin>/history/<nb:int>' % self.API_VERSION, method="GET", callback=self._api_history
)
self._app.route('/api/%s/<plugin>/top/<nb:int>' % self.API_VERSION, method="GET", callback=self._api_top)
self._app.route('/api/%s/<plugin>/limits' % self.API_VERSION, method="GET", callback=self._api_limits)
self._app.route('/api/%s/<plugin>/views' % self.API_VERSION, method="GET", callback=self._api_views)
self._app.route('/api/%s/<plugin>/<item>' % self.API_VERSION, method="GET", callback=self._api_item)
self._app.route(
'/api/%s/<plugin>/<item>/history' % self.API_VERSION, method="GET", callback=self._api_item_history
)
self._app.route(
'/api/%s/<plugin>/<item>/history/<nb:int>' % self.API_VERSION, method="GET", callback=self._api_item_history
)
self._app.route('/api/%s/<plugin>/<item>/<value>' % self.API_VERSION, method="GET", callback=self._api_value)
self._app.route(
'/api/%s/<plugin>/<item>/<value:path>' % self.API_VERSION, method="GET", callback=self._api_value
)
bindmsg = 'Glances RESTful API Server started on {}api/{}'.format(self.bind_url, self.API_VERSION)
logger.info(bindmsg)
# WEB UI
if not self.args.disable_webui:
self._app.route('/', method="GET", callback=self._index)
self._app.route('/<refresh_time:int>', method=["GET"], callback=self._index)
self._app.route('/<filepath:path>', method="GET", callback=self._resource)
bindmsg = 'Glances Web User Interface started on {}'.format(self.bind_url)
else:
bindmsg = 'The WebUI is disable (--disable-webui)'
logger.info(bindmsg)
print(bindmsg)
def start(self, stats):
"""Start the bottle."""
# Init stats
self.stats = stats
# Init plugin list
self.plugins_list = self.stats.getPluginsList()
# Bind the Bottle TCP address/port
if self.args.open_web_browser:
# Implementation of the issue #946
# Try to open the Glances Web UI in the default Web browser if:
# 1) --open-web-browser option is used
# 2) Glances standalone mode is running on Windows OS
webbrowser.open(self.bind_url, new=2, autoraise=1)
# Run the Web application
if self.url_prefix != '/':
# Create an outer Bottle class instance to manage url_prefix
self.main_app = Bottle()
self.main_app.mount(self.url_prefix, self._app)
try:
self.main_app.run(host=self.args.bind_address, port=self.args.port, quiet=not self.args.debug)
except socket.error as e:
logger.critical('Error: Can not ran Glances Web server ({})'.format(e))
else:
try:
self._app.run(host=self.args.bind_address, port=self.args.port, quiet=not self.args.debug)
except socket.error as e:
logger.critical('Error: Can not ran Glances Web server ({})'.format(e))
def end(self):
"""End the bottle."""
logger.info("Close the Web server")
self._app.close()
if self.url_prefix != '/':
self.main_app.close()
def _index(self, refresh_time=None):
"""Bottle callback for index.html (/) file."""
if refresh_time is None or refresh_time < 1:
refresh_time = int(self.args.time)
# Update the stat
self.__update__()
# Display
return template("index.html", refresh_time=refresh_time)
def _resource(self, filepath):
"""Bottle callback for resources files."""
# Return the static file
return static_file(filepath, root=self.STATIC_PATH)
@compress
def _api_status(self):
"""Glances API RESTful implementation.
Return a 200 status code.
This entry point should be used to check the API health.
See related issue: Web server health check endpoint #1988
"""
response.status = 200
return "Active"
@compress
def _api_help(self):
"""Glances API RESTful implementation.
Return the help data or 404 error.
"""
response.content_type = 'application/json; charset=utf-8'
# Update the stat
view_data = self.stats.get_plugin("help").get_view_data()
try:
plist = json_dumps(view_data)
except Exception as e:
abort(404, "Cannot get help view data (%s)" % str(e))
return plist
@compress
def _api_plugins(self):
"""Glances API RESTFul implementation.
@api {get} /api/%s/pluginslist Get plugins list
@apiVersion 2.0
@apiName pluginslist
@apiGroup plugin
@apiSuccess {String[]} Plugins list.
@apiSuccessExample Success-Response:
HTTP/1.1 200 OK
[
"load",
"help",
"ip",
"memswap",
"processlist",
...
]
@apiError Cannot get plugin list.
@apiErrorExample Error-Response:
HTTP/1.1 404 Not Found
"""
response.content_type = 'application/json; charset=utf-8'
# Update the stat
self.__update__()
try:
plist = json_dumps(self.plugins_list)
except Exception as e:
abort(404, "Cannot get plugin list (%s)" % str(e))
return plist
@compress
def _api_all(self):
"""Glances API RESTful implementation.
Return the JSON representation of all the plugins
HTTP/200 if OK
HTTP/400 if plugin is not found
HTTP/404 if others error
"""
response.content_type = 'application/json; charset=utf-8'
if self.args.debug:
fname = os.path.join(tempfile.gettempdir(), 'glances-debug.json')
try:
with open(fname) as f:
return f.read()
except IOError:
logger.debug("Debug file (%s) not found" % fname)
# Update the stat
self.__update__()
try:
# Get the JSON value of the stat ID
statval = json_dumps(self.stats.getAllAsDict())
except Exception as e:
abort(404, "Cannot get stats (%s)" % str(e))
return statval
@compress
def _api_all_limits(self):
"""Glances API RESTful implementation.
Return the JSON representation of all the plugins limits
HTTP/200 if OK
HTTP/400 if plugin is not found
HTTP/404 if others error
"""
response.content_type = 'application/json; charset=utf-8'
try:
# Get the JSON value of the stat limits
limits = json_dumps(self.stats.getAllLimitsAsDict())
except Exception as e:
abort(404, "Cannot get limits (%s)" % (str(e)))
return limits
@compress
def _api_all_views(self):
"""Glances API RESTful implementation.
Return the JSON representation of all the plugins views
HTTP/200 if OK
HTTP/400 if plugin is not found
HTTP/404 if others error
"""
response.content_type = 'application/json; charset=utf-8'
try:
# Get the JSON value of the stat view
limits = json_dumps(self.stats.getAllViewsAsDict())
except Exception as e:
abort(404, "Cannot get views (%s)" % (str(e)))
return limits
@compress
def _api(self, plugin):
"""Glances API RESTful implementation.
Return the JSON representation of a given plugin
HTTP/200 if OK
HTTP/400 if plugin is not found
HTTP/404 if others error
"""
response.content_type = 'application/json; charset=utf-8'
if plugin not in self.plugins_list:
abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list))
# Update the stat
self.__update__()
try:
# Get the JSON value of the stat ID
statval = self.stats.get_plugin(plugin).get_stats()
except Exception as e:
abort(404, "Cannot get plugin %s (%s)" % (plugin, str(e)))
return statval
@compress
def _api_top(self, plugin, nb=0):
"""Glances API RESTful implementation.
Return the JSON representation of a given plugin limited to the top nb items.
It is used to reduce the payload of the HTTP response (example: processlist).
HTTP/200 if OK
HTTP/400 if plugin is not found
HTTP/404 if others error
"""
response.content_type = 'application/json; charset=utf-8'
if plugin not in self.plugins_list:
abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list))
# Update the stat
self.__update__()
try:
# Get the value of the stat ID
statval = self.stats.get_plugin(plugin).get_export()
except Exception as e:
abort(404, "Cannot get plugin %s (%s)" % (plugin, str(e)))
if isinstance(statval, list):
return json_dumps(statval[:nb])
else:
return json_dumps(statval)
@compress
def _api_history(self, plugin, nb=0):
"""Glances API RESTful implementation.
Return the JSON representation of a given plugin history
Limit to the last nb items (all if nb=0)
HTTP/200 if OK
HTTP/400 if plugin is not found
HTTP/404 if others error
"""
response.content_type = 'application/json; charset=utf-8'
if plugin not in self.plugins_list:
abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list))
# Update the stat
self.__update__()
try:
# Get the JSON value of the stat ID
statval = self.stats.get_plugin(plugin).get_stats_history(nb=int(nb))
except Exception as e:
abort(404, "Cannot get plugin history %s (%s)" % (plugin, str(e)))
return statval
@compress
def _api_limits(self, plugin):
"""Glances API RESTful implementation.
Return the JSON limits of a given plugin
HTTP/200 if OK
HTTP/400 if plugin is not found
HTTP/404 if others error
"""
response.content_type = 'application/json; charset=utf-8'
if plugin not in self.plugins_list:
abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list))
# Update the stat
# self.__update__()
try:
# Get the JSON value of the stat limits
ret = self.stats.get_plugin(plugin).limits
except Exception as e:
abort(404, "Cannot get limits for plugin %s (%s)" % (plugin, str(e)))
return ret
@compress
def _api_views(self, plugin):
"""Glances API RESTful implementation.
Return the JSON views of a given plugin
HTTP/200 if OK
HTTP/400 if plugin is not found
HTTP/404 if others error
"""
response.content_type = 'application/json; charset=utf-8'
if plugin not in self.plugins_list:
abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list))
# Update the stat
# self.__update__()
try:
# Get the JSON value of the stat views
ret = self.stats.get_plugin(plugin).get_views()
except Exception as e:
abort(404, "Cannot get views for plugin %s (%s)" % (plugin, str(e)))
return ret
# No compression see issue #1228
# @compress
def _api_itemvalue(self, plugin, item, value=None, history=False, nb=0):
"""Father method for _api_item and _api_value."""
response.content_type = 'application/json; charset=utf-8'
if plugin not in self.plugins_list:
abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list))
# Update the stat
self.__update__()
if value is None:
if history:
ret = self.stats.get_plugin(plugin).get_stats_history(item, nb=int(nb))
else:
ret = self.stats.get_plugin(plugin).get_stats_item(item)
if ret is None:
abort(404, "Cannot get item %s%s in plugin %s" % (item, 'history ' if history else '', plugin))
else:
if history:
# Not available
ret = None
else:
ret = self.stats.get_plugin(plugin).get_stats_value(item, value)
if ret is None:
abort(
404, "Cannot get item %s(%s=%s) in plugin %s" % ('history ' if history else '', item, value, plugin)
)
return ret
@compress
def _api_item(self, plugin, item):
"""Glances API RESTful implementation.
Return the JSON representation of the couple plugin/item
HTTP/200 if OK
HTTP/400 if plugin is not found
HTTP/404 if others error
"""
return self._api_itemvalue(plugin, item)
@compress
def _api_item_history(self, plugin, item, nb=0):
"""Glances API RESTful implementation.
Return the JSON representation of the couple plugin/history of item
HTTP/200 if OK
HTTP/400 if plugin is not found
HTTP/404 if others error
"""
return self._api_itemvalue(plugin, item, history=True, nb=int(nb))
@compress
def _api_value(self, plugin, item, value):
"""Glances API RESTful implementation.
Return the process stats (dict) for the given item=value
HTTP/200 if OK
HTTP/400 if plugin is not found
HTTP/404 if others error
"""
return self._api_itemvalue(plugin, item, value)
@compress
def _api_config(self):
"""Glances API RESTful implementation.
Return the JSON representation of the Glances configuration file
HTTP/200 if OK
HTTP/404 if others error
"""
response.content_type = 'application/json; charset=utf-8'
try:
# Get the JSON value of the config' dict
args_json = json_dumps(self.config.as_dict())
except Exception as e:
abort(404, "Cannot get config (%s)" % str(e))
return args_json
@compress
def _api_config_item(self, item):
"""Glances API RESTful implementation.
Return the JSON representation of the Glances configuration item
HTTP/200 if OK
HTTP/400 if item is not found
HTTP/404 if others error
"""
response.content_type = 'application/json; charset=utf-8'
config_dict = self.config.as_dict()
if item not in config_dict:
abort(400, "Unknown configuration item %s" % item)
try:
# Get the JSON value of the config' dict
args_json = json_dumps(config_dict[item])
except Exception as e:
abort(404, "Cannot get config item (%s)" % str(e))
return args_json
@compress
def _api_args(self):
"""Glances API RESTful implementation.
Return the JSON representation of the Glances command line arguments
HTTP/200 if OK
HTTP/404 if others error
"""
response.content_type = 'application/json; charset=utf-8'
try:
# Get the JSON value of the args' dict
# Use vars to convert namespace to dict
# Source: https://docs.python.org/%s/library/functions.html#vars
args_json = json_dumps(vars(self.args))
except Exception as e:
abort(404, "Cannot get args (%s)" % str(e))
return args_json
@compress
def _api_args_item(self, item):
"""Glances API RESTful implementation.
Return the JSON representation of the Glances command line arguments item
HTTP/200 if OK
HTTP/400 if item is not found
HTTP/404 if others error
"""
response.content_type = 'application/json; charset=utf-8'
if item not in self.args:
abort(400, "Unknown argument item %s" % item)
try:
# Get the JSON value of the args' dict
# Use vars to convert namespace to dict
# Source: https://docs.python.org/%s/library/functions.html#vars
args_json = json_dumps(vars(self.args)[item])
except Exception as e:
abort(404, "Cannot get args item (%s)" % str(e))
return args_json
class EnableCors(object):
name = 'enable_cors'
api = 2
def apply(self, fn, context):
def _enable_cors(*args, **kwargs):
# set CORS headers
response.headers['Access-Control-Allow-Origin'] = '*'
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, OPTIONS'
response.headers[
'Access-Control-Allow-Headers'
] = 'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token'
if request.method != 'OPTIONS':
# actual request; reply with the actual response
return fn(*args, **kwargs)
return _enable_cors

View File

@ -65,7 +65,7 @@ static
|
|--- public # path where builds are put
|
|--- templates (bottle)
|--- templates
```
## Data

View File

@ -470,7 +470,7 @@ class PluginModel(GlancesPluginModel):
# Process list
# Loop over processes (sorted by the sort key previously compute)
# This is a Glances bottleneck (see flame graph),
# get_process_curses_data should be optimzed
# TODO: get_process_curses_data should be optimzed
for position, process in enumerate(processes_list_sorted):
ret.extend(self.get_process_curses_data(process, position == args.cursor_position, args))

View File

@ -2,17 +2,17 @@
#
# This file is part of Glances.
#
# SPDX-FileCopyrightText: 2022 Nicolas Hennion <nicolas@nicolargo.com>
# SPDX-FileCopyrightText: 2023 Nicolas Hennion <nicolas@nicolargo.com>
#
# SPDX-License-Identifier: LGPL-3.0-only
#
"""Glances Web Interface (Bottle based)."""
"""Glances Restful/API and Web based interface."""
from glances.globals import WINDOWS
from glances.processes import glances_processes
from glances.stats import GlancesStats
from glances.outputs.glances_bottle import GlancesBottle
from glances.outputs.glances_restful_api import GlancesRestfulApi
class GlancesWebServer(object):
@ -30,8 +30,8 @@ class GlancesWebServer(object):
# Initial system information update
self.stats.update()
# Init the Bottle Web server
self.web = GlancesBottle(config=config, args=args)
# Init the Web server
self.web = GlancesRestfulApi(config=config, args=args)
def serve_forever(self):
"""Main loop for the Web server."""

View File

@ -3,11 +3,11 @@
batinfo
bernhard
bottle
cassandra-driver
chevron
docker>=6.1.1
elasticsearch
fastapi; python_version >= "3.8"
graphitesender
hddtemp
influxdb>=1.0.0 # For InfluxDB < 1.8
@ -34,6 +34,7 @@ scandir; python_version < "3.5"
six
sparklines
statsd
uvicorn; python_version >= "3.8"
wifi
zeroconf==0.112.0; python_version < "3.7"
zeroconf; python_version >= "3.7"

View File

@ -1,7 +1,5 @@
psutil>=5.6.7
defusedxml
packaging
ujson<4; python_version >= "3.5" and python_version < "3.6"
ujson<5; python_version >= "3.6" and python_version < "3.7"
ujson>=5.4.0; python_version >= "3.7"
ujson>=5.4.0
pytz

View File

@ -44,7 +44,8 @@ def get_install_requires():
'ujson>=5.4.0',
]
if sys.platform.startswith('win'):
requires.append('bottle')
requires.append('fastapi')
requires.append('uvicorn')
requires.append('requests')
return requires
@ -67,7 +68,7 @@ def get_install_extras_require():
'smart': ['pySMART.smartx'],
'snmp': ['pysnmp'],
'sparklines': ['sparklines'],
'web': ['bottle', 'requests'],
'web': ['fastapi', 'uvicorn', 'requests'],
'wifi': ['wifi']
}
if sys.platform.startswith('linux'):
@ -123,7 +124,7 @@ setup(
'Development Status :: 5 - Production/Stable',
'Environment :: Console :: Curses',
'Environment :: Web Environment',
'Framework :: Bottle',
'Framework :: FastAPI',
'Intended Audience :: Developers',
'Intended Audience :: End Users/Desktop',
'Intended Audience :: System Administrators',

View File

@ -53,16 +53,28 @@ parts:
override-pull: |
snapcraftctl pull
"$SNAPCRAFT_STAGE"/scriptlets/selective-checkout
bottle:
fastapi:
plugin: python
source: https://github.com/bottlepy/bottle.git
source-branch: release-0.12
source: https://github.com/tiangolo/fastapi.git
source-tag: '0.104.1'
source-depth: 1
override-build: |
mkdir -p $SNAPCRAFT_PART_BUILD/dist
cp -r $SNAPCRAFT_PART_BUILD/dist $SNAPCRAFT_PART_INSTALL/bottle-dist
cp -r $SNAPCRAFT_PART_BUILD/dist $SNAPCRAFT_PART_INSTALL/fastapi-dist
organize:
bottle-dist: bottle/dist
fastapi-dist: fastapi/dist
uvicorn:
plugin: python
source: https://github.com/encode/uvicorn.git
source-tag: '0.24.0.post1'
source-depth: 1
override-build: |
mkdir -p $SNAPCRAFT_PART_BUILD/dist
cp -r $SNAPCRAFT_PART_BUILD/dist $SNAPCRAFT_PART_INSTALL/uvicorn-dist
organize:
uvicorn-dist: uvicorn/dist
docker:
plugin: python

View File

@ -19,7 +19,8 @@ deps =
defusedxml
packaging
ujson
bottle
fastapi
uvicorn
requests
commands =
python unitest.py

View File

@ -1,4 +1,5 @@
# install with base requirements file
-r requirements.txt
bottle
fastapi; python_version >= "3.8"
uvicorn; python_version >= "3.8"