diff --git a/app/components/dashboard/dashboardController.js b/app/components/dashboard/dashboardController.js
index 13b2c8617..1eba490a2 100644
--- a/app/components/dashboard/dashboardController.js
+++ b/app/components/dashboard/dashboardController.js
@@ -14,6 +14,7 @@ function ($scope, $q, Config, Container, ContainerHelper, Image, Network, Volume
$scope.volumeData = {
total: 0
};
+ $scope.swarm_mode = false;
function prepareContainerData(d, containersToHideLabels) {
var running = 0;
@@ -63,6 +64,9 @@ function ($scope, $q, Config, Container, ContainerHelper, Image, Network, Volume
function prepareInfoData(d) {
var info = d;
$scope.infoData = info;
+ if ($scope.swarm && info.Swarm) {
+ $scope.swarm_mode = true;
+ }
}
function fetchDashboardData(containersToHideLabels) {
diff --git a/app/components/dashboard/master-ctrl.js b/app/components/dashboard/master-ctrl.js
index 9d5080005..498bbee13 100644
--- a/app/components/dashboard/master-ctrl.js
+++ b/app/components/dashboard/master-ctrl.js
@@ -1,5 +1,6 @@
angular.module('dashboard')
-.controller('MasterCtrl', ['$scope', '$cookieStore', 'Settings', 'Config', function ($scope, $cookieStore, Settings, Config) {
+.controller('MasterCtrl', ['$scope', '$cookieStore', 'Settings', 'Config', 'Info',
+function ($scope, $cookieStore, Settings, Config, Info) {
/**
* Sidebar Toggle & Cookie Control
*/
@@ -9,7 +10,20 @@ angular.module('dashboard')
return window.innerWidth;
};
- $scope.config = Config;
+ $scope.swarm_mode = false;
+
+ Config.$promise.then(function (c) {
+ $scope.swarm = c.swarm;
+ Info.get({}, function(d) {
+ if ($scope.swarm && d.Swarm) {
+ $scope.swarm_mode = true;
+ $scope.swarm_manager = false;
+ if (d.Swarm.ControlAvailable) {
+ $scope.swarm_manager = true;
+ }
+ }
+ });
+ });
$scope.$watch($scope.getWidth, function(newValue, oldValue) {
if (newValue >= mobileView) {
diff --git a/app/components/images/imagesController.js b/app/components/images/imagesController.js
index 2d692c707..1f7e0394c 100644
--- a/app/components/images/imagesController.js
+++ b/app/components/images/imagesController.js
@@ -72,7 +72,7 @@ function ($scope, $state, Config, Image, Messages) {
counter = counter + 1;
Image.remove({id: i.Id}, function (d) {
if (d[0].message) {
- $('#loadingViewSpinner').hide();
+ $('#loadImagesSpinner').hide();
Messages.error("Unable to remove image", {}, d[0].message);
} else {
Messages.send("Image deleted", i.Id);
diff --git a/app/components/networks/networksController.js b/app/components/networks/networksController.js
index 57c4e73d2..84e755c07 100644
--- a/app/components/networks/networksController.js
+++ b/app/components/networks/networksController.js
@@ -15,6 +15,11 @@ function ($scope, $state, Network, Config, Messages) {
var config = angular.copy($scope.config);
if ($scope.swarm) {
config.Driver = 'overlay';
+ // Force IPAM Driver to 'default', should not be required.
+ // See: https://github.com/docker/docker/issues/25735
+ config.IPAM = {
+ Driver: 'default'
+ };
}
return config;
}
diff --git a/app/components/service/service.html b/app/components/service/service.html
new file mode 100644
index 000000000..ad674e4e5
--- /dev/null
+++ b/app/components/service/service.html
@@ -0,0 +1,150 @@
+
+
+
+
+
+
+
+
+ Services > {{ service.Name }}
+
+
+
+
+
+
diff --git a/app/components/service/serviceController.js b/app/components/service/serviceController.js
new file mode 100644
index 000000000..07fecf38a
--- /dev/null
+++ b/app/components/service/serviceController.js
@@ -0,0 +1,97 @@
+angular.module('service', [])
+.controller('ServiceController', ['$scope', '$stateParams', '$state', 'Service', 'ServiceHelper', 'Task', 'Node', 'Messages',
+function ($scope, $stateParams, $state, Service, ServiceHelper, Task, Node, Messages) {
+
+ $scope.service = {};
+ $scope.tasks = [];
+ $scope.displayNode = false;
+ $scope.sortType = 'Status';
+ $scope.sortReverse = false;
+
+ $scope.order = function (sortType) {
+ $scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
+ $scope.sortType = sortType;
+ };
+
+ $scope.renameService = function renameService(service) {
+ $('#loadServicesSpinner').show();
+ var serviceName = service.Name;
+ var config = ServiceHelper.serviceToConfig(service.Model);
+ config.Name = service.newServiceName;
+ Service.update({ id: service.Id, version: service.Version }, config, function (data) {
+ $('#loadServicesSpinner').hide();
+ Messages.send("Service successfully renamed", "New name: " + service.newServiceName);
+ $state.go('service', {id: service.Id}, {reload: true});
+ }, function (e) {
+ $('#loadServicesSpinner').hide();
+ service.EditName = false;
+ service.Name = serviceName;
+ Messages.error("Failure", e, "Unable to rename service");
+ });
+ };
+
+ $scope.scaleService = function scaleService(service) {
+ $('#loadServicesSpinner').show();
+ var config = ServiceHelper.serviceToConfig(service.Model);
+ config.Mode.Replicated.Replicas = service.Replicas;
+ Service.update({ id: service.Id, version: service.Version }, config, function (data) {
+ $('#loadServicesSpinner').hide();
+ Messages.send("Service successfully scaled", "New replica count: " + service.Replicas);
+ $state.go('service', {id: service.Id}, {reload: true});
+ }, function (e) {
+ $('#loadServicesSpinner').hide();
+ service.Scale = false;
+ service.Replicas = service.ReplicaCount;
+ Messages.error("Failure", e, "Unable to scale service");
+ });
+ };
+
+ $scope.removeService = function removeService() {
+ $('#loadingViewSpinner').show();
+ Service.remove({id: $stateParams.id}, function (d) {
+ if (d.message) {
+ $('#loadingViewSpinner').hide();
+ Messages.send("Error", {}, d.message);
+ } else {
+ $('#loadingViewSpinner').hide();
+ Messages.send("Service removed", $stateParams.id);
+ $state.go('services', {});
+ }
+ }, function (e) {
+ $('#loadingViewSpinner').hide();
+ Messages.error("Failure", e, "Unable to remove service");
+ });
+ };
+
+ function fetchServiceDetails() {
+ $('#loadingViewSpinner').show();
+ Service.get({id: $stateParams.id}, function (d) {
+ var service = new ServiceViewModel(d);
+ service.newServiceName = service.Name;
+ $scope.service = service;
+ Task.query({filters: {service: [service.Name]}}, function (tasks) {
+ Node.query({}, function (nodes) {
+ $scope.displayNode = true;
+ $scope.tasks = tasks.map(function (task) {
+ return new TaskViewModel(task, nodes);
+ });
+ $('#loadingViewSpinner').hide();
+ }, function (e) {
+ $('#loadingViewSpinner').hide();
+ $scope.tasks = tasks.map(function (task) {
+ return new TaskViewModel(task, null);
+ });
+ Messages.error("Failure", e, "Unable to retrieve node information");
+ });
+ }, function (e) {
+ $('#loadingViewSpinner').hide();
+ Messages.error("Failure", e, "Unable to retrieve tasks associated to the service");
+ });
+ }, function (e) {
+ $('#loadingViewSpinner').hide();
+ Messages.error("Failure", e, "Unable to retrieve service details");
+ });
+ }
+
+ fetchServiceDetails();
+}]);
diff --git a/app/components/services/services.html b/app/components/services/services.html
new file mode 100644
index 000000000..8cc618a28
--- /dev/null
+++ b/app/components/services/services.html
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+ Services
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/components/services/servicesController.js b/app/components/services/servicesController.js
new file mode 100644
index 000000000..ac7054e01
--- /dev/null
+++ b/app/components/services/servicesController.js
@@ -0,0 +1,84 @@
+angular.module('services', [])
+.controller('ServicesController', ['$scope', '$stateParams', '$state', 'Service', 'ServiceHelper', 'Messages',
+function ($scope, $stateParams, $state, Service, ServiceHelper, Messages) {
+
+ $scope.services = [];
+ $scope.state = {};
+ $scope.state.selectedItemCount = 0;
+ $scope.sortType = 'Name';
+ $scope.sortReverse = false;
+
+ $scope.scaleService = function scaleService(service) {
+ $('#loadServicesSpinner').show();
+ var config = ServiceHelper.serviceToConfig(service.Model);
+ config.Mode.Replicated.Replicas = service.Replicas;
+ Service.update({ id: service.Id, version: service.Version }, config, function (data) {
+ $('#loadServicesSpinner').hide();
+ Messages.send("Service successfully scaled", "New replica count: " + service.Replicas);
+ $state.go('services', {}, {reload: true});
+ }, function (e) {
+ $('#loadServicesSpinner').hide();
+ service.Scale = false;
+ service.Replicas = service.ReplicaCount;
+ Messages.error("Failure", e, "Unable to scale service");
+ });
+ };
+
+ $scope.order = function (sortType) {
+ $scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
+ $scope.sortType = sortType;
+ };
+
+ $scope.selectItem = function (item) {
+ if (item.Checked) {
+ $scope.state.selectedItemCount++;
+ } else {
+ $scope.state.selectedItemCount--;
+ }
+ };
+
+ $scope.removeAction = function () {
+ $('#loadServicesSpinner').show();
+ var counter = 0;
+ var complete = function () {
+ counter = counter - 1;
+ if (counter === 0) {
+ $('#loadServicesSpinner').hide();
+ }
+ };
+ angular.forEach($scope.services, function (service) {
+ if (service.Checked) {
+ counter = counter + 1;
+ Service.remove({id: service.Id}, function (d) {
+ if (d.message) {
+ $('#loadServicesSpinner').hide();
+ Messages.error("Unable to remove service", {}, d[0].message);
+ } else {
+ Messages.send("Service deleted", service.Id);
+ var index = $scope.services.indexOf(service);
+ $scope.services.splice(index, 1);
+ }
+ complete();
+ }, function (e) {
+ Messages.error("Failure", e, 'Unable to remove service');
+ complete();
+ });
+ }
+ });
+ };
+
+ function fetchServices() {
+ $('#loadServicesSpinner').show();
+ Service.query({}, function (d) {
+ $scope.services = d.map(function (service) {
+ return new ServiceViewModel(service);
+ });
+ $('#loadServicesSpinner').hide();
+ }, function(e) {
+ $('#loadServicesSpinner').hide();
+ Messages.error("Failure", e, "Unable to retrieve services");
+ });
+ }
+
+ fetchServices();
+}]);
diff --git a/app/components/swarm/swarm.html b/app/components/swarm/swarm.html
index 2e09be30a..a9b5316e8 100644
--- a/app/components/swarm/swarm.html
+++ b/app/components/swarm/swarm.html
@@ -16,13 +16,14 @@
Nodes |
- {{ swarm.Nodes }} |
+ {{ swarm.Nodes }} |
+ {{ info.Swarm.Nodes }} |
-
+
Images |
{{ info.Images }} |
-
+
Swarm version |
{{ docker.Version|swarmversion }} |
@@ -30,27 +31,29 @@
Docker API version |
{{ docker.ApiVersion }} |
-
+
Strategy |
{{ swarm.Strategy }} |
Total CPU |
- {{ info.NCPU }} |
+ {{ info.NCPU }} |
+ {{ totalCPU }} |
Total memory |
- {{ info.MemTotal|humansize }} |
+ {{ info.MemTotal|humansize }} |
+ {{ totalMemory|humansize }} |
-
+
Operating system |
{{ info.OperatingSystem }} |
-
+
Kernel version |
{{ info.KernelVersion }} |
-
+
Go version |
{{ docker.GoVersion }} |
@@ -60,8 +63,9 @@
+
-
diff --git a/app/components/swarm/swarmController.js b/app/components/swarm/swarmController.js
index eca575753..148155011 100644
--- a/app/components/swarm/swarmController.js
+++ b/app/components/swarm/swarmController.js
@@ -1,62 +1,80 @@
angular.module('swarm', [])
- .controller('SwarmController', ['$scope', 'Info', 'Version', 'Settings',
- function ($scope, Info, Version, Settings) {
+.controller('SwarmController', ['$scope', 'Info', 'Version', 'Node',
+function ($scope, Info, Version, Node) {
- $scope.sortType = 'Name';
- $scope.sortReverse = true;
- $scope.info = {};
- $scope.docker = {};
- $scope.swarm = {};
+ $scope.sortType = 'Name';
+ $scope.sortReverse = true;
+ $scope.info = {};
+ $scope.docker = {};
+ $scope.swarm = {};
+ $scope.swarm_mode = false;
+ $scope.totalCPU = 0;
+ $scope.totalMemory = 0;
- $scope.order = function(sortType) {
- $scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
- $scope.sortType = sortType;
- };
+ $scope.order = function(sortType) {
+ $scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
+ $scope.sortType = sortType;
+ };
- Version.get({}, function (d) {
- $scope.docker = d;
- });
- Info.get({}, function (d) {
- $scope.info = d;
- extractSwarmInfo(d);
+ Version.get({}, function (d) {
+ $scope.docker = d;
+ });
+
+ Info.get({}, function (d) {
+ $scope.info = d;
+ if (d.Swarm) {
+ $scope.swarm_mode = true;
+ Node.query({}, function(d) {
+ $scope.nodes = d;
+ var CPU = 0, memory = 0;
+ angular.forEach(d, function(node) {
+ CPU += node.Description.Resources.NanoCPUs;
+ memory += node.Description.Resources.MemoryBytes;
+ });
+ $scope.totalCPU = CPU / 1000000000;
+ $scope.totalMemory = memory;
});
+ } else {
+ extractSwarmInfo(d);
+ }
+ });
- function extractSwarmInfo(info) {
- // Swarm info is available in SystemStatus object
- var systemStatus = info.SystemStatus;
- // Swarm strategy
- $scope.swarm[systemStatus[1][0]] = systemStatus[1][1];
- // Swarm filters
- $scope.swarm[systemStatus[2][0]] = systemStatus[2][1];
- // Swarm node count
- var node_count = parseInt(systemStatus[3][1], 10);
- $scope.swarm[systemStatus[3][0]] = node_count;
+ function extractSwarmInfo(info) {
+ // Swarm info is available in SystemStatus object
+ var systemStatus = info.SystemStatus;
+ // Swarm strategy
+ $scope.swarm[systemStatus[1][0]] = systemStatus[1][1];
+ // Swarm filters
+ $scope.swarm[systemStatus[2][0]] = systemStatus[2][1];
+ // Swarm node count
+ var node_count = parseInt(systemStatus[3][1], 10);
+ $scope.swarm[systemStatus[3][0]] = node_count;
- $scope.swarm.Status = [];
- extractNodesInfo(systemStatus, node_count);
- }
+ $scope.swarm.Status = [];
+ extractNodesInfo(systemStatus, node_count);
+ }
- function extractNodesInfo(info, node_count) {
- // First information for node1 available at element #4 of SystemStatus
- // The next 10 elements are information related to the node
- var node_offset = 4;
- for (i = 0; i < node_count; i++) {
- extractNodeInfo(info, node_offset);
- node_offset += 9;
- }
- }
+ function extractNodesInfo(info, node_count) {
+ // First information for node1 available at element #4 of SystemStatus
+ // The next 10 elements are information related to the node
+ var node_offset = 4;
+ for (i = 0; i < node_count; i++) {
+ extractNodeInfo(info, node_offset);
+ node_offset += 9;
+ }
+ }
- function extractNodeInfo(info, offset) {
- var node = {};
- node.name = info[offset][0];
- node.ip = info[offset][1];
- node.id = info[offset + 1][1];
- node.status = info[offset + 2][1];
- node.containers = info[offset + 3][1];
- node.cpu = info[offset + 4][1].split('/')[1];
- node.memory = info[offset + 5][1].split('/')[1];
- node.labels = info[offset + 6][1];
- node.version = info[offset + 8][1];
- $scope.swarm.Status.push(node);
- }
- }]);
+ function extractNodeInfo(info, offset) {
+ var node = {};
+ node.name = info[offset][0];
+ node.ip = info[offset][1];
+ node.id = info[offset + 1][1];
+ node.status = info[offset + 2][1];
+ node.containers = info[offset + 3][1];
+ node.cpu = info[offset + 4][1].split('/')[1];
+ node.memory = info[offset + 5][1].split('/')[1];
+ node.labels = info[offset + 6][1];
+ node.version = info[offset + 8][1];
+ $scope.swarm.Status.push(node);
+ }
+}]);
diff --git a/app/components/task/task.html b/app/components/task/task.html
new file mode 100644
index 000000000..1769c8c1b
--- /dev/null
+++ b/app/components/task/task.html
@@ -0,0 +1,50 @@
+
+
+
+
+
+ Services > {{ serviceName }} > {{ task.ID }}
+
+
+
+
+
+
+
+
+
+
+
+ ID |
+ {{ task.ID }} |
+
+
+ State |
+ {{ task.Status.State }} |
+
+
+ Error message |
+ {{ task.Status.Err }} |
+
+
+ Image |
+ {{ task.Spec.ContainerSpec.Image }} |
+
+
+ Slot |
+ {{ task.Slot }} |
+
+
+ Created |
+ {{ task.CreatedAt|getisodate }} |
+
+
+ Container ID |
+ {{ task.Status.ContainerStatus.ContainerID }} |
+
+
+
+
+
+
+
diff --git a/app/components/task/taskController.js b/app/components/task/taskController.js
new file mode 100644
index 000000000..e6a1dc33e
--- /dev/null
+++ b/app/components/task/taskController.js
@@ -0,0 +1,29 @@
+angular.module('task', [])
+.controller('TaskController', ['$scope', '$stateParams', '$state', 'Task', 'Service', 'Messages',
+function ($scope, $stateParams, $state, Task, Service, Messages) {
+
+ $scope.task = {};
+ $scope.serviceName = 'service';
+ $scope.isTaskRunning = false;
+
+ function fetchTaskDetails() {
+ $('#loadingViewSpinner').show();
+ Task.get({id: $stateParams.id}, function (d) {
+ $scope.task = d;
+ fetchAssociatedServiceDetails(d.ServiceID);
+ $('#loadingViewSpinner').hide();
+ }, function (e) {
+ Messages.error("Failure", e, "Unable to retrieve task details");
+ });
+ }
+
+ function fetchAssociatedServiceDetails(serviceId) {
+ Service.get({id: serviceId}, function (d) {
+ $scope.serviceName = d.Spec.Name;
+ }, function (e) {
+ Messages.error("Failure", e, "Unable to retrieve associated service details");
+ });
+ }
+
+ fetchTaskDetails();
+}]);
diff --git a/app/components/templates/templates.html b/app/components/templates/templates.html
index fe4b06be9..40a42bedc 100644
--- a/app/components/templates/templates.html
+++ b/app/components/templates/templates.html
@@ -34,11 +34,17 @@