Major upgrade: Docker Manager is now a Standalone Ember App, contains
many improvements.
This commit is contained in:
		
							parent
							
								
									bfd70506bf
								
							
						
					
					
						commit
						3c804ec605
					
				
							
								
								
									
										10
									
								
								README.md
								
								
								
								
							
							
						
						
									
										10
									
								
								README.md
								
								
								
								
							|  | @ -3,3 +3,13 @@ This is plugin works with the Discourse docker image. | |||
| It allows you to perform upgrades via the web UI and monitor activity in the container. | ||||
| 
 | ||||
| Warning: experimental. | ||||
| 
 | ||||
| ### Development Notes | ||||
| 
 | ||||
| The client application is built using [Ember App Kit](https://github.com/stefanpenner/ember-app-kit). | ||||
| 
 | ||||
| In development mode, using `grunt server` will proxy to your Discourse instance running on Port 3000. | ||||
| Just open up a browser to post 8000 and you're off to the races!. | ||||
| 
 | ||||
| To create a compiled version for distrubtion, run the `./compile_client.sh` to compile the site and | ||||
| move it into the proper directories. | ||||
|  |  | |||
|  | @ -1,45 +0,0 @@ | |||
| $(function(){ | ||||
|   Discourse.MessageBus.start(); | ||||
|   Discourse.MessageBus.subscribe("/docker/log", function(message){ | ||||
|     if(message === "DONE"){ | ||||
|       $("button.upgrade").attr("disabled", false); | ||||
|     } else { | ||||
|       $("#log").append($("<pre>" + message + "<pre>")); | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   $("button.upgrade").click(function(){ | ||||
|     $("button.upgrade").attr("disabled", true); | ||||
|     Discourse.ajax({ | ||||
|       url: "/admin/docker/upgrade", | ||||
|       data: { path: $(this).data("path") }, | ||||
|       dataType: "text", | ||||
|       method: "POST" | ||||
|     }).then(function() { | ||||
|       alert("scroll to the bottom of your browser to watch the update"); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   var ps = function(){ | ||||
|     Discourse.ajax({ | ||||
|       url: "/admin/docker/ps", | ||||
|       dataType: "text" | ||||
|     }).then( | ||||
|       function(data){ | ||||
|         $('#ps').text(data); | ||||
|       } | ||||
|     ); | ||||
|   }; | ||||
|   ps(); | ||||
|   setInterval(ps, 5000); | ||||
| 
 | ||||
|   Discourse.csrfToken = $('meta[name=csrf-token]').attr('content'); | ||||
| 
 | ||||
|   $.ajaxPrefilter(function(options, originalOptions, xhr) { | ||||
|     if (!options.crossDomain) { | ||||
|       xhr.setRequestHeader('X-CSRF-Token', Discourse.csrfToken); | ||||
|     } | ||||
|   }); | ||||
| }); | ||||
| 
 | ||||
| 
 | ||||
|  | @ -1,22 +1,72 @@ | |||
| require_dependency 'docker_manager/git_repo' | ||||
| require_dependency 'docker_manager/upgrader' | ||||
| 
 | ||||
| module DockerManager | ||||
|   class AdminController < DockerManager::ApplicationController | ||||
|     layout nil | ||||
| 
 | ||||
|     def index | ||||
|       require_dependency 'docker_manager/git_repo' | ||||
|       @main_repo = DockerManager::GitRepo.new(Rails.root) | ||||
|       render | ||||
|     end | ||||
| 
 | ||||
|     def repos | ||||
|       repos = [DockerManager::GitRepo.new(Rails.root.to_s, 'discourse')] | ||||
|       Discourse.plugins.each do |p| | ||||
|         repos << DockerManager::GitRepo.new(File.dirname(p.path), p.name) | ||||
|       end | ||||
|       repos.map! do |r| | ||||
|         result = {name: r.name, path: r.path } | ||||
|         if r.valid? | ||||
|           result[:id] = r.name.downcase.gsub(/[^a-z]/, '_').gsub(/_+/, '_').sub(/_$/, '') | ||||
|           result[:version] = r.latest_local_commit | ||||
|           result[:url] = r.url | ||||
|           result[:upgrading] = r.upgrading? | ||||
|         end | ||||
|         result | ||||
|       end | ||||
| 
 | ||||
|       render json: {repos: repos} | ||||
|     end | ||||
| 
 | ||||
|     def progress | ||||
|       repo = DockerManager::GitRepo.new(params[:path]) | ||||
|       upgrader = Upgrader.new(current_user.id, repo, params[:version]) | ||||
|       render json: {progress: {logs: upgrader.find_logs, percentage: upgrader.last_percentage } } | ||||
|     end | ||||
| 
 | ||||
|     def latest | ||||
|       repo = DockerManager::GitRepo.new(params[:path]) | ||||
|       repo.update! | ||||
| 
 | ||||
|       render json: {latest: {version: repo.latest_origin_commit, | ||||
|                              commits_behind: repo.commits_behind, | ||||
|                              date: repo.latest_origin_commit_date } } | ||||
|     end | ||||
| 
 | ||||
|     def upgrade | ||||
|       require_dependency 'docker_manager/upgrader' | ||||
|       repo = DockerManager::GitRepo.new(params[:path]) | ||||
|       Thread.new do | ||||
|         Upgrader.upgrade(current_user.id, params[:path]) | ||||
|         upgrader = Upgrader.new(current_user.id, repo, params[:version]) | ||||
|         upgrader.upgrade | ||||
|       end | ||||
|       render text: "OK" | ||||
|     end | ||||
| 
 | ||||
|     def reset_upgrade | ||||
|       repo = DockerManager::GitRepo.new(params[:path]) | ||||
|       upgrader = Upgrader.new(current_user.id, repo, params[:version]) | ||||
|       upgrader.reset! | ||||
|       render text: "OK" | ||||
|     end | ||||
| 
 | ||||
|     def ps | ||||
|       render text: `ps aux --sort -rss` | ||||
|       # Normally we don't run on OSX but this is useful for debugging | ||||
|       if RUBY_PLATFORM =~ /darwin/ | ||||
|         ps_output = `ps aux -m` | ||||
|       else | ||||
|         ps_output = `ps aux --sort -rss` | ||||
|       end | ||||
|       render text: ps_output | ||||
|     end | ||||
| 
 | ||||
|     def runaway_cpu | ||||
|  |  | |||
|  | @ -1,10 +0,0 @@ | |||
| <% if repo.valid? %> | ||||
|   Current version: <%= repo.latest_local_commit %> (<%= time_ago_in_words repo.latest_local_commit_date %> ago), | ||||
|   Remote version: <a href="<%= repo.url %>"><%= repo.latest_origin_commit %></a> (<%= time_ago_in_words repo.latest_origin_commit_date %> ago) | ||||
|   <% if repo.commits_behind > 0 %> | ||||
|     commits behind: <%= repo.commits_behind %> | ||||
|     <button class="upgrade" action="" data-path="<%= repo.path %>"><%= upgrade_button_text %></button> | ||||
|   <% end %> | ||||
| <% else %> | ||||
|   Not under source control. | ||||
| <% end %> | ||||
|  | @ -1,45 +1,21 @@ | |||
| <!DOCTYPE html> | ||||
| <html> | ||||
|   <head> | ||||
|     <%= csrf_meta_tags %> | ||||
|     <style> | ||||
|       #ps, #log { width: 1000px; max-height: 800px; height: 800px; overflow: auto; } | ||||
|     </style> | ||||
|   </head> | ||||
|   <body> | ||||
|     <h2>Discourse</h2> | ||||
|     <p> | ||||
|       <span style="color:red">You <i>must</i> manually refresh this page several times to get the latest version status. Yes, seriously.</span> | ||||
|     </p> | ||||
|     <p> | ||||
|       <%= render partial: 'git_status', locals: { repo: @main_repo, upgrade_button_text: "Upgrade Discourse" } %> | ||||
|     </p> | ||||
| <head> | ||||
|   <meta charset="utf-8"> | ||||
|   <meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||||
|   <title>Docker Manager</title> | ||||
|   <meta name="description" content=""> | ||||
|   <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
| 
 | ||||
|     <h2>Plugins</h2> | ||||
|     <ul> | ||||
|       <% Discourse.plugins.each do |plugin| %> | ||||
|         <li> | ||||
|           <%= plugin.name %> - <%= render partial: 'git_status', locals: { repo: DockerManager::GitRepo.new(File.dirname(plugin.path)), upgrade_button_text: "Upgrade Plugin" } %> | ||||
|         </li> | ||||
|       <% end %> | ||||
|     </ul> | ||||
|   <%= javascript_include_tag "docker-manager-vendor" %> | ||||
|   <%= javascript_include_tag "docker-manager-app" %> | ||||
|   <%= javascript_include_tag "docker-manager-config" %> | ||||
|   <%= stylesheet_link_tag "docker-manager-app" %> | ||||
| </head> | ||||
| <body> | ||||
|   <script> | ||||
|     window.App = require('docker-manager/app')['default'].create(ENV.APP); | ||||
|   </script> | ||||
| 
 | ||||
|     <h2>Processes</h2> | ||||
|     <pre id="ps"></pre> | ||||
| 
 | ||||
| 
 | ||||
|     <h2>Log</h2> | ||||
|     <div id="log"></div> | ||||
|     <div id="main"></div> | ||||
|     <script> | ||||
|       Discourse = {}; | ||||
|     </script> | ||||
|     <%= javascript_include_tag "jquery_include.js" %> | ||||
|     <%= javascript_include_tag "message-bus.js" %> | ||||
|     <script> | ||||
|       Discourse.MessageBus = window.MessageBus; | ||||
|       Discourse.ajax = $.ajax; | ||||
|       Discourse.MessageBus.start(); | ||||
|     </script> | ||||
|     <%= javascript_include_tag "docker_manager" %> | ||||
|   </body> | ||||
| </body> | ||||
| </html> | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -0,0 +1,774 @@ | |||
| define("docker-manager/app",  | ||||
|   ["ember/resolver","ember/load-initializers","exports"], | ||||
|   function(__dependency1__, __dependency2__, __exports__) { | ||||
|     "use strict"; | ||||
|     var Resolver = __dependency1__["default"]; | ||||
|     var loadInitializers = __dependency2__["default"]; | ||||
| 
 | ||||
|     var App = Ember.Application.extend({ | ||||
|       modulePrefix: 'docker-manager', // TODO: loaded via config
 | ||||
|       Resolver: Resolver | ||||
|     }); | ||||
| 
 | ||||
|     loadInitializers(App, 'docker-manager'); | ||||
| 
 | ||||
|     __exports__["default"] = App; | ||||
|   }); | ||||
| define("docker-manager/components/progress-bar",  | ||||
|   ["exports"], | ||||
|   function(__exports__) { | ||||
|     "use strict"; | ||||
|     __exports__["default"] = Em.Component.extend({ | ||||
|       classNameBindings: [':progress'], | ||||
| 
 | ||||
|       barStyle: function() { | ||||
|         var percent = parseInt(this.get('percent'), 10); | ||||
|         if (percent > 0)  { | ||||
|           if (percent > 100) { percent = 100; } | ||||
|           return 'width: ' + this.get('percent') + '%'; | ||||
|         } | ||||
|       }.property('percent') | ||||
| 
 | ||||
|     }); | ||||
|   }); | ||||
| define("docker-manager/components/x-tab",  | ||||
|   ["exports"], | ||||
|   function(__exports__) { | ||||
|     "use strict"; | ||||
|     __exports__["default"] = Em.Component.extend({ | ||||
|       tagName: 'li', | ||||
|       classNameBindings: ['active'], | ||||
|       active: function() { | ||||
|         return this.get('childViews').anyBy('active'); | ||||
|       }.property('childViews.@each.active') | ||||
|     }); | ||||
|   }); | ||||
| define("docker-manager/controllers/index",  | ||||
|   ["exports"], | ||||
|   function(__exports__) { | ||||
|     "use strict"; | ||||
|     __exports__["default"] = Em.ObjectController.extend({ | ||||
|       upgrading: null | ||||
|     }); | ||||
|   }); | ||||
| define("docker-manager/controllers/processes",  | ||||
|   ["exports"], | ||||
|   function(__exports__) { | ||||
|     "use strict"; | ||||
|     __exports__["default"] = Ember.ObjectController.extend({ | ||||
|       autoRefresh: false, | ||||
| 
 | ||||
|       init: function() { | ||||
|         this._super(); | ||||
|         var self = this; | ||||
| 
 | ||||
|         window.setInterval(function() { | ||||
|           self.performRefresh(); | ||||
|         }, 5000); | ||||
|       }, | ||||
| 
 | ||||
|       performRefresh: function() { | ||||
|         if (this.get('autoRefresh')) { | ||||
|           this.get('model').refresh(); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|     }); | ||||
|   }); | ||||
| define("docker-manager/controllers/repo",  | ||||
|   ["exports"], | ||||
|   function(__exports__) { | ||||
|     "use strict"; | ||||
|     __exports__["default"] = Em.ObjectController.extend({ | ||||
|       needs: ['index'], | ||||
| 
 | ||||
|       upgradingRepo: Em.computed.alias('controllers.index.upgrading'), | ||||
|       managerRepo: Em.computed.alias('controllers.index.managerRepo'), | ||||
| 
 | ||||
|       upgradeDisabled: function() { | ||||
|         var upgradingRepo = this.get('upgradingRepo'); | ||||
| 
 | ||||
|         if (Em.isNone(upgradingRepo)) { | ||||
|           var managerRepo = this.get('managerRepo'); | ||||
|           if (!managerRepo) { return false; } | ||||
|           return (!managerRepo.get('upToDate')) && managerRepo !== this.get('model'); | ||||
|         } | ||||
|         return true; | ||||
|       }.property('upgradingRepo', 'model', 'managerRepo', 'managerRepo.upToDate') | ||||
| 
 | ||||
|     }); | ||||
|   }); | ||||
| define("docker-manager/controllers/upgrade",  | ||||
|   ["exports"], | ||||
|   function(__exports__) { | ||||
|     "use strict"; | ||||
|     /* global MessageBus, bootbox */ | ||||
| 
 | ||||
|     __exports__["default"] = Em.ObjectController.extend({ | ||||
| 
 | ||||
|       init: function() { | ||||
|         this._super(); | ||||
|         this.reset(); | ||||
|       }, | ||||
| 
 | ||||
|       complete: Em.computed.equal('status', 'complete'), | ||||
|       failed: Em.computed.equal('status', 'failed'), | ||||
| 
 | ||||
|       messageReceived: function(msg) { | ||||
|         switch(msg.type) { | ||||
|           case "log": | ||||
|             this.set('output', this.get('output') + msg.value + "\n"); | ||||
|             break; | ||||
|           case "percent": | ||||
|             this.set('percent', msg.value); | ||||
|             break; | ||||
|           case "status": | ||||
|             this.set('status', msg.value); | ||||
| 
 | ||||
|             if (msg.value === 'complete' || msg.value === 'failed') { | ||||
|               this.set('upgrading', false); | ||||
|             } | ||||
| 
 | ||||
|             if (msg.value === 'complete') { | ||||
|               this.set('version', this.get('latest.version')); | ||||
|             } | ||||
|             break; | ||||
|         } | ||||
|       }, | ||||
| 
 | ||||
|       upgradeButtonText: function() { | ||||
|         if (this.get('upgrading')) { | ||||
|           return "Upgrading..."; | ||||
|         } else { | ||||
|           return "Start Upgrading"; | ||||
|         } | ||||
|       }.property('upgrading'), | ||||
| 
 | ||||
|       startBus: function() { | ||||
|         var self = this; | ||||
|         MessageBus.subscribe("/docker/upgrade", function(msg) { | ||||
|           self.messageReceived(msg); | ||||
|         }); | ||||
|       }, | ||||
| 
 | ||||
|       stopBus: function() { | ||||
|         MessageBus.unsubscribe("/docker/upgrade"); | ||||
|       }, | ||||
| 
 | ||||
|       reset: function() { | ||||
|         this.setProperties({ output: '', status: null, percent: 0 }); | ||||
|       }, | ||||
| 
 | ||||
|       actions: { | ||||
|         start: function() { | ||||
|           this.reset(); | ||||
|           var repo = this.get('model'); | ||||
|           if (repo.get('upgrading')) { return; } | ||||
|           repo.startUpgrade(); | ||||
|         }, | ||||
| 
 | ||||
|         resetUpgrade: function() { | ||||
|           var self = this; | ||||
|           bootbox.confirm("<p><b>WARNING:</b> You should only reset upgrades that have failed and are not running.</p> <p>This will NOT cancel currently running builds and should only be used as a last resort.</p>", function(cancel) { | ||||
|             if (cancel) { | ||||
|               var repo = self.get('model'); | ||||
|               repo.resetUpgrade().then(function() { | ||||
|                 self.reset(); | ||||
|               }); | ||||
|             } | ||||
|           }); | ||||
|         } | ||||
|       }, | ||||
| 
 | ||||
|     }); | ||||
|   }); | ||||
| define("docker-manager/helpers/fmt-commit",  | ||||
|   ["exports"], | ||||
|   function(__exports__) { | ||||
|     "use strict"; | ||||
|     __exports__["default"] = Em.Handlebars.makeBoundHelper(function(sha1, url) { | ||||
|       if (Em.isNone(url)) { return; } | ||||
|       return new Em.Handlebars.SafeString("(<a href='" + url + "'>" + sha1 + "</a>)"); | ||||
|     }); | ||||
|   }); | ||||
| define("docker-manager/initializers/csrf-token",  | ||||
|   ["ic-ajax","exports"], | ||||
|   function(__dependency1__, __exports__) { | ||||
|     "use strict"; | ||||
|     var ajax = __dependency1__["default"]; | ||||
| 
 | ||||
|     __exports__["default"] = { | ||||
|       name: "findCsrfToken", | ||||
| 
 | ||||
|       initialize: function(container, application) { | ||||
|         return ajax('/session/csrf').then(function(result) { | ||||
|           var token = result.csrf; | ||||
|           $.ajaxPrefilter(function(options, originalOptions, xhr) { | ||||
|             if (!options.crossDomain) { | ||||
|               xhr.setRequestHeader('X-CSRF-Token', token); | ||||
|             } | ||||
|           }); | ||||
|         }); | ||||
|       } | ||||
|     }; | ||||
|   }); | ||||
| define("docker-manager/models/process-list",  | ||||
|   ["ic-ajax","exports"], | ||||
|   function(__dependency1__, __exports__) { | ||||
|     "use strict"; | ||||
|     var ajax = __dependency1__["default"]; | ||||
| 
 | ||||
|     var ProcessList = Em.Object.extend({ | ||||
| 
 | ||||
|       init: function() { | ||||
|         this._super(); | ||||
|       }, | ||||
| 
 | ||||
|       refresh: function() { | ||||
|         var self = this; | ||||
|         return ajax("/admin/docker/ps").then(function(result) { | ||||
|           self.set('output', result); | ||||
|           return self; | ||||
|         }); | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     ProcessList.reopenClass({ | ||||
|       find: function() { | ||||
|         var list = ProcessList.create(); | ||||
|         return list.refresh(); | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
| 
 | ||||
|     __exports__["default"] = ProcessList; | ||||
|   }); | ||||
| define("docker-manager/models/repo",  | ||||
|   ["ic-ajax","exports"], | ||||
|   function(__dependency1__, __exports__) { | ||||
|     "use strict"; | ||||
|     var ajax = __dependency1__["default"]; | ||||
| 
 | ||||
|     var loaded = []; | ||||
| 
 | ||||
|     var Repo = Em.Object.extend({ | ||||
| 
 | ||||
|       upToDate: function() { | ||||
|         return this.get('version') === this.get('latest.version'); | ||||
|       }.property('version', 'latest.version'), | ||||
| 
 | ||||
|       shouldCheck: function() { | ||||
|         if (Em.isNone(this.get('version'))) { return false; } | ||||
|         if (this.get('checking')) { return false; } | ||||
| 
 | ||||
|         // Only check once every minute
 | ||||
|         var lastCheckedAt = this.get('lastCheckedAt'); | ||||
|         if (lastCheckedAt) { | ||||
|           var ago = new Date().getTime() - lastCheckedAt; | ||||
|           return ago > 60 * 1000; | ||||
|         } | ||||
|         return true; | ||||
|       }.property().volatile(), | ||||
| 
 | ||||
|       repoAjax: function(url, args) { | ||||
|         args = args || {}; | ||||
|         args.data = this.getProperties('path', 'version'); | ||||
|         return ajax(url, args); | ||||
|       }, | ||||
| 
 | ||||
|       findLatest: function() { | ||||
|         var self = this; | ||||
| 
 | ||||
|         return new Em.RSVP.Promise(function(resolve, reject) { | ||||
|           if (!self.get('shouldCheck')) { return resolve(); } | ||||
| 
 | ||||
|           self.set('checking', true); | ||||
|           self.repoAjax('/admin/docker/latest').then(function(result) { | ||||
|             self.setProperties({ | ||||
|               checking: false, | ||||
|               lastCheckedAt: new Date().getTime(), | ||||
|               latest: Em.Object.create(result.latest) | ||||
|             }); | ||||
|             resolve(); | ||||
|           }); | ||||
|         }); | ||||
|       }, | ||||
| 
 | ||||
|       findProgress: function() { | ||||
|         return this.repoAjax('/admin/docker/progress').then(function(result) { | ||||
|           return result.progress; | ||||
|         }); | ||||
|       }, | ||||
| 
 | ||||
|       resetUpgrade: function() { | ||||
|         var self = this; | ||||
|         return this.repoAjax('/admin/docker/upgrade', { type: 'DELETE' }).then(function() { | ||||
|           self.set('upgrading', false); | ||||
|         }); | ||||
|       }, | ||||
| 
 | ||||
|       startUpgrade: function() { | ||||
|         var self = this; | ||||
|         this.set('upgrading', true); | ||||
| 
 | ||||
|         return this.repoAjax('/admin/docker/upgrade', { type: 'POST' }).catch(function() { | ||||
|           self.set('upgrading', false); | ||||
|         }); | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     Repo.reopenClass({ | ||||
|       findAll: function() { | ||||
|         return new Em.RSVP.Promise(function (resolve) { | ||||
|           if (loaded.length) { return resolve(loaded); } | ||||
| 
 | ||||
|           ajax("/admin/docker/repos").then(function(result) { | ||||
|             loaded = result.repos.map(function(r) { | ||||
|               return Repo.create(r); | ||||
|             }); | ||||
|             resolve(loaded); | ||||
|           }); | ||||
|         }); | ||||
|       }, | ||||
| 
 | ||||
|       findUpgrading: function() { | ||||
|         return this.findAll().then(function(result) { | ||||
|           return result.findBy('upgrading', true); | ||||
|         }); | ||||
|       }, | ||||
| 
 | ||||
|       find: function(id) { | ||||
|         return this.findAll().then(function(result) { | ||||
|           return result.findBy('id', id); | ||||
|         }); | ||||
|       }, | ||||
| 
 | ||||
|     }); | ||||
| 
 | ||||
|     __exports__["default"] = Repo; | ||||
|   }); | ||||
| define("docker-manager/router",  | ||||
|   ["exports"], | ||||
|   function(__exports__) { | ||||
|     "use strict"; | ||||
|     var Router = Ember.Router.extend(); // ensure we don't share routes between all Router instances
 | ||||
| 
 | ||||
|     Router.map(function() { | ||||
|       this.route("processes"); | ||||
|       this.resource('upgrade', { path: '/upgrade/:id' }); | ||||
|     }); | ||||
| 
 | ||||
|     __exports__["default"] = Router; | ||||
|   }); | ||||
| define("docker-manager/routes/index",  | ||||
|   ["docker-manager/models/repo","exports"], | ||||
|   function(__dependency1__, __exports__) { | ||||
|     "use strict"; | ||||
|     var Repo = __dependency1__["default"]; | ||||
| 
 | ||||
|     __exports__["default"] = Em.Route.extend({ | ||||
|       model: function() { | ||||
|         return Repo.findAll(); | ||||
|       }, | ||||
| 
 | ||||
|       setupController: function(controller, model) { | ||||
|         controller.setProperties({ model: model, upgrading: null }); | ||||
| 
 | ||||
|         model.forEach(function(repo) { | ||||
|           repo.findLatest(); | ||||
|           if (repo.get('upgrading')) { | ||||
|             controller.set('upgrading', repo); | ||||
|           } | ||||
| 
 | ||||
|           // Special case: Upgrade docker manager first
 | ||||
|           if (repo.get('id') === 'docker_manager') { | ||||
|             controller.set('managerRepo', repo); | ||||
|           } | ||||
|         }); | ||||
|       }, | ||||
| 
 | ||||
|       actions: { | ||||
|         upgrade: function(repo) { | ||||
|           this.transitionTo('upgrade', repo); | ||||
|         } | ||||
|       } | ||||
|     }); | ||||
|   }); | ||||
| define("docker-manager/routes/processes",  | ||||
|   ["docker-manager/models/process-list","exports"], | ||||
|   function(__dependency1__, __exports__) { | ||||
|     "use strict"; | ||||
|     var ProcessList = __dependency1__["default"]; | ||||
| 
 | ||||
|     __exports__["default"] = Em.Route.extend({ | ||||
|       model: function() { | ||||
|         return ProcessList.find(); | ||||
|       } | ||||
|     }); | ||||
|   }); | ||||
| define("docker-manager/routes/upgrade",  | ||||
|   ["docker-manager/models/repo","exports"], | ||||
|   function(__dependency1__, __exports__) { | ||||
|     "use strict"; | ||||
|     var Repo = __dependency1__["default"]; | ||||
| 
 | ||||
|     __exports__["default"] = Em.Route.extend({ | ||||
| 
 | ||||
|       model: function(params) { | ||||
|         return Repo.find(params.id); | ||||
|       }, | ||||
| 
 | ||||
|       afterModel: function(model, transition) { | ||||
|         var self = this; | ||||
|         return Repo.findUpgrading().then(function(u) { | ||||
|           if (u && u !== model) { | ||||
|             return Ember.RSVP.Promise.reject("wat"); | ||||
|           } | ||||
|           return model.findLatest().then(function() { | ||||
|             return model.findProgress().then(function(progress) { | ||||
|               self.set("progress", progress); | ||||
|             }); | ||||
|           }); | ||||
|         }); | ||||
| 
 | ||||
|       }, | ||||
| 
 | ||||
|       setupController: function(controller, model) { | ||||
|         controller.reset(); | ||||
|         controller.setProperties({ | ||||
|           model: model, | ||||
|           output: this.get('progress.logs'), | ||||
|           percent: this.get('progress.percentage') | ||||
|         }); | ||||
|         controller.startBus(); | ||||
|       }, | ||||
| 
 | ||||
|       deactivate: function() { | ||||
|         this.controllerFor('upgrade').stopBus(); | ||||
|       } | ||||
| 
 | ||||
|     }); | ||||
|   }); | ||||
| define("docker-manager/utils/ajax",  | ||||
|   ["exports"], | ||||
|   function(__exports__) { | ||||
|     "use strict"; | ||||
|     /* global ic */ | ||||
|     __exports__["default"] = function ajax(){ | ||||
|       return ic.ajax.apply(null, arguments); | ||||
|     } | ||||
|   }); | ||||
| define("docker-manager/views/loading",  | ||||
|   ["exports"], | ||||
|   function(__exports__) { | ||||
|     "use strict"; | ||||
|     __exports__["default"] = Em.View.extend({ | ||||
|       _showOnInsert: function() { | ||||
|         var self = this; | ||||
|         self.set('runner', Em.run.later(function() { | ||||
|           self.$('h3').show(); | ||||
|         }, 200)); | ||||
|       }.on('didInsertElement'), | ||||
| 
 | ||||
|       _cancelFade: function() { | ||||
|         Em.run.cancel(this.get('runner')); | ||||
|       }.on('willDestroyElement') | ||||
|     }); | ||||
|   }); | ||||
| define("docker-manager/views/processes",  | ||||
|   ["exports"], | ||||
|   function(__exports__) { | ||||
|     "use strict"; | ||||
|     __exports__["default"] = Em.View.extend({ | ||||
| 
 | ||||
|       _insertedIntoDOM: function() { | ||||
|         this.set('controller.autoRefresh', true); | ||||
|       }.on('didInsertElement'), | ||||
| 
 | ||||
|       _removedFromDOM: function() { | ||||
|         this.set('controller.autoRefresh', false); | ||||
|       }.on('willDestroyElement') | ||||
| 
 | ||||
|     }); | ||||
|   }); | ||||
| //# sourceMappingURL=app.js.map
 | ||||
| define('docker-manager/templates/application', ['exports'], function(__exports__){ __exports__['default'] = Ember.Handlebars.template(function anonymous(Handlebars,depth0,helpers,partials,data) { | ||||
| this.compilerInfo = [4,'>= 1.0.0']; | ||||
| helpers = this.merge(helpers, Ember.Handlebars.helpers); data = data || {}; | ||||
|   var buffer = '', stack1, helper, options, self=this, helperMissing=helpers.helperMissing; | ||||
| 
 | ||||
| function program1(depth0,data) { | ||||
|    | ||||
|    | ||||
|   data.buffer.push("<img src=\"/assets/images/docker-manager-ea64623b074c8ec2b0303bae846e21e6.png\" class=\"logo\">"); | ||||
|   } | ||||
| 
 | ||||
| function program3(depth0,data) { | ||||
|    | ||||
|    | ||||
|   data.buffer.push("Docker Manager"); | ||||
|   } | ||||
| 
 | ||||
| function program5(depth0,data) { | ||||
|    | ||||
|    | ||||
|   data.buffer.push("Home"); | ||||
|   } | ||||
| 
 | ||||
| function program7(depth0,data) { | ||||
|    | ||||
|    | ||||
|   data.buffer.push("Processes"); | ||||
|   } | ||||
| 
 | ||||
|   data.buffer.push("<header class=\"container\">\n  "); | ||||
|   stack1 = (helper = helpers['link-to'] || (depth0 && depth0['link-to']),options={hash:{},hashTypes:{},hashContexts:{},inverse:self.noop,fn:self.program(1, program1, data),contexts:[depth0],types:["STRING"],data:data},helper ? helper.call(depth0, "index", options) : helperMissing.call(depth0, "link-to", "index", options)); | ||||
|   if(stack1 || stack1 === 0) { data.buffer.push(stack1); } | ||||
|   data.buffer.push("\n  <h1>"); | ||||
|   stack1 = (helper = helpers['link-to'] || (depth0 && depth0['link-to']),options={hash:{},hashTypes:{},hashContexts:{},inverse:self.noop,fn:self.program(3, program3, data),contexts:[depth0],types:["STRING"],data:data},helper ? helper.call(depth0, "index", options) : helperMissing.call(depth0, "link-to", "index", options)); | ||||
|   if(stack1 || stack1 === 0) { data.buffer.push(stack1); } | ||||
|   data.buffer.push("</h1>\n</header>\n\n\n<div class=\"container\">\n\n  <ul class=\"nav nav-tabs\">\n    "); | ||||
|   stack1 = (helper = helpers['x-tab'] || (depth0 && depth0['x-tab']),options={hash:{ | ||||
|     'route': ("index") | ||||
|   },hashTypes:{'route': "STRING"},hashContexts:{'route': depth0},inverse:self.noop,fn:self.program(5, program5, data),contexts:[],types:[],data:data},helper ? helper.call(depth0, options) : helperMissing.call(depth0, "x-tab", options)); | ||||
|   if(stack1 || stack1 === 0) { data.buffer.push(stack1); } | ||||
|   data.buffer.push("\n    "); | ||||
|   stack1 = (helper = helpers['x-tab'] || (depth0 && depth0['x-tab']),options={hash:{ | ||||
|     'route': ("processes") | ||||
|   },hashTypes:{'route': "STRING"},hashContexts:{'route': depth0},inverse:self.noop,fn:self.program(7, program7, data),contexts:[],types:[],data:data},helper ? helper.call(depth0, options) : helperMissing.call(depth0, "x-tab", options)); | ||||
|   if(stack1 || stack1 === 0) { data.buffer.push(stack1); } | ||||
|   data.buffer.push("\n  </ul>\n\n  "); | ||||
|   stack1 = helpers._triageMustache.call(depth0, "outlet", {hash:{},hashTypes:{},hashContexts:{},contexts:[depth0],types:["ID"],data:data}); | ||||
|   if(stack1 || stack1 === 0) { data.buffer.push(stack1); } | ||||
|   data.buffer.push("\n</div>\n"); | ||||
|   return buffer; | ||||
|    | ||||
| }); }); | ||||
| 
 | ||||
| define('docker-manager/templates/components/progress-bar', ['exports'], function(__exports__){ __exports__['default'] = Ember.Handlebars.template(function anonymous(Handlebars,depth0,helpers,partials,data) { | ||||
| this.compilerInfo = [4,'>= 1.0.0']; | ||||
| helpers = this.merge(helpers, Ember.Handlebars.helpers); data = data || {}; | ||||
|   var buffer = '', escapeExpression=this.escapeExpression; | ||||
| 
 | ||||
| 
 | ||||
|   data.buffer.push("<div class=\"progress-bar\" "); | ||||
|   data.buffer.push(escapeExpression(helpers['bind-attr'].call(depth0, {hash:{ | ||||
|     'style': ("barStyle") | ||||
|   },hashTypes:{'style': "STRING"},hashContexts:{'style': depth0},contexts:[],types:[],data:data}))); | ||||
|   data.buffer.push("></div>\n"); | ||||
|   return buffer; | ||||
|    | ||||
| }); }); | ||||
| 
 | ||||
| define('docker-manager/templates/components/x-tab', ['exports'], function(__exports__){ __exports__['default'] = Ember.Handlebars.template(function anonymous(Handlebars,depth0,helpers,partials,data) { | ||||
| this.compilerInfo = [4,'>= 1.0.0']; | ||||
| helpers = this.merge(helpers, Ember.Handlebars.helpers); data = data || {}; | ||||
|   var buffer = '', stack1, helper, options, self=this, helperMissing=helpers.helperMissing; | ||||
| 
 | ||||
| function program1(depth0,data) { | ||||
|    | ||||
|   var stack1; | ||||
|   stack1 = helpers._triageMustache.call(depth0, "yield", {hash:{},hashTypes:{},hashContexts:{},contexts:[depth0],types:["ID"],data:data}); | ||||
|   if(stack1 || stack1 === 0) { data.buffer.push(stack1); } | ||||
|   else { data.buffer.push(''); } | ||||
|   } | ||||
| 
 | ||||
|   stack1 = (helper = helpers['link-to'] || (depth0 && depth0['link-to']),options={hash:{},hashTypes:{},hashContexts:{},inverse:self.noop,fn:self.program(1, program1, data),contexts:[depth0],types:["ID"],data:data},helper ? helper.call(depth0, "route", options) : helperMissing.call(depth0, "link-to", "route", options)); | ||||
|   if(stack1 || stack1 === 0) { data.buffer.push(stack1); } | ||||
|   data.buffer.push("\n"); | ||||
|   return buffer; | ||||
|    | ||||
| }); }); | ||||
| 
 | ||||
| define('docker-manager/templates/index', ['exports'], function(__exports__){ __exports__['default'] = Ember.Handlebars.template(function anonymous(Handlebars,depth0,helpers,partials,data) { | ||||
| this.compilerInfo = [4,'>= 1.0.0']; | ||||
| helpers = this.merge(helpers, Ember.Handlebars.helpers); data = data || {}; | ||||
|   var buffer = '', stack1, escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing, self=this; | ||||
| 
 | ||||
| function program1(depth0,data) { | ||||
|    | ||||
|   var buffer = '', stack1, helper, options; | ||||
|   data.buffer.push("\n    <tr>\n      <td>\n        "); | ||||
|   stack1 = helpers._triageMustache.call(depth0, "name", {hash:{},hashTypes:{},hashContexts:{},contexts:[depth0],types:["ID"],data:data}); | ||||
|   if(stack1 || stack1 === 0) { data.buffer.push(stack1); } | ||||
|   data.buffer.push("\n        "); | ||||
|   data.buffer.push(escapeExpression((helper = helpers['fmt-commit'] || (depth0 && depth0['fmt-commit']),options={hash:{},hashTypes:{},hashContexts:{},contexts:[depth0,depth0],types:["ID","ID"],data:data},helper ? helper.call(depth0, "version", "url", options) : helperMissing.call(depth0, "fmt-commit", "version", "url", options)))); | ||||
|   data.buffer.push("\n      </td>\n      <td>\n        "); | ||||
|   stack1 = helpers['if'].call(depth0, "checking", {hash:{},hashTypes:{},hashContexts:{},inverse:self.program(4, program4, data),fn:self.program(2, program2, data),contexts:[depth0],types:["ID"],data:data}); | ||||
|   if(stack1 || stack1 === 0) { data.buffer.push(stack1); } | ||||
|   data.buffer.push("\n      </td>\n    </tr>\n    "); | ||||
|   return buffer; | ||||
|   } | ||||
| function program2(depth0,data) { | ||||
|    | ||||
|    | ||||
|   data.buffer.push("\n          Checking for new version...\n        "); | ||||
|   } | ||||
| 
 | ||||
| function program4(depth0,data) { | ||||
|    | ||||
|   var buffer = '', stack1; | ||||
|   data.buffer.push("\n          "); | ||||
|   stack1 = helpers['if'].call(depth0, "upToDate", {hash:{},hashTypes:{},hashContexts:{},inverse:self.program(7, program7, data),fn:self.program(5, program5, data),contexts:[depth0],types:["ID"],data:data}); | ||||
|   if(stack1 || stack1 === 0) { data.buffer.push(stack1); } | ||||
|   data.buffer.push("\n        "); | ||||
|   return buffer; | ||||
|   } | ||||
| function program5(depth0,data) { | ||||
|    | ||||
|    | ||||
|   data.buffer.push("\n            Up to date\n          "); | ||||
|   } | ||||
| 
 | ||||
| function program7(depth0,data) { | ||||
|    | ||||
|   var buffer = '', stack1, helper, options; | ||||
|   data.buffer.push("\n            <div class='new-version'>\n              <h4>New Version Available!</h4>\n              <ul>\n                <li>Remote Version: "); | ||||
|   data.buffer.push(escapeExpression((helper = helpers['fmt-commit'] || (depth0 && depth0['fmt-commit']),options={hash:{},hashTypes:{},hashContexts:{},contexts:[depth0,depth0],types:["ID","ID"],data:data},helper ? helper.call(depth0, "latest.version", "url", options) : helperMissing.call(depth0, "fmt-commit", "latest.version", "url", options)))); | ||||
|   data.buffer.push("</li>\n                <li>Last Updated: "); | ||||
|   stack1 = helpers._triageMustache.call(depth0, "latest.date", {hash:{},hashTypes:{},hashContexts:{},contexts:[depth0],types:["ID"],data:data}); | ||||
|   if(stack1 || stack1 === 0) { data.buffer.push(stack1); } | ||||
|   data.buffer.push("</li>\n                <li class='new-commits'>"); | ||||
|   stack1 = helpers._triageMustache.call(depth0, "latest.commits_behind", {hash:{},hashTypes:{},hashContexts:{},contexts:[depth0],types:["ID"],data:data}); | ||||
|   if(stack1 || stack1 === 0) { data.buffer.push(stack1); } | ||||
|   data.buffer.push(" new commits</li>\n              </ul>\n              "); | ||||
|   stack1 = helpers['if'].call(depth0, "upgrading", {hash:{},hashTypes:{},hashContexts:{},inverse:self.program(10, program10, data),fn:self.program(8, program8, data),contexts:[depth0],types:["ID"],data:data}); | ||||
|   if(stack1 || stack1 === 0) { data.buffer.push(stack1); } | ||||
|   data.buffer.push("\n            </div>\n          "); | ||||
|   return buffer; | ||||
|   } | ||||
| function program8(depth0,data) { | ||||
|    | ||||
|   var buffer = ''; | ||||
|   data.buffer.push("\n                <button class=\"btn\" "); | ||||
|   data.buffer.push(escapeExpression(helpers.action.call(depth0, "upgrade", "", {hash:{},hashTypes:{},hashContexts:{},contexts:[depth0,depth0],types:["ID","ID"],data:data}))); | ||||
|   data.buffer.push(">Currently Upgrading...</button>\n              "); | ||||
|   return buffer; | ||||
|   } | ||||
| 
 | ||||
| function program10(depth0,data) { | ||||
|    | ||||
|   var buffer = ''; | ||||
|   data.buffer.push("\n                <button class=\"btn\" "); | ||||
|   data.buffer.push(escapeExpression(helpers.action.call(depth0, "upgrade", "", {hash:{},hashTypes:{},hashContexts:{},contexts:[depth0,depth0],types:["ID","ID"],data:data}))); | ||||
|   data.buffer.push(" "); | ||||
|   data.buffer.push(escapeExpression(helpers['bind-attr'].call(depth0, {hash:{ | ||||
|     'disabled': ("upgradeDisabled") | ||||
|   },hashTypes:{'disabled': "STRING"},hashContexts:{'disabled': depth0},contexts:[],types:[],data:data}))); | ||||
|   data.buffer.push(">Upgrade to the Latest Version</button>\n              "); | ||||
|   return buffer; | ||||
|   } | ||||
| 
 | ||||
|   data.buffer.push("<h3>Repositories</h3>\n\n<table class='table' id='repos'>\n  <tr>\n    <th style='width: 50%'>Name</th>\n    <th>Status</th>\n  </tr>\n  <tbody>\n    "); | ||||
|   stack1 = helpers.each.call(depth0, "model", {hash:{ | ||||
|     'itemController': ("repo") | ||||
|   },hashTypes:{'itemController': "STRING"},hashContexts:{'itemController': depth0},inverse:self.noop,fn:self.program(1, program1, data),contexts:[depth0],types:["ID"],data:data}); | ||||
|   if(stack1 || stack1 === 0) { data.buffer.push(stack1); } | ||||
|   data.buffer.push("\n  </tbody>\n</table>\n"); | ||||
|   return buffer; | ||||
|    | ||||
| }); }); | ||||
| 
 | ||||
| define('docker-manager/templates/loading', ['exports'], function(__exports__){ __exports__['default'] = Ember.Handlebars.template(function anonymous(Handlebars,depth0,helpers,partials,data) { | ||||
| this.compilerInfo = [4,'>= 1.0.0']; | ||||
| helpers = this.merge(helpers, Ember.Handlebars.helpers); data = data || {}; | ||||
|    | ||||
| 
 | ||||
| 
 | ||||
|   data.buffer.push("<h3 class='loading'>Loading...</h3>\n"); | ||||
|    | ||||
| }); }); | ||||
| 
 | ||||
| define('docker-manager/templates/processes', ['exports'], function(__exports__){ __exports__['default'] = Ember.Handlebars.template(function anonymous(Handlebars,depth0,helpers,partials,data) { | ||||
| this.compilerInfo = [4,'>= 1.0.0']; | ||||
| helpers = this.merge(helpers, Ember.Handlebars.helpers); data = data || {}; | ||||
|   var buffer = '', stack1; | ||||
| 
 | ||||
| 
 | ||||
|   data.buffer.push("<h3>Processes</h3>\n\n<div class='logs'>"); | ||||
|   stack1 = helpers._triageMustache.call(depth0, "output", {hash:{},hashTypes:{},hashContexts:{},contexts:[depth0],types:["ID"],data:data}); | ||||
|   if(stack1 || stack1 === 0) { data.buffer.push(stack1); } | ||||
|   data.buffer.push("</div>\n"); | ||||
|   return buffer; | ||||
|    | ||||
| }); }); | ||||
| 
 | ||||
| define('docker-manager/templates/upgrade', ['exports'], function(__exports__){ __exports__['default'] = Ember.Handlebars.template(function anonymous(Handlebars,depth0,helpers,partials,data) { | ||||
| this.compilerInfo = [4,'>= 1.0.0']; | ||||
| helpers = this.merge(helpers, Ember.Handlebars.helpers); data = data || {}; | ||||
|   var buffer = '', stack1, helper, options, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, self=this; | ||||
| 
 | ||||
| function program1(depth0,data) { | ||||
|    | ||||
|    | ||||
|   data.buffer.push("\n  <p>Upgrade completed successfully!</p>\n  <p>Note: The web server restarts in the background. It's a good idea to wait 30 seconds or so\n     before refreshing your browser to see the latest version of the application.</p>\n"); | ||||
|   } | ||||
| 
 | ||||
| function program3(depth0,data) { | ||||
|    | ||||
|    | ||||
|   data.buffer.push("\n  <p>Sorry, there wasn an error upgrading Discourse. Please check the logs.</p>\n"); | ||||
|   } | ||||
| 
 | ||||
| function program5(depth0,data) { | ||||
|    | ||||
|   var buffer = '', stack1, helper, options; | ||||
|   data.buffer.push("\n  <p>"); | ||||
|   stack1 = helpers._triageMustache.call(depth0, "name", {hash:{},hashTypes:{},hashContexts:{},contexts:[depth0],types:["ID"],data:data}); | ||||
|   if(stack1 || stack1 === 0) { data.buffer.push(stack1); } | ||||
|   data.buffer.push(" is at the newest version "); | ||||
|   data.buffer.push(escapeExpression((helper = helpers['fmt-commit'] || (depth0 && depth0['fmt-commit']),options={hash:{},hashTypes:{},hashContexts:{},contexts:[depth0,depth0],types:["ID","ID"],data:data},helper ? helper.call(depth0, "version", "url", options) : helperMissing.call(depth0, "fmt-commit", "version", "url", options)))); | ||||
|   data.buffer.push(".</p>\n"); | ||||
|   return buffer; | ||||
|   } | ||||
| 
 | ||||
| function program7(depth0,data) { | ||||
|    | ||||
|   var buffer = '', stack1; | ||||
|   data.buffer.push("\n  <div style='clear: both'>\n    <button "); | ||||
|   data.buffer.push(escapeExpression(helpers.action.call(depth0, "start", {hash:{},hashTypes:{},hashContexts:{},contexts:[depth0],types:["ID"],data:data}))); | ||||
|   data.buffer.push(" "); | ||||
|   data.buffer.push(escapeExpression(helpers['bind-attr'].call(depth0, {hash:{ | ||||
|     'disabled': ("upgrading") | ||||
|   },hashTypes:{'disabled': "STRING"},hashContexts:{'disabled': depth0},contexts:[],types:[],data:data}))); | ||||
|   data.buffer.push(" class='btn'>"); | ||||
|   stack1 = helpers._triageMustache.call(depth0, "upgradeButtonText", {hash:{},hashTypes:{},hashContexts:{},contexts:[depth0],types:["ID"],data:data}); | ||||
|   if(stack1 || stack1 === 0) { data.buffer.push(stack1); } | ||||
|   data.buffer.push("</button>\n    "); | ||||
|   stack1 = helpers['if'].call(depth0, "upgrading", {hash:{},hashTypes:{},hashContexts:{},inverse:self.noop,fn:self.program(8, program8, data),contexts:[depth0],types:["ID"],data:data}); | ||||
|   if(stack1 || stack1 === 0) { data.buffer.push(stack1); } | ||||
|   data.buffer.push("\n  </div>\n"); | ||||
|   return buffer; | ||||
|   } | ||||
| function program8(depth0,data) { | ||||
|    | ||||
|   var buffer = ''; | ||||
|   data.buffer.push("\n      <button "); | ||||
|   data.buffer.push(escapeExpression(helpers.action.call(depth0, "resetUpgrade", {hash:{},hashTypes:{},hashContexts:{},contexts:[depth0],types:["ID"],data:data}))); | ||||
|   data.buffer.push(" class=\"btn unlock\">Reset Upgrade</button>\n    "); | ||||
|   return buffer; | ||||
|   } | ||||
| 
 | ||||
|   data.buffer.push("<h3>Upgrade "); | ||||
|   stack1 = helpers._triageMustache.call(depth0, "name", {hash:{},hashTypes:{},hashContexts:{},contexts:[depth0],types:["ID"],data:data}); | ||||
|   if(stack1 || stack1 === 0) { data.buffer.push(stack1); } | ||||
|   data.buffer.push("</h3>\n\n"); | ||||
|   data.buffer.push(escapeExpression((helper = helpers['progress-bar'] || (depth0 && depth0['progress-bar']),options={hash:{ | ||||
|     'percent': ("percent") | ||||
|   },hashTypes:{'percent': "ID"},hashContexts:{'percent': depth0},contexts:[],types:[],data:data},helper ? helper.call(depth0, options) : helperMissing.call(depth0, "progress-bar", options)))); | ||||
|   data.buffer.push("\n\n"); | ||||
|   stack1 = helpers['if'].call(depth0, "complete", {hash:{},hashTypes:{},hashContexts:{},inverse:self.noop,fn:self.program(1, program1, data),contexts:[depth0],types:["ID"],data:data}); | ||||
|   if(stack1 || stack1 === 0) { data.buffer.push(stack1); } | ||||
|   data.buffer.push("\n\n"); | ||||
|   stack1 = helpers['if'].call(depth0, "failed", {hash:{},hashTypes:{},hashContexts:{},inverse:self.noop,fn:self.program(3, program3, data),contexts:[depth0],types:["ID"],data:data}); | ||||
|   if(stack1 || stack1 === 0) { data.buffer.push(stack1); } | ||||
|   data.buffer.push("\n\n"); | ||||
|   stack1 = helpers['if'].call(depth0, "upToDate", {hash:{},hashTypes:{},hashContexts:{},inverse:self.program(7, program7, data),fn:self.program(5, program5, data),contexts:[depth0],types:["ID"],data:data}); | ||||
|   if(stack1 || stack1 === 0) { data.buffer.push(stack1); } | ||||
|   data.buffer.push("\n\n<div class='logs'>"); | ||||
|   stack1 = helpers._triageMustache.call(depth0, "output", {hash:{},hashTypes:{},hashContexts:{},contexts:[depth0],types:["ID"],data:data}); | ||||
|   if(stack1 || stack1 === 0) { data.buffer.push(stack1); } | ||||
|   data.buffer.push("</div>\n"); | ||||
|   return buffer; | ||||
|    | ||||
| }); }); | ||||
|  | @ -0,0 +1,18 @@ | |||
| // Put general configuration here. This file is included
 | ||||
| // in both production and development BEFORE Ember is
 | ||||
| // loaded.
 | ||||
| //
 | ||||
| // For example to enable a feature on a canary build you
 | ||||
| // might do:
 | ||||
| //
 | ||||
| // window.ENV = {FEATURES: {'with-controller': true}};
 | ||||
| 
 | ||||
| window.ENV = window.ENV || {}; | ||||
| 
 | ||||
| window.ENV.MODEL_FACTORY_INJECTIONS = true; | ||||
| // Put your production configuration here.
 | ||||
| //
 | ||||
| // This is useful when using a separate API
 | ||||
| // endpoint in development than in production.
 | ||||
| //
 | ||||
| // window.ENV.public_key = '123456'
 | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 33 KiB | 
|  | @ -0,0 +1,12 @@ | |||
| #!/bin/bash | ||||
| 
 | ||||
| # Compile the application using grunt | ||||
| (cd manager-client && grunt dist) | ||||
| 
 | ||||
| # Remove old assets | ||||
| rm assets/docker-manager-* | ||||
| 
 | ||||
| cp manager-client/dist/assets/app.min.css assets/docker-manager-app.css | ||||
| cp manager-client/dist/assets/app.min.js assets/docker-manager-app.js | ||||
| cp manager-client/dist/assets/vendor.min.js assets/docker-manager-vendor.js | ||||
| cp manager-client/dist/assets/config.min.js assets/docker-manager-config.js | ||||
|  | @ -1,7 +1,12 @@ | |||
| DockerManager::Engine.routes.draw do | ||||
|   get "admin/docker" => "admin#index" | ||||
|   get "admin/docker/repos" => "admin#repos" | ||||
|   get "admin/docker/latest" => "admin#latest" | ||||
|   get "admin/docker/progress" => "admin#progress" | ||||
|   get "admin/docker/ps" => "admin#ps" | ||||
|   post "admin/docker/upgrade" => "admin#upgrade" | ||||
|   delete "admin/docker/upgrade" => "admin#reset_upgrade" | ||||
|   get "admin/docker/runaway_cpu" => "admin#runaway_cpu" | ||||
|   get "admin/docker/runaway_mem" => "admin#runaway_mem" | ||||
|   get 'admin/docker/csrf' => 'admin#csrf' | ||||
| end | ||||
|  |  | |||
|  | @ -1,12 +1,25 @@ | |||
| # like Grit just very very minimal | ||||
| class DockerManager::GitRepo | ||||
|   attr_reader :path | ||||
|   attr_reader :path, :name | ||||
| 
 | ||||
|   def initialize(path) | ||||
|   def initialize(path, name=nil) | ||||
|     @path = path | ||||
|     @name = name | ||||
|     @memoize = {} | ||||
|   end | ||||
| 
 | ||||
|   def start_upgrading | ||||
|     $redis.setnx(upgrade_key, 1) | ||||
|   end | ||||
| 
 | ||||
|   def stop_upgrading | ||||
|     $redis.del(upgrade_key) | ||||
|   end | ||||
| 
 | ||||
|   def upgrading? | ||||
|     $redis.get(upgrade_key).present? | ||||
|   end | ||||
| 
 | ||||
|   def valid? | ||||
|     File.directory?("#{path}/.git") | ||||
|   end | ||||
|  | @ -41,8 +54,16 @@ class DockerManager::GitRepo | |||
|     url | ||||
|   end | ||||
| 
 | ||||
|   def update! | ||||
|     `cd #{path} && git remote update` | ||||
|   end | ||||
| 
 | ||||
|   protected | ||||
| 
 | ||||
|   def upgrade_key | ||||
|     @upgrade_key ||= "upgrade:#{path}" | ||||
|   end | ||||
| 
 | ||||
|   def commit_date(commit) | ||||
|     unix_timestamp = run('show -s --format="%ct" ' << commit).to_i | ||||
|     Time.at(unix_timestamp).to_datetime | ||||
|  | @ -52,15 +73,7 @@ class DockerManager::GitRepo | |||
|     run "for-each-ref --format='%(upstream:short)' $(git symbolic-ref -q HEAD)" | ||||
|   end | ||||
| 
 | ||||
|   def ensure_updated | ||||
|     @updated ||= Thread.new do | ||||
|                    # this is a very slow operation, make it async | ||||
|                    `cd #{path} && git remote update` | ||||
|                  end | ||||
|   end | ||||
| 
 | ||||
|   def run(cmd) | ||||
|     ensure_updated | ||||
|     @memoize[cmd] ||= `cd #{path} && git #{cmd}`.strip | ||||
|   rescue => e | ||||
|     p e | ||||
|  |  | |||
|  | @ -1,25 +1,35 @@ | |||
| class DockerManager::Upgrader | ||||
|   attr_accessor :user_id, :path | ||||
| 
 | ||||
|   def self.upgrade(user_id, path) | ||||
|     self.new(user_id: user_id, path: path).upgrade | ||||
|   def initialize(user_id, repo, from_version) | ||||
|     @user_id = user_id | ||||
|     @repo = repo | ||||
|     @from_version = from_version | ||||
|   end | ||||
| 
 | ||||
|   def initialize(opts) | ||||
|     self.user_id = opts[:user_id] | ||||
|     self.path= opts[:path] | ||||
|   def reset! | ||||
|     @repo.stop_upgrading | ||||
|     clear_logs | ||||
|     percent(0) | ||||
|   end | ||||
| 
 | ||||
|   def upgrade | ||||
|     return unless @repo.start_upgrading | ||||
| 
 | ||||
|     clear_logs | ||||
| 
 | ||||
|     # HEAD@{upstream} is just a fancy way how to say origin/master (in normal case) | ||||
|     # see http://stackoverflow.com/a/12699604/84283 | ||||
|     run("cd #{path} && git fetch && git reset --hard HEAD@{upstream}") | ||||
|     run("cd #{@repo.path} && git fetch && git reset --hard HEAD@{upstream}") | ||||
|     log("********************************************************") | ||||
|     log("*** Please be patient, next steps might take a while ***") | ||||
|     log("********************************************************") | ||||
|     run("bundle install --deployment --without test --without development") | ||||
|     percent(25) | ||||
|     run("bundle exec rake multisite:migrate") | ||||
|     percent(50) | ||||
|     log("***  Bundling assets. This might take a while *** ") | ||||
|     run("bundle exec rake assets:precompile") | ||||
|     percent(75) | ||||
|     sidekiq_pid = `ps aux | grep sidekiq.*busy | grep -v grep | awk '{ print $2 }'`.strip.to_i | ||||
|     if sidekiq_pid > 0 | ||||
|       Process.kill("TERM", sidekiq_pid) | ||||
|  | @ -27,6 +37,8 @@ class DockerManager::Upgrader | |||
|     else | ||||
|       log("Warning: Sidekiq was not found") | ||||
|     end | ||||
|     percent(100) | ||||
|     publish('status', 'complete') | ||||
|     pid = `ps aux  | grep unicorn_launcher | grep -v sudo | grep -v grep | awk '{ print $2 }'`.strip | ||||
|     if pid.to_i > 0 | ||||
|       log("***********************************************") | ||||
|  | @ -38,9 +50,17 @@ class DockerManager::Upgrader | |||
|     else | ||||
|       log("Did not find unicorn launcher") | ||||
|     end | ||||
|   rescue | ||||
|   rescue => ex | ||||
|     publish('status', 'failed') | ||||
|     STDERR.puts("Docker Manager: FAILED TO UPGRADE") | ||||
|     STDERR.puts(ex.inspect) | ||||
|     raise | ||||
|   ensure | ||||
|     @repo.stop_upgrading | ||||
|   end | ||||
| 
 | ||||
|   def publish(type, value) | ||||
|     MessageBus.publish("/docker/upgrade", {type: type, value: value}, user_ids: [@user_id]) | ||||
|   end | ||||
| 
 | ||||
|   def run(cmd) | ||||
|  | @ -67,7 +87,35 @@ class DockerManager::Upgrader | |||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def logs_key | ||||
|     "logs:#{@repo.path}:#{@from_version}" | ||||
|   end | ||||
| 
 | ||||
|   def clear_logs | ||||
|     $redis.del(logs_key) | ||||
|   end | ||||
| 
 | ||||
|   def find_logs | ||||
|     $redis.get(logs_key) | ||||
|   end | ||||
| 
 | ||||
|   def percent_key | ||||
|     "percent:#{@repo.path}:#{@from_version}" | ||||
|   end | ||||
| 
 | ||||
|   def last_percentage | ||||
|     $redis.get(percent_key) | ||||
|   end | ||||
| 
 | ||||
|   def percent(val) | ||||
|     $redis.set(percent_key, val) | ||||
|     $redis.expire(percent_key, 30.minutes) | ||||
|     publish('percent', val) | ||||
|   end | ||||
| 
 | ||||
|   def log(message) | ||||
|     MessageBus.publish("/docker/log", message, user_ids: [user_id]) | ||||
|     $redis.append logs_key, message + "\n" | ||||
|     $redis.expire(logs_key, 30.minutes) | ||||
|     publish 'log', message | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -0,0 +1,3 @@ | |||
| { | ||||
|   "directory": "vendor" | ||||
| } | ||||
|  | @ -0,0 +1,20 @@ | |||
| # See http://help.github.com/ignore-files/ for more about ignoring files. | ||||
| 
 | ||||
| # compiled output | ||||
| /dist | ||||
| /tmp | ||||
| /docs | ||||
| 
 | ||||
| # dependencies | ||||
| /node_modules | ||||
| /vendor/* | ||||
| !/vendor/ember-shim.js | ||||
| !/vendor/qunit-shim.js | ||||
| 
 | ||||
| # misc | ||||
| /.sass-cache | ||||
| /connect.lock | ||||
| /libpeerconnection.log | ||||
| .DS_Store | ||||
| Thumbs.db | ||||
| /coverage/* | ||||
|  | @ -0,0 +1,37 @@ | |||
| { | ||||
|   "predef": { | ||||
|     "document": true, | ||||
|     "window": true, | ||||
|     "location": true, | ||||
|     "setTimeout": true, | ||||
|     "Ember": true, | ||||
|     "Em": true, | ||||
|     "DS": true, | ||||
|     "$": true | ||||
|   }, | ||||
|   "node" : false, | ||||
|   "browser" : false, | ||||
|   "boss" : true, | ||||
|   "curly": false, | ||||
|   "debug": false, | ||||
|   "devel": false, | ||||
|   "eqeqeq": true, | ||||
|   "evil": true, | ||||
|   "forin": false, | ||||
|   "immed": false, | ||||
|   "laxbreak": false, | ||||
|   "newcap": true, | ||||
|   "noarg": true, | ||||
|   "noempty": false, | ||||
|   "nonew": false, | ||||
|   "nomen": false, | ||||
|   "onevar": false, | ||||
|   "plusplus": false, | ||||
|   "regexp": false, | ||||
|   "undef": true, | ||||
|   "sub": true, | ||||
|   "strict": false, | ||||
|   "white": false, | ||||
|   "eqnull": true, | ||||
|   "esnext": true | ||||
| } | ||||
|  | @ -0,0 +1,7 @@ | |||
| language: node_js | ||||
| node_js: | ||||
|   - 0.10 | ||||
| before_script: | ||||
|   - npm install -g grunt-cli | ||||
|   - npm install -g bower | ||||
|   - bower install | ||||
|  | @ -0,0 +1,240 @@ | |||
| // jshint node:true
 | ||||
| 
 | ||||
| module.exports = function(grunt) { | ||||
|   // To support Coffeescript, SASS, LESS and others, just install
 | ||||
|   // the appropriate grunt package and it will be automatically included
 | ||||
|   // in the build process:
 | ||||
|   //
 | ||||
|   // * for Coffeescript, run `npm install --save-dev grunt-contrib-coffee`
 | ||||
|   //
 | ||||
|   // * for SCSS (without SASS), run `npm install --save-dev grunt-sass`
 | ||||
|   // * for SCSS/SASS support (may be slower), run
 | ||||
|   //   `npm install --save-dev grunt-contrib-sass`
 | ||||
|   //   This depends on the ruby sass gem, which can be installed with
 | ||||
|   //   `gem install sass`
 | ||||
|   // * for Compass, run `npm install --save-dev grunt-contrib-compass`
 | ||||
|   //   This depends on the ruby compass gem, which can be installed with
 | ||||
|   //   `gem install compass`
 | ||||
|   //   You should not install SASS if you have installed Compass.
 | ||||
|   //
 | ||||
|   // * for LESS, run `npm install --save-dev grunt-contrib-less`
 | ||||
|   //
 | ||||
|   // * for Stylus/Nib, `npm install --save-dev grunt-contrib-stylus`
 | ||||
|   //
 | ||||
|   // * for Emblem, run the following commands:
 | ||||
|   //   `npm uninstall --save-dev grunt-ember-templates`
 | ||||
|   //   `npm install --save-dev grunt-emblem`
 | ||||
|   //   `bower install emblem.js --save`
 | ||||
|   //
 | ||||
|   // * For EmberScript, run `npm install --save-dev grunt-ember-script`
 | ||||
|   //
 | ||||
|   // * for LiveReload, `npm install --save-dev connect-livereload`
 | ||||
|   //
 | ||||
|   // * for YUIDoc support, `npm install --save-dev grunt-contrib-yuidoc`
 | ||||
|   //   It is also nice to use a theme other than default. For example,
 | ||||
|   //   simply do: `npm install yuidoc-theme-blue`
 | ||||
|   //   Currently, only the `app` directory is used for generating docs.
 | ||||
|   //   When installed, visit: http[s]://[host:port]/docs
 | ||||
|   //
 | ||||
|   // * for displaying the execution time of the grunt tasks,
 | ||||
|   //   `npm install --save-dev time-grunt`
 | ||||
|   //
 | ||||
|   // * for minimizing the index.html at the end of the dist task
 | ||||
|   //   `npm install --save-dev grunt-contrib-htmlmin`
 | ||||
|   //
 | ||||
|   // * for minimizing images in the dist task
 | ||||
|   //   `npm install --save-dev grunt-contrib-imagemin`
 | ||||
|   //
 | ||||
|   // * for using images based CSS sprites (http://youtu.be/xD8DW6IQ6r0)
 | ||||
|   //   `npm install --save-dev grunt-fancy-sprites`
 | ||||
|   //   `bower install --save fancy-sprites-scss`
 | ||||
|   //
 | ||||
|   // * for automatically adding CSS vendor prefixes (autoprefixer)
 | ||||
|   //   `npm install --save-dev grunt-autoprefixer`
 | ||||
|   //
 | ||||
|   // * for package import validations
 | ||||
|   //   `npm install --save-dev grunt-es6-import-validate`
 | ||||
|   //
 | ||||
| 
 | ||||
|   var Helpers = require('./tasks/helpers'), | ||||
|       filterAvailable = Helpers.filterAvailableTasks, | ||||
|       _ = grunt.util._, | ||||
|       path = require('path'); | ||||
| 
 | ||||
|   Helpers.pkg = require("./package.json"); | ||||
| 
 | ||||
|   if (Helpers.isPackageAvailable("time-grunt")) { | ||||
|     require("time-grunt")(grunt); | ||||
|   } | ||||
| 
 | ||||
|   // Loads task options from `tasks/options/` and `tasks/custom-options`
 | ||||
|   // and loads tasks defined in `package.json`
 | ||||
|   var config = _.extend({}, | ||||
|     require('load-grunt-config')(grunt, { | ||||
|         configPath: path.join(__dirname, 'tasks/options'), | ||||
|         loadGruntTasks: false, | ||||
|         init: false | ||||
|       }), | ||||
|     require('load-grunt-config')(grunt, { // Custom options have precedence
 | ||||
|         configPath: path.join(__dirname, 'tasks/custom-options'), | ||||
|         init: false | ||||
|       }) | ||||
|   ); | ||||
| 
 | ||||
|   grunt.loadTasks('tasks'); // Loads tasks in `tasks/` folder
 | ||||
| 
 | ||||
|   config.env = process.env; | ||||
| 
 | ||||
| 
 | ||||
|   // App Kit's Main Tasks
 | ||||
|   // ====================
 | ||||
| 
 | ||||
| 
 | ||||
|   // Generate the production version
 | ||||
|   // ------------------
 | ||||
|   grunt.registerTask('dist', "Build a minified & production-ready version of your app.", [ | ||||
|                      'clean:dist', | ||||
|                      'build:dist', | ||||
|                      'copy:assemble', | ||||
|                      'createDistVersion' | ||||
|                      ]); | ||||
| 
 | ||||
| 
 | ||||
|   // Default Task
 | ||||
|   // ------------------
 | ||||
|   grunt.registerTask('default', "Build (in debug mode)"); | ||||
| 
 | ||||
| 
 | ||||
|   // Servers
 | ||||
|   // -------------------
 | ||||
|   grunt.registerTask('server', "Run your server in development mode, auto-rebuilding when files change.", function(proxyMethod) { | ||||
|     var expressServerTask = 'expressServer:debug'; | ||||
|     if (proxyMethod) { | ||||
|       expressServerTask += ':' + proxyMethod; | ||||
|     } | ||||
| 
 | ||||
|     grunt.task.run(['clean:debug', | ||||
|                     'build:debug', | ||||
|                     expressServerTask, | ||||
|                     'watch' | ||||
|                     ]); | ||||
|   }); | ||||
| 
 | ||||
|   grunt.registerTask('server:dist', "Build and preview a minified & production-ready version of your app.", [ | ||||
|                      'dist', | ||||
|                      'expressServer:dist:keepalive' | ||||
|                      ]); | ||||
| 
 | ||||
| 
 | ||||
|   // Worker tasks
 | ||||
|   // =================================
 | ||||
| 
 | ||||
|   grunt.registerTask('build:dist', filterAvailable([ | ||||
|                      'createResultDirectory', // Create directoy beforehand, fixes race condition
 | ||||
|                      'fancySprites:create', | ||||
|                      'concurrent:buildDist', // Executed in parallel, see config below
 | ||||
|                      ])); | ||||
| 
 | ||||
|   grunt.registerTask('build:debug', filterAvailable([ | ||||
|                      'jshint:tooling', | ||||
|                      'createResultDirectory', // Create directoy beforehand, fixes race condition
 | ||||
|                      'fancySprites:create', | ||||
|                      'concurrent:buildDebug', // Executed in parallel, see config below
 | ||||
|                      ])); | ||||
| 
 | ||||
|   grunt.registerTask('createDistVersion', filterAvailable([ | ||||
|                      'useminPrepare', // Configures concat, cssmin and uglify
 | ||||
|                      'concat', // Combines css and javascript files
 | ||||
| 
 | ||||
|                      //'cssmin', // Minifies css
 | ||||
|                      //'uglify', // Minifies javascript
 | ||||
|                      //'imagemin', // Optimizes image compression
 | ||||
|                      // 'svgmin',
 | ||||
|                      'copy:dist', // Copies files not covered by concat and imagemin
 | ||||
| 
 | ||||
|                      //'rev', // Appends 8 char hash value to filenames
 | ||||
|                      //'usemin', // Replaces file references
 | ||||
|                      //'htmlmin:dist' // Removes comments and whitespace
 | ||||
|                      ])); | ||||
| 
 | ||||
|   // Documentation
 | ||||
|   // -------
 | ||||
|   grunt.registerTask('docs', "Build YUIDoc documentation.", [ | ||||
|                      'buildDocs', | ||||
|                      'server:debug' | ||||
|                      ]); | ||||
| 
 | ||||
| 
 | ||||
|   // Parallelize most of the build process
 | ||||
|   _.merge(config, { | ||||
|     concurrent: { | ||||
|       buildDist: [ | ||||
|         "buildTemplates:dist", | ||||
|         "buildScripts", | ||||
|         "buildStyles", | ||||
|         "buildIndexHTML:dist" | ||||
|       ], | ||||
|       buildDebug: [ | ||||
|         "buildTemplates:debug", | ||||
|         "buildScripts", | ||||
|         "buildStyles", | ||||
|         "buildIndexHTML:debug" | ||||
|       ] | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   // Templates
 | ||||
|   grunt.registerTask('buildTemplates:dist', filterAvailable([ | ||||
|                      'emblem:compile', | ||||
|                      'emberTemplates:dist' | ||||
|                      ])); | ||||
| 
 | ||||
|   grunt.registerTask('buildTemplates:debug', filterAvailable([ | ||||
|                      'emblem:compile', | ||||
|                      'emberTemplates:debug' | ||||
|                      ])); | ||||
| 
 | ||||
|   // Scripts
 | ||||
|   grunt.registerTask('buildScripts', filterAvailable([ | ||||
|                      'jshint:app', | ||||
|                      'validate-imports:app', | ||||
|                      'coffee', | ||||
|                      'emberscript', | ||||
|                      'copy:javascriptToTmp', | ||||
|                      'transpile', | ||||
|                      'buildDocs', | ||||
|                      'concat_sourcemap' | ||||
|                      ])); | ||||
| 
 | ||||
|   // Styles
 | ||||
|   grunt.registerTask('buildStyles', filterAvailable([ | ||||
|                      'compass:compile', | ||||
|                      'sass:compile', | ||||
|                      'less:compile', | ||||
|                      'stylus:compile', | ||||
|                      'copy:cssToResult', | ||||
|                      'autoprefixer:app' | ||||
|                      ])); | ||||
| 
 | ||||
|   // Documentation
 | ||||
|   grunt.registerTask('buildDocs', filterAvailable([ | ||||
|                      'yuidoc:debug', | ||||
|                      ])); | ||||
| 
 | ||||
|   // Index HTML
 | ||||
|   grunt.registerTask('buildIndexHTML:dist', [ | ||||
|                      'preprocess:indexHTMLDistApp', | ||||
|                      'preprocess:indexHTMLDistTests' | ||||
|                      ]); | ||||
| 
 | ||||
|   grunt.registerTask('buildIndexHTML:debug', [ | ||||
|                      'preprocess:indexHTMLDebugApp', | ||||
|                      'preprocess:indexHTMLDebugTests' | ||||
|                      ]); | ||||
| 
 | ||||
|   grunt.registerTask('createResultDirectory', function() { | ||||
|     grunt.file.mkdir('tmp/result'); | ||||
|   }); | ||||
| 
 | ||||
|   grunt.initConfig(config); | ||||
| }; | ||||
|  | @ -0,0 +1,120 @@ | |||
| API Stub | ||||
| ======== | ||||
| 
 | ||||
| The stub allows you to implement express routes to fake API calls. | ||||
| Simply add API routes in the routes.js file. The benefit of an API | ||||
| stub is that you can use the REST adapter from the get go. It's a | ||||
| way to use fixtures without having to use the fixture adapter. | ||||
| 
 | ||||
| As development progresses, the API stub becomes a functioning spec | ||||
| for the real backend. Once you have a separate development API | ||||
| server running, then switch from the stub to the proxy pass through. | ||||
| 
 | ||||
| To configure which API method to use edit **package.json**. | ||||
| 
 | ||||
| * Set the **APIMethod** to 'stub' to use these express stub routes.  | ||||
| 
 | ||||
| * Set the method to 'proxy' and define the **proxyURL** to pass all API requests to the proxy URL. | ||||
| 
 | ||||
| Default Example | ||||
| ----------------  | ||||
| 
 | ||||
| 1. Create the following models: | ||||
| 
 | ||||
| 		app/models/post.js | ||||
| 
 | ||||
| 		``` | ||||
| 		var attr = DS.attr, | ||||
| 		    hasMany = DS.hasMany, | ||||
| 		    belongsTo = DS.belongsTo; | ||||
| 
 | ||||
| 		var Post = DS.Model.extend({ | ||||
| 		  title: attr(), | ||||
| 		  comments: hasMany('comment'), | ||||
| 		  user: attr(), | ||||
| 		}); | ||||
| 
 | ||||
| 		export default Post; | ||||
| 		``` | ||||
| 
 | ||||
| 		app/models/comment.js | ||||
| 
 | ||||
| 		``` | ||||
| 		var attr = DS.attr, | ||||
| 		    hasMany = DS.hasMany, | ||||
| 		    belongsTo = DS.belongsTo; | ||||
| 
 | ||||
| 		var Comment = DS.Model.extend({ | ||||
| 		  body: attr() | ||||
| 		}); | ||||
| 
 | ||||
| 		export default Comment; | ||||
| 		``` | ||||
| 
 | ||||
| 2. Setup the REST adapter for the application: | ||||
| 
 | ||||
| 		app/adapters/application.js | ||||
| 
 | ||||
| 		``` | ||||
| 		var ApplicationAdapter = DS.RESTAdapter.extend({ | ||||
| 			namespace: 'api' | ||||
| 		}); | ||||
| 
 | ||||
| 		export default ApplicationAdapter; | ||||
| 		``` | ||||
| 
 | ||||
| 3. Tell the Index router to query for a post: | ||||
| 
 | ||||
| 		app/routes/index.js | ||||
| 
 | ||||
| 		``` | ||||
| 		var IndexRoute = Ember.Route.extend({ | ||||
| 		  model: function() { | ||||
| 		    return this.store.find('post', 1); | ||||
| 		  } | ||||
| 		}); | ||||
| 
 | ||||
| 		export default IndexRoute; | ||||
| 		``` | ||||
| 
 | ||||
| 
 | ||||
| 4. Expose the model properties in the index.hbs template | ||||
| 
 | ||||
| 		app/templates/index.hbs | ||||
| 
 | ||||
| 		``` | ||||
| 		<h2>{{title}}</h2> | ||||
| 		<p>{{body}}</p> | ||||
| 		<section class="comments"> | ||||
| 			<ul> | ||||
| 			{{#each comment in comments}} | ||||
| 			  <li> | ||||
| 			    <div>{{comment.body}}</div> | ||||
| 			  </li> | ||||
| 			{{/each}} | ||||
| 			</ul> | ||||
| 		</section> | ||||
| 		``` | ||||
| 
 | ||||
| When Ember Data queries the store for the post, it will make an API call to | ||||
| http://localhost:8000/api/posts/1, to which the express server will respond with | ||||
| some mock data: | ||||
| 
 | ||||
| ``` | ||||
| { | ||||
|   "post": { | ||||
|     "id": 1, | ||||
|     "title": "Rails is omakase", | ||||
|     "comments": ["1", "2"], | ||||
|     "user" : "dhh" | ||||
|   }, | ||||
| 
 | ||||
|   "comments": [{ | ||||
|     "id": "1", | ||||
|     "body": "Rails is unagi" | ||||
|   }, { | ||||
|     "id": "2", | ||||
|     "body": "Omakase O_o" | ||||
|   }] | ||||
| } | ||||
| ``` | ||||
|  | @ -0,0 +1,29 @@ | |||
| module.exports = function(server) { | ||||
| 
 | ||||
|   // Create an API namespace, so that the root does not
 | ||||
|   // have to be repeated for each end point.
 | ||||
|   server.namespace('/api', function() { | ||||
| 
 | ||||
|     // Return fixture data for '/api/posts/:id'
 | ||||
|     server.get('/posts/:id', function(req, res) { | ||||
|       var post = { | ||||
|         "post": { | ||||
|           "id": 1, | ||||
|           "title": "Rails is omakase", | ||||
|           "comments": ["1", "2"], | ||||
|           "user" : "dhh" | ||||
|         }, | ||||
| 
 | ||||
|         "comments": [{ | ||||
|           "id": "1", | ||||
|           "body": "Rails is unagi" | ||||
|         }, { | ||||
|           "id": "2", | ||||
|           "body": "Omakase O_o" | ||||
|         }] | ||||
|       }; | ||||
| 
 | ||||
|       res.send(post); | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
|  | @ -0,0 +1,11 @@ | |||
| import Resolver from 'ember/resolver'; | ||||
| import loadInitializers from 'ember/load-initializers'; | ||||
| 
 | ||||
| var App = Ember.Application.extend({ | ||||
|   modulePrefix: 'docker-manager', // TODO: loaded via config
 | ||||
|   Resolver: Resolver | ||||
| }); | ||||
| 
 | ||||
| loadInitializers(App, 'docker-manager'); | ||||
| 
 | ||||
| export default App; | ||||
|  | @ -0,0 +1,13 @@ | |||
| export default Em.Component.extend({ | ||||
|   classNameBindings: [':progress'], | ||||
| 
 | ||||
|   barStyle: function() { | ||||
|     var percent = parseInt(this.get('percent'), 10); | ||||
|     if (percent > 0)  { | ||||
|       if (percent > 100) { percent = 100; } | ||||
|       return 'width: ' + this.get('percent') + '%'; | ||||
|     } | ||||
|   }.property('percent') | ||||
| 
 | ||||
| }); | ||||
| 
 | ||||
|  | @ -0,0 +1,8 @@ | |||
| export default Em.Component.extend({ | ||||
|   tagName: 'li', | ||||
|   classNameBindings: ['active'], | ||||
|   active: function() { | ||||
|     return this.get('childViews').anyBy('active'); | ||||
|   }.property('childViews.@each.active') | ||||
| }); | ||||
| 
 | ||||
|  | @ -0,0 +1,4 @@ | |||
| export default Em.ObjectController.extend({ | ||||
|   upgrading: null | ||||
| }); | ||||
| 
 | ||||
|  | @ -0,0 +1,20 @@ | |||
| export default Ember.ObjectController.extend({ | ||||
|   autoRefresh: false, | ||||
| 
 | ||||
|   init: function() { | ||||
|     this._super(); | ||||
|     var self = this; | ||||
| 
 | ||||
|     window.setInterval(function() { | ||||
|       self.performRefresh(); | ||||
|     }, 5000); | ||||
|   }, | ||||
| 
 | ||||
|   performRefresh: function() { | ||||
|     if (this.get('autoRefresh')) { | ||||
|       this.get('model').refresh(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| }); | ||||
| 
 | ||||
|  | @ -0,0 +1,19 @@ | |||
| export default Em.ObjectController.extend({ | ||||
|   needs: ['index'], | ||||
| 
 | ||||
|   upgradingRepo: Em.computed.alias('controllers.index.upgrading'), | ||||
|   managerRepo: Em.computed.alias('controllers.index.managerRepo'), | ||||
| 
 | ||||
|   upgradeDisabled: function() { | ||||
|     var upgradingRepo = this.get('upgradingRepo'); | ||||
| 
 | ||||
|     if (Em.isNone(upgradingRepo)) { | ||||
|       var managerRepo = this.get('managerRepo'); | ||||
|       if (!managerRepo) { return false; } | ||||
|       return (!managerRepo.get('upToDate')) && managerRepo !== this.get('model'); | ||||
|     } | ||||
|     return true; | ||||
|   }.property('upgradingRepo', 'model', 'managerRepo', 'managerRepo.upToDate') | ||||
| 
 | ||||
| }); | ||||
| 
 | ||||
|  | @ -0,0 +1,79 @@ | |||
| /* global MessageBus, bootbox */ | ||||
| 
 | ||||
| export default Em.ObjectController.extend({ | ||||
| 
 | ||||
|   init: function() { | ||||
|     this._super(); | ||||
|     this.reset(); | ||||
|   }, | ||||
| 
 | ||||
|   complete: Em.computed.equal('status', 'complete'), | ||||
|   failed: Em.computed.equal('status', 'failed'), | ||||
| 
 | ||||
|   messageReceived: function(msg) { | ||||
|     switch(msg.type) { | ||||
|       case "log": | ||||
|         this.set('output', this.get('output') + msg.value + "\n"); | ||||
|         break; | ||||
|       case "percent": | ||||
|         this.set('percent', msg.value); | ||||
|         break; | ||||
|       case "status": | ||||
|         this.set('status', msg.value); | ||||
| 
 | ||||
|         if (msg.value === 'complete' || msg.value === 'failed') { | ||||
|           this.set('upgrading', false); | ||||
|         } | ||||
| 
 | ||||
|         if (msg.value === 'complete') { | ||||
|           this.set('version', this.get('latest.version')); | ||||
|         } | ||||
|         break; | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   upgradeButtonText: function() { | ||||
|     if (this.get('upgrading')) { | ||||
|       return "Upgrading..."; | ||||
|     } else { | ||||
|       return "Start Upgrading"; | ||||
|     } | ||||
|   }.property('upgrading'), | ||||
| 
 | ||||
|   startBus: function() { | ||||
|     var self = this; | ||||
|     MessageBus.subscribe("/docker/upgrade", function(msg) { | ||||
|       self.messageReceived(msg); | ||||
|     }); | ||||
|   }, | ||||
| 
 | ||||
|   stopBus: function() { | ||||
|     MessageBus.unsubscribe("/docker/upgrade"); | ||||
|   }, | ||||
| 
 | ||||
|   reset: function() { | ||||
|     this.setProperties({ output: '', status: null, percent: 0 }); | ||||
|   }, | ||||
| 
 | ||||
|   actions: { | ||||
|     start: function() { | ||||
|       this.reset(); | ||||
|       var repo = this.get('model'); | ||||
|       if (repo.get('upgrading')) { return; } | ||||
|       repo.startUpgrade(); | ||||
|     }, | ||||
| 
 | ||||
|     resetUpgrade: function() { | ||||
|       var self = this; | ||||
|       bootbox.confirm("<p><b>WARNING:</b> You should only reset upgrades that have failed and are not running.</p> <p>This will NOT cancel currently running builds and should only be used as a last resort.</p>", function(cancel) { | ||||
|         if (cancel) { | ||||
|           var repo = self.get('model'); | ||||
|           repo.resetUpgrade().then(function() { | ||||
|             self.reset(); | ||||
|           }); | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
| }); | ||||
|  | @ -0,0 +1,4 @@ | |||
| export default Em.Handlebars.makeBoundHelper(function(sha1, url) { | ||||
|   if (Em.isNone(url)) { return; } | ||||
|   return new Em.Handlebars.SafeString("(<a href='" + url + "'>" + sha1 + "</a>)"); | ||||
| }); | ||||
|  | @ -0,0 +1,66 @@ | |||
| <!DOCTYPE html> | ||||
| <html> | ||||
| <head> | ||||
|   <meta charset="utf-8"> | ||||
|   <meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||||
|   <title>Docker Manager</title> | ||||
|   <meta name="description" content=""> | ||||
|   <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
| 
 | ||||
|   <!-- build:css(tmp/result) /assets/app.min.css --> | ||||
|   <link rel="stylesheet" href="/assets/app.css"> | ||||
|   <link rel="stylesheet" href="/vendor/bootstrap/dist/css/bootstrap.css"> | ||||
|   <!-- endbuild --> | ||||
| 
 | ||||
|   <!-- for more details visit: https://github.com/yeoman/grunt-usemin --> | ||||
| 
 | ||||
|   <!-- build:js(tmp/result) /assets/config.min.js --> | ||||
| 
 | ||||
|     <script src="/config/environment.js"></script> | ||||
| 
 | ||||
|     <!-- @if dist=false --> | ||||
|     <script src="/config/environments/development.js"></script> | ||||
|     <!-- @endif --><!-- @if dist=true --> | ||||
|     <script src="/config/environments/production.js"></script> | ||||
|     <!-- @endif --> | ||||
| 
 | ||||
|   <!-- endbuild --> | ||||
| 
 | ||||
|   <!-- build:js(tmp/result) /assets/vendor.min.js --> | ||||
| 
 | ||||
|     <script src="/vendor/jquery/jquery.js"></script> | ||||
| 
 | ||||
|     <!-- @if dist=false --> | ||||
|     <script src="/vendor/handlebars/handlebars.js"></script> | ||||
|     <script src="/vendor/ember/ember.js"></script> | ||||
|     <!-- @endif --><!-- @if dist=true --> | ||||
|     <script src="/vendor/handlebars/handlebars.runtime.js"></script> | ||||
|     <script src="/vendor/ember/ember.prod.js"></script> | ||||
|     <!-- @endif --> | ||||
| 
 | ||||
|     <script src="/vendor/loader.js/loader.js"></script> | ||||
|     <script src="/vendor/ember-resolver/dist/ember-resolver.js"></script> | ||||
|     <script src="/vendor/ember-shim.js"></script> | ||||
|     <script src="/vendor/ic-ajax/dist/named-amd/main.js"></script> | ||||
|     <script src="/vendor/ember-load-initializers/ember-load-initializers.js"></script> | ||||
|     <script src="/vendor/message-bus/assets/message-bus.js"></script> | ||||
|     <script src="/vendor/bootstrap/js/modal.js"></script> | ||||
|     <script src="/vendor/bootbox/bootbox.js"></script> | ||||
| 
 | ||||
|   <!-- endbuild --> | ||||
| 
 | ||||
|   <!-- build:js(tmp/result) /assets/app.min.js --> | ||||
| 
 | ||||
|     <script src="/assets/app.js"></script> | ||||
|     <script src="/assets/templates.js"></script> | ||||
| 
 | ||||
|   <!-- endbuild --> | ||||
| 
 | ||||
| </head> | ||||
| <body> | ||||
|   <script> | ||||
|     window.App = require('docker-manager/app')['default'].create(ENV.APP); | ||||
|   </script> | ||||
| 
 | ||||
| </body> | ||||
| </html> | ||||
|  | @ -0,0 +1,16 @@ | |||
| import ajax from "ic-ajax"; | ||||
| 
 | ||||
| export default { | ||||
|   name: "findCsrfToken", | ||||
| 
 | ||||
|   initialize: function(container, application) { | ||||
|     return ajax('/session/csrf').then(function(result) { | ||||
|       var token = result.csrf; | ||||
|       $.ajaxPrefilter(function(options, originalOptions, xhr) { | ||||
|         if (!options.crossDomain) { | ||||
|           xhr.setRequestHeader('X-CSRF-Token', token); | ||||
|         } | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,26 @@ | |||
| import ajax from "ic-ajax"; | ||||
| 
 | ||||
| var ProcessList = Em.Object.extend({ | ||||
| 
 | ||||
|   init: function() { | ||||
|     this._super(); | ||||
|   }, | ||||
| 
 | ||||
|   refresh: function() { | ||||
|     var self = this; | ||||
|     return ajax("/admin/docker/ps").then(function(result) { | ||||
|       self.set('output', result); | ||||
|       return self; | ||||
|     }); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| ProcessList.reopenClass({ | ||||
|   find: function() { | ||||
|     var list = ProcessList.create(); | ||||
|     return list.refresh(); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| 
 | ||||
| export default ProcessList; | ||||
|  | @ -0,0 +1,99 @@ | |||
| import ajax from "ic-ajax"; | ||||
| 
 | ||||
| var loaded = []; | ||||
| 
 | ||||
| var Repo = Em.Object.extend({ | ||||
| 
 | ||||
|   upToDate: function() { | ||||
|     return this.get('version') === this.get('latest.version'); | ||||
|   }.property('version', 'latest.version'), | ||||
| 
 | ||||
|   shouldCheck: function() { | ||||
|     if (Em.isNone(this.get('version'))) { return false; } | ||||
|     if (this.get('checking')) { return false; } | ||||
| 
 | ||||
|     // Only check once every minute
 | ||||
|     var lastCheckedAt = this.get('lastCheckedAt'); | ||||
|     if (lastCheckedAt) { | ||||
|       var ago = new Date().getTime() - lastCheckedAt; | ||||
|       return ago > 60 * 1000; | ||||
|     } | ||||
|     return true; | ||||
|   }.property().volatile(), | ||||
| 
 | ||||
|   repoAjax: function(url, args) { | ||||
|     args = args || {}; | ||||
|     args.data = this.getProperties('path', 'version'); | ||||
|     return ajax(url, args); | ||||
|   }, | ||||
| 
 | ||||
|   findLatest: function() { | ||||
|     var self = this; | ||||
| 
 | ||||
|     return new Em.RSVP.Promise(function(resolve, reject) { | ||||
|       if (!self.get('shouldCheck')) { return resolve(); } | ||||
| 
 | ||||
|       self.set('checking', true); | ||||
|       self.repoAjax('/admin/docker/latest').then(function(result) { | ||||
|         self.setProperties({ | ||||
|           checking: false, | ||||
|           lastCheckedAt: new Date().getTime(), | ||||
|           latest: Em.Object.create(result.latest) | ||||
|         }); | ||||
|         resolve(); | ||||
|       }); | ||||
|     }); | ||||
|   }, | ||||
| 
 | ||||
|   findProgress: function() { | ||||
|     return this.repoAjax('/admin/docker/progress').then(function(result) { | ||||
|       return result.progress; | ||||
|     }); | ||||
|   }, | ||||
| 
 | ||||
|   resetUpgrade: function() { | ||||
|     var self = this; | ||||
|     return this.repoAjax('/admin/docker/upgrade', { type: 'DELETE' }).then(function() { | ||||
|       self.set('upgrading', false); | ||||
|     }); | ||||
|   }, | ||||
| 
 | ||||
|   startUpgrade: function() { | ||||
|     var self = this; | ||||
|     this.set('upgrading', true); | ||||
| 
 | ||||
|     return this.repoAjax('/admin/docker/upgrade', { type: 'POST' }).catch(function() { | ||||
|       self.set('upgrading', false); | ||||
|     }); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| Repo.reopenClass({ | ||||
|   findAll: function() { | ||||
|     return new Em.RSVP.Promise(function (resolve) { | ||||
|       if (loaded.length) { return resolve(loaded); } | ||||
| 
 | ||||
|       ajax("/admin/docker/repos").then(function(result) { | ||||
|         loaded = result.repos.map(function(r) { | ||||
|           return Repo.create(r); | ||||
|         }); | ||||
|         resolve(loaded); | ||||
|       }); | ||||
|     }); | ||||
|   }, | ||||
| 
 | ||||
|   findUpgrading: function() { | ||||
|     return this.findAll().then(function(result) { | ||||
|       return result.findBy('upgrading', true); | ||||
|     }); | ||||
|   }, | ||||
| 
 | ||||
|   find: function(id) { | ||||
|     return this.findAll().then(function(result) { | ||||
|       return result.findBy('id', id); | ||||
|     }); | ||||
|   }, | ||||
| 
 | ||||
| }); | ||||
| 
 | ||||
| export default Repo; | ||||
|  | @ -0,0 +1,8 @@ | |||
| var Router = Ember.Router.extend(); // ensure we don't share routes between all Router instances
 | ||||
| 
 | ||||
| Router.map(function() { | ||||
|   this.route("processes"); | ||||
|   this.resource('upgrade', { path: '/upgrade/:id' }); | ||||
| }); | ||||
| 
 | ||||
| export default Router; | ||||
|  | @ -0,0 +1,30 @@ | |||
| import Repo from 'docker-manager/models/repo'; | ||||
| 
 | ||||
| export default Em.Route.extend({ | ||||
|   model: function() { | ||||
|     return Repo.findAll(); | ||||
|   }, | ||||
| 
 | ||||
|   setupController: function(controller, model) { | ||||
|     controller.setProperties({ model: model, upgrading: null }); | ||||
| 
 | ||||
|     model.forEach(function(repo) { | ||||
|       repo.findLatest(); | ||||
|       if (repo.get('upgrading')) { | ||||
|         controller.set('upgrading', repo); | ||||
|       } | ||||
| 
 | ||||
|       // Special case: Upgrade docker manager first
 | ||||
|       if (repo.get('id') === 'docker_manager') { | ||||
|         controller.set('managerRepo', repo); | ||||
|       } | ||||
|     }); | ||||
|   }, | ||||
| 
 | ||||
|   actions: { | ||||
|     upgrade: function(repo) { | ||||
|       this.transitionTo('upgrade', repo); | ||||
|     } | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
|  | @ -0,0 +1,7 @@ | |||
| import ProcessList from 'docker-manager/models/process-list'; | ||||
| 
 | ||||
| export default Em.Route.extend({ | ||||
|   model: function() { | ||||
|     return ProcessList.find(); | ||||
|   } | ||||
| }); | ||||
|  | @ -0,0 +1,38 @@ | |||
| import Repo from 'docker-manager/models/repo'; | ||||
| 
 | ||||
| export default Em.Route.extend({ | ||||
| 
 | ||||
|   model: function(params) { | ||||
|     return Repo.find(params.id); | ||||
|   }, | ||||
| 
 | ||||
|   afterModel: function(model, transition) { | ||||
|     var self = this; | ||||
|     return Repo.findUpgrading().then(function(u) { | ||||
|       if (u && u !== model) { | ||||
|         return Ember.RSVP.Promise.reject("wat"); | ||||
|       } | ||||
|       return model.findLatest().then(function() { | ||||
|         return model.findProgress().then(function(progress) { | ||||
|           self.set("progress", progress); | ||||
|         }); | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|   }, | ||||
| 
 | ||||
|   setupController: function(controller, model) { | ||||
|     controller.reset(); | ||||
|     controller.setProperties({ | ||||
|       model: model, | ||||
|       output: this.get('progress.logs'), | ||||
|       percent: this.get('progress.percentage') | ||||
|     }); | ||||
|     controller.startBus(); | ||||
|   }, | ||||
| 
 | ||||
|   deactivate: function() { | ||||
|     this.controllerFor('upgrade').stopBus(); | ||||
|   } | ||||
| 
 | ||||
| }); | ||||
|  | @ -0,0 +1,83 @@ | |||
| /* Put your CSS here */ | ||||
| 
 | ||||
| html{ | ||||
|   margin: 20px; | ||||
|   overflow-y: scroll; | ||||
|   overflow-x: auto; | ||||
| } | ||||
| 
 | ||||
| header { | ||||
|   img.logo { | ||||
|     width: 200px; | ||||
|     float: left; | ||||
|   } | ||||
| 
 | ||||
|   a[href] { | ||||
|     text-decoration: none; | ||||
|     color: black; | ||||
|   } | ||||
|   h1 { | ||||
|     text-align: right; | ||||
|   } | ||||
|   clear: both; | ||||
| } | ||||
| 
 | ||||
| div.logs { | ||||
|   margin-top: 20px; | ||||
|   overflow-x: scroll; | ||||
|   height: 400px; | ||||
|   white-space: pre; | ||||
|   font-family: monospace; | ||||
|   background-color: black; | ||||
|   color: #ddd; | ||||
|   padding: 10px; | ||||
| } | ||||
| 
 | ||||
| table#repos { | ||||
|   margin-top: 10px; | ||||
| } | ||||
| 
 | ||||
| h3.loading { | ||||
|   display: none; | ||||
| } | ||||
| 
 | ||||
| .new-version { | ||||
|   h4 { | ||||
|     margin: 0 0 10px 0; | ||||
|     font-weight: bold; | ||||
|     font-size: 14px; | ||||
|   } | ||||
| 
 | ||||
|   ul { | ||||
|     list-style: none; | ||||
|     padding: 0; | ||||
|   } | ||||
| 
 | ||||
|   li.new-commits { | ||||
|     font-style: italic; | ||||
|   } | ||||
| 
 | ||||
|   button { | ||||
|     display: block; | ||||
|     padding: 2px 5px; | ||||
|     margin: 10px 0 0px 0; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| button.btn { | ||||
|   background: #00aaff; | ||||
|   color: white !important; | ||||
|   border-radius: 0; | ||||
| 
 | ||||
|   &:hover { | ||||
|     background-color: #0081c2; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| button.unlock { | ||||
|   float: right; | ||||
|   background-color: red; | ||||
|   &:hover { | ||||
|     background-color: #a00; | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,15 @@ | |||
| <header class="container"> | ||||
|   {{#link-to 'index'}}<img src="/assets/images/docker-manager-ea64623b074c8ec2b0303bae846e21e6.png" class="logo">{{/link-to}} | ||||
|   <h1>{{#link-to 'index'}}Docker Manager{{/link-to}}</h1> | ||||
| </header> | ||||
| 
 | ||||
| 
 | ||||
| <div class="container"> | ||||
| 
 | ||||
|   <ul class="nav nav-tabs"> | ||||
|     {{#x-tab route="index"}}Home{{/x-tab}} | ||||
|     {{#x-tab route="processes"}}Processes{{/x-tab}} | ||||
|   </ul> | ||||
| 
 | ||||
|   {{outlet}} | ||||
| </div> | ||||
|  | @ -0,0 +1 @@ | |||
| <div class="progress-bar" {{bind-attr style="barStyle"}}></div> | ||||
|  | @ -0,0 +1 @@ | |||
| {{#link-to route}}{{yield}}{{/link-to}} | ||||
|  | @ -0,0 +1,41 @@ | |||
| <h3>Repositories</h3> | ||||
| 
 | ||||
| <table class='table' id='repos'> | ||||
|   <tr> | ||||
|     <th style='width: 50%'>Name</th> | ||||
|     <th>Status</th> | ||||
|   </tr> | ||||
|   <tbody> | ||||
|     {{#each model itemController="repo"}} | ||||
|     <tr> | ||||
|       <td> | ||||
|         {{name}} | ||||
|         {{fmt-commit version url}} | ||||
|       </td> | ||||
|       <td> | ||||
|         {{#if checking}} | ||||
|           Checking for new version... | ||||
|         {{else}} | ||||
|           {{#if upToDate}} | ||||
|             Up to date | ||||
|           {{else}} | ||||
|             <div class='new-version'> | ||||
|               <h4>New Version Available!</h4> | ||||
|               <ul> | ||||
|                 <li>Remote Version: {{fmt-commit latest.version url}}</li> | ||||
|                 <li>Last Updated: {{latest.date}}</li> | ||||
|                 <li class='new-commits'>{{latest.commits_behind}} new commits</li> | ||||
|               </ul> | ||||
|               {{#if upgrading}} | ||||
|                 <button class="btn" {{action upgrade this}}>Currently Upgrading...</button> | ||||
|               {{else}} | ||||
|                 <button class="btn" {{action upgrade this}} {{bind-attr disabled="upgradeDisabled"}}>Upgrade to the Latest Version</button> | ||||
|               {{/if}} | ||||
|             </div> | ||||
|           {{/if}} | ||||
|         {{/if}} | ||||
|       </td> | ||||
|     </tr> | ||||
|     {{/each}} | ||||
|   </tbody> | ||||
| </table> | ||||
|  | @ -0,0 +1 @@ | |||
| <h3 class='loading'>Loading...</h3> | ||||
|  | @ -0,0 +1,3 @@ | |||
| <h3>Processes</h3> | ||||
| 
 | ||||
| <div class='logs'>{{output}}</div> | ||||
|  | @ -0,0 +1,26 @@ | |||
| <h3>Upgrade {{name}}</h3> | ||||
| 
 | ||||
| {{progress-bar percent=percent}} | ||||
| 
 | ||||
| {{#if complete}} | ||||
|   <p>Upgrade completed successfully!</p> | ||||
|   <p>Note: The web server restarts in the background. It's a good idea to wait 30 seconds or so | ||||
|      before refreshing your browser to see the latest version of the application.</p> | ||||
| {{/if}} | ||||
| 
 | ||||
| {{#if failed}} | ||||
|   <p>Sorry, there wasn an error upgrading Discourse. Please check the logs.</p> | ||||
| {{/if}} | ||||
| 
 | ||||
| {{#if upToDate}} | ||||
|   <p>{{name}} is at the newest version {{fmt-commit version url}}.</p> | ||||
| {{else}} | ||||
|   <div style='clear: both'> | ||||
|     <button {{action start}} {{bind-attr disabled="upgrading"}} class='btn'>{{upgradeButtonText}}</button> | ||||
|     {{#if upgrading}} | ||||
|       <button {{action resetUpgrade}} class="btn unlock">Reset Upgrade</button> | ||||
|     {{/if}} | ||||
|   </div> | ||||
| {{/if}} | ||||
| 
 | ||||
| <div class='logs'>{{output}}</div> | ||||
|  | @ -0,0 +1,4 @@ | |||
| /* global ic */ | ||||
| export default function ajax(){ | ||||
|   return ic.ajax.apply(null, arguments); | ||||
| } | ||||
|  | @ -0,0 +1,13 @@ | |||
| export default Em.View.extend({ | ||||
|   _showOnInsert: function() { | ||||
|     var self = this; | ||||
|     self.set('runner', Em.run.later(function() { | ||||
|       self.$('h3').show(); | ||||
|     }, 200)); | ||||
|   }.on('didInsertElement'), | ||||
| 
 | ||||
|   _cancelFade: function() { | ||||
|     Em.run.cancel(this.get('runner')); | ||||
|   }.on('willDestroyElement') | ||||
| }); | ||||
| 
 | ||||
|  | @ -0,0 +1,12 @@ | |||
| export default Em.View.extend({ | ||||
| 
 | ||||
|   _insertedIntoDOM: function() { | ||||
|     this.set('controller.autoRefresh', true); | ||||
|   }.on('didInsertElement'), | ||||
| 
 | ||||
|   _removedFromDOM: function() { | ||||
|     this.set('controller.autoRefresh', false); | ||||
|   }.on('willDestroyElement') | ||||
| 
 | ||||
| }); | ||||
| 
 | ||||
|  | @ -0,0 +1,14 @@ | |||
| { | ||||
|   "name": "ember-app-kit", | ||||
|   "dependencies": { | ||||
|     "bootstrap": "~3.1.1", | ||||
|     "handlebars": "~1.1.2", | ||||
|     "jquery": "~1.9.1", | ||||
|     "ember": "~1.5.0", | ||||
|     "ember-resolver": "git://github.com/stefanpenner/ember-jj-abrams-resolver.git#master", | ||||
|     "ic-ajax": "~1.0.4", | ||||
|     "loader.js": "git://github.com/stefanpenner/loader.js", | ||||
|     "ember-load-initializers": "git://github.com/stefanpenner/ember-load-initializers.git#0.0.1", | ||||
|     "message-bus": "https://raw.githubusercontent.com/SamSaffron/message_bus.git" | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,12 @@ | |||
| // Put general configuration here. This file is included
 | ||||
| // in both production and development BEFORE Ember is
 | ||||
| // loaded.
 | ||||
| //
 | ||||
| // For example to enable a feature on a canary build you
 | ||||
| // might do:
 | ||||
| //
 | ||||
| // window.ENV = {FEATURES: {'with-controller': true}};
 | ||||
| 
 | ||||
| window.ENV = window.ENV || {}; | ||||
| 
 | ||||
| window.ENV.MODEL_FACTORY_INJECTIONS = true; | ||||
|  | @ -0,0 +1,14 @@ | |||
| // Put your development configuration here.
 | ||||
| //
 | ||||
| // This is useful when using a separate API 
 | ||||
| // endpoint in development than in production.
 | ||||
| //
 | ||||
| // window.ENV.public_key = '123456'
 | ||||
| 
 | ||||
| window.ENV.APP = { | ||||
|   LOG_ACTIVE_GENERATION:    true, | ||||
|   LOG_MODULE_RESOLVER:      true, | ||||
|   LOG_TRANSITIONS:          true, | ||||
|   LOG_TRANSITIONS_INTERNAL: true, | ||||
|   LOG_VIEW_LOOKUPS:         true | ||||
| }; | ||||
|  | @ -0,0 +1,6 @@ | |||
| // Put your production configuration here.
 | ||||
| //
 | ||||
| // This is useful when using a separate API
 | ||||
| // endpoint in development than in production.
 | ||||
| //
 | ||||
| // window.ENV.public_key = '123456'
 | ||||
|  | @ -0,0 +1,6 @@ | |||
| // Put your test configuration here.
 | ||||
| //
 | ||||
| // This is useful when using a separate API
 | ||||
| // endpoint in test than in production.
 | ||||
| //
 | ||||
| // window.ENV.public_key = '123456'
 | ||||
|  | @ -0,0 +1,57 @@ | |||
| { | ||||
|   "name": "docker-manager", | ||||
|   "namespace": "docker-manager", | ||||
|   "APIMethod": "proxy", | ||||
|   "proxyURL": "http://localhost:3000", | ||||
|   "proxyPath": "/admin/docker", | ||||
|   "docURL": "http://localhost:8000/docs/index.html", | ||||
|   "version": "0.0.0", | ||||
|   "private": true, | ||||
|   "directories": { | ||||
|     "doc": "doc", | ||||
|     "test": "test" | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "start": "grunt server", | ||||
|     "build": "grunt build:debug", | ||||
|     "test": "grunt test:ci", | ||||
|     "postinstall": "bower install" | ||||
|   }, | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|     "url": "git://github.com/stefanpenner/ember-app-kit.git" | ||||
|   }, | ||||
|   "author": "", | ||||
|   "license": "MIT", | ||||
|   "devDependencies": { | ||||
|     "express": "~3.4.8", | ||||
|     "lockfile": "~0.4.2", | ||||
|     "bower": "~1.3.2", | ||||
|     "grunt": "~0.4.2", | ||||
|     "grunt-cli": "~0.1.9", | ||||
|     "load-grunt-config": "~0.7.0", | ||||
|     "grunt-contrib-watch": "~0.5.3", | ||||
|     "grunt-contrib-copy": "~0.4.1", | ||||
|     "grunt-contrib-concat": "~0.3.0", | ||||
|     "grunt-contrib-clean": "~0.5.0", | ||||
|     "grunt-contrib-jshint": "~0.8.0", | ||||
|     "grunt-contrib-uglify": "~0.2.7", | ||||
|     "grunt-contrib-cssmin": "~0.6.2", | ||||
|     "grunt-preprocess": "~3.0.1", | ||||
|     "grunt-es6-module-transpiler": "~0.6.0", | ||||
|     "grunt-concat-sourcemap": "~0.4.0", | ||||
|     "grunt-concurrent": "~0.4.3", | ||||
|     "grunt-usemin": "~0.1.13", | ||||
|     "grunt-rev": "~0.1.0", | ||||
|     "grunt-ember-templates": "~0.4.18", | ||||
|     "grunt-contrib-testem": "~0.5.14", | ||||
|     "express-namespace": "~0.1.1", | ||||
|     "request": "~2.33.0", | ||||
|     "loom-generators-ember-appkit": "~1.0.5", | ||||
|     "originate": "~0.1.5", | ||||
|     "loom": "~3.1.2", | ||||
|     "connect-livereload": "~0.3.1", | ||||
|     "grunt-es6-import-validate": "0.0.6", | ||||
|     "grunt-sass": "~0.12.1" | ||||
|   } | ||||
| } | ||||
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 33 KiB | 
|  | @ -0,0 +1,15 @@ | |||
| <?xml version="1.0"?> | ||||
| <!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd"> | ||||
| <cross-domain-policy> | ||||
|     <!-- Read this: www.adobe.com/devnet/articles/crossdomain_policy_file_spec.html --> | ||||
| 
 | ||||
|     <!-- Most restrictive policy: --> | ||||
|     <site-control permitted-cross-domain-policies="none"/> | ||||
| 
 | ||||
|     <!-- Least restrictive policy: --> | ||||
|     <!-- | ||||
|     <site-control permitted-cross-domain-policies="all"/> | ||||
|     <allow-access-from domain="*" to-ports="*" secure="false"/> | ||||
|     <allow-http-request-headers-from domain="*" headers="*" secure="false"/> | ||||
|     --> | ||||
| </cross-domain-policy> | ||||
|  | @ -0,0 +1,15 @@ | |||
| # humanstxt.org/ | ||||
| # The humans responsible & technology colophon | ||||
| 
 | ||||
| # TEAM | ||||
| 
 | ||||
|     <name> -- <role> -- <twitter> | ||||
| 
 | ||||
| # THANKS | ||||
| 
 | ||||
|     <name> | ||||
| 
 | ||||
| # TECHNOLOGY COLOPHON | ||||
| 
 | ||||
|     HTML5, CSS3 | ||||
|     Ember.js | ||||
|  | @ -0,0 +1,3 @@ | |||
| # robotstxt.org/ | ||||
| 
 | ||||
| User-agent: * | ||||
|  | @ -0,0 +1,19 @@ | |||
| /** | ||||
|  * This is dummy file that exists for the sole purpose  | ||||
|  * of allowing tests to run directly in the browser as | ||||
|  * well as by Testem. | ||||
|  * | ||||
|  * Testem is configured to run tests directly against  | ||||
|  * the test build of index.html, which requires a | ||||
|  * snippet to load the testem.js file: | ||||
|  *   <script src="/testem.js"></script> | ||||
|  * This has to go after the qunit framework and app | ||||
|  * tests are loaded. | ||||
|  * | ||||
|  * Testem internally supplies this file. However, if you | ||||
|  * run the tests directly in the browser (localhost:8000/tests), | ||||
|  * this file does not exist. | ||||
|  * | ||||
|  * Hence the purpose of this fake file. This file is served | ||||
|  * directly from the express server to satisify the script load. | ||||
|  */ | ||||
|  | @ -0,0 +1,27 @@ | |||
| { | ||||
|   "node" : true, | ||||
|   "browser" : false, | ||||
|   "boss" : true, | ||||
|   "curly": false, | ||||
|   "debug": false, | ||||
|   "devel": false, | ||||
|   "eqeqeq": true, | ||||
|   "evil": true, | ||||
|   "forin": false, | ||||
|   "immed": false, | ||||
|   "laxbreak": false, | ||||
|   "newcap": true, | ||||
|   "noarg": true, | ||||
|   "noempty": false, | ||||
|   "nonew": false, | ||||
|   "nomen": false, | ||||
|   "onevar": false, | ||||
|   "plusplus": false, | ||||
|   "regexp": false, | ||||
|   "undef": true, | ||||
|   "sub": true, | ||||
|   "strict": false, | ||||
|   "white": false, | ||||
|   "eqnull": true, | ||||
|   "esnext": true | ||||
| } | ||||
|  | @ -0,0 +1,131 @@ | |||
| module.exports = function(grunt) { | ||||
|   var express = require('express'), | ||||
|       lockFile = require('lockfile'), | ||||
|       Helpers = require('./helpers'), | ||||
|       fs = require('fs'), | ||||
|       path = require('path'), | ||||
|       request = require('request'); | ||||
| 
 | ||||
|   /** | ||||
|   Task for serving the static files. | ||||
| 
 | ||||
|   Note: The expressServer:debug task looks for files in multiple directories. | ||||
|   */ | ||||
|   grunt.registerTask('expressServer', function(target, proxyMethodToUse) { | ||||
|     // Load namespace module before creating the server
 | ||||
|     require('express-namespace'); | ||||
| 
 | ||||
|     var app = express(), | ||||
|         done = this.async(), | ||||
|         proxyMethod = proxyMethodToUse || grunt.config('express-server.options.APIMethod'); | ||||
| 
 | ||||
|     app.use(lock); | ||||
|     app.use(express.compress()); | ||||
| 
 | ||||
|     if (proxyMethod === 'stub') { | ||||
|       grunt.log.writeln('Using API Stub'); | ||||
| 
 | ||||
|       // Load API stub routes
 | ||||
|       app.use(express.json()); | ||||
|       app.use(express.urlencoded()); | ||||
|       require('../api-stub/routes')(app); | ||||
|     } else if (proxyMethod === 'proxy') { | ||||
|       var proxyURL = grunt.config('express-server.options.proxyURL'), | ||||
|           proxyPath = grunt.config('express-server.options.proxyPath') || '/api'; | ||||
|       grunt.log.writeln('Proxying API requests matching ' + proxyPath + '/* to: ' + proxyURL); | ||||
| 
 | ||||
|       // Use API proxy
 | ||||
|       app.all(proxyPath + '/*', passThrough(proxyURL)); | ||||
|       app.all('/message-bus/*', passThrough(proxyURL)); | ||||
|       app.all('/session/*', passThrough(proxyURL)); | ||||
|     } | ||||
| 
 | ||||
|     if (target === 'debug') { | ||||
|       // For `expressServer:debug`
 | ||||
| 
 | ||||
|       // Add livereload middleware after lock middleware if enabled
 | ||||
|       if (Helpers.isPackageAvailable("connect-livereload")) { | ||||
|         var liveReloadPort = grunt.config('watch.options.livereload'); | ||||
|         app.use(require("connect-livereload")({port: liveReloadPort})); | ||||
|       } | ||||
| 
 | ||||
|       // YUIDoc serves static HTML, so just serve the index.html
 | ||||
|       app.all('/docs', function(req, res) { res.redirect(302, '/docs/index.html'); }); | ||||
|       app.use(static({ urlRoot: '/docs', directory: 'docs' })); | ||||
| 
 | ||||
|       // These three lines simulate what the `copy:assemble` task does
 | ||||
|       app.use(static({ urlRoot: '/config', directory: 'config' })); | ||||
|       app.use(static({ urlRoot: '/vendor', directory: 'vendor' })); | ||||
|       app.use(static({ directory: 'public' })); | ||||
|       app.use(static({ urlRoot: '/tests', directory: 'tests' })); // For test-helper.js and test-loader.js
 | ||||
|       app.use(static({ directory: 'tmp/result' })); | ||||
|       app.use(static({ file: 'tmp/result/index.html', ignoredFileExtensions: /\.\w{1,5}$/ })); // Gotta catch 'em all
 | ||||
|     } else { | ||||
|       // For `expressServer:dist`
 | ||||
| 
 | ||||
|       app.use(lock); | ||||
|       app.use(static({ directory: 'dist' })); | ||||
|       app.use(static({ file: 'dist/index.html', ignoredFileExtensions: /\.\w{1,5}$/ })); // Gotta catch 'em all
 | ||||
|     } | ||||
| 
 | ||||
|     var port = parseInt(process.env.PORT || 8000, 10); | ||||
|     if (isNaN(port) || port < 1 || port > 65535) { | ||||
|       grunt.fail.fatal('The PORT environment variable of ' + process.env.PORT + ' is not valid.'); | ||||
|     } | ||||
|     app.listen(port); | ||||
|     grunt.log.ok('Started development server on port %d.', port); | ||||
|     if (!this.flags.keepalive) { done(); } | ||||
|   }); | ||||
| 
 | ||||
| 
 | ||||
|   // Middleware
 | ||||
|   // ==========
 | ||||
| 
 | ||||
|   function lock(req, res, next) { // Works with tasks/locking.js
 | ||||
|     (function retry() { | ||||
|       if (lockFile.checkSync('tmp/connect.lock')) { | ||||
|         setTimeout(retry, 30); | ||||
|       } else { next(); } | ||||
|     })(); | ||||
|   } | ||||
| 
 | ||||
|   function static(options) { | ||||
|     return function(req, res, next) { // Gotta catch 'em all (and serve index.html)
 | ||||
|       var filePath = ""; | ||||
|       if (options.directory) { | ||||
|         var regex = new RegExp('^' + (options.urlRoot || '')); | ||||
|         // URL must begin with urlRoot's value
 | ||||
|         if (!req.path.match(regex)) { next(); return; } | ||||
|         filePath = options.directory + req.path.replace(regex, ''); | ||||
|       } else if (options.file) { | ||||
|         filePath = options.file; | ||||
|       } else { throw new Error('static() isn\'t properly configured!'); } | ||||
| 
 | ||||
|       fs.stat(filePath, function(err, stats) { | ||||
|         if (err) { next(); return; } // Not a file, not a folder => can't handle it
 | ||||
| 
 | ||||
|         if (options.ignoredFileExtensions) { | ||||
|           if (options.ignoredFileExtensions.test(req.path)) { | ||||
|             res.send(404, {error: 'Resource not found'}); | ||||
|             return; // Do not serve index.html
 | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|         // Is it a directory? If so, search for an index.html in it.
 | ||||
|         if (stats.isDirectory()) { filePath = path.join(filePath, 'index.html'); } | ||||
| 
 | ||||
|         // Serve the file
 | ||||
|         res.sendfile(filePath, function(err) { | ||||
|           if (err) { next(); return; } | ||||
|           grunt.verbose.ok('Served: ' + filePath); | ||||
|         }); | ||||
|       }); | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   function passThrough(target) { | ||||
|     return function(req, res) { | ||||
|       req.pipe(request(target+req.url)).pipe(res); | ||||
|     }; | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,60 @@ | |||
| var grunt = require('grunt'), | ||||
|     _ = grunt.util._, | ||||
|     Helpers = {}; | ||||
| 
 | ||||
| // List of package requisits for tasks
 | ||||
| // Notated in conjunctive normal form (CNF)
 | ||||
| // e.g. ['a', ['b', 'alternative-to-b']]
 | ||||
| var taskRequirements = { | ||||
|   coffee: ['grunt-contrib-coffee'], | ||||
|   compass: ['grunt-contrib-compass'], | ||||
|   sass: [['grunt-sass', 'grunt-contrib-sass']], | ||||
|   less: ['grunt-contrib-less'], | ||||
|   stylus: ['grunt-contrib-stylus'], | ||||
|   emberTemplates: ['grunt-ember-templates'], | ||||
|   emblem: ['grunt-emblem'], | ||||
|   emberscript: ['grunt-ember-script'], | ||||
|   imagemin: ['grunt-contrib-imagemin'], | ||||
|   htmlmin: ['grunt-contrib-htmlmin'], | ||||
|   fancySprites: ['grunt-fancy-sprites'], | ||||
|   autoprefixer: ['grunt-autoprefixer'], | ||||
|   rev: ['grunt-rev'], | ||||
|   'validate-imports': ['grunt-es6-import-validate'], | ||||
|   yuidoc: ['grunt-contrib-yuidoc'] | ||||
| }; | ||||
| 
 | ||||
| // Task fallbacks
 | ||||
| // e.g. 'a': ['fallback-a-step-1', 'fallback-a-step-2']
 | ||||
| var taskFallbacks = { | ||||
|   'imagemin': 'copy:imageminFallback' | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| Helpers.filterAvailableTasks = function(tasks){ | ||||
|   tasks = tasks.map(function(taskName) { | ||||
|     // Maps to task name or fallback if task is unavailable
 | ||||
| 
 | ||||
|     var baseName = taskName.split(':')[0]; // e.g. 'coffee' for 'coffee:compile'
 | ||||
|     var reqs = taskRequirements[baseName]; | ||||
|     var isAvailable = Helpers.isPackageAvailable(reqs); | ||||
|     return isAvailable ? taskName : taskFallbacks[taskName]; | ||||
|   }); | ||||
| 
 | ||||
|   return _.flatten(_.compact(tasks)); // Remove undefined's and flatten it
 | ||||
| }; | ||||
| 
 | ||||
| Helpers.isPackageAvailable = function(pkgNames) { | ||||
|   if (!pkgNames) return true;  // packages are assumed to exist
 | ||||
| 
 | ||||
|   if (!_.isArray(pkgNames)) { pkgNames = [pkgNames]; } | ||||
| 
 | ||||
|   return _.every(pkgNames, function(pkgNames) { | ||||
|     if (!_.isArray(pkgNames)) { pkgNames = [pkgNames]; } | ||||
| 
 | ||||
|     return _.any(pkgNames, function(pkgName) { | ||||
|       return !!Helpers.pkg.devDependencies[pkgName]; | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| module.exports = Helpers; | ||||
|  | @ -0,0 +1,14 @@ | |||
| var lockFile = require('lockfile'); | ||||
| 
 | ||||
| module.exports = function(grunt) { | ||||
|   grunt.registerTask('lock', 'Set semaphore for connect server to wait on.', function() { | ||||
|     grunt.file.mkdir('tmp'); | ||||
|     lockFile.lockSync('tmp/connect.lock'); | ||||
|     grunt.log.writeln("Locked - Development server won't answer incoming requests until App Kit is done updating."); | ||||
|   }); | ||||
| 
 | ||||
|   grunt.registerTask('unlock', 'Release semaphore that connect server waits on.', function() { | ||||
|     lockFile.unlockSync('tmp/connect.lock'); | ||||
|     grunt.log.writeln("Unlocked - Development server now handles requests."); | ||||
|   }); | ||||
| }; | ||||
|  | @ -0,0 +1,5 @@ | |||
| module.exports = { | ||||
|   app: { | ||||
|     src: 'tmp/result/assets/**/*.css' | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,4 @@ | |||
| module.exports = { | ||||
|   'debug': ['tmp'], | ||||
|   'dist': ['tmp', 'dist'] | ||||
| }; | ||||
|  | @ -0,0 +1,42 @@ | |||
| // CoffeeScript compilation. This must be enabled by modification
 | ||||
| // of Gruntfile.js.
 | ||||
| //
 | ||||
| // The `bare` option is used since this file will be transpiled
 | ||||
| // anyway. In CoffeeScript files, you need to escape out for
 | ||||
| // some ES6 features like import and export. For example:
 | ||||
| //
 | ||||
| // `import User from 'appkit/models/user'`
 | ||||
| //
 | ||||
| // Post = Em.Object.extend
 | ||||
| //   init: (userId) ->
 | ||||
| //     @set 'user', User.findById(userId)
 | ||||
| //
 | ||||
| // `export default Post`
 | ||||
| //
 | ||||
| 
 | ||||
| module.exports = { | ||||
|   "test": { | ||||
|     options: { | ||||
|       bare: true | ||||
|     }, | ||||
|     files: [{ | ||||
|       expand: true, | ||||
|       cwd: 'tests/', | ||||
|       src: '**/*.coffee', | ||||
|       dest: 'tmp/javascript/tests', | ||||
|       ext: '.js' | ||||
|     }] | ||||
|   }, | ||||
|   "app": { | ||||
|     options: { | ||||
|       bare: true | ||||
|     }, | ||||
|     files: [{ | ||||
|       expand: true, | ||||
|       cwd: 'app/', | ||||
|       src: '**/*.coffee', | ||||
|       dest: 'tmp/javascript/app', | ||||
|       ext: '.js' | ||||
|     }] | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,17 @@ | |||
| module.exports = { | ||||
|   options: { | ||||
|     sassDir: "app/styles", | ||||
|     cssDir: "tmp/result/assets", | ||||
|     generatedImagesDir: "tmp/result/assets/images/generated", | ||||
|     imagesDir: "public/assets/images", | ||||
|     javascriptsDir: "app", | ||||
|     fontsDir: "public/assets/fonts", | ||||
|     importPath: ["vendor"], | ||||
|     httpImagesPath: "/assets/images", | ||||
|     httpGeneratedImagesPath: "/assets/images/generated", | ||||
|     httpFontsPath: "/assets/fonts", | ||||
|     relativeAssets: false, | ||||
|     debugInfo: true | ||||
|   }, | ||||
|   compile: {} | ||||
| }; | ||||
|  | @ -0,0 +1,25 @@ | |||
| module.exports = { | ||||
|   app: { | ||||
|     src: ['tmp/transpiled/app/**/*.js'], | ||||
|     dest: 'tmp/result/assets/app.js', | ||||
|     options: { | ||||
|       sourcesContent: true | ||||
|     }, | ||||
|   }, | ||||
| 
 | ||||
|   config: { | ||||
|     src: ['tmp/result/config/**/*.js'], | ||||
|     dest: 'tmp/result/assets/config.js', | ||||
|     options: { | ||||
|       sourcesContent: true | ||||
|     }, | ||||
|   }, | ||||
| 
 | ||||
|   test: { | ||||
|     src: 'tmp/transpiled/tests/**/*.js', | ||||
|     dest: 'tmp/result/tests/tests.js', | ||||
|     options: { | ||||
|       sourcesContent: true | ||||
|     } | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,6 @@ | |||
| module.exports = { | ||||
|   // Remaining configuration done in Gruntfile.js
 | ||||
|   options: { | ||||
|     logConcurrentOutput: true | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,72 @@ | |||
| module.exports = { | ||||
| 
 | ||||
|   // Note: These tasks are listed in the order in which they will run.
 | ||||
| 
 | ||||
|   javascriptToTmp: { | ||||
|     files: [{ | ||||
|       expand: true, | ||||
|       cwd: 'app', | ||||
|       src: '**/*.js', | ||||
|       dest: 'tmp/javascript/app' | ||||
|     }, | ||||
|     { | ||||
|       expand: true, | ||||
|       cwd: 'tests', | ||||
|       src: ['**/*.js', '!test-helper.js', '!test-loader.js'], | ||||
|       dest: 'tmp/javascript/tests/' | ||||
|     }] | ||||
|   }, | ||||
| 
 | ||||
|   cssToResult: { | ||||
|     expand: true, | ||||
|     cwd: 'app/styles', | ||||
|     src: ['**/*.css'], | ||||
|     dest: 'tmp/result/assets' | ||||
|   }, | ||||
| 
 | ||||
|   // Assembles everything in `tmp/result`.
 | ||||
|   // The sole purpose of this task is to keep things neat. Gathering everything in one
 | ||||
|   // place (tmp/dist) enables the subtasks of dist to only look there. Note: However,
 | ||||
|   // for normal development this is done on the fly by the development server.
 | ||||
|   assemble: { | ||||
|     files: [{ | ||||
|       expand: true, | ||||
|       cwd: 'tests', | ||||
|       src: ['test-helper.js', 'test-loader.js'], | ||||
|       dest: 'tmp/result/tests/' | ||||
|     }, { | ||||
|       expand: true, | ||||
|       cwd: 'public', | ||||
|       src: ['**'], | ||||
|       dest: 'tmp/result/' | ||||
|     }, { | ||||
|       src: ['vendor/**/*.js', 'vendor/**/*.css'], | ||||
|       dest: 'tmp/result/' | ||||
|     }, { | ||||
|       src: ['config/environment.js', 'config/environments/production.js'], | ||||
|       dest: 'tmp/result/' | ||||
|     } | ||||
| 
 | ||||
|     ] | ||||
|   }, | ||||
| 
 | ||||
|   imageminFallback: { | ||||
|     files: '<%= imagemin.dist.files %>' | ||||
|   }, | ||||
| 
 | ||||
|   dist: { | ||||
|     files: [{ | ||||
|       expand: true, | ||||
|       cwd: 'tmp/result', | ||||
|       src: [ | ||||
|         '**', | ||||
|         '!**/*.{css,js}', // Already handled by concat
 | ||||
|         '!**/*.{png,gif,jpg,jpeg}', // Already handled by imagemin
 | ||||
|         '!tests/**/*', // No tests, please
 | ||||
|         '!**/*.map' // No source maps
 | ||||
|       ], | ||||
|       filter: 'isFile', | ||||
|       dest: 'dist/' | ||||
|     }] | ||||
|   }, | ||||
| }; | ||||
|  | @ -0,0 +1,22 @@ | |||
| var grunt = require('grunt'); | ||||
| 
 | ||||
| module.exports = { | ||||
|   options: { | ||||
|     templateBasePath: /app\//, | ||||
|     templateFileExtensions: /\.(hbs|hjs|handlebars)/, | ||||
|     templateRegistration: function(name, template) { | ||||
|       return grunt.config.process("define('<%= package.namespace %>/") + name + "', ['exports'], function(__exports__){ __exports__['default'] = " + template + "; });"; | ||||
|     } | ||||
|   }, | ||||
|   debug: { | ||||
|     options: { | ||||
|       precompile: false | ||||
|     }, | ||||
|     src: "app/**/*.{hbs,hjs,handlebars}", | ||||
|     dest: "tmp/result/assets/templates.js" | ||||
|   }, | ||||
|   dist: { | ||||
|     src: "<%= emberTemplates.debug.src %>", | ||||
|     dest: "<%= emberTemplates.debug.dest %>" | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,42 @@ | |||
| // EmberScript compilation. This must be enabled by modification
 | ||||
| // of Gruntfile.js.
 | ||||
| //
 | ||||
| // The `bare` option is used since this file will be transpiled
 | ||||
| // anyway. In EmberScript files, you need to escape out for
 | ||||
| // some ES6 features like import and export. For example:
 | ||||
| //
 | ||||
| // `import User from 'appkit/models/user'`
 | ||||
| //
 | ||||
| // class Post
 | ||||
| //   init: (userId) ->
 | ||||
| //     @user = User.findById(userId)
 | ||||
| //
 | ||||
| // `export default Post`
 | ||||
| //
 | ||||
| 
 | ||||
| module.exports = { | ||||
|   "test": { | ||||
|     options: { | ||||
|       bare: true | ||||
|     }, | ||||
|     files: [{ | ||||
|       expand: true, | ||||
|       cwd: 'tests/', | ||||
|       src: '**/*.em', | ||||
|       dest: 'tmp/javascript/tests', | ||||
|       ext: '.js' | ||||
|     }] | ||||
|   }, | ||||
|   "app": { | ||||
|     options: { | ||||
|       bare: true | ||||
|     }, | ||||
|     files: [{ | ||||
|       expand: true, | ||||
|       cwd: 'app/', | ||||
|       src: '**/*.em', | ||||
|       dest: 'tmp/javascript/app', | ||||
|       ext: '.js' | ||||
|     }] | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,16 @@ | |||
| module.exports = { | ||||
|   compile: { | ||||
|     files: { | ||||
|       "tmp/result/assets/templates.js": ['app/templates/**/*.{emblem,hbs,hjs,handlebars}'] | ||||
|     }, | ||||
|     options: { | ||||
|       root: 'app/templates/', | ||||
|       dependencies: { | ||||
|         jquery: 'vendor/jquery/jquery.js', | ||||
|         ember: 'vendor/ember/ember.js', | ||||
|         handlebars: 'vendor/handlebars/handlebars.js', | ||||
|         emblem: 'vendor/emblem.js/emblem.js' | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,9 @@ | |||
| var grunt = require('grunt'); | ||||
| 
 | ||||
| module.exports = { | ||||
| 	options: { | ||||
| 		APIMethod: "<%= package.APIMethod %>",		// stub or proxy
 | ||||
| 		proxyURL: "<%= package.proxyURL %>",		// URL to the API server, if using APIMethod: 'proxy'
 | ||||
| 		proxyPath: "<%= package.proxyPath %>"		// path for the API endpoints, default is '/api', if using APIMethod: 'proxy'
 | ||||
| 	} | ||||
| }; | ||||
|  | @ -0,0 +1,22 @@ | |||
| var path = require('path'); | ||||
| 
 | ||||
| module.exports = { | ||||
|   create: { | ||||
|     destStyles: 'tmp/sprites', | ||||
|     destSpriteSheets: 'tmp/result/assets/sprites', | ||||
|     files: [{ | ||||
|         src: ['app/sprites/**/*.{png,jpg,jpeg}', '!app/sprites/**/*@2x.{png,jpg,jpeg}'], | ||||
|         spriteSheetName: '1x', | ||||
|         spriteName: function(name) { | ||||
|           return path.basename(name, path.extname(name)); | ||||
|         } | ||||
|       }, { | ||||
|         src: 'app/sprites/**/*@2x.{png,jpg,jpeg}', | ||||
|         spriteSheetName: '2x', | ||||
|         spriteName: function(name) { | ||||
|           return path.basename(name, path.extname(name)).replace(/@2x/, ''); | ||||
|         } | ||||
|       } | ||||
|     ] | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,12 @@ | |||
| module.exports = { | ||||
|   dist: { | ||||
|     options: { | ||||
|       removeComments: true, | ||||
|       collapseWhitespace: true | ||||
|     }, | ||||
|     files: [{ | ||||
|       src: 'dist/index.html', | ||||
|       dest: 'dist/index.html' | ||||
|     }] | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,13 @@ | |||
| module.exports = { | ||||
|   dist: { | ||||
|     options: { | ||||
|       cache: false | ||||
|     }, | ||||
|     files: [{ | ||||
|       expand: true, | ||||
|       cwd: 'tmp/result', | ||||
|       src: '**/*.{png,gif,jpg,jpeg}', | ||||
|       dest: 'dist/' | ||||
|     }] | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,27 @@ | |||
| module.exports = { | ||||
|   app: { | ||||
|     src: [ | ||||
|       'app/**/*.js' | ||||
|     ], | ||||
|     options: { jshintrc: '.jshintrc' } | ||||
|   }, | ||||
| 
 | ||||
|   tooling: { | ||||
|     src: [ | ||||
|       'Gruntfile.js', | ||||
|       'tasks/**/*.js' | ||||
|     ], | ||||
|     options: { jshintrc: 'tasks/.jshintrc' } | ||||
|   }, | ||||
| 
 | ||||
|   tests: { | ||||
|     src: [ | ||||
|       'tests/**/*.js', | ||||
|     ], | ||||
|     options: { jshintrc: 'tests/.jshintrc' } | ||||
|   }, | ||||
| 
 | ||||
|   options: { | ||||
|     force: true | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,11 @@ | |||
| module.exports = { | ||||
|   compile: { | ||||
|     files: [{ | ||||
|       expand: true, | ||||
|       cwd: 'app/styles', | ||||
|       src: ['**/*.less', '!**/_*.less'], | ||||
|       dest: 'tmp/result/assets/', | ||||
|       ext: '.css' | ||||
|     }] | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,18 @@ | |||
| module.exports = { | ||||
|   indexHTMLDebugApp: { | ||||
|     src : 'app/index.html', dest : 'tmp/result/index.html', | ||||
|     options: { context: { dist: false, tests: false } } | ||||
|   }, | ||||
|   indexHTMLDebugTests: { | ||||
|     src : 'app/index.html', dest : 'tmp/result/tests/index.html', | ||||
|     options: { context: { dist: false, tests: true } } | ||||
|   }, | ||||
|   indexHTMLDistApp: { | ||||
|     src : 'app/index.html', dest : 'tmp/result/index.html', | ||||
|     options: { context: { dist: true, tests: false } } | ||||
|   }, | ||||
|   indexHTMLDistTests: { | ||||
|     src : 'app/index.html', dest : 'tmp/result/tests/index.html', | ||||
|     options: { context: { dist: true, tests: true } } | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,12 @@ | |||
| module.exports = { | ||||
|   dist: { | ||||
|     files: { | ||||
|       src: [ | ||||
|         'dist/assets/config.min.js', | ||||
|         'dist/assets/app.min.js', | ||||
|         'dist/assets/vendor.min.js', | ||||
|         'dist/assets/app.min.css' | ||||
|       ] | ||||
|     } | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,11 @@ | |||
| module.exports = { | ||||
|   compile: { | ||||
|     files: [{ | ||||
|       expand: true, | ||||
|       cwd: 'app/styles', | ||||
|       src: ['**/*.{scss,sass}', '!**/_*.{scss,sass}'], | ||||
|       dest: 'tmp/result/assets/', | ||||
|       ext: '.css' | ||||
|     }] | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,11 @@ | |||
| module.exports = { | ||||
|   compile: { | ||||
|     files: [{ | ||||
|       expand: true, | ||||
|       cwd: 'app/styles', | ||||
|       src: ['**/*.styl', '!**/_*.styl'], | ||||
|       dest: 'tmp/result/assets/', | ||||
|       ext: '.css' | ||||
|     }] | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,55 @@ | |||
| // See https://npmjs.org/package/grunt-contrib-testem for more config options
 | ||||
| module.exports = { | ||||
|   basic: { | ||||
|     options: { | ||||
|       parallel: 2, | ||||
|       framework: 'qunit', | ||||
|       port: (parseInt(process.env.PORT || 7358, 10) + 1), | ||||
|       test_page: 'tmp/result/tests/index.html', | ||||
|       routes: { | ||||
|         '/tests/tests.js': 'tmp/result/tests/tests.js', | ||||
|         '/assets/app.js': 'tmp/result/assets/app.js', | ||||
|         '/assets/templates.js': 'tmp/result/assets/templates.js', | ||||
|         '/assets/app.css': 'tmp/result/assets/app.css' | ||||
|       }, | ||||
|       src_files: [ | ||||
|         'tmp/result/**/*.js' | ||||
|       ], | ||||
|       launch_in_dev: ['PhantomJS', 'Chrome'], | ||||
|       launch_in_ci: ['PhantomJS', 'Chrome'], | ||||
|     } | ||||
|   }, | ||||
|   browsers: { | ||||
|     options: { | ||||
|       parallel: 8, | ||||
|       framework: 'qunit', | ||||
|       port: (parseInt(process.env.PORT || 7358, 10) + 1), | ||||
|       test_page: 'tmp/result/tests/index.html', | ||||
|       routes: { | ||||
|         '/tests/tests.js': 'tmp/result/tests/tests.js', | ||||
|         '/assets/app.js': 'tmp/result/assets/app.js', | ||||
|         '/assets/templates.js': 'tmp/result/assets/templates.js', | ||||
|         '/assets/app.css': 'tmp/result/assets/app.css' | ||||
|       }, | ||||
|       src_files: [ | ||||
|         'tmp/result/**/*.js' | ||||
|       ], | ||||
|       launch_in_dev: ['PhantomJS', | ||||
|                      'Chrome', | ||||
|                      'ChromeCanary', | ||||
|                      'Firefox', | ||||
|                      'Safari', | ||||
|                      'IE7', | ||||
|                      'IE8', | ||||
|                      'IE9'], | ||||
|       launch_in_ci: ['PhantomJS', | ||||
|                      'Chrome', | ||||
|                      'ChromeCanary', | ||||
|                      'Firefox', | ||||
|                      'Safari', | ||||
|                      'IE7', | ||||
|                      'IE8', | ||||
|                      'IE9'], | ||||
|     } | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,28 @@ | |||
| var grunt = require('grunt'); | ||||
| 
 | ||||
| module.exports = { | ||||
|   "tests": { | ||||
|     type: 'amd', | ||||
|     moduleName: function(path) { | ||||
|       return grunt.config.process('<%= package.namespace %>/tests/') + path; | ||||
|     }, | ||||
|     files: [{ | ||||
|       expand: true, | ||||
|       cwd: 'tmp/javascript/tests/', | ||||
|       src: '**/*.js', | ||||
|       dest: 'tmp/transpiled/tests/' | ||||
|     }] | ||||
|   }, | ||||
|   "app": { | ||||
|     type: 'amd', | ||||
|     moduleName: function(path) { | ||||
|       return grunt.config.process('<%= package.namespace %>/') + path; | ||||
|     }, | ||||
|     files: [{ | ||||
|       expand: true, | ||||
|       cwd: 'tmp/javascript/app/', | ||||
|       src: '**/*.js', | ||||
|       dest: 'tmp/transpiled/app/' | ||||
|     }] | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,3 @@ | |||
| module.exports = { | ||||
|   html: ['dist/index.html'], | ||||
| }; | ||||
|  | @ -0,0 +1,6 @@ | |||
| module.exports = { | ||||
|   html: 'tmp/result/index.html', | ||||
|   options: { | ||||
|     dest: 'dist/' | ||||
|   } | ||||
| }; | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue