Showing
8 changed files
with
533 additions
and
60 deletions
... | ... | @@ -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,6 +263,55 @@ function AttributeService($http, $q, $filter, types, telemetryWebsocketService) |
260 | 263 | return deferred.promise; |
261 | 264 | } |
262 | 265 | |
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); | |
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 | + | |
263 | 315 | function deleteEntityAttributes(entityType, entityId, attributeScope, attributes) { |
264 | 316 | var deferred = $q.defer(); |
265 | 317 | var keys = ''; |
... | ... | @@ -278,5 +330,22 @@ function AttributeService($http, $q, $filter, types, telemetryWebsocketService) |
278 | 330 | return deferred.promise; |
279 | 331 | } |
280 | 332 | |
333 | + function deleteEntityTimeseries(entityType, entityId, timeseries) { | |
334 | + var deferred = $q.defer(); | |
335 | + var keys = ''; | |
336 | + for (var i = 0; i < timeseries.length; i++) { | |
337 | + if (i > 0) { | |
338 | + keys += ','; | |
339 | + } | |
340 | + keys += timeseries[i].key; | |
341 | + } | |
342 | + var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/delete' + '?keys=' + keys; | |
343 | + $http.delete(url).then(function success() { | |
344 | + deferred.resolve(); | |
345 | + }, function fail() { | |
346 | + deferred.reject(); | |
347 | + }); | |
348 | + return deferred.promise; | |
349 | + } | |
281 | 350 | |
282 | 351 | } |
\ No newline at end of file | ... | ... |
... | ... | @@ -31,6 +31,7 @@ function DeviceService($http, $q, $window, userService, attributeService, custom |
31 | 31 | getDeviceCredentials: getDeviceCredentials, |
32 | 32 | getTenantDevices: getTenantDevices, |
33 | 33 | saveDevice: saveDevice, |
34 | + saveDeviceParameters: saveDeviceParameters, | |
34 | 35 | saveDeviceCredentials: saveDeviceCredentials, |
35 | 36 | unassignDeviceFromCustomer: unassignDeviceFromCustomer, |
36 | 37 | makeDevicePublic: makeDevicePublic, |
... | ... | @@ -42,7 +43,8 @@ function DeviceService($http, $q, $window, userService, attributeService, custom |
42 | 43 | sendOneWayRpcCommand: sendOneWayRpcCommand, |
43 | 44 | sendTwoWayRpcCommand: sendTwoWayRpcCommand, |
44 | 45 | findByQuery: findByQuery, |
45 | - getDeviceTypes: getDeviceTypes | |
46 | + getDeviceTypes: getDeviceTypes, | |
47 | + findByName: findByName | |
46 | 48 | } |
47 | 49 | |
48 | 50 | return service; |
... | ... | @@ -124,7 +126,7 @@ function DeviceService($http, $q, $window, userService, attributeService, custom |
124 | 126 | if (!config) { |
125 | 127 | config = {}; |
126 | 128 | } |
127 | - config = Object.assign(config, { ignoreErrors: ignoreErrors }); | |
129 | + config = Object.assign(config, {ignoreErrors: ignoreErrors}); | |
128 | 130 | $http.get(url, config).then(function success(response) { |
129 | 131 | deferred.resolve(response.data); |
130 | 132 | }, function fail(response) { |
... | ... | @@ -136,8 +138,8 @@ function DeviceService($http, $q, $window, userService, attributeService, custom |
136 | 138 | function getDevices(deviceIds, config) { |
137 | 139 | var deferred = $q.defer(); |
138 | 140 | var ids = ''; |
139 | - for (var i=0;i<deviceIds.length;i++) { | |
140 | - if (i>0) { | |
141 | + for (var i = 0; i < deviceIds.length; i++) { | |
142 | + if (i > 0) { | |
141 | 143 | ids += ','; |
142 | 144 | } |
143 | 145 | ids += deviceIds[i]; |
... | ... | @@ -146,11 +148,11 @@ function DeviceService($http, $q, $window, userService, attributeService, custom |
146 | 148 | $http.get(url, config).then(function success(response) { |
147 | 149 | var devices = response.data; |
148 | 150 | 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; | |
151 | + var id1 = device1.id.id; | |
152 | + var id2 = device2.id.id; | |
153 | + var index1 = deviceIds.indexOf(id1); | |
154 | + var index2 = deviceIds.indexOf(id2); | |
155 | + return index1 - index2; | |
154 | 156 | }); |
155 | 157 | deferred.resolve(devices); |
156 | 158 | }, function fail(response) { |
... | ... | @@ -159,10 +161,11 @@ function DeviceService($http, $q, $window, userService, attributeService, custom |
159 | 161 | return deferred.promise; |
160 | 162 | } |
161 | 163 | |
162 | - function saveDevice(device) { | |
164 | + function saveDevice(device, config) { | |
165 | + config = config || {}; | |
163 | 166 | var deferred = $q.defer(); |
164 | 167 | var url = '/api/device'; |
165 | - $http.post(url, device).then(function success(response) { | |
168 | + $http.post(url, device, config).then(function success(response) { | |
166 | 169 | deferred.resolve(response.data); |
167 | 170 | }, function fail() { |
168 | 171 | deferred.reject(); |
... | ... | @@ -170,6 +173,54 @@ function DeviceService($http, $q, $window, userService, attributeService, custom |
170 | 173 | return deferred.promise; |
171 | 174 | } |
172 | 175 | |
176 | + function saveDeviceRelarion(deviceId, deviceRelation, config) { | |
177 | + var deferred = $q.defer(); | |
178 | + var attributesType = Object.keys(types.attributesScope); | |
179 | + var allPromise = []; | |
180 | + var promise = ""; | |
181 | + for (var i = 0; i < attributesType.length; i++) { | |
182 | + if (deviceRelation.attributes[attributesType[i]] && deviceRelation.attributes[attributesType[i]].length !== 0) { | |
183 | + promise = attributeService.saveEntityAttributes(types.entityType.device, deviceId, types.attributesScope[attributesType[i]].value, deviceRelation.attributes[attributesType[i]], config).then(function success() {}); | |
184 | + allPromise.push(promise); | |
185 | + } | |
186 | + } | |
187 | + if (deviceRelation.timeseries.length !== 0) { | |
188 | + promise = attributeService.saveEntityTimeseries(types.entityType.device, deviceId, "time", deviceRelation.timeseries, config).then(function success() { | |
189 | + }); | |
190 | + allPromise.push(promise); | |
191 | + } | |
192 | + $q.all(allPromise).then(function success() { | |
193 | + deferred.resolve(); | |
194 | + }); | |
195 | + return deferred.promise; | |
196 | + } | |
197 | + | |
198 | + function saveDeviceParameters(deviceParameters, update, config) { | |
199 | + config = config || {}; | |
200 | + var deferred = $q.defer(); | |
201 | + var newDevice = { | |
202 | + name: deviceParameters.name, | |
203 | + type: deviceParameters.type | |
204 | + }; | |
205 | + saveDevice(newDevice, config).then(function success(response) { | |
206 | + saveDeviceRelarion(response.id.id, deviceParameters, config).then(function success() { | |
207 | + deferred.resolve(); | |
208 | + }); | |
209 | + }, function fail() { | |
210 | + if (update) { | |
211 | + findByName(deviceParameters.name, config).then(function success(response) { | |
212 | + saveDeviceRelarion(response.id.id, deviceParameters, config).then(function success() { | |
213 | + deferred.resolve(); | |
214 | + }); | |
215 | + }); | |
216 | + } else { | |
217 | + deferred.resolve(); | |
218 | + } | |
219 | + console.log("error"); // eslint-disable-line | |
220 | + }); | |
221 | + return deferred.promise; | |
222 | + } | |
223 | + | |
173 | 224 | function deleteDevice(deviceId) { |
174 | 225 | var deferred = $q.defer(); |
175 | 226 | var url = '/api/device/' + deviceId; |
... | ... | @@ -297,7 +348,7 @@ function DeviceService($http, $q, $window, userService, attributeService, custom |
297 | 348 | if (!config) { |
298 | 349 | config = {}; |
299 | 350 | } |
300 | - config = Object.assign(config, { ignoreErrors: ignoreErrors }); | |
351 | + config = Object.assign(config, {ignoreErrors: ignoreErrors}); | |
301 | 352 | $http.post(url, query, config).then(function success(response) { |
302 | 353 | deferred.resolve(response.data); |
303 | 354 | }, function fail() { |
... | ... | @@ -317,4 +368,15 @@ function DeviceService($http, $q, $window, userService, attributeService, custom |
317 | 368 | return deferred.promise; |
318 | 369 | } |
319 | 370 | |
371 | + function findByName(deviceName, config) { | |
372 | + config = config || {}; | |
373 | + var deferred = $q.defer(); | |
374 | + var url = '/api/tenant/devices?deviceName=' + deviceName; | |
375 | + $http.get(url, config).then(function success(response) { | |
376 | + deferred.resolve(response.data); | |
377 | + }, function fail() { | |
378 | + deferred.reject(); | |
379 | + }); | |
380 | + return deferred.promise; | |
381 | + } | |
320 | 382 | } | ... | ... |
... | ... | @@ -350,6 +350,40 @@ export default angular.module('thingsboard.types', []) |
350 | 350 | rulenode: "RULE_NODE", |
351 | 351 | entityView: "ENTITY_VIEW" |
352 | 352 | }, |
353 | + entityGroup: { | |
354 | + columnType: { | |
355 | + sharedAttribute: { | |
356 | + name: 'entity-group.column-type.shared-attribute', | |
357 | + value: 'SHARED_ATTRIBUTE' | |
358 | + }, | |
359 | + serverAttribute: { | |
360 | + name: 'entity-group.column-type.server-attribute', | |
361 | + value: 'SERVER_ATTRIBUTE' | |
362 | + }, | |
363 | + timeseries: { | |
364 | + name: 'entity-group.column-type.timeseries', | |
365 | + value: 'TIMESERIES' | |
366 | + }, | |
367 | + entityField: { | |
368 | + name: 'entity-group.column-type.entity-field', | |
369 | + value: 'ENTITY_FIELD' | |
370 | + } | |
371 | + }, | |
372 | + entityField: { | |
373 | + name: { | |
374 | + name: 'entity-group.entity-field.name', | |
375 | + value: 'name' | |
376 | + }, | |
377 | + type: { | |
378 | + name: 'entity-group.entity-field.type', | |
379 | + value: 'type' | |
380 | + }, | |
381 | + assigned_customer: { | |
382 | + name: 'entity-group.entity-field.assigned_customer', | |
383 | + value: 'assigned_customer' | |
384 | + } | |
385 | + } | |
386 | + }, | |
353 | 387 | aliasEntityType: { |
354 | 388 | current_customer: "CURRENT_CUSTOMER" |
355 | 389 | }, | ... | ... |
... | ... | @@ -16,7 +16,7 @@ |
16 | 16 | import './import-dialog.scss'; |
17 | 17 | |
18 | 18 | /*@ngInject*/ |
19 | -export default function ImportDialogCsvController($scope, $mdDialog, toast, importTitle, importFileLabel) { | |
19 | +export default function ImportDialogCsvController($scope, $mdDialog, toast, importTitle, importFileLabel, importExport, types, $timeout) { | |
20 | 20 | |
21 | 21 | var vm = this; |
22 | 22 | |
... | ... | @@ -25,9 +25,58 @@ export default function ImportDialogCsvController($scope, $mdDialog, toast, impo |
25 | 25 | vm.fileAdded = fileAdded; |
26 | 26 | vm.clearFile = clearFile; |
27 | 27 | |
28 | + vm.addDevices = addDevices; | |
29 | + vm.importParams = { | |
30 | + isUpdate: true | |
31 | + }; | |
32 | + | |
28 | 33 | vm.importTitle = importTitle; |
29 | 34 | vm.importFileLabel = importFileLabel; |
30 | 35 | |
36 | + vm.columnsParam = []; | |
37 | + | |
38 | + vm.entityType = types.entityType.device; | |
39 | + vm.columnTypes = {}; | |
40 | + vm.entityField = {}; | |
41 | + | |
42 | + var parseData = {}; | |
43 | + | |
44 | + switch (vm.entityType) { | |
45 | + case types.entityType.device: | |
46 | + vm.columnTypes = types.entityGroup.columnType; | |
47 | + break; | |
48 | + } | |
49 | + | |
50 | + vm.entityField.name = types.entityGroup.entityField.name; | |
51 | + | |
52 | + switch (vm.entityType) { | |
53 | + case types.entityType.device: | |
54 | + vm.entityField.type = types.entityGroup.entityField.type; | |
55 | + // vm.entityField.assigned_customer = types.entityGroup.entityField.assigned_customer; | |
56 | + break; | |
57 | + } | |
58 | + | |
59 | + $scope.$watch('vm.columnsParam', function(newVal, prevVal){ | |
60 | + if (newVal && !angular.equals(newVal, prevVal)) { | |
61 | + var isSelectName = false; | |
62 | + var isSelectType = false; | |
63 | + for (var i = 0; i < newVal.length; i++) { | |
64 | + if (newVal[i].type === types.entityGroup.columnType.entityField.value && | |
65 | + newVal[i].key === types.entityGroup.entityField.name.value) { | |
66 | + isSelectName = true; | |
67 | + } | |
68 | + if (newVal[i].type === types.entityGroup.columnType.entityField.value && | |
69 | + newVal[i].key === types.entityGroup.entityField.type.value) { | |
70 | + isSelectType = true; | |
71 | + } | |
72 | + } | |
73 | + $timeout(function () { | |
74 | + vm.entityField.name.disable = isSelectName; | |
75 | + vm.entityField.type.disable = isSelectType; | |
76 | + }); | |
77 | + } | |
78 | + }, true); | |
79 | + | |
31 | 80 | |
32 | 81 | function cancel() { |
33 | 82 | $mdDialog.cancel(); |
... | ... | @@ -36,16 +85,16 @@ export default function ImportDialogCsvController($scope, $mdDialog, toast, impo |
36 | 85 | function fileAdded($file) { |
37 | 86 | if ($file.getExtension() === 'csv') { |
38 | 87 | var reader = new FileReader(); |
39 | - reader.onload = function(event) { | |
40 | - $scope.$apply(function() { | |
88 | + reader.onload = function (event) { | |
89 | + $scope.$apply(function () { | |
41 | 90 | if (event.target.result) { |
42 | 91 | $scope.theForm.$setDirty(); |
43 | 92 | var importCSV = event.target.result; |
44 | 93 | if (importCSV && importCSV.length > 0) { |
45 | 94 | try { |
46 | 95 | vm.importData = importCSV; |
47 | - console.log(vm.importData); // eslint-disable-line | |
48 | 96 | vm.fileName = $file.name; |
97 | + parseCSVData(vm.importData); | |
49 | 98 | } catch (err) { |
50 | 99 | vm.fileName = null; |
51 | 100 | toast.showError(err.message); |
... | ... | @@ -58,10 +107,89 @@ export default function ImportDialogCsvController($scope, $mdDialog, toast, impo |
58 | 107 | } |
59 | 108 | } |
60 | 109 | |
110 | + function parseCSVData(importData) { | |
111 | + var config = { | |
112 | + delim: vm.importParams.delim, | |
113 | + header: vm.importParams.isHeader | |
114 | + }; | |
115 | + parseData = importExport.convertCSVToJson(importData, config); | |
116 | + for (var i = 0; i < parseData.headers.length; i++) { | |
117 | + var columnParam = { | |
118 | + type: types.entityGroup.columnType.serverAttribute.value, | |
119 | + key: vm.importParams.isHeader ? parseData.headers[i] : "", | |
120 | + sampleData: parseData.rows[0][i] | |
121 | + }; | |
122 | + vm.columnsParam.push(columnParam); | |
123 | + } | |
124 | + } | |
125 | + | |
126 | + function addDevices () { | |
127 | + var arrayParam = [{type: "ENTITY_FIELD", key: "name", sampleData: "Device 1"}, {type: "ENTITY_FIELD", key: "type", sampleData: "test"}, {type: "SERVER_ATTRIBUTE", key: "test", sampleData: "test"}, {type: "TIMESERIES", key: "testBoolean", sampleData: false}, {type: "SHARED_ATTRIBUTE", key: "testNumber", sampleData: 123}]; // eslint-disable-line | |
128 | + var data = {headers: ["Device 1", "test", "test", "FALSE", "123"], | |
129 | + rows:[["Device 1", "test", "test", false, 123.5]]}; | |
130 | + // rows:[["Device 1", "test", "test", false, 123], | |
131 | + // ["Device 2", "test", "test", false, 124], | |
132 | + // ["Device 3", "test", "test", false, 125], | |
133 | + // ["Device 4", "test", "test", false, 126], | |
134 | + // ["Device 5", "test", "test", false, 127]]}; | |
135 | + var arrayData = []; | |
136 | + var config = { | |
137 | + ignoreErrors: true | |
138 | + }; | |
139 | + for (var i = 0; i < data.rows.length; i ++) { | |
140 | + var obj = { | |
141 | + name: "", | |
142 | + type: "", | |
143 | + attributes: { | |
144 | + server: [], | |
145 | + shared: [] | |
146 | + }, | |
147 | + timeseries: [] | |
148 | + }; | |
149 | + for(var j = 0; j < arrayParam.length; j++){ | |
150 | + switch (arrayParam[j].type) { | |
151 | + case types.entityGroup.columnType.serverAttribute.value: | |
152 | + obj.attributes.server.push({ | |
153 | + key: arrayParam[j].key, | |
154 | + value: data.rows[i][j] | |
155 | + }); | |
156 | + break; | |
157 | + case types.entityGroup.columnType.sharedAttribute.value: | |
158 | + obj.attributes.shared.push({ | |
159 | + key: arrayParam[j].key, | |
160 | + value: data.rows[i][j] | |
161 | + }); | |
162 | + break; | |
163 | + case types.entityGroup.columnType.timeseries.value: | |
164 | + obj.timeseries.push({ | |
165 | + key: arrayParam[j].key, | |
166 | + value: data.rows[i][j] | |
167 | + }); | |
168 | + break; | |
169 | + case types.entityGroup.columnType.entityField.value: | |
170 | + switch (arrayParam[j].key) { | |
171 | + case types.entityGroup.entityField.name.value: | |
172 | + obj.name = data.rows[i][j]; | |
173 | + break; | |
174 | + case types.entityGroup.entityField.type.value: | |
175 | + obj.type = data.rows[i][j]; | |
176 | + break; | |
177 | + } | |
178 | + break; | |
179 | + } | |
180 | + } | |
181 | + arrayData.push(obj); | |
182 | + } | |
183 | + importExport.createMultiEntity(arrayData, vm.entityType, vm.importParams.isUpdate, config).then(function () { | |
184 | + $mdDialog.hide(); | |
185 | + }); | |
186 | + } | |
187 | + | |
61 | 188 | function clearFile() { |
62 | 189 | $scope.theForm.$setDirty(); |
63 | 190 | vm.fileName = null; |
64 | 191 | vm.importData = null; |
192 | + vm.columnsParam = []; | |
65 | 193 | } |
66 | 194 | |
67 | 195 | function importFromJson() { | ... | ... |
... | ... | @@ -26,7 +26,8 @@ |
26 | 26 | </md-button> |
27 | 27 | </div> |
28 | 28 | </md-toolbar> |
29 | - <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!$root.loading" ng-show="$root.loading"></md-progress-linear> | |
29 | + <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!$root.loading" | |
30 | + ng-show="$root.loading"></md-progress-linear> | |
30 | 31 | <span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span> |
31 | 32 | <md-dialog-content> |
32 | 33 | <div class="md-dialog-content"> |
... | ... | @@ -38,7 +39,8 @@ |
38 | 39 | flow-file-added="vm.fileAdded( $file )" class="tb-file-select-container"> |
39 | 40 | <div class="tb-file-clear-container"> |
40 | 41 | <md-button ng-click="vm.clearFile()" |
41 | - class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ 'action.remove' | translate }}"> | |
42 | + class="tb-file-clear-btn md-icon-button md-primary" | |
43 | + aria-label="{{ 'action.remove' | translate }}"> | |
42 | 44 | <md-tooltip md-direction="top"> |
43 | 45 | {{ 'action.remove' | translate }} |
44 | 46 | </md-tooltip> |
... | ... | @@ -49,7 +51,8 @@ |
49 | 51 | </div> |
50 | 52 | <div class="alert tb-flow-drop" flow-drop> |
51 | 53 | <label for="select" translate>import.drop-file-csv</label> |
52 | - <input class="file-input" flow-btn flow-attrs="{accept:'.csv,application/csv,text/csv'}" id="select"> | |
54 | + <input class="file-input" flow-btn | |
55 | + flow-attrs="{accept:'.csv,application/csv,text/csv'}" id="select"> | |
53 | 56 | </div> |
54 | 57 | </div> |
55 | 58 | </div> |
... | ... | @@ -59,14 +62,81 @@ |
59 | 62 | </div> |
60 | 63 | </div> |
61 | 64 | </fieldset> |
65 | + <div flex layout="row"> | |
66 | + <md-input-container class="md-block"> | |
67 | + <label translate>CSV delimiter parametr</label> | |
68 | + <input ng-model="vm.importParams.delim"> | |
69 | + </md-input-container> | |
70 | + <md-input-container class="md-block"> | |
71 | + <md-checkbox ng-model="vm.importParams.isHeader" aria-label="Checkbox 1"> | |
72 | + Use first line is header | |
73 | + </md-checkbox> | |
74 | + </md-input-container> | |
75 | + <md-input-container class="md-block"> | |
76 | + <md-checkbox ng-model="vm.importParams.isUpdate" aria-label="Checkbox 1"> | |
77 | + Update parameter device | |
78 | + </md-checkbox> | |
79 | + </md-input-container> | |
80 | + </div> | |
81 | + <md-table-container flex class="tb-table-select"> | |
82 | + <table md-table> | |
83 | + <thead md-head> | |
84 | + <tr md-row> | |
85 | + <th md-column> </th> | |
86 | + <th md-column>Example value data</th> | |
87 | + <th md-column style="min-width: 140px">Column type</th> | |
88 | + <th md-column style="min-width: 140px">Value</th> | |
89 | + </tr> | |
90 | + </thead> | |
91 | + <tbody md-body> | |
92 | + <tr md-row ng-repeat="column in vm.columnsParam"> | |
93 | + <td md-cell>{{$index + 1}}</td> | |
94 | + <td md-cell>{{column.sampleData}}</td> | |
95 | + <td md-cell> | |
96 | + <md-select ng-model="column.type" required name="columnType" | |
97 | + aria-label="{{ 'entity-group.column-type' | translate }}"> | |
98 | + <md-option ng-repeat="type in vm.columnTypes" ng-value="type.value"> | |
99 | + {{type.name | translate}} | |
100 | + </md-option> | |
101 | + </md-select> | |
102 | + </td> | |
103 | + <td md-cell> | |
104 | + <md-select ng-if="column.type == vm.columnTypes.entityField.value" | |
105 | + required name="columnKey" ng-model="column.key" | |
106 | + aria-label="{{ 'entity-group.column-value' | translate }}"> | |
107 | + <md-option ng-repeat="field in vm.entityField" ng-value="field.value" ng-disabled="field.disable"> | |
108 | + {{field.name | translate}} | |
109 | + </md-option> | |
110 | + </md-select> | |
111 | + <md-input-container md-no-float | |
112 | + ng-if="column.type != vm.columnTypes.entityField.value && | |
113 | + column.type != vm.columnTypes.name.value && | |
114 | + column.type != vm.columnTypes.type.value"> | |
115 | + <input required name="columnKeyName" | |
116 | + placeholder="{{ 'entity-group.column-value' | translate }}" | |
117 | + ng-model="column.key" | |
118 | + aria-label="{{ 'entity-group.column-value' | translate }}"> | |
119 | + </md-input-container> | |
120 | + </td> | |
121 | + </tr> | |
122 | + </tbody> | |
123 | + </table> | |
124 | + <md-divider></md-divider> | |
125 | + </md-table-container> | |
62 | 126 | </div> |
63 | 127 | </md-dialog-content> |
64 | 128 | <md-dialog-actions layout="row"> |
65 | 129 | <span flex></span> |
66 | - <md-button ng-disabled="$root.loading || !theForm.$dirty || !theForm.$valid || !vm.importData" type="submit" class="md-raised md-primary"> | |
130 | + <md-button ng-disabled="$root.loading" ng-click="vm.addDevices()" style="margin-right:20px;"> | |
131 | + Add Device | |
132 | + </md-button> | |
133 | + <md-button ng-disabled="$root.loading || !theForm.$dirty || !theForm.$valid || !vm.importData" type="submit" | |
134 | + class="md-raised md-primary"> | |
67 | 135 | {{ 'action.import' | translate }} |
68 | 136 | </md-button> |
69 | - <md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button> | |
137 | + <md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | |
138 | + | translate }} | |
139 | + </md-button> | |
70 | 140 | </md-dialog-actions> |
71 | 141 | </form> |
72 | 142 | </md-dialog> | ... | ... |
... | ... | @@ -25,7 +25,7 @@ import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html'; |
25 | 25 | /* eslint-disable no-undef, angular/window-service, angular/document-service */ |
26 | 26 | |
27 | 27 | /*@ngInject*/ |
28 | -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, $timeout, deviceService, | |
29 | 29 | dashboardUtils, entityService, dashboardService, ruleChainService, widgetService, toast, attributeService) { |
30 | 30 | |
31 | 31 | |
... | ... | @@ -43,7 +43,9 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
43 | 43 | exportExtension: exportExtension, |
44 | 44 | importExtension: importExtension, |
45 | 45 | importDevices: importDevices, |
46 | - exportToPc: exportToPc | |
46 | + convertCSVToJson: convertCSVToJson, | |
47 | + exportToPc: exportToPc, | |
48 | + createMultiEntity: createMultiEntity | |
47 | 49 | }; |
48 | 50 | |
49 | 51 | return service; |
... | ... | @@ -56,11 +58,11 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
56 | 58 | var bundleAlias = widgetsBundle.alias; |
57 | 59 | var isSystem = widgetsBundle.tenantId.id === types.id.nullUid; |
58 | 60 | widgetService.getBundleWidgetTypes(bundleAlias, isSystem).then( |
59 | - function success (widgetTypes) { | |
61 | + function success(widgetTypes) { | |
60 | 62 | prepareExport(widgetsBundle); |
61 | 63 | var widgetsBundleItem = { |
62 | - widgetsBundle: prepareExport(widgetsBundle), | |
63 | - widgetTypes: [] | |
64 | + widgetsBundle: prepareExport(widgetsBundle), | |
65 | + widgetTypes: [] | |
64 | 66 | }; |
65 | 67 | for (var t in widgetTypes) { |
66 | 68 | var widgetType = widgetTypes[t]; |
... | ... | @@ -70,10 +72,10 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
70 | 72 | widgetsBundleItem.widgetTypes.push(prepareExport(widgetType)); |
71 | 73 | } |
72 | 74 | var name = widgetsBundle.title; |
73 | - name = name.toLowerCase().replace(/\W/g,"_"); | |
75 | + name = name.toLowerCase().replace(/\W/g, "_"); | |
74 | 76 | exportToPc(widgetsBundleItem, name + '.json'); |
75 | 77 | }, |
76 | - function fail (rejection) { | |
78 | + function fail(rejection) { | |
77 | 79 | var message = rejection; |
78 | 80 | if (!message) { |
79 | 81 | message = $translate.instant('error.unknown-error'); |
... | ... | @@ -170,7 +172,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
170 | 172 | delete widgetType.bundleAlias; |
171 | 173 | } |
172 | 174 | var name = widgetType.name; |
173 | - name = name.toLowerCase().replace(/\W/g,"_"); | |
175 | + name = name.toLowerCase().replace(/\W/g, "_"); | |
174 | 176 | exportToPc(prepareExport(widgetType), name + '.json'); |
175 | 177 | }, |
176 | 178 | function fail(rejection) { |
... | ... | @@ -211,8 +213,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
211 | 213 | |
212 | 214 | function validateImportedWidgetType(widgetType) { |
213 | 215 | if (angular.isUndefined(widgetType.name) |
214 | - || angular.isUndefined(widgetType.descriptor)) | |
215 | - { | |
216 | + || angular.isUndefined(widgetType.descriptor)) { | |
216 | 217 | return false; |
217 | 218 | } |
218 | 219 | return true; |
... | ... | @@ -230,7 +231,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
230 | 231 | metadata: prepareRuleChainMetaData(ruleChainMetaData) |
231 | 232 | }; |
232 | 233 | var name = ruleChain.name; |
233 | - name = name.toLowerCase().replace(/\W/g,"_"); | |
234 | + name = name.toLowerCase().replace(/\W/g, "_"); | |
234 | 235 | exportToPc(ruleChainExport, name + '.json'); |
235 | 236 | }, |
236 | 237 | (rejection) => { |
... | ... | @@ -255,7 +256,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
255 | 256 | |
256 | 257 | function prepareRuleChainMetaData(ruleChainMetaData) { |
257 | 258 | delete ruleChainMetaData.ruleChainId; |
258 | - for (var i=0;i<ruleChainMetaData.nodes.length;i++) { | |
259 | + for (var i = 0; i < ruleChainMetaData.nodes.length; i++) { | |
259 | 260 | var node = ruleChainMetaData.nodes[i]; |
260 | 261 | delete node.ruleChainId; |
261 | 262 | ruleChainMetaData.nodes[i] = prepareExport(node); |
... | ... | @@ -307,7 +308,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
307 | 308 | function exportWidget(dashboard, sourceState, sourceLayout, widget) { |
308 | 309 | var widgetItem = itembuffer.prepareWidgetItem(dashboard, sourceState, sourceLayout, widget); |
309 | 310 | var name = widgetItem.widget.config.title; |
310 | - name = name.toLowerCase().replace(/\W/g,"_"); | |
311 | + name = name.toLowerCase().replace(/\W/g, "_"); | |
311 | 312 | exportToPc(prepareExport(widgetItem), name + '.json'); |
312 | 313 | } |
313 | 314 | |
... | ... | @@ -417,10 +418,10 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
417 | 418 | var aliasIds = Object.keys(entityAliases); |
418 | 419 | if (aliasIds.length > 0) { |
419 | 420 | processEntityAliases(entityAliases, aliasIds).then( |
420 | - function(missingEntityAliases) { | |
421 | + function (missingEntityAliases) { | |
421 | 422 | if (Object.keys(missingEntityAliases).length > 0) { |
422 | - editMissingAliases($event, [ widget ], | |
423 | - true, 'dashboard.widget-import-missing-aliases-title', missingEntityAliases).then( | |
423 | + editMissingAliases($event, [widget], | |
424 | + true, 'dashboard.widget-import-missing-aliases-title', missingEntityAliases).then( | |
424 | 425 | function success(updatedEntityAliases) { |
425 | 426 | for (var aliasId in updatedEntityAliases) { |
426 | 427 | var entityAlias = updatedEntityAliases[aliasId]; |
... | ... | @@ -485,14 +486,14 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
485 | 486 | function success(targetLayout) { |
486 | 487 | itembuffer.addWidgetToDashboard(dashboard, targetState, targetLayout, widget, |
487 | 488 | aliasesInfo, onAliasesUpdateFunction, originalColumns, originalSize, -1, -1).then( |
488 | - function() { | |
489 | - deferred.resolve( | |
490 | - { | |
491 | - widget: widget, | |
492 | - layoutId: targetLayout | |
493 | - } | |
494 | - ); | |
495 | - } | |
489 | + function () { | |
490 | + deferred.resolve( | |
491 | + { | |
492 | + widget: widget, | |
493 | + layoutId: targetLayout | |
494 | + } | |
495 | + ); | |
496 | + } | |
496 | 497 | ); |
497 | 498 | }, |
498 | 499 | function fail() { |
... | ... | @@ -507,7 +508,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
507 | 508 | dashboardService.getDashboard(dashboardId).then( |
508 | 509 | function success(dashboard) { |
509 | 510 | var name = dashboard.title; |
510 | - name = name.toLowerCase().replace(/\W/g,"_"); | |
511 | + name = name.toLowerCase().replace(/\W/g, "_"); | |
511 | 512 | exportToPc(prepareDashboardExport(dashboard), name + '.json'); |
512 | 513 | }, |
513 | 514 | function fail(rejection) { |
... | ... | @@ -540,13 +541,13 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
540 | 541 | dashboard = dashboardUtils.validateAndUpdateDashboard(dashboard); |
541 | 542 | var entityAliases = dashboard.configuration.entityAliases; |
542 | 543 | if (entityAliases) { |
543 | - var aliasIds = Object.keys( entityAliases ); | |
544 | + var aliasIds = Object.keys(entityAliases); | |
544 | 545 | if (aliasIds.length > 0) { |
545 | 546 | processEntityAliases(entityAliases, aliasIds).then( |
546 | - function(missingEntityAliases) { | |
547 | - if (Object.keys( missingEntityAliases ).length > 0) { | |
547 | + function (missingEntityAliases) { | |
548 | + if (Object.keys(missingEntityAliases).length > 0) { | |
548 | 549 | editMissingAliases($event, dashboard.configuration.widgets, |
549 | - false, 'dashboard.dashboard-import-missing-aliases-title', missingEntityAliases).then( | |
550 | + false, 'dashboard.dashboard-import-missing-aliases-title', missingEntityAliases).then( | |
550 | 551 | function success(updatedEntityAliases) { |
551 | 552 | for (var aliasId in updatedEntityAliases) { |
552 | 553 | entityAliases[aliasId] = updatedEntityAliases[aliasId]; |
... | ... | @@ -617,6 +618,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
617 | 618 | // saveImportedDashboard(dashboard, deferred); |
618 | 619 | // } |
619 | 620 | // } |
621 | + deferred.resolve(); | |
620 | 622 | }, |
621 | 623 | function fail() { |
622 | 624 | deferred.reject(); |
... | ... | @@ -644,14 +646,13 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
644 | 646 | } |
645 | 647 | |
646 | 648 | |
647 | - | |
648 | 649 | function exportExtension(extensionId) { |
649 | 650 | |
650 | 651 | getExtension(extensionId) |
651 | 652 | .then( |
652 | 653 | function success(extension) { |
653 | 654 | var name = extension.title; |
654 | - name = name.toLowerCase().replace(/\W/g,"_"); | |
655 | + name = name.toLowerCase().replace(/\W/g, "_"); | |
655 | 656 | exportToPc(prepareExport(extension), name + '.json'); |
656 | 657 | }, |
657 | 658 | function fail(rejection) { |
... | ... | @@ -711,7 +712,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
711 | 712 | function validateImportedExtension(configuration) { |
712 | 713 | if (configuration.length) { |
713 | 714 | for (let i = 0; i < configuration.length; i++) { |
714 | - if (angular.isUndefined(configuration[i].configuration) || angular.isUndefined(configuration[i].id )|| angular.isUndefined(configuration[i].type)) { | |
715 | + if (angular.isUndefined(configuration[i].configuration) || angular.isUndefined(configuration[i].id) || angular.isUndefined(configuration[i].type)) { | |
715 | 716 | return false; |
716 | 717 | } |
717 | 718 | } |
... | ... | @@ -742,7 +743,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
742 | 743 | var aliasId = aliasIds[index]; |
743 | 744 | var entityAlias = entityAliases[aliasId]; |
744 | 745 | entityService.checkEntityAlias(entityAlias).then( |
745 | - function(result) { | |
746 | + function (result) { | |
746 | 747 | if (result) { |
747 | 748 | checkNextEntityAliasOrComplete(index, aliasIds, entityAliases, missingEntityAliases, deferred); |
748 | 749 | } else { |
... | ... | @@ -782,6 +783,91 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
782 | 783 | return deferred.promise; |
783 | 784 | } |
784 | 785 | |
786 | + /** | |
787 | + * splitCSV function (c) 2009 Brian Huisman, see http://www.greywyvern.com/?post=258 | |
788 | + * Works by spliting on seperators first, then patching together quoted values | |
789 | + */ | |
790 | + function splitCSV(str, sep) { | |
791 | + for (var foo = str.split(sep = sep || ","), x = foo.length - 1, tl; x >= 0; x--) { | |
792 | + if (foo[x].replace(/"\s+$/, '"').charAt(foo[x].length - 1) == '"') { | |
793 | + if ((tl = foo[x].replace(/^\s+"/, '"')).length > 1 && tl.charAt(0) == '"') { | |
794 | + foo[x] = foo[x].replace(/^\s*"|"\s*$/g, '').replace(/""/g, '"'); | |
795 | + } else if (x) { | |
796 | + foo.splice(x - 1, 2, [foo[x - 1], foo[x]].join(sep)); | |
797 | + } else foo = foo.shift().split(sep).concat(foo); | |
798 | + } else foo[x].replace(/""/g, '"'); | |
799 | + } | |
800 | + return foo; | |
801 | + } | |
802 | + | |
803 | + function isNumeric(str) { | |
804 | + str = str.replace(',', '.'); | |
805 | + return !isNaN(parseFloat(str)) && isFinite(str); | |
806 | + } | |
807 | + | |
808 | + function parseStringToFormatJS(str) { | |
809 | + if (isNumeric(str.replace(',', '.'))) { | |
810 | + return parseFloat(str.replace(',', '.')); | |
811 | + } | |
812 | + if (str.search(/^(true|false)$/im) === 0) { | |
813 | + return str.toLowerCase() === 'true'; | |
814 | + } | |
815 | + if (str === "") { | |
816 | + return null; | |
817 | + } | |
818 | + return str; | |
819 | + } | |
820 | + | |
821 | + function convertCSVToJson(csvdata, config) { | |
822 | + config = config || {}; | |
823 | + const delim = config.delim || ","; | |
824 | + const header = config.header || false; | |
825 | + | |
826 | + let csvlines = csvdata.split(/[\r\n]+/); | |
827 | + let csvheaders = splitCSV(csvlines[0], delim); | |
828 | + let csvrows = header ? csvlines.slice(1, csvlines.length) : csvlines; | |
829 | + | |
830 | + let result = {}; | |
831 | + result.headers = csvheaders; | |
832 | + result.rows = []; | |
833 | + | |
834 | + for (let r in csvrows) { | |
835 | + if (csvrows.hasOwnProperty(r)) { | |
836 | + let row = csvrows[r]; | |
837 | + | |
838 | + if (row.length === 0) | |
839 | + break; | |
840 | + | |
841 | + let rowitems = splitCSV(row, delim); | |
842 | + for (let i = 0; i < rowitems.length; i++) { | |
843 | + rowitems[i] = parseStringToFormatJS(rowitems[i]); | |
844 | + } | |
845 | + result.rows.push(rowitems); | |
846 | + } | |
847 | + } | |
848 | + return result; | |
849 | + } | |
850 | + | |
851 | + function createMultiEntity(arrayData, entityType, update, config) { | |
852 | + var deferred = $q.defer(); | |
853 | + var allPromise = []; | |
854 | + switch (entityType) { | |
855 | + case types.entityType.device: | |
856 | + for(var i = 0; i < arrayData.length; i++){ | |
857 | + var promise = deviceService.saveDeviceParameters(arrayData[i], update, config); | |
858 | + allPromise.push(promise); | |
859 | + } | |
860 | + break; | |
861 | + } | |
862 | + $q.all(allPromise).then(function success() { | |
863 | + deferred.resolve(); | |
864 | + $timeout(function () { | |
865 | + console.log("1"); // eslint-disable-line | |
866 | + }, 1000); | |
867 | + }); | |
868 | + return deferred.promise; | |
869 | + } | |
870 | + | |
785 | 871 | // Common functions |
786 | 872 | |
787 | 873 | function prepareExport(data) { |
... | ... | @@ -821,8 +907,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
821 | 907 | |
822 | 908 | if (window.navigator && window.navigator.msSaveOrOpenBlob) { |
823 | 909 | window.navigator.msSaveOrOpenBlob(blob, filename); |
824 | - } | |
825 | - else{ | |
910 | + } else { | |
826 | 911 | var e = document.createEvent('MouseEvents'), |
827 | 912 | a = document.createElement('a'); |
828 | 913 | |
... | ... | @@ -873,7 +958,6 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
873 | 958 | targetEvent: $event |
874 | 959 | }).then(function (importData) { |
875 | 960 | deferred.resolve(importData); |
876 | - | |
877 | 961 | }, function () { |
878 | 962 | deferred.reject(); |
879 | 963 | }); | ... | ... |
... | ... | @@ -778,6 +778,22 @@ |
778 | 778 | "no-data": "No data to display", |
779 | 779 | "columns-to-display": "Columns to Display" |
780 | 780 | }, |
781 | + "entity-group": { | |
782 | + "column-value": "Value", | |
783 | + "column-title": "Title", | |
784 | + "column-type": { | |
785 | + "column-type": "Column type", | |
786 | + "shared-attribute": "Shared attribute", | |
787 | + "server-attribute": "Server attribute", | |
788 | + "timeseries": "Timeseries", | |
789 | + "entity-field": "Entity field" | |
790 | + }, | |
791 | + "entity-field": { | |
792 | + "name": "Name", | |
793 | + "type": "Type", | |
794 | + "assigned_customer": "Assigned Customer" | |
795 | + } | |
796 | + }, | |
781 | 797 | "entity-view": { |
782 | 798 | "entity-view": "Entity View", |
783 | 799 | "entity-view-required": "Entity view is required.", | ... | ... |