mirror of https://github.com/rancher/ui.git
Nodepool objects, cluster edit
This commit is contained in:
parent
583bafd6cf
commit
2cfe986d22
|
|
@ -0,0 +1,29 @@
|
|||
import { get, set } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import Route from '@ember/routing/route';
|
||||
import { hash/* , all */ } from 'rsvp';
|
||||
|
||||
export default Route.extend({
|
||||
access: service(),
|
||||
globalStore: service(),
|
||||
|
||||
model(params) {
|
||||
let globalStore = this.get('globalStore');
|
||||
|
||||
return hash({
|
||||
cluster: globalStore.find('cluster', params.cluster_id),
|
||||
nodeTemplates: globalStore.findAll('nodeTemplate'),
|
||||
nodeDrivers: globalStore.findAll('nodeDriver'),
|
||||
psps: globalStore.findAll('podSecurityPolicyTemplate'),
|
||||
roleTemplates: globalStore.findAll('roleTemplate'),
|
||||
users: globalStore.findAll('user'),
|
||||
clusterRoleTemplateBinding: globalStore.findAll('clusterRoleTemplateBinding'),
|
||||
me: get(this, 'access.me'),
|
||||
});
|
||||
},
|
||||
|
||||
setupController(controller/*, model*/) {
|
||||
this._super(...arguments);
|
||||
set(controller, 'step', 1);
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{{cru-cluster
|
||||
model=model
|
||||
initialProvider=model.cluster.provider
|
||||
mode="edit"
|
||||
close=(action "close")
|
||||
}}
|
||||
|
|
@ -17,13 +17,14 @@ export default Resource.extend(ResourceUsage, {
|
|||
namespaces: hasMany('id', 'namespace', 'clusterId'),
|
||||
projects: hasMany('id', 'project', 'clusterId'),
|
||||
nodes: hasMany('id', 'node', 'clusterId'),
|
||||
nodePools: hasMany('id', 'nodePool', 'clusterId'),
|
||||
machines: alias('nodes'),
|
||||
clusterRoleTemplateBindings: hasMany('id', 'clusterRoleTemplateBinding', 'clusterId'),
|
||||
roleTemplateBindings: alias('clusterRoleTemplateBindings'),
|
||||
|
||||
actions: {
|
||||
edit() {
|
||||
this.get('router').transitionTo('global-admin.clusters.detail.edit', this.get('id'));
|
||||
this.get('router').transitionTo('authenticated.cluster.edit', this.get('id'));
|
||||
},
|
||||
|
||||
scaleDownPool(uuid) {
|
||||
|
|
@ -55,6 +56,31 @@ export default Resource.extend(ResourceUsage, {
|
|||
return null;
|
||||
}),
|
||||
|
||||
provider: computed('configName','nodePools.@each.nodeTemplateId', function() {
|
||||
const intl = get(this, 'intl');
|
||||
const pools = get(this,'nodePools')||[];
|
||||
const firstTemplate = get(pools,'firstObject.nodeTemplate');
|
||||
|
||||
switch ( get(this,'configName') ) {
|
||||
case 'azureKubernetesServiceConfig':
|
||||
return 'azureaks';
|
||||
case 'googleKubernetesEngineConfig':
|
||||
return 'googlegke';
|
||||
case 'rancherKubernetesEngineConfig':
|
||||
if ( !!pools ) {
|
||||
if ( firstTemplate ) {
|
||||
return get(firstTemplate, 'driver');
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return 'custom';
|
||||
}
|
||||
default:
|
||||
return 'import';
|
||||
}
|
||||
}),
|
||||
|
||||
displayProvider: computed('configName','nodePools.@each.nodeTemplateId', function() {
|
||||
const intl = get(this, 'intl');
|
||||
const pools = get(this,'nodePools')||[];
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@
|
|||
justify-content: center;
|
||||
|
||||
& > .nav-box-item {
|
||||
min-height : 150px;
|
||||
min-height : 120px;
|
||||
flex : 0 1 auto;
|
||||
position : relative;
|
||||
outline : 0;
|
||||
|
|
|
|||
|
|
@ -1,104 +1,15 @@
|
|||
import Controller from '@ember/controller';
|
||||
import { get, set, computed } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import ViewNewEdit from 'shared/mixins/view-new-edit';
|
||||
import ChildHook from 'shared/mixins/child-hook';
|
||||
import { alias } from '@ember/object/computed';
|
||||
|
||||
const MEMBER_CONFIG = {
|
||||
type: 'clusterRoleTemplateBinding',
|
||||
};
|
||||
|
||||
export default Controller.extend(ViewNewEdit, ChildHook, {
|
||||
globalStore: service(),
|
||||
intl: service(),
|
||||
|
||||
export default Controller.extend({
|
||||
cluster: alias('model.cluster'),
|
||||
primaryResource: alias('model.cluster'),
|
||||
|
||||
step: 1,
|
||||
provider: 'googlegke',
|
||||
memberConfig: MEMBER_CONFIG,
|
||||
|
||||
queryParams: ['provider'],
|
||||
|
||||
actions: {
|
||||
clickNext() {
|
||||
this.$('BUTTON[type="submit"]').click();
|
||||
},
|
||||
|
||||
close() {
|
||||
this.send('goToPrevious', 'global-admin.clusters.index');
|
||||
},
|
||||
},
|
||||
|
||||
providerChoices: computed('nodeDrivers.[]','intl.locale', function() {
|
||||
const intl = get(this, 'intl');
|
||||
|
||||
const out = [
|
||||
{name: 'googlegke', driver: 'googlegke'},
|
||||
// {name: 'amazoneks', driver: 'amazoneks'},
|
||||
{name: 'azureaks', driver: 'azureaks'},
|
||||
];
|
||||
|
||||
get(this, 'model.nodeDrivers').filterBy('active',true).sortBy('name').forEach((driver) => {
|
||||
const name = get(driver, 'name');
|
||||
const hasUi = get(driver, 'hasUi');
|
||||
|
||||
out.push({
|
||||
name: name,
|
||||
driver: 'rke',
|
||||
nodeComponent: hasUi ? name : 'generic',
|
||||
nodeWhich: name,
|
||||
});
|
||||
}),
|
||||
|
||||
out.push({name: 'custom', driver: 'rke', nodeWhich: 'custom'});
|
||||
out.push({name: 'import', driver: 'import'});
|
||||
|
||||
out.forEach((driver) => {
|
||||
const key = `clusterNew.${driver.name}.label`;
|
||||
if ( !get(driver,'displayName') && intl.exists(key) ) {
|
||||
set(driver, 'displayName', intl.t(key));
|
||||
}
|
||||
});
|
||||
|
||||
return out;
|
||||
}),
|
||||
|
||||
driverInfo: computed('provider', function() {
|
||||
const name = get(this, 'provider');
|
||||
const choices = get(this, 'providerChoices');
|
||||
const entry = choices.findBy('name', name);
|
||||
if ( entry ) {
|
||||
return {
|
||||
name: entry.name,
|
||||
driverComponent: `cluster-driver/driver-${entry.driver}`,
|
||||
nodeComponent: `node-driver/driver-${entry.nodeComponent}`,
|
||||
nodeWhich: entry.nodeWhich,
|
||||
};
|
||||
}
|
||||
}),
|
||||
|
||||
willSave() {
|
||||
const cluster = get(this, 'cluster');
|
||||
const field = get(this, 'configField');
|
||||
cluster.clearProvidersExcept(field);
|
||||
return this._super(...arguments);
|
||||
},
|
||||
|
||||
didSave() {
|
||||
const originalCluster = get(this, 'cluster');
|
||||
return originalCluster.waitForCondition('BackingNamespaceCreated').then(() => {
|
||||
return this.applyHooks().then(() => {
|
||||
const clone = originalCluster.clone();
|
||||
set(this, 'cluster', clone);
|
||||
return clone;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
doneSaving() {
|
||||
set(this, 'step', 2);
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,68 +1,6 @@
|
|||
<section class="header">
|
||||
<h1>{{t (if (eq step 1) 'clustersPage.newCluster' 'clustersPage.newClusterName') name=cluster.displayName}}</h1>
|
||||
</section>
|
||||
|
||||
{{#if (eq step 1)}}
|
||||
<form onsubmit={{action "clickNext"}}>
|
||||
<div class="row">
|
||||
{{form-name-description
|
||||
model=cluster
|
||||
nameRequired=true
|
||||
nameLabel='clusterNew.name.label'
|
||||
namePlaceholder='clusterNew.name.placeholder'
|
||||
descriptionPlaceholder='clusterNew.description.placeholder'
|
||||
}}
|
||||
</div>
|
||||
|
||||
{{#accordion-list showExpandAll=false as |al expandFn|}}
|
||||
{{#accordion-list-item
|
||||
title=(t 'clusterNew.members.label')
|
||||
detail=(t 'clusterNew.members.detail')
|
||||
showExpand=false
|
||||
expandOnInit=true
|
||||
expandAll=al.expandAll
|
||||
expand=(action expandFn)
|
||||
}}
|
||||
{{form-members
|
||||
editing=true
|
||||
memberConfig=memberConfig
|
||||
primaryResource=cluster
|
||||
creator=model.me
|
||||
roles=model.roleTemplates
|
||||
users=model.users
|
||||
type="cluster"
|
||||
registerHook=(action "registerHook")
|
||||
}}
|
||||
{{/accordion-list-item}}
|
||||
|
||||
{{#accordion-list-item
|
||||
title=(t 'clusterNew.config.label')
|
||||
detail=(t 'clusterNew.config.detail')
|
||||
showExpand=false
|
||||
expandOnInit=true
|
||||
expandAll=al.expandAll
|
||||
expand=(action expandFn)
|
||||
}}
|
||||
<div class="row nav nav-boxes checked-active">
|
||||
{{#each providerChoices as |choice|}}
|
||||
{{#link-to (query-params provider=choice.name) class=(concat "col span-2 nav-box-item driver machine-driver " choice.name)}}
|
||||
<p class="sr-only">{{choice.displayName}}</p>
|
||||
{{/link-to}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/accordion-list-item}}
|
||||
{{/accordion-list}}
|
||||
|
||||
{{top-errors errors=errors}}
|
||||
{{save-cancel createLabel='saveCancel.next' save='save' cancel='close'}}
|
||||
</form>
|
||||
{{else}}
|
||||
{{component driverInfo.driverComponent
|
||||
editing=false
|
||||
nodeComponent=driverInfo.nodeComponent
|
||||
nodeWhich=driverInfo.nodeWhich
|
||||
model=model
|
||||
close=(action 'close')
|
||||
}}
|
||||
{{/if}}
|
||||
|
||||
{{cru-cluster
|
||||
model=model
|
||||
provider=provider
|
||||
mode="new"
|
||||
close=(action "close")
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -136,5 +136,5 @@
|
|||
{{/accordion-list}}
|
||||
|
||||
{{top-errors errors=errors}}
|
||||
{{save-cancel save="save" cancel=close}}
|
||||
{{save-cancel save="driverSave" cancel=close}}
|
||||
{{/if}}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import Component from '@ember/component'
|
||||
import ClusterDriver from 'global-admin/mixins/cluster-driver';
|
||||
import { resolve } from 'rsvp';
|
||||
import { equal } from '@ember/object/computed';
|
||||
import { get, set, computed, observer } from '@ember/object';
|
||||
import { satisfies } from 'shared/utils/parse-version';
|
||||
|
|
@ -62,6 +63,8 @@ export default Component.extend(ClusterDriver, {
|
|||
configField: 'rancherKubernetesEngineConfig',
|
||||
headers,
|
||||
|
||||
model: null,
|
||||
|
||||
initialVersion: null,
|
||||
|
||||
networkChoices: [
|
||||
|
|
@ -131,31 +134,18 @@ export default Component.extend(ClusterDriver, {
|
|||
this.set('labels', out);
|
||||
},
|
||||
|
||||
addPool() {
|
||||
let nodePools = get(this, 'cluster.nodePools');
|
||||
if ( !nodePools ) {
|
||||
nodePools = [];
|
||||
set(this, 'cluster.nodePools', nodePools);
|
||||
}
|
||||
driverSave(cb) {
|
||||
cb = cb || function() {};
|
||||
|
||||
let templateId = null;
|
||||
const lastNode = nodePools[nodePools.length-1];
|
||||
if ( lastNode ) {
|
||||
templateId = get(lastNode, 'nodeTemplateId');
|
||||
}
|
||||
resolve(this.willSave()).then((ok) => {
|
||||
if ( !ok ) {
|
||||
// Validation or something else said not to save
|
||||
cb(false);
|
||||
return;
|
||||
}
|
||||
|
||||
nodePools.pushObject(get(this, 'globalStore').createRecord({
|
||||
type: 'nodePool',
|
||||
nodeTemplateId: templateId
|
||||
}));
|
||||
},
|
||||
|
||||
addNodeTemplate(node) {
|
||||
get(this,'modalService').toggleModal('modal-edit-node-template', {nodeTemplate: null, driver: get(this, 'nodeWhich'), onAdd: onAdd});
|
||||
|
||||
function onAdd(nodeTemplate) {
|
||||
set(node, 'nodeTemplateId', get(nodeTemplate, 'id'));
|
||||
}
|
||||
this.sendAction('save', cb);
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -177,7 +167,7 @@ export default Component.extend(ClusterDriver, {
|
|||
const intl = get(this, 'intl');
|
||||
|
||||
this._super(...arguments);
|
||||
let errors = this.get('errors')||[];
|
||||
const errors = get(this,'errors')||[];
|
||||
|
||||
if ( !get(this, 'isCustom') ) {
|
||||
if ( !get(this, 'etcdOk') ) {
|
||||
|
|
@ -197,7 +187,6 @@ export default Component.extend(ClusterDriver, {
|
|||
return errors.length === 0;
|
||||
},
|
||||
|
||||
|
||||
doneSaving() {
|
||||
if ( get(this, 'isCustom') ){
|
||||
const cluster = get(this,'cluster');
|
||||
|
|
@ -210,41 +199,6 @@ export default Component.extend(ClusterDriver, {
|
|||
}
|
||||
},
|
||||
|
||||
filteredNodeTemplates: computed('nodeWhich','model.nodeTemplates.@each.{state,driver}', function() {
|
||||
const driver = get(this, 'nodeWhich');
|
||||
let templates = get(this, 'model.nodeTemplates').filterBy('state','active').filterBy('driver', driver);
|
||||
return templates;
|
||||
}),
|
||||
|
||||
_nodeCountFor(role) {
|
||||
let count = 0;
|
||||
(get(this, 'cluster.nodePools')||[]).filterBy(role,true).forEach((pool) => {
|
||||
let more = get(pool, 'quantity');
|
||||
if ( more ) {
|
||||
more = parseInt(more, 10);
|
||||
}
|
||||
|
||||
count += more;
|
||||
});
|
||||
|
||||
return count;
|
||||
},
|
||||
|
||||
etcdOk: computed('cluster.nodePools.@each.{quantity,etcd}', function() {
|
||||
let count = this._nodeCountFor('etcd');
|
||||
return count === 1 || count === 3 || count === 5
|
||||
}),
|
||||
|
||||
controlPlaneOk: computed('cluster.nodePools.@each.{quantity,controlPlane}', function() {
|
||||
let count = this._nodeCountFor('controlPlane');
|
||||
return count >= 1;
|
||||
}),
|
||||
|
||||
workerOk: computed('cluster.nodePools.@each.{quantity,worker}', function() {
|
||||
let count = this._nodeCountFor('worker');
|
||||
return count >= 1;
|
||||
}),
|
||||
|
||||
versionChanged: observer('config.kubernetesVersion','versionChoices.[]', function() {
|
||||
const versions = get(this, 'versionChoices')||[];
|
||||
const current = get(this, 'config.kubernetesVersion');
|
||||
|
|
|
|||
|
|
@ -1,100 +1,12 @@
|
|||
{{#if (eq step 1)}}
|
||||
{{#accordion-list showExpandAll=false as |al expandFn|}}
|
||||
{{#unless isCustom}}
|
||||
{{#accordion-list-item
|
||||
title=(t 'clusterNew.rke.nodes.title')
|
||||
detail=(t 'clusterNew.rke.nodes.detail')
|
||||
showExpand=false
|
||||
expandOnInit=true
|
||||
expandAll=al.expandAll
|
||||
expand=(action expandFn)
|
||||
{{cru-node-pools
|
||||
cluster=cluster
|
||||
driver=nodeWhich
|
||||
nodeTemplates=model.nodeTemplates
|
||||
registerHook=(action "registerHook")
|
||||
}}
|
||||
{{#sortable-table
|
||||
classNames="grid sortable-table"
|
||||
body=cluster.nodePools
|
||||
suffix=true
|
||||
search=false
|
||||
bulkActions=false
|
||||
rowActions=false
|
||||
pagingLabel="pagination.node"
|
||||
headers=headers
|
||||
as |sortable kind node dt|
|
||||
}}
|
||||
{{#if (eq kind "row")}}
|
||||
<tr class="main-row">
|
||||
<td data-title="{{dt.hostnamePrefix}}">
|
||||
<div class="mr-20">
|
||||
{{input class="input-sm" value=node.hostnamePrefix}}
|
||||
</div>
|
||||
</td>
|
||||
<td data-title="{{dt.quantity}}">
|
||||
<div class="input-group mr-20">
|
||||
{{input class="input-sm" type="number" min="1" value=node.quantity}}
|
||||
<span class="input-group-addon bg-default">x</span>
|
||||
</div>
|
||||
</td>
|
||||
<td data-title="{{dt.nodeTemplate}}">
|
||||
{{#if filteredNodeTemplates.length}}
|
||||
<div class="input-group input-sm">
|
||||
{{new-select
|
||||
class="input-sm"
|
||||
content=filteredNodeTemplates
|
||||
prompt="clusterNew.rke.nodes.templatePrompt"
|
||||
localizedPrompt=true
|
||||
optionLabelPath="displayName"
|
||||
optionValuePath="id"
|
||||
value=node.nodeTemplateId
|
||||
}}
|
||||
<div class="input-group-btn bg-primary">
|
||||
<button type="button" class="btn btn-sm bg-primary" {{action "addNodeTemplate" node}}><i class="icon icon-plus"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<button class="btn bg-primary" {{action "addNodeTemplate" node}}>{{t 'clusterNew.rke.nodes.addTemplate'}}</button>
|
||||
{{/if}}
|
||||
</td>
|
||||
<td data-title="{{dt.etcd}}" class="text-center">
|
||||
{{input type="checkbox" checked=node.etcd}}
|
||||
</td>
|
||||
<td data-title="{{dt.controlplane}}" class="text-center">
|
||||
{{input type="checkbox" checked=node.controlPlane}}
|
||||
</td>
|
||||
<td data-title="{{dt.worker}}" class="text-center">
|
||||
{{input type="checkbox" checked=node.worker}}
|
||||
</td>
|
||||
<td data-title="{{dt.remove}}" class="text-center">
|
||||
<button class="btn bg-primary btn-sm" {{action "removeNode" node}}><i class="icon icon-minus"/></button>
|
||||
</td>
|
||||
</tr>
|
||||
{{else if (eq kind "norows")}}
|
||||
<tr><td colspan="{{sortable.fullColspan}}" class="text-center text-muted pt-40 pb-40">{{t 'nodesPage.table.noData'}}</td></tr>
|
||||
{{else if (eq kind "suffix")}}
|
||||
<tr class="banner bg-info suffix">
|
||||
<td colspan="3" class="pl-20 text-bold">{{t 'clusterNew.rke.role.requirements.label'}}</td>
|
||||
<td class="text-center {{if etcdOk "text-success" "text-error text-bold"}}">
|
||||
<i class="icon {{if etcdOk "icon-success" "icon-x-circle"}}"></i>
|
||||
{{t 'clusterNew.rke.role.requirements.etcd'}}
|
||||
</td>
|
||||
<td class="text-center {{if controlPlaneOk "text-success" "text-error text-bold"}}">
|
||||
<i class="icon {{if controlPlaneOk "icon-success" "icon-x-circle"}}"></i>
|
||||
{{t 'clusterNew.rke.role.requirements.controlplane'}}
|
||||
</td>
|
||||
<td class="text-center {{if workerOk "text-success" "text-error text-bold"}}">
|
||||
<i class="icon {{if workerOk "icon-success" "icon-x-circle"}}"></i>
|
||||
{{t 'clusterNew.rke.role.requirements.worker'}}
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
{{/sortable-table}}
|
||||
|
||||
<div class="mt-20">
|
||||
<button class="btn bg-primary icon-btn p-0" {{action "addPool"}}>
|
||||
<span class="darken"><i class="icon icon-plus text-small"/></span>
|
||||
<span>{{t 'clusterNew.rke.nodes.add'}}</span>
|
||||
</button>
|
||||
</div>
|
||||
{{/accordion-list-item}}
|
||||
{{/unless}}
|
||||
|
||||
{{#accordion-list-item
|
||||
|
|
@ -185,7 +97,7 @@
|
|||
{{/accordion-list}}
|
||||
|
||||
{{top-errors errors=errors}}
|
||||
{{save-cancel createLabel=(if isCustom 'saveCancel.next' 'saveCancel.create') save="save" cancel=cancel}}
|
||||
{{save-cancel createLabel=(if isCustom 'saveCancel.next' 'saveCancel.create') save=(action 'driverSave') cancel=cancel}}
|
||||
{{else}}
|
||||
|
||||
<div class="mt-20">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,124 @@
|
|||
import Component from '@ember/component';
|
||||
import { get, set, computed } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import ViewNewEdit from 'shared/mixins/view-new-edit';
|
||||
import ChildHook from 'shared/mixins/child-hook';
|
||||
import { alias } from '@ember/object/computed';
|
||||
|
||||
const MEMBER_CONFIG = {
|
||||
type: 'clusterRoleTemplateBinding',
|
||||
};
|
||||
|
||||
export default Component.extend(ViewNewEdit, ChildHook, {
|
||||
globalStore: service(),
|
||||
intl: service(),
|
||||
access: service(),
|
||||
|
||||
cluster: alias('model.cluster'),
|
||||
primaryResource: alias('model.cluster'),
|
||||
|
||||
step: 1,
|
||||
initialProvider: null,
|
||||
memberConfig: MEMBER_CONFIG,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
||||
// On edit pass in initialProvider, for create just set provider directly
|
||||
const initialProvider = get(this, 'initialProvider');
|
||||
if ( initialProvider ) {
|
||||
set(this, 'provider', initialProvider);
|
||||
}
|
||||
|
||||
if ( get(this, 'cluster.id') && initialProvider ){
|
||||
set(this,'step', 2);
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
clickNext() {
|
||||
this.$('BUTTON[type="submit"]').click();
|
||||
},
|
||||
|
||||
close() {
|
||||
this.send('goToPrevious', 'global-admin.clusters.index');
|
||||
},
|
||||
|
||||
maybeSave(cb) {
|
||||
const info = get(this, 'driverInfo');
|
||||
if ( info.preSave ) {
|
||||
this.send('save',cb);
|
||||
} else {
|
||||
this.doneSaving();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
providerChoices: computed('nodeDrivers.[]','intl.locale', function() {
|
||||
const intl = get(this, 'intl');
|
||||
|
||||
const out = [
|
||||
{name: 'googlegke', driver: 'googlegke'},
|
||||
// {name: 'amazoneks', driver: 'amazoneks'},
|
||||
{name: 'azureaks', driver: 'azureaks'},
|
||||
];
|
||||
|
||||
get(this, 'model.nodeDrivers').filterBy('active',true).sortBy('name').forEach((driver) => {
|
||||
const name = get(driver, 'name');
|
||||
const hasUi = get(driver, 'hasUi');
|
||||
|
||||
out.push({
|
||||
name: name,
|
||||
driver: 'rke',
|
||||
nodeComponent: hasUi ? name : 'generic',
|
||||
nodeWhich: name,
|
||||
});
|
||||
}),
|
||||
|
||||
out.push({name: 'custom', driver: 'rke', nodeWhich: 'custom', preSave: true});
|
||||
out.push({name: 'import', driver: 'import', preSave: true});
|
||||
|
||||
out.forEach((driver) => {
|
||||
const key = `clusterNew.${driver.name}.label`;
|
||||
if ( !get(driver,'displayName') && intl.exists(key) ) {
|
||||
set(driver, 'displayName', intl.t(key));
|
||||
}
|
||||
});
|
||||
|
||||
return out;
|
||||
}),
|
||||
|
||||
driverInfo: computed('provider', function() {
|
||||
const name = get(this, 'provider');
|
||||
const choices = get(this, 'providerChoices');
|
||||
const entry = choices.findBy('name', name);
|
||||
if ( entry ) {
|
||||
return {
|
||||
name: entry.name,
|
||||
driverComponent: `cluster-driver/driver-${entry.driver}`,
|
||||
nodeComponent: `node-driver/driver-${entry.nodeComponent}`,
|
||||
nodeWhich: entry.nodeWhich,
|
||||
preSave: !!entry.preSave,
|
||||
};
|
||||
}
|
||||
}),
|
||||
|
||||
didSave() {
|
||||
const originalCluster = get(this, 'cluster');
|
||||
return originalCluster.waitForCondition('BackingNamespaceCreated').then(() => {
|
||||
return this.applyHooks().then(() => {
|
||||
const clone = originalCluster.clone();
|
||||
set(this, 'cluster', clone);
|
||||
return clone;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
doneSaving() {
|
||||
if ( get(this, 'driverInfo.preSave') ) {
|
||||
set(this, 'step', 2);
|
||||
} else {
|
||||
this.sendAction('close');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
<section class="header">
|
||||
<h1>
|
||||
{{#if cluster.id}}
|
||||
{{t 'clustersPage.editClusterName' name=cluster.displayName}}
|
||||
{{else if (eq step 1)}}
|
||||
{{t 'clustersPage.newCluster'}}
|
||||
{{else}}
|
||||
{{t 'clustersPage.newClusterName' name=cluster.displayName}}
|
||||
{{/if}}
|
||||
</h1>
|
||||
</section>
|
||||
|
||||
<div class="row nav nav-boxes checked-active">
|
||||
{{#each providerChoices as |choice|}}
|
||||
{{#link-to (query-params provider=choice.name) class=(concat "col span-2 nav-box-item driver machine-driver " choice.name)}}
|
||||
<p class="sr-only">{{choice.displayName}}</p>
|
||||
{{/link-to}}
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
{{#if (or isEdit (eq step 1))}}
|
||||
<form onsubmit={{action "clickNext"}}>
|
||||
<div class="row">
|
||||
{{form-name-description
|
||||
model=model.cluster
|
||||
nameRequired=true
|
||||
nameLabel='clusterNew.name.label'
|
||||
namePlaceholder='clusterNew.name.placeholder'
|
||||
descriptionPlaceholder='clusterNew.description.placeholder'
|
||||
}}
|
||||
</div>
|
||||
|
||||
{{#accordion-list showExpandAll=false as |al expandFn|}}
|
||||
{{#accordion-list-item
|
||||
title=(t 'clusterNew.members.label')
|
||||
detail=(t 'clusterNew.members.detail')
|
||||
expandAll=al.expandAll
|
||||
everExpanded=true
|
||||
expand=(action expandFn)
|
||||
}}
|
||||
{{form-members
|
||||
editing=notView
|
||||
memberConfig=memberConfig
|
||||
primaryResource=cluster
|
||||
creator=model.me
|
||||
roles=model.roleTemplates
|
||||
users=model.users
|
||||
type="cluster"
|
||||
registerHook=(action "registerHook")
|
||||
}}
|
||||
{{/accordion-list-item}}
|
||||
{{/accordion-list}}
|
||||
|
||||
{{top-errors errors=errors}}
|
||||
{{#if (and isNew driverInfo.preSave)}}
|
||||
{{save-cancel createLabel='saveCancel.next' save='maybeSave' cancel='close'}}
|
||||
{{/if}}
|
||||
</form>
|
||||
{{/if}}
|
||||
|
||||
{{#unless (and isNew driverInfo.preSave)}}
|
||||
{{component driverInfo.driverComponent
|
||||
mode=mode
|
||||
nodeComponent=driverInfo.nodeComponent
|
||||
nodeWhich=driverInfo.nodeWhich
|
||||
model=model
|
||||
save=(action 'save')
|
||||
close=(action 'close')
|
||||
registerHook=(action "registerHook")
|
||||
}}
|
||||
{{/unless}}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
import Component from '@ember/component';
|
||||
import layout from './template';
|
||||
import { computed, get, set } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { all as PromiseAll } from 'rsvp';
|
||||
|
||||
export default Component.extend({
|
||||
layout,
|
||||
globalStore: service(),
|
||||
|
||||
cluster: null,
|
||||
nodeTemplates: null,
|
||||
driver: null, // docker-machine driver
|
||||
|
||||
originalPools: null,
|
||||
nodePools: null,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
const originalPools = (get(this,'cluster.nodePools')||[]).slice();
|
||||
set(this, 'originalPools', originalPools);
|
||||
set(this, 'nodePools', originalPools.slice());
|
||||
this.sendAction('registerHook', this.savePools.bind(this), 'savePools');
|
||||
},
|
||||
|
||||
actions: {
|
||||
addPool() {
|
||||
let nodePools = get(this, 'nodePools');
|
||||
|
||||
let templateId = null;
|
||||
const lastNode = nodePools[nodePools.length-1];
|
||||
if ( lastNode ) {
|
||||
templateId = get(lastNode, 'nodeTemplateId');
|
||||
}
|
||||
|
||||
nodePools.pushObject(get(this, 'globalStore').createRecord({
|
||||
type: 'nodePool',
|
||||
nodeTemplateId: templateId
|
||||
}));
|
||||
},
|
||||
|
||||
removePool(pool) {
|
||||
get(this, 'nodePools').removeObject(pool);
|
||||
},
|
||||
|
||||
addNodeTemplate(node) {
|
||||
get(this,'modalService').toggleModal('modal-edit-node-template', {
|
||||
nodeTemplate: null,
|
||||
driver: get(this, 'driver'),
|
||||
onAdd: function(nodeTemplate) {
|
||||
set(node, 'nodeTemplateId', get(nodeTemplate, 'id'));
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
savePools: function() {
|
||||
const nodePools = get(this, 'nodePools');
|
||||
const original = get(this, 'originalPools');
|
||||
|
||||
const remove = [];
|
||||
original.forEach((pool) => {
|
||||
if ( !nodePools.includes(pool) ) {
|
||||
// Remove
|
||||
remove.push(pool);
|
||||
}
|
||||
});
|
||||
|
||||
const clusterId = get(this, 'cluster.id');
|
||||
nodePools.forEach((pool) => {
|
||||
set(pool, 'clusterId', clusterId);
|
||||
});
|
||||
|
||||
|
||||
return PromiseAll(nodePools.map(x => x.save())).then(() => {
|
||||
return PromiseAll(remove.map(x => x.delete())).then(() => {
|
||||
return get(this, 'cluster');
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
filteredNodeTemplates: computed('driver','nodeTemplates.@each.{state,driver}', function() {
|
||||
const driver = get(this, 'driver');
|
||||
let templates = get(this, 'nodeTemplates').filterBy('state','active').filterBy('driver', driver);
|
||||
return templates;
|
||||
}),
|
||||
|
||||
_nodeCountFor(role) {
|
||||
let count = 0;
|
||||
(get(this, 'cluster.nodePools')||[]).filterBy(role,true).forEach((pool) => {
|
||||
let more = get(pool, 'quantity');
|
||||
if ( more ) {
|
||||
more = parseInt(more, 10);
|
||||
}
|
||||
|
||||
count += more;
|
||||
});
|
||||
|
||||
return count;
|
||||
},
|
||||
|
||||
etcdOk: computed('cluster.nodePools.@each.{quantity,etcd}', function() {
|
||||
let count = this._nodeCountFor('etcd');
|
||||
return count === 1 || count === 3 || count === 5
|
||||
}),
|
||||
|
||||
controlPlaneOk: computed('cluster.nodePools.@each.{quantity,controlPlane}', function() {
|
||||
let count = this._nodeCountFor('controlPlane');
|
||||
return count >= 1;
|
||||
}),
|
||||
|
||||
workerOk: computed('cluster.nodePools.@each.{quantity,worker}', function() {
|
||||
let count = this._nodeCountFor('worker');
|
||||
return count >= 1;
|
||||
}),
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
{{#accordion-list-item
|
||||
title=(t 'clusterNew.rke.nodes.title')
|
||||
detail=(t 'clusterNew.rke.nodes.detail')
|
||||
showExpand=false
|
||||
expandOnInit=true
|
||||
expandAll=al.expandAll
|
||||
expand=(action expandFn)
|
||||
}}
|
||||
{{#sortable-table
|
||||
classNames="grid sortable-table"
|
||||
body=model
|
||||
suffix=true
|
||||
search=false
|
||||
bulkActions=false
|
||||
rowActions=false
|
||||
pagingLabel="pagination.nodePool"
|
||||
headers=headers
|
||||
as |sortable kind pool dt|
|
||||
}}
|
||||
{{#if (eq kind "row")}}
|
||||
<tr class="main-row">
|
||||
<td data-title="{{dt.hostnamePrefix}}">
|
||||
<div class="mr-20">
|
||||
{{input class="input-sm" value=pool.hostnamePrefix}}
|
||||
</div>
|
||||
</td>
|
||||
<td data-title="{{dt.quantity}}">
|
||||
<div class="input-group mr-20">
|
||||
{{input class="input-sm" type="number" min="1" value=pool.quantity}}
|
||||
<span class="input-group-addon bg-default">x</span>
|
||||
</div>
|
||||
</td>
|
||||
<td data-title="{{dt.nodeTemplate}}">
|
||||
{{#if filteredNodeTemplates.length}}
|
||||
<div class="input-group input-sm">
|
||||
{{new-select
|
||||
class="input-sm"
|
||||
content=filteredNodeTemplates
|
||||
prompt="clusterNew.rke.nodes.templatePrompt"
|
||||
localizedPrompt=true
|
||||
optionLabelPath="displayName"
|
||||
optionValuePath="id"
|
||||
value=pool.nodeTemplateId
|
||||
}}
|
||||
<div class="input-group-btn bg-primary">
|
||||
<button type="button" class="btn btn-sm bg-primary" {{action "addNodeTemplate" pool}}><i class="icon icon-plus"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<button class="btn bg-primary" {{action "addNodeTemplate" pool}}>{{t 'clusterNew.rke.nodes.addTemplate'}}</button>
|
||||
{{/if}}
|
||||
</td>
|
||||
<td data-title="{{dt.etcd}}" class="text-center">
|
||||
{{input type="checkbox" checked=pool.etcd}}
|
||||
</td>
|
||||
<td data-title="{{dt.controlplane}}" class="text-center">
|
||||
{{input type="checkbox" checked=pool.controlPlane}}
|
||||
</td>
|
||||
<td data-title="{{dt.worker}}" class="text-center">
|
||||
{{input type="checkbox" checked=pool.worker}}
|
||||
</td>
|
||||
<td data-title="{{dt.remove}}" class="text-center">
|
||||
<button class="btn bg-primary btn-sm" {{action "removePool" pool}}><i class="icon icon-minus"/></button>
|
||||
</td>
|
||||
</tr>
|
||||
{{else if (eq kind "norows")}}
|
||||
<tr><td colspan="{{sortable.fullColspan}}" class="text-center text-muted pt-40 pb-40">{{t 'nodesPage.table.noData'}}</td></tr>
|
||||
{{else if (eq kind "suffix")}}
|
||||
<tr class="banner bg-info suffix">
|
||||
<td colspan="3" class="pl-20 text-bold">{{t 'clusterNew.rke.role.requirements.label'}}</td>
|
||||
<td class="text-center {{if etcdOk "text-success" "text-error text-bold"}}">
|
||||
<i class="icon {{if etcdOk "icon-success" "icon-x-circle"}}"></i>
|
||||
{{t 'clusterNew.rke.role.requirements.etcd'}}
|
||||
</td>
|
||||
<td class="text-center {{if controlPlaneOk "text-success" "text-error text-bold"}}">
|
||||
<i class="icon {{if controlPlaneOk "icon-success" "icon-x-circle"}}"></i>
|
||||
{{t 'clusterNew.rke.role.requirements.controlplane'}}
|
||||
</td>
|
||||
<td class="text-center {{if workerOk "text-success" "text-error text-bold"}}">
|
||||
<i class="icon {{if workerOk "icon-success" "icon-x-circle"}}"></i>
|
||||
{{t 'clusterNew.rke.role.requirements.worker'}}
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
{{/sortable-table}}
|
||||
|
||||
<div class="mt-20">
|
||||
<button class="btn bg-primary icon-btn p-0" {{action "addPool"}}>
|
||||
<span class="darken"><i class="icon icon-plus text-small"/></span>
|
||||
<span>{{t 'clusterNew.rke.nodes.add'}}</span>
|
||||
</button>
|
||||
</div>
|
||||
{{/accordion-list-item}}
|
||||
|
|
@ -1,37 +1,62 @@
|
|||
import Mixin from '@ember/object/mixin';
|
||||
import ViewNewEdit from 'shared/mixins/view-new-edit';
|
||||
import { get, computed } from '@ember/object';
|
||||
import { get, set, computed } from '@ember/object';
|
||||
import { alias } from '@ember/object/computed';
|
||||
import { resolve } from 'rsvp';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default Mixin.create(ViewNewEdit, {
|
||||
export default Mixin.create({
|
||||
configField: '<override me>',
|
||||
|
||||
close: null, // action to finish adding
|
||||
mode: null,
|
||||
save: null, // Action to save
|
||||
close: null, // Action on complete
|
||||
registerHook: null,
|
||||
|
||||
globalStore: service(),
|
||||
router: service(),
|
||||
|
||||
cluster: alias('model.cluster'),
|
||||
primaryResource: alias('model.cluster'),
|
||||
errors: null,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
},
|
||||
|
||||
actions: {
|
||||
close() {
|
||||
this.sendAction('close');
|
||||
}
|
||||
},
|
||||
|
||||
config: computed('configField', function() {
|
||||
const field = 'cluster.' + get(this, 'configField');
|
||||
return get(this, field);
|
||||
}),
|
||||
|
||||
doneSaving() {
|
||||
this.get('router').transitionTo('global-admin.clusters.index');
|
||||
actions: {
|
||||
driverSave(cb) {
|
||||
resolve(this.willSave()).then((ok) => {
|
||||
if ( !ok ) {
|
||||
// Validation or something else said not to save
|
||||
cb(false);
|
||||
return;
|
||||
}
|
||||
|
||||
this.sendAction('save', cb);
|
||||
});
|
||||
},
|
||||
|
||||
registerHook() {
|
||||
const args = [].slice.call(arguments);
|
||||
args.unshift('registerHook');
|
||||
this.sendAction.apply(this, args);
|
||||
}
|
||||
},
|
||||
|
||||
willSave() {
|
||||
const cluster = get(this, 'cluster');
|
||||
const field = get(this, 'configField');
|
||||
cluster.clearProvidersExcept(field);
|
||||
|
||||
set(this, 'errors',null);
|
||||
const ok = this.validate();
|
||||
return ok;
|
||||
},
|
||||
|
||||
validate() {
|
||||
const model = get(this,'cluster');
|
||||
const errors = model.validationErrors();
|
||||
set(this, 'errors', errors);
|
||||
return errors.length === 0;
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -140,6 +140,10 @@ export default Component.extend({
|
|||
return PromiseAll(add.map(x => x.save())).then(() => {
|
||||
return PromiseAll(update.map(x => x.save())).then(() => {
|
||||
return PromiseAll(remove.map(x => x.delete())).then(() => {
|
||||
if ( this.isDestroyed || this.isDestroying ) {
|
||||
return;
|
||||
}
|
||||
|
||||
set(this, 'memberArray', []);
|
||||
return get(this, 'primaryResource');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -645,6 +645,7 @@ clustersPage:
|
|||
header: Clusters
|
||||
newCluster: Add Cluster
|
||||
newClusterName: "Add Cluster: {name}"
|
||||
editClusterName: "Edit Cluster: {name}"
|
||||
cluster:
|
||||
label: Cluster Name
|
||||
provider:
|
||||
|
|
@ -3141,7 +3142,7 @@ clusterNew:
|
|||
placeholder: e.g. Cluster for dev and test workloads
|
||||
members:
|
||||
label: Member Roles
|
||||
detail: Control who as access to the cluster and what permission they have to change it
|
||||
detail: Control who has access to the cluster and what permission they have to change it.
|
||||
config:
|
||||
label: Provider
|
||||
detail: Choose where the nodes for the cluster will come from
|
||||
|
|
@ -4165,6 +4166,11 @@ pagination:
|
|||
=0 {No Nodes}
|
||||
=1 {{count} {count, plural, =1 {Node} other {Nodes}}}
|
||||
other {{from} - {to} of {count} Nodes}}
|
||||
nodePool: |
|
||||
{pages, plural,
|
||||
=0 {No Node Pools}
|
||||
=1 {{count} {count, plural, =1 {Node Pool} other {Node Pools}}}
|
||||
other {{from} - {to} of {count} Node Pools}}
|
||||
nodeTemplate: |
|
||||
{pages, plural,
|
||||
=0 {No Node Templates}
|
||||
|
|
|
|||
Loading…
Reference in New Issue