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
|
||||
manager-client/vendor/*
|
||||
*.sublime-workspace
|
||||
.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
|
||||
|
||||
* Install `node.js`, `bower`, `grunt`
|
||||
* Install `node.js` and `yarn`
|
||||
* 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`)
|
||||
* 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:
|
||||
* `cd manager-client`
|
||||
* `npm install`
|
||||
* `bower install`
|
||||
* Use `ember server --proxy "http://localhost:3000"` to proxy to your Discourse instance running on port 3000
|
||||
* `yarn install`
|
||||
* Make sure your local Discourse instance is running at 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`
|
||||
* 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
|
||||
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
|
||||
|
||||
1. Fork it
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ module DockerManager
|
|||
if r.valid?
|
||||
result[:id] = r.name.downcase.gsub(/[^a-z]/, '_').gsub(/_+/, '_').sub(/_$/, '')
|
||||
result[:version] = r.latest_local_commit
|
||||
result[:pretty_version] = r.latest_local_tag_version.presence
|
||||
result[:url] = r.url
|
||||
if r.upgrading?
|
||||
result[:upgrading] = true
|
||||
|
|
@ -61,6 +62,7 @@ module DockerManager
|
|||
{
|
||||
path: repo.path,
|
||||
version: repo.latest_origin_commit,
|
||||
pretty_version: repo.latest_origin_tag_version.presence,
|
||||
commits_behind: repo.commits_behind,
|
||||
date: repo.latest_origin_commit_date
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,25 +7,7 @@
|
|||
<meta name="description" content="">
|
||||
<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" />
|
||||
|
||||
<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>
|
||||
<meta id="preloaded-data" data-preload="<%= { rootUrl: discourse_root_url, longPollingBaseUrl: long_polling_base_url }.to_json %>">
|
||||
|
||||
<%= javascript_include_tag "docker-manager-vendor" %>
|
||||
<%= 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}"
|
||||
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
|
||||
commit_date(latest_origin_commit)
|
||||
end
|
||||
|
|
@ -83,6 +91,16 @@ class DockerManager::GitRepo
|
|||
|
||||
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
|
||||
@upgrade_key ||= "upgrade:#{path}"
|
||||
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/)
|
||||
* [Node.js](https://nodejs.org/) (with NPM)
|
||||
* [Bower](https://bower.io/)
|
||||
* [Yarn](https://yarnpkg.com)
|
||||
* [Ember CLI](https://ember-cli.com/)
|
||||
* [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
|
||||
* `cd manager-client`
|
||||
* `npm install`
|
||||
* `bower install`
|
||||
* `yarn install`
|
||||
|
||||
## Running / Development
|
||||
|
||||
* `ember serve`
|
||||
* `./dev_server` (in root directory of this plugin)
|
||||
* Visit your app at [http://localhost:4200](http://localhost:4200).
|
||||
|
||||
### 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 --environment production` (production)
|
||||
|
||||
### Deploying
|
||||
|
||||
Specify what it takes to deploy your app.
|
||||
|
||||
## Further Reading / Useful Links
|
||||
|
||||
* [ember.js](http://emberjs.com/)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import Ember from 'ember';
|
||||
import Resolver from './resolver';
|
||||
import loadInitializers from 'ember-load-initializers';
|
||||
import config from './config/environment';
|
||||
import Ember from "ember";
|
||||
import Resolver from "./resolver";
|
||||
import loadInitializers from "ember-load-initializers";
|
||||
import config from "./config/environment";
|
||||
import Application from "@ember/application";
|
||||
|
||||
let App;
|
||||
|
||||
Ember.MODEL_FACTORY_INJECTIONS = true;
|
||||
|
||||
App = Ember.Application.extend({
|
||||
App = Application.extend({
|
||||
modulePrefix: config.modulePrefix,
|
||||
podModulePrefix: config.podModulePrefix,
|
||||
Resolver
|
||||
|
|
|
|||
|
|
@ -1,21 +1,22 @@
|
|||
import Ember from 'ember';
|
||||
import Component from "@ember/component";
|
||||
import { computed } from "@ember/object";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
classNameBindings: [':progress', ':progress-striped', 'active'],
|
||||
export default Component.extend({
|
||||
classNameBindings: [":progress", ":progress-striped", "active"],
|
||||
|
||||
active: function() {
|
||||
return parseInt(this.get('percent'), 10) !== 100;
|
||||
}.property('percent'),
|
||||
active: computed("percent", function() {
|
||||
return parseInt(this.get("percent"), 10) !== 100;
|
||||
}),
|
||||
|
||||
barStyle: function() {
|
||||
let percent = parseInt(this.get('percent'), 10);
|
||||
if (percent > 0) {
|
||||
if (percent > 100) { percent = 100; }
|
||||
return ('width: ' + this.get('percent') + '%').htmlSafe();
|
||||
barStyle: computed("percent", function() {
|
||||
let percent = parseInt(this.get("percent"), 10);
|
||||
if (percent > 0) {
|
||||
if (percent > 100) {
|
||||
percent = 100;
|
||||
}
|
||||
return ("width: " + this.get("percent") + "%").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({
|
||||
tagName: 'tr',
|
||||
export default Component.extend({
|
||||
router: service(),
|
||||
tagName: "tr",
|
||||
|
||||
upgradeDisabled: function() {
|
||||
const upgradingRepo = this.get('upgradingRepo');
|
||||
upgradeDisabled: computed(
|
||||
"upgradingRepo",
|
||||
"repo",
|
||||
"managerRepo",
|
||||
"managerRepo.upToDate",
|
||||
function() {
|
||||
const upgradingRepo = this.get("upgradingRepo");
|
||||
|
||||
if (!upgradingRepo) {
|
||||
const managerRepo = this.get('managerRepo');
|
||||
if (!managerRepo) { return false; }
|
||||
return (!managerRepo.get('upToDate')) && managerRepo !== this.get('repo');
|
||||
if (!upgradingRepo) {
|
||||
const managerRepo = this.get("managerRepo");
|
||||
if (!managerRepo) {
|
||||
return false;
|
||||
}
|
||||
return !managerRepo.get("upToDate") && managerRepo !== this.get("repo");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}.property('upgradingRepo', 'repo', 'managerRepo', 'managerRepo.upToDate'),
|
||||
),
|
||||
|
||||
officialRepoImageSrc: function() {
|
||||
if (!this.get('repo.official')) { return; }
|
||||
return Discourse.getURL("/plugins/docker_manager/images/font-awesome-check-circle.png");
|
||||
}.property('repo.official'),
|
||||
officialRepoImageSrc: computed("repo.official", function() {
|
||||
if (!this.get("repo.official")) {
|
||||
return;
|
||||
}
|
||||
return Discourse.getURL(
|
||||
"/plugins/docker_manager/images/font-awesome-check-circle.png"
|
||||
);
|
||||
}),
|
||||
|
||||
actions: {
|
||||
upgrade: function() {
|
||||
this.sendAction('upgrade', this.get('repo'));
|
||||
upgrade() {
|
||||
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({
|
||||
classNameBindings: [':logs'],
|
||||
export default Component.extend({
|
||||
classNameBindings: [":logs"],
|
||||
|
||||
_outputChanged: function() {
|
||||
Ember.run.scheduleOnce('afterRender', this, '_scrollBottom');
|
||||
}.observes('output'),
|
||||
_outputChanged: observer("output", function() {
|
||||
scheduleOnce("afterRender", this, "_scrollBottom");
|
||||
}),
|
||||
|
||||
_scrollBottom: function() {
|
||||
if (this.get('followOutput')) {
|
||||
_scrollBottom() {
|
||||
if (this.get("followOutput")) {
|
||||
this.$().scrollTop(this.$()[0].scrollHeight);
|
||||
}
|
||||
},
|
||||
|
||||
_scrollOnInsert: function() {
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
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 Ember.Controller.extend({
|
||||
showBanner: function(){
|
||||
if(this.get("bannerDismissed")){
|
||||
export default Controller.extend({
|
||||
showBanner: computed("banner", "bannerDismissed", "banner.[]", function() {
|
||||
if (this.get("bannerDismissed")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const banner = this.get("banner");
|
||||
return banner && banner.length > 0;
|
||||
}.property("banner", "bannerDismissed", "banner.@each"),
|
||||
}),
|
||||
|
||||
appendBannerHtml: function(html){
|
||||
appendBannerHtml(html) {
|
||||
const banner = this.get("banner") || [];
|
||||
if(banner.indexOf(html) === -1){
|
||||
if (banner.indexOf(html) === -1) {
|
||||
banner.pushObject(html);
|
||||
}
|
||||
this.set("banner", banner);
|
||||
},
|
||||
|
||||
logoUrl: function() {
|
||||
return Discourse.getURL("/assets/images/docker-manager-aff8eaea0445c0488c19f8cfd14faa8c2b278924438f19048eacc175d7d134e4.png");
|
||||
}.property(),
|
||||
logoUrl: computed(function() {
|
||||
return Discourse.getURL(
|
||||
"/assets/images/docker-manager-aff8eaea0445c0488c19f8cfd14faa8c2b278924438f19048eacc175d7d134e4.png"
|
||||
);
|
||||
}),
|
||||
|
||||
returnToSiteUrl: function() {
|
||||
returnToSiteUrl: computed(function() {
|
||||
return Discourse.getURL("/");
|
||||
}.property(),
|
||||
}),
|
||||
|
||||
backupsUrl: function() {
|
||||
backupsUrl: computed(function() {
|
||||
return Discourse.getURL("/admin/backups");
|
||||
}.property(),
|
||||
}),
|
||||
|
||||
actions: {
|
||||
dismiss: function () {
|
||||
dismiss() {
|
||||
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,
|
||||
upgrading: null,
|
||||
|
||||
upgradeAllButtonDisabled: function () {
|
||||
return !this.get("managerRepo.upToDate") || this.get("allUpToDate");
|
||||
}.property("managerRepo.upToDate", "allUpToDate"),
|
||||
upgradeAllButtonDisabled: computed(
|
||||
"managerRepo.upToDate",
|
||||
"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"));
|
||||
}.property("model.@each.upToDate"),
|
||||
}),
|
||||
|
||||
actions: {
|
||||
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,
|
||||
|
||||
init: function() {
|
||||
init() {
|
||||
this._super();
|
||||
var self = this;
|
||||
|
||||
window.setInterval(function() {
|
||||
self.performRefresh();
|
||||
window.setInterval(() => {
|
||||
this.performRefresh();
|
||||
}, 5000);
|
||||
},
|
||||
|
||||
performRefresh: function() {
|
||||
if (this.get('autoRefresh')) {
|
||||
this.get('model').refresh();
|
||||
performRefresh() {
|
||||
if (this.get("autoRefresh")) {
|
||||
this.get("model").refresh();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
/* 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,
|
||||
|
||||
init() {
|
||||
|
|
@ -10,24 +12,24 @@ export default Ember.Controller.extend({
|
|||
this.reset();
|
||||
},
|
||||
|
||||
complete: Ember.computed.equal('status', 'complete'),
|
||||
failed: Ember.computed.equal('status', 'failed'),
|
||||
complete: equal("status", "complete"),
|
||||
failed: equal("status", "failed"),
|
||||
|
||||
multiUpgrade: function() {
|
||||
multiUpgrade: computed("model.length", function() {
|
||||
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");
|
||||
}.property("model.@each.name"),
|
||||
}),
|
||||
|
||||
isUpToDate: function () {
|
||||
isUpToDate: computed("model.[].upToDate", function() {
|
||||
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"));
|
||||
}.property("model.@each.upgrading"),
|
||||
}),
|
||||
|
||||
repos() {
|
||||
const model = this.get("model");
|
||||
|
|
@ -36,43 +38,45 @@ export default Ember.Controller.extend({
|
|||
|
||||
updateAttribute(key, value, valueIsKey = false) {
|
||||
this.get("model").forEach(repo => {
|
||||
value = valueIsKey ? repo.get(value) : value;
|
||||
value = valueIsKey ? repo.get(value) : value;
|
||||
repo.set(key, value);
|
||||
});
|
||||
},
|
||||
|
||||
messageReceived(msg) {
|
||||
switch(msg.type) {
|
||||
switch (msg.type) {
|
||||
case "log":
|
||||
this.set('output', this.get('output') + msg.value + "\n");
|
||||
this.set("output", this.get("output") + msg.value + "\n");
|
||||
break;
|
||||
case "percent":
|
||||
this.set('percent', msg.value);
|
||||
this.set("percent", msg.value);
|
||||
break;
|
||||
case "status":
|
||||
this.set('status', msg.value);
|
||||
this.set("status", msg.value);
|
||||
|
||||
if (msg.value === "complete") {
|
||||
this.get("model").filter(repo => repo.get("upgrading")).forEach(repo => {
|
||||
repo.set("version", repo.get("latest.version"));
|
||||
});
|
||||
this.get("model")
|
||||
.filter(repo => repo.get("upgrading"))
|
||||
.forEach(repo => {
|
||||
repo.set("version", repo.get("latest.version"));
|
||||
});
|
||||
}
|
||||
|
||||
if (msg.value === 'complete' || msg.value === 'failed') {
|
||||
this.updateAttribute('upgrading', false);
|
||||
if (msg.value === "complete" || msg.value === "failed") {
|
||||
this.updateAttribute("upgrading", false);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
upgradeButtonText: function() {
|
||||
upgradeButtonText: computed("upgrading", function() {
|
||||
if (this.get("upgrading")) {
|
||||
return "Upgrading...";
|
||||
} else {
|
||||
return "Start Upgrading";
|
||||
}
|
||||
}.property("upgrading"),
|
||||
}),
|
||||
|
||||
startBus() {
|
||||
MessageBus.subscribe("/docker/upgrade", msg => {
|
||||
|
|
@ -85,7 +89,7 @@ export default Ember.Controller.extend({
|
|||
},
|
||||
|
||||
reset() {
|
||||
this.setProperties({ output: '', status: null, percent: 0 });
|
||||
this.setProperties({ output: "", status: null, percent: 0 });
|
||||
},
|
||||
|
||||
actions: {
|
||||
|
|
@ -93,33 +97,41 @@ export default Ember.Controller.extend({
|
|||
this.reset();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
const repo = this.get('model')[0];
|
||||
if (repo.get('upgrading')) { return; }
|
||||
const repo = this.get("model")[0];
|
||||
if (repo.get("upgrading")) {
|
||||
return;
|
||||
}
|
||||
repo.startUpgrade();
|
||||
},
|
||||
|
||||
resetUpgrade() {
|
||||
bootbox.confirm("WARNING: You should only reset upgrades that have failed and are not running.\n\n"+
|
||||
"This will NOT cancel currently running builds and should only be used as a last resort.", result => {
|
||||
if (result) {
|
||||
if (this.get("multiUpgrade")) {
|
||||
return Repo.resetAll(this.get("model").filter(repo => !repo.get("upToDate"))).finally(() => {
|
||||
bootbox.confirm(
|
||||
"WARNING: You should only reset upgrades that have failed and are not running.\n\n" +
|
||||
"This will NOT cancel currently running builds and should only be used as a last resort.",
|
||||
result => {
|
||||
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.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;
|
||||
|
||||
if (parseInt(commitsBehind) === 0) {
|
||||
|
|
@ -11,12 +13,12 @@ export default Ember.Helper.helper(function(params) {
|
|||
commitsBehind === 1 ? "" : "s"
|
||||
}`;
|
||||
|
||||
if (Ember.isNone(url)) {
|
||||
if (isNone(url)) {
|
||||
return description;
|
||||
}
|
||||
|
||||
var _url = url.substr(0, url.search(/(\.git)?$/));
|
||||
description = `<a href='${_url}/compare/${oldSha}..${newSha}'>${description}</a>`;
|
||||
const _url = url.substr(0, url.search(/(\.git)?$/));
|
||||
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>
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta id="preloaded-data" data-preload="{"rootUrl":"/","longPollingBaseUrl":"/"}">
|
||||
|
||||
{{content-for 'head'}}
|
||||
|
||||
|
|
@ -14,17 +15,6 @@
|
|||
|
||||
{{content-for 'head-footer'}}
|
||||
|
||||
<script>
|
||||
window.Discourse = {
|
||||
rootUrl: '/',
|
||||
longPollingBaseUrl: '/',
|
||||
|
||||
getURL: function(url) {
|
||||
return url;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
{{content-for 'body'}}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
/* global $:true, Discourse */
|
||||
import request from 'ember-ajax/request';
|
||||
import Discourse from "manager-client/discourse";
|
||||
|
||||
export default {
|
||||
name: "findCsrfToken",
|
||||
|
||||
initialize() {
|
||||
return request(Discourse.getURL('/session/csrf')).then(function(result) {
|
||||
var token = result.csrf;
|
||||
$.ajaxPrefilter(function(options, originalOptions, xhr) {
|
||||
return Em.$.ajax(Discourse.getURL("/session/csrf")).then(result => {
|
||||
const token = result.csrf;
|
||||
Em.$.ajaxPrefilter((options, originalOptions, xhr) => {
|
||||
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 {
|
||||
name: "message-bus",
|
||||
|
|
@ -6,14 +6,16 @@ export default {
|
|||
initialize() {
|
||||
MessageBus.baseUrl = Discourse.longPollingBaseUrl;
|
||||
|
||||
if (MessageBus.baseUrl !== '/') {
|
||||
if (MessageBus.baseUrl !== "/") {
|
||||
MessageBus.ajax = function(opts) {
|
||||
opts.headers = opts.headers || {};
|
||||
opts.headers['X-Shared-Session-Key'] = $('meta[name=shared_session_key]').attr('content');
|
||||
return $.ajax(opts);
|
||||
opts.headers["X-Shared-Session-Key"] = Em.$(
|
||||
"meta[name=shared_session_key]"
|
||||
).attr("content");
|
||||
return Em.$.ajax(opts);
|
||||
};
|
||||
} 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';
|
||||
import Ember from 'ember';
|
||||
|
||||
const ProcessList = Ember.Object.extend({
|
||||
const ProcessList = EmberObject.extend({
|
||||
output: null,
|
||||
|
||||
refresh() {
|
||||
return request(Discourse.getURL("/admin/docker/ps"), {dataType: 'text'}).then(result => {
|
||||
this.set('output', result);
|
||||
return Em.$.ajax(Discourse.getURL("/admin/docker/ps"), {
|
||||
dataType: "text"
|
||||
}).then(result => {
|
||||
this.set("output", result);
|
||||
return this;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
/* global Discourse */
|
||||
|
||||
import request from 'ember-ajax/request';
|
||||
import Ember from 'ember';
|
||||
import Discourse from "manager-client/discourse";
|
||||
import { default as EmberObject, computed } from "@ember/object";
|
||||
import { or } from "@ember/object/computed";
|
||||
import { isNone } from "@ember/utils";
|
||||
import { Promise } from "rsvp";
|
||||
|
||||
let loaded = [];
|
||||
|
||||
|
|
@ -9,109 +10,150 @@ function concatVersions(repos) {
|
|||
return repos.map(repo => repo.get("version")).join(", ");
|
||||
}
|
||||
|
||||
const Repo = Ember.Object.extend({
|
||||
const Repo = EmberObject.extend({
|
||||
unloaded: true,
|
||||
checking: false,
|
||||
|
||||
checkingStatus: Ember.computed.or('unloaded', 'checking'),
|
||||
upToDate: function() {
|
||||
return !this.get('upgrading') & (this.get('version') === this.get('latest.version'));
|
||||
}.property('upgrading', 'version', 'latest.version'),
|
||||
checkingStatus: or("unloaded", "checking"),
|
||||
upToDate: computed("upgrading", "version", "latest.version", function() {
|
||||
return (
|
||||
!this.get("upgrading") &
|
||||
(this.get("version") === this.get("latest.version"))
|
||||
);
|
||||
}),
|
||||
|
||||
shouldCheck: function() {
|
||||
if (Ember.isNone(this.get('version'))) { return false; }
|
||||
if (this.get('checking')) { return false; }
|
||||
prettyVersion: computed("version", "pretty_version", function() {
|
||||
return this.get("pretty_version") || this.get("version");
|
||||
}),
|
||||
|
||||
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
|
||||
var lastCheckedAt = this.get('lastCheckedAt');
|
||||
const lastCheckedAt = this.get("lastCheckedAt");
|
||||
if (lastCheckedAt) {
|
||||
var ago = new Date().getTime() - lastCheckedAt;
|
||||
const ago = new Date().getTime() - lastCheckedAt;
|
||||
return ago > 60 * 1000;
|
||||
}
|
||||
return true;
|
||||
}.property().volatile(),
|
||||
}).volatile(),
|
||||
|
||||
repoAjax: function(url, args) {
|
||||
repoAjax(url, 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() {
|
||||
return new Ember.RSVP.Promise(resolve => {
|
||||
if (!this.get('shouldCheck')) {
|
||||
this.set('unloaded', false);
|
||||
findLatest() {
|
||||
return new Promise(resolve => {
|
||||
if (!this.get("shouldCheck")) {
|
||||
this.set("unloaded", false);
|
||||
return resolve();
|
||||
}
|
||||
|
||||
this.set('checking', true);
|
||||
this.repoAjax(Discourse.getURL('/admin/docker/latest')).then(result => {
|
||||
this.set("checking", true);
|
||||
this.repoAjax(Discourse.getURL("/admin/docker/latest")).then(result => {
|
||||
this.setProperties({
|
||||
unloaded: false,
|
||||
checking: false,
|
||||
lastCheckedAt: new Date().getTime(),
|
||||
latest: Ember.Object.create(result.latest)
|
||||
latest: EmberObject.create(result.latest)
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
findProgress: function() {
|
||||
return this.repoAjax(Discourse.getURL('/admin/docker/progress')).then(result => result.progress);
|
||||
findProgress() {
|
||||
return this.repoAjax(Discourse.getURL("/admin/docker/progress")).then(
|
||||
result => result.progress
|
||||
);
|
||||
},
|
||||
|
||||
resetUpgrade: function() {
|
||||
return this.repoAjax(Discourse.getURL('/admin/docker/upgrade'), { dataType: 'text', type: 'DELETE' }).then(() => {
|
||||
this.set('upgrading', false);
|
||||
resetUpgrade() {
|
||||
return this.repoAjax(Discourse.getURL("/admin/docker/upgrade"), {
|
||||
dataType: "text",
|
||||
type: "DELETE"
|
||||
}).then(() => {
|
||||
this.set("upgrading", false);
|
||||
});
|
||||
},
|
||||
|
||||
startUpgrade: function() {
|
||||
this.set('upgrading', true);
|
||||
startUpgrade() {
|
||||
this.set("upgrading", true);
|
||||
|
||||
return this.repoAjax(Discourse.getURL('/admin/docker/upgrade'), { dataType: 'text', type: 'POST' }).catch(() => {
|
||||
this.set('upgrading', false);
|
||||
return this.repoAjax(Discourse.getURL("/admin/docker/upgrade"), {
|
||||
dataType: "text",
|
||||
type: "POST"
|
||||
}).catch(() => {
|
||||
this.set("upgrading", false);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Repo.reopenClass({
|
||||
findAll() {
|
||||
return new Ember.RSVP.Promise(function (resolve) {
|
||||
if (loaded.length) { return resolve(loaded); }
|
||||
return new Promise(resolve => {
|
||||
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));
|
||||
resolve(loaded);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
findUpgrading: function() {
|
||||
return this.findAll().then(result => result.findBy('upgrading', true));
|
||||
findUpgrading() {
|
||||
return this.findAll().then(result => result.findBy("upgrading", true));
|
||||
},
|
||||
|
||||
find: function(id) {
|
||||
return this.findAll().then(result => result.findBy('id', id));
|
||||
find(id) {
|
||||
return this.findAll().then(result => result.findBy("id", id));
|
||||
},
|
||||
|
||||
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) {
|
||||
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() {
|
||||
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) {
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
import Resolver from 'ember-resolver';
|
||||
import Resolver from "ember-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,
|
||||
rootURL: config.rootURL
|
||||
});
|
||||
|
||||
Router.map(function() {
|
||||
this.route("processes");
|
||||
this.route('upgrade', { path: '/upgrade/:id' });
|
||||
this.route("upgrade", { path: "/upgrade/:id" });
|
||||
});
|
||||
|
||||
export default Router;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
import Repo from 'manager-client/models/repo';
|
||||
import Ember from 'ember';
|
||||
import Repo from "manager-client/models/repo";
|
||||
import Route from "@ember/routing/route";
|
||||
|
||||
export default Ember.Route.extend({
|
||||
export default Route.extend({
|
||||
model() {
|
||||
return Repo.findAll();
|
||||
},
|
||||
|
||||
loadRepos(list) {
|
||||
if (list.length === 0) { return; }
|
||||
if (list.length === 0) {
|
||||
return;
|
||||
}
|
||||
this.loadRepo(list.shift()).then(() => this.loadRepos(list));
|
||||
},
|
||||
|
||||
|
|
@ -16,31 +18,30 @@ export default Ember.Route.extend({
|
|||
},
|
||||
|
||||
setupController(controller, model) {
|
||||
const applicationController = this.controllerFor('application');
|
||||
const applicationController = this.controllerFor("application");
|
||||
controller.setProperties({ model, upgrading: null });
|
||||
|
||||
model.forEach(repo => {
|
||||
if (repo.get('upgrading')) {
|
||||
controller.set('upgrading', repo);
|
||||
if (repo.get("upgrading")) {
|
||||
controller.set("upgrading", repo);
|
||||
}
|
||||
|
||||
// Special case: Upgrade docker manager first
|
||||
if (repo.get('id') === 'docker_manager') {
|
||||
controller.set('managerRepo', repo);
|
||||
if (repo.get("id") === "docker_manager") {
|
||||
controller.set("managerRepo", repo);
|
||||
}
|
||||
|
||||
// Special case: If the branch is "master" warn user
|
||||
if (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>.");
|
||||
if (
|
||||
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));
|
||||
},
|
||||
|
||||
actions: {
|
||||
upgrade(repo) {
|
||||
this.transitionTo('upgrade', repo);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { find } from 'manager-client/models/process-list';
|
||||
import Ember from 'ember';
|
||||
import { find } from "manager-client/models/process-list";
|
||||
import Route from "@ember/routing/route";
|
||||
|
||||
export default Ember.Route.extend({
|
||||
export default Route.extend({
|
||||
model: find
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,26 +1,31 @@
|
|||
import Repo from 'manager-client/models/repo';
|
||||
import Ember from 'ember';
|
||||
import Repo from "manager-client/models/repo";
|
||||
import Route from "@ember/routing/route";
|
||||
import EmberObject from "@ember/object";
|
||||
import { Promise } from "rsvp";
|
||||
|
||||
export default Ember.Route.extend({
|
||||
|
||||
model: function(params) {
|
||||
export default Route.extend({
|
||||
model(params) {
|
||||
if (params.id === "all") {
|
||||
return Repo.findAll();
|
||||
}
|
||||
return Repo.find(params.id);
|
||||
},
|
||||
|
||||
afterModel: function(model) {
|
||||
afterModel(model) {
|
||||
if (Array.isArray(model)) {
|
||||
return Repo.findLatestAll().then(response => {
|
||||
JSON.parse(response).repos.forEach(_repo => {
|
||||
const repo = model.find(repo => repo.get("path") === _repo.path);
|
||||
if (!repo) { return; }
|
||||
if (!repo) {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
|
@ -28,7 +33,7 @@ export default Ember.Route.extend({
|
|||
|
||||
return Repo.findUpgrading().then(u => {
|
||||
if (u && u !== model) {
|
||||
return Ember.RSVP.Promise.reject("wat");
|
||||
return Promise.reject("wat");
|
||||
}
|
||||
return model.findLatest().then(() => {
|
||||
return model.findProgress().then(progress => {
|
||||
|
|
@ -36,21 +41,19 @@ export default Ember.Route.extend({
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
setupController: function(controller, model) {
|
||||
setupController(controller, model) {
|
||||
controller.reset();
|
||||
controller.setProperties({
|
||||
model: Array.isArray(model) ? model : [model],
|
||||
output: this.get('progress.logs'),
|
||||
percent: this.get('progress.percentage')
|
||||
output: this.get("progress.logs"),
|
||||
percent: this.get("progress.percentage")
|
||||
});
|
||||
controller.startBus();
|
||||
},
|
||||
|
||||
deactivate: function() {
|
||||
this.controllerFor('upgrade').stopBus();
|
||||
deactivate() {
|
||||
this.controllerFor("upgrade").stopBus();
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -113,6 +113,10 @@ h3.loading {
|
|||
}
|
||||
}
|
||||
|
||||
span.commit-hash {
|
||||
color: #959595;
|
||||
}
|
||||
|
||||
button.btn {
|
||||
background: #00aaff;
|
||||
color: white !important;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
<td>
|
||||
{{#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}}
|
||||
</td>
|
||||
<td>
|
||||
{{repo.name}}
|
||||
{{repo.version}}
|
||||
<a href="{{repo.url}}">{{repo.name}}</a>
|
||||
<span class="current commit-hash">{{repo.prettyVersion}}</span>
|
||||
</td>
|
||||
<td>
|
||||
{{#if repo.checkingStatus}}
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
<div class='new-version'>
|
||||
<h4>New Version Available!</h4>
|
||||
<ul>
|
||||
<li>Remote Version: {{repo.latest.version}}</li>
|
||||
<li>Remote Version: <span class="new commit-hash">{{repo.prettyLatestVersion}}</span></li>
|
||||
<li>Last Updated:
|
||||
{{#if repo.latest.date}}
|
||||
{{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>
|
||||
</ul>
|
||||
{{#if repo.upgrading}}
|
||||
<button class="btn" {{action "upgrade"}}>Currently Upgrading...</button>
|
||||
<button class="btn" disabled=true>Currently Upgrading...</button>
|
||||
{{else}}
|
||||
<button class="btn" {{action "upgrade"}} disabled={{upgradeDisabled}}>Upgrade</button>
|
||||
<button class="upgrade-button btn" {{action "upgrade"}} disabled={{upgradeDisabled}}>Upgrade</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
</tr>
|
||||
<tbody>
|
||||
{{#each model as |repo|}}
|
||||
{{repo-status repo=repo upgradingRepo=upgrading managerRepo=managerRepo upgrade="upgrade"}}
|
||||
{{repo-status repo=repo upgradingRepo=upgrading managerRepo=managerRepo}}
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</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.rootElement = '#ember-testing';
|
||||
ENV.APP.autoboot = false;
|
||||
}
|
||||
|
||||
if (environment === 'production') {
|
||||
|
|
|
|||
|
|
@ -23,10 +23,10 @@ module.exports = function(defaults) {
|
|||
// modules that you would like to import into your application
|
||||
// please specify an object with the list of modules as keys
|
||||
// along with the exports of each module as its value.
|
||||
app.import("bower_components/bootbox/bootbox.js");
|
||||
app.import("bower_components/message-bus/assets/message-bus.js");
|
||||
app.import("bower_components/bootstrap/js/dist/util.js");
|
||||
app.import("bower_components/bootstrap/js/dist/modal.js");
|
||||
app.import("node_modules/bootbox/bootbox.min.js");
|
||||
app.import("node_modules/bootstrap/js/dist/util.js");
|
||||
app.import("node_modules/bootstrap/js/dist/modal.js");
|
||||
app.import("vendor/message-bus.js");
|
||||
|
||||
return app.toTree();
|
||||
};
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -15,31 +15,43 @@
|
|||
"test": "ember test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"broccoli-asset-rev": "^2.4.5",
|
||||
"ember-ajax": "2.5.3",
|
||||
"ember-cli": "2.10.0",
|
||||
"ember-cli-app-version": "^2.0.0",
|
||||
"ember-cli-babel": "^5.1.7",
|
||||
"ember-cli-dependency-checker": "^1.3.0",
|
||||
"ember-cli-htmlbars": "^1.0.10",
|
||||
"ember-cli-htmlbars-inline-precompile": "^0.3.3",
|
||||
"ember-cli-inject-live-reload": "^1.4.1",
|
||||
"ember-cli-jshint": "^2.0.1",
|
||||
"ember-cli-moment-shim": "3.0.1",
|
||||
"ember-cli-qunit": "^3.0.1",
|
||||
"ember-cli-release": "^0.2.9",
|
||||
"ember-cli-sass": "6.1.0",
|
||||
"ember-cli-sri": "^2.1.0",
|
||||
"ember-cli-test-loader": "^1.1.0",
|
||||
"ember-cli-uglify": "^1.2.0",
|
||||
"ember-export-application-global": "^1.0.5",
|
||||
"ember-load-initializers": "^0.5.1",
|
||||
"ember-moment": "7.3.0",
|
||||
"ember-resolver": "^2.0.3",
|
||||
"loader.js": "^4.0.10"
|
||||
"@ember/jquery": "^0.5.2",
|
||||
"@ember/optional-features": "^0.6.3",
|
||||
"broccoli-asset-rev": "^2.7.0",
|
||||
"ember-ajax": "^4.0.1",
|
||||
"ember-cli": "~3.6.0",
|
||||
"ember-cli-app-version": "^3.2.0",
|
||||
"ember-cli-babel": "^7.1.2",
|
||||
"ember-cli-dependency-checker": "^3.0.0",
|
||||
"ember-cli-eslint": "^4.2.3",
|
||||
"ember-cli-htmlbars": "^3.0.0",
|
||||
"ember-cli-htmlbars-inline-precompile": "^1.0.3",
|
||||
"ember-cli-inject-live-reload": "^1.8.2",
|
||||
"ember-cli-moment-shim": "3.7.1",
|
||||
"ember-cli-sass": "8.0.1",
|
||||
"ember-cli-sri": "^2.1.1",
|
||||
"ember-cli-template-lint": "^1.0.0-beta.1",
|
||||
"ember-cli-uglify": "^2.1.0",
|
||||
"ember-data": "~3.6.0",
|
||||
"ember-export-application-global": "^2.0.0",
|
||||
"ember-load-initializers": "^1.1.0",
|
||||
"ember-maybe-import-regenerator": "^0.1.6",
|
||||
"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": {
|
||||
"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) {
|
||||
Ember.run(application, 'destroy');
|
||||
run(application, "destroy");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import { module } from 'qunit';
|
||||
import Ember from 'ember';
|
||||
import startApp from '../helpers/start-app';
|
||||
import destroyApp from '../helpers/destroy-app';
|
||||
|
||||
const { RSVP: { Promise } } = Ember;
|
||||
import { module } from "qunit";
|
||||
import startApp from "../helpers/start-app";
|
||||
import destroyApp from "../helpers/destroy-app";
|
||||
import { Promise } from "rsvp";
|
||||
|
||||
export default function(name, options = {}) {
|
||||
module(name, {
|
||||
|
|
@ -16,8 +14,11 @@ export default function(name, options = {}) {
|
|||
},
|
||||
|
||||
afterEach() {
|
||||
let afterEach = options.afterEach && options.afterEach.apply(this, arguments);
|
||||
return Promise.resolve(afterEach).then(() => destroyApp(this.application));
|
||||
let afterEach =
|
||||
options.afterEach && options.afterEach.apply(this, arguments);
|
||||
return Promise.resolve(afterEach).then(() =>
|
||||
destroyApp(this.application)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import Resolver from '../../resolver';
|
||||
import config from '../../config/environment';
|
||||
import Resolver from "../../resolver";
|
||||
import config from "../../config/environment";
|
||||
|
||||
const resolver = Resolver.create();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
import Ember from 'ember';
|
||||
import Application from '../../app';
|
||||
import config from '../../config/environment';
|
||||
import Application from "../../app";
|
||||
import config from "../../config/environment";
|
||||
import { assign } from "@ember/polyfills";
|
||||
import { run } from "@ember/runloop";
|
||||
|
||||
export default function startApp(attrs) {
|
||||
let application;
|
||||
|
||||
// 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.setupForTesting();
|
||||
application.injectTestHelpers();
|
||||
|
|
|
|||
|
|
@ -3,9 +3,10 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>ManagerClient Tests</title>
|
||||
<title>Manager Client Tests</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta id="preloaded-data" data-preload="{"rootUrl":"/","longPollingBaseUrl":"/"}">
|
||||
|
||||
{{content-for "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 {
|
||||
setResolver
|
||||
} from 'ember-qunit';
|
||||
import Application from "../app";
|
||||
import config from "../config/environment";
|
||||
import { setApplication } from "@ember/test-helpers";
|
||||
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