harden CSV import reconciliation and preserve saved model selections

Default fuzzy reconcile threshold to 0 and refuse merges when digit runs differ so numbered labels like Bay 311 never bind to Bay 310. Keep edit forms from clearing saved model fields when category filtering hides untyped rows. Includes in-progress computers policy and endpoints command feature work.
This commit is contained in:
benjamin.baker
2026-08-28 09:29:09 -04:00
parent d68c6db9ab
commit 01734e5449
46 changed files with 4241 additions and 52 deletions

View File

@@ -98,6 +98,37 @@ export function badges(panel) {
return list.map((b) => ({ label: b.label, color: b.color }))
}
// Header status rollup for a card. A panel opts in with
// status: { key, levels: { <rowvalue>: 'critical'|'warning'|'ok'|'unknown' } }.
// The card header then shows a single green/yellow/red dot reflecting the worst
// row: any critical -> red, else any warning -> yellow, else ok -> green. A
// value not in levels is treated as unknown and does not raise the level.
export const STATUS_RANK = { ok: 1, unknown: 1, warning: 2, critical: 3 }
export const STATUS_COLOR = {
ok: '#2dce89',
warning: '#ffc107',
critical: '#f5365c',
unknown: '#6c757d',
}
export function rollupStatus(panel) {
const status = panel && panel.status
if (!status || !status.key) return null
const items = rows(panel)
if (!items.length) return null
const levels = status.levels || {}
let worst = 'ok'
let sawKnown = false
for (const item of items) {
const level = levels[item[status.key]]
if (!level) continue
sawKnown = true
if (STATUS_RANK[level] > STATUS_RANK[worst]) worst = level
}
if (!sawKnown) return null
return { level: worst, color: STATUS_COLOR[worst] || STATUS_COLOR.unknown }
}
// tabs: [{ label, fields: [{label, value, mono}] }]. One section is shown at a
// time, so a panel with 30 values stays the height of its largest section
// instead of dragging one multicol column far past the other.
@@ -114,6 +145,30 @@ export function tabSections(panel) {
.filter((s) => s.fields.length > 0)
}
// Inline row action for a list panel. A monitored services/processes row may
// carry action: { running, start, stop, status } when it has a registered
// command (e.g. MTConnect). The card renders a single toggle on that row: when
// running it offers Stop, otherwise Start. Rows with no action are badge-only.
// commandendpoint on the panel is where the enqueue POST goes.
export function rowAction(panel, item) {
const action = item && item.action
if (!action || (!action.start && !action.stop)) return null
const running = action.running === true
return {
running,
// The action name to enqueue for the primary toggle button.
toggle: running ? action.stop : action.start,
toggleLabel: running ? 'Stop' : 'Start',
// status is a non-mutating probe, always available when declared.
status: action.status || null,
// Generic actions (service_*/process_*) carry the subject to act on; fixed
// actions (mtconnect_*) leave this null.
target: action.target || null,
// The declared enqueue endpoint template; the caller substitutes assetid.
endpoint: panel.commandendpoint || null,
}
}
export function panelVisible(panel) {
if (panel.render === 'tabs') return tabSections(panel).length > 0
if (panel.render === 'list') return rows(panel).length > 0 || !!panel.empty