refactor(global): service separation #552

pull/553/head
Anthony Lapenna 2017-02-01 12:26:29 +13:00 committed by GitHub
parent 0abe8883d1
commit fe0bf77bbb
48 changed files with 1029 additions and 1015 deletions

View File

@ -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';

View File

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

View File

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

32
app/helpers/infoHelper.js Normal file
View File

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

View File

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

14
app/helpers/nodeHelper.js Normal file
View File

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

View File

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

View File

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

20
app/models/container.js Normal file
View File

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

120
app/models/event.js Normal file
View File

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

9
app/models/image.js Normal file
View File

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

35
app/models/node.js Normal file
View File

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

36
app/models/service.js Normal file
View File

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

15
app/models/task.js Normal file
View File

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

9
app/rest/auth.js Normal file
View File

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

4
app/rest/config.js Normal file
View File

@ -0,0 +1,4 @@
angular.module('portainer.rest')
.factory('Config', ['$resource', 'CONFIG_ENDPOINT', function ConfigFactory($resource, CONFIG_ENDPOINT) {
return $resource(CONFIG_ENDPOINT).get();
}]);

37
app/rest/container.js Normal file
View File

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

View File

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

20
app/rest/containerLogs.js Normal file
View File

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

15
app/rest/containerTop.js Normal file
View File

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

13
app/rest/endpoint.js Normal file
View File

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

10
app/rest/event.js Normal file
View File

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

10
app/rest/exec.js Normal file
View File

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

25
app/rest/image.js Normal file
View File

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

5
app/rest/info.js Normal file
View File

@ -0,0 +1,5 @@
angular.module('portainer.rest')
.factory('Info', ['$resource', 'Settings', function InfoFactory($resource, Settings) {
'use strict';
return $resource(Settings.url + '/info', {});
}]);

12
app/rest/network.js Normal file
View File

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

10
app/rest/node.js Normal file
View File

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

11
app/rest/service.js Normal file
View File

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

7
app/rest/swarm.js Normal file
View File

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

8
app/rest/task.js Normal file
View File

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

6
app/rest/templates.js Normal file
View File

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

12
app/rest/user.js Normal file
View File

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

5
app/rest/version.js Normal file
View File

@ -0,0 +1,5 @@
angular.module('portainer.rest')
.factory('Version', ['$resource', 'Settings', function VersionFactory($resource, Settings) {
'use strict';
return $resource(Settings.url + '/version', {});
}]);

12
app/rest/volume.js Normal file
View File

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

View File

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

View File

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

View File

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

55
app/services/lineChart.js Normal file
View File

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

View File

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

38
app/services/messages.js Normal file
View File

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

View File

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

17
app/services/settings.js Normal file
View File

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

View File

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

View File

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

View File

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

View File

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