Add UI support for multiple operations on keys
This commit is contained in:
parent
726cb5fe6a
commit
a55e92bc95
6 changed files with 283 additions and 22 deletions
|
@ -1208,9 +1208,9 @@ a:focus {
|
|||
|
||||
.co-checkable-menu-state.some:after {
|
||||
content: "-";
|
||||
font-size: 19px;
|
||||
top: -6px;
|
||||
left: 3px;
|
||||
font-size: 24px;
|
||||
top: -10px;
|
||||
left: 4px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
|
|
|
@ -95,4 +95,22 @@
|
|||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.service-keys-manager-element .keys-list {
|
||||
list-style: circle;
|
||||
padding: 10px;
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
.service-keys-manager-element .keys-list li {
|
||||
padding: 4px;
|
||||
font-family: Consolas, "Lucida Console", Monaco, monospace;
|
||||
}
|
||||
|
||||
.service-keys-manager-element .expiration-form .datetime-picker {
|
||||
margin-top: 4px;
|
||||
display: block;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
|
|
@ -27,17 +27,17 @@
|
|||
|
||||
<div class="co-check-bar">
|
||||
<span class="cor-checkable-menu" controller="checkedTags">
|
||||
<div class="cor-checkable-menu-item" item-filter="allTagFilter">
|
||||
<div class="cor-checkable-menu-item" item-filter="allTagFilter(item)">
|
||||
<i class="fa fa-check-square-o"></i>All Tags
|
||||
</div>
|
||||
<div class="cor-checkable-menu-item" item-filter="noTagFilter(tag)">
|
||||
<div class="cor-checkable-menu-item" item-filter="noTagFilter(item)">
|
||||
<i class="fa fa-square-o"></i>No Tags
|
||||
</div>
|
||||
<div class="cor-checkable-menu-item" item-filter="commitTagFilter(tag)">
|
||||
<div class="cor-checkable-menu-item" item-filter="commitTagFilter(item)">
|
||||
<i class="fa fa-git"></i>Commit SHAs
|
||||
</div>
|
||||
|
||||
<div class="cor-checkable-menu-item" item-filter="imageIDFilter(it.image_id, tag)"
|
||||
<div class="cor-checkable-menu-item" item-filter="imageIDFilter(it.image_id, item)"
|
||||
ng-repeat="it in imageTracks">
|
||||
<i class="fa fa-circle-o" ng-style="{'color': it.color}"></i> {{ it.image_id.substr(0, 12) }}
|
||||
</div>
|
||||
|
|
|
@ -11,12 +11,56 @@
|
|||
<a href="https://tectonic.com/quay-enterprise/docs/latest/build-support.html" target="_blank">build workers</a>.
|
||||
</div>
|
||||
|
||||
<span class="co-filter-box" ng-if="keys.length">
|
||||
<span class="filter-message" ng-if="options.filter">
|
||||
Showing {{ orderedKeys.entries.length }} of {{ keys.length }} keys
|
||||
<div class="co-check-bar" ng-show="keys.length">
|
||||
<span class="cor-checkable-menu" controller="checkedKeys">
|
||||
<div class="cor-checkable-menu-item" item-filter="allKeyFilter(item)">
|
||||
<i class="fa fa-check-square-o"></i>All Keys
|
||||
</div>
|
||||
<div class="cor-checkable-menu-item" item-filter="noKeyFilter(item)">
|
||||
<i class="fa fa-square-o"></i>No Keys
|
||||
</div>
|
||||
<div class="cor-checkable-menu-item" item-filter="unapprovedKeyFilter(item)">
|
||||
<i class="fa fa-question-circle"></i>Unapproved Keys
|
||||
</div>
|
||||
<div class="cor-checkable-menu-item" item-filter="expiredKeyFilter(item)">
|
||||
<i class="fa fa-warning"></i>Expired Keys
|
||||
</div>
|
||||
</span>
|
||||
<input class="form-control" type="text" ng-model="options.filter" placeholder="Filter Keys...">
|
||||
</span>
|
||||
|
||||
<span class="co-checked-actions" ng-if="checkedKeys.checked.length">
|
||||
<button class="btn btn-primary"
|
||||
ng-click="askApproveMultipleKeys(checkedKeys.checked)"
|
||||
ng-show="allRequireApproval(checkedKeys.checked)">
|
||||
<i class="fa fa-check"></i><span class="text">Approve Keys</span>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-primary"
|
||||
ng-click="askChangeExpirationMultipleKeys(checkedKeys.checked)"
|
||||
ng-if="allExpired(checkedKeys.checked)">
|
||||
<i class="fa fa-refresh"></i>
|
||||
<span class="text">Revive Keys</span>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-default"
|
||||
ng-click="askChangeExpirationMultipleKeys(checkedKeys.checked)"
|
||||
ng-if="!allExpired(checkedKeys.checked)">
|
||||
<i class="fa fa-clock-o"></i>
|
||||
<span class="text">Change Keys Expiration</span>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-default"
|
||||
ng-click="askDeleteMultipleKeys(checkedKeys.checked)">
|
||||
<i class="fa fa-times"></i><span class="text">Delete Keys</span>
|
||||
</button>
|
||||
</span>
|
||||
|
||||
<span class="co-filter-box">
|
||||
<span class="filter-message" ng-if="options.filter">
|
||||
Showing {{ orderedKeys.entries.length }} of {{ keys.length }} keys
|
||||
</span>
|
||||
<input class="form-control" type="text" ng-model="options.filter" placeholder="Filter Keys...">
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<div class="empty" ng-if="!keys.length" style="margin-top: 20px;">
|
||||
|
@ -26,6 +70,7 @@
|
|||
|
||||
<table class="co-table" ng-show="keys.length">
|
||||
<thead>
|
||||
<td class="checkbox-col"></td>
|
||||
<td class="caret-col"></td>
|
||||
<td ng-class="TableService.tablePredicateClass('name', options.predicate, options.reverse)">
|
||||
<a href="javascript:void(0)" ng-click="TableService.orderBy('name', options)">Name</a>
|
||||
|
@ -44,8 +89,12 @@
|
|||
</td>
|
||||
<td class="hidden-xs options-col"></td>
|
||||
</thead>
|
||||
<tbody ng-repeat="key in orderedKeys.visibleEntries" bindonce>
|
||||
<tbody class="co-checkable-row"
|
||||
ng-repeat="key in orderedKeys.visibleEntries"
|
||||
ng-class="checkedKeys.isChecked(key, checkedKeys.checked) ? 'checked' : ''"
|
||||
bindonce>
|
||||
<tr>
|
||||
<td><span class="cor-checkable-item" controller="checkedKeys" item="key"></span></td>
|
||||
<td class="caret-col">
|
||||
<span ng-click="toggleDetails(key)">
|
||||
<i class="fa"
|
||||
|
@ -88,7 +137,6 @@
|
|||
<i class="fa fa-refresh"></i>Approved via key rotation
|
||||
</span>
|
||||
<span class="approval-required" bo-if="!key.approval">
|
||||
<i class="fa fa-warning"></i>
|
||||
Awaiting Approval <a ng-click="showApproveKey(key)">Approve Now</a>
|
||||
</span>
|
||||
</td>
|
||||
|
@ -130,6 +178,24 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Change Keys Expiration Confirm -->
|
||||
<div class="cor-confirm-dialog"
|
||||
dialog-context="changeKeysInfo"
|
||||
dialog-action="changeKeysExpiration(changeKeysInfo, callback)"
|
||||
dialog-title="Change Service Keys Expiration"
|
||||
dialog-action-title="Change Expiration">
|
||||
<form class="expiration-form">
|
||||
Please choose the new expiration date and time (if any) for the following keys:
|
||||
<ul class="keys-list">
|
||||
<li ng-repeat="key in changeKeysInfo.keys">{{ getKeyTitle(key) }}</li>
|
||||
</ul>
|
||||
<label>Expiration Date:</label>
|
||||
<span class="datetime-picker" datetime="changeKeysInfo.expiration_date"></span>
|
||||
<span class="co-help-text">
|
||||
If specified, the date and time at which the keys expire. It is highly recommended to have an expiration date.
|
||||
</span>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Change Key Expiration Confirm -->
|
||||
<div class="cor-confirm-dialog"
|
||||
|
@ -137,7 +203,8 @@
|
|||
dialog-action="changeKeyExpiration(context.expirationChangeInfo, callback)"
|
||||
dialog-title="Change Service Key Expiration"
|
||||
dialog-action-title="Change Expiration">
|
||||
<form>
|
||||
<form class="expiration-form">
|
||||
<label>Expiration Date:</label>
|
||||
<span class="datetime-picker" datetime="context.expirationChangeInfo.expiration_date"></span>
|
||||
<span class="co-help-text">
|
||||
If specified, the date and time that the key expires. It is highly recommended to have an expiration date.
|
||||
|
@ -145,6 +212,19 @@
|
|||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Delete Keys Confirm -->
|
||||
<div class="cor-confirm-dialog"
|
||||
dialog-context="deleteKeysInfo"
|
||||
dialog-action="deleteKeys(deleteKeysInfo, callback)"
|
||||
dialog-title="Delete Service Keys"
|
||||
dialog-action-title="Delete Keys">
|
||||
Are you <strong>sure</strong> you want to delete the follopwing service keys?<br>
|
||||
All external services that use these keys for authentication will fail.
|
||||
<ul class="keys-list">
|
||||
<li ng-repeat="key in deleteKeysInfo.keys">{{ getKeyTitle(key) }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Delete Key Confirm -->
|
||||
<div class="cor-confirm-dialog"
|
||||
dialog-context="deleteKeyInfo"
|
||||
|
@ -155,6 +235,26 @@
|
|||
All external services that use this key for authentication will fail.
|
||||
</div>
|
||||
|
||||
<!-- Approve Keys Confirm -->
|
||||
<div class="cor-confirm-dialog"
|
||||
dialog-context="approveKeysInfo"
|
||||
dialog-action="approveKeys(approveKeysInfo, callback)"
|
||||
dialog-title="Approve Service Keys"
|
||||
dialog-action-title="Approve Keys">
|
||||
<form>
|
||||
<div style="margin-bottom: 20px; padding-bottom: 10px; border-bottom: 1px solid #eee;">
|
||||
Approve the following service keys?
|
||||
<ul class="keys-list">
|
||||
<li ng-repeat="key in approveKeysInfo.keys">{{ getKeyTitle(key) }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="markdown-editor" content="approveKeysInfo.notes"></div>
|
||||
<span class="co-help-text">
|
||||
Enter optional notes for additional human-readable information about why the keys were approved.
|
||||
</span>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Approve Key Confirm -->
|
||||
<div class="cor-confirm-dialog"
|
||||
dialog-context="approvalKeyInfo"
|
||||
|
|
|
@ -674,8 +674,8 @@ angular.module("core-ui", [])
|
|||
};
|
||||
|
||||
this.checkByFilter = function(filter) {
|
||||
$scope.controller.checkByFilter(function(tag) {
|
||||
return filter({'tag': tag});
|
||||
$scope.controller.checkByFilter(function(item) {
|
||||
return filter({'item': item});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -11,13 +11,19 @@ angular.module('quay').directive('serviceKeysManager', function () {
|
|||
scope: {
|
||||
'isEnabled': '=isEnabled'
|
||||
},
|
||||
controller: function($scope, $element, ApiService, TableService) {
|
||||
controller: function($scope, $element, ApiService, TableService, UIService) {
|
||||
$scope.options = {
|
||||
'filter': null,
|
||||
'predicate': 'expiration_datetime',
|
||||
'reverse': false,
|
||||
};
|
||||
|
||||
$scope.deleteKeysInfo = null;
|
||||
$scope.approveKeysInfo = null;
|
||||
$scope.changeKeysInfo = null;
|
||||
|
||||
$scope.checkedKeys = UIService.createCheckStateController([], 'kid');
|
||||
|
||||
$scope.TableService = TableService;
|
||||
$scope.newKey = null;
|
||||
$scope.creatingKey = false;
|
||||
|
@ -48,6 +54,8 @@ angular.module('quay').directive('serviceKeysManager', function () {
|
|||
$scope.orderedKeys = TableService.buildOrderedItems(keys, $scope.options,
|
||||
['name', 'kid', 'service'],
|
||||
['creation_datetime', 'expiration_datetime'])
|
||||
|
||||
$scope.checkedKeys = UIService.createCheckStateController($scope.orderedKeys.visibleEntries, 'kid');
|
||||
};
|
||||
|
||||
var loadServiceKeys = function() {
|
||||
|
@ -131,7 +139,10 @@ angular.module('quay').directive('serviceKeysManager', function () {
|
|||
};
|
||||
|
||||
$scope.changeKeyExpiration = function(changeInfo, callback) {
|
||||
var errorHandler = ApiService.errorDisplay('Could not change expiration on service key', callback);
|
||||
var errorHandler = ApiService.errorDisplay('Could not change expiration on service key', function() {
|
||||
loadServiceKeys();
|
||||
callback(false);
|
||||
});
|
||||
|
||||
var data = {
|
||||
'expiration': changeInfo.expiration_date
|
||||
|
@ -166,7 +177,10 @@ angular.module('quay').directive('serviceKeysManager', function () {
|
|||
};
|
||||
|
||||
$scope.approveKey = function(approvalKeyInfo, callback) {
|
||||
var errorHandler = ApiService.errorDisplay('Could not approve service key', callback);
|
||||
var errorHandler = ApiService.errorDisplay('Could not approve service key', function() {
|
||||
loadServiceKeys();
|
||||
callback(false);
|
||||
});
|
||||
|
||||
var data = {
|
||||
'notes': approvalKeyInfo.notes
|
||||
|
@ -197,7 +211,10 @@ angular.module('quay').directive('serviceKeysManager', function () {
|
|||
};
|
||||
|
||||
$scope.deleteKey = function(deleteKeyInfo, callback) {
|
||||
var errorHandler = ApiService.errorDisplay('Could not delete service key', callback);
|
||||
var errorHandler = ApiService.errorDisplay('Could not delete service key', function() {
|
||||
loadServiceKeys();
|
||||
callback(false);
|
||||
});
|
||||
|
||||
var params = {
|
||||
'kid': deleteKeyInfo.key.kid
|
||||
|
@ -225,6 +242,132 @@ angular.module('quay').directive('serviceKeysManager', function () {
|
|||
saveAs(blob, $scope.getKeyTitle(key) + '.pem');
|
||||
};
|
||||
|
||||
$scope.askDeleteMultipleKeys = function(keys) {
|
||||
$scope.deleteKeysInfo = {
|
||||
'keys': keys
|
||||
};
|
||||
};
|
||||
|
||||
$scope.askApproveMultipleKeys = function(keys) {
|
||||
$scope.approveKeysInfo = {
|
||||
'keys': keys
|
||||
};
|
||||
};
|
||||
|
||||
$scope.askChangeExpirationMultipleKeys = function(keys) {
|
||||
$scope.changeKeysInfo = {
|
||||
'keys': keys
|
||||
};
|
||||
};
|
||||
|
||||
$scope.allKeyFilter = function(key) {
|
||||
return true;
|
||||
};
|
||||
|
||||
$scope.noKeyFilter = function(key) {
|
||||
return false;
|
||||
};
|
||||
|
||||
$scope.unapprovedKeyFilter = function(key) {
|
||||
return !key.approval;
|
||||
};
|
||||
|
||||
$scope.expiredKeyFilter = function(key) {
|
||||
return $scope.getExpirationInfo(key)['className'] == 'expired';
|
||||
};
|
||||
|
||||
$scope.allRequireApproval = function(keys) {
|
||||
for (var i = 0; i < keys.length; ++i) {
|
||||
if (keys[i].approval) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
$scope.allExpired = function(keys) {
|
||||
for (var i = 0; i < keys.length; ++i) {
|
||||
if (!$scope.expiredKeyFilter(keys[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
var forAllKeys = function(keys, error_msg, performer, callback) {
|
||||
var counter = 0;
|
||||
var performAction = function() {
|
||||
if (counter >= keys.length) {
|
||||
loadServiceKeys();
|
||||
callback(true);
|
||||
return;
|
||||
}
|
||||
|
||||
var key = keys[counter];
|
||||
var errorHandler = function(resp) {
|
||||
if (resp.status != 404) {
|
||||
bootbox.alert(error_msg);
|
||||
loadServiceKeys();
|
||||
callback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
performAction();
|
||||
};
|
||||
|
||||
counter++;
|
||||
performer(key).then(performAction, errorHandler);
|
||||
};
|
||||
|
||||
performAction();
|
||||
};
|
||||
|
||||
$scope.deleteKeys = function(info, callback) {
|
||||
var performer = function(key) {
|
||||
var params = {
|
||||
'kid': key.kid
|
||||
};
|
||||
|
||||
return ApiService.deleteServiceKey(null, params);
|
||||
};
|
||||
|
||||
forAllKeys(info.keys, 'Could not delete service key', performer, callback);
|
||||
};
|
||||
|
||||
$scope.approveKeys = function(info, callback) {
|
||||
var performer = function(key) {
|
||||
var params = {
|
||||
'kid': key.kid
|
||||
};
|
||||
|
||||
var data = {
|
||||
'notes': $scope.approveKeysInfo.notes
|
||||
};
|
||||
|
||||
return ApiService.approveServiceKey(data, params);
|
||||
};
|
||||
|
||||
forAllKeys(info.keys, 'Could not approve service key', performer, callback);
|
||||
};
|
||||
|
||||
$scope.changeKeysExpiration = function(info, callback) {
|
||||
var performer = function(key) {
|
||||
var data = {
|
||||
'expiration': info.expiration_date || null
|
||||
};
|
||||
|
||||
var params = {
|
||||
'kid': key.kid
|
||||
};
|
||||
|
||||
return ApiService.updateServiceKey(data, params);
|
||||
};
|
||||
|
||||
forAllKeys(info.keys, 'Could not update service key', performer, callback);
|
||||
};
|
||||
|
||||
$scope.$watch('options.filter', buildOrderedKeys);
|
||||
$scope.$watch('options.predicate', buildOrderedKeys);
|
||||
$scope.$watch('options.reverse', buildOrderedKeys);
|
||||
|
|
Reference in a new issue