Show a guest what they can see, not a blank page

A guest got a heading and nothing else.

THE STRUCTURAL FAULT WAS MINE

Moving the Estate panel into DashboardCards' rail slot put it behind that
component's root v-if, which was `liveCards.length > 0`. A guest is permitted
zero cards, so the whole component disappeared and took the slot with it. Before
that move the tiles were top-level and always rendered. The wrapper no longer
vanishes with the cards.

TWO LIES THE EMPTY STATE WAS TELLING

"Nothing needs attention" is now shown only to somebody who can see the checks.
A reader permitted none of them is not looking at a healthy fleet, they are
looking at nothing, and saying otherwise is the same fault as reporting Low
Supplies 0 while the monitoring is down. The count of PERMITTED cards is now
tracked separately from the count that currently hold rows.

The Estate panel no longer renders zeros for a caller who cannot read totals.
/api/dashboard/summary needs assets.view, and the failure was swallowed into an
empty object that drew a tidy column of zeros - an estate of nothing, stated
with confidence.

WHAT A GUEST GETS INSTEAD

Where they can go: the same permission-filtered navigation the sidebar uses,
which the server already filters to the guest role for a signed-out visitor. So
it cannot offer a link that dead-ends at the router guard, and this page names no
plugin it had to know about. Verified against the running instance - a guest is
offered Guides and Tech Tools, matching guides.view and tools.view.

ALSO: the measuring tools row

totalassets counts five types - machines, PCs, network, printers AND measuring
tools - but only four had rows, so the total stopped matching the addends above
it wherever gages exist: 610 of visible rows under a stated 729. That is exactly
the fault this panel was built to remove, and it looked right here only because
this database has no measuring tools. Every counted type now has a row.

Frontend 314 passed.
This commit is contained in:
cproudlock
2026-08-30 09:52:29 -04:00
parent 1311dea113
commit 0f2da9beaf
2 changed files with 88 additions and 16 deletions

View File

@@ -1,5 +1,5 @@
<template>
<div v-if="hasBoard" class="dc-board">
<div class="dc-board">
<!-- The rollup. Counts ROWS, not cards, and each count filters the queue
below rather than navigating away: landing on a number that tells you
@@ -54,7 +54,8 @@
<!-- ONE queue, worst first, instead of a card per source. Reading order
is work order. -->
<section v-if="queue.length || overflow.length" class="dc-panel dc-queue">
<section v-if="canseechecks && (queue.length || overflow.length)"
class="dc-panel dc-queue">
<header class="dc-panel-head">
<h3>Needs attention</h3>
<span class="dc-panel-count">{{ queueTotal }}</span>
@@ -78,7 +79,12 @@
</p>
</section>
<section v-else class="dc-panel dc-clear">
<!-- Only claim the board is clear to somebody who can actually see the
checks. A reader with no permission for any of them is not looking at
a healthy fleet, they are looking at nothing - and telling them
"nothing needs attention" is the same lie as reporting Low Supplies 0
while the monitoring is down. -->
<section v-else-if="canseechecks" class="dc-panel dc-clear">
<p class="dc-clear-line">Nothing needs attention.</p>
</section>
@@ -195,7 +201,11 @@ const context = computed(
const queueTotal = computed(
() => queue.value.length + overflow.value.reduce((sum, e) => sum + e.hidden, 0))
const hasBoard = computed(() => liveCards.value.length > 0)
// How many cards this caller is ALLOWED to see, independent of whether they
// currently hold rows. Zero means no visibility into exceptions at all, which
// must not be drawn as "nothing wrong".
const permittedcount = ref(0)
const canseechecks = computed(() => permittedcount.value > 0)
function toggleFilter(level) {
filter.value = filter.value === level ? '' : level
@@ -237,6 +247,8 @@ async function load() {
const wanted = renderableCards(
permittedCards(declared, (name) => auth.hasPermission(name)))
permittedcount.value = wanted.length
cards.value = await Promise.all(wanted.map(async (card) => {
try {
const response = await api.get(toApiPath(card.endpoint))