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

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

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

@ -50,10 +50,6 @@ class SuperUserRegistryStatus(ApiResource):
@verify_not_prod
def get(self):
""" 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 not CONFIG_PROVIDER.volume_exists():

View file

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

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

@ -705,5 +705,11 @@
}
.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;
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="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,39 +1,75 @@
<div class="ps-usage-graph-element">
<div quay-show="Features.BUILD_SUPPORT" ng-if="data.build && data.build.job_total">
Build Workers:
<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"></div>
<!-- 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="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"></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:
<div class="realtime-line-chart" data="data.count.cpu" counter="counter"
label-template="CPU #{x} %"></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>
Process Count:
<div class="realtime-line-chart" data="data.count.processes" counter="counter"
label-template="Process Count"></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>
Virtual Memory:
<div class="realtime-line-chart" data="data.count.virtual_mem[2]" counter="counter"
label-template="Virtual Memory %"></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>
Swap Memory:
<div class="realtime-line-chart" data="data.count.swap_mem[3]" counter="counter"
label-template="Swap Memory %"></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>
Network Connections:
<div class="realtime-line-chart" data="data.count.connections" counter="counter"
label-template="Network Connection Count"></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>
Network Usage:
<div class="realtime-line-chart" data="data.count.network" labels="['Bytes In', 'Bytes Out']" counter="counter"></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

@ -1,3 +1,6 @@
<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>

View file

@ -1,3 +1,6 @@
<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>

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 () {
var directiveDefinitionObject = {
priority: 0,
@ -6782,18 +6610,40 @@ quayApp.directive('psUsageGraph', function () {
transclude: false,
restrict: 'C',
scope: {
'isEnabled': '=isEnabled'
},
controller: function($scope, $element) {
$scope.counter = -1;
$scope.data = null;
var source = new EventSource('/realtime/ps');
source.onmessage = function(e) {
$scope.$apply(function() {
$scope.counter++;
$scope.data = JSON.parse(e.data);
});
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;

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

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

View file

@ -20,7 +20,8 @@
tab-target="#users" tab-init="loadUsers()">
<i class="fa fa-group"></i>
</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>
</span>
<span class="cor-tab" tab-title="Container Usage" tab-target="#usage-counter" tab-init="getUsage()">
@ -47,7 +48,7 @@
<!-- Dashboard tab -->
<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>
<!-- Debugging tab -->