UI: VAULT-9408 Delete all issuers toolbar action + modal (#19756)

This commit is contained in:
Kianna
2023-04-05 14:25:55 -07:00
committed by GitHub
parent fa5f0e6222
commit e279d538c2
24 changed files with 449 additions and 50 deletions

View File

@@ -47,4 +47,10 @@ export default class PkiIssuerAdapter extends ApplicationAdapter {
const { backend, id } = query;
return this.ajax(this.urlForQuery(backend, id), 'GET', this.optionsForQuery(id));
}
deleteAllIssuers(backend) {
const deleteAllIssuersAndKeysUrl = `${this.buildURL()}/${encodePath(backend)}/root`;
return this.ajax(deleteAllIssuersAndKeysUrl, 'DELETE');
}
}

View File

@@ -150,6 +150,7 @@ export default class PkiIssuerModel extends Model {
@lazyCapabilities(apiPath`${'backend'}/root/rotate/exported`) rotateExported;
@lazyCapabilities(apiPath`${'backend'}/root/rotate/internal`) rotateInternal;
@lazyCapabilities(apiPath`${'backend'}/root/rotate/existing`) rotateExisting;
@lazyCapabilities(apiPath`${'backend'}/root`, 'backend') deletePath;
@lazyCapabilities(apiPath`${'backend'}/intermediate/cross-sign`) crossSignPath;
@lazyCapabilities(apiPath`${'backend'}/issuer/${'issuerId'}/sign-intermediate`) signIntermediate;
get canRotateIssuer() {
@@ -168,4 +169,7 @@ export default class PkiIssuerModel extends Model {
get canConfigure() {
return this.issuerPath.get('canUpdate') !== false;
}
get canDeleteAllIssuers() {
return this.deletePath.get('isLoading') || this.deletePath.get('canDelete') !== false;
}
}

View File

@@ -222,6 +222,9 @@
.has-background-gray-200 {
background-color: $ui-gray-200;
}
.has-background-gray-100 {
background-color: $ui-gray-100;
}
@each $name, $pair in $colors {
$color: nth($pair, 1);
.has-background-#{$name} {

View File

@@ -1,25 +1,56 @@
<h2 class="title has-bottom-margin-xs has-top-margin-m is-4 has-border-bottom-light has-bottom-padding-s">
Global URLs
</h2>
<InfoTableRow @label="Issuing certificates" @value={{or @urls.issuingCertificates "None"}} />
<InfoTableRow
@label="CRL distribution points"
@value={{if @urls.crlDistributionPoints @urls.crlDistributionPoints "None"}}
/>
<Toolbar>
<ToolbarActions>
{{#if @canDeleteAllIssuers}}
<button
type="button"
class="toolbar-link"
{{on "click" (fn (mut this.showDeleteAllIssuers) true)}}
data-test-delete-all-issuers-link
>
Delete all issuers
<Icon @name="chevron-right" />
<h2 class="title has-bottom-margin-xs has-top-margin-xl is-4 has-border-bottom-light has-bottom-padding-s">
Certificate Revocation List (CRL)
</h2>
<InfoTableRow @label="Expiry" @value={{@crl.expiry}} />
<InfoTableRow @label="Auto-rebuild" @value={{if @crl.autoRebuild "On" "Off"}} />
</button>
<div class="toolbar-separator"></div>
{{/if}}
<ToolbarLink @route="configuration.tidy">
Tidy
</ToolbarLink>
<ToolbarLink @route="configuration.edit">
Edit configuration
</ToolbarLink>
</ToolbarActions>
</Toolbar>
<h2 class="title has-bottom-margin-xs has-top-margin-xl is-4 has-border-bottom-light has-bottom-padding-s">
Online Certificate Status Protocol (OCSP)
</h2>
<InfoTableRow @label="Responder APIs" @value={{if @crl.ocspDisable "Disabled" "Enabled"}} />
<InfoTableRow @label="Interval" @value={{@crl.ocspExpiry}} />
{{#if (not (eq @urls 403))}}
<h2 class="title is-4 has-bottom-margin-xs has-top-margin-m has-border-bottom-light has-bottom-padding-s">
Global URLs
</h2>
<InfoTableRow @label="Issuing certificates" @value={{or @urls.issuingCertificates "None"}} />
<InfoTableRow
@label="CRL distribution points"
@value={{if @urls.crlDistributionPoints @urls.crlDistributionPoints "None"}}
/>
{{/if}}
<h2 class="title has-bottom-margin-xs has-top-margin-xl is-4 has-border-bottom-light has-bottom-padding-s">
{{#if (not (eq @crl 403))}}
<h2 class="title is-4 has-bottom-margin-xs has-top-margin-xl has-border-bottom-light has-bottom-padding-s">
Certificate Revocation List (CRL)
</h2>
<InfoTableRow @label="Expiry" @value={{@crl.expiry}} />
<InfoTableRow @label="Auto-rebuild" @value={{if @crl.autoRebuild "On" "Off"}} />
<h2 class="title is-4 has-bottom-margin-xs has-top-margin-xl has-border-bottom-light has-bottom-padding-s">
Online Certificate Status Protocol (OCSP)
</h2>
<InfoTableRow @label="Responder APIs" @value={{if @crl.ocspDisable "Disabled" "Enabled"}} />
<InfoTableRow @label="Interval" @value={{@crl.ocspExpiry}} />
{{/if}}
<h2
class="title is-4 has-bottom-margin-xs has-border-bottom-light has-bottom-padding-s
{{if (and (eq @crl 403) (eq @urls 403)) 'has-top-margin-m' 'has-top-margin-xl'}}"
>
Mount Configuration
</h2>
<InfoTableRow @label="Secret engine type" @value={{@mountConfig.engineType}} />
@@ -30,4 +61,23 @@
<InfoTableRow @label="Default lease TTL" @value={{@mountConfig.config.defaultLeaseTtl}} />
<InfoTableRow @label="Max lease TTL" @value={{@mountConfig.config.maxLeaseTtl}} />
<InfoTableRow @label="Allowed managed keys" @value={{or @mountConfig.config.allowedManagedKeys "None"}} />
<div class="has-top-margin-l"></div>
<div class="has-top-margin-l"></div>
{{#if this.showDeleteAllIssuers}}
<ConfirmationModal
@title="Delete All Issuers?"
@toConfirmMsg="deleting all issuers and keys."
@buttonText="Confirm"
@confirmText="delete-all"
@isActive={{this.showDeleteAllIssuers}}
@onClose={{action (mut this.showDeleteAllIssuers) false}}
@onConfirm={{this.deleteAllIssuers}}
>
<section class="modal-card-custom">
This endpoint deletes
<strong>all</strong>
issuers and keys within the mount. It is highly recommended to use the individual delete operations instead. This mount
will be unusable until new issuers and keys are provisioned.
</section>
</ConfirmationModal>
{{/if}}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import Component from '@glimmer/component';
import errorMessage from 'vault/utils/error-message';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
//TYPES
import RouterService from '@ember/routing/router-service';
import FlashMessageService from 'vault/services/flash-messages';
import Store from '@ember-data/store';
interface Args {
currentPath: string;
}
export default class PkiConfigurationDetails extends Component<Args> {
@service declare readonly store: Store;
@service declare readonly router: RouterService;
@service declare readonly flashMessages: FlashMessageService;
@tracked showDeleteAllIssuers = false;
@action
async deleteAllIssuers() {
try {
const issuerAdapter = this.store.adapterFor('pki/issuer');
await issuerAdapter.deleteAllIssuers(this.args.currentPath);
this.flashMessages.success('Successfully deleted all issuers and keys');
this.showDeleteAllIssuers = false;
this.router.transitionTo('vault.cluster.secrets.backend.pki.configuration.index');
} catch (error) {
this.showDeleteAllIssuers = false;
this.flashMessages.danger(errorMessage(error));
}
}
}

View File

@@ -7,6 +7,7 @@ import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { withConfig } from 'pki/decorators/check-config';
import { hash } from 'rsvp';
import { getCliMessage } from 'pki/routes/overview';
@withConfig()
export default class PkiCertificatesIndexRoute extends Route {
@@ -32,4 +33,12 @@ export default class PkiCertificatesIndexRoute extends Route {
parentModel: this.modelFor('certificates'),
});
}
setupController(controller, resolvedModel) {
super.setupController(controller, resolvedModel);
const certificates = resolvedModel.certificates;
if (certificates?.length) controller.message = getCliMessage('certificates');
else controller.message = getCliMessage();
}
}

View File

@@ -7,21 +7,27 @@ import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { withConfig } from 'pki/decorators/check-config';
import { hash } from 'rsvp';
import { PKI_DEFAULT_EMPTY_STATE_MSG } from 'pki/routes/overview';
@withConfig()
export default class ConfigurationIndexRoute extends Route {
@service store;
@service secretMountPath;
model() {
const backend = this.secretMountPath.currentPath;
return hash({
hasConfig: this.shouldPromptConfig,
engine: this.modelFor('application'),
urls: this.store.findRecord('pki/urls', backend),
crl: this.store.findRecord('pki/crl', backend),
mountConfig: this.fetchMountConfig(backend),
});
async fetchUrls(backend) {
try {
return await this.store.findRecord('pki/urls', backend);
} catch (e) {
return e.httpStatus;
}
}
async fetchCrl(backend) {
try {
return await this.store.findRecord('pki/crl', backend);
} catch (e) {
return e.httpStatus;
}
}
async fetchMountConfig(path) {
@@ -31,4 +37,23 @@ export default class ConfigurationIndexRoute extends Route {
return mountConfig.get('firstObject');
}
}
async model() {
const backend = this.secretMountPath.currentPath;
return hash({
hasConfig: this.shouldPromptConfig,
engine: this.modelFor('application'),
urls: this.fetchUrls(backend),
crl: this.fetchCrl(backend),
mountConfig: this.fetchMountConfig(backend),
issuerModel: this.store.createRecord('pki/issuer'),
});
}
setupController(controller, resolvedModel) {
super.setupController(controller, resolvedModel);
controller.message = PKI_DEFAULT_EMPTY_STATE_MSG;
}
}

View File

@@ -5,6 +5,7 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { PKI_DEFAULT_EMPTY_STATE_MSG } from 'pki/routes/overview';
export default class PkiIssuersListRoute extends Route {
@service store;
@@ -38,5 +39,6 @@ export default class PkiIssuersListRoute extends Route {
{ label: this.secretMountPath.currentPath, route: 'overview' },
{ label: 'issuers', route: 'issuers.index' },
];
controller.message = PKI_DEFAULT_EMPTY_STATE_MSG;
}
}

View File

@@ -7,6 +7,7 @@ import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { withConfig } from 'pki/decorators/check-config';
import { hash } from 'rsvp';
import { PKI_DEFAULT_EMPTY_STATE_MSG } from 'pki/routes/overview';
@withConfig()
export default class PkiKeysIndexRoute extends Route {
@@ -40,5 +41,6 @@ export default class PkiKeysIndexRoute extends Route {
{ label: this.secretMountPath.currentPath, route: 'overview' },
{ label: 'keys', route: 'keys.index' },
];
controller.message = PKI_DEFAULT_EMPTY_STATE_MSG;
}
}

View File

@@ -8,12 +8,29 @@ import { inject as service } from '@ember/service';
import { withConfig } from 'pki/decorators/check-config';
import { hash } from 'rsvp';
export const PKI_DEFAULT_EMPTY_STATE_MSG =
"This PKI mount hasn't yet been configured with a certificate issuer.";
export const getCliMessage = (msg) => {
if (!msg) return PKI_DEFAULT_EMPTY_STATE_MSG;
return `${PKI_DEFAULT_EMPTY_STATE_MSG} There are existing ${msg}. Use the CLI to perform any operations with them until an issuer is configured.`;
};
@withConfig()
export default class PkiOverviewRoute extends Route {
@service secretMountPath;
@service auth;
@service store;
async fetchAllCertificates() {
try {
return await this.store.query('pki/certificate/base', { backend: this.secretMountPath.currentPath });
} catch (e) {
return e.httpStatus;
}
}
async fetchAllRoles() {
try {
return await this.store.query('pki/role', { backend: this.secretMountPath.currentPath });
@@ -36,10 +53,19 @@ export default class PkiOverviewRoute extends Route {
engine: this.modelFor('application'),
roles: this.fetchAllRoles(),
issuers: this.fetchAllIssuers(),
certificates: this.fetchAllCertificates(),
});
}
setupController(controller, resolvedModel) {
super.setupController(controller, resolvedModel);
const roles = resolvedModel.roles;
const certificates = resolvedModel.certificates;
controller.message = getCliMessage();
if (roles?.length) controller.message = getCliMessage('roles');
if (certificates?.length) controller.message = getCliMessage('certificates');
if (roles?.length && certificates?.length) controller.message = getCliMessage('roles and certificates');
}
}

View File

@@ -7,7 +7,7 @@ import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { withConfig } from 'pki/decorators/check-config';
import { hash } from 'rsvp';
import { getCliMessage } from 'pki/routes/overview';
@withConfig()
export default class PkiRolesIndexRoute extends Route {
@service store;
@@ -32,4 +32,12 @@ export default class PkiRolesIndexRoute extends Route {
parentModel: this.modelFor('roles'),
});
}
setupController(controller, resolvedModel) {
super.setupController(controller, resolvedModel);
const roles = resolvedModel.roles;
if (roles?.length) controller.message = getCliMessage('roles');
else controller.message = getCliMessage();
}
}

View File

@@ -68,7 +68,7 @@
</EmptyState>
{{/if}}
{{else}}
<EmptyState @title="PKI not configured" @message="This PKI mount hasn't yet been configured with a certificate issuer.">
<EmptyState @title="PKI not configured" @message={{this.message}}>
<LinkTo @route="configuration.create">
Configure PKI
</LinkTo>

View File

@@ -10,22 +10,16 @@
/>
{{#if this.model.hasConfig}}
<Toolbar>
<ToolbarActions>
<ToolbarLink @route="configuration.tidy">
Tidy
</ToolbarLink>
<ToolbarLink @route="configuration.edit">
Edit configuration
</ToolbarLink>
</ToolbarActions>
</Toolbar>
<Page::PkiConfigurationDetails @urls={{this.model.urls}} @crl={{this.model.crl}} @mountConfig={{this.model.mountConfig}} />
<Page::PkiConfigurationDetails
@urls={{this.model.urls}}
@crl={{this.model.crl}}
@mountConfig={{this.model.mountConfig}}
@currentPath={{this.model.engine.id}}
@canDeleteAllIssuers={{this.model.issuerModel.canDeleteAllIssuers}}
/>
{{else}}
<Toolbar />
<EmptyState @title="PKI not configured" @message="This PKI mount hasn't yet been configured with a certificate issuer.">
<EmptyState @title="PKI not configured" @message={{this.message}}>
<LinkTo @route="configuration.create">
Configure PKI
</LinkTo>

View File

@@ -93,7 +93,7 @@
</LinkedBlock>
{{/each}}
{{else}}
<EmptyState @title="PKI not configured" @message="This PKI mount hasn't yet been configured with a certificate issuer.">
<EmptyState @title="PKI not configured" @message={{this.message}}>
<LinkTo @route="configuration.create">
Configure PKI
</LinkTo>

View File

@@ -19,7 +19,7 @@
/>
{{else}}
<Toolbar />
<EmptyState @title="PKI not configured" @message="This PKI mount hasn't yet been configured with a certificate issuer.">
<EmptyState @title="PKI not configured" @message={{this.message}}>
<LinkTo @route="configuration.create">
Configure PKI
</LinkTo>

View File

@@ -21,8 +21,8 @@
{{#if this.model.hasConfig}}
<Page::PkiOverview @issuers={{this.model.issuers}} @roles={{this.model.roles}} @engine={{this.model.engine}} />
{{else}}
<EmptyState @title="PKI not configured" @message="This PKI mount hasn't yet been configured with a certificate issuer.">
<LinkTo @route="configuration.create" @model={{this.model.engine}}>
<EmptyState @title="PKI not configured" @message={{this.message}}>
<LinkTo @route="configuration.create">
Configure PKI
</LinkTo>
</EmptyState>

View File

@@ -65,7 +65,7 @@
</EmptyState>
{{/if}}
{{else}}
<EmptyState @title="PKI not configured" @message="This PKI mount hasn't yet been configured with a certificate issuer.">
<EmptyState @title="PKI not configured" @message={{this.message}}>
<LinkTo @route="configuration.create">
Configure PKI
</LinkTo>

View File

@@ -0,0 +1,197 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support';
import { click, currentURL, fillIn, visit, isSettled, waitUntil } from '@ember/test-helpers';
import { v4 as uuidv4 } from 'uuid';
import authPage from 'vault/tests/pages/auth';
import logout from 'vault/tests/pages/logout';
import enablePage from 'vault/tests/pages/settings/mount-secret-backend';
import { runCommands } from 'vault/tests/helpers/pki/pki-run-commands';
import { SELECTORS } from 'vault/tests/helpers/pki/workflow';
import { issuerPemBundle } from 'vault/tests/helpers/pki/values';
module('Acceptance | pki configuration', function (hooks) {
setupApplicationTest(hooks);
hooks.beforeEach(async function () {
this.pemBundle = issuerPemBundle;
await authPage.login();
// Setup PKI engine
const mountPath = `pki-workflow-${uuidv4()}`;
await enablePage.enable('pki', mountPath);
this.mountPath = mountPath;
await logout.visit();
});
hooks.afterEach(async function () {
await logout.visit();
await authPage.login();
// Cleanup engine
await runCommands([`delete sys/mounts/${this.mountPath}`]);
await logout.visit();
});
module('delete all issuers modal and empty states', function (hooks) {
setupMirage(hooks);
test('it shows the delete all issuers modal', async function (assert) {
await authPage.login(this.pkiAdminToken);
await visit(`/vault/secrets/${this.mountPath}/pki/configuration`);
await click(SELECTORS.emptyStateLink);
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/configuration/create`);
await isSettled();
await click(SELECTORS.configuration.generateRootOption);
await fillIn(SELECTORS.configuration.typeField, 'exported');
await fillIn(SELECTORS.configuration.generateRootCommonNameField, 'issuer-common-0');
await fillIn(SELECTORS.configuration.generateRootIssuerNameField, 'issuer-0');
await click(SELECTORS.configuration.generateRootSave);
await click(SELECTORS.configuration.doneButton);
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/overview`);
await isSettled();
await click(SELECTORS.configTab);
await isSettled();
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/configuration`);
await click(SELECTORS.configuration.issuerLink);
await isSettled();
assert.dom(SELECTORS.configuration.deleteAllIssuerModal).exists();
await fillIn(SELECTORS.configuration.deleteAllIssuerInput, 'delete-all');
await click(SELECTORS.configuration.deleteAllIssuerButton);
assert.dom(SELECTORS.configuration.deleteAllIssuerModal).doesNotExist();
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/configuration`);
});
test('it shows the correct empty state message if certificates exists after delete all issuers', async function (assert) {
await authPage.login(this.pkiAdminToken);
await visit(`/vault/secrets/${this.mountPath}/pki/configuration`);
await click(SELECTORS.emptyStateLink);
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/configuration/create`);
await click(SELECTORS.configuration.generateRootOption);
await fillIn(SELECTORS.configuration.typeField, 'exported');
await fillIn(SELECTORS.configuration.generateRootCommonNameField, 'issuer-common-0');
await fillIn(SELECTORS.configuration.generateRootIssuerNameField, 'issuer-0');
await click(SELECTORS.configuration.generateRootSave);
await click(SELECTORS.configuration.doneButton);
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/overview`);
await click(SELECTORS.configTab);
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/configuration`);
await click(SELECTORS.configuration.issuerLink);
assert.dom(SELECTORS.configuration.deleteAllIssuerModal).exists();
await fillIn(SELECTORS.configuration.deleteAllIssuerInput, 'delete-all');
await click(SELECTORS.configuration.deleteAllIssuerButton);
await isSettled();
assert.dom(SELECTORS.configuration.deleteAllIssuerModal).doesNotExist();
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/configuration`);
await isSettled();
await visit(`/vault/secrets/${this.mountPath}/pki/overview`);
await waitUntil(() => currentURL() === `/vault/secrets/${this.mountPath}/pki/overview`);
await isSettled();
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/overview`);
assert
.dom(SELECTORS.emptyStateMessage)
.hasText(
"This PKI mount hasn't yet been configured with a certificate issuer. There are existing certificates. Use the CLI to perform any operations with them until an issuer is configured."
);
await visit(`/vault/secrets/${this.mountPath}/pki/roles`);
await isSettled();
assert
.dom(SELECTORS.emptyStateMessage)
.hasText("This PKI mount hasn't yet been configured with a certificate issuer.");
await visit(`/vault/secrets/${this.mountPath}/pki/issuers`);
await isSettled();
assert
.dom(SELECTORS.emptyStateMessage)
.hasText("This PKI mount hasn't yet been configured with a certificate issuer.");
await visit(`/vault/secrets/${this.mountPath}/pki/keys`);
await isSettled();
assert
.dom(SELECTORS.emptyStateMessage)
.hasText("This PKI mount hasn't yet been configured with a certificate issuer.");
await visit(`/vault/secrets/${this.mountPath}/pki/certificates`);
await isSettled();
assert
.dom(SELECTORS.emptyStateMessage)
.hasText(
"This PKI mount hasn't yet been configured with a certificate issuer. There are existing certificates. Use the CLI to perform any operations with them until an issuer is configured."
);
});
test('it shows the correct empty state message if roles and certificates exists after delete all issuers', async function (assert) {
await authPage.login(this.pkiAdminToken);
await visit(`/vault/secrets/${this.mountPath}/pki/configuration`);
await click(SELECTORS.emptyStateLink);
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/configuration/create`);
await click(SELECTORS.configuration.generateRootOption);
await fillIn(SELECTORS.configuration.typeField, 'exported');
await fillIn(SELECTORS.configuration.generateRootCommonNameField, 'issuer-common-0');
await fillIn(SELECTORS.configuration.generateRootIssuerNameField, 'issuer-0');
await click(SELECTORS.configuration.generateRootSave);
await click(SELECTORS.configuration.doneButton);
await runCommands([
`write ${this.mountPath}/roles/some-role \
issuer_ref="default" \
allowed_domains="example.com" \
allow_subdomains=true \
max_ttl="720h"`,
]);
await runCommands([`write ${this.mountPath}/root/generate/internal common_name="Hashicorp Test"`]);
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/overview`);
await click(SELECTORS.configTab);
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/configuration`);
await click(SELECTORS.configuration.issuerLink);
assert.dom(SELECTORS.configuration.deleteAllIssuerModal).exists();
await fillIn(SELECTORS.configuration.deleteAllIssuerInput, 'delete-all');
await click(SELECTORS.configuration.deleteAllIssuerButton);
await isSettled();
assert.dom(SELECTORS.configuration.deleteAllIssuerModal).doesNotExist();
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/configuration`);
await isSettled();
await visit(`/vault/secrets/${this.mountPath}/pki/overview`);
await waitUntil(() => currentURL() === `/vault/secrets/${this.mountPath}/pki/overview`);
await isSettled();
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/overview`);
assert
.dom(SELECTORS.emptyStateMessage)
.hasText(
"This PKI mount hasn't yet been configured with a certificate issuer. There are existing roles and certificates. Use the CLI to perform any operations with them until an issuer is configured."
);
await visit(`/vault/secrets/${this.mountPath}/pki/roles`);
await isSettled();
assert
.dom(SELECTORS.emptyStateMessage)
.hasText(
"This PKI mount hasn't yet been configured with a certificate issuer. There are existing roles. Use the CLI to perform any operations with them until an issuer is configured."
);
await visit(`/vault/secrets/${this.mountPath}/pki/issuers`);
await isSettled();
assert
.dom(SELECTORS.emptyStateMessage)
.hasText("This PKI mount hasn't yet been configured with a certificate issuer.");
await visit(`/vault/secrets/${this.mountPath}/pki/keys`);
await isSettled();
assert
.dom(SELECTORS.emptyStateMessage)
.hasText("This PKI mount hasn't yet been configured with a certificate issuer.");
await visit(`/vault/secrets/${this.mountPath}/pki/certificates`);
await isSettled();
assert
.dom(SELECTORS.emptyStateMessage)
.hasText(
"This PKI mount hasn't yet been configured with a certificate issuer. There are existing certificates. Use the CLI to perform any operations with them until an issuer is configured."
);
});
});
});

View File

@@ -13,8 +13,10 @@ export const SELECTORS = {
optionByKey: (key) => `[data-test-pki-config-option="${key}"]`,
cancelButton: '[data-test-pki-config-cancel]',
saveButton: '[data-test-pki-config-save]',
doneButton: '[data-test-done]',
// pki-generate-root
...GENERATE_ROOT,
generateRootOption: '[data-test-pki-config-option="generate-root"]',
// pki-ca-cert-import
importForm: '[data-test-pki-import-pem-bundle-form]',
importSubmit: '[data-test-pki-import-pem-bundle]',

View File

@@ -0,0 +1,11 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
export const SELECTORS = {
issuerLink: '[data-test-delete-all-issuers-link]',
deleteAllIssuerModal: '[data-test-modal-background="Delete All Issuers?"]',
deleteAllIssuerInput: '[data-test-confirmation-modal-input="Delete All Issuers?"]',
deleteAllIssuerButton: '[data-test-confirm-button="Delete All Issuers?"]',
};

View File

@@ -16,6 +16,8 @@ export const SELECTORS = {
fieldByName: (name) => `[data-test-field="${name}"]`,
generateRootSave: '[data-test-pki-generate-root-save]',
generateRootCancel: '[data-test-pki-generate-root-cancel]',
generateRootCommonNameField: '[data-test-input="commonName"]',
generateRootIssuerNameField: '[data-test-input="issuerName"]',
formInvalidError: '[data-test-pki-generate-root-validation-error]',
urlsSection: '[data-test-urls-section]',
urlField: '[data-test-urls-section] [data-test-input]',

View File

@@ -9,6 +9,7 @@ import { SELECTORS as KEYFORM } from './pki-key-form';
import { SELECTORS as KEYPAGES } from './page/pki-keys';
import { SELECTORS as ISSUERDETAILS } from './pki-issuer-details';
import { SELECTORS as CONFIGURATION } from './pki-configure-create';
import { SELECTORS as DELETE } from './pki-delete-all-issuers';
export const SELECTORS = {
breadcrumbContainer: '[data-test-breadcrumbs]',
@@ -16,6 +17,7 @@ export const SELECTORS = {
overviewBreadcrumb: '[data-test-breadcrumbs] li:nth-of-type(2) > a',
pageTitle: '[data-test-pki-role-page-title]',
alertBanner: '[data-test-alert-banner="alert"]',
emptyState: '[data-test-component="empty-state"]',
emptyStateTitle: '[data-test-empty-state-title]',
emptyStateLink: '.empty-state-actions a',
emptyStateMessage: '[data-test-empty-state-message]',
@@ -63,5 +65,6 @@ export const SELECTORS = {
pkiBetaBanner: '[data-test-pki-configuration-banner]',
pkiBetaBannerLink: '[data-test-pki-configuration-banner] a',
...CONFIGURATION,
...DELETE,
},
};

View File

@@ -6,11 +6,13 @@
import Application from 'vault/adapters/application';
import Adapter from 'ember-data/adapter';
import ModelRegistry from 'ember-data/types/registries/model';
import PkiIssuerAdapter from 'vault/adapters/pki/issuer';
/**
* Catch-all for ember-data.
*/
export default interface AdapterRegistry {
'pki/issuer': PkiIssuerAdapter;
application: Application;
[key: keyof ModelRegistry]: Adapter;
}

12
ui/types/vault/adapters/pki/issuer.d.ts vendored Normal file
View File

@@ -0,0 +1,12 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import Store from '@ember-data/store';
import { AdapterRegistry } from 'ember-data/adapter';
export default interface PkiIssuerAdapter extends AdapterRegistry {
namespace: string;
deleteAllIssuers(backend: string);
}