Add the ability to view the system logs in the superuser endpoint
This commit is contained in:
parent
1f9f4ef26b
commit
5c7a9d0daf
10 changed files with 440 additions and 145 deletions
|
@ -1,9 +1,10 @@
|
|||
import string
|
||||
import logging
|
||||
import json
|
||||
import os
|
||||
|
||||
from random import SystemRandom
|
||||
from app import app
|
||||
from app import app, avatar
|
||||
from flask import request
|
||||
|
||||
from endpoints.api import (ApiResource, nickname, resource, validate_json_request, request_error,
|
||||
|
@ -22,6 +23,57 @@ import features
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
LOGS_PATH = "/var/log/%s/current"
|
||||
SERVICES_PATH = "conf/init/"
|
||||
|
||||
def get_immediate_subdirectories(directory):
|
||||
return [name for name in os.listdir(directory) if os.path.isdir(os.path.join(directory, name))]
|
||||
|
||||
|
||||
@resource('/v1/superuser/systemlogs/<service>')
|
||||
@internal_only
|
||||
@show_if(features.SUPER_USERS)
|
||||
class SuperUserGetLogsForService(ApiResource):
|
||||
""" Resource for fetching the kinds of system logs in the system. """
|
||||
@nickname('getSystemLogs')
|
||||
def get(self, service):
|
||||
""" Returns the logs for the specific service. """
|
||||
if SuperUserPermission().can():
|
||||
services = get_immediate_subdirectories(SERVICES_PATH)
|
||||
if not service in services:
|
||||
abort(404)
|
||||
|
||||
try:
|
||||
with open(LOGS_PATH % service, 'r') as f:
|
||||
logs = f.read()
|
||||
except Exception as ex:
|
||||
logger.exception('Cannot read logs')
|
||||
abort(400)
|
||||
|
||||
return {
|
||||
'logs': logs
|
||||
}
|
||||
|
||||
abort(403)
|
||||
|
||||
|
||||
@resource('/v1/superuser/systemlogs/')
|
||||
@internal_only
|
||||
@show_if(features.SUPER_USERS)
|
||||
class SuperUserSystemLogServices(ApiResource):
|
||||
""" Resource for fetching the kinds of system logs in the system. """
|
||||
@nickname('listSystemLogServices')
|
||||
def get(self):
|
||||
""" List the system logs for the current system. """
|
||||
if SuperUserPermission().can():
|
||||
return {
|
||||
'services': get_immediate_subdirectories(SERVICES_PATH)
|
||||
}
|
||||
|
||||
abort(403)
|
||||
|
||||
|
||||
|
||||
@resource('/v1/superuser/logs')
|
||||
@internal_only
|
||||
@show_if(features.SUPER_USERS)
|
||||
|
@ -33,7 +85,7 @@ class SuperUserLogs(ApiResource):
|
|||
@query_param('endtime', 'Latest time to which to get logs. (%m/%d/%Y %Z)', type=str)
|
||||
@query_param('performer', 'Username for which to filter logs.', type=str)
|
||||
def get(self, args):
|
||||
""" List the logs for the current system. """
|
||||
""" List the usage logs for the current system. """
|
||||
if SuperUserPermission().can():
|
||||
performer_name = args['performer']
|
||||
start_time = args['starttime']
|
||||
|
@ -49,6 +101,7 @@ def user_view(user):
|
|||
'username': user.username,
|
||||
'email': user.email,
|
||||
'verified': user.verified,
|
||||
'avatar': avatar.compute_hash(user.email, name=user.username),
|
||||
'super_user': user.username in app.config['SUPER_USERS']
|
||||
}
|
||||
|
||||
|
|
164
static/css/core-ui.css
Normal file
164
static/css/core-ui.css
Normal file
|
@ -0,0 +1,164 @@
|
|||
|
||||
.co-options-menu .fa-gear {
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.co-options-menu .dropdown.open .fa-gear {
|
||||
color: #428BCA;
|
||||
}
|
||||
|
||||
.co-img-bg-network {
|
||||
background: url('/static/img/network-tile.png') left top repeat, linear-gradient(30deg, #2277ad, #144768) no-repeat left top fixed;
|
||||
background-color: #2277ad;
|
||||
background-size: auto, 100% 100%;
|
||||
}
|
||||
|
||||
.co-m-navbar {
|
||||
background-color: white;
|
||||
margin: 0;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.co-fx-box-shadow {
|
||||
-webkit-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
|
||||
-moz-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
|
||||
-ms-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
|
||||
-o-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.co-fx-box-shadow-heavy {
|
||||
-webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
|
||||
-moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
|
||||
-ms-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
|
||||
-o-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
|
||||
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.co-fx-text-shadow {
|
||||
text-shadow: rgba(0, 0, 0, 1) 1px 1px 2px;
|
||||
}
|
||||
|
||||
.co-nav-title {
|
||||
height: 70px;
|
||||
margin-top: -22px;
|
||||
}
|
||||
|
||||
.co-nav-title .co-nav-title-content {
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.co-tab-container {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.co-tabs {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
width: 82px;
|
||||
background-color: #e8f1f6;
|
||||
border-right: 1px solid #DDE7ED;
|
||||
|
||||
display: table-cell;
|
||||
float: none;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.co-tab-content {
|
||||
width: 100%;
|
||||
display: table-cell;
|
||||
float: none;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.co-tabs li {
|
||||
list-style: none;
|
||||
display: block;
|
||||
border-bottom: 1px solid #DDE7ED;
|
||||
}
|
||||
|
||||
|
||||
.co-tabs li.active {
|
||||
background-color: white;
|
||||
border-right: 1px solid white;
|
||||
margin-right: -1px;
|
||||
}
|
||||
|
||||
.co-tabs li a {
|
||||
display: block;
|
||||
width: 82px;
|
||||
height: 82px;
|
||||
line-height: 82px;
|
||||
text-align: center;
|
||||
font-size: 36px;
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.co-tabs li.active a {
|
||||
color: black;
|
||||
}
|
||||
|
||||
|
||||
.co-main-content-panel {
|
||||
margin-bottom: 20px;
|
||||
background-color: #fff;
|
||||
border: 1px solid transparent;
|
||||
padding: 10px;
|
||||
|
||||
-webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
|
||||
-moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
|
||||
-ms-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
|
||||
-o-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
|
||||
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.co-tab-panel {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
|
||||
.cor-log-box {
|
||||
width: 100%;
|
||||
height: 550px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.co-log-viewer {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
right: 20px;
|
||||
height: 500px;
|
||||
|
||||
padding: 20px;
|
||||
|
||||
background: rgb(55, 55, 55);
|
||||
border: 1px solid black;
|
||||
color: white;
|
||||
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.co-log-viewer .co-log-content {
|
||||
font-family: Consolas, "Lucida Console", Monaco, monospace;
|
||||
font-size: 12px;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.cor-log-box .co-log-viewer-new-logs i {
|
||||
margin-left: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.cor-log-box .co-log-viewer-new-logs {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
bottom: 40px;
|
||||
right: 30px;
|
||||
padding: 10px;
|
||||
color: white;
|
||||
border-radius: 10px;
|
||||
background: rgba(72, 158, 72, 0.8);
|
||||
}
|
|
@ -88,116 +88,6 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.co-img-bg-network {
|
||||
background: url('/static/img/network-tile.png') left top repeat, linear-gradient(30deg, #2277ad, #144768) no-repeat left top fixed;
|
||||
background-color: #2277ad;
|
||||
background-size: auto, 100% 100%;
|
||||
}
|
||||
|
||||
.co-m-navbar {
|
||||
background-color: white;
|
||||
margin: 0;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.co-fx-box-shadow {
|
||||
-webkit-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
|
||||
-moz-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
|
||||
-ms-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
|
||||
-o-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.co-fx-box-shadow-heavy {
|
||||
-webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
|
||||
-moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
|
||||
-ms-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
|
||||
-o-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
|
||||
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.co-fx-text-shadow {
|
||||
text-shadow: rgba(0, 0, 0, 1) 1px 1px 2px;
|
||||
}
|
||||
|
||||
.co-nav-title {
|
||||
height: 70px;
|
||||
margin-top: -22px;
|
||||
}
|
||||
|
||||
.co-nav-title .co-nav-title-content {
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.co-tab-container {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.co-tabs {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
width: 82px;
|
||||
background-color: #e8f1f6;
|
||||
border-right: 1px solid #DDE7ED;
|
||||
|
||||
display: table-cell;
|
||||
float: none;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.co-tab-content {
|
||||
width: 100%;
|
||||
display: table-cell;
|
||||
float: none;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.co-tabs li {
|
||||
list-style: none;
|
||||
display: block;
|
||||
border-bottom: 1px solid #DDE7ED;
|
||||
}
|
||||
|
||||
|
||||
.co-tabs li.active {
|
||||
background-color: white;
|
||||
border-right: 1px solid white;
|
||||
margin-right: -1px;
|
||||
}
|
||||
|
||||
.co-tabs li a {
|
||||
display: block;
|
||||
width: 82px;
|
||||
height: 82px;
|
||||
line-height: 82px;
|
||||
text-align: center;
|
||||
font-size: 36px;
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.co-tabs li.active a {
|
||||
color: black;
|
||||
}
|
||||
|
||||
|
||||
.co-main-content-panel {
|
||||
margin-bottom: 20px;
|
||||
background-color: #fff;
|
||||
border: 1px solid transparent;
|
||||
padding: 10px;
|
||||
|
||||
-webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
|
||||
-moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
|
||||
-ms-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
|
||||
-o-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
|
||||
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.co-tab-panel {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.main-panel {
|
||||
margin-bottom: 20px;
|
||||
background-color: #fff;
|
||||
|
@ -4512,8 +4402,12 @@ pre.command:before {
|
|||
padding: 6px;
|
||||
}
|
||||
|
||||
.user-row.super-user td {
|
||||
background-color: #eeeeee;
|
||||
.user-row {
|
||||
border-bottom: 0px;
|
||||
}
|
||||
|
||||
.user-row td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.user-row .user-class {
|
||||
|
@ -4982,3 +4876,7 @@ i.slack-icon {
|
|||
#gen-token input[type="checkbox"] {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.system-log-download-panel {
|
||||
padding: 20px;
|
||||
}
|
||||
|
|
11
static/directives/cor-log-box.html
Normal file
11
static/directives/cor-log-box.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
<div class="co-log-box-element">
|
||||
<div id="co-log-viewer" class="co-log-viewer" ng-if="logs">
|
||||
<div class="quay-spinner" ng-if="!logs"></div>
|
||||
<div class="co-log-container">
|
||||
<div id="co-log-content" class="co-log-content">{{ logs }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="co-log-viewer-new-logs" ng-show="hasNewLogs" ng-click="moveToBottom()">
|
||||
New Logs <i class="fa fa-lg fa-arrow-circle-down"></i>
|
||||
</div>
|
||||
</div>
|
3
static/directives/cor-option.html
Normal file
3
static/directives/cor-option.html
Normal file
|
@ -0,0 +1,3 @@
|
|||
<li>
|
||||
<a href="javascript:void(0)" ng-click="optionClick()" ng-transclude></a>
|
||||
</li>
|
6
static/directives/cor-options-menu.html
Normal file
6
static/directives/cor-options-menu.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
<span class="co-options-menu">
|
||||
<div class="dropdown" style="text-align: left;">
|
||||
<i class="fa fa-gear fa-lg dropdown-toggle" data-toggle="dropdown" data-title="Options" bs-tooltip></i>
|
||||
<ul class="dropdown-menu pull-right" ng-transclude></ul>
|
||||
</div>
|
||||
</span>
|
|
@ -2225,7 +2225,7 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
|||
templateUrl: '/static/partials/repo-list.html', controller: RepoListCtrl, reloadOnSearch: false}).
|
||||
when('/user/', {title: 'Account Settings', description:'Account settings for ' + title, templateUrl: '/static/partials/user-admin.html',
|
||||
reloadOnSearch: false, controller: UserAdminCtrl}).
|
||||
when('/superuser/', {title: 'Superuser Admin Panel', description:'Admin panel for ' + title, templateUrl: '/static/partials/super-user.html',
|
||||
when('/superuser/', {title: 'Enterprise Registry Setup', description:'Admin panel for ' + title, templateUrl: '/static/partials/super-user.html',
|
||||
reloadOnSearch: false, controller: SuperUserAdminCtrl, newLayout: true}).
|
||||
when('/guide/', {title: 'Guide', description:'Guide to using private docker repositories on ' + title,
|
||||
templateUrl: '/static/partials/guide.html',
|
||||
|
|
|
@ -2810,7 +2810,7 @@ function ManageApplicationCtrl($scope, $routeParams, $rootScope, $location, $tim
|
|||
}
|
||||
|
||||
|
||||
function SuperUserAdminCtrl($scope, ApiService, Features, UserService) {
|
||||
function SuperUserAdminCtrl($scope, $timeout, ApiService, Features, UserService, AngularPollChannel) {
|
||||
if (!Features.SUPER_USERS) {
|
||||
return;
|
||||
}
|
||||
|
@ -2822,6 +2822,52 @@ function SuperUserAdminCtrl($scope, ApiService, Features, UserService) {
|
|||
$scope.newUser = {};
|
||||
$scope.createdUsers = [];
|
||||
$scope.systemUsage = null;
|
||||
$scope.debugServices = null;
|
||||
$scope.debugLogs = null;
|
||||
$scope.pollChannel = null;
|
||||
$scope.logsScrolled = false;
|
||||
|
||||
$scope.viewSystemLogs = function(service) {
|
||||
if ($scope.pollChannel) {
|
||||
$scope.pollChannel.stop();
|
||||
}
|
||||
|
||||
$scope.debugService = service;
|
||||
$scope.debugLogs = null;
|
||||
|
||||
$scope.pollChannel = AngularPollChannel.create($scope, $scope.loadServiceLogs, 1 * 1000 /* 1s */);
|
||||
$scope.pollChannel.start();
|
||||
};
|
||||
|
||||
$scope.loadServiceLogs = function(callback) {
|
||||
if (!$scope.debugService) { return; }
|
||||
|
||||
var params = {
|
||||
'service': $scope.debugService
|
||||
};
|
||||
|
||||
var errorHandler = ApiService.errorDisplay('Cannot load system logs. Please contact support.',
|
||||
function() {
|
||||
callback(false);
|
||||
})
|
||||
|
||||
ApiService.getSystemLogs(null, params, /* background */true).then(function(resp) {
|
||||
$scope.debugLogs = resp['logs'];
|
||||
callback(true);
|
||||
}, errorHandler);
|
||||
};
|
||||
|
||||
$scope.loadDebugServices = function() {
|
||||
if ($scope.pollChannel) {
|
||||
$scope.pollChannel.stop();
|
||||
}
|
||||
|
||||
$scope.debugService = null;
|
||||
|
||||
ApiService.listSystemLogServices().then(function(resp) {
|
||||
$scope.debugServices = resp['services'];
|
||||
}, ApiService.errorDisplay('Cannot load system logs. Please contact support.'))
|
||||
};
|
||||
|
||||
$scope.getUsage = function() {
|
||||
if ($scope.systemUsage) { return; }
|
||||
|
|
|
@ -1,4 +1,96 @@
|
|||
angular.module("core-ui", [])
|
||||
.directive('corLogBox', function() {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 1,
|
||||
templateUrl: '/static/directives/cor-log-box.html',
|
||||
replace: true,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'logs': '=logs'
|
||||
},
|
||||
controller: function($rootScope, $scope, $element, $timeout) {
|
||||
$scope.hasNewLogs = false;
|
||||
|
||||
var scrollHandlerBound = false;
|
||||
var isAnimatedScrolling = false;
|
||||
var isScrollBottom = true;
|
||||
|
||||
var scrollHandler = function() {
|
||||
if (isAnimatedScrolling) { return; }
|
||||
var element = $element.find("#co-log-viewer")[0];
|
||||
isScrollBottom = element.scrollHeight - element.scrollTop === element.clientHeight;
|
||||
|
||||
if (isScrollBottom) {
|
||||
$scope.hasNewLogs = false;
|
||||
}
|
||||
};
|
||||
|
||||
var animateComplete = function() {
|
||||
isAnimatedScrolling = false;
|
||||
};
|
||||
|
||||
$scope.moveToBottom = function() {
|
||||
$scope.hasNewLogs = false;
|
||||
isAnimatedScrolling = true;
|
||||
isScrollBottom = true;
|
||||
|
||||
$element.find("#co-log-viewer").animate(
|
||||
{ scrollTop: $element.find("#co-log-content").height() }, "slow", null, animateComplete);
|
||||
};
|
||||
|
||||
$scope.$watch('logs', function(value, oldValue) {
|
||||
if (!value) { return; }
|
||||
|
||||
$timeout(function() {
|
||||
if (!scrollHandlerBound) {
|
||||
$element.find("#co-log-viewer").on('scroll', scrollHandler);
|
||||
scrollHandlerBound = true;
|
||||
}
|
||||
|
||||
if (!isScrollBottom) {
|
||||
$scope.hasNewLogs = true;
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.moveToBottom();
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
})
|
||||
|
||||
.directive('corOptionsMenu', function() {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 1,
|
||||
templateUrl: '/static/directives/cor-options-menu.html',
|
||||
replace: true,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {},
|
||||
controller: function($rootScope, $scope, $element) {
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
})
|
||||
|
||||
.directive('corOption', function() {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 1,
|
||||
templateUrl: '/static/directives/cor-option.html',
|
||||
replace: true,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'optionClick': '&optionClick'
|
||||
},
|
||||
controller: function($rootScope, $scope, $element) {
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
})
|
||||
|
||||
|
||||
.directive('corTitle', function() {
|
||||
var directiveDefinitionObject = {
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
|
||||
<div class="cor-tab-panel">
|
||||
<div class="cor-tabs">
|
||||
<!--<span class="cor-tab" tab-active="true" tab-title="Registry Settings" tab-target="#setup"
|
||||
<span class="cor-tab" tab-active="true" tab-title="Registry Settings" tab-target="#setup"
|
||||
tab-init="loadConfig()">
|
||||
<i class="fa fa-cog"></i>
|
||||
</span>-->
|
||||
</span>
|
||||
<span class="cor-tab" tab-title="Manage Users" tab-target="#users" tab-init="loadUsers()">
|
||||
<i class="fa fa-group"></i>
|
||||
</span>
|
||||
|
@ -19,12 +19,37 @@
|
|||
<span class="cor-tab" tab-title="Usage Logs" tab-target="#logs" tab-init="loadUsageLogs()">
|
||||
<i class="fa fa-bar-chart"></i>
|
||||
</span>
|
||||
<!--<span class="cor-tab" tab-title="Internal Logs and Debugging" tab-target="#debug">
|
||||
<span class="cor-tab" tab-title="Internal Logs and Debugging" tab-target="#debug" tab-init="loadDebugServices()">
|
||||
<i class="fa fa-bug"></i>
|
||||
</span>-->
|
||||
</span>
|
||||
</div> <!-- /cor-tabs -->
|
||||
|
||||
<div class="cor-tab-content">
|
||||
<!-- Setup tab -->
|
||||
<div id="setup" class="tab-pane active">
|
||||
setup
|
||||
</div>
|
||||
|
||||
<!-- Debugging tab -->
|
||||
<div id="debug" class="tab-pane">
|
||||
<div class="quay-spinner" ng-show="!debugServices"></div>
|
||||
|
||||
<div role="tabpanel" ng-show="debugServices">
|
||||
<!-- Nav tabs -->
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" ng-repeat="service in debugServices"
|
||||
ng-class="debugService == service ? 'active' : ''">
|
||||
<a href="javascript:void(0)" ng-click="viewSystemLogs(service)">{{ service }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="system-log-download-panel" ng-if="!debugService">
|
||||
Please choose a service above to view its logs.
|
||||
</div>
|
||||
<div class="cor-log-box" logs="debugLogs" ng-show="debugService"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logs tab -->
|
||||
<div id="logs" class="tab-pane">
|
||||
<div class="logsView" makevisible="logsCounter" all-logs="true"></div>
|
||||
|
@ -34,7 +59,7 @@
|
|||
<div id="usage-counter" class="tab-pane">
|
||||
<div class="quay-spinner" ng-show="systemUsage == null"></div>
|
||||
<div class="usage-chart" total="systemUsage.allowed" limit="systemUsageLimit"
|
||||
current="systemUsage.usage" usage-title="Deployed Repositories"></div>
|
||||
current="systemUsage.usage" usage-title="Deployed Containers"></div>
|
||||
|
||||
<!-- Alerts -->
|
||||
<div class="alert alert-danger" ng-show="systemUsageLimit == 'over' && systemUsage">
|
||||
|
@ -51,10 +76,12 @@
|
|||
You are nearing the number of allowed deployed repositories. It might be time to think about
|
||||
upgrading your subscription by contacting <a href="mailto:sales@coreos.com">CoreOS Sales</a>.
|
||||
</div>
|
||||
|
||||
For more information: <a href="https://coreos.com/products/enterprise-registry/plans/">See Here</a>.
|
||||
</div> <!-- /usage-counter tab-->
|
||||
|
||||
<!-- Users tab -->
|
||||
<div id="users" class="tab-pane active">
|
||||
<div id="users" class="tab-pane">
|
||||
<div class="quay-spinner" ng-show="!users"></div>
|
||||
<div class="alert alert-error" ng-show="usersError">
|
||||
{{ usersError }}
|
||||
|
@ -72,42 +99,37 @@
|
|||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<th style="width: 24px;"></th>
|
||||
<th>Username</th>
|
||||
<th>E-mail address</th>
|
||||
<th style="width: 24px;"></th>
|
||||
</thead>
|
||||
|
||||
<tr ng-repeat="current_user in (users | filter:search | orderBy:'username' | limitTo:100)"
|
||||
class="user-row"
|
||||
ng-class="current_user.super_user ? 'super-user' : ''">
|
||||
class="user-row">
|
||||
<td>
|
||||
<span class="avatar" hash="current_user.avatar" size="24"></span>
|
||||
</td>
|
||||
<td>
|
||||
<i class="fa fa-user" style="margin-right: 6px"></i>
|
||||
{{ current_user.username }}
|
||||
</td>
|
||||
<td>
|
||||
<a href="mailto:{{ current_user.email }}">{{ current_user.email }}</a>
|
||||
</td>
|
||||
<td style="text-align: center;">
|
||||
<i class="fa fa-ge fa-lg" ng-if="current_user.super_user" data-title="Super User" bs-tooltip></i>
|
||||
<div class="dropdown" style="text-align: left;"
|
||||
ng-if="user.username != current_user.username && !current_user.super_user">
|
||||
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="caret"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu pull-right">
|
||||
<li>
|
||||
<a href="javascript:void(0)" ng-click="showChangePassword(current_user)">
|
||||
<i class="fa fa-key"></i> Change Password
|
||||
</a>
|
||||
<a href="javascript:void(0)" ng-click="sendRecoveryEmail(current_user)" quay-show="Features.MAILING">
|
||||
<i class="fa fa-envelope"></i> Send Recovery Email
|
||||
</a>
|
||||
<a href="javascript:void(0)" ng-click="showDeleteUser(current_user)">
|
||||
<i class="fa fa-times"></i> Delete User
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<span class="cor-options-menu"
|
||||
ng-if="user.username != current_user.username && !current_user.super_user">
|
||||
<span class="cor-option" option-click="showChangePassword(current_user)">
|
||||
<i class="fa fa-key"></i> Change Password
|
||||
</span>
|
||||
<span class="cor-option" option-click="sendRecoveryEmail(current_user)"
|
||||
quay-show="Features.MAILING">
|
||||
<i class="fa fa-envelope"></i> Send Recovery Email
|
||||
</span>
|
||||
<span class="cor-option" option-click="showDeleteUser(current_user)">
|
||||
<i class="fa fa-times"></i> Delete User
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
Reference in a new issue