From 3df948e9ab71b30533a98fd37bb32ce0cf730455 Mon Sep 17 00:00:00 2001 From: Westly Wright Date: Thu, 7 Feb 2019 12:02:53 -0700 Subject: [PATCH] Add cloud credentials page, resource, and add modal WIP WIP WIP cleanup --- app/initializers/aws-sdk.js | 14 +- app/instance-initializers/nav.js | 10 +- app/models/cloudcredential.js | 38 +++ .../cru-cloud-credential/component.js | 130 +++++++++ .../cru-cloud-credential/template.hbs | 191 +++++++++++++ .../modal-add-cloud-key/component.js | 8 + .../modal-add-cloud-key/template.hbs | 4 + lib/global-admin/addon/routes.js | 4 + .../security/cloud-keys/index/controller.js | 46 +++ .../addon/security/cloud-keys/index/route.js | 11 + .../security/cloud-keys/index/template.hbs | 59 ++++ .../cru-cloud-credential/component.js | 1 + .../modal-add-cloud-key/component.js | 1 + .../components/driver-amazonec2/component.js | 68 +++-- .../components/driver-amazonec2/template.hbs | 126 ++++----- .../components/driver-azure/component.js | 20 +- .../components/driver-azure/template.hbs | 266 +++++++++++++----- .../driver-digitalocean/component.js | 55 ++-- .../driver-digitalocean/template.hbs | 87 +++--- .../driver-vmwarevsphere/component.js | 44 +-- .../driver-vmwarevsphere/template.hbs | 33 +-- lib/nodes/addon/node-templates/route.js | 6 +- .../form-auth-cloud-credential/component.js | 29 ++ .../form-auth-cloud-credential/template.hbs | 70 +++++ lib/shared/addon/mixins/modal-base.js | 13 +- lib/shared/addon/mixins/node-driver.js | 65 ++++- .../form-auth-cloud-credential/component.js | 1 + translations/en-us.yaml | 45 +++ 28 files changed, 1143 insertions(+), 302 deletions(-) create mode 100644 app/models/cloudcredential.js create mode 100644 lib/global-admin/addon/components/cru-cloud-credential/component.js create mode 100644 lib/global-admin/addon/components/cru-cloud-credential/template.hbs create mode 100644 lib/global-admin/addon/components/modal-add-cloud-key/component.js create mode 100644 lib/global-admin/addon/components/modal-add-cloud-key/template.hbs create mode 100644 lib/global-admin/addon/security/cloud-keys/index/controller.js create mode 100644 lib/global-admin/addon/security/cloud-keys/index/route.js create mode 100644 lib/global-admin/addon/security/cloud-keys/index/template.hbs create mode 100644 lib/global-admin/app/components/cru-cloud-credential/component.js create mode 100644 lib/global-admin/app/components/modal-add-cloud-key/component.js create mode 100644 lib/shared/addon/components/form-auth-cloud-credential/component.js create mode 100644 lib/shared/addon/components/form-auth-cloud-credential/template.hbs create mode 100644 lib/shared/app/components/form-auth-cloud-credential/component.js diff --git a/app/initializers/aws-sdk.js b/app/initializers/aws-sdk.js index 55d0c0e07..e6ca2454d 100644 --- a/app/initializers/aws-sdk.js +++ b/app/initializers/aws-sdk.js @@ -1,12 +1,20 @@ +import { get } from '@ember/object'; + export function initialize(application) { // Monkey patch AWS SDK to go through our proxy var orig = AWS.XHRClient.prototype.handleRequest; AWS.XHRClient.prototype.handleRequest = function handleRequest(httpRequest, httpOptions, callback, errCallback) { - httpRequest.endpoint.protocol = 'http:'; - httpRequest.endpoint.port = 80; httpRequest.headers['X-Api-Headers-Restrict'] = 'Content-Length'; - httpRequest.headers['X-Api-Auth-Header'] = httpRequest.headers['Authorization']; + + if (get(httpOptions, 'cloudCredentialId')) { + httpRequest.headers['X-Api-CattleAuth-Header'] = `awsv4 credID=${ get(httpOptions, 'cloudCredentialId') }`; + } else { + httpRequest.endpoint.protocol = 'http:'; + httpRequest.endpoint.port = 80; + httpRequest.headers['X-Api-Auth-Header'] = httpRequest.headers['Authorization']; + } + delete httpRequest.headers['Authorization']; httpRequest.headers['Content-Type'] = `rancher:${ httpRequest.headers['Content-Type'] }`; diff --git a/app/instance-initializers/nav.js b/app/instance-initializers/nav.js index c139802b2..724363061 100644 --- a/app/instance-initializers/nav.js +++ b/app/instance-initializers/nav.js @@ -353,7 +353,7 @@ const rootNav = [ { id: 'global-security-roles', localizedLabel: 'nav.admin.security.roles', - icon: 'icon icon-key', + icon: 'icon icon-users', route: 'global-admin.security.roles', resource: ['roletemplate'], resourceScope: 'global', @@ -366,6 +366,14 @@ const rootNav = [ resource: ['podsecuritypolicytemplate'], resourceScope: 'global', }, + { + id: 'global-security-cloud-keys', + localizedLabel: 'nav.admin.security.cloudKeys', + icon: 'icon icon-secrets', + route: 'global-admin.security.cloud-keys', + resource: ['cloudcredential'], + resourceScope: 'global', + }, { id: 'global-security-authentication', localizedLabel: 'nav.admin.security.authentication', diff --git a/app/models/cloudcredential.js b/app/models/cloudcredential.js new file mode 100644 index 000000000..eea6bf7fb --- /dev/null +++ b/app/models/cloudcredential.js @@ -0,0 +1,38 @@ +import Resource from '@rancher/ember-api-store/models/resource'; +import { computed } from '@ember/object'; +import { notEmpty } from '@ember/object/computed'; + +const cloudCredential = Resource.extend({ + type: 'cloudCredential', + + canClone: false, + canEdit: false, + + isAmazon: notEmpty('amazonec2credentialConfig'), + isAzure: notEmpty('azurecredentialConfig'), + isDo: notEmpty('digitaloceancredentialConfig'), + isVMware: notEmpty('vmwarevspherecredentialConfig'), + + displayType: computed('amazonec2credentialConfig', 'azurecredentialConfig', 'digitaloceancredentialConfig', 'vmwarevspherecredentialConfig', function() { + const { + isAmazon, + isAzure, + isDo, + isVMware + } = this; + + if (isAmazon) { + return 'Amazon'; + } else if (isAzure) { + return 'Azure'; + } else if (isDo) { + return 'Digital Ocean'; + } else if (isVMware) { + return 'VMware vSphere'; + } + }), + + +}); + +export default cloudCredential; diff --git a/lib/global-admin/addon/components/cru-cloud-credential/component.js b/lib/global-admin/addon/components/cru-cloud-credential/component.js new file mode 100644 index 000000000..ede89321f --- /dev/null +++ b/lib/global-admin/addon/components/cru-cloud-credential/component.js @@ -0,0 +1,130 @@ +import { inject as service } from '@ember/service'; +import Component from '@ember/component'; +import NewOrEdit from 'shared/mixins/new-or-edit'; +import layout from './template'; +import { get, set, computed } from '@ember/object'; +import { next } from '@ember/runloop'; + +const CRED_CONFIG_CHOICES = [ + { + name: 'amazon', + displayName: 'Amazon', + driver: 'amazonec2', + configField: 'amazonec2credentialConfig', + }, + { + name: 'azure', + displayName: 'Azure', + driver: 'azure', + configField: 'azurecredentialConfig', + }, + { + name: 'digitalOcean', + displayName: 'Digital Ocean', + driver: 'digitalocean', + configField: 'digitaloceancredentialConfig', + }, + { + name: 'vmware', + displayName: 'VMware vSphere', + driver: 'vmwarevsphere', + configField: 'vmwarevspherecredentialConfig', + }, +] + +export default Component.extend(NewOrEdit, { + globalStore: service(), + layout, + nodeConfigTemplateType: null, + cloudKeyType: null, + model: null, + cancelAdd: null, + doneSavingCloudCredential: null, + disableHeader: false, + + didReceiveAttrs() { + set(this, 'model', this.globalStore.createRecord({ type: 'cloudCredential' })); + }, + + actions: { + selectConfig(configType) { + this.cleanupPreviousConfig(); + + set(this, 'cloudKeyType', configType); + + this.initCloudCredentialConfig(); + }, + }, + + config: computed('cloudKeyType', { + get() { + const { model } = this; + const type = this.getConfigField(); + + return get(model, type); + } + }), + + configChoices: computed('driverName', function() { + if (get(this, 'driverName')) { + const { driverName } = this; + + let match = CRED_CONFIG_CHOICES.findBy('driver', driverName); + + + next(() => { + set(this, 'cloudKeyType', get(match, 'name')); + this.initCloudCredentialConfig(); + }) + + return [match]; + } else { + return CRED_CONFIG_CHOICES.sortBy('displayName'); + } + }), + + saveDisabled: computed('config.{amazonec2credentialConfig,azurecredentialConfig,digitaloceancredentialConfig,vmwarevspherecredentialConfig}', 'cloudKeyType', function() { + if (this.getConfigField()) { + return false; + } + + return true; + }), + + initCloudCredentialConfig() { + const { model } = this; + const type = this.getConfigField(); + + set(model, type, this.globalStore.createRecord({ type: type.toLowerCase() })); + }, + + doneSaving(neu) { + this.doneSavingCloudCredential(neu); + }, + + cleanupPreviousConfig() { + const { model } = this; + const configField = this.getConfigField(); + + if (configField) { + delete model[configField]; + } + }, + + getConfigField() { + const { cloudKeyType, configChoices } = this; + + if (cloudKeyType) { + const matchType = configChoices.findBy('name', cloudKeyType); + + return get(matchType, 'configField'); + } + + return; + }, + + parseNodeTemplateConfigType(nodeTemplate) { + return Object.keys(nodeTemplate).find((f) => f.toLowerCase().indexOf('config') > -1); + }, + +}); diff --git a/lib/global-admin/addon/components/cru-cloud-credential/template.hbs b/lib/global-admin/addon/components/cru-cloud-credential/template.hbs new file mode 100644 index 000000000..6324ef298 --- /dev/null +++ b/lib/global-admin/addon/components/cru-cloud-credential/template.hbs @@ -0,0 +1,191 @@ +
+ {{#unless disableHeader}} +

+ {{t "modalAddCloudKey.header"}} +

+ {{/unless}} +
+ + {{form-name-description + model=primaryResource + namePlaceholder="newSecret.name.placeholder" + descriptionPlaceholder="newSecret.description.placeholder" + }} + +
+
+ + + + +
+ + {{#if (eq cloudKeyType "amazon")}} +
+
+ + {{input + type="text" + name="username" + classNames="form-control" + placeholder=(t "modalAddCloudKey.amazonec2.accessKey.placeholder") + value=config.accessKey + id="amazonec2-accessKey" + }} +
+ +
+ + {{input + type="password" + name="password" + classNames="form-control" + placeholder=(t "modalAddCloudKey.amazonec2.secretKey.placeholder") + value=config.secretKey + id="amazonec2-secretKey" + }} +
+
+ {{else if (eq cloudKeyType "azure")}} +
+
+ + {{input + type="text" + value=config.subscriptionId + classNames="form-control" + placeholder=(t "nodeDriver.azure.subscriptionId.placeholder") + }} +
+
+
+
+ + {{input + type="text" + value=config.clientId + classNames="form-control" + id="azure-clientId" + placeholder=(t "modalAddCloudKey.azure.clientId.placeholder") + }} +
+
+ + {{input + type="password" + value=config.clientSecret + classNames="form-control" + id="azure-clientSecret" + placeholder=(t "modalAddCloudKey.azure.clientSecret.placeholder") + }} +
+
+ {{else if (eq cloudKeyType "digitalOcean")}} +
+
+ + {{input + type="password" + value=config.accessToken + classNames="form-control" + placeholder=(t "modalAddCloudKey.digitalocean.accessToken.placeholder") + id="digitalocean-accessToken" + }} +

+ {{t "modalAddCloudKey.digitalocean.accessToken.help" htmlSafe=true}} +

+
+
+ {{else if (eq cloudKeyType "vmware")}} +
+
+ + {{input + type="text" + class="form-control" + value=config.vcenter + placeholder=(t "nodeDriver.vmwarevsphere.vcenter.placeholder") + }} +
+
+ + {{input-integer + min=1 + max=65535 + class="form-control" + value=config.vcenterPort + }} +
+
+ +
+
+ + {{input + type="text" + value=config.username + classNames="form-control" + }} +
+ +
+ + {{input + type="password" + value=config.password + classNames="form-control" + }} +
+
+

{{t "nodeDriver.vmwarevsphere.access.help"}}

+ {{/if}} +
+ + + +
\ No newline at end of file diff --git a/lib/global-admin/addon/components/modal-add-cloud-key/component.js b/lib/global-admin/addon/components/modal-add-cloud-key/component.js new file mode 100644 index 000000000..f400d9230 --- /dev/null +++ b/lib/global-admin/addon/components/modal-add-cloud-key/component.js @@ -0,0 +1,8 @@ +import Component from '@ember/component'; +import ModalBase from 'shared/mixins/modal-base'; +import layout from './template'; + +export default Component.extend(ModalBase, { + layout, + classNames: ['large-modal', 'alert'], +}); diff --git a/lib/global-admin/addon/components/modal-add-cloud-key/template.hbs b/lib/global-admin/addon/components/modal-add-cloud-key/template.hbs new file mode 100644 index 000000000..59ac52471 --- /dev/null +++ b/lib/global-admin/addon/components/modal-add-cloud-key/template.hbs @@ -0,0 +1,4 @@ +{{cru-cloud-credential + doneSavingCloudCredential=(action "close") + cancelAdd=(action "cancel") +}} diff --git a/lib/global-admin/addon/routes.js b/lib/global-admin/addon/routes.js index f58060070..1edd0d39d 100644 --- a/lib/global-admin/addon/routes.js +++ b/lib/global-admin/addon/routes.js @@ -69,5 +69,9 @@ export default buildRoutes(function() { this.route('okta'); this.route('freeipa'); }); + + this.route('cloud-keys', function() { + this.route('index', { path: '/' }); + }); }); }); diff --git a/lib/global-admin/addon/security/cloud-keys/index/controller.js b/lib/global-admin/addon/security/cloud-keys/index/controller.js new file mode 100644 index 000000000..227ea6fac --- /dev/null +++ b/lib/global-admin/addon/security/cloud-keys/index/controller.js @@ -0,0 +1,46 @@ +import Controller from '@ember/controller'; +import { computed, get } from '@ember/object'; +import { inject as service } from '@ember/service'; +import layout from './template'; + +const HEADERS = [ + { + name: 'type', + sort: ['displayType'], + searchField: 'displayType', + translationKey: 'generic.type', + type: 'string', + }, + { + name: 'name', + sort: ['displayName'], + searchField: 'displayName', + translationKey: 'generic.name', + type: 'string', + }, + { + classNames: 'text-right pr-20', + name: 'created', + sort: ['created'], + translationKey: 'generic.created', + }, +]; + +export default Controller.extend({ + modal: service(), + + layout, + sortBy: 'created', + searchText: '', + headers: HEADERS, + + actions: { + addCloudKey() { + this.modal.toggleModal('modal-add-cloud-key'); + } + }, + + filteredContent: computed('model.@each.{id}', function() { + return get(this, 'model').sortBy('id'); + }), +}); diff --git a/lib/global-admin/addon/security/cloud-keys/index/route.js b/lib/global-admin/addon/security/cloud-keys/index/route.js new file mode 100644 index 000000000..5e2270abd --- /dev/null +++ b/lib/global-admin/addon/security/cloud-keys/index/route.js @@ -0,0 +1,11 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; +// import { get } from '@ember/object'; + +export default Route.extend({ + globalStore: service(), + + model(/* params */) { + return this.globalStore.findAll('cloudcredential'); + }, +}); diff --git a/lib/global-admin/addon/security/cloud-keys/index/template.hbs b/lib/global-admin/addon/security/cloud-keys/index/template.hbs new file mode 100644 index 000000000..9aa15a1a3 --- /dev/null +++ b/lib/global-admin/addon/security/cloud-keys/index/template.hbs @@ -0,0 +1,59 @@ +
+
+ +
+
+
+ {{#sortable-table + bulkActions=true + classNames="grid sortable-table" + sortBy=sortBy + descending=descending + headers=headers + searchText=searchText + showHeader=true + body=filteredContent + rightActions=true + as |sortable kind row dt| + }} + {{#if (eq kind "row")}} + + + {{check-box nodeId=row.id}} + + + {{row.displayType}} + + + {{row.displayName}} + + + {{date-calendar row.created}} + + + {{action-menu model=row}} + + + {{else if (eq kind "nomatch")}} + + {{t "cloudKeysPage.index.table.noMatch"}} + + {{else if (eq kind "norows")}} + + {{t "cloudKeysPage.index.table.noData"}} + + {{/if}} + {{/sortable-table}} +
diff --git a/lib/global-admin/app/components/cru-cloud-credential/component.js b/lib/global-admin/app/components/cru-cloud-credential/component.js new file mode 100644 index 000000000..aacabf043 --- /dev/null +++ b/lib/global-admin/app/components/cru-cloud-credential/component.js @@ -0,0 +1 @@ +export { default } from 'global-admin/components/cru-cloud-credential/component'; \ No newline at end of file diff --git a/lib/global-admin/app/components/modal-add-cloud-key/component.js b/lib/global-admin/app/components/modal-add-cloud-key/component.js new file mode 100644 index 000000000..957824283 --- /dev/null +++ b/lib/global-admin/app/components/modal-add-cloud-key/component.js @@ -0,0 +1 @@ +export { default } from 'global-admin/components/modal-add-cloud-key/component'; diff --git a/lib/nodes/addon/components/driver-amazonec2/component.js b/lib/nodes/addon/components/driver-amazonec2/component.js index f65950c9d..4ec205c31 100644 --- a/lib/nodes/addon/components/driver-amazonec2/component.js +++ b/lib/nodes/addon/components/driver-amazonec2/component.js @@ -9,6 +9,7 @@ import Component from '@ember/component'; import NodeDriver from 'shared/mixins/node-driver'; import layout from './template'; import { INSTANCE_TYPES, nameFromResource, tagsFromResource, REGIONS } from 'shared/utils/amazon'; +import { randomStr } from 'shared/utils/util'; let RANCHER_GROUP = 'rancher-nodes'; @@ -84,21 +85,28 @@ export default Component.extend(NodeDriver, { }, actions: { + finishAndSelectCloudCredential(cred) { + if (cred) { + set(this, 'primaryResource.cloudCredentialId', get(cred, 'id')); + + this.send('awsLogin'); + } + }, + awsLogin(cb) { let self = this; - setProperties(this, { - 'errors': null, - 'config.accessKey': (get(this, 'config.accessKey') || '').trim(), - 'config.secretKey': (get(this, 'config.secretKey') || '').trim(), - }); + set(this, 'errors', null); let subnets = []; let rName = get(this, 'config.region'); + + // have to have something in there before we describe the request even though we are going to replace with the actual cred id let ec2 = new AWS.EC2({ - accessKeyId: get(this, 'config.accessKey'), - secretAccessKey: get(this, 'config.secretKey'), - region: rName, + accessKeyId: randomStr(), + secretAccessKey: randomStr(), + region: rName, + httpOptions: { cloudCredentialId: get(this, 'model.cloudCredentialId') }, }); let vpcNames = {}; @@ -110,7 +118,9 @@ export default Component.extend(NodeDriver, { errors.pushObject(err); set(this, 'errors', errors); - cb(); + if (cb && typeof cb === 'function') { + cb(); + } return; } @@ -126,7 +136,9 @@ export default Component.extend(NodeDriver, { errors.pushObject(err); set(this, 'errors', errors); - cb(); + if (cb && typeof cb === 'function') { + cb(); + } return; } @@ -154,7 +166,9 @@ export default Component.extend(NodeDriver, { 'allSubnets': subnets, 'step': 2, }); - cb(); + if (cb && typeof cb === 'function') { + cb(); + } }); }); }, @@ -164,14 +178,18 @@ export default Component.extend(NodeDriver, { if ( !get(this, 'selectedZone') ) { set(this, 'errors', ['Select an Availability Zone']); - cb(); + if (cb && typeof cb === 'function') { + cb(); + } return; } if ( !get(this, 'selectedSubnet') ) { set(this, 'errors', ['Select a VPC or Subnet']); - cb(); + if (cb && typeof cb === 'function') { + cb(); + } return; } @@ -185,7 +203,9 @@ export default Component.extend(NodeDriver, { ec2.describeSecurityGroups({ Filters: [filter] }, (err, data) => { if ( err ) { set(this, 'errors', [err]); - cb(); + if (cb && typeof cb === 'function') { + cb(); + } return; } @@ -242,7 +262,9 @@ export default Component.extend(NodeDriver, { setProperties(this, { step: 4, }); - cb(); + if (cb && typeof cb === 'function') { + cb(); + } }, }, @@ -257,11 +279,12 @@ export default Component.extend(NodeDriver, { set(this, 'config.tags', array.join(',')); }), - stepDidChange: function() { + + stepDidChange: observer('context.step', function() { scheduleOnce('afterRender', this, () => { document.body.scrollTop = document.body.scrollHeight; }); - }.observes('context.step'), + }), selectedZone: computed('config.{region,zone}', { get() { @@ -301,7 +324,7 @@ export default Component.extend(NodeDriver, { } }), - zoneChoices: function() { + zoneChoices: computed('allSubnets.@each.{zone}', function() { const choices = (get(this, 'allSubnets') || []).map((subnet) => { return get(subnet, 'zone'); }).sort().uniq(); @@ -315,9 +338,9 @@ export default Component.extend(NodeDriver, { } return choices; - }.property('allSubnets.@each.{zone}'), + }), - subnetChoices: function() { + subnetChoices: computed('selectedZone', 'allSubnets.@each.{subnetId,vpcId,zone}', function() { let out = []; let seenVpcs = []; @@ -350,7 +373,7 @@ export default Component.extend(NodeDriver, { }); return out.sortBy('sortKey'); - }.property('selectedZone', 'allSubnets.@each.{subnetId,vpcId,zone}'), + }), selectedSubnet: computed('config.{subnetId,vpcId}', { set(key, val) { @@ -390,7 +413,6 @@ export default Component.extend(NodeDriver, { }), bootstrap() { - let pref = get(this, 'prefs.amazonec2') || {}; let config = get(this, 'globalStore').createRecord({ type: 'amazonec2Config', region: 'us-west-2', @@ -398,8 +420,6 @@ export default Component.extend(NodeDriver, { securityGroup: '', zone: 'a', rootSize: '16', - accessKey: pref.accessKey || '', - secretKey: pref.secretKey || '', }); set(this, 'model.amazonec2Config', config); diff --git a/lib/nodes/addon/components/driver-amazonec2/template.hbs b/lib/nodes/addon/components/driver-amazonec2/template.hbs index ac77bae57..a26e5fd10 100644 --- a/lib/nodes/addon/components/driver-amazonec2/template.hbs +++ b/lib/nodes/addon/components/driver-amazonec2/template.hbs @@ -2,15 +2,15 @@
{{driverOptionsTitle}}
{{#accordion-list-item - title=(t 'nodeDriver.amazonec2.access.title') - detail=(t 'nodeDriver.amazonec2.access.detail') + title=(t "nodeDriver.amazonec2.access.title") + detail=(t "nodeDriver.amazonec2.access.detail") expandAll=expandAll expand=(action expandFn) expandOnInit=true }}
- + {{#if (eq step 1)}} {{#each zoneChoices as |choice|}} @@ -79,7 +73,7 @@
- + {{#if subnetChoices.length}} {{#each subnetChoices as |choice|}} {{#if choice.isVpc}} @@ -115,11 +109,11 @@ {{else}}
- + {{config.region}}{{config.zone}}
- + {{#if config.subnetId}} {{config.subnetId}} {{else}} @@ -141,10 +135,10 @@ {{/if}}
-
+
{{#accordion-list-item - title=(t 'nodeDriver.amazonec2.securityGroup.title') - detail=(t 'nodeDriver.amazonec2.securityGroup.detail') + title=(t "nodeDriver.amazonec2.securityGroup.title") + detail=(t "nodeDriver.amazonec2.securityGroup.detail") expandAll=expandAll expand=(action expandFn) expandOnInit=true @@ -154,23 +148,23 @@
{{#if (and settings.isRancher (not isCustomSecurityGroup))}} -

{{t 'nodeDriver.amazonec2.portHelp.link'}} {{t 'nodeDriver.amazonec2.portHelp.text'}}

+

{{t "nodeDriver.amazonec2.portHelp.link"}} {{t "nodeDriver.amazonec2.portHelp.text"}}

{{/if}}
{{#if isCustomSecurityGroup}} - {{#each allSecurityGroups as |choice|}} {{/each}} @@ -203,17 +197,17 @@ {{/if}}
-
+
{{#accordion-list-item - title=(t 'nodeDriver.amazonec2.instance.title') - detail=(t 'nodeDriver.amazonec2.instance.detail') + title=(t "nodeDriver.amazonec2.instance.title") + detail=(t "nodeDriver.amazonec2.instance.detail") expandAll=expandAll expand=(action expandFn) expandOnInit=true }}
- + {{new-select classNames="form-control" value=config.instanceType @@ -225,59 +219,59 @@
- +
{{input type="text" classNames="form-control" placeholder="" value=config.rootSize}} - {{t 'nodeDriver.amazonec2.rootSize.unit'}} + {{t "nodeDriver.amazonec2.rootSize.unit"}}
- - {{input type="text" classNames="form-control" placeholder=(t 'nodeDriver.amazonec2.ami.placeholder') value=config.ami}} + + {{input type="text" classNames="form-control" placeholder=(t "nodeDriver.amazonec2.ami.placeholder") value=config.ami}} {{#if settings.isRancher}}

- {{t 'nodeDriver.amazonec2.ami.rancherList'}} + {{t "nodeDriver.amazonec2.ami.rancherList"}}

{{/if}}
- - {{input type="text" classNames="form-control" placeholder=(t 'nodeDriver.amazonec2.sshUser.placeholder') value=config.sshUser}} + + {{input type="text" classNames="form-control" placeholder=(t "nodeDriver.amazonec2.sshUser.placeholder") value=config.sshUser}}
- - {{input type="text" classNames="form-control" value=config.iamInstanceProfile placeholder=(t 'nodeDriver.amazonec2.iam.placeholder')}} + + {{input type="text" classNames="form-control" value=config.iamInstanceProfile placeholder=(t "nodeDriver.amazonec2.iam.placeholder")}}
- +
- +
- +
- +
{{#if config.requestSpotInstance}}
- +
{{input type="text" classNames="form-control" placeholder="" value=config.spotPrice}} - {{t 'nodeDriver.amazonec2.spotPrice.unit'}} + {{t "nodeDriver.amazonec2.spotPrice.unit"}}
{{/if}} @@ -303,7 +297,7 @@ {{form-user-labels initialLabels=labelResource.labels - setLabels=(action 'setLabels') + setLabels=(action "setLabels") expandAll=expandAll expand=(action expandFn) }} diff --git a/lib/nodes/addon/components/driver-azure/component.js b/lib/nodes/addon/components/driver-azure/component.js index 7aa938d68..9849e3faf 100644 --- a/lib/nodes/addon/components/driver-azure/component.js +++ b/lib/nodes/addon/components/driver-azure/component.js @@ -46,6 +46,12 @@ export default Component.extend(NodeDriver, { }); }, + actions: { + finishAndSelectCloudCredential(credential) { + set(this, 'model.cloudCredentialId', get(credential, 'id')) + } + }, + evironmentChoiceObserver: observer('config.environment', function() { let environment = get(this, 'config.environment'); @@ -116,8 +122,6 @@ export default Component.extend(NodeDriver, { let config = get(this, 'globalStore').createRecord({ type: CONFIG, subscriptionId: '', - clientId: '', - clientSecret: '', openPort: ['6443/tcp', '2379/tcp', '2380/tcp', '8472/udp', '4789/udp', '10256/tcp', '10250/tcp', '10251/tcp', '10252/tcp'], }); @@ -146,18 +150,6 @@ export default Component.extend(NodeDriver, { errors.push('Name is required'); } - if (!get(this, 'config.subscriptionId')) { - errors.push('Subscription ID is required'); - } - - if (!get(this, 'config.clientId')) { - errors.push('Client ID is requried'); - } - - if (!get(this, 'config.clientSecret')) { - errors.push('Client Secret is requried'); - } - if (errors.length) { set(this, 'errors', errors.uniq()); diff --git a/lib/nodes/addon/components/driver-azure/template.hbs b/lib/nodes/addon/components/driver-azure/template.hbs index ec95f121a..ca052839c 100644 --- a/lib/nodes/addon/components/driver-azure/template.hbs +++ b/lib/nodes/addon/components/driver-azure/template.hbs @@ -1,147 +1,264 @@ {{#accordion-list showExpandAll=false as | al expandFn |}} -
{{driverOptionsTitle}}
+
+ + {{driverOptionsTitle}} + +
{{#accordion-list-item - title=(t 'nodeDriver.azure.placement.title') - detail=(t 'nodeDriver.azure.placement.detail') + title=(t "nodeDriver.azure.access.title") + detail=(t "nodeDriver.azure.access.detail") expandAll=expandAll expand=(action expandFn) expandOnInit=true + }} + {{form-auth-cloud-credential + driverName=driverName + errors=errros + primaryResource=primaryResource + cloudCredentials=cloudCredentials + finishAndSelectCloudCredential=(action "finishAndSelectCloudCredential") + progressStep=(action "finishAndSelectCloudCredential") + cancel=(action "cancel") + hideSave=true + }} + + {{/accordion-list-item}} + + {{#accordion-list-item + title=(t "nodeDriver.azure.placement.title") + detail=(t "nodeDriver.azure.placement.detail") + expandAll=expandAll + expand=(action expandFn) + expandOnInit=true }}
- - {{new-select classNames="form-control" content=environments optionLabelPath='value' value=config.environment}} + + {{new-select + classNames="form-control" + content=environments + optionLabelPath="value" + value=config.environment + }}
- - {{new-select classNames="form-control" content=regionChoices optionLabelPath='displayName' optionValuePath='name' value=config.location}} + + {{new-select + classNames="form-control" + content=regionChoices + optionLabelPath="displayName" + optionValuePath="name" + value=config.location + }}
- - {{input type="text" value=config.availabilitySet classNames="form-control" placeholder=(t 'nodeDriver.azure.availabilitySet.placeholder')}} + + {{input + type="text" + value=config.availabilitySet + classNames="form-control" + placeholder=(t "nodeDriver.azure.availabilitySet.placeholder") + }}
- - {{input type="text" value=config.resourceGroup classNames="form-control" placeholder=(t 'nodeDriver.azure.resourceGroup.placeholder')}} + + {{input + type="text" + value=config.resourceGroup + classNames="form-control" + placeholder=(t "nodeDriver.azure.resourceGroup.placeholder") + }}
{{/accordion-list-item}} {{#accordion-list-item - title=(t 'nodeDriver.azure.access.title') - detail=(t 'nodeDriver.azure.access.detail') + title=(t "nodeDriver.azure.network.title") + detail=(t "nodeDriver.azure.network.detail") expandAll=expandAll expand=(action expandFn) expandOnInit=true }}
- - {{input type="text" value=config.subscriptionId classNames="form-control" placeholder=(t 'nodeDriver.azure.subscriptionId.placeholder')}} + + {{input + type="text" + value=config.subnet + classNames="form-control" + placeholder=(t "nodeDriver.azure.subnet.placeholder") + }} +
+
+ + {{input + type="text" + value=config.subnetPrefix + classNames="form-control" + placeholder=(t "nodeDriver.azure.subnetPrefix.placeholder") + }}
- - {{input type="text" value=config.clientId classNames="form-control"}} + + {{input + type="text" + value=config.vnet + classNames="form-control" + placeholder=(t "nodeDriver.azure.vnet.placeholder") + }}
- - {{input type="password" value=config.clientSecret classNames="form-control"}} -
-
- {{/accordion-list-item}} - - {{#accordion-list-item - title=(t 'nodeDriver.azure.network.title') - detail=(t 'nodeDriver.azure.network.detail') - expandAll=expandAll - expand=(action expandFn) - expandOnInit=true - }} -
-
- - {{input type="text" value=config.subnet classNames="form-control" placeholder=(t 'nodeDriver.azure.subnet.placeholder')}} -
-
- - {{input type="text" value=config.subnetPrefix classNames="form-control" placeholder=(t 'nodeDriver.azure.subnetPrefix.placeholder')}} + + {{new-select + classNames="form-control" + content=publicIpChoices + optionLabelPath="name" + optionValuePath="value" + value=publicIpChoice + }}
- - {{input type="text" value=config.vnet classNames="form-control" placeholder=(t 'nodeDriver.azure.vnet.placeholder')}} + + {{input + type="text" + value=config.privateIpAddress + classNames="form-control" + placeholder=(t "nodeDriver.azure.privateIpAddress.placeholder") + }}
- - {{new-select classNames="form-control" content=publicIpChoices optionLabelPath='name' optionValuePath='value' value=publicIpChoice}} -
-
- -
-
- - {{input type="text" value=config.privateIpAddress classNames="form-control" placeholder=(t 'nodeDriver.azure.privateIpAddress.placeholder')}} -
-
- +
- {{input type="checkbox" checked=config.usePrivateIp disabled=privateSet}} + {{input + type="checkbox" + checked=config.usePrivateIp + disabled=privateSet + }}
{{/accordion-list-item}} {{#accordion-list-item - title=(t 'nodeDriver.azure.instance.title') - detail=(t 'nodeDriver.azure.instance.detail') + title=(t "nodeDriver.azure.instance.title") + detail=(t "nodeDriver.azure.instance.detail") expandAll=expandAll expand=(action expandFn) expandOnInit=true }}
- - {{input type="text" value=config.image classNames="form-control" placeholder=(t 'nodeDriver.azure.image.placeholder')}} + + {{input + type="text" + value=config.image + classNames="form-control" + placeholder=(t "nodeDriver.azure.image.placeholder") + }}
- - {{new-select classNames="form-control" content=sizeChoices optionLabelPath='value' optionGroupPath='group' value=config.size}} + + {{new-select + classNames="form-control" + content=sizeChoices + optionLabelPath="value" + optionGroupPath="group" + value=config.size + }}
- - {{input type="text" value=config.dockerPort classNames="form-control" placeholder=(t 'nodeDriver.azure.dockerPort.placeholder')}} + + {{input + type="text" + value=config.dockerPort + classNames="form-control" + placeholder=(t "nodeDriver.azure.dockerPort.placeholder") + }}
- - {{input type="text" value=openPorts classNames="form-control" placeholder=(t 'nodeDriver.azure.openPort.placeholder')}} + + {{input + type="text" + value=openPorts + classNames="form-control" + placeholder=(t "nodeDriver.azure.openPort.placeholder") + }}
- - {{input type="text" value=config.sshUser classNames="form-control" placeholder=(t 'nodeDriver.azure.sshUser.placeholder')}} + + {{input + type="text" + value=config.sshUser + classNames="form-control" + placeholder=(t "nodeDriver.azure.sshUser.placeholder") + }}
- - {{new-select classNames="form-control" content=storageTypeChoices optionLabelPath='name' optionValuePath='value' value=config.storageType}} + + {{new-select + classNames="form-control" + content=storageTypeChoices + optionLabelPath="name" + optionValuePath="value" + value=config.storageType + }}
{{/accordion-list-item}} -
{{templateOptionsTitle}}
+
+ + {{templateOptionsTitle}} + +
{{form-name-description model=model @@ -150,7 +267,7 @@ {{form-user-labels initialLabels=labelResource.labels - setLabels=(action 'setLabels') + setLabels=(action "setLabels") expandAll=expandAll expand=(action expandFn) }} @@ -160,6 +277,13 @@ showEngineUrl=showEngineUrl }} - {{top-errors errors=errors}} - {{save-cancel save="save" cancel="cancel" editing=editing}} + {{top-errors + errors=errors + }} + + {{save-cancel + save="save" + cancel="cancel" + editing=editing + }} {{/accordion-list}} diff --git a/lib/nodes/addon/components/driver-digitalocean/component.js b/lib/nodes/addon/components/driver-digitalocean/component.js index 9454d6238..25166e26c 100644 --- a/lib/nodes/addon/components/driver-digitalocean/component.js +++ b/lib/nodes/addon/components/driver-digitalocean/component.js @@ -31,30 +31,34 @@ const VALID_IMAGES = [ ]; export default Component.extend(NodeDriver, { - app: service(), + app: service(), layout, - driverName: 'digitalocean', - regionChoices: null, - model: null, + driverName: 'digitalocean', + regionChoices: null, + model: null, + step: 1, + sizeChoices: null, + imageChoices: null, + tags: null, - step: 1, - sizeChoices: null, - imageChoices: null, - tags: null, + config: alias('primaryResource.digitaloceanConfig'), - config: alias('model.digitaloceanConfig'), init() { this._super(...arguments); - const tags = get(this, 'config.tags'); - - if (tags) { - set(this, 'tags', tags.split(',')); - } + this.initTags(); }, actions: { + finishAndSelectCloudCredential(cred) { + if (cred) { + set(this, 'primaryResource.cloudCredentialId', get(cred, 'id')); + + this.send('getData'); + } + }, + getData(cb) { let promises = { regions: this.apiRequest('regions'), @@ -103,7 +107,9 @@ export default Component.extend(NodeDriver, { setProperties(this, { errors, }); - cb(); + if (cb && typeof cb === 'function') { + cb(); + } }); }, }, @@ -134,6 +140,14 @@ export default Component.extend(NodeDriver, { return out; }), + initTags() { + const tags = get(this, 'config.tags'); + + if (tags) { + set(this, 'tags', tags.split(',')); + } + }, + bootstrap() { let config = get(this, 'globalStore').createRecord({ type: 'digitaloceanConfig', @@ -143,15 +157,16 @@ export default Component.extend(NodeDriver, { sshUser: 'root' }); - const model = get(this, 'model'); + const primaryResource = get(this, 'primaryResource'); - set(model, 'digitaloceanConfig', config); + set(primaryResource, 'digitaloceanConfig', config); }, apiRequest(command, opt, out) { opt = opt || {}; - let url = `${ get(this, 'app.proxyEndpoint') }/`; + let url = `${ get(this, 'app.proxyEndpoint') }/`; + let cloudCredentialId = get(this, 'primaryResource.cloudCredentialId'); if ( opt.url ) { url += opt.url.replace(/^http[s]?\/\//, ''); @@ -163,8 +178,8 @@ export default Component.extend(NodeDriver, { return fetch(url, { headers: { - 'Accept': 'application/json', - 'X-Api-Auth-Header': `Bearer ${ get(this, 'config.accessToken') }`, + 'Accept': 'application/json', + 'x-api-cattleauth-header': `Bearer credID=${ cloudCredentialId } passwordField=accessToken`, }, }).then((res) => { let body = res.body; diff --git a/lib/nodes/addon/components/driver-digitalocean/template.hbs b/lib/nodes/addon/components/driver-digitalocean/template.hbs index 7830ee7b7..ed428086f 100644 --- a/lib/nodes/addon/components/driver-digitalocean/template.hbs +++ b/lib/nodes/addon/components/driver-digitalocean/template.hbs @@ -1,33 +1,40 @@
- {{#if (eq step 1)}} -
-
- - {{input type="password" value=config.accessToken classNames="form-control" placeholder=(t 'nodeDriver.digitalocean.accessToken.placeholder')}} -

{{t 'nodeDriver.digitalocean.accessToken.help' htmlSafe=true}}

-
-
- {{top-errors errors=errors}} - {{save-cancel - save="getData" - cancel="cancel" - createLabel="nodeDriver.digitalocean.authAccountButton" - savingLabel="generic.loading" - }} - {{else}} - {{#accordion-list showExpandAll=false as | al expandFn |}} + + {{#accordion-list showExpandAll=false as | al expandFn |}} + + {{#if (eq step 1)}} + {{#accordion-list-item + title=(t "nodeDriver.digitalocean.accessToken.label") + detail=(t "nodeDriver.digitalocean.accessToken.help" htmlSafe=true) + expandAll=expandAll + expand=(action expandFn) + expandOnInit=true + }} + {{form-auth-cloud-credential + driverName=driverName + errors=errros + primaryResource=primaryResource + cloudCredentials=cloudCredentials + finishAndSelectCloudCredential=(action "finishAndSelectCloudCredential") + progressStep=(action "getData") + cancel=(action "cancel") + createLabel="nodeDriver.digitalocean.authAccountButton" + }} + {{/accordion-list-item}} + {{top-errors errors=errors}} + {{else}}
{{driverOptionsTitle}}
{{#accordion-list-item - title=(t 'nodeDriver.digitalocean.droplet.title') - detail=(t 'nodeDriver.digitalocean.droplet.detail') - expandAll=expandAll - expand=(action expandFn) - expandOnInit=true + title=(t "nodeDriver.digitalocean.droplet.title") + detail=(t "nodeDriver.digitalocean.droplet.detail") + expandAll=expandAll + expand=(action expandFn) + expandOnInit=true }}
- +
- +
- +
- - {{input type="text" value=config.sshUser classNames="form-control" placeholder=(t 'nodeDriver.digitalocean.sshUser.placeholder')}} + + {{input type="text" value=config.sshUser classNames="form-control" placeholder=(t "nodeDriver.digitalocean.sshUser.placeholder")}}
@@ -87,9 +94,9 @@
{{form-value-array initialValues=tags - addActionLabel='nodeDriver.digitalocean.tags.addActionLabel' - valueLabel='nodeDriver.digitalocean.tags.valueLabel' - valuePlaceholder='nodeDriver.digitalocean.tags.placeholder' + addActionLabel="nodeDriver.digitalocean.tags.addActionLabel" + valueLabel="nodeDriver.digitalocean.tags.valueLabel" + valuePlaceholder="nodeDriver.digitalocean.tags.placeholder" changed=(action (mut tags)) }}
@@ -105,7 +112,7 @@ {{form-user-labels initialLabels=labelResource.labels - setLabels=(action 'setLabels') + setLabels=(action "setLabels") expandAll=expandAll expand=(action expandFn) }} @@ -114,9 +121,9 @@ machine=model showEngineUrl=showEngineUrl }} - {{/accordion-list}} - {{top-errors errors=errors}} - {{save-cancel save="save" cancel="cancel" editing=editing}} - {{/if}} + {{top-errors errors=errors}} + {{save-cancel save="save" cancel="cancel" editing=editing}} + {{/if}} + {{/accordion-list}}
diff --git a/lib/nodes/addon/components/driver-vmwarevsphere/component.js b/lib/nodes/addon/components/driver-vmwarevsphere/component.js index c7c719bb2..3a9825935 100644 --- a/lib/nodes/addon/components/driver-vmwarevsphere/component.js +++ b/lib/nodes/addon/components/driver-vmwarevsphere/component.js @@ -23,7 +23,7 @@ const stringsToParams = (params, str) => { } return params; -} +}; const paramsToStrings = (strs, param) => { if (param.value && param.key) { @@ -31,27 +31,35 @@ const paramsToStrings = (strs, param) => { } return strs; -} +}; const initialVAppOptions = { vappIpprotocol: '', vappIpallocationpolicy: '', vappTransport: '', vappProperty: [] +}; + +const getDefaultVappOptions = (networks) => { + return { + vappIpprotocol: 'IPv4', + vappIpallocationpolicy: 'fixedAllocated', + vappTransport: 'com.vmware.guestInfo', + vappProperty: networksToVappProperties(networks) + }; +}; + +const networksToVappProperties = (networks) => { + return networks.length === 0 + ? [] + : networks.reduce(networkToVappProperties, [ + `guestinfo.dns.servers=\${ dns:${ networks[0] } }`, + `guestinfo.dns.domains=\${ searchPath:${ networks[0] } }` + ]); } -const getDefaultVappOptions = (networks) => ({ - vappIpprotocol: 'IPv4', - vappIpallocationpolicy: 'fixedAllocated', - vappTransport: 'com.vmware.guestInfo', - vappProperty: networksToVappProperties(networks) -}) -const networksToVappProperties = (networks) => networks.length === 0 - ? [] - : networks.reduce(networkToVappProperties, [ - `guestinfo.dns.servers=\${dns:${ networks[0] }}`, - `guestinfo.dns.domains=\${searchPath:${ networks[0] }}` - ]) + + const networkToVappProperties = (props, network, i) => { const n = i.toString(); @@ -62,7 +70,7 @@ const networkToVappProperties = (props, network, i) => { ); return props; -} +}; const getInitialVappMode = (c) => { const vappProperty = c.vappProperty || [] @@ -89,7 +97,7 @@ const getInitialVappMode = (c) => { } return VAPP_MODE_MANUAL; -} +}; export default Component.extend(NodeDriver, { settings: service(), @@ -103,6 +111,7 @@ export default Component.extend(NodeDriver, { vappMode: VAPP_MODE_DISABLED, config: alias(`model.${ CONFIG }`), + init() { this._super(...arguments); this.initKeyValueParams('config.cfgparam', 'initParamArray'); @@ -116,6 +125,9 @@ export default Component.extend(NodeDriver, { }, vappPropertyChanged(array) { this.updateKeyValueParams('config.vappProperty', array); + }, + finishAndSelectCloudCredential(credential) { + set(this, 'model.cloudCredentialId', get(credential, 'id')) } }, diff --git a/lib/nodes/addon/components/driver-vmwarevsphere/template.hbs b/lib/nodes/addon/components/driver-vmwarevsphere/template.hbs index 610e62335..0684778b2 100644 --- a/lib/nodes/addon/components/driver-vmwarevsphere/template.hbs +++ b/lib/nodes/addon/components/driver-vmwarevsphere/template.hbs @@ -8,29 +8,18 @@ expand=(action expandFn) expandOnInit=true }} -
-
- - {{input type="text" class="form-control" value=config.vcenter placeholder=(t 'nodeDriver.vmwarevsphere.vcenter.placeholder')}} -
-
- - {{input-integer min=1 max=65535 class="form-control" value=config.vcenterPort}} -
-
-
-
- - {{input type="text" value=config.username classNames="form-control"}} -
+ {{form-auth-cloud-credential + driverName=driverName + errors=errros + primaryResource=model + cloudCredentials=cloudCredentials + finishAndSelectCloudCredential=(action "finishAndSelectCloudCredential") + progressStep=(action "finishAndSelectCloudCredential") + cancel=(action "cancel") + hideSave=true + }} -
- - {{input type="password" value=config.password classNames="form-control"}} -
-
-

{{t 'nodeDriver.vmwarevsphere.access.help'}}

{{/accordion-list-item}} {{#accordion-list-item @@ -66,7 +55,7 @@
{{t 'nodeDriver.vmwarevsphere.diskSize.unit'}}
- +
{{input type="text" value=config.cloudinit classNames="form-control" placeholder=(t 'nodeDriver.vmwarevsphere.cloudinit.placeholder')}} diff --git a/lib/nodes/addon/node-templates/route.js b/lib/nodes/addon/node-templates/route.js index e3c5c5f6c..22a2eb466 100644 --- a/lib/nodes/addon/node-templates/route.js +++ b/lib/nodes/addon/node-templates/route.js @@ -1,12 +1,14 @@ import { hash } from 'rsvp'; import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; -import { get } from '@ember/object'; export default Route.extend({ globalStore: service(), model() { - return hash({ nodeTemplates: get(this, 'globalStore').findAll('nodeTemplate') }); + return hash({ + nodeTemplates: this.globalStore.findAll('nodeTemplate'), + cloudCredential: this.globalStore.findAll('cloudcredential'), + }); }, }); diff --git a/lib/shared/addon/components/form-auth-cloud-credential/component.js b/lib/shared/addon/components/form-auth-cloud-credential/component.js new file mode 100644 index 000000000..877c4da3d --- /dev/null +++ b/lib/shared/addon/components/form-auth-cloud-credential/component.js @@ -0,0 +1,29 @@ +import Component from '@ember/component'; +import layout from './template'; +import { get, set } from '@ember/object'; + +export default Component.extend({ + layout, + showAddCloudCredential: null, + errors: null, + hideSave: false, + + createLabel: 'saveCancel.create', + savingLabel: 'generic.loading', + + actions: { + doneSavingCloudCredential(cred) { + if (cred) { + get(this, 'finishAndSelectCloudCredential')(cred) + + set(this, 'showAddCloudCredential', false); + } + }, + addCloudCredential() { + set(this, 'showAddCloudCredential', true); + }, + cancleNewCloudCredential() { + set(this, 'showAddCloudCredential', false); + }, + }, +}); diff --git a/lib/shared/addon/components/form-auth-cloud-credential/template.hbs b/lib/shared/addon/components/form-auth-cloud-credential/template.hbs new file mode 100644 index 000000000..0eaa68888 --- /dev/null +++ b/lib/shared/addon/components/form-auth-cloud-credential/template.hbs @@ -0,0 +1,70 @@ +
+
+ + +
+ + + + +
+
+
+ +
+ +{{#if showAddCloudCredential}} +
+ {{cru-cloud-credential + driverName=driverName + errors=errors + disableHeader=true + doneSavingCloudCredential=(action "doneSavingCloudCredential") + cancelAdd=(action "cancleNewCloudCredential") + }} +
+
+{{else}} + {{#unless hideSave}} + {{save-cancel + saveDisabled=(unless primaryResource.cloudCredentialId true) + save=progressStep + cancel=cancel + createLabel=createLabel + savingLabel=savingLabel + }} + {{/unless}} +{{/if}} \ No newline at end of file diff --git a/lib/shared/addon/mixins/modal-base.js b/lib/shared/addon/mixins/modal-base.js index d004ab990..7f2a90871 100644 --- a/lib/shared/addon/mixins/modal-base.js +++ b/lib/shared/addon/mixins/modal-base.js @@ -4,20 +4,11 @@ import Mixin from '@ember/object/mixin'; import C from 'ui/utils/constants'; export default Mixin.create({ - classNames: ['modal-container'/* , 'alert' */], + classNames: ['modal-container'], modalService: service('modal'), modalOpts: alias('modalService.modalOpts'), - // Focus does not want to focus on modal el here, dont know why but - // esc wont work if a modal doesnt have a focused element - // init() { - // this._super(...arguments); - // Ember.run.scheduleOnce('afterRender', ()=> { - // console.log('Focused: ', this.$()); - // this.$().focus(); - // }); - // }, - // + keyUp(e) { if (e.which === C.KEY.ESCAPE && this.escToClose()) { this.get('modalService').toggleModal(); diff --git a/lib/shared/addon/mixins/node-driver.js b/lib/shared/addon/mixins/node-driver.js index cc0a49203..d1b58209c 100644 --- a/lib/shared/addon/mixins/node-driver.js +++ b/lib/shared/addon/mixins/node-driver.js @@ -5,7 +5,7 @@ import Mixin from '@ember/object/mixin'; import NewOrEdit from 'shared/mixins/new-or-edit'; import ManageLabels from 'shared/mixins/manage-labels'; import { addAction } from 'ui/utils/add-view-action'; -import { get, set, computed } from '@ember/object'; +import { get, set, computed, setProperties } from '@ember/object'; import { ucFirst } from 'shared/utils/util'; import C from 'ui/utils/constants'; @@ -66,13 +66,15 @@ export default Mixin.create(NewOrEdit, ManageLabels, { router: service(), globalStore: service(), - driverName: null, - showEngineUrl: true, // On some drivers this isn't configurable + driverName: null, + showEngineUrl: true, // On some drivers this isn't configurable + model: null, + labelResource: alias('model'), - model: null, - labelResource: alias('model'), actions: { + finishAndSelectCloudCredential() {}, + addLabel: addAction('addLabel', '.key'), setLabels(labels) { @@ -94,13 +96,7 @@ export default Mixin.create(NewOrEdit, ManageLabels, { this._super(...arguments); if ( !get(this, 'editing') && typeof get(this, 'bootstrap') === 'function') { - if ( get(this, 'showEngineUrl') ) { - set(this, 'model.engineInstallURL', get(this, `settings.${ C.SETTING.ENGINE_URL }`) || ''); - set(this, 'model.engineRegistryMirror', []); - } else { - set(this, 'model.engineInstallURL', null); - set(this, 'model.engineRegistryMirror', []); - } + this.initEngineUrl(); this.bootstrap(); } @@ -111,6 +107,37 @@ export default Mixin.create(NewOrEdit, ManageLabels, { // Populate the appropriate *Config field with defaults for your driver }, + cloudCredentials: computed('model.cloudCredentialId', 'driverName', function() { + const { driverName } = this; + + return this.globalStore.all('cloudcredential').filter((cc) => { + switch (driverName) { + case 'digitalocean': + if (get(cc, 'isDO')) { + return cc; + } + break; + case 'amazonec2': + if (get(cc, 'isAmazon')) { + return cc; + } + break; + case 'azure': + if (get(cc, 'isAzure')) { + return cc; + } + break; + case 'vmwarevsphere': + if (get(cc, 'isVMware')) { + return cc; + } + break; + default: + return; + } + }); + }), + driverOptionsTitle: computed('driverName', 'intl.locale', function() { const intl = get(this, 'intl'); const driver = get(this, 'driverName'); @@ -131,6 +158,20 @@ export default Mixin.create(NewOrEdit, ManageLabels, { return intl.t('nodeDriver.templateOptions', { appName }); }), + initEngineUrl() { + let engineInstallURL = null; + let engineRegistryMirror = []; + + if ( get(this, 'showEngineUrl') ) { + engineInstallURL = get(this, `settings.${ C.SETTING.ENGINE_URL }`) || ''; + } + + setProperties(this, { + 'model.engineInstallURL': engineInstallURL, + 'model.engineRegistryMirror': engineRegistryMirror, + }); + }, + didInsertElement() { this._super(); diff --git a/lib/shared/app/components/form-auth-cloud-credential/component.js b/lib/shared/app/components/form-auth-cloud-credential/component.js new file mode 100644 index 000000000..9667e6701 --- /dev/null +++ b/lib/shared/app/components/form-auth-cloud-credential/component.js @@ -0,0 +1 @@ +export { default } from 'shared/components/form-auth-cloud-credential/component'; \ No newline at end of file diff --git a/translations/en-us.yaml b/translations/en-us.yaml index 3767a2e3e..3e489315c 100644 --- a/translations/en-us.yaml +++ b/translations/en-us.yaml @@ -643,6 +643,13 @@ catalogPage: notCompatible: Not Compatible alreadyDeployed: Already Deployed +cloudKeysPage: + addKey: Add Cloud Key + index: + table: + noData: There are no cloud keys yet + noMatch: No cloud keys match the current search + clusterCatalogPage: header: Catalogs @@ -4690,6 +4697,13 @@ formAnnotations: errors: invalidJSON: Annotation JSON format is invalid. topLevelValueInvalid: Annotation JSON top-level value must be an object. + +formAuthCloudCredential: + label: Cloud Credentials + selectCreds: + prompt: Select a cloud credential + + formJobConfig: title: Job Configuration detail: Configure the desired behavior of jobs. @@ -5182,6 +5196,35 @@ modalAddPayment: euro: "Euro (€)" dollar: "US Dollar ($)" +modalAddCloudKey: + header: Add Cloud Credential + type: Cloud Credential Type + typeSelect: + prompt: Choose a cloud credential provider type + amazonec2: + accessKey: + label: Access Key + placeholder: Your AWS access key + secretKey: + label: Secret Key + placeholder: Your AWS secret key + azure: + clientId: + label: Client ID + placeholder: Your Client ID + clientSecret: + label: Client Secret + placeholder: Your Client Secret + digitalocean: + accessToken: + label: Access Token + placeholder: Your DigitalOcean API access token + help: | + Paste in a Personal Access Token from the DigitalOcean + Applications & API screen + vmwarevsphere: + password: + label: Password modalContainerStop: header: "Are you sure you want to stop" @@ -5190,6 +5233,7 @@ modalContainerStop: label: Timeout button: Stop + modalConfirmDeactivate: header: Are you sure you want to protip: "ProTip: Hold the {key} key while clicking {isServiceButton} to bypass this confirmation." @@ -6735,6 +6779,7 @@ nav: globalDnsProviders: Providers multiClusterApps: Multi-Cluster Apps security: + cloudKeys: Cloud Keys tab: Security roles: Roles members: Members