mirror of https://github.com/rancher/ui.git
Support for GitHub authentication
This commit is contained in:
parent
a6cdf94f93
commit
c2c93d4e32
|
|
@ -7,5 +7,5 @@
|
|||
*/
|
||||
"disableAnalytics": true,
|
||||
"port": 8000,
|
||||
"host": "127.0.0.1"
|
||||
"host": "0.0.0.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,5 +49,7 @@ app.import('bower_components/c3/c3.css');
|
|||
app.import('vendor/term.js/src/term.js');
|
||||
app.import('bower_components/bootstrap-multiselect/dist/js/bootstrap-multiselect.js');
|
||||
app.import('bower_components/bootstrap-multiselect/dist/css/bootstrap-multiselect.css');
|
||||
app.import('bower_components/bootstrap-switch/dist/js/bootstrap-switch.js');
|
||||
app.import('bower_components/bootstrap-switch/dist/css/bootstrap3/bootstrap-switch.css');
|
||||
|
||||
module.exports = app.toTree();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
import Ember from 'ember';
|
||||
import config from 'torii/configuration';
|
||||
import bootstrap from 'torii/bootstrap/torii';
|
||||
|
||||
export function initialize(container, application) {
|
||||
application.deferReadiness();
|
||||
var store = container.lookup('store:main');
|
||||
|
||||
// Find out if auth is enabled
|
||||
store.rawRequest({
|
||||
url: 'token', // Base url, which will be /v1
|
||||
headers: { 'authorization': undefined }
|
||||
})
|
||||
.then(function(obj) {
|
||||
var body = JSON.parse(obj.xhr.responseText);
|
||||
var token = body.data[0];
|
||||
|
||||
application.set('hasAuthentication', true);
|
||||
application.set('authenticationEnabled', token.security);
|
||||
configureTorii(token.clientId);
|
||||
|
||||
return Ember.RSVP.resolve(undefined,'API supports authentication');
|
||||
})
|
||||
.catch(function(obj) {
|
||||
application.set('hasAuthentication', false);
|
||||
application.set('authenticationEnabled', false);
|
||||
application.set('initError', obj);
|
||||
return Ember.RSVP.resolve(undefined,'Error determining API authentication');
|
||||
})
|
||||
.finally(function() {
|
||||
application.advanceReadiness();
|
||||
});
|
||||
|
||||
function configureTorii(clientId) {
|
||||
config.providers['github-oauth2'] = {
|
||||
apiKey: clientId,
|
||||
scope: 'read:org'
|
||||
};
|
||||
|
||||
bootstrap(container);
|
||||
application.inject('controller', 'torii', 'torii:main');
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'authentication',
|
||||
after: 'store',
|
||||
initialize: initialize
|
||||
};
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import SessionStorage from 'ui/utils/session-storage';
|
||||
|
||||
export function initialize(container, application) {
|
||||
container.register('session:main', SessionStorage);
|
||||
application.inject('controller', 'session', 'session:main');
|
||||
application.inject('route', 'session', 'session:main');
|
||||
application.inject('model', 'session', 'session:main');
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'session',
|
||||
before: 'store',
|
||||
initialize: initialize
|
||||
};
|
||||
|
|
@ -1,13 +1,33 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
export function initialize(container, application) {
|
||||
application.deferReadiness();
|
||||
|
||||
var store = container.lookup('store:main');
|
||||
var session = container.lookup('session:main');
|
||||
store.set('removeAfterDelete', false);
|
||||
|
||||
store.reopen({
|
||||
baseUrl: application.apiEndpoint,
|
||||
|
||||
headers: function() {
|
||||
var out = {
|
||||
'x-api-no-challenge': 'true', // Don't send me www-authenticate headers
|
||||
};
|
||||
|
||||
var token = session.get('token');
|
||||
if ( token )
|
||||
{
|
||||
out['authorization'] = 'Bearer ' + token;
|
||||
}
|
||||
|
||||
var accountId = session.get('accountId');
|
||||
if ( accountId )
|
||||
{
|
||||
out['x-api-project-id'] = accountId;
|
||||
}
|
||||
|
||||
return out;
|
||||
}.property().volatile(),
|
||||
|
||||
reallyAll: store.all,
|
||||
all: function(type) {
|
||||
type = this.normalizeType(type);
|
||||
|
|
@ -27,10 +47,6 @@ export function initialize(container, application) {
|
|||
return proxy;
|
||||
}
|
||||
});
|
||||
|
||||
store.find('schema', null, {url: 'schemas'}).then(function() {
|
||||
application.advanceReadiness();
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
import Ember from "ember";
|
||||
|
||||
export default Ember.Mixin.create({
|
||||
beforeModel: function(transition) {
|
||||
this._super(transition);
|
||||
var isLoggedIn = (this.get('session.isLoggedIn') === '1') && this.get('session.token');
|
||||
if ( this.get('app.authenticationEnabled') && !isLoggedIn )
|
||||
{
|
||||
transition.send('logout',transition,true);
|
||||
return Ember.RSVP.reject('Not logged in');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -1,19 +1,8 @@
|
|||
import Ember from "ember";
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
navExpand: (parseInt($.cookie('navExpand'),10) !== 0),
|
||||
error: null,
|
||||
pageName: '',
|
||||
|
||||
navExpandChange: function() {
|
||||
var inAYear = new Date();
|
||||
inAYear.setYear(inAYear.getFullYear()+1);
|
||||
|
||||
$.cookie('navExpand', (this.get('navExpand') ? 1 : 0), {
|
||||
expires: inAYear
|
||||
});
|
||||
}.observes('navExpand'),
|
||||
|
||||
requiresAuthentication: null,
|
||||
|
||||
absoluteEndpoint: function() {
|
||||
var url = this.get('app.endpoint');
|
||||
|
|
@ -23,11 +12,8 @@ export default Ember.Controller.extend({
|
|||
{
|
||||
url = window.location.origin + '/' + url.replace(/^\/+/,'');
|
||||
}
|
||||
else if ( url.charAt(url.length-1) !== '/' ) {
|
||||
url = url + "/";
|
||||
}
|
||||
|
||||
// Url must end in a single slash
|
||||
// URL must end in a single slash
|
||||
url = url.replace(/\/+$/,'') + '/';
|
||||
|
||||
return url;
|
||||
|
|
|
|||
|
|
@ -1,257 +1,32 @@
|
|||
import Ember from 'ember';
|
||||
import Socket from 'ui/utils/socket';
|
||||
import Util from 'ui/utils/util';
|
||||
|
||||
export default Ember.Route.extend({
|
||||
socket: null,
|
||||
|
||||
actions: {
|
||||
beforeModel: function() {
|
||||
var err = this.get('app.initError');
|
||||
if ( err )
|
||||
{
|
||||
this.send('error',err);
|
||||
}
|
||||
},
|
||||
|
||||
error: function(err) {
|
||||
this.controller.set('error',err);
|
||||
this.transitionTo('failWhale');
|
||||
console.log('Application ' + err.stack);
|
||||
},
|
||||
|
||||
setPageName: function(str) {
|
||||
this.controller.set('pageName',str);
|
||||
},
|
||||
logout: function(transition,timedOut) {
|
||||
this.set('session.isLoggedIn',0);
|
||||
this.set('app.afterLoginTransition', transition);
|
||||
var params = {queryParams: {}};
|
||||
|
||||
// Raw message from the WebSocket
|
||||
wsMessage: function(/*data*/) {
|
||||
//console.log('wsMessage',data);
|
||||
},
|
||||
|
||||
// WebSocket connected
|
||||
wsConnected: function(tries,msec) {
|
||||
var msg = 'WebSocket connected';
|
||||
if (tries > 0)
|
||||
if ( timedOut )
|
||||
{
|
||||
msg += ' (after '+ tries + ' ' + (tries === 1 ? 'try' : 'tries');
|
||||
if (msec)
|
||||
{
|
||||
msg += ', ' + (msec/1000) + ' sec';
|
||||
params.queryParams.timedOut = true;
|
||||
}
|
||||
|
||||
msg += ')';
|
||||
}
|
||||
console.log(msg);
|
||||
},
|
||||
|
||||
// WebSocket disconnected
|
||||
wsDisconnected: function() {
|
||||
console.log('WebSocket disconnected');
|
||||
},
|
||||
|
||||
wsPing: function() {
|
||||
console.log('WebSocket ping');
|
||||
},
|
||||
|
||||
/*
|
||||
agentChanged: function(change) {
|
||||
if (!change || !change.data || !change.data.resource)
|
||||
{
|
||||
return;
|
||||
}
|
||||
//console.log('Agent Changed:', change);
|
||||
var agent = change.data.resource;
|
||||
var id = agent.id;
|
||||
delete agent.hosts;
|
||||
|
||||
var hosts = this.controllerFor('hosts');
|
||||
hosts.forEach(function(host) {
|
||||
if ( host.get('agent.id') === id )
|
||||
{
|
||||
host.get('agent').setProperties(agent);
|
||||
}
|
||||
});
|
||||
},
|
||||
*/
|
||||
|
||||
containerChanged: function(change) {
|
||||
this._instanceChanged(change);
|
||||
},
|
||||
|
||||
instanceChanged: function(change) {
|
||||
this._instanceChanged(change);
|
||||
},
|
||||
|
||||
mountChanged: function(change) {
|
||||
var mount = change.data.resource;
|
||||
var volume = this.get('store').getById('volume', mount.get('volumeId'));
|
||||
if ( volume )
|
||||
{
|
||||
var mounts = volume.get('mounts');
|
||||
if ( !Ember.isArray('mounts') )
|
||||
{
|
||||
mounts = [];
|
||||
volume.set('mounts',mounts);
|
||||
}
|
||||
|
||||
var existingMount = mounts.filterBy('id', mount.get('id')).get('firstObject');
|
||||
if ( existingMount )
|
||||
{
|
||||
existingMount.setProperties(mount);
|
||||
}
|
||||
else
|
||||
{
|
||||
mounts.pushObject(mount);
|
||||
}
|
||||
}
|
||||
this.transitionTo('login', params);
|
||||
}
|
||||
},
|
||||
|
||||
enter: function() {
|
||||
var self = this;
|
||||
var store = this.get('store');
|
||||
var boundTypeify = store._typeify.bind(store);
|
||||
|
||||
var socket = Socket.create({
|
||||
url: "ws://"+window.location.host + this.get('app.wsEndpoint'),
|
||||
});
|
||||
|
||||
socket.on('message', function(event) {
|
||||
var d = JSON.parse(event.data, boundTypeify);
|
||||
self._trySend('wsMessage',d);
|
||||
|
||||
var str = d.name;
|
||||
if ( d.resourceType )
|
||||
{
|
||||
str += ' ' + d.resourceType;
|
||||
|
||||
if ( d.resourceId )
|
||||
{
|
||||
str += ' ' + d.resourceId;
|
||||
}
|
||||
}
|
||||
|
||||
var action;
|
||||
if ( d.name === 'resource.change' )
|
||||
{
|
||||
action = d.resourceType+'Changed';
|
||||
}
|
||||
else if ( d.name === 'ping' )
|
||||
{
|
||||
action = 'wsPing';
|
||||
}
|
||||
|
||||
if ( action )
|
||||
{
|
||||
self._trySend(action,d);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('connected', function(tries, after) {
|
||||
self._trySend('wsConnected', tries, after);
|
||||
});
|
||||
|
||||
socket.on('disconnected', function() {
|
||||
self._trySend('wsDisconnected', self.get('tries'));
|
||||
});
|
||||
|
||||
this.set('socket', socket);
|
||||
socket.connect();
|
||||
},
|
||||
|
||||
exit: function() {
|
||||
var socket = this.get('socket');
|
||||
if ( socket )
|
||||
{
|
||||
socket.disconnect();
|
||||
}
|
||||
},
|
||||
|
||||
_trySend: function(/*arguments*/) {
|
||||
try
|
||||
{
|
||||
this.send.apply(this,arguments);
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
if ( err instanceof Ember.Error && err.message.indexOf('Nothing handled the action') === 0 )
|
||||
{
|
||||
// Don't care
|
||||
}
|
||||
else
|
||||
{
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
_findInCollection: function(collectionName,id) {
|
||||
var collection = this.controllerFor(collectionName);
|
||||
var existing = collection.filterBy('id',id).get('firstObject');
|
||||
return existing;
|
||||
},
|
||||
|
||||
_instanceChanged: function(change) {
|
||||
if (!change || !change.data || !change.data.resource)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//console.log('Instance Changed:',change);
|
||||
var self = this;
|
||||
var instance = change.data.resource;
|
||||
var id = instance.get('id');
|
||||
|
||||
// All the hosts
|
||||
var allHosts = self.get('store').all('host');
|
||||
|
||||
// Host IDs the instance should be on
|
||||
var expectedHostIds = [];
|
||||
if ( instance.get('state') !== 'purged' )
|
||||
{
|
||||
expectedHostIds = (instance.get('hosts')||[]).map(function(host) {
|
||||
return host.get('id');
|
||||
});
|
||||
}
|
||||
|
||||
// Host IDs it is currently on
|
||||
var curHostIds = [];
|
||||
allHosts.forEach(function(host) {
|
||||
var existing = (host.get('instances')||[]).filterBy('id', id);
|
||||
if ( existing.length )
|
||||
{
|
||||
curHostIds.push(host.get('id'));
|
||||
}
|
||||
});
|
||||
|
||||
// Remove from hosts the instance shouldn't be on
|
||||
var remove = Util.arrayDiff(curHostIds, expectedHostIds);
|
||||
remove.forEach(function(hostId) {
|
||||
var host = self._findInCollection('hosts',hostId);
|
||||
if ( host )
|
||||
{
|
||||
var instances = host.get('instances');
|
||||
if ( !instances )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
instances.removeObjects(instances.filterBy('id', id));
|
||||
}
|
||||
});
|
||||
|
||||
// Add or update hosts the instance should be on
|
||||
expectedHostIds.forEach(function(hostId) {
|
||||
var host = self._findInCollection('hosts',hostId);
|
||||
if ( host )
|
||||
{
|
||||
var instances = host.get('instances');
|
||||
if ( !instances )
|
||||
{
|
||||
instances = [];
|
||||
host.set('instances',instances);
|
||||
}
|
||||
|
||||
var existing = instances.filterBy('id', id);
|
||||
if ( existing.length === 0)
|
||||
{
|
||||
instances.pushObject(instance);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,14 +1,10 @@
|
|||
<div id="underlay" class="underlay hide"></div>
|
||||
<div id="loading-underlay" class="underlay"></div>
|
||||
<div id="loading-overlay" class="loading-overlay">
|
||||
<div id="loading-underlay" class="underlay hide"></div>
|
||||
<div id="loading-overlay" class="loading-overlay hide">
|
||||
<div class="loading-box">
|
||||
<h3><i class="fa fa-spinner fa-spin fa-fw"></i> Loading…</h3>
|
||||
</div>
|
||||
</div>
|
||||
{{outlet "overlay"}}
|
||||
{{outlet "error"}}
|
||||
{{page-nav expand=navExpand}}
|
||||
{{page-header pageName=pageName}}
|
||||
<main>
|
||||
{{outlet}}
|
||||
</main>
|
||||
{{outlet}}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import Ember from "ember";
|
||||
|
||||
export default Ember.View.extend({
|
||||
classNameBindings: ['context.navExpand:nav-expand'],
|
||||
|
||||
didInsertElement: function() {
|
||||
this.$().tooltip({
|
||||
selector: '*[tooltip]',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
import Ember from "ember";
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
navExpand: (parseInt($.cookie('navExpand'),10) !== 0),
|
||||
error: null,
|
||||
pageName: '',
|
||||
|
||||
navExpandChange: function() {
|
||||
var inAYear = new Date();
|
||||
inAYear.setYear(inAYear.getFullYear()+1);
|
||||
|
||||
$.cookie('navExpand', (this.get('navExpand') ? 1 : 0), {
|
||||
expires: inAYear
|
||||
});
|
||||
}.observes('navExpand'),
|
||||
});
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Route.extend({
|
||||
beforeModel: function() {
|
||||
this.transitionTo('hosts');
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,282 @@
|
|||
import Ember from 'ember';
|
||||
import Socket from 'ui/utils/socket';
|
||||
import Util from 'ui/utils/util';
|
||||
import AuthenticatedRouteMixin from 'ui/mixins/authenticated-route';
|
||||
|
||||
export default Ember.Route.extend(AuthenticatedRouteMixin, {
|
||||
socket: null,
|
||||
|
||||
model: function() {
|
||||
return this.get('store').find('schema', null, {url: 'schemas'}).then(function() {
|
||||
return Ember.RSVP.resolve();
|
||||
});
|
||||
},
|
||||
|
||||
actions: {
|
||||
error: function(err,transition) {
|
||||
// Unauthorized error, send back to login screen
|
||||
if ( err.status === 401 )
|
||||
{
|
||||
this.send('logout',transition,true);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Bubble up
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
setPageName: function(str) {
|
||||
this.controller.set('pageName',str);
|
||||
},
|
||||
|
||||
// Raw message from the WebSocket
|
||||
wsMessage: function(/*data*/) {
|
||||
//console.log('wsMessage',data);
|
||||
},
|
||||
|
||||
// WebSocket connected
|
||||
wsConnected: function(tries,msec) {
|
||||
var msg = 'WebSocket connected';
|
||||
if (tries > 0)
|
||||
{
|
||||
msg += ' (after '+ tries + ' ' + (tries === 1 ? 'try' : 'tries');
|
||||
if (msec)
|
||||
{
|
||||
msg += ', ' + (msec/1000) + ' sec';
|
||||
}
|
||||
|
||||
msg += ')';
|
||||
}
|
||||
console.log(msg);
|
||||
},
|
||||
|
||||
// WebSocket disconnected
|
||||
wsDisconnected: function() {
|
||||
console.log('WebSocket disconnected');
|
||||
},
|
||||
|
||||
wsPing: function() {
|
||||
console.log('WebSocket ping');
|
||||
},
|
||||
|
||||
/*
|
||||
agentChanged: function(change) {
|
||||
if (!change || !change.data || !change.data.resource)
|
||||
{
|
||||
return;
|
||||
}
|
||||
//console.log('Agent Changed:', change);
|
||||
var agent = change.data.resource;
|
||||
var id = agent.id;
|
||||
delete agent.hosts;
|
||||
|
||||
var hosts = this.controllerFor('hosts');
|
||||
hosts.forEach(function(host) {
|
||||
if ( host.get('agent.id') === id )
|
||||
{
|
||||
host.get('agent').setProperties(agent);
|
||||
}
|
||||
});
|
||||
},
|
||||
*/
|
||||
|
||||
containerChanged: function(change) {
|
||||
this._instanceChanged(change);
|
||||
},
|
||||
|
||||
instanceChanged: function(change) {
|
||||
this._instanceChanged(change);
|
||||
},
|
||||
|
||||
mountChanged: function(change) {
|
||||
var mount = change.data.resource;
|
||||
var volume = this.get('store').getById('volume', mount.get('volumeId'));
|
||||
if ( volume )
|
||||
{
|
||||
var mounts = volume.get('mounts');
|
||||
if ( !Ember.isArray('mounts') )
|
||||
{
|
||||
mounts = [];
|
||||
volume.set('mounts',mounts);
|
||||
}
|
||||
|
||||
var existingMount = mounts.filterBy('id', mount.get('id')).get('firstObject');
|
||||
if ( existingMount )
|
||||
{
|
||||
existingMount.setProperties(mount);
|
||||
}
|
||||
else
|
||||
{
|
||||
mounts.pushObject(mount);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
enter: function() {
|
||||
var self = this;
|
||||
var store = this.get('store');
|
||||
var boundTypeify = store._typeify.bind(store);
|
||||
|
||||
var url = "ws://"+window.location.host + this.get('app.wsEndpoint');
|
||||
var token = this.get('session.token');
|
||||
if ( token )
|
||||
{
|
||||
url += (url.indexOf('?') >= 0 ? '&' : '?') + 'token=' + encodeURIComponent(token);
|
||||
}
|
||||
|
||||
var socket = Socket.create({
|
||||
url: url
|
||||
});
|
||||
|
||||
socket.on('message', function(event) {
|
||||
var d = JSON.parse(event.data, boundTypeify);
|
||||
self._trySend('wsMessage',d);
|
||||
|
||||
var str = d.name;
|
||||
if ( d.resourceType )
|
||||
{
|
||||
str += ' ' + d.resourceType;
|
||||
|
||||
if ( d.resourceId )
|
||||
{
|
||||
str += ' ' + d.resourceId;
|
||||
}
|
||||
}
|
||||
|
||||
var action;
|
||||
if ( d.name === 'resource.change' )
|
||||
{
|
||||
action = d.resourceType+'Changed';
|
||||
}
|
||||
else if ( d.name === 'ping' )
|
||||
{
|
||||
action = 'wsPing';
|
||||
}
|
||||
|
||||
if ( action )
|
||||
{
|
||||
self._trySend(action,d);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('connected', function(tries, after) {
|
||||
self._trySend('wsConnected', tries, after);
|
||||
});
|
||||
|
||||
socket.on('disconnected', function() {
|
||||
self._trySend('wsDisconnected', self.get('tries'));
|
||||
});
|
||||
|
||||
this.set('socket', socket);
|
||||
socket.connect();
|
||||
},
|
||||
|
||||
exit: function() {
|
||||
var socket = this.get('socket');
|
||||
if ( socket )
|
||||
{
|
||||
socket.disconnect();
|
||||
}
|
||||
|
||||
// Forget all the things
|
||||
this.get('store').reset();
|
||||
},
|
||||
|
||||
_trySend: function(/*arguments*/) {
|
||||
try
|
||||
{
|
||||
this.send.apply(this,arguments);
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
if ( err instanceof Ember.Error && err.message.indexOf('Nothing handled the action') === 0 )
|
||||
{
|
||||
// Don't care
|
||||
}
|
||||
else
|
||||
{
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
_findInCollection: function(collectionName,id) {
|
||||
var collection = this.controllerFor(collectionName);
|
||||
var existing = collection.filterBy('id',id).get('firstObject');
|
||||
return existing;
|
||||
},
|
||||
|
||||
_instanceChanged: function(change) {
|
||||
if (!change || !change.data || !change.data.resource)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//console.log('Instance Changed:',change);
|
||||
var self = this;
|
||||
var instance = change.data.resource;
|
||||
var id = instance.get('id');
|
||||
|
||||
// All the hosts
|
||||
var allHosts = self.get('store').all('host');
|
||||
|
||||
// Host IDs the instance should be on
|
||||
var expectedHostIds = [];
|
||||
if ( instance.get('state') !== 'purged' )
|
||||
{
|
||||
expectedHostIds = (instance.get('hosts')||[]).map(function(host) {
|
||||
return host.get('id');
|
||||
});
|
||||
}
|
||||
|
||||
// Host IDs it is currently on
|
||||
var curHostIds = [];
|
||||
allHosts.forEach(function(host) {
|
||||
var existing = (host.get('instances')||[]).filterBy('id', id);
|
||||
if ( existing.length )
|
||||
{
|
||||
curHostIds.push(host.get('id'));
|
||||
}
|
||||
});
|
||||
|
||||
// Remove from hosts the instance shouldn't be on
|
||||
var remove = Util.arrayDiff(curHostIds, expectedHostIds);
|
||||
remove.forEach(function(hostId) {
|
||||
var host = self._findInCollection('hosts',hostId);
|
||||
if ( host )
|
||||
{
|
||||
var instances = host.get('instances');
|
||||
if ( !instances )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
instances.removeObjects(instances.filterBy('id', id));
|
||||
}
|
||||
});
|
||||
|
||||
// Add or update hosts the instance should be on
|
||||
expectedHostIds.forEach(function(hostId) {
|
||||
var host = self._findInCollection('hosts',hostId);
|
||||
if ( host )
|
||||
{
|
||||
var instances = host.get('instances');
|
||||
if ( !instances )
|
||||
{
|
||||
instances = [];
|
||||
host.set('instances',instances);
|
||||
}
|
||||
|
||||
var existing = instances.filterBy('id', id);
|
||||
if ( existing.length === 0)
|
||||
{
|
||||
instances.pushObject(instance);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{{page-nav expand=navExpand}}
|
||||
{{page-header pageName=pageName}}
|
||||
<main>
|
||||
{{outlet}}
|
||||
</main>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import Ember from "ember";
|
||||
|
||||
export default Ember.View.extend({
|
||||
classNameBindings: ['context.navExpand:nav-expand'],
|
||||
});
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
type: 'user',
|
||||
login: null,
|
||||
|
||||
classNames: ['gh-avatar'],
|
||||
name: 'Loading...',
|
||||
avatarUrl: null,
|
||||
|
||||
nameChanged: function() {
|
||||
var self = this;
|
||||
var url = 'https://api.github.com/' + this.get('type') + 's/' + this.get('login') + '?s=40';
|
||||
|
||||
Ember.$.ajax({url: url, dataType: 'json'}).then(function(body) {
|
||||
self.set('name', body.name);
|
||||
self.set('avatarUrl', body.avatar_url);
|
||||
}, function() {
|
||||
var type = self.get('type');
|
||||
type = type.substr(0,1).toUpperCase() + type.substr(1);
|
||||
|
||||
self.set('name', 'Warning: ' + type + ' not found');
|
||||
});
|
||||
}.observes('login','type').on('init'),
|
||||
|
||||
url: function() {
|
||||
return 'https://github.com/'+ encodeURIComponent(this.get('login'));
|
||||
}.property('login'),
|
||||
});
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{{yield}}
|
||||
<div class="gh-icon">
|
||||
{{#if avatarUrl}}
|
||||
<a {{bind-attr href=url}} target="_blank">
|
||||
<img {{bind-attr src=avatarUrl}} width="40" height="40">
|
||||
</a>
|
||||
{{else}}
|
||||
<div class="gh-placeholder"></div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="gh-avatar-content">
|
||||
<div class="clip">
|
||||
<a {{bind-attr href=url}} target="_blank">{{login}}</a>
|
||||
</div>
|
||||
<div class="text-muted clip">{{name}}</div>
|
||||
</div>
|
||||
|
|
@ -1,7 +1,33 @@
|
|||
<div class="pull-right hide">
|
||||
<span class="fa-stack fa-lg">
|
||||
{{#if app.hasAuthentication}}
|
||||
{{#if app.authenticationEnabled}}
|
||||
<div class="pull-right">
|
||||
<div class="dropdown">
|
||||
<span class="fa-stack fa-lg hand" id="user-dropdown" data-toggle="dropdown" aria-expanded="true">
|
||||
<i class="fa fa-circle-o fa-stack-2x"></i>
|
||||
<i class="fa fa-user fa-stack-1x"></i>
|
||||
</span>
|
||||
</div>
|
||||
<h2>{{pageName}}</h2>
|
||||
<ul class="dropdown-menu dropdown-menu-right" role="menu" aria-labelledby="user-dropdown">
|
||||
<li role="presentation" class="disabled">
|
||||
<a role="menuitem" tabindex="-1">Settings</a>
|
||||
</li>
|
||||
|
||||
<li role="presentation">
|
||||
{{#link-to "settings.auth" role="menuitem" tabindex="-1"}}Access Control{{/link-to}}
|
||||
</li>
|
||||
|
||||
<li role="presentation" class="divider"></li>
|
||||
<li role="presentation">{{#link-to "logout" tabindex="-1"}}Log Out{{/link-to}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="pull-right">
|
||||
<div class="alert alert-warning" role="alert" style="padding: 0 15px;">
|
||||
<i class="fa fa-exclamation-triangle"></i>
|
||||
Access Control is not configured
|
||||
{{#link-to "settings.auth"}}Settings{{/link-to}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
<h2>{{pageName}}</h2>
|
||||
{{/if}}
|
||||
|
|
|
|||
|
|
@ -17,4 +17,6 @@
|
|||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="text-center text-muted">No hosts or containers yet.</div>
|
||||
{{/each}}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,6 @@ export default Ember.Route.extend({
|
|||
return this.get('store').findAll('host');
|
||||
},
|
||||
|
||||
renderTemplate: function() {
|
||||
this._super();
|
||||
this.render('hosts', {into: 'application'});
|
||||
},
|
||||
|
||||
actions: {
|
||||
newContainer: function() {
|
||||
this.transitionTo('newContainer');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
queryParams: ['timedOut'],
|
||||
|
||||
timedOut: false,
|
||||
waiting: false,
|
||||
errorMsg: null,
|
||||
|
||||
infoColor: function() {
|
||||
if ( this.get('errorMsg') )
|
||||
{
|
||||
return 'alert-warning';
|
||||
}
|
||||
{
|
||||
return 'alert-info';
|
||||
}
|
||||
}.property('errorMsg'),
|
||||
|
||||
infoMsg: function() {
|
||||
if ( this.get('errorMsg') )
|
||||
{
|
||||
return this.get('errorMsg');
|
||||
}
|
||||
else if ( this.get('timedOut') )
|
||||
{
|
||||
return 'Your session has timed out. Log in again to continue.';
|
||||
}
|
||||
else
|
||||
{
|
||||
return '';
|
||||
}
|
||||
}.property('timedOut','waiting','errorMsg'),
|
||||
|
||||
actions: {
|
||||
authenticate: function() {
|
||||
var self = this;
|
||||
var session = self.get('session');
|
||||
var app = self.get('app');
|
||||
|
||||
self.set('timedOut', false);
|
||||
self.set('waiting', true);
|
||||
|
||||
self.get('torii').open('github-oauth2').then(function(github){
|
||||
return self.get('store').rawRequest({
|
||||
url: 'token',
|
||||
method: 'POST',
|
||||
data: {
|
||||
code: github.authorizationCode,
|
||||
}
|
||||
}).then(function(res) {
|
||||
var auth = JSON.parse(res.xhr.responseText);
|
||||
session.set('token', auth.jwt);
|
||||
session.set('isLoggedIn',1);
|
||||
var transition = app.get('afterLoginTransition');
|
||||
if ( transition )
|
||||
{
|
||||
app.set('afterLoginTransition', null);
|
||||
transition.retry();
|
||||
}
|
||||
else
|
||||
{
|
||||
self.transitionToRoute('index');
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(function(res) {
|
||||
if ( res.xhr && res.xhr.responseText )
|
||||
{
|
||||
var body = JSON.parse(res.xhr.responseText);
|
||||
self.set('errorMsg', body.message);
|
||||
}
|
||||
else
|
||||
{
|
||||
self.set('errorMsg', res.err);
|
||||
}
|
||||
}).finally(function() {
|
||||
self.set('waiting', false);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Route.extend({
|
||||
beforeModel: function() {
|
||||
if ( !this.get('app.authenticationEnabled') )
|
||||
{
|
||||
this.transitionTo('index');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<div class="row">
|
||||
<div class="col-sm-3"></div>
|
||||
<div class="col-sm-6">
|
||||
<div class="farm-box">
|
||||
<section>
|
||||
<h1>Welcome to Rancher</h1>
|
||||
<br/>
|
||||
|
||||
{{#if infoMsg}}
|
||||
<div {{bind-attr class=":alert infoColor"}} role="alert">{{infoMsg}}</div>
|
||||
{{/if}}
|
||||
|
||||
<p>Rancher uses GitHub to manage acounts and teams. Click this comically large button to log in and give Rancher read-only access to your GitHub account.</p>
|
||||
<br/>
|
||||
|
||||
<p class="text-center">
|
||||
<button {{bind-attr disabled=waiting}} class="btn btn-lg btn-primary" {{action "authenticate"}}>
|
||||
<i class="fa fa-github"></i> Authenticate with GitHub
|
||||
</button>
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-3"></div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import Ember from "ember";
|
||||
|
||||
export default Ember.View.extend({
|
||||
didInsertElement: function() {
|
||||
$('BODY').addClass('farm');
|
||||
},
|
||||
|
||||
willDestroyElement: function() {
|
||||
$('BODY').removeClass('farm');
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Route.extend({
|
||||
beforeModel: function() {
|
||||
var session = this.get('session');
|
||||
session.clear();
|
||||
this.send('logout');
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
export default Ember.ObjectController.extend({
|
||||
confirmDisable: false,
|
||||
error: null,
|
||||
|
||||
createIncomplete: function() {
|
||||
var id = (this.get('clientId')||'').trim();
|
||||
var secret = (this.get('clientSecret')||'').trim();
|
||||
return id.length < 20 ||secret.length < 40;
|
||||
}.property('clientId','clientSecret'),
|
||||
|
||||
destinationUrl: function() {
|
||||
return window.location.origin+'/';
|
||||
}.property(),
|
||||
|
||||
actions: {
|
||||
test: function() {
|
||||
var self = this;
|
||||
|
||||
var model = this.get('model');
|
||||
model.set('clientId', model.get('clientId').trim());
|
||||
model.set('clientSecret', model.get('clientSecret').trim());
|
||||
model.set('enabled',false); // It should already be, but just in case..
|
||||
model.set('allowOrganizations',null);
|
||||
model.set('allowUsers',null);
|
||||
|
||||
model.save().then(function() {
|
||||
self.send('authenticate');
|
||||
}).catch(function(err) {
|
||||
self.send('gotError', err);
|
||||
});
|
||||
},
|
||||
|
||||
authenticate: function() {
|
||||
var self = this;
|
||||
self.get('torii').open('github-oauth2').then(function(github){
|
||||
return self.get('store').rawRequest({
|
||||
url: 'token',
|
||||
method: 'POST',
|
||||
data: {
|
||||
code: github.authorizationCode,
|
||||
}
|
||||
}).then(function(res) {
|
||||
var auth = JSON.parse(res.xhr.responseText);
|
||||
self.send('authenticationSucceeded', auth);
|
||||
}).catch(function(err) {
|
||||
// Github auth succeeded but didn't get back a token
|
||||
self.send('gotError', err);
|
||||
});
|
||||
})
|
||||
.catch(function(err) {
|
||||
// Github auth failed.. try again
|
||||
self.send('gotError', err);
|
||||
});
|
||||
},
|
||||
|
||||
authenticationSucceeded: function(auth) {
|
||||
console.log('Authentication succeeded');
|
||||
var self = this;
|
||||
var session = self.get('session');
|
||||
|
||||
session.set('token', auth.jwt);
|
||||
session.set('isLoggedIn',1);
|
||||
|
||||
var model = this.get('model');
|
||||
model.set('enabled',true);
|
||||
model.set('allowOrganizations', auth.orgs||[]);
|
||||
model.set('allowUsers', [auth.user]);
|
||||
model.save().then(function() {
|
||||
self.send('waitAndRefresh', true);
|
||||
}).catch(function() {
|
||||
// @TODO something
|
||||
});
|
||||
},
|
||||
|
||||
waitAndRefresh: function(expect,limit) {
|
||||
console.log('Wait and refresh',expect,limit);
|
||||
var self = this;
|
||||
if ( limit === undefined )
|
||||
{
|
||||
limit = 5;
|
||||
}
|
||||
else if ( limit === 0 )
|
||||
{
|
||||
self.send('error', 'Timed out waiting for access control to turn ' + (expect ? 'on' : 'off'));
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(function() {
|
||||
self.get('store').rawRequest({
|
||||
url: 'schemas',
|
||||
headers: { 'authorization': undefined }
|
||||
}).then(function() {
|
||||
if ( expect === false )
|
||||
{
|
||||
window.location.href = window.location.href;
|
||||
}
|
||||
else
|
||||
{
|
||||
self.send('waitAndRefresh',expect,limit-1);
|
||||
}
|
||||
}).catch(function() {
|
||||
if ( expect === true )
|
||||
{
|
||||
window.location.href = window.location.href;
|
||||
}
|
||||
else
|
||||
{
|
||||
self.send('waitAndRefresh',expect,limit-1);
|
||||
}
|
||||
});
|
||||
}, 5000/limit);
|
||||
},
|
||||
|
||||
addUser: function() {
|
||||
var str = (this.get('addUser')||'').trim();
|
||||
if ( str )
|
||||
{
|
||||
this.get('allowUsers').pushObject(str);
|
||||
this.set('addUser','');
|
||||
}
|
||||
},
|
||||
|
||||
removeUser: function(user) {
|
||||
this.get('allowUsers').removeObject(user);
|
||||
},
|
||||
|
||||
addOrg: function() {
|
||||
var str = (this.get('addOrg')||'').trim();
|
||||
if ( str )
|
||||
{
|
||||
this.get('allowOrganizations').pushObject(str);
|
||||
this.set('addOrg','');
|
||||
}
|
||||
},
|
||||
|
||||
removeOrg: function(org) {
|
||||
this.get('allowOrganizations').removeObject(org);
|
||||
},
|
||||
|
||||
saveAuthorization: function() {
|
||||
var self = this;
|
||||
var model = self.get('model');
|
||||
model.save().then(function() {
|
||||
self.send('waitAndRefresh', true);
|
||||
}).catch(function(err) {
|
||||
self.send('gotError', err);
|
||||
});
|
||||
},
|
||||
|
||||
promptDisable: function() {
|
||||
this.set('confirmDisable',true);
|
||||
},
|
||||
|
||||
gotError: function(err) {
|
||||
this.set('error', err.message);
|
||||
window.scrollY = 0;
|
||||
},
|
||||
|
||||
disable: function() {
|
||||
var self = this;
|
||||
|
||||
var model = this.get('model');
|
||||
model.set('allowOrganizations',[]);
|
||||
model.set('allowUsers',[]);
|
||||
model.set('enabled',false);
|
||||
|
||||
model.save().then(function() {
|
||||
self.send('waitAndRefresh', false);
|
||||
}).catch(function(err) {
|
||||
self.send('gotError', err);
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import AuthenticatedRouteMixin from 'ui/mixins/authenticated-route';
|
||||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Route.extend(AuthenticatedRouteMixin,{
|
||||
model: function() {
|
||||
return this.get('store').find('githubconfig').then(function(collection) {
|
||||
return collection.get('firstObject');
|
||||
});
|
||||
},
|
||||
|
||||
setupController: function(controller, model) {
|
||||
this._super(controller,model);
|
||||
controller.set('confirmDisable',false);
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
<div style="padding: 20px 20px 0 20px;">
|
||||
<div class="well">
|
||||
<h2>Access Control is {{#if app.authenticationEnabled}}<b>enabled</b>{{else}}<b class="text-warning">not configured</b>{{/if}}</h2>
|
||||
<p>
|
||||
{{#if app.authenticationEnabled}}
|
||||
Rancher is configured to restrict access to the GitHub users and organization members below.
|
||||
{{else}}
|
||||
Rancher can be configured to restrict access to a set of GitHub users and organization members. This is not currently set up, so anybody that reach this page (or the API) has full control over the system.
|
||||
{{/if}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{{#if error}}
|
||||
<div class="alert alert-danger">
|
||||
<i style="float: left;" class="fa fa-exclamation-circle"></i>
|
||||
<p style="margin-left: 50px">{{error}}</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if app.authenticationEnabled}}
|
||||
|
||||
<div class="well">
|
||||
<h4>Configure Authorization</h4>
|
||||
<hr/>
|
||||
<p>Configure what users and organization members should be allowed to use Rancher.</p>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<label>Users</label>
|
||||
|
||||
<form {{action "addUser"}}>
|
||||
<div class="input-group">
|
||||
{{input type="text" value=addUser placeholder="Add GitHub username" class="form-control"}}
|
||||
<div class="input-group-btn">
|
||||
<button class="btn btn-primary" {{action "addUser"}}><i class="fa fa-plus"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hr/>
|
||||
|
||||
<ul class="list-unstyled gh-avatar-list">
|
||||
{{#each user in allowUsers}}
|
||||
<li>
|
||||
{{#github-avatar type="user" login=user}}
|
||||
<button class="btn btn-danger btn-sm pull-right gh-action" {{action "removeUser" user}}>Remove</button>
|
||||
{{/github-avatar}}
|
||||
</li>
|
||||
{{else}}
|
||||
<span class="text-muted">No Users</span>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6">
|
||||
<label>Organizations</label>
|
||||
|
||||
<form {{action "addOrg"}}>
|
||||
<div class="input-group">
|
||||
{{input type="text" value=addOrg placeholder="Add GitHub organization name" class="form-control"}}
|
||||
<div class="input-group-btn">
|
||||
<button class="btn btn-primary" {{action "addOrg"}}><i class="fa fa-plus"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hr/>
|
||||
|
||||
<ul class="list-unstyled gh-avatar-list">
|
||||
{{#each org in allowOrganizations}}
|
||||
<li>
|
||||
{{#github-avatar type="org" login=org}}
|
||||
<button class="btn btn-danger btn-sm pull-right gh-action" {{action "removeOrg" org}}>Remove</button>
|
||||
{{/github-avatar}}
|
||||
</li>
|
||||
{{else}}
|
||||
<span class="text-muted">No Organizations</span>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<button class="btn btn-primary" {{action "saveAuthorization"}}>
|
||||
Save Authorization Configuration
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="well">
|
||||
<h4>Danger Zone™</h4>
|
||||
<hr/>
|
||||
|
||||
<p>
|
||||
<b class="text-danger">Caution:</b> Disabling access control will give complete control over Rancher to anyone that can reach this page or the API.
|
||||
</p>
|
||||
|
||||
{{#if confirmDisable}}
|
||||
<button class="btn btn-danger" {{action "disable"}}>
|
||||
<i class="fa fa-fire"></i> Are you sure? Click again to really disable Access Control
|
||||
</button>
|
||||
{{else}}
|
||||
<button class="btn btn-danger" {{action "promptDisable"}}>
|
||||
<i class="fa fa-fire"></i> Disable Access Control
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#unless app.authenticationEnabled}}
|
||||
<div class="well">
|
||||
<h4>1. Setup a GitHub Application</h4>
|
||||
<hr/>
|
||||
<p>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://github.com/settings/applications" target="_blank">Click here</a> to go to your GitHub applications settings in a new window.
|
||||
</li>
|
||||
<li>
|
||||
Click "Register new application" and fill out the form:
|
||||
<ul>
|
||||
<li><b>Application name:</b> <span class="text-muted">Anything you like, e.g. My Rancher</span></li>
|
||||
<li>
|
||||
<b>Homepage URL: </b> <span>{{destinationUrl}}</span>
|
||||
</li>
|
||||
<li><b>Application description:</b> <span class="text-muted">Anything you like, optional</span></li>
|
||||
<li><b>Authorization callback URL</b> <span>{{destinationUrl}}</span></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Click "Register application"</li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="well">
|
||||
<h4>2. Configure Rancher to use your Application for Authentication</h4>
|
||||
<hr/>
|
||||
<p>Copy and paste the Client ID and Secret from the upper-right corner of your newly-created application.</p>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="client-id">Client ID</label>
|
||||
{{input id="client-id" type="text" value=clientId classNames="form-control"}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="client-secret">Client Secret</label>
|
||||
{{input id="client-secret" type="text" value=clientSecret classNames="form-control"}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="well">
|
||||
<h4>3. Test and enable Authentication</h4>
|
||||
<hr/>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p>Check that your application is configured correctly by testing authentication with it:</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<button {{bind-attr disabled=createIncomplete}} class="btn btn-primary" {{action "test"}}>
|
||||
<i class="fa fa-github"></i> Authenticate with GitHub
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/unless}}
|
||||
</div>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
export default Ember.View.extend({
|
||||
didInsertElement: function() {
|
||||
$('#authEnabled').bootstrapSwitch();
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Route.extend({
|
||||
});
|
||||
|
|
@ -0,0 +1 @@
|
|||
{{outlet}}
|
||||
|
|
@ -4,9 +4,4 @@ export default Ember.Route.extend({
|
|||
model: function() {
|
||||
return this.get('store').findAll('volume');
|
||||
},
|
||||
|
||||
renderTemplate: function() {
|
||||
this.render('volumes', {into: 'application'});
|
||||
},
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,7 +6,15 @@ var Router = Ember.Router.extend({
|
|||
});
|
||||
|
||||
Router.map(function() {
|
||||
this.route('failWhale', { path: '/failwhale' });
|
||||
this.route('index');
|
||||
this.route('failWhale', { path: '/fail' });
|
||||
|
||||
this.route('login');
|
||||
this.route('logout');
|
||||
this.route('authenticated', { path: '/'}, function() {
|
||||
this.resource('settings', function() {
|
||||
this.route('auth');
|
||||
});
|
||||
|
||||
this.resource('hosts', { path: '/hosts'}, function() {
|
||||
this.resource('host', { path: '/:host_id' }, function() {
|
||||
|
|
@ -34,6 +42,8 @@ Router.map(function() {
|
|||
this.route('delete');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
export default Router;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Route.extend({
|
||||
enter: function() {
|
||||
$('BODY').addClass('farm');
|
||||
},
|
||||
|
||||
exit: function() {
|
||||
$('BODY').removeClass('farm');
|
||||
},
|
||||
|
||||
model: function() {
|
||||
return this.controllerFor('application').get('error');
|
||||
},
|
||||
|
|
@ -8,7 +16,7 @@ export default Ember.Route.extend({
|
|||
afterModel: function(model) {
|
||||
if ( !model )
|
||||
{
|
||||
this.transitionTo('hosts');
|
||||
// this.transitionTo('hosts');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,5 +3,5 @@ import Ember from 'ember';
|
|||
export default Ember.Route.extend({
|
||||
enter: function() {
|
||||
this.transitionTo('hosts');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ export default Ember.Route.extend({
|
|||
|
||||
Ember.run(function() {
|
||||
$('#loading-underlay').show().fadeIn({duration: 100, queue: false, easing: 'linear', complete: function() {
|
||||
$('#loading-overlay').fadeIn({duration: 200, queue: false, easing: 'linear'});
|
||||
$('#loading-overlay').show().fadeIn({duration: 200, queue: false, easing: 'linear'});
|
||||
}});
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -10,3 +10,4 @@
|
|||
@import "app/styles/overlay";
|
||||
@import "app/styles/progress-bar";
|
||||
@import "app/styles/host";
|
||||
@import "app/styles/github-avatar";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
.gh-avatar-list {
|
||||
margin-bottom: 0;
|
||||
|
||||
LI {
|
||||
margin-bottom: 5px;
|
||||
|
||||
&:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gh-avatar {
|
||||
IMG {
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.gh-icon {
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.gh-placeholder {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.gh-avatar-content {
|
||||
margin-left: 50px;
|
||||
}
|
||||
|
||||
.gh-action {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover .gh-action {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,13 @@ BODY {
|
|||
background-color: #eceff4;
|
||||
}
|
||||
|
||||
.farm {
|
||||
background-color: #85a514;
|
||||
background-image: url('images/background.jpg');
|
||||
background-position: top center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
/**********
|
||||
* Navigation (left side bar)
|
||||
**********/
|
||||
|
|
@ -109,12 +116,13 @@ HEADER {
|
|||
z-index: 2;
|
||||
margin-left: $nav_width;
|
||||
background-color: $header_bg;
|
||||
padding: 0 20px;
|
||||
padding: 0;
|
||||
|
||||
H2 {
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
line-height: $header_height;
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -247,23 +255,26 @@ TABLE.graphs {
|
|||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.fail-whale {
|
||||
background-color: #85a514;
|
||||
background-image: url('images/background.jpg');
|
||||
background-position: top center;
|
||||
background-repeat: no-repeat;
|
||||
position: absolute;
|
||||
.farm-box {
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 999;
|
||||
|
||||
SECTION {
|
||||
width: 50%;
|
||||
margin: 50px auto 0 auto;
|
||||
min-width: 50%;
|
||||
margin: 20px auto 0 auto;
|
||||
background-color: white;
|
||||
border-radius: 5px;
|
||||
padding: 30px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
SECTION:first-of-type {
|
||||
margin-top: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.hand {
|
||||
cursor: pointer;
|
||||
cursor: hand;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
<div class="fail-whale">
|
||||
<div class="row">
|
||||
<div class="col-sm-3"></div>
|
||||
<div class="col-sm-6">
|
||||
<div class="farm-box">
|
||||
<section>
|
||||
<h1>Error</h1>
|
||||
<div>{{code}} {{#if status}}({{status}}){{/if}}</div>
|
||||
|
|
@ -7,4 +10,7 @@
|
|||
<a href="#" onclick="window.location.href = window.location.href; return false;">Reload</a> to try again
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-3"></div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
import Ember from "ember";
|
||||
|
||||
export default Ember.Object.extend({
|
||||
unknownProperty: function(key) {
|
||||
return localStorage[key];
|
||||
},
|
||||
|
||||
setUnknownProperty: function(key, value) {
|
||||
if( Ember.isNone(value) )
|
||||
{
|
||||
delete localStorage[key];
|
||||
}
|
||||
else
|
||||
{
|
||||
localStorage[key] = value;
|
||||
}
|
||||
|
||||
this.notifyPropertyChange(key);
|
||||
return value;
|
||||
},
|
||||
|
||||
clear: function() {
|
||||
var i;
|
||||
|
||||
this.beginPropertyChanges();
|
||||
for ( i = 0 ; i < localStorage.length ; i++ )
|
||||
{
|
||||
this.set(localStorage.key(i));
|
||||
}
|
||||
|
||||
localStorage.clear();
|
||||
this.endPropertyChanges();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import Ember from "ember";
|
||||
|
||||
export default Ember.Object.extend({
|
||||
unknownProperty: function(key) {
|
||||
return sessionStorage[key];
|
||||
},
|
||||
|
||||
setUnknownProperty: function(key, value) {
|
||||
if( Ember.isNone(value) )
|
||||
{
|
||||
delete sessionStorage[key];
|
||||
}
|
||||
else
|
||||
{
|
||||
sessionStorage[key] = value;
|
||||
}
|
||||
|
||||
this.notifyPropertyChange(key);
|
||||
return value;
|
||||
},
|
||||
|
||||
clear: function() {
|
||||
var i;
|
||||
|
||||
this.beginPropertyChanges();
|
||||
for ( i = 0 ; i < sessionStorage.length ; i++ )
|
||||
{
|
||||
this.set(sessionStorage.key(i));
|
||||
}
|
||||
|
||||
sessionStorage.clear();
|
||||
this.endPropertyChanges();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import AuthenticatedRouteMixin from 'simple-auth/mixins/authenticated-route-mixin';
|
||||
import AuthenticatedRouteMixin from 'ui/mixins/authenticated-route';
|
||||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Route.extend(AuthenticatedRouteMixin, {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
"c3": "~0.4.3",
|
||||
"jquery.cookie": "~1.4.1",
|
||||
"bootstrap-sass-official": "~3.3.1",
|
||||
"bootstrap-multiselect": "~0.9.10"
|
||||
"bootstrap-multiselect": "~0.9.10",
|
||||
"bootstrap-switch": "~3.3.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,12 +16,17 @@ module.exports = function(environment) {
|
|||
}
|
||||
},
|
||||
|
||||
torii: {
|
||||
// This is configured at runtime, but torii complains
|
||||
// on startup if there's no entry in the environment
|
||||
},
|
||||
|
||||
contentSecurityPolicy: {
|
||||
// Allow the occasional <elem style="blah">...
|
||||
'style-src': "'self' cdn.rancher.io 'unsafe-inline'",
|
||||
'font-src': "'self' cdn.rancher.io",
|
||||
'script-src': "'self' cdn.rancher.io",
|
||||
'img-src': "'self' cdn.rancher.io",
|
||||
'img-src': "'self' cdn.rancher.io avatars.githubusercontent.com",
|
||||
|
||||
// Allow connect to anywhere, for console and event stream socket
|
||||
'connect-src': '*'
|
||||
|
|
@ -43,7 +48,7 @@ module.exports = function(environment) {
|
|||
// ENV.APP.LOG_RESOLVER = true;
|
||||
ENV.APP.LOG_ACTIVE_GENERATION = true;
|
||||
ENV.APP.LOG_TRANSITIONS = true;
|
||||
// ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
|
||||
ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
|
||||
ENV.APP.LOG_VIEW_LOOKUPS = true;
|
||||
ENV.contentSecurityPolicy['script-src'] = ENV.contentSecurityPolicy['script-src'] + " 'unsafe-eval'";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "ui",
|
||||
"version": "0.7.4",
|
||||
"version": "0.8.0",
|
||||
"private": true,
|
||||
"directories": {
|
||||
"doc": "doc",
|
||||
|
|
@ -18,11 +18,12 @@
|
|||
"author": "Rancher Labs",
|
||||
"license": "Apache 2",
|
||||
"devDependencies": {
|
||||
"ember-api-store": "1.0.7",
|
||||
|
||||
"body-parser": "^1.2.0",
|
||||
"broccoli-asset-rev": "^1.0.0",
|
||||
"broccoli-sass": "^0.3.3",
|
||||
"connect-restreamer": "^1.0.1",
|
||||
"ember-api-store": "^1.0.6",
|
||||
"ember-cli": "^0.1.5",
|
||||
"ember-cli-content-security-policy": "0.3.0",
|
||||
"ember-cli-dependency-checker": "0.0.7",
|
||||
|
|
@ -37,6 +38,7 @@
|
|||
"express": "^4.8.5",
|
||||
"forever-agent": "^0.5.2",
|
||||
"glob": "^4.0.5",
|
||||
"http-proxy": "^1.6.2"
|
||||
"http-proxy": "^1.6.2",
|
||||
"torii": "^0.2.2"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ module.exports = function(app, options) {
|
|||
});
|
||||
|
||||
proxy.on('error', function onProxyError(err, req, res) {
|
||||
console.log('Proxy Error:', err);
|
||||
console.log('Proxy Error: on', req.method,'to', req.url,':', err);
|
||||
var error = {
|
||||
type: 'error',
|
||||
status: 500,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
import {
|
||||
moduleFor,
|
||||
test
|
||||
} from 'ember-qunit';
|
||||
|
||||
moduleFor('route:authenticated', 'AuthenticatedRoute', {
|
||||
// Specify the other units that are required for this test.
|
||||
// needs: ['controller:foo']
|
||||
});
|
||||
|
||||
test('it exists', function() {
|
||||
var route = this.subject();
|
||||
ok(route);
|
||||
});
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import {
|
||||
moduleFor,
|
||||
test
|
||||
} from 'ember-qunit';
|
||||
|
||||
moduleFor('route:login', 'LoginRoute', {
|
||||
// Specify the other units that are required for this test.
|
||||
// needs: ['controller:foo']
|
||||
});
|
||||
|
||||
test('it exists', function() {
|
||||
var route = this.subject();
|
||||
ok(route);
|
||||
});
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import {
|
||||
moduleFor,
|
||||
test
|
||||
} from 'ember-qunit';
|
||||
|
||||
moduleFor('route:logout', 'LogoutRoute', {
|
||||
// Specify the other units that are required for this test.
|
||||
// needs: ['controller:foo']
|
||||
});
|
||||
|
||||
test('it exists', function() {
|
||||
var route = this.subject();
|
||||
ok(route);
|
||||
});
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import {
|
||||
moduleFor,
|
||||
test
|
||||
} from 'ember-qunit';
|
||||
|
||||
moduleFor('route:settings/auth', 'SettingsAuthRoute', {
|
||||
// Specify the other units that are required for this test.
|
||||
// needs: ['controller:foo']
|
||||
});
|
||||
|
||||
test('it exists', function() {
|
||||
var route = this.subject();
|
||||
ok(route);
|
||||
});
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import {
|
||||
moduleFor,
|
||||
test
|
||||
} from 'ember-qunit';
|
||||
|
||||
moduleFor('route:settings', 'SettingsRoute', {
|
||||
// Specify the other units that are required for this test.
|
||||
// needs: ['controller:foo']
|
||||
});
|
||||
|
||||
test('it exists', function() {
|
||||
var route = this.subject();
|
||||
ok(route);
|
||||
});
|
||||
Loading…
Reference in New Issue