diff --git a/README.md b/README.md
index a2a16ee24..11bf86ad8 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,6 @@
--external-endpoints
flag. Endpoint management via the UI is disabled.
+
+ + | Name @@ -147,16 +159,16 @@ | -+ | ||||
---|---|---|---|---|---|---|
+ | {{ endpoint.Name }} | {{ endpoint.URL | stripprotocol }} | - | + |
Edit
diff --git a/app/components/sidebar/sidebar.html b/app/components/sidebar/sidebar.html
index d8e97c81b..1d29b8f8d 100644
--- a/app/components/sidebar/sidebar.html
+++ b/app/components/sidebar/sidebar.html
@@ -47,7 +47,7 @@
Docker
{{title}} {{username}} ',
+ template: '{{title}} {{username}} ',
restrict: 'E'
};
return directive;
diff --git a/app/shared/filters.js b/app/filters/filters.js
similarity index 100%
rename from app/shared/filters.js
rename to app/filters/filters.js
diff --git a/app/helpers/containerHelper.js b/app/helpers/containerHelper.js
new file mode 100644
index 000000000..101d9a108
--- /dev/null
+++ b/app/helpers/containerHelper.js
@@ -0,0 +1,20 @@
+angular.module('portainer.helpers')
+.factory('ContainerHelper', [function ContainerHelperFactory() {
+ 'use strict';
+ return {
+ hideContainers: function(containers, containersToHideLabels) {
+ return containers.filter(function (container) {
+ var filterContainer = false;
+ containersToHideLabels.forEach(function(label, index) {
+ if (_.has(container.Labels, label.name) &&
+ container.Labels[label.name] === label.value) {
+ filterContainer = true;
+ }
+ });
+ if (!filterContainer) {
+ return container;
+ }
+ });
+ }
+ };
+}]);
diff --git a/app/helpers/imageHelper.js b/app/helpers/imageHelper.js
new file mode 100644
index 000000000..864f248bc
--- /dev/null
+++ b/app/helpers/imageHelper.js
@@ -0,0 +1,30 @@
+angular.module('portainer.helpers')
+.factory('ImageHelper', [function ImageHelperFactory() {
+ 'use strict';
+ return {
+ createImageConfigForCommit: function(imageName, registry) {
+ var imageNameAndTag = imageName.split(':');
+ var image = imageNameAndTag[0];
+ if (registry) {
+ image = registry + '/' + imageNameAndTag[0];
+ }
+ var imageConfig = {
+ repo: image,
+ tag: imageNameAndTag[1] ? imageNameAndTag[1] : 'latest'
+ };
+ return imageConfig;
+ },
+ createImageConfigForContainer: function (imageName, registry) {
+ var imageNameAndTag = imageName.split(':');
+ var image = imageNameAndTag[0];
+ if (registry) {
+ image = registry + '/' + imageNameAndTag[0];
+ }
+ var imageConfig = {
+ fromImage: image,
+ tag: imageNameAndTag[1] ? imageNameAndTag[1] : 'latest'
+ };
+ return imageConfig;
+ }
+ };
+}]);
diff --git a/app/helpers/infoHelper.js b/app/helpers/infoHelper.js
new file mode 100644
index 000000000..5f2ad14cd
--- /dev/null
+++ b/app/helpers/infoHelper.js
@@ -0,0 +1,32 @@
+angular.module('portainer.helpers')
+.factory('InfoHelper', [function InfoHelperFactory() {
+ 'use strict';
+ return {
+ determineEndpointMode: function(info) {
+ var mode = {
+ provider: '',
+ role: ''
+ };
+ if (_.startsWith(info.ServerVersion, 'swarm')) {
+ mode.provider = "DOCKER_SWARM";
+ if (info.SystemStatus[0][1] === 'primary') {
+ mode.role = "PRIMARY";
+ } else {
+ mode.role = "REPLICA";
+ }
+ } else {
+ if (!info.Swarm || _.isEmpty(info.Swarm.NodeID)) {
+ mode.provider = "DOCKER_STANDALONE";
+ } else {
+ mode.provider = "DOCKER_SWARM_MODE";
+ if (info.Swarm.ControlAvailable) {
+ mode.role = "MANAGER";
+ } else {
+ mode.role = "WORKER";
+ }
+ }
+ }
+ return mode;
+ }
+ };
+}]);
diff --git a/app/helpers/labelHelper.js b/app/helpers/labelHelper.js
new file mode 100644
index 000000000..8963d0d1f
--- /dev/null
+++ b/app/helpers/labelHelper.js
@@ -0,0 +1,23 @@
+angular.module('portainer.helpers')
+.factory('LabelHelper', [function LabelHelperFactory() {
+ 'use strict';
+ return {
+ fromLabelHashToKeyValue: function(labels) {
+ if (labels) {
+ return Object.keys(labels).map(function(key) {
+ return {key: key, value: labels[key], originalKey: key, originalValue: labels[key], added: true};
+ });
+ }
+ return [];
+ },
+ fromKeyValueToLabelHash: function(labelKV) {
+ var labels = {};
+ if (labelKV) {
+ labelKV.forEach(function(label) {
+ labels[label.key] = label.value;
+ });
+ }
+ return labels;
+ }
+ };
+}]);
diff --git a/app/helpers/nodeHelper.js b/app/helpers/nodeHelper.js
new file mode 100644
index 000000000..835a3055f
--- /dev/null
+++ b/app/helpers/nodeHelper.js
@@ -0,0 +1,14 @@
+angular.module('portainer.helpers')
+.factory('NodeHelper', [function NodeHelperFactory() {
+ 'use strict';
+ return {
+ nodeToConfig: function(node) {
+ return {
+ Name: node.Spec.Name,
+ Role: node.Spec.Role,
+ Labels: node.Spec.Labels,
+ Availability: node.Spec.Availability
+ };
+ }
+ };
+}]);
diff --git a/app/helpers/serviceHelper.js b/app/helpers/serviceHelper.js
new file mode 100644
index 000000000..c5e4a2e58
--- /dev/null
+++ b/app/helpers/serviceHelper.js
@@ -0,0 +1,17 @@
+angular.module('portainer.helpers')
+.factory('ServiceHelper', [function ServiceHelperFactory() {
+ 'use strict';
+ return {
+ serviceToConfig: function(service) {
+ return {
+ Name: service.Spec.Name,
+ Labels: service.Spec.Labels,
+ TaskTemplate: service.Spec.TaskTemplate,
+ Mode: service.Spec.Mode,
+ UpdateConfig: service.Spec.UpdateConfig,
+ Networks: service.Spec.Networks,
+ EndpointSpec: service.Spec.EndpointSpec
+ };
+ }
+ };
+}]);
diff --git a/app/helpers/templateHelper.js b/app/helpers/templateHelper.js
new file mode 100644
index 000000000..c52ed25c7
--- /dev/null
+++ b/app/helpers/templateHelper.js
@@ -0,0 +1,40 @@
+angular.module('portainer.helpers')
+.factory('TemplateHelper', [function TemplateHelperFactory() {
+ 'use strict';
+ return {
+ getPortBindings: function(ports) {
+ var bindings = [];
+ ports.forEach(function (port) {
+ var portAndProtocol = _.split(port, '/');
+ var binding = {
+ containerPort: portAndProtocol[0],
+ protocol: portAndProtocol[1]
+ };
+ bindings.push(binding);
+ });
+ return bindings;
+ },
+ //Not used atm, may prove useful later
+ getVolumeBindings: function(volumes) {
+ var bindings = [];
+ volumes.forEach(function (volume) {
+ bindings.push({ containerPath: volume });
+ });
+ return bindings;
+ },
+ //Not used atm, may prove useful later
+ getEnvBindings: function(env) {
+ var bindings = [];
+ env.forEach(function (envvar) {
+ var binding = {
+ name: envvar.name
+ };
+ if (envvar.set) {
+ binding.value = envvar.set;
+ }
+ bindings.push(binding);
+ });
+ return bindings;
+ }
+ };
+}]);
diff --git a/app/models/container.js b/app/models/container.js
new file mode 100644
index 000000000..2df1912a6
--- /dev/null
+++ b/app/models/container.js
@@ -0,0 +1,20 @@
+function ContainerViewModel(data) {
+ this.Id = data.Id;
+ this.Status = data.Status;
+ this.State = data.State;
+ this.Names = data.Names;
+ // Unavailable in Docker < 1.10
+ if (data.NetworkSettings && !_.isEmpty(data.NetworkSettings.Networks)) {
+ this.IP = data.NetworkSettings.Networks[Object.keys(data.NetworkSettings.Networks)[0]].IPAddress;
+ }
+ this.Image = data.Image;
+ this.Command = data.Command;
+ this.Checked = false;
+ this.Ports = [];
+ for (var i = 0; i < data.Ports.length; ++i) {
+ var p = data.Ports[i];
+ if (p.PublicPort) {
+ this.Ports.push({ host: p.IP, private: p.PrivatePort, public: p.PublicPort });
+ }
+ }
+}
diff --git a/app/models/event.js b/app/models/event.js
new file mode 100644
index 000000000..6ce3647ca
--- /dev/null
+++ b/app/models/event.js
@@ -0,0 +1,120 @@
+function createEventDetails(event) {
+ var eventAttr = event.Actor.Attributes;
+ var details = '';
+ switch (event.Type) {
+ case 'container':
+ switch (event.Action) {
+ case 'stop':
+ details = 'Container ' + eventAttr.name + ' stopped';
+ break;
+ case 'destroy':
+ details = 'Container ' + eventAttr.name + ' deleted';
+ break;
+ case 'create':
+ details = 'Container ' + eventAttr.name + ' created';
+ break;
+ case 'start':
+ details = 'Container ' + eventAttr.name + ' started';
+ break;
+ case 'kill':
+ details = 'Container ' + eventAttr.name + ' killed';
+ break;
+ case 'die':
+ details = 'Container ' + eventAttr.name + ' exited with status code ' + eventAttr.exitCode;
+ break;
+ case 'commit':
+ details = 'Container ' + eventAttr.name + ' committed';
+ break;
+ case 'restart':
+ details = 'Container ' + eventAttr.name + ' restarted';
+ break;
+ case 'pause':
+ details = 'Container ' + eventAttr.name + ' paused';
+ break;
+ case 'unpause':
+ details = 'Container ' + eventAttr.name + ' unpaused';
+ break;
+ case 'attach':
+ details = 'Container ' + eventAttr.name + ' attached';
+ break;
+ default:
+ if (event.Action.indexOf('exec_create') === 0) {
+ details = 'Exec instance created';
+ } else if (event.Action.indexOf('exec_start') === 0) {
+ details = 'Exec instance started';
+ } else {
+ details = 'Unsupported event';
+ }
+ }
+ break;
+ case 'image':
+ switch (event.Action) {
+ case 'delete':
+ details = 'Image deleted';
+ break;
+ case 'tag':
+ details = 'New tag created for ' + eventAttr.name;
+ break;
+ case 'untag':
+ details = 'Image untagged';
+ break;
+ case 'pull':
+ details = 'Image ' + event.Actor.ID + ' pulled';
+ break;
+ default:
+ details = 'Unsupported event';
+ }
+ break;
+ case 'network':
+ switch (event.Action) {
+ case 'create':
+ details = 'Network ' + eventAttr.name + ' created';
+ break;
+ case 'destroy':
+ details = 'Network ' + eventAttr.name + ' deleted';
+ break;
+ case 'connect':
+ details = 'Container connected to ' + eventAttr.name + ' network';
+ break;
+ case 'disconnect':
+ details = 'Container disconnected from ' + eventAttr.name + ' network';
+ break;
+ default:
+ details = 'Unsupported event';
+ }
+ break;
+ case 'volume':
+ switch (event.Action) {
+ case 'create':
+ details = 'Volume ' + event.Actor.ID + ' created';
+ break;
+ case 'destroy':
+ details = 'Volume ' + event.Actor.ID + ' deleted';
+ break;
+ case 'mount':
+ details = 'Volume ' + event.Actor.ID + ' mounted';
+ break;
+ case 'unmount':
+ details = 'Volume ' + event.Actor.ID + ' unmounted';
+ break;
+ default:
+ details = 'Unsupported event';
+ }
+ break;
+ default:
+ details = 'Unsupported event';
+ }
+ return details;
+}
+
+function EventViewModel(data) {
+ // Type, Action, Actor unavailable in Docker < 1.10
+ this.Time = data.time;
+ if (data.Type) {
+ this.Type = data.Type;
+ this.Details = createEventDetails(data);
+ } else {
+ this.Type = data.status;
+ this.Details = data.from;
+ }
+}
diff --git a/app/models/image.js b/app/models/image.js
new file mode 100644
index 000000000..d050cfe9f
--- /dev/null
+++ b/app/models/image.js
@@ -0,0 +1,9 @@
+function ImageViewModel(data) {
+ this.Id = data.Id;
+ this.Tag = data.Tag;
+ this.Repository = data.Repository;
+ this.Created = data.Created;
+ this.Checked = false;
+ this.RepoTags = data.RepoTags;
+ this.VirtualSize = data.VirtualSize;
+}
diff --git a/app/models/node.js b/app/models/node.js
new file mode 100644
index 000000000..49aab6a13
--- /dev/null
+++ b/app/models/node.js
@@ -0,0 +1,35 @@
+function NodeViewModel(data) {
+ this.Model = data;
+ this.Id = data.ID;
+ this.Version = data.Version.Index;
+ this.Name = data.Spec.Name;
+ this.Role = data.Spec.Role;
+ this.CreatedAt = data.CreatedAt;
+ this.UpdatedAt = data.UpdatedAt;
+ this.Availability = data.Spec.Availability;
+
+ var labels = data.Spec.Labels;
+ if (labels) {
+ this.Labels = Object.keys(labels).map(function(key) {
+ return { key: key, value: labels[key], originalKey: key, originalValue: labels[key], added: true };
+ });
+ } else {
+ this.Labels = [];
+ }
+
+ this.Hostname = data.Description.Hostname;
+ this.PlatformArchitecture = data.Description.Platform.Architecture;
+ this.PlatformOS = data.Description.Platform.OS;
+ this.CPUs = data.Description.Resources.NanoCPUs;
+ this.Memory = data.Description.Resources.MemoryBytes;
+ this.EngineVersion = data.Description.Engine.EngineVersion;
+ this.EngineLabels = data.Description.Engine.Labels;
+ this.Plugins = data.Description.Engine.Plugins;
+ this.Status = data.Status.State;
+
+ if (data.ManagerStatus) {
+ this.Leader = data.ManagerStatus.Leader;
+ this.Reachability = data.ManagerStatus.Reachability;
+ this.ManagerAddr = data.ManagerStatus.Addr;
+ }
+}
diff --git a/app/models/service.js b/app/models/service.js
new file mode 100644
index 000000000..ab182761d
--- /dev/null
+++ b/app/models/service.js
@@ -0,0 +1,36 @@
+function ServiceViewModel(data) {
+ this.Model = data;
+ this.Id = data.ID;
+ this.Name = data.Spec.Name;
+ this.Image = data.Spec.TaskTemplate.ContainerSpec.Image;
+ this.Version = data.Version.Index;
+ if (data.Spec.Mode.Replicated) {
+ this.Mode = 'replicated' ;
+ this.Replicas = data.Spec.Mode.Replicated.Replicas;
+ } else {
+ this.Mode = 'global';
+ }
+ this.Labels = data.Spec.Labels;
+ if (data.Spec.TaskTemplate.ContainerSpec) {
+ this.ContainerLabels = data.Spec.TaskTemplate.ContainerSpec.Labels;
+ }
+ if (data.Spec.TaskTemplate.ContainerSpec.Env) {
+ this.Env = data.Spec.TaskTemplate.ContainerSpec.Env;
+ }
+ if (data.Endpoint.Ports) {
+ this.Ports = data.Endpoint.Ports;
+ }
+ if (data.Spec.UpdateConfig) {
+ this.UpdateParallelism = (typeof data.Spec.UpdateConfig.Parallelism !== undefined) ? data.Spec.UpdateConfig.Parallelism || 0 : 1;
+ this.UpdateDelay = data.Spec.UpdateConfig.Delay || 0;
+ this.UpdateFailureAction = data.Spec.UpdateConfig.FailureAction || 'pause';
+ } else {
+ this.UpdateParallelism = 1;
+ this.UpdateDelay = 0;
+ this.UpdateFailureAction = 'pause';
+ }
+
+ this.Checked = false;
+ this.Scale = false;
+ this.EditName = false;
+}
diff --git a/app/models/task.js b/app/models/task.js
new file mode 100644
index 000000000..d1b82e8a6
--- /dev/null
+++ b/app/models/task.js
@@ -0,0 +1,15 @@
+function TaskViewModel(data, node_data) {
+ this.Id = data.ID;
+ this.Created = data.CreatedAt;
+ this.Updated = data.UpdatedAt;
+ this.Slot = data.Slot;
+ this.Status = data.Status.State;
+ this.Image = data.Spec.ContainerSpec ? data.Spec.ContainerSpec.Image : '';
+ if (node_data) {
+ for (var i = 0; i < node_data.length; ++i) {
+ if (data.NodeID === node_data[i].ID) {
+ this.Node = node_data[i].Description.Hostname;
+ }
+ }
+ }
+}
diff --git a/app/rest/auth.js b/app/rest/auth.js
new file mode 100644
index 000000000..c7ed49447
--- /dev/null
+++ b/app/rest/auth.js
@@ -0,0 +1,9 @@
+angular.module('portainer.rest')
+.factory('Auth', ['$resource', 'AUTH_ENDPOINT', function AuthFactory($resource, AUTH_ENDPOINT) {
+ 'use strict';
+ return $resource(AUTH_ENDPOINT, {}, {
+ login: {
+ method: 'POST'
+ }
+ });
+}]);
diff --git a/app/rest/config.js b/app/rest/config.js
new file mode 100644
index 000000000..2e8f14f9f
--- /dev/null
+++ b/app/rest/config.js
@@ -0,0 +1,4 @@
+angular.module('portainer.rest')
+.factory('Config', ['$resource', 'CONFIG_ENDPOINT', function ConfigFactory($resource, CONFIG_ENDPOINT) {
+ return $resource(CONFIG_ENDPOINT).get();
+}]);
diff --git a/app/rest/container.js b/app/rest/container.js
new file mode 100644
index 000000000..a3e4d3874
--- /dev/null
+++ b/app/rest/container.js
@@ -0,0 +1,37 @@
+angular.module('portainer.rest')
+.factory('Container', ['$resource', 'Settings', function ContainerFactory($resource, Settings) {
+ 'use strict';
+ return $resource(Settings.url + '/containers/:id/:action', {
+ name: '@name'
+ }, {
+ query: {method: 'GET', params: {all: 0, action: 'json', filters: '@filters' }, isArray: true},
+ get: {method: 'GET', params: {action: 'json'}},
+ stop: {method: 'POST', params: {id: '@id', t: 5, action: 'stop'}},
+ restart: {method: 'POST', params: {id: '@id', t: 5, action: 'restart'}},
+ kill: {method: 'POST', params: {id: '@id', action: 'kill'}},
+ pause: {method: 'POST', params: {id: '@id', action: 'pause'}},
+ unpause: {method: 'POST', params: {id: '@id', action: 'unpause'}},
+ changes: {method: 'GET', params: {action: 'changes'}, isArray: true},
+ stats: {method: 'GET', params: {id: '@id', stream: false, action: 'stats'}, timeout: 5000},
+ start: {
+ method: 'POST', params: {id: '@id', action: 'start'},
+ transformResponse: genericHandler
+ },
+ create: {
+ method: 'POST', params: {action: 'create'},
+ transformResponse: genericHandler
+ },
+ remove: {
+ method: 'DELETE', params: {id: '@id', v: 0},
+ transformResponse: genericHandler
+ },
+ rename: {
+ method: 'POST', params: {id: '@id', action: 'rename', name: '@name'},
+ transformResponse: genericHandler
+ },
+ exec: {
+ method: 'POST', params: {id: '@id', action: 'exec'},
+ transformResponse: genericHandler
+ }
+ });
+}]);
diff --git a/app/rest/containerCommit.js b/app/rest/containerCommit.js
new file mode 100644
index 000000000..3fdb0c83d
--- /dev/null
+++ b/app/rest/containerCommit.js
@@ -0,0 +1,7 @@
+angular.module('portainer.rest')
+.factory('ContainerCommit', ['$resource', 'Settings', function ContainerCommitFactory($resource, Settings) {
+ 'use strict';
+ return $resource(Settings.url + '/commit', {}, {
+ commit: {method: 'POST', params: {container: '@id', repo: '@repo', tag: '@tag'}}
+ });
+}]);
diff --git a/app/rest/containerLogs.js b/app/rest/containerLogs.js
new file mode 100644
index 000000000..a9c07f583
--- /dev/null
+++ b/app/rest/containerLogs.js
@@ -0,0 +1,20 @@
+angular.module('portainer.rest')
+.factory('ContainerLogs', ['$http', 'Settings', function ContainerLogsFactory($http, Settings) {
+ 'use strict';
+ return {
+ get: function (id, params, callback) {
+ $http({
+ method: 'GET',
+ url: Settings.url + '/containers/' + id + '/logs',
+ params: {
+ 'stdout': params.stdout || 0,
+ 'stderr': params.stderr || 0,
+ 'timestamps': params.timestamps || 0,
+ 'tail': params.tail || 'all'
+ }
+ }).success(callback).error(function (data, status, headers, config) {
+ console.log(error, data);
+ });
+ }
+ };
+}]);
diff --git a/app/rest/containerTop.js b/app/rest/containerTop.js
new file mode 100644
index 000000000..7515452a8
--- /dev/null
+++ b/app/rest/containerTop.js
@@ -0,0 +1,15 @@
+angular.module('portainer.rest')
+.factory('ContainerTop', ['$http', 'Settings', function ($http, Settings) {
+ 'use strict';
+ return {
+ get: function (id, params, callback, errorCallback) {
+ $http({
+ method: 'GET',
+ url: Settings.url + '/containers/' + id + '/top',
+ params: {
+ ps_args: params.ps_args
+ }
+ }).success(callback);
+ }
+ };
+}]);
diff --git a/app/rest/endpoint.js b/app/rest/endpoint.js
new file mode 100644
index 000000000..d5eb432a3
--- /dev/null
+++ b/app/rest/endpoint.js
@@ -0,0 +1,13 @@
+angular.module('portainer.rest')
+.factory('Endpoints', ['$resource', 'ENDPOINTS_ENDPOINT', function EndpointsFactory($resource, ENDPOINTS_ENDPOINT) {
+ 'use strict';
+ return $resource(ENDPOINTS_ENDPOINT + '/:id/:action', {}, {
+ create: { method: 'POST' },
+ query: { method: 'GET', isArray: true },
+ get: { method: 'GET', params: { id: '@id' } },
+ update: { method: 'PUT', params: { id: '@id' } },
+ remove: { method: 'DELETE', params: { id: '@id'} },
+ getActiveEndpoint: { method: 'GET', params: { id: '0' } },
+ setActiveEndpoint: { method: 'POST', params: { id: '@id', action: 'active' } }
+ });
+}]);
diff --git a/app/rest/event.js b/app/rest/event.js
new file mode 100644
index 000000000..9dbb80e3b
--- /dev/null
+++ b/app/rest/event.js
@@ -0,0 +1,10 @@
+angular.module('portainer.rest')
+.factory('Events', ['$resource', 'Settings', function EventFactory($resource, Settings) {
+ 'use strict';
+ return $resource(Settings.url + '/events', {}, {
+ query: {
+ method: 'GET', params: {since: '@since', until: '@until'},
+ isArray: true, transformResponse: jsonObjectsToArrayHandler
+ }
+ });
+}]);
diff --git a/app/rest/exec.js b/app/rest/exec.js
new file mode 100644
index 000000000..25ed67b47
--- /dev/null
+++ b/app/rest/exec.js
@@ -0,0 +1,10 @@
+angular.module('portainer.rest')
+.factory('Exec', ['$resource', 'Settings', function ExecFactory($resource, Settings) {
+ 'use strict';
+ return $resource(Settings.url + '/exec/:id/:action', {}, {
+ resize: {
+ method: 'POST', params: {id: '@id', action: 'resize', h: '@height', w: '@width'},
+ transformResponse: genericHandler
+ }
+ });
+}]);
diff --git a/app/rest/image.js b/app/rest/image.js
new file mode 100644
index 000000000..9361136fc
--- /dev/null
+++ b/app/rest/image.js
@@ -0,0 +1,25 @@
+angular.module('portainer.rest')
+.factory('Image', ['$resource', 'Settings', function ImageFactory($resource, Settings) {
+ 'use strict';
+ return $resource(Settings.url + '/images/:id/:action', {}, {
+ query: {method: 'GET', params: {all: 0, action: 'json'}, isArray: true},
+ get: {method: 'GET', params: {action: 'json'}},
+ search: {method: 'GET', params: {action: 'search'}},
+ history: {method: 'GET', params: {action: 'history'}, isArray: true},
+ insert: {method: 'POST', params: {id: '@id', action: 'insert'}},
+ tag: {method: 'POST', params: {id: '@id', action: 'tag', force: 0, repo: '@repo', tag: '@tag'}},
+ inspect: {method: 'GET', params: {id: '@id', action: 'json'}},
+ push: {
+ method: 'POST', params: {action: 'push', id: '@tag'},
+ isArray: true, transformResponse: jsonObjectsToArrayHandler
+ },
+ create: {
+ method: 'POST', params: {action: 'create', fromImage: '@fromImage', tag: '@tag'},
+ isArray: true, transformResponse: jsonObjectsToArrayHandler
+ },
+ remove: {
+ method: 'DELETE', params: {id: '@id'},
+ isArray: true, transformResponse: deleteImageHandler
+ }
+ });
+}]);
diff --git a/app/rest/info.js b/app/rest/info.js
new file mode 100644
index 000000000..d138a341a
--- /dev/null
+++ b/app/rest/info.js
@@ -0,0 +1,5 @@
+angular.module('portainer.rest')
+.factory('Info', ['$resource', 'Settings', function InfoFactory($resource, Settings) {
+ 'use strict';
+ return $resource(Settings.url + '/info', {});
+}]);
diff --git a/app/rest/network.js b/app/rest/network.js
new file mode 100644
index 000000000..793463d12
--- /dev/null
+++ b/app/rest/network.js
@@ -0,0 +1,12 @@
+angular.module('portainer.rest')
+.factory('Network', ['$resource', 'Settings', function NetworkFactory($resource, Settings) {
+ 'use strict';
+ return $resource(Settings.url + '/networks/:id/:action', {id: '@id'}, {
+ query: {method: 'GET', isArray: true},
+ get: {method: 'GET'},
+ create: {method: 'POST', params: {action: 'create'}, transformResponse: genericHandler},
+ remove: { method: 'DELETE', transformResponse: genericHandler },
+ connect: {method: 'POST', params: {action: 'connect'}},
+ disconnect: {method: 'POST', params: {action: 'disconnect'}}
+ });
+}]);
diff --git a/app/rest/node.js b/app/rest/node.js
new file mode 100644
index 000000000..e1896059a
--- /dev/null
+++ b/app/rest/node.js
@@ -0,0 +1,10 @@
+angular.module('portainer.rest')
+.factory('Node', ['$resource', 'Settings', function NodeFactory($resource, Settings) {
+ 'use strict';
+ return $resource(Settings.url + '/nodes/:id/:action', {}, {
+ query: {method: 'GET', isArray: true},
+ get: {method: 'GET', params: {id: '@id'}},
+ update: { method: 'POST', params: {id: '@id', action: 'update', version: '@version'} },
+ remove: { method: 'DELETE', params: {id: '@id'} }
+ });
+}]);
diff --git a/app/shared/responseHandlers.js b/app/rest/response/handlers.js
similarity index 100%
rename from app/shared/responseHandlers.js
rename to app/rest/response/handlers.js
diff --git a/app/rest/service.js b/app/rest/service.js
new file mode 100644
index 000000000..904998438
--- /dev/null
+++ b/app/rest/service.js
@@ -0,0 +1,11 @@
+angular.module('portainer.rest')
+.factory('Service', ['$resource', 'Settings', function ServiceFactory($resource, Settings) {
+ 'use strict';
+ return $resource(Settings.url + '/services/:id/:action', {}, {
+ get: { method: 'GET', params: {id: '@id'} },
+ query: { method: 'GET', isArray: true },
+ create: { method: 'POST', params: {action: 'create'} },
+ update: { method: 'POST', params: {id: '@id', action: 'update', version: '@version'} },
+ remove: { method: 'DELETE', params: {id: '@id'} }
+ });
+}]);
diff --git a/app/rest/swarm.js b/app/rest/swarm.js
new file mode 100644
index 000000000..e0cc40e6b
--- /dev/null
+++ b/app/rest/swarm.js
@@ -0,0 +1,7 @@
+angular.module('portainer.rest')
+.factory('Swarm', ['$resource', 'Settings', function SwarmFactory($resource, Settings) {
+ 'use strict';
+ return $resource(Settings.url + '/swarm', {}, {
+ get: {method: 'GET'}
+ });
+}]);
diff --git a/app/rest/task.js b/app/rest/task.js
new file mode 100644
index 000000000..91a8c2a93
--- /dev/null
+++ b/app/rest/task.js
@@ -0,0 +1,8 @@
+angular.module('portainer.rest')
+.factory('Task', ['$resource', 'Settings', function TaskFactory($resource, Settings) {
+ 'use strict';
+ return $resource(Settings.url + '/tasks/:id', {}, {
+ get: { method: 'GET', params: {id: '@id'} },
+ query: { method: 'GET', isArray: true, params: {filters: '@filters'} }
+ });
+}]);
diff --git a/app/rest/templates.js b/app/rest/templates.js
new file mode 100644
index 000000000..412e5a9f5
--- /dev/null
+++ b/app/rest/templates.js
@@ -0,0 +1,6 @@
+angular.module('portainer.rest')
+.factory('Templates', ['$resource', 'TEMPLATES_ENDPOINT', function TemplatesFactory($resource, TEMPLATES_ENDPOINT) {
+ return $resource(TEMPLATES_ENDPOINT, {}, {
+ get: {method: 'GET', isArray: true}
+ });
+}]);
diff --git a/app/rest/user.js b/app/rest/user.js
new file mode 100644
index 000000000..a67d040a9
--- /dev/null
+++ b/app/rest/user.js
@@ -0,0 +1,12 @@
+angular.module('portainer.rest')
+.factory('Users', ['$resource', 'USERS_ENDPOINT', function UsersFactory($resource, USERS_ENDPOINT) {
+ 'use strict';
+ return $resource(USERS_ENDPOINT + '/:username/:action', {}, {
+ create: { method: 'POST' },
+ get: { method: 'GET', params: { username: '@username' } },
+ update: { method: 'PUT', params: { username: '@username' } },
+ checkPassword: { method: 'POST', params: { username: '@username', action: 'passwd' } },
+ checkAdminUser: { method: 'GET', params: { username: 'admin', action: 'check' } },
+ initAdminUser: { method: 'POST', params: { username: 'admin', action: 'init' } }
+ });
+}]);
diff --git a/app/rest/version.js b/app/rest/version.js
new file mode 100644
index 000000000..29ff766f0
--- /dev/null
+++ b/app/rest/version.js
@@ -0,0 +1,5 @@
+angular.module('portainer.rest')
+.factory('Version', ['$resource', 'Settings', function VersionFactory($resource, Settings) {
+ 'use strict';
+ return $resource(Settings.url + '/version', {});
+}]);
diff --git a/app/rest/volume.js b/app/rest/volume.js
new file mode 100644
index 000000000..dd3f94ad0
--- /dev/null
+++ b/app/rest/volume.js
@@ -0,0 +1,12 @@
+angular.module('portainer.rest')
+.factory('Volume', ['$resource', 'Settings', function VolumeFactory($resource, Settings) {
+ 'use strict';
+ return $resource(Settings.url + '/volumes/:name/:action', {name: '@name'}, {
+ query: {method: 'GET'},
+ get: {method: 'GET'},
+ create: {method: 'POST', params: {action: 'create'}, transformResponse: genericHandler},
+ remove: {
+ method: 'DELETE', transformResponse: genericHandler
+ }
+ });
+}]);
diff --git a/app/services/authentication.js b/app/services/authentication.js
new file mode 100644
index 000000000..d9f58b844
--- /dev/null
+++ b/app/services/authentication.js
@@ -0,0 +1,38 @@
+angular.module('portainer.services')
+.factory('Authentication', ['$q', 'Auth', 'jwtHelper', 'LocalStorage', 'StateManager', function AuthenticationFactory($q, Auth, jwtHelper, LocalStorage, StateManager) {
+ 'use strict';
+
+ var credentials = {};
+ return {
+ init: function() {
+ var jwt = LocalStorage.getJWT();
+ if (jwt) {
+ var tokenPayload = jwtHelper.decodeToken(jwt);
+ credentials.username = tokenPayload.username;
+ }
+ },
+ login: function(username, password) {
+ return $q(function (resolve, reject) {
+ Auth.login({username: username, password: password}).$promise
+ .then(function(data) {
+ LocalStorage.storeJWT(data.jwt);
+ credentials.username = username;
+ resolve();
+ }, function() {
+ reject();
+ });
+ });
+ },
+ logout: function() {
+ StateManager.clean();
+ LocalStorage.clean();
+ },
+ isAuthenticated: function() {
+ var jwt = LocalStorage.getJWT();
+ return jwt && !jwtHelper.isTokenExpired(jwt);
+ },
+ getCredentials: function() {
+ return credentials;
+ }
+ };
+}]);
diff --git a/app/services/endpointService.js b/app/services/endpointService.js
new file mode 100644
index 000000000..511004b90
--- /dev/null
+++ b/app/services/endpointService.js
@@ -0,0 +1,84 @@
+angular.module('portainer.services')
+.factory('EndpointService', ['$q', 'Endpoints', 'FileUploadService', function EndpointServiceFactory($q, Endpoints, FileUploadService) {
+ 'use strict';
+ return {
+ getActive: function() {
+ return Endpoints.getActiveEndpoint().$promise;
+ },
+ setActive: function(endpointID) {
+ return Endpoints.setActiveEndpoint({id: endpointID}).$promise;
+ },
+ endpoint: function(endpointID) {
+ return Endpoints.get({id: endpointID}).$promise;
+ },
+ endpoints: function() {
+ return Endpoints.query({}).$promise;
+ },
+ updateEndpoint: function(ID, name, URL, TLS, TLSCAFile, TLSCertFile, TLSKeyFile, type) {
+ var endpoint = {
+ id: ID,
+ Name: name,
+ URL: type === 'local' ? ("unix://" + URL) : ("tcp://" + URL),
+ TLS: TLS
+ };
+ var deferred = $q.defer();
+ Endpoints.update({}, endpoint, function success(data) {
+ FileUploadService.uploadTLSFilesForEndpoint(ID, TLSCAFile, TLSCertFile, TLSKeyFile).then(function success(data) {
+ deferred.notify({upload: false});
+ deferred.resolve(data);
+ }, function error(err) {
+ deferred.notify({upload: false});
+ deferred.reject({msg: 'Unable to upload TLS certs', err: err});
+ });
+ }, function error(err) {
+ deferred.reject({msg: 'Unable to update endpoint', err: err});
+ });
+ return deferred.promise;
+ },
+ deleteEndpoint: function(endpointID) {
+ return Endpoints.remove({id: endpointID}).$promise;
+ },
+ createLocalEndpoint: function(name, URL, TLS, active) {
+ var endpoint = {
+ Name: "local",
+ URL: "unix:///var/run/docker.sock",
+ TLS: false
+ };
+ return Endpoints.create({active: active}, endpoint).$promise;
+ },
+ createRemoteEndpoint: function(name, URL, TLS, TLSCAFile, TLSCertFile, TLSKeyFile, active) {
+ var endpoint = {
+ Name: name,
+ URL: 'tcp://' + URL,
+ TLS: TLS
+ };
+ var deferred = $q.defer();
+ Endpoints.create({active: active}, endpoint, function success(data) {
+ var endpointID = data.Id;
+ if (TLS) {
+ deferred.notify({upload: true});
+ FileUploadService.uploadTLSFilesForEndpoint(endpointID, TLSCAFile, TLSCertFile, TLSKeyFile).then(function success(data) {
+ deferred.notify({upload: false});
+ if (active) {
+ Endpoints.setActiveEndpoint({}, {id: endpointID}, function success(data) {
+ deferred.resolve(data);
+ }, function error(err) {
+ deferred.reject({msg: 'Unable to create endpoint', err: err});
+ });
+ } else {
+ deferred.resolve(data);
+ }
+ }, function error(err) {
+ deferred.notify({upload: false});
+ deferred.reject({msg: 'Unable to upload TLS certs', err: err});
+ });
+ } else {
+ deferred.resolve(data);
+ }
+ }, function error(err) {
+ deferred.reject({msg: 'Unable to create endpoint', err: err});
+ });
+ return deferred.promise;
+ }
+ };
+}]);
diff --git a/app/services/fileUpload.js b/app/services/fileUpload.js
new file mode 100644
index 000000000..89eddafc2
--- /dev/null
+++ b/app/services/fileUpload.js
@@ -0,0 +1,44 @@
+angular.module('portainer.services')
+.factory('FileUploadService', ['$q', 'Upload', function FileUploadFactory($q, Upload) {
+ 'use strict';
+ function uploadFile(url, file) {
+ var deferred = $q.defer();
+ Upload.upload({
+ url: url,
+ data: { file: file }
+ }).then(function success(data) {
+ deferred.resolve(data);
+ }, function error(e) {
+ deferred.reject(e);
+ }, function progress(evt) {
+ });
+ return deferred.promise;
+ }
+ return {
+ uploadTLSFilesForEndpoint: function(endpointID, TLSCAFile, TLSCertFile, TLSKeyFile) {
+ var deferred = $q.defer();
+ var queue = [];
+
+ if (TLSCAFile !== null) {
+ var uploadTLSCA = uploadFile('api/upload/tls/' + endpointID + '/ca', TLSCAFile);
+ queue.push(uploadTLSCA);
+ }
+ if (TLSCertFile !== null) {
+ var uploadTLSCert = uploadFile('api/upload/tls/' + endpointID + '/cert', TLSCertFile);
+ queue.push(uploadTLSCert);
+ }
+ if (TLSKeyFile !== null) {
+ var uploadTLSKey = uploadFile('api/upload/tls/' + endpointID + '/key', TLSKeyFile);
+ queue.push(uploadTLSKey);
+ }
+ $q.all(queue).then(function (data) {
+ deferred.resolve(data);
+ }, function (err) {
+ deferred.reject(err);
+ }, function update(evt) {
+ deferred.notify(evt);
+ });
+ return deferred.promise;
+ }
+ };
+}]);
diff --git a/app/services/lineChart.js b/app/services/lineChart.js
new file mode 100644
index 000000000..12c848ee0
--- /dev/null
+++ b/app/services/lineChart.js
@@ -0,0 +1,55 @@
+angular.module('portainer.services')
+.factory('LineChart', ['Settings', function LineChartFactory(Settings) {
+ 'use strict';
+ return {
+ build: function (id, data, getkey) {
+ var chart = new Chart($(id).get(0).getContext("2d"));
+ var map = {};
+
+ for (var i = 0; i < data.length; i++) {
+ var c = data[i];
+ var key = getkey(c);
+
+ var count = map[key];
+ if (count === undefined) {
+ count = 0;
+ }
+ count += 1;
+ map[key] = count;
+ }
+
+ var labels = [];
+ data = [];
+ var keys = Object.keys(map);
+ var max = 1;
+
+ for (i = keys.length - 1; i > -1; i--) {
+ var k = keys[i];
+ labels.push(k);
+ data.push(map[k]);
+ if (map[k] > max) {
+ max = map[k];
+ }
+ }
+ var steps = Math.min(max, 10);
+ var dataset = {
+ fillColor: "rgba(151,187,205,0.5)",
+ strokeColor: "rgba(151,187,205,1)",
+ pointColor: "rgba(151,187,205,1)",
+ pointStrokeColor: "#fff",
+ data: data
+ };
+ chart.Line({
+ labels: labels,
+ datasets: [dataset]
+ },
+ {
+ scaleStepWidth: Math.ceil(max / steps),
+ pointDotRadius: 1,
+ scaleIntegersOnly: true,
+ scaleOverride: true,
+ scaleSteps: steps
+ });
+ }
+ };
+}]);
diff --git a/app/services/localStorage.js b/app/services/localStorage.js
new file mode 100644
index 000000000..5d8e413fd
--- /dev/null
+++ b/app/services/localStorage.js
@@ -0,0 +1,36 @@
+angular.module('portainer.services')
+.factory('LocalStorage', ['localStorageService', function LocalStorageFactory(localStorageService) {
+ 'use strict';
+ return {
+ storeEndpointState: function(state) {
+ localStorageService.set('ENDPOINT_STATE', state);
+ },
+ getEndpointState: function() {
+ return localStorageService.get('ENDPOINT_STATE');
+ },
+ storeApplicationState: function(state) {
+ localStorageService.set('APPLICATION_STATE', state);
+ },
+ getApplicationState: function() {
+ return localStorageService.get('APPLICATION_STATE');
+ },
+ storeJWT: function(jwt) {
+ localStorageService.set('JWT', jwt);
+ },
+ getJWT: function() {
+ return localStorageService.get('JWT');
+ },
+ deleteJWT: function() {
+ localStorageService.remove('JWT');
+ },
+ storePaginationCount: function(key, count) {
+ localStorageService.cookie.set('pagination_' + key, count);
+ },
+ getPaginationCount: function(key) {
+ return localStorageService.cookie.get('pagination_' + key);
+ },
+ clean: function() {
+ localStorageService.clearAll();
+ }
+ };
+}]);
diff --git a/app/services/messages.js b/app/services/messages.js
new file mode 100644
index 000000000..3caf63ba6
--- /dev/null
+++ b/app/services/messages.js
@@ -0,0 +1,38 @@
+angular.module('portainer.services')
+.factory('Messages', ['$sanitize', function MessagesFactory($sanitize) {
+ 'use strict';
+ return {
+ send: function (title, text) {
+ $.gritter.add({
+ title: $sanitize(title),
+ text: $sanitize(text),
+ time: 2000,
+ before_open: function () {
+ if ($('.gritter-item-wrapper').length === 3) {
+ return false;
+ }
+ }
+ });
+ },
+ error: function (title, e, fallbackText) {
+ var msg = fallbackText;
+ if (e.data && e.data.message) {
+ msg = e.data.message;
+ } else if (e.message) {
+ msg = e.message;
+ } else if (e.data && e.data.length > 0 && e.data[0].message) {
+ msg = e.data[0].message;
+ }
+ $.gritter.add({
+ title: $sanitize(title),
+ text: $sanitize(msg),
+ time: 10000,
+ before_open: function () {
+ if ($('.gritter-item-wrapper').length === 4) {
+ return false;
+ }
+ }
+ });
+ }
+ };
+}]);
diff --git a/app/services/pagination.js b/app/services/pagination.js
new file mode 100644
index 000000000..23bfecf8c
--- /dev/null
+++ b/app/services/pagination.js
@@ -0,0 +1,17 @@
+angular.module('portainer.services')
+.factory('Pagination', ['LocalStorage', 'Settings', function PaginationFactory(LocalStorage, Settings) {
+ 'use strict';
+ return {
+ getPaginationCount: function(key) {
+ var storedCount = LocalStorage.getPaginationCount(key);
+ var paginationCount = Settings.pagination_count;
+ if (storedCount !== null) {
+ paginationCount = storedCount;
+ }
+ return '' + paginationCount;
+ },
+ setPaginationCount: function(key, count) {
+ LocalStorage.storePaginationCount(key, count);
+ }
+ };
+}]);
diff --git a/app/services/settings.js b/app/services/settings.js
new file mode 100644
index 000000000..ed367f38c
--- /dev/null
+++ b/app/services/settings.js
@@ -0,0 +1,17 @@
+angular.module('portainer.services')
+.factory('Settings', ['DOCKER_ENDPOINT', 'DOCKER_PORT', 'UI_VERSION', 'PAGINATION_MAX_ITEMS', function SettingsFactory(DOCKER_ENDPOINT, DOCKER_PORT, UI_VERSION, PAGINATION_MAX_ITEMS) {
+ 'use strict';
+ var url = DOCKER_ENDPOINT;
+ if (DOCKER_PORT) {
+ url = url + DOCKER_PORT + '\\' + DOCKER_PORT;
+ }
+ var firstLoad = (localStorage.getItem('firstLoad') || 'true') === 'true';
+ return {
+ displayAll: true,
+ endpoint: DOCKER_ENDPOINT,
+ uiVersion: UI_VERSION,
+ url: url,
+ firstLoad: firstLoad,
+ pagination_count: PAGINATION_MAX_ITEMS
+ };
+}]);
diff --git a/app/services/stateManager.js b/app/services/stateManager.js
new file mode 100644
index 000000000..573747984
--- /dev/null
+++ b/app/services/stateManager.js
@@ -0,0 +1,66 @@
+angular.module('portainer.services')
+.factory('StateManager', ['$q', 'Config', 'Info', 'InfoHelper', 'Version', 'LocalStorage', function StateManagerFactory($q, Config, Info, InfoHelper, Version, LocalStorage) {
+ 'use strict';
+
+ var state = {
+ loading: true,
+ application: {},
+ endpoint: {}
+ };
+
+ return {
+ initialize: function() {
+ var endpointState = LocalStorage.getEndpointState();
+ if (endpointState) {
+ state.endpoint = endpointState;
+ }
+
+ var deferred = $q.defer();
+ var applicationState = LocalStorage.getApplicationState();
+ if (applicationState) {
+ state.application = applicationState;
+ state.loading = false;
+ deferred.resolve(state);
+ } else {
+ Config.$promise.then(function success(data) {
+ state.application.authentication = data.authentication;
+ state.application.endpointManagement = data.endpointManagement;
+ state.application.logo = data.logo;
+ LocalStorage.storeApplicationState(state.application);
+ state.loading = false;
+ deferred.resolve(state);
+ }, function error(err) {
+ state.loading = false;
+ deferred.reject({msg: 'Unable to retrieve server configuration', err: err});
+ });
+ }
+ return deferred.promise;
+ },
+ clean: function() {
+ state.endpoint = {};
+ },
+ updateEndpointState: function(loading) {
+ var deferred = $q.defer();
+ if (loading) {
+ state.loading = true;
+ }
+ $q.all([Info.get({}).$promise, Version.get({}).$promise])
+ .then(function success(data) {
+ var endpointMode = InfoHelper.determineEndpointMode(data[0]);
+ var endpointAPIVersion = parseFloat(data[1].ApiVersion);
+ state.endpoint.mode = endpointMode;
+ state.endpoint.apiVersion = endpointAPIVersion;
+ LocalStorage.storeEndpointState(state.endpoint);
+ state.loading = false;
+ deferred.resolve();
+ }, function error(err) {
+ state.loading = false;
+ deferred.reject({msg: 'Unable to connect to the Docker endpoint', err: err});
+ });
+ return deferred.promise;
+ },
+ getState: function() {
+ return state;
+ }
+ };
+}]);
diff --git a/app/shared/helpers.js b/app/shared/helpers.js
deleted file mode 100644
index 3cddb5c65..000000000
--- a/app/shared/helpers.js
+++ /dev/null
@@ -1,170 +0,0 @@
-angular.module('portainer.helpers', [])
-.factory('InfoHelper', [function InfoHelperFactory() {
- 'use strict';
- return {
- determineEndpointMode: function(info) {
- var mode = {
- provider: '',
- role: ''
- };
- if (_.startsWith(info.ServerVersion, 'swarm')) {
- mode.provider = "DOCKER_SWARM";
- if (info.SystemStatus[0][1] === 'primary') {
- mode.role = "PRIMARY";
- } else {
- mode.role = "REPLICA";
- }
- } else {
- if (!info.Swarm || _.isEmpty(info.Swarm.NodeID)) {
- mode.provider = "DOCKER_STANDALONE";
- } else {
- mode.provider = "DOCKER_SWARM_MODE";
- if (info.Swarm.ControlAvailable) {
- mode.role = "MANAGER";
- } else {
- mode.role = "WORKER";
- }
- }
- }
- return mode;
- }
- };
-}])
-.factory('LabelHelper', [function LabelHelperFactory() {
- 'use strict';
- return {
- fromLabelHashToKeyValue: function(labels) {
- if (labels) {
- return Object.keys(labels).map(function(key) {
- return {key: key, value: labels[key], originalKey: key, originalValue: labels[key], added: true};
- });
- }
- return [];
- },
- fromKeyValueToLabelHash: function(labelKV) {
- var labels = {};
- if (labelKV) {
- labelKV.forEach(function(label) {
- labels[label.key] = label.value;
- });
- }
- return labels;
- }
- };
-}])
-.factory('ImageHelper', [function ImageHelperFactory() {
- 'use strict';
- return {
- createImageConfigForCommit: function(imageName, registry) {
- var imageNameAndTag = imageName.split(':');
- var image = imageNameAndTag[0];
- if (registry) {
- image = registry + '/' + imageNameAndTag[0];
- }
- var imageConfig = {
- repo: image,
- tag: imageNameAndTag[1] ? imageNameAndTag[1] : 'latest'
- };
- return imageConfig;
- },
- createImageConfigForContainer: function (imageName, registry) {
- var imageNameAndTag = imageName.split(':');
- var image = imageNameAndTag[0];
- if (registry) {
- image = registry + '/' + imageNameAndTag[0];
- }
- var imageConfig = {
- fromImage: image,
- tag: imageNameAndTag[1] ? imageNameAndTag[1] : 'latest'
- };
- return imageConfig;
- }
- };
-}])
-.factory('ContainerHelper', [function ContainerHelperFactory() {
- 'use strict';
- return {
- hideContainers: function(containers, containersToHideLabels) {
- return containers.filter(function (container) {
- var filterContainer = false;
- containersToHideLabels.forEach(function(label, index) {
- if (_.has(container.Labels, label.name) &&
- container.Labels[label.name] === label.value) {
- filterContainer = true;
- }
- });
- if (!filterContainer) {
- return container;
- }
- });
- }
- };
-}])
-.factory('ServiceHelper', [function ServiceHelperFactory() {
- 'use strict';
- return {
- serviceToConfig: function(service) {
- return {
- Name: service.Spec.Name,
- Labels: service.Spec.Labels,
- TaskTemplate: service.Spec.TaskTemplate,
- Mode: service.Spec.Mode,
- UpdateConfig: service.Spec.UpdateConfig,
- Networks: service.Spec.Networks,
- EndpointSpec: service.Spec.EndpointSpec
- };
- }
- };
-}])
-.factory('NodeHelper', [function NodeHelperFactory() {
- 'use strict';
- return {
- nodeToConfig: function(node) {
- return {
- Name: node.Spec.Name,
- Role: node.Spec.Role,
- Labels: node.Spec.Labels,
- Availability: node.Spec.Availability
- };
- }
- };
-}])
-.factory('TemplateHelper', [function TemplateHelperFactory() {
- 'use strict';
- return {
- getPortBindings: function(ports) {
- var bindings = [];
- ports.forEach(function (port) {
- var portAndProtocol = _.split(port, '/');
- var binding = {
- containerPort: portAndProtocol[0],
- protocol: portAndProtocol[1]
- };
- bindings.push(binding);
- });
- return bindings;
- },
- //Not used atm, may prove useful later
- getVolumeBindings: function(volumes) {
- var bindings = [];
- volumes.forEach(function (volume) {
- bindings.push({ containerPath: volume });
- });
- return bindings;
- },
- //Not used atm, may prove useful later
- getEnvBindings: function(env) {
- var bindings = [];
- env.forEach(function (envvar) {
- var binding = {
- name: envvar.name
- };
- if (envvar.set) {
- binding.value = envvar.set;
- }
- bindings.push(binding);
- });
- return bindings;
- }
- };
-}]);
diff --git a/app/shared/services.js b/app/shared/services.js
deleted file mode 100644
index dbd982663..000000000
--- a/app/shared/services.js
+++ /dev/null
@@ -1,594 +0,0 @@
-angular.module('portainer.services', ['ngResource', 'ngSanitize'])
- .factory('Container', ['$resource', 'Settings', function ContainerFactory($resource, Settings) {
- 'use strict';
- // Resource for interacting with the docker containers
- // http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#2-1-containers
- return $resource(Settings.url + '/containers/:id/:action', {
- name: '@name'
- }, {
- query: {method: 'GET', params: {all: 0, action: 'json', filters: '@filters' }, isArray: true},
- get: {method: 'GET', params: {action: 'json'}},
- stop: {method: 'POST', params: {id: '@id', t: 5, action: 'stop'}},
- restart: {method: 'POST', params: {id: '@id', t: 5, action: 'restart'}},
- kill: {method: 'POST', params: {id: '@id', action: 'kill'}},
- pause: {method: 'POST', params: {id: '@id', action: 'pause'}},
- unpause: {method: 'POST', params: {id: '@id', action: 'unpause'}},
- changes: {method: 'GET', params: {action: 'changes'}, isArray: true},
- stats: {method: 'GET', params: {id: '@id', stream: false, action: 'stats'}, timeout: 5000},
- start: {
- method: 'POST', params: {id: '@id', action: 'start'},
- transformResponse: genericHandler
- },
- create: {
- method: 'POST', params: {action: 'create'},
- transformResponse: genericHandler
- },
- remove: {
- method: 'DELETE', params: {id: '@id', v: 0},
- transformResponse: genericHandler
- },
- rename: {
- method: 'POST', params: {id: '@id', action: 'rename', name: '@name'},
- transformResponse: genericHandler
- },
- exec: {
- method: 'POST', params: {id: '@id', action: 'exec'},
- transformResponse: genericHandler
- }
- });
- }])
- .factory('Service', ['$resource', 'Settings', function ServiceFactory($resource, Settings) {
- 'use strict';
- // https://docs.docker.com/engine/reference/api/docker_remote_api_<%= remoteApiVersion %>/#/3-9-services
- return $resource(Settings.url + '/services/:id/:action', {}, {
- get: { method: 'GET', params: {id: '@id'} },
- query: { method: 'GET', isArray: true },
- create: { method: 'POST', params: {action: 'create'} },
- update: { method: 'POST', params: {id: '@id', action: 'update', version: '@version'} },
- remove: { method: 'DELETE', params: {id: '@id'} }
- });
- }])
- .factory('Task', ['$resource', 'Settings', function TaskFactory($resource, Settings) {
- 'use strict';
- // https://docs.docker.com/engine/reference/api/docker_remote_api_<%= remoteApiVersion %>/#/3-9-services
- return $resource(Settings.url + '/tasks/:id', {}, {
- get: { method: 'GET', params: {id: '@id'} },
- query: { method: 'GET', isArray: true, params: {filters: '@filters'} }
- });
- }])
- .factory('Exec', ['$resource', 'Settings', function ExecFactory($resource, Settings) {
- 'use strict';
- // https://docs.docker.com/engine/reference/api/docker_remote_api_<%= remoteApiVersion %>/#/exec-resize
- return $resource(Settings.url + '/exec/:id/:action', {}, {
- resize: {
- method: 'POST', params: {id: '@id', action: 'resize', h: '@height', w: '@width'},
- transformResponse: genericHandler
- }
- });
- }])
- .factory('ContainerCommit', ['$resource', '$http', 'Settings', function ContainerCommitFactory($resource, $http, Settings) {
- 'use strict';
- // http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#create-a-new-image-from-a-container-s-changes
- return $resource(Settings.url + '/commit', {}, {
- commit: {method: 'POST', params: {container: '@id', repo: '@repo', tag: '@tag'}}
- });
- }])
- .factory('ContainerLogs', ['$resource', '$http', 'Settings', function ContainerLogsFactory($resource, $http, Settings) {
- 'use strict';
- // http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#get-container-logs
- return {
- get: function (id, params, callback) {
- $http({
- method: 'GET',
- url: Settings.url + '/containers/' + id + '/logs',
- params: {
- 'stdout': params.stdout || 0,
- 'stderr': params.stderr || 0,
- 'timestamps': params.timestamps || 0,
- 'tail': params.tail || 'all'
- }
- }).success(callback).error(function (data, status, headers, config) {
- console.log(error, data);
- });
- }
- };
- }])
- .factory('ContainerTop', ['$http', 'Settings', function ($http, Settings) {
- 'use strict';
- // http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#list-processes-running-inside-a-container
- return {
- get: function (id, params, callback, errorCallback) {
- $http({
- method: 'GET',
- url: Settings.url + '/containers/' + id + '/top',
- params: {
- ps_args: params.ps_args
- }
- }).success(callback);
- }
- };
- }])
- .factory('Image', ['$resource', 'Settings', function ImageFactory($resource, Settings) {
- 'use strict';
- // http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#2-2-images
- return $resource(Settings.url + '/images/:id/:action', {}, {
- query: {method: 'GET', params: {all: 0, action: 'json'}, isArray: true},
- get: {method: 'GET', params: {action: 'json'}},
- search: {method: 'GET', params: {action: 'search'}},
- history: {method: 'GET', params: {action: 'history'}, isArray: true},
- insert: {method: 'POST', params: {id: '@id', action: 'insert'}},
- tag: {method: 'POST', params: {id: '@id', action: 'tag', force: 0, repo: '@repo', tag: '@tag'}},
- inspect: {method: 'GET', params: {id: '@id', action: 'json'}},
- push: {
- method: 'POST', params: {action: 'push', id: '@tag'},
- isArray: true, transformResponse: jsonObjectsToArrayHandler
- },
- create: {
- method: 'POST', params: {action: 'create', fromImage: '@fromImage', tag: '@tag'},
- isArray: true, transformResponse: jsonObjectsToArrayHandler
- },
- remove: {
- method: 'DELETE', params: {id: '@id'},
- isArray: true, transformResponse: deleteImageHandler
- }
- });
- }])
- .factory('Events', ['$resource', 'Settings', function EventFactory($resource, Settings) {
- 'use strict';
- // http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#/monitor-docker-s-events
- return $resource(Settings.url + '/events', {}, {
- query: {
- method: 'GET', params: {since: '@since', until: '@until'},
- isArray: true, transformResponse: jsonObjectsToArrayHandler
- }
- });
- }])
- .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', {}, {
- get: {method: 'GET'}
- });
- }])
- .factory('Node', ['$resource', 'Settings', function NodeFactory($resource, Settings) {
- 'use strict';
- // https://docs.docker.com/engine/reference/api/docker_remote_api_<%= remoteApiVersion %>/#/3-7-nodes
- return $resource(Settings.url + '/nodes/:id/:action', {}, {
- query: {method: 'GET', isArray: true},
- get: {method: 'GET', params: {id: '@id'}},
- update: { method: 'POST', params: {id: '@id', action: 'update', version: '@version'} },
- remove: { method: 'DELETE', params: {id: '@id'} }
- });
- }])
- .factory('Swarm', ['$resource', 'Settings', function SwarmFactory($resource, Settings) {
- 'use strict';
- // https://docs.docker.com/engine/reference/api/docker_remote_api_<%= remoteApiVersion %>/#/3-8-swarm
- return $resource(Settings.url + '/swarm', {}, {
- get: {method: 'GET'}
- });
- }])
- .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('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'}, transformResponse: genericHandler},
- remove: { method: 'DELETE', transformResponse: genericHandler },
- 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'}, transformResponse: genericHandler},
- remove: {
- method: 'DELETE', transformResponse: genericHandler
- }
- });
- }])
- .factory('Config', ['$resource', 'CONFIG_ENDPOINT', function ConfigFactory($resource, CONFIG_ENDPOINT) {
- return $resource(CONFIG_ENDPOINT).get();
- }])
- .factory('Templates', ['$resource', 'TEMPLATES_ENDPOINT', function TemplatesFactory($resource, TEMPLATES_ENDPOINT) {
- return $resource(TEMPLATES_ENDPOINT, {}, {
- get: {method: 'GET', isArray: true}
- });
- }])
- .factory('Settings', ['DOCKER_ENDPOINT', 'DOCKER_PORT', 'UI_VERSION', 'PAGINATION_MAX_ITEMS', function SettingsFactory(DOCKER_ENDPOINT, DOCKER_PORT, UI_VERSION, PAGINATION_MAX_ITEMS) {
- 'use strict';
- var url = DOCKER_ENDPOINT;
- if (DOCKER_PORT) {
- url = url + DOCKER_PORT + '\\' + DOCKER_PORT;
- }
- var firstLoad = (localStorage.getItem('firstLoad') || 'true') === 'true';
- return {
- displayAll: true,
- endpoint: DOCKER_ENDPOINT,
- uiVersion: UI_VERSION,
- url: url,
- firstLoad: firstLoad,
- pagination_count: PAGINATION_MAX_ITEMS
- };
- }])
- .factory('Auth', ['$resource', 'AUTH_ENDPOINT', function AuthFactory($resource, AUTH_ENDPOINT) {
- 'use strict';
- return $resource(AUTH_ENDPOINT, {}, {
- login: {
- method: 'POST'
- }
- });
- }])
- .factory('Users', ['$resource', 'USERS_ENDPOINT', function UsersFactory($resource, USERS_ENDPOINT) {
- 'use strict';
- return $resource(USERS_ENDPOINT + '/:username/:action', {}, {
- create: { method: 'POST' },
- get: { method: 'GET', params: { username: '@username' } },
- update: { method: 'PUT', params: { username: '@username' } },
- checkPassword: { method: 'POST', params: { username: '@username', action: 'passwd' } },
- checkAdminUser: { method: 'GET', params: { username: 'admin', action: 'check' } },
- initAdminUser: { method: 'POST', params: { username: 'admin', action: 'init' } }
- });
- }])
- .factory('Authentication', ['$q', '$rootScope', 'Auth', 'jwtHelper', 'LocalStorage', 'StateManager', function AuthenticationFactory($q, $rootScope, Auth, jwtHelper, LocalStorage, StateManager) {
- 'use strict';
- return {
- init: function() {
- var jwt = LocalStorage.getJWT();
- if (jwt) {
- var tokenPayload = jwtHelper.decodeToken(jwt);
- $rootScope.username = tokenPayload.username;
- }
- },
- login: function(username, password) {
- return $q(function (resolve, reject) {
- Auth.login({username: username, password: password}).$promise
- .then(function(data) {
- LocalStorage.storeJWT(data.jwt);
- $rootScope.username = username;
- resolve();
- }, function() {
- reject();
- });
- });
- },
- logout: function() {
- StateManager.clean();
- LocalStorage.clean();
- },
- isAuthenticated: function() {
- var jwt = LocalStorage.getJWT();
- return jwt && !jwtHelper.isTokenExpired(jwt);
- }
- };
- }])
- .factory('FileUploadService', ['$q', 'Upload', function FileUploadFactory($q, Upload) {
- 'use strict';
- function uploadFile(url, file) {
- var deferred = $q.defer();
- Upload.upload({
- url: url,
- data: { file: file }
- }).then(function success(data) {
- deferred.resolve(data);
- }, function error(e) {
- deferred.reject(e);
- }, function progress(evt) {
- });
- return deferred.promise;
- }
- return {
- uploadTLSFilesForEndpoint: function(endpointID, TLSCAFile, TLSCertFile, TLSKeyFile) {
- var deferred = $q.defer();
- var queue = [];
-
- if (TLSCAFile !== null) {
- var uploadTLSCA = uploadFile('api/upload/tls/' + endpointID + '/ca', TLSCAFile);
- queue.push(uploadTLSCA);
- }
- if (TLSCertFile !== null) {
- var uploadTLSCert = uploadFile('api/upload/tls/' + endpointID + '/cert', TLSCertFile);
- queue.push(uploadTLSCert);
- }
- if (TLSKeyFile !== null) {
- var uploadTLSKey = uploadFile('api/upload/tls/' + endpointID + '/key', TLSKeyFile);
- queue.push(uploadTLSKey);
- }
- $q.all(queue).then(function (data) {
- deferred.resolve(data);
- }, function (err) {
- deferred.reject(err);
- }, function update(evt) {
- deferred.notify(evt);
- });
- return deferred.promise;
- }
- };
- }])
- .factory('Endpoints', ['$resource', 'ENDPOINTS_ENDPOINT', function EndpointsFactory($resource, ENDPOINTS_ENDPOINT) {
- 'use strict';
- return $resource(ENDPOINTS_ENDPOINT + '/:id/:action', {}, {
- create: { method: 'POST' },
- query: { method: 'GET', isArray: true },
- get: { method: 'GET', params: { id: '@id' } },
- update: { method: 'PUT', params: { id: '@id' } },
- remove: { method: 'DELETE', params: { id: '@id'} },
- getActiveEndpoint: { method: 'GET', params: { id: '0' } },
- setActiveEndpoint: { method: 'POST', params: { id: '@id', action: 'active' } }
- });
- }])
- .factory('Pagination', ['LocalStorage', 'Settings', function PaginationFactory(LocalStorage, Settings) {
- 'use strict';
- return {
- getPaginationCount: function(key) {
- var storedCount = LocalStorage.getPaginationCount(key);
- var paginationCount = Settings.pagination_count;
- if (storedCount !== null) {
- paginationCount = storedCount;
- }
- return '' + paginationCount;
- },
- setPaginationCount: function(key, count) {
- LocalStorage.storePaginationCount(key, count);
- }
- };
- }])
- .factory('LocalStorage', ['localStorageService', function LocalStorageFactory(localStorageService) {
- 'use strict';
- return {
- storeEndpointState: function(state) {
- localStorageService.set('ENDPOINT_STATE', state);
- },
- getEndpointState: function() {
- return localStorageService.get('ENDPOINT_STATE');
- },
- storeJWT: function(jwt) {
- localStorageService.set('JWT', jwt);
- },
- getJWT: function() {
- return localStorageService.get('JWT');
- },
- deleteJWT: function() {
- localStorageService.remove('JWT');
- },
- storePaginationCount: function(key, count) {
- localStorageService.cookie.set('pagination_' + key, count);
- },
- getPaginationCount: function(key) {
- return localStorageService.cookie.get('pagination_' + key);
- },
- clean: function() {
- localStorageService.clearAll();
- }
- };
- }])
- .factory('StateManager', ['$q', 'Info', 'InfoHelper', 'Version', 'LocalStorage', function StateManagerFactory($q, Info, InfoHelper, Version, LocalStorage) {
- 'use strict';
-
- var state = {
- loading: true,
- application: {},
- endpoint: {}
- };
-
- return {
- init: function() {
- var endpointState = LocalStorage.getEndpointState();
- if (endpointState) {
- state.endpoint = endpointState;
- }
- state.loading = false;
- },
- clean: function() {
- state.endpoint = {};
- },
- updateEndpointState: function(loading) {
- var deferred = $q.defer();
- if (loading) {
- state.loading = true;
- }
- $q.all([Info.get({}).$promise, Version.get({}).$promise])
- .then(function success(data) {
- var endpointMode = InfoHelper.determineEndpointMode(data[0]);
- var endpointAPIVersion = parseFloat(data[1].ApiVersion);
- state.endpoint.mode = endpointMode;
- state.endpoint.apiVersion = endpointAPIVersion;
- LocalStorage.storeEndpointState(state.endpoint);
- state.loading = false;
- deferred.resolve();
- }, function error(err) {
- state.loading = false;
- deferred.reject({msg: 'Unable to connect to the Docker endpoint', err: err});
- });
- return deferred.promise;
- },
- getState: function() {
- return state;
- }
- };
- }])
- .factory('EndpointService', ['$q', 'Endpoints', 'FileUploadService', function EndpointServiceFactory($q, Endpoints, FileUploadService) {
- 'use strict';
- return {
- getActive: function() {
- return Endpoints.getActiveEndpoint().$promise;
- },
- setActive: function(endpointID) {
- return Endpoints.setActiveEndpoint({id: endpointID}).$promise;
- },
- endpoint: function(endpointID) {
- return Endpoints.get({id: endpointID}).$promise;
- },
- endpoints: function() {
- return Endpoints.query({}).$promise;
- },
- updateEndpoint: function(ID, name, URL, TLS, TLSCAFile, TLSCertFile, TLSKeyFile, type) {
- var endpoint = {
- id: ID,
- Name: name,
- URL: type === 'local' ? ("unix://" + URL) : ("tcp://" + URL),
- TLS: TLS
- };
- var deferred = $q.defer();
- Endpoints.update({}, endpoint, function success(data) {
- FileUploadService.uploadTLSFilesForEndpoint(ID, TLSCAFile, TLSCertFile, TLSKeyFile).then(function success(data) {
- deferred.notify({upload: false});
- deferred.resolve(data);
- }, function error(err) {
- deferred.notify({upload: false});
- deferred.reject({msg: 'Unable to upload TLS certs', err: err});
- });
- }, function error(err) {
- deferred.reject({msg: 'Unable to update endpoint', err: err});
- });
- return deferred.promise;
- },
- deleteEndpoint: function(endpointID) {
- return Endpoints.remove({id: endpointID}).$promise;
- },
- createLocalEndpoint: function(name, URL, TLS, active) {
- var endpoint = {
- Name: "local",
- URL: "unix:///var/run/docker.sock",
- TLS: false
- };
- return Endpoints.create({active: active}, endpoint).$promise;
- },
- createRemoteEndpoint: function(name, URL, TLS, TLSCAFile, TLSCertFile, TLSKeyFile, active) {
- var endpoint = {
- Name: name,
- URL: 'tcp://' + URL,
- TLS: TLS
- };
- var deferred = $q.defer();
- Endpoints.create({active: active}, endpoint, function success(data) {
- var endpointID = data.Id;
- if (TLS) {
- deferred.notify({upload: true});
- FileUploadService.uploadTLSFilesForEndpoint(endpointID, TLSCAFile, TLSCertFile, TLSKeyFile).then(function success(data) {
- deferred.notify({upload: false});
- if (active) {
- Endpoints.setActiveEndpoint({}, {id: endpointID}, function success(data) {
- deferred.resolve(data);
- }, function error(err) {
- deferred.reject({msg: 'Unable to create endpoint', err: err});
- });
- } else {
- deferred.resolve(data);
- }
- }, function error(err) {
- deferred.notify({upload: false});
- deferred.reject({msg: 'Unable to upload TLS certs', err: err});
- });
- } else {
- deferred.resolve(data);
- }
- }, function error(err) {
- deferred.reject({msg: 'Unable to create endpoint', err: err});
- });
- return deferred.promise;
- }
- };
- }])
- .factory('Messages', ['$rootScope', '$sanitize', function MessagesFactory($rootScope, $sanitize) {
- 'use strict';
- return {
- send: function (title, text) {
- $.gritter.add({
- title: $sanitize(title),
- text: $sanitize(text),
- time: 2000,
- before_open: function () {
- if ($('.gritter-item-wrapper').length === 3) {
- return false;
- }
- }
- });
- },
- error: function (title, e, fallbackText) {
- var msg = fallbackText;
- if (e.data && e.data.message) {
- msg = e.data.message;
- } else if (e.message) {
- msg = e.message;
- } else if (e.data && e.data.length > 0 && e.data[0].message) {
- msg = e.data[0].message;
- }
- $.gritter.add({
- title: $sanitize(title),
- text: $sanitize(msg),
- time: 10000,
- before_open: function () {
- if ($('.gritter-item-wrapper').length === 4) {
- return false;
- }
- }
- });
- }
- };
- }])
- .factory('LineChart', ['Settings', function LineChartFactory(Settings) {
- 'use strict';
- return {
- build: function (id, data, getkey) {
- var chart = new Chart($(id).get(0).getContext("2d"));
- var map = {};
-
- for (var i = 0; i < data.length; i++) {
- var c = data[i];
- var key = getkey(c);
-
- var count = map[key];
- if (count === undefined) {
- count = 0;
- }
- count += 1;
- map[key] = count;
- }
-
- var labels = [];
- data = [];
- var keys = Object.keys(map);
- var max = 1;
-
- for (i = keys.length - 1; i > -1; i--) {
- var k = keys[i];
- labels.push(k);
- data.push(map[k]);
- if (map[k] > max) {
- max = map[k];
- }
- }
- var steps = Math.min(max, 10);
- var dataset = {
- fillColor: "rgba(151,187,205,0.5)",
- strokeColor: "rgba(151,187,205,1)",
- pointColor: "rgba(151,187,205,1)",
- pointStrokeColor: "#fff",
- data: data
- };
- chart.Line({
- labels: labels,
- datasets: [dataset]
- },
- {
- scaleStepWidth: Math.ceil(max / steps),
- pointDotRadius: 1,
- scaleIntegersOnly: true,
- scaleOverride: true,
- scaleSteps: steps
- });
- }
- };
- }]);
diff --git a/app/shared/viewmodel.js b/app/shared/viewmodel.js
deleted file mode 100644
index 6d9e4e0b1..000000000
--- a/app/shared/viewmodel.js
+++ /dev/null
@@ -1,240 +0,0 @@
-function ImageViewModel(data) {
- this.Id = data.Id;
- this.Tag = data.Tag;
- this.Repository = data.Repository;
- this.Created = data.Created;
- this.Checked = false;
- this.RepoTags = data.RepoTags;
- this.VirtualSize = data.VirtualSize;
-}
-
-function TaskViewModel(data, node_data) {
- this.Id = data.ID;
- this.Created = data.CreatedAt;
- this.Updated = data.UpdatedAt;
- this.Slot = data.Slot;
- this.Status = data.Status.State;
- this.Image = data.Spec.ContainerSpec ? data.Spec.ContainerSpec.Image : '';
- if (node_data) {
- for (var i = 0; i < node_data.length; ++i) {
- if (data.NodeID === node_data[i].ID) {
- this.Node = node_data[i].Description.Hostname;
- }
- }
- }
-}
-
-function ServiceViewModel(data) {
- this.Model = data;
- this.Id = data.ID;
- this.Name = data.Spec.Name;
- this.Image = data.Spec.TaskTemplate.ContainerSpec.Image;
- this.Version = data.Version.Index;
- if (data.Spec.Mode.Replicated) {
- this.Mode = 'replicated' ;
- this.Replicas = data.Spec.Mode.Replicated.Replicas;
- } else {
- this.Mode = 'global';
- }
- this.Labels = data.Spec.Labels;
- if (data.Spec.TaskTemplate.ContainerSpec) {
- this.ContainerLabels = data.Spec.TaskTemplate.ContainerSpec.Labels;
- }
- if (data.Spec.TaskTemplate.ContainerSpec.Env) {
- this.Env = data.Spec.TaskTemplate.ContainerSpec.Env;
- }
- if (data.Endpoint.Ports) {
- this.Ports = data.Endpoint.Ports;
- }
- if (data.Spec.UpdateConfig) {
- this.UpdateParallelism = (typeof data.Spec.UpdateConfig.Parallelism !== undefined) ? data.Spec.UpdateConfig.Parallelism || 0 : 1;
- this.UpdateDelay = data.Spec.UpdateConfig.Delay || 0;
- this.UpdateFailureAction = data.Spec.UpdateConfig.FailureAction || 'pause';
- } else {
- this.UpdateParallelism = 1;
- this.UpdateDelay = 0;
- this.UpdateFailureAction = 'pause';
- }
-
- this.Checked = false;
- this.Scale = false;
- this.EditName = false;
-}
-
-function NodeViewModel(data) {
- this.Model = data;
- this.Id = data.ID;
- this.Version = data.Version.Index;
- this.Name = data.Spec.Name;
- this.Role = data.Spec.Role;
- this.CreatedAt = data.CreatedAt;
- this.UpdatedAt = data.UpdatedAt;
- this.Availability = data.Spec.Availability;
-
- var labels = data.Spec.Labels;
- if (labels) {
- this.Labels = Object.keys(labels).map(function(key) {
- return { key: key, value: labels[key], originalKey: key, originalValue: labels[key], added: true };
- });
- } else {
- this.Labels = [];
- }
-
- this.Hostname = data.Description.Hostname;
- this.PlatformArchitecture = data.Description.Platform.Architecture;
- this.PlatformOS = data.Description.Platform.OS;
- this.CPUs = data.Description.Resources.NanoCPUs;
- this.Memory = data.Description.Resources.MemoryBytes;
- this.EngineVersion = data.Description.Engine.EngineVersion;
- this.EngineLabels = data.Description.Engine.Labels;
- this.Plugins = data.Description.Engine.Plugins;
- this.Status = data.Status.State;
-
- if (data.ManagerStatus) {
- this.Leader = data.ManagerStatus.Leader;
- this.Reachability = data.ManagerStatus.Reachability;
- this.ManagerAddr = data.ManagerStatus.Addr;
- }
-}
-
-function ContainerViewModel(data) {
- this.Id = data.Id;
- this.Status = data.Status;
- this.State = data.State;
- this.Names = data.Names;
- // Unavailable in Docker < 1.10
- if (data.NetworkSettings && !_.isEmpty(data.NetworkSettings.Networks)) {
- this.IP = data.NetworkSettings.Networks[Object.keys(data.NetworkSettings.Networks)[0]].IPAddress;
- }
- this.Image = data.Image;
- this.Command = data.Command;
- this.Checked = false;
- this.Ports = [];
- for (var i = 0; i < data.Ports.length; ++i) {
- var p = data.Ports[i];
- if (p.PublicPort) {
- this.Ports.push({ host: p.IP, private: p.PrivatePort, public: p.PublicPort });
- }
- }
-}
-
-function createEventDetails(event) {
- var eventAttr = event.Actor.Attributes;
- var details = '';
- switch (event.Type) {
- case 'container':
- switch (event.Action) {
- case 'stop':
- details = 'Container ' + eventAttr.name + ' stopped';
- break;
- case 'destroy':
- details = 'Container ' + eventAttr.name + ' deleted';
- break;
- case 'create':
- details = 'Container ' + eventAttr.name + ' created';
- break;
- case 'start':
- details = 'Container ' + eventAttr.name + ' started';
- break;
- case 'kill':
- details = 'Container ' + eventAttr.name + ' killed';
- break;
- case 'die':
- details = 'Container ' + eventAttr.name + ' exited with status code ' + eventAttr.exitCode;
- break;
- case 'commit':
- details = 'Container ' + eventAttr.name + ' committed';
- break;
- case 'restart':
- details = 'Container ' + eventAttr.name + ' restarted';
- break;
- case 'pause':
- details = 'Container ' + eventAttr.name + ' paused';
- break;
- case 'unpause':
- details = 'Container ' + eventAttr.name + ' unpaused';
- break;
- case 'attach':
- details = 'Container ' + eventAttr.name + ' attached';
- break;
- default:
- if (event.Action.indexOf('exec_create') === 0) {
- details = 'Exec instance created';
- } else if (event.Action.indexOf('exec_start') === 0) {
- details = 'Exec instance started';
- } else {
- details = 'Unsupported event';
- }
- }
- break;
- case 'image':
- switch (event.Action) {
- case 'delete':
- details = 'Image deleted';
- break;
- case 'tag':
- details = 'New tag created for ' + eventAttr.name;
- break;
- case 'untag':
- details = 'Image untagged';
- break;
- case 'pull':
- details = 'Image ' + event.Actor.ID + ' pulled';
- break;
- default:
- details = 'Unsupported event';
- }
- break;
- case 'network':
- switch (event.Action) {
- case 'create':
- details = 'Network ' + eventAttr.name + ' created';
- break;
- case 'destroy':
- details = 'Network ' + eventAttr.name + ' deleted';
- break;
- case 'connect':
- details = 'Container connected to ' + eventAttr.name + ' network';
- break;
- case 'disconnect':
- details = 'Container disconnected from ' + eventAttr.name + ' network';
- break;
- default:
- details = 'Unsupported event';
- }
- break;
- case 'volume':
- switch (event.Action) {
- case 'create':
- details = 'Volume ' + event.Actor.ID + ' created';
- break;
- case 'destroy':
- details = 'Volume ' + event.Actor.ID + ' deleted';
- break;
- case 'mount':
- details = 'Volume ' + event.Actor.ID + ' mounted';
- break;
- case 'unmount':
- details = 'Volume ' + event.Actor.ID + ' unmounted';
- break;
- default:
- details = 'Unsupported event';
- }
- break;
- default:
- details = 'Unsupported event';
- }
- return details;
-}
-
-function EventViewModel(data) {
- // Type, Action, Actor unavailable in Docker < 1.10
- this.Time = data.time;
- if (data.Type) {
- this.Type = data.Type;
- this.Details = createEventDetails(data);
- } else {
- this.Type = data.status;
- this.Details = data.from;
- }
-}
diff --git a/assets/js/.jshintrc b/assets/js/.jshintrc
deleted file mode 100755
index e0722690b..000000000
--- a/assets/js/.jshintrc
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "validthis": true,
- "laxcomma" : true,
- "laxbreak" : true,
- "browser" : true,
- "eqnull" : true,
- "debug" : true,
- "devel" : true,
- "boss" : true,
- "expr" : true,
- "asi" : true
-}
\ No newline at end of file
diff --git a/bower.json b/bower.json
index d69e8403e..5fb8e2982 100644
--- a/bower.json
+++ b/bower.json
@@ -1,6 +1,6 @@
{
"name": "portainer",
- "version": "1.11.3",
+ "version": "1.11.4",
"homepage": "https://github.com/portainer/portainer",
"authors": [
"Anthony Lapenna |