Merge branch 'quark'

This commit is contained in:
Joseph Schorr 2015-02-18 15:59:36 -05:00
commit 89eb5bdcc5
60 changed files with 637 additions and 133 deletions

View file

@ -8,3 +8,6 @@ class BaseComponent(ApplicationSession):
self.parent_manager = None
self.build_logs = None
self.user_files = None
def kind(self):
raise NotImplementedError

View file

@ -49,6 +49,9 @@ class BuildComponent(BaseComponent):
BaseComponent.__init__(self, config, **kwargs)
def kind(self):
return 'builder'
def onConnect(self):
self.join(self.builder_realm)

View file

@ -25,6 +25,9 @@ class DynamicRegistrationComponent(BaseComponent):
logger.debug('Registering new build component+worker with realm %s', realm)
return realm
def kind(self):
return 'registration'
class EnterpriseManager(BaseManager):
""" Build manager implementation for the Enterprise Registry. """
@ -82,5 +85,7 @@ class EnterpriseManager(BaseManager):
if build_component in self.ready_components:
self.ready_components.remove(build_component)
self.unregister_component(build_component)
def num_workers(self):
return len(self.all_components)

View file

@ -271,8 +271,6 @@ class EphemeralBuilderManager(BaseManager):
def build_component_disposed(self, build_component, timed_out):
logger.debug('Calling build_component_disposed.')
# TODO make it so that I don't have to unregister the component if it timed out
self.unregister_component(build_component)
@coroutine

View file

@ -1,5 +1,6 @@
import logging
import trollius
import json
from autobahn.asyncio.wamp import RouterFactory, RouterSessionFactory
from autobahn.asyncio.websocket import WampWebSocketServerFactory
@ -69,7 +70,21 @@ class BuilderServer(object):
@controller_app.route('/status')
def status():
return server._current_status
metrics = server._queue.get_metrics(require_transaction=False)
(running_count, available_not_running_count, available_count) = metrics
workers = [component for component in server._current_components
if component.kind() == 'builder']
data = {
'status': server._current_status,
'running_local': server._job_count,
'running_total': running_count,
'workers': len(workers),
'job_total': available_count + running_count
}
return json.dumps(data)
self._controller_app = controller_app

View file

@ -170,8 +170,7 @@ def _create_user(username, email):
pass
try:
new_user = User.create(username=username, email=email)
return new_user
return User.create(username=username, email=email)
except Exception as ex:
raise DataModelException(ex.message)

View file

@ -6,6 +6,12 @@ from util.morecollections import AttrDict
MINIMUM_EXTENSION = timedelta(seconds=20)
class NoopWith:
def __enter__(self):
pass
def __exit__(self, type, value, traceback):
pass
class WorkQueue(object):
def __init__(self, queue_name, transaction_factory,
@ -49,21 +55,32 @@ class WorkQueue(object):
def _item_by_id_for_update(self, queue_id):
return db_for_update(QueueItem.select().where(QueueItem.id == queue_id)).get()
def update_metrics(self):
if self._reporter is None:
return
with self._transaction_factory(db):
def get_metrics(self, require_transaction=True):
guard = self._transaction_factory(db) if require_transaction else NoopWith()
with guard:
now = datetime.utcnow()
name_match_query = self._name_match_query()
running_query = self._running_jobs(now, name_match_query)
running_count = running_query.distinct().count()
available_query = self._available_jobs_not_running(now, name_match_query, running_query)
available_query = self._available_jobs(now, name_match_query)
available_count = available_query.select(QueueItem.queue_name).distinct().count()
self._reporter(self._currently_processing, running_count, running_count + available_count)
available_not_running_query = self._available_jobs_not_running(now, name_match_query,
running_query)
available_not_running_count = (available_not_running_query.select(QueueItem.queue_name)
.distinct().count())
return (running_count, available_not_running_count, available_count)
def update_metrics(self):
if self._reporter is None:
return
(running_count, available_not_running_count, available_count) = self.get_metrics()
self._reporter(self._currently_processing, running_count,
running_count + available_not_running_count)
def put(self, canonical_name_list, message, available_after=0, retries_remaining=5):
"""

View file

@ -334,7 +334,11 @@ def validate_json_request(schema_name):
def wrapped(self, *args, **kwargs):
schema = self.schemas[schema_name]
try:
validate(request.get_json(), schema)
json_data = request.get_json()
if json_data is None:
raise InvalidRequest('Missing JSON body')
validate(json_data, schema)
return func(self, *args, **kwargs)
except ValidationError as ex:
raise InvalidRequest(ex.message)

View file

@ -50,6 +50,7 @@ class SuperUserRegistryStatus(ApiResource):
@verify_not_prod
def get(self):
""" Returns the status of the registry. """
# If there is no conf/stack volume, then report that status.
if not CONFIG_PROVIDER.volume_exists():
return {

View file

@ -4,13 +4,62 @@ import json
from flask import request, Blueprint, abort, Response
from flask.ext.login import current_user
from auth.auth import require_session_login
from app import userevents
from endpoints.common import route_show_if
from app import app, userevents
from auth.permissions import SuperUserPermission
import features
import psutil
import time
logger = logging.getLogger(__name__)
realtime = Blueprint('realtime', __name__)
@realtime.route("/ps")
@route_show_if(features.SUPER_USERS)
@require_session_login
def ps():
if not SuperUserPermission().can():
abort(403)
def generator():
while True:
build_status = {}
try:
builder_data = app.config['HTTPCLIENT'].get('http://localhost:8686/status', timeout=1)
if builder_data.status_code == 200:
build_status = json.loads(builder_data.text)
except:
pass
try:
data = {
'count': {
'cpu': psutil.cpu_percent(interval=1, percpu=True),
'virtual_mem': psutil.virtual_memory(),
'swap_mem': psutil.swap_memory(),
'connections': len(psutil.net_connections()),
'processes': len(psutil.pids()),
'network': psutil.net_io_counters()
},
'build': build_status
}
except psutil.AccessDenied:
data = {}
json_string = json.dumps(data)
yield 'data: %s\n\n' % json_string
time.sleep(1)
try:
return Response(generator(), mimetype="text/event-stream")
except:
pass
@realtime.route("/user/")
@require_session_login
def index():

View file

@ -6,7 +6,7 @@ LOCAL_DIRECTORY = 'static/ldn/'
EXTERNAL_JS = [
'code.jquery.com/jquery.js',
'netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js',
'netdna.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js',
'ajax.googleapis.com/ajax/libs/angularjs/1.2.9/angular.min.js',
'ajax.googleapis.com/ajax/libs/angularjs/1.2.9/angular-route.min.js',
'ajax.googleapis.com/ajax/libs/angularjs/1.2.9/angular-sanitize.min.js',
@ -19,7 +19,7 @@ EXTERNAL_JS = [
EXTERNAL_CSS = [
'netdna.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.css',
'netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.no-icons.min.css',
'netdna.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css',
'fonts.googleapis.com/css?family=Source+Sans+Pro:400,700',
]

View file

@ -47,3 +47,4 @@ pyOpenSSL
pygpgme
cachetools
mock
psutil

View file

@ -39,6 +39,7 @@ mixpanel-py==3.2.1
mock==1.0.1
paramiko==1.15.2
peewee==2.4.7
psutil==2.2.1
psycopg2==2.5.4
py-bcrypt==0.4
pycrypto==2.6.1

View file

@ -702,4 +702,19 @@
.co-alert .co-step-bar {
float: right;
margin-top: 6px;
}
.realtime-area-chart, .realtime-line-chart {
margin: 10px;
text-align: center;
}
.rickshaw_graph {
overflow: hidden;
padding-bottom: 40px;
}
.cor-container {
padding-left: 15px;
padding-right: 15px;
}

View file

@ -3,6 +3,10 @@
margin: 0;
}
.btn {
outline: none !important;
}
@media (max-width: 410px) {
.olrk-normal {
display: none;
@ -159,7 +163,7 @@ nav.navbar-default .navbar-nav>li>a.active {
.notification-view-element .circle {
position: absolute;
top: 14px;
top: 15px;
left: 0px;
width: 12px;
@ -179,13 +183,13 @@ nav.navbar-default .navbar-nav>li>a.active {
margin-bottom: 4px;
}
.notification-view-element .container {
.notification-view-element .notification-content {
padding: 10px;
border-radius: 6px;
margin-left: 16px;
}
.notification-view-element .container:hover {
.notification-view-element .notification-content:hover {
background: rgba(66, 139, 202, 0.1);
}
@ -1140,59 +1144,59 @@ i.toggle-icon:hover {
}
.visible-sm-inline {
display: none;
display: none !important;
}
.visible-md-inline {
display: none;
display: none !important;
}
.hidden-sm-inline {
display: inline;
display: inline !important;
}
.hidden-xs-inline {
display: inline;
display: inline !important;
}
@media (min-width: 991px) {
.visible-md-inline {
display: inline;
display: inline !important;
}
}
@media (max-width: 991px) and (min-width: 768px) {
.visible-sm-inline {
display: inline;
display: inline !important;
}
.hidden-sm-inline {
display: none;
display: none !important;
}
}
@media (max-width: 700px) {
.hidden-xs-inline {
display: none;
display: none !important;
}
}
.visible-xl {
display: none;
display: none !important;
}
.visible-xl-inline {
display: none;
display: none !important;
}
@media (min-width: 1200px) {
.visible-xl {
display: block;
display: block !important;
}
.visible-xl-inline {
display: inline-block;
display: inline-block !important;
}
}
@ -1413,6 +1417,10 @@ i.toggle-icon:hover {
background: transparent;
}
.jumbotron p {
font-size: 100%;
}
.jumbotron .disclaimer-link {
font-size: .3em;
vertical-align: 23px;
@ -1483,6 +1491,8 @@ i.toggle-icon:hover {
.landing-content {
z-index: 2;
padding-left: 20px;
padding-right: 20px;
}
.landing .call-to-action i.fa {
@ -3762,7 +3772,7 @@ p.editable:hover i {
text-align: center;
position: relative;
color: white;
left: -42px;
left: -38px;
top: -9px;
font-weight: bold;
font-size: .4em;
@ -3772,23 +3782,6 @@ p.editable:hover i {
margin-bottom: 40px;
}
.landing .social-alternate {
color: #777;
font-size: 2em;
margin-left: 43px;
line-height: 1em;
}
.landing .social-alternate .inner-text {
text-align: center;
position: relative;
color: white;
left: -43px;
top: -9px;
font-weight: bold;
font-size: .4em;
}
.contact-options {
margin-top: 60px;
}
@ -4972,3 +4965,9 @@ i.slack-icon {
left: 16px;
font-size: 28px;
}
.chart-col h4, .chart-col h5 {
display: block;
text-align: center;
}

View file

@ -1,7 +1,7 @@
<div class="application-manager-element">
<div class="quay-spinner" ng-show="loading"></div>
<div class="container" ng-show="!loading">
<div class="cor-container" ng-show="!loading">
<div class="side-controls">
<span class="popup-input-button" placeholder="'Application Name'" submitted="createApplication(value)">
<i class="fa fa-plus"></i> Create New Application

View file

@ -1,5 +1,5 @@
<div class="config-setup-tool-element">
<div class="quay-spinner" ng-if="!config"></div>
<div class="cor-loader" ng-if="!config"></div>
<div ng-show="config && config['SUPER_USERS']">
<form id="configform" name="configform">
@ -289,7 +289,7 @@
<div class="description">
<p>
Authentication for the registry can be handled by either the registry itself or LDAP.
External authentication providers (such as Github) can be used on top of this choice.
External authentication providers (such as GitHub) can be used on top of this choice.
</p>
</div>
@ -339,20 +339,20 @@
</div>
</div> <!-- /Authentication -->
<!-- Github Authentication -->
<!-- GitHub Authentication -->
<div class="co-panel">
<div class="co-panel-heading">
<i class="fa fa-github"></i> Github (Enterprise) Authentication
<i class="fa fa-github"></i> GitHub (Enterprise) Authentication
</div>
<div class="co-panel-body">
<div class="description">
<p>
If enabled, users can use Github or Github Enterprise to authenticate to the registry.
If enabled, users can use GitHub or GitHub Enterprise to authenticate to the registry.
</p>
<p>
<strong>Note:</strong> A registered Github (Enterprise) OAuth application is required.
<strong>Note:</strong> A registered GitHub (Enterprise) OAuth application is required.
View instructions on how to
<a href="https://coreos.com/docs/enterprise-registry/github-auth/" target="_blank">
<a href="https://coreos.com/docs/enterprise-registry/github-app/" target="_blank">
Create an OAuth Application in GitHub
</a>
</p>
@ -360,21 +360,21 @@
<div class="co-checkbox">
<input id="ftghl" type="checkbox" ng-model="config.FEATURE_GITHUB_LOGIN">
<label for="ftghl">Enable Github Authentication</label>
<label for="ftghl">Enable GitHub Authentication</label>
</div>
<table class="config-table" ng-if="config.FEATURE_GITHUB_LOGIN">
<tr>
<td>Github:</td>
<td>GitHub:</td>
<td>
<select ng-model="mapped.GITHUB_LOGIN_KIND">
<option value="hosted">Github.com</option>
<option value="enterprise">Github Enterprise</option>
<option value="hosted">GitHub.com</option>
<option value="enterprise">GitHub Enterprise</option>
</select>
</td>
</tr>
<tr ng-if="mapped.GITHUB_LOGIN_KIND == 'enterprise'">
<td>Github Endpoint:</td>
<td>GitHub Endpoint:</td>
<td>
<span class="config-string-field"
binding="config.GITHUB_LOGIN_CONFIG.GITHUB_ENDPOINT"
@ -382,7 +382,7 @@
pattern="{{ GITHUB_REGEX }}">
</span>
<div class="help-text">
The Github Enterprise endpoint. Must start with http:// or https://.
The GitHub Enterprise endpoint. Must start with http:// or https://.
</div>
</td>
</tr>
@ -402,7 +402,7 @@
</tr>
</table>
</div>
</div> <!-- /Github Authentication -->
</div> <!-- /GitHub Authentication -->
<!-- Google Authentication -->
<div class="co-panel">
@ -471,20 +471,20 @@
</div> <!-- /Build Support -->
<!-- Github Trigger -->
<!-- GitHub Trigger -->
<div class="co-panel" ng-if="config.FEATURE_BUILD_SUPPORT" style="margin-top: 20px;">
<div class="co-panel-heading">
<i class="fa fa-github"></i> Github (Enterprise) Build Triggers
<i class="fa fa-github"></i> GitHub (Enterprise) Build Triggers
</div>
<div class="co-panel-body">
<div class="description">
<p>
If enabled, users can setup Github or Github Enterprise triggers to invoke Registry builds.
If enabled, users can setup GitHub or GitHub Enterprise triggers to invoke Registry builds.
</p>
<p>
<strong>Note:</strong> A registered Github (Enterprise) OAuth application (<strong>separate from Github Authentication</strong>) is required.
<strong>Note:</strong> A registered GitHub (Enterprise) OAuth application (<strong>separate from GitHub Authentication</strong>) is required.
View instructions on how to
<a href="https://coreos.com/docs/enterprise-registry/github-auth/" target="_blank">
<a href="https://coreos.com/docs/enterprise-registry/github-app/" target="_blank">
Create an OAuth Application in GitHub
</a>
</p>
@ -492,21 +492,21 @@
<div class="co-checkbox">
<input id="ftgb" type="checkbox" ng-model="config.FEATURE_GITHUB_BUILD">
<label for="ftgb">Enable Github Triggers</label>
<label for="ftgb">Enable GitHub Triggers</label>
</div>
<table class="config-table" ng-if="config.FEATURE_GITHUB_BUILD">
<tr>
<td>Github:</td>
<td>GitHub:</td>
<td>
<select ng-model="mapped.GITHUB_TRIGGER_KIND">
<option value="hosted">Github.com</option>
<option value="enterprise">Github Enterprise</option>
<option value="hosted">GitHub.com</option>
<option value="enterprise">GitHub Enterprise</option>
</select>
</td>
</tr>
<tr ng-if="mapped.GITHUB_TRIGGER_KIND == 'enterprise'">
<td>Github Endpoint:</td>
<td>GitHub Endpoint:</td>
<td>
<span class="config-string-field"
binding="config.GITHUB_TRIGGER_CONFIG.GITHUB_ENDPOINT"
@ -514,7 +514,7 @@
pattern="{{ GITHUB_REGEX }}">
</span>
<div class="help-text">
The Github Enterprise endpoint. Must start with http:// or https://.
The GitHub Enterprise endpoint. Must start with http:// or https://.
</div>
</td>
</tr>
@ -534,7 +534,7 @@
</tr>
</table>
</div>
</div> <!-- /Github Trigger -->
</div> <!-- /GitHub Trigger -->
</form>
<!-- Save Bar -->

View file

@ -1,3 +1,3 @@
<div class="co-main-content-panel co-tab-panel co-fx-box-shadow-heavy">
<div class="container co-tab-container" ng-transclude></div>
<div class="co-tab-container" ng-transclude></div>
</div>

View file

@ -1,8 +1,8 @@
<div class="dockerfile-build-form-element">
<div class="container" ng-show="building">
<div ng-show="building">
<div class="quay-spinner"></div>
</div>
<div class="container" ng-show="uploading">
<div ng-show="uploading">
<span class="message">Uploading file {{ upload_file }}</span>
<div class="progress progress-striped active">
<div class="progress-bar" role="progressbar" aria-valuenow="{{ upload_progress }}" aria-valuemin="0" aria-valuemax="100" style="{{ 'width: ' + upload_progress + '%' }}">
@ -10,7 +10,7 @@
</div>
</div>
<div class="container" ng-show="!uploading && !building">
<div ng-show="!uploading && !building">
<table>
<tr>
<td style="vertical-align: middle;">Dockerfile or <code>.tar.gz</code> or <code>.zip</code>:</td>

View file

@ -1,4 +1,4 @@
<div class="container loading-status-element">
<div class="loading-status-element">
<div ng-show="hasError && !loading">
<span ng-transclude></span>
</div>

View file

@ -1,6 +1,6 @@
<div class="logs-view-element">
<div class="container header">
<div class="header">
<span class="header-text">
<span ng-show="!performer">Usage Logs</span>
<span class="entity-reference" entity="performer" ng-show="performer"></span>
@ -20,9 +20,7 @@
</span>
</div>
<div ng-show="loading">
<div class="quay-spinner 3x"></div>
</div>
<div class="cor-loader-inline" ng-show="loading"></div>
<div ng-show="!loading">
<div id="bar-chart" style="width: 800px; height: 500px;" ng-show="chartVisible">
<svg style="width: 800px; height: 500px;"></svg>

View file

@ -1,5 +1,5 @@
<div class="notification-view-element">
<div class="container" ng-click="showNotification();">
<div class="notification-content" ng-click="showNotification();">
<div class="circle" ng-class="getClass(notification)"></div>
<div class="message" ng-bind-html="getMessage(notification)"></div>
<div class="orginfo" ng-if="notification.organization">

View file

@ -0,0 +1,75 @@
<div class="ps-usage-graph-element">
<!-- Build Charts -->
<div quay-show="Features.BUILD_SUPPORT">
<div class="alert alert-warning" ng-if="data.build && data.build.job_total == null">
Cannot load build system status. Please restart your container.
</div>
<div ng-if="data.build && data.build.job_total >= 0">
<div class="col-md-6 chart-col">
<h4>Build Queue</h4>
<h5>
Running Jobs: {{ data.build.running_total }} | Total Jobs: {{ data.build.job_total }}
</h5>
<div class="realtime-area-chart"
data="[data.build.job_total, data.build.running_total]"
labels="['Queued Build Jobs', 'Running Build Jobs']"
colors="['rgb(157, 194, 211)', 'rgb(56, 122, 163)']"
counter="counter"
minimum="-10"
maximum="auto"></div>
</div>
<div class="col-md-6 chart-col">
<h4>Local Build Workers</h4>
<h5>
Local Workers: {{ data.build.workers }} | Working: {{ data.build.running_local }}
</h5>
<div class="realtime-area-chart"
data="[data.build.job_total, data.build.workers, data.build.running_local]"
labels="['Queued Build Jobs', 'Build Workers (local)', 'Running Build Jobs (local)']"
colors="['rgb(157, 194, 211)', 'rgb(161, 208, 93)', 'rgb(210, 237, 130)']"
counter="counter"
minimum="-10"
maximum="auto"></div>
</div>
</div>
</div>
<!-- CPU, Memory and Network -->
<div class="col-md-4 chart-col">
<h4>CPU Usage %</h4>
<div class="realtime-line-chart" data="data.count.cpu" counter="counter"
label-template="CPU #{x} %"
minimum="-10" maximum="110"></div>
</div>
<div class="col-md-4 chart-col">
<h4>Process Count</h4>
<div class="realtime-line-chart" data="data.count.processes" counter="counter"
label-template="Process Count"></div>
</div>
<div class="col-md-4 chart-col">
<h4>Virtual Memory %</h4>
<div class="realtime-line-chart" data="data.count.virtual_mem[2]" counter="counter"
label-template="Virtual Memory %"
minimum="-10" maximum="110"></div>
</div>
<div class="col-md-4 chart-col">
<h4>Swap Memory</h4>
<div class="realtime-line-chart" data="data.count.swap_mem[3]" counter="counter"
label-template="Swap Memory %"></div>
</div>
<div class="col-md-4 chart-col">
<h4>Network Connections</h4>
<div class="realtime-line-chart" data="data.count.connections" counter="counter"
label-template="Network Connection Count"></div>
</div>
<div class="col-md-4 chart-col">
<h4>Network Usage (Bytes)</h4>
<div class="realtime-line-chart" data="data.count.network" labels="['Bytes In', 'Bytes Out']" counter="counter"></div>
</div>
</div>

View file

@ -0,0 +1,6 @@
<div class="realtime-area-chart-element">
<div ng-show="counter >= 1">
<div class="chart"></div>
</div>
<div class="cor-loader-inline" ng-if="counter < 1"></div>
</div>

View file

@ -0,0 +1,6 @@
<div class="realtime-line-chart-element">
<div ng-show="counter >= 1">
<div class="chart"></div>
</div>
<div class="cor-loader-inline" ng-if="counter < 1"></div>
</div>

View file

@ -2,7 +2,7 @@
<div class="quay-spinner" ng-show="loading"></div>
<div class="alert alert-info">Robot accounts allow for delegating access in multiple repositories to role-based accounts that you manage</div>
<div class="container" ng-show="!loading">
<div ng-show="!loading">
<div class="side-controls">
<span class="popup-input-button" pattern="ROBOT_PATTERN" placeholder="'Robot Account Name'"
submitted="createRobot(value)">

View file

@ -6612,6 +6612,54 @@ quayApp.directive('locationView', function () {
});
quayApp.directive('psUsageGraph', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/ps-usage-graph.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'isEnabled': '=isEnabled'
},
controller: function($scope, $element) {
$scope.counter = -1;
$scope.data = null;
var source = null;
var connect = function() {
if (source) { return; }
source = new EventSource('/realtime/ps');
source.onmessage = function(e) {
$scope.$apply(function() {
$scope.counter++;
$scope.data = JSON.parse(e.data);
});
};
};
var disconnect = function() {
if (!source) { return; }
source.close();
source = null;
};
$scope.$watch('isEnabled', function(value) {
if (value) {
connect();
} else {
disconnect();
}
});
$scope.$on("$destroy", disconnect);
}
};
return directiveDefinitionObject;
});
quayApp.directive('avatar', function () {
var directiveDefinitionObject = {
priority: 0,

View file

@ -17,6 +17,11 @@ function SuperUserAdminCtrl($scope, $timeout, ApiService, Features, UserService,
$scope.pollChannel = null;
$scope.logsScrolled = false;
$scope.csrf_token = encodeURIComponent(window.__token);
$scope.dashboardActive = false;
$scope.setDashboardActive = function(active) {
$scope.dashboardActive = active;
};
$scope.configurationSaved = function() {
$scope.requiresRestart = true;

View file

@ -205,6 +205,10 @@ angular.module("core-config-setup", ['angularFileUpload'])
};
var getKey = function(config, path) {
if (!config) {
return null;
}
var parts = path.split('.');
var current = config;
for (var i = 0; i < parts.length; ++i) {
@ -303,10 +307,10 @@ angular.module("core-config-setup", ['angularFileUpload'])
if (!value) { return; }
ApiService.scGetConfig().then(function(resp) {
$scope.config = resp['config'];
$scope.config = resp['config'] || {};
initializeMappedLogic($scope.config);
$scope.mapped['$hasChanges'] = false;
});
}, ApiService.errorDisplay('Could not load config'));
});
}
};

View file

@ -270,9 +270,18 @@ angular.module("core-ui", [])
'tabActive': '@tabActive',
'tabTitle': '@tabTitle',
'tabTarget': '@tabTarget',
'tabInit': '&tabInit'
'tabInit': '&tabInit',
'tabShown': '&tabShown',
'tabHidden': '&tabHidden'
},
controller: function($rootScope, $scope, $element) {
$element.find('a[data-toggle="tab"]').on('hidden.bs.tab', function (e) {
$scope.tabHidden({});
});
$element.find('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
$scope.tabShown({});
});
}
};
return directiveDefinitionObject;
@ -297,6 +306,236 @@ angular.module("core-ui", [])
return directiveDefinitionObject;
})
.directive('realtimeAreaChart', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/realtime-area-chart.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'data': '=data',
'labels': '=labels',
'colors': '=colors',
'counter': '=counter'
},
controller: function($scope, $element) {
var graph = null;
var series = [];
var palette = new Rickshaw.Color.Palette( { scheme: 'spectrum14' } );
var colors = $scope.colors || [];
var setupGraph = function() {
for (var i = 0; i < $scope.labels.length; ++i) {
series.push({
name: $scope.labels[i],
color: i >= colors.length ? palette.color(): $scope.colors[i],
stroke: 'rgba(0,0,0,0.15)',
data: []
});
}
var options = {
element: $element.find('.chart')[0],
renderer: 'area',
stroke: true,
series: series,
min: 0,
padding: {
'top': 0.3,
'left': 0,
'right': 0,
'bottom': 0.3
}
};
if ($scope.minimum != null) {
options['min'] = $scope.minimum == 'auto' ? 'auto' : $scope.minimum * 1;
} else {
options['min'] = 0;
}
if ($scope.maximum != null) {
options['max'] = $scope.maximum == 'auto' ? 'auto' : $scope.maximum * 1;
}
graph = new Rickshaw.Graph(options);
xaxes = new Rickshaw.Graph.Axis.Time({
graph: graph,
timeFixture: new Rickshaw.Fixtures.Time.Local()
});
yaxes = new Rickshaw.Graph.Axis.Y({
graph: graph,
tickFormat: Rickshaw.Fixtures.Number.formatKMBT
});
hoverDetail = new Rickshaw.Graph.HoverDetail({
graph: graph,
xFormatter: function(x) {
return new Date(x * 1000).toString();
}
});
};
var refresh = function(data) {
if (!data || $scope.counter < 0) { return; }
if (!graph) {
setupGraph();
}
var timecode = new Date().getTime() / 1000;
for (var i = 0; i < $scope.data.length; ++i) {
var arr = series[i].data;
arr.push(
{'x': timecode, 'y': $scope.data[i] }
);
if (arr.length > 10) {
series[i].data = arr.slice(arr.length - 10, arr.length);
}
}
graph.renderer.unstack = true;
graph.update();
};
$scope.$watch('counter', function() {
refresh($scope.data_raw);
});
$scope.$watch('data', function(data) {
$scope.data_raw = data;
refresh($scope.data_raw);
});
}
};
return directiveDefinitionObject;
})
.directive('realtimeLineChart', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/realtime-line-chart.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'data': '=data',
'labels': '=labels',
'counter': '=counter',
'labelTemplate': '@labelTemplate',
'minimum': '@minimum',
'maximum': '@maximum'
},
controller: function($scope, $element) {
var graph = null;
var xaxes = null;
var yaxes = null;
var hoverDetail = null;
var series = [];
var counter = 0;
var palette = new Rickshaw.Color.Palette( { scheme: 'spectrum14' } );
var setupGraph = function() {
var options = {
element: $element.find('.chart')[0],
renderer: 'line',
series: series,
padding: {
'top': 0.3,
'left': 0,
'right': 0,
'bottom': 0.3
}
};
if ($scope.minimum != null) {
options['min'] = $scope.minimum == 'auto' ? 'auto' : $scope.minimum * 1;
} else {
options['min'] = 0;
}
if ($scope.maximum != null) {
options['max'] = $scope.maximum == 'auto' ? 'auto' : $scope.maximum * 1;
}
graph = new Rickshaw.Graph(options);
xaxes = new Rickshaw.Graph.Axis.Time({
graph: graph,
timeFixture: new Rickshaw.Fixtures.Time.Local()
});
yaxes = new Rickshaw.Graph.Axis.Y({
graph: graph,
tickFormat: Rickshaw.Fixtures.Number.formatKMBT
});
hoverDetail = new Rickshaw.Graph.HoverDetail({
graph: graph,
xFormatter: function(x) {
return new Date(x * 1000).toString();
}
});
};
var refresh = function(data) {
if (data == null) { return; }
if (!graph) {
setupGraph();
}
if (typeof data == 'number') {
data = [data];
}
if ($scope.labels) {
data = data.slice(0, $scope.labels.length);
}
if (series.length == 0){
for (var i = 0; i < data.length; ++i) {
var title = $scope.labels ? $scope.labels[i] : $scope.labelTemplate.replace('{x}', i + 1);
series.push({
'color': palette.color(),
'data': [],
'name': title
})
}
}
counter++;
var timecode = new Date().getTime() / 1000;
for (var i = 0; i < data.length; ++i) {
var arr = series[i].data;
arr.push({
'x': timecode,
'y': data[i]
})
if (arr.length > 10) {
series[i].data = arr.slice(arr.length - 10, arr.length);
}
}
graph.update();
};
$scope.$watch('counter', function(counter) {
refresh($scope.data_raw);
});
$scope.$watch('data', function(data) {
$scope.data_raw = data;
});
}
};
return directiveDefinitionObject;
})
.directive('corStepBar', function() {
var directiveDefinitionObject = {
priority: 4,

1
static/lib/rickshaw.min.css vendored Normal file

File diff suppressed because one or more lines are too long

3
static/lib/rickshaw.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -1,4 +1,4 @@
<div class="container about-us">
<div class="cor-container about-us">
<h2>
About Us
</h2>

View file

@ -1,8 +1,8 @@
<div class="resource-view" resource="repository_build" error-message="'No matching repository build found'"></div>
<div class="container repo repo-build" ng-show="accessDenied">
<div class="cor-container repo repo-build" ng-show="accessDenied">
You do not have permission to view this page
</div>
<div class="container repo repo-build repo-build-pack" ng-show="repobuild">
<div class="cor-container repo repo-build repo-build-pack" ng-show="repobuild">
<div class="header row">
<a href="{{ '/repository/' + repo.namespace + '/' + repo.name + '/build' }}" class="back"><i class="fa fa-chevron-left"></i></a>
<h3>

View file

@ -1,5 +1,5 @@
<div class="confirm-invite">
<div class="container signin-container">
<div class="cor-container signin-container">
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
<div class="user-setup" ng-show="user.anonymous" redirect-url="redirectUrl"

View file

@ -1,4 +1,4 @@
<div class="container">
<div class="cor-container">
<h2>
Contact Us<br>
<small>We are here to help!</small>

View file

@ -1,4 +1,4 @@
<div class="container">
<div class="cor-container">
<h3>Redirecting...</h3>
<META http-equiv="refresh" content="0;URL=http://docs.quay.io/solution/getting-started.html">
If this page does not redirect, please <a href="http://docs.quay.io/solution/getting-started.html"> click here</a>.

View file

@ -1,5 +1,5 @@
<div class="resource-view" resource="image" error-message="'No image found'">
<div class="container repo repo-image-view">
<div class="cor-container repo repo-image-view">
<div class="header">
<a href="{{ '/repository/' + repo.namespace + '/' + repo.name }}" class="back"><i class="fa fa-chevron-left"></i></a>
<h3>

View file

@ -2,7 +2,7 @@
<div class="landing-background" ng-class="user.anonymous ? 'landing': 'signedin'"></div>
<div class="landing-filter" ng-class="user.anonymous ? 'landing': 'signedin'"></div>
<div class="landing-content">
<div class="container">
<div class="cor-container">
<div class="row messages">
<div class="col-md-7">
<div ng-show="user.anonymous" style="text-align: center">
@ -54,6 +54,6 @@
</div>
</div> <!-- col -->
</div> <!-- row -->
</div> <!-- container -->
</div> <!-- cor-container -->
</div>
</div> <!-- jumbotron -->

View file

@ -13,7 +13,7 @@
<div class="landing-background" ng-class="user.anonymous ? 'landing': 'signedin'"></div>
<div class="landing-filter" ng-class="user.anonymous ? 'landing': 'signedin'"></div>
<div class="landing-content">
<div class="container">
<div class="cor-container">
<div class="row messages">
<div class="col-md-7">
<div ng-show="user.anonymous">
@ -70,7 +70,7 @@
<div class="rows" ng-show="user.anonymous">
<div class=" shoutout-row landing-section">
<div class="container">
<div class="cor-container">
<div class="row">
<div class="col-md-4 shoutout">
<i class="fa fa-lock"></i>
@ -105,7 +105,7 @@
<div class=" landing-section">
<h2>Trusted by companies who use Docker</h2>
<div class="container">
<div class="cor-container">
<div class="row">
<div class="trusted-logos">
<div class="col-md-3 trusted-logo">
@ -127,7 +127,7 @@
<div class="landing-section">
<h2>Built with our users in mind</h2>
<div class="container">
<div class="cor-container">
<div class="row">
<div class="col-lg-8 feature-shoutout">
<img id="screenshot" ng-src="{{ '/static/img/' + currentScreenshot + '.png' }}" class="img-responsive">
@ -171,7 +171,7 @@
<div class="landing-section">
<h2>Seamlessly integrate into your Docker-based infrastructure</h2>
<div class="container">
<div class="cor-container">
<div class="row testimonial">
<div class="message">
Quay.io has become an essential part of our infrastructure as we move to Docker-based deploys.
@ -192,7 +192,7 @@
<div class="landing-section">
<h2>See what other people are saying about Quay.io</h2>
<div class="container">
<div class="cor-container">
<div class="row">
<div class="jcarousel-wrapper">
<div class="jcarousel">
@ -263,7 +263,7 @@
<div class="landing-section">
<h2>Start pushing to Quay.io in under a minute</h2>
<div class="container">
<div class="cor-container">
<div class="row landing-action">
<a href="/plans" class="btn btn-primary">Start Free Trial</a>
</div>
@ -272,7 +272,7 @@
</div>
<div class="container" ng-if="user.anonymous">
<div class="cor-container" ng-if="user.anonymous">
<div class="row">
<div style="border-top: 1px solid #eee; padding-top: 20px;" class="col-md-12">
<a href="https://mixpanel.com/f/partner"><img src="//cdn.mxpnl.com/site_media/images/partner/badge_light.png" alt="Mobile Analytics" /></a>

View file

@ -2,7 +2,7 @@
</div>
<div ng-show="application">
<div class="container manage-application">
<div class="cor-container manage-application">
<!-- Header -->
<div class="row">
<div class="col-md-12">

View file

@ -2,7 +2,7 @@
<div class="quay-spinner"></div>
</div>
<div class="container create-org" ng-show="!creating">
<div class="cor-container create-org" ng-show="!creating">
<div class="row header-row">
<div class="col-md-12">

View file

@ -1,18 +1,18 @@
<div class="container" ng-show="user.anonymous">
<div class="cor-container" ng-show="user.anonymous">
<div class="col-sm-6 col-sm-offset-3">
<div class="user-setup" redirect-url="'/new/'"></div>
</div>
</div>
<div class="container" ng-show="!user.anonymous && building">
<div class="cor-container" ng-show="!user.anonymous && building">
<div class="quay-spinner"></div>
</div>
<div class="container" ng-show="!user.anonymous && creating">
<div class="cor-container" ng-show="!user.anonymous && creating">
<div class="quay-spinner"></div>
</div>
<div class="container new-repo" ng-show="!user.anonymous && !creating && !building">
<div class="cor-container new-repo" ng-show="!user.anonymous && !creating && !building">
<form method="post" name="newRepoForm" id="newRepoForm" ng-submit="createNewRepo()">
<!-- Header -->

View file

@ -1,5 +1,5 @@
<div class="resource-view" resource="orgResource" error-message="'No organization found'"></div>
<div class="org-admin container" ng-show="organization">
<div class="org-admin cor-container" ng-show="organization">
<div class="organization-header" organization="organization" clickable="true"></div>
<div class="row">

View file

@ -1,5 +1,5 @@
<div class="resource-view" resource="memberResource" error-message="'Member not found'">
<div class="org-member-logs container">
<div class="org-member-logs cor-container">
<div class="organization-header" organization="organization" clickable="true"></div>
<div class="logs-view" organization="organization" performer="memberInfo"
makevisible="organization && memberInfo && ready"></div>

View file

@ -1,5 +1,5 @@
<div class="resource-view" resource="orgResource" error-message="'No matching organization found'">
<div class="org-view container">
<div class="org-view cor-container">
<div class="organization-header" organization="organization">
<div class="header-buttons" ng-show="organization.is_admin">

View file

@ -1,4 +1,4 @@
<div class="container org-list conntent-container">
<div class="cor-container org-list conntent-container">
<div class="loading" ng-show="!user">
<div class="quay-spinner"></div>
</div>

View file

@ -1,4 +1,4 @@
<div class="container plans content-container">
<div class="cor-container plans content-container">
<div class="row plans-list">
<div class="col-sm-2">
<div class="features-bar hidden-xs">

View file

@ -1,9 +1,9 @@
<div class="container" ng-show="deleting"><div class="quay-spinner"></div></div>
<div class="cor-container" ng-show="deleting"><div class="quay-spinner"></div></div>
<div class="resource-view" resource="repository" error-message="'No repository found'"></div>
<div class="container repo repo-admin" ng-show="accessDenied">
<div class="cor-container repo repo-admin" ng-show="accessDenied">
You do not have permission to view this page
</div>
<div class="container repo repo-admin" ng-show="repo && !deleting">
<div class="cor-container repo repo-admin" ng-show="repo && !deleting">
<div class="header row">
<a href="{{ '/repository/' + repo.namespace + '/' + repo.name }}" class="back"><i class="fa fa-chevron-left"></i></a>
<h3>

View file

@ -1,8 +1,8 @@
<div class="resource-view" resource="repository" error-message="'No repository found'"></div>
<div class="container repo repo-build" ng-show="accessDenied">
<div class="cor-container repo repo-build" ng-show="accessDenied">
You do not have permission to view this page
</div>
<div class="container repo repo-build" ng-show="repo">
<div class="cor-container repo repo-build" ng-show="repo">
<div class="header row">
<a href="{{ '/repository/' + repo.namespace + '/' + repo.name }}" class="back"><i class="fa fa-chevron-left"></i></a>
<h3>

View file

@ -1,4 +1,4 @@
<div class="container">
<div class="cor-container">
<div class="repo-list" ng-show="!user.anonymous">
<div ng-class="user.organizations.length ? 'section-header' : ''">
<div class="button-bar-right">

View file

@ -1,4 +1,4 @@
<div class="container">
<div class="cor-container">
<div class="row">
<div class="col-md-12">
<h1>Quay.io Security</h1>

View file

@ -1,4 +1,4 @@
<div class="container signin-container">
<div class="cor-container signin-container">
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
<div class="user-setup" redirect-url="redirectUrl"></div>

View file

@ -20,6 +20,10 @@
tab-target="#users" tab-init="loadUsers()">
<i class="fa fa-group"></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>
</span>
<span class="cor-tab" tab-title="Container Usage" tab-target="#usage-counter" tab-init="getUsage()">
<i class="fa fa-pie-chart"></i>
</span>
@ -42,6 +46,11 @@
configuration-saved="configurationSaved()"></div>
</div>
<!-- Dashboard tab -->
<div id="dashboard" class="tab-pane">
<div class="ps-usage-graph" is-enabled="dashboardActive"></div>
</div>
<!-- Debugging tab -->
<div id="debug" class="tab-pane">
<div class="cor-loader" ng-show="!debugServices"></div>

View file

@ -1,5 +1,5 @@
<div class="resource-view" resource="orgResource" error-message="'No matching organization'">
<div class="team-view container">
<div class="team-view cor-container">
<div class="organization-header" organization="organization" team-name="teamname">
<div ng-show="canEditMembers" class="side-controls">
<div class="hidden-xs">

View file

@ -6,6 +6,6 @@
</ul>
</div>
<div class="container">
<div class="cor-container">
<div class="tour-content" kind="kind"></div>
</div>

View file

@ -1,3 +1,3 @@
<div class="container">
<div class="cor-container">
<div class="angular-tour-ui" tour="tour" inline="true"></div>
</div>

View file

@ -6,7 +6,7 @@
No matching user found
</div>
<div class="user-admin container" ng-show="!user.anonymous">
<div class="user-admin cor-container" ng-show="!user.anonymous">
<div class="row">
<div class="organization-header-element">
<span class="avatar" size="24" hash="user.avatar"></span>

View file

@ -1,4 +1,4 @@
<div class="container">
<div class="cor-container">
<div ng-show="!user.anonymous && user.verified">
<h3>Welcome <b>{{ user.username }}</b>. Your account is fully activated!</h3>

View file

@ -1,8 +1,8 @@
<div class="container">
<div class="alert alert-info" style="padding: 4px;">
<div class="checkbox">
<label>
<input name="showSudo" type="checkbox" ng-model="tour.tourScope.showSudo" style="display: inline-block; margin-left: 0px; margin-right: 10px;">
<input name="showSudo" type="checkbox" ng-model="tour.tourScope.showSudo" style="display: inline-block; margin-left: 10px; margin-right: 10px;">
<label for="showSudo" style="padding-left: 30px;">
My OS requires me to run all <code>docker</code> commands with <code>sudo</code>
</label>
</div>