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.
(cherry picked from commit 0f2da9beaf)
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -16,19 +16,27 @@
|
||||
an exception and DashboardCards has no business declaring one. -->
|
||||
<DashboardCards>
|
||||
<template #rail>
|
||||
<section class="dc-panel estate">
|
||||
<!-- Only when the totals actually loaded. /api/dashboard/summary
|
||||
needs assets.view, and a caller without it got an empty object
|
||||
that rendered as a tidy column of zeros - an estate of nothing,
|
||||
stated with total confidence. Same fault as reporting Low
|
||||
Supplies 0 while the monitoring is down. -->
|
||||
<section v-if="summaryloaded" class="dc-panel estate">
|
||||
<header class="estate-head">
|
||||
<h3>Estate</h3>
|
||||
<router-link to="/reports" class="estate-link">Reports</router-link>
|
||||
</header>
|
||||
|
||||
<!-- One line per asset type, then the total under a rule.
|
||||
These used to be seven equal tiles in which four of them summed
|
||||
to a fifth: machines + PCs + printers + network IS all assets,
|
||||
shown beside its own addends at the same weight. A total that
|
||||
looks like a sixth category invites people to add it in again.
|
||||
Two of the seven also counted STATUS rather than type without
|
||||
saying so. -->
|
||||
These used to be seven equal tiles in which some of them summed
|
||||
to another: the type counts ARE all assets, shown beside their
|
||||
own total at the same weight. A total that looks like another
|
||||
category invites people to add it in again. Two of the seven
|
||||
also counted STATUS rather than type without saying so.
|
||||
|
||||
EVERY counted type must appear here. The endpoint's grand total
|
||||
is machines + PCs + network + printers + measuring tools, and
|
||||
omitting one leaves a total nothing on screen explains. -->
|
||||
<div class="estate-row">
|
||||
<router-link to="/machines" class="estate-k">Machines</router-link>
|
||||
<span class="estate-v">{{ stats.totalmachines || 0 }}</span>
|
||||
@@ -45,6 +53,16 @@
|
||||
<router-link to="/network" class="estate-k">Network devices</router-link>
|
||||
<span class="estate-v">{{ stats.totalnetwork || 0 }}</span>
|
||||
</div>
|
||||
<!-- Measuring tools are IN the grand total the endpoint returns, so
|
||||
they have to be visible here. Left out, the total silently
|
||||
stopped matching the rows above it - 610 of rows over a stated
|
||||
729 on an instance with gages - which is the exact fault this
|
||||
panel was built to remove. Dev has none, so it looked correct
|
||||
here and was wrong everywhere the import had run. -->
|
||||
<div class="estate-row">
|
||||
<router-link to="/measuringtools" class="estate-k">Measuring tools</router-link>
|
||||
<span class="estate-v">{{ stats.totalmeasuringtool || 0 }}</span>
|
||||
</div>
|
||||
|
||||
<div class="estate-row estate-total">
|
||||
<span class="estate-k">All assets</span>
|
||||
@@ -62,6 +80,23 @@
|
||||
</span>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- Never leave the page blank, and do not apologise either: show
|
||||
what this reader CAN open. A guest who can see none of the cards
|
||||
and cannot read the totals previously got a heading and nothing
|
||||
else, which reads as a broken site rather than a closed door.
|
||||
The list is the same permission-filtered navigation the sidebar
|
||||
uses - server-filtered for a signed-out visitor - so it can never
|
||||
offer a link that dead-ends at the router guard, and it names no
|
||||
plugin this page had to know about. -->
|
||||
<section v-else-if="openable.length" class="dc-panel estate">
|
||||
<header class="estate-head">
|
||||
<h3>Where you can go</h3>
|
||||
</header>
|
||||
<div v-for="item in openable" :key="item.route" class="estate-row">
|
||||
<router-link :to="item.route" class="estate-k">{{ item.name }}</router-link>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
</DashboardCards>
|
||||
</template>
|
||||
@@ -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
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user