diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 88385df1..024a37bc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -123,19 +123,12 @@ jobs: - name: Retrieve Repository Docker metadata id: docker_meta - uses: crazy-max/ghaction-docker-meta@v5.0.0 + uses: docker/metadata-action@v5 with: images: ${{ env.DEFAULT_DOCKER_IMAGE }} labels: | org.opencontainers.image.url=https://nicolargo.github.io/glances/ - - name: Cache Docker layers - uses: actions/cache@v4 - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ env.NODE_ENV }}-${{ matrix.os }}-${{ matrix.tag.tag }} - restore-keys: ${{ runner.os }}-buildx-${{ env.NODE_ENV }}-${{ matrix.os }} - - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: @@ -166,5 +159,7 @@ jobs: platforms: ${{ matrix.os != 'ubuntu' && env.DOCKER_PLATFORMS || env.DOCKER_PLATFORMS_UBUNTU }} target: ${{ matrix.tag.target }} labels: ${{ steps.docker_meta.outputs.labels }} - cache-from: type=local,src=/tmp/.buildx-cache - cache-to: type=local,dest=/tmp/.buildx-cache,mode=max + # GHA default behaviour overwrites last build cache. Causes alpine and ubuntu cache to overwrite each other. + # Use `scope` with the os name to prevent that + cache-from: 'type=gha,scope=${{ matrix.os }}' + cache-to: 'type=gha,mode=max,scope=${{ matrix.os }}' diff --git a/NEWS.rst b/NEWS.rst index e58a9c14..08f9519b 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -48,6 +48,62 @@ Version 4.0.2 Thanks to RazCrimson for the sensors patch ! +=============== +Version 4.0.8 +=============== + +* Make CORS option configurable security webui #2812 +* When Glances is installed via venv, default configuration file is not used documentation packaging #2803 +* GET /1272f6e9e8f9d6bfd6de.png results in 404 bug webui #2781 by Emporea was closed May 25, 2024 +* Screen frequently flickers when outputting to local display bug needs test #2490 +* Retire ujson for being in maintenance mode dependencies enhancement #2791 + +=============== +Version 4.0.7 +=============== + +* cpu_hz_current not available on NetBSD #2792 +* SensorType change in REST API breaks compatibility in 4.0.4 #2788 + +=============== +Version 4.0.6 +=============== + +* No GPU info on Web View #2796 + +=============== +Version 4.0.5 +=============== + +* SensorType change in REST API breaks compatibility in 4.0.4 #2788 +* Please make pydantic optional dependency, not required one #2777 +* Update the Grafana dashboard #2780 +* 4.0.4 - On Glances startup "ERROR -- Can not init battery class #2776 +* In codeSpace (with Python 3.8), an error occurs in ./unittest-restful.py #2773 + +Use Ruff as default Linter. + +=============== +Version 4.0.4 +=============== + +Hostfix release for support sensors plugin on python 3.8 + +=============== +Version 4.0.3 +=============== + +Additional fixes for Sensor plugin + +=============== +Version 4.0.2 +=============== + +* hotfix: plugin(sensors) - race conditions btw fan_speed & temperature… #2766 +* fix: include requirements.txt and SECURITY.md for pypi dist #2761 + +Thanks to RazCrimson for the sensors patch ! + =============== Version 4.0.1 =============== diff --git a/README.rst b/README.rst index 5331994d..7bee0ed3 100644 --- a/README.rst +++ b/README.rst @@ -86,7 +86,7 @@ Requirements - ``psutil`` (better with latest version) - ``defusedxml`` (in order to monkey patch xmlrpc) - ``packaging`` (for the version comparison) -- ``ujson`` (an optimized alternative to the standard json module) +- ``orjson`` (an optimized alternative to the standard json module) *Note for Python 2 users* @@ -115,7 +115,6 @@ Optional dependencies: - ``podman`` (for the Containers Podman monitoring support) - ``potsdb`` (for the OpenTSDB export module) - ``prometheus_client`` (for the Prometheus export module) -- ``py-cpuinfo`` (for the Quicklook CPU info module) - ``pygal`` (for the graph export module) - ``pymdstat`` (for RAID support) [Linux-only] - ``pymongo`` (for the MongoDB export module) diff --git a/conf/glances.conf b/conf/glances.conf index 83f0dac3..4de3d3a9 100644 --- a/conf/glances.conf +++ b/conf/glances.conf @@ -23,12 +23,16 @@ history_size=1200 ############################################################################## [outputs] +# Options for all UIs +#-------------------- # Separator in the Curses and WebUI interface (between top and others plugins) separator=True # Set the the Curses and WebUI interface left menu plugin list (comma-separated) #left_menu=network,wifi,connections,ports,diskio,fs,irq,folders,raid,smart,sensors,now # Limit the number of processes to display (in the WebUI) max_processes_display=25 +# Options for WebUI +#------------------ # Set URL prefix for the WebUI and the API # Example: url_prefix=/glances/ => http://localhost/glances/ # Note: The final / is mandatory @@ -41,9 +45,22 @@ max_processes_display=25 # then configure this folder with the webui_root_path key # Default is folder where glances_restfull_api.py is hosted #webui_root_path= +# CORS options +# Comma separated list of origins that should be permitted to make cross-origin requests. +# Default is * +#cors_origins=* +# Indicate that cookies should be supported for cross-origin requests. +# Default is True +#cors_credentials=True +# Comma separated list of HTTP methods that should be allowed for cross-origin requests. +# Default is * +#cors_methods=* +# Comma separated list of HTTP request headers that should be supported for cross-origin requests. +# Default is * +#cors_headers=* ############################################################################## -# plugins +# Plugins ############################################################################## [quicklook] @@ -199,6 +216,10 @@ tx_critical=90 hide=docker.*,lo # Define the list of wireless network interfaces to be show (comma-separated) #show=docker.* +# Automatically hide interface not up (default is False) +#hide_no_up=True +# Automatically hide interface with no IP address (default is False) +#hide_no_ip=True # It is possible to overwrite the bitrate thresholds per interface # WLAN 0 Default limits (in bits per second aka bps) for interface bitrate #wlan0_rx_careful=4000000 @@ -765,13 +786,13 @@ refresh=3 countmax=20 [amp_conntrack] -# Use comma separated for multiple commands (no space around the comma) +# Use && separator for multiple commands # If the regex key is not defined, the AMP will be executed every refresh second # and the process count will not be displayed (countmin and countmax will be ignore) enable=false refresh=30 one_line=false -command=sysctl net.netfilter.nf_conntrack_count;sysctl net.netfilter.nf_conntrack_max +command=sysctl net.netfilter.nf_conntrack_count && sysctl net.netfilter.nf_conntrack_max [amp_nginx] # Use the NGinx AMP diff --git a/doc-requirements.txt b/doc-requirements.txt index 804d5398..74d6796a 100644 --- a/doc-requirements.txt +++ b/doc-requirements.txt @@ -1,5 +1,5 @@ +orjson reuse setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid a vulnerability sphinx sphinx_rtd_theme -ujson diff --git a/docker-compose/glances.conf b/docker-compose/glances.conf index 0686b528..792a155d 100755 --- a/docker-compose/glances.conf +++ b/docker-compose/glances.conf @@ -23,12 +23,16 @@ history_size=1200 ############################################################################## [outputs] +# Options for all UIs +#-------------------- # Separator in the Curses and WebUI interface (between top and others plugins) separator=True # Set the the Curses and WebUI interface left menu plugin list (comma-separated) #left_menu=network,wifi,connections,ports,diskio,fs,irq,folders,raid,smart,sensors,now # Limit the number of processes to display (in the WebUI) max_processes_display=25 +# Options for WebUI +#------------------ # Set URL prefix for the WebUI and the API # Example: url_prefix=/glances/ => http://localhost/glances/ # Note: The final / is mandatory @@ -41,6 +45,19 @@ max_processes_display=25 # then configure this folder with the webui_root_path key # Default is folder where glances_restfull_api.py is hosted #webui_root_path= +# CORS options +# Comma separated list of origins that should be permitted to make cross-origin requests. +# Default is * +#cors_origins=* +# Indicate that cookies should be supported for cross-origin requests. +# Default is True +#cors_credentials=True +# Comma separated list of HTTP methods that should be allowed for cross-origin requests. +# Default is * +#cors_methods=* +# Comma separated list of HTTP request headers that should be supported for cross-origin requests. +# Default is * +#cors_headers=* ############################################################################## # plugins @@ -199,6 +216,10 @@ tx_critical=90 #hide=docker.*,lo # Define the list of wireless network interfaces to be show (comma-separated) #show=docker.* +# Automatically hide interface not up (default is False) +hide_no_up=True +# Automatically hide interface with no IP address (default is False) +hide_no_ip=True # It is possible to overwrite the bitrate thresholds per interface # WLAN 0 Default limits (in bits per second aka bps) for interface bitrate #wlan0_rx_careful=4000000 @@ -765,13 +786,13 @@ refresh=3 countmax=20 [amp_conntrack] -# Use comma separated for multiple commands (no space around the comma) +# Use && separator for multiple commands # If the regex key is not defined, the AMP will be executed every refresh second # and the process count will not be displayed (countmin and countmax will be ignore) enable=false refresh=30 one_line=false -command=sysctl net.netfilter.nf_conntrack_count;sysctl net.netfilter.nf_conntrack_max +command=sysctl net.netfilter.nf_conntrack_count && sysctl net.netfilter.nf_conntrack_max [amp_nginx] # Use the NGinx AMP diff --git a/docker-requirements.txt b/docker-requirements.txt index feac4040..d73baf4f 100644 --- a/docker-requirements.txt +++ b/docker-requirements.txt @@ -1,9 +1,8 @@ # install with base requirements file -r requirements.txt -docker>=6.1.1; python_version >= "3.7" -packaging; python_version >= "3.7" -podman; python_version >= "3.6" +docker>=6.1.1 +podman python-dateutil requests six diff --git a/docs/aoa/amps.rst b/docs/aoa/amps.rst index be1d2f02..83f03c65 100644 --- a/docs/aoa/amps.rst +++ b/docs/aoa/amps.rst @@ -61,9 +61,11 @@ For example: enable=false refresh=30 one_line=false - command=sysctl net.netfilter.nf_conntrack_count;sysctl net.netfilter.nf_conntrack_max + command=sysctl net.netfilter.nf_conntrack_count && sysctl net.netfilter.nf_conntrack_max -For security reason, pipe is not directly allowed in a AMP command but you create a sheel +Note: for multiple command, please use the '&&'' separator. + +For security reason, pipe is not directly allowed in a AMP command but you create a shell script with your command: .. code-block:: ini diff --git a/docs/aoa/network.rst b/docs/aoa/network.rst index a1f116ac..d6b02d91 100644 --- a/docs/aoa/network.rst +++ b/docs/aoa/network.rst @@ -17,6 +17,8 @@ In this case thresholds values are define in bps. Additionally, you can define: - a list of network interfaces to hide +- automatically hide interfaces not up +- automatically hide interfaces without IP address - per-interface limit values - aliases for interface name @@ -41,6 +43,10 @@ virtual docker interface (docker0, docker1, ...): hide=docker.*,lo # Define the list of network interfaces to show (comma-separated regexp) #show=eth0,eth1 + # Automatically hide interface not up (default is False) + hide_no_up=True + # Automatically hide interface with no IP address (default is False) + hide_no_ip=True # WLAN 0 alias wlan0_alias=Wireless IF # It is possible to overwrite the bitrate thresholds per interface diff --git a/docs/api.rst b/docs/api.rst index b05afe3d..e6b058fa 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -141,7 +141,7 @@ Get plugin stats:: "refresh": 3.0, "regex": True, "result": None, - "timer": 0.17942380905151367}, + "timer": 0.24439311027526855}, {"count": 0, "countmax": 20.0, "countmin": None, @@ -150,7 +150,7 @@ Get plugin stats:: "refresh": 3.0, "regex": True, "result": None, - "timer": 0.17932724952697754}] + "timer": 0.2443389892578125}] Fields descriptions: @@ -178,7 +178,7 @@ Get a specific item when field matches the given value:: "refresh": 3.0, "regex": True, "result": None, - "timer": 0.17942380905151367}]} + "timer": 0.24439311027526855}]} GET cloud --------- @@ -219,7 +219,21 @@ GET containers Get plugin stats:: # curl http://localhost:61208/api/4/containers - [] + [{"command": "/bin/sh -c /venv/bin/python3 -m glances $GLANCES_OPT", + "cpu": {"total": 0.0}, + "cpu_percent": 0.0, + "created": "2024-05-25T13:52:22.535373707Z", + "engine": "docker", + "id": "bb99d31288db8904ed4cd43db8255a926830936189bc180d77c3459cbaa7f490", + "image": ["nicolargo/glances:latest"], + "io": {}, + "key": "name", + "memory": {}, + "memory_usage": None, + "name": "wizardly_nightingale", + "network": {}, + "status": "running", + "uptime": "14 mins"}] Fields descriptions: @@ -240,6 +254,31 @@ Fields descriptions: * **pod_name**: Pod name (only with Podman) (unit is *None*) * **pod_id**: Pod ID (only with Podman) (unit is *None*) +Get a specific field:: + + # curl http://localhost:61208/api/4/containers/name + {"name": ["wizardly_nightingale"]} + +Get a specific item when field matches the given value:: + + # curl http://localhost:61208/api/4/containers/name/wizardly_nightingale + {"wizardly_nightingale": [{"command": "/bin/sh -c /venv/bin/python3 -m glances " + "$GLANCES_OPT", + "cpu": {"total": 0.0}, + "cpu_percent": 0.0, + "created": "2024-05-25T13:52:22.535373707Z", + "engine": "docker", + "id": "bb99d31288db8904ed4cd43db8255a926830936189bc180d77c3459cbaa7f490", + "image": ["nicolargo/glances:latest"], + "io": {}, + "key": "name", + "memory": {}, + "memory_usage": None, + "name": "wizardly_nightingale", + "network": {}, + "status": "running", + "uptime": "14 mins"}]} + GET core -------- @@ -265,18 +304,18 @@ Get plugin stats:: # curl http://localhost:61208/api/4/cpu {"cpucore": 16, - "ctx_switches": 247960513, + "ctx_switches": 7772781, "guest": 0.0, - "idle": 0.0, - "interrupts": 235154276, + "idle": 3.0, + "interrupts": 6643340, "iowait": 0.0, "irq": 0.0, "nice": 0.0, - "soft_interrupts": 79848589, + "soft_interrupts": 1761276, "steal": 0.0, "syscalls": 0, "system": 0.0, - "total": 0.0, + "total": 16.7, "user": 1.0} Fields descriptions: @@ -310,7 +349,7 @@ Fields descriptions: Get a specific field:: # curl http://localhost:61208/api/4/cpu/total - {"total": 0.0} + {"total": 16.7} GET diskio ---------- @@ -320,14 +359,14 @@ Get plugin stats:: # curl http://localhost:61208/api/4/diskio [{"disk_name": "nvme0n1", "key": "disk_name", - "read_bytes": 4854106624, - "read_count": 207109, - "write_bytes": 15446680576, - "write_count": 891078}, + "read_bytes": 3983976960, + "read_count": 115648, + "write_bytes": 3073111040, + "write_count": 91409}, {"disk_name": "nvme0n1p1", "key": "disk_name", - "read_bytes": 7484416, - "read_count": 592, + "read_bytes": 7476224, + "read_count": 576, "write_bytes": 1024, "write_count": 2}] @@ -363,10 +402,10 @@ Get a specific item when field matches the given value:: # curl http://localhost:61208/api/4/diskio/disk_name/nvme0n1 {"nvme0n1": [{"disk_name": "nvme0n1", "key": "disk_name", - "read_bytes": 4854106624, - "read_count": 207109, - "write_bytes": 15446680576, - "write_count": 891078}]} + "read_bytes": 3983976960, + "read_count": 115648, + "write_bytes": 3073111040, + "write_count": 91409}]} GET folders ----------- @@ -393,13 +432,13 @@ Get plugin stats:: # curl http://localhost:61208/api/4/fs [{"device_name": "/dev/mapper/ubuntu--vg-ubuntu--lv", - "free": 904231424000, + "free": 902284546048, "fs_type": "ext4", "key": "mnt_point", "mnt_point": "/", - "percent": 5.1, + "percent": 5.3, "size": 1003736440832, - "used": 48442511360}] + "used": 50389389312}] Fields descriptions: @@ -420,13 +459,13 @@ Get a specific item when field matches the given value:: # curl http://localhost:61208/api/4/fs/mnt_point// {"/": [{"device_name": "/dev/mapper/ubuntu--vg-ubuntu--lv", - "free": 904231424000, + "free": 902284546048, "fs_type": "ext4", "key": "mnt_point", "mnt_point": "/", - "percent": 5.1, + "percent": 5.3, "size": 1003736440832, - "used": 48442511360}]} + "used": 50389389312}]} GET gpu ------- @@ -500,9 +539,9 @@ Get plugin stats:: # curl http://localhost:61208/api/4/load {"cpucore": 16, - "min1": 0.2802734375, - "min15": 0.6240234375, - "min5": 0.61376953125} + "min1": 0.560546875, + "min15": 0.54833984375, + "min5": 0.70166015625} Fields descriptions: @@ -514,7 +553,7 @@ Fields descriptions: Get a specific field:: # curl http://localhost:61208/api/4/load/min1 - {"min1": 0.2802734375} + {"min1": 0.560546875} GET mem ------- @@ -522,16 +561,16 @@ GET mem Get plugin stats:: # curl http://localhost:61208/api/4/mem - {"active": 8875126784, - "available": 7975804928, - "buffers": 463708160, - "cached": 7862255616, - "free": 7975804928, - "inactive": 4694634496, - "percent": 51.4, - "shared": 849006592, + {"active": 5540470784, + "available": 11137699840, + "buffers": 226918400, + "cached": 6333566976, + "free": 11137699840, + "inactive": 3872489472, + "percent": 32.2, + "shared": 656637952, "total": 16422486016, - "used": 8446681088} + "used": 5284786176} Fields descriptions: @@ -558,13 +597,13 @@ GET memswap Get plugin stats:: # curl http://localhost:61208/api/4/memswap - {"free": 4293914624, + {"free": 4294963200, "percent": 0.0, "sin": 0, - "sout": 114688, + "sout": 0, "time_since_update": 1, "total": 4294963200, - "used": 1048576} + "used": 0} Fields descriptions: @@ -589,15 +628,26 @@ Get plugin stats:: # curl http://localhost:61208/api/4/network [{"alias": None, "bytes_all": 0, - "bytes_all_gauge": 1608088786, + "bytes_all_gauge": 422462144, "bytes_recv": 0, - "bytes_recv_gauge": 1294928139, + "bytes_recv_gauge": 413272561, "bytes_sent": 0, - "bytes_sent_gauge": 313160647, + "bytes_sent_gauge": 9189583, "interface_name": "wlp0s20f3", "key": "interface_name", "speed": 0, - "time_since_update": 0.18457698822021484}] + "time_since_update": 0.24814295768737793}, + {"alias": None, + "bytes_all": 0, + "bytes_all_gauge": 18987, + "bytes_recv": 0, + "bytes_recv_gauge": 528, + "bytes_sent": 0, + "bytes_sent_gauge": 18459, + "interface_name": "vethfc47299", + "key": "interface_name", + "speed": 10485760000, + "time_since_update": 0.24814295768737793}] Fields descriptions: @@ -619,22 +669,22 @@ Fields descriptions: Get a specific field:: # curl http://localhost:61208/api/4/network/interface_name - {"interface_name": ["wlp0s20f3"]} + {"interface_name": ["wlp0s20f3", "vethfc47299"]} Get a specific item when field matches the given value:: # curl http://localhost:61208/api/4/network/interface_name/wlp0s20f3 {"wlp0s20f3": [{"alias": None, "bytes_all": 0, - "bytes_all_gauge": 1608088786, + "bytes_all_gauge": 422462144, "bytes_recv": 0, - "bytes_recv_gauge": 1294928139, + "bytes_recv_gauge": 413272561, "bytes_sent": 0, - "bytes_sent_gauge": 313160647, + "bytes_sent_gauge": 9189583, "interface_name": "wlp0s20f3", "key": "interface_name", "speed": 0, - "time_since_update": 0.18457698822021484}]} + "time_since_update": 0.24814295768737793}]} GET now ------- @@ -642,7 +692,7 @@ GET now Get plugin stats:: # curl http://localhost:61208/api/4/now - {"custom": "2024-05-25 16:10:02 CEST", "iso": "2024-05-25T16:10:02+02:00"} + {"custom": "2024-06-08 10:22:29 CEST", "iso": "2024-06-08T10:22:29+02:00"} Fields descriptions: @@ -652,7 +702,7 @@ Fields descriptions: Get a specific field:: # curl http://localhost:61208/api/4/now/iso - {"iso": "2024-05-25T16:10:02+02:00"} + {"iso": "2024-06-08T10:22:29+02:00"} GET percpu ---------- @@ -719,7 +769,7 @@ Get plugin stats:: "port": 0, "refresh": 30, "rtt_warning": None, - "status": 0.005044, + "status": 0.005593, "timeout": 3}] Fields descriptions: @@ -747,7 +797,7 @@ Get a specific item when field matches the given value:: "port": 0, "refresh": 30, "rtt_warning": None, - "status": 0.005044, + "status": 0.005593, "timeout": 3}]} GET processcount @@ -756,7 +806,7 @@ GET processcount Get plugin stats:: # curl http://localhost:61208/api/4/processcount - {"pid_max": 0, "running": 0, "sleeping": 292, "thread": 1710, "total": 432} + {"pid_max": 0, "running": 0, "sleeping": 279, "thread": 1568, "total": 410} Fields descriptions: @@ -769,7 +819,7 @@ Fields descriptions: Get a specific field:: # curl http://localhost:61208/api/4/processcount/total - {"total": 432} + {"total": 410} GET processlist --------------- @@ -809,14 +859,14 @@ GET quicklook Get plugin stats:: # curl http://localhost:61208/api/4/quicklook - {"cpu": 0.0, + {"cpu": 16.7, "cpu_hz": 4475000000.0, - "cpu_hz_current": 1610092062.5, + "cpu_hz_current": 1230624312.5, "cpu_log_core": 16, "cpu_name": "13th Gen Intel(R) Core(TM) i7-13620H", "cpu_phys_core": 10, - "load": 3.9, - "mem": 51.5, + "load": 3.4, + "mem": 32.1, "percpu": [{"cpu_number": 0, "guest": 0.0, "guest_nice": 0.0, @@ -924,7 +974,7 @@ Get plugin stats:: {"cpu_number": 8, "guest": 0.0, "guest_nice": 0.0, - "idle": 0.0, + "idle": 1.0, "iowait": 0.0, "irq": 0.0, "key": "cpu_number", @@ -932,7 +982,7 @@ Get plugin stats:: "softirq": 0.0, "steal": 0.0, "system": 0.0, - "total": 100.0, + "total": 99.0, "user": 0.0}, {"cpu_number": 9, "guest": 0.0, @@ -1063,14 +1113,14 @@ Get plugin stats:: "label": "Ambient", "type": "temperature_core", "unit": "C", - "value": 38, + "value": 36, "warning": 0}, {"critical": None, "key": "label", "label": "Ambient 3", "type": "temperature_core", "unit": "C", - "value": 35, + "value": 29, "warning": 0}] Fields descriptions: @@ -1131,7 +1181,7 @@ Get a specific item when field matches the given value:: "label": "Ambient", "type": "temperature_core", "unit": "C", - "value": 38, + "value": 36, "warning": 0}]} GET smart @@ -1175,7 +1225,7 @@ GET uptime Get plugin stats:: # curl http://localhost:61208/api/4/uptime - "11 days, 17:02:42" + "0:14:44" GET version ----------- @@ -1183,7 +1233,7 @@ GET version Get plugin stats:: # curl http://localhost:61208/api/4/version - "4.0.7" + "4.0.8" GET wifi -------- @@ -1192,8 +1242,8 @@ Get plugin stats:: # curl http://localhost:61208/api/4/wifi [{"key": "ssid", - "quality_level": -62.0, - "quality_link": 48.0, + "quality_level": -66.0, + "quality_link": 44.0, "ssid": "wlp0s20f3"}] Get a specific field:: @@ -1205,8 +1255,8 @@ Get a specific item when field matches the given value:: # curl http://localhost:61208/api/4/wifi/ssid/wlp0s20f3 {"wlp0s20f3": [{"key": "ssid", - "quality_level": -62.0, - "quality_link": 48.0, + "quality_level": -66.0, + "quality_link": 44.0, "ssid": "wlp0s20f3"}]} GET all stats @@ -1251,34 +1301,34 @@ GET stats history History of a plugin:: # curl http://localhost:61208/api/4/cpu/history - {"system": [["2024-05-25T16:10:03.933630", 0.0], - ["2024-05-25T16:10:04.963537", 0.0], - ["2024-05-25T16:10:06.018120", 0.0]], - "user": [["2024-05-25T16:10:03.933619", 1.0], - ["2024-05-25T16:10:04.963532", 1.0], - ["2024-05-25T16:10:06.018110", 1.0]]} + {"system": [["2024-06-08T10:22:30.631056", 0.0], + ["2024-06-08T10:22:31.667772", 1.0], + ["2024-06-08T10:22:32.723737", 1.0]], + "user": [["2024-06-08T10:22:30.631046", 1.0], + ["2024-06-08T10:22:31.667765", 1.0], + ["2024-06-08T10:22:32.723728", 1.0]]} Limit history to last 2 values:: # curl http://localhost:61208/api/4/cpu/history/2 - {"system": [["2024-05-25T16:10:04.963537", 0.0], - ["2024-05-25T16:10:06.018120", 0.0]], - "user": [["2024-05-25T16:10:04.963532", 1.0], - ["2024-05-25T16:10:06.018110", 1.0]]} + {"system": [["2024-06-08T10:22:31.667772", 1.0], + ["2024-06-08T10:22:32.723737", 1.0]], + "user": [["2024-06-08T10:22:31.667765", 1.0], + ["2024-06-08T10:22:32.723728", 1.0]]} History for a specific field:: # curl http://localhost:61208/api/4/cpu/system/history - {"system": [["2024-05-25T16:10:02.825532", 0.0], - ["2024-05-25T16:10:03.933630", 0.0], - ["2024-05-25T16:10:04.963537", 0.0], - ["2024-05-25T16:10:06.018120", 0.0]]} + {"system": [["2024-06-08T10:22:29.515717", 0.0], + ["2024-06-08T10:22:30.631056", 0.0], + ["2024-06-08T10:22:31.667772", 1.0], + ["2024-06-08T10:22:32.723737", 1.0]]} Limit history for a specific field to last 2 values:: # curl http://localhost:61208/api/4/cpu/system/history - {"system": [["2024-05-25T16:10:04.963537", 0.0], - ["2024-05-25T16:10:06.018120", 0.0]]} + {"system": [["2024-06-08T10:22:31.667772", 1.0], + ["2024-06-08T10:22:32.723737", 1.0]]} GET limits (used for thresholds) -------------------------------- diff --git a/docs/config.rst b/docs/config.rst index 53d6d84f..0c87a81b 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -21,6 +21,7 @@ You can place your ``glances.conf`` file in the following locations: ``*BSD`` ~/.config/glances/, /usr/local/etc/glances/, /usr/share/docs/glances/ ``macOS`` ~/.config/glances/, ~/Library/Application Support/glances/, /usr/local/etc/glances/, /usr/share/docs/glances/ ``Windows`` %APPDATA%\\glances\\glances.conf +``All`` + /share/doc/glances/ ==================== ============================================================= - On Windows XP, ``%APPDATA%`` is: ``C:\Documents and Settings\\Application Data``. @@ -59,17 +60,41 @@ than a second one concerning the user interface: .. code-block:: ini [outputs] + # Options for all UIs + #-------------------- # Separator in the Curses and WebUI interface (between top and others plugins) separator=True # Set the the Curses and WebUI interface left menu plugin list (comma-separated) #left_menu=network,wifi,connections,ports,diskio,fs,irq,folders,raid,smart,sensors,now - # Limit the number of processes to display (for the WebUI) + # Limit the number of processes to display (in the WebUI) max_processes_display=25 - # Set the URL prefix (for the WebUI and the API) + # Options for WebUI + #------------------ + # Set URL prefix for the WebUI and the API # Example: url_prefix=/glances/ => http://localhost/glances/ - # The final / is mandatory + # Note: The final / is mandatory # Default is no prefix (/) #url_prefix=/glances/ + # Set root path for WebUI statics files + # Why ? On Debian system, WebUI statics files are not provided. + # You can download it in a specific folder + # thanks to https://github.com/nicolargo/glances/issues/2021 + # then configure this folder with the webui_root_path key + # Default is folder where glances_restfull_api.py is hosted + #webui_root_path= + # CORS options + # Comma separated list of origins that should be permitted to make cross-origin requests. + # Default is * + #cors_origins=* + # Indicate that cookies should be supported for cross-origin requests. + # Default is True + #cors_credentials=True + # Comma separated list of HTTP methods that should be allowed for cross-origin requests. + # Default is * + #cors_methods=* + # Comma separated list of HTTP request headers that should be supported for cross-origin requests. + # Default is * + #cors_headers=* Each plugin, export module, and application monitoring process (AMP) can have a section. Below is an example for the CPU plugin: diff --git a/docs/man/glances.1 b/docs/man/glances.1 index ccb2eee6..5927396d 100644 --- a/docs/man/glances.1 +++ b/docs/man/glances.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "GLANCES" "1" "May 25, 2024" "4.0.7" "Glances" +.TH "GLANCES" "1" "Jun 08, 2024" "4.0.8" "Glances" .SH NAME glances \- An eye on your system .SH SYNOPSIS @@ -585,6 +585,15 @@ T} T{ %APPDATA%\eglances\eglances.conf T} _ +T{ +\fBAll\fP +T} T{ +.INDENT 0.0 +.IP \(bu 2 +/share/doc/glances/ +.UNINDENT +T} +_ .TE .INDENT 0.0 .IP \(bu 2 @@ -632,17 +641,41 @@ than a second one concerning the user interface: .nf .ft C [outputs] +# Options for all UIs +#\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- # Separator in the Curses and WebUI interface (between top and others plugins) separator=True # Set the the Curses and WebUI interface left menu plugin list (comma\-separated) #left_menu=network,wifi,connections,ports,diskio,fs,irq,folders,raid,smart,sensors,now -# Limit the number of processes to display (for the WebUI) +# Limit the number of processes to display (in the WebUI) max_processes_display=25 -# Set the URL prefix (for the WebUI and the API) +# Options for WebUI +#\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- +# Set URL prefix for the WebUI and the API # Example: url_prefix=/glances/ => http://localhost/glances/ -# The final / is mandatory +# Note: The final / is mandatory # Default is no prefix (/) #url_prefix=/glances/ +# Set root path for WebUI statics files +# Why ? On Debian system, WebUI statics files are not provided. +# You can download it in a specific folder +# thanks to https://github.com/nicolargo/glances/issues/2021 +# then configure this folder with the webui_root_path key +# Default is folder where glances_restfull_api.py is hosted +#webui_root_path= +# CORS options +# Comma separated list of origins that should be permitted to make cross\-origin requests. +# Default is * +#cors_origins=* +# Indicate that cookies should be supported for cross\-origin requests. +# Default is True +#cors_credentials=True +# Comma separated list of HTTP methods that should be allowed for cross\-origin requests. +# Default is * +#cors_methods=* +# Comma separated list of HTTP request headers that should be supported for cross\-origin requests. +# Default is * +#cors_headers=* .ft P .fi .UNINDENT diff --git a/glances/__init__.py b/glances/__init__.py index 0b9c5d86..b1a0ec31 100644 --- a/glances/__init__.py +++ b/glances/__init__.py @@ -19,7 +19,7 @@ import tracemalloc # Global name # Version should start and end with a numerical char # See https://packaging.python.org/specifications/core-metadata/#version -__version__ = '4.0.7' +__version__ = '4.0.8' __apiversion__ = '4' __author__ = 'Nicolas Hennion ' __license__ = 'LGPLv3' diff --git a/glances/amps/default/__init__.py b/glances/amps/default/__init__.py index 793847cd..8d3f4fee 100644 --- a/glances/amps/default/__init__.py +++ b/glances/amps/default/__init__.py @@ -24,11 +24,9 @@ one_line=false command=foo status """ -from subprocess import STDOUT, CalledProcessError, check_output - from glances.amps.amp import GlancesAmp -from glances.globals import to_ascii, u from glances.logger import logger +from glances.secure import secure_popen class Amp(GlancesAmp): @@ -68,10 +66,7 @@ class Amp(GlancesAmp): # Run command(s) # Comma separated commands can be executed try: - msg = '' - for cmd in res.split(';'): - msg += u(check_output(cmd.split(), stderr=STDOUT)) - self.set_result(to_ascii(msg.rstrip())) - except CalledProcessError as e: + self.set_result(secure_popen(res).rstrip()) + except Exception as e: self.set_result(e.output) return self.result() diff --git a/glances/amps/systemv/__init__.py b/glances/amps/systemv/__init__.py index 7d53d568..4b0d91a7 100644 --- a/glances/amps/systemv/__init__.py +++ b/glances/amps/systemv/__init__.py @@ -33,11 +33,10 @@ one_line=true service_cmd=/usr/bin/service --status-all """ -from subprocess import STDOUT, check_output - from glances.amps.amp import GlancesAmp from glances.globals import iteritems from glances.logger import logger +from glances.secure import secure_popen class Amp(GlancesAmp): @@ -58,8 +57,9 @@ class Amp(GlancesAmp): # Get the systemctl status logger.debug('{}: Update stats using service {}'.format(self.NAME, self.get('service_cmd'))) try: - res = check_output(self.get('service_cmd').split(), stderr=STDOUT).decode('utf-8') - except OSError as e: + # res = check_output(self.get('service_cmd').split(), stderr=STDOUT).decode('utf-8') + res = secure_popen(self.get('service_cmd')) + except Exception as e: logger.debug(f'{self.NAME}: Error while executing service ({e})') else: status = {'running': 0, 'stopped': 0, 'upstart': 0} diff --git a/glances/client.py b/glances/client.py index a73fedb0..308f2061 100644 --- a/glances/client.py +++ b/glances/client.py @@ -11,7 +11,7 @@ import sys import time -import ujson +import orjson from glances import __version__ from glances.globals import Fault, ProtocolError, ServerProxy, Transport @@ -118,7 +118,7 @@ class GlancesClient: if __version__.split('.')[0] == client_version.split('.')[0]: # Init stats self.stats = GlancesStatsClient(config=self.config, args=self.args) - self.stats.set_plugins(ujson.loads(self.client.getAllPlugins())) + self.stats.set_plugins(orjson.loads(self.client.getAllPlugins())) logger.debug(f"Client version: {__version__} / Server version: {client_version}") else: self.log_and_exit( @@ -195,7 +195,7 @@ class GlancesClient: """ # Update the stats try: - server_stats = ujson.loads(self.client.getAll()) + server_stats = orjson.loads(self.client.getAll()) except OSError: # Client cannot get server stats return "Disconnected" diff --git a/glances/client_browser.py b/glances/client_browser.py index 4e98cde4..8e0ec721 100644 --- a/glances/client_browser.py +++ b/glances/client_browser.py @@ -10,7 +10,7 @@ import threading -import ujson +import orjson from glances.autodiscover import GlancesAutoDiscoverServer from glances.client import GlancesClient, GlancesClientTransport @@ -95,12 +95,12 @@ class GlancesClientBrowser: # Mandatory stats try: # CPU% - cpu_percent = 100 - ujson.loads(s.getCpu())['idle'] + cpu_percent = 100 - orjson.loads(s.getPlugin('cpu'))['idle'] server['cpu_percent'] = f'{cpu_percent:.1f}' # MEM% - server['mem_percent'] = ujson.loads(s.getMem())['percent'] + server['mem_percent'] = orjson.loads(s.getPlugin('mem'))['percent'] # OS (Human Readable name) - server['hr_name'] = ujson.loads(s.getSystem())['hr_name'] + server['hr_name'] = orjson.loads(s.getPlugin('system'))['hr_name'] except (OSError, Fault, KeyError) as e: logger.debug(f"Error while grabbing stats form server ({e})") server['status'] = 'OFFLINE' @@ -120,7 +120,7 @@ class GlancesClientBrowser: # Optional stats (load is not available on Windows OS) try: # LOAD - load_min5 = ujson.loads(s.getLoad())['min5'] + load_min5 = orjson.loads(s.getPlugin('load'))['min5'] server['load_min5'] = f'{load_min5:.2f}' except Exception as e: logger.warning(f"Error while grabbing stats form server ({e})") diff --git a/glances/config.py b/glances/config.py index a59a837f..84c6b7ea 100644 --- a/glances/config.py +++ b/glances/config.py @@ -81,16 +81,29 @@ def default_config_dir(): - Linux, SunOS, *BSD, macOS: /usr/share/doc (as defined in the setup.py files) - Windows: %APPDATA%\glances """ - if LINUX or SUNOS or BSD or MACOS: - path = '/usr/share/doc' - else: - path = os.environ.get('APPDATA') - if path is None: - path = '' - else: - path = os.path.join(path, 'glances') + path = [] + # Add venv path (solve issue #2803) + if in_virtualenv(): + path.append(os.path.join(sys.prefix, 'share', 'doc', 'glances')) - return [path] + # Add others system path + if LINUX or SUNOS or BSD or MACOS: + path.append('/usr/share/doc') + else: + path.append(os.environ.get('APPDATA')) + + return path + + +def in_virtualenv(): + # Source: https://stackoverflow.com/questions/1871549/how-to-determine-if-python-is-running-inside-a-virtualenv/1883251#1883251 + return sys.prefix != get_base_prefix_compat() + + +def get_base_prefix_compat(): + """Get base/real prefix, or sys.prefix if there is none.""" + # Source: https://stackoverflow.com/questions/1871549/how-to-determine-if-python-is-running-inside-a-virtualenv/1883251#1883251 + return getattr(sys, "base_prefix", None) or getattr(sys, "real_prefix", None) or sys.prefix class Config: @@ -104,6 +117,7 @@ class Config: self.config_dir = config_dir self.config_filename = 'glances.conf' self._loaded_config_file = None + self._config_file_paths = self.config_file_paths() # Re pattern for optimize research of `foo` self.re_pattern = re.compile(r'(\`.+?\`)') @@ -151,7 +165,7 @@ class Config: def read(self): """Read the config file, if it exists. Using defaults otherwise.""" - for config_file in self.config_file_paths(): + for config_file in self._config_file_paths: logger.debug(f'Search glances.conf file in {config_file}') if os.path.exists(config_file): try: diff --git a/glances/cpu_percent.py b/glances/cpu_percent.py index b02ca82b..690f9fd1 100644 --- a/glances/cpu_percent.py +++ b/glances/cpu_percent.py @@ -23,7 +23,7 @@ class CpuPercent: self.percpu_percent = [] # Get CPU name - self.__get_cpu_name() + self.cpu_info['cpu_name'] = self.__get_cpu_name() # cached_timer_cpu is the minimum time interval between stats updates # since last update is passed (will retrieve old cached info instead) @@ -71,12 +71,18 @@ class CpuPercent: def __get_cpu_name(self): # Get the CPU name once from the /proc/cpuinfo file - # TODO: Multisystem... + # Read the first line with the "model name" + ret = None try: - self.cpu_info['cpu_name'] = open('/proc/cpuinfo').readlines()[4].split(':')[1].strip() - except (FileNotFoundError, PermissionError, IndexError, KeyError, AttributeError): - self.cpu_info['cpu_name'] = 'CPU' - return self.cpu_info['cpu_name'] + cpuinfo_file = open('/proc/cpuinfo').readlines() + except (FileNotFoundError, PermissionError): + pass + else: + for line in cpuinfo_file: + if line.startswith('model name'): + ret = line.split(':')[1].strip() + break + return ret if ret else 'CPU' def __get_cpu(self): """Update and/or return the CPU using the psutil library.""" diff --git a/glances/globals.py b/glances/globals.py index 56002c38..96d8f781 100644 --- a/glances/globals.py +++ b/glances/globals.py @@ -33,7 +33,7 @@ from urllib.request import Request, urlopen from xmlrpc.client import Fault, ProtocolError, Server, ServerProxy, Transport from xmlrpc.server import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer -import ujson +import orjson # Correct issue #1025 by monkey path the xmlrpc lib from defusedxml.xmlrpc import monkey_patch @@ -309,9 +309,9 @@ def json_dumps(data): Manage the issue #815 for Windows OS with UnicodeDecodeError catching. """ try: - return ujson.dumps(data) + return orjson.dumps(data) except UnicodeDecodeError: - return ujson.dumps(data, ensure_ascii=False) + return orjson.dumps(data, ensure_ascii=False) def dictlist(data, item): diff --git a/glances/outputs/glances_curses_browser.py b/glances/outputs/glances_curses_browser.py index 950a4349..858fd5fc 100644 --- a/glances/outputs/glances_curses_browser.py +++ b/glances/outputs/glances_curses_browser.py @@ -249,15 +249,19 @@ class GlancesCursesBrowser(_GlancesCurses): screen_x = self.screen.getmaxyx()[1] screen_y = self.screen.getmaxyx()[0] stats_max = screen_y - 3 - stats_len = len(stats) - self._page_max_lines = stats_max - self._page_max = int(math.ceil(stats_len / stats_max)) - # Init position - x = 0 - y = 0 + self._page_max = int(math.ceil(len(stats) / stats_max)) - # Display top header + # Display header + x, y = self.__display_header(stats, 0, 0, screen_x, screen_y) + + # Display Glances server list + # ================================ + return self.__display_server_list(stats, x, y, screen_x, screen_y) + + def __display_header(self, stats, x, y, screen_x, screen_y): + stats_len = len(stats) + stats_max = screen_y - 3 if stats_len == 0: if self.first_scan and not self.args.disable_autodiscover: msg = 'Glances is scanning your network. Please wait...' @@ -282,11 +286,14 @@ class GlancesCursesBrowser(_GlancesCurses): msg = f'{page_lines} servers displayed.({self._current_page + 1}/{self._page_max}) {status_count}' self.term_window.addnstr(y + 1, x, msg, screen_x - x) - if stats_len == 0: + return x, y + + def __display_server_list(self, stats, x, y, screen_x, screen_y): + if len(stats) == 0: + # No server to display return False - # Display the Glances server list - # ================================ + stats_max = screen_y - 3 # Table of table # Item description: [stats_id, column name, column size] diff --git a/glances/outputs/glances_restful_api.py b/glances/outputs/glances_restful_api.py index 59647a39..61f5fa9d 100644 --- a/glances/outputs/glances_restful_api.py +++ b/glances/outputs/glances_restful_api.py @@ -130,11 +130,11 @@ class GlancesRestfulApi: # https://fastapi.tiangolo.com/tutorial/cors/ self._app.add_middleware( CORSMiddleware, - # allow_origins=["*"], - allow_origins=[self.bind_url], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], + # Related to https://github.com/nicolargo/glances/issues/2812 + allow_origins=config.get_list_value('outputs', 'cors_origins', default=["*"]), + allow_credentials=config.get_bool_value('outputs', 'cors_credentials', default=True), + allow_methods=config.get_list_value('outputs', 'cors_methods', default=["*"]), + allow_headers=config.get_list_value('outputs', 'cors_headers', default=["*"]), ) # FastAPI Enable GZIP compression diff --git a/glances/outputs/static/css/style.scss b/glances/outputs/static/css/style.scss index 8c0694cd..15a87b68 100644 --- a/glances/outputs/static/css/style.scss +++ b/glances/outputs/static/css/style.scss @@ -218,23 +218,23 @@ body { padding-left: 10px; } -/* Loading page */ +// /* Loading page */ -#loading-page .glances-logo { - background: url('../images/glances.png') no-repeat center center; - background-size: contain; -} +// #loading-page .glances-logo { +// background: url('../images/glances.png') no-repeat center center; +// background-size: contain; +// } -@media (max-width: 750px) { - #loading-page .glances-logo { - height: 400px; - } -} -@media (min-width: 750px) { - #loading-page .glances-logo { - height: 500px; - } -} +// @media (max-width: 750px) { +// #loading-page .glances-logo { +// height: 400px; +// } +// } +// @media (min-width: 750px) { +// #loading-page .glances-logo { +// height: 500px; +// } +// } /* diff --git a/glances/outputs/static/js/App.vue b/glances/outputs/static/js/App.vue index db60b550..ecab78fc 100644 --- a/glances/outputs/static/js/App.vue +++ b/glances/outputs/static/js/App.vue @@ -1,7 +1,6 @@