diff --git a/plugins/EcoStruxureIT-DCE/v1/configValidation.json b/plugins/EcoStruxureIT-DCE/v1/configValidation.json index fc472506..2620af20 100644 --- a/plugins/EcoStruxureIT-DCE/v1/configValidation.json +++ b/plugins/EcoStruxureIT-DCE/v1/configValidation.json @@ -4,14 +4,14 @@ "displayName": "Connect to Data Center Expert", "dataStream": { "name": "rootDeviceGroup" }, "required": true, - "error": "Could not sign in to Data Center Expert. Check the Server URL is reachable from SquaredUp (or the on-premise agent), the username and password are correct, and enable Ignore certificate errors if the server uses a self-signed certificate.", + "error": "The first request to Data Center Expert failed. Check the Server URL is reachable from SquaredUp (or the relay agent), the username and password are correct, and Ignore certificate errors is on if the server uses a self-signed certificate. To find the exact error, see [Troubleshooting](https://github.com/squaredup/plugins/blob/main/plugins/EcoStruxureIT-DCE/v1/docs/README.md#troubleshooting).", "success": "Connected to Data Center Expert." }, { "displayName": "Check device access", "dataStream": { "name": "devices" }, "required": false, - "error": "Signed in, but no devices were returned. Check the user has access to at least one device group in Data Center Expert.", + "error": "No devices were returned. If the check above also failed, fix that first. Otherwise, check the user has access to at least one device group in Data Center Expert.", "success": "Devices are accessible." } ] diff --git a/plugins/EcoStruxureIT-DCE/v1/dataStreams/alarmHistory.json b/plugins/EcoStruxureIT-DCE/v1/dataStreams/alarmHistory.json index 1e8e72df..77805e2c 100644 --- a/plugins/EcoStruxureIT-DCE/v1/dataStreams/alarmHistory.json +++ b/plugins/EcoStruxureIT-DCE/v1/dataStreams/alarmHistory.json @@ -1,7 +1,7 @@ { "name": "alarmHistory", "displayName": "Alarm History", - "description": "Resolved alarms that started within the timeframe, optionally filtered by device", + "description": "Alarms resolved within the timeframe, optionally filtered by device (across all devices, the last 24 hours at most)", "tags": [ "Alerts", "Device" @@ -9,15 +9,23 @@ "baseDataSourceName": "httpRequestUnscoped", "config": { "httpMethod": "get", - "endpointPath": "v1/alarms/history", + "endpointPath": "{{ typeof device !== 'undefined' && Array.isArray(device) && device.length === 1 ? 'v1/devices/' + encodeURIComponent([].concat(device[0].rawId)[0]) + '/alarms/history' : 'v1/alarms/history' }}", "getArgs": [ + { + "key": "startTime", + "value": "{{ typeof device !== 'undefined' && Array.isArray(device) && device.length === 1 ? String(new Date(timeframe.start).getTime()) : null }}" + }, + { + "key": "endTime", + "value": "{{ typeof device !== 'undefined' && Array.isArray(device) && device.length === 1 ? String(new Date(timeframe.end).getTime()) : null }}" + }, { "key": "startDateTime", - "value": "{{ String(new Date(timeframe.start).getTime()) }}" + "value": "{{ typeof device !== 'undefined' && Array.isArray(device) && device.length === 1 ? null : String(Math.max(new Date(timeframe.start).getTime(), new Date(timeframe.end).getTime() - 86400000)) }}" }, { "key": "endDateTime", - "value": "{{ String(new Date(timeframe.end).getTime()) }}" + "value": "{{ typeof device !== 'undefined' && Array.isArray(device) && device.length === 1 ? null : String(new Date(timeframe.end).getTime()) }}" } ], "postRequestScript": "alarms.js", @@ -49,6 +57,16 @@ "shape": "string", "role": "label" }, + { + "name": "category", + "displayName": "Category", + "shape": "string" + }, + { + "name": "statusText", + "displayName": "Status Text", + "shape": "string" + }, { "name": "severityText", "displayName": "Severity", @@ -100,13 +118,6 @@ "sourceType": "Device", "objectPropertyPath": "name" }, - { - "name": "sensorName", - "displayName": "Sensor", - "sourceId": "sensorGuid", - "sourceType": "Sensor", - "objectPropertyPath": "name" - }, { "name": "recommendedAction", "displayName": "Recommended Action", @@ -150,6 +161,8 @@ { "name": "deviceGuid", "displayName": "Device ID", + "sourceId": "deviceGuid", + "sourceType": "Device", "shape": "string", "visible": false }, diff --git a/plugins/EcoStruxureIT-DCE/v1/dataStreams/alarmSummary.json b/plugins/EcoStruxureIT-DCE/v1/dataStreams/alarmSummary.json index aacdd422..2eb54099 100644 --- a/plugins/EcoStruxureIT-DCE/v1/dataStreams/alarmSummary.json +++ b/plugins/EcoStruxureIT-DCE/v1/dataStreams/alarmSummary.json @@ -32,7 +32,7 @@ "displayName": "Status", "shape": ["state", { "map": { "success": ["success"], "warning": ["warning"], "error": ["error"] } }] }, - { "name": "deviceGroupGuid", "displayName": "Device Group ID", "shape": "string", "role": "id", "visible": false } + { "name": "deviceGroupGuid", "displayName": "Device Group ID", "sourceId": "deviceGroupGuid", "sourceType": "Device Group", "shape": "string", "role": "id", "visible": false } ], "defaultShaping": { "sort": { "by": [["criticalAlarmCount", "desc"], ["warningAlarmCount", "desc"]] } }, "timeframes": false diff --git a/plugins/EcoStruxureIT-DCE/v1/dataStreams/alarms.json b/plugins/EcoStruxureIT-DCE/v1/dataStreams/alarms.json index 5c97d2d9..bea53c37 100644 --- a/plugins/EcoStruxureIT-DCE/v1/dataStreams/alarms.json +++ b/plugins/EcoStruxureIT-DCE/v1/dataStreams/alarms.json @@ -1,15 +1,16 @@ { "name": "alarms", "displayName": "Active Alarms", - "description": "Currently active alarms, optionally filtered by device", + "description": "Currently active alarms, optionally filtered by device or device group", "tags": [ "Alerts", - "Device" + "Device", + "Device Group" ], "baseDataSourceName": "httpRequestUnscoped", "config": { "httpMethod": "get", - "endpointPath": "v1/alarms", + "endpointPath": "{{ typeof deviceGroup !== 'undefined' && Array.isArray(deviceGroup) && deviceGroup.length === 1 ? 'v1/deviceGroups/' + encodeURIComponent([].concat(deviceGroup[0].rawId)[0]) + '/alarms' : 'v1/alarms' }}", "postRequestScript": "alarms.js", "errorHandling": { "type": "script", @@ -30,6 +31,19 @@ ] } } + }, + { + "type": "objects", + "name": "deviceGroup", + "label": "Device group (optional)", + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "Device Group" + ] + } + } } ], "metadata": [ @@ -39,6 +53,16 @@ "shape": "string", "role": "label" }, + { + "name": "category", + "displayName": "Category", + "shape": "string" + }, + { + "name": "statusText", + "displayName": "Status Text", + "shape": "string" + }, { "name": "severityText", "displayName": "Severity", @@ -85,13 +109,6 @@ "sourceType": "Device", "objectPropertyPath": "name" }, - { - "name": "sensorName", - "displayName": "Sensor", - "sourceId": "sensorGuid", - "sourceType": "Sensor", - "objectPropertyPath": "name" - }, { "name": "recommendedAction", "displayName": "Recommended Action", @@ -135,6 +152,8 @@ { "name": "deviceGuid", "displayName": "Device ID", + "sourceId": "deviceGuid", + "sourceType": "Device", "shape": "string", "visible": false }, diff --git a/plugins/EcoStruxureIT-DCE/v1/dataStreams/deviceGroupDevices.json b/plugins/EcoStruxureIT-DCE/v1/dataStreams/deviceGroupDevices.json index 2e39014c..98931a90 100644 --- a/plugins/EcoStruxureIT-DCE/v1/dataStreams/deviceGroupDevices.json +++ b/plugins/EcoStruxureIT-DCE/v1/dataStreams/deviceGroupDevices.json @@ -7,6 +7,7 @@ "config": { "httpMethod": "get", "endpointPath": "v1/deviceGroups/{{encodeURIComponent(object.rawId)}}/devices", + "postRequestScript": "statusText.js", "errorHandling": { "type": "script", "script": "errorHandling/dceError.js" } }, "matches": { @@ -14,6 +15,7 @@ }, "metadata": [ { "sourceId": "isxcGuid", "sourceType": "Device", "name": "label", "displayName": "Device", "shape": "string", "role": "label" }, + { "name": "statusText", "displayName": "Status Text", "shape": "string" }, { "name": "severityText", "displayName": "Status", @@ -21,7 +23,7 @@ "state", { "map": { - "success": ["Information", "Informational", "Normal", "OK", "None"], + "success": ["Information", "Informational", "Normal", "OK", "Ok", "None"], "warning": ["Warning"], "error": ["Error", "Critical", "Failure"] } diff --git a/plugins/EcoStruxureIT-DCE/v1/dataStreams/deviceGroups.json b/plugins/EcoStruxureIT-DCE/v1/dataStreams/deviceGroups.json index 4a89b533..7cb6d6e7 100644 --- a/plugins/EcoStruxureIT-DCE/v1/dataStreams/deviceGroups.json +++ b/plugins/EcoStruxureIT-DCE/v1/dataStreams/deviceGroups.json @@ -7,11 +7,13 @@ "config": { "httpMethod": "get", "endpointPath": "v1/deviceGroups", + "postRequestScript": "statusText.js", "errorHandling": { "type": "script", "script": "errorHandling/dceError.js" } }, "matches": "none", "metadata": [ { "sourceId": "isxcGuid", "sourceType": "Device Group", "name": "name", "displayName": "Device Group", "shape": "string", "role": "label" }, + { "name": "statusText", "displayName": "Status Text", "shape": "string" }, { "name": "severityText", "displayName": "Status", @@ -19,7 +21,7 @@ "state", { "map": { - "success": ["Information", "Informational", "Normal", "OK", "None"], + "success": ["Information", "Informational", "Normal", "OK", "Ok", "None"], "warning": ["Warning"], "error": ["Error", "Critical", "Failure"] } diff --git a/plugins/EcoStruxureIT-DCE/v1/dataStreams/deviceSensorHistory.json b/plugins/EcoStruxureIT-DCE/v1/dataStreams/deviceSensorHistory.json new file mode 100644 index 00000000..80b8d7e4 --- /dev/null +++ b/plugins/EcoStruxureIT-DCE/v1/dataStreams/deviceSensorHistory.json @@ -0,0 +1,105 @@ +{ + "name": "deviceSensorHistory", + "displayName": "Device Sensor History", + "description": "Readings over the timeframe from the sensors of the selected devices that match the given names and kinds (their environmental sensors if neither is given)", + "tags": ["Sensor", "Power", "Environment"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "v1/devices/{{encodeURIComponent(object.rawId)}}/sensors", + "paging": { "mode": "nextUrl", "in": { "realm": "payload", "path": "nextPageUrl" } }, + "postRequestScript": "sensorHistory.js", + "errorHandling": { "type": "script", "script": "errorHandling/dceError.js" } + }, + "matches": { + "sourceType": { "type": "oneOf", "values": ["Device"] } + }, + "ui": [ + { + "type": "autocomplete", + "name": "sensorName", + "label": "Sensor name(s) (optional)", + "isMulti": true, + "isClearable": true, + "allowCustomValues": true, + "data": { + "source": "fixed", + "values": [ + { "value": "Battery Capacity Remaining", "label": "Battery Capacity Remaining" }, + { "value": "Battery Time Remaining", "label": "Battery Time Remaining" }, + { "value": "Output Load", "label": "Output Load" }, + { "value": "Battery Temperature", "label": "Battery Temperature" }, + { "value": "Battery Voltage", "label": "Battery Voltage" }, + { "value": "Input Voltage", "label": "Input Voltage" }, + { "value": "Output Voltage", "label": "Output Voltage" }, + { "value": "Output Current", "label": "Output Current" }, + { "value": "Input Frequency", "label": "Input Frequency" }, + { "value": "Output Frequency", "label": "Output Frequency" }, + { "value": "UPS Operation Mode", "label": "UPS Operation Mode" }, + { "value": "Battery Status", "label": "Battery Status" } + ] + } + }, + { + "type": "autocomplete", + "name": "kind", + "label": "Sensor kind(s) (optional)", + "isMulti": true, + "isClearable": true, + "allowCustomValues": true, + "data": { + "source": "fixed", + "values": [ + { "value": "TEMPERATURE", "label": "Temperature" }, + { "value": "DIFF_TEMP", "label": "Temperature difference" }, + { "value": "HUMIDITY", "label": "Humidity" }, + { "value": "DEW_POINT", "label": "Dew point" }, + { "value": "AIRFLOW", "label": "Airflow" }, + { "value": "CURRENT", "label": "Current" }, + { "value": "AMP_DETECTOR", "label": "Current (amp detector)" }, + { "value": "VOLTAGE", "label": "Voltage" }, + { "value": "POWER_WATTS", "label": "Power (W)" }, + { "value": "POWER_KWATTS", "label": "Power (kW)" }, + { "value": "POWER_VA", "label": "Apparent power (VA)" }, + { "value": "POWER_KVA", "label": "Apparent power (kVA)" }, + { "value": "ENERGY_KWH", "label": "Energy (kWh)" }, + { "value": "KWATT_HOUR", "label": "Energy (kW hour)" }, + { "value": "DOOR", "label": "Door" }, + { "value": "MOTION_SENSOR", "label": "Motion" }, + { "value": "DRY_CONTACT", "label": "Dry contact" }, + { "value": "COMMUNICATION_SENSOR", "label": "Communication" }, + { "value": "STATE", "label": "State" }, + { "value": "NUMBER", "label": "Number" } + ] + } + } + ], + "metadata": [ + { "name": "time", "displayName": "Time", "shape": "date", "role": "timestamp" }, + { "name": "value", "displayName": "Value", "shape": "number", "role": "value" }, + { "name": "series", "displayName": "Sensor", "shape": "string", "role": "label" }, + { "name": "units", "displayName": "Units", "shape": "string", "role": "unitLabel" }, + { "name": "category", "displayName": "Category", "shape": "string" }, + { "name": "valueText", "displayName": "Reading", "shape": "string" }, + { + "name": "severityText", + "displayName": "Status", + "shape": [ + "state", + { + "map": { + "success": ["Information", "Informational", "Normal", "OK", "Ok", "None"], + "warning": ["Warning"], + "error": ["Error", "Critical", "Failure"] + } + } + ] + }, + { "name": "sensorLabel", "displayName": "Sensor Name", "shape": "string" }, + { "sourceId": "deviceGuid", "sourceType": "Device", "name": "deviceLabel", "displayName": "Device", "shape": "string" }, + { "name": "kind", "displayName": "Kind", "shape": "string" }, + { "name": "sensorGuid", "displayName": "Sensor ID", "shape": "string", "visible": false }, + { "name": "deviceGuid", "displayName": "Device ID", "shape": "string", "visible": false } + ], + "timeframes": ["last1hour", "last12hours", "last24hours", "last7days", "last30days"] +} diff --git a/plugins/EcoStruxureIT-DCE/v1/dataStreams/devices.json b/plugins/EcoStruxureIT-DCE/v1/dataStreams/devices.json index aac8e3a1..a638abd9 100644 --- a/plugins/EcoStruxureIT-DCE/v1/dataStreams/devices.json +++ b/plugins/EcoStruxureIT-DCE/v1/dataStreams/devices.json @@ -7,11 +7,13 @@ "config": { "httpMethod": "get", "endpointPath": "v1/devices", + "postRequestScript": "statusText.js", "errorHandling": { "type": "script", "script": "errorHandling/dceError.js" } }, "matches": "none", "metadata": [ { "sourceId": "isxcGuid", "sourceType": "Device", "name": "label", "displayName": "Device", "shape": "string", "role": "label" }, + { "name": "statusText", "displayName": "Status Text", "shape": "string" }, { "name": "severityText", "displayName": "Status", @@ -19,7 +21,7 @@ "state", { "map": { - "success": ["Information", "Informational", "Normal", "OK", "None"], + "success": ["Information", "Informational", "Normal", "OK", "Ok", "None"], "warning": ["Warning"], "error": ["Error", "Critical", "Failure"] } diff --git a/plugins/EcoStruxureIT-DCE/v1/dataStreams/environmentalHistory.json b/plugins/EcoStruxureIT-DCE/v1/dataStreams/environmentalHistory.json new file mode 100644 index 00000000..1e6c2960 --- /dev/null +++ b/plugins/EcoStruxureIT-DCE/v1/dataStreams/environmentalHistory.json @@ -0,0 +1,45 @@ +{ + "name": "environmentalHistory", + "displayName": "Environmental History", + "description": "Readings over the timeframe from the environmental sensors (temperature, humidity, airflow, leak, door) of the selected devices", + "tags": ["Sensor", "Environment"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "v1/devices/{{encodeURIComponent(object.rawId)}}/sensors", + "paging": { "mode": "nextUrl", "in": { "realm": "payload", "path": "nextPageUrl" } }, + "postRequestScript": "sensorHistory.js", + "errorHandling": { "type": "script", "script": "errorHandling/dceError.js" } + }, + "matches": { + "sourceType": { "type": "oneOf", "values": ["Device"] } + }, + "metadata": [ + { "name": "time", "displayName": "Time", "shape": "date", "role": "timestamp" }, + { "name": "value", "displayName": "Value", "shape": "number", "role": "value" }, + { "name": "series", "displayName": "Sensor", "shape": "string", "role": "label" }, + { "name": "units", "displayName": "Units", "shape": "string", "role": "unitLabel" }, + { "name": "category", "displayName": "Category", "shape": "string" }, + { "name": "valueText", "displayName": "Reading", "shape": "string" }, + { + "name": "severityText", + "displayName": "Status", + "shape": [ + "state", + { + "map": { + "success": ["Information", "Informational", "Normal", "OK", "Ok", "None"], + "warning": ["Warning"], + "error": ["Error", "Critical", "Failure"] + } + } + ] + }, + { "name": "sensorLabel", "displayName": "Sensor Name", "shape": "string" }, + { "sourceId": "deviceGuid", "sourceType": "Device", "name": "deviceLabel", "displayName": "Device", "shape": "string" }, + { "name": "kind", "displayName": "Kind", "shape": "string" }, + { "name": "sensorGuid", "displayName": "Sensor ID", "shape": "string", "visible": false }, + { "name": "deviceGuid", "displayName": "Device ID", "shape": "string", "visible": false } + ], + "timeframes": ["last1hour", "last12hours", "last24hours", "last7days", "last30days"] +} diff --git a/plugins/EcoStruxureIT-DCE/v1/dataStreams/scripts/alarms.js b/plugins/EcoStruxureIT-DCE/v1/dataStreams/scripts/alarms.js index ee73a4c8..7f13934d 100644 --- a/plugins/EcoStruxureIT-DCE/v1/dataStreams/scripts/alarms.js +++ b/plugins/EcoStruxureIT-DCE/v1/dataStreams/scripts/alarms.js @@ -6,16 +6,34 @@ const unwrap = (v) => (Array.isArray(v) ? v[0] : v); const selectedDevices = (context.config && context.config.device) || []; const deviceIds = new Set(selectedDevices.map((o) => unwrap(o.rawId)).filter(Boolean)); +// One device group is read from /v1/deviceGroups/{guid}/alarms. Alarms don't say which group +// they belong to, so several groups can't be filtered from /v1/alarms; refuse rather than +// silently returning every alarm. +const selectedGroups = (context.config && context.config.deviceGroup) || []; +if (Array.isArray(selectedGroups) && selectedGroups.length > 1) { + throw new Error("Select a single device group. Active Alarms can only filter by one device group at a time."); +} + // Times are Unix milliseconds; a resolveTime of 0 means the alarm is still active. const toIso = (ms) => (typeof ms === "number" && ms > 0 ? new Date(ms).toISOString() : null); +// An alarm only carries its sensor's ID, not the sensor's kind, so classify it from its +// description (e.g. "Temperature violation", "Leak detected"). +const ENVIRONMENTAL = /temperature|humidity|dew point|air ?flow|leak|fluid|water|smoke|door|motion|cooling/i; +const POWER = /power|voltage|current|battery|load|outlet|input|output|bypass|frequency|phase|runtime|ups/i; +const categoryOf = (description) => + ENVIRONMENTAL.test(description || "") ? "Environmental" : POWER.test(description || "") ? "Power" : "Other"; + result = (Array.isArray(data) ? data : []) .filter((a) => a && a.isxcGuid) .filter((a) => deviceIds.size === 0 || deviceIds.has(a.deviceGuid)) .map((a) => ({ isxcGuid: a.isxcGuid, description: a.description, + category: categoryOf(a.description), severityText: a.severityText, + // DCE's own wording for badges; severityText becomes a mapped health state. + statusText: a.severityText || "Unknown", severity: a.severity, startTime: toIso(a.startTime), resolveTime: toIso(a.resolveTime), diff --git a/plugins/EcoStruxureIT-DCE/v1/dataStreams/scripts/sensorHistory.js b/plugins/EcoStruxureIT-DCE/v1/dataStreams/scripts/sensorHistory.js index d05fc3a0..4e9707ae 100644 --- a/plugins/EcoStruxureIT-DCE/v1/dataStreams/scripts/sensorHistory.js +++ b/plugins/EcoStruxureIT-DCE/v1/dataStreams/scripts/sensorHistory.js @@ -6,11 +6,76 @@ // at MAX_ROWS in total. Each sensor gets a share of that budget: numeric readings beyond it // are averaged into equal time buckets, and state readings are reduced to their changes (then // evenly sampled if there are still too many). +// +// Shared by the sensorHistory stream (scoped to Sensor objects) and the Device-scoped +// environmentalHistory and deviceSensorHistory streams. The Device-scoped ones have no sensor +// IDs to start from, so they run in two steps using nextUrl paging: the first request lists the +// device's sensors, and this script picks out the wanted ones (those matching the sensorName and +// kind parameters, or else the environmental ones) and returns their history URL as the next page. +// The second request is then an ordinary history request. DCE doesn't page either response (each +// is one complete array), so the stream's nextPageUrl path never matches anything and this script's +// pagingContext is the only "next page" there is. Two small calls per device stay well +// inside DCE's rate limit, unlike importing every sensor, which is too slow and too large on big +// installations. const MAX_ROWS = 20000; const toIso = (ms) => (typeof ms === "number" && ms > 0 ? new Date(ms).toISOString() : null); -const sensors = (Array.isArray(data) ? data : []) +// Keep these lists in step with sensors.js. +const ENVIRONMENTAL_KINDS = new Set([ + "TEMPERATURE", "DIFF_TEMP", "HUMIDITY", "DEW_POINT", "AIRFLOW", "VOL_AIRFLOW", "VOL_AIRFLOW_SEC", + "DOOR", "RA_DOOR", "RMT_DOOR", "MOTION_SENSOR" +]); +const ENVIRONMENTAL_LABEL = /temperature|humidity|dew point|air ?flow|leak|fluid|water|smoke|door|motion/i; +const POWER_KINDS = new Set([ + "POWER_KVA", "POWER_KWATTS", "K_REACT_POWER", "KVH", "KVA_HOUR", "KWATT_HOUR", "ENERGY_KWH", + "CURRENT", "AMP_DETECTOR", "POWER_VA", "POWER_WATTS", "VOLTAGE" +]); +const categoryOf = (kind, label, sensorType) => { + const k = String(kind || "").toUpperCase(); + if (ENVIRONMENTAL_KINDS.has(k) || ENVIRONMENTAL_LABEL.test(label || "")) return "Environmental"; + if (POWER_KINDS.has(k) || String(sensorType || "").startsWith("NUMBER_POWER")) return "Power"; + return "Other"; +}; + +// Names and kinds arrive as [{ value }] from a picker, or as plain strings when typed or set by a +// dashboard. Names are matched whole and ignoring case; they can contain commas ("State, Outlet 1"). +// Kinds suit devices whose sensor names vary by model, such as rack PDUs. +const pickedValues = (picked) => + new Set( + (Array.isArray(picked) ? picked : [picked]) + .map((n) => (n && typeof n === "object" ? n.value : n)) + .filter(Boolean) + .map((n) => String(n).trim().toLowerCase()) + ); +const sensorNames = pickedValues((context.config && context.config.sensorName) || []); +const sensorKinds = pickedValues((context.config && context.config.kind) || []); +const isWanted = (s) => + sensorNames.size === 0 && sensorKinds.size === 0 + ? categoryOf(s.kind, s.label, s.sensorType) === "Environmental" + : (sensorNames.size === 0 || sensorNames.has(String(s.label || "").trim().toLowerCase())) && + (sensorKinds.size === 0 || sensorKinds.has(String(s.kind || "").toLowerCase())); + +// Step one of the Device-scoped streams: the response is a device's sensor list, not history. +const isDeviceSensorList = /[/]devices[/][^/]+[/]sensors$/.test(url.pathname); +let historyUrl; +if (isDeviceSensorList) { + const guids = (Array.isArray(data) ? data : []) + .filter((s) => s && s.guid && isWanted(s)) + .map((s) => s.guid); + if (guids.length > 0) { + const apiBase = url.origin + url.pathname.replace(/[/]v1[/].*$/, ""); + const query = guids.map((g) => "sensorGuid=" + encodeURIComponent(g)).join("&"); + // Carry the data source's locale over, so the history's labels and units match step one. + const locale = url.searchParams.get("locale"); + historyUrl = + `${apiBase}/v1/sensors/history?${query}` + + `&startTime=${Date.parse(context.timeframe.start)}&endTime=${Date.parse(context.timeframe.end)}` + + (locale ? `&locale=${encodeURIComponent(locale)}` : ""); + } +} + +const sensors = (isDeviceSensorList ? [] : Array.isArray(data) ? data : []) .filter(Boolean) .map((sensor) => ({ sensor, @@ -20,12 +85,19 @@ const sensors = (Array.isArray(data) ? data : []) .sort((a, b) => a.time - b.time) })); +// A relay agent reads the plugin's output in the Windows code page rather than UTF-8, so a +// degree sign arrives as "°". Drop it ("°C" becomes "C") until the agent is fixed. +const asciiUnits = (text) => (typeof text === "string" ? text.replace(/°\s?/g, "") : text); + const rowFor = (sensor, time, value, valueText, severityText) => ({ time: toIso(time), value, - valueText, + valueText: asciiUnits(valueText), severityText, - units: sensor.units, + // DCE's own wording for badges; history readings often carry only a numeric severity. + statusText: severityText ? (/^(none|ok|normal)$/i.test(severityText) ? "OK" : severityText) : undefined, + units: asciiUnits(sensor.units), + category: categoryOf(sensor.kind, sensor.sensorLabel, sensor.sensorType), kind: sensor.kind, sensorGuid: sensor.sensorGuid, sensorLabel: sensor.sensorLabel, @@ -146,3 +218,7 @@ const rowsBySensor = new Map(); }); result = sensors.flatMap((entry) => rowsBySensor.get(entry)); + +// The history URL becomes the next page; leaving it undefined ends the stream (a device with no +// environmental sensors, or the history page itself). +pagingContext = historyUrl; diff --git a/plugins/EcoStruxureIT-DCE/v1/dataStreams/scripts/sensors.js b/plugins/EcoStruxureIT-DCE/v1/dataStreams/scripts/sensors.js index 8d64adfe..7639e7c6 100644 --- a/plugins/EcoStruxureIT-DCE/v1/dataStreams/scripts/sensors.js +++ b/plugins/EcoStruxureIT-DCE/v1/dataStreams/scripts/sensors.js @@ -72,6 +72,29 @@ const toNumber = (sensor) => { return parsed !== null ? parsed : parseReading(sensor.rawValue, "."); }; +// Environmental readings are what data center teams watch most. The kind identifies most of +// them; leak, smoke and similar detectors report as generic STATE sensors, so their label is +// checked too. Keep this list in step with sensorHistory.js. +const ENVIRONMENTAL_KINDS = new Set([ + "TEMPERATURE", "DIFF_TEMP", "HUMIDITY", "DEW_POINT", "AIRFLOW", "VOL_AIRFLOW", "VOL_AIRFLOW_SEC", + "DOOR", "RA_DOOR", "RMT_DOOR", "MOTION_SENSOR" +]); +const ENVIRONMENTAL_LABEL = /temperature|humidity|dew point|air ?flow|leak|fluid|water|smoke|door|motion/i; +const POWER_KINDS = new Set([ + "POWER_KVA", "POWER_KWATTS", "K_REACT_POWER", "KVH", "KVA_HOUR", "KWATT_HOUR", "ENERGY_KWH", + "CURRENT", "AMP_DETECTOR", "POWER_VA", "POWER_WATTS", "VOLTAGE" +]); +const categoryOf = (s) => { + const kind = String(s.kind || "").toUpperCase(); + if (ENVIRONMENTAL_KINDS.has(kind) || ENVIRONMENTAL_LABEL.test(s.label || "")) return "Environmental"; + if (POWER_KINDS.has(kind) || String(s.sensorType || "").startsWith("NUMBER_POWER")) return "Power"; + return "Other"; +}; + +// A relay agent reads the plugin's output in the Windows code page rather than UTF-8, so a +// degree sign arrives as "°". Drop it ("21.5 °C" becomes "21.5 C") until the agent is fixed. +const asciiUnits = (text) => (typeof text === "string" ? text.replace(/°\s?/g, "") : text); + result = sensors .filter((s) => deviceIds.size === 0 || deviceIds.has(s.podId)) .filter((s) => kinds.size === 0 || kinds.has(String(s.kind || "").toUpperCase())) @@ -79,12 +102,15 @@ result = sensors guid: s.guid, label: s.label || s.guid, deviceGuid: s.podId, + category: categoryOf(s), kind: s.kind, sensorType: s.sensorType, location: s.location, numericValue: toNumber(s), - units: s.units, - value: s.value, + units: asciiUnits(s.units), + value: asciiUnits(s.value), rawValue: s.rawValue, - severityText: s.severityText + severityText: s.severityText, + // DCE's own wording for badges (severityText becomes a mapped health state); "None" is healthy. + statusText: /^(none|ok|normal)$/i.test(String(s.severityText || "")) ? "OK" : s.severityText || "Unknown" })); diff --git a/plugins/EcoStruxureIT-DCE/v1/dataStreams/scripts/statusText.js b/plugins/EcoStruxureIT-DCE/v1/dataStreams/scripts/statusText.js new file mode 100644 index 00000000..41e08d12 --- /dev/null +++ b/plugins/EcoStruxureIT-DCE/v1/dataStreams/scripts/statusText.js @@ -0,0 +1,9 @@ +// Shared by the devices, deviceGroups and deviceGroupDevices streams. Adds statusText, Data +// Center Expert's own status wording for badges and legends. The severityText column maps to a +// SquaredUp health state, so anything derived from it in metadata sees "success" or "unknown" +// rather than "Warning" or "Information"; it has to be read here, before that mapping. +// DCE reports a healthy device or group as "None" (or "Ok"), shown here as "OK". +const statusTextOf = (severityText) => + /^(none|ok|normal)$/i.test(String(severityText || "")) ? "OK" : severityText || "Unknown"; + +result = (Array.isArray(data) ? data : []).map((row) => ({ ...row, statusText: statusTextOf(row.severityText) })); diff --git a/plugins/EcoStruxureIT-DCE/v1/dataStreams/sensorHistory.json b/plugins/EcoStruxureIT-DCE/v1/dataStreams/sensorHistory.json index 837b6e21..9bc4e617 100644 --- a/plugins/EcoStruxureIT-DCE/v1/dataStreams/sensorHistory.json +++ b/plugins/EcoStruxureIT-DCE/v1/dataStreams/sensorHistory.json @@ -23,7 +23,9 @@ { "name": "value", "displayName": "Value", "shape": "number", "role": "value" }, { "name": "series", "displayName": "Sensor", "shape": "string", "role": "label" }, { "name": "units", "displayName": "Units", "shape": "string", "role": "unitLabel" }, + { "name": "category", "displayName": "Category", "shape": "string" }, { "name": "valueText", "displayName": "Reading", "shape": "string" }, + { "name": "statusText", "displayName": "Status Text", "shape": "string" }, { "name": "severityText", "displayName": "Status", @@ -31,7 +33,7 @@ "state", { "map": { - "success": ["Information", "Informational", "Normal", "OK", "None"], + "success": ["Information", "Informational", "Normal", "OK", "Ok", "None"], "warning": ["Warning"], "error": ["Error", "Critical", "Failure"] } diff --git a/plugins/EcoStruxureIT-DCE/v1/dataStreams/sensorImport.json b/plugins/EcoStruxureIT-DCE/v1/dataStreams/sensorImport.json new file mode 100644 index 00000000..6ebee19d --- /dev/null +++ b/plugins/EcoStruxureIT-DCE/v1/dataStreams/sensorImport.json @@ -0,0 +1,70 @@ +{ + "name": "sensorImport", + "displayName": "Sensor Import", + "description": "Every sensor, when Import sensors is turned on in the data source (otherwise none)", + "tags": [ + "Sensor" + ], + "baseDataSourceName": "httpRequestUnscoped", + "visibility": { + "type": "hidden" + }, + "config": { + "httpMethod": "get", + "endpointPath": "{{ String(dataSource.importSensors) === 'true' ? 'v1/devices/sensors' : 'v1/deviceGroups/top/guid' }}", + "postRequestScript": "sensors.js", + "errorHandling": { + "type": "script", + "script": "errorHandling/dceError.js" + } + }, + "matches": "none", + "metadata": [ + { + "name": "label", + "displayName": "Sensor", + "shape": "string", + "role": "label" + }, + { + "name": "category", + "displayName": "Category", + "shape": "string" + }, + { + "name": "units", + "displayName": "Units", + "shape": "string", + "role": "unitLabel" + }, + { + "name": "kind", + "displayName": "Kind", + "shape": "string" + }, + { + "name": "sensorType", + "displayName": "Sensor Type", + "shape": "string" + }, + { + "name": "location", + "displayName": "Location", + "shape": "string" + }, + { + "name": "guid", + "displayName": "Sensor ID", + "shape": "string", + "role": "id", + "visible": false + }, + { + "name": "deviceGuid", + "displayName": "Device ID", + "shape": "string", + "visible": false + } + ], + "timeframes": false +} diff --git a/plugins/EcoStruxureIT-DCE/v1/dataStreams/sensors.json b/plugins/EcoStruxureIT-DCE/v1/dataStreams/sensors.json index c95f8efc..e809e860 100644 --- a/plugins/EcoStruxureIT-DCE/v1/dataStreams/sensors.json +++ b/plugins/EcoStruxureIT-DCE/v1/dataStreams/sensors.json @@ -6,7 +6,7 @@ "baseDataSourceName": "httpRequestUnscoped", "config": { "httpMethod": "get", - "endpointPath": "v1/devices/sensors", + "endpointPath": "{{ typeof device !== 'undefined' && Array.isArray(device) && device.length === 1 ? 'v1/devices/' + encodeURIComponent([].concat(device[0].rawId)[0]) + '/sensors' : 'v1/devices/sensors' }}", "postRequestScript": "sensors.js", "errorHandling": { "type": "script", "script": "errorHandling/dceError.js" } }, @@ -55,11 +55,13 @@ } ], "metadata": [ - { "sourceId": "guid", "sourceType": "Sensor", "name": "label", "displayName": "Sensor", "shape": "string", "role": "label" }, + { "name": "label", "displayName": "Sensor", "shape": "string", "role": "label" }, + { "name": "category", "displayName": "Category", "shape": "string" }, { "name": "deviceName", "displayName": "Device", "sourceId": "deviceGuid", "sourceType": "Device", "objectPropertyPath": "name" }, { "name": "numericValue", "displayName": "Value", "shape": "number", "role": "value" }, { "name": "units", "displayName": "Units", "shape": "string", "role": "unitLabel" }, { "name": "value", "displayName": "Reading", "shape": "string" }, + { "name": "statusText", "displayName": "Status Text", "shape": "string" }, { "name": "severityText", "displayName": "Status", @@ -67,7 +69,7 @@ "state", { "map": { - "success": ["Information", "Informational", "Normal", "OK", "None"], + "success": ["Information", "Informational", "Normal", "OK", "Ok", "None"], "warning": ["Warning"], "error": ["Error", "Critical", "Failure"] } @@ -78,7 +80,7 @@ { "name": "sensorType", "displayName": "Sensor Type", "shape": "string" }, { "name": "location", "displayName": "Location", "shape": "string" }, { "name": "guid", "displayName": "Sensor ID", "shape": "string", "role": "id", "visible": false }, - { "name": "deviceGuid", "displayName": "Device ID", "shape": "string", "visible": false }, + { "name": "deviceGuid", "displayName": "Device ID", "sourceId": "deviceGuid", "sourceType": "Device", "shape": "string", "visible": false }, { "name": "rawValue", "displayName": "Raw Value", "shape": "string", "visible": false } ], "timeframes": false diff --git a/plugins/EcoStruxureIT-DCE/v1/defaultContent/dataCenterOverview.dash.json b/plugins/EcoStruxureIT-DCE/v1/defaultContent/dataCenterOverview.dash.json index fa24932d..bdbbfe25 100644 --- a/plugins/EcoStruxureIT-DCE/v1/defaultContent/dataCenterOverview.dash.json +++ b/plugins/EcoStruxureIT-DCE/v1/defaultContent/dataCenterOverview.dash.json @@ -9,17 +9,292 @@ "version": 1, "contents": [ { - "i": "28324941-9c73-42f1-b4cb-d669ed188c03", + "i": "c76c2fd3-7dee-45d8-a9fd-3d676777a7c9", "x": 0, "y": 0, - "w": 2, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Devices", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[devices]}}", + "name": "devices", + "pluginConfigId": "{{configId}}", + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none", + "label": "Devices" + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "count", + "type": "quantitative", + "title": "Devices" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "7168c3b5-5372-41e1-87b3-542b57b98021", + "x": 1, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Devices Needing Attention", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[devices]}}", + "name": "devices", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "or", + "filters": [ + { + "column": "severityText", + "operation": "equals", + "value": "Warning" + }, + { + "column": "severityText", + "operation": "equals", + "value": "Error" + }, + { + "column": "severityText", + "operation": "equals", + "value": "Critical" + }, + { + "column": "severityText", + "operation": "equals", + "value": "Failure" + } + ] + }, + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none", + "label": "Devices Needing Attention" + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "count", + "type": "quantitative", + "title": "Devices Needing Attention" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "1fb9b63b-b699-4e64-9ec8-153bee9d8abb", + "x": 2, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Active Alarms", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[alarms]}}", + "name": "alarms", + "pluginConfigId": "{{configId}}", + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none", + "label": "Active Alarms" + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "count", + "type": "quantitative", + "title": "Active Alarms" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "d69bd2cf-4a3d-428b-897a-b72474962387", + "x": 3, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Environmental Alarms", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[alarms]}}", + "name": "alarms", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "category", + "operation": "equals", + "value": "Environmental" + } + ] + }, + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none", + "label": "Environmental Alarms" + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "count", + "type": "quantitative", + "title": "Environmental Alarms" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "28324941-9c73-42f1-b4cb-d669ed188c03", + "x": 0, + "y": 2, + "w": 1, "h": 3, "moved": false, "static": false, "z": 0, "config": { "_type": "tile/data-stream", - "title": "Devices by Status", + "title": "Device Health", "description": "", "timeframe": "none", "activePluginConfigIds": [ @@ -32,7 +307,7 @@ "group": { "by": [ [ - "severityText", + "statusText", "uniqueValues" ] ], @@ -48,69 +323,151 @@ "config": { "data-stream-donut-chart": { "valueColumn": "count", - "labelColumn": "severityText_uniqueValues", + "labelColumn": "statusText_uniqueValues", "hideCenterValue": false, "showValuesAsPercentage": false, "legendPosition": "auto", "legendMode": "table" } } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "pie", + "encoding": { + "label": { + "field": "statusText_uniqueValues", + "type": "nominal", + "title": "Status" + }, + "value": { + "field": "count", + "type": "quantitative", + "title": "Devices" + } + }, + "style": { + "pieInnerRadius": 60, + "pieDonutCenterValue": "total", + "labelFontSize": 16, + "pieLegendStyle": "table", + "mappedColors": { + "OK": "#2fb344", + "Normal": "#2fb344", + "Information": "#7cc68b", + "Informational": "#7cc68b", + "None": "#b2dfbb", + "Warning": "#f59f00", + "Error": "#e8590c", + "Critical": "#d63939", + "Failure": "#a61e1e" + } + } + } } } }, { - "i": "64d81670-e535-48bc-8f50-d20af00ec6aa", - "x": 2, - "y": 0, - "w": 2, + "i": "8cab1b6c-2ae2-4b5d-862b-a946c63ac99f", + "x": 1, + "y": 2, + "w": 1, "h": 3, "moved": false, "static": false, "z": 0, "config": { "_type": "tile/data-stream", - "title": "Alarms by Device Group", - "description": "", + "title": "Alarms by Category", + "description": "Active alarms by category", "timeframe": "none", "activePluginConfigIds": [ "{{configId}}" ], "dataStream": { - "id": "{{dataStreams.[alarmSummary]}}", - "name": "alarmSummary", - "pluginConfigId": "{{configId}}" + "id": "{{dataStreams.[alarms]}}", + "name": "alarms", + "pluginConfigId": "{{configId}}", + "group": { + "by": [ + [ + "category", + "uniqueValues" + ] + ], + "aggregate": [ + { + "type": "count" + } + ] + }, + "sort": { + "by": [ + [ + "count", + "desc" + ] + ] + } }, "visualisation": { - "type": "data-stream-table", + "type": "data-stream-donut-chart", "config": { - "data-stream-table": { - "transpose": false, - "columnOrder": [ - "deviceGroupName", - "status", - "criticalAlarmCount", - "warningAlarmCount", - "infoAlarmCount", - "totalAlarmCount" - ] + "data-stream-donut-chart": { + "valueColumn": "count", + "labelColumn": "category_uniqueValues", + "hideCenterValue": false, + "showValuesAsPercentage": false, + "legendPosition": "auto", + "legendMode": "table" + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "pie", + "encoding": { + "label": { + "field": "category_uniqueValues", + "type": "nominal", + "title": "Category" + }, + "value": { + "field": "count", + "type": "quantitative", + "title": "Alarms" + } + }, + "style": { + "pieInnerRadius": 60, + "pieDonutCenterValue": "total", + "labelFontSize": 16, + "pieLegendStyle": "table", + "mappedColors": { + "Environmental": "#1c7ed6", + "Power": "#7048e8", + "Other": "#868e96" + } } } } } }, { - "i": "a5683fcf-81f3-4269-95da-9c7b718f52f7", - "x": 0, - "y": 3, - "w": 4, - "h": 4, + "i": "0308d883-3885-4b49-b8d6-c08bd81d181c", + "x": 2, + "y": 2, + "w": 2, + "h": 3, "moved": false, "static": false, "z": 0, "config": { "_type": "tile/data-stream", - "title": "Active Alarms", - "description": "", + "title": "Environmental Alarms", + "description": "Active temperature, humidity, airflow, leak and door alarms", "timeframe": "none", "activePluginConfigIds": [ "{{configId}}" @@ -118,7 +475,17 @@ "dataStream": { "id": "{{dataStreams.[alarms]}}", "name": "alarms", - "pluginConfigId": "{{configId}}" + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "category", + "operation": "equals", + "value": "Environmental" + } + ] + } }, "visualisation": { "type": "data-stream-table", @@ -129,14 +496,71 @@ "severityText", "description", "deviceName", - "sensorName", "startTime", "durationSeconds", "recommendedAction" ], "hiddenColumns": [ - "hidden", - "alarmAlertSuppressed" + "alarmAlertSuppressed", + "category", + "hidden" + ] + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "statusText", + "title": "Severity", + "renderer": { + "type": "badge", + "colors": { + "OK": "#2fb344", + "Normal": "#2fb344", + "None": "#2fb344", + "Information": "#2fb344", + "Informational": "#2fb344", + "Warning": "#f59f00", + "Error": "#d63939", + "Critical": "#d63939", + "Failure": "#d63939" + } + } + }, + { + "field": "description", + "title": "Alarm", + "renderer": { + "type": "text" + } + }, + { + "field": "deviceName", + "title": "Device", + "renderer": { + "type": "text" + } + }, + { + "field": "startTime", + "title": "Started", + "renderer": { + "type": "date", + "relative": true + } + }, + { + "field": "recommendedAction", + "title": "Recommended Action", + "renderer": { + "type": "text" + } + } ] } } @@ -144,18 +568,149 @@ } }, { - "i": "4bda965d-57dc-4542-be3d-d7b8aeb42d01", + "i": "64d81670-e535-48bc-8f50-d20af00ec6aa", "x": 0, - "y": 7, - "w": 4, - "h": 3, + "y": 5, + "w": 2, + "h": 4, "moved": false, "static": false, "z": 0, "config": { "_type": "tile/data-stream", - "title": "Alarm History", + "title": "Device Groups with Most Alarms", "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[alarmSummary]}}", + "name": "alarmSummary", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "totalAlarmCount", + "operation": "greaterthan", + "value": "0" + }, + { + "column": "deviceGroupGuid", + "operation": "notcontains", + "value": "RootDeviceGroup" + } + ] + }, + "sort": { + "by": [ + [ + "criticalAlarmCount", + "desc" + ], + [ + "warningAlarmCount", + "desc" + ], + [ + "totalAlarmCount", + "desc" + ] + ], + "top": 10 + } + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "deviceGroupName", + "status", + "criticalAlarmCount", + "warningAlarmCount", + "infoAlarmCount", + "totalAlarmCount" + ] + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "deviceGroupName", + "title": "Device Group", + "renderer": { + "type": "text" + } + }, + { + "field": "status", + "title": "Status", + "renderer": { + "type": "status", + "showLabel": false + }, + "width": 70, + "align": "center" + }, + { + "field": "criticalAlarmCount", + "title": "Critical", + "renderer": { + "type": "bar", + "color": "#d63939" + } + }, + { + "field": "warningAlarmCount", + "title": "Warning", + "renderer": { + "type": "bar", + "color": "#f59f00" + } + }, + { + "field": "infoAlarmCount", + "title": "Info", + "renderer": { + "type": "number" + }, + "align": "right" + }, + { + "field": "totalAlarmCount", + "title": "Total", + "renderer": { + "type": "number" + }, + "align": "right" + } + ] + } + } + } + } + }, + { + "i": "4bda965d-57dc-4542-be3d-d7b8aeb42d01", + "x": 2, + "y": 5, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Alarm History (Last 24 Hours)", + "description": "Alarms resolved per hour by category. Across all devices, alarm history covers the last 24 hours at most.", "activePluginConfigIds": [ "{{configId}}" ], @@ -166,11 +721,11 @@ "group": { "by": [ [ - "startTime", - "byDay" + "resolveTime", + "byHour" ], [ - "severityText", + "category", "uniqueValues" ] ], @@ -183,7 +738,7 @@ "sort": { "by": [ [ - "startTime_byDay", + "resolveTime_byHour", "asc" ] ] @@ -193,11 +748,11 @@ "type": "data-stream-bar-chart", "config": { "data-stream-bar-chart": { - "xAxisData": "startTime_byDay", + "xAxisData": "resolveTime_byHour", "yAxisData": [ "count" ], - "xAxisGroup": "severityText_uniqueValues", + "xAxisGroup": "category_uniqueValues", "xAxisLabel": "", "yAxisLabel": "Alarms", "showXAxisLabel": false, @@ -215,13 +770,46 @@ } } } - } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "bar", + "encoding": { + "x": { + "field": "resolveTime_byHour", + "type": "temporal", + "title": "Resolved" + }, + "y": { + "field": "count", + "type": "quantitative", + "title": "Alarms" + }, + "series": { + "field": "category_uniqueValues", + "type": "nominal", + "title": "Category" + } + }, + "style": { + "cartesianStack": "on", + "legendPosition": "bottom", + "mappedColors": { + "Environmental": "#1c7ed6", + "Power": "#7048e8", + "Other": "#868e96" + } + } + } + }, + "timeframe": "last24hours" } }, { - "i": "a3922981-af15-457f-a3cf-b27bc956c1b1", + "i": "a5683fcf-81f3-4269-95da-9c7b718f52f7", "x": 0, - "y": 10, + "y": 9, "w": 4, "h": 4, "moved": false, @@ -229,15 +817,15 @@ "z": 0, "config": { "_type": "tile/data-stream", - "title": "Devices", + "title": "Active Alarms", "description": "", "timeframe": "none", "activePluginConfigIds": [ "{{configId}}" ], "dataStream": { - "id": "{{dataStreams.[devices]}}", - "name": "devices", + "id": "{{dataStreams.[alarms]}}", + "name": "alarms", "pluginConfigId": "{{configId}}" }, "visualisation": { @@ -246,15 +834,85 @@ "data-stream-table": { "transpose": false, "columnOrder": [ - "label", "severityText", - "type", - "model", - "hostname", - "ipAddress", - "location", - "serialNumber", - "inMaintenance" + "description", + "deviceName", + "startTime", + "durationSeconds", + "recommendedAction" + ], + "hiddenColumns": [ + "hidden", + "alarmAlertSuppressed" + ] + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "statusText", + "title": "Severity", + "renderer": { + "type": "badge", + "colors": { + "OK": "#2fb344", + "Normal": "#2fb344", + "None": "#2fb344", + "Information": "#2fb344", + "Informational": "#2fb344", + "Warning": "#f59f00", + "Error": "#d63939", + "Critical": "#d63939", + "Failure": "#d63939" + } + } + }, + { + "field": "category", + "title": "Category", + "renderer": { + "type": "badge", + "colors": { + "Environmental": "#1c7ed6", + "Power": "#7048e8", + "Other": "#868e96" + } + } + }, + { + "field": "description", + "title": "Alarm", + "renderer": { + "type": "text" + } + }, + { + "field": "deviceName", + "title": "Device", + "renderer": { + "type": "text" + } + }, + { + "field": "startTime", + "title": "Started", + "renderer": { + "type": "date", + "relative": true + } + }, + { + "field": "recommendedAction", + "title": "Recommended Action", + "renderer": { + "type": "text" + } + } ] } } @@ -262,25 +920,25 @@ } }, { - "i": "de3e4732-ac67-4e23-ba6f-edce015f3598", + "i": "a3922981-af15-457f-a3cf-b27bc956c1b1", "x": 0, - "y": 14, + "y": 13, "w": 4, - "h": 3, + "h": 5, "moved": false, "static": false, "z": 0, "config": { "_type": "tile/data-stream", - "title": "Device Groups", + "title": "Devices", "description": "", "timeframe": "none", "activePluginConfigIds": [ "{{configId}}" ], "dataStream": { - "id": "{{dataStreams.[deviceGroups]}}", - "name": "deviceGroups", + "id": "{{dataStreams.[devices]}}", + "name": "devices", "pluginConfigId": "{{configId}}" }, "visualisation": { @@ -289,20 +947,125 @@ "data-stream-table": { "transpose": false, "columnOrder": [ - "name", + "label", "severityText", - "deviceCount", - "parentName" + "type", + "model", + "hostname", + "ipAddress", + "location", + "serialNumber", + "inMaintenance" + ] + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "label", + "title": "Device", + "renderer": { + "type": "text" + } + }, + { + "field": "statusText", + "title": "Status", + "renderer": { + "type": "badge", + "colors": { + "OK": "#2fb344", + "Normal": "#2fb344", + "None": "#2fb344", + "Information": "#2fb344", + "Informational": "#2fb344", + "Warning": "#f59f00", + "Error": "#d63939", + "Critical": "#d63939", + "Failure": "#d63939" + } + } + }, + { + "field": "type", + "title": "Type", + "renderer": { + "type": "text" + } + }, + { + "field": "model", + "title": "Model", + "renderer": { + "type": "text" + } + }, + { + "field": "location", + "title": "Location", + "renderer": { + "type": "text" + } + }, + { + "field": "ipAddress", + "title": "IP Address", + "renderer": { + "type": "text" + } + }, + { + "field": "hostname", + "title": "Hostname", + "renderer": { + "type": "text" + } + }, + { + "field": "serialNumber", + "title": "Serial Number", + "renderer": { + "type": "text" + } + }, + { + "field": "inMaintenance", + "title": "In Maintenance", + "renderer": { + "type": "boolean", + "trueLabel": "Yes", + "falseLabel": "No", + "trueColor": "#f59f00", + "falseColor": "#868e96" + }, + "align": "center" + } ] + }, + "style": { + "richTableRowHighlight": { + "field": "statusText", + "colors": { + "Warning": "rgba(245,159,0,0.12)", + "Error": "rgba(214,57,57,0.12)", + "Critical": "rgba(214,57,57,0.12)", + "Failure": "rgba(214,57,57,0.12)" + } + } } } } } }, { - "i": "10fc902e-7a2e-41ad-9075-dc080646a36a", + "i": "de3e4732-ac67-4e23-ba6f-edce015f3598", "x": 0, - "y": 17, + "y": 18, "w": 4, "h": 4, "moved": false, @@ -310,23 +1073,16 @@ "z": 0, "config": { "_type": "tile/data-stream", - "title": "Event Log", + "title": "Device Groups", "description": "", + "timeframe": "none", "activePluginConfigIds": [ "{{configId}}" ], "dataStream": { - "id": "{{dataStreams.[eventLog]}}", - "name": "eventLog", - "pluginConfigId": "{{configId}}", - "sort": { - "by": [ - [ - "timeStamp", - "desc" - ] - ] - } + "id": "{{dataStreams.[deviceGroups]}}", + "name": "deviceGroups", + "pluginConfigId": "{{configId}}" }, "visualisation": { "type": "data-stream-table", @@ -334,13 +1090,71 @@ "data-stream-table": { "transpose": false, "columnOrder": [ - "timeStamp", - "severity", - "message", - "categories", - "remoteUser", - "remoteAddr" + "name", + "severityText", + "deviceCount", + "parentName" + ] + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "name", + "title": "Device Group", + "renderer": { + "type": "text" + } + }, + { + "field": "statusText", + "title": "Status", + "renderer": { + "type": "badge", + "colors": { + "OK": "#2fb344", + "Normal": "#2fb344", + "None": "#2fb344", + "Information": "#2fb344", + "Informational": "#2fb344", + "Warning": "#f59f00", + "Error": "#d63939", + "Critical": "#d63939", + "Failure": "#d63939" + } + } + }, + { + "field": "deviceCount", + "title": "Devices", + "renderer": { + "type": "bar" + } + }, + { + "field": "parentName", + "title": "Parent Group", + "renderer": { + "type": "text" + } + } ] + }, + "style": { + "richTableRowHighlight": { + "field": "statusText", + "colors": { + "Warning": "rgba(245,159,0,0.12)", + "Error": "rgba(214,57,57,0.12)", + "Critical": "rgba(214,57,57,0.12)", + "Failure": "rgba(214,57,57,0.12)" + } + } } } } diff --git a/plugins/EcoStruxureIT-DCE/v1/defaultContent/device.dash.json b/plugins/EcoStruxureIT-DCE/v1/defaultContent/device.dash.json index 94b06d4d..fb1c91a3 100644 --- a/plugins/EcoStruxureIT-DCE/v1/defaultContent/device.dash.json +++ b/plugins/EcoStruxureIT-DCE/v1/defaultContent/device.dash.json @@ -15,7 +15,7 @@ "x": 0, "y": 0, "w": 1, - "h": 4, + "h": 5, "moved": false, "static": false, "z": 0, @@ -50,11 +50,503 @@ } }, { - "i": "32412c4f-4de0-4d1d-a72f-7685fd0327d9", + "i": "648caef5-6d8b-460b-bf37-9bb02313a465", "x": 1, "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Temperature", + "description": "Highest room temperature reading on the device, against ASHRAE bands (°F). Excludes battery temperature.", + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[sensors]}}", + "name": "sensors", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "device": { + "variable": "{{variables.[Device]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Devices]}}" + } + }, + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "kind", + "operation": "equals", + "value": "TEMPERATURE" + }, + { + "column": "label", + "operation": "notcontains", + "value": "Battery" + }, + { + "column": "numericValue", + "operation": "notempty" + } + ] + } + }, + "variables": [ + "{{variables.[Device]}}" + ], + "visualisation": { + "type": "data-stream-gauge", + "config": { + "data-stream-gauge": { + "value": { + "type": "mean", + "columns": [ + "numericValue" + ] + }, + "minimum": 50, + "maximum": 100 + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "gauge", + "encoding": { + "value": { + "field": "numericValue", + "type": "quantitative", + "title": "Reading", + "aggregate": "max" + } + }, + "style": { + "gaugeMin": 50, + "gaugeMax": 100, + "gaugeBands": [ + { + "to": 0.18, + "color": "#d63939" + }, + { + "to": 0.288, + "color": "#f59f00" + }, + { + "to": 0.612, + "color": "#2fb344" + }, + { + "to": 0.792, + "color": "#f59f00" + }, + { + "to": 1.0, + "color": "#d63939" + } + ], + "gaugeAxisLabels": true, + "gaugeLabelText": "Highest (F)", + "labelFormat": "{{value}}" + } + } + } + } + }, + { + "i": "bfef0f7b-1e21-49fe-85ce-c6c8034064f1", + "x": 2, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Humidity", + "description": "Average relative humidity on the device, against ASHRAE bands.", + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[sensors]}}", + "name": "sensors", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "device": { + "variable": "{{variables.[Device]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Devices]}}" + } + }, + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "kind", + "operation": "equals", + "value": "HUMIDITY" + }, + { + "column": "numericValue", + "operation": "notempty" + } + ] + } + }, + "variables": [ + "{{variables.[Device]}}" + ], + "visualisation": { + "type": "data-stream-gauge", + "config": { + "data-stream-gauge": { + "value": { + "type": "mean", + "columns": [ + "numericValue" + ] + }, + "minimum": 0, + "maximum": 100 + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "gauge", + "encoding": { + "value": { + "field": "numericValue", + "type": "quantitative", + "title": "Reading", + "aggregate": "avg" + } + }, + "style": { + "gaugeMin": 0, + "gaugeMax": 100, + "gaugeBands": [ + { + "to": 0.08, + "color": "#d63939" + }, + { + "to": 0.7, + "color": "#2fb344" + }, + { + "to": 0.8, + "color": "#f59f00" + }, + { + "to": 1.0, + "color": "#d63939" + } + ], + "gaugeAxisLabels": true, + "gaugeLabelText": "Average (%RH)", + "labelFormat": "{{value}}" + } + } + } + } + }, + { + "i": "09eeff8b-047f-491e-a96c-ae1627074095", + "x": 3, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Device Status", + "description": "The device's own status in Data Center Expert, coloured by its severity.", + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[sensors]}}", + "name": "sensors", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "device": { + "variable": "{{variables.[Device]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Devices]}}" + } + }, + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "kind", + "operation": "equals", + "value": "STATE" + }, + { + "column": "label", + "operation": "contains", + "value": "Device Status" + } + ] + } + }, + "variables": [ + "{{variables.[Device]}}" + ], + "visualisation": { + "type": "data-stream-blocks", + "config": { + "data-stream-blocks": { + "labelColumn": "label", + "stateColumn": "severityText", + "sublabel": "value", + "linkColumn": "none", + "columns": 1 + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "statusGrid", + "encoding": { + "label": { + "field": "label", + "type": "nominal", + "title": "Sensor" + }, + "status": { + "field": "severityText", + "type": "nominal", + "title": "Status" + }, + "value": { + "field": "value", + "type": "nominal", + "title": "State" + } + }, + "style": { + "statusGridColumnsPerRow": 1, + "statusGridOrderByField": "label", + "statusGridOrderDirection": "asc", + "statusGridLabelWrap": true + } + } + } + } + }, + { + "i": "08b0e08a-d04b-490a-abfd-78b76233d0c7", + "x": 1, + "y": 2, "w": 3, - "h": 4, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Temperature History", + "description": "Room temperature sensors over the dashboard timeframe (battery temperature excluded). Green band: ASHRAE recommended range (64.4-80.6 °F); amber lines: ASHRAE class A1 allowable limits (59 and 89.6 °F).", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[environmentalHistory]}}", + "name": "environmentalHistory", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "kind", + "operation": "equals", + "value": "TEMPERATURE" + }, + { + "column": "sensorLabel", + "operation": "notcontains", + "value": "Battery" + } + ] + } + }, + "scope": { + "scope": "{{scopes.[Devices]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Device]}}" + }, + "variables": [ + "{{variables.[Device]}}" + ], + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "xAxisColumn": "time", + "yAxisColumn": [ + "value" + ], + "seriesColumn": "sensorLabel", + "showLegend": true, + "legendPosition": "bottom", + "yAxisLabel": "", + "showYAxisLabel": false, + "showTrendLine": false + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "line", + "encoding": { + "x": { + "field": "time", + "type": "temporal", + "title": "Time" + }, + "y": { + "field": "value", + "type": "quantitative", + "title": "Temperature" + }, + "series": { + "field": "sensorLabel", + "type": "nominal", + "title": "Sensor Name" + } + }, + "style": { + "legendPosition": "bottom", + "cartesianYAxisUnitColumn": "units", + "cartesianYAxisMin": 50, + "cartesianYAxisMax": 100 + }, + "markAreas": [ + { + "from": 64.4, + "to": 80.6, + "label": "ASHRAE recommended", + "color": "rgba(82,196,26,0.12)" + } + ], + "markLines": [ + { + "value": 59, + "label": "A1 allowable min", + "color": "#f59e0b" + }, + { + "value": 89.6, + "label": "A1 allowable max", + "color": "#f59e0b" + } + ] + } + } + } + }, + { + "i": "53def2f3-4261-472f-92e5-5e2e69a18b70", + "x": 0, + "y": 5, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Environmental Readings", + "description": "Current temperature, humidity, airflow, leak and door readings", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[sensors]}}", + "name": "sensors", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "device": { + "variable": "{{variables.[Device]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Devices]}}" + } + }, + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "category", + "operation": "equals", + "value": "Environmental" + } + ] + } + }, + "variables": [ + "{{variables.[Device]}}" + ], + "visualisation": { + "type": "data-stream-blocks", + "config": { + "data-stream-blocks": { + "labelColumn": "label", + "stateColumn": "severityText", + "sublabel": "value", + "linkColumn": "none", + "columns": 4 + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "statusGrid", + "encoding": { + "label": { + "field": "label", + "type": "nominal", + "title": "Sensor" + }, + "status": { + "field": "severityText", + "type": "nominal", + "title": "Status" + }, + "value": { + "field": "value", + "type": "nominal", + "title": "Reading" + } + }, + "style": { + "statusGridColumnsPerRow": 4, + "statusGridOrderByField": "label", + "statusGridOrderDirection": "asc", + "statusGridLabelWrap": false, + "statusGridMaxBlockHeight": 120 + } + } + } + } + }, + { + "i": "32412c4f-4de0-4d1d-a72f-7685fd0327d9", + "x": 2, + "y": 5, + "w": 2, + "h": 3, "moved": false, "static": false, "z": 0, @@ -89,7 +581,6 @@ "columnOrder": [ "severityText", "description", - "sensorName", "startTime", "durationSeconds", "recommendedAction" @@ -101,59 +592,155 @@ ] } } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "statusText", + "title": "Severity", + "renderer": { + "type": "badge", + "colors": { + "OK": "#2fb344", + "Normal": "#2fb344", + "None": "#2fb344", + "Information": "#2fb344", + "Informational": "#2fb344", + "Warning": "#f59f00", + "Error": "#d63939", + "Critical": "#d63939", + "Failure": "#d63939" + } + } + }, + { + "field": "category", + "title": "Category", + "renderer": { + "type": "badge", + "colors": { + "Environmental": "#1c7ed6", + "Power": "#7048e8", + "Other": "#868e96" + } + } + }, + { + "field": "description", + "title": "Alarm", + "renderer": { + "type": "text" + } + }, + { + "field": "startTime", + "title": "Started", + "renderer": { + "type": "date", + "relative": true + } + }, + { + "field": "recommendedAction", + "title": "Recommended Action", + "renderer": { + "type": "text" + } + } + ] + } + } } } }, { - "i": "53def2f3-4261-472f-92e5-5e2e69a18b70", + "i": "61a90715-94e6-4526-becf-de20e90af90e", "x": 0, - "y": 4, - "w": 4, - "h": 4, + "y": 8, + "w": 2, + "h": 3, "moved": false, "static": false, "z": 0, "config": { "_type": "tile/data-stream", - "title": "Sensors", - "description": "", - "timeframe": "none", + "title": "Environmental History", + "description": "Trend and latest reading of each numeric environmental sensor (temperature, humidity, dew point, airflow) over the dashboard timeframe", "activePluginConfigIds": [ "{{configId}}" ], "dataStream": { - "id": "{{dataStreams.[sensors]}}", - "name": "sensors", + "id": "{{dataStreams.[environmentalHistory]}}", + "name": "environmentalHistory", "pluginConfigId": "{{configId}}", - "dataSourceConfig": { - "device": { - "variable": "{{variables.[Device]}}", - "workspace": "{{workspaceId}}", - "scope": "{{scopes.[Devices]}}" - } + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "value", + "operation": "notempty" + }, + { + "column": "sensorLabel", + "operation": "notcontains", + "value": "Can be Open For" + } + ] } }, + "scope": { + "scope": "{{scopes.[Devices]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Device]}}" + }, "variables": [ "{{variables.[Device]}}" ], "visualisation": { - "type": "data-stream-table", + "type": "data-stream-line-graph", "config": { - "data-stream-table": { - "transpose": false, - "columnOrder": [ - "label", - "severityText", - "value", - "numericValue", - "units", - "kind", - "sensorType", - "location" + "data-stream-line-graph": { + "xAxisColumn": "time", + "yAxisColumn": [ + "value" ], - "hiddenColumns": [ - "deviceName" - ] + "seriesColumn": "sensorLabel", + "showLegend": true, + "legendPosition": "bottom", + "yAxisLabel": "", + "showYAxisLabel": false, + "showTrendLine": false + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "sparkline", + "encoding": { + "time": { + "field": "time", + "type": "temporal", + "title": "Time" + }, + "value": { + "field": "value", + "type": "quantitative", + "title": "Value" + }, + "label": { + "field": "sensorLabel", + "type": "nominal", + "title": "Sensor Name" + } + }, + "style": { + "sparklineValue": "latest", + "sparklineSortBy": "label" } } } @@ -161,10 +748,10 @@ }, { "i": "5832506d-2f53-42f9-81a1-0232eadd640e", - "x": 0, + "x": 2, "y": 8, - "w": 4, - "h": 4, + "w": 2, + "h": 3, "moved": false, "static": false, "z": 0, @@ -198,7 +785,6 @@ "columnOrder": [ "severityText", "description", - "sensorName", "startTime", "resolveTime", "durationSeconds" @@ -211,6 +797,73 @@ ] } } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "statusText", + "title": "Severity", + "renderer": { + "type": "badge", + "colors": { + "OK": "#2fb344", + "Normal": "#2fb344", + "None": "#2fb344", + "Information": "#2fb344", + "Informational": "#2fb344", + "Warning": "#f59f00", + "Error": "#d63939", + "Critical": "#d63939", + "Failure": "#d63939" + } + } + }, + { + "field": "category", + "title": "Category", + "renderer": { + "type": "badge", + "colors": { + "Environmental": "#1c7ed6", + "Power": "#7048e8", + "Other": "#868e96" + } + } + }, + { + "field": "description", + "title": "Alarm", + "renderer": { + "type": "text" + } + }, + { + "field": "startTime", + "title": "Started", + "renderer": { + "type": "date" + } + }, + { + "field": "resolveTime", + "title": "Resolved", + "renderer": { + "type": "date" + } + } + ] + }, + "style": { + "richTableSort": { + "field": "startTime", + "direction": "desc" + } + } + } } } } diff --git a/plugins/EcoStruxureIT-DCE/v1/defaultContent/deviceGroup.dash.json b/plugins/EcoStruxureIT-DCE/v1/defaultContent/deviceGroup.dash.json index 09ba9421..13f1b4fc 100644 --- a/plugins/EcoStruxureIT-DCE/v1/defaultContent/deviceGroup.dash.json +++ b/plugins/EcoStruxureIT-DCE/v1/defaultContent/deviceGroup.dash.json @@ -14,8 +14,8 @@ "i": "dcccef89-6a4c-4a94-b3e8-349194170dbc", "x": 0, "y": 0, - "w": 2, - "h": 3, + "w": 1, + "h": 4, "moved": false, "static": false, "z": 0, @@ -51,10 +51,10 @@ }, { "i": "dd120dd0-e1f0-42fc-a488-14f350ff949e", - "x": 2, + "x": 1, "y": 0, - "w": 2, - "h": 3, + "w": 1, + "h": 4, "moved": false, "static": false, "z": 0, @@ -98,15 +98,174 @@ ] } } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "criticalAlarmCount", + "type": "quantitative", + "title": "Critical", + "aggregate": "sum" + }, + { + "field": "warningAlarmCount", + "type": "quantitative", + "title": "Warning", + "aggregate": "sum" + }, + { + "field": "infoAlarmCount", + "type": "quantitative", + "title": "Informational", + "aggregate": "sum" + }, + { + "field": "totalAlarmCount", + "type": "quantitative", + "title": "Total", + "aggregate": "sum" + } + ] + }, + "style": { + "scalarColumns": 2 + } + } + } + } + }, + { + "i": "f0aaf1f2-a8b6-4be2-9554-56be51911800", + "x": 2, + "y": 0, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Active Alarms", + "description": "Active alarms on devices in this group and its sub-groups", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[alarms]}}", + "name": "alarms", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "deviceGroup": { + "variable": "{{variables.[Device Group]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Device Groups]}}" + } + } + }, + "variables": [ + "{{variables.[Device Group]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "severityText", + "description", + "deviceName", + "startTime", + "durationSeconds" + ], + "hiddenColumns": [ + "deviceName", + "hidden", + "alarmAlertSuppressed" + ] + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "statusText", + "title": "Severity", + "renderer": { + "type": "badge", + "colors": { + "OK": "#2fb344", + "Normal": "#2fb344", + "None": "#2fb344", + "Information": "#2fb344", + "Informational": "#2fb344", + "Warning": "#f59f00", + "Error": "#d63939", + "Critical": "#d63939", + "Failure": "#d63939" + } + } + }, + { + "field": "category", + "title": "Category", + "renderer": { + "type": "badge", + "colors": { + "Environmental": "#1c7ed6", + "Power": "#7048e8", + "Other": "#868e96" + } + } + }, + { + "field": "description", + "title": "Alarm", + "renderer": { + "type": "text" + } + }, + { + "field": "deviceName", + "title": "Device", + "renderer": { + "type": "text" + } + }, + { + "field": "startTime", + "title": "Started", + "renderer": { + "type": "date", + "relative": true + } + } + ] + }, + "style": { + "richTableSort": { + "field": "startTime", + "direction": "desc" + } + } + } } } }, { "i": "1b35a20c-ed9c-4f99-9b6e-c7e7d786e347", "x": 0, - "y": 3, + "y": 4, "w": 4, - "h": 4, + "h": 5, "moved": false, "static": false, "z": 0, @@ -149,6 +308,106 @@ ] } } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "label", + "title": "Device", + "renderer": { + "type": "text" + } + }, + { + "field": "statusText", + "title": "Status", + "renderer": { + "type": "badge", + "colors": { + "OK": "#2fb344", + "Normal": "#2fb344", + "None": "#2fb344", + "Information": "#2fb344", + "Informational": "#2fb344", + "Warning": "#f59f00", + "Error": "#d63939", + "Critical": "#d63939", + "Failure": "#d63939" + } + } + }, + { + "field": "type", + "title": "Type", + "renderer": { + "type": "text" + } + }, + { + "field": "model", + "title": "Model", + "renderer": { + "type": "text" + } + }, + { + "field": "location", + "title": "Location", + "renderer": { + "type": "text" + } + }, + { + "field": "ipAddress", + "title": "IP Address", + "renderer": { + "type": "text" + } + }, + { + "field": "hostname", + "title": "Hostname", + "renderer": { + "type": "text" + } + }, + { + "field": "serialNumber", + "title": "Serial Number", + "renderer": { + "type": "text" + } + }, + { + "field": "inMaintenance", + "title": "In Maintenance", + "renderer": { + "type": "boolean", + "trueLabel": "Yes", + "falseLabel": "No", + "trueColor": "#f59f00", + "falseColor": "#868e96" + }, + "align": "center" + } + ] + }, + "style": { + "richTableRowHighlight": { + "field": "statusText", + "colors": { + "Warning": "rgba(245,159,0,0.12)", + "Error": "rgba(214,57,57,0.12)", + "Critical": "rgba(214,57,57,0.12)", + "Failure": "rgba(214,57,57,0.12)" + } + } + } + } } } } diff --git a/plugins/EcoStruxureIT-DCE/v1/defaultContent/environmental.dash.json b/plugins/EcoStruxureIT-DCE/v1/defaultContent/environmental.dash.json new file mode 100644 index 00000000..cd492b54 --- /dev/null +++ b/plugins/EcoStruxureIT-DCE/v1/defaultContent/environmental.dash.json @@ -0,0 +1,891 @@ +{ + "name": "Environmental", + "schemaVersion": "1.5", + "timeframe": "last24hours", + "variables": [ + "{{variables.[Environmental Monitor]}}" + ], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "ec5af109-7072-4027-b7db-06ab4b08c58e", + "x": 0, + "y": 0, + "w": 1, + "h": 5, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Properties", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[Environmental Monitors]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Environmental Monitor]}}" + }, + "variables": [ + "{{variables.[Environmental Monitor]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true + } + } + } + } + }, + { + "i": "c8bec88c-c656-4a5e-9820-aed649bf232c", + "x": 1, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Temperature", + "description": "Highest room temperature reading on the device, against ASHRAE bands (°F). Excludes battery temperature.", + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[sensors]}}", + "name": "sensors", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "device": { + "variable": "{{variables.[Environmental Monitor]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Environmental Monitors]}}" + } + }, + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "kind", + "operation": "equals", + "value": "TEMPERATURE" + }, + { + "column": "label", + "operation": "notcontains", + "value": "Battery" + }, + { + "column": "numericValue", + "operation": "notempty" + } + ] + } + }, + "variables": [ + "{{variables.[Environmental Monitor]}}" + ], + "visualisation": { + "type": "data-stream-gauge", + "config": { + "data-stream-gauge": { + "value": { + "type": "mean", + "columns": [ + "numericValue" + ] + }, + "minimum": 50, + "maximum": 100 + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "gauge", + "encoding": { + "value": { + "field": "numericValue", + "type": "quantitative", + "title": "Reading", + "aggregate": "max" + } + }, + "style": { + "gaugeMin": 50, + "gaugeMax": 100, + "gaugeBands": [ + { + "to": 0.18, + "color": "#d63939" + }, + { + "to": 0.288, + "color": "#f59f00" + }, + { + "to": 0.612, + "color": "#2fb344" + }, + { + "to": 0.792, + "color": "#f59f00" + }, + { + "to": 1.0, + "color": "#d63939" + } + ], + "gaugeAxisLabels": true, + "gaugeLabelText": "Highest (F)", + "labelFormat": "{{value}}" + } + } + } + } + }, + { + "i": "cc62ee33-6718-443f-a035-c9e24a3e169b", + "x": 2, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Humidity", + "description": "Average relative humidity on the device, against ASHRAE bands.", + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[sensors]}}", + "name": "sensors", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "device": { + "variable": "{{variables.[Environmental Monitor]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Environmental Monitors]}}" + } + }, + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "kind", + "operation": "equals", + "value": "HUMIDITY" + }, + { + "column": "numericValue", + "operation": "notempty" + } + ] + } + }, + "variables": [ + "{{variables.[Environmental Monitor]}}" + ], + "visualisation": { + "type": "data-stream-gauge", + "config": { + "data-stream-gauge": { + "value": { + "type": "mean", + "columns": [ + "numericValue" + ] + }, + "minimum": 0, + "maximum": 100 + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "gauge", + "encoding": { + "value": { + "field": "numericValue", + "type": "quantitative", + "title": "Reading", + "aggregate": "avg" + } + }, + "style": { + "gaugeMin": 0, + "gaugeMax": 100, + "gaugeBands": [ + { + "to": 0.08, + "color": "#d63939" + }, + { + "to": 0.7, + "color": "#2fb344" + }, + { + "to": 0.8, + "color": "#f59f00" + }, + { + "to": 1.0, + "color": "#d63939" + } + ], + "gaugeAxisLabels": true, + "gaugeLabelText": "Average (%RH)", + "labelFormat": "{{value}}" + } + } + } + } + }, + { + "i": "e42c6453-b858-411f-a010-f22e74db9f2b", + "x": 3, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Device Status", + "description": "The device's own status in Data Center Expert, coloured by its severity.", + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[sensors]}}", + "name": "sensors", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "device": { + "variable": "{{variables.[Environmental Monitor]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Environmental Monitors]}}" + } + }, + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "kind", + "operation": "equals", + "value": "STATE" + }, + { + "column": "label", + "operation": "contains", + "value": "Device Status" + } + ] + } + }, + "variables": [ + "{{variables.[Environmental Monitor]}}" + ], + "visualisation": { + "type": "data-stream-blocks", + "config": { + "data-stream-blocks": { + "labelColumn": "label", + "stateColumn": "severityText", + "sublabel": "value", + "linkColumn": "none", + "columns": 1 + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "statusGrid", + "encoding": { + "label": { + "field": "label", + "type": "nominal", + "title": "Sensor" + }, + "status": { + "field": "severityText", + "type": "nominal", + "title": "Status" + }, + "value": { + "field": "value", + "type": "nominal", + "title": "State" + } + }, + "style": { + "statusGridColumnsPerRow": 1, + "statusGridOrderByField": "label", + "statusGridOrderDirection": "asc", + "statusGridLabelWrap": true + } + } + } + } + }, + { + "i": "b6acd28a-b96b-497b-af28-66ea89f7d98d", + "x": 1, + "y": 2, + "w": 3, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Temperature History", + "description": "Room temperature sensors over the dashboard timeframe (battery temperature excluded). Green band: ASHRAE recommended range (64.4-80.6 °F); amber lines: ASHRAE class A1 allowable limits (59 and 89.6 °F).", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[environmentalHistory]}}", + "name": "environmentalHistory", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "kind", + "operation": "equals", + "value": "TEMPERATURE" + }, + { + "column": "sensorLabel", + "operation": "notcontains", + "value": "Battery" + } + ] + } + }, + "scope": { + "scope": "{{scopes.[Environmental Monitors]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Environmental Monitor]}}" + }, + "variables": [ + "{{variables.[Environmental Monitor]}}" + ], + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "xAxisColumn": "time", + "yAxisColumn": [ + "value" + ], + "seriesColumn": "sensorLabel", + "showLegend": true, + "legendPosition": "bottom", + "yAxisLabel": "", + "showYAxisLabel": false, + "showTrendLine": false + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "line", + "encoding": { + "x": { + "field": "time", + "type": "temporal", + "title": "Time" + }, + "y": { + "field": "value", + "type": "quantitative", + "title": "Temperature" + }, + "series": { + "field": "sensorLabel", + "type": "nominal", + "title": "Sensor Name" + } + }, + "style": { + "legendPosition": "bottom", + "cartesianYAxisUnitColumn": "units", + "cartesianYAxisMin": 50, + "cartesianYAxisMax": 100 + }, + "markAreas": [ + { + "from": 64.4, + "to": 80.6, + "label": "ASHRAE recommended", + "color": "rgba(82,196,26,0.12)" + } + ], + "markLines": [ + { + "value": 59, + "label": "A1 allowable min", + "color": "#f59e0b" + }, + { + "value": 89.6, + "label": "A1 allowable max", + "color": "#f59e0b" + } + ] + } + } + } + }, + { + "i": "c9168964-9174-4169-ab0e-5182477e68e8", + "x": 0, + "y": 5, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Environmental Readings", + "description": "Current temperature, humidity, airflow, leak and door readings", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[sensors]}}", + "name": "sensors", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "device": { + "variable": "{{variables.[Environmental Monitor]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Environmental Monitors]}}" + } + }, + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "category", + "operation": "equals", + "value": "Environmental" + } + ] + } + }, + "variables": [ + "{{variables.[Environmental Monitor]}}" + ], + "visualisation": { + "type": "data-stream-blocks", + "config": { + "data-stream-blocks": { + "labelColumn": "label", + "stateColumn": "severityText", + "sublabel": "value", + "linkColumn": "none", + "columns": 5 + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "statusGrid", + "encoding": { + "label": { + "field": "label", + "type": "nominal", + "title": "Sensor" + }, + "status": { + "field": "severityText", + "type": "nominal", + "title": "Status" + }, + "value": { + "field": "value", + "type": "nominal", + "title": "Reading" + } + }, + "style": { + "statusGridColumnsPerRow": 5, + "statusGridOrderByField": "label", + "statusGridOrderDirection": "asc", + "statusGridLabelWrap": false, + "statusGridMaxBlockHeight": 120 + } + } + } + } + }, + { + "i": "f43a4476-2923-4a30-8a05-ad79690e8e7b", + "x": 2, + "y": 5, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Humidity History", + "description": "Humidity sensors over the dashboard timeframe. Amber line: 70 %RH, the ASHRAE recommended ceiling; red lines: 8 and 80 %RH, the ASHRAE class A1 allowable limits.", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[environmentalHistory]}}", + "name": "environmentalHistory", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "kind", + "operation": "equals", + "value": "HUMIDITY" + }, + { + "column": "value", + "operation": "notempty" + } + ] + } + }, + "scope": { + "scope": "{{scopes.[Environmental Monitors]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Environmental Monitor]}}" + }, + "variables": [ + "{{variables.[Environmental Monitor]}}" + ], + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "xAxisColumn": "time", + "yAxisColumn": [ + "value" + ], + "seriesColumn": "sensorLabel", + "showLegend": true, + "legendPosition": "bottom", + "yAxisLabel": "", + "showYAxisLabel": false, + "showTrendLine": false + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "line", + "encoding": { + "x": { + "field": "time", + "type": "temporal", + "title": "Time" + }, + "y": { + "field": "value", + "type": "quantitative", + "title": "Humidity" + }, + "series": { + "field": "sensorLabel", + "type": "nominal", + "title": "Sensor Name" + } + }, + "style": { + "legendPosition": "bottom", + "cartesianYAxisUnitColumn": "units", + "cartesianYAxisMin": 0, + "cartesianYAxisMax": 100 + }, + "markLines": [ + { + "value": 8, + "label": "A1 allowable min", + "color": "#d63939" + }, + { + "value": 70, + "label": "ASHRAE recommended max", + "color": "#f59e0b" + }, + { + "value": 80, + "label": "A1 allowable max", + "color": "#d63939" + } + ] + } + } + } + }, + { + "i": "5af4553c-6626-4d3e-9ca1-cacf8528ce87", + "x": 0, + "y": 8, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Active Alarms", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[alarms]}}", + "name": "alarms", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "device": { + "variable": "{{variables.[Environmental Monitor]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Environmental Monitors]}}" + } + } + }, + "variables": [ + "{{variables.[Environmental Monitor]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "severityText", + "description", + "startTime", + "durationSeconds", + "recommendedAction" + ], + "hiddenColumns": [ + "deviceName", + "hidden", + "alarmAlertSuppressed" + ] + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "statusText", + "title": "Severity", + "renderer": { + "type": "badge", + "colors": { + "OK": "#2fb344", + "Normal": "#2fb344", + "None": "#2fb344", + "Information": "#2fb344", + "Informational": "#2fb344", + "Warning": "#f59f00", + "Error": "#d63939", + "Critical": "#d63939", + "Failure": "#d63939" + } + } + }, + { + "field": "category", + "title": "Category", + "renderer": { + "type": "badge", + "colors": { + "Environmental": "#1c7ed6", + "Power": "#7048e8", + "Other": "#868e96" + } + } + }, + { + "field": "description", + "title": "Alarm", + "renderer": { + "type": "text" + } + }, + { + "field": "startTime", + "title": "Started", + "renderer": { + "type": "date", + "relative": true + } + }, + { + "field": "recommendedAction", + "title": "Recommended Action", + "renderer": { + "type": "text" + } + } + ] + } + } + } + } + }, + { + "i": "ef759ccb-4084-4f59-8fdb-8004ff14e8ec", + "x": 2, + "y": 8, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Alarm History", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[alarmHistory]}}", + "name": "alarmHistory", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "device": { + "variable": "{{variables.[Environmental Monitor]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Environmental Monitors]}}" + } + } + }, + "variables": [ + "{{variables.[Environmental Monitor]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "severityText", + "description", + "startTime", + "resolveTime", + "durationSeconds" + ], + "hiddenColumns": [ + "deviceName", + "recommendedAction", + "hidden", + "alarmAlertSuppressed" + ] + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "statusText", + "title": "Severity", + "renderer": { + "type": "badge", + "colors": { + "OK": "#2fb344", + "Normal": "#2fb344", + "None": "#2fb344", + "Information": "#2fb344", + "Informational": "#2fb344", + "Warning": "#f59f00", + "Error": "#d63939", + "Critical": "#d63939", + "Failure": "#d63939" + } + } + }, + { + "field": "category", + "title": "Category", + "renderer": { + "type": "badge", + "colors": { + "Environmental": "#1c7ed6", + "Power": "#7048e8", + "Other": "#868e96" + } + } + }, + { + "field": "description", + "title": "Alarm", + "renderer": { + "type": "text" + } + }, + { + "field": "startTime", + "title": "Started", + "renderer": { + "type": "date" + } + }, + { + "field": "resolveTime", + "title": "Resolved", + "renderer": { + "type": "date" + } + } + ] + }, + "style": { + "richTableSort": { + "field": "startTime", + "direction": "desc" + } + } + } + } + } + } + ] + } +} diff --git a/plugins/EcoStruxureIT-DCE/v1/defaultContent/manifest.json b/plugins/EcoStruxureIT-DCE/v1/defaultContent/manifest.json index af1b5d3d..729311b5 100644 --- a/plugins/EcoStruxureIT-DCE/v1/defaultContent/manifest.json +++ b/plugins/EcoStruxureIT-DCE/v1/defaultContent/manifest.json @@ -12,6 +12,18 @@ "name": "device", "type": "dashboard" }, + { + "name": "ups", + "type": "dashboard" + }, + { + "name": "rackPdu", + "type": "dashboard" + }, + { + "name": "environmental", + "type": "dashboard" + }, { "name": "sensor", "type": "dashboard" diff --git a/plugins/EcoStruxureIT-DCE/v1/defaultContent/rackPdu.dash.json b/plugins/EcoStruxureIT-DCE/v1/defaultContent/rackPdu.dash.json new file mode 100644 index 00000000..ae6763d6 --- /dev/null +++ b/plugins/EcoStruxureIT-DCE/v1/defaultContent/rackPdu.dash.json @@ -0,0 +1,791 @@ +{ + "name": "Rack PDU", + "schemaVersion": "1.5", + "timeframe": "last24hours", + "variables": [ + "{{variables.[Rack PDU]}}" + ], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "fb5ee1ce-7e37-4980-8381-d2a398bd94a8", + "x": 0, + "y": 0, + "w": 1, + "h": 5, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Properties", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[Rack PDUs]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Rack PDU]}}" + }, + "variables": [ + "{{variables.[Rack PDU]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true + } + } + } + } + }, + { + "i": "7ea33846-ca7c-4116-906c-78c1be44b9a8", + "x": 1, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Total Power", + "description": "Total real power drawn through the PDU.", + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[sensors]}}", + "name": "sensors", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "device": { + "variable": "{{variables.[Rack PDU]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Rack PDUs]}}" + } + }, + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "kind", + "operation": "equals", + "value": "POWER_WATTS" + }, + { + "column": "label", + "operation": "contains", + "value": "Total" + }, + { + "column": "numericValue", + "operation": "notempty" + } + ] + } + }, + "variables": [ + "{{variables.[Rack PDU]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "numericValue", + "label": "watts" + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "numericValue", + "type": "quantitative", + "title": "watts", + "aggregate": "max" + } + ] + }, + "style": { + "scalarLabelText": "watts" + } + } + } + } + }, + { + "i": "524e9944-d5ee-43ba-87b2-c0f6ddb7f205", + "x": 2, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Highest Current", + "description": "Highest current on any phase or bank of the PDU.", + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[sensors]}}", + "name": "sensors", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "device": { + "variable": "{{variables.[Rack PDU]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Rack PDUs]}}" + } + }, + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "kind", + "operation": "equals", + "value": "AMP_DETECTOR" + }, + { + "column": "label", + "operation": "notcontains", + "value": "Peak" + }, + { + "column": "numericValue", + "operation": "notempty" + } + ] + } + }, + "variables": [ + "{{variables.[Rack PDU]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "numericValue", + "label": "amps (highest phase or bank)" + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "numericValue", + "type": "quantitative", + "title": "amps (highest phase or bank)", + "aggregate": "max" + } + ] + }, + "style": { + "scalarLabelText": "amps (highest phase or bank)" + } + } + } + } + }, + { + "i": "b0fdea72-9a0d-41ed-9f66-d70bd990acee", + "x": 3, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Device Status", + "description": "The device's own status in Data Center Expert, coloured by its severity.", + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[sensors]}}", + "name": "sensors", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "device": { + "variable": "{{variables.[Rack PDU]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Rack PDUs]}}" + } + }, + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "kind", + "operation": "equals", + "value": "STATE" + }, + { + "column": "label", + "operation": "contains", + "value": "Device Status" + } + ] + } + }, + "variables": [ + "{{variables.[Rack PDU]}}" + ], + "visualisation": { + "type": "data-stream-blocks", + "config": { + "data-stream-blocks": { + "labelColumn": "label", + "stateColumn": "severityText", + "sublabel": "value", + "linkColumn": "none", + "columns": 1 + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "statusGrid", + "encoding": { + "label": { + "field": "label", + "type": "nominal", + "title": "Sensor" + }, + "status": { + "field": "severityText", + "type": "nominal", + "title": "Status" + }, + "value": { + "field": "value", + "type": "nominal", + "title": "State" + } + }, + "style": { + "statusGridColumnsPerRow": 1, + "statusGridOrderByField": "label", + "statusGridOrderDirection": "asc", + "statusGridLabelWrap": true + } + } + } + } + }, + { + "i": "0bccce87-fe72-4643-8da3-591091a8d649", + "x": 1, + "y": 2, + "w": 3, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Current by Phase and Bank", + "description": "Present current on each phase and bank of the PDU.", + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[sensors]}}", + "name": "sensors", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "device": { + "variable": "{{variables.[Rack PDU]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Rack PDUs]}}" + } + }, + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "kind", + "operation": "equals", + "value": "AMP_DETECTOR" + }, + { + "column": "label", + "operation": "notcontains", + "value": "Peak" + }, + { + "column": "numericValue", + "operation": "notempty" + } + ] + } + }, + "variables": [ + "{{variables.[Rack PDU]}}" + ], + "visualisation": { + "type": "data-stream-bar-chart", + "config": { + "data-stream-bar-chart": { + "xAxisData": "label", + "yAxisData": [ + "numericValue" + ], + "xAxisGroup": "none", + "xAxisLabel": "", + "yAxisLabel": "Current (A)", + "showXAxisLabel": false, + "showYAxisLabel": false, + "showLegend": false, + "legendPosition": "bottom", + "showGrid": true, + "horizontalLayout": "vertical", + "displayMode": "actual", + "showTotals": false, + "showValue": true + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "bar", + "encoding": { + "x": { + "field": "label", + "type": "nominal", + "title": "Phase or bank" + }, + "y": { + "field": "numericValue", + "type": "quantitative", + "title": "Current (A)" + } + }, + "style": { + "showLabels": true, + "cartesianYAxisUnitColumn": "units", + "cartesianBarBorderRadius": 4 + } + } + } + } + }, + { + "i": "0c51064b-c52c-4302-ba00-732442d75319", + "x": 0, + "y": 5, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Power History", + "description": "Total real power drawn through the PDU over the dashboard timeframe.", + "dataStream": { + "id": "{{dataStreams.[deviceSensorHistory]}}", + "name": "deviceSensorHistory", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "kind": [ + "AMP_DETECTOR", + "POWER_WATTS" + ] + }, + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "kind", + "operation": "equals", + "value": "POWER_WATTS" + }, + { + "column": "sensorLabel", + "operation": "contains", + "value": "Total" + }, + { + "column": "value", + "operation": "notempty" + } + ] + } + }, + "variables": [ + "{{variables.[Rack PDU]}}" + ], + "scope": { + "scope": "{{scopes.[Rack PDUs]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Rack PDU]}}" + }, + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "xAxisColumn": "time", + "yAxisColumn": [ + "value" + ], + "seriesColumn": "sensorLabel", + "showLegend": true, + "legendPosition": "bottom", + "yAxisLabel": "", + "showYAxisLabel": false, + "showTrendLine": false + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "line", + "encoding": { + "x": { + "field": "time", + "type": "temporal", + "title": "Time" + }, + "y": { + "field": "value", + "type": "quantitative", + "title": "Value" + }, + "series": { + "field": "sensorLabel", + "type": "nominal", + "title": "Sensor Name" + } + }, + "style": { + "legendPosition": "bottom", + "cartesianYAxisUnitColumn": "units" + } + } + } + } + }, + { + "i": "85bdf560-b887-46e3-a106-6e6474683150", + "x": 2, + "y": 5, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Current History", + "description": "Current on each phase and bank over the dashboard timeframe.", + "dataStream": { + "id": "{{dataStreams.[deviceSensorHistory]}}", + "name": "deviceSensorHistory", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "kind": [ + "AMP_DETECTOR", + "POWER_WATTS" + ] + }, + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "kind", + "operation": "equals", + "value": "AMP_DETECTOR" + }, + { + "column": "sensorLabel", + "operation": "notcontains", + "value": "Peak" + }, + { + "column": "value", + "operation": "notempty" + } + ] + } + }, + "variables": [ + "{{variables.[Rack PDU]}}" + ], + "scope": { + "scope": "{{scopes.[Rack PDUs]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Rack PDU]}}" + }, + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "xAxisColumn": "time", + "yAxisColumn": [ + "value" + ], + "seriesColumn": "sensorLabel", + "showLegend": true, + "legendPosition": "bottom", + "yAxisLabel": "", + "showYAxisLabel": false, + "showTrendLine": false + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "line", + "encoding": { + "x": { + "field": "time", + "type": "temporal", + "title": "Time" + }, + "y": { + "field": "value", + "type": "quantitative", + "title": "Value" + }, + "series": { + "field": "sensorLabel", + "type": "nominal", + "title": "Sensor Name" + } + }, + "style": { + "legendPosition": "bottom", + "cartesianYAxisUnitColumn": "units" + } + } + } + } + }, + { + "i": "25611c67-ada1-4632-9e2a-15a4e384846d", + "x": 0, + "y": 8, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Outlets", + "description": "State of each switched outlet, coloured by Data Center Expert's severity. Metered PDUs without switched outlets show no data.", + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[sensors]}}", + "name": "sensors", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "device": { + "variable": "{{variables.[Rack PDU]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Rack PDUs]}}" + } + }, + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "kind", + "operation": "equals", + "value": "STATE" + }, + { + "column": "label", + "operation": "contains", + "value": "State," + } + ] + } + }, + "variables": [ + "{{variables.[Rack PDU]}}" + ], + "visualisation": { + "type": "data-stream-blocks", + "config": { + "data-stream-blocks": { + "labelColumn": "label", + "stateColumn": "severityText", + "sublabel": "value", + "linkColumn": "none", + "columns": 6 + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "statusGrid", + "encoding": { + "label": { + "field": "label", + "type": "nominal", + "title": "Outlet" + }, + "status": { + "field": "severityText", + "type": "nominal", + "title": "Status" + }, + "value": { + "field": "value", + "type": "nominal", + "title": "State" + } + }, + "style": { + "statusGridColumnsPerRow": 6, + "statusGridOrderByField": "label", + "statusGridOrderDirection": "asc", + "statusGridLabelWrap": false, + "statusGridMaxBlockHeight": 80 + } + } + } + } + }, + { + "i": "9c36865f-114e-4d65-b768-69ed1192d7c0", + "x": 2, + "y": 8, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Active Alarms", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[alarms]}}", + "name": "alarms", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "device": { + "variable": "{{variables.[Rack PDU]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Rack PDUs]}}" + } + } + }, + "variables": [ + "{{variables.[Rack PDU]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "severityText", + "description", + "startTime", + "durationSeconds", + "recommendedAction" + ], + "hiddenColumns": [ + "deviceName", + "hidden", + "alarmAlertSuppressed" + ] + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "statusText", + "title": "Severity", + "renderer": { + "type": "badge", + "colors": { + "OK": "#2fb344", + "Normal": "#2fb344", + "None": "#2fb344", + "Information": "#2fb344", + "Informational": "#2fb344", + "Warning": "#f59f00", + "Error": "#d63939", + "Critical": "#d63939", + "Failure": "#d63939" + } + } + }, + { + "field": "category", + "title": "Category", + "renderer": { + "type": "badge", + "colors": { + "Environmental": "#1c7ed6", + "Power": "#7048e8", + "Other": "#868e96" + } + } + }, + { + "field": "description", + "title": "Alarm", + "renderer": { + "type": "text" + } + }, + { + "field": "startTime", + "title": "Started", + "renderer": { + "type": "date", + "relative": true + } + }, + { + "field": "recommendedAction", + "title": "Recommended Action", + "renderer": { + "type": "text" + } + } + ] + } + } + } + } + } + ] + } +} diff --git a/plugins/EcoStruxureIT-DCE/v1/defaultContent/scopes.json b/plugins/EcoStruxureIT-DCE/v1/defaultContent/scopes.json index 410fa9cc..023345d3 100644 --- a/plugins/EcoStruxureIT-DCE/v1/defaultContent/scopes.json +++ b/plugins/EcoStruxureIT-DCE/v1/defaultContent/scopes.json @@ -16,6 +16,76 @@ "type": "object" } }, + { + "name": "UPS Devices", + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "Device" + ] + }, + "deviceType": { + "type": "oneOf", + "values": [ + "UPS" + ] + } + }, + "variable": { + "name": "UPS", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + }, + { + "name": "Rack PDUs", + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "Device" + ] + }, + "deviceType": { + "type": "oneOf", + "values": [ + "Rack PDU" + ] + } + }, + "variable": { + "name": "Rack PDU", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + }, + { + "name": "Environmental Monitors", + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "Device" + ] + }, + "deviceType": { + "type": "oneOf", + "values": [ + "Environmental Monitor", + "Rack Manager" + ] + } + }, + "variable": { + "name": "Environmental Monitor", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + }, { "name": "Device Groups", "matches": { diff --git a/plugins/EcoStruxureIT-DCE/v1/defaultContent/sensor.dash.json b/plugins/EcoStruxureIT-DCE/v1/defaultContent/sensor.dash.json index 5e38918b..fbc9654b 100644 --- a/plugins/EcoStruxureIT-DCE/v1/defaultContent/sensor.dash.json +++ b/plugins/EcoStruxureIT-DCE/v1/defaultContent/sensor.dash.json @@ -94,6 +94,29 @@ "showTrendLine": false } } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "line", + "encoding": { + "x": { + "field": "time", + "type": "temporal", + "title": "Time" + }, + "y": { + "field": "value", + "type": "quantitative", + "title": "Value" + } + }, + "style": { + "cartesianAreaGradient": true, + "cartesianYAxisUnitColumn": "units", + "cartesianYAxisRangeMode": "fit" + } + } } } }, @@ -154,6 +177,70 @@ ] } } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "time", + "title": "Time", + "renderer": { + "type": "date" + } + }, + { + "field": "valueText", + "title": "Reading", + "renderer": { + "type": "text" + } + }, + { + "field": "units", + "title": "Units", + "renderer": { + "type": "text" + } + }, + { + "field": "statusText", + "title": "Status", + "renderer": { + "type": "badge", + "colors": { + "OK": "#2fb344", + "Normal": "#2fb344", + "None": "#2fb344", + "Information": "#2fb344", + "Informational": "#2fb344", + "Warning": "#f59f00", + "Error": "#d63939", + "Critical": "#d63939", + "Failure": "#d63939" + } + } + } + ] + }, + "style": { + "richTableRowHighlight": { + "field": "statusText", + "colors": { + "Warning": "rgba(245,159,0,0.12)", + "Error": "rgba(214,57,57,0.12)", + "Critical": "rgba(214,57,57,0.12)", + "Failure": "rgba(214,57,57,0.12)" + } + }, + "richTableSort": { + "field": "time", + "direction": "desc" + } + } + } } } } diff --git a/plugins/EcoStruxureIT-DCE/v1/defaultContent/ups.dash.json b/plugins/EcoStruxureIT-DCE/v1/defaultContent/ups.dash.json new file mode 100644 index 00000000..76446f6a --- /dev/null +++ b/plugins/EcoStruxureIT-DCE/v1/defaultContent/ups.dash.json @@ -0,0 +1,859 @@ +{ + "name": "UPS", + "schemaVersion": "1.5", + "timeframe": "last24hours", + "variables": [ + "{{variables.[UPS]}}" + ], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "0f8a92f5-bab9-4208-a4f0-21ddf64375bd", + "x": 0, + "y": 0, + "w": 1, + "h": 5, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Properties", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[UPS Devices]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[UPS]}}" + }, + "variables": [ + "{{variables.[UPS]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true + } + } + } + } + }, + { + "i": "805194ce-a6f8-4883-a566-c13de8948ed3", + "x": 1, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Battery Capacity", + "description": "Charge remaining in the UPS battery.", + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[sensors]}}", + "name": "sensors", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "device": { + "variable": "{{variables.[UPS]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[UPS Devices]}}" + } + }, + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "label", + "operation": "equals", + "value": "Battery Capacity Remaining" + }, + { + "column": "numericValue", + "operation": "notempty" + } + ] + } + }, + "variables": [ + "{{variables.[UPS]}}" + ], + "visualisation": { + "type": "data-stream-gauge", + "config": { + "data-stream-gauge": { + "value": { + "type": "mean", + "columns": [ + "numericValue" + ] + }, + "minimum": 0, + "maximum": 100 + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "gauge", + "encoding": { + "value": { + "field": "numericValue", + "type": "quantitative", + "title": "Reading", + "aggregate": "max" + } + }, + "style": { + "gaugeMin": 0, + "gaugeMax": 100, + "gaugeBands": [ + { + "to": 0.25, + "color": "#d63939" + }, + { + "to": 0.75, + "color": "#f59f00" + }, + { + "to": 1, + "color": "#2fb344" + } + ], + "gaugeAxisLabels": true, + "gaugeLabelText": "Capacity (%)" + } + } + } + } + }, + { + "i": "b962a7b7-4fcd-488d-9618-d3e73345579a", + "x": 2, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Runtime Remaining", + "description": "Estimated runtime on battery at the current load.", + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[sensors]}}", + "name": "sensors", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "device": { + "variable": "{{variables.[UPS]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[UPS Devices]}}" + } + }, + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "label", + "operation": "equals", + "value": "Battery Time Remaining" + }, + { + "column": "numericValue", + "operation": "notempty" + } + ] + } + }, + "variables": [ + "{{variables.[UPS]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "numericValue", + "label": "Runtime (min)" + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "numericValue", + "type": "quantitative", + "title": "Runtime (min)", + "aggregate": "max" + } + ] + }, + "style": { + "scalarLabelText": "minutes" + } + } + } + } + }, + { + "i": "1831a9c6-a51c-479c-be27-74465bc53d20", + "x": 3, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Output Load", + "description": "Load on the UPS as a share of its capacity. Amber from 60%, red from 80%.", + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[sensors]}}", + "name": "sensors", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "device": { + "variable": "{{variables.[UPS]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[UPS Devices]}}" + } + }, + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "label", + "operation": "equals", + "value": "Output Load" + }, + { + "column": "numericValue", + "operation": "notempty" + } + ] + } + }, + "variables": [ + "{{variables.[UPS]}}" + ], + "visualisation": { + "type": "data-stream-gauge", + "config": { + "data-stream-gauge": { + "value": { + "type": "mean", + "columns": [ + "numericValue" + ] + }, + "minimum": 0, + "maximum": 100 + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "gauge", + "encoding": { + "value": { + "field": "numericValue", + "type": "quantitative", + "title": "Reading", + "aggregate": "max" + } + }, + "style": { + "gaugeMin": 0, + "gaugeMax": 100, + "gaugeBands": [ + { + "to": 0.6, + "color": "#2fb344" + }, + { + "to": 0.8, + "color": "#f59f00" + }, + { + "to": 1, + "color": "#d63939" + } + ], + "gaugeAxisLabels": true, + "gaugeLabelText": "Load (%)" + } + } + } + } + }, + { + "i": "e9cb1815-0f3e-4b59-bb9e-337b5472a78e", + "x": 1, + "y": 2, + "w": 3, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "UPS Status", + "description": "The UPS's device status and key states and readings, coloured by Data Center Expert's severity.", + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[sensors]}}", + "name": "sensors", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "device": { + "variable": "{{variables.[UPS]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[UPS Devices]}}" + } + }, + "filter": { + "multiOperation": "or", + "filters": [ + { + "column": "label", + "operation": "contains", + "value": "Device Status" + }, + { + "column": "label", + "operation": "equals", + "value": "UPS Operation Mode" + }, + { + "column": "label", + "operation": "equals", + "value": "Battery Status" + }, + { + "column": "label", + "operation": "equals", + "value": "Battery Replacement Status" + }, + { + "column": "label", + "operation": "equals", + "value": "Self-Test Status" + }, + { + "column": "label", + "operation": "equals", + "value": "Battery Temperature" + }, + { + "column": "label", + "operation": "equals", + "value": "Battery Age" + }, + { + "column": "label", + "operation": "equals", + "value": "Input Voltage" + }, + { + "column": "label", + "operation": "equals", + "value": "Output Voltage" + } + ] + } + }, + "variables": [ + "{{variables.[UPS]}}" + ], + "visualisation": { + "type": "data-stream-blocks", + "config": { + "data-stream-blocks": { + "labelColumn": "label", + "stateColumn": "severityText", + "sublabel": "value", + "linkColumn": "none", + "columns": 4 + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "statusGrid", + "encoding": { + "label": { + "field": "label", + "type": "nominal", + "title": "Sensor" + }, + "status": { + "field": "severityText", + "type": "nominal", + "title": "Status" + }, + "value": { + "field": "value", + "type": "nominal", + "title": "Reading" + } + }, + "style": { + "statusGridColumnsPerRow": 4, + "statusGridOrderByField": "label", + "statusGridOrderDirection": "asc", + "statusGridLabelWrap": false, + "statusGridMaxBlockHeight": 120 + } + } + } + } + }, + { + "i": "413eb948-7bbe-435a-85a6-de993478b434", + "x": 0, + "y": 5, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Load and Battery Capacity", + "description": "Output load and battery capacity over the dashboard timeframe. Amber line: 80% load.", + "dataStream": { + "id": "{{dataStreams.[deviceSensorHistory]}}", + "name": "deviceSensorHistory", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "sensorName": [ + "Output Load", + "Battery Capacity Remaining" + ] + }, + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "value", + "operation": "notempty" + } + ] + } + }, + "scope": { + "scope": "{{scopes.[UPS Devices]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[UPS]}}" + }, + "variables": [ + "{{variables.[UPS]}}" + ], + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "xAxisColumn": "time", + "yAxisColumn": [ + "value" + ], + "seriesColumn": "sensorLabel", + "showLegend": true, + "legendPosition": "bottom", + "yAxisLabel": "", + "showYAxisLabel": false, + "showTrendLine": false + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "line", + "encoding": { + "x": { + "field": "time", + "type": "temporal", + "title": "Time" + }, + "y": { + "field": "value", + "type": "quantitative", + "title": "Value" + }, + "series": { + "field": "sensorLabel", + "type": "nominal", + "title": "Sensor Name" + } + }, + "style": { + "cartesianYAxisMin": 0, + "cartesianYAxisMax": 100, + "cartesianYAxisUnitColumn": "units", + "legendPosition": "bottom" + }, + "markLines": [ + { + "value": 80, + "label": "80% load", + "color": "#f59e0b" + } + ] + } + } + } + }, + { + "i": "34c70bef-c243-4ab7-8397-af9a17f9f336", + "x": 2, + "y": 5, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Input and Output Voltage", + "description": "Input and output voltage over the dashboard timeframe.", + "dataStream": { + "id": "{{dataStreams.[deviceSensorHistory]}}", + "name": "deviceSensorHistory", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "sensorName": [ + "Input Voltage", + "Output Voltage" + ] + }, + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "value", + "operation": "notempty" + } + ] + } + }, + "scope": { + "scope": "{{scopes.[UPS Devices]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[UPS]}}" + }, + "variables": [ + "{{variables.[UPS]}}" + ], + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "xAxisColumn": "time", + "yAxisColumn": [ + "value" + ], + "seriesColumn": "sensorLabel", + "showLegend": true, + "legendPosition": "bottom", + "yAxisLabel": "", + "showYAxisLabel": false, + "showTrendLine": false + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "line", + "encoding": { + "x": { + "field": "time", + "type": "temporal", + "title": "Time" + }, + "y": { + "field": "value", + "type": "quantitative", + "title": "Value" + }, + "series": { + "field": "sensorLabel", + "type": "nominal", + "title": "Sensor Name" + } + }, + "style": { + "cartesianYAxisRangeMode": "fit", + "cartesianYAxisUnitColumn": "units", + "legendPosition": "bottom" + } + } + } + } + }, + { + "i": "95414e0e-b86b-4d8f-8db2-f9e6817bb551", + "x": 0, + "y": 8, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Active Alarms", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[alarms]}}", + "name": "alarms", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "device": { + "variable": "{{variables.[UPS]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[UPS Devices]}}" + } + } + }, + "variables": [ + "{{variables.[UPS]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "severityText", + "description", + "startTime", + "durationSeconds", + "recommendedAction" + ], + "hiddenColumns": [ + "deviceName", + "hidden", + "alarmAlertSuppressed" + ] + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "statusText", + "title": "Severity", + "renderer": { + "type": "badge", + "colors": { + "OK": "#2fb344", + "Normal": "#2fb344", + "None": "#2fb344", + "Information": "#2fb344", + "Informational": "#2fb344", + "Warning": "#f59f00", + "Error": "#d63939", + "Critical": "#d63939", + "Failure": "#d63939" + } + } + }, + { + "field": "category", + "title": "Category", + "renderer": { + "type": "badge", + "colors": { + "Environmental": "#1c7ed6", + "Power": "#7048e8", + "Other": "#868e96" + } + } + }, + { + "field": "description", + "title": "Alarm", + "renderer": { + "type": "text" + } + }, + { + "field": "startTime", + "title": "Started", + "renderer": { + "type": "date", + "relative": true + } + }, + { + "field": "recommendedAction", + "title": "Recommended Action", + "renderer": { + "type": "text" + } + } + ] + } + } + } + } + }, + { + "i": "d4f486da-6751-455f-91fe-f34252502dff", + "x": 2, + "y": 8, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Alarm History", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[alarmHistory]}}", + "name": "alarmHistory", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "device": { + "variable": "{{variables.[UPS]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[UPS Devices]}}" + } + } + }, + "variables": [ + "{{variables.[UPS]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "severityText", + "description", + "startTime", + "resolveTime", + "durationSeconds" + ], + "hiddenColumns": [ + "deviceName", + "recommendedAction", + "hidden", + "alarmAlertSuppressed" + ] + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "statusText", + "title": "Severity", + "renderer": { + "type": "badge", + "colors": { + "OK": "#2fb344", + "Normal": "#2fb344", + "None": "#2fb344", + "Information": "#2fb344", + "Informational": "#2fb344", + "Warning": "#f59f00", + "Error": "#d63939", + "Critical": "#d63939", + "Failure": "#d63939" + } + } + }, + { + "field": "category", + "title": "Category", + "renderer": { + "type": "badge", + "colors": { + "Environmental": "#1c7ed6", + "Power": "#7048e8", + "Other": "#868e96" + } + } + }, + { + "field": "description", + "title": "Alarm", + "renderer": { + "type": "text" + } + }, + { + "field": "startTime", + "title": "Started", + "renderer": { + "type": "date" + } + }, + { + "field": "resolveTime", + "title": "Resolved", + "renderer": { + "type": "date" + } + } + ] + }, + "style": { + "richTableSort": { + "field": "startTime", + "direction": "desc" + } + } + } + } + } + } + ] + } +} diff --git a/plugins/EcoStruxureIT-DCE/v1/docs/README.md b/plugins/EcoStruxureIT-DCE/v1/docs/README.md index 497d4060..b49c133e 100644 --- a/plugins/EcoStruxureIT-DCE/v1/docs/README.md +++ b/plugins/EcoStruxureIT-DCE/v1/docs/README.md @@ -15,18 +15,50 @@ You will need a Data Center Expert **user account** and network access to the se | Field | What it is | Where to find it | Required | | ----- | ---------- | ---------------- | -------- | -| **Server URL** | Address of the Data Center Expert server, for example `https://dce.example.com`. The `/isxg` REST API path is added automatically. | The address you use to open the Data Center Expert web interface. | Yes | +| **Server URL** | Address of the Data Center Expert server, for example `https://dce.example.com`. The `/isxg` REST API path is added automatically, and anything after `/isxg` (such as `/isxg/dce-rest-api.html`) is ignored. | The address you use to open the Data Center Expert web interface. | Yes | | **Username** | Data Center Expert user the plugin signs in as. | Data Center Expert client → user management. | Yes | | **Password** | Password for that user. | Set when the user is created. | Yes | | **Ignore certificate errors** | Skips HTTPS certificate checks. | Turn this on when the server uses a self-signed certificate. | No | -| **OAuth client ID** | OAuth client sent when requesting an access token. Leave blank unless sign-in fails (see below). | See [If sign-in fails](#if-sign-in-fails). | No | -| **OAuth client secret** | Secret for the OAuth client above. | See [If sign-in fails](#if-sign-in-fails). | No | +| **OAuth client ID** | OAuth client sent when requesting an access token. Leave blank unless sign-in fails with valid credentials. | See [If sign-in fails with valid credentials](#if-sign-in-fails-with-valid-credentials). | No | +| **OAuth client secret** | Secret for the OAuth client above. | See [If sign-in fails with valid credentials](#if-sign-in-fails-with-valid-credentials). | No | +| **Import sensors** | Imports every sensor as an object, for the **Sensor** dashboard and sensor history. Off by default. | Turn on only for smaller installations; see [Known limitations](#known-limitations). | No | -On save, the plugin signs in to Data Center Expert and reads the top-level device group, then checks that at least one device is visible. If the first check fails, the server address, credentials or certificate setting is wrong. If the second check fails, the user has no access to any device groups. +On save, the plugin signs in to Data Center Expert and reads the top-level device group, then checks that at least one device is visible. If the first check fails, see [Troubleshooting](#troubleshooting). If only the second check fails, the user has no access to any device groups. -### If sign-in fails +### Troubleshooting -The plugin gets a short-lived access token from `https:///isxg/oauth/token` using the username and password. Some Data Center Expert versions may also need an OAuth client ID and secret. If setup fails with a valid username and password: +The setup checks show only a summary. When the plugin connects through a relay agent, the agent's log records the actual error: + +1. On the relay agent machine, open `Agent\appsettings.json` in the agent's install folder and set `"LogLevel": "Debug"`. The agent picks up the change without a restart. While Debug is on, the log records the Data Center Expert password in plain text (see step 5). +2. In SquaredUp, click **Rerun tests** on the data source. +3. Open the newest log file in `Agent\transient\logs` (named `SquaredUpCloudLog-.log`). The log doesn't name the plugin (every request shows as `WebAPIOnPremise`), so find this plugin's requests by searching for `/isxg/` or the server's address, which appear in lines like `Calling token URL: https:///isxg/oauth/token`. +4. Note that line's `CorrelationId` up to the last `-` (for example `FqINBh87IAMEcmg=`) and search for it. This shows every line for that request, including `Plugin reported error` and `original error:` lines with the underlying error. +5. Set `"LogLevel"` back to `"Information"` when you're done, then delete the log files written while Debug was on. Turning Debug off doesn't remove what's already logged, and when **OAuth client ID** is blank those files contain the password in the token URL (see [Known limitations](#known-limitations)). If you need to share a log, remove the `Calling token URL` lines first. If a log has already been copied or shared, change the Data Center Expert user's password. + +If more than one relay agent is installed on the machine, each has its own install folder. Each log line names the agent group it served (`AgentGroup: "..."`), so check the log of the agent in the data source's agent group. + +| Error | Likely cause | +| ----- | ------------ | +| `Request to …/oauth/token failed with status 400` or `401` | The username or password is wrong, or the user may not use the REST API. If **OAuth client ID** is filled in, also check it's correct. | +| `invalid_client` / `Given client ID does not match authenticated client` | An **OAuth client ID** was sent that this server doesn't expect. Leave **OAuth client ID** and **OAuth client secret** blank unless the server requires them. | +| `self-signed certificate`, `unable to verify the first certificate` or another certificate error | Turn on **Ignore certificate errors**. | +| `ECONNREFUSED`, `ETIMEDOUT` or `ENOTFOUND` | The relay agent machine can't reach the server. Check the Server URL and any firewalls in between. | +| `Invalid URL` | The Server URL isn't a full address. Include `https://`. | +| `429` | Data Center Expert's rate limit was reached. Wait a minute and rerun the tests. | + +To check the username and password independently of SquaredUp, run this in PowerShell 7 (`pwsh`) on the relay agent machine. It prompts for the username and password, so the password isn't typed into the command or saved in PowerShell's history. A reply showing an `access_token` means the credentials work: + +```powershell +$cred = Get-Credential -Message "Data Center Expert user" +$body = @{ grant_type = "password"; username = $cred.UserName; password = $cred.GetNetworkCredential().Password } +Invoke-RestMethod -Method Post -Uri "https:///isxg/oauth/token" -Body $body -SkipCertificateCheck +``` + +`-SkipCertificateCheck` turns off certificate verification, matching **Ignore certificate errors**, so the check works against Data Center Expert's default self-signed certificate. If the server has a certificate the relay agent machine trusts, leave it out so the certificate is checked too. Windows PowerShell 5.1 doesn't have `-SkipCertificateCheck`, so there the check only works against a trusted certificate. + +#### If sign-in fails with valid credentials + +The plugin gets a short-lived access token from `https:///isxg/oauth/token` using the username and password, without an OAuth client. Some Data Center Expert versions may need an OAuth client ID and secret instead: 1. Open `https:///isxg/js/springfox-dce.js` in a browser. 2. Search the file for `client` or `Basic` to find the client ID and secret the API page itself uses. @@ -35,23 +67,26 @@ The plugin gets a short-lived access token from `https:///isxg/oauth/tok ## What this plugin monitors - **Device health**: the status of every UPS, PDU, cooling unit, NetBotz appliance and other device in Data Center Expert, plus model, serial number, IP address and maintenance state. -- **Sensors**: current readings such as temperature, humidity, power, current and door state, with status and units. History is available for any sensor. +- **Environmental conditions**: temperature, humidity, dew point, airflow, leak and door readings for each device, with their history, and environmental alarms across the whole installation. +- **Sensors**: current readings such as temperature, humidity, power, current and door state, with status and units. Each sensor and alarm is categorised as **Environmental**, **Power** or **Other**. - **Alarms**: active alarms with severity and recommended action, resolved alarm history, and alarm counts by device group. - **Event log**: Data Center Expert's own event log messages. -The out-of-the-box dashboards include a **Data Center Overview**, plus perspectives for each **Device**, **Sensor** and **Device Group**. +The out-of-the-box dashboards include a **Data Center Overview** (with environmental alarms), plus perspectives for each **Device** (with environmental readings and history), **UPS** (battery, load, runtime and voltage), **Rack PDU** (power, current by phase and bank, outlet states), **Environmental** (NetBotz and other environmental monitors: temperature and humidity against ASHRAE ranges, door and sensor states), **Sensor** and **Device Group**. A UPS, rack PDU or environmental monitor shows the **Device** perspective as well as its own. ## Data streams - **Devices**: every device with its current status, model and network details (account-wide). - **Device Groups**: every device group with status and device count (account-wide). - **Device Group Devices**: devices in a selected device group, including its sub-groups (per device group). -- **Sensors**: current reading and status of every sensor, optionally filtered by device and sensor kind. +- **Sensors**: current reading, status and category of every sensor, optionally filtered by device and sensor kind. - **Sensor History**: readings over time for the selected sensors (per sensor). -- **Active Alarms**: currently active alarms, optionally filtered by device. -- **Alarm History**: resolved alarms that started within the timeframe, optionally filtered by device. +- **Environmental History**: readings over time from the environmental sensors of the selected devices (per device). Needs no Sensor objects, so it works on installations of any size. +- **Device Sensor History**: readings over time from the sensors of the selected devices that match the given names (for example **Output Load**) and kinds (for example **Current**), per device. With neither given, it reads the device's environmental sensors. +- **Active Alarms**: currently active alarms with their category, optionally filtered by device or by a single device group (including its sub-groups). +- **Alarm History**: alarms resolved within the timeframe (whenever they started), with their category, optionally filtered by device. For a single device it covers the whole timeframe; across all devices it covers the last 24 hours at most. - **Alarm Summary**: active alarm counts by severity per device group, optionally filtered by device group. -- **Event Log**: the newest 1,000 Data Center Expert event log messages in the timeframe (account-wide). +- **Event Log**: the newest 1,000 Data Center Expert event log messages in the timeframe (account-wide). Not on the out-of-the-box dashboards; see Known limitations. ## What gets indexed @@ -59,15 +94,23 @@ The out-of-the-box dashboards include a **Data Center Overview**, plus perspecti | ----------- | ---------- | ---------- | | **Device** | `GET /v1/devices` | A managed device such as a UPS, PDU, cooling unit or NetBotz appliance. | | **Device Group** | `GET /v1/deviceGroups` | A device group from the Data Center Expert group tree. | -| **Sensor** | `GET /v1/devices/sensors` | A single sensor on a device, for example a temperature probe or an output current reading. | +| **Sensor** | `GET /v1/devices/sensors` | A single sensor on a device, for example a temperature probe or an output current reading. Imported only when **Import sensors** is on. | **Relationships:** each Sensor stores its parent device's ID (`deviceGuid`), each Device stores its parent device's ID (`parentGuid`), and each Device Group stores its parent group's ID (`parentIsxcGuid`). ## Known limitations -- **Strict rate limit**: Data Center Expert allows only 5 requests per minute for each API area (devices, device groups, alarms, sensors, event log). Each tile makes one request, so a dashboard with many Data Center Expert tiles, or several dashboards refreshing at once, can be throttled. Throttled tiles show a rate-limit message and load again on the next refresh. -- **Large estates**: sensors are read in a single request for the whole server. Very large installations (many thousands of sensors) may exceed SquaredUp's response size limit. +- **Strict rate limit**: Data Center Expert allows only 5 requests per minute for each API area (devices, device groups, alarms, sensors, event log), and enforces it. The limit is shared by every tile and user of a data source, so several dashboards refreshing at once can be throttled. Throttled tiles show a rate-limit message and load again on the next refresh. +- **Large installations can't import sensors**: sensors can only be imported with a single request for every sensor on the server. On a large installation (tens of thousands of sensors) that takes longer, and returns more data, than SquaredUp allows, and the rate limit rules out reading each device's sensors in turn. So **Import sensors** is off by default and the **Sensor** perspective has nothing to show; turned on for a large installation, the **sensors** import step finishes with a warning while devices and device groups are still imported. Environmental readings and history on each **Device** dashboard read only that device's sensors, so they work regardless. +- **Some calls are slow on Data Center Expert's side**: reading device groups and the alarm summary takes Data Center Expert around 10 seconds (up to about 25 seconds when busy), so those tiles load slowly. The event log took about 27 seconds even when empty in testing, longer than SquaredUp allows, so it isn't on the out-of-the-box dashboards. +- **Alarm history across all devices covers the last 24 hours at most**: on a large installation Data Center Expert returns thousands of resolved alarms a day (about 6,500 a day, 2.4 MB, in testing), and a week is far more than SquaredUp can take in one response. A single device's alarm history uses its own endpoint and covers any timeframe. +- **Categories are best-effort**: a sensor's category comes from its kind and label; an alarm's comes from keywords in its description (for example "temperature" or "leak"), because alarms don't say which kind of sensor raised them. - **Sensor history is downsampled** over long timeframes: a tile returns at most 20,000 readings in total, shared across its sensors. Beyond that, numeric readings are averaged into time buckets, and state readings (such as door open or closed) are reduced to the points where the state changed. If a sensor has more state changes than its share of the limit, the changes are sampled evenly, so some changes may not be shown. - **Sensor names are not unique**: a sensor is named as it appears in Data Center Expert (for example "Temperature"), so search results show many sensors with the same name. Drill down from a device to find its sensors. -- **Units follow the server locale**: readings use the Data Center Expert server's default locale (metric or US units). +- **Temperature bands use °F**: the **Temperature** gauge and **Temperature History** chart on each Device dashboard use a 50–100 °F scale, show the ASHRAE recommended range (64.4–80.6 °F), and mark the ASHRAE class A1 allowable limits (59 and 89.6 °F). Both leave out sensors with "Battery" in their name, since ASHRAE ranges apply to room air rather than UPS batteries. The **Humidity** gauge is green up to 70 %RH, amber to 80 %RH, and red outside the 8–80 %RH allowable range. +- **Sensors are shown on their device's dashboard**: with **Import sensors** off there are no Sensor objects, so sensor names in device tiles are plain text rather than links. Each Device dashboard reads its sensors' current values and history directly from Data Center Expert. +- **Readings are in English and US units**: the plugin asks Data Center Expert for the `en_US` locale on every request, whatever the server's own locale. Sensor names, states and alarm text are therefore in English, and temperatures are in °F. The dashboards depend on this: the **UPS** tiles find their sensors by name (such as **Output Load**), and categories and temperature bands assume English names and °F. Temperatures show as `F` without the degree sign, because a SquaredUp relay agent currently garbles non-ASCII characters (`°F` would appear as `°F`). +- **The UPS perspective relies on standard APC sensor names**: its tiles read **Battery Capacity Remaining**, **Battery Time Remaining**, **Output Load**, **Input Voltage**, **Output Voltage** and similar sensors, which were identical across the Smart-UPS 1000, 1500, 3000 XL and SRT 5000 in testing. A UPS model that names them differently shows "No data" in those tiles. Status blocks are coloured by Data Center Expert's own severity for each sensor, and the plugin never overrides it. So a state such as **UPS Operation Mode: On Battery** or **Self-Test Status: Invalid Test** shows green unless Data Center Expert raises it. To have those states flagged, set up alarm thresholds for them in Data Center Expert, or edit the **UPS Status** tile to colour by the reading instead. +- **The Rack PDU perspective selects sensors by kind**: PDU sensor names differ between APC model families (**Total Power Output** or **Total Real Power**, **Current - Phase 1** or **Current, Phase 1**), so its tiles pick sensors by kind (current, power) plus a keyword. In testing this covered the AP7900B, AP7968B, AP7863, AP8965 and AP8841. **Outlets** shows only on switched PDUs; metered models have no outlet states, so that tile shows "No data". Outlet blocks are coloured by Data Center Expert's severity, like the UPS status blocks. +- **Sign-in details are sent as URL parameters**: when **OAuth client ID** is blank, the token request sends the username and password as URL parameters, the form Data Center Expert accepts without an OAuth client. HTTPS encrypts them in transit, but they may appear in the Data Center Expert web server's access logs, and a relay agent with `"LogLevel": "Debug"` writes the token URL, password included, to its log. After troubleshooting, turn Debug logging off and delete the log files written while it was on (see [Troubleshooting](#troubleshooting)). Setting an **OAuth client ID** sends them in the request body instead. - **Read-only**: the plugin never acknowledges alarms, changes device controls, or modifies anything in Data Center Expert. diff --git a/plugins/EcoStruxureIT-DCE/v1/indexDefinitions/default.json b/plugins/EcoStruxureIT-DCE/v1/indexDefinitions/default.json index bdc86428..e12d5aa9 100644 --- a/plugins/EcoStruxureIT-DCE/v1/indexDefinitions/default.json +++ b/plugins/EcoStruxureIT-DCE/v1/indexDefinitions/default.json @@ -32,8 +32,9 @@ }, { "name": "sensors", - "dataStream": { "name": "sensors" }, + "dataStream": { "name": "sensorImport" }, "timeframe": "none", + "optional": true, "objectMapping": { "id": "guid", "name": "label", diff --git a/plugins/EcoStruxureIT-DCE/v1/metadata.json b/plugins/EcoStruxureIT-DCE/v1/metadata.json index d17ef699..98fbaa36 100644 --- a/plugins/EcoStruxureIT-DCE/v1/metadata.json +++ b/plugins/EcoStruxureIT-DCE/v1/metadata.json @@ -1,7 +1,7 @@ { "name": "ecostruxure-it-dce", "displayName": "EcoStruxure IT Data Center Expert", - "version": "1.0.1", + "version": "1.0.2", "author": { "name": "@shawn149", "type": "community" @@ -39,19 +39,26 @@ "label": "Repository" } ], + "screenshots": [ + "https://github.com/squaredup/plugins/blob/main/plugins/EcoStruxureIT-DCE/v1/screenshots/data-center-overview.png", + "https://github.com/squaredup/plugins/blob/main/plugins/EcoStruxureIT-DCE/v1/screenshots/device-group.png", + "https://github.com/squaredup/plugins/blob/main/plugins/EcoStruxureIT-DCE/v1/screenshots/ups.png", + "https://github.com/squaredup/plugins/blob/main/plugins/EcoStruxureIT-DCE/v1/screenshots/rack-pdu.png", + "https://github.com/squaredup/plugins/blob/main/plugins/EcoStruxureIT-DCE/v1/screenshots/environmental.png" + ], "base": { "plugin": "WebAPI", "majorVersion": "1", "config": { - "baseUrl": "{{serverUrl.trim().replace(/\\/+$/, '').replace(/\\/isxg$/i, '')}}/isxg", + "baseUrl": "{{serverUrl.trim().replace(/[/]isxg([/?#].*)?$/i, '').replace(/[/]+$/, '')}}/isxg", "authMode": "oauth2", "oauth2GrantType": "password", - "oauth2TokenUrl": "{{serverUrl.trim().replace(/\\/+$/, '').replace(/\\/isxg$/i, '')}}/isxg/oauth/token", + "oauth2TokenUrl": "{{serverUrl.trim().replace(/[/]isxg([/?#].*)?$/i, '').replace(/[/]+$/, '')}}/isxg/oauth/token", "oauth2PasswordGrantUserName": "{{username}}", "oauth2PasswordGrantPassword": "{{password}}", "oauth2ClientId": "{{typeof clientId === 'string' && clientId.trim() ? clientId.trim() : 'notused'}}", "oauth2ClientSecret": "{{typeof clientSecret === 'string' && clientSecret ? clientSecret : 'notused'}}", - "oauth2ClientSecretLocationDuringAuth": "{{typeof clientId === 'string' && clientId.trim() ? 'header' : 'body'}}", + "oauth2ClientSecretLocationDuringAuth": "{{typeof clientId === 'string' && clientId.trim() ? 'header' : 'none'}}", "oauth2Scope": "", "oauth2TokenExtraArgs": [], "oauth2TokenExtraHeaders": [], @@ -61,7 +68,7 @@ "value": "application/json" } ], - "queryArgs": [] + "queryArgs": [{ "key": "locale", "value": "en_US" }] } } } diff --git a/plugins/EcoStruxureIT-DCE/v1/screenshots/data-center-overview.png b/plugins/EcoStruxureIT-DCE/v1/screenshots/data-center-overview.png new file mode 100644 index 00000000..840b7ea0 Binary files /dev/null and b/plugins/EcoStruxureIT-DCE/v1/screenshots/data-center-overview.png differ diff --git a/plugins/EcoStruxureIT-DCE/v1/screenshots/device-group.png b/plugins/EcoStruxureIT-DCE/v1/screenshots/device-group.png new file mode 100644 index 00000000..0466ae1c Binary files /dev/null and b/plugins/EcoStruxureIT-DCE/v1/screenshots/device-group.png differ diff --git a/plugins/EcoStruxureIT-DCE/v1/screenshots/environmental.png b/plugins/EcoStruxureIT-DCE/v1/screenshots/environmental.png new file mode 100644 index 00000000..cba1f186 Binary files /dev/null and b/plugins/EcoStruxureIT-DCE/v1/screenshots/environmental.png differ diff --git a/plugins/EcoStruxureIT-DCE/v1/screenshots/rack-pdu.png b/plugins/EcoStruxureIT-DCE/v1/screenshots/rack-pdu.png new file mode 100644 index 00000000..162383ce Binary files /dev/null and b/plugins/EcoStruxureIT-DCE/v1/screenshots/rack-pdu.png differ diff --git a/plugins/EcoStruxureIT-DCE/v1/screenshots/ups.png b/plugins/EcoStruxureIT-DCE/v1/screenshots/ups.png new file mode 100644 index 00000000..2a967473 Binary files /dev/null and b/plugins/EcoStruxureIT-DCE/v1/screenshots/ups.png differ diff --git a/plugins/EcoStruxureIT-DCE/v1/ui.json b/plugins/EcoStruxureIT-DCE/v1/ui.json index 7ad9d4bd..50b0c0bf 100644 --- a/plugins/EcoStruxureIT-DCE/v1/ui.json +++ b/plugins/EcoStruxureIT-DCE/v1/ui.json @@ -50,6 +50,12 @@ "name": "clientSecret", "label": "OAuth client secret", "placeholder": "Leave blank unless required" + }, + { + "type": "toggle", + "name": "importSensors", + "label": "Import sensors", + "help": "Imports every sensor as an object, for the Sensor dashboard and sensor history. Leave off for large installations (thousands of sensors): Data Center Expert can't return them all in the time SquaredUp allows, so the import would finish with a warning. Environmental readings on each Device dashboard work either way." } ] }