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