diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..35f9ef908 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +dockerui diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..3ee4c68dc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +# Dockerfile for DockerUI + +FROM ubuntu + +MAINTAINER Michael Crosby http://crosbymichael.com + +RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list +RUN apt-get update +RUN apt-get upgrade + +ADD . /app/ +ADD dockerui dockerui + +EXPOSE 9000:9000 + diff --git a/css/app.css b/css/app.css index dc0d0731e..d9738bf2f 100644 --- a/css/app.css +++ b/css/app.css @@ -102,7 +102,7 @@ margin: 0 auto; } -#editor { +#editor { height: 300px; width: 100%; border: 1px solid #DDD; @@ -110,6 +110,7 @@ } .messages { - overflow: scroll; max-height: 50px; + overflow-y: scroll; + overflow-x: hidden; } diff --git a/dockerui.go b/dockerui.go new file mode 100644 index 000000000..25d812668 --- /dev/null +++ b/dockerui.go @@ -0,0 +1,71 @@ +package main + +import ( + "flag" + "fmt" + "github.com/elazarl/goproxy" + "log" + "net/http" + "strings" +) + +var ( + endpoint = flag.String("e", "", "Docker d endpoint.") + verbose = flag.Bool("v", false, "Verbose logging.") + port = flag.String("p", "9000", "Port to serve dockerui.") +) + +type multiHandler struct { + base http.Handler + proxy *goproxy.ProxyHttpServer + verbose bool +} + +func (h *multiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if h.verbose { + log.Printf("%s: %s\n", r.Method, r.URL.String()) + } + if isDockerRequest(r.URL.String()) { + h.proxy.ServeHTTP(w, r) + } else { + h.base.ServeHTTP(w, r) + } +} + +func isDockerRequest(url string) bool { + return strings.Contains(url, "dockerapi/") +} + +func createHandler(dir string) http.Handler { + fileHandler := http.FileServer(http.Dir(dir)) + proxy := goproxy.NewProxyHttpServer() + proxy.Verbose = *verbose + + proxy.OnRequest().DoFunc(func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { + c := http.Client{} + path := strings.Replace(r.URL.RequestURI(), "dockerapi/", "", -1) + n, err := http.NewRequest(r.Method, *endpoint+path, r.Body) + n.Header = r.Header + + if err != nil { + log.Fatal(err) + } + resp, err := c.Do(n) + if err != nil { + log.Fatal(err) + } + return r, resp + + }) + return &multiHandler{base: fileHandler, proxy: proxy, verbose: *verbose} +} + +func main() { + flag.Parse() + + path := fmt.Sprintf(":%s", *port) + + handler := createHandler("/app") + + log.Fatal(http.ListenAndServe(path, handler)) +} diff --git a/js/app.js b/js/app.js index cb85fd625..c71dc83d0 100644 --- a/js/app.js +++ b/js/app.js @@ -11,7 +11,8 @@ angular.module('dockerui', ['dockerui.services', 'dockerui.filters']) $routeProvider.otherwise({redirectTo: '/'}); }]) // This is your docker url that the api will use to make requests - .constant('DOCKER_ENDPOINT', 'http://192.168.1.9') - .constant('DOCKER_PORT', ':4243') + // You need to set this to the api endpoint without the port i.e. http://192.168.1.9 + .constant('DOCKER_ENDPOINT', '/dockerapi') + .constant('DOCKER_PORT', '') // Docker port, leave as an empty string if no port is requred. i.e. 4243 .constant('UI_VERSION', 'v0.2') - .constant('DOCKER_API_VERSION', 'v1.1'); + .constant('DOCKER_API_VERSION', 'v1.2'); diff --git a/js/controllers.js b/js/controllers.js index 0d7068b68..530657a69 100644 --- a/js/controllers.js +++ b/js/controllers.js @@ -10,14 +10,14 @@ function MessageController($scope, Messages) { $scope.template = 'partials/messages.html'; $scope.messages = []; $scope.$watch('messages.length', function(o, n) { - $('#message-display').show(); + $('#message-display').show(); }); $scope.$on(Messages.event, function(e, msg) { $scope.messages.push(msg); setTimeout(function() { - $('#message-display').hide('slow'); - }, 10000); + $('#message-display').hide('slow'); + }, 30000); }); } @@ -38,70 +38,62 @@ function SideBarController($scope, Container, Settings) { }); } -function SettingsController($scope, Auth, System, Docker, Settings, Messages) { - $scope.auth = {}; +function SettingsController($scope, System, Docker, Settings, Messages) { $scope.info = {}; $scope.docker = {}; $scope.endpoint = Settings.endpoint; $scope.apiVersion = Settings.version; - $scope.updateAuthInfo = function() { - if ($scope.auth.password != $scope.auth.cpassword) { - alert('Your passwords do not match.'); - return; - } - 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) { - 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; }); } // Controls the page that displays a single container and actions on that container. -function ContainerController($scope, $routeParams, $location, Container, Messages) { +function ContainerController($scope, $routeParams, $location, Container, Messages, ViewSpinner) { $scope.changes = []; $scope.start = function(){ + ViewSpinner.spin(); Container.start({id: $routeParams.id}, function(d) { Messages.send({class: 'text-success', data: 'Container started.'}); + ViewSpinner.stop(); }, function(e) { failedRequestHandler(e, Messages); + ViewSpinner.stop(); }); }; $scope.stop = function() { + ViewSpinner.spin(); Container.stop({id: $routeParams.id}, function(d) { Messages.send({class: 'text-success', data: 'Container stopped.'}); + ViewSpinner.stop(); }, function(e) { failedRequestHandler(e, Messages); + ViewSpinner.stop(); }); }; $scope.kill = function() { + ViewSpinner.spin(); Container.kill({id: $routeParams.id}, function(d) { Messages.send({class: 'text-success', data: 'Container killed.'}); + ViewSpinner.stop(); }, function(e) { failedRequestHandler(e, Messages); + ViewSpinner.stop(); }); }; $scope.remove = function() { - if (confirm("Are you sure you want to remove the container?")) { - Container.remove({id: $routeParams.id}, function(d) { - Messages.send({class: 'text-success', data: 'Container removed.'}); - }, function(e){ - failedRequestHandler(e, Messages); - }); - } + ViewSpinner.spin(); + Container.remove({id: $routeParams.id}, function(d) { + Messages.send({class: 'text-success', data: 'Container removed.'}); + ViewSpinner.stop(); + }, function(e){ + failedRequestHandler(e, Messages); + ViewSpinner.stop(); + }); }; $scope.hasContent = function(data) { @@ -141,12 +133,25 @@ function ContainersController($scope, Container, Settings, Messages, ViewSpinner }; var batch = function(items, action) { + ViewSpinner.spin(); + var counter = 0; + var complete = function() { + counter = counter -1; + if (counter === 0) { + ViewSpinner.stop(); + } + }; angular.forEach(items, function(c) { if (c.Checked) { + counter = counter + 1; action({id: c.Id}, function(d) { - Messages.send({class: 'text-success', data: d}); + Messages.send({class: 'text-success', data: 'Container ' + c.Id + ' Removed.'}); + var index = $scope.containers.indexOf(c); + $scope.containers.splice(index, 1); + complete(); }, function(e) { failedRequestHandler(e, Messages); + complete(); }); } }); @@ -157,7 +162,7 @@ function ContainersController($scope, Container, Settings, Messages, ViewSpinner i.Checked = $scope.toggle; }); }; - + $scope.toggleGetAll = function() { Settings.displayAll = $scope.displayAll; var data = {all: 0}; @@ -190,6 +195,7 @@ function ContainersController($scope, Container, Settings, Messages, ViewSpinner // Controller for the list of images function ImagesController($scope, Image, ViewSpinner, Messages) { $scope.toggle = false; + $scope.predicate = '-Created'; $scope.showBuilder = function() { $('#build-modal').modal('show'); @@ -204,7 +210,7 @@ function ImagesController($scope, Image, ViewSpinner, Messages) { ViewSpinner.stop(); } }; - angular.forEach($scope.images, function(i) { + angular.forEach($scope.images, function(i) { if (i.Checked) { counter = counter + 1; Image.remove({id: i.Id}, function(d) { @@ -222,7 +228,7 @@ function ImagesController($scope, Image, ViewSpinner, Messages) { } }); }; - + $scope.toggleSelectAll = function() { angular.forEach($scope.images, function(i) { i.Checked = $scope.toggle; @@ -243,15 +249,13 @@ function ImagesController($scope, Image, ViewSpinner, Messages) { function ImageController($scope, $routeParams, $location, Image, Messages) { $scope.history = []; $scope.tag = {repo: '', force: false}; - + $scope.remove = function() { - if (confirm("Are you sure you want to delete this image?")) { - Image.remove({id: $routeParams.id}, function(d) { - Messages.send({class: 'text-success', data: 'Image removed.'}); - }, function(e) { - failedRequestHandler(e, Messages); - }); - } + Image.remove({id: $routeParams.id}, function(d) { + Messages.send({class: 'text-success', data: 'Image removed.'}); + }, function(e) { + failedRequestHandler(e, Messages); + }); }; $scope.getHistory = function() { @@ -307,10 +311,10 @@ function StartContainerController($scope, $routeParams, $location, Container, Me var s = $scope; Container.create({ - Image: id, - Memory: $scope.config.memory, - MemorySwap: $scope.config.memorySwap, - Cmd: cmds, + Image: id, + Memory: $scope.config.memory, + MemorySwap: $scope.config.memorySwap, + Cmd: cmds, VolumesFrom: $scope.config.volumesFrom }, function(d) { if (d.Id) { @@ -331,12 +335,19 @@ function BuilderController($scope, Dockerfile, Messages) { $scope.template = '/partials/builder.html'; ace.config.set('basePath', '/lib/ace-builds/src-noconflict/'); + var spinner = new Spinner(); $scope.build = function() { + spinner.spin(document.getElementById('build-modal')); Dockerfile.build(editor.getValue(), function(d) { - Messages.send({class:'text-info', data: d}); + console.log(d.currentTarget.response); + $scope.messages = d.currentTarget.response; + $scope.$apply(); + spinner.stop(); }, function(e) { - Messages.send({class:'text-error', data: e}); + $scope.messages = e; + $scope.$apply(); + spinner.stop(); }); }; } diff --git a/js/services.js b/js/services.js index a59da988c..f15570ac2 100644 --- a/js/services.js +++ b/js/services.js @@ -80,12 +80,12 @@ angular.module('dockerui.services', ['ngResource']) return { event: 'messageSend', send: function(msg) { - $rootScope.$broadcast('messageSend', msg); - } + $rootScope.$broadcast('messageSend', msg); + } }; }) .factory('Dockerfile', function(Settings) { - var url = Settings.rawUrl + '/build'; + var url = Settings.rawUrl + '/build'; return { build: function(file, callback) { var data = new FormData(); diff --git a/partials/builder.html b/partials/builder.html index ea2192aaa..8cc96d336 100644 --- a/partials/builder.html +++ b/partials/builder.html @@ -5,6 +5,7 @@
{{ messages }}