Fix collapse, group skew, empty tables and the tab strip scrollbar
This commit is contained in:
@@ -1083,6 +1083,21 @@ td.actions .btn + .btn {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
/* Full-width alternative to .content-grid, for a tab holding one long list
|
||||
rather than short fact cards. An application inventory or an event feed
|
||||
split into two balanced columns reads as two unrelated lists, and the eye
|
||||
has to hunt for where the first one ended. */
|
||||
.detail-full {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.detail-full > * {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Section Cards */
|
||||
.section-card {
|
||||
background: var(--bg-card);
|
||||
|
||||
@@ -70,11 +70,21 @@ function onKey(event, index) {
|
||||
.adt {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
/* overflow-y MUST be stated. Per the spec, when one axis is not `visible`
|
||||
the other computes from `visible` to `auto` - so `overflow-x: auto` alone
|
||||
silently makes this scrollable vertically too, and the -1px margin the
|
||||
tabs use to sit on the border is enough to trip it. The result is a tiny
|
||||
vertical scrollbar inside the strip. */
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
scrollbar-width: thin;
|
||||
border-bottom: 1px solid var(--border);
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
.adt-tab {
|
||||
/* Never shrink: a squeezed tab wraps its label, which grows the strip and is
|
||||
what horizontal scrolling exists to avoid. */
|
||||
flex: none;
|
||||
font: inherit;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
|
||||
@@ -191,18 +191,30 @@ const props = defineProps({
|
||||
|
||||
const panels = ref([])
|
||||
|
||||
// The server resolves every panel's group before we see it, so an unknown or
|
||||
// absent one is already 'details' by the time it arrives here. Filtering can
|
||||
// therefore be a plain equality check and nothing falls through the gap.
|
||||
// The groups this build renders. A panel in any other group would be fetched,
|
||||
// held, and shown on no tab at all.
|
||||
//
|
||||
// The server normalises group before we see it, so a version-matched pair
|
||||
// never lands here. A MISMATCHED pair does: deploy a new frontend against a
|
||||
// server still serving an older vocabulary and every panel in a group that
|
||||
// has since been renamed or removed silently disappears. That is what happened
|
||||
// to tracked services when it moved out of 'actions' - the backend still said
|
||||
// 'actions', this build had no such tab, and the card was simply gone with
|
||||
// nothing logged. Fall back the same way the server does rather than drop it.
|
||||
const RENDERED_GROUPS = ['details', 'software', 'metrics', 'activity']
|
||||
function groupOf(panel) {
|
||||
return RENDERED_GROUPS.includes(panel.group) ? panel.group : 'details'
|
||||
}
|
||||
|
||||
const groupedPanels = computed(() =>
|
||||
props.group ? panels.value.filter((p) => p.group === props.group) : panels.value
|
||||
props.group ? panels.value.filter((p) => groupOf(p) === props.group) : panels.value
|
||||
)
|
||||
|
||||
// Which groups actually have a panel with data, so a parent can hide a tab
|
||||
// rather than offering an empty one.
|
||||
const populatedGroups = computed(() => {
|
||||
const seen = new Set()
|
||||
panels.value.forEach((p) => { if (panelVisible(p)) seen.add(p.group || 'details') })
|
||||
panels.value.forEach((p) => { if (panelVisible(p)) seen.add(groupOf(p)) })
|
||||
return seen
|
||||
})
|
||||
|
||||
|
||||
@@ -246,11 +246,17 @@ export function rowAction(panel, item) {
|
||||
}
|
||||
}
|
||||
|
||||
// A panel declaring `empty` is asking to stay visible with nothing in it and
|
||||
// say so - "No installed software reported." is an answer, where an absent
|
||||
// card is a question. `list` honoured that and `table` did not, for no reason
|
||||
// anybody wrote down, so a table panel with an empty message still vanished.
|
||||
// It matters more now that a panel can be the only thing in a tab: the tab
|
||||
// itself disappears with it.
|
||||
export function panelVisible(panel) {
|
||||
if (panel.render === 'tabs') return tabSections(panel).length > 0
|
||||
if (panel.render === 'list') return rows(panel).length > 0 || !!panel.empty
|
||||
if (panel.render === 'keyvalue') return keyvalueFields(panel).length > 0
|
||||
if (panel.render === 'table') return rows(panel).length > 0
|
||||
if (panel.render === 'table') return rows(panel).length > 0 || !!panel.empty
|
||||
if (panel.render === 'badge') return badges(panel).length > 0
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -327,3 +327,16 @@ describe('facet chips on a card that shows a health dot', () => {
|
||||
expect(facetedRows(services, ['Start Pending']).length).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('panelVisible and empty', () => {
|
||||
it('keeps a table panel that declares an empty message', () => {
|
||||
// It is the only card in the Software tab, so hiding it hides the tab and
|
||||
// the reader cannot tell "nothing reported" from "feature missing".
|
||||
const panel = { render: 'table', _data: { items: [] }, empty: 'No installed software reported.' }
|
||||
expect(panelVisible(panel)).toBe(true)
|
||||
})
|
||||
|
||||
it('still hides a table panel with nothing to say', () => {
|
||||
expect(panelVisible({ render: 'table', _data: { items: [] } })).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -603,11 +603,6 @@ async function confirmRetire() {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Software and Activity are single long lists, so they use the full page width
|
||||
rather than .content-grid's two balanced columns. Cards inside still need
|
||||
their own vertical rhythm, which the column flow was providing. */
|
||||
.detail-full { display: flex; flex-direction: column; gap: 1rem; }
|
||||
.detail-full > * { min-width: 0; }
|
||||
|
||||
/* PC-specific styles - shared styles are in global style.css */
|
||||
|
||||
|
||||
@@ -462,17 +462,20 @@ class EndpointsPlugin(BasePlugin):
|
||||
'group': 'metrics',
|
||||
},
|
||||
{
|
||||
# Installed-software (SBOM) inventory. Low-priority reference
|
||||
# data, not something looked at on every visit, so it starts
|
||||
# collapsed and sits near the bottom. Raw discovered list for
|
||||
# Installed-software (SBOM) inventory. Raw discovered list for
|
||||
# now: no managed-application linkage is surfaced yet.
|
||||
#
|
||||
# NOT collapsible. It used to be, and started collapsed, because
|
||||
# it was low-priority reference data sitting near the bottom of
|
||||
# one long page. It is now the Software tab's whole reason to
|
||||
# exist, and somebody who clicked that tab has already said what
|
||||
# they came for - making them click again to see it is a step
|
||||
# that asks a question already answered.
|
||||
'id': 'endpoints-applications',
|
||||
'title': 'Installed software',
|
||||
'assettypes': ['computer'],
|
||||
'endpoint': '/api/endpoints/asset/{assetid}/applications',
|
||||
'render': 'table',
|
||||
'collapsible': True,
|
||||
'collapsedDefault': True,
|
||||
'columns': [
|
||||
{'key': 'displayname', 'label': 'Name'},
|
||||
{'key': 'displayversion', 'label': 'Version'},
|
||||
|
||||
Reference in New Issue
Block a user