feat: 🎸 create form component for storage bucket (#118)

* feat: 🎸 create form component for storage bucket

✅ Closes: ICU-8235
This commit is contained in:
lisbet-alvarez authored and GitHub committed 2023-03-17 08:31:51 -07:00
1 parent 2c97809730
commit c81e3357d7
9 files changed
+427 -8

No files matched your search

+24 -2
View File
@@ -238,8 +238,30 @@ storage-bucket:
actions:
delete: Delete Storage Bucket
form:
scope: Scope
provider: Provider
scope:
label: Scope
help: You will be able to associate this storage bucket with any target.
provider:
label: Provider
help: Boundary currently only supports Amazon S3 storage.
bucket_name:
label: Bucket name
help: The name of this bucket in AWS
bucket_prefix:
label: Bucket prefix
help: The base path where session recordings will be stored.
region:
label: Region
help: The AWS region to use
access_key_id:
label: Access key ID
help: The AWS access key ID to use.
secret_access_key:
label: Secret access key
help: The secret access key to use.
worker_filter:
label: Worker filter
help: Filter for Boundary workers that have network access to your storage. Boolean expression.
project:
title: Project
title_plural: Projects
@@ -0,0 +1,249 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: MPL-2.0
}}
<Rose::Form
@onSubmit={{@submit}}
@cancel={{@cancel}}
@disabled={{@model.isSaving}}
@showEditToggle={{if @model.isNew false true}}
as |form|
>
<Hds::Form::TextInput::Field
@isRequired={{true}}
@value={{@model.name}}
@isInvalid={{@model.errors.name}}
@type='text'
name='name'
disabled={{form.disabled}}
{{on 'input' (set-from-event @model 'name')}}
as |F|
>
<F.Label>{{t 'form.name.label'}}</F.Label>
<F.HelperText>{{t 'form.name.help'}}</F.HelperText>
{{#if @model.errors.name}}
<F.Error as |E|>
{{#each @model.errors.name as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</F.Error>
{{/if}}
</Hds::Form::TextInput::Field>
<Hds::Form::Textarea::Field
@isOptional={{true}}
@value={{@model.description}}
@isInvalid={{@model.errors.description}}
name='description'
disabled={{form.disabled}}
as |F|
>
<F.Label>{{t 'form.description.label'}}</F.Label>
<F.HelperText>{{t 'form.description.help'}}</F.HelperText>
{{#if @model.errors.description}}
<F.Error as |E|>
{{#each @model.errors.description as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</F.Error>
{{/if}}
</Hds::Form::Textarea::Field>
{{#if (or @model.isNew form.isEditable)}}
<Hds::Form::Select::Field
@isRequired={{true}}
@width='100%'
disabled={{form.disabled}}
name='scope'
{{on 'change' this.updateScope}}
as |F|
>
<F.Label>{{t 'resources.storage-bucket.form.scope.label'}}</F.Label>
<F.HelperText>{{t
'resources.storage-bucket.form.scope.help'
}}</F.HelperText>
<F.Options>
{{#each @scopes as |scope|}}
<option value={{scope.model.id}}>{{scope.model.displayName}}</option>
{{/each}}
</F.Options>
</Hds::Form::Select::Field>
{{else if @model.scope}}
<InfoField
@value={{@model.scopeModel.displayName}}
@icon={{if @model.scopeModel.isGlobal 'globe' 'org'}}
disabled={{form.disabled}}
as |F|
>
<F.Label>{{t 'resources.storage-bucket.form.scope.label'}}</F.Label>
<F.HelperText>{{t
'resources.storage-bucket.form.scope.help'
}}</F.HelperText>
</InfoField>
{{/if}}
<InfoField
@value={{t 'resources.storage-bucket.titles.aws'}}
@icon='aws-color'
disabled={{form.disabled}}
as |F|
>
<F.Label>{{t 'titles.provider'}}</F.Label>
<F.HelperText>{{t
'resources.storage-bucket.form.provider.help'
}}</F.HelperText>
</InfoField>
<Hds::Form::TextInput::Field
@isRequired={{true}}
@value={{@model.bucket_name}}
@isInvalid={{@model.errors.bucket_name}}
@type='text'
name='bucket_name'
disabled={{form.disabled}}
{{on 'input' (set-from-event @model 'bucket_name')}}
as |F|
>
<F.Label>{{t 'resources.storage-bucket.form.bucket_name.label'}}</F.Label>
<F.HelperText>{{t
'resources.storage-bucket.form.bucket_name.help'
}}</F.HelperText>
{{#if @model.errors.bucket_name}}
<F.Error as |E|>
{{#each @model.errors.bucket_name as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</F.Error>
{{/if}}
</Hds::Form::TextInput::Field>
<Hds::Form::TextInput::Field
@isOptional={{true}}
@value={{@model.bucket_prefix}}
@isInvalid={{@model.errors.bucket_prefix}}
@type='text'
name='bucket_prefix'
disabled={{form.disabled}}
{{on 'input' (set-from-event @model 'bucket_prefix')}}
as |F|
>
<F.Label>{{t 'resources.storage-bucket.form.bucket_prefix.label'}}</F.Label>
<F.HelperText>{{t
'resources.storage-bucket.form.bucket_prefix.help'
}}</F.HelperText>
{{#if @model.errors.bucket_prefix}}
<F.Error as |E|>
{{#each @model.errors.bucket_prefix as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</F.Error>
{{/if}}
</Hds::Form::TextInput::Field>
<Hds::Form::TextInput::Field
@isRequired={{true}}
@value={{@model.region}}
@isInvalid={{@model.errors.region}}
@type='text'
name='region'
disabled={{form.disabled}}
{{on 'input' (set-from-event @model 'region')}}
as |F|
>
<F.Label>{{t 'resources.storage-bucket.form.region.label'}}</F.Label>
<F.HelperText>{{t
'resources.storage-bucket.form.region.help'
}}</F.HelperText>
{{#if @model.errors.region}}
<F.Error as |E|>
{{#each @model.errors.region as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</F.Error>
{{/if}}
</Hds::Form::TextInput::Field>
<Hds::Form::TextInput::Field
@isRequired={{true}}
@value={{@model.access_key_id}}
@isInvalid={{@model.errors.access_key_id}}
@type='text'
name='access_key_id'
disabled={{form.disabled}}
{{on 'input' (set-from-event @model 'access_key_id')}}
as |F|
>
<F.Label>{{t 'resources.storage-bucket.form.access_key_id.label'}}</F.Label>
<F.HelperText>{{t
'resources.storage-bucket.form.access_key_id.help'
}}</F.HelperText>
{{#if @model.errors.access_key_id}}
<F.Error as |E|>
{{#each @model.errors.access_key_id as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</F.Error>
{{/if}}
</Hds::Form::TextInput::Field>
<Hds::Form::TextInput::Field
@isRequired={{true}}
@value={{@model.secret_access_key}}
@isInvalid={{@model.errors.secret_access_key}}
@type='text'
name='secret_access_key'
disabled={{form.disabled}}
{{on 'input' (set-from-event @model 'secret_access_key')}}
as |F|
>
<F.Label>{{t
'resources.storage-bucket.form.secret_access_key.label'
}}</F.Label>
<F.HelperText>{{t
'resources.storage-bucket.form.secret_access_key.help'
}}</F.HelperText>
{{#if @model.errors.secret_access_key}}
<F.Error as |E|>
{{#each @model.errors.secret_access_key as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</F.Error>
{{/if}}
</Hds::Form::TextInput::Field>
<Hds::Form::TextInput::Field
@isRequired={{true}}
@value={{@model.worker_filter}}
@isInvalid={{@model.errors.worker_filter}}
@type='text'
name='worker_filter'
disabled={{form.disabled}}
{{on 'input' (set-from-event @model 'worker_filter')}}
as |F|
>
<F.Label>{{t 'resources.storage-bucket.form.worker_filter.label'}}</F.Label>
<F.HelperText>
{{t 'resources.storage-bucket.form.worker_filter.help'}}
{{! TODO: Link to the correct url for worker-filters }}
<Hds::Link::Inline @href={{doc-url 'worker-filters'}}>{{t
'actions.learn-more'
}}</Hds::Link::Inline>
</F.HelperText>
{{#if @model.errors.worker_filter}}
<F.Error as |E|>
{{#each @model.errors.worker_filter as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</F.Error>
{{/if}}
</Hds::Form::TextInput::Field>
{{#if (can 'save model' @model)}}
<form.actions
@enableEditText={{t 'actions.edit-form'}}
@submitText={{t 'actions.save'}}
@cancelText={{t 'actions.cancel'}}
/>
{{/if}}
</Rose::Form>
@@ -0,0 +1,20 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import Component from '@glimmer/component';
import { action } from '@ember/object';
export default class FormStorageBucketComponent extends Component {
// =actions
@action
updateScope(event) {
const selectedScopeId = event.target.value;
const selectedScopeModel = this.args.scopes.find(
(element) => element.model.id === selectedScopeId
).model;
this.args.model.scopeModel = selectedScopeModel;
}
}
@@ -0,0 +1,21 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
export default class ScopesScopeStorageBucketsNewController extends Controller {
// =services
@service intl;
/**
* Breadcrumb for new route
* @type {string}
*/
get breadCrumb() {
return this.intl.t('resources.storage-bucket.titles.new');
}
}
@@ -5,12 +5,14 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
import { loading } from 'ember-loading';
import { notifySuccess, notifyError } from 'core/decorators/notify';
export default class ScopesScopeStorageBucketsRoute extends Route {
// =services
@service store;
@service intl;
@service session;
@service can;
@service router;
@@ -31,10 +33,42 @@ export default class ScopesScopeStorageBucketsRoute extends Route {
async model() {
const scope = this.modelFor('scopes.scope');
const { id: scope_id } = scope;
if (this.can.can('list storage-bucket', scope, { collection: 'storage-buckets' })) {
if (
this.can.can('list storage-bucket', scope, {
collection: 'storage-buckets',
})
) {
return this.store.query('storage-bucket', { scope_id });
}
}
// =actions
/**
* Handle save
* @param {StorageBucketModel} storageBucket
*/
@action
@loading
@notifyError(({ message }) => message)
@notifySuccess('notifications.save-success')
async save(storageBucket) {
await storageBucket.save();
await this.router.transitionTo(
'scopes.scope.storage-buckets.storage-bucket',
storageBucket
);
this.refresh();
}
/**
* Rollback changes on storage buckets.
* @param {StorageBucketModel} storageBucket
*/
@action
cancel(storageBucket) {
const { isNew } = storageBucket;
storageBucket.rollbackAttributes();
if (isNew) this.router.transitionTo('scopes.scope.storage-buckets');
}
}
@@ -5,6 +5,10 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import {
TYPE_STORAGE_BUCKET_PLUGIN,
TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3,
} from 'api/models/storage-bucket';
export default class ScopesScopeStorageBucketsNewRoute extends Route {
// =services
@@ -19,8 +23,37 @@ export default class ScopesScopeStorageBucketsNewRoute extends Route {
*/
model() {
const scopeModel = this.modelFor('scopes.scope');
const record = this.store.createRecord('storage-bucket');
const record = this.store.createRecord('storage-bucket', {
type: TYPE_STORAGE_BUCKET_PLUGIN,
compositeType: TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3,
});
record.scopeModel = scopeModel;
return record;
}
async afterModel() {
let scopes;
const currentScope = this.modelFor('scopes.scope');
const orgScopes = (
await this.store.query('scope', { scope_id: 'global' })
).map((scope) => ({ model: scope }));
if (currentScope.id === 'global') {
scopes = [{ model: currentScope }, ...orgScopes];
} else {
scopes = [
{ model: this.store.peekRecord('scope', 'global') },
...orgScopes,
];
}
this.scopes = scopes;
}
/**
* Adds available global and org scopes to the context.
* @param {Controller} controller
*/
setupController(controller) {
super.setupController(...arguments);
controller.set('scopes', this.scopes);
}
}
@@ -37,8 +37,8 @@
@columns={{array
(hash label=(t 'form.name.label'))
(hash label=(t 'form.id.label'))
(hash label=(t 'resources.storage-bucket.form.provider'))
(hash label=(t 'resources.storage-bucket.form.scope'))
(hash label=(t 'resources.storage-bucket.form.provider.label'))
(hash label=(t 'resources.storage-bucket.form.scope.label'))
(hash label='')
}}
>
@@ -3,4 +3,28 @@
SPDX-License-Identifier: MPL-2.0
}}
{{page-title (t 'titles.new')}}
{{page-title (t 'titles.new')}}
<Rose::Layout::Page as |page|>
<page.breadcrumbs>
<BreadCrumbs />
</page.breadcrumbs>
<page.header>
{{! TODO: Add valid doc link and description}}
<h2>
{{t 'resources.storage-bucket.titles.new'}}
<DocLink @doc='storage-bucket' @iconSize='large' />
</h2>
</page.header>
<page.body>
<Form::StorageBucket
@model={{@model}}
@submit={{route-action 'save' @model}}
@cancel={{route-action 'cancel' @model}}
@scopes={{this.scopes}}
/>
</page.body>
</Rose::Layout::Page>
@@ -0,0 +1,16 @@
import { module, test } from 'qunit';
import { setupTest } from 'admin/tests/helpers';
module(
'Unit | Controller | scopes/scope/storage-buckets/new',
function (hooks) {
setupTest(hooks);
test('it exists', function (assert) {
let controller = this.owner.lookup(
'controller:scopes/scope/storage-buckets/new'
);
assert.ok(controller);
});
}
);