Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions addons/rose/addon/styles/hds/overrides.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
form {
[class*='hds-form-field--layout'],
[class*='hds-form-group--layout'],
[class='hds-form-key-value-inputs'],
.rose-form-actions.hds-button-set {
margin-bottom: 1.5rem;
}
Expand Down
76 changes: 41 additions & 35 deletions ui/admin/app/components/form/auth-method/oidc/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -247,45 +247,51 @@
</Form::Field::ListWrapper>

{{! Account Claim Maps }}
<Form::Field::ListWrapper
@layout='horizontal'
@isOptional={{true}}
<Form::Field::KeyValue
@name='account_claim_maps'
@data={{@model.account_claim_maps}}
@onChange={{this.updateAccountClaimMaps}}
@legend={{t 'form.account_claim_maps.label'}}
@helperText={{t 'form.account_claim_maps.help'}}
@errors={{@model.errors.account_claim_maps}}
@disabled={{form.disabled}}
@isOptional={{true}}
>
<:fieldset as |F|>
<F.Legend>
{{t 'form.account_claim_maps.label'}}
</F.Legend>
<F.HelperText>
{{t 'form.account_claim_maps.help'}}
</F.HelperText>

{{#if @model.errors.account_claim_maps}}
<F.Error as |E|>
{{#each @model.errors.account_claim_maps as |error|}}
<E.Message>{{error.message}}</E.Message>
<:row as |R|>
<R.Field as |F|>
<F.Label>{{t 'form.from_claim.label'}}</F.Label>
<F.TextInput
data-test-key-input
@value={{R.rowData.key}}
@isInvalid={{@model.errors.account_claim_maps}}
{{on 'input' (fn R.updateAndNotify 'key')}}
/>
</R.Field>

<R.Field as |F|>
<F.Label>{{t 'form.to_claim.label'}}</F.Label>
<F.Select
data-test-value-input
@isInvalid={{@model.errors.account_claim_maps}}
{{on 'change' (fn R.updateAndNotify 'value')}}
>
<option disabled hidden selected value=''>
{{t 'titles.choose-an-option'}}
</option>
{{#each this.toClaims as |claim|}}
<option value={{claim}} selected={{eq R.rowData.value claim}}>
{{claim}}
</option>
{{/each}}
</F.Error>
</F.Select>
</R.Field>

{{#if (R.hasData)}}
<R.DeleteRowButton data-test-delete-button @onClick={{R.removeRow}} />
{{/if}}
</:fieldset>
<:field as |F|>
<F.KeyValue
@name='account_claim_maps'
@options={{@model.account_claim_maps}}
@model={{@model}}
@keyLabel={{t 'form.from_claim.label'}}
@valueLabel={{t 'form.to_claim.label'}}
@width='100%'
>
<:key as |K|>
<K.text />
</:key>
<:value as |V|>
<V.select @selectOptions={{this.toClaims}} />
</:value>
</F.KeyValue>
</:field>
</Form::Field::ListWrapper>
</:row>

</Form::Field::KeyValue>

{{! Certificates }}
<Form::Field::ListWrapper
Expand Down
8 changes: 8 additions & 0 deletions ui/admin/app/components/form/auth-method/oidc/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ export default class FormAuthMethodOidcComponent extends Component {

//actions

/**
* Updates the account claim maps on the model with new data
* @param {Array} newData - Array of account claim map objects
*/
@action
updateAccountClaimMaps(newData) {
this.args.model.account_claim_maps = newData;
}
/**
* @param {string} value
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,45 +225,43 @@
{{/if}}
</Hds::Form::TextInput::Field>

<Form::Field::ListWrapper
@layout='horizontal'
@isOptional={{true}}
<Form::Field::KeyValue
@name='critical_options'
@data={{@model.critical_options}}
@onChange={{this.updateCriticalOptions}}
@legend={{t 'resources.credential-library.form.critical_options.label'}}
@helperText={{t 'resources.credential-library.form.critical_options.help'}}
@errors={{@model.errors.critical_options}}
@disabled={{form.disabled}}
@isOptional={{true}}
>
<:fieldset as |F|>
<F.Legend>
{{t 'resources.credential-library.form.critical_options.label'}}
</F.Legend>
<F.HelperText>
{{t 'resources.credential-library.form.critical_options.help'}}
</F.HelperText>
<:row as |R|>
<R.Field as |F|>
<F.Label>{{t 'form.key.label'}}</F.Label>
<F.TextInput
data-test-key-input
@value={{R.rowData.key}}
@isInvalid={{@model.errors.critical_options}}
{{on 'input' (fn R.updateAndNotify 'key')}}
/>
</R.Field>

{{#if @model.errors.critical_options}}
<F.Error as |E|>
{{#each @model.errors.critical_options as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</F.Error>
{{/if}}
</:fieldset>
<R.Field as |F|>
<F.Label>{{t 'form.value.label'}}</F.Label>
<F.TextInput
data-test-value-input
@value={{R.rowData.value}}
@isInvalid={{@model.errors.critical_options}}
{{on 'input' (fn R.updateAndNotify 'value')}}
/>
</R.Field>

<:field as |F|>
<F.KeyValue
@name='critical_options'
@options={{@model.critical_options}}
@model={{@model}}
@disabled={{form.disabled}}
>
<:key as |K|>
<K.text />
</:key>
<:value as |K|>
<K.text />
</:value>
</F.KeyValue>
</:field>
{{#if (R.hasData)}}
<R.DeleteRowButton data-test-delete-button @onClick={{R.removeRow}} />
{{/if}}
</:row>

</Form::Field::ListWrapper>
</Form::Field::KeyValue>

<Form::Field::ListWrapper
@layout='horizontal'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import Component from '@glimmer/component';

import { action } from '@ember/object';
import { options } from 'api/models/credential-library';

export default class FormCredentialLibraryVaultSshCertComponent extends Component {
Expand All @@ -23,4 +24,13 @@ export default class FormCredentialLibraryVaultSshCertComponent extends Componen
const keyType = this.args.model.key_type;
return keyType === 'rsa' || keyType === 'ecdsa';
}

/**
* Updates the critical options on the credential library model with new data
* @param {Array} newData - Array of critical option objects
*/
@action
updateCriticalOptions(newData) {
this.args.model.critical_options = newData;
}
}
47 changes: 47 additions & 0 deletions ui/admin/app/components/form/field/key-value/index.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
}}
<Hds::Form::KeyValueInputs
name={{@name}}
@data={{this.data}}
@isRequired={{@isRequired}}
@isOptional={{@isOptional}}
disabled={{@disabled}}
>
<:header as |H|>
<H.Legend data-test-legend>{{@legend}}</H.Legend>
{{#if @helperText}}
<H.HelperText data-test-helper-text>{{@helperText}}</H.HelperText>
{{/if}}
</:header>

<:row as |R|>
{{yield
(hash
Field=R.Field
DeleteRowButton=R.DeleteRowButton
rowData=R.rowData
hasData=(fn this.hasData R.rowData)
removeRow=(fn this.removeRow R.rowData)
updateAndNotify=(fn this.updateAndNotify R.rowData)
)
to='row'
}}
</:row>
<:footer as |F|>

{{#if @errors}}
<F.Error as |E|>
{{#each @errors as |error|}}
<E.Message data-test-error-message>{{error.message}}</E.Message>
{{/each}}
</F.Error>
{{/if}}
<F.AddRowButton
@text={{t 'actions.add'}}
data-test-add-button
@onClick={{this.addNewRow}}
/>
</:footer>
</Hds::Form::KeyValueInputs>
73 changes: 73 additions & 0 deletions ui/admin/app/components/form/field/key-value/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';

export default class FormFieldKeyValueComponent extends Component {
// =properties

@tracked data = this.args.data?.length ? [...this.args.data] : [{}];

// =actions

@action
addNewRow() {
this.data = [...this.data, {}];
this.notifyChange();
}

@action
removeRow(rowData) {
this.data = this.data.filter((item) => item !== rowData);

// Ensure at least one row exists
if (this.data.length === 0) {
this.data = [{}];
}
this.notifyChange();
}

/**
* Action to update row data from input events and notify consumer
* @param {Object} rowData - The row object to update
* @param {string} property - The property name to update
* @param {string} value - The new value
*/
@action
updateAndNotify(rowData, property, { target: { value } }) {
rowData[property] = value;
this.data = [...this.data];
this.notifyChange();
}

/**
* Checks if a row has any non-empty data
* @param {Object} rowData - The row object to check
* @returns {boolean} - True if the row has any non-empty values
*/
@action
hasData(rowData) {
if (!this.data.includes(rowData)) return false;

return Object.values(rowData).some(
(value) => value != null && value !== '',
);
}

/**
* Notifies consumer of data changes with filtered results
*/
notifyChange() {
if (!this.args.onChange) return;

// Filter out rows where the 'key' property is empty or missing
const filteredData = this.data.filter(
(item) => item.key != null && item.key !== '',
);
this.args.onChange(filteredData);
}
}
2 changes: 1 addition & 1 deletion ui/admin/tests/acceptance/auth-methods/create-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,11 @@ module('Acceptance | auth-methods | create', function (hooks) {
selectors.FIELD_ACCOUNT_CLAIM_MAPS_FROM_CLAIM,
selectors.FIELD_ACCOUNT_CLAIM_MAPS_FROM_CLAIM_VALUE,
);

await select(
selectors.FIELD_ACCOUNT_CLAIM_MAPS_TO_CLAIM,
selectors.FIELD_ACCOUNT_CLAIM_MAPS_TO_CLAIM_VALUE,
);
await click(selectors.FIELD_ACCOUNT_CLAIM_MAPS_ADD_BTN);

await fillIn(selectors.FIELD_IDP_CERTS, selectors.FIELD_IDP_CERTS_VALUE);
await click(selectors.FIELD_IDP_CERTS_ADD_BTN);
Expand Down
9 changes: 3 additions & 6 deletions ui/admin/tests/acceptance/auth-methods/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,12 @@ export const FIELD_CLAIMS_SCOPES_ADD_BTN = '[name=claims_scopes] button';
export const FIELD_CLAIMS_SCOPES_DELETE_BTN =
'[name=claims_scopes] tbody td:last-child button[aria-label=Remove]';
export const FIELD_ACCOUNT_CLAIM_MAPS_FROM_CLAIM =
'[name=account_claim_maps] tbody td:nth-of-type(1) input';
'[name=account_claim_maps] [data-test-key-input]';
export const FIELD_ACCOUNT_CLAIM_MAPS_FROM_CLAIM_VALUE = 'from_claim';
export const FIELD_ACCOUNT_CLAIM_MAPS_TO_CLAIM =
'[name=account_claim_maps] tbody td:nth-of-type(2) select';
'[name=account_claim_maps] [data-test-value-input]';
export const FIELD_ACCOUNT_CLAIM_MAPS_TO_CLAIM_VALUE = 'email';
export const FIELD_ACCOUNT_CLAIM_MAPS_ADD_BTN =
'[name=account_claim_maps] button';
export const FIELD_ACCOUNT_CLAIM_MAPS_DELETE_BTN =
'[name=account_claim_maps] tbody td:last-child button[aria-label=Remove]';
export const FIELD_ACCOUNT_CLAIM_MAPS_DELETE_BTN = '[data-test-delete-button]';
export const FIELD_IDP_CERTS = '[name=idp_ca_certs] textarea';
export const FIELD_IDP_CERTS_VALUE = 'IDP certificates';
export const FIELD_IDP_CERTS_ADD_BTN = '[name=idp_ca_certs] button';
Expand Down
2 changes: 0 additions & 2 deletions ui/admin/tests/acceptance/auth-methods/update-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,6 @@ module('Acceptance | auth-methods | update', function (hooks) {
selectors.FIELD_ACCOUNT_CLAIM_MAPS_TO_CLAIM_VALUE,
);

await click(selectors.FIELD_ACCOUNT_CLAIM_MAPS_ADD_BTN);

// Remove all certificates
const certificatesList = findAll(selectors.FIELD_IDP_CERTS_DELETE_BTN);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,6 @@ module('Acceptance | credential-libraries | create', function (hooks) {
await fillIn(selectors.FIELD_KEY_ID, selectors.FIELD_KEY_ID_VALUE);
await fillIn(selectors.FIELD_CRIT_OPTS_KEY, 'co_key');
await fillIn(selectors.FIELD_CRIT_OPTS_VALUE, 'co_value');
await click(selectors.FIELD_CRIT_OPTS_BTN);
await fillIn(selectors.FIELD_EXT_KEY, 'ext_key');
await fillIn(selectors.FIELD_EXT_VALUE, 'ext_value');
await click(selectors.FIELD_EXT_BTN);
Expand Down
5 changes: 2 additions & 3 deletions ui/admin/tests/acceptance/credential-library/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,9 @@ export const TYPE_VAULT_LDAP = '[value="vault-ldap"]';
export const TYPE_VAULT_GENERIC = '[value="vault-generic"]';

export const FIELD_CRIT_OPTS_KEY =
'[name="critical_options"] tbody td:nth-of-type(1) input';
'[name="critical_options"] [data-test-key-input]';
export const FIELD_CRIT_OPTS_VALUE =
'[name="critical_options"] tbody td:nth-of-type(2) input';
export const FIELD_CRIT_OPTS_BTN = '[name="critical_options"] button';
'[name="critical_options"] [data-test-value-input]';

export const FIELD_EXT_KEY =
'[name="extensions"] tbody td:nth-of-type(1) input';
Expand Down
Loading
Loading