diff --git a/all-requirements.txt b/all-requirements.txt index 4c7ceaab..a806bdde 100644 --- a/all-requirements.txt +++ b/all-requirements.txt @@ -37,13 +37,17 @@ click==8.1.8 colorama==0.4.6 ; sys_platform == 'win32' # via click cryptography==46.0.3 - # via pysnmpcrypto + # via + # pysnmpcrypto + # python-jose defusedxml==0.7.1 # via glances dnspython==2.8.0 # via pymongo docker==7.1.0 # via glances +ecdsa==0.19.1 + # via python-jose elastic-transport==9.2.1 # via elasticsearch elasticsearch==9.2.1 @@ -119,7 +123,10 @@ psycopg-binary==3.3.2 ; implementation_name != 'pypy' pyarrow==22.0.0 # via influxdb3-python pyasn1==0.6.1 - # via pysnmp-lextudio + # via + # pysnmp-lextudio + # python-jose + # rsa pycparser==2.23 ; (implementation_name != 'PyPy' and platform_python_implementation != 'PyPy') or (implementation_name == 'pypy' and platform_python_implementation == 'PyPy') # via cffi pydantic==2.12.5 @@ -155,6 +162,8 @@ python-dateutil==2.9.0.post0 # influxdb # influxdb-client # influxdb3-python +python-jose==3.5.0 + # via glances pytz==2025.2 # via influxdb pywin32==311 ; sys_platform == 'win32' @@ -174,6 +183,8 @@ requests==2.32.5 # influxdb # podman # pysmi-lextudio +rsa==4.9.1 + # via python-jose setuptools==80.9.0 # via # influxdb-client @@ -182,6 +193,7 @@ shtab==1.8.0 ; sys_platform != 'win32' # via glances six==1.17.0 # via + # ecdsa # glances # influxdb # python-dateutil diff --git a/conf/glances.conf b/conf/glances.conf index 02c031f9..1d458ad7 100644 --- a/conf/glances.conf +++ b/conf/glances.conf @@ -51,6 +51,7 @@ history_size=1200 # then configure this folder with the webui_root_path key # Default is folder where glances_restful_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 * @@ -64,10 +65,18 @@ history_size=1200 # Comma separated list of HTTP request headers that should be supported for cross-origin requests. # Default is * #cors_headers=* +# # Define SSL files (keyfile_password is optional) #ssl_keyfile_password=kfp #ssl_keyfile=./glances.local+3-key.pem #ssl_certfile=./glances.local+3.pem +# +# JWT Authentication settings +# Secret key for signing JWT tokens (generate with: openssl rand -hex 32) +# If not set, a random key is generated per server instance (tokens won't survive restart) +#jwt_secret_key=your-secure-secret-key-here +# Token expiration time in minutes (default: 60) +#jwt_expire_minutes=60 ############################################################################## # Plugins diff --git a/docker-compose/glances.conf b/docker-compose/glances.conf index a4918289..d4439458 100644 --- a/docker-compose/glances.conf +++ b/docker-compose/glances.conf @@ -51,6 +51,7 @@ max_processes_display=25 # then configure this folder with the webui_root_path key # Default is folder where glances_restful_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 * @@ -64,10 +65,18 @@ max_processes_display=25 # Comma separated list of HTTP request headers that should be supported for cross-origin requests. # Default is * #cors_headers=* +# # Define SSL files (keyfile_password is optional) #ssl_keyfile_password=kfp #ssl_keyfile=./glances.local+3-key.pem #ssl_certfile=./glances.local+3.pem +# +# JWT Authentication settings +# Secret key for signing JWT tokens (generate with: openssl rand -hex 32) +# If not set, a random key is generated per server instance (tokens won't survive restart) +#jwt_secret_key=your-secure-secret-key-here +# Token expiration time in minutes (default: 60) +#jwt_expire_minutes=60 ############################################################################## # Plugins diff --git a/docker-requirements.txt b/docker-requirements.txt index ab2fb801..b3169ec1 100644 --- a/docker-requirements.txt +++ b/docker-requirements.txt @@ -8,16 +8,22 @@ anyio==4.12.0 # via starlette certifi==2025.11.12 # via requests +cffi==2.0.0 ; platform_python_implementation != 'PyPy' + # via cryptography charset-normalizer==3.4.4 # via requests click==8.1.8 # via uvicorn colorama==0.4.6 ; sys_platform == 'win32' # via click +cryptography==46.0.3 + # via python-jose defusedxml==0.7.1 # via glances docker==7.1.0 # via glances +ecdsa==0.19.1 + # via python-jose exceptiongroup==1.2.2 ; python_full_version < '3.11' # via anyio fastapi==0.128.0 @@ -38,12 +44,20 @@ podman==5.6.0 # via glances psutil==7.2.1 # via glances +pyasn1==0.6.1 + # via + # python-jose + # rsa +pycparser==2.23 ; implementation_name != 'PyPy' and platform_python_implementation != 'PyPy' + # via cffi pydantic==2.12.5 # via fastapi pydantic-core==2.41.5 # via pydantic python-dateutil==2.9.0.post0 # via glances +python-jose==3.5.0 + # via glances pywin32==311 ; sys_platform == 'win32' # via docker requests==2.32.5 @@ -51,10 +65,13 @@ requests==2.32.5 # docker # glances # podman +rsa==4.9.1 + # via python-jose shtab==1.8.0 ; sys_platform != 'win32' # via glances six==1.17.0 # via + # ecdsa # glances # python-dateutil starlette==0.50.0 @@ -64,6 +81,7 @@ tomli==2.0.2 ; python_full_version < '3.11' typing-extensions==4.15.0 # via # anyio + # cryptography # fastapi # pydantic # pydantic-core diff --git a/docs/api/python.rst b/docs/api/python.rst index 1c84ec94..20365932 100644 --- a/docs/api/python.rst +++ b/docs/api/python.rst @@ -22,25 +22,25 @@ use the following code: >>> gl = api.GlancesAPI() >>> gl.cpu {'cpucore': 16, - 'ctx_switches': 472872341, + 'ctx_switches': 541113367, 'guest': 0.0, - 'idle': 93.5, - 'interrupts': 410386146, - 'iowait': 0.3, + 'idle': 93.1, + 'interrupts': 464358062, + 'iowait': 0.5, 'irq': 0.0, 'nice': 0.0, - 'soft_interrupts': 174928185, + 'soft_interrupts': 197631605, 'steal': 0.0, 'syscalls': 0, - 'system': 4.4, - 'total': 7.2, - 'user': 1.7} + 'system': 4.6, + 'total': 7.9, + 'user': 1.9} >>> gl.cpu.get("total") - 7.2 + 7.9 >>> gl.mem.get("used") - 12810799128 + 10525812760 >>> gl.auto_unit(gl.mem.get("used")) - 11.9G + 9.80G If the stats return a list of items (like network interfaces or processes), you can access them by their name: @@ -51,19 +51,19 @@ access them by their name: ['wlp0s20f3'] >>> gl.network["wlp0s20f3"] {'alias': None, - 'bytes_all': 214, - 'bytes_all_gauge': 15142743463, - 'bytes_all_rate_per_sec': 606.0, - 'bytes_recv': 128, - 'bytes_recv_gauge': 13975900109, - 'bytes_recv_rate_per_sec': 362.0, - 'bytes_sent': 86, - 'bytes_sent_gauge': 1166843354, - 'bytes_sent_rate_per_sec': 243.0, + 'bytes_all': 1903, + 'bytes_all_gauge': 15435196525, + 'bytes_all_rate_per_sec': 13613.0, + 'bytes_recv': 1731, + 'bytes_recv_gauge': 14118055339, + 'bytes_recv_rate_per_sec': 12382.0, + 'bytes_sent': 172, + 'bytes_sent_gauge': 1317141186, + 'bytes_sent_rate_per_sec': 1230.0, 'interface_name': 'wlp0s20f3', 'key': 'interface_name', 'speed': 0, - 'time_since_update': 0.3526580333709717} + 'time_since_update': 0.13979172706604004} Init Glances Python API ----------------------- @@ -95,32 +95,7 @@ Alert stats: >>> type(gl.alert) >>> gl.alert - [{'avg': 99.96957776029373, - 'begin': 1767449522, - 'count': 2, - 'desc': '', - 'end': -1, - 'global_msg': 'High swap (paging) usage', - 'max': 99.96957776029373, - 'min': 99.96957776029373, - 'sort': 'memory_percent', - 'state': 'CRITICAL', - 'sum': 199.93915552058746, - 'top': ['code', 'code', 'code'], - 'type': 'MEMSWAP'}, - {'avg': 77.95894443790266, - 'begin': 1767449522, - 'count': 2, - 'desc': '', - 'end': -1, - 'global_msg': 'High swap (paging) usage', - 'max': 78.00605779103407, - 'min': 77.91183108477124, - 'sort': 'memory_percent', - 'state': 'WARNING', - 'sum': 155.91788887580532, - 'top': [], - 'type': 'MEM'}] + [] Alert fields description: @@ -161,7 +136,7 @@ Ports stats: 'port': 0, 'refresh': 30, 'rtt_warning': None, - 'status': 0.008168, + 'status': 0.006093, 'timeout': 3}] Ports fields description: @@ -202,14 +177,14 @@ Diskio stats: >>> gl.diskio.get("nvme0n1") {'disk_name': 'nvme0n1', 'key': 'disk_name', - 'read_bytes': 29268151808, - 'read_count': 1162807, + 'read_bytes': 47595146752, + 'read_count': 1611927, 'read_latency': 0, - 'read_time': 313717, - 'write_bytes': 44187964416, - 'write_count': 3047678, + 'read_time': 492221, + 'write_bytes': 47440806912, + 'write_count': 3244300, 'write_latency': 0, - 'write_time': 2442776} + 'write_time': 2733363} Diskio fields description: @@ -294,11 +269,11 @@ Processcount stats: >>> type(gl.processcount) >>> gl.processcount - {'pid_max': 0, 'running': 1, 'sleeping': 442, 'thread': 2444, 'total': 597} + {'pid_max': 0, 'running': 1, 'sleeping': 412, 'thread': 1984, 'total': 561} >>> gl.processcount.keys() ['total', 'running', 'sleeping', 'thread', 'pid_max'] >>> gl.processcount.get("total") - 597 + 561 Processcount fields description: @@ -371,7 +346,7 @@ Percpu stats: 'dpc': None, 'guest': 0.0, 'guest_nice': 0.0, - 'idle': 44.0, + 'idle': 29.0, 'interrupt': None, 'iowait': 0.0, 'irq': 0.0, @@ -380,8 +355,8 @@ Percpu stats: 'softirq': 0.0, 'steal': 0.0, 'system': 10.0, - 'total': 56.0, - 'user': 0.0} + 'total': 71.0, + 'user': 1.0} Percpu fields description: @@ -471,18 +446,18 @@ Network stats: >>> gl.network.get("wlp0s20f3") {'alias': None, 'bytes_all': 0, - 'bytes_all_gauge': 15142743463, + 'bytes_all_gauge': 15435196525, 'bytes_all_rate_per_sec': 0.0, 'bytes_recv': 0, - 'bytes_recv_gauge': 13975900109, + 'bytes_recv_gauge': 14118055339, 'bytes_recv_rate_per_sec': 0.0, 'bytes_sent': 0, - 'bytes_sent_gauge': 1166843354, + 'bytes_sent_gauge': 1317141186, 'bytes_sent_rate_per_sec': 0.0, 'interface_name': 'wlp0s20f3', 'key': 'interface_name', 'speed': 0, - 'time_since_update': 0.00287628173828125} + 'time_since_update': 0.00189208984375} Network fields description: @@ -523,23 +498,23 @@ Cpu stats: >>> gl.cpu {'cpucore': 16, - 'ctx_switches': 472872341, + 'ctx_switches': 541113367, 'guest': 0.0, - 'idle': 93.5, - 'interrupts': 410386146, - 'iowait': 0.3, + 'idle': 93.1, + 'interrupts': 464358062, + 'iowait': 0.5, 'irq': 0.0, 'nice': 0.0, - 'soft_interrupts': 174928185, + 'soft_interrupts': 197631605, 'steal': 0.0, 'syscalls': 0, - 'system': 4.4, - 'total': 7.2, - 'user': 1.7} + 'system': 4.6, + 'total': 7.9, + 'user': 1.9} >>> gl.cpu.keys() ['total', 'user', 'nice', 'system', 'idle', 'iowait', 'irq', 'steal', 'guest', 'ctx_switches', 'interrupts', 'soft_interrupts', 'syscalls', 'cpucore'] >>> gl.cpu.get("total") - 7.2 + 7.9 Cpu fields description: @@ -611,7 +586,7 @@ Amps stats: 'refresh': 3.0, 'regex': True, 'result': None, - 'timer': 0.31592440605163574} + 'timer': 0.16622281074523926} Amps fields description: @@ -642,32 +617,32 @@ Processlist stats: >>> gl.processlist Return a dict of dict with key= >>> gl.processlist.keys() - [1, 2, 3, 4, 5, 6, 7, 8, 10, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 29, 30, 31, 32, 33, 35, 36, 37, 38, 39, 41, 42, 43, 44, 45, 47, 48, 49, 50, 51, 53, 54, 55, 56, 57, 59, 60, 61, 62, 63, 65, 66, 67, 68, 69, 71, 72, 73, 74, 75, 77, 78, 79, 80, 81, 83, 84, 85, 86, 87, 89, 90, 91, 92, 93, 95, 96, 97, 98, 99, 101, 102, 103, 104, 105, 107, 108, 109, 110, 111, 113, 114, 115, 116, 117, 118, 121, 122, 123, 124, 125, 126, 127, 128, 133, 135, 136, 137, 138, 139, 140, 142, 143, 144, 145, 146, 147, 148, 150, 154, 156, 157, 158, 166, 179, 188, 189, 218, 219, 239, 240, 258, 267, 268, 269, 270, 271, 273, 279, 280, 364, 367, 369, 370, 371, 372, 373, 450, 452, 613, 618, 619, 620, 627, 659, 660, 726, 757, 758, 787, 795, 970, 971, 986, 1037, 1040, 1042, 1043, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1056, 1058, 1059, 1064, 1065, 1218, 1219, 1223, 1275, 1277, 1278, 1279, 1320, 1327, 1534, 1537, 1965, 1966, 1967, 1968, 1969, 1970, 1971, 1972, 1973, 1974, 1975, 1976, 1977, 1978, 2007, 2008, 2009, 2010, 2011, 2013, 2014, 2016, 2017, 2018, 2019, 2020, 2021, 2023, 2052, 2053, 2054, 2055, 2056, 2057, 2058, 2059, 2060, 2061, 2062, 2063, 2064, 2065, 2066, 2067, 2068, 2069, 2070, 2071, 2072, 2073, 2074, 2075, 2078, 2079, 2080, 2081, 2082, 2083, 2084, 2085, 2086, 2087, 2088, 2089, 2090, 2091, 2092, 2093, 2094, 2095, 2096, 2097, 2098, 2099, 2100, 2101, 2102, 2103, 2104, 2105, 2117, 2122, 2123, 2124, 2125, 2126, 2127, 2128, 2129, 2130, 2133, 2134, 2135, 2136, 2137, 2138, 2139, 2140, 2141, 2142, 2143, 2144, 2145, 2149, 2603, 2604, 2605, 2606, 2613, 2615, 2744, 2745, 2746, 2750, 2751, 2755, 2762, 2774, 2781, 2788, 2791, 2796, 2799, 2802, 2813, 2820, 2823, 2887, 2904, 2905, 2916, 3031, 3038, 3081, 3181, 3182, 3186, 3191, 3192, 3240, 3382, 3383, 3670, 3671, 3695, 3698, 3807, 3817, 3818, 3822, 3827, 3828, 3846, 3853, 3874, 3901, 3902, 3903, 3904, 3905, 3906, 3907, 3908, 3982, 4194, 4596, 5146, 5166, 5172, 5179, 5189, 5190, 5193, 5195, 5197, 5207, 5263, 5267, 5274, 5318, 5333, 5421, 5425, 5441, 5453, 5457, 5492, 5505, 5509, 5593, 5612, 5620, 5621, 5634, 5657, 5658, 5661, 5664, 5665, 5667, 5669, 5672, 5675, 5678, 5680, 5685, 5687, 5693, 5695, 5700, 5708, 5726, 5734, 5829, 5832, 5833, 5844, 5861, 5871, 5914, 5921, 5931, 5941, 5954, 5957, 6017, 6063, 6087, 6093, 6094, 6123, 6154, 6206, 6244, 6475, 6505, 6552, 6559, 6562, 7227, 8412, 8526, 9631, 9649, 9661, 9731, 9734, 11010, 12121, 12123, 12124, 12138, 12192, 12239, 12278, 12321, 12335, 12336, 12417, 12686, 12726, 12735, 12929, 12931, 12933, 12934, 13048, 13115, 13536, 13665, 36430, 40117, 53755, 53764, 62488, 62637, 72064, 72065, 72120, 72139, 102070, 340569, 388396, 413445, 444052, 444061, 471623, 471714, 471892, 471899, 471919, 471929, 471954, 472557, 472566, 472570, 472785, 472854, 472861, 473314, 481055, 513901, 514243, 524431, 524432, 524447, 524457, 524551, 546233, 546254, 547205, 547220, 547714, 547732, 548091, 548102, 548622, 561234, 564559, 567573, 654061, 661802, 671732, 683980, 686191, 695926, 701770, 705018, 705167, 707900, 708398, 709364, 709585, 710008, 710952, 714751, 714947, 721023, 721204, 726507, 728005, 728006, 728008, 728465, 730137, 731844, 732902, 733166, 733167, 733841, 733865, 734183, 735109, 735551, 736180, 736316, 736396, 736607, 737036, 737183, 737369, 737866, 738311, 738732, 739425, 739889, 740008, 740325, 740438, 741044, 741346, 741856, 742102, 742589, 742967, 743001, 743292, 743314, 743466, 743467, 743468, 743469, 743470, 743471, 743554, 743752, 743753, 743869, 743926, 743942, 744023, 744188, 744189, 744276, 744281, 745624, 745766, 745875, 745878, 745879, 745882] + [1, 2, 3, 4, 5, 6, 7, 8, 10, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 29, 30, 31, 32, 33, 35, 36, 37, 38, 39, 41, 42, 43, 44, 45, 47, 48, 49, 50, 51, 53, 54, 55, 56, 57, 59, 60, 61, 62, 63, 65, 66, 67, 68, 69, 71, 72, 73, 74, 75, 77, 78, 79, 80, 81, 83, 84, 85, 86, 87, 89, 90, 91, 92, 93, 95, 96, 97, 98, 99, 101, 102, 103, 104, 105, 107, 108, 109, 110, 111, 113, 114, 115, 116, 117, 118, 121, 122, 123, 124, 125, 126, 127, 128, 133, 135, 136, 137, 138, 139, 140, 142, 143, 144, 145, 146, 147, 148, 150, 154, 156, 157, 158, 166, 179, 188, 189, 218, 219, 239, 240, 258, 267, 268, 269, 270, 271, 273, 279, 280, 364, 367, 369, 370, 371, 372, 373, 450, 452, 613, 618, 619, 620, 627, 659, 660, 726, 757, 758, 787, 795, 970, 971, 986, 1037, 1040, 1042, 1043, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1056, 1058, 1059, 1064, 1065, 1218, 1219, 1223, 1275, 1277, 1278, 1279, 1320, 1327, 1534, 1537, 1965, 1966, 1967, 1968, 1969, 1970, 1971, 1972, 1973, 1974, 1975, 1976, 1977, 1978, 2007, 2008, 2009, 2010, 2011, 2013, 2014, 2016, 2017, 2018, 2019, 2020, 2021, 2023, 2052, 2053, 2054, 2055, 2056, 2057, 2058, 2059, 2060, 2061, 2062, 2063, 2064, 2065, 2066, 2067, 2068, 2069, 2070, 2071, 2072, 2073, 2074, 2075, 2078, 2079, 2080, 2081, 2082, 2083, 2084, 2085, 2086, 2087, 2088, 2089, 2090, 2091, 2092, 2093, 2094, 2095, 2096, 2097, 2098, 2099, 2100, 2101, 2102, 2103, 2104, 2105, 2117, 2122, 2123, 2124, 2125, 2126, 2127, 2128, 2129, 2130, 2133, 2134, 2135, 2136, 2137, 2138, 2139, 2140, 2141, 2142, 2143, 2144, 2145, 2149, 2603, 2604, 2605, 2606, 2613, 2615, 2744, 2745, 2746, 2750, 2751, 2755, 2762, 2774, 2781, 2788, 2791, 2796, 2799, 2802, 2813, 2820, 2823, 2887, 2904, 2905, 2916, 3031, 3038, 3081, 3181, 3182, 3186, 3191, 3192, 3240, 3382, 3383, 3670, 3671, 3695, 3698, 3807, 3817, 3818, 3822, 3827, 3828, 3846, 3853, 3874, 3901, 3902, 3903, 3904, 3905, 3906, 3907, 3908, 3982, 4194, 4596, 5166, 6562, 36430, 444052, 444061, 481055, 686191, 791450, 797892, 800110, 800150, 800151, 800285, 806441, 808018, 811099, 813104, 813640, 813665, 813669, 813681, 813682, 813683, 813685, 813687, 813689, 813699, 813749, 813753, 813760, 813779, 813795, 813873, 813876, 813892, 813903, 813906, 813949, 813962, 813963, 814046, 814083, 814091, 814092, 814106, 814107, 814128, 814129, 814130, 814133, 814135, 814138, 814140, 814143, 814147, 814150, 814152, 814156, 814158, 814162, 814170, 814177, 814191, 814192, 814284, 814293, 814295, 814306, 814324, 814353, 814356, 814384, 814388, 814397, 814410, 814412, 814429, 814438, 814507, 814547, 814577, 814588, 814592, 814614, 814657, 814874, 814875, 814918, 814987, 814992, 815007, 815154, 815196, 815236, 815252, 815324, 815555, 815952, 815959, 815967, 816178, 816198, 816205, 816444, 816560, 816572, 817391, 817393, 817394, 817439, 817491, 817540, 817578, 817620, 817648, 817649, 817933, 817952, 818000, 818044, 818206, 818210, 818211, 818213, 818223, 818670, 818773, 818940, 818964, 819036, 819054, 819077, 819153, 819443, 820148, 820306, 820835, 823577, 825801, 828210, 828339, 828612, 829135, 829999, 831005, 831221, 834846, 836649, 836885, 837028, 837106, 837195, 838107, 838946, 839073, 839074, 839230, 839243, 842229, 842773, 843197, 843881, 844002, 844988, 845334, 845619, 846865, 847391, 847596, 847599, 847816, 848168, 848169, 848296, 848442, 848637, 848983, 848984, 848985, 848986, 848987, 849482, 849620, 850436, 850804, 851714, 851777, 852971, 853655, 854520, 854521, 854586, 854783, 854845, 854876, 855229, 855232, 855233, 855236] >>> gl.processlist.get("1") {'cmdline': ['/sbin/init', 'splash'], 'cpu_percent': 0.0, - 'cpu_times': {'children_system': 292.72, - 'children_user': 5149.74, + 'cpu_times': {'children_system': 11313.22, + 'children_user': 46981.78, 'iowait': 0.0, - 'system': 9.16, - 'user': 13.08}, + 'system': 10.51, + 'user': 14.18}, 'gids': {'effective': 0, 'real': 0, 'saved': 0}, 'io_counters': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'key': 'pid', 'memory_info': {'data': 7282688, 'dirty': 0, 'lib': 0, - 'rss': 14585856, - 'shared': 8134656, + 'rss': 12140544, + 'shared': 5771264, 'text': 45056, 'vms': 26632192}, - 'memory_percent': 0.0888145317633538, + 'memory_percent': 0.07392481666570644, 'name': 'systemd', 'nice': 0, 'num_threads': 1, 'pid': 1, 'status': 'S', - 'time_since_update': 0.6452903747558594, + 'time_since_update': 0.40526819229125977, 'username': 'root'} Processlist fields description: @@ -752,13 +727,13 @@ Load stats: >>> gl.load {'cpucore': 16, - 'min1': 0.869140625, - 'min15': 1.04248046875, - 'min5': 1.04052734375} + 'min1': 0.62451171875, + 'min15': 0.71826171875, + 'min5': 0.67919921875} >>> gl.load.keys() ['min1', 'min5', 'min15', 'cpucore'] >>> gl.load.get("min1") - 0.869140625 + 0.62451171875 Load fields description: @@ -797,7 +772,7 @@ Sensors stats: 'label': 'Ambient', 'type': 'temperature_core', 'unit': 'C', - 'value': 34, + 'value': 36, 'warning': 0} Sensors fields description: @@ -835,7 +810,7 @@ Uptime stats: >>> type(gl.uptime) >>> gl.uptime - '6 days, 22:46:17' + '7 days, 18:13:52' Uptime limits: @@ -854,11 +829,11 @@ Now stats: >>> type(gl.now) >>> gl.now - {'custom': '2026-01-03 15:12:02 CET', 'iso': '2026-01-03T15:12:02+01:00'} + {'custom': '2026-01-04 10:39:37 CET', 'iso': '2026-01-04T10:39:37+01:00'} >>> gl.now.keys() ['iso', 'custom'] >>> gl.now.get("iso") - '2026-01-03T15:12:02+01:00' + '2026-01-04T10:39:37+01:00' Now fields description: @@ -887,14 +862,14 @@ Fs stats: ['/', '/zsfpool'] >>> gl.fs.get("/") {'device_name': '/dev/mapper/ubuntu--vg-ubuntu--lv', - 'free': 582918623232, + 'free': 582995017728, 'fs_type': 'ext4', 'key': 'mnt_point', 'mnt_point': '/', 'options': 'rw,relatime', 'percent': 38.8, 'size': 1003736440832, - 'used': 369755312128} + 'used': 369678917632} Fs fields description: @@ -934,8 +909,8 @@ Wifi stats: ['wlp0s20f3'] >>> gl.wifi.get("wlp0s20f3") {'key': 'ssid', - 'quality_level': -60.0, - 'quality_link': 50.0, + 'quality_level': -62.0, + 'quality_link': 48.0, 'ssid': 'wlp0s20f3'} Wifi limits: @@ -959,13 +934,9 @@ Ip stats: >>> type(gl.ip) >>> gl.ip - {'address': '192.168.1.26', - 'mask': '255.255.255.0', - 'mask_cidr': 24, - 'public_address': '', - 'public_info_human': ''} + {'address': '192.168.1.26', 'mask': '255.255.255.0', 'mask_cidr': 24} >>> gl.ip.keys() - ['address', 'mask', 'mask_cidr', 'public_address', 'public_info_human'] + ['address', 'mask', 'mask_cidr'] >>> gl.ip.get("address") '192.168.1.26' @@ -1067,16 +1038,16 @@ Mem stats: >>> type(gl.mem) >>> gl.mem - {'active': 5529219072, - 'available': 3612026856, - 'buffers': 130482176, - 'cached': 3568499560, - 'free': 1333854208, - 'inactive': 7177965568, - 'percent': 78.0, - 'shared': 840089600, + {'active': 9791209472, + 'available': 5897013224, + 'buffers': 257441792, + 'cached': 5707590504, + 'free': 704032768, + 'inactive': 4103938048, + 'percent': 64.1, + 'shared': 734093312, 'total': 16422825984, - 'used': 12810799128} + 'used': 10525812760} >>> gl.mem.keys() ['total', 'available', 'percent', 'used', 'free', 'active', 'inactive', 'buffers', 'cached', 'shared'] >>> gl.mem.get("total") @@ -1146,19 +1117,19 @@ Quicklook stats: >>> type(gl.quicklook) >>> gl.quicklook - {'cpu': 7.2, + {'cpu': 7.9, 'cpu_hz': 4475000000.0, - 'cpu_hz_current': 608909250.0, + 'cpu_hz_current': 1034989000.0, 'cpu_log_core': 16, 'cpu_name': '13th Gen Intel(R) Core(TM) i7-13620H', 'cpu_phys_core': 10, - 'load': 6.5, - 'mem': 78.0, + 'load': 4.5, + 'mem': 64.1, 'percpu': [{'cpu_number': 0, 'dpc': None, 'guest': 0.0, 'guest_nice': 0.0, - 'idle': 44.0, + 'idle': 29.0, 'interrupt': None, 'iowait': 0.0, 'irq': 0.0, @@ -1167,13 +1138,13 @@ Quicklook stats: 'softirq': 0.0, 'steal': 0.0, 'system': 10.0, - 'total': 56.0, - 'user': 0.0}, + 'total': 71.0, + 'user': 1.0}, {'cpu_number': 1, 'dpc': None, 'guest': 0.0, 'guest_nice': 0.0, - 'idle': 55.0, + 'idle': 39.0, 'interrupt': None, 'iowait': 0.0, 'irq': 0.0, @@ -1182,13 +1153,13 @@ Quicklook stats: 'softirq': 0.0, 'steal': 0.0, 'system': 0.0, - 'total': 45.0, - 'user': 0.0}, + 'total': 61.0, + 'user': 1.0}, {'cpu_number': 2, 'dpc': None, 'guest': 0.0, 'guest_nice': 0.0, - 'idle': 55.0, + 'idle': 37.0, 'interrupt': None, 'iowait': 0.0, 'irq': 0.0, @@ -1197,13 +1168,13 @@ Quicklook stats: 'softirq': 0.0, 'steal': 0.0, 'system': 1.0, - 'total': 45.0, - 'user': 1.0}, + 'total': 63.0, + 'user': 2.0}, {'cpu_number': 3, 'dpc': None, 'guest': 0.0, 'guest_nice': 0.0, - 'idle': 55.0, + 'idle': 41.0, 'interrupt': None, 'iowait': 0.0, 'irq': 0.0, @@ -1211,59 +1182,14 @@ Quicklook stats: 'nice': 0.0, 'softirq': 0.0, 'steal': 0.0, - 'system': 0.0, - 'total': 45.0, + 'system': 1.0, + 'total': 59.0, 'user': 0.0}, {'cpu_number': 4, 'dpc': None, 'guest': 0.0, 'guest_nice': 0.0, - 'idle': 51.0, - 'interrupt': None, - 'iowait': 0.0, - 'irq': 0.0, - 'key': 'cpu_number', - 'nice': 0.0, - 'softirq': 0.0, - 'steal': 0.0, - 'system': 3.0, - 'total': 49.0, - 'user': 1.0}, - {'cpu_number': 5, - 'dpc': None, - 'guest': 0.0, - 'guest_nice': 0.0, - 'idle': 55.0, - 'interrupt': None, - 'iowait': 0.0, - 'irq': 0.0, - 'key': 'cpu_number', - 'nice': 0.0, - 'softirq': 0.0, - 'steal': 0.0, - 'system': 0.0, - 'total': 45.0, - 'user': 0.0}, - {'cpu_number': 6, - 'dpc': None, - 'guest': 0.0, - 'guest_nice': 0.0, - 'idle': 45.0, - 'interrupt': None, - 'iowait': 0.0, - 'irq': 0.0, - 'key': 'cpu_number', - 'nice': 0.0, - 'softirq': 0.0, - 'steal': 0.0, - 'system': 8.0, - 'total': 55.0, - 'user': 3.0}, - {'cpu_number': 7, - 'dpc': None, - 'guest': 0.0, - 'guest_nice': 0.0, - 'idle': 28.0, + 'idle': 27.0, 'interrupt': None, 'iowait': 1.0, 'irq': 0.0, @@ -1271,29 +1197,74 @@ Quicklook stats: 'nice': 0.0, 'softirq': 0.0, 'steal': 0.0, - 'system': 15.0, - 'total': 72.0, - 'user': 11.0}, - {'cpu_number': 8, + 'system': 3.0, + 'total': 73.0, + 'user': 7.0}, + {'cpu_number': 5, 'dpc': None, 'guest': 0.0, 'guest_nice': 0.0, - 'idle': 52.0, + 'idle': 41.0, 'interrupt': None, - 'iowait': 2.0, + 'iowait': 0.0, 'irq': 0.0, 'key': 'cpu_number', 'nice': 0.0, 'softirq': 0.0, 'steal': 0.0, - 'system': 1.0, - 'total': 48.0, + 'system': 0.0, + 'total': 59.0, + 'user': 0.0}, + {'cpu_number': 6, + 'dpc': None, + 'guest': 0.0, + 'guest_nice': 0.0, + 'idle': 34.0, + 'interrupt': None, + 'iowait': 0.0, + 'irq': 0.0, + 'key': 'cpu_number', + 'nice': 0.0, + 'softirq': 0.0, + 'steal': 0.0, + 'system': 4.0, + 'total': 66.0, + 'user': 1.0}, + {'cpu_number': 7, + 'dpc': None, + 'guest': 0.0, + 'guest_nice': 0.0, + 'idle': 28.0, + 'interrupt': None, + 'iowait': 0.0, + 'irq': 0.0, + 'key': 'cpu_number', + 'nice': 0.0, + 'softirq': 0.0, + 'steal': 0.0, + 'system': 6.0, + 'total': 72.0, + 'user': 5.0}, + {'cpu_number': 8, + 'dpc': None, + 'guest': 0.0, + 'guest_nice': 0.0, + 'idle': 37.0, + 'interrupt': None, + 'iowait': 0.0, + 'irq': 0.0, + 'key': 'cpu_number', + 'nice': 0.0, + 'softirq': 0.0, + 'steal': 0.0, + 'system': 2.0, + 'total': 63.0, 'user': 1.0}, {'cpu_number': 9, 'dpc': None, 'guest': 0.0, 'guest_nice': 0.0, - 'idle': 55.0, + 'idle': 40.0, 'interrupt': None, 'iowait': 0.0, 'irq': 0.0, @@ -1302,13 +1273,13 @@ Quicklook stats: 'softirq': 0.0, 'steal': 0.0, 'system': 1.0, - 'total': 45.0, + 'total': 60.0, 'user': 0.0}, {'cpu_number': 10, 'dpc': None, 'guest': 0.0, 'guest_nice': 0.0, - 'idle': 54.0, + 'idle': 37.0, 'interrupt': None, 'iowait': 0.0, 'irq': 0.0, @@ -1316,14 +1287,14 @@ Quicklook stats: 'nice': 0.0, 'softirq': 0.0, 'steal': 0.0, - 'system': 1.0, - 'total': 46.0, + 'system': 2.0, + 'total': 63.0, 'user': 1.0}, {'cpu_number': 11, 'dpc': None, 'guest': 0.0, 'guest_nice': 0.0, - 'idle': 56.0, + 'idle': 41.0, 'interrupt': None, 'iowait': 0.0, 'irq': 0.0, @@ -1332,13 +1303,13 @@ Quicklook stats: 'softirq': 0.0, 'steal': 0.0, 'system': 0.0, - 'total': 44.0, + 'total': 59.0, 'user': 0.0}, {'cpu_number': 12, 'dpc': None, 'guest': 0.0, 'guest_nice': 0.0, - 'idle': 55.0, + 'idle': 39.0, 'interrupt': None, 'iowait': 0.0, 'irq': 0.0, @@ -1346,14 +1317,14 @@ Quicklook stats: 'nice': 0.0, 'softirq': 0.0, 'steal': 0.0, - 'system': 0.0, - 'total': 45.0, - 'user': 0.0}, + 'system': 1.0, + 'total': 61.0, + 'user': 1.0}, {'cpu_number': 13, 'dpc': None, 'guest': 0.0, 'guest_nice': 0.0, - 'idle': 55.0, + 'idle': 40.0, 'interrupt': None, 'iowait': 0.0, 'irq': 0.0, @@ -1361,14 +1332,14 @@ Quicklook stats: 'nice': 0.0, 'softirq': 0.0, 'steal': 0.0, - 'system': 0.0, - 'total': 45.0, - 'user': 0.0}, + 'system': 1.0, + 'total': 60.0, + 'user': 1.0}, {'cpu_number': 14, 'dpc': None, 'guest': 0.0, 'guest_nice': 0.0, - 'idle': 55.0, + 'idle': 39.0, 'interrupt': None, 'iowait': 0.0, 'irq': 0.0, @@ -1377,13 +1348,13 @@ Quicklook stats: 'softirq': 0.0, 'steal': 0.0, 'system': 1.0, - 'total': 45.0, - 'user': 0.0}, + 'total': 61.0, + 'user': 1.0}, {'cpu_number': 15, 'dpc': None, 'guest': 0.0, 'guest_nice': 0.0, - 'idle': 55.0, + 'idle': 40.0, 'interrupt': None, 'iowait': 0.0, 'irq': 0.0, @@ -1392,9 +1363,9 @@ Quicklook stats: 'softirq': 0.0, 'steal': 0.0, 'system': 1.0, - 'total': 45.0, + 'total': 60.0, 'user': 1.0}], - 'swap': 100.0} + 'swap': 1.9} >>> gl.quicklook.keys() ['cpu_name', 'cpu_hz_current', 'cpu_hz', 'cpu', 'percpu', 'mem', 'swap', 'cpu_log_core', 'cpu_phys_core', 'load'] >>> gl.quicklook.get("cpu_name") @@ -1444,13 +1415,13 @@ Memswap stats: >>> type(gl.memswap) >>> gl.memswap - {'free': 1306624, - 'percent': 100.0, - 'sin': 2306797568, - 'sout': 7715819520, - 'time_since_update': 0.7321233749389648, + {'free': 4211441664, + 'percent': 1.9, + 'sin': 2388013056, + 'sout': 7785721856, + 'time_since_update': 0.35303711891174316, 'total': 4294963200, - 'used': 4293656576} + 'used': 83521536} >>> gl.memswap.keys() ['total', 'used', 'free', 'percent', 'sin', 'sout', 'time_since_update'] >>> gl.memswap.get("total") @@ -1485,10 +1456,10 @@ Use auto_unit() function to generate a human-readable string with the unit: .. code-block:: python >>> gl.mem.get("used") - 12810799128 + 10525812760 >>> gl.auto_unit(gl.mem.get("used")) - 11.9G + 9.80G Args: @@ -1514,7 +1485,7 @@ Use bar() function to generate a bar: .. code-block:: python >>> gl.bar(gl.mem["percent"]) - ■■■■■■■■■■■■■■□□□□ + ■■■■■■■■■■■□□□□□□□ Args: @@ -1544,7 +1515,7 @@ Use top_process() function to generate a list of top processes sorted by CPU or .. code-block:: python >>> gl.top_process() - [{'status': 'S', 'memory_info': {'rss': 833155072, 'vms': 1499914432512, 'shared': 37560320, 'text': 148733952, 'lib': 0, 'data': 2573008896, 'dirty': 0}, 'name': 'code', 'nice': 0, 'memory_percent': 5.073152895924881, 'gids': {'real': 1000, 'effective': 1000, 'saved': 1000}, 'num_threads': 22, 'io_counters': [1554405376, 611483648, 1554405376, 611483648, 1], 'pid': 12321, 'cpu_percent': 1.6, 'cpu_times': {'user': 1797.6, 'system': 767.31, 'children_user': 1013.92, 'children_system': 950.58, 'iowait': 0.0}, 'key': 'pid', 'time_since_update': 0.6452903747558594, 'cmdline': ['/proc/self/exe', '--type=utility', '--utility-sub-type=node.mojom.NodeService', '--lang=en-US', '--service-sandbox-type=none', '--no-sandbox', '--dns-result-order=ipv4first', '--experimental-network-inspection', '--inspect-port=0', '--crashpad-handler-pid=12138', '--enable-crash-reporter=864d4bb7-dd20-4851-830f-29e81dd93517,no_channel', '--user-data-dir=/home/nicolargo/.config/Code', '--standard-schemes=vscode-webview,vscode-file', '--secure-schemes=vscode-webview,vscode-file', '--cors-schemes=vscode-webview,vscode-file', '--fetch-schemes=vscode-webview,vscode-file', '--service-worker-schemes=vscode-webview', '--code-cache-schemes=vscode-webview,vscode-file', '--shared-files=v8_context_snapshot_data:100', '--field-trial-handle=3,i,768474601394521582,1148810169323883902,262144', '--enable-features=DocumentPolicyIncludeJSCallStacksInCrashReports,EarlyEstablishGpuChannel,EstablishGpuChannelAsync', '--disable-features=CalculateNativeWinOcclusion,FontationsLinuxSystemFonts,ScreenAIOCREnabled,SpareRendererForSitePerProcess', '--variations-seed-version'], 'username': 'nicolargo'}, {'status': 'S', 'memory_info': {'rss': 610873344, 'vms': 31620141056, 'shared': 180371456, 'text': 708608, 'lib': 0, 'data': 1298669568, 'dirty': 0}, 'name': 'firefox', 'nice': 0, 'memory_percent': 3.71966033492132, 'gids': {'real': 1000, 'effective': 1000, 'saved': 1000}, 'num_threads': 157, 'io_counters': [1705658368, 4228337664, 1705658368, 4228337664, 1], 'pid': 471623, 'cpu_percent': 1.6, 'cpu_times': {'user': 2637.37, 'system': 751.51, 'children_user': 0.13, 'children_system': 1.18, 'iowait': 0.0}, 'key': 'pid', 'time_since_update': 0.6452903747558594, 'cmdline': ['/snap/firefox/7559/usr/lib/firefox/firefox'], 'username': 'nicolargo'}, {'status': 'S', 'memory_info': {'rss': 478920704, 'vms': 4367515648, 'shared': 76931072, 'text': 708608, 'lib': 0, 'data': 692023296, 'dirty': 0}, 'name': 'Isolated Web Co', 'nice': 0, 'memory_percent': 2.9161893602635156, 'gids': {'real': 1000, 'effective': 1000, 'saved': 1000}, 'num_threads': 33, 'io_counters': [38509568, 0, 38509568, 0, 1], 'pid': 472861, 'cpu_percent': 1.6, 'cpu_times': {'user': 925.58, 'system': 94.68, 'children_user': 0.0, 'children_system': 0.0, 'iowait': 0.0}, 'key': 'pid', 'time_since_update': 0.6452903747558594, 'cmdline': ['/snap/firefox/7559/usr/lib/firefox/firefox', '-contentproc', '-isForBrowser', '-prefsHandle', '0:45904', '-prefMapHandle', '1:280269', '-jsInitHandle', '2:223356', '-parentBuildID', '20251217233610', '-sandboxReporter', '3', '-chrootClient', '4', '-ipcHandle', '5', '-initialChannelId', '{b750b1b5-b51f-4cbd-8493-9c918fb23a11}', '-parentPid', '471623', '-crashReporter', '6', '-crashHelper', '7', '-greomni', '/snap/firefox/7559/usr/lib/firefox/omni.ja', '-appomni', '/snap/firefox/7559/usr/lib/firefox/browser/omni.ja', '-appDir', '/snap/firefox/7559/usr/lib/firefox/browser', '11', 'tab'], 'username': 'nicolargo'}] + [{'pid': 817540, 'memory_percent': 2.7617802712023183, 'status': 'S', 'num_threads': 25, 'gids': {'real': 1000, 'effective': 1000, 'saved': 1000}, 'io_counters': [23214080, 8192, 23214080, 8192, 1], 'cpu_times': {'user': 501.78, 'system': 42.5, 'children_user': 0.0, 'children_system': 0.0, 'iowait': 0.0}, 'nice': 0, 'cpu_percent': 5.2, 'name': 'code', 'memory_info': {'rss': 453562368, 'vms': 1517460189184, 'shared': 143192064, 'text': 148733952, 'lib': 0, 'data': 1318674432, 'dirty': 0}, 'key': 'pid', 'time_since_update': 0.40526819229125977, 'cmdline': ['/snap/code/211/usr/share/code/code', '--type=zygote', '--no-sandbox'], 'username': 'nicolargo'}, {'pid': 814875, 'memory_percent': 4.359469147986559, 'status': 'S', 'num_threads': 146, 'gids': {'real': 1000, 'effective': 1000, 'saved': 1000}, 'io_counters': [778276864, 563982336, 778276864, 563982336, 1], 'cpu_times': {'user': 154.0, 'system': 52.27, 'children_user': 0.09, 'children_system': 0.41, 'iowait': 0.0}, 'nice': 0, 'cpu_percent': 2.6, 'name': 'firefox', 'memory_info': {'rss': 715948032, 'vms': 22647500800, 'shared': 272883712, 'text': 708608, 'lib': 0, 'data': 1006481408, 'dirty': 0}, 'key': 'pid', 'time_since_update': 0.40526819229125977, 'cmdline': ['/snap/firefox/7559/usr/lib/firefox/firefox'], 'username': 'nicolargo'}, {'pid': 813963, 'memory_percent': 2.5835526261641473, 'status': 'S', 'num_threads': 24, 'gids': {'real': 1000, 'effective': 1000, 'saved': 1000}, 'io_counters': [52350976, 73728, 52350976, 73728, 1], 'cpu_times': {'user': 183.97, 'system': 98.37, 'children_user': 1.44, 'children_system': 1.33, 'iowait': 0.0}, 'nice': 0, 'cpu_percent': 2.6, 'name': 'gnome-shell', 'memory_info': {'rss': 424292352, 'vms': 5059411968, 'shared': 166699008, 'text': 8192, 'lib': 0, 'data': 479715328, 'dirty': 0}, 'key': 'pid', 'time_since_update': 0.40526819229125977, 'cmdline': ['/usr/bin/gnome-shell'], 'username': 'nicolargo'}] Args: diff --git a/docs/api/restful.rst b/docs/api/restful.rst index e9446b72..b417b0bf 100644 --- a/docs/api/restful.rst +++ b/docs/api/restful.rst @@ -40,6 +40,7 @@ Note: The url_prefix should always end with a slash (``/``). For example: .. code-block:: ini + [outputs] url_prefix = /glances/ @@ -52,6 +53,97 @@ API documentation URL The API documentation is embeded in the server and available at the following URL: ``http://localhost:61208/docs#/``. +Authentication +-------------- + +Glances API supports both HTTP Basic authentication and JWT (JSON Web Token) Bearer authentication. + +To enable authentication, start Glances with the ``--password`` option. + +To generate a new login/password pair, use the following command: + +.. code-block:: bash + + glances -w --username + > Enter new username: foo + > Enter new password: ******** + > Confirm new password: ******** + > User 'username' created/updated successfully. + +To reuse an existing login/password pair, start Glances with the ``-u `` option: + +.. code-block:: bash + + glances -w -u foo + +JWT Token Authentication +~~~~~~~~~~~~~~~~~~~~~~~~ + +JWT authentication requires the ``python-jose`` library to be installed. + +**Step 1: Get a JWT Token** + +Request a token by sending your credentials to the token endpoint: + +.. code-block:: bash + + curl -X POST http://localhost:61208/api/4/token \ + -H "Content-Type: application/json" \ + -d '{"username": "your_username", "password": "your_password"}' + +This will return a response like: + +.. code-block:: json + + { + "access_token": "...", + "token_type": "bearer", + "expires_in": 3600 + } + +**Step 2: Use the Token** + +Use the token in the Authorization header with Bearer authentication: + +.. code-block:: bash + + # Store the token in a variable + TOKEN="your_access_token_here" + + # Access a protected endpoint + curl -H "Authorization: Bearer $TOKEN" \ + http://localhost:61208/api/4/cpu + +**Complete Example:** + +.. code-block:: bash + + # Get token and extract access_token + TOKEN=$(curl -s -X POST http://localhost:61208/api/4/token \ + -H "Content-Type: application/json" \ + -d '{"username": "glances", "password": "mypassword"}' \ + | grep -o '"access_token":"[^"]*"' \ + | cut -d'"' -f4) + + # Use the token to get CPU stats + curl -H "Authorization: Bearer $TOKEN" \ + http://localhost:61208/api/4/cpu + +**Configuration:** + +You can configure JWT settings in the Glances configuration file: + +.. code-block:: ini + + [outputs] + # JWT secret key (if not set, a random key will be generated) + jwt_secret_key = your-secret-key-here + # JWT token expiration in minutes (default: 60) + jwt_expire_minutes = 60 + +**Note:** The token endpoint (``/api/4/token``) does not require authentication. +Protected endpoints support both Bearer token and Basic Auth authentication methods. + WebUI refresh ------------- @@ -160,7 +252,7 @@ Get plugin stats:: "refresh": 3.0, "regex": True, "result": None, - "timer": 0.5492854118347168}, + "timer": 0.3574552536010742}, {"count": 0, "countmax": 20.0, "countmin": None, @@ -169,7 +261,7 @@ Get plugin stats:: "refresh": 3.0, "regex": True, "result": None, - "timer": 0.5491828918457031}] + "timer": 0.35739827156066895}] Fields descriptions: @@ -197,7 +289,7 @@ Get a specific item when field matches the given value:: "refresh": 3.0, "regex": True, "result": None, - "timer": 0.5492854118347168}]} + "timer": 0.3574552536010742}]} GET cloud --------- @@ -287,19 +379,19 @@ Get plugin stats:: # curl http://localhost:61208/api/4/cpu {"cpucore": 16, - "ctx_switches": 472895569, + "ctx_switches": 541137856, "guest": 0.0, - "idle": 93.1, - "interrupts": 410401982, - "iowait": 0.3, + "idle": 89.7, + "interrupts": 464412401, + "iowait": 0.6, "irq": 0.0, "nice": 0.0, - "soft_interrupts": 174937132, + "soft_interrupts": 197642152, "steal": 0.0, "syscalls": 0, - "system": 2.9, - "total": 6.6, - "user": 3.7} + "system": 4.1, + "total": 9.0, + "user": 5.6} Fields descriptions: @@ -332,7 +424,7 @@ Fields descriptions: Get a specific field:: # curl http://localhost:61208/api/4/cpu/total - {"total": 6.6} + {"total": 9.0} GET diskio ---------- @@ -342,20 +434,20 @@ Get plugin stats:: # curl http://localhost:61208/api/4/diskio [{"disk_name": "nvme0n1", "key": "disk_name", - "read_bytes": 29274824192, - "read_count": 1163128, + "read_bytes": 47595146752, + "read_count": 1611927, "read_latency": 0, - "read_time": 313808, - "write_bytes": 44188152832, - "write_count": 3047685, + "read_time": 492221, + "write_bytes": 47441617920, + "write_count": 3244385, "write_latency": 0, - "write_time": 2442820}, + "write_time": 2733634}, {"disk_name": "nvme0n1p1", "key": "disk_name", - "read_bytes": 14747648, - "read_count": 1062, + "read_bytes": 15820800, + "read_count": 1110, "read_latency": 0, - "read_time": 887, + "read_time": 898, "write_bytes": 1024, "write_count": 2, "write_latency": 0, @@ -401,14 +493,14 @@ Get a specific item when field matches the given value:: # curl http://localhost:61208/api/4/diskio/disk_name/value/nvme0n1 {"nvme0n1": [{"disk_name": "nvme0n1", "key": "disk_name", - "read_bytes": 29274824192, - "read_count": 1163128, + "read_bytes": 47595146752, + "read_count": 1611927, "read_latency": 0, - "read_time": 313808, - "write_bytes": 44188152832, - "write_count": 3047685, + "read_time": 492221, + "write_bytes": 47441617920, + "write_count": 3244385, "write_latency": 0, - "write_time": 2442820}]} + "write_time": 2733634}]} GET folders ----------- @@ -435,14 +527,14 @@ Get plugin stats:: # curl http://localhost:61208/api/4/fs [{"device_name": "/dev/mapper/ubuntu--vg-ubuntu--lv", - "free": 582918635520, + "free": 582995034112, "fs_type": "ext4", "key": "mnt_point", "mnt_point": "/", "options": "rw,relatime", "percent": 38.8, "size": 1003736440832, - "used": 369755299840}, + "used": 369678901248}, {"device_name": "zsfpool", "free": 41680896, "fs_type": "zfs", @@ -473,14 +565,14 @@ Get a specific item when field matches the given value:: # curl http://localhost:61208/api/4/fs/mnt_point/value// {"/": [{"device_name": "/dev/mapper/ubuntu--vg-ubuntu--lv", - "free": 582918635520, + "free": 582995034112, "fs_type": "ext4", "key": "mnt_point", "mnt_point": "/", "options": "rw,relatime", "percent": 38.8, "size": 1003736440832, - "used": 369755299840}]} + "used": 369678901248}]} GET gpu ------- @@ -513,11 +605,7 @@ GET ip Get plugin stats:: # curl http://localhost:61208/api/4/ip - {"address": "192.168.1.26", - "mask": "255.255.255.0", - "mask_cidr": 24, - "public_address": "", - "public_info_human": ""} + {"address": "192.168.1.26", "mask": "255.255.255.0", "mask_cidr": 24} Fields descriptions: @@ -553,9 +641,9 @@ Get plugin stats:: # curl http://localhost:61208/api/4/load {"cpucore": 16, - "min1": 0.79931640625, - "min15": 1.03662109375, - "min5": 1.02294921875} + "min1": 0.57421875, + "min15": 0.71435546875, + "min5": 0.66748046875} Fields descriptions: @@ -567,7 +655,7 @@ Fields descriptions: Get a specific field:: # curl http://localhost:61208/api/4/load/min1 - {"min1": 0.79931640625} + {"min1": 0.57421875} GET mem ------- @@ -575,16 +663,16 @@ GET mem Get plugin stats:: # curl http://localhost:61208/api/4/mem - {"active": 5510766592, - "available": 3639285736, - "buffers": 130744320, - "cached": 3574328168, - "free": 1352519680, - "inactive": 7184216064, - "percent": 77.8, - "shared": 839827456, + {"active": 9780051968, + "available": 5885732840, + "buffers": 257511424, + "cached": 5740473192, + "free": 692682752, + "inactive": 4103991296, + "percent": 64.2, + "shared": 766976000, "total": 16422825984, - "used": 12783540248} + "used": 10537093144} Fields descriptions: @@ -611,13 +699,13 @@ GET memswap Get plugin stats:: # curl http://localhost:61208/api/4/memswap - {"free": 1576960, - "percent": 100.0, - "sin": 2307080192, - "sout": 7715819520, + {"free": 4211441664, + "percent": 1.9, + "sin": 2388013056, + "sout": 7785721856, "time_since_update": 1, "total": 4294963200, - "used": 4293386240} + "used": 83521536} Fields descriptions: @@ -642,18 +730,18 @@ Get plugin stats:: # curl http://localhost:61208/api/4/network [{"alias": None, "bytes_all": 0, - "bytes_all_gauge": 15142749821, + "bytes_all_gauge": 15435261591, "bytes_all_rate_per_sec": 0, "bytes_recv": 0, - "bytes_recv_gauge": 13975902890, + "bytes_recv_gauge": 14118069401, "bytes_recv_rate_per_sec": 0, "bytes_sent": 0, - "bytes_sent_gauge": 1166846931, + "bytes_sent_gauge": 1317192190, "bytes_sent_rate_per_sec": 0, "interface_name": "wlp0s20f3", "key": "interface_name", "speed": 0, - "time_since_update": 0.5604753494262695}] + "time_since_update": 0.365170955657959}] Fields descriptions: @@ -682,18 +770,18 @@ Get a specific item when field matches the given value:: # curl http://localhost:61208/api/4/network/interface_name/value/wlp0s20f3 {"wlp0s20f3": [{"alias": None, "bytes_all": 0, - "bytes_all_gauge": 15142749821, + "bytes_all_gauge": 15435261591, "bytes_all_rate_per_sec": 0, "bytes_recv": 0, - "bytes_recv_gauge": 13975902890, + "bytes_recv_gauge": 14118069401, "bytes_recv_rate_per_sec": 0, "bytes_sent": 0, - "bytes_sent_gauge": 1166846931, + "bytes_sent_gauge": 1317192190, "bytes_sent_rate_per_sec": 0, "interface_name": "wlp0s20f3", "key": "interface_name", "speed": 0, - "time_since_update": 0.5604753494262695}]} + "time_since_update": 0.365170955657959}]} GET now ------- @@ -701,7 +789,7 @@ GET now Get plugin stats:: # curl http://localhost:61208/api/4/now - {"custom": "2026-01-03 15:12:07 CET", "iso": "2026-01-03T15:12:07+01:00"} + {"custom": "2026-01-04 10:39:41 CET", "iso": "2026-01-04T10:39:41+01:00"} Fields descriptions: @@ -711,7 +799,7 @@ Fields descriptions: Get a specific field:: # curl http://localhost:61208/api/4/now/iso - {"iso": "2026-01-03T15:12:07+01:00"} + {"iso": "2026-01-04T10:39:41+01:00"} GET percpu ---------- @@ -723,7 +811,7 @@ Get plugin stats:: "dpc": None, "guest": 0.0, "guest_nice": 0.0, - "idle": 43.0, + "idle": 25.0, "interrupt": None, "iowait": 0.0, "irq": 0.0, @@ -731,14 +819,14 @@ Get plugin stats:: "nice": 0.0, "softirq": 0.0, "steal": 0.0, - "system": 11.0, - "total": 57.0, - "user": 0.0}, + "system": 9.0, + "total": 75.0, + "user": 1.0}, {"cpu_number": 1, "dpc": None, "guest": 0.0, "guest_nice": 0.0, - "idle": 54.0, + "idle": 34.0, "interrupt": None, "iowait": 0.0, "irq": 0.0, @@ -746,8 +834,8 @@ Get plugin stats:: "nice": 0.0, "softirq": 0.0, "steal": 0.0, - "system": 0.0, - "total": 46.0, + "system": 1.0, + "total": 66.0, "user": 0.0}] Fields descriptions: @@ -784,7 +872,7 @@ Get plugin stats:: "port": 0, "refresh": 30, "rtt_warning": None, - "status": 0.007946, + "status": 0.005842, "timeout": 3}] Fields descriptions: @@ -812,7 +900,7 @@ Get a specific item when field matches the given value:: "port": 0, "refresh": 30, "rtt_warning": None, - "status": 0.007946, + "status": 0.005842, "timeout": 3}]} GET processcount @@ -821,7 +909,7 @@ GET processcount Get plugin stats:: # curl http://localhost:61208/api/4/processcount - {"pid_max": 0, "running": 1, "sleeping": 442, "thread": 2426, "total": 597} + {"pid_max": 0, "running": 1, "sleeping": 412, "thread": 1984, "total": 561} Fields descriptions: @@ -834,7 +922,7 @@ Fields descriptions: Get a specific field:: # curl http://localhost:61208/api/4/processcount/total - {"total": 597} + {"total": 561} GET processlist --------------- @@ -844,22 +932,22 @@ Get plugin stats:: # curl http://localhost:61208/api/4/processlist [{"cmdline": ["/sbin/init", "splash"], "cpu_percent": 0.0, - "cpu_times": {"children_system": 292.72, - "children_user": 5149.74, + "cpu_times": {"children_system": 11313.22, + "children_user": 46981.78, "iowait": 0.0, - "system": 9.16, - "user": 13.08}, + "system": 10.51, + "user": 14.18}, "gids": {"effective": 0, "real": 0, "saved": 0}, "io_counters": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "key": "pid", "memory_info": {"data": 7282688, "dirty": 0, "lib": 0, - "rss": 14585856, - "shared": 8134656, + "rss": 12140544, + "shared": 5771264, "text": 45056, "vms": 26632192}, - "memory_percent": 0.0888145317633538, + "memory_percent": 0.07392481666570644, "name": "systemd", "nice": 0, "num_threads": 1, @@ -872,7 +960,7 @@ Get plugin stats:: "cpu_times": {"children_system": 0.0, "children_user": 0.0, "iowait": 0.0, - "system": 0.22, + "system": 0.25, "user": 0.0}, "gids": {"effective": 0, "real": 0, "saved": 0}, "io_counters": [0, 0, 0, 0, 0], @@ -915,20 +1003,20 @@ GET programlist Get plugin stats:: # curl http://localhost:61208/api/4/programlist - [{"childrens": [1, 5172], + [{"childrens": [1, 813665], "cmdline": ["systemd"], "cpu_percent": 0, - "cpu_times": {"children_system": 1588.9, - "children_user": 15264.5, - "system": 12.5, - "user": 33.54}, + "cpu_times": {"children_system": 11313.57, + "children_user": 46982.78, + "system": 10.86, + "user": 16.91}, "io_counters": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - "memory_info": {"data": 10764288, - "rss": 26198016, - "shared": 16584704, + "memory_info": {"data": 10608640, + "rss": 25923584, + "shared": 15585280, "text": 90112, - "vms": 49463296}, - "memory_percent": 0.15952197280494548, + "vms": 49307648}, + "memory_percent": 0.15785093275211068, "name": "systemd", "nice": 0, "nprocs": 2, @@ -943,7 +1031,7 @@ Get plugin stats:: "cpu_times": {"children_system": 0.0, "children_user": 0.0, "iowait": 0.0, - "system": 0.22, + "system": 0.25, "user": 0.0}, "io_counters": [0, 0, 0, 0, 0], "memory_info": {"data": 0, @@ -993,75 +1081,15 @@ GET quicklook Get plugin stats:: # curl http://localhost:61208/api/4/quicklook - {"cpu": 6.6, + {"cpu": 9.0, "cpu_hz": 4475000000.0, - "cpu_hz_current": 654981687.4999999, + "cpu_hz_current": 1156295562.5, "cpu_log_core": 16, "cpu_name": "13th Gen Intel(R) Core(TM) i7-13620H", "cpu_phys_core": 10, - "load": 6.5, - "mem": 77.8, + "load": 4.5, + "mem": 64.2, "percpu": [{"cpu_number": 0, - "dpc": None, - "guest": 0.0, - "guest_nice": 0.0, - "idle": 43.0, - "interrupt": None, - "iowait": 0.0, - "irq": 0.0, - "key": "cpu_number", - "nice": 0.0, - "softirq": 0.0, - "steal": 0.0, - "system": 11.0, - "total": 57.0, - "user": 0.0}, - {"cpu_number": 1, - "dpc": None, - "guest": 0.0, - "guest_nice": 0.0, - "idle": 54.0, - "interrupt": None, - "iowait": 0.0, - "irq": 0.0, - "key": "cpu_number", - "nice": 0.0, - "softirq": 0.0, - "steal": 0.0, - "system": 0.0, - "total": 46.0, - "user": 0.0}, - {"cpu_number": 2, - "dpc": None, - "guest": 0.0, - "guest_nice": 0.0, - "idle": 53.0, - "interrupt": None, - "iowait": 0.0, - "irq": 0.0, - "key": "cpu_number", - "nice": 0.0, - "softirq": 0.0, - "steal": 0.0, - "system": 0.0, - "total": 47.0, - "user": 0.0}, - {"cpu_number": 3, - "dpc": None, - "guest": 0.0, - "guest_nice": 0.0, - "idle": 55.0, - "interrupt": None, - "iowait": 0.0, - "irq": 0.0, - "key": "cpu_number", - "nice": 0.0, - "softirq": 0.0, - "steal": 0.0, - "system": 0.0, - "total": 45.0, - "user": 0.0}, - {"cpu_number": 4, "dpc": None, "guest": 0.0, "guest_nice": 0.0, @@ -1073,59 +1101,14 @@ Get plugin stats:: "nice": 0.0, "softirq": 0.0, "steal": 0.0, - "system": 18.0, + "system": 9.0, "total": 75.0, - "user": 10.0}, - {"cpu_number": 5, - "dpc": None, - "guest": 0.0, - "guest_nice": 0.0, - "idle": 54.0, - "interrupt": None, - "iowait": 0.0, - "irq": 0.0, - "key": "cpu_number", - "nice": 0.0, - "softirq": 0.0, - "steal": 0.0, - "system": 0.0, - "total": 46.0, - "user": 0.0}, - {"cpu_number": 6, - "dpc": None, - "guest": 0.0, - "guest_nice": 0.0, - "idle": 41.0, - "interrupt": None, - "iowait": 2.0, - "irq": 0.0, - "key": "cpu_number", - "nice": 0.0, - "softirq": 0.0, - "steal": 0.0, - "system": 4.0, - "total": 59.0, - "user": 7.0}, - {"cpu_number": 7, - "dpc": None, - "guest": 0.0, - "guest_nice": 0.0, - "idle": 55.0, - "interrupt": None, - "iowait": 0.0, - "irq": 0.0, - "key": "cpu_number", - "nice": 0.0, - "softirq": 0.0, - "steal": 0.0, - "system": 0.0, - "total": 45.0, "user": 1.0}, - {"cpu_number": 8, + {"cpu_number": 1, "dpc": None, "guest": 0.0, "guest_nice": 0.0, - "idle": 50.0, + "idle": 34.0, "interrupt": None, "iowait": 0.0, "irq": 0.0, @@ -1133,74 +1116,14 @@ Get plugin stats:: "nice": 0.0, "softirq": 0.0, "steal": 0.0, - "system": 2.0, - "total": 50.0, + "system": 1.0, + "total": 66.0, "user": 0.0}, - {"cpu_number": 9, + {"cpu_number": 2, "dpc": None, "guest": 0.0, "guest_nice": 0.0, - "idle": 54.0, - "interrupt": None, - "iowait": 0.0, - "irq": 0.0, - "key": "cpu_number", - "nice": 0.0, - "softirq": 0.0, - "steal": 0.0, - "system": 0.0, - "total": 46.0, - "user": 1.0}, - {"cpu_number": 10, - "dpc": None, - "guest": 0.0, - "guest_nice": 0.0, - "idle": 54.0, - "interrupt": None, - "iowait": 0.0, - "irq": 0.0, - "key": "cpu_number", - "nice": 0.0, - "softirq": 0.0, - "steal": 0.0, - "system": 0.0, - "total": 46.0, - "user": 1.0}, - {"cpu_number": 11, - "dpc": None, - "guest": 0.0, - "guest_nice": 0.0, - "idle": 55.0, - "interrupt": None, - "iowait": 0.0, - "irq": 0.0, - "key": "cpu_number", - "nice": 0.0, - "softirq": 0.0, - "steal": 0.0, - "system": 0.0, - "total": 45.0, - "user": 0.0}, - {"cpu_number": 12, - "dpc": None, - "guest": 0.0, - "guest_nice": 0.0, - "idle": 54.0, - "interrupt": None, - "iowait": 0.0, - "irq": 0.0, - "key": "cpu_number", - "nice": 0.0, - "softirq": 0.0, - "steal": 0.0, - "system": 0.0, - "total": 46.0, - "user": 0.0}, - {"cpu_number": 13, - "dpc": None, - "guest": 0.0, - "guest_nice": 0.0, - "idle": 53.0, + "idle": 32.0, "interrupt": None, "iowait": 1.0, "irq": 0.0, @@ -1208,14 +1131,179 @@ Get plugin stats:: "nice": 0.0, "softirq": 0.0, "steal": 0.0, + "system": 1.0, + "total": 68.0, + "user": 1.0}, + {"cpu_number": 3, + "dpc": None, + "guest": 0.0, + "guest_nice": 0.0, + "idle": 35.0, + "interrupt": None, + "iowait": 0.0, + "irq": 0.0, + "key": "cpu_number", + "nice": 0.0, + "softirq": 0.0, + "steal": 0.0, + "system": 1.0, + "total": 65.0, + "user": 1.0}, + {"cpu_number": 4, + "dpc": None, + "guest": 0.0, + "guest_nice": 0.0, + "idle": 21.0, + "interrupt": None, + "iowait": 0.0, + "irq": 0.0, + "key": "cpu_number", + "nice": 0.0, + "softirq": 0.0, + "steal": 0.0, + "system": 10.0, + "total": 79.0, + "user": 3.0}, + {"cpu_number": 5, + "dpc": None, + "guest": 0.0, + "guest_nice": 0.0, + "idle": 36.0, + "interrupt": None, + "iowait": 0.0, + "irq": 0.0, + "key": "cpu_number", + "nice": 0.0, + "softirq": 0.0, + "steal": 0.0, "system": 0.0, - "total": 47.0, + "total": 64.0, + "user": 0.0}, + {"cpu_number": 6, + "dpc": None, + "guest": 0.0, + "guest_nice": 0.0, + "idle": 21.0, + "interrupt": None, + "iowait": 1.0, + "irq": 0.0, + "key": "cpu_number", + "nice": 0.0, + "softirq": 0.0, + "steal": 0.0, + "system": 6.0, + "total": 79.0, + "user": 6.0}, + {"cpu_number": 7, + "dpc": None, + "guest": 0.0, + "guest_nice": 0.0, + "idle": 35.0, + "interrupt": None, + "iowait": 0.0, + "irq": 0.0, + "key": "cpu_number", + "nice": 0.0, + "softirq": 0.0, + "steal": 0.0, + "system": 0.0, + "total": 65.0, + "user": 0.0}, + {"cpu_number": 8, + "dpc": None, + "guest": 0.0, + "guest_nice": 0.0, + "idle": 31.0, + "interrupt": None, + "iowait": 0.0, + "irq": 0.0, + "key": "cpu_number", + "nice": 0.0, + "softirq": 0.0, + "steal": 0.0, + "system": 1.0, + "total": 69.0, + "user": 2.0}, + {"cpu_number": 9, + "dpc": None, + "guest": 0.0, + "guest_nice": 0.0, + "idle": 36.0, + "interrupt": None, + "iowait": 0.0, + "irq": 0.0, + "key": "cpu_number", + "nice": 0.0, + "softirq": 0.0, + "steal": 0.0, + "system": 0.0, + "total": 64.0, + "user": 0.0}, + {"cpu_number": 10, + "dpc": None, + "guest": 0.0, + "guest_nice": 0.0, + "idle": 33.0, + "interrupt": None, + "iowait": 0.0, + "irq": 0.0, + "key": "cpu_number", + "nice": 0.0, + "softirq": 0.0, + "steal": 0.0, + "system": 0.0, + "total": 67.0, + "user": 2.0}, + {"cpu_number": 11, + "dpc": None, + "guest": 0.0, + "guest_nice": 0.0, + "idle": 34.0, + "interrupt": None, + "iowait": 0.0, + "irq": 0.0, + "key": "cpu_number", + "nice": 0.0, + "softirq": 0.0, + "steal": 0.0, + "system": 0.0, + "total": 66.0, + "user": 0.0}, + {"cpu_number": 12, + "dpc": None, + "guest": 0.0, + "guest_nice": 0.0, + "idle": 33.0, + "interrupt": None, + "iowait": 0.0, + "irq": 0.0, + "key": "cpu_number", + "nice": 0.0, + "softirq": 0.0, + "steal": 0.0, + "system": 1.0, + "total": 67.0, + "user": 1.0}, + {"cpu_number": 13, + "dpc": None, + "guest": 0.0, + "guest_nice": 0.0, + "idle": 34.0, + "interrupt": None, + "iowait": 0.0, + "irq": 0.0, + "key": "cpu_number", + "nice": 0.0, + "softirq": 0.0, + "steal": 0.0, + "system": 0.0, + "total": 66.0, "user": 1.0}, {"cpu_number": 14, "dpc": None, "guest": 0.0, "guest_nice": 0.0, - "idle": 54.0, + "idle": 35.0, "interrupt": None, "iowait": 0.0, "irq": 0.0, @@ -1224,13 +1312,13 @@ Get plugin stats:: "softirq": 0.0, "steal": 0.0, "system": 0.0, - "total": 46.0, - "user": 1.0}, + "total": 65.0, + "user": 0.0}, {"cpu_number": 15, "dpc": None, "guest": 0.0, "guest_nice": 0.0, - "idle": 54.0, + "idle": 35.0, "interrupt": None, "iowait": 0.0, "irq": 0.0, @@ -1239,9 +1327,9 @@ Get plugin stats:: "softirq": 0.0, "steal": 0.0, "system": 0.0, - "total": 46.0, - "user": 1.0}], - "swap": 100.0} + "total": 65.0, + "user": 0.0}], + "swap": 1.9} Fields descriptions: @@ -1279,14 +1367,14 @@ Get plugin stats:: "label": "Ambient", "type": "temperature_core", "unit": "C", - "value": 34, + "value": 36, "warning": 0}, {"critical": None, "key": "label", "label": "Ambient 3", "type": "temperature_core", "unit": "C", - "value": 29, + "value": 28, "warning": 0}] Fields descriptions: @@ -1349,7 +1437,7 @@ Get a specific item when field matches the given value:: "label": "Ambient", "type": "temperature_core", "unit": "C", - "value": 34, + "value": 36, "warning": 0}]} GET smart @@ -1393,7 +1481,7 @@ GET uptime Get plugin stats:: # curl http://localhost:61208/api/4/uptime - "6 days, 22:46:22" + "7 days, 18:13:56" GET version ----------- @@ -1438,8 +1526,8 @@ Get plugin stats:: # curl http://localhost:61208/api/4/wifi [{"key": "ssid", - "quality_level": -62.0, - "quality_link": 48.0, + "quality_level": -61.0, + "quality_link": 49.0, "ssid": "wlp0s20f3"}] Get a specific field:: @@ -1451,8 +1539,8 @@ Get a specific item when field matches the given value:: # curl http://localhost:61208/api/4/wifi/ssid/value/wlp0s20f3 {"wlp0s20f3": [{"key": "ssid", - "quality_level": -62.0, - "quality_link": 48.0, + "quality_level": -61.0, + "quality_link": 49.0, "ssid": "wlp0s20f3"}]} GET all stats @@ -1516,34 +1604,34 @@ GET stats history History of a plugin:: # curl http://localhost:61208/api/4/cpu/history - {"system": [["2026-01-03T14:12:08.389170+00:00", 2.9], - ["2026-01-03T14:12:09.494051+00:00", 1.1], - ["2026-01-03T14:12:10.540637+00:00", 1.1]], - "user": [["2026-01-03T14:12:08.389165+00:00", 3.7], - ["2026-01-03T14:12:09.494048+00:00", 1.3], - ["2026-01-03T14:12:10.540632+00:00", 1.3]]} + {"system": [["2026-01-04T09:39:42.831148+00:00", 4.1], + ["2026-01-04T09:39:43.889230+00:00", 1.1], + ["2026-01-04T09:39:44.917289+00:00", 1.1]], + "user": [["2026-01-04T09:39:42.831146+00:00", 5.6], + ["2026-01-04T09:39:43.889229+00:00", 1.8], + ["2026-01-04T09:39:44.917287+00:00", 1.8]]} Limit history to last 2 values:: # curl http://localhost:61208/api/4/cpu/history/2 - {"system": [["2026-01-03T14:12:09.494051+00:00", 1.1], - ["2026-01-03T14:12:10.540637+00:00", 1.1]], - "user": [["2026-01-03T14:12:09.494048+00:00", 1.3], - ["2026-01-03T14:12:10.540632+00:00", 1.3]]} + {"system": [["2026-01-04T09:39:43.889230+00:00", 1.1], + ["2026-01-04T09:39:44.917289+00:00", 1.1]], + "user": [["2026-01-04T09:39:43.889229+00:00", 1.8], + ["2026-01-04T09:39:44.917287+00:00", 1.8]]} History for a specific field:: # curl http://localhost:61208/api/4/cpu/system/history - {"system": [["2026-01-03T14:12:07.138062+00:00", 2.9], - ["2026-01-03T14:12:08.389170+00:00", 2.9], - ["2026-01-03T14:12:09.494051+00:00", 1.1], - ["2026-01-03T14:12:10.540637+00:00", 1.1]]} + {"system": [["2026-01-04T09:39:41.637794+00:00", 4.1], + ["2026-01-04T09:39:42.831148+00:00", 4.1], + ["2026-01-04T09:39:43.889230+00:00", 1.1], + ["2026-01-04T09:39:44.917289+00:00", 1.1]]} Limit history for a specific field to last 2 values:: # curl http://localhost:61208/api/4/cpu/system/history - {"system": [["2026-01-03T14:12:09.494051+00:00", 1.1], - ["2026-01-03T14:12:10.540637+00:00", 1.1]]} + {"system": [["2026-01-04T09:39:43.889230+00:00", 1.1], + ["2026-01-04T09:39:44.917289+00:00", 1.1]]} GET limits (used for thresholds) -------------------------------- diff --git a/docs/man/glances.1 b/docs/man/glances.1 index 503dcb97..8c2c17de 100644 --- a/docs/man/glances.1 +++ b/docs/man/glances.1 @@ -28,7 +28,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" "Jan 03, 2026" "4.4.2_dev1" "Glances" +.TH "GLANCES" "1" "Jan 04, 2026" "4.4.2_dev1" "Glances" .SH NAME glances \- An eye on your system .SH SYNOPSIS diff --git a/glances/jwt_utils.py b/glances/jwt_utils.py new file mode 100644 index 00000000..be13e64b --- /dev/null +++ b/glances/jwt_utils.py @@ -0,0 +1,104 @@ +# +# This file is part of Glances. +# +# SPDX-FileCopyrightText: 2026 Nicolas Hennion +# +# SPDX-License-Identifier: LGPL-3.0-only +# + +"""JWT utilities for Glances REST API authentication.""" + +import secrets +from datetime import datetime, timedelta, timezone + +from glances.logger import logger + +# JWT library import with fallback +try: + from jose import JWTError, jwt + + JWT_AVAILABLE = True +except ImportError: + JWT_AVAILABLE = False + JWTError = Exception # Placeholder + + +class JWTHandler: + """Handle JWT token creation and validation.""" + + # Algorithm for JWT signing + ALGORITHM = "HS256" + + def __init__(self, secret_key: str | None = None, expire_minutes: int = 60): + """Initialize JWT handler. + + Args: + secret_key: Secret key for signing tokens. If None, generates a random key. + expire_minutes: Token expiration time in minutes (default: 60) + """ + if not JWT_AVAILABLE: + logger.warning("python-jose library not available. JWT authentication disabled.") + self._secret_key = None + else: + # Use provided key or generate a secure random key + self._secret_key = secret_key or secrets.token_urlsafe(32) + if secret_key is None: + logger.info("JWT secret key generated (valid for this server instance only)") + + self._expire_minutes = expire_minutes + + @property + def is_available(self) -> bool: + """Check if JWT functionality is available.""" + return JWT_AVAILABLE and self._secret_key is not None + + @property + def expire_minutes(self) -> int: + """Return the token expiration time in minutes.""" + return self._expire_minutes + + def create_access_token(self, username: str) -> str: + """Create a JWT access token for the given username. + + Args: + username: The username to encode in the token + + Returns: + Encoded JWT token string + + Raises: + RuntimeError: If JWT is not available + """ + if not self.is_available: + raise RuntimeError("JWT authentication is not available") + + expire = datetime.now(timezone.utc) + timedelta(minutes=self._expire_minutes) + to_encode = { + "sub": username, + "exp": expire, + "iat": datetime.now(timezone.utc), + "iss": "glances", + } + return jwt.encode(to_encode, self._secret_key, algorithm=self.ALGORITHM) + + def verify_token(self, token: str) -> str | None: + """Verify a JWT token and extract the username. + + Args: + token: The JWT token to verify + + Returns: + Username if valid, None otherwise + """ + if not self.is_available: + return None + + try: + payload = jwt.decode(token, self._secret_key, algorithms=[self.ALGORITHM]) + username: str = payload.get("sub") + if username is None: + return None + return username + except JWTError as e: + logger.debug(f"JWT verification failed: {e}") + return None diff --git a/glances/main.py b/glances/main.py index 7e8d8b07..bb63a6ae 100644 --- a/glances/main.py +++ b/glances/main.py @@ -427,12 +427,13 @@ Examples of use: dest='bind_address', help='bind server to the given IPv4/IPv6 address or hostname', ) + parser.add_argument('-u', dest='username_used', help='use or define the given username') parser.add_argument( '--username', action='store_true', default=False, dest='username_prompt', - help='define a client/server username', + help='define or use an username', ) parser.add_argument( '--password', @@ -441,7 +442,6 @@ Examples of use: dest='password_prompt', help='define a client/server password', ) - parser.add_argument('-u', dest='username_used', help='use the given client/server username') parser.add_argument('--snmp-community', default='public', dest='snmp_community', help='SNMP community') parser.add_argument('--snmp-port', default=161, type=int, dest='snmp_port', help='SNMP port') parser.add_argument('--snmp-version', default='2c', dest='snmp_version', help='SNMP version (1, 2c or 3)') @@ -775,12 +775,10 @@ Examples of use: # Every username needs a password args.password_prompt = True # Prompt username - if args.server: - args.username = self.__get_username(description='Define the Glances server username: ') - elif args.webserver: - args.username = self.__get_username(description='Define the Glances webserver username: ') + if args.server or args.webserver: + args.username = self.__get_username(description='Enter new username: ') elif args.client: - args.username = self.__get_username(description='Enter the Glances server username: ') + args.username = self.__get_username(description='Enter username: ') else: if args.username_used: # A username has been set using the -u option ? @@ -791,21 +789,15 @@ Examples of use: if args.password_prompt or args.username_used: # Interactive or file password - if args.server: + if args.server or args.webserver: args.password = self.__get_password( - description=f'Define the Glances server password ({args.username} username): ', - confirm=True, - username=args.username, - ) - elif args.webserver: - args.password = self.__get_password( - description=f'Define the Glances webserver password ({args.username} username): ', + description='Enter new password: ', confirm=True, username=args.username, ) elif args.client: args.password = self.__get_password( - description=f'Enter the Glances server password ({args.username} username): ', + description='Enter password: ', clear=True, username=args.username, ) diff --git a/glances/outputs/glances_restful_api.py b/glances/outputs/glances_restful_api.py index 03bb7e64..ce88a014 100644 --- a/glances/outputs/glances_restful_api.py +++ b/glances/outputs/glances_restful_api.py @@ -20,6 +20,15 @@ from glances.events_list import glances_events from glances.globals import json_dumps from glances.logger import logger from glances.password import GlancesPassword + +# JWT import with fallback +try: + from glances.jwt_utils import JWTHandler + + JWT_AVAILABLE = True +except ImportError: + JWT_AVAILABLE = False + JWTHandler = None from glances.plugins.plugin.dag import get_plugin_dependencies from glances.processes import glances_processes from glances.servers_list import GlancesServersList @@ -33,7 +42,7 @@ try: from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.gzip import GZipMiddleware from fastapi.responses import HTMLResponse, JSONResponse - from fastapi.security import HTTPBasic, HTTPBasicCredentials + from fastapi.security import HTTPAuthorizationCredentials, HTTPBasic, HTTPBasicCredentials, HTTPBearer from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates except ImportError as e: @@ -50,7 +59,8 @@ import contextlib import threading import time -security = HTTPBasic() +security = HTTPBasic(auto_error=False) +bearer_security = HTTPBearer(auto_error=False) class GlancesJSONResponse(JSONResponse): @@ -122,13 +132,23 @@ class GlancesRestfulApi: self.bind_url = urljoin(f'{self.protocol}://{self.args.bind_address}:{self.args.port}/', self.url_prefix) # FastAPI Init + # Note: Authentication is now applied at router level, not app level, + # to allow the token endpoint to be unauthenticated + self._app = FastAPI(default_response_class=GlancesJSONResponse) if self.args.password: - self._app = FastAPI(default_response_class=GlancesJSONResponse, dependencies=[Depends(self.authentication)]) self._password = GlancesPassword(username=args.username, config=config) - + # Initialize JWT handler + if JWT_AVAILABLE: + jwt_secret = config.get_value('outputs', 'jwt_secret_key', default=None) + jwt_expire = config.get_int_value('outputs', 'jwt_expire_minutes', default=60) + self._jwt_handler = JWTHandler(secret_key=jwt_secret, expire_minutes=jwt_expire) + logger.info(f"JWT authentication enabled (token expiration: {jwt_expire} minutes)") + else: + self._jwt_handler = None + logger.info("JWT authentication not available (python-jose not installed)") else: - self._app = FastAPI(default_response_class=GlancesJSONResponse) self._password = None + self._jwt_handler = None # Set path for WebUI webui_root_path = config.get_value( @@ -158,6 +178,9 @@ class GlancesRestfulApi: ) # FastAPI Define routes + # Token endpoint router (no authentication required) - must be added first + if self.args.password and self._jwt_handler is not None: + self._app.include_router(self._token_router()) self._app.include_router(self._router()) # Enable auto discovering of the service @@ -206,16 +229,36 @@ class GlancesRestfulApi: self.servers_list.update_servers_stats() self.timer = Timer(self.args.cached_time) - def authentication(self, creds: Annotated[HTTPBasicCredentials, Depends(security)]): - """Check if a username/password combination is valid.""" - if creds.username == self.args.username: - # check_password - if self._password.check_password(self.args.password, self._password.get_hash(creds.password)): - return creds.username + def authentication( + self, + basic_creds: Annotated[HTTPBasicCredentials | None, Depends(security)] = None, + bearer_creds: Annotated[HTTPAuthorizationCredentials | None, Depends(bearer_security)] = None, + ): + """Check if a username/password combination or JWT token is valid. - # If the username/password combination is invalid, return an HTTP 401 + Supports both HTTP Basic Auth and Bearer Token (JWT) authentication. + """ + # Try JWT Bearer token first + if bearer_creds is not None and self._jwt_handler is not None: + username = self._jwt_handler.verify_token(bearer_creds.credentials) + if username is not None: + # Verify the username matches the configured username + if username == self.args.username: + return username + logger.warning(f"JWT token contains unknown username: {username}") + + # Fall back to Basic Auth + if basic_creds is not None: + if basic_creds.username == self.args.username: + if self._password.check_password(self.args.password, self._password.get_hash(basic_creds.password)): + return basic_creds.username + + # If no valid authentication provided, return HTTP 401 + www_authenticate = "Bearer, Basic" if self._jwt_handler and self._jwt_handler.is_available else "Basic" raise HTTPException( - status.HTTP_401_UNAUTHORIZED, "Incorrect username or password", {"WWW-Authenticate": "Basic"} + status.HTTP_401_UNAUTHORIZED, + "Incorrect authentication", + {"WWW-Authenticate": www_authenticate}, ) def _logo(self): @@ -228,13 +271,24 @@ class GlancesRestfulApi: \_____|_|\__,_|_| |_|\___\___||___/ {__version__} """ + def _token_router(self) -> APIRouter: + """Define a router for the token endpoint (no authentication required).""" + base_path = f'/api/{self.API_VERSION}' + router = APIRouter(prefix=self.url_prefix) + # Override global dependencies with empty list to disable authentication for this route + router.add_api_route(f'{base_path}/token', self._api_token, methods=['POST'], dependencies=[]) + return router + def _router(self) -> APIRouter: """Define a custom router for Glances path.""" base_path = f'/api/{self.API_VERSION}' plugin_path = f"{base_path}/{{plugin}}" - # Create the main router - router = APIRouter(prefix=self.url_prefix) + # Create the main router with authentication if password is set + if self.args.password: + router = APIRouter(prefix=self.url_prefix, dependencies=[Depends(self.authentication)]) + else: + router = APIRouter(prefix=self.url_prefix) # REST API route definition # ========================== @@ -426,6 +480,80 @@ class GlancesRestfulApi: glances_events.clean(critical=True) return GlancesJSONResponse({}) + async def _api_token(self, request: Request): + """Glances API RESTful implementation. + + Generate a JWT access token for authenticated users. + + Expected JSON body: + { + "username": "string", + "password": "string" + } + + Returns: + { + "access_token": "string", + "token_type": "bearer", + "expires_in": int (seconds) + } + """ + # Check if JWT is available + if self._jwt_handler is None or not self._jwt_handler.is_available: + raise HTTPException( + status.HTTP_501_NOT_IMPLEMENTED, + "JWT authentication is not available. Install python-jose or check configuration.", + ) + + # Check if password authentication is enabled + if self._password is None: + raise HTTPException( + status.HTTP_501_NOT_IMPLEMENTED, + "Password authentication is not enabled. Start Glances with --password option.", + ) + + # Parse request body + try: + body = await request.json() + except Exception: + raise HTTPException(status.HTTP_400_BAD_REQUEST, "Invalid JSON body") + + username = body.get('username') + password = body.get('password') + + if not username or not password: + raise HTTPException( + status.HTTP_400_BAD_REQUEST, + "Missing username or password in request body", + ) + + # Validate credentials + if username != self.args.username: + raise HTTPException( + status.HTTP_401_UNAUTHORIZED, + "Incorrect authentication", + {"WWW-Authenticate": "Bearer"}, + ) + + # Check password + if not self._password.check_password(self.args.password, self._password.get_hash(password)): + raise HTTPException( + status.HTTP_401_UNAUTHORIZED, + "Incorrect authentication", + {"WWW-Authenticate": "Bearer"}, + ) + + # Generate token + access_token = self._jwt_handler.create_access_token(username) + + return GlancesJSONResponse( + { + "access_token": access_token, + "token_type": "bearer", + "expires_in": self._jwt_handler.expire_minutes * 60, + } + ) + def _api_help(self): """Glances API RESTful implementation. diff --git a/glances/outputs/glances_stdout_api_restful_doc.py b/glances/outputs/glances_stdout_api_restful_doc.py index 941ec8c9..0b396aa6 100644 --- a/glances/outputs/glances_stdout_api_restful_doc.py +++ b/glances/outputs/glances_stdout_api_restful_doc.py @@ -60,6 +60,7 @@ Note: The url_prefix should always end with a slash (``/``). For example: .. code-block:: ini + [outputs] url_prefix = /glances/ @@ -72,6 +73,97 @@ API documentation URL The API documentation is embeded in the server and available at the following URL: ``http://localhost:61208/docs#/``. +Authentication +-------------- + +Glances API supports both HTTP Basic authentication and JWT (JSON Web Token) Bearer authentication. + +To enable authentication, start Glances with the ``--password`` option. + +To generate a new login/password pair, use the following command: + +.. code-block:: bash + + glances -w --username + > Enter new username: foo + > Enter new password: ******** + > Confirm new password: ******** + > User 'username' created/updated successfully. + +To reuse an existing login/password pair, start Glances with the ``-u `` option: + +.. code-block:: bash + + glances -w -u foo + +JWT Token Authentication +~~~~~~~~~~~~~~~~~~~~~~~~ + +JWT authentication requires the ``python-jose`` library to be installed. + +**Step 1: Get a JWT Token** + +Request a token by sending your credentials to the token endpoint: + +.. code-block:: bash + + curl -X POST http://localhost:61208/api/{__apiversion__}/token \\ + -H "Content-Type: application/json" \\ + -d '{{"username": "your_username", "password": "your_password"}}' + +This will return a response like: + +.. code-block:: json + + {{ + "access_token": "...", + "token_type": "bearer", + "expires_in": 3600 + }} + +**Step 2: Use the Token** + +Use the token in the Authorization header with Bearer authentication: + +.. code-block:: bash + + # Store the token in a variable + TOKEN="your_access_token_here" + + # Access a protected endpoint + curl -H "Authorization: Bearer $TOKEN" \\ + http://localhost:61208/api/{__apiversion__}/cpu + +**Complete Example:** + +.. code-block:: bash + + # Get token and extract access_token + TOKEN=$(curl -s -X POST http://localhost:61208/api/{__apiversion__}/token \\ + -H "Content-Type: application/json" \\ + -d '{{"username": "glances", "password": "mypassword"}}' \\ + | grep -o '"access_token":"[^"]*"' \\ + | cut -d'"' -f4) + + # Use the token to get CPU stats + curl -H "Authorization: Bearer $TOKEN" \\ + http://localhost:61208/api/{__apiversion__}/cpu + +**Configuration:** + +You can configure JWT settings in the Glances configuration file: + +.. code-block:: ini + + [outputs] + # JWT secret key (if not set, a random key will be generated) + jwt_secret_key = your-secret-key-here + # JWT token expiration in minutes (default: 60) + jwt_expire_minutes = 60 + +**Note:** The token endpoint (``/api/{__apiversion__}/token``) does not require authentication. +Protected endpoints support both Bearer token and Basic Auth authentication methods. + WebUI refresh ------------- diff --git a/glances/password.py b/glances/password.py index d24a5a42..f5be4e97 100644 --- a/glances/password.py +++ b/glances/password.py @@ -85,7 +85,7 @@ class GlancesPassword: password_hashed = self.hash_password(password_hash) if confirm: # password_confirm is the clear password (only used to compare) - password_confirm = self.get_hash(getpass.getpass('Password (confirm): ')) + password_confirm = self.get_hash(getpass.getpass('Confirm new password: ')) if not self.check_password(password_hashed, password_confirm): logger.critical("Sorry, passwords do not match. Exit.") @@ -99,7 +99,7 @@ class GlancesPassword: # Save the hashed password to the password file if not clear: - save_input = input('Do you want to save the password? [Yes/No]: ') + save_input = input(f'Do you want to save the password in {self.password_file} ? [Yes/No]: ') if save_input and save_input[0].upper() == 'Y': self.save_password(password_hashed) diff --git a/pyproject.toml b/pyproject.toml index 79e65048..91dbae84 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,6 +102,7 @@ snmp = ["pysnmp-lextudio<6.2.0"] sparklines = ["sparklines"] web = [ "fastapi>=0.82.0", + "python-jose[cryptography]>=3.3.0", "requests", "uvicorn", ]