236 Commits
Author SHA1 Message Date
nicolargo f05c60d29c Merge branch 'develop' of github.com:nicolargo/glances into develop 2026-09-12 15:30:22 +02:00
nicolargo f3f50323d8 SQL Injection leading to Data Exfiltration via OOB (Out-of-Band) in Glances ClickHouse Export Module - GHSA-2hvx-g9v6-w29h 2026-09-12 15:30:08 +02:00
Nicolas Hennion dfa6137a39 Merge pull request #3732 from datrixlab/fix/connections-disable-sticks
Remember that a connections probe was disabled
2026-09-12 15:14:56 +02:00
datrixlab 4591a6f52d Remember that a connections probe was disabled
The connections plugin gives up on a probe that fails: psutil.net_connections
raising, or the nf_conntrack files not being there. It records that by
writing net_connections_enabled / nf_conntrack_enabled into stats, but
update() starts every refresh with

    stats = self.get_init_value()

which is a copy of stats_init_value, where both flags are True. The flag is
therefore forgotten as soon as it is written, and update() calls the failing
probe again on the next refresh. Measured with the conntrack path pointing at
a missing file and three refreshes: three reads and three
"Can not get network connections track" warnings, instead of one.

That also defeats the guard around psutil.net_connections, which is the
expensive call the plugin's own comment is about ("because it consumes lots
of CPU"), on a host where it raises, such as macOS without root.

Keep the two flags on the plugin, the way mem keeps zfs_enabled and sensors
keeps its probe state, and copy them into stats each refresh so the views and
the REST API still see them.
2026-09-12 11:36:06 +07:00
datrixlab ac741d299c Read web_x_ssl_verify as a boolean, not as a CA bundle path
GlancesWebList takes web_x_ssl_verify through config.get_value(), which
returns the raw string, and hands it to requests.head(verify=...).
Requests reads a string verify as the path to a CA bundle, so the value
from the configuration file is looked up as a file name:

    verify='false'            -> OSError: Could not find a suitable TLS CA
    verify='true'             -> OSError: ... invalid path: true
    verify=False / verify=True -> the request is actually made

ThreadScanner._web_scan catches everything and sets status='Error', so the
URL sits permanently red in the curses view and the WebUI, with only a
debug-level log line to explain it. Setting the key to true, which is what
a user does to turn verification back on explicitly, breaks the scan the
same way as false.

Read it with config.get_bool_value(), the helper the network and diskio
plugins already use for their switches, and keep a non-boolean value as a
string: a path to a CA bundle is a valid value for requests' verify, and
that is the one form that worked before.

The key was undocumented, which is probably how this survived; document it
next to the other web_x_ options in conf/glances.conf and docs/aoa/ports.rst.
2026-09-12 11:31:29 +07:00
Nicolas Hennion 1756895fe9 Merge pull request #3730 from datrixlab/fix/outputs-cli-overrides-config
Let --disable-separator and --disable-bg override glances.conf
2026-09-11 19:32:50 +02:00
datrixlab d2836579c5 Let --disable-separator and --disable-bg override glances.conf
_GlancesCurses.load_config read [outputs] separator and disable_bg with the
command-line value passed in as the default, so any value present in the
file won: with separator=True in glances.conf, `glances --disable-separator`
still drew the separator, and so did --disable-unicode, undoing main.py's
"Unicode => No separator". With disable_bg=False in the file, --disable-bg
was ignored. docs/config.rst says options given on the command line
override the configuration files.

Both flags only move one way -- --disable-separator can only turn the
separator off and --disable-bg can only turn the background off -- so keep
the value when the flag is set and let the file decide otherwise. With no
flag, or with the key absent, the result is unchanged.
2026-09-11 12:14:21 +07:00
datrixlab 2aa9c9e1bf Read a False config switch as False in get_conf_value
get_conf_value(convert_bool=True) returned bool(ret[0]). load_limits stores
a non-numeric value as a one-item list of strings, so the documented
off switch

    [processlist]
    disable_virtual_memory=False

came back as bool('False') == True and hid the VIRT column in the curses
UI -- while the WebUI, reading the same key, compares the text to "true"
and shows it. A value that parses as a number is stored as a float
instead, so disable_virtual_memory=0 or =1 raised TypeError on ret[0].

Read the text the way the *_log check a few lines above and the WebUI
already do, and take bool() of a float.
2026-09-11 12:09:36 +07:00
Nicolas Hennion ddaa65c459 Merge pull request #3728 from datrixlab/fix/focus-filter-command-line-precedence
Replace the focus filter list on set instead of extending it
2026-09-10 19:14:36 +02:00
Nicolas Hennion 32b684c1a3 Merge pull request #3727 from ntdatt812/fix/webui-hide-zero-row-polarity
fix(webui): keep a row while any of its hide_zero fields is visible
2026-09-10 19:13:41 +02:00
Nicolas Hennion 7a6f5ba69b Merge pull request #3726 from ntdatt812/fix/network-hide-threshold-bytes
fix(network): read the hide_threshold_bytes the plugin documents
2026-09-10 19:12:11 +02:00
Nicolas Hennion a9bb613c17 Merge pull request #3725 from ntdatt812/fix/hide-zero-threshold-off-by-one
fix(views): keep hide_zero hiding a stat that never moves
2026-09-10 19:11:12 +02:00
Nicolas Hennion ffa90046c5 Merge pull request #3724 from ntdatt812/fix/containers-webui-decoration
fix(webui): colour a container's CPU and MEM the way curses already does
2026-09-10 19:09:57 +02:00
Nicolas Hennion 4c58ba1d5b Merge pull request #3723 from PGrayCS/fix/timeout-fallback-result
Fix return value in timeout permission fallback
2026-09-10 19:09:04 +02:00
Nicolas Hennion ba2c8b7fb9 Merge pull request #3722 from ntdatt812/fix/vms-decorations
fix(vms): colour the columns msg_curse already asks decorations for
2026-09-10 19:07:44 +02:00
Nicolas Hennion 2db5e73701 Merge pull request #3721 from ntdatt812/fix/rate-new-stat-in-list
fix(rate): initialise a stat that is new since the previous sample
2026-09-10 19:05:54 +02:00
Nicolas Hennion b903fa364f Merge pull request #3718 from ntdatt812/fix/npu-temperature-alert
fix(npu): alert on the temperature both interfaces already ask for
2026-09-10 19:05:05 +02:00
datrixlab 1691303ab9 Replace the focus filter list on set instead of extending it
GlancesFilterList.filter appended to the existing list, so the property
never described the current filter: every assignment added to whatever was
already there. Its sibling in the same file, GlancesFilter.filter, replaces.

Both process_focus and export_process_filter are written twice in a normal
startup. The processlist plugin reads glances.conf when it loads
(plugins/processlist/__init__.py), and the command line is applied just
after -- set_args() for the focus filter, standalone.py for the export one.
Appending left both live, and is_filtered() ORs them, so

    focus=.*firefox.*         in glances.conf
    glances --process-focus .*python.*

focused on firefox and python together. The command line could widen the
list from the configuration file but never narrow it, while config.rst
states that "options given on the command line overrides both".

Assigning the whole list restores that precedence: the configuration file
is read first, the command line replaces it.
2026-09-10 09:06:36 +07:00
Nguyen Thanh Dat ff80c90355 fix(webui): keep a row while any of its hide_zero fields is visible
hide_zero is decided per field but applied per row, and the two front ends
read that per-field verdict in opposite directions.

msg_curse drops a disk only when *all* of its hide_zero_fields are hidden:

    if all(self.get_views(item=..., key=f, option='hidden') for f in self.hide_zero_fields):
        continue

plugin-diskio.vue and plugin-network.vue keep a row only when *all* of them
are visible:

    return (
        (!readBytesRate || readBytesRate.hidden === false) &&
        (!writeBytesRate || writeBytesRate.hidden === false)
    );

Those agree while both fields say the same thing, and disagree the moment
one moves and the other does not -- a disk that has only ever been read, an
interface that has only ever received. Curses shows it; the browser drops
it entirely.

Use the same rule as msg_curse in both components.
2026-09-09 14:52:11 +07:00
Nguyen Thanh Dat d88f9d9820 fix(network): read the hide_threshold_bytes the plugin documents
Both the reference configuration and docs/aoa/network.rst offer the option
under [network]:

    [network]
    hide_zero=True
    hide_threshold_bytes=0

but NetworkPlugin never read it, so it stayed at the base class default of
0 whatever the user wrote. diskio has read it since the option was added;
network was left out.

Nothing reports an option a plugin ignores: hide_zero keeps working, it
just never applies the threshold, so an interface carrying a trickle of
background traffic is shown when the user asked for it to be hidden.

Read it beside hide_zero, exactly as diskio does.
2026-09-09 14:48:17 +07:00
Nguyen Thanh Dat cc5e2bab65 fix(views): keep hide_zero hiding a stat that never moves
The reference configuration documents the boundary for both plugins that
use the feature:

    # Set hide_threshold_bytes to an integer value to automatically hide
    # interface with traffic less or equal than this value
    #hide_threshold_bytes=0

"less or equal than this value", with a documented default of 0, means a
rate of exactly 0 is hidden. The comparison un-hid on `>=` instead, so at
the default threshold every zero rate satisfied `0 >= 0` and the row came
straight back.

The first refresh still hides, because it has no previous view to carry
forward and falls into the `else` branch. From the second refresh on,
nothing was ever hidden again -- which is why `hide_zero=True` looked like
it worked for one frame and then stopped.

This restores the behaviour that `i[field] != 0` had before the threshold
option was introduced, and honours a non-zero threshold the way the
configuration file describes it.

Tests pin the boundary rather than any rendered message: the views dict
keeps every key either way, only `hidden` moves.
2026-09-09 14:46:17 +07:00
Nguyen Thanh Dat 6017667a56 fix(webui): colour a container's CPU and MEM the way curses already does
The containers plugin decorates each container's cpu and mem against that
container's own threshold from the config file, falling back to the global
one. The curses view reads both. plugin-containers.vue did not: it rendered
the two cells as plain text in both its narrow and wide tables, so a
container over its limit was red in the terminal and black in the browser.

The component already exposes the views it needs -- it uses them for
show_engine_name and show_pod_name -- so this only adds the reader and the
two class bindings, defensive the same way plugin-diskio.vue is for a
container present in stats but not yet in views.

The added test pins the server half of the pair: without it, removing the
decoration upstream would take the colour out of both front ends silently,
since get_views() answers DEFAULT for a key it cannot find.
2026-09-09 14:25:51 +07:00
PGray 8870aa1344 Use unittest assertion for fallback regression test 2026-09-08 21:35:24 +01:00
PGray 60b666772f Fix return value in timeout permission fallback 2026-09-08 21:29:30 +01:00
Nguyen Thanh Dat ef11e7d48a style(vms): wrap two test lines over the 120-column ruff limit 2026-09-08 14:36:17 +07:00
Nguyen Thanh Dat f8657a0a97 fix(vms): colour the columns msg_curse already asks decorations for
The VM table asks for a decoration on four columns:

    self.get_views(item=vm['name'], key='cpu_count', option='decoration')
    self.get_views(item=vm['name'], key='cpu_time_rate_per_sec', ...)
    self.get_views(item=vm['name'], key='memory_usage', ...)
    self.get_views(item=vm['name'], key='load_1min', ...)

Nothing ever set one. No vms field declares `alert` or `log`, so the base
`_build_field_decoration` returns 'DEFAULT' for all of them, and
`update_views` only computed `show_engine_name`. Every one of those four
reads has always come back 'DEFAULT', which makes the VM table the one
table that never changes colour -- the sibling containers plugin has done
this since it was written.

Three of the four get thresholds, with the per-VM override and DEFAULT
fallback containers already uses. `cpu_count` keeps its read and no
colour: a core count is not a threshold.

Decorations are written onto the views the base already built rather than
replacing them, so `optional` and `hidden` survive -- containers replaces
the whole per-item view, which is why its curses code reads nested 'cpu'
and 'mem' keys instead of field names.

Thresholds are commented out in the shipped conf, so the table stays
exactly as it is until someone opts in.

Verified with tests/test_vms_decorations.py (9 passed) and by reverting
each claim: measuring memory against a fixed maximum instead of the VM's
own memory_total fails 4 tests, dropping the per-VM override fails 1.

A `value is None` guard was written first and removed: `get_alert` already
answers DEFAULT for a missing value, because the percentage it computes
raises TypeError and is caught, so no test could tell the guard from its
absence. A test pins that behaviour instead.
2026-09-08 09:21:09 +07:00
Nguyen Thanh Dat 9029fd66d0 fix(rate): initialise a stat that is new since the previous sample
compute_rate_on_list() looked up each stat's previous sample by key and
skipped the stat entirely when there was none. Interfaces, disks and VMs
appear while Glances is running -- a VPN comes up, a container starts, a
disk is plugged in -- so a stat with no previous sample is not only a
start-up case, and the plugins that carry rate fields on a list are
network, diskio and vms.

A skipped stat kept the raw psutil counter in the delta field and got no
_gauge, no _rate_per_sec and no time_since_update at all. Two consequences:

  * That refresh published the whole since-boot total as if it were one
    refresh of traffic. On a quiet link the first sample of a new
    interface reads in gigabytes.

  * NetworkPlugin.update_views() assigns
    views/<iface>/bytes_recv_rate_per_sec directly. Views are built only
    from the fields a stat actually carries, so there was no view to
    decorate and the refresh raised KeyError: 'bytes_recv_rate_per_sec'.

Give compute_rate an empty previous instead of skipping, which is the
path a dict stat already takes on its own first sample: record the gauge,
publish a rate of 0, and measure a real delta on the next sample. The
stat then also reaches its second sample one refresh sooner, because the
gauge it needs is now recorded the moment it appears.

Verified on Windows with the full suite: the set of failing test names is
byte-identical before and after (63 pre-existing environment failures,
xmlrpc and browser-restful servers). Reverting the one changed line turns
4 of the 5 new tests red, including the KeyError one; the fifth is the
guard that the neighbouring stat is undisturbed, so it stays green.
2026-09-07 15:13:36 +07:00
Nguyen Thanh Dat 20555568ff fix(npu): alert on the temperature both interfaces already ask for
update_views() replaced the base class's per-field views with a dict naming
only load, freq and mem, so views[npu_id]['temperature'] did not exist. Both
readers then fell back to no style: msg_curse's get_views() returns 'DEFAULT'
for a missing key, and the WebUI's getDecoration returns undefined. An Intel
NPU -- the one driver that reports the reading -- was printed plain at any
temperature.

Adds the key and the alert, mirroring the GPU plugin, which has had the same
three alerts (proc/mem/temperature) all along. A card whose driver leaves the
reading None (AMD, Rockchip) keeps a view with no decoration rather than being
alerted on nothing.

conf/glances.conf gains the matching [npu] temperature thresholds, using the
same 60/70/80 ladder as [gpu]: without limits get_alert() returns DEFAULT, so
the code half alone would change nothing.
2026-09-07 10:26:25 +07:00
Nguyen Thanh Dat 26a9fe96dd fix(quicklook): colour each --percpu bar by that core's own load
Both interfaces drew every per-core bar with the aggregate CPU style:
`_msg_create_line` read `views['cpu']['decoration']` for every row, and
plugin-quicklook.vue called `getDecoration('cpu')` inside the percpu loop.
A core pegged at 100% among idle ones was therefore painted with the colour
of the average, which is the one number that cannot show it.

update_views now publishes `views['percpu_decoration']`: a style per core,
plus 'other' for the row that averages the cores neither interface shows.
Both interfaces read it, so they agree.

The styles are computed from the plugin's own quicklook_cpu_* limits rather
than through get_alert(), because get_alert() also records a threshold that
glances.events_list reads and can run a configured action, and both are keyed
by the stat name alone. Every core shares 'quicklook_cpu' with the aggregate,
so a per-core call would leave the last core as the value those two see.

Also drops a duplicated `msg = msg =` assignment on a line already touched.
2026-09-07 10:07:07 +07:00
nicolargo 84da2bfa0b Merge branch 'GHSA-56xw-p9qm-r437' into develop 2026-09-05 15:17:22 +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
nicolargo dfa991ffcb Argument Injection in On-Alert Action Commands via Unescaped Quote Characters in secure.py's Command Tokenizer - GHSA-56xw-p9qm-r437 2026-09-05 10:02:01 +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
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
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
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
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 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 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