mirror of
https://github.com/hashicorp/vault.git
synced 2026-02-03 20:40:45 -05:00
PKI role create view (#17263)
* dynamically render the secretlistheader in the parent route. * start getting form setup even without openAPi working * add in create and cancel * making openAPI work * add default openAPI params * wip for new component with two radio options a ttl and input * handle createRecord on pki-roles-form * remove tooltips and cleanup * move formfieldgroupsloop back to non addon * cleanup * move secretListHeader * broadcast from radioSelectTtlOrString to parent * cleanup * cleanup from pr comments * more cleanup * addressing Jordans comments * use formFieldGroupsLoop move into addon. * cleanup
This commit is contained in:
parent
2136c1f8a3
commit
6771e564d4
20 changed files with 418 additions and 44 deletions
|
|
@ -20,9 +20,8 @@ export default class PkiIssuerEngineAdapter extends ApplicationAdapter {
|
|||
return url;
|
||||
}
|
||||
|
||||
async query(store, type, query) {
|
||||
query(store, type, query) {
|
||||
const { backend, id } = query;
|
||||
let response = await this.ajax(this.urlForQuery(backend, id), 'GET', this.optionsForQuery(id));
|
||||
return response;
|
||||
return this.ajax(this.urlForQuery(backend, id), 'GET', this.optionsForQuery(id));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,9 +20,8 @@ export default class PkiKeyEngineAdapter extends ApplicationAdapter {
|
|||
return url;
|
||||
}
|
||||
|
||||
async query(store, type, query) {
|
||||
query(store, type, query) {
|
||||
const { backend, id } = query;
|
||||
let response = await this.ajax(this.urlForQuery(backend, id), 'GET', this.optionsForQuery(id));
|
||||
return response;
|
||||
return this.ajax(this.urlForQuery(backend, id), 'GET', this.optionsForQuery(id));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,52 @@
|
|||
import PkiRoleAdapter from './pki-role';
|
||||
import ApplicationAdapter from '../application';
|
||||
import { assign } from '@ember/polyfills';
|
||||
import { encodePath } from 'vault/utils/path-encoding-helpers';
|
||||
|
||||
export default class PkiRoleEngineAdapter extends PkiRoleAdapter {}
|
||||
export default class PkiRoleEngineAdapter extends ApplicationAdapter {
|
||||
namespace = 'v1';
|
||||
|
||||
_urlForRole(backend, id) {
|
||||
let url = `${this.buildURL()}/${encodePath(backend)}/roles`;
|
||||
if (id) {
|
||||
url = url + '/' + encodePath(id);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
_optionsForQuery(id) {
|
||||
let data = {};
|
||||
if (!id) {
|
||||
data['list'] = true;
|
||||
}
|
||||
return { data };
|
||||
}
|
||||
|
||||
createRecord(store, type, snapshot) {
|
||||
let name = snapshot.attr('name');
|
||||
let url = this._urlForRole(snapshot.record.backend, name);
|
||||
|
||||
return this.ajax(url, 'POST', { data: this.serialize(snapshot) }).then(() => {
|
||||
return {
|
||||
id: name,
|
||||
name,
|
||||
backend: snapshot.record.backend,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
fetchByQuery(store, query) {
|
||||
const { id, backend } = query;
|
||||
return this.ajax(this._urlForRole(backend, id), 'GET', this._optionsForQuery(id)).then((resp) => {
|
||||
const data = {
|
||||
id,
|
||||
name: id,
|
||||
backend,
|
||||
};
|
||||
|
||||
return assign({}, resp, data);
|
||||
});
|
||||
}
|
||||
|
||||
query(store, type, query) {
|
||||
return this.fetchByQuery(store, query);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,25 +6,77 @@ import { withModelValidations } from 'vault/decorators/model-validations';
|
|||
import fieldToAttrs from 'vault/utils/field-to-attrs';
|
||||
|
||||
const validations = {
|
||||
name: [
|
||||
{ type: 'presence', message: 'Name is required.' },
|
||||
{
|
||||
type: 'containsWhiteSpace',
|
||||
message: 'Name cannot contain whitespace.',
|
||||
},
|
||||
],
|
||||
name: [{ type: 'presence', message: 'Name is required.' }],
|
||||
};
|
||||
|
||||
@withModelValidations(validations)
|
||||
export default class PkiRoleEngineModel extends Model {
|
||||
@attr('string', { readOnly: true }) backend;
|
||||
|
||||
@attr('string', {
|
||||
label: 'Role name',
|
||||
fieldValue: 'id',
|
||||
readOnly: true,
|
||||
fieldValue: 'name',
|
||||
})
|
||||
name;
|
||||
|
||||
@attr('string', {
|
||||
label: 'Issuer reference',
|
||||
defaultValue: 'default',
|
||||
subText:
|
||||
'Specifies the issuer that will be used to create certificates with this role. To find this, run [command]. By default, we will use the mounts default issuer.',
|
||||
})
|
||||
issuerRef;
|
||||
|
||||
@attr({
|
||||
label: 'Not valid 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',
|
||||
})
|
||||
customTtl;
|
||||
|
||||
@attr({
|
||||
label: 'Backdate validity',
|
||||
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',
|
||||
hideToggle: true,
|
||||
})
|
||||
notBeforeDuration;
|
||||
|
||||
@attr({
|
||||
label: 'Max TTL',
|
||||
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',
|
||||
})
|
||||
maxTtl;
|
||||
|
||||
@attr('boolean', {
|
||||
label: 'Generate lease with certificate',
|
||||
subText:
|
||||
'Specifies if certificates issued/signed against this role will have Vault leases attached to them.',
|
||||
editType: 'boolean',
|
||||
docLink: '/api-docs/secret/pki#create-update-role',
|
||||
})
|
||||
generateLease;
|
||||
|
||||
@attr('boolean', {
|
||||
label: 'Do not store certificates in storage backend',
|
||||
subText:
|
||||
'This can improve performance when issuing large numbers of certificates. However, certificates issued in this way cannot be enumerated or revoked.',
|
||||
editType: 'boolean',
|
||||
docLink: '/api-docs/secret/pki#create-update-role',
|
||||
})
|
||||
noStore;
|
||||
|
||||
@attr('boolean', {
|
||||
label: 'Basic constraints valid for non CA.',
|
||||
subText: 'Mark Basic Constraints valid when issuing non-CA certificates.',
|
||||
editType: 'boolean',
|
||||
})
|
||||
addBasicConstraints;
|
||||
|
||||
// must be a getter so it can be added to the prototype needed in the pathHelp service on the line here: if (newModel.merged || modelProto.useOpenAPI !== true) {
|
||||
get useOpenAPI() {
|
||||
return true;
|
||||
|
|
@ -72,7 +124,18 @@ export default class PkiRoleEngineModel extends Model {
|
|||
get fieldGroups() {
|
||||
if (!this._fieldToAttrsGroups) {
|
||||
this._fieldToAttrsGroups = fieldToAttrs(this, [
|
||||
{ default: ['name'] },
|
||||
{
|
||||
default: [
|
||||
'name',
|
||||
'issuerRef',
|
||||
'customTtl',
|
||||
'notBeforeDuration',
|
||||
'maxTtl',
|
||||
'generateLease',
|
||||
'noStore',
|
||||
'addBasicConstraints',
|
||||
],
|
||||
},
|
||||
{
|
||||
'Domain handling': [
|
||||
'allowedDomains',
|
||||
|
|
|
|||
|
|
@ -213,6 +213,9 @@
|
|||
.has-top-margin-xxl {
|
||||
margin-top: $spacing-xxl;
|
||||
}
|
||||
.has-top-margin-negative-s {
|
||||
margin-top: (-1 * $spacing-s);
|
||||
}
|
||||
.has-left-margin-xxs {
|
||||
margin-left: $spacing-xxs;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,15 @@
|
|||
{{#each fields as |attr|}}
|
||||
{{! template-lint-configure simple-unless "warn" }}
|
||||
{{#unless (and (not-eq @mode "create") (eq attr.name "name"))}}
|
||||
<FormField data-test-field={{true}} @attr={{attr}} @model={{@model}} />
|
||||
<FormField
|
||||
data-test-field={{true}}
|
||||
@attr={{attr}}
|
||||
@model={{@model}}
|
||||
@modelValidations={{@modelValidations}}
|
||||
@showHelpText={{@showHelpText}}
|
||||
>
|
||||
{{yield attr}}
|
||||
</FormField>
|
||||
{{/unless}}
|
||||
{{/each}}
|
||||
{{else}}
|
||||
|
|
@ -20,7 +28,15 @@
|
|||
{{#if (get @model prop)}}
|
||||
<div class="box is-marginless">
|
||||
{{#each fields as |attr|}}
|
||||
<FormField data-test-field={{true}} @attr={{attr}} @model={{@model}} />
|
||||
<FormField
|
||||
data-test-field={{true}}
|
||||
@attr={{attr}}
|
||||
@model={{@model}}
|
||||
@modelValidations={{@modelValidations}}
|
||||
@showHelpText={{@showHelpText}}
|
||||
>
|
||||
{{yield attr}}
|
||||
</FormField>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
<FormFieldLabel
|
||||
for={{@attr.name}}
|
||||
@label={{this.labelString}}
|
||||
@helpText={{@attr.options.helpText}}
|
||||
@helpText={{(if this.showHelpText @attr.options.helpText)}}
|
||||
@subText={{@attr.options.subText}}
|
||||
@docLink={{@attr.options.docLink}}
|
||||
/>
|
||||
|
|
@ -321,7 +321,6 @@
|
|||
onchange={{this.onChangeWithEvent}}
|
||||
data-test-input={{@attr.name}}
|
||||
/>
|
||||
|
||||
<label for={{@attr.name}} class="is-label">
|
||||
{{this.labelString}}
|
||||
{{#if (and this.showHelpText @attr.options.helpText)}}
|
||||
|
|
@ -329,7 +328,14 @@
|
|||
{{/if}}
|
||||
</label>
|
||||
{{#if @attr.options.subText}}
|
||||
<p class="sub-text">{{@attr.options.subText}}</p>
|
||||
<p class="sub-text">
|
||||
{{@attr.options.subText}}
|
||||
{{#if @attr.options.docLink}}
|
||||
<DocLink @path={{@attr.options.docLink}}>
|
||||
Learn more here.
|
||||
</DocLink>
|
||||
{{/if}}
|
||||
</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{else if (eq @attr.type "object")}}
|
||||
|
|
@ -339,5 +345,7 @@
|
|||
@valueUpdated={{fn this.codemirrorUpdated false}}
|
||||
@helpText={{@attr.options.helpText}}
|
||||
/>
|
||||
{{else if (eq @attr.options.editType "yield")}}
|
||||
{{yield}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
|
@ -21,7 +21,7 @@ import { dasherize } from 'vault/helpers/dasherize';
|
|||
* label: "Foo", // custom label to be shown, otherwise attr.name will be displayed
|
||||
* defaultValue: "", // default value to display if model value is not present
|
||||
* fieldValue: "bar", // used for value lookup on model over attr.name
|
||||
* editType: "ttl", type of field to use -- example boolean, searchSelect, etc.
|
||||
* editType: "ttl", type of field to use. List of editTypes:boolean, file, json, kv, optionalText, mountAccessor, password, radio, regex, searchSelect, stringArray,textarea, ttl, yield.
|
||||
* helpText: "This will be in a tooltip",
|
||||
* readOnly: true
|
||||
* },
|
||||
|
|
@ -58,7 +58,7 @@ export default class FormFieldComponent extends Component {
|
|||
return this.args.disabled || false;
|
||||
}
|
||||
get showHelpText() {
|
||||
return this.args.showHelpText || true;
|
||||
return this.args.showHelpText === false ? false : true;
|
||||
}
|
||||
get subText() {
|
||||
return this.args.subText || '';
|
||||
|
|
|
|||
48
ui/lib/core/addon/components/radio-select-ttl-or-string.hbs
Normal file
48
ui/lib/core/addon/components/radio-select-ttl-or-string.hbs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<div class="column is-narrow is-flex-center has-text-grey has-right-margin-s has-top-margin-negative-s">
|
||||
<RadioButton
|
||||
class="radio"
|
||||
name="ttl"
|
||||
@value="ttl"
|
||||
@onChange={{this.onRadioButtonChange}}
|
||||
@groupValue={{this.groupValue}}
|
||||
/>
|
||||
<label class="has-left-margin-xs">
|
||||
<TtlPicker2
|
||||
data-test-input="ttl"
|
||||
@onChange={{this.setAndBroadcastTtl}}
|
||||
@label="TTL"
|
||||
@helperTextEnabled={{@attr.options.helperTextEnabled}}
|
||||
@description={{@attr.helpText}}
|
||||
@time={{this.ttlTime}}
|
||||
@unit="d"
|
||||
@hideToggle={{true}}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div class="column is-narrow is-flex-center has-text-grey has-right-margin-s">
|
||||
<RadioButton
|
||||
class="radio"
|
||||
name="not_after"
|
||||
@value="specificDate"
|
||||
@onChange={{this.onRadioButtonChange}}
|
||||
@groupValue={{this.groupValue}}
|
||||
/>
|
||||
<label class="has-left-margin-xs">
|
||||
<span class="ttl-picker-label is-large">Specific date</span><br />
|
||||
<p class="sub-text">
|
||||
This value format should be given in UTC format YYYY-MM-ddTHH:MM:SSZ.
|
||||
</p>
|
||||
{{#if (eq this.groupValue "specificDate")}}
|
||||
<input
|
||||
data-test-input="not_after"
|
||||
id="not_after"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
value={{this.notAfter}}
|
||||
{{on "input" this.setAndBroadcastInput}}
|
||||
class="input"
|
||||
maxLength="21"
|
||||
/>
|
||||
{{/if}}
|
||||
</label>
|
||||
</div>
|
||||
47
ui/lib/core/addon/components/radio-select-ttl-or-string.js
Normal file
47
ui/lib/core/addon/components/radio-select-ttl-or-string.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import Component from '@glimmer/component';
|
||||
import { action } from '@ember/object';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
/**
|
||||
* @module RadioSelectTtlOrString
|
||||
* `RadioSelectTtlOrString` components are yielded out within the formField component when the editType on the model is yield.
|
||||
* The component is two radio buttons, where the first option is a TTL, and the second option is an input field without a title.
|
||||
* This component is used in the PKI engine inside various forms.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* {{#each @model.fields as |attr|}}
|
||||
* <RadioSelectTtlOrString @attr={{attr}} @model={{this.model}} />
|
||||
* {{/each}}
|
||||
* ```
|
||||
* @param {Model} model - Ember Data model that `attr` is defined on.
|
||||
* @param {Object} attr - Usually derived from ember model `attributes` lookup, and all members of `attr.options` are optional.
|
||||
*/
|
||||
|
||||
export default class RadioSelectTtlOrString extends Component {
|
||||
@tracked groupValue = 'ttl';
|
||||
@tracked ttlTime;
|
||||
@tracked notAfter;
|
||||
|
||||
@action onRadioButtonChange(selection) {
|
||||
this.groupValue = selection;
|
||||
// Clear the previous selection if they have clicked the other radio button.
|
||||
if (selection === 'specificDate') {
|
||||
this.args.model.set('ttl', '');
|
||||
this.ttlTime = ''; //clear out the form field
|
||||
}
|
||||
if (selection === 'tll') {
|
||||
this.args.model.set('notAfter', '');
|
||||
this.notAfter = ''; //clear out the form field
|
||||
}
|
||||
}
|
||||
|
||||
@action setAndBroadcastTtl(value) {
|
||||
let valueToSet = value.enabled === true ? `${value.seconds}s` : 0;
|
||||
this.args.model.set('ttl', `${valueToSet}`);
|
||||
}
|
||||
|
||||
@action setAndBroadcastInput(event) {
|
||||
this.args.model.set('notAfter', event.target.value);
|
||||
}
|
||||
}
|
||||
1
ui/lib/core/app/components/form-field-groups-loop.js
Normal file
1
ui/lib/core/app/components/form-field-groups-loop.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from 'core/components/form-field-groups-loop';
|
||||
1
ui/lib/core/app/components/radio-select-ttl-or-string.js
Normal file
1
ui/lib/core/app/components/radio-select-ttl-or-string.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from 'core/components/radio-select-ttl-or-string';
|
||||
60
ui/lib/pki/addon/components/pki-role-form.hbs
Normal file
60
ui/lib/pki/addon/components/pki-role-form.hbs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
<PageHeader as |p|>
|
||||
<p.top>
|
||||
<KeyValueHeader
|
||||
@root={{hash label="role" text="role" path="vault.cluster.secrets.backend.pki.roles.index"}}
|
||||
@isEngine={{true}}
|
||||
>
|
||||
<li>
|
||||
<span class="sep">
|
||||
/
|
||||
</span>
|
||||
<LinkTo @route="roles.index">
|
||||
{{@model.backend}}
|
||||
</LinkTo>
|
||||
</li>
|
||||
</KeyValueHeader>
|
||||
</p.top>
|
||||
<p.levelLeft>
|
||||
<h1 class="title is-3">
|
||||
{{#if @model.isNew}}
|
||||
Create a PKI role
|
||||
{{else}}
|
||||
Edit a
|
||||
{{@model.id}}
|
||||
{{/if}}
|
||||
</h1>
|
||||
</p.levelLeft>
|
||||
</PageHeader>
|
||||
|
||||
<form {{on "submit" (perform this.save)}}>
|
||||
<div class="box is-sideless is-fullwidth is-marginless">
|
||||
<MessageError @errorMessage={{this.errorBanner}} class="has-top-margin-s" />
|
||||
{{! ARG TODO write a test for namespace reminder }}
|
||||
<NamespaceReminder @mode={{if @model.isNew "create" "update"}} @noun="PKI role" />
|
||||
<FormFieldGroupsLoop
|
||||
@model={{@model}}
|
||||
@mode={{if @model.isNew "create" "update"}}
|
||||
@modelValidations={{@modelValidations}}
|
||||
@showHelpText={{false}}
|
||||
as |attr|
|
||||
>
|
||||
<RadioSelectTtlOrString @model={{@model}} @attr={{attr}} />
|
||||
</FormFieldGroupsLoop>
|
||||
</div>
|
||||
<div class="has-top-padding-s">
|
||||
<button type="submit" class="button is-primary {{if this.save.isRunning 'is-loading'}}" disabled={{this.save.isRunning}}>
|
||||
{{if @model.isNew "Create" "Update"}}
|
||||
</button>
|
||||
<button type="button" class="button has-left-margin-s" disabled={{this.save.isRunning}} {{on "click" this.cancel}}>
|
||||
Cancel
|
||||
</button>
|
||||
{{#if this.modelValidations.targets.errors}}
|
||||
<AlertInline @type="danger" @message={{join ", " this.modelValidations.targets.errors}} @paddingTop={{true}} />
|
||||
{{/if}}
|
||||
{{#if this.invalidFormAlert}}
|
||||
<div class="control">
|
||||
<AlertInline @type="danger" @paddingTop={{true}} @message={{this.invalidFormAlert}} @mimicRefresh={{true}} />
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</form>
|
||||
56
ui/lib/pki/addon/components/pki-role-form.js
Normal file
56
ui/lib/pki/addon/components/pki-role-form.js
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import Component from '@glimmer/component';
|
||||
import { action } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { task } from 'ember-concurrency';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
/**
|
||||
* @module PkiRoleForm
|
||||
* PkiRoleForm components are used to create and update PKI roles.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* <PkiRoleForm @model={{this.model}}/>
|
||||
* ```
|
||||
* @callback onCancel
|
||||
* @callback onSave
|
||||
* @param {Object} model - Pki-role-engine model.
|
||||
* @param {onCancel} onCancel - Callback triggered when cancel button is clicked.
|
||||
* @param {onSave} onSave - Callback triggered on save success.
|
||||
*/
|
||||
|
||||
export default class PkiRoleForm extends Component {
|
||||
@service store;
|
||||
@service flashMessages;
|
||||
|
||||
@tracked errorBanner;
|
||||
@tracked invalidFormAlert;
|
||||
@tracked modelValidations;
|
||||
|
||||
@task
|
||||
*save(event) {
|
||||
event.preventDefault();
|
||||
try {
|
||||
const { isValid, state, invalidFormMessage } = this.args.model.validate();
|
||||
this.modelValidations = isValid ? null : state;
|
||||
this.invalidFormAlert = invalidFormMessage;
|
||||
if (isValid) {
|
||||
const { isNew, name } = this.args.model;
|
||||
yield this.args.model.save();
|
||||
this.flashMessages.success(`Successfully ${isNew ? 'created' : 'updated'} the role ${name}.`);
|
||||
this.args.onSave();
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error.errors ? error.errors.join('. ') : error.message;
|
||||
this.errorBanner = message;
|
||||
this.invalidFormAlert = 'There was an error submitting this form.';
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
cancel() {
|
||||
const method = this.args.model.isNew ? 'unloadRecord' : 'rollbackAttributes';
|
||||
this.args.model[method]();
|
||||
this.args.onCancel();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
import Route from '@ember/routing/route';
|
||||
|
||||
export default class PkiRolesRoute extends Route {}
|
||||
18
ui/lib/pki/addon/routes/roles/create.js
Normal file
18
ui/lib/pki/addon/routes/roles/create.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class PkiRolesCreateRoute extends Route {
|
||||
@service store;
|
||||
@service secretMountPath;
|
||||
@service pathHelp;
|
||||
|
||||
beforeModel() {
|
||||
return this.pathHelp.getNewModel('pki/pki-role-engine', 'pki');
|
||||
}
|
||||
|
||||
model() {
|
||||
return this.store.createRecord('pki/pki-role-engine', {
|
||||
backend: this.secretMountPath.currentPath,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -6,12 +6,17 @@ export default class RolesIndexRoute extends Route {
|
|||
@service secretMountPath;
|
||||
@service pathHelp;
|
||||
|
||||
model() {
|
||||
// the pathHelp service is needed for adding openAPI to the model
|
||||
this.pathHelp.getNewModel('pki/pki-role-engine', 'pki');
|
||||
beforeModel() {
|
||||
// Must call this promise before the model hook otherwise it doesn't add OpenApi to record.
|
||||
return this.pathHelp.getNewModel('pki/pki-role-engine', 'pki');
|
||||
}
|
||||
|
||||
model() {
|
||||
return this.store
|
||||
.query('pki/pki-role-engine', { backend: this.secretMountPath.currentPath })
|
||||
.then((roleModel) => {
|
||||
return { roleModel, parentModel: this.modelFor('roles') };
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.httpStatus === 404) {
|
||||
return [];
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
<SecretListHeader
|
||||
@model={{this.model}}
|
||||
@backendCrumb={{hash
|
||||
label=this.model.id
|
||||
text=this.model.id
|
||||
path="vault.cluster.secrets.backend.list-root"
|
||||
model=this.model.id
|
||||
}}
|
||||
@isEngine={{true}}
|
||||
/>
|
||||
{{outlet}}
|
||||
5
ui/lib/pki/addon/templates/roles/create.hbs
Normal file
5
ui/lib/pki/addon/templates/roles/create.hbs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<PkiRoleForm
|
||||
@model={{this.model}}
|
||||
@onCancel={{transition-to "vault.cluster.secrets.backend.pki.roles.index"}}
|
||||
@onSave={{transition-to "vault.cluster.secrets.backend.pki.roles.role.details" this.model.id}}
|
||||
/>
|
||||
|
|
@ -1,3 +1,13 @@
|
|||
<SecretListHeader
|
||||
@model={{this.model.parentModel}}
|
||||
@backendCrumb={{hash
|
||||
label=this.model.parentModel.id
|
||||
text=this.model.parentModel.id
|
||||
path="vault.cluster.secrets.backend.list-root"
|
||||
model=this.model.parentModel.id
|
||||
}}
|
||||
@isEngine={{true}}
|
||||
/>
|
||||
<Toolbar>
|
||||
<ToolbarActions>
|
||||
<ToolbarLink @type="add" @params={{array "roles.create"}}>
|
||||
|
|
@ -6,8 +16,8 @@
|
|||
</ToolbarActions>
|
||||
</Toolbar>
|
||||
|
||||
{{#if (gt this.model.length 0)}}
|
||||
{{#each this.model as |pkiRole|}}
|
||||
{{#if (gt this.model.roleModel.length 0)}}
|
||||
{{#each this.model.roleModel as |pkiRole|}}
|
||||
<LinkedBlock class="list-item-row" @params={{array "roles.role.details" pkiRole.id}} @linkPrefix={{this.mountPoint}}>
|
||||
<div class="level is-mobile">
|
||||
<div class="level-left">
|
||||
|
|
|
|||
Loading…
Reference in a new issue