Alternative Alarm Status Table Styling - Use Pills for Priority and State columns
Disclaimer: I haven’t tested any of this all that thoroughly yet!
I had a request from a customer to change the alarm status table styles. This was a bit of fun (and headache).
This requires CSS and a bit of JS injection (required for the splitting of the State values).
It has a number of parts:
-
Alarm Status Table component which contains specific rowStyles
Alarm Status Table - Copy JSON and Paste into a Flex Container
[ { "type": "ia.display.alarmstatustable", "version": 0, "props": { "refreshRate": 2000, "toolbar": { "enableShelvedTab": false, "enableFilter": false, "toggleableFilter": false, "enablePreFilters": false, "enableConfiguration": false }, "filters": { "active": { "priorities": { "diagnostic": true } } }, "rowStyles": { "activeUnacked": { "base": { "backgroundColor": "white", "color": "hsl(0 0% 5%)", "maxHeight": "3rem", "minHeight": "3rem" }, "priorities": { "low": { "backgroundColor": "white" }, "medium": { "backgroundColor": "white" }, "high": { "backgroundColor": "white" }, "critical": { "backgroundColor": "white" } } }, "activeAcked": { "base": { "backgroundColor": "white", "color": "hsl(0 0% 5%)", "fontWeight": "normal", "maxHeight": "3rem", "minHeight": "3rem" }, "priorities": { "low": { "backgroundColor": "white" }, "medium": { "backgroundColor": "white" }, "high": { "backgroundColor": "white" }, "critical": { "backgroundColor": "white" } } }, "clearUnacked": { "base": { "backgroundColor": "white", "fontWeight": "lighter", "color": "hsl(0 0% 70%)", "maxHeight": "3rem", "minHeight": "3rem" }, "priorities": { "diagnostic": { "backgroundColor": "white", "color": "hsl(0 0% 70%)" }, "low": { "backgroundColor": "white" }, "medium": { "backgroundColor": "white" }, "high": { "backgroundColor": "white" }, "critical": { "backgroundColor": "white" } } }, "clearAcked": { "base": { "backgroundColor": "white", "color": "hsl(0 0% 70%)", "fontWeight": "lighter", "maxHeight": "3rem", "minHeight": "3rem" }, "priorities": { "diagnostic": { "color": "hsl(0 0% 70%)" }, "low": { "backgroundColor": "white" }, "medium": { "backgroundColor": "white" }, "high": { "backgroundColor": "white" }, "critical": { "backgroundColor": "white" } } } }, "dateFormat": "DD MMM yyyy HH:mm z", "activeSortOrder": [ "activeTime" ], "columns": { "active": { "activeTime": { "sort": "descending", "width": 230, "strictWidth": true, "order": 3 }, "displayPath": { "width": 350, "strictWidth": true }, "priority": { "sort": "none", "width": 160, "strictWidth": true, "order": 0 }, "state": { "sort": "none", "width": 320, "strictWidth": true, "order": 4 }, "source": { "enabled": false, "width": 240, "order": 3 }, "name": { "enabled": false, "order": 2 } } }, "columnsAssociated": { "active": [ { "field": "Description", "enabled": true, "width": "", "strictWidth": false, "sort": "none", "order": 2 } ] } }, "meta": { "name": "AlarmStatusTable" }, "position": { "grow": 1, "basis": "400px" }, "custom": {} } ] -
Markdown Javascript injector component which converts the State column text into separate components and adds them into a flex
Markdown JS Injector (State-only mods) - Copy JSON and Paste into the View containing the AST
[ { "type": "ia.display.markdown", "version": 0, "props": { "style": { "flex": "--neutral-40" }, "markdown": { "escapeHtml": false } }, "meta": { "name": "JSInject_ConvertStateColToPillBoxes" }, "position": { "shrink": 0, "basis": 0 }, "custom": { "inlineJavascript": "(() => {\n const styleAlarmStates = () => {\n document.querySelectorAll(\".ia_table__cell[data-column-id=state]:not(.ia_table__head__header__cell) .content > div\").forEach(el => {\n if (el.querySelector(\".custom_alarmStatusTable__stateCol_pill\"))\n return;\n const text = el.textContent.trim();\n if (!text)\n return;\n const parts = text.split(\",\").map(s => s.trim());\n const classMap = {\n \"Active\": \"custom_alarmStatusTable__stateCol_active\",\n \"Cleared\": \"custom_alarmStatusTable__stateCol_cleared\",\n \"Acknowledged\": \"custom_alarmStatusTable__stateCol_ack\",\n \"Unacknowledged\": \"custom_alarmStatusTable__stateCol_unack\"\n };\n const pills = parts.map(part => {\n const stateClass = classMap[part] || \"\";\n return \"<span class=\\\"custom_alarmStatusTable__stateCol_pill \" + stateClass + \"\\\">\" + part + \"</span>\";\n }).join(\"\");\n el.innerHTML = \"<div class=\\\"custom_alarmStatusTable__stateCol_container\\\">\" + pills + \"</div>\";\n });\n };\n const setupObserver = () => {\n let rafId;\n const observer = new MutationObserver(() => {\n if (rafId)\n return;\n rafId = requestAnimationFrame(() => {\n styleAlarmStates();\n rafId = null;\n });\n });\n const tableContainer = document.querySelector(\".ReactVirtualized__Grid__innerScrollContainer\");\n if (tableContainer) {\n styleAlarmStates();\n observer.observe(tableContainer, {\n childList: true,\n attributes: true,\n subtree: true,\n attributeFilter: [\"title\", \"style\"]\n });\n }\n };\n setupObserver();\n if (!document.querySelector(\".ReactVirtualized__Grid__innerScrollContainer\")) {\n const waitObserver = new MutationObserver(() => {\n if (document.querySelector(\".ReactVirtualized__Grid__innerScrollContainer\")) {\n waitObserver.disconnect();\n setupObserver();\n }\n });\n waitObserver.observe(document.body, {\n childList: true,\n subtree: true\n });\n }\n})();\n" }, "propConfig": { "props.source": { "binding": { "config": { "struct": { "script": "{this.custom.inlineJavascript}" }, "waitOnAll": true }, "transforms": [ { "code": "\tcode = \"<img style='display:none' src='/favicon.ico' onload=\\\"\" + value.script.replace('\"', '"') + '\\\"></img>'\n\t\n\tcode = code.replace(\"\\n\", \"\").replace(\"\\t\", \"\").replace(\"\\n\", \"\").replace(\"\\r\", \"\").replace(\"\\r\", \"\").replace(\" \", \" \").replace(\" \", \" \").replace(\" \", \" \").replace(\" \", \" \")\n\t\n\treturn code", "type": "script" } ], "type": "expr-struct" } } } } ] -
CSS to add to the Advanced Stylesheet
Stylesheet CSS to define the: alarm priority colours and the state pill styles
/* === Setup the standard colour variables ================================================================================= */ :root { /** Format: --c-<area>-<prop><-modifier>, where 'c' means 'colour' **/ /* Alarm Priority Colours */ --c-alarm-critical: hsl(5 95% 65%); /* red */ --c-alarm-critical-text: white; --c-alarm-high: hsl(30, 98%, 55%); /* orange */ --c-alarm-high-text: white; --c-alarm-medium: hsl(50, 98%, 71%); /* yellow */ --c-alarm-medium-text: hsl(0, 0%, 5%); --c-alarm-low: hsl(200, 88%, 51%); /* blue */ --c-alarm-low-text: white; --c-alarm-diagnostic: hsl(0, 0%, 75%); /* grey */ --c-alarm-diagnostic-text: white; } /* === Alarm Status Table Styles =================================================================== */ /* Allow cell text to word wrap */ .alarmStatusTable .ia_table__cell .content div { white-space: pre-wrap; } /* Make the Priority column show a pill-box with the priority colour in the background */ .alarmStatusTable .ia_table__cell[data-column-id="priority"] .content:has([title]){ width: 7rem; border-radius: 50vh; height: 1.7rem; padding: 0.2rem 0.5rem; text-align: center; font-weight: normal; text-transform: uppercase; justify-content: center !important; align-items: center; } .alarmStatusTable .ia_table__cell[data-column-id="priority"] .content:has([title="Critical"]) { background-color: var(--c-alarm-critical); color: var(--c-alarm-critical-text); } .alarmStatusTable .ia_table__cell[data-column-id="priority"] .content:has([title="High"]) { background-color: var(--c-alarm-high); color: var(--c-alarm-high-text); } .alarmStatusTable .ia_table__cell[data-column-id="priority"] .content:has([title="Medium"]) { background-color: var(--c-alarm-medium); color: var(--c-alarm-medium-text); } .alarmStatusTable .ia_table__cell[data-column-id="priority"] .content:has([title="Low"]) { background-color: var(--c-alarm-low); color: var(--c-alarm-low-text); } .alarmStatusTable .ia_table__cell[data-column-id="priority"] .content:has([title="Diagnostic"]) { background-color: var(--c-alarm-diagnostic); color: var(--c-alarm-diagnostic-text); } /* defines the flex container housing the two state pills */ .custom_alarmStatusTable__stateCol_container { display: flex; gap: 0.3rem; align-items: center;; } /* styles for the pill containers housing the active/cleared and ack/unack states */ .custom_alarmStatusTable__stateCol_pill { border-top-left-radius: 50vh; border-top-right-radius: 50vh; border-bottom-left-radius: 50vh; border-bottom-right-radius: 50vh; margin: ;height: 150%; padding: 0.2rem 0.5rem; text-transform: uppercase; } /* styles for the ack state */ .custom_alarmStatusTable__stateCol_ack { background-color: #C0C1BE; color: hsl(0 0% 5%); font-weight: normal; } /* styles for the unack state */ .custom_alarmStatusTable__stateCol_unack { background-color: #FFC700; color: hsl(0 0% 5%); font-weight: normal; } /* styles for the active state */ .custom_alarmStatusTable__stateCol_active { background-color: #98E737; color: hsl(0 0% 5%); font-weight: normal; } /* styles for the cleared state */ .custom_alarmStatusTable__stateCol_cleared { background-color: #C0C1BE; color: hsl(0 0% 5%); font-weight: normal; }
If you just want to import a single View, here’s the project export in 8.3.2. You’ll still need to copy in the styling into the adv. stylesheet though.
AlarmStatusTable-AlternateStyling.zip (44.2 KB)
They also wanted the datetime formatted differently as well, in case you want this too. If so, replace the Markdown above with this version:
Markdown JS Injector (State & Datetime mods) - Copy JSON and Paste into the View containing the AST
[
{
"type": "ia.display.markdown",
"version": 0,
"props": {
"style": {
"flex": "--neutral-40"
},
"markdown": {
"escapeHtml": false
}
},
"meta": {
"name": "JSInject_ConvertStateColToPillBoxes"
},
"position": {
"shrink": 0,
"basis": 0
},
"custom": {
"inlineJavascript": "(() => {\n const styleAlarmStates = () => {\n document.querySelectorAll(\".ia_table__cell[data-column-id=state]:not(.ia_table__head__header__cell) .content > div\").forEach(el => {\n if (el.querySelector(\".custom_alarmStatusTable__stateCol_pill\"))\n return;\n const text = el.textContent.trim();\n if (!text)\n return;\n const parts = text.split(\",\").map(s => s.trim());\n const classMap = {\n \"Active\": \"custom_alarmStatusTable__stateCol_active\",\n \"Cleared\": \"custom_alarmStatusTable__stateCol_cleared\",\n \"Acknowledged\": \"custom_alarmStatusTable__stateCol_ack\",\n \"Unacknowledged\": \"custom_alarmStatusTable__stateCol_unack\"\n };\n const pills = parts.map(part => {\n const stateClass = classMap[part] || \"\";\n return \"<span class=\\\"custom_alarmStatusTable__stateCol_pill \" + stateClass + \"\\\">\" + part + \"</span>\";\n }).join(\"\");\n el.innerHTML = \"<div class=\\\"custom_alarmStatusTable__stateCol_container\\\">\" + pills + \"</div>\";\n });\n };\n\n const formatDateTimeCells = () => {\n document.querySelectorAll('.alarmStatusTable .ia_table__cell:not(.ia_table__head__header__cell) .content > div').forEach(div => {\n const title = div.getAttribute('title');\n const datetimeRegex = /^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d{3}[+-]\\d{4}$/;\n \n if (title && datetimeRegex.test(title) && !div.querySelector('div[style*=\"flex\"]')) {\n const text = div.textContent.trim();\n const parts = text.split(' ');\n \n if (parts.length >= 5) {\n const date = parts.slice(0, 3).join(' ').trim();\n const time = parts.slice(3).join(' ').trim();\n \n div.innerHTML = `\n <div style=\"display: flex; flex-direction: column; gap: 2px; align-items: flex-end;\">\n <div style=\"font-weight: bold;\">${date}</div>\n <div style=\"font-size: 0.9em; color: #666;\">${time}</div>\n </div>\n `;\n }\n }\n });\n };\n\n const setupObserver = () => {\n let rafId;\n const observer = new MutationObserver(() => {\n if (rafId)\n return;\n rafId = requestAnimationFrame(() => {\n styleAlarmStates();\n formatDateTimeCells();\n rafId = null;\n });\n });\n const tableContainer = document.querySelector(\".ReactVirtualized__Grid__innerScrollContainer\");\n if (tableContainer) {\n styleAlarmStates();\n formatDateTimeCells();\n observer.observe(tableContainer, {\n childList: true,\n attributes: true,\n subtree: true,\n attributeFilter: [\"title\", \"style\"]\n });\n }\n };\n setupObserver();\n if (!document.querySelector(\".ReactVirtualized__Grid__innerScrollContainer\")) {\n const waitObserver = new MutationObserver(() => {\n if (document.querySelector(\".ReactVirtualized__Grid__innerScrollContainer\")) {\n waitObserver.disconnect();\n setupObserver();\n }\n });\n waitObserver.observe(document.body, {\n childList: true,\n subtree: true\n });\n }\n})();"
},
"propConfig": {
"props.source": {
"binding": {
"config": {
"struct": {
"script": "{this.custom.inlineJavascript}"
},
"waitOnAll": true
},
"transforms": [
{
"code": "\tcode = \"<img style='display:none' src='/favicon.ico' onload=\\\"\" + value.script.replace('\"', '"') + '\\\"></img>'\n\t\n\tcode = code.replace(\"\\n\", \"\").replace(\"\\t\", \"\").replace(\"\\n\", \"\").replace(\"\\r\", \"\").replace(\"\\r\", \"\").replace(\" \", \" \").replace(\" \", \" \").replace(\" \", \" \").replace(\" \", \" \")\n\t\n\treturn code",
"type": "script"
}
],
"type": "expr-struct"
}
}
}
}
]

