Merge pull request #495 from vincent99/master

K8s, PL fixes, Subscription fixes
This commit is contained in:
Vincent Fiduccia 2016-03-02 01:28:17 -07:00
commit 6028533916
46 changed files with 773 additions and 580 deletions

View File

@ -3,6 +3,7 @@ import C from 'ui/utils/constants';
export default Ember.Controller.extend({ export default Ember.Controller.extend({
settings: Ember.inject.service(), settings: Ember.inject.service(),
projects: Ember.inject.service(),
queryParams: ['backToAdd'], queryParams: ['backToAdd'],
backToAdd: false, backToAdd: false,
@ -30,7 +31,6 @@ export default Ember.Controller.extend({
} }
if (this.get('backToAdd')) { if (this.get('backToAdd')) {
propsOut[C.SETTING.API_HOST] = model.host; propsOut[C.SETTING.API_HOST] = model.host;
} else { } else {
@ -59,7 +59,7 @@ export default Ember.Controller.extend({
if (this.get('backToAdd')) { if (this.get('backToAdd')) {
this.transitionToRoute('hosts.new'); this.transitionToRoute('hosts.new', this.get('projects.current.id'));
} else { } else {
this.send('goToPrevious'); this.send('goToPrevious');

View File

@ -5,6 +5,7 @@ export default Ember.Route.extend({
cookies: Ember.inject.service(), cookies: Ember.inject.service(),
github: Ember.inject.service(), github: Ember.inject.service(),
access: Ember.inject.service(), access: Ember.inject.service(),
settings: Ember.inject.service(),
previousParams: null, previousParams: null,
previousRoute: null, previousRoute: null,
@ -163,7 +164,13 @@ export default Ember.Route.extend({
} }
}, },
updateWindowTitle: function() {
document.title = this.get('settings.appName');
}.observes('settings.appName'),
beforeModel() { beforeModel() {
this.updateWindowTitle();
var agent = window.navigator.userAgent.toLowerCase(); var agent = window.navigator.userAgent.toLowerCase();
if ( agent.indexOf('msie ') >= 0 || agent.indexOf('trident/') >= 0 || agent.indexOf('edge/') >= 0 ) if ( agent.indexOf('msie ') >= 0 || agent.indexOf('trident/') >= 0 || agent.indexOf('edge/') >= 0 )
{ {

View File

@ -18,7 +18,7 @@
<div class="loadfield"> <div class="loadfield">
<div class="grass"></div> <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>
</div> </div>
{{/if}} {{/if}}

View File

@ -11,4 +11,13 @@ export default Ember.Controller.extend({
hasKubernetes: false, hasKubernetes: false,
hasSystem: false, hasSystem: false,
hasVm: Ember.computed.alias('settings.hasVm'), 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'),
}); });

View File

@ -4,14 +4,11 @@ export default Ember.Route.extend({
projects: Ember.inject.service(), projects: Ember.inject.service(),
redirect() { redirect() {
this.get('projects').selectDefault().then((project) => { var project = this.get('projects.current');
if ( project ) { if ( project ) {
this.replaceWith('authenticated.project', project.get('id')); this.replaceWith('authenticated.project', project.get('id'));
} else { } else {
this.replaceWith('settings.projects');
}
}).catch(() => {
this.replaceWith('settings.projects'); this.replaceWith('settings.projects');
}); }
}, },
}); });

View File

@ -1,50 +1,31 @@
import Ember from 'ember'; 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'; import { hasThings } from 'ui/authenticated/project/controller';
export default Ember.Route.extend({ export default Ember.Route.extend({
k8s : Ember.inject.service(),
prefs : Ember.inject.service(),
projects : Ember.inject.service(),
access : Ember.inject.service(), access : Ember.inject.service(),
userTheme : Ember.inject.service('user-theme'),
socket : null,
pingTimer : null,
k8sUidBlacklist: null,
model(params, transition) { model(params, transition) {
if ( !params.project_id ) var project = this.modelFor('authenticated').project;
if ( !project )
{ {
// If there isn't a project, pick one this.replaceWith('settings.projects');
return this.get('projects').selectDefault().then((project) => { return;
if ( project )
{
this.replaceWith('authenticated.project', project.get('id'));
}
else
{
this.replaceWith('settings.projects');
}
}).catch(() => {
this.replaceWith('settings.projects');
});
} }
return this.loadProject(params.project_id).then((project) => { // If the project ID in the URL is out of sync somehow, bail
this.set(`tab-session.${C.TABSESSION.PROJECT}`, project.get('id')); if ( project.get('id') !== params.project_id )
this.get('projects').setCurrent(project); {
this.replaceWith('authenticated');
return;
}
return this.loadSchemas().then(() => { return this.loadSchemas().then(() => {
return this.loadStacks().then((stacks) => { return this.loadStacks().then((stacks) => {
hasThings(stacks, project, window.lc('authenticated')); hasThings(stacks, project, window.lc('authenticated'));
return Ember.Object.create({ return Ember.Object.create({
project: project, project: project,
stacks: stacks, stacks: stacks,
});
}); });
}); });
}).catch((err) => { }).catch((err) => {
@ -65,10 +46,6 @@ export default Ember.Route.extend({
return ret; return ret;
}, },
loadProject(id) {
return this.get('store').find('project', id);
},
loadSchemas() { loadSchemas() {
var store = this.get('store'); var store = this.get('store');
store.resetType('schema'); store.resetType('schema');
@ -79,345 +56,4 @@ export default Ember.Route.extend({
return this.get('store').findAllUnremoved('environment'); 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(() => {});
});
},
}); });

View File

@ -1,8 +1,9 @@
import Ember from 'ember'; import Ember from 'ember';
import C from 'ui/utils/constants'; import C from 'ui/utils/constants';
import Service from 'ui/models/service'; 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(), prefs : Ember.inject.service(),
projects : Ember.inject.service(), projects : Ember.inject.service(),
k8s : Ember.inject.service(), k8s : Ember.inject.service(),
@ -29,26 +30,35 @@ export default Ember.Route.extend({
preferences: this.loadPreferences(), preferences: this.loadPreferences(),
settings: this.loadPublicSettings(), settings: this.loadPublicSettings(),
}).then((hash) => { }).then((hash) => {
var projectId = null;
if ( transition.params && transition.params['authenticated.project'] && transition.params['authenticated.project'].project_id ) if ( transition.params && transition.params['authenticated.project'] && transition.params['authenticated.project'].project_id )
{ {
return hash; projectId = transition.params['authenticated.project'].project_id;
} }
else
{ // Make sure a valid project is selected
// If not going to a project-specific page, make sure a project is selected return this.get('projects').selectDefault(projectId).then((project) => {
return this.get('projects').selectDefault().then(() => { hash.project = project;
return hash; return this.loadKubernetes(project, hash).then((out) => {
return Ember.Object.create(out);
}); });
} });
}).catch((err) => { }).catch((err) => {
return this.loadingError(err, transition, Ember.Object.create({ return this.loadingError(err, transition, Ember.Object.create({
projects: [], projects: [],
project: null,
})); }));
}); });
}, },
activate() {
this._super();
this.connectSubscribe();
},
deactivate() { deactivate() {
this._super(); this._super();
this.disconnectSubscribe();
// Forget all the things // Forget all the things
this.reset(); 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() { loadProjects() {
var svc = this.get('projects'); var svc = this.get('projects');
return svc.getAll().then((all) => { return svc.getAll().then((all) => {
@ -126,6 +166,13 @@ export default Ember.Route.extend({
this.refresh(); this.refresh();
}, },
refreshKubernetes() {
var model = this.get('controller.model');
this.loadKubernetes(model.get('project')).then((hash) => {
model.setProperties(hash);
});
},
switchNamespace(namespaceId) { switchNamespace(namespaceId) {
var route = window.lc('application').get('currentRouteName'); var route = window.lc('application').get('currentRouteName');
var okRoutes = [ var okRoutes = [

View File

@ -136,11 +136,13 @@ export default Ember.Component.extend({
var requestLine=''; var requestLine='';
if ( path ) if ( path )
{ {
requestLine = method + ' ' + path + ' ' + version; requestLine = method + ' ' + path + ' "' + version;
if ( host ) if ( host )
{ {
requestLine += '\r\nHost:\\ ' + host; requestLine += '\r\nHost: ' + host;
} }
requestLine += '"';
} }
this.set('healthCheck.requestLine', requestLine); this.set('healthCheck.requestLine', requestLine);
} }

View File

@ -119,7 +119,7 @@
</div> </div>
{{/if}} {{/if}}
{{#if isKubernetesTab}} {{#if (and isKubernetesTab k8s.namespace)}}
<div class="dropdown btn-group project-btn pull-right"> <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"> <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>&nbsp;{{k8s.namespace.displayName}} <i class="icon icon-thumbnails icon-fw"></i>&nbsp;{{k8s.namespace.displayName}}

View File

@ -168,7 +168,7 @@ export default Ember.Component.extend(NewOrEdit, Sortable, {
doneSaving: function() { doneSaving: function() {
var out = this._super(); var out = this._super();
this.get('projects').refreshAll(); this.get('projects').refreshAll();
this.get('router').transitionTo('settings.projects.detail', this.get('project.id'), {queryParams: {editing: false}}); this.sendAction('done');
return out; return out;
}, },
}); });

View File

@ -56,17 +56,10 @@
<div class="{{if showEdit 'col-sm-12 col-md-8' 'col-xs-12'}}"> <div class="{{if showEdit 'col-sm-12 col-md-8' 'col-xs-12'}}">
<div class="well"> <div class="well">
{{#if showEdit}} {{#if (and accessEnabled showEdit)}}
<div class="row form-group"> <div class="row form-group">
<div class="col-sm-12"> <div class="col-sm-12">
{{#if accessEnabled}} {{input-identity action="checkMember" onError="error"}}
{{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>
</div> </div>
{{/if}} {{/if}}
@ -118,6 +111,11 @@
{{/each}} {{/each}}
</tbody> </tbody>
</table> </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}} {{/if}}
</div> </div>
</div> </div>

View File

@ -14,16 +14,16 @@
{{#unless settings.isPrivateLabel}} {{#unless settings.isPrivateLabel}}
<div class="failplane"> <div class="failplane">
<img src="{{app.baseAssets}}assets/images/logos/fail_plane.svg" width="484" height="269" /> <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 class="failprop" src="{{app.baseAssets}}assets/images/logos/fail-prop.svg" width="123" height="246" />
</div> </div>
<img class="failcow" src="{{app.baseAssets}}assets/images/logos/fail_cowparachute.svg" width="178" height="255"/> <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-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-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-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-cloud4.svg" width="300" height="142" />
<img class="cloud" src="{{app.baseAssets}}assets/images/logos/fail_cloud5.svg" width="320" height="152" /> <img class="cloud" src="{{app.baseAssets}}assets/images/logos/fail-cloud5.svg" width="320" height="152" />
<div class="wave"></div> <div class="wave"></div>
{{/unless}} {{/unless}}

View File

@ -4,7 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="icon" href="{{content-for 'baseAssets'}}favicon.ico"> <link rel="icon" href="{{content-for 'baseAssets'}}favicon.ico">
<title>{{content-for 'appName'}}</title> <title>UI</title>
<!-- {{content-for 'version'}} --> <!-- {{content-for 'version'}} -->
<meta name="description" content=""> <meta name="description" content="">
<meta name="viewport" content="width=694"> <meta name="viewport" content="width=694">

View File

@ -3,19 +3,28 @@ import Ember from 'ember';
export default Ember.Route.extend({ export default Ember.Route.extend({
k8s: Ember.inject.service(), k8s: Ember.inject.service(),
redirect() {
if ( !this.modelFor('authenticated').kubernetesReady )
{
this.transitionTo('k8s-tab.waiting');
}
},
model() { model() {
var k8s = this.get('k8s'); var k8s = this.get('k8s');
if ( this.modelFor('authenticated').kubernetesReady )
return Ember.RSVP.hash({ {
namespaces: k8s.allNamespaces(), return Ember.RSVP.hash({
services: k8s.allServices(), namespaces: k8s.allNamespaces(),
rcs: k8s.allRCs(), services: k8s.allServices(),
pods: k8s.allPods(), rcs: k8s.allRCs(),
containers: this.get('store').findAll('container'), pods: k8s.allPods(),
}).then((hash) => { containers: this.get('store').findAll('container'),
return k8s.selectNamespace().then(() => { }).then((hash) => {
k8s.setProperties(hash); return k8s.selectNamespace().then(() => {
k8s.setProperties(hash);
});
}); });
}); }
}, },
}); });

View File

@ -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'));
}
});

View File

@ -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'),
});
},
});

View File

@ -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}}

344
app/mixins/subscribe.js Normal file
View File

@ -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(() => {});
});
},
});

View File

@ -1,6 +1,7 @@
import Resource from 'ember-api-store/models/resource'; import Resource from 'ember-api-store/models/resource';
import C from 'ui/utils/constants'; import C from 'ui/utils/constants';
import { normalizeType } from 'ember-api-store/utils/normalize'; import { normalizeType } from 'ember-api-store/utils/normalize';
import Util from 'ui/utils/util';
var K8sResource = Resource.extend({ var K8sResource = Resource.extend({
actions: { 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) { linkFor: function(name) {
@ -70,9 +90,10 @@ var K8sResource = Resource.extend({
availableActions: function() { availableActions: function() {
var choices = [ var choices = [
{ label: 'Edit', icon: 'icon icon-edit', action: 'edit', enabled: true }, { 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 }, { divider: true },
{ label: 'Delete', icon: 'icon icon-trash', action: 'promptDelete', enabled: true, altAction: 'delete', color: 'text-warning' }, { label: 'Delete', icon: 'icon icon-trash', action: 'promptDelete', enabled: true, altAction: 'delete', color: 'text-warning' },
]; ];
return choices; return choices;

View File

@ -143,6 +143,7 @@ Router.map(function() {
// Kubernetes // Kubernetes
this.route('k8s-tab', {path: '/kubernetes', resetNamespace: true}, function() { this.route('k8s-tab', {path: '/kubernetes', resetNamespace: true}, function() {
this.route('index', {path: '/'}); this.route('index', {path: '/'});
this.route('waiting', {path: '/waiting'});
this.route('apply', {path: '/apply'}); this.route('apply', {path: '/apply'});
this.route('kubectl', {path: '/kubectl'}); this.route('kubectl', {path: '/kubectl'});

View File

@ -259,7 +259,7 @@ export default Ember.Service.extend({
} }
// Un-namespaced things are cacheable // Un-namespaced things are cacheable
var str = `${C.K8S.BASE}/namespaces/`; var str = `${C.K8S.BASE_VERSION}/namespaces/`;
var pos = opt.url.indexOf(str); var pos = opt.url.indexOf(str);
if ( pos >= 0 ) if ( pos >= 0 )
{ {
@ -317,7 +317,7 @@ export default Ember.Service.extend({
{ {
if ( opt.url.substr(0,1) !== '/' ) 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); return findWithUrl(opt.url);
@ -424,6 +424,15 @@ export default Ember.Service.extend({
}.property('containers.@each.externalId'), }.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) { _getCollection(type, resourceName) {
return this._find(`${C.K8S.TYPE_PREFIX}${type}`, null, { return this._find(`${C.K8S.TYPE_PREFIX}${type}`, null, {
@ -495,7 +504,7 @@ export default Ember.Service.extend({
selectNamespace(desiredName) { selectNamespace(desiredName) {
var self = this; var self = this;
return this.allNamespaces().then((all) => { return this.allNamespaces().then((all) => {
// Asked fror a specific one // Asked for a specific one
var obj = objForName(desiredName); var obj = objForName(desiredName);
if ( obj ) if ( obj )
{ {

View File

@ -37,45 +37,47 @@ export default Ember.Service.extend({
}); });
}, },
selectDefault: function() { selectDefault: function(desired) {
var self = this; var self = this;
var tabSession = this.get('tab-session'); var tabSession = this.get('tab-session');
// Try the project ID in the session // The one specifically asked for
return this._activeProjectFromId(tabSession.get(C.TABSESSION.PROJECT)).then(select) return this._activeProjectFromId(desired).then(select)
.catch(() => { .catch(() => {
// Then the default project ID from the prefs // Try the project ID in the session
return this._activeProjectFromId(this.get('prefs').get(C.PREFS.PROJECT_DEFAULT)).then(select) return this._activeProjectFromId(tabSession.get(C.TABSESSION.PROJECT)).then(select)
.catch(() => { .catch(() => {
// Setting this now and then just below breaks API uniqueness checking // Then the default project ID from the prefs
// this.get('prefs').set(C.PREFS.PROJECT_DEFAULT, ""); return this._activeProjectFromId(this.get('prefs').get(C.PREFS.PROJECT_DEFAULT)).then(select)
.catch(() => {
// Then the first active project // Then the first active project you're a member of
var project = this.get('active.firstObject'); var project = this.get('active.firstObject');
if ( project ) if ( project )
{ {
return select(project, true); return select(project, true);
} }
else if ( this.get('access.admin') ) else if ( this.get('access.admin') )
{ {
return this.getAll().then((all) => { // Then if you're an admin the first active of any kind
var firstActive = all.filterBy('state','active')[0]; return this.getAll().then((all) => {
if ( firstActive ) var firstActive = all.filterBy('state','active')[0];
{ if ( firstActive )
return select(firstActive, true); {
} return select(firstActive, true);
else }
{ else
{
return fail();
}
}).catch(() => {
return fail(); return fail();
} });
}).catch(() => { }
else
{
return fail(); return fail();
}); }
} });
else
{
return fail();
}
}); });
}); });
@ -109,19 +111,7 @@ export default Ember.Service.extend({
setCurrent: function(project) { setCurrent: function(project) {
this.set('current', project); this.set('current', project);
return Ember.RSVP.resolve(project);
if ( project && project.get('kubernetes') )
{
return this.get('k8s').allNamespaces().then(() => {
return this.get('k8s').selectNamespace().then(() => {
return project;
});
});
}
else
{
return project;
}
}, },
_activeProjectFromId: function(projectId) { _activeProjectFromId: function(projectId) {
@ -129,9 +119,10 @@ export default Ember.Service.extend({
if ( !projectId ) if ( !projectId )
{ {
reject(); 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' ) if ( project.get('state') === 'active' )
{ {
resolve(project); resolve(project);

View File

@ -66,7 +66,7 @@ export default Ember.Service.extend(Ember.Evented, {
obj.save().then(() => { obj.save().then(() => {
this.notifyPropertyChange(normalizeName(key)); this.notifyPropertyChange(normalizeName(key));
}).catch((err) => { }).catch((err) => {
this.trigger('gotError', err); console.log('Error saving setting:', err);
}).finally(() => { }).finally(() => {
this.decrementProperty('promiseCount'); this.decrementProperty('promiseCount');
}); });

View File

@ -7,8 +7,14 @@ export default Ember.Controller.extend({
queryParams: ['editing'], queryParams: ['editing'],
actions: { actions: {
done() {
this.transitionTo('settings.projects').then(() => {
this.send('refreshKubernetes');
});
},
cancel() { cancel() {
this.transitionTo('settings.projects.detail', this.get('model.project.id'), {queryParams: {editing: false}}); this.transitionTo('settings.projects');
}, },
}, },
}); });

View File

@ -2,5 +2,6 @@
model=model model=model
showEdit=editing showEdit=editing
editing=true editing=true
done=(action "done")
cancel=(action "cancel") cancel=(action "cancel")
}} }}

View File

@ -2,6 +2,11 @@ import Ember from 'ember';
export default Ember.Controller.extend({ export default Ember.Controller.extend({
actions: { actions: {
done() {
this.send('refreshKubernetes');
this.send('goToPrevious');
},
cancel() { cancel() {
this.send('goToPrevious'); this.send('goToPrevious');
} }

View File

@ -2,5 +2,6 @@
model=model model=model
showEdit=true showEdit=true
editing=false editing=false
done=(action "done")
cancel=(action "cancel") cancel=(action "cancel")
}} }}

View File

@ -5,7 +5,7 @@
<p> <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. 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. {{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> </p>
{{#link-to "hosts.new" class="btn btn-primary"}}Add Host{{/link-to}} {{#link-to "hosts.new" class="btn btn-primary"}}Add Host{{/link-to}}
</section> </section>
@ -19,7 +19,7 @@
A service is simply a group of containers created from the same Docker image but extends Docker's &quot;link&quot; concept to leverage {{settings.appName}}'s lightweight distributed DNS service for service discovery. A service is simply a group of containers created from the same Docker image but extends Docker's &quot;link&quot; 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> 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. <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> </p>
<a class="btn btn-primary" {{action "newService"}}>Add Service</a> <a class="btn btn-primary" {{action "newService"}}>Add Service</a>
{{#link-to "applications-tab.catalog" class="btn btn-primary"}}Add From Catalog{{/link-to}} {{#link-to "applications-tab.catalog" class="btn btn-primary"}}Add From Catalog{{/link-to}}

View File

@ -8,6 +8,6 @@
} }
&.rancher { &.rancher {
background-image: url('images/logos/provider-orchestration.svg'); background-image: url('images/logos/provider-clustering.svg');
} }
} }

View File

@ -1,9 +1,9 @@
{{#if hasKubernetes}} {{#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}} {{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}} {{#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}}
{{/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}}

View File

@ -1,8 +1,8 @@
{{#link-to "hosts"}}<i class="icon icon-host"></i>Hosts{{/link-to}} {{#link-to "hosts" project.id}}<i class="icon icon-host"></i>Hosts{{/link-to}}
{{#link-to "containers"}}<i class="icon icon-box"></i>Containers{{/link-to}} {{#link-to "containers" project.id}}<i class="icon icon-box"></i>Containers{{/link-to}}
{{#if hasVm}} {{#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}} {{/if}}
{{#link-to "storagepools"}}<i class="icon icon-hdd"></i>Storage Pools{{/link-to}} {{#link-to "storagepools" project.id}}<i class="icon icon-hdd"></i>Storage Pools{{/link-to}}
{{#link-to "certificates"}}<i class="icon icon-certificate"></i>Certificates{{/link-to}} {{#link-to "certificates" project.id}}<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 "registries" project.id}}<i class="icon icon-database"></i>Registries{{/link-to}}

View File

@ -1,4 +1,6 @@
{{#link-to "k8s-tab.namespace.services" namespace.id}}<i class="icon icon-compass"></i>Services{{/link-to}} {{#if namespace.id}}
{{#link-to "k8s-tab.namespace.rcs" namespace.id}}<i class="icon icon-tachometer"></i>RCs{{/link-to}} {{#link-to "k8s-tab.namespace.services" project.id namespace.id}}<i class="icon icon-compass"></i>Services{{/link-to}}
{{#link-to "k8s-tab.namespace.pods" namespace.id}}<i class="icon icon-containers"></i>Pods{{/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.kubectl"}}<i class="icon icon-terminal"></i>kubectl{{/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}}

View File

@ -200,7 +200,8 @@ var C = {
}, },
K8S: { K8S: {
BASE: 'api/v1', BASE: 'api',
BASE_VERSION: 'api/v1',
TYPE_PREFIX: 'k8s-', TYPE_PREFIX: 'k8s-',
ID_SEPARATOR: '::' ID_SEPARATOR: '::'
} }

View File

@ -63,9 +63,7 @@ module.exports = function(environment) {
apiEndpoint: '/v1', apiEndpoint: '/v1',
catalogServer: '', catalogServer: '',
catalogEndpoint: '/v1-catalog', catalogEndpoint: '/v1-catalog',
kubernetesServer: '',
kubernetesEndpoint: '/r/kubernetes', kubernetesEndpoint: '/r/kubernetes',
kubectlServer: '',
kubectlEndpoint: '/r/kubectld:8091/v1-kubectl', kubectlEndpoint: '/r/kubectld:8091/v1-kubectl',
proxyEndpoint: '/v1/proxy', proxyEndpoint: '/v1/proxy',
wsEndpoint: '/v1/subscribe' + wsEndpoint: '/v1/subscribe' +
@ -138,28 +136,6 @@ module.exports = function(environment) {
ENV.APP.catalogServer = ''; 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; var pl = process.env.PL;
if ( pl ) if ( pl )
{ {

View File

@ -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``` ```-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. This replaces the entire folder, so you must supply all the files or they will be missing.
| File | Usage | | File | Usage |
|:--------------------|:----------------------------------------------------------| |:------------------------|:----------------------------------------------------------|
| dark.svg | On the Login screen when access control is enabled | | dark.svg | On the Login screen when access control is enabled |
| graphic.svg | The parachuting cow part of the image on the About screen | | fail-*.svg | On the branded error screen (for ui.pl="rancher" only) |
| login-bg.jpg | Background for the login screen box | | graphic.svg | The parachuting cow part of the image on the About screen |
| main.svg | Top-left corner of the main header | | login-bg.jpg | Background for the login screen box |
| provider-custom.svg | Custom "Add Host" provider | | main.svg | Top-left corner of the main header |
| provider-local.svg | Local "Access Control" provider | | main-loading.svg | On the branded loading screen (for ui.pl="rancher" only) |
| text.svg | The text part of hte image on the About screen | | 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 |
| provider-clustering.svg | "Cattle" environment clustering provider |
| text.svg | The text part of the image on the About screen |

View File

Before

Width:  |  Height:  |  Size: 772 B

After

Width:  |  Height:  |  Size: 772 B

View File

Before

Width:  |  Height:  |  Size: 771 B

After

Width:  |  Height:  |  Size: 771 B

View File

Before

Width:  |  Height:  |  Size: 744 B

After

Width:  |  Height:  |  Size: 744 B

View File

Before

Width:  |  Height:  |  Size: 772 B

After

Width:  |  Height:  |  Size: 772 B

View File

Before

Width:  |  Height:  |  Size: 766 B

After

Width:  |  Height:  |  Size: 766 B

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@ -75,49 +75,7 @@ module.exports = function(app, options) {
console.log('Catalog Proxy', req.method, 'to', req.url); console.log('Catalog Proxy', req.method, 'to', req.url);
catalogProxy.web(req, res); 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) { function onProxyError(err, req, res) {
console.log('Proxy Error: on', req.method,'to', req.url,':', err); console.log('Proxy Error: on', req.method,'to', req.url,':', err);