feat: 🎸 create form component for storage bucket (#118)
* feat: 🎸 create form component for storage bucket ✅ Closes: ICU-8235
This commit is contained in:
1 parent
2c97809730
commit
c81e3357d7
9 files changed
+427
-8
No files matched your search
@@ -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);
|
||||
});
|
||||
}
|
||||
);
|
||||
Reference in new issue
Block a user