mirror of
https://github.com/nextcloud/passman.git
synced 2025-12-11 14:16:07 +08:00
Merge branch 'master' into usePromises
This commit is contained in:
commit
3230d1e952
16 changed files with 175 additions and 25 deletions
|
|
@ -52,6 +52,7 @@ return [
|
|||
['name' => 'internal#remind', 'url' => '/api/internal/notifications/remind/{credential_id}', 'verb' => 'POST'],
|
||||
['name' => 'internal#read', 'url' => '/api/internal/notifications/read/{credential_id}', 'verb' => 'DELETE'],
|
||||
['name' => 'internal#getAppVersion', 'url' => '/api/internal/version', 'verb' => 'GET'],
|
||||
['name' => 'internal#generatePerson', 'url' => '/api/internal/generate_person', 'verb' => 'GET'],
|
||||
|
||||
]
|
||||
];
|
||||
|
|
@ -70,4 +70,12 @@ class InternalController extends ApiController {
|
|||
return new JSONResponse(array('version' => OC_App::getAppVersion('passman')));
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*/
|
||||
public function generatePerson() {
|
||||
$random_person = json_decode(file_get_contents('http://api.namefake.com/'));
|
||||
return new JSONResponse($random_person);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -82,14 +82,17 @@ angular.module('passmanApp')
|
|||
|
||||
$scope.startImport = function(){
|
||||
if(file_data){
|
||||
parsed_data = $window.PassmanImporter[$scope.selectedImporter.id].readFile(file_data);
|
||||
_log('Parsed '+ parsed_data.length + ' credentials, starting to import');
|
||||
$scope.current_import_length = parsed_data.length;
|
||||
if( parsed_data.length > 0){
|
||||
addCredential(0);
|
||||
} else {
|
||||
// @TODO Show message no data found
|
||||
}
|
||||
$window.PassmanImporter[$scope.selectedImporter.id].readFile(file_data, function(parseddata){
|
||||
parsed_data = parseddata;
|
||||
_log('Parsed '+ parsed_data.length + ' credentials, starting to import');
|
||||
$scope.current_import_length = parsed_data.length;
|
||||
if( parsed_data.length > 0){
|
||||
addCredential(0);
|
||||
} else {
|
||||
// @TODO Show message no data found
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,7 +128,6 @@ angular.module('passmanApp')
|
|||
return;
|
||||
}
|
||||
VaultService.createVault(vault_name).then(function (vault) {
|
||||
$scope.creating_vault_msg = 'Creating vault..'
|
||||
$scope.vaults.push(vault);
|
||||
var _vault = angular.copy(vault);
|
||||
_vault.vaultKey = angular.copy(vault_key);
|
||||
|
|
|
|||
22
js/app/directives/clickselect.js
Normal file
22
js/app/directives/clickselect.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name passmanApp.directive:selectOnClick
|
||||
* @description
|
||||
* # selectOnClick
|
||||
*/
|
||||
angular.module('passmanApp')
|
||||
.directive('selectOnClick', ['$window', function ($window) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function (scope, element, attrs) {
|
||||
element.on('click', function () {
|
||||
if (!$window.getSelection().toString()) {
|
||||
// Required for mobile Safari
|
||||
this.setSelectionRange(0, this.value.length)
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
|
@ -11,7 +11,7 @@ PassmanImporter.clippers = {
|
|||
}
|
||||
};
|
||||
|
||||
PassmanImporter.clippers.readFile = function (file_data) {
|
||||
PassmanImporter.clippers.readFile = function (file_data, callback) {
|
||||
var credential_list = [];
|
||||
var re = /<textarea>(.*?)<\/textarea>/gi;
|
||||
var matches = re.exec(file_data);
|
||||
|
|
@ -41,8 +41,10 @@ PassmanImporter.clippers.readFile = function (file_data) {
|
|||
}
|
||||
)
|
||||
}
|
||||
credential_list.push(_credential);
|
||||
if(_credential.label){
|
||||
credential_list.push(_credential);
|
||||
}
|
||||
}
|
||||
}
|
||||
return credential_list;
|
||||
callback(credential_list);
|
||||
};
|
||||
|
|
@ -11,7 +11,7 @@ PassmanImporter.dashLaneCsv = {
|
|||
}
|
||||
};
|
||||
|
||||
PassmanImporter.dashLaneCsv.readFile = function (file_data) {
|
||||
PassmanImporter.dashLaneCsv.readFile = function (file_data, callback) {
|
||||
var rows = file_data.split('\n');
|
||||
var credential_list = [];
|
||||
for (var i = 1, row; row = rows[i]; i++) {
|
||||
|
|
@ -35,5 +35,5 @@ PassmanImporter.dashLaneCsv.readFile = function (file_data) {
|
|||
credential_list.push(_credential);
|
||||
}
|
||||
}
|
||||
return credential_list;
|
||||
callback(credential_list);
|
||||
};
|
||||
|
|
@ -11,7 +11,7 @@ PassmanImporter.keepassCsv = {
|
|||
}
|
||||
};
|
||||
|
||||
PassmanImporter.keepassCsv.readFile = function (file_data) {
|
||||
PassmanImporter.keepassCsv.readFile = function (file_data, callback) {
|
||||
var parsed_csv = PassmanImporter.readCsv(file_data);
|
||||
var credential_list = [];
|
||||
for (var i = 0; i < parsed_csv.length; i++) {
|
||||
|
|
@ -35,5 +35,5 @@ PassmanImporter.keepassCsv.readFile = function (file_data) {
|
|||
_credential.tags = tags;
|
||||
credential_list.push(_credential);
|
||||
}
|
||||
return credential_list;
|
||||
callback(credential_list);
|
||||
};
|
||||
|
|
@ -11,7 +11,7 @@ PassmanImporter.lastpassCsv = {
|
|||
}
|
||||
};
|
||||
|
||||
PassmanImporter.lastpassCsv.readFile = function (file_data) {
|
||||
PassmanImporter.lastpassCsv.readFile = function (file_data, callback) {
|
||||
var parsed_csv = PassmanImporter.readCsv(file_data);
|
||||
var credential_list = [];
|
||||
for (var i = 0; i < parsed_csv.length; i++) {
|
||||
|
|
@ -27,5 +27,5 @@ PassmanImporter.lastpassCsv.readFile = function (file_data) {
|
|||
credential_list.push(_credential);
|
||||
}
|
||||
}
|
||||
return credential_list;
|
||||
callback(credential_list);
|
||||
};
|
||||
|
|
@ -11,7 +11,7 @@ PassmanImporter.passmanJson = {
|
|||
}
|
||||
};
|
||||
|
||||
PassmanImporter.passmanJson.readFile = function (file_data) {
|
||||
PassmanImporter.passmanJson.readFile = function (file_data, callback) {
|
||||
var parsed_json = PassmanImporter.readJson(file_data);
|
||||
var credential_list = [];
|
||||
for (var i = 0; i < parsed_json.length; i++) {
|
||||
|
|
@ -56,5 +56,5 @@ PassmanImporter.passmanJson.readFile = function (file_data) {
|
|||
credential_list.push(_credential);
|
||||
}
|
||||
}
|
||||
return credential_list;
|
||||
callback(credential_list);
|
||||
};
|
||||
42
js/importers/importer-passpackcsv.js
Normal file
42
js/importers/importer-passpackcsv.js
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// Importers should always start with this
|
||||
if (!window['PassmanImporter']) {
|
||||
var PassmanImporter = {}
|
||||
}
|
||||
// Define the importer
|
||||
PassmanImporter.passpackCsv = {
|
||||
info: {
|
||||
name: 'Passpack csv',
|
||||
id: 'passpackCsv',
|
||||
description: 'Create an csv export with the following options enabled: http://i.imgur.com/CaeTA4d.png'
|
||||
}
|
||||
};
|
||||
|
||||
PassmanImporter.passpackCsv.readFile = function (file_data, callback) {
|
||||
var parsed_csv = PassmanImporter.readCsv(file_data, false);
|
||||
var credential_list = [];
|
||||
for (var i = 0; i < parsed_csv.length; i++) {
|
||||
var row = parsed_csv[i];
|
||||
var _credential = PassmanImporter.newCredential();
|
||||
_credential.label = row[0];
|
||||
_credential.username = row[1];
|
||||
_credential.password = row[2];
|
||||
_credential.url = row[3];
|
||||
var tags = row[4].split(' ');
|
||||
if (tags.length > 0) {
|
||||
_credential.tags = tags.map(function (item) {
|
||||
if (item) {
|
||||
return {text: item}
|
||||
}
|
||||
|
||||
}).filter(function (item) {
|
||||
return (item);
|
||||
});
|
||||
}
|
||||
_credential.description = row[5];
|
||||
_credential.email = row[6];
|
||||
if (_credential.label) {
|
||||
credential_list.push(_credential);
|
||||
}
|
||||
}
|
||||
callback(credential_list);
|
||||
};
|
||||
70
js/importers/importer-randomdata.js
Normal file
70
js/importers/importer-randomdata.js
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
// Importers should always start with this
|
||||
if (!window['PassmanImporter']) {
|
||||
var PassmanImporter = {}
|
||||
}
|
||||
// Define the importer
|
||||
PassmanImporter.randomData = {
|
||||
info: {
|
||||
name: 'Random data',
|
||||
id: 'randomData',
|
||||
description: 'Create\'s 10 random credentials for testing purposes.'
|
||||
}
|
||||
};
|
||||
|
||||
PassmanImporter.randomData.readFile = function (file_data,callback) {
|
||||
var credential_list = [];
|
||||
var tags =
|
||||
['Social media',
|
||||
'Hosting',
|
||||
'Forums',
|
||||
'Webshops',
|
||||
'FTP',
|
||||
'SSH',
|
||||
'Banking',
|
||||
'Applications',
|
||||
'Server stuff',
|
||||
'mysql',
|
||||
'Wifi',
|
||||
'Games',
|
||||
'Certificate',
|
||||
'Serials'
|
||||
];
|
||||
var label;
|
||||
var generateCredential = function (max, i, cb) {
|
||||
if(jQuery){
|
||||
var url = OC.generateUrl('apps/passman/api/internal/generate_person');
|
||||
$.ajax({
|
||||
url: url,
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
var _credential = PassmanImporter.newCredential();
|
||||
label = (Math.random() >= 0.5) ? data.domain : data.email_d +' - ' + data.email_u;
|
||||
_credential.label = label;
|
||||
_credential.username = data.username;
|
||||
_credential.password = data.password;
|
||||
_credential.url = data.url;
|
||||
|
||||
var tag_amount = Math.floor(Math.random()*5);
|
||||
for(var ta = 0; ta < tag_amount; ta++){
|
||||
var item = tags[Math.floor(Math.random()*tags.length)];
|
||||
var tag = {
|
||||
text: item
|
||||
};
|
||||
if(_credential.tags.indexOf(tag) === -1){
|
||||
_credential.tags.push(tag);
|
||||
}
|
||||
}
|
||||
credential_list.push(_credential);
|
||||
if(i <= max){
|
||||
generateCredential(max, i + 1, callback)
|
||||
} else {
|
||||
cb(credential_list)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
generateCredential(9, 0,function(credential_list){
|
||||
callback(credential_list);
|
||||
});
|
||||
};
|
||||
|
|
@ -11,7 +11,7 @@ PassmanImporter.zohoCsv = {
|
|||
}
|
||||
};
|
||||
|
||||
PassmanImporter.zohoCsv.readFile = function (file_data) {
|
||||
PassmanImporter.zohoCsv.readFile = function (file_data, callback) {
|
||||
var parsed_csv = PassmanImporter.readCsv(file_data, false);
|
||||
var credential_list = [];
|
||||
for (var i = 0; i < parsed_csv.length; i++) {
|
||||
|
|
@ -26,5 +26,5 @@ PassmanImporter.zohoCsv.readFile = function (file_data) {
|
|||
credential_list.push(_credential);
|
||||
}
|
||||
}
|
||||
return credential_list;
|
||||
callback(credential_list);
|
||||
};
|
||||
|
|
@ -107,7 +107,7 @@ angular.module('views/share_credential.html', []).run(['$templateCache', functio
|
|||
angular.module('views/show_vault.html', []).run(['$templateCache', function($templateCache) {
|
||||
'use strict';
|
||||
$templateCache.put('views/show_vault.html',
|
||||
'<div id="passman-controls"><div class="breadcrumb"><div class="breadcrumb"><div class="crumb svg ui-droppable" data-dir="/"><a><i class="fa fa-home"></i></a></div><div class="crumb svg last"><a>{{active_vault.name}}</a></div></div></div><div class="actions creatable"><span ng-click="menuOpen = !menuOpen" class="button new" ng-init="menuOpen = false" off-click="menuOpen = false;"><span>+</span></span><div class="actionList popovermenu bubble menu" ng-show="menuOpen"><ul><li><span ng-click="addCredential()" class="menuitem action"><span class="icon icon-rename"></span> <span>New credential</span></span></li><li><span href="#" class="menuitem action"><span class="icon icon-shared"></span> <span>New shared credential</span></span>w</li></ul></div><span class="title" ng-if="delete_time">Showing deleted since: <span ng-if="delete_time == 1">All time</span> <span ng-if="delete_time > 1">{{delete_time | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</span></span></div><div class="searchboxContainer"><input type="text" ng-model="filterOptions.filterText" class="searchbox" placeholder="Search credential..."></div></div><div off-click="closeSelected()"><table class="credential-table" ng-init="menuOpen = false;"><tr ng-repeat="credential in credentials | credentialSearch:filterOptions | tagFilter:selectedtags | as:this:\'filtered_credentials\'" ng-if="credential.hidden == 0 && showCredentialRow(credential)" ng-click="selectCredential(credential)" ng-class="{\'selected\': selectedCredential.credential_id == credential.credential_id}"><td><span class="icon"><i class="fa fa-lock"></i></span> <span class="label">{{credential.label}}</span> <span class="tags"><span class="tag" ng-repeat="tag in credential.tags_raw">{{tag.text}}</span></span></td></tr></table><div id="app-sidebar" class="detailsView scroll-container app_sidebar" ng-show="selectedCredential"><span class="close icon-close" ng-click="closeSelected()" alt="Close"></span><table><tr ng-show="selectedCredential.label"><td>Label</td><td>{{selectedCredential.label}}</td></tr><tr ng-show="selectedCredential.username"><td>Account</td><td><span credential-field value="selectedCredential.username"></span></td></tr><tr ng-show="selectedCredential.password"><td>Password</td><td><span credential-field value="selectedCredential.password" secret="\'true\'"></span></td></tr><tr ng-show="selectedCredential.otp.secret"><td>OTP</td><td><span otp-generator secret="selectedCredential.otp.secret"></span></td></tr><tr ng-show="selectedCredential.email"><td>E-mail</td><td><span credential-field value="selectedCredential.email"></span></td></tr><tr ng-show="selectedCredential.url"><td>URL</td><td><span credential-field value="selectedCredential.url"></span></td></tr><tr ng-show="selectedCredential.files.length > 0"><td>Files</td><td><div ng-repeat="file in selectedCredential.files" class="link" ng-click="downloadFile(file)">{{file.filename}} ({{file.size | bytes}})</div></td></tr><tr ng-repeat="field in selectedCredential.custom_fields"><td>{{field.label}}</td><td><span credential-field value="field.value" secret="field.secret"></span></td></tr><tr ng-show="selectedCredential.expire_time > 0"><td>Expire time</td><td>{{selectedCredential.expire_time * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</td></tr><tr ng-show="selectedCredential.changed"><td>Changed</td><td>{{selectedCredential.changed * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</td></tr><tr ng-show="selectedCredential.created"><td>Created</td><td>{{selectedCredential.created * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</td></tr></table><div class="tags"><span class="tag" ng-repeat="tag in selectedCredential.tags">{{tag.text}}</span></div><div ng-show="selectedCredential"><button class="button" ng-click="editCredential(selectedCredential)" ng-if="selectedCredential.delete_time == 0"><span class="fa fa-edit"></span> Edit</button> <button class="button" ng-click="deleteCredential(selectedCredential)" ng-if="selectedCredential.delete_time == 0"><span class="fa fa-trash"></span> Delete</button> <button class="button" ng-click="shareCredential(selectedCredential)" ng-if="selectedCredential.delete_time == 0"><span class="fa fa-share"></span> Share</button> <button class="button" ng-click="getRevisions(selectedCredential)" ng-if="selectedCredential.delete_time == 0"><span class="fa fa-undo"></span> Revisions</button> <button class="button" ng-if="selectedCredential.delete_time > 0" ng-click="recoverCredential(selectedCredential)"><span class="fa fa-recycle"></span> Recover</button> <button class="button" ng-if="selectedCredential.delete_time > 0" ng-click="destroyCredential(selectedCredential)"><span class="fa fa-bomb"></span> Destroy</button></div></div></div>');
|
||||
'<div id="passman-controls"><div class="breadcrumb"><div class="breadcrumb"><div class="crumb svg ui-droppable" data-dir="/"><a><i class="fa fa-home"></i></a></div><div class="crumb svg last"><a>{{active_vault.name}}</a></div></div></div><div class="actions creatable"><span ng-click="menuOpen = !menuOpen" class="button new" ng-init="menuOpen = false" off-click="menuOpen = false;"><span>+</span></span><div class="actionList popovermenu bubble menu" ng-show="menuOpen"><ul><li><span ng-click="addCredential()" class="menuitem action"><span class="icon icon-rename"></span> <span>New credential</span></span></li><li><span href="#" class="menuitem action"><span class="icon icon-shared"></span> <span>New shared credential</span></span>w</li></ul></div><span class="title" ng-if="delete_time">Showing deleted since: <span ng-if="delete_time == 1">All time</span> <span ng-if="delete_time > 1">{{delete_time | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</span></span></div><div class="searchboxContainer"><input type="text" ng-model="filterOptions.filterText" class="searchbox" placeholder="Search credential..." select-on-click></div></div><div off-click="closeSelected()"><table class="credential-table" ng-init="menuOpen = false;"><tr ng-repeat="credential in credentials | credentialSearch:filterOptions | tagFilter:selectedtags | orderBy:\'label\'| as:this:\'filtered_credentials\'" ng-if="credential.hidden == 0 && showCredentialRow(credential)" ng-click="selectCredential(credential)" ng-class="{\'selected\': selectedCredential.credential_id == credential.credential_id}"><td><span class="icon"><i class="fa fa-lock"></i></span> <span class="label">{{credential.label}}</span> <span class="tags"><span class="tag" ng-repeat="tag in credential.tags_raw">{{tag.text}}</span></span></td></tr></table><div id="app-sidebar" class="detailsView scroll-container app_sidebar" ng-show="selectedCredential"><span class="close icon-close" ng-click="closeSelected()" alt="Close"></span><table><tr ng-show="selectedCredential.label"><td>Label</td><td>{{selectedCredential.label}}</td></tr><tr ng-show="selectedCredential.username"><td>Account</td><td><span credential-field value="selectedCredential.username"></span></td></tr><tr ng-show="selectedCredential.password"><td>Password</td><td><span credential-field value="selectedCredential.password" secret="\'true\'"></span></td></tr><tr ng-show="selectedCredential.otp.secret"><td>OTP</td><td><span otp-generator secret="selectedCredential.otp.secret"></span></td></tr><tr ng-show="selectedCredential.email"><td>E-mail</td><td><span credential-field value="selectedCredential.email"></span></td></tr><tr ng-show="selectedCredential.url"><td>URL</td><td><span credential-field value="selectedCredential.url"></span></td></tr><tr ng-show="selectedCredential.files.length > 0"><td>Files</td><td><div ng-repeat="file in selectedCredential.files" class="link" ng-click="downloadFile(file)">{{file.filename}} ({{file.size | bytes}})</div></td></tr><tr ng-repeat="field in selectedCredential.custom_fields"><td>{{field.label}}</td><td><span credential-field value="field.value" secret="field.secret"></span></td></tr><tr ng-show="selectedCredential.expire_time > 0"><td>Expire time</td><td>{{selectedCredential.expire_time * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</td></tr><tr ng-show="selectedCredential.changed"><td>Changed</td><td>{{selectedCredential.changed * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</td></tr><tr ng-show="selectedCredential.created"><td>Created</td><td>{{selectedCredential.created * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</td></tr></table><div class="tags"><span class="tag" ng-repeat="tag in selectedCredential.tags">{{tag.text}}</span></div><div ng-show="selectedCredential"><button class="button" ng-click="editCredential(selectedCredential)" ng-if="selectedCredential.delete_time == 0"><span class="fa fa-edit"></span> Edit</button> <button class="button" ng-click="deleteCredential(selectedCredential)" ng-if="selectedCredential.delete_time == 0"><span class="fa fa-trash"></span> Delete</button> <button class="button" ng-click="shareCredential(selectedCredential)" ng-if="selectedCredential.delete_time == 0"><span class="fa fa-share"></span> Share</button> <button class="button" ng-click="getRevisions(selectedCredential)" ng-if="selectedCredential.delete_time == 0"><span class="fa fa-undo"></span> Revisions</button> <button class="button" ng-if="selectedCredential.delete_time > 0" ng-click="recoverCredential(selectedCredential)"><span class="fa fa-recycle"></span> Recover</button> <button class="button" ng-if="selectedCredential.delete_time > 0" ng-click="destroyCredential(selectedCredential)"><span class="fa fa-bomb"></span> Destroy</button></div></div></div>');
|
||||
}]);
|
||||
|
||||
angular.module('views/vaults.html', []).run(['$templateCache', function($templateCache) {
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ script('passman', 'app/directives/use-theme');
|
|||
script('passman', 'app/directives/credentialfield');
|
||||
script('passman', 'app/directives/ngenter');
|
||||
script('passman', 'app/directives/autoscroll');
|
||||
script('passman', 'app/directives/clickselect');
|
||||
script('passman', 'importers/import-main');
|
||||
script('passman', 'importers/importer-keepasscsv');
|
||||
script('passman', 'importers/importer-lastpasscsv');
|
||||
|
|
@ -71,6 +72,8 @@ script('passman', 'importers/importer-dashlanecsv');
|
|||
script('passman', 'importers/importer-zohocsv');
|
||||
script('passman', 'importers/importer-passmanjson');
|
||||
script('passman', 'importers/importer-clipperz');
|
||||
script('passman', 'importers/importer-passpackcsv');
|
||||
script('passman', 'importers/importer-randomdata');
|
||||
|
||||
/*
|
||||
* Styles
|
||||
|
|
|
|||
|
|
@ -42,12 +42,12 @@
|
|||
</div>
|
||||
<div class="searchboxContainer">
|
||||
<input type="text" ng-model="filterOptions.filterText" class="searchbox"
|
||||
placeholder="Search credential...">
|
||||
placeholder="Search credential..." select-on-click>
|
||||
</div>
|
||||
</div>
|
||||
<div off-click="closeSelected()">
|
||||
<table class="credential-table" ng-init="menuOpen = false;">
|
||||
<tr ng-repeat="credential in credentials | credentialSearch:filterOptions | tagFilter:selectedtags | as:this:'filtered_credentials'"
|
||||
<tr ng-repeat="credential in credentials | credentialSearch:filterOptions | tagFilter:selectedtags | orderBy:'label'| as:this:'filtered_credentials'"
|
||||
ng-if="credential.hidden == 0 && showCredentialRow(credential)"
|
||||
ng-click="selectCredential(credential)"
|
||||
ng-class="{'selected': selectedCredential.credential_id == credential.credential_id}">
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue