[UI][Bugfix]: General Settings Bugfix Pt.1 (#9820) (#9837)

* VAULT-39898 page scroll locked after discarding

* VAULT-39904 update lease duration card text

* Ensure ttl fields dont get reset if there are errors

* Fix failing tests

* VAULT-39900 close the unsaved changes modal if it errors on save

* Switch max and lease duration

* Refactor modal save/close

* Remove promise!

* Code cleanup!

Co-authored-by: Kianna <30884335+kiannaquach@users.noreply.github.com>
This commit is contained in:
Vault Automation 2025-10-03 15:33:36 -04:00 committed by GitHub
parent bddc79aeaa
commit 06948c7588
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 66 additions and 34 deletions

View File

@ -6,16 +6,15 @@
@level="mid"
@hasBorder={{true}}
class="has-padding-m has-top-bottom-margin"
data-test-card-container="lease-duration"
data-test-card-container="secrets duration"
...attributes
>
<Hds::Text::Display @size="300" @tag="h2" class="has-bottom-margin-s hds-foreground-strong">Lease Duration</Hds::Text::Display>
<Hds::Text::Display @size="300" @tag="h2" class="has-bottom-margin-s hds-foreground-strong">Secrets duration</Hds::Text::Display>
<Hds::Layout::Flex @align="start" @gap="16" class="has-bottom-margin-m">
<Hds::Layout::Flex @direction="column" @gap="4">
<Hds::Text::Body @size="200" @tag="p">Lease, measured by the “time-to-live” value, defines how long any secret issued
by this engine remains valid.</Hds::Text::Body>
{{! TODO: Verify what link this is supposed to be }}
<Hds::Text::Body @size="200" @tag="p">Time-to-live defines how long secrets issued by this engine remain valid before
expiring.</Hds::Text::Body>
<Hds::Link::Standalone
@icon="external-link"
@iconPosition="trailing"
@ -26,7 +25,7 @@
</Hds::Layout::Flex>
<Hds::Layout::Flex @direction="column" @gap="24" @isInline="true" class="has-top-padding-s has-bottom-padding-s">
<SecretEngine::TtlPickerV2 @model={{@model}} @ttlKey="default_lease_ttl" />
<SecretEngine::TtlPickerV2 @model={{@model}} @ttlKey="max_lease_ttl" />
<SecretEngine::TtlPickerV2 @model={{@model}} @initialUnit={{@defaultLeaseUnit}} @ttlKey="default_lease_ttl" />
<SecretEngine::TtlPickerV2 @model={{@model}} @initialUnit={{@maxLeaseUnit}} @ttlKey="max_lease_ttl" />
</Hds::Layout::Flex>
</Hds::Card::Container>

View File

@ -31,7 +31,12 @@
<SecretEngine::Card::Metadata @model={{@model}} class="is-fullwidth" />
</LG.Item>
<LG.Item @colspan={{1}}>
<SecretEngine::Card::LeaseDuration @model={{@model}} class="is-fullwidth" />
<SecretEngine::Card::SecretsDuration
@model={{@model}}
@defaultLeaseUnit={{this.defaultLeaseUnit}}
@maxLeaseUnit={{this.maxLeaseUnit}}
class="is-fullwidth"
/>
<SecretEngine::Card::Security @model={{@model}} class="is-fullwidth" />
</LG.Item>
</Hds::Layout::Grid>
@ -61,20 +66,20 @@
<br />
Would you like to apply them?
</M.Body>
<M.Footer>
<M.Footer as |F|>
<Hds::ButtonSet>
<Hds::Button
type="button"
@text="Save changes"
data-test-button="save"
{{on "click" (perform this.saveGeneralSettings)}}
{{on "click" (fn this.closeAndHandle F.close "save")}}
/>
<Hds::Button
type="button"
@text="Discard changes"
@color="secondary"
data-test-button="discard"
{{on "click" this.discardChanges}}
{{on "click" (fn this.closeAndHandle F.close "discard")}}
/>
</Hds::ButtonSet>
</M.Footer>

View File

@ -49,6 +49,9 @@ export default class GeneralSettingsComponent extends Component<Args> {
@tracked showUnsavedChangesModal = false;
@tracked changedFields: string[] = [];
@tracked defaultLeaseUnit = '';
@tracked maxLeaseUnit = '';
originalModel = JSON.parse(JSON.stringify(this.args.model));
getUnsavedChanges(newModel: SecretsEngineResource, originalModel: SecretsEngineResource) {
@ -183,20 +186,31 @@ export default class GeneralSettingsComponent extends Component<Args> {
@action
closeUnsavedChangesModal() {
this.showUnsavedChangesModal = !this.showUnsavedChangesModal;
this.showUnsavedChangesModal = false;
this.changedFields = [];
}
@action
discardChanges() {
this.closeUnsavedChangesModal();
this.router.transitionTo(this.args?.model?.secretsEngine?.backendConfigurationLink);
closeAndHandle(close: () => void, action: 'save' | 'discard') {
close();
if (action === 'save') {
this.saveGeneralSettings.perform();
}
if (action === 'discard') {
this.router.transitionTo(this.args?.model?.secretsEngine?.backendConfigurationLink);
}
}
saveGeneralSettings = task(async (event) => {
event.preventDefault();
saveGeneralSettings = task(async (event?) => {
// event is an optional arg because saveGeneralSettings can be called in the closeAndHandle function.
// There are instances where we will save in the modal where that doesn't require an event.
if (event) event.preventDefault();
const { defaultLeaseTime, maxLeaseTime } = this.getFormData();
const { defaultLeaseTime, defaultLeaseUnit, maxLeaseTime, maxLeaseUnit } = this.getFormData();
this.defaultLeaseUnit = defaultLeaseUnit;
this.maxLeaseUnit = maxLeaseUnit;
if (!this.validateTtl(defaultLeaseTime) || !this.validateTtl(maxLeaseTime)) {
this.errorMessage = 'Only use numbers for this setting.';
@ -214,11 +228,11 @@ export default class GeneralSettingsComponent extends Component<Args> {
});
this.flashMessages.success('Engine settings successfully updated.');
this.router.transitionTo(this.args?.model?.secretsEngine?.backendConfigurationLink);
} catch (e) {
const { message } = await this.api.parseError(e);
this.errorMessage = message;
this.flashMessages.danger(`Try again or check your network connection. ${message}`);
}
});
}

View File

@ -33,6 +33,7 @@ interface Args {
model: {
secretsEngine: SecretsEngineResource;
};
initialUnit: string;
ttlKey: 'default_lease_ttl' | 'max_lease_ttl';
}
@ -66,7 +67,11 @@ export default class TtlPickerV2 extends Component<Args> {
} else {
const parseDuration = durationToSeconds(ttlValue || '');
// if parsing fails leave it empty
if (parseDuration === null) return;
if (parseDuration === null) {
this.time = ttlValue || '';
this.selectedUnit = this.args.initialUnit;
return;
}
seconds = parseDuration;
}
@ -87,11 +92,14 @@ export default class TtlPickerV2 extends Component<Args> {
get formField() {
return {
label: this.args?.ttlKey === 'default_lease_ttl' ? 'Time-to-live (TTL)' : 'Maximum Time-to-live (TTL)',
label:
this.args?.ttlKey === 'default_lease_ttl'
? 'Default time-to-live (TTL)'
: 'Maximum time-to-live (TTL)',
helperText:
this.args?.ttlKey === 'default_lease_ttl'
? 'Standard expiry deadline.'
: 'Maximum possible extension for expiry.',
? 'How long secrets in this engine stay valid.'
: 'Maximum extension for the secrets life beyond default.',
};
}

View File

@ -43,7 +43,7 @@ module('Acceptance | Enterprise | keymgmt-configuration-workflow', function (hoo
assert.dom(SELECTORS.versionCard.engineType).hasText(keymgmtType, 'shows keymgmt engine type');
assert.dom(GENERAL.cardContainer('metadata')).exists('metadata card exists');
assert.dom(GENERAL.inputByAttr('path')).hasValue(`${keymgmtType}/`, 'show path value');
assert.dom(GENERAL.cardContainer('lease-duration')).exists('lease-duration card exists');
assert.dom(GENERAL.cardContainer('secrets duration')).exists('secrets duration card exists');
assert.dom(GENERAL.cardContainer('security')).exists('security card exists');
// fill in values to tune

View File

@ -21,12 +21,16 @@ module('Integration | Component | SecretEngine::Card::LeaseDuration', function (
test('it shows default and max ttl pickers', async function (assert) {
assert.expect(5);
await render(hbs`
<SecretEngine::Card::LeaseDuration @model={{this.model}} />
<SecretEngine::Card::SecretsDuration @model={{this.model}} />
`);
assert.dom(SELECTORS.ttlPickerV2).exists({ count: 2 });
assert.dom(GENERAL.fieldLabelbyAttr('default_lease_ttl')).hasText('Time-to-live (TTL)');
assert.dom(GENERAL.fieldLabelbyAttr('max_lease_ttl')).hasText('Maximum Time-to-live (TTL)');
assert.dom(GENERAL.helpTextByAttr('default_lease_ttl')).hasText('Standard expiry deadline.');
assert.dom(GENERAL.helpTextByAttr('max_lease_ttl')).hasText('Maximum possible extension for expiry.');
assert.dom(GENERAL.fieldLabelbyAttr('default_lease_ttl')).hasText('Default time-to-live (TTL)');
assert.dom(GENERAL.fieldLabelbyAttr('max_lease_ttl')).hasText('Maximum time-to-live (TTL)');
assert
.dom(GENERAL.helpTextByAttr('default_lease_ttl'))
.hasText('How long secrets in this engine stay valid.');
assert
.dom(GENERAL.helpTextByAttr('max_lease_ttl'))
.hasText('Maximum extension for the secrets life beyond default.');
});
});

View File

@ -48,7 +48,7 @@ module('Integration | Component | SecretEngine::Page::GeneralSettings', function
await render(hbs`
<SecretEngine::Page::GeneralSettings @model={{this.model}} />
`);
assert.dom(GENERAL.cardContainer('lease-duration')).exists(`Lease duration card exists`);
assert.dom(GENERAL.cardContainer('secrets duration')).exists(`Lease duration card exists`);
assert.dom(GENERAL.cardContainer('security')).exists(`Security card exists`);
assert.dom(GENERAL.cardContainer('version')).exists(`Version card exists`);
assert.dom(GENERAL.cardContainer('metadata')).exists(`Metadata card exists`);

View File

@ -23,8 +23,8 @@ module('Integration | Component | SecretEngine::TtlPickerV2', function (hooks) {
await render(hbs`
<SecretEngine::TtlPickerV2 @model={{this.model}} @ttlKey={{this.ttlKey}} />
`);
assert.dom(GENERAL.fieldLabelbyAttr(this.ttlKey)).hasText('Time-to-live (TTL)');
assert.dom(GENERAL.helpTextByAttr(this.ttlKey)).hasText('Standard expiry deadline.');
assert.dom(GENERAL.fieldLabelbyAttr(this.ttlKey)).hasText('Default time-to-live (TTL)');
assert.dom(GENERAL.helpTextByAttr(this.ttlKey)).hasText('How long secrets in this engine stay valid.');
await fillIn(GENERAL.inputByAttr(this.ttlKey), 5);
await fillIn(GENERAL.selectByAttr(this.ttlKey), 'm');
assert.dom(GENERAL.inputByAttr(this.ttlKey)).hasValue('5');
@ -37,8 +37,10 @@ module('Integration | Component | SecretEngine::TtlPickerV2', function (hooks) {
await render(hbs`
<SecretEngine::TtlPickerV2 @model={{this.model}} @ttlKey={{this.ttlKey}} />
`);
assert.dom(GENERAL.fieldLabelbyAttr(this.ttlKey)).hasText('Maximum Time-to-live (TTL)');
assert.dom(GENERAL.helpTextByAttr(this.ttlKey)).hasText('Maximum possible extension for expiry.');
assert.dom(GENERAL.fieldLabelbyAttr(this.ttlKey)).hasText('Maximum time-to-live (TTL)');
assert
.dom(GENERAL.helpTextByAttr(this.ttlKey))
.hasText('Maximum extension for the secrets life beyond default.');
await fillIn(GENERAL.inputByAttr(this.ttlKey), 10);
await fillIn(GENERAL.selectByAttr(this.ttlKey), 'm');
assert.dom(GENERAL.inputByAttr(this.ttlKey)).hasValue('10');