Drop the Actions tab, and give wide panels the width instead of a scrollbar
Four changes from looking at the built page. ACTIONS HELD ONE CARD. Tracked services was the only thing in it, and it was the wrong split anyway: a service row reads as state first and a Start/Stop second, so separating the buttons from the state they act on made a tab that answered no question on its own. Services move to Metrics, beside the rest of what the endpoint reports, and the group is gone from the contract. WIDE PANELS SPAN, THEY DO NOT SCROLL. Network interfaces carries adapter, address, MAC, speed and state; at half page width in the two-column flow it either squeezed every cell to three lines or offered a horizontal scrollbar. A scrollbar for data that just needs room is a layout failure. Table panels now take column-span, which is the multi-column layout's own answer, so short fact cards stay two-up around them. The wrapper keeps overflow-x only as a floor for a genuinely narrow viewport. SOFTWARE AND ACTIVITY RUN FULL WIDTH. Both are single long lists, and a list split into two balanced columns reads as two unrelated lists with no way to tell where the first ended. Details and Metrics keep the two-column flow because they are short cards, which is what that flow is good at. Contract 0.29.0 keeps its number: it is unreleased, and the group vocabulary it introduces has simply lost one value before anybody could depend on it. Scope: product
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
a panel as JSON (title, endpoint, render mode, field map); this renders it
|
a panel as JSON (title, endpoint, render mode, field map); this renders it
|
||||||
on any asset detail page, so a plugin adds detail-page UI with no Vue. -->
|
on any asset detail page, so a plugin adds detail-page UI with no Vue. -->
|
||||||
<template v-for="panel in groupedPanels" :key="panel.id">
|
<template v-for="panel in groupedPanels" :key="panel.id">
|
||||||
<div v-if="panelVisible(panel)" class="section-card">
|
<div v-if="panelVisible(panel)" class="section-card" :class="{ 'pap-span': panel.render === 'table' }">
|
||||||
<!-- collapsible panels get a clickable header with a caret and, when the
|
<!-- collapsible panels get a clickable header with a caret and, when the
|
||||||
panel declares a status rollup, a green/yellow/red dot for the worst
|
panel declares a status rollup, a green/yellow/red dot for the worst
|
||||||
row (a stopped service or a warning/critical event). --><h3
|
row (a stopped service or a warning/critical event). --><h3
|
||||||
@@ -430,7 +430,18 @@ defineExpose({ refresh: load, populatedGroups })
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pap-table-wrap { overflow-x: auto; }
|
/* A table panel SPANS BOTH COLUMNS rather than scrolling inside one.
|
||||||
|
Network interfaces is the case that forced this: adapter, address, MAC,
|
||||||
|
speed and state at half page width either squeezed every cell to three
|
||||||
|
lines or handed the reader a horizontal scrollbar, and a scrollbar for
|
||||||
|
data that simply needs room is a layout failure, not a feature. column-span
|
||||||
|
is the multi-column layout's own answer, so the short fact cards stay two-up
|
||||||
|
around it.
|
||||||
|
|
||||||
|
The wrapper keeps overflow-x as a floor for the genuinely too-wide case on
|
||||||
|
a narrow viewport, where nothing else can help. */
|
||||||
|
.pap-span { column-span: all; }
|
||||||
|
.pap-table-wrap { overflow-x: auto; max-width: 100%; }
|
||||||
.pap-table { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
|
.pap-table { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
|
||||||
.pap-table th, .pap-table td { text-align: left; padding: 0.4rem 0.6rem; border-bottom: 1px solid var(--border); }
|
.pap-table th, .pap-table td { text-align: left; padding: 0.4rem 0.6rem; border-bottom: 1px solid var(--border); }
|
||||||
.pap-badges { display: flex; flex-wrap: wrap; gap: 0.5rem; }
|
.pap-badges { display: flex; flex-wrap: wrap; gap: 0.5rem; }
|
||||||
|
|||||||
@@ -272,11 +272,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Software. The core installed-applications list plus anything a plugin
|
<!-- Software and Activity run FULL WIDTH, not in the two-column flow the
|
||||||
declared for this group. -->
|
Details tab uses. Both are long lists rather than short fact cards:
|
||||||
<div v-show="activegroup === 'software'" class="content-grid">
|
an application inventory or an event feed split into balanced columns
|
||||||
|
reads as two unrelated lists, and the eye has to find where the first
|
||||||
|
one ended. -->
|
||||||
|
<div v-show="activegroup === 'software'" class="detail-full">
|
||||||
<PluginAssetPanels :assetid="computer.assetid" group="software" />
|
<PluginAssetPanels :assetid="computer.assetid" group="software" />
|
||||||
<div class="content-column">
|
<div>
|
||||||
<div class="section-card" v-if="installedApps.length > 0">
|
<div class="section-card" v-if="installedApps.length > 0">
|
||||||
<h3 class="section-title">Installed Applications</h3>
|
<h3 class="section-title">Installed Applications</h3>
|
||||||
<div class="app-list">
|
<div class="app-list">
|
||||||
@@ -299,16 +302,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Metrics, Actions and Activity are entirely plugin-contributed: the
|
<!-- Metrics and Activity are entirely plugin-contributed: the telemetry,
|
||||||
telemetry, the service controls and the event feed all belong to the
|
the service state and its controls, and the event feed all belong to
|
||||||
endpoints plugin, and core knows nothing about them. -->
|
the endpoints plugin, and core knows nothing about them.
|
||||||
|
|
||||||
|
Metrics keeps the two-column flow because it is short cards. -->
|
||||||
<div v-show="activegroup === 'metrics'" class="content-grid">
|
<div v-show="activegroup === 'metrics'" class="content-grid">
|
||||||
<PluginAssetPanels :assetid="computer.assetid" group="metrics" />
|
<PluginAssetPanels :assetid="computer.assetid" group="metrics" />
|
||||||
</div>
|
</div>
|
||||||
<div v-show="activegroup === 'actions'" class="content-grid">
|
<div v-show="activegroup === 'activity'" class="detail-full">
|
||||||
<PluginAssetPanels :assetid="computer.assetid" group="actions" />
|
|
||||||
</div>
|
|
||||||
<div v-show="activegroup === 'activity'" class="content-grid">
|
|
||||||
<PluginAssetPanels :assetid="computer.assetid" group="activity" />
|
<PluginAssetPanels :assetid="computer.assetid" group="activity" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -386,7 +388,6 @@ const detailtabs = computed(() => {
|
|||||||
tabs.push({ key: 'software', label: 'Software', count: installedApps.value.length || null })
|
tabs.push({ key: 'software', label: 'Software', count: installedApps.value.length || null })
|
||||||
}
|
}
|
||||||
if (has('metrics')) tabs.push({ key: 'metrics', label: 'Metrics' })
|
if (has('metrics')) tabs.push({ key: 'metrics', label: 'Metrics' })
|
||||||
if (has('actions')) tabs.push({ key: 'actions', label: 'Actions' })
|
|
||||||
if (has('activity')) tabs.push({ key: 'activity', label: 'Activity' })
|
if (has('activity')) tabs.push({ key: 'activity', label: 'Activity' })
|
||||||
return tabs
|
return tabs
|
||||||
})
|
})
|
||||||
@@ -602,6 +603,12 @@ async function confirmRetire() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<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 */
|
/* PC-specific styles - shared styles are in global style.css */
|
||||||
|
|
||||||
/* Remote-access protocol links */
|
/* Remote-access protocol links */
|
||||||
|
|||||||
@@ -351,8 +351,9 @@ class EndpointsPlugin(BasePlugin):
|
|||||||
'permission': 'endpoints.command',
|
'permission': 'endpoints.command',
|
||||||
'empty': 'No monitored services reported.',
|
'empty': 'No monitored services reported.',
|
||||||
'position': 12,
|
'position': 12,
|
||||||
# Start/Stop act on the machine, so this is a control surface
|
# Service state is telemetry first; the Start/Stop controls
|
||||||
'group': 'actions',
|
# belong beside it rather than in a tab of their own.
|
||||||
|
'group': 'metrics',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'id': 'endpoints-tracked-processes',
|
'id': 'endpoints-tracked-processes',
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ pluginui_bp = Blueprint('pluginui', __name__)
|
|||||||
#
|
#
|
||||||
# 'details' is first and is the fallback: it holds what an asset IS, which is
|
# 'details' is first and is the fallback: it holds what an asset IS, which is
|
||||||
# what a panel declaring nothing almost always contributes.
|
# what a panel declaring nothing almost always contributes.
|
||||||
ASSET_PANEL_GROUPS = ('details', 'software', 'metrics', 'actions', 'activity')
|
ASSET_PANEL_GROUPS = ('details', 'software', 'metrics', 'activity')
|
||||||
|
|
||||||
|
|
||||||
def _collect(hookname):
|
def _collect(hookname):
|
||||||
|
|||||||
@@ -372,8 +372,7 @@ class BasePlugin(ABC):
|
|||||||
'position': int, # order among panels
|
'position': int, # order among panels
|
||||||
'group': str, # OPTIONAL. Which tab on the detail page:
|
'group': str, # OPTIONAL. Which tab on the detail page:
|
||||||
# 'details' | 'software' | 'metrics'
|
# 'details' | 'software' | 'metrics'
|
||||||
# | 'actions' | 'activity'. Omitted means
|
# | 'activity'. Omitted means 'details'.
|
||||||
# 'details'.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ON 'group'. The detail pages are tabbed, and a panel says which tab it
|
ON 'group'. The detail pages are tabbed, and a panel says which tab it
|
||||||
@@ -384,8 +383,14 @@ class BasePlugin(ABC):
|
|||||||
existing plugin working untouched: an unknown or absent group resolves
|
existing plugin working untouched: an unknown or absent group resolves
|
||||||
to 'details', which is the tab that holds what an asset IS. Reach for
|
to 'details', which is the tab that holds what an asset IS. Reach for
|
||||||
another group only when the panel is the thing that tab exists for -
|
another group only when the panel is the thing that tab exists for -
|
||||||
an inventory listing, live telemetry, controls that act on the machine,
|
an inventory listing, live telemetry, or a history feed.
|
||||||
or a history feed.
|
|
||||||
|
There is deliberately no group for controls. A panel that acts on the
|
||||||
|
machine belongs beside the state it acts on: tracked services read as
|
||||||
|
telemetry first and a Start/Stop second, so they live in 'metrics' with
|
||||||
|
the rest of what the endpoint reports. Splitting the buttons away from
|
||||||
|
the state they change made a tab that held one card and answered no
|
||||||
|
question on its own.
|
||||||
|
|
||||||
Consumed by GET /api/pluginui/asset-panels?assetid=<id>, which returns
|
Consumed by GET /api/pluginui/asset-panels?assetid=<id>, which returns
|
||||||
the panels matching that asset's type. Disabled plugins
|
the panels matching that asset's type. Disabled plugins
|
||||||
|
|||||||
Reference in New Issue
Block a user