mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-28 04:10:44 -04:00
* updates pki tidy auto route to use api service * updates pki tidy status view to use api service * updates pki tidy auto and manual workflows to use api service and form class Co-authored-by: Jordan Reimer <zofskeez@gmail.com>
This commit is contained in:
parent
4e2f3ba489
commit
004d6da92c
22 changed files with 457 additions and 418 deletions
23
ui/app/forms/secrets/pki/tidy.ts
Normal file
23
ui/app/forms/secrets/pki/tidy.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import OpenApiForm from 'vault/forms/open-api';
|
||||
|
||||
import type { PkiTidyRequest, PkiConfigureAutoTidyRequest } from '@hashicorp/vault-client-typescript';
|
||||
|
||||
type PkiTidyFormRequest = PkiTidyRequest | PkiConfigureAutoTidyRequest;
|
||||
|
||||
export default class PkiTidyForm extends OpenApiForm<PkiTidyFormRequest> {
|
||||
constructor(...args: ConstructorParameters<typeof OpenApiForm>) {
|
||||
super(...args);
|
||||
|
||||
// use ttl picker for pause_duration
|
||||
const pauseDuration = this.formFields.find((field) => field.name === 'pause_duration');
|
||||
if (pauseDuration) {
|
||||
pauseDuration.options.editType = 'ttl';
|
||||
this.data.pause_duration = '0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,8 +16,8 @@
|
|||
</PageHeader>
|
||||
|
||||
<PkiTidyForm
|
||||
@tidy={{@model}}
|
||||
@form={{@form}}
|
||||
@tidyType="auto"
|
||||
@onSave={{transition-to "vault.cluster.secrets.backend.pki.tidy.auto"}}
|
||||
@onCancel={{transition-to (concat "vault.cluster.secrets.backend.pki.tidy" (if @model.enabled ".auto" ""))}}
|
||||
@onCancel={{transition-to (concat "vault.cluster.secrets.backend.pki.tidy" (if @form.data.enabled ".auto" ""))}}
|
||||
/>
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
<Toolbar>
|
||||
<ToolbarActions>
|
||||
<LinkTo @route="tidy.auto.configure" @model={{@model.id}} class="toolbar-link" data-test-pki-edit-tidy-auto-link>
|
||||
<LinkTo @route="tidy.auto.configure" @model={{@backend}} class="toolbar-link" data-test-pki-edit-tidy-auto-link>
|
||||
Edit auto-tidy
|
||||
<Icon @name="chevron-right" />
|
||||
</LinkTo>
|
||||
|
|
@ -24,20 +24,32 @@
|
|||
</Toolbar>
|
||||
|
||||
<main>
|
||||
{{#each @model.allGroups as |group|}}
|
||||
{{#each (tidy-groups) as |group|}}
|
||||
{{#each-in group as |label fields|}}
|
||||
{{#if (not-eq label "autoTidy")}}
|
||||
{{#if (not-eq label "default")}}
|
||||
<h2 class="title is-5 has-top-margin-l has-bottom-margin-xs" data-test-group-title={{label}}>
|
||||
{{label}}
|
||||
</h2>
|
||||
{{/if}}
|
||||
|
||||
{{#each fields as |attr|}}
|
||||
{{#each fields as |field|}}
|
||||
<InfoTableRow
|
||||
@label={{or attr.options.detailsLabel attr.options.label (humanize (dasherize attr.name))}}
|
||||
@value={{get @model attr.name}}
|
||||
@formatTtl={{attr.options.formatTtl}}
|
||||
data-test-row={{attr.name}}
|
||||
@label={{tidy-field-label field}}
|
||||
@value={{get @model field}}
|
||||
@formatTtl={{includes
|
||||
field
|
||||
(array
|
||||
"acme_account_safety_buffer"
|
||||
"interval_duration"
|
||||
"min_startup_backoff_duration"
|
||||
"max_startup_backoff_duration"
|
||||
"issuer_safety_buffer"
|
||||
"pause_duration"
|
||||
"revocation_queue_safety_buffer"
|
||||
"safety_buffer"
|
||||
)
|
||||
}}
|
||||
data-test-row={{field}}
|
||||
/>
|
||||
{{/each}}
|
||||
{{/each-in}}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
</PageHeader>
|
||||
|
||||
<PkiTidyForm
|
||||
@tidy={{@model}}
|
||||
@form={{@model}}
|
||||
@tidyType="manual"
|
||||
@onSave={{transition-to "vault.cluster.secrets.backend.pki.tidy"}}
|
||||
@onCancel={{transition-to "vault.cluster.secrets.backend.pki.tidy"}}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@
|
|||
<Toolbar>
|
||||
<ToolbarActions>
|
||||
<div class="toolbar-separator"></div>
|
||||
{{#if @autoTidyConfig.enabled}}
|
||||
<ToolbarLink @route="tidy.auto" @model={{@autoTidyConfig.id}} data-test-pki-auto-tidy-config>
|
||||
{{#if @enabled}}
|
||||
<ToolbarLink @route="tidy.auto" @model={{this.secretMountPath.currentPath}} data-test-pki-auto-tidy-config>
|
||||
Auto-tidy configuration
|
||||
</ToolbarLink>
|
||||
<ToolbarLink @route="tidy.manual" @model={{@autoTidyConfig.id}} data-test-pki-manual-tidy-config>
|
||||
<ToolbarLink @route="tidy.manual" @model={{this.secretMountPath.currentPath}} data-test-pki-manual-tidy-config>
|
||||
Perform manual tidy
|
||||
</ToolbarLink>
|
||||
{{else}}
|
||||
|
|
@ -149,13 +149,13 @@
|
|||
<Hds::Button
|
||||
@text="Automatic tidy"
|
||||
@route="tidy.auto.configure"
|
||||
@model={{@autoTidyConfig.id}}
|
||||
@model={{this.secretMountPath.currentPath}}
|
||||
data-test-tidy-modal-auto-button
|
||||
/>
|
||||
<Hds::Button
|
||||
@text="Manual tidy"
|
||||
@route="tidy.manual"
|
||||
@model={{@autoTidyConfig.id}}
|
||||
@model={{this.secretMountPath.currentPath}}
|
||||
data-test-tidy-modal-manual-button
|
||||
/>
|
||||
<Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} data-test-tidy-modal-cancel-button />
|
||||
|
|
|
|||
|
|
@ -8,17 +8,15 @@ import { tracked } from '@glimmer/tracking';
|
|||
import { service } from '@ember/service';
|
||||
import { task } from 'ember-concurrency';
|
||||
import { waitFor } from '@ember/test-waiters';
|
||||
import errorMessage from 'vault/utils/error-message';
|
||||
|
||||
import type Store from '@ember-data/store';
|
||||
import type ApiService from 'vault/services/api';
|
||||
import type SecretMountPath from 'vault/services/secret-mount-path';
|
||||
import type FlashMessageService from 'vault/services/flash-messages';
|
||||
import type VersionService from 'vault/services/version';
|
||||
import type PkiTidyModel from 'vault/models/pki/tidy';
|
||||
import type RouterService from '@ember/routing/router-service';
|
||||
|
||||
interface Args {
|
||||
autoTidyConfig: PkiTidyModel;
|
||||
enabled: boolean;
|
||||
tidyStatus: TidyStatusParams;
|
||||
}
|
||||
|
||||
|
|
@ -48,7 +46,7 @@ interface TidyStatusParams {
|
|||
}
|
||||
|
||||
export default class PkiTidyStatusComponent extends Component<Args> {
|
||||
@service declare readonly store: Store;
|
||||
@service declare readonly api: ApiService;
|
||||
@service declare readonly secretMountPath: SecretMountPath;
|
||||
@service declare readonly flashMessages: FlashMessageService;
|
||||
@service declare readonly version: VersionService;
|
||||
|
|
@ -92,7 +90,7 @@ export default class PkiTidyStatusComponent extends Component<Args> {
|
|||
|
||||
get hasTidyConfig() {
|
||||
return !this.tidyStatusConfigFields.every(
|
||||
(attr) => this.args.tidyStatus[attr as keyof TidyStatusParams] === null
|
||||
(attr) => this.args.tidyStatus[attr as keyof TidyStatusParams] === undefined
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -147,17 +145,17 @@ export default class PkiTidyStatusComponent extends Component<Args> {
|
|||
}
|
||||
}
|
||||
|
||||
@task
|
||||
@waitFor
|
||||
*cancelTidy() {
|
||||
try {
|
||||
const tidyAdapter = this.store.adapterFor('pki/tidy');
|
||||
yield tidyAdapter.cancelTidy(this.secretMountPath.currentPath);
|
||||
this.router.transitionTo('vault.cluster.secrets.backend.pki.tidy');
|
||||
} catch (error) {
|
||||
this.flashMessages.danger(errorMessage(error));
|
||||
} finally {
|
||||
this.confirmCancelTidy = false;
|
||||
}
|
||||
}
|
||||
cancelTidy = task(
|
||||
waitFor(async () => {
|
||||
try {
|
||||
await this.api.secrets.pkiTidyCancel(this.secretMountPath.currentPath);
|
||||
this.router.transitionTo('vault.cluster.secrets.backend.pki.tidy');
|
||||
} catch (error) {
|
||||
const { message } = await this.api.parseError(error);
|
||||
this.flashMessages.danger(message);
|
||||
} finally {
|
||||
this.confirmCancelTidy = false;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@
|
|||
|
||||
<hr class="is-marginless has-background-gray-200" />
|
||||
|
||||
<p class="has-top-margin-m has-bottom-margin-l">Tidying cleans up the storage backend and/or CRL by removing certificates
|
||||
that have expired and are past a certain buffer period beyond their expiration time.
|
||||
<p class="has-top-margin-m has-bottom-margin-l">
|
||||
Tidying cleans up the storage backend and/or CRL by removing certificates that have expired and are past a certain buffer
|
||||
period beyond their expiration time.
|
||||
<DocLink @path="/vault/api-docs/secret/pki#{{if (eq @tidyType 'manual') 'tidy' 'configure-automatic-tidy'}}">Learn more</DocLink>
|
||||
</p>
|
||||
|
||||
|
|
@ -14,52 +15,55 @@
|
|||
|
||||
<form class="has-bottom-margin-s" {{on "submit" (perform this.save)}} data-test-tidy-form={{@tidyType}}>
|
||||
{{#if (eq @tidyType "auto")}}
|
||||
{{#let (get @tidy.allByKey "enabled") as |enabledAttr|}}
|
||||
<div class="field">
|
||||
<Toggle @onChange={{fn (mut @tidy.enabled)}} @checked={{@tidy.enabled}} @name={{enabledAttr.name}}>
|
||||
<legend>
|
||||
<span class="ttl-picker-label is-large">
|
||||
{{if @tidy.enabled enabledAttr.options.label enabledAttr.options.labelDisabled}}
|
||||
</span>
|
||||
{{#unless @tidy.enabled}}
|
||||
<p class="sub-text">{{enabledAttr.options.helperTextDisabled}}</p>
|
||||
{{/unless}}
|
||||
</legend>
|
||||
</Toggle>
|
||||
</div>
|
||||
{{/let}}
|
||||
{{#if @tidy.enabled}}
|
||||
<div class="field">
|
||||
<Toggle @onChange={{fn (mut @form.data.enabled)}} @checked={{@form.data.enabled}} @name="enabled">
|
||||
<legend>
|
||||
<span class="ttl-picker-label is-large">
|
||||
{{if @form.data.enabled "Automatic tidy enabled" "Automatic tidy disabled"}}
|
||||
</span>
|
||||
{{#unless @form.data.enabled}}
|
||||
<p class="sub-text">Automatic tidy operations will not run.</p>
|
||||
{{/unless}}
|
||||
</legend>
|
||||
</Toggle>
|
||||
</div>
|
||||
|
||||
{{#if @form.data.enabled}}
|
||||
<h2 class="title is-size-5 has-border-bottom-light page-header" data-test-tidy-header="Automatic tidy settings">
|
||||
Automatic tidy settings
|
||||
</h2>
|
||||
{{#each @tidy.autoTidyConfigFields as |field|}}
|
||||
<FormField @attr={{get @tidy.allByKey field}} @model={{@tidy}} />
|
||||
{{#each (tidy-groups "auto") as |fieldGroup|}}
|
||||
{{#each-in fieldGroup as |group fields|}}
|
||||
{{#each fields as |field|}}
|
||||
<FormField @attr={{find-by "name" field @form.formFields}} @model={{@form}} />
|
||||
{{/each}}
|
||||
{{/each-in}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if (or (eq @tidyType "manual") @tidy.enabled)}}
|
||||
{{#each @tidy.formFieldGroups as |fieldGroup|}}
|
||||
{{#if (or (eq @tidyType "manual") @form.data.enabled)}}
|
||||
{{#each (tidy-groups "manual") as |fieldGroup|}}
|
||||
{{#each-in fieldGroup as |group fields|}}
|
||||
<h2 class="title is-size-5 has-border-bottom-light page-header" data-test-tidy-header={{group}}>
|
||||
{{group}}
|
||||
</h2>
|
||||
{{#each fields as |attr|}}
|
||||
{{#if (eq attr.name "acmeAccountSafetyBuffer")}}
|
||||
{{#each fields as |field|}}
|
||||
{{#if (eq field "acme_account_safety_buffer")}}
|
||||
<TtlPicker
|
||||
data-test-input={{attr.name}}
|
||||
@onChange={{fn this.handleTtl attr}}
|
||||
@label={{attr.options.label}}
|
||||
@labelDisabled={{attr.options.labelDisabled}}
|
||||
@helperTextDisabled={{attr.options.helperTextDisabled}}
|
||||
@helperTextEnabled={{attr.options.helperTextEnabled}}
|
||||
@initialEnabled={{get @tidy attr.options.mapToBoolean}}
|
||||
@initialValue={{get @tidy attr.name}}
|
||||
data-test-input={{field}}
|
||||
@onChange={{this.handleAcmeTtl}}
|
||||
@label="Tidy ACME enabled"
|
||||
@labelDisabled="Tidy ACME disabled"
|
||||
@helperTextDisabled="Tidying of ACME accounts, orders and authorizations is disabled."
|
||||
@helperTextEnabled="The amount of time that must pass after creation that an account with no orders is marked revoked, and the amount of time after being marked revoked or deactivated."
|
||||
@initialEnabled={{@form.data.tidy_acme}}
|
||||
@initialValue={{get @form.data field}}
|
||||
/>
|
||||
{{else}}
|
||||
{{! tidyAcme is handled by the ttl above }}
|
||||
{{#if (not-eq attr.name "tidyAcme")}}
|
||||
<FormField @attr={{attr}} @model={{@tidy}} />
|
||||
{{! tidy_acme is set via the TtlPicker change event above }}
|
||||
{{#if (not-eq field "tidy_acme")}}
|
||||
<FormField @attr={{find-by "name" field @form.formFields}} @model={{@form}} />
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
*/
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import errorMessage from 'vault/utils/error-message';
|
||||
import { action } from '@ember/object';
|
||||
import { service } from '@ember/service';
|
||||
import { task } from 'ember-concurrency';
|
||||
|
|
@ -12,47 +11,51 @@ import { waitFor } from '@ember/test-waiters';
|
|||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
import type RouterService from '@ember/routing/router-service';
|
||||
import type PkiTidyModel from 'vault/models/pki/tidy';
|
||||
import type { FormField, TtlEvent } from 'vault/app-types';
|
||||
import type PkiTidyForm from 'vault/forms/secrets/pki/tidy';
|
||||
import type { TtlEvent } from 'vault/app-types';
|
||||
import type ApiService from 'vault/services/api';
|
||||
import type SecretMountPathService from 'vault/services/secret-mount-path';
|
||||
|
||||
interface Args {
|
||||
tidy: PkiTidyModel;
|
||||
form: PkiTidyForm;
|
||||
tidyType: string;
|
||||
onSave: CallableFunction;
|
||||
onCancel: CallableFunction;
|
||||
}
|
||||
|
||||
interface PkiTidyTtls {
|
||||
acmeAccountSafetyBuffer: string;
|
||||
}
|
||||
interface PkiTidyBooleans {
|
||||
tidyAcme: boolean;
|
||||
}
|
||||
|
||||
export default class PkiTidyForm extends Component<Args> {
|
||||
export default class PkiTidyFormComponent extends Component<Args> {
|
||||
@service('app-router') declare readonly router: RouterService;
|
||||
@service declare readonly api: ApiService;
|
||||
@service declare readonly secretMountPath: SecretMountPathService;
|
||||
|
||||
@tracked errorBanner = '';
|
||||
@tracked invalidFormAlert = '';
|
||||
|
||||
@task
|
||||
@waitFor
|
||||
*save(event: Event) {
|
||||
event.preventDefault();
|
||||
try {
|
||||
yield this.args.tidy.save({ adapterOptions: { tidyType: this.args.tidyType } });
|
||||
this.args.onSave();
|
||||
} catch (e) {
|
||||
this.errorBanner = errorMessage(e);
|
||||
this.invalidFormAlert = 'There was an error submitting this form.';
|
||||
}
|
||||
}
|
||||
save = task(
|
||||
waitFor(async (event: Event) => {
|
||||
event.preventDefault();
|
||||
try {
|
||||
const { currentPath } = this.secretMountPath;
|
||||
const { data } = this.args.form.toJSON();
|
||||
|
||||
if (this.args.tidyType === 'auto') {
|
||||
await this.api.secrets.pkiConfigureAutoTidy(currentPath, data);
|
||||
} else {
|
||||
await this.api.secrets.pkiTidy(currentPath, data);
|
||||
}
|
||||
this.args.onSave();
|
||||
} catch (e) {
|
||||
const { message } = await this.api.parseError(e);
|
||||
this.errorBanner = message;
|
||||
this.invalidFormAlert = 'There was an error submitting this form.';
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@action
|
||||
handleTtl(attr: FormField, e: TtlEvent) {
|
||||
handleAcmeTtl(e: TtlEvent) {
|
||||
const { enabled, goSafeTimeString } = e;
|
||||
const ttlAttr = attr.name;
|
||||
this.args.tidy[ttlAttr as keyof PkiTidyTtls] = goSafeTimeString;
|
||||
this.args.tidy[attr.options.mapToBoolean as keyof PkiTidyBooleans] = enabled;
|
||||
this.args.form.data.acme_account_safety_buffer = goSafeTimeString;
|
||||
this.args.form.data.tidy_acme = enabled;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
23
ui/lib/pki/addon/helpers/tidy-field-label.ts
Normal file
23
ui/lib/pki/addon/helpers/tidy-field-label.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* Copyright IBM Corp. 2016, 2025
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
import { toLabel } from 'core/helpers/to-label';
|
||||
|
||||
export default function tidyFieldLabel(field: string) {
|
||||
const label = toLabel([field]);
|
||||
return (
|
||||
{
|
||||
acme_account_safety_buffer: 'ACME account safety buffer',
|
||||
tidy_acme: 'Tidy ACME',
|
||||
enabled: 'Automatic tidy enabled',
|
||||
tidy_cert_store: 'Tidy the certificate store',
|
||||
tidy_cross_cluster_revoked_certs: 'Tidy cross-cluster revoked certificates',
|
||||
tidy_cmpv2_nonce_store: 'Tidy CMPv2 nonce store',
|
||||
tidy_move_legacy_ca_bundle: 'Tidy legacy CA bundle',
|
||||
tidy_revocation_queue: 'Tidy cross-cluster revocation requests',
|
||||
tidy_revoked_cert_issuer_associations: 'Tidy revoked certificate issuer associations',
|
||||
tidy_revoked_certs: 'Tidy revoked certificates',
|
||||
}[field] || label
|
||||
);
|
||||
}
|
||||
62
ui/lib/pki/addon/helpers/tidy-groups.ts
Normal file
62
ui/lib/pki/addon/helpers/tidy-groups.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/**
|
||||
* Copyright IBM Corp. 2016, 2025
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Helper from '@ember/component/helper';
|
||||
import { service } from '@ember/service';
|
||||
|
||||
import type VersionService from 'vault/services/version';
|
||||
|
||||
export default class TidyGroups extends Helper {
|
||||
@service declare readonly version: VersionService;
|
||||
|
||||
compute([group]: [group?: 'auto' | 'manual']) {
|
||||
// groups that are shared between auto and manual tidy types
|
||||
const shared: Array<Record<string, Array<string>>> = [
|
||||
{
|
||||
'Universal operations': [
|
||||
'tidy_cert_store',
|
||||
'tidy_cert_metadata',
|
||||
'tidy_revoked_certs',
|
||||
'tidy_revoked_cert_issuer_associations',
|
||||
'safety_buffer',
|
||||
'pause_duration',
|
||||
],
|
||||
},
|
||||
{
|
||||
'ACME operations': ['tidy_acme', 'acme_account_safety_buffer'],
|
||||
},
|
||||
{
|
||||
'Issuer operations': ['tidy_expired_issuers', 'tidy_move_legacy_ca_bundle', 'issuer_safety_buffer'],
|
||||
},
|
||||
];
|
||||
// cross cluster operations are only available in enterprise
|
||||
if (this.version.isEnterprise) {
|
||||
shared.push({
|
||||
'Cross-cluster operations': [
|
||||
'tidy_revocation_queue',
|
||||
'tidy_cross_cluster_revoked_certs',
|
||||
'tidy_cmpv2_nonce_store',
|
||||
'revocation_queue_safety_buffer',
|
||||
],
|
||||
});
|
||||
}
|
||||
// auto tidy specific fields
|
||||
const auto = {
|
||||
default: ['interval_duration', 'min_startup_backoff_duration', 'max_startup_backoff_duration'],
|
||||
};
|
||||
|
||||
if (group === 'auto') {
|
||||
return [auto];
|
||||
}
|
||||
if (group === 'manual') {
|
||||
return shared;
|
||||
}
|
||||
// if group is not specified, return combined groups
|
||||
// add enabled field to the top of auto tidy fields for details view
|
||||
auto.default.unshift('enabled');
|
||||
shared.unshift(auto);
|
||||
return shared;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,18 +6,19 @@
|
|||
import Route from '@ember/routing/route';
|
||||
import { service } from '@ember/service';
|
||||
import { withConfig } from 'pki/decorators/check-issuers';
|
||||
import { hash } from 'rsvp';
|
||||
|
||||
@withConfig()
|
||||
export default class PkiTidyRoute extends Route {
|
||||
@service store;
|
||||
@service api;
|
||||
|
||||
model() {
|
||||
async model() {
|
||||
const engine = this.modelFor('application');
|
||||
return hash({
|
||||
const autoTidyConfig = await this.api.secrets.pkiReadAutoTidyConfiguration(engine.id);
|
||||
|
||||
return {
|
||||
hasConfig: this.pkiMountHasConfig,
|
||||
engine,
|
||||
autoTidyConfig: this.store.findRecord('pki/tidy', engine.id),
|
||||
});
|
||||
autoTidyConfig,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,25 +5,27 @@
|
|||
|
||||
import Route from '@ember/routing/route';
|
||||
import { service } from '@ember/service';
|
||||
import { withConfirmLeave } from 'core/decorators/confirm-leave';
|
||||
import PkiTidyForm from 'vault/forms/secrets/pki/tidy';
|
||||
|
||||
@withConfirmLeave()
|
||||
export default class PkiTidyAutoConfigureRoute extends Route {
|
||||
@service store;
|
||||
@service secretMountPath;
|
||||
|
||||
// inherits model from tidy/auto
|
||||
model() {
|
||||
const { autoTidyConfig } = this.modelFor('tidy');
|
||||
return new PkiTidyForm('PkiConfigureAutoTidyRequest', autoTidyConfig);
|
||||
}
|
||||
|
||||
setupController(controller, resolvedModel) {
|
||||
// autoTidyConfig id is the backend path
|
||||
const { id: backend } = resolvedModel;
|
||||
const { currentPath } = this.secretMountPath;
|
||||
super.setupController(controller, resolvedModel);
|
||||
controller.breadcrumbs = [
|
||||
{ label: 'Secrets', route: 'secrets', linkExternal: true },
|
||||
{ label: this.secretMountPath.currentPath, route: 'overview', model: backend },
|
||||
{ label: 'Configuration', route: 'configuration.index', model: backend },
|
||||
{ label: 'Tidy', route: 'tidy', model: backend },
|
||||
{ label: 'Auto', route: 'tidy.auto', model: backend },
|
||||
{ label: currentPath, route: 'overview', model: currentPath },
|
||||
{ label: 'Configuration', route: 'configuration.index', model: currentPath },
|
||||
{ label: 'Tidy', route: 'tidy', model: currentPath },
|
||||
{ label: 'Auto', route: 'tidy.auto', model: currentPath },
|
||||
{ label: 'Configure' },
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,15 +13,14 @@ export default class TidyAutoIndexRoute extends Route {
|
|||
// inherits model from tidy/auto
|
||||
|
||||
setupController(controller, resolvedModel) {
|
||||
// autoTidyConfig id is the backend path
|
||||
const { id: backend } = resolvedModel;
|
||||
super.setupController(...arguments);
|
||||
const { currentPath } = this.secretMountPath;
|
||||
super.setupController(controller, resolvedModel);
|
||||
controller.breadcrumbs = [
|
||||
{ label: 'Secrets', route: 'secrets', linkExternal: true },
|
||||
{ label: this.secretMountPath.currentPath, route: 'overview', model: backend },
|
||||
{ label: 'Tidy', route: 'tidy.index', model: backend },
|
||||
{ label: currentPath, route: 'overview', model: currentPath },
|
||||
{ label: 'Tidy', route: 'tidy.index', model: currentPath },
|
||||
{ label: 'Auto' },
|
||||
];
|
||||
controller.title = this.secretMountPath.currentPath;
|
||||
controller.backend = currentPath;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,35 +4,28 @@
|
|||
*/
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { PKI_DEFAULT_EMPTY_STATE_MSG } from '../overview';
|
||||
import { hash } from 'rsvp';
|
||||
import { service } from '@ember/service';
|
||||
import { PKI_DEFAULT_EMPTY_STATE_MSG } from '../overview';
|
||||
import timestamp from 'core/utils/timestamp';
|
||||
|
||||
export default class PkiTidyIndexRoute extends Route {
|
||||
@service store;
|
||||
@service api;
|
||||
@service secretMountPath;
|
||||
|
||||
async fetchTidyStatus() {
|
||||
const adapter = this.store.adapterFor('application');
|
||||
const tidyStatusResponse = await adapter.ajax(
|
||||
`/v1/${this.secretMountPath.currentPath}/tidy-status`,
|
||||
'GET'
|
||||
);
|
||||
const responseTimestamp = timestamp.now();
|
||||
tidyStatusResponse.data.responseTimestamp = responseTimestamp;
|
||||
return tidyStatusResponse.data;
|
||||
const status = await this.api.secrets.pkiTidyStatus(this.secretMountPath.currentPath);
|
||||
return { ...status, responseTimestamp: timestamp.now() };
|
||||
}
|
||||
|
||||
model() {
|
||||
async model() {
|
||||
const { hasConfig, autoTidyConfig, engine } = this.modelFor('tidy');
|
||||
const tidyStatus = await this.fetchTidyStatus();
|
||||
|
||||
return hash({
|
||||
tidyStatus: this.fetchTidyStatus(),
|
||||
return {
|
||||
tidyStatus,
|
||||
hasConfig,
|
||||
autoTidyConfig,
|
||||
engine,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
setupController(controller, resolvedModel) {
|
||||
|
|
|
|||
|
|
@ -5,24 +5,35 @@
|
|||
|
||||
import Route from '@ember/routing/route';
|
||||
import { service } from '@ember/service';
|
||||
import { withConfirmLeave } from 'core/decorators/confirm-leave';
|
||||
import PkiTidyForm from 'vault/forms/secrets/pki/tidy';
|
||||
|
||||
@withConfirmLeave()
|
||||
export default class PkiTidyManualRoute extends Route {
|
||||
@service store;
|
||||
@service secretMountPath;
|
||||
|
||||
model() {
|
||||
return this.store.createRecord('pki/tidy', { backend: this.secretMountPath.currentPath });
|
||||
return new PkiTidyForm(
|
||||
'PkiTidyRequest',
|
||||
{
|
||||
acme_account_safety_buffer: 2592000,
|
||||
issuer_safety_buffer: 31536000,
|
||||
revocation_queue_safety_buffer: 172800,
|
||||
safety_buffer: 259200,
|
||||
tidy_acme: false,
|
||||
tidy_revocation_queue: false,
|
||||
},
|
||||
{ isNew: true }
|
||||
);
|
||||
}
|
||||
|
||||
setupController(controller, resolvedModel) {
|
||||
super.setupController(controller, resolvedModel);
|
||||
const { currentPath } = this.secretMountPath;
|
||||
controller.breadcrumbs = [
|
||||
{ label: 'Secrets', route: 'secrets', linkExternal: true },
|
||||
{ label: this.secretMountPath.currentPath, route: 'overview', model: resolvedModel.backend },
|
||||
{ label: 'Configuration', route: 'configuration.index', model: resolvedModel.backend },
|
||||
{ label: 'Tidy', route: 'tidy', model: resolvedModel.backend },
|
||||
{ label: currentPath, route: 'overview', model: currentPath },
|
||||
{ label: 'Configuration', route: 'configuration.index', model: currentPath },
|
||||
{ label: 'Tidy', route: 'tidy', model: currentPath },
|
||||
{ label: 'Manual' },
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@
|
|||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
<Page::PkiTidyAutoConfigure @model={{this.model}} @breadcrumbs={{this.breadcrumbs}} />
|
||||
<Page::PkiTidyAutoConfigure @form={{this.model}} @breadcrumbs={{this.breadcrumbs}} />
|
||||
|
|
@ -3,4 +3,4 @@
|
|||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
<Page::PkiTidyAutoSettings @model={{this.model}} @breadcrumbs={{this.breadcrumbs}} />
|
||||
<Page::PkiTidyAutoSettings @model={{this.model}} @backend={{this.backend}} @breadcrumbs={{this.breadcrumbs}} />
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
<PkiPageHeader @backend={{this.model.engine}} />
|
||||
|
||||
{{#if this.model.hasConfig}}
|
||||
<Page::PkiTidyStatus @autoTidyConfig={{this.model.autoTidyConfig}} @tidyStatus={{this.tidyStatus}} />
|
||||
<Page::PkiTidyStatus @enabled={{this.model.autoTidyConfig.enabled}} @tidyStatus={{this.tidyStatus}} />
|
||||
{{else}}
|
||||
<Toolbar />
|
||||
<EmptyState @title="PKI not configured" @message={{this.notConfiguredMessage}}>
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ module('Acceptance | pki tidy', function (hooks) {
|
|||
.exists('Configure manual tidy button exists');
|
||||
await click(PKI_TIDY.tidyConfigureModal.tidyModalManualButton);
|
||||
assert.dom(PKI_TIDY_FORM.tidyFormName('manual')).exists('Manual tidy form exists');
|
||||
await click(PKI_TIDY_FORM.inputByAttr('tidyCertStore'));
|
||||
await click(PKI_TIDY_FORM.inputByAttr('tidy_cert_store'));
|
||||
await fillIn(PKI_TIDY_FORM.tidyPauseDuration, '10');
|
||||
await click(PKI_TIDY_FORM.tidySave);
|
||||
await click(PKI_TIDY.cancelTidyAction);
|
||||
|
|
@ -130,12 +130,12 @@ module('Acceptance | pki tidy', function (hooks) {
|
|||
assert
|
||||
.dom(PKI_TIDY_FORM.tidySectionHeader('ACME operations'))
|
||||
.exists('Auto tidy form enabled shows ACME operations field');
|
||||
await click(PKI_TIDY_FORM.inputByAttr('tidyCertStore'));
|
||||
await click(PKI_TIDY_FORM.inputByAttr('tidy_cert_store'));
|
||||
await click(PKI_TIDY_FORM.tidySave);
|
||||
assert.strictEqual(currentRouteName(), 'vault.cluster.secrets.backend.pki.tidy.auto.index');
|
||||
await click(PKI_TIDY_FORM.editAutoTidyButton);
|
||||
assert.strictEqual(currentRouteName(), 'vault.cluster.secrets.backend.pki.tidy.auto.configure');
|
||||
await click(PKI_TIDY_FORM.inputByAttr('tidyRevokedCerts'));
|
||||
await click(PKI_TIDY_FORM.inputByAttr('tidy_revoked_certs'));
|
||||
await click(PKI_TIDY_FORM.tidySave);
|
||||
assert.strictEqual(currentRouteName(), 'vault.cluster.secrets.backend.pki.tidy.auto.index');
|
||||
});
|
||||
|
|
@ -151,7 +151,7 @@ module('Acceptance | pki tidy', function (hooks) {
|
|||
await click(PKI_TIDY.tidyConfigureModal.tidyModalManualButton);
|
||||
|
||||
assert.dom(PKI_TIDY_FORM.tidyFormName('manual')).exists();
|
||||
await click(PKI_TIDY_FORM.inputByAttr('tidyCertStore'));
|
||||
await click(PKI_TIDY_FORM.inputByAttr('tidy_cert_store'));
|
||||
|
||||
await click(GENERAL.ttl.toggle('Tidy ACME disabled'));
|
||||
assert
|
||||
|
|
@ -190,8 +190,8 @@ module('Acceptance | pki tidy', function (hooks) {
|
|||
assert.dom(PKI_TIDY.tidyConfigureModal.configureTidyModal).exists('Configure tidy modal exists');
|
||||
await click(PKI_TIDY.tidyConfigureModal.tidyModalAutoButton);
|
||||
await click(GENERAL.ttl.toggle('enabled'));
|
||||
await click(PKI_TIDY_FORM.inputByAttr('tidyCertStore'));
|
||||
await click(PKI_TIDY_FORM.inputByAttr('tidyRevokedCerts'));
|
||||
await click(PKI_TIDY_FORM.inputByAttr('tidy_cert_store'));
|
||||
await click(PKI_TIDY_FORM.inputByAttr('tidy_revoked_certs'));
|
||||
await click(PKI_TIDY_FORM.tidySave);
|
||||
await visit(`/vault/secrets-engines/${this.mountPath}/pki/tidy`);
|
||||
assert
|
||||
|
|
|
|||
|
|
@ -15,35 +15,32 @@ module('Integration | Component | page/pki-tidy-auto-settings', function (hooks)
|
|||
setupEngine(hooks, 'pki');
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
const backend = 'pki-auto-tidy';
|
||||
this.backend = backend;
|
||||
|
||||
this.context = { owner: this.engine };
|
||||
this.store = this.owner.lookup('service:store');
|
||||
|
||||
this.backend = 'pki-auto-tidy';
|
||||
this.breadcrumbs = [
|
||||
{ label: 'Secrets', route: 'secrets', linkExternal: true },
|
||||
{ label: backend, route: 'overview', model: backend },
|
||||
{ label: 'Tidy', route: 'tidy.index', model: backend },
|
||||
{ label: this.backend, route: 'overview', model: this.backend },
|
||||
{ label: 'Tidy', route: 'tidy.index', model: this.backend },
|
||||
{ label: 'Auto' },
|
||||
];
|
||||
|
||||
this.model = {
|
||||
enabled: false,
|
||||
interval_duration: '2d',
|
||||
tidy_cert_store: false,
|
||||
tidy_expired_issuers: true,
|
||||
};
|
||||
|
||||
this.renderComponent = () =>
|
||||
render(
|
||||
hbs`<Page::PkiTidyAutoSettings @breadcrumbs={{this.breadcrumbs}} @model={{this.model}} @backend={{this.backend}} />`,
|
||||
{
|
||||
owner: this.engine,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('it renders', async function (assert) {
|
||||
const model = this.store.createRecord('pki/tidy', {
|
||||
backend: this.backend,
|
||||
tidyType: 'auto',
|
||||
enabled: false,
|
||||
intervalDuration: '2d',
|
||||
tidyCertStore: false,
|
||||
tidyExpiredIssuers: true,
|
||||
});
|
||||
this.set('model', model);
|
||||
|
||||
await render(
|
||||
hbs`<Page::PkiTidyAutoSettings @breadcrumbs={{this.breadcrumbs}} @model={{this.model}} />`,
|
||||
this.context
|
||||
);
|
||||
await this.renderComponent();
|
||||
|
||||
assert.dom('[data-test-breadcrumbs] li').exists({ count: 4 }, 'an item exists for each breadcrumb');
|
||||
assert.dom('[data-test-header-title]').hasText('Automatic Tidy Configuration', 'title is correct');
|
||||
|
|
|
|||
|
|
@ -17,14 +17,10 @@ module('Integration | Component | Page::PkiTidyStatus', function (hooks) {
|
|||
setupMirage(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.store = this.owner.lookup('service:store');
|
||||
this.secretMountPath = this.owner.lookup('service:secret-mount-path');
|
||||
this.secretMountPath.currentPath = 'pki-test';
|
||||
this.backend = 'pki-test';
|
||||
this.secretMountPath = this.owner.lookup('service:secret-mount-path').update();
|
||||
|
||||
this.store.createRecord('pki/issuer', { issuerId: 'abcd-efgh' });
|
||||
this.store.createRecord('pki/tidy', { backend: this.secretMountPath.currentPath, tidyType: 'auto' });
|
||||
|
||||
this.autoTidyConfig = this.store.peekAll('pki/tidy');
|
||||
this.enabled = true;
|
||||
this.tidyStatus = {
|
||||
acme_account_deleted_count: 0,
|
||||
acme_account_revoked_count: 0,
|
||||
|
|
@ -54,60 +50,45 @@ module('Integration | Component | Page::PkiTidyStatus', function (hooks) {
|
|||
time_started: '2023-05-18T13:27:36.390959-07:00',
|
||||
};
|
||||
this.engineId = 'pki';
|
||||
|
||||
this.renderComponent = () =>
|
||||
render(hbs`<Page::PkiTidyStatus @enabled={{this.enabled}} @tidyStatus={{this.tidyStatus}} />`, {
|
||||
owner: this.engine,
|
||||
});
|
||||
});
|
||||
|
||||
test('shows the correct titles for the alert banner based on states', async function (assert) {
|
||||
await render(
|
||||
hbs`<Page::PkiTidyStatus @autoTidyConfig={{this.autoTidyConfig}} @tidyStatus={{this.tidyStatus}} />`,
|
||||
{ owner: this.engine }
|
||||
);
|
||||
await this.renderComponent();
|
||||
// running state
|
||||
assert.dom(PKI_TIDY.hdsAlertTitle).hasText('Tidy in progress');
|
||||
assert.dom(PKI_TIDY.cancelTidyAction).exists();
|
||||
assert.dom(PKI_TIDY.hdsAlertButtonText).hasText('Cancel tidy');
|
||||
// inactive state
|
||||
this.tidyStatus.state = 'Inactive';
|
||||
await render(
|
||||
hbs`<Page::PkiTidyStatus @autoTidyConfig={{this.autoTidyConfig}} @tidyStatus={{this.tidyStatus}} />`,
|
||||
{ owner: this.engine }
|
||||
);
|
||||
await this.renderComponent();
|
||||
assert.dom(PKI_TIDY.hdsAlertTitle).hasText('Tidy is inactive');
|
||||
// finished state
|
||||
this.tidyStatus.state = 'Finished';
|
||||
await render(
|
||||
hbs`<Page::PkiTidyStatus @autoTidyConfig={{this.autoTidyConfig}} @tidyStatus={{this.tidyStatus}} />`,
|
||||
{ owner: this.engine }
|
||||
);
|
||||
await this.renderComponent();
|
||||
assert.dom(PKI_TIDY.hdsAlertTitle).hasText('Tidy operation finished');
|
||||
// error state
|
||||
this.tidyStatus.state = 'Error';
|
||||
await render(
|
||||
hbs`<Page::PkiTidyStatus @autoTidyConfig={{this.autoTidyConfig}} @tidyStatus={{this.tidyStatus}} />`,
|
||||
{ owner: this.engine }
|
||||
);
|
||||
await this.renderComponent();
|
||||
assert.dom(PKI_TIDY.hdsAlertTitle).hasText('Tidy operation failed');
|
||||
// cancelling state
|
||||
this.tidyStatus.state = 'Cancelling';
|
||||
await render(
|
||||
hbs`<Page::PkiTidyStatus @autoTidyConfig={{this.autoTidyConfig}} @tidyStatus={{this.tidyStatus}} />`,
|
||||
{ owner: this.engine }
|
||||
);
|
||||
await this.renderComponent();
|
||||
assert.dom(PKI_TIDY.hdsAlertTitle).hasText('Tidy operation cancelling');
|
||||
// cancelled state
|
||||
this.tidyStatus.state = 'Cancelled';
|
||||
await render(
|
||||
hbs`<Page::PkiTidyStatus @autoTidyConfig={{this.autoTidyConfig}} @tidyStatus={{this.tidyStatus}} />`,
|
||||
{ owner: this.engine }
|
||||
);
|
||||
await this.renderComponent();
|
||||
assert.dom(PKI_TIDY.hdsAlertTitle).hasText('Tidy operation cancelled');
|
||||
});
|
||||
|
||||
test('shows the fields even if the data returns null values', async function (assert) {
|
||||
this.tidyStatus.time_started = null;
|
||||
this.tidyStatus.time_finished = null;
|
||||
await render(
|
||||
hbs`<Page::PkiTidyStatus @autoTidyConfig={{this.autoTidyConfig}} @tidyStatus={{this.tidyStatus}} />`,
|
||||
{ owner: this.engine }
|
||||
);
|
||||
await this.renderComponent();
|
||||
assert.dom(PKI_TIDY.timeStartedRow).exists();
|
||||
assert.dom(PKI_TIDY.timeFinishedRow).exists();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,41 +8,60 @@ import { setupRenderingTest } from 'ember-qunit';
|
|||
import { click, render, fillIn } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import { setupEngine } from 'ember-engines/test-support';
|
||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
import { PKI_TIDY_FORM } from 'vault/tests/helpers/pki/pki-selectors';
|
||||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
import { convertToSeconds } from 'core/utils/duration-utils';
|
||||
import PkiTidyForm from 'vault/forms/secrets/pki/tidy';
|
||||
import TidyGroupsHelper from 'pki/helpers/tidy-groups';
|
||||
import tidyFieldLabel from 'pki/helpers/tidy-field-label';
|
||||
import sinon from 'sinon';
|
||||
|
||||
module('Integration | Component | pki tidy form', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
setupEngine(hooks, 'pki');
|
||||
setupMirage(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.store = this.owner.lookup('service:store');
|
||||
this.backend = 'pki-test';
|
||||
this.owner.lookup('service:secret-mount-path').update(this.backend);
|
||||
|
||||
this.version = this.owner.lookup('service:version');
|
||||
this.version.type = 'enterprise';
|
||||
this.server.post('/sys/capabilities-self', () => {});
|
||||
this.onSave = () => {};
|
||||
this.onCancel = () => {};
|
||||
this.manualTidy = this.store.createRecord('pki/tidy', { backend: 'pki-manual-tidy' });
|
||||
|
||||
this.tidyGroupsHelper = new TidyGroupsHelper(this.owner);
|
||||
const groupsReducer = (fields, group) => [...fields, ...Object.values(group)[0]];
|
||||
this.allFields = this.tidyGroupsHelper.compute([]).reduce(groupsReducer, []);
|
||||
this.autoFields = this.tidyGroupsHelper.compute(['auto']).reduce(groupsReducer, []);
|
||||
|
||||
this.onSave = sinon.spy();
|
||||
this.onCancel = sinon.spy();
|
||||
|
||||
const { secrets } = this.owner.lookup('service:api');
|
||||
this.tidyStub = sinon.stub(secrets, 'pkiTidy').resolves();
|
||||
this.autoTidyStub = sinon.stub(secrets, 'pkiConfigureAutoTidy').resolves();
|
||||
// setting defaults here to simulate how this form works in the app.
|
||||
// on init, we retrieve these from the server and pre-populate form
|
||||
this.autoTidyServerDefaults = {
|
||||
enabled: false,
|
||||
acme_account_safety_buffer: '48h',
|
||||
interval_duration: '12h',
|
||||
safety_buffer: '3d',
|
||||
issuer_safety_buffer: '365d',
|
||||
min_startup_backoff_duration: '5m',
|
||||
max_startup_backoff_duration: '15m',
|
||||
revocation_queue_safety_buffer: '10s',
|
||||
pause_duration: '0',
|
||||
};
|
||||
|
||||
this.renderComponent = (type = 'auto') => {
|
||||
this.tidyType = type;
|
||||
const formData = type === 'auto' ? this.autoTidyServerDefaults : {};
|
||||
const schemaKey = type === 'auto' ? 'PkiConfigureAutoTidyRequest' : 'PkiTidyRequest';
|
||||
this.form = new PkiTidyForm(schemaKey, formData, { isNew: type === 'manual' });
|
||||
return render(
|
||||
hbs`<PkiTidyForm @form={{this.form}} @tidyType={{this.tidyType}} @onSave={{this.onSave}} @onCancel={{this.onCancel}} />`,
|
||||
{ owner: this.engine }
|
||||
);
|
||||
};
|
||||
this.store.pushPayload('pki/tidy', {
|
||||
modelName: 'pki/tidy',
|
||||
id: 'pki-auto-tidy',
|
||||
// setting defaults here to simulate how this form works in the app.
|
||||
// on init, we retrieve these from the server and pre-populate form (instead of explicitly set on the model)
|
||||
...this.autoTidyServerDefaults,
|
||||
});
|
||||
this.autoTidy = this.store.peekRecord('pki/tidy', 'pki-auto-tidy');
|
||||
this.numTidyAttrs = Object.keys(this.autoTidy.allByKey).length;
|
||||
});
|
||||
|
||||
test('it hides or shows fields depending on auto-tidy toggle', async function (assert) {
|
||||
|
|
@ -53,31 +72,24 @@ module('Integration | Component | pki tidy form', function (hooks) {
|
|||
'Issuer operations',
|
||||
'Cross-cluster operations',
|
||||
];
|
||||
const loopAssertCount = this.numTidyAttrs * 2 - 3; // loop skips 3 params
|
||||
const headerAssertCount = sectionHeaders.length * 2;
|
||||
|
||||
await this.renderComponent();
|
||||
// the form isn't created until we render so expect assertions need to be after render
|
||||
const loopAssertCount = this.allFields.length * 2 - 3; // loop skips 3 params
|
||||
assert.expect(loopAssertCount + headerAssertCount + 4);
|
||||
|
||||
await render(
|
||||
hbs`
|
||||
<PkiTidyForm
|
||||
@tidy={{this.autoTidy}}
|
||||
@tidyType="auto"
|
||||
@onSave={{this.onSave}}
|
||||
@onCancel={{this.onCancel}}
|
||||
/>
|
||||
`,
|
||||
{ owner: this.engine }
|
||||
);
|
||||
assert.dom(GENERAL.toggleInput('enabled')).isNotChecked();
|
||||
assert
|
||||
.dom(GENERAL.ttl.toggle('enabled'))
|
||||
.hasText('Automatic tidy disabled Automatic tidy operations will not run.');
|
||||
|
||||
this.autoTidy.eachAttribute((attr) => {
|
||||
if (attr === 'enabled') return;
|
||||
assert
|
||||
.dom(PKI_TIDY_FORM.inputByAttr(attr))
|
||||
.doesNotExist(`does not render ${attr} when auto tidy disabled`);
|
||||
this.allFields.forEach((field) => {
|
||||
if (field !== 'enabled') {
|
||||
assert
|
||||
.dom(PKI_TIDY_FORM.inputByAttr(field))
|
||||
.doesNotExist(`does not render ${field} when auto tidy disabled`);
|
||||
}
|
||||
});
|
||||
|
||||
sectionHeaders.forEach((group) => {
|
||||
|
|
@ -89,10 +101,12 @@ module('Integration | Component | pki tidy form', function (hooks) {
|
|||
assert.dom(GENERAL.toggleInput('enabled')).isChecked();
|
||||
assert.dom(GENERAL.ttl.toggle('enabled')).hasText('Automatic tidy enabled');
|
||||
|
||||
this.autoTidy.eachAttribute((attr) => {
|
||||
const skipFields = ['enabled', 'tidyAcme'];
|
||||
if (skipFields.includes(attr)) return; // combined with duration ttl or asserted elsewhere
|
||||
assert.dom(PKI_TIDY_FORM.inputByAttr(attr)).exists(`renders ${attr} when auto tidy enabled`);
|
||||
this.allFields.forEach((field) => {
|
||||
const skipFields = ['enabled', 'tidy_acme'];
|
||||
// combined with duration ttl or asserted elsewhere
|
||||
if (!skipFields.includes(field)) {
|
||||
assert.dom(PKI_TIDY_FORM.inputByAttr(field)).exists(`renders ${field} when auto tidy enabled`);
|
||||
}
|
||||
});
|
||||
|
||||
sectionHeaders.forEach((group) => {
|
||||
|
|
@ -102,74 +116,49 @@ module('Integration | Component | pki tidy form', function (hooks) {
|
|||
|
||||
test('it renders all attribute fields, including enterprise', async function (assert) {
|
||||
assert.expect(35);
|
||||
this.autoTidy.enabled = true;
|
||||
const skipFields = ['enabled', 'tidyAcme']; // combined with duration ttl or asserted separately
|
||||
await render(
|
||||
hbs`
|
||||
<PkiTidyForm
|
||||
@tidy={{this.autoTidy}}
|
||||
@tidyType="auto"
|
||||
@onSave={{this.onSave}}
|
||||
@onCancel={{this.onCancel}}
|
||||
/>
|
||||
`,
|
||||
{ owner: this.engine }
|
||||
);
|
||||
|
||||
this.autoTidy.eachAttribute((attr) => {
|
||||
if (skipFields.includes(attr)) return;
|
||||
assert.dom(PKI_TIDY_FORM.inputByAttr(attr)).exists(`renders ${attr} for auto tidyType`);
|
||||
await this.renderComponent();
|
||||
await click(GENERAL.toggleInput('enabled'));
|
||||
|
||||
const skipFields = ['enabled', 'tidy_acme']; // combined with duration ttl or asserted separately
|
||||
this.allFields.forEach((field) => {
|
||||
if (!skipFields.includes(field)) {
|
||||
assert.dom(PKI_TIDY_FORM.inputByAttr(field)).exists(`renders ${field} for auto tidyType`);
|
||||
}
|
||||
});
|
||||
|
||||
// MANUAL TIDY
|
||||
await render(
|
||||
hbs`
|
||||
<PkiTidyForm
|
||||
@tidy={{this.manualTidy}}
|
||||
@tidyType="manual"
|
||||
@onSave={{this.onSave}}
|
||||
@onCancel={{this.onCancel}}
|
||||
/>
|
||||
`,
|
||||
{ owner: this.engine }
|
||||
);
|
||||
await this.renderComponent('manual');
|
||||
|
||||
assert.dom(GENERAL.toggleInput('enabled')).doesNotExist('hides automatic tidy toggle');
|
||||
|
||||
this.manualTidy.eachAttribute((attr) => {
|
||||
if (skipFields.includes(attr)) return;
|
||||
// auto tidy fields we shouldn't see in the manual tidy form
|
||||
if (this.manualTidy.autoTidyConfigFields.includes(attr)) {
|
||||
assert
|
||||
.dom(PKI_TIDY_FORM.inputByAttr(attr))
|
||||
.doesNotExist(`${attr} should not appear on manual tidyType`);
|
||||
} else {
|
||||
assert.dom(PKI_TIDY_FORM.inputByAttr(attr)).exists(`renders ${attr} for manual tidyType`);
|
||||
this.allFields.forEach((field) => {
|
||||
if (!skipFields.includes(field)) {
|
||||
// auto tidy fields we shouldn't see in the manual tidy form
|
||||
if (this.autoFields.includes(field)) {
|
||||
assert
|
||||
.dom(PKI_TIDY_FORM.inputByAttr(field))
|
||||
.doesNotExist(`${field} should not appear on manual tidyType`);
|
||||
} else {
|
||||
assert.dom(PKI_TIDY_FORM.inputByAttr(field)).exists(`renders ${field} for manual tidyType`);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('it hides enterprise fields for CE', async function (assert) {
|
||||
this.version.type = 'community';
|
||||
this.autoTidy.enabled = true;
|
||||
|
||||
const enterpriseFields = [
|
||||
'tidyRevocationQueue',
|
||||
'tidyCrossClusterRevokedCerts',
|
||||
'revocationQueueSafetyBuffer',
|
||||
'tidy_revocation_queue',
|
||||
'tidy_cross_cluster_revoked_certs',
|
||||
'tidy_cmpv2_nonce_store',
|
||||
'revocation_queue_safety_buffer',
|
||||
];
|
||||
|
||||
// tidyType = auto
|
||||
await render(
|
||||
hbs`
|
||||
<PkiTidyForm
|
||||
@tidy={{this.autoTidy}}
|
||||
@tidyType="auto"
|
||||
@onSave={{this.onSave}}
|
||||
@onCancel={{this.onCancel}}
|
||||
/>
|
||||
`,
|
||||
{ owner: this.engine }
|
||||
);
|
||||
await this.renderComponent();
|
||||
await click(GENERAL.toggleInput('enabled'));
|
||||
|
||||
assert
|
||||
.dom(PKI_TIDY_FORM.tidySectionHeader('Cross-cluster operations'))
|
||||
|
|
@ -182,17 +171,7 @@ module('Integration | Component | pki tidy form', function (hooks) {
|
|||
});
|
||||
|
||||
// tidyType = manual
|
||||
await render(
|
||||
hbs`
|
||||
<PkiTidyForm
|
||||
@tidy={{this.manualTidy}}
|
||||
@tidyType="manual"
|
||||
@onSave={{this.onSave}}
|
||||
@onCancel={{this.onCancel}}
|
||||
/>
|
||||
`,
|
||||
{ owner: this.engine }
|
||||
);
|
||||
await this.renderComponent('manual');
|
||||
|
||||
enterpriseFields.forEach((entAttr) => {
|
||||
assert
|
||||
|
|
@ -201,160 +180,111 @@ module('Integration | Component | pki tidy form', function (hooks) {
|
|||
});
|
||||
});
|
||||
|
||||
test('it should change the attributes on the model', async function (assert) {
|
||||
assert.expect(12);
|
||||
test('it should update form values', async function (assert) {
|
||||
assert.expect(11);
|
||||
// ttl picker defaults to seconds, unless unit is set by default value (set in beforeEach hook)
|
||||
// on submit, any user inputted values should be converted to seconds for the payload
|
||||
const fillInValues = {
|
||||
acmeAccountSafetyBuffer: { time: 680, unit: 'h' },
|
||||
intervalDuration: { time: 10, unit: 'h' },
|
||||
issuerSafetyBuffer: { time: 20, unit: 'd' },
|
||||
maxStartupBackoffDuration: { time: 30, unit: 'm' },
|
||||
minStartupBackoffDuration: { time: 10, unit: 'm' },
|
||||
pauseDuration: { time: 30, unit: 's' },
|
||||
revocationQueueSafetyBuffer: { time: 40, unit: 's' },
|
||||
safetyBuffer: { time: 50, unit: 'd' },
|
||||
acme_account_safety_buffer: { time: 680, unit: 'h' },
|
||||
interval_duration: { time: 10, unit: 'h' },
|
||||
issuer_safety_buffer: { time: 20, unit: 'd' },
|
||||
max_startup_backoff_duration: { time: 30, unit: 'm' },
|
||||
min_startup_backoff_duration: { time: 10, unit: 'm' },
|
||||
pause_duration: { time: 30, unit: 's' },
|
||||
revocation_queue_safety_buffer: { time: 40, unit: 's' },
|
||||
safety_buffer: { time: 50, unit: 'd' },
|
||||
};
|
||||
const calcValue = (param) => {
|
||||
const { time, unit } = fillInValues[param];
|
||||
return `${convertToSeconds(time, unit)}s`;
|
||||
};
|
||||
this.server.post('/pki-auto-tidy/config/auto-tidy', (schema, req) => {
|
||||
assert.propEqual(
|
||||
JSON.parse(req.requestBody),
|
||||
{
|
||||
acme_account_safety_buffer: '48h',
|
||||
enabled: true,
|
||||
min_startup_backoff_duration: calcValue('minStartupBackoffDuration'),
|
||||
max_startup_backoff_duration: calcValue('maxStartupBackoffDuration'),
|
||||
interval_duration: calcValue('intervalDuration'),
|
||||
issuer_safety_buffer: calcValue('issuerSafetyBuffer'),
|
||||
pause_duration: calcValue('pauseDuration'),
|
||||
revocation_queue_safety_buffer: calcValue('revocationQueueSafetyBuffer'),
|
||||
safety_buffer: calcValue('safetyBuffer'),
|
||||
tidy_acme: true,
|
||||
tidy_cert_metadata: true,
|
||||
tidy_cert_store: true,
|
||||
tidy_cmpv2_nonce_store: true,
|
||||
tidy_cross_cluster_revoked_certs: true,
|
||||
tidy_expired_issuers: true,
|
||||
tidy_move_legacy_ca_bundle: true,
|
||||
tidy_revocation_queue: true,
|
||||
tidy_revoked_cert_issuer_associations: true,
|
||||
tidy_revoked_certs: true,
|
||||
},
|
||||
'response contains updated model values'
|
||||
);
|
||||
});
|
||||
await render(
|
||||
hbs`
|
||||
<PkiTidyForm
|
||||
@tidy={{this.autoTidy}}
|
||||
@tidyType="auto"
|
||||
@onSave={{this.onSave}}
|
||||
@onCancel={{this.onCancel}}
|
||||
/>
|
||||
`,
|
||||
{ owner: this.engine }
|
||||
);
|
||||
|
||||
await this.renderComponent();
|
||||
|
||||
assert.dom(GENERAL.toggleInput('enabled')).isNotChecked();
|
||||
assert.dom(GENERAL.ttl.toggle('enabled')).hasTextContaining('Automatic tidy disabled');
|
||||
assert.false(this.autoTidy.enabled, 'enabled is false on model');
|
||||
assert.false(this.form.data.enabled, 'enabled is false on form');
|
||||
|
||||
// enable auto-tidy
|
||||
await click(GENERAL.toggleInput('enabled'));
|
||||
assert.dom(GENERAL.toggleInput('enabled')).isChecked();
|
||||
assert.dom(GENERAL.ttl.toggle('enabled')).hasText('Automatic tidy enabled');
|
||||
|
||||
assert.dom(PKI_TIDY_FORM.toggleInput('acmeAccountSafetyBuffer')).isNotChecked('ACME tidy is disabled');
|
||||
assert.dom(PKI_TIDY_FORM.toggleInput('acme_account_safety_buffer')).isNotChecked('ACME tidy is disabled');
|
||||
assert
|
||||
.dom(PKI_TIDY_FORM.toggleLabel('Tidy ACME disabled'))
|
||||
.exists('ACME label has correct disabled text');
|
||||
assert.false(this.autoTidy.tidyAcme, 'tidyAcme is false on model');
|
||||
|
||||
await click(PKI_TIDY_FORM.toggleInput('acmeAccountSafetyBuffer'));
|
||||
await click(PKI_TIDY_FORM.toggleInput('acme_account_safety_buffer'));
|
||||
await fillIn(PKI_TIDY_FORM.acmeAccountSafetyBuffer, 2); // units are days based on defaultValue
|
||||
assert.dom(PKI_TIDY_FORM.toggleInput('acmeAccountSafetyBuffer')).isChecked('ACME tidy is enabled');
|
||||
assert.dom(PKI_TIDY_FORM.toggleInput('acme_account_safety_buffer')).isChecked('ACME tidy is enabled');
|
||||
assert.dom(PKI_TIDY_FORM.toggleLabel('Tidy ACME enabled')).exists('ACME label has correct enabled text');
|
||||
assert.true(this.autoTidy.tidyAcme, 'tidyAcme toggles to true');
|
||||
assert.true(this.form.data.tidy_acme, 'tidy_acme toggles to true');
|
||||
|
||||
this.autoTidy.eachAttribute(async (attr, { type }) => {
|
||||
const skipFields = ['enabled', 'tidyAcme', 'acmeAccountSafetyBuffer']; // combined with duration ttl or asserted separately
|
||||
if (skipFields.includes(attr)) return;
|
||||
// all params right now are either a boolean or TTL, this if/else will need to be updated if that changes
|
||||
if (type === 'boolean') {
|
||||
await click(PKI_TIDY_FORM.inputByAttr(attr));
|
||||
} else {
|
||||
const { time } = fillInValues[attr];
|
||||
await fillIn(PKI_TIDY_FORM.toggleInput(attr), `${time}`);
|
||||
for (const field of this.allFields) {
|
||||
const skipFields = ['enabled', 'tidy_acme', 'acme_account_safety_buffer']; // combined with duration ttl or asserted separately
|
||||
if (!skipFields.includes(field)) {
|
||||
// all params right now are either a boolean or TTL, this if/else will need to be updated if that changes
|
||||
if (Object.keys(fillInValues).includes(field)) {
|
||||
const { time } = fillInValues[field];
|
||||
await fillIn(GENERAL.ttl.input(tidyFieldLabel(field)), `${time}`);
|
||||
} else {
|
||||
await click(PKI_TIDY_FORM.inputByAttr(field));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await click(PKI_TIDY_FORM.tidySave);
|
||||
const payload = {
|
||||
acme_account_safety_buffer: '48h',
|
||||
enabled: true,
|
||||
min_startup_backoff_duration: calcValue('min_startup_backoff_duration'),
|
||||
max_startup_backoff_duration: calcValue('max_startup_backoff_duration'),
|
||||
interval_duration: calcValue('interval_duration'),
|
||||
issuer_safety_buffer: calcValue('issuer_safety_buffer'),
|
||||
pause_duration: calcValue('pause_duration'),
|
||||
revocation_queue_safety_buffer: calcValue('revocation_queue_safety_buffer'),
|
||||
safety_buffer: calcValue('safety_buffer'),
|
||||
tidy_acme: true,
|
||||
tidy_cert_metadata: true,
|
||||
tidy_cert_store: true,
|
||||
tidy_cmpv2_nonce_store: true,
|
||||
tidy_cross_cluster_revoked_certs: true,
|
||||
tidy_expired_issuers: true,
|
||||
tidy_move_legacy_ca_bundle: true,
|
||||
tidy_revocation_queue: true,
|
||||
tidy_revoked_cert_issuer_associations: true,
|
||||
tidy_revoked_certs: true,
|
||||
};
|
||||
assert.true(this.autoTidyStub.calledWith(this.backend, payload), 'API called with correct payload');
|
||||
});
|
||||
|
||||
test('it updates auto-tidy config', async function (assert) {
|
||||
assert.expect(4);
|
||||
this.server.post('/pki-auto-tidy/config/auto-tidy', (schema, req) => {
|
||||
assert.ok(true, 'Request made to update auto-tidy');
|
||||
assert.propEqual(
|
||||
JSON.parse(req.requestBody),
|
||||
{
|
||||
...this.autoTidyServerDefaults,
|
||||
acme_account_safety_buffer: '720h',
|
||||
tidy_acme: false,
|
||||
},
|
||||
'response contains default auto-tidy params'
|
||||
);
|
||||
});
|
||||
this.onSave = () => assert.ok(true, 'onSave callback fires on save success');
|
||||
this.onCancel = () => assert.ok(true, 'onCancel callback fires on save success');
|
||||
assert.expect(3);
|
||||
|
||||
await render(
|
||||
hbs`
|
||||
<PkiTidyForm
|
||||
@tidy={{this.autoTidy}}
|
||||
@tidyType="auto"
|
||||
@onSave={{this.onSave}}
|
||||
@onCancel={{this.onCancel}}
|
||||
/>
|
||||
`,
|
||||
{ owner: this.engine }
|
||||
);
|
||||
await this.renderComponent();
|
||||
|
||||
await click(PKI_TIDY_FORM.tidySave);
|
||||
assert.true(
|
||||
this.autoTidyStub.calledWith(this.backend, this.autoTidyServerDefaults),
|
||||
'API called with correct payload'
|
||||
);
|
||||
assert.true(this.onSave.called, 'onSave called on save success');
|
||||
|
||||
await click(PKI_TIDY_FORM.tidyCancel);
|
||||
assert.true(this.onCancel.called, 'onCancel called on click');
|
||||
});
|
||||
|
||||
test('it saves and performs manual tidy', async function (assert) {
|
||||
assert.expect(4);
|
||||
assert.expect(3);
|
||||
|
||||
this.server.post('/pki-manual-tidy/tidy', (schema, req) => {
|
||||
assert.ok(true, 'Request made to perform manual tidy');
|
||||
assert.propEqual(
|
||||
JSON.parse(req.requestBody),
|
||||
{ acme_account_safety_buffer: '720h', tidy_acme: false },
|
||||
'response contains manual tidy params'
|
||||
);
|
||||
return { id: 'pki-manual-tidy' };
|
||||
});
|
||||
this.onSave = () => assert.ok(true, 'onSave callback fires on save success');
|
||||
this.onCancel = () => assert.ok(true, 'onCancel callback fires on save success');
|
||||
|
||||
await render(
|
||||
hbs`
|
||||
<PkiTidyForm
|
||||
@tidy={{this.manualTidy}}
|
||||
@tidyType="manual"
|
||||
@onSave={{this.onSave}}
|
||||
@onCancel={{this.onCancel}}
|
||||
/>
|
||||
`,
|
||||
{ owner: this.engine }
|
||||
);
|
||||
await this.renderComponent('manual');
|
||||
|
||||
await click(PKI_TIDY_FORM.tidySave);
|
||||
assert.true(this.tidyStub.calledWith(this.backend), 'API called with correct payload');
|
||||
assert.true(this.onSave.called, 'onSave called on save success');
|
||||
|
||||
await click(PKI_TIDY_FORM.tidyCancel);
|
||||
assert.true(this.onCancel.called, 'onCancel called on click');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue