mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 02:28:09 +00:00 
			
		
		
		
	UI: PKI Role toolbar (#18229)
This commit is contained in:
		| @@ -55,4 +55,8 @@ export default class PkiRoleAdapter extends ApplicationAdapter { | |||||||
|   queryRecord(store, type, query) { |   queryRecord(store, type, query) { | ||||||
|     return this.fetchByQuery(store, query); |     return this.fetchByQuery(store, query); | ||||||
|   } |   } | ||||||
|  |   deleteRecord(store, type, snapshot) { | ||||||
|  |     const { id, record } = snapshot; | ||||||
|  |     return this.ajax(this._urlForRole(record.backend, id), 'DELETE'); | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,13 +1,71 @@ | |||||||
| import Model, { attr } from '@ember-data/model'; | import Model, { attr } from '@ember-data/model'; | ||||||
| import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities'; | import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities'; | ||||||
| import { withModelValidations } from 'vault/decorators/model-validations'; | import { withModelValidations } from 'vault/decorators/model-validations'; | ||||||
|  | import { withFormFields } from 'vault/decorators/model-form-fields'; | ||||||
| import fieldToAttrs from 'vault/utils/field-to-attrs'; |  | ||||||
|  |  | ||||||
| const validations = { | const validations = { | ||||||
|   name: [{ type: 'presence', message: 'Name is required.' }], |   name: [{ type: 'presence', message: 'Name is required.' }], | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | const fieldGroups = [ | ||||||
|  |   { | ||||||
|  |     default: [ | ||||||
|  |       'name', | ||||||
|  |       'issuerRef', | ||||||
|  |       'customTtl', | ||||||
|  |       'notBeforeDuration', | ||||||
|  |       'maxTtl', | ||||||
|  |       'generateLease', | ||||||
|  |       'noStore', | ||||||
|  |       'addBasicConstraints', | ||||||
|  |     ], | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     'Domain handling': [ | ||||||
|  |       'allowedDomains', | ||||||
|  |       'allowedDomainsTemplate', | ||||||
|  |       'allowBareDomains', | ||||||
|  |       'allowSubdomains', | ||||||
|  |       'allowGlobDomains', | ||||||
|  |       'allowWildcardCertificates', | ||||||
|  |       'allowLocalhost', // default: true (returned true by OpenApi) | ||||||
|  |       'allowAnyName', | ||||||
|  |       'enforceHostnames', // default: true (returned true by OpenApi) | ||||||
|  |     ], | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     'Key parameters': ['keyType', 'keyBits', 'signatureBits'], | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     'Key usage': ['keyUsage', 'extKeyUsage', 'extKeyUsageOids'], | ||||||
|  |   }, | ||||||
|  |   { 'Policy identifiers': ['policyIdentifiers'] }, | ||||||
|  |   { | ||||||
|  |     'Subject Alternative Name (SAN) Options': [ | ||||||
|  |       'allowIpSans', | ||||||
|  |       'allowedUriSans', | ||||||
|  |       'allowUriSansTemplate', | ||||||
|  |       'allowedOtherSans', | ||||||
|  |     ], | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     'Additional subject fields': [ | ||||||
|  |       'allowedSerialNumbers', | ||||||
|  |       'requireCn', | ||||||
|  |       'useCsrCommonName', | ||||||
|  |       'useCsrSans', | ||||||
|  |       'ou', | ||||||
|  |       'organization', | ||||||
|  |       'country', | ||||||
|  |       'locality', | ||||||
|  |       'province', | ||||||
|  |       'streetAddress', | ||||||
|  |       'postalCode', | ||||||
|  |     ], | ||||||
|  |   }, | ||||||
|  | ]; | ||||||
|  |  | ||||||
|  | @withFormFields(null, fieldGroups) | ||||||
| @withModelValidations(validations) | @withModelValidations(validations) | ||||||
| export default class PkiRoleModel extends Model { | export default class PkiRoleModel extends Model { | ||||||
|   get useOpenAPI() { |   get useOpenAPI() { | ||||||
| @@ -242,34 +300,33 @@ export default class PkiRoleModel extends Model { | |||||||
|   @attr({ hideFormSection: true }) postalCode; |   @attr({ hideFormSection: true }) postalCode; | ||||||
|   /* End of overriding Additional subject field options */ |   /* End of overriding Additional subject field options */ | ||||||
|  |  | ||||||
|   /* CAPABILITIES */ |   /* CAPABILITIES | ||||||
|  |    * Default to show UI elements unless we know they can't access the given path | ||||||
|  |    */ | ||||||
|   @lazyCapabilities(apiPath`${'backend'}/roles/${'id'}`, 'backend', 'id') updatePath; |   @lazyCapabilities(apiPath`${'backend'}/roles/${'id'}`, 'backend', 'id') updatePath; | ||||||
|   get canDelete() { |   get canDelete() { | ||||||
|     return this.updatePath.get('canCreate'); |     return this.updatePath.get('isLoading') || this.updatePath.get('canCreate') !== false; | ||||||
|   } |   } | ||||||
|   get canEdit() { |   get canEdit() { | ||||||
|     return this.updatePath.get('canEdit'); |     return this.updatePath.get('isLoading') || this.updatePath.get('canUpdate') !== false; | ||||||
|   } |   } | ||||||
|   get canRead() { |   get canRead() { | ||||||
|     return this.updatePath.get('canRead'); |     return this.updatePath.get('isLoading') || this.updatePath.get('canRead') !== false; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @lazyCapabilities(apiPath`${'backend'}/issue/${'id'}`, 'backend', 'id') generatePath; |   @lazyCapabilities(apiPath`${'backend'}/issue/${'id'}`, 'backend', 'id') generatePath; | ||||||
|   get canReadIssue() { |   get canGenerateCert() { | ||||||
|     // ARG TODO was duplicate name, added Issue |     return this.generatePath.get('isLoading') || this.generatePath.get('canUpdate') !== false; | ||||||
|     return this.generatePath.get('canUpdate'); |  | ||||||
|   } |   } | ||||||
|   @lazyCapabilities(apiPath`${'backend'}/sign/${'id'}`, 'backend', 'id') signPath; |   @lazyCapabilities(apiPath`${'backend'}/sign/${'id'}`, 'backend', 'id') signPath; | ||||||
|   get canSign() { |   get canSign() { | ||||||
|     return this.signPath.get('canUpdate'); |     return this.signPath.get('isLoading') || this.signPath.get('canUpdate') !== false; | ||||||
|   } |   } | ||||||
|   @lazyCapabilities(apiPath`${'backend'}/sign-verbatim/${'id'}`, 'backend', 'id') signVerbatimPath; |   @lazyCapabilities(apiPath`${'backend'}/sign-verbatim/${'id'}`, 'backend', 'id') signVerbatimPath; | ||||||
|   get canSignVerbatim() { |   get canSignVerbatim() { | ||||||
|     return this.signVerbatimPath.get('canUpdate'); |     return this.signVerbatimPath.get('isLoading') || this.signVerbatimPath.get('canUpdate') !== false; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   _fieldToAttrsGroups = null; |  | ||||||
|  |  | ||||||
|   // Gets header/footer copy for specific toggle groups. |   // Gets header/footer copy for specific toggle groups. | ||||||
|   get fieldGroupsInfo() { |   get fieldGroupsInfo() { | ||||||
|     return { |     return { | ||||||
| @@ -297,67 +354,4 @@ export default class PkiRoleModel extends Model { | |||||||
|       }, |       }, | ||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   get fieldGroups() { |  | ||||||
|     if (!this._fieldToAttrsGroups) { |  | ||||||
|       this._fieldToAttrsGroups = fieldToAttrs(this, [ |  | ||||||
|         { |  | ||||||
|           default: [ |  | ||||||
|             'name', |  | ||||||
|             'issuerRef', |  | ||||||
|             'customTtl', |  | ||||||
|             'notBeforeDuration', |  | ||||||
|             'maxTtl', |  | ||||||
|             'generateLease', |  | ||||||
|             'noStore', |  | ||||||
|             'addBasicConstraints', |  | ||||||
|           ], |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           'Domain handling': [ |  | ||||||
|             'allowedDomains', |  | ||||||
|             'allowedDomainsTemplate', |  | ||||||
|             'allowBareDomains', |  | ||||||
|             'allowSubdomains', |  | ||||||
|             'allowGlobDomains', |  | ||||||
|             'allowWildcardCertificates', |  | ||||||
|             'allowLocalhost', // default: true (returned true by OpenApi) |  | ||||||
|             'allowAnyName', |  | ||||||
|             'enforceHostnames', // default: true (returned true by OpenApi) |  | ||||||
|           ], |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           'Key parameters': ['keyType', 'keyBits', 'signatureBits'], |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           'Key usage': ['keyUsage', 'extKeyUsage', 'extKeyUsageOids'], |  | ||||||
|         }, |  | ||||||
|         { 'Policy identifiers': ['policyIdentifiers'] }, |  | ||||||
|         { |  | ||||||
|           'Subject Alternative Name (SAN) Options': [ |  | ||||||
|             'allowIpSans', |  | ||||||
|             'allowedUriSans', |  | ||||||
|             'allowUriSansTemplate', |  | ||||||
|             'allowedOtherSans', |  | ||||||
|           ], |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           'Additional subject fields': [ |  | ||||||
|             'allowedSerialNumbers', |  | ||||||
|             'requireCn', |  | ||||||
|             'useCsrCommonName', |  | ||||||
|             'useCsrSans', |  | ||||||
|             'ou', |  | ||||||
|             'organization', |  | ||||||
|             'country', |  | ||||||
|             'locality', |  | ||||||
|             'province', |  | ||||||
|             'streetAddress', |  | ||||||
|             'postalCode', |  | ||||||
|           ], |  | ||||||
|         }, |  | ||||||
|       ]); |  | ||||||
|     } |  | ||||||
|     return this._fieldToAttrsGroups; |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -83,7 +83,7 @@ | |||||||
|   </form> |   </form> | ||||||
| {{else}} | {{else}} | ||||||
|   <div class="box is-sideless is-fullwidth is-marginless"> |   <div class="box is-sideless is-fullwidth is-marginless"> | ||||||
|     {{#each this.model.fieldGroups as |fieldGroup|}} |     {{#each this.model.formFieldGroups as |fieldGroup|}} | ||||||
|       {{#each-in fieldGroup as |group fields|}} |       {{#each-in fieldGroup as |group fields|}} | ||||||
|         {{#if (or (eq group "default") (eq group "Options"))}} |         {{#if (or (eq group "default") (eq group "Options"))}} | ||||||
|           {{#each fields as |attr|}} |           {{#each fields as |attr|}} | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| {{#unless this.dontShowTab}} | {{#unless this.dontShowTab}} | ||||||
|   {{#if @isEngine}} |   {{#if @isEngine}} | ||||||
|     <LinkTo @route={{@link}}> |     <LinkTo @route={{@link}} data-test-secret-list-tab={{@label}}> | ||||||
|       {{@label}} |       {{@label}} | ||||||
|     </LinkTo> |     </LinkTo> | ||||||
|   {{else}} |   {{else}} | ||||||
|   | |||||||
| @@ -1,33 +1,38 @@ | |||||||
| <PageHeader as |p|> |  | ||||||
|   <p.top> |  | ||||||
|     <Page::Breadcrumbs @breadcrumbs={{this.breadcrumbs}} /> |  | ||||||
|   </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> | <Toolbar> | ||||||
|   <ToolbarActions> |   <ToolbarActions> | ||||||
|     <ConfirmAction |     {{#if @role.canDelete}} | ||||||
|       @buttonClasses="toolbar-link" |       <ConfirmAction | ||||||
|       @onConfirmAction={{this.deleteRole}} |         @buttonClasses="toolbar-link" | ||||||
|       @confirmTitle="Delete role?" |         @onConfirmAction={{this.deleteRole}} | ||||||
|       @confirmButtonText="Delete" |         @confirmTitle="Delete role?" | ||||||
|       data-test-pki-role-delete |         @confirmButtonText="Delete" | ||||||
|     > |         data-test-pki-role-delete | ||||||
|       Delete |       > | ||||||
|     </ConfirmAction> |         Delete | ||||||
|     <div class="toolbar-separator"></div> |       </ConfirmAction> | ||||||
|     <LinkTo class="toolbar-link" @route="overview">Generate Certificate <Icon @name="chevron-right" /></LinkTo> |       <div class="toolbar-separator"></div> | ||||||
|     <LinkTo class="toolbar-link" @route="overview">Sign Certificate <Icon @name="chevron-right" /></LinkTo> |     {{/if}} | ||||||
|     <LinkTo class="toolbar-link" @route="roles.role.edit" @model={{@role.id}}>Edit <Icon @name="chevron-right" /></LinkTo> |     {{#if @role.canGenerateCert}} | ||||||
|  |       <LinkTo class="toolbar-link" @route="roles.role.generate" @model={{@role.id}} data-test-pki-role-generate-cert> | ||||||
|  |         Generate Certificate | ||||||
|  |         <Icon @name="chevron-right" /> | ||||||
|  |       </LinkTo> | ||||||
|  |     {{/if}} | ||||||
|  |     {{#if @role.canSign}} | ||||||
|  |       <LinkTo class="toolbar-link" @route="roles.role.sign" @model={{@role.id}} data-test-pki-role-sign-cert> | ||||||
|  |         Sign Certificate | ||||||
|  |         <Icon @name="chevron-right" /> | ||||||
|  |       </LinkTo> | ||||||
|  |     {{/if}} | ||||||
|  |     {{#if @role.canEdit}} | ||||||
|  |       <LinkTo class="toolbar-link" @route="roles.role.edit" @model={{@role.id}} data-test-pki-role-edit-link> | ||||||
|  |         Edit | ||||||
|  |         <Icon @name="chevron-right" /> | ||||||
|  |       </LinkTo> | ||||||
|  |     {{/if}} | ||||||
|   </ToolbarActions> |   </ToolbarActions> | ||||||
| </Toolbar> | </Toolbar> | ||||||
| {{#each @role.fieldGroups as |fg|}} | {{#each @role.formFieldGroups as |fg|}} | ||||||
|   {{#each-in fg as |group fields|}} |   {{#each-in fg as |group fields|}} | ||||||
|     {{#if (not-eq group "default")}} |     {{#if (not-eq group "default")}} | ||||||
|       <h3 class="is-size-4 has-text-semibold has-top-margin-m">{{group}}</h3> |       <h3 class="is-size-4 has-text-semibold has-top-margin-m">{{group}}</h3> | ||||||
|   | |||||||
| @@ -1,22 +1,24 @@ | |||||||
| import { action } from '@ember/object'; | import { action } from '@ember/object'; | ||||||
|  | import RouterService from '@ember/routing/router-service'; | ||||||
| import Component from '@glimmer/component'; | import Component from '@glimmer/component'; | ||||||
|  | import FlashMessageService from 'vault/services/flash-messages'; | ||||||
| // interface Attribute { | import { inject as service } from '@ember/service'; | ||||||
| //   name: string; | import errorMessage from 'vault/utils/error-message'; | ||||||
| //   options?: { |  | ||||||
| //     label?: string; |  | ||||||
| //   }; |  | ||||||
| // } |  | ||||||
|  |  | ||||||
| // TODO: pull this in from route model once it's TS | // TODO: pull this in from route model once it's TS | ||||||
| interface Args { | interface Args { | ||||||
|   role: { |   role: { | ||||||
|     backend: string; |     backend: string; | ||||||
|     id: string; |     id: string; | ||||||
|  |     rollbackAttributes: () => void; | ||||||
|  |     destroyRecord: () => void; | ||||||
|   }; |   }; | ||||||
| } | } | ||||||
|  |  | ||||||
| export default class DetailsPage extends Component<Args> { | export default class DetailsPage extends Component<Args> { | ||||||
|  |   @service declare readonly router: RouterService; | ||||||
|  |   @service declare readonly flashMessages: FlashMessageService; | ||||||
|  |  | ||||||
|   get breadcrumbs() { |   get breadcrumbs() { | ||||||
|     return [ |     return [ | ||||||
|       { label: 'secrets', route: 'secrets', linkExternal: true }, |       { label: 'secrets', route: 'secrets', linkExternal: true }, | ||||||
| @@ -30,7 +32,15 @@ export default class DetailsPage extends Component<Args> { | |||||||
|     return ['keyUsage', 'extKeyUsage', 'extKeyUsageOids']; |     return ['keyUsage', 'extKeyUsage', 'extKeyUsageOids']; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @action deleteRole() { |   @action | ||||||
|     // TODO: delete role |   async deleteRole() { | ||||||
|  |     try { | ||||||
|  |       await this.args.role.destroyRecord(); | ||||||
|  |       this.flashMessages.success('Role deleted successfully'); | ||||||
|  |       this.router.transitionTo('vault.cluster.secrets.backend.pki.roles.index'); | ||||||
|  |     } catch (error) { | ||||||
|  |       this.args.role.rollbackAttributes(); | ||||||
|  |       this.flashMessages.danger(errorMessage(error)); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,25 +1,9 @@ | |||||||
| <PageHeader as |p|> |  | ||||||
|   <p.top> |  | ||||||
|     <Page::Breadcrumbs @breadcrumbs={{this.breadcrumbs}} /> |  | ||||||
|   </p.top> |  | ||||||
|   <p.levelLeft> |  | ||||||
|     <h1 class="title is-3" data-test-role-details-title> |  | ||||||
|       {{#if @model.isNew}} |  | ||||||
|         Create a PKI role |  | ||||||
|       {{else}} |  | ||||||
|         Edit |  | ||||||
|         {{@model.name}} |  | ||||||
|       {{/if}} |  | ||||||
|     </h1> |  | ||||||
|   </p.levelLeft> |  | ||||||
| </PageHeader> |  | ||||||
|  |  | ||||||
| <form {{on "submit" (perform this.save)}}> | <form {{on "submit" (perform this.save)}}> | ||||||
|   <div class="box is-sideless is-fullwidth is-marginless"> |   <div class="box is-sideless is-fullwidth is-marginless"> | ||||||
|     <MessageError @errorMessage={{this.errorBanner}} class="has-top-margin-s" /> |     <MessageError @errorMessage={{this.errorBanner}} class="has-top-margin-s" /> | ||||||
|     {{! ARG TODO write a test for namespace reminder }} |     {{! ARG TODO write a test for namespace reminder }} | ||||||
|     <NamespaceReminder @mode={{if @model.isNew "create" "update"}} @noun="PKI role" /> |     <NamespaceReminder @mode={{if @model.isNew "create" "update"}} @noun="PKI role" /> | ||||||
|     {{#each @model.fieldGroups as |fieldGroup|}} |     {{#each @model.formFieldGroups as |fieldGroup|}} | ||||||
|       {{#each-in fieldGroup as |group fields|}} |       {{#each-in fieldGroup as |group fields|}} | ||||||
|         {{! DEFAULT VIEW }} |         {{! DEFAULT VIEW }} | ||||||
|         {{#if (eq group "default")}} |         {{#if (eq group "default")}} | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ export default class PkiCertificatesIndexRoute extends Route { | |||||||
|  |  | ||||||
|   beforeModel() { |   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 it doesn't add OpenApi to record. | ||||||
|     return this.pathHelp.getNewModel('pki/certificate', 'pki'); |     return this.pathHelp.getNewModel('pki/certificate', this.secretMountPath.currentPath); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   model() { |   model() { | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ export default class PkiIssuersIndexRoute extends Route { | |||||||
|  |  | ||||||
|   beforeModel() { |   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 it doesn't add OpenApi to record. | ||||||
|     return this.pathHelp.getNewModel('pki/issuer', 'pki'); |     return this.pathHelp.getNewModel('pki/issuer', this.secretMountPath.currentPath); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   model() { |   model() { | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ export default class PkiKeysIndexRoute extends Route { | |||||||
|  |  | ||||||
|   beforeModel() { |   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 it doesn't add OpenApi to record. | ||||||
|     return this.pathHelp.getNewModel('pki/key', 'pki'); |     return this.pathHelp.getNewModel('pki/key', this.secretMountPath.currentPath); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   model() { |   model() { | ||||||
|   | |||||||
| @@ -12,4 +12,15 @@ export default class PkiRolesCreateRoute extends PkiRolesIndexRoute { | |||||||
|       backend: this.secretMountPath.currentPath, |       backend: this.secretMountPath.currentPath, | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   setupController(controller, resolvedModel) { | ||||||
|  |     super.setupController(controller, resolvedModel); | ||||||
|  |     const backend = this.secretMountPath.currentPath || 'pki'; | ||||||
|  |     controller.breadcrumbs = [ | ||||||
|  |       { label: 'secrets', route: 'secrets', linkExternal: true }, | ||||||
|  |       { label: backend, route: 'overview' }, | ||||||
|  |       { label: 'roles', route: 'roles.index' }, | ||||||
|  |       { label: 'create' }, | ||||||
|  |     ]; | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ export default class PkiRolesIndexRoute extends Route { | |||||||
|   beforeModel() { |   beforeModel() { | ||||||
|     // Must call this promise before the model hook otherwise |     // Must call this promise before the model hook otherwise | ||||||
|     // the model doesn't hydrate from OpenAPI correctly. |     // the model doesn't hydrate from OpenAPI correctly. | ||||||
|     return this.pathHelp.getNewModel('pki/role', 'pki'); |     return this.pathHelp.getNewModel('pki/role', this.secretMountPath.currentPath); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   model() { |   model() { | ||||||
|   | |||||||
| @@ -8,4 +8,16 @@ export default class RolesRoleDetailsRoute extends PkiRolesIndexRoute { | |||||||
|       id: role, |       id: role, | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   setupController(controller, resolvedModel) { | ||||||
|  |     super.setupController(controller, resolvedModel); | ||||||
|  |     const { id } = resolvedModel; | ||||||
|  |     const backend = this.secretMountPath.currentPath || 'pki'; | ||||||
|  |     controller.breadcrumbs = [ | ||||||
|  |       { label: 'secrets', route: 'secrets', linkExternal: true }, | ||||||
|  |       { label: backend, route: 'overview' }, | ||||||
|  |       { label: 'roles', route: 'roles.index' }, | ||||||
|  |       { label: id }, | ||||||
|  |     ]; | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -10,4 +10,17 @@ export default class PkiRoleEditRoute extends PkiRolesIndexRoute { | |||||||
|       id: role, |       id: role, | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   setupController(controller, resolvedModel) { | ||||||
|  |     super.setupController(controller, resolvedModel); | ||||||
|  |     const { id } = resolvedModel; | ||||||
|  |     const backend = this.secretMountPath.currentPath || 'pki'; | ||||||
|  |     controller.breadcrumbs = [ | ||||||
|  |       { label: 'secrets', route: 'secrets', linkExternal: true }, | ||||||
|  |       { label: backend, route: 'overview' }, | ||||||
|  |       { label: 'roles', route: 'roles.index' }, | ||||||
|  |       { label: id, route: 'roles.role.details' }, | ||||||
|  |       { label: 'edit' }, | ||||||
|  |     ]; | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,3 +1,14 @@ | |||||||
|  | <PageHeader as |p|> | ||||||
|  |   <p.top> | ||||||
|  |     <Page::Breadcrumbs @breadcrumbs={{this.breadcrumbs}} /> | ||||||
|  |   </p.top> | ||||||
|  |   <p.levelLeft> | ||||||
|  |     <h1 class="title is-3" data-test-pki-role-page-title> | ||||||
|  |       Create a PKI role | ||||||
|  |     </h1> | ||||||
|  |   </p.levelLeft> | ||||||
|  | </PageHeader> | ||||||
|  |  | ||||||
| <PkiRoleForm | <PkiRoleForm | ||||||
|   @model={{this.model}} |   @model={{this.model}} | ||||||
|   @onCancel={{transition-to "vault.cluster.secrets.backend.pki.roles.index"}} |   @onCancel={{transition-to "vault.cluster.secrets.backend.pki.roles.index"}} | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ | |||||||
| /> | /> | ||||||
| <Toolbar> | <Toolbar> | ||||||
|   <ToolbarActions> |   <ToolbarActions> | ||||||
|     <ToolbarLink @type="add" @route="roles.create"> |     <ToolbarLink @type="add" @route="roles.create" data-test-pki-role-create-link> | ||||||
|       Create role |       Create role | ||||||
|     </ToolbarLink> |     </ToolbarLink> | ||||||
|   </ToolbarActions> |   </ToolbarActions> | ||||||
|   | |||||||
| @@ -1 +1,13 @@ | |||||||
|  | <PageHeader as |p|> | ||||||
|  |   <p.top> | ||||||
|  |     <Page::Breadcrumbs @breadcrumbs={{this.breadcrumbs}} /> | ||||||
|  |   </p.top> | ||||||
|  |   <p.levelLeft> | ||||||
|  |     <h1 class="title is-3" data-test-pki-role-page-title> | ||||||
|  |       <Icon @name="file-text" @size="24" class="has-text-grey-light" /> | ||||||
|  |       PKI Role | ||||||
|  |       <code>{{this.model.name}}</code> | ||||||
|  |     </h1> | ||||||
|  |   </p.levelLeft> | ||||||
|  | </PageHeader> | ||||||
| <Page::PkiRoleDetails @role={{this.model}} /> | <Page::PkiRoleDetails @role={{this.model}} /> | ||||||
| @@ -1,3 +1,13 @@ | |||||||
|  | <PageHeader as |p|> | ||||||
|  |   <p.top> | ||||||
|  |     <Page::Breadcrumbs @breadcrumbs={{this.breadcrumbs}} /> | ||||||
|  |   </p.top> | ||||||
|  |   <p.levelLeft> | ||||||
|  |     <h1 class="title is-3" data-test-pki-role-page-title> | ||||||
|  |       Edit role | ||||||
|  |     </h1> | ||||||
|  |   </p.levelLeft> | ||||||
|  | </PageHeader> | ||||||
| <PkiRoleForm | <PkiRoleForm | ||||||
|   @model={{this.model}} |   @model={{this.model}} | ||||||
|   @onCancel={{transition-to "vault.cluster.secrets.backend.pki.roles.role.details" this.model.id}} |   @onCancel={{transition-to "vault.cluster.secrets.backend.pki.roles.role.details" this.model.id}} | ||||||
|   | |||||||
							
								
								
									
										205
									
								
								ui/tests/acceptance/pki/pki-engine-workflow-test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								ui/tests/acceptance/pki/pki-engine-workflow-test.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,205 @@ | |||||||
|  | import { create } from 'ember-cli-page-object'; | ||||||
|  | import { module, test } from 'qunit'; | ||||||
|  | import { setupApplicationTest } from 'ember-qunit'; | ||||||
|  | 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 consoleClass from 'vault/tests/pages/components/console/ui-panel'; | ||||||
|  | import { click, currentURL, fillIn, visit } from '@ember/test-helpers'; | ||||||
|  | import { SELECTORS } from 'vault/tests/helpers/pki/workflow'; | ||||||
|  |  | ||||||
|  | const consoleComponent = create(consoleClass); | ||||||
|  |  | ||||||
|  | const tokenWithPolicy = async function (name, policy) { | ||||||
|  |   await consoleComponent.runCommands([ | ||||||
|  |     `write sys/policies/acl/${name} policy=${btoa(policy)}`, | ||||||
|  |     `write -field=client_token auth/token/create policies=${name}`, | ||||||
|  |   ]); | ||||||
|  |   return consoleComponent.lastLogOutput; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const runCommands = async function (commands) { | ||||||
|  |   try { | ||||||
|  |     await consoleComponent.runCommands(commands); | ||||||
|  |     const res = consoleComponent.lastLogOutput; | ||||||
|  |     if (res.includes('Error')) { | ||||||
|  |       throw new Error(res); | ||||||
|  |     } | ||||||
|  |     return res; | ||||||
|  |   } catch (error) { | ||||||
|  |     // eslint-disable-next-line no-console | ||||||
|  |     console.error( | ||||||
|  |       `The following occurred when trying to run the command(s):\n ${commands.join('\n')} \n\n ${ | ||||||
|  |         consoleComponent.lastLogOutput | ||||||
|  |       }` | ||||||
|  |     ); | ||||||
|  |     throw error; | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * This test module should test the PKI workflow, including: | ||||||
|  |  * - link between pages and confirm that the url is as expected | ||||||
|  |  * - log in as user with a policy and ensure expected UI elements are shown/hidden | ||||||
|  |  */ | ||||||
|  | module('Acceptance | pki workflow', function (hooks) { | ||||||
|  |   setupApplicationTest(hooks); | ||||||
|  |  | ||||||
|  |   hooks.beforeEach(async function () { | ||||||
|  |     await authPage.login(); | ||||||
|  |     // Setup PKI engine | ||||||
|  |     const mountPath = `pki-workflow-${new Date().getTime()}`; | ||||||
|  |     await enablePage.enable('pki', mountPath); | ||||||
|  |     await runCommands([ | ||||||
|  |       `write ${mountPath}/roles/some-role \ | ||||||
|  |     issuer_ref="default" \ | ||||||
|  |     allowed_domains="example.com" \ | ||||||
|  |     allow_subdomains=true \ | ||||||
|  |     max_ttl="720h"`, | ||||||
|  |     ]); | ||||||
|  |     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('roles', function (hooks) { | ||||||
|  |     hooks.beforeEach(async function () { | ||||||
|  |       await authPage.login(); | ||||||
|  |       // Setup role-specific items | ||||||
|  |       await runCommands([ | ||||||
|  |         `write ${this.mountPath}/roles/some-role \ | ||||||
|  |       issuer_ref="default" \ | ||||||
|  |       allowed_domains="example.com" \ | ||||||
|  |       allow_subdomains=true \ | ||||||
|  |       max_ttl="720h"`, | ||||||
|  |       ]); | ||||||
|  |       const pki_admin_policy = ` | ||||||
|  |           path "${this.mountPath}/*" { | ||||||
|  |             capabilities = ["create", "read", "update", "delete", "list"] | ||||||
|  |           }, | ||||||
|  |         `; | ||||||
|  |       const pki_reader_policy = ` | ||||||
|  |         path "${this.mountPath}/roles" { | ||||||
|  |           capabilities = ["read", "list"] | ||||||
|  |         }, | ||||||
|  |         path "${this.mountPath}/roles/*" { | ||||||
|  |           capabilities = ["read", "list"] | ||||||
|  |         }, | ||||||
|  |       `; | ||||||
|  |       const pki_editor_policy = ` | ||||||
|  |         path "${this.mountPath}/roles" { | ||||||
|  |           capabilities = ["read", "list"] | ||||||
|  |         }, | ||||||
|  |         path "${this.mountPath}/roles/*" { | ||||||
|  |           capabilities = ["read", "update"] | ||||||
|  |         }, | ||||||
|  |       `; | ||||||
|  |       this.pkiRoleReader = await tokenWithPolicy('pki-reader', pki_reader_policy); | ||||||
|  |       this.pkiRoleEditor = await tokenWithPolicy('pki-editor', pki_editor_policy); | ||||||
|  |       this.pkiAdminToken = await tokenWithPolicy('pki-admin', pki_admin_policy); | ||||||
|  |       await logout.visit(); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     test('shows correct items if user has all permissions', async function (assert) { | ||||||
|  |       await authPage.login(this.pkiAdminToken); | ||||||
|  |       await visit(`/vault/secrets/${this.mountPath}/pki/overview`); | ||||||
|  |       assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/overview`); | ||||||
|  |       assert.dom(SELECTORS.rolesTab).exists('Roles tab is present'); | ||||||
|  |       await click(SELECTORS.rolesTab); | ||||||
|  |       assert.dom(SELECTORS.createRoleLink).exists({ count: 1 }, 'Create role link is rendered'); | ||||||
|  |       assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/roles`); | ||||||
|  |       assert.dom('.linked-block').exists({ count: 1 }, 'One role is in list'); | ||||||
|  |       await click('.linked-block'); | ||||||
|  |       assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/roles/some-role/details`); | ||||||
|  |  | ||||||
|  |       assert.dom(SELECTORS.generateCertLink).exists('Generate cert link is shown'); | ||||||
|  |       await click(SELECTORS.generateCertLink); | ||||||
|  |       assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/roles/some-role/generate`); | ||||||
|  |  | ||||||
|  |       // Go back to details and test all the links | ||||||
|  |       await visit(`/vault/secrets/${this.mountPath}/pki/roles/some-role/details`); | ||||||
|  |       assert.dom(SELECTORS.signCertLink).exists('Sign cert link is shown'); | ||||||
|  |       await click(SELECTORS.signCertLink); | ||||||
|  |       assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/roles/some-role/sign`); | ||||||
|  |  | ||||||
|  |       await visit(`/vault/secrets/${this.mountPath}/pki/roles/some-role/details`); | ||||||
|  |       assert.dom(SELECTORS.editRoleLink).exists('Edit link is shown'); | ||||||
|  |       await click(SELECTORS.editRoleLink); | ||||||
|  |       assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/roles/some-role/edit`); | ||||||
|  |  | ||||||
|  |       await visit(`/vault/secrets/${this.mountPath}/pki/roles/some-role/details`); | ||||||
|  |       assert.dom(SELECTORS.deleteRoleButton).exists('Delete role button is shown'); | ||||||
|  |       await click(`${SELECTORS.deleteRoleButton} [data-test-confirm-action-trigger]`); | ||||||
|  |       await click(`[data-test-confirm-button]`); | ||||||
|  |       assert.strictEqual( | ||||||
|  |         currentURL(), | ||||||
|  |         `/vault/secrets/${this.mountPath}/pki/roles`, | ||||||
|  |         'redirects to roles list after deletion' | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     test('it does not show toolbar items the user does not have permission to see', async function (assert) { | ||||||
|  |       await authPage.login(this.pkiRoleReader); | ||||||
|  |       await visit(`/vault/secrets/${this.mountPath}/pki/overview`); | ||||||
|  |       assert.dom(SELECTORS.rolesTab).exists('Roles tab is present'); | ||||||
|  |       await click(SELECTORS.rolesTab); | ||||||
|  |       assert.dom(SELECTORS.createRoleLink).exists({ count: 1 }, 'Create role link is rendered'); | ||||||
|  |       assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/roles`); | ||||||
|  |       assert.dom('.linked-block').exists({ count: 1 }, 'One role is in list'); | ||||||
|  |       await click('.linked-block'); | ||||||
|  |       assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/roles/some-role/details`); | ||||||
|  |       assert.dom(SELECTORS.deleteRoleButton).doesNotExist('Delete role button is not shown'); | ||||||
|  |       assert.dom(SELECTORS.generateCertLink).doesNotExist('Generate cert link is not shown'); | ||||||
|  |       assert.dom(SELECTORS.signCertLink).doesNotExist('Sign cert link is not shown'); | ||||||
|  |       assert.dom(SELECTORS.editRoleLink).doesNotExist('Edit link is not shown'); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     test('it shows correct toolbar items for the user policy', async function (assert) { | ||||||
|  |       await authPage.login(this.pkiRoleEditor); | ||||||
|  |       await visit(`/vault/secrets/${this.mountPath}/pki/overview`); | ||||||
|  |       assert.dom(SELECTORS.rolesTab).exists('Roles tab is present'); | ||||||
|  |       await click(SELECTORS.rolesTab); | ||||||
|  |       assert.dom(SELECTORS.createRoleLink).exists({ count: 1 }, 'Create role link is rendered'); | ||||||
|  |       assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/roles`); | ||||||
|  |       assert.dom('.linked-block').exists({ count: 1 }, 'One role is in list'); | ||||||
|  |       await click('.linked-block'); | ||||||
|  |       assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/roles/some-role/details`); | ||||||
|  |       assert.dom(SELECTORS.deleteRoleButton).doesNotExist('Delete role button is not shown'); | ||||||
|  |       assert.dom(SELECTORS.generateCertLink).doesNotExist('Generate cert link is not shown'); | ||||||
|  |       assert.dom(SELECTORS.signCertLink).doesNotExist('Sign cert link is not shown'); | ||||||
|  |       assert.dom(SELECTORS.editRoleLink).exists('Edit link is shown'); | ||||||
|  |       await click(SELECTORS.editRoleLink); | ||||||
|  |       assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/roles/some-role/edit`); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     test('create role happy path', async function (assert) { | ||||||
|  |       const roleName = 'another-role'; | ||||||
|  |       await authPage.login(this.pkiAdminToken); | ||||||
|  |       await visit(`/vault/secrets/${this.mountPath}/pki/overview`); | ||||||
|  |       assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/overview`); | ||||||
|  |       assert.dom(SELECTORS.rolesTab).exists('Roles tab is present'); | ||||||
|  |       await click(SELECTORS.rolesTab); | ||||||
|  |       assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/roles`); | ||||||
|  |       await click(SELECTORS.createRoleLink); | ||||||
|  |       assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/roles/create`); | ||||||
|  |       assert.dom(SELECTORS.breadcrumbContainer).exists({ count: 1 }, 'breadcrumbs are rendered'); | ||||||
|  |       assert.dom(SELECTORS.breadcrumbs).exists({ count: 4 }, 'Shows 4 breadcrumbs'); | ||||||
|  |       assert.dom(SELECTORS.pageTitle).hasText('Create a PKI role'); | ||||||
|  |  | ||||||
|  |       await fillIn(SELECTORS.roleForm.roleName, roleName); | ||||||
|  |       await click(SELECTORS.roleForm.roleCreateButton); | ||||||
|  |  | ||||||
|  |       assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/roles/${roleName}/details`); | ||||||
|  |       assert.dom(SELECTORS.breadcrumbs).exists({ count: 4 }, 'Shows 4 breadcrumbs'); | ||||||
|  |       assert.dom(SELECTORS.pageTitle).hasText(`PKI Role ${roleName}`); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
| @@ -1,7 +1,4 @@ | |||||||
| export const SELECTORS = { | export const SELECTORS = { | ||||||
|   breadcrumbContainer: '[data-test-breadcrumbs]', |  | ||||||
|   breadcrumbs: '[data-test-breadcrumbs] li', |  | ||||||
|   title: '[data-test-role-details-title]', |  | ||||||
|   issuerLabel: '[data-test-row-label="Issuer"]', |   issuerLabel: '[data-test-row-label="Issuer"]', | ||||||
|   noStoreValue: '[data-test-value-div="Store in storage backend"]', |   noStoreValue: '[data-test-value-div="Store in storage backend"]', | ||||||
|   keyUsageValue: '[data-test-value-div="Key usage"]', |   keyUsageValue: '[data-test-value-div="Key usage"]', | ||||||
|   | |||||||
							
								
								
									
										22
									
								
								ui/tests/helpers/pki/workflow.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								ui/tests/helpers/pki/workflow.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | import { SELECTORS as ROLEFORM } from './roles/form'; | ||||||
|  | export const SELECTORS = { | ||||||
|  |   breadcrumbContainer: '[data-test-breadcrumbs]', | ||||||
|  |   breadcrumbs: '[data-test-breadcrumbs] li', | ||||||
|  |   pageTitle: '[data-test-pki-role-page-title]', | ||||||
|  |   // TABS | ||||||
|  |   overviewTab: '[data-test-secret-list-tab="Overview"]', | ||||||
|  |   rolesTab: '[data-test-secret-list-tab="Roles"]', | ||||||
|  |   issuersTab: '[data-test-secret-list-tab="Issuers"]', | ||||||
|  |   certsTab: '[data-test-secret-list-tab="Certificates"]', | ||||||
|  |   keysTab: '[data-test-secret-list-tab="Keys"]', | ||||||
|  |   configTab: '[data-test-secret-list-tab="Configuration"]', | ||||||
|  |   // ROLES | ||||||
|  |   deleteRoleButton: '[data-test-pki-role-delete]', | ||||||
|  |   generateCertLink: '[data-test-pki-role-generate-cert]', | ||||||
|  |   signCertLink: '[data-test-pki-role-sign-cert]', | ||||||
|  |   editRoleLink: '[data-test-pki-role-edit-link]', | ||||||
|  |   createRoleLink: '[data-test-pki-role-create-link]', | ||||||
|  |   roleForm: { | ||||||
|  |     ...ROLEFORM, | ||||||
|  |   }, | ||||||
|  | }; | ||||||
| @@ -27,7 +27,7 @@ module('Integration | Component | pki key details page', function (hooks) { | |||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   test('it renders the page component and deletes a key', async function (assert) { |   test('it renders the page component and deletes a key', async function (assert) { | ||||||
|     assert.expect(9); |     assert.expect(7); | ||||||
|     this.server.delete(`${this.backend}/key/${this.model.keyId}`, () => { |     this.server.delete(`${this.backend}/key/${this.model.keyId}`, () => { | ||||||
|       assert.ok(true, 'confirming delete fires off destroyRecord()'); |       assert.ok(true, 'confirming delete fires off destroyRecord()'); | ||||||
|     }); |     }); | ||||||
| @@ -39,8 +39,6 @@ module('Integration | Component | pki key details page', function (hooks) { | |||||||
|       { owner: this.engine } |       { 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.title).containsText('View key', 'title renders'); | ||||||
|     assert.dom(SELECTORS.keyIdValue).hasText(' 724862ff-6438-bad0-b598-77a6c7f4e934', 'key id 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.keyNameValue).hasText('test-key', 'key name renders'); | ||||||
|   | |||||||
| @@ -11,9 +11,8 @@ module('Integration | Component | pki-key-parameters', function (hooks) { | |||||||
|  |  | ||||||
|   hooks.beforeEach(function () { |   hooks.beforeEach(function () { | ||||||
|     this.store = this.owner.lookup('service:store'); |     this.store = this.owner.lookup('service:store'); | ||||||
|     this.model = this.store.createRecord('pki/role'); |     this.model = this.store.createRecord('pki/role', { backend: 'pki' }); | ||||||
|     this.model.backend = 'pki'; |     [this.fields] = Object.values(this.model.formFieldGroups.find((g) => g['Key parameters'])); | ||||||
|     [this.fields] = Object.values(this.model.fieldGroups.find((g) => g['Key parameters'])); |  | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   test('it should render the component and display the correct defaults', async function (assert) { |   test('it should render the component and display the correct defaults', async function (assert) { | ||||||
|   | |||||||
| @@ -22,17 +22,13 @@ module('Integration | Component | pki role details page', function (hooks) { | |||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   test('it should render the page component', async function (assert) { |   test('it should render the page component', async function (assert) { | ||||||
|     assert.expect(8); |     assert.expect(5); | ||||||
|     await render( |     await render( | ||||||
|       hbs` |       hbs` | ||||||
|       <Page::PkiRoleDetails @role={{this.model}} /> |       <Page::PkiRoleDetails @role={{this.model}} /> | ||||||
|   `, |   `, | ||||||
|       { owner: this.engine } |       { 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.issuerLabel).hasText('Issuer', 'Label is'); | ||||||
|     assert.dom(SELECTORS.keyUsageValue).hasText('None', 'Key usage shows none when array is empty'); |     assert.dom(SELECTORS.keyUsageValue).hasText('None', 'Key usage shows none when array is empty'); | ||||||
|     assert |     assert | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Chelsea Shaw
					Chelsea Shaw