{{/if}}
{{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}}
\ No newline at end of file
diff --git a/ui/lib/core/addon/components/form-field.js b/ui/lib/core/addon/components/form-field.js
index 81408e49c6..aa7b754dd7 100644
--- a/ui/lib/core/addon/components/form-field.js
+++ b/ui/lib/core/addon/components/form-field.js
@@ -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 || '';
diff --git a/ui/lib/core/addon/components/radio-select-ttl-or-string.hbs b/ui/lib/core/addon/components/radio-select-ttl-or-string.hbs
new file mode 100644
index 0000000000..60ff240bc4
--- /dev/null
+++ b/ui/lib/core/addon/components/radio-select-ttl-or-string.hbs
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ui/lib/core/addon/components/radio-select-ttl-or-string.js b/ui/lib/core/addon/components/radio-select-ttl-or-string.js
new file mode 100644
index 0000000000..b9d1e29702
--- /dev/null
+++ b/ui/lib/core/addon/components/radio-select-ttl-or-string.js
@@ -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|}}
+ *
+ * {{/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);
+ }
+}
diff --git a/ui/lib/core/app/components/form-field-groups-loop.js b/ui/lib/core/app/components/form-field-groups-loop.js
new file mode 100644
index 0000000000..a3935ebc5f
--- /dev/null
+++ b/ui/lib/core/app/components/form-field-groups-loop.js
@@ -0,0 +1 @@
+export { default } from 'core/components/form-field-groups-loop';
diff --git a/ui/lib/core/app/components/radio-select-ttl-or-string.js b/ui/lib/core/app/components/radio-select-ttl-or-string.js
new file mode 100644
index 0000000000..bc3f167f65
--- /dev/null
+++ b/ui/lib/core/app/components/radio-select-ttl-or-string.js
@@ -0,0 +1 @@
+export { default } from 'core/components/radio-select-ttl-or-string';
diff --git a/ui/lib/pki/addon/components/pki-role-form.hbs b/ui/lib/pki/addon/components/pki-role-form.hbs
new file mode 100644
index 0000000000..87f51ad1dd
--- /dev/null
+++ b/ui/lib/pki/addon/components/pki-role-form.hbs
@@ -0,0 +1,60 @@
+
+
+
+
+
+ /
+
+
+ {{@model.backend}}
+
+
+
+
+
+
+ {{#if @model.isNew}}
+ Create a PKI role
+ {{else}}
+ Edit a
+ {{@model.id}}
+ {{/if}}
+
+
+
+
+
\ No newline at end of file
diff --git a/ui/lib/pki/addon/components/pki-role-form.js b/ui/lib/pki/addon/components/pki-role-form.js
new file mode 100644
index 0000000000..c0bbf5b81c
--- /dev/null
+++ b/ui/lib/pki/addon/components/pki-role-form.js
@@ -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
+ *
+ * ```
+ * @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();
+ }
+}
diff --git a/ui/lib/pki/addon/routes/roles.js b/ui/lib/pki/addon/routes/roles.js
deleted file mode 100644
index b135ba450b..0000000000
--- a/ui/lib/pki/addon/routes/roles.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import Route from '@ember/routing/route';
-
-export default class PkiRolesRoute extends Route {}
diff --git a/ui/lib/pki/addon/routes/roles/create.js b/ui/lib/pki/addon/routes/roles/create.js
new file mode 100644
index 0000000000..5092f4e976
--- /dev/null
+++ b/ui/lib/pki/addon/routes/roles/create.js
@@ -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,
+ });
+ }
+}
diff --git a/ui/lib/pki/addon/routes/roles/index.js b/ui/lib/pki/addon/routes/roles/index.js
index 90732db9a6..35c5e0aa06 100644
--- a/ui/lib/pki/addon/routes/roles/index.js
+++ b/ui/lib/pki/addon/routes/roles/index.js
@@ -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 [];
diff --git a/ui/lib/pki/addon/templates/roles.hbs b/ui/lib/pki/addon/templates/roles.hbs
deleted file mode 100644
index c1651378d9..0000000000
--- a/ui/lib/pki/addon/templates/roles.hbs
+++ /dev/null
@@ -1,11 +0,0 @@
-
-{{outlet}}
\ No newline at end of file
diff --git a/ui/lib/pki/addon/templates/roles/create.hbs b/ui/lib/pki/addon/templates/roles/create.hbs
new file mode 100644
index 0000000000..6f6978786e
--- /dev/null
+++ b/ui/lib/pki/addon/templates/roles/create.hbs
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/ui/lib/pki/addon/templates/roles/index.hbs b/ui/lib/pki/addon/templates/roles/index.hbs
index ef641c5f09..a463a05189 100644
--- a/ui/lib/pki/addon/templates/roles/index.hbs
+++ b/ui/lib/pki/addon/templates/roles/index.hbs
@@ -1,3 +1,13 @@
+
@@ -6,8 +16,8 @@
-{{#if (gt this.model.length 0)}}
- {{#each this.model as |pkiRole|}}
+{{#if (gt this.model.roleModel.length 0)}}
+ {{#each this.model.roleModel as |pkiRole|}}