Redo the build log view as per discussions

This commit is contained in:
Joseph Schorr 2014-02-12 21:16:11 -05:00
parent 5511c9c4cf
commit 46991e47a6
10 changed files with 252 additions and 185 deletions

View file

@ -1749,69 +1749,113 @@ p.editable:hover i {
overflow: auto;
}
.repo-build .build-pane .build-logs .command-logs {
margin: 10px;
padding-right: 10px;
.repo-build .build-pane .build-logs .container-header {
padding: 2px;
}
.repo-build .build-pane .build-logs .command-entry,
.repo-build .build-pane .build-logs .log-entry {
.repo-build .build-pane .build-logs .container-logs {
margin: 4px;
padding-bottom: 4px;
}
.repo-build .build-pane .build-logs .command-title,
.repo-build .build-pane .build-logs .log-entry .message {
font-family: Consolas, "Lucida Console", Monaco, monospace;
font-size: 13px;
}
.repo-build .build-pane .build-logs .command-entry {
.repo-build .build-pane .build-logs .container-header {
cursor: pointer;
position: relative;
}
.repo-build .build-pane .build-logs .command-entry i.fa.chevron {
.repo-build .build-pane .build-logs .container-header i.fa.chevron {
color: #666;
margin-right: 4px;
width: 14px;
text-align: center;
position: absolute;
top: 2px;
top: 6px;
left: 0px;
}
.repo-build .build-pane .build-logs .command-entry .label {
text-align: center;
.repo-build .build-pane .build-logs .log-container.command {
margin-left: 42px;
}
.repo-build .build-pane .build-logs .container-header.building {
margin-bottom: 10px;
}
.repo-build .build-pane .build-logs .container-header.pushing {
margin-top: 10px;
}
.repo-build .build-log-error-element {
position: relative;
display: inline-block;
margin: 10px;
padding: 10px;
background: rgba(255, 0, 0, 0.17);
border-radius: 10px;
margin-left: 22px;
}
.repo-build .build-log-error-element i.fa {
color: red;
position: absolute;
top: 13px;
left: 11px;
}
.repo-build .build-log-error-element .error-message {
display: inline-block;
margin-left: 25px;
}
.repo-build .build-pane .build-logs .container-header .label {
padding-top: 4px;
text-align: right;
margin-right: 4px;
vertical-align: middle;
width: 86px;
display: inline-block;
background-color: #aaa;
border-right: 4px solid #aaa;
background-color: #444;
position: absolute;
top: 2px;
top: 4px;
left: 24px;
}
.repo-build .build-pane .build-logs .command-entry .command-title {
.repo-build .build-pane .build-logs .container-header .container-content {
display: block;
padding-left: 20px;
}
.repo-build .build-pane .build-logs .container-header .container-content.build-log-command {
padding-left: 120px;
}
.label.FROM {
background-color: #5bc0de !important;
border-color: #5bc0de !important;
}
.label.CMD, .label.EXPOSE, .label.ENTRYPOINT {
background-color: #428bca !important;
border-color: #428bca !important;
}
.label.RUN, .label.ADD {
background-color: #5cb85c !important;
border-color: #5cb85c !important;
}
.label.ENV, .label.VOLUME, .label.USER, .label.WORKDIR {
background-color: #f0ad4e !important;
border-color: #f0ad4e !important;
}
.label.MAINTAINER {
background-color: #aaa !important;
border-color: #aaa !important;
}
.repo-build .build-pane .build-logs .log-entry {

View file

@ -0,0 +1,6 @@
<span class="command" bindonce>
<span class="label" bo-class="getCommandKind(command.message)" bo-show="getCommandKind(command.message)"
bo-text="getCommandKind(command.message)">
</span>
<span class="command-title" bo-html="getCommandTitleHtml(command.message)"></span>
</span>

View file

@ -0,0 +1,4 @@
<span bindonce class="build-log-error-element">
<i class="fa fa-exclamation-triangle"></i>
<span class="error-message" bo-text="error.message"></span>
</span>

View file

@ -0,0 +1,4 @@
<span bindonce class="build-log-phase-element">
<span class="phase-icon" ng-class="phase.message"></span>
<span class="build-message" phase="phase.message"></span>
</span>

View file

@ -1 +1 @@
<span class="build-message-element">{{ getBuildMessage(build) }}</span>
<span class="build-message-element">{{ getBuildMessage(phase) }}</span>

View file

@ -1,5 +1,5 @@
<div class="build-progress-element">
<div class="progress" ng-class="getPercentage(build) < 100 ? 'active progress-striped' : ''" ng-show="getPercentage(build) > 0 && getPercentage(build) < 100">
<div class="progress" ng-class="getPercentage(build) < 100 ? 'active progress-striped' : ''">
<div class="progress-bar" role="progressbar" aria-valuenow="{{ getPercentage(build) }}" aria-valuemin="0" aria-valuemax="100" style="{{ 'width: ' + getPercentage(build) + '%' }}">
</div>
</div>

View file

@ -1,7 +1,7 @@
<div id="build-status-container" class="build-status-container">
<div>
<span class="phase-icon" ng-class="build.phase"></span>
<span class="build-message" build="build"></span>
<span class="build-message" phase="build.phase"></span>
</div>
<div class="timing">
<i class="fa fa-clock-o"></i>

View file

@ -2476,6 +2476,119 @@ quayApp.directive('namespaceSelector', function () {
});
quayApp.directive('buildLogPhase', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/build-log-phase.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'phase': '=phase'
},
controller: function($scope, $element) {
}
};
return directiveDefinitionObject;
});
quayApp.directive('buildLogError', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/build-log-error.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'error': '=error'
},
controller: function($scope, $element) {
}
};
return directiveDefinitionObject;
});
quayApp.directive('buildLogCommand', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/build-log-command.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'command': '=command'
},
controller: function($scope, $element, $sanitize) {
var registryHandlers = {
'quay.io': function(pieces) {
var rnamespace = pieces[pieces.length - 2];
var rname = pieces[pieces.length - 1];
return '/repository/' + rnamespace + '/' + rname + '/';
},
'': function(pieces) {
var rnamespace = pieces.length == 1 ? '_' : pieces[0];
var rname = pieces[pieces.length - 1];
return 'https://index.docker.io/' + rnamespace + '/' + rname + '/';
}
};
var kindHandlers = {
'FROM': function(title) {
var pieces = title.split('/');
var registry = pieces.length < 2 ? '' : pieces[0];
if (!registryHandlers[registry]) {
return title;
}
return '<i class="fa fa-hdd-o"></i> <a href="' + registryHandlers[registry](pieces) + '">' + title + '</a>';
}
};
$scope.getCommandKind = function(fullTitle) {
var colon = fullTitle.indexOf(':');
var title = getTitleWithoutStep(fullTitle);
if (!title) {
return null;
}
var space = title.indexOf(' ');
return title.substring(0, space);
};
$scope.getCommandTitleHtml = function(fullTitle) {
var title = getTitleWithoutStep(fullTitle) || fullTitle;
var space = title.indexOf(' ');
if (space <= 0) {
return $sanitize(title);
}
var kind = $scope.getCommandKind(fullTitle);
var sanitized = $sanitize(title.substring(space + 1));
var handler = kindHandlers[kind || ''];
if (handler) {
return handler(sanitized);
} else {
return sanitized;
}
};
var getTitleWithoutStep = function(fullTitle) {
var colon = fullTitle.indexOf(':');
if (colon <= 0) {
return null;
}
return $.trim(fullTitle.substring(colon + 1));
};
}
};
return directiveDefinitionObject;
});
quayApp.directive('buildStatus', function () {
var directiveDefinitionObject = {
priority: 0,
@ -2501,17 +2614,17 @@ quayApp.directive('buildMessage', function () {
transclude: false,
restrict: 'C',
scope: {
'build': '=build'
'phase': '=phase'
},
controller: function($scope, $element) {
$scope.getBuildMessage = function (buildInfo) {
switch (buildInfo.phase) {
$scope.getBuildMessage = function (phase) {
switch (phase) {
case 'starting':
case 'initializing':
return 'Starting Dockerfile build';
case 'waiting':
return 'Waiting for available build worker.';
return 'Waiting for available build worker';
case 'building':
return 'Building image from Dockerfile';
@ -2523,7 +2636,7 @@ quayApp.directive('buildMessage', function () {
return 'Dockerfile build completed and pushed';
case 'error':
return 'Dockerfile build failed.';
return 'Dockerfile build failed';
}
};
}

View file

@ -624,32 +624,6 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope
var name = $routeParams.name;
var pollTimerHandle = null;
var registryHandlers = {
'quay.io': function(pieces) {
var rnamespace = pieces[pieces.length - 2];
var rname = pieces[pieces.length - 1];
return '/repository/' + rnamespace + '/' + rname + '/';
},
'': function(pieces) {
var rnamespace = pieces.length == 1 ? '_' : pieces[0];
var rname = pieces[pieces.length - 1];
return 'https://index.docker.io/' + rnamespace + '/' + rname + '/';
}
};
var kindHandlers = {
'FROM': function(title) {
var pieces = title.split('/');
var registry = pieces.length < 2 ? '' : pieces[0];
if (!registryHandlers[registry]) {
return title;
}
return '<i class="fa fa-hdd-o"></i> <a href="' + registryHandlers[registry](pieces) + '">' + title + '</a>';
}
};
$scope.$on('$destroy', function() {
stopPollTimer();
});
@ -668,43 +642,19 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope
$('.build-logs').height($(window).height() - 365);
};
$scope.getCommandKind = function(fullTitle) {
var colon = fullTitle.indexOf(':');
var title = getTitleWithoutStep(fullTitle);
if (!title) {
return null;
}
var space = title.indexOf(' ');
return title.substring(0, space);
$scope.hasLogs = function(container) {
return ((container.logs && container.logs.length) || (container._logs && container._logs.length));
};
$scope.getCommandTitleHtml = function(fullTitle) {
var title = getTitleWithoutStep(fullTitle) || fullTitle;
var space = title.indexOf(' ');
if (space <= 0) {
return $sanitize(title);
}
var kind = $scope.getCommandKind(fullTitle);
var sanitized = $sanitize(title.substring(space + 1));
var handler = kindHandlers[kind || ''];
if (handler) {
return handler(sanitized);
$scope.toggleLogs = function(container) {
if (container._logs) {
container.logs = container._logs;
container._logs = null;
} else {
return sanitized;
container._logs = container.logs;
container.logs = null;
}
};
$scope.toggleCommand = function(command) {
command.expanded = !command.expanded;
if (command.expanded && !command.logs) {
// Load the logs for the command.
loadCommandLogs(command);
}
};
};
$scope.setCurrentBuild = function(buildId, opt_updateURL) {
// Find the build.
@ -721,10 +671,10 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope
stopPollTimer();
$scope.commands = null;
$scope.commandMap = {};
$scope.logs = null;
$scope.logEntries = null;
$scope.logStartIndex = null;
$scope.currentParentEntry = null;
$scope.currentBuild = build;
if (opt_updateURL) {
@ -748,15 +698,6 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope
checkPollTimer();
};
var getTitleWithoutStep = function(fullTitle) {
var colon = fullTitle.indexOf(':');
if (colon <= 0) {
return null;
}
return $.trim(fullTitle.substring(colon + 1));
}
var checkPollTimer = function() {
var build = $scope.currentBuild;
if (!build) {
@ -783,65 +724,25 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope
};
var processLogs = function(logs, startIndex) {
var currentCommand = $scope.commands.length > 0 ? $scope.commands[$scope.commands.length - 1] : null;
if (!$scope.logEntries) { $scope.logEntries = []; }
for (var i = 0; i < logs.length; ++i) {
var entry = logs[i];
if (entry['is_command']) {
var index = startIndex + i;
var existing = $scope.commandMap[index];
if (existing) {
currentCommand = existing;
continue;
var type = entry['type'] || 'entry';
if (type == 'command' || type == 'phase' || type == 'error') {
entry['_logs'] = [];
entry['index'] = startIndex + i;
$scope.logEntries.push(entry);
$scope.currentParentEntry = entry;
} else if ($scope.currentParentEntry) {
if ($scope.currentParentEntry['logs']) {
$scope.currentParentEntry['logs'].push(entry);
} else {
$scope.currentParentEntry['_logs'].push(entry);
}
currentCommand = {
'message': entry['message'],
'index': index
};
$scope.commands.push(currentCommand);
$scope.commandMap[index] = currentCommand;
continue;
}
if (!currentCommand.logs) {
currentCommand.logs = [];
}
currentCommand.logs.push(entry);
}
if (currentCommand.expanded == null) {
currentCommand.expanded = true;
}
};
var loadCommandLogs = function(command) {
var start = command['index'] + 1;
var end = null;
var currentCommandIndex = jQuery.inArray(command, $scope.commands);
if (currentCommandIndex >= 0 && currentCommandIndex < $scope.commands.length - 1) {
var nextCommand = $scope.commands[currentCommandIndex + 1];
end = nextCommand.index ? nextCommand.index - 1 : null;
}
var params = {
'repository': namespace + '/' + name,
'build_uuid': $scope.currentBuild.id
};
var options = {
'start': start
};
if (end != null) {
options['end'] = end;
}
ApiService.getRepoBuildLogsAsResource(params, true).withOptions(options).get(function(resp) {
if (resp['logs']) {
command.logs = resp['logs'];
}
});
};
var getBuildStatusAndLogs = function() {
@ -862,20 +763,10 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope
// Load the updated logs for the build.
var options = {
'commands': $scope.commands == null,
'start': $scope.logStartIndex
};
ApiService.getRepoBuildLogsAsResource(params, true).withOptions(options).get(function(resp) {
if (resp['commands']) {
$scope.commands = resp['commands'];
// Add the commands to the map.
for (var i = 0; i < $scope.commands.length; ++i) {
var command = $scope.commands[i];
$scope.commandMap[command['index']] = command;
}
}
ApiService.getRepoBuildLogsAsResource(params, true).withOptions(options).get(function(resp) {
processLogs(resp['logs'], resp['start']);
$scope.logStartIndex = resp['total'];
$scope.polling = false;

View file

@ -8,11 +8,11 @@
</h3>
</div>
<div class="row" ng-show="!repo.is_building">
There are no active builds for this repository
<div class="row" ng-show="!builds.length">
There are no builds for this repository
</div>
<div class="row" ng-show="repo.is_building">
<div class="row" ng-show="builds.length">
<!-- Side tabs -->
<div class="col-sm-2">
<ul class="nav nav-pills nav-stacked">
@ -35,31 +35,36 @@
Started: <span am-time-ago="build.started || 0"></span>
</div>
<span class="phase-icon" ng-class="build.phase"></span>
<span class="build-message" build="build"></span>
<span class="build-message" phase="build.phase"></span>
<div class="build-progress" build="build"></div>
</div>
<div class="build-logs">
<div ng-show="!commands">
<div ng-show="!logEntries">
<span class="quay-spinner"></span>
</div>
<div class="command" ng-repeat="command in commands">
<div class="command-entry" ng-click="toggleCommand(command)">
<i class="fa chevron" ng-class="command.expanded ? 'fa-chevron-down' : 'fa-chevron-right'"></i>
<span bindonce>
<span class="label" bo-class="getCommandKind(command.message)" bo-show="getCommandKind(command.message)"
bo-text="getCommandKind(command.message)">
</span>
<span class="command-title" bo-html="getCommandTitleHtml(command.message)"></span>
</span>
</div>
<div class="command-logs panel-collapse collapse" ng-class="command.expanded ? 'in' : 'out'">
<div class="log-entry" bindonce ng-repeat="log in command.logs">
<span class="id" bo-text="$index + command.index + 1"></span>
<span class="message" bo-text="log.message"></span>
<div class="log-container" ng-class="container.type" ng-repeat="container in logEntries">
<div class="container-header" ng-class="container.type == 'phase' ? container.message : ''"
ng-switch on="container.type" ng-click="toggleLogs(container)">
<i class="fa chevron"
ng-class="container.logs ? 'fa-chevron-down' : 'fa-chevron-right'" ng-show="hasLogs(container)"></i>
<div ng-switch-when="phase">
<span class="container-content build-log-phase" phase="container"></span>
</div>
<div ng-show="!command.logs">
<span class="quay-spinner"></span>
<div ng-switch-when="error">
<span class="container-content build-log-error" error="container"></span>
</div>
<div ng-switch-when="command">
<span class="container-content build-log-command" command="container"></span>
</div>
</div>
<!-- Display the entries for the container -->
<div class="container-logs" ng-show="container.logs">
<div class="log-entry" bindonce ng-repeat="entry in container.logs">
<span class="id" bo-text="$index + container.index + 1"></span>
<span class="message" bo-text="entry.message"></span>
</div>
</div>
</div>