Merge branch 'main' into target-credential-library-api-modeling
This commit is contained in:
20 files changed
+360
-7
No files matched your search
@@ -2,6 +2,13 @@ import GeneratedCredentialLibraryModel from '../generated/models/credential-libr
|
||||
import { attr } from '@ember-data/model';
|
||||
import { fragment } from 'ember-data-model-fragments/attributes';
|
||||
|
||||
/**
|
||||
* Enum options for credential library.
|
||||
*/
|
||||
export const options = {
|
||||
http_method: ['GET', 'POST'],
|
||||
};
|
||||
|
||||
export default class CredentialLibraryModel extends GeneratedCredentialLibraryModel {
|
||||
// =error attributes
|
||||
// These attributes exist solely to capture errors on nested fields.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Fragment from 'ember-data-model-fragments/fragment';
|
||||
import { attr } from '@ember-data/model';
|
||||
import { options } from './credential-library';
|
||||
|
||||
export default class FragmentCredentialLibraryAttributesModel extends Fragment {
|
||||
//= attributes (vault)
|
||||
@@ -7,6 +8,7 @@ export default class FragmentCredentialLibraryAttributesModel extends Fragment {
|
||||
@attr('string', {
|
||||
description:
|
||||
'The HTTP method the library uses when requesting credentials from Vault.',
|
||||
defaultValue: () => options.http_method[0],
|
||||
})
|
||||
http_method;
|
||||
|
||||
|
||||
@@ -497,7 +497,15 @@ export default function () {
|
||||
) => credentialLibraries.where({ credentialStoreId })
|
||||
);
|
||||
this.get('/credential-libraries/:id');
|
||||
this.post('/credential-libraries/:id');
|
||||
this.post(
|
||||
'/credential-libraries',
|
||||
function ({ credentialStores, credentialLibraries }) {
|
||||
const attrs = this.normalizedRequestAttrs();
|
||||
const credentialStore = credentialStores.find(attrs.credentialStoreId);
|
||||
attrs.scopeId = credentialStore.scope.id;
|
||||
return credentialLibraries.create(attrs);
|
||||
}
|
||||
);
|
||||
this.del('/credential-libraries/:id');
|
||||
this.patch('/credential-libraries/:id');
|
||||
|
||||
|
||||
@@ -277,9 +277,19 @@ credential-library:
|
||||
title: Credential Library
|
||||
title_plural: Credential Libraries
|
||||
description: A credential library is a resource that provides credentials.
|
||||
actions:
|
||||
create: New Credential Library
|
||||
messages:
|
||||
none:
|
||||
title: No Credential Libraries Available
|
||||
description: No Credential Libraries available in this credential store.
|
||||
actions:
|
||||
create: New Credential Library
|
||||
form:
|
||||
vault_path:
|
||||
label: Vault Path
|
||||
help: The path the library uses when requesting credentials from Vault.
|
||||
http_method:
|
||||
label: HTTP Method
|
||||
help: The HTTP method the library uses when requesting credentials from Vault.
|
||||
http_request_body:
|
||||
label: HTTP Method POST Request Body
|
||||
help: The body of the HTTP request the library sends to Vault when requesting credentials. Only allowed when HTTP method is set to POST.
|
||||
@@ -20,6 +20,7 @@
|
||||
input=(component 'rose/form/input' disabled=this.disabled)
|
||||
select=(component 'rose/form/select' disabled=this.disabled)
|
||||
textarea=(component 'rose/form/textarea' disabled=this.disabled)
|
||||
fieldset=(component 'rose/form/fieldset')
|
||||
actions=(component
|
||||
(if this.showEditToggleButton
|
||||
'rose/form/actions/edit-toggle'
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
margin-bottom: sizing.rems(m);
|
||||
|
||||
.rose-form-fieldset-title {
|
||||
@include type.type(s, bold);
|
||||
@include type.type(s, semibold);
|
||||
}
|
||||
|
||||
.rose-form-fieldset-description {
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
<Rose::Form
|
||||
@onSubmit={{@submit}}
|
||||
@cancel={{@cancel}}
|
||||
@disabled={{@model.isSaving}}
|
||||
@showEditToggle={{if @model.isNew false true}}
|
||||
as |form|
|
||||
>
|
||||
<form.input
|
||||
@name='name'
|
||||
@type='name'
|
||||
@value={{@model.name}}
|
||||
@label={{t 'form.name.label'}}
|
||||
@error={{@model.errors.name}}
|
||||
@helperText={{t 'form.name.help'}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.name}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.name as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
|
||||
<form.textarea
|
||||
@name='description'
|
||||
@type='description'
|
||||
@value={{@model.description}}
|
||||
@label={{t 'form.description.label'}}
|
||||
@error={{@model.errors.description}}
|
||||
@helperText={{t 'form.description.help'}}
|
||||
@disabled={{@model.isSaving}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.description}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.description as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.textarea>
|
||||
|
||||
<form.input
|
||||
@name='vault_path'
|
||||
@type='text'
|
||||
@value={{@model.attributes.path}}
|
||||
@label={{t 'resources.credential-library.form.vault_path.label'}}
|
||||
@error={{@model.errors.attributes_path}}
|
||||
@helperText={{t 'resources.credential-library.form.vault_path.help'}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.attributes_path}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.attributes_path as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
|
||||
<form.fieldset>
|
||||
<:title>{{t 'resources.credential-library.form.http_method.label'}}</:title>
|
||||
<:description>{{t
|
||||
'resources.credential-library.form.http_method.help'
|
||||
}}</:description>
|
||||
<:body>
|
||||
<form.radioGroup
|
||||
@name='http_method'
|
||||
@selectedValue={{@model.attributes.http_method}}
|
||||
@changed={{fn (mut @model.attributes.http_method)}}
|
||||
@inline={{true}}
|
||||
as |radioGroup|
|
||||
>
|
||||
{{#each this.httpMethodOptions as |httpMethod|}}
|
||||
<radioGroup.radio @label={{httpMethod}} @value={{httpMethod}} />
|
||||
{{/each}}
|
||||
</form.radioGroup>
|
||||
</:body>
|
||||
</form.fieldset>
|
||||
|
||||
{{#if this.isHttpRequestBodyAllowed}}
|
||||
<form.textarea
|
||||
@name='http_request_body'
|
||||
@type='text'
|
||||
@value={{@model.attributes.http_request_body}}
|
||||
@label={{t 'resources.credential-library.form.http_request_body.label'}}
|
||||
@error={{@model.errors.attributes_http_request_body}}
|
||||
@helperText={{t
|
||||
'resources.credential-library.form.http_request_body.help'
|
||||
}}
|
||||
@disabled={{this.cannotSaveHttpRequestBody}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.attributes_http_request_body}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.attributes_http_request_body as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.textarea>
|
||||
{{/if}}
|
||||
|
||||
<form.actions
|
||||
@disabled={{if @model.cannotSave @model.cannotSave}}
|
||||
@enableEditText={{t 'actions.edit-form'}}
|
||||
@submitText={{t 'actions.save'}}
|
||||
@cancelText={{t 'actions.cancel'}}
|
||||
/>
|
||||
</Rose::Form>
|
||||
@@ -0,0 +1,20 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { options } from 'api/models/credential-library';
|
||||
import { computed } from '@ember/object';
|
||||
|
||||
export default class FormCredentialLibraryIndexComponent extends Component {
|
||||
// =properties
|
||||
/**
|
||||
* @type {object}
|
||||
*/
|
||||
httpMethodOptions = options.http_method;
|
||||
|
||||
/**
|
||||
* Only allow HTTP request body field if http_method is set to POST.
|
||||
* @type {boolean}
|
||||
*/
|
||||
@computed('args.model.attributes.http_method')
|
||||
get isHttpRequestBodyAllowed() {
|
||||
return this.args.model.attributes.http_method?.match(/post/i);
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrariesController extends Controller {
|
||||
// =services
|
||||
|
||||
@service intl;
|
||||
|
||||
// =attributes
|
||||
|
||||
/**
|
||||
* Translated credential libraries breadcrumb
|
||||
* @type {string}
|
||||
*/
|
||||
get breadCrumb() {
|
||||
return this.intl.t('resources.credential-library.title_plural');
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrariesNewController extends Controller {
|
||||
// =services
|
||||
|
||||
@service intl;
|
||||
|
||||
// =attributes
|
||||
|
||||
/**
|
||||
* Translated new credential libraries breadcrumb
|
||||
* @type {string}
|
||||
*/
|
||||
get breadCrumb() {
|
||||
return this.intl.t('titles.new');
|
||||
}
|
||||
}
|
||||
@@ -91,7 +91,9 @@ Router.map(function () {
|
||||
'credential-store',
|
||||
{ path: ':credential_store_id' },
|
||||
function () {
|
||||
this.route('credential-libraries', function () {});
|
||||
this.route('credential-libraries', function () {
|
||||
this.route('new');
|
||||
});
|
||||
}
|
||||
);
|
||||
this.route('new');
|
||||
|
||||
+37
@@ -1,4 +1,7 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { action } from '@ember/object';
|
||||
import loading from 'ember-loading/decorator';
|
||||
import { notifySuccess, notifyError } from 'core/decorators/notify';
|
||||
|
||||
export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrariesRoute extends Route {
|
||||
// =methods
|
||||
@@ -13,4 +16,38 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrari
|
||||
);
|
||||
return this.store.query('credential-library', { credential_store_id });
|
||||
}
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Rollback changes on a credential library.
|
||||
* @param {CredentialLibraryModel} credentialLibrary
|
||||
*/
|
||||
@action
|
||||
cancel(credentialLibrary) {
|
||||
const { isNew } = credentialLibrary;
|
||||
credentialLibrary.rollbackAttributes();
|
||||
if (isNew)
|
||||
this.transitionTo(
|
||||
'scopes.scope.credential-stores.credential-store.credential-libraries'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle save of a credential library
|
||||
* @param {CredentialLibraryModel} credentialLibrary
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@notifyError(({ message }) => message)
|
||||
@notifySuccess(({ isNew }) =>
|
||||
isNew ? 'notifications.create-success' : 'notifications.save-success'
|
||||
)
|
||||
async save(credentialLibrary) {
|
||||
await credentialLibrary.save();
|
||||
await this.transitionTo(
|
||||
'scopes.scope.credential-stores.credential-store.credential-libraries'
|
||||
);
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import Route from '@ember/routing/route';
|
||||
|
||||
export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrariesNewRoute extends Route {
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Creates a new unsaved credential library in current credential store.
|
||||
* @return {CredentialLibraryModel}
|
||||
*/
|
||||
model() {
|
||||
const { id: credential_store_id } = this.modelFor(
|
||||
'scopes.scope.credential-stores.credential-store'
|
||||
);
|
||||
return this.store.createRecord('credential-library', {
|
||||
credential_store_id,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders new credential library specific templates for header, navigation,
|
||||
* and action page sections.
|
||||
* @override
|
||||
*/
|
||||
renderTemplate() {
|
||||
super.renderTemplate(...arguments);
|
||||
|
||||
this.render(
|
||||
'scopes/scope/credential-stores/credential-store/credential-libraries/new/-header',
|
||||
{
|
||||
into: 'scopes/scope/credential-stores/credential-store',
|
||||
outlet: 'header',
|
||||
}
|
||||
);
|
||||
|
||||
this.render('_empty', {
|
||||
into: 'scopes/scope/credential-stores/credential-store',
|
||||
outlet: 'navigation',
|
||||
});
|
||||
|
||||
this.render('_empty', {
|
||||
into: 'scopes/scope/credential-stores/credential-store',
|
||||
outlet: 'actions',
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,13 @@
|
||||
@dropdownRight={{true}}
|
||||
as |dropdown|
|
||||
>
|
||||
{{!-- <dropdown.link @route='scopes.scope.credential-stores.new' @model={{@model}}>
|
||||
<dropdown.link
|
||||
@route='scopes.scope.credential-stores.credential-store.credential-libraries.new'
|
||||
@model={{@model}}
|
||||
>
|
||||
{{t 'resources.credential-library.actions.create'}}
|
||||
</dropdown.link>
|
||||
<dropdown.separator /> --}}
|
||||
<dropdown.separator />
|
||||
<dropdown.button
|
||||
@style='danger'
|
||||
@disabled={{@model.canSave}}
|
||||
|
||||
+6
@@ -38,6 +38,12 @@
|
||||
<message.description>
|
||||
{{t 'resources.credential-library.messages.none.description'}}
|
||||
</message.description>
|
||||
<message.link
|
||||
@route='scopes.scope.credential-stores.credential-store.credential-libraries.new'
|
||||
>
|
||||
<Rose::Icon @name='plus-circle-outline' />
|
||||
{{t 'titles.new'}}
|
||||
</message.link>
|
||||
</Rose::Message>
|
||||
</Rose::Layout::Centered>
|
||||
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
{{page-title (t 'titles.new')}}
|
||||
|
||||
<Form::CredentialLibrary
|
||||
@model={{@model}}
|
||||
@submit={{route-action 'save' @model}}
|
||||
@cancel={{route-action 'cancel' @model}}
|
||||
/>
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
<h2>
|
||||
{{t 'titles.new'}}
|
||||
</h2>
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module(
|
||||
'Unit | Controller | scopes/scope/credential-stores/credential-store/credential-libraries',
|
||||
function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// TODO: Replace this with your real tests.
|
||||
test('it exists', function (assert) {
|
||||
let controller = this.owner.lookup(
|
||||
'controller:scopes/scope/credential-stores/credential-store/credential-libraries'
|
||||
);
|
||||
assert.ok(controller);
|
||||
});
|
||||
}
|
||||
);
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module(
|
||||
'Unit | Controller | scopes/scope/credential-stores/credential-store/credential-libraries/new',
|
||||
function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// TODO: Replace this with your real tests.
|
||||
test('it exists', function (assert) {
|
||||
let controller = this.owner.lookup(
|
||||
'controller:scopes/scope/credential-stores/credential-store/credential-libraries/new'
|
||||
);
|
||||
assert.ok(controller);
|
||||
});
|
||||
}
|
||||
);
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module(
|
||||
'Unit | Route | scopes/scope/credential-stores/credential-store/credential-libraries/new',
|
||||
function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function (assert) {
|
||||
let route = this.owner.lookup(
|
||||
'route:scopes/scope/credential-stores/credential-store/credential-libraries/new'
|
||||
);
|
||||
assert.ok(route);
|
||||
});
|
||||
}
|
||||
);
|
||||
Reference in new issue
Block a user