From fe0bf77bbb0f34fe3a37fa32c352b6a2ec7852ac Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Wed, 1 Feb 2017 12:26:29 +1300 Subject: [PATCH] refactor(global): service separation #552 --- app/app.js | 27 +- app/{shared => filters}/filters.js | 0 app/helpers/containerHelper.js | 20 + app/helpers/imageHelper.js | 30 + app/helpers/infoHelper.js | 32 + app/helpers/labelHelper.js | 23 + app/helpers/nodeHelper.js | 14 + app/helpers/serviceHelper.js | 17 + app/helpers/templateHelper.js | 40 ++ app/models/container.js | 20 + app/models/event.js | 120 ++++ app/models/image.js | 9 + app/models/node.js | 35 ++ app/models/service.js | 36 ++ app/models/task.js | 15 + app/rest/auth.js | 9 + app/rest/config.js | 4 + app/rest/container.js | 37 ++ app/rest/containerCommit.js | 7 + app/rest/containerLogs.js | 20 + app/rest/containerTop.js | 15 + app/rest/endpoint.js | 13 + app/rest/event.js | 10 + app/rest/exec.js | 10 + app/rest/image.js | 25 + app/rest/info.js | 5 + app/rest/network.js | 12 + app/rest/node.js | 10 + .../response/handlers.js} | 0 app/rest/service.js | 11 + app/rest/swarm.js | 7 + app/rest/task.js | 8 + app/rest/templates.js | 6 + app/rest/user.js | 12 + app/rest/version.js | 5 + app/rest/volume.js | 12 + app/services/authentication.js | 33 + app/services/endpointService.js | 84 +++ app/services/fileUpload.js | 44 ++ app/services/lineChart.js | 55 ++ app/services/localStorage.js | 30 + app/services/messages.js | 38 ++ app/services/pagination.js | 17 + app/services/settings.js | 17 + app/services/stateManager.js | 46 ++ app/shared/helpers.js | 170 ----- app/shared/services.js | 594 ------------------ app/shared/viewmodel.js | 240 ------- 48 files changed, 1029 insertions(+), 1015 deletions(-) rename app/{shared => filters}/filters.js (100%) create mode 100644 app/helpers/containerHelper.js create mode 100644 app/helpers/imageHelper.js create mode 100644 app/helpers/infoHelper.js create mode 100644 app/helpers/labelHelper.js create mode 100644 app/helpers/nodeHelper.js create mode 100644 app/helpers/serviceHelper.js create mode 100644 app/helpers/templateHelper.js create mode 100644 app/models/container.js create mode 100644 app/models/event.js create mode 100644 app/models/image.js create mode 100644 app/models/node.js create mode 100644 app/models/service.js create mode 100644 app/models/task.js create mode 100644 app/rest/auth.js create mode 100644 app/rest/config.js create mode 100644 app/rest/container.js create mode 100644 app/rest/containerCommit.js create mode 100644 app/rest/containerLogs.js create mode 100644 app/rest/containerTop.js create mode 100644 app/rest/endpoint.js create mode 100644 app/rest/event.js create mode 100644 app/rest/exec.js create mode 100644 app/rest/image.js create mode 100644 app/rest/info.js create mode 100644 app/rest/network.js create mode 100644 app/rest/node.js rename app/{shared/responseHandlers.js => rest/response/handlers.js} (100%) create mode 100644 app/rest/service.js create mode 100644 app/rest/swarm.js create mode 100644 app/rest/task.js create mode 100644 app/rest/templates.js create mode 100644 app/rest/user.js create mode 100644 app/rest/version.js create mode 100644 app/rest/volume.js create mode 100644 app/services/authentication.js create mode 100644 app/services/endpointService.js create mode 100644 app/services/fileUpload.js create mode 100644 app/services/lineChart.js create mode 100644 app/services/localStorage.js create mode 100644 app/services/messages.js create mode 100644 app/services/pagination.js create mode 100644 app/services/settings.js create mode 100644 app/services/stateManager.js delete mode 100644 app/shared/helpers.js delete mode 100644 app/shared/services.js delete mode 100644 app/shared/viewmodel.js diff --git a/app/app.js b/app/app.js index 3c563dd9e..c6333b0ab 100644 --- a/app/app.js +++ b/app/app.js @@ -1,5 +1,8 @@ +angular.module('portainer.filters', []); +angular.module('portainer.rest', ['ngResource']); +angular.module('portainer.services', []); +angular.module('portainer.helpers', []); angular.module('portainer', [ - 'portainer.templates', 'ui.bootstrap', 'ui.router', 'ui.select', @@ -9,9 +12,11 @@ angular.module('portainer', [ 'angularUtils.directives.dirPagination', 'LocalStorageModule', 'angular-jwt', - 'portainer.services', - 'portainer.helpers', + 'portainer.templates', 'portainer.filters', + 'portainer.rest', + 'portainer.helpers', + 'portainer.services', 'auth', 'dashboard', 'container', @@ -19,29 +24,29 @@ angular.module('portainer', [ 'containerLogs', 'containers', 'createContainer', + 'createNetwork', + 'createService', + 'createVolume', 'docker', 'endpoint', 'endpointInit', 'endpoints', 'events', - 'images', 'image', + 'images', 'main', + 'network', + 'networks', + 'node', 'service', 'services', 'settings', 'sidebar', - 'createService', 'stats', 'swarm', - 'network', - 'networks', - 'node', - 'createNetwork', 'task', 'templates', - 'volumes', - 'createVolume']) + 'volumes']) .config(['$stateProvider', '$urlRouterProvider', '$httpProvider', 'localStorageServiceProvider', 'jwtOptionsProvider', function ($stateProvider, $urlRouterProvider, $httpProvider, localStorageServiceProvider, jwtOptionsProvider) { 'use strict'; 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..9d22c215f --- /dev/null +++ b/app/services/authentication.js @@ -0,0 +1,33 @@ +angular.module('portainer.services') +.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); + } + }; +}]); 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..45d793b59 --- /dev/null +++ b/app/services/localStorage.js @@ -0,0 +1,30 @@ +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'); + }, + 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..ad2f464b0 --- /dev/null +++ b/app/services/stateManager.js @@ -0,0 +1,46 @@ +angular.module('portainer.services') +.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; + } + }; +}]); 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; - } -}