diff --git a/addons/rose/addon/components/rose/form/index.hbs b/addons/rose/addon/components/rose/form/index.hbs index 80da2b7de..e9d8b5527 100644 --- a/addons/rose/addon/components/rose/form/index.hbs +++ b/addons/rose/addon/components/rose/form/index.hbs @@ -21,7 +21,6 @@ }} {{yield (hash - radioGroup=(component 'rose/form/radio/group' disabled=this.disabled) checkbox=(component 'rose/form/checkbox' disabled=this.disabled) checkboxGroup=(component 'rose/form/checkbox/group' disabled=this.disabled diff --git a/addons/rose/addon/components/rose/form/radio/group/index.hbs b/addons/rose/addon/components/rose/form/radio/group/index.hbs deleted file mode 100644 index 129aa7901..000000000 --- a/addons/rose/addon/components/rose/form/radio/group/index.hbs +++ /dev/null @@ -1,17 +0,0 @@ -{{! - Copyright (c) HashiCorp, Inc. - SPDX-License-Identifier: BUSL-1.1 -}} - -{{yield - (hash - radio=(component - 'rose/form/radio/radio' - name=@name - selectedValue=@selectedValue - disabled=@disabled - changed=@changed - inline=@inline - ) - ) -}} \ No newline at end of file diff --git a/addons/rose/addon/components/rose/form/radio/radio/index.hbs b/addons/rose/addon/components/rose/form/radio/radio/index.hbs deleted file mode 100644 index 1da2f8f69..000000000 --- a/addons/rose/addon/components/rose/form/radio/radio/index.hbs +++ /dev/null @@ -1,43 +0,0 @@ -{{! - Copyright (c) HashiCorp, Inc. - SPDX-License-Identifier: BUSL-1.1 -}} - -
- - - - - - {{yield}} - -
\ No newline at end of file diff --git a/addons/rose/addon/components/rose/form/radio/radio/index.js b/addons/rose/addon/components/rose/form/radio/radio/index.js deleted file mode 100644 index 722c1ac0d..000000000 --- a/addons/rose/addon/components/rose/form/radio/radio/index.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -import Component from '@glimmer/component'; -import { generateComponentID } from '../../../../../utilities/component-auto-id'; - -export default class RoseFormRadioRadioComponent extends Component { - // =attributes - className = 'rose-form-radio'; - id = generateComponentID(); -} diff --git a/addons/rose/app/components/rose/form/radio/card.js b/addons/rose/app/components/rose/form/radio/card.js deleted file mode 100644 index ebaf29376..000000000 --- a/addons/rose/app/components/rose/form/radio/card.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -export { default } from 'rose/components/rose/form/radio/card'; diff --git a/addons/rose/app/components/rose/form/radio/group.js b/addons/rose/app/components/rose/form/radio/group.js deleted file mode 100644 index 26cbe09ee..000000000 --- a/addons/rose/app/components/rose/form/radio/group.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -export { default } from 'rose/components/rose/form/radio/group'; diff --git a/addons/rose/app/components/rose/form/radio/radio.js b/addons/rose/app/components/rose/form/radio/radio.js deleted file mode 100644 index 4ccbb649e..000000000 --- a/addons/rose/app/components/rose/form/radio/radio.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -export { default } from 'rose/components/rose/form/radio/radio'; diff --git a/addons/rose/app/styles/rose/components/form/_index.scss b/addons/rose/app/styles/rose/components/form/_index.scss index 2fbe85210..41d0c795a 100644 --- a/addons/rose/app/styles/rose/components/form/_index.scss +++ b/addons/rose/app/styles/rose/components/form/_index.scss @@ -9,5 +9,4 @@ @import 'input'; @import 'checkbox'; @import 'select'; -@import 'radio'; @import 'fieldset'; diff --git a/addons/rose/app/styles/rose/components/form/_radio.scss b/addons/rose/app/styles/rose/components/form/_radio.scss deleted file mode 100644 index 6a6bfb0e2..000000000 --- a/addons/rose/app/styles/rose/components/form/_radio.scss +++ /dev/null @@ -1,184 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -@use '../../variables/sizing'; -@use '../../utilities/type'; -@use '../../variables/typography'; - -$radio-svg: ( - 'default': - "", - 'hover': - "", - 'focus': - "", - 'checked': - "", - 'disabled': - "", - 'disabled-checked': - "", - 'error': - "", - 'error-checked': - "", -); -$data-svg-prefix: 'data:image/svg+xml;utf-8,'; - -.rose-form-radio { - &.inline { - display: inline-block; - - .rose-form-radio-label { - margin-right: sizing.rems(s); - } - } - @include type.type(m); - - position: relative; - margin-bottom: sizing.rems(xs); - color: var(--ui-gray); - - .rose-form-radio-label { - display: flex; - align-items: center; - - .rose-form-helper-text { - font-weight: normal; - margin-left: sizing.rems(xxs); - margin-bottom: 0; - } - - @include type.type(s, semibold); - - color: currentcolor; - padding-left: sizing.rems(l); - padding-top: sizing.rems(xxxs); - - .rose-icon { - margin-right: sizing.rems(xxs); - margin-left: sizing.rems(xxs); - } - - &::before { - position: absolute; - top: sizing.rems(xs); - left: 0; - display: block; - width: sizing.rems(m); - height: sizing.rems(m); - pointer-events: none; - content: ' '; - } - - &::after { - position: absolute; - top: -#{sizing.rems(xxxxs)}; - left: -#{sizing.rems(xxxs)}; - display: block; - width: sizing.rems(l); - height: sizing.rems(l); - transform: scale(2); - pointer-events: none; - content: ' '; - background: no-repeat 50%/50% 50%; - background-image: url($data-svg-prefix + map-get($radio-svg, 'default')); - } - } - - .rose-form-radio-field { - position: absolute; - left: 0; - z-index: -1; - width: sizing.rems(m); - height: sizing.rems(m); - opacity: 0; - - &:not(:disabled) { - &:hover { - ~ .rose-form-radio-label { - &::after { - background-image: url($data-svg-prefix + map-get( - $radio-svg, - 'hover' - )); - } - } - } - - &:focus { - ~ .rose-form-radio-label { - &::after { - background-image: url($data-svg-prefix + map-get( - $radio-svg, - 'focus' - )); - } - } - } - - &:checked { - ~ .rose-form-radio-label { - &::after { - background-image: url($data-svg-prefix + map-get( - $radio-svg, - 'checked' - )); - } - } - } - } - - &:disabled { - ~ .rose-form-radio-label { - &::after { - background-image: url($data-svg-prefix + map-get( - $radio-svg, - 'disabled' - )); - } - } - - &:checked { - ~ .rose-form-radio-label { - &::after { - background-image: url($data-svg-prefix + map-get( - $radio-svg, - 'disabled-checked' - )); - } - } - } - } - } - - &.error { - color: var(--failure); - - .rose-form-radio-field { - &:not(:disabled, :focus) { - ~ .rose-form-radio-label { - &::after { - background-image: url($data-svg-prefix + map-get( - $radio-svg, - 'error' - )); - } - } - - &:checked { - ~ .rose-form-radio-label { - &::after { - background-image: url($data-svg-prefix + map-get( - $radio-svg, - 'error-checked' - )); - } - } - } - } - } - } -} diff --git a/addons/rose/package.json b/addons/rose/package.json index 2eb04e6c1..ed70325aa 100644 --- a/addons/rose/package.json +++ b/addons/rose/package.json @@ -40,7 +40,6 @@ "ember-composable-helpers": "^5.0.0", "ember-focus-trap": "^1.0.1", "ember-named-blocks-polyfill": "^0.2.5", - "ember-radio-button": "github:yapplabs/ember-radio-button.git#b2ab65f", "ember-uuid": "^2.1.0", "jsonlint": "^1.6.3", "sass": "^1.69.5" diff --git a/addons/rose/tests/integration/components/rose/form-test.js b/addons/rose/tests/integration/components/rose/form-test.js index ad04bb2ae..e4e7e59fe 100644 --- a/addons/rose/tests/integration/components/rose/form-test.js +++ b/addons/rose/tests/integration/components/rose/form-test.js @@ -72,19 +72,12 @@ module('Integration | Component | rose/form', function (hooks) { - - - `); assert.ok(find('[name="input-field"]')); assert.ok(find('[name="textarea-field"]')); assert.ok(find('[name="select-field"]')); assert.ok(find('[name="checkbox-field"]')); - assert.ok(find('[name="radio-group-field"]')); }); test('it supports an editability toggle when @showEditToggle is true', async function (assert) { diff --git a/addons/rose/tests/integration/components/rose/form/radio/group-test.js b/addons/rose/tests/integration/components/rose/form/radio/group-test.js deleted file mode 100644 index 13af307e2..000000000 --- a/addons/rose/tests/integration/components/rose/form/radio/group-test.js +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -import { module, test } from 'qunit'; -import { setupRenderingTest } from 'ember-qunit'; -import { render, click, findAll } from '@ember/test-helpers'; -import { hbs } from 'ember-cli-htmlbars'; - -module('Integration | Component | rose/form/radio/group', function (hooks) { - setupRenderingTest(hooks); - - test('it renders', async function (assert) { - await render(hbs` - - - - - `); - - assert.strictEqual(findAll('input').length, 2); - assert.strictEqual(findAll('input')[0].name, 'bird'); - assert.strictEqual(findAll('input')[1].name, 'bird'); - }); - - test('it renders with @selectedValue value selected', async function (assert) { - await render(hbs` - - - - - `); - - assert.false(findAll('[type="radio"]')[0].checked); - assert.true(findAll('[type="radio"]')[1].checked); - - await click(findAll('[type="radio"]')[0]); - - assert.true(findAll('[type="radio"]')[0].checked); - assert.false(findAll('[type="radio"]')[1].checked); - }); - - test('it reflects active radiofield value in @selectedValue and triggers a @changed function', async function (assert) { - assert.expect(5); - this.changed = (value) => { - this.selectedValue = value; - assert.ok(value); - }; - await render(hbs` - - - - - `); - - assert.notOk(this.selectedValue); - - await click(findAll('[type="radio"]')[0]); - assert.strictEqual(this.selectedValue, 'pegion'); - - await click(findAll('[type="radio"]')[1]); - assert.strictEqual(this.selectedValue, 'flamingo'); - }); - - test('it uses @selectedValue value to set active radiofield', async function (assert) { - this.set('selectedValue', 'cat'); - await render(hbs` - - - - - `); - - assert.false(findAll('[type="radio"]')[0].checked); - assert.false(findAll('[type="radio"]')[1].checked); - - this.set('selectedValue', 'flamingo'); - assert.false(findAll('[type="radio"]')[0].checked); - assert.true(findAll('[type="radio"]')[1].checked); - - this.set('selectedValue', 'pegion'); - assert.true(findAll('[type="radio"]')[0].checked); - assert.false(findAll('[type="radio"]')[1].checked); - - this.set('selectedValue', 'dog'); - assert.false(findAll('[type="radio"]')[0].checked); - assert.false(findAll('[type="radio"]')[1].checked); - }); -}); diff --git a/addons/rose/tests/integration/components/rose/form/radio/radio-test.js b/addons/rose/tests/integration/components/rose/form/radio/radio-test.js deleted file mode 100644 index 51e537b70..000000000 --- a/addons/rose/tests/integration/components/rose/form/radio/radio-test.js +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -import { module, test } from 'qunit'; -import { setupRenderingTest } from 'ember-qunit'; -import { render, find } from '@ember/test-helpers'; -import { hbs } from 'ember-cli-htmlbars'; - -module('Integration | Component | rose/form/radio/radio', function (hooks) { - setupRenderingTest(hooks); - - test('it renders', async function (assert) { - await render(hbs``); - assert.ok(await find('input')); - assert.strictEqual(await find('input').type, 'radio'); - }); - - test('it renders with label', async function (assert) { - await render(hbs``); - assert.strictEqual(await find('label').textContent.trim(), 'Label'); - }); - - test('it supports an icon', async function (assert) { - await render( - hbs``, - ); - assert.ok(find('svg')); - }); - - test('it is checked when @value and @selectedValue values match', async function (assert) { - await render( - hbs``, - ); - assert.true(await find('input').checked); - }); - - test('it is not checked when @value and @selectedValue values are different', async function (assert) { - await render(hbs``); - assert.false(await find('input').checked); - - await render(hbs``); - assert.false(await find('input').checked); - - await render( - hbs``, - ); - assert.false(await find('input').checked); - }); - - test('it is not disabled by default', async function (assert) { - await render(hbs``); - assert.false(await find('input').disabled); - }); - - test('it is not in error state by default', async function (assert) { - await render(hbs``); - assert.notOk(await find('.error')); - }); - - test('it is disabled when @disabled={{true}}', async function (assert) { - await render(hbs``); - assert.true(await find('input').disabled); - }); - - test('it is in error state when @error={{true}}', async function (assert) { - await render(hbs``); - assert.ok(await find('.error')); - }); -}); diff --git a/ui/admin/app/components/auth-methods/auth-method/actions/index.hbs b/ui/admin/app/components/auth-methods/auth-method/actions/index.hbs index 5586c0a22..629f8ddbf 100644 --- a/ui/admin/app/components/auth-methods/auth-method/actions/index.hbs +++ b/ui/admin/app/components/auth-methods/auth-method/actions/index.hbs @@ -4,40 +4,29 @@ }} {{#if (or @model.isOIDC @model.isLDAP)}} - - - - - {{#each @model.options.state as |state|}} - - - - {{/each}} - - - - + + + + + {{#each @model.options.state as |state|}} + + {{t (concat 'states.' state)}} + + {{/each}} + + {{/if}} diff --git a/ui/admin/app/controllers/application.js b/ui/admin/app/controllers/application.js index f4ced6574..d1ae49a91 100644 --- a/ui/admin/app/controllers/application.js +++ b/ui/admin/app/controllers/application.js @@ -8,6 +8,21 @@ import { inject as service } from '@ember/service'; import { getOwner } from '@ember/application'; import { action } from '@ember/object'; +const THEMES = [ + { + label: 'system', + value: 'system-default-theme', + }, + { + label: 'light', + value: 'light', + }, + { + label: 'dark', + value: 'dark', + }, +]; + export default class ApplicationController extends Controller { // =services @@ -16,6 +31,14 @@ export default class ApplicationController extends Controller { @service featureEdition; @service flashMessages; + /** + * Returns available themes + * @type {array} + */ + get themes() { + return THEMES; + } + // =actions /** diff --git a/ui/admin/app/routes/application.js b/ui/admin/app/routes/application.js index 4cb0dc6ac..e91d193c0 100644 --- a/ui/admin/app/routes/application.js +++ b/ui/admin/app/routes/application.js @@ -4,7 +4,6 @@ */ import Route from '@ember/routing/route'; -import { getOwner } from '@ember/application'; import { inject as service } from '@ember/service'; import { action } from '@ember/object'; import { A } from '@ember/array'; @@ -65,7 +64,9 @@ export default class ApplicationRoute extends Route { this.intl.setLocale(['en-us']); await this.session.setup(); const theme = this.session.get('data.theme'); - this.toggleTheme(theme); + /* eslint-disable-next-line ember/no-controller-access-in-routes */ + const controller = this.controllerFor(this.routeName); + controller.toggleTheme(theme); // Setup the DB from a successful authentication restoration if (this.session.isAuthenticated) { @@ -77,32 +78,6 @@ export default class ApplicationRoute extends Route { } } - /** - * Applies the specified color theme to the root ember element. - * @param {string} theme - "light", "dark", or nullish (system default) - */ - @action - toggleTheme(theme) { - const rootElementSelector = getOwner(this).rootElement; - const rootEl = getOwner(this) - .lookup('service:-document') - .querySelector(rootElementSelector); - this.session.set('data.theme', theme); - switch (theme) { - case 'light': - rootEl.classList.add('rose-theme-light'); - rootEl.classList.remove('rose-theme-dark'); - break; - case 'dark': - rootEl.classList.add('rose-theme-dark'); - rootEl.classList.remove('rose-theme-light'); - break; - default: - rootEl.classList.remove('rose-theme-dark'); - rootEl.classList.remove('rose-theme-light'); - } - } - /** * Invalidates the session if a 401 error occurs and returns false to * prevent further error handling. diff --git a/ui/admin/app/styles/app.scss b/ui/admin/app/styles/app.scss index 13fbd6fad..97022c9d0 100644 --- a/ui/admin/app/styles/app.scss +++ b/ui/admin/app/styles/app.scss @@ -1078,3 +1078,13 @@ margin-bottom: 0; } } + +.change-state { + margin-right: 1rem; +} + +.dropdown-radio-btn { + .hds-form-group__control-field { + padding: 0.25rem 0.75rem; + } +} diff --git a/ui/admin/app/templates/application.hbs b/ui/admin/app/templates/application.hbs index 34b3aaec4..e91c8750d 100644 --- a/ui/admin/app/templates/application.hbs +++ b/ui/admin/app/templates/application.hbs @@ -34,36 +34,22 @@ - - - - - - - - - - - - - + + {{#each this.themes as |theme|}} + + {{t (concat 'themes.' theme.label)}} + + {{/each}} + @@ -79,23 +65,23 @@ as |dropdown| > - - - {{#each this.featureEdition.editions as |edition|}} - - - - {{/each}} - - + + {{#each this.featureEdition.editions as |edition|}} + + {{edition}} + + {{/each}} + + diff --git a/ui/admin/tests/acceptance/auth-methods/oidc-test.js b/ui/admin/tests/acceptance/auth-methods/oidc-test.js index 6e28f8004..1dbb09379 100644 --- a/ui/admin/tests/acceptance/auth-methods/oidc-test.js +++ b/ui/admin/tests/acceptance/auth-methods/oidc-test.js @@ -25,6 +25,11 @@ import { import { TYPE_AUTH_METHOD_OIDC } from 'api/models/auth-method'; +const CHANGE_STATE_SELECTOR = '[data-test-change-state] button:first-child'; +const CHANGE_STATE_INPUT_CHECKED = '[data-test-change-state] input:checked'; + +const CHANGE_STATE_INPUT_SELECTOR = '[data-test-change-state]'; + module('Acceptance | auth-methods | oidc', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); @@ -67,9 +72,9 @@ module('Acceptance | auth-methods | oidc', function (hooks) { test('can view oidc state', async function (assert) { await visit(urls.authMethod); - await click('.rose-layout-page-actions .rose-dropdown-trigger'); + await click(CHANGE_STATE_SELECTOR); assert.strictEqual( - find('.rose-dropdown[open] input:checked').value, + find(CHANGE_STATE_INPUT_CHECKED).value, instances.authMethod.attributes.state, ); }); @@ -77,29 +82,45 @@ module('Acceptance | auth-methods | oidc', function (hooks) { test('can update oidc state', async function (assert) { const updateValue = 'inactive'; await visit(urls.authMethod); - await click('.rose-layout-page-actions .rose-dropdown-trigger'); - await click(`.rose-dropdown[open] input[value="${updateValue}"]`); - const authMethod = this.server.db.authMethods.find(instances.authMethod.id); + await click(CHANGE_STATE_SELECTOR); + await click(`${CHANGE_STATE_INPUT_SELECTOR} input[value="${updateValue}"]`); + const authMethod = this.server.schema.authMethods.findBy({ + id: instances.authMethod.id, + }); + assert.strictEqual(authMethod.attributes.state, updateValue); }); test('can update oidc state to active-private', async function (assert) { const updateValue = 'active-private'; await visit(urls.authMethod); - await click('.rose-layout-page-actions .rose-dropdown-trigger'); - await click(`.rose-dropdown[open] input[value="${updateValue}"]`); - const authMethod = this.server.db.authMethods.find(instances.authMethod.id); + await click(CHANGE_STATE_SELECTOR); + await click(`${CHANGE_STATE_INPUT_SELECTOR} input[value="${updateValue}"]`); + const authMethod = this.server.schema.authMethods.findBy({ + id: instances.authMethod.id, + }); + assert.strictEqual( + find(CHANGE_STATE_INPUT_CHECKED).value, + instances.authMethod.attributes.state, + ); assert.strictEqual(authMethod.attributes.state, updateValue); }); test('can update oidc state to active-public', async function (assert) { - // Update default 'active-public' state to inactive instances.authMethod.attributes.state = 'inactive'; const updateValue = 'active-public'; await visit(urls.authMethod); - await click('.rose-layout-page-actions .rose-dropdown-trigger'); - await click(`.rose-dropdown[open] input[value="${updateValue}"]`); - const authMethod = this.server.db.authMethods.find(instances.authMethod.id); + await click(CHANGE_STATE_SELECTOR); + await click(`${CHANGE_STATE_INPUT_SELECTOR} input[value="${updateValue}"]`); + const authMethod = this.server.schema.authMethods.findBy({ + id: instances.authMethod.id, + }); + + assert.strictEqual( + find(CHANGE_STATE_INPUT_CHECKED).value, + instances.authMethod.attributes.state, + ); + assert.strictEqual(authMethod.attributes.state, updateValue); }); diff --git a/ui/admin/tests/acceptance/workers/create-test.js b/ui/admin/tests/acceptance/workers/create-test.js index 233eab219..833016957 100644 --- a/ui/admin/tests/acceptance/workers/create-test.js +++ b/ui/admin/tests/acceptance/workers/create-test.js @@ -53,7 +53,7 @@ module('Acceptance | workers | create', function (hooks) { const featuresService = this.owner.lookup('service:features'); featuresService.enable('byow-pki-hcp-cluster-id'); await visit(newWorkerURL); - const labels = findAll('label.hds-form-label'); + const labels = findAll('.worker-create-section label.hds-form-label'); assert.dom(labels[0]).hasText('Boundary Cluster ID (Optional)'); assert.dom(labels[2]).doesNotIncludeText('Initial Upstreams'); }); @@ -61,7 +61,7 @@ module('Acceptance | workers | create', function (hooks) { test('initial upstreams input field is visible for `oss` binary', async function (assert) { const featuresService = this.owner.lookup('service:features'); await visit(newWorkerURL); - const labels = findAll('label.hds-form-label'); + const labels = findAll('.worker-create-section label.hds-form-label'); assert.false(featuresService.isEnabled('byow-pki-hcp-cluster-id')); assert.dom(labels[0]).doesNotIncludeText('Boundary Cluster ID'); assert.dom(labels[2]).hasText('Initial Upstreams (Optional)'); diff --git a/yarn.lock b/yarn.lock index 6315c73e3..d1396e1f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9345,13 +9345,6 @@ ember-qunit@^8.1.0: ember-cli-test-loader "^3.1.0" qunit-theme-ember "^1.0.0" -"ember-radio-button@github:yapplabs/ember-radio-button.git#b2ab65f": - version "0.0.0" - resolved "https://codeload.github.com/yapplabs/ember-radio-button/tar.gz/b2ab65f81cae3b0f087cc99b8c01172b754cb181" - dependencies: - ember-cli-babel "^7.26.3" - ember-cli-htmlbars "^5.7.1" - ember-resolver@^10.0.0: version "10.1.1" resolved "https://registry.yarnpkg.com/ember-resolver/-/ember-resolver-10.1.1.tgz#1106a2386337b13a9ee509a9ad2a18b53a728ab3"