keys ui WIP

This commit is contained in:
Joseph Schorr 2016-04-01 13:55:29 -04:00 committed by Jimmy Zelinskie
parent dc593c0197
commit 11ff3e9b59
25 changed files with 1154 additions and 74 deletions

View file

@ -55,6 +55,30 @@ a:focus {
outline: none !important;
}
.co-form-table label {
white-space: nowrap;
}
.co-form-table td {
padding: 8px;
}
.co-form-table td:first-child {
vertical-align: top;
padding-top: 14px;
}
.co-form-table td .co-help-text {
margin-top: 10px;
margin-bottom: 4px;
}
.co-help-text {
margin-top: 6px;
color: #aaa;
display: inline-block;
}
.co-options-menu .fa-gear {
color: #999;
cursor: pointer;

View file

@ -0,0 +1,31 @@
.markdown-editor-element .wmd-panel .btn {
background-color: #ddd;
}
.markdown-editor-element .wmd-panel .btn:hover {
background-color: #eee;
}
.markdown-editor-element .wmd-panel .btn:active {
background-color: #ccc;
}
.markdown-editor-element .preview-btn {
float: right;
}
.markdown-editor-element .preview-btn.active {
box-shadow: inset 0 3px 5px rgba(0,0,0,.125);
}
.markdown-editor-element .preview-panel .markdown-view {
border: 1px solid #eee;
padding: 4px;
min-height: 150px;
}
.markdown-editor-element .preview-top-bar {
height: 43px;
line-height: 43px;
color: #ddd;
}

View file

@ -0,0 +1,89 @@
.service-keys-manager-element .co-filter-box {
float: right;
}
.service-keys-manager-element .manager-header {
margin-bottom: 20px;
}
@media (max-width: 767px) {
.service-keys-manager-element .co-filter-box {
float: none;
display: block;
}
}
.service-keys-manager-element .approval-user .pretext {
vertical-align: middle;
margin-right: 4px;
font-size: 12px;
color: #777;
}
.service-keys-manager-element .expired a {
color: #D64456;
}
.service-keys-manager-element .critical a {
color: #F77454;
}
.service-keys-manager-element .warning a {
color: #FCA657;
}
.service-keys-manager-element .info a {
color: #2FC98E;
}
.service-keys-manager-element .rotation {
color: #777;
}
.service-keys-manager-element .no-expiration {
color: #145884;
}
.service-keys-manager-element i.fa {
margin-right: 4px;
}
.service-keys-manager-element .approval-rotation {
font-size: 12px;
color: #777;
}
.service-keys-manager-element .approval-rotation i.fa {
margin-right: 6px;
}
.service-keys-manager-element .subtitle {
color: #999;
font-size: 90%;
text-transform: uppercase;
font-weight: 300;
padding-top: 0!important;
text-align: left;
margin-bottom: 6px;
margin-top: 10px;
}
.service-keys-manager-element .approval-required i.fa {
margin-right: 4px;
}
.service-keys-manager-element .approval-required a {
margin-left: 10px;
}
.service-keys-manager-element .unnamed {
color: #ddd;
}
.service-keys-manager-element .key-display {
margin-top: 10px;
font-size: 12px;
font-family: Menlo,Monaco,Consolas,"Courier New",monospace;
background: white;
min-height: 500px;
}

View file

@ -1382,7 +1382,6 @@ p.editable:hover i {
.modal-body textarea {
width: 100%;
height: 150px;
border: 0px;
}
.tag-specific-images-view .image-listings {
@ -4034,13 +4033,28 @@ i.rocket-icon {
text-align: center;
}
.section-description-header {
.section-description-header {
position: relative;
margin-bottom: 10px;
min-height: 50px;
padding-bottom: 10px;
}
.section-description-header.twenty {
margin-top: -20px;
}
.section-description-header:before {
font-family: FontAwesome;
content: "\f05a";
position: absolute;
top: 50%;
left: 10px;
font-size: 22px;
color: #D2D2D2;
transform: translateY(-50%);
}
.nvtooltip h3 {
margin: 0;
padding: 4px 14px;

View file

@ -0,0 +1,3 @@
<span class="datetime-picker-element">
<input class="form-control" type="text" ng-model="entered_datetime"/>
</span>

View file

@ -0,0 +1,11 @@
<div class="markdown-editor-element">
<a class="btn btn-default preview-btn" ng-click="togglePreview()" ng-class="{'active': previewing}">Preview</a>
<div class="wmd-panel" ng-show="!previewing">
<div id="wmd-button-bar-{{id}}"></div>
<textarea class="wmd-input form-control" id="wmd-input-{{id}}" ng-model="content"></textarea>
</div>
<div class="preview-panel" ng-show="previewing">
<div class="preview-top-bar">Viewing preview</div>
<div class="markdown-view" content="content || '(Nothing entered)'"></div>
</div>
</div>

View file

@ -0,0 +1,260 @@
<div class="service-keys-manager-element">
<div class="resource-view" resource="keysResource" error-message="'Could not load service keys'">
<div class="manager-header" header-title="Service Keys">
<button class="btn btn-primary" ng-click="showCreateKey()">
Create Preshareable Key
</button>
</div>
<div class="section-description-header twenty">
Service keys provide a recognized means of authentication between Quay Enterprise and external services, as well as between external services. <br>Example services include Quay Security Scanner speaking to a <a href="https://github.com/coreos/clair" target="_blank">Clair</a> cluster, or Quay Enterprise speaking to its
<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
</span>
<input class="form-control" type="text" ng-model="options.filter" placeholder="Filter Keys...">
</span>
<!-- Table -->
<div class="empty" ng-if="!keys.length" style="margin-top: 20px;">
<div class="empty-primary-msg">No service keys defined</div>
<div class="empty-secondary-msg">There are no keys defined for working with external services</div>
</div>
<table class="co-table" ng-show="keys.length">
<thead>
<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>
</td>
<td ng-class="TableService.tablePredicateClass('service', options.predicate, options.reverse)">
<a href="javascript:void(0)" ng-click="TableService.orderBy('service', options)">Service Name</a>
</td>
<td ng-class="TableService.tablePredicateClass('creation_datetime', options.predicate, options.reverse)">
<a href="javascript:void(0)" ng-click="TableService.orderBy('creation_datetime', options)">Created</a>
</td>
<td ng-class="TableService.tablePredicateClass('expiration_datetime', options.predicate, options.reverse)">
<a href="javascript:void(0)" ng-click="TableService.orderBy('expiration_datetime', options)">Expires</a>
</td>
<td>
Approval Status
</td>
<td class="hidden-xs options-col"></td>
</thead>
<tbody ng-repeat="key in orderedKeys.visibleEntries" bindonce>
<tr>
<td class="caret-col">
<span ng-click="toggleDetails(key)">
<i class="fa"
ng-class="key.expanded ? 'fa-caret-down' : 'fa-caret-right'"
data-title="View Details" bs-tooltip></i>
</span>
</td>
<td>
<a ng-click="toggleDetails(key)" bo-if="key.name"><span bo-text="key.name"></span></a>
<a ng-click="toggleDetails(key)" bo-if="!key.name" class="unnamed">(Unnamed)</a>
</td>
<td><span bo-text="key.service"></span></td>
<td>
<span am-time-ago="key.created_date"></span>
</td>
<td>
<span class="rotation" bo-if="key.expiration_date && getExpirationInfo(key).rotateDate">
<i class="fa" ng-class="getExpirationInfo(key).icon"></i>
Automatically rotated <span am-time-ago="getExpirationInfo(key).rotateDate"></span>
</span>
<span bo-if="key.expiration_date && !getExpirationInfo(key).rotateDate">
<span ng-class="getExpirationInfo(key).className">
<a ng-click="showChangeExpiration(key)">
<i class="fa" ng-class="getExpirationInfo(key).icon"></i>
Expires <span am-time-ago="key.expiration_date"></span>
</a>
</span>
</span>
<span class="no-expiration" bo-if="!key.expiration_date">
<i class="fa fa-info-circle"></i> Does not expire
</span>
</td>
<td>
<span class="approval-user" bo-if="key.approval && key.approval.approval_type == 'ServiceKeyApprovalType.SUPERUSER'">
<span class="pretext">Approved by</span><span class="entity-reference" entity="key.approval.approver"></span>
</span>
<span class="approval-rotation" bo-if="key.approval && key.approval.approval_type == 'ServiceKeyApprovalType.KEY_ROTATION'">
<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>
<td class="options-col">
<span class="cor-options-menu">
<span class="cor-option" option-click="showChangeName(key)">
<i class="fa fa-tag"></i> Set Friendly Name
</span>
<span class="cor-option" option-click="showChangeExpiration(key)">
<i class="fa fa-clock-o"></i> Change Expiration Time
</span>
<span class="cor-option" option-click="showApproveKey(key)" ng-show="!key.approval">
<i class="fa fa-check-circle"></i> Approve Key
</span>
<span class="cor-option" option-click="showDeleteKey(key)">
<i class="fa fa-times"></i> Delete Key
</span>
</span>
</td>
</tr>
<tr ng-if="key.expanded">
<td colspan="7">
<div class="subtitle">Full Key ID</div>
<span bo-text="key.kid"></span>
<div bo-if="key.approval.notes">
<div class="subtitle">Approval notes</div>
<div class="markdown-view" content="key.approval.notes"></div>
</div>
</td>
</tr>
</tbody>
</table>
<div class="empty" ng-if="keys.length && !orderedKeys.entries.length"
style="margin-top: 20px;">
<div class="empty-primary-msg">No matching keys found.</div>
<div class="empty-secondary-msg">Try expanding your filtering terms.</div>
</div>
</div>
<!-- Change Key Expiration Confirm -->
<div class="cor-confirm-dialog"
dialog-context="context.expirationChangeInfo"
dialog-action="changeKeyExpiration(context.expirationChangeInfo, callback)"
dialog-title="Change Service Key Expiration"
dialog-action-title="Change Expiration">
<form>
<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.
</span>
</form>
</div>
<!-- Delete Key Confirm -->
<div class="cor-confirm-dialog"
dialog-context="deleteKeyInfo"
dialog-action="deleteKey(deleteKeyInfo, callback)"
dialog-title="Delete Service Key"
dialog-action-title="Delete Key">
Are you <strong>sure</strong> you want to delete service key <strong>{{ getKeyTitle(deleteKeyInfo.key) }}</strong>?<br><br>
All external services that use this key for authentication will fail.
</div>
<!-- Approve Key Confirm -->
<div class="cor-confirm-dialog"
dialog-context="approvalKeyInfo"
dialog-action="approveKey(approvalKeyInfo, callback)"
dialog-title="Approve Service Key"
dialog-action-title="Approve Key">
<form>
<div style="margin-bottom: 10px;">
Approve service key <strong>{{ getKeyTitle(approvalKeyInfo.key) }}</strong>?
</div>
<div class="markdown-editor" content="approvalKeyInfo.notes"></div>
<span class="co-help-text">
Enter optional notes for additional human-readable information about why the key was approved.
</span>
</form>
</div>
<!-- Created key modal -->
<div id="createdKeyModal" class="modal fade co-dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" ng-show="!creatingKey" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Created Preshareable Service Key <strong>{{ getKeyTitle(createdKey) }}</strong></h4>
</div>
<div class="modal-body">
<div class="co-alert co-alert-warning">
Please copy or download the following private key. <strong>Once this dialog is closed the key will not be accessible anywhere else</strong>.
</div>
<textarea class="key-display form-control" onclick="this.focus();this.select()" readonly>{{ createdKey.private_key }}</textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" ng-click="downloadPrivateKey(createdKey)" ng-if="isDownloadSupported()">
<i class="fa fa-download"></i> Download Private Key
</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<!-- Create key modal -->
<div id="createKeyModal" class="modal fade co-dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" ng-show="!creatingKey" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Create Preshareable Service Key</h4>
</div>
<div class="modal-body" ng-show="creatingKey">
<div class="cor-loader"></div>
</div>
<div class="modal-body" ng-show="!creatingKey">
<form name="createForm" ng-submit="createServiceKey()">
<table class="co-form-table">
<tr>
<td><label for="create-key-name">Key Name:</label></td>
<td>
<input class="form-control" name="create-key-name" type="text" ng-model="newKey.name" placeholder="Friendly Key Name" required>
<span class="co-help-text">
A friendly name for the key for later reference.
</span>
</td>
</tr>
<tr>
<td><label for="create-servce-name">Service Name:</label></td>
<td>
<input class="form-control" name="create-servce-name" type="text" ng-model="newKey.service" placeholder="Service Name" ng-pattern="/^[a-z0-9_]+$/" required>
<span class="co-help-text">
The name of the service for the key. Keys within the same cluster should share service names, representing
a single logical service. Must match [a-z0-9_]+.
</span>
</td>
</tr>
<tr>
<td><label for="create-key-expiration">Expires:</label></td>
<td>
<span class="datetime-picker" datetime="newKey.expiration"></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.
</span>
</td>
</tr>
<tr>
<td><label for="create-key-notes">Approval Notes:</label></td>
<td>
<div class="markdown-editor" content="newKey.notes"></div>
<span class="co-help-text">
Optional notes for additional human-readable information about why the key was added.
</span>
</td>
</tr>
</table>
</form>
</div>
<div class="modal-footer" ng-show="!creatingKey">
<button type="button" class="btn btn-primary" ng-click="createServiceKey()" ng-disabled="createForm.$invalid">
Create Key
</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
</div>

View file

@ -0,0 +1,51 @@
/**
* An element which displays a datetime picker.
*/
angular.module('quay').directive('datetimePicker', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/datetime-picker.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'datetime': '=datetime',
},
controller: function($scope, $element) {
$scope.entered_datetime = null;
$(function() {
$element.find('input').datetimepicker({
'format': 'LLL',
'sideBySide': true,
'showClear': true
});
$element.find('input').on("dp.change", function (e) {
$scope.datetime = e.date ? e.date.unix() : null;
});
});
$scope.$watch('entered_datetime', function(value) {
if (!value) {
if ($scope.datetime) {
$scope.datetime = null;
}
return;
}
$scope.datetime = (new Date(value)).getTime()/1000;
});
$scope.$watch('datetime', function(value) {
if (!value) {
$scope.entered_datetime = null;
return;
}
$scope.entered_datetime = moment.unix(value).format('LLL');
});
}
};
return directiveDefinitionObject;
});

View file

@ -37,6 +37,14 @@ angular.module('quay').directive('logsView', function () {
return '';
};
var getServiceKeyTitle = function(metadata) {
if (metadata.name) {
return metadata.name;
}
return metadata.kind.substr(0, 12);
};
var logDescriptions = {
'account_change_plan': 'Change plan',
'account_change_cc': 'Update credit card',
@ -195,6 +203,20 @@ angular.module('quay').directive('logsView', function () {
'regenerate_robot_token': 'Regenerated token for robot {robot}',
'service_key_create': function(metadata) {
if (metadata.preshared) {
return 'Manual creation of preshared service key {kid} for service {service}';
} else {
return 'Creation of service key {kid} for service {service} by {user_agent}';
}
},
'service_key_approve': 'Approval of service key {kid}',
'service_key_modify': 'Modification of service key {kid}',
'service_key_delete': 'Deletion of service key {kid}',
'service_key_extend': 'Change of expiration of service key {kid} from {old_expiration_date} to {expiration_date}',
'service_key_rotate': 'Automatic rotation of service key {kid} by {user_agent}',
// Note: These are deprecated.
'add_repo_webhook': 'Add webhook in repository {repo}',
'delete_repo_webhook': 'Delete webhook in repository {repo}'
@ -245,6 +267,12 @@ angular.module('quay').directive('logsView', function () {
'add_repo_notification': 'Add repository notification',
'delete_repo_notification': 'Delete repository notification',
'regenerate_robot_token': 'Regenerate Robot Token',
'service_key_create': 'Create Service Key',
'service_key_approve': 'Approve Service Key',
'service_key_modify': 'Modify Service Key',
'service_key_delete': 'Delete Service Key',
'service_key_extend': 'Extend Service Key Expiration',
'service_key_rotate': 'Automatic rotation of Service Key',
// Note: these are deprecated.
'add_repo_webhook': 'Add webhook',

View file

@ -0,0 +1,32 @@
/**
* An element which display an inline editor for writing and previewing markdown text.
*/
angular.module('quay').directive('markdownEditor', function () {
var counter = 0;
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/markdown-editor.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'content': '=content',
},
controller: function($scope, $element, $timeout) {
$scope.id = (counter++);
$scope.previewing = false;
$timeout(function() {
var converter = Markdown.getSanitizingConverter();
var editor = new Markdown.Editor(converter, '-' + $scope.id);
editor.run();
});
$scope.togglePreview = function() {
$scope.previewing = !$scope.previewing;
};
}
};
return directiveDefinitionObject;
});

View file

@ -0,0 +1,229 @@
/**
* An element which displays a panel for managing keys for external services.
*/
angular.module('quay').directive('serviceKeysManager', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/service-keys-manager.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'isEnabled': '=isEnabled'
},
controller: function($scope, $element, ApiService, TableService) {
$scope.options = {
'filter': null,
'predicate': 'expiration_datetime',
'reverse': false,
};
$scope.TableService = TableService;
$scope.newKey = null;
$scope.creatingKey = false;
$scope.context = {
'expirationChangeInfo': null
};
var buildOrderedKeys = function() {
if (!$scope.keys) {
return;
}
var keys = $scope.keys.map(function(key) {
var expiration_datetime = -Number.MAX_VALUE;
if (key.expiration_date) {
expiration_datetime = new Date(key.expiration_date).valueOf() * (-1);
}
return $.extend(key, {
'creation_datetime': new Date(key.creation_date).valueOf() * (-1),
'expiration_datetime': expiration_datetime,
'expanded': false
});
});
$scope.orderedKeys = TableService.buildOrderedItems(keys, $scope.options,
['name', 'kid', 'service'],
['creation_datetime', 'expiration_datetime'])
};
var loadServiceKeys = function() {
$scope.options.filter = null;
$scope.now = new Date();
$scope.keysResource = ApiService.getServiceKeysAsResource().get(function(resp) {
$scope.keys = resp['keys'];
buildOrderedKeys();
});
};
$scope.getKeyTitle = function(key) {
if (!key) { return ''; }
return key.name || key.kid.substr(0, 12);
};
$scope.toggleDetails = function(key) {
key.expanded = !key.expanded;
};
$scope.getExpirationInfo = function(key) {
if (!key.expiration_date) {
return '';
}
if (key.metadata.rotation_ttl) {
var rotate_date = moment(key.created_date).add(key.metadata.rotation_ttl, 's')
if (moment().isBefore(rotate_date)) {
return {'className': 'rotation', 'icon': 'fa-refresh', 'rotateDate': rotate_date};
}
}
expiration_date = moment(key.expiration_date);
if (moment().isAfter(expiration_date)) {
return {'className': 'expired', 'icon': 'fa-warning'};
}
if (moment().add(1, 'week').isAfter(expiration_date)) {
return {'className': 'critical', 'icon': 'fa-warning'};
}
if (moment().add(1, 'month').isAfter(expiration_date)) {
return {'className': 'warning', 'icon': 'fa-warning'};
}
return {'className': 'info', 'icon': 'fa-info-circle'};
};
$scope.showChangeName = function(key) {
bootbox.prompt('Enter a friendly name for key ' + $scope.getKeyTitle(key), function(value) {
if (value) {
var data = {
'name': value
};
var params = {
'kid': key.kid
};
ApiService.updateServiceKey(data, params).then(function(resp) {
loadServiceKeys();
}, ApiService.errorDisplay('Could not update service key'));
}
});
};
$scope.showChangeExpiration = function(key) {
$scope.context.expirationChangeInfo = {
'key': key,
'expiration_date': key.expiration_date ? (new Date(key.expiration_date).getTime() / 1000) : null
};
};
$scope.changeKeyExpiration = function(changeInfo, callback) {
var errorHandler = ApiService.errorDisplay('Could not change expiration on service key', callback);
var data = {
'expiration': changeInfo.expiration_date
};
var params = {
'kid': changeInfo.key.kid
};
ApiService.updateServiceKey(data, params).then(function(resp) {
loadServiceKeys();
callback(true);
}, errorHandler);
};
$scope.createServiceKey = function() {
$scope.creatingKey = true;
ApiService.createServiceKey($scope.newKey).then(function(resp) {
$scope.creatingKey = false;
$('#createKeyModal').modal('hide');
$scope.createdKey = resp;
$('#createdKeyModal').modal('show');
loadServiceKeys();
}, ApiService.errorDisplay('Could not create service key'));
};
$scope.showApproveKey = function(key) {
$scope.approvalKeyInfo = {
'key': key,
'notes': ''
};
};
$scope.approveKey = function(approvalKeyInfo, callback) {
var errorHandler = ApiService.errorDisplay('Could not approve service key', callback);
var data = {
'notes': approvalKeyInfo.notes
};
var params = {
'kid': approvalKeyInfo.key.kid
};
ApiService.approveServiceKey(data, params).then(function(resp) {
loadServiceKeys();
callback(true);
}, errorHandler);
};
$scope.showCreateKey = function() {
$scope.newKey = {
'expiration': null
};
$('#createKeyModal').modal('show');
};
$scope.showDeleteKey = function(key) {
$scope.deleteKeyInfo = {
'key': key
};
};
$scope.deleteKey = function(deleteKeyInfo, callback) {
var errorHandler = ApiService.errorDisplay('Could not delete service key', callback);
var params = {
'kid': deleteKeyInfo.key.kid
};
ApiService.deleteServiceKey(null, params).then(function(resp) {
loadServiceKeys();
callback(true);
}, errorHandler);
};
$scope.isDownloadSupported = function() {
var isSafari = /^((?!chrome).)*safari/i.test(navigator.userAgent);
if (isSafari) {
// Doesn't work properly in Safari, sadly.
return false;
}
try { return !!new Blob(); } catch(e) {}
return false;
};
$scope.downloadPrivateKey = function(key) {
var blob = new Blob([key.private_key]);
saveAs(blob, $scope.getKeyTitle(key) + '.pem');
};
$scope.$watch('options.filter', buildOrderedKeys);
$scope.$watch('options.predicate', buildOrderedKeys);
$scope.$watch('options.reverse', buildOrderedKeys);
$scope.$watch('isEnabled', function(value) {
if (value) {
loadServiceKeys();
}
});
}
};
return directiveDefinitionObject;
});

View file

@ -31,6 +31,7 @@
$scope.csrf_token = encodeURIComponent(window.__token);
$scope.dashboardActive = false;
$scope.currentConfig = null;
$scope.serviceKeysActive = false;
$scope.setDashboardActive = function(active) {
$scope.dashboardActive = active;
@ -46,6 +47,10 @@
$('#createUserModal').modal('show');
};
$scope.loadServiceKeys = function() {
$scope.serviceKeysActive = true;
};
$scope.viewSystemLogs = function(service) {
if ($scope.pollChannel) {
$scope.pollChannel.stop();

View file

@ -25,6 +25,34 @@ angular.module('quay').factory('StringBuilderService', ['$sce', 'UtilService', f
'client_id': 'chain'
};
var filters = {
'obj': function(value) {
if (!value) { return []; }
return Object.getOwnPropertyNames(value);
},
'updated_tags': function(value) {
if (!value) { return []; }
return Object.getOwnPropertyNames(value);
},
'kid': function(kid, metadata) {
if (metadata.name) {
return metadata.name;
}
return metadata.kid.substr(0, 12);
},
'expiration_date': function(value) {
return moment.unix(value).format('LLL');
},
'old_expiration_date': function(value) {
return moment.unix(value).format('LLL');
}
};
stringBuilderService.buildUrl = function(value_or_func, metadata) {
var url = value_or_func;
if (typeof url != 'string') {
@ -105,18 +133,6 @@ angular.module('quay').factory('StringBuilderService', ['$sce', 'UtilService', f
}
stringBuilderService.buildString = function(value_or_func, metadata, opt_codetag) {
var filters = {
'obj': function(value) {
if (!value) { return []; }
return Object.getOwnPropertyNames(value);
},
'updated_tags': function(value) {
if (!value) { return []; }
return Object.getOwnPropertyNames(value);
}
};
var description = value_or_func;
if (typeof description != 'string') {
description = description(metadata);
@ -126,7 +142,7 @@ angular.module('quay').factory('StringBuilderService', ['$sce', 'UtilService', f
if (metadata.hasOwnProperty(key)) {
var value = metadata[key] != null ? metadata[key] : '(Unknown)';
if (filters[key]) {
value = filters[key](value);
value = filters[key](value, metadata);
}
description = stringBuilderService.replaceField(description, '', key, value, opt_codetag);

View file

@ -24,6 +24,10 @@
tab-target="#organizations" tab-init="loadOrganizations()">
<i class="fa fa-sitemap"></i>
</span>
<span class="cor-tab" tab-title="Manage Service Keys"
tab-target="#servicekeys" tab-init="loadServiceKeys()">
<i class="fa fa-key"></i>
</span>
<span class="cor-tab" tab-title="Dashboard" tab-target="#dashboard"
tab-shown="setDashboardActive(true)" tab-hidden="setDashboardActive(false)">
<i class="fa fa-tachometer"></i>
@ -50,6 +54,11 @@
configuration-saved="configurationSaved(config)"></div>
</div>
<!-- Service keys tab -->
<div id="servicekeys" class="tab-pane">
<div class="service-keys-manager" is-enabled="serviceKeysActive"></div>
</div>
<!-- Dashboard tab -->
<div id="dashboard" class="tab-pane">
<div class="ps-usage-graph" is-enabled="dashboardActive"></div>