Showing
20 changed files
with
1187 additions
and
67 deletions
... | ... | @@ -8375,6 +8375,10 @@ |
8375 | 8375 | "integrity": "sha1-mnHEh0chjrylHlGmbaaCA4zct78=", |
8376 | 8376 | "dev": true |
8377 | 8377 | }, |
8378 | + "material-steppers": { | |
8379 | + "version": "git://github.com/thingsboard/material-steppers.git#ee6241d63b97b7c4a3f3b240cdb347c02a3dcb0a", | |
8380 | + "from": "git://github.com/thingsboard/material-steppers.git#master" | |
8381 | + }, | |
8378 | 8382 | "material-ui": { |
8379 | 8383 | "version": "0.16.7", |
8380 | 8384 | "resolved": "https://registry.npmjs.org/material-ui/-/material-ui-0.16.7.tgz", | ... | ... |
... | ... | @@ -65,6 +65,7 @@ |
65 | 65 | "leaflet": "^1.5.1", |
66 | 66 | "leaflet-polylinedecorator": "^1.6.0", |
67 | 67 | "leaflet-providers": "^1.8.0", |
68 | + "material-steppers": "git://github.com/thingsboard/material-steppers.git#master", | |
68 | 69 | "material-ui": "^0.16.1", |
69 | 70 | "material-ui-number-input": "^5.0.16", |
70 | 71 | "md-color-picker": "0.2.6", | ... | ... |
... | ... | @@ -32,7 +32,8 @@ function AssetService($http, $q, customerService, userService) { |
32 | 32 | getCustomerAssets: getCustomerAssets, |
33 | 33 | findByQuery: findByQuery, |
34 | 34 | fetchAssetsByNameFilter: fetchAssetsByNameFilter, |
35 | - getAssetTypes: getAssetTypes | |
35 | + getAssetTypes: getAssetTypes, | |
36 | + findByName: findByName | |
36 | 37 | } |
37 | 38 | |
38 | 39 | return service; |
... | ... | @@ -276,4 +277,16 @@ function AssetService($http, $q, customerService, userService) { |
276 | 277 | return deferred.promise; |
277 | 278 | } |
278 | 279 | |
280 | + function findByName(assetName, config) { | |
281 | + config = config || {}; | |
282 | + var deferred = $q.defer(); | |
283 | + var url = '/api/tenant/assets?assetName=' + assetName; | |
284 | + $http.get(url, config).then(function success(response) { | |
285 | + deferred.resolve(response.data); | |
286 | + }, function fail() { | |
287 | + deferred.reject(); | |
288 | + }); | |
289 | + return deferred.promise; | |
290 | + } | |
291 | + | |
279 | 292 | } | ... | ... |
... | ... | @@ -30,7 +30,9 @@ function AttributeService($http, $q, $filter, types, telemetryWebsocketService) |
30 | 30 | subscribeForEntityAttributes: subscribeForEntityAttributes, |
31 | 31 | unsubscribeForEntityAttributes: unsubscribeForEntityAttributes, |
32 | 32 | saveEntityAttributes: saveEntityAttributes, |
33 | - deleteEntityAttributes: deleteEntityAttributes | |
33 | + deleteEntityAttributes: deleteEntityAttributes, | |
34 | + saveEntityTimeseries: saveEntityTimeseries, | |
35 | + deleteEntityTimeseries: deleteEntityTimeseries | |
34 | 36 | } |
35 | 37 | |
36 | 38 | return service; |
... | ... | @@ -212,7 +214,8 @@ function AttributeService($http, $q, $filter, types, telemetryWebsocketService) |
212 | 214 | } |
213 | 215 | } |
214 | 216 | |
215 | - function saveEntityAttributes(entityType, entityId, attributeScope, attributes) { | |
217 | + function saveEntityAttributes(entityType, entityId, attributeScope, attributes, config) { | |
218 | + config = config || {}; | |
216 | 219 | var deferred = $q.defer(); |
217 | 220 | var attributesData = {}; |
218 | 221 | var deleteAttributes = []; |
... | ... | @@ -229,7 +232,7 @@ function AttributeService($http, $q, $filter, types, telemetryWebsocketService) |
229 | 232 | } |
230 | 233 | if (Object.keys(attributesData).length) { |
231 | 234 | var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/' + attributeScope; |
232 | - $http.post(url, attributesData).then(function success(response) { | |
235 | + $http.post(url, attributesData, config).then(function success(response) { | |
233 | 236 | if (deleteEntityAttributesPromise) { |
234 | 237 | deleteEntityAttributesPromise.then( |
235 | 238 | function success() { |
... | ... | @@ -260,7 +263,57 @@ function AttributeService($http, $q, $filter, types, telemetryWebsocketService) |
260 | 263 | return deferred.promise; |
261 | 264 | } |
262 | 265 | |
263 | - function deleteEntityAttributes(entityType, entityId, attributeScope, attributes) { | |
266 | + function saveEntityTimeseries(entityType, entityId, timeseriesScope, timeseries, config) { | |
267 | + config = config || {}; | |
268 | + var deferred = $q.defer(); | |
269 | + var timeseriesData = {}; | |
270 | + var deleteTimeseries = []; | |
271 | + for (var a=0; a<timeseries.length;a++) { | |
272 | + if (angular.isDefined(timeseries[a].value) && timeseries[a].value !== null) { | |
273 | + timeseriesData[timeseries[a].key] = timeseries[a].value; | |
274 | + } else { | |
275 | + deleteTimeseries.push(timeseries[a]); | |
276 | + } | |
277 | + } | |
278 | + var deleteEntityTimeseriesPromise; | |
279 | + if (deleteTimeseries.length) { | |
280 | + deleteEntityTimeseriesPromise = deleteEntityTimeseries(entityType, entityId, deleteTimeseries, config); | |
281 | + } | |
282 | + if (Object.keys(timeseriesData).length) { | |
283 | + var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/' + timeseriesScope; | |
284 | + $http.post(url, timeseriesData, config).then(function success(response) { | |
285 | + if (deleteEntityTimeseriesPromise) { | |
286 | + deleteEntityTimeseriesPromise.then( | |
287 | + function success() { | |
288 | + deferred.resolve(response.data); | |
289 | + }, | |
290 | + function fail() { | |
291 | + deferred.reject(); | |
292 | + } | |
293 | + ) | |
294 | + } else { | |
295 | + deferred.resolve(response.data); | |
296 | + } | |
297 | + }, function fail() { | |
298 | + deferred.reject(); | |
299 | + }); | |
300 | + } else if (deleteEntityTimeseriesPromise) { | |
301 | + deleteEntityTimeseriesPromise.then( | |
302 | + function success() { | |
303 | + deferred.resolve(); | |
304 | + }, | |
305 | + function fail() { | |
306 | + deferred.reject(); | |
307 | + } | |
308 | + ) | |
309 | + } else { | |
310 | + deferred.resolve(); | |
311 | + } | |
312 | + return deferred.promise; | |
313 | + } | |
314 | + | |
315 | + function deleteEntityAttributes(entityType, entityId, attributeScope, attributes, config) { | |
316 | + config = config || {}; | |
264 | 317 | var deferred = $q.defer(); |
265 | 318 | var keys = ''; |
266 | 319 | for (var i = 0; i < attributes.length; i++) { |
... | ... | @@ -270,7 +323,7 @@ function AttributeService($http, $q, $filter, types, telemetryWebsocketService) |
270 | 323 | keys += attributes[i].key; |
271 | 324 | } |
272 | 325 | var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/' + attributeScope + '?keys=' + keys; |
273 | - $http.delete(url).then(function success() { | |
326 | + $http.delete(url, config).then(function success() { | |
274 | 327 | deferred.resolve(); |
275 | 328 | }, function fail() { |
276 | 329 | deferred.reject(); |
... | ... | @@ -278,5 +331,23 @@ function AttributeService($http, $q, $filter, types, telemetryWebsocketService) |
278 | 331 | return deferred.promise; |
279 | 332 | } |
280 | 333 | |
334 | + function deleteEntityTimeseries(entityType, entityId, timeseries, config) { | |
335 | + config = config || {}; | |
336 | + var deferred = $q.defer(); | |
337 | + var keys = ''; | |
338 | + for (var i = 0; i < timeseries.length; i++) { | |
339 | + if (i > 0) { | |
340 | + keys += ','; | |
341 | + } | |
342 | + keys += timeseries[i].key; | |
343 | + } | |
344 | + var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/delete' + '?keys=' + keys; | |
345 | + $http.delete(url, config).then(function success() { | |
346 | + deferred.resolve(); | |
347 | + }, function fail() { | |
348 | + deferred.reject(); | |
349 | + }); | |
350 | + return deferred.promise; | |
351 | + } | |
281 | 352 | |
282 | 353 | } |
\ No newline at end of file | ... | ... |
... | ... | @@ -42,8 +42,9 @@ function DeviceService($http, $q, $window, userService, attributeService, custom |
42 | 42 | sendOneWayRpcCommand: sendOneWayRpcCommand, |
43 | 43 | sendTwoWayRpcCommand: sendTwoWayRpcCommand, |
44 | 44 | findByQuery: findByQuery, |
45 | - getDeviceTypes: getDeviceTypes | |
46 | - } | |
45 | + getDeviceTypes: getDeviceTypes, | |
46 | + findByName: findByName | |
47 | + }; | |
47 | 48 | |
48 | 49 | return service; |
49 | 50 | |
... | ... | @@ -124,7 +125,7 @@ function DeviceService($http, $q, $window, userService, attributeService, custom |
124 | 125 | if (!config) { |
125 | 126 | config = {}; |
126 | 127 | } |
127 | - config = Object.assign(config, { ignoreErrors: ignoreErrors }); | |
128 | + config = Object.assign(config, {ignoreErrors: ignoreErrors}); | |
128 | 129 | $http.get(url, config).then(function success(response) { |
129 | 130 | deferred.resolve(response.data); |
130 | 131 | }, function fail(response) { |
... | ... | @@ -136,8 +137,8 @@ function DeviceService($http, $q, $window, userService, attributeService, custom |
136 | 137 | function getDevices(deviceIds, config) { |
137 | 138 | var deferred = $q.defer(); |
138 | 139 | var ids = ''; |
139 | - for (var i=0;i<deviceIds.length;i++) { | |
140 | - if (i>0) { | |
140 | + for (var i = 0; i < deviceIds.length; i++) { | |
141 | + if (i > 0) { | |
141 | 142 | ids += ','; |
142 | 143 | } |
143 | 144 | ids += deviceIds[i]; |
... | ... | @@ -146,11 +147,11 @@ function DeviceService($http, $q, $window, userService, attributeService, custom |
146 | 147 | $http.get(url, config).then(function success(response) { |
147 | 148 | var devices = response.data; |
148 | 149 | devices.sort(function (device1, device2) { |
149 | - var id1 = device1.id.id; | |
150 | - var id2 = device2.id.id; | |
151 | - var index1 = deviceIds.indexOf(id1); | |
152 | - var index2 = deviceIds.indexOf(id2); | |
153 | - return index1 - index2; | |
150 | + var id1 = device1.id.id; | |
151 | + var id2 = device2.id.id; | |
152 | + var index1 = deviceIds.indexOf(id1); | |
153 | + var index2 = deviceIds.indexOf(id2); | |
154 | + return index1 - index2; | |
154 | 155 | }); |
155 | 156 | deferred.resolve(devices); |
156 | 157 | }, function fail(response) { |
... | ... | @@ -159,10 +160,11 @@ function DeviceService($http, $q, $window, userService, attributeService, custom |
159 | 160 | return deferred.promise; |
160 | 161 | } |
161 | 162 | |
162 | - function saveDevice(device) { | |
163 | + function saveDevice(device, config) { | |
164 | + config = config || {}; | |
163 | 165 | var deferred = $q.defer(); |
164 | 166 | var url = '/api/device'; |
165 | - $http.post(url, device).then(function success(response) { | |
167 | + $http.post(url, device, config).then(function success(response) { | |
166 | 168 | deferred.resolve(response.data); |
167 | 169 | }, function fail() { |
168 | 170 | deferred.reject(); |
... | ... | @@ -181,7 +183,8 @@ function DeviceService($http, $q, $window, userService, attributeService, custom |
181 | 183 | return deferred.promise; |
182 | 184 | } |
183 | 185 | |
184 | - function getDeviceCredentials(deviceId, sync) { | |
186 | + function getDeviceCredentials(deviceId, sync, config) { | |
187 | + config = config || {}; | |
185 | 188 | var deferred = $q.defer(); |
186 | 189 | var url = '/api/device/' + deviceId + '/credentials'; |
187 | 190 | if (sync) { |
... | ... | @@ -196,7 +199,7 @@ function DeviceService($http, $q, $window, userService, attributeService, custom |
196 | 199 | deferred.reject(); |
197 | 200 | } |
198 | 201 | } else { |
199 | - $http.get(url, null).then(function success(response) { | |
202 | + $http.get(url, config).then(function success(response) { | |
200 | 203 | deferred.resolve(response.data); |
201 | 204 | }, function fail() { |
202 | 205 | deferred.reject(); |
... | ... | @@ -205,10 +208,11 @@ function DeviceService($http, $q, $window, userService, attributeService, custom |
205 | 208 | return deferred.promise; |
206 | 209 | } |
207 | 210 | |
208 | - function saveDeviceCredentials(deviceCredentials) { | |
211 | + function saveDeviceCredentials(deviceCredentials, config) { | |
212 | + config = config || {}; | |
209 | 213 | var deferred = $q.defer(); |
210 | 214 | var url = '/api/device/credentials'; |
211 | - $http.post(url, deviceCredentials).then(function success(response) { | |
215 | + $http.post(url, deviceCredentials, config).then(function success(response) { | |
212 | 216 | deferred.resolve(response.data); |
213 | 217 | }, function fail() { |
214 | 218 | deferred.reject(); |
... | ... | @@ -297,7 +301,7 @@ function DeviceService($http, $q, $window, userService, attributeService, custom |
297 | 301 | if (!config) { |
298 | 302 | config = {}; |
299 | 303 | } |
300 | - config = Object.assign(config, { ignoreErrors: ignoreErrors }); | |
304 | + config = Object.assign(config, {ignoreErrors: ignoreErrors}); | |
301 | 305 | $http.post(url, query, config).then(function success(response) { |
302 | 306 | deferred.resolve(response.data); |
303 | 307 | }, function fail() { |
... | ... | @@ -317,4 +321,15 @@ function DeviceService($http, $q, $window, userService, attributeService, custom |
317 | 321 | return deferred.promise; |
318 | 322 | } |
319 | 323 | |
324 | + function findByName(deviceName, config) { | |
325 | + config = config || {}; | |
326 | + var deferred = $q.defer(); | |
327 | + var url = '/api/tenant/devices?deviceName=' + deviceName; | |
328 | + $http.get(url, config).then(function success(response) { | |
329 | + deferred.resolve(response.data); | |
330 | + }, function fail() { | |
331 | + deferred.reject(); | |
332 | + }); | |
333 | + return deferred.promise; | |
334 | + } | |
320 | 335 | } | ... | ... |
... | ... | @@ -38,6 +38,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
38 | 38 | createAlarmSourceFromSubscriptionInfo: createAlarmSourceFromSubscriptionInfo, |
39 | 39 | getRelatedEntities: getRelatedEntities, |
40 | 40 | saveRelatedEntity: saveRelatedEntity, |
41 | + saveEntityParameters: saveEntityParameters, | |
41 | 42 | getRelatedEntity: getRelatedEntity, |
42 | 43 | deleteRelatedEntity: deleteRelatedEntity, |
43 | 44 | moveEntity: moveEntity, |
... | ... | @@ -1072,6 +1073,119 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
1072 | 1073 | return deferred.promise; |
1073 | 1074 | } |
1074 | 1075 | |
1076 | + function saveEntityRelation(entityType, entityId, entityRelation, config) { | |
1077 | + const deferred = $q.defer(); | |
1078 | + let attributesType = Object.keys(types.attributesScope); | |
1079 | + let allPromise = []; | |
1080 | + let promise = ""; | |
1081 | + if (entityRelation.accessToken !== "") { | |
1082 | + promise = deviceService.getDeviceCredentials(entityId.id, null, config).then(function (response) { | |
1083 | + response.credentialsId = entityRelation.accessToken; | |
1084 | + response.credentialsType = "ACCESS_TOKEN"; | |
1085 | + response.credentialsValue = null; | |
1086 | + return deviceService.saveDeviceCredentials(response, config).catch(function () { | |
1087 | + return "error"; | |
1088 | + }); | |
1089 | + }); | |
1090 | + allPromise.push(promise) | |
1091 | + } | |
1092 | + for (let i = 0; i < attributesType.length; i++) { | |
1093 | + let attribute = attributesType[i]; | |
1094 | + if (entityRelation.attributes[attribute] && entityRelation.attributes[attribute].length !== 0) { | |
1095 | + promise = attributeService.saveEntityAttributes(entityType, entityId.id, types.attributesScope[attribute].value, entityRelation.attributes[attribute], config).catch(function () { | |
1096 | + return "error"; | |
1097 | + }); | |
1098 | + allPromise.push(promise); | |
1099 | + } | |
1100 | + } | |
1101 | + if (entityRelation.timeseries.length !== 0) { | |
1102 | + promise = attributeService.saveEntityTimeseries(entityType, entityId.id, "time", entityRelation.timeseries, config).catch(function(){ | |
1103 | + return "error"; | |
1104 | + }); | |
1105 | + allPromise.push(promise); | |
1106 | + } | |
1107 | + $q.all(allPromise).then(function success(response) { | |
1108 | + let isResponseHasError = false; | |
1109 | + for(let i = 0; i < response.length; i++){ | |
1110 | + if(response[i] === "error"){ | |
1111 | + isResponseHasError = true; | |
1112 | + break; | |
1113 | + } | |
1114 | + } | |
1115 | + isResponseHasError ? deferred.reject() : deferred.resolve(); | |
1116 | + }); | |
1117 | + return deferred.promise; | |
1118 | + } | |
1119 | + | |
1120 | + function saveEntityParameters(entityType, entityParameters, update, config) { | |
1121 | + config = config || {}; | |
1122 | + const deferred = $q.defer(); | |
1123 | + let statisticalInfo = {}; | |
1124 | + let newEntity = { | |
1125 | + name: entityParameters.name, | |
1126 | + type: entityParameters.type | |
1127 | + }; | |
1128 | + let promise; | |
1129 | + switch (entityType) { | |
1130 | + case types.entityType.device: | |
1131 | + promise = deviceService.saveDevice(newEntity, config); | |
1132 | + break; | |
1133 | + case types.entityType.asset: | |
1134 | + promise = assetService.saveAsset(newEntity, true, config); | |
1135 | + break; | |
1136 | + } | |
1137 | + | |
1138 | + promise.then(function success(response) { | |
1139 | + saveEntityRelation(entityType, response.id, entityParameters, config).then(function success() { | |
1140 | + statisticalInfo.create = { | |
1141 | + entity: 1 | |
1142 | + }; | |
1143 | + deferred.resolve(statisticalInfo); | |
1144 | + }, function fail() { | |
1145 | + statisticalInfo.error = { | |
1146 | + entity: 1 | |
1147 | + }; | |
1148 | + deferred.resolve(statisticalInfo); | |
1149 | + }); | |
1150 | + }, function fail() { | |
1151 | + if (update) { | |
1152 | + let findIdEntity; | |
1153 | + switch (entityType) { | |
1154 | + case types.entityType.device: | |
1155 | + findIdEntity = deviceService.findByName(entityParameters.name, config); | |
1156 | + break; | |
1157 | + case types.entityType.asset: | |
1158 | + findIdEntity = assetService.findByName(entityParameters.name, config); | |
1159 | + break; | |
1160 | + } | |
1161 | + findIdEntity.then(function success(response) { | |
1162 | + saveEntityRelation(entityType, response.id, entityParameters, config).then(function success() { | |
1163 | + statisticalInfo.update = { | |
1164 | + entity: 1 | |
1165 | + }; | |
1166 | + deferred.resolve(statisticalInfo); | |
1167 | + }, function fail() { | |
1168 | + statisticalInfo.error = { | |
1169 | + entity: 1 | |
1170 | + }; | |
1171 | + deferred.resolve(statisticalInfo); | |
1172 | + }); | |
1173 | + }, function fail() { | |
1174 | + statisticalInfo.error = { | |
1175 | + entity: 1 | |
1176 | + }; | |
1177 | + deferred.resolve(statisticalInfo); | |
1178 | + }); | |
1179 | + } else { | |
1180 | + statisticalInfo.error = { | |
1181 | + entity: 1 | |
1182 | + }; | |
1183 | + deferred.resolve(statisticalInfo); | |
1184 | + } | |
1185 | + }); | |
1186 | + return deferred.promise; | |
1187 | + } | |
1188 | + | |
1075 | 1189 | function getRelatedEntity(entityId, keys, typeTranslatePrefix) { |
1076 | 1190 | var deferred = $q.defer(); |
1077 | 1191 | getEntityPromise(entityId.entityType, entityId.id, {ignoreLoading: true}).then( | ... | ... |
... | ... | @@ -54,6 +54,8 @@ import react from 'ngreact'; |
54 | 54 | import '@flowjs/ng-flow/dist/ng-flow-standalone.min'; |
55 | 55 | import 'ngFlowchart/dist/ngFlowchart'; |
56 | 56 | import 'jstree/dist/jstree.min'; |
57 | +import 'material-steppers/dist/material-steppers'; | |
58 | +import 'material-steppers/dist/material-steppers.css' | |
57 | 59 | import 'jstree-bootstrap-theme/dist/themes/proton/style.min.css'; |
58 | 60 | import 'typeface-roboto'; |
59 | 61 | import 'font-awesome/css/font-awesome.min.css'; |
... | ... | @@ -127,6 +129,7 @@ angular.module('thingsboard', [ |
127 | 129 | react.name, |
128 | 130 | 'flow', |
129 | 131 | 'flowchart', |
132 | + 'mdSteppers', | |
130 | 133 | thingsboardThirdpartyFix, |
131 | 134 | thingsboardTranslateHandler, |
132 | 135 | thingsboardLogin, | ... | ... |
... | ... | @@ -48,7 +48,7 @@ export function AssetCardController(types) { |
48 | 48 | |
49 | 49 | /*@ngInject*/ |
50 | 50 | export function AssetController($rootScope, userService, assetService, customerService, $state, $stateParams, |
51 | - $document, $mdDialog, $q, $translate, types) { | |
51 | + $document, $mdDialog, $q, $translate, types, importExport) { | |
52 | 52 | |
53 | 53 | var customerId = $stateParams.customerId; |
54 | 54 | |
... | ... | @@ -56,6 +56,29 @@ export function AssetController($rootScope, userService, assetService, customerS |
56 | 56 | |
57 | 57 | var assetGroupActionsList = []; |
58 | 58 | |
59 | + var assetAddItemActionsList = [ | |
60 | + { | |
61 | + onAction: function ($event) { | |
62 | + vm.grid.addItem($event); | |
63 | + }, | |
64 | + name: function() { return $translate.instant('action.add') }, | |
65 | + details: function() { return $translate.instant('asset.add-asset-text') }, | |
66 | + icon: "insert_drive_file" | |
67 | + }, | |
68 | + { | |
69 | + onAction: function ($event) { | |
70 | + importExport.importEntities($event, types.entityType.asset).then( | |
71 | + function() { | |
72 | + vm.grid.refreshList(); | |
73 | + } | |
74 | + ); | |
75 | + }, | |
76 | + name: function() { return $translate.instant('action.import') }, | |
77 | + details: function() { return $translate.instant('asset.import') }, | |
78 | + icon: "file_upload" | |
79 | + } | |
80 | + ]; | |
81 | + | |
59 | 82 | var vm = this; |
60 | 83 | |
61 | 84 | vm.types = types; |
... | ... | @@ -77,6 +100,7 @@ export function AssetController($rootScope, userService, assetService, customerS |
77 | 100 | |
78 | 101 | actionsList: assetActionsList, |
79 | 102 | groupActionsList: assetGroupActionsList, |
103 | + addItemActions: assetAddItemActionsList, | |
80 | 104 | |
81 | 105 | onGridInited: gridInited, |
82 | 106 | |
... | ... | @@ -294,6 +318,8 @@ export function AssetController($rootScope, userService, assetService, customerS |
294 | 318 | } else if (vm.assetsScope === 'customer_user') { |
295 | 319 | vm.assetGridConfig.addItemAction = {}; |
296 | 320 | } |
321 | + vm.assetGridConfig.addItemActions = []; | |
322 | + | |
297 | 323 | } |
298 | 324 | |
299 | 325 | vm.assetGridConfig.refreshParamsFunc = refreshAssetsParamsFunction; | ... | ... |
... | ... | @@ -350,6 +350,40 @@ export default angular.module('thingsboard.types', []) |
350 | 350 | rulenode: "RULE_NODE", |
351 | 351 | entityView: "ENTITY_VIEW" |
352 | 352 | }, |
353 | + importEntityColumnType: { | |
354 | + name: { | |
355 | + name: 'import.column-type.name', | |
356 | + value: 'name' | |
357 | + }, | |
358 | + type: { | |
359 | + name: 'import.column-type.type', | |
360 | + value: 'type' | |
361 | + }, | |
362 | + clientAttribute: { | |
363 | + name: 'import.column-type.client-attribute', | |
364 | + value: 'CLIENT_ATTRIBUTE' | |
365 | + }, | |
366 | + sharedAttribute: { | |
367 | + name: 'import.column-type.shared-attribute', | |
368 | + value: 'SHARED_ATTRIBUTE' | |
369 | + }, | |
370 | + serverAttribute: { | |
371 | + name: 'import.column-type.server-attribute', | |
372 | + value: 'SERVER_ATTRIBUTE' | |
373 | + }, | |
374 | + timeseries: { | |
375 | + name: 'import.column-type.timeseries', | |
376 | + value: 'TIMESERIES' | |
377 | + }, | |
378 | + entityField: { | |
379 | + name: 'import.column-type.entity-field', | |
380 | + value: 'ENTITY_FIELD' | |
381 | + }, | |
382 | + accessToken: { | |
383 | + name: 'import.column-type.access-token', | |
384 | + value: 'ACCESS_TOKEN' | |
385 | + } | |
386 | + }, | |
353 | 387 | aliasEntityType: { |
354 | 388 | current_customer: "CURRENT_CUSTOMER" |
355 | 389 | }, | ... | ... |
... | ... | @@ -49,7 +49,7 @@ export function DeviceCardController(types) { |
49 | 49 | |
50 | 50 | /*@ngInject*/ |
51 | 51 | export function DeviceController($rootScope, userService, deviceService, customerService, $state, $stateParams, |
52 | - $document, $mdDialog, $q, $translate, types) { | |
52 | + $document, $mdDialog, $q, $translate, types, importExport) { | |
53 | 53 | |
54 | 54 | var customerId = $stateParams.customerId; |
55 | 55 | |
... | ... | @@ -57,6 +57,29 @@ export function DeviceController($rootScope, userService, deviceService, custome |
57 | 57 | |
58 | 58 | var deviceGroupActionsList = []; |
59 | 59 | |
60 | + var deviceAddItemActionsList = [ | |
61 | + { | |
62 | + onAction: function ($event) { | |
63 | + vm.grid.addItem($event); | |
64 | + }, | |
65 | + name: function() { return $translate.instant('action.add') }, | |
66 | + details: function() { return $translate.instant('device.add-device-text') }, | |
67 | + icon: "insert_drive_file" | |
68 | + }, | |
69 | + { | |
70 | + onAction: function ($event) { | |
71 | + importExport.importEntities($event, types.entityType.device).then( | |
72 | + function() { | |
73 | + vm.grid.refreshList(); | |
74 | + } | |
75 | + ); | |
76 | + }, | |
77 | + name: function() { return $translate.instant('action.import') }, | |
78 | + details: function() { return $translate.instant('device.import') }, | |
79 | + icon: "file_upload" | |
80 | + } | |
81 | + ]; | |
82 | + | |
60 | 83 | var vm = this; |
61 | 84 | |
62 | 85 | vm.types = types; |
... | ... | @@ -78,12 +101,12 @@ export function DeviceController($rootScope, userService, deviceService, custome |
78 | 101 | |
79 | 102 | actionsList: deviceActionsList, |
80 | 103 | groupActionsList: deviceGroupActionsList, |
104 | + addItemActions: deviceAddItemActionsList, | |
81 | 105 | |
82 | 106 | onGridInited: gridInited, |
83 | 107 | |
84 | 108 | addItemTemplateUrl: addDeviceTemplate, |
85 | 109 | |
86 | - addItemText: function() { return $translate.instant('device.add-device-text') }, | |
87 | 110 | noItemsText: function() { return $translate.instant('device.no-devices-text') }, |
88 | 111 | itemDetailsText: function() { return $translate.instant('device.device-details') }, |
89 | 112 | isDetailsReadOnly: isCustomerUser, |
... | ... | @@ -314,7 +337,6 @@ export function DeviceController($rootScope, userService, deviceService, custome |
314 | 337 | icon: "add" |
315 | 338 | }; |
316 | 339 | |
317 | - | |
318 | 340 | } else if (vm.devicesScope === 'customer_user') { |
319 | 341 | deviceActionsList.push( |
320 | 342 | { |
... | ... | @@ -329,6 +351,8 @@ export function DeviceController($rootScope, userService, deviceService, custome |
329 | 351 | |
330 | 352 | vm.deviceGridConfig.addItemAction = {}; |
331 | 353 | } |
354 | + vm.deviceGridConfig.addItemActions = []; | |
355 | + | |
332 | 356 | } |
333 | 357 | |
334 | 358 | vm.deviceGridConfig.refreshParamsFunc = refreshDevicesParamsFunction; | ... | ... |
... | ... | @@ -21,6 +21,7 @@ export default function GlobalInterceptor($rootScope, $q, $injector) { |
21 | 21 | var userService; |
22 | 22 | var types; |
23 | 23 | var http; |
24 | + var timeout; | |
24 | 25 | |
25 | 26 | var internalUrlPrefixes = [ |
26 | 27 | '/api/auth/token', |
... | ... | @@ -71,6 +72,13 @@ export default function GlobalInterceptor($rootScope, $q, $injector) { |
71 | 72 | return http; |
72 | 73 | } |
73 | 74 | |
75 | + function getTimeout() { | |
76 | + if (!timeout) { | |
77 | + timeout = $injector.get("$timeout"); | |
78 | + } | |
79 | + return timeout; | |
80 | + } | |
81 | + | |
74 | 82 | function rejectionErrorCode(rejection) { |
75 | 83 | if (rejection && rejection.data && rejection.data.errorCode) { |
76 | 84 | return rejection.data.errorCode; |
... | ... | @@ -144,12 +152,20 @@ export default function GlobalInterceptor($rootScope, $q, $injector) { |
144 | 152 | return response; |
145 | 153 | } |
146 | 154 | |
155 | + function retryRequest (httpConfig) { | |
156 | + var thisTimeout = 1000 + Math.random() * 3000; | |
157 | + return getTimeout()(function() { | |
158 | + return getHttp()(httpConfig); | |
159 | + }, thisTimeout); | |
160 | + } | |
161 | + | |
147 | 162 | function responseError(rejection) { |
148 | 163 | if (rejection.config.url.startsWith('/api/')) { |
149 | 164 | updateLoadingState(rejection.config, false); |
150 | 165 | } |
151 | 166 | var unhandled = false; |
152 | 167 | var ignoreErrors = rejection.config.ignoreErrors; |
168 | + var resendRequest = rejection.config.resendRequest; | |
153 | 169 | if (rejection.refreshTokenPending || rejection.status === 401) { |
154 | 170 | var errorCode = rejectionErrorCode(rejection); |
155 | 171 | if (rejection.refreshTokenPending || (errorCode && errorCode === getTypes().serverErrorCode.jwtTokenExpired)) { |
... | ... | @@ -161,6 +177,10 @@ export default function GlobalInterceptor($rootScope, $q, $injector) { |
161 | 177 | if (!ignoreErrors) { |
162 | 178 | $rootScope.$broadcast('forbidden'); |
163 | 179 | } |
180 | + } else if (rejection.status === 429) { | |
181 | + if (resendRequest) { | |
182 | + return retryRequest(rejection.config); | |
183 | + } | |
164 | 184 | } else if (rejection.status === 0 || rejection.status === -1) { |
165 | 185 | getToast().showError(getTranslate().instant('error.unable-to-connect')); |
166 | 186 | } else if (!rejection.config.url.startsWith('/api/plugins/rpc')) { | ... | ... |
... | ... | @@ -96,6 +96,7 @@ export default angular.module('thingsboard.help', []) |
96 | 96 | assets: helpBaseUrl + "/docs/user-guide/ui/assets", |
97 | 97 | devices: helpBaseUrl + "/docs/user-guide/ui/devices", |
98 | 98 | entityViews: helpBaseUrl + "/docs/user-guide/ui/entity-views", |
99 | + entitiesImport: helpBaseUrl + "/docs/user-guide/bulk-provisioning", | |
99 | 100 | dashboards: helpBaseUrl + "/docs/user-guide/ui/dashboards", |
100 | 101 | users: helpBaseUrl + "/docs/user-guide/ui/users", |
101 | 102 | widgetsBundles: helpBaseUrl + "/docs/user-guide/ui/widget-library#bundles", | ... | ... |
1 | +/* | |
2 | + * Copyright © 2016-2019 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 | +import './import-dialog.scss'; | |
17 | + | |
18 | +/*@ngInject*/ | |
19 | +export default function ImportDialogCsvController($scope, $mdDialog, toast, importTitle, importFileLabel, entityType, importExport, types, $mdStepper, $timeout) { | |
20 | + | |
21 | + var vm = this; | |
22 | + | |
23 | + vm.cancel = cancel; | |
24 | + vm.fileAdded = fileAdded; | |
25 | + vm.clearFile = clearFile; | |
26 | + vm.nextStep = nextStep; | |
27 | + vm.previousStep = previousStep; | |
28 | + | |
29 | + vm.importParameters = { | |
30 | + delim: ',', | |
31 | + isUpdate: true, | |
32 | + isHeader: true | |
33 | + }; | |
34 | + | |
35 | + vm.importTitle = importTitle; | |
36 | + vm.importFileLabel = importFileLabel; | |
37 | + vm.entityType = entityType; | |
38 | + | |
39 | + vm.isVertical = true; | |
40 | + vm.isLinear = true; | |
41 | + vm.isAlternative = false; | |
42 | + vm.isMobileStepText = true; | |
43 | + vm.isImportData = false; | |
44 | + | |
45 | + vm.parseData = []; | |
46 | + | |
47 | + vm.delimiters = [{ | |
48 | + key: ',', | |
49 | + value: ',' | |
50 | + }, { | |
51 | + key: ';', | |
52 | + value: ';' | |
53 | + }, { | |
54 | + key: '|', | |
55 | + value: '|' | |
56 | + }, { | |
57 | + key: '\t', | |
58 | + value: 'Tab' | |
59 | + }]; | |
60 | + | |
61 | + vm.progressCreate = 0; | |
62 | + | |
63 | + var parseData = {}; | |
64 | + | |
65 | + function fileAdded($file) { | |
66 | + if ($file.getExtension() === 'csv') { | |
67 | + var reader = new FileReader(); | |
68 | + reader.onload = function (event) { | |
69 | + $scope.$apply(function () { | |
70 | + if (event.target.result) { | |
71 | + vm.theFormStep1.$setDirty(); | |
72 | + var importCSV = event.target.result; | |
73 | + if (importCSV && importCSV.length > 0) { | |
74 | + try { | |
75 | + vm.importData = importCSV; | |
76 | + vm.fileName = $file.name; | |
77 | + } catch (err) { | |
78 | + vm.fileName = null; | |
79 | + toast.showError(err.message); | |
80 | + } | |
81 | + } | |
82 | + } | |
83 | + }); | |
84 | + }; | |
85 | + reader.readAsText($file.file); | |
86 | + } | |
87 | + } | |
88 | + | |
89 | + function parseCSV(importData) { | |
90 | + var config = { | |
91 | + delim: vm.importParameters.delim, | |
92 | + header: vm.importParameters.isHeader | |
93 | + }; | |
94 | + return importExport.convertCSVToJson(importData, config); | |
95 | + } | |
96 | + | |
97 | + function createColumnsData(parseData) { | |
98 | + vm.columnsParam = []; | |
99 | + var columnParam = {}; | |
100 | + for (var i = 0; i < parseData.headers.length; i++) { | |
101 | + if (vm.importParameters.isHeader && parseData.headers[i].search(/^(name|type)$/im) === 0) { | |
102 | + columnParam = { | |
103 | + type: types.importEntityColumnType[parseData.headers[i].toLowerCase()].value, | |
104 | + key: parseData.headers[i].toLowerCase(), | |
105 | + sampleData: parseData.rows[0][i] | |
106 | + }; | |
107 | + } else { | |
108 | + columnParam = { | |
109 | + type: types.importEntityColumnType.serverAttribute.value, | |
110 | + key: vm.importParameters.isHeader ? parseData.headers[i] : "", | |
111 | + sampleData: parseData.rows[0][i] | |
112 | + }; | |
113 | + } | |
114 | + vm.columnsParam.push(columnParam); | |
115 | + } | |
116 | + } | |
117 | + | |
118 | + function addEntities (importData, parameterColumns) { | |
119 | + var entitiesData = []; | |
120 | + var sentDataLength = 0; | |
121 | + var config = { | |
122 | + ignoreErrors: true, | |
123 | + resendRequest: true | |
124 | + }; | |
125 | + for (var i = 0; i < importData.rows.length; i++) { | |
126 | + var entityData = { | |
127 | + name: "", | |
128 | + type: "", | |
129 | + accessToken: "", | |
130 | + attributes: { | |
131 | + server: [], | |
132 | + shared: [] | |
133 | + }, | |
134 | + timeseries: [] | |
135 | + }; | |
136 | + for (var j = 0; j < parameterColumns.length; j++) { | |
137 | + switch (parameterColumns[j].type) { | |
138 | + case types.importEntityColumnType.serverAttribute.value: | |
139 | + entityData.attributes.server.push({ | |
140 | + key: parameterColumns[j].key, | |
141 | + value: importData.rows[i][j] | |
142 | + }); | |
143 | + break; | |
144 | + case types.importEntityColumnType.timeseries.value: | |
145 | + entityData.timeseries.push({ | |
146 | + key: parameterColumns[j].key, | |
147 | + value: importData.rows[i][j] | |
148 | + }); | |
149 | + break; | |
150 | + case types.importEntityColumnType.sharedAttribute.value: | |
151 | + entityData.attributes.shared.push({ | |
152 | + key: parameterColumns[j].key, | |
153 | + value: importData.rows[i][j] | |
154 | + }); | |
155 | + break; | |
156 | + case types.importEntityColumnType.accessToken.value: | |
157 | + entityData.accessToken = importData.rows[i][j]; | |
158 | + break; | |
159 | + case types.importEntityColumnType.name.value: | |
160 | + entityData.name = importData.rows[i][j]; | |
161 | + break; | |
162 | + case types.importEntityColumnType.type.value: | |
163 | + entityData.type = importData.rows[i][j]; | |
164 | + break; | |
165 | + } | |
166 | + } | |
167 | + entitiesData.push(entityData); | |
168 | + } | |
169 | + $scope.$on('createImportEntityCompleted', function () { | |
170 | + sentDataLength++; | |
171 | + vm.progressCreate = Math.round((sentDataLength / importData.rows.length) * 100); | |
172 | + }); | |
173 | + importExport.createMultiEntity(entitiesData, vm.entityType, vm.importParameters.isUpdate, config).then(function (response) { | |
174 | + vm.statistical = response; | |
175 | + vm.isImportData = false; | |
176 | + $mdStepper('import-stepper').next(); | |
177 | + }); | |
178 | + } | |
179 | + | |
180 | + function clearFile() { | |
181 | + vm.theFormStep1.$setDirty(); | |
182 | + vm.fileName = null; | |
183 | + vm.importData = null; | |
184 | + } | |
185 | + | |
186 | + function previousStep(step) { | |
187 | + let steppers = $mdStepper('import-stepper'); | |
188 | + switch (step) { | |
189 | + case 1: | |
190 | + steppers.back(); | |
191 | + $timeout(function () { | |
192 | + vm.theFormStep1.$setDirty(); | |
193 | + }); | |
194 | + break; | |
195 | + default: | |
196 | + steppers.back(); | |
197 | + break; | |
198 | + } | |
199 | + } | |
200 | + | |
201 | + function nextStep(step) { | |
202 | + let steppers = $mdStepper('import-stepper'); | |
203 | + switch (step) { | |
204 | + case 2: | |
205 | + steppers.next(); | |
206 | + break; | |
207 | + case 3: | |
208 | + parseData = parseCSV(vm.importData); | |
209 | + if (parseData === -1) { | |
210 | + steppers.back(); | |
211 | + $timeout(function () { | |
212 | + clearFile(); | |
213 | + }); | |
214 | + } else { | |
215 | + createColumnsData(parseData); | |
216 | + steppers.next(); | |
217 | + } | |
218 | + break; | |
219 | + case 4: | |
220 | + steppers.next(); | |
221 | + vm.isImportData = true; | |
222 | + addEntities(parseData, vm.columnsParam); | |
223 | + break; | |
224 | + case 6: | |
225 | + $mdDialog.hide(); | |
226 | + break; | |
227 | + } | |
228 | + | |
229 | + } | |
230 | + | |
231 | + function cancel() { | |
232 | + if($mdStepper('import-stepper').currentStep > 2){ | |
233 | + $mdDialog.hide(); | |
234 | + } else { | |
235 | + $mdDialog.cancel(); | |
236 | + } | |
237 | + } | |
238 | +} | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2019 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<md-dialog aria-label="{{ vm.importTitle | translate }}" class="tb-import-stepper" tb-help="'entitiesImport'" | |
19 | + help-container-id="help-container"> | |
20 | + <md-toolbar> | |
21 | + <div class="md-toolbar-tools"> | |
22 | + <h2 translate>{{ vm.importTitle }}</h2> | |
23 | + <span flex></span> | |
24 | + <div id="help-container"></div> | |
25 | + <md-button class="md-icon-button" ng-click="vm.cancel()" ng-disabled="vm.isImportData"> | |
26 | + <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon> | |
27 | + </md-button> | |
28 | + </div> | |
29 | + </md-toolbar> | |
30 | + | |
31 | + <span style="max-height: 5px; height: 5px;" flex></span> | |
32 | + <md-dialog-content> | |
33 | + <md-stepper id="import-stepper" md-mobile-step-text="vm.isMobileStepText" md-vertical="vm.isVertical" | |
34 | + md-linear="vm.isLinear" md-alternative="vm.isAlternative"> | |
35 | + <md-step md-label="{{ 'import.stepper-text.select-file' | translate }}"> | |
36 | + <md-step-body> | |
37 | + <form name="vm.theFormStep1"> | |
38 | + <fieldset ng-disabled="$root.loading"> | |
39 | + <div layout="column" layout-padding> | |
40 | + <div class="tb-container"> | |
41 | + <label class="tb-label" translate>{{ vm.importFileLabel }}</label> | |
42 | + <div flow-init="{singleFile:true}" | |
43 | + flow-file-added="vm.fileAdded( $file )" class="tb-file-select-container"> | |
44 | + <div class="tb-file-clear-container"> | |
45 | + <md-button ng-click="vm.clearFile()" | |
46 | + class="tb-file-clear-btn md-icon-button md-primary" | |
47 | + aria-label="{{ 'action.remove' | translate }}"> | |
48 | + <md-tooltip md-direction="top"> | |
49 | + {{ 'action.remove' | translate }} | |
50 | + </md-tooltip> | |
51 | + <md-icon aria-label="{{ 'action.remove' | translate }}" | |
52 | + class="material-icons"> | |
53 | + close | |
54 | + </md-icon> | |
55 | + </md-button> | |
56 | + </div> | |
57 | + <div class="alert tb-flow-drop" flow-drop> | |
58 | + <label for="select" translate>import.drop-file-csv</label> | |
59 | + <input class="file-input" flow-btn | |
60 | + flow-attrs="{accept:'.csv,application/csv,text/csv'}" | |
61 | + id="select"> | |
62 | + </div> | |
63 | + </div> | |
64 | + </div> | |
65 | + <div> | |
66 | + <div ng-show="!vm.fileName" translate>import.no-file</div> | |
67 | + <div ng-show="vm.fileName">{{ vm.fileName }}</div> | |
68 | + </div> | |
69 | + </div> | |
70 | + </fieldset> | |
71 | + </form> | |
72 | + </md-step-body> | |
73 | + | |
74 | + <md-step-actions layout="row"> | |
75 | + <span flex></span> | |
76 | + <md-button ng-disabled="$root.loading" ng-click="vm.cancel()"> | |
77 | + {{ 'action.cancel' | translate }} | |
78 | + </md-button> | |
79 | + <md-button class="md-primary md-raised" | |
80 | + ng-disabled="$root.loading || !vm.theFormStep1.$dirty || !vm.theFormStep1.$valid || !vm.importData" | |
81 | + ng-click="vm.nextStep(2);"> | |
82 | + {{ 'action.continue' | translate }} | |
83 | + </md-button> | |
84 | + </md-step-actions> | |
85 | + </md-step> | |
86 | + | |
87 | + <md-step md-label="{{ 'import.stepper-text.configuration' | translate }}"> | |
88 | + <md-step-body> | |
89 | + <div layout="column"> | |
90 | + <md-input-container> | |
91 | + <label translate>import.csv-delimiter</label> | |
92 | + <md-select ng-model="vm.importParameters.delim"> | |
93 | + <md-option ng-repeat="delimiter in vm.delimiters" ng-value="delimiter.key"> | |
94 | + {{delimiter.value}} | |
95 | + </md-option> | |
96 | + </md-select> | |
97 | + </md-input-container> | |
98 | + <md-checkbox ng-model="vm.importParameters.isHeader" | |
99 | + aria-label="First line contains column names"> | |
100 | + {{ 'import.csv-first-line-header' | translate }} | |
101 | + </md-checkbox> | |
102 | + <md-checkbox ng-model="vm.importParameters.isUpdate" aria-label="Update attributes/telemetry"> | |
103 | + {{ 'import.csv-update-data' | translate }} | |
104 | + </md-checkbox> | |
105 | + </div> | |
106 | + </md-step-body> | |
107 | + | |
108 | + <md-step-actions layout="row"> | |
109 | + <md-button ng-disabled="$root.loading" ng-click="vm.previousStep(1);">Back | |
110 | + </md-button> | |
111 | + <span flex></span> | |
112 | + <md-button ng-disabled="$root.loading" ng-click="vm.cancel()"> | |
113 | + {{ 'action.cancel' | translate }} | |
114 | + </md-button> | |
115 | + <md-button class="md-primary md-raised" ng-disabled="$root.loading" ng-click="vm.nextStep(3);"> | |
116 | + {{ 'action.continue' | translate }} | |
117 | + </md-button> | |
118 | + </md-step-actions> | |
119 | + </md-step> | |
120 | + | |
121 | + <md-step md-label="{{ 'import.stepper-text.column-type' | translate }}"> | |
122 | + <md-step-body> | |
123 | + <form name="vm.theFormStep3"> | |
124 | + <tb-table-columns-assignment columns="vm.columnsParam" the-form="vm.theFormStep3" | |
125 | + entity-type="vm.entityType"></tb-table-columns-assignment> | |
126 | + </form> | |
127 | + </md-step-body> | |
128 | + | |
129 | + <md-step-actions layout="row"> | |
130 | + <md-button ng-disabled="$root.loading" ng-click="vm.previousStep();">Back | |
131 | + </md-button> | |
132 | + <span flex></span> | |
133 | + <md-button ng-disabled="$root.loading" ng-click="vm.cancel()"> | |
134 | + {{ 'action.cancel' | translate }} | |
135 | + </md-button> | |
136 | + <md-button class="md-primary md-raised" | |
137 | + ng-disabled="$root.loading || !vm.theFormStep3.$dirty || !vm.theFormStep3.$valid" | |
138 | + ng-click="vm.nextStep(4);"> | |
139 | + {{ 'action.continue' | translate }} | |
140 | + </md-button> | |
141 | + </md-step-actions> | |
142 | + </md-step> | |
143 | + | |
144 | + <md-step md-label="{{ 'import.stepper-text.creat-entities' | translate }}"> | |
145 | + <md-step-body> | |
146 | + <md-progress-linear class="md-warn tb-import-progress" md-mode="determinate" | |
147 | + value="{{vm.progressCreate}}"></md-progress-linear> | |
148 | + </md-step-body> | |
149 | + </md-step> | |
150 | + <md-step md-label="{{ 'import.stepper-text.done' | translate }}"> | |
151 | + <md-step-body layout="column"> | |
152 | + <div> | |
153 | + <p class="md-body-1" translate translate-values="{count: vm.statistical.create.entity}" | |
154 | + ng-if="vm.statistical.create && vm.statistical.create.entity">import.message.create-entities</p> | |
155 | + <p class="md-body-1" translate translate-values="{count: vm.statistical.update.entity}" | |
156 | + ng-if="vm.statistical.update && vm.statistical.update.entity">import.message.update-entities</p> | |
157 | + <p class="md-body-1" translate translate-values="{count: vm.statistical.error.entity}" | |
158 | + ng-if="vm.statistical.error && vm.statistical.error.entity">import.message.error-entities</p> | |
159 | + </div> | |
160 | + </md-step-body> | |
161 | + <md-step-actions layout="row"> | |
162 | + <span flex></span> | |
163 | + <md-button class="md-primary md-raised" ng-disabled="$root.loading" ng-click="vm.nextStep(6);"> | |
164 | + {{ 'action.ok' | translate }} | |
165 | + </md-button> | |
166 | + </md-step-actions> | |
167 | + </md-step> | |
168 | + | |
169 | + </md-stepper> | |
170 | + </md-dialog-content> | |
171 | +</md-dialog> | ... | ... |
... | ... | @@ -13,6 +13,8 @@ |
13 | 13 | * See the License for the specific language governing permissions and |
14 | 14 | * limitations under the License. |
15 | 15 | */ |
16 | +@import "../../scss/constants.scss"; | |
17 | + | |
16 | 18 | $previewSize: 100px !default; |
17 | 19 | |
18 | 20 | .tb-file-select-container { |
... | ... | @@ -40,3 +42,21 @@ $previewSize: 100px !default; |
40 | 42 | top: 50%; |
41 | 43 | transform: translate(0%, -50%) !important; |
42 | 44 | } |
45 | + | |
46 | +.tb-table-select{ | |
47 | + md-input-container{ | |
48 | + margin: 0; | |
49 | + | |
50 | + .md-errors-spacer{ | |
51 | + min-height: 0; | |
52 | + } | |
53 | + } | |
54 | +} | |
55 | + | |
56 | +.md-stepper-indicator-wrapper{ | |
57 | + background-color: transparent; | |
58 | +} | |
59 | + | |
60 | +.tb-import-progress{ | |
61 | + margin: 7px 0; | |
62 | +} | ... | ... |
... | ... | @@ -13,9 +13,10 @@ |
13 | 13 | * See the License for the specific language governing permissions and |
14 | 14 | * limitations under the License. |
15 | 15 | */ |
16 | -/* eslint-disable import/no-unresolved, import/default */ | |
16 | +/* eslint-disable import/no-unresolved, import/default*/ | |
17 | 17 | |
18 | 18 | import importDialogTemplate from './import-dialog.tpl.html'; |
19 | +import importDialogCSVTemplate from './import-dialog-csv.tpl.html'; | |
19 | 20 | import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html'; |
20 | 21 | |
21 | 22 | /* eslint-enable import/no-unresolved, import/default */ |
... | ... | @@ -24,7 +25,7 @@ import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html'; |
24 | 25 | /* eslint-disable no-undef, angular/window-service, angular/document-service */ |
25 | 26 | |
26 | 27 | /*@ngInject*/ |
27 | -export default function ImportExport($log, $translate, $q, $mdDialog, $document, $http, itembuffer, utils, types, | |
28 | +export default function ImportExport($log, $translate, $q, $mdDialog, $document, $http, itembuffer, utils, types, $rootScope, | |
28 | 29 | dashboardUtils, entityService, dashboardService, ruleChainService, widgetService, toast, attributeService) { |
29 | 30 | |
30 | 31 | |
... | ... | @@ -41,7 +42,10 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
41 | 42 | importWidgetsBundle: importWidgetsBundle, |
42 | 43 | exportExtension: exportExtension, |
43 | 44 | importExtension: importExtension, |
44 | - exportToPc: exportToPc | |
45 | + importEntities: importEntities, | |
46 | + convertCSVToJson: convertCSVToJson, | |
47 | + exportToPc: exportToPc, | |
48 | + createMultiEntity: createMultiEntity | |
45 | 49 | }; |
46 | 50 | |
47 | 51 | return service; |
... | ... | @@ -54,11 +58,11 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
54 | 58 | var bundleAlias = widgetsBundle.alias; |
55 | 59 | var isSystem = widgetsBundle.tenantId.id === types.id.nullUid; |
56 | 60 | widgetService.getBundleWidgetTypes(bundleAlias, isSystem).then( |
57 | - function success (widgetTypes) { | |
61 | + function success(widgetTypes) { | |
58 | 62 | prepareExport(widgetsBundle); |
59 | 63 | var widgetsBundleItem = { |
60 | - widgetsBundle: prepareExport(widgetsBundle), | |
61 | - widgetTypes: [] | |
64 | + widgetsBundle: prepareExport(widgetsBundle), | |
65 | + widgetTypes: [] | |
62 | 66 | }; |
63 | 67 | for (var t in widgetTypes) { |
64 | 68 | var widgetType = widgetTypes[t]; |
... | ... | @@ -68,10 +72,10 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
68 | 72 | widgetsBundleItem.widgetTypes.push(prepareExport(widgetType)); |
69 | 73 | } |
70 | 74 | var name = widgetsBundle.title; |
71 | - name = name.toLowerCase().replace(/\W/g,"_"); | |
75 | + name = name.toLowerCase().replace(/\W/g, "_"); | |
72 | 76 | exportToPc(widgetsBundleItem, name + '.json'); |
73 | 77 | }, |
74 | - function fail (rejection) { | |
78 | + function fail(rejection) { | |
75 | 79 | var message = rejection; |
76 | 80 | if (!message) { |
77 | 81 | message = $translate.instant('error.unknown-error'); |
... | ... | @@ -168,7 +172,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
168 | 172 | delete widgetType.bundleAlias; |
169 | 173 | } |
170 | 174 | var name = widgetType.name; |
171 | - name = name.toLowerCase().replace(/\W/g,"_"); | |
175 | + name = name.toLowerCase().replace(/\W/g, "_"); | |
172 | 176 | exportToPc(prepareExport(widgetType), name + '.json'); |
173 | 177 | }, |
174 | 178 | function fail(rejection) { |
... | ... | @@ -209,8 +213,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
209 | 213 | |
210 | 214 | function validateImportedWidgetType(widgetType) { |
211 | 215 | if (angular.isUndefined(widgetType.name) |
212 | - || angular.isUndefined(widgetType.descriptor)) | |
213 | - { | |
216 | + || angular.isUndefined(widgetType.descriptor)) { | |
214 | 217 | return false; |
215 | 218 | } |
216 | 219 | return true; |
... | ... | @@ -228,7 +231,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
228 | 231 | metadata: prepareRuleChainMetaData(ruleChainMetaData) |
229 | 232 | }; |
230 | 233 | var name = ruleChain.name; |
231 | - name = name.toLowerCase().replace(/\W/g,"_"); | |
234 | + name = name.toLowerCase().replace(/\W/g, "_"); | |
232 | 235 | exportToPc(ruleChainExport, name + '.json'); |
233 | 236 | }, |
234 | 237 | (rejection) => { |
... | ... | @@ -253,7 +256,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
253 | 256 | |
254 | 257 | function prepareRuleChainMetaData(ruleChainMetaData) { |
255 | 258 | delete ruleChainMetaData.ruleChainId; |
256 | - for (var i=0;i<ruleChainMetaData.nodes.length;i++) { | |
259 | + for (var i = 0; i < ruleChainMetaData.nodes.length; i++) { | |
257 | 260 | var node = ruleChainMetaData.nodes[i]; |
258 | 261 | delete node.ruleChainId; |
259 | 262 | ruleChainMetaData.nodes[i] = prepareExport(node); |
... | ... | @@ -305,7 +308,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
305 | 308 | function exportWidget(dashboard, sourceState, sourceLayout, widget) { |
306 | 309 | var widgetItem = itembuffer.prepareWidgetItem(dashboard, sourceState, sourceLayout, widget); |
307 | 310 | var name = widgetItem.widget.config.title; |
308 | - name = name.toLowerCase().replace(/\W/g,"_"); | |
311 | + name = name.toLowerCase().replace(/\W/g, "_"); | |
309 | 312 | exportToPc(prepareExport(widgetItem), name + '.json'); |
310 | 313 | } |
311 | 314 | |
... | ... | @@ -415,10 +418,10 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
415 | 418 | var aliasIds = Object.keys(entityAliases); |
416 | 419 | if (aliasIds.length > 0) { |
417 | 420 | processEntityAliases(entityAliases, aliasIds).then( |
418 | - function(missingEntityAliases) { | |
421 | + function (missingEntityAliases) { | |
419 | 422 | if (Object.keys(missingEntityAliases).length > 0) { |
420 | - editMissingAliases($event, [ widget ], | |
421 | - true, 'dashboard.widget-import-missing-aliases-title', missingEntityAliases).then( | |
423 | + editMissingAliases($event, [widget], | |
424 | + true, 'dashboard.widget-import-missing-aliases-title', missingEntityAliases).then( | |
422 | 425 | function success(updatedEntityAliases) { |
423 | 426 | for (var aliasId in updatedEntityAliases) { |
424 | 427 | var entityAlias = updatedEntityAliases[aliasId]; |
... | ... | @@ -483,14 +486,14 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
483 | 486 | function success(targetLayout) { |
484 | 487 | itembuffer.addWidgetToDashboard(dashboard, targetState, targetLayout, widget, |
485 | 488 | aliasesInfo, onAliasesUpdateFunction, originalColumns, originalSize, -1, -1).then( |
486 | - function() { | |
487 | - deferred.resolve( | |
488 | - { | |
489 | - widget: widget, | |
490 | - layoutId: targetLayout | |
491 | - } | |
492 | - ); | |
493 | - } | |
489 | + function () { | |
490 | + deferred.resolve( | |
491 | + { | |
492 | + widget: widget, | |
493 | + layoutId: targetLayout | |
494 | + } | |
495 | + ); | |
496 | + } | |
494 | 497 | ); |
495 | 498 | }, |
496 | 499 | function fail() { |
... | ... | @@ -505,7 +508,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
505 | 508 | dashboardService.getDashboard(dashboardId).then( |
506 | 509 | function success(dashboard) { |
507 | 510 | var name = dashboard.title; |
508 | - name = name.toLowerCase().replace(/\W/g,"_"); | |
511 | + name = name.toLowerCase().replace(/\W/g, "_"); | |
509 | 512 | exportToPc(prepareDashboardExport(dashboard), name + '.json'); |
510 | 513 | }, |
511 | 514 | function fail(rejection) { |
... | ... | @@ -538,13 +541,13 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
538 | 541 | dashboard = dashboardUtils.validateAndUpdateDashboard(dashboard); |
539 | 542 | var entityAliases = dashboard.configuration.entityAliases; |
540 | 543 | if (entityAliases) { |
541 | - var aliasIds = Object.keys( entityAliases ); | |
544 | + var aliasIds = Object.keys(entityAliases); | |
542 | 545 | if (aliasIds.length > 0) { |
543 | 546 | processEntityAliases(entityAliases, aliasIds).then( |
544 | - function(missingEntityAliases) { | |
545 | - if (Object.keys( missingEntityAliases ).length > 0) { | |
547 | + function (missingEntityAliases) { | |
548 | + if (Object.keys(missingEntityAliases).length > 0) { | |
546 | 549 | editMissingAliases($event, dashboard.configuration.widgets, |
547 | - false, 'dashboard.dashboard-import-missing-aliases-title', missingEntityAliases).then( | |
550 | + false, 'dashboard.dashboard-import-missing-aliases-title', missingEntityAliases).then( | |
548 | 551 | function success(updatedEntityAliases) { |
549 | 552 | for (var aliasId in updatedEntityAliases) { |
550 | 553 | entityAliases[aliasId] = updatedEntityAliases[aliasId]; |
... | ... | @@ -575,6 +578,34 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
575 | 578 | return deferred.promise; |
576 | 579 | } |
577 | 580 | |
581 | + function importEntities($event, entityType) { | |
582 | + var deferred = $q.defer(); | |
583 | + | |
584 | + switch (entityType) { | |
585 | + case types.entityType.device: | |
586 | + openImportDialogCSV($event, entityType, 'device.import', 'device.device-file').then( | |
587 | + function success() { | |
588 | + deferred.resolve(); | |
589 | + }, | |
590 | + function fail() { | |
591 | + deferred.reject(); | |
592 | + } | |
593 | + ); | |
594 | + return deferred.promise; | |
595 | + case types.entityType.asset: | |
596 | + openImportDialogCSV($event, entityType, 'asset.import', 'asset.asset-file').then( | |
597 | + function success() { | |
598 | + deferred.resolve(); | |
599 | + }, | |
600 | + function fail() { | |
601 | + deferred.reject(); | |
602 | + } | |
603 | + ); | |
604 | + return deferred.promise; | |
605 | + } | |
606 | + | |
607 | + } | |
608 | + | |
578 | 609 | function saveImportedDashboard(dashboard, deferred) { |
579 | 610 | dashboardService.saveDashboard(dashboard).then( |
580 | 611 | function success() { |
... | ... | @@ -594,14 +625,13 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
594 | 625 | } |
595 | 626 | |
596 | 627 | |
597 | - | |
598 | 628 | function exportExtension(extensionId) { |
599 | 629 | |
600 | 630 | getExtension(extensionId) |
601 | 631 | .then( |
602 | 632 | function success(extension) { |
603 | 633 | var name = extension.title; |
604 | - name = name.toLowerCase().replace(/\W/g,"_"); | |
634 | + name = name.toLowerCase().replace(/\W/g, "_"); | |
605 | 635 | exportToPc(prepareExport(extension), name + '.json'); |
606 | 636 | }, |
607 | 637 | function fail(rejection) { |
... | ... | @@ -661,7 +691,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
661 | 691 | function validateImportedExtension(configuration) { |
662 | 692 | if (configuration.length) { |
663 | 693 | for (let i = 0; i < configuration.length; i++) { |
664 | - if (angular.isUndefined(configuration[i].configuration) || angular.isUndefined(configuration[i].id )|| angular.isUndefined(configuration[i].type)) { | |
694 | + if (angular.isUndefined(configuration[i].configuration) || angular.isUndefined(configuration[i].id) || angular.isUndefined(configuration[i].type)) { | |
665 | 695 | return false; |
666 | 696 | } |
667 | 697 | } |
... | ... | @@ -692,7 +722,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
692 | 722 | var aliasId = aliasIds[index]; |
693 | 723 | var entityAlias = entityAliases[aliasId]; |
694 | 724 | entityService.checkEntityAlias(entityAlias).then( |
695 | - function(result) { | |
725 | + function (result) { | |
696 | 726 | if (result) { |
697 | 727 | checkNextEntityAliasOrComplete(index, aliasIds, entityAliases, missingEntityAliases, deferred); |
698 | 728 | } else { |
... | ... | @@ -732,6 +762,124 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
732 | 762 | return deferred.promise; |
733 | 763 | } |
734 | 764 | |
765 | + function splitCSV(str, sep) { | |
766 | + for (var foo = str.split(sep = sep || ","), x = foo.length - 1, tl; x >= 0; x--) { | |
767 | + if (foo[x].replace(/"\s+$/, '"').charAt(foo[x].length - 1) == '"') { | |
768 | + if ((tl = foo[x].replace(/^\s+"/, '"')).length > 1 && tl.charAt(0) == '"') { | |
769 | + foo[x] = foo[x].replace(/^\s*"|"\s*$/g, '').replace(/""/g, '"'); | |
770 | + } else if (x) { | |
771 | + foo.splice(x - 1, 2, [foo[x - 1], foo[x]].join(sep)); | |
772 | + } else foo = foo.shift().split(sep).concat(foo); | |
773 | + } else foo[x].replace(/""/g, '"'); | |
774 | + } | |
775 | + return foo; | |
776 | + } | |
777 | + | |
778 | + function isNumeric(str) { | |
779 | + str = str.replace(',', '.'); | |
780 | + return !isNaN(parseFloat(str)) && isFinite(str); | |
781 | + } | |
782 | + | |
783 | + function convertStringToJSType(str) { | |
784 | + if (isNumeric(str.replace(',', '.'))) { | |
785 | + return parseFloat(str.replace(',', '.')); | |
786 | + } | |
787 | + if (str.search(/^(true|false)$/im) === 0) { | |
788 | + return str === 'true'; | |
789 | + } | |
790 | + if (str === "") { | |
791 | + return null; | |
792 | + } | |
793 | + return str; | |
794 | + } | |
795 | + | |
796 | + function convertCSVToJson(csvdata, config) { | |
797 | + config = config || {}; | |
798 | + const delim = config.delim || ","; | |
799 | + const header = config.header || false; | |
800 | + let result = {}; | |
801 | + | |
802 | + let csvlines = csvdata.split(/[\r\n]+/); | |
803 | + let csvheaders = splitCSV(csvlines[0], delim); | |
804 | + if (csvheaders.length < 2) { | |
805 | + toast.showError($translate.instant('import.import-csv-number-columns-error')); | |
806 | + return -1; | |
807 | + } | |
808 | + let csvrows = header ? csvlines.slice(1, csvlines.length) : csvlines; | |
809 | + | |
810 | + result.headers = csvheaders; | |
811 | + result.rows = []; | |
812 | + | |
813 | + for (let r in csvrows) { | |
814 | + if (csvrows.hasOwnProperty(r)) { | |
815 | + let row = csvrows[r]; | |
816 | + | |
817 | + if (row.length === 0) | |
818 | + break; | |
819 | + | |
820 | + let rowitems = splitCSV(row, delim); | |
821 | + if (rowitems.length !== result.headers.length) { | |
822 | + toast.showError($translate.instant('import.import-csv-invalid-format-error', {line: (header ? result.rows.length + 2: result.rows.length + 1)})); | |
823 | + return -1; | |
824 | + } | |
825 | + for (let i = 0; i < rowitems.length; i++) { | |
826 | + rowitems[i] = convertStringToJSType(rowitems[i]); | |
827 | + } | |
828 | + result.rows.push(rowitems); | |
829 | + } | |
830 | + } | |
831 | + return result; | |
832 | + } | |
833 | + | |
834 | + function sumObject(obj1, obj2){ | |
835 | + Object.keys(obj2).map(function(key) { | |
836 | + if (angular.isObject(obj2[key])) { | |
837 | + obj1[key] = obj1[key] || {}; | |
838 | + angular.merge(obj1[key], sumObject(obj1[key], obj2[key])); | |
839 | + } else { | |
840 | + obj1[key] = (obj1[key] || 0) + obj2[key]; | |
841 | + } | |
842 | + }); | |
843 | + return obj1; | |
844 | + } | |
845 | + | |
846 | + function qAllWithProgress(promises) { | |
847 | + promises.forEach(function(p) { | |
848 | + p.then(function() { | |
849 | + $rootScope.$broadcast('createImportEntityCompleted', {}); | |
850 | + }); | |
851 | + }); | |
852 | + return $q.all(promises); | |
853 | + } | |
854 | + | |
855 | + function createMultiEntity(arrayData, entityType, updateData, config) { | |
856 | + let partSize = 100; | |
857 | + partSize = arrayData.length > partSize ? partSize : arrayData.length; | |
858 | + let allPromise = []; | |
859 | + let statisticalInfo = {}; | |
860 | + let deferred = $q.defer(); | |
861 | + | |
862 | + for(let i = 0; i < partSize; i++){ | |
863 | + const promise = entityService.saveEntityParameters(entityType, arrayData[i], updateData, config); | |
864 | + allPromise.push(promise); | |
865 | + } | |
866 | + | |
867 | + qAllWithProgress(allPromise).then(function success(response) { | |
868 | + for (let i = 0; i < response.length; i++){ | |
869 | + statisticalInfo = sumObject(statisticalInfo, response[i]); | |
870 | + } | |
871 | + arrayData.splice(0, partSize); | |
872 | + if(arrayData.length > 0){ | |
873 | + deferred.resolve(createMultiEntity(arrayData, entityType, updateData, config).then(function (response) { | |
874 | + return sumObject(statisticalInfo, response); | |
875 | + })); | |
876 | + } else { | |
877 | + deferred.resolve(statisticalInfo); | |
878 | + } | |
879 | + }); | |
880 | + return deferred.promise; | |
881 | + } | |
882 | + | |
735 | 883 | // Common functions |
736 | 884 | |
737 | 885 | function prepareExport(data) { |
... | ... | @@ -771,8 +919,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
771 | 919 | |
772 | 920 | if (window.navigator && window.navigator.msSaveOrOpenBlob) { |
773 | 921 | window.navigator.msSaveOrOpenBlob(blob, filename); |
774 | - } | |
775 | - else{ | |
922 | + } else { | |
776 | 923 | var e = document.createEvent('MouseEvents'), |
777 | 924 | a = document.createElement('a'); |
778 | 925 | |
... | ... | @@ -807,6 +954,34 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
807 | 954 | return deferred.promise; |
808 | 955 | } |
809 | 956 | |
957 | + function openImportDialogCSV($event, entityType, importTitle, importFileLabel) { | |
958 | + var deferred = $q.defer(); | |
959 | + $mdDialog.show({ | |
960 | + controller: 'ImportDialogCSVController', | |
961 | + controllerAs: 'vm', | |
962 | + templateUrl: importDialogCSVTemplate, | |
963 | + locals: { | |
964 | + importTitle: importTitle, | |
965 | + importFileLabel: importFileLabel, | |
966 | + entityType: entityType | |
967 | + }, | |
968 | + parent: angular.element($document[0].body), | |
969 | + onComplete: fixedDialogSize, | |
970 | + multiple: true, | |
971 | + fullscreen: true, | |
972 | + targetEvent: $event | |
973 | + }).then(function () { | |
974 | + deferred.resolve(); | |
975 | + }, function () { | |
976 | + deferred.reject(); | |
977 | + }); | |
978 | + return deferred.promise; | |
979 | + } | |
980 | + | |
981 | + function fixedDialogSize(scope, element) { | |
982 | + let dialogElement = element[0].getElementsByTagName('md-dialog'); | |
983 | + dialogElement[0].style.width = dialogElement[0].offsetWidth + 2 + "px"; | |
984 | + } | |
810 | 985 | } |
811 | 986 | |
812 | 987 | /* eslint-enable no-undef, angular/window-service, angular/document-service */ | ... | ... |
... | ... | @@ -15,9 +15,13 @@ |
15 | 15 | */ |
16 | 16 | import ImportExport from './import-export.service'; |
17 | 17 | import ImportDialogController from './import-dialog.controller'; |
18 | +import ImportDialogCSVController from './import-dialog-csv.controller'; | |
19 | +import TableColumnsAssignment from './table-columns-assignment.directive'; | |
18 | 20 | |
19 | 21 | |
20 | 22 | export default angular.module('thingsboard.importexport', []) |
21 | 23 | .factory('importExport', ImportExport) |
22 | 24 | .controller('ImportDialogController', ImportDialogController) |
25 | + .controller('ImportDialogCSVController', ImportDialogCSVController) | |
26 | + .directive('tbTableColumnsAssignment', TableColumnsAssignment) | |
23 | 27 | .name; | ... | ... |
1 | +/* | |
2 | + * Copyright © 2016-2019 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 | + | |
17 | +/* eslint-disable import/no-unresolved, import/default */ | |
18 | + | |
19 | +import tableColumnsAssignment from './table-columns-assignment.tpl.html'; | |
20 | + | |
21 | +/* eslint-enable import/no-unresolved, import/default */ | |
22 | + | |
23 | +/*@ngInject*/ | |
24 | +export default function TableColumnsAssignment() { | |
25 | + return { | |
26 | + restrict: "E", | |
27 | + scope: true, | |
28 | + bindToController: { | |
29 | + theForm: '=?', | |
30 | + columns: '=', | |
31 | + entityType: '=', | |
32 | + }, | |
33 | + templateUrl: tableColumnsAssignment, | |
34 | + controller: TableColumnsAssignmentController, | |
35 | + controllerAs: 'vm' | |
36 | + }; | |
37 | +} | |
38 | + | |
39 | +/*@ngInject*/ | |
40 | +function TableColumnsAssignmentController($scope, types, $timeout) { | |
41 | + var vm = this; | |
42 | + | |
43 | + vm.columnTypes = {}; | |
44 | + | |
45 | + vm.columnTypes.name = types.importEntityColumnType.name; | |
46 | + vm.columnTypes.type = types.importEntityColumnType.type; | |
47 | + | |
48 | + switch (vm.entityType) { | |
49 | + case types.entityType.device: | |
50 | + vm.columnTypes.sharedAttribute = types.importEntityColumnType.sharedAttribute; | |
51 | + vm.columnTypes.serverAttribute = types.importEntityColumnType.serverAttribute; | |
52 | + vm.columnTypes.timeseries = types.importEntityColumnType.timeseries; | |
53 | + vm.columnTypes.accessToken = types.importEntityColumnType.accessToken; | |
54 | + break; | |
55 | + case types.entityType.asset: | |
56 | + vm.columnTypes.serverAttribute = types.importEntityColumnType.serverAttribute; | |
57 | + vm.columnTypes.timeseries = types.importEntityColumnType.timeseries; | |
58 | + break; | |
59 | + } | |
60 | + | |
61 | + $scope.$watch('vm.columns', function(newVal){ | |
62 | + if (newVal) { | |
63 | + var isSelectName = false; | |
64 | + var isSelectType = false; | |
65 | + var isSelectCredentials = false; | |
66 | + for (var i = 0; i < newVal.length; i++) { | |
67 | + switch (newVal[i].type) { | |
68 | + case types.importEntityColumnType.name.value: | |
69 | + isSelectName = true; | |
70 | + break; | |
71 | + case types.importEntityColumnType.type.value: | |
72 | + isSelectType = true; | |
73 | + break; | |
74 | + case types.importEntityColumnType.accessToken.value: | |
75 | + isSelectCredentials = true; | |
76 | + break; | |
77 | + } | |
78 | + } | |
79 | + if(isSelectName && isSelectType) { | |
80 | + vm.theForm.$setDirty(); | |
81 | + } else { | |
82 | + vm.theForm.$setPristine(); | |
83 | + } | |
84 | + $timeout(function () { | |
85 | + vm.columnTypes.name.disable = isSelectName; | |
86 | + vm.columnTypes.type.disable = isSelectType; | |
87 | + if (angular.isDefined(vm.columnTypes.accessToken)) { | |
88 | + vm.columnTypes.accessToken.disable = isSelectCredentials; | |
89 | + } | |
90 | + }); | |
91 | + } | |
92 | + }, true); | |
93 | +} | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2019 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<md-table-container class="tb-table-select"> | |
19 | + <table md-table> | |
20 | + <thead md-head> | |
21 | + <tr md-row> | |
22 | + <th md-column> </th> | |
23 | + <th md-column>{{ 'import.column-example' | translate }}</th> | |
24 | + <th md-column>{{ 'import.column-type.column-type' | translate }}</th> | |
25 | + <th md-column>{{ 'import.column-key' | translate }}</th> | |
26 | + </tr> | |
27 | + </thead> | |
28 | + <tbody md-body> | |
29 | + <tr md-row ng-repeat="column in vm.columns track by $index"> | |
30 | + <td md-cell>{{$index + 1}}</td> | |
31 | + <td md-cell>{{column.sampleData}}</td> | |
32 | + <td md-cell> | |
33 | + <md-select ng-model="column.type" required name="columnType" | |
34 | + aria-label="{{ 'import.column-type.column-type' | translate }}"> | |
35 | + <md-option ng-repeat="type in vm.columnTypes" ng-value="type.value" ng-disabled="type.disable"> | |
36 | + {{type.name | translate}} | |
37 | + </md-option> | |
38 | + </md-select> | |
39 | + </td> | |
40 | + <td md-cell> | |
41 | + <md-input-container md-no-float | |
42 | + ng-if="column.type != vm.columnTypes.name.value && | |
43 | + column.type != vm.columnTypes.type.value && | |
44 | + column.type != vm.columnTypes.accessToken.value"> | |
45 | + <input required name="columnKeyName" | |
46 | + placeholder="{{ 'import.column-value' | translate }}" | |
47 | + ng-model="column.key" | |
48 | + aria-label="{{ 'import.column-value' | translate }}"> | |
49 | + </md-input-container> | |
50 | + </td> | |
51 | + </tr> | |
52 | + </tbody> | |
53 | + </table> | |
54 | + <md-divider></md-divider> | |
55 | +</md-table-container> | ... | ... |
... | ... | @@ -48,7 +48,8 @@ |
48 | 48 | "paste-reference": "Paste reference", |
49 | 49 | "import": "Import", |
50 | 50 | "export": "Export", |
51 | - "share-via": "Share via {{provider}}" | |
51 | + "share-via": "Share via {{provider}}", | |
52 | + "continue": "Continue" | |
52 | 53 | }, |
53 | 54 | "aggregation": { |
54 | 55 | "aggregation": "Aggregation", |
... | ... | @@ -246,7 +247,9 @@ |
246 | 247 | "select-asset": "Select asset", |
247 | 248 | "no-assets-matching": "No assets matching '{{entity}}' were found.", |
248 | 249 | "asset-required": "Asset is required", |
249 | - "name-starts-with": "Asset name starts with" | |
250 | + "name-starts-with": "Asset name starts with", | |
251 | + "import": "Import assets", | |
252 | + "asset-file": "Asset file" | |
250 | 253 | }, |
251 | 254 | "attribute": { |
252 | 255 | "attributes": "Attributes", |
... | ... | @@ -665,7 +668,9 @@ |
665 | 668 | "is-gateway": "Is gateway", |
666 | 669 | "public": "Public", |
667 | 670 | "device-public": "Device is public", |
668 | - "select-device": "Select device" | |
671 | + "select-device": "Select device", | |
672 | + "import": "Import device", | |
673 | + "device-file": "Device file" | |
669 | 674 | }, |
670 | 675 | "dialog": { |
671 | 676 | "close": "Close dialog" |
... | ... | @@ -1093,7 +1098,40 @@ |
1093 | 1098 | }, |
1094 | 1099 | "import": { |
1095 | 1100 | "no-file": "No file selected", |
1096 | - "drop-file": "Drop a JSON file or click to select a file to upload." | |
1101 | + "drop-file": "Drop a JSON file or click to select a file to upload.", | |
1102 | + "drop-file-csv": "Drop a CSV file or click to select a file to upload.", | |
1103 | + "column-value": "Value", | |
1104 | + "column-title": "Title", | |
1105 | + "column-example": "Example value data", | |
1106 | + "column-key": "Attribute/telemetry key", | |
1107 | + "csv-delimiter": "CSV delimiter", | |
1108 | + "csv-first-line-header": "First line contains column names", | |
1109 | + "csv-update-data": "Update attributes/telemetry", | |
1110 | + "import-csv-number-columns-error": "A file should contain at least two columns", | |
1111 | + "import-csv-invalid-format-error": "Invalid file format. Line: '{{line}}'", | |
1112 | + "column-type": { | |
1113 | + "name": "Name", | |
1114 | + "type": "Type", | |
1115 | + "column-type": "Column type", | |
1116 | + "client-attribute": "Client attribute", | |
1117 | + "shared-attribute": "Shared attribute", | |
1118 | + "server-attribute": "Server attribute", | |
1119 | + "timeseries": "Timeseries", | |
1120 | + "entity-field": "Entity field", | |
1121 | + "access-token": "Access token" | |
1122 | + }, | |
1123 | + "stepper-text":{ | |
1124 | + "select-file": "Select a file", | |
1125 | + "configuration": "Import configuration", | |
1126 | + "column-type": "Select columns type", | |
1127 | + "creat-entities": "Creating new entities", | |
1128 | + "done": "Done" | |
1129 | + }, | |
1130 | + "message": { | |
1131 | + "create-entities": "{{count}} new entities were successfully created.", | |
1132 | + "update-entities": "{{count}} entities were successfully updated.", | |
1133 | + "error-entities": "There was an error creating {{count}} entities." | |
1134 | + } | |
1097 | 1135 | }, |
1098 | 1136 | "item": { |
1099 | 1137 | "selected": "Selected" | ... | ... |