Phase 2: dashboard tabs + popups, chart fixes, shell relocation
Built via parallel Workflow (5 builders + adversarial verifiers), then live-rendered and fixed in a headless browser against the running gateway. Tabs (PrimeControls/Tabs/*): - Overview: priority cards, KPI status row, alarm-rate timeline, insights, top-5 - Analysis: hour x day heatmap, priority donut vs ISA 80/15/5, MTTA/MTTR, floods - BadActors: chattering/standing/fleeting tables, focus chip, AlarmDetail wiring - Journal: paginated getJournalPage table, CSV export, EventDetail wiring Popups (PrimeControls/Popups/*): HealthScore, ShiftReport, AlarmDetail, EventDetail Chart fixes (verified rendering in-browser): - Overview timeline: seeded dataSources.rate, switched to static fills, split flood bins into their own red stacked series (per-point deriveFieldsFromData fill was crashing render with React.cloneElement null). - BadActors: removed the dual-axis Pareto XY chart (scaling was unusable); replaced with a ranked "Top Sources" table (share-of-total progress bar + cumulative %). Same insight, no axis scaling to break. Structural / hygiene: - Relocated Header + FilterBar from Dashboard/ to Shell/ and updated the two embed paths. A Perspective view cannot resolve sub-views nested inside another view's folder, so both were rendering as "view does not exist" placeholders. - Set persistent:false on all view input params. persistent:true caused the gateway to bake the live bundle (with sim tag paths) into Tabs/Analysis on render; nulled that baked data and reset the session query range to 0. lint_project.py: 0 failures / 0 warnings. Verified in-browser: Overview, BadActors. NOT yet live-verified: Analysis, Journal, and the four popups (schema-verified only). Header content is next phase (renders now, but a benign cloneElement console warning remains). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,55 +1,463 @@
|
||||
{
|
||||
"custom": {},
|
||||
"custom": {
|
||||
"page": 0,
|
||||
"pageData": null
|
||||
},
|
||||
"params": {
|
||||
"bundle": null
|
||||
},
|
||||
"propConfig": {
|
||||
"custom.pageData": {
|
||||
"binding": {
|
||||
"config": {
|
||||
"expression": "{session.custom.PrimeControls.query.refreshToken} + '|' + {session.custom.PrimeControls.query.startMs} + '|' + {session.custom.PrimeControls.query.endMs} + '|' + {view.custom.page}"
|
||||
},
|
||||
"transforms": [
|
||||
{
|
||||
"code": "\tsafe = {'rows': [], 'total': 0, 'page': 0, 'page_size': 50, 'truncated': False, 'error': None}\n\ttry:\n\t\tq = self.session.custom.PrimeControls.query\n\t\tif not q.startMs or not q.endMs:\n\t\t\treturn safe\n\t\ttry:\n\t\t\tpage = max(0, int(self.view.custom.page or 0))\n\t\texcept:\n\t\t\tpage = 0\n\t\treturn PrimeControls.alarms.getJournalPage(q.startMs, q.endMs, {'priorities': list(q.priorities), 'areas': list(q.areas), 'states': list(q.states), 'search': q.search}, page, 50)\n\texcept:\n\t\timport sys\n\t\tsafe['error'] = '%s' % (sys.exc_info()[1],)\n\t\treturn safe",
|
||||
"type": "script"
|
||||
}
|
||||
],
|
||||
"type": "expr"
|
||||
}
|
||||
},
|
||||
"params.bundle": {
|
||||
"paramDirection": "input",
|
||||
"persistent": true
|
||||
"persistent": false
|
||||
}
|
||||
},
|
||||
"props": {
|
||||
"defaultSize": {
|
||||
"height": 800,
|
||||
"width": 1600
|
||||
}
|
||||
},
|
||||
"props": {},
|
||||
"root": {
|
||||
"children": [
|
||||
{
|
||||
"meta": {
|
||||
"name": "ph"
|
||||
},
|
||||
"props": {
|
||||
"style": {
|
||||
"classes": "PrimeControls/Empty"
|
||||
"children": [
|
||||
{
|
||||
"meta": {
|
||||
"name": "lblTotal"
|
||||
},
|
||||
"position": {
|
||||
"shrink": 0
|
||||
},
|
||||
"propConfig": {
|
||||
"props.text": {
|
||||
"binding": {
|
||||
"config": {
|
||||
"path": "view.custom.pageData"
|
||||
},
|
||||
"transforms": [
|
||||
{
|
||||
"code": "\ttry:\n\t\ttotal = int(value['total'] or 0)\n\texcept:\n\t\ttotal = 0\n\ttry:\n\t\ttxt = '%s events' % PrimeControls.fmt.num(total)\n\texcept:\n\t\ttxt = '%d events' % total\n\ttry:\n\t\tif value['truncated']:\n\t\t\ttxt = txt + ' (truncated)'\n\texcept:\n\t\tpass\n\treturn txt",
|
||||
"type": "script"
|
||||
}
|
||||
],
|
||||
"type": "property"
|
||||
}
|
||||
}
|
||||
},
|
||||
"props": {
|
||||
"textStyle": {
|
||||
"fontWeight": "bold"
|
||||
}
|
||||
},
|
||||
"type": "ia.display.label"
|
||||
},
|
||||
"text": "Journal - built in Phase 2"
|
||||
},
|
||||
"type": "ia.display.label"
|
||||
},
|
||||
{
|
||||
"meta": {
|
||||
"name": "evt"
|
||||
},
|
||||
"propConfig": {
|
||||
"props.text": {
|
||||
"binding": {
|
||||
"config": {
|
||||
"path": "view.params.bundle"
|
||||
{
|
||||
"meta": {
|
||||
"name": "lblError"
|
||||
},
|
||||
"position": {
|
||||
"shrink": 1
|
||||
},
|
||||
"propConfig": {
|
||||
"position.display": {
|
||||
"binding": {
|
||||
"config": {
|
||||
"path": "view.custom.pageData"
|
||||
},
|
||||
"transforms": [
|
||||
{
|
||||
"code": "\ttry:\n\t\treturn bool(value['error'])\n\texcept:\n\t\treturn False",
|
||||
"type": "script"
|
||||
}
|
||||
],
|
||||
"type": "property"
|
||||
}
|
||||
},
|
||||
"transforms": [
|
||||
{
|
||||
"code": "\ttry:\n\t\treturn 'bundle: %s events in range' % value['meta']['event_count']\n\texcept:\n\t\treturn 'bundle: not loaded'",
|
||||
"props.text": {
|
||||
"binding": {
|
||||
"config": {
|
||||
"path": "view.custom.pageData"
|
||||
},
|
||||
"transforms": [
|
||||
{
|
||||
"code": "\ttry:\n\t\treturn value['error'] or ''\n\texcept:\n\t\treturn ''",
|
||||
"type": "script"
|
||||
}
|
||||
],
|
||||
"type": "property"
|
||||
}
|
||||
}
|
||||
},
|
||||
"props": {
|
||||
"style": {
|
||||
"classes": "PrimeControls/Text/Muted",
|
||||
"color": "#D64550"
|
||||
}
|
||||
},
|
||||
"type": "ia.display.label"
|
||||
},
|
||||
{
|
||||
"children": [],
|
||||
"meta": {
|
||||
"name": "spacer"
|
||||
},
|
||||
"position": {
|
||||
"grow": 1
|
||||
},
|
||||
"props": {},
|
||||
"type": "ia.container.flex"
|
||||
},
|
||||
{
|
||||
"events": {
|
||||
"component": {
|
||||
"onActionPerformed": {
|
||||
"config": {
|
||||
"script": "\ttry:\n\t\tp = int(self.view.custom.page or 0)\n\texcept:\n\t\tp = 0\n\tif p > 0:\n\t\tself.view.custom.page = p - 1"
|
||||
},
|
||||
"scope": "G",
|
||||
"type": "script"
|
||||
}
|
||||
],
|
||||
"type": "property"
|
||||
}
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"name": "btnPrev"
|
||||
},
|
||||
"position": {
|
||||
"basis": "80px",
|
||||
"shrink": 0
|
||||
},
|
||||
"propConfig": {
|
||||
"props.enabled": {
|
||||
"binding": {
|
||||
"config": {
|
||||
"path": "view.custom.pageData"
|
||||
},
|
||||
"transforms": [
|
||||
{
|
||||
"code": "\ttry:\n\t\treturn int(value['page'] or 0) > 0\n\texcept:\n\t\treturn False",
|
||||
"type": "script"
|
||||
}
|
||||
],
|
||||
"type": "property"
|
||||
}
|
||||
}
|
||||
},
|
||||
"props": {
|
||||
"primary": false,
|
||||
"text": "Prev"
|
||||
},
|
||||
"type": "ia.input.button"
|
||||
},
|
||||
{
|
||||
"meta": {
|
||||
"name": "lblPage"
|
||||
},
|
||||
"position": {
|
||||
"shrink": 0
|
||||
},
|
||||
"propConfig": {
|
||||
"props.text": {
|
||||
"binding": {
|
||||
"config": {
|
||||
"path": "view.custom.pageData"
|
||||
},
|
||||
"transforms": [
|
||||
{
|
||||
"code": "\ttry:\n\t\ttotal = int(value['total'] or 0)\n\t\tsize = int(value['page_size'] or 50)\n\t\tpage = int(value['page'] or 0)\n\texcept:\n\t\ttotal = 0\n\t\tsize = 50\n\t\tpage = 0\n\tif size < 1:\n\t\tsize = 50\n\tpages = (total + size - 1) // size\n\tif pages < 1:\n\t\tpages = 1\n\ttry:\n\t\treturn 'Page %s of %s' % (PrimeControls.fmt.num(page + 1), PrimeControls.fmt.num(pages))\n\texcept:\n\t\treturn 'Page %d of %d' % (page + 1, pages)",
|
||||
"type": "script"
|
||||
}
|
||||
],
|
||||
"type": "property"
|
||||
}
|
||||
}
|
||||
},
|
||||
"props": {
|
||||
"style": {
|
||||
"classes": "PrimeControls/Text/Muted"
|
||||
},
|
||||
"textStyle": {
|
||||
"textAlign": "center"
|
||||
}
|
||||
},
|
||||
"type": "ia.display.label"
|
||||
},
|
||||
{
|
||||
"events": {
|
||||
"component": {
|
||||
"onActionPerformed": {
|
||||
"config": {
|
||||
"script": "\ttry:\n\t\tp = int(self.view.custom.page or 0)\n\texcept:\n\t\tp = 0\n\ttry:\n\t\td = self.view.custom.pageData\n\t\ttotal = int(d['total'] or 0)\n\t\tsize = int(d['page_size'] or 50)\n\texcept:\n\t\ttotal = 0\n\t\tsize = 50\n\tif size < 1:\n\t\tsize = 50\n\tpages = (total + size - 1) // size\n\tif pages < 1:\n\t\tpages = 1\n\tif p < pages - 1:\n\t\tself.view.custom.page = p + 1"
|
||||
},
|
||||
"scope": "G",
|
||||
"type": "script"
|
||||
}
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"name": "btnNext"
|
||||
},
|
||||
"position": {
|
||||
"basis": "80px",
|
||||
"shrink": 0
|
||||
},
|
||||
"propConfig": {
|
||||
"props.enabled": {
|
||||
"binding": {
|
||||
"config": {
|
||||
"path": "view.custom.pageData"
|
||||
},
|
||||
"transforms": [
|
||||
{
|
||||
"code": "\ttry:\n\t\ttotal = int(value['total'] or 0)\n\t\tsize = int(value['page_size'] or 50)\n\t\tpage = int(value['page'] or 0)\n\texcept:\n\t\treturn False\n\tif size < 1:\n\t\tsize = 50\n\treturn (page + 1) * size < total",
|
||||
"type": "script"
|
||||
}
|
||||
],
|
||||
"type": "property"
|
||||
}
|
||||
}
|
||||
},
|
||||
"props": {
|
||||
"primary": false,
|
||||
"text": "Next"
|
||||
},
|
||||
"type": "ia.input.button"
|
||||
},
|
||||
{
|
||||
"events": {
|
||||
"component": {
|
||||
"onActionPerformed": {
|
||||
"config": {
|
||||
"script": "\ttry:\n\t\tq = self.session.custom.PrimeControls.query\n\t\tcsv = PrimeControls.alarms.journalCsv(q.startMs, q.endMs, {'priorities': list(q.priorities), 'areas': list(q.areas), 'states': list(q.states), 'search': q.search})\n\t\tsystem.perspective.download('alarm_journal.csv', csv)\n\texcept:\n\t\tpass"
|
||||
},
|
||||
"scope": "G",
|
||||
"type": "script"
|
||||
}
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"name": "btnExport"
|
||||
},
|
||||
"position": {
|
||||
"basis": "110px",
|
||||
"shrink": 0
|
||||
},
|
||||
"props": {
|
||||
"primary": true,
|
||||
"text": "Export CSV"
|
||||
},
|
||||
"type": "ia.input.button"
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"name": "toolbar"
|
||||
},
|
||||
"position": {
|
||||
"shrink": 0
|
||||
},
|
||||
"props": {
|
||||
"alignItems": "center",
|
||||
"style": {
|
||||
"classes": "PrimeControls/Text/Muted"
|
||||
"classes": "PrimeControls/Toolbar",
|
||||
"gap": "8px"
|
||||
}
|
||||
},
|
||||
"type": "ia.display.label"
|
||||
"type": "ia.container.flex"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"events": {
|
||||
"component": {
|
||||
"onRowClick": {
|
||||
"config": {
|
||||
"script": "\trow = event.value\n\tevt = {}\n\ttry:\n\t\tfor k in row:\n\t\t\tv = row[k]\n\t\t\ttry:\n\t\t\t\tevt[k] = v['value']\n\t\t\texcept:\n\t\t\t\tevt[k] = v\n\texcept:\n\t\tevt = {}\n\ttry:\n\t\tif evt:\n\t\t\tsystem.perspective.openPopup('PC_EventDetail', 'PrimeControls/Popups/EventDetail', params = {'event': evt}, showCloseIcon = True, draggable = True)\n\texcept:\n\t\tpass"
|
||||
},
|
||||
"scope": "G",
|
||||
"type": "script"
|
||||
}
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"name": "tblJournal"
|
||||
},
|
||||
"position": {
|
||||
"grow": 1
|
||||
},
|
||||
"propConfig": {
|
||||
"position.display": {
|
||||
"binding": {
|
||||
"config": {
|
||||
"path": "view.custom.pageData"
|
||||
},
|
||||
"transforms": [
|
||||
{
|
||||
"code": "\ttry:\n\t\treturn len(value['rows'] or []) > 0\n\texcept:\n\t\treturn False",
|
||||
"type": "script"
|
||||
}
|
||||
],
|
||||
"type": "property"
|
||||
}
|
||||
},
|
||||
"props.data": {
|
||||
"binding": {
|
||||
"config": {
|
||||
"path": "view.custom.pageData"
|
||||
},
|
||||
"transforms": [
|
||||
{
|
||||
"code": "\tstate_cls = {'active': 'PrimeControls/State/Active', 'ack': 'PrimeControls/State/Acked', 'clear': 'PrimeControls/State/Cleared'}\n\tprio_cls = {'Diagnostic': 'PrimeControls/PrioritySoft/Diagnostic', 'Low': 'PrimeControls/PrioritySoft/Low', 'Medium': 'PrimeControls/PrioritySoft/Medium', 'High': 'PrimeControls/PrioritySoft/High', 'Critical': 'PrimeControls/PrioritySoft/Critical'}\n\ttry:\n\t\trows = value['rows'] or []\n\texcept:\n\t\trows = []\n\tout = []\n\tfor r in rows:\n\t\ttry:\n\t\t\tsc = state_cls.get(r['state'], '')\n\t\t\tpc = prio_cls.get(r['priority_name'], '')\n\t\t\tout.append({'time_label': r['time_label'] or '', 'state_label': {'value': r['state_label'] or '', 'style': {'classes': sc}}, 'priority_name': {'value': r['priority_name'] or '', 'style': {'classes': pc}}, 'label': r['label'] or '', 'area': r['area'] or '', 'ack_user': r['ack_user'] or '', 'id': r['id'] or '', 'time_ms': r['time_ms'] or 0, 'source': r['source'] or '', 'state': r['state'] or '', 'priority': r['priority']})\n\t\texcept:\n\t\t\tpass\n\treturn out",
|
||||
"type": "script"
|
||||
}
|
||||
],
|
||||
"type": "property"
|
||||
}
|
||||
}
|
||||
},
|
||||
"props": {
|
||||
"columns": [
|
||||
{
|
||||
"field": "time_label",
|
||||
"header": {
|
||||
"title": "Time"
|
||||
},
|
||||
"sortable": false,
|
||||
"strictWidth": true,
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"field": "state_label",
|
||||
"header": {
|
||||
"title": "State"
|
||||
},
|
||||
"justify": "center",
|
||||
"sortable": false,
|
||||
"strictWidth": true,
|
||||
"width": 110
|
||||
},
|
||||
{
|
||||
"field": "priority_name",
|
||||
"header": {
|
||||
"title": "Priority"
|
||||
},
|
||||
"justify": "center",
|
||||
"sortable": false,
|
||||
"strictWidth": true,
|
||||
"width": 110
|
||||
},
|
||||
{
|
||||
"field": "label",
|
||||
"header": {
|
||||
"title": "Alarm"
|
||||
},
|
||||
"sortable": false
|
||||
},
|
||||
{
|
||||
"field": "area",
|
||||
"header": {
|
||||
"title": "Area"
|
||||
},
|
||||
"sortable": false,
|
||||
"strictWidth": true,
|
||||
"width": 160
|
||||
},
|
||||
{
|
||||
"field": "ack_user",
|
||||
"header": {
|
||||
"title": "Ack By"
|
||||
},
|
||||
"sortable": false,
|
||||
"strictWidth": true,
|
||||
"width": 120
|
||||
},
|
||||
{
|
||||
"field": "id",
|
||||
"visible": false
|
||||
},
|
||||
{
|
||||
"field": "time_ms",
|
||||
"visible": false
|
||||
},
|
||||
{
|
||||
"field": "source",
|
||||
"visible": false
|
||||
},
|
||||
{
|
||||
"field": "state",
|
||||
"visible": false
|
||||
},
|
||||
{
|
||||
"field": "priority",
|
||||
"visible": false
|
||||
}
|
||||
],
|
||||
"pager": {
|
||||
"bottom": false,
|
||||
"top": false
|
||||
}
|
||||
},
|
||||
"type": "ia.display.table"
|
||||
},
|
||||
{
|
||||
"meta": {
|
||||
"name": "empty"
|
||||
},
|
||||
"position": {
|
||||
"grow": 1
|
||||
},
|
||||
"propConfig": {
|
||||
"position.display": {
|
||||
"binding": {
|
||||
"config": {
|
||||
"path": "view.custom.pageData"
|
||||
},
|
||||
"transforms": [
|
||||
{
|
||||
"code": "\ttry:\n\t\treturn len(value['rows'] or []) == 0\n\texcept:\n\t\treturn True",
|
||||
"type": "script"
|
||||
}
|
||||
],
|
||||
"type": "property"
|
||||
}
|
||||
}
|
||||
},
|
||||
"props": {
|
||||
"params": {
|
||||
"icon": "search",
|
||||
"message": "No events match the current filters"
|
||||
},
|
||||
"path": "PrimeControls/Components/EmptyState"
|
||||
},
|
||||
"type": "ia.display.view"
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"name": "cardTable"
|
||||
},
|
||||
"position": {
|
||||
"grow": 1
|
||||
},
|
||||
"props": {
|
||||
"direction": "column",
|
||||
"style": {
|
||||
"classes": "PrimeControls/Card"
|
||||
}
|
||||
},
|
||||
"type": "ia.container.flex"
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
@@ -58,11 +466,9 @@
|
||||
"props": {
|
||||
"direction": "column",
|
||||
"style": {
|
||||
"alignItems": "center",
|
||||
"classes": "PrimeControls/Card",
|
||||
"justifyContent": "center"
|
||||
"gap": "12px"
|
||||
}
|
||||
},
|
||||
"type": "ia.container.flex"
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user