Wait for K8S to be ready

This commit is contained in:
Vincent Fiduccia 2016-03-01 16:35:30 -07:00
parent 185e46d773
commit 0f34c09181
27 changed files with 360 additions and 491 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = [

View File

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

View File

@ -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>&nbsp;{{k8s.namespace.displayName}}

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@ -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 )
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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