mirror of
https://github.com/nextcloud/passman.git
synced 2024-09-20 14:56:21 +08:00
Remove server side encryption setting at admin page.
Fix counting credentials (Fixes #235) Fix for deleting shared credental (Fixes #232) Fix for empty sharing keys in vaults (Fixes #230) Fix removed shared credential leaves tags Fix for lastpass import #233 Disable share button when link sharing is disabled. Or when it's not shared with a user. Add issue template Bump version
This commit is contained in:
parent
053a364d88
commit
0a386d77ce
44
ISSUE_TEMPLATE.md
Normal file
44
ISSUE_TEMPLATE.md
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<!--
|
||||||
|
Thanks for reporting issues back to us!
|
||||||
|
This is the bug tracker for the Passman. Find passman-webextension at https://github.com/nextcloud/passman-webextension
|
||||||
|
|
||||||
|
|
||||||
|
To make it possible for us to help you please fill out below information carefully.
|
||||||
|
-->
|
||||||
|
### Steps to reproduce
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
|
||||||
|
### Expected behaviour
|
||||||
|
Tell us what should happen
|
||||||
|
|
||||||
|
### Actual behaviour
|
||||||
|
Tell us what happens instead
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
**Operating system**:
|
||||||
|
|
||||||
|
|
||||||
|
**Browser**:
|
||||||
|
|
||||||
|
|
||||||
|
**Extensions that might cause interference**:
|
||||||
|
|
||||||
|
|
||||||
|
**Passman version**:
|
||||||
|
|
||||||
|
|
||||||
|
**Nextcloud version**:
|
||||||
|
|
||||||
|
#### Browser log
|
||||||
|
<details>
|
||||||
|
<summary>Browser log</summary>
|
||||||
|
```
|
||||||
|
Insert your browser log here, this could for example include:
|
||||||
|
|
||||||
|
a) The javascript console log
|
||||||
|
b) The network log
|
||||||
|
c) ...
|
||||||
|
```
|
||||||
|
</details>
|
|
@ -18,7 +18,7 @@ For an demo of this app visit [https://demo.passman.cc](https://demo.passman.cc)
|
||||||
]]></description>
|
]]></description>
|
||||||
|
|
||||||
<licence>AGPL</licence>
|
<licence>AGPL</licence>
|
||||||
<version>2.0.1</version>
|
<version>2.0.2</version>
|
||||||
<author homepage="https://github.com/brantje">Sander Brand</author>
|
<author homepage="https://github.com/brantje">Sander Brand</author>
|
||||||
<author homepage="https://github.com/animalillo">Marcos Zuriaga</author>
|
<author homepage="https://github.com/animalillo">Marcos Zuriaga</author>
|
||||||
<namespace>Passman</namespace>
|
<namespace>Passman</namespace>
|
||||||
|
|
|
@ -79,9 +79,6 @@
|
||||||
//$location.path('/')
|
//$location.path('/')
|
||||||
|
|
||||||
}
|
}
|
||||||
if (_credential.tags) {
|
|
||||||
TagService.addTags(_credential.tags);
|
|
||||||
}
|
|
||||||
_credentials[i] = _credential;
|
_credentials[i] = _credential;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,14 +97,23 @@
|
||||||
_shared_credential_data.acl = _shared_credential;
|
_shared_credential_data.acl = _shared_credential;
|
||||||
_shared_credential_data.acl.permissions = new SharingACL(_shared_credential_data.acl.permissions);
|
_shared_credential_data.acl.permissions = new SharingACL(_shared_credential_data.acl.permissions);
|
||||||
_shared_credential_data.tags_raw = _shared_credential_data.tags;
|
_shared_credential_data.tags_raw = _shared_credential_data.tags;
|
||||||
if (_shared_credential_data.tags) {
|
|
||||||
TagService.addTags(_shared_credential_data.tags);
|
|
||||||
}
|
|
||||||
_credentials.push(_shared_credential_data);
|
_credentials.push(_shared_credential_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
angular.merge($scope.active_vault.credentials, _credentials);
|
angular.merge($scope.active_vault.credentials, _credentials);
|
||||||
$scope.show_spinner = false;
|
$scope.show_spinner = false;
|
||||||
|
|
||||||
|
if(!vault.private_sharing_key){
|
||||||
|
var key_size = 1024;
|
||||||
|
ShareService.generateRSAKeys(key_size).then(function (kp) {
|
||||||
|
var pem = ShareService.rsaKeyPairToPEM(kp);
|
||||||
|
$scope.creating_keys = false;
|
||||||
|
$scope.active_vault.private_sharing_key = pem.privateKey;
|
||||||
|
$scope.active_vault.public_sharing_key = pem.publicKey;
|
||||||
|
$scope.$digest();
|
||||||
|
VaultService.updateSharingKeys($scope.active_vault);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -125,6 +131,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var refresh_data_interval = null;
|
var refresh_data_interval = null;
|
||||||
if ($scope.active_vault) {
|
if ($scope.active_vault) {
|
||||||
$scope.$parent.selectedVault = true;
|
$scope.$parent.selectedVault = true;
|
||||||
|
@ -257,7 +264,8 @@
|
||||||
}
|
}
|
||||||
notification = NotificationService.showNotification($translate.instant('credential.deleted'), 5000,
|
notification = NotificationService.showNotification($translate.instant('credential.deleted'), 5000,
|
||||||
function () {
|
function () {
|
||||||
CredentialService.updateCredential(_credential).then(function (result) {
|
var key = CredentialService.getSharedKeyFromCredential(_credential);
|
||||||
|
CredentialService.updateCredential(_credential, false, key).then(function (result) {
|
||||||
if (result.delete_time > 0) {
|
if (result.delete_time > 0) {
|
||||||
notification = false;
|
notification = false;
|
||||||
|
|
||||||
|
@ -286,7 +294,8 @@
|
||||||
}
|
}
|
||||||
NotificationService.showNotification($translate.instant('credential.recovered'), 5000,
|
NotificationService.showNotification($translate.instant('credential.recovered'), 5000,
|
||||||
function () {
|
function () {
|
||||||
CredentialService.updateCredential(_credential).then(function () {
|
var key = CredentialService.getSharedKeyFromCredential(_credential);
|
||||||
|
CredentialService.updateCredential(_credential, false, key).then(function () {
|
||||||
notification = false;
|
notification = false;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -329,7 +338,15 @@
|
||||||
filtered_credentials = $filter('tagFilter')(filtered_credentials, $scope.selectedtags);
|
filtered_credentials = $filter('tagFilter')(filtered_credentials, $scope.selectedtags);
|
||||||
filtered_credentials = $filter('filter')(filtered_credentials, {hidden: 0});
|
filtered_credentials = $filter('filter')(filtered_credentials, {hidden: 0});
|
||||||
$scope.filtered_credentials = filtered_credentials;
|
$scope.filtered_credentials = filtered_credentials;
|
||||||
|
$scope.filterOptions.selectedtags = angular.copy($scope.selectedtags);
|
||||||
|
for (var i = 0; i < $scope.active_vault.credentials.length; i++) {
|
||||||
|
var _credential = $scope.active_vault.credentials[i];
|
||||||
|
if (_credential.tags) {
|
||||||
|
TagService.addTags(_credential.tags);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
$scope.selectedtags = [];
|
$scope.selectedtags = [];
|
||||||
|
@ -406,21 +423,13 @@
|
||||||
|
|
||||||
$scope.downloadFile = function (credential, file) {
|
$scope.downloadFile = function (credential, file) {
|
||||||
var callback = function (result) {
|
var callback = function (result) {
|
||||||
var key = null;
|
var key = EncryptService.getSharedKeyFromCredential(credential);
|
||||||
|
|
||||||
if (!result.hasOwnProperty('file_data')) {
|
if (!result.hasOwnProperty('file_data')) {
|
||||||
NotificationService.showNotification($translate.instant('error.loading.file.perm'), 5000);
|
NotificationService.showNotification($translate.instant('error.loading.file.perm'), 5000);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
}
|
}
|
||||||
if (!credential.hasOwnProperty('acl') && credential.hasOwnProperty('shared_key')) {
|
|
||||||
if (credential.shared_key) {
|
|
||||||
key = EncryptService.decryptString(angular.copy(credential.shared_key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (credential.hasOwnProperty('acl')) {
|
|
||||||
key = EncryptService.decryptString(angular.copy(credential.acl.shared_key));
|
|
||||||
}
|
|
||||||
|
|
||||||
var file_data = EncryptService.decryptString(result.file_data, key);
|
var file_data = EncryptService.decryptString(result.file_data, key);
|
||||||
download(file_data, escapeHTML(file.filename), file.mimetype);
|
download(file_data, escapeHTML(file.filename), file.mimetype);
|
||||||
|
|
||||||
|
|
|
@ -237,19 +237,23 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.unshareCredential = function (credential) {
|
$scope.unshareCredential = function (credential) {
|
||||||
ShareService.unshareCredential(credential);
|
|
||||||
var _credential = angular.copy(credential);
|
var _credential = angular.copy(credential);
|
||||||
var old_key = EncryptService.decryptString(angular.copy(_credential.shared_key));
|
var old_key = EncryptService.decryptString(angular.copy(_credential.shared_key));
|
||||||
var new_key = VaultService.getActiveVault().vaultKey;
|
var new_key = VaultService.getActiveVault().vaultKey;
|
||||||
_credential.shared_key = null;
|
_credential.shared_key = null;
|
||||||
_credential.unshare_action = true;
|
_credential.unshare_action = true;
|
||||||
_credential.skip_revision = true;
|
_credential.skip_revision = true;
|
||||||
|
CredentialService.reencryptCredential(_credential.guid, old_key, new_key, true).then(function (data) {
|
||||||
_credential = CredentialService.encryptCredential(_credential, old_key);
|
getAcl();
|
||||||
CredentialService.updateCredential(_credential, true).then(function () {
|
var c = data.cryptogram;
|
||||||
NotificationService.showNotification($translate.instant('credential.unshared'), 4000);
|
c.shared_key = null;
|
||||||
CredentialService.reencryptCredential(_credential.guid, old_key, new_key).then(function () {
|
c.unshare_action = true;
|
||||||
getAcl();
|
c.skip_revision = true;
|
||||||
|
ShareService.unshareCredential(c);
|
||||||
|
CredentialService.updateCredential(c, true).then(function () {
|
||||||
|
NotificationService.showNotification($translate.instant('credential.unshared'), 4000);
|
||||||
|
$scope.sharing_complete = true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
63
js/app/directives/credentialcounter.js
Normal file
63
js/app/directives/credentialcounter.js
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
/**
|
||||||
|
* Nextcloud - passman
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2016, Sander Brand (brantje@gmail.com)
|
||||||
|
* @copyright Copyright (c) 2016, Marcos Zuriaga Miguel (wolfi@wolfi.es)
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
/**
|
||||||
|
* @ngdoc directive
|
||||||
|
* @name passmanApp.directive:passwordGen
|
||||||
|
* @description
|
||||||
|
* # passwordGen
|
||||||
|
*/
|
||||||
|
angular.module('passmanApp')
|
||||||
|
.directive('credentialCounter', [function () {
|
||||||
|
return {
|
||||||
|
template: '<div ng-show="counter" translate="number.filtered" translate-values="{number_filtered: counter, credential_number: total}"></div>',
|
||||||
|
replace: false,
|
||||||
|
restrict: 'A',
|
||||||
|
scope: {
|
||||||
|
credentials: '=credentialCounter',
|
||||||
|
deleteTime: '=',
|
||||||
|
vault: '=',
|
||||||
|
filters: '='
|
||||||
|
},
|
||||||
|
|
||||||
|
link: function (scope) {
|
||||||
|
function countCredentials() {
|
||||||
|
var countedCredentials = 0;
|
||||||
|
var total = 0;
|
||||||
|
angular.forEach(scope.credentials, function (credential) {
|
||||||
|
total = (credential.hidden !== 1) ? total + 1 : total;
|
||||||
|
if(credential.delete_time >= scope.deleteTime && credential.hidden === 0){
|
||||||
|
countedCredentials = countedCredentials+1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
scope.counter = countedCredentials;
|
||||||
|
scope.total = total;
|
||||||
|
}
|
||||||
|
scope.$watch('[credentials, deleteTime, filters]', function () {
|
||||||
|
countCredentials();
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}]);
|
||||||
|
}());
|
|
@ -82,13 +82,13 @@
|
||||||
getEncryptedFields: function () {
|
getEncryptedFields: function () {
|
||||||
return _encryptedFields;
|
return _encryptedFields;
|
||||||
},
|
},
|
||||||
updateCredential: function (credential, skipEncyption) {
|
updateCredential: function (credential, skipEncryption, key) {
|
||||||
var _credential = angular.copy(credential);
|
var _credential = angular.copy(credential);
|
||||||
if (!skipEncyption) {
|
if (!skipEncryption) {
|
||||||
for (var i = 0; i < _encryptedFields.length; i++) {
|
for (var i = 0; i < _encryptedFields.length; i++) {
|
||||||
var field = _encryptedFields[i];
|
var field = _encryptedFields[i];
|
||||||
var fieldValue = angular.copy(credential[field]);
|
var fieldValue = angular.copy(credential[field]);
|
||||||
_credential[field] = EncryptService.encryptString(JSON.stringify(fieldValue));
|
_credential[field] = EncryptService.encryptString(JSON.stringify(fieldValue), key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_credential.expire_time = new Date(angular.copy(credential.expire_time)).getTime() / 1000;
|
_credential.expire_time = new Date(angular.copy(credential.expire_time)).getTime() / 1000;
|
||||||
|
@ -150,6 +150,18 @@
|
||||||
}
|
}
|
||||||
return credential;
|
return credential;
|
||||||
},
|
},
|
||||||
|
getSharedKeyFromCredential: function (credential) {
|
||||||
|
var key = null;
|
||||||
|
if (!credential.hasOwnProperty('acl') && credential.hasOwnProperty('shared_key')) {
|
||||||
|
if (credential.shared_key) {
|
||||||
|
key = EncryptService.decryptString(angular.copy(credential.shared_key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (credential.hasOwnProperty('acl')) {
|
||||||
|
key = EncryptService.decryptString(angular.copy(credential.acl.shared_key));
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
},
|
||||||
getRevisions: function (guid) {
|
getRevisions: function (guid) {
|
||||||
var queryUrl = OC.generateUrl('apps/passman/api/v2/credentials/' + guid + '/revision');
|
var queryUrl = OC.generateUrl('apps/passman/api/v2/credentials/' + guid + '/revision');
|
||||||
return $http.get(queryUrl).then(function (response) {
|
return $http.get(queryUrl).then(function (response) {
|
||||||
|
@ -182,7 +194,7 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
reencryptCredential: function (credential_guid, old_password, new_password) {
|
reencryptCredential: function (credential_guid, old_password, new_password, skipSharingKey) {
|
||||||
|
|
||||||
var service = this;
|
var service = this;
|
||||||
|
|
||||||
|
@ -198,8 +210,7 @@
|
||||||
this.parent.plain_credential = service.decryptCredential(credential, this.parent.old_password);
|
this.parent.plain_credential = service.decryptCredential(credential, this.parent.old_password);
|
||||||
var tmp = angular.copy(this.parent.plain_credential);
|
var tmp = angular.copy(this.parent.plain_credential);
|
||||||
|
|
||||||
//@FIXME Your shared credentials are not updated properly
|
if (tmp.hasOwnProperty('shared_key') && tmp.shared_key !== null && !skipSharingKey) {
|
||||||
if (tmp.hasOwnProperty('shared_key') && tmp.shared_key !== null) {
|
|
||||||
var shared_key = EncryptService.decryptString(angular.copy(tmp.shared_key)).trim();
|
var shared_key = EncryptService.decryptString(angular.copy(tmp.shared_key)).trim();
|
||||||
tmp.shared_key = EncryptService.encryptString(angular.copy(shared_key), this.parent.new_password);
|
tmp.shared_key = EncryptService.encryptString(angular.copy(shared_key), this.parent.new_password);
|
||||||
tmp.set_share_key = true;
|
tmp.set_share_key = true;
|
||||||
|
|
|
@ -48,7 +48,7 @@ var PassmanImporter = PassmanImporter || {};
|
||||||
_credential.url = row.url;
|
_credential.url = row.url;
|
||||||
_credential.tags = (row.grouping) ? [{text: row.grouping}] : [];
|
_credential.tags = (row.grouping) ? [{text: row.grouping}] : [];
|
||||||
_credential.description = row.extra;
|
_credential.description = row.extra;
|
||||||
if(_credential.label){
|
if(_credential.label && _credential.label !== "undefined"){
|
||||||
credential_list.push(_credential);
|
credential_list.push(_credential);
|
||||||
}
|
}
|
||||||
var progress = {
|
var progress = {
|
||||||
|
@ -58,7 +58,7 @@ var PassmanImporter = PassmanImporter || {};
|
||||||
};
|
};
|
||||||
this.call_progress(progress);
|
this.call_progress(progress);
|
||||||
}
|
}
|
||||||
this.call_then(credential_list)
|
this.call_then(credential_list);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
})(window, $, PassmanImporter);
|
})(window, $, PassmanImporter);
|
File diff suppressed because one or more lines are too long
|
@ -74,6 +74,7 @@ script('passman', 'app/directives/ngenter');
|
||||||
script('passman', 'app/directives/autoscroll');
|
script('passman', 'app/directives/autoscroll');
|
||||||
script('passman', 'app/directives/clickselect');
|
script('passman', 'app/directives/clickselect');
|
||||||
script('passman', 'app/directives/colorfromstring');
|
script('passman', 'app/directives/colorfromstring');
|
||||||
|
script('passman', 'app/directives/credentialcounter');
|
||||||
script('passman', 'app/directives/clearbutton2');
|
script('passman', 'app/directives/clearbutton2');
|
||||||
script('passman', 'importers/import-main');
|
script('passman', 'importers/import-main');
|
||||||
script('passman', 'importers/importer-keepasscsv');
|
script('passman', 'importers/importer-keepasscsv');
|
||||||
|
|
|
@ -108,17 +108,5 @@ $ciphers = openssl_get_cipher_methods();
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
|
||||||
|
|
||||||
<label for="server_side_encryption">Server side encryption
|
|
||||||
method:</label>
|
|
||||||
<select name="server_side_encryption2" id="server_side_encryption2">
|
|
||||||
<?php
|
|
||||||
foreach ($ciphers as $cipher) {
|
|
||||||
print '<option value="' . $cipher . '">' . $cipher . '</option>';
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
</select> (Not working atm. OpenSSL has no equivalent of <code>mcrypt_get_key_size()</code>)
|
|
||||||
</p>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
<div class="tab_container share_credential" ng-show="currentTab">
|
<div class="tab_container share_credential" ng-show="currentTab">
|
||||||
<div ng-include="currentTab.url"></div>
|
<div ng-include="currentTab.url"></div>
|
||||||
|
|
||||||
<button ng-click="applyShare()">{{ 'share' | translate}}</button>
|
<button ng-click="applyShare()" ng-disabled="!share_settings.linkSharing.enabled || share_settings.credentialSharedWithUserAndGroup.length > 0">{{ 'share' | translate}}</button>
|
||||||
<button ng-click="cancel()">{{ 'cancel' | translate}}</button>
|
<button ng-click="cancel()">{{ 'cancel' | translate}}</button>
|
||||||
<button class="btn btn-danger" ng-click="unshareCredential(storedCredential)">{{ 'unshare' | translate}}</button>
|
<button class="btn btn-danger" ng-click="unshareCredential(storedCredential)">{{ 'unshare' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,21 +10,18 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="title" ng-if="delete_time">
|
|
||||||
{{ 'deleted.since' | translate }}:
|
|
||||||
<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 class="actions creatable">
|
<div class="actions creatable">
|
||||||
<span ng-click="addCredential()" class="button new">
|
<span ng-click="addCredential()" class="button new">
|
||||||
<span >+</span></span>
|
<span >+</span></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="title" ng-show="filtered_credentials.length > 0" translate="number.filtered"
|
<div class="title" credential-counter="filtered_credentials" vault="active_vault" delete-time="delete_time" filters="filterOptions"></div>
|
||||||
translate-values="{number_filtered: filtered_credentials.length, credential_number: active_vault.credentials.length - 1}"
|
<!--<span class="title" ng-if="delete_time"><br />
|
||||||
>
|
{{ 'deleted.since' | translate }}:
|
||||||
|
<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>
|
||||||
|
|
||||||
</div>
|
</span> -->
|
||||||
<div class="searchboxContainer" ng-init="filterOptionShown = false;" off-click="filterOptionShown = false;">
|
<div class="searchboxContainer" ng-init="filterOptionShown = false;" off-click="filterOptionShown = false;">
|
||||||
<input type="text" ng-model="filterOptions.filterText" class="searchbox" id="searchBox"
|
<input type="text" ng-model="filterOptions.filterText" class="searchbox" id="searchBox"
|
||||||
placeholder="{{'search.credential' | translate}}" select-on-click clear-btn ng-click="filterOptionShown = true;">
|
placeholder="{{'search.credential' | translate}}" select-on-click clear-btn ng-click="filterOptionShown = true;">
|
||||||
|
|
Loading…
Reference in a new issue