Merge pull request #495 from vincent99/master
K8s, PL fixes, Subscription fixes
|
|
@ -3,6 +3,7 @@ import C from 'ui/utils/constants';
|
|||
|
||||
export default Ember.Controller.extend({
|
||||
settings: Ember.inject.service(),
|
||||
projects: Ember.inject.service(),
|
||||
|
||||
queryParams: ['backToAdd'],
|
||||
backToAdd: false,
|
||||
|
|
@ -30,7 +31,6 @@ export default Ember.Controller.extend({
|
|||
}
|
||||
|
||||
if (this.get('backToAdd')) {
|
||||
|
||||
propsOut[C.SETTING.API_HOST] = model.host;
|
||||
} else {
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ export default Ember.Controller.extend({
|
|||
|
||||
if (this.get('backToAdd')) {
|
||||
|
||||
this.transitionToRoute('hosts.new');
|
||||
this.transitionToRoute('hosts.new', this.get('projects.current.id'));
|
||||
} else {
|
||||
|
||||
this.send('goToPrevious');
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ export default Ember.Route.extend({
|
|||
cookies: Ember.inject.service(),
|
||||
github: Ember.inject.service(),
|
||||
access: Ember.inject.service(),
|
||||
settings: Ember.inject.service(),
|
||||
|
||||
previousParams: null,
|
||||
previousRoute: null,
|
||||
|
|
@ -163,7 +164,13 @@ export default Ember.Route.extend({
|
|||
}
|
||||
},
|
||||
|
||||
updateWindowTitle: function() {
|
||||
document.title = this.get('settings.appName');
|
||||
}.observes('settings.appName'),
|
||||
|
||||
beforeModel() {
|
||||
this.updateWindowTitle();
|
||||
|
||||
var agent = window.navigator.userAgent.toLowerCase();
|
||||
if ( agent.indexOf('msie ') >= 0 || agent.indexOf('trident/') >= 0 || agent.indexOf('edge/') >= 0 )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
<div class="loadfield">
|
||||
<div class="grass"></div>
|
||||
<img class="cow" src="{{app.baseAssets}}assets/images/logos/main_blue.svg" />
|
||||
<img class="cow" src="{{app.baseAssets}}assets/images/logos/main-loading.svg" />
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
|
|
|||
|
|
@ -11,4 +11,13 @@ export default Ember.Controller.extend({
|
|||
hasKubernetes: false,
|
||||
hasSystem: false,
|
||||
hasVm: Ember.computed.alias('settings.hasVm'),
|
||||
|
||||
init() {
|
||||
this._super();
|
||||
this.k8sChanged();
|
||||
},
|
||||
|
||||
k8sChanged: function() {
|
||||
this.set('hasKubernetes', !!this.get('projects.current.kubernetes'));
|
||||
}.observes('projects.current.kubernetes'),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,14 +4,11 @@ export default Ember.Route.extend({
|
|||
projects: Ember.inject.service(),
|
||||
|
||||
redirect() {
|
||||
this.get('projects').selectDefault().then((project) => {
|
||||
var project = this.get('projects.current');
|
||||
if ( project ) {
|
||||
this.replaceWith('authenticated.project', project.get('id'));
|
||||
} else {
|
||||
this.replaceWith('settings.projects');
|
||||
}
|
||||
}).catch(() => {
|
||||
this.replaceWith('settings.projects');
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,41 +1,23 @@
|
|||
import Ember from 'ember';
|
||||
import Socket from 'ui/utils/socket';
|
||||
import Util from 'ui/utils/util';
|
||||
import C from 'ui/utils/constants';
|
||||
import { hasThings } from 'ui/authenticated/project/controller';
|
||||
|
||||
export default Ember.Route.extend({
|
||||
k8s : Ember.inject.service(),
|
||||
prefs : Ember.inject.service(),
|
||||
projects : Ember.inject.service(),
|
||||
access : Ember.inject.service(),
|
||||
userTheme : Ember.inject.service('user-theme'),
|
||||
|
||||
socket : null,
|
||||
pingTimer : null,
|
||||
k8sUidBlacklist: null,
|
||||
|
||||
model(params, transition) {
|
||||
if ( !params.project_id )
|
||||
{
|
||||
// If there isn't a project, pick one
|
||||
return this.get('projects').selectDefault().then((project) => {
|
||||
if ( project )
|
||||
{
|
||||
this.replaceWith('authenticated.project', project.get('id'));
|
||||
}
|
||||
else
|
||||
var project = this.modelFor('authenticated').project;
|
||||
if ( !project )
|
||||
{
|
||||
this.replaceWith('settings.projects');
|
||||
}
|
||||
}).catch(() => {
|
||||
this.replaceWith('settings.projects');
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
return this.loadProject(params.project_id).then((project) => {
|
||||
this.set(`tab-session.${C.TABSESSION.PROJECT}`, project.get('id'));
|
||||
this.get('projects').setCurrent(project);
|
||||
// If the project ID in the URL is out of sync somehow, bail
|
||||
if ( project.get('id') !== params.project_id )
|
||||
{
|
||||
this.replaceWith('authenticated');
|
||||
return;
|
||||
}
|
||||
|
||||
return this.loadSchemas().then(() => {
|
||||
return this.loadStacks().then((stacks) => {
|
||||
|
|
@ -46,7 +28,6 @@ export default Ember.Route.extend({
|
|||
stacks: stacks,
|
||||
});
|
||||
});
|
||||
});
|
||||
}).catch((err) => {
|
||||
return this.loadingError(err, transition, null);
|
||||
});
|
||||
|
|
@ -65,10 +46,6 @@ export default Ember.Route.extend({
|
|||
return ret;
|
||||
},
|
||||
|
||||
loadProject(id) {
|
||||
return this.get('store').find('project', id);
|
||||
},
|
||||
|
||||
loadSchemas() {
|
||||
var store = this.get('store');
|
||||
store.resetType('schema');
|
||||
|
|
@ -79,345 +56,4 @@ export default Ember.Route.extend({
|
|||
return this.get('store').findAllUnremoved('environment');
|
||||
},
|
||||
|
||||
activate() {
|
||||
this._super();
|
||||
|
||||
console.log('Activate socket for', this.get(`tab-session.${C.TABSESSION.PROJECT}`));
|
||||
var store = this.get('store');
|
||||
var boundTypeify = store._typeify.bind(store);
|
||||
|
||||
if ( !this.get('k8sUidBlacklist') )
|
||||
{
|
||||
this.set('k8sUidBlacklist', []);
|
||||
}
|
||||
|
||||
var url = "ws://"+window.location.host + this.get('app.wsEndpoint');
|
||||
|
||||
url = Util.addQueryParam(url, 'projectId', this.get(`tab-session.${C.TABSESSION.PROJECT}`));
|
||||
var socket = Socket.create({
|
||||
url: url
|
||||
});
|
||||
this.set('socket', socket);
|
||||
|
||||
socket.on('message', (event) => {
|
||||
var d = JSON.parse(event.data, boundTypeify);
|
||||
//this._trySend('subscribeMessage',d);
|
||||
|
||||
if ( d.name === 'resource.change' )
|
||||
{
|
||||
this._trySend(d.resourceType+'Changed', d);
|
||||
}
|
||||
else if ( d.name === 'service.kubernetes.change' )
|
||||
{
|
||||
var changeType = (Ember.get(d, 'data.type')||'').toLowerCase();
|
||||
var obj = Ember.get(d, 'data.object');
|
||||
if ( changeType && obj )
|
||||
{
|
||||
this._trySend('k8sResourceChanged', changeType, obj);
|
||||
}
|
||||
}
|
||||
else if ( d.name === 'ping' )
|
||||
{
|
||||
this._trySend('subscribePing', d);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('connected', (tries, after) => {
|
||||
this._trySend('subscribeConnected', tries, after);
|
||||
});
|
||||
|
||||
socket.on('disconnected', () => {
|
||||
this._trySend('subscribeDisconnected', this.get('tries'));
|
||||
});
|
||||
|
||||
socket.connect();
|
||||
},
|
||||
|
||||
deactivate() {
|
||||
this._super();
|
||||
this.closeSocket();
|
||||
},
|
||||
|
||||
resetController() {
|
||||
this._super();
|
||||
this.closeSocket();
|
||||
},
|
||||
|
||||
closeSocket() {
|
||||
var socket = this.get('socket');
|
||||
if ( socket )
|
||||
{
|
||||
socket.disconnect();
|
||||
this.set('socket', null);
|
||||
}
|
||||
|
||||
Ember.run.cancel(this.get('pingTimer'));
|
||||
},
|
||||
|
||||
actions: {
|
||||
// Raw message from the WebSocket
|
||||
//subscribeMessage: function(/*data*/) {
|
||||
//console.log('subscribeMessage',data);
|
||||
//},
|
||||
|
||||
// WebSocket connected
|
||||
subscribeConnected: function(tries,msec) {
|
||||
var msg = 'Subscribe connected';
|
||||
if (tries > 0)
|
||||
{
|
||||
msg += ' (after '+ tries + ' ' + (tries === 1 ? 'try' : 'tries');
|
||||
if (msec)
|
||||
{
|
||||
msg += ', ' + (msec/1000) + ' sec';
|
||||
}
|
||||
|
||||
msg += ')';
|
||||
}
|
||||
console.log(msg);
|
||||
},
|
||||
|
||||
// WebSocket disconnected
|
||||
subscribeDisconnected: function() {
|
||||
console.log('Subscribe disconnected');
|
||||
this.closeSocket();
|
||||
},
|
||||
|
||||
subscribePing: function() {
|
||||
console.log('Subscribe ping');
|
||||
if ( this.get('pingTimer') )
|
||||
{
|
||||
Ember.run.cancel(this.get('pingTimer'));
|
||||
}
|
||||
|
||||
this.set('pingTimer', Ember.run.later(this, function() {
|
||||
console.log('Subscribe missed 2 pings...');
|
||||
if ( this.get('socket') )
|
||||
{
|
||||
this.get('socket').connect();
|
||||
}
|
||||
}, 11000));
|
||||
},
|
||||
|
||||
hostChanged: function(change) {
|
||||
// If the host has a physicalHostId, ensure it is in the machine's hosts array.
|
||||
var host = change.data.resource;
|
||||
var machine = this.get('store').getById('machine', host.get('physicalHostId'));
|
||||
if ( machine )
|
||||
{
|
||||
machine.get('hosts').addObject(host);
|
||||
}
|
||||
},
|
||||
|
||||
containerChanged: function(change) {
|
||||
this._includeChanged('host', 'instances', 'hosts', change.data.resource);
|
||||
},
|
||||
|
||||
virtualMachineChanged: function(change) {
|
||||
this._includeChanged('host', 'instances', 'hosts', change.data.resource);
|
||||
},
|
||||
|
||||
instanceChanged: function(change) {
|
||||
this._includeChanged('host', 'instances', 'hosts', change.data.resource);
|
||||
},
|
||||
|
||||
ipAddressChanged: function(change) {
|
||||
this._includeChanged('host', 'ipAddresses', 'hosts', change.data.resource);
|
||||
// this._includeChanged('container', 'container', 'ipAddresses', 'containers', change.data.resource);
|
||||
},
|
||||
|
||||
loadBalancerTargetChanged: function(change) {
|
||||
this._includeChanged('loadBalancer', 'loadBalancerTargets', 'loadBalancerId', change.data.resource);
|
||||
},
|
||||
|
||||
loadBalancerChanged: function(change) {
|
||||
var balancer = change.data.resource;
|
||||
var config = balancer.get('loadBalancerConfig');
|
||||
var balancers = config.get('loadBalancers');
|
||||
if ( !balancers )
|
||||
{
|
||||
balancers = [];
|
||||
config.set('loadBalancers',balancers);
|
||||
}
|
||||
|
||||
if ( config.get('state') === 'removed' )
|
||||
{
|
||||
balancers.removeObject(balancer);
|
||||
}
|
||||
else
|
||||
{
|
||||
balancers.addObject(balancer);
|
||||
}
|
||||
},
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
registryCredentialChanged: function(change) {
|
||||
this._includeChanged('registry', 'credentials', 'registryId', change.data.resource);
|
||||
},
|
||||
|
||||
loadBalancerServiceChanged: function(change) {
|
||||
this._includeChanged('environment', 'services', 'environmentId', change.data.resource);
|
||||
},
|
||||
|
||||
dnsServiceChanged: function(change) {
|
||||
this._includeChanged('environment', 'services', 'environmentId', change.data.resource);
|
||||
},
|
||||
|
||||
externalServiceChanged: function(change) {
|
||||
this._includeChanged('environment', 'services', 'environmentId', change.data.resource);
|
||||
},
|
||||
|
||||
serviceChanged: function(change) {
|
||||
this._includeChanged('environment', 'services', 'environmentId', change.data.resource);
|
||||
},
|
||||
|
||||
kubernetesServiceChanged: function(change) {
|
||||
this._includeChanged('environment', 'services', 'environmentId', change.data.resource);
|
||||
},
|
||||
|
||||
k8sResourceChanged: function(changeType, obj) {
|
||||
//console.log('k8s change', changeType, (obj && obj.metadata && obj.metadata.uid ? obj.metadata.uid : 'none'));
|
||||
if ( obj && obj.metadata && obj.metadata.uid && this.get('k8sUidBlacklist').indexOf(obj.metadata.uid) >= 0 )
|
||||
{
|
||||
//console.log('^-- Ignoring', changeType, 'for removed resource');
|
||||
return;
|
||||
}
|
||||
|
||||
var resource = this.get('k8s')._typeify(obj);
|
||||
|
||||
if ( changeType === 'deleted' )
|
||||
{
|
||||
this.get('k8sUidBlacklist').pushObject(obj.metadata.uid);
|
||||
this.get('store')._remove(resource.get('type'), resource);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_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;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// Update the `?include=`-ed arrays of a host,
|
||||
// e.g. when an instance changes:
|
||||
// Update the destProperty='instances' array on all models of type resourceName='hosts'.
|
||||
// to match the list in the the 'changed' resource's expectedProperty='hosts'
|
||||
// _includeChanged( 'host', 'hosts', 'instances', 'hosts', instance)
|
||||
_includeChanged: function(resourceName, destProperty, expectedProperty, changed) {
|
||||
if (!changed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var changedId = changed.get('id');
|
||||
var store = this.get('store');
|
||||
|
||||
//console.log('Include changed',resourceName,destProperty,expectedProperty,changedId);
|
||||
|
||||
// All the resources
|
||||
var all = store.all(resourceName);
|
||||
|
||||
// IDs the resource should be on
|
||||
var expectedIds = [];
|
||||
var expected = changed.get(expectedProperty)||[];
|
||||
if ( !Ember.isArray(expected) )
|
||||
{
|
||||
expected = [expected];
|
||||
}
|
||||
|
||||
if ( changed.get('state') !== 'purged' )
|
||||
{
|
||||
expectedIds = expected.map(function(item) {
|
||||
if ( typeof item === 'object' )
|
||||
{
|
||||
return item.get('id');
|
||||
}
|
||||
else
|
||||
{
|
||||
return item;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// IDs it is currently on
|
||||
var curIds = [];
|
||||
all.forEach(function(item) {
|
||||
var existing = (item.get(destProperty)||[]).filterBy('id', changedId);
|
||||
if ( existing.length )
|
||||
{
|
||||
curIds.push(item.get('id'));
|
||||
}
|
||||
});
|
||||
|
||||
// Remove from resources the changed shouldn't be on
|
||||
var remove = Util.arrayDiff(curIds, expectedIds);
|
||||
remove.forEach((id) => {
|
||||
//console.log('Remove',id);
|
||||
store.find(resourceName, id).then((item) => {
|
||||
var list = item.get(destProperty);
|
||||
if ( list )
|
||||
{
|
||||
//console.log('Removing',changedId,'from',item.get('id'));
|
||||
list.removeObjects(list.filterBy('id', changedId));
|
||||
}
|
||||
}).catch(() => {});
|
||||
});
|
||||
|
||||
// Add or update resources the changed should be on
|
||||
expectedIds.forEach((id) => {
|
||||
//console.log('Expect',id);
|
||||
store.find(resourceName, id).then((item) => {
|
||||
var list = item.get(destProperty);
|
||||
if ( !list )
|
||||
{
|
||||
list = [];
|
||||
//console.log('Adding empty to',item.get('id'), destProperty);
|
||||
item.set(destProperty, list);
|
||||
}
|
||||
|
||||
var existing = list.filterBy('id', changedId);
|
||||
if ( existing.length === 0)
|
||||
{
|
||||
//console.log('Adding',changedId,'to',item.get('id'), destProperty);
|
||||
list.pushObject(changed);
|
||||
}
|
||||
}).catch(() => {});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import Ember from 'ember';
|
||||
import C from 'ui/utils/constants';
|
||||
import Service from 'ui/models/service';
|
||||
import Subscribe from 'ui/mixins/subscribe';
|
||||
|
||||
export default Ember.Route.extend({
|
||||
export default Ember.Route.extend(Subscribe, {
|
||||
prefs : Ember.inject.service(),
|
||||
projects : Ember.inject.service(),
|
||||
k8s : Ember.inject.service(),
|
||||
|
|
@ -29,26 +30,35 @@ export default Ember.Route.extend({
|
|||
preferences: this.loadPreferences(),
|
||||
settings: this.loadPublicSettings(),
|
||||
}).then((hash) => {
|
||||
var projectId = null;
|
||||
if ( transition.params && transition.params['authenticated.project'] && transition.params['authenticated.project'].project_id )
|
||||
{
|
||||
return hash;
|
||||
projectId = transition.params['authenticated.project'].project_id;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If not going to a project-specific page, make sure a project is selected
|
||||
return this.get('projects').selectDefault().then(() => {
|
||||
return hash;
|
||||
|
||||
// Make sure a valid project is selected
|
||||
return this.get('projects').selectDefault(projectId).then((project) => {
|
||||
hash.project = project;
|
||||
return this.loadKubernetes(project, hash).then((out) => {
|
||||
return Ember.Object.create(out);
|
||||
});
|
||||
});
|
||||
}
|
||||
}).catch((err) => {
|
||||
return this.loadingError(err, transition, Ember.Object.create({
|
||||
projects: [],
|
||||
project: null,
|
||||
}));
|
||||
});
|
||||
},
|
||||
|
||||
activate() {
|
||||
this._super();
|
||||
this.connectSubscribe();
|
||||
},
|
||||
|
||||
deactivate() {
|
||||
this._super();
|
||||
this.disconnectSubscribe();
|
||||
|
||||
// Forget all the things
|
||||
this.reset();
|
||||
|
|
@ -81,6 +91,36 @@ export default Ember.Route.extend({
|
|||
});
|
||||
},
|
||||
|
||||
loadKubernetes(project, hash) {
|
||||
hash = hash || {};
|
||||
|
||||
if ( !project.get('kubernetes') )
|
||||
{
|
||||
hash.kubernetesReady = false;
|
||||
return Ember.RSVP.resolve(hash);
|
||||
}
|
||||
|
||||
var svc = this.get('k8s');
|
||||
return svc.isReady().then((ready) => {
|
||||
if ( ready )
|
||||
{
|
||||
return this.get('k8s').allNamespaces().then((all) => {
|
||||
return this.get('k8s').selectNamespace().then((ns) => {
|
||||
hash.kubernetesReady = true;
|
||||
hash.namespaces = all;
|
||||
hash.namespace = ns;
|
||||
return hash;
|
||||
});
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
hash.kubernetesReady = false;
|
||||
return Ember.RSVP.resolve(hash);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
loadProjects() {
|
||||
var svc = this.get('projects');
|
||||
return svc.getAll().then((all) => {
|
||||
|
|
@ -126,6 +166,13 @@ export default Ember.Route.extend({
|
|||
this.refresh();
|
||||
},
|
||||
|
||||
refreshKubernetes() {
|
||||
var model = this.get('controller.model');
|
||||
this.loadKubernetes(model.get('project')).then((hash) => {
|
||||
model.setProperties(hash);
|
||||
});
|
||||
},
|
||||
|
||||
switchNamespace(namespaceId) {
|
||||
var route = window.lc('application').get('currentRouteName');
|
||||
var okRoutes = [
|
||||
|
|
|
|||
|
|
@ -136,11 +136,13 @@ export default Ember.Component.extend({
|
|||
var requestLine='';
|
||||
if ( path )
|
||||
{
|
||||
requestLine = method + ' ' + path + ' ' + version;
|
||||
requestLine = method + ' ' + path + ' "' + version;
|
||||
if ( host )
|
||||
{
|
||||
requestLine += '\r\nHost:\\ ' + host;
|
||||
requestLine += '\r\nHost: ' + host;
|
||||
}
|
||||
|
||||
requestLine += '"';
|
||||
}
|
||||
this.set('healthCheck.requestLine', requestLine);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@
|
|||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if isKubernetesTab}}
|
||||
{{#if (and isKubernetesTab k8s.namespace)}}
|
||||
<div class="dropdown btn-group project-btn pull-right">
|
||||
<button type="button" class="btn btn-link dropdown-toggle text-left clip" data-toggle="dropdown" aria-expanded="false">
|
||||
<i class="icon icon-thumbnails icon-fw"></i> {{k8s.namespace.displayName}}
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ export default Ember.Component.extend(NewOrEdit, Sortable, {
|
|||
doneSaving: function() {
|
||||
var out = this._super();
|
||||
this.get('projects').refreshAll();
|
||||
this.get('router').transitionTo('settings.projects.detail', this.get('project.id'), {queryParams: {editing: false}});
|
||||
this.sendAction('done');
|
||||
return out;
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -56,17 +56,10 @@
|
|||
|
||||
<div class="{{if showEdit 'col-sm-12 col-md-8' 'col-xs-12'}}">
|
||||
<div class="well">
|
||||
{{#if showEdit}}
|
||||
{{#if (and accessEnabled showEdit)}}
|
||||
<div class="row form-group">
|
||||
<div class="col-sm-12">
|
||||
{{#if accessEnabled}}
|
||||
{{input-identity action="checkMember" onError="error"}}
|
||||
{{else}}
|
||||
<p class="help-block text-center r-mt0">
|
||||
Access Control is not enabled.
|
||||
Anybody with access to the API/UI acts as an admin and will be able to use any environment.
|
||||
</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
|
@ -118,6 +111,11 @@
|
|||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{else}}
|
||||
<p class="help-block text-center">
|
||||
Access Control is not enabled.<br/>
|
||||
Anybody with access to the API/UI acts as an admin and will be able to use any environment.
|
||||
</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -14,16 +14,16 @@
|
|||
|
||||
{{#unless settings.isPrivateLabel}}
|
||||
<div class="failplane">
|
||||
<img src="{{app.baseAssets}}assets/images/logos/fail_plane.svg" width="484" height="269" />
|
||||
<img class="failprop" src="{{app.baseAssets}}assets/images/logos/fail_prop.svg" width="123" height="246" />
|
||||
<img src="{{app.baseAssets}}assets/images/logos/fail-plane.svg" width="484" height="269" />
|
||||
<img class="failprop" src="{{app.baseAssets}}assets/images/logos/fail-prop.svg" width="123" height="246" />
|
||||
</div>
|
||||
|
||||
<img class="failcow" src="{{app.baseAssets}}assets/images/logos/fail_cowparachute.svg" width="178" height="255"/>
|
||||
<img class="cloud" src="{{app.baseAssets}}assets/images/logos/fail_cloud1.svg" width="254" height="121" />
|
||||
<img class="cloud" src="{{app.baseAssets}}assets/images/logos/fail_cloud2.svg" width="151" height="91" />
|
||||
<img class="cloud" src="{{app.baseAssets}}assets/images/logos/fail_cloud3.svg" width="175" height="102" />
|
||||
<img class="cloud" src="{{app.baseAssets}}assets/images/logos/fail_cloud4.svg" width="300" height="142" />
|
||||
<img class="cloud" src="{{app.baseAssets}}assets/images/logos/fail_cloud5.svg" width="320" height="152" />
|
||||
<img class="failcow" src="{{app.baseAssets}}assets/images/logos/fail-cowparachute.svg" width="178" height="255"/>
|
||||
<img class="cloud" src="{{app.baseAssets}}assets/images/logos/fail-cloud1.svg" width="254" height="121" />
|
||||
<img class="cloud" src="{{app.baseAssets}}assets/images/logos/fail-cloud2.svg" width="151" height="91" />
|
||||
<img class="cloud" src="{{app.baseAssets}}assets/images/logos/fail-cloud3.svg" width="175" height="102" />
|
||||
<img class="cloud" src="{{app.baseAssets}}assets/images/logos/fail-cloud4.svg" width="300" height="142" />
|
||||
<img class="cloud" src="{{app.baseAssets}}assets/images/logos/fail-cloud5.svg" width="320" height="152" />
|
||||
|
||||
<div class="wave"></div>
|
||||
{{/unless}}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<link rel="icon" href="{{content-for 'baseAssets'}}favicon.ico">
|
||||
<title>{{content-for 'appName'}}</title>
|
||||
<title>UI</title>
|
||||
<!-- {{content-for 'version'}} -->
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=694">
|
||||
|
|
|
|||
|
|
@ -3,9 +3,17 @@ import Ember from 'ember';
|
|||
export default Ember.Route.extend({
|
||||
k8s: Ember.inject.service(),
|
||||
|
||||
redirect() {
|
||||
if ( !this.modelFor('authenticated').kubernetesReady )
|
||||
{
|
||||
this.transitionTo('k8s-tab.waiting');
|
||||
}
|
||||
},
|
||||
|
||||
model() {
|
||||
var k8s = this.get('k8s');
|
||||
|
||||
if ( this.modelFor('authenticated').kubernetesReady )
|
||||
{
|
||||
return Ember.RSVP.hash({
|
||||
namespaces: k8s.allNamespaces(),
|
||||
services: k8s.allServices(),
|
||||
|
|
@ -17,5 +25,6 @@ export default Ember.Route.extend({
|
|||
k8s.setProperties(hash);
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
import Ember from 'ember';
|
||||
import { debouncedObserver } from 'ui/utils/debounce';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
k8s: Ember.inject.service(),
|
||||
settings: Ember.inject.service(),
|
||||
|
||||
timer: null,
|
||||
currentStep: 0,
|
||||
|
||||
steps: [
|
||||
'Add at least one host',
|
||||
'Waiting for a host to be active',
|
||||
'Creating Kubernetes system stack',
|
||||
'Starting services',
|
||||
'Waiting for Kubernetes API',
|
||||
'Creating Namespace',
|
||||
],
|
||||
|
||||
updateStep: debouncedObserver('model.hosts.@each.state','model.stacks.@each.{state,externalId}', function() {
|
||||
if ( (this.get('model.hosts.length') + this.get('model.machines.length')) === 0 )
|
||||
{
|
||||
this.set('currentStep', 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( this.get('model.hosts').filterBy('state','active').get('length') === 0 )
|
||||
{
|
||||
this.set('currentStep', 1);
|
||||
return;
|
||||
}
|
||||
|
||||
var stack = this.get('model.stacks').filterBy('externalId','system://kubernetes')[0];
|
||||
if ( !stack )
|
||||
{
|
||||
this.set('currentStep', 2);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( stack.get('state') !== 'active' )
|
||||
{
|
||||
if ( stack.get('state') === 'inactive' )
|
||||
{
|
||||
stack.doAction('activate');
|
||||
}
|
||||
|
||||
this.set('currentStep', 3);
|
||||
return;
|
||||
}
|
||||
|
||||
var services = this.get('model.services').filterBy('environmentId', stack.get('id'));
|
||||
var num = services.get('length');
|
||||
var active = services.filterBy('state','active').get('length');
|
||||
if ( num === 0 || active < num )
|
||||
{
|
||||
this.set('currentStep', 3);
|
||||
return;
|
||||
}
|
||||
|
||||
this.set('currentStep', 4);
|
||||
this.get('k8s').isReady().then((ready) => {
|
||||
if ( ready )
|
||||
{
|
||||
this.get('k8s').getNamespace('default').then(() => {
|
||||
this.set('currentStep', 6);
|
||||
}).catch(() => {
|
||||
this.set('currentStep', 5);
|
||||
reschedule();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
reschedule();
|
||||
}
|
||||
}).catch(() => {
|
||||
reschedule();
|
||||
});
|
||||
|
||||
var self = this;
|
||||
function reschedule() {
|
||||
self.set('timer', Ember.run.later(self, 'updateStep', 5000));
|
||||
}
|
||||
}),
|
||||
|
||||
onInit: function() {
|
||||
this.updateStep();
|
||||
}.on('init'),
|
||||
|
||||
stepChanged: function(){
|
||||
if ( this.get('currentStep') === 6 )
|
||||
{
|
||||
this.send('refreshKubernetes');
|
||||
this.transitionTo('k8s-tab.index');
|
||||
}
|
||||
}.observes('currentStep'),
|
||||
|
||||
deactivate() {
|
||||
Ember.run.cancel(this.get('timer'));
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
export default Ember.Route.extend({
|
||||
redirect() {
|
||||
if ( this.modelFor('authenticated').kubernetesReady )
|
||||
{
|
||||
this.transitionTo('k8s-tab.index');
|
||||
}
|
||||
},
|
||||
|
||||
model() {
|
||||
return Ember.RSVP.hash({
|
||||
hosts: this.get('store').findAllUnremoved('host'),
|
||||
machines: this.get('store').findAllUnremoved('machine'),
|
||||
stacks: this.get('store').findAllUnremoved('environment'),
|
||||
services: this.get('store').findAllUnremoved('service'),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
{{#if (gt currentStep 0)}}
|
||||
<div class="well r-mt40 text" style="width: 50%; margin-left: 25%">
|
||||
<h2 class="loading">Setting Up</h2>
|
||||
|
||||
<ul class="list-unstyled r-mt20">
|
||||
{{#each steps as |step index|}}
|
||||
<li class="{{if (lt currentStep index) 'text-muted'}}">
|
||||
{{#if (eq currentStep index)}}
|
||||
<i class="icon icon-spinner icon-spin" />
|
||||
{{else}}
|
||||
{{#if (gt currentStep index)}}
|
||||
<i class="icon icon-check text-success" />
|
||||
{{else}}
|
||||
<i class="icon icon-circle-o" />
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
<span>{{step}}</span>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
{{else}}
|
||||
<section class="welcome well">
|
||||
<i class="icon icon-host"></i>
|
||||
<h2>Adding your first Host</h2>
|
||||
<p>
|
||||
Before launching Kubernetes, you must first add at least one Linux host that supports Docker 1.9.1+ and be able to reach the {{settings.appName}} server via HTTP.
|
||||
{{settings.appName}} supports adding Linux hosts in the form of a virtual or physical machine from any public cloud providers, privately hosted clouds, or even bare metal servers.
|
||||
{{#unless settings.isPrivateLabel}}<a href="{{docsBase}}/rancher-ui/infrastructure/hosts/" target="_blank">Learn More</a>{{/unless}}
|
||||
</p>
|
||||
{{#link-to "hosts.new" class="btn btn-primary"}}Add Host{{/link-to}}
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
|
|
@ -0,0 +1,344 @@
|
|||
import Ember from 'ember';
|
||||
import Socket from 'ui/utils/socket';
|
||||
import Util from 'ui/utils/util';
|
||||
import C from 'ui/utils/constants';
|
||||
|
||||
export default Ember.Mixin.create({
|
||||
k8s : Ember.inject.service(),
|
||||
projects : Ember.inject.service(),
|
||||
'tab-session' : Ember.inject.service(),
|
||||
|
||||
subscribeSocket : null,
|
||||
pingTimer : null,
|
||||
k8sUidBlacklist : null,
|
||||
|
||||
connectSubscribe() {
|
||||
var projectId = this.get(`tab-session.${C.TABSESSION.PROJECT}`);
|
||||
|
||||
console.log('Connect socket for', projectId);
|
||||
|
||||
var store = this.get('store');
|
||||
var boundTypeify = store._typeify.bind(store);
|
||||
|
||||
if ( !this.get('k8sUidBlacklist') )
|
||||
{
|
||||
this.set('k8sUidBlacklist', []);
|
||||
}
|
||||
|
||||
var url = Util.addQueryParam("ws://"+window.location.host + this.get('app.wsEndpoint'), 'projectId', projectId);
|
||||
var socket = Socket.create({url: url});
|
||||
socket._projectId = projectId;
|
||||
this.set('subscribeSocket', socket);
|
||||
|
||||
socket.on('message', (event) => {
|
||||
var d = JSON.parse(event.data, boundTypeify);
|
||||
//this._trySend('subscribeMessage',d);
|
||||
|
||||
if ( d.name === 'resource.change' )
|
||||
{
|
||||
this._trySend(d.resourceType+'Changed', d);
|
||||
}
|
||||
else if ( d.name === 'service.kubernetes.change' )
|
||||
{
|
||||
var changeType = (Ember.get(d, 'data.type')||'').toLowerCase();
|
||||
var obj = Ember.get(d, 'data.object');
|
||||
if ( changeType && obj )
|
||||
{
|
||||
this._trySend('k8sResourceChanged', changeType, obj);
|
||||
}
|
||||
}
|
||||
else if ( d.name === 'ping' )
|
||||
{
|
||||
this._trySend('subscribePing', d);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('connected', (tries, after) => {
|
||||
this._trySend('subscribeConnected', tries, after);
|
||||
});
|
||||
|
||||
socket.on('disconnected', () => {
|
||||
this._trySend('subscribeDisconnected', this.get('tries'));
|
||||
});
|
||||
|
||||
socket.connect();
|
||||
},
|
||||
|
||||
disconnectSubscribe() {
|
||||
Ember.run.cancel(this.get('pingTimer'));
|
||||
|
||||
var socket = this.get('subscribeSocket');
|
||||
if ( socket )
|
||||
{
|
||||
console.log('Disconnect socket for', socket._projectId);
|
||||
socket.disconnect();
|
||||
this.set('socket', null);
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
// Raw message from the WebSocket
|
||||
//subscribeMessage: function(/*data*/) {
|
||||
//console.log('subscribeMessage',data);
|
||||
//},
|
||||
|
||||
// WebSocket connected
|
||||
subscribeConnected: function(tries,msec) {
|
||||
var msg = 'Subscribe connected';
|
||||
if (tries > 0)
|
||||
{
|
||||
msg += ' (after '+ tries + ' ' + (tries === 1 ? 'try' : 'tries');
|
||||
if (msec)
|
||||
{
|
||||
msg += ', ' + (msec/1000) + ' sec';
|
||||
}
|
||||
|
||||
msg += ')';
|
||||
}
|
||||
console.log(msg);
|
||||
},
|
||||
|
||||
// WebSocket disconnected
|
||||
subscribeDisconnected: function() {
|
||||
console.log('Subscribe disconnected');
|
||||
this.disconnectSubscribe();
|
||||
},
|
||||
|
||||
subscribePing: function() {
|
||||
console.log('Subscribe ping');
|
||||
if ( this.get('pingTimer') )
|
||||
{
|
||||
Ember.run.cancel(this.get('pingTimer'));
|
||||
}
|
||||
|
||||
this.set('pingTimer', Ember.run.later(this, function() {
|
||||
console.log('Subscribe missed 2 pings...');
|
||||
if ( this.get('subscribeSocket') )
|
||||
{
|
||||
this.get('subscribeSocket').connect();
|
||||
}
|
||||
}, 11000));
|
||||
},
|
||||
|
||||
hostChanged: function(change) {
|
||||
// If the host has a physicalHostId, ensure it is in the machine's hosts array.
|
||||
var host = change.data.resource;
|
||||
var machine = this.get('store').getById('machine', host.get('physicalHostId'));
|
||||
if ( machine )
|
||||
{
|
||||
machine.get('hosts').addObject(host);
|
||||
}
|
||||
},
|
||||
|
||||
containerChanged: function(change) {
|
||||
this._includeChanged('host', 'instances', 'hosts', change.data.resource);
|
||||
},
|
||||
|
||||
virtualMachineChanged: function(change) {
|
||||
this._includeChanged('host', 'instances', 'hosts', change.data.resource);
|
||||
},
|
||||
|
||||
instanceChanged: function(change) {
|
||||
this._includeChanged('host', 'instances', 'hosts', change.data.resource);
|
||||
},
|
||||
|
||||
ipAddressChanged: function(change) {
|
||||
this._includeChanged('host', 'ipAddresses', 'hosts', change.data.resource);
|
||||
// this._includeChanged('container', 'container', 'ipAddresses', 'containers', change.data.resource);
|
||||
},
|
||||
|
||||
loadBalancerTargetChanged: function(change) {
|
||||
this._includeChanged('loadBalancer', 'loadBalancerTargets', 'loadBalancerId', change.data.resource);
|
||||
},
|
||||
|
||||
loadBalancerChanged: function(change) {
|
||||
var balancer = change.data.resource;
|
||||
var config = balancer.get('loadBalancerConfig');
|
||||
var balancers = config.get('loadBalancers');
|
||||
if ( !balancers )
|
||||
{
|
||||
balancers = [];
|
||||
config.set('loadBalancers',balancers);
|
||||
}
|
||||
|
||||
if ( config.get('state') === 'removed' )
|
||||
{
|
||||
balancers.removeObject(balancer);
|
||||
}
|
||||
else
|
||||
{
|
||||
balancers.addObject(balancer);
|
||||
}
|
||||
},
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
registryCredentialChanged: function(change) {
|
||||
this._includeChanged('registry', 'credentials', 'registryId', change.data.resource);
|
||||
},
|
||||
|
||||
loadBalancerServiceChanged: function(change) {
|
||||
this._includeChanged('environment', 'services', 'environmentId', change.data.resource);
|
||||
},
|
||||
|
||||
dnsServiceChanged: function(change) {
|
||||
this._includeChanged('environment', 'services', 'environmentId', change.data.resource);
|
||||
},
|
||||
|
||||
externalServiceChanged: function(change) {
|
||||
this._includeChanged('environment', 'services', 'environmentId', change.data.resource);
|
||||
},
|
||||
|
||||
serviceChanged: function(change) {
|
||||
this._includeChanged('environment', 'services', 'environmentId', change.data.resource);
|
||||
},
|
||||
|
||||
kubernetesServiceChanged: function(change) {
|
||||
this._includeChanged('environment', 'services', 'environmentId', change.data.resource);
|
||||
},
|
||||
|
||||
k8sResourceChanged: function(changeType, obj) {
|
||||
//console.log('k8s change', changeType, (obj && obj.metadata && obj.metadata.uid ? obj.metadata.uid : 'none'));
|
||||
if ( obj && obj.metadata && obj.metadata.uid && this.get('k8sUidBlacklist').indexOf(obj.metadata.uid) >= 0 )
|
||||
{
|
||||
//console.log('^-- Ignoring', changeType, 'for removed resource');
|
||||
return;
|
||||
}
|
||||
|
||||
var resource = this.get('k8s')._typeify(obj);
|
||||
|
||||
if ( changeType === 'deleted' )
|
||||
{
|
||||
this.get('k8sUidBlacklist').pushObject(obj.metadata.uid);
|
||||
this.get('store')._remove(resource.get('type'), resource);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_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;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Update the `?include=`-ed arrays of a host,
|
||||
// e.g. when an instance changes:
|
||||
// Update the destProperty='instances' array on all models of type resourceName='hosts'.
|
||||
// to match the list in the the 'changed' resource's expectedProperty='hosts'
|
||||
// _includeChanged( 'host', 'hosts', 'instances', 'hosts', instance)
|
||||
_includeChanged: function(resourceName, destProperty, expectedProperty, changed) {
|
||||
if (!changed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var changedId = changed.get('id');
|
||||
var store = this.get('store');
|
||||
|
||||
//console.log('Include changed',resourceName,destProperty,expectedProperty,changedId);
|
||||
|
||||
// All the resources
|
||||
var all = store.all(resourceName);
|
||||
|
||||
// IDs the resource should be on
|
||||
var expectedIds = [];
|
||||
var expected = changed.get(expectedProperty)||[];
|
||||
if ( !Ember.isArray(expected) )
|
||||
{
|
||||
expected = [expected];
|
||||
}
|
||||
|
||||
if ( changed.get('state') !== 'purged' )
|
||||
{
|
||||
expectedIds = expected.map(function(item) {
|
||||
if ( typeof item === 'object' )
|
||||
{
|
||||
return item.get('id');
|
||||
}
|
||||
else
|
||||
{
|
||||
return item;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// IDs it is currently on
|
||||
var curIds = [];
|
||||
all.forEach(function(item) {
|
||||
var existing = (item.get(destProperty)||[]).filterBy('id', changedId);
|
||||
if ( existing.length )
|
||||
{
|
||||
curIds.push(item.get('id'));
|
||||
}
|
||||
});
|
||||
|
||||
// Remove from resources the changed shouldn't be on
|
||||
var remove = Util.arrayDiff(curIds, expectedIds);
|
||||
remove.forEach((id) => {
|
||||
//console.log('Remove',id);
|
||||
store.find(resourceName, id).then((item) => {
|
||||
var list = item.get(destProperty);
|
||||
if ( list )
|
||||
{
|
||||
//console.log('Removing',changedId,'from',item.get('id'));
|
||||
list.removeObjects(list.filterBy('id', changedId));
|
||||
}
|
||||
}).catch(() => {});
|
||||
});
|
||||
|
||||
// Add or update resources the changed should be on
|
||||
expectedIds.forEach((id) => {
|
||||
//console.log('Expect',id);
|
||||
store.find(resourceName, id).then((item) => {
|
||||
var list = item.get(destProperty);
|
||||
if ( !list )
|
||||
{
|
||||
list = [];
|
||||
//console.log('Adding empty to',item.get('id'), destProperty);
|
||||
item.set(destProperty, list);
|
||||
}
|
||||
|
||||
var existing = list.filterBy('id', changedId);
|
||||
if ( existing.length === 0)
|
||||
{
|
||||
//console.log('Adding',changedId,'to',item.get('id'), destProperty);
|
||||
list.pushObject(changed);
|
||||
}
|
||||
}).catch(() => {});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import Resource from 'ember-api-store/models/resource';
|
||||
import C from 'ui/utils/constants';
|
||||
import { normalizeType } from 'ember-api-store/utils/normalize';
|
||||
import Util from 'ui/utils/util';
|
||||
|
||||
var K8sResource = Resource.extend({
|
||||
actions: {
|
||||
|
|
@ -12,6 +13,25 @@ var K8sResource = Resource.extend({
|
|||
}
|
||||
});
|
||||
},
|
||||
|
||||
goToApi: function() {
|
||||
var endpoint = this.get('endpoint.absolute'); // http://e.f.g.h/ , does not include version. e.f.g.h is where the API actually is.
|
||||
var projectId = this.get(`tab-session.${C.TABSESSION.PROJECT}`);
|
||||
var relative = this.linkFor('self').replace(/^\/r\/kubernetes/,'');
|
||||
var url = `${endpoint}r/projects/${projectId}/kubernetes${relative}`;
|
||||
|
||||
// For local development where API doesn't match origin, add basic auth token
|
||||
if ( url.indexOf(window.location.origin) !== 0 )
|
||||
{
|
||||
var token = this.get('cookies').get(C.COOKIE.TOKEN);
|
||||
if ( token )
|
||||
{
|
||||
url = Util.addAuthorization(url, C.USER.BASIC_BEARER, token);
|
||||
}
|
||||
}
|
||||
|
||||
window.open(url, '_blank');
|
||||
},
|
||||
},
|
||||
|
||||
linkFor: function(name) {
|
||||
|
|
@ -71,6 +91,7 @@ var K8sResource = Resource.extend({
|
|||
availableActions: function() {
|
||||
var choices = [
|
||||
{ label: 'Edit', icon: 'icon icon-edit', action: 'edit', enabled: true },
|
||||
{ label: 'View in API', icon: 'icon icon-external-link', action: 'goToApi', enabled: true },
|
||||
{ divider: true },
|
||||
{ label: 'Delete', icon: 'icon icon-trash', action: 'promptDelete', enabled: true, altAction: 'delete', color: 'text-warning' },
|
||||
];
|
||||
|
|
|
|||
|
|
@ -143,6 +143,7 @@ Router.map(function() {
|
|||
// Kubernetes
|
||||
this.route('k8s-tab', {path: '/kubernetes', resetNamespace: true}, function() {
|
||||
this.route('index', {path: '/'});
|
||||
this.route('waiting', {path: '/waiting'});
|
||||
|
||||
this.route('apply', {path: '/apply'});
|
||||
this.route('kubectl', {path: '/kubectl'});
|
||||
|
|
|
|||
|
|
@ -259,7 +259,7 @@ export default Ember.Service.extend({
|
|||
}
|
||||
|
||||
// Un-namespaced things are cacheable
|
||||
var str = `${C.K8S.BASE}/namespaces/`;
|
||||
var str = `${C.K8S.BASE_VERSION}/namespaces/`;
|
||||
var pos = opt.url.indexOf(str);
|
||||
if ( pos >= 0 )
|
||||
{
|
||||
|
|
@ -317,7 +317,7 @@ export default Ember.Service.extend({
|
|||
{
|
||||
if ( opt.url.substr(0,1) !== '/' )
|
||||
{
|
||||
opt.url = `${self.get('app.kubernetesEndpoint')}/${C.K8S.BASE}/` + opt.url;
|
||||
opt.url = `${self.get('app.kubernetesEndpoint')}/${C.K8S.BASE_VERSION}/` + opt.url;
|
||||
}
|
||||
|
||||
return findWithUrl(opt.url);
|
||||
|
|
@ -424,6 +424,15 @@ export default Ember.Service.extend({
|
|||
}.property('containers.@each.externalId'),
|
||||
|
||||
|
||||
isReady() {
|
||||
return this.request({
|
||||
url: `${this.get('app.kubernetesEndpoint')}/${C.K8S.BASE}`
|
||||
}).then(() => {
|
||||
return true;
|
||||
}).catch(() => {
|
||||
return Ember.RSVP.resolve(false);
|
||||
});
|
||||
},
|
||||
|
||||
_getCollection(type, resourceName) {
|
||||
return this._find(`${C.K8S.TYPE_PREFIX}${type}`, null, {
|
||||
|
|
@ -495,7 +504,7 @@ export default Ember.Service.extend({
|
|||
selectNamespace(desiredName) {
|
||||
var self = this;
|
||||
return this.allNamespaces().then((all) => {
|
||||
// Asked fror a specific one
|
||||
// Asked for a specific one
|
||||
var obj = objForName(desiredName);
|
||||
if ( obj )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -37,20 +37,20 @@ export default Ember.Service.extend({
|
|||
});
|
||||
},
|
||||
|
||||
selectDefault: function() {
|
||||
selectDefault: function(desired) {
|
||||
var self = this;
|
||||
var tabSession = this.get('tab-session');
|
||||
|
||||
// The one specifically asked for
|
||||
return this._activeProjectFromId(desired).then(select)
|
||||
.catch(() => {
|
||||
// Try the project ID in the session
|
||||
return this._activeProjectFromId(tabSession.get(C.TABSESSION.PROJECT)).then(select)
|
||||
.catch(() => {
|
||||
// Then the default project ID from the prefs
|
||||
return this._activeProjectFromId(this.get('prefs').get(C.PREFS.PROJECT_DEFAULT)).then(select)
|
||||
.catch(() => {
|
||||
// Setting this now and then just below breaks API uniqueness checking
|
||||
// this.get('prefs').set(C.PREFS.PROJECT_DEFAULT, "");
|
||||
|
||||
// Then the first active project
|
||||
// Then the first active project you're a member of
|
||||
var project = this.get('active.firstObject');
|
||||
if ( project )
|
||||
{
|
||||
|
|
@ -58,6 +58,7 @@ export default Ember.Service.extend({
|
|||
}
|
||||
else if ( this.get('access.admin') )
|
||||
{
|
||||
// Then if you're an admin the first active of any kind
|
||||
return this.getAll().then((all) => {
|
||||
var firstActive = all.filterBy('state','active')[0];
|
||||
if ( firstActive )
|
||||
|
|
@ -78,6 +79,7 @@ export default Ember.Service.extend({
|
|||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function select(project, overwriteDefault) {
|
||||
if ( project )
|
||||
|
|
@ -109,19 +111,7 @@ export default Ember.Service.extend({
|
|||
|
||||
setCurrent: function(project) {
|
||||
this.set('current', project);
|
||||
|
||||
if ( project && project.get('kubernetes') )
|
||||
{
|
||||
return this.get('k8s').allNamespaces().then(() => {
|
||||
return this.get('k8s').selectNamespace().then(() => {
|
||||
return project;
|
||||
});
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
return project;
|
||||
}
|
||||
return Ember.RSVP.resolve(project);
|
||||
},
|
||||
|
||||
_activeProjectFromId: function(projectId) {
|
||||
|
|
@ -129,9 +119,10 @@ export default Ember.Service.extend({
|
|||
if ( !projectId )
|
||||
{
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
this.get('store').find('project', projectId).then((project) => {
|
||||
this.get('store').find('project', projectId, {url: 'projects/'+encodeURIComponent(projectId)}).then((project) => {
|
||||
if ( project.get('state') === 'active' )
|
||||
{
|
||||
resolve(project);
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ export default Ember.Service.extend(Ember.Evented, {
|
|||
obj.save().then(() => {
|
||||
this.notifyPropertyChange(normalizeName(key));
|
||||
}).catch((err) => {
|
||||
this.trigger('gotError', err);
|
||||
console.log('Error saving setting:', err);
|
||||
}).finally(() => {
|
||||
this.decrementProperty('promiseCount');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,8 +7,14 @@ export default Ember.Controller.extend({
|
|||
queryParams: ['editing'],
|
||||
|
||||
actions: {
|
||||
done() {
|
||||
this.transitionTo('settings.projects').then(() => {
|
||||
this.send('refreshKubernetes');
|
||||
});
|
||||
},
|
||||
|
||||
cancel() {
|
||||
this.transitionTo('settings.projects.detail', this.get('model.project.id'), {queryParams: {editing: false}});
|
||||
this.transitionTo('settings.projects');
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,5 +2,6 @@
|
|||
model=model
|
||||
showEdit=editing
|
||||
editing=true
|
||||
done=(action "done")
|
||||
cancel=(action "cancel")
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@ import Ember from 'ember';
|
|||
|
||||
export default Ember.Controller.extend({
|
||||
actions: {
|
||||
done() {
|
||||
this.send('refreshKubernetes');
|
||||
this.send('goToPrevious');
|
||||
},
|
||||
|
||||
cancel() {
|
||||
this.send('goToPrevious');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,5 +2,6 @@
|
|||
model=model
|
||||
showEdit=true
|
||||
editing=false
|
||||
done=(action "done")
|
||||
cancel=(action "cancel")
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<p>
|
||||
Before adding your first service or launching a container, you must add at least a single Linux host that supports Docker 1.9.1+ and be able to reach the {{settings.appName}} server via HTTP.
|
||||
{{settings.appName}} supports adding Linux hosts in the form of a virtual or physical machine from any public cloud providers, privately hosted clouds, or even bare metal servers.
|
||||
{{#unless settings.isPrivateLabel}}<a href="{[docsBase}}/rancher-ui/infrastructure/hosts/" target="_blank">Learn More</a>{{/unless}}
|
||||
{{#unless settings.isPrivateLabel}}<a href="{{docsBase}}/rancher-ui/infrastructure/hosts/" target="_blank">Learn More</a>{{/unless}}
|
||||
</p>
|
||||
{{#link-to "hosts.new" class="btn btn-primary"}}Add Host{{/link-to}}
|
||||
</section>
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
A service is simply a group of containers created from the same Docker image but extends Docker's "link" concept to leverage {{settings.appName}}'s lightweight distributed DNS service for service discovery.
|
||||
Services can be added individually or by deploying an item from the Catalog.</p>
|
||||
<p>A service is also capable of leveraging other {{settings.appName}} built-in services such as load balancers, health monitoring, upgrade support, and high-availability.
|
||||
{{#unless settings.isPrivateLabel}}<a href="{[docsBase}}/rancher-ui/applications/stacks/adding-services/" target="_blank">Learn More</a>{{/unless}}
|
||||
{{#unless settings.isPrivateLabel}}<a href="{{docsBase}}/rancher-ui/applications/stacks/adding-services/" target="_blank">Learn More</a>{{/unless}}
|
||||
</p>
|
||||
<a class="btn btn-primary" {{action "newService"}}>Add Service</a>
|
||||
{{#link-to "applications-tab.catalog" class="btn btn-primary"}}Add From Catalog{{/link-to}}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,6 @@
|
|||
}
|
||||
|
||||
&.rancher {
|
||||
background-image: url('images/logos/provider-orchestration.svg');
|
||||
background-image: url('images/logos/provider-clustering.svg');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
{{#if hasKubernetes}}
|
||||
{{#link-to "environments" (query-params which="not-kubernetes")}}<i class="icon icon-layers"></i>Stacks{{/link-to}}
|
||||
{{#link-to "environments" project.id (query-params which="not-kubernetes")}}<i class="icon icon-layers"></i>Stacks{{/link-to}}
|
||||
{{else}}
|
||||
{{#link-to "environments" (query-params which="user")}}<i class="icon icon-layers"></i> Stacks{{/link-to}}
|
||||
{{#link-to "environments" project.id (query-params which="user")}}<i class="icon icon-layers"></i> Stacks{{/link-to}}
|
||||
{{#if hasSystem}}
|
||||
{{#link-to "environments" (query-params which="system")}}<i class="icon icon-network"></i>System{{/link-to}}
|
||||
{{#link-to "environments" project.id (query-params which="system")}}<i class="icon icon-network"></i>System{{/link-to}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#link-to "applications-tab.catalog"}}<i class="icon icon-catalog"></i> Catalog{{/link-to}}
|
||||
{{#link-to "applications-tab.catalog" project.id}}<i class="icon icon-catalog"></i> Catalog{{/link-to}}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
{{#link-to "hosts"}}<i class="icon icon-host"></i>Hosts{{/link-to}}
|
||||
{{#link-to "containers"}}<i class="icon icon-box"></i>Containers{{/link-to}}
|
||||
{{#link-to "hosts" project.id}}<i class="icon icon-host"></i>Hosts{{/link-to}}
|
||||
{{#link-to "containers" project.id}}<i class="icon icon-box"></i>Containers{{/link-to}}
|
||||
{{#if hasVm}}
|
||||
{{#link-to "virtualmachines"}}<i class="icon icon-vm"></i>Virtual Machines{{/link-to}}
|
||||
{{#link-to "virtualmachines" project.id}}<i class="icon icon-vm"></i>Virtual Machines{{/link-to}}
|
||||
{{/if}}
|
||||
{{#link-to "storagepools"}}<i class="icon icon-hdd"></i>Storage Pools{{/link-to}}
|
||||
{{#link-to "certificates"}}<i class="icon icon-certificate"></i>Certificates{{/link-to}}
|
||||
{{#link-to "registries" role="menuitem" tabindex="-1"}}<i class="icon icon-database"></i>Registries{{/link-to}}
|
||||
{{#link-to "storagepools" project.id}}<i class="icon icon-hdd"></i>Storage Pools{{/link-to}}
|
||||
{{#link-to "certificates" project.id}}<i class="icon icon-certificate"></i>Certificates{{/link-to}}
|
||||
{{#link-to "registries" project.id}}<i class="icon icon-database"></i>Registries{{/link-to}}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
{{#link-to "k8s-tab.namespace.services" namespace.id}}<i class="icon icon-compass"></i>Services{{/link-to}}
|
||||
{{#link-to "k8s-tab.namespace.rcs" namespace.id}}<i class="icon icon-tachometer"></i>RCs{{/link-to}}
|
||||
{{#link-to "k8s-tab.namespace.pods" namespace.id}}<i class="icon icon-containers"></i>Pods{{/link-to}}
|
||||
{{#link-to "k8s-tab.kubectl"}}<i class="icon icon-terminal"></i>kubectl{{/link-to}}
|
||||
{{#if namespace.id}}
|
||||
{{#link-to "k8s-tab.namespace.services" project.id namespace.id}}<i class="icon icon-compass"></i>Services{{/link-to}}
|
||||
{{#link-to "k8s-tab.namespace.rcs" project.id namespace.id}}<i class="icon icon-tachometer"></i>RCs{{/link-to}}
|
||||
{{#link-to "k8s-tab.namespace.pods" project.id namespace.id}}<i class="icon icon-containers"></i>Pods{{/link-to}}
|
||||
{{#link-to "k8s-tab.kubectl" project.id}}<i class="icon icon-terminal"></i>kubectl{{/link-to}}
|
||||
{{/if}}
|
||||
|
|
|
|||
|
|
@ -200,7 +200,8 @@ var C = {
|
|||
},
|
||||
|
||||
K8S: {
|
||||
BASE: 'api/v1',
|
||||
BASE: 'api',
|
||||
BASE_VERSION: 'api/v1',
|
||||
TYPE_PREFIX: 'k8s-',
|
||||
ID_SEPARATOR: '::'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,9 +63,7 @@ module.exports = function(environment) {
|
|||
apiEndpoint: '/v1',
|
||||
catalogServer: '',
|
||||
catalogEndpoint: '/v1-catalog',
|
||||
kubernetesServer: '',
|
||||
kubernetesEndpoint: '/r/kubernetes',
|
||||
kubectlServer: '',
|
||||
kubectlEndpoint: '/r/kubectld:8091/v1-kubectl',
|
||||
proxyEndpoint: '/v1/proxy',
|
||||
wsEndpoint: '/v1/subscribe' +
|
||||
|
|
@ -138,28 +136,6 @@ module.exports = function(environment) {
|
|||
ENV.APP.catalogServer = '';
|
||||
}
|
||||
|
||||
// Override the K8s server/endpoint with environment var
|
||||
server = process.env.KUBERNETES;
|
||||
if ( server )
|
||||
{
|
||||
ENV.APP.kubernetesServer = normalizeHost(server,8090);
|
||||
}
|
||||
else if (environment === 'production')
|
||||
{
|
||||
ENV.APP.kubernetesServer = '';
|
||||
}
|
||||
|
||||
// Override the Kubectl server/endpoint with environment var
|
||||
server = process.env.KUBECTL;
|
||||
if ( server )
|
||||
{
|
||||
ENV.APP.kubectlServer = normalizeHost(server,8091);
|
||||
}
|
||||
else if (environment === 'production')
|
||||
{
|
||||
ENV.APP.kubectlServer = '';
|
||||
}
|
||||
|
||||
var pl = process.env.PL;
|
||||
if ( pl )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,15 +1,33 @@
|
|||
This folder contians all the images that have the logo in them. You can replace them by running rancher/server (>= v0.44) with:
|
||||
# Private Labeling #
|
||||
|
||||
Setting `/v1/settings/ui.pl` to a value other than `rancher` will (on rancher/server >= v0.61):
|
||||
- Change (most) places that say "Rancher" in the UI to the value you provide.
|
||||
- This does not include things like "rancher-compose.yml" that are expected to have a certain name.
|
||||
- Disable the footer and help links to Rancher Labs-specific docs, forums, and repos.
|
||||
- Change the loading screen to a generic spinner.
|
||||
- Remove the animations/images to make the error screen generic.
|
||||
|
||||
You can set this manually/through the API after installation, or by setting the `CATTLE_UI.PL` environment
|
||||
variable when running the `rancher/server` container.
|
||||
|
||||
# Images #
|
||||
This folder contains all the images that have the Rancher-specific content in them.
|
||||
You can replace them by running the `rancher/server` container (>= v0.44) with:
|
||||
|
||||
```-v /path/to/your/logos:/usr/share/cattle/war/assets/images/logos```
|
||||
|
||||
This replaces the entire folder, so you must supply all the files or they will be missing.
|
||||
|
||||
| File | Usage |
|
||||
|:--------------------|:----------------------------------------------------------|
|
||||
|:------------------------|:----------------------------------------------------------|
|
||||
| dark.svg | On the Login screen when access control is enabled |
|
||||
| fail-*.svg | On the branded error screen (for ui.pl="rancher" only) |
|
||||
| graphic.svg | The parachuting cow part of the image on the About screen |
|
||||
| login-bg.jpg | Background for the login screen box |
|
||||
| main.svg | Top-left corner of the main header |
|
||||
| main-loading.svg | On the branded loading screen (for ui.pl="rancher" only) |
|
||||
| main_k8s.svg | Top-left corner of the main header, for k8s tab |
|
||||
| provider-custom.svg | Custom "Add Host" provider |
|
||||
| provider-local.svg | Local "Access Control" provider |
|
||||
| text.svg | The text part of hte image on the About screen |
|
||||
| provider-clustering.svg | "Cattle" environment clustering provider |
|
||||
| text.svg | The text part of the image on the About screen |
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 772 B After Width: | Height: | Size: 772 B |
|
Before Width: | Height: | Size: 771 B After Width: | Height: | Size: 771 B |
|
Before Width: | Height: | Size: 744 B After Width: | Height: | Size: 744 B |
|
Before Width: | Height: | Size: 772 B After Width: | Height: | Size: 772 B |
|
Before Width: | Height: | Size: 766 B After Width: | Height: | Size: 766 B |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
|
|
@ -75,49 +75,7 @@ module.exports = function(app, options) {
|
|||
console.log('Catalog Proxy', req.method, 'to', req.url);
|
||||
catalogProxy.web(req, res);
|
||||
});
|
||||
|
||||
// Kubernetes API
|
||||
var kubernetesPath = config.kubernetesEndpoint;
|
||||
// Default catalog to the regular API
|
||||
var kubernetesServer = config.kubernetesServer || config.apiServer;
|
||||
console.log('Proxying Kubernetes to', kubernetesServer);
|
||||
app.use(kubernetesPath, function(req, res, next) {
|
||||
req.headers['X-Forwarded-Proto'] = req.protocol;
|
||||
var kubernetesProxy = HttpProxy.createProxyServer({
|
||||
xfwd: false,
|
||||
target: kubernetesServer
|
||||
});
|
||||
|
||||
kubernetesProxy.on('error', onProxyError);
|
||||
|
||||
// Don't include root path in proxied request
|
||||
// req.url = path.join(kubernetesPath, req.url);
|
||||
|
||||
console.log('Kubernetes Proxy', req.method, 'to', req.url);
|
||||
kubernetesProxy.web(req, res);
|
||||
});
|
||||
|
||||
// Kubectl API
|
||||
var kubectlPath = config.kubectlEndpoint;
|
||||
// Default catalog to the regular API
|
||||
var kubectlServer = config.kubectlServer || config.kubernetesServer || config.apiServer;
|
||||
console.log('Proxying Kubectl to', kubectlServer);
|
||||
app.use(kubectlPath, function(req, res, next) {
|
||||
req.headers['X-Forwarded-Proto'] = req.protocol;
|
||||
var kubectlProxy = HttpProxy.createProxyServer({
|
||||
xfwd: false,
|
||||
target: kubectlServer
|
||||
});
|
||||
|
||||
kubectlProxy.on('error', onProxyError);
|
||||
|
||||
// include root path in proxied request
|
||||
req.url = path.join(kubectlPath, req.url);
|
||||
|
||||
console.log('Kubectl Proxy', req.method, 'to', req.url);
|
||||
kubectlProxy.web(req, res);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function onProxyError(err, req, res) {
|
||||
console.log('Proxy Error: on', req.method,'to', req.url,':', err);
|
||||
|
|
|
|||