20 changed files with 1187 additions and 67 deletions
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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); |
|||
} |
|||
@ -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> </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> |
|||
Loading…
Reference in new issue