Browse Source

Merge branch 'vvlladd28-feature/import_device'

pull/1812/head
Igor Kulikov 7 years ago
parent
commit
3d8aaf69e8
  1. 4
      ui/package-lock.json
  2. 1
      ui/package.json
  3. 15
      ui/src/app/api/asset.service.js
  4. 81
      ui/src/app/api/attribute.service.js
  5. 49
      ui/src/app/api/device.service.js
  6. 114
      ui/src/app/api/entity.service.js
  7. 3
      ui/src/app/app.js
  8. 28
      ui/src/app/asset/asset.controller.js
  9. 34
      ui/src/app/common/types.constant.js
  10. 30
      ui/src/app/device/device.controller.js
  11. 20
      ui/src/app/global-interceptor.service.js
  12. 1
      ui/src/app/help/help-links.constant.js
  13. 238
      ui/src/app/import-export/import-dialog-csv.controller.js
  14. 171
      ui/src/app/import-export/import-dialog-csv.tpl.html
  15. 20
      ui/src/app/import-export/import-dialog.scss
  16. 247
      ui/src/app/import-export/import-export.service.js
  17. 4
      ui/src/app/import-export/index.js
  18. 93
      ui/src/app/import-export/table-columns-assignment.directive.js
  19. 55
      ui/src/app/import-export/table-columns-assignment.tpl.html
  20. 46
      ui/src/app/locale/locale.constant-en_US.json

4
ui/package-lock.json

@ -8375,6 +8375,10 @@
"integrity": "sha1-mnHEh0chjrylHlGmbaaCA4zct78=",
"dev": true
},
"material-steppers": {
"version": "git://github.com/thingsboard/material-steppers.git#ee6241d63b97b7c4a3f3b240cdb347c02a3dcb0a",
"from": "git://github.com/thingsboard/material-steppers.git#master"
},
"material-ui": {
"version": "0.16.7",
"resolved": "https://registry.npmjs.org/material-ui/-/material-ui-0.16.7.tgz",

1
ui/package.json

@ -65,6 +65,7 @@
"leaflet": "^1.5.1",
"leaflet-polylinedecorator": "^1.6.0",
"leaflet-providers": "^1.8.0",
"material-steppers": "git://github.com/thingsboard/material-steppers.git#master",
"material-ui": "^0.16.1",
"material-ui-number-input": "^5.0.16",
"md-color-picker": "0.2.6",

15
ui/src/app/api/asset.service.js

@ -32,7 +32,8 @@ function AssetService($http, $q, customerService, userService) {
getCustomerAssets: getCustomerAssets,
findByQuery: findByQuery,
fetchAssetsByNameFilter: fetchAssetsByNameFilter,
getAssetTypes: getAssetTypes
getAssetTypes: getAssetTypes,
findByName: findByName
}
return service;
@ -276,4 +277,16 @@ function AssetService($http, $q, customerService, userService) {
return deferred.promise;
}
function findByName(assetName, config) {
config = config || {};
var deferred = $q.defer();
var url = '/api/tenant/assets?assetName=' + assetName;
$http.get(url, config).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
}

81
ui/src/app/api/attribute.service.js

@ -30,7 +30,9 @@ function AttributeService($http, $q, $filter, types, telemetryWebsocketService)
subscribeForEntityAttributes: subscribeForEntityAttributes,
unsubscribeForEntityAttributes: unsubscribeForEntityAttributes,
saveEntityAttributes: saveEntityAttributes,
deleteEntityAttributes: deleteEntityAttributes
deleteEntityAttributes: deleteEntityAttributes,
saveEntityTimeseries: saveEntityTimeseries,
deleteEntityTimeseries: deleteEntityTimeseries
}
return service;
@ -212,7 +214,8 @@ function AttributeService($http, $q, $filter, types, telemetryWebsocketService)
}
}
function saveEntityAttributes(entityType, entityId, attributeScope, attributes) {
function saveEntityAttributes(entityType, entityId, attributeScope, attributes, config) {
config = config || {};
var deferred = $q.defer();
var attributesData = {};
var deleteAttributes = [];
@ -229,7 +232,7 @@ function AttributeService($http, $q, $filter, types, telemetryWebsocketService)
}
if (Object.keys(attributesData).length) {
var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/' + attributeScope;
$http.post(url, attributesData).then(function success(response) {
$http.post(url, attributesData, config).then(function success(response) {
if (deleteEntityAttributesPromise) {
deleteEntityAttributesPromise.then(
function success() {
@ -260,7 +263,57 @@ function AttributeService($http, $q, $filter, types, telemetryWebsocketService)
return deferred.promise;
}
function deleteEntityAttributes(entityType, entityId, attributeScope, attributes) {
function saveEntityTimeseries(entityType, entityId, timeseriesScope, timeseries, config) {
config = config || {};
var deferred = $q.defer();
var timeseriesData = {};
var deleteTimeseries = [];
for (var a=0; a<timeseries.length;a++) {
if (angular.isDefined(timeseries[a].value) && timeseries[a].value !== null) {
timeseriesData[timeseries[a].key] = timeseries[a].value;
} else {
deleteTimeseries.push(timeseries[a]);
}
}
var deleteEntityTimeseriesPromise;
if (deleteTimeseries.length) {
deleteEntityTimeseriesPromise = deleteEntityTimeseries(entityType, entityId, deleteTimeseries, config);
}
if (Object.keys(timeseriesData).length) {
var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/' + timeseriesScope;
$http.post(url, timeseriesData, config).then(function success(response) {
if (deleteEntityTimeseriesPromise) {
deleteEntityTimeseriesPromise.then(
function success() {
deferred.resolve(response.data);
},
function fail() {
deferred.reject();
}
)
} else {
deferred.resolve(response.data);
}
}, function fail() {
deferred.reject();
});
} else if (deleteEntityTimeseriesPromise) {
deleteEntityTimeseriesPromise.then(
function success() {
deferred.resolve();
},
function fail() {
deferred.reject();
}
)
} else {
deferred.resolve();
}
return deferred.promise;
}
function deleteEntityAttributes(entityType, entityId, attributeScope, attributes, config) {
config = config || {};
var deferred = $q.defer();
var keys = '';
for (var i = 0; i < attributes.length; i++) {
@ -270,7 +323,7 @@ function AttributeService($http, $q, $filter, types, telemetryWebsocketService)
keys += attributes[i].key;
}
var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/' + attributeScope + '?keys=' + keys;
$http.delete(url).then(function success() {
$http.delete(url, config).then(function success() {
deferred.resolve();
}, function fail() {
deferred.reject();
@ -278,5 +331,23 @@ function AttributeService($http, $q, $filter, types, telemetryWebsocketService)
return deferred.promise;
}
function deleteEntityTimeseries(entityType, entityId, timeseries, config) {
config = config || {};
var deferred = $q.defer();
var keys = '';
for (var i = 0; i < timeseries.length; i++) {
if (i > 0) {
keys += ',';
}
keys += timeseries[i].key;
}
var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/delete' + '?keys=' + keys;
$http.delete(url, config).then(function success() {
deferred.resolve();
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
}

49
ui/src/app/api/device.service.js

@ -42,8 +42,9 @@ function DeviceService($http, $q, $window, userService, attributeService, custom
sendOneWayRpcCommand: sendOneWayRpcCommand,
sendTwoWayRpcCommand: sendTwoWayRpcCommand,
findByQuery: findByQuery,
getDeviceTypes: getDeviceTypes
}
getDeviceTypes: getDeviceTypes,
findByName: findByName
};
return service;
@ -124,7 +125,7 @@ function DeviceService($http, $q, $window, userService, attributeService, custom
if (!config) {
config = {};
}
config = Object.assign(config, { ignoreErrors: ignoreErrors });
config = Object.assign(config, {ignoreErrors: ignoreErrors});
$http.get(url, config).then(function success(response) {
deferred.resolve(response.data);
}, function fail(response) {
@ -136,8 +137,8 @@ function DeviceService($http, $q, $window, userService, attributeService, custom
function getDevices(deviceIds, config) {
var deferred = $q.defer();
var ids = '';
for (var i=0;i<deviceIds.length;i++) {
if (i>0) {
for (var i = 0; i < deviceIds.length; i++) {
if (i > 0) {
ids += ',';
}
ids += deviceIds[i];
@ -146,11 +147,11 @@ function DeviceService($http, $q, $window, userService, attributeService, custom
$http.get(url, config).then(function success(response) {
var devices = response.data;
devices.sort(function (device1, device2) {
var id1 = device1.id.id;
var id2 = device2.id.id;
var index1 = deviceIds.indexOf(id1);
var index2 = deviceIds.indexOf(id2);
return index1 - index2;
var id1 = device1.id.id;
var id2 = device2.id.id;
var index1 = deviceIds.indexOf(id1);
var index2 = deviceIds.indexOf(id2);
return index1 - index2;
});
deferred.resolve(devices);
}, function fail(response) {
@ -159,10 +160,11 @@ function DeviceService($http, $q, $window, userService, attributeService, custom
return deferred.promise;
}
function saveDevice(device) {
function saveDevice(device, config) {
config = config || {};
var deferred = $q.defer();
var url = '/api/device';
$http.post(url, device).then(function success(response) {
$http.post(url, device, config).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
@ -181,7 +183,8 @@ function DeviceService($http, $q, $window, userService, attributeService, custom
return deferred.promise;
}
function getDeviceCredentials(deviceId, sync) {
function getDeviceCredentials(deviceId, sync, config) {
config = config || {};
var deferred = $q.defer();
var url = '/api/device/' + deviceId + '/credentials';
if (sync) {
@ -196,7 +199,7 @@ function DeviceService($http, $q, $window, userService, attributeService, custom
deferred.reject();
}
} else {
$http.get(url, null).then(function success(response) {
$http.get(url, config).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
@ -205,10 +208,11 @@ function DeviceService($http, $q, $window, userService, attributeService, custom
return deferred.promise;
}
function saveDeviceCredentials(deviceCredentials) {
function saveDeviceCredentials(deviceCredentials, config) {
config = config || {};
var deferred = $q.defer();
var url = '/api/device/credentials';
$http.post(url, deviceCredentials).then(function success(response) {
$http.post(url, deviceCredentials, config).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
@ -297,7 +301,7 @@ function DeviceService($http, $q, $window, userService, attributeService, custom
if (!config) {
config = {};
}
config = Object.assign(config, { ignoreErrors: ignoreErrors });
config = Object.assign(config, {ignoreErrors: ignoreErrors});
$http.post(url, query, config).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
@ -317,4 +321,15 @@ function DeviceService($http, $q, $window, userService, attributeService, custom
return deferred.promise;
}
function findByName(deviceName, config) {
config = config || {};
var deferred = $q.defer();
var url = '/api/tenant/devices?deviceName=' + deviceName;
$http.get(url, config).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
}

114
ui/src/app/api/entity.service.js

@ -38,6 +38,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
createAlarmSourceFromSubscriptionInfo: createAlarmSourceFromSubscriptionInfo,
getRelatedEntities: getRelatedEntities,
saveRelatedEntity: saveRelatedEntity,
saveEntityParameters: saveEntityParameters,
getRelatedEntity: getRelatedEntity,
deleteRelatedEntity: deleteRelatedEntity,
moveEntity: moveEntity,
@ -1072,6 +1073,119 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
return deferred.promise;
}
function saveEntityRelation(entityType, entityId, entityRelation, config) {
const deferred = $q.defer();
let attributesType = Object.keys(types.attributesScope);
let allPromise = [];
let promise = "";
if (entityRelation.accessToken !== "") {
promise = deviceService.getDeviceCredentials(entityId.id, null, config).then(function (response) {
response.credentialsId = entityRelation.accessToken;
response.credentialsType = "ACCESS_TOKEN";
response.credentialsValue = null;
return deviceService.saveDeviceCredentials(response, config).catch(function () {
return "error";
});
});
allPromise.push(promise)
}
for (let i = 0; i < attributesType.length; i++) {
let attribute = attributesType[i];
if (entityRelation.attributes[attribute] && entityRelation.attributes[attribute].length !== 0) {
promise = attributeService.saveEntityAttributes(entityType, entityId.id, types.attributesScope[attribute].value, entityRelation.attributes[attribute], config).catch(function () {
return "error";
});
allPromise.push(promise);
}
}
if (entityRelation.timeseries.length !== 0) {
promise = attributeService.saveEntityTimeseries(entityType, entityId.id, "time", entityRelation.timeseries, config).catch(function(){
return "error";
});
allPromise.push(promise);
}
$q.all(allPromise).then(function success(response) {
let isResponseHasError = false;
for(let i = 0; i < response.length; i++){
if(response[i] === "error"){
isResponseHasError = true;
break;
}
}
isResponseHasError ? deferred.reject() : deferred.resolve();
});
return deferred.promise;
}
function saveEntityParameters(entityType, entityParameters, update, config) {
config = config || {};
const deferred = $q.defer();
let statisticalInfo = {};
let newEntity = {
name: entityParameters.name,
type: entityParameters.type
};
let promise;
switch (entityType) {
case types.entityType.device:
promise = deviceService.saveDevice(newEntity, config);
break;
case types.entityType.asset:
promise = assetService.saveAsset(newEntity, true, config);
break;
}
promise.then(function success(response) {
saveEntityRelation(entityType, response.id, entityParameters, config).then(function success() {
statisticalInfo.create = {
entity: 1
};
deferred.resolve(statisticalInfo);
}, function fail() {
statisticalInfo.error = {
entity: 1
};
deferred.resolve(statisticalInfo);
});
}, function fail() {
if (update) {
let findIdEntity;
switch (entityType) {
case types.entityType.device:
findIdEntity = deviceService.findByName(entityParameters.name, config);
break;
case types.entityType.asset:
findIdEntity = assetService.findByName(entityParameters.name, config);
break;
}
findIdEntity.then(function success(response) {
saveEntityRelation(entityType, response.id, entityParameters, config).then(function success() {
statisticalInfo.update = {
entity: 1
};
deferred.resolve(statisticalInfo);
}, function fail() {
statisticalInfo.error = {
entity: 1
};
deferred.resolve(statisticalInfo);
});
}, function fail() {
statisticalInfo.error = {
entity: 1
};
deferred.resolve(statisticalInfo);
});
} else {
statisticalInfo.error = {
entity: 1
};
deferred.resolve(statisticalInfo);
}
});
return deferred.promise;
}
function getRelatedEntity(entityId, keys, typeTranslatePrefix) {
var deferred = $q.defer();
getEntityPromise(entityId.entityType, entityId.id, {ignoreLoading: true}).then(

3
ui/src/app/app.js

@ -54,6 +54,8 @@ import react from 'ngreact';
import '@flowjs/ng-flow/dist/ng-flow-standalone.min';
import 'ngFlowchart/dist/ngFlowchart';
import 'jstree/dist/jstree.min';
import 'material-steppers/dist/material-steppers';
import 'material-steppers/dist/material-steppers.css'
import 'jstree-bootstrap-theme/dist/themes/proton/style.min.css';
import 'typeface-roboto';
import 'font-awesome/css/font-awesome.min.css';
@ -127,6 +129,7 @@ angular.module('thingsboard', [
react.name,
'flow',
'flowchart',
'mdSteppers',
thingsboardThirdpartyFix,
thingsboardTranslateHandler,
thingsboardLogin,

28
ui/src/app/asset/asset.controller.js

@ -48,7 +48,7 @@ export function AssetCardController(types) {
/*@ngInject*/
export function AssetController($rootScope, userService, assetService, customerService, $state, $stateParams,
$document, $mdDialog, $q, $translate, types) {
$document, $mdDialog, $q, $translate, types, importExport) {
var customerId = $stateParams.customerId;
@ -56,6 +56,29 @@ export function AssetController($rootScope, userService, assetService, customerS
var assetGroupActionsList = [];
var assetAddItemActionsList = [
{
onAction: function ($event) {
vm.grid.addItem($event);
},
name: function() { return $translate.instant('action.add') },
details: function() { return $translate.instant('asset.add-asset-text') },
icon: "insert_drive_file"
},
{
onAction: function ($event) {
importExport.importEntities($event, types.entityType.asset).then(
function() {
vm.grid.refreshList();
}
);
},
name: function() { return $translate.instant('action.import') },
details: function() { return $translate.instant('asset.import') },
icon: "file_upload"
}
];
var vm = this;
vm.types = types;
@ -77,6 +100,7 @@ export function AssetController($rootScope, userService, assetService, customerS
actionsList: assetActionsList,
groupActionsList: assetGroupActionsList,
addItemActions: assetAddItemActionsList,
onGridInited: gridInited,
@ -294,6 +318,8 @@ export function AssetController($rootScope, userService, assetService, customerS
} else if (vm.assetsScope === 'customer_user') {
vm.assetGridConfig.addItemAction = {};
}
vm.assetGridConfig.addItemActions = [];
}
vm.assetGridConfig.refreshParamsFunc = refreshAssetsParamsFunction;

34
ui/src/app/common/types.constant.js

@ -350,6 +350,40 @@ export default angular.module('thingsboard.types', [])
rulenode: "RULE_NODE",
entityView: "ENTITY_VIEW"
},
importEntityColumnType: {
name: {
name: 'import.column-type.name',
value: 'name'
},
type: {
name: 'import.column-type.type',
value: 'type'
},
clientAttribute: {
name: 'import.column-type.client-attribute',
value: 'CLIENT_ATTRIBUTE'
},
sharedAttribute: {
name: 'import.column-type.shared-attribute',
value: 'SHARED_ATTRIBUTE'
},
serverAttribute: {
name: 'import.column-type.server-attribute',
value: 'SERVER_ATTRIBUTE'
},
timeseries: {
name: 'import.column-type.timeseries',
value: 'TIMESERIES'
},
entityField: {
name: 'import.column-type.entity-field',
value: 'ENTITY_FIELD'
},
accessToken: {
name: 'import.column-type.access-token',
value: 'ACCESS_TOKEN'
}
},
aliasEntityType: {
current_customer: "CURRENT_CUSTOMER"
},

30
ui/src/app/device/device.controller.js

@ -49,7 +49,7 @@ export function DeviceCardController(types) {
/*@ngInject*/
export function DeviceController($rootScope, userService, deviceService, customerService, $state, $stateParams,
$document, $mdDialog, $q, $translate, types) {
$document, $mdDialog, $q, $translate, types, importExport) {
var customerId = $stateParams.customerId;
@ -57,6 +57,29 @@ export function DeviceController($rootScope, userService, deviceService, custome
var deviceGroupActionsList = [];
var deviceAddItemActionsList = [
{
onAction: function ($event) {
vm.grid.addItem($event);
},
name: function() { return $translate.instant('action.add') },
details: function() { return $translate.instant('device.add-device-text') },
icon: "insert_drive_file"
},
{
onAction: function ($event) {
importExport.importEntities($event, types.entityType.device).then(
function() {
vm.grid.refreshList();
}
);
},
name: function() { return $translate.instant('action.import') },
details: function() { return $translate.instant('device.import') },
icon: "file_upload"
}
];
var vm = this;
vm.types = types;
@ -78,12 +101,12 @@ export function DeviceController($rootScope, userService, deviceService, custome
actionsList: deviceActionsList,
groupActionsList: deviceGroupActionsList,
addItemActions: deviceAddItemActionsList,
onGridInited: gridInited,
addItemTemplateUrl: addDeviceTemplate,
addItemText: function() { return $translate.instant('device.add-device-text') },
noItemsText: function() { return $translate.instant('device.no-devices-text') },
itemDetailsText: function() { return $translate.instant('device.device-details') },
isDetailsReadOnly: isCustomerUser,
@ -314,7 +337,6 @@ export function DeviceController($rootScope, userService, deviceService, custome
icon: "add"
};
} else if (vm.devicesScope === 'customer_user') {
deviceActionsList.push(
{
@ -329,6 +351,8 @@ export function DeviceController($rootScope, userService, deviceService, custome
vm.deviceGridConfig.addItemAction = {};
}
vm.deviceGridConfig.addItemActions = [];
}
vm.deviceGridConfig.refreshParamsFunc = refreshDevicesParamsFunction;

20
ui/src/app/global-interceptor.service.js

@ -21,6 +21,7 @@ export default function GlobalInterceptor($rootScope, $q, $injector) {
var userService;
var types;
var http;
var timeout;
var internalUrlPrefixes = [
'/api/auth/token',
@ -71,6 +72,13 @@ export default function GlobalInterceptor($rootScope, $q, $injector) {
return http;
}
function getTimeout() {
if (!timeout) {
timeout = $injector.get("$timeout");
}
return timeout;
}
function rejectionErrorCode(rejection) {
if (rejection && rejection.data && rejection.data.errorCode) {
return rejection.data.errorCode;
@ -144,12 +152,20 @@ export default function GlobalInterceptor($rootScope, $q, $injector) {
return response;
}
function retryRequest (httpConfig) {
var thisTimeout = 1000 + Math.random() * 3000;
return getTimeout()(function() {
return getHttp()(httpConfig);
}, thisTimeout);
}
function responseError(rejection) {
if (rejection.config.url.startsWith('/api/')) {
updateLoadingState(rejection.config, false);
}
var unhandled = false;
var ignoreErrors = rejection.config.ignoreErrors;
var resendRequest = rejection.config.resendRequest;
if (rejection.refreshTokenPending || rejection.status === 401) {
var errorCode = rejectionErrorCode(rejection);
if (rejection.refreshTokenPending || (errorCode && errorCode === getTypes().serverErrorCode.jwtTokenExpired)) {
@ -161,6 +177,10 @@ export default function GlobalInterceptor($rootScope, $q, $injector) {
if (!ignoreErrors) {
$rootScope.$broadcast('forbidden');
}
} else if (rejection.status === 429) {
if (resendRequest) {
return retryRequest(rejection.config);
}
} else if (rejection.status === 0 || rejection.status === -1) {
getToast().showError(getTranslate().instant('error.unable-to-connect'));
} else if (!rejection.config.url.startsWith('/api/plugins/rpc')) {

1
ui/src/app/help/help-links.constant.js

@ -96,6 +96,7 @@ export default angular.module('thingsboard.help', [])
assets: helpBaseUrl + "/docs/user-guide/ui/assets",
devices: helpBaseUrl + "/docs/user-guide/ui/devices",
entityViews: helpBaseUrl + "/docs/user-guide/ui/entity-views",
entitiesImport: helpBaseUrl + "/docs/user-guide/bulk-provisioning",
dashboards: helpBaseUrl + "/docs/user-guide/ui/dashboards",
users: helpBaseUrl + "/docs/user-guide/ui/users",
widgetsBundles: helpBaseUrl + "/docs/user-guide/ui/widget-library#bundles",

238
ui/src/app/import-export/import-dialog-csv.controller.js

@ -0,0 +1,238 @@
/*
* Copyright © 2016-2019 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import './import-dialog.scss';
/*@ngInject*/
export default function ImportDialogCsvController($scope, $mdDialog, toast, importTitle, importFileLabel, entityType, importExport, types, $mdStepper, $timeout) {
var vm = this;
vm.cancel = cancel;
vm.fileAdded = fileAdded;
vm.clearFile = clearFile;
vm.nextStep = nextStep;
vm.previousStep = previousStep;
vm.importParameters = {
delim: ',',
isUpdate: true,
isHeader: true
};
vm.importTitle = importTitle;
vm.importFileLabel = importFileLabel;
vm.entityType = entityType;
vm.isVertical = true;
vm.isLinear = true;
vm.isAlternative = false;
vm.isMobileStepText = true;
vm.isImportData = false;
vm.parseData = [];
vm.delimiters = [{
key: ',',
value: ','
}, {
key: ';',
value: ';'
}, {
key: '|',
value: '|'
}, {
key: '\t',
value: 'Tab'
}];
vm.progressCreate = 0;
var parseData = {};
function fileAdded($file) {
if ($file.getExtension() === 'csv') {
var reader = new FileReader();
reader.onload = function (event) {
$scope.$apply(function () {
if (event.target.result) {
vm.theFormStep1.$setDirty();
var importCSV = event.target.result;
if (importCSV && importCSV.length > 0) {
try {
vm.importData = importCSV;
vm.fileName = $file.name;
} catch (err) {
vm.fileName = null;
toast.showError(err.message);
}
}
}
});
};
reader.readAsText($file.file);
}
}
function parseCSV(importData) {
var config = {
delim: vm.importParameters.delim,
header: vm.importParameters.isHeader
};
return importExport.convertCSVToJson(importData, config);
}
function createColumnsData(parseData) {
vm.columnsParam = [];
var columnParam = {};
for (var i = 0; i < parseData.headers.length; i++) {
if (vm.importParameters.isHeader && parseData.headers[i].search(/^(name|type)$/im) === 0) {
columnParam = {
type: types.importEntityColumnType[parseData.headers[i].toLowerCase()].value,
key: parseData.headers[i].toLowerCase(),
sampleData: parseData.rows[0][i]
};
} else {
columnParam = {
type: types.importEntityColumnType.serverAttribute.value,
key: vm.importParameters.isHeader ? parseData.headers[i] : "",
sampleData: parseData.rows[0][i]
};
}
vm.columnsParam.push(columnParam);
}
}
function addEntities (importData, parameterColumns) {
var entitiesData = [];
var sentDataLength = 0;
var config = {
ignoreErrors: true,
resendRequest: true
};
for (var i = 0; i < importData.rows.length; i++) {
var entityData = {
name: "",
type: "",
accessToken: "",
attributes: {
server: [],
shared: []
},
timeseries: []
};
for (var j = 0; j < parameterColumns.length; j++) {
switch (parameterColumns[j].type) {
case types.importEntityColumnType.serverAttribute.value:
entityData.attributes.server.push({
key: parameterColumns[j].key,
value: importData.rows[i][j]
});
break;
case types.importEntityColumnType.timeseries.value:
entityData.timeseries.push({
key: parameterColumns[j].key,
value: importData.rows[i][j]
});
break;
case types.importEntityColumnType.sharedAttribute.value:
entityData.attributes.shared.push({
key: parameterColumns[j].key,
value: importData.rows[i][j]
});
break;
case types.importEntityColumnType.accessToken.value:
entityData.accessToken = importData.rows[i][j];
break;
case types.importEntityColumnType.name.value:
entityData.name = importData.rows[i][j];
break;
case types.importEntityColumnType.type.value:
entityData.type = importData.rows[i][j];
break;
}
}
entitiesData.push(entityData);
}
$scope.$on('createImportEntityCompleted', function () {
sentDataLength++;
vm.progressCreate = Math.round((sentDataLength / importData.rows.length) * 100);
});
importExport.createMultiEntity(entitiesData, vm.entityType, vm.importParameters.isUpdate, config).then(function (response) {
vm.statistical = response;
vm.isImportData = false;
$mdStepper('import-stepper').next();
});
}
function clearFile() {
vm.theFormStep1.$setDirty();
vm.fileName = null;
vm.importData = null;
}
function previousStep(step) {
let steppers = $mdStepper('import-stepper');
switch (step) {
case 1:
steppers.back();
$timeout(function () {
vm.theFormStep1.$setDirty();
});
break;
default:
steppers.back();
break;
}
}
function nextStep(step) {
let steppers = $mdStepper('import-stepper');
switch (step) {
case 2:
steppers.next();
break;
case 3:
parseData = parseCSV(vm.importData);
if (parseData === -1) {
steppers.back();
$timeout(function () {
clearFile();
});
} else {
createColumnsData(parseData);
steppers.next();
}
break;
case 4:
steppers.next();
vm.isImportData = true;
addEntities(parseData, vm.columnsParam);
break;
case 6:
$mdDialog.hide();
break;
}
}
function cancel() {
if($mdStepper('import-stepper').currentStep > 2){
$mdDialog.hide();
} else {
$mdDialog.cancel();
}
}
}

171
ui/src/app/import-export/import-dialog-csv.tpl.html

@ -0,0 +1,171 @@
<!--
Copyright © 2016-2019 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<md-dialog aria-label="{{ vm.importTitle | translate }}" class="tb-import-stepper" tb-help="'entitiesImport'"
help-container-id="help-container">
<md-toolbar>
<div class="md-toolbar-tools">
<h2 translate>{{ vm.importTitle }}</h2>
<span flex></span>
<div id="help-container"></div>
<md-button class="md-icon-button" ng-click="vm.cancel()" ng-disabled="vm.isImportData">
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
</md-button>
</div>
</md-toolbar>
<span style="max-height: 5px; height: 5px;" flex></span>
<md-dialog-content>
<md-stepper id="import-stepper" md-mobile-step-text="vm.isMobileStepText" md-vertical="vm.isVertical"
md-linear="vm.isLinear" md-alternative="vm.isAlternative">
<md-step md-label="{{ 'import.stepper-text.select-file' | translate }}">
<md-step-body>
<form name="vm.theFormStep1">
<fieldset ng-disabled="$root.loading">
<div layout="column" layout-padding>
<div class="tb-container">
<label class="tb-label" translate>{{ vm.importFileLabel }}</label>
<div flow-init="{singleFile:true}"
flow-file-added="vm.fileAdded( $file )" class="tb-file-select-container">
<div class="tb-file-clear-container">
<md-button ng-click="vm.clearFile()"
class="tb-file-clear-btn md-icon-button md-primary"
aria-label="{{ 'action.remove' | translate }}">
<md-tooltip md-direction="top">
{{ 'action.remove' | translate }}
</md-tooltip>
<md-icon aria-label="{{ 'action.remove' | translate }}"
class="material-icons">
close
</md-icon>
</md-button>
</div>
<div class="alert tb-flow-drop" flow-drop>
<label for="select" translate>import.drop-file-csv</label>
<input class="file-input" flow-btn
flow-attrs="{accept:'.csv,application/csv,text/csv'}"
id="select">
</div>
</div>
</div>
<div>
<div ng-show="!vm.fileName" translate>import.no-file</div>
<div ng-show="vm.fileName">{{ vm.fileName }}</div>
</div>
</div>
</fieldset>
</form>
</md-step-body>
<md-step-actions layout="row">
<span flex></span>
<md-button ng-disabled="$root.loading" ng-click="vm.cancel()">
{{ 'action.cancel' | translate }}
</md-button>
<md-button class="md-primary md-raised"
ng-disabled="$root.loading || !vm.theFormStep1.$dirty || !vm.theFormStep1.$valid || !vm.importData"
ng-click="vm.nextStep(2);">
{{ 'action.continue' | translate }}
</md-button>
</md-step-actions>
</md-step>
<md-step md-label="{{ 'import.stepper-text.configuration' | translate }}">
<md-step-body>
<div layout="column">
<md-input-container>
<label translate>import.csv-delimiter</label>
<md-select ng-model="vm.importParameters.delim">
<md-option ng-repeat="delimiter in vm.delimiters" ng-value="delimiter.key">
{{delimiter.value}}
</md-option>
</md-select>
</md-input-container>
<md-checkbox ng-model="vm.importParameters.isHeader"
aria-label="First line contains column names">
{{ 'import.csv-first-line-header' | translate }}
</md-checkbox>
<md-checkbox ng-model="vm.importParameters.isUpdate" aria-label="Update attributes/telemetry">
{{ 'import.csv-update-data' | translate }}
</md-checkbox>
</div>
</md-step-body>
<md-step-actions layout="row">
<md-button ng-disabled="$root.loading" ng-click="vm.previousStep(1);">Back
</md-button>
<span flex></span>
<md-button ng-disabled="$root.loading" ng-click="vm.cancel()">
{{ 'action.cancel' | translate }}
</md-button>
<md-button class="md-primary md-raised" ng-disabled="$root.loading" ng-click="vm.nextStep(3);">
{{ 'action.continue' | translate }}
</md-button>
</md-step-actions>
</md-step>
<md-step md-label="{{ 'import.stepper-text.column-type' | translate }}">
<md-step-body>
<form name="vm.theFormStep3">
<tb-table-columns-assignment columns="vm.columnsParam" the-form="vm.theFormStep3"
entity-type="vm.entityType"></tb-table-columns-assignment>
</form>
</md-step-body>
<md-step-actions layout="row">
<md-button ng-disabled="$root.loading" ng-click="vm.previousStep();">Back
</md-button>
<span flex></span>
<md-button ng-disabled="$root.loading" ng-click="vm.cancel()">
{{ 'action.cancel' | translate }}
</md-button>
<md-button class="md-primary md-raised"
ng-disabled="$root.loading || !vm.theFormStep3.$dirty || !vm.theFormStep3.$valid"
ng-click="vm.nextStep(4);">
{{ 'action.continue' | translate }}
</md-button>
</md-step-actions>
</md-step>
<md-step md-label="{{ 'import.stepper-text.creat-entities' | translate }}">
<md-step-body>
<md-progress-linear class="md-warn tb-import-progress" md-mode="determinate"
value="{{vm.progressCreate}}"></md-progress-linear>
</md-step-body>
</md-step>
<md-step md-label="{{ 'import.stepper-text.done' | translate }}">
<md-step-body layout="column">
<div>
<p class="md-body-1" translate translate-values="{count: vm.statistical.create.entity}"
ng-if="vm.statistical.create && vm.statistical.create.entity">import.message.create-entities</p>
<p class="md-body-1" translate translate-values="{count: vm.statistical.update.entity}"
ng-if="vm.statistical.update && vm.statistical.update.entity">import.message.update-entities</p>
<p class="md-body-1" translate translate-values="{count: vm.statistical.error.entity}"
ng-if="vm.statistical.error && vm.statistical.error.entity">import.message.error-entities</p>
</div>
</md-step-body>
<md-step-actions layout="row">
<span flex></span>
<md-button class="md-primary md-raised" ng-disabled="$root.loading" ng-click="vm.nextStep(6);">
{{ 'action.ok' | translate }}
</md-button>
</md-step-actions>
</md-step>
</md-stepper>
</md-dialog-content>
</md-dialog>

20
ui/src/app/import-export/import-dialog.scss

@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import "../../scss/constants.scss";
$previewSize: 100px !default;
.tb-file-select-container {
@ -40,3 +42,21 @@ $previewSize: 100px !default;
top: 50%;
transform: translate(0%, -50%) !important;
}
.tb-table-select{
md-input-container{
margin: 0;
.md-errors-spacer{
min-height: 0;
}
}
}
.md-stepper-indicator-wrapper{
background-color: transparent;
}
.tb-import-progress{
margin: 7px 0;
}

247
ui/src/app/import-export/import-export.service.js

@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable import/no-unresolved, import/default */
/* eslint-disable import/no-unresolved, import/default*/
import importDialogTemplate from './import-dialog.tpl.html';
import importDialogCSVTemplate from './import-dialog-csv.tpl.html';
import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
@ -24,7 +25,7 @@ import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html';
/* eslint-disable no-undef, angular/window-service, angular/document-service */
/*@ngInject*/
export default function ImportExport($log, $translate, $q, $mdDialog, $document, $http, itembuffer, utils, types,
export default function ImportExport($log, $translate, $q, $mdDialog, $document, $http, itembuffer, utils, types, $rootScope,
dashboardUtils, entityService, dashboardService, ruleChainService, widgetService, toast, attributeService) {
@ -41,7 +42,10 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
importWidgetsBundle: importWidgetsBundle,
exportExtension: exportExtension,
importExtension: importExtension,
exportToPc: exportToPc
importEntities: importEntities,
convertCSVToJson: convertCSVToJson,
exportToPc: exportToPc,
createMultiEntity: createMultiEntity
};
return service;
@ -54,11 +58,11 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
var bundleAlias = widgetsBundle.alias;
var isSystem = widgetsBundle.tenantId.id === types.id.nullUid;
widgetService.getBundleWidgetTypes(bundleAlias, isSystem).then(
function success (widgetTypes) {
function success(widgetTypes) {
prepareExport(widgetsBundle);
var widgetsBundleItem = {
widgetsBundle: prepareExport(widgetsBundle),
widgetTypes: []
widgetsBundle: prepareExport(widgetsBundle),
widgetTypes: []
};
for (var t in widgetTypes) {
var widgetType = widgetTypes[t];
@ -68,10 +72,10 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
widgetsBundleItem.widgetTypes.push(prepareExport(widgetType));
}
var name = widgetsBundle.title;
name = name.toLowerCase().replace(/\W/g,"_");
name = name.toLowerCase().replace(/\W/g, "_");
exportToPc(widgetsBundleItem, name + '.json');
},
function fail (rejection) {
function fail(rejection) {
var message = rejection;
if (!message) {
message = $translate.instant('error.unknown-error');
@ -168,7 +172,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
delete widgetType.bundleAlias;
}
var name = widgetType.name;
name = name.toLowerCase().replace(/\W/g,"_");
name = name.toLowerCase().replace(/\W/g, "_");
exportToPc(prepareExport(widgetType), name + '.json');
},
function fail(rejection) {
@ -209,8 +213,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
function validateImportedWidgetType(widgetType) {
if (angular.isUndefined(widgetType.name)
|| angular.isUndefined(widgetType.descriptor))
{
|| angular.isUndefined(widgetType.descriptor)) {
return false;
}
return true;
@ -228,7 +231,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
metadata: prepareRuleChainMetaData(ruleChainMetaData)
};
var name = ruleChain.name;
name = name.toLowerCase().replace(/\W/g,"_");
name = name.toLowerCase().replace(/\W/g, "_");
exportToPc(ruleChainExport, name + '.json');
},
(rejection) => {
@ -253,7 +256,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
function prepareRuleChainMetaData(ruleChainMetaData) {
delete ruleChainMetaData.ruleChainId;
for (var i=0;i<ruleChainMetaData.nodes.length;i++) {
for (var i = 0; i < ruleChainMetaData.nodes.length; i++) {
var node = ruleChainMetaData.nodes[i];
delete node.ruleChainId;
ruleChainMetaData.nodes[i] = prepareExport(node);
@ -305,7 +308,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
function exportWidget(dashboard, sourceState, sourceLayout, widget) {
var widgetItem = itembuffer.prepareWidgetItem(dashboard, sourceState, sourceLayout, widget);
var name = widgetItem.widget.config.title;
name = name.toLowerCase().replace(/\W/g,"_");
name = name.toLowerCase().replace(/\W/g, "_");
exportToPc(prepareExport(widgetItem), name + '.json');
}
@ -415,10 +418,10 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
var aliasIds = Object.keys(entityAliases);
if (aliasIds.length > 0) {
processEntityAliases(entityAliases, aliasIds).then(
function(missingEntityAliases) {
function (missingEntityAliases) {
if (Object.keys(missingEntityAliases).length > 0) {
editMissingAliases($event, [ widget ],
true, 'dashboard.widget-import-missing-aliases-title', missingEntityAliases).then(
editMissingAliases($event, [widget],
true, 'dashboard.widget-import-missing-aliases-title', missingEntityAliases).then(
function success(updatedEntityAliases) {
for (var aliasId in updatedEntityAliases) {
var entityAlias = updatedEntityAliases[aliasId];
@ -483,14 +486,14 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
function success(targetLayout) {
itembuffer.addWidgetToDashboard(dashboard, targetState, targetLayout, widget,
aliasesInfo, onAliasesUpdateFunction, originalColumns, originalSize, -1, -1).then(
function() {
deferred.resolve(
{
widget: widget,
layoutId: targetLayout
}
);
}
function () {
deferred.resolve(
{
widget: widget,
layoutId: targetLayout
}
);
}
);
},
function fail() {
@ -505,7 +508,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
dashboardService.getDashboard(dashboardId).then(
function success(dashboard) {
var name = dashboard.title;
name = name.toLowerCase().replace(/\W/g,"_");
name = name.toLowerCase().replace(/\W/g, "_");
exportToPc(prepareDashboardExport(dashboard), name + '.json');
},
function fail(rejection) {
@ -538,13 +541,13 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
dashboard = dashboardUtils.validateAndUpdateDashboard(dashboard);
var entityAliases = dashboard.configuration.entityAliases;
if (entityAliases) {
var aliasIds = Object.keys( entityAliases );
var aliasIds = Object.keys(entityAliases);
if (aliasIds.length > 0) {
processEntityAliases(entityAliases, aliasIds).then(
function(missingEntityAliases) {
if (Object.keys( missingEntityAliases ).length > 0) {
function (missingEntityAliases) {
if (Object.keys(missingEntityAliases).length > 0) {
editMissingAliases($event, dashboard.configuration.widgets,
false, 'dashboard.dashboard-import-missing-aliases-title', missingEntityAliases).then(
false, 'dashboard.dashboard-import-missing-aliases-title', missingEntityAliases).then(
function success(updatedEntityAliases) {
for (var aliasId in updatedEntityAliases) {
entityAliases[aliasId] = updatedEntityAliases[aliasId];
@ -575,6 +578,34 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
return deferred.promise;
}
function importEntities($event, entityType) {
var deferred = $q.defer();
switch (entityType) {
case types.entityType.device:
openImportDialogCSV($event, entityType, 'device.import', 'device.device-file').then(
function success() {
deferred.resolve();
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
case types.entityType.asset:
openImportDialogCSV($event, entityType, 'asset.import', 'asset.asset-file').then(
function success() {
deferred.resolve();
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
}
}
function saveImportedDashboard(dashboard, deferred) {
dashboardService.saveDashboard(dashboard).then(
function success() {
@ -594,14 +625,13 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
}
function exportExtension(extensionId) {
getExtension(extensionId)
.then(
function success(extension) {
var name = extension.title;
name = name.toLowerCase().replace(/\W/g,"_");
name = name.toLowerCase().replace(/\W/g, "_");
exportToPc(prepareExport(extension), name + '.json');
},
function fail(rejection) {
@ -661,7 +691,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
function validateImportedExtension(configuration) {
if (configuration.length) {
for (let i = 0; i < configuration.length; i++) {
if (angular.isUndefined(configuration[i].configuration) || angular.isUndefined(configuration[i].id )|| angular.isUndefined(configuration[i].type)) {
if (angular.isUndefined(configuration[i].configuration) || angular.isUndefined(configuration[i].id) || angular.isUndefined(configuration[i].type)) {
return false;
}
}
@ -692,7 +722,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
var aliasId = aliasIds[index];
var entityAlias = entityAliases[aliasId];
entityService.checkEntityAlias(entityAlias).then(
function(result) {
function (result) {
if (result) {
checkNextEntityAliasOrComplete(index, aliasIds, entityAliases, missingEntityAliases, deferred);
} else {
@ -732,6 +762,124 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
return deferred.promise;
}
function splitCSV(str, sep) {
for (var foo = str.split(sep = sep || ","), x = foo.length - 1, tl; x >= 0; x--) {
if (foo[x].replace(/"\s+$/, '"').charAt(foo[x].length - 1) == '"') {
if ((tl = foo[x].replace(/^\s+"/, '"')).length > 1 && tl.charAt(0) == '"') {
foo[x] = foo[x].replace(/^\s*"|"\s*$/g, '').replace(/""/g, '"');
} else if (x) {
foo.splice(x - 1, 2, [foo[x - 1], foo[x]].join(sep));
} else foo = foo.shift().split(sep).concat(foo);
} else foo[x].replace(/""/g, '"');
}
return foo;
}
function isNumeric(str) {
str = str.replace(',', '.');
return !isNaN(parseFloat(str)) && isFinite(str);
}
function convertStringToJSType(str) {
if (isNumeric(str.replace(',', '.'))) {
return parseFloat(str.replace(',', '.'));
}
if (str.search(/^(true|false)$/im) === 0) {
return str === 'true';
}
if (str === "") {
return null;
}
return str;
}
function convertCSVToJson(csvdata, config) {
config = config || {};
const delim = config.delim || ",";
const header = config.header || false;
let result = {};
let csvlines = csvdata.split(/[\r\n]+/);
let csvheaders = splitCSV(csvlines[0], delim);
if (csvheaders.length < 2) {
toast.showError($translate.instant('import.import-csv-number-columns-error'));
return -1;
}
let csvrows = header ? csvlines.slice(1, csvlines.length) : csvlines;
result.headers = csvheaders;
result.rows = [];
for (let r in csvrows) {
if (csvrows.hasOwnProperty(r)) {
let row = csvrows[r];
if (row.length === 0)
break;
let rowitems = splitCSV(row, delim);
if (rowitems.length !== result.headers.length) {
toast.showError($translate.instant('import.import-csv-invalid-format-error', {line: (header ? result.rows.length + 2: result.rows.length + 1)}));
return -1;
}
for (let i = 0; i < rowitems.length; i++) {
rowitems[i] = convertStringToJSType(rowitems[i]);
}
result.rows.push(rowitems);
}
}
return result;
}
function sumObject(obj1, obj2){
Object.keys(obj2).map(function(key) {
if (angular.isObject(obj2[key])) {
obj1[key] = obj1[key] || {};
angular.merge(obj1[key], sumObject(obj1[key], obj2[key]));
} else {
obj1[key] = (obj1[key] || 0) + obj2[key];
}
});
return obj1;
}
function qAllWithProgress(promises) {
promises.forEach(function(p) {
p.then(function() {
$rootScope.$broadcast('createImportEntityCompleted', {});
});
});
return $q.all(promises);
}
function createMultiEntity(arrayData, entityType, updateData, config) {
let partSize = 100;
partSize = arrayData.length > partSize ? partSize : arrayData.length;
let allPromise = [];
let statisticalInfo = {};
let deferred = $q.defer();
for(let i = 0; i < partSize; i++){
const promise = entityService.saveEntityParameters(entityType, arrayData[i], updateData, config);
allPromise.push(promise);
}
qAllWithProgress(allPromise).then(function success(response) {
for (let i = 0; i < response.length; i++){
statisticalInfo = sumObject(statisticalInfo, response[i]);
}
arrayData.splice(0, partSize);
if(arrayData.length > 0){
deferred.resolve(createMultiEntity(arrayData, entityType, updateData, config).then(function (response) {
return sumObject(statisticalInfo, response);
}));
} else {
deferred.resolve(statisticalInfo);
}
});
return deferred.promise;
}
// Common functions
function prepareExport(data) {
@ -771,8 +919,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(blob, filename);
}
else{
} else {
var e = document.createEvent('MouseEvents'),
a = document.createElement('a');
@ -807,6 +954,34 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
return deferred.promise;
}
function openImportDialogCSV($event, entityType, importTitle, importFileLabel) {
var deferred = $q.defer();
$mdDialog.show({
controller: 'ImportDialogCSVController',
controllerAs: 'vm',
templateUrl: importDialogCSVTemplate,
locals: {
importTitle: importTitle,
importFileLabel: importFileLabel,
entityType: entityType
},
parent: angular.element($document[0].body),
onComplete: fixedDialogSize,
multiple: true,
fullscreen: true,
targetEvent: $event
}).then(function () {
deferred.resolve();
}, function () {
deferred.reject();
});
return deferred.promise;
}
function fixedDialogSize(scope, element) {
let dialogElement = element[0].getElementsByTagName('md-dialog');
dialogElement[0].style.width = dialogElement[0].offsetWidth + 2 + "px";
}
}
/* eslint-enable no-undef, angular/window-service, angular/document-service */

4
ui/src/app/import-export/index.js

@ -15,9 +15,13 @@
*/
import ImportExport from './import-export.service';
import ImportDialogController from './import-dialog.controller';
import ImportDialogCSVController from './import-dialog-csv.controller';
import TableColumnsAssignment from './table-columns-assignment.directive';
export default angular.module('thingsboard.importexport', [])
.factory('importExport', ImportExport)
.controller('ImportDialogController', ImportDialogController)
.controller('ImportDialogCSVController', ImportDialogCSVController)
.directive('tbTableColumnsAssignment', TableColumnsAssignment)
.name;

93
ui/src/app/import-export/table-columns-assignment.directive.js

@ -0,0 +1,93 @@
/*
* Copyright © 2016-2019 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable import/no-unresolved, import/default */
import tableColumnsAssignment from './table-columns-assignment.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function TableColumnsAssignment() {
return {
restrict: "E",
scope: true,
bindToController: {
theForm: '=?',
columns: '=',
entityType: '=',
},
templateUrl: tableColumnsAssignment,
controller: TableColumnsAssignmentController,
controllerAs: 'vm'
};
}
/*@ngInject*/
function TableColumnsAssignmentController($scope, types, $timeout) {
var vm = this;
vm.columnTypes = {};
vm.columnTypes.name = types.importEntityColumnType.name;
vm.columnTypes.type = types.importEntityColumnType.type;
switch (vm.entityType) {
case types.entityType.device:
vm.columnTypes.sharedAttribute = types.importEntityColumnType.sharedAttribute;
vm.columnTypes.serverAttribute = types.importEntityColumnType.serverAttribute;
vm.columnTypes.timeseries = types.importEntityColumnType.timeseries;
vm.columnTypes.accessToken = types.importEntityColumnType.accessToken;
break;
case types.entityType.asset:
vm.columnTypes.serverAttribute = types.importEntityColumnType.serverAttribute;
vm.columnTypes.timeseries = types.importEntityColumnType.timeseries;
break;
}
$scope.$watch('vm.columns', function(newVal){
if (newVal) {
var isSelectName = false;
var isSelectType = false;
var isSelectCredentials = false;
for (var i = 0; i < newVal.length; i++) {
switch (newVal[i].type) {
case types.importEntityColumnType.name.value:
isSelectName = true;
break;
case types.importEntityColumnType.type.value:
isSelectType = true;
break;
case types.importEntityColumnType.accessToken.value:
isSelectCredentials = true;
break;
}
}
if(isSelectName && isSelectType) {
vm.theForm.$setDirty();
} else {
vm.theForm.$setPristine();
}
$timeout(function () {
vm.columnTypes.name.disable = isSelectName;
vm.columnTypes.type.disable = isSelectType;
if (angular.isDefined(vm.columnTypes.accessToken)) {
vm.columnTypes.accessToken.disable = isSelectCredentials;
}
});
}
}, true);
}

55
ui/src/app/import-export/table-columns-assignment.tpl.html

@ -0,0 +1,55 @@
<!--
Copyright © 2016-2019 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<md-table-container class="tb-table-select">
<table md-table>
<thead md-head>
<tr md-row>
<th md-column>&nbsp</th>
<th md-column>{{ 'import.column-example' | translate }}</th>
<th md-column>{{ 'import.column-type.column-type' | translate }}</th>
<th md-column>{{ 'import.column-key' | translate }}</th>
</tr>
</thead>
<tbody md-body>
<tr md-row ng-repeat="column in vm.columns track by $index">
<td md-cell>{{$index + 1}}</td>
<td md-cell>{{column.sampleData}}</td>
<td md-cell>
<md-select ng-model="column.type" required name="columnType"
aria-label="{{ 'import.column-type.column-type' | translate }}">
<md-option ng-repeat="type in vm.columnTypes" ng-value="type.value" ng-disabled="type.disable">
{{type.name | translate}}
</md-option>
</md-select>
</td>
<td md-cell>
<md-input-container md-no-float
ng-if="column.type != vm.columnTypes.name.value &&
column.type != vm.columnTypes.type.value &&
column.type != vm.columnTypes.accessToken.value">
<input required name="columnKeyName"
placeholder="{{ 'import.column-value' | translate }}"
ng-model="column.key"
aria-label="{{ 'import.column-value' | translate }}">
</md-input-container>
</td>
</tr>
</tbody>
</table>
<md-divider></md-divider>
</md-table-container>

46
ui/src/app/locale/locale.constant-en_US.json

@ -48,7 +48,8 @@
"paste-reference": "Paste reference",
"import": "Import",
"export": "Export",
"share-via": "Share via {{provider}}"
"share-via": "Share via {{provider}}",
"continue": "Continue"
},
"aggregation": {
"aggregation": "Aggregation",
@ -246,7 +247,9 @@
"select-asset": "Select asset",
"no-assets-matching": "No assets matching '{{entity}}' were found.",
"asset-required": "Asset is required",
"name-starts-with": "Asset name starts with"
"name-starts-with": "Asset name starts with",
"import": "Import assets",
"asset-file": "Asset file"
},
"attribute": {
"attributes": "Attributes",
@ -665,7 +668,9 @@
"is-gateway": "Is gateway",
"public": "Public",
"device-public": "Device is public",
"select-device": "Select device"
"select-device": "Select device",
"import": "Import device",
"device-file": "Device file"
},
"dialog": {
"close": "Close dialog"
@ -1093,7 +1098,40 @@
},
"import": {
"no-file": "No file selected",
"drop-file": "Drop a JSON file or click to select a file to upload."
"drop-file": "Drop a JSON file or click to select a file to upload.",
"drop-file-csv": "Drop a CSV file or click to select a file to upload.",
"column-value": "Value",
"column-title": "Title",
"column-example": "Example value data",
"column-key": "Attribute/telemetry key",
"csv-delimiter": "CSV delimiter",
"csv-first-line-header": "First line contains column names",
"csv-update-data": "Update attributes/telemetry",
"import-csv-number-columns-error": "A file should contain at least two columns",
"import-csv-invalid-format-error": "Invalid file format. Line: '{{line}}'",
"column-type": {
"name": "Name",
"type": "Type",
"column-type": "Column type",
"client-attribute": "Client attribute",
"shared-attribute": "Shared attribute",
"server-attribute": "Server attribute",
"timeseries": "Timeseries",
"entity-field": "Entity field",
"access-token": "Access token"
},
"stepper-text":{
"select-file": "Select a file",
"configuration": "Import configuration",
"column-type": "Select columns type",
"creat-entities": "Creating new entities",
"done": "Done"
},
"message": {
"create-entities": "{{count}} new entities were successfully created.",
"update-entities": "{{count}} entities were successfully updated.",
"error-entities": "There was an error creating {{count}} entities."
}
},
"item": {
"selected": "Selected"

Loading…
Cancel
Save