From 0f2da9beaf3c6c1bde53f6190e8ef2d0cf2d2b8f Mon Sep 17 00:00:00 2001 From: cproudlock Date: Sun, 30 Aug 2026 09:52:29 -0400 Subject: [PATCH] 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. --- frontend/src/components/DashboardCards.vue | 20 ++++-- frontend/src/views/Dashboard.vue | 84 ++++++++++++++++++---- 2 files changed, 88 insertions(+), 16 deletions(-) diff --git a/frontend/src/components/DashboardCards.vue b/frontend/src/components/DashboardCards.vue index 4ce82ba4..076b7887 100644 --- a/frontend/src/components/DashboardCards.vue +++ b/frontend/src/components/DashboardCards.vue @@ -1,5 +1,5 @@ @@ -84,21 +119,46 @@ // needed a person put together. A filtered "recently changed" view belongs in // Reports. import DashboardCards from '@/components/DashboardCards.vue' -import { ref, onMounted } from 'vue' +import { ref, computed, onMounted } from 'vue' import { dashboardApi } from '../api' const loading = ref(true) const stats = ref({}) +// Whether the totals actually came back. Swallowing the error and rendering +// stats as {} turned a 403 into a column of confident zeros. +const summaryloaded = ref(false) + +// Where this reader can actually go, for the case where the board has nothing +// for them. Same source as the sidebar, so it is already filtered to what they +// hold - and to what the guest role reaches, when nobody is signed in. +const navitems = ref([]) + +const openable = computed( + // The dashboard itself is not somewhere to go from the dashboard. + () => navitems.value.filter((item) => item.route && item.route !== '/')) onMounted(async () => { try { - const response = await dashboardApi.summary().catch(() => ({ data: { data: {} } })) + const response = await dashboardApi.summary() stats.value = response.data.data || {} + summaryloaded.value = Object.keys(stats.value).length > 0 } catch (error) { - console.error('Dashboard load error:', error) - } finally { - loading.value = false + // A caller without assets.view is restricted, not broken, so this is not + // logged as an error - the panel offers them somewhere to go instead. + summaryloaded.value = false } + + if (!summaryloaded.value) { + // Only fetched when it is needed, so the common case costs nothing. + try { + const response = await dashboardApi.navigation() + navitems.value = response.data.data || [] + } catch (error) { + navitems.value = [] + } + } + + loading.value = false })