Rename Metrics to Monitors and move it second
"Metrics" promised numbers. The tab holds tracked services, tracked processes and network interfaces - things being WATCHED and their current state, only one of which is a measurement. Monitors is what the panels actually are, and it matches the word the agent's own config already uses for them. Moved to second, ahead of Software. What is being watched on this box is the question asked right after "what is it", and well before its application inventory - which is reference data somebody goes looking for deliberately. Tab order is now Details, Monitors, Software, Activity. Contract 0.29.0 keeps its number. It is unreleased, so the group vocabulary it introduces is still being settled and nothing can yet depend on the old name. Both the server and the client fall back to 'details' for a group they do not recognise, so a site that deploys these halves out of step gets a misplaced card rather than a missing one. Scope: product
This commit is contained in:
@@ -47,7 +47,7 @@ Recorded in the comment block in `shopdb/__init__.py`:
|
||||
| 0.25.0 | Added `caller_holds(permission)`, which answers whether the current caller holds a permission WITHOUT refusing the request - the complement to `require_permission` for a route that serves everyone but should only offer what the caller can act on. The endpoints services panel is the case: it emitted Start/Stop row actions to every reader, because the emission never consulted the caller. Clicking them was always refused server-side, so nothing was exploitable, but offering a read-only viewer controls over a shop-floor PC is the wrong signal, and it becomes a real problem once a site grants that panel to guests. Guests are included in the answer, since a signed-out visitor holds whatever the guest role was granted | additive surface (minor) |
|
||||
| 0.26.0 | Dashboard cards may declare `shortname`. The dashboard merged its per-source cards into one worst-first queue, and a card's `title` is written as a heading ("Endpoints with crashes or shutdowns") which as a row badge ran wider than the hostname it qualified. Optional, defaults to the title, so a plugin that does not declare one is unaffected | additive field (minor) |
|
||||
| 0.27.0 | Added `Script` so a plugin can read the ShopDB-hosted script library that backs endpoint `run_script` delivery without importing core model internals. The endpoints plugin resolves a delivered script by name and serves its body by digest to the agent | additive surface (minor) |
|
||||
| 0.29.0 | Added an optional `group` to `get_asset_panels`, naming which detail-page tab a panel belongs to: `details` (the default) / `software` / `metrics` / `actions` / `activity`. The detail pages are tabbed, and a page that placed panels by knowing their ids would defeat the hook. Additive: an absent or unrecognised group resolves to `details` server-side, so every existing plugin keeps working with no change | additive surface (minor) |
|
||||
| 0.29.0 | Added an optional `group` to `get_asset_panels`, naming which detail-page tab a panel belongs to: `details` (the default) / `monitors` / `software` / `activity`. The detail pages are tabbed, and a page that placed panels by knowing their ids would defeat the hook. Additive: an absent or unrecognised group resolves to `details` server-side, so every existing plugin keeps working with no change | additive surface (minor) |
|
||||
| 0.28.0 | Added `client_ip` and `strip_port` so a plugin does not hand-roll caller-IP extraction. Two plugins had, and both read the first `X-Forwarded-For` hop, which the caller supplies - so a rate limit keyed on it was defeated by rotating the header. The helper resolves from `remote_addr`, which ProxyFix has already filled from the header when `TRUSTED_PROXY_COUNT` says a proxy is really in front | additive surface (minor) |
|
||||
|
||||
The source comment block documents 0.3.0, 0.4.0, 0.6.0, 0.7.0, 0.9.0, 0.10.0,
|
||||
|
||||
@@ -200,8 +200,9 @@ const panels = ref([])
|
||||
// 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']
|
||||
// nothing logged. The same applies to the 'metrics' to 'monitors' rename.
|
||||
// Fall back the same way the server does rather than drop it.
|
||||
const RENDERED_GROUPS = ['details', 'monitors', 'software', 'activity']
|
||||
function groupOf(panel) {
|
||||
return RENDERED_GROUPS.includes(panel.group) ? panel.group : 'details'
|
||||
}
|
||||
|
||||
@@ -302,13 +302,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Metrics and Activity are entirely plugin-contributed: the telemetry,
|
||||
the service state and its controls, and the event feed all belong to
|
||||
the endpoints plugin, and core knows nothing about them.
|
||||
<!-- Monitors and Activity are entirely plugin-contributed: what is being
|
||||
watched on the box, the service state and its controls, and the event
|
||||
feed all belong to 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">
|
||||
<PluginAssetPanels :assetid="computer.assetid" group="metrics" />
|
||||
Monitors keeps the two-column flow because it is short cards. -->
|
||||
<div v-show="activegroup === 'monitors'" class="content-grid">
|
||||
<PluginAssetPanels :assetid="computer.assetid" group="monitors" />
|
||||
</div>
|
||||
<div v-show="activegroup === 'activity'" class="detail-full">
|
||||
<PluginAssetPanels :assetid="computer.assetid" group="activity" />
|
||||
@@ -384,10 +385,12 @@ const detailtabs = computed(() => {
|
||||
const filled = assetPanels.value?.populatedGroups ?? new Set()
|
||||
const has = (key) => filled.has ? filled.has(key) : false
|
||||
const tabs = [{ key: 'details', label: 'Details' }]
|
||||
// Monitors sits second: what is being watched on this box is the question
|
||||
// asked right after "what is it", and well before its software inventory.
|
||||
if (has('monitors')) tabs.push({ key: 'monitors', label: 'Monitors' })
|
||||
if (has('software') || installedApps.value.length > 0) {
|
||||
tabs.push({ key: 'software', label: 'Software', count: installedApps.value.length || null })
|
||||
}
|
||||
if (has('metrics')) tabs.push({ key: 'metrics', label: 'Metrics' })
|
||||
if (has('activity')) tabs.push({ key: 'activity', label: 'Activity' })
|
||||
return tabs
|
||||
})
|
||||
|
||||
@@ -351,9 +351,9 @@ class EndpointsPlugin(BasePlugin):
|
||||
'permission': 'endpoints.command',
|
||||
'empty': 'No monitored services reported.',
|
||||
'position': 12,
|
||||
# Service state is telemetry first; the Start/Stop controls
|
||||
# belong beside it rather than in a tab of their own.
|
||||
'group': 'metrics',
|
||||
# Service state is what is being watched; the Start/Stop
|
||||
# controls belong beside it, not in a tab of their own.
|
||||
'group': 'monitors',
|
||||
},
|
||||
{
|
||||
'id': 'endpoints-tracked-processes',
|
||||
@@ -393,7 +393,7 @@ class EndpointsPlugin(BasePlugin):
|
||||
'empty': 'No monitored processes reported.',
|
||||
'position': 13,
|
||||
# observed process state, not something you act on here
|
||||
'group': 'metrics',
|
||||
'group': 'monitors',
|
||||
},
|
||||
{
|
||||
# One feed instead of two cards. Events and commands answer the
|
||||
@@ -458,8 +458,8 @@ class EndpointsPlugin(BasePlugin):
|
||||
],
|
||||
'empty': 'No network interfaces reported.',
|
||||
'position': 16,
|
||||
# adapter state is telemetry, not something you act on
|
||||
'group': 'metrics',
|
||||
# adapter state is watched, not something you act on
|
||||
'group': 'monitors',
|
||||
},
|
||||
{
|
||||
# Installed-software (SBOM) inventory. Raw discovered list for
|
||||
|
||||
@@ -25,7 +25,7 @@ pluginui_bp = Blueprint('pluginui', __name__)
|
||||
#
|
||||
# 'details' is first and is the fallback: it holds what an asset IS, which is
|
||||
# what a panel declaring nothing almost always contributes.
|
||||
ASSET_PANEL_GROUPS = ('details', 'software', 'metrics', 'activity')
|
||||
ASSET_PANEL_GROUPS = ('details', 'monitors', 'software', 'activity')
|
||||
|
||||
|
||||
def _collect(hookname):
|
||||
|
||||
@@ -371,7 +371,7 @@ class BasePlugin(ABC):
|
||||
# keys, rendered generically - see warranty)
|
||||
'position': int, # order among panels
|
||||
'group': str, # OPTIONAL. Which tab on the detail page:
|
||||
# 'details' | 'software' | 'metrics'
|
||||
# 'details' | 'monitors' | 'software'
|
||||
# | 'activity'. Omitted means 'details'.
|
||||
}
|
||||
|
||||
@@ -383,12 +383,13 @@ class BasePlugin(ABC):
|
||||
existing plugin working untouched: an unknown or absent group resolves
|
||||
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 -
|
||||
an inventory listing, live telemetry, or a history feed.
|
||||
what is being watched on the box, an inventory listing, 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
|
||||
state first and a Start/Stop second, so they live in 'monitors' with
|
||||
the rest of what is being watched. Splitting the buttons away from
|
||||
the state they change made a tab that held one card and answered no
|
||||
question on its own.
|
||||
|
||||
|
||||
@@ -198,7 +198,7 @@ def test_a_group_nobody_defined_falls_back_rather_than_disappearing(
|
||||
monkeypatch.setattr(plugin, 'get_asset_panels', lambda: [{
|
||||
'id': 'typo', 'title': 'Typo', 'assettypes': ['*'],
|
||||
'endpoint': '/api/whatever', 'render': 'keyvalue', 'position': 1,
|
||||
'group': 'metrcis',
|
||||
'group': 'montiors',
|
||||
}])
|
||||
assetid = _make_asset(db)
|
||||
|
||||
@@ -210,18 +210,18 @@ def test_a_group_nobody_defined_falls_back_rather_than_disappearing(
|
||||
|
||||
def test_group_matching_is_not_case_or_space_sensitive(app, client, db,
|
||||
auth_headers, monkeypatch):
|
||||
"""'Metrics ' is obviously meant to be metrics; do not send it to details."""
|
||||
"""'Monitors ' is obviously meant to be monitors; do not send it to details."""
|
||||
pm = app.extensions['plugin_manager']
|
||||
monkeypatch.setattr(pm.registry, 'is_enabled', lambda name: True)
|
||||
plugin = next(iter(pm.get_all_plugins().values()))
|
||||
monkeypatch.setattr(plugin, 'get_asset_panels', lambda: [{
|
||||
'id': 'shouty', 'title': 'Shouty', 'assettypes': ['*'],
|
||||
'endpoint': '/api/whatever', 'render': 'keyvalue', 'position': 1,
|
||||
'group': ' Metrics ',
|
||||
'group': ' Monitors ',
|
||||
}])
|
||||
assetid = _make_asset(db)
|
||||
|
||||
panels = _data(client, '/api/pluginui/asset-panels?assetid=%d' % assetid,
|
||||
auth_headers)
|
||||
panel = next(p for p in panels if p['id'] == 'shouty')
|
||||
assert panel['group'] == 'metrics'
|
||||
assert panel['group'] == 'monitors'
|
||||
|
||||
Reference in New Issue
Block a user