diff --git a/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/Alarm-Views/RealTime/view.json b/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/Alarm-Views/RealTime/view.json index c4d2c05..589bff2 100644 --- a/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/Alarm-Views/RealTime/view.json +++ b/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/Alarm-Views/RealTime/view.json @@ -17,7 +17,7 @@ "system": { "onStartup": { "config": { - "script": "\tself.getChild(\"root\").getChild(\"TabContainer\").getChild(\"Hit_List\").getChild(\"Filters\").getChild(\"Time\").getChild(\"Dropdown\").props.value \u003d 30\n\tself.getChild(\"root\").getChild(\"TabContainer\").getChild(\"Historical_tab\").getChild(\"root\").getChild(\"Filters\").getChild(\"Time\").getChild(\"Dropdown\").props.value \u003d 30" + "script": "\tself.getChild(\"root\").getChild(\"TabContainer\").getChild(\"Active_tab\").getChild(\"FlexContainer\").getChild(\"FlexContainer\").getChild(\"Dropdown_0\").props.value \u003d \"medium\"\n\tself.getChild(\"root\").getChild(\"TabContainer\").getChild(\"Hit_List\").getChild(\"Filters\").getChild(\"Time\").getChild(\"Dropdown\").props.value \u003d 30\n\tself.getChild(\"root\").getChild(\"TabContainer\").getChild(\"Hit_List\").getChild(\"Filters\").getChild(\"Priority\").getChild(\"Dropdown\").props.value \u003d \"medium\"\n\tself.getChild(\"root\").getChild(\"TabContainer\").getChild(\"Historical_tab\").getChild(\"root\").getChild(\"Filters\").getChild(\"Time\").getChild(\"Dropdown\").props.value \u003d 30\n\tself.getChild(\"root\").getChild(\"TabContainer\").getChild(\"Historical_tab\").getChild(\"root\").getChild(\"Filters\").getChild(\"Priority\").getChild(\"Dropdown\").props.value \u003d \"medium\"" }, "scope": "G", "type": "script" @@ -56,15 +56,28 @@ "type": "ia.display.label" }, { - "custom": { - "Severity": "High", - "background_on": "true" + "meta": { + "name": "Label" }, + "position": { + "basis": "176px" + }, + "props": { + "style": { + "fontFamily": "Arial", + "fontWeight": "bold", + "textAlign": "center" + }, + "text": "Minumum Priority:" + }, + "type": "ia.display.label" + }, + { "events": { "component": { "onActionPerformed": { "config": { - "script": "\tpriority \u003d \u0027high\u0027\n\t# Copy and toggle the filter\n\tpriorities \u003d dict(self.parent.custom.priorities)\n\tpriorities[priority] \u003d not priorities.get(priority, False)\n\tself.parent.custom.priorities \u003d priorities\n\t\n\tself.custom.background_on \u003d \"true\" if priorities[priority] else \"false\"" + "script": "\tmessaging.message_handler.set_priority_filters(self)" }, "scope": "G", "type": "script" @@ -72,347 +85,47 @@ } }, "meta": { - "name": "Button_0" + "name": "Dropdown_0" }, "position": { - "basis": "120px" + "basis": "490px" }, "propConfig": { - "props.style.classes": { - "binding": { - "config": { - "expression": "if({this.custom.background_on}\u003d\"false\",0,\r\nif({session.custom.colours.colour_impaired},2,1))" - }, - "transforms": [ - { - "fallback": "", - "inputType": "scalar", - "mappings": [ - { - "input": 0, - "output": "" - }, - { - "input": 1, - "output": "Alarms-Styles/High" - }, - { - "input": 2, - "output": "Alarms-Styles/Alt-Colours/High" - } - ], - "outputType": "style-list", - "type": "map" - } - ], - "type": "expr" - } + "props.value": { + "persistent": false } }, "props": { - "image": { - "icon": { - "path": "material/priority_high" + "options": [ + { + "label": "All", + "value": "" + }, + { + "label": "Diagnostic", + "value": "diagnostic" + }, + { + "label": "Low", + "value": "low" + }, + { + "label": "Medium", + "value": "medium" + }, + { + "label": "High", + "value": "high" } + ], + "placeholder": { + "text": "" }, - "primary": false, "style": { "margin": 15 - }, - "text": "High" - }, - "scripts": { - "customMethods": [], - "extensionFunctions": null, - "messageHandlers": [ - { - "messageType": "reset-filters", - "pageScope": true, - "script": "\tbackground \u003d \"false\"\n\tseverity \u003d payload[\"reset\"]\n\tif severity \u003d\u003d \"false\":\n\t\tbackground \u003d \"false\"\n\telse:\n\t\tbackground \u003d \"true\"\n\tself.custom.background_on \u003d background", - "sessionScope": false, - "viewScope": false - } - ] - }, - "type": "ia.input.button" - }, - { - "meta": { - "name": "Label_1" - }, - "position": { - "basis": "10px" - }, - "type": "ia.display.label" - }, - { - "meta": { - "name": "Label_4" - }, - "position": { - "basis": "10px" - }, - "type": "ia.display.label" - }, - { - "custom": { - "Severity": "Medium", - "background_on": "true" - }, - "events": { - "component": { - "onActionPerformed": { - "config": { - "script": "\tpriority \u003d \u0027medium\u0027\n\t# Copy and toggle the filter\n\tpriorities \u003d dict(self.parent.custom.priorities)\n\tpriorities[priority] \u003d not priorities.get(priority, False)\n\tself.parent.custom.priorities \u003d priorities\n\t\n\tself.custom.background_on \u003d \"true\" if priorities[priority] else \"false\"" - }, - "scope": "G", - "type": "script" - } } }, - "meta": { - "name": "Button_1" - }, - "position": { - "basis": "120px" - }, - "propConfig": { - "props.style.classes": { - "binding": { - "config": { - "expression": "if({this.custom.background_on}\u003d\"false\",0,\r\nif({session.custom.colours.colour_impaired},2,1))" - }, - "transforms": [ - { - "fallback": "Buttons/PB_1", - "inputType": "scalar", - "mappings": [ - { - "input": 0, - "output": "" - }, - { - "input": 1, - "output": "Alarms-Styles/Medium" - }, - { - "input": 2, - "output": "Alarms-Styles/Alt-Colours/Medium" - } - ], - "outputType": "style-list", - "type": "map" - } - ], - "type": "expr" - } - } - }, - "props": { - "image": { - "icon": { - "path": "material/priority_high" - } - }, - "primary": false, - "style": { - "margin": 15 - }, - "text": "Medium" - }, - "scripts": { - "customMethods": [], - "extensionFunctions": null, - "messageHandlers": [ - { - "messageType": "reset-filters", - "pageScope": true, - "script": "\tbackground \u003d \"false\"\n\tseverity \u003d payload[\"reset\"]\n\tif severity \u003d\u003d \"false\":\n\t\tbackground \u003d \"false\"\n\telse:\n\t\tbackground \u003d \"true\"\n\tself.custom.background_on \u003d background", - "sessionScope": false, - "viewScope": false - } - ] - }, - "type": "ia.input.button" - }, - { - "meta": { - "name": "Label_2" - }, - "position": { - "basis": "10px" - }, - "type": "ia.display.label" - }, - { - "custom": { - "Severity": "Low", - "background_on": "false" - }, - "events": { - "component": { - "onActionPerformed": { - "config": { - "script": "\tpriority \u003d \u0027low\u0027\n\t# Copy and toggle the filter\n\tpriorities \u003d dict(self.parent.custom.priorities)\n\tpriorities[priority] \u003d not priorities.get(priority, False)\n\tself.parent.custom.priorities \u003d priorities\n\t\n\tself.custom.background_on \u003d \"true\" if priorities[priority] else \"false\"" - }, - "scope": "G", - "type": "script" - } - } - }, - "meta": { - "name": "Button_2" - }, - "position": { - "basis": "120px" - }, - "propConfig": { - "props.style.classes": { - "binding": { - "config": { - "expression": "if({this.custom.background_on}\u003d\"false\",0,\r\nif({session.custom.colours.colour_impaired},2,1))" - }, - "transforms": [ - { - "fallback": "Buttons/PB_1", - "inputType": "scalar", - "mappings": [ - { - "input": 0, - "output": "" - }, - { - "input": 1, - "output": "Alarms-Styles/Low" - }, - { - "input": 2, - "output": "Alarms-Styles/Alt-Colours/Low" - } - ], - "outputType": "style-list", - "type": "map" - } - ], - "type": "expr" - } - } - }, - "props": { - "image": { - "icon": { - "path": "material/low_priority" - } - }, - "primary": false, - "style": { - "margin": 15 - }, - "text": "Low" - }, - "scripts": { - "customMethods": [], - "extensionFunctions": null, - "messageHandlers": [ - { - "messageType": "reset-filters", - "pageScope": true, - "script": "\tbackground \u003d \"false\"\n\tseverity \u003d payload[\"reset\"]\n\tif severity \u003d\u003d \"false\":\n\t\tbackground \u003d \"false\"\n\telse:\n\t\tbackground \u003d \"true\"\n\tself.custom.background_on \u003d background", - "sessionScope": false, - "viewScope": false - } - ] - }, - "type": "ia.input.button" - }, - { - "meta": { - "name": "Label_3" - }, - "position": { - "basis": "10px" - }, - "type": "ia.display.label" - }, - { - "custom": { - "background_on": "false" - }, - "events": { - "component": { - "onActionPerformed": { - "config": { - "script": "\tpriority \u003d \u0027diagnostic\u0027\n\n\tpriorities \u003d dict(self.parent.custom.priorities)\n\tpriorities[priority] \u003d not priorities.get(priority, False)\n\tself.parent.custom.priorities \u003d priorities\n\t\n\tself.custom.background_on \u003d \"true\" if priorities[priority] else \"false\"" - }, - "scope": "G", - "type": "script" - } - } - }, - "meta": { - "name": "Button_3" - }, - "position": { - "basis": "120px" - }, - "propConfig": { - "props.style.classes": { - "binding": { - "config": { - "expression": "if({this.custom.background_on}\u003d\"false\",0,\r\nif({session.custom.colours.colour_impaired},2,1))" - }, - "transforms": [ - { - "fallback": "Buttons/PB_1", - "inputType": "scalar", - "mappings": [ - { - "input": 0, - "output": "" - }, - { - "input": 1, - "output": "Alarms-Styles/Diagnostic" - }, - { - "input": 2, - "output": "Alarms-Styles/Alt-Colours/Diagnostic" - } - ], - "outputType": "style-list", - "type": "map" - } - ], - "type": "expr" - } - } - }, - "props": { - "image": { - "icon": { - "path": "material/warning" - } - }, - "primary": false, - "style": { - "margin": 15 - }, - "text": "Diagnostic" - }, - "scripts": { - "customMethods": [], - "extensionFunctions": null, - "messageHandlers": [ - { - "messageType": "reset-filters", - "pageScope": true, - "script": "\t# implement your handler here\n\tbackground \u003d \"false\"\n\tseverity \u003d payload[\"reset\"]\n\tif severity \u003d\u003d \"false\":\n\t\tbackground \u003d \"false\"\n\telse:\n\t\tbackground \u003d \"true\"\n\tself.custom.background_on \u003d background", - "sessionScope": false, - "viewScope": false - } - ] - }, - "type": "ia.input.button" + "type": "ia.input.dropdown" }, { "meta": { @@ -502,6 +215,42 @@ "basis": "881px", "shrink": 0 }, + "propConfig": { + "custom.FilterStatus": { + "binding": { + "config": { + "path": "./Dropdown_0.props.value" + }, + "transforms": [ + { + "fallback": 0, + "inputType": "scalar", + "mappings": [ + { + "input": "high", + "output": 3 + }, + { + "input": "medium", + "output": 2 + }, + { + "input": "low", + "output": 1 + }, + { + "input": "diagnostic", + "output": 0 + } + ], + "outputType": "scalar", + "type": "map" + } + ], + "type": "property" + } + } + }, "props": { "style": { "padding": 0 @@ -729,19 +478,12 @@ "script": "\tfrom system import date\n\t\n\tdef class_color(cls):\n\t\tm \u003d {\"Error\":\"#FFE5E5\",\"Warning\":\"#FFF7E0\",\"Message\":\"#EAF4FF\"}\n\t\treturn m.get(cls, \"#FFFFFF\")\n\t\n\tdef mk_row(number_id, start_ts, end_ts, duration_hms, cls, area, desc_, tag_):\n\t\treturn {\n\t\t\t\"value\": {\n\t\t\t\t\"NumberID\": number_id,\n\t\t\t\t\"Start Timestamp\": start_ts,\n\t\t\t\t\"End Timestamp\": end_ts,\n\t\t\t\t\"Duration\": duration_hms,\n\t\t\t\t\"Class\": cls,\n\t\t\t\t\"Area\": area,\n\t\t\t\t\"Description\": desc_,\n\t\t\t\t\"Tag\": tag_\n\t\t\t},\n\t\t\t\"style\": {\"backgroundColor\": class_color(cls), \"classes\": \"some-class\"}\n\t\t}\n\t\n\tds_name \u003d \"MariaDB80\"\n\t\n\t# Time window (java.util.Date)\n\tend_dt \u003d getattr(self.custom, \"endTime\", None) or date.now()\n\tstart_dt \u003d getattr(self.custom, \"startTime\", None) or date.addHours(end_dt, -24)\n\t\n\t# Class filter from DropdownMinClass\n\ttry:\n\t\tmin_choice \u003d self.parent.parent.parent.getChild(\"DropdownMinClass\").props.value\n\texcept:\n\t\tmin_choice \u003d \"Message\"\n\t\n\tif min_choice \u003d\u003d \"Error\":\n\t\tclass_list \u003d [\"Error\"]\n\telif min_choice \u003d\u003d \"Warning\":\n\t\tclass_list \u003d [\"Error\",\"Warning\"]\n\telse:\n\t\tclass_list \u003d [\"Error\",\"Warning\",\"Message\"]\n\t\n\tcls1 \u003d class_list[0] if len(class_list)\u003e0 else None\n\tcls2 \u003d class_list[1] if len(class_list)\u003e1 else None\n\tcls3 \u003d class_list[2] if len(class_list)\u003e2 else None\n\t\n\t# Optional priorities CSV from self.custom.priorities\n\tpriorities_csv \u003d getattr(self.custom, \"priorities\", \"\") or \"\"\n\t\n\tsql \u003d u\"\"\"\n\tSELECT\n\t ae.id AS NumberID,\n\t ae.eventtime AS `Start Timestamp`,\n\t clr.eventtime AS `End Timestamp`,\n\t IFNULL(\n\t SEC_TO_TIME(TIMESTAMPDIFF(SECOND, ae.eventtime, clr.eventtime)),\n\t SEC_TO_TIME(TIMESTAMPDIFF(SECOND, ae.eventtime, NOW()))\n\t ) AS Duration,\n\t cls.strvalue AS Class,\n\t loc.strvalue AS Area,\n\t des.strvalue AS Description,\n\t tag.strvalue AS Tag\n\tFROM alarm_events ae\n\tLEFT JOIN alarm_events clr\n\t ON clr.eventid \u003d ae.eventid AND clr.eventtype \u003d 1\n\tLEFT JOIN alarm_event_data cls\n\t ON cls.id \u003d ae.id AND cls.propname \u003d \u0027Class\u0027\n\tLEFT JOIN alarm_event_data loc\n\t ON loc.id \u003d ae.id AND loc.propname \u003d \u0027Area\u0027\n\tLEFT JOIN alarm_event_data des\n\t ON des.id \u003d ae.id AND des.propname \u003d \u0027Description\u0027\n\tLEFT JOIN alarm_event_data tag\n\t ON tag.id \u003d ae.id AND tag.propname \u003d \u0027Tag\u0027\n\tWHERE\n\t ae.eventtype \u003d 0\n\t AND ae.eventtime BETWEEN ? AND ?\n\t AND (\n\t ( ? IS NOT NULL AND cls.strvalue \u003d ? )\n\t OR ( ? IS NOT NULL AND cls.strvalue \u003d ? )\n\t OR ( ? IS NOT NULL AND cls.strvalue \u003d ? )\n\t )\n\t AND ( ? \u003d \u0027\u0027 OR FIND_IN_SET(CAST(ae.priority AS CHAR), ?) \u003e 0 )\n\tORDER BY ae.eventtime DESC\n\t\"\"\"\n\t\n\t# ORDER MATTERS: must match the ? placeholders above\n\tparams \u003d [\n\t\tstart_dt, end_dt,\n\t\tcls1, cls1,\n\t\tcls2, cls2,\n\t\tcls3, cls3,\n\t\tpriorities_csv, priorities_csv\n\t]\n\t\n\trows \u003d system.db.runPrepQuery(sql, params, ds_name)\n\t\n\tdata \u003d []\n\tfor r in rows:\n\t\ttry:\n\t\t\tstart_s \u003d system.date.format(r[\"Start Timestamp\"], \"yyyy-MM-dd HH:mm:ss\") if r[\"Start Timestamp\"] else \"\"\n\t\t\tend_s \u003d system.date.format(r[\"End Timestamp\"], \"yyyy-MM-dd HH:mm:ss\") if r[\"End Timestamp\"] else \"\"\n\t\t\tdur_s \u003d str(r[\"Duration\"]) if r[\"Duration\"] is not None else \"\"\n\t\t\tdata.append(\n\t\t\t\tmk_row(\n\t\t\t\t\tnumber_id \u003d int(r[\"NumberID\"]) + 30000,\n\t\t\t\t\tstart_ts \u003d start_s,\n\t\t\t\t\tend_ts \u003d end_s,\n\t\t\t\t\tduration_hms\u003d dur_s,\n\t\t\t\t\tcls \u003d r[\"Class\"] or \"\",\n\t\t\t\t\tarea \u003d r[\"Area\"] or \"\",\n\t\t\t\t\tdesc_ \u003d r[\"Description\"] or \"\",\n\t\t\t\t\ttag_ \u003d r[\"Tag\"] or \"\"\n\t\t\t\t)\n\t\t\t)\n\t\texcept Exception as ex:\n\t\t\tsystem.perspective.print(\"Row shape error: %s\" % ex)\n\t\n\tself.props.data \u003d data" } }, - "props.columns[3].filter.string.value": { - "binding": { - "config": { - "path": ".../FlexContainer/FlexContainer/Dropdown.props.value" - }, - "type": "property" - } - }, "props.data": { "binding": { "config": { "parameters": { - "priorityList": "{this.custom.priorities}" + "location": "{.../FlexContainer/FlexContainer/Dropdown.props.value}", + "priority": "{.../FlexContainer/FlexContainer.custom.FilterStatus}" }, "polling": { "enabled": true, @@ -751,7 +493,7 @@ }, "transforms": [ { - "code": "\n\tfrom system.dataset import toPyDataSet\n\n\tds \u003d toPyDataSet(value)\n\tdata \u003d []\n\n\tcolumn_names \u003d list(ds.columnNames)\n\n\tfor row in ds:\n\t\tpriority \u003d row[\"Priority\"]\n\n\t\t# Use style class names from Perspective\n\t\tif priority \u003d\u003d \"High\":\n\t\t\tclassName \u003d \"Alarms-Styles/High\"\n\t\telif priority \u003d\u003d \"Medium\":\n\t\t\tclassName \u003d \"Alarms-Styles/Medium\"\n\t\telif priority \u003d\u003d \"Low\":\n\t\t\tclassName \u003d \"Alarms-Styles/Low\"\n\t\telif priority \u003d\u003d \"Diagnostic\":\n\t\t\tclassName \u003d \"Alarms-Styles/Diagnostic\"\n\t\telse:\n\t\t\tclassName \u003d \"Alarms-Styles/NoAlarm\"\n\n\t\t# Apply the style class to all cells in the row\n\t\trow_dict \u003d {\n\t\t\tcol: {\n\t\t\t\t\"value\": \"SMC\" if col \u003d\u003d \"Location\" and row[col] \u003d\u003d \"Chute\" else row[col],\n\t\t\t\t\"style\": { \"classes\": className }\n\t\t\t\t\n\t\t\t} for col in column_names\n\t\t}\n\t\tdata.append(row_dict)\n\n\n\treturn data", + "code": "\tfrom system.dataset import toPyDataSet\n\t\n\tds \u003d toPyDataSet(value)\n\tdata \u003d []\n\t\n\tcolumn_names \u003d list(ds.columnNames)\n\t\n\tfor row in ds:\n\t\t# Get the style class from the Style column returned by SQL\n\t\tclassName \u003d row[\"Style\"]\n\t\t\n\t\t# Apply style to each cell individually (required for Ignition Perspective tables)\n\t\trow_dict \u003d {\n\t\t\tcol: {\n\t\t\t\t\"value\": row[col],\n\t\t\t\t\"style\": {\"classes\": className}\n\t\t\t} for col in column_names\n\t\t}\n\t\tdata.append(row_dict)\n\t\n\treturn data", "type": "script" } ], @@ -851,7 +593,7 @@ "viewParams": {}, "viewPath": "", "visible": true, - "width": "" + "width": 40 }, { "align": "center", @@ -1035,7 +777,7 @@ "viewParams": {}, "viewPath": "", "visible": true, - "width": 70 + "width": 40 }, { "align": "center", @@ -1051,13 +793,14 @@ "condition": "", "value": "" }, - "enabled": true, + "enabled": false, "number": { "condition": "", "value": "" }, "string": { - "condition": "equals" + "condition": "equals", + "value": "" }, "visible": "never" }, @@ -1126,7 +869,7 @@ "viewParams": {}, "viewPath": "", "visible": true, - "width": 70 + "width": 40 }, { "align": "center", @@ -1218,7 +961,7 @@ "viewParams": {}, "viewPath": "", "visible": true, - "width": 70 + "width": 40 }, { "align": "center", @@ -1310,7 +1053,7 @@ "viewParams": {}, "viewPath": "", "visible": true, - "width": 150 + "width": 100 }, { "align": "center", @@ -1403,7 +1146,7 @@ "viewParams": {}, "viewPath": "", "visible": true, - "width": "" + "width": 150 }, { "align": "center", @@ -1589,7 +1332,7 @@ "viewParams": {}, "viewPath": "", "visible": true, - "width": "" + "width": 100 } ], "emptyMessage": { @@ -1644,7 +1387,7 @@ "children": [ { "custom": { - "SetFilter": true + "SetFilter": false }, "events": { "component": { @@ -1747,17 +1490,17 @@ "$": [ "ts", 192, - 1760981138025 + 1761245346885 ], - "$ts": 1760981138025 + "$ts": 1761245346885 }, "startDate": { "$": [ "ts", 192, - 1760981138025 + 1761245346885 ], - "$ts": 1760979338025 + "$ts": 1761243546885 } }, "meta": { @@ -1924,7 +1667,7 @@ } }, "props": { - "formattedValue": "Oct 20, 2025 8:55 PM", + "formattedValue": "Oct 23, 2025 9:13 PM", "minDate": { "$": [ "ts", @@ -2016,7 +1759,7 @@ } }, "props": { - "formattedValue": "Oct 20, 2025 9:25 PM", + "formattedValue": "Oct 23, 2025 9:43 PM", "style": { "margin": 15 }, @@ -2024,9 +1767,9 @@ "$": [ "ts", 192, - 1760981138025 + 1761245346885 ], - "$ts": 1760981138025 + "$ts": 1761245346885 } }, "scripts": { @@ -2061,7 +1804,7 @@ "name": "Label" }, "position": { - "basis": "100px" + "basis": "132px" }, "props": { "style": { @@ -2069,7 +1812,7 @@ "fontWeight": "bold", "textAlign": "center" }, - "text": "Priority" + "text": "Minumum Priority:" }, "type": "ia.display.label" }, @@ -2119,23 +1862,13 @@ "value": "high" } ], + "placeholder": { + "text": "" + }, "style": { "margin": 15 } }, - "scripts": { - "customMethods": [], - "extensionFunctions": null, - "messageHandlers": [ - { - "messageType": "reset-historical-filters", - "pageScope": true, - "script": "#\treset \u003d payload[\"data\"]\n#\tself.props.value \u003d None\n\tpass", - "sessionScope": false, - "viewScope": false - } - ] - }, "type": "ia.input.dropdown" }, { @@ -2205,19 +1938,6 @@ "margin": 15 } }, - "scripts": { - "customMethods": [], - "extensionFunctions": null, - "messageHandlers": [ - { - "messageType": "hjsgdfn", - "pageScope": false, - "script": "\tpass", - "sessionScope": false, - "viewScope": false - } - ] - }, "type": "ia.input.dropdown" } ], @@ -2232,16 +1952,51 @@ } ], "custom": { - "ShowFilters": true + "ShowFilters": false }, "meta": { "name": "Filters" }, "position": { "basis": "180px", + "display": false, "shrink": 0 }, "propConfig": { + "custom.FilterStatus": { + "binding": { + "config": { + "path": "./Priority/Dropdown.props.value" + }, + "transforms": [ + { + "fallback": 0, + "inputType": "scalar", + "mappings": [ + { + "input": "high", + "output": 3 + }, + { + "input": "medium", + "output": 2 + }, + { + "input": "low", + "output": 1 + }, + { + "input": "diagnostic", + "output": 0 + } + ], + "outputType": "scalar", + "type": "map" + } + ], + "type": "property" + } + }, "position.display": { "binding": { "config": { @@ -2301,27 +2056,13 @@ "grow": 1 }, "propConfig": { - "props.columns[4].filter.string.value": { - "binding": { - "config": { - "path": ".../Filters/Priority/Dropdown_0.props.value" - }, - "type": "property" - } - }, - "props.columns[5].filter.string.value": { - "binding": { - "config": { - "path": ".../Filters/Priority/Dropdown.props.value" - }, - "type": "property" - } - }, "props.data": { "binding": { "config": { "parameters": { "endTime": "{.../Filters/Time/DateTimeInput_0.props.value}", + "location": "{.../Filters/Priority/Dropdown_0.props.value}", + "priority": "{.../Filters.custom.FilterStatus}", "startTime": "{.../Filters/Time/DateTimeInput.props.value}" }, "polling": { @@ -2332,7 +2073,7 @@ }, "transforms": [ { - "code": "\n\tfrom system.dataset import toPyDataSet\n\n\tds \u003d toPyDataSet(value)\n\tdata \u003d []\n\n\tcolumn_names \u003d list(ds.columnNames)\n\n\tfor row in ds:\n\t\tpriority \u003d row[\"Priority\"]\n\n\t\t# Use style class names from Perspective\n\t\tif priority \u003d\u003d \"High\":\n\t\t\tclassName \u003d \"Alarms-Styles/High\"\n\t\telif priority \u003d\u003d \"Medium\":\n\t\t\tclassName \u003d \"Alarms-Styles/Medium\"\n\t\telif priority \u003d\u003d \"Low\":\n\t\t\tclassName \u003d \"Alarms-Styles/Low\"\n\t\telif priority \u003d\u003d \"Diagnostic\":\n\t\t\tclassName \u003d \"Alarms-Styles/Diagnostic\"\n\t\telse:\n\t\t\tclassName \u003d \"Alarms-Styles/NoAlarm\"\n\t\n\t\trow_dict \u003d {\n\t\t\tcol: {\n\t\t\t\t\"value\": \"SMC\" if col \u003d\u003d \"Location\" and row[col] \u003d\u003d \"Chute\" else row[col],\n\t\t\t\t\"style\": { \"classes\": className }\n\t\t\t\t\n\t\t\t} for col in column_names\n\t\t}\n\t\tdata.append(row_dict)\n\n\treturn data\n", + "code": "\tfrom system.dataset import toPyDataSet\n\t\n\tds \u003d toPyDataSet(value)\n\tdata \u003d []\n\t\n\tcolumn_names \u003d list(ds.columnNames)\n\t\n\tfor row in ds:\n\t\t# Get the style class from the Style column returned by SQL\n\t\tclassName \u003d row[\"Style\"]\n\t\t\n\t\t# Apply style to each cell individually (required for Ignition Perspective tables)\n\t\trow_dict \u003d {\n\t\t\tcol: {\n\t\t\t\t\"value\": row[col],\n\t\t\t\t\"style\": {\"classes\": className}\n\t\t\t} for col in column_names\n\t\t}\n\t\tdata.append(row_dict)\n\t\n\treturn data", "type": "script" } ], @@ -2441,7 +2182,7 @@ "viewParams": {}, "viewPath": "", "visible": true, - "width": "" + "width": 70 }, { "align": "center", @@ -2533,7 +2274,7 @@ "viewParams": {}, "viewPath": "", "visible": true, - "width": "" + "width": 70 }, { "align": "center", @@ -2625,7 +2366,7 @@ "viewParams": {}, "viewPath": "", "visible": true, - "width": "" + "width": 40 }, { "align": "center", @@ -2717,7 +2458,7 @@ "viewParams": {}, "viewPath": "", "visible": true, - "width": "" + "width": 40 }, { "align": "center", @@ -2733,13 +2474,14 @@ "condition": "", "value": "" }, - "enabled": true, + "enabled": false, "number": { "condition": "", "value": "" }, "string": { - "condition": "equals" + "condition": "equals", + "value": "" }, "visible": "never" }, @@ -2808,7 +2550,7 @@ "viewParams": {}, "viewPath": "", "visible": true, - "width": "" + "width": 40 }, { "align": "center", @@ -2824,13 +2566,14 @@ "condition": "", "value": "" }, - "enabled": true, + "enabled": false, "number": { "condition": "", "value": "" }, "string": { - "condition": "equals" + "condition": "equals", + "value": "medium" }, "visible": "never" }, @@ -2884,7 +2627,7 @@ }, "render": "auto", "resizable": true, - "sort": "ascending", + "sort": "none", "sortable": true, "strictWidth": false, "style": { @@ -2899,7 +2642,7 @@ "viewParams": {}, "viewPath": "", "visible": true, - "width": "" + "width": 40 }, { "align": "center", @@ -2991,7 +2734,7 @@ "viewParams": {}, "viewPath": "", "visible": true, - "width": 150 + "width": 100 }, { "align": "center", @@ -3083,7 +2826,7 @@ "viewParams": {}, "viewPath": "", "visible": true, - "width": "" + "width": 150 }, { "align": "center", @@ -3315,7 +3058,7 @@ "component": { "onActionPerformed": { "config": { - "script": "\t\t\n from datetime import datetime\n \n try:\n table \u003d self.parent.parent.getChild(\"FlexContainer_0\").getChild(\"AlarmsTable\")\n filtered_data \u003d table.props.filter.results.data\n data \u003d filtered_data if filtered_data and len(filtered_data) \u003e 0 else table.props.data\n \n column_order \u003d [\n \"FirstTimestamp\",\n \"LastTimestamp\",\n \"Count\",\n \"Duration\",\n \"Priority\",\n \"Location\",\n \"Description\",\n \"Tag\"\n ]\n \n csv_content \u003d \",\".join(column_order) + \"\\n\"\n \n def unwrap(v):\n if hasattr(v, \"value\"):\n return str(v.value)\n return str(v)\n \n if data and len(data) \u003e 0:\n for item in data:\n row_data \u003d []\n for col in column_order:\n cell \u003d item.get(col, \"\")\n if isinstance(cell, dict) and \"value\" in cell:\n raw_value \u003d cell[\"value\"]\n else:\n raw_value \u003d cell\n \n processed_value \u003d unwrap(raw_value).replace(\",\", \";\")\n row_data.append(processed_value)\n \n csv_content +\u003d \",\".join(row_data) + \"\\n\"\n else:\n csv_content +\u003d \"No alarms in current view\\n\"\n \n except Exception as e:\n system.perspective.print(\"Export Error: \" + str(e))\n csv_content \u003d \"Export failed\\n\"\n \n csv_bytes \u003d csv_content.encode(\"utf-8\")\n system.perspective.download(\"hitList_alarms.csv\", csv_bytes)" + "script": "\n\tfrom datetime import datetime\n\t\n\ttry:\n\t data \u003d self.parent.parent.getChild(\"FlexContainer_0\").getChild(\"AlarmsTable\").props.data\n\t\n\t column_order \u003d [\n\t \"FirstTimestamp\",\n\t \"LastTimestamp\", \n\t \"Count\",\n\t \"Duration\",\n\t \"Priority\",\n\t \"Location\",\n\t \"Description\",\n\t \"Tag\"\n\t ]\n\t\n\t csv_content \u003d \",\".join(column_order) + \"\\n\"\n\t \n\t def unwrap(v):\n\t if hasattr(v, \u0027value\u0027):\n\t return str(v.value)\n\t return str(v)\n\t\n\t if data and len(data) \u003e 0:\n\t for item in data:\n\t row_data \u003d []\n\t for col in column_order:\n\t if col in item:\n\t cell \u003d item[col]\n\t if isinstance(cell, dict) and \"value\" in cell:\n\t raw_value \u003d cell[\"value\"]\n\t else:\n\t raw_value \u003d cell\n\t else:\n\t raw_value \u003d \"\"\n\t\n\t processed_value \u003d unwrap(raw_value).replace(\",\", \";\")\n\t row_data.append(processed_value)\n\t\n\t csv_content +\u003d \",\".join(row_data) + \"\\n\"\n\t else:\n\t csv_content +\u003d \"No alarms in current view\\n\"\n\t\n\texcept Exception as e:\n\t system.perspective.print(\"Export Error: \" + str(e))\n\t csv_content \u003d \"Export failed\\n\"\n\t\n\tcsv_bytes \u003d csv_content.encode(\"utf-8\")\n\tsystem.perspective.download(\"hitList_alarms.csv\", csv_bytes)" }, "scope": "G", "type": "script" @@ -3386,7 +3129,7 @@ "children": [ { "custom": { - "SetFilter": true + "SetFilter": false }, "events": { "component": { @@ -3489,17 +3232,17 @@ "$": [ "ts", 192, - 1760981138025 + 1761245346885 ], - "$ts": 1760981138025 + "$ts": 1761245346885 }, "startDate": { "$": [ "ts", 192, - 1760981138025 + 1761245346885 ], - "$ts": 1760979338025 + "$ts": 1761243546885 } }, "meta": { @@ -3666,7 +3409,7 @@ } }, "props": { - "formattedValue": "Oct 17, 2025 6:13 AM", + "formattedValue": "Oct 23, 2025 10:19 PM", "minDate": { "$": [ "ts", @@ -3758,7 +3501,7 @@ } }, "props": { - "formattedValue": "Oct 17, 2025 6:43 AM", + "formattedValue": "Oct 23, 2025 10:49 PM", "style": { "margin": 15 }, @@ -3766,9 +3509,9 @@ "$": [ "ts", 192, - 1760981138025 + 1761245346885 ], - "$ts": 1760981138025 + "$ts": 1761245346885 } }, "scripts": { @@ -3803,7 +3546,7 @@ "name": "Label" }, "position": { - "basis": "100px" + "basis": "130px" }, "props": { "style": { @@ -3811,7 +3554,7 @@ "fontWeight": "bold", "textAlign": "center" }, - "text": "Priority" + "text": "Minumum Priority:" }, "type": "ia.display.label" }, @@ -3974,17 +3717,52 @@ } ], "custom": { - "ShowFilters": true + "ShowFilters": false }, "meta": { "name": "Filters" }, "position": { "basis": "180px", + "display": false, "grow": 1, "shrink": 0 }, "propConfig": { + "custom.FilterStatus": { + "binding": { + "config": { + "path": "./Priority/Dropdown.props.value" + }, + "transforms": [ + { + "fallback": 0, + "inputType": "scalar", + "mappings": [ + { + "input": "high", + "output": 3 + }, + { + "input": "medium", + "output": 2 + }, + { + "input": "low", + "output": 1 + }, + { + "input": "diagnostic", + "output": 0 + } + ], + "outputType": "scalar", + "type": "map" + } + ], + "type": "property" + } + }, "position.display": { "binding": { "config": { @@ -4099,6 +3877,7 @@ "config": { "path": "this.custom.time_from_filter" }, + "enabled": false, "transforms": [ { "expression": "coalesce({value}, dateArithmetic(now(), -30, \"minute\"))", @@ -4113,6 +3892,7 @@ "config": { "path": "this.custom.time_to_filter" }, + "enabled": false, "transforms": [ { "expression": "coalesce({value},NOW())", @@ -4122,34 +3902,13 @@ "type": "property" } }, - "props.columns[5].filter.string.value": { - "binding": { - "config": { - "path": "this.custom.priority_filters" - }, - "transforms": [ - { - "expression": "coalesce({value},\"\")", - "type": "expression" - } - ], - "type": "property" - } - }, - "props.columns[6].filter.string.value": { - "binding": { - "config": { - "path": ".../Filters/Priority/Dropdown_0.props.value" - }, - "type": "property" - } - }, "props.data": { "binding": { "config": { "parameters": { - "TabIndex": "{....../TabContainer.props.currentTabIndex}", "endtime": "{this.custom.time_to_filter}", + "location": "{.../Filters/Priority/Dropdown_0.props.value}", + "priority": "{.../Filters.custom.FilterStatus}", "starttime": "{this.custom.time_from_filter}" }, "polling": { @@ -4160,7 +3919,7 @@ }, "transforms": [ { - "code": "\t# Transform (script)\n\tfrom system.dataset import toPyDataSet\n\t\n\t# Handle null/empty input\n\tds \u003d toPyDataSet(value) if value is not None else None\n#\tif not ds:\n#\t self.custom.loading \u003d False\n#\t self.custom.record_count \u003d 0\n#\t self.custom.hit_limit \u003d False\n#\t return []\n\t\n\t# Priority to style class mapping\n\tPRIORITY_STYLES \u003d {\n\t \"High\": \"Alarms-Styles/High\",\n\t \"Medium\": \"Alarms-Styles/Medium\",\n\t \"Low\": \"Alarms-Styles/Low\",\n\t \"Diagnostic\": \"Alarms-Styles/Diagnostic\",\n\t \"Critical\": \"Alarms-Styles/Critical\"\n\t}\n\tDEFAULT_STYLE \u003d \"Alarms-Styles/NoAlarm\"\n\t\n\t\n\tcols \u003d list(ds.columnNames)\n\tdata \u003d []\n\t\n\tfor row in ds:\n\t # Get priority and map to style class\n\t priority \u003d row.get(\"Priority\", None) if hasattr(row, \"get\") else row[\"Priority\"]\n\t className \u003d PRIORITY_STYLES.get(priority, DEFAULT_STYLE)\n\t \n\t # Build row with transformations\n\t row_dict \u003d {}\n\t for col in cols:\n\t cell_value \u003d row[col]\n\t \n\t # Transform Location: Chute -\u003e SMC\n\t if col \u003d\u003d \"Location\" and cell_value \u003d\u003d \"Chute\":\n\t cell_value \u003d \"SMC\"\n\t \n\t row_dict[col] \u003d {\n\t \"value\": cell_value,\n\t \"style\": {\"classes\": className}\n\t }\n\t \n\t data.append(row_dict)\n\t\n\t# Update component state\n\tself.custom.loading \u003d False\n\tself.custom.record_count \u003d len(data)\n\tself.custom.hit_limit \u003d False\n\t\n\treturn data", + "code": "\tfrom system.dataset import toPyDataSet\n\t\n\tds \u003d toPyDataSet(value)\n\tdata \u003d []\n\t\n\tcolumn_names \u003d list(ds.columnNames)\n\t\n\tfor row in ds:\n\t\t# Get the style class from the Style column returned by SQL\n\t\tclassName \u003d row[\"Style\"]\n\t\t\n\t\t# Apply style to each cell individually (required for Ignition Perspective tables)\n\t\trow_dict \u003d {\n\t\t\tcol: {\n\t\t\t\t\"value\": row[col],\n\t\t\t\t\"style\": {\"classes\": className}\n\t\t\t} for col in column_names\n\t\t}\n\t\tdata.append(row_dict)\n\t\n\treturn data", "type": "script" } ], @@ -4263,7 +4022,7 @@ "viewParams": {}, "viewPath": "", "visible": true, - "width": 50 + "width": 40 }, { "align": "center", @@ -4276,9 +4035,17 @@ "condition": "" }, "date": { - "condition": "later than date time" + "condition": "later than date time", + "value": { + "$": [ + "ts", + 192, + 1761236207089 + ], + "$ts": 1761234407082 + } }, - "enabled": true, + "enabled": false, "number": { "condition": "", "value": "" @@ -4354,7 +4121,7 @@ "viewParams": {}, "viewPath": "", "visible": true, - "width": 50 + "width": 70 }, { "align": "center", @@ -4367,9 +4134,17 @@ "condition": "" }, "date": { - "condition": "earlier than date time" + "condition": "earlier than date time", + "value": { + "$": [ + "ts", + 192, + 1761236207088 + ], + "$ts": 1761236207082 + } }, - "enabled": true, + "enabled": false, "number": { "condition": "", "value": "" @@ -4445,7 +4220,7 @@ "viewParams": {}, "viewPath": "", "visible": true, - "width": 50 + "width": 70 }, { "align": "center", @@ -4538,7 +4313,7 @@ "viewParams": {}, "viewPath": "", "visible": true, - "width": 50 + "width": 70 }, { "align": "center", @@ -4630,7 +4405,7 @@ "viewParams": {}, "viewPath": "", "visible": true, - "width": 50 + "width": 100 }, { "align": "center", @@ -4646,13 +4421,14 @@ "condition": "", "value": "" }, - "enabled": true, + "enabled": false, "number": { "condition": "", "value": "" }, "string": { - "condition": "contains" + "condition": "contains", + "value": "medium" }, "visible": "never" }, @@ -4721,7 +4497,7 @@ "viewParams": {}, "viewPath": "", "visible": true, - "width": 50 + "width": 40 }, { "align": "center", @@ -4737,13 +4513,14 @@ "condition": "", "value": "" }, - "enabled": true, + "enabled": false, "number": { "condition": "", "value": "" }, "string": { - "condition": "equals" + "condition": "equals", + "value": "" }, "visible": "never" }, @@ -4812,7 +4589,7 @@ "viewParams": {}, "viewPath": "", "visible": true, - "width": 50 + "width": 40 }, { "align": "center", @@ -4904,7 +4681,7 @@ "viewParams": {}, "viewPath": "", "visible": true, - "width": 50 + "width": 150 }, { "align": "center", @@ -5908,7 +5685,6 @@ "contentStyle": { "classes": "Background-Styles/Grey-Background" }, - "currentTabIndex": 1, "menuType": "modern", "style": { "classes": "Background-Styles/Grey-Background" diff --git a/SCADA_PERSPECTIVE_PARENT_PROJECT/ignition/named-query/GetActiveAlarms/query.sql b/SCADA_PERSPECTIVE_PARENT_PROJECT/ignition/named-query/GetActiveAlarms/query.sql index 2df5559..cd21053 100644 --- a/SCADA_PERSPECTIVE_PARENT_PROJECT/ignition/named-query/GetActiveAlarms/query.sql +++ b/SCADA_PERSPECTIVE_PARENT_PROJECT/ignition/named-query/GetActiveAlarms/query.sql @@ -1,13 +1,11 @@ --- GetActiveAlarms: Returns all currently active (uncleared) alarms --- Uses: idx_alarm_events_active, idx_alarm_events_clear, idx_alarm_event_data_lookup --- Expected performance: <200ms on 37K rows, <500ms on 1M+ rows --- Param: :priorityList (comma-separated priority numbers, or empty string for all) +/*+ MAX_EXECUTION_TIME(8000) */ SELECT ae.id AS ID, ae.eventtime AS StartTimestamp, TIME_FORMAT(SEC_TO_TIME(TIMESTAMPDIFF(SECOND, ae.eventtime, NOW())), '%H:%i:%s') AS Duration, CONCAT(REPLACE(ae.displaypath, '_', '-'), ' ', SUBSTRING_INDEX(ae.source, ':/alm:', -1)) AS Description, + CASE ae.priority WHEN 0 THEN 'Diagnostic' WHEN 1 THEN 'Low' @@ -16,22 +14,55 @@ SELECT WHEN 4 THEN 'Critical' ELSE 'Unknown' END AS Priority, + CONCAT(ae.displaypath, '.HMI.Alarm.', SUBSTRING_INDEX(IFNULL(aed.strValue, ''), '/', -1)) AS Tag, - SUBSTRING_INDEX(SUBSTRING_INDEX(IFNULL(aed.strValue, ''), '/', 2), '/', -1) AS Location, + + CASE + WHEN SUBSTRING_INDEX(SUBSTRING_INDEX(IFNULL(aed.strValue, ''), '/', 2), '/', -1) = 'Chute' THEN 'SMC' + ELSE SUBSTRING_INDEX(SUBSTRING_INDEX(IFNULL(aed.strValue, ''), '/', 2), '/', -1) + END AS Location, + + CASE + WHEN ae.priority = 3 THEN 'Alarms-Styles/High' + WHEN ae.priority = 2 THEN 'Alarms-Styles/Medium' + WHEN ae.priority = 1 THEN 'Alarms-Styles/Low' + WHEN ae.priority = 0 THEN 'Alarms-Styles/Diagnostic' + ELSE 'Alarms-Styles/NoAlarm' + END AS Style, + aed.strValue AS FullTag, ae.displaypath AS Device + FROM alarm_events ae FORCE INDEX (idx_alarm_events_active) LEFT JOIN alarm_event_data aed FORCE INDEX (idx_alarm_event_data_lookup) ON aed.id = ae.id AND aed.propname = 'myTag' -WHERE ae.eventtype = 0 + +WHERE + ae.eventtype = 0 AND ae.displaypath NOT LIKE '%System Startup%' AND ae.source NOT LIKE '%System Startup%' - AND (:priorityList = '' OR FIND_IN_SET(CAST(ae.priority AS CHAR), :priorityList) > 0) - -- FORCE INDEX ensures idx_alarm_events_clear is used in subquery + + -- Priority filter (same logic as other queries) + AND ( + :priority IS NULL OR :priority = '' OR :priority = 0 + OR (:priority = 3 AND ae.priority = 3) + OR (:priority = 2 AND ae.priority BETWEEN 2 AND 3) + OR (:priority = 1 AND ae.priority BETWEEN 1 AND 3) + ) + + -- Location filter + AND ( + :location IS NULL OR :location = '' + OR SUBSTRING_INDEX(SUBSTRING_INDEX(IFNULL(aed.strValue, ''), '/', 2), '/', -1) + LIKE CONCAT('%', :location, '%') + ) + + -- Exclude cleared alarms AND NOT EXISTS ( SELECT 1 FROM alarm_events clr FORCE INDEX (idx_alarm_events_clear) WHERE clr.eventid = ae.eventid AND clr.eventtype = 1 ) -ORDER BY ae.eventtime DESC; \ No newline at end of file + +ORDER BY ae.eventtime DESC; diff --git a/SCADA_PERSPECTIVE_PARENT_PROJECT/ignition/named-query/GetAlarms/query.sql b/SCADA_PERSPECTIVE_PARENT_PROJECT/ignition/named-query/GetAlarms/query.sql index 84e3d95..57b27bb 100644 --- a/SCADA_PERSPECTIVE_PARENT_PROJECT/ignition/named-query/GetAlarms/query.sql +++ b/SCADA_PERSPECTIVE_PARENT_PROJECT/ignition/named-query/GetAlarms/query.sql @@ -1,9 +1,3 @@ --- GetAlarms: Alarms that ended (cleared) within the specified timeframe --- Shows active alarms ONLY if the timeframe includes current time --- MAXIMUM PERFORMANCE: Optimized for EndTimestamp filtering --- Expected performance: <500ms on 37K rows --- Params: :starttime (DATETIME), :endtime (DATETIME) - /*+ MAX_EXECUTION_TIME(8000) */ SELECT @@ -15,6 +9,7 @@ SELECT '%H:%i:%s' ) AS Duration, CONCAT(REPLACE(a.displaypath, '_', '-'), ' ', SUBSTRING_INDEX(a.source, ':/alm:', -1)) AS Description, + CASE a.priority WHEN 0 THEN 'Diagnostic' WHEN 1 THEN 'Low' @@ -23,13 +18,27 @@ SELECT WHEN 4 THEN 'Critical' ELSE 'Unknown' END AS Priority, + CONCAT(a.displaypath, '.HMI.Alarm.', SUBSTRING_INDEX(IFNULL(aed.strValue, ''), '/', -1)) AS Tag, - SUBSTRING_INDEX(SUBSTRING_INDEX(IFNULL(aed.strValue, ''), '/', 2), '/', -1) AS Location, + + CASE + WHEN SUBSTRING_INDEX(SUBSTRING_INDEX(IFNULL(aed.strValue, ''), '/', 2), '/', -1) = 'Chute' THEN 'SMC' + ELSE SUBSTRING_INDEX(SUBSTRING_INDEX(IFNULL(aed.strValue, ''), '/', 2), '/', -1) + END AS Location, + + CASE + WHEN a.priority = 3 THEN 'Alarms-Styles/High' + WHEN a.priority = 2 THEN 'Alarms-Styles/Medium' + WHEN a.priority = 1 THEN 'Alarms-Styles/Low' + WHEN a.priority = 0 THEN 'Alarms-Styles/Diagnostic' + ELSE 'Alarms-Styles/NoAlarm' + END AS Style, + aed.strValue AS FullTag, a.displaypath AS Device + FROM alarm_events a FORCE INDEX (idx_alarm_events_type_time_id) LEFT JOIN ( - -- Find first clear time for each alarm SELECT eventid, MIN(eventtime) AS min_clear_time FROM alarm_events FORCE INDEX (idx_alarm_events_clear) WHERE eventtype = 1 @@ -39,17 +48,15 @@ LEFT JOIN ( ) clr ON clr.eventid = a.eventid AND clr.min_clear_time >= a.eventtime LEFT JOIN alarm_event_data aed FORCE INDEX (idx_alarm_event_data_lookup) ON aed.id = a.id AND aed.propname = 'myTag' + WHERE a.eventtype = 0 AND a.displaypath NOT LIKE '%System Startup%' AND a.source NOT LIKE '%System Startup%' - -- Smart time filtering: - -- If endtime is near NOW (within 5 mins), get ALL active alarms + historical in window - -- If endtime is in the past, only get alarms that started in the window + + -- Smart time filtering AND ( - -- Historical alarms: started in the time window (a.eventtime >= :starttime AND a.eventtime < :endtime) - -- OR Live mode: ALL active alarms if endtime is within 5 mins of NOW OR ( :endtime >= DATE_SUB(NOW(), INTERVAL 5 MINUTE) AND NOT EXISTS ( @@ -59,13 +66,28 @@ WHERE ) ) ) - -- Final filter: Cleared alarms must have cleared in window + AND ( - clr.min_clear_time IS NOT NULL -- Cleared alarms in window - OR :endtime >= DATE_SUB(NOW(), INTERVAL 5 MINUTE) -- Active alarms if endtime is recent + clr.min_clear_time IS NOT NULL + OR :endtime >= DATE_SUB(NOW(), INTERVAL 5 MINUTE) + ) + + -- Priority filter + AND ( + :priority IS NULL OR :priority = '' OR :priority = 0 + OR (:priority = 3 AND a.priority = 3) + OR (:priority = 2 AND a.priority BETWEEN 2 AND 3) + OR (:priority = 1 AND a.priority BETWEEN 1 AND 3) + ) + + -- Location filter + AND ( + :location IS NULL OR :location = '' + OR SUBSTRING_INDEX(SUBSTRING_INDEX(IFNULL(aed.strValue, ''), '/', 2), '/', -1) + LIKE CONCAT('%', :location, '%') ) ORDER BY CASE WHEN clr.min_clear_time IS NULL THEN 0 ELSE 1 END, IFNULL(clr.min_clear_time, a.eventtime) DESC, - a.id DESC; \ No newline at end of file + a.id DESC; diff --git a/SCADA_PERSPECTIVE_PARENT_PROJECT/ignition/named-query/GetAlarmsWithCount/query.sql b/SCADA_PERSPECTIVE_PARENT_PROJECT/ignition/named-query/GetAlarmsWithCount/query.sql index b771a3e..4526b52 100644 --- a/SCADA_PERSPECTIVE_PARENT_PROJECT/ignition/named-query/GetAlarmsWithCount/query.sql +++ b/SCADA_PERSPECTIVE_PARENT_PROJECT/ignition/named-query/GetAlarmsWithCount/query.sql @@ -1,12 +1,6 @@ --- GetAlarmsWithCount: Alarm statistics with activation counts for a time window --- Uses: idx_alarm_events_type_time_id, idx_alarm_events_clear, idx_alarm_event_data_lookup --- Expected performance: <300ms on 37K rows, <800ms on 1M+ rows --- Params: :startTime (DATETIME or NULL/empty), :endTime (DATETIME or NULL/empty) - /*+ MAX_EXECUTION_TIME(8000) */ WITH ClearedEvents AS ( - -- Pre-aggregate clear times - FORCED index usage for speed SELECT eventid, MIN(eventtime) AS clear_time @@ -20,7 +14,10 @@ SELECT CONCAT(ae.displaypath, '.HMI.Alarm.', SUBSTRING_INDEX(IFNULL(aed.strValue, ''), '/', -1)) AS Tag, - SUBSTRING_INDEX(SUBSTRING_INDEX(IFNULL(aed.strValue, ''), '/', 2), '/', -1) AS Location, + CASE + WHEN SUBSTRING_INDEX(SUBSTRING_INDEX(IFNULL(aed.strValue, ''), '/', 2), '/', -1) = 'Chute' THEN 'SMC' + ELSE SUBSTRING_INDEX(SUBSTRING_INDEX(IFNULL(aed.strValue, ''), '/', 2), '/', -1) + END AS Location, CASE ae.priority WHEN 0 THEN 'Diagnostic' @@ -30,8 +27,15 @@ SELECT WHEN 4 THEN 'Critical' ELSE 'Unknown' END AS Priority, + + CASE + WHEN ae.priority = 3 THEN 'Alarms-Styles/High' + WHEN ae.priority = 2 THEN 'Alarms-Styles/Medium' + WHEN ae.priority = 1 THEN 'Alarms-Styles/Low' + WHEN ae.priority = 0 THEN 'Alarms-Styles/Diagnostic' + ELSE 'Alarms-Styles/NoAlarm' + END AS Style, - -- First/Last timestamps (clipped to window if provided) MIN( GREATEST( ae.eventtime, @@ -46,7 +50,6 @@ SELECT ) ) AS LastTimestamp, - -- Duration within window (formatted as HH:MM:SS string) TIME_FORMAT( SEC_TO_TIME( SUM( @@ -55,7 +58,7 @@ SELECT SECOND, GREATEST(ae.eventtime, IFNULL(NULLIF(:startTime, ''), ae.eventtime)), LEAST(IFNULL(clr.clear_time, NOW()), - IFNULL(NULLIF(:endTime, ''), IFNULL(clr.clear_time, NOW()))) + IFNULL(NULLIF(:endTime, ''), IFNULL(clr.clear_time, NOW())) ) ), 0 ) @@ -64,9 +67,7 @@ SELECT '%H:%i:%s' ) AS Duration, - -- Activation count: how many times alarm triggered in the window COUNT(*) AS "Count", - aed.strValue AS FullTag, ae.displaypath AS Device @@ -80,11 +81,22 @@ WHERE ae.eventtype = 0 AND ae.displaypath NOT LIKE '%System Startup%' AND ae.source NOT LIKE '%System Startup%' - -- Time window filter: FORCE INDEX ensures fast range scan AND ( (:startTime IS NULL OR :startTime = '' OR ae.eventtime >= :startTime) AND - (:endTime IS NULL OR :endTime = '' OR ae.eventtime <= :endTime) + (:endTime IS NULL OR :endTime = '' OR ae.eventtime <= :endTime) + ) + -- Priority filter + AND ( + :priority IS NULL OR :priority = '' OR :priority = 0 + OR (:priority = 3 AND ae.priority = 3) + OR (:priority = 2 AND ae.priority BETWEEN 2 AND 3) + OR (:priority = 1 AND ae.priority BETWEEN 1 AND 3) + ) + -- Location filter (matches if string found anywhere) + AND ( + :location IS NULL OR :location = '' + OR SUBSTRING_INDEX(SUBSTRING_INDEX(IFNULL(aed.strValue, ''), '/', 2), '/', -1) LIKE CONCAT('%', :location, '%') ) GROUP BY @@ -95,4 +107,4 @@ GROUP BY ORDER BY FirstTimestamp DESC, - MIN(ae.id) DESC; \ No newline at end of file + MIN(ae.id) DESC;