mirror of https://github.com/portainer/portainer
Implement volumes, hide networks and volumes tabs for APIs older than v1.21.
parent
8a900254ae
commit
eede27e263
|
@ -21,7 +21,8 @@ angular.module('dockerui', [
|
||||||
'events',
|
'events',
|
||||||
'stats',
|
'stats',
|
||||||
'network',
|
'network',
|
||||||
'networks'])
|
'networks',
|
||||||
|
'volumes'])
|
||||||
.config(['$routeProvider', function ($routeProvider) {
|
.config(['$routeProvider', function ($routeProvider) {
|
||||||
'use strict';
|
'use strict';
|
||||||
$routeProvider.when('/', {
|
$routeProvider.when('/', {
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
<li><a href="#/containers/">Containers</a></li>
|
<li><a href="#/containers/">Containers</a></li>
|
||||||
<li><a href="#/containers_network/">Containers Network</a></li>
|
<li><a href="#/containers_network/">Containers Network</a></li>
|
||||||
<li><a href="#/images/">Images</a></li>
|
<li><a href="#/images/">Images</a></li>
|
||||||
<li><a href="#/networks/">Networks</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>
|
<li><a href="#/info/">Info</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,11 @@
|
||||||
angular.module('masthead', [])
|
angular.module('masthead', [])
|
||||||
.controller('MastheadController', ['$scope', function ($scope) {
|
.controller('MastheadController', ['$scope', 'Version', function ($scope, Version) {
|
||||||
$scope.template = 'app/components/masthead/masthead.html';
|
$scope.template = 'app/components/masthead/masthead.html';
|
||||||
|
$scope.showNetworksVolumes = false;
|
||||||
|
|
||||||
|
Version.get(function(d) {
|
||||||
|
if (d.ApiVersion >= 1.21) {
|
||||||
|
$scope.showNetworksVolumes = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
}]);
|
}]);
|
||||||
|
|
|
@ -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>
|
|
@ -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();
|
||||||
|
}]);
|
|
@ -117,7 +117,7 @@ angular.module('dockerui.services', ['ngResource'])
|
||||||
get: {method: 'GET'}
|
get: {method: 'GET'}
|
||||||
});
|
});
|
||||||
}])
|
}])
|
||||||
.factory('Network', ['$resource', 'Settings', function NetworksFactory($resource, Settings) {
|
.factory('Network', ['$resource', 'Settings', function NetworkFactory($resource, Settings) {
|
||||||
'use strict';
|
'use strict';
|
||||||
// http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#2-5-networks
|
// http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#2-5-networks
|
||||||
return $resource(Settings.url + '/networks/:id/:action', {id: '@id'}, {
|
return $resource(Settings.url + '/networks/:id/:action', {id: '@id'}, {
|
||||||
|
@ -129,6 +129,16 @@ angular.module('dockerui.services', ['ngResource'])
|
||||||
disconnect: {method: 'POST', params: {action: 'disconnect'}}
|
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) {
|
.factory('Settings', ['DOCKER_ENDPOINT', 'DOCKER_PORT', 'UI_VERSION', function SettingsFactory(DOCKER_ENDPOINT, DOCKER_PORT, UI_VERSION) {
|
||||||
'use strict';
|
'use strict';
|
||||||
var url = DOCKER_ENDPOINT;
|
var url = DOCKER_ENDPOINT;
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
Loading…
Reference in New Issue