Get dashboard working and upgrade bootstrap. Note: the bootstrap fixes will be coming in the followup CL

This commit is contained in:
Joseph Schorr 2015-02-17 19:15:54 -05:00
parent 79f39697fe
commit 524705b88c
18 changed files with 429 additions and 260 deletions

View file

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

View file

@ -271,8 +271,6 @@ class EphemeralBuilderManager(BaseManager):
def build_component_disposed(self, build_component, timed_out): def build_component_disposed(self, build_component, timed_out):
logger.debug('Calling build_component_disposed.') 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) self.unregister_component(build_component)
@coroutine @coroutine

View file

@ -70,7 +70,8 @@ class BuilderServer(object):
@controller_app.route('/status') @controller_app.route('/status')
def status(): def status():
(running_count, available_count) = server._queue.get_metrics() 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 workers = [component for component in server._current_components
if component.kind() == 'builder'] if component.kind() == 'builder']
@ -80,7 +81,7 @@ class BuilderServer(object):
'running_local': server._job_count, 'running_local': server._job_count,
'running_total': running_count, 'running_total': running_count,
'workers': len(workers), 'workers': len(workers),
'job_total': available_count 'job_total': available_count + running_count
} }
return json.dumps(data) return json.dumps(data)

View file

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

View file

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

View file

@ -47,9 +47,12 @@ def ps():
} }
json_string = json.dumps(data) json_string = json.dumps(data)
yield 'data: %s\n\n' % json_string yield 'data: %s\n\n' % json_string
time.sleep(0.25) time.sleep(1)
try:
return Response(generator(), mimetype="text/event-stream") return Response(generator(), mimetype="text/event-stream")
except:
pass

View file

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

View file

@ -705,5 +705,11 @@
} }
.realtime-area-chart, .realtime-line-chart { .realtime-area-chart, .realtime-line-chart {
display: inline-block; margin: 10px;
text-align: center;
}
.rickshaw_graph {
overflow: hidden;
padding-bottom: 40px;
} }

View file

@ -4972,3 +4972,9 @@ i.slack-icon {
left: 16px; left: 16px;
font-size: 28px; font-size: 28px;
} }
.chart-col h4, .chart-col h5 {
display: block;
text-align: center;
}

View file

@ -1,5 +1,5 @@
<div class="config-setup-tool-element"> <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']"> <div ng-show="config && config['SUPER_USERS']">
<form id="configform" name="configform"> <form id="configform" name="configform">
@ -289,7 +289,7 @@
<div class="description"> <div class="description">
<p> <p>
Authentication for the registry can be handled by either the registry itself or LDAP. 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> </p>
</div> </div>
@ -339,20 +339,20 @@
</div> </div>
</div> <!-- /Authentication --> </div> <!-- /Authentication -->
<!-- Github Authentication --> <!-- GitHub Authentication -->
<div class="co-panel"> <div class="co-panel">
<div class="co-panel-heading"> <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>
<div class="co-panel-body"> <div class="co-panel-body">
<div class="description"> <div class="description">
<p> <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>
<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 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 Create an OAuth Application in GitHub
</a> </a>
</p> </p>
@ -360,21 +360,21 @@
<div class="co-checkbox"> <div class="co-checkbox">
<input id="ftghl" type="checkbox" ng-model="config.FEATURE_GITHUB_LOGIN"> <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> </div>
<table class="config-table" ng-if="config.FEATURE_GITHUB_LOGIN"> <table class="config-table" ng-if="config.FEATURE_GITHUB_LOGIN">
<tr> <tr>
<td>Github:</td> <td>GitHub:</td>
<td> <td>
<select ng-model="mapped.GITHUB_LOGIN_KIND"> <select ng-model="mapped.GITHUB_LOGIN_KIND">
<option value="hosted">Github.com</option> <option value="hosted">GitHub.com</option>
<option value="enterprise">Github Enterprise</option> <option value="enterprise">GitHub Enterprise</option>
</select> </select>
</td> </td>
</tr> </tr>
<tr ng-if="mapped.GITHUB_LOGIN_KIND == 'enterprise'"> <tr ng-if="mapped.GITHUB_LOGIN_KIND == 'enterprise'">
<td>Github Endpoint:</td> <td>GitHub Endpoint:</td>
<td> <td>
<span class="config-string-field" <span class="config-string-field"
binding="config.GITHUB_LOGIN_CONFIG.GITHUB_ENDPOINT" binding="config.GITHUB_LOGIN_CONFIG.GITHUB_ENDPOINT"
@ -382,7 +382,7 @@
pattern="{{ GITHUB_REGEX }}"> pattern="{{ GITHUB_REGEX }}">
</span> </span>
<div class="help-text"> <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> </div>
</td> </td>
</tr> </tr>
@ -402,7 +402,7 @@
</tr> </tr>
</table> </table>
</div> </div>
</div> <!-- /Github Authentication --> </div> <!-- /GitHub Authentication -->
<!-- Google Authentication --> <!-- Google Authentication -->
<div class="co-panel"> <div class="co-panel">
@ -471,20 +471,20 @@
</div> <!-- /Build Support --> </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" ng-if="config.FEATURE_BUILD_SUPPORT" style="margin-top: 20px;">
<div class="co-panel-heading"> <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>
<div class="co-panel-body"> <div class="co-panel-body">
<div class="description"> <div class="description">
<p> <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>
<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 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 Create an OAuth Application in GitHub
</a> </a>
</p> </p>
@ -492,21 +492,21 @@
<div class="co-checkbox"> <div class="co-checkbox">
<input id="ftgb" type="checkbox" ng-model="config.FEATURE_GITHUB_BUILD"> <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> </div>
<table class="config-table" ng-if="config.FEATURE_GITHUB_BUILD"> <table class="config-table" ng-if="config.FEATURE_GITHUB_BUILD">
<tr> <tr>
<td>Github:</td> <td>GitHub:</td>
<td> <td>
<select ng-model="mapped.GITHUB_TRIGGER_KIND"> <select ng-model="mapped.GITHUB_TRIGGER_KIND">
<option value="hosted">Github.com</option> <option value="hosted">GitHub.com</option>
<option value="enterprise">Github Enterprise</option> <option value="enterprise">GitHub Enterprise</option>
</select> </select>
</td> </td>
</tr> </tr>
<tr ng-if="mapped.GITHUB_TRIGGER_KIND == 'enterprise'"> <tr ng-if="mapped.GITHUB_TRIGGER_KIND == 'enterprise'">
<td>Github Endpoint:</td> <td>GitHub Endpoint:</td>
<td> <td>
<span class="config-string-field" <span class="config-string-field"
binding="config.GITHUB_TRIGGER_CONFIG.GITHUB_ENDPOINT" binding="config.GITHUB_TRIGGER_CONFIG.GITHUB_ENDPOINT"
@ -514,7 +514,7 @@
pattern="{{ GITHUB_REGEX }}"> pattern="{{ GITHUB_REGEX }}">
</span> </span>
<div class="help-text"> <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> </div>
</td> </td>
</tr> </tr>
@ -534,7 +534,7 @@
</tr> </tr>
</table> </table>
</div> </div>
</div> <!-- /Github Trigger --> </div> <!-- /GitHub Trigger -->
</form> </form>
<!-- Save Bar --> <!-- Save Bar -->

View file

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

View file

@ -1,3 +1,6 @@
<div class="realtime-area-chart-element"> <div class="realtime-area-chart-element">
<div class="chart" style="width: 450px; height: 250px;"></div> <div ng-show="counter >= 1">
<div class="chart"></div>
</div>
<div class="cor-loader-inline" ng-if="counter < 1"></div>
</div> </div>

View file

@ -1,3 +1,6 @@
<div class="realtime-line-chart-element"> <div class="realtime-line-chart-element">
<div class="chart" style="width: 450px; height: 250px;"></div> <div ng-show="counter >= 1">
<div class="chart"></div>
</div>
<div class="cor-loader-inline" ng-if="counter < 1"></div>
</div> </div>

View file

@ -6602,178 +6602,6 @@ quayApp.directive('locationView', function () {
}); });
quayApp.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: []
});
}
graph = new Rickshaw.Graph( {
element: $element.find('.chart')[0],
renderer: 'area',
stroke: true,
series: series,
min: 0
});
};
var refresh = function(data) {
if (!data || $scope.counter < 0) { return; }
if (!graph) {
setupGraph();
}
for (var i = 0; i < $scope.data.length; ++i) {
series[i].data.push(
{'x': $scope.counter, 'y': $scope.data[i] }
);
}
hoverDetail = new Rickshaw.Graph.HoverDetail({
graph: graph,
xFormatter: function(x) {
return x.toString();
}
});
graph.renderer.unstack = true;
graph.render();
};
$scope.$watch('counter', function() {
refresh($scope.data_raw);
});
$scope.$watch('data', function(data) {
$scope.data_raw = data;
refresh($scope.data_raw);
});
}
};
return directiveDefinitionObject;
});
quayApp.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'
},
controller: function($scope, $element) {
var graph = null;
var hoverDetail = null;
var series = [];
var counter = 0;
var palette = new Rickshaw.Color.Palette( { scheme: 'spectrum14' } );
var setupGraph = function() {
graph = new Rickshaw.Graph({
element: $element.find('.chart')[0],
renderer: 'line',
series: series,
min: 'auto',
padding: {
'top': 0.1,
'left': 0.01,
'right': 0.01,
'bottom': 0.1
}
});
hoverDetail = new Rickshaw.Graph.HoverDetail({
graph: graph,
xFormatter: function(x) {
return x.toString();
}
});
};
var refresh = function(data) {
if (!data) { 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++;
for (var i = 0; i < data.length; ++i) {
var arr = series[i].data;
arr.push({
'x': counter,
'y': data[i]
})
if (arr.length > 10) {
series[i].data = arr.slice(arr.length - 10, arr.length);
}
}
graph.render();
};
$scope.$watch('counter', function(counter) {
refresh($scope.data_raw);
});
$scope.$watch('data', function(data) {
$scope.data_raw = data;
});
}
};
return directiveDefinitionObject;
});
quayApp.directive('psUsageGraph', function () { quayApp.directive('psUsageGraph', function () {
var directiveDefinitionObject = { var directiveDefinitionObject = {
priority: 0, priority: 0,
@ -6782,18 +6610,40 @@ quayApp.directive('psUsageGraph', function () {
transclude: false, transclude: false,
restrict: 'C', restrict: 'C',
scope: { scope: {
'isEnabled': '=isEnabled'
}, },
controller: function($scope, $element) { controller: function($scope, $element) {
$scope.counter = -1; $scope.counter = -1;
$scope.data = null; $scope.data = null;
var source = new EventSource('/realtime/ps'); var source = null;
var connect = function() {
if (source) { return; }
source = new EventSource('/realtime/ps');
source.onmessage = function(e) { source.onmessage = function(e) {
$scope.$apply(function() { $scope.$apply(function() {
$scope.counter++; $scope.counter++;
$scope.data = JSON.parse(e.data); $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; return directiveDefinitionObject;

View file

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

View file

@ -307,10 +307,10 @@ angular.module("core-config-setup", ['angularFileUpload'])
if (!value) { return; } if (!value) { return; }
ApiService.scGetConfig().then(function(resp) { ApiService.scGetConfig().then(function(resp) {
$scope.config = resp['config']; $scope.config = resp['config'] || {};
initializeMappedLogic($scope.config); initializeMappedLogic($scope.config);
$scope.mapped['$hasChanges'] = false; $scope.mapped['$hasChanges'] = false;
}); }, ApiService.errorDisplay('Could not load config'));
}); });
} }
}; };

View file

@ -270,9 +270,18 @@ angular.module("core-ui", [])
'tabActive': '@tabActive', 'tabActive': '@tabActive',
'tabTitle': '@tabTitle', 'tabTitle': '@tabTitle',
'tabTarget': '@tabTarget', 'tabTarget': '@tabTarget',
'tabInit': '&tabInit' 'tabInit': '&tabInit',
'tabShown': '&tabShown',
'tabHidden': '&tabHidden'
}, },
controller: function($rootScope, $scope, $element) { 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; return directiveDefinitionObject;
@ -297,6 +306,236 @@ angular.module("core-ui", [])
return directiveDefinitionObject; 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() { .directive('corStepBar', function() {
var directiveDefinitionObject = { var directiveDefinitionObject = {
priority: 4, priority: 4,

View file

@ -20,7 +20,8 @@
tab-target="#users" tab-init="loadUsers()"> tab-target="#users" tab-init="loadUsers()">
<i class="fa fa-group"></i> <i class="fa fa-group"></i>
</span> </span>
<span class="cor-tab" tab-title="Dashboard" tab-target="#dashboard"> <span class="cor-tab" tab-title="Dashboard" tab-target="#dashboard"
tab-shown="setDashboardActive(true)" tab-hidden="setDashboardActive(false)">
<i class="fa fa-tachometer"></i> <i class="fa fa-tachometer"></i>
</span> </span>
<span class="cor-tab" tab-title="Container Usage" tab-target="#usage-counter" tab-init="getUsage()"> <span class="cor-tab" tab-title="Container Usage" tab-target="#usage-counter" tab-init="getUsage()">
@ -47,7 +48,7 @@
<!-- Dashboard tab --> <!-- Dashboard tab -->
<div id="dashboard" class="tab-pane"> <div id="dashboard" class="tab-pane">
<div class="ps-usage-graph" is-enabled="true"></div> <div class="ps-usage-graph" is-enabled="dashboardActive"></div>
</div> </div>
<!-- Debugging tab --> <!-- Debugging tab -->