Merge branch 'batch-actions' into create-containers

pull/2/head
Michael Crosby 2013-06-19 18:12:07 -09:00
commit be9a883800
15 changed files with 216 additions and 125 deletions

View File

@ -22,6 +22,8 @@ DockerUI currently supports the v1.1 Remote API
###Stack
* Angular.js
* Flatstrap ( Flat Twitter Bootstrap )
* Spin.js
* Ace editor
###Todo:

View File

@ -94,7 +94,7 @@
}
.footer {
max-height:6px;
max-height:6px;
}
#response {
@ -108,3 +108,8 @@
border: 1px solid #DDD;
margin-top: 5px;
}
.messages {
overflow: scroll;
max-height: 50px;
}

View File

@ -29,6 +29,7 @@
<div class="container">
<div ng-include="template" ng-controller="MastheadController"></div>
<div ng-include="template" ng-controller="MessageController"></div>
<div id="view" ng-view></div>
@ -59,6 +60,7 @@
<script src="js/services.js"></script>
<script src="js/filters.js"></script>
<script src="js/controllers.js"></script>
<script src="js/viewmodel.js"></script>
</body>
</html>

View File

@ -6,6 +6,21 @@ function MastheadController($scope) {
function DashboardController($scope, Container) {
}
function MessageController($scope, Messages) {
$scope.template = 'partials/messages.html';
$scope.messages = [];
$scope.$watch('messages.length', function(o, n) {
$('#message-display').show();
});
$scope.$on(Messages.event, function(e, msg) {
$scope.messages.push(msg);
setTimeout(function() {
$('#message-display').hide('slow');
}, 10000);
});
}
function StatusBarController($scope, Settings) {
$scope.template = 'partials/statusbar.html';
@ -20,96 +35,75 @@ function SideBarController($scope, Container, Settings) {
Container.query({all: 0}, function(d) {
$scope.containers = d;
});
});
}
function SettingsController($scope, Auth, System, Docker, Settings) {
function SettingsController($scope, Auth, System, Docker, Settings, Messages) {
$scope.auth = {};
$scope.info = {};
$scope.docker = {};
$scope.endpoint = Settings.endpoint;
$scope.apiVersion = Settings.version;
$('#response').hide();
$scope.alertClass = 'block';
$scope.updateAuthInfo = function() {
if ($scope.auth.password != $scope.auth.cpassword) {
setSuccessfulResponse($scope, 'Your passwords do not match.', '#response');
alert('Your passwords do not match.');
return;
}
Auth.update(
{username: $scope.auth.username, email: $scope.auth.email, password: $scope.auth.password}, function(d) {
console.log(d);
setSuccessfulResponse($scope, 'Auth information updated.', '#response');
Auth.update({
username: $scope.auth.username,
email: $scope.auth.email,
password: $scope.auth.password
}, function(d) {
Messages.send({class: 'text-success', data: 'Auth information updated.'});
}, function(e) {
console.log(e);
setFailedResponse($scope, e.data, '#response');
Messages.send({class: 'text-error', data: e.data});
});
};
Auth.get({}, function(d) {
$scope.auth = d;
});
Docker.get({}, function(d) {
$scope.docker = d;
});
System.get({}, function(d) {
$scope.info = d;
});
Auth.get({}, function(d) { $scope.auth = d; });
Docker.get({}, function(d) { $scope.docker = d; });
System.get({}, function(d) { $scope.info = d; });
}
// Controls the page that displays a single container and actions on that container.
function ContainerController($scope, $routeParams, $location, Container) {
$('#response').hide();
$scope.alertClass = 'block';
function ContainerController($scope, $routeParams, $location, Container, Messages) {
$scope.changes = [];
$scope.start = function(){
Container.start({id: $routeParams.id}, function(d) {
console.log(d);
setSuccessfulResponse($scope, 'Container started.', '#response');
Messages.send({class: 'text-success', data: 'Container started.'});
}, function(e) {
console.log(e);
setFailedResponse($scope, e.data, '#response');
});
failedRequestHandler(e, Messages);
});
};
$scope.stop = function() {
Container.stop({id: $routeParams.id}, function(d) {
console.log(d);
setSuccessfulResponse($scope, 'Container stopped.', '#response');
Messages.send({class: 'text-success', data: 'Container stopped.'});
}, function(e) {
console.log(e);
setFailedResponse($scope, e.data, '#response');
failedRequestHandler(e, Messages);
});
};
$scope.kill = function() {
Container.kill({id: $routeParams.id}, function(d) {
console.log(d);
setSuccessfulResponse($scope, 'Container killed.', '#response');
Messages.send({class: 'text-success', data: 'Container killed.'});
}, function(e) {
console.log(e);
setFailedResponse($scope, e.data, '#response');
failedRequestHandler(e, Messages);
});
};
$scope.remove = function() {
if (confirm("Are you sure you want to remove the container?")) {
Container.remove({id: $routeParams.id}, function(d) {
console.log(d);
setSuccessfulResponse($scope, 'Container removed.', '#response');
Messages.send({class: 'text-success', data: 'Container removed.'});
}, function(e){
console.log(e);
setFailedResponse($scope, e.data, '#response');
failedRequestHandler(e, Messages);
});
}
};
$scope.changes = [];
$scope.hasContent = function(data) {
return data !== null && data !== undefined && data.length > 1;
};
@ -123,8 +117,7 @@ function ContainerController($scope, $routeParams, $location, Container) {
Container.get({id: $routeParams.id}, function(d) {
$scope.container = d;
}, function(e) {
console.log(e);
setFailedResponse($scope, e.data, '#response');
failedRequestHandler(e, Messages);
if (e.status === 404) {
$('.detail').hide();
}
@ -134,69 +127,129 @@ function ContainerController($scope, $routeParams, $location, Container) {
}
// Controller for the list of containers
function ContainersController($scope, Container, Settings, ViewSpinner) {
function ContainersController($scope, Container, Settings, Messages, ViewSpinner) {
$scope.displayAll = Settings.displayAll;
$scope.predicate = '-Created';
$scope.toggle = false;
var update = function(data) {
ViewSpinner.spin();
Container.query(data, function(d) {
$scope.containers = d;
$scope.containers = d.map(function(item) { return new ContainerViewModel(item); });
ViewSpinner.stop();
});
};
var batch = function(items, action) {
angular.forEach(items, function(c) {
if (c.Checked) {
action({id: c.Id}, function(d) {
Messages.send({class: 'text-success', data: d});
}, function(e) {
failedRequestHandler(e, Messages);
});
}
});
};
$scope.toggleSelectAll = function() {
angular.forEach($scope.containers, function(i) {
i.Checked = $scope.toggle;
});
};
$scope.toggleGetAll = function() {
Settings.displayAll = $scope.displayAll;
var u = update;
var data = {all: 0};
if ($scope.displayAll) {
data.all = 1;
}
u(data);
update(data);
};
$scope.startAction = function() {
batch($scope.containers, Container.start);
};
$scope.stopAction = function() {
batch($scope.containers, Container.stop);
};
$scope.killAction = function() {
batch($scope.containers, Container.kill);
};
$scope.removeAction = function() {
batch($scope.containers, Container.remove);
};
update({all: $scope.displayAll ? 1 : 0});
}
// Controller for the list of images
function ImagesController($scope, Image, ViewSpinner) {
$scope.predicate = '-Created';
$('#response').hide();
$scope.alertClass = 'block';
function ImagesController($scope, Image, ViewSpinner, Messages) {
$scope.toggle = false;
$scope.showBuilder = function() {
$('#build-modal').modal('show');
};
$scope.removeAction = function() {
ViewSpinner.spin();
var counter = 0;
var complete = function() {
counter = counter - 1;
if (counter === 0) {
ViewSpinner.stop();
}
};
angular.forEach($scope.images, function(i) {
if (i.Checked) {
counter = counter + 1;
Image.remove({id: i.Id}, function(d) {
angular.forEach(d, function(resource) {
Messages.send({class: 'text-success', data: 'Deleted: ' + resource.Deleted});
});
//Remove the image from the list
var index = $scope.images.indexOf(i);
$scope.images.splice(index, 1);
complete();
}, function(e) {
Messages.send({class: 'text-error', data: e.data});
complete();
});
}
});
};
$scope.toggleSelectAll = function() {
angular.forEach($scope.images, function(i) {
i.Checked = $scope.toggle;
});
};
ViewSpinner.spin();
Image.query({}, function(d) {
$scope.images = d;
$scope.images = d.map(function(item) { return new ImageViewModel(item); });
ViewSpinner.stop();
}, function (e) {
console.log(e);
setFailedResponse($scope, e.data, '#response');
failedRequestHandler(e, Messages);
ViewSpinner.stop();
});
}
// Controller for a single image and actions on that image
function ImageController($scope, $routeParams, $location, Image) {
function ImageController($scope, $routeParams, $location, Image, Messages) {
$scope.history = [];
$scope.tag = {repo: '', force: false};
$('#response').hide();
$scope.alertClass = 'block';
$scope.remove = function() {
if (confirm("Are you sure you want to delete this image?")) {
Image.remove({id: $routeParams.id}, function(d) {
console.log(d);
setSuccessfulResponse($scope, 'Image removed.', '#response');
Messages.send({class: 'text-success', data: 'Image removed.'});
}, function(e) {
console.log(e);
setFailedResponse($scope, e.data, '#response');
failedRequestHandler(e, Messages);
});
}
};
@ -210,11 +263,9 @@ function ImageController($scope, $routeParams, $location, Image) {
$scope.updateTag = function() {
var tag = $scope.tag;
Image.tag({id: $routeParams.id, repo: tag.repo, force: tag.force ? 1 : 0}, function(d) {
console.log(d);
setSuccessfulResponse($scope, 'Tag added.', '#response');
Messages.send({class: 'text-success', data: 'Tag added.'});
}, function(e) {
console.log(e);
setFailedResponse($scope, e.data, '#response');
failedRequestHandler(e, Messages);
});
};
@ -225,8 +276,7 @@ function ImageController($scope, $routeParams, $location, Image) {
Image.get({id: $routeParams.id}, function(d) {
$scope.image = d;
}, function(e) {
console.log(e);
setFailedResponse($scope, e.data, '#response');
failedRequestHandler(e, Messages);
if (e.status === 404) {
$('.detail').hide();
}
@ -235,7 +285,7 @@ function ImageController($scope, $routeParams, $location, Image) {
$scope.getHistory();
}
function StartContainerController($scope, $routeParams, $location, Container) {
function StartContainerController($scope, $routeParams, $location, Container, Messages) {
$scope.template = 'partials/startcontainer.html';
$scope.config = {
memory: 0,
@ -247,7 +297,6 @@ function StartContainerController($scope, $routeParams, $location, Container) {
$scope.commandPlaceholder = '["/bin/echo", "Hello world"]';
$scope.create = function() {
$scope.response = '';
var cmds = null;
if ($scope.config.commands !== '') {
cmds = angular.fromJson($scope.config.commands);
@ -264,45 +313,34 @@ function StartContainerController($scope, $routeParams, $location, Container) {
Cmd: cmds,
VolumesFrom: $scope.config.volumesFrom
}, function(d) {
console.log(d);
if (d.Id) {
ctor.start({id: d.Id}, function(cd) {
console.log(cd);
$('#create-modal').modal('hide');
loc.path('/containers/' + d.Id + '/');
}, function(e) {
console.log(e);
s.resonse = e.data;
failedRequestHandler(e, Messages);
});
}
}, function(e) {
console.log(e);
$scope.response = e.data;
failedRequestHandler(e, Messages);
});
};
}
function BuilderController($scope, Dockerfile) {
function BuilderController($scope, Dockerfile, Messages) {
$scope.template = '/partials/builder.html';
ace.config.set('basePath', '/lib/ace-builds/src-noconflict/');
$scope.build = function() {
Dockerfile.build(editor.getValue(), function(e) {
console.log(e);
Dockerfile.build(editor.getValue(), function(d) {
Messages.send({class:'text-info', data: d});
}, function(e) {
Messages.send({class:'text-error', data: e});
});
};
}
function setSuccessfulResponse($scope, msg, msgId) {
$scope.alertClass = 'success';
$scope.response = msg;
$(msgId).show();
setTimeout(function() { $(msgId).hide();}, 5000);
}
function setFailedResponse($scope, msg, msgId) {
$scope.alertClass = 'error';
$scope.response = msg;
$(msgId).show();
function failedRequestHandler(e, Messages) {
Messages.send({class: 'text-error', data: e.data});
}

View File

@ -53,10 +53,20 @@ angular.module('dockerui.filters', [])
return '';
};
})
.filter('humansize', function() {
return function(bytes) {
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (bytes == 0) {
return 'n/a';
}
var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[[i]];
};
})
.filter('getdate', function() {
return function(data) {
//Multiply by 1000 for the unix format
var date = new Date(data * 1000);
return date.toDateString();
};
};
});

View File

@ -28,7 +28,7 @@ angular.module('dockerui.services', ['ngResource'])
insert :{method: 'POST', params: {id: '@id', action:'insert'}},
push :{method: 'POST', params: {id: '@id', action:'push'}},
tag :{method: 'POST', params: {id: '@id', action:'tag', force: 0, repo: '@repo'}},
delete :{id: '@id', method: 'DELETE'}
remove :{method: 'DELETE', params: {id: '@id'}, isArray: true}
});
})
.factory('Docker', function($resource, Settings) {
@ -76,6 +76,14 @@ angular.module('dockerui.services', ['ngResource'])
stop: function() { spinner.stop(); }
};
})
.factory('Messages', function($rootScope) {
return {
event: 'messageSend',
send: function(msg) {
$rootScope.$broadcast('messageSend', msg);
}
};
})
.factory('Dockerfile', function(Settings) {
var url = Settings.rawUrl + '/build';
return {

18
js/viewmodel.js Normal file
View File

@ -0,0 +1,18 @@
function ImageViewModel(data) {
this.Id = data.Id;
this.Tag = data.Tag;
this.Repository = data.Repository;
this.Created = data.Created;
this.Checked = false;
}
function ContainerViewModel(data) {
this.Id = data.Id;
this.Image = data.Image;
this.Command = data.Command;
this.Created = data.Created;
this.SizeRw = data.SizeRw;
this.Status = data.Status;
this.Checked = false;
}

View File

@ -4,9 +4,6 @@
<h3>Build Image</h3>
</div>
<div class="modal-body">
<div>
{{ response }}
</div>
<div id="editor"></div>
</div>
<div class="modal-footer">

View File

@ -1,7 +1,3 @@
<div id="response" class="alert alert-{{ alertClass }}">
{{ response }}
</div>
<div class="detail">
<h4>Container: {{ container.Id }}</h4>

View File

@ -1,25 +1,43 @@
<h2>Containers:</h2>
<div style="float:right;">
<input type="checkbox" ng-model="displayAll" ng-click="toggleGetAll()"/> Display All
<div>
<ul class="nav nav-pills pull-left">
<li class="dropdown">
<a class="dropdown-toggle" id="drop4" role="button" data-toggle="dropdown" href="#">Actions <b class="caret"></b></a>
<ul id="menu1" class="dropdown-menu" role="menu" aria-labelledby="drop4">
<li><a tabindex="-1" href="" ng-click="startAction()">Start</a></li>
<li><a tabindex="-1" href="" ng-click="stopAction()">Stop</a></li>
<li><a tabindex="-1" href="" ng-click="killAction()">Kill</a></li>
<li><a tabindex="-1" href="" ng-click="removeAction()">Remove</a></li>
</ul>
</li>
</ul>
<div class="pull-right">
<input type="checkbox" ng-model="displayAll" ng-click="toggleGetAll()"/> Display All
</div>
</div>
<table class="table table-striped">
<thead>
<tr>
<th><input type="checkbox" ng-model="toggle" ng-click="toggleSelectAll()" /> Action</th>
<th>Id</th>
<th>Image</th>
<th>Command</th>
<th>Created</th>
<th>Size</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="container in containers|orderBy:predicate">
<td><input type="checkbox" ng-model="container.Checked" /></td>
<td><a href="/#/containers/{{ container.Id }}/">{{ container.Id|truncate:10}}</a></td>
<td><a href="/#/images/{{ container.Image }}/">{{ container.Image }}</a></td>
<td>{{ container.Command|truncate:40 }}</td>
<td>{{ container.Created|getdate }}</td>
<td>{{ container.SizeRw|humansize }}</td>
<td><span class="label label-{{ container.Status|statusbadge }}">{{ container.Status }}</span></td>
</tr>
</tbody>

View File

@ -1,8 +1,3 @@
<div id="response" class="alert alert-{{ alertClass }}">
{{ response }}
</div>
<div ng-include="template" ng-controller="StartContainerController"></div>
<div class="detail">

View File

@ -3,14 +3,19 @@
<h2>Images:</h2>
<div id="response" class="alert alert-{{ alertClass }}">
{{ response }}
</div>
<a href="" ng-click="showBuilder()">Build Image</a>
<ul class="nav nav-pills">
<li class="active"><a href="" ng-click="showBuilder()">Build Image</a></li>
<li class="dropdown">
<a class="dropdown-toggle" id="drop4" role="button" data-toggle="dropdown" href="#">Actions <b class="caret"></b></a>
<ul id="menu1" class="dropdown-menu" role="menu" aria-labelledby="drop4">
<li><a tabindex="-1" href="" ng-click="removeAction()">Remove</a></li>
</ul>
</li>
</ul>
<table class="table table-striped">
<thead>
<tr>
<th><input type="checkbox" ng-model="toggle" ng-click="toggleSelectAll()" /> Action</th>
<th>Id</th>
<th>Tag</th>
<th>Repository</th>
@ -19,7 +24,8 @@
</thead>
<tbody>
<tr ng-repeat="image in images | orderBy:predicate">
<td><a href="/#/images/{{ image.Id }}/">{{ image.Id|truncate:10}}</a></td>
<td><input type="checkbox" ng-model="image.Checked" /></td>
<td><a href="/#/images/{{ image.Id }}/">{{ image.Id|truncate:20}}</a></td>
<td>{{ image.Tag }}</td>
<td>{{ image.Repository }}</td>
<td>{{ image.Created|getdate }}</td>

3
partials/messages.html Normal file
View File

@ -0,0 +1,3 @@
<div id="message-display" class="center well messages" style="display:none;">
<p ng-repeat="message in messages"><span class="{{ message.class }}">{{ message.data }}</span></p>
</div>

View File

@ -1,8 +1,4 @@
<div class="detail">
<div id="response" class="alert alert-{{ alertClass }}">
{{ response }}
</div>
<h3>Docker Information</h3>
<div>
<p class="lead">

View File

@ -4,9 +4,6 @@
<h3>Create Container</h3>
</div>
<div class="modal-body">
<div>
{{ response }}
</div>
<form>
<fieldset>
<legend>Start container from Image</legend>