Ember upgrade to 3.6.1 (#49)
* Ember upgrade to 3.6.1 * ember-ajax printing deprecations; stop using it * Use tag versions when possible instead of hashes * Remove inline scripts for CSP * dump `git describe` errors * Commit hashes still needed for github diff link * Update READMEs * Production build * Make repo-status component more isolated * Minor tweaks * Get JS tests in a working state, and write tests for repo-status component * Add a note about tests in README * Apply Prettier * Production build #2 * We need vendor/message-bus.js
This commit is contained in:
		
							parent
							
								
									a929416aa2
								
							
						
					
					
						commit
						1309955216
					
				|  | @ -1,5 +1,4 @@ | ||||||
| *.swp | *.swp | ||||||
| manager-client/vendor/* |  | ||||||
| *.sublime-workspace | *.sublime-workspace | ||||||
| .DS_Store | .DS_Store | ||||||
| ._.DS_Store | ._.DS_Store | ||||||
|  |  | ||||||
							
								
								
									
										19
									
								
								README.md
								
								
								
								
							
							
						
						
									
										19
									
								
								README.md
								
								
								
								
							|  | @ -4,9 +4,9 @@ This plugin works with the Discourse docker image. It allows you to perform upgr | ||||||
| 
 | 
 | ||||||
| ## Development Notes | ## Development Notes | ||||||
| 
 | 
 | ||||||
| * Install `node.js`, `bower`, `grunt` | * Install `node.js` and `yarn` | ||||||
| * Clone this repo to desired folder path | * Clone this repo to desired folder path | ||||||
| * In console, from folder path do `cd manager-client`, `npm install`, `bower install` | * In console, from folder path do `cd manager-client`, `yarn install` | ||||||
| * Create a symlink for this folder in your local Discourse instance "plugins" folder (eg. `path/to/your/discourse_folder/plugins/discourse_manager`) | * Create a symlink for this folder in your local Discourse instance "plugins" folder (eg. `path/to/your/discourse_folder/plugins/discourse_manager`) | ||||||
| * Make sure your Discourse instance is running locally at port 3000 and you are logged in as Admin | * Make sure your Discourse instance is running locally at port 3000 and you are logged in as Admin | ||||||
| 
 | 
 | ||||||
|  | @ -14,9 +14,9 @@ This plugin works with the Discourse docker image. It allows you to perform upgr | ||||||
| 
 | 
 | ||||||
| * Install the client app dependencies: | * Install the client app dependencies: | ||||||
|   * `cd manager-client` |   * `cd manager-client` | ||||||
|   * `npm install` |   * `yarn install` | ||||||
|   * `bower install` | * Make sure your local Discourse instance is running at port 3000 | ||||||
| * Use `ember server --proxy "http://localhost:3000"` to proxy to your Discourse instance running on port 3000 | * Run `./dev_server` which will run ember server for you with proxy to your local Discourse instance | ||||||
|   * If that gives errors, you may need to start your Discourse rails server like this: `bundle exec rails s -b 127.0.0.1` |   * If that gives errors, you may need to start your Discourse rails server like this: `bundle exec rails s -b 127.0.0.1` | ||||||
| * JUST open up a browser to port 4200 and you're off to the races! | * JUST open up a browser to port 4200 and you're off to the races! | ||||||
| 
 | 
 | ||||||
|  | @ -25,6 +25,15 @@ The client application is built using [Ember CLI](http://www.ember-cli.com/). | ||||||
| To create a compiled version for distribution, run `./compile_client.sh` to compile the site and | To create a compiled version for distribution, run `./compile_client.sh` to compile the site and | ||||||
| move it into the proper directories. | move it into the proper directories. | ||||||
| 
 | 
 | ||||||
|  | ## Running tests | ||||||
|  | 
 | ||||||
|  | * Ruby | ||||||
|  |   * Run `RAILS_ENV=test bundle exec rake plugin:spec[docker_manager]` in your discourse directory. | ||||||
|  | 
 | ||||||
|  | * JS Tests | ||||||
|  |   * Run `ember s` in the `/manager-client` directory | ||||||
|  |   * Open up your favorite browser and head to `http://localhost:4200/tests`and you should see all passing/failing tests | ||||||
|  | 
 | ||||||
| ## Contributing | ## Contributing | ||||||
| 
 | 
 | ||||||
| 1. Fork it | 1. Fork it | ||||||
|  |  | ||||||
|  | @ -35,6 +35,7 @@ module DockerManager | ||||||
|         if r.valid? |         if r.valid? | ||||||
|           result[:id] = r.name.downcase.gsub(/[^a-z]/, '_').gsub(/_+/, '_').sub(/_$/, '') |           result[:id] = r.name.downcase.gsub(/[^a-z]/, '_').gsub(/_+/, '_').sub(/_$/, '') | ||||||
|           result[:version] = r.latest_local_commit |           result[:version] = r.latest_local_commit | ||||||
|  |           result[:pretty_version] = r.latest_local_tag_version.presence | ||||||
|           result[:url] = r.url |           result[:url] = r.url | ||||||
|           if r.upgrading? |           if r.upgrading? | ||||||
|             result[:upgrading] = true |             result[:upgrading] = true | ||||||
|  | @ -61,6 +62,7 @@ module DockerManager | ||||||
|         { |         { | ||||||
|           path: repo.path, |           path: repo.path, | ||||||
|           version: repo.latest_origin_commit, |           version: repo.latest_origin_commit, | ||||||
|  |           pretty_version: repo.latest_origin_tag_version.presence, | ||||||
|           commits_behind: repo.commits_behind, |           commits_behind: repo.commits_behind, | ||||||
|           date: repo.latest_origin_commit_date |           date: repo.latest_origin_commit_date | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -7,25 +7,7 @@ | ||||||
|   <meta name="description" content=""> |   <meta name="description" content=""> | ||||||
|   <meta name="viewport" content="width=device-width, initial-scale=1"> |   <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||||
|   <meta name="manager-client/config/environment" content="%7B%22modulePrefix%22%3A%22manager-client%22%2C%22environment%22%3A%22development%22%2C%22rootURL%22%3A%22/%22%2C%22locationType%22%3A%22hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%7D%7D%2C%22APP%22%3A%7B%22name%22%3A%22manager-client%22%2C%22version%22%3A%220.0.0+cfc32897%22%7D%2C%22exportApplicationGlobal%22%3Atrue%7D" /> |   <meta name="manager-client/config/environment" content="%7B%22modulePrefix%22%3A%22manager-client%22%2C%22environment%22%3A%22development%22%2C%22rootURL%22%3A%22/%22%2C%22locationType%22%3A%22hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%7D%7D%2C%22APP%22%3A%7B%22name%22%3A%22manager-client%22%2C%22version%22%3A%220.0.0+cfc32897%22%7D%2C%22exportApplicationGlobal%22%3Atrue%7D" /> | ||||||
| 
 |   <meta id="preloaded-data" data-preload="<%= { rootUrl: discourse_root_url, longPollingBaseUrl: long_polling_base_url }.to_json %>"> | ||||||
|   <script> |  | ||||||
|     window.Discourse = { |  | ||||||
|       rootUrl: '<%= discourse_root_url %>', |  | ||||||
|       longPollingBaseUrl: '<%= long_polling_base_url %>', |  | ||||||
| 
 |  | ||||||
|       getURL: function(url) { |  | ||||||
|         if (!url) return url; |  | ||||||
| 
 |  | ||||||
|         // if it's a non relative URL, return it. |  | ||||||
|         if (url !== '/' && !/^\/[^\/]/.test(url)) return url; |  | ||||||
| 
 |  | ||||||
|         if (url.indexOf(Discourse.rootUrl) !== -1) return url; |  | ||||||
|         if (url[0] !== "/") url = "/" + url; |  | ||||||
| 
 |  | ||||||
|         return Discourse.rootUrl + url; |  | ||||||
|       } |  | ||||||
|     }; |  | ||||||
|   </script> |  | ||||||
| 
 | 
 | ||||||
|   <%= javascript_include_tag "docker-manager-vendor" %> |   <%= javascript_include_tag "docker-manager-vendor" %> | ||||||
|   <%= javascript_include_tag "docker-manager-app" %> |   <%= javascript_include_tag "docker-manager-app" %> | ||||||
|  |  | ||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							|  | @ -37,6 +37,14 @@ class DockerManager::GitRepo | ||||||
|     run "rev-parse --short #{tracking_branch}" |     run "rev-parse --short #{tracking_branch}" | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |   def latest_local_tag_version | ||||||
|  |     prettify_tag_version("describe HEAD 2>/dev/null") | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def latest_origin_tag_version | ||||||
|  |     prettify_tag_version("describe #{tracking_branch} 2>/dev/null") | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   def latest_origin_commit_date |   def latest_origin_commit_date | ||||||
|     commit_date(latest_origin_commit) |     commit_date(latest_origin_commit) | ||||||
|   end |   end | ||||||
|  | @ -83,6 +91,16 @@ class DockerManager::GitRepo | ||||||
| 
 | 
 | ||||||
|   protected |   protected | ||||||
| 
 | 
 | ||||||
|  |   def prettify_tag_version(command) | ||||||
|  |     result = run(command) | ||||||
|  |     return unless result.present? | ||||||
|  | 
 | ||||||
|  |     if result =~ /-(\d+)-/ | ||||||
|  |       result.gsub!(/-(\d+)-.*/, " +#{$1}") | ||||||
|  |     end | ||||||
|  |     result | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   def upgrade_key |   def upgrade_key | ||||||
|     @upgrade_key ||= "upgrade:#{path}" |     @upgrade_key ||= "upgrade:#{path}" | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  | @ -1,4 +0,0 @@ | ||||||
| { |  | ||||||
|   "directory": "bower_components", |  | ||||||
|   "analytics": false |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,20 @@ | ||||||
|  | # unconventional js | ||||||
|  | /blueprints/*/files/ | ||||||
|  | /vendor/ | ||||||
|  | 
 | ||||||
|  | # compiled output | ||||||
|  | /dist/ | ||||||
|  | /tmp/ | ||||||
|  | 
 | ||||||
|  | # dependencies | ||||||
|  | /bower_components/ | ||||||
|  | /node_modules/ | ||||||
|  | 
 | ||||||
|  | # misc | ||||||
|  | /coverage/ | ||||||
|  | !.* | ||||||
|  | 
 | ||||||
|  | # ember-try | ||||||
|  | /.node_modules.ember-try/ | ||||||
|  | /bower.json.ember-try | ||||||
|  | /package.json.ember-try | ||||||
|  | @ -0,0 +1,45 @@ | ||||||
|  | module.exports = { | ||||||
|  |   root: true, | ||||||
|  |   parserOptions: { | ||||||
|  |     ecmaVersion: 2017, | ||||||
|  |     sourceType: 'module' | ||||||
|  |   }, | ||||||
|  |   plugins: [ | ||||||
|  |     'ember' | ||||||
|  |   ], | ||||||
|  |   globals: { | ||||||
|  |     Em: false, | ||||||
|  |     MessageBus: false | ||||||
|  |   }, | ||||||
|  |   extends: [ | ||||||
|  |     'eslint:recommended', | ||||||
|  |     'plugin:ember/recommended' | ||||||
|  |   ], | ||||||
|  |   env: { | ||||||
|  |     browser: true | ||||||
|  |   }, | ||||||
|  |   rules: { | ||||||
|  |   }, | ||||||
|  |   overrides: [ | ||||||
|  |     // node files
 | ||||||
|  |     { | ||||||
|  |       files: [ | ||||||
|  |         '.eslintrc.js', | ||||||
|  |         '.template-lintrc.js', | ||||||
|  |         'ember-cli-build.js', | ||||||
|  |         'testem.js', | ||||||
|  |         'blueprints/*/index.js', | ||||||
|  |         'config/**/*.js', | ||||||
|  |         'lib/*/index.js' | ||||||
|  |       ], | ||||||
|  |       parserOptions: { | ||||||
|  |         sourceType: 'script', | ||||||
|  |         ecmaVersion: 2015 | ||||||
|  |       }, | ||||||
|  |       env: { | ||||||
|  |         browser: false, | ||||||
|  |         node: true | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | }; | ||||||
|  | @ -9,7 +9,7 @@ You will need the following things properly installed on your computer. | ||||||
| 
 | 
 | ||||||
| * [Git](https://git-scm.com/) | * [Git](https://git-scm.com/) | ||||||
| * [Node.js](https://nodejs.org/) (with NPM) | * [Node.js](https://nodejs.org/) (with NPM) | ||||||
| * [Bower](https://bower.io/) | * [Yarn](https://yarnpkg.com) | ||||||
| * [Ember CLI](https://ember-cli.com/) | * [Ember CLI](https://ember-cli.com/) | ||||||
| * [PhantomJS](http://phantomjs.org/) | * [PhantomJS](http://phantomjs.org/) | ||||||
| 
 | 
 | ||||||
|  | @ -17,12 +17,11 @@ You will need the following things properly installed on your computer. | ||||||
| 
 | 
 | ||||||
| * `git clone <repository-url>` this repository | * `git clone <repository-url>` this repository | ||||||
| * `cd manager-client` | * `cd manager-client` | ||||||
| * `npm install` | * `yarn install` | ||||||
| * `bower install` |  | ||||||
| 
 | 
 | ||||||
| ## Running / Development | ## Running / Development | ||||||
| 
 | 
 | ||||||
| * `ember serve` | * `./dev_server` (in root directory of this plugin) | ||||||
| * Visit your app at [http://localhost:4200](http://localhost:4200). | * Visit your app at [http://localhost:4200](http://localhost:4200). | ||||||
| 
 | 
 | ||||||
| ### Code Generators | ### Code Generators | ||||||
|  | @ -39,10 +38,6 @@ Make use of the many generators for code, try `ember help generate` for more det | ||||||
| * `ember build` (development) | * `ember build` (development) | ||||||
| * `ember build --environment production` (production) | * `ember build --environment production` (production) | ||||||
| 
 | 
 | ||||||
| ### Deploying |  | ||||||
| 
 |  | ||||||
| Specify what it takes to deploy your app. |  | ||||||
| 
 |  | ||||||
| ## Further Reading / Useful Links | ## Further Reading / Useful Links | ||||||
| 
 | 
 | ||||||
| * [ember.js](http://emberjs.com/) | * [ember.js](http://emberjs.com/) | ||||||
|  |  | ||||||
|  | @ -1,13 +1,14 @@ | ||||||
| import Ember from 'ember'; | import Ember from "ember"; | ||||||
| import Resolver from './resolver'; | import Resolver from "./resolver"; | ||||||
| import loadInitializers from 'ember-load-initializers'; | import loadInitializers from "ember-load-initializers"; | ||||||
| import config from './config/environment'; | import config from "./config/environment"; | ||||||
|  | import Application from "@ember/application"; | ||||||
| 
 | 
 | ||||||
| let App; | let App; | ||||||
| 
 | 
 | ||||||
| Ember.MODEL_FACTORY_INJECTIONS = true; | Ember.MODEL_FACTORY_INJECTIONS = true; | ||||||
| 
 | 
 | ||||||
| App = Ember.Application.extend({ | App = Application.extend({ | ||||||
|   modulePrefix: config.modulePrefix, |   modulePrefix: config.modulePrefix, | ||||||
|   podModulePrefix: config.podModulePrefix, |   podModulePrefix: config.podModulePrefix, | ||||||
|   Resolver |   Resolver | ||||||
|  |  | ||||||
|  | @ -1,21 +1,22 @@ | ||||||
| import Ember from 'ember'; | import Component from "@ember/component"; | ||||||
|  | import { computed } from "@ember/object"; | ||||||
| 
 | 
 | ||||||
| export default Ember.Component.extend({ | export default Component.extend({ | ||||||
|   classNameBindings: [':progress', ':progress-striped', 'active'], |   classNameBindings: [":progress", ":progress-striped", "active"], | ||||||
| 
 | 
 | ||||||
|   active: function() { |   active: computed("percent", function() { | ||||||
|     return parseInt(this.get('percent'), 10) !== 100; |     return parseInt(this.get("percent"), 10) !== 100; | ||||||
|   }.property('percent'), |   }), | ||||||
| 
 | 
 | ||||||
|   barStyle: function() { |   barStyle: computed("percent", function() { | ||||||
|     let percent = parseInt(this.get('percent'), 10); |     let percent = parseInt(this.get("percent"), 10); | ||||||
|     if (percent > 0)  { |     if (percent > 0) { | ||||||
|       if (percent > 100) { percent = 100; } |       if (percent > 100) { | ||||||
|       return ('width: ' + this.get('percent') + '%').htmlSafe(); |         percent = 100; | ||||||
|  |       } | ||||||
|  |       return ("width: " + this.get("percent") + "%").htmlSafe(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return "".htmlSafe(); |     return "".htmlSafe(); | ||||||
|   }.property('percent') |   }) | ||||||
| 
 |  | ||||||
| }); | }); | ||||||
| 
 |  | ||||||
|  |  | ||||||
|  | @ -1,27 +1,43 @@ | ||||||
| import Ember from 'ember'; | import Discourse from "manager-client/discourse"; | ||||||
|  | import Component from "@ember/component"; | ||||||
|  | import { computed } from "@ember/object"; | ||||||
|  | import { inject as service } from "@ember/service"; | ||||||
| 
 | 
 | ||||||
| export default Ember.Component.extend({ | export default Component.extend({ | ||||||
|   tagName: 'tr', |   router: service(), | ||||||
|  |   tagName: "tr", | ||||||
| 
 | 
 | ||||||
|   upgradeDisabled: function() { |   upgradeDisabled: computed( | ||||||
|     const upgradingRepo = this.get('upgradingRepo'); |     "upgradingRepo", | ||||||
|  |     "repo", | ||||||
|  |     "managerRepo", | ||||||
|  |     "managerRepo.upToDate", | ||||||
|  |     function() { | ||||||
|  |       const upgradingRepo = this.get("upgradingRepo"); | ||||||
| 
 | 
 | ||||||
|     if (!upgradingRepo) { |       if (!upgradingRepo) { | ||||||
|       const managerRepo = this.get('managerRepo'); |         const managerRepo = this.get("managerRepo"); | ||||||
|       if (!managerRepo) { return false; } |         if (!managerRepo) { | ||||||
|       return (!managerRepo.get('upToDate')) && managerRepo !== this.get('repo'); |           return false; | ||||||
|  |         } | ||||||
|  |         return !managerRepo.get("upToDate") && managerRepo !== this.get("repo"); | ||||||
|  |       } | ||||||
|  |       return true; | ||||||
|     } |     } | ||||||
|     return true; |   ), | ||||||
|   }.property('upgradingRepo', 'repo', 'managerRepo', 'managerRepo.upToDate'), |  | ||||||
| 
 | 
 | ||||||
|   officialRepoImageSrc: function() { |   officialRepoImageSrc: computed("repo.official", function() { | ||||||
|     if (!this.get('repo.official')) { return; } |     if (!this.get("repo.official")) { | ||||||
|     return Discourse.getURL("/plugins/docker_manager/images/font-awesome-check-circle.png"); |       return; | ||||||
|   }.property('repo.official'), |     } | ||||||
|  |     return Discourse.getURL( | ||||||
|  |       "/plugins/docker_manager/images/font-awesome-check-circle.png" | ||||||
|  |     ); | ||||||
|  |   }), | ||||||
| 
 | 
 | ||||||
|   actions: { |   actions: { | ||||||
|     upgrade: function() { |     upgrade() { | ||||||
|       this.sendAction('upgrade', this.get('repo')); |       this.get("router").transitionTo("upgrade", this.get("repo")); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -1,20 +1,22 @@ | ||||||
| import Ember from 'ember'; | import Component from "@ember/component"; | ||||||
|  | import { observer } from "@ember/object"; | ||||||
|  | import { scheduleOnce } from "@ember/runloop"; | ||||||
| 
 | 
 | ||||||
| export default Ember.Component.extend({ | export default Component.extend({ | ||||||
|   classNameBindings: [':logs'], |   classNameBindings: [":logs"], | ||||||
| 
 | 
 | ||||||
|   _outputChanged: function() { |   _outputChanged: observer("output", function() { | ||||||
|     Ember.run.scheduleOnce('afterRender', this, '_scrollBottom'); |     scheduleOnce("afterRender", this, "_scrollBottom"); | ||||||
|   }.observes('output'), |   }), | ||||||
| 
 | 
 | ||||||
|   _scrollBottom: function() { |   _scrollBottom() { | ||||||
|     if (this.get('followOutput')) { |     if (this.get("followOutput")) { | ||||||
|       this.$().scrollTop(this.$()[0].scrollHeight); |       this.$().scrollTop(this.$()[0].scrollHeight); | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   _scrollOnInsert: function() { |   didInsertElement() { | ||||||
|  |     this._super(...arguments); | ||||||
|     this._scrollBottom(); |     this._scrollBottom(); | ||||||
|   }.on('didInsertElement') |   } | ||||||
| }); | }); | ||||||
| 
 |  | ||||||
|  |  | ||||||
|  | @ -1,39 +1,41 @@ | ||||||
| /* global Discourse */ | import Discourse from "manager-client/discourse"; | ||||||
|  | import Controller from "@ember/controller"; | ||||||
|  | import { computed } from "@ember/object"; | ||||||
| 
 | 
 | ||||||
| import Ember from "ember"; | export default Controller.extend({ | ||||||
| 
 |   showBanner: computed("banner", "bannerDismissed", "banner.[]", function() { | ||||||
| export default Ember.Controller.extend({ |     if (this.get("bannerDismissed")) { | ||||||
|   showBanner: function(){ |  | ||||||
|     if(this.get("bannerDismissed")){ |  | ||||||
|       return false; |       return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const banner = this.get("banner"); |     const banner = this.get("banner"); | ||||||
|     return banner && banner.length > 0; |     return banner && banner.length > 0; | ||||||
|   }.property("banner", "bannerDismissed", "banner.@each"), |   }), | ||||||
| 
 | 
 | ||||||
|   appendBannerHtml: function(html){ |   appendBannerHtml(html) { | ||||||
|     const banner = this.get("banner") || []; |     const banner = this.get("banner") || []; | ||||||
|     if(banner.indexOf(html) === -1){ |     if (banner.indexOf(html) === -1) { | ||||||
|       banner.pushObject(html); |       banner.pushObject(html); | ||||||
|     } |     } | ||||||
|     this.set("banner", banner); |     this.set("banner", banner); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   logoUrl: function() { |   logoUrl: computed(function() { | ||||||
|     return Discourse.getURL("/assets/images/docker-manager-aff8eaea0445c0488c19f8cfd14faa8c2b278924438f19048eacc175d7d134e4.png"); |     return Discourse.getURL( | ||||||
|   }.property(), |       "/assets/images/docker-manager-aff8eaea0445c0488c19f8cfd14faa8c2b278924438f19048eacc175d7d134e4.png" | ||||||
|  |     ); | ||||||
|  |   }), | ||||||
| 
 | 
 | ||||||
|   returnToSiteUrl: function() { |   returnToSiteUrl: computed(function() { | ||||||
|     return Discourse.getURL("/"); |     return Discourse.getURL("/"); | ||||||
|   }.property(), |   }), | ||||||
| 
 | 
 | ||||||
|   backupsUrl: function() { |   backupsUrl: computed(function() { | ||||||
|     return Discourse.getURL("/admin/backups"); |     return Discourse.getURL("/admin/backups"); | ||||||
|   }.property(), |   }), | ||||||
| 
 | 
 | ||||||
|   actions: { |   actions: { | ||||||
|     dismiss: function () { |     dismiss() { | ||||||
|       this.set("bannerDismissed", true); |       this.set("bannerDismissed", true); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -1,16 +1,21 @@ | ||||||
| import Ember from 'ember'; | import Controller from "@ember/controller"; | ||||||
|  | import { computed } from "@ember/object"; | ||||||
| 
 | 
 | ||||||
| export default Ember.Controller.extend({ | export default Controller.extend({ | ||||||
|   managerRepo: null, |   managerRepo: null, | ||||||
|   upgrading: null, |   upgrading: null, | ||||||
| 
 | 
 | ||||||
|   upgradeAllButtonDisabled: function () { |   upgradeAllButtonDisabled: computed( | ||||||
|     return !this.get("managerRepo.upToDate") || this.get("allUpToDate"); |     "managerRepo.upToDate", | ||||||
|   }.property("managerRepo.upToDate", "allUpToDate"), |     "allUpToDate", | ||||||
|  |     function() { | ||||||
|  |       return !this.get("managerRepo.upToDate") || this.get("allUpToDate"); | ||||||
|  |     } | ||||||
|  |   ), | ||||||
| 
 | 
 | ||||||
|   allUpToDate: function() { |   allUpToDate: computed("model.[].upToDate", function() { | ||||||
|     return this.get("model").every(repo => repo.get("upToDate")); |     return this.get("model").every(repo => repo.get("upToDate")); | ||||||
|   }.property("model.@each.upToDate"), |   }), | ||||||
| 
 | 
 | ||||||
|   actions: { |   actions: { | ||||||
|     upgradeAllButton() { |     upgradeAllButton() { | ||||||
|  |  | ||||||
|  | @ -1,22 +1,18 @@ | ||||||
| import Ember from 'ember'; | import Controller from "@ember/controller"; | ||||||
| 
 | 
 | ||||||
| export default Ember.Controller.extend({ | export default Controller.extend({ | ||||||
|   autoRefresh: false, |   autoRefresh: false, | ||||||
| 
 | 
 | ||||||
|   init: function() { |   init() { | ||||||
|     this._super(); |     this._super(); | ||||||
|     var self = this; |     window.setInterval(() => { | ||||||
| 
 |       this.performRefresh(); | ||||||
|     window.setInterval(function() { |  | ||||||
|       self.performRefresh(); |  | ||||||
|     }, 5000); |     }, 5000); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   performRefresh: function() { |   performRefresh() { | ||||||
|     if (this.get('autoRefresh')) { |     if (this.get("autoRefresh")) { | ||||||
|       this.get('model').refresh(); |       this.get("model").refresh(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 |  | ||||||
| }); | }); | ||||||
| 
 |  | ||||||
|  |  | ||||||
|  | @ -1,8 +1,10 @@ | ||||||
| /* global MessageBus, bootbox */ | /* global MessageBus, bootbox */ | ||||||
| import Ember from 'ember'; | import Repo from "manager-client/models/repo"; | ||||||
| import Repo from 'manager-client/models/repo'; | import Controller from "@ember/controller"; | ||||||
|  | import { equal } from "@ember/object/computed"; | ||||||
|  | import { computed } from "@ember/object"; | ||||||
| 
 | 
 | ||||||
| export default Ember.Controller.extend({ | export default Controller.extend({ | ||||||
|   output: null, |   output: null, | ||||||
| 
 | 
 | ||||||
|   init() { |   init() { | ||||||
|  | @ -10,24 +12,24 @@ export default Ember.Controller.extend({ | ||||||
|     this.reset(); |     this.reset(); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   complete: Ember.computed.equal('status', 'complete'), |   complete: equal("status", "complete"), | ||||||
|   failed: Ember.computed.equal('status', 'failed'), |   failed: equal("status", "failed"), | ||||||
| 
 | 
 | ||||||
|   multiUpgrade: function() { |   multiUpgrade: computed("model.length", function() { | ||||||
|     return this.get("model.length") !== 1; |     return this.get("model.length") !== 1; | ||||||
|   }.property("model.length"), |   }), | ||||||
| 
 | 
 | ||||||
|   title: function () { |   title: computed("model.[].name", function() { | ||||||
|     return this.get("multiUpgrade") ? "All" : this.get("model")[0].get("name"); |     return this.get("multiUpgrade") ? "All" : this.get("model")[0].get("name"); | ||||||
|   }.property("model.@each.name"), |   }), | ||||||
| 
 | 
 | ||||||
|   isUpToDate: function () { |   isUpToDate: computed("model.[].upToDate", function() { | ||||||
|     return this.get("model").every(repo => repo.get("upToDate")); |     return this.get("model").every(repo => repo.get("upToDate")); | ||||||
|   }.property("model.@each.upToDate"), |   }), | ||||||
| 
 | 
 | ||||||
|   upgrading: function () { |   upgrading: computed("model.[].upgrading", function() { | ||||||
|     return this.get("model").some(repo => repo.get("upgrading")); |     return this.get("model").some(repo => repo.get("upgrading")); | ||||||
|   }.property("model.@each.upgrading"), |   }), | ||||||
| 
 | 
 | ||||||
|   repos() { |   repos() { | ||||||
|     const model = this.get("model"); |     const model = this.get("model"); | ||||||
|  | @ -36,43 +38,45 @@ export default Ember.Controller.extend({ | ||||||
| 
 | 
 | ||||||
|   updateAttribute(key, value, valueIsKey = false) { |   updateAttribute(key, value, valueIsKey = false) { | ||||||
|     this.get("model").forEach(repo => { |     this.get("model").forEach(repo => { | ||||||
|       value = valueIsKey ? repo.get(value) : value;  |       value = valueIsKey ? repo.get(value) : value; | ||||||
|       repo.set(key, value); |       repo.set(key, value); | ||||||
|     }); |     }); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   messageReceived(msg) { |   messageReceived(msg) { | ||||||
|     switch(msg.type) { |     switch (msg.type) { | ||||||
|       case "log": |       case "log": | ||||||
|         this.set('output', this.get('output') + msg.value + "\n"); |         this.set("output", this.get("output") + msg.value + "\n"); | ||||||
|         break; |         break; | ||||||
|       case "percent": |       case "percent": | ||||||
|         this.set('percent', msg.value); |         this.set("percent", msg.value); | ||||||
|         break; |         break; | ||||||
|       case "status": |       case "status": | ||||||
|         this.set('status', msg.value); |         this.set("status", msg.value); | ||||||
| 
 | 
 | ||||||
|         if (msg.value === "complete") { |         if (msg.value === "complete") { | ||||||
| 	        this.get("model").filter(repo => repo.get("upgrading")).forEach(repo => { |           this.get("model") | ||||||
|             repo.set("version", repo.get("latest.version")); |             .filter(repo => repo.get("upgrading")) | ||||||
|           }); |             .forEach(repo => { | ||||||
|  |               repo.set("version", repo.get("latest.version")); | ||||||
|  |             }); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (msg.value === 'complete' || msg.value === 'failed') { |         if (msg.value === "complete" || msg.value === "failed") { | ||||||
|           this.updateAttribute('upgrading', false); |           this.updateAttribute("upgrading", false); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   upgradeButtonText: function() { |   upgradeButtonText: computed("upgrading", function() { | ||||||
|     if (this.get("upgrading")) { |     if (this.get("upgrading")) { | ||||||
|       return "Upgrading..."; |       return "Upgrading..."; | ||||||
|     } else { |     } else { | ||||||
|       return "Start Upgrading"; |       return "Start Upgrading"; | ||||||
|     } |     } | ||||||
|   }.property("upgrading"), |   }), | ||||||
| 
 | 
 | ||||||
|   startBus() { |   startBus() { | ||||||
|     MessageBus.subscribe("/docker/upgrade", msg => { |     MessageBus.subscribe("/docker/upgrade", msg => { | ||||||
|  | @ -85,7 +89,7 @@ export default Ember.Controller.extend({ | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   reset() { |   reset() { | ||||||
|     this.setProperties({ output: '', status: null, percent: 0 }); |     this.setProperties({ output: "", status: null, percent: 0 }); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   actions: { |   actions: { | ||||||
|  | @ -93,33 +97,41 @@ export default Ember.Controller.extend({ | ||||||
|       this.reset(); |       this.reset(); | ||||||
| 
 | 
 | ||||||
|       if (this.get("multiUpgrade")) { |       if (this.get("multiUpgrade")) { | ||||||
|         this.get("model").filter(repo => !repo.get("upToDate")).forEach(repo => repo.set("upgrading", true)); |         this.get("model") | ||||||
|  |           .filter(repo => !repo.get("upToDate")) | ||||||
|  |           .forEach(repo => repo.set("upgrading", true)); | ||||||
|         return Repo.upgradeAll(); |         return Repo.upgradeAll(); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       const repo = this.get('model')[0]; |       const repo = this.get("model")[0]; | ||||||
|       if (repo.get('upgrading')) { return; } |       if (repo.get("upgrading")) { | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|       repo.startUpgrade(); |       repo.startUpgrade(); | ||||||
|     }, |     }, | ||||||
| 
 | 
 | ||||||
|     resetUpgrade() { |     resetUpgrade() { | ||||||
|       bootbox.confirm("WARNING: You should only reset upgrades that have failed and are not running.\n\n"+ |       bootbox.confirm( | ||||||
|                       "This will NOT cancel currently running builds and should only be used as a last resort.", result => { |         "WARNING: You should only reset upgrades that have failed and are not running.\n\n" + | ||||||
|         if (result) { |           "This will NOT cancel currently running builds and should only be used as a last resort.", | ||||||
|           if (this.get("multiUpgrade")) { |         result => { | ||||||
|             return Repo.resetAll(this.get("model").filter(repo => !repo.get("upToDate"))).finally(() => { |           if (result) { | ||||||
|  |             if (this.get("multiUpgrade")) { | ||||||
|  |               return Repo.resetAll( | ||||||
|  |                 this.get("model").filter(repo => !repo.get("upToDate")) | ||||||
|  |               ).finally(() => { | ||||||
|  |                 this.reset(); | ||||||
|  |                 this.updateAttribute("upgrading", false); | ||||||
|  |               }); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             const repo = this.get("model")[0]; | ||||||
|  |             repo.resetUpgrade().then(() => { | ||||||
|               this.reset(); |               this.reset(); | ||||||
|               this.updateAttribute("upgrading", false); |  | ||||||
|             }); |             }); | ||||||
|           } |           } | ||||||
| 
 |  | ||||||
|           const repo = this.get('model')[0]; |  | ||||||
|           repo.resetUpgrade().then(function() { |  | ||||||
|             this.reset(); |  | ||||||
|           }); |  | ||||||
|         } |         } | ||||||
|       }); |       ); | ||||||
|     } |     } | ||||||
|   }, |   } | ||||||
| 
 |  | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -0,0 +1,23 @@ | ||||||
|  | function init() { | ||||||
|  |   const data = Em.$("#preloaded-data").data("preload"); | ||||||
|  |   Em.$.extend(Discourse, data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const Discourse = { | ||||||
|  |   getURL(url) { | ||||||
|  |     if (!this.hasOwnProperty("rootUrl")) { | ||||||
|  |       init(); | ||||||
|  |     } | ||||||
|  |     if (!url) return url; | ||||||
|  | 
 | ||||||
|  |     // if it's a non relative URL, return it.
 | ||||||
|  |     if (url !== "/" && !/^\/[^/]/.test(url)) return url; | ||||||
|  | 
 | ||||||
|  |     if (url.indexOf(this.rootUrl) !== -1) return url; | ||||||
|  |     if (url[0] !== "/") url = "/" + url; | ||||||
|  | 
 | ||||||
|  |     return this.rootUrl + url; | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default Discourse; | ||||||
|  | @ -1,6 +1,8 @@ | ||||||
| import Ember from "ember"; | import { helper as buildHelper } from "@ember/component/helper"; | ||||||
|  | import { isNone } from "@ember/utils"; | ||||||
|  | import { htmlSafe } from "@ember/template"; | ||||||
| 
 | 
 | ||||||
| export default Ember.Helper.helper(function(params) { | export default buildHelper(function(params) { | ||||||
|   const [commitsBehind, oldSha, newSha, url] = params; |   const [commitsBehind, oldSha, newSha, url] = params; | ||||||
| 
 | 
 | ||||||
|   if (parseInt(commitsBehind) === 0) { |   if (parseInt(commitsBehind) === 0) { | ||||||
|  | @ -11,12 +13,12 @@ export default Ember.Helper.helper(function(params) { | ||||||
|     commitsBehind === 1 ? "" : "s" |     commitsBehind === 1 ? "" : "s" | ||||||
|   }`;
 |   }`;
 | ||||||
| 
 | 
 | ||||||
|   if (Ember.isNone(url)) { |   if (isNone(url)) { | ||||||
|     return description; |     return description; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   var _url = url.substr(0, url.search(/(\.git)?$/)); |   const _url = url.substr(0, url.search(/(\.git)?$/)); | ||||||
|   description = `<a href='${_url}/compare/${oldSha}..${newSha}'>${description}</a>`; |   description = `<a href='${_url}/compare/${oldSha}...${newSha}'>${description}</a>`; | ||||||
| 
 | 
 | ||||||
|   return new Ember.String.htmlSafe(description); |   return new htmlSafe(description); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ | ||||||
|     <title>DockerManager</title> |     <title>DockerManager</title> | ||||||
|     <meta name="description" content=""> |     <meta name="description" content=""> | ||||||
|     <meta name="viewport" content="width=device-width, initial-scale=1"> |     <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||||
|  |     <meta id="preloaded-data" data-preload="{"rootUrl":"/","longPollingBaseUrl":"/"}"> | ||||||
| 
 | 
 | ||||||
|     {{content-for 'head'}} |     {{content-for 'head'}} | ||||||
| 
 | 
 | ||||||
|  | @ -14,17 +15,6 @@ | ||||||
| 
 | 
 | ||||||
|     {{content-for 'head-footer'}} |     {{content-for 'head-footer'}} | ||||||
| 
 | 
 | ||||||
|   <script> |  | ||||||
|     window.Discourse = { |  | ||||||
|       rootUrl: '/', |  | ||||||
|       longPollingBaseUrl: '/', |  | ||||||
| 
 |  | ||||||
|       getURL: function(url) { |  | ||||||
|         return url; |  | ||||||
|       } |  | ||||||
|     }; |  | ||||||
|   </script> |  | ||||||
| 
 |  | ||||||
|   </head> |   </head> | ||||||
|   <body> |   <body> | ||||||
|     {{content-for 'body'}} |     {{content-for 'body'}} | ||||||
|  |  | ||||||
|  | @ -1,15 +1,14 @@ | ||||||
| /* global $:true, Discourse */ | import Discourse from "manager-client/discourse"; | ||||||
| import request from 'ember-ajax/request'; |  | ||||||
| 
 | 
 | ||||||
| export default { | export default { | ||||||
|   name: "findCsrfToken", |   name: "findCsrfToken", | ||||||
| 
 | 
 | ||||||
|   initialize() { |   initialize() { | ||||||
|     return request(Discourse.getURL('/session/csrf')).then(function(result) { |     return Em.$.ajax(Discourse.getURL("/session/csrf")).then(result => { | ||||||
|       var token = result.csrf; |       const token = result.csrf; | ||||||
|       $.ajaxPrefilter(function(options, originalOptions, xhr) { |       Em.$.ajaxPrefilter((options, originalOptions, xhr) => { | ||||||
|         if (!options.crossDomain) { |         if (!options.crossDomain) { | ||||||
|           xhr.setRequestHeader('X-CSRF-Token', token); |           xhr.setRequestHeader("X-CSRF-Token", token); | ||||||
|         } |         } | ||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| /* global MessageBus, Discourse, $:true */ | import Discourse from "manager-client/discourse"; | ||||||
| 
 | 
 | ||||||
| export default { | export default { | ||||||
|   name: "message-bus", |   name: "message-bus", | ||||||
|  | @ -6,14 +6,16 @@ export default { | ||||||
|   initialize() { |   initialize() { | ||||||
|     MessageBus.baseUrl = Discourse.longPollingBaseUrl; |     MessageBus.baseUrl = Discourse.longPollingBaseUrl; | ||||||
| 
 | 
 | ||||||
|     if (MessageBus.baseUrl !== '/') { |     if (MessageBus.baseUrl !== "/") { | ||||||
|       MessageBus.ajax = function(opts) { |       MessageBus.ajax = function(opts) { | ||||||
|         opts.headers = opts.headers || {}; |         opts.headers = opts.headers || {}; | ||||||
|         opts.headers['X-Shared-Session-Key'] = $('meta[name=shared_session_key]').attr('content'); |         opts.headers["X-Shared-Session-Key"] = Em.$( | ||||||
|         return $.ajax(opts); |           "meta[name=shared_session_key]" | ||||||
|  |         ).attr("content"); | ||||||
|  |         return Em.$.ajax(opts); | ||||||
|       }; |       }; | ||||||
|     } else { |     } else { | ||||||
|       MessageBus.baseUrl = Discourse.getURL('/'); |       MessageBus.baseUrl = Discourse.getURL("/"); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -1,14 +1,14 @@ | ||||||
| /* global Discourse */ | import EmberObject from "@ember/object"; | ||||||
|  | import Discourse from "manager-client/discourse"; | ||||||
| 
 | 
 | ||||||
| import request from 'ember-ajax/request'; | const ProcessList = EmberObject.extend({ | ||||||
| import Ember from 'ember'; |  | ||||||
| 
 |  | ||||||
| const ProcessList = Ember.Object.extend({ |  | ||||||
|   output: null, |   output: null, | ||||||
| 
 | 
 | ||||||
|   refresh() { |   refresh() { | ||||||
|     return request(Discourse.getURL("/admin/docker/ps"), {dataType: 'text'}).then(result => { |     return Em.$.ajax(Discourse.getURL("/admin/docker/ps"), { | ||||||
|       this.set('output', result); |       dataType: "text" | ||||||
|  |     }).then(result => { | ||||||
|  |       this.set("output", result); | ||||||
|       return this; |       return this; | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -1,7 +1,8 @@ | ||||||
| /* global Discourse */ | import Discourse from "manager-client/discourse"; | ||||||
| 
 | import { default as EmberObject, computed } from "@ember/object"; | ||||||
| import request from 'ember-ajax/request'; | import { or } from "@ember/object/computed"; | ||||||
| import Ember from 'ember'; | import { isNone } from "@ember/utils"; | ||||||
|  | import { Promise } from "rsvp"; | ||||||
| 
 | 
 | ||||||
| let loaded = []; | let loaded = []; | ||||||
| 
 | 
 | ||||||
|  | @ -9,109 +10,150 @@ function concatVersions(repos) { | ||||||
|   return repos.map(repo => repo.get("version")).join(", "); |   return repos.map(repo => repo.get("version")).join(", "); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const Repo = Ember.Object.extend({ | const Repo = EmberObject.extend({ | ||||||
|   unloaded: true, |   unloaded: true, | ||||||
|   checking: false, |   checking: false, | ||||||
| 
 | 
 | ||||||
|   checkingStatus: Ember.computed.or('unloaded', 'checking'), |   checkingStatus: or("unloaded", "checking"), | ||||||
|   upToDate: function() { |   upToDate: computed("upgrading", "version", "latest.version", function() { | ||||||
|     return !this.get('upgrading') & (this.get('version') === this.get('latest.version')); |     return ( | ||||||
|   }.property('upgrading', 'version', 'latest.version'), |       !this.get("upgrading") & | ||||||
|  |       (this.get("version") === this.get("latest.version")) | ||||||
|  |     ); | ||||||
|  |   }), | ||||||
| 
 | 
 | ||||||
|   shouldCheck: function() { |   prettyVersion: computed("version", "pretty_version", function() { | ||||||
|     if (Ember.isNone(this.get('version'))) { return false; } |     return this.get("pretty_version") || this.get("version"); | ||||||
|     if (this.get('checking')) { return false; } |   }), | ||||||
|  | 
 | ||||||
|  |   prettyLatestVersion: computed("latest.{version,pretty_version}", function() { | ||||||
|  |     return this.get("latest.pretty_version") || this.get("latest.version"); | ||||||
|  |   }), | ||||||
|  | 
 | ||||||
|  |   shouldCheck: computed(function() { | ||||||
|  |     if (isNone(this.get("version"))) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |     if (this.get("checking")) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     // Only check once every minute
 |     // Only check once every minute
 | ||||||
|     var lastCheckedAt = this.get('lastCheckedAt'); |     const lastCheckedAt = this.get("lastCheckedAt"); | ||||||
|     if (lastCheckedAt) { |     if (lastCheckedAt) { | ||||||
|       var ago = new Date().getTime() - lastCheckedAt; |       const ago = new Date().getTime() - lastCheckedAt; | ||||||
|       return ago > 60 * 1000; |       return ago > 60 * 1000; | ||||||
|     } |     } | ||||||
|     return true; |     return true; | ||||||
|   }.property().volatile(), |   }).volatile(), | ||||||
| 
 | 
 | ||||||
|   repoAjax: function(url, args) { |   repoAjax(url, args) { | ||||||
|     args = args || {}; |     args = args || {}; | ||||||
|     args.data = this.getProperties('path', 'version', 'branch'); |     args.data = this.getProperties("path", "version", "branch"); | ||||||
| 
 | 
 | ||||||
|     return request(Discourse.getURL(url), args); |     return Em.$.ajax(Discourse.getURL(url), args); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   findLatest: function() { |   findLatest() { | ||||||
|     return new Ember.RSVP.Promise(resolve => { |     return new Promise(resolve => { | ||||||
|       if (!this.get('shouldCheck')) { |       if (!this.get("shouldCheck")) { | ||||||
|         this.set('unloaded', false); |         this.set("unloaded", false); | ||||||
|         return resolve(); |         return resolve(); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       this.set('checking', true); |       this.set("checking", true); | ||||||
|       this.repoAjax(Discourse.getURL('/admin/docker/latest')).then(result => { |       this.repoAjax(Discourse.getURL("/admin/docker/latest")).then(result => { | ||||||
|         this.setProperties({ |         this.setProperties({ | ||||||
|           unloaded: false, |           unloaded: false, | ||||||
|           checking: false, |           checking: false, | ||||||
|           lastCheckedAt: new Date().getTime(), |           lastCheckedAt: new Date().getTime(), | ||||||
|           latest: Ember.Object.create(result.latest) |           latest: EmberObject.create(result.latest) | ||||||
|         }); |         }); | ||||||
|         resolve(); |         resolve(); | ||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   findProgress: function() { |   findProgress() { | ||||||
|     return this.repoAjax(Discourse.getURL('/admin/docker/progress')).then(result => result.progress); |     return this.repoAjax(Discourse.getURL("/admin/docker/progress")).then( | ||||||
|  |       result => result.progress | ||||||
|  |     ); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   resetUpgrade: function() { |   resetUpgrade() { | ||||||
|     return this.repoAjax(Discourse.getURL('/admin/docker/upgrade'), { dataType: 'text', type: 'DELETE' }).then(() => { |     return this.repoAjax(Discourse.getURL("/admin/docker/upgrade"), { | ||||||
|       this.set('upgrading', false); |       dataType: "text", | ||||||
|  |       type: "DELETE" | ||||||
|  |     }).then(() => { | ||||||
|  |       this.set("upgrading", false); | ||||||
|     }); |     }); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   startUpgrade: function() { |   startUpgrade() { | ||||||
|     this.set('upgrading', true); |     this.set("upgrading", true); | ||||||
| 
 | 
 | ||||||
|     return this.repoAjax(Discourse.getURL('/admin/docker/upgrade'), { dataType: 'text', type: 'POST' }).catch(() => { |     return this.repoAjax(Discourse.getURL("/admin/docker/upgrade"), { | ||||||
|       this.set('upgrading', false); |       dataType: "text", | ||||||
|  |       type: "POST" | ||||||
|  |     }).catch(() => { | ||||||
|  |       this.set("upgrading", false); | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| Repo.reopenClass({ | Repo.reopenClass({ | ||||||
|   findAll() { |   findAll() { | ||||||
|     return new Ember.RSVP.Promise(function (resolve) { |     return new Promise(resolve => { | ||||||
|       if (loaded.length) { return resolve(loaded); } |       if (loaded.length) { | ||||||
|  |         return resolve(loaded); | ||||||
|  |       } | ||||||
| 
 | 
 | ||||||
|       request(Discourse.getURL("/admin/docker/repos")).then(result => { |       Em.$.ajax(Discourse.getURL("/admin/docker/repos")).then(result => { | ||||||
|         loaded = result.repos.map(r => Repo.create(r)); |         loaded = result.repos.map(r => Repo.create(r)); | ||||||
|         resolve(loaded); |         resolve(loaded); | ||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   findUpgrading: function() { |   findUpgrading() { | ||||||
|     return this.findAll().then(result => result.findBy('upgrading', true)); |     return this.findAll().then(result => result.findBy("upgrading", true)); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   find: function(id) { |   find(id) { | ||||||
|     return this.findAll().then(result => result.findBy('id', id)); |     return this.findAll().then(result => result.findBy("id", id)); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   upgradeAll() { |   upgradeAll() { | ||||||
|     return request(Discourse.getURL("/admin/docker/upgrade"), { dataType: "text", type: "POST", data: { path: "all" } }); |     return Em.$.ajax(Discourse.getURL("/admin/docker/upgrade"), { | ||||||
|  |       dataType: "text", | ||||||
|  |       type: "POST", | ||||||
|  |       data: { path: "all" } | ||||||
|  |     }); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   resetAll(repos) { |   resetAll(repos) { | ||||||
|     return request(Discourse.getURL("/admin/docker/upgrade"), { dataType: "text", type: "DELETE", data: { path: "all", version: concatVersions(repos) } }); |     return Em.$.ajax(Discourse.getURL("/admin/docker/upgrade"), { | ||||||
|  |       dataType: "text", | ||||||
|  |       type: "DELETE", | ||||||
|  |       data: { path: "all", version: concatVersions(repos) } | ||||||
|  |     }); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   findLatestAll() { |   findLatestAll() { | ||||||
|     return request(Discourse.getURL("/admin/docker/latest"), { dataType: "text", type: "GET", data: { path: "all" } }); |     return Em.$.ajax(Discourse.getURL("/admin/docker/latest"), { | ||||||
|  |       dataType: "text", | ||||||
|  |       type: "GET", | ||||||
|  |       data: { path: "all" } | ||||||
|  |     }); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   findAllProgress(repos) { |   findAllProgress(repos) { | ||||||
|     return request(Discourse.getURL("/admin/docker/progress"), { dataType: "text", type: "GET", data: { path: "all", version: concatVersions(repos) } }); |     return Em.$.ajax(Discourse.getURL("/admin/docker/progress"), { | ||||||
|   }, |       dataType: "text", | ||||||
|  |       type: "GET", | ||||||
|  |       data: { path: "all", version: concatVersions(repos) } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| export default Repo; | export default Repo; | ||||||
|  |  | ||||||
|  | @ -1,3 +1,3 @@ | ||||||
| import Resolver from 'ember-resolver'; | import Resolver from "ember-resolver"; | ||||||
| 
 | 
 | ||||||
| export default Resolver; | export default Resolver; | ||||||
|  |  | ||||||
|  | @ -1,14 +1,14 @@ | ||||||
| import Ember from 'ember'; | import config from "./config/environment"; | ||||||
| import config from './config/environment'; | import EmberRouter from "@ember/routing/router"; | ||||||
| 
 | 
 | ||||||
| const Router = Ember.Router.extend({ | const Router = EmberRouter.extend({ | ||||||
|   location: config.locationType, |   location: config.locationType, | ||||||
|   rootURL: config.rootURL |   rootURL: config.rootURL | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| Router.map(function() { | Router.map(function() { | ||||||
|   this.route("processes"); |   this.route("processes"); | ||||||
|   this.route('upgrade', { path: '/upgrade/:id' }); |   this.route("upgrade", { path: "/upgrade/:id" }); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| export default Router; | export default Router; | ||||||
|  |  | ||||||
|  | @ -1,13 +1,15 @@ | ||||||
| import Repo from 'manager-client/models/repo'; | import Repo from "manager-client/models/repo"; | ||||||
| import Ember from 'ember'; | import Route from "@ember/routing/route"; | ||||||
| 
 | 
 | ||||||
| export default Ember.Route.extend({ | export default Route.extend({ | ||||||
|   model() { |   model() { | ||||||
|     return Repo.findAll(); |     return Repo.findAll(); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   loadRepos(list) { |   loadRepos(list) { | ||||||
|     if (list.length === 0) { return; } |     if (list.length === 0) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|     this.loadRepo(list.shift()).then(() => this.loadRepos(list)); |     this.loadRepo(list.shift()).then(() => this.loadRepos(list)); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|  | @ -16,31 +18,30 @@ export default Ember.Route.extend({ | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   setupController(controller, model) { |   setupController(controller, model) { | ||||||
|     const applicationController = this.controllerFor('application'); |     const applicationController = this.controllerFor("application"); | ||||||
|     controller.setProperties({ model, upgrading: null }); |     controller.setProperties({ model, upgrading: null }); | ||||||
| 
 | 
 | ||||||
|     model.forEach(repo => { |     model.forEach(repo => { | ||||||
|       if (repo.get('upgrading')) { |       if (repo.get("upgrading")) { | ||||||
|         controller.set('upgrading', repo); |         controller.set("upgrading", repo); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       // Special case: Upgrade docker manager first
 |       // Special case: Upgrade docker manager first
 | ||||||
|       if (repo.get('id') === 'docker_manager') { |       if (repo.get("id") === "docker_manager") { | ||||||
|         controller.set('managerRepo', repo); |         controller.set("managerRepo", repo); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       // Special case: If the branch is "master" warn user
 |       // Special case: If the branch is "master" warn user
 | ||||||
|       if (repo.get('id') === 'discourse' && repo.get('branch') === 'origin/master') { |       if ( | ||||||
|         applicationController.appendBannerHtml("<b>WARNING:</b> Your Discourse is tracking the 'master' branch which may be unstable, <a href='https://meta.discourse.org/t/change-tracking-branch-for-your-discourse-instance/17014'>we recommend tracking the 'tests-passed' branch</a>."); |         repo.get("id") === "discourse" && | ||||||
|  |         repo.get("branch") === "origin/master" | ||||||
|  |       ) { | ||||||
|  |         applicationController.appendBannerHtml( | ||||||
|  |           "<b>WARNING:</b> Your Discourse is tracking the 'master' branch which may be unstable, <a href='https://meta.discourse.org/t/change-tracking-branch-for-your-discourse-instance/17014'>we recommend tracking the 'tests-passed' branch</a>." | ||||||
|  |         ); | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     this.loadRepos(model.slice(0)); |     this.loadRepos(model.slice(0)); | ||||||
|   }, |  | ||||||
| 
 |  | ||||||
|   actions: { |  | ||||||
|     upgrade(repo) { |  | ||||||
|       this.transitionTo('upgrade', repo); |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import { find } from 'manager-client/models/process-list'; | import { find } from "manager-client/models/process-list"; | ||||||
| import Ember from 'ember'; | import Route from "@ember/routing/route"; | ||||||
| 
 | 
 | ||||||
| export default Ember.Route.extend({ | export default Route.extend({ | ||||||
|   model: find |   model: find | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -1,26 +1,31 @@ | ||||||
| import Repo from 'manager-client/models/repo'; | import Repo from "manager-client/models/repo"; | ||||||
| import Ember from 'ember'; | import Route from "@ember/routing/route"; | ||||||
|  | import EmberObject from "@ember/object"; | ||||||
|  | import { Promise } from "rsvp"; | ||||||
| 
 | 
 | ||||||
| export default Ember.Route.extend({ | export default Route.extend({ | ||||||
| 
 |   model(params) { | ||||||
|   model: function(params) { |  | ||||||
|     if (params.id === "all") { |     if (params.id === "all") { | ||||||
|       return Repo.findAll(); |       return Repo.findAll(); | ||||||
|     } |     } | ||||||
|     return Repo.find(params.id); |     return Repo.find(params.id); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   afterModel: function(model) { |   afterModel(model) { | ||||||
|     if (Array.isArray(model)) { |     if (Array.isArray(model)) { | ||||||
|       return Repo.findLatestAll().then(response => { |       return Repo.findLatestAll().then(response => { | ||||||
|         JSON.parse(response).repos.forEach(_repo => { |         JSON.parse(response).repos.forEach(_repo => { | ||||||
|           const repo = model.find(repo => repo.get("path") === _repo.path); |           const repo = model.find(repo => repo.get("path") === _repo.path); | ||||||
|           if (!repo) { return; } |           if (!repo) { | ||||||
|  |             return; | ||||||
|  |           } | ||||||
|           delete _repo.path; |           delete _repo.path; | ||||||
|           repo.set("latest", Ember.Object.create(_repo)); |           repo.set("latest", EmberObject.create(_repo)); | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         return Repo.findAllProgress(model.filter(repo => !repo.get("upToDate"))).then(progress => { |         return Repo.findAllProgress( | ||||||
|  |           model.filter(repo => !repo.get("upToDate")) | ||||||
|  |         ).then(progress => { | ||||||
|           this.set("progress", JSON.parse(progress).progress); |           this.set("progress", JSON.parse(progress).progress); | ||||||
|         }); |         }); | ||||||
|       }); |       }); | ||||||
|  | @ -28,7 +33,7 @@ export default Ember.Route.extend({ | ||||||
| 
 | 
 | ||||||
|     return Repo.findUpgrading().then(u => { |     return Repo.findUpgrading().then(u => { | ||||||
|       if (u && u !== model) { |       if (u && u !== model) { | ||||||
|         return Ember.RSVP.Promise.reject("wat"); |         return Promise.reject("wat"); | ||||||
|       } |       } | ||||||
|       return model.findLatest().then(() => { |       return model.findLatest().then(() => { | ||||||
|         return model.findProgress().then(progress => { |         return model.findProgress().then(progress => { | ||||||
|  | @ -36,21 +41,19 @@ export default Ember.Route.extend({ | ||||||
|         }); |         }); | ||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
| 
 |  | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   setupController: function(controller, model) { |   setupController(controller, model) { | ||||||
|     controller.reset(); |     controller.reset(); | ||||||
|     controller.setProperties({ |     controller.setProperties({ | ||||||
|       model: Array.isArray(model) ? model : [model], |       model: Array.isArray(model) ? model : [model], | ||||||
|       output: this.get('progress.logs'), |       output: this.get("progress.logs"), | ||||||
|       percent: this.get('progress.percentage') |       percent: this.get("progress.percentage") | ||||||
|     }); |     }); | ||||||
|     controller.startBus(); |     controller.startBus(); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   deactivate: function() { |   deactivate() { | ||||||
|     this.controllerFor('upgrade').stopBus(); |     this.controllerFor("upgrade").stopBus(); | ||||||
|   } |   } | ||||||
| 
 |  | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -113,6 +113,10 @@ h3.loading { | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | span.commit-hash { | ||||||
|  |   color: #959595; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| button.btn { | button.btn { | ||||||
|   background: #00aaff; |   background: #00aaff; | ||||||
|   color: white !important; |   color: white !important; | ||||||
|  |  | ||||||
|  | @ -1,11 +1,11 @@ | ||||||
| <td> | <td> | ||||||
|   {{#if repo.official}} |   {{#if repo.official}} | ||||||
|     <img class="check-circle" src={{officialRepoImageSrc}} alt="Official Plugin" title="Official Plugin"/> |     <img class="check-circle" src={{officialRepoImageSrc}} alt="Official Plugin" title="Official Plugin"> | ||||||
|   {{/if}} |   {{/if}} | ||||||
| </td> | </td> | ||||||
| <td> | <td> | ||||||
|   {{repo.name}} |   <a href="{{repo.url}}">{{repo.name}}</a> | ||||||
|   {{repo.version}} |   <span class="current commit-hash">{{repo.prettyVersion}}</span> | ||||||
| </td> | </td> | ||||||
| <td> | <td> | ||||||
|   {{#if repo.checkingStatus}} |   {{#if repo.checkingStatus}} | ||||||
|  | @ -16,7 +16,7 @@ | ||||||
|     <div class='new-version'> |     <div class='new-version'> | ||||||
|       <h4>New Version Available!</h4> |       <h4>New Version Available!</h4> | ||||||
|       <ul> |       <ul> | ||||||
|         <li>Remote Version: {{repo.latest.version}}</li> |         <li>Remote Version: <span class="new commit-hash">{{repo.prettyLatestVersion}}</span></li> | ||||||
|         <li>Last Updated: |         <li>Last Updated: | ||||||
|           {{#if repo.latest.date}} |           {{#if repo.latest.date}} | ||||||
|             {{moment-from-now repo.latest.date interval=1000}} |             {{moment-from-now repo.latest.date interval=1000}} | ||||||
|  | @ -27,9 +27,9 @@ | ||||||
|         <li class='new-commits'>{{new-commits repo.latest.commits_behind repo.version repo.latest.version repo.url}}</li> |         <li class='new-commits'>{{new-commits repo.latest.commits_behind repo.version repo.latest.version repo.url}}</li> | ||||||
|       </ul> |       </ul> | ||||||
|       {{#if repo.upgrading}} |       {{#if repo.upgrading}} | ||||||
|         <button class="btn" {{action "upgrade"}}>Currently Upgrading...</button> |         <button class="btn" disabled=true>Currently Upgrading...</button> | ||||||
|       {{else}} |       {{else}} | ||||||
|         <button class="btn" {{action "upgrade"}} disabled={{upgradeDisabled}}>Upgrade</button> |         <button class="upgrade-button btn" {{action "upgrade"}} disabled={{upgradeDisabled}}>Upgrade</button> | ||||||
|       {{/if}} |       {{/if}} | ||||||
|     </div> |     </div> | ||||||
|   {{/if}} |   {{/if}} | ||||||
|  |  | ||||||
|  | @ -14,7 +14,7 @@ | ||||||
|   </tr> |   </tr> | ||||||
|   <tbody> |   <tbody> | ||||||
|     {{#each model as |repo|}} |     {{#each model as |repo|}} | ||||||
|       {{repo-status repo=repo upgradingRepo=upgrading managerRepo=managerRepo upgrade="upgrade"}} |       {{repo-status repo=repo upgradingRepo=upgrading managerRepo=managerRepo}} | ||||||
|     {{/each}} |     {{/each}} | ||||||
|   </tbody> |   </tbody> | ||||||
| </table> | </table> | ||||||
|  |  | ||||||
|  | @ -1,9 +0,0 @@ | ||||||
| { |  | ||||||
|   "name": "manager-client", |  | ||||||
|   "dependencies": { |  | ||||||
|     "ember": "~2.10.0", |  | ||||||
|     "bootbox": "~4.3.0", |  | ||||||
|     "ember-cli-shims": "0.1.3", |  | ||||||
|     "message-bus": "https://github.com/SamSaffron/message_bus.git#84f733c14e5b4e7f2464b5b45944ceccec727899" |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  | @ -40,6 +40,7 @@ module.exports = function(environment) { | ||||||
|     ENV.APP.LOG_VIEW_LOOKUPS = false; |     ENV.APP.LOG_VIEW_LOOKUPS = false; | ||||||
| 
 | 
 | ||||||
|     ENV.APP.rootElement = '#ember-testing'; |     ENV.APP.rootElement = '#ember-testing'; | ||||||
|  |     ENV.APP.autoboot = false; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   if (environment === 'production') { |   if (environment === 'production') { | ||||||
|  |  | ||||||
|  | @ -23,10 +23,10 @@ module.exports = function(defaults) { | ||||||
|   // modules that you would like to import into your application
 |   // modules that you would like to import into your application
 | ||||||
|   // please specify an object with the list of modules as keys
 |   // please specify an object with the list of modules as keys
 | ||||||
|   // along with the exports of each module as its value.
 |   // along with the exports of each module as its value.
 | ||||||
|   app.import("bower_components/bootbox/bootbox.js"); |   app.import("node_modules/bootbox/bootbox.min.js"); | ||||||
|   app.import("bower_components/message-bus/assets/message-bus.js"); |   app.import("node_modules/bootstrap/js/dist/util.js"); | ||||||
|   app.import("bower_components/bootstrap/js/dist/util.js"); |   app.import("node_modules/bootstrap/js/dist/modal.js"); | ||||||
|   app.import("bower_components/bootstrap/js/dist/modal.js"); |   app.import("vendor/message-bus.js"); | ||||||
| 
 | 
 | ||||||
|   return app.toTree(); |   return app.toTree(); | ||||||
| }; | }; | ||||||
|  |  | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -15,31 +15,43 @@ | ||||||
|     "test": "ember test" |     "test": "ember test" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "broccoli-asset-rev": "^2.4.5", |     "@ember/jquery": "^0.5.2", | ||||||
|     "ember-ajax": "2.5.3", |     "@ember/optional-features": "^0.6.3", | ||||||
|     "ember-cli": "2.10.0", |     "broccoli-asset-rev": "^2.7.0", | ||||||
|     "ember-cli-app-version": "^2.0.0", |     "ember-ajax": "^4.0.1", | ||||||
|     "ember-cli-babel": "^5.1.7", |     "ember-cli": "~3.6.0", | ||||||
|     "ember-cli-dependency-checker": "^1.3.0", |     "ember-cli-app-version": "^3.2.0", | ||||||
|     "ember-cli-htmlbars": "^1.0.10", |     "ember-cli-babel": "^7.1.2", | ||||||
|     "ember-cli-htmlbars-inline-precompile": "^0.3.3", |     "ember-cli-dependency-checker": "^3.0.0", | ||||||
|     "ember-cli-inject-live-reload": "^1.4.1", |     "ember-cli-eslint": "^4.2.3", | ||||||
|     "ember-cli-jshint": "^2.0.1", |     "ember-cli-htmlbars": "^3.0.0", | ||||||
|     "ember-cli-moment-shim": "3.0.1", |     "ember-cli-htmlbars-inline-precompile": "^1.0.3", | ||||||
|     "ember-cli-qunit": "^3.0.1", |     "ember-cli-inject-live-reload": "^1.8.2", | ||||||
|     "ember-cli-release": "^0.2.9", |     "ember-cli-moment-shim": "3.7.1", | ||||||
|     "ember-cli-sass": "6.1.0", |     "ember-cli-sass": "8.0.1", | ||||||
|     "ember-cli-sri": "^2.1.0", |     "ember-cli-sri": "^2.1.1", | ||||||
|     "ember-cli-test-loader": "^1.1.0", |     "ember-cli-template-lint": "^1.0.0-beta.1", | ||||||
|     "ember-cli-uglify": "^1.2.0", |     "ember-cli-uglify": "^2.1.0", | ||||||
|     "ember-export-application-global": "^1.0.5", |     "ember-data": "~3.6.0", | ||||||
|     "ember-load-initializers": "^0.5.1", |     "ember-export-application-global": "^2.0.0", | ||||||
|     "ember-moment": "7.3.0", |     "ember-load-initializers": "^1.1.0", | ||||||
|     "ember-resolver": "^2.0.3", |     "ember-maybe-import-regenerator": "^0.1.6", | ||||||
|     "loader.js": "^4.0.10" |     "ember-moment": "7.8.0", | ||||||
|  |     "ember-qunit": "^3.4.1", | ||||||
|  |     "ember-resolver": "^5.0.1", | ||||||
|  |     "ember-source": "~3.6.0", | ||||||
|  |     "ember-welcome-page": "^3.2.0", | ||||||
|  |     "eslint-plugin-ember": "^5.2.0", | ||||||
|  |     "loader.js": "^4.7.0", | ||||||
|  |     "qunit-dom": "^0.8.0", | ||||||
|  |     "sass": "^1.15.2" | ||||||
|   }, |   }, | ||||||
|   "engines": { |   "engines": { | ||||||
|     "node": ">= 0.12.0" |     "node": ">= 0.12.0" | ||||||
|   }, |   }, | ||||||
|   "private": true |   "private": true, | ||||||
|  |   "dependencies": { | ||||||
|  |     "bootbox": "^4.4.0", | ||||||
|  |     "bootstrap": "^4.2.1" | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import Ember from 'ember'; | import { run } from "@ember/runloop"; | ||||||
| 
 | 
 | ||||||
| export default function destroyApp(application) { | export default function destroyApp(application) { | ||||||
|   Ember.run(application, 'destroy'); |   run(application, "destroy"); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,9 +1,7 @@ | ||||||
| import { module } from 'qunit'; | import { module } from "qunit"; | ||||||
| import Ember from 'ember'; | import startApp from "../helpers/start-app"; | ||||||
| import startApp from '../helpers/start-app'; | import destroyApp from "../helpers/destroy-app"; | ||||||
| import destroyApp from '../helpers/destroy-app'; | import { Promise } from "rsvp"; | ||||||
| 
 |  | ||||||
| const { RSVP: { Promise } } = Ember; |  | ||||||
| 
 | 
 | ||||||
| export default function(name, options = {}) { | export default function(name, options = {}) { | ||||||
|   module(name, { |   module(name, { | ||||||
|  | @ -16,8 +14,11 @@ export default function(name, options = {}) { | ||||||
|     }, |     }, | ||||||
| 
 | 
 | ||||||
|     afterEach() { |     afterEach() { | ||||||
|       let afterEach = options.afterEach && options.afterEach.apply(this, arguments); |       let afterEach = | ||||||
|       return Promise.resolve(afterEach).then(() => destroyApp(this.application)); |         options.afterEach && options.afterEach.apply(this, arguments); | ||||||
|  |       return Promise.resolve(afterEach).then(() => | ||||||
|  |         destroyApp(this.application) | ||||||
|  |       ); | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import Resolver from '../../resolver'; | import Resolver from "../../resolver"; | ||||||
| import config from '../../config/environment'; | import config from "../../config/environment"; | ||||||
| 
 | 
 | ||||||
| const resolver = Resolver.create(); | const resolver = Resolver.create(); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,14 +1,15 @@ | ||||||
| import Ember from 'ember'; | import Application from "../../app"; | ||||||
| import Application from '../../app'; | import config from "../../config/environment"; | ||||||
| import config from '../../config/environment'; | import { assign } from "@ember/polyfills"; | ||||||
|  | import { run } from "@ember/runloop"; | ||||||
| 
 | 
 | ||||||
| export default function startApp(attrs) { | export default function startApp(attrs) { | ||||||
|   let application; |   let application; | ||||||
| 
 | 
 | ||||||
|   // use defaults, but you can override
 |   // use defaults, but you can override
 | ||||||
|   let attributes = Ember.assign({}, config.APP, attrs); |   let attributes = assign({}, config.APP, attrs); | ||||||
| 
 | 
 | ||||||
|   Ember.run(() => { |   run(() => { | ||||||
|     application = Application.create(attributes); |     application = Application.create(attributes); | ||||||
|     application.setupForTesting(); |     application.setupForTesting(); | ||||||
|     application.injectTestHelpers(); |     application.injectTestHelpers(); | ||||||
|  |  | ||||||
|  | @ -3,9 +3,10 @@ | ||||||
|   <head> |   <head> | ||||||
|     <meta charset="utf-8"> |     <meta charset="utf-8"> | ||||||
|     <meta http-equiv="X-UA-Compatible" content="IE=edge"> |     <meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||||||
|     <title>ManagerClient Tests</title> |     <title>Manager Client Tests</title> | ||||||
|     <meta name="description" content=""> |     <meta name="description" content=""> | ||||||
|     <meta name="viewport" content="width=device-width, initial-scale=1"> |     <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||||
|  |     <meta id="preloaded-data" data-preload="{"rootUrl":"/","longPollingBaseUrl":"/"}"> | ||||||
| 
 | 
 | ||||||
|     {{content-for "head"}} |     {{content-for "head"}} | ||||||
|     {{content-for "test-head"}} |     {{content-for "test-head"}} | ||||||
|  |  | ||||||
|  | @ -0,0 +1,120 @@ | ||||||
|  | import { module, test } from "qunit"; | ||||||
|  | import { setupRenderingTest } from "ember-qunit"; | ||||||
|  | import { render, find } from "@ember/test-helpers"; | ||||||
|  | import hbs from "htmlbars-inline-precompile"; | ||||||
|  | import Repo from "manager-client/models/repo"; | ||||||
|  | 
 | ||||||
|  | const repoObject = Repo.create({ | ||||||
|  |   unloaded: false, | ||||||
|  |   branch: "origin/master", | ||||||
|  |   id: "discourse", | ||||||
|  |   name: "discourse", | ||||||
|  |   official: false, | ||||||
|  |   path: "/c/discourse", | ||||||
|  |   pretty_version: "v2.2.0.beta6 +98", | ||||||
|  |   url: "https://github.com/discourse/discourse", | ||||||
|  |   version: "8f65e4f", | ||||||
|  |   latest: { | ||||||
|  |     commits_behind: 3, | ||||||
|  |     date: "2018-12-26T20:52:07.000+03:00", | ||||||
|  |     path: "/c/discourse", | ||||||
|  |     pretty_version: "v2.2.0.beta6 +101", | ||||||
|  |     version: "2b006c0" | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const managerRepo = Repo.create({ | ||||||
|  |   unloaded: false, | ||||||
|  |   branch: "origin/master", | ||||||
|  |   id: "docker_manager", | ||||||
|  |   name: "docker_manager", | ||||||
|  |   official: true, | ||||||
|  |   path: "/c/discourse/plugins/docker_manager", | ||||||
|  |   pretty_version: null, | ||||||
|  |   url: "https://github.com/discourse/docker_manager", | ||||||
|  |   version: "0b1fb4b", | ||||||
|  |   latest: { | ||||||
|  |     commits_behind: 0, | ||||||
|  |     date: "2018-12-26T20:52:07.000+03:00", | ||||||
|  |     path: "/c/discourse/plugins/docker_manager", | ||||||
|  |     pretty_version: null, | ||||||
|  |     version: "0b1fb4b" | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | module("Integration | Component | repo-status", function(hooks) { | ||||||
|  |   setupRenderingTest(hooks); | ||||||
|  | 
 | ||||||
|  |   test("it renders correctly", async function(assert) { | ||||||
|  |     this.set("repo", repoObject); | ||||||
|  |     this.set("managerRepo", managerRepo); | ||||||
|  |     await render(hbs`{{repo-status repo=repo managerRepo=managerRepo}}`); | ||||||
|  | 
 | ||||||
|  |     assert.equal( | ||||||
|  |       find("span.current.commit-hash").textContent.trim(), | ||||||
|  |       "v2.2.0.beta6 +98", | ||||||
|  |       "tag version is used when present" | ||||||
|  |     ); | ||||||
|  |     assert.equal( | ||||||
|  |       find("span.new.commit-hash").textContent.trim(), | ||||||
|  |       "v2.2.0.beta6 +101", | ||||||
|  |       "tag version is used when present" | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     assert.equal( | ||||||
|  |       find("li.new-commits a").textContent.trim(), | ||||||
|  |       "3 new commits", | ||||||
|  |       "shows number of new commits" | ||||||
|  |     ); | ||||||
|  |     assert.equal( | ||||||
|  |       find("li.new-commits a").href.trim(), | ||||||
|  |       "https://github.com/discourse/discourse/compare/8f65e4f...2b006c0", | ||||||
|  |       "links to GitHub diff page" | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     this.set("repo.pretty_version", null); | ||||||
|  |     this.set("repo.latest.pretty_version", null); | ||||||
|  | 
 | ||||||
|  |     assert.equal( | ||||||
|  |       find("span.current.commit-hash").textContent.trim(), | ||||||
|  |       "8f65e4f", | ||||||
|  |       "commit hash is used when tag version is absent" | ||||||
|  |     ); | ||||||
|  |     assert.equal( | ||||||
|  |       find("span.new.commit-hash").textContent.trim(), | ||||||
|  |       "2b006c0", | ||||||
|  |       "commit hash is used when tag version is absent" | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     assert | ||||||
|  |       .dom("img.check-circle") | ||||||
|  |       .doesNotExist("green check is absent when not official"); | ||||||
|  |     this.set("repo.official", true); | ||||||
|  |     assert | ||||||
|  |       .dom("img.check-circle") | ||||||
|  |       .exists("green check is present when official"); | ||||||
|  | 
 | ||||||
|  |     assert | ||||||
|  |       .dom("button.upgrade-button") | ||||||
|  |       .exists("upgrade button is visibile when plugin is out-of-date"); | ||||||
|  | 
 | ||||||
|  |     assert.equal( | ||||||
|  |       find("button.upgrade-button").disabled, | ||||||
|  |       false, | ||||||
|  |       "upgrade button is not disabled when docker_manager repo is up-to-date" | ||||||
|  |     ); | ||||||
|  |     this.set("managerRepo.upToDate", false); | ||||||
|  |     assert.equal( | ||||||
|  |       find("button.upgrade-button").disabled, | ||||||
|  |       true, | ||||||
|  |       "upgrade button is disabled when docker_manager repo is out-of-date" | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     this.set("repo.latest.commits_behind", 0); | ||||||
|  |     this.set("repo.version", "2b006c0"); | ||||||
|  |     this.set("repo.pretty_version", "v2.2.0.beta6 +101"); | ||||||
|  |     assert | ||||||
|  |       .dom("button.upgrade-button") | ||||||
|  |       .doesNotExist("upgrade button is not visibile when plugin is up-to-date"); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | @ -1,6 +1,8 @@ | ||||||
| import resolver from './helpers/resolver'; | import Application from "../app"; | ||||||
| import { | import config from "../config/environment"; | ||||||
|   setResolver | import { setApplication } from "@ember/test-helpers"; | ||||||
| } from 'ember-qunit'; | import { start } from "ember-qunit"; | ||||||
| 
 | 
 | ||||||
| setResolver(resolver); | setApplication(Application.create(config.APP)); | ||||||
|  | 
 | ||||||
|  | start(); | ||||||
|  |  | ||||||
|  | @ -0,0 +1,515 @@ | ||||||
|  | /*jshint bitwise: false*/ | ||||||
|  | (function(global, document, undefined) { | ||||||
|  |   'use strict'; | ||||||
|  |   var previousMessageBus = global.MessageBus; | ||||||
|  | 
 | ||||||
|  |   // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
 | ||||||
|  |   var callbacks, clientId, failCount, shouldLongPoll, queue, responseCallbacks, uniqueId, baseUrl; | ||||||
|  |   var me, started, stopped, longPoller, pollTimeout, paused, later, jQuery, interval, chunkedBackoff; | ||||||
|  |   var delayPollTimeout; | ||||||
|  | 
 | ||||||
|  |   var ajaxInProgress = false; | ||||||
|  | 
 | ||||||
|  |   uniqueId = function() { | ||||||
|  |     return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) { | ||||||
|  |       var r, v; | ||||||
|  |       r = Math.random() * 16 | 0; | ||||||
|  |       v = c === 'x' ? r : (r & 0x3 | 0x8); | ||||||
|  |       return v.toString(16); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   clientId = uniqueId(); | ||||||
|  |   responseCallbacks = {}; | ||||||
|  |   callbacks = []; | ||||||
|  |   queue = []; | ||||||
|  |   interval = null; | ||||||
|  |   failCount = 0; | ||||||
|  |   baseUrl = "/"; | ||||||
|  |   paused = false; | ||||||
|  |   later = []; | ||||||
|  |   chunkedBackoff = 0; | ||||||
|  |   jQuery = global.jQuery; | ||||||
|  |   var hiddenProperty; | ||||||
|  | 
 | ||||||
|  |   (function(){ | ||||||
|  |     var prefixes = ["","webkit","ms","moz"]; | ||||||
|  |     for(var i=0; i<prefixes.length; i++) { | ||||||
|  |       var prefix = prefixes[i]; | ||||||
|  |       var check = prefix + (prefix === "" ? "hidden" : "Hidden"); | ||||||
|  |       if(document[check] !== undefined ){ | ||||||
|  |         hiddenProperty = check; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   })(); | ||||||
|  | 
 | ||||||
|  |   var isHidden = function() { | ||||||
|  |     if (hiddenProperty !== undefined){ | ||||||
|  |       return document[hiddenProperty]; | ||||||
|  |     } else { | ||||||
|  |       return !document.hasFocus; | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   var hasLocalStorage = (function() { | ||||||
|  |     try { | ||||||
|  |       localStorage.setItem("mbTestLocalStorage", Date.now()); | ||||||
|  |       localStorage.removeItem("mbTestLocalStorage"); | ||||||
|  |       return true; | ||||||
|  |     } catch(e) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |   })(); | ||||||
|  | 
 | ||||||
|  |   var updateLastAjax = function() { | ||||||
|  |     if (hasLocalStorage) { | ||||||
|  |       localStorage.setItem("__mbLastAjax", Date.now()); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   var hiddenTabShouldWait = function() { | ||||||
|  |     if (hasLocalStorage && isHidden()) { | ||||||
|  |       var lastAjaxCall = parseInt(localStorage.getItem("__mbLastAjax"), 10); | ||||||
|  |       var deltaAjax = Date.now() - lastAjaxCall; | ||||||
|  | 
 | ||||||
|  |       return deltaAjax >= 0 && deltaAjax < me.minHiddenPollInterval; | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   var hasonprogress = (new XMLHttpRequest()).onprogress === null; | ||||||
|  |   var allowChunked = function(){ | ||||||
|  |     return me.enableChunkedEncoding && hasonprogress; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   shouldLongPoll = function() { | ||||||
|  |     return me.alwaysLongPoll || !isHidden(); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   var totalAjaxFailures = 0; | ||||||
|  |   var totalAjaxCalls = 0; | ||||||
|  |   var lastAjax; | ||||||
|  | 
 | ||||||
|  |   var processMessages = function(messages) { | ||||||
|  |     var gotData = false; | ||||||
|  |     if (!messages) return false; // server unexpectedly closed connection
 | ||||||
|  | 
 | ||||||
|  |     for (var i=0; i<messages.length; i++) { | ||||||
|  |       var message = messages[i]; | ||||||
|  |       gotData = true; | ||||||
|  |       for (var j=0; j<callbacks.length; j++) { | ||||||
|  |         var callback = callbacks[j]; | ||||||
|  |         if (callback.channel === message.channel) { | ||||||
|  |           callback.last_id = message.message_id; | ||||||
|  |           try { | ||||||
|  |             callback.func(message.data, message.global_id, message.message_id); | ||||||
|  |           } | ||||||
|  |           catch(e){ | ||||||
|  |             if(console.log) { | ||||||
|  |               console.log("MESSAGE BUS FAIL: callback " + callback.channel +  " caused exception " + e.message); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         if (message.channel === "/__status") { | ||||||
|  |           if (message.data[callback.channel] !== undefined) { | ||||||
|  |             callback.last_id = message.data[callback.channel]; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return gotData; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   var reqSuccess = function(messages) { | ||||||
|  |     failCount = 0; | ||||||
|  |     if (paused) { | ||||||
|  |       if (messages) { | ||||||
|  |         for (var i=0; i<messages.length; i++) { | ||||||
|  |           later.push(messages[i]); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       return processMessages(messages); | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   longPoller = function(poll, data) { | ||||||
|  | 
 | ||||||
|  |     if (ajaxInProgress) { | ||||||
|  |       // never allow concurrent ajax reqs
 | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     var gotData = false; | ||||||
|  |     var aborted = false; | ||||||
|  |     lastAjax = new Date(); | ||||||
|  |     totalAjaxCalls += 1; | ||||||
|  |     data.__seq = totalAjaxCalls; | ||||||
|  | 
 | ||||||
|  |     var longPoll = shouldLongPoll() && me.enableLongPolling; | ||||||
|  |     var chunked = longPoll && allowChunked(); | ||||||
|  |     if (chunkedBackoff > 0) { | ||||||
|  |       chunkedBackoff--; | ||||||
|  |       chunked = false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     var headers = { | ||||||
|  |       'X-SILENCE-LOGGER': 'true' | ||||||
|  |     }; | ||||||
|  |     for (var name in me.headers){ | ||||||
|  |       headers[name] = me.headers[name]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (!chunked){ | ||||||
|  |       headers["Dont-Chunk"] = 'true'; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     var dataType = chunked ? "text" : "json"; | ||||||
|  | 
 | ||||||
|  |     var handle_progress = function(payload, position) { | ||||||
|  | 
 | ||||||
|  |       var separator = "\r\n|\r\n"; | ||||||
|  |       var endChunk = payload.indexOf(separator, position); | ||||||
|  | 
 | ||||||
|  |       if (endChunk === -1) { | ||||||
|  |         return position; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       var chunk = payload.substring(position, endChunk); | ||||||
|  |       chunk = chunk.replace(/\r\n\|\|\r\n/g, separator); | ||||||
|  | 
 | ||||||
|  |       try { | ||||||
|  |         reqSuccess(JSON.parse(chunk)); | ||||||
|  |       } catch(e) { | ||||||
|  |         if (console.log) { | ||||||
|  |           console.log("FAILED TO PARSE CHUNKED REPLY"); | ||||||
|  |           console.log(data); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       return handle_progress(payload, endChunk + separator.length); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     var disableChunked = function(){ | ||||||
|  |       if (me.longPoll) { | ||||||
|  |         me.longPoll.abort(); | ||||||
|  |         chunkedBackoff = 30; | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     var setOnProgressListener = function(xhr) { | ||||||
|  |       var position = 0; | ||||||
|  |       // if it takes longer than 3000 ms to get first chunk, we have some proxy
 | ||||||
|  |       // this is messing with us, so just backoff from using chunked for now
 | ||||||
|  |       var chunkedTimeout = setTimeout(disableChunked,3000); | ||||||
|  |       xhr.onprogress = function () { | ||||||
|  |         clearTimeout(chunkedTimeout); | ||||||
|  |         if(xhr.getResponseHeader('Content-Type') === 'application/json; charset=utf-8') { | ||||||
|  |           // not chunked we are sending json back
 | ||||||
|  |           chunked = false; | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  |         position = handle_progress(xhr.responseText, position); | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  |     if (!me.ajax){ | ||||||
|  |       throw new Error("Either jQuery or the ajax adapter must be loaded"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     updateLastAjax(); | ||||||
|  | 
 | ||||||
|  |     ajaxInProgress = true; | ||||||
|  |     var req = me.ajax({ | ||||||
|  |       url: me.baseUrl + "message-bus/" + me.clientId + "/poll" + (!longPoll ? "?dlp=t" : ""), | ||||||
|  |       data: data, | ||||||
|  |       cache: false, | ||||||
|  |       async: true, | ||||||
|  |       dataType: dataType, | ||||||
|  |       type: 'POST', | ||||||
|  |       headers: headers, | ||||||
|  |       messageBus: { | ||||||
|  |         chunked: chunked, | ||||||
|  |         onProgressListener: function(xhr) { | ||||||
|  |           var position = 0; | ||||||
|  |           // if it takes longer than 3000 ms to get first chunk, we have some proxy
 | ||||||
|  |           // this is messing with us, so just backoff from using chunked for now
 | ||||||
|  |           var chunkedTimeout = setTimeout(disableChunked,3000); | ||||||
|  |           return xhr.onprogress = function () { | ||||||
|  |             clearTimeout(chunkedTimeout); | ||||||
|  |             if(xhr.getResponseHeader('Content-Type') === 'application/json; charset=utf-8') { | ||||||
|  |               chunked = false; // not chunked, we are sending json back
 | ||||||
|  |             } else { | ||||||
|  |               position = handle_progress(xhr.responseText, position); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       xhr: function() { | ||||||
|  |         var xhr = jQuery.ajaxSettings.xhr(); | ||||||
|  |         if (!chunked) { | ||||||
|  |           return xhr; | ||||||
|  |         } | ||||||
|  |         this.messageBus.onProgressListener(xhr); | ||||||
|  |         return xhr; | ||||||
|  |       }, | ||||||
|  |       success: function(messages) { | ||||||
|  |          if (!chunked) { | ||||||
|  |            // we may have requested text so jQuery will not parse
 | ||||||
|  |            if (typeof(messages) === "string") { | ||||||
|  |              messages = JSON.parse(messages); | ||||||
|  |            } | ||||||
|  |            gotData = reqSuccess(messages); | ||||||
|  |          } | ||||||
|  |        }, | ||||||
|  |       error: function(xhr, textStatus, err) { | ||||||
|  |         if(textStatus === "abort") { | ||||||
|  |           aborted = true; | ||||||
|  |         } else { | ||||||
|  |           failCount += 1; | ||||||
|  |           totalAjaxFailures += 1; | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       complete: function() { | ||||||
|  | 
 | ||||||
|  |         ajaxInProgress = false; | ||||||
|  | 
 | ||||||
|  |         var interval; | ||||||
|  |         try { | ||||||
|  |           if (gotData || aborted) { | ||||||
|  |             interval = me.minPollInterval; | ||||||
|  |           } else { | ||||||
|  |             interval = me.callbackInterval; | ||||||
|  |             if (failCount > 2) { | ||||||
|  |               interval = interval * failCount; | ||||||
|  |             } else if (!shouldLongPoll()) { | ||||||
|  |               interval = me.backgroundCallbackInterval; | ||||||
|  |             } | ||||||
|  |             if (interval > me.maxPollInterval) { | ||||||
|  |               interval = me.maxPollInterval; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             interval -= (new Date() - lastAjax); | ||||||
|  | 
 | ||||||
|  |             if (interval < 100) { | ||||||
|  |               interval = 100; | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } catch(e) { | ||||||
|  |           if(console.log && e.message) { | ||||||
|  |             console.log("MESSAGE BUS FAIL: " + e.message); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (pollTimeout) { | ||||||
|  |           clearTimeout(pollTimeout); | ||||||
|  |           pollTimeout = null; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (started) { | ||||||
|  |           pollTimeout = setTimeout(function(){ | ||||||
|  |             pollTimeout = null; | ||||||
|  |             poll(); | ||||||
|  |           }, interval); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         me.longPoll = null; | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     return req; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   me = { | ||||||
|  |     /* shared between all tabs */ | ||||||
|  |     minHiddenPollInterval: 1500, | ||||||
|  |     enableChunkedEncoding: true, | ||||||
|  |     enableLongPolling: true, | ||||||
|  |     callbackInterval: 15000, | ||||||
|  |     backgroundCallbackInterval: 60000, | ||||||
|  |     minPollInterval: 100, | ||||||
|  |     maxPollInterval: 3 * 60 * 1000, | ||||||
|  |     callbacks: callbacks, | ||||||
|  |     clientId: clientId, | ||||||
|  |     alwaysLongPoll: false, | ||||||
|  |     baseUrl: baseUrl, | ||||||
|  |     headers: {}, | ||||||
|  |     ajax: (jQuery && jQuery.ajax), | ||||||
|  |     noConflict: function(){ | ||||||
|  |       global.MessageBus = global.MessageBus.previousMessageBus; | ||||||
|  |       return this; | ||||||
|  |     }, | ||||||
|  |     diagnostics: function(){ | ||||||
|  |       console.log("Stopped: " + stopped + " Started: " + started); | ||||||
|  |       console.log("Current callbacks"); | ||||||
|  |       console.log(callbacks); | ||||||
|  |       console.log("Total ajax calls: " + totalAjaxCalls + " Recent failure count: " + failCount + " Total failures: " + totalAjaxFailures); | ||||||
|  |       console.log("Last ajax call: " + (new Date() - lastAjax) / 1000  + " seconds ago") ; | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     pause: function() { | ||||||
|  |       paused = true; | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     resume: function() { | ||||||
|  |       paused = false; | ||||||
|  |       processMessages(later); | ||||||
|  |       later = []; | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     stop: function() { | ||||||
|  |       stopped = true; | ||||||
|  |       started = false; | ||||||
|  |       if (delayPollTimeout) { | ||||||
|  |         clearTimeout(delayPollTimeout); | ||||||
|  |         delayPollTimeout = null; | ||||||
|  |       } | ||||||
|  |       if (me.longPoll) { | ||||||
|  |         me.longPoll.abort(); | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     // Start polling
 | ||||||
|  |     start: function() { | ||||||
|  |       var poll; | ||||||
|  | 
 | ||||||
|  |       if (started) return; | ||||||
|  |       started = true; | ||||||
|  |       stopped = false; | ||||||
|  | 
 | ||||||
|  |       poll = function() { | ||||||
|  |         var data; | ||||||
|  | 
 | ||||||
|  |         if(stopped) { | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (callbacks.length === 0 || hiddenTabShouldWait()) { | ||||||
|  |           if(!delayPollTimeout) { | ||||||
|  |             delayPollTimeout = setTimeout(function() { | ||||||
|  |               delayPollTimeout = null; | ||||||
|  |               poll(); | ||||||
|  |             }, parseInt(500 + Math.random() * 500)); | ||||||
|  |           } | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         data = {}; | ||||||
|  |         for (var i=0;i<callbacks.length;i++) { | ||||||
|  |           data[callbacks[i].channel] = callbacks[i].last_id; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // could possibly already be started
 | ||||||
|  |         // notice the delay timeout above
 | ||||||
|  |         if (!me.longPoll) { | ||||||
|  |           me.longPoll = longPoller(poll, data); | ||||||
|  |         } | ||||||
|  |       }; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |       // monitor visibility, issue a new long poll when the page shows
 | ||||||
|  |       if(document.addEventListener && 'hidden' in document){ | ||||||
|  |         me.visibilityEvent = global.document.addEventListener('visibilitychange', function(){ | ||||||
|  |           if(!document.hidden && !me.longPoll && pollTimeout){ | ||||||
|  | 
 | ||||||
|  |             clearTimeout(pollTimeout); | ||||||
|  |             clearTimeout(delayPollTimeout); | ||||||
|  | 
 | ||||||
|  |             delayPollTimeout = null; | ||||||
|  |             pollTimeout = null; | ||||||
|  |             poll(); | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       poll(); | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     "status": function() { | ||||||
|  |       if (paused) { | ||||||
|  |          return "paused"; | ||||||
|  |       } else if (started) { | ||||||
|  |          return "started"; | ||||||
|  |       } else if (stopped) { | ||||||
|  |         return "stopped"; | ||||||
|  |       } else { | ||||||
|  |         throw "Cannot determine current status"; | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     // Subscribe to a channel
 | ||||||
|  |     // if lastId is 0 or larger, it will recieve messages AFTER that id
 | ||||||
|  |     // if lastId is negative it will perform lookbehind
 | ||||||
|  |     // -1 will subscribe to all new messages
 | ||||||
|  |     // -2 will recieve last message + all new messages
 | ||||||
|  |     // -3 will recieve last 2 messages + all new messages
 | ||||||
|  |     subscribe: function(channel, func, lastId) { | ||||||
|  | 
 | ||||||
|  |       if(!started && !stopped){ | ||||||
|  |         me.start(); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (typeof(lastId) !== "number") { | ||||||
|  |         lastId = -1; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (typeof(channel) !== "string") { | ||||||
|  |         throw "Channel name must be a string!"; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       callbacks.push({ | ||||||
|  |         channel: channel, | ||||||
|  |         func: func, | ||||||
|  |         last_id: lastId | ||||||
|  |       }); | ||||||
|  |       if (me.longPoll) { | ||||||
|  |         me.longPoll.abort(); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       return func; | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     // Unsubscribe from a channel
 | ||||||
|  |     unsubscribe: function(channel, func) { | ||||||
|  |       // TODO allow for globbing in the middle of a channel name
 | ||||||
|  |       // like /something/*/something
 | ||||||
|  |       // at the moment we only support globbing /something/*
 | ||||||
|  |       var glob; | ||||||
|  |       if (channel.indexOf("*", channel.length - 1) !== -1) { | ||||||
|  |         channel = channel.substr(0, channel.length - 1); | ||||||
|  |         glob = true; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       var removed = false; | ||||||
|  | 
 | ||||||
|  |       for (var i=callbacks.length-1; i>=0; i--) { | ||||||
|  | 
 | ||||||
|  |         var callback = callbacks[i]; | ||||||
|  |         var keep; | ||||||
|  | 
 | ||||||
|  |         if (glob) { | ||||||
|  |           keep = callback.channel.substr(0, channel.length) !== channel; | ||||||
|  |         } else { | ||||||
|  |           keep = callback.channel !== channel; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(!keep && func && callback.func !== func){ | ||||||
|  |           keep = true; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (!keep) { | ||||||
|  |           callbacks.splice(i,1); | ||||||
|  |           removed = true; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (removed && me.longPoll) { | ||||||
|  |         me.longPoll.abort(); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       return removed; | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |   global.MessageBus = me; | ||||||
|  | })(window, document); | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Loading…
	
		Reference in New Issue