Commit 36dff7d169fc5015eb913c9c205b7c2b895f1ad5
Merge branch 'master' of github.com:thingsboard/thingsboard
Showing
25 changed files
with
2677 additions
and
217 deletions
Too many changes to show.
To preserve performance only 25 of 421 files are displayed.
... | ... | @@ -19,8 +19,8 @@ |
19 | 19 | "templateHtml": "<tb-alarms-table-widget \n [ctx]=\"ctx\">\n</tb-alarms-table-widget>", |
20 | 20 | "templateCss": "", |
21 | 21 | "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.alarmsTableWidget.onDataUpdated();\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true,\n hasShowCondition: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", |
22 | - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"AlarmTableSettings\",\n \"properties\": {\n \"alarmsTitle\": {\n \"title\": \"Alarms table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSelection\": {\n \"title\": \"Enable alarms selection\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSearch\": {\n \"title\": \"Enable alarms search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableFilter\": {\n \"title\": \"Enable alarm filter\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"reserveSpaceForHiddenAction\": {\n \"title\": \"Hidden cell button actions display mode\",\n \"type\": \"string\",\n \"default\": \"true\"\n },\n \"displayDetails\": {\n \"title\": \"Display alarm details\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowAcknowledgment\": {\n \"title\": \"Allow alarms acknowledgment\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowClear\": {\n \"title\": \"Allow alarms clear\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"-createdTime\"\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(alarm, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"alarmsTitle\",\n \"enableSelection\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"enableFilter\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n {\n \"key\": \"reserveSpaceForHiddenAction\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"true\",\n \"label\": \"Show empty space instead of hidden cell button action\"\n },\n {\n \"value\": \"false\",\n \"label\": \"Don't reserve space for hidden action buttons\"\n }\n ]\n },\n \"displayDetails\",\n \"allowAcknowledgment\",\n \"allowClear\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}", | |
23 | - "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value, alarm, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, alarm, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"defaultColumnVisibility\": {\n \"title\": \"Default column visibility\",\n \"type\": \"string\",\n \"default\": \"visible\"\n },\n \"columnSelectionToDisplay\": {\n \"title\": \"Column selection in 'Columns to Display'\",\n \"type\": \"string\",\n \"default\": \"enabled\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useCellStyleFunction === true\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useCellContentFunction === true\"\n },\n {\n \"key\": \"defaultColumnVisibility\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"visible\",\n \"label\": \"Visible\"\n },\n {\n \"value\": \"hidden\",\n \"label\": \"Hidden\"\n } \n ]\n },\n {\n \"key\": \"columnSelectionToDisplay\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"enabled\",\n \"label\": \"Enabled\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n } \n ]\n }\n ]\n}", | |
22 | + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"AlarmTableSettings\",\n \"properties\": {\n \"alarmsTitle\": {\n \"title\": \"Alarms table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSelection\": {\n \"title\": \"Enable alarms selection\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSearch\": {\n \"title\": \"Enable alarms search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableFilter\": {\n \"title\": \"Enable alarm filter\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"reserveSpaceForHiddenAction\": {\n \"title\": \"Hidden cell button actions display mode\",\n \"type\": \"string\",\n \"default\": \"true\"\n },\n \"displayDetails\": {\n \"title\": \"Display alarm details\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowAcknowledgment\": {\n \"title\": \"Allow alarms acknowledgment\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowClear\": {\n \"title\": \"Allow alarms clear\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"-createdTime\"\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(alarm, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"alarmsTitle\",\n \"enableSelection\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"enableFilter\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n {\n \"key\": \"reserveSpaceForHiddenAction\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"true\",\n \"label\": \"Show empty space instead of hidden cell button action\"\n },\n {\n \"value\": \"false\",\n \"label\": \"Don't reserve space for hidden action buttons\"\n }\n ]\n },\n \"displayDetails\",\n \"allowAcknowledgment\",\n \"allowClear\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/alarm/row_style_fn\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}", | |
23 | + "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value, alarm, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, alarm, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"defaultColumnVisibility\": {\n \"title\": \"Default column visibility\",\n \"type\": \"string\",\n \"default\": \"visible\"\n },\n \"columnSelectionToDisplay\": {\n \"title\": \"Column selection in 'Columns to Display'\",\n \"type\": \"string\",\n \"default\": \"enabled\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/alarm/cell_style_fn\",\n \"condition\": \"model.useCellStyleFunction === true\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/alarm/cell_content_fn\",\n \"condition\": \"model.useCellContentFunction === true\"\n },\n {\n \"key\": \"defaultColumnVisibility\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"visible\",\n \"label\": \"Visible\"\n },\n {\n \"value\": \"hidden\",\n \"label\": \"Hidden\"\n } \n ]\n },\n {\n \"key\": \"columnSelectionToDisplay\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"enabled\",\n \"label\": \"Enabled\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n } \n ]\n }\n ]\n}", | |
24 | 24 | "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"allowAcknowledgment\":true,\"allowClear\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"-createdTime\",\"enableSelectColumnDisplay\":true,\"enableStickyAction\":false,\"enableFilter\":true},\"title\":\"Alarms table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"alarmSource\":{\"type\":\"function\",\"dataKeys\":[{\"name\":\"createdTime\",\"type\":\"alarm\",\"label\":\"Created time\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.021092237451093787},{\"name\":\"originator\",\"type\":\"alarm\",\"label\":\"Originator\",\"color\":\"#4caf50\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.2780007688856758},{\"name\":\"type\",\"type\":\"alarm\",\"label\":\"Type\",\"color\":\"#f44336\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.7323586880398418},{\"name\":\"severity\",\"type\":\"alarm\",\"label\":\"Severity\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":false,\"useCellContentFunction\":false},\"_hash\":0.09927019860088193},{\"name\":\"status\",\"type\":\"alarm\",\"label\":\"Status\",\"color\":\"#607d8b\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6588418951443418}],\"entityAliasId\":null,\"name\":\"alarms\"},\"alarmSearchStatus\":\"ANY\",\"alarmsPollingInterval\":5,\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{},\"alarmStatusList\":[],\"alarmSeverityList\":[],\"alarmTypeList\":[],\"searchPropagatedAlarms\":false}" |
25 | 25 | } |
26 | 26 | } | ... | ... |
... | ... | @@ -55,8 +55,8 @@ |
55 | 55 | "templateHtml": "<tb-timeseries-table-widget \n [ctx]=\"ctx\">\n</tb-timeseries-table-widget>", |
56 | 56 | "templateCss": "", |
57 | 57 | "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n ignoreDataUpdateOnIntervalTick: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true,\n hasShowCondition: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}", |
58 | - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"enableSearch\": {\n \"title\": \"Enable search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"reserveSpaceForHiddenAction\": {\n \"title\": \"Hidden cell button actions display mode\",\n \"type\": \"string\",\n \"default\": \"true\"\n },\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showMilliseconds\": {\n \"title\": \"Display timestamp milliseconds\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"useEntityLabel\": {\n \"title\": \"Use entity label in tab name\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"hideEmptyLines\": {\n \"title\": \"Hide empty lines\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"disableStickyHeader\": {\n \"title\": \"Disable sticky header\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"enableSearch\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n {\n \"key\": \"reserveSpaceForHiddenAction\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"true\",\n \"label\": \"Show empty space instead of hidden cell button action\"\n },\n {\n \"value\": \"false\",\n \"label\": \"Don't reserve space for hidden action buttons\"\n }\n ]\n },\n \"showTimestamp\",\n \"showMilliseconds\",\n \"displayPagination\",\n \"useEntityLabel\",\n \"defaultPageSize\",\n \"identifyDeviceSelector\",\n \"hideEmptyLines\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}", | |
59 | - "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useCellStyleFunction === true\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useCellContentFunction === true\"\n }\n ]\n}", | |
58 | + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"enableSearch\": {\n \"title\": \"Enable search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"reserveSpaceForHiddenAction\": {\n \"title\": \"Hidden cell button actions display mode\",\n \"type\": \"string\",\n \"default\": \"true\"\n },\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showMilliseconds\": {\n \"title\": \"Display timestamp milliseconds\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"useEntityLabel\": {\n \"title\": \"Use entity label in tab name\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"hideEmptyLines\": {\n \"title\": \"Hide empty lines\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"disableStickyHeader\": {\n \"title\": \"Disable sticky header\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"enableSearch\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n {\n \"key\": \"reserveSpaceForHiddenAction\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"true\",\n \"label\": \"Show empty space instead of hidden cell button action\"\n },\n {\n \"value\": \"false\",\n \"label\": \"Don't reserve space for hidden action buttons\"\n }\n ]\n },\n \"showTimestamp\",\n \"showMilliseconds\",\n \"displayPagination\",\n \"useEntityLabel\",\n \"defaultPageSize\",\n \"identifyDeviceSelector\",\n \"hideEmptyLines\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/row_style_fn\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}", | |
59 | + "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/cell_style_fn\",\n \"condition\": \"model.useCellStyleFunction === true\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/cell_content_fn\",\n \"condition\": \"model.useCellContentFunction === true\"\n }\n ]\n}", | |
60 | 60 | "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', amount = percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\"}" |
61 | 61 | } |
62 | 62 | }, |
... | ... | @@ -127,8 +127,8 @@ |
127 | 127 | "templateHtml": "<tb-entities-table-widget \n [ctx]=\"ctx\">\n</tb-entities-table-widget>", |
128 | 128 | "templateCss": "", |
129 | 129 | "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n hasDataPageLink: true,\n warnOnPageDataOverflow: false,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true,\n hasShowCondition: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n },\n 'rowDoubleClick': {\n name: 'widget-action.row-double-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", |
130 | - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"reserveSpaceForHiddenAction\": {\n \"title\": \"Hidden cell button actions display mode\",\n \"type\": \"string\",\n \"default\": \"true\"\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityLabel\": {\n \"title\": \"Display entity label column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"entityLabelColumnTitle\": {\n \"title\": \"Entity label column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n {\n \"key\": \"reserveSpaceForHiddenAction\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"true\",\n \"label\": \"Show empty space instead of hidden cell button action\"\n },\n {\n \"value\": \"false\",\n \"label\": \"Don't reserve space for hidden action buttons\"\n }\n ]\n },\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityLabel\",\n \"entityLabelColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}", | |
131 | - "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value, entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"defaultColumnVisibility\": {\n \"title\": \"Default column visibility\",\n \"type\": \"string\",\n \"default\": \"visible\"\n },\n \"columnSelectionToDisplay\": {\n \"title\": \"Column selection in 'Columns to Display'\",\n \"type\": \"string\",\n \"default\": \"enabled\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useCellStyleFunction === true\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useCellContentFunction === true\"\n },\n {\n \"key\": \"defaultColumnVisibility\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"visible\",\n \"label\": \"Visible\"\n },\n {\n \"value\": \"hidden\",\n \"label\": \"Hidden\"\n } \n ]\n },\n {\n \"key\": \"columnSelectionToDisplay\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"enabled\",\n \"label\": \"Enabled\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n } \n ]\n }\n ]\n}", | |
130 | + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"reserveSpaceForHiddenAction\": {\n \"title\": \"Hidden cell button actions display mode\",\n \"type\": \"string\",\n \"default\": \"true\"\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityLabel\": {\n \"title\": \"Display entity label column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"entityLabelColumnTitle\": {\n \"title\": \"Entity label column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n {\n \"key\": \"reserveSpaceForHiddenAction\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"true\",\n \"label\": \"Show empty space instead of hidden cell button action\"\n },\n {\n \"value\": \"false\",\n \"label\": \"Don't reserve space for hidden action buttons\"\n }\n ]\n },\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityLabel\",\n \"entityLabelColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/entity/row_style_fn\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}", | |
131 | + "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value, entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"defaultColumnVisibility\": {\n \"title\": \"Default column visibility\",\n \"type\": \"string\",\n \"default\": \"visible\"\n },\n \"columnSelectionToDisplay\": {\n \"title\": \"Column selection in 'Columns to Display'\",\n \"type\": \"string\",\n \"default\": \"enabled\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/entity/cell_style_fn\",\n \"condition\": \"model.useCellStyleFunction === true\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/entity/cell_content_fn\",\n \"condition\": \"model.useCellContentFunction === true\"\n },\n {\n \"key\": \"defaultColumnVisibility\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"visible\",\n \"label\": \"Visible\"\n },\n {\n \"value\": \"hidden\",\n \"label\": \"Hidden\"\n } \n ]\n },\n {\n \"key\": \"columnSelectionToDisplay\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"enabled\",\n \"label\": \"Enabled\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n } \n ]\n }\n ]\n}", | |
132 | 132 | "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true},\"title\":\"Entities table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.472295003170325,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Cos\",\"color\":\"#4caf50\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.8926244886945558,\"funcBody\":\"return Math.round(1000*Math.cos(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}]}" |
133 | 133 | } |
134 | 134 | }, |
... | ... | @@ -145,7 +145,7 @@ |
145 | 145 | "templateHtml": "<tb-entities-hierarchy-widget \n [ctx]=\"ctx\">\n</tb-entities-hierarchy-widget>", |
146 | 146 | "templateCss": "", |
147 | 147 | "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesHierarchyWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'nodeSelected': {\n name: 'widget-action.node-selected',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", |
148 | - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesHierarchySettings\",\n \"properties\": {\n \"nodeRelationQueryFunction\": {\n \"title\": \"Node relations query function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeHasChildrenFunction\": {\n \"title\": \"Node has children function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeOpenedFunction\": {\n \"title\": \"Default node opened function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeDisabledFunction\": {\n \"title\": \"Node disabled function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeIconFunction\": {\n \"title\": \"Node icon function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeTextFunction\": {\n \"title\": \"Node text function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodesSortFunction\": {\n \"title\": \"Nodes sort function: f(nodeCtx1, nodeCtx2)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"nodeRelationQueryFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeHasChildrenFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeOpenedFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeDisabledFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeIconFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeTextFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodesSortFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", | |
148 | + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesHierarchySettings\",\n \"properties\": {\n \"nodeRelationQueryFunction\": {\n \"title\": \"Node relations query function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeHasChildrenFunction\": {\n \"title\": \"Node has children function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeOpenedFunction\": {\n \"title\": \"Default node opened function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeDisabledFunction\": {\n \"title\": \"Node disabled function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeIconFunction\": {\n \"title\": \"Node icon function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeTextFunction\": {\n \"title\": \"Node text function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodesSortFunction\": {\n \"title\": \"Nodes sort function: f(nodeCtx1, nodeCtx2)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"nodeRelationQueryFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/entities_hierarchy/node_relation_query_fn\"\n },\n {\n \"key\": \"nodeHasChildrenFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/entities_hierarchy/node_has_children_fn\"\n },\n {\n \"key\": \"nodeOpenedFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/entities_hierarchy/node_opened_fn\"\n },\n {\n \"key\": \"nodeDisabledFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/entities_hierarchy/node_disabled_fn\"\n },\n {\n \"key\": \"nodeIconFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/entities_hierarchy/node_icon_fn\"\n },\n {\n \"key\": \"nodeTextFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/entities_hierarchy/node_text_fn\"\n },\n {\n \"key\": \"nodesSortFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/entities_hierarchy/nodes_sort_fn\"\n }\n ]\n}", | |
149 | 149 | "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {},\n \"required\": []\n },\n \"form\": []\n}", |
150 | 150 | "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"nodeRelationQueryFunction\":\"/**\\n\\n// Function should return relations query object for current node used to fetch entity children.\\n// Function can return 'default' string value. In this case default relations query will be used.\\n\\n// The following example code will construct simple relations query that will fetch relations of type 'Contains'\\n// from the current entity.\\n\\nvar entity = nodeCtx.entity;\\nvar query = {\\n parameters: {\\n rootId: entity.id.id,\\n rootType: entity.id.entityType,\\n direction: \\\"FROM\\\",\\n maxLevel: 1\\n },\\n filters: [{\\n relationType: \\\"Contains\\\",\\n entityTypes: []\\n }]\\n};\\nreturn query;\\n\\n**/\\n\",\"nodeHasChildrenFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node has children (whether it can be expanded).\\n\\n// The following example code will restrict entities hierarchy expansion up to third level.\\n\\nreturn nodeCtx.level <= 2;\\n\\n// The next example code will restrict entities expansion according to the value of example 'nodeHasChildren' attribute.\\n\\nvar data = nodeCtx.data;\\nif (data.hasOwnProperty('nodeHasChildren') && data['nodeHasChildren'] !== null) {\\n return data['nodeHasChildren'] === 'true';\\n} else {\\n return true;\\n}\\n \\n**/\\n \",\"nodeTextFunction\":\"/**\\n\\n// Function should return text (can be HTML code) for the current node.\\n\\n// The following example code will generate node text consisting of entity name and temperature if temperature value is present in entity attributes/timeseries.\\n\\nvar data = nodeCtx.data;\\nvar entity = nodeCtx.entity;\\nvar text = entity.name;\\nif (data.hasOwnProperty('temperature') && data['temperature'] !== null) {\\n text += \\\" <b>\\\"+ data['temperature'] +\\\" °C</b>\\\";\\n}\\nreturn text;\\n\\n**/\",\"nodeIconFunction\":\"/** \\n\\n// Function should return node icon info object.\\n// Resulting object should contain either 'materialIcon' or 'iconUrl' property. \\n// Where:\\n - 'materialIcon' - name of the material icon to be used from the Material Icons Library (https://material.io/tools/icons);\\n - 'iconUrl' - url of the external image to be used as node icon.\\n// Function can return 'default' string value. In this case default icons according to entity type will be used.\\n\\n// The following example code shows how to use external image for devices which name starts with 'Test' and use \\n// default icons for the rest of entities.\\n\\nvar entity = nodeCtx.entity;\\nif (entity.id.entityType === 'DEVICE' && entity.name.startsWith('Test')) {\\n return {iconUrl: 'https://avatars1.githubusercontent.com/u/14793288?v=4&s=117'};\\n} else {\\n return 'default';\\n}\\n \\n**/\",\"nodeDisabledFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node should be disabled (not selectable).\\n\\n// The following example code will disable current node according to the value of example 'nodeDisabled' attribute.\\n\\nvar data = nodeCtx.data;\\nif (data.hasOwnProperty('nodeDisabled') && data['nodeDisabled'] !== null) {\\n return data['nodeDisabled'] === 'true';\\n} else {\\n return false;\\n}\\n \\n**/\\n\",\"nodesSortFunction\":\"/**\\n\\n// This function is used to sort nodes of the same level. Function should compare two nodes and return \\n// integer value: \\n// - less than 0 - sort nodeCtx1 to an index lower than nodeCtx2\\n// - 0 - leave nodeCtx1 and nodeCtx2 unchanged with respect to each other\\n// - greater than 0 - sort nodeCtx2 to an index lower than nodeCtx1\\n\\n// The following example code will sort entities first by entity type in alphabetical order then\\n// by entity name in alphabetical order.\\n\\nvar result = nodeCtx1.entity.id.entityType.localeCompare(nodeCtx2.entity.id.entityType);\\nif (result === 0) {\\n result = nodeCtx1.entity.name.localeCompare(nodeCtx2.entity.name);\\n}\\nreturn result;\\n \\n**/\",\"nodeOpenedFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node should be opened (expanded) when it first loaded.\\n\\n// The following example code will open by default nodes up to third level.\\n\\nreturn nodeCtx.level <= 2;\\n\\n**/\\n \"},\"title\":\"Entities hierarchy\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.472295003170325,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Cos\",\"color\":\"#4caf50\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.8926244886945558,\"funcBody\":\"return Math.round(1000*Math.cos(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"widgetStyle\":{},\"actions\":{}}" |
151 | 151 | } |
... | ... | @@ -162,15 +162,15 @@ |
162 | 162 | "resources": [], |
163 | 163 | "templateHtml": "<tb-qrcode-widget \n [ctx]=\"ctx\">\n</tb-qrcode-widget>", |
164 | 164 | "templateCss": "#container {\n overflow: auto;\n}\n\n.tbDatasource-container {\n margin: 5px;\n padding: 8px;\n}\n\n.tbDatasource-title {\n font-size: 1.200rem;\n font-weight: 500;\n padding-bottom: 10px;\n}\n\n.tbDatasource-table {\n width: 100%;\n box-shadow: 0 0 10px #ccc;\n border-collapse: collapse;\n white-space: nowrap;\n font-size: 1.000rem;\n color: #757575;\n}\n\n.tbDatasource-table td {\n position: relative;\n border-top: 1px solid rgba(0, 0, 0, 0.12);\n border-bottom: 1px solid rgba(0, 0, 0, 0.12);\n padding: 0px 18px;\n box-sizing: border-box;\n}", |
165 | - "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.qrCodeWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n dataKeysOptional: true,\n singleEntity: true\n };\n}\n\nself.onDestroy = function() {\n}\n\n", | |
166 | - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"QR Code\",\n \"properties\": {\n \"qrCodeTextPattern\": {\n \"title\": \"QR code text pattern (for ex. '${entityName} | ${keyName} - some text.')\",\n \"type\": \"string\",\n \"default\": \"${entityName}\"\n },\n \"useQrCodeTextFunction\": {\n \"title\": \"Use QR code text function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"qrCodeTextFunction\": {\n \"title\": \"QR code text function: f(data)\",\n \"type\": \"string\",\n \"default\": \"return data['entityName'];\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"qrCodeTextPattern\",\n \"useQrCodeTextFunction\",\n {\n \"key\": \"qrCodeTextFunction\",\n \"type\": \"javascript\"\n }\n ]\n}\n", | |
165 | + "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.qrCodeWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n dataKeysOptional: true,\n datasourcesOptional: true\n };\n}\n\nself.onDestroy = function() {\n}\n\n", | |
166 | + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"QR Code\",\n \"properties\": {\n \"qrCodeTextPattern\": {\n \"title\": \"QR code text pattern (for ex. '${entityName} | ${keyName} - some text.')\",\n \"type\": \"string\",\n \"default\": \"${entityName}\"\n },\n \"useQrCodeTextFunction\": {\n \"title\": \"Use QR code text function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"qrCodeTextFunction\": {\n \"title\": \"QR code text function: f(data)\",\n \"type\": \"string\",\n \"default\": \"return data[0]['entityName'];\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useQrCodeTextFunction\",\n {\n \"key\": \"qrCodeTextPattern\",\n \"condition\": \"model.useQrCodeTextFunction !== true\"\n },\n {\n \"key\": \"qrCodeTextFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/qrcode/qrcode_text_fn\",\n \"condition\": \"model.useQrCodeTextFunction === true\"\n }\n ]\n}\n", | |
167 | 167 | "dataKeySettingsSchema": "{}\n", |
168 | - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7036904308224163,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"qrCodeTextPattern\":\"${entityName}\",\"useQrCodeTextFunction\":false,\"qrCodeTextFunction\":\"return data['entityName'];\"},\"title\":\"QR Code\"}" | |
168 | + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7036904308224163,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"qrCodeTextPattern\":\"${entityName}\",\"useQrCodeTextFunction\":false,\"qrCodeTextFunction\":\"return data[0] ? data[0]['entityName'] : '';\"},\"title\":\"QR Code\"}" | |
169 | 169 | } |
170 | 170 | }, |
171 | 171 | { |
172 | 172 | "alias": "markdown_card", |
173 | - "name": "Markdown Card", | |
173 | + "name": "Markdown/HTML Card", | |
174 | 174 | "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAYAAABJ/yOpAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAIABJREFUeJztnXl8E9Xax79pku4bLbuyVJGCQClgK2WnQAVFlgKCgvQqoiAKCO53ebkovF5fAVEvKiCIiEJBEES2Fig7CAUr+9qN0oWmadM2SbPN+0dsaNpM2pSCLPP9fPh86JkzZ54zk2fmnDO/eR6ZIAiCyWRCrVaj1+uxWCxISNyvuLm54enpSWBgIEqlEpnRaBRycnLw9PRELpff9AHq1atXB2ZKSPw1mM1mSktL0Wg0NG7cGIVara4z55CQuNuRy+X4+/sDoFarcdPr9ZJzSEhUwsfHB71ej5s055CQqIpcLsdiseD2VxsiIXEnIznInxiNRk6ePFltvXPnzqHVam+DRc4RBMHp9jvFzrudWjmITqdj165dLu2zefNmBEHg4MGDqFSqWpdXRq/X8/PPP9v+3rhxIzqdziXbALKysnj77berrffGG2+Ql5fncvt1SUZGBo8//jhpaWmidW7GTovFwpkzZ2pp3b2Fyw5y/vx5Jk+ezFdffVXjffR6PZ9//jkymYxPPvnEdvdztdwRmZmZvP/++2RkZJCVlcV7771HZmamq926q2jYsCETJ06kcePGt6T9kpISJk6ceEvavttQuFLZbDazYsUKnn32Wb755psa7ZOYmMixY8cAWLJkCRkZGfz+++8ALpX379/fYfs5OTl06dKFxMREFAoFnTt3Jj8/n9atW7N+/Xr27t2Lj48PsbGxdOnSBYDDhw+ze/durl+/jkKhYMaMGXZtGo1GvvnmG4YOHUqTJk1ISUlh7dq1yOVyNBqNrd6lS5f4/vvvkclkjB07llatWrF27Vr69OlDgwYNACgoKCAxMZGBAweyZMkSrl27ho+PD88++yxt27YVPW9i7XTo0IEtW7YAoFKpeOCBB2z7iNl5/vx5Vq9ebVvbnzRpEn5+fmRkZLBixQq0Wi2xsbFERERw9epVvvvuO0pLS5k3bx4AI0eOpEWLFuIX+R7GpSeIXC5n7ty5tG/fvsb71K9fH7VaTXh4OGazmZCQEOrXr+9yuRgFBQX06tWLI0eOcOjQIXr16sX169cBaNeuHa+99hp9+vRhypQplJaWArBs2TJ8fHwYP348o0aNIjAw0NZeWVkZ06dPR6lU0qRJE7Kzs5kyZQq9e/emV69eGI1GAPLz85kwYQLdunXj8ccfZ8KECahUKn777TeuXLlCbm4u2dnZpKenc+TIEQoKCvjll18YP348nTp14uWXX7a15Qixdho0aEBUVBT79+8nOzvbVl/MzuzsbF566SW6du1KXFwcv/76KyqVCpVKxYQJE+jatSuxsbG8/fbbZGVl4e/vT2RkJO7u7kRFRREVFWV3fu43XHqC1Ibw8HDWrl1LTEwMeXl5dO/enfDwcACXyx1RWFiIj48PgYGBmM1mfH19UavVADRt2pTDhw9jNpvx9/fn2rVrPPLIIwA89thjVdrVarW89NJLxMbGMnz4cAB27dpFdHQ0AwYMACA4OBiAhIQEevfuTUxMDAD79+8nMTGRpk2bkp2dzfbt29HpdPTs2ZPmzZsD4OXlRceOHenYsSOLFy8mKyuLli1bOuyXWDvlN5GAgAC7+mJ2JiYm0q9fP5544gmbDeX1mzVrhtlsRqVSERISwpEjR4iNjSUyMhKlUkm3bt3EL+x9wi13kP/+978kJiZy+fJl8vPz8fDwQKlUArhUPmXKFIftFxcX06hRI95++20EQWD37t2o1WrUajUjR45k0KBBBAcHo9PpMJvNTm01m83o9Xq7OYxWq8XPz8/hccvfuAIEBASg0WgICQkhMzOT1NRUDAYDqampPPTQQ1X29/b2Rq/Xi9pS03aqs9NgMNicoiJqtRqTyWTra1RUFA8//LBo+/crt3yZ9/nnn8fb25vFixcTHBzMJ598wosvvuhyuRilpaUolUqCg4OpX78+SqUSrVbL5cuXadCgAW+++SYvvPACTZo0qdZWPz8/Vq5cyZkzZ2zj7/DwcPbs2VNlZaxjx44cPHgQo9GIwWDgwIEDhIeH065dO9avX0+3bt3o0aMH8fHxtGnTxuXzVl07vr6+FBQU2P4WszMyMpJt27Zx9uxZzpw5Y5ubdOrUiaKiIsaOHcvEiROZOHEiHTt2BKzOW1pa6tSB7xdcfoL8+9//5sSJE6hUKkaOHMmsWbOczkmysrJ45JFH8PPzIy8vjzZt2iCTyUhLS3OpXAytVotCcaMbCoWC0tJS2rdvj5eXF0OGDKF+/fqkpqbWSFLj6enJZ599xrhx4/j5558ZNmwY0dHRDBo0iIYNG5KXl4dCoeDxxx/n8ccfZ9CgQVgsFgYOHEhERARms5mSkhKGDx+OyWRi+fLlPPTQQy6vrD300EMO2yln6NChzJo1iy+//JJp06bRp08fh3Z26NCBN954g//85z/4+vpiNBqRy+VERETwxBNP8MQTT9CkSRNKSkrYtGkTCoUChULB008/zVNPPUVAQADTpk2jd+/eLtl/ryBLS0sTKg4VbpY7Tc1bVFREWVkZ9erVsw3haoPBYKCwsLBKO1qtFplM5nAYc6vR6XTodDqCgoJE7bRYLJhMJtzd3cnOzmbYsGHs37/f1ofyOYij86PRaJDJZA6HbvcD6enpt34O8ldTeTJbW9zd3WnYsGGVcm9v7zppvzZ4eXlVcczKdubk5DB+/Hjc3NxQKBT8z//8j50jyOVyh/0CqMsb593KPe8g9ztNmzYlMTHxrzbjrkXSYklIOMHlJ8jRo0c5ePAgAQEBDBkyxG78KyFxr+HSEyQ3N5cff/yRsLAwioqKmDlz5q2yS0LijqDWq1hGo5F+/fqRlJSEm9sNP7vTVrEkJGpLenp67ecgiYmJPPbYY3bOISFxr1GrVaxjx46xbNkyvvjii7q2R0LijsJlBzl37hxz585l/vz5NGrU6FbYJCFxx+DS+EgQBD744AP++c9/iqpQJSTuJVx6guTm5pKWlsacOXNsZW+88Qbdu3evc8MkJO4EXHKQxo0bc+DAgVtli4TEHYe0BCUh4QTJQSQknHDPOsjdFudKjOriX0ncWlxyEIvFwp49e/jiiy/47rvvKCwsrPG+Z86c4dSpU9WW3ytxrmraX2fUJP6VxK3FZQc5deoUbdu25dq1a7z//vvV7mM2m8nIyOD48eMcO3aMq1evYjKZRMvv9jhXrvbXGbc6/pVE9bi0iqVQKGzBEzp16sSYMWOq3SczM5OpU6fi5eWFIAisX7+ehQsXIpPJHJbf7XGuXO1vSEiIw/N29uxZ0fhXycnJrFu3Dp1OxyOPPCIa0KIcsfMmUT21moPs2LGDjz76yGkwhXJatmzJ0qVL8fDwwNvbmyVLlhASEiJafrfHuXK1v2KIxb/S6/W89tprDB48mKlTpxIREVHtNRA7bxLVUysHuX79OgaDgT/++KNGKdu2bdvG4MGDeeqpp9i+fbvT8opxrnx8fKrEuUpNTbWLc1VOeZyriIgIPD09gRtxrvr378+ECRMA+/hR/fr1cxjnauDAgfTs2dMuztXXX3/NZ599RnZ2dpU4V8OHD8fX15esrCyX+ytG/fr16datW5VPht3d3alXrx6HDh3Cw8ODyMjIas+/s/Mm4ZxaiRXHjh3Lc889R1xcHCdPnrSFixFj/PjxNS6/F+JcudJfV3Fzc2P16tWsXr3a9sT717/+JVq/NudN4gYuT9LLMRqNaLXaOg9acK/GuaotleNfCYKAp6cnL7/8Mps3byYhIYGMjAzR/Wtz3iRu4NITJDU1lffee48GDRqQlZVF//79baE864p7Nc5Vbakc/+rRRx9lzJgxPPDAAxQVFdGmTRuaNm0qun9tz5uEFZe/KDQYDOTn51O/fn3c3d2rbL/VXxTey3GuxKgc/8pkMqFSqfD29q5xzKq6Om/3E+np6fd+4DgJidpyU5/cSkjcD9wWB8nJyWHdunWsW7eOEydOANb3DgcPHuTgwYN2L+vKqakGqaaaq5vlTrOnptxp9txt1NpBLl++zKFDh2pU98KFC8THx9v9yK5du8a2bdt49913uXDhgl19VzRIzjRXdZVr706zxxVqqkmTcEytHOTq1avMmDGDtWvX1nifZs2aMWrUKDp16gRAWFgYs2fPdvhOoa40SHWVa+9Os0fi9uHyi0Kj0cjcuXOJi4tj//79dW6QMw2So1x7YH0Z9u677+Lh4cHYsWNp3bq1aK693377zaG26plnnnGYs+9W2yOW+y85OZkNGzYQFBRk04xNnz4dPz+/OtGkKRQKtm/fzrhx4wBYvnw5w4YNQ6FQ8OWXX5Kbm0tQUBDjxo27b/MTQi2eICtWrGDAgAFOdUQ3g7McfI5y7QEolUpGjBhBeHg4L730EiaTSTTXnpi2Sixn3622R4yDBw/SpEkT1Gq1TTu1evVqoG40aSqVig0bNti2x8fHU1RUhEqlYsuWLcTFxdGmTRvGjRvnNA33vY5LT5CcnByOHTvGokWLSElJuSUGieXgE8u1B9a3zREREURERPDVV1+RlZVFixYtHObaE8v95yxn3620xxkNGzYkMDCQ4uJiWrZsyfHjx219uNnci87w8vIiLCyMsLAwjh07RmJiIqNHj66RzfcaLjnIxo0bUalUTJ8+nZKSEq5du8aaNWtuy8kTy7VXGV9fX6cfWIlpq7Kzs13K2VdX9tRk/4oZtupKkyaXy6v9HgWsN6yioqJa23+345KDjBkzhsGDBwPWL+M2bNhgu4PWlopq3crlFTVIkZGRTJ06lSFDhiAIgsOl4cpUzLVXrvBt164dCxcu5LnnnsNkMrF69WqWLl1K48aN+fXXXxk7dqxDfdmtsqc6hg4dCliVwGCvrQLYunVrtW2Ua9KmT5/OvHnzmDlzJg0bNiQ7O5uSkhJ8fX3t6ptMJiwWCxaLhUOHDlWZy9xPuOQgAQEBtqFGXl4enp6eN51Du1xrtGjRIluuvYrlFXPwOcq15+zu6SjXXo8ePRxqq+RyuWjOvltpj6u5/+pSk/bcc8/x1FNP0bhxY/Lz81EoFJhMJnJzc3nyyScxGo1ERUXd1+mgb4vUZO/evWzcuNG2elMZrVaLXq+vkmukogapulx7znAl156znH1/hT1i1JW2Sq/XU1paSmBgIHK5nLS0NCZPnsxPP/2E2Wy+b/MTwm3MUejm5sbhw4cZPXo0MTExto+XyvH29nY4rKmYg6+6XHvOcOUG4Cxn319hjxh1lXvR09PT4XDvr8y9eCchiRUlJES4LWLF6jRXdwMnTpywaclycnKqbC8uLrZ9N1+ZzZs3o9Pp7nhNVLmdNS0vp7p+3aq4XrfrfLrsIOfPn2fjxo22f2VlZU7rO9Nc3U0IgkB8fHyVPqjVauLi4mzvKCqSk5PDvHnz8PT0rJVG63Zptyra6azckT3O+nUr43rdLo2Zyw6ya9euGgc+A+eaq7uFTp06MWrUKJo1a1Zl2+zZsxk8eLDD5e7t27cTExNj9x7DEWIardul3RKzs3K5q/bcC3G9XJ6kl5aW0rNnT3r16nXTB3ekKdJoNA7jTYFj7ZNer3eoKapXr56oZslRO35+fg61WM64dOkSFy9eFF2dS0hIYNq0aba/XdFoyeVyh+VBQUGiWiln8bISEhIAGDBgQLV2OioXs1OsX2IaNlevr5+fn2i/NBoNs2fPxmAwMGrUKFvwEEfxzaB28cFcfoIUFBSwd+9eEhISbDGlaosjTZFYvCkx7ZOYpkisfWftONJiOSMxMZEnn3zSYZ5GlUpFeno6nTt3tpW5otESKxfTSlUXL2vZsmUsXbq0RnY6KnemJXPULzENm6vX11m/jEYj3bt3p1evXkyZMoWcnBzR+GZiv4fqcNlBnn/+eSIjI0lKSmLSpEk35SRi8ZocxZuqqH0KCwurkczDUfti7YhpsZyRlZVli5FVmYSEBPr27Wv3Eq9cozV8+HC8vLzIysqy/fDKNVrlsbDEysvPT1hYGCNGjKBHjx4kJiZWGy9rwYIFfPrppzWy01G5M3sc9Ussrle5/TW9vs76FRwcTL9+/YiJiSE6Oppdu3aJxjeD2sUHc9lB2rZtS0xMDHPmzMFkMnHu3DlXmwCsj+Vhw4aRkpJCdna2qKaoPN6UmPZJTFMk1r5YO2q12qbFyszMrFaLBdYLLbbCs2PHDofDmXJuVqNVTrlWqjxelr+/PxMmTGD27Nl29Ro3buww5I+YndXZL4ar/aru+lbXr3L8/PwoLS0VjW9W099blePXuCd/Uh4by2AwoNVq8fDwqNF+lTVXrsZrioyMZNu2bZw9e5YzZ87YlowraooqIta+WDudOnWiqKiIsWPHMnHiRCZOnFhtQLw2bdrYPiGuiEaj4dy5c3Tt2rX6E4O9Rqsm5eVaKZPJxKFDh2jfvn218bJ+/PFHVq1aVSM7xcrF7HFGZQ2bGGLXpSZxwPR6Pfv27SM8PFw0vllt44O5NEnPz89n0qRJNGzYkGvXrhEdHU3r1q1rtG9lzVXXrl1d0hR16NDBofYpICDAoaZITLPUpk0bh+1EREQ41WI5YsCAASxYsICrV6/y4IMP2sp37txJjx49avxmXUyj5ai8RYsWDrVSeXl5TuNlbdu2DYvFwtixY6u1U6xczB5nVNawiSV/Fbu+169fd9ivnJwc8vLyGDt2LFlZWQwZMsQ2P3EU30yv19cuPlhaWppQUFBQ4385OTnCyZMnhezsbIfbnVFaWiqoVCq7ssLCQiE3N1cwGAxO9zWbzUJZWZkgCIJw7do1ITIy0m4fnU4n5OfnCyaTyWn71bVjMplE7ZkxY4awZ88eu7K1a9cKI0eOFDQaja1s0qRJwo4dO5z2xxFFRUV27TgqT01NFQYOHCiUlpZWqWs0GoWcnByHbWi1WkGr1dqVidlZnf1idoqh1WqrXPfKOLsuYv0yGAxCbm6ubb+KlJaWVumvINT89yYIgpCWlia4vMzr7u5u99mpKzjSXNVUU1Sd9klMU1S5/eracaTF+uabb9ixYwdXr16t8sHRyJEjKSwsZOvWrTzzzDNotVqOHz/O/Pnza9SviohJfhyVO9JKKRQK0dz1lcf3YnbWxH5XpUkVNWxiOLsuYv1SKpWiujkxLZmrGjZJiyUhIcIdFTiuprkChVpqe8Q0Rbc6R2HF4+bl5VVZTJC4s6mVg1gsFo4ePcrmzZvrzJCa5AqsrbZHTGtU0+PWlsrHPXjwIHFxcXetaPN+xGUHyc3NZcKECWzatOmm36S7Sm21PTXVRNU1lY87bNgwevTowZw5c26rHRK1x+VJ+qxZsxg9ejQDBw6s8T6O4jU1btxYNFegI5zFpxLT3pRTWWvkSo5CsL4X+OGHH7h48SIeHh5MnjyZZs2aVZsr0JHG6fXXX2fw4MGkpaWJLnlK3Dm49ARJT0/n8uXL5OXl8e2339Z4qOMoXpNYrkAxxLQ9zrQ3UFVT5GqOQr1ez3PPPYdWq2X8+PE8/fTTBAQEVKt9EtM4KRQKnnrqKZv8QeLOxqUnSFpaGg0aNKB58+ZotVqmTJnCihUrqF+/frX7Vo7XVDFXIGDLFSiGWLysitobgP3799vFcaqsKRI7rlg7DzzwAIGBgVUie1gsFptG6Nlnn62ifRLTOIE1sWlycrLT/krcGbj0BFEoFDz44IP06dOHJ598kvDwcIdSi5oglivQVcS0N+VU1hS5mqNQrVY7vAFUpxFypmXS6XTSN993CS45SKtWrTh//jxlZWUIgkB2drYtxq2riOUKLMeRdgiqanvEtDfgWFPkao7CsLAwjh49WkX6LjjRCFWnxUpJSbmteQ4lao9LQ6xGjRoxdOhQ4uLi8Pb2JiQkpEqYy5oSERHhMFdgOY60Q+A4PpUj7Q041hSJHVcsRyHAm2++yZgxY2jUqBEGg4EZM2Y4zRXoTIuVnZ3NgQMH+Pvf/16r8yZxe6nVm/TyIASO9nP1TbpYrsDyO7wjiULlnH3gOLfg5MmTiY2NdTjUcTVHoSAI5OXl4efnZxseieUKFDtueYzc0aNH2yImSty53NM5CrVaLX379iUpKem2JuR0dtz4+HhKS0t54YUXbps9ErXnnnYQCYmb5Y7SYklI3IlIDiIh4QTJQSQknKAA6lRdKilVJe4lFMB9naRRQkIMaZIuIVENkoNISDhBchAJCSdIDiIh4QTJQSQknCA5iISEEyQHkZBwguQgEhJOkBxEQsIJkoNISDhBchAJCSdIDiIh4QTJQSQknHBHOsj58+dZsmQJV69erVH97du3s3z58ltsVd2Rnp6OwWBwuK2wsJAlS5ZIgeXuEGrlIFFRUURGRrJ69eoq286fP09kZCSRkZGcPHmyVkadPHmSefPm1Ti06YYNG/jss89qdazbTUZGBk888QT//e9/HW5XqVTMmzePQ4cO1cnxLly4wJUrV+qkrfuRWjmIRqNBo9Gwdu3aKts2bNhg216TLKL3G02bNmX69Ok8/fTTt+V4r7zyCu+///5tOda9iMvR3csJDAzk9OnTXLhwwZbI02Qy8csvv1CvXj27jLZgjc6+b98+wJrRtDzgXFZWFgcOHCAyMpJDhw7RrFmzKsfKzMy0bYuKisJisZCYmMiVK1fo0qWLQ/sOHz5McnIywcHB9O7d25bVdOPGjQQFBdGzZ08ADhw4gEwmo1u3bgDs3buXwsJChgwZwk8//UTz5s3x9/dn7969+Pn5MXTo0CrhfFQqFTt37iQiIoKQkBAsFgvr1q3j8ccfp0WLFpjNZn766ScefvhhWrduTVBQkF1kx/L87W5ubrRt27ZKXzIyMti1axfu7u7ExMSwa9cuwsLCbNEZCwsL2bJlCxqNhvDwcLp27Yper2fTpk1otVpUKhXx8fEMGTLEYY4UCSekpaVVm8ywMo8++qgwc+ZMoUuXLsJHH31kK09MTBRCQ0OFf/3rX0JoaKhw4sQJQRAEYeXKlUKbNm2EJ598Uhg4cKAQGhoqrFy5UhAEQdixY4cQGhoqdO/eXQgNDRXmzJkjrF27VggNDRX27dsnqFQqISYmRoiKihLS09MFQRCEt956SwgNDRW6desmdOzYUejcubMQHh5us+Of//ynEBoaKkRHRwtdunQRunTpIhw6dEgQBEGYMGGC0L9/f0EQBMFisQi9evUSevbsKVgsFkEQBCE6Olp4+eWXBUEQhPDwcKFPnz5C586dhd69ewuhoaFCXFxclfNRUlIitGvXznYufv/9dyE0NFSYPXu2IAiCcObMGSE0NFRYs2aNkJaWJoSGhgqffvqpIAiCcPHiRSEyMlJo166d0KtXL6FLly5CaGio8Pnnn9va6ty5s9C+fXuhV69eQrdu3YTQ0FBh8eLFgiAIwqVLl4Ru3boJnTt3FgYMGCCEhoYKn3zyiZCfny/0799fePTRR4WwsDChf//+glqtdvla38+kpaUJtZ6ky2QyBg0axKZNmzCZTAD8/PPPhISEEBoaalf3+PHjjBo1is2bN/Prr7/SqlUrvvvuO7s6bdq0YevWrUydOtVWptVqmThxIvn5+SxdupTmzZvzxx9/sGnTJoYOHcr+/fvZu3evXYTFw4cPEx8fz/jx49m5cyeJiYkEBQXx97//HUEQ6N69O5mZmeTm5nLmzBny8vLIy8vj7Nmz5OXlkZWVZXuagDWpZ2JiIklJScTExHD48GGKiorsbPfx8SE8PNw2sd69ezcymYykpCQAW4DvHj16VDmPX3zxBSUlJcTHx7Nnzx7eeecdu+0LFizAbDazadMm9uzZw2uvvWa3fc6cOZjNZrZu3cqOHTuIi4tj2bJl6PV6EhISaNiwIW3btiUhIYHAwEDxCyrhkFo7iF6vJzY2FpVKZRuW7Nmzh5EjR1JWVmZXd/78+YwYMYKVK1fy6aefUlZWZpfDA2D48OGEhITg6+trK/voo484ffo0c+fO5dFHHwXgjz/+AOCZZ55BJpPh7+/Pww8/bNunfBg3btw4wDoUHDJkCFlZWaSnp9O9e3cAkpOT2bVrF23btqVVq1bs3r2b48ePA9jqgDVgd3kwvPKhZH5+fpXzERUVxenTp9FqtezatYuhQ4eSlZXFhQsXSE5OJiQkxC5veTkpKSl06NDB1r+K+UQEQeD3338nKiqKkJAQwBpkuxyDwcDhw4dp2rQpSUlJxMfHIwgCZrOZs2fPVjmWhOvU2kG0Wi2dOnXioYceYsOGDWzatAmz2czQoUPR6/V2defOnctzzz1HcnKyS0k4c3NzkcvlbN++3VZWngRTLJ1v+di+4vby/6tUKlq3bk3jxo1JTk5m9+7dREdH069fP5uDNGrUqEqGqprQo0cPzGYz27dv58KFC7zwwgs8/PDDtnYrOl1FSkpKRO/sJpOJsrIy0b6WlJRgsVjIyclhzZo1rFmzhuTkZNq1a1frZKcS9tR6kl5+AWJjY1m4cCEXL16kT58+1K9f3271SqfTsWrVKoYMGcL//u//Atal4IopDMR46623UKvVfP311/Tu3ZuhQ4faJtupqam2H3LFH0N5hJbLly/b7sYXL15EJpPZFgCioqLYtWsXOTk5fPjhh5hMJhYvXkxhYaHoD7k62rdvT0BAAJ999hkPPPAAoaGh9OvXj3Xr1pGdnS3abtOmTUlNTUUQBGQymV1flEolDRo0IDU11VZWcXtQUBB+fn5Vhqxms9lh4h4J17npF4XDhw9HEATS0tKIjY2tsl0ul6NUKjl58iRHjhxh+fLlHDx40DZvcUarVq2YOnUqHTp04MMPPyQrK4u+ffvi6+vL/Pnz2b17N99++y0HDhyw7TN48GD8/Pz44IMPOHjwIPHx8WzcuJHevXvbks5369aN7OxsGjduTNu2benQoQONGjXi6tWrtXYQuVxO165dyc7Opl+/fgD069ePzMxMFApFlRRtFe1NT0/n448/Zu/evcydO9du+5AhQ0hJSWHhwoXs2bOHTz75xG776NGj+e2335g3bx7JycksWbKEQYMG2W5zgRuiAAASx0lEQVRA/v7+pKenk5SUVOXJLlE9N+0gwcHB9OrVy7acWhl3d3c+/PBDcnJyiIuLY+3atURERFBWVmaXa1AMuVzORx99hNFo5K233sLHx4f58+dTWFjI5MmTWbdund2PLzg4mIULF6LVannxxReZNWsWPXr04KOPPrLV6dGjB25ubkRHRyOTyZDJZERHR+Pm5kZUVFStz0W5c0VHRwMQFhZG48aN6dSpk93cqiLleQ+/++47Jk+eTPPmze22T548mQEDBvD111/z6quv2pZp3dysl+7111/nb3/7GytXrmTs2LF8/fXXDBs2zLZwMXbsWDQaDZMmTaoy75OoHllaWppwOwLHmc1mCgsLq81FWFMEQUCtVtutYFWmsLAQpVKJj49PnRzzVlJaWopcLq/ynsJisaBWq3F3d0epVLJz505mzpzJggULGDRokK2e2WxGpVJVyXcC1rmK2WwWnctIOCY9Pb32cxBXkcvldeYcYF1mduYcwF21rCnmxD/++COLFi3imWeewd3dne+//54mTZpUWTKWy+W2IWRlxJ5eEtVz2xxEonYMGzaM3NxcEhMTMRqNdOvWjalTp9ZJAlSJ6rltQywJibsNKTavhEQ1SA4iIeEEyUEkJJwgOYiEhBMkB5GQcILkIBISTpAcRELCCZKDSEg4QXIQCQknSA4iIeEEyUEkJJzgsoMUFRUxb948Fi9eXGVbecC3ip/I1gSdTkebNm1Yv36903plZWW0adOG+Ph4l9q/GZKSkkhISKjzdiPnFJBRYMZkho+3laIzOP9E9pt9Ov75c0md21GZY2lGNv5eVm29fvPUnMmu/qO3ux2XHUSj0bBkyRLmz5/P+fPn7bYtXryYJUuWsGfPnjoz8K/Gw8MDDw+PW9a+TAY+HjJkstq3seaonvkJ2jqxx10hw0NxE8YAY5cWcfn6vRE0sNZDLF9fX37++Wfb32q1mqSkpCrfHly6dIn4+Hh+/fVX2yefKpWKbdu2kZGRwZo1azAajXb7nDlzhm3bttkCMBw9epQ1a9Y4jNX722+/sWrVKg4ePGj7Xvvw4cOcPn0asH4stH37diwWi63+yZMnyczMJCEhAZVKxbp169ixY4etTkWCg4PtvrNISUlh1apV7Nmzx2F9sAab+Pnnn21RJp0hk0GrhgoUbtYfZZlJYMvJMjallHG92MKe8/YxfE9fM7H6Nz2nr1nv3qeyTCSnmziVZeLQZSNpKjOnsm7c2Y+mGlFrredFaxDYde5Gexdyzfx4RM/hKzfOv7+XjAcCb/ws0lRm4o/qOZpm5EKumdT8Gz98gwm2nTLw0/EyivUCZgtsP20gLd9M0nkDWYWOz8/dRK0dpH///nYxsX755RcaNmxoi/YH8MMPPzB8+HASExNZsGABY8aMwWg0cv78eaZPn86oUaP4+OOP7QI5nzp1inHjxnH69Gm8vLxYvnw548ePZ/369bz00kt2NsyZM4eJEyeSkJDA1KlTmTFjBgBbtmyxBYjYuXMn06ZNs8UJnjFjBseOHePQoUNMmzaNuLg4NmzYwPTp0/n000+r9HPz5s1s3LgRgNWrV/OPf/yD0tJSFi9e7DCk5++//864cePIyckhJSWF2NhYuyiKlTGZBWasKUZvEhAEeGVlMWuPlXEpz8zk74v59y+ltroHLhlZcVBPdpGFF5ZrOHnVREGphSKdhdIygbxiC3kaC7M3l/7ZNkxdXcwvKdYh0+ErRlYdtt6kNv5exow1xRTpBT7fpbU9gZLOGfjhiLXOH1dNPLu4iNR8M2uPlfHSCg2JZ25cqw83l3Iyy0TC6TJeWanBIsDVAjNmC+RprDbd7dTaQaKjo9HpdOzduxewBo2LjY21i4nl4+PDp59+yuLFi/nPf/7DuXPnuHjxom37e++9R3Jysu1ruszMTF555RX69evHjBkzMJvNLFq0iPHjx7NmzRq7yB2XL19m5cqVzJ07l2+//ZbFixezdetWDh8+TI8ePTh58iQGg4GkpCQ8PT3ZvXs3GRkZ5Ofn277Gs1gsLFq0iFWrVjFo0CD279/vtM/79+9nzJgxvPzyy3zxxRcO41wBfPLJJ0yaNIlZs2bh4eHBqVOnanROj6QayVKbWRrnz4wB3rz1hLfd9taN5Hw80pc3BngT86g7h68Y6dXanYiWSkIbyRka7kHn5koyC8wU6QSOphlpFiS3PTWOphrp+YgSoxn+s7WUT8f4Mam3F1+N82fVYX2VedDSfTom9PDinUE+fDzSl07N7b+ve72fF2894c2C0X78cdVEmUlgQk8vvN1lDOvkQetGd39klZuKrBgTE8OGDRu4cOEC586dY/jw4XZ3y8cee4yEhASefvppZs6cCVi/vS4nLCzMrs3FixejVquZMmUKMpmMvLw8iouL6dq1K4DdUKd8CFUeKKFz5874+Pjwxx9/0LVrV0wmEydOnODAgQO8+uqrtvhU9evXt4t71ahRIwDq1auHVut8HP/iiy+ybNkyxo8fz5YtW5gwYUKVOm3btiUpKYlx48YxcuRIsrKynD5BKpJ63Uz7BxTI/7wqfl72c4EGfjcul5+nDJ2x6h1aIYfHQ5T8lmpk5zkDr/TyoqDEglor8Fuqid6h7lxVmynWC3y0tZSXVmh4Y00xFgGuFdkPia5cN9PxwRtO4e/p2B4vdxluMusQ7l6j1p/cGo1GRowYwYsvvoiXlxddu3aladOmdvOJN998Ex8fH5YtW0ZBQQFDhw512manTp0oLS3lH//4BytWrLAFia4cqRGs4WzAugIWGBiI0WikrKwMDw8P/P396dChA1999RXBwcE8//zzLFq0iF9//ZUePXogq+WMuHPnziQmJpKSksKPP/7Ixo0bq6yoLVq0iJycHJYuXYqnpycjR46scfveHjIMdbAw1Ku1O4cuGzlyxcjMAd6kZLrzy+9laA0CIfXl5GosKOQy/vW0D24VTkVDP/v7pbeHDMO9MdeuNbV+ggiCwGOPPUaTJk3YtGkTI0aMqFLn2rVrBAQEIJPJ2LZtG4DTlAixsbH83//9H6dOnWLp0qUEBgbSunVrfvjhB7Kzs9m0aZOtbufOnQkICGDJkiUUFhaydOlSBEGgV69egDW0z6FDh4iOjsbLy4tu3bqxb9++Wse9AmsMsP379xMeHs706dM5e/ZslfhemZmZNGvWDE9PTy5dukRGRkaN00B0bq4gOd3IVbX1Tn4srWbeonCzv3v3fETJ5j/KeKiBHC93Gf3auvP1Xi09HrFGO2nk70bLYDeOXDHyYD05Df3c2H/RiNzN/sYR2VLJhuN6BAF0BsG2MFAdSjnoHOcHuuu4qReFMpmM2NhY/Pz8bMHSKjJz5kx27txJz549OXfuHEC1SXFatWrFtGnT+Oyzz0hJSeHDDz8kLS2Nvn37snXrVttTxd/fnwULFrBr1y66du3Kt99+y4cffmiLYVsegLrcrv79+yOTyW4q7tWbb77JBx98wNChQxkxYgTvvPMOCoX9Q/iFF14gPj6e6Oho3n33XZo0aUJ6enqN2m8WJGdKX29GfVXIEwsK2XexZr+yrg8r2XvRyKvfFwPWoU+LIDn92roDEPagAk+ljF6t3W37fDzKjx+O6Hn680JiFhRyvcRiG9qVM7GXF9dLBPrNUzPiyyJk1Gw5um8bd17/UcOWk9W/T7nTueVBG8qHPjcTekYQBEpKSkQjeRQWFuLn53fbwm1qNBp8fHxEj2exWCgpKbENA11qWyfgqQSLAPsvGll2QMcPE6uPZ6U3ClgE8HZ3bfio0Ql4KHH47sNgsrZpNIOvh4wJKzQM7+TB0x2rfy9UqBXw85RVcbq7ifT0dCmqyV/Bf3drKSitOqG1CLDtVBlNA+V4KuBinpnwZkqaBf01v7Kraguns0y0qO9Gsc66jPxEew8UNTSnoZ8br/T2qr7iHYrkIHcgxXqBfRcN5JcIdH1I+ZcvlV7INZOcZsTLXUZMO3eXn1B3M7c1sqJEzfDzlPFkh1snbXGV1o3kf7mT/pXcxSNECYlbzx3hICVlAu3+pcJgqvsXTR9vK60zIZ/E/ccd4SASEncqLs9BivUCKZkmWtaXc/iKgcb+crq3UnIm28TvGSYebiin60M3wu9nFpg5kmrC2x2i27jjqZShMwgcumIkpL6cExkmYtq52x3jbLaJkjKBiJZKtp820DdUibtCRplJYM95IzHt3LlWaCG/xIJcBqeumWj/gIJ2TavvTmq+mWNpRvy93Ihu445Sbl3qPJll4uEGcvZfMhLsI6NPqLttzT+zwMzhP1+qNfJ3wyzAIw3l7LtopMMDCgK9rRUTzxro/rASL3errQcvGcnVWHispZJWDW+M4y/mmTmebqRNYwUKuVXC0SzIuv30NRN/ZJp4pJGcx1oqq9gvcXtx+QlyrdDM9DXFzP21lOwiC7N/KeHl7zQs2q1DrbXw7k8ltg9udp0zMGllMUU6C7vPGRm/TIMggKrUqmB9a20JZyt9dHMiw8Sr3xfj6yFDEGDGmmJK/lSFFusF3lxrfRmWnG7k1e81LN6n46rawoRvNSSccf5ibePvZUz70dre5pQyXv3eKkXPVJuZGV/MrE0l5GksfLxNyzf7rfqpC7lmRn9dxIVcM5v/KOOF5Rq2/vkCbM6vpaSrbrwlf/enElSlAjqDwPNLNey5YKRYL/DCcg1HU60SnEOXjcR9U0RmgYXlB3RMWlnMwcvWbT/+puf99SUU6QX+b7uWr/bUTMMlceuo1SqWuxwWPuuHUg4h9eUsSNCy4416yN0gwMuNfRcMDA33wFMp49Nn/XikoRyLAFFzC8gqNAPWO+6SOH/qectsDnAqy8wba4pZOMaPtk0UVJeHskWwnIVjrC8PQxvLWbJXx4BH3UXrB3rJ+Pw5P1oEy3nucYHHPihAo7MeRCaDec/44eMho0WwGz8dL+Olnl4s269jTKQnU/tZlbXvr6/+q75Sg8DfunvaVqP0RoGEswYiQpR8vUfHtP7ejI6wJsp5YbnGVmdBgpYNUwJ5INCNEZ09GLSwkIk9ve7ql213O7VyEA+FDOWfIwZ/TxnBPm62i+jjIUP7p16xfVMFXyZpOXXNRJkR9CYBnRG8lKCQy6jnbb+mPm11MV0fUhLevGZm+Xjc2L9TcyWzNpU6qQ0dHlTw3906zueYMJhAwGoTWN9Al7fn6yGzSb+v5JsZ1P6G0/l5Vv8eoL6vG0q5jIkrNJSUCeRqLPT8Uwd1Jd9MeLOqCtkr+WYMJoH/2XjDAQ0m68u5JgGSh/xV3NL3IHO3lNLQ343lfwtAIYdeH6ud1p8+wJtFu7UknDHYngQ1Fd5qDQJeSueV/7GhlE7NFbz/ZAByN+j47+pz9nm7iytaZVidrDKnskx8sr2Ub18MoEmAG0v26riqNt9oz4Hmz9tdhqdSxqwh9pmmGvhKzvFXckvPfmaBmRbBchRyOJ5uQqOzYDKLj5ueDnNnwWg/Zv9SylW1GZnMejcu/7654qek1vYttuHZhuN6Ordw7u8ZBWZCGsiRu8G+i0bMFutXd86IbKlkw4kyzBbr57B/XL1hQwM/N678aduFXLNtmTpTbcHXw42Gfm7ojQKHrxgxmsvbU7D+hHUOo9EJXMi1bmhWT049bzdSMk08WE9Ofd9yha1z+yRuLbf0CTKpjzfv/lTMV0lamgdZL3pmgYVHm4pf9bAHFfytuycz4ktY9VIAU/p68foPxQR4ufFQA/s3ujIZvLhcQ2mZgEwGX45znpbstWhv/vlzCf+7RUabxgoCvGRkFpjxdTJsGt/NkzdWF9N/nhoPpQz/Ch8xvdTTk3d+KmHJXh2NAtzw/PMJ1jdUSfxRPX0/UaNwg3ZNFWT++QR5rZ83r/1gbc/LXYZCbn0Syd1g3mhf3llXwtd7dBTpBJ6P8rypYA4SN88t12KZLdbhT03G7mKUmawBASrqgH5JKeOXlDIWj/dHoxPsfrjOMJmt8w5fj5rVL39ymC3WOc/UH4vp+YjSNsk2mUFndNw/jU7Ax8Ne0aozCCjk1jmOv5eMkV8W8Xo/L3pXkKIX6QS8lNYIIxJ/HbXSYn2w2flE+HaRpjKTnm++5fbkFFk4kWkkJFhOqUHgWqGFet5uXMit3XGvXDdz+bqZ5kFy1FoLRTqBpHNG9l4wOqz/WEul3SKBxO3FZQf55+A7I+f4pTwzl/LMDLwNP57L1838lmrEXSEj5lH3m3oaApzOMnEi00SAl1Uhe7NxqCRuHZLcXUJCBCnLrYRENUgOIiHhBMlBJCScIDmIhIQTJAeRkHCC5CASEk6QHERCwglubm5uNQ6NKSFxv2A2m3Fzc8PN09PTLuK6hISENfGSp6cnboGBgWg0GjQajfQkkbjvMZvNaDQaiouLCQoKQiYIgmAymVCr1ej1etG0YhIS9wNubm54enoSFBSEXC7n/wF1srhlxTIbmgAAAABJRU5ErkJggg==", |
175 | 175 | "description": "Renders markdown/HTML using configurable pattern or function with applied attributes or timeseries values.", |
176 | 176 | "descriptor": { |
... | ... | @@ -180,10 +180,10 @@ |
180 | 180 | "resources": [], |
181 | 181 | "templateHtml": "<tb-markdown-widget \n [ctx]=\"ctx\">\n</tb-markdown-widget>", |
182 | 182 | "templateCss": "#container tb-markdown-widget {\n height: 100%;\n display: block;\n}\n\n#container tb-markdown-widget .tb-markdown-view {\n height: 100%;\n overflow: auto;\n}\n", |
183 | - "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.markdownWidget.onDataUpdated();\n}\n\nself.actionSources = function() {\n return {\n 'elementClick': {\n name: 'widget-action.element-click',\n multiple: true\n }\n };\n}\n\nself.onDestroy = function() {\n}\n\n", | |
184 | - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Markdown card\",\n \"properties\": {\n \"markdownTextPattern\": {\n \"title\": \"Markdown pattern (markdown with variables, for ex. '${entityName} or ${keyName} - some text.')\",\n \"type\": \"string\",\n \"default\": \"# Markdown card \\n - **Current entity**: **${entityName}**. \\n - **Current value**: **${Random}**.\"\n },\n \"markdownCss\": {\n \"title\": \"Markdown CSS\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useMarkdownTextFunction\": {\n \"title\": \"Use markdown text function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"markdownTextFunction\": {\n \"title\": \"Markdown text function: f(data)\",\n \"type\": \"string\",\n \"default\": \"return '# Some title\\\\n - Entity name: ' + data[0]['entityName'];\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"markdownTextPattern\",\n \"type\": \"markdown\"\n },\n {\n \"key\": \"markdownCss\",\n \"type\": \"css\"\n },\n \"useMarkdownTextFunction\",\n {\n \"key\": \"markdownTextFunction\",\n \"type\": \"javascript\"\n }\n ]\n}\n", | |
183 | + "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.markdownWidget.onDataUpdated();\n}\n\nself.actionSources = function() {\n return {\n 'elementClick': {\n name: 'widget-action.element-click',\n multiple: true\n }\n };\n}\n\nself.typeParameters = function() {\n return {\n dataKeysOptional: true,\n datasourcesOptional: true\n };\n}\n\nself.onDestroy = function() {\n}\n\n", | |
184 | + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Markdown/HTML card\",\n \"properties\": {\n \"markdownTextPattern\": {\n \"title\": \"Markdown/HTML pattern (markdown or HTML with variables, for ex. '${entityName} or ${keyName} - some text.')\",\n \"type\": \"string\",\n \"default\": \"# Markdown/HTML card \\n - **Current entity**: **${entityName}**. \\n - **Current value**: **${Random}**.\"\n },\n \"markdownCss\": {\n \"title\": \"Markdown/HTML CSS\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useMarkdownTextFunction\": {\n \"title\": \"Use markdown/HTML value function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"markdownTextFunction\": {\n \"title\": \"Markdown/HTML value function: f(data)\",\n \"type\": \"string\",\n \"default\": \"return '# Some title\\\\n - Entity name: ' + data[0]['entityName'];\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useMarkdownTextFunction\",\n {\n \"key\": \"markdownTextPattern\",\n \"type\": \"markdown\",\n \"condition\": \"model.useMarkdownTextFunction !== true\"\n },\n {\n \"key\": \"markdownTextFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/markdown/markdown_text_fn\",\n \"condition\": \"model.useMarkdownTextFunction === true\"\n },\n {\n \"key\": \"markdownCss\",\n \"type\": \"css\"\n }\n ]\n}\n", | |
185 | 185 | "dataKeySettingsSchema": "{}\n", |
186 | - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"markdownTextPattern\":\"### Markdown card\\n - **Current entity**: ${entityName}.\\n - **Current value**: ${Random}.\",\"markdownTextFunction\":\"return '# Some title\\\\n - Entity name: ' + data[0]['entityName'];\"},\"title\":\"Markdown Card\",\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"showLegend\":false}" | |
186 | + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"markdownTextPattern\":\"### Markdown/HTML card\\n - **Current entity**: ${entityName}.\\n - **Current value**: ${Random}.\",\"markdownTextFunction\":\"return '# Some title\\\\n - Entity name: ' + data[0]['entityName'];\",\"useMarkdownTextFunction\":false},\"title\":\"Markdown/HTML Card\",\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"showLegend\":false}" | |
187 | 187 | } |
188 | 188 | } |
189 | 189 | ] | ... | ... |
... | ... | @@ -73,7 +73,7 @@ |
73 | 73 | "templateHtml": "<tb-switch [ctx]='ctx'></tb-switch>", |
74 | 74 | "templateCss": "", |
75 | 75 | "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n", |
76 | - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"Switch title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showOnOffLabels\": {\n \"title\": \"Show on/off labels\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve on/off value using method\",\n \"type\": \"string\",\n \"default\": \"rpc\"\n },\n \"valueKey\": {\n \"title\": \"Attribute/Timeseries value key (only when subscribe for attribute/timeseries method)\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"getValueMethod\": {\n \"title\": \"RPC get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"RPC set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"convertValueFunction\": {\n \"title\": \"Convert value function, f(value), returns payload used by RPC set value method\",\n \"type\": \"string\",\n \"default\": \"return value;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"persistentPollingInterval\": {\n \"title\": \"Polling interval in milliseconds to get persistent RPC command response\",\n \"type\": \"number\",\n \"default\": 5000,\n \"minimum\": 1000\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n \"showOnOffLabels\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"none\",\n \"label\": \"Don't retrieve\"\n },\n {\n \"value\": \"rpc\",\n \"label\": \"Call RPC get value method\"\n },\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueKey\",\n \"getValueMethod\",\n \"setValueMethod\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\",\n \"requestPersistent\",\n {\n \"key\": \"persistentPollingInterval\",\n \"condition\": \"model.requestPersistent === true\"\n }\n ]\n}", | |
76 | + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"Switch title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showOnOffLabels\": {\n \"title\": \"Show on/off labels\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve on/off value using method\",\n \"type\": \"string\",\n \"default\": \"rpc\"\n },\n \"valueKey\": {\n \"title\": \"Attribute/Timeseries value key (only when subscribe for attribute/timeseries method)\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"getValueMethod\": {\n \"title\": \"RPC get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"RPC set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"convertValueFunction\": {\n \"title\": \"Convert value function, f(value), returns payload used by RPC set value method\",\n \"type\": \"string\",\n \"default\": \"return value;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"persistentPollingInterval\": {\n \"title\": \"Polling interval in milliseconds to get persistent RPC command response\",\n \"type\": \"number\",\n \"default\": 5000,\n \"minimum\": 1000\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n \"showOnOffLabels\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"none\",\n \"label\": \"Don't retrieve\"\n },\n {\n \"value\": \"rpc\",\n \"label\": \"Call RPC get value method\"\n },\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueKey\",\n \"getValueMethod\",\n \"setValueMethod\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/rpc/parse_value_fn\"\n },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/rpc/convert_value_fn\"\n },\n \"requestTimeout\",\n \"requestPersistent\",\n {\n \"key\": \"persistentPollingInterval\",\n \"condition\": \"model.requestPersistent === true\"\n }\n ]\n}", | |
77 | 77 | "dataKeySettingsSchema": "{}\n", |
78 | 78 | "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":false,\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\",\"showOnOffLabels\":true,\"title\":\"Switch control\"},\"title\":\"Switch Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" |
79 | 79 | } |
... | ... | @@ -91,7 +91,7 @@ |
91 | 91 | "templateHtml": "<tb-round-switch [ctx]='ctx'></tb-round-switch>", |
92 | 92 | "templateCss": "", |
93 | 93 | "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n", |
94 | - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"Switch title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve on/off value using method\",\n \"type\": \"string\",\n \"default\": \"rpc\"\n },\n \"valueKey\": {\n \"title\": \"Attribute/Timeseries value key (only when subscribe for attribute/timeseries method)\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"getValueMethod\": {\n \"title\": \"RPC get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"RPC set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"convertValueFunction\": {\n \"title\": \"Convert value function, f(value), returns payload used by RPC set value method\",\n \"type\": \"string\",\n \"default\": \"return value;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"persistentPollingInterval\": {\n \"title\": \"Polling interval in milliseconds to get persistent RPC command response\",\n \"type\": \"number\",\n \"default\": 5000,\n \"minimum\": 1000\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"none\",\n \"label\": \"Don't retrieve\"\n },\n {\n \"value\": \"rpc\",\n \"label\": \"Call RPC get value method\"\n },\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueKey\",\n \"getValueMethod\",\n \"setValueMethod\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\",\n \"requestPersistent\",\n {\n \"key\": \"persistentPollingInterval\",\n \"condition\": \"model.requestPersistent === true\"\n }\n ]\n}", | |
94 | + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"Switch title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve on/off value using method\",\n \"type\": \"string\",\n \"default\": \"rpc\"\n },\n \"valueKey\": {\n \"title\": \"Attribute/Timeseries value key (only when subscribe for attribute/timeseries method)\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"getValueMethod\": {\n \"title\": \"RPC get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"RPC set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"convertValueFunction\": {\n \"title\": \"Convert value function, f(value), returns payload used by RPC set value method\",\n \"type\": \"string\",\n \"default\": \"return value;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"persistentPollingInterval\": {\n \"title\": \"Polling interval in milliseconds to get persistent RPC command response\",\n \"type\": \"number\",\n \"default\": 5000,\n \"minimum\": 1000\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"none\",\n \"label\": \"Don't retrieve\"\n },\n {\n \"value\": \"rpc\",\n \"label\": \"Call RPC get value method\"\n },\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueKey\",\n \"getValueMethod\",\n \"setValueMethod\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/rpc/parse_value_fn\"\n },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/rpc/convert_value_fn\"\n },\n \"requestTimeout\",\n \"requestPersistent\",\n {\n \"key\": \"persistentPollingInterval\",\n \"condition\": \"model.requestPersistent === true\"\n }\n ]\n}", | |
95 | 95 | "dataKeySettingsSchema": "{}\n", |
96 | 96 | "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":false,\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\",\"title\":\"Round switch\",\"retrieveValueMethod\":\"rpc\",\"valueKey\":\"value\",\"parseValueFunction\":\"return data ? true : false;\",\"convertValueFunction\":\"return value;\"},\"title\":\"Round switch\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" |
97 | 97 | } |
... | ... | @@ -109,7 +109,7 @@ |
109 | 109 | "templateHtml": "<tb-led-indicator [ctx]='ctx'></tb-led-indicator>", |
110 | 110 | "templateCss": "", |
111 | 111 | "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n", |
112 | - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"LED title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"ledColor\": {\n \"title\": \"LED Color\",\n \"type\": \"string\",\n \"default\": \"green\"\n },\n \"performCheckStatus\": {\n \"title\": \"Perform RPC device status check\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"checkStatusMethod\": {\n \"title\": \"RPC check device status method\",\n \"type\": \"string\",\n \"default\": \"checkStatus\"\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve led status value using method\",\n \"type\": \"string\",\n \"default\": \"attribute\"\n },\n \"valueAttribute\": {\n \"title\": \"Device attribute/timeseries containing led status value\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse led status value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"persistentPollingInterval\": {\n \"title\": \"Polling interval in milliseconds to get persistent RPC command response\",\n \"type\": \"number\",\n \"default\": 5000,\n \"minimum\": 1000\n }\n },\n \"required\": [\"valueAttribute\", \"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"ledColor\",\n \"type\": \"color\"\n },\n \"performCheckStatus\",\n \"checkStatusMethod\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueAttribute\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\",\n \"requestPersistent\",\n {\n \"key\": \"persistentPollingInterval\",\n \"condition\": \"model.requestPersistent === true\"\n }\n ]\n}", | |
112 | + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"LED title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"ledColor\": {\n \"title\": \"LED Color\",\n \"type\": \"string\",\n \"default\": \"green\"\n },\n \"performCheckStatus\": {\n \"title\": \"Perform RPC device status check\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"checkStatusMethod\": {\n \"title\": \"RPC check device status method\",\n \"type\": \"string\",\n \"default\": \"checkStatus\"\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve led status value using method\",\n \"type\": \"string\",\n \"default\": \"attribute\"\n },\n \"valueAttribute\": {\n \"title\": \"Device attribute/timeseries containing led status value\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse led status value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"persistentPollingInterval\": {\n \"title\": \"Polling interval in milliseconds to get persistent RPC command response\",\n \"type\": \"number\",\n \"default\": 5000,\n \"minimum\": 1000\n }\n },\n \"required\": [\"valueAttribute\", \"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"ledColor\",\n \"type\": \"color\"\n },\n \"performCheckStatus\",\n \"checkStatusMethod\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueAttribute\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/rpc/parse_value_fn\"\n },\n \"requestTimeout\",\n \"requestPersistent\",\n {\n \"key\": \"persistentPollingInterval\",\n \"condition\": \"model.requestPersistent === true\"\n }\n ]\n}", | |
113 | 113 | "dataKeySettingsSchema": "{}\n", |
114 | 114 | "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":true,\"title\":\"Led indicator\",\"ledColor\":\"#4caf50\",\"valueAttribute\":\"value\",\"retrieveValueMethod\":\"attribute\",\"parseValueFunction\":\"return data ? true : false;\",\"performCheckStatus\":true,\"checkStatusMethod\":\"checkStatus\"},\"title\":\"Led indicator\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" |
115 | 115 | } |
... | ... | @@ -151,4 +151,4 @@ |
151 | 151 | } |
152 | 152 | } |
153 | 153 | ] |
154 | -} | |
154 | +} | |
\ No newline at end of file | ... | ... |
... | ... | @@ -19,8 +19,8 @@ |
19 | 19 | "templateHtml": "<tb-entities-table-widget \n [ctx]=\"ctx\">\n</tb-entities-table-widget>", |
20 | 20 | "templateCss": "", |
21 | 21 | "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n hasDataPageLink: true,\n warnOnPageDataOverflow: false,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true,\n hasShowCondition: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n },\n 'rowDoubleClick': {\n name: 'widget-action.row-double-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", |
22 | - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"reserveSpaceForHiddenAction\": {\n \"title\": \"Hidden cell button actions display mode\",\n \"type\": \"string\",\n \"default\": \"true\"\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityLabel\": {\n \"title\": \"Display entity label column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"entityLabelColumnTitle\": {\n \"title\": \"Entity label column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n {\n \"key\": \"reserveSpaceForHiddenAction\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"true\",\n \"label\": \"Show empty space instead of hidden cell button action\"\n },\n {\n \"value\": \"false\",\n \"label\": \"Don't reserve space for hidden action buttons\"\n }\n ]\n },\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityLabel\",\n \"entityLabelColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}", | |
23 | - "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value, entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"defaultColumnVisibility\": {\n \"title\": \"Default column visibility\",\n \"type\": \"string\",\n \"default\": \"visible\"\n },\n \"columnSelectionToDisplay\": {\n \"title\": \"Column selection in 'Columns to Display'\",\n \"type\": \"string\",\n \"default\": \"enabled\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useCellStyleFunction === true\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useCellContentFunction === true\"\n },\n {\n \"key\": \"defaultColumnVisibility\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"visible\",\n \"label\": \"Visible\"\n },\n {\n \"value\": \"hidden\",\n \"label\": \"Hidden\"\n } \n ]\n },\n {\n \"key\": \"columnSelectionToDisplay\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"enabled\",\n \"label\": \"Enabled\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n } \n ]\n }\n ]\n}", | |
22 | + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"reserveSpaceForHiddenAction\": {\n \"title\": \"Hidden cell button actions display mode\",\n \"type\": \"string\",\n \"default\": \"true\"\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityLabel\": {\n \"title\": \"Display entity label column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"entityLabelColumnTitle\": {\n \"title\": \"Entity label column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n {\n \"key\": \"reserveSpaceForHiddenAction\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"true\",\n \"label\": \"Show empty space instead of hidden cell button action\"\n },\n {\n \"value\": \"false\",\n \"label\": \"Don't reserve space for hidden action buttons\"\n }\n ]\n },\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityLabel\",\n \"entityLabelColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/entity/row_style_fn\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}", | |
23 | + "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value, entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"defaultColumnVisibility\": {\n \"title\": \"Default column visibility\",\n \"type\": \"string\",\n \"default\": \"visible\"\n },\n \"columnSelectionToDisplay\": {\n \"title\": \"Column selection in 'Columns to Display'\",\n \"type\": \"string\",\n \"default\": \"enabled\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/entity/cell_style_fn\",\n \"condition\": \"model.useCellStyleFunction === true\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/entity/cell_content_fn\",\n \"condition\": \"model.useCellContentFunction === true\"\n },\n {\n \"key\": \"defaultColumnVisibility\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"visible\",\n \"label\": \"Visible\"\n },\n {\n \"value\": \"hidden\",\n \"label\": \"Hidden\"\n } \n ]\n },\n {\n \"key\": \"columnSelectionToDisplay\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"enabled\",\n \"label\": \"Enabled\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n } \n ]\n }\n ]\n}", | |
24 | 24 | "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSearch\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true,\"entitiesTitle\":\"Device admin table\",\"enableSelectColumnDisplay\":true},\"title\":\"Device admin table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{\"headerButton\":[{\"name\":\"Add device\",\"icon\":\"add\",\"type\":\"customPretty\",\"customHtml\":\"<form #addDeviceForm=\\\"ngForm\\\" [formGroup]=\\\"addDeviceFormGroup\\\"\\n (ngSubmit)=\\\"save()\\\" style=\\\"width: 480px;\\\">\\n <mat-toolbar fxLayout=\\\"row\\\" color=\\\"primary\\\">\\n <h2>Add device</h2>\\n <span fxFlex></span>\\n <button mat-button mat-icon-button\\n (click)=\\\"cancel()\\\"\\n type=\\\"button\\\">\\n <mat-icon class=\\\"material-icons\\\">close</mat-icon>\\n </button>\\n </mat-toolbar>\\n <mat-progress-bar color=\\\"warn\\\" mode=\\\"indeterminate\\\" *ngIf=\\\"isLoading$ | async\\\">\\n </mat-progress-bar>\\n <div style=\\\"height: 4px;\\\" *ngIf=\\\"!(isLoading$ | async)\\\"></div>\\n <div mat-dialog-content>\\n <div class=\\\"mat-padding\\\" fxLayout=\\\"column\\\">\\n <mat-form-field class=\\\"mat-block\\\">\\n <mat-label>Device name</mat-label>\\n <input matInput formControlName=\\\"deviceName\\\" required>\\n <mat-error *ngIf=\\\"addDeviceFormGroup.get('deviceName').hasError('required')\\\">\\n Device name is required.\\n </mat-error>\\n </mat-form-field>\\n <div fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <tb-entity-subtype-autocomplete\\n fxFlex=\\\"50\\\"\\n formControlName=\\\"deviceType\\\"\\n [required]=\\\"true\\\"\\n [entityType]=\\\"'DEVICE'\\\"\\n ></tb-entity-subtype-autocomplete>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Label</mat-label>\\n <input matInput formControlName=\\\"deviceLabel\\\">\\n </mat-form-field>\\n </div>\\n <div formGroupName=\\\"attributes\\\" fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Latitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"latitude\\\">\\n </mat-form-field>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Longitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"longitude\\\">\\n </mat-form-field>\\n </div>\\n </div> \\n </div>\\n <div mat-dialog-actions fxLayout=\\\"row\\\">\\n <span fxFlex></span>\\n <button mat-button color=\\\"primary\\\"\\n type=\\\"button\\\"\\n [disabled]=\\\"(isLoading$ | async)\\\"\\n (click)=\\\"cancel()\\\" cdkFocusInitial>\\n Cancel\\n </button>\\n <button mat-button mat-raised-button color=\\\"primary\\\"\\n style=\\\"margin-right: 20px;\\\"\\n type=\\\"submit\\\"\\n [disabled]=\\\"(isLoading$ | async) || addDeviceForm.invalid || !addDeviceForm.dirty\\\">\\n Create\\n </button>\\n </div>\\n</form>\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenAddDeviceDialog();\\n\\nfunction openAddDeviceDialog() {\\n customDialog.customDialog(htmlTemplate, AddDeviceDialogController).subscribe();\\n}\\n\\nfunction AddDeviceDialogController(instance) {\\n let vm = instance;\\n \\n vm.addDeviceFormGroup = vm.fb.group({\\n deviceName: ['', [vm.validators.required]],\\n deviceType: ['', [vm.validators.required]],\\n deviceLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.addDeviceFormGroup.markAsPristine();\\n let device = {\\n name: vm.addDeviceFormGroup.get('deviceName').value,\\n type: vm.addDeviceFormGroup.get('deviceType').value,\\n label: vm.addDeviceFormGroup.get('deviceLabel').value\\n };\\n deviceService.saveDevice(device).subscribe(\\n function (device) {\\n saveAttributes(device.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n function saveAttributes(entityId) {\\n let attributes = vm.addDeviceFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"70837a9d-c3de-a9a7-03c5-dccd14998758\"}],\"actionCellButton\":[{\"name\":\"Edit device\",\"icon\":\"edit\",\"type\":\"customPretty\",\"customHtml\":\"<form #editDeviceForm=\\\"ngForm\\\" [formGroup]=\\\"editDeviceFormGroup\\\"\\n (ngSubmit)=\\\"save()\\\" style=\\\"width: 480px;\\\">\\n <mat-toolbar fxLayout=\\\"row\\\" color=\\\"primary\\\">\\n <h2>Edit device</h2>\\n <span fxFlex></span>\\n <button mat-button mat-icon-button\\n (click)=\\\"cancel()\\\"\\n type=\\\"button\\\">\\n <mat-icon class=\\\"material-icons\\\">close</mat-icon>\\n </button>\\n </mat-toolbar>\\n <mat-progress-bar color=\\\"warn\\\" mode=\\\"indeterminate\\\" *ngIf=\\\"isLoading$ | async\\\">\\n </mat-progress-bar>\\n <div style=\\\"height: 4px;\\\" *ngIf=\\\"!(isLoading$ | async)\\\"></div>\\n <div mat-dialog-content>\\n <div class=\\\"mat-padding\\\" fxLayout=\\\"column\\\">\\n <mat-form-field class=\\\"mat-block\\\">\\n <mat-label>Device name</mat-label>\\n <input matInput formControlName=\\\"deviceName\\\" required>\\n <mat-error *ngIf=\\\"editDeviceFormGroup.get('deviceName').hasError('required')\\\">\\n Device name is required.\\n </mat-error>\\n </mat-form-field>\\n <div fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <tb-entity-subtype-autocomplete\\n fxFlex=\\\"50\\\"\\n formControlName=\\\"deviceType\\\"\\n [required]=\\\"true\\\"\\n [entityType]=\\\"'DEVICE'\\\"\\n ></tb-entity-subtype-autocomplete>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Label</mat-label>\\n <input matInput formControlName=\\\"deviceLabel\\\">\\n </mat-form-field>\\n </div>\\n <div formGroupName=\\\"attributes\\\" fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Latitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"latitude\\\">\\n </mat-form-field>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Longitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"longitude\\\">\\n </mat-form-field>\\n </div>\\n </div> \\n </div>\\n <div mat-dialog-actions fxLayout=\\\"row\\\">\\n <span fxFlex></span>\\n <button mat-button color=\\\"primary\\\"\\n type=\\\"button\\\"\\n [disabled]=\\\"(isLoading$ | async)\\\"\\n (click)=\\\"cancel()\\\" cdkFocusInitial>\\n Cancel\\n </button>\\n <button mat-button mat-raised-button color=\\\"primary\\\"\\n style=\\\"margin-right: 20px;\\\"\\n type=\\\"submit\\\"\\n [disabled]=\\\"(isLoading$ | async) || editDeviceForm.invalid || !editDeviceForm.dirty\\\">\\n Update\\n </button>\\n </div>\\n</form>\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenEditDeviceDialog();\\n\\nfunction openEditDeviceDialog() {\\n customDialog.customDialog(htmlTemplate, EditDeviceDialogController).subscribe();\\n}\\n\\nfunction EditDeviceDialogController(instance) {\\n let vm = instance;\\n \\n vm.device = null;\\n vm.attributes = {};\\n \\n vm.editDeviceFormGroup = vm.fb.group({\\n deviceName: ['', [vm.validators.required]],\\n deviceType: ['', [vm.validators.required]],\\n deviceLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.editDeviceFormGroup.markAsPristine();\\n vm.device.name = vm.editDeviceFormGroup.get('deviceName').value,\\n vm.device.type = vm.editDeviceFormGroup.get('deviceType').value,\\n vm.device.label = vm.editDeviceFormGroup.get('deviceLabel').value\\n deviceService.saveDevice(vm.device).subscribe(\\n function () {\\n saveAttributes().subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n getEntityInfo();\\n \\n function getEntityInfo() {\\n deviceService.getDevice(entityId.id).subscribe(\\n function (device) {\\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE',\\n ['latitude', 'longitude']).subscribe(\\n function (attributes) {\\n for (let i = 0; i < attributes.length; i++) {\\n vm.attributes[attributes[i].key] = attributes[i].value; \\n }\\n vm.device = device;\\n vm.editDeviceFormGroup.patchValue(\\n {\\n deviceName: vm.device.name,\\n deviceType: vm.device.type,\\n deviceLabel: vm.device.label,\\n attributes: {\\n latitude: vm.attributes.latitude,\\n longitude: vm.attributes.longitude\\n }\\n }, {emitEvent: false}\\n );\\n } \\n );\\n }\\n ); \\n }\\n \\n function saveAttributes() {\\n let attributes = vm.editDeviceFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, 'SERVER_SCOPE', attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"93931e52-5d7c-903e-67aa-b9435df44ff4\"},{\"name\":\"Delete device\",\"icon\":\"delete\",\"type\":\"custom\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\n\\nopenDeleteDeviceDialog();\\n\\nfunction openDeleteDeviceDialog() {\\n let title = \\\"Are you sure you want to delete the device \\\" + entityName + \\\"?\\\";\\n let content = \\\"Be careful, after the confirmation, the device and all related data will become unrecoverable!\\\";\\n dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe(\\n function (result) {\\n if (result) {\\n deleteDevice();\\n }\\n }\\n );\\n}\\n\\nfunction deleteDevice() {\\n deviceService.deleteDevice(entityId.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n }\\n );\\n}\\n\",\"id\":\"ec2708f6-9ff0-186b-e4fc-7635ebfa3074\"}]}}" |
25 | 25 | } |
26 | 26 | }, |
... | ... | @@ -37,8 +37,8 @@ |
37 | 37 | "templateHtml": "<tb-entities-table-widget \n [ctx]=\"ctx\">\n</tb-entities-table-widget>", |
38 | 38 | "templateCss": "", |
39 | 39 | "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n hasDataPageLink: true,\n warnOnPageDataOverflow: false,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true,\n hasShowCondition: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n },\n 'rowDoubleClick': {\n name: 'widget-action.row-double-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", |
40 | - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"reserveSpaceForHiddenAction\": {\n \"title\": \"Hidden cell button actions display mode\",\n \"type\": \"string\",\n \"default\": \"true\"\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityLabel\": {\n \"title\": \"Display entity label column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"entityLabelColumnTitle\": {\n \"title\": \"Entity label column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n {\n \"key\": \"reserveSpaceForHiddenAction\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"true\",\n \"label\": \"Show empty space instead of hidden cell button action\"\n },\n {\n \"value\": \"false\",\n \"label\": \"Don't reserve space for hidden action buttons\"\n }\n ]\n },\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityLabel\",\n \"entityLabelColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}", | |
41 | - "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value, entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"defaultColumnVisibility\": {\n \"title\": \"Default column visibility\",\n \"type\": \"string\",\n \"default\": \"visible\"\n },\n \"columnSelectionToDisplay\": {\n \"title\": \"Column selection in 'Columns to Display'\",\n \"type\": \"string\",\n \"default\": \"enabled\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useCellStyleFunction === true\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useCellContentFunction === true\"\n },\n {\n \"key\": \"defaultColumnVisibility\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"visible\",\n \"label\": \"Visible\"\n },\n {\n \"value\": \"hidden\",\n \"label\": \"Hidden\"\n } \n ]\n },\n {\n \"key\": \"columnSelectionToDisplay\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"enabled\",\n \"label\": \"Enabled\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n } \n ]\n }\n ]\n}", | |
40 | + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"reserveSpaceForHiddenAction\": {\n \"title\": \"Hidden cell button actions display mode\",\n \"type\": \"string\",\n \"default\": \"true\"\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityLabel\": {\n \"title\": \"Display entity label column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"entityLabelColumnTitle\": {\n \"title\": \"Entity label column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n {\n \"key\": \"reserveSpaceForHiddenAction\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"true\",\n \"label\": \"Show empty space instead of hidden cell button action\"\n },\n {\n \"value\": \"false\",\n \"label\": \"Don't reserve space for hidden action buttons\"\n }\n ]\n },\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityLabel\",\n \"entityLabelColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/entity/row_style_fn\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}", | |
41 | + "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value, entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"defaultColumnVisibility\": {\n \"title\": \"Default column visibility\",\n \"type\": \"string\",\n \"default\": \"visible\"\n },\n \"columnSelectionToDisplay\": {\n \"title\": \"Column selection in 'Columns to Display'\",\n \"type\": \"string\",\n \"default\": \"enabled\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/entity/cell_style_fn\",\n \"condition\": \"model.useCellStyleFunction === true\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/entity/cell_content_fn\",\n \"condition\": \"model.useCellContentFunction === true\"\n },\n {\n \"key\": \"defaultColumnVisibility\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"visible\",\n \"label\": \"Visible\"\n },\n {\n \"value\": \"hidden\",\n \"label\": \"Hidden\"\n } \n ]\n },\n {\n \"key\": \"columnSelectionToDisplay\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"enabled\",\n \"label\": \"Enabled\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n } \n ]\n }\n ]\n}", | |
42 | 42 | "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSearch\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true,\"entitiesTitle\":\"Asset admin table\",\"enableSelectColumnDisplay\":true},\"title\":\"Asset admin table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{\"headerButton\":[{\"name\":\"Add asset\",\"icon\":\"add\",\"type\":\"customPretty\",\"customHtml\":\"<form #addAssetForm=\\\"ngForm\\\" [formGroup]=\\\"addAssetFormGroup\\\"\\n (ngSubmit)=\\\"save()\\\" style=\\\"width: 480px;\\\">\\n <mat-toolbar fxLayout=\\\"row\\\" color=\\\"primary\\\">\\n <h2>Add asset</h2>\\n <span fxFlex></span>\\n <button mat-button mat-icon-button\\n (click)=\\\"cancel()\\\"\\n type=\\\"button\\\">\\n <mat-icon class=\\\"material-icons\\\">close</mat-icon>\\n </button>\\n </mat-toolbar>\\n <mat-progress-bar color=\\\"warn\\\" mode=\\\"indeterminate\\\" *ngIf=\\\"isLoading$ | async\\\">\\n </mat-progress-bar>\\n <div style=\\\"height: 4px;\\\" *ngIf=\\\"!(isLoading$ | async)\\\"></div>\\n <div mat-dialog-content>\\n <div class=\\\"mat-padding\\\" fxLayout=\\\"column\\\">\\n <mat-form-field class=\\\"mat-block\\\">\\n <mat-label>Asset name</mat-label>\\n <input matInput formControlName=\\\"assetName\\\" required>\\n <mat-error *ngIf=\\\"addAssetFormGroup.get('assetName').hasError('required')\\\">\\n Asset name is required.\\n </mat-error>\\n </mat-form-field>\\n <div fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <tb-entity-subtype-autocomplete\\n fxFlex=\\\"50\\\"\\n formControlName=\\\"assetType\\\"\\n [required]=\\\"true\\\"\\n [entityType]=\\\"'ASSET'\\\"\\n ></tb-entity-subtype-autocomplete>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Label</mat-label>\\n <input matInput formControlName=\\\"assetLabel\\\">\\n </mat-form-field>\\n </div>\\n <div formGroupName=\\\"attributes\\\" fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Latitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"latitude\\\">\\n </mat-form-field>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Longitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"longitude\\\">\\n </mat-form-field>\\n </div>\\n </div> \\n </div>\\n <div mat-dialog-actions fxLayout=\\\"row\\\">\\n <span fxFlex></span>\\n <button mat-button color=\\\"primary\\\"\\n type=\\\"button\\\"\\n [disabled]=\\\"(isLoading$ | async)\\\"\\n (click)=\\\"cancel()\\\" cdkFocusInitial>\\n Cancel\\n </button>\\n <button mat-button mat-raised-button color=\\\"primary\\\"\\n style=\\\"margin-right: 20px;\\\"\\n type=\\\"submit\\\"\\n [disabled]=\\\"(isLoading$ | async) || addAssetForm.invalid || !addAssetForm.dirty\\\">\\n Create\\n </button>\\n </div>\\n</form>\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenAddAssetDialog();\\n\\nfunction openAddAssetDialog() {\\n customDialog.customDialog(htmlTemplate, AddAssetDialogController).subscribe();\\n}\\n\\nfunction AddAssetDialogController(instance) {\\n let vm = instance;\\n \\n vm.addAssetFormGroup = vm.fb.group({\\n assetName: ['', [vm.validators.required]],\\n assetType: ['', [vm.validators.required]],\\n assetLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.addAssetFormGroup.markAsPristine();\\n let asset = {\\n name: vm.addAssetFormGroup.get('assetName').value,\\n type: vm.addAssetFormGroup.get('assetType').value,\\n label: vm.addAssetFormGroup.get('assetLabel').value\\n };\\n assetService.saveAsset(asset).subscribe(\\n function (asset) {\\n saveAttributes(asset.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n function saveAttributes(entityId) {\\n let attributes = vm.addAssetFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"70837a9d-c3de-a9a7-03c5-dccd14998758\"}],\"actionCellButton\":[{\"name\":\"Edit asset\",\"icon\":\"edit\",\"type\":\"customPretty\",\"customHtml\":\"<form #editAssetForm=\\\"ngForm\\\" [formGroup]=\\\"editAssetFormGroup\\\"\\n (ngSubmit)=\\\"save()\\\" style=\\\"width: 480px;\\\">\\n <mat-toolbar fxLayout=\\\"row\\\" color=\\\"primary\\\">\\n <h2>Edit asset</h2>\\n <span fxFlex></span>\\n <button mat-button mat-icon-button\\n (click)=\\\"cancel()\\\"\\n type=\\\"button\\\">\\n <mat-icon class=\\\"material-icons\\\">close</mat-icon>\\n </button>\\n </mat-toolbar>\\n <mat-progress-bar color=\\\"warn\\\" mode=\\\"indeterminate\\\" *ngIf=\\\"isLoading$ | async\\\">\\n </mat-progress-bar>\\n <div style=\\\"height: 4px;\\\" *ngIf=\\\"!(isLoading$ | async)\\\"></div>\\n <div mat-dialog-content>\\n <div class=\\\"mat-padding\\\" fxLayout=\\\"column\\\">\\n <mat-form-field class=\\\"mat-block\\\">\\n <mat-label>Asset name</mat-label>\\n <input matInput formControlName=\\\"assetName\\\" required>\\n <mat-error *ngIf=\\\"editAssetFormGroup.get('assetName').hasError('required')\\\">\\n Asset name is required.\\n </mat-error>\\n </mat-form-field>\\n <div fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <tb-entity-subtype-autocomplete\\n fxFlex=\\\"50\\\"\\n formControlName=\\\"assetType\\\"\\n [required]=\\\"true\\\"\\n [entityType]=\\\"'ASSET'\\\"\\n ></tb-entity-subtype-autocomplete>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Label</mat-label>\\n <input matInput formControlName=\\\"assetLabel\\\">\\n </mat-form-field>\\n </div>\\n <div formGroupName=\\\"attributes\\\" fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Latitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"latitude\\\">\\n </mat-form-field>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Longitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"longitude\\\">\\n </mat-form-field>\\n </div>\\n </div> \\n </div>\\n <div mat-dialog-actions fxLayout=\\\"row\\\">\\n <span fxFlex></span>\\n <button mat-button color=\\\"primary\\\"\\n type=\\\"button\\\"\\n [disabled]=\\\"(isLoading$ | async)\\\"\\n (click)=\\\"cancel()\\\" cdkFocusInitial>\\n Cancel\\n </button>\\n <button mat-button mat-raised-button color=\\\"primary\\\"\\n type=\\\"submit\\\"\\n style=\\\"margin-right: 20px;\\\"\\n [disabled]=\\\"(isLoading$ | async) || editAssetForm.invalid || !editAssetForm.dirty\\\">\\n Update\\n </button>\\n </div>\\n</form>\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenEditAssetDialog();\\n\\nfunction openEditAssetDialog() {\\n customDialog.customDialog(htmlTemplate, EditAssetDialogController).subscribe();\\n}\\n\\nfunction EditAssetDialogController(instance) {\\n let vm = instance;\\n \\n vm.asset = null;\\n vm.attributes = {};\\n \\n vm.editAssetFormGroup = vm.fb.group({\\n assetName: ['', [vm.validators.required]],\\n assetType: ['', [vm.validators.required]],\\n assetLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.editAssetFormGroup.markAsPristine();\\n vm.asset.name = vm.editAssetFormGroup.get('assetName').value,\\n vm.asset.type = vm.editAssetFormGroup.get('assetType').value,\\n vm.asset.label = vm.editAssetFormGroup.get('assetLabel').value\\n assetService.saveAsset(vm.asset).subscribe(\\n function () {\\n saveAttributes().subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n getEntityInfo();\\n \\n function getEntityInfo() {\\n assetService.getAsset(entityId.id).subscribe(\\n function (asset) {\\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE',\\n ['latitude', 'longitude']).subscribe(\\n function (attributes) {\\n for (let i = 0; i < attributes.length; i++) {\\n vm.attributes[attributes[i].key] = attributes[i].value; \\n }\\n vm.asset = asset;\\n vm.editAssetFormGroup.patchValue(\\n {\\n assetName: vm.asset.name,\\n assetType: vm.asset.type,\\n assetLabel: vm.asset.label,\\n attributes: {\\n latitude: vm.attributes.latitude,\\n longitude: vm.attributes.longitude\\n }\\n }, {emitEvent: false}\\n );\\n } \\n );\\n }\\n ); \\n }\\n \\n function saveAttributes() {\\n let attributes = vm.editAssetFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, 'SERVER_SCOPE', attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"93931e52-5d7c-903e-67aa-b9435df44ff4\"},{\"name\":\"Delete asset\",\"icon\":\"delete\",\"type\":\"custom\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));\\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\\n\\nopenDeleteAssetDialog();\\n\\nfunction openDeleteAssetDialog() {\\n let title = \\\"Are you sure you want to delete the asset \\\" + entityName + \\\"?\\\";\\n let content = \\\"Be careful, after the confirmation, the asset and all related data will become unrecoverable!\\\";\\n dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe(\\n function (result) {\\n if (result) {\\n deleteAsset();\\n }\\n }\\n );\\n}\\n\\nfunction deleteAsset() {\\n assetService.deleteAsset(entityId.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n }\\n );\\n}\\n\",\"id\":\"ec2708f6-9ff0-186b-e4fc-7635ebfa3074\"}]}}" |
43 | 43 | } |
44 | 44 | } | ... | ... |
... | ... | @@ -19,7 +19,7 @@ |
19 | 19 | "templateHtml": "<fieldset class=\"gpio-panel\" style=\"height: 100%;\">\n <section class=\"gpio-row\" fxLayout=\"row\" *ngFor=\"let row of rows\" \n [ngStyle]=\"{'height': prefferedRowHeight+'px'}\">\n <section fxFlex fxLayout=\"row\" *ngFor=\"let cell of row; let $index = index\">\n <section fxLayout=\"row\" fxFlex *ngIf=\"cell\" fxLayoutAlign=\"{{$index===0 ? 'end center' : 'start center'}}\">\n <span class=\"gpio-left-label\" [fxShow]=\"$index===0\">{{ cell.label }}</span>\n <section fxLayout=\"row\" class=\"switch-panel\" fxLayoutAlign=\"start center\" [ngClass]=\"$index===0 ? 'col-0' : 'col-1'\"\n [ngStyle]=\"{'height': prefferedRowHeight+'px', 'backgroundColor': switchPanelBackgroundColor }\">\n <span class=\"pin\" [fxShow]=\"$index===0\">{{cell.pin}}</span>\n <span fxFlex [fxShow]=\"$index===1\"></span>\n <mat-slide-toggle\n [disabled]=\"!rpcEnabled || executingRpcRequest\"\n [checked]=\"cell.enabled\" \n (change)=\"gpioToggleChange($event, cell)\" \n (click)=\"gpioClick($event, cell)\">\n </mat-slide-toggle>\n <span fxFlex [fxShow]=\"$index===0\"></span>\n <span class=\"pin\" [fxShow]=\"$index===1\">{{cell.pin}}</span>\n </section>\n <span class=\"gpio-right-label\" [fxShow]=\"$index===1\">{{ cell.label }}</span>\n </section>\n <section fxLayout=\"row\" fxFlex *ngIf=\"!cell\">\n <span fxFlex [fxShow]=\"$index===0\"></span>\n <span class=\"switch-panel\"\n [ngStyle]=\"{'height': prefferedRowHeight+'px', 'backgroundColor': switchPanelBackgroundColor }\"></span>\n <span fxFlex [fxShow]=\"$index===1\"></span>\n </section>\n </section>\n </section> \n <span class=\"error\" style=\"position: absolute; bottom: 5px;\" [fxShow]=\"rpcErrorText\">{{rpcErrorText}}</span>\n <mat-progress-bar [fxShow]=\"executingRpcRequest\" style=\"position: absolute; bottom: 0;\" mode=\"indeterminate\"></mat-progress-bar>\n</fieldset>", |
20 | 20 | "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.gpio-panel section[fxflex] {\n min-width: 0px;\n}\n\n\n.switch-panel {\n margin: 0;\n height: 32px;\n width: 66px;\n min-width: 66px;\n}\n\n.switch-panel mat-slide-toggle {\n margin: 0;\n width: 36px;\n min-width: 36px;\n}\n\n.switch-panel.col-0 mat-slide-toggle {\n margin-left: 8px;\n margin-right: 4px;\n}\n\n.switch-panel.col-1 mat-slide-toggle {\n margin-left: 4px;\n margin-right: 8px;\n}\n\n.gpio-row {\n height: 32px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.switch-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.switch-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}", |
21 | 21 | "controllerScript": "var namespace;\nvar cssParser = new cssjs();\n\nself.onInit = function() {\n var utils = self.ctx.$injector.get(self.ctx.servicesMap.get('utils'));\n namespace = 'gpio-control-' + utils.guid();\n cssParser.testMode = false;\n cssParser.cssPreviewNamespace = namespace;\n self.ctx.$container.addClass(namespace);\n self.ctx.ngZone.run(function() {\n init(); \n });\n}\n\nfunction init() {\n \n var i, gpio;\n var scope = self.ctx.$scope;\n var settings = self.ctx.settings;\n scope.gpioList = [];\n for (var g = 0; g < settings.gpioList.length; g++) {\n gpio = settings.gpioList[g];\n scope.gpioList.push(\n {\n row: gpio.row,\n col: gpio.col,\n pin: gpio.pin,\n label: gpio.label,\n enabled: false\n }\n );\n }\n\n scope.requestTimeout = settings.requestTimeout || 1000;\n\n scope.switchPanelBackgroundColor = settings.switchPanelBackgroundColor || tinycolor('green').lighten(2).toRgbString();\n\n scope.gpioStatusRequest = {\n method: \"getGpioStatus\",\n paramsBody: \"{}\"\n };\n \n if (settings.gpioStatusRequest) {\n scope.gpioStatusRequest.method = settings.gpioStatusRequest.method || scope.gpioStatusRequest.method;\n scope.gpioStatusRequest.paramsBody = settings.gpioStatusRequest.paramsBody || scope.gpioStatusRequest.paramsBody;\n }\n \n scope.gpioStatusChangeRequest = {\n method: \"setGpioStatus\",\n paramsBody: \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n };\n \n if (settings.gpioStatusChangeRequest) {\n scope.gpioStatusChangeRequest.method = settings.gpioStatusChangeRequest.method || scope.gpioStatusChangeRequest.method;\n scope.gpioStatusChangeRequest.paramsBody = settings.gpioStatusChangeRequest.paramsBody || scope.gpioStatusChangeRequest.paramsBody;\n }\n \n scope.parseGpioStatusFunction = \"return body[pin] === true;\";\n \n if (settings.parseGpioStatusFunction && settings.parseGpioStatusFunction.length > 0) {\n scope.parseGpioStatusFunction = settings.parseGpioStatusFunction;\n }\n \n scope.parseGpioStatusFunction = new Function(\"body, pin\", scope.parseGpioStatusFunction);\n \n function requestGpioStatus() {\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusRequest.method, \n scope.gpioStatusRequest.paramsBody, \n scope.requestTimeout)\n .subscribe(\n function success(responseBody) {\n for (var g = 0; g < scope.gpioList.length; g++) {\n var gpio = scope.gpioList[g];\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled; \n self.ctx.detectChanges();\n }\n }\n );\n }\n \n function changeGpioStatus(gpio) {\n var pin = gpio.pin + '';\n var enabled = !gpio.enabled;\n enabled = enabled === true ? 'true' : 'false';\n var paramsBody = scope.gpioStatusChangeRequest.paramsBody;\n var requestBody = JSON.parse(paramsBody.replace(\"\\\"{$pin}\\\"\", pin).replace(\"\\\"{$enabled}\\\"\", enabled));\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusChangeRequest.method, \n requestBody, scope.requestTimeout)\n .subscribe(\n function success(responseBody) {\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled;\n self.ctx.detectChanges();\n }\n );\n }\n \n scope.gpioCells = {};\n var rowCount = 0;\n for (i = 0; i < scope.gpioList.length; i++) {\n gpio = scope.gpioList[i];\n scope.gpioCells[gpio.row+'_'+gpio.col] = gpio;\n rowCount = Math.max(rowCount, gpio.row+1);\n }\n \n scope.prefferedRowHeight = 32;\n scope.rows = [];\n for (i = 0; i < rowCount; i++) {\n var row = [];\n for (var c =0; c<2;c++) {\n if (scope.gpioCells[i+'_'+c]) {\n row[c] = scope.gpioCells[i+'_'+c];\n } else {\n row[c] = null;\n }\n }\n scope.rows.push(row);\n }\n\n scope.gpioClick = function($event, gpio) {\n if (scope.rpcEnabled && !scope.executingRpcRequest) {\n changeGpioStatus(gpio);\n }\n };\n \n scope.gpioToggleChange = function($event, gpio) {\n gpio.enabled = !$event.checked;\n $event.source.toggle();\n self.ctx.detectChanges();\n }\n \n if (scope.rpcEnabled) {\n requestGpioStatus(); \n }\n \n self.onResize();\n}\n\nself.onResize = function() {\n var scope = self.ctx.$scope;\n var rowCount = scope.rows.length;\n var prefferedRowHeight = (self.ctx.height - 35)/rowCount;\n prefferedRowHeight = Math.min(32, prefferedRowHeight);\n prefferedRowHeight = Math.max(12, prefferedRowHeight);\n scope.prefferedRowHeight = prefferedRowHeight;\n var ratio = prefferedRowHeight/32;\n \n var css = '.mat-slide-toggle .mat-slide-toggle-bar {\\n' +\n ' height: ' + 14*ratio+'px;\\n'+\n ' width: ' + 36*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-thumb-container {\\n' +\n ' height: ' + 20*ratio+'px;\\n'+\n ' width: ' + 20*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-thumb {\\n' +\n ' height: ' + 20*ratio+'px;\\n'+\n ' width: ' + 20*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-ripple {\\n' +\n ' height: ' + 40*ratio+'px;\\n'+\n ' width: ' + 40*ratio+'px;\\n'+\n ' top: calc(50% - '+20*ratio+'px);\\n'+\n ' left: calc(50% - '+20*ratio+'px);\\n'+\n '}\\n';\n css += '.gpio-left-label, .gpio-right-label {\\n' +\n ' font-size: ' + 16*ratio+'px;\\n'+\n '}\\n';\n var pinsFontSize = Math.max(9, 12*ratio);\n css += '.pin {\\n' +\n ' font-size: ' + pinsFontSize+'px;\\n'+\n '}\\n';\n\n cssParser.createStyleElement(namespace, css);\n \n self.ctx.detectChanges();\n}\n\nself.onDestroy = function() {\n}\n", |
22 | - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"gpioList\": {\n \"title\": \"Gpio switches\",\n \"type\": \"array\",\n \"minItems\" : 1,\n \"items\": {\n \"title\": \"Gpio switch\",\n \"type\": \"object\",\n \"properties\": {\n \"pin\": {\n \"title\": \"Pin\",\n \"type\": \"number\"\n },\n \"label\": {\n \"title\": \"Label\",\n \"type\": \"string\"\n },\n \"row\": {\n \"title\": \"Row\",\n \"type\": \"number\"\n },\n \"col\": {\n \"title\": \"Column\",\n \"type\": \"number\"\n }\n },\n \"required\": [\"pin\", \"label\", \"row\", \"col\"]\n }\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"switchPanelBackgroundColor\": {\n \"title\": \"Switches panel background color\",\n \"type\": \"string\",\n \"default\": \"#008a00\"\n },\n \"gpioStatusRequest\": {\n \"title\": \"GPIO status request\",\n \"type\": \"object\",\n \"properties\": {\n \"method\": {\n \"title\": \"Method name\",\n \"type\": \"string\",\n \"default\": \"getGpioStatus\"\n },\n \"paramsBody\": {\n \"title\": \"Method body\",\n \"type\": \"string\",\n \"default\": \"{}\"\n }\n },\n \"required\": [\"method\", \"paramsBody\"]\n },\n \"gpioStatusChangeRequest\": {\n \"title\": \"GPIO status change request\",\n \"type\": \"object\",\n \"properties\": {\n \"method\": {\n \"title\": \"Method name\",\n \"type\": \"string\",\n \"default\": \"setGpioStatus\"\n },\n \"paramsBody\": {\n \"title\": \"Method body\",\n \"type\": \"string\",\n \"default\": \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n }\n },\n \"required\": [\"method\", \"paramsBody\"]\n },\n \"parseGpioStatusFunction\": {\n \"title\": \"Parse gpio status function\",\n \"type\": \"string\",\n \"default\": \"return body[pin] === true;\"\n } \n },\n \"required\": [\"gpioList\", \n \"requestTimeout\",\n \"switchPanelBackgroundColor\",\n \"gpioStatusRequest\",\n \"gpioStatusChangeRequest\",\n \"parseGpioStatusFunction\"]\n },\n \"form\": [\n \"gpioList\",\n \"requestTimeout\",\n {\n \"key\": \"switchPanelBackgroundColor\",\n \"type\": \"color\"\n },\n {\n \"key\": \"gpioStatusRequest\",\n \"items\": [\n \"gpioStatusRequest.method\",\n {\n \"key\": \"gpioStatusRequest.paramsBody\",\n \"type\": \"json\"\n }\n ]\n },\n {\n \"key\": \"gpioStatusChangeRequest\",\n \"items\": [\n \"gpioStatusChangeRequest.method\",\n {\n \"key\": \"gpioStatusChangeRequest.paramsBody\",\n \"type\": \"json\"\n }\n ]\n },\n {\n \"key\": \"parseGpioStatusFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", | |
22 | + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"gpioList\": {\n \"title\": \"Gpio switches\",\n \"type\": \"array\",\n \"minItems\" : 1,\n \"items\": {\n \"title\": \"Gpio switch\",\n \"type\": \"object\",\n \"properties\": {\n \"pin\": {\n \"title\": \"Pin\",\n \"type\": \"number\"\n },\n \"label\": {\n \"title\": \"Label\",\n \"type\": \"string\"\n },\n \"row\": {\n \"title\": \"Row\",\n \"type\": \"number\"\n },\n \"col\": {\n \"title\": \"Column\",\n \"type\": \"number\"\n }\n },\n \"required\": [\"pin\", \"label\", \"row\", \"col\"]\n }\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"switchPanelBackgroundColor\": {\n \"title\": \"Switches panel background color\",\n \"type\": \"string\",\n \"default\": \"#008a00\"\n },\n \"gpioStatusRequest\": {\n \"title\": \"GPIO status request\",\n \"type\": \"object\",\n \"properties\": {\n \"method\": {\n \"title\": \"Method name\",\n \"type\": \"string\",\n \"default\": \"getGpioStatus\"\n },\n \"paramsBody\": {\n \"title\": \"Method body\",\n \"type\": \"string\",\n \"default\": \"{}\"\n }\n },\n \"required\": [\"method\", \"paramsBody\"]\n },\n \"gpioStatusChangeRequest\": {\n \"title\": \"GPIO status change request\",\n \"type\": \"object\",\n \"properties\": {\n \"method\": {\n \"title\": \"Method name\",\n \"type\": \"string\",\n \"default\": \"setGpioStatus\"\n },\n \"paramsBody\": {\n \"title\": \"Method body\",\n \"type\": \"string\",\n \"default\": \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n }\n },\n \"required\": [\"method\", \"paramsBody\"]\n },\n \"parseGpioStatusFunction\": {\n \"title\": \"Parse gpio status function\",\n \"type\": \"string\",\n \"default\": \"return body[pin] === true;\"\n } \n },\n \"required\": [\"gpioList\", \n \"requestTimeout\",\n \"switchPanelBackgroundColor\",\n \"gpioStatusRequest\",\n \"gpioStatusChangeRequest\",\n \"parseGpioStatusFunction\"]\n },\n \"form\": [\n \"gpioList\",\n \"requestTimeout\",\n {\n \"key\": \"switchPanelBackgroundColor\",\n \"type\": \"color\"\n },\n {\n \"key\": \"gpioStatusRequest\",\n \"items\": [\n \"gpioStatusRequest.method\",\n {\n \"key\": \"gpioStatusRequest.paramsBody\",\n \"type\": \"json\"\n }\n ]\n },\n {\n \"key\": \"gpioStatusChangeRequest\",\n \"items\": [\n \"gpioStatusChangeRequest.method\",\n {\n \"key\": \"gpioStatusChangeRequest.paramsBody\",\n \"type\": \"json\"\n }\n ]\n },\n {\n \"key\": \"parseGpioStatusFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/rpc/parse_gpio_status_fn\"\n }\n ]\n}", | |
23 | 23 | "dataKeySettingsSchema": "{}\n", |
24 | 24 | "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"Basic GPIO Control\"}" |
25 | 25 | } |
... | ... | @@ -73,7 +73,7 @@ |
73 | 73 | "templateHtml": "<fieldset class=\"gpio-panel\" style=\"height: 100%;\">\n <section class=\"gpio-row\" fxLayout=\"row\" *ngFor=\"let row of rows\" \n [ngStyle]=\"{'height': prefferedRowHeight+'px'}\">\n <section fxFlex fxLayout=\"row\" *ngFor=\"let cell of row; let $index = index\">\n <section fxLayout=\"row\" fxFlex *ngIf=\"cell\" fxLayoutAlign=\"{{$index===0 ? 'end center' : 'start center'}}\">\n <span class=\"gpio-left-label\" [fxShow]=\"$index===0\">{{ cell.label }}</span>\n <section fxLayout=\"row\" class=\"switch-panel\" fxLayoutAlign=\"start center\" [ngClass]=\"$index===0 ? 'col-0' : 'col-1'\"\n [ngStyle]=\"{'height': prefferedRowHeight+'px', 'backgroundColor': switchPanelBackgroundColor }\">\n <span class=\"pin\" [fxShow]=\"$index===0\">{{cell.pin}}</span>\n <span fxFlex [fxShow]=\"$index===1\"></span>\n <mat-slide-toggle\n [disabled]=\"!rpcEnabled || executingRpcRequest\"\n [checked]=\"cell.enabled\" \n (change)=\"gpioToggleChange($event, cell)\" \n (click)=\"gpioClick($event, cell)\">\n </mat-slide-toggle>\n <span fxFlex [fxShow]=\"$index===0\"></span>\n <span class=\"pin\" [fxShow]=\"$index===1\">{{cell.pin}}</span>\n </section>\n <span class=\"gpio-right-label\" [fxShow]=\"$index===1\">{{ cell.label }}</span>\n </section>\n <section fxLayout=\"row\" fxFlex *ngIf=\"!cell\">\n <span fxFlex [fxShow]=\"$index===0\"></span>\n <span class=\"switch-panel\"\n [ngStyle]=\"{'height': prefferedRowHeight+'px', 'backgroundColor': switchPanelBackgroundColor }\"></span>\n <span fxFlex [fxShow]=\"$index===1\"></span>\n </section>\n </section>\n </section> \n <span class=\"error\" style=\"position: absolute; bottom: 5px;\" [fxShow]=\"rpcErrorText\">{{rpcErrorText}}</span>\n <mat-progress-bar [fxShow]=\"executingRpcRequest\" style=\"position: absolute; bottom: 0;\" mode=\"indeterminate\"></mat-progress-bar>\n</fieldset>", |
74 | 74 | "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.gpio-panel section[fxflex] {\n min-width: 0px;\n}\n\n.switch-panel {\n margin: 0;\n height: 32px;\n width: 66px;\n min-width: 66px;\n}\n\n.switch-panel mat-slide-toggle {\n margin: 0;\n width: 36px;\n min-width: 36px;\n}\n\n.switch-panel.col-0 mat-slide-toggle {\n margin-left: 8px;\n margin-right: 4px;\n}\n\n.switch-panel.col-1 mat-slide-toggle {\n margin-left: 4px;\n margin-right: 8px;\n}\n\n.gpio-row {\n height: 32px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.switch-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.switch-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}", |
75 | 75 | "controllerScript": "var namespace;\nvar cssParser = new cssjs();\n\nself.onInit = function() {\n var utils = self.ctx.$injector.get(self.ctx.servicesMap.get('utils'));\n namespace = 'gpio-control-' + utils.guid();\n cssParser.testMode = false;\n cssParser.cssPreviewNamespace = namespace;\n self.ctx.$container.addClass(namespace);\n self.ctx.ngZone.run(function() {\n init(); \n });\n}\n\nfunction init() {\n \n var i, gpio;\n var scope = self.ctx.$scope;\n var settings = self.ctx.settings;\n scope.gpioList = [];\n for (var g = 0; g < settings.gpioList.length; g++) {\n gpio = settings.gpioList[g];\n scope.gpioList.push(\n {\n row: gpio.row,\n col: gpio.col,\n pin: gpio.pin,\n label: gpio.label,\n enabled: false\n }\n );\n }\n\n scope.requestTimeout = settings.requestTimeout || 1000;\n\n scope.switchPanelBackgroundColor = settings.switchPanelBackgroundColor || tinycolor('green').lighten(2).toRgbString();\n\n scope.gpioStatusRequest = {\n method: \"getGpioStatus\",\n paramsBody: \"{}\"\n };\n \n if (settings.gpioStatusRequest) {\n scope.gpioStatusRequest.method = settings.gpioStatusRequest.method || scope.gpioStatusRequest.method;\n scope.gpioStatusRequest.paramsBody = settings.gpioStatusRequest.paramsBody || scope.gpioStatusRequest.paramsBody;\n }\n \n scope.gpioStatusChangeRequest = {\n method: \"setGpioStatus\",\n paramsBody: \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n };\n \n if (settings.gpioStatusChangeRequest) {\n scope.gpioStatusChangeRequest.method = settings.gpioStatusChangeRequest.method || scope.gpioStatusChangeRequest.method;\n scope.gpioStatusChangeRequest.paramsBody = settings.gpioStatusChangeRequest.paramsBody || scope.gpioStatusChangeRequest.paramsBody;\n }\n \n scope.parseGpioStatusFunction = \"return body[pin] === true;\";\n \n if (settings.parseGpioStatusFunction && settings.parseGpioStatusFunction.length > 0) {\n scope.parseGpioStatusFunction = settings.parseGpioStatusFunction;\n }\n \n scope.parseGpioStatusFunction = new Function(\"body, pin\", scope.parseGpioStatusFunction);\n \n function requestGpioStatus() {\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusRequest.method, \n scope.gpioStatusRequest.paramsBody, \n scope.requestTimeout)\n .subscribe(\n function success(responseBody) {\n for (var g = 0; g < scope.gpioList.length; g++) {\n var gpio = scope.gpioList[g];\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled; \n self.ctx.detectChanges();\n }\n }\n );\n }\n \n function changeGpioStatus(gpio) {\n var pin = gpio.pin + '';\n var enabled = !gpio.enabled;\n enabled = enabled === true ? 'true' : 'false';\n var paramsBody = scope.gpioStatusChangeRequest.paramsBody;\n var requestBody = JSON.parse(paramsBody.replace(\"\\\"{$pin}\\\"\", pin).replace(\"\\\"{$enabled}\\\"\", enabled));\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusChangeRequest.method, \n requestBody, scope.requestTimeout)\n .subscribe(\n function success(responseBody) {\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled;\n self.ctx.detectChanges();\n }\n );\n }\n \n scope.gpioCells = {};\n var rowCount = 0;\n for (i = 0; i < scope.gpioList.length; i++) {\n gpio = scope.gpioList[i];\n scope.gpioCells[gpio.row+'_'+gpio.col] = gpio;\n rowCount = Math.max(rowCount, gpio.row+1);\n }\n \n scope.prefferedRowHeight = 32;\n scope.rows = [];\n for (i = 0; i < rowCount; i++) {\n var row = [];\n for (var c =0; c<2;c++) {\n if (scope.gpioCells[i+'_'+c]) {\n row[c] = scope.gpioCells[i+'_'+c];\n } else {\n row[c] = null;\n }\n }\n scope.rows.push(row);\n }\n\n scope.gpioClick = function($event, gpio) {\n if (scope.rpcEnabled && !scope.executingRpcRequest) {\n changeGpioStatus(gpio);\n }\n };\n \n scope.gpioToggleChange = function($event, gpio) {\n gpio.enabled = !$event.checked;\n $event.source.toggle();\n self.ctx.detectChanges();\n }\n \n if (scope.rpcEnabled) {\n requestGpioStatus(); \n }\n \n self.onResize();\n}\n\nself.onResize = function() {\n var scope = self.ctx.$scope;\n var rowCount = scope.rows.length;\n var prefferedRowHeight = (self.ctx.height - 35)/rowCount;\n prefferedRowHeight = Math.min(32, prefferedRowHeight);\n prefferedRowHeight = Math.max(12, prefferedRowHeight);\n scope.prefferedRowHeight = prefferedRowHeight;\n var ratio = prefferedRowHeight/32;\n \n var css = '.mat-slide-toggle .mat-slide-toggle-bar {\\n' +\n ' height: ' + 14*ratio+'px;\\n'+\n ' width: ' + 36*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-thumb-container {\\n' +\n ' height: ' + 20*ratio+'px;\\n'+\n ' width: ' + 20*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-thumb {\\n' +\n ' height: ' + 20*ratio+'px;\\n'+\n ' width: ' + 20*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-ripple {\\n' +\n ' height: ' + 40*ratio+'px;\\n'+\n ' width: ' + 40*ratio+'px;\\n'+\n ' top: calc(50% - '+20*ratio+'px);\\n'+\n ' left: calc(50% - '+20*ratio+'px);\\n'+\n '}\\n';\n css += '.gpio-left-label, .gpio-right-label {\\n' +\n ' font-size: ' + 16*ratio+'px;\\n'+\n '}\\n';\n var pinsFontSize = Math.max(9, 12*ratio);\n css += '.pin {\\n' +\n ' font-size: ' + pinsFontSize+'px;\\n'+\n '}\\n';\n\n cssParser.createStyleElement(namespace, css);\n \n self.ctx.detectChanges();\n}\n\nself.onDestroy = function() {\n}\n", |
76 | - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"gpioList\": {\n \"title\": \"Gpio switches\",\n \"type\": \"array\",\n \"minItems\" : 1,\n \"items\": {\n \"title\": \"Gpio switch\",\n \"type\": \"object\",\n \"properties\": {\n \"pin\": {\n \"title\": \"Pin\",\n \"type\": \"number\"\n },\n \"label\": {\n \"title\": \"Label\",\n \"type\": \"string\"\n },\n \"row\": {\n \"title\": \"Row\",\n \"type\": \"number\"\n },\n \"col\": {\n \"title\": \"Column\",\n \"type\": \"number\"\n }\n },\n \"required\": [\"pin\", \"label\", \"row\", \"col\"]\n }\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"switchPanelBackgroundColor\": {\n \"title\": \"Switches panel background color\",\n \"type\": \"string\",\n \"default\": \"#008a00\"\n },\n \"gpioStatusRequest\": {\n \"title\": \"GPIO status request\",\n \"type\": \"object\",\n \"properties\": {\n \"method\": {\n \"title\": \"Method name\",\n \"type\": \"string\",\n \"default\": \"getGpioStatus\"\n },\n \"paramsBody\": {\n \"title\": \"Method body\",\n \"type\": \"string\",\n \"default\": \"{}\"\n }\n },\n \"required\": [\"method\", \"paramsBody\"]\n },\n \"gpioStatusChangeRequest\": {\n \"title\": \"GPIO status change request\",\n \"type\": \"object\",\n \"properties\": {\n \"method\": {\n \"title\": \"Method name\",\n \"type\": \"string\",\n \"default\": \"setGpioStatus\"\n },\n \"paramsBody\": {\n \"title\": \"Method body\",\n \"type\": \"string\",\n \"default\": \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n }\n },\n \"required\": [\"method\", \"paramsBody\"]\n },\n \"parseGpioStatusFunction\": {\n \"title\": \"Parse gpio status function\",\n \"type\": \"string\",\n \"default\": \"return body[pin] === true;\"\n } \n },\n \"required\": [\"gpioList\", \n \"requestTimeout\",\n \"switchPanelBackgroundColor\",\n \"gpioStatusRequest\",\n \"gpioStatusChangeRequest\",\n \"parseGpioStatusFunction\"]\n },\n \"form\": [\n \"gpioList\",\n \"requestTimeout\",\n {\n \"key\": \"switchPanelBackgroundColor\",\n \"type\": \"color\"\n },\n {\n \"key\": \"gpioStatusRequest\",\n \"items\": [\n \"gpioStatusRequest.method\",\n {\n \"key\": \"gpioStatusRequest.paramsBody\",\n \"type\": \"json\"\n }\n ]\n },\n {\n \"key\": \"gpioStatusChangeRequest\",\n \"items\": [\n \"gpioStatusChangeRequest.method\",\n {\n \"key\": \"gpioStatusChangeRequest.paramsBody\",\n \"type\": \"json\"\n }\n ]\n },\n {\n \"key\": \"parseGpioStatusFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", | |
76 | + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"gpioList\": {\n \"title\": \"Gpio switches\",\n \"type\": \"array\",\n \"minItems\" : 1,\n \"items\": {\n \"title\": \"Gpio switch\",\n \"type\": \"object\",\n \"properties\": {\n \"pin\": {\n \"title\": \"Pin\",\n \"type\": \"number\"\n },\n \"label\": {\n \"title\": \"Label\",\n \"type\": \"string\"\n },\n \"row\": {\n \"title\": \"Row\",\n \"type\": \"number\"\n },\n \"col\": {\n \"title\": \"Column\",\n \"type\": \"number\"\n }\n },\n \"required\": [\"pin\", \"label\", \"row\", \"col\"]\n }\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"switchPanelBackgroundColor\": {\n \"title\": \"Switches panel background color\",\n \"type\": \"string\",\n \"default\": \"#008a00\"\n },\n \"gpioStatusRequest\": {\n \"title\": \"GPIO status request\",\n \"type\": \"object\",\n \"properties\": {\n \"method\": {\n \"title\": \"Method name\",\n \"type\": \"string\",\n \"default\": \"getGpioStatus\"\n },\n \"paramsBody\": {\n \"title\": \"Method body\",\n \"type\": \"string\",\n \"default\": \"{}\"\n }\n },\n \"required\": [\"method\", \"paramsBody\"]\n },\n \"gpioStatusChangeRequest\": {\n \"title\": \"GPIO status change request\",\n \"type\": \"object\",\n \"properties\": {\n \"method\": {\n \"title\": \"Method name\",\n \"type\": \"string\",\n \"default\": \"setGpioStatus\"\n },\n \"paramsBody\": {\n \"title\": \"Method body\",\n \"type\": \"string\",\n \"default\": \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n }\n },\n \"required\": [\"method\", \"paramsBody\"]\n },\n \"parseGpioStatusFunction\": {\n \"title\": \"Parse gpio status function\",\n \"type\": \"string\",\n \"default\": \"return body[pin] === true;\"\n } \n },\n \"required\": [\"gpioList\", \n \"requestTimeout\",\n \"switchPanelBackgroundColor\",\n \"gpioStatusRequest\",\n \"gpioStatusChangeRequest\",\n \"parseGpioStatusFunction\"]\n },\n \"form\": [\n \"gpioList\",\n \"requestTimeout\",\n {\n \"key\": \"switchPanelBackgroundColor\",\n \"type\": \"color\"\n },\n {\n \"key\": \"gpioStatusRequest\",\n \"items\": [\n \"gpioStatusRequest.method\",\n {\n \"key\": \"gpioStatusRequest.paramsBody\",\n \"type\": \"json\"\n }\n ]\n },\n {\n \"key\": \"gpioStatusChangeRequest\",\n \"items\": [\n \"gpioStatusChangeRequest.method\",\n {\n \"key\": \"gpioStatusChangeRequest.paramsBody\",\n \"type\": \"json\"\n }\n ]\n },\n {\n \"key\": \"parseGpioStatusFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/rpc/parse_gpio_status_fn\"\n }\n ]\n}", | |
77 | 77 | "dataKeySettingsSchema": "{}\n", |
78 | 78 | "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#008a00\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":7,\"label\":\"GPIO 4 (GPCLK0)\",\"row\":3,\"col\":0,\"_uniqueKey\":0},{\"pin\":11,\"label\":\"GPIO 17\",\"row\":5,\"col\":0,\"_uniqueKey\":1},{\"pin\":12,\"label\":\"GPIO 18\",\"row\":5,\"col\":1,\"_uniqueKey\":2},{\"_uniqueKey\":3,\"pin\":13,\"label\":\"GPIO 27\",\"row\":6,\"col\":0},{\"_uniqueKey\":4,\"pin\":15,\"label\":\"GPIO 22\",\"row\":7,\"col\":0},{\"_uniqueKey\":5,\"pin\":16,\"label\":\"GPIO 23\",\"row\":7,\"col\":1},{\"_uniqueKey\":6,\"pin\":18,\"label\":\"GPIO 24\",\"row\":8,\"col\":1},{\"_uniqueKey\":7,\"pin\":22,\"label\":\"GPIO 25\",\"row\":10,\"col\":1},{\"_uniqueKey\":8,\"pin\":29,\"label\":\"GPIO 5\",\"row\":14,\"col\":0},{\"_uniqueKey\":9,\"pin\":31,\"label\":\"GPIO 6\",\"row\":15,\"col\":0},{\"_uniqueKey\":10,\"pin\":32,\"label\":\"GPIO 12\",\"row\":15,\"col\":1},{\"_uniqueKey\":11,\"pin\":33,\"label\":\"GPIO 13\",\"row\":16,\"col\":0},{\"_uniqueKey\":12,\"pin\":35,\"label\":\"GPIO 19\",\"row\":17,\"col\":0},{\"_uniqueKey\":13,\"pin\":36,\"label\":\"GPIO 16\",\"row\":17,\"col\":1},{\"_uniqueKey\":14,\"pin\":37,\"label\":\"GPIO 26\",\"row\":18,\"col\":0},{\"_uniqueKey\":15,\"pin\":38,\"label\":\"GPIO 20\",\"row\":18,\"col\":1},{\"_uniqueKey\":16,\"pin\":40,\"label\":\"GPIO 21\",\"row\":19,\"col\":1}]},\"title\":\"Raspberry Pi GPIO Control\"}" |
79 | 79 | } | ... | ... |
... | ... | @@ -38,7 +38,7 @@ |
38 | 38 | "templateCss": ".tb-toast {\n min-width: 0;\n font-size: 14px !important;\n}", |
39 | 39 | "controllerScript": "self.onInit = function() {\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n self.ctx.$scope.multipleInputWidget.onDataUpdated();\r\n}\r\n", |
40 | 40 | "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"MultipleInput\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showActionButtons\":{\n \"title\":\"Show action buttons\",\n \"type\":\"boolean\",\n \"default\": true\n },\n \"updateAllValues\": {\n \"title\":\"Update all values, not only modified\",\n \"type\":\"boolean\",\n \"default\": false\n },\n \"saveButtonLabel\": {\n \"title\": \"'SAVE' button label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"resetButtonLabel\": {\n \"title\": \"'UNDO' button label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\": true\n },\n \"showGroupTitle\": {\n \"title\":\"Show title for group of fields, related to different entities\",\n \"type\":\"boolean\",\n \"default\": false\n },\n \"groupTitle\": {\n \"title\": \"Group title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"fieldsAlignment\": {\n \"title\": \"Fields alignment\",\n \"type\": \"string\",\n \"default\": \"row\"\n },\n \"fieldsInRow\": {\n \"title\": \"Number of fields in the row\",\n \"type\": \"number\",\n \"default\": \"2\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showActionButtons\",\n {\n \"key\": \"updateAllValues\",\n \"condition\": \"model.showActionButtons === true\"\n },\n {\n \"key\": \"saveButtonLabel\",\n \"condition\": \"model.showActionButtons === true\"\n },\n {\n \"key\": \"resetButtonLabel\",\n \"condition\": \"model.showActionButtons === true\"\n },\n \"showResultMessage\",\n \"showGroupTitle\",\n \"groupTitle\",\n {\n \"key\": \"fieldsAlignment\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"row\",\n \"label\": \"Row (default)\"\n },\n {\n \"value\": \"column\",\n \"label\": \"Column\"\n }\n ]\n },\n {\n \"key\": \"fieldsInRow\",\n \"condition\": \"model.fieldsAlignment === 'row'\"\n }\n ]\n}", |
41 | - "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"dataKeyType\": {\n \"title\": \"Datakey type\",\n \"type\": \"string\",\n \"default\": \"server\"\n },\n \"dataKeyValueType\": {\n \"title\": \"Datakey value type\",\n \"type\": \"string\",\n \"default\": \"string\"\n },\n \"step\": {\n \"title\": \"Step interval between values\",\n \"type\": \"number\",\n \"default\": \"1\"\n },\n \"minValue\": {\n \"title\": \"Minimum value\",\n \"type\": \"number\"\n },\n \"maxValue\": {\n \"title\": \"Maximum value\",\n \"type\": \"number\"\n },\n \"required\": {\n \"title\": \"Value is required\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"minValueErrorMessage\": {\n \"title\": \"'Min Value' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValueErrorMessage\": {\n \"title\": \"'Max Value' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"invalidDateErrorMessage\": {\n \"title\": \"'Invalid Date' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"isEditable\": {\n \"title\": \"Ability to edit attribute\",\n \"type\": \"string\",\n \"default\": \"editable\"\n },\n \"disabledOnDataKey\": {\n \"title\": \"Disable on false value of another datakey (specify datakey name)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"dataKeyHidden\": {\n \"title\": \"Hide input field\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"icon\": {\n \"title\": \"Icon to show before input cell\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"dataKeyType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"server\",\n \"label\": \"Server attribute (default)\"\n },\n {\n \"value\": \"shared\",\n \"label\": \"Shared attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Timeseries\"\n }\n ]\n },\n {\n \"key\": \"dataKeyValueType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"string\",\n \"label\": \"String\"\n },\n {\n \"value\": \"double\",\n \"label\": \"Double\"\n },\n {\n \"value\": \"integer\",\n \"label\": \"Integer\"\n },\n {\n \"value\": \"booleanCheckbox\",\n \"label\": \"Boolean (Checkbox)\"\n },\n {\n \"value\": \"booleanSwitch\",\n \"label\": \"Boolean (Switch)\"\n },\n {\n \"value\": \"dateTime\",\n \"label\": \"Date & Time\"\n },\n {\n \"value\": \"date\",\n \"label\": \"Date\"\n },\n {\n \"value\": \"time\",\n \"label\": \"Time\"\n }\n ]\n },\n {\n \"key\": \"step\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"minValue\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"maxValue\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n \"required\",\n {\n \"key\": \"requiredErrorMessage\",\n \"condition\": \"model.required === true\"\n },\n {\n \"key\": \"invalidDateErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'dateTime' || model.dataKeyValueType === 'date' || model.dataKeyValueType === 'time'\"\n },\n {\n \"key\": \"minValueErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"maxValueErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"isEditable\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"editable\",\n \"label\": \"Editable (default)\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n },\n {\n \"value\": \"readonly\",\n \"label\": \"Read-only\"\n }\n ]\n },\n \"disabledOnDataKey\",\n \"dataKeyHidden\",\n\t\t{\n \t\t\"key\": \"icon\",\n\t\t\t\"type\": \"icon\"\n\t\t}\n ]\n}\n", | |
41 | + "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"dataKeyType\": {\n \"title\": \"Datakey type\",\n \"type\": \"string\",\n \"default\": \"server\"\n },\n \"dataKeyValueType\": {\n \"title\": \"Datakey value type\",\n \"type\": \"string\",\n \"default\": \"string\"\n },\n \"selectOptions\": {\n \"title\": \"Select options\",\n \"type\": \"array\",\n \"default\": [],\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"value\": {\n \"title\": \"Value (write 'null' for create empty option)\",\n \"type\": \"string\"\n },\n \"label\": {\n \"title\": \"Label\",\n \"type\": \"string\"\n }\n },\n \"required\": [\"value\"]\n }\n },\n \"step\": {\n \"title\": \"Step interval between values\",\n \"type\": \"number\",\n \"default\": \"1\"\n },\n \"minValue\": {\n \"title\": \"Minimum value\",\n \"type\": \"number\"\n },\n \"maxValue\": {\n \"title\": \"Maximum value\",\n \"type\": \"number\"\n },\n \"required\": {\n \"title\": \"Value is required\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"minValueErrorMessage\": {\n \"title\": \"'Min Value' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValueErrorMessage\": {\n \"title\": \"'Max Value' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"invalidDateErrorMessage\": {\n \"title\": \"'Invalid Date' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"isEditable\": {\n \"title\": \"Ability to edit attribute\",\n \"type\": \"string\",\n \"default\": \"editable\"\n },\n \"disabledOnDataKey\": {\n \"title\": \"Disable on false value of another datakey (specify datakey name)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"dataKeyHidden\": {\n \"title\": \"Hide input field\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"icon\": {\n \"title\": \"Icon to show before input cell\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"dataKeyType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"server\",\n \"label\": \"Server attribute (default)\"\n },\n {\n \"value\": \"shared\",\n \"label\": \"Shared attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Timeseries\"\n }\n ]\n },\n {\n \"key\": \"dataKeyValueType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"string\",\n \"label\": \"String\"\n },\n {\n \"value\": \"double\",\n \"label\": \"Double\"\n },\n {\n \"value\": \"integer\",\n \"label\": \"Integer\"\n },\n {\n \"value\": \"booleanCheckbox\",\n \"label\": \"Boolean (Checkbox)\"\n },\n {\n \"value\": \"booleanSwitch\",\n \"label\": \"Boolean (Switch)\"\n },\n {\n \"value\": \"dateTime\",\n \"label\": \"Date & Time\"\n },\n {\n \"value\": \"date\",\n \"label\": \"Date\"\n },\n {\n \"value\": \"time\",\n \"label\": \"Time\"\n },\n {\n \"value\": \"select\",\n \"label\": \"Select\"\n }\n ]\n },\n {\n \"key\": \"selectOptions\",\n \"condition\": \"model.dataKeyValueType === 'select'\",\n \"items\": [\"selectOptions[].value\", \"selectOptions[].label\"]\n },\n {\n \"key\": \"step\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"minValue\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"maxValue\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n \"required\",\n {\n \"key\": \"requiredErrorMessage\",\n \"condition\": \"model.required === true\"\n },\n {\n \"key\": \"invalidDateErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'dateTime' || model.dataKeyValueType === 'date' || model.dataKeyValueType === 'time'\"\n },\n {\n \"key\": \"minValueErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"maxValueErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"isEditable\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"editable\",\n \"label\": \"Editable (default)\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n },\n {\n \"value\": \"readonly\",\n \"label\": \"Read-only\"\n }\n ]\n },\n \"disabledOnDataKey\",\n \"dataKeyHidden\",\n\t\t{\n \t\t\"key\": \"icon\",\n\t\t\t\"type\": \"icon\"\n\t\t}\n ]\n}\n", | |
42 | 42 | "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.23592248334107624,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update Multiple Attributes\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" |
43 | 43 | } |
44 | 44 | }, | ... | ... |
... | ... | @@ -59,7 +59,8 @@ import org.thingsboard.server.dao.edge.EdgeEventService; |
59 | 59 | import org.thingsboard.server.dao.edge.EdgeService; |
60 | 60 | import org.thingsboard.server.dao.entityview.EntityViewService; |
61 | 61 | import org.thingsboard.server.dao.event.EventService; |
62 | -import org.thingsboard.server.dao.nosql.CassandraBufferedRateExecutor; | |
62 | +import org.thingsboard.server.dao.nosql.CassandraBufferedRateReadExecutor; | |
63 | +import org.thingsboard.server.dao.nosql.CassandraBufferedRateWriteExecutor; | |
63 | 64 | import org.thingsboard.server.dao.ota.OtaPackageService; |
64 | 65 | import org.thingsboard.server.dao.relation.RelationService; |
65 | 66 | import org.thingsboard.server.dao.resource.ResourceService; |
... | ... | @@ -85,7 +86,6 @@ import org.thingsboard.server.cluster.TbClusterService; |
85 | 86 | import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; |
86 | 87 | import org.thingsboard.server.service.rpc.TbRpcService; |
87 | 88 | import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService; |
88 | -import org.thingsboard.server.service.script.JsExecutorService; | |
89 | 89 | import org.thingsboard.server.service.script.JsInvokeService; |
90 | 90 | import org.thingsboard.server.service.session.DeviceSessionCacheService; |
91 | 91 | import org.thingsboard.server.service.sms.SmsExecutorService; |
... | ... | @@ -421,7 +421,11 @@ public class ActorSystemContext { |
421 | 421 | |
422 | 422 | @Autowired(required = false) |
423 | 423 | @Getter |
424 | - private CassandraBufferedRateExecutor cassandraBufferedRateExecutor; | |
424 | + private CassandraBufferedRateReadExecutor cassandraBufferedRateReadExecutor; | |
425 | + | |
426 | + @Autowired(required = false) | |
427 | + @Getter | |
428 | + private CassandraBufferedRateWriteExecutor cassandraBufferedRateWriteExecutor; | |
425 | 429 | |
426 | 430 | @Autowired(required = false) |
427 | 431 | @Getter | ... | ... |
... | ... | @@ -244,7 +244,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { |
244 | 244 | rpc.setExpirationTime(request.getExpirationTime()); |
245 | 245 | rpc.setRequest(JacksonUtil.valueToTree(request)); |
246 | 246 | rpc.setStatus(status); |
247 | - rpc.setAdditionalInfo(JacksonUtil.valueToTree(request.getAdditionalInfo())); | |
247 | + rpc.setAdditionalInfo(JacksonUtil.toJsonNode(request.getAdditionalInfo())); | |
248 | 248 | return systemContext.getTbRpcService().save(tenantId, rpc); |
249 | 249 | } |
250 | 250 | |
... | ... | @@ -731,6 +731,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { |
731 | 731 | } |
732 | 732 | |
733 | 733 | private void notifyTransportAboutClosedSessionMaxSessionsLimit(UUID sessionId, SessionInfoMetaData sessionMd) { |
734 | + log.debug("remove eldest session (max concurrent sessions limit reached per device) sessionId [{}] sessionMd [{}]", sessionId, sessionMd); | |
734 | 735 | notifyTransportAboutClosedSession(sessionId, sessionMd, "max concurrent sessions limit reached per device!"); |
735 | 736 | } |
736 | 737 | ... | ... |
... | ... | @@ -190,13 +190,13 @@ class DefaultTbContext implements TbContext { |
190 | 190 | @Override |
191 | 191 | public void enqueueForTellNext(TbMsg tbMsg, String queueName, String relationType, Runnable onSuccess, Consumer<Throwable> onFailure) { |
192 | 192 | TopicPartitionInfo tpi = resolvePartition(tbMsg, queueName); |
193 | - enqueueForTellNext(tpi, tbMsg, Collections.singleton(relationType), null, onSuccess, onFailure); | |
193 | + enqueueForTellNext(tpi, queueName, tbMsg, Collections.singleton(relationType), null, onSuccess, onFailure); | |
194 | 194 | } |
195 | 195 | |
196 | 196 | @Override |
197 | 197 | public void enqueueForTellNext(TbMsg tbMsg, String queueName, Set<String> relationTypes, Runnable onSuccess, Consumer<Throwable> onFailure) { |
198 | 198 | TopicPartitionInfo tpi = resolvePartition(tbMsg, queueName); |
199 | - enqueueForTellNext(tpi, tbMsg, relationTypes, null, onSuccess, onFailure); | |
199 | + enqueueForTellNext(tpi, queueName, tbMsg, relationTypes, null, onSuccess, onFailure); | |
200 | 200 | } |
201 | 201 | |
202 | 202 | private TopicPartitionInfo resolvePartition(TbMsg tbMsg, String queueName) { |
... | ... | @@ -211,9 +211,13 @@ class DefaultTbContext implements TbContext { |
211 | 211 | } |
212 | 212 | |
213 | 213 | private void enqueueForTellNext(TopicPartitionInfo tpi, TbMsg source, Set<String> relationTypes, String failureMessage, Runnable onSuccess, Consumer<Throwable> onFailure) { |
214 | + enqueueForTellNext(tpi, source.getQueueName(), source, relationTypes, failureMessage, onSuccess, onFailure); | |
215 | + } | |
216 | + | |
217 | + private void enqueueForTellNext(TopicPartitionInfo tpi, String queueName, TbMsg source, Set<String> relationTypes, String failureMessage, Runnable onSuccess, Consumer<Throwable> onFailure) { | |
214 | 218 | RuleChainId ruleChainId = nodeCtx.getSelf().getRuleChainId(); |
215 | 219 | RuleNodeId ruleNodeId = nodeCtx.getSelf().getId(); |
216 | - TbMsg tbMsg = TbMsg.newMsg(source, ruleChainId, ruleNodeId); | |
220 | + TbMsg tbMsg = TbMsg.newMsg(source, queueName, ruleChainId, ruleNodeId); | |
217 | 221 | TransportProtos.ToRuleEngineMsg.Builder msg = TransportProtos.ToRuleEngineMsg.newBuilder() |
218 | 222 | .setTenantIdMSB(getTenantId().getId().getMostSignificantBits()) |
219 | 223 | .setTenantIdLSB(getTenantId().getId().getLeastSignificantBits()) |
... | ... | @@ -553,8 +557,13 @@ class DefaultTbContext implements TbContext { |
553 | 557 | } |
554 | 558 | |
555 | 559 | @Override |
556 | - public TbResultSetFuture submitCassandraTask(CassandraStatementTask task) { | |
557 | - return mainCtx.getCassandraBufferedRateExecutor().submit(task); | |
560 | + public TbResultSetFuture submitCassandraReadTask(CassandraStatementTask task) { | |
561 | + return mainCtx.getCassandraBufferedRateReadExecutor().submit(task); | |
562 | + } | |
563 | + | |
564 | + @Override | |
565 | + public TbResultSetFuture submitCassandraWriteTask(CassandraStatementTask task) { | |
566 | + return mainCtx.getCassandraBufferedRateWriteExecutor().submit(task); | |
558 | 567 | } |
559 | 568 | |
560 | 569 | @Override | ... | ... |
... | ... | @@ -60,7 +60,7 @@ public abstract class AbstractRpcController extends BaseController { |
60 | 60 | protected TbCoreDeviceRpcService deviceRpcService; |
61 | 61 | |
62 | 62 | @Autowired |
63 | - private AccessValidator accessValidator; | |
63 | + protected AccessValidator accessValidator; | |
64 | 64 | |
65 | 65 | @Value("${server.rest.server_side_rpc.min_timeout:5000}") |
66 | 66 | protected long minTimeout; | ... | ... |
... | ... | @@ -16,6 +16,8 @@ |
16 | 16 | package org.thingsboard.server.controller; |
17 | 17 | |
18 | 18 | import com.fasterxml.jackson.databind.node.ObjectNode; |
19 | +import io.swagger.annotations.ApiOperation; | |
20 | +import io.swagger.annotations.ApiParam; | |
19 | 21 | import org.springframework.beans.factory.annotation.Autowired; |
20 | 22 | import org.springframework.security.access.prepost.PreAuthorize; |
21 | 23 | import org.springframework.web.bind.annotation.PathVariable; |
... | ... | @@ -39,6 +41,8 @@ import org.thingsboard.server.service.security.permission.Resource; |
39 | 41 | import org.thingsboard.server.service.security.system.SystemSecurityService; |
40 | 42 | import org.thingsboard.server.service.update.UpdateService; |
41 | 43 | |
44 | +import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH; | |
45 | + | |
42 | 46 | @RestController |
43 | 47 | @TbCoreComponent |
44 | 48 | @RequestMapping("/api/admin") |
... | ... | @@ -59,10 +63,14 @@ public class AdminController extends BaseController { |
59 | 63 | @Autowired |
60 | 64 | private UpdateService updateService; |
61 | 65 | |
66 | + @ApiOperation(value = "Get the Administration Settings object using key (getAdminSettings)", | |
67 | + notes = "Get the Administration Settings object using specified string key. Referencing non-existing key will cause an error." + SYSTEM_AUTHORITY_PARAGRAPH) | |
62 | 68 | @PreAuthorize("hasAuthority('SYS_ADMIN')") |
63 | 69 | @RequestMapping(value = "/settings/{key}", method = RequestMethod.GET) |
64 | 70 | @ResponseBody |
65 | - public AdminSettings getAdminSettings(@PathVariable("key") String key) throws ThingsboardException { | |
71 | + public AdminSettings getAdminSettings( | |
72 | + @ApiParam(value = "A string value of the key (e.g. 'general' or 'mail').") | |
73 | + @PathVariable("key") String key) throws ThingsboardException { | |
66 | 74 | try { |
67 | 75 | accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); |
68 | 76 | AdminSettings adminSettings = checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, key)); |
... | ... | @@ -75,10 +83,17 @@ public class AdminController extends BaseController { |
75 | 83 | } |
76 | 84 | } |
77 | 85 | |
86 | + | |
87 | + @ApiOperation(value = "Get the Administration Settings object using key (getAdminSettings)", | |
88 | + notes = "Creates or Updates the Administration Settings. Platform generates random Administration Settings Id during settings creation. " + | |
89 | + "The Administration Settings Id will be present in the response. Specify the Administration Settings Id when you would like to update the Administration Settings. " + | |
90 | + "Referencing non-existing Administration Settings Id will cause an error." + SYSTEM_AUTHORITY_PARAGRAPH) | |
78 | 91 | @PreAuthorize("hasAuthority('SYS_ADMIN')") |
79 | 92 | @RequestMapping(value = "/settings", method = RequestMethod.POST) |
80 | 93 | @ResponseBody |
81 | - public AdminSettings saveAdminSettings(@RequestBody AdminSettings adminSettings) throws ThingsboardException { | |
94 | + public AdminSettings saveAdminSettings( | |
95 | + @ApiParam(value = "A JSON value representing the Administration Settings.") | |
96 | + @RequestBody AdminSettings adminSettings) throws ThingsboardException { | |
82 | 97 | try { |
83 | 98 | accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.WRITE); |
84 | 99 | adminSettings = checkNotNull(adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, adminSettings)); |
... | ... | @@ -94,6 +109,8 @@ public class AdminController extends BaseController { |
94 | 109 | } |
95 | 110 | } |
96 | 111 | |
112 | + @ApiOperation(value = "Get the Security Settings object", | |
113 | + notes = "Get the Security Settings object that contains password policy, etc." + SYSTEM_AUTHORITY_PARAGRAPH) | |
97 | 114 | @PreAuthorize("hasAuthority('SYS_ADMIN')") |
98 | 115 | @RequestMapping(value = "/securitySettings", method = RequestMethod.GET) |
99 | 116 | @ResponseBody |
... | ... | @@ -106,10 +123,14 @@ public class AdminController extends BaseController { |
106 | 123 | } |
107 | 124 | } |
108 | 125 | |
126 | + @ApiOperation(value = "Update Security Settings (saveSecuritySettings)", | |
127 | + notes = "Updates the Security Settings object that contains password policy, etc." + SYSTEM_AUTHORITY_PARAGRAPH) | |
109 | 128 | @PreAuthorize("hasAuthority('SYS_ADMIN')") |
110 | 129 | @RequestMapping(value = "/securitySettings", method = RequestMethod.POST) |
111 | 130 | @ResponseBody |
112 | - public SecuritySettings saveSecuritySettings(@RequestBody SecuritySettings securitySettings) throws ThingsboardException { | |
131 | + public SecuritySettings saveSecuritySettings( | |
132 | + @ApiParam(value = "A JSON value representing the Security Settings.") | |
133 | + @RequestBody SecuritySettings securitySettings) throws ThingsboardException { | |
113 | 134 | try { |
114 | 135 | accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.WRITE); |
115 | 136 | securitySettings = checkNotNull(systemSecurityService.saveSecuritySettings(TenantId.SYS_TENANT_ID, securitySettings)); |
... | ... | @@ -119,14 +140,19 @@ public class AdminController extends BaseController { |
119 | 140 | } |
120 | 141 | } |
121 | 142 | |
143 | + @ApiOperation(value = "Send test email (sendTestMail)", | |
144 | + notes = "Attempts to send test email to the System Administrator User using Mail Settings provided as a parameter. " + | |
145 | + "You may change the 'To' email in the user profile of the System Administrator. " + SYSTEM_AUTHORITY_PARAGRAPH) | |
122 | 146 | @PreAuthorize("hasAuthority('SYS_ADMIN')") |
123 | 147 | @RequestMapping(value = "/settings/testMail", method = RequestMethod.POST) |
124 | - public void sendTestMail(@RequestBody AdminSettings adminSettings) throws ThingsboardException { | |
148 | + public void sendTestMail( | |
149 | + @ApiParam(value = "A JSON value representing the Mail Settings.") | |
150 | + @RequestBody AdminSettings adminSettings) throws ThingsboardException { | |
125 | 151 | try { |
126 | 152 | accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); |
127 | 153 | adminSettings = checkNotNull(adminSettings); |
128 | 154 | if (adminSettings.getKey().equals("mail")) { |
129 | - if(!adminSettings.getJsonValue().has("password")) { | |
155 | + if (!adminSettings.getJsonValue().has("password")) { | |
130 | 156 | AdminSettings mailSettings = checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "mail")); |
131 | 157 | ((ObjectNode) adminSettings.getJsonValue()).put("password", mailSettings.getJsonValue().get("password").asText()); |
132 | 158 | } |
... | ... | @@ -138,9 +164,14 @@ public class AdminController extends BaseController { |
138 | 164 | } |
139 | 165 | } |
140 | 166 | |
167 | + @ApiOperation(value = "Send test sms (sendTestMail)", | |
168 | + notes = "Attempts to send test sms to the System Administrator User using SMS Settings and phone number provided as a parameters of the request. " | |
169 | + + SYSTEM_AUTHORITY_PARAGRAPH) | |
141 | 170 | @PreAuthorize("hasAuthority('SYS_ADMIN')") |
142 | 171 | @RequestMapping(value = "/settings/testSms", method = RequestMethod.POST) |
143 | - public void sendTestSms(@RequestBody TestSmsRequest testSmsRequest) throws ThingsboardException { | |
172 | + public void sendTestSms( | |
173 | + @ApiParam(value = "A JSON value representing the Test SMS request.") | |
174 | + @RequestBody TestSmsRequest testSmsRequest) throws ThingsboardException { | |
144 | 175 | try { |
145 | 176 | accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); |
146 | 177 | smsService.sendTestSms(testSmsRequest); |
... | ... | @@ -149,6 +180,9 @@ public class AdminController extends BaseController { |
149 | 180 | } |
150 | 181 | } |
151 | 182 | |
183 | + @ApiOperation(value = "Check for new Platform Releases (checkUpdates)", | |
184 | + notes = "Check notifications about new platform releases. " | |
185 | + + SYSTEM_AUTHORITY_PARAGRAPH) | |
152 | 186 | @PreAuthorize("hasAuthority('SYS_ADMIN')") |
153 | 187 | @RequestMapping(value = "/updates", method = RequestMethod.GET) |
154 | 188 | @ResponseBody | ... | ... |
... | ... | @@ -15,8 +15,11 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.controller; |
17 | 17 | |
18 | +import io.swagger.annotations.ApiOperation; | |
19 | +import io.swagger.annotations.ApiParam; | |
18 | 20 | import org.apache.commons.lang3.StringUtils; |
19 | 21 | import org.springframework.http.HttpStatus; |
22 | +import org.springframework.http.MediaType; | |
20 | 23 | import org.springframework.security.access.prepost.PreAuthorize; |
21 | 24 | import org.springframework.web.bind.annotation.PathVariable; |
22 | 25 | import org.springframework.web.bind.annotation.RequestBody; |
... | ... | @@ -49,17 +52,46 @@ import org.thingsboard.server.service.security.permission.Resource; |
49 | 52 | |
50 | 53 | import java.util.List; |
51 | 54 | |
55 | +import static org.thingsboard.server.controller.ControllerConstants.ALARM_ID_PARAM_DESCRIPTION; | |
56 | +import static org.thingsboard.server.controller.ControllerConstants.ALARM_INFO_DESCRIPTION; | |
57 | +import static org.thingsboard.server.controller.ControllerConstants.ALARM_SORT_PROPERTY_ALLOWABLE_VALUES; | |
58 | +import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID; | |
59 | +import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID_PARAM_DESCRIPTION; | |
60 | +import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE; | |
61 | +import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE_PARAM_DESCRIPTION; | |
62 | +import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; | |
63 | +import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; | |
64 | +import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; | |
65 | +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; | |
66 | +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; | |
67 | +import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; | |
68 | +import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; | |
69 | + | |
52 | 70 | @RestController |
53 | 71 | @TbCoreComponent |
54 | 72 | @RequestMapping("/api") |
55 | 73 | public class AlarmController extends BaseController { |
56 | 74 | |
57 | 75 | public static final String ALARM_ID = "alarmId"; |
76 | + private static final String ALARM_SECURITY_CHECK = "If the user has the authority of 'Tenant Administrator', the server checks that the originator of alarm is owned by the same tenant. " + | |
77 | + "If the user has the authority of 'Customer User', the server checks that the originator of alarm belongs to the customer. "; | |
78 | + private static final String ALARM_QUERY_SEARCH_STATUS_DESCRIPTION = "A string value representing one of the AlarmSearchStatus enumeration value"; | |
79 | + private static final String ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES = "ANY, ACTIVE, CLEARED, ACK, UNACK"; | |
80 | + private static final String ALARM_QUERY_STATUS_DESCRIPTION = "A string value representing one of the AlarmStatus enumeration value"; | |
81 | + private static final String ALARM_QUERY_STATUS_ALLOWABLE_VALUES = "ACTIVE_UNACK, ACTIVE_ACK, CLEARED_UNACK, CLEARED_ACK"; | |
82 | + private static final String ALARM_QUERY_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on of next alarm fields: type, severity or status"; | |
83 | + private static final String ALARM_QUERY_START_TIME_DESCRIPTION = "The start timestamp in milliseconds of the search time range over the Alarm class field: 'createdTime'."; | |
84 | + private static final String ALARM_QUERY_END_TIME_DESCRIPTION = "The end timestamp in milliseconds of the search time range over the Alarm class field: 'createdTime'."; | |
85 | + private static final String ALARM_QUERY_FETCH_ORIGINATOR_DESCRIPTION = "A boolean value to specify if the alarm originator name will be " + | |
86 | + "filled in the AlarmInfo object field: 'originatorName' or will returns as null."; | |
58 | 87 | |
59 | - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") | |
88 | + @ApiOperation(value = "Get Alarm (getAlarmById)", | |
89 | + notes = "Fetch the Alarm object based on the provided Alarm Id. " + ALARM_SECURITY_CHECK, produces = MediaType.APPLICATION_JSON_VALUE) | |
90 | + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") | |
60 | 91 | @RequestMapping(value = "/alarm/{alarmId}", method = RequestMethod.GET) |
61 | 92 | @ResponseBody |
62 | - public Alarm getAlarmById(@PathVariable(ALARM_ID) String strAlarmId) throws ThingsboardException { | |
93 | + public Alarm getAlarmById(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) | |
94 | + @PathVariable(ALARM_ID) String strAlarmId) throws ThingsboardException { | |
63 | 95 | checkParameter(ALARM_ID, strAlarmId); |
64 | 96 | try { |
65 | 97 | AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); |
... | ... | @@ -69,10 +101,14 @@ public class AlarmController extends BaseController { |
69 | 101 | } |
70 | 102 | } |
71 | 103 | |
72 | - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") | |
104 | + @ApiOperation(value = "Get Alarm Info (getAlarmInfoById)", | |
105 | + notes = "Fetch the Alarm Info object based on the provided Alarm Id. " + | |
106 | + ALARM_SECURITY_CHECK + ALARM_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) | |
107 | + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") | |
73 | 108 | @RequestMapping(value = "/alarm/info/{alarmId}", method = RequestMethod.GET) |
74 | 109 | @ResponseBody |
75 | - public AlarmInfo getAlarmInfoById(@PathVariable(ALARM_ID) String strAlarmId) throws ThingsboardException { | |
110 | + public AlarmInfo getAlarmInfoById(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) | |
111 | + @PathVariable(ALARM_ID) String strAlarmId) throws ThingsboardException { | |
76 | 112 | checkParameter(ALARM_ID, strAlarmId); |
77 | 113 | try { |
78 | 114 | AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); |
... | ... | @@ -82,10 +118,20 @@ public class AlarmController extends BaseController { |
82 | 118 | } |
83 | 119 | } |
84 | 120 | |
85 | - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") | |
121 | + @ApiOperation(value = "Create or update Alarm (saveAlarm)", | |
122 | + notes = "Creates or Updates the Alarm. " + | |
123 | + "When creating alarm, platform generates Alarm Id as [time-based UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_1_(date-time_and_MAC_address). " + | |
124 | + "The newly created Alarm id will be present in the response. Specify existing Alarm id to update the alarm. " + | |
125 | + "Referencing non-existing Alarm Id will cause 'Not Found' error. " + | |
126 | + "\n\nPlatform also deduplicate the alarms based on the entity id of originator and alarm 'type'. " + | |
127 | + "For example, if the user or system component create the alarm with the type 'HighTemperature' for device 'Device A' the new active alarm is created. " + | |
128 | + "If the user tries to create 'HighTemperature' alarm for the same device again, the previous alarm will be updated (the 'end_ts' will be set to current timestamp). " + | |
129 | + "If the user clears the alarm (see 'Clear Alarm(clearAlarm)'), than new alarm with the same type and same device may be created. " + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH | |
130 | + , produces = MediaType.APPLICATION_JSON_VALUE) | |
131 | + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") | |
86 | 132 | @RequestMapping(value = "/alarm", method = RequestMethod.POST) |
87 | 133 | @ResponseBody |
88 | - public Alarm saveAlarm(@RequestBody Alarm alarm) throws ThingsboardException { | |
134 | + public Alarm saveAlarm(@ApiParam(value = "A JSON value representing the alarm.") @RequestBody Alarm alarm) throws ThingsboardException { | |
89 | 135 | try { |
90 | 136 | alarm.setTenantId(getCurrentUser().getTenantId()); |
91 | 137 | |
... | ... | @@ -106,10 +152,12 @@ public class AlarmController extends BaseController { |
106 | 152 | } |
107 | 153 | } |
108 | 154 | |
109 | - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") | |
155 | + @ApiOperation(value = "Delete Alarm (deleteAlarm)", | |
156 | + notes = "Deletes the Alarm. Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) | |
157 | + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") | |
110 | 158 | @RequestMapping(value = "/alarm/{alarmId}", method = RequestMethod.DELETE) |
111 | 159 | @ResponseBody |
112 | - public Boolean deleteAlarm(@PathVariable(ALARM_ID) String strAlarmId) throws ThingsboardException { | |
160 | + public Boolean deleteAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws ThingsboardException { | |
113 | 161 | checkParameter(ALARM_ID, strAlarmId); |
114 | 162 | try { |
115 | 163 | AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); |
... | ... | @@ -124,15 +172,19 @@ public class AlarmController extends BaseController { |
124 | 172 | sendAlarmDeleteNotificationMsg(getTenantId(), alarmId, relatedEdgeIds, alarm); |
125 | 173 | |
126 | 174 | return alarmService.deleteAlarm(getTenantId(), alarmId); |
127 | - } catch (Exception e) { | |
175 | + } catch (Exception e) { | |
128 | 176 | throw handleException(e); |
129 | 177 | } |
130 | 178 | } |
131 | 179 | |
132 | - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") | |
180 | + @ApiOperation(value = "Acknowledge Alarm (ackAlarm)", | |
181 | + notes = "Acknowledge the Alarm. " + | |
182 | + "Once acknowledged, the 'ack_ts' field will be set to current timestamp and special rule chain event 'ALARM_ACK' will be generated. " + | |
183 | + "Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) | |
184 | + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") | |
133 | 185 | @RequestMapping(value = "/alarm/{alarmId}/ack", method = RequestMethod.POST) |
134 | 186 | @ResponseStatus(value = HttpStatus.OK) |
135 | - public void ackAlarm(@PathVariable(ALARM_ID) String strAlarmId) throws ThingsboardException { | |
187 | + public void ackAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws ThingsboardException { | |
136 | 188 | checkParameter(ALARM_ID, strAlarmId); |
137 | 189 | try { |
138 | 190 | AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); |
... | ... | @@ -149,10 +201,14 @@ public class AlarmController extends BaseController { |
149 | 201 | } |
150 | 202 | } |
151 | 203 | |
152 | - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") | |
204 | + @ApiOperation(value = "Clear Alarm (clearAlarm)", | |
205 | + notes = "Clear the Alarm. " + | |
206 | + "Once cleared, the 'clear_ts' field will be set to current timestamp and special rule chain event 'ALARM_CLEAR' will be generated. " + | |
207 | + "Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) | |
208 | + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") | |
153 | 209 | @RequestMapping(value = "/alarm/{alarmId}/clear", method = RequestMethod.POST) |
154 | 210 | @ResponseStatus(value = HttpStatus.OK) |
155 | - public void clearAlarm(@PathVariable(ALARM_ID) String strAlarmId) throws ThingsboardException { | |
211 | + public void clearAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws ThingsboardException { | |
156 | 212 | checkParameter(ALARM_ID, strAlarmId); |
157 | 213 | try { |
158 | 214 | AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); |
... | ... | @@ -169,21 +225,36 @@ public class AlarmController extends BaseController { |
169 | 225 | } |
170 | 226 | } |
171 | 227 | |
172 | - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") | |
228 | + @ApiOperation(value = "Get Alarms (getAlarms)", | |
229 | + notes = "Returns a page of alarms for the selected entity. Specifying both parameters 'searchStatus' and 'status' at the same time will cause an error. " + | |
230 | + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) | |
231 | + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") | |
173 | 232 | @RequestMapping(value = "/alarm/{entityType}/{entityId}", method = RequestMethod.GET) |
174 | 233 | @ResponseBody |
175 | 234 | public PageData<AlarmInfo> getAlarms( |
176 | - @PathVariable("entityType") String strEntityType, | |
177 | - @PathVariable("entityId") String strEntityId, | |
235 | + @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION) | |
236 | + @PathVariable(ENTITY_TYPE) String strEntityType, | |
237 | + @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION) | |
238 | + @PathVariable(ENTITY_ID) String strEntityId, | |
239 | + @ApiParam(value = ALARM_QUERY_SEARCH_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES) | |
178 | 240 | @RequestParam(required = false) String searchStatus, |
241 | + @ApiParam(value = ALARM_QUERY_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_STATUS_ALLOWABLE_VALUES) | |
179 | 242 | @RequestParam(required = false) String status, |
243 | + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) | |
180 | 244 | @RequestParam int pageSize, |
245 | + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) | |
181 | 246 | @RequestParam int page, |
247 | + @ApiParam(value = ALARM_QUERY_TEXT_SEARCH_DESCRIPTION) | |
182 | 248 | @RequestParam(required = false) String textSearch, |
249 | + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ALARM_SORT_PROPERTY_ALLOWABLE_VALUES) | |
183 | 250 | @RequestParam(required = false) String sortProperty, |
251 | + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) | |
184 | 252 | @RequestParam(required = false) String sortOrder, |
253 | + @ApiParam(value = ALARM_QUERY_START_TIME_DESCRIPTION) | |
185 | 254 | @RequestParam(required = false) Long startTime, |
255 | + @ApiParam(value = ALARM_QUERY_END_TIME_DESCRIPTION) | |
186 | 256 | @RequestParam(required = false) Long endTime, |
257 | + @ApiParam(value = ALARM_QUERY_FETCH_ORIGINATOR_DESCRIPTION) | |
187 | 258 | @RequestParam(required = false) Boolean fetchOriginator |
188 | 259 | ) throws ThingsboardException { |
189 | 260 | checkParameter("EntityId", strEntityId); |
... | ... | @@ -204,20 +275,35 @@ public class AlarmController extends BaseController { |
204 | 275 | throw handleException(e); |
205 | 276 | } |
206 | 277 | } |
207 | - | |
208 | - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") | |
278 | + @ApiOperation(value = "Get All Alarms (getAllAlarms)", | |
279 | + notes = "Returns a page of alarms that belongs to the current user owner. " + | |
280 | + "If the user has the authority of 'Tenant Administrator', the server returns alarms that belongs to the tenant of current user. " + | |
281 | + "If the user has the authority of 'Customer User', the server returns alarms that belongs to the customer of current user. " + | |
282 | + "Specifying both parameters 'searchStatus' and 'status' at the same time will cause an error. " + | |
283 | + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) | |
284 | + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") | |
209 | 285 | @RequestMapping(value = "/alarms", method = RequestMethod.GET) |
210 | 286 | @ResponseBody |
211 | 287 | public PageData<AlarmInfo> getAllAlarms( |
288 | + @ApiParam(value = ALARM_QUERY_SEARCH_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES) | |
212 | 289 | @RequestParam(required = false) String searchStatus, |
290 | + @ApiParam(value = ALARM_QUERY_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_STATUS_ALLOWABLE_VALUES) | |
213 | 291 | @RequestParam(required = false) String status, |
292 | + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) | |
214 | 293 | @RequestParam int pageSize, |
294 | + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) | |
215 | 295 | @RequestParam int page, |
296 | + @ApiParam(value = ALARM_QUERY_TEXT_SEARCH_DESCRIPTION) | |
216 | 297 | @RequestParam(required = false) String textSearch, |
298 | + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ALARM_SORT_PROPERTY_ALLOWABLE_VALUES) | |
217 | 299 | @RequestParam(required = false) String sortProperty, |
300 | + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) | |
218 | 301 | @RequestParam(required = false) String sortOrder, |
302 | + @ApiParam(value = ALARM_QUERY_START_TIME_DESCRIPTION) | |
219 | 303 | @RequestParam(required = false) Long startTime, |
304 | + @ApiParam(value = ALARM_QUERY_END_TIME_DESCRIPTION) | |
220 | 305 | @RequestParam(required = false) Long endTime, |
306 | + @ApiParam(value = ALARM_QUERY_FETCH_ORIGINATOR_DESCRIPTION) | |
221 | 307 | @RequestParam(required = false) Boolean fetchOriginator |
222 | 308 | ) throws ThingsboardException { |
223 | 309 | AlarmSearchStatus alarmSearchStatus = StringUtils.isEmpty(searchStatus) ? null : AlarmSearchStatus.valueOf(searchStatus); |
... | ... | @@ -239,13 +325,21 @@ public class AlarmController extends BaseController { |
239 | 325 | } |
240 | 326 | } |
241 | 327 | |
242 | - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") | |
328 | + @ApiOperation(value = "Get Highest Alarm Severity (getHighestAlarmSeverity)", | |
329 | + notes = "Search the alarms by originator ('entityType' and entityId') and optional 'status' or 'searchStatus' filters and returns the highest AlarmSeverity(CRITICAL, MAJOR, MINOR, WARNING or INDETERMINATE). " + | |
330 | + "Specifying both parameters 'searchStatus' and 'status' at the same time will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH | |
331 | + , produces = MediaType.APPLICATION_JSON_VALUE) | |
332 | + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") | |
243 | 333 | @RequestMapping(value = "/alarm/highestSeverity/{entityType}/{entityId}", method = RequestMethod.GET) |
244 | 334 | @ResponseBody |
245 | 335 | public AlarmSeverity getHighestAlarmSeverity( |
246 | - @PathVariable("entityType") String strEntityType, | |
247 | - @PathVariable("entityId") String strEntityId, | |
336 | + @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) | |
337 | + @PathVariable(ENTITY_TYPE) String strEntityType, | |
338 | + @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) | |
339 | + @PathVariable(ENTITY_ID) String strEntityId, | |
340 | + @ApiParam(value = ALARM_QUERY_SEARCH_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES) | |
248 | 341 | @RequestParam(required = false) String searchStatus, |
342 | + @ApiParam(value = ALARM_QUERY_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_STATUS_ALLOWABLE_VALUES) | |
249 | 343 | @RequestParam(required = false) String status |
250 | 344 | ) throws ThingsboardException { |
251 | 345 | checkParameter("EntityId", strEntityId); | ... | ... |
... | ... | @@ -16,9 +16,12 @@ |
16 | 16 | package org.thingsboard.server.controller; |
17 | 17 | |
18 | 18 | import com.google.common.util.concurrent.ListenableFuture; |
19 | +import io.swagger.annotations.ApiOperation; | |
20 | +import io.swagger.annotations.ApiParam; | |
19 | 21 | import lombok.RequiredArgsConstructor; |
20 | 22 | import lombok.extern.slf4j.Slf4j; |
21 | 23 | import org.springframework.http.HttpStatus; |
24 | +import org.springframework.http.MediaType; | |
22 | 25 | import org.springframework.security.access.prepost.PreAuthorize; |
23 | 26 | import org.springframework.web.bind.annotation.PathVariable; |
24 | 27 | import org.springframework.web.bind.annotation.PostMapping; |
... | ... | @@ -37,8 +40,8 @@ import org.thingsboard.server.common.data.asset.AssetInfo; |
37 | 40 | import org.thingsboard.server.common.data.asset.AssetSearchQuery; |
38 | 41 | import org.thingsboard.server.common.data.audit.ActionType; |
39 | 42 | import org.thingsboard.server.common.data.edge.Edge; |
40 | -import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; | |
41 | 43 | import org.thingsboard.server.common.data.edge.EdgeEventActionType; |
44 | +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; | |
42 | 45 | import org.thingsboard.server.common.data.exception.ThingsboardException; |
43 | 46 | import org.thingsboard.server.common.data.id.AssetId; |
44 | 47 | import org.thingsboard.server.common.data.id.CustomerId; |
... | ... | @@ -61,9 +64,28 @@ import java.util.ArrayList; |
61 | 64 | import java.util.List; |
62 | 65 | import java.util.stream.Collectors; |
63 | 66 | |
64 | -import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE; | |
65 | - | |
67 | +import static org.thingsboard.server.controller.ControllerConstants.ASSET_ID_PARAM_DESCRIPTION; | |
68 | +import static org.thingsboard.server.controller.ControllerConstants.ASSET_INFO_DESCRIPTION; | |
69 | +import static org.thingsboard.server.controller.ControllerConstants.ASSET_NAME_DESCRIPTION; | |
70 | +import static org.thingsboard.server.controller.ControllerConstants.ASSET_SORT_PROPERTY_ALLOWABLE_VALUES; | |
71 | +import static org.thingsboard.server.controller.ControllerConstants.ASSET_TEXT_SEARCH_DESCRIPTION; | |
72 | +import static org.thingsboard.server.controller.ControllerConstants.ASSET_TYPE_DESCRIPTION; | |
73 | +import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID_PARAM_DESCRIPTION; | |
74 | +import static org.thingsboard.server.controller.ControllerConstants.EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION; | |
75 | +import static org.thingsboard.server.controller.ControllerConstants.EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION; | |
76 | +import static org.thingsboard.server.controller.ControllerConstants.EDGE_ID_PARAM_DESCRIPTION; | |
77 | +import static org.thingsboard.server.controller.ControllerConstants.EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION; | |
78 | +import static org.thingsboard.server.controller.ControllerConstants.EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION; | |
79 | +import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; | |
80 | +import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; | |
81 | +import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; | |
82 | +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; | |
83 | +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; | |
84 | +import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; | |
85 | +import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; | |
86 | +import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; | |
66 | 87 | import static org.thingsboard.server.controller.EdgeController.EDGE_ID; |
88 | +import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE; | |
67 | 89 | |
68 | 90 | @RestController |
69 | 91 | @TbCoreComponent |
... | ... | @@ -75,10 +97,16 @@ public class AssetController extends BaseController { |
75 | 97 | |
76 | 98 | public static final String ASSET_ID = "assetId"; |
77 | 99 | |
100 | + @ApiOperation(value = "Get Asset (getAssetById)", | |
101 | + notes = "Fetch the Asset object based on the provided Asset Id. " + | |
102 | + "If the user has the authority of 'Tenant Administrator', the server checks that the asset is owned by the same tenant. " + | |
103 | + "If the user has the authority of 'Customer User', the server checks that the asset is assigned to the same customer." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH | |
104 | + , produces = MediaType.APPLICATION_JSON_VALUE) | |
78 | 105 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
79 | 106 | @RequestMapping(value = "/asset/{assetId}", method = RequestMethod.GET) |
80 | 107 | @ResponseBody |
81 | - public Asset getAssetById(@PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { | |
108 | + public Asset getAssetById(@ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) | |
109 | + @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { | |
82 | 110 | checkParameter(ASSET_ID, strAssetId); |
83 | 111 | try { |
84 | 112 | AssetId assetId = new AssetId(toUUID(strAssetId)); |
... | ... | @@ -88,10 +116,16 @@ public class AssetController extends BaseController { |
88 | 116 | } |
89 | 117 | } |
90 | 118 | |
119 | + @ApiOperation(value = "Get Asset Info (getAssetInfoById)", | |
120 | + notes = "Fetch the Asset Info object based on the provided Asset Id. " + | |
121 | + "If the user has the authority of 'Tenant Administrator', the server checks that the asset is owned by the same tenant. " + | |
122 | + "If the user has the authority of 'Customer User', the server checks that the asset is assigned to the same customer. " | |
123 | + + ASSET_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) | |
91 | 124 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
92 | 125 | @RequestMapping(value = "/asset/info/{assetId}", method = RequestMethod.GET) |
93 | 126 | @ResponseBody |
94 | - public AssetInfo getAssetInfoById(@PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { | |
127 | + public AssetInfo getAssetInfoById(@ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) | |
128 | + @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { | |
95 | 129 | checkParameter(ASSET_ID, strAssetId); |
96 | 130 | try { |
97 | 131 | AssetId assetId = new AssetId(toUUID(strAssetId)); |
... | ... | @@ -101,10 +135,15 @@ public class AssetController extends BaseController { |
101 | 135 | } |
102 | 136 | } |
103 | 137 | |
138 | + @ApiOperation(value = "Create Or Update Asset (saveAsset)", | |
139 | + notes = "Creates or Updates the Asset. When creating asset, platform generates Asset Id as [time-based UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_1_(date-time_and_MAC_address) " + | |
140 | + "The newly created Asset id will be present in the response. " + | |
141 | + "Specify existing Asset id to update the asset. " + | |
142 | + "Referencing non-existing Asset Id will cause 'Not Found' error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) | |
104 | 143 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
105 | 144 | @RequestMapping(value = "/asset", method = RequestMethod.POST) |
106 | 145 | @ResponseBody |
107 | - public Asset saveAsset(@RequestBody Asset asset) throws ThingsboardException { | |
146 | + public Asset saveAsset(@ApiParam(value = "A JSON value representing the asset.") @RequestBody Asset asset) throws ThingsboardException { | |
108 | 147 | try { |
109 | 148 | if (TB_SERVICE_QUEUE.equals(asset.getType())) { |
110 | 149 | throw new ThingsboardException("Unable to save asset with type " + TB_SERVICE_QUEUE, ThingsboardErrorCode.BAD_REQUEST_PARAMS); |
... | ... | @@ -116,7 +155,7 @@ public class AssetController extends BaseController { |
116 | 155 | |
117 | 156 | Asset savedAsset = checkNotNull(assetService.saveAsset(asset)); |
118 | 157 | |
119 | - onAssetCreatedOrUpdated(savedAsset, asset.getId() != null); | |
158 | + onAssetCreatedOrUpdated(savedAsset, asset.getId() != null, getCurrentUser()); | |
120 | 159 | |
121 | 160 | return savedAsset; |
122 | 161 | } catch (Exception e) { |
... | ... | @@ -126,9 +165,9 @@ public class AssetController extends BaseController { |
126 | 165 | } |
127 | 166 | } |
128 | 167 | |
129 | - private void onAssetCreatedOrUpdated(Asset asset, boolean updated) { | |
168 | + private void onAssetCreatedOrUpdated(Asset asset, boolean updated, SecurityUser user) { | |
130 | 169 | try { |
131 | - logEntityAction(asset.getId(), asset, | |
170 | + logEntityAction(user, asset.getId(), asset, | |
132 | 171 | asset.getCustomerId(), |
133 | 172 | updated ? ActionType.UPDATED : ActionType.ADDED, null); |
134 | 173 | } catch (ThingsboardException e) { |
... | ... | @@ -140,10 +179,12 @@ public class AssetController extends BaseController { |
140 | 179 | } |
141 | 180 | } |
142 | 181 | |
182 | + @ApiOperation(value = "Delete asset (deleteAsset)", | |
183 | + notes = "Deletes the asset and all the relations (from and to the asset). Referencing non-existing asset Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) | |
143 | 184 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
144 | 185 | @RequestMapping(value = "/asset/{assetId}", method = RequestMethod.DELETE) |
145 | 186 | @ResponseStatus(value = HttpStatus.OK) |
146 | - public void deleteAsset(@PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { | |
187 | + public void deleteAsset(@ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { | |
147 | 188 | checkParameter(ASSET_ID, strAssetId); |
148 | 189 | try { |
149 | 190 | AssetId assetId = new AssetId(toUUID(strAssetId)); |
... | ... | @@ -167,11 +208,13 @@ public class AssetController extends BaseController { |
167 | 208 | } |
168 | 209 | } |
169 | 210 | |
211 | + @ApiOperation(value = "Assign asset to customer (assignAssetToCustomer)", | |
212 | + notes = "Creates assignment of the asset to customer. Customer will be able to query asset afterwards." + TENANT_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) | |
170 | 213 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
171 | 214 | @RequestMapping(value = "/customer/{customerId}/asset/{assetId}", method = RequestMethod.POST) |
172 | 215 | @ResponseBody |
173 | - public Asset assignAssetToCustomer(@PathVariable("customerId") String strCustomerId, | |
174 | - @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { | |
216 | + public Asset assignAssetToCustomer(@ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) @PathVariable("customerId") String strCustomerId, | |
217 | + @ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { | |
175 | 218 | checkParameter("customerId", strCustomerId); |
176 | 219 | checkParameter(ASSET_ID, strAssetId); |
177 | 220 | try { |
... | ... | @@ -201,10 +244,12 @@ public class AssetController extends BaseController { |
201 | 244 | } |
202 | 245 | } |
203 | 246 | |
247 | + @ApiOperation(value = "Unassign asset from customer (unassignAssetFromCustomer)", | |
248 | + notes = "Clears assignment of the asset to customer. Customer will not be able to query asset afterwards." + TENANT_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) | |
204 | 249 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
205 | 250 | @RequestMapping(value = "/customer/asset/{assetId}", method = RequestMethod.DELETE) |
206 | 251 | @ResponseBody |
207 | - public Asset unassignAssetFromCustomer(@PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { | |
252 | + public Asset unassignAssetFromCustomer(@ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { | |
208 | 253 | checkParameter(ASSET_ID, strAssetId); |
209 | 254 | try { |
210 | 255 | AssetId assetId = new AssetId(toUUID(strAssetId)); |
... | ... | @@ -235,10 +280,14 @@ public class AssetController extends BaseController { |
235 | 280 | } |
236 | 281 | } |
237 | 282 | |
283 | + @ApiOperation(value = "Make asset publicly available (assignAssetToPublicCustomer)", | |
284 | + notes = "Asset will be available for non-authorized (not logged-in) users. " + | |
285 | + "This is useful to create dashboards that you plan to share/embed on a publicly available website. " + | |
286 | + "However, users that are logged-in and belong to different tenant will not be able to access the asset." + TENANT_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) | |
238 | 287 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
239 | 288 | @RequestMapping(value = "/customer/public/asset/{assetId}", method = RequestMethod.POST) |
240 | 289 | @ResponseBody |
241 | - public Asset assignAssetToPublicCustomer(@PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { | |
290 | + public Asset assignAssetToPublicCustomer(@ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { | |
242 | 291 | checkParameter(ASSET_ID, strAssetId); |
243 | 292 | try { |
244 | 293 | AssetId assetId = new AssetId(toUUID(strAssetId)); |
... | ... | @@ -261,15 +310,24 @@ public class AssetController extends BaseController { |
261 | 310 | } |
262 | 311 | } |
263 | 312 | |
313 | + @ApiOperation(value = "Get Tenant Assets (getTenantAssets)", | |
314 | + notes = "Returns a page of assets owned by tenant. " + | |
315 | + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) | |
264 | 316 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
265 | 317 | @RequestMapping(value = "/tenant/assets", params = {"pageSize", "page"}, method = RequestMethod.GET) |
266 | 318 | @ResponseBody |
267 | 319 | public PageData<Asset> getTenantAssets( |
320 | + @ApiParam(value = PAGE_SIZE_DESCRIPTION) | |
268 | 321 | @RequestParam int pageSize, |
322 | + @ApiParam(value = PAGE_NUMBER_DESCRIPTION) | |
269 | 323 | @RequestParam int page, |
324 | + @ApiParam(value = ASSET_TYPE_DESCRIPTION) | |
270 | 325 | @RequestParam(required = false) String type, |
326 | + @ApiParam(value = ASSET_TEXT_SEARCH_DESCRIPTION) | |
271 | 327 | @RequestParam(required = false) String textSearch, |
328 | + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ASSET_SORT_PROPERTY_ALLOWABLE_VALUES) | |
272 | 329 | @RequestParam(required = false) String sortProperty, |
330 | + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) | |
273 | 331 | @RequestParam(required = false) String sortOrder) throws ThingsboardException { |
274 | 332 | try { |
275 | 333 | TenantId tenantId = getCurrentUser().getTenantId(); |
... | ... | @@ -284,15 +342,24 @@ public class AssetController extends BaseController { |
284 | 342 | } |
285 | 343 | } |
286 | 344 | |
345 | + @ApiOperation(value = "Get Tenant Asset Infos (getTenantAssetInfos)", | |
346 | + notes = "Returns a page of assets info objects owned by tenant. " + | |
347 | + PAGE_DATA_PARAMETERS + ASSET_INFO_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) | |
287 | 348 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
288 | 349 | @RequestMapping(value = "/tenant/assetInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) |
289 | 350 | @ResponseBody |
290 | 351 | public PageData<AssetInfo> getTenantAssetInfos( |
352 | + @ApiParam(value = PAGE_SIZE_DESCRIPTION) | |
291 | 353 | @RequestParam int pageSize, |
354 | + @ApiParam(value = PAGE_NUMBER_DESCRIPTION) | |
292 | 355 | @RequestParam int page, |
356 | + @ApiParam(value = ASSET_TYPE_DESCRIPTION) | |
293 | 357 | @RequestParam(required = false) String type, |
358 | + @ApiParam(value = ASSET_TEXT_SEARCH_DESCRIPTION) | |
294 | 359 | @RequestParam(required = false) String textSearch, |
360 | + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ASSET_SORT_PROPERTY_ALLOWABLE_VALUES) | |
295 | 361 | @RequestParam(required = false) String sortProperty, |
362 | + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) | |
296 | 363 | @RequestParam(required = false) String sortOrder) throws ThingsboardException { |
297 | 364 | try { |
298 | 365 | TenantId tenantId = getCurrentUser().getTenantId(); |
... | ... | @@ -307,10 +374,14 @@ public class AssetController extends BaseController { |
307 | 374 | } |
308 | 375 | } |
309 | 376 | |
377 | + @ApiOperation(value = "Get Tenant Asset (getTenantAsset)", | |
378 | + notes = "Requested asset must be owned by tenant that the user belongs to. " + | |
379 | + "Asset name is an unique property of asset. So it can be used to identify the asset." + TENANT_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) | |
310 | 380 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
311 | 381 | @RequestMapping(value = "/tenant/assets", params = {"assetName"}, method = RequestMethod.GET) |
312 | 382 | @ResponseBody |
313 | 383 | public Asset getTenantAsset( |
384 | + @ApiParam(value = ASSET_NAME_DESCRIPTION) | |
314 | 385 | @RequestParam String assetName) throws ThingsboardException { |
315 | 386 | try { |
316 | 387 | TenantId tenantId = getCurrentUser().getTenantId(); |
... | ... | @@ -320,16 +391,26 @@ public class AssetController extends BaseController { |
320 | 391 | } |
321 | 392 | } |
322 | 393 | |
394 | + @ApiOperation(value = "Get Customer Assets (getCustomerAssets)", | |
395 | + notes = "Returns a page of assets objects assigned to customer. " + | |
396 | + PAGE_DATA_PARAMETERS, produces = MediaType.APPLICATION_JSON_VALUE) | |
323 | 397 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
324 | 398 | @RequestMapping(value = "/customer/{customerId}/assets", params = {"pageSize", "page"}, method = RequestMethod.GET) |
325 | 399 | @ResponseBody |
326 | 400 | public PageData<Asset> getCustomerAssets( |
401 | + @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) | |
327 | 402 | @PathVariable("customerId") String strCustomerId, |
403 | + @ApiParam(value = PAGE_SIZE_DESCRIPTION) | |
328 | 404 | @RequestParam int pageSize, |
405 | + @ApiParam(value = PAGE_NUMBER_DESCRIPTION) | |
329 | 406 | @RequestParam int page, |
407 | + @ApiParam(value = ASSET_TYPE_DESCRIPTION) | |
330 | 408 | @RequestParam(required = false) String type, |
409 | + @ApiParam(value = ASSET_TEXT_SEARCH_DESCRIPTION) | |
331 | 410 | @RequestParam(required = false) String textSearch, |
411 | + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ASSET_SORT_PROPERTY_ALLOWABLE_VALUES) | |
332 | 412 | @RequestParam(required = false) String sortProperty, |
413 | + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) | |
333 | 414 | @RequestParam(required = false) String sortOrder) throws ThingsboardException { |
334 | 415 | checkParameter("customerId", strCustomerId); |
335 | 416 | try { |
... | ... | @@ -347,16 +428,26 @@ public class AssetController extends BaseController { |
347 | 428 | } |
348 | 429 | } |
349 | 430 | |
431 | + @ApiOperation(value = "Get Customer Asset Infos (getCustomerAssetInfos)", | |
432 | + notes = "Returns a page of assets info objects assigned to customer. " + | |
433 | + PAGE_DATA_PARAMETERS + ASSET_INFO_DESCRIPTION, produces = MediaType.APPLICATION_JSON_VALUE) | |
350 | 434 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
351 | 435 | @RequestMapping(value = "/customer/{customerId}/assetInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) |
352 | 436 | @ResponseBody |
353 | 437 | public PageData<AssetInfo> getCustomerAssetInfos( |
438 | + @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) | |
354 | 439 | @PathVariable("customerId") String strCustomerId, |
440 | + @ApiParam(value = PAGE_SIZE_DESCRIPTION) | |
355 | 441 | @RequestParam int pageSize, |
442 | + @ApiParam(value = PAGE_NUMBER_DESCRIPTION) | |
356 | 443 | @RequestParam int page, |
444 | + @ApiParam(value = ASSET_TYPE_DESCRIPTION) | |
357 | 445 | @RequestParam(required = false) String type, |
446 | + @ApiParam(value = ASSET_TEXT_SEARCH_DESCRIPTION) | |
358 | 447 | @RequestParam(required = false) String textSearch, |
448 | + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ASSET_SORT_PROPERTY_ALLOWABLE_VALUES) | |
359 | 449 | @RequestParam(required = false) String sortProperty, |
450 | + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) | |
360 | 451 | @RequestParam(required = false) String sortOrder) throws ThingsboardException { |
361 | 452 | checkParameter("customerId", strCustomerId); |
362 | 453 | try { |
... | ... | @@ -374,10 +465,13 @@ public class AssetController extends BaseController { |
374 | 465 | } |
375 | 466 | } |
376 | 467 | |
468 | + @ApiOperation(value = "Get Assets By Ids (getAssetsByIds)", | |
469 | + notes = "Requested assets must be owned by tenant or assigned to customer which user is performing the request. ", produces = MediaType.APPLICATION_JSON_VALUE) | |
377 | 470 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
378 | 471 | @RequestMapping(value = "/assets", params = {"assetIds"}, method = RequestMethod.GET) |
379 | 472 | @ResponseBody |
380 | 473 | public List<Asset> getAssetsByIds( |
474 | + @ApiParam(value = "A list of assets ids, separated by comma ','") | |
381 | 475 | @RequestParam("assetIds") String[] strAssetIds) throws ThingsboardException { |
382 | 476 | checkArrayParameter("assetIds", strAssetIds); |
383 | 477 | try { |
... | ... | @@ -400,6 +494,10 @@ public class AssetController extends BaseController { |
400 | 494 | } |
401 | 495 | } |
402 | 496 | |
497 | + @ApiOperation(value = "Find related assets (findByQuery)", | |
498 | + notes = "Returns all assets that are related to the specific entity. " + | |
499 | + "The entity id, relation type, asset types, depth of the search, and other query parameters defined using complex 'AssetSearchQuery' object. " + | |
500 | + "See 'Model' tab of the Parameters for more info.", produces = MediaType.APPLICATION_JSON_VALUE) | |
403 | 501 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
404 | 502 | @RequestMapping(value = "/assets", method = RequestMethod.POST) |
405 | 503 | @ResponseBody |
... | ... | @@ -424,6 +522,8 @@ public class AssetController extends BaseController { |
424 | 522 | } |
425 | 523 | } |
426 | 524 | |
525 | + @ApiOperation(value = "Get Asset Types (getAssetTypes)", | |
526 | + notes = "Returns a set of unique asset types based on assets that are either owned by the tenant or assigned to the customer which user is performing the request.", produces = MediaType.APPLICATION_JSON_VALUE) | |
427 | 527 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
428 | 528 | @RequestMapping(value = "/asset/types", method = RequestMethod.GET) |
429 | 529 | @ResponseBody |
... | ... | @@ -438,11 +538,18 @@ public class AssetController extends BaseController { |
438 | 538 | } |
439 | 539 | } |
440 | 540 | |
541 | + @ApiOperation(value = "Assign asset to edge (assignAssetToEdge)", | |
542 | + notes = "Creates assignment of an existing asset to an instance of The Edge. " + | |
543 | + EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION + | |
544 | + "Second, remote edge service will receive a copy of assignment asset " + | |
545 | + EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION + ". " + | |
546 | + "Third, once asset will be delivered to edge service, it's going to be available for usage on remote edge instance.", | |
547 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
441 | 548 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
442 | 549 | @RequestMapping(value = "/edge/{edgeId}/asset/{assetId}", method = RequestMethod.POST) |
443 | 550 | @ResponseBody |
444 | - public Asset assignAssetToEdge(@PathVariable(EDGE_ID) String strEdgeId, | |
445 | - @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { | |
551 | + public Asset assignAssetToEdge(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION) @PathVariable(EDGE_ID) String strEdgeId, | |
552 | + @ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { | |
446 | 553 | checkParameter(EDGE_ID, strEdgeId); |
447 | 554 | checkParameter(ASSET_ID, strAssetId); |
448 | 555 | try { |
... | ... | @@ -471,11 +578,18 @@ public class AssetController extends BaseController { |
471 | 578 | } |
472 | 579 | } |
473 | 580 | |
581 | + @ApiOperation(value = "Unassign asset from edge (unassignAssetFromEdge)", | |
582 | + notes = "Clears assignment of the asset to the edge. " + | |
583 | + EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION + | |
584 | + "Second, remote edge service will receive an 'unassign' command to remove asset " + | |
585 | + EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION + ". " + | |
586 | + "Third, once 'unassign' command will be delivered to edge service, it's going to remove asset locally.", | |
587 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
474 | 588 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
475 | 589 | @RequestMapping(value = "/edge/{edgeId}/asset/{assetId}", method = RequestMethod.DELETE) |
476 | 590 | @ResponseBody |
477 | - public Asset unassignAssetFromEdge(@PathVariable(EDGE_ID) String strEdgeId, | |
478 | - @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { | |
591 | + public Asset unassignAssetFromEdge(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION) @PathVariable(EDGE_ID) String strEdgeId, | |
592 | + @ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { | |
479 | 593 | checkParameter(EDGE_ID, strEdgeId); |
480 | 594 | checkParameter(ASSET_ID, strAssetId); |
481 | 595 | try { |
... | ... | @@ -504,18 +618,30 @@ public class AssetController extends BaseController { |
504 | 618 | } |
505 | 619 | } |
506 | 620 | |
621 | + @ApiOperation(value = "Get assets assigned to edge (getEdgeAssets)", | |
622 | + notes = "Returns a page of assets assigned to edge. " + | |
623 | + PAGE_DATA_PARAMETERS, produces = MediaType.APPLICATION_JSON_VALUE) | |
507 | 624 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
508 | 625 | @RequestMapping(value = "/edge/{edgeId}/assets", params = {"pageSize", "page"}, method = RequestMethod.GET) |
509 | 626 | @ResponseBody |
510 | 627 | public PageData<Asset> getEdgeAssets( |
628 | + @ApiParam(value = EDGE_ID_PARAM_DESCRIPTION) | |
511 | 629 | @PathVariable(EDGE_ID) String strEdgeId, |
630 | + @ApiParam(value = PAGE_SIZE_DESCRIPTION) | |
512 | 631 | @RequestParam int pageSize, |
632 | + @ApiParam(value = PAGE_NUMBER_DESCRIPTION) | |
513 | 633 | @RequestParam int page, |
634 | + @ApiParam(value = ASSET_TYPE_DESCRIPTION) | |
514 | 635 | @RequestParam(required = false) String type, |
636 | + @ApiParam(value = ASSET_TEXT_SEARCH_DESCRIPTION) | |
515 | 637 | @RequestParam(required = false) String textSearch, |
638 | + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ASSET_SORT_PROPERTY_ALLOWABLE_VALUES) | |
516 | 639 | @RequestParam(required = false) String sortProperty, |
640 | + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) | |
517 | 641 | @RequestParam(required = false) String sortOrder, |
642 | + @ApiParam(value = "Timestamp. Assets with creation time before it won't be queried") | |
518 | 643 | @RequestParam(required = false) Long startTime, |
644 | + @ApiParam(value = "Timestamp. Assets with creation time after it won't be queried") | |
519 | 645 | @RequestParam(required = false) Long endTime) throws ThingsboardException { |
520 | 646 | checkParameter(EDGE_ID, strEdgeId); |
521 | 647 | try { |
... | ... | @@ -547,11 +673,14 @@ public class AssetController extends BaseController { |
547 | 673 | } |
548 | 674 | } |
549 | 675 | |
676 | + @ApiOperation(value = "Import the bulk of assets (processAssetsBulkImport)", | |
677 | + notes = "There's an ability to import the bulk of assets using the only .csv file.", produces = MediaType.APPLICATION_JSON_VALUE) | |
550 | 678 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") |
551 | 679 | @PostMapping("/asset/bulk_import") |
552 | 680 | public BulkImportResult<Asset> processAssetsBulkImport(@RequestBody BulkImportRequest request) throws Exception { |
553 | - return assetBulkImportService.processBulkImport(request, getCurrentUser(), importedAssetInfo -> { | |
554 | - onAssetCreatedOrUpdated(importedAssetInfo.getEntity(), importedAssetInfo.isUpdated()); | |
681 | + SecurityUser user = getCurrentUser(); | |
682 | + return assetBulkImportService.processBulkImport(request, user, importedAssetInfo -> { | |
683 | + onAssetCreatedOrUpdated(importedAssetInfo.getEntity(), importedAssetInfo.isUpdated(), user); | |
555 | 684 | }); |
556 | 685 | } |
557 | 686 | ... | ... |
... | ... | @@ -15,7 +15,10 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.controller; |
17 | 17 | |
18 | +import io.swagger.annotations.ApiOperation; | |
19 | +import io.swagger.annotations.ApiParam; | |
18 | 20 | import org.apache.commons.lang3.StringUtils; |
21 | +import org.springframework.http.MediaType; | |
19 | 22 | import org.springframework.security.access.prepost.PreAuthorize; |
20 | 23 | import org.springframework.web.bind.annotation.PathVariable; |
21 | 24 | import org.springframework.web.bind.annotation.RequestMapping; |
... | ... | @@ -39,23 +42,60 @@ import java.util.List; |
39 | 42 | import java.util.UUID; |
40 | 43 | import java.util.stream.Collectors; |
41 | 44 | |
45 | +import static org.thingsboard.server.controller.ControllerConstants.AUDIT_LOG_SORT_PROPERTY_ALLOWABLE_VALUES; | |
46 | +import static org.thingsboard.server.controller.ControllerConstants.AUDIT_LOG_TEXT_SEARCH_DESCRIPTION; | |
47 | +import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID_PARAM_DESCRIPTION; | |
48 | +import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID_PARAM_DESCRIPTION; | |
49 | +import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE_PARAM_DESCRIPTION; | |
50 | +import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; | |
51 | +import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; | |
52 | +import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; | |
53 | +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; | |
54 | +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; | |
55 | +import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; | |
56 | +import static org.thingsboard.server.controller.ControllerConstants.USER_ID_PARAM_DESCRIPTION; | |
57 | + | |
42 | 58 | @RestController |
43 | 59 | @TbCoreComponent |
44 | 60 | @RequestMapping("/api") |
45 | 61 | public class AuditLogController extends BaseController { |
46 | 62 | |
63 | + private static final String AUDIT_LOG_QUERY_START_TIME_DESCRIPTION = "The start timestamp in milliseconds of the search time range over the AuditLog class field: 'createdTime'."; | |
64 | + private static final String AUDIT_LOG_QUERY_END_TIME_DESCRIPTION = "The end timestamp in milliseconds of the search time range over the AuditLog class field: 'createdTime'."; | |
65 | + private static final String AUDIT_LOG_QUERY_ACTION_TYPES_DESCRIPTION = "A String value representing comma-separated list of action types. " + | |
66 | + "This parameter is optional, but it can be used to filter results to fetch only audit logs of specific action types. " + | |
67 | + "For example, 'LOGIN', 'LOGOUT'. See the 'Model' tab of the Response Class for more details."; | |
68 | + private static final String AUDIT_LOG_SORT_PROPERTY_DESCRIPTION = "Property of audit log to sort by. " + | |
69 | + "See the 'Model' tab of the Response Class for more details. " + | |
70 | + "Note: entityType sort property is not defined in the AuditLog class, however, it can be used to sort audit logs by types of entities that were logged."; | |
71 | + | |
72 | + | |
73 | + @ApiOperation(value = "Get audit logs by customer id (getAuditLogsByCustomerId)", | |
74 | + notes = "Returns a page of audit logs related to the targeted customer entities (devices, assets, etc.), " + | |
75 | + "and users actions (login, logout, etc.) that belong to this customer. " + | |
76 | + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, | |
77 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
47 | 78 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
48 | 79 | @RequestMapping(value = "/audit/logs/customer/{customerId}", params = {"pageSize", "page"}, method = RequestMethod.GET) |
49 | 80 | @ResponseBody |
50 | 81 | public PageData<AuditLog> getAuditLogsByCustomerId( |
82 | + @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) | |
51 | 83 | @PathVariable("customerId") String strCustomerId, |
84 | + @ApiParam(value = PAGE_SIZE_DESCRIPTION) | |
52 | 85 | @RequestParam int pageSize, |
86 | + @ApiParam(value = PAGE_NUMBER_DESCRIPTION) | |
53 | 87 | @RequestParam int page, |
88 | + @ApiParam(value = AUDIT_LOG_TEXT_SEARCH_DESCRIPTION) | |
54 | 89 | @RequestParam(required = false) String textSearch, |
90 | + @ApiParam(value = AUDIT_LOG_SORT_PROPERTY_DESCRIPTION, allowableValues = AUDIT_LOG_SORT_PROPERTY_ALLOWABLE_VALUES) | |
55 | 91 | @RequestParam(required = false) String sortProperty, |
92 | + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) | |
56 | 93 | @RequestParam(required = false) String sortOrder, |
94 | + @ApiParam(value = AUDIT_LOG_QUERY_START_TIME_DESCRIPTION) | |
57 | 95 | @RequestParam(required = false) Long startTime, |
96 | + @ApiParam(value = AUDIT_LOG_QUERY_END_TIME_DESCRIPTION) | |
58 | 97 | @RequestParam(required = false) Long endTime, |
98 | + @ApiParam(value = AUDIT_LOG_QUERY_ACTION_TYPES_DESCRIPTION) | |
59 | 99 | @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException { |
60 | 100 | try { |
61 | 101 | checkParameter("CustomerId", strCustomerId); |
... | ... | @@ -68,18 +108,32 @@ public class AuditLogController extends BaseController { |
68 | 108 | } |
69 | 109 | } |
70 | 110 | |
111 | + @ApiOperation(value = "Get audit logs by user id (getAuditLogsByUserId)", | |
112 | + notes = "Returns a page of audit logs related to the actions of targeted user. " + | |
113 | + "For example, RPC call to a particular device, or alarm acknowledgment for a specific device, etc. " + | |
114 | + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, | |
115 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
71 | 116 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
72 | 117 | @RequestMapping(value = "/audit/logs/user/{userId}", params = {"pageSize", "page"}, method = RequestMethod.GET) |
73 | 118 | @ResponseBody |
74 | 119 | public PageData<AuditLog> getAuditLogsByUserId( |
120 | + @ApiParam(value = USER_ID_PARAM_DESCRIPTION) | |
75 | 121 | @PathVariable("userId") String strUserId, |
122 | + @ApiParam(value = PAGE_SIZE_DESCRIPTION) | |
76 | 123 | @RequestParam int pageSize, |
124 | + @ApiParam(value = PAGE_NUMBER_DESCRIPTION) | |
77 | 125 | @RequestParam int page, |
126 | + @ApiParam(value = AUDIT_LOG_TEXT_SEARCH_DESCRIPTION) | |
78 | 127 | @RequestParam(required = false) String textSearch, |
128 | + @ApiParam(value = AUDIT_LOG_SORT_PROPERTY_DESCRIPTION, allowableValues = AUDIT_LOG_SORT_PROPERTY_ALLOWABLE_VALUES) | |
79 | 129 | @RequestParam(required = false) String sortProperty, |
130 | + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) | |
80 | 131 | @RequestParam(required = false) String sortOrder, |
132 | + @ApiParam(value = AUDIT_LOG_QUERY_START_TIME_DESCRIPTION) | |
81 | 133 | @RequestParam(required = false) Long startTime, |
134 | + @ApiParam(value = AUDIT_LOG_QUERY_END_TIME_DESCRIPTION) | |
82 | 135 | @RequestParam(required = false) Long endTime, |
136 | + @ApiParam(value = AUDIT_LOG_QUERY_ACTION_TYPES_DESCRIPTION) | |
83 | 137 | @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException { |
84 | 138 | try { |
85 | 139 | checkParameter("UserId", strUserId); |
... | ... | @@ -92,19 +146,35 @@ public class AuditLogController extends BaseController { |
92 | 146 | } |
93 | 147 | } |
94 | 148 | |
149 | + @ApiOperation(value = "Get audit logs by entity id (getAuditLogsByEntityId)", | |
150 | + notes = "Returns a page of audit logs related to the actions on the targeted entity. " + | |
151 | + "Basically, this API call is used to get the full lifecycle of some specific entity. " + | |
152 | + "For example to see when a device was created, updated, assigned to some customer, or even deleted from the system. " + | |
153 | + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, | |
154 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
95 | 155 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
96 | 156 | @RequestMapping(value = "/audit/logs/entity/{entityType}/{entityId}", params = {"pageSize", "page"}, method = RequestMethod.GET) |
97 | 157 | @ResponseBody |
98 | 158 | public PageData<AuditLog> getAuditLogsByEntityId( |
159 | + @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION) | |
99 | 160 | @PathVariable("entityType") String strEntityType, |
161 | + @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION) | |
100 | 162 | @PathVariable("entityId") String strEntityId, |
163 | + @ApiParam(value = PAGE_SIZE_DESCRIPTION) | |
101 | 164 | @RequestParam int pageSize, |
165 | + @ApiParam(value = PAGE_NUMBER_DESCRIPTION) | |
102 | 166 | @RequestParam int page, |
167 | + @ApiParam(value = AUDIT_LOG_TEXT_SEARCH_DESCRIPTION) | |
103 | 168 | @RequestParam(required = false) String textSearch, |
169 | + @ApiParam(value = AUDIT_LOG_SORT_PROPERTY_DESCRIPTION, allowableValues = AUDIT_LOG_SORT_PROPERTY_ALLOWABLE_VALUES) | |
104 | 170 | @RequestParam(required = false) String sortProperty, |
171 | + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) | |
105 | 172 | @RequestParam(required = false) String sortOrder, |
173 | + @ApiParam(value = AUDIT_LOG_QUERY_START_TIME_DESCRIPTION) | |
106 | 174 | @RequestParam(required = false) Long startTime, |
175 | + @ApiParam(value = AUDIT_LOG_QUERY_END_TIME_DESCRIPTION) | |
107 | 176 | @RequestParam(required = false) Long endTime, |
177 | + @ApiParam(value = AUDIT_LOG_QUERY_ACTION_TYPES_DESCRIPTION) | |
108 | 178 | @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException { |
109 | 179 | try { |
110 | 180 | checkParameter("EntityId", strEntityId); |
... | ... | @@ -118,17 +188,29 @@ public class AuditLogController extends BaseController { |
118 | 188 | } |
119 | 189 | } |
120 | 190 | |
191 | + @ApiOperation(value = "Get all audit logs (getAuditLogs)", | |
192 | + notes = "Returns a page of audit logs related to all entities in the scope of the current user's Tenant. " + | |
193 | + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, | |
194 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
121 | 195 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
122 | 196 | @RequestMapping(value = "/audit/logs", params = {"pageSize", "page"}, method = RequestMethod.GET) |
123 | 197 | @ResponseBody |
124 | 198 | public PageData<AuditLog> getAuditLogs( |
199 | + @ApiParam(value = PAGE_SIZE_DESCRIPTION) | |
125 | 200 | @RequestParam int pageSize, |
201 | + @ApiParam(value = PAGE_NUMBER_DESCRIPTION) | |
126 | 202 | @RequestParam int page, |
203 | + @ApiParam(value = AUDIT_LOG_TEXT_SEARCH_DESCRIPTION) | |
127 | 204 | @RequestParam(required = false) String textSearch, |
205 | + @ApiParam(value = AUDIT_LOG_SORT_PROPERTY_DESCRIPTION, allowableValues = AUDIT_LOG_SORT_PROPERTY_ALLOWABLE_VALUES) | |
128 | 206 | @RequestParam(required = false) String sortProperty, |
207 | + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) | |
129 | 208 | @RequestParam(required = false) String sortOrder, |
209 | + @ApiParam(value = AUDIT_LOG_QUERY_START_TIME_DESCRIPTION) | |
130 | 210 | @RequestParam(required = false) Long startTime, |
211 | + @ApiParam(value = AUDIT_LOG_QUERY_END_TIME_DESCRIPTION) | |
131 | 212 | @RequestParam(required = false) Long endTime, |
213 | + @ApiParam(value = AUDIT_LOG_QUERY_ACTION_TYPES_DESCRIPTION) | |
132 | 214 | @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException { |
133 | 215 | try { |
134 | 216 | TenantId tenantId = getCurrentUser().getTenantId(); | ... | ... |
... | ... | @@ -18,6 +18,8 @@ package org.thingsboard.server.controller; |
18 | 18 | import com.fasterxml.jackson.databind.JsonNode; |
19 | 19 | import com.fasterxml.jackson.databind.ObjectMapper; |
20 | 20 | import com.fasterxml.jackson.databind.node.ObjectNode; |
21 | +import io.swagger.annotations.ApiOperation; | |
22 | +import io.swagger.annotations.ApiParam; | |
21 | 23 | import lombok.RequiredArgsConstructor; |
22 | 24 | import lombok.extern.slf4j.Slf4j; |
23 | 25 | import org.springframework.context.ApplicationEventPublisher; |
... | ... | @@ -48,8 +50,13 @@ import org.thingsboard.server.common.data.security.model.SecuritySettings; |
48 | 50 | import org.thingsboard.server.common.data.security.model.UserPasswordPolicy; |
49 | 51 | import org.thingsboard.server.dao.audit.AuditLogService; |
50 | 52 | import org.thingsboard.server.queue.util.TbCoreComponent; |
53 | +import org.thingsboard.server.service.security.model.ActivateUserRequest; | |
54 | +import org.thingsboard.server.service.security.model.ChangePasswordRequest; | |
55 | +import org.thingsboard.server.service.security.model.ResetPasswordEmailRequest; | |
56 | +import org.thingsboard.server.service.security.model.ResetPasswordRequest; | |
51 | 57 | import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; |
52 | 58 | import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails; |
59 | +import org.thingsboard.server.service.security.model.JwtTokenPair; | |
53 | 60 | import org.thingsboard.server.service.security.model.SecurityUser; |
54 | 61 | import org.thingsboard.server.service.security.model.UserPrincipal; |
55 | 62 | import org.thingsboard.server.service.security.model.token.JwtTokenFactory; |
... | ... | @@ -74,9 +81,13 @@ public class AuthController extends BaseController { |
74 | 81 | private final AuditLogService auditLogService; |
75 | 82 | private final ApplicationEventPublisher eventPublisher; |
76 | 83 | |
84 | + | |
85 | + @ApiOperation(value = "Get current User (getUser)", | |
86 | + notes = "Get the information about the User which credentials are used to perform this REST API call.") | |
77 | 87 | @PreAuthorize("isAuthenticated()") |
78 | 88 | @RequestMapping(value = "/auth/user", method = RequestMethod.GET) |
79 | - public @ResponseBody User getUser() throws ThingsboardException { | |
89 | + public @ResponseBody | |
90 | + User getUser() throws ThingsboardException { | |
80 | 91 | try { |
81 | 92 | SecurityUser securityUser = getCurrentUser(); |
82 | 93 | return userService.findUserById(securityUser.getTenantId(), securityUser.getId()); |
... | ... | @@ -85,6 +96,8 @@ public class AuthController extends BaseController { |
85 | 96 | } |
86 | 97 | } |
87 | 98 | |
99 | + @ApiOperation(value = "Logout (logout)", | |
100 | + notes = "Special API call to record the 'logout' of the user to the Audit Logs. Since platform uses [JWT](https://jwt.io/), the actual logout is the procedure of clearing the [JWT](https://jwt.io/) token on the client side. ") | |
88 | 101 | @PreAuthorize("isAuthenticated()") |
89 | 102 | @RequestMapping(value = "/auth/logout", method = RequestMethod.POST) |
90 | 103 | @ResponseStatus(value = HttpStatus.OK) |
... | ... | @@ -92,13 +105,17 @@ public class AuthController extends BaseController { |
92 | 105 | logLogoutAction(request); |
93 | 106 | } |
94 | 107 | |
108 | + @ApiOperation(value = "Change password for current User (changePassword)", | |
109 | + notes = "Change the password for the User which credentials are used to perform this REST API call. Be aware that previously generated [JWT](https://jwt.io/) tokens will be still valid until they expire.") | |
95 | 110 | @PreAuthorize("isAuthenticated()") |
96 | 111 | @RequestMapping(value = "/auth/changePassword", method = RequestMethod.POST) |
97 | 112 | @ResponseStatus(value = HttpStatus.OK) |
98 | - public ObjectNode changePassword(@RequestBody JsonNode changePasswordRequest) throws ThingsboardException { | |
113 | + public ObjectNode changePassword( | |
114 | + @ApiParam(value = "Change Password Request") | |
115 | + @RequestBody ChangePasswordRequest changePasswordRequest) throws ThingsboardException { | |
99 | 116 | try { |
100 | - String currentPassword = changePasswordRequest.get("currentPassword").asText(); | |
101 | - String newPassword = changePasswordRequest.get("newPassword").asText(); | |
117 | + String currentPassword = changePasswordRequest.getCurrentPassword(); | |
118 | + String newPassword = changePasswordRequest.getNewPassword(); | |
102 | 119 | SecurityUser securityUser = getCurrentUser(); |
103 | 120 | UserCredentials userCredentials = userService.findUserCredentialsByUserId(TenantId.SYS_TENANT_ID, securityUser.getId()); |
104 | 121 | if (!passwordEncoder.matches(currentPassword, userCredentials.getPassword())) { |
... | ... | @@ -123,6 +140,8 @@ public class AuthController extends BaseController { |
123 | 140 | } |
124 | 141 | } |
125 | 142 | |
143 | + @ApiOperation(value = "Get the current User password policy (getUserPasswordPolicy)", | |
144 | + notes = "API call to get the password policy for the password validation form(s).") | |
126 | 145 | @RequestMapping(value = "/noauth/userPasswordPolicy", method = RequestMethod.GET) |
127 | 146 | @ResponseBody |
128 | 147 | public UserPasswordPolicy getUserPasswordPolicy() throws ThingsboardException { |
... | ... | @@ -135,8 +154,13 @@ public class AuthController extends BaseController { |
135 | 154 | } |
136 | 155 | } |
137 | 156 | |
157 | + @ApiOperation(value = "Check Activate User Token (checkActivateToken)", | |
158 | + notes = "Checks the activation token and forwards user to 'Create Password' page. " + | |
159 | + "If token is valid, returns '303 See Other' (redirect) response code with the correct address of 'Create Password' page and same 'activateToken' specified in the URL parameters. " + | |
160 | + "If token is not valid, returns '409 Conflict'.") | |
138 | 161 | @RequestMapping(value = "/noauth/activate", params = {"activateToken"}, method = RequestMethod.GET) |
139 | 162 | public ResponseEntity<String> checkActivateToken( |
163 | + @ApiParam(value = "The activate token string.") | |
140 | 164 | @RequestParam(value = "activateToken") String activateToken) { |
141 | 165 | HttpHeaders headers = new HttpHeaders(); |
142 | 166 | HttpStatus responseStatus; |
... | ... | @@ -157,13 +181,17 @@ public class AuthController extends BaseController { |
157 | 181 | return new ResponseEntity<>(headers, responseStatus); |
158 | 182 | } |
159 | 183 | |
184 | + @ApiOperation(value = "Request reset password email (requestResetPasswordByEmail)", | |
185 | + notes = "Request to send the reset password email if the user with specified email address is present in the database. " + | |
186 | + "Always return '200 OK' status for security purposes.") | |
160 | 187 | @RequestMapping(value = "/noauth/resetPasswordByEmail", method = RequestMethod.POST) |
161 | 188 | @ResponseStatus(value = HttpStatus.OK) |
162 | 189 | public void requestResetPasswordByEmail( |
163 | - @RequestBody JsonNode resetPasswordByEmailRequest, | |
190 | + @ApiParam(value = "The JSON object representing the reset password email request.") | |
191 | + @RequestBody ResetPasswordEmailRequest resetPasswordByEmailRequest, | |
164 | 192 | HttpServletRequest request) throws ThingsboardException { |
165 | 193 | try { |
166 | - String email = resetPasswordByEmailRequest.get("email").asText(); | |
194 | + String email = resetPasswordByEmailRequest.getEmail(); | |
167 | 195 | UserCredentials userCredentials = userService.requestPasswordReset(TenantId.SYS_TENANT_ID, email); |
168 | 196 | User user = userService.findUserById(TenantId.SYS_TENANT_ID, userCredentials.getUserId()); |
169 | 197 | String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request); |
... | ... | @@ -176,8 +204,13 @@ public class AuthController extends BaseController { |
176 | 204 | } |
177 | 205 | } |
178 | 206 | |
207 | + @ApiOperation(value = "Check password reset token (checkResetToken)", | |
208 | + notes = "Checks the password reset token and forwards user to 'Reset Password' page. " + | |
209 | + "If token is valid, returns '303 See Other' (redirect) response code with the correct address of 'Reset Password' page and same 'resetToken' specified in the URL parameters. " + | |
210 | + "If token is not valid, returns '409 Conflict'.") | |
179 | 211 | @RequestMapping(value = "/noauth/resetPassword", params = {"resetToken"}, method = RequestMethod.GET) |
180 | 212 | public ResponseEntity<String> checkResetToken( |
213 | + @ApiParam(value = "The reset token string.") | |
181 | 214 | @RequestParam(value = "resetToken") String resetToken) { |
182 | 215 | HttpHeaders headers = new HttpHeaders(); |
183 | 216 | HttpStatus responseStatus; |
... | ... | @@ -198,16 +231,24 @@ public class AuthController extends BaseController { |
198 | 231 | return new ResponseEntity<>(headers, responseStatus); |
199 | 232 | } |
200 | 233 | |
234 | + @ApiOperation(value = "Activate User", | |
235 | + notes = "Checks the activation token and updates corresponding user password in the database. " + | |
236 | + "Now the user may start using his password to login. " + | |
237 | + "The response already contains the [JWT](https://jwt.io) activation and refresh tokens, " + | |
238 | + "to simplify the user activation flow and avoid asking user to input password again after activation. " + | |
239 | + "If token is valid, returns the object that contains [JWT](https://jwt.io/) access and refresh tokens. " + | |
240 | + "If token is not valid, returns '404 Bad Request'.") | |
201 | 241 | @RequestMapping(value = "/noauth/activate", method = RequestMethod.POST) |
202 | 242 | @ResponseStatus(value = HttpStatus.OK) |
203 | 243 | @ResponseBody |
204 | - public JsonNode activateUser( | |
205 | - @RequestBody JsonNode activateRequest, | |
244 | + public JwtTokenPair activateUser( | |
245 | + @ApiParam(value = "Activate user request.") | |
246 | + @RequestBody ActivateUserRequest activateRequest, | |
206 | 247 | @RequestParam(required = false, defaultValue = "true") boolean sendActivationMail, |
207 | 248 | HttpServletRequest request) throws ThingsboardException { |
208 | 249 | try { |
209 | - String activateToken = activateRequest.get("activateToken").asText(); | |
210 | - String password = activateRequest.get("password").asText(); | |
250 | + String activateToken = activateRequest.getActivateToken(); | |
251 | + String password = activateRequest.getPassword(); | |
211 | 252 | systemSecurityService.validatePassword(TenantId.SYS_TENANT_ID, password, null); |
212 | 253 | String encodedPassword = passwordEncoder.encode(password); |
213 | 254 | UserCredentials credentials = userService.activateUserCredentials(TenantId.SYS_TENANT_ID, activateToken, encodedPassword); |
... | ... | @@ -232,25 +273,26 @@ public class AuthController extends BaseController { |
232 | 273 | JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); |
233 | 274 | JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); |
234 | 275 | |
235 | - ObjectMapper objectMapper = new ObjectMapper(); | |
236 | - ObjectNode tokenObject = objectMapper.createObjectNode(); | |
237 | - tokenObject.put("token", accessToken.getToken()); | |
238 | - tokenObject.put("refreshToken", refreshToken.getToken()); | |
239 | - return tokenObject; | |
276 | + return new JwtTokenPair(accessToken.getToken(), refreshToken.getToken()); | |
240 | 277 | } catch (Exception e) { |
241 | 278 | throw handleException(e); |
242 | 279 | } |
243 | 280 | } |
244 | 281 | |
282 | + @ApiOperation(value = "Reset password (resetPassword)", | |
283 | + notes = "Checks the password reset token and updates the password. " + | |
284 | + "If token is valid, returns the object that contains [JWT](https://jwt.io/) access and refresh tokens. " + | |
285 | + "If token is not valid, returns '404 Bad Request'.") | |
245 | 286 | @RequestMapping(value = "/noauth/resetPassword", method = RequestMethod.POST) |
246 | 287 | @ResponseStatus(value = HttpStatus.OK) |
247 | 288 | @ResponseBody |
248 | - public JsonNode resetPassword( | |
249 | - @RequestBody JsonNode resetPasswordRequest, | |
289 | + public JwtTokenPair resetPassword( | |
290 | + @ApiParam(value = "Reset password request.") | |
291 | + @RequestBody ResetPasswordRequest resetPasswordRequest, | |
250 | 292 | HttpServletRequest request) throws ThingsboardException { |
251 | 293 | try { |
252 | - String resetToken = resetPasswordRequest.get("resetToken").asText(); | |
253 | - String password = resetPasswordRequest.get("password").asText(); | |
294 | + String resetToken = resetPasswordRequest.getResetToken(); | |
295 | + String password = resetPasswordRequest.getPassword(); | |
254 | 296 | UserCredentials userCredentials = userService.findUserCredentialsByResetToken(TenantId.SYS_TENANT_ID, resetToken); |
255 | 297 | if (userCredentials != null) { |
256 | 298 | systemSecurityService.validatePassword(TenantId.SYS_TENANT_ID, password, userCredentials); |
... | ... | @@ -273,11 +315,7 @@ public class AuthController extends BaseController { |
273 | 315 | JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); |
274 | 316 | JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); |
275 | 317 | |
276 | - ObjectMapper objectMapper = new ObjectMapper(); | |
277 | - ObjectNode tokenObject = objectMapper.createObjectNode(); | |
278 | - tokenObject.put("token", accessToken.getToken()); | |
279 | - tokenObject.put("refreshToken", refreshToken.getToken()); | |
280 | - return tokenObject; | |
318 | + return new JwtTokenPair(accessToken.getToken(), refreshToken.getToken()); | |
281 | 319 | } else { |
282 | 320 | throw new ThingsboardException("Invalid reset token!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); |
283 | 321 | } | ... | ... |
... | ... | @@ -147,17 +147,15 @@ import java.util.Optional; |
147 | 147 | import java.util.Set; |
148 | 148 | import java.util.UUID; |
149 | 149 | |
150 | +import static org.thingsboard.server.controller.ControllerConstants.DEFAULT_PAGE_SIZE; | |
151 | +import static org.thingsboard.server.controller.ControllerConstants.INCORRECT_TENANT_ID; | |
150 | 152 | import static org.thingsboard.server.dao.service.Validator.validateId; |
151 | 153 | |
152 | 154 | @Slf4j |
153 | 155 | @TbCoreComponent |
154 | 156 | public abstract class BaseController { |
155 | 157 | |
156 | - public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; | |
157 | - protected static final String DEFAULT_DASHBOARD = "defaultDashboardId"; | |
158 | - protected static final String HOME_DASHBOARD = "homeDashboardId"; | |
159 | - | |
160 | - private static final int DEFAULT_PAGE_SIZE = 1000; | |
158 | + /*Swagger UI description*/ | |
161 | 159 | |
162 | 160 | private static final ObjectMapper json = new ObjectMapper(); |
163 | 161 | |
... | ... | @@ -897,7 +895,7 @@ public abstract class BaseController { |
897 | 895 | PageDataIterableByTenantIdEntityId<EdgeId> relatedEdgeIdsIterator = |
898 | 896 | new PageDataIterableByTenantIdEntityId<>(edgeService::findRelatedEdgeIdsByEntityId, tenantId, entityId, DEFAULT_PAGE_SIZE); |
899 | 897 | List<EdgeId> result = new ArrayList<>(); |
900 | - for(EdgeId edgeId : relatedEdgeIdsIterator) { | |
898 | + for (EdgeId edgeId : relatedEdgeIdsIterator) { | |
901 | 899 | result.add(edgeId); |
902 | 900 | } |
903 | 901 | return result; | ... | ... |
... | ... | @@ -15,6 +15,8 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.controller; |
17 | 17 | |
18 | +import io.swagger.annotations.ApiOperation; | |
19 | +import io.swagger.annotations.ApiParam; | |
18 | 20 | import org.apache.commons.lang3.StringUtils; |
19 | 21 | import org.springframework.security.access.prepost.PreAuthorize; |
20 | 22 | import org.springframework.web.bind.annotation.PathVariable; |
... | ... | @@ -33,15 +35,27 @@ import java.util.HashSet; |
33 | 35 | import java.util.List; |
34 | 36 | import java.util.Set; |
35 | 37 | |
38 | +import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH; | |
39 | + | |
36 | 40 | @RestController |
37 | 41 | @TbCoreComponent |
38 | 42 | @RequestMapping("/api") |
39 | 43 | public class ComponentDescriptorController extends BaseController { |
40 | 44 | |
45 | + private static final String COMPONENT_DESCRIPTOR_DEFINITION = "Each Component Descriptor represents configuration of specific rule node (e.g. 'Save Timeseries' or 'Send Email'.). " + | |
46 | + "The Component Descriptors are used by the rule chain Web UI to build the configuration forms for the rule nodes. " + | |
47 | + "The Component Descriptors are discovered at runtime by scanning the class path and searching for @RuleNode annotation. " + | |
48 | + "Once discovered, the up to date list of descriptors is persisted to the database."; | |
49 | + | |
50 | + @ApiOperation(value = "Get Component Descriptor (getComponentDescriptorByClazz)", | |
51 | + notes = "Gets the Component Descriptor object using class name from the path parameters. " + | |
52 | + COMPONENT_DESCRIPTOR_DEFINITION + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) | |
41 | 53 | @PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')") |
42 | 54 | @RequestMapping(value = "/component/{componentDescriptorClazz:.+}", method = RequestMethod.GET) |
43 | 55 | @ResponseBody |
44 | - public ComponentDescriptor getComponentDescriptorByClazz(@PathVariable("componentDescriptorClazz") String strComponentDescriptorClazz) throws ThingsboardException { | |
56 | + public ComponentDescriptor getComponentDescriptorByClazz( | |
57 | + @ApiParam(value = "Component Descriptor class name", required = true) | |
58 | + @PathVariable("componentDescriptorClazz") String strComponentDescriptorClazz) throws ThingsboardException { | |
45 | 59 | checkParameter("strComponentDescriptorClazz", strComponentDescriptorClazz); |
46 | 60 | try { |
47 | 61 | return checkComponentDescriptorByClazz(strComponentDescriptorClazz); |
... | ... | @@ -50,11 +64,17 @@ public class ComponentDescriptorController extends BaseController { |
50 | 64 | } |
51 | 65 | } |
52 | 66 | |
67 | + @ApiOperation(value = "Get Component Descriptors (getComponentDescriptorsByType)", | |
68 | + notes = "Gets the Component Descriptors using rule node type and optional rule chain type request parameters. " + | |
69 | + COMPONENT_DESCRIPTOR_DEFINITION + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) | |
53 | 70 | @PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')") |
54 | 71 | @RequestMapping(value = "/components/{componentType}", method = RequestMethod.GET) |
55 | 72 | @ResponseBody |
56 | - public List<ComponentDescriptor> getComponentDescriptorsByType(@PathVariable("componentType") String strComponentType, | |
57 | - @RequestParam(value = "ruleChainType", required = false) String strRuleChainType) throws ThingsboardException { | |
73 | + public List<ComponentDescriptor> getComponentDescriptorsByType( | |
74 | + @ApiParam(value = "Type of the Rule Node", allowableValues = "ENRICHMENT,FILTER,TRANSFORMATION,ACTION,EXTERNAL", required = true) | |
75 | + @PathVariable("componentType") String strComponentType, | |
76 | + @ApiParam(value = "Type of the Rule Chain", allowableValues = "CORE,EDGE") | |
77 | + @RequestParam(value = "ruleChainType", required = false) String strRuleChainType) throws ThingsboardException { | |
58 | 78 | checkParameter("componentType", strComponentType); |
59 | 79 | try { |
60 | 80 | return checkComponentDescriptorsByType(ComponentType.valueOf(strComponentType), getRuleChainType(strRuleChainType)); |
... | ... | @@ -63,11 +83,17 @@ public class ComponentDescriptorController extends BaseController { |
63 | 83 | } |
64 | 84 | } |
65 | 85 | |
86 | + @ApiOperation(value = "Get Component Descriptors (getComponentDescriptorsByTypes)", | |
87 | + notes = "Gets the Component Descriptors using coma separated list of rule node types and optional rule chain type request parameters. " + | |
88 | + COMPONENT_DESCRIPTOR_DEFINITION + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) | |
66 | 89 | @PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')") |
67 | 90 | @RequestMapping(value = "/components", params = {"componentTypes"}, method = RequestMethod.GET) |
68 | 91 | @ResponseBody |
69 | - public List<ComponentDescriptor> getComponentDescriptorsByTypes(@RequestParam("componentTypes") String[] strComponentTypes, | |
70 | - @RequestParam(value = "ruleChainType", required = false) String strRuleChainType) throws ThingsboardException { | |
92 | + public List<ComponentDescriptor> getComponentDescriptorsByTypes( | |
93 | + @ApiParam(value = "List of types of the Rule Nodes, (ENRICHMENT, FILTER, TRANSFORMATION, ACTION or EXTERNAL)", required = true) | |
94 | + @RequestParam("componentTypes") String[] strComponentTypes, | |
95 | + @ApiParam(value = "Type of the Rule Chain", allowableValues = "CORE,EDGE") | |
96 | + @RequestParam(value = "ruleChainType", required = false) String strRuleChainType) throws ThingsboardException { | |
71 | 97 | checkArrayParameter("componentTypes", strComponentTypes); |
72 | 98 | try { |
73 | 99 | Set<ComponentType> componentTypes = new HashSet<>(); | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.controller; | |
17 | + | |
18 | +public class ControllerConstants { | |
19 | + | |
20 | + | |
21 | + protected static final String NEW_LINE = "\n\n"; | |
22 | + protected static final int DEFAULT_PAGE_SIZE = 1000; | |
23 | + protected static final String ENTITY_TYPE = "entityType"; | |
24 | + protected static final String CUSTOMER_ID = "customerId"; | |
25 | + protected static final String TENANT_ID = "tenantId"; | |
26 | + protected static final String DEVICE_ID = "deviceId"; | |
27 | + protected static final String RPC_ID = "rpcId"; | |
28 | + protected static final String ENTITY_ID = "entityId"; | |
29 | + protected static final String PAGE_DATA_PARAMETERS = "You can specify parameters to filter the results. " + | |
30 | + "The result is wrapped with PageData object that allows you to iterate over result set using pagination. " + | |
31 | + "See the 'Model' tab of the Response Class for more details. "; | |
32 | + protected static final String DASHBOARD_ID_PARAM_DESCRIPTION = "A string value representing the device id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; | |
33 | + protected static final String RPC_ID_PARAM_DESCRIPTION = "A string value representing the rpc id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; | |
34 | + protected static final String DEVICE_ID_PARAM_DESCRIPTION = "A string value representing the device id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; | |
35 | + protected static final String DEVICE_PROFILE_ID_PARAM_DESCRIPTION = "A string value representing the device profile id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; | |
36 | + protected static final String TENANT_PROFILE_ID_PARAM_DESCRIPTION = "A string value representing the tenant profile id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; | |
37 | + protected static final String TENANT_ID_PARAM_DESCRIPTION = "A string value representing the tenant id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; | |
38 | + protected static final String EDGE_ID_PARAM_DESCRIPTION = "A string value representing the edge id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; | |
39 | + protected static final String CUSTOMER_ID_PARAM_DESCRIPTION = "A string value representing the customer id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; | |
40 | + protected static final String USER_ID_PARAM_DESCRIPTION = "A string value representing the user id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; | |
41 | + protected static final String ASSET_ID_PARAM_DESCRIPTION = "A string value representing the asset id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; | |
42 | + protected static final String ALARM_ID_PARAM_DESCRIPTION = "A string value representing the alarm id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; | |
43 | + protected static final String ENTITY_ID_PARAM_DESCRIPTION = "A string value representing the entity id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; | |
44 | + protected static final String OTA_PACKAGE_ID_PARAM_DESCRIPTION = "A string value representing the ota package id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; | |
45 | + protected static final String ENTITY_TYPE_PARAM_DESCRIPTION = "A string value representing the entity type. For example, 'DEVICE'"; | |
46 | + protected static final String RULE_CHAIN_ID_PARAM_DESCRIPTION = "A string value representing the rule chain id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; | |
47 | + protected static final String WIDGET_BUNDLE_ID_PARAM_DESCRIPTION = "A string value representing the widget bundle id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; | |
48 | + protected static final String WIDGET_TYPE_ID_PARAM_DESCRIPTION = "A string value representing the widget type id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; | |
49 | + protected static final String RESOURCE_ID_PARAM_DESCRIPTION = "A string value representing the resource id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; | |
50 | + protected static final String SYSTEM_AUTHORITY_PARAGRAPH = "\n\nAvailable for users with 'SYS_ADMIN' authority."; | |
51 | + protected static final String SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH = "\n\nAvailable for users with 'SYS_ADMIN' or 'TENANT_ADMIN' authority."; | |
52 | + protected static final String TENANT_AUTHORITY_PARAGRAPH = "\n\nAvailable for users with 'TENANT_ADMIN' authority."; | |
53 | + protected static final String TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH = "\n\nAvailable for users with 'TENANT_ADMIN' or 'CUSTOMER_USER' authority."; | |
54 | + protected static final String CUSTOMER_AUTHORITY_PARAGRAPH = "\n\nAvailable for users with 'CUSTOMER_USER' authority."; | |
55 | + protected static final String AVAILABLE_FOR_ANY_AUTHORIZED_USER = "\n\nAvailable for any authorized user. "; | |
56 | + protected static final String PAGE_SIZE_DESCRIPTION = "Maximum amount of entities in a one page"; | |
57 | + protected static final String PAGE_NUMBER_DESCRIPTION = "Sequence number of page starting from 0"; | |
58 | + protected static final String DEVICE_TYPE_DESCRIPTION = "Device type as the name of the device profile"; | |
59 | + protected static final String ASSET_TYPE_DESCRIPTION = "Asset type"; | |
60 | + protected static final String EDGE_TYPE_DESCRIPTION = "A string value representing the edge type. For example, 'default'"; | |
61 | + protected static final String RULE_CHAIN_TYPE_DESCRIPTION = "Rule chain type (CORE or EDGE)"; | |
62 | + protected static final String ASSET_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the asset name."; | |
63 | + protected static final String DASHBOARD_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the dashboard title."; | |
64 | + protected static final String WIDGET_BUNDLE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the widget bundle title."; | |
65 | + protected static final String RPC_TEXT_SEARCH_DESCRIPTION = "Not implemented. Leave empty."; | |
66 | + protected static final String DEVICE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the device name."; | |
67 | + protected static final String USER_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the user email."; | |
68 | + protected static final String TENANT_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the tenant name."; | |
69 | + protected static final String TENANT_PROFILE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the tenant profile name."; | |
70 | + protected static final String RULE_CHAIN_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the rule chain name."; | |
71 | + protected static final String DEVICE_PROFILE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the device profile name."; | |
72 | + protected static final String CUSTOMER_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the customer title."; | |
73 | + protected static final String EDGE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the edge name."; | |
74 | + protected static final String EVENT_TEXT_SEARCH_DESCRIPTION = "The value is not used in searching."; | |
75 | + protected static final String AUDIT_LOG_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on one of the next properties: entityType, entityName, userName, actionType, actionStatus."; | |
76 | + protected static final String SORT_PROPERTY_DESCRIPTION = "Property of entity to sort by"; | |
77 | + protected static final String DASHBOARD_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, title"; | |
78 | + protected static final String CUSTOMER_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, title, email, country, city"; | |
79 | + protected static final String RPC_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, expirationTime, request, response"; | |
80 | + protected static final String DEVICE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, deviceProfileName, label, customerTitle"; | |
81 | + protected static final String USER_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, firstName, lastName, email"; | |
82 | + protected static final String TENANT_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, title, email, country, state, city, address, address2, zip, phone, email"; | |
83 | + protected static final String TENANT_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, description, isDefault"; | |
84 | + protected static final String TENANT_PROFILE_INFO_SORT_PROPERTY_ALLOWABLE_VALUES = "id, name"; | |
85 | + protected static final String TENANT_INFO_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, tenantProfileName, title, email, country, state, city, address, address2, zip, phone, email"; | |
86 | + protected static final String DEVICE_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, type, transportType, description, isDefault"; | |
87 | + protected static final String ASSET_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, type, label, customerTitle"; | |
88 | + protected static final String ALARM_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, startTs, endTs, type, ackTs, clearTs, severity, status"; | |
89 | + protected static final String EVENT_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, id"; | |
90 | + protected static final String EDGE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, type, label, customerTitle"; | |
91 | + protected static final String RULE_CHAIN_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, root"; | |
92 | + protected static final String WIDGET_BUNDLE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, title, tenantId"; | |
93 | + protected static final String AUDIT_LOG_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, entityType, entityName, userName, actionType, actionStatus"; | |
94 | + protected static final String SORT_ORDER_DESCRIPTION = "Sort order. ASC (ASCENDING) or DESC (DESCENDING)"; | |
95 | + protected static final String SORT_ORDER_ALLOWABLE_VALUES = "ASC, DESC"; | |
96 | + protected static final String RPC_STATUS_ALLOWABLE_VALUES = "QUEUED, SENT, DELIVERED, SUCCESSFUL, TIMEOUT, EXPIRED, FAILED"; | |
97 | + protected static final String RULE_CHAIN_TYPES_ALLOWABLE_VALUES = "CORE, EDGE"; | |
98 | + protected static final String TRANSPORT_TYPE_ALLOWABLE_VALUES = "DEFAULT, MQTT, COAP, LWM2M, SNMP"; | |
99 | + protected static final String DEVICE_INFO_DESCRIPTION = "Device Info is an extension of the default Device object that contains information about the assigned customer name and device profile name. "; | |
100 | + protected static final String ASSET_INFO_DESCRIPTION = "Asset Info is an extension of the default Asset object that contains information about the assigned customer name. "; | |
101 | + protected static final String ALARM_INFO_DESCRIPTION = "Alarm Info is an extension of the default Alarm object that also contains name of the alarm originator."; | |
102 | + protected static final String RELATION_INFO_DESCRIPTION = "Relation Info is an extension of the default Relation object that contains information about the 'from' and 'to' entity names. "; | |
103 | + protected static final String EDGE_INFO_DESCRIPTION = "Edge Info is an extension of the default Edge object that contains information about the assigned customer name. "; | |
104 | + protected static final String DEVICE_PROFILE_INFO_DESCRIPTION = "Device Profile Info is a lightweight object that includes main information about Device Profile excluding the heavyweight configuration object. "; | |
105 | + protected static final String QUEUE_SERVICE_TYPE_DESCRIPTION = "Service type (implemented only for the TB-RULE-ENGINE)"; | |
106 | + protected static final String QUEUE_SERVICE_TYPE_ALLOWABLE_VALUES = "TB-RULE-ENGINE, TB-CORE, TB-TRANSPORT, JS-EXECUTOR"; | |
107 | + protected static final String OTA_PACKAGE_INFO_DESCRIPTION = "OTA Package Info is a lightweight object that includes main information about the OTA Package excluding the heavyweight data. "; | |
108 | + protected static final String OTA_PACKAGE_DESCRIPTION = "OTA Package is a heavyweight object that includes main information about the OTA Package and also data. "; | |
109 | + protected static final String OTA_PACKAGE_CHECKSUM_ALGORITHM_ALLOWABLE_VALUES = "MD5, SHA256, SHA384, SHA512, CRC32, MURMUR3_32, MURMUR3_128"; | |
110 | + protected static final String OTA_PACKAGE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the ota package title."; | |
111 | + protected static final String OTA_PACKAGE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, type, title, version, tag, url, fileName, dataSize, checksum"; | |
112 | + protected static final String RESOURCE_INFO_DESCRIPTION = "Resource Info is a lightweight object that includes main information about the Resource excluding the heavyweight data. "; | |
113 | + protected static final String RESOURCE_DESCRIPTION = "Resource is a heavyweight object that includes main information about the Resource and also data. "; | |
114 | + | |
115 | + protected static final String RESOURCE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the resource title."; | |
116 | + protected static final String RESOURCE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, title, resourceType, tenantId"; | |
117 | + protected static final String LWM2M_OBJECT_DESCRIPTION = "LwM2M Object is a object that includes information about the LwM2M model which can be used in transport configuration for the LwM2M device profile. "; | |
118 | + protected static final String LWM2M_OBJECT_SORT_PROPERTY_ALLOWABLE_VALUES = "id, name"; | |
119 | + | |
120 | + protected static final String DEVICE_NAME_DESCRIPTION = "A string value representing the Device name."; | |
121 | + protected static final String ASSET_NAME_DESCRIPTION = "A string value representing the Asset name."; | |
122 | + | |
123 | + protected static final String EVENT_START_TIME_DESCRIPTION = "Timestamp. Events with creation time before it won't be queried."; | |
124 | + protected static final String EVENT_END_TIME_DESCRIPTION = "Timestamp. Events with creation time after it won't be queried."; | |
125 | + | |
126 | + protected static final String EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION = "Unassignment works in async way - first, 'unassign' notification event pushed to edge queue on platform. "; | |
127 | + protected static final String EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION = "(Edge will receive this instantly, if it's currently connected, or once it's going to be connected to platform)"; | |
128 | + protected static final String EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION = "Assignment works in async way - first, notification event pushed to edge service queue on platform. "; | |
129 | + protected static final String EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION = "(Edge will receive this instantly, if it's currently connected, or once it's going to be connected to platform)"; | |
130 | + | |
131 | + protected static final String MARKDOWN_CODE_BLOCK_START = "```json\n"; | |
132 | + protected static final String MARKDOWN_CODE_BLOCK_END = "\n```"; | |
133 | + protected static final String EVENT_ERROR_FILTER_OBJ = MARKDOWN_CODE_BLOCK_START + | |
134 | + "{\n" + | |
135 | + " \"eventType\":\"ERROR\",\n" + | |
136 | + " \"server\":\"ip-172-31-24-152\",\n" + | |
137 | + " \"method\":\"onClusterEventMsg\",\n" + | |
138 | + " \"errorStr\":\"Error Message\"\n" + | |
139 | + "}" + | |
140 | + MARKDOWN_CODE_BLOCK_END; | |
141 | + protected static final String EVENT_LC_EVENT_FILTER_OBJ = MARKDOWN_CODE_BLOCK_START + | |
142 | + "{\n" + | |
143 | + " \"eventType\":\"LC_EVENT\",\n" + | |
144 | + " \"server\":\"ip-172-31-24-152\",\n" + | |
145 | + " \"event\":\"STARTED\",\n" + | |
146 | + " \"status\":\"Success\",\n" + | |
147 | + " \"errorStr\":\"Error Message\"\n" + | |
148 | + "}" + | |
149 | + MARKDOWN_CODE_BLOCK_END; | |
150 | + protected static final String EVENT_STATS_FILTER_OBJ = MARKDOWN_CODE_BLOCK_START + | |
151 | + "{\n" + | |
152 | + " \"eventType\":\"STATS\",\n" + | |
153 | + " \"server\":\"ip-172-31-24-152\",\n" + | |
154 | + " \"messagesProcessed\":10,\n" + | |
155 | + " \"errorsOccurred\":5\n" + | |
156 | + "}" + | |
157 | + MARKDOWN_CODE_BLOCK_END; | |
158 | + protected static final String DEBUG_FILTER_OBJ = | |
159 | + " \"msgDirectionType\":\"IN\",\n" + | |
160 | + " \"server\":\"ip-172-31-24-152\",\n" + | |
161 | + " \"dataSearch\":\"humidity\",\n" + | |
162 | + " \"metadataSearch\":\"deviceName\",\n" + | |
163 | + " \"entityName\":\"DEVICE\",\n" + | |
164 | + " \"relationType\":\"Success\",\n" + | |
165 | + " \"entityId\":\"de9d54a0-2b7a-11ec-a3cc-23386423d98f\",\n" + | |
166 | + " \"msgType\":\"POST_TELEMETRY_REQUEST\",\n" + | |
167 | + " \"isError\":\"false\",\n" + | |
168 | + " \"errorStr\":\"Error Message\"\n" + | |
169 | + "}"; | |
170 | + protected static final String EVENT_DEBUG_RULE_NODE_FILTER_OBJ = MARKDOWN_CODE_BLOCK_START + "{\n" + | |
171 | + " \"eventType\":\"DEBUG_RULE_NODE\",\n" + DEBUG_FILTER_OBJ + MARKDOWN_CODE_BLOCK_END; | |
172 | + protected static final String EVENT_DEBUG_RULE_CHAIN_FILTER_OBJ = MARKDOWN_CODE_BLOCK_START + "{\n" + | |
173 | + " \"eventType\":\"DEBUG_RULE_CHAIN\",\n" + DEBUG_FILTER_OBJ + MARKDOWN_CODE_BLOCK_END; | |
174 | + | |
175 | + protected static final String FILTER_VALUE_TYPE = NEW_LINE + "## Value Type and Operations" + NEW_LINE + | |
176 | + "Provides a hint about the data type of the entity field that is defined in the filter key. " + | |
177 | + "The value type impacts the list of possible operations that you may use in the corresponding predicate. For example, you may use 'STARTS_WITH' or 'END_WITH', but you can't use 'GREATER_OR_EQUAL' for string values." + | |
178 | + "The following filter value types and corresponding predicate operations are supported: " + NEW_LINE + | |
179 | + " * 'STRING' - used to filter any 'String' or 'JSON' values. Operations: EQUAL, NOT_EQUAL, STARTS_WITH, ENDS_WITH, CONTAINS, NOT_CONTAINS; \n" + | |
180 | + " * 'NUMERIC' - used for 'Long' and 'Double' values. Operations: EQUAL, NOT_EQUAL, GREATER, LESS, GREATER_OR_EQUAL, LESS_OR_EQUAL; \n" + | |
181 | + " * 'BOOLEAN' - used for boolean values. Operations: EQUAL, NOT_EQUAL;\n" + | |
182 | + " * 'DATE_TIME' - similar to numeric, transforms value to milliseconds since epoch. Operations: EQUAL, NOT_EQUAL, GREATER, LESS, GREATER_OR_EQUAL, LESS_OR_EQUAL; \n"; | |
183 | + | |
184 | + protected static final String DEVICE_PROFILE_ALARM_SCHEDULE_SPECIFIC_TIME_EXAMPLE = MARKDOWN_CODE_BLOCK_START + | |
185 | + "{\n" + | |
186 | + " \"schedule\":{\n" + | |
187 | + " \"type\":\"SPECIFIC_TIME\",\n" + | |
188 | + " \"endsOn\":64800000,\n" + | |
189 | + " \"startsOn\":43200000,\n" + | |
190 | + " \"timezone\":\"Europe/Kiev\",\n" + | |
191 | + " \"daysOfWeek\":[\n" + | |
192 | + " 1,\n" + | |
193 | + " 3,\n" + | |
194 | + " 5\n" + | |
195 | + " ]\n" + | |
196 | + " }\n" + | |
197 | + "}" + | |
198 | + MARKDOWN_CODE_BLOCK_END; | |
199 | + protected static final String DEVICE_PROFILE_ALARM_SCHEDULE_CUSTOM_EXAMPLE = MARKDOWN_CODE_BLOCK_START + | |
200 | + "{\n" + | |
201 | + " \"schedule\":{\n" + | |
202 | + " \"type\":\"CUSTOM\",\n" + | |
203 | + " \"items\":[\n" + | |
204 | + " {\n" + | |
205 | + " \"endsOn\":0,\n" + | |
206 | + " \"enabled\":false,\n" + | |
207 | + " \"startsOn\":0,\n" + | |
208 | + " \"dayOfWeek\":1\n" + | |
209 | + " },\n" + | |
210 | + " {\n" + | |
211 | + " \"endsOn\":64800000,\n" + | |
212 | + " \"enabled\":true,\n" + | |
213 | + " \"startsOn\":43200000,\n" + | |
214 | + " \"dayOfWeek\":2\n" + | |
215 | + " },\n" + | |
216 | + " {\n" + | |
217 | + " \"endsOn\":0,\n" + | |
218 | + " \"enabled\":false,\n" + | |
219 | + " \"startsOn\":0,\n" + | |
220 | + " \"dayOfWeek\":3\n" + | |
221 | + " },\n" + | |
222 | + " {\n" + | |
223 | + " \"endsOn\":57600000,\n" + | |
224 | + " \"enabled\":true,\n" + | |
225 | + " \"startsOn\":36000000,\n" + | |
226 | + " \"dayOfWeek\":4\n" + | |
227 | + " },\n" + | |
228 | + " {\n" + | |
229 | + " \"endsOn\":0,\n" + | |
230 | + " \"enabled\":false,\n" + | |
231 | + " \"startsOn\":0,\n" + | |
232 | + " \"dayOfWeek\":5\n" + | |
233 | + " },\n" + | |
234 | + " {\n" + | |
235 | + " \"endsOn\":0,\n" + | |
236 | + " \"enabled\":false,\n" + | |
237 | + " \"startsOn\":0,\n" + | |
238 | + " \"dayOfWeek\":6\n" + | |
239 | + " },\n" + | |
240 | + " {\n" + | |
241 | + " \"endsOn\":0,\n" + | |
242 | + " \"enabled\":false,\n" + | |
243 | + " \"startsOn\":0,\n" + | |
244 | + " \"dayOfWeek\":7\n" + | |
245 | + " }\n" + | |
246 | + " ],\n" + | |
247 | + " \"timezone\":\"Europe/Kiev\"\n" + | |
248 | + " }\n" + | |
249 | + "}" + | |
250 | + MARKDOWN_CODE_BLOCK_END; | |
251 | + protected static final String DEVICE_PROFILE_ALARM_SCHEDULE_ALWAYS_EXAMPLE = MARKDOWN_CODE_BLOCK_START + "\"schedule\": null" + MARKDOWN_CODE_BLOCK_END; | |
252 | + | |
253 | + protected static final String DEVICE_PROFILE_ALARM_CONDITION_REPEATING_EXAMPLE = MARKDOWN_CODE_BLOCK_START + | |
254 | + "{\n" + | |
255 | + " \"spec\":{\n" + | |
256 | + " \"type\":\"REPEATING\",\n" + | |
257 | + " \"predicate\":{\n" + | |
258 | + " \"userValue\":null,\n" + | |
259 | + " \"defaultValue\":5,\n" + | |
260 | + " \"dynamicValue\":{\n" + | |
261 | + " \"inherit\":true,\n" + | |
262 | + " \"sourceType\":\"CURRENT_DEVICE\",\n" + | |
263 | + " \"sourceAttribute\":\"tempAttr\"\n" + | |
264 | + " }\n" + | |
265 | + " }\n" + | |
266 | + " }\n" + | |
267 | + "}" + | |
268 | + MARKDOWN_CODE_BLOCK_END; | |
269 | + protected static final String DEVICE_PROFILE_ALARM_CONDITION_DURATION_EXAMPLE = MARKDOWN_CODE_BLOCK_START + | |
270 | + "{\n" + | |
271 | + " \"spec\":{\n" + | |
272 | + " \"type\":\"DURATION\",\n" + | |
273 | + " \"unit\":\"MINUTES\",\n" + | |
274 | + " \"predicate\":{\n" + | |
275 | + " \"userValue\":null,\n" + | |
276 | + " \"defaultValue\":30,\n" + | |
277 | + " \"dynamicValue\":null\n" + | |
278 | + " }\n" + | |
279 | + " }\n" + | |
280 | + "}" + | |
281 | + MARKDOWN_CODE_BLOCK_END; | |
282 | + | |
283 | + protected static final String RELATION_TYPE_PARAM_DESCRIPTION = "A string value representing relation type between entities. For example, 'Contains', 'Manages'. It can be any string value."; | |
284 | + protected static final String RELATION_TYPE_GROUP_PARAM_DESCRIPTION = "A string value representing relation type group. For example, 'COMMON'"; | |
285 | + | |
286 | + public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; | |
287 | + protected static final String DEFAULT_DASHBOARD = "defaultDashboardId"; | |
288 | + protected static final String HOME_DASHBOARD = "homeDashboardId"; | |
289 | + | |
290 | + protected static final String SINGLE_ENTITY = "\n\n## Single Entity\n\n" + | |
291 | + "Allows to filter only one entity based on the id. For example, this entity filter selects certain device:\n\n" + | |
292 | + MARKDOWN_CODE_BLOCK_START + | |
293 | + "{\n" + | |
294 | + " \"type\": \"singleEntity\",\n" + | |
295 | + " \"singleEntity\": {\n" + | |
296 | + " \"id\": \"d521edb0-2a7a-11ec-94eb-213c95f54092\",\n" + | |
297 | + " \"entityType\": \"DEVICE\"\n" + | |
298 | + " }\n" + | |
299 | + "}" + | |
300 | + MARKDOWN_CODE_BLOCK_END + | |
301 | + ""; | |
302 | + | |
303 | + protected static final String ENTITY_LIST = "\n\n## Entity List Filter\n\n" + | |
304 | + "Allows to filter entities of the same type using their ids. For example, this entity filter selects two devices:\n\n" + | |
305 | + MARKDOWN_CODE_BLOCK_START + | |
306 | + "{\n" + | |
307 | + " \"type\": \"entityList\",\n" + | |
308 | + " \"entityType\": \"DEVICE\",\n" + | |
309 | + " \"entityList\": [\n" + | |
310 | + " \"e6501f30-2a7a-11ec-94eb-213c95f54092\",\n" + | |
311 | + " \"e6657bf0-2a7a-11ec-94eb-213c95f54092\"\n" + | |
312 | + " ]\n" + | |
313 | + "}" + | |
314 | + MARKDOWN_CODE_BLOCK_END + | |
315 | + ""; | |
316 | + | |
317 | + protected static final String ENTITY_NAME = "\n\n## Entity Name Filter\n\n" + | |
318 | + "Allows to filter entities of the same type using the **'starts with'** expression over entity name. " + | |
319 | + "For example, this entity filter selects all devices which name starts with 'Air Quality':\n\n" + | |
320 | + MARKDOWN_CODE_BLOCK_START + | |
321 | + "{\n" + | |
322 | + " \"type\": \"entityName\",\n" + | |
323 | + " \"entityType\": \"DEVICE\",\n" + | |
324 | + " \"entityNameFilter\": \"Air Quality\"\n" + | |
325 | + "}" + | |
326 | + MARKDOWN_CODE_BLOCK_END + | |
327 | + ""; | |
328 | + | |
329 | + protected static final String ENTITY_TYPE_FILTER = "\n\n## Entity Type Filter\n\n" + | |
330 | + "Allows to filter entities based on their type (CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, etc)" + | |
331 | + "For example, this entity filter selects all tenant customers:\n\n" + | |
332 | + MARKDOWN_CODE_BLOCK_START + | |
333 | + "{\n" + | |
334 | + " \"type\": \"entityType\",\n" + | |
335 | + " \"entityType\": \"CUSTOMER\"\n" + | |
336 | + "}" + | |
337 | + MARKDOWN_CODE_BLOCK_END + | |
338 | + ""; | |
339 | + | |
340 | + protected static final String ASSET_TYPE = "\n\n## Asset Type Filter\n\n" + | |
341 | + "Allows to filter assets based on their type and the **'starts with'** expression over their name. " + | |
342 | + "For example, this entity filter selects all 'charging station' assets which name starts with 'Tesla':\n\n" + | |
343 | + MARKDOWN_CODE_BLOCK_START + | |
344 | + "{\n" + | |
345 | + " \"type\": \"assetType\",\n" + | |
346 | + " \"assetType\": \"charging station\",\n" + | |
347 | + " \"assetNameFilter\": \"Tesla\"\n" + | |
348 | + "}" + | |
349 | + MARKDOWN_CODE_BLOCK_END + | |
350 | + ""; | |
351 | + | |
352 | + protected static final String DEVICE_TYPE = "\n\n## Device Type Filter\n\n" + | |
353 | + "Allows to filter devices based on their type and the **'starts with'** expression over their name. " + | |
354 | + "For example, this entity filter selects all 'Temperature Sensor' devices which name starts with 'ABC':\n\n" + | |
355 | + MARKDOWN_CODE_BLOCK_START + | |
356 | + "{\n" + | |
357 | + " \"type\": \"deviceType\",\n" + | |
358 | + " \"deviceType\": \"Temperature Sensor\",\n" + | |
359 | + " \"deviceNameFilter\": \"ABC\"\n" + | |
360 | + "}" + | |
361 | + MARKDOWN_CODE_BLOCK_END + | |
362 | + ""; | |
363 | + | |
364 | + protected static final String EDGE_TYPE = "\n\n## Edge Type Filter\n\n" + | |
365 | + "Allows to filter edge instances based on their type and the **'starts with'** expression over their name. " + | |
366 | + "For example, this entity filter selects all 'Factory' edge instances which name starts with 'Nevada':\n\n" + | |
367 | + MARKDOWN_CODE_BLOCK_START + | |
368 | + "{\n" + | |
369 | + " \"type\": \"edgeType\",\n" + | |
370 | + " \"edgeType\": \"Factory\",\n" + | |
371 | + " \"edgeNameFilter\": \"Nevada\"\n" + | |
372 | + "}" + | |
373 | + MARKDOWN_CODE_BLOCK_END + | |
374 | + ""; | |
375 | + | |
376 | + protected static final String ENTITY_VIEW_TYPE = "\n\n## Entity View Filter\n\n" + | |
377 | + "Allows to filter entity views based on their type and the **'starts with'** expression over their name. " + | |
378 | + "For example, this entity filter selects all 'Concrete Mixer' entity views which name starts with 'CAT':\n\n" + | |
379 | + MARKDOWN_CODE_BLOCK_START + | |
380 | + "{\n" + | |
381 | + " \"type\": \"entityViewType\",\n" + | |
382 | + " \"entityViewType\": \"Concrete Mixer\",\n" + | |
383 | + " \"entityViewNameFilter\": \"CAT\"\n" + | |
384 | + "}" + | |
385 | + MARKDOWN_CODE_BLOCK_END + | |
386 | + ""; | |
387 | + | |
388 | + protected static final String API_USAGE = "\n\n## Api Usage Filter\n\n" + | |
389 | + "Allows to query for Api Usage based on optional customer id. If the customer id is not set, returns current tenant API usage." + | |
390 | + "For example, this entity filter selects the 'Api Usage' entity for customer with id 'e6501f30-2a7a-11ec-94eb-213c95f54092':\n\n" + | |
391 | + MARKDOWN_CODE_BLOCK_START + | |
392 | + "{\n" + | |
393 | + " \"type\": \"apiUsageState\",\n" + | |
394 | + " \"customerId\": {\n" + | |
395 | + " \"id\": \"d521edb0-2a7a-11ec-94eb-213c95f54092\",\n" + | |
396 | + " \"entityType\": \"CUSTOMER\"\n" + | |
397 | + " }\n" + | |
398 | + "}" + | |
399 | + MARKDOWN_CODE_BLOCK_END + | |
400 | + ""; | |
401 | + | |
402 | + protected static final String MAX_LEVEL_DESCRIPTION = "Possible direction values are 'TO' and 'FROM'. The 'maxLevel' defines how many relation levels should the query search 'recursively'. "; | |
403 | + protected static final String FETCH_LAST_LEVEL_ONLY_DESCRIPTION = "Assuming the 'maxLevel' is > 1, the 'fetchLastLevelOnly' defines either to return all related entities or only entities that are on the last level of relations. "; | |
404 | + | |
405 | + protected static final String RELATIONS_QUERY_FILTER = "\n\n## Relations Query Filter\n\n" + | |
406 | + "Allows to filter entities that are related to the provided root entity. " + | |
407 | + MAX_LEVEL_DESCRIPTION + | |
408 | + FETCH_LAST_LEVEL_ONLY_DESCRIPTION + | |
409 | + "The 'filter' object allows you to define the relation type and set of acceptable entity types to search for. " + | |
410 | + "The relation query calculates all related entities, even if they are filtered using different relation types, and then extracts only those who match the 'filters'.\n\n" + | |
411 | + "For example, this entity filter selects all devices and assets which are related to the asset with id 'e51de0c0-2a7a-11ec-94eb-213c95f54092':\n\n" + | |
412 | + MARKDOWN_CODE_BLOCK_START + | |
413 | + "{\n" + | |
414 | + " \"type\": \"relationsQuery\",\n" + | |
415 | + " \"rootEntity\": {\n" + | |
416 | + " \"entityType\": \"ASSET\",\n" + | |
417 | + " \"id\": \"e51de0c0-2a7a-11ec-94eb-213c95f54092\"\n" + | |
418 | + " },\n" + | |
419 | + " \"direction\": \"FROM\",\n" + | |
420 | + " \"maxLevel\": 1,\n" + | |
421 | + " \"fetchLastLevelOnly\": false,\n" + | |
422 | + " \"filters\": [\n" + | |
423 | + " {\n" + | |
424 | + " \"relationType\": \"Contains\",\n" + | |
425 | + " \"entityTypes\": [\n" + | |
426 | + " \"DEVICE\",\n" + | |
427 | + " \"ASSET\"\n" + | |
428 | + " ]\n" + | |
429 | + " }\n" + | |
430 | + " ]\n" + | |
431 | + "}" + | |
432 | + MARKDOWN_CODE_BLOCK_END + | |
433 | + ""; | |
434 | + | |
435 | + | |
436 | + protected static final String ASSET_QUERY_FILTER = "\n\n## Asset Search Query\n\n" + | |
437 | + "Allows to filter assets that are related to the provided root entity. Filters related assets based on the relation type and set of asset types. " + | |
438 | + MAX_LEVEL_DESCRIPTION + | |
439 | + FETCH_LAST_LEVEL_ONLY_DESCRIPTION + | |
440 | + "The 'relationType' defines the type of the relation to search for. " + | |
441 | + "The 'assetTypes' defines the type of the asset to search for. " + | |
442 | + "The relation query calculates all related entities, even if they are filtered using different relation types, and then extracts only assets that match 'relationType' and 'assetTypes' conditions.\n\n" + | |
443 | + "For example, this entity filter selects 'charging station' assets which are related to the asset with id 'e51de0c0-2a7a-11ec-94eb-213c95f54092' using 'Contains' relation:\n\n" + | |
444 | + MARKDOWN_CODE_BLOCK_START + | |
445 | + "{\n" + | |
446 | + " \"type\": \"assetSearchQuery\",\n" + | |
447 | + " \"rootEntity\": {\n" + | |
448 | + " \"entityType\": \"ASSET\",\n" + | |
449 | + " \"id\": \"e51de0c0-2a7a-11ec-94eb-213c95f54092\"\n" + | |
450 | + " },\n" + | |
451 | + " \"direction\": \"FROM\",\n" + | |
452 | + " \"maxLevel\": 1,\n" + | |
453 | + " \"fetchLastLevelOnly\": false,\n" + | |
454 | + " \"relationType\": \"Contains\",\n" + | |
455 | + " \"assetTypes\": [\n" + | |
456 | + " \"charging station\"\n" + | |
457 | + " ]\n" + | |
458 | + "}" + | |
459 | + MARKDOWN_CODE_BLOCK_END + | |
460 | + ""; | |
461 | + | |
462 | + protected static final String DEVICE_QUERY_FILTER = "\n\n## Device Search Query\n\n" + | |
463 | + "Allows to filter devices that are related to the provided root entity. Filters related devices based on the relation type and set of device types. " + | |
464 | + MAX_LEVEL_DESCRIPTION + | |
465 | + FETCH_LAST_LEVEL_ONLY_DESCRIPTION + | |
466 | + "The 'relationType' defines the type of the relation to search for. " + | |
467 | + "The 'deviceTypes' defines the type of the device to search for. " + | |
468 | + "The relation query calculates all related entities, even if they are filtered using different relation types, and then extracts only devices that match 'relationType' and 'deviceTypes' conditions.\n\n" + | |
469 | + "For example, this entity filter selects 'Charging port' and 'Air Quality Sensor' devices which are related to the asset with id 'e52b0020-2a7a-11ec-94eb-213c95f54092' using 'Contains' relation:\n\n" + | |
470 | + MARKDOWN_CODE_BLOCK_START + | |
471 | + "{\n" + | |
472 | + " \"type\": \"deviceSearchQuery\",\n" + | |
473 | + " \"rootEntity\": {\n" + | |
474 | + " \"entityType\": \"ASSET\",\n" + | |
475 | + " \"id\": \"e52b0020-2a7a-11ec-94eb-213c95f54092\"\n" + | |
476 | + " },\n" + | |
477 | + " \"direction\": \"FROM\",\n" + | |
478 | + " \"maxLevel\": 2,\n" + | |
479 | + " \"fetchLastLevelOnly\": true,\n" + | |
480 | + " \"relationType\": \"Contains\",\n" + | |
481 | + " \"deviceTypes\": [\n" + | |
482 | + " \"Air Quality Sensor\",\n" + | |
483 | + " \"Charging port\"\n" + | |
484 | + " ]\n" + | |
485 | + "}" + | |
486 | + MARKDOWN_CODE_BLOCK_END + | |
487 | + ""; | |
488 | + | |
489 | + protected static final String EV_QUERY_FILTER = "\n\n## Entity View Query\n\n" + | |
490 | + "Allows to filter entity views that are related to the provided root entity. Filters related entity views based on the relation type and set of entity view types. " + | |
491 | + MAX_LEVEL_DESCRIPTION + | |
492 | + FETCH_LAST_LEVEL_ONLY_DESCRIPTION + | |
493 | + "The 'relationType' defines the type of the relation to search for. " + | |
494 | + "The 'entityViewTypes' defines the type of the entity view to search for. " + | |
495 | + "The relation query calculates all related entities, even if they are filtered using different relation types, and then extracts only devices that match 'relationType' and 'deviceTypes' conditions.\n\n" + | |
496 | + "For example, this entity filter selects 'Concrete mixer' entity views which are related to the asset with id 'e52b0020-2a7a-11ec-94eb-213c95f54092' using 'Contains' relation:\n\n" + | |
497 | + MARKDOWN_CODE_BLOCK_START + | |
498 | + "{\n" + | |
499 | + " \"type\": \"entityViewSearchQuery\",\n" + | |
500 | + " \"rootEntity\": {\n" + | |
501 | + " \"entityType\": \"ASSET\",\n" + | |
502 | + " \"id\": \"e52b0020-2a7a-11ec-94eb-213c95f54092\"\n" + | |
503 | + " },\n" + | |
504 | + " \"direction\": \"FROM\",\n" + | |
505 | + " \"maxLevel\": 1,\n" + | |
506 | + " \"fetchLastLevelOnly\": false,\n" + | |
507 | + " \"relationType\": \"Contains\",\n" + | |
508 | + " \"entityViewTypes\": [\n" + | |
509 | + " \"Concrete mixer\"\n" + | |
510 | + " ]\n" + | |
511 | + "}" + | |
512 | + MARKDOWN_CODE_BLOCK_END + | |
513 | + ""; | |
514 | + | |
515 | + protected static final String EDGE_QUERY_FILTER = "\n\n## Edge Search Query\n\n" + | |
516 | + "Allows to filter edge instances that are related to the provided root entity. Filters related edge instances based on the relation type and set of edge types. " + | |
517 | + MAX_LEVEL_DESCRIPTION + | |
518 | + FETCH_LAST_LEVEL_ONLY_DESCRIPTION + | |
519 | + "The 'relationType' defines the type of the relation to search for. " + | |
520 | + "The 'deviceTypes' defines the type of the device to search for. " + | |
521 | + "The relation query calculates all related entities, even if they are filtered using different relation types, and then extracts only devices that match 'relationType' and 'deviceTypes' conditions.\n\n" + | |
522 | + "For example, this entity filter selects 'Factory' edge instances which are related to the asset with id 'e52b0020-2a7a-11ec-94eb-213c95f54092' using 'Contains' relation:\n\n" + | |
523 | + MARKDOWN_CODE_BLOCK_START + | |
524 | + "{\n" + | |
525 | + " \"type\": \"deviceSearchQuery\",\n" + | |
526 | + " \"rootEntity\": {\n" + | |
527 | + " \"entityType\": \"ASSET\",\n" + | |
528 | + " \"id\": \"e52b0020-2a7a-11ec-94eb-213c95f54092\"\n" + | |
529 | + " },\n" + | |
530 | + " \"direction\": \"FROM\",\n" + | |
531 | + " \"maxLevel\": 2,\n" + | |
532 | + " \"fetchLastLevelOnly\": true,\n" + | |
533 | + " \"relationType\": \"Contains\",\n" + | |
534 | + " \"edgeTypes\": [\n" + | |
535 | + " \"Factory\"\n" + | |
536 | + " ]\n" + | |
537 | + "}" + | |
538 | + MARKDOWN_CODE_BLOCK_END + | |
539 | + ""; | |
540 | + | |
541 | + protected static final String EMPTY = "\n\n## Entity Type Filter\n\n" + | |
542 | + "Allows to filter multiple entities of the same type using the **'starts with'** expression over entity name. " + | |
543 | + "For example, this entity filter selects all devices which name starts with 'Air Quality':\n\n" + | |
544 | + MARKDOWN_CODE_BLOCK_START + | |
545 | + "" + | |
546 | + MARKDOWN_CODE_BLOCK_END + | |
547 | + ""; | |
548 | + | |
549 | + protected static final String ENTITY_FILTERS = | |
550 | + "\n\n # Entity Filters" + | |
551 | + "\nEntity Filter body depends on the 'type' parameter. Let's review available entity filter types. In fact, they do correspond to available dashboard aliases." + | |
552 | + SINGLE_ENTITY + ENTITY_LIST + ENTITY_NAME + ENTITY_TYPE_FILTER + ASSET_TYPE + DEVICE_TYPE + EDGE_TYPE + ENTITY_VIEW_TYPE + API_USAGE + RELATIONS_QUERY_FILTER | |
553 | + + ASSET_QUERY_FILTER + DEVICE_QUERY_FILTER + EV_QUERY_FILTER + EDGE_QUERY_FILTER; | |
554 | + | |
555 | + protected static final String FILTER_KEY = "\n\n## Filter Key\n\n" + | |
556 | + "Filter Key defines either entity field, attribute or telemetry. It is a JSON object that consists the key name and type. " + | |
557 | + "The following filter key types are supported: \n\n" + | |
558 | + " * 'CLIENT_ATTRIBUTE' - used for client attributes; \n" + | |
559 | + " * 'SHARED_ATTRIBUTE' - used for shared attributes; \n" + | |
560 | + " * 'SERVER_ATTRIBUTE' - used for server attributes; \n" + | |
561 | + " * 'ATTRIBUTE' - used for any of the above; \n" + | |
562 | + " * 'TIME_SERIES' - used for time-series values; \n" + | |
563 | + " * 'ENTITY_FIELD' - used for accessing entity fields like 'name', 'label', etc. The list of available fields depends on the entity type; \n" + | |
564 | + " * 'ALARM_FIELD' - similar to entity field, but is used in alarm queries only; \n" + | |
565 | + "\n\n Let's review the example:\n\n" + | |
566 | + MARKDOWN_CODE_BLOCK_START + | |
567 | + "{\n" + | |
568 | + " \"type\": \"TIME_SERIES\",\n" + | |
569 | + " \"key\": \"temperature\"\n" + | |
570 | + "}" + | |
571 | + MARKDOWN_CODE_BLOCK_END + | |
572 | + ""; | |
573 | + | |
574 | + protected static final String FILTER_PREDICATE = "\n\n## Filter Predicate\n\n" + | |
575 | + "Filter Predicate defines the logical expression to evaluate. The list of available operations depends on the filter value type, see above. " + | |
576 | + "Platform supports 4 predicate types: 'STRING', 'NUMERIC', 'BOOLEAN' and 'COMPLEX'. The last one allows to combine multiple operations over one filter key." + | |
577 | + "\n\nSimple predicate example to check 'value < 100': \n\n" + | |
578 | + MARKDOWN_CODE_BLOCK_START + | |
579 | + "{\n" + | |
580 | + " \"operation\": \"LESS\",\n" + | |
581 | + " \"value\": {\n" + | |
582 | + " \"defaultValue\": 100,\n" + | |
583 | + " \"dynamicValue\": null\n" + | |
584 | + " },\n" + | |
585 | + " \"type\": \"NUMERIC\"\n" + | |
586 | + "}" + | |
587 | + MARKDOWN_CODE_BLOCK_END + | |
588 | + "\n\nComplex predicate example, to check 'value < 10 or value > 20': \n\n" + | |
589 | + MARKDOWN_CODE_BLOCK_START + | |
590 | + "{\n" + | |
591 | + " \"type\": \"COMPLEX\",\n" + | |
592 | + " \"operation\": \"OR\",\n" + | |
593 | + " \"predicates\": [\n" + | |
594 | + " {\n" + | |
595 | + " \"operation\": \"LESS\",\n" + | |
596 | + " \"value\": {\n" + | |
597 | + " \"defaultValue\": 10,\n" + | |
598 | + " \"dynamicValue\": null\n" + | |
599 | + " },\n" + | |
600 | + " \"type\": \"NUMERIC\"\n" + | |
601 | + " },\n" + | |
602 | + " {\n" + | |
603 | + " \"operation\": \"GREATER\",\n" + | |
604 | + " \"value\": {\n" + | |
605 | + " \"defaultValue\": 20,\n" + | |
606 | + " \"dynamicValue\": null\n" + | |
607 | + " },\n" + | |
608 | + " \"type\": \"NUMERIC\"\n" + | |
609 | + " }\n" + | |
610 | + " ]\n" + | |
611 | + "}" + | |
612 | + MARKDOWN_CODE_BLOCK_END + | |
613 | + "\n\nMore complex predicate example, to check 'value < 10 or (value > 50 && value < 60)': \n\n" + | |
614 | + MARKDOWN_CODE_BLOCK_START + | |
615 | + "{\n" + | |
616 | + " \"type\": \"COMPLEX\",\n" + | |
617 | + " \"operation\": \"OR\",\n" + | |
618 | + " \"predicates\": [\n" + | |
619 | + " {\n" + | |
620 | + " \"operation\": \"LESS\",\n" + | |
621 | + " \"value\": {\n" + | |
622 | + " \"defaultValue\": 10,\n" + | |
623 | + " \"dynamicValue\": null\n" + | |
624 | + " },\n" + | |
625 | + " \"type\": \"NUMERIC\"\n" + | |
626 | + " },\n" + | |
627 | + " {\n" + | |
628 | + " \"type\": \"COMPLEX\",\n" + | |
629 | + " \"operation\": \"AND\",\n" + | |
630 | + " \"predicates\": [\n" + | |
631 | + " {\n" + | |
632 | + " \"operation\": \"GREATER\",\n" + | |
633 | + " \"value\": {\n" + | |
634 | + " \"defaultValue\": 50,\n" + | |
635 | + " \"dynamicValue\": null\n" + | |
636 | + " },\n" + | |
637 | + " \"type\": \"NUMERIC\"\n" + | |
638 | + " },\n" + | |
639 | + " {\n" + | |
640 | + " \"operation\": \"LESS\",\n" + | |
641 | + " \"value\": {\n" + | |
642 | + " \"defaultValue\": 60,\n" + | |
643 | + " \"dynamicValue\": null\n" + | |
644 | + " },\n" + | |
645 | + " \"type\": \"NUMERIC\"\n" + | |
646 | + " }\n" + | |
647 | + " ]\n" + | |
648 | + " }\n" + | |
649 | + " ]\n" + | |
650 | + "}" + | |
651 | + MARKDOWN_CODE_BLOCK_END + | |
652 | + "\n\n You may also want to replace hardcoded values (for example, temperature > 20) with the more dynamic " + | |
653 | + "expression (for example, temperature > 'value of the tenant attribute with key 'temperatureThreshold'). " + | |
654 | + "It is possible to use 'dynamicValue' to define attribute of the tenant, customer or user that is performing the API call. " + | |
655 | + "See example below: \n\n" + | |
656 | + MARKDOWN_CODE_BLOCK_START + | |
657 | + "{\n" + | |
658 | + " \"operation\": \"GREATER\",\n" + | |
659 | + " \"value\": {\n" + | |
660 | + " \"defaultValue\": 0,\n" + | |
661 | + " \"dynamicValue\": {\n" + | |
662 | + " \"sourceType\": \"CURRENT_USER\",\n" + | |
663 | + " \"sourceAttribute\": \"temperatureThreshold\"\n" + | |
664 | + " }\n" + | |
665 | + " },\n" + | |
666 | + " \"type\": \"NUMERIC\"\n" + | |
667 | + "}" + | |
668 | + MARKDOWN_CODE_BLOCK_END + | |
669 | + "\n\n Note that you may use 'CURRENT_USER', 'CURRENT_CUSTOMER' and 'CURRENT_TENANT' as a 'sourceType'. The 'defaultValue' is used when the attribute with such a name is not defined for the chosen source."; | |
670 | + | |
671 | + protected static final String KEY_FILTERS = | |
672 | + "\n\n # Key Filters" + | |
673 | + "\nKey Filter allows you to define complex logical expressions over entity field, attribute or latest time-series value. The filter is defined using 'key', 'valueType' and 'predicate' objects. " + | |
674 | + "Single Entity Query may have zero, one or multiple predicates. If multiple filters are defined, they are evaluated using logical 'AND'. " + | |
675 | + "The example below checks that temperature of the entity is above 20 degrees:" + | |
676 | + "\n\n" + MARKDOWN_CODE_BLOCK_START + | |
677 | + "{\n" + | |
678 | + " \"key\": {\n" + | |
679 | + " \"type\": \"TIME_SERIES\",\n" + | |
680 | + " \"key\": \"temperature\"\n" + | |
681 | + " },\n" + | |
682 | + " \"valueType\": \"NUMERIC\",\n" + | |
683 | + " \"predicate\": {\n" + | |
684 | + " \"operation\": \"GREATER\",\n" + | |
685 | + " \"value\": {\n" + | |
686 | + " \"defaultValue\": 20,\n" + | |
687 | + " \"dynamicValue\": null\n" + | |
688 | + " },\n" + | |
689 | + " \"type\": \"NUMERIC\"\n" + | |
690 | + " }\n" + | |
691 | + "}" + | |
692 | + MARKDOWN_CODE_BLOCK_END + | |
693 | + "\n\n Now let's review 'key', 'valueType' and 'predicate' objects in detail." | |
694 | + + FILTER_KEY + FILTER_VALUE_TYPE + FILTER_PREDICATE; | |
695 | + | |
696 | + protected static final String ENTITY_COUNT_QUERY_DESCRIPTION = | |
697 | + "Allows to run complex queries to search the count of platform entities (devices, assets, customers, etc) " + | |
698 | + "based on the combination of main entity filter and multiple key filters. Returns the number of entities that match the query definition.\n\n" + | |
699 | + "# Query Definition\n\n" + | |
700 | + "\n\nMain **entity filter** is mandatory and defines generic search criteria. " + | |
701 | + "For example, \"find all devices with profile 'Moisture Sensor'\" or \"Find all devices related to asset 'Building A'\"" + | |
702 | + "\n\nOptional **key filters** allow to filter results of the entity filter by complex criteria against " + | |
703 | + "main entity fields (name, label, type, etc), attributes and telemetry. " + | |
704 | + "For example, \"temperature > 20 or temperature< 10\" or \"name starts with 'T', and attribute 'model' is 'T1000', and timeseries field 'batteryLevel' > 40\"." + | |
705 | + "\n\nLet's review the example:" + | |
706 | + "\n\n" + MARKDOWN_CODE_BLOCK_START + | |
707 | + "{\n" + | |
708 | + " \"entityFilter\": {\n" + | |
709 | + " \"type\": \"entityType\",\n" + | |
710 | + " \"entityType\": \"DEVICE\"\n" + | |
711 | + " },\n" + | |
712 | + " \"keyFilters\": [\n" + | |
713 | + " {\n" + | |
714 | + " \"key\": {\n" + | |
715 | + " \"type\": \"ATTRIBUTE\",\n" + | |
716 | + " \"key\": \"active\"\n" + | |
717 | + " },\n" + | |
718 | + " \"valueType\": \"BOOLEAN\",\n" + | |
719 | + " \"predicate\": {\n" + | |
720 | + " \"operation\": \"EQUAL\",\n" + | |
721 | + " \"value\": {\n" + | |
722 | + " \"defaultValue\": true,\n" + | |
723 | + " \"dynamicValue\": null\n" + | |
724 | + " },\n" + | |
725 | + " \"type\": \"BOOLEAN\"\n" + | |
726 | + " }\n" + | |
727 | + " }\n" + | |
728 | + " ]\n" + | |
729 | + "}" + | |
730 | + MARKDOWN_CODE_BLOCK_END + | |
731 | + "\n\n Example mentioned above search all devices which have attribute 'active' set to 'true'. Now let's review available entity filters and key filters syntax:" + | |
732 | + ENTITY_FILTERS + | |
733 | + KEY_FILTERS + | |
734 | + ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; | |
735 | + | |
736 | + protected static final String ENTITY_DATA_QUERY_DESCRIPTION = | |
737 | + "Allows to run complex queries over platform entities (devices, assets, customers, etc) " + | |
738 | + "based on the combination of main entity filter and multiple key filters. " + | |
739 | + "Returns the paginated result of the query that contains requested entity fields and latest values of requested attributes and time-series data.\n\n" + | |
740 | + "# Query Definition\n\n" + | |
741 | + "\n\nMain **entity filter** is mandatory and defines generic search criteria. " + | |
742 | + "For example, \"find all devices with profile 'Moisture Sensor'\" or \"Find all devices related to asset 'Building A'\"" + | |
743 | + "\n\nOptional **key filters** allow to filter results of the **entity filter** by complex criteria against " + | |
744 | + "main entity fields (name, label, type, etc), attributes and telemetry. " + | |
745 | + "For example, \"temperature > 20 or temperature< 10\" or \"name starts with 'T', and attribute 'model' is 'T1000', and timeseries field 'batteryLevel' > 40\"." + | |
746 | + "\n\nThe **entity fields** and **latest values** contains list of entity fields and latest attribute/telemetry fields to fetch for each entity." + | |
747 | + "\n\nThe **page link** contains information about the page to fetch and the sort ordering." + | |
748 | + "\n\nLet's review the example:" + | |
749 | + "\n\n" + MARKDOWN_CODE_BLOCK_START + | |
750 | + "{\n" + | |
751 | + " \"entityFilter\": {\n" + | |
752 | + " \"type\": \"entityType\",\n" + | |
753 | + " \"resolveMultiple\": true,\n" + | |
754 | + " \"entityType\": \"DEVICE\"\n" + | |
755 | + " },\n" + | |
756 | + " \"keyFilters\": [\n" + | |
757 | + " {\n" + | |
758 | + " \"key\": {\n" + | |
759 | + " \"type\": \"TIME_SERIES\",\n" + | |
760 | + " \"key\": \"temperature\"\n" + | |
761 | + " },\n" + | |
762 | + " \"valueType\": \"NUMERIC\",\n" + | |
763 | + " \"predicate\": {\n" + | |
764 | + " \"operation\": \"GREATER\",\n" + | |
765 | + " \"value\": {\n" + | |
766 | + " \"defaultValue\": 0,\n" + | |
767 | + " \"dynamicValue\": {\n" + | |
768 | + " \"sourceType\": \"CURRENT_USER\",\n" + | |
769 | + " \"sourceAttribute\": \"temperatureThreshold\",\n" + | |
770 | + " \"inherit\": false\n" + | |
771 | + " }\n" + | |
772 | + " },\n" + | |
773 | + " \"type\": \"NUMERIC\"\n" + | |
774 | + " }\n" + | |
775 | + " }\n" + | |
776 | + " ],\n" + | |
777 | + " \"entityFields\": [\n" + | |
778 | + " {\n" + | |
779 | + " \"type\": \"ENTITY_FIELD\",\n" + | |
780 | + " \"key\": \"name\"\n" + | |
781 | + " },\n" + | |
782 | + " {\n" + | |
783 | + " \"type\": \"ENTITY_FIELD\",\n" + | |
784 | + " \"key\": \"label\"\n" + | |
785 | + " },\n" + | |
786 | + " {\n" + | |
787 | + " \"type\": \"ENTITY_FIELD\",\n" + | |
788 | + " \"key\": \"additionalInfo\"\n" + | |
789 | + " }\n" + | |
790 | + " ],\n" + | |
791 | + " \"latestValues\": [\n" + | |
792 | + " {\n" + | |
793 | + " \"type\": \"ATTRIBUTE\",\n" + | |
794 | + " \"key\": \"model\"\n" + | |
795 | + " },\n" + | |
796 | + " {\n" + | |
797 | + " \"type\": \"TIME_SERIES\",\n" + | |
798 | + " \"key\": \"temperature\"\n" + | |
799 | + " }\n" + | |
800 | + " ],\n" + | |
801 | + " \"pageLink\": {\n" + | |
802 | + " \"page\": 0,\n" + | |
803 | + " \"pageSize\": 10,\n" + | |
804 | + " \"sortOrder\": {\n" + | |
805 | + " \"key\": {\n" + | |
806 | + " \"key\": \"name\",\n" + | |
807 | + " \"type\": \"ENTITY_FIELD\"\n" + | |
808 | + " },\n" + | |
809 | + " \"direction\": \"ASC\"\n" + | |
810 | + " }\n" + | |
811 | + " }\n" + | |
812 | + "}" + | |
813 | + MARKDOWN_CODE_BLOCK_END + | |
814 | + "\n\n Example mentioned above search all devices which have attribute 'active' set to 'true'. Now let's review available entity filters and key filters syntax:" + | |
815 | + ENTITY_FILTERS + | |
816 | + KEY_FILTERS + | |
817 | + ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; | |
818 | + | |
819 | + | |
820 | + protected static final String ALARM_DATA_QUERY_DESCRIPTION = "This method description defines how Alarm Data Query extends the Entity Data Query. " + | |
821 | + "See method 'Find Entity Data by Query' first to get the info about 'Entity Data Query'." + | |
822 | + "\n\n The platform will first search the entities that match the entity and key filters. Then, the platform will use 'Alarm Page Link' to filter the alarms related to those entities. " + | |
823 | + "Finally, platform fetch the properties of alarm that are defined in the **'alarmFields'** and combine them with the other entity, attribute and latest time-series fields to return the result. " + | |
824 | + "\n\n See example of the alarm query below. The query will search first 100 active alarms with type 'Temperature Alarm' or 'Fire Alarm' for any device with current temperature > 0. " + | |
825 | + "The query will return combination of the entity fields: name of the device, device model and latest temperature reading and alarms fields: createdTime, type, severity and status: " + | |
826 | + "\n\n" + MARKDOWN_CODE_BLOCK_START + | |
827 | + "{\n" + | |
828 | + " \"entityFilter\": {\n" + | |
829 | + " \"type\": \"entityType\",\n" + | |
830 | + " \"resolveMultiple\": true,\n" + | |
831 | + " \"entityType\": \"DEVICE\"\n" + | |
832 | + " },\n" + | |
833 | + " \"pageLink\": {\n" + | |
834 | + " \"page\": 0,\n" + | |
835 | + " \"pageSize\": 100,\n" + | |
836 | + " \"textSearch\": null,\n" + | |
837 | + " \"searchPropagatedAlarms\": false,\n" + | |
838 | + " \"statusList\": [\n" + | |
839 | + " \"ACTIVE\"\n" + | |
840 | + " ],\n" + | |
841 | + " \"severityList\": [\n" + | |
842 | + " \"CRITICAL\",\n" + | |
843 | + " \"MAJOR\"\n" + | |
844 | + " ],\n" + | |
845 | + " \"typeList\": [\n" + | |
846 | + " \"Temperature Alarm\",\n" + | |
847 | + " \"Fire Alarm\"\n" + | |
848 | + " ],\n" + | |
849 | + " \"sortOrder\": {\n" + | |
850 | + " \"key\": {\n" + | |
851 | + " \"key\": \"createdTime\",\n" + | |
852 | + " \"type\": \"ALARM_FIELD\"\n" + | |
853 | + " },\n" + | |
854 | + " \"direction\": \"DESC\"\n" + | |
855 | + " },\n" + | |
856 | + " \"timeWindow\": 86400000\n" + | |
857 | + " },\n" + | |
858 | + " \"keyFilters\": [\n" + | |
859 | + " {\n" + | |
860 | + " \"key\": {\n" + | |
861 | + " \"type\": \"TIME_SERIES\",\n" + | |
862 | + " \"key\": \"temperature\"\n" + | |
863 | + " },\n" + | |
864 | + " \"valueType\": \"NUMERIC\",\n" + | |
865 | + " \"predicate\": {\n" + | |
866 | + " \"operation\": \"GREATER\",\n" + | |
867 | + " \"value\": {\n" + | |
868 | + " \"defaultValue\": 0,\n" + | |
869 | + " \"dynamicValue\": null\n" + | |
870 | + " },\n" + | |
871 | + " \"type\": \"NUMERIC\"\n" + | |
872 | + " }\n" + | |
873 | + " }\n" + | |
874 | + " ],\n" + | |
875 | + " \"alarmFields\": [\n" + | |
876 | + " {\n" + | |
877 | + " \"type\": \"ALARM_FIELD\",\n" + | |
878 | + " \"key\": \"createdTime\"\n" + | |
879 | + " },\n" + | |
880 | + " {\n" + | |
881 | + " \"type\": \"ALARM_FIELD\",\n" + | |
882 | + " \"key\": \"type\"\n" + | |
883 | + " },\n" + | |
884 | + " {\n" + | |
885 | + " \"type\": \"ALARM_FIELD\",\n" + | |
886 | + " \"key\": \"severity\"\n" + | |
887 | + " },\n" + | |
888 | + " {\n" + | |
889 | + " \"type\": \"ALARM_FIELD\",\n" + | |
890 | + " \"key\": \"status\"\n" + | |
891 | + " }\n" + | |
892 | + " ],\n" + | |
893 | + " \"entityFields\": [\n" + | |
894 | + " {\n" + | |
895 | + " \"type\": \"ENTITY_FIELD\",\n" + | |
896 | + " \"key\": \"name\"\n" + | |
897 | + " }\n" + | |
898 | + " ],\n" + | |
899 | + " \"latestValues\": [\n" + | |
900 | + " {\n" + | |
901 | + " \"type\": \"ATTRIBUTE\",\n" + | |
902 | + " \"key\": \"model\"\n" + | |
903 | + " },\n" + | |
904 | + " {\n" + | |
905 | + " \"type\": \"TIME_SERIES\",\n" + | |
906 | + " \"key\": \"temperature\"\n" + | |
907 | + " }\n" + | |
908 | + " ]\n" + | |
909 | + "}" + | |
910 | + MARKDOWN_CODE_BLOCK_END + | |
911 | + ""; | |
912 | + | |
913 | + protected static final String COAP_TRANSPORT_CONFIGURATION_EXAMPLE = MARKDOWN_CODE_BLOCK_START + | |
914 | + "{\n" + | |
915 | + " \"type\":\"COAP\",\n" + | |
916 | + " \"clientSettings\":{\n" + | |
917 | + " \"edrxCycle\":null,\n" + | |
918 | + " \"powerMode\":\"DRX\",\n" + | |
919 | + " \"psmActivityTimer\":null,\n" + | |
920 | + " \"pagingTransmissionWindow\":null\n" + | |
921 | + " },\n" + | |
922 | + " \"coapDeviceTypeConfiguration\":{\n" + | |
923 | + " \"coapDeviceType\":\"DEFAULT\",\n" + | |
924 | + " \"transportPayloadTypeConfiguration\":{\n" + | |
925 | + " \"transportPayloadType\":\"JSON\"\n" + | |
926 | + " }\n" + | |
927 | + " }\n" + | |
928 | + "}" | |
929 | + + MARKDOWN_CODE_BLOCK_END; | |
930 | + | |
931 | + protected static final String TRANSPORT_CONFIGURATION = "# Transport Configuration" + NEW_LINE + | |
932 | + "5 transport configuration types are available:\n" + | |
933 | + " * 'DEFAULT';\n" + | |
934 | + " * 'MQTT';\n" + | |
935 | + " * 'LWM2M';\n" + | |
936 | + " * 'COAP';\n" + | |
937 | + " * 'SNMP'." + NEW_LINE + "Default type supports basic MQTT, HTTP, CoAP and LwM2M transports. " + | |
938 | + "Please refer to the [docs](https://thingsboard.io/docs/user-guide/device-profiles/#transport-configuration) for more details about other types.\n" + | |
939 | + "\nSee another example of COAP transport configuration below:" + NEW_LINE + COAP_TRANSPORT_CONFIGURATION_EXAMPLE; | |
940 | + | |
941 | + protected static final String ALARM_FILTER_KEY = "## Alarm Filter Key" + NEW_LINE + | |
942 | + "Filter Key defines either entity field, attribute, telemetry or constant. It is a JSON object that consists the key name and type. The following filter key types are supported:\n" + | |
943 | + " * 'ATTRIBUTE' - used for attributes values;\n" + | |
944 | + " * 'TIME_SERIES' - used for time-series values;\n" + | |
945 | + " * 'ENTITY_FIELD' - used for accessing entity fields like 'name', 'label', etc. The list of available fields depends on the entity type;\n" + | |
946 | + " * 'CONSTANT' - constant value specified." + NEW_LINE + "Let's review the example:" + NEW_LINE + | |
947 | + MARKDOWN_CODE_BLOCK_START + | |
948 | + "{\n" + | |
949 | + " \"type\": \"TIME_SERIES\",\n" + | |
950 | + " \"key\": \"temperature\"\n" + | |
951 | + "}" + | |
952 | + MARKDOWN_CODE_BLOCK_END; | |
953 | + | |
954 | + protected static final String DEVICE_PROFILE_FILTER_PREDICATE = NEW_LINE + "## Filter Predicate" + NEW_LINE + | |
955 | + "Filter Predicate defines the logical expression to evaluate. The list of available operations depends on the filter value type, see above. " + | |
956 | + "Platform supports 4 predicate types: 'STRING', 'NUMERIC', 'BOOLEAN' and 'COMPLEX'. The last one allows to combine multiple operations over one filter key." + NEW_LINE + | |
957 | + "Simple predicate example to check 'value < 100': " + NEW_LINE + | |
958 | + MARKDOWN_CODE_BLOCK_START + | |
959 | + "{\n" + | |
960 | + " \"operation\": \"LESS\",\n" + | |
961 | + " \"value\": {\n" + | |
962 | + " \"userValue\": null,\n" + | |
963 | + " \"defaultValue\": 100,\n" + | |
964 | + " \"dynamicValue\": null\n" + | |
965 | + " },\n" + | |
966 | + " \"type\": \"NUMERIC\"\n" + | |
967 | + "}" + | |
968 | + MARKDOWN_CODE_BLOCK_END + NEW_LINE + | |
969 | + "Complex predicate example, to check 'value < 10 or value > 20': " + NEW_LINE + | |
970 | + MARKDOWN_CODE_BLOCK_START + | |
971 | + "{\n" + | |
972 | + " \"type\": \"COMPLEX\",\n" + | |
973 | + " \"operation\": \"OR\",\n" + | |
974 | + " \"predicates\": [\n" + | |
975 | + " {\n" + | |
976 | + " \"operation\": \"LESS\",\n" + | |
977 | + " \"value\": {\n" + | |
978 | + " \"userValue\": null,\n" + | |
979 | + " \"defaultValue\": 10,\n" + | |
980 | + " \"dynamicValue\": null\n" + | |
981 | + " },\n" + | |
982 | + " \"type\": \"NUMERIC\"\n" + | |
983 | + " },\n" + | |
984 | + " {\n" + | |
985 | + " \"operation\": \"GREATER\",\n" + | |
986 | + " \"value\": {\n" + | |
987 | + " \"userValue\": null,\n" + | |
988 | + " \"defaultValue\": 20,\n" + | |
989 | + " \"dynamicValue\": null\n" + | |
990 | + " },\n" + | |
991 | + " \"type\": \"NUMERIC\"\n" + | |
992 | + " }\n" + | |
993 | + " ]\n" + | |
994 | + "}" + | |
995 | + MARKDOWN_CODE_BLOCK_END + NEW_LINE + | |
996 | + "More complex predicate example, to check 'value < 10 or (value > 50 && value < 60)': " + NEW_LINE + | |
997 | + MARKDOWN_CODE_BLOCK_START + | |
998 | + "{\n" + | |
999 | + " \"type\": \"COMPLEX\",\n" + | |
1000 | + " \"operation\": \"OR\",\n" + | |
1001 | + " \"predicates\": [\n" + | |
1002 | + " {\n" + | |
1003 | + " \"operation\": \"LESS\",\n" + | |
1004 | + " \"value\": {\n" + | |
1005 | + " \"userValue\": null,\n" + | |
1006 | + " \"defaultValue\": 10,\n" + | |
1007 | + " \"dynamicValue\": null\n" + | |
1008 | + " },\n" + | |
1009 | + " \"type\": \"NUMERIC\"\n" + | |
1010 | + " },\n" + | |
1011 | + " {\n" + | |
1012 | + " \"type\": \"COMPLEX\",\n" + | |
1013 | + " \"operation\": \"AND\",\n" + | |
1014 | + " \"predicates\": [\n" + | |
1015 | + " {\n" + | |
1016 | + " \"operation\": \"GREATER\",\n" + | |
1017 | + " \"value\": {\n" + | |
1018 | + " \"userValue\": null,\n" + | |
1019 | + " \"defaultValue\": 50,\n" + | |
1020 | + " \"dynamicValue\": null\n" + | |
1021 | + " },\n" + | |
1022 | + " \"type\": \"NUMERIC\"\n" + | |
1023 | + " },\n" + | |
1024 | + " {\n" + | |
1025 | + " \"operation\": \"LESS\",\n" + | |
1026 | + " \"value\": {\n" + | |
1027 | + " \"userValue\": null,\n" + | |
1028 | + " \"defaultValue\": 60,\n" + | |
1029 | + " \"dynamicValue\": null\n" + | |
1030 | + " },\n" + | |
1031 | + " \"type\": \"NUMERIC\"\n" + | |
1032 | + " }\n" + | |
1033 | + " ]\n" + | |
1034 | + " }\n" + | |
1035 | + " ]\n" + | |
1036 | + "}" + | |
1037 | + MARKDOWN_CODE_BLOCK_END + NEW_LINE + | |
1038 | + "You may also want to replace hardcoded values (for example, temperature > 20) with the more dynamic " + | |
1039 | + "expression (for example, temperature > value of the tenant attribute with key 'temperatureThreshold'). " + | |
1040 | + "It is possible to use 'dynamicValue' to define attribute of the tenant, customer or device. " + | |
1041 | + "See example below:" + NEW_LINE + | |
1042 | + MARKDOWN_CODE_BLOCK_START + | |
1043 | + "{\n" + | |
1044 | + " \"operation\": \"GREATER\",\n" + | |
1045 | + " \"value\": {\n" + | |
1046 | + " \"userValue\": null,\n" + | |
1047 | + " \"defaultValue\": 0,\n" + | |
1048 | + " \"dynamicValue\": {\n" + | |
1049 | + " \"inherit\": false,\n" + | |
1050 | + " \"sourceType\": \"CURRENT_TENANT\",\n" + | |
1051 | + " \"sourceAttribute\": \"temperatureThreshold\"\n" + | |
1052 | + " }\n" + | |
1053 | + " },\n" + | |
1054 | + " \"type\": \"NUMERIC\"\n" + | |
1055 | + "}" + | |
1056 | + MARKDOWN_CODE_BLOCK_END + NEW_LINE + | |
1057 | + "Note that you may use 'CURRENT_DEVICE', 'CURRENT_CUSTOMER' and 'CURRENT_TENANT' as a 'sourceType'. The 'defaultValue' is used when the attribute with such a name is not defined for the chosen source. " + | |
1058 | + "The 'sourceAttribute' can be inherited from the owner of the specified 'sourceType' if 'inherit' is set to true."; | |
1059 | + | |
1060 | + protected static final String KEY_FILTERS_DESCRIPTION = "# Key Filters" + NEW_LINE + | |
1061 | + "Key filter objects are created under the **'condition'** array. They allow you to define complex logical expressions over entity field, " + | |
1062 | + "attribute, latest time-series value or constant. The filter is defined using 'key', 'valueType', " + | |
1063 | + "'value' (refers to the value of the 'CONSTANT' alarm filter key type) and 'predicate' objects. Let's review each object:" + NEW_LINE + | |
1064 | + ALARM_FILTER_KEY + FILTER_VALUE_TYPE + NEW_LINE + DEVICE_PROFILE_FILTER_PREDICATE + NEW_LINE; | |
1065 | + | |
1066 | + protected static final String DEFAULT_DEVICE_PROFILE_DATA_EXAMPLE = MARKDOWN_CODE_BLOCK_START + "{\n" + | |
1067 | + " \"alarms\":[\n" + | |
1068 | + " ],\n" + | |
1069 | + " \"configuration\":{\n" + | |
1070 | + " \"type\":\"DEFAULT\"\n" + | |
1071 | + " },\n" + | |
1072 | + " \"provisionConfiguration\":{\n" + | |
1073 | + " \"type\":\"DISABLED\",\n" + | |
1074 | + " \"provisionDeviceSecret\":null\n" + | |
1075 | + " },\n" + | |
1076 | + " \"transportConfiguration\":{\n" + | |
1077 | + " \"type\":\"DEFAULT\"\n" + | |
1078 | + " }\n" + | |
1079 | + "}" + MARKDOWN_CODE_BLOCK_END; | |
1080 | + | |
1081 | + protected static final String CUSTOM_DEVICE_PROFILE_DATA_EXAMPLE = MARKDOWN_CODE_BLOCK_START + "{\n" + | |
1082 | + " \"alarms\":[\n" + | |
1083 | + " {\n" + | |
1084 | + " \"id\":\"2492b935-1226-59e9-8615-17d8978a4f93\",\n" + | |
1085 | + " \"alarmType\":\"Temperature Alarm\",\n" + | |
1086 | + " \"clearRule\":{\n" + | |
1087 | + " \"schedule\":null,\n" + | |
1088 | + " \"condition\":{\n" + | |
1089 | + " \"spec\":{\n" + | |
1090 | + " \"type\":\"SIMPLE\"\n" + | |
1091 | + " },\n" + | |
1092 | + " \"condition\":[\n" + | |
1093 | + " {\n" + | |
1094 | + " \"key\":{\n" + | |
1095 | + " \"key\":\"temperature\",\n" + | |
1096 | + " \"type\":\"TIME_SERIES\"\n" + | |
1097 | + " },\n" + | |
1098 | + " \"value\":null,\n" + | |
1099 | + " \"predicate\":{\n" + | |
1100 | + " \"type\":\"NUMERIC\",\n" + | |
1101 | + " \"value\":{\n" + | |
1102 | + " \"userValue\":null,\n" + | |
1103 | + " \"defaultValue\":30.0,\n" + | |
1104 | + " \"dynamicValue\":null\n" + | |
1105 | + " },\n" + | |
1106 | + " \"operation\":\"LESS\"\n" + | |
1107 | + " },\n" + | |
1108 | + " \"valueType\":\"NUMERIC\"\n" + | |
1109 | + " }\n" + | |
1110 | + " ]\n" + | |
1111 | + " },\n" + | |
1112 | + " \"dashboardId\":null,\n" + | |
1113 | + " \"alarmDetails\":null\n" + | |
1114 | + " },\n" + | |
1115 | + " \"propagate\":false,\n" + | |
1116 | + " \"createRules\":{\n" + | |
1117 | + " \"MAJOR\":{\n" + | |
1118 | + " \"schedule\":{\n" + | |
1119 | + " \"type\":\"SPECIFIC_TIME\",\n" + | |
1120 | + " \"endsOn\":64800000,\n" + | |
1121 | + " \"startsOn\":43200000,\n" + | |
1122 | + " \"timezone\":\"Europe/Kiev\",\n" + | |
1123 | + " \"daysOfWeek\":[\n" + | |
1124 | + " 1,\n" + | |
1125 | + " 3,\n" + | |
1126 | + " 5\n" + | |
1127 | + " ]\n" + | |
1128 | + " },\n" + | |
1129 | + " \"condition\":{\n" + | |
1130 | + " \"spec\":{\n" + | |
1131 | + " \"type\":\"DURATION\",\n" + | |
1132 | + " \"unit\":\"MINUTES\",\n" + | |
1133 | + " \"predicate\":{\n" + | |
1134 | + " \"userValue\":null,\n" + | |
1135 | + " \"defaultValue\":30,\n" + | |
1136 | + " \"dynamicValue\":null\n" + | |
1137 | + " }\n" + | |
1138 | + " },\n" + | |
1139 | + " \"condition\":[\n" + | |
1140 | + " {\n" + | |
1141 | + " \"key\":{\n" + | |
1142 | + " \"key\":\"temperature\",\n" + | |
1143 | + " \"type\":\"TIME_SERIES\"\n" + | |
1144 | + " },\n" + | |
1145 | + " \"value\":null,\n" + | |
1146 | + " \"predicate\":{\n" + | |
1147 | + " \"type\":\"COMPLEX\",\n" + | |
1148 | + " \"operation\":\"OR\",\n" + | |
1149 | + " \"predicates\":[\n" + | |
1150 | + " {\n" + | |
1151 | + " \"type\":\"NUMERIC\",\n" + | |
1152 | + " \"value\":{\n" + | |
1153 | + " \"userValue\":null,\n" + | |
1154 | + " \"defaultValue\":50.0,\n" + | |
1155 | + " \"dynamicValue\":null\n" + | |
1156 | + " },\n" + | |
1157 | + " \"operation\":\"LESS_OR_EQUAL\"\n" + | |
1158 | + " },\n" + | |
1159 | + " {\n" + | |
1160 | + " \"type\":\"NUMERIC\",\n" + | |
1161 | + " \"value\":{\n" + | |
1162 | + " \"userValue\":null,\n" + | |
1163 | + " \"defaultValue\":30.0,\n" + | |
1164 | + " \"dynamicValue\":null\n" + | |
1165 | + " },\n" + | |
1166 | + " \"operation\":\"GREATER\"\n" + | |
1167 | + " }\n" + | |
1168 | + " ]\n" + | |
1169 | + " },\n" + | |
1170 | + " \"valueType\":\"NUMERIC\"\n" + | |
1171 | + " }\n" + | |
1172 | + " ]\n" + | |
1173 | + " },\n" + | |
1174 | + " \"dashboardId\":null,\n" + | |
1175 | + " \"alarmDetails\":null\n" + | |
1176 | + " },\n" + | |
1177 | + " \"WARNING\":{\n" + | |
1178 | + " \"schedule\":{\n" + | |
1179 | + " \"type\":\"CUSTOM\",\n" + | |
1180 | + " \"items\":[\n" + | |
1181 | + " {\n" + | |
1182 | + " \"endsOn\":0,\n" + | |
1183 | + " \"enabled\":false,\n" + | |
1184 | + " \"startsOn\":0,\n" + | |
1185 | + " \"dayOfWeek\":1\n" + | |
1186 | + " },\n" + | |
1187 | + " {\n" + | |
1188 | + " \"endsOn\":64800000,\n" + | |
1189 | + " \"enabled\":true,\n" + | |
1190 | + " \"startsOn\":43200000,\n" + | |
1191 | + " \"dayOfWeek\":2\n" + | |
1192 | + " },\n" + | |
1193 | + " {\n" + | |
1194 | + " \"endsOn\":0,\n" + | |
1195 | + " \"enabled\":false,\n" + | |
1196 | + " \"startsOn\":0,\n" + | |
1197 | + " \"dayOfWeek\":3\n" + | |
1198 | + " },\n" + | |
1199 | + " {\n" + | |
1200 | + " \"endsOn\":57600000,\n" + | |
1201 | + " \"enabled\":true,\n" + | |
1202 | + " \"startsOn\":36000000,\n" + | |
1203 | + " \"dayOfWeek\":4\n" + | |
1204 | + " },\n" + | |
1205 | + " {\n" + | |
1206 | + " \"endsOn\":0,\n" + | |
1207 | + " \"enabled\":false,\n" + | |
1208 | + " \"startsOn\":0,\n" + | |
1209 | + " \"dayOfWeek\":5\n" + | |
1210 | + " },\n" + | |
1211 | + " {\n" + | |
1212 | + " \"endsOn\":0,\n" + | |
1213 | + " \"enabled\":false,\n" + | |
1214 | + " \"startsOn\":0,\n" + | |
1215 | + " \"dayOfWeek\":6\n" + | |
1216 | + " },\n" + | |
1217 | + " {\n" + | |
1218 | + " \"endsOn\":0,\n" + | |
1219 | + " \"enabled\":false,\n" + | |
1220 | + " \"startsOn\":0,\n" + | |
1221 | + " \"dayOfWeek\":7\n" + | |
1222 | + " }\n" + | |
1223 | + " ],\n" + | |
1224 | + " \"timezone\":\"Europe/Kiev\"\n" + | |
1225 | + " },\n" + | |
1226 | + " \"condition\":{\n" + | |
1227 | + " \"spec\":{\n" + | |
1228 | + " \"type\":\"REPEATING\",\n" + | |
1229 | + " \"predicate\":{\n" + | |
1230 | + " \"userValue\":null,\n" + | |
1231 | + " \"defaultValue\":5,\n" + | |
1232 | + " \"dynamicValue\":null\n" + | |
1233 | + " }\n" + | |
1234 | + " },\n" + | |
1235 | + " \"condition\":[\n" + | |
1236 | + " {\n" + | |
1237 | + " \"key\":{\n" + | |
1238 | + " \"key\":\"tempConstant\",\n" + | |
1239 | + " \"type\":\"CONSTANT\"\n" + | |
1240 | + " },\n" + | |
1241 | + " \"value\":30,\n" + | |
1242 | + " \"predicate\":{\n" + | |
1243 | + " \"type\":\"NUMERIC\",\n" + | |
1244 | + " \"value\":{\n" + | |
1245 | + " \"userValue\":null,\n" + | |
1246 | + " \"defaultValue\":0.0,\n" + | |
1247 | + " \"dynamicValue\":{\n" + | |
1248 | + " \"inherit\":false,\n" + | |
1249 | + " \"sourceType\":\"CURRENT_DEVICE\",\n" + | |
1250 | + " \"sourceAttribute\":\"tempThreshold\"\n" + | |
1251 | + " }\n" + | |
1252 | + " },\n" + | |
1253 | + " \"operation\":\"EQUAL\"\n" + | |
1254 | + " },\n" + | |
1255 | + " \"valueType\":\"NUMERIC\"\n" + | |
1256 | + " }\n" + | |
1257 | + " ]\n" + | |
1258 | + " },\n" + | |
1259 | + " \"dashboardId\":null,\n" + | |
1260 | + " \"alarmDetails\":null\n" + | |
1261 | + " },\n" + | |
1262 | + " \"CRITICAL\":{\n" + | |
1263 | + " \"schedule\":null,\n" + | |
1264 | + " \"condition\":{\n" + | |
1265 | + " \"spec\":{\n" + | |
1266 | + " \"type\":\"SIMPLE\"\n" + | |
1267 | + " },\n" + | |
1268 | + " \"condition\":[\n" + | |
1269 | + " {\n" + | |
1270 | + " \"key\":{\n" + | |
1271 | + " \"key\":\"temperature\",\n" + | |
1272 | + " \"type\":\"TIME_SERIES\"\n" + | |
1273 | + " },\n" + | |
1274 | + " \"value\":null,\n" + | |
1275 | + " \"predicate\":{\n" + | |
1276 | + " \"type\":\"NUMERIC\",\n" + | |
1277 | + " \"value\":{\n" + | |
1278 | + " \"userValue\":null,\n" + | |
1279 | + " \"defaultValue\":50.0,\n" + | |
1280 | + " \"dynamicValue\":null\n" + | |
1281 | + " },\n" + | |
1282 | + " \"operation\":\"GREATER\"\n" + | |
1283 | + " },\n" + | |
1284 | + " \"valueType\":\"NUMERIC\"\n" + | |
1285 | + " }\n" + | |
1286 | + " ]\n" + | |
1287 | + " },\n" + | |
1288 | + " \"dashboardId\":null,\n" + | |
1289 | + " \"alarmDetails\":null\n" + | |
1290 | + " }\n" + | |
1291 | + " },\n" + | |
1292 | + " \"propagateRelationTypes\":null\n" + | |
1293 | + " }\n" + | |
1294 | + " ],\n" + | |
1295 | + " \"configuration\":{\n" + | |
1296 | + " \"type\":\"DEFAULT\"\n" + | |
1297 | + " },\n" + | |
1298 | + " \"provisionConfiguration\":{\n" + | |
1299 | + " \"type\":\"ALLOW_CREATE_NEW_DEVICES\",\n" + | |
1300 | + " \"provisionDeviceSecret\":\"vaxb9hzqdbz3oqukvomg\"\n" + | |
1301 | + " },\n" + | |
1302 | + " \"transportConfiguration\":{\n" + | |
1303 | + " \"type\":\"MQTT\",\n" + | |
1304 | + " \"deviceTelemetryTopic\":\"v1/devices/me/telemetry\",\n" + | |
1305 | + " \"deviceAttributesTopic\":\"v1/devices/me/attributes\",\n" + | |
1306 | + " \"transportPayloadTypeConfiguration\":{\n" + | |
1307 | + " \"transportPayloadType\":\"PROTOBUF\",\n" + | |
1308 | + " \"deviceTelemetryProtoSchema\":\"syntax =\\\"proto3\\\";\\npackage telemetry;\\n\\nmessage SensorDataReading {\\n\\n optional double temperature = 1;\\n optional double humidity = 2;\\n InnerObject innerObject = 3;\\n\\n message InnerObject {\\n optional string key1 = 1;\\n optional bool key2 = 2;\\n optional double key3 = 3;\\n optional int32 key4 = 4;\\n optional string key5 = 5;\\n }\\n}\",\n" + | |
1309 | + " \"deviceAttributesProtoSchema\":\"syntax =\\\"proto3\\\";\\npackage attributes;\\n\\nmessage SensorConfiguration {\\n optional string firmwareVersion = 1;\\n optional string serialNumber = 2;\\n}\",\n" + | |
1310 | + " \"deviceRpcRequestProtoSchema\":\"syntax =\\\"proto3\\\";\\npackage rpc;\\n\\nmessage RpcRequestMsg {\\n optional string method = 1;\\n optional int32 requestId = 2;\\n optional string params = 3;\\n}\",\n" + | |
1311 | + " \"deviceRpcResponseProtoSchema\":\"syntax =\\\"proto3\\\";\\npackage rpc;\\n\\nmessage RpcResponseMsg {\\n optional string payload = 1;\\n}\"\n" + | |
1312 | + " }\n" + | |
1313 | + " }\n" + | |
1314 | + "}" + MARKDOWN_CODE_BLOCK_END; | |
1315 | + protected static final String DEVICE_PROFILE_DATA_DEFINITION = NEW_LINE + "# Device profile data definition" + NEW_LINE + | |
1316 | + "Device profile data object contains alarm rules configuration, device provision strategy and transport type configuration for device connectivity. Let's review some examples. " + | |
1317 | + "First one is the default device profile data configuration and second one - the custom one. " + | |
1318 | + NEW_LINE + DEFAULT_DEVICE_PROFILE_DATA_EXAMPLE + NEW_LINE + CUSTOM_DEVICE_PROFILE_DATA_EXAMPLE + | |
1319 | + NEW_LINE + "Let's review some specific objects examples related to the device profile configuration:"; | |
1320 | + | |
1321 | + protected static final String ALARM_SCHEDULE = NEW_LINE + "# Alarm Schedule" + NEW_LINE + | |
1322 | + "Alarm Schedule JSON object represents the time interval during which the alarm rule is active. Note, " + | |
1323 | + NEW_LINE + DEVICE_PROFILE_ALARM_SCHEDULE_ALWAYS_EXAMPLE + NEW_LINE + "means alarm rule is active all the time. " + | |
1324 | + "**'daysOfWeek'** field represents Monday as 1, Tuesday as 2 and so on. **'startsOn'** and **'endsOn'** fields represent hours in millis (e.g. 64800000 = 18:00 or 6pm). " + | |
1325 | + "**'enabled'** flag specifies if item in a custom rule is active for specific day of the week:" + NEW_LINE + | |
1326 | + "## Specific Time Schedule" + NEW_LINE + | |
1327 | + DEVICE_PROFILE_ALARM_SCHEDULE_SPECIFIC_TIME_EXAMPLE + NEW_LINE + | |
1328 | + "## Custom Schedule" + | |
1329 | + NEW_LINE + DEVICE_PROFILE_ALARM_SCHEDULE_CUSTOM_EXAMPLE + NEW_LINE; | |
1330 | + | |
1331 | + protected static final String ALARM_CONDITION_TYPE = "# Alarm condition type (**'spec'**)" + NEW_LINE + | |
1332 | + "Alarm condition type can be either simple, duration, or repeating. For example, 5 times in a row or during 5 minutes." + NEW_LINE + | |
1333 | + "Note, **'userValue'** field is not used and reserved for future usage, **'dynamicValue'** is used for condition appliance by using the value of the **'sourceAttribute'** " + | |
1334 | + "or else **'defaultValue'** is used (if **'sourceAttribute'** is absent).\n" + | |
1335 | + "\n**'sourceType'** of the **'sourceAttribute'** can be: \n" + | |
1336 | + " * 'CURRENT_DEVICE';\n" + | |
1337 | + " * 'CURRENT_CUSTOMER';\n" + | |
1338 | + " * 'CURRENT_TENANT'." + NEW_LINE + | |
1339 | + "**'sourceAttribute'** can be inherited from the owner if **'inherit'** is set to true (for CURRENT_DEVICE and CURRENT_CUSTOMER)." + NEW_LINE + | |
1340 | + "## Repeating alarm condition" + NEW_LINE + | |
1341 | + DEVICE_PROFILE_ALARM_CONDITION_REPEATING_EXAMPLE + NEW_LINE + | |
1342 | + "## Duration alarm condition" + NEW_LINE + | |
1343 | + DEVICE_PROFILE_ALARM_CONDITION_DURATION_EXAMPLE + NEW_LINE + | |
1344 | + "**'unit'** can be: \n" + | |
1345 | + " * 'SECONDS';\n" + | |
1346 | + " * 'MINUTES';\n" + | |
1347 | + " * 'HOURS';\n" + | |
1348 | + " * 'DAYS'." + NEW_LINE; | |
1349 | + | |
1350 | + protected static final String PROVISION_CONFIGURATION = "# Provision Configuration" + NEW_LINE + | |
1351 | + "There are 3 types of device provision configuration for the device profile: \n" + | |
1352 | + " * 'DISABLED';\n" + | |
1353 | + " * 'ALLOW_CREATE_NEW_DEVICES';\n" + | |
1354 | + " * 'CHECK_PRE_PROVISIONED_DEVICES'." + NEW_LINE + | |
1355 | + "Please refer to the [docs](https://thingsboard.io/docs/user-guide/device-provisioning/) for more details." + NEW_LINE; | |
1356 | + | |
1357 | + protected static final String DEVICE_PROFILE_DATA = DEVICE_PROFILE_DATA_DEFINITION + ALARM_SCHEDULE + ALARM_CONDITION_TYPE + | |
1358 | + KEY_FILTERS_DESCRIPTION + PROVISION_CONFIGURATION + TRANSPORT_CONFIGURATION; | |
1359 | + | |
1360 | + protected static final String DEVICE_PROFILE_ID = "deviceProfileId"; | |
1361 | + | |
1362 | +} | ... | ... |
... | ... | @@ -18,6 +18,8 @@ package org.thingsboard.server.controller; |
18 | 18 | import com.fasterxml.jackson.databind.JsonNode; |
19 | 19 | import com.fasterxml.jackson.databind.ObjectMapper; |
20 | 20 | import com.fasterxml.jackson.databind.node.ObjectNode; |
21 | +import io.swagger.annotations.ApiOperation; | |
22 | +import io.swagger.annotations.ApiParam; | |
21 | 23 | import org.springframework.http.HttpStatus; |
22 | 24 | import org.springframework.security.access.prepost.PreAuthorize; |
23 | 25 | import org.springframework.web.bind.annotation.PathVariable; |
... | ... | @@ -45,23 +47,43 @@ import org.thingsboard.server.service.security.permission.Resource; |
45 | 47 | |
46 | 48 | import java.util.List; |
47 | 49 | |
50 | +import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID; | |
51 | +import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID_PARAM_DESCRIPTION; | |
52 | +import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_SORT_PROPERTY_ALLOWABLE_VALUES; | |
53 | +import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_TEXT_SEARCH_DESCRIPTION; | |
54 | +import static org.thingsboard.server.controller.ControllerConstants.HOME_DASHBOARD; | |
55 | +import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; | |
56 | +import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; | |
57 | +import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; | |
58 | +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; | |
59 | +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; | |
60 | +import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; | |
61 | +import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; | |
62 | +import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; | |
63 | + | |
48 | 64 | @RestController |
49 | 65 | @TbCoreComponent |
50 | 66 | @RequestMapping("/api") |
51 | 67 | public class CustomerController extends BaseController { |
52 | 68 | |
53 | - public static final String CUSTOMER_ID = "customerId"; | |
54 | 69 | public static final String IS_PUBLIC = "isPublic"; |
70 | + public static final String CUSTOMER_SECURITY_CHECK = "If the user has the authority of 'Tenant Administrator', the server checks that the customer is owned by the same tenant. " + | |
71 | + "If the user has the authority of 'Customer User', the server checks that the user belongs to the customer."; | |
55 | 72 | |
73 | + @ApiOperation(value = "Get Customer (getCustomerById)", | |
74 | + notes = "Get the Customer object based on the provided Customer Id. " | |
75 | + + CUSTOMER_SECURITY_CHECK + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) | |
56 | 76 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
57 | 77 | @RequestMapping(value = "/customer/{customerId}", method = RequestMethod.GET) |
58 | 78 | @ResponseBody |
59 | - public Customer getCustomerById(@PathVariable(CUSTOMER_ID) String strCustomerId) throws ThingsboardException { | |
79 | + public Customer getCustomerById( | |
80 | + @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) | |
81 | + @PathVariable(CUSTOMER_ID) String strCustomerId) throws ThingsboardException { | |
60 | 82 | checkParameter(CUSTOMER_ID, strCustomerId); |
61 | 83 | try { |
62 | 84 | CustomerId customerId = new CustomerId(toUUID(strCustomerId)); |
63 | 85 | Customer customer = checkCustomerId(customerId, Operation.READ); |
64 | - if(!customer.getAdditionalInfo().isNull()) { | |
86 | + if (!customer.getAdditionalInfo().isNull()) { | |
65 | 87 | processDashboardIdFromAdditionalInfo((ObjectNode) customer.getAdditionalInfo(), HOME_DASHBOARD); |
66 | 88 | } |
67 | 89 | return customer; |
... | ... | @@ -70,10 +92,16 @@ public class CustomerController extends BaseController { |
70 | 92 | } |
71 | 93 | } |
72 | 94 | |
95 | + | |
96 | + @ApiOperation(value = "Get short Customer info (getShortCustomerInfoById)", | |
97 | + notes = "Get the short customer object that contains only the title and 'isPublic' flag. " | |
98 | + + CUSTOMER_SECURITY_CHECK + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) | |
73 | 99 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
74 | 100 | @RequestMapping(value = "/customer/{customerId}/shortInfo", method = RequestMethod.GET) |
75 | 101 | @ResponseBody |
76 | - public JsonNode getShortCustomerInfoById(@PathVariable(CUSTOMER_ID) String strCustomerId) throws ThingsboardException { | |
102 | + public JsonNode getShortCustomerInfoById( | |
103 | + @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) | |
104 | + @PathVariable(CUSTOMER_ID) String strCustomerId) throws ThingsboardException { | |
77 | 105 | checkParameter(CUSTOMER_ID, strCustomerId); |
78 | 106 | try { |
79 | 107 | CustomerId customerId = new CustomerId(toUUID(strCustomerId)); |
... | ... | @@ -88,10 +116,15 @@ public class CustomerController extends BaseController { |
88 | 116 | } |
89 | 117 | } |
90 | 118 | |
119 | + @ApiOperation(value = "Get Customer Title (getCustomerTitleById)", | |
120 | + notes = "Get the title of the customer. " | |
121 | + + CUSTOMER_SECURITY_CHECK + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) | |
91 | 122 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
92 | 123 | @RequestMapping(value = "/customer/{customerId}/title", method = RequestMethod.GET, produces = "application/text") |
93 | 124 | @ResponseBody |
94 | - public String getCustomerTitleById(@PathVariable(CUSTOMER_ID) String strCustomerId) throws ThingsboardException { | |
125 | + public String getCustomerTitleById( | |
126 | + @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) | |
127 | + @PathVariable(CUSTOMER_ID) String strCustomerId) throws ThingsboardException { | |
95 | 128 | checkParameter(CUSTOMER_ID, strCustomerId); |
96 | 129 | try { |
97 | 130 | CustomerId customerId = new CustomerId(toUUID(strCustomerId)); |
... | ... | @@ -102,10 +135,15 @@ public class CustomerController extends BaseController { |
102 | 135 | } |
103 | 136 | } |
104 | 137 | |
138 | + @ApiOperation(value = "Create or update Customer (saveCustomer)", | |
139 | + notes = "Creates or Updates the Customer. When creating customer, platform generates Customer Id as [time-based UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_1_(date-time_and_MAC_address) " + | |
140 | + "The newly created Customer Id will be present in the response. " + | |
141 | + "Specify existing Customer Id to update the Customer. " + | |
142 | + "Referencing non-existing Customer Id will cause 'Not Found' error." + TENANT_AUTHORITY_PARAGRAPH) | |
105 | 143 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
106 | 144 | @RequestMapping(value = "/customer", method = RequestMethod.POST) |
107 | 145 | @ResponseBody |
108 | - public Customer saveCustomer(@RequestBody Customer customer) throws ThingsboardException { | |
146 | + public Customer saveCustomer(@ApiParam(value = "A JSON value representing the customer.") @RequestBody Customer customer) throws ThingsboardException { | |
109 | 147 | try { |
110 | 148 | customer.setTenantId(getCurrentUser().getTenantId()); |
111 | 149 | |
... | ... | @@ -131,10 +169,15 @@ public class CustomerController extends BaseController { |
131 | 169 | } |
132 | 170 | } |
133 | 171 | |
172 | + @ApiOperation(value = "Delete Customer (deleteCustomer)", | |
173 | + notes = "Deletes the Customer and all customer Users. " + | |
174 | + "All assigned Dashboards, Assets, Devices, etc. will be unassigned but not deleted. " + | |
175 | + "Referencing non-existing Customer Id will cause an error." + TENANT_AUTHORITY_PARAGRAPH) | |
134 | 176 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
135 | 177 | @RequestMapping(value = "/customer/{customerId}", method = RequestMethod.DELETE) |
136 | 178 | @ResponseStatus(value = HttpStatus.OK) |
137 | - public void deleteCustomer(@PathVariable(CUSTOMER_ID) String strCustomerId) throws ThingsboardException { | |
179 | + public void deleteCustomer(@ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) | |
180 | + @PathVariable(CUSTOMER_ID) String strCustomerId) throws ThingsboardException { | |
138 | 181 | checkParameter(CUSTOMER_ID, strCustomerId); |
139 | 182 | try { |
140 | 183 | CustomerId customerId = new CustomerId(toUUID(strCustomerId)); |
... | ... | @@ -161,14 +204,23 @@ public class CustomerController extends BaseController { |
161 | 204 | } |
162 | 205 | } |
163 | 206 | |
207 | + @ApiOperation(value = "Get Tenant Customers (getCustomers)", | |
208 | + notes = "Returns a page of customers owned by tenant. " + | |
209 | + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH) | |
164 | 210 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
165 | 211 | @RequestMapping(value = "/customers", params = {"pageSize", "page"}, method = RequestMethod.GET) |
166 | 212 | @ResponseBody |
167 | - public PageData<Customer> getCustomers(@RequestParam int pageSize, | |
168 | - @RequestParam int page, | |
169 | - @RequestParam(required = false) String textSearch, | |
170 | - @RequestParam(required = false) String sortProperty, | |
171 | - @RequestParam(required = false) String sortOrder) throws ThingsboardException { | |
213 | + public PageData<Customer> getCustomers( | |
214 | + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) | |
215 | + @RequestParam int pageSize, | |
216 | + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) | |
217 | + @RequestParam int page, | |
218 | + @ApiParam(value = CUSTOMER_TEXT_SEARCH_DESCRIPTION) | |
219 | + @RequestParam(required = false) String textSearch, | |
220 | + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = CUSTOMER_SORT_PROPERTY_ALLOWABLE_VALUES) | |
221 | + @RequestParam(required = false) String sortProperty, | |
222 | + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) | |
223 | + @RequestParam(required = false) String sortOrder) throws ThingsboardException { | |
172 | 224 | try { |
173 | 225 | PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); |
174 | 226 | TenantId tenantId = getCurrentUser().getTenantId(); |
... | ... | @@ -178,10 +230,13 @@ public class CustomerController extends BaseController { |
178 | 230 | } |
179 | 231 | } |
180 | 232 | |
233 | + @ApiOperation(value = "Get Tenant Customer by Customer title (getTenantCustomer)", | |
234 | + notes = "Get the Customer using Customer Title. " + TENANT_AUTHORITY_PARAGRAPH) | |
181 | 235 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
182 | 236 | @RequestMapping(value = "/tenant/customers", params = {"customerTitle"}, method = RequestMethod.GET) |
183 | 237 | @ResponseBody |
184 | 238 | public Customer getTenantCustomer( |
239 | + @ApiParam(value = "A string value representing the Customer title.") | |
185 | 240 | @RequestParam String customerTitle) throws ThingsboardException { |
186 | 241 | try { |
187 | 242 | TenantId tenantId = getCurrentUser().getTenantId(); | ... | ... |
... | ... | @@ -17,8 +17,11 @@ package org.thingsboard.server.controller; |
17 | 17 | |
18 | 18 | import com.fasterxml.jackson.databind.JsonNode; |
19 | 19 | import com.fasterxml.jackson.databind.node.ObjectNode; |
20 | +import io.swagger.annotations.ApiOperation; | |
21 | +import io.swagger.annotations.ApiParam; | |
20 | 22 | import org.springframework.beans.factory.annotation.Value; |
21 | 23 | import org.springframework.http.HttpStatus; |
24 | +import org.springframework.http.MediaType; | |
22 | 25 | import org.springframework.security.access.prepost.PreAuthorize; |
23 | 26 | import org.springframework.web.bind.annotation.PathVariable; |
24 | 27 | import org.springframework.web.bind.annotation.RequestBody; |
... | ... | @@ -28,6 +31,7 @@ import org.springframework.web.bind.annotation.RequestParam; |
28 | 31 | import org.springframework.web.bind.annotation.ResponseBody; |
29 | 32 | import org.springframework.web.bind.annotation.ResponseStatus; |
30 | 33 | import org.springframework.web.bind.annotation.RestController; |
34 | +import org.thingsboard.common.util.JacksonUtil; | |
31 | 35 | import org.thingsboard.server.common.data.Customer; |
32 | 36 | import org.thingsboard.server.common.data.Dashboard; |
33 | 37 | import org.thingsboard.server.common.data.DashboardInfo; |
... | ... | @@ -47,7 +51,6 @@ import org.thingsboard.server.common.data.id.EdgeId; |
47 | 51 | import org.thingsboard.server.common.data.id.TenantId; |
48 | 52 | import org.thingsboard.server.common.data.page.PageData; |
49 | 53 | import org.thingsboard.server.common.data.page.PageLink; |
50 | -import org.thingsboard.common.util.JacksonUtil; | |
51 | 54 | import org.thingsboard.server.common.data.page.TimePageLink; |
52 | 55 | import org.thingsboard.server.queue.util.TbCoreComponent; |
53 | 56 | import org.thingsboard.server.service.security.model.SecurityUser; |
... | ... | @@ -59,6 +62,27 @@ import java.util.List; |
59 | 62 | import java.util.Set; |
60 | 63 | import java.util.stream.Collectors; |
61 | 64 | |
65 | +import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID; | |
66 | +import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID_PARAM_DESCRIPTION; | |
67 | +import static org.thingsboard.server.controller.ControllerConstants.DASHBOARD_ID_PARAM_DESCRIPTION; | |
68 | +import static org.thingsboard.server.controller.ControllerConstants.DASHBOARD_SORT_PROPERTY_ALLOWABLE_VALUES; | |
69 | +import static org.thingsboard.server.controller.ControllerConstants.DASHBOARD_TEXT_SEARCH_DESCRIPTION; | |
70 | +import static org.thingsboard.server.controller.ControllerConstants.EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION; | |
71 | +import static org.thingsboard.server.controller.ControllerConstants.EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION; | |
72 | +import static org.thingsboard.server.controller.ControllerConstants.EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION; | |
73 | +import static org.thingsboard.server.controller.ControllerConstants.EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION; | |
74 | +import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; | |
75 | +import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; | |
76 | +import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; | |
77 | +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; | |
78 | +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; | |
79 | +import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; | |
80 | +import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH; | |
81 | +import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; | |
82 | +import static org.thingsboard.server.controller.ControllerConstants.TENANT_ID; | |
83 | +import static org.thingsboard.server.controller.ControllerConstants.TENANT_ID_PARAM_DESCRIPTION; | |
84 | +import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; | |
85 | + | |
62 | 86 | @RestController |
63 | 87 | @TbCoreComponent |
64 | 88 | @RequestMapping("/api") |
... | ... | @@ -68,11 +92,16 @@ public class DashboardController extends BaseController { |
68 | 92 | |
69 | 93 | private static final String HOME_DASHBOARD_ID = "homeDashboardId"; |
70 | 94 | private static final String HOME_DASHBOARD_HIDE_TOOLBAR = "homeDashboardHideToolbar"; |
95 | + public static final String DASHBOARD_INFO_DEFINITION = "The Dashboard Info object contains lightweight information about the dashboard (e.g. title, image, assigned customers) but does not contain the heavyweight configuration JSON."; | |
96 | + public static final String DASHBOARD_DEFINITION = "The Dashboard object is a heavyweight object that contains information about the dashboard (e.g. title, image, assigned customers) and also configuration JSON (e.g. layouts, widgets, entity aliases)."; | |
97 | + public static final String HIDDEN_FOR_MOBILE = "Exclude dashboards that are hidden for mobile"; | |
71 | 98 | |
72 | 99 | @Value("${dashboard.max_datapoints_limit}") |
73 | 100 | private long maxDatapointsLimit; |
74 | 101 | |
75 | - | |
102 | + @ApiOperation(value = "Get server time (getServerTime)", | |
103 | + notes = "Get the server time (milliseconds since January 1, 1970 UTC). " + | |
104 | + "Used to adjust view of the dashboards according to the difference between browser and server time.") | |
76 | 105 | @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") |
77 | 106 | @RequestMapping(value = "/dashboard/serverTime", method = RequestMethod.GET) |
78 | 107 | @ResponseBody |
... | ... | @@ -80,6 +109,11 @@ public class DashboardController extends BaseController { |
80 | 109 | return System.currentTimeMillis(); |
81 | 110 | } |
82 | 111 | |
112 | + @ApiOperation(value = "Get max data points limit (getMaxDatapointsLimit)", | |
113 | + notes = "Get the maximum number of data points that dashboard may request from the server per in a single subscription command. " + | |
114 | + "This value impacts the time window behavior. It impacts 'Max values' parameter in case user selects 'None' as 'Data aggregation function'. " + | |
115 | + "It also impacts the 'Grouping interval' in case of any other 'Data aggregation function' is selected. " + | |
116 | + "The actual value of the limit is configurable in the system configuration file.") | |
83 | 117 | @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") |
84 | 118 | @RequestMapping(value = "/dashboard/maxDatapointsLimit", method = RequestMethod.GET) |
85 | 119 | @ResponseBody |
... | ... | @@ -87,10 +121,16 @@ public class DashboardController extends BaseController { |
87 | 121 | return maxDatapointsLimit; |
88 | 122 | } |
89 | 123 | |
124 | + @ApiOperation(value = "Get Dashboard Info (getDashboardInfoById)", | |
125 | + notes = "Get the information about the dashboard based on 'dashboardId' parameter. " + DASHBOARD_INFO_DEFINITION, | |
126 | + produces = MediaType.APPLICATION_JSON_VALUE | |
127 | + ) | |
90 | 128 | @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") |
91 | 129 | @RequestMapping(value = "/dashboard/info/{dashboardId}", method = RequestMethod.GET) |
92 | 130 | @ResponseBody |
93 | - public DashboardInfo getDashboardInfoById(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { | |
131 | + public DashboardInfo getDashboardInfoById( | |
132 | + @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) | |
133 | + @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { | |
94 | 134 | checkParameter(DASHBOARD_ID, strDashboardId); |
95 | 135 | try { |
96 | 136 | DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); |
... | ... | @@ -100,10 +140,16 @@ public class DashboardController extends BaseController { |
100 | 140 | } |
101 | 141 | } |
102 | 142 | |
143 | + @ApiOperation(value = "Get Dashboard (getDashboardById)", | |
144 | + notes = "Get the dashboard based on 'dashboardId' parameter. " + DASHBOARD_DEFINITION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, | |
145 | + produces = MediaType.APPLICATION_JSON_VALUE | |
146 | + ) | |
103 | 147 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
104 | 148 | @RequestMapping(value = "/dashboard/{dashboardId}", method = RequestMethod.GET) |
105 | 149 | @ResponseBody |
106 | - public Dashboard getDashboardById(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { | |
150 | + public Dashboard getDashboardById( | |
151 | + @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) | |
152 | + @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { | |
107 | 153 | checkParameter(DASHBOARD_ID, strDashboardId); |
108 | 154 | try { |
109 | 155 | DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); |
... | ... | @@ -113,10 +159,20 @@ public class DashboardController extends BaseController { |
113 | 159 | } |
114 | 160 | } |
115 | 161 | |
162 | + @ApiOperation(value = "Create Or Update Dashboard (saveDashboard)", | |
163 | + notes = "Create or update the Dashboard. When creating dashboard, platform generates Dashboard Id as [time-based UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_1_(date-time_and_MAC_address)." + | |
164 | + "The newly created Dashboard id will be present in the response. " + | |
165 | + "Specify existing Dashboard id to update the dashboard. " + | |
166 | + "Referencing non-existing dashboard Id will cause 'Not Found' error. " + | |
167 | + TENANT_AUTHORITY_PARAGRAPH, | |
168 | + produces = MediaType.APPLICATION_JSON_VALUE, | |
169 | + consumes = MediaType.APPLICATION_JSON_VALUE) | |
116 | 170 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
117 | 171 | @RequestMapping(value = "/dashboard", method = RequestMethod.POST) |
118 | 172 | @ResponseBody |
119 | - public Dashboard saveDashboard(@RequestBody Dashboard dashboard) throws ThingsboardException { | |
173 | + public Dashboard saveDashboard( | |
174 | + @ApiParam(value = "A JSON value representing the dashboard.") | |
175 | + @RequestBody Dashboard dashboard) throws ThingsboardException { | |
120 | 176 | try { |
121 | 177 | dashboard.setTenantId(getCurrentUser().getTenantId()); |
122 | 178 | |
... | ... | @@ -141,10 +197,14 @@ public class DashboardController extends BaseController { |
141 | 197 | } |
142 | 198 | } |
143 | 199 | |
200 | + @ApiOperation(value = "Delete the Dashboard (deleteDashboard)", | |
201 | + notes = "Delete the Dashboard." + TENANT_AUTHORITY_PARAGRAPH) | |
144 | 202 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
145 | 203 | @RequestMapping(value = "/dashboard/{dashboardId}", method = RequestMethod.DELETE) |
146 | 204 | @ResponseStatus(value = HttpStatus.OK) |
147 | - public void deleteDashboard(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { | |
205 | + public void deleteDashboard( | |
206 | + @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) | |
207 | + @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { | |
148 | 208 | checkParameter(DASHBOARD_ID, strDashboardId); |
149 | 209 | try { |
150 | 210 | DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); |
... | ... | @@ -170,12 +230,19 @@ public class DashboardController extends BaseController { |
170 | 230 | } |
171 | 231 | } |
172 | 232 | |
233 | + @ApiOperation(value = "Assign the Dashboard (assignDashboardToCustomer)", | |
234 | + notes = "Assign the Dashboard to specified Customer or do nothing if the Dashboard is already assigned to that Customer. " + | |
235 | + "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH, | |
236 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
173 | 237 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
174 | 238 | @RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.POST) |
175 | 239 | @ResponseBody |
176 | - public Dashboard assignDashboardToCustomer(@PathVariable("customerId") String strCustomerId, | |
177 | - @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { | |
178 | - checkParameter("customerId", strCustomerId); | |
240 | + public Dashboard assignDashboardToCustomer( | |
241 | + @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) | |
242 | + @PathVariable(CUSTOMER_ID) String strCustomerId, | |
243 | + @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) | |
244 | + @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { | |
245 | + checkParameter(CUSTOMER_ID, strCustomerId); | |
179 | 246 | checkParameter(DASHBOARD_ID, strDashboardId); |
180 | 247 | try { |
181 | 248 | CustomerId customerId = new CustomerId(toUUID(strCustomerId)); |
... | ... | @@ -203,11 +270,18 @@ public class DashboardController extends BaseController { |
203 | 270 | } |
204 | 271 | } |
205 | 272 | |
273 | + @ApiOperation(value = "Unassign the Dashboard (unassignDashboardFromCustomer)", | |
274 | + notes = "Unassign the Dashboard from specified Customer or do nothing if the Dashboard is already assigned to that Customer. " + | |
275 | + "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH, | |
276 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
206 | 277 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
207 | 278 | @RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.DELETE) |
208 | 279 | @ResponseBody |
209 | - public Dashboard unassignDashboardFromCustomer(@PathVariable("customerId") String strCustomerId, | |
210 | - @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { | |
280 | + public Dashboard unassignDashboardFromCustomer( | |
281 | + @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) | |
282 | + @PathVariable(CUSTOMER_ID) String strCustomerId, | |
283 | + @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) | |
284 | + @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { | |
211 | 285 | checkParameter("customerId", strCustomerId); |
212 | 286 | checkParameter(DASHBOARD_ID, strDashboardId); |
213 | 287 | try { |
... | ... | @@ -235,11 +309,20 @@ public class DashboardController extends BaseController { |
235 | 309 | } |
236 | 310 | } |
237 | 311 | |
312 | + @ApiOperation(value = "Update the Dashboard Customers (updateDashboardCustomers)", | |
313 | + notes = "Updates the list of Customers that this Dashboard is assigned to. Removes previous assignments to customers that are not in the provided list. " + | |
314 | + "Returns the Dashboard object. " + TENANT_AUTHORITY_PARAGRAPH, | |
315 | + produces = MediaType.APPLICATION_JSON_VALUE, | |
316 | + consumes = MediaType.APPLICATION_JSON_VALUE) | |
317 | + | |
238 | 318 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
239 | 319 | @RequestMapping(value = "/dashboard/{dashboardId}/customers", method = RequestMethod.POST) |
240 | 320 | @ResponseBody |
241 | - public Dashboard updateDashboardCustomers(@PathVariable(DASHBOARD_ID) String strDashboardId, | |
242 | - @RequestBody(required = false) String[] strCustomerIds) throws ThingsboardException { | |
321 | + public Dashboard updateDashboardCustomers( | |
322 | + @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) | |
323 | + @PathVariable(DASHBOARD_ID) String strDashboardId, | |
324 | + @ApiParam(value = "JSON array with the list of customer ids, or empty to remove all customers") | |
325 | + @RequestBody(required = false) String[] strCustomerIds) throws ThingsboardException { | |
243 | 326 | checkParameter(DASHBOARD_ID, strDashboardId); |
244 | 327 | try { |
245 | 328 | DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); |
... | ... | @@ -301,11 +384,19 @@ public class DashboardController extends BaseController { |
301 | 384 | } |
302 | 385 | } |
303 | 386 | |
387 | + @ApiOperation(value = "Adds the Dashboard Customers (addDashboardCustomers)", | |
388 | + notes = "Adds the list of Customers to the existing list of assignments for the Dashboard. Keeps previous assignments to customers that are not in the provided list. " + | |
389 | + "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH, | |
390 | + produces = MediaType.APPLICATION_JSON_VALUE, | |
391 | + consumes = MediaType.APPLICATION_JSON_VALUE) | |
304 | 392 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
305 | 393 | @RequestMapping(value = "/dashboard/{dashboardId}/customers/add", method = RequestMethod.POST) |
306 | 394 | @ResponseBody |
307 | - public Dashboard addDashboardCustomers(@PathVariable(DASHBOARD_ID) String strDashboardId, | |
308 | - @RequestBody String[] strCustomerIds) throws ThingsboardException { | |
395 | + public Dashboard addDashboardCustomers( | |
396 | + @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) | |
397 | + @PathVariable(DASHBOARD_ID) String strDashboardId, | |
398 | + @ApiParam(value = "JSON array with the list of customer ids") | |
399 | + @RequestBody String[] strCustomerIds) throws ThingsboardException { | |
309 | 400 | checkParameter(DASHBOARD_ID, strDashboardId); |
310 | 401 | try { |
311 | 402 | DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); |
... | ... | @@ -345,11 +436,19 @@ public class DashboardController extends BaseController { |
345 | 436 | } |
346 | 437 | } |
347 | 438 | |
439 | + @ApiOperation(value = "Remove the Dashboard Customers (removeDashboardCustomers)", | |
440 | + notes = "Removes the list of Customers from the existing list of assignments for the Dashboard. Keeps other assignments to customers that are not in the provided list. " + | |
441 | + "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH, | |
442 | + produces = MediaType.APPLICATION_JSON_VALUE, | |
443 | + consumes = MediaType.APPLICATION_JSON_VALUE) | |
348 | 444 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
349 | 445 | @RequestMapping(value = "/dashboard/{dashboardId}/customers/remove", method = RequestMethod.POST) |
350 | 446 | @ResponseBody |
351 | - public Dashboard removeDashboardCustomers(@PathVariable(DASHBOARD_ID) String strDashboardId, | |
352 | - @RequestBody String[] strCustomerIds) throws ThingsboardException { | |
447 | + public Dashboard removeDashboardCustomers( | |
448 | + @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) | |
449 | + @PathVariable(DASHBOARD_ID) String strDashboardId, | |
450 | + @ApiParam(value = "JSON array with the list of customer ids") | |
451 | + @RequestBody String[] strCustomerIds) throws ThingsboardException { | |
353 | 452 | checkParameter(DASHBOARD_ID, strDashboardId); |
354 | 453 | try { |
355 | 454 | DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); |
... | ... | @@ -389,10 +488,20 @@ public class DashboardController extends BaseController { |
389 | 488 | } |
390 | 489 | } |
391 | 490 | |
491 | + @ApiOperation(value = "Assign the Dashboard to Public Customer (assignDashboardToPublicCustomer)", | |
492 | + notes = "Assigns the dashboard to a special, auto-generated 'Public' Customer. Once assigned, unauthenticated users may browse the dashboard. " + | |
493 | + "This method is useful if you like to embed the dashboard on public web pages to be available for users that are not logged in. " + | |
494 | + "Be aware that making the dashboard public does not mean that it automatically makes all devices and assets you use in the dashboard to be public." + | |
495 | + "Use [assign Asset to Public Customer](#!/asset-controller/assignAssetToPublicCustomerUsingPOST) and " + | |
496 | + "[assign Device to Public Customer](#!/device-controller/assignDeviceToPublicCustomerUsingPOST) for this purpose. " + | |
497 | + "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH, | |
498 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
392 | 499 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
393 | 500 | @RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.POST) |
394 | 501 | @ResponseBody |
395 | - public Dashboard assignDashboardToPublicCustomer(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { | |
502 | + public Dashboard assignDashboardToPublicCustomer( | |
503 | + @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) | |
504 | + @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { | |
396 | 505 | checkParameter(DASHBOARD_ID, strDashboardId); |
397 | 506 | try { |
398 | 507 | DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); |
... | ... | @@ -415,10 +524,16 @@ public class DashboardController extends BaseController { |
415 | 524 | } |
416 | 525 | } |
417 | 526 | |
527 | + @ApiOperation(value = "Unassign the Dashboard from Public Customer (unassignDashboardFromPublicCustomer)", | |
528 | + notes = "Unassigns the dashboard from a special, auto-generated 'Public' Customer. Once unassigned, unauthenticated users may no longer browse the dashboard. " + | |
529 | + "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH, | |
530 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
418 | 531 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
419 | 532 | @RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.DELETE) |
420 | 533 | @ResponseBody |
421 | - public Dashboard unassignDashboardFromPublicCustomer(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { | |
534 | + public Dashboard unassignDashboardFromPublicCustomer( | |
535 | + @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) | |
536 | + @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { | |
422 | 537 | checkParameter(DASHBOARD_ID, strDashboardId); |
423 | 538 | try { |
424 | 539 | DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); |
... | ... | @@ -442,15 +557,25 @@ public class DashboardController extends BaseController { |
442 | 557 | } |
443 | 558 | } |
444 | 559 | |
560 | + @ApiOperation(value = "Get Tenant Dashboards by System Administrator (getTenantDashboards)", | |
561 | + notes = "Returns a page of dashboard info objects owned by tenant. " + DASHBOARD_INFO_DEFINITION + " " + PAGE_DATA_PARAMETERS + | |
562 | + SYSTEM_AUTHORITY_PARAGRAPH, | |
563 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
445 | 564 | @PreAuthorize("hasAuthority('SYS_ADMIN')") |
446 | 565 | @RequestMapping(value = "/tenant/{tenantId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET) |
447 | 566 | @ResponseBody |
448 | 567 | public PageData<DashboardInfo> getTenantDashboards( |
449 | - @PathVariable("tenantId") String strTenantId, | |
568 | + @ApiParam(value = TENANT_ID_PARAM_DESCRIPTION, required = true) | |
569 | + @PathVariable(TENANT_ID) String strTenantId, | |
570 | + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) | |
450 | 571 | @RequestParam int pageSize, |
572 | + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) | |
451 | 573 | @RequestParam int page, |
574 | + @ApiParam(value = DASHBOARD_TEXT_SEARCH_DESCRIPTION) | |
452 | 575 | @RequestParam(required = false) String textSearch, |
576 | + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DASHBOARD_SORT_PROPERTY_ALLOWABLE_VALUES) | |
453 | 577 | @RequestParam(required = false) String sortProperty, |
578 | + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) | |
454 | 579 | @RequestParam(required = false) String sortOrder) throws ThingsboardException { |
455 | 580 | try { |
456 | 581 | TenantId tenantId = new TenantId(toUUID(strTenantId)); |
... | ... | @@ -462,20 +587,30 @@ public class DashboardController extends BaseController { |
462 | 587 | } |
463 | 588 | } |
464 | 589 | |
590 | + @ApiOperation(value = "Get Tenant Dashboards (getTenantDashboards)", | |
591 | + notes = "Returns a page of dashboard info objects owned by the tenant of a current user. " | |
592 | + + DASHBOARD_INFO_DEFINITION + " " + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, | |
593 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
465 | 594 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
466 | 595 | @RequestMapping(value = "/tenant/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET) |
467 | 596 | @ResponseBody |
468 | 597 | public PageData<DashboardInfo> getTenantDashboards( |
598 | + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) | |
469 | 599 | @RequestParam int pageSize, |
600 | + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) | |
470 | 601 | @RequestParam int page, |
602 | + @ApiParam(value = HIDDEN_FOR_MOBILE) | |
471 | 603 | @RequestParam(required = false) Boolean mobile, |
604 | + @ApiParam(value = DASHBOARD_TEXT_SEARCH_DESCRIPTION) | |
472 | 605 | @RequestParam(required = false) String textSearch, |
606 | + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DASHBOARD_SORT_PROPERTY_ALLOWABLE_VALUES) | |
473 | 607 | @RequestParam(required = false) String sortProperty, |
608 | + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) | |
474 | 609 | @RequestParam(required = false) String sortOrder) throws ThingsboardException { |
475 | 610 | try { |
476 | 611 | TenantId tenantId = getCurrentUser().getTenantId(); |
477 | 612 | PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); |
478 | - if (mobile != null && mobile.booleanValue()) { | |
613 | + if (mobile != null && mobile) { | |
479 | 614 | return checkNotNull(dashboardService.findMobileDashboardsByTenantId(tenantId, pageLink)); |
480 | 615 | } else { |
481 | 616 | return checkNotNull(dashboardService.findDashboardsByTenantId(tenantId, pageLink)); |
... | ... | @@ -485,24 +620,35 @@ public class DashboardController extends BaseController { |
485 | 620 | } |
486 | 621 | } |
487 | 622 | |
623 | + @ApiOperation(value = "Get Customer Dashboards (getCustomerDashboards)", | |
624 | + notes = "Returns a page of dashboard info objects owned by the specified customer. " | |
625 | + + DASHBOARD_INFO_DEFINITION + " " + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, | |
626 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
488 | 627 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
489 | 628 | @RequestMapping(value = "/customer/{customerId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET) |
490 | 629 | @ResponseBody |
491 | 630 | public PageData<DashboardInfo> getCustomerDashboards( |
492 | - @PathVariable("customerId") String strCustomerId, | |
631 | + @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION, required = true) | |
632 | + @PathVariable(CUSTOMER_ID) String strCustomerId, | |
633 | + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) | |
493 | 634 | @RequestParam int pageSize, |
635 | + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) | |
494 | 636 | @RequestParam int page, |
637 | + @ApiParam(value = HIDDEN_FOR_MOBILE) | |
495 | 638 | @RequestParam(required = false) Boolean mobile, |
639 | + @ApiParam(value = DASHBOARD_TEXT_SEARCH_DESCRIPTION) | |
496 | 640 | @RequestParam(required = false) String textSearch, |
641 | + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DASHBOARD_SORT_PROPERTY_ALLOWABLE_VALUES) | |
497 | 642 | @RequestParam(required = false) String sortProperty, |
643 | + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) | |
498 | 644 | @RequestParam(required = false) String sortOrder) throws ThingsboardException { |
499 | - checkParameter("customerId", strCustomerId); | |
645 | + checkParameter(CUSTOMER_ID, strCustomerId); | |
500 | 646 | try { |
501 | 647 | TenantId tenantId = getCurrentUser().getTenantId(); |
502 | 648 | CustomerId customerId = new CustomerId(toUUID(strCustomerId)); |
503 | 649 | checkCustomerId(customerId, Operation.READ); |
504 | 650 | PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); |
505 | - if (mobile != null && mobile.booleanValue()) { | |
651 | + if (mobile != null && mobile) { | |
506 | 652 | return checkNotNull(dashboardService.findMobileDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink)); |
507 | 653 | } else { |
508 | 654 | return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink)); |
... | ... | @@ -512,6 +658,12 @@ public class DashboardController extends BaseController { |
512 | 658 | } |
513 | 659 | } |
514 | 660 | |
661 | + @ApiOperation(value = "Get Home Dashboard (getHomeDashboard)", | |
662 | + notes = "Returns the home dashboard object that is configured as 'homeDashboardId' parameter in the 'additionalInfo' of the User. " + | |
663 | + "If 'homeDashboardId' parameter is not set on the User level and the User has authority 'CUSTOMER_USER', check the same parameter for the corresponding Customer. " + | |
664 | + "If 'homeDashboardId' parameter is not set on the User and Customer levels then checks the same parameter for the Tenant that owns the user. " | |
665 | + + DASHBOARD_DEFINITION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, | |
666 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
515 | 667 | @PreAuthorize("isAuthenticated()") |
516 | 668 | @RequestMapping(value = "/dashboard/home", method = RequestMethod.GET) |
517 | 669 | @ResponseBody |
... | ... | @@ -543,6 +695,12 @@ public class DashboardController extends BaseController { |
543 | 695 | } |
544 | 696 | } |
545 | 697 | |
698 | + @ApiOperation(value = "Get Home Dashboard Info (getHomeDashboardInfo)", | |
699 | + notes = "Returns the home dashboard info object that is configured as 'homeDashboardId' parameter in the 'additionalInfo' of the User. " + | |
700 | + "If 'homeDashboardId' parameter is not set on the User level and the User has authority 'CUSTOMER_USER', check the same parameter for the corresponding Customer. " + | |
701 | + "If 'homeDashboardId' parameter is not set on the User and Customer levels then checks the same parameter for the Tenant that owns the user. " + | |
702 | + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, | |
703 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
546 | 704 | @PreAuthorize("isAuthenticated()") |
547 | 705 | @RequestMapping(value = "/dashboard/home/info", method = RequestMethod.GET) |
548 | 706 | @ResponseBody |
... | ... | @@ -574,6 +732,10 @@ public class DashboardController extends BaseController { |
574 | 732 | } |
575 | 733 | } |
576 | 734 | |
735 | + @ApiOperation(value = "Get Tenant Home Dashboard Info (getTenantHomeDashboardInfo)", | |
736 | + notes = "Returns the home dashboard info object that is configured as 'homeDashboardId' parameter in the 'additionalInfo' of the corresponding tenant. " + | |
737 | + TENANT_AUTHORITY_PARAGRAPH, | |
738 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
577 | 739 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
578 | 740 | @RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.GET) |
579 | 741 | @ResponseBody |
... | ... | @@ -596,10 +758,16 @@ public class DashboardController extends BaseController { |
596 | 758 | } |
597 | 759 | } |
598 | 760 | |
761 | + @ApiOperation(value = "Update Tenant Home Dashboard Info (getTenantHomeDashboardInfo)", | |
762 | + notes = "Update the home dashboard assignment for the current tenant. " + | |
763 | + TENANT_AUTHORITY_PARAGRAPH, | |
764 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
599 | 765 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
600 | 766 | @RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.POST) |
601 | 767 | @ResponseStatus(value = HttpStatus.OK) |
602 | - public void setTenantHomeDashboardInfo(@RequestBody HomeDashboardInfo homeDashboardInfo) throws ThingsboardException { | |
768 | + public void setTenantHomeDashboardInfo( | |
769 | + @ApiParam(value = "A JSON object that represents home dashboard id and other parameters", required = true) | |
770 | + @RequestBody HomeDashboardInfo homeDashboardInfo) throws ThingsboardException { | |
603 | 771 | try { |
604 | 772 | if (homeDashboardInfo.getDashboardId() != null) { |
605 | 773 | checkDashboardId(homeDashboardInfo.getDashboardId(), Operation.READ); |
... | ... | @@ -635,7 +803,8 @@ public class DashboardController extends BaseController { |
635 | 803 | } |
636 | 804 | return new HomeDashboardInfo(dashboardId, hideDashboardToolbar); |
637 | 805 | } |
638 | - } catch (Exception e) {} | |
806 | + } catch (Exception e) { | |
807 | + } | |
639 | 808 | return null; |
640 | 809 | } |
641 | 810 | |
... | ... | @@ -651,10 +820,19 @@ public class DashboardController extends BaseController { |
651 | 820 | } |
652 | 821 | return new HomeDashboard(dashboard, hideDashboardToolbar); |
653 | 822 | } |
654 | - } catch (Exception e) {} | |
823 | + } catch (Exception e) { | |
824 | + } | |
655 | 825 | return null; |
656 | 826 | } |
657 | 827 | |
828 | + @ApiOperation(value = "Assign dashboard to edge (assignDashboardToEdge)", | |
829 | + notes = "Creates assignment of an existing dashboard to an instance of The Edge. " + | |
830 | + EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION + | |
831 | + "Second, remote edge service will receive a copy of assignment dashboard " + | |
832 | + EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION + ". " + | |
833 | + "Third, once dashboard will be delivered to edge service, it's going to be available for usage on remote edge instance." + | |
834 | + TENANT_AUTHORITY_PARAGRAPH, | |
835 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
658 | 836 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
659 | 837 | @RequestMapping(value = "/edge/{edgeId}/dashboard/{dashboardId}", method = RequestMethod.POST) |
660 | 838 | @ResponseBody |
... | ... | @@ -688,6 +866,14 @@ public class DashboardController extends BaseController { |
688 | 866 | } |
689 | 867 | } |
690 | 868 | |
869 | + @ApiOperation(value = "Unassign dashboard from edge (unassignDashboardFromEdge)", | |
870 | + notes = "Clears assignment of the dashboard to the edge. " + | |
871 | + EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION + | |
872 | + "Second, remote edge service will receive an 'unassign' command to remove dashboard " + | |
873 | + EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION + ". " + | |
874 | + "Third, once 'unassign' command will be delivered to edge service, it's going to remove dashboard locally." + | |
875 | + TENANT_AUTHORITY_PARAGRAPH, | |
876 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
691 | 877 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
692 | 878 | @RequestMapping(value = "/edge/{edgeId}/dashboard/{dashboardId}", method = RequestMethod.DELETE) |
693 | 879 | @ResponseBody | ... | ... |
... | ... | @@ -19,9 +19,12 @@ import com.google.common.util.concurrent.FutureCallback; |
19 | 19 | import com.google.common.util.concurrent.Futures; |
20 | 20 | import com.google.common.util.concurrent.ListenableFuture; |
21 | 21 | import com.google.common.util.concurrent.MoreExecutors; |
22 | +import io.swagger.annotations.ApiOperation; | |
23 | +import io.swagger.annotations.ApiParam; | |
22 | 24 | import lombok.RequiredArgsConstructor; |
23 | 25 | import lombok.extern.slf4j.Slf4j; |
24 | 26 | import org.springframework.http.HttpStatus; |
27 | +import org.springframework.http.MediaType; | |
25 | 28 | import org.springframework.http.ResponseEntity; |
26 | 29 | import org.springframework.security.access.prepost.PreAuthorize; |
27 | 30 | import org.springframework.web.bind.annotation.PathVariable; |
... | ... | @@ -83,6 +86,29 @@ import java.util.List; |
83 | 86 | import java.util.UUID; |
84 | 87 | import java.util.stream.Collectors; |
85 | 88 | |
89 | +import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_AUTHORITY_PARAGRAPH; | |
90 | +import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID_PARAM_DESCRIPTION; | |
91 | +import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID_PARAM_DESCRIPTION; | |
92 | +import static org.thingsboard.server.controller.ControllerConstants.DEVICE_INFO_DESCRIPTION; | |
93 | +import static org.thingsboard.server.controller.ControllerConstants.DEVICE_NAME_DESCRIPTION; | |
94 | +import static org.thingsboard.server.controller.ControllerConstants.DEVICE_PROFILE_ID_PARAM_DESCRIPTION; | |
95 | +import static org.thingsboard.server.controller.ControllerConstants.DEVICE_SORT_PROPERTY_ALLOWABLE_VALUES; | |
96 | +import static org.thingsboard.server.controller.ControllerConstants.DEVICE_TEXT_SEARCH_DESCRIPTION; | |
97 | +import static org.thingsboard.server.controller.ControllerConstants.DEVICE_TYPE_DESCRIPTION; | |
98 | +import static org.thingsboard.server.controller.ControllerConstants.EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION; | |
99 | +import static org.thingsboard.server.controller.ControllerConstants.EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION; | |
100 | +import static org.thingsboard.server.controller.ControllerConstants.EDGE_ID_PARAM_DESCRIPTION; | |
101 | +import static org.thingsboard.server.controller.ControllerConstants.EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION; | |
102 | +import static org.thingsboard.server.controller.ControllerConstants.EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION; | |
103 | +import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; | |
104 | +import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; | |
105 | +import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; | |
106 | +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; | |
107 | +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; | |
108 | +import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; | |
109 | +import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; | |
110 | +import static org.thingsboard.server.controller.ControllerConstants.TENANT_ID_PARAM_DESCRIPTION; | |
111 | +import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; | |
86 | 112 | import static org.thingsboard.server.controller.EdgeController.EDGE_ID; |
87 | 113 | |
88 | 114 | @RestController |
... | ... | @@ -91,16 +117,23 @@ import static org.thingsboard.server.controller.EdgeController.EDGE_ID; |
91 | 117 | @RequiredArgsConstructor |
92 | 118 | @Slf4j |
93 | 119 | public class DeviceController extends BaseController { |
94 | - private final DeviceBulkImportService deviceBulkImportService; | |
95 | 120 | |
96 | - private static final String DEVICE_ID = "deviceId"; | |
97 | - private static final String DEVICE_NAME = "deviceName"; | |
98 | - private static final String TENANT_ID = "tenantId"; | |
121 | + protected static final String DEVICE_ID = "deviceId"; | |
122 | + protected static final String DEVICE_NAME = "deviceName"; | |
123 | + protected static final String TENANT_ID = "tenantId"; | |
124 | + | |
125 | + private final DeviceBulkImportService deviceBulkImportService; | |
99 | 126 | |
127 | + @ApiOperation(value = "Get Device (getDeviceById)", | |
128 | + notes = "Fetch the Device object based on the provided Device Id. " + | |
129 | + "If the user has the authority of 'TENANT_ADMIN', the server checks that the device is owned by the same tenant. " + | |
130 | + "If the user has the authority of 'CUSTOMER_USER', the server checks that the device is assigned to the same customer." + | |
131 | + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) | |
100 | 132 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
101 | 133 | @RequestMapping(value = "/device/{deviceId}", method = RequestMethod.GET) |
102 | 134 | @ResponseBody |
103 | - public Device getDeviceById(@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { | |
135 | + public Device getDeviceById(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) | |
136 | + @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { | |
104 | 137 | checkParameter(DEVICE_ID, strDeviceId); |
105 | 138 | try { |
106 | 139 | DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); |
... | ... | @@ -110,10 +143,16 @@ public class DeviceController extends BaseController { |
110 | 143 | } |
111 | 144 | } |
112 | 145 | |
146 | + @ApiOperation(value = "Get Device Info (getDeviceInfoById)", | |
147 | + notes = "Fetch the Device Info object based on the provided Device Id. " + | |
148 | + "If the user has the authority of 'Tenant Administrator', the server checks that the device is owned by the same tenant. " + | |
149 | + "If the user has the authority of 'Customer User', the server checks that the device is assigned to the same customer. " + | |
150 | + DEVICE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) | |
113 | 151 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
114 | 152 | @RequestMapping(value = "/device/info/{deviceId}", method = RequestMethod.GET) |
115 | 153 | @ResponseBody |
116 | - public DeviceInfo getDeviceInfoById(@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { | |
154 | + public DeviceInfo getDeviceInfoById(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) | |
155 | + @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { | |
117 | 156 | checkParameter(DEVICE_ID, strDeviceId); |
118 | 157 | try { |
119 | 158 | DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); |
... | ... | @@ -123,11 +162,20 @@ public class DeviceController extends BaseController { |
123 | 162 | } |
124 | 163 | } |
125 | 164 | |
165 | + @ApiOperation(value = "Create Or Update Device (saveDevice)", | |
166 | + notes = "Create or update the Device. When creating device, platform generates Device Id as [time-based UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_1_(date-time_and_MAC_address). " + | |
167 | + "Device credentials are also generated if not provided in the 'accessToken' request parameter. " + | |
168 | + "The newly created device id will be present in the response. " + | |
169 | + "Specify existing Device id to update the device. " + | |
170 | + "Referencing non-existing device Id will cause 'Not Found' error." + | |
171 | + "\n\nDevice name is unique in the scope of tenant. Use unique identifiers like MAC or IMEI for the device names and non-unique 'label' field for user-friendly visualization purposes." | |
172 | + + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) | |
126 | 173 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
127 | 174 | @RequestMapping(value = "/device", method = RequestMethod.POST) |
128 | 175 | @ResponseBody |
129 | - public Device saveDevice(@RequestBody Device device, | |
130 | - @RequestParam(name = "accessToken", required = false) String accessToken) throws ThingsboardException { | |
176 | + public Device saveDevice(@ApiParam(value = "A JSON value representing the device.") @RequestBody Device device, | |
177 | + @ApiParam(value = "Optional value of the device credentials to be used during device creation. " + | |
178 | + "If omitted, access token will be auto-generated.") @RequestParam(name = "accessToken", required = false) String accessToken) throws ThingsboardException { | |
131 | 179 | boolean created = device.getId() == null; |
132 | 180 | try { |
133 | 181 | device.setTenantId(getCurrentUser().getTenantId()); |
... | ... | @@ -141,7 +189,7 @@ public class DeviceController extends BaseController { |
141 | 189 | |
142 | 190 | Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken)); |
143 | 191 | |
144 | - onDeviceCreatedOrUpdated(savedDevice, oldDevice, !created); | |
192 | + onDeviceCreatedOrUpdated(savedDevice, oldDevice, !created, getCurrentUser()); | |
145 | 193 | |
146 | 194 | return savedDevice; |
147 | 195 | } catch (Exception e) { |
... | ... | @@ -152,11 +200,11 @@ public class DeviceController extends BaseController { |
152 | 200 | |
153 | 201 | } |
154 | 202 | |
155 | - private void onDeviceCreatedOrUpdated(Device savedDevice, Device oldDevice, boolean updated) { | |
203 | + private void onDeviceCreatedOrUpdated(Device savedDevice, Device oldDevice, boolean updated, SecurityUser user) { | |
156 | 204 | tbClusterService.onDeviceUpdated(savedDevice, oldDevice); |
157 | 205 | |
158 | 206 | try { |
159 | - logEntityAction(savedDevice.getId(), savedDevice, | |
207 | + logEntityAction(user, savedDevice.getId(), savedDevice, | |
160 | 208 | savedDevice.getCustomerId(), |
161 | 209 | updated ? ActionType.UPDATED : ActionType.ADDED, null); |
162 | 210 | } catch (ThingsboardException e) { |
... | ... | @@ -164,10 +212,13 @@ public class DeviceController extends BaseController { |
164 | 212 | } |
165 | 213 | } |
166 | 214 | |
215 | + @ApiOperation(value = "Delete device (deleteDevice)", | |
216 | + notes = "Deletes the device, it's credentials and all the relations (from and to the device). Referencing non-existing device Id will cause an error." + TENANT_AUTHORITY_PARAGRAPH) | |
167 | 217 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
168 | 218 | @RequestMapping(value = "/device/{deviceId}", method = RequestMethod.DELETE) |
169 | 219 | @ResponseStatus(value = HttpStatus.OK) |
170 | - public void deleteDevice(@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { | |
220 | + public void deleteDevice(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) | |
221 | + @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { | |
171 | 222 | checkParameter(DEVICE_ID, strDeviceId); |
172 | 223 | try { |
173 | 224 | DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); |
... | ... | @@ -193,10 +244,14 @@ public class DeviceController extends BaseController { |
193 | 244 | } |
194 | 245 | } |
195 | 246 | |
247 | + @ApiOperation(value = "Assign device to customer (assignDeviceToCustomer)", | |
248 | + notes = "Creates assignment of the device to customer. Customer will be able to query device afterwards." + TENANT_AUTHORITY_PARAGRAPH) | |
196 | 249 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
197 | 250 | @RequestMapping(value = "/customer/{customerId}/device/{deviceId}", method = RequestMethod.POST) |
198 | 251 | @ResponseBody |
199 | - public Device assignDeviceToCustomer(@PathVariable("customerId") String strCustomerId, | |
252 | + public Device assignDeviceToCustomer(@ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) | |
253 | + @PathVariable("customerId") String strCustomerId, | |
254 | + @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) | |
200 | 255 | @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { |
201 | 256 | checkParameter("customerId", strCustomerId); |
202 | 257 | checkParameter(DEVICE_ID, strDeviceId); |
... | ... | @@ -225,10 +280,13 @@ public class DeviceController extends BaseController { |
225 | 280 | } |
226 | 281 | } |
227 | 282 | |
283 | + @ApiOperation(value = "Unassign device from customer (unassignDeviceFromCustomer)", | |
284 | + notes = "Clears assignment of the device to customer. Customer will not be able to query device afterwards." + TENANT_AUTHORITY_PARAGRAPH) | |
228 | 285 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
229 | 286 | @RequestMapping(value = "/customer/device/{deviceId}", method = RequestMethod.DELETE) |
230 | 287 | @ResponseBody |
231 | - public Device unassignDeviceFromCustomer(@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { | |
288 | + public Device unassignDeviceFromCustomer(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) | |
289 | + @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { | |
232 | 290 | checkParameter(DEVICE_ID, strDeviceId); |
233 | 291 | try { |
234 | 292 | DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); |
... | ... | @@ -256,10 +314,15 @@ public class DeviceController extends BaseController { |
256 | 314 | } |
257 | 315 | } |
258 | 316 | |
317 | + @ApiOperation(value = "Make device publicly available (assignDeviceToPublicCustomer)", | |
318 | + notes = "Device will be available for non-authorized (not logged-in) users. " + | |
319 | + "This is useful to create dashboards that you plan to share/embed on a publicly available website. " + | |
320 | + "However, users that are logged-in and belong to different tenant will not be able to access the device." + TENANT_AUTHORITY_PARAGRAPH) | |
259 | 321 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
260 | 322 | @RequestMapping(value = "/customer/public/device/{deviceId}", method = RequestMethod.POST) |
261 | 323 | @ResponseBody |
262 | - public Device assignDeviceToPublicCustomer(@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { | |
324 | + public Device assignDeviceToPublicCustomer(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) | |
325 | + @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { | |
263 | 326 | checkParameter(DEVICE_ID, strDeviceId); |
264 | 327 | try { |
265 | 328 | DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); |
... | ... | @@ -280,10 +343,13 @@ public class DeviceController extends BaseController { |
280 | 343 | } |
281 | 344 | } |
282 | 345 | |
346 | + @ApiOperation(value = "Get Device Credentials (getDeviceCredentialsByDeviceId)", | |
347 | + notes = "If during device creation there wasn't specified any credentials, platform generates random 'ACCESS_TOKEN' credentials." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) | |
283 | 348 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
284 | 349 | @RequestMapping(value = "/device/{deviceId}/credentials", method = RequestMethod.GET) |
285 | 350 | @ResponseBody |
286 | - public DeviceCredentials getDeviceCredentialsByDeviceId(@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { | |
351 | + public DeviceCredentials getDeviceCredentialsByDeviceId(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) | |
352 | + @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { | |
287 | 353 | checkParameter(DEVICE_ID, strDeviceId); |
288 | 354 | try { |
289 | 355 | DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); |
... | ... | @@ -301,10 +367,16 @@ public class DeviceController extends BaseController { |
301 | 367 | } |
302 | 368 | } |
303 | 369 | |
370 | + @ApiOperation(value = "Update device credentials (updateDeviceCredentials)", notes = "During device creation, platform generates random 'ACCESS_TOKEN' credentials. " + | |
371 | + "Use this method to update the device credentials. First use 'getDeviceCredentialsByDeviceId' to get the credentials id and value. " + | |
372 | + "Then use current method to update the credentials type and value. It is not possible to create multiple device credentials for the same device. " + | |
373 | + "The structure of device credentials id and value is simple for the 'ACCESS_TOKEN' but is much more complex for the 'MQTT_BASIC' or 'LWM2M_CREDENTIALS'." + TENANT_AUTHORITY_PARAGRAPH) | |
304 | 374 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
305 | 375 | @RequestMapping(value = "/device/credentials", method = RequestMethod.POST) |
306 | 376 | @ResponseBody |
307 | - public DeviceCredentials saveDeviceCredentials(@RequestBody DeviceCredentials deviceCredentials) throws ThingsboardException { | |
377 | + public DeviceCredentials updateDeviceCredentials( | |
378 | + @ApiParam(value = "A JSON value representing the device credentials.") | |
379 | + @RequestBody DeviceCredentials deviceCredentials) throws ThingsboardException { | |
308 | 380 | checkNotNull(deviceCredentials); |
309 | 381 | try { |
310 | 382 | Device device = checkDeviceId(deviceCredentials.getDeviceId(), Operation.WRITE_CREDENTIALS); |
... | ... | @@ -325,15 +397,24 @@ public class DeviceController extends BaseController { |
325 | 397 | } |
326 | 398 | } |
327 | 399 | |
400 | + @ApiOperation(value = "Get Tenant Devices (getTenantDevices)", | |
401 | + notes = "Returns a page of devices owned by tenant. " + | |
402 | + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH) | |
328 | 403 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
329 | 404 | @RequestMapping(value = "/tenant/devices", params = {"pageSize", "page"}, method = RequestMethod.GET) |
330 | 405 | @ResponseBody |
331 | 406 | public PageData<Device> getTenantDevices( |
407 | + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) | |
332 | 408 | @RequestParam int pageSize, |
409 | + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) | |
333 | 410 | @RequestParam int page, |
411 | + @ApiParam(value = DEVICE_TYPE_DESCRIPTION) | |
334 | 412 | @RequestParam(required = false) String type, |
413 | + @ApiParam(value = DEVICE_TEXT_SEARCH_DESCRIPTION) | |
335 | 414 | @RequestParam(required = false) String textSearch, |
415 | + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_SORT_PROPERTY_ALLOWABLE_VALUES) | |
336 | 416 | @RequestParam(required = false) String sortProperty, |
417 | + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) | |
337 | 418 | @RequestParam(required = false) String sortOrder) throws ThingsboardException { |
338 | 419 | try { |
339 | 420 | TenantId tenantId = getCurrentUser().getTenantId(); |
... | ... | @@ -348,17 +429,28 @@ public class DeviceController extends BaseController { |
348 | 429 | } |
349 | 430 | } |
350 | 431 | |
432 | + @ApiOperation(value = "Get Tenant Device Infos (getTenantDeviceInfos)", | |
433 | + notes = "Returns a page of devices info objects owned by tenant. " + | |
434 | + PAGE_DATA_PARAMETERS + DEVICE_INFO_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH) | |
351 | 435 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
352 | 436 | @RequestMapping(value = "/tenant/deviceInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) |
353 | 437 | @ResponseBody |
354 | 438 | public PageData<DeviceInfo> getTenantDeviceInfos( |
439 | + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) | |
355 | 440 | @RequestParam int pageSize, |
441 | + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) | |
356 | 442 | @RequestParam int page, |
443 | + @ApiParam(value = DEVICE_TYPE_DESCRIPTION) | |
357 | 444 | @RequestParam(required = false) String type, |
445 | + @ApiParam(value = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) | |
358 | 446 | @RequestParam(required = false) String deviceProfileId, |
447 | + @ApiParam(value = DEVICE_TEXT_SEARCH_DESCRIPTION) | |
359 | 448 | @RequestParam(required = false) String textSearch, |
449 | + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_SORT_PROPERTY_ALLOWABLE_VALUES) | |
360 | 450 | @RequestParam(required = false) String sortProperty, |
361 | - @RequestParam(required = false) String sortOrder) throws ThingsboardException { | |
451 | + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) | |
452 | + @RequestParam(required = false) String sortOrder | |
453 | + ) throws ThingsboardException { | |
362 | 454 | try { |
363 | 455 | TenantId tenantId = getCurrentUser().getTenantId(); |
364 | 456 | PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); |
... | ... | @@ -375,10 +467,14 @@ public class DeviceController extends BaseController { |
375 | 467 | } |
376 | 468 | } |
377 | 469 | |
470 | + @ApiOperation(value = "Get Tenant Device (getTenantDevice)", | |
471 | + notes = "Requested device must be owned by tenant that the user belongs to. " + | |
472 | + "Device name is an unique property of device. So it can be used to identify the device." + TENANT_AUTHORITY_PARAGRAPH) | |
378 | 473 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
379 | 474 | @RequestMapping(value = "/tenant/devices", params = {"deviceName"}, method = RequestMethod.GET) |
380 | 475 | @ResponseBody |
381 | 476 | public Device getTenantDevice( |
477 | + @ApiParam(value = DEVICE_NAME_DESCRIPTION) | |
382 | 478 | @RequestParam String deviceName) throws ThingsboardException { |
383 | 479 | try { |
384 | 480 | TenantId tenantId = getCurrentUser().getTenantId(); |
... | ... | @@ -388,16 +484,26 @@ public class DeviceController extends BaseController { |
388 | 484 | } |
389 | 485 | } |
390 | 486 | |
487 | + @ApiOperation(value = "Get Customer Devices (getCustomerDevices)", | |
488 | + notes = "Returns a page of devices objects assigned to customer. " + | |
489 | + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) | |
391 | 490 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
392 | 491 | @RequestMapping(value = "/customer/{customerId}/devices", params = {"pageSize", "page"}, method = RequestMethod.GET) |
393 | 492 | @ResponseBody |
394 | 493 | public PageData<Device> getCustomerDevices( |
494 | + @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION, required = true) | |
395 | 495 | @PathVariable("customerId") String strCustomerId, |
496 | + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) | |
396 | 497 | @RequestParam int pageSize, |
498 | + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) | |
397 | 499 | @RequestParam int page, |
500 | + @ApiParam(value = DEVICE_TYPE_DESCRIPTION) | |
398 | 501 | @RequestParam(required = false) String type, |
502 | + @ApiParam(value = DEVICE_TEXT_SEARCH_DESCRIPTION) | |
399 | 503 | @RequestParam(required = false) String textSearch, |
504 | + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_SORT_PROPERTY_ALLOWABLE_VALUES) | |
400 | 505 | @RequestParam(required = false) String sortProperty, |
506 | + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) | |
401 | 507 | @RequestParam(required = false) String sortOrder) throws ThingsboardException { |
402 | 508 | checkParameter("customerId", strCustomerId); |
403 | 509 | try { |
... | ... | @@ -415,17 +521,28 @@ public class DeviceController extends BaseController { |
415 | 521 | } |
416 | 522 | } |
417 | 523 | |
524 | + @ApiOperation(value = "Get Customer Device Infos (getCustomerDeviceInfos)", | |
525 | + notes = "Returns a page of devices info objects assigned to customer. " + | |
526 | + PAGE_DATA_PARAMETERS + DEVICE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) | |
418 | 527 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
419 | 528 | @RequestMapping(value = "/customer/{customerId}/deviceInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) |
420 | 529 | @ResponseBody |
421 | 530 | public PageData<DeviceInfo> getCustomerDeviceInfos( |
531 | + @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION, required = true) | |
422 | 532 | @PathVariable("customerId") String strCustomerId, |
533 | + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) | |
423 | 534 | @RequestParam int pageSize, |
535 | + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) | |
424 | 536 | @RequestParam int page, |
537 | + @ApiParam(value = DEVICE_TYPE_DESCRIPTION) | |
425 | 538 | @RequestParam(required = false) String type, |
539 | + @ApiParam(value = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) | |
426 | 540 | @RequestParam(required = false) String deviceProfileId, |
541 | + @ApiParam(value = DEVICE_TEXT_SEARCH_DESCRIPTION) | |
427 | 542 | @RequestParam(required = false) String textSearch, |
543 | + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_SORT_PROPERTY_ALLOWABLE_VALUES) | |
428 | 544 | @RequestParam(required = false) String sortProperty, |
545 | + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) | |
429 | 546 | @RequestParam(required = false) String sortOrder) throws ThingsboardException { |
430 | 547 | checkParameter("customerId", strCustomerId); |
431 | 548 | try { |
... | ... | @@ -446,10 +563,13 @@ public class DeviceController extends BaseController { |
446 | 563 | } |
447 | 564 | } |
448 | 565 | |
566 | + @ApiOperation(value = "Get Devices By Ids (getDevicesByIds)", | |
567 | + notes = "Requested devices must be owned by tenant or assigned to customer which user is performing the request. " + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) | |
449 | 568 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
450 | 569 | @RequestMapping(value = "/devices", params = {"deviceIds"}, method = RequestMethod.GET) |
451 | 570 | @ResponseBody |
452 | 571 | public List<Device> getDevicesByIds( |
572 | + @ApiParam(value = "A list of devices ids, separated by comma ','") | |
453 | 573 | @RequestParam("deviceIds") String[] strDeviceIds) throws ThingsboardException { |
454 | 574 | checkArrayParameter("deviceIds", strDeviceIds); |
455 | 575 | try { |
... | ... | @@ -472,10 +592,16 @@ public class DeviceController extends BaseController { |
472 | 592 | } |
473 | 593 | } |
474 | 594 | |
595 | + @ApiOperation(value = "Find related devices (findByQuery)", | |
596 | + notes = "Returns all devices that are related to the specific entity. " + | |
597 | + "The entity id, relation type, device types, depth of the search, and other query parameters defined using complex 'DeviceSearchQuery' object. " + | |
598 | + "See 'Model' tab of the Parameters for more info." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) | |
475 | 599 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
476 | 600 | @RequestMapping(value = "/devices", method = RequestMethod.POST) |
477 | 601 | @ResponseBody |
478 | - public List<Device> findByQuery(@RequestBody DeviceSearchQuery query) throws ThingsboardException { | |
602 | + public List<Device> findByQuery( | |
603 | + @ApiParam(value = "The device search query JSON") | |
604 | + @RequestBody DeviceSearchQuery query) throws ThingsboardException { | |
479 | 605 | checkNotNull(query); |
480 | 606 | checkNotNull(query.getParameters()); |
481 | 607 | checkNotNull(query.getDeviceTypes()); |
... | ... | @@ -496,6 +622,9 @@ public class DeviceController extends BaseController { |
496 | 622 | } |
497 | 623 | } |
498 | 624 | |
625 | + @ApiOperation(value = "Get Device Types (getDeviceTypes)", | |
626 | + notes = "Returns a set of unique device profile names based on devices that are either owned by the tenant or assigned to the customer which user is performing the request." | |
627 | + + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) | |
499 | 628 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
500 | 629 | @RequestMapping(value = "/device/types", method = RequestMethod.GET) |
501 | 630 | @ResponseBody |
... | ... | @@ -510,10 +639,19 @@ public class DeviceController extends BaseController { |
510 | 639 | } |
511 | 640 | } |
512 | 641 | |
642 | + @ApiOperation(value = "Claim device (claimDevice)", | |
643 | + notes = "Claiming makes it possible to assign a device to the specific customer using device/server side claiming data (in the form of secret key)." + | |
644 | + "To make this happen you have to provide unique device name and optional claiming data (it is needed only for device-side claiming)." + | |
645 | + "Once device is claimed, the customer becomes its owner and customer users may access device data as well as control the device. \n" + | |
646 | + "In order to enable claiming devices feature a system parameter security.claim.allowClaimingByDefault should be set to true, " + | |
647 | + "otherwise a server-side claimingAllowed attribute with the value true is obligatory for provisioned devices. \n" + | |
648 | + "See official documentation for more details regarding claiming." + CUSTOMER_AUTHORITY_PARAGRAPH) | |
513 | 649 | @PreAuthorize("hasAuthority('CUSTOMER_USER')") |
514 | 650 | @RequestMapping(value = "/customer/device/{deviceName}/claim", method = RequestMethod.POST) |
515 | 651 | @ResponseBody |
516 | - public DeferredResult<ResponseEntity> claimDevice(@PathVariable(DEVICE_NAME) String deviceName, | |
652 | + public DeferredResult<ResponseEntity> claimDevice(@ApiParam(value = "Unique name of the device which is going to be claimed") | |
653 | + @PathVariable(DEVICE_NAME) String deviceName, | |
654 | + @ApiParam(value = "Claiming request which can optionally contain secret key") | |
517 | 655 | @RequestBody(required = false) ClaimRequest claimRequest) throws ThingsboardException { |
518 | 656 | checkParameter(DEVICE_NAME, deviceName); |
519 | 657 | try { |
... | ... | @@ -564,10 +702,14 @@ public class DeviceController extends BaseController { |
564 | 702 | } |
565 | 703 | } |
566 | 704 | |
705 | + @ApiOperation(value = "Reclaim device (reClaimDevice)", | |
706 | + notes = "Reclaiming means the device will be unassigned from the customer and the device will be available for claiming again." | |
707 | + + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) | |
567 | 708 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
568 | 709 | @RequestMapping(value = "/customer/device/{deviceName}/claim", method = RequestMethod.DELETE) |
569 | 710 | @ResponseStatus(value = HttpStatus.OK) |
570 | - public DeferredResult<ResponseEntity> reClaimDevice(@PathVariable(DEVICE_NAME) String deviceName) throws ThingsboardException { | |
711 | + public DeferredResult<ResponseEntity> reClaimDevice(@ApiParam(value = "Unique name of the device which is going to be reclaimed") | |
712 | + @PathVariable(DEVICE_NAME) String deviceName) throws ThingsboardException { | |
571 | 713 | checkParameter(DEVICE_NAME, deviceName); |
572 | 714 | try { |
573 | 715 | final DeferredResult<ResponseEntity> deferredResult = new DeferredResult<>(); |
... | ... | @@ -615,10 +757,14 @@ public class DeviceController extends BaseController { |
615 | 757 | return DataConstants.DEFAULT_SECRET_KEY; |
616 | 758 | } |
617 | 759 | |
760 | + @ApiOperation(value = "Assign device to tenant (assignDeviceToTenant)", | |
761 | + notes = "Creates assignment of the device to tenant. Thereafter tenant will be able to reassign the device to a customer." + TENANT_AUTHORITY_PARAGRAPH) | |
618 | 762 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
619 | 763 | @RequestMapping(value = "/tenant/{tenantId}/device/{deviceId}", method = RequestMethod.POST) |
620 | 764 | @ResponseBody |
621 | - public Device assignDeviceToTenant(@PathVariable(TENANT_ID) String strTenantId, | |
765 | + public Device assignDeviceToTenant(@ApiParam(value = TENANT_ID_PARAM_DESCRIPTION) | |
766 | + @PathVariable(TENANT_ID) String strTenantId, | |
767 | + @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) | |
622 | 768 | @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { |
623 | 769 | checkParameter(TENANT_ID, strTenantId); |
624 | 770 | checkParameter(DEVICE_ID, strDeviceId); |
... | ... | @@ -665,10 +811,19 @@ public class DeviceController extends BaseController { |
665 | 811 | return metaData; |
666 | 812 | } |
667 | 813 | |
814 | + @ApiOperation(value = "Assign device to edge (assignDeviceToEdge)", | |
815 | + notes = "Creates assignment of an existing device to an instance of The Edge. " + | |
816 | + EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION + | |
817 | + "Second, remote edge service will receive a copy of assignment device " + | |
818 | + EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION + ". " + | |
819 | + "Third, once device will be delivered to edge service, it's going to be available for usage on remote edge instance." + TENANT_AUTHORITY_PARAGRAPH, | |
820 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
668 | 821 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
669 | 822 | @RequestMapping(value = "/edge/{edgeId}/device/{deviceId}", method = RequestMethod.POST) |
670 | 823 | @ResponseBody |
671 | - public Device assignDeviceToEdge(@PathVariable(EDGE_ID) String strEdgeId, | |
824 | + public Device assignDeviceToEdge(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION) | |
825 | + @PathVariable(EDGE_ID) String strEdgeId, | |
826 | + @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) | |
672 | 827 | @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { |
673 | 828 | checkParameter(EDGE_ID, strEdgeId); |
674 | 829 | checkParameter(DEVICE_ID, strDeviceId); |
... | ... | @@ -699,10 +854,19 @@ public class DeviceController extends BaseController { |
699 | 854 | } |
700 | 855 | } |
701 | 856 | |
857 | + @ApiOperation(value = "Unassign device from edge (unassignDeviceFromEdge)", | |
858 | + notes = "Clears assignment of the device to the edge. " + | |
859 | + EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION + | |
860 | + "Second, remote edge service will receive an 'unassign' command to remove device " + | |
861 | + EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION + ". " + | |
862 | + "Third, once 'unassign' command will be delivered to edge service, it's going to remove device locally." + TENANT_AUTHORITY_PARAGRAPH, | |
863 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
702 | 864 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
703 | 865 | @RequestMapping(value = "/edge/{edgeId}/device/{deviceId}", method = RequestMethod.DELETE) |
704 | 866 | @ResponseBody |
705 | - public Device unassignDeviceFromEdge(@PathVariable(EDGE_ID) String strEdgeId, | |
867 | + public Device unassignDeviceFromEdge(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION) | |
868 | + @PathVariable(EDGE_ID) String strEdgeId, | |
869 | + @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) | |
706 | 870 | @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { |
707 | 871 | checkParameter(EDGE_ID, strEdgeId); |
708 | 872 | checkParameter(DEVICE_ID, strDeviceId); |
... | ... | @@ -733,18 +897,30 @@ public class DeviceController extends BaseController { |
733 | 897 | } |
734 | 898 | } |
735 | 899 | |
900 | + @ApiOperation(value = "Get devices assigned to edge (getEdgeDevices)", | |
901 | + notes = "Returns a page of devices assigned to edge. " + | |
902 | + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) | |
736 | 903 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
737 | 904 | @RequestMapping(value = "/edge/{edgeId}/devices", params = {"pageSize", "page"}, method = RequestMethod.GET) |
738 | 905 | @ResponseBody |
739 | 906 | public PageData<Device> getEdgeDevices( |
907 | + @ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) | |
740 | 908 | @PathVariable(EDGE_ID) String strEdgeId, |
909 | + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) | |
741 | 910 | @RequestParam int pageSize, |
911 | + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) | |
742 | 912 | @RequestParam int page, |
913 | + @ApiParam(value = DEVICE_TYPE_DESCRIPTION) | |
743 | 914 | @RequestParam(required = false) String type, |
915 | + @ApiParam(value = DEVICE_TEXT_SEARCH_DESCRIPTION) | |
744 | 916 | @RequestParam(required = false) String textSearch, |
917 | + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_SORT_PROPERTY_ALLOWABLE_VALUES) | |
745 | 918 | @RequestParam(required = false) String sortProperty, |
919 | + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) | |
746 | 920 | @RequestParam(required = false) String sortOrder, |
921 | + @ApiParam(value = "Timestamp. Devices with creation time before it won't be queried") | |
747 | 922 | @RequestParam(required = false) Long startTime, |
923 | + @ApiParam(value = "Timestamp. Devices with creation time after it won't be queried") | |
748 | 924 | @RequestParam(required = false) Long endTime) throws ThingsboardException { |
749 | 925 | checkParameter(EDGE_ID, strEdgeId); |
750 | 926 | try { |
... | ... | @@ -776,10 +952,17 @@ public class DeviceController extends BaseController { |
776 | 952 | } |
777 | 953 | } |
778 | 954 | |
955 | + @ApiOperation(value = "Count devices by device profile (countByDeviceProfileAndEmptyOtaPackage)", | |
956 | + notes = "The platform gives an ability to load OTA (over-the-air) packages to devices. " + | |
957 | + "It can be done in two different ways: device scope or device profile scope." + | |
958 | + "In the response you will find the number of devices with specified device profile, but without previously defined device scope OTA package. " + | |
959 | + "It can be useful when you want to define number of devices that will be affected with future OTA package" + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) | |
779 | 960 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
780 | 961 | @RequestMapping(value = "/devices/count/{otaPackageType}/{deviceProfileId}", method = RequestMethod.GET) |
781 | 962 | @ResponseBody |
782 | - public Long countByDeviceProfileAndEmptyOtaPackage(@PathVariable("otaPackageType") String otaPackageType, | |
963 | + public Long countByDeviceProfileAndEmptyOtaPackage(@ApiParam(value = "OTA package type", allowableValues = "FIRMWARE, SOFTWARE") | |
964 | + @PathVariable("otaPackageType") String otaPackageType, | |
965 | + @ApiParam(value = "Device Profile Id. I.g. '784f394c-42b6-435a-983c-b7beff2784f9'") | |
783 | 966 | @PathVariable("deviceProfileId") String deviceProfileId) throws ThingsboardException { |
784 | 967 | checkParameter("OtaPackageType", otaPackageType); |
785 | 968 | checkParameter("DeviceProfileId", deviceProfileId); |
... | ... | @@ -793,11 +976,14 @@ public class DeviceController extends BaseController { |
793 | 976 | } |
794 | 977 | } |
795 | 978 | |
979 | + @ApiOperation(value = "Import the bulk of devices (processDevicesBulkImport)", | |
980 | + notes = "There's an ability to import the bulk of devices using the only .csv file." + TENANT_AUTHORITY_PARAGRAPH) | |
796 | 981 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") |
797 | 982 | @PostMapping("/device/bulk_import") |
798 | 983 | public BulkImportResult<Device> processDevicesBulkImport(@RequestBody BulkImportRequest request) throws Exception { |
799 | - return deviceBulkImportService.processBulkImport(request, getCurrentUser(), importedDeviceInfo -> { | |
800 | - onDeviceCreatedOrUpdated(importedDeviceInfo.getEntity(), importedDeviceInfo.getOldEntity(), importedDeviceInfo.isUpdated()); | |
984 | + SecurityUser user = getCurrentUser(); | |
985 | + return deviceBulkImportService.processBulkImport(request, user, importedDeviceInfo -> { | |
986 | + onDeviceCreatedOrUpdated(importedDeviceInfo.getEntity(), importedDeviceInfo.getOldEntity(), importedDeviceInfo.isUpdated(), user); | |
801 | 987 | }); |
802 | 988 | } |
803 | 989 | ... | ... |
... | ... | @@ -15,6 +15,8 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.controller; |
17 | 17 | |
18 | +import io.swagger.annotations.ApiOperation; | |
19 | +import io.swagger.annotations.ApiParam; | |
18 | 20 | import lombok.extern.slf4j.Slf4j; |
19 | 21 | import org.apache.commons.lang3.StringUtils; |
20 | 22 | import org.springframework.beans.factory.annotation.Autowired; |
... | ... | @@ -47,21 +49,43 @@ import java.util.List; |
47 | 49 | import java.util.Objects; |
48 | 50 | import java.util.UUID; |
49 | 51 | |
52 | +import static org.thingsboard.server.controller.ControllerConstants.DEVICE_PROFILE_DATA; | |
53 | +import static org.thingsboard.server.controller.ControllerConstants.DEVICE_PROFILE_ID; | |
54 | +import static org.thingsboard.server.controller.ControllerConstants.DEVICE_PROFILE_ID_PARAM_DESCRIPTION; | |
55 | +import static org.thingsboard.server.controller.ControllerConstants.DEVICE_PROFILE_INFO_DESCRIPTION; | |
56 | +import static org.thingsboard.server.controller.ControllerConstants.DEVICE_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES; | |
57 | +import static org.thingsboard.server.controller.ControllerConstants.DEVICE_PROFILE_TEXT_SEARCH_DESCRIPTION; | |
58 | +import static org.thingsboard.server.controller.ControllerConstants.NEW_LINE; | |
59 | +import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; | |
60 | +import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; | |
61 | +import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; | |
62 | +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; | |
63 | +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; | |
64 | +import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; | |
65 | +import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; | |
66 | +import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; | |
67 | +import static org.thingsboard.server.controller.ControllerConstants.TRANSPORT_TYPE_ALLOWABLE_VALUES; | |
68 | + | |
50 | 69 | @RestController |
51 | 70 | @TbCoreComponent |
52 | 71 | @RequestMapping("/api") |
53 | 72 | @Slf4j |
54 | 73 | public class DeviceProfileController extends BaseController { |
55 | 74 | |
56 | - private static final String DEVICE_PROFILE_ID = "deviceProfileId"; | |
57 | 75 | |
58 | 76 | @Autowired |
59 | 77 | private TimeseriesService timeseriesService; |
60 | 78 | |
79 | + @ApiOperation(value = "Get Device Profile (getDeviceProfileById)", | |
80 | + notes = "Fetch the Device Profile object based on the provided Device Profile Id. " + | |
81 | + "The server checks that the device profile is owned by the same tenant. " + TENANT_AUTHORITY_PARAGRAPH, | |
82 | + produces = "application/json") | |
61 | 83 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") |
62 | 84 | @RequestMapping(value = "/deviceProfile/{deviceProfileId}", method = RequestMethod.GET) |
63 | 85 | @ResponseBody |
64 | - public DeviceProfile getDeviceProfileById(@PathVariable(DEVICE_PROFILE_ID) String strDeviceProfileId) throws ThingsboardException { | |
86 | + public DeviceProfile getDeviceProfileById( | |
87 | + @ApiParam(value = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) | |
88 | + @PathVariable(DEVICE_PROFILE_ID) String strDeviceProfileId) throws ThingsboardException { | |
65 | 89 | checkParameter(DEVICE_PROFILE_ID, strDeviceProfileId); |
66 | 90 | try { |
67 | 91 | DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId)); |
... | ... | @@ -71,10 +95,16 @@ public class DeviceProfileController extends BaseController { |
71 | 95 | } |
72 | 96 | } |
73 | 97 | |
98 | + @ApiOperation(value = "Get Device Profile Info (getDeviceProfileInfoById)", | |
99 | + notes = "Fetch the Device Profile Info object based on the provided Device Profile Id. " | |
100 | + + DEVICE_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, | |
101 | + produces = "application/json") | |
74 | 102 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
75 | 103 | @RequestMapping(value = "/deviceProfileInfo/{deviceProfileId}", method = RequestMethod.GET) |
76 | 104 | @ResponseBody |
77 | - public DeviceProfileInfo getDeviceProfileInfoById(@PathVariable(DEVICE_PROFILE_ID) String strDeviceProfileId) throws ThingsboardException { | |
105 | + public DeviceProfileInfo getDeviceProfileInfoById( | |
106 | + @ApiParam(value = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) | |
107 | + @PathVariable(DEVICE_PROFILE_ID) String strDeviceProfileId) throws ThingsboardException { | |
78 | 108 | checkParameter(DEVICE_PROFILE_ID, strDeviceProfileId); |
79 | 109 | try { |
80 | 110 | DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId)); |
... | ... | @@ -84,6 +114,10 @@ public class DeviceProfileController extends BaseController { |
84 | 114 | } |
85 | 115 | } |
86 | 116 | |
117 | + @ApiOperation(value = "Get Default Device Profile (getDefaultDeviceProfileInfo)", | |
118 | + notes = "Fetch the Default Device Profile Info object. " + | |
119 | + DEVICE_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, | |
120 | + produces = "application/json") | |
87 | 121 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
88 | 122 | @RequestMapping(value = "/deviceProfileInfo/default", method = RequestMethod.GET) |
89 | 123 | @ResponseBody |
... | ... | @@ -95,10 +129,18 @@ public class DeviceProfileController extends BaseController { |
95 | 129 | } |
96 | 130 | } |
97 | 131 | |
132 | + @ApiOperation(value = "Get time-series keys (getTimeseriesKeys)", | |
133 | + notes = "Get a set of unique time-series keys used by devices that belong to specified profile. " + | |
134 | + "If profile is not set returns a list of unique keys among all profiles. " + | |
135 | + "The call is used for auto-complete in the UI forms. " + | |
136 | + "The implementation limits the number of devices that participate in search to 100 as a trade of between accurate results and time-consuming queries. " + | |
137 | + TENANT_AUTHORITY_PARAGRAPH, | |
138 | + produces = "application/json") | |
98 | 139 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") |
99 | 140 | @RequestMapping(value = "/deviceProfile/devices/keys/timeseries", method = RequestMethod.GET) |
100 | 141 | @ResponseBody |
101 | 142 | public List<String> getTimeseriesKeys( |
143 | + @ApiParam(value = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) | |
102 | 144 | @RequestParam(name = DEVICE_PROFILE_ID, required = false) String deviceProfileIdStr) throws ThingsboardException { |
103 | 145 | DeviceProfileId deviceProfileId; |
104 | 146 | if (StringUtils.isNotEmpty(deviceProfileIdStr)) { |
... | ... | @@ -115,10 +157,18 @@ public class DeviceProfileController extends BaseController { |
115 | 157 | } |
116 | 158 | } |
117 | 159 | |
160 | + @ApiOperation(value = "Get attribute keys (getAttributesKeys)", | |
161 | + notes = "Get a set of unique attribute keys used by devices that belong to specified profile. " + | |
162 | + "If profile is not set returns a list of unique keys among all profiles. " + | |
163 | + "The call is used for auto-complete in the UI forms. " + | |
164 | + "The implementation limits the number of devices that participate in search to 100 as a trade of between accurate results and time-consuming queries. " + | |
165 | + TENANT_AUTHORITY_PARAGRAPH, | |
166 | + produces = "application/json") | |
118 | 167 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") |
119 | 168 | @RequestMapping(value = "/deviceProfile/devices/keys/attributes", method = RequestMethod.GET) |
120 | 169 | @ResponseBody |
121 | 170 | public List<String> getAttributesKeys( |
171 | + @ApiParam(value = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) | |
122 | 172 | @RequestParam(name = DEVICE_PROFILE_ID, required = false) String deviceProfileIdStr) throws ThingsboardException { |
123 | 173 | DeviceProfileId deviceProfileId; |
124 | 174 | if (StringUtils.isNotEmpty(deviceProfileIdStr)) { |
... | ... | @@ -135,10 +185,21 @@ public class DeviceProfileController extends BaseController { |
135 | 185 | } |
136 | 186 | } |
137 | 187 | |
188 | + @ApiOperation(value = "Create Or Update Device Profile (saveDeviceProfile)", | |
189 | + notes = "Create or update the Device Profile. When creating device profile, platform generates device profile id as [time-based UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_1_(date-time_and_MAC_address). " + | |
190 | + "The newly created device profile id will be present in the response. " + | |
191 | + "Specify existing device profile id to update the device profile. " + | |
192 | + "Referencing non-existing device profile Id will cause 'Not Found' error. " + NEW_LINE + | |
193 | + "Device profile name is unique in the scope of tenant. Only one 'default' device profile may exist in scope of tenant." + DEVICE_PROFILE_DATA + | |
194 | + TENANT_AUTHORITY_PARAGRAPH, | |
195 | + produces = "application/json", | |
196 | + consumes = "application/json") | |
138 | 197 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
139 | 198 | @RequestMapping(value = "/deviceProfile", method = RequestMethod.POST) |
140 | 199 | @ResponseBody |
141 | - public DeviceProfile saveDeviceProfile(@RequestBody DeviceProfile deviceProfile) throws ThingsboardException { | |
200 | + public DeviceProfile saveDeviceProfile( | |
201 | + @ApiParam(value = "A JSON value representing the device profile.") | |
202 | + @RequestBody DeviceProfile deviceProfile) throws ThingsboardException { | |
142 | 203 | try { |
143 | 204 | boolean created = deviceProfile.getId() == null; |
144 | 205 | deviceProfile.setTenantId(getTenantId()); |
... | ... | @@ -180,10 +241,16 @@ public class DeviceProfileController extends BaseController { |
180 | 241 | } |
181 | 242 | } |
182 | 243 | |
244 | + @ApiOperation(value = "Delete device profile (deleteDeviceProfile)", | |
245 | + notes = "Deletes the device profile. Referencing non-existing device profile Id will cause an error. " + | |
246 | + "Can't delete the device profile if it is referenced by existing devices." + TENANT_AUTHORITY_PARAGRAPH, | |
247 | + produces = "application/json") | |
183 | 248 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
184 | 249 | @RequestMapping(value = "/deviceProfile/{deviceProfileId}", method = RequestMethod.DELETE) |
185 | 250 | @ResponseStatus(value = HttpStatus.OK) |
186 | - public void deleteDeviceProfile(@PathVariable(DEVICE_PROFILE_ID) String strDeviceProfileId) throws ThingsboardException { | |
251 | + public void deleteDeviceProfile( | |
252 | + @ApiParam(value = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) | |
253 | + @PathVariable(DEVICE_PROFILE_ID) String strDeviceProfileId) throws ThingsboardException { | |
187 | 254 | checkParameter(DEVICE_PROFILE_ID, strDeviceProfileId); |
188 | 255 | try { |
189 | 256 | DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId)); |
... | ... | @@ -207,10 +274,15 @@ public class DeviceProfileController extends BaseController { |
207 | 274 | } |
208 | 275 | } |
209 | 276 | |
277 | + @ApiOperation(value = "Make Device Profile Default (setDefaultDeviceProfile)", | |
278 | + notes = "Marks device profile as default within a tenant scope." + TENANT_AUTHORITY_PARAGRAPH, | |
279 | + produces = "application/json") | |
210 | 280 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") |
211 | 281 | @RequestMapping(value = "/deviceProfile/{deviceProfileId}/default", method = RequestMethod.POST) |
212 | 282 | @ResponseBody |
213 | - public DeviceProfile setDefaultDeviceProfile(@PathVariable(DEVICE_PROFILE_ID) String strDeviceProfileId) throws ThingsboardException { | |
283 | + public DeviceProfile setDefaultDeviceProfile( | |
284 | + @ApiParam(value = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) | |
285 | + @PathVariable(DEVICE_PROFILE_ID) String strDeviceProfileId) throws ThingsboardException { | |
214 | 286 | checkParameter(DEVICE_PROFILE_ID, strDeviceProfileId); |
215 | 287 | try { |
216 | 288 | DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId)); |
... | ... | @@ -238,14 +310,24 @@ public class DeviceProfileController extends BaseController { |
238 | 310 | } |
239 | 311 | } |
240 | 312 | |
313 | + @ApiOperation(value = "Get Device Profiles (getDeviceProfiles)", | |
314 | + notes = "Returns a page of devices profile objects owned by tenant. " + | |
315 | + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, | |
316 | + produces = "application/json") | |
241 | 317 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
242 | 318 | @RequestMapping(value = "/deviceProfiles", params = {"pageSize", "page"}, method = RequestMethod.GET) |
243 | 319 | @ResponseBody |
244 | - public PageData<DeviceProfile> getDeviceProfiles(@RequestParam int pageSize, | |
245 | - @RequestParam int page, | |
246 | - @RequestParam(required = false) String textSearch, | |
247 | - @RequestParam(required = false) String sortProperty, | |
248 | - @RequestParam(required = false) String sortOrder) throws ThingsboardException { | |
320 | + public PageData<DeviceProfile> getDeviceProfiles( | |
321 | + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) | |
322 | + @RequestParam int pageSize, | |
323 | + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) | |
324 | + @RequestParam int page, | |
325 | + @ApiParam(value = DEVICE_PROFILE_TEXT_SEARCH_DESCRIPTION) | |
326 | + @RequestParam(required = false) String textSearch, | |
327 | + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES) | |
328 | + @RequestParam(required = false) String sortProperty, | |
329 | + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) | |
330 | + @RequestParam(required = false) String sortOrder) throws ThingsboardException { | |
249 | 331 | try { |
250 | 332 | PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); |
251 | 333 | return checkNotNull(deviceProfileService.findDeviceProfiles(getTenantId(), pageLink)); |
... | ... | @@ -254,15 +336,26 @@ public class DeviceProfileController extends BaseController { |
254 | 336 | } |
255 | 337 | } |
256 | 338 | |
339 | + @ApiOperation(value = "Get Device Profiles for transport type (getDeviceProfileInfos)", | |
340 | + notes = "Returns a page of devices profile info objects owned by tenant. " + | |
341 | + PAGE_DATA_PARAMETERS + DEVICE_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, | |
342 | + produces = "application/json") | |
257 | 343 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
258 | 344 | @RequestMapping(value = "/deviceProfileInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) |
259 | 345 | @ResponseBody |
260 | - public PageData<DeviceProfileInfo> getDeviceProfileInfos(@RequestParam int pageSize, | |
261 | - @RequestParam int page, | |
262 | - @RequestParam(required = false) String textSearch, | |
263 | - @RequestParam(required = false) String sortProperty, | |
264 | - @RequestParam(required = false) String sortOrder, | |
265 | - @RequestParam(required = false) String transportType) throws ThingsboardException { | |
346 | + public PageData<DeviceProfileInfo> getDeviceProfileInfos( | |
347 | + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) | |
348 | + @RequestParam int pageSize, | |
349 | + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) | |
350 | + @RequestParam int page, | |
351 | + @ApiParam(value = DEVICE_PROFILE_TEXT_SEARCH_DESCRIPTION) | |
352 | + @RequestParam(required = false) String textSearch, | |
353 | + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES) | |
354 | + @RequestParam(required = false) String sortProperty, | |
355 | + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) | |
356 | + @RequestParam(required = false) String sortOrder, | |
357 | + @ApiParam(value = "Type of the transport", allowableValues = TRANSPORT_TYPE_ALLOWABLE_VALUES) | |
358 | + @RequestParam(required = false) String transportType) throws ThingsboardException { | |
266 | 359 | try { |
267 | 360 | PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); |
268 | 361 | return checkNotNull(deviceProfileService.findDeviceProfileInfos(getTenantId(), pageLink, transportType)); | ... | ... |
... | ... | @@ -17,9 +17,12 @@ package org.thingsboard.server.controller; |
17 | 17 | |
18 | 18 | import com.fasterxml.jackson.databind.JsonNode; |
19 | 19 | import com.google.common.util.concurrent.ListenableFuture; |
20 | +import io.swagger.annotations.ApiOperation; | |
21 | +import io.swagger.annotations.ApiParam; | |
20 | 22 | import lombok.RequiredArgsConstructor; |
21 | 23 | import lombok.extern.slf4j.Slf4j; |
22 | 24 | import org.springframework.http.HttpStatus; |
25 | +import org.springframework.http.MediaType; | |
23 | 26 | import org.springframework.http.ResponseEntity; |
24 | 27 | import org.springframework.security.access.prepost.PreAuthorize; |
25 | 28 | import org.springframework.web.bind.annotation.PathVariable; |
... | ... | @@ -66,6 +69,22 @@ import java.util.ArrayList; |
66 | 69 | import java.util.List; |
67 | 70 | import java.util.stream.Collectors; |
68 | 71 | |
72 | +import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID_PARAM_DESCRIPTION; | |
73 | +import static org.thingsboard.server.controller.ControllerConstants.EDGE_ID_PARAM_DESCRIPTION; | |
74 | +import static org.thingsboard.server.controller.ControllerConstants.EDGE_INFO_DESCRIPTION; | |
75 | +import static org.thingsboard.server.controller.ControllerConstants.EDGE_SORT_PROPERTY_ALLOWABLE_VALUES; | |
76 | +import static org.thingsboard.server.controller.ControllerConstants.EDGE_TEXT_SEARCH_DESCRIPTION; | |
77 | +import static org.thingsboard.server.controller.ControllerConstants.EDGE_TYPE_DESCRIPTION; | |
78 | +import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; | |
79 | +import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; | |
80 | +import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; | |
81 | +import static org.thingsboard.server.controller.ControllerConstants.RULE_CHAIN_ID_PARAM_DESCRIPTION; | |
82 | +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; | |
83 | +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; | |
84 | +import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; | |
85 | +import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; | |
86 | +import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; | |
87 | + | |
69 | 88 | @RestController |
70 | 89 | @TbCoreComponent |
71 | 90 | @Slf4j |
... | ... | @@ -75,7 +94,11 @@ public class EdgeController extends BaseController { |
75 | 94 | private final EdgeBulkImportService edgeBulkImportService; |
76 | 95 | |
77 | 96 | public static final String EDGE_ID = "edgeId"; |
97 | + public static final String EDGE_SECURITY_CHECK = "If the user has the authority of 'Tenant Administrator', the server checks that the edge is owned by the same tenant. " + | |
98 | + "If the user has the authority of 'Customer User', the server checks that the edge is assigned to the same customer."; | |
78 | 99 | |
100 | + @ApiOperation(value = "Is edges support enabled (isEdgesSupportEnabled)", | |
101 | + notes = "Returns 'true' if edges support enabled on server, 'false' - otherwise.") | |
79 | 102 | @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") |
80 | 103 | @RequestMapping(value = "/edges/enabled", method = RequestMethod.GET) |
81 | 104 | @ResponseBody |
... | ... | @@ -83,10 +106,14 @@ public class EdgeController extends BaseController { |
83 | 106 | return edgesEnabled; |
84 | 107 | } |
85 | 108 | |
109 | + @ApiOperation(value = "Get Edge (getEdgeById)", | |
110 | + notes = "Get the Edge object based on the provided Edge Id. " + EDGE_SECURITY_CHECK + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, | |
111 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
86 | 112 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
87 | 113 | @RequestMapping(value = "/edge/{edgeId}", method = RequestMethod.GET) |
88 | 114 | @ResponseBody |
89 | - public Edge getEdgeById(@PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException { | |
115 | + public Edge getEdgeById(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) | |
116 | + @PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException { | |
90 | 117 | checkParameter(EDGE_ID, strEdgeId); |
91 | 118 | try { |
92 | 119 | EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); |
... | ... | @@ -100,10 +127,14 @@ public class EdgeController extends BaseController { |
100 | 127 | } |
101 | 128 | } |
102 | 129 | |
130 | + @ApiOperation(value = "Get Edge Info (getEdgeInfoById)", | |
131 | + notes = "Get the Edge Info object based on the provided Edge Id. " + EDGE_SECURITY_CHECK + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, | |
132 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
103 | 133 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
104 | 134 | @RequestMapping(value = "/edge/info/{edgeId}", method = RequestMethod.GET) |
105 | 135 | @ResponseBody |
106 | - public EdgeInfo getEdgeInfoById(@PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException { | |
136 | + public EdgeInfo getEdgeInfoById(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) | |
137 | + @PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException { | |
107 | 138 | checkParameter(EDGE_ID, strEdgeId); |
108 | 139 | try { |
109 | 140 | EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); |
... | ... | @@ -117,10 +148,19 @@ public class EdgeController extends BaseController { |
117 | 148 | } |
118 | 149 | } |
119 | 150 | |
151 | + @ApiOperation(value = "Create Or Update Edge (saveEdge)", | |
152 | + notes = "Create or update the Edge. When creating edge, platform generates Edge Id as [time-based UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_1_(date-time_and_MAC_address). " + | |
153 | + "The newly created edge id will be present in the response. " + | |
154 | + "Specify existing Edge id to update the edge. " + | |
155 | + "Referencing non-existing Edge Id will cause 'Not Found' error." + | |
156 | + "\n\nEdge name is unique in the scope of tenant. Use unique identifiers like MAC or IMEI for the edge names and non-unique 'label' field for user-friendly visualization purposes." | |
157 | + + TENANT_AUTHORITY_PARAGRAPH, | |
158 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
120 | 159 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
121 | 160 | @RequestMapping(value = "/edge", method = RequestMethod.POST) |
122 | 161 | @ResponseBody |
123 | - public Edge saveEdge(@RequestBody Edge edge) throws ThingsboardException { | |
162 | + public Edge saveEdge(@ApiParam(value = "A JSON value representing the edge.", required = true) | |
163 | + @RequestBody Edge edge) throws ThingsboardException { | |
124 | 164 | try { |
125 | 165 | TenantId tenantId = getCurrentUser().getTenantId(); |
126 | 166 | edge.setTenantId(tenantId); |
... | ... | @@ -140,7 +180,7 @@ public class EdgeController extends BaseController { |
140 | 180 | edge.getId(), edge); |
141 | 181 | |
142 | 182 | Edge savedEdge = checkNotNull(edgeService.saveEdge(edge, true)); |
143 | - onEdgeCreatedOrUpdated(tenantId, savedEdge, edgeTemplateRootRuleChain, !created); | |
183 | + onEdgeCreatedOrUpdated(tenantId, savedEdge, edgeTemplateRootRuleChain, !created, getCurrentUser()); | |
144 | 184 | |
145 | 185 | return savedEdge; |
146 | 186 | } catch (Exception e) { |
... | ... | @@ -150,7 +190,7 @@ public class EdgeController extends BaseController { |
150 | 190 | } |
151 | 191 | } |
152 | 192 | |
153 | - private void onEdgeCreatedOrUpdated(TenantId tenantId, Edge edge, RuleChain edgeTemplateRootRuleChain, boolean updated) throws IOException, ThingsboardException { | |
193 | + private void onEdgeCreatedOrUpdated(TenantId tenantId, Edge edge, RuleChain edgeTemplateRootRuleChain, boolean updated, SecurityUser user) throws IOException, ThingsboardException { | |
154 | 194 | if (!updated) { |
155 | 195 | ruleChainService.assignRuleChainToEdge(tenantId, edgeTemplateRootRuleChain.getId(), edge.getId()); |
156 | 196 | edgeNotificationService.setEdgeRootRuleChain(tenantId, edge, edgeTemplateRootRuleChain.getId()); |
... | ... | @@ -160,13 +200,16 @@ public class EdgeController extends BaseController { |
160 | 200 | tbClusterService.broadcastEntityStateChangeEvent(edge.getTenantId(), edge.getId(), |
161 | 201 | updated ? ComponentLifecycleEvent.UPDATED : ComponentLifecycleEvent.CREATED); |
162 | 202 | |
163 | - logEntityAction(edge.getId(), edge, null, updated ? ActionType.UPDATED : ActionType.ADDED, null); | |
203 | + logEntityAction(user, edge.getId(), edge, null, updated ? ActionType.UPDATED : ActionType.ADDED, null); | |
164 | 204 | } |
165 | 205 | |
206 | + @ApiOperation(value = "Delete edge (deleteEdge)", | |
207 | + notes = "Deletes the edge. Referencing non-existing edge Id will cause an error."+ TENANT_AUTHORITY_PARAGRAPH) | |
166 | 208 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
167 | 209 | @RequestMapping(value = "/edge/{edgeId}", method = RequestMethod.DELETE) |
168 | 210 | @ResponseStatus(value = HttpStatus.OK) |
169 | - public void deleteEdge(@PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException { | |
211 | + public void deleteEdge(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) | |
212 | + @PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException { | |
170 | 213 | checkParameter(EDGE_ID, strEdgeId); |
171 | 214 | try { |
172 | 215 | EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); |
... | ... | @@ -191,13 +234,21 @@ public class EdgeController extends BaseController { |
191 | 234 | } |
192 | 235 | } |
193 | 236 | |
237 | + @ApiOperation(value = "Get Tenant Edges (getEdges)", | |
238 | + notes = "Returns a page of edges owned by tenant. " + | |
239 | + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) | |
194 | 240 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
195 | 241 | @RequestMapping(value = "/edges", params = {"pageSize", "page"}, method = RequestMethod.GET) |
196 | 242 | @ResponseBody |
197 | - public PageData<Edge> getEdges(@RequestParam int pageSize, | |
243 | + public PageData<Edge> getEdges(@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) | |
244 | + @RequestParam int pageSize, | |
245 | + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) | |
198 | 246 | @RequestParam int page, |
247 | + @ApiParam(value = EDGE_TEXT_SEARCH_DESCRIPTION) | |
199 | 248 | @RequestParam(required = false) String textSearch, |
249 | + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = EDGE_SORT_PROPERTY_ALLOWABLE_VALUES) | |
200 | 250 | @RequestParam(required = false) String sortProperty, |
251 | + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) | |
201 | 252 | @RequestParam(required = false) String sortOrder) throws ThingsboardException { |
202 | 253 | try { |
203 | 254 | PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); |
... | ... | @@ -208,10 +259,15 @@ public class EdgeController extends BaseController { |
208 | 259 | } |
209 | 260 | } |
210 | 261 | |
262 | + @ApiOperation(value = "Assign edge to customer (assignEdgeToCustomer)", | |
263 | + notes = "Creates assignment of the edge to customer. Customer will be able to query edge afterwards." + TENANT_AUTHORITY_PARAGRAPH, | |
264 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
211 | 265 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
212 | 266 | @RequestMapping(value = "/customer/{customerId}/edge/{edgeId}", method = RequestMethod.POST) |
213 | 267 | @ResponseBody |
214 | - public Edge assignEdgeToCustomer(@PathVariable("customerId") String strCustomerId, | |
268 | + public Edge assignEdgeToCustomer(@ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION, required = true) | |
269 | + @PathVariable("customerId") String strCustomerId, | |
270 | + @ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) | |
215 | 271 | @PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException { |
216 | 272 | checkParameter("customerId", strCustomerId); |
217 | 273 | checkParameter(EDGE_ID, strEdgeId); |
... | ... | @@ -243,10 +299,14 @@ public class EdgeController extends BaseController { |
243 | 299 | } |
244 | 300 | } |
245 | 301 | |
302 | + @ApiOperation(value = "Unassign edge from customer (unassignEdgeFromCustomer)", | |
303 | + notes = "Clears assignment of the edge to customer. Customer will not be able to query edge afterwards." + TENANT_AUTHORITY_PARAGRAPH, | |
304 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
246 | 305 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
247 | 306 | @RequestMapping(value = "/customer/edge/{edgeId}", method = RequestMethod.DELETE) |
248 | 307 | @ResponseBody |
249 | - public Edge unassignEdgeFromCustomer(@PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException { | |
308 | + public Edge unassignEdgeFromCustomer(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) | |
309 | + @PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException { | |
250 | 310 | checkParameter(EDGE_ID, strEdgeId); |
251 | 311 | try { |
252 | 312 | EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); |
... | ... | @@ -277,10 +337,16 @@ public class EdgeController extends BaseController { |
277 | 337 | } |
278 | 338 | } |
279 | 339 | |
340 | + @ApiOperation(value = "Make edge publicly available (assignEdgeToPublicCustomer)", | |
341 | + notes = "Edge will be available for non-authorized (not logged-in) users. " + | |
342 | + "This is useful to create dashboards that you plan to share/embed on a publicly available website. " + | |
343 | + "However, users that are logged-in and belong to different tenant will not be able to access the edge." + TENANT_AUTHORITY_PARAGRAPH, | |
344 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
280 | 345 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
281 | 346 | @RequestMapping(value = "/customer/public/edge/{edgeId}", method = RequestMethod.POST) |
282 | 347 | @ResponseBody |
283 | - public Edge assignEdgeToPublicCustomer(@PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException { | |
348 | + public Edge assignEdgeToPublicCustomer(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) | |
349 | + @PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException { | |
284 | 350 | checkParameter(EDGE_ID, strEdgeId); |
285 | 351 | try { |
286 | 352 | EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); |
... | ... | @@ -304,15 +370,24 @@ public class EdgeController extends BaseController { |
304 | 370 | } |
305 | 371 | } |
306 | 372 | |
373 | + @ApiOperation(value = "Get Tenant Edges (getTenantEdges)", | |
374 | + notes = "Returns a page of edges owned by tenant. " + | |
375 | + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) | |
307 | 376 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
308 | 377 | @RequestMapping(value = "/tenant/edges", params = {"pageSize", "page"}, method = RequestMethod.GET) |
309 | 378 | @ResponseBody |
310 | 379 | public PageData<Edge> getTenantEdges( |
380 | + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) | |
311 | 381 | @RequestParam int pageSize, |
382 | + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) | |
312 | 383 | @RequestParam int page, |
384 | + @ApiParam(value = EDGE_TYPE_DESCRIPTION) | |
313 | 385 | @RequestParam(required = false) String type, |
386 | + @ApiParam(value = EDGE_TEXT_SEARCH_DESCRIPTION) | |
314 | 387 | @RequestParam(required = false) String textSearch, |
388 | + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = EDGE_SORT_PROPERTY_ALLOWABLE_VALUES) | |
315 | 389 | @RequestParam(required = false) String sortProperty, |
390 | + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) | |
316 | 391 | @RequestParam(required = false) String sortOrder) throws ThingsboardException { |
317 | 392 | try { |
318 | 393 | TenantId tenantId = getCurrentUser().getTenantId(); |
... | ... | @@ -327,15 +402,25 @@ public class EdgeController extends BaseController { |
327 | 402 | } |
328 | 403 | } |
329 | 404 | |
405 | + @ApiOperation(value = "Get Tenant Edge Infos (getTenantEdgeInfos)", | |
406 | + notes = "Returns a page of edges info objects owned by tenant. " + | |
407 | + PAGE_DATA_PARAMETERS + EDGE_INFO_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH, | |
408 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
330 | 409 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
331 | 410 | @RequestMapping(value = "/tenant/edgeInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) |
332 | 411 | @ResponseBody |
333 | 412 | public PageData<EdgeInfo> getTenantEdgeInfos( |
413 | + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) | |
334 | 414 | @RequestParam int pageSize, |
415 | + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) | |
335 | 416 | @RequestParam int page, |
417 | + @ApiParam(value = EDGE_TYPE_DESCRIPTION) | |
336 | 418 | @RequestParam(required = false) String type, |
419 | + @ApiParam(value = EDGE_TEXT_SEARCH_DESCRIPTION) | |
337 | 420 | @RequestParam(required = false) String textSearch, |
421 | + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = EDGE_SORT_PROPERTY_ALLOWABLE_VALUES) | |
338 | 422 | @RequestParam(required = false) String sortProperty, |
423 | + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) | |
339 | 424 | @RequestParam(required = false) String sortOrder) throws ThingsboardException { |
340 | 425 | try { |
341 | 426 | TenantId tenantId = getCurrentUser().getTenantId(); |
... | ... | @@ -350,10 +435,15 @@ public class EdgeController extends BaseController { |
350 | 435 | } |
351 | 436 | } |
352 | 437 | |
438 | + @ApiOperation(value = "Get Tenant Edge (getTenantEdge)", | |
439 | + notes = "Requested edge must be owned by tenant or customer that the user belongs to. " + | |
440 | + "Edge name is an unique property of edge. So it can be used to identify the edge." + TENANT_AUTHORITY_PARAGRAPH, | |
441 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
353 | 442 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
354 | 443 | @RequestMapping(value = "/tenant/edges", params = {"edgeName"}, method = RequestMethod.GET) |
355 | 444 | @ResponseBody |
356 | - public Edge getTenantEdge(@RequestParam String edgeName) throws ThingsboardException { | |
445 | + public Edge getTenantEdge(@ApiParam(value = "Unique name of the edge", required = true) | |
446 | + @RequestParam String edgeName) throws ThingsboardException { | |
357 | 447 | try { |
358 | 448 | TenantId tenantId = getCurrentUser().getTenantId(); |
359 | 449 | return checkNotNull(edgeService.findEdgeByTenantIdAndName(tenantId, edgeName)); |
... | ... | @@ -362,10 +452,16 @@ public class EdgeController extends BaseController { |
362 | 452 | } |
363 | 453 | } |
364 | 454 | |
455 | + @ApiOperation(value = "Set root rule chain for provided edge (setRootRuleChain)", | |
456 | + notes = "Change root rule chain of the edge to the new provided rule chain. \n" + | |
457 | + "This operation will send a notification to update root rule chain on remote edge service." + TENANT_AUTHORITY_PARAGRAPH, | |
458 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
365 | 459 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") |
366 | 460 | @RequestMapping(value = "/edge/{edgeId}/{ruleChainId}/root", method = RequestMethod.POST) |
367 | 461 | @ResponseBody |
368 | - public Edge setRootRuleChain(@PathVariable(EDGE_ID) String strEdgeId, | |
462 | + public Edge setRootRuleChain(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) | |
463 | + @PathVariable(EDGE_ID) String strEdgeId, | |
464 | + @ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION, required = true) | |
369 | 465 | @PathVariable("ruleChainId") String strRuleChainId) throws ThingsboardException { |
370 | 466 | checkParameter(EDGE_ID, strEdgeId); |
371 | 467 | checkParameter("ruleChainId", strRuleChainId); |
... | ... | @@ -394,16 +490,26 @@ public class EdgeController extends BaseController { |
394 | 490 | } |
395 | 491 | } |
396 | 492 | |
493 | + @ApiOperation(value = "Get Customer Edges (getCustomerEdges)", | |
494 | + notes = "Returns a page of edges objects assigned to customer. " + | |
495 | + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) | |
397 | 496 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
398 | 497 | @RequestMapping(value = "/customer/{customerId}/edges", params = {"pageSize", "page"}, method = RequestMethod.GET) |
399 | 498 | @ResponseBody |
400 | 499 | public PageData<Edge> getCustomerEdges( |
500 | + @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) | |
401 | 501 | @PathVariable("customerId") String strCustomerId, |
502 | + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) | |
402 | 503 | @RequestParam int pageSize, |
504 | + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) | |
403 | 505 | @RequestParam int page, |
506 | + @ApiParam(value = EDGE_TYPE_DESCRIPTION) | |
404 | 507 | @RequestParam(required = false) String type, |
508 | + @ApiParam(value = EDGE_TEXT_SEARCH_DESCRIPTION) | |
405 | 509 | @RequestParam(required = false) String textSearch, |
510 | + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = EDGE_SORT_PROPERTY_ALLOWABLE_VALUES) | |
406 | 511 | @RequestParam(required = false) String sortProperty, |
512 | + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) | |
407 | 513 | @RequestParam(required = false) String sortOrder) throws ThingsboardException { |
408 | 514 | checkParameter("customerId", strCustomerId); |
409 | 515 | try { |
... | ... | @@ -429,16 +535,26 @@ public class EdgeController extends BaseController { |
429 | 535 | } |
430 | 536 | } |
431 | 537 | |
538 | + @ApiOperation(value = "Get Customer Edge Infos (getCustomerEdgeInfos)", | |
539 | + notes = "Returns a page of edges info objects assigned to customer. " + | |
540 | + PAGE_DATA_PARAMETERS + EDGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) | |
432 | 541 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
433 | 542 | @RequestMapping(value = "/customer/{customerId}/edgeInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) |
434 | 543 | @ResponseBody |
435 | 544 | public PageData<EdgeInfo> getCustomerEdgeInfos( |
545 | + @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) | |
436 | 546 | @PathVariable("customerId") String strCustomerId, |
547 | + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) | |
437 | 548 | @RequestParam int pageSize, |
549 | + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) | |
438 | 550 | @RequestParam int page, |
551 | + @ApiParam(value = EDGE_TYPE_DESCRIPTION) | |
439 | 552 | @RequestParam(required = false) String type, |
553 | + @ApiParam(value = EDGE_TEXT_SEARCH_DESCRIPTION) | |
440 | 554 | @RequestParam(required = false) String textSearch, |
555 | + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = EDGE_SORT_PROPERTY_ALLOWABLE_VALUES) | |
441 | 556 | @RequestParam(required = false) String sortProperty, |
557 | + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) | |
442 | 558 | @RequestParam(required = false) String sortOrder) throws ThingsboardException { |
443 | 559 | checkParameter("customerId", strCustomerId); |
444 | 560 | try { |
... | ... | @@ -464,10 +580,14 @@ public class EdgeController extends BaseController { |
464 | 580 | } |
465 | 581 | } |
466 | 582 | |
583 | + @ApiOperation(value = "Get Edges By Ids (getEdgesByIds)", | |
584 | + notes = "Requested edges must be owned by tenant or assigned to customer which user is performing the request." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, | |
585 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
467 | 586 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
468 | 587 | @RequestMapping(value = "/edges", params = {"edgeIds"}, method = RequestMethod.GET) |
469 | 588 | @ResponseBody |
470 | 589 | public List<Edge> getEdgesByIds( |
590 | + @ApiParam(value = "A list of edges ids, separated by comma ','", required = true) | |
471 | 591 | @RequestParam("edgeIds") String[] strEdgeIds) throws ThingsboardException { |
472 | 592 | checkArrayParameter("edgeIds", strEdgeIds); |
473 | 593 | try { |
... | ... | @@ -496,6 +616,11 @@ public class EdgeController extends BaseController { |
496 | 616 | } |
497 | 617 | } |
498 | 618 | |
619 | + @ApiOperation(value = "Find related edges (findByQuery)", | |
620 | + notes = "Returns all edges that are related to the specific entity. " + | |
621 | + "The entity id, relation type, edge types, depth of the search, and other query parameters defined using complex 'EdgeSearchQuery' object. " + | |
622 | + "See 'Model' tab of the Parameters for more info." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, | |
623 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
499 | 624 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
500 | 625 | @RequestMapping(value = "/edges", method = RequestMethod.POST) |
501 | 626 | @ResponseBody |
... | ... | @@ -527,6 +652,10 @@ public class EdgeController extends BaseController { |
527 | 652 | } |
528 | 653 | } |
529 | 654 | |
655 | + @ApiOperation(value = "Get Edge Types (getEdgeTypes)", | |
656 | + notes = "Returns a set of unique edge types based on edges that are either owned by the tenant or assigned to the customer which user is performing the request." | |
657 | + + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, | |
658 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
530 | 659 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
531 | 660 | @RequestMapping(value = "/edge/types", method = RequestMethod.GET) |
532 | 661 | @ResponseBody |
... | ... | @@ -541,9 +670,13 @@ public class EdgeController extends BaseController { |
541 | 670 | } |
542 | 671 | } |
543 | 672 | |
673 | + @ApiOperation(value = "Sync edge (syncEdge)", | |
674 | + notes = "Starts synchronization process between edge and cloud. \n" + | |
675 | + "All entities that are assigned to particular edge are going to be send to remote edge service." + TENANT_AUTHORITY_PARAGRAPH) | |
544 | 676 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
545 | 677 | @RequestMapping(value = "/edge/sync/{edgeId}", method = RequestMethod.POST) |
546 | - public void syncEdge(@PathVariable("edgeId") String strEdgeId) throws ThingsboardException { | |
678 | + public void syncEdge(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) | |
679 | + @PathVariable("edgeId") String strEdgeId) throws ThingsboardException { | |
547 | 680 | checkParameter("edgeId", strEdgeId); |
548 | 681 | try { |
549 | 682 | if (isEdgesEnabled()) { |
... | ... | @@ -560,10 +693,13 @@ public class EdgeController extends BaseController { |
560 | 693 | } |
561 | 694 | } |
562 | 695 | |
696 | + @ApiOperation(value = "Find missing rule chains (findMissingToRelatedRuleChains)", | |
697 | + notes = "Returns list of rule chains ids that are not assigned to particular edge, but these rule chains are present in the already assigned rule chains to edge." + TENANT_AUTHORITY_PARAGRAPH) | |
563 | 698 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
564 | 699 | @RequestMapping(value = "/edge/missingToRelatedRuleChains/{edgeId}", method = RequestMethod.GET) |
565 | 700 | @ResponseBody |
566 | - public String findMissingToRelatedRuleChains(@PathVariable("edgeId") String strEdgeId) throws ThingsboardException { | |
701 | + public String findMissingToRelatedRuleChains(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) | |
702 | + @PathVariable("edgeId") String strEdgeId) throws ThingsboardException { | |
567 | 703 | try { |
568 | 704 | EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); |
569 | 705 | edgeId = checkNotNull(edgeId); |
... | ... | @@ -575,9 +711,12 @@ public class EdgeController extends BaseController { |
575 | 711 | } |
576 | 712 | } |
577 | 713 | |
714 | + @ApiOperation(value = "Import the bulk of edges (processEdgesBulkImport)", | |
715 | + notes = "There's an ability to import the bulk of edges using the only .csv file." + TENANT_AUTHORITY_PARAGRAPH, | |
716 | + produces = MediaType.APPLICATION_JSON_VALUE) | |
578 | 717 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") |
579 | 718 | @PostMapping("/edge/bulk_import") |
580 | - public BulkImportResult<Edge> processEdgeBulkImport(@RequestBody BulkImportRequest request) throws Exception { | |
719 | + public BulkImportResult<Edge> processEdgesBulkImport(@RequestBody BulkImportRequest request) throws Exception { | |
581 | 720 | SecurityUser user = getCurrentUser(); |
582 | 721 | RuleChain edgeTemplateRootRuleChain = ruleChainService.getEdgeTemplateRootRuleChain(user.getTenantId()); |
583 | 722 | if (edgeTemplateRootRuleChain == null) { |
... | ... | @@ -586,7 +725,7 @@ public class EdgeController extends BaseController { |
586 | 725 | |
587 | 726 | return edgeBulkImportService.processBulkImport(request, user, importedAssetInfo -> { |
588 | 727 | try { |
589 | - onEdgeCreatedOrUpdated(user.getTenantId(), importedAssetInfo.getEntity(), edgeTemplateRootRuleChain, importedAssetInfo.isUpdated()); | |
728 | + onEdgeCreatedOrUpdated(user.getTenantId(), importedAssetInfo.getEntity(), edgeTemplateRootRuleChain, importedAssetInfo.isUpdated(), user); | |
590 | 729 | } catch (Exception e) { |
591 | 730 | throw new RuntimeException(e); |
592 | 731 | } | ... | ... |
... | ... | @@ -15,8 +15,11 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.controller; |
17 | 17 | |
18 | +import io.swagger.annotations.ApiOperation; | |
19 | +import io.swagger.annotations.ApiParam; | |
18 | 20 | import lombok.extern.slf4j.Slf4j; |
19 | 21 | import org.springframework.beans.factory.annotation.Autowired; |
22 | +import org.springframework.http.MediaType; | |
20 | 23 | import org.springframework.security.access.prepost.PreAuthorize; |
21 | 24 | import org.springframework.web.bind.annotation.PathVariable; |
22 | 25 | import org.springframework.web.bind.annotation.RequestMapping; |
... | ... | @@ -34,6 +37,15 @@ import org.thingsboard.server.dao.edge.EdgeEventService; |
34 | 37 | import org.thingsboard.server.queue.util.TbCoreComponent; |
35 | 38 | import org.thingsboard.server.service.security.permission.Operation; |
36 | 39 | |
40 | +import static org.thingsboard.server.controller.ControllerConstants.EDGE_ID_PARAM_DESCRIPTION; | |
41 | +import static org.thingsboard.server.controller.ControllerConstants.EDGE_SORT_PROPERTY_ALLOWABLE_VALUES; | |
42 | +import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; | |
43 | +import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; | |
44 | +import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; | |
45 | +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; | |
46 | +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; | |
47 | +import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; | |
48 | + | |
37 | 49 | @Slf4j |
38 | 50 | @RestController |
39 | 51 | @TbCoreComponent |
... | ... | @@ -45,17 +57,28 @@ public class EdgeEventController extends BaseController { |
45 | 57 | |
46 | 58 | public static final String EDGE_ID = "edgeId"; |
47 | 59 | |
60 | + @ApiOperation(value = "Get Edge Events (getEdgeEvents)", | |
61 | + notes = "Returns a page of edge events for the requested edge. " + | |
62 | + PAGE_DATA_PARAMETERS, produces = MediaType.APPLICATION_JSON_VALUE) | |
48 | 63 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
49 | 64 | @RequestMapping(value = "/edge/{edgeId}/events", method = RequestMethod.GET) |
50 | 65 | @ResponseBody |
51 | 66 | public PageData<EdgeEvent> getEdgeEvents( |
67 | + @ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) | |
52 | 68 | @PathVariable(EDGE_ID) String strEdgeId, |
69 | + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) | |
53 | 70 | @RequestParam int pageSize, |
71 | + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) | |
54 | 72 | @RequestParam int page, |
73 | + @ApiParam(value = "The case insensitive 'startsWith' filter based on the edge event type name.") | |
55 | 74 | @RequestParam(required = false) String textSearch, |
75 | + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = EDGE_SORT_PROPERTY_ALLOWABLE_VALUES) | |
56 | 76 | @RequestParam(required = false) String sortProperty, |
77 | + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) | |
57 | 78 | @RequestParam(required = false) String sortOrder, |
79 | + @ApiParam(value = "Timestamp. Edge events with creation time before it won't be queried") | |
58 | 80 | @RequestParam(required = false) Long startTime, |
81 | + @ApiParam(value = "Timestamp. Edge events with creation time after it won't be queried") | |
59 | 82 | @RequestParam(required = false) Long endTime) throws ThingsboardException { |
60 | 83 | checkParameter(EDGE_ID, strEdgeId); |
61 | 84 | try { | ... | ... |