Gate endpoint UI refresh on fresh telemetry and land endpoint agent

A command reaching 'succeeded' only proves the agent ran the action, not that it posted a new telemetry snapshot. Reloading panels on command success alone re-rendered stale service/process rows, so a stopped process could still show running right after Refresh Status.

Add a lightweight telemetrymeta probe exposing lastreportedat and gate both the header Refresh Status action and inline row Start/Stop on that stamp advancing past a pre-action baseline. Polling is bounded so an offline or idle agent never hangs the button, and messaging distinguishes a confirmed refresh from a queued one.

Bring the .NET endpoint agent source (service, tray, installer) into the repo so Cameron can merge and test, with an agent/.gitignore keeping bin/obj/publish out. Includes the DncMain launch-path fix, tray single-file host env scrub, and best-effort file logger.
This commit is contained in:
benjamin.baker
2026-08-28 15:07:20 -04:00
parent c001657582
commit af133372b4
46 changed files with 4593 additions and 128 deletions

View File

@@ -72,6 +72,34 @@ describe('list render mapping (warranty)', () => {
})
})
describe('list row filtering (tracked services vs processes)', () => {
const feedPanel = {
id: 'endpoints-tracked-services', render: 'list',
filter: { key: 'kind', equals: 'service' },
map: { title: 'name' },
_data: [
{ name: 'UDC MTConnect Agent', kind: 'service' },
{ name: 'DncMain', kind: 'process' },
{ name: 'Spooler', kind: 'SERVICE' },
],
}
it('keeps only rows whose filter key matches, case-insensitively', () => {
const kept = rows(feedPanel).map((r) => r.name)
expect(kept).toEqual(['UDC MTConnect Agent', 'Spooler'])
})
it('filters the same feed to processes when equals is process', () => {
const kept = rows({ ...feedPanel, filter: { key: 'kind', equals: 'process' } })
expect(kept.map((r) => r.name)).toEqual(['DncMain'])
})
it('returns every row when no filter is declared', () => {
const kept = rows({ ...feedPanel, filter: undefined })
expect(kept).toHaveLength(3)
})
})
describe('keyvalue + table + badge', () => {
it('keyvalue reads a fields array or a plain object', () => {
const fromFields = keyvalueFields({