diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index f01c9df7e..059a043a6 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -37,6 +37,7 @@ Any other info e.g. Why do you consider this to be a bug? What did you expect to **Technical details:** * Portainer version: +* Portainer Docker image tag (latest/arm/windows...): * Target Docker version (the host/cluster you manage): * Target Swarm version (if applicable): * Platform (windows/linux): diff --git a/.gitignore b/.gitignore index 42c695629..244386e1e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ bower_components dist portainer-checksum.txt api/cmd/portainer/portainer* +.tmp diff --git a/api/cli/cli.go b/api/cli/cli.go index ccd9c1f9a..10f476c9b 100644 --- a/api/cli/cli.go +++ b/api/cli/cli.go @@ -25,14 +25,14 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) { Endpoint: kingpin.Flag("host", "Dockerd endpoint").Short('H').String(), Logo: kingpin.Flag("logo", "URL for the logo displayed in the UI").String(), Labels: pairs(kingpin.Flag("hide-label", "Hide containers with a specific label in the UI").Short('l')), - Addr: kingpin.Flag("bind", "Address and port to serve Portainer").Default(":9000").Short('p').String(), - Assets: kingpin.Flag("assets", "Path to the assets").Default(".").Short('a').String(), - Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default("/data").Short('d').String(), - Templates: kingpin.Flag("templates", "URL to the templates (apps) definitions").Default("https://raw.githubusercontent.com/portainer/templates/master/templates.json").Short('t').String(), - TLSVerify: kingpin.Flag("tlsverify", "TLS support").Default("false").Bool(), - TLSCacert: kingpin.Flag("tlscacert", "Path to the CA").Default("/certs/ca.pem").String(), - TLSCert: kingpin.Flag("tlscert", "Path to the TLS certificate file").Default("/certs/cert.pem").String(), - TLSKey: kingpin.Flag("tlskey", "Path to the TLS key").Default("/certs/key.pem").String(), + Addr: kingpin.Flag("bind", "Address and port to serve Portainer").Default(defaultBindAddress).Short('p').String(), + Assets: kingpin.Flag("assets", "Path to the assets").Default(defaultAssetsDirectory).Short('a').String(), + Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default(defaultDataDirectory).Short('d').String(), + Templates: kingpin.Flag("templates", "URL to the templates (apps) definitions").Default(defaultTemplatesURL).Short('t').String(), + TLSVerify: kingpin.Flag("tlsverify", "TLS support").Default(defaultTLSVerify).Bool(), + TLSCacert: kingpin.Flag("tlscacert", "Path to the CA").Default(defaultTLSCACertPath).String(), + TLSCert: kingpin.Flag("tlscert", "Path to the TLS certificate file").Default(defaultTLSCertPath).String(), + TLSKey: kingpin.Flag("tlskey", "Path to the TLS key").Default(defaultTLSKeyPath).String(), } kingpin.Parse() diff --git a/api/cli/defaults.go b/api/cli/defaults.go new file mode 100644 index 000000000..adf8affbf --- /dev/null +++ b/api/cli/defaults.go @@ -0,0 +1,14 @@ +// +build !windows + +package cli + +const ( + defaultBindAddress = ":9000" + defaultDataDirectory = "/data" + defaultAssetsDirectory = "." + defaultTemplatesURL = "https://raw.githubusercontent.com/portainer/templates/master/templates.json" + defaultTLSVerify = "false" + defaultTLSCACertPath = "/certs/ca.pem" + defaultTLSCertPath = "/certs/cert.pem" + defaultTLSKeyPath = "/certs/key.pem" +) diff --git a/api/cli/defaults_windows.go b/api/cli/defaults_windows.go new file mode 100644 index 000000000..3a4106c74 --- /dev/null +++ b/api/cli/defaults_windows.go @@ -0,0 +1,12 @@ +package cli + +const ( + defaultBindAddress = ":9000" + defaultDataDirectory = "C:\\data" + defaultAssetsDirectory = "." + defaultTemplatesURL = "https://raw.githubusercontent.com/portainer/templates/master/templates.json" + defaultTLSVerify = "false" + defaultTLSCACertPath = "C:\\certs\\ca.pem" + defaultTLSCertPath = "C:\\certs\\cert.pem" + defaultTLSKeyPath = "C:\\certs\\key.pem" +) diff --git a/api/cmd/portainer/main.go b/api/cmd/portainer/main.go index c11f9b34f..e01c304a9 100644 --- a/api/cmd/portainer/main.go +++ b/api/cmd/portainer/main.go @@ -29,6 +29,11 @@ func main() { Logo: *flags.Logo, } + fileService, err := file.NewService(*flags.Data, "") + if err != nil { + log.Fatal(err) + } + var store = bolt.NewStore(*flags.Data) err = store.Open() if err != nil { @@ -41,11 +46,6 @@ func main() { log.Fatal(err) } - fileService, err := file.NewService(*flags.Data) - if err != nil { - log.Fatal(err) - } - var cryptoService portainer.CryptoService = &crypto.Service{} // Initialize the active endpoint from the CLI only if there is no diff --git a/api/file/file.go b/api/file/file.go index cd76acec9..c9ad71a4a 100644 --- a/api/file/file.go +++ b/api/file/file.go @@ -1,13 +1,12 @@ package file import ( - "strconv" - "github.com/portainer/portainer" "io" "os" "path" + "strconv" ) const ( @@ -21,18 +20,26 @@ const ( TLSKeyFile = "key.pem" ) -// Service represents a service for managing files. +// Service represents a service for managing files and directories. type Service struct { + dataStorePath string fileStorePath string } -// NewService initializes a new service. -func NewService(fileStorePath string) (*Service, error) { +// NewService initializes a new service. It creates a data directory and a directory to store files +// inside this directory if they don't exist. +func NewService(dataStorePath, fileStorePath string) (*Service, error) { service := &Service{ - fileStorePath: fileStorePath, + dataStorePath: dataStorePath, + fileStorePath: path.Join(dataStorePath, fileStorePath), } - err := service.createFolderInStoreIfNotExist(TLSStorePath) + err := createDirectoryIfNotExist(dataStorePath, 0755) + if err != nil { + return nil, err + } + + err = service.createDirectoryInStoreIfNotExist(TLSStorePath) if err != nil { return nil, err } @@ -44,7 +51,7 @@ func NewService(fileStorePath string) (*Service, error) { func (service *Service) StoreTLSFile(endpointID portainer.EndpointID, fileType portainer.TLSFileType, r io.Reader) error { ID := strconv.Itoa(int(endpointID)) endpointStorePath := path.Join(TLSStorePath, ID) - err := service.createFolderInStoreIfNotExist(endpointStorePath) + err := service.createDirectoryInStoreIfNotExist(endpointStorePath) if err != nil { return err } @@ -97,12 +104,20 @@ func (service *Service) DeleteTLSFiles(endpointID portainer.EndpointID) error { return nil } -// createFolderInStoreIfNotExist creates a new folder in the file store if it doesn't exists on the file system. -func (service *Service) createFolderInStoreIfNotExist(name string) error { +// createDirectoryInStoreIfNotExist creates a new directory in the file store if it doesn't exists on the file system. +func (service *Service) createDirectoryInStoreIfNotExist(name string) error { path := path.Join(service.fileStorePath, name) + return createDirectoryIfNotExist(path, 0700) +} + +// createDirectoryIfNotExist creates a directory if it doesn't exists on the file system. +func createDirectoryIfNotExist(path string, mode uint32) error { _, err := os.Stat(path) if os.IsNotExist(err) { - os.Mkdir(path, 0600) + err = os.Mkdir(path, os.FileMode(mode)) + if err != nil { + return err + } } else if err != nil { return err } diff --git a/api/http/file_handler.go b/api/http/file_handler.go new file mode 100644 index 000000000..95ebc022c --- /dev/null +++ b/api/http/file_handler.go @@ -0,0 +1,20 @@ +package http + +import "net/http" + +// FileHandler represents an HTTP API handler for managing static files. +type FileHandler struct { + http.Handler +} + +func newFileHandler(assetPath string) *FileHandler { + h := &FileHandler{ + Handler: http.FileServer(http.Dir(assetPath)), + } + return h +} + +func (fileHandler *FileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Cache-Control", "max-age=31536000") + fileHandler.Handler.ServeHTTP(w, r) +} diff --git a/api/http/handler.go b/api/http/handler.go index ca4b15ede..5c88a2805 100644 --- a/api/http/handler.go +++ b/api/http/handler.go @@ -19,7 +19,7 @@ type Handler struct { DockerHandler *DockerHandler WebSocketHandler *WebSocketHandler UploadHandler *UploadHandler - FileHandler http.Handler + FileHandler *FileHandler } const ( diff --git a/api/http/server.go b/api/http/server.go index cd376883b..32975944b 100644 --- a/api/http/server.go +++ b/api/http/server.go @@ -63,7 +63,7 @@ func (server *Server) Start() error { endpointHandler.server = server var uploadHandler = NewUploadHandler(middleWareService) uploadHandler.FileService = server.FileService - var fileHandler = http.FileServer(http.Dir(server.AssetsPath)) + var fileHandler = newFileHandler(server.AssetsPath) server.Handler = &Handler{ AuthHandler: authHandler, diff --git a/api/portainer.go b/api/portainer.go index a2e99e0de..c03679e84 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -118,7 +118,7 @@ type ( const ( // APIVersion is the version number of portainer API. - APIVersion = "1.11.0" + APIVersion = "1.11.1" ) const ( diff --git a/app/app.js b/app/app.js index a070100ed..2c2a58589 100644 --- a/app/app.js +++ b/app/app.js @@ -547,11 +547,11 @@ angular.module('portainer', [ // This is your docker url that the api will use to make requests // You need to set this to the api endpoint without the port i.e. http://192.168.1.9 .constant('DOCKER_PORT', '') // Docker port, leave as an empty string if no port is required. If you have a port, prefix it with a ':' i.e. :4243 - .constant('DOCKER_ENDPOINT', '/api/docker') - .constant('CONFIG_ENDPOINT', '/api/settings') - .constant('AUTH_ENDPOINT', '/api/auth') - .constant('USERS_ENDPOINT', '/api/users') - .constant('ENDPOINTS_ENDPOINT', '/api/endpoints') - .constant('TEMPLATES_ENDPOINT', '/api/templates') + .constant('DOCKER_ENDPOINT', 'api/docker') + .constant('CONFIG_ENDPOINT', 'api/settings') + .constant('AUTH_ENDPOINT', 'api/auth') + .constant('USERS_ENDPOINT', 'api/users') + .constant('ENDPOINTS_ENDPOINT', 'api/endpoints') + .constant('TEMPLATES_ENDPOINT', 'api/templates') .constant('PAGINATION_MAX_ITEMS', 10) - .constant('UI_VERSION', 'v1.11.0'); + .constant('UI_VERSION', 'v1.11.1'); diff --git a/app/components/container/container.html b/app/components/container/container.html index 7a58a335a..a204903d4 100644 --- a/app/components/container/container.html +++ b/app/components/container/container.html @@ -174,6 +174,23 @@ + + Restart policies + + + + + + + + + + +
Name{{ container.HostConfig.RestartPolicy.Name }}
MaximumRetryCount + {{ container.HostConfig.RestartPolicy.MaximumRetryCount }} +
+ + diff --git a/app/components/container/containerController.js b/app/components/container/containerController.js index 7b78aa658..ae3b793e1 100644 --- a/app/components/container/containerController.js +++ b/app/components/container/containerController.js @@ -75,8 +75,8 @@ function ($scope, $state, $stateParams, $filter, Container, ContainerCommit, Ima $scope.commit = function () { $('#createImageSpinner').show(); - var image = _.toLower($scope.config.Image); - var registry = _.toLower($scope.config.Registry); + var image = $scope.config.Image; + var registry = $scope.config.Registry; var imageConfig = ImageHelper.createImageConfigForCommit(image, registry); ContainerCommit.commit({id: $stateParams.id, tag: imageConfig.tag, repo: imageConfig.repo}, function (d) { $('#createImageSpinner').hide(); diff --git a/app/components/containers/containers.html b/app/components/containers/containers.html index 7d1ecca69..367763841 100644 --- a/app/components/containers/containers.html +++ b/app/components/containers/containers.html @@ -37,7 +37,9 @@ - +
+ + State diff --git a/app/components/containers/containersController.js b/app/components/containers/containersController.js index 7705ef770..92fe95a9e 100644 --- a/app/components/containers/containersController.js +++ b/app/components/containers/containersController.js @@ -94,6 +94,15 @@ function ($scope, $filter, Container, ContainerHelper, Info, Settings, Messages, } }; + $scope.selectItems = function (allSelected) { + angular.forEach($scope.state.filteredContainers, function (container) { + if (container.Checked !== allSelected) { + container.Checked = allSelected; + $scope.selectItem(container); + } + }); + }; + $scope.selectItem = function (item) { if (item.Checked) { $scope.state.selectedItemCount++; diff --git a/app/components/createContainer/createContainerController.js b/app/components/createContainer/createContainerController.js index 439b7bf34..be70bdec7 100644 --- a/app/components/createContainer/createContainerController.js +++ b/app/components/createContainer/createContainerController.js @@ -141,7 +141,7 @@ function ($scope, $state, $stateParams, $filter, Config, Info, Container, Contai } function prepareImageConfig(config) { - var image = _.toLower(config.Image); + var image = config.Image; var registry = $scope.formValues.Registry; var imageConfig = ImageHelper.createImageConfigForContainer(image, registry); config.Image = imageConfig.fromImage + ':' + imageConfig.tag; diff --git a/app/components/endpointInit/endpointInit.html b/app/components/endpointInit/endpointInit.html index dbaf4a69d..7d0f46eb5 100644 --- a/app/components/endpointInit/endpointInit.html +++ b/app/components/endpointInit/endpointInit.html @@ -31,9 +31,9 @@
-
- Note: ensure that the Docker socket is bind mounted in the Portainer container at /var/run/docker.sock -
+ + This feature is not available with Docker on Windows yet. +
On Linux / Mac, ensure that you have started Portainer container with the following Docker flag -v "/var/run/docker.sock:/var/run/docker.sock"
diff --git a/app/components/image/imageController.js b/app/components/image/imageController.js index ec4ba5c91..7c474086a 100644 --- a/app/components/image/imageController.js +++ b/app/components/image/imageController.js @@ -21,8 +21,8 @@ function ($scope, $stateParams, $state, Image, ImageHelper, Messages) { $scope.tagImage = function() { $('#loadingViewSpinner').show(); - var image = _.toLower($scope.config.Image); - var registry = _.toLower($scope.config.Registry); + var image = $scope.config.Image; + var registry = $scope.config.Registry; var imageConfig = ImageHelper.createImageConfigForCommit(image, registry); Image.tag({id: $stateParams.id, tag: imageConfig.tag, repo: imageConfig.repo}, function (d) { Messages.send('Image successfully tagged'); diff --git a/app/components/images/images.html b/app/components/images/images.html index 6e44d4e72..65b8577bb 100644 --- a/app/components/images/images.html +++ b/app/components/images/images.html @@ -66,7 +66,9 @@ - +
+ + Id diff --git a/app/components/images/imagesController.js b/app/components/images/imagesController.js index b9be43dc4..fe7c7cf08 100644 --- a/app/components/images/imagesController.js +++ b/app/components/images/imagesController.js @@ -17,6 +17,15 @@ function ($scope, $state, Config, Image, ImageHelper, Messages, Settings) { $scope.sortType = sortType; }; + $scope.selectItems = function (allSelected) { + angular.forEach($scope.state.filteredImages, function (image) { + if (image.Checked !== allSelected) { + image.Checked = allSelected; + $scope.selectItem(image); + } + }); + }; + $scope.selectItem = function (item) { if (item.Checked) { $scope.state.selectedItemCount++; @@ -27,8 +36,8 @@ function ($scope, $state, Config, Image, ImageHelper, Messages, Settings) { $scope.pullImage = function() { $('#pullImageSpinner').show(); - var image = _.toLower($scope.config.Image); - var registry = _.toLower($scope.config.Registry); + var image = $scope.config.Image; + var registry = $scope.config.Registry; var imageConfig = ImageHelper.createImageConfigForContainer(image, registry); Image.create(imageConfig, function (data) { var err = data.length > 0 && data[data.length - 1].hasOwnProperty('error'); diff --git a/app/components/networks/networks.html b/app/components/networks/networks.html index 806640a95..3903365bf 100644 --- a/app/components/networks/networks.html +++ b/app/components/networks/networks.html @@ -68,7 +68,9 @@ - + diff --git a/app/components/service/serviceController.js b/app/components/service/serviceController.js index e33db7dab..5181d0a47 100644 --- a/app/components/service/serviceController.js +++ b/app/components/service/serviceController.js @@ -25,7 +25,8 @@ function ($scope, $stateParams, $state, Service, ServiceHelper, Task, Node, Mess service.EditImage = false; }; $scope.scaleService = function scaleService(service) { - updateServiceAttribute(service, 'Replicas', service.newServiceReplicas || service.Replicas); + var replicas = service.newServiceReplicas === null || isNaN(service.newServiceReplicas) ? service.Replicas : service.newServiceReplicas; + updateServiceAttribute(service, 'Replicas', replicas); service.EditReplicas = false; }; diff --git a/app/components/sidebar/sidebar.html b/app/components/sidebar/sidebar.html index 4bdfed76a..fda02fb20 100644 --- a/app/components/sidebar/sidebar.html +++ b/app/components/sidebar/sidebar.html @@ -17,41 +17,41 @@
+ + Name diff --git a/app/components/networks/networksController.js b/app/components/networks/networksController.js index dfd1ced1e..0ec8f30ee 100644 --- a/app/components/networks/networksController.js +++ b/app/components/networks/networksController.js @@ -47,6 +47,15 @@ function ($scope, $state, Network, Config, Messages, Settings) { $scope.sortType = sortType; }; + $scope.selectItems = function(allSelected) { + angular.forEach($scope.state.filteredNetworks, function (network) { + if (network.Checked !== allSelected) { + network.Checked = allSelected; + $scope.selectItem(network); + } + }); + }; + $scope.selectItem = function (item) { if (item.Checked) { $scope.state.selectedItemCount++; diff --git a/app/components/service/service.html b/app/components/service/service.html index 596ee3c73..ebfc0b639 100644 --- a/app/components/service/service.html +++ b/app/components/service/service.html @@ -200,17 +200,14 @@ Update Failure Action
-
- - -
-
+ +
- +
+ + Name diff --git a/app/components/volumes/volumesController.js b/app/components/volumes/volumesController.js index 042458a90..2910dec80 100644 --- a/app/components/volumes/volumesController.js +++ b/app/components/volumes/volumesController.js @@ -15,6 +15,15 @@ function ($scope, $state, Volume, Messages, Settings) { $scope.sortType = sortType; }; + $scope.selectItems = function (allSelected) { + angular.forEach($scope.state.filteredVolumes, function (volume) { + if (volume.Checked !== allSelected) { + volume.Checked = allSelected; + $scope.selectItem(volume); + } + }); + }; + $scope.selectItem = function (item) { if (item.Checked) { $scope.state.selectedItemCount++; diff --git a/assets/css/app.css b/assets/css/app.css index 140d1b1d9..9a870e85f 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -262,3 +262,10 @@ input[type="radio"] { width: 80%; margin: 0 auto; } + +ul.sidebar .sidebar-list a.active { + color: #fff; + text-indent: 22px; + border-left: 3px solid #fff; + background: #2d3e63; +} diff --git a/bower.json b/bower.json index 80e07497c..e33a4c23c 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "portainer", - "version": "1.11.0", + "version": "1.11.1", "homepage": "https://github.com/portainer/portainer", "authors": [ "Anthony Lapenna " diff --git a/build.sh b/build.sh index 1d4a75cc9..5b976fe9c 100755 --- a/build.sh +++ b/build.sh @@ -27,6 +27,13 @@ cd /tmp/portainer-build-arm tar cvpfz portainer-${VERSION}-linux-arm.tar.gz portainer cd - +grunt release-arm64 +rm -rf /tmp/portainer-build-arm64 && mkdir -pv /tmp/portainer-build-arm64/portainer +mv dist/* /tmp/portainer-build-arm64/portainer +cd /tmp/portainer-build-arm64 +tar cvpfz portainer-${VERSION}-linux-arm64.tar.gz portainer +cd - + grunt release-macos rm -rf /tmp/portainer-build-darwin && mkdir -pv /tmp/portainer-build-darwin/portainer mv dist/* /tmp/portainer-build-darwin/portainer diff --git a/build/windows/microsoftservercore/Dockerfile b/build/windows/microsoftservercore/Dockerfile index 23e75264f..dfcd8b15a 100644 --- a/build/windows/microsoftservercore/Dockerfile +++ b/build/windows/microsoftservercore/Dockerfile @@ -2,6 +2,8 @@ FROM microsoft/windowsservercore COPY dist / +VOLUME C:\\data + WORKDIR / EXPOSE 9000 diff --git a/build/windows/nanoserver/Dockerfile b/build/windows/nanoserver/Dockerfile index d4aa2d657..ae8a25a39 100644 --- a/build/windows/nanoserver/Dockerfile +++ b/build/windows/nanoserver/Dockerfile @@ -2,6 +2,8 @@ FROM microsoft/nanoserver COPY dist / +VOLUME C:\\data + WORKDIR / EXPOSE 9000 diff --git a/gruntFile.js b/gruntFile.js deleted file mode 100644 index 2690804f5..000000000 --- a/gruntFile.js +++ /dev/null @@ -1,390 +0,0 @@ -module.exports = function (grunt) { - - grunt.loadNpmTasks('grunt-contrib-concat'); - grunt.loadNpmTasks('grunt-contrib-jshint'); - grunt.loadNpmTasks('grunt-contrib-uglify'); - grunt.loadNpmTasks('grunt-contrib-clean'); - grunt.loadNpmTasks('grunt-contrib-copy'); - grunt.loadNpmTasks('grunt-contrib-watch'); - grunt.loadNpmTasks('grunt-recess'); - grunt.loadNpmTasks('grunt-karma'); - grunt.loadNpmTasks('grunt-html2js'); - grunt.loadNpmTasks('grunt-shell'); - grunt.loadNpmTasks('grunt-if'); - - // Default task. - grunt.registerTask('default', ['jshint', 'build', 'karma:unit']); - grunt.registerTask('build', [ - 'clean:app', - 'if:unixBinaryNotExist', - 'html2js', - 'concat', - 'clean:tmpl', - 'recess:build', - 'copy' - ]); - grunt.registerTask('release', [ - 'clean:all', - 'if:unixBinaryNotExist', - 'html2js', - 'uglify', - 'clean:tmpl', - 'jshint', - //'karma:unit', - 'concat:index', - 'recess:min', - 'copy' - ]); - grunt.registerTask('release-win', [ - 'clean:all', - 'if:windowsBinaryNotExist', - 'html2js', - 'uglify', - 'clean:tmpl', - 'jshint', - //'karma:unit', - 'concat:index', - 'recess:min', - 'copy' - ]); - grunt.registerTask('release-arm', [ - 'clean:all', - 'if:unixArmBinaryNotExist', - 'html2js', - 'uglify', - 'clean:tmpl', - 'jshint', - //'karma:unit', - 'concat:index', - 'recess:min', - 'copy' - ]); - grunt.registerTask('release-macos', [ - 'clean:all', - 'if:darwinBinaryNotExist', - 'html2js', - 'uglify', - 'clean:tmpl', - 'jshint', - //'karma:unit', - 'concat:index', - 'recess:min', - 'copy' - ]); - grunt.registerTask('lint', ['jshint']); - grunt.registerTask('test-watch', ['karma:watch']); - grunt.registerTask('run', ['if:unixBinaryNotExist', 'build', 'shell:buildImage', 'shell:run']); - grunt.registerTask('run-swarm', ['if:unixBinaryNotExist', 'build', 'shell:buildImage', 'shell:runSwarm', 'watch:buildSwarm']); - grunt.registerTask('run-swarm-local', ['if:unixBinaryNotExist', 'build', 'shell:buildImage', 'shell:runSwarmLocal', 'watch:buildSwarm']); - grunt.registerTask('run-dev', ['if:unixBinaryNotExist', 'shell:buildImage', 'shell:run', 'watch:build']); - grunt.registerTask('run-ssl', ['if:unixBinaryNotExist', 'shell:buildImage', 'shell:runSsl', 'watch:buildSsl']); - grunt.registerTask('clear', ['clean:app']); - - // Print a timestamp (useful for when watching) - grunt.registerTask('timestamp', function () { - grunt.log.subhead(Date()); - }); - - var karmaConfig = function (configFile, customOptions) { - var options = {configFile: configFile, keepalive: true}; - var travisOptions = process.env.TRAVIS && {browsers: ['Firefox'], reporters: 'dots'}; - return grunt.util._.extend(options, customOptions, travisOptions); - }; - - // Project configuration. - grunt.initConfig({ - distdir: 'dist', - pkg: grunt.file.readJSON('package.json'), - remoteApiVersion: 'v1.20', - banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>\n' + - '<%= pkg.homepage ? " * " + pkg.homepage + "\\n" : "" %>' + - ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %>;\n' + - ' * Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %>\n */\n', - src: { - js: ['app/**/*.js', '!app/**/*.spec.js'], - jsTpl: ['<%= distdir %>/templates/**/*.js'], - jsVendor: [ - '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/filesize/lib/filesize.min.js', - 'bower_components/moment/min/moment.min.js', - 'bower_components/xterm.js/dist/xterm.js', - 'assets/js/jquery.gritter.js', // Using custom version to fix error in minified build due to "use strict" - 'assets/js/legend.js' // Not a bower package - ], - specs: ['test/**/*.spec.js'], - scenarios: ['test/**/*.scenario.js'], - html: ['index.html'], - tpl: ['app/components/**/*.html'], - css: ['assets/css/app.css'], - cssVendor: [ - 'bower_components/bootstrap/dist/css/bootstrap.css', - 'bower_components/jquery.gritter/css/jquery.gritter.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' - ] - }, - clean: { - all: ['<%= distdir %>/*'], - app: ['<%= distdir %>/*', '!<%= distdir %>/portainer'], - tmpl: ['<%= distdir %>/templates'] - }, - copy: { - 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: ['**', '!trees.jpg'], - expand: true, - cwd: 'bower_components/jquery.gritter/images/' - }, - { - dest: '<%= distdir %>/images/', - src: ['**'], - expand: true, - cwd: 'assets/images/' - }, - {dest: '<%= distdir %>/ico', src: '**', expand: true, cwd: 'assets/ico'} - ] - } - }, - karma: { - unit: {options: karmaConfig('test/unit/karma.conf.js')}, - watch: {options: karmaConfig('test/unit/karma.conf.js', {singleRun: false, autoWatch: true})} - }, - html2js: { - app: { - options: { - base: '.' - }, - src: ['<%= src.tpl %>'], - dest: '<%= distdir %>/templates/app.js', - module: '<%= pkg.name %>.templates' - } - }, - concat: { - dist: { - options: { - banner: "<%= banner %>", - process: true - }, - src: ['<%= src.js %>', '<%= src.jsTpl %>'], - dest: '<%= distdir %>/js/<%= pkg.name %>.js' - }, - vendor: { - src: ['<%= src.jsVendor %>'], - dest: '<%= distdir %>/js/vendor.js' - }, - index: { - src: ['index.html'], - dest: '<%= distdir %>/index.html', - options: { - process: true - } - }, - angular: { - 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', - 'bower_components/angular-jwt/dist/angular-jwt.min.js', - 'bower_components/angular-ui-router/release/angular-ui-router.min.js', - 'bower_components/angular-resource/angular-resource.min.js', - 'bower_components/angular-bootstrap/ui-bootstrap-tpls.min.js', - 'bower_components/ng-file-upload/ng-file-upload.min.js', - 'bower_components/angular-utils-pagination/dirPagination.js', - 'bower_components/angular-ui-select/dist/select.min.js'], - dest: '<%= distdir %>/js/angular.js' - } - }, - uglify: { - dist: { - options: { - banner: "<%= banner %>" - }, - src: ['<%= src.js %>', '<%= src.jsTpl %>'], - dest: '<%= distdir %>/js/<%= pkg.name %>.js' - }, - vendor: { - options: { - preserveComments: 'some' // Preserve license comments - }, - src: ['<%= src.jsVendor %>'], - dest: '<%= distdir %>/js/vendor.js' - }, - angular: { - options: { - preserveComments: 'some' // Preserve license comments - }, - src: ['<%= concat.angular.src %>'], - dest: '<%= distdir %>/js/angular.js' - } - }, - recess: { // TODO: not maintained, unable to preserve license comments, switch out for something better. - 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 %>'] - }, - options: { - compile: true, - compress: true, - noOverqualifying: false // TODO: Added because of .nav class, rename - } - } - }, - watch: { - all: { - files: ['<%= src.js %>', '<%= src.specs %>', '<%= src.css %>', '<%= src.tpl %>', '<%= src.html %>'], - tasks: ['default', 'timestamp'] - }, - build: { - files: ['<%= src.js %>', '<%= src.specs %>', '<%= src.css %>', '<%= src.tpl %>', '<%= src.html %>'], - tasks: ['build', 'shell:buildImage', 'shell:run', 'shell:cleanImages'] - /* - * Why don't we just use a host volume - * http.FileServer uses sendFile which virtualbox hates - * Tried using a host volume with -v, copying files with `docker cp`, restating container, none worked - * Rebuilding image on each change was only method that worked, takes ~4s per change to update - */ - }, - buildSwarm: { - files: ['<%= src.js %>', '<%= src.specs %>', '<%= src.css %>', '<%= src.tpl %>', '<%= src.html %>'], - tasks: ['build', 'shell:buildImage', 'shell:runSwarm', 'shell:cleanImages'] - }, - buildSsl: { - files: ['<%= src.js %>', '<%= src.specs %>', '<%= src.css %>', '<%= src.tpl %>', '<%= src.html %>'], - tasks: ['build', 'shell:buildImage', 'shell:runSsl', 'shell:cleanImages'] - } - }, - jshint: { - files: ['gruntFile.js', '<%= src.js %>', '<%= src.specs %>', '<%= src.scenarios %>'], - options: { - curly: true, - eqeqeq: true, - immed: true, - latedef: true, - newcap: true, - noarg: true, - sub: true, - boss: true, - eqnull: true, - globals: { - angular: false, - '$': false - } - } - }, - shell: { - buildImage: { - command: 'docker build --rm -t portainer -f build/linux/Dockerfile .' - }, - buildBinary: { - 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(' && ') - }, - buildUnixArmBinary: { - 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(' && ') - }, - buildDarwinBinary: { - 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(' && ') - }, - buildWindowsBinary: { - 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(' && ') - }, - run: { - command: [ - 'docker stop portainer', - 'docker rm portainer', - 'docker run --privileged -d -p 9000:9000 -v /tmp/portainer:/data -v /var/run/docker.sock:/var/run/docker.sock --name portainer portainer -d /data' - ].join(';') - }, - runSwarm: { - command: [ - 'docker stop portainer', - 'docker rm portainer', - 'docker run -d -p 9000:9000 -v /tmp/portainer:/data --name portainer portainer -H tcp://10.0.7.10:2375 --swarm -d /data' - ].join(';') - }, - runSwarmLocal: { - command: [ - 'docker stop portainer', - 'docker rm portainer', - 'docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock --name portainer portainer --swarm' - ].join(';') - }, - runSsl: { - command: [ - 'docker stop portainer', - 'docker rm portainer', - 'docker run -d -p 9000:9000 -v /tmp/portainer:/data -v /tmp/docker-ssl:/certs --name portainer portainer -H tcp://10.0.7.10:2376 -d /data --tlsverify' - ].join(';') - }, - cleanImages: { - command: 'docker rmi $(docker images -q -f dangling=true)' - } - }, - 'if': { - unixBinaryNotExist: { - options: { - executable: 'dist/portainer' - }, - ifFalse: ['shell:buildBinary'] - }, - unixArmBinaryNotExist: { - options: { - executable: 'dist/portainer' - }, - ifFalse: ['shell:buildUnixArmBinary'] - }, - darwinBinaryNotExist: { - options: { - executable: 'dist/portainer' - }, - ifFalse: ['shell:buildDarwinBinary'] - }, - windowsBinaryNotExist: { - options: { - executable: 'dist/portainer.exe' - }, - ifFalse: ['shell:buildWindowsBinary'] - } - } - }); -}; diff --git a/gruntfile.js b/gruntfile.js new file mode 100644 index 000000000..6e86f4404 --- /dev/null +++ b/gruntfile.js @@ -0,0 +1,462 @@ +module.exports = function (grunt) { + + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + 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'); + + // Default task. + grunt.registerTask('default', ['jshint', 'build']); + grunt.registerTask('build', [ + 'clean:app', + 'if:unixBinaryNotExist', + 'html2js', + 'useminPrepare:dev', + 'recess:build', + 'concat', + 'clean:tmpl', + 'copy', + 'filerev', + 'usemin', + 'clean:tmp' + ]); + grunt.registerTask('release', [ + 'clean:all', + 'if:unixBinaryNotExist', + 'html2js', + 'useminPrepare:release', + 'recess:build', + 'concat', + 'clean:tmpl', + 'cssmin', + 'uglify', + 'copy:assets', + 'filerev', + 'usemin', + 'clean:tmp' + ]); + grunt.registerTask('release-win', [ + 'clean:all', + 'if:windowsBinaryNotExist', + 'html2js', + 'useminPrepare', + 'recess:build', + 'concat', + 'clean:tmpl', + 'cssmin', + 'uglify', + 'copy', + 'filerev', + 'usemin', + 'clean:tmp' + ]); + grunt.registerTask('release-arm', [ + 'clean:all', + 'if:unixArmBinaryNotExist', + 'html2js', + 'useminPrepare', + 'recess:build', + 'concat', + 'clean:tmpl', + 'cssmin', + 'uglify', + 'copy', + 'filerev', + 'usemin', + 'clean:tmp' + ]); + grunt.registerTask('release-arm64', [ + 'clean:all', + 'if:unixArm64BinaryNotExist', + 'html2js', + 'useminPrepare', + 'recess:build', + 'concat', + 'clean:tmpl', + 'cssmin', + 'uglify', + 'copy', + 'filerev', + 'usemin', + 'clean:tmp' + ]); + grunt.registerTask('release-macos', [ + 'clean:all', + 'if:darwinBinaryNotExist', + 'html2js', + 'useminPrepare', + 'recess:build', + 'concat', + 'clean:tmpl', + 'cssmin', + 'uglify', + 'copy', + 'filerev', + 'usemin', + 'clean:tmp' + ]); + grunt.registerTask('lint', ['jshint']); + grunt.registerTask('run', ['if:unixBinaryNotExist', 'build', 'shell:buildImage', 'shell:run']); + grunt.registerTask('run-swarm', ['if:unixBinaryNotExist', 'build', 'shell:buildImage', 'shell:runSwarm', 'watch:buildSwarm']); + grunt.registerTask('run-swarm-local', ['if:unixBinaryNotExist', 'build', 'shell:buildImage', 'shell:runSwarmLocal', 'watch:buildSwarm']); + grunt.registerTask('run-dev', ['if:unixBinaryNotExist', 'shell:buildImage', 'shell:run', 'watch:build']); + grunt.registerTask('run-ssl', ['if:unixBinaryNotExist', 'shell:buildImage', 'shell:runSsl', 'watch:buildSsl']); + 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'), + src: { + js: ['app/**/*.js', '!app/**/*.spec.js'], + jsTpl: ['<%= distdir %>/templates/**/*.js'], + jsVendor: [ + '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/filesize/lib/filesize.min.js', + 'bower_components/moment/min/moment.min.js', + 'bower_components/xterm.js/dist/xterm.js', + 'assets/js/jquery.gritter.js', // Using custom version to fix error in minified build due to "use strict" + 'assets/js/legend.js' // Not a bower package + ], + html: ['index.html'], + tpl: ['app/components/**/*.html'], + css: ['assets/css/app.css'], + cssVendor: [ + 'bower_components/bootstrap/dist/css/bootstrap.css', + 'bower_components/jquery.gritter/css/jquery.gritter.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' + ] + }, + clean: { + all: ['<%= distdir %>/*'], + app: ['<%= distdir %>/*', '!<%= distdir %>/portainer'], + tmpl: ['<%= distdir %>/templates'], + tmp: ['<%= distdir %>/js/*', '!<%= distdir %>/js/app.*.js', '<%= distdir %>/css/*', '!<%= distdir %>/css/app.*.css'] + }, + useminPrepare: { + dev: { + src: '<%= src.html %>', + options: { + root: '<%= distdir %>', + flow: { + steps: { + js: ['concat'], + css: ['concat'] + } + } + } + }, + release: { + src: '<%= src.html %>', + options: { + root: '<%= distdir %>' + } + } + }, + filerev: { + files: { + src: ['<%= distdir %>/js/*.js', '<%= distdir %>/css/*.css'] + } + }, + usemin: { + html: ['<%= distdir %>/index.html'] + }, + copy: { + bundle: { + files: [ + { + dest: '<%= distdir %>/js/', + src: ['app.js'], + expand: true, + cwd: '.tmp/concat/js/' + }, + { + dest: '<%= distdir %>/css/', + src: ['app.css'], + expand: true, + cwd: '.tmp/concat/css/' + } + ] + }, + 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: ['**', '!trees.jpg'], + expand: true, + cwd: 'bower_components/jquery.gritter/images/' + }, + { + dest: '<%= distdir %>/images/', + src: ['**'], + expand: true, + cwd: 'assets/images/' + }, + {dest: '<%= distdir %>/ico', src: '**', expand: true, cwd: 'assets/ico'} + ] + } + }, + html2js: { + app: { + options: { + base: '.' + }, + src: ['<%= src.tpl %>'], + dest: '<%= distdir %>/templates/app.js', + module: '<%= pkg.name %>.templates' + } + }, + concat: { + dist: { + options: { + process: true + }, + src: ['<%= src.js %>', '<%= src.jsTpl %>'], + dest: '<%= distdir %>/js/<%= pkg.name %>.js' + }, + vendor: { + src: ['<%= src.jsVendor %>'], + dest: '<%= distdir %>/js/vendor.js' + }, + index: { + src: ['index.html'], + dest: '<%= distdir %>/index.html', + options: { + process: true + } + }, + angular: { + 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', + 'bower_components/angular-jwt/dist/angular-jwt.min.js', + 'bower_components/angular-ui-router/release/angular-ui-router.min.js', + 'bower_components/angular-resource/angular-resource.min.js', + 'bower_components/angular-bootstrap/ui-bootstrap-tpls.min.js', + 'bower_components/ng-file-upload/ng-file-upload.min.js', + 'bower_components/angular-utils-pagination/dirPagination.js', + 'bower_components/angular-ui-select/dist/select.min.js'], + dest: '<%= distdir %>/js/angular.js' + } + }, + uglify: { + dist: { + // options: { + // }, + src: ['<%= src.js %>', '<%= src.jsTpl %>'], + dest: '<%= distdir %>/js/<%= pkg.name %>.js' + }, + vendor: { + options: { + preserveComments: 'some' // Preserve license comments + }, + src: ['<%= src.jsVendor %>'], + dest: '<%= distdir %>/js/vendor.js' + }, + angular: { + options: { + preserveComments: 'some' // Preserve license comments + }, + src: ['<%= concat.angular.src %>'], + dest: '<%= distdir %>/js/angular.js' + } + }, + recess: { // TODO: not maintained, unable to preserve license comments, switch out for something better. + 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 %>'] + }, + options: { + compile: true, + compress: true, + noOverqualifying: false // TODO: Added because of .nav class, rename + } + } + }, + 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', 'shell:buildImage', 'shell:run', 'shell:cleanImages'] + /* + * Why don't we just use a host volume + * http.FileServer uses sendFile which virtualbox hates + * Tried using a host volume with -v, copying files with `docker cp`, restating container, none worked + * Rebuilding image on each change was only method that worked, takes ~4s per change to update + */ + }, + 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'] + } + }, + jshint: { + files: ['gruntfile.js', '<%= src.js %>'], + options: { + curly: true, + eqeqeq: true, + immed: true, + latedef: true, + newcap: true, + noarg: true, + sub: true, + boss: true, + eqnull: true, + globals: { + angular: false, + '$': false + } + } + }, + shell: { + buildImage: { + command: 'docker build --rm -t portainer -f build/linux/Dockerfile .' + }, + buildBinary: { + 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(' && ') + }, + buildUnixArmBinary: { + 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(' && ') + }, + buildUnixArm64Binary: { + 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(' && ') + }, + buildDarwinBinary: { + 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(' && ') + }, + buildWindowsBinary: { + 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(' && ') + }, + run: { + command: [ + 'docker stop portainer', + 'docker rm portainer', + 'docker run --privileged -d -p 9000:9000 -v /tmp/portainer:/data -v /var/run/docker.sock:/var/run/docker.sock --name portainer portainer -d /data' + ].join(';') + }, + runSwarm: { + command: [ + 'docker stop portainer', + 'docker rm portainer', + 'docker run -d -p 9000:9000 -v /tmp/portainer:/data --name portainer portainer -H tcp://10.0.7.10:2375 -d /data' + ].join(';') + }, + runSwarmLocal: { + command: [ + 'docker stop portainer', + 'docker rm portainer', + 'docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock --name portainer portainer' + ].join(';') + }, + runSsl: { + command: [ + 'docker stop portainer', + 'docker rm portainer', + 'docker run -d -p 9000:9000 -v /tmp/portainer:/data -v /tmp/docker-ssl:/certs --name portainer portainer -H tcp://10.0.7.10:2376 -d /data --tlsverify' + ].join(';') + }, + cleanImages: { + command: 'docker rmi $(docker images -q -f dangling=true)' + } + }, + 'if': { + unixBinaryNotExist: { + options: { + executable: 'dist/portainer' + }, + ifFalse: ['shell:buildBinary'] + }, + unixArmBinaryNotExist: { + options: { + executable: 'dist/portainer' + }, + ifFalse: ['shell:buildUnixArmBinary'] + }, + unixArm64BinaryNotExist: { + options: { + executable: 'dist/portainer' + }, + ifFalse: ['shell:buildUnixArm64Binary'] + }, + darwinBinaryNotExist: { + options: { + executable: 'dist/portainer' + }, + ifFalse: ['shell:buildDarwinBinary'] + }, + windowsBinaryNotExist: { + options: { + executable: 'dist/portainer.exe' + }, + ifFalse: ['shell:buildWindowsBinary'] + } + } + }); +}; diff --git a/index.html b/index.html index 114070f3b..0b0bd1a1d 100644 --- a/index.html +++ b/index.html @@ -7,17 +7,21 @@ + - + + + - + + diff --git a/package.json b/package.json index 986211aaa..1b8f6721a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Portainer.io", "name": "portainer", "homepage": "http://portainer.io", - "version": "1.11.0", + "version": "1.11.1", "repository": { "type": "git", "url": "git@github.com:portainer/portainer.git" @@ -26,14 +26,17 @@ "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": "~0.2.0", "grunt-contrib-uglify": "^0.9.2", "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-recess": "~0.3", - "grunt-shell": "^1.1.2" + "grunt-shell": "^1.1.2", + "grunt-usemin": "^3.1.1" }, "scripts": { "postinstall": "bower install"