diff --git a/README.md b/README.md index 4f69b1753..20f7b1ffb 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ DockerUI currently supports the v1.1 Remote API ###Stack * Angular.js * Flatstrap ( Flat Twitter Bootstrap ) +* Spin.js +* Ace editor ###Todo: diff --git a/css/app.css b/css/app.css index 183e4b127..dc0d0731e 100644 --- a/css/app.css +++ b/css/app.css @@ -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; +} diff --git a/index.html b/index.html index 6945c1bf9..02b1f4ea0 100644 --- a/index.html +++ b/index.html @@ -29,6 +29,7 @@
+
@@ -59,6 +60,7 @@ + diff --git a/js/controllers.js b/js/controllers.js index 335597cf5..0d7068b68 100644 --- a/js/controllers.js +++ b/js/controllers.js @@ -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}); } diff --git a/js/filters.js b/js/filters.js index bac632e30..35969e815 100644 --- a/js/filters.js +++ b/js/filters.js @@ -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(); - }; + }; }); diff --git a/js/services.js b/js/services.js index cddac3f98..a59da988c 100644 --- a/js/services.js +++ b/js/services.js @@ -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 { diff --git a/js/viewmodel.js b/js/viewmodel.js new file mode 100644 index 000000000..20763a255 --- /dev/null +++ b/js/viewmodel.js @@ -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; +} diff --git a/partials/builder.html b/partials/builder.html index a572322c3..ea2192aaa 100644 --- a/partials/builder.html +++ b/partials/builder.html @@ -4,9 +4,6 @@

Build Image