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"