Merge pull request #162 from kevana/networks-volumes

Networks and Volumes
pull/2/head
Kevan Ahlquist 2016-01-10 20:39:14 -06:00
commit 22d35eb32f
20 changed files with 830 additions and 63 deletions

View File

@ -1,4 +1,28 @@
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'])
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', function ($routeProvider) {
'use strict';
$routeProvider.when('/', {
@ -48,5 +72,4 @@ angular.module('dockerui', ['dockerui.templates', 'ngRoute', 'dockerui.services'
// 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.9.0-beta')
.constant('DOCKER_API_VERSION', 'v1.20');
.constant('UI_VERSION', 'v0.9.0-beta');

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';
}]);

View File

@ -21,6 +21,7 @@ angular.module('dashboard', [])
if (Settings.firstLoad) {
opts.animation = true;
Settings.firstLoad = false;
localStorage.setItem('firstLoad', false);
$('#masthead').show();
setTimeout(function () {

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;
});
}]);

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;
});
}]);

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><a href="#/networks/" ng-if="showNetworksVolumes">Networks</a></li>
<li><a href="#/volumes/" ng-if="showNetworksVolumes">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>

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();
}
}]);

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>

View File

@ -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();
});
}]);

View File

@ -0,0 +1,80 @@
<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>
<!--<li><a data-toggle="modal" data-target="#pull-modal" href="">Pull</a></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><input type="checkbox" ng-model="toggle" ng-change="toggleSelectAll()"/> Select</th>
<th>Name</th>
<th>Id</th>
<th>Scope</th>
<th>Driver</th>
<th>IPAM Driver</th>
<th>IPAM Subnet</th>
<th>IPAM Gateway</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="network in networks | filter:filter | orderBy:predicate">
<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>

View File

@ -0,0 +1,82 @@
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.toggle = false;
$scope.predicate = '-Created';
$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();
}]);

View File

@ -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 () {

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 = [];

View File

@ -0,0 +1,56 @@
<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><input type="checkbox" ng-model="toggle" ng-change="toggleSelectAll()"/> Select</th>
<th>Name</th>
<th>Driver</th>
<th>Mountpoint</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="volume in volumes | filter:filter | orderBy:predicate">
<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>

View File

@ -0,0 +1,75 @@
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.toggle = false;
$scope.predicate = '-Created';
$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();
}]);

View File

@ -95,7 +95,7 @@ angular.module('dockerui.services', ['ngResource'])
remove: {method: 'DELETE', params: {id: '@id'}, isArray: true}
});
}])
.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 +110,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 +197,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,7 +226,7 @@ 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);

View File

@ -14,8 +14,27 @@ 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:all',
'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('run-dev', ['if:binaryNotExist', 'shell:buildImage', 'shell:run', 'watch:build']);

View File

@ -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"
}
});
}
});

View File

@ -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": {}
}
]);
}
});

View File

@ -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"
}
]
});
}
});