Commit Graph
7354 Commits
Author SHA1 Message Date
nicolargo a0812e36e4 Fix broken WebUI build: declare terser-webpack-plugin explicitly
webpack.config.js requires terser-webpack-plugin, but the package was
never declared in package.json. It only resolved through npm hoisting,
because webpack itself depended on it up to 5.107.2.

webpack 5.110.3 replaced that internal dependency with
minimizer-webpack-plugin, so the phantom dependency disappeared from
node_modules and `make webui` failed before the build even started:

    Cannot find module 'terser-webpack-plugin'
    at Object.<anonymous> (glances/outputs/static/webpack.config.js:5:29)

Declare it in devDependencies so the config no longer depends on
another package's transitive dependency tree.

Also includes the JS dependency bumps from `make webui-update` and the
regenerated bundles.
2026-09-05 11:06:40 +02:00
nicolargo a28ba033a8 TypeError: string indices must be integers, not 'str' in smart plugin during _add_device_stats #3704 2026-09-05 10:46:03 +02:00
Nicolas Hennion 7835dc0888 Merge pull request #3702 from jamescowens/3465-ip-plugin-prefer-default-route-address
IP plugin: prefer the default-route address over interface order
2026-09-05 10:08:30 +02:00
James C. OwensandClaude Fable 5 ea11231b82 IP plugin: prefer the default-route address over interface order (#3465)
get_ip_address() returned the address of the first up, non-loopback
interface psutil happened to enumerate. On hosts running Docker, VMware,
libvirt or similar, a virtual bridge (docker0, vmnet8, br-*...) can occupy
that position and silently win, so the IP plugin reports a bridge address
as the primary IP. Which interface wins depends on interface creation
order, so the symptom can appear after something as routine as a
NetworkManager restart recreating a bond.

Instead, ask the OS routing table which source address it would use for
default-route traffic: connect() on a SOCK_DGRAM socket resolves the route
without sending any packet, honouring metrics, policy routing and
per-route source hints, and works on every platform Glances supports. The
probe address is from the documentation range (RFC 5737 / RFC 3849) and is
never contacted. A loopback or unspecified probe result (hosts that
locally blackhole bogon ranges) is rejected. The previous interface scan
is kept as the fallback, and the existing tests now pin exactly that
fallback behaviour.

This also improves servers_list_dynamic.py, which uses get_ip_address()
for the zeroconf bind address and could previously announce a virtual
bridge address to LAN peers.

Verified on a host with 22 interfaces (bond, 3 docker bridges, 2 vmware
vmnets, 10 veths): returns the bond address for IPv4 and the global
address for IPv6, independent of enumeration order.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 11:15:22 -04:00
nicolargo afe80db374 Merge branch 'develop' of github.com:nicolargo/glances into develop 2026-08-29 10:46:52 +02:00
nicolargo a303d067cd Generate webui with latests PT 2026-08-29 10:46:36 +02:00
Continuous Integration a9ffc03b00 Continuous Integration Build Artifacts 2026-08-29 08:40:42 +00:00
Nicolas Hennion d17b078ffc Merge pull request #3696 from ntdatt812/fix/webui-amps-alert
fix(webui): match the AMP alert to the plugin's get_alert
2026-08-29 10:35:42 +02:00
Continuous Integration c1e0703237 Continuous Integration Build Artifacts 2026-08-29 08:35:00 +00:00
Nicolas Hennion 8e2fe721ed Merge pull request #3698 from ntdatt812/fix/percpu-iowait-alert-wrong-field
fix(webui): colour the per-CPU iowait cell by iowait, not system
2026-08-29 10:30:44 +02:00
Nicolas Hennion 985970410c Merge pull request #3697 from ntdatt812/fix/webui-vms-status
fix(webui): match the VM status colour to the plugin's vm_alert
2026-08-29 10:29:26 +02:00
Continuous Integration 0733a49fbf Continuous Integration Build Artifacts 2026-08-29 08:29:12 +00:00
Nicolas Hennion 278d643fd9 Merge pull request #3695 from ntdatt812/fix/webui-ports-status
fix(webui): read the ports status as the API sends it, and use the web alert
2026-08-29 10:27:12 +02:00
Nicolas Hennion 10d5d5c451 Merge pull request #3699 from ntdatt812/fix/processlist-mem-alert-wrong-field
fix(webui): colour the process MEM% cell by memory_percent, not cpu_percent
2026-08-29 10:24:43 +02:00
Nguyen Thanh Dat 616b6580eb fix(webui): colour the per-CPU iowait cell by iowait, not system
getIOWaitAlert passed cpu.system to the iowait threshold lookup, so the
iowait column was decorated from a completely different field. The curses
UI colours each per-CPU stat with self.get_alert(cpu[stat], header=stat) —
every stat by its own value — so the web UI disagreed with it.

With the shipped defaults (iowait 50/70/90) that means a CPU pinned at 95%
iowait with low system time shows no alert at all, and a CPU at 95% system
with 2% iowait paints the iowait cell red.

Also fixes the column header, which read "steel" instead of "steal".

Bundle rebuilt: exactly 7 bytes change in public/glances.js, the six of
system -> iowait and the one of steel -> steal.
2026-08-29 15:12:01 +07:00
Nguyen Thanh Dat 15ee238097 fix(webui): match the VM status colour to the plugin's vm_alert
`plugin-vms.vue` decided the status colour inline:

    :class="vm.status == 'stopped' ? 'careful' : 'ok'"

`VmsPlugin.vm_alert()` — which the TUI calls directly at
`vms/__init__.py:271` — classifies three states the WebUI had never heard of:

    running                                   -> OK
    starting, restarting, delayed shutdown    -> WARNING
    everything else                           -> INFO

So the only status the WebUI recognised was `stopped`, and **every other
non-running state fell through to "ok"**. A VM that is starting, restarting,
or in delayed shutdown rendered green in the browser while the TUI showed
WARNING — a VM stuck restarting looked healthy. `stopped` was also
over-reported as CAREFUL where the plugin says INFO.

`getStatusClass` now mirrors `vm_alert`, and follows the shape
`plugin-containers.vue` already uses for its equivalent (`container_alert`),
including returning `info` for the fallback.

Evidence, since the WebUI has no JS test runner: the string
`delayed shutdown` goes 0 -> 1 occurrences in the rebuilt bundle. It was
absent entirely before, which is the point — that state had no representation
in the WebUI at all.

`npx eslint` on the component: 0 errors. Its 5 warnings sit at lines 9/15/19/24
(header attributes) and 69 (the props block); this change touches line 35 and
adds a methods block, so none of them are new.
2026-08-29 15:12:01 +07:00
Nguyen Thanh Dat 85615163fc fix(webui): colour the process MEM% cell by memory_percent, not cpu_percent
getMemoryPercentAlert passed process.cpu_percent to the memory threshold
lookup, so the MEM% column in the process list was decorated from CPU
usage. The curses UI colours it correctly, from p['memory_percent'] with
header="mem", so the two UIs disagreed about the same process table.

With the shipped defaults (mem 50/70/90) a process holding 94% of RAM
while idle shows no alert, and a busy loop with a 0.4% footprint paints
its MEM% cell red. Both the list and the tree render path go through this
one helper.

Bundle rebuilt. Undoing just this identifier in the rebuilt public/glances.js
makes it byte-identical to the committed one, so nothing else rode along.
2026-08-29 15:12:01 +07:00
Nguyen Thanh Dat 4ddfb11b70 fix(webui): match the AMP alert to the plugin's get_alert
`plugin-amps.vue` disagreed with `AmpsPlugin.get_alert()`, which is the
authority — the TUI calls it directly at `amps/__init__.py:114`.

Two divergences:

1. An out-of-range process count rendered "careful"; the plugin returns
   WARNING. Those are different levels with different colours in style.scss
   (`.careful` #295183, `.warning` #5D4062), so an AMP outside its configured
   bounds was under-reported in the WebUI relative to the TUI.

2. With a count of 0 the WebUI returned "critical" for any non-null
   `countmin`, including `countmin=0`. The plugin defaults an unset bound to
   the observed count and then returns OK when `int(countmin) == 0`, so a
   configured minimum of zero means zero processes is fine. The plugin
   supports that value explicitly; the WebUI flagged it red.

The range check itself already matched and is unchanged in behaviour — it is
only rewritten to default the bounds the way the plugin does, rather than
short-circuiting on null, so the two read alike.

Not mirrored: the plugin's `nbprocess is None -> OK` guard. `count` is
initialised to 0 in `amps/amp.py` and only ever assigned a real count, so that
branch is unreachable from the API and adding it would be dead code.

Evidence, since the WebUI has no JS test runner: in the rebuilt bundle the
`careful` occurrences drop 18 -> 17. `npx eslint` on the component reports
0 errors (the two warnings, `vue/require-default-prop` and `vue/no-v-html`,
are on untouched code and predate this change).
2026-08-29 15:12:00 +07:00
Nguyen Thanh Dat a8ef0b6727 fix(webui): read the ports status as the API sends it, and use the web alert
`plugin-ports.vue` disagreed with the ports plugin on two counts. Both were
verified against a live `/api/4/ports` response rather than inferred.

1. URL rows were decorated by `getPortDecoration`. `getWebDecoration` was
   defined in the same file and never called.

   The port form compares `status` against an rtt threshold, but a URL's
   status is an HTTP code or the string 'Error'. With the default
   `rtt_warning: null` every URL therefore fell through to "ok": an
   unreachable URL rendered green while the TUI rendered it CRITICAL. With a
   threshold configured it was still wrong the other way -- `200 > 1` is true,
   so a healthy URL rendered "warning", and a 404 rendered "warning" instead
   of "critical".

2. The status text compared against the strings 'null', 'false' and 'true'.
   The API sends real JSON values. Captured from a running server:

     {"description": "ClosedPort", "status": false, "rtt_warning": null, ...}
     {"description": "UnreachableWeb", "status": "Error", "elapsed": 0, ...}

   `false == 'false'` is `0 == NaN` in JavaScript, so none of the three
   branches could ever match. A closed or timed-out port fell through to
   `false * 1000` and displayed "0ms" -- which reads as an instant, perfect
   response for a port that is down. "Scanning" and "Timeout" never rendered
   at all, and the 'true' branch was doubly dead: the scanner only ever sets
   `status` to None, a float, or False.

Both now mirror the plugin, which is the authority: `set_status_if_host`,
`set_status_if_url` and `get_conds_if_url`. The URL branch falls through to
the raw status string as `set_status_if_url` does, rather than special-casing
'Error'.

Evidence, since the WebUI has no JS test runner -- counted in the rebuilt
bundle:

  getPortDecoration  3 -> 2   (definition + two call sites -> + one)
  getWebDecoration   1 -> 2   (definition only -> definition + call site)
  "null" literals    4 -> 2
  "false" literals   4 -> 3

`npx eslint` on the component: 0 errors (the one `vue/require-default-prop`
warning is on the untouched `props` block and predates this change).
2026-08-29 15:12:00 +07:00
nicolargo 076469f372 Improve SKILL to avoid glances.js in PR 2026-08-29 10:03:59 +02:00
Continuous Integration aaf9d4c147 Continuous Integration Build Artifacts 2026-08-29 07:46:21 +00:00
Nicolas Hennion 622ffa9684 Merge pull request #3686 from justadityaraj/feat/container-io-sorting
fix(WebUI): Make container IO and network columns sortable
2026-08-29 09:41:49 +02:00
Aditya Raj Singh 7305ee75d0 feat(webui): sort containers by IO and network rates 2026-08-29 13:06:42 +05:30
Nicolas Hennion e63213e57e Merge pull request #3681 from ntdatt812/fix/webui-nice-null-question-mark
fix(webui): show ? for a process whose nice value the OS will not report
2026-08-29 09:20:01 +02:00
Nicolas Hennion 7df05d8c8d Merge pull request #3701 from ntdatt812/fix/ports-scan-socket-timeout-scope
fix(ports): scope the TCP scan timeout to the scanning socket
2026-08-29 09:12:14 +02:00
Nicolas Hennion d395e3874e Merge pull request #3700 from ntdatt812/fix/strip-config-list-values
fix(config): strip whitespace around comma-separated config values
2026-08-29 09:10:27 +02:00
nicolargo 00b63fd155 Remove stars graph from README file 2026-08-29 09:08:09 +02:00
Nguyen Thanh Dat 6b9982efec fix(ports): scope the TCP scan timeout to the scanning socket
socket.setdefaulttimeout() is process-wide and was never restored, so
once a port had been scanned every socket created afterwards anywhere in
Glances that does not set its own timeout inherited the last scanned
port's timeout -- the hddtemp grabber among them.

_socket.settimeout() sets the same value on the socket the scan actually
uses and leaves the rest of the process alone. Scan results are
unchanged.

Also return when the socket cannot be created: the code fell through
with _socket unbound, so connect_ex raised UnboundLocalError into the
'Error while scanning port' handler and the finally clause raised it
again with nothing left to catch it.
2026-08-29 07:49:32 +07:00
Nguyen Thanh Dat aa4674d9cd fix(config): strip whitespace around comma-separated config values
load_limits split a config list on ',' and kept the spaces, so 'list=cpu, mem,
load' became ['cpu', ' mem', ' load']. glances.conf writes lists that way in its
own comments, so following the shipped documentation produced a config that did
not work.

The loudest symptom is show/hide: every item is used as a re.fullmatch pattern,
and a leading space makes the pattern match nothing. 'hide=sda2, loop.*' hid
sda2 and silently kept showing every loop device.

Quicklook also answered a bad list by falling back to AVAILABLE_STATS_LIST -
more than the user asked for, and its two GPU entries flip the gpu_stats polling
flags, so a typo started polling the GPU. It now falls back to the documented
DEFAULT_STATS_LIST and names the offending entries in the warning.
2026-08-28 12:18:08 +07:00
nicolargo a240d8dfb3 Change token for Star History Chart 2026-08-27 19:06:28 +02:00
Nicolas Hennion 03949b6220 Merge pull request #3694 from ntdatt812/fix/network-zero-rate-alert
fix(network): a rate of 0 in one direction must not mute the other
2026-08-27 18:18:13 +02:00
Nicolas Hennion 079a1a1133 Merge pull request #3693 from ntdat812/fix/sort-tolerates-missing-cpu-times
fix(sort): read a missing sort value as zero instead of discarding the sort
2026-08-27 18:16:40 +02:00
Nguyen Thanh Dat 106002570f fix(webui): show ? for a process whose nice value the OS will not report
`exclamation` mapped `undefined` and `""` to `?` but not `null`, and the
API sends JSON: a process whose nice value psutil cannot read arrives at
the WebUI as `null`. Vue renders `null` as an empty string, so those rows
showed a blank NI cell while the TUI showed `?` for the same process.

On this Windows box that is 144 of 513 processes.

Explicit comparisons rather than a falsy test: `0` is a real nice value on
every POSIX system and has to keep rendering as `0`.

The filter has exactly two call sites, both the NI column, so nothing else
changes shape.
2026-08-27 19:47:43 +07:00
Nguyen Thanh Dat 8b89bf68a4 fix(network): a rate of 0 in one direction must not mute the other
`update_views` skipped the whole interface when either direction measured
0 bytes/s:

    if not i.get('bytes_recv_rate_per_sec') or not i.get('bytes_sent_rate_per_sec'):
        continue

The guard is `or`, so one idle direction suppressed the alert for *both*. A
send-only interface saturating its uplink went undecorated because its rx rate
happened to be exactly 0, and a receive-only one — a monitor/SPAN port — the
same way. 0 bytes/s is a real measurement, not a missing one.

The rates now default to 0 instead of skipping, which is what the diskio plugin
already does: it dropped this same guard in #3684 and reads the rate through
`or 0` for the same reason.

The WebUI needs no change — `plugin-network.vue` renders `getDecoration(...)`
straight from the server-supplied view, so it inherits this.

Tests: four cases in `TestNetworkPluginZeroRateAlerts`. Three fail before this
change (saturated tx with idle rx, saturated rx with idle tx, and a fully idle
interface reading DEFAULT instead of OK); the fourth — both directions busy —
passes before and after, which is the path that already worked.
`tests/test_plugin_network.py` 39 passed.
2026-08-27 10:31:01 +07:00
Nguyen Thanh Dat 7928ee55ea fix(sort): read a missing sort value as zero instead of discarding the sort
sort_stats wraps the specific sort helpers in try/except and falls back to
cpu_percent for the whole list. _sort_cpu_times indexed cpu_times['user'] and
_sort_io_counters indexed io_counters[0..3], so one row with an empty value
raised and silently reordered every other row while the header still read TIME
or IOR/IOW.

Those empty values are expected rather than corrupt: programs.py builds a
program with p['cpu_times'] or {} and list(p['io_counters'] or NO_IO_COUNTERS),
and its own comment says some values can be None on macOS system processes.

Each helper now reads a missing value as zero, so the degenerate row sorts last
and the rest keep their order.
2026-08-26 23:38:44 +07:00
Nicolas Hennion 6808b87fcf Merge pull request #3692 from ntdat812/fix/program-io-counters-sum
fix(programs): sum a program's io_counters instead of concatenating them
2026-08-26 16:06:06 +02:00
Nicolas Hennion 8d6a84524c Merge pull request #3691 from ntdatt812/fix/wifi-partial-thresholds
fix(wifi): honour a partially configured set of signal thresholds
2026-08-26 16:04:10 +02:00
Nicolas Hennion c71504b7e9 Merge pull request #3690 from ntdatt812/fix/webui-raid-alert-parity
fix(webui): match the RAID alert to raid_alert instead of a bare comparison
2026-08-26 16:01:45 +02:00
Nicolas Hennion 629c9f8362 Merge pull request #3688 from ntdatt812/fix/sensors-zero-value-alert
fix(sensors): alert on a reading of 0 instead of skipping it
2026-08-26 15:58:43 +02:00
Nicolas Hennion 89d7b61446 Merge pull request #3687 from ntdatt812/fix/percpu-summarize-not-displayed
fix(percpu): summarize the cores that are not displayed, not the ones that are
2026-08-26 15:56:17 +02:00
Nguyen Thanh Dat b362582562 style: move the long explanations out of docstrings and into comments
Codacy's Prospector/pydocstyle profile flagged 23 issues on the new file: the
multi-line docstrings all put a description straight under the summary line
(D205, D209, D213, D400, D415). Keeping the summary to one line and putting the
reasoning in comments below says the same thing and leaves the file clean.

No test logic changed: still 7 red on origin/develop, 9 green with the fix.
2026-08-26 19:15:40 +07:00
Nguyen Thanh Dat 042b9384f6 fix(programs): sum a program's io_counters instead of concatenating them
io_counters is a fixed five-slot list -
[read_bytes, write_bytes, read_bytes_old, write_bytes_old, io_tag] - and
update_program_dict merged it with '+=', which concatenates lists rather than
adding them. Three things followed:

- Readers index the list, so a program showed only its first process's disk
  IO. Two processes reading 100 and 1000 bytes displayed 90 B/s where the
  sum is 990 B/s.
- '+=' extends in place, and create_program_dict stored the process's own
  list, so aggregating wrote back into the process list.
- Re-aggregating the same processes - which a re-sort does - appended again
  every time, growing the list without bound.

Sum the four byte slots and OR the io_tag: processlist only displays a rate
when the tag is exactly 1, so adding it would blank the columns instead.
2026-08-26 18:56:00 +07:00
Nguyen Thanh Dat ea62d0abe6 fix(wifi): honour a partially configured set of signal thresholds
WifiPlugin.get_alert chained its three comparisons, so the first undefined level
was compared against None, raised TypeError, and the blanket handler dropped the
result to DEFAULT. Because 'critical' is tested first, leaving it out silenced
the other two: a config defining only wifi_careful produced no alert at all, and
even a strong signal came back undecorated instead of OK.

GlancesPluginModel.get_alert already guards each level separately, and the
sensors plugin has tests pinning that partial thresholds work. This brings wifi
in line. `is not None` rather than truthiness because these limits are negative
dBm values, where `if threshold` would discard a 0.

The TypeError handler is kept for a non-numeric signal level (issue #1373) but
narrowed to that case, which is now the only way it can fire.
2026-08-26 16:03:26 +07:00
Nguyen Thanh Dat 08e1ebc451 fix(webui): match the RAID alert to raid_alert instead of a bare comparison
The WebUI decided degradation with `diskData.used < diskData.available` and
nothing else, while the authoritative RaidPlugin.raid_alert applies three rules
before that comparison: raid0 is always OK, an inactive array is CRITICAL, and
an unknown device count is left undecorated.

Two of those omissions are visible:

- A raid0 array reporting fewer used than available devices was drawn as
  "Degraded mode" in warning colour. raid0 has no redundancy, so that comparison
  carries no meaning there — msg_curse excludes raid0 from the same banner.
- `used` is None whenever the parser cannot read the device count, and in
  JavaScript `null < 5` is true, so an unreadable array was reported as degraded.
  The TUI leaves it undecorated.

Comparing both implementations across seven arrays, four disagreed before and
none after.

One asymmetry is preserved rather than fixed: raid_alert returns OK for an
inactive raid0 because the raid0 branch runs first. This change matches that so
the two views agree; whether the Python ordering itself is right is a separate
question, and the inactive line is rendered regardless.
2026-08-26 16:01:40 +07:00
Nguyen Thanh Dat b196a809ea fix(sensors): alert on a reading of 0 instead of skipping it
`update_views` guarded the alert loop with `if not i['value']: continue`, which
skips a genuine reading of 0 along with the absent ones.

The battery alert is computed on `100 - value`, so an empty battery is the most
critical reading there is — yet a battery at 0% was left undecorated while one at
3%, a strictly better state, was flagged CRITICAL. A fan reporting 0 RPM was
likewise never evaluated.

Tests for a usable number instead. Sensors reporting a placeholder — no battery
is an empty list, hddtemp uses b'ERR'/b'SLP'/b'UNK'/b'NOS' — keep the DEFAULT
decoration the parent update_views() already assigns, so nothing that was skipped
before starts being decorated now. It also keeps non-numbers away from the
`100 - value` subtraction, which raises TypeError on them today.
2026-08-26 14:42:35 +07:00
Nguyen Thanh Dat 294fefe3db fix(percpu): summarize the cores that are not displayed, not the ones that are
`summarize_all_cpus_not_displayed` sliced `percpu_list[0 : max_cpu_display]` —
the exact slice `msg_curse` has just printed one line above. So the CPU* row
averaged the cores already on screen instead of the ones that did not fit.

`manage_max_cpu_to_display` sorts by total descending whenever the list
overflows, so the displayed slice is the busiest cores. On an 8-core box showing
4, with four cores at ~87% and four idle at 2%, the CPU* row read 87.0% — a
figure for cores the user can already see, and 43x the load of the group it
claims to represent. The busier the top cores, the more misleading the summary.

Slices the tail instead, so the displayed slice and the summarized slice
partition the list.
2026-08-26 14:39:33 +07:00
Nicolas Hennion c15ab4c73b Merge pull request #3685 from justadityaraj/fix/percpu-guest-nice
fix(percpu): report the guest nice CPU value
2026-08-26 09:38:31 +02:00
Nicolas Hennion 32344ad6e2 Merge pull request #3684 from ntdatt812/fix/diskio-alert-on-rate
fix(diskio): alert on the bitrate, and decorate the field the WebUI reads
2026-08-26 09:36:46 +02:00
Nicolas Hennion 5a38e1550d Merge pull request #3683 from ntdatt812/fix/connections-conntrack-alert
fix(connections): alert on the tracked percentage, not on 0
2026-08-26 09:34:32 +02:00
Nicolas Hennion 5b41982377 Merge pull request #3678 from ntdatt812/fix/containers-title-duplicate-fragment
fix(containers): stop repeating a title fragment when several engines run
2026-08-26 09:30:24 +02:00