ui: delete pki key functionality (#18146)
* add deletekey * fix types * move page components into folder * finish tests * make linting changes * declare flashmessages ts service * restructure pki test files * add delete test * add more folders
This commit is contained in:
parent
e75633eddc
commit
5f79edc49c
|
@ -1,9 +1,7 @@
|
|||
import ApplicationAdapter from '../application';
|
||||
import { encodePath } from 'vault/utils/path-encoding-helpers';
|
||||
|
||||
export default class PkiKeyAdapter extends ApplicationAdapter {
|
||||
namespace = 'v1';
|
||||
|
||||
getUrl(backend, id) {
|
||||
const url = `${this.buildURL()}/${encodePath(backend)}`;
|
||||
if (id) {
|
||||
|
@ -21,4 +19,9 @@ export default class PkiKeyAdapter extends ApplicationAdapter {
|
|||
const { backend, id } = query;
|
||||
return this.ajax(this.getUrl(backend, id), 'GET');
|
||||
}
|
||||
|
||||
deleteRecord(store, type, snapshot) {
|
||||
const { id, record } = snapshot;
|
||||
return this.ajax(this.getUrl(record.backend, id), 'DELETE');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
import Model, { attr } from '@ember-data/model';
|
||||
import { expandAttributeMeta } from 'vault/utils/field-to-attrs';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { withFormFields } from 'vault/decorators/model-form-fields';
|
||||
|
||||
@withFormFields(['keyId', 'keyName', 'keyType', 'keyBits'])
|
||||
export default class PkiKeyModel extends Model {
|
||||
@attr('string', { readOnly: true }) backend;
|
||||
@service secretMountPath;
|
||||
|
||||
@attr('boolean') isDefault;
|
||||
@attr('string', { possibleValues: ['internal', 'external'] }) type;
|
||||
@attr('string', { detailsLabel: 'Key ID' }) keyId;
|
||||
@attr('string') keyName;
|
||||
@attr('string') keyType;
|
||||
@attr('string', { detailsLabel: 'Key bit length' }) keyBits; // TODO confirm with crypto team to remove this field from details page
|
||||
@attr('string') keyBits;
|
||||
|
||||
// TODO refactor when field-to-attrs util is refactored as decorator
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.formFields = expandAttributeMeta(this, ['keyId', 'keyName', 'keyType', 'keyBits']);
|
||||
get backend() {
|
||||
return this.secretMountPath.currentPath;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import FlashMessages from 'ember-cli-flash/services/flash-messages';
|
||||
|
||||
export default FlashMessages.extend({
|
||||
stickyInfo(message) {
|
||||
export default class FlashMessageService extends FlashMessages {
|
||||
stickyInfo(message: string) {
|
||||
return this.info(message, {
|
||||
sticky: true,
|
||||
priority: 300,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
// accepts an error and returns error.errors joined with a comma, error.message or a fallback message
|
||||
export default function (error, fallbackMessage = 'An error occurred, please try again') {
|
||||
if (error?.errors) {
|
||||
if (error instanceof Error && error?.errors) {
|
||||
return error.errors.join(', ');
|
||||
}
|
||||
return error?.message || fallbackMessage;
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
import { action } from '@ember/object';
|
||||
import Component from '@glimmer/component';
|
||||
import RouterService from '@ember/routing/router-service';
|
||||
import FlashMessageService from 'vault/services/flash-messages';
|
||||
import { inject as service } from '@ember/service';
|
||||
import errorMessage from 'vault/utils/error-message';
|
||||
interface Args {
|
||||
key: {
|
||||
rollbackAttributes: () => void;
|
||||
destroyRecord: () => void;
|
||||
backend: string;
|
||||
keyName: string;
|
||||
keyId: string;
|
||||
};
|
||||
}
|
||||
|
||||
export default class PkiKeyDetails extends Component<Args> {
|
||||
@service declare readonly router: RouterService;
|
||||
@service declare readonly flashMessages: FlashMessageService;
|
||||
|
||||
get breadcrumbs() {
|
||||
return [
|
||||
{ label: 'secrets', route: 'secrets', linkExternal: true },
|
||||
{ label: this.args.key.backend || 'pki', route: 'overview' },
|
||||
{ label: 'keys', route: 'keys.index' },
|
||||
{ label: this.args.key.keyId },
|
||||
];
|
||||
}
|
||||
|
||||
@action
|
||||
async deleteKey() {
|
||||
try {
|
||||
await this.args.key.destroyRecord();
|
||||
this.flashMessages.success('Key deleted successfully');
|
||||
this.router.transitionTo('vault.cluster.secrets.backend.pki.keys.index');
|
||||
} catch (error) {
|
||||
this.args.key.rollbackAttributes();
|
||||
this.flashMessages.danger(errorMessage(error));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
import { action } from '@ember/object';
|
||||
import Component from '@glimmer/component';
|
||||
import { inject as service } from '@ember/service';
|
||||
interface Args {
|
||||
key: {
|
||||
backend: string;
|
||||
keyName: string;
|
||||
keyId: string;
|
||||
};
|
||||
}
|
||||
|
||||
export default class PkiKeyDetails extends Component<Args> {
|
||||
@service declare secretMountPath: { currentPath: string };
|
||||
|
||||
get breadcrumbs() {
|
||||
return [
|
||||
{ label: 'secrets', route: 'secrets', linkExternal: true },
|
||||
{ label: this.secretMountPath.currentPath || 'pki', route: 'overview' },
|
||||
{ label: 'keys', route: 'keys.index' },
|
||||
{ label: this.args.key.keyId },
|
||||
];
|
||||
}
|
||||
|
||||
@action deleteKey() {
|
||||
// TODO handle delete
|
||||
}
|
||||
}
|
|
@ -1 +1 @@
|
|||
<PkiKeyDetails @key={{this.model}} />
|
||||
<Page::PkiKeyDetails @key={{this.model}} />
|
|
@ -1 +1 @@
|
|||
<PkiRoleDetailsPage @role={{this.model}} />
|
||||
<Page::PkiRoleDetails @role={{this.model}} />
|
|
@ -0,0 +1,11 @@
|
|||
export const SELECTORS = {
|
||||
breadcrumbContainer: '[data-test-breadcrumbs]',
|
||||
breadcrumbs: '[data-test-breadcrumbs] li',
|
||||
title: '[data-test-key-details-title]',
|
||||
keyIdValue: '[data-test-value-div="Key ID"]',
|
||||
keyNameValue: '[data-test-value-div="Key name"]',
|
||||
keyTypeValue: '[data-test-value-div="Key type"]',
|
||||
keyBitsValue: '[data-test-value-div="Key bits"]',
|
||||
keyDeleteButton: '[data-test-pki-key-delete] button',
|
||||
confirmDelete: '[data-test-confirm-button]',
|
||||
};
|
|
@ -1,7 +1,6 @@
|
|||
export const PKI_BASE_URL = `/vault/cluster/secrets/backend/pki/roles`;
|
||||
|
||||
export const SELECTORS = {
|
||||
// Pki role
|
||||
roleName: '[data-test-input="name"]',
|
||||
issuerRef: '[data-test-input="issuerRef"]',
|
||||
customTtl: '[data-test-field="customTtl"]',
|
|
@ -0,0 +1,53 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { click, render } 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 { SELECTORS } from 'vault/tests/helpers/pki/keys/page-details';
|
||||
|
||||
module('Integration | Component | pki key details page', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
setupEngine(hooks, 'pki');
|
||||
setupMirage(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.owner.lookup('service:flash-messages').registerTypes(['success', 'danger']);
|
||||
this.store = this.owner.lookup('service:store');
|
||||
this.secretMountPath = this.owner.lookup('service:secret-mount-path');
|
||||
this.backend = 'pki-test';
|
||||
this.secretMountPath.currentPath = this.backend;
|
||||
this.store.pushPayload('pki/key', {
|
||||
modelName: 'pki/key',
|
||||
key_id: '724862ff-6438-bad0-b598-77a6c7f4e934',
|
||||
key_type: 'ec',
|
||||
key_name: 'test-key',
|
||||
});
|
||||
this.model = this.store.peekRecord('pki/key', '724862ff-6438-bad0-b598-77a6c7f4e934');
|
||||
});
|
||||
|
||||
test('it renders the page component and deletes a key', async function (assert) {
|
||||
assert.expect(9);
|
||||
this.server.delete(`${this.backend}/key/${this.model.keyId}`, () => {
|
||||
assert.ok(true, 'confirming delete fires off destroyRecord()');
|
||||
});
|
||||
|
||||
await render(
|
||||
hbs`
|
||||
<Page::PkiKeyDetails @key={{this.model}} />
|
||||
`,
|
||||
{ owner: this.engine }
|
||||
);
|
||||
|
||||
assert.dom(SELECTORS.breadcrumbContainer).exists({ count: 1 }, 'breadcrumb containers exist');
|
||||
assert.dom(SELECTORS.breadcrumbs).exists({ count: 4 }, 'Shows 4 breadcrumbs');
|
||||
assert.dom(SELECTORS.title).containsText('View key', 'title renders');
|
||||
assert.dom(SELECTORS.keyIdValue).hasText(' 724862ff-6438-bad0-b598-77a6c7f4e934', 'key id renders');
|
||||
assert.dom(SELECTORS.keyNameValue).hasText('test-key', 'key name renders');
|
||||
assert.dom(SELECTORS.keyTypeValue).hasText('ec', 'key type renders');
|
||||
assert.dom(SELECTORS.keyBitsValue).doesNotExist('does not render empty value');
|
||||
assert.dom(SELECTORS.keyDeleteButton).exists('renders delete button');
|
||||
await click(SELECTORS.keyDeleteButton);
|
||||
await click(SELECTORS.confirmDelete);
|
||||
});
|
||||
});
|
|
@ -3,7 +3,7 @@ import { setupRenderingTest } from 'ember-qunit';
|
|||
import { render, fillIn } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import { setupEngine } from 'ember-engines/test-support';
|
||||
import { SELECTORS } from 'vault/tests/helpers/pki-engine';
|
||||
import { SELECTORS } from 'vault/tests/helpers/pki/roles/form';
|
||||
|
||||
module('Integration | Component | pki-key-parameters', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
|
|
@ -3,7 +3,7 @@ import { setupRenderingTest } from 'ember-qunit';
|
|||
import { render, click, findAll } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import { setupEngine } from 'ember-engines/test-support';
|
||||
import { SELECTORS } from 'vault/tests/helpers/pki-engine';
|
||||
import { SELECTORS } from 'vault/tests/helpers/pki/roles/form';
|
||||
|
||||
module('Integration | Component | pki-key-usage', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
|
|
@ -3,7 +3,7 @@ import { setupRenderingTest } from 'ember-qunit';
|
|||
import { render, click, fillIn, find } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import { setupEngine } from 'ember-engines/test-support';
|
||||
import { SELECTORS } from 'vault/tests/helpers/pki-engine';
|
||||
import { SELECTORS } from 'vault/tests/helpers/pki/roles/form';
|
||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
|
||||
module('Integration | Component | pki-role-form', function (hooks) {
|
|
@ -3,7 +3,7 @@ import { setupRenderingTest } from 'ember-qunit';
|
|||
import { render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import { setupEngine } from 'ember-engines/test-support';
|
||||
import { SELECTORS } from 'vault/tests/helpers/pki/page-role-details';
|
||||
import { SELECTORS } from 'vault/tests/helpers/pki/roles/page-details';
|
||||
|
||||
module('Integration | Component | pki role details page', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
@ -24,7 +24,7 @@ module('Integration | Component | pki role details page', function (hooks) {
|
|||
assert.expect(7);
|
||||
await render(
|
||||
hbs`
|
||||
<PkiRoleDetailsPage @role={{this.model}} />
|
||||
<Page::PkiRoleDetails @role={{this.model}} />
|
||||
`,
|
||||
{ owner: this.engine }
|
||||
);
|
|
@ -0,0 +1,60 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
|
||||
module('Unit | Adapter | pki/key', function (hooks) {
|
||||
setupTest(hooks);
|
||||
setupMirage(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.store = this.owner.lookup('service:store');
|
||||
this.secretMountPath = this.owner.lookup('service:secret-mount-path');
|
||||
this.backend = 'pki-test';
|
||||
this.secretMountPath.currentPath = this.backend;
|
||||
this.data = {
|
||||
key_id: '724862ff-6438-bad0-b598-77a6c7f4e934',
|
||||
key_type: 'ec',
|
||||
key_name: 'test-key',
|
||||
key_bits: '256',
|
||||
};
|
||||
});
|
||||
|
||||
hooks.afterEach(function () {
|
||||
this.server.shutdown();
|
||||
});
|
||||
|
||||
test('it should make request to correct endpoint on query', async function (assert) {
|
||||
assert.expect(1);
|
||||
const { key_id, ...otherAttrs } = this.data; // excludes key_id from key_info data
|
||||
const key_info = { [key_id]: { ...otherAttrs } };
|
||||
this.server.get(`${this.backend}/keys`, (schema, req) => {
|
||||
assert.strictEqual(req.queryParams.list, 'true', 'request is made to correct endpoint on query');
|
||||
return { data: { keys: [key_id], key_info } };
|
||||
});
|
||||
|
||||
this.store.query('pki/key', { backend: this.backend });
|
||||
});
|
||||
|
||||
test('it should make request to correct endpoint on queryRecord', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.server.get(`${this.backend}/key/${this.data.key_id}`, () => {
|
||||
assert.ok(true, 'request is made to correct endpoint on query record');
|
||||
return { data: this.data };
|
||||
});
|
||||
|
||||
this.store.queryRecord('pki/key', { backend: this.backend, id: this.data.key_id });
|
||||
});
|
||||
|
||||
test('it should make request to correct endpoint on delete', async function (assert) {
|
||||
assert.expect(1);
|
||||
this.store.pushPayload('pki/key', { modelName: 'pki/key', ...this.data });
|
||||
this.server.get(`${this.backend}/key/${this.data.key_id}`, () => ({ data: this.data }));
|
||||
this.server.delete(`${this.backend}/key/${this.data.key_id}`, () => {
|
||||
assert.ok(true, 'request made to correct endpoint on delete');
|
||||
});
|
||||
|
||||
const model = await this.store.queryRecord('pki/key', { backend: this.backend, id: this.data.key_id });
|
||||
await model.destroyRecord();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,44 @@
|
|||
declare module 'ember-cli-flash/services/flash-messages' {
|
||||
import Service from '@ember/service';
|
||||
import FlashObject from 'ember-cli-flash/flash/object';
|
||||
import { A } from '@ember/array';
|
||||
|
||||
type Partial<T> = { [K in keyof T]?: T[K] };
|
||||
|
||||
interface MessageOptions {
|
||||
type: string;
|
||||
priority: number;
|
||||
timeout: number;
|
||||
sticky: boolean;
|
||||
showProgress: boolean;
|
||||
extendedTimeout: number;
|
||||
destroyOnClick: boolean;
|
||||
onDestroy: () => void;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface CustomMessageInfo extends Partial<MessageOptions> {
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface FlashFunction {
|
||||
(message: string, options?: Partial<MessageOptions>): FlashMessageService;
|
||||
}
|
||||
|
||||
class FlashMessageService extends Service {
|
||||
queue: A<FlashObject>;
|
||||
success: FlashFunction;
|
||||
warning: FlashFunction;
|
||||
info: FlashFunction;
|
||||
error: FlashFunction;
|
||||
danger: FlashFunction;
|
||||
alert: FlashFunction;
|
||||
secondary: FlashFunction;
|
||||
add(messageInfo: CustomMessageInfo): FlashMessageService;
|
||||
clearMessages(): FlashMessageService;
|
||||
registerTypes(types: string[]): FlashMessageService;
|
||||
getFlashObject(): FlashObject;
|
||||
}
|
||||
|
||||
export default FlashMessageService;
|
||||
}
|
Loading…
Reference in New Issue