UI: PKI Read Role Details (#17985)
This commit is contained in:
parent
d392754914
commit
1c0b2df8f1
|
@ -25,3 +25,6 @@
|
|||
/package-lock.json.ember-try
|
||||
/yarn.lock.ember-try
|
||||
/tests/helpers/vault-keys.js
|
||||
|
||||
# typescript declaration files
|
||||
*.d.ts
|
||||
|
|
|
@ -51,4 +51,8 @@ export default class PkiRoleAdapter extends ApplicationAdapter {
|
|||
query(store, type, query) {
|
||||
return this.fetchByQuery(store, query);
|
||||
}
|
||||
|
||||
queryRecord(store, type, query) {
|
||||
return this.fetchByQuery(store, query);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ export default class PkiRoleModel extends Model {
|
|||
|
||||
@attr('string', {
|
||||
label: 'Issuer reference',
|
||||
detailsLabel: 'Issuer',
|
||||
defaultValue: 'default',
|
||||
subText: `Specifies the issuer that will be used to create certificates with this role. To find this, run read -field=default pki_int/config/issuers in the console. By default, we will use the mounts default issuer.`,
|
||||
})
|
||||
|
@ -36,6 +37,7 @@ export default class PkiRoleModel extends Model {
|
|||
|
||||
@attr({
|
||||
label: 'Not valid after',
|
||||
detailsLabel: 'Issued certificates expire after',
|
||||
subText:
|
||||
'The time after which this certificate will no longer be valid. This can be a TTL (a range of time from now) or a specific date. If no TTL is set, the system uses "default" or the value of max_ttl, whichever is shorter. Alternatively, you can set the not_after date below.',
|
||||
editType: 'yield',
|
||||
|
@ -44,6 +46,7 @@ export default class PkiRoleModel extends Model {
|
|||
|
||||
@attr({
|
||||
label: 'Backdate validity',
|
||||
detailsLabel: 'Issued certificate backdating',
|
||||
helperTextEnabled:
|
||||
'Also called the not_before_duration property. Allows certificates to be valid for a certain time period before now. This is useful to correct clock misalignment on various systems when setting up your CA.',
|
||||
editType: 'ttl',
|
||||
|
@ -57,6 +60,7 @@ export default class PkiRoleModel extends Model {
|
|||
helperTextDisabled:
|
||||
'The maximum Time-To-Live of certificates generated by this role. If not set, the system max lease TTL will be used.',
|
||||
editType: 'ttl',
|
||||
defaultShown: 'System default',
|
||||
})
|
||||
maxTtl;
|
||||
|
||||
|
@ -71,6 +75,7 @@ export default class PkiRoleModel extends Model {
|
|||
|
||||
@attr('boolean', {
|
||||
label: 'Do not store certificates in storage backend',
|
||||
detailsLabel: 'Store in storage backend', // template reverses value
|
||||
subText:
|
||||
'This can improve performance when issuing large numbers of certificates. However, certificates issued in this way cannot be enumerated or revoked.',
|
||||
editType: 'boolean',
|
||||
|
@ -80,6 +85,7 @@ export default class PkiRoleModel extends Model {
|
|||
|
||||
@attr('boolean', {
|
||||
label: 'Basic constraints valid for non-CA',
|
||||
detailsLabel: 'Add basic constraints',
|
||||
subText: 'Mark Basic Constraints valid when issuing non-CA certificates.',
|
||||
editType: 'boolean',
|
||||
})
|
||||
|
@ -231,16 +237,20 @@ export default class PkiRoleModel extends Model {
|
|||
defaultValue() {
|
||||
return ['DigitalSignature', 'KeyAgreement', 'KeyEncipherment'];
|
||||
},
|
||||
defaultShown: 'None',
|
||||
})
|
||||
keyUsage;
|
||||
|
||||
@attr('array', {
|
||||
defaultValue() {
|
||||
return [];
|
||||
},
|
||||
defaultShown: 'None',
|
||||
})
|
||||
extKeyUsage;
|
||||
|
||||
@attr('array', {
|
||||
defaultShown: 'None',
|
||||
})
|
||||
extKeyUsageOids;
|
||||
|
||||
@attr({ hideFormSection: true }) organization;
|
||||
@attr({ hideFormSection: true }) country;
|
||||
@attr({ hideFormSection: true }) locality;
|
||||
|
@ -332,7 +342,7 @@ export default class PkiRoleModel extends Model {
|
|||
'Key parameters': ['keyType', 'keyBits', 'signatureBits'],
|
||||
},
|
||||
{
|
||||
'Key usage': ['keyUsage', 'extKeyUsage'],
|
||||
'Key usage': ['keyUsage', 'extKeyUsage', 'extKeyUsageOids'],
|
||||
},
|
||||
{ 'Policy identifiers': ['policyIdentifiers'] },
|
||||
{
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
<PageHeader as |p|>
|
||||
<p.top>
|
||||
{{! TODO: This should be replaced with HDS::Breadcrumbs }}
|
||||
<nav class="key-value-header breadcrumb" aria-label="breadcrumbs" data-test-breadcrumbs="role-details">
|
||||
<ul>
|
||||
<li>
|
||||
<span class="sep">/</span>
|
||||
<LinkToExternal @route="secrets">secrets</LinkToExternal>
|
||||
</li>
|
||||
{{#each this.breadcrumbs as |breadcrumb|}}
|
||||
<li>
|
||||
<span class="sep">/</span>
|
||||
{{#if breadcrumb.path}}
|
||||
<LinkTo @route={{breadcrumb.path}}>
|
||||
{{breadcrumb.label}}
|
||||
</LinkTo>
|
||||
{{else}}
|
||||
{{breadcrumb.label}}
|
||||
{{/if}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</nav>
|
||||
</p.top>
|
||||
<p.levelLeft>
|
||||
<h1 class="title is-3" data-test-role-details-title>
|
||||
<Icon @name="file-text" @size="24" class="has-text-grey-light" />
|
||||
PKI Role
|
||||
<code>{{@role.name}}</code>
|
||||
</h1>
|
||||
</p.levelLeft>
|
||||
</PageHeader>
|
||||
<Toolbar>
|
||||
<ToolbarActions>
|
||||
<ConfirmAction
|
||||
@buttonClasses="toolbar-link"
|
||||
@onConfirmAction={{this.deleteRole}}
|
||||
@confirmTitle="Delete role?"
|
||||
@confirmButtonText="Delete"
|
||||
data-test-pki-role-delete
|
||||
>
|
||||
Delete
|
||||
</ConfirmAction>
|
||||
<div class="toolbar-separator"></div>
|
||||
<LinkTo class="toolbar-link" @route="overview">Generate Certificate <Icon @name="chevron-right" /></LinkTo>
|
||||
<LinkTo class="toolbar-link" @route="overview">Sign Certificate <Icon @name="chevron-right" /></LinkTo>
|
||||
<LinkTo class="toolbar-link" @route="roles.role.edit" @model={{@role.id}}>Edit <Icon @name="chevron-right" /></LinkTo>
|
||||
</ToolbarActions>
|
||||
</Toolbar>
|
||||
{{#each @role.fieldGroups as |fg|}}
|
||||
{{#each-in fg as |group fields|}}
|
||||
{{#if (not-eq group "default")}}
|
||||
<h3 class="is-size-4 has-text-semibold has-top-margin-m">{{group}}</h3>
|
||||
{{/if}}
|
||||
{{#each fields as |attr|}}
|
||||
{{#let (get @role attr.name) as |val|}}
|
||||
{{#if (eq attr.name "issuerRef")}}
|
||||
<InfoTableRow
|
||||
@label={{capitalize (or attr.options.detailsLabel attr.options.label (humanize (dasherize attr.name)))}}
|
||||
@value={{val}}
|
||||
@alwaysRender={{true}}
|
||||
>
|
||||
<LinkTo @route="issuers.issuer.details" @model={{val}}>{{val}}</LinkTo>
|
||||
</InfoTableRow>
|
||||
{{else if (includes attr.name this.arrayAttrs)}}
|
||||
<InfoTableRow
|
||||
@label={{capitalize (or attr.options.detailsLabel attr.options.label (humanize (dasherize attr.name)))}}
|
||||
@value={{val}}
|
||||
@alwaysRender={{true}}
|
||||
>
|
||||
{{#if (gt val.length 0)}}
|
||||
{{#each val as |key|}}
|
||||
<span>{{key}},</span>
|
||||
{{/each}}
|
||||
{{else}}
|
||||
None
|
||||
{{/if}}
|
||||
</InfoTableRow>
|
||||
{{else if (eq attr.name "noStore")}}
|
||||
<InfoTableRow
|
||||
@label={{capitalize (or attr.options.detailsLabel attr.options.label (humanize (dasherize attr.name)))}}
|
||||
@value={{not val}}
|
||||
@alwaysRender={{true}}
|
||||
/>
|
||||
{{else}}
|
||||
<InfoTableRow
|
||||
@label={{capitalize (or attr.options.detailsLabel attr.options.label (humanize (dasherize attr.name)))}}
|
||||
@value={{val}}
|
||||
@alwaysRender={{true}}
|
||||
@formatDate={{eq attr.name "customTtl"}}
|
||||
@type={{or attr.type attr.options.type}}
|
||||
@defaultShown={{attr.options.defaultShown}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/let}}
|
||||
{{/each}}
|
||||
{{/each-in}}
|
||||
{{/each}}
|
|
@ -0,0 +1,35 @@
|
|||
import { action } from '@ember/object';
|
||||
import Component from '@glimmer/component';
|
||||
|
||||
// interface Attribute {
|
||||
// name: string;
|
||||
// options?: {
|
||||
// label?: string;
|
||||
// };
|
||||
// }
|
||||
|
||||
// TODO: pull this in from route model once it's TS
|
||||
interface Args {
|
||||
role: {
|
||||
backend: string;
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
export default class DetailsPage extends Component<Args> {
|
||||
get breadcrumbs() {
|
||||
return [
|
||||
{ label: this.args.role.backend || 'pki', path: 'overview' },
|
||||
{ label: 'roles', path: 'roles.index' },
|
||||
{ label: this.args.role.id },
|
||||
];
|
||||
}
|
||||
|
||||
get arrayAttrs() {
|
||||
return ['keyUsage', 'extKeyUsage', 'extKeyUsageOids'];
|
||||
}
|
||||
|
||||
@action deleteRole() {
|
||||
// TODO: delete role
|
||||
}
|
||||
}
|
|
@ -7,7 +7,8 @@ export default class PkiRolesIndexRoute extends Route {
|
|||
@service pathHelp;
|
||||
|
||||
beforeModel() {
|
||||
// Must call this promise before the model hook otherwise it doesn't add OpenApi to record.
|
||||
// Must call this promise before the model hook otherwise
|
||||
// the model doesn't hydrate from OpenAPI correctly.
|
||||
return this.pathHelp.getNewModel('pki/role', 'pki');
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
import Route from '@ember/routing/route';
|
||||
import PkiRolesIndexRoute from '../index';
|
||||
|
||||
export default class PkiRoleDetailsRoute extends Route {}
|
||||
export default class RolesRoleDetailsRoute extends PkiRolesIndexRoute {
|
||||
model() {
|
||||
const { role } = this.paramsFor('roles/role');
|
||||
return this.store.queryRecord('pki/role', {
|
||||
backend: this.secretMountPath.currentPath,
|
||||
id: role,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
roles.role.details
|
||||
<PkiRoleDetailsPage @role={{this.model}} />
|
|
@ -7,7 +7,35 @@
|
|||
"dependencies": {
|
||||
"ember-cli-babel": "*",
|
||||
"ember-cli-htmlbars": "*",
|
||||
"ember-cli-typescript": "*"
|
||||
"ember-cli-typescript": "*",
|
||||
"@types/ember": "latest",
|
||||
"@types/ember-data": "latest",
|
||||
"@types/ember-data__adapter": "latest",
|
||||
"@types/ember-data__model": "latest",
|
||||
"@types/ember-data__serializer": "latest",
|
||||
"@types/ember-data__store": "latest",
|
||||
"@types/ember-qunit": "latest",
|
||||
"@types/ember-resolver": "latest",
|
||||
"@types/ember__application": "latest",
|
||||
"@types/ember__array": "latest",
|
||||
"@types/ember__component": "latest",
|
||||
"@types/ember__controller": "latest",
|
||||
"@types/ember__debug": "latest",
|
||||
"@types/ember__destroyable": "latest",
|
||||
"@types/ember__engine": "latest",
|
||||
"@types/ember__error": "latest",
|
||||
"@types/ember__object": "latest",
|
||||
"@types/ember__polyfills": "latest",
|
||||
"@types/ember__routing": "latest",
|
||||
"@types/ember__runloop": "latest",
|
||||
"@types/ember__service": "latest",
|
||||
"@types/ember__string": "latest",
|
||||
"@types/ember__template": "latest",
|
||||
"@types/ember__test": "latest",
|
||||
"@types/ember__test-helpers": "latest",
|
||||
"@types/ember__utils": "latest",
|
||||
"@types/qunit": "latest",
|
||||
"@types/rsvp": "latest"
|
||||
},
|
||||
"ember-addon": {
|
||||
"paths": [
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
export const SELECTORS = {
|
||||
breadcrumbContainer: '[data-test-breadcrumbs="role-details"]',
|
||||
breadcrumbs: '[data-test-breadcrumbs="role-details"] li',
|
||||
title: '[data-test-role-details-title]',
|
||||
issuerLabel: '[data-test-row-label="Issuer"]',
|
||||
noStoreValue: '[data-test-value-div="Store in storage backend"]',
|
||||
keyUsageValue: '[data-test-value-div="Key usage"]',
|
||||
extKeyUsageValue: '[data-test-value-div="Ext key usage"]',
|
||||
};
|
|
@ -0,0 +1,42 @@
|
|||
import { module, test } from 'qunit';
|
||||
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';
|
||||
|
||||
module('Integration | Component | pki role details page', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
setupEngine(hooks, 'pki');
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.store = this.owner.lookup('service:store');
|
||||
this.model = this.store.createRecord('pki/role', {
|
||||
name: 'Foobar',
|
||||
noStore: false,
|
||||
keyUsage: [],
|
||||
extKeyUsage: ['bar', 'baz'],
|
||||
});
|
||||
this.model.backend = 'pki';
|
||||
});
|
||||
|
||||
test('it should render the page component', async function (assert) {
|
||||
assert.expect(7);
|
||||
await render(
|
||||
hbs`
|
||||
<PkiRoleDetailsPage @role={{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('PKI Role Foobar', 'Title includes type and name of role');
|
||||
// Attribute-specific checks
|
||||
assert.dom(SELECTORS.issuerLabel).hasText('Issuer', 'Label is');
|
||||
assert.dom(SELECTORS.keyUsageValue).hasText('None', 'Key usage shows none when array is empty');
|
||||
assert
|
||||
.dom(SELECTORS.extKeyUsageValue)
|
||||
.hasText('bar, baz,', 'Key usage shows comma-joined values when array has items');
|
||||
assert.dom(SELECTORS.noStoreValue).containsText('Yes', 'noStore shows opposite of what the value is');
|
||||
});
|
||||
});
|
|
@ -1,6 +1,17 @@
|
|||
{
|
||||
"extends": "@tsconfig/ember/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"allowJs": true,
|
||||
"strict": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noEmitOnError": true,
|
||||
"skipLibCheck": true,
|
||||
// The combination of `baseUrl` with `paths` allows Ember's classic package
|
||||
// layout, which is not resolvable with the Node resolution algorithm, to
|
||||
// work with TypeScript.
|
||||
|
|
Loading…
Reference in New Issue