Compare commits

...

63 Commits

Author SHA1 Message Date
Kevan Ahlquist d40b487756 Merge branch 'master' into dist 2016-03-23 00:58:24 -05:00
Kevan Ahlquist 8cdb675abc Merge pull request #191 from erwin314/show-port-bindings2
Fixed: Show actual port bindings
2016-03-23 00:14:03 -05:00
Kevan Ahlquist a25829bbd9 Merge pull request #190 from erwin314/master
Fixed: Check if variable is plain object before using isEmptyObject()
2016-03-23 00:13:49 -05:00
Erwin Molendijk 194aa9a750 Fixed: Show actual port bindings 2016-03-21 11:13:21 +01:00
Erwin Molendijk e21af77246 Revert "Changed: Show automatic port bindings when PublishAllPorts is set."
This reverts commit bfe9038630.
2016-03-21 10:36:34 +01:00
Erwin Molendijk bfe9038630 Changed: Show automatic port bindings when PublishAllPorts is set. 2016-03-21 10:21:50 +01:00
Erwin Molendijk a33123469a Fixed: Check if variable is plain object before using isEmptyObject() 2016-03-21 09:25:40 +01:00
Kevan Ahlquist 4700882a1a Pull latest into dist from master. 2016-03-19 18:57:37 -05:00
Kevan Ahlquist f353dc2c41 Add a global interceptor to catch 'conflict.' API responses. 2016-03-19 18:50:11 -05:00
Kevan Ahlquist a2a367725b Merge pull request #189 from crosbymichael/tables
Add column sorting to tables
2016-03-19 18:11:42 -05:00
Kevan Ahlquist db90a0eed7 Add column sorting to networks and volumes pages. 2016-03-19 18:09:17 -05:00
Kevan Ahlquist 93dba3f92f Add column sorting to images page. 2016-03-19 17:45:45 -05:00
Kevan Ahlquist 5a20b9fc04 Add column sorting to containers page. 2016-03-19 17:30:47 -05:00
Kevan Ahlquist 9793c3f3ee Fix jshint errors. 2016-03-07 00:07:46 -06:00
Kevan Ahlquist ddd995696b Merge branch 'master' into dist 2016-03-04 00:12:35 -06:00
Kevan Ahlquist 6963a1ae8a Merge pull request #184 from ramzes642/master
Container edit feature
2016-03-03 00:46:01 -06:00
Roman Usachev da1a5ead39 Bugfixes 2016-02-24 10:07:59 +03:00
Roman Usachev f1b5037ee5 Host mode fix 2016-02-24 09:19:02 +03:00
Roman Usachev cf18a3cd60 Null vars fix 2016-02-24 07:16:07 +03:00
Roman Usachev cc1b67575c Null vars fix 2016-02-24 05:43:04 +03:00
Roman Usachev 50d33a07df Filesystem binds edit 2016-02-24 03:13:24 +03:00
Roman Usachev fc0dedfda7 Ports list now using HostConfig.PortBindings 2016-02-24 01:34:20 +03:00
Roman Usachev 35dbacdfff Port bindings edit, HostConfig fix 2016-02-23 20:47:21 +03:00
Roman Usachev ad0d23d686 Container env vars editing using commit and recreate 2016-02-22 09:10:16 +03:00
Kevan Ahlquist 61831104a4 Merge branch 'master' into dist 2016-02-16 00:14:12 -06:00
Kevan Ahlquist 786b94b285 Allow running containers to be committed. 2016-02-16 00:09:12 -06:00
Kevan Ahlquist 0ddf4a1828 Fix spec for top view. 2016-02-15 23:49:20 -06:00
Kevan Ahlquist 6e8022e687 Merge latest master into dist. 2016-02-15 23:48:13 -06:00
Kevan Ahlquist b244242cb2 Add container name to stats and top pages. 2016-02-15 23:34:55 -06:00
Kevan Ahlquist 3df4342f08 Merge latest from master and bump version to 0.9.0. 2016-01-31 23:24:10 -06:00
Kevan Ahlquist 49d4c5800f Added swarm on docker-machine example. 2016-01-24 17:03:20 -06:00
Kevan Ahlquist 89e7e46980 Merge master into dist 2016-01-23 16:45:14 -06:00
Kevan Ahlquist a198382c06 Bump stats endpoint timeout to 5 seconds, retry on error. 2016-01-23 16:39:31 -06:00
Kevan Ahlquist 2a4bc92d08 Merge master into 'dist'. 2016-01-18 23:41:44 -06:00
Kevan Ahlquist 316017c516 Merge pull request #172 from chazmuzz/master
Use NetworkSettings.Ports rather than HostConfig.PortBindings
2016-01-18 23:34:09 -06:00
Charlie Murray f62cb483ce Use NetworkSettings.Ports rather than HostConfig.PortBindings to diplay host port bindings 2016-01-18 11:05:08 +00:00
Kevan Ahlquist cd232bed13 Merge branch 'master' into dist 2016-01-10 22:08:16 -06:00
Kevan Ahlquist 44c88f1ed5 Merge pull request #170 from kevana/stats-fix
Fix network stats error for 1.9.0+ clients, hide networks and volumes…
2016-01-10 22:00:38 -06:00
Kevan Ahlquist 9d1193c0b5 Fix network stats error for 1.9.0+ clients, hide networks and volumes better on older clients. 2016-01-10 21:51:51 -06:00
Kevan Ahlquist c56f94c6d2 Merge branch 'master' into dist 2016-01-10 20:58:00 -06:00
Kevan Ahlquist 7a14f43b4a Merge branch 'master' into dist
Conflicts:
	app/app.js
	bower.json
	package.json
2016-01-10 20:49:34 -06:00
Kevan Ahlquist 22d35eb32f Merge pull request #162 from kevana/networks-volumes
Networks and Volumes
2016-01-10 20:39:14 -06:00
Kevan Ahlquist 2683ddb1ee Add refresh button. 2016-01-10 20:36:22 -06:00
Kevan Ahlquist fe7646e939 Use localStorage to only show 'learn more' banner on first load. 2016-01-10 20:02:04 -06:00
Kevan Ahlquist 00528edd7c Merge branch 'master' into networks-volumes 2016-01-10 19:34:03 -06:00
Michael Crosby 06c2ac4d5c Merge pull request #168 from linquize/chart-max-steps
Show maximum 10 steps of chart
2016-01-04 09:28:58 -08:00
Michael Crosby a447d64d83 Merge pull request #169 from linquize/desc
Change the description of DockerUI
2016-01-04 09:27:17 -08:00
Linquize 9260f23d41 DockerUI is the UI for Docker container engine
It is not only on Linux, but on Windows too.
2016-01-03 13:55:27 +08:00
Linquize d7082c3959 Show maximum 10 steps of chart 2016-01-03 12:59:43 +08:00
Kevan Ahlquist 437e171639 Merge pull request #163 from israelroldan/issue-158
Redirect to container list when removing a container (Fix for #158)
2015-12-26 17:54:23 -06:00
Israel Roldan 6de45cd422 Redirect to container list when removing a container (Fixes #158) 2015-12-24 00:04:47 -06:00
Kevan Ahlquist a065e0e259 Merge pull request #160 from crosbymichael/icons
Add custom logo, nginx basic auth docker-compose example
2015-12-20 21:25:34 -06:00
Kevan Ahlquist eede27e263 Implement volumes, hide networks and volumes tabs for APIs older than v1.21. 2015-12-20 21:17:01 -06:00
Kevan Ahlquist 8a900254ae Remove unused Dockerfile service, rename services to match API endpoint names: Docker => Version, System => Info. 2015-12-20 20:23:17 -06:00
Kevan Ahlquist 5f4641af67 Catch plaintext errors for creation and connection, implement network creation, 2015-12-20 20:07:57 -06:00
Kevan Ahlquist 8a4be8b93a Implemented single network view, connect, disconnect, remove. 2015-12-20 19:13:53 -06:00
Kevan Ahlquist a712d5b77e Progress on Networks. 2015-12-17 23:35:04 -06:00
Kevan Ahlquist 5947e262fc Added docker-compose config for nginx-basic-auth. 2015-12-17 01:31:07 -06:00
Kevan Ahlquist c3f22fe989 Add custom logo. 2015-12-17 00:08:29 -06:00
Kevan Ahlquist f9aedad71a Merge branch 'crosbymichael-master' into crosbymichael-dist 2015-12-14 22:46:01 -06:00
Kevan Ahlquist d4cfa11569 Merge branch 'crosbymichael-master' into crosbymichael-dist 2015-12-14 22:45:22 -06:00
Kevan Ahlquist 2f003a5bb5 Merge pull request #159 from crosbymichael/tags-support
Support RepoTags for versions of the Docker API older than 1.21.
2015-12-14 22:24:41 -06:00
Kevan Ahlquist 75085b213a Support RepoTags for versions of the Docker API older than 1.21. 2015-12-13 23:28:28 -06:00
58 changed files with 1443 additions and 143 deletions
+2 -4
View File
@@ -1,4 +1,2 @@
node_modules
bower_components
.git
Dockerfile
*
!dist
+43 -4
View File
@@ -1,5 +1,29 @@
angular.module('dockerui', ['dockerui.templates', 'ngRoute', 'dockerui.services', 'dockerui.filters', 'masthead', 'footer', 'dashboard', 'container', 'containers', 'containersNetwork', 'images', 'image', 'pullImage', 'startContainer', 'sidebar', 'info', 'builder', 'containerLogs', 'containerTop', 'events', 'stats'])
.config(['$routeProvider', function ($routeProvider) {
angular.module('dockerui', [
'dockerui.templates',
'ngRoute',
'dockerui.services',
'dockerui.filters',
'masthead',
'footer',
'dashboard',
'container',
'containers',
'containersNetwork',
'images',
'image',
'pullImage',
'startContainer',
'sidebar',
'info',
'builder',
'containerLogs',
'containerTop',
'events',
'stats',
'network',
'networks',
'volumes'])
.config(['$routeProvider', '$httpProvider', function ($routeProvider, $httpProvider) {
'use strict';
$routeProvider.when('/', {
templateUrl: 'app/components/dashboard/dashboard.html',
@@ -43,10 +67,25 @@ angular.module('dockerui', ['dockerui.templates', 'ngRoute', 'dockerui.services'
controller: 'EventsController'
});
$routeProvider.otherwise({redirectTo: '/'});
// The Docker API likes to return plaintext errors, this catches them and disp
$httpProvider.interceptors.push(function() {
return {
'response': function(response) {
if (typeof(response.data) === 'string' && response.data.startsWith('Conflict.')) {
$.gritter.add({
title: 'Error',
text: response.data,
time: 10000
});
}
return response;
}
};
});
}])
// This is your docker url that the api will use to make requests
// 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. If you have a port, prefix it with a ':' i.e. :4243
.constant('UI_VERSION', 'v0.8.0')
.constant('DOCKER_API_VERSION', 'v1.20');
.constant('UI_VERSION', 'v0.10.0-beta');
+2 -2
View File
@@ -1,5 +1,5 @@
angular.module('builder', [])
.controller('BuilderController', ['$scope', 'Dockerfile', 'Messages',
function ($scope, Dockerfile, Messages) {
.controller('BuilderController', ['$scope',
function ($scope) {
$scope.template = 'app/components/builder/builder.html';
}]);
+110 -8
View File
@@ -45,8 +45,7 @@
ng-show="container.State.Running && !container.State.Stopped">Restart
</button>
<button class="btn btn-primary"
ng-click="commit()"
ng-show="container.State.Running && !container.State.Paused">Commit
ng-click="commit()">Commit
</button>
</div>
@@ -77,9 +76,43 @@
<tr>
<td>Environment:</td>
<td>
<ul>
<li ng-repeat="k in container.Config.Env">{{ k }}</li>
</ul>
<div ng-show="!editEnv">
<button class="btn btn-default btn-xs pull-right" ng-click="editEnv = true"><i class="glyphicon glyphicon-pencil"></i></button>
<ul>
<li ng-repeat="k in container.Config.Env">{{ k }}</li>
</ul>
</div>
<div class="form-group" ng-show="editEnv">
<label>Env:</label>
<div ng-repeat="envar in newCfg.Env">
<div class="form-group form-inline">
<div class="form-group">
<label class="sr-only">Variable Name:</label>
<input type="text" ng-model="envar.name" class="form-control input-sm"
placeholder="NAME"/>
</div>
<div class="form-group">
<label class="sr-only">Variable Value:</label>
<input type="text" ng-model="envar.value" class="form-control input-sm" style="width: 400px"
placeholder="value"/>
</div>
<div class="form-group">
<button class="btn btn-danger btn-sm input-sm form-control"
ng-click="rmEntry(newCfg.Env, envar)"><i class="glyphicon glyphicon-remove"></i>
</button>
</div>
</div>
</div>
<button type="button" class="btn btn-success btn-sm"
ng-click="addEntry(newCfg.Env, {name: '', value: ''})"><i class="glyphicon glyphicon-plus"></i> Add
</button>
<button class="btn btn-primary btn-sm"
ng-click="restartEnv()"
ng-show="!container.State.Restarting">Commit and restart</button>
</div>
</td>
</tr>
<tr>
@@ -105,11 +138,42 @@
<tr>
<td>Ports:</td>
<td>
<ul style="display:inline-table">
<li ng-repeat="(containerport, hostports) in container.HostConfig.PortBindings">
{{ containerport }} => <span class="label label-default" ng-repeat="(k,v) in hostports">{{ v.HostIp }}:{{ v.HostPort }}</span>
<div ng-show="!editPorts">
<button class="btn btn-default btn-xs pull-right" ng-click="editPorts = true"><i class="glyphicon glyphicon-pencil"></i></button>
<ul>
<li ng-repeat="(containerport, hostports) in container.NetworkSettings.Ports">
{{ containerport }} =>
<span class="label label-default" style="margin-right: 5px;" ng-repeat="(k,v) in hostports">{{ v.HostIp }}:{{ v.HostPort }}</span>
</li>
</ul>
</div>
<div ng-show="editPorts">
<div ng-repeat="(containerport, hostports) in newCfg.Ports" style="margin-bottom: 5px;">
<label>{{ containerport }}</label>
<div style="margin-left: 20px;">
<div ng-repeat="(k,v) in hostports" class="form-group form-inline">
<div class="form-group">
<input type="text" ng-model="v.HostIp" class="form-control input-sm" placeholder="IP address, ex. 0.0.0.0" />
</div>
<div class="form-group">
<input type="text" ng-model="v.HostPort" class="form-control input-sm"
placeholder="Port" />
</div>
<div class="form-group">
<button class="btn btn-danger btn-sm input-sm form-control"
ng-click="rmEntry(hostports, v)"><i class="glyphicon glyphicon-remove"></i>
</button>
</div>
</div>
</div>
<button type="button" class="btn btn-success btn-sm"
ng-click="addEntry(hostports, {HostIp: '0.0.0.0', HostPort: ''})"><i class="glyphicon glyphicon-plus"></i> Add
</button>
</div>
<button class="btn btn-primary btn-sm"
ng-click="restartEnv()"
ng-show="!container.State.Restarting">Commit and restart</button>
</div>
</td>
</tr>
@@ -131,6 +195,44 @@
<pre>{{ container.Config.Entrypoint.join(' ') }}</pre>
</td>
</tr>
<tr>
<td>Bindings:</td>
<td>
<div ng-show="!editBinds">
<button class="btn btn-default btn-xs pull-right" ng-click="editBinds = true"><i class="glyphicon glyphicon-pencil"></i></button>
<ul>
<li ng-repeat="b in container.HostConfig.Binds">{{ b }}</li>
</ul>
</div>
<div ng-show="editBinds">
<div ng-repeat="(vol, b) in newCfg.Binds" class="form-group form-inline">
<div class="form-group">
<input type="text" ng-model="b.HostPath" class="form-control input-sm"
placeholder="Host path or volume name" style="width: 250px;" />
</div>
<div class="form-group">
<input type="text" ng-model="b.ContPath" ng-readonly="b.DefaultBind" class="form-control input-sm" placeholder="Container path" />
</div>
<div class="form-group">
<label><input type="checkbox" ng-model="b.ReadOnly" /> read only</label>
</div>
<div class="form-group">
<button class="btn btn-danger btn-sm input-sm form-control"
ng-click="rmEntry(newCfg.Binds, b)"><i class="glyphicon glyphicon-remove"></i>
</button>
</div>
</div>
<button type="button" class="btn btn-success btn-sm"
ng-click="addEntry(newCfg.Binds, { ContPath: '', HostPath: '', ReadOnly: false, DefaultBind: false })"><i class="glyphicon glyphicon-plus"></i> Add
</button>
<button class="btn btn-primary btn-sm"
ng-click="restartEnv()"
ng-show="!container.State.Restarting">Commit and restart</button>
</div>
</td>
</tr>
<tr>
<td>Volumes:</td>
<td>{{ container.Volumes }}</td>
+173 -4
View File
@@ -1,8 +1,14 @@
angular.module('container', [])
.controller('ContainerController', ['$scope', '$routeParams', '$location', 'Container', 'ContainerCommit', 'Messages', 'ViewSpinner',
function ($scope, $routeParams, $location, Container, ContainerCommit, Messages, ViewSpinner) {
angular.module('container', [])
.controller('ContainerController', ['$scope', '$routeParams', '$location', 'Container', 'ContainerCommit', 'Image', 'Messages', 'ViewSpinner', '$timeout',
function ($scope, $routeParams, $location, Container, ContainerCommit, Image, Messages, ViewSpinner, $timeout) {
$scope.changes = [];
$scope.edit = false;
$scope.editEnv = false;
$scope.editPorts = false;
$scope.editBinds = false;
$scope.newCfg = {
Env: [],
Ports: {}
};
var update = function () {
ViewSpinner.spin();
@@ -10,6 +16,51 @@ angular.module('container', [])
$scope.container = d;
$scope.container.edit = false;
$scope.container.newContainerName = d.Name;
// fill up env
if (d.Config.Env) {
$scope.newCfg.Env = d.Config.Env.map(function (entry) {
return {name: entry.split('=')[0], value: entry.split('=')[1]};
});
}
// fill up ports
$scope.newCfg.Ports = {};
angular.forEach(d.Config.ExposedPorts, function(i, port) {
if (d.HostConfig.PortBindings && port in d.HostConfig.PortBindings) {
$scope.newCfg.Ports[port] = d.HostConfig.PortBindings[port];
}
else {
$scope.newCfg.Ports[port] = [];
}
});
// fill up bindings
$scope.newCfg.Binds = [];
var defaultBinds = {};
angular.forEach(d.Config.Volumes, function(value, vol) {
defaultBinds[vol] = { ContPath: vol, HostPath: '', ReadOnly: false, DefaultBind: true };
});
angular.forEach(d.HostConfig.Binds, function(binding, i) {
var mountpoint = binding.split(':')[0];
var vol = binding.split(':')[1] || '';
var ro = binding.split(':').length > 2 && binding.split(':')[2] === 'ro';
var defaultBind = false;
if (vol === '') {
vol = mountpoint;
mountpoint = '';
}
if (vol in defaultBinds) {
delete defaultBinds[vol];
defaultBind = true;
}
$scope.newCfg.Binds.push({ ContPath: vol, HostPath: mountpoint, ReadOnly: ro, DefaultBind: defaultBind });
});
angular.forEach(defaultBinds, function(bind) {
$scope.newCfg.Binds.push(bind);
});
ViewSpinner.stop();
}, function (e) {
if (e.status === 404) {
@@ -20,6 +71,7 @@ angular.module('container', [])
}
ViewSpinner.stop();
});
};
$scope.start = function () {
@@ -58,6 +110,109 @@ angular.module('container', [])
});
};
$scope.restartEnv = function () {
var config = angular.copy($scope.container.Config);
config.Env = $scope.newCfg.Env.map(function(entry) {
return entry.name+"="+entry.value;
});
var portBindings = angular.copy($scope.newCfg.Ports);
angular.forEach(portBindings, function(item, key) {
if (item.length === 0) {
delete portBindings[key];
}
});
var binds = [];
angular.forEach($scope.newCfg.Binds, function(b) {
if (b.ContPath !== '') {
var bindLine = '';
if (b.HostPath !== '') {
bindLine = b.HostPath + ':';
}
bindLine += b.ContPath;
if (b.ReadOnly) {
bindLine += ':ro';
}
if (b.HostPath !== '' || !b.DefaultBind) {
binds.push(bindLine);
}
}
});
ViewSpinner.spin();
ContainerCommit.commit({id: $routeParams.id, tag: $scope.container.Config.Image, config: config }, function (d) {
if ('Id' in d) {
var imageId = d.Id;
Image.inspect({id: imageId}, function(imageData) {
// Append current host config to image with new port bindings
imageData.Config.HostConfig = angular.copy($scope.container.HostConfig);
imageData.Config.HostConfig.PortBindings = portBindings;
imageData.Config.HostConfig.Binds = binds;
if (imageData.Config.HostConfig.NetworkMode === 'host') {
imageData.Config.Hostname = '';
}
Container.create(imageData.Config, function(containerData) {
if (!('Id' in containerData)) {
Messages.error("Failure", "Container failed to create.");
return;
}
// Stop current if running
if ($scope.container.State.Running) {
Container.stop({id: $routeParams.id}, function (d) {
Messages.send("Container stopped", $routeParams.id);
// start new
Container.start({
id: containerData.Id
}, function (d) {
$location.url('/containers/' + containerData.Id + '/');
Messages.send("Container started", $routeParams.id);
}, function (e) {
update();
Messages.error("Failure", "Container failed to start." + e.data);
});
}, function (e) {
update();
Messages.error("Failure", "Container failed to stop." + e.data);
});
} else {
// start new
Container.start({
id: containerData.Id
}, function (d) {
$location.url('/containers/'+containerData.Id+'/');
Messages.send("Container started", $routeParams.id);
}, function (e) {
update();
Messages.error("Failure", "Container failed to start." + e.data);
});
}
}, function(e) {
update();
Messages.error("Failure", "Image failed to get." + e.data);
});
}, function (e) {
update();
Messages.error("Failure", "Image failed to get." + e.data);
});
} else {
update();
Messages.error("Failure", "Container commit failed.");
}
}, function (e) {
update();
Messages.error("Failure", "Container failed to commit." + e.data);
});
};
$scope.commit = function () {
ViewSpinner.spin();
ContainerCommit.commit({id: $routeParams.id, repo: $scope.container.Config.Image}, function (d) {
@@ -94,6 +249,7 @@ angular.module('container', [])
ViewSpinner.spin();
Container.remove({id: $routeParams.id}, function (d) {
update();
$location.path('/containers');
Messages.send("Container removed", $routeParams.id);
}, function (e) {
update();
@@ -138,6 +294,19 @@ angular.module('container', [])
$scope.container.edit = false;
};
$scope.addEntry = function (array, entry) {
array.push(entry);
};
$scope.rmEntry = function (array, entry) {
var idx = array.indexOf(entry);
array.splice(idx, 1);
};
$scope.toggleEdit = function() {
$scope.edit = !$scope.edit;
};
update();
$scope.getChanges();
}]);
+26 -16
View File
@@ -1,19 +1,29 @@
<div class="containerTop">
<div class="form-group col-xs-2">
<input type="text" class="form-control" placeholder="[options] (aux)" ng-model="ps_args">
<div class="row">
<div class="col-xs-12">
<h1>Top for: {{ containerName }}</h1>
</div>
</div>
<div class="row">
<div class="form-group col-xs-2">
<input type="text" class="form-control" placeholder="[options] (aux)" ng-model="ps_args">
</div>
<button type="button" class="btn btn-default" ng-click="getTop()">Submit</button>
</div>
<div class="row">
<div class="col-xs-12">
<table class="table table-striped">
<thead>
<tr>
<th ng-repeat="title in containerTop.Titles">{{title}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="processInfos in containerTop.Processes">
<td ng-repeat="processInfo in processInfos track by $index">{{processInfo}}</td>
</tr>
</tbody>
</table>
</div>
</div>
<button type="button" class="btn btn-default" ng-click="getTop()">Submit</button>
<table class="table table-striped">
<thead>
<tr>
<th ng-repeat="title in containerTop.Titles">{{title}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="processInfos in containerTop.Processes">
<td ng-repeat="processInfo in processInfos track by $index">{{processInfo}}</td>
</tr>
</tbody>
</table>
</div>
@@ -1,5 +1,5 @@
angular.module('containerTop', [])
.controller('ContainerTopController', ['$scope', '$routeParams', 'ContainerTop', 'ViewSpinner', function ($scope, $routeParams, ContainerTop, ViewSpinner) {
.controller('ContainerTopController', ['$scope', '$routeParams', 'ContainerTop', 'Container', 'ViewSpinner', function ($scope, $routeParams, ContainerTop, Container, ViewSpinner) {
$scope.ps_args = '';
/**
@@ -15,5 +15,11 @@ angular.module('containerTop', [])
});
};
Container.get({id: $routeParams.id}, function (d) {
$scope.containerName = d.Name.substring(1);
}, function (e) {
Messages.error("Failure", e.data);
});
$scope.getTop();
}]);
+37 -7
View File
@@ -25,16 +25,46 @@
<table class="table table-striped">
<thead>
<tr>
<th><input type="checkbox" ng-model="toggle" ng-change="toggleSelectAll()" /> Action</th>
<th>Name</th>
<th>Image</th>
<th>Command</th>
<th>Created</th>
<th>Status</th>
<th><label><input type="checkbox" ng-model="toggle" ng-change="toggleSelectAll()" /> Select</label></th>
<th>
<a href="#/containers/" ng-click="order('Names')">
Name
<span ng-show="sortType == 'Names' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Names' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/containers/" ng-click="order('Image')">
Image
<span ng-show="sortType == 'Image' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Image' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/containers/" ng-click="order('Command')">
Command
<span ng-show="sortType == 'Command' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Command' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/containers/" ng-click="order('Created')">
Created
<span ng-show="sortType == 'Created' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Created' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/containers/" ng-click="order('Status')">
Status
<span ng-show="sortType == 'Status' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Status' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="container in containers | filter:filter | orderBy:predicate">
<tr ng-repeat="container in containers | filter:filter | orderBy:sortType:sortReverse">
<td><input type="checkbox" ng-model="container.Checked" /></td>
<td><a href="#/containers/{{ container.Id }}/">{{ container|containername}}</a></td>
<td><a href="#/images/{{ container.Image }}/">{{ container.Image }}</a></td>
@@ -1,10 +1,16 @@
angular.module('containers', [])
.controller('ContainersController', ['$scope', 'Container', 'Settings', 'Messages', 'ViewSpinner',
function ($scope, Container, Settings, Messages, ViewSpinner) {
$scope.predicate = '-Created';
$scope.sortType = 'Created';
$scope.sortReverse = true;
$scope.toggle = false;
$scope.displayAll = Settings.displayAll;
$scope.order = function(sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
var update = function (data) {
ViewSpinner.spin();
Container.query(data, function (d) {
+1 -1
View File
@@ -7,7 +7,7 @@
<div class="jumbotron">
<h1>DockerUI</h1>
<p class="lead">The Linux container engine</p>
<p class="lead">The UI for Docker container engine</p>
<a class="btn btn-large btn-success" href="http://docker.io">Learn more.</a>
</div>
</div>
@@ -21,6 +21,7 @@ angular.module('dashboard', [])
if (Settings.firstLoad) {
opts.animation = true;
Settings.firstLoad = false;
localStorage.setItem('firstLoad', false);
$('#masthead').show();
setTimeout(function () {
+2 -2
View File
@@ -1,9 +1,9 @@
angular.module('footer', [])
.controller('FooterController', ['$scope', 'Settings', 'Docker', function ($scope, Settings, Docker) {
.controller('FooterController', ['$scope', 'Settings', 'Version', function ($scope, Settings, Version) {
$scope.template = 'app/components/footer/statusbar.html';
$scope.uiVersion = Settings.uiVersion;
Docker.get({}, function (d) {
Version.get({}, function (d) {
$scope.apiVersion = d.ApiVersion;
});
}]);
+20 -1
View File
@@ -63,10 +63,29 @@ angular.module('image', [])
return defer.promise;
}
/**
* Get RepoTags from the /images/query endpoint instead of /image/json,
* for backwards compatibility with Docker API versions older than 1.21
* @param {string} imageId
*/
function getRepoTags(imageId) {
Image.query({}, function (d) {
d.forEach(function(image) {
if (image.Id === imageId && image.RepoTags[0] !== '<none>:<none>') {
$scope.RepoTags = image.RepoTags;
}
});
});
}
Image.get({id: $routeParams.id}, function (d) {
$scope.image = d;
$scope.id = d.Id;
$scope.RepoTags = d.RepoTags;
if (d.RepoTags) {
$scope.RepoTags = d.RepoTags;
} else {
getRepoTags($scope.id);
}
getContainersFromImage($q, Container, $scope.id).then(function (containers) {
LineChart.build('#containers-started-chart', containers, function (c) {
+30 -6
View File
@@ -21,15 +21,39 @@
<table class="table table-striped">
<thead>
<tr>
<th><input type="checkbox" ng-model="toggle" ng-change="toggleSelectAll()" /> Action</th>
<th>Id</th>
<th>Repository</th>
<th>VirtualSize</th>
<th>Created</th>
<th><label><input type="checkbox" ng-model="toggle" ng-change="toggleSelectAll()" /> Select</label></th>
<th>
<a href="#/images/" ng-click="order('Id')">
Id
<span ng-show="sortType == 'Id' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Id' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/images/" ng-click="order('RepoTags')">
Repository
<span ng-show="sortType == 'RepoTags' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'RepoTags' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/images/" ng-click="order('VirtualSize')">
VirtualSize
<span ng-show="sortType == 'VirtualSize' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'VirtualSize' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/images/" ng-click="order('Created')">
Created
<span ng-show="sortType == 'Created' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Created' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="image in images | filter:filter | orderBy:predicate">
<tr ng-repeat="image in images | filter:filter | orderBy:sortType:sortReverse">
<td><input type="checkbox" ng-model="image.Checked" /></td>
<td><a href="#/images/{{ image.Id }}/?tag={{ image|repotag }}">{{ image.Id|truncate:20}}</a></td>
<td>{{ image|repotag }}</td>
+7 -1
View File
@@ -1,8 +1,14 @@
angular.module('images', [])
.controller('ImagesController', ['$scope', 'Image', 'ViewSpinner', 'Messages',
function ($scope, Image, ViewSpinner, Messages) {
$scope.sortType = 'Created';
$scope.sortReverse = true;
$scope.toggle = false;
$scope.predicate = '-Created';
$scope.order = function(sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.showBuilder = function () {
$('#build-modal').modal('show');
+4 -5
View File
@@ -1,15 +1,14 @@
angular.module('info', [])
.controller('InfoController', ['$scope', 'System', 'Docker', 'Settings', 'Messages',
function ($scope, System, Docker, Settings, Messages) {
.controller('InfoController', ['$scope', 'Info', 'Version', 'Settings',
function ($scope, Info, Version, Settings) {
$scope.info = {};
$scope.docker = {};
$scope.endpoint = Settings.endpoint;
$scope.apiVersion = Settings.version;
Docker.get({}, function (d) {
Version.get({}, function (d) {
$scope.docker = d;
});
System.get({}, function (d) {
Info.get({}, function (d) {
$scope.info = d;
});
}]);
+18 -7
View File
@@ -1,10 +1,21 @@
<div class="masthead">
<h3 class="text-muted">DockerUI</h3>
<ul class="nav well">
<li><a href="#/">Dashboard</a></li>
<li><a href="#/containers/">Containers</a></li>
<li><a href="#/containers_network/">Containers Network</a></li>
<li><a href="#/images/">Images</a></li>
<li><a href="#/info/">Info</a></li>
</ul>
<div class="col-xs-11">
<ul class="nav well">
<li><a href="#/">Dashboard</a></li>
<li><a href="#/containers/">Containers</a></li>
<li><a href="#/containers_network/">Containers Network</a></li>
<li><a href="#/images/">Images</a></li>
<li ng-if="showNetworksVolumes"><a href="#/networks/">Networks</a></li>
<li ng-if="showNetworksVolumes"><a href="#/volumes/">Volumes</a></li>
<li><a href="#/info/">Info</a></li>
</ul>
</div>
<div class="col-xs-1">
<button class="btn btn-primary" ng-click="refresh()">
<span class="glyphicon glyphicon-refresh" aria-hidden="true"></span>
Refresh
</button>
</div>
</div>
+12 -1
View File
@@ -1,4 +1,15 @@
angular.module('masthead', [])
.controller('MastheadController', ['$scope', function ($scope) {
.controller('MastheadController', ['$scope', 'Version', function ($scope, Version) {
$scope.template = 'app/components/masthead/masthead.html';
$scope.showNetworksVolumes = false;
Version.get(function(d) {
if (d.ApiVersion >= 1.21) {
$scope.showNetworksVolumes = true;
}
});
$scope.refresh = function() {
location.reload();
};
}]);
+110
View File
@@ -0,0 +1,110 @@
<div class="detail">
<h4>Network: {{ network.Name }}</h4>
<table class="table table-striped">
<tbody>
<tr>
<td>Name:</td>
<td>{{ network.Name }}</td>
</tr>
<tr>
<td>Id:</td>
<td>{{ network.Id }}</td>
</tr>
<tr>
<td>Scope:</td>
<td>{{ network.Scope }}</td>
</tr>
<tr>
<td>Driver:</td>
<td>{{ network.Driver }}</td>
</tr>
<tr>
<td>IPAM:</td>
<td>
<table class="table table-striped">
<tr>
<td>Driver:</td>
<td>{{ network.IPAM.Driver }}</td>
</tr>
<tr>
<td>Subnet:</td>
<td>{{ network.IPAM.Config[0].Subnet }}</td>
</tr>
<tr>
<td>Gateway:</td>
<td>{{ network.IPAM.Config[0].Gateway }}</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>Containers:</td>
<td>
<table class="table table-striped" ng-repeat="(Id, container) in network.Containers">
<tr>
<td>Id:</td>
<td><a href="#/containers/{{ Id }}">{{ Id }}</a></td>
<td>
<button ng-click="disconnect(network.Id, Id)" class="btn btn-danger btn-sm">
Disconnect from network
</button>
</td>
</tr>
<tr>
<td>EndpointID:</td>
<td>{{ container.EndpointID}}</td>
</tr>
<tr>
<td>MacAddress:</td>
<td>{{ container.MacAddress}}</td>
</tr>
<tr>
<td>IPv4Address:</td>
<td>{{ container.IPv4Address}}</td>
</tr>
<tr>
<td>IPv6Address:</td>
<td>{{ container.IPv6Address}}</td>
</tr>
</table>
<form class="form-inline">
<div class="form-group">
<label>Container ID:
<input ng-model="containerId" placeholder="3613f73ba0e4" class="form-control">
</label>
</div>
<button ng-click="connect(network.Id, containerId)" class="btn btn-primary">
Connect
</button>
</form>
</td>
</tr>
<tr>
<td>Options:</td>
<td>
<table role="table" class="table table-striped">
<tr>
<th>Key</th>
<th>Value</th>
</tr>
<tr ng-repeat="(k, v) in network.Options">
<td>{{ k }}</td>
<td>{{ v }}</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
<hr/>
<div class="btn-remove">
<button class="btn btn-large btn-block btn-primary btn-danger" ng-click="removeImage(id)">Remove Network
</button>
</div>
</div>
@@ -0,0 +1,56 @@
angular.module('network', []).config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/networks/:id/', {
templateUrl: 'app/components/network/network.html',
controller: 'NetworkController'
});
}]).controller('NetworkController', ['$scope', 'Network', 'ViewSpinner', 'Messages', '$routeParams', '$location', 'errorMsgFilter',
function ($scope, Network, ViewSpinner, Messages, $routeParams, $location, errorMsgFilter) {
$scope.disconnect = function disconnect(networkId, containerId) {
ViewSpinner.spin();
Network.disconnect({id: $routeParams.id}, {Container: containerId}, function (d) {
ViewSpinner.stop();
Messages.send("Container disconnected", containerId);
$location.path('/networks/' + $routeParams.id); // Refresh the current page.
}, function (e) {
ViewSpinner.stop();
Messages.error("Failure", e.data);
});
};
$scope.connect = function connect(networkId, containerId) {
ViewSpinner.spin();
Network.connect({id: $routeParams.id}, {Container: containerId}, function (d) {
ViewSpinner.stop();
var errmsg = errorMsgFilter(d);
if (errmsg) {
Messages.error('Error', errmsg);
} else {
Messages.send("Container connected", d);
}
$location.path('/networks/' + $routeParams.id); // Refresh the current page.
}, function (e) {
ViewSpinner.stop();
Messages.error("Failure", e.data);
});
};
$scope.remove = function remove(networkId) {
ViewSpinner.spin();
Network.remove({id: $routeParams.id}, function (d) {
ViewSpinner.stop();
Messages.send("Network removed", d);
$location.path('/networks'); // Go to the networks page
}, function (e) {
ViewSpinner.stop();
Messages.error("Failure", e.data);
});
};
ViewSpinner.spin();
Network.get({id: $routeParams.id}, function (d) {
$scope.network = d;
ViewSpinner.stop();
}, function (e) {
Messages.error("Failure", e.data);
ViewSpinner.stop();
});
}]);
+121
View File
@@ -0,0 +1,121 @@
<h2>Networks:</h2>
<div>
<ul class="nav nav-pills pull-left">
<li class="dropdown">
<a class="dropdown-toggle" id="drop4" role="button" data-toggle="dropdown" data-target="#">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>
<div class="pull-right form-inline">
<input type="text" class="form-control" id="filter" placeholder="Filter" ng-model="filter"/> <label
class="sr-only" for="filter">Filter</label>
</div>
</div>
<table class="table table-striped">
<thead>
<tr>
<th><label><input type="checkbox" ng-model="toggle" ng-change="toggleSelectAll()"/> Select</label></th>
<th>
<a href="#/networks/" ng-click="order('Name')">
Name
<span ng-show="sortType == 'Name' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/networks/" ng-click="order('Id')">
Id
<span ng-show="sortType == 'Id' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Id' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/networks/" ng-click="order('Scope')">
Scope
<span ng-show="sortType == 'Scope' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Scope' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/networks/" ng-click="order('Driver')">
Driver
<span ng-show="sortType == 'Driver' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Driver' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/networks/" ng-click="order('IPAM.Driver')">
IPAM Driver
<span ng-show="sortType == 'IPAM.Driver' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'IPAM.Driver' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/networks/" ng-click="order('IPAM.Config[0].Subnet')">
IPAM Subnet
<span ng-show="sortType == 'IPAM.Config[0].Subnet' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'IPAM.Config[0].Subnet' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/networks/" ng-click="order('IPAM.Config[0].Gateway')">
IPAM Gateway
<span ng-show="sortType == 'IPAM.Config[0].Gateway' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'IPAM.Config[0].Gateway' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="network in networks | filter:filter | orderBy:sortType:sortReverse">
<td><input type="checkbox" ng-model="network.Checked"/></td>
<td><a href="#/networks/{{ network.Id }}/">{{ network.Name|truncate:20}}</a></td>
<td>{{ network.Id }}</td>
<td>{{ network.Scope }}</td>
<td>{{ network.Driver }}</td>
<td>{{ network.IPAM.Driver }}</td>
<td>{{ network.IPAM.Config[0].Subnet }}</td>
<td>{{ network.IPAM.Config[0].Gateway }}</td>
</tr>
</tbody>
</table>
<div class="row">
<div class="col-xs-offset-3 col-xs-6">
<form role="form" class="">
<div class="form-group">
<label>Name:</label>
<input type="text" placeholder='isolated_nw'
ng-model="createNetworkConfig.Name" class="form-control"/>
</div>
<div class="form-group">
<label>Driver:</label>
<input type="text" placeholder='bridge'
ng-model="createNetworkConfig.Driver" class="form-control"/>
</div>
<div class="form-group">
<label>Subnet:</label>
<input type="text" placeholder='172.20.0.0/16'
ng-model="createNetworkConfig.IPAM.Config[0].Subnet" class="form-control"/>
</div>
<div class="form-group">
<label>IPRange:</label>
<input type="text" placeholder='172.20.10.0/24'
ng-model="createNetworkConfig.IPAM.Config[0].IPRange" class="form-control"/>
</div>
<div class="form-group">
<label>Gateway:</label>
<input type="text" placeholder='172.20.10.11'
ng-model="createNetworkConfig.IPAM.Config[0].Gateway" class="form-control"/>
</div>
<button type="button" class="btn btn-success btn-sm"
ng-click="addNetwork(createNetworkConfig)">
Create Network
</button>
</form>
</div>
</div>
@@ -0,0 +1,87 @@
angular.module('networks', []).config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/networks/', {
templateUrl: 'app/components/networks/networks.html',
controller: 'NetworksController'
});
}]).controller('NetworksController', ['$scope', 'Network', 'ViewSpinner', 'Messages', '$route', 'errorMsgFilter',
function ($scope, Network, ViewSpinner, Messages, $route, errorMsgFilter) {
$scope.sortType = 'Name';
$scope.sortReverse = true;
$scope.toggle = false;
$scope.order = function(sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.createNetworkConfig = {
"Name": '',
"Driver": '',
"IPAM": {
"Config": [{
"Subnet": '',
"IPRange": '',
"Gateway": ''
}]
}
};
$scope.removeAction = function () {
ViewSpinner.spin();
var counter = 0;
var complete = function () {
counter = counter - 1;
if (counter === 0) {
ViewSpinner.stop();
}
};
angular.forEach($scope.networks, function (network) {
if (network.Checked) {
counter = counter + 1;
Network.remove({id: network.Id}, function (d) {
Messages.send("Network deleted", network.Id);
var index = $scope.networks.indexOf(network);
$scope.networks.splice(index, 1);
complete();
}, function (e) {
Messages.error("Failure", e.data);
complete();
});
}
});
};
$scope.toggleSelectAll = function () {
angular.forEach($scope.networks, function (i) {
i.Checked = $scope.toggle;
});
};
$scope.addNetwork = function addNetwork(createNetworkConfig) {
ViewSpinner.spin();
Network.create(createNetworkConfig, function (d) {
if (d.Id) {
Messages.send("Network created", d.Id);
} else {
Messages.error('Failure', errorMsgFilter(d));
}
ViewSpinner.stop();
fetchNetworks();
}, function (e) {
Messages.error("Failure", e.data);
ViewSpinner.stop();
});
};
function fetchNetworks() {
ViewSpinner.spin();
Network.query({}, function (d) {
$scope.networks = d;
ViewSpinner.stop();
}, function (e) {
Messages.error("Failure", e.data);
ViewSpinner.stop();
});
}
fetchNetworks();
}]);
@@ -1,6 +1,6 @@
angular.module('pullImage', [])
.controller('PullImageController', ['$scope', '$log', 'Dockerfile', 'Messages', 'Image', 'ViewSpinner',
function ($scope, $log, Dockerfile, Messages, Image, ViewSpinner) {
.controller('PullImageController', ['$scope', '$log', 'Messages', 'Image', 'ViewSpinner',
function ($scope, $log, Messages, Image, ViewSpinner) {
$scope.template = 'app/components/pullImage/pullImage.html';
$scope.init = function () {
@@ -40,7 +40,7 @@ angular.module('startContainer', ['ui.bootstrap'])
function rmEmptyKeys(col) {
for (var key in col) {
if (col[key] === null || col[key] === undefined || col[key] === '' || $.isEmptyObject(col[key]) || col[key].length === 0) {
if (col[key] === null || col[key] === undefined || col[key] === '' || ($.isPlainObject(col[key]) && $.isEmptyObject(col[key])) || col[key].length === 0) {
delete col[key];
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
<div class="row">
<div class="col-xs-12">
<h1>Stats</h1>
<h1>Stats for: {{ containerName }}</h1>
<h2>CPU</h2>
@@ -44,7 +44,7 @@
</div>
</div>
<h1>Network</h1>
<h1>Network {{ networkName}}</h1>
<div class="row">
<div class="col-sm-7">
<canvas id="network-stats-chart" width="650" height="300"></canvas>
+15 -20
View File
@@ -1,21 +1,7 @@
angular.module('stats', [])
.controller('StatsController', ['Settings', '$scope', 'Messages', '$timeout', 'Container', '$routeParams', 'humansizeFilter', '$sce', function (Settings, $scope, Messages, $timeout, Container, $routeParams, humansizeFilter, $sce) {
// TODO: Implement memory chart, force scale to 0-100 for cpu, 0 to limit for memory, fix charts on dashboard,
// TODO: Force scale to 0-100 for cpu, fix charts on dashboard,
// TODO: Force memory scale to 0 - max memory
//var initialStats = {}; // Used to set scale of memory graph.
//
//Container.stats({id: $routeParams.id}, function (d) {
// var arr = Object.keys(d).map(function (key) {
// return d[key];
// });
// if (arr.join('').indexOf('no such id') !== -1) {
// Messages.error('Unable to retrieve stats', 'Is this container running?');
// return;
// }
// initialStats = d;
//}, function () {
// Messages.error('Unable to retrieve stats', 'Is this container running?');
//});
var cpuLabels = [];
var cpuData = [];
@@ -124,9 +110,10 @@ angular.module('stats', [])
updateCpuChart(d);
updateMemoryChart(d);
updateNetworkChart(d);
timeout = $timeout(updateStats, 2000);
timeout = $timeout(updateStats, 5000);
}, function () {
Messages.error('Unable to retrieve stats', 'Is this container running?');
timeout = $timeout(updateStats, 5000);
});
}
@@ -138,13 +125,11 @@ angular.module('stats', [])
updateStats();
function updateCpuChart(data) {
console.log('updateCpuChart', data);
cpuChart.addData([calculateCPUPercent(data)], new Date(data.read).toLocaleTimeString());
cpuChart.removeData();
}
function updateMemoryChart(data) {
console.log('updateMemoryChart', data);
memoryChart.addData([data.memory_stats.usage], new Date(data.read).toLocaleTimeString());
memoryChart.removeData();
}
@@ -152,6 +137,12 @@ angular.module('stats', [])
var lastRxBytes = 0, lastTxBytes = 0;
function updateNetworkChart(data) {
// 1.9+ contains an object of networks, for now we'll just show stats for the first network
// TODO: Show graphs for all networks
if (data.networks) {
$scope.networkName = Object.keys(data.networks)[0];
data.network = data.networks[$scope.networkName];
}
var rxBytes = 0, txBytes = 0;
if (lastRxBytes !== 0 || lastTxBytes !== 0) {
// These will be zero on first call, ignore to prevent large graph spike
@@ -160,7 +151,6 @@ angular.module('stats', [])
}
lastRxBytes = data.network.rx_bytes;
lastTxBytes = data.network.tx_bytes;
console.log('updateNetworkChart', data);
networkChart.addData([rxBytes, txBytes], new Date(data.read).toLocaleTimeString());
networkChart.removeData();
}
@@ -178,10 +168,15 @@ angular.module('stats', [])
var systemDelta = curCpu.system_cpu_usage - prevCpu.system_cpu_usage;
if (systemDelta > 0.0 && cpuDelta > 0.0) {
//console.log('size thing:', curCpu.cpu_usage.percpu_usage);
cpuPercent = (cpuDelta / systemDelta) * curCpu.cpu_usage.percpu_usage.length * 100.0;
}
return cpuPercent;
}
Container.get({id: $routeParams.id}, function (d) {
$scope.containerName = d.Name.substring(1);
}, function (e) {
Messages.error("Failure", e.data);
});
}])
;
+74
View File
@@ -0,0 +1,74 @@
<h2>Volumes:</h2>
<div>
<ul class="nav nav-pills pull-left">
<li class="dropdown">
<a class="dropdown-toggle" id="drop4" role="button" data-toggle="dropdown" data-target="#">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>
<div class="pull-right form-inline">
<input type="text" class="form-control" id="filter" placeholder="Filter" ng-model="filter"/> <label
class="sr-only" for="filter">Filter</label>
</div>
</div>
<table class="table table-striped">
<thead>
<tr>
<th><label><input type="checkbox" ng-model="toggle" ng-change="toggleSelectAll()"/> Select</label></th>
<th>
<a href="#/volumes/" ng-click="order('Name')">
Name
<span ng-show="sortType == 'Name' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/volumes/" ng-click="order('Driver')">
Driver
<span ng-show="sortType == 'Driver' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Driver' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/volumes/" ng-click="order('Mountpoint')">
Mountpoint
<span ng-show="sortType == 'Mountpoint' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Mountpoint' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="volume in volumes | filter:filter | orderBy:sortType:sortReverse">
<td><input type="checkbox" ng-model="volume.Checked"/></td>
<td>{{ volume.Name|truncate:20 }}</td>
<td>{{ volume.Driver }}</td>
<td>{{ volume.Mountpoint }}</td>
</tr>
</tbody>
</table>
<div class="row">
<div class="col-xs-offset-3 col-xs-6">
<form role="form" class="">
<div class="form-group">
<label>Name:</label>
<input type="text" placeholder='tardis'
ng-model="createVolumeConfig.Name" class="form-control"/>
</div>
<div class="form-group">
<label>Driver:</label>
<input type="text" placeholder='local'
ng-model="createVolumeConfig.Driver" class="form-control"/>
</div>
<button type="button" class="btn btn-success btn-sm"
ng-click="addVolume(createVolumeConfig)">
Create Volume
</button>
</form>
</div>
</div>
@@ -0,0 +1,80 @@
angular.module('volumes', []).config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/volumes/', {
templateUrl: 'app/components/volumes/volumes.html',
controller: 'VolumesController'
});
}]).controller('VolumesController', ['$scope', 'Volume', 'ViewSpinner', 'Messages', '$route', 'errorMsgFilter',
function ($scope, Volume, ViewSpinner, Messages, $route, errorMsgFilter) {
$scope.sortType = 'Name';
$scope.sortReverse = true;
$scope.toggle = false;
$scope.order = function(sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.createVolumeConfig = {
"Name": "",
"Driver": ""
};
$scope.removeAction = function () {
ViewSpinner.spin();
var counter = 0;
var complete = function () {
counter = counter - 1;
if (counter === 0) {
ViewSpinner.stop();
}
};
angular.forEach($scope.volumes, function (volume) {
if (volume.Checked) {
counter = counter + 1;
Volume.remove({name: volume.Name}, function (d) {
Messages.send("Volume deleted", volume.Name);
var index = $scope.volumes.indexOf(volume);
$scope.volumes.splice(index, 1);
complete();
}, function (e) {
Messages.error("Failure", e.data);
complete();
});
}
});
};
$scope.toggleSelectAll = function () {
angular.forEach($scope.volumes, function (i) {
i.Checked = $scope.toggle;
});
};
$scope.addVolume = function addVolume(createVolumeConfig) {
ViewSpinner.spin();
Volume.create(createVolumeConfig, function (d) {
if (d.Name) {
Messages.send("Volume created", d.Name);
} else {
Messages.error('Failure', errorMsgFilter(d));
}
ViewSpinner.stop();
fetchVolumes();
}, function (e) {
Messages.error("Failure", e.data);
ViewSpinner.stop();
});
};
function fetchVolumes() {
ViewSpinner.spin();
Volume.query({}, function (d) {
$scope.volumes = d.Volumes;
ViewSpinner.stop();
}, function (e) {
Messages.error("Failure", e.data);
ViewSpinner.stop();
});
}
fetchVolumes();
}]);
+39 -30
View File
@@ -18,7 +18,7 @@ angular.module('dockerui.services', ['ngResource'])
create: {method: 'POST', params: {action: 'create'}},
remove: {method: 'DELETE', params: {id: '@id', v: 0}},
rename: {method: 'POST', params: {id: '@id', action: 'rename'}, isArray: false},
stats: {method: 'GET', params: {id: '@id', stream: false, action: 'stats'}, timeout: 2000}
stats: {method: 'GET', params: {id: '@id', stream: false, action: 'stats'}, timeout: 5000}
});
}])
.factory('ContainerCommit', ['$resource', '$http', 'Settings', function ContainerCommitFactory($resource, $http, Settings) {
@@ -31,8 +31,10 @@ angular.module('dockerui.services', ['ngResource'])
url: Settings.url + '/commit',
params: {
'container': params.id,
'repo': params.repo
}
'tag': params.tag || null,
'repo': params.repo || null
},
data: params.config
}).success(callback).error(function (data, status, headers, config) {
console.log(error, data);
});
@@ -92,10 +94,11 @@ 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', tag: '@tag'}},
remove: {method: 'DELETE', params: {id: '@id'}, isArray: true}
remove: {method: 'DELETE', params: {id: '@id'}, isArray: true},
inspect: {method: 'GET', params: {id: '@id', action: 'json'}}
});
}])
.factory('Docker', ['$resource', 'Settings', function DockerFactory($resource, Settings) {
.factory('Version', ['$resource', 'Settings', function VersionFactory($resource, Settings) {
'use strict';
// http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#show-the-docker-version-information
return $resource(Settings.url + '/version', {}, {
@@ -110,27 +113,48 @@ angular.module('dockerui.services', ['ngResource'])
update: {method: 'POST'}
});
}])
.factory('System', ['$resource', 'Settings', function SystemFactory($resource, Settings) {
.factory('Info', ['$resource', 'Settings', function InfoFactory($resource, Settings) {
'use strict';
// http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#display-system-wide-information
return $resource(Settings.url + '/info', {}, {
get: {method: 'GET'}
});
}])
.factory('Settings', ['DOCKER_ENDPOINT', 'DOCKER_PORT', 'DOCKER_API_VERSION', 'UI_VERSION', function SettingsFactory(DOCKER_ENDPOINT, DOCKER_PORT, DOCKER_API_VERSION, UI_VERSION) {
.factory('Network', ['$resource', 'Settings', function NetworkFactory($resource, Settings) {
'use strict';
// http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#2-5-networks
return $resource(Settings.url + '/networks/:id/:action', {id: '@id'}, {
query: {method: 'GET', isArray: true},
get: {method: 'GET'},
create: {method: 'POST', params: {action: 'create'}},
remove: {method: 'DELETE'},
connect: {method: 'POST', params: {action: 'connect'}},
disconnect: {method: 'POST', params: {action: 'disconnect'}}
});
}])
.factory('Volume', ['$resource', 'Settings', function VolumeFactory($resource, Settings) {
'use strict';
// http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#2-5-networks
return $resource(Settings.url + '/volumes/:name/:action', {name: '@name'}, {
query: {method: 'GET'},
get: {method: 'GET'},
create: {method: 'POST', params: {action: 'create'}},
remove: {method: 'DELETE'}
});
}])
.factory('Settings', ['DOCKER_ENDPOINT', 'DOCKER_PORT', 'UI_VERSION', function SettingsFactory(DOCKER_ENDPOINT, DOCKER_PORT, UI_VERSION) {
'use strict';
var url = DOCKER_ENDPOINT;
if (DOCKER_PORT) {
url = url + DOCKER_PORT + '\\' + DOCKER_PORT;
}
var firstLoad = (localStorage.getItem('firstLoad') || 'true') === 'true';
return {
displayAll: false,
endpoint: DOCKER_ENDPOINT,
version: DOCKER_API_VERSION,
rawUrl: DOCKER_ENDPOINT + DOCKER_PORT + '/' + DOCKER_API_VERSION,
uiVersion: UI_VERSION,
url: url,
firstLoad: true
firstLoad: firstLoad
};
}])
.factory('ViewSpinner', function ViewSpinnerFactory() {
@@ -176,23 +200,6 @@ angular.module('dockerui.services', ['ngResource'])
}
};
}])
.factory('Dockerfile', ['Settings', function DockerfileFactory(Settings) {
'use strict';
// http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#build-image-from-a-dockerfile
var url = Settings.rawUrl + '/build';
return {
build: function (file, callback) {
var data = new FormData();
var dockerfile = new Blob([file], {type: 'text/text'});
data.append('Dockerfile', dockerfile);
var request = new XMLHttpRequest();
request.onload = callback;
request.open('POST', url);
request.send(data);
}
};
}])
.factory('LineChart', ['Settings', function LineChartFactory(Settings) {
'use strict';
return {
@@ -222,9 +229,10 @@ angular.module('dockerui.services', ['ngResource'])
labels.push(k);
data.push(map[k]);
if (map[k] > max) {
max = map[k];
max = map[k];
}
}
var steps = Math.min(max, 10);
var dataset = {
fillColor: "rgba(151,187,205,0.5)",
strokeColor: "rgba(151,187,205,1)",
@@ -237,10 +245,11 @@ angular.module('dockerui.services', ['ngResource'])
datasets: [dataset]
},
{
scaleStepWidth: 1,
scaleStepWidth: Math.ceil(max / steps),
pointDotRadius: 1,
scaleIntegersOnly: true,
scaleOverride: true,
scaleSteps: max
scaleSteps: steps
});
}
};
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Executable → Regular
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 15 KiB

+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "dockerui",
"version": "0.8.0",
"version": "0.10.0-beta",
"homepage": "https://github.com/crosbymichael/dockerui",
"authors": [
"Michael Crosby <crosbymichael@gmail.com>",
+6 -5
View File
File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 15 KiB

+1 -4
View File
@@ -22,10 +22,7 @@
<!-- Fav and touch icons -->
<link rel="shortcut icon" href="ico/favicon.ico">
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="ico/apple-touch-icon-144-precomposed.png">
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="ico/apple-touch-icon-114-precomposed.png">
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="ico/apple-touch-icon-72-precomposed.png">
<link rel="apple-touch-icon-precomposed" href="ico/apple-touch-icon-57-precomposed.png">
<link rel="apple-touch-icon-precomposed" href="ico/apple-touch-icon-precomposed.png">
</head>
<body>
+4
View File
@@ -0,0 +1,4 @@
FROM nginx:1.9.9
COPY default.conf /etc/nginx/conf.d/default.conf
COPY users.htpasswd /etc/nginx/users.htpasswd
+17
View File
@@ -0,0 +1,17 @@
upstream dockerui {
server dockerui:9000;
}
server {
listen 80;
server_name localhost;
location / {
auth_basic "Docker UI";
auth_basic_user_file /etc/nginx/users.htpasswd;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://dockerui;
}
}
@@ -0,0 +1,12 @@
dockerui:
image: dockerui/dockerui
privileged: true
volumes:
- /var/run/docker.sock:/var/run/docker.sock
nginx:
build: .
links:
- dockerui
ports:
- 80:80
+1
View File
@@ -0,0 +1 @@
user:{PLAIN}password
+3
View File
@@ -0,0 +1,3 @@
FROM debian
RUN apt-get update && apt-get install -y socat
+11
View File
@@ -0,0 +1,11 @@
# DockerUI with Swarm
This example works with swarm clusters created with docker-machine.
## Usage
Make sure your client is pointed directly to the Docker daemon on the swarm-master's node (not through swarm).
```
docker-compose up -d
```
+11
View File
@@ -0,0 +1,11 @@
dockerui:
image: dockerui/dockerui
command: -e http://127.0.0.1:2375
net: "host"
socat:
build: .
net: "host"
command: socat -d -d TCP-L:2375,fork,bind=localhost ssl:127.0.0.1:3376,cert=/var/lib/boot2docker/server.pem,cafile=/var/lib/boot2docker/ca.pem,key=/var/lib/boot2docker/server-key.pem
volumes:
- /var/lib/boot2docker:/var/lib/boot2docker
+29 -2
View File
@@ -14,10 +14,30 @@ module.exports = function (grunt) {
// Default task.
grunt.registerTask('default', ['jshint', 'build', 'karma:unit']);
grunt.registerTask('build', ['clean:app', 'if:binaryNotExist', 'html2js', 'concat', 'clean:tmpl', 'recess:build', 'copy']);
grunt.registerTask('release', ['clean:all', 'if:binaryNotExist', 'html2js', 'uglify', 'clean:tmpl', 'jshint', 'karma:unit', 'concat:index', 'recess:min', 'copy']);
grunt.registerTask('build', [
'clean:app',
'if:binaryNotExist',
'html2js',
'concat',
'clean:tmpl',
'recess:build',
'copy'
]);
grunt.registerTask('release', [
'clean:app',
'if:binaryNotExist',
'html2js',
'uglify',
'clean:tmpl',
'jshint',
//'karma:unit',
'concat:index',
'recess:min',
'copy'
]);
grunt.registerTask('test-watch', ['karma:watch']);
grunt.registerTask('run', ['if:binaryNotExist', 'build', 'shell:buildImage', 'shell:run']);
grunt.registerTask('runSwarm', ['if:binaryNotExist', 'build', 'shell:buildImage', 'shell:runSwarm']);
grunt.registerTask('run-dev', ['if:binaryNotExist', 'shell:buildImage', 'shell:run', 'watch:build']);
// Print a timestamp (useful for when watching)
@@ -240,6 +260,13 @@ module.exports = function (grunt) {
'docker run --privileged -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock --name dockerui dockerui'
].join(';')
},
runSwarm: {
command: [
'docker stop dockerui',
'docker rm dockerui',
'docker run --net=host -d --name dockerui dockerui -e http://127.0.0.1:2374'
].join(';')
},
cleanImages: {
command: 'docker rmi $(docker images -q -f dangling=true)'
}
+1 -4
View File
@@ -22,10 +22,7 @@
<!-- Fav and touch icons -->
<link rel="shortcut icon" href="ico/favicon.ico">
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="ico/apple-touch-icon-144-precomposed.png">
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="ico/apple-touch-icon-114-precomposed.png">
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="ico/apple-touch-icon-72-precomposed.png">
<link rel="apple-touch-icon-precomposed" href="ico/apple-touch-icon-57-precomposed.png">
<link rel="apple-touch-icon-precomposed" href="ico/apple-touch-icon-precomposed.png">
</head>
<body>
+1 -1
View File
@@ -2,7 +2,7 @@
"author": "Michael Crosby & Kevan Ahlquist",
"name": "dockerui",
"homepage": "https://github.com/crosbymichael/dockerui",
"version": "0.8.0",
"version": "0.10.0-beta",
"repository": {
"type": "git",
"url": "git@github.com:crosbymichael/dockerui.git"
@@ -15,12 +15,14 @@ describe("ContainerTopController", function () {
}));
it("should test controller initialize", function () {
$httpBackend.expectGET('dockerapi/containers/b17882378cee8ec0136f482681b764cca430befd52a9bfd1bde031f49b8bba9f/json').respond(200, {Name: '/foo'});
$httpBackend.expectGET('dockerapi/containers/b17882378cee8ec0136f482681b764cca430befd52a9bfd1bde031f49b8bba9f/top?ps_args=').respond(200);
expect($scope.ps_args).toBeDefined();
$httpBackend.flush();
});
it("a correct top request to the Docker remote API", function () {
$httpBackend.expectGET('dockerapi/containers/b17882378cee8ec0136f482681b764cca430befd52a9bfd1bde031f49b8bba9f/json').respond(200, {Name: '/foo'});
$httpBackend.expectGET('dockerapi/containers/' + $routeParams.id + '/top?ps_args=').respond(200);
$routeParams.id = '123456789123456789123456789';
$scope.ps_args = 'aux';
@@ -0,0 +1,83 @@
describe('NetworkController', function () {
var $scope, $httpBackend, $routeParams;
beforeEach(module('dockerui'));
beforeEach(inject(function (_$httpBackend_, $controller, _$routeParams_) {
$scope = {};
$httpBackend = _$httpBackend_;
$routeParams = _$routeParams_;
$routeParams.id = 'f1e1ce1613ccd374a75caf5e2c3ab35520d1944f91498c1974ec86fb4019c79b';
$controller('NetworkController', {
'$scope': $scope,
'$routeParams': $routeParams
});
}));
it('initializes correctly', function () {
expectGetNetwork();
$httpBackend.flush();
});
it('issues a correct connect call to the remote API', function () {
expectGetNetwork();
$httpBackend.expectPOST('dockerapi/networks/f1e1ce1613ccd374a75caf5e2c3ab35520d1944f91498c1974ec86fb4019c79b/connect', {'Container': 'containerId'}).respond(200);
$scope.connect($routeParams.id, 'containerId');
$httpBackend.flush();
});
it('issues a correct disconnect call to the remote API', function () {
expectGetNetwork();
$httpBackend.expectPOST('dockerapi/networks/f1e1ce1613ccd374a75caf5e2c3ab35520d1944f91498c1974ec86fb4019c79b/disconnect', {'Container': 'containerId'}).respond(200);
$scope.disconnect($routeParams.id, 'containerId');
$httpBackend.flush();
});
it('issues a correct remove call to the remote API', function () {
expectGetNetwork();
$httpBackend.expectDELETE('dockerapi/networks/f1e1ce1613ccd374a75caf5e2c3ab35520d1944f91498c1974ec86fb4019c79b').respond(204);
$scope.remove($routeParams.id);
$httpBackend.flush();
});
function expectGetNetwork() {
$httpBackend.expectGET('dockerapi/networks/f1e1ce1613ccd374a75caf5e2c3ab35520d1944f91498c1974ec86fb4019c79b').respond({
"Name": "bridge",
"Id": "f1e1ce1613ccd374a75caf5e2c3ab35520d1944f91498c1974ec86fb4019c79b",
"Scope": "local",
"Driver": "bridge",
"IPAM": {
"Driver": "default",
"Config": [{
"Subnet": "172.17.0.1/16",
"Gateway": "172.17.0.1"
}]
},
"Containers": {
"727fe76cd0bd65033baab3045508784a166fbc67d177e91c1874b6b29eae946a": {
"EndpointID": "c17ec80e2cfc8eaedc7737b7bb6f954adff439767197ef89c4a5b4127d07b267",
"MacAddress": "02:42:ac:11:00:03",
"IPv4Address": "172.17.0.3/16",
"IPv6Address": ""
},
"8c32c2446c3dfe0defac2dc8b5fd927cd394f15e08051c677a681bf36877175b": {
"EndpointID": "cf7e795c978ab194d1af4a3efdc177d84c075582ba30a7cff414c7d516236af1",
"MacAddress": "02:42:ac:11:00:04",
"IPv4Address": "172.17.0.4/16",
"IPv6Address": ""
},
"cfe81fc97b1f857fdb3061fe487a064b8b57d8f112910954ac16910400d2e058": {
"EndpointID": "611929ffcff2ced1db8e88f77e009c4fb4a4736395251cd97553b242e2e23bf1",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enable_icc": "true",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "docker0",
"com.docker.network.driver.mtu": "1500"
}
});
}
});
@@ -0,0 +1,107 @@
describe('NetworksController', function () {
var $scope, $httpBackend, $routeParams;
beforeEach(module('dockerui'));
beforeEach(inject(function (_$httpBackend_, $controller, _$routeParams_) {
$scope = {};
$httpBackend = _$httpBackend_;
$routeParams = _$routeParams_;
$controller('NetworksController', {
'$scope': $scope,
'$routeParams': $routeParams
});
}));
it('initializes correctly', function () {
expectGetNetwork();
$httpBackend.flush();
});
it('issues correct remove calls to the remote API', function () {
expectGetNetwork();
$httpBackend.flush();
$scope.networks[0].Checked = true;
$scope.networks[2].Checked = true;
$httpBackend.expectDELETE('dockerapi/networks/f2de39df4171b0dc801e8002d1d999b77256983dfc63041c0f34030aa3977566').respond(204);
$httpBackend.expectDELETE('dockerapi/networks/13e871235c677f196c4e1ecebb9dc733b9b2d2ab589e30c539efeda84a24215e').respond(204);
$scope.removeAction();
$httpBackend.flush();
});
it('issues a correct network creation call to the remote API', function () {
expectGetNetwork();
var createBody = {
"Name":"isolated_nw",
"Driver":"bridge",
"IPAM":{
"Config":[{
"Subnet":"172.20.0.0/16",
"IPRange":"172.20.10.0/24",
"Gateway":"172.20.10.11"
}]
}};
$httpBackend.expectPOST('dockerapi/networks/create', createBody).respond(201);
expectGetNetwork();
$scope.addNetwork(createBody);
$httpBackend.flush();
});
function expectGetNetwork() {
$httpBackend.expectGET('dockerapi/networks').respond([
{
"Name": "bridge",
"Id": "f2de39df4171b0dc801e8002d1d999b77256983dfc63041c0f34030aa3977566",
"Scope": "local",
"Driver": "bridge",
"IPAM": {
"Driver": "default",
"Config": [
{
"Subnet": "172.17.0.0/16"
}
]
},
"Containers": {
"39b69226f9d79f5634485fb236a23b2fe4e96a0a94128390a7fbbcc167065867": {
"EndpointID": "ed2419a97c1d9954d05b46e462e7002ea552f216e9b136b80a7db8d98b442eda",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enable_icc": "true",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "docker0",
"com.docker.network.driver.mtu": "1500"
}
},
{
"Name": "none",
"Id": "e086a3893b05ab69242d3c44e49483a3bbbd3a26b46baa8f61ab797c1088d794",
"Scope": "local",
"Driver": "null",
"IPAM": {
"Driver": "default",
"Config": []
},
"Containers": {},
"Options": {}
},
{
"Name": "host",
"Id": "13e871235c677f196c4e1ecebb9dc733b9b2d2ab589e30c539efeda84a24215e",
"Scope": "local",
"Driver": "host",
"IPAM": {
"Driver": "default",
"Config": []
},
"Containers": {},
"Options": {}
}
]);
}
});
@@ -0,0 +1,64 @@
describe('VolumesController', function () {
var $scope, $httpBackend, $routeParams;
beforeEach(module('dockerui'));
beforeEach(inject(function (_$httpBackend_, $controller, _$routeParams_) {
$scope = {};
$httpBackend = _$httpBackend_;
$routeParams = _$routeParams_;
$controller('VolumesController', {
'$scope': $scope,
'$routeParams': $routeParams
});
}));
it('initializes correctly', function () {
expectGetVolumes();
$httpBackend.flush();
});
it('issues correct remove calls to the remote API', function () {
expectGetVolumes();
$httpBackend.flush();
$scope.volumes[0].Checked = true;
$scope.volumes[2].Checked = true;
$httpBackend.expectDELETE('dockerapi/volumes/tardis').respond(200);
$httpBackend.expectDELETE('dockerapi/volumes/bar').respond(200);
$scope.removeAction();
$httpBackend.flush();
});
it('issues a correct volume creation call to the remote API', function () {
expectGetVolumes();
var createBody = {
"Name": "tardis",
"Driver": "local"
};
$httpBackend.expectPOST('dockerapi/volumes/create', createBody).respond(201);
expectGetVolumes();
$scope.addVolume(createBody);
$httpBackend.flush();
});
function expectGetVolumes() {
$httpBackend.expectGET('dockerapi/volumes').respond({
"Volumes": [
{
"Name": "tardis",
"Driver": "local",
"Mountpoint": "/var/lib/docker/volumes/tardis"
},
{
"Name": "foo",
"Driver": "local",
"Mountpoint": "/var/lib/docker/volumes/foo"
},
{
"Name": "bar",
"Driver": "local",
"Mountpoint": "/var/lib/docker/volumes/bar"
}
]
});
}
});