From 80bb94e7459b52ec346870d8423bb085618c0972 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Fri, 30 Jun 2017 14:52:04 +0200 Subject: [PATCH 01/34] docs(README): update README --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a3483f6f9..14a404772 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,11 @@ [![Gitter](https://badges.gitter.im/portainer/Lobby.svg)](https://gitter.im/portainer/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YHXZJQNJQ36H6) -**_Portainer_** is a lightweight management UI which allows you to **easily** manage your Docker host or Swarm cluster. +**_Portainer_** is a lightweight management UI which allows you to **easily** manage your different Docker environments (Docker hosts or Swarm clusters). -**_Portainer_** is meant to be as **simple** to deploy as it is to use. It consists of a single container that can run on any Docker engine (Docker for Linux and Docker for Windows are supported). +**_Portainer_** is meant to be as **simple** to deploy as it is to use. It consists of a single container that can run on any Docker engine (can be deployed as Linux container or a Windows native container). -**_Portainer_** allows you to manage your Docker containers, images, volumes, networks and more ! It is compatible with the *standalone Docker* engine and with *Docker Swarm*. +**_Portainer_** allows you to manage your Docker containers, images, volumes, networks and more ! It is compatible with the *standalone Docker* engine and with *Docker Swarm mode*. ## Demo @@ -34,8 +34,8 @@ Please note that the public demo cluster is **reset every 15min**. * Issues: https://github.com/portainer/portainer/issues * FAQ: https://portainer.readthedocs.io/en/latest/faq.html +* Slack (chat): https://portainer.io/slack/ * Gitter (chat): https://gitter.im/portainer/Lobby -* Slack: https://portainer.io/slack/ ## Reporting bugs and contributing From 07a0c4dfe33f5ac29057c505456787373596aafd Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Mon, 3 Jul 2017 08:36:18 +0200 Subject: [PATCH 02/34] feat(endpoints): update information message (#974) --- app/components/endpoints/endpoints.html | 5 ++++- app/components/registries/registries.html | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/components/endpoints/endpoints.html b/app/components/endpoints/endpoints.html index fd6660b66..2421d4261 100644 --- a/app/components/endpoints/endpoints.html +++ b/app/components/endpoints/endpoints.html @@ -14,7 +14,10 @@ - Portainer has been started using the --external-endpoints flag. Endpoint management via the UI is disabled. You can still manage endpoint access. + Portainer has been started using the --external-endpoints flag. + Endpoint management via the UI is disabled. + You can still manage endpoint access. + diff --git a/app/components/registries/registries.html b/app/components/registries/registries.html index cf5772dcc..17ba58425 100644 --- a/app/components/registries/registries.html +++ b/app/components/registries/registries.html @@ -133,10 +133,10 @@ - Loading... + Loading... - No registries available. + No registries available. From c5ce45f5880792a44c5c036da7d24f07e3129c18 Mon Sep 17 00:00:00 2001 From: Konstantin Azizov Date: Tue, 4 Jul 2017 15:30:23 +0300 Subject: [PATCH 03/34] chore(build-system): replace Recess with PostCSS (#975) --- gruntfile.js | 60 ++++++++++++++++++++-------------------------------- package.json | 6 +++--- 2 files changed, 26 insertions(+), 40 deletions(-) diff --git a/gruntfile.js b/gruntfile.js index 3c24f3062..1c8df5bad 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -1,3 +1,6 @@ +var autoprefixer = require('autoprefixer'); +var cssnano = require('cssnano'); + module.exports = function (grunt) { grunt.loadNpmTasks('grunt-contrib-concat'); @@ -6,15 +9,14 @@ module.exports = function (grunt) { grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-contrib-copy'); grunt.loadNpmTasks('grunt-contrib-watch'); - grunt.loadNpmTasks('grunt-recess'); grunt.loadNpmTasks('grunt-html2js'); grunt.loadNpmTasks('grunt-shell'); grunt.loadNpmTasks('grunt-if'); grunt.loadNpmTasks('grunt-filerev'); - grunt.loadNpmTasks('grunt-contrib-cssmin'); grunt.loadNpmTasks('grunt-usemin'); grunt.loadNpmTasks('grunt-replace'); grunt.loadNpmTasks('grunt-config'); + grunt.loadNpmTasks('grunt-postcss'); grunt.registerTask('default', ['eslint', 'build']); grunt.registerTask('build', [ @@ -23,7 +25,6 @@ module.exports = function (grunt) { 'if:linuxAmd64BinaryNotExist', 'html2js', 'useminPrepare:dev', - 'recess:build', 'concat', 'clean:tmpl', 'replace', @@ -37,10 +38,9 @@ module.exports = function (grunt) { 'clean:all', 'html2js', 'useminPrepare:release', - 'recess:build', 'concat', + 'postcss:build', 'clean:tmpl', - 'cssmin', 'replace', 'uglify', 'copy:assets', @@ -54,10 +54,9 @@ module.exports = function (grunt) { 'if:linux386BinaryNotExist', 'html2js', 'useminPrepare:release', - 'recess:build', 'concat', + 'postcss:build', 'clean:tmpl', - 'cssmin', 'replace', 'uglify', 'copy:assets', @@ -71,10 +70,9 @@ module.exports = function (grunt) { 'if:linuxAmd64BinaryNotExist', 'html2js', 'useminPrepare:release', - 'recess:build', 'concat', + 'postcss:build', 'clean:tmpl', - 'cssmin', 'replace', 'uglify', 'copy:assets', @@ -88,10 +86,9 @@ module.exports = function (grunt) { 'if:linuxArmBinaryNotExist', 'html2js', 'useminPrepare:release', - 'recess:build', 'concat', + 'postcss:build', 'clean:tmpl', - 'cssmin', 'replace', 'uglify', 'copy', @@ -105,10 +102,9 @@ module.exports = function (grunt) { 'if:linuxArm64BinaryNotExist', 'html2js', 'useminPrepare:release', - 'recess:build', 'concat', + 'postcss:build', 'clean:tmpl', - 'cssmin', 'replace', 'uglify', 'copy', @@ -122,10 +118,9 @@ module.exports = function (grunt) { 'if:linuxPpc64leBinaryNotExist', 'html2js', 'useminPrepare:release', - 'recess:build', 'concat', + 'postcss:build', 'clean:tmpl', - 'cssmin', 'replace', 'uglify', 'copy', @@ -139,10 +134,9 @@ module.exports = function (grunt) { 'if:windowsAmd64BinaryNotExist', 'html2js', 'useminPrepare:release', - 'recess:build', 'concat', + 'postcss:build', 'clean:tmpl', - 'cssmin', 'replace', 'uglify', 'copy', @@ -156,10 +150,9 @@ module.exports = function (grunt) { 'if:darwinAmd64BinaryNotExist', 'html2js', 'useminPrepare:release', - 'recess:build', 'concat', + 'postcss:build', 'clean:tmpl', - 'cssmin', 'replace', 'uglify', 'copy', @@ -304,6 +297,10 @@ module.exports = function (grunt) { } }, concat: { + css: { + src: ['<%= src.cssVendor %>', '<%= src.css %>'], + dest: '<%= distdir %>/css/<%= pkg.name %>.css' + }, dist: { options: { process: true @@ -358,27 +355,16 @@ module.exports = function (grunt) { dest: '<%= distdir %>/js/angular.js' } }, - recess: { // TODO: not maintained, unable to preserve license comments, switch out for something better. + postcss: { build: { - files: { - '<%= distdir %>/css/<%= pkg.name %>.css': ['<%= src.css %>'], - '<%= distdir %>/css/vendor.css': ['<%= src.cssVendor %>'] - }, options: { - compile: true, - noOverqualifying: false // TODO: Added because of .nav class, rename - } - }, - min: { - files: { - '<%= distdir %>/css/<%= pkg.name %>.css': ['<%= src.css %>'], - '<%= distdir %>/css/vendor.css': ['<%= src.cssVendor %>'] + processors: [ + autoprefixer({browsers: 'last 2 versions'}), // add vendor prefixes + cssnano() // minify the result + ] }, - options: { - compile: true, - compress: true, - noOverqualifying: false // TODO: Added because of .nav class, rename - } + src: '<%= distdir %>/css/<%= pkg.name %>.css', + dest: '<%= distdir %>/css/app.css' } }, watch: { diff --git a/package.json b/package.json index a1bfab28d..345e5f4a8 100644 --- a/package.json +++ b/package.json @@ -22,9 +22,10 @@ "engines": { "node": ">= 0.8.4" }, - "dependencies": {}, "devDependencies": { + "autoprefixer": "^7.1.1", "bower": "^1.5.2", + "cssnano": "^3.10.0", "eslint": "^3.19.0", "grunt": "~0.4.0", "grunt-cli": "^1.2.0", @@ -32,7 +33,6 @@ "grunt-contrib-clean": "~0.4.0", "grunt-contrib-concat": "~0.1.3", "grunt-contrib-copy": "~0.4.0", - "grunt-contrib-cssmin": "^1.0.2", "grunt-contrib-jshint": "^1.1.0", "grunt-contrib-uglify": "^0.9.2", "grunt-contrib-watch": "~0.3.1", @@ -40,7 +40,7 @@ "grunt-html2js": "~0.1.0", "grunt-if": "^0.1.5", "grunt-karma": "~0.4.4", - "grunt-recess": "~0.3", + "grunt-postcss": "^0.8.0", "grunt-replace": "^1.0.1", "grunt-shell": "^1.1.2", "grunt-usemin": "^3.1.1", From 54c8872d259cd071abbc4a70cf8b7adb32173136 Mon Sep 17 00:00:00 2001 From: Konstantin Azizov Date: Wed, 5 Jul 2017 08:16:57 +0300 Subject: [PATCH 04/34] feat(container-console): add ability to specify the user (#976) --- .../containerConsole/containerConsole.html | 53 ++++++++++++------- .../containerConsoleController.js | 1 + app/directives/tooltip.js | 2 +- 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/app/components/containerConsole/containerConsole.html b/app/components/containerConsole/containerConsole.html index e2f1f4d30..eba9b5ea1 100644 --- a/app/components/containerConsole/containerConsole.html +++ b/app/components/containerConsole/containerConsole.html @@ -16,29 +16,46 @@ -
-
+ +
-
-
- - - - - +
+ +
+
+ + + + + +
+
+
+ +
+ +
+
- -
- - +
+
+ +
+
+ + +
diff --git a/app/components/containerConsole/containerConsoleController.js b/app/components/containerConsole/containerConsoleController.js index b9da6c20f..8ea20b53b 100644 --- a/app/components/containerConsole/containerConsoleController.js +++ b/app/components/containerConsole/containerConsoleController.js @@ -45,6 +45,7 @@ function ($scope, $stateParams, Container, Image, Exec, $timeout, EndpointProvid AttachStdout: true, AttachStderr: true, Tty: true, + User: $scope.state.user, Cmd: $scope.state.command.replace(' ', ',').split(',') }; diff --git a/app/directives/tooltip.js b/app/directives/tooltip.js index 2293fbf72..b4a696090 100644 --- a/app/directives/tooltip.js +++ b/app/directives/tooltip.js @@ -6,7 +6,7 @@ angular message: '@', position: '@' }, - template: '', + template: '', restrict: 'E' }; return directive; From 7473681c5b15cfb18a0dfaba9bac239ba1da7f1f Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Wed, 5 Jul 2017 19:06:28 +0200 Subject: [PATCH 05/34] fix(container-details): fix the ability to commit a container (#983) --- app/components/container/containerController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/container/containerController.js b/app/components/container/containerController.js index e9c6b3303..f7e964e33 100644 --- a/app/components/container/containerController.js +++ b/app/components/container/containerController.js @@ -86,7 +86,7 @@ function ($scope, $state, $stateParams, $filter, Container, ContainerCommit, Con $('#createImageSpinner').show(); var image = $scope.config.Image; var registry = $scope.config.Registry; - var imageConfig = ImageHelper.createImageConfigForCommit(image, registry); + var imageConfig = ImageHelper.createImageConfigForCommit(image, registry.URL); ContainerCommit.commit({id: $stateParams.id, tag: imageConfig.tag, repo: imageConfig.repo}, function (d) { $('#createImageSpinner').hide(); update(); From 6d6f4f092dfd4ad2d7cd33fcb9f605b566589197 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Fri, 7 Jul 2017 15:45:31 +0200 Subject: [PATCH 06/34] fix(secrets): fix an issue when removing a secret that is in use (#984) --- app/services/docker/secretService.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/app/services/docker/secretService.js b/app/services/docker/secretService.js index b15fdbcbc..16dda7603 100644 --- a/app/services/docker/secretService.js +++ b/app/services/docker/secretService.js @@ -36,7 +36,21 @@ angular.module('portainer.services') }; service.remove = function(secretId) { - return Secret.remove({ id: secretId }).$promise; + var deferred = $q.defer(); + + Secret.remove({ id: secretId }).$promise + .then(function success(data) { + if (data.message) { + deferred.reject({ msg: data.message }); + } else { + deferred.resolve(); + } + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to remove secret', err: err }); + }); + + return deferred.promise; }; service.create = function(secretConfig) { From b6b579d55d20a21775fb07ed68fd9088e7814e3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Kov=C3=A1cs?= Date: Sat, 8 Jul 2017 08:59:32 +0200 Subject: [PATCH 07/34] feat(image-details): simple image history (#425) --- app/components/image/image.html | 39 +++++++++++++++++++++++++ app/components/image/imageController.js | 18 ++++++++++++ app/filters/filters.js | 6 ++++ app/models/imageLayer.js | 8 +++++ app/services/docker/imageService.js | 20 +++++++++++++ assets/css/app.css | 16 +++++----- 6 files changed, 99 insertions(+), 8 deletions(-) create mode 100644 app/models/imageLayer.js diff --git a/app/components/image/image.html b/app/components/image/image.html index 799dd2386..e8a780cb7 100644 --- a/app/components/image/image.html +++ b/app/components/image/image.html @@ -167,3 +167,42 @@
+ +
+
+ + + + + + + + + + + + + + + + +
SizeLayer
{{layer.Size|humansize}} +
+ + + {{layer.CreatedBy|createdby|limitTo:100}}{{layer.CreatedBy.length > 100 ? '...' : ''}} + +
+
+ + {{layer.CreatedBy|createdby}} + +
+
+
+
+
+
+
diff --git a/app/components/image/imageController.js b/app/components/image/imageController.js index fa2925b07..0e4552532 100644 --- a/app/components/image/imageController.js +++ b/app/components/image/imageController.js @@ -6,6 +6,12 @@ function ($scope, $stateParams, $state, $timeout, ImageService, RegistryService, Registry: '' }; + $scope.toggleLayerCommand = function(layerId) { + $('#layer-command-expander'+layerId+' span').toggleClass('glyphicon-plus-sign glyphicon-minus-sign'); + $('#layer-command-'+layerId+'-short').toggle(); + $('#layer-command-'+layerId+'-full').toggle(); + }; + $scope.tagImage = function() { $('#loadingViewSpinner').show(); var image = $scope.formValues.Image; @@ -108,6 +114,18 @@ function ($scope, $stateParams, $state, $timeout, ImageService, RegistryService, .finally(function final() { $('#loadingViewSpinner').hide(); }); + + $('#loadingViewSpinner').show(); + ImageService.history($stateParams.id) + .then(function success(data) { + $scope.history = data; + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to retrieve image history'); + }) + .finally(function final() { + $('#loadingViewSpinner').hide(); + }); } retrieveImageDetails(); diff --git a/app/filters/filters.js b/app/filters/filters.js index 5f0569cd4..57096d633 100644 --- a/app/filters/filters.js +++ b/app/filters/filters.js @@ -271,4 +271,10 @@ angular.module('portainer.filters', []) } return ''; }; +}) +.filter('createdby', function () { + 'use strict'; + return function (createdBy) { + return createdBy.replace('/bin/sh -c #(nop) ','').replace('/bin/sh -c ', 'RUN '); + }; }); diff --git a/app/models/imageLayer.js b/app/models/imageLayer.js new file mode 100644 index 000000000..59889d290 --- /dev/null +++ b/app/models/imageLayer.js @@ -0,0 +1,8 @@ +function ImageLayerViewModel(data) { + this.Id = data.Id; + this.Created = data.Created; + this.CreatedBy = data.CreatedBy; + this.Size = data.Size; + this.Comment = data.Comment; + this.Tags = data.Tags; +} diff --git a/app/services/docker/imageService.js b/app/services/docker/imageService.js index c3dbebb1e..2966abdaf 100644 --- a/app/services/docker/imageService.js +++ b/app/services/docker/imageService.js @@ -35,6 +35,26 @@ angular.module('portainer.services') return deferred.promise; }; + service.history = function(imageId) { + var deferred = $q.defer(); + Image.history({id: imageId}).$promise + .then(function success(data) { + if (data.message) { + deferred.reject({ msg: data.message }); + } else { + var layers = []; + angular.forEach(data, function(imageLayer) { + layers.push(new ImageLayerViewModel(imageLayer)); + }); + deferred.resolve(layers); + } + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to retrieve image details', err: err }); + }); + return deferred.promise; + }; + service.pushImage = function(tag, registry) { var deferred = $q.defer(); diff --git a/assets/css/app.css b/assets/css/app.css index adbfa66a7..7be1a7fbb 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -281,14 +281,6 @@ a[ng-click]{ margin: 0 auto; } -ul.sidebar { - bottom: 40px; -} - -ul.sidebar .sidebar-title { - height: auto; -} - ul.sidebar .sidebar-list a.active { color: #fff; text-indent: 22px; @@ -296,6 +288,14 @@ ul.sidebar .sidebar-list a.active { background: #2d3e63; } +#image-layers .btn{ + padding: 0; +} + +#image-layers .expand{ + padding-right: 0; +} + ul.sidebar .sidebar-list .sidebar-sublist a { text-indent: 35px; font-size: 12px; From 317303fc43b3a0ac22bf90e871fca735c6120bce Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Sat, 8 Jul 2017 09:21:30 +0200 Subject: [PATCH 08/34] feat(image-details): image layer enhancements --- app/components/image/image.html | 40 +++++++++++++++++-------- app/components/image/imageController.js | 8 +++++ app/filters/filters.js | 4 +-- assets/css/app.css | 8 +++++ 4 files changed, 46 insertions(+), 14 deletions(-) diff --git a/app/components/image/image.html b/app/components/image/image.html index e8a780cb7..65a188905 100644 --- a/app/components/image/image.html +++ b/app/components/image/image.html @@ -175,29 +175,45 @@ - - - - + + - - + + diff --git a/app/components/image/imageController.js b/app/components/image/imageController.js index 0e4552532..f5c0c6142 100644 --- a/app/components/image/imageController.js +++ b/app/components/image/imageController.js @@ -6,6 +6,14 @@ function ($scope, $stateParams, $state, $timeout, ImageService, RegistryService, Registry: '' }; + $scope.sortType = 'Size'; + $scope.sortReverse = true; + + $scope.order = function(sortType) { + $scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false; + $scope.sortType = sortType; + }; + $scope.toggleLayerCommand = function(layerId) { $('#layer-command-expander'+layerId+' span').toggleClass('glyphicon-plus-sign glyphicon-minus-sign'); $('#layer-command-'+layerId+'-short').toggle(); diff --git a/app/filters/filters.js b/app/filters/filters.js index 57096d633..b1201dcf7 100644 --- a/app/filters/filters.js +++ b/app/filters/filters.js @@ -272,9 +272,9 @@ angular.module('portainer.filters', []) return ''; }; }) -.filter('createdby', function () { +.filter('imagelayercommand', function () { 'use strict'; return function (createdBy) { - return createdBy.replace('/bin/sh -c #(nop) ','').replace('/bin/sh -c ', 'RUN '); + return createdBy.replace('/bin/sh -c #(nop) ', '').replace('/bin/sh -c ', 'RUN '); }; }); diff --git a/assets/css/app.css b/assets/css/app.css index 7be1a7fbb..3eb9b9c4e 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -281,6 +281,14 @@ a[ng-click]{ margin: 0 auto; } +ul.sidebar { + bottom: 40px; +} + +ul.sidebar .sidebar-title { + height: auto; +} + ul.sidebar .sidebar-list a.active { color: #fff; text-indent: 22px; From b3f4c6f751016f050b974b85a9631c74af4b4764 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Sat, 8 Jul 2017 09:22:39 +0200 Subject: [PATCH 09/34] refactor(image-details): place imageLayer model under models/docker --- app/models/{ => docker}/imageLayer.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/models/{ => docker}/imageLayer.js (100%) diff --git a/app/models/imageLayer.js b/app/models/docker/imageLayer.js similarity index 100% rename from app/models/imageLayer.js rename to app/models/docker/imageLayer.js From 472834ac42fd0b958ee05d115929f6aa79adbdff Mon Sep 17 00:00:00 2001 From: Konstantin Azizov Date: Sat, 8 Jul 2017 11:07:08 +0300 Subject: [PATCH 10/34] feat(containers): add buttons disabling based on cluster selection (#985) --- app/components/containers/containers.html | 8 ++-- .../containers/containersController.js | 38 ++++++++++++++++--- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/app/components/containers/containers.html b/app/components/containers/containers.html index 9d5a86722..5d37ef4a8 100644 --- a/app/components/containers/containers.html +++ b/app/components/containers/containers.html @@ -25,12 +25,12 @@
- - + + - - + +
Add container diff --git a/app/components/containers/containersController.js b/app/components/containers/containersController.js index da858ade5..1dd1a22be 100644 --- a/app/components/containers/containersController.js +++ b/app/components/containers/containersController.js @@ -41,6 +41,7 @@ angular.module('containers', []) } return model; }); + updateSelectionFlags(); $('#loadContainersSpinner').hide(); }, function (e) { $('#loadContainersSpinner').hide(); @@ -117,17 +118,15 @@ angular.module('containers', []) angular.forEach($scope.state.filteredContainers, function (container) { if (container.Checked !== allSelected) { container.Checked = allSelected; - $scope.selectItem(container); + toggleItemSelection(container); } }); + updateSelectionFlags(); }; $scope.selectItem = function (item) { - if (item.Checked) { - $scope.state.selectedItemCount++; - } else { - $scope.state.selectedItemCount--; - } + toggleItemSelection(item); + updateSelectionFlags(); }; $scope.toggleGetAll = function () { @@ -186,6 +185,33 @@ angular.module('containers', []) } ); }; + + function toggleItemSelection(item) { + if (item.Checked) { + $scope.state.selectedItemCount++; + } else { + $scope.state.selectedItemCount--; + } + } + + function updateSelectionFlags() { + $scope.state.noStoppedItemsSelected = true; + $scope.state.noRunningItemsSelected = true; + $scope.state.noPausedItemsSelected = true; + $scope.containers.forEach(function(container) { + if(!container.Checked) { + return; + } + + if(container.Status === 'paused') { + $scope.state.noPausedItemsSelected = false; + } else if(container.Status === 'stopped') { + $scope.state.noStoppedItemsSelected = false; + } else if(container.Status === 'running') { + $scope.state.noRunningItemsSelected = false; + } + } ); + } function retrieveSwarmHostsInfo(data) { var swarm_hosts = {}; From 90a32d1b676db35728fb4e61da2c1d9feebae4c5 Mon Sep 17 00:00:00 2001 From: Glowbal Date: Sat, 8 Jul 2017 10:23:00 +0200 Subject: [PATCH 11/34] refactor(html): fix html tags and escape special characters (#987) --- .../common/accessControlForm/accessControlForm.html | 8 ++++---- .../common/accessControlPanel/accessControlPanel.html | 8 ++++---- app/components/container/container.html | 4 ++-- app/components/containerConsole/containerConsole.html | 2 +- app/components/containerLogs/containerlogs.html | 2 +- app/components/containers/containers.html | 2 +- app/components/createContainer/createcontainer.html | 2 +- app/components/createNetwork/createnetwork.html | 2 +- app/components/createRegistry/createregistry.html | 2 +- app/components/createSecret/createsecret.html | 2 +- app/components/createService/createservice.html | 2 +- app/components/createVolume/createvolume.html | 2 +- app/components/endpoint/endpoint.html | 2 +- app/components/endpointAccess/endpointAccess.html | 2 +- app/components/endpoints/endpoints.html | 4 ++-- app/components/events/events.html | 2 +- app/components/image/image.html | 2 +- app/components/images/images.html | 6 +++--- app/components/network/network.html | 2 +- app/components/networks/networks.html | 2 +- app/components/node/node.html | 2 +- app/components/registries/registries.html | 2 +- app/components/registry/registry.html | 2 +- app/components/registryAccess/registryAccess.html | 2 +- app/components/secret/secret.html | 2 +- app/components/secrets/secrets.html | 2 +- app/components/service/includes/ports.html | 2 +- app/components/service/service.html | 2 +- app/components/services/services.html | 2 +- app/components/sidebar/sidebar.html | 2 +- app/components/stats/stats.html | 2 +- app/components/task/task.html | 2 +- app/components/team/team.html | 4 ++-- app/components/teams/teams.html | 6 +++--- app/components/user/user.html | 2 +- app/components/users/users.html | 8 ++++---- app/components/volume/volume.html | 2 +- app/components/volumes/volumes.html | 4 ++-- 38 files changed, 55 insertions(+), 55 deletions(-) diff --git a/app/components/common/accessControlForm/accessControlForm.html b/app/components/common/accessControlForm/accessControlForm.html index 21e4c3869..f3bd32b7b 100644 --- a/app/components/common/accessControlForm/accessControlForm.html +++ b/app/components/common/accessControlForm/accessControlForm.html @@ -79,7 +79,7 @@ - You have not yet created any team. Head over the teams view to manage user teams. + You have not yet created any team. Head over the teams view to manage user teams.
@@ -105,7 +105,7 @@ - You have not yet created any user. Head over the users view to manage users. + You have not yet created any user. Head over the users view to manage users. diff --git a/app/components/common/accessControlPanel/accessControlPanel.html b/app/components/common/accessControlPanel/accessControlPanel.html index eedd4ae66..f8031ad27 100644 --- a/app/components/common/accessControlPanel/accessControlPanel.html +++ b/app/components/common/accessControlPanel/accessControlPanel.html @@ -120,7 +120,7 @@
@@ -142,7 +142,7 @@ diff --git a/app/components/container/container.html b/app/components/container/container.html index 574e67aec..5961a3c89 100644 --- a/app/components/container/container.html +++ b/app/components/container/container.html @@ -3,7 +3,7 @@ - Containers > {{ container.Name|trimcontainername }} + Containers > {{ container.Name|trimcontainername }} @@ -114,7 +114,7 @@
SizeLayer
+ + Size + + + + + + Layer + + + +
{{layer.Size|humansize}}
+ {{ layer.Size | humansize }} + -
+
- {{layer.CreatedBy|createdby|limitTo:100}}{{layer.CreatedBy.length > 100 ? '...' : ''}} + {{ layer.CreatedBy | imagelayercommand | truncate:130 }} + + + + +
-
+
- {{layer.CreatedBy|createdby}} + {{ layer.CreatedBy | imagelayercommand }}
-
Teams - You have not yet created any team. Head over the teams view to manage user teams. + You have not yet created any team. Head over the teams view to manage user teams.
Users - You have not yet created any user. Head over the users view to manage users. + You have not yet created any user. Head over the users view to manage users.
- +
diff --git a/app/components/containerConsole/containerConsole.html b/app/components/containerConsole/containerConsole.html index eba9b5ea1..1189b16ed 100644 --- a/app/components/containerConsole/containerConsole.html +++ b/app/components/containerConsole/containerConsole.html @@ -3,7 +3,7 @@ - Containers > {{ container.Name|trimcontainername }} > Console + Containers > {{ container.Name|trimcontainername }} > Console diff --git a/app/components/containerLogs/containerlogs.html b/app/components/containerLogs/containerlogs.html index b20af8bf8..fe12b3732 100644 --- a/app/components/containerLogs/containerlogs.html +++ b/app/components/containerLogs/containerlogs.html @@ -3,7 +3,7 @@ - Containers > {{ container.Name|trimcontainername }} > Logs + Containers > {{ container.Name|trimcontainername }} > Logs diff --git a/app/components/containers/containers.html b/app/components/containers/containers.html index 5d37ef4a8..3cd1a3aee 100644 --- a/app/components/containers/containers.html +++ b/app/components/containers/containers.html @@ -137,5 +137,5 @@
- + diff --git a/app/components/createContainer/createcontainer.html b/app/components/createContainer/createcontainer.html index 1798c0230..8600fe092 100644 --- a/app/components/createContainer/createcontainer.html +++ b/app/components/createContainer/createcontainer.html @@ -1,7 +1,7 @@ - Containers > Add container + Containers > Add container diff --git a/app/components/createNetwork/createnetwork.html b/app/components/createNetwork/createnetwork.html index 98992d438..fdf591b88 100644 --- a/app/components/createNetwork/createnetwork.html +++ b/app/components/createNetwork/createnetwork.html @@ -1,7 +1,7 @@ - Networks > Add network + Networks > Add network diff --git a/app/components/createRegistry/createregistry.html b/app/components/createRegistry/createregistry.html index def77d867..f1204ba6f 100644 --- a/app/components/createRegistry/createregistry.html +++ b/app/components/createRegistry/createregistry.html @@ -3,7 +3,7 @@ - Registries > Add registry + Registries > Add registry diff --git a/app/components/createSecret/createsecret.html b/app/components/createSecret/createsecret.html index 385bb3067..2a840af60 100644 --- a/app/components/createSecret/createsecret.html +++ b/app/components/createSecret/createsecret.html @@ -1,7 +1,7 @@ - Secrets > Add secret + Secrets > Add secret diff --git a/app/components/createService/createservice.html b/app/components/createService/createservice.html index 3f2f03016..25e58e568 100644 --- a/app/components/createService/createservice.html +++ b/app/components/createService/createservice.html @@ -3,7 +3,7 @@ - Services > Add service + Services > Add service diff --git a/app/components/createVolume/createvolume.html b/app/components/createVolume/createvolume.html index 178194fe7..7444cac54 100644 --- a/app/components/createVolume/createvolume.html +++ b/app/components/createVolume/createvolume.html @@ -3,7 +3,7 @@ - Volumes > Add volume + Volumes > Add volume diff --git a/app/components/endpoint/endpoint.html b/app/components/endpoint/endpoint.html index 543bc7062..80be7c92c 100644 --- a/app/components/endpoint/endpoint.html +++ b/app/components/endpoint/endpoint.html @@ -3,7 +3,7 @@ - Endpoints > {{ endpoint.Name }} + Endpoints > {{ endpoint.Name }} diff --git a/app/components/endpointAccess/endpointAccess.html b/app/components/endpointAccess/endpointAccess.html index e4b14312c..695dc6955 100644 --- a/app/components/endpointAccess/endpointAccess.html +++ b/app/components/endpointAccess/endpointAccess.html @@ -3,7 +3,7 @@ - Endpoints > {{ endpoint.Name }} > Access management + Endpoints > {{ endpoint.Name }} > Access management diff --git a/app/components/endpoints/endpoints.html b/app/components/endpoints/endpoints.html index 2421d4261..6528ae711 100644 --- a/app/components/endpoints/endpoints.html +++ b/app/components/endpoints/endpoints.html @@ -18,7 +18,7 @@ Endpoint management via the UI is disabled. You can still manage endpoint access. - +
@@ -206,6 +206,6 @@ - + diff --git a/app/components/events/events.html b/app/components/events/events.html index e59c9fc8d..829bc38e3 100644 --- a/app/components/events/events.html +++ b/app/components/events/events.html @@ -69,6 +69,6 @@ - + diff --git a/app/components/image/image.html b/app/components/image/image.html index 65a188905..7e4341611 100644 --- a/app/components/image/image.html +++ b/app/components/image/image.html @@ -3,7 +3,7 @@ - Images > {{ image.Id }} + Images > {{ image.Id }} diff --git a/app/components/images/images.html b/app/components/images/images.html index cfecf029a..2b078d290 100644 --- a/app/components/images/images.html +++ b/app/components/images/images.html @@ -132,6 +132,6 @@ - - - + + + diff --git a/app/components/network/network.html b/app/components/network/network.html index ae0cc846d..b2c1c9b6e 100644 --- a/app/components/network/network.html +++ b/app/components/network/network.html @@ -3,7 +3,7 @@ - Networks > {{ network.Name }} + Networks > {{ network.Name }} diff --git a/app/components/networks/networks.html b/app/components/networks/networks.html index e25170c97..aa0286ac9 100644 --- a/app/components/networks/networks.html +++ b/app/components/networks/networks.html @@ -154,6 +154,6 @@ - + diff --git a/app/components/node/node.html b/app/components/node/node.html index 480009e57..4b9f87910 100644 --- a/app/components/node/node.html +++ b/app/components/node/node.html @@ -5,7 +5,7 @@ - Swarm nodes > {{ node.Hostname }} + Swarm nodes > {{ node.Hostname }} diff --git a/app/components/registries/registries.html b/app/components/registries/registries.html index 17ba58425..fac2ee8d5 100644 --- a/app/components/registries/registries.html +++ b/app/components/registries/registries.html @@ -145,6 +145,6 @@ - + diff --git a/app/components/registry/registry.html b/app/components/registry/registry.html index 2bbb7c2e6..971d99848 100644 --- a/app/components/registry/registry.html +++ b/app/components/registry/registry.html @@ -3,7 +3,7 @@ - Registries > {{ registry.Name }} + Registries > {{ registry.Name }} diff --git a/app/components/registryAccess/registryAccess.html b/app/components/registryAccess/registryAccess.html index 84c3d2395..44ff3d130 100644 --- a/app/components/registryAccess/registryAccess.html +++ b/app/components/registryAccess/registryAccess.html @@ -3,7 +3,7 @@ - Registries > {{ registry.Name }} > Access management + Registries > {{ registry.Name }} > Access management diff --git a/app/components/secret/secret.html b/app/components/secret/secret.html index 8c9d10e18..fb349ebbb 100644 --- a/app/components/secret/secret.html +++ b/app/components/secret/secret.html @@ -6,7 +6,7 @@ - Secrets > {{ secret.Name }} + Secrets > {{ secret.Name }} diff --git a/app/components/secrets/secrets.html b/app/components/secrets/secrets.html index 215ac1796..abd9ba6eb 100644 --- a/app/components/secrets/secrets.html +++ b/app/components/secrets/secrets.html @@ -63,6 +63,6 @@ - + diff --git a/app/components/service/includes/ports.html b/app/components/service/includes/ports.html index 7367bfbb7..95e4b232d 100644 --- a/app/components/service/includes/ports.html +++ b/app/components/service/includes/ports.html @@ -63,7 +63,7 @@ - - + diff --git a/app/components/sidebar/sidebar.html b/app/components/sidebar/sidebar.html index b4a590722..3d8d6a2c3 100644 --- a/app/components/sidebar/sidebar.html +++ b/app/components/sidebar/sidebar.html @@ -58,7 +58,7 @@
  • Network & published ports
  • Resource limits & reservations
  • Placement constraints
  • +
  • Placement preferences
  • Restart policy
  • Update configuration
  • Service labels
  • @@ -152,6 +153,7 @@

    Service specification

    +
    diff --git a/app/components/service/serviceController.js b/app/components/service/serviceController.js index a69c2fb78..83b3d9353 100644 --- a/app/components/service/serviceController.js +++ b/app/components/service/serviceController.js @@ -1,6 +1,6 @@ angular.module('service', []) -.controller('ServiceController', ['$q', '$scope', '$stateParams', '$state', '$location', '$timeout', '$anchorScroll', 'ServiceService', 'Secret', 'SecretHelper', 'Service', 'ServiceHelper', 'TaskService', 'NodeService', 'Notifications', 'Pagination', 'ModalService', 'ControllerDataPipeline', -function ($q, $scope, $stateParams, $state, $location, $timeout, $anchorScroll, ServiceService, Secret, SecretHelper, Service, ServiceHelper, TaskService, NodeService, Notifications, Pagination, ModalService, ControllerDataPipeline) { +.controller('ServiceController', ['$q', '$scope', '$stateParams', '$state', '$location', '$timeout', '$anchorScroll', 'ServiceService', 'Secret', 'SecretHelper', 'Service', 'ServiceHelper', 'LabelHelper', 'TaskService', 'NodeService', 'Notifications', 'Pagination', 'ModalService', 'ControllerDataPipeline', +function ($q, $scope, $stateParams, $state, $location, $timeout, $anchorScroll, ServiceService, Secret, SecretHelper, Service, ServiceHelper, LabelHelper, TaskService, NodeService, Notifications, Pagination, ModalService, ControllerDataPipeline) { $scope.state = {}; $scope.state.pagination_count = Pagination.getPaginationCount('service_tasks'); @@ -124,10 +124,24 @@ function ($q, $scope, $stateParams, $state, $location, $timeout, $anchorScroll, updateServiceArray(service, 'ServiceConstraints', service.ServiceConstraints); } }; - $scope.updatePlacementConstraint = function updatePlacementConstraint(service, constraint) { + $scope.updatePlacementConstraint = function(service, constraint) { updateServiceArray(service, 'ServiceConstraints', service.ServiceConstraints); }; + $scope.addPlacementPreference = function(service) { + service.ServicePreferences.push({ strategy: 'spread', value: '' }); + updateServiceArray(service, 'ServicePreferences', service.ServicePreferences); + }; + $scope.removePlacementPreference = function(service, index) { + var removedElement = service.ServicePreferences.splice(index, 1); + if (removedElement !== null) { + updateServiceArray(service, 'ServicePreferences', service.ServicePreferences); + } + }; + $scope.updatePlacementPreference = function(service, constraint) { + updateServiceArray(service, 'ServicePreferences', service.ServicePreferences); + }; + $scope.addPublishedPort = function addPublishedPort(service) { if (!service.Ports) { service.Ports = []; @@ -174,9 +188,9 @@ function ($q, $scope, $stateParams, $state, $location, $timeout, $anchorScroll, $('#loadingViewSpinner').show(); var config = ServiceHelper.serviceToConfig(service.Model); config.Name = service.Name; - config.Labels = translateServiceLabelsToLabels(service.ServiceLabels); - config.TaskTemplate.ContainerSpec.Env = translateEnvironmentVariablesToEnv(service.EnvironmentVariables); - config.TaskTemplate.ContainerSpec.Labels = translateServiceLabelsToLabels(service.ServiceContainerLabels); + config.Labels = LabelHelper.fromKeyValueToLabelHash(service.ServiceLabels); + config.TaskTemplate.ContainerSpec.Env = ServiceHelper.translateEnvironmentVariablesToEnv(service.EnvironmentVariables); + config.TaskTemplate.ContainerSpec.Labels = LabelHelper.fromKeyValueToLabelHash(service.ServiceContainerLabels); config.TaskTemplate.ContainerSpec.Image = service.Image; config.TaskTemplate.ContainerSpec.Secrets = service.ServiceSecrets ? service.ServiceSecrets.map(SecretHelper.secretConfig) : []; @@ -188,6 +202,7 @@ function ($q, $scope, $stateParams, $state, $location, $timeout, $anchorScroll, config.TaskTemplate.Placement = {}; } config.TaskTemplate.Placement.Constraints = ServiceHelper.translateKeyValueToPlacementConstraints(service.ServiceConstraints); + config.TaskTemplate.Placement.Preferences = ServiceHelper.translateKeyValueToPlacementPreferences(service.ServicePreferences); config.TaskTemplate.Resources = { Limits: { @@ -263,11 +278,12 @@ function ($q, $scope, $stateParams, $state, $location, $timeout, $anchorScroll, function translateServiceArrays(service) { service.ServiceSecrets = service.Secrets ? service.Secrets.map(SecretHelper.flattenSecret) : []; - service.EnvironmentVariables = translateEnvironmentVariables(service.Env); - service.ServiceLabels = translateLabelsToServiceLabels(service.Labels); - service.ServiceContainerLabels = translateLabelsToServiceLabels(service.ContainerLabels); + service.EnvironmentVariables = ServiceHelper.translateEnvironmentVariables(service.Env); + service.ServiceLabels = LabelHelper.fromLabelHashToKeyValue(service.Labels); + service.ServiceContainerLabels = LabelHelper.fromLabelHashToKeyValue(service.ContainerLabels); service.ServiceMounts = angular.copy(service.Mounts); - service.ServiceConstraints = translateConstraintsToKeyValue(service.Constraints); + service.ServiceConstraints = ServiceHelper.translateConstraintsToKeyValue(service.Constraints); + service.ServicePreferences = ServiceHelper.translatePreferencesToKeyValue(service.Preferences); } function initView() { @@ -310,7 +326,6 @@ function ($q, $scope, $stateParams, $state, $location, $timeout, $anchorScroll, Notifications.error('Failure', err, 'Unable to retrieve service details'); }) .finally(function final() { - $('#loadingViewSpinner').hide(); }); } @@ -341,80 +356,5 @@ function ($q, $scope, $stateParams, $state, $location, $timeout, $anchorScroll, service.hasChanges = true; } - function translateEnvironmentVariables(env) { - if (env) { - var variables = []; - env.forEach(function(variable) { - var idx = variable.indexOf('='); - var keyValue = [variable.slice(0,idx), variable.slice(idx+1)]; - var originalValue = (keyValue.length > 1) ? keyValue[1] : ''; - variables.push({ key: keyValue[0], value: originalValue, originalKey: keyValue[0], originalValue: originalValue, added: true}); - }); - return variables; - } - return []; - } - function translateEnvironmentVariablesToEnv(env) { - if (env) { - var variables = []; - env.forEach(function(variable) { - if (variable.key && variable.key !== '') { - variables.push(variable.key + '=' + variable.value); - } - }); - return variables; - } - return []; - } - - function translateLabelsToServiceLabels(Labels) { - var labels = []; - if (Labels) { - Object.keys(Labels).forEach(function(key) { - labels.push({ key: key, value: Labels[key], originalKey: key, originalValue: Labels[key], added: true}); - }); - } - return labels; - } - function translateServiceLabelsToLabels(labels) { - var Labels = {}; - if (labels) { - labels.forEach(function(label) { - Labels[label.key] = label.value; - }); - } - return Labels; - } - - function translateConstraintsToKeyValue(constraints) { - function getOperator(constraint) { - var indexEquals = constraint.indexOf('=='); - if (indexEquals >= 0) { - return [indexEquals, '==']; - } - return [constraint.indexOf('!='), '!=']; - } - if (constraints) { - var keyValueConstraints = []; - constraints.forEach(function(constraint) { - var operatorIndices = getOperator(constraint); - - var key = constraint.slice(0, operatorIndices[0]); - var operator = operatorIndices[1]; - var value = constraint.slice(operatorIndices[0] + 2); - - keyValueConstraints.push({ - key: key, - value: value, - operator: operator, - originalKey: key, - originalValue: value - }); - }); - return keyValueConstraints; - } - return []; - } - initView(); }]); diff --git a/app/helpers/serviceHelper.js b/app/helpers/serviceHelper.js index 1b2f453ce..bddab33c6 100644 --- a/app/helpers/serviceHelper.js +++ b/app/helpers/serviceHelper.js @@ -1,5 +1,4 @@ -angular.module('portainer.helpers') -.factory('ServiceHelper', [function ServiceHelperFactory() { +angular.module('portainer.helpers').factory('ServiceHelper', [function ServiceHelperFactory() { 'use strict'; return { serviceToConfig: function(service) { @@ -13,17 +12,113 @@ angular.module('portainer.helpers') EndpointSpec: service.Spec.EndpointSpec }; }, + translateKeyValueToPlacementPreferences: function(keyValuePreferences) { + if (keyValuePreferences) { + var preferences = []; + keyValuePreferences.forEach(function(preference) { + if (preference.strategy && preference.strategy !== '' && preference.value && preference.value !== '') { + switch (preference.strategy.toLowerCase()) { + case 'spread': + preferences.push({ + 'Spread': { + 'SpreadDescriptor': preference.value + } + }); + break; + } + } + }); + return preferences; + } + return []; + }, translateKeyValueToPlacementConstraints: function(keyValueConstraints) { if (keyValueConstraints) { var constraints = []; - keyValueConstraints.forEach(function(keyValueConstraint) { - if (keyValueConstraint.key && keyValueConstraint.key !== '' && keyValueConstraint.value && keyValueConstraint.value !== '') { - constraints.push(keyValueConstraint.key + keyValueConstraint.operator + keyValueConstraint.value); + keyValueConstraints.forEach(function(constraint) { + if (constraint.key && constraint.key !== '' && constraint.value && constraint.value !== '') { + constraints.push(constraint.key + constraint.operator + constraint.value); } }); return constraints; } return []; - } + }, + translateEnvironmentVariables: function(env) { + if (env) { + var variables = []; + env.forEach(function(variable) { + var idx = variable.indexOf('='); + var keyValue = [variable.slice(0, idx), variable.slice(idx + 1)]; + var originalValue = (keyValue.length > 1) ? keyValue[1] : ''; + variables.push({ + key: keyValue[0], + value: originalValue, + originalKey: keyValue[0], + originalValue: originalValue, + added: true + }); + }); + return variables; + } + return []; + }, + translateEnvironmentVariablesToEnv: function(env) { + if (env) { + var variables = []; + env.forEach(function(variable) { + if (variable.key && variable.key !== '') { + variables.push(variable.key + '=' + variable.value); + } + }); + return variables; + } + return []; + }, + translatePreferencesToKeyValue: function(preferences) { + if (preferences) { + var keyValuePreferences = []; + preferences.forEach(function(preference) { + if (preference.Spread) { + keyValuePreferences.push({ + strategy: 'Spread', + value: preference.Spread.SpreadDescriptor + }); + } + }); + return keyValuePreferences; + } + return []; + }, + translateConstraintsToKeyValue: function(constraints) { + function getOperator(constraint) { + var indexEquals = constraint.indexOf('=='); + if (indexEquals >= 0) { + return [indexEquals, '==']; + } + return [constraint.indexOf('!='), '!=']; + } + if (constraints) { + var keyValueConstraints = []; + constraints.forEach(function(constraint) { + var operatorIndices = getOperator(constraint); + + var key = constraint.slice(0, operatorIndices[0]); + var operator = operatorIndices[1]; + var value = constraint.slice(operatorIndices[0] + 2); + + keyValueConstraints.push({ + key: key, + value: value, + operator: operator, + originalKey: key, + originalValue: value + }); + }); + return keyValueConstraints; + } + return []; + } + }; }]); diff --git a/app/models/docker/service.js b/app/models/docker/service.js index 766a4aaee..5b2a7f53e 100644 --- a/app/models/docker/service.js +++ b/app/models/docker/service.js @@ -41,27 +41,37 @@ function ServiceViewModel(data, runningTasks, nodes) { this.RestartWindow = 0; } this.Constraints = data.Spec.TaskTemplate.Placement ? data.Spec.TaskTemplate.Placement.Constraints || [] : []; + this.Preferences = data.Spec.TaskTemplate.Placement ? data.Spec.TaskTemplate.Placement.Preferences || [] : []; + this.Platforms = data.Spec.TaskTemplate.Placement ? data.Spec.TaskTemplate.Placement.Platforms || [] : []; this.Labels = data.Spec.Labels; var containerSpec = data.Spec.TaskTemplate.ContainerSpec; if (containerSpec) { this.ContainerLabels = containerSpec.Labels; - this.Env = containerSpec.Env; - this.Mounts = containerSpec.Mounts || []; - this.User = containerSpec.User; - this.Dir = containerSpec.Dir; this.Command = containerSpec.Command; this.Arguments = containerSpec.Args; + this.Hostname = containerSpec.Hostname; + this.Env = containerSpec.Env; + this.Dir = containerSpec.Dir; + this.User = containerSpec.User; + this.Groups = containerSpec.Groups; + this.TTY = containerSpec.TTY; + this.OpenStdin = containerSpec.OpenStdin; + this.ReadOnly = containerSpec.ReadOnly; + this.Mounts = containerSpec.Mounts || []; + this.StopSignal = containerSpec.StopSignal; + this.StopGracePeriod = containerSpec.StopGracePeriod; + this.HealthCheck = containerSpec.HealthCheck || {}; + this.Hosts = containerSpec.Hosts; + this.DNSConfig = containerSpec.DNSConfig; this.Secrets = containerSpec.Secrets; } if (data.Endpoint) { this.Ports = data.Endpoint.Ports; } - this.Mounts = []; - if (data.Spec.TaskTemplate.ContainerSpec.Mounts) { - this.Mounts = data.Spec.TaskTemplate.ContainerSpec.Mounts; - } + this.LogDriver = data.Spec.TaskTemplate.LogDriver; + this.Runtime = data.Spec.TaskTemplate.Runtime; this.VirtualIPs = data.Endpoint ? data.Endpoint.VirtualIPs : []; @@ -75,6 +85,8 @@ function ServiceViewModel(data, runningTasks, nodes) { this.UpdateFailureAction = 'pause'; } + this.RollbackConfig = data.Spec.RollbackConfig; + this.Checked = false; this.Scale = false; this.EditName = false; From b23943e30bcfaad9cc6fe6ab35530517ed4ffe6e Mon Sep 17 00:00:00 2001 From: 1138-4EB <1138-4EB@users.noreply.github.com> Date: Tue, 11 Jul 2017 09:30:25 +0200 Subject: [PATCH 18/34] refactor(build-system): reduce gruntfile verbosity, drop grunt-if, allow custom build (#939) --- build.sh | 96 +++------ build/build_in_container.sh | 10 + gruntfile.js | 394 +++++++----------------------------- package.json | 1 - 4 files changed, 115 insertions(+), 386 deletions(-) create mode 100755 build/build_in_container.sh diff --git a/build.sh b/build.sh index a7a0a10e4..3219679e4 100755 --- a/build.sh +++ b/build.sh @@ -1,79 +1,49 @@ #!/usr/bin/env bash ARCHIVE_BUILD_FOLDER="/tmp/portainer-builds" -VERSION=$1 -if [[ $# -ne 1 ]] ; then - echo "Usage: $(basename $0) " - exit 1 -fi - -# parameters platform, architecture +# parameter: "platform-architecture" function build_and_push_images() { - PLATFORM=$1 - ARCH=$2 - - docker build -t portainer/portainer:${PLATFORM}-${ARCH}-${VERSION} -f build/linux/Dockerfile . - docker push portainer/portainer:${PLATFORM}-${ARCH}-${VERSION} - docker build -t portainer/portainer:${PLATFORM}-${ARCH} -f build/linux/Dockerfile . - docker push portainer/portainer:${PLATFORM}-${ARCH} + docker build -t "portainer/portainer:$1-${VERSION}" -f build/linux/Dockerfile . + docker tag "portainer/portainer:$1-${VERSION}" "portainer/portainer:$1" + docker push "portainer/portainer:$1-${VERSION}" + docker push "portainer/portainer:$1" } -# parameters: platform, architecture +# parameter: "platform-architecture" function build_archive() { - PLATFORM=$1 - ARCH=$2 - - BUILD_FOLDER=${ARCHIVE_BUILD_FOLDER}/${PLATFORM}-${ARCH} - + BUILD_FOLDER="${ARCHIVE_BUILD_FOLDER}/$1" rm -rf ${BUILD_FOLDER} && mkdir -pv ${BUILD_FOLDER}/portainer mv dist/* ${BUILD_FOLDER}/portainer/ cd ${BUILD_FOLDER} - tar cvpfz portainer-${VERSION}-${PLATFORM}-${ARCH}.tar.gz portainer - mv portainer-${VERSION}-${PLATFORM}-${ARCH}.tar.gz ${ARCHIVE_BUILD_FOLDER}/ + tar cvpfz "portainer-${VERSION}-$1.tar.gz" portainer + mv "portainer-${VERSION}-$1.tar.gz" ${ARCHIVE_BUILD_FOLDER}/ cd - } -mkdir -pv /tmp/portainer-builds +function build_all() { + mkdir -pv "${ARCHIVE_BUILD_FOLDER}" + for tag in $@; do + grunt "release:`echo "$tag" | tr '-' ':'`" + name="portainer"; if [ "$(echo "$tag" | cut -c1)" = "w" ]; then name="${name}.exe"; fi + mv dist/portainer-$tag* dist/$name + if [ `echo $tag | cut -d \- -f 1` == 'linux' ]; then build_and_push_images "$tag"; fi + build_archive "$tag" + done + docker rmi $(docker images -q -f dangling=true) +} -PLATFORM="linux" -ARCH="amd64" -grunt release-${PLATFORM}-${ARCH} -build_and_push_images ${PLATFORM} ${ARCH} -build_archive ${PLATFORM} ${ARCH} +if [[ $# -ne 1 ]] ; then + echo "Usage: $(basename $0) " + echo " $(basename $0) \"echo 'Custom' && \"" + exit 1 +else + VERSION="$1" + if [ `echo "$@" | cut -c1-4` == 'echo' ]; then + bash -c "$@"; + else + build_all 'linux-amd64 linux-386 linux-arm linux-arm64 linux-ppc64le darwin-amd64 windows-amd64' + exit 0 + fi +fi -PLATFORM="linux" -ARCH="386" -grunt release-${PLATFORM}-${ARCH} -build_and_push_images ${PLATFORM} ${ARCH} -build_archive ${PLATFORM} ${ARCH} - -PLATFORM="linux" -ARCH="arm" -grunt release-${PLATFORM}-${ARCH} -build_and_push_images ${PLATFORM} ${ARCH} -build_archive ${PLATFORM} ${ARCH} - -PLATFORM="linux" -ARCH="arm64" -grunt release-${PLATFORM}-${ARCH} -build_and_push_images ${PLATFORM} ${ARCH} -build_archive ${PLATFORM} ${ARCH} - -PLATFORM="linux" -ARCH="ppc64le" -grunt release-${PLATFORM}-${ARCH} -build_and_push_images ${PLATFORM} ${ARCH} -build_archive ${PLATFORM} ${ARCH} - -PLATFORM="darwin" -ARCH="amd64" -grunt release-${PLATFORM}-${ARCH} -build_archive ${PLATFORM} ${ARCH} - -PLATFORM="windows" -ARCH="amd64" -grunt release-${PLATFORM}-${ARCH} -build_archive ${PLATFORM} ${ARCH} - -exit 0 diff --git a/build/build_in_container.sh b/build/build_in_container.sh new file mode 100755 index 000000000..a313dcb59 --- /dev/null +++ b/build/build_in_container.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +binary="portainer-$1-$2" + +mkdir -p dist + +docker run -tv $(pwd)/api:/src -e BUILD_GOOS="$1" -e BUILD_GOARCH="$2" portainer/golang-builder:cross-platform /src/cmd/portainer + +mv "api/cmd/portainer/$binary" dist/ +#sha256sum "dist/$binary" > portainer-checksum.txt diff --git a/gruntfile.js b/gruntfile.js index ce105e653..70ad61d4c 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -11,218 +11,93 @@ module.exports = function (grunt) { grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-html2js'); grunt.loadNpmTasks('grunt-shell'); - grunt.loadNpmTasks('grunt-if'); grunt.loadNpmTasks('grunt-filerev'); grunt.loadNpmTasks('grunt-usemin'); grunt.loadNpmTasks('grunt-replace'); grunt.loadNpmTasks('grunt-config'); grunt.loadNpmTasks('grunt-postcss'); - grunt.registerTask('default', ['eslint', 'build']); + grunt.registerTask('before-copy', [ + 'html2js', + 'useminPrepare:release', + 'concat', + 'postcss:build', + 'clean:tmpl', + 'replace', + 'uglify' + ]); + grunt.registerTask('after-copy', [ + 'filerev', + 'usemin', + 'clean:tmp' + ]); + grunt.registerTask('build-webapp', [ + 'config:prod', + 'clean:all', + 'before-copy', + 'copy:assets', + 'after-copy' + ]); grunt.registerTask('build', [ 'config:dev', 'clean:app', - 'if:linuxAmd64BinaryNotExist', + 'shell:buildBinary:linux:amd64', 'html2js', 'useminPrepare:dev', 'concat', 'clean:tmpl', 'replace', 'copy', - 'filerev', - 'usemin', - 'clean:tmp' - ]); - grunt.registerTask('build-webapp', [ - 'config:prod', - 'clean:all', - 'html2js', - 'useminPrepare:release', - 'concat', - 'postcss:build', - 'clean:tmpl', - 'replace', - 'uglify', - 'copy:assets', - 'filerev', - 'usemin', - 'clean:tmp' - ]); - grunt.registerTask('release-linux-386', [ - 'config:prod', - 'clean:all', - 'if:linux386BinaryNotExist', - 'html2js', - 'useminPrepare:release', - 'concat', - 'postcss:build', - 'clean:tmpl', - 'replace', - 'uglify', - 'copy:assets', - 'filerev', - 'usemin', - 'clean:tmp' - ]); - grunt.registerTask('release-linux-amd64', [ - 'config:prod', - 'clean:all', - 'if:linuxAmd64BinaryNotExist', - 'html2js', - 'useminPrepare:release', - 'concat', - 'postcss:build', - 'clean:tmpl', - 'replace', - 'uglify', - 'copy:assets', - 'filerev', - 'usemin', - 'clean:tmp' - ]); - grunt.registerTask('release-linux-arm', [ - 'config:prod', - 'clean:all', - 'if:linuxArmBinaryNotExist', - 'html2js', - 'useminPrepare:release', - 'concat', - 'postcss:build', - 'clean:tmpl', - 'replace', - 'uglify', - 'copy', - 'filerev', - 'usemin', - 'clean:tmp' - ]); - grunt.registerTask('release-linux-arm64', [ - 'config:prod', - 'clean:all', - 'if:linuxArm64BinaryNotExist', - 'html2js', - 'useminPrepare:release', - 'concat', - 'postcss:build', - 'clean:tmpl', - 'replace', - 'uglify', - 'copy', - 'filerev', - 'usemin', - 'clean:tmp' - ]); - grunt.registerTask('release-linux-ppc64le', [ - 'config:prod', - 'clean:all', - 'if:linuxPpc64leBinaryNotExist', - 'html2js', - 'useminPrepare:release', - 'concat', - 'postcss:build', - 'clean:tmpl', - 'replace', - 'uglify', - 'copy', - 'filerev', - 'usemin', - 'clean:tmp' - ]); - grunt.registerTask('release-windows-amd64', [ - 'config:prod', - 'clean:all', - 'if:windowsAmd64BinaryNotExist', - 'html2js', - 'useminPrepare:release', - 'concat', - 'postcss:build', - 'clean:tmpl', - 'replace', - 'uglify', - 'copy', - 'filerev', - 'usemin', - 'clean:tmp' - ]); - grunt.registerTask('release-darwin-amd64', [ - 'config:prod', - 'clean:all', - 'if:darwinAmd64BinaryNotExist', - 'html2js', - 'useminPrepare:release', - 'concat', - 'postcss:build', - 'clean:tmpl', - 'replace', - 'uglify', - 'copy', - 'filerev', - 'usemin', - 'clean:tmp' + 'after-copy' ]); + grunt.task.registerTask('release', 'release::', function(p, a) { + grunt.task.run(['config:prod', 'clean:all', 'shell:buildBinary:'+p+':'+a, 'before-copy', 'copy:assets', 'after-copy' ]); + }); grunt.registerTask('lint', ['eslint']); - grunt.registerTask('run', ['if:linuxAmd64BinaryNotExist', 'build', 'shell:buildImage', 'shell:run']); - grunt.registerTask('run-dev', ['if:linuxAmd64BinaryNotExist', 'shell:run', 'watch:build']); + grunt.registerTask('run-dev', ['build', 'shell:run', 'watch:build']); grunt.registerTask('clear', ['clean:app']); - // Print a timestamp (useful for when watching) - grunt.registerTask('timestamp', function () { - grunt.log.subhead(Date()); - }); - // Project configuration. grunt.initConfig({ distdir: 'dist', pkg: grunt.file.readJSON('package.json'), config: { - dev: { - options: { - variables: { - 'environment': 'development' - } - } - }, - prod: { - options: { - variables: { - 'environment': 'production' - } - } - } + dev: { options: { variables: { 'environment': 'development' }}}, + prod: { options: { variables: { 'environment': 'production' }}} }, src: { js: ['app/**/*.js', '!app/**/*.spec.js'], jsTpl: ['<%= distdir %>/templates/**/*.js'], jsVendor: [ + 'bower_components/angular-multi-select/isteven-multi-select.js', + 'bower_components/bootbox.js/bootbox.js', 'bower_components/jquery/dist/jquery.min.js', 'bower_components/bootstrap/dist/js/bootstrap.min.js', 'bower_components/Chart.js/Chart.min.js', - 'bower_components/lodash/dist/lodash.min.js', - 'bower_components/splitargs/src/splitargs.js', 'bower_components/filesize/lib/filesize.min.js', + 'bower_components/lodash/dist/lodash.min.js', 'bower_components/moment/min/moment.min.js', - 'bower_components/xterm.js/dist/xterm.js', - 'bower_components/bootbox.js/bootbox.js', - 'bower_components/angular-multi-select/isteven-multi-select.js', + 'bower_components/splitargs/src/splitargs.js', 'bower_components/toastr/toastr.min.js', + 'bower_components/xterm.js/dist/xterm.js', 'assets/js/legend.js' // Not a bower package ], html: ['index.html'], tpl: ['app/components/**/*.html', 'app/directives/**/*.html'], css: ['assets/css/app.css'], cssVendor: [ + 'bower_components/angular-multi-select/isteven-multi-select.css', + 'bower_components/angular-ui-select/dist/select.min.css', 'bower_components/bootstrap/dist/css/bootstrap.css', 'bower_components/font-awesome/css/font-awesome.min.css', 'bower_components/rdash-ui/dist/css/rdash.min.css', - 'bower_components/angular-ui-select/dist/select.min.css', - 'bower_components/xterm.js/dist/xterm.css', - 'bower_components/angular-multi-select/isteven-multi-select.css', - 'bower_components/toastr/toastr.min.css' + 'bower_components/toastr/toastr.min.css', + 'bower_components/xterm.js/dist/xterm.css' ] }, clean: { all: ['<%= distdir %>/*'], - app: ['<%= distdir %>/*', '!<%= distdir %>/portainer'], + app: ['<%= distdir %>/*', '!<%= distdir %>/portainer*'], tmpl: ['<%= distdir %>/templates'], tmp: ['<%= distdir %>/js/*', '!<%= distdir %>/js/app.*.js', '<%= distdir %>/css/*', '!<%= distdir %>/css/app.*.css'] }, @@ -273,24 +148,21 @@ module.exports = function (grunt) { }, assets: { files: [ - {dest: '<%= distdir %>/fonts/', src: '*.{ttf,woff,woff2,eof,svg}', expand: true, cwd: 'bower_components/bootstrap/fonts/'}, - {dest: '<%= distdir %>/fonts/', src: '*.{ttf,woff,woff2,eof,svg}', expand: true, cwd: 'bower_components/font-awesome/fonts/'}, - {dest: '<%= distdir %>/fonts/', src: '*.{ttf,woff,woff2,eof,svg}', expand: true, cwd: 'bower_components/rdash-ui/dist/fonts/'}, - { - dest: '<%= distdir %>/images/', - src: ['**'], - expand: true, - cwd: 'assets/images/' - }, - {dest: '<%= distdir %>/ico', src: '**', expand: true, cwd: 'assets/ico'} + {dest: '<%= distdir %>/fonts/', src: '*.{ttf,woff,woff2,eof,svg}', expand: true, cwd: 'bower_components/bootstrap/fonts/'}, + {dest: '<%= distdir %>/fonts/', src: '*.{ttf,woff,woff2,eof,svg}', expand: true, cwd: 'bower_components/font-awesome/fonts/'}, + {dest: '<%= distdir %>/fonts/', src: '*.{ttf,woff,woff2,eof,svg}', expand: true, cwd: 'bower_components/rdash-ui/dist/fonts/'}, + {dest: '<%= distdir %>/images/', src: '**', expand: true, cwd: 'assets/images/'}, + {dest: '<%= distdir %>/ico', src: '**', expand: true, cwd: 'assets/ico'} ] } }, + eslint: { + src: ['gruntfile.js', '<%= src.js %>'], + options: { configFile: '.eslintrc.yml' } + }, html2js: { app: { - options: { - base: '.' - }, + options: { base: '.' }, src: ['<%= src.tpl %>'], dest: '<%= distdir %>/templates/app.js', module: '<%= pkg.name %>.templates' @@ -301,26 +173,23 @@ module.exports = function (grunt) { src: ['<%= src.cssVendor %>', '<%= src.css %>'], dest: '<%= distdir %>/css/<%= pkg.name %>.css' }, - dist: { - options: { - process: true - }, - src: ['<%= src.js %>', '<%= src.jsTpl %>'], - dest: '<%= distdir %>/js/<%= pkg.name %>.js' - }, vendor: { src: ['<%= src.jsVendor %>'], dest: '<%= distdir %>/js/vendor.js' }, + dist: { + options: { process: true }, + src: ['<%= src.js %>', '<%= src.jsTpl %>'], + dest: '<%= distdir %>/js/<%= pkg.name %>.js' + }, index: { + options: { process: true }, src: ['index.html'], - dest: '<%= distdir %>/index.html', - options: { - process: true - } + dest: '<%= distdir %>/index.html' }, angular: { - src: ['bower_components/angular/angular.min.js', + src: [ + 'bower_components/angular/angular.min.js', 'bower_components/angular-sanitize/angular-sanitize.min.js', 'bower_components/angular-cookies/angular-cookies.min.js', 'bower_components/angular-local-storage/dist/angular-local-storage.min.js', @@ -341,16 +210,12 @@ module.exports = function (grunt) { dest: '<%= distdir %>/js/<%= pkg.name %>.js' }, vendor: { - options: { - preserveComments: 'some' // Preserve license comments - }, + options: { preserveComments: 'some' }, // Preserve license comments src: ['<%= src.jsVendor %>'], dest: '<%= distdir %>/js/vendor.js' }, angular: { - options: { - preserveComments: 'some' // Preserve license comments - }, + options: { preserveComments: 'some' }, // Preserve license comments src: ['<%= concat.angular.src %>'], dest: '<%= distdir %>/js/angular.js' } @@ -368,142 +233,27 @@ module.exports = function (grunt) { } }, watch: { - all: { - files: ['<%= src.js %>', '<%= src.css %>', '<%= src.tpl %>', '<%= src.html %>'], - tasks: ['default', 'timestamp'] - }, build: { files: ['<%= src.js %>', '<%= src.css %>', '<%= src.tpl %>', '<%= src.html %>'], tasks: ['build'] - }, - buildSwarm: { - files: ['<%= src.js %>', '<%= src.css %>', '<%= src.tpl %>', '<%= src.html %>'], - tasks: ['build', 'shell:buildImage', 'shell:runSwarm', 'shell:cleanImages'] - }, - buildSsl: { - files: ['<%= src.js %>', '<%= src.css %>', '<%= src.tpl %>', '<%= src.html %>'], - tasks: ['build', 'shell:buildImage', 'shell:runSsl', 'shell:cleanImages'] } }, - eslint: { - src: ['gruntfile.js', '<%= src.js %>'], - options: { - configFile: '.eslintrc.yml' - } - }, shell: { - buildImage: { - command: 'docker build --rm -t portainer -f build/linux/Dockerfile .' - }, - buildLinuxAmd64Binary: { - command: [ - 'docker run --rm -v $(pwd)/api:/src portainer/golang-builder /src/cmd/portainer', - 'shasum api/cmd/portainer/portainer > portainer-checksum.txt', - 'mkdir -p dist', - 'mv api/cmd/portainer/portainer dist/' - ].join(' && ') - }, - buildLinux386Binary: { - command: [ - 'docker run --rm -v $(pwd)/api:/src -e BUILD_GOOS="linux" -e BUILD_GOARCH="386" portainer/golang-builder:cross-platform /src/cmd/portainer', - 'shasum api/cmd/portainer/portainer-linux-386 > portainer-checksum.txt', - 'mkdir -p dist', - 'mv api/cmd/portainer/portainer-linux-386 dist/portainer' - ].join(' && ') - }, - buildLinuxArmBinary: { - command: [ - 'docker run --rm -v $(pwd)/api:/src -e BUILD_GOOS="linux" -e BUILD_GOARCH="arm" portainer/golang-builder:cross-platform /src/cmd/portainer', - 'shasum api/cmd/portainer/portainer-linux-arm > portainer-checksum.txt', - 'mkdir -p dist', - 'mv api/cmd/portainer/portainer-linux-arm dist/portainer' - ].join(' && ') - }, - buildLinuxArm64Binary: { - command: [ - 'docker run --rm -v $(pwd)/api:/src -e BUILD_GOOS="linux" -e BUILD_GOARCH="arm64" portainer/golang-builder:cross-platform /src/cmd/portainer', - 'shasum api/cmd/portainer/portainer-linux-arm64 > portainer-checksum.txt', - 'mkdir -p dist', - 'mv api/cmd/portainer/portainer-linux-arm64 dist/portainer' - ].join(' && ') - }, - buildLinuxPpc64leBinary: { - command: [ - 'docker run --rm -v $(pwd)/api:/src -e BUILD_GOOS="linux" -e BUILD_GOARCH="ppc64le" portainer/golang-builder:cross-platform /src/cmd/portainer', - 'shasum api/cmd/portainer/portainer-linux-ppc64le > portainer-checksum.txt', - 'mkdir -p dist', - 'mv api/cmd/portainer/portainer-linux-ppc64le dist/portainer' - ].join(' && ') - }, - buildDarwinAmd64Binary: { - command: [ - 'docker run --rm -v $(pwd)/api:/src -e BUILD_GOOS="darwin" -e BUILD_GOARCH="amd64" portainer/golang-builder:cross-platform /src/cmd/portainer', - 'shasum api/cmd/portainer/portainer-darwin-amd64 > portainer-checksum.txt', - 'mkdir -p dist', - 'mv api/cmd/portainer/portainer-darwin-amd64 dist/portainer' - ].join(' && ') - }, - buildWindowsAmd64Binary: { - command: [ - 'docker run --rm -v $(pwd)/api:/src -e BUILD_GOOS="windows" -e BUILD_GOARCH="amd64" portainer/golang-builder:cross-platform /src/cmd/portainer', - 'shasum api/cmd/portainer/portainer-windows-amd64 > portainer-checksum.txt', - 'mkdir -p dist', - 'mv api/cmd/portainer/portainer-windows-amd64 dist/portainer.exe' - ].join(' && ') - }, + buildBinary: { + command: function (p, a) { + var binfile = 'dist/portainer-'+p+'-'+a; + if (grunt.file.isFile( ( p === 'windows' ) ? binfile+'.exe' : binfile )) { + return 'echo \'BinaryExists\''; + } else { + return 'build/build_in_container.sh ' + p + ' ' + a; + } + } + }, run: { command: [ - 'docker stop portainer', - 'docker rm portainer', - 'docker run -d -p 9000:9000 -v $(pwd)/dist:/app -v /tmp/portainer:/data -v /var/run/docker.sock:/var/run/docker.sock:z --name portainer centurylink/ca-certs /app/portainer --no-analytics -a /app' + 'docker rm -f portainer', + 'docker run -d -p 9000:9000 -v $(pwd)/dist:/app -v /tmp/portainer:/data -v /var/run/docker.sock:/var/run/docker.sock:z --name portainer centurylink/ca-certs /app/portainer-linux-amd64 --no-analytics -a /app' ].join(';') - }, - cleanImages: { - command: 'docker rmi $(docker images -q -f dangling=true)' - } - }, - 'if': { - linuxAmd64BinaryNotExist: { - options: { - executable: 'dist/portainer' - }, - ifFalse: ['shell:buildLinuxAmd64Binary'] - }, - linux386BinaryNotExist: { - options: { - executable: 'dist/portainer' - }, - ifFalse: ['shell:buildLinux386Binary'] - }, - linuxArmBinaryNotExist: { - options: { - executable: 'dist/portainer' - }, - ifFalse: ['shell:buildLinuxArmBinary'] - }, - linuxArm64BinaryNotExist: { - options: { - executable: 'dist/portainer' - }, - ifFalse: ['shell:buildLinuxArm64Binary'] - }, - linuxPpc64leBinaryNotExist: { - options: { - executable: 'dist/portainer' - }, - ifFalse: ['shell:buildLinuxPpc64leBinary'] - }, - darwinAmd64BinaryNotExist: { - options: { - executable: 'dist/portainer' - }, - ifFalse: ['shell:buildDarwinAmd64Binary'] - }, - windowsAmd64BinaryNotExist: { - options: { - executable: 'dist/portainer.exe' - }, - ifFalse: ['shell:buildWindowsAmd64Binary'] } }, replace: { diff --git a/package.json b/package.json index 345e5f4a8..b346a6f45 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,6 @@ "grunt-contrib-watch": "~0.3.1", "grunt-filerev": "^2.3.1", "grunt-html2js": "~0.1.0", - "grunt-if": "^0.1.5", "grunt-karma": "~0.4.4", "grunt-postcss": "^0.8.0", "grunt-replace": "^1.0.1", From bc4b0a0b35ea58ab91d7aeded0f31c4afa4a5351 Mon Sep 17 00:00:00 2001 From: Konstantin Azizov Date: Tue, 11 Jul 2017 10:56:28 +0300 Subject: [PATCH 19/34] feat(images): display unused images tags (#1009) --- app/components/images/images.html | 17 +++++++++++++++-- app/components/networks/networks.html | 2 +- app/components/service/includes/tasks.html | 2 +- app/components/volumes/volumes.html | 2 +- app/models/docker/image.js | 1 + app/rest/docker/system.js | 3 ++- app/services/docker/imageService.js | 14 +++++++++++--- app/services/docker/systemService.js | 4 ++++ assets/css/app.css | 5 +++++ 9 files changed, 41 insertions(+), 9 deletions(-) diff --git a/app/components/images/images.html b/app/components/images/images.html index 2b078d290..fc2fbf06d 100644 --- a/app/components/images/images.html +++ b/app/components/images/images.html @@ -70,6 +70,17 @@
    + + + + +
    @@ -110,9 +121,11 @@ - + - {{ image.Id|truncate:20}} + + {{ image.Id|truncate:20}} + Unused {{ tag }} diff --git a/app/components/networks/networks.html b/app/components/networks/networks.html index aa0286ac9..5d8c110be 100644 --- a/app/components/networks/networks.html +++ b/app/components/networks/networks.html @@ -134,7 +134,7 @@ {{ network.Name|truncate:40}} - {{ network.Id }} + {{ network.Id|truncate:20 }} {{ network.Scope }} {{ network.Driver }} {{ network.IPAM.Driver }} diff --git a/app/components/service/includes/tasks.html b/app/components/service/includes/tasks.html index 49a2845a8..e182e4ad3 100644 --- a/app/components/service/includes/tasks.html +++ b/app/components/service/includes/tasks.html @@ -49,7 +49,7 @@ - {{ task.Id }} + {{ task.Id }} {{ task.Status.State }} {{ task.Slot }} {{ task.NodeId | tasknodename: nodes }} diff --git a/app/components/volumes/volumes.html b/app/components/volumes/volumes.html index c3fe10427..d0eb0f744 100644 --- a/app/components/volumes/volumes.html +++ b/app/components/volumes/volumes.html @@ -83,7 +83,7 @@ - {{ volume.Id|truncate:25 }} + {{ volume.Id|truncate:25 }} {{ volume.Driver }} {{ volume.Mountpoint | truncate:52 }} diff --git a/app/models/docker/image.js b/app/models/docker/image.js index d050cfe9f..d6188a37d 100644 --- a/app/models/docker/image.js +++ b/app/models/docker/image.js @@ -3,6 +3,7 @@ function ImageViewModel(data) { this.Tag = data.Tag; this.Repository = data.Repository; this.Created = data.Created; + this.Containers = data.dataUsage.Containers; this.Checked = false; this.RepoTags = data.RepoTags; this.VirtualSize = data.VirtualSize; diff --git a/app/rest/docker/system.js b/app/rest/docker/system.js index ccc3e61e2..242f1f120 100644 --- a/app/rest/docker/system.js +++ b/app/rest/docker/system.js @@ -12,6 +12,7 @@ angular.module('portainer.rest') method: 'GET', params: { action: 'events', since: '@since', until: '@until' }, isArray: true, transformResponse: jsonObjectsToArrayHandler }, - auth: { method: 'POST', params: { action: 'auth' } } + auth: { method: 'POST', params: { action: 'auth' } }, + dataUsage: { method: 'GET', params: { action: 'system/df' } } }); }]); diff --git a/app/services/docker/imageService.js b/app/services/docker/imageService.js index 2966abdaf..8a1e49fb8 100644 --- a/app/services/docker/imageService.js +++ b/app/services/docker/imageService.js @@ -1,5 +1,5 @@ angular.module('portainer.services') -.factory('ImageService', ['$q', 'Image', 'ImageHelper', 'RegistryService', 'HttpRequestHelper', function ImageServiceFactory($q, Image, ImageHelper, RegistryService, HttpRequestHelper) { +.factory('ImageService', ['$q', 'Image', 'ImageHelper', 'RegistryService', 'HttpRequestHelper', 'SystemService', function ImageServiceFactory($q, Image, ImageHelper, RegistryService, HttpRequestHelper, SystemService) { 'use strict'; var service = {}; @@ -22,9 +22,17 @@ angular.module('portainer.services') service.images = function() { var deferred = $q.defer(); - Image.query({}).$promise + + $q.all({ + dataUsage: SystemService.dataUsage(), + images: Image.query({}).$promise + }) .then(function success(data) { - var images = data.map(function (item) { + var images = data.images.map(function(item) { + item.dataUsage = data.dataUsage.Images.find(function(usage) { + return item.Id === usage.Id; + }); + return new ImageViewModel(item); }); deferred.resolve(images); diff --git a/app/services/docker/systemService.js b/app/services/docker/systemService.js index 4b4bf85b2..4c017c0be 100644 --- a/app/services/docker/systemService.js +++ b/app/services/docker/systemService.js @@ -40,6 +40,10 @@ angular.module('portainer.services') return deferred.promise; }; + + service.dataUsage = function () { + return System.dataUsage().$promise; + }; return service; }]); diff --git a/assets/css/app.css b/assets/css/app.css index 3eb9b9c4e..e9b61a130 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -513,3 +513,8 @@ ul.sidebar .sidebar-list .sidebar-sublist a.active { opacity: 0.9; } /*!toaster override*/ + +.monospaced { + font-family: monospace; + font-weight: 600; +} \ No newline at end of file From 344eee098de6646f5f4ba6f77158b068a9147b96 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Tue, 11 Jul 2017 16:52:39 +0200 Subject: [PATCH 20/34] chore(deps): update xtermjs version (#1012) --- .../containerConsole/containerConsoleController.js | 6 +++--- bower.json | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/components/containerConsole/containerConsoleController.js b/app/components/containerConsole/containerConsoleController.js index 14469cc05..9a221f365 100644 --- a/app/components/containerConsole/containerConsoleController.js +++ b/app/components/containerConsole/containerConsoleController.js @@ -5,7 +5,7 @@ function ($scope, $stateParams, Container, Image, Exec, $timeout, EndpointProvid $scope.state.loaded = false; $scope.state.connected = false; $scope.formValues = {}; - + var socket, term; // Ensure the socket is closed before leaving the view @@ -40,7 +40,7 @@ function ($scope, $stateParams, Container, Image, Exec, $timeout, EndpointProvid $('#loadConsoleSpinner').show(); var termWidth = Math.round($('#terminal-container').width() / 8.2); var termHeight = 30; - var command = $scope.formValues.isCustomCommand ? + var command = $scope.formValues.isCustomCommand ? $scope.formValues.customCommand : $scope.formValues.command; var execConfig = { id: $stateParams.id, @@ -107,7 +107,7 @@ function ($scope, $stateParams, Container, Image, Exec, $timeout, EndpointProvid term.on('data', function (data) { socket.send(data); }); - term.open(document.getElementById('terminal-container')); + term.open(document.getElementById('terminal-container'), true); term.resize(width, height); term.setOption('cursorBlink', true); diff --git a/bower.json b/bower.json index 3c5b32df4..ab0ebcc00 100644 --- a/bower.json +++ b/bower.json @@ -43,13 +43,13 @@ "lodash": "4.12.0", "rdash-ui": "1.0.*", "moment": "~2.14.1", - "xterm.js": "~2.0.1", "font-awesome": "~4.7.0", "ng-file-upload": "~12.2.13", "splitargs": "~0.2.0", "bootbox.js": "bootbox#^4.4.0", "angular-multi-select": "~4.0.0", - "toastr": "~2.1.3" + "toastr": "~2.1.3", + "xterm.js": "~2.8.1" }, "resolutions": { "angular": "1.5.11" From a86464169211e84f456d41558db9b75babfd7d35 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Wed, 12 Jul 2017 09:51:51 +0200 Subject: [PATCH 21/34] refactor(UAC): refactor common views to components (#1013) --- app/app.js | 2 - .../accessControlForm/accessControlForm.html | 126 -------------- .../accessControlFormController.js | 55 ------ .../accessControlPanelController.js | 158 ------------------ app/components/container/container.html | 8 +- .../container/containerController.js | 5 +- .../createContainerController.js | 9 +- .../createContainer/createcontainer.html | 2 +- .../createService/createServiceController.js | 9 +- .../createService/createservice.html | 2 +- .../createVolume/createVolumeController.js | 9 +- app/components/createVolume/createvolume.html | 2 +- app/components/service/service.html | 8 +- app/components/service/serviceController.js | 5 +- app/components/templates/templates.html | 7 +- .../templates/templatesController.js | 9 +- app/components/volume/volume.html | 8 +- app/components/volume/volumeController.js | 5 +- .../por-access-control-form.js | 12 ++ .../porAccessControlForm.html | 124 ++++++++++++++ .../porAccessControlFormController.js | 76 +++++++++ .../porAccessControlFormModel.js | 6 + .../por-access-control-panel.js | 12 ++ .../porAccessControlPanel.html} | 88 +++++----- .../porAccessControlPanelController.js | 156 +++++++++++++++++ app/helpers/resourceControlHelper.js | 12 +- app/services/api/resourceControlService.js | 14 +- app/services/controllerDataPipeline.js | 36 ---- app/services/formValidator.js | 12 +- gruntfile.js | 14 +- 30 files changed, 510 insertions(+), 481 deletions(-) delete mode 100644 app/components/common/accessControlForm/accessControlForm.html delete mode 100644 app/components/common/accessControlForm/accessControlFormController.js delete mode 100644 app/components/common/accessControlPanel/accessControlPanelController.js create mode 100644 app/directives/accessControlForm/por-access-control-form.js create mode 100644 app/directives/accessControlForm/porAccessControlForm.html create mode 100644 app/directives/accessControlForm/porAccessControlFormController.js create mode 100644 app/directives/accessControlForm/porAccessControlFormModel.js create mode 100644 app/directives/accessControlPanel/por-access-control-panel.js rename app/{components/common/accessControlPanel/accessControlPanel.html => directives/accessControlPanel/porAccessControlPanel.html} (58%) create mode 100644 app/directives/accessControlPanel/porAccessControlPanelController.js delete mode 100644 app/services/controllerDataPipeline.js diff --git a/app/app.js b/app/app.js index b81cb3a5a..ab07489e5 100644 --- a/app/app.js +++ b/app/app.js @@ -20,8 +20,6 @@ angular.module('portainer', [ 'portainer.services', 'auth', 'dashboard', - 'common.accesscontrol.panel', - 'common.accesscontrol.form', 'container', 'containerConsole', 'containerLogs', diff --git a/app/components/common/accessControlForm/accessControlForm.html b/app/components/common/accessControlForm/accessControlForm.html deleted file mode 100644 index f3bd32b7b..000000000 --- a/app/components/common/accessControlForm/accessControlForm.html +++ /dev/null @@ -1,126 +0,0 @@ -
    -
    - Access control -
    - -
    -
    - - -
    -
    - - -
    -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    - - -
    -
    - - - You have not yet created any team. Head over the teams view to manage user teams. - - - -
    -
    - - -
    -
    - - - You have not yet created any user. Head over the users view to manage users. - - - -
    -
    - -
    diff --git a/app/components/common/accessControlForm/accessControlFormController.js b/app/components/common/accessControlForm/accessControlFormController.js deleted file mode 100644 index 2656b900e..000000000 --- a/app/components/common/accessControlForm/accessControlFormController.js +++ /dev/null @@ -1,55 +0,0 @@ -angular.module('common.accesscontrol.form', []) -.controller('AccessControlFormController', ['$q', '$scope', '$state', 'UserService', 'ResourceControlService', 'Notifications', 'Authentication', 'ModalService', 'ControllerDataPipeline', -function ($q, $scope, $state, UserService, ResourceControlService, Notifications, Authentication, ModalService, ControllerDataPipeline) { - - $scope.availableTeams = []; - $scope.availableUsers = []; - - $scope.formValues = { - enableAccessControl: true, - Ownership_Teams: [], - Ownership_Users: [], - Ownership: 'private' - }; - - $scope.synchronizeFormData = function() { - ControllerDataPipeline.setAccessControlFormData($scope.formValues.enableAccessControl, - $scope.formValues.Ownership, $scope.formValues.Ownership_Users, $scope.formValues.Ownership_Teams); - }; - - function initAccessControlForm() { - $('#loadingViewSpinner').show(); - - var userDetails = Authentication.getUserDetails(); - var isAdmin = userDetails.role === 1 ? true: false; - $scope.isAdmin = isAdmin; - - if (isAdmin) { - $scope.formValues.Ownership = 'administrators'; - } - - $q.all({ - availableTeams: UserService.userTeams(userDetails.ID), - availableUsers: isAdmin ? UserService.users(false) : [] - }) - .then(function success(data) { - $scope.availableUsers = data.availableUsers; - - var availableTeams = data.availableTeams; - $scope.availableTeams = availableTeams; - if (!isAdmin && availableTeams.length === 1) { - $scope.formValues.Ownership_Teams = availableTeams; - } - - $scope.synchronizeFormData(); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to retrieve access control information'); - }) - .finally(function final() { - $('#loadingViewSpinner').hide(); - }); - } - - initAccessControlForm(); -}]); diff --git a/app/components/common/accessControlPanel/accessControlPanelController.js b/app/components/common/accessControlPanel/accessControlPanelController.js deleted file mode 100644 index 8283b6d97..000000000 --- a/app/components/common/accessControlPanel/accessControlPanelController.js +++ /dev/null @@ -1,158 +0,0 @@ -angular.module('common.accesscontrol.panel', []) -.controller('AccessControlPanelController', ['$q', '$scope', '$state', 'UserService', 'ResourceControlService', 'Notifications', 'Authentication', 'ModalService', 'ControllerDataPipeline', 'FormValidator', -function ($q, $scope, $state, UserService, ResourceControlService, Notifications, Authentication, ModalService, ControllerDataPipeline, FormValidator) { - - $scope.state = { - displayAccessControlPanel: false, - canEditOwnership: false, - editOwnership: false, - formValidationError: '' - }; - - $scope.formValues = { - Ownership: 'public', - Ownership_Users: [], - Ownership_Teams: [] - }; - - $scope.authorizedUsers = []; - $scope.availableUsers = []; - $scope.authorizedTeams = []; - $scope.availableTeams = []; - - $scope.confirmUpdateOwnership = function (force) { - if (!validateForm()) { - return; - } - ModalService.confirmAccessControlUpdate(function (confirmed) { - if(!confirmed) { return; } - updateOwnership(); - }); - }; - - function processOwnershipFormValues() { - var userIds = []; - angular.forEach($scope.formValues.Ownership_Users, function(user) { - userIds.push(user.Id); - }); - var teamIds = []; - angular.forEach($scope.formValues.Ownership_Teams, function(team) { - teamIds.push(team.Id); - }); - var administratorsOnly = $scope.formValues.Ownership === 'administrators' ? true : false; - - return { - ownership: $scope.formValues.Ownership, - authorizedUserIds: administratorsOnly ? [] : userIds, - authorizedTeamIds: administratorsOnly ? [] : teamIds, - administratorsOnly: administratorsOnly - }; - } - - function validateForm() { - $scope.state.formValidationError = ''; - var error = ''; - - var accessControlData = { - ownership: $scope.formValues.Ownership, - authorizedUsers: $scope.formValues.Ownership_Users, - authorizedTeams: $scope.formValues.Ownership_Teams - }; - var isAdmin = $scope.isAdmin; - error = FormValidator.validateAccessControl(accessControlData, isAdmin); - if (error) { - $scope.state.formValidationError = error; - return false; - } - return true; - } - - function updateOwnership() { - $('#loadingViewSpinner').show(); - - var accessControlData = ControllerDataPipeline.getAccessControlData(); - var resourceId = accessControlData.resourceId; - var ownershipParameters = processOwnershipFormValues(); - - ResourceControlService.applyResourceControlChange(accessControlData.resourceType, resourceId, - $scope.resourceControl, ownershipParameters) - .then(function success(data) { - Notifications.success('Access control successfully updated'); - $state.reload(); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to update access control'); - }) - .finally(function final() { - $('#loadingViewSpinner').hide(); - }); - } - - function initAccessControlPanel() { - $('#loadingViewSpinner').show(); - - var userDetails = Authentication.getUserDetails(); - var isAdmin = userDetails.role === 1 ? true: false; - var userId = userDetails.ID; - $scope.isAdmin = isAdmin; - - var accessControlData = ControllerDataPipeline.getAccessControlData(); - var resourceControl = accessControlData.resourceControl; - $scope.resourceType = accessControlData.resourceType; - $scope.resourceControl = resourceControl; - - if (isAdmin) { - if (resourceControl) { - $scope.formValues.Ownership = resourceControl.Ownership === 'private' ? 'restricted' : resourceControl.Ownership; - } else { - $scope.formValues.Ownership = 'public'; - } - } else { - $scope.formValues.Ownership = 'public'; - } - - ResourceControlService.retrieveOwnershipDetails(resourceControl) - .then(function success(data) { - $scope.authorizedUsers = data.authorizedUsers; - $scope.authorizedTeams = data.authorizedTeams; - return ResourceControlService.retrieveUserPermissionsOnResource(userId, isAdmin, resourceControl); - }) - .then(function success(data) { - $scope.state.canEditOwnership = data.isPartOfRestrictedUsers || data.isLeaderOfAnyRestrictedTeams; - $scope.state.canChangeOwnershipToTeam = data.isPartOfRestrictedUsers; - - return $q.all({ - availableUsers: isAdmin ? UserService.users(false) : [], - availableTeams: isAdmin || data.isPartOfRestrictedUsers ? UserService.userTeams(userId) : [] - }); - }) - .then(function success(data) { - $scope.availableUsers = data.availableUsers; - angular.forEach($scope.availableUsers, function(user) { - var found = _.find($scope.authorizedUsers, { Id: user.Id }); - if (found) { - user.selected = true; - } - }); - $scope.availableTeams = data.availableTeams; - angular.forEach(data.availableTeams, function(team) { - var found = _.find($scope.authorizedTeams, { Id: team.Id }); - if (found) { - team.selected = true; - } - }); - if (data.availableTeams.length === 1) { - $scope.formValues.Ownership_Teams.push(data.availableTeams[0]); - } - $scope.state.displayAccessControlPanel = true; - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to retrieve access control information'); - }) - .finally(function final() { - $('#loadingViewSpinner').hide(); - }); - } - - initAccessControlPanel(); -}]); diff --git a/app/components/container/container.html b/app/components/container/container.html index f2ee6c857..c3890b856 100644 --- a/app/components/container/container.html +++ b/app/components/container/container.html @@ -87,7 +87,13 @@
    -
    + + + +
    diff --git a/app/components/container/containerController.js b/app/components/container/containerController.js index f7e964e33..22acf056c 100644 --- a/app/components/container/containerController.js +++ b/app/components/container/containerController.js @@ -1,6 +1,6 @@ angular.module('container', []) -.controller('ContainerController', ['$scope', '$state','$stateParams', '$filter', 'Container', 'ContainerCommit', 'ContainerService', 'ImageHelper', 'Network', 'Notifications', 'Pagination', 'ModalService', 'ControllerDataPipeline', -function ($scope, $state, $stateParams, $filter, Container, ContainerCommit, ContainerService, ImageHelper, Network, Notifications, Pagination, ModalService, ControllerDataPipeline) { +.controller('ContainerController', ['$scope', '$state','$stateParams', '$filter', 'Container', 'ContainerCommit', 'ContainerService', 'ImageHelper', 'Network', 'Notifications', 'Pagination', 'ModalService', +function ($scope, $state, $stateParams, $filter, Container, ContainerCommit, ContainerService, ImageHelper, Network, Notifications, Pagination, ModalService) { $scope.activityTime = 0; $scope.portBindings = []; $scope.config = { @@ -19,7 +19,6 @@ function ($scope, $state, $stateParams, $filter, Container, ContainerCommit, Con Container.get({id: $stateParams.id}, function (d) { var container = new ContainerDetailsViewModel(d); $scope.container = container; - ControllerDataPipeline.setAccessControlData('container', $stateParams.id, container.ResourceControl); $scope.container.edit = false; $scope.container.newContainerName = $filter('trimcontainername')(container.Name); diff --git a/app/components/createContainer/createContainerController.js b/app/components/createContainer/createContainerController.js index b578a8bc0..048e3f2ce 100644 --- a/app/components/createContainer/createContainerController.js +++ b/app/components/createContainer/createContainerController.js @@ -1,8 +1,8 @@ // @@OLD_SERVICE_CONTROLLER: this service should be rewritten to use services. // See app/components/templates/templatesController.js as a reference. angular.module('createContainer', []) -.controller('CreateContainerController', ['$q', '$scope', '$state', '$stateParams', '$filter', 'Container', 'ContainerHelper', 'Image', 'ImageHelper', 'Volume', 'Network', 'ResourceControlService', 'Authentication', 'Notifications', 'ContainerService', 'ImageService', 'ControllerDataPipeline', 'FormValidator', -function ($q, $scope, $state, $stateParams, $filter, Container, ContainerHelper, Image, ImageHelper, Volume, Network, ResourceControlService, Authentication, Notifications, ContainerService, ImageService, ControllerDataPipeline, FormValidator) { +.controller('CreateContainerController', ['$q', '$scope', '$state', '$stateParams', '$filter', 'Container', 'ContainerHelper', 'Image', 'ImageHelper', 'Volume', 'Network', 'ResourceControlService', 'Authentication', 'Notifications', 'ContainerService', 'ImageService', 'FormValidator', +function ($q, $scope, $state, $stateParams, $filter, Container, ContainerHelper, Image, ImageHelper, Volume, Network, ResourceControlService, Authentication, Notifications, ContainerService, ImageService, FormValidator) { $scope.formValues = { alwaysPull: true, @@ -13,7 +13,8 @@ function ($q, $scope, $state, $stateParams, $filter, Container, ContainerHelper, Labels: [], ExtraHosts: [], IPv4: '', - IPv6: '' + IPv6: '', + AccessControlData: new AccessControlFormData() }; $scope.state = { @@ -285,7 +286,7 @@ function ($q, $scope, $state, $stateParams, $filter, Container, ContainerHelper, $scope.create = function () { $('#createContainerSpinner').show(); - var accessControlData = ControllerDataPipeline.getAccessControlFormData(); + var accessControlData = $scope.formValues.AccessControlData; var userDetails = Authentication.getUserDetails(); var isAdmin = userDetails.role === 1 ? true : false; diff --git a/app/components/createContainer/createcontainer.html b/app/components/createContainer/createcontainer.html index 8600fe092..356f244f5 100644 --- a/app/components/createContainer/createcontainer.html +++ b/app/components/createContainer/createcontainer.html @@ -98,7 +98,7 @@
    -
    +
    diff --git a/app/components/createService/createServiceController.js b/app/components/createService/createServiceController.js index 846e188f1..3ede157b1 100644 --- a/app/components/createService/createServiceController.js +++ b/app/components/createService/createServiceController.js @@ -1,8 +1,8 @@ // @@OLD_SERVICE_CONTROLLER: this service should be rewritten to use services. // See app/components/templates/templatesController.js as a reference. angular.module('createService', []) -.controller('CreateServiceController', ['$q', '$scope', '$state', 'Service', 'ServiceHelper', 'SecretHelper', 'SecretService', 'VolumeService', 'NetworkService', 'ImageHelper', 'LabelHelper', 'Authentication', 'ResourceControlService', 'Notifications', 'ControllerDataPipeline', 'FormValidator', 'RegistryService', 'HttpRequestHelper', -function ($q, $scope, $state, Service, ServiceHelper, SecretHelper, SecretService, VolumeService, NetworkService, ImageHelper, LabelHelper, Authentication, ResourceControlService, Notifications, ControllerDataPipeline, FormValidator, RegistryService, HttpRequestHelper) { +.controller('CreateServiceController', ['$q', '$scope', '$state', 'Service', 'ServiceHelper', 'SecretHelper', 'SecretService', 'VolumeService', 'NetworkService', 'ImageHelper', 'LabelHelper', 'Authentication', 'ResourceControlService', 'Notifications', 'FormValidator', 'RegistryService', 'HttpRequestHelper', +function ($q, $scope, $state, Service, ServiceHelper, SecretHelper, SecretService, VolumeService, NetworkService, ImageHelper, LabelHelper, Authentication, ResourceControlService, Notifications, FormValidator, RegistryService, HttpRequestHelper) { $scope.formValues = { Name: '', @@ -26,7 +26,8 @@ function ($q, $scope, $state, Service, ServiceHelper, SecretHelper, SecretServic PlacementPreferences: [], UpdateDelay: 0, FailureAction: 'pause', - Secrets: [] + Secrets: [], + AccessControlData: new AccessControlFormData() }; $scope.state = { @@ -286,7 +287,7 @@ function ($q, $scope, $state, Service, ServiceHelper, SecretHelper, SecretServic $scope.create = function createService() { $('#createServiceSpinner').show(); - var accessControlData = ControllerDataPipeline.getAccessControlFormData(); + var accessControlData = $scope.formValues.AccessControlData; var userDetails = Authentication.getUserDetails(); var isAdmin = userDetails.role === 1 ? true : false; diff --git a/app/components/createService/createservice.html b/app/components/createService/createservice.html index f96f834ef..49e796069 100644 --- a/app/components/createService/createservice.html +++ b/app/components/createService/createservice.html @@ -101,7 +101,7 @@
    -
    +
    diff --git a/app/components/createVolume/createVolumeController.js b/app/components/createVolume/createVolumeController.js index a7b5f3d85..7107fb077 100644 --- a/app/components/createVolume/createVolumeController.js +++ b/app/components/createVolume/createVolumeController.js @@ -1,10 +1,11 @@ angular.module('createVolume', []) -.controller('CreateVolumeController', ['$scope', '$state', 'VolumeService', 'SystemService', 'ResourceControlService', 'Authentication', 'Notifications', 'ControllerDataPipeline', 'FormValidator', -function ($scope, $state, VolumeService, SystemService, ResourceControlService, Authentication, Notifications, ControllerDataPipeline, FormValidator) { +.controller('CreateVolumeController', ['$scope', '$state', 'VolumeService', 'SystemService', 'ResourceControlService', 'Authentication', 'Notifications', 'FormValidator', +function ($scope, $state, VolumeService, SystemService, ResourceControlService, Authentication, Notifications, FormValidator) { $scope.formValues = { Driver: 'local', - DriverOptions: [] + DriverOptions: [], + AccessControlData: new AccessControlFormData() }; $scope.state = { @@ -40,8 +41,8 @@ function ($scope, $state, VolumeService, SystemService, ResourceControlService, var driver = $scope.formValues.Driver; var driverOptions = $scope.formValues.DriverOptions; var volumeConfiguration = VolumeService.createVolumeConfiguration(name, driver, driverOptions); + var accessControlData = $scope.formValues.AccessControlData; var userDetails = Authentication.getUserDetails(); - var accessControlData = ControllerDataPipeline.getAccessControlFormData(); var isAdmin = userDetails.role === 1 ? true : false; if (!validateForm(accessControlData, isAdmin)) { diff --git a/app/components/createVolume/createvolume.html b/app/components/createVolume/createvolume.html index 7444cac54..2ca08ec84 100644 --- a/app/components/createVolume/createvolume.html +++ b/app/components/createVolume/createvolume.html @@ -65,7 +65,7 @@
    -
    +
    diff --git a/app/components/service/service.html b/app/components/service/service.html index fd88628c2..cec143309 100644 --- a/app/components/service/service.html +++ b/app/components/service/service.html @@ -125,7 +125,13 @@
    -
    + + + +

    diff --git a/app/components/service/serviceController.js b/app/components/service/serviceController.js index 83b3d9353..b61dbb256 100644 --- a/app/components/service/serviceController.js +++ b/app/components/service/serviceController.js @@ -1,6 +1,6 @@ angular.module('service', []) -.controller('ServiceController', ['$q', '$scope', '$stateParams', '$state', '$location', '$timeout', '$anchorScroll', 'ServiceService', 'Secret', 'SecretHelper', 'Service', 'ServiceHelper', 'LabelHelper', 'TaskService', 'NodeService', 'Notifications', 'Pagination', 'ModalService', 'ControllerDataPipeline', -function ($q, $scope, $stateParams, $state, $location, $timeout, $anchorScroll, ServiceService, Secret, SecretHelper, Service, ServiceHelper, LabelHelper, TaskService, NodeService, Notifications, Pagination, ModalService, ControllerDataPipeline) { +.controller('ServiceController', ['$q', '$scope', '$stateParams', '$state', '$location', '$timeout', '$anchorScroll', 'ServiceService', 'Secret', 'SecretHelper', 'Service', 'ServiceHelper', 'LabelHelper', 'TaskService', 'NodeService', 'Notifications', 'Pagination', 'ModalService', +function ($q, $scope, $stateParams, $state, $location, $timeout, $anchorScroll, ServiceService, Secret, SecretHelper, Service, ServiceHelper, LabelHelper, TaskService, NodeService, Notifications, Pagination, ModalService) { $scope.state = {}; $scope.state.pagination_count = Pagination.getPaginationCount('service_tasks'); @@ -299,7 +299,6 @@ function ($q, $scope, $stateParams, $state, $location, $timeout, $anchorScroll, translateServiceArrays(service); $scope.service = service; - ControllerDataPipeline.setAccessControlData('service', $stateParams.id, service.ResourceControl); originalService = angular.copy(service); return $q.all({ diff --git a/app/components/templates/templates.html b/app/components/templates/templates.html index 8b273388c..c4a99b153 100644 --- a/app/components/templates/templates.html +++ b/app/components/templates/templates.html @@ -68,7 +68,7 @@
    -
    +
    @@ -202,13 +202,14 @@
    - + When using Swarm, we recommend deploying containers in a shared network. Looks like you don't have any shared network, head over the networks view to create one. - + App templates cannot be deployed as Swarm Mode services for the moment. You can still use them to quickly deploy containers on the Docker host. + {{ state.formValidationError }}
    diff --git a/app/components/templates/templatesController.js b/app/components/templates/templatesController.js index e4ab1f384..5b62b5b1b 100644 --- a/app/components/templates/templatesController.js +++ b/app/components/templates/templatesController.js @@ -1,6 +1,6 @@ angular.module('templates', []) -.controller('TemplatesController', ['$scope', '$q', '$state', '$stateParams', '$anchorScroll', '$filter', 'ContainerService', 'ContainerHelper', 'ImageService', 'NetworkService', 'TemplateService', 'TemplateHelper', 'VolumeService', 'Notifications', 'Pagination', 'ResourceControlService', 'Authentication', 'ControllerDataPipeline', 'FormValidator', -function ($scope, $q, $state, $stateParams, $anchorScroll, $filter, ContainerService, ContainerHelper, ImageService, NetworkService, TemplateService, TemplateHelper, VolumeService, Notifications, Pagination, ResourceControlService, Authentication, ControllerDataPipeline, FormValidator) { +.controller('TemplatesController', ['$scope', '$q', '$state', '$stateParams', '$anchorScroll', '$filter', 'ContainerService', 'ContainerHelper', 'ImageService', 'NetworkService', 'TemplateService', 'TemplateHelper', 'VolumeService', 'Notifications', 'Pagination', 'ResourceControlService', 'Authentication', 'FormValidator', +function ($scope, $q, $state, $stateParams, $anchorScroll, $filter, ContainerService, ContainerHelper, ImageService, NetworkService, TemplateService, TemplateHelper, VolumeService, Notifications, Pagination, ResourceControlService, Authentication, FormValidator) { $scope.state = { selectedTemplate: null, showAdvancedOptions: false, @@ -14,7 +14,8 @@ function ($scope, $q, $state, $stateParams, $anchorScroll, $filter, ContainerSer $scope.formValues = { network: '', - name: '' + name: '', + AccessControlData: new AccessControlFormData() }; $scope.addVolume = function () { @@ -49,7 +50,7 @@ function ($scope, $q, $state, $stateParams, $anchorScroll, $filter, ContainerSer $('#createContainerSpinner').show(); var userDetails = Authentication.getUserDetails(); - var accessControlData = ControllerDataPipeline.getAccessControlFormData(); + var accessControlData = $scope.formValues.AccessControlData; var isAdmin = userDetails.role === 1 ? true : false; if (!validateForm(accessControlData, isAdmin)) { diff --git a/app/components/volume/volume.html b/app/components/volume/volume.html index 79f00f7d8..fcdc8f52b 100644 --- a/app/components/volume/volume.html +++ b/app/components/volume/volume.html @@ -47,7 +47,13 @@
    -
    + + + +
    diff --git a/app/components/volume/volumeController.js b/app/components/volume/volumeController.js index 1a0b5cc01..2753d1b19 100644 --- a/app/components/volume/volumeController.js +++ b/app/components/volume/volumeController.js @@ -1,6 +1,6 @@ angular.module('volume', []) -.controller('VolumeController', ['$scope', '$state', '$stateParams', 'VolumeService', 'Notifications', 'ControllerDataPipeline', -function ($scope, $state, $stateParams, VolumeService, Notifications, ControllerDataPipeline) { +.controller('VolumeController', ['$scope', '$state', '$stateParams', 'VolumeService', 'Notifications', +function ($scope, $state, $stateParams, VolumeService, Notifications) { $scope.removeVolume = function removeVolume() { $('#loadingViewSpinner').show(); @@ -22,7 +22,6 @@ function ($scope, $state, $stateParams, VolumeService, Notifications, Controller VolumeService.volume($stateParams.id) .then(function success(data) { var volume = data; - ControllerDataPipeline.setAccessControlData('volume', volume.Id, volume.ResourceControl); $scope.volume = volume; }) .catch(function error(err) { diff --git a/app/directives/accessControlForm/por-access-control-form.js b/app/directives/accessControlForm/por-access-control-form.js new file mode 100644 index 000000000..4f71b3acf --- /dev/null +++ b/app/directives/accessControlForm/por-access-control-form.js @@ -0,0 +1,12 @@ +angular.module('portainer').component('porAccessControlForm', { + templateUrl: 'app/directives/accessControlForm/porAccessControlForm.html', + controller: 'porAccessControlFormController', + bindings: { + // This object will be populated with the form data. + // Model reference in porAccessControlFromModel.js + formData: '=', + // Optional. An existing resource control object that will be used to set + // the default values of the component. + resourceControl: '<' + } +}); diff --git a/app/directives/accessControlForm/porAccessControlForm.html b/app/directives/accessControlForm/porAccessControlForm.html new file mode 100644 index 000000000..a8978408f --- /dev/null +++ b/app/directives/accessControlForm/porAccessControlForm.html @@ -0,0 +1,124 @@ +
    +
    + Access control +
    + +
    +
    + + +
    +
    + + +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    + + +
    +
    + + + You have not yet created any team. Head over the teams view to manage user teams. + + + +
    +
    + + +
    +
    + + + You have not yet created any user. Head over the users view to manage users. + + + +
    +
    + +
    diff --git a/app/directives/accessControlForm/porAccessControlFormController.js b/app/directives/accessControlForm/porAccessControlFormController.js new file mode 100644 index 000000000..cd40cc10c --- /dev/null +++ b/app/directives/accessControlForm/porAccessControlFormController.js @@ -0,0 +1,76 @@ +angular.module('portainer') +.controller('porAccessControlFormController', ['$q', 'UserService', 'Notifications', 'Authentication', 'ResourceControlService', +function ($q, UserService, Notifications, Authentication, ResourceControlService) { + var ctrl = this; + + ctrl.availableTeams = []; + ctrl.availableUsers = []; + + function setOwnership(resourceControl, isAdmin) { + if (isAdmin && resourceControl.Ownership === 'private') { + ctrl.formData.Ownership = 'restricted'; + } else { + ctrl.formData.Ownership = resourceControl.Ownership; + } + } + + function setAuthorizedUsersAndTeams(authorizedUsers, authorizedTeams) { + angular.forEach(ctrl.availableUsers, function(user) { + var found = _.find(authorizedUsers, { Id: user.Id }); + if (found) { + user.selected = true; + } + }); + + angular.forEach(ctrl.availableTeams, function(team) { + var found = _.find(authorizedTeams, { Id: team.Id }); + if (found) { + team.selected = true; + } + }); + } + + function initComponent() { + $('#loadingViewSpinner').show(); + + var userDetails = Authentication.getUserDetails(); + var isAdmin = userDetails.role === 1 ? true: false; + ctrl.isAdmin = isAdmin; + + if (isAdmin) { + ctrl.formData.Ownership = 'administrators'; + } + + $q.all({ + availableTeams: UserService.userTeams(userDetails.ID), + availableUsers: isAdmin ? UserService.users(false) : [] + }) + .then(function success(data) { + ctrl.availableUsers = data.availableUsers; + + var availableTeams = data.availableTeams; + ctrl.availableTeams = availableTeams; + if (!isAdmin && availableTeams.length === 1) { + ctrl.formData.AuthorizedTeams = availableTeams; + } + + return $q.when(ctrl.resourceControl && ResourceControlService.retrieveOwnershipDetails(ctrl.resourceControl)); + }) + .then(function success(data) { + if (data) { + var authorizedUsers = data.authorizedUsers; + var authorizedTeams = data.authorizedTeams; + setOwnership(ctrl.resourceControl, isAdmin); + setAuthorizedUsersAndTeams(authorizedUsers, authorizedTeams); + } + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to retrieve access control information'); + }) + .finally(function final() { + $('#loadingViewSpinner').hide(); + }); + } + + initComponent(); +}]); diff --git a/app/directives/accessControlForm/porAccessControlFormModel.js b/app/directives/accessControlForm/porAccessControlFormModel.js new file mode 100644 index 000000000..1e973786f --- /dev/null +++ b/app/directives/accessControlForm/porAccessControlFormModel.js @@ -0,0 +1,6 @@ +function AccessControlFormData() { + this.AccessControlEnabled = true; + this.Ownership = 'private'; + this.AuthorizedUsers = []; + this.AuthorizedTeams = []; +} diff --git a/app/directives/accessControlPanel/por-access-control-panel.js b/app/directives/accessControlPanel/por-access-control-panel.js new file mode 100644 index 000000000..6bde5f128 --- /dev/null +++ b/app/directives/accessControlPanel/por-access-control-panel.js @@ -0,0 +1,12 @@ +angular.module('portainer').component('porAccessControlPanel', { + templateUrl: 'app/directives/accessControlPanel/porAccessControlPanel.html', + controller: 'porAccessControlPanelController', + bindings: { + // The component will display information about this resource control object. + resourceControl: '=', + // This component is usually displayed inside a resource-details view. + // This variable specifies the type of the associated resource. + // Accepted values: 'container', 'service' or 'volume'. + resourceType: '<' + } +}); diff --git a/app/components/common/accessControlPanel/accessControlPanel.html b/app/directives/accessControlPanel/porAccessControlPanel.html similarity index 58% rename from app/components/common/accessControlPanel/accessControlPanel.html rename to app/directives/accessControlPanel/porAccessControlPanel.html index f8031ad27..221b18515 100644 --- a/app/components/common/accessControlPanel/accessControlPanel.html +++ b/app/directives/accessControlPanel/porAccessControlPanel.html @@ -1,5 +1,5 @@ -
    -
    +
    +
    @@ -9,63 +9,63 @@ Ownership - - + + public - - {{ resourceControl.Ownership }} - - - + + {{ $ctrl.resourceControl.Ownership }} + + + - + - Access control on this resource is inherited from the following service: {{ resourceControl.ResourceId | truncate }} + Access control on this resource is inherited from the following service: {{ $ctrl.resourceControl.ResourceId | truncate }} - + - Access control on this resource is inherited from the following container: {{ resourceControl.ResourceId | truncate }} + Access control on this resource is inherited from the following container: {{ $ctrl.resourceControl.ResourceId | truncate }} - + Authorized users - {{user.Username}}{{$last ? '' : ', '}} + {{user.Username}}{{$last ? '' : ', '}} - + Authorized teams - {{team.Name}}{{$last ? '' : ', '}} + {{team.Name}}{{$last ? '' : ', '}} - + - Change ownership + Change ownership - +
    -
    - +
    +