From 1d01a73a8f0345f87e6ce772a802c9a84da814b4 Mon Sep 17 00:00:00 2001 From: John Cowen Date: Mon, 11 May 2020 16:37:11 +0100 Subject: [PATCH] UI: Improved Login/Logout flow inc SSO support (#7790) * 6 new components for new login/logout flow, plus SSO support UI Components: 1. AuthDialog: Wraps/orchestrates AuthForm and AuthProfile 2. AuthForm: Authorization form shown when logged out. 3. AuthProfile: Simple presentational component to show the users 'Profile' 4. OidcSelect: A 'select' component for selecting an OIDC provider, dynamically uses either a single select menu or multiple buttons depending on the amount of providers Data Components: 1. JwtSource: Given an OIDC provider URL this component will request a token from the provider and fire an donchange event when it has been retrieved. Used by TokenSource. 2. TokenSource: Given a oidc provider name or a Consul SecretID, TokenSource will use whichever method/API requests required to retrieve Consul ACL Token, which is emitted to the onchange event handler. Very basic README documentation included here, which is likely to be refined somewhat. * CSS required for new auth/SSO UI components * Remaining app code required to tie the new auth/SSO work together * CSS code required to help tie the auth/SSO work together * Test code in order to get current tests passing with new auth/SSO flow ..plus extremely basics/skipped rendering tests for the new components * Treat the secret received from the server as the truth Previously we've always treated what the user typed as the truth, this breaks down when using SSO as the user doesn't type anything to retrieve a token. Therefore we change this so that we use the secret in the API response as the truth. * Make sure removing an dom tree from a buffer only removes its own tree --- ui-v2/app/adapters/oidc-provider.js | 2 +- ui-v2/app/components/auth-dialog/README.mdx | 56 +++++++++ .../components/auth-dialog/chart.xstate.js | 34 +++++ ui-v2/app/components/auth-dialog/index.hbs | 40 ++++++ ui-v2/app/components/auth-dialog/index.js | 42 +++++++ ui-v2/app/components/auth-form/README.mdx | 18 +++ .../app/components/auth-form/chart.xstate.js | 55 +++++++++ ui-v2/app/components/auth-form/index.hbs | 100 +++++++++++++++ ui-v2/app/components/auth-form/index.js | 21 ++++ ui-v2/app/components/auth-profile/README.mdx | 20 +++ ui-v2/app/components/auth-profile/index.hbs | 9 ++ ui-v2/app/components/auth-profile/index.js | 5 + ui-v2/app/components/child-selector/index.hbs | 4 +- ui-v2/app/components/child-selector/index.js | 1 + ui-v2/app/components/dom-buffer/index.js | 5 +- ui-v2/app/components/form-component/index.js | 1 + .../app/components/hashicorp-consul/index.hbs | 88 +++++++------ .../app/components/hashicorp-consul/index.js | 116 ++---------------- ui-v2/app/components/jwt-source/README.mdx | 24 ++++ ui-v2/app/components/jwt-source/index.js | 31 +++++ .../components/oidc-select/chart.xstate.js | 16 +++ ui-v2/app/components/oidc-select/index.hbs | 46 +++++++ ui-v2/app/components/oidc-select/index.js | 20 +++ .../app/components/policy-selector/index.hbs | 2 +- ui-v2/app/components/role-form/index.hbs | 2 +- ui-v2/app/components/token-source/README.mdx | 32 +++++ .../components/token-source/chart.xstate.js | 30 +++++ ui-v2/app/components/token-source/index.hbs | 33 +++++ ui-v2/app/components/token-source/index.js | 36 ++++++ ui-v2/app/controllers/application.js | 62 +++++++++- ui-v2/app/routes/application.js | 38 +----- .../services/data-source/protocols/http.js | 17 +++ ui-v2/app/services/data-source/service.js | 16 ++- ui-v2/app/services/dom-buffer.js | 38 ++++-- .../base/components/form-elements/index.scss | 1 + .../base/components/form-elements/layout.scss | 4 + .../base/components/form-elements/skin.scss | 17 +-- .../app/styles/base/icons/base-variables.scss | 4 +- .../styles/base/icons/icon-placeholders.scss | 20 +++ ui-v2/app/styles/components/app-view.scss | 16 ++- .../styles/components/app-view/layout.scss | 3 + ui-v2/app/styles/components/auth-form.scss | 4 + .../styles/components/auth-form/index.scss | 2 + .../styles/components/auth-form/layout.scss | 34 +++++ .../app/styles/components/auth-form/skin.scss | 12 ++ ui-v2/app/styles/components/auth-modal.scss | 4 + .../styles/components/auth-modal/index.scss | 7 ++ .../styles/components/auth-modal/layout.scss | 12 ++ .../styles/components/auth-modal/skin.scss | 7 ++ ui-v2/app/styles/components/buttons.scss | 8 +- .../styles/components/empty-state/skin.scss | 3 + .../app/styles/components/form-elements.scss | 29 ++--- ui-v2/app/styles/components/index.scss | 3 + .../components/main-nav-horizontal/index.scss | 13 +- .../components/main-nav-horizontal/skin.scss | 3 - ui-v2/app/styles/components/oidc-select.scss | 4 + .../styles/components/oidc-select/index.scss | 8 ++ .../styles/components/oidc-select/layout.scss | 7 ++ .../styles/components/oidc-select/skin.scss | 30 +++++ ui-v2/app/styles/core/typography.scss | 3 +- ui-v2/app/styles/variables/index.scss | 5 + ui-v2/app/templates/application.hbs | 10 +- .../app/templates/dc/acls/-authorization.hbs | 34 +++-- ui-v2/app/templates/error.hbs | 65 ++++++++-- ui-v2/app/utils/get-environment.js | 3 + ui-v2/config/environment.js | 14 ++- ui-v2/lib/startup/index.js | 3 + ui-v2/lib/startup/templates/head.html.js | 3 +- ui-v2/node-tests/config/environment.js | 14 +++ ui-v2/public/oidc/callback | 19 +++ ui-v2/server/index.js | 26 ++++ .../policies/as-many/add-existing.feature | 4 +- .../acls/roles/as-many/add-existing.feature | 4 +- ui-v2/tests/acceptance/dc/error.feature | 2 +- .../acceptance/dc/services/error.feature | 4 +- .../dc/services/instances/error.feature | 2 +- .../{dc/acls/tokens => }/login-errors.feature | 14 +-- .../{dc/acls/tokens => }/login.feature | 11 +- .../acceptance/steps/login-errors-steps.js | 10 ++ ui-v2/tests/acceptance/steps/login-steps.js | 10 ++ ui-v2/tests/acceptance/token-header.feature | 7 +- .../components/auth-dialog-test.js | 26 ++++ .../components/auth-profile-test.js | 24 ++++ .../integration/components/jwt-source-test.js | 17 +++ .../components/oidc-select-test.js | 26 ++++ .../serializers/oidc-provider-test.js | 75 +++++++++++ ui-v2/tests/pages.js | 16 ++- ui-v2/tests/pages/components/auth-form.js | 6 + ui-v2/tests/pages/components/page.js | 77 +++++++----- ui-v2/tests/pages/dc/acls/edit.js | 3 +- ui-v2/tests/pages/dc/acls/policies/edit.js | 6 +- ui-v2/tests/pages/dc/acls/roles/edit.js | 6 +- ui-v2/tests/pages/dc/acls/tokens/edit.js | 6 +- ui-v2/tests/pages/dc/intentions/edit.js | 3 +- ui-v2/tests/pages/dc/kv/edit.js | 2 +- ui-v2/tests/pages/dc/nspaces/edit.js | 6 +- ui-v2/tests/pages/dc/services/index.js | 3 +- ui-v2/tests/unit/models/oidc-provider-test.js | 13 ++ .../unit/serializers/oidc-provider-test.js | 23 ++++ 99 files changed, 1576 insertions(+), 344 deletions(-) create mode 100644 ui-v2/app/components/auth-dialog/README.mdx create mode 100644 ui-v2/app/components/auth-dialog/chart.xstate.js create mode 100644 ui-v2/app/components/auth-dialog/index.hbs create mode 100644 ui-v2/app/components/auth-dialog/index.js create mode 100644 ui-v2/app/components/auth-form/README.mdx create mode 100644 ui-v2/app/components/auth-form/chart.xstate.js create mode 100644 ui-v2/app/components/auth-form/index.hbs create mode 100644 ui-v2/app/components/auth-form/index.js create mode 100644 ui-v2/app/components/auth-profile/README.mdx create mode 100644 ui-v2/app/components/auth-profile/index.hbs create mode 100644 ui-v2/app/components/auth-profile/index.js create mode 100644 ui-v2/app/components/jwt-source/README.mdx create mode 100644 ui-v2/app/components/jwt-source/index.js create mode 100644 ui-v2/app/components/oidc-select/chart.xstate.js create mode 100644 ui-v2/app/components/oidc-select/index.hbs create mode 100644 ui-v2/app/components/oidc-select/index.js create mode 100644 ui-v2/app/components/token-source/README.mdx create mode 100644 ui-v2/app/components/token-source/chart.xstate.js create mode 100644 ui-v2/app/components/token-source/index.hbs create mode 100644 ui-v2/app/components/token-source/index.js create mode 100644 ui-v2/app/styles/components/auth-form.scss create mode 100644 ui-v2/app/styles/components/auth-form/index.scss create mode 100644 ui-v2/app/styles/components/auth-form/layout.scss create mode 100644 ui-v2/app/styles/components/auth-form/skin.scss create mode 100644 ui-v2/app/styles/components/auth-modal.scss create mode 100644 ui-v2/app/styles/components/auth-modal/index.scss create mode 100644 ui-v2/app/styles/components/auth-modal/layout.scss create mode 100644 ui-v2/app/styles/components/auth-modal/skin.scss create mode 100644 ui-v2/app/styles/components/oidc-select.scss create mode 100644 ui-v2/app/styles/components/oidc-select/index.scss create mode 100644 ui-v2/app/styles/components/oidc-select/layout.scss create mode 100644 ui-v2/app/styles/components/oidc-select/skin.scss create mode 100644 ui-v2/public/oidc/callback create mode 100644 ui-v2/server/index.js rename ui-v2/tests/acceptance/{dc/acls/tokens => }/login-errors.feature (82%) rename ui-v2/tests/acceptance/{dc/acls/tokens => }/login.feature (64%) create mode 100644 ui-v2/tests/acceptance/steps/login-errors-steps.js create mode 100644 ui-v2/tests/acceptance/steps/login-steps.js create mode 100644 ui-v2/tests/integration/components/auth-dialog-test.js create mode 100644 ui-v2/tests/integration/components/auth-profile-test.js create mode 100644 ui-v2/tests/integration/components/jwt-source-test.js create mode 100644 ui-v2/tests/integration/components/oidc-select-test.js create mode 100644 ui-v2/tests/integration/serializers/oidc-provider-test.js create mode 100644 ui-v2/tests/pages/components/auth-form.js create mode 100644 ui-v2/tests/unit/models/oidc-provider-test.js create mode 100644 ui-v2/tests/unit/serializers/oidc-provider-test.js diff --git a/ui-v2/app/adapters/oidc-provider.js b/ui-v2/app/adapters/oidc-provider.js index 7d1da6df3c8a..f2b65da2e220 100644 --- a/ui-v2/app/adapters/oidc-provider.js +++ b/ui-v2/app/adapters/oidc-provider.js @@ -33,7 +33,7 @@ export default Adapter.extend({ ${{ ...Namespace(ns), AuthMethod: id, - RedirectURI: `${this.env.var('CONSUL_BASE_UI_URL')}/torii/redirect.html`, + RedirectURI: `${this.env.var('CONSUL_BASE_UI_URL')}/oidc/callback`, }} `; }, diff --git a/ui-v2/app/components/auth-dialog/README.mdx b/ui-v2/app/components/auth-dialog/README.mdx new file mode 100644 index 000000000000..676d55379661 --- /dev/null +++ b/ui-v2/app/components/auth-dialog/README.mdx @@ -0,0 +1,56 @@ +## AuthDialog + +```handlebars + + {{#let components.AuthForm components.AuthProfile as |AuthForm AuthProfile|}} + + Here's the login form: + + + + Here's your profile: + + + Contact your administrator for login credentials. + +{{#if (env 'CONSUL_SSO_ENABLED')}} + + {{#if (gt providers.length 0)}} +

+ or +

+ {{/if}} + +{{/if}} + + + + + \ No newline at end of file diff --git a/ui-v2/app/components/auth-form/index.js b/ui-v2/app/components/auth-form/index.js new file mode 100644 index 000000000000..c6a95e277591 --- /dev/null +++ b/ui-v2/app/components/auth-form/index.js @@ -0,0 +1,21 @@ +import Component from '@ember/component'; + +import chart from './chart.xstate'; + +export default Component.extend({ + tagName: '', + onsubmit: function(e) {}, + onchange: function(e) {}, + init: function() { + this._super(...arguments); + this.chart = chart; + }, + actions: { + hasValue: function(context, event, meta) { + return this.value !== '' && typeof this.value !== 'undefined'; + }, + focus: function() { + this.input.focus(); + }, + }, +}); diff --git a/ui-v2/app/components/auth-profile/README.mdx b/ui-v2/app/components/auth-profile/README.mdx new file mode 100644 index 000000000000..f4fa59771d18 --- /dev/null +++ b/ui-v2/app/components/auth-profile/README.mdx @@ -0,0 +1,20 @@ +## AuthProfile + +```handlebars + +``` + +A straightforward partial-like component for rendering a user profile. + +### Arguments + +| Argument | Type | Default | Description | +| --- | --- | --- | --- | +| `item` | `Object` | | A Consul shaped token object (currently only requires an AccessorID property to be set | + +### See + +- [Component Source Code](./index.js) +- [Template Source Code](./index.hbs) + +--- diff --git a/ui-v2/app/components/auth-profile/index.hbs b/ui-v2/app/components/auth-profile/index.hbs new file mode 100644 index 000000000000..6f46fd37cbd9 --- /dev/null +++ b/ui-v2/app/components/auth-profile/index.hbs @@ -0,0 +1,9 @@ +
+
+ My ACL Token
+ AccessorID +
+
+ {{substr item.AccessorID -8}} +
+
\ No newline at end of file diff --git a/ui-v2/app/components/auth-profile/index.js b/ui-v2/app/components/auth-profile/index.js new file mode 100644 index 000000000000..4798652642ba --- /dev/null +++ b/ui-v2/app/components/auth-profile/index.js @@ -0,0 +1,5 @@ +import Component from '@ember/component'; + +export default Component.extend({ + tagName: '', +}); diff --git a/ui-v2/app/components/child-selector/index.hbs b/ui-v2/app/components/child-selector/index.hbs index 51fd38ef93ab..1b13235f0e6b 100644 --- a/ui-v2/app/components/child-selector/index.hbs +++ b/ui-v2/app/components/child-selector/index.hbs @@ -1,3 +1,4 @@ +
{{yield}} {{yield}}
\ No newline at end of file diff --git a/ui-v2/app/components/child-selector/index.js b/ui-v2/app/components/child-selector/index.js index cf01b8138a8e..c1f58ef5b8ac 100644 --- a/ui-v2/app/components/child-selector/index.js +++ b/ui-v2/app/components/child-selector/index.js @@ -8,6 +8,7 @@ import WithListeners from 'consul-ui/mixins/with-listeners'; export default Component.extend(SlotsMixin, WithListeners, { onchange: function() {}, + tagName: '', error: function() {}, type: '', diff --git a/ui-v2/app/components/dom-buffer/index.js b/ui-v2/app/components/dom-buffer/index.js index 63c4d62f8339..eb2706e88164 100644 --- a/ui-v2/app/components/dom-buffer/index.js +++ b/ui-v2/app/components/dom-buffer/index.js @@ -9,10 +9,11 @@ export default Component.extend({ }, didInsertElement: function() { this._super(...arguments); - this.buffer.add(this.getBufferName(), this.element); + this._element = this.buffer.add(this.getBufferName(), this.element); }, didDestroyElement: function() { this._super(...arguments); - this.buffer.remove(this.getBufferName()); + this.buffer.remove(this.getBufferName(), this._element); + this._element = null; }, }); diff --git a/ui-v2/app/components/form-component/index.js b/ui-v2/app/components/form-component/index.js index 0be3328b03f0..520cb2148e09 100644 --- a/ui-v2/app/components/form-component/index.js +++ b/ui-v2/app/components/form-component/index.js @@ -6,6 +6,7 @@ import WithListeners from 'consul-ui/mixins/with-listeners'; // match anything that isn't a [ or ] into multiple groups const propRe = /([^[\]])+/g; export default Component.extend(WithListeners, SlotsMixin, { + tagName: '', onreset: function() {}, onchange: function() {}, onerror: function() {}, diff --git a/ui-v2/app/components/hashicorp-consul/index.hbs b/ui-v2/app/components/hashicorp-consul/index.hbs index cbf153e0cd34..2ca5f255ed59 100644 --- a/ui-v2/app/components/hashicorp-consul/index.hbs +++ b/ui-v2/app/components/hashicorp-consul/index.hbs @@ -1,4 +1,4 @@ - +
Consul @@ -100,7 +100,7 @@ @@ -172,5 +185,4 @@

Consul {{env 'CONSUL_VERSION'}}

Documentation {{{concat ''}}} - - \ No newline at end of file + \ No newline at end of file diff --git a/ui-v2/app/components/hashicorp-consul/index.js b/ui-v2/app/components/hashicorp-consul/index.js index 6a6767a673ba..72f5f9f90e14 100644 --- a/ui-v2/app/components/hashicorp-consul/index.js +++ b/ui-v2/app/components/hashicorp-consul/index.js @@ -1,19 +1,12 @@ import Component from '@ember/component'; import { inject as service } from '@ember/service'; -import { get, set, computed } from '@ember/object'; -import { getOwner } from '@ember/application'; +import { computed } from '@ember/object'; export default Component.extend({ dom: service('dom'), - env: service('env'), - feedback: service('feedback'), - router: service('router'), - http: service('repository/type/event-source'), - client: service('client/http'), - store: service('store'), - settings: service('settings'), didInsertElement: function() { + this._super(...arguments); this.dom.root().classList.remove('template-with-vertical-menu'); }, // TODO: Right now this is the only place where we need permissions @@ -25,108 +18,15 @@ export default Component.extend({ }) !== 'undefined' ); }), - forwardForACL: function(token) { - let routeName = this.router.currentRouteName; - const route = getOwner(this).lookup(`route:${routeName}`); - // a null AccessorID means we are in legacy mode - // take the user to the legacy acls - // otherwise just refresh the page - if (get(token, 'AccessorID') === null) { - // returning false for a feedback action means even though - // its successful, please skip this notification and don't display it - return route.transitionTo('dc.acls'); - } else { - // TODO: Ideally we wouldn't need to use env() at a component level - // transitionTo should probably remove it instead if NSPACES aren't enabled - if (this.env.var('CONSUL_NSPACES_ENABLED') && get(token, 'Namespace') !== this.nspace.Name) { - if (!routeName.startsWith('nspace')) { - routeName = `nspace.${routeName}`; - } - const nspace = get(token, 'Namespace'); - // you potentially have a new namespace - if (typeof nspace !== 'undefined') { - return route.transitionTo(`${routeName}`, `~${nspace}`, this.dc.Name); - } - // you are logging out, just refresh - return route.refresh(); - } else { - if (route.routeName === 'dc.acls.index') { - return route.transitionTo('dc.acls.tokens.index'); - } - return route.refresh(); - } - } - }, actions: { - send: function(el, method, ...rest) { - const component = this.dom.component(el); - component.actions[method].apply(component, rest || []); + keypressClick: function(e) { + e.target.dispatchEvent(new MouseEvent('click')); }, - changeToken: function(token = {}) { - const prev = this.token; - if (token === '') { - token = {}; - } - set(this, 'token', token); - // if this is just the initial 'find out what the current token is' - // then don't do anything - if (typeof prev === 'undefined') { - return; - } - let notification; - let action = () => this.forwardForACL(token); - switch (true) { - case get(this, 'token.AccessorID') === null && get(this, 'token.SecretID') === null: - // 'everything is null, 403 this needs deleting' token - this.settings.delete('token'); - return; - case get(prev, 'AccessorID') === null && get(prev, 'SecretID') === null: - // we just had an 'everything is null, this needs deleting' token - // reject and break so this acts differently to just logging out - action = () => Promise.reject({}); - notification = 'authorize'; - break; - case typeof get(prev, 'AccessorID') !== 'undefined' && - typeof get(this, 'token.AccessorID') !== 'undefined': - // change of both Accessor and Secret, means use - notification = 'use'; - break; - case get(this, 'token.AccessorID') === null && - typeof get(this, 'token.SecretID') !== 'undefined': - // legacy login, don't do anything as we don't use self for auth here but the endpoint itself - // self is successful, but skip this notification and don't display it - return this.forwardForACL(token); - case typeof get(prev, 'AccessorID') === 'undefined' && - typeof get(this, 'token.AccessorID') !== 'undefined': - // normal login - notification = 'authorize'; - break; - case (typeof get(prev, 'AccessorID') !== 'undefined' || get(prev, 'AccessorID') === null) && - typeof get(this, 'token.AccessorID') === 'undefined': - //normal logout - notification = 'logout'; - break; - } - this.actions.reauthorize.apply(this, [ - { - type: notification, - action: action, - }, - ]); + open: function() { + this.authForm.focus(); }, - reauthorize: function(e) { - this.client.abort(); - this.http.resetCache(); - this.store.init(); - const type = get(e, 'type'); - this.feedback.execute( - e.action, - type, - function(type, e) { - return type; - }, - {} - ); + close: function() { + this.authForm.reset(); }, change: function(e) { const win = this.dom.viewport(); diff --git a/ui-v2/app/components/jwt-source/README.mdx b/ui-v2/app/components/jwt-source/README.mdx new file mode 100644 index 000000000000..51b1a12f2293 --- /dev/null +++ b/ui-v2/app/components/jwt-source/README.mdx @@ -0,0 +1,24 @@ +## JwtSource + +```handlebars + +``` + +### Arguments + +| Argument | Type | Default | Description | +| --- | --- | --- | --- | +| `src` | `String` | | The source to subscribe to updates to, this should map to a string based URI | +| `onchange` | `Function` | | The action to fire when the data changes. Emits an Event-like object with a `data` property containing the jwt data, in this case the autorizationCode and the status | +| `onerror` | `Function` | | The action to fire when an error occurs. Emits ErrorEvent object with an `error` property containing the Error. | + +This component will go through the steps of requesting a JWT token from a third party oauth provider. `src` should contain the full URL of the authorization URL for the 3rd party provider. Once the user has logged into the 3rd party provider the `onchange` event will be fired containing an event-like object whose data contains the JWT information. + +The component need only be place into the DOM in order to begin the OAuth dance. + +### See + +- [Component Source Code](./index.js) +- [Template Source Code](./index.hbs) + +--- diff --git a/ui-v2/app/components/jwt-source/index.js b/ui-v2/app/components/jwt-source/index.js new file mode 100644 index 000000000000..da84dc672caa --- /dev/null +++ b/ui-v2/app/components/jwt-source/index.js @@ -0,0 +1,31 @@ +import Component from '@ember/component'; +import { inject as service } from '@ember/service'; +import { fromPromise } from 'consul-ui/utils/dom/event-source'; + +export default Component.extend({ + repo: service('repository/oidc-provider'), + dom: service('dom'), + tagName: '', + onchange: function(e) {}, + onerror: function(e) {}, + init: function() { + this._super(...arguments); + this._listeners = this.dom.listeners(); + }, + willDestroy: function() { + this._super(...arguments); + this.repo.close(); + this._listeners.remove(); + }, + didInsertElement: function() { + if (this.source) { + this.source.close(); + } + // TODO: Could this use once? Double check but I don't think it can + this.source = fromPromise(this.repo.findCodeByURL(this.src)); + this._listeners.add(this.source, { + message: e => this.onchange(e), + error: e => this.onerror(e), + }); + }, +}); diff --git a/ui-v2/app/components/oidc-select/chart.xstate.js b/ui-v2/app/components/oidc-select/chart.xstate.js new file mode 100644 index 000000000000..08e65961d8b8 --- /dev/null +++ b/ui-v2/app/components/oidc-select/chart.xstate.js @@ -0,0 +1,16 @@ +export default { + id: 'oidc-select', + initial: 'loading', + states: { + loaded: {}, + loading: { + on: { + SUCCESS: [ + { + target: 'loaded', + }, + ], + }, + }, + }, +}; diff --git a/ui-v2/app/components/oidc-select/index.hbs b/ui-v2/app/components/oidc-select/index.hbs new file mode 100644 index 000000000000..99b173be5fba --- /dev/null +++ b/ui-v2/app/components/oidc-select/index.hbs @@ -0,0 +1,46 @@ + + + +
+ {{#if (lt items.length 3)}} +
    + {{#each items as |item|}} +
  • + +
  • + {{/each}} +
+ {{else}} + {{#let (or provider (object-at 0 items)) as |item|}} + + + {{/let}} + {{/if}} +
+
+ +
+
+
\ No newline at end of file diff --git a/ui-v2/app/components/oidc-select/index.js b/ui-v2/app/components/oidc-select/index.js new file mode 100644 index 000000000000..a0b5b164d9ae --- /dev/null +++ b/ui-v2/app/components/oidc-select/index.js @@ -0,0 +1,20 @@ +import Component from '@ember/component'; +import chart from './chart.xstate'; + +export default Component.extend({ + tagName: '', + onchange: function() {}, + onerror: function() {}, + init: function() { + this._super(...arguments); + this.chart = chart; + }, + didReceiveAttrs: function() { + // This gets called no matter which attr has changed + // such as disabled, thing is once we are in loaded state + // it doesn't do anything anymore + if (typeof this.items !== 'undefined') { + this.dispatch('SUCCESS'); + } + }, +}); diff --git a/ui-v2/app/components/policy-selector/index.hbs b/ui-v2/app/components/policy-selector/index.hbs index 8b702a2b86f6..6e2b7198acc2 100644 --- a/ui-v2/app/components/policy-selector/index.hbs +++ b/ui-v2/app/components/policy-selector/index.hbs @@ -1,4 +1,4 @@ - + {{yield}} Apply an existing policy diff --git a/ui-v2/app/components/role-form/index.hbs b/ui-v2/app/components/role-form/index.hbs index c5a7ecaa1298..460c6d6904a1 100644 --- a/ui-v2/app/components/role-form/index.hbs +++ b/ui-v2/app/components/role-form/index.hbs @@ -1,5 +1,5 @@ {{yield}} -
+