Add UI support for multiple operations on keys

This commit is contained in:
Joseph Schorr 2016-04-27 17:44:44 -04:00 committed by Jimmy Zelinskie
parent 726cb5fe6a
commit a55e92bc95
6 changed files with 283 additions and 22 deletions

View file

@ -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) {

View file

@ -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;
}

View file

@ -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>

View file

@ -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"

View file

@ -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});
});
};
}

View file

@ -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);