PR 1209 review

feat: add resource health dashboard, by Yu-Jack. Reviewed at dc84fe7 on 2026-11-04, round 1. Nothing is posted on GitHub.

Verdict: request changes

Test it yourself

The preview is built from dc84fe7 with VUE_APP_SERVER_VERSION=v1.10.0, against .118 (admin password in .env). The real controller and my fixtures stay installed until you say to remove them: 7 failing zz1209-* images and a running VM zz1209-nodeselector. No ignore rules are left, so you start clean.

  1. Open the Dashboard link above and scroll to Component Health at the bottom.
  2. Ignore 3 of the 7 images on ImageRetryLimitExceeded, tick Show ignored, then Restore just one of them (finding 1).
  3. Open Ignore on that row again and look at what it preselects (finding 3).
  4. Make the window narrower than 1400px (finding 5).
Walkthrough
Real controller data. Summary, messages, resource lists and the filter.
Findings
1

One Restore click on a resource restores every resource in that rule

pkg/harvester/components/health/ComponentHealthSection.vue:223, :377
Should fix

Each ignored resource has its own Restore link, but the link deletes the whole HealthIgnoreRule. The default way to ignore puts all selected resources in one rule, so this case is the normal one. I clicked Restore on db-primary; db-replica and sles-16-minimal came back too (DELETE on the rule, then 204). The HEP does say Restore deletes the rule, but it describes the action on "an ignored check or resource", and nothing in the UI warns about this.

Suggested comment

Restore on a single resource deletes the whole rule, so if the rule lists three resources all three come back. I would expect only the one I clicked. Could it update the rule to drop that resource, and delete the rule only when it was the last one?
2

The row Restore only undoes one rule, but says the check is restored

pkg/harvester/components/health/ComponentHealthSection.vue:321, :223, :235
Should fix

The opposite of finding 1. When several rules together cover every resource, the controller marks the check as ignored and ignoredBy names only the first rule. The row Restore deletes that one. With 3 rules over 7 images, the check came back with 4 images still ignored, the growl said "The check is restored", and the row went back to an Ignore button.

Suggested comment

When several rules cover a check, the row Restore only deletes the rule in ignoredBy. With three rules over 7 images, 4 stay ignored after "The check is restored". It should delete every rule that applies to the check.
3

The Ignore popup keeps its old state after a successful save

pkg/harvester/components/health/IgnoreCheckDropdown.vue:35-37, :97
Should fix

selected, duration and reason are never reset. After ignoring 1 of 7 and reopening on the same row: Resources is empty (the HEP says the default is all selected), 30 days is still set, the old reason is still filled in, and Ignore is disabled. It also goes wrong silently: after a Restore, the popup opened with my previous 3 resources preselected rather than all of them, and I created a rule I didn't mean to during testing.

Reopened on the same row: empty Resources, old reason, Ignore disabled
Reopened on the same row: empty Resources, old reason, Ignore disabled

Suggested comment

The popup keeps its state after saving. Reopening it on the same row gives an empty Resources field, the previous reason and duration, and a disabled Ignore button. Could it reset on success so it always opens on the defaults?
4

Ignore and Reason are hidden below the popup fold from 2 resources up

pkg/harvester/components/health/IgnoreCheckDropdown.vue:171
Should fix

max-height: 420px applies even when the screen has room. The content is 405px with one resource, plus 23px per extra chip, so 2 resources already clip it. At 7 it is 543px, and the submit button and Reason are out of sight with no visible hint to scroll. Errors land there too: a failed save put the banner partly below the fold, so it can look like nothing happened. The window was 1000px tall in these shots.

7 resources: no Reason field and no Ignore button in view
7 resources: no Reason field and no Ignore button in view
Failed save (stubbed 403): the error is cut off by the fold
Failed save (stubbed 403): the error is cut off by the fold

Suggested comment

From 2 resources the Ignore button and Reason are below the fold of the popup, with nothing showing that it scrolls, and a save error lands there as well. Could the 420px limit go, or apply only to the resources list so the button stays visible?
5

The table overflows below about 1405px, and at 1280 the Ignore button is off-screen

pkg/harvester/components/health/ComponentHealthSection.vue:253
Should fix

VMNodeSelectorLiveMigrationUnsupported cannot wrap, so Check takes 281px. Add the side tabs and the table needs more room than the section has. Measured: fine at 1405, 15px of page scroll at 1366, and at 1280 the Ignore button sits at x=1296–1351, entirely off-screen, so the whole page has to scroll sideways. Expanding a message overflows even at 1440 (table right edge 1447, section 1416). Finding 14 would solve this too.

1280px wide: Last Updated cut off, Actions gone
1280px wide: Last Updated cut off, Actions gone

Suggested comment

On a 1280px screen the table is wider than the section, so Actions is off-screen and the whole page scrolls sideways. The long check names are the main cause, because they can't wrap. Could they break, or the side tabs go away?
6

Ignore and Restore stay enabled without permission (Copilot's two notes, still open)

pkg/harvester/components/health/IgnoreCheckDropdown.vue:156, pkg/harvester/components/health/ComponentHealthSection.vue:217
Should fix

Copilot raised this on 10-28 as "previously missed", and the diff still has no canCreate or canDelete check at dc84fe7. Tested with stubbed 403s only, not a real read-only user: Ignore shows the half-hidden banner from finding 4, and Restore shows a red growl. So nothing breaks, but the user finds out only after filling in the form.

Suggested comment

Copilot's note about permissions still applies: a user who can't create or delete rules still gets active Ignore and Restore buttons, and only finds out when the request fails. Could they check the rule schema and the rule's canDelete?
7

Show More can make a row about 1100px tall

pkg/harvester/components/health/ComponentHealthSection.vue:353
Suggestion

Message is the narrowest text column, at 171px. With 7 images the expanded text pushes the row to 1109px, more than a full screen. The text itself comes from the backend and is mostly repeated (see FYI), so a capped scroll area or a wider column would help more than the toggle.

Expanded message: 7 images, 1109px tall row, table past the section edge
Expanded message: 7 images, 1109px tall row, table past the section edge
8

The success messages describe the wrong thing

pkg/harvester/l10n/en-us.yaml:555-556, pkg/harvester/components/health/IgnoreCheckDropdown.vue:92
Suggestion

Ignoring 1 of 7 says "The check ImageRetryLimitExceeded is ignored". Restoring one resource says "The check is restored". Both are visible in the clips above.

Suggested comment

Ignoring 1 of 7 resources says the check is ignored, and restoring one resource says the check is restored. Could these name the resources when the action is partial?
9

The count stays at (7) with 6 listed

pkg/harvester/components/health/ComponentHealthSection.vue:368
Suggestion

The controller keeps affectedCount as a raw fact by design, but the UI prints it next to the list. With 1 ignored and Show ignored off, the header says 7 and nothing explains the gap. The message also still leads with the ignored image. Something like "6 of 7" would be clear.

10

The reason and expiry can't be seen anywhere

pkg/harvester/components/health/ComponentHealthSection.vue:321
Suggestion

The popup asks for a reason and a duration, but after saving neither appears, even with Show ignored on. To find out why something is ignored or when it comes back, you need kubectl get healthignorerules. A tooltip on "Ignored" would be enough.

Show ignored on: "known bad mirror" and the 30 days are nowhere
Show ignored on: "known bad mirror" and the 30 days are nowhere
11

After Restore, the cell wrongly says "Managed by the addon values"

pkg/harvester/components/health/ComponentHealthSection.vue:329-341
Suggestion

Once the rule leaves the store, canRestore is false but the check is still marked ignored until the controller reconciles. So the row falls through to plain "Ignored" with the addon tooltip. It lasted at least 4.5s in my probe. It corrects itself, but it says something untrue at the moment the user is looking.

Just after Restore: "Ignored" under a green "restored" growl
Just after Restore: "Ignored" under a green "restored" growl
12

Filter gives no sign that it is active

pkg/harvester/components/health/ComponentHealthSection.vue:269
Suggestion

With Error unchecked, the Summary says Critical, the table shows no Error row, and the button looks exactly as it does unfiltered. Shown at the end of the walkthrough clip. A count on the button would fix it.

13

Resource names are plain text

pkg/harvester/components/health/ComponentHealthSection.vue:375
Suggestion

HEP story 1: "I can click through to see which resource is affected". The Events table just above links the same VM. The mockups do show plain text, so this may be planned for later.

14

Should this be one table instead of tabs per controller?

pkg/harvester/components/health/ComponentHealthSection.vue:253
Question

votdev asked on issue 8436 (10-19): end users don't know what each controller is, so use a table with component as a column, plus filters. It has no answer there. On real data every check belongs to harvester-controller, so today the tabs hold one entry, the Summary has one row, and the tab column costs the width that causes finding 5. The multi-tab layout works, but I could only see it with CRs I made up. Raw values like harvesterhci.io/v1beta1, VirtualMachineImage belong to the same audience question.

<b>Synthetic data:</b> extra components I created to test tabs, Node column and paging
Synthetic data: extra components I created to test tabs, Node column and paging

Suggested comment

votdev's question on the issue about a single table with component as a column still looks open. With one real component today, the tab and the Summary row add little and take width from the table. What is the plan here?
15

Critical or Error? And what do the numbers count?

pkg/harvester/utils/component-health.js:37
Question

The Summary says Critical for what the detail calls Error. Errors also counts checks, not resources: 7 failed images read as "Errors 1". Both match the HEP, so these are product decisions, not bugs.

16

Is the bottom of the page the right place?

pkg/harvester/list/harvesterhci.io.dashboard.vue:834
Question

It matches the mockup. But HEP story 1 says "I open the dashboard and immediately see" the error, and at 1440×900 you must scroll past Capacity and Events to find a Critical. No badge or banner at the top points to it.

The sections start below Events
The sections start below Events
17

With the addon off (the default): a console error every visit and no hint

pkg/harvester/components/health/ComponentHealthSection.vue:30, pkg/harvester/components/health/HealthSummaryTable.vue:18
Question

The sections hide correctly on v1.10 without the addon, and the page looks normal. But each visit logs Failed fetching… Unknown schema twice, because findAll runs without checking the schema. And nothing tells users the feature exists, while the monitoring addon on the same page gets a "click here to enable" banner.

Addon off: the page is unaffected, apart from the console
Addon off: the page is unaffected, apart from the console

Suggested comment

Without the addon, which is the default, every dashboard visit logs two errors, because the components call findAll without checking the schema exists. Could they check first? Also, do we want a hint that the addon exists, like monitoring has?
18

Smaller things

pkg/harvester/components/health/ComponentHealthSection.vue:329, :86, :287
Nit
Double-click on Restore
Double-click on Restore
FYI: backend, not this PR
What was checked
CaseResultNotes
Case 1: VM with a node selectorPassReal fixture, VMNodeSelectorLiveMigrationUnsupported Warning.
Case 2: not enough volume replicasPassNo fixture needed: this one-node box already reports ReplicaSchedulingFailure on 6 volumes.
Case 3: image with a bad URLPass7 images. Warning while retrying, then Error ImageRetryLimitExceeded.
Case 5: support bundlePassInfo row appeared live after about 5s. Cleared in the controller at ready (about 3.4 min).
Case 8.1: ignore one resourceFindings 3, 8, 9The resource leaves the list about 3s later, without a reload.
Case 8.2: ignore the entire checkPassDone on the VM check with Forever: no resources or expiresAt sent. Summary dropped from Critical to Warning when the Error check was fully ignored.
Case 8.3: expired rulePassCheck returned about 3s after setting a past expiresAt.
Cases 4, 6, 7Not runThese need a full bucket or a guest cluster. They exercise the controller, and render through the same table.
Severity sort, both tablesPassError, Warning, Info. Copilot's fix holds.
Show More and +N morePassCopilot's fix holds. Real buttons, reachable by keyboard.
Filter counts and empty messagePass"No checks match" versus "No issues detected" are told apart correctly.
Node column, paging, no-resource rows, empty componentPassSynthetic CRs only.
Addon-managed rulePassSimulated by labelling a rule source=default: "Ignored" with no Restore.
Light themePassTables and popup are fine. Body class swap only, nothing saved.
Unit tests and page errorsPassThe PR's 5 tests pass, CI is green, no uncaught page errors in any run.
Real read-only userNot testedOnly stubbed 403s. I didn't create a user on .118.
Inside Rancher (extension mode)Not testedStandalone preview only.
Real v1.10 backendNot tested.118 is v1.9.0-rc4 with the version forced in the build, and the controller is the author's dev-18 image.
Suggested review (Request changes, inline comments as drafted above)

I tested this on a real Harvester with the resource health controller (dev-18), plus failing images and a VM with a node selector. Reading the data works well and live updates are quick. The problems are mostly in Ignore and Restore, details inline.