mirror of
https://github.com/nextcloud/passman.git
synced 2025-10-18 09:27:02 +08:00
Merge branch 'import-export-improvements'
Signed-off-by: binsky <timo@binsky.org>
This commit is contained in:
commit
0e09b4f579
11 changed files with 234 additions and 151 deletions
|
@ -1276,10 +1276,10 @@ h3 {
|
|||
.setting-group input[type="text"], .setting-group input[type="password"], .setting-group textarea {
|
||||
width: 100%; }
|
||||
|
||||
.setting-group.margin-bottom-25 {
|
||||
.setting-group.margin-bottom-25, .margin-bottom-25 {
|
||||
margin-bottom: 25px; }
|
||||
|
||||
.setting-group.margin-bottom-10 {
|
||||
.setting-group.margin-bottom-10, .margin-bottom-10 {
|
||||
margin-bottom: 10px; }
|
||||
|
||||
.display-grid {
|
||||
|
@ -1295,7 +1295,7 @@ h3 {
|
|||
label[for=confirmVaultPWChange] {
|
||||
margin-bottom: 10px; }
|
||||
|
||||
label[for=confirmVaultDelete] {
|
||||
label[for=confirmVaultDelete], label[for=skipFirstRow] {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px; }
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -32,8 +32,8 @@
|
|||
* Controller of the passmanApp
|
||||
*/
|
||||
angular.module('passmanApp')
|
||||
.controller('GenericCsvImportCtrl', ['$scope', 'CredentialService', 'FileService', 'EncryptService', '$translate', '$q',
|
||||
function ($scope, CredentialService, FileService, EncryptService, $translate, $q) {
|
||||
.controller('GenericCsvImportCtrl', ['$scope', '$rootScope', 'CredentialService', 'FileService', 'EncryptService', '$translate', '$q',
|
||||
function ($scope, $rootScope, CredentialService, FileService, EncryptService, $translate, $q) {
|
||||
$scope.hello = 'world';
|
||||
|
||||
$scope.credentialProperties = [
|
||||
|
@ -45,17 +45,27 @@
|
|||
{
|
||||
label: 'Username',
|
||||
prop: 'username',
|
||||
matching: ['username', 'user', 'login', 'login name']
|
||||
matching: ['username', 'user', 'login', 'login name', 'login_username']
|
||||
},
|
||||
{
|
||||
label: 'Password',
|
||||
prop: 'password',
|
||||
matching: ['password', 'pass', 'pw']
|
||||
matching: ['password', 'pass', 'pw', 'login_password']
|
||||
},
|
||||
{
|
||||
label: 'TOTP Secret',
|
||||
label: 'TOTP Secret or Object',
|
||||
prop: 'otp',
|
||||
matching: ['totp']
|
||||
matching: ['otp', 'otp_object', 'totp', 'login_totp']
|
||||
},
|
||||
{
|
||||
label: 'Email',
|
||||
prop: 'email',
|
||||
matching: ['email', 'mail']
|
||||
},
|
||||
{
|
||||
label: 'Notes',
|
||||
prop: 'description',
|
||||
matching: ['notes', 'description', 'comments']
|
||||
},
|
||||
{
|
||||
label: 'Custom field',
|
||||
|
@ -71,24 +81,45 @@
|
|||
prop: 'files',
|
||||
matching: ['files']
|
||||
},
|
||||
{
|
||||
label: 'Notes',
|
||||
prop: 'description',
|
||||
matching: ['notes', 'description', 'comments']
|
||||
},
|
||||
{
|
||||
label: 'Email',
|
||||
prop: 'email',
|
||||
matching: ['email', 'mail']
|
||||
},
|
||||
{
|
||||
label: 'URL',
|
||||
prop: 'url',
|
||||
matching: ['website', 'url', 'fulladdress', 'site', 'web site']
|
||||
matching: ['website', 'url', 'fulladdress', 'site', 'web site', 'login_uri']
|
||||
},
|
||||
{
|
||||
label: 'Tags',
|
||||
prop: 'tags'
|
||||
prop: 'tags',
|
||||
matching: ['tags', 'folder']
|
||||
},
|
||||
{
|
||||
label: 'Created',
|
||||
prop: 'created',
|
||||
matching: ['created', 'creation']
|
||||
},
|
||||
{
|
||||
label: 'Changed',
|
||||
prop: 'changed',
|
||||
matching: ['changed', 'edited']
|
||||
},
|
||||
{
|
||||
label: 'Expire time',
|
||||
prop: 'expire_time',
|
||||
matching: ['expire_time', 'expire', 'expires', 'expires_at']
|
||||
},
|
||||
{
|
||||
label: 'Delete time',
|
||||
prop: 'delete_time',
|
||||
matching: ['delete_time', 'delete', 'deleted_at']
|
||||
},
|
||||
{
|
||||
label: 'Icon',
|
||||
prop: 'icon',
|
||||
matching: ['icon', 'favicon']
|
||||
},
|
||||
{
|
||||
label: 'Compromised',
|
||||
prop: 'compromised',
|
||||
matching: ['compromised']
|
||||
},
|
||||
{
|
||||
label: 'Ignored',
|
||||
|
@ -99,14 +130,23 @@
|
|||
return {text: t};
|
||||
};
|
||||
var rowToCredential = async function (row) {
|
||||
var _credential = PassmanImporter.newCredential();
|
||||
for(var k = 0; k < $scope.import_fields.length; k++){
|
||||
var field = $scope.import_fields[k];
|
||||
let _credential = PassmanImporter.newCredential();
|
||||
for(let k = 0; k < $scope.import_fields.length; k++){
|
||||
const field = $scope.import_fields[k];
|
||||
if(field){
|
||||
if(field === 'otp'){
|
||||
_credential.otp.secret = row[k];
|
||||
if (typeof row[k] === 'object' || row[k].includes('{"')) {
|
||||
const otpobj = JSON.parse(row[k]);
|
||||
if (typeof otpobj === 'object' && otpobj.secret !== undefined && otpobj.algorithm !== undefined && otpobj.period !== undefined && otpobj.digits !== undefined) {
|
||||
_credential.otp = otpobj;
|
||||
} else if (otpobj.secret !== undefined) {
|
||||
_credential.otp.secret = otpobj.secret;
|
||||
}
|
||||
} else if (row[k] !== '{}') {
|
||||
_credential.otp.secret = row[k];
|
||||
}
|
||||
} else if(field === 'custom_field'){
|
||||
var key = ($scope.matched) ? $scope.parsed_csv[0][k] : 'Custom field '+ k;
|
||||
const key = ($scope.matched) ? $scope.parsed_csv[0][k] : 'Custom field '+ k;
|
||||
_credential.custom_fields.push({
|
||||
'label': key,
|
||||
'value': row[k],
|
||||
|
@ -116,46 +156,44 @@
|
|||
if (row[k] !== undefined && (typeof row[k] === 'string' || row[k] instanceof String) && row[k].length > 1){
|
||||
try {
|
||||
row[k] = JSON.parse(row[k]);
|
||||
for(let i = 0; k < row[k].length; i++){
|
||||
_credential.custom_fields.push({
|
||||
'label': row[k][i].label,
|
||||
'secret': row[k][i].secret,
|
||||
'field_type': row[k][i].field_type,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore row[k], it contains no valid json data
|
||||
// console.error(e);
|
||||
console.error(e);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
for(let j = 0; j < row[k].length; j++){
|
||||
if (row[k][j].field_type === 'file'){
|
||||
var _file = {
|
||||
filename: row[k][j].value.filename,
|
||||
size: row[k][j].value.size,
|
||||
mimetype: row[k][j].value.mimetype,
|
||||
data: row[k][j].value.file_data
|
||||
};
|
||||
|
||||
row[k][j].value = await FileService.uploadFile(_file).then(FileService.getEmptyFileWithDecryptedFilename);
|
||||
}
|
||||
for(let j = 0; j < row[k].length; j++){
|
||||
if (row[k][j].field_type === 'file'){
|
||||
const _file = {
|
||||
filename: row[k][j].value.filename,
|
||||
size: row[k][j].value.size,
|
||||
mimetype: row[k][j].value.mimetype,
|
||||
data: row[k][j].value.file_data ? row[k][j].value.file_data : row[k][j].value.data
|
||||
};
|
||||
if (_file.data === undefined) {
|
||||
console.error('Unable to parse file data from ', row[k][j]);
|
||||
$scope.log.push('Unable to parse file data from file ' + _file.filename);
|
||||
continue;
|
||||
}
|
||||
_credential.custom_fields.push(row[k][j]);
|
||||
row[k][j].value = await FileService.uploadFile(_file).then(FileService.getEmptyFileWithDecryptedFilename);
|
||||
}
|
||||
_credential.custom_fields.push(row[k][j]);
|
||||
}
|
||||
} else if(field === 'files'){
|
||||
if (row[k] !== undefined && (typeof row[k] === 'string' || row[k] instanceof String) && row[k].length > 1){
|
||||
try {
|
||||
row[k] = JSON.parse(row[k]);
|
||||
for(let i = 0; k < row[k].length; i++){
|
||||
_credential.files.push({
|
||||
for(let i = 0; i < row[k].length; i++){
|
||||
_credential.files.push(await FileService.uploadFile({
|
||||
filename: row[k][i].filename,
|
||||
size: row[k][i].size,
|
||||
mimetype: row[k][i].mimetype
|
||||
});
|
||||
mimetype: row[k][i].mimetype,
|
||||
data: row[k][i].file_data ? row[k][i].file_data : row[k][i].data
|
||||
}).then(FileService.getEmptyFileWithDecryptedFilename));
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore row[k], it contains no valid json data
|
||||
// console.error(e);
|
||||
console.error(e);
|
||||
}
|
||||
} else {
|
||||
for(let j = 0; j < row[k].length; j++){
|
||||
|
@ -163,15 +201,25 @@
|
|||
filename: row[k][j].filename,
|
||||
size: row[k][j].size,
|
||||
mimetype: row[k][j].mimetype,
|
||||
data: row[k][j].file_data
|
||||
data: row[k][j].file_data ? row[k][j].file_data : row[k][j].data
|
||||
}).then(FileService.getEmptyFileWithDecryptedFilename));
|
||||
}
|
||||
}
|
||||
} else if(field === 'tags'){
|
||||
if( row[k]) {
|
||||
var tags = row[k].split(',');
|
||||
if(row[k] && row[k] !== '' && row[k] !== '[]') {
|
||||
if (row[k].startsWith('[') && row[k].endsWith(']')) {
|
||||
row[k] = row[k].substring(1, row[k].length - 1);
|
||||
}
|
||||
const tags = row[k].split(',');
|
||||
_credential.tags = tags.map(tagMapper);
|
||||
}
|
||||
} else if(field === 'compromised'){
|
||||
_credential[field] = (row[k] === 'true' || row[k] === '1');
|
||||
} else if (field === 'created' || field === 'changed' || field === 'expire_time' || field === 'delete_time') {
|
||||
const num = parseInt(row[k]);
|
||||
if (!isNaN(num)) {
|
||||
_credential[field] = num;
|
||||
}
|
||||
} else{
|
||||
_credential[field] = row[k];
|
||||
}
|
||||
|
@ -181,19 +229,19 @@
|
|||
};
|
||||
|
||||
|
||||
$scope.inspectCredential = function (row) {
|
||||
$scope.inspected_credential = rowToCredential(row);
|
||||
$scope.inspectCredential = async function (row) {
|
||||
$scope.inspected_credential = await rowToCredential(row);
|
||||
};
|
||||
|
||||
$scope.csvLoaded = function (file) {
|
||||
$scope.import_fields = [];
|
||||
$scope.inspected_credential = false;
|
||||
$scope.matched = false;
|
||||
$scope.inspected_credential = {};
|
||||
$scope.atLeastlabelMatched = false;
|
||||
var file_data = file.data.split(',');
|
||||
file_data = decodeURIComponent(escape(window.atob(file_data[1])));
|
||||
/** global: Papa */
|
||||
Papa.parse(file_data, {
|
||||
complete: function(results) {
|
||||
complete: async function(results) {
|
||||
if(results.data) {
|
||||
for(var i = 0; i < results.data[0].length; i++){
|
||||
var propName = results.data[0][i];
|
||||
|
@ -203,13 +251,15 @@
|
|||
if(credentialProperty.matching){
|
||||
if(credentialProperty.matching.indexOf(propName.toLowerCase()) !== -1){
|
||||
$scope.import_fields[i] = credentialProperty.prop;
|
||||
$scope.matched = true;
|
||||
if (credentialProperty.prop === 'label') {
|
||||
$scope.atLeastlabelMatched = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if($scope.matched){
|
||||
$scope.inspectCredential(results.data[1]);
|
||||
if($scope.atLeastlabelMatched){
|
||||
await $scope.inspectCredential(results.data[1]);
|
||||
}
|
||||
|
||||
for(var j = 0; j < results.data.length; j++){
|
||||
|
@ -242,6 +292,7 @@
|
|||
};
|
||||
$scope.log.push($translate.instant('done'));
|
||||
$scope.importing = false;
|
||||
$rootScope.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -274,5 +325,12 @@
|
|||
var start = ($scope.skipFirstRow) ? 1 : 0;
|
||||
$scope.inspectCredential($scope.parsed_csv[start]);
|
||||
};
|
||||
|
||||
$scope.fileLoadError = function (file) {
|
||||
console.error($translate.instant('error.loading.file'), file);
|
||||
};
|
||||
$scope.fileSelectProgress = function () {
|
||||
|
||||
};
|
||||
}]);
|
||||
}());
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
replace: true,
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
credential: '=credentialTemplate'
|
||||
credential: '='
|
||||
},
|
||||
|
||||
link: function (scope, element, attrs) {
|
||||
|
|
|
@ -34,33 +34,50 @@ PassmanExporter.csv.export = function (credentials, FileService, EncryptService,
|
|||
/** global: C_Promise */
|
||||
return new C_Promise(function () {
|
||||
PassmanExporter.getCredentialsWithFiles(credentials, FileService, EncryptService, _log, $translate).then((function(){
|
||||
var headers = ['label', 'username', 'password', 'email', 'description', 'tags', 'url', 'custom_fields', 'files'];
|
||||
var file_data = '"' + headers.join('","') + '"\n';
|
||||
for (var i = 0; i < credentials.length; i++) {
|
||||
var _credential = credentials[i];
|
||||
var row_data = [];
|
||||
for (var h = 0; h < headers.length; h++) {
|
||||
var field = headers[h];
|
||||
const headers = [
|
||||
'label',
|
||||
'description',
|
||||
'created',
|
||||
'changed',
|
||||
'tags',
|
||||
'email',
|
||||
'icon',
|
||||
'username',
|
||||
'password',
|
||||
'url',
|
||||
'renew_interval',
|
||||
'expire_time',
|
||||
'delete_time',
|
||||
'files',
|
||||
'custom_fields',
|
||||
'otp',
|
||||
'compromised',
|
||||
'hidden'
|
||||
];
|
||||
let file_data = '"' + headers.join('","') + '"\n';
|
||||
for (let i = 0; i < credentials.length; i++) {
|
||||
const _credential = credentials[i];
|
||||
let row_data = [];
|
||||
for (const field of headers) {
|
||||
if (field === 'tags') {
|
||||
var _tags = [];
|
||||
for (var t = 0; t < _credential[field].length; t++) {
|
||||
_tags.push(_credential[field][t].text);
|
||||
let _tags = [];
|
||||
for (const tag_field of _credential[field]) {
|
||||
_tags.push(tag_field.text);
|
||||
}
|
||||
var tag_data = '[' + _tags.join(",") + ']';
|
||||
const tag_data = '[' + _tags.join(",") + ']';
|
||||
row_data.push('"' + tag_data.replaceAll('"', '""') + '"');
|
||||
}
|
||||
else if (field == 'custom_fields' || field == 'files') {
|
||||
var _fields = JSON.stringify(_credential[field]);
|
||||
_fields = _fields.replaceAll('"', '""');
|
||||
row_data.push('"' + _fields + '"');
|
||||
}
|
||||
else {
|
||||
var data = _credential[field],
|
||||
value = data === null ? '':data.replaceAll('"', '""');
|
||||
row_data.push('"' + value + '"');
|
||||
} else if (field === 'custom_fields' || field === 'files' || field === 'otp' || field === 'icon') {
|
||||
let _fields = JSON.stringify(_credential[field]);
|
||||
_fields = _fields.replaceAll('"', '""');
|
||||
row_data.push('"' + _fields + '"');
|
||||
} else {
|
||||
let data = _credential[field];
|
||||
data = typeof data === 'number' || typeof data === 'boolean' ? "" + data : data;
|
||||
const value = (data === null || data === undefined) ? '' : data.replaceAll('"', '""');
|
||||
row_data.push('"' + value + '"');
|
||||
}
|
||||
}
|
||||
var progress = {
|
||||
let progress = {
|
||||
percent: i / credentials.length * 100,
|
||||
loaded: i,
|
||||
total: credentials.length
|
||||
|
|
|
@ -34,9 +34,9 @@ PassmanExporter.json.export = function (credentials, FileService, EncryptService
|
|||
/** global: C_Promise */
|
||||
return new C_Promise(function () {
|
||||
PassmanExporter.getCredentialsWithFiles(credentials, FileService, EncryptService, _log, $translate).then((function(){
|
||||
var _output = [];
|
||||
for (var i = 0; i < credentials.length; i++) {
|
||||
var _credential = angular.copy(credentials[i]);
|
||||
let _output = [];
|
||||
for (let i = 0; i < credentials.length; i++) {
|
||||
let _credential = angular.copy(credentials[i]);
|
||||
|
||||
delete _credential.vault_key;
|
||||
delete _credential.vault_id;
|
||||
|
@ -44,14 +44,14 @@ PassmanExporter.json.export = function (credentials, FileService, EncryptService
|
|||
|
||||
_output.push(_credential);
|
||||
|
||||
var progress = {
|
||||
let progress = {
|
||||
percent: i / credentials.length * 100,
|
||||
loaded: i,
|
||||
total: credentials.length
|
||||
};
|
||||
this.call_progress(progress);
|
||||
}
|
||||
var file_data = JSON.stringify(_output);
|
||||
let file_data = JSON.stringify(_output);
|
||||
this.call_then();
|
||||
download(file_data, 'passman-export.json');
|
||||
}).bind(this)).progress(function() {
|
||||
|
|
|
@ -75,44 +75,49 @@ if (!window['PassmanExporter']) {
|
|||
}
|
||||
}).bind(this);
|
||||
|
||||
for (var i = 0; i < credentials.length; i++) {
|
||||
|
||||
var item = credentials[i];
|
||||
for (let i = 0; i < credentials.length; i++) {
|
||||
const credential = credentials[i];
|
||||
|
||||
// Custom fields
|
||||
for (c = 0; c < item.custom_fields.length; c++) {
|
||||
var cf = item.custom_fields[c];
|
||||
for (let c = 0; c < credential.custom_fields.length; c++) {
|
||||
const cf = credential.custom_fields[c];
|
||||
if (cf.field_type === 'file') {
|
||||
const file = cf.value;
|
||||
if (file !== "undefined" && file !== undefined && file !== null && file.file_id !== undefined) {
|
||||
this.parent.total++;
|
||||
this.parent.fileGUID_cred[file.guid] = {
|
||||
cred_pos: i,
|
||||
on: 'custom_fields',
|
||||
at: c
|
||||
};
|
||||
|
||||
this.parent.FS.getFile(file).then((function (data) {
|
||||
this.parent.step(data);
|
||||
}).bind(this), (function (error) {
|
||||
this.parent.stepFailed(error);
|
||||
}).bind(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also get all files
|
||||
for (let c = 0; c < credential.files.length; c++) {
|
||||
const file = credential.files[c];
|
||||
if (file !== "undefined" && file !== undefined && file !== null && file.file_id !== undefined) {
|
||||
this.parent.total++;
|
||||
this.parent.fileGUID_cred[cf.value.guid] = {
|
||||
this.parent.fileGUID_cred[file.guid] = {
|
||||
cred_pos: i,
|
||||
on: 'custom_fields',
|
||||
on: 'files',
|
||||
at: c
|
||||
};
|
||||
|
||||
this.parent.FS.getFile(cf.value).then((function (data) {
|
||||
this.parent.FS.getFile(file).then((function (data) {
|
||||
this.parent.step(data);
|
||||
}).bind(this), (function (error) {
|
||||
this.parent.stepFailed(error);
|
||||
}).bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
// Also get all files
|
||||
for (var c = 0; c < item.files.length; c++) {
|
||||
this.parent.total++;
|
||||
this.parent.fileGUID_cred[item.files[c].guid] = {
|
||||
cred_pos: i,
|
||||
on: 'files',
|
||||
at: c
|
||||
};
|
||||
|
||||
this.parent.FS.getFile(item.files[c]).then((function (data) {
|
||||
this.parent.step(data);
|
||||
}).bind(this), (function (error) {
|
||||
this.parent.stepFailed(error);
|
||||
}).bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
// We have finished downloading everything, so let's hand over job to somewhere else!
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -98,10 +98,10 @@ h3 {
|
|||
width: 100%;
|
||||
}
|
||||
}
|
||||
.setting-group.margin-bottom-25 {
|
||||
.setting-group.margin-bottom-25, .margin-bottom-25 {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
.setting-group.margin-bottom-10 {
|
||||
.setting-group.margin-bottom-10, .margin-bottom-10 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.display-grid {
|
||||
|
@ -117,7 +117,7 @@ h3 {
|
|||
label[for=confirmVaultPWChange] {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
label[for=confirmVaultDelete] {
|
||||
label[for=confirmVaultDelete], label[for=skipFirstRow] {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<div ng-controller="GenericCsvImportCtrl">
|
||||
<div class="row">
|
||||
<div class="row margin-bottom-25">
|
||||
<div class="col-xs-12 col-md-3">
|
||||
<div>{{ 'select.csv' | translate}}
|
||||
<input type="file" file-select accept=".csv"
|
||||
success="csvLoaded">
|
||||
success="csvLoaded" error="fileLoadError" progress="fileSelectProgress">
|
||||
</div>
|
||||
<div ng-show="parsed_csv">
|
||||
<span translate="parsed.csv.rows" translate-value-rows="{{ parsed_csv.length }}">
|
||||
|
@ -11,7 +11,8 @@
|
|||
</span>
|
||||
</div>
|
||||
<div ng-show="parsed_csv">
|
||||
<input type="checkbox" ng-model="skipFirstRow"> {{ 'skip.first.row' | translate}}
|
||||
<input id="skipFirstRow" class="checkbox" type="checkbox" ng-model="skipFirstRow">
|
||||
<label for="skipFirstRow">{{'skip.first.row' | translate}}</label>
|
||||
</div>
|
||||
<div ng-show="import_fields.indexOf('label') === -1 && parsed_csv">
|
||||
<b>{{ 'import.csv.label.req' | translate}}</b>
|
||||
|
@ -25,40 +26,42 @@
|
|||
<div progress-bar="import_progress.progress" index="import_progress.loaded" total="import_progress.total"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div ng-if="log" class="import_log">
|
||||
<textarea id="import_log" auto-scroll="log">{{log.join('\n')}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-9" ng-show="parsed_csv">
|
||||
<div class="col-xs-12 col-md-9">
|
||||
<div ng-if="log" class="import_log">
|
||||
<textarea id="import_log" auto-scroll="log">{{log.join('\n')}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 display-grid" ng-show="parsed_csv">
|
||||
<b>{{ 'first.five.lines' | translate }}</b><br />
|
||||
{{ 'assign.column' | translate }}
|
||||
<div class="import-table-outter">
|
||||
<table class="import-table">
|
||||
<tr ng-repeat="line in parsed_csv | limitTo:5">
|
||||
<td class="inspect"><i class="fa fa-search"
|
||||
ng-click="inspectCredential(line)"
|
||||
ng-if="($index > 0 && matched && import_fields.length > 0) || ($index >= 0 && !matched && import_fields.length > 0)"></i>
|
||||
</td>
|
||||
<td ng-repeat="prop in line track by $index">
|
||||
{{line[$index]}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-repeat="line in parsed_csv | limitTo:1">
|
||||
<td></td>
|
||||
<td ng-repeat="prop in line track by $index">
|
||||
<select ng-model="import_fields[$index]" ng-change="updateExample()"
|
||||
ng-options="property.prop as property.label for property in credentialProperties">
|
||||
<div class="import-table-outter margin-bottom-25">
|
||||
<table class="import-table">
|
||||
<tr ng-repeat="line in parsed_csv | limitTo:5">
|
||||
<td class="inspect"><i class="fa fa-search"
|
||||
ng-click="inspectCredential(line)"
|
||||
ng-if="($index > 0 && matched && import_fields.length > 0) || ($index >= 0 && !matched && import_fields.length > 0)"></i>
|
||||
</td>
|
||||
<td ng-repeat="prop in line track by $index">
|
||||
{{"" + prop | limitTo: 100}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-repeat="line in parsed_csv | limitTo:1">
|
||||
<td></td>
|
||||
<td ng-repeat="prop in line track by $index">
|
||||
<select ng-model="import_fields[$index]" ng-change="updateExample()"
|
||||
ng-options="property.prop as property.label for property in credentialProperties">
|
||||
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div ng-show="inspected_credential && import_fields.length > 0">
|
||||
<b>{{ 'example.credential' | translate}}</b>
|
||||
<div credential-template="inspected_credential" show-label>
|
||||
<div credential-template credential="inspected_credential" show-label>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -158,7 +158,7 @@
|
|||
<h2 class="sidebar-label">{{selectedCredential.label}}</h2>
|
||||
</div>
|
||||
|
||||
<div credential-template="selectedCredential"></div>
|
||||
<div credential-template credential="selectedCredential"></div>
|
||||
|
||||
<div ng-show="selectedCredential">
|
||||
<div>
|
||||
|
|
Loading…
Add table
Reference in a new issue