From 6e98237419b9f176cb1f9c44373dbf1eb8fd67ec Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Sat, 31 Dec 2016 12:20:38 +1300 Subject: [PATCH] feat(api): introduce cache busting mechanism (#439) --- .gitignore | 1 + api/http/file_handler.go | 20 ++ api/http/handler.go | 2 +- api/http/server.go | 2 +- gruntFile.js | 390 ----------------------------------- gruntfile.js | 433 +++++++++++++++++++++++++++++++++++++++ index.html | 8 +- package.json | 5 +- 8 files changed, 466 insertions(+), 395 deletions(-) create mode 100644 api/http/file_handler.go delete mode 100644 gruntFile.js create mode 100644 gruntfile.js 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/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/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..686708c47 --- /dev/null +++ b/gruntfile.js @@ -0,0 +1,433 @@ +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:app', + 'if:unixBinaryNotExist', + 'html2js', + 'useminPrepare:release', + 'recess:build', + 'concat', + 'clean:tmpl', + 'cssmin', + 'uglify', + 'copy:assets', + 'filerev', + 'usemin', + 'clean:tmp' + ]); + grunt.registerTask('release-win', [ + 'clean:app', + 'if:windowsBinaryNotExist', + 'html2js', + 'useminPrepare', + 'recess:build', + 'concat', + 'clean:tmpl', + 'cssmin', + 'uglify', + 'copy', + 'filerev', + 'usemin', + 'clean:tmp' + ]); + grunt.registerTask('release-arm', [ + 'clean:app', + 'if:unixArmBinaryNotExist', + 'html2js', + 'useminPrepare', + 'recess:build', + 'concat', + 'clean:tmpl', + 'cssmin', + 'uglify', + 'copy', + 'filerev', + 'usemin', + 'clean:tmp' + ]); + grunt.registerTask('release-macos', [ + 'clean:app', + '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 %>', '<%= 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/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..5dfcfb783 100644 --- a/package.json +++ b/package.json @@ -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"