diff --git a/addons/api/addon/generated/models/storage-bucket.js b/addons/api/addon/generated/models/storage-bucket.js
index 7b855d801..3b3b8831c 100644
--- a/addons/api/addon/generated/models/storage-bucket.js
+++ b/addons/api/addon/generated/models/storage-bucket.js
@@ -73,6 +73,12 @@ export default class GeneratedStorageBucketModel extends BaseModel {
})
region;
+ @attr('string', {
+ isNestedAttribute: true,
+ description: 'The MinIO location server.',
+ })
+ endpoint_url;
+
//this field is not used in the UI, but this is kept here to replicate the API
@attr('string', {
readOnly: true,
diff --git a/addons/api/addon/models/storage-bucket.js b/addons/api/addon/models/storage-bucket.js
index 8a1a4f1f7..27733448c 100644
--- a/addons/api/addon/models/storage-bucket.js
+++ b/addons/api/addon/models/storage-bucket.js
@@ -9,8 +9,10 @@ export const TYPE_STORAGE_BUCKET_PLUGIN = 'plugin';
export const TYPES_STORAGE_BUCKET = Object.freeze([TYPE_STORAGE_BUCKET_PLUGIN]);
export const TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3 = 'aws';
+export const TYPE_STORAGE_BUCKET_PLUGIN_MINIO = 'minio';
export const TYPES_STORAGE_BUCKET_PLUGIN = Object.freeze([
TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3,
+ TYPE_STORAGE_BUCKET_PLUGIN_MINIO,
]);
export const TYPE_CREDENTIAL_STATIC = 'static';
@@ -75,6 +77,14 @@ export default class StorageBucketModel extends GeneratedStorageBucketModel {
return this.compositeType === TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3;
}
+ /**
+ * True if the storage bucket plugin type is MinIO
+ * @type {boolean}
+ */
+ get isMinIO() {
+ return this.compositeType === TYPE_STORAGE_BUCKET_PLUGIN_MINIO;
+ }
+
/**
* If the storage bucket is a plugin return `plugin.name`,
* otherwise return the storage bucket type.
diff --git a/addons/api/addon/serializers/storage-bucket.js b/addons/api/addon/serializers/storage-bucket.js
index 9f9da0aa6..5ffc9cbb9 100644
--- a/addons/api/addon/serializers/storage-bucket.js
+++ b/addons/api/addon/serializers/storage-bucket.js
@@ -4,7 +4,10 @@
*/
import ApplicationSerializer from './application';
-import { TYPE_CREDENTIAL_DYNAMIC } from '../models/storage-bucket';
+import {
+ TYPE_CREDENTIAL_DYNAMIC,
+ TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3,
+} from '../models/storage-bucket';
const fieldsByCredentialType = {
static: [
@@ -12,27 +15,55 @@ const fieldsByCredentialType = {
'secret_access_key',
'region',
'disable_credential_rotation',
+ 'endpoint_url',
],
dynamic: [
'role_arn',
'role_external_id',
'role_session_name',
'role_tags',
- 'disable_credential_rotation',
'region',
+ 'disable_credential_rotation',
+ 'endpoint_url',
],
};
export default class StorageBucketSerializer extends ApplicationSerializer {
+ /**
+ * Serializes storage-bucket. ATM just AWS needs specific serialization,
+ * for the other plugin types use default.
+ * @param {object} snapshot
+ * @returns
+ */
serialize(snapshot) {
- const serialized = super.serialize(...arguments);
const { credentialType } = snapshot.record;
+ const pluginType = snapshot.record.plugin?.name;
+ const serialized = super.serialize(...arguments);
+
+ // Common for all pluginTypes
if (!snapshot.record.isNew) {
delete serialized.bucket_name;
delete serialized.bucket_prefix;
}
- if (credentialType === TYPE_CREDENTIAL_DYNAMIC) {
- serialized.attributes.disable_credential_rotation = true;
+ if (pluginType === TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3) {
+ return this.serializeAws(serialized, credentialType);
+ }
+
+ return serialized;
+ }
+
+ /**
+ * Returns a storage bucket serialized object specific for aws
+ * @param {object} serialized
+ * @param {string} credentialType
+ * @returns
+ */
+ serializeAws(serialized, credentialType) {
+ if (serialized.attributes) {
+ if (credentialType === TYPE_CREDENTIAL_DYNAMIC) {
+ serialized.attributes.disable_credential_rotation = true;
+ }
+ delete serialized.attributes.endpoint_url;
}
return serialized;
}
@@ -45,7 +76,7 @@ export default class StorageBucketSerializer extends ApplicationSerializer {
if (options.isNestedAttribute && json.attributes) {
// The key must be included in the fieldsByType list above
if (!fieldsByCredentialType[credentialType].includes(key))
- //API requires these fields to be null
+ // API requires these fields to be null
json.attributes[key] = null;
}
diff --git a/addons/api/mirage/factories/storage-bucket.js b/addons/api/mirage/factories/storage-bucket.js
index 537d443fb..af635c36d 100644
--- a/addons/api/mirage/factories/storage-bucket.js
+++ b/addons/api/mirage/factories/storage-bucket.js
@@ -11,6 +11,7 @@ import {
TYPES_STORAGE_BUCKET as types,
TYPES_STORAGE_BUCKET_PLUGIN,
TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3,
+ TYPE_STORAGE_BUCKET_PLUGIN_MINIO,
} from 'api/models/storage-bucket';
// Represents known plugin types, except "foobar" which models the possibility
@@ -50,6 +51,10 @@ export default factory.extend({
)}`,
disable_credential_rotation: faker.datatype.boolean(),
};
+ case TYPE_STORAGE_BUCKET_PLUGIN_MINIO:
+ return {
+ endpoint_url: faker.internet.url(),
+ };
}
},
});
diff --git a/addons/api/tests/unit/serializers/storage-bucket-test.js b/addons/api/tests/unit/serializers/storage-bucket-test.js
index 9e81e289f..a42c6be1c 100644
--- a/addons/api/tests/unit/serializers/storage-bucket-test.js
+++ b/addons/api/tests/unit/serializers/storage-bucket-test.js
@@ -7,6 +7,7 @@ import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import {
TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3,
+ TYPE_STORAGE_BUCKET_PLUGIN_MINIO,
TYPE_STORAGE_BUCKET_PLUGIN,
TYPE_CREDENTIAL_STATIC,
TYPE_CREDENTIAL_DYNAMIC,
@@ -15,15 +16,6 @@ import {
module('Unit | Serializer | storage bucket', function (hooks) {
setupTest(hooks);
- test('it serializes records', function (assert) {
- let store = this.owner.lookup('service:store');
- let record = store.createRecord('storage-bucket', {});
-
- let serializedRecord = record.serialize();
-
- assert.ok(serializedRecord);
- });
-
test('it serializes a new static aws plugin as expected', async function (assert) {
const store = this.owner.lookup('service:store');
const record = store.createRecord('storage-bucket', {
@@ -70,6 +62,47 @@ module('Unit | Serializer | storage bucket', function (hooks) {
assert.deepEqual(record.serialize(), expectedResult);
});
+ test('it serializes a new minio (static by default) plugin as expected', async function (assert) {
+ const store = this.owner.lookup('service:store');
+ const record = store.createRecord('storage-bucket', {
+ compositeType: TYPE_STORAGE_BUCKET_PLUGIN_MINIO,
+ credentialType: TYPE_CREDENTIAL_STATIC,
+ name: 'minio',
+ description: 'this has a minio plugin',
+ bucket_name: 'bucketname',
+ bucket_prefix: 'bucketprefix',
+ worker_filter: 'workerfilter',
+ endpoint_url: 'http://hashicorp.com',
+ region: 'eu-west-1',
+ access_key_id: 'foobars',
+ secret_access_key: 'testing',
+ disable_credential_rotation: true,
+ });
+ const expectedResult = {
+ name: 'minio',
+ description: 'this has a minio plugin',
+ type: TYPE_STORAGE_BUCKET_PLUGIN,
+ bucket_name: 'bucketname',
+ bucket_prefix: 'bucketprefix',
+ worker_filter: 'workerfilter',
+ attributes: {
+ region: 'eu-west-1',
+ disable_credential_rotation: true,
+ endpoint_url: 'http://hashicorp.com',
+ role_arn: null,
+ role_external_id: null,
+ role_session_name: null,
+ role_tags: null,
+ secrets_hmac: null,
+ },
+ secrets: {
+ access_key_id: 'foobars',
+ secret_access_key: 'testing',
+ },
+ };
+ assert.deepEqual(record.serialize(), expectedResult);
+ });
+
test('it serializes a new aws plugin with dynamic credentials as expected', async function (assert) {
const store = this.owner.lookup('service:store');
const record = store.createRecord('storage-bucket', {
@@ -126,6 +159,7 @@ module('Unit | Serializer | storage bucket', function (hooks) {
bucket_name: 'bucketname',
bucket_prefix: 'bucketprefix',
worker_filter: 'workerfilter',
+ plugin: { name: 'aws' },
region: 'eu-west-1',
access_key_id: 'foobars',
secret_access_key: 'testing',
@@ -183,6 +217,7 @@ module('Unit | Serializer | storage bucket', function (hooks) {
bucket_name: 'bucketname',
bucket_prefix: 'bucketprefix',
worker_filter: 'workerfilter',
+ plugin: { name: 'aws' },
region: 'eu-west-1',
disable_credential_rotation: true,
access_key_id: 'test',
@@ -222,6 +257,61 @@ module('Unit | Serializer | storage bucket', function (hooks) {
assert.deepEqual(serializedRecord, expectedResult);
});
+ test('it serializes when updating a MinIO (static credential by default) storage bucket correctly', async function (assert) {
+ const store = this.owner.lookup('service:store');
+ const serializer = store.serializerFor('storage-bucket');
+ store.push({
+ data: {
+ id: '3',
+ type: 'storage-bucket',
+ attributes: {
+ type: TYPE_STORAGE_BUCKET_PLUGIN,
+ plugin: { name: 'minio' },
+ name: 'minio',
+ description: 'this has a minio plugin',
+ bucket_name: 'bucketname',
+ bucket_prefix: 'bucketprefix',
+ worker_filter: 'workerfilter',
+ endpoint_url: 'http://hashicorp.com',
+ region: 'eu-west-1',
+ disable_credential_rotation: true,
+ access_key_id: 'test',
+ secret_access_key: 'test',
+ version: 1,
+ },
+ },
+ });
+ const record = store.peekRecord('storage-bucket', '3');
+ record.description = 'This is new description';
+ record.name = 'This is the new name';
+ record.endpoint_url = 'http://developer.hashicorp.com';
+ const snapshot = record._createSnapshot();
+ const serializedRecord = serializer.serialize(snapshot);
+
+ const expectedResult = {
+ name: 'This is the new name',
+ description: 'This is new description',
+ type: TYPE_STORAGE_BUCKET_PLUGIN,
+ worker_filter: 'workerfilter',
+ attributes: {
+ region: 'eu-west-1',
+ disable_credential_rotation: true,
+ endpoint_url: 'http://developer.hashicorp.com',
+ role_arn: null,
+ role_external_id: null,
+ role_session_name: null,
+ role_tags: null,
+ secrets_hmac: null,
+ },
+ secrets: {
+ access_key_id: 'test',
+ secret_access_key: 'test',
+ },
+ version: 1,
+ };
+ assert.deepEqual(serializedRecord, expectedResult);
+ });
+
test('it normalizes correctly', function (assert) {
const store = this.owner.lookup('service:store');
const serializer = store.serializerFor('storage-bucket');
diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml
index a4eeee4dd..6caea4291 100644
--- a/addons/core/translations/resources/en-us.yaml
+++ b/addons/core/translations/resources/en-us.yaml
@@ -424,6 +424,7 @@ storage-bucket:
titles:
new: New Storage Bucket
amazon_s3: Amazon S3
+ minio: MinIO
actions:
delete: Delete Storage Bucket
types:
@@ -435,6 +436,9 @@ storage-bucket:
dynamic:
title: Dynamic
description: Specify an Assume Role used to generate credentials to use with this AWS bucket
+ plugin-types:
+ aws: Amazon S3
+ minio: MinIO
form:
scope:
label: Scope
@@ -444,33 +448,36 @@ storage-bucket:
provider:
label: Provider
help: Boundary currently only supports Amazon S3 storage.
+ endpoint_url:
+ label: Endpoint URL
+ help: Cannot be changed later.
bucket_name:
label: Bucket name
- help: The name of this bucket in AWS
+ help: The name of this storage bucket.
bucket_prefix:
label: Bucket prefix
help: The base path where session recordings will be stored.
role_arn:
label: Role ARN
- help: The ARN (Assume Role Name) used by this instance that the worker runs on
+ help: The ARN (Assume Role Name) used by this instance that the worker runs on.
role_external_id:
label: Role external ID
- help: Used to provide access to this account if you have this configured
+ help: Used to provide access to this account if you have this configured.
role_tags:
label: Role tags
- help: Attributes to pass to AWS
+ help: Attributes to pass to AWS.
role_session_name:
label: Role session name
- help: Unique identifier for the AWS session to help identify it in your logs
+ help: Unique identifier for the AWS session to help identify it in your logs.
region:
label: Region
- help: The AWS region to use
+ help: The region that this storage bucket is in.
access_key_id:
label: Access key ID
- help: The Access key ID generated by AWS for the IAM User to use with this storage bucket. We will not show this data after it is saved, but you can replace it.
+ help: The Access key ID generated by your storage provider for the IAM User to use with this storage bucket. We will not show this data after it is saved, but you can replace it.
secret_access_key:
label: Secret access key
- help: The secret access key generated by AWS for the IAM user to use with this storage bucket. We will not show this data after it is saved, but you can replace it.
+ help: The secret access key generated by your storage provider for the IAM user to use with this storage bucket. We will not show this data after it is saved, but you can replace it.
worker_filter:
label: Worker filter
help: Filter for Boundary workers that have network access to your storage. Boolean expression.
diff --git a/ui/admin/app/components/form/storage-bucket/aws/index.hbs b/ui/admin/app/components/form/storage-bucket/aws/index.hbs
new file mode 100644
index 000000000..e42c52961
--- /dev/null
+++ b/ui/admin/app/components/form/storage-bucket/aws/index.hbs
@@ -0,0 +1,491 @@
+
+ {{! name }}
+
+ {{t 'form.name.label'}}
+ {{t 'form.name.help'}}
+ {{#if @model.errors.name}}
+
+ {{#each @model.errors.name as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+
+
+ {{! description }}
+
+ {{t 'form.description.label'}}
+ {{t 'form.description.help'}}
+ {{#if @model.errors.description}}
+
+ {{#each @model.errors.description as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+
+
+ {{! scope }}
+ {{#if @model.isNew}}
+
+ {{t 'resources.storage-bucket.form.scope.label'}}
+
+ {{#if @model.scope}}
+ {{#if @model.scope.isGlobal}}
+ {{t 'resources.storage-bucket.form.scope.help_global'}}
+ {{else if @model.scope.isOrg}}
+ {{t 'resources.storage-bucket.form.scope.help_org'}}
+ {{/if}}
+ {{else}}
+ {{t 'resources.storage-bucket.form.scope.help'}}
+ {{/if}}
+
+
+
+ {{#each @scopes as |scope|}}
+
+ {{/each}}
+
+
+ {{else}}
+
+ {{t 'resources.storage-bucket.form.scope.label'}}
+
+ {{#if @model.scopeModel.isGlobal}}
+ {{t 'resources.storage-bucket.form.scope.help_global'}}
+ {{else}}
+ {{t 'resources.storage-bucket.form.scope.help_org'}}
+ {{/if}}
+
+
+ {{/if}}
+
+ {{! Provider / plugin.name }}
+
+ {{t 'titles.provider'}}
+ {{t 'descriptions.choose-a-provider'}}
+
+ {{#each @pluginTypes as |pluginType|}}
+
+ {{t
+ (concat 'resources.storage-bucket.plugin-types.' pluginType)
+ }}
+ {{#if (eq pluginType 'aws')}}
+
+ {{else}}
+
+ {{/if}}
+
+ {{/each}}
+
+
+ {{! bucket_name }}
+
+ {{t 'resources.storage-bucket.form.bucket_name.label'}}
+ {{t
+ 'resources.storage-bucket.form.bucket_name.help'
+ }}
+ {{#if @model.errors.bucket_name}}
+
+ {{#each @model.errors.bucket_name as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+
+
+ {{! bucket_prefix }}
+
+ {{t 'resources.storage-bucket.form.bucket_prefix.label'}}
+ {{t
+ 'resources.storage-bucket.form.bucket_prefix.help'
+ }}
+ {{#if @model.errors.bucket_prefix}}
+
+ {{#each @model.errors.bucket_prefix as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+
+
+ {{! region }}
+
+ {{t 'resources.storage-bucket.form.region.label'}}
+ {{t
+ 'resources.storage-bucket.form.region.help'
+ }}
+ {{#if @model.errors.region}}
+
+ {{#each @model.errors.region as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+
+
+ {{! credential_type }}
+
+ {{t 'resources.storage-bucket.types.credential'}}
+ {{#each this.credentials as |credentialType|}}
+
+ {{t
+ (concat 'resources.storage-bucket.types.' credentialType '.title')
+ }}
+ {{t
+ (concat
+ 'resources.storage-bucket.types.' credentialType '.description'
+ )
+ }}
+
+ {{/each}}
+
+
+ {{! static credentials fields }}
+ {{#unless this.showDynamicCredentials}}
+ {{! Creation }}
+ {{#if @model.isNew}}
+ {{! access_key }}
+
+ {{t
+ 'resources.storage-bucket.form.access_key_id.label'
+ }}
+ {{t
+ 'resources.storage-bucket.form.access_key_id.help'
+ }}
+ {{#if @model.errors.secrets}}
+
+ {{#each @model.errors.secrets as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+
+
+ {{! secret_access_key }}
+
+ {{t
+ 'resources.storage-bucket.form.secret_access_key.label'
+ }}
+ {{t
+ 'resources.storage-bucket.form.secret_access_key.help'
+ }}
+ {{#if @model.errors.secrets}}
+
+ {{#each @model.errors.secrets as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+
+ {{! Edit }}
+ {{else}}
+ {{! access_key }}
+
+ {{t
+ 'resources.storage-bucket.form.access_key_id.label'
+ }}
+ {{t
+ 'resources.storage-bucket.form.access_key_id.help'
+ }}
+
+
+ {{! secret_access_key }}
+
+ {{t
+ 'resources.storage-bucket.form.secret_access_key.label'
+ }}
+ {{t
+ 'resources.storage-bucket.form.secret_access_key.help'
+ }}
+
+ {{/if}}
+
+ {{/unless}}
+
+ {{! dynamic credentials }}
+ {{#if this.showDynamicCredentials}}
+ {{! role_arn }}
+
+ {{t 'resources.storage-bucket.form.role_arn.label'}}
+
+ {{t 'resources.storage-bucket.form.role_arn.help'}}
+
+ {{#if @model.errors.role_arn}}
+
+ {{#each @model.errors.role_arn as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+
+
+ {{! role_external_id }}
+
+
+ {{t 'resources.storage-bucket.form.role_external_id.label'}}
+
+
+ {{t 'resources.storage-bucket.form.role_external_id.help'}}
+
+ {{#if @model.errors.role_external_id}}
+
+ {{#each @model.errors.role_external_id as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+
+
+ {{! role_session_name }}
+
+
+ {{t 'resources.storage-bucket.form.role_session_name.label'}}
+
+
+ {{t 'resources.storage-bucket.form.role_session_name.help'}}
+
+ {{#if @model.errors.role_session_name}}
+
+ {{#each @model.errors.role_session_name as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+
+
+ {{! role_tags }}
+
+ <:fieldset as |F|>
+
+ {{t 'resources.storage-bucket.form.role_tags.label'}}
+
+
+ {{t 'resources.storage-bucket.form.role_tags.label'}}
+
+
+ {{#if @model.errors.role_tags}}
+
+ {{#each @model.errors.role_tags as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+
+ <:field as |F|>
+
+
+
+ {{/if}}
+
+ {{! worker_filter }}
+
+ {{t 'resources.storage-bucket.form.worker_filter.label'}}
+
+ {{t 'resources.storage-bucket.form.worker_filter.help'}}
+
+ {{t 'actions.learn-more'}}
+
+
+ {{#if @model.errors.worker_filter}}
+
+ {{#each @model.errors.worker_filter as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+
+
+ {{! JUST for static credentials }}
+ {{#unless this.showDynamicCredentials}}
+ {{! disable_credential_rotation }}
+
+ {{t
+ 'resources.storage-bucket.form.disable_credential_rotation.label'
+ }}
+
+ {{t 'resources.storage-bucket.form.disable_credential_rotation.help'}}
+
+ {{t 'actions.learn-more'}}
+
+
+
+ {{/unless}}
+
+ {{! Actions }}
+ {{#if (can 'save model' @model)}}
+
+ {{/if}}
+
\ No newline at end of file
diff --git a/ui/admin/app/components/form/storage-bucket/aws/index.js b/ui/admin/app/components/form/storage-bucket/aws/index.js
new file mode 100644
index 000000000..3f041a0f7
--- /dev/null
+++ b/ui/admin/app/components/form/storage-bucket/aws/index.js
@@ -0,0 +1,38 @@
+import Component from '@glimmer/component';
+import { action } from '@ember/object';
+import {
+ TYPES_CREDENTIALS,
+ TYPE_CREDENTIAL_DYNAMIC,
+} from 'api/models/storage-bucket';
+import { tracked } from '@glimmer/tracking';
+
+export default class FormStorageBucketAwsIndexComponent extends Component {
+ // =attributes
+ @tracked selectedCredentialType = this.args.model.credentialType;
+
+ @tracked showDynamicCredentials =
+ this.args.model.credentialType === TYPE_CREDENTIAL_DYNAMIC;
+ /**
+ * returns an array of available credential types
+ * @type {array}
+ */
+ get credentials() {
+ return TYPES_CREDENTIALS;
+ }
+
+ // =actions
+ /**
+ * Allows to update the credential type
+ * @param type {string}
+ */
+ @action
+ updateCredentialTypeSelection(type) {
+ this.selectedCredentialType = type;
+ if (type === TYPE_CREDENTIAL_DYNAMIC) {
+ this.showDynamicCredentials = true;
+ } else {
+ this.showDynamicCredentials = false;
+ }
+ this.args.changeCredentialType(type);
+ }
+}
diff --git a/ui/admin/app/components/form/storage-bucket/index.hbs b/ui/admin/app/components/form/storage-bucket/index.hbs
index cef65146d..7653901c8 100644
--- a/ui/admin/app/components/form/storage-bucket/index.hbs
+++ b/ui/admin/app/components/form/storage-bucket/index.hbs
@@ -2,455 +2,18 @@
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: MPL-2.0
}}
-
-
-
- {{t 'form.name.label'}}
- {{t 'form.name.help'}}
- {{#if @model.errors.name}}
-
- {{#each @model.errors.name as |error|}}
- {{error.message}}
- {{/each}}
-
- {{/if}}
-
-
-
- {{t 'form.description.label'}}
- {{t 'form.description.help'}}
- {{#if @model.errors.description}}
-
- {{#each @model.errors.description as |error|}}
- {{error.message}}
- {{/each}}
-
- {{/if}}
-
-
- {{#if @model.isNew}}
-
- {{t 'resources.storage-bucket.form.scope.label'}}
-
- {{this.scopeFieldDescription}}
-
-
-
- {{#each @scopes as |scope|}}
-
- {{/each}}
-
-
- {{else}}
-
- {{t 'resources.storage-bucket.form.scope.label'}}
-
- {{#if @model.scopeModel.isGlobal}}
- {{t 'resources.storage-bucket.form.scope.help_global'}}
- {{else}}
- {{t 'resources.storage-bucket.form.scope.help_org'}}
- {{/if}}
-
-
- {{/if}}
-
-
- {{t 'titles.provider'}}
- {{t
- 'resources.storage-bucket.form.provider.help'
- }}
-
-
-
- {{t 'resources.storage-bucket.form.bucket_name.label'}}
- {{t
- 'resources.storage-bucket.form.bucket_name.help'
- }}
- {{#if @model.errors.bucket_name}}
-
- {{#each @model.errors.bucket_name as |error|}}
- {{error.message}}
- {{/each}}
-
- {{/if}}
-
-
-
- {{t 'resources.storage-bucket.form.bucket_prefix.label'}}
- {{t
- 'resources.storage-bucket.form.bucket_prefix.help'
- }}
- {{#if @model.errors.bucket_prefix}}
-
- {{#each @model.errors.bucket_prefix as |error|}}
- {{error.message}}
- {{/each}}
-
- {{/if}}
-
-
-
- {{t 'resources.storage-bucket.form.region.label'}}
- {{t
- 'resources.storage-bucket.form.region.help'
- }}
- {{#if @model.errors.region}}
-
- {{#each @model.errors.region as |error|}}
- {{error.message}}
- {{/each}}
-
- {{/if}}
-
-
-
- {{t 'resources.storage-bucket.types.credential'}}
- {{#each this.credentials as |credentialType|}}
-
- {{t
- (concat 'resources.storage-bucket.types.' credentialType '.title')
- }}
- {{t
- (concat
- 'resources.storage-bucket.types.' credentialType '.description'
- )
- }}
-
-
- {{/each}}
-
-
- {{#unless this.showDynamicCredentials}}
-
- {{#if @model.isNew}}
-
- {{t
- 'resources.storage-bucket.form.access_key_id.label'
- }}
- {{t
- 'resources.storage-bucket.form.access_key_id.help'
- }}
- {{#if @model.errors.secrets}}
-
- {{#each @model.errors.secrets as |error|}}
- {{error.message}}
- {{/each}}
-
- {{/if}}
-
-
-
- {{t
- 'resources.storage-bucket.form.secret_access_key.label'
- }}
- {{t
- 'resources.storage-bucket.form.secret_access_key.help'
- }}
- {{#if @model.errors.secrets}}
-
- {{#each @model.errors.secrets as |error|}}
- {{error.message}}
- {{/each}}
-
- {{/if}}
-
- {{else}}
-
- {{t
- 'resources.storage-bucket.form.access_key_id.label'
- }}
- {{t
- 'resources.storage-bucket.form.access_key_id.help'
- }}
-
-
-
- {{t
- 'resources.storage-bucket.form.secret_access_key.label'
- }}
- {{t
- 'resources.storage-bucket.form.secret_access_key.help'
- }}
-
- {{/if}}
- {{/unless}}
-
- {{#if this.showDynamicCredentials}}
-
- {{t 'resources.storage-bucket.form.role_arn.label'}}
-
- {{t 'resources.storage-bucket.form.role_arn.help'}}
-
- {{#if @model.errors.role_arn}}
-
- {{#each @model.errors.role_arn as |error|}}
- {{error.message}}
- {{/each}}
-
- {{/if}}
-
-
-
-
- {{t 'resources.storage-bucket.form.role_external_id.label'}}
-
-
- {{t 'resources.storage-bucket.form.role_external_id.help'}}
-
- {{#if @model.errors.role_external_id}}
-
- {{#each @model.errors.role_external_id as |error|}}
- {{error.message}}
- {{/each}}
-
- {{/if}}
-
-
-
-
- {{t 'resources.storage-bucket.form.role_session_name.label'}}
-
-
- {{t 'resources.storage-bucket.form.role_session_name.help'}}
-
- {{#if @model.errors.role_session_name}}
-
- {{#each @model.errors.role_session_name as |error|}}
- {{error.message}}
- {{/each}}
-
- {{/if}}
-
-
-
- <:fieldset as |F|>
-
- {{t 'resources.storage-bucket.form.role_tags.label'}}
-
-
- {{t 'resources.storage-bucket.form.role_tags.label'}}
-
-
- {{#if @model.errors.role_tags}}
-
- {{#each @model.errors.role_tags as |error|}}
- {{error.message}}
- {{/each}}
-
- {{/if}}
-
- <:field as |F|>
-
-
-
- {{/if}}
-
-
- {{t 'resources.storage-bucket.form.worker_filter.label'}}
-
- {{t 'resources.storage-bucket.form.worker_filter.help'}}
-
- {{t 'actions.learn-more'}}
-
-
- {{#if @model.errors.worker_filter}}
-
- {{#each @model.errors.worker_filter as |error|}}
- {{error.message}}
- {{/each}}
-
- {{/if}}
-
-
- {{#unless this.showDynamicCredentials}}
-
- {{t
- 'resources.storage-bucket.form.disable_credential_rotation.label'
- }}
-
- {{t 'resources.storage-bucket.form.disable_credential_rotation.help'}}
-
- {{t 'actions.learn-more'}}
-
-
-
- {{/unless}}
- {{#if (can 'save model' @model)}}
-
- {{/if}}
-
\ No newline at end of file
+{{#if @model.type}}
+ {{component
+ (concat 'form/storage-bucket/' @model.compositeType)
+ model=@model
+ submit=@submit
+ cancel=@cancel
+ updateScope=this.updateScope
+ scopes=@scopes
+ pluginTypes=this.pluginTypes
+ changePluginType=@changePluginType
+ changeCredentialType=@changeCredentialType
+ rollbackSecretAttrs=this.rollbackSecretAttrs
+ toggleDisableCredentialRotation=this.toggleDisableCredentialRotation
+ }}
+{{/if}}
\ No newline at end of file
diff --git a/ui/admin/app/components/form/storage-bucket/index.js b/ui/admin/app/components/form/storage-bucket/index.js
index 768a8d6c5..421b903d4 100644
--- a/ui/admin/app/components/form/storage-bucket/index.js
+++ b/ui/admin/app/components/form/storage-bucket/index.js
@@ -5,64 +5,26 @@
import Component from '@glimmer/component';
import { action } from '@ember/object';
-import { tracked } from '@glimmer/tracking';
import { inject as service } from '@ember/service';
import { set } from '@ember/object';
import {
- TYPES_CREDENTIALS,
TYPE_CREDENTIAL_DYNAMIC,
+ TYPES_STORAGE_BUCKET_PLUGIN,
} from 'api/models/storage-bucket';
export default class FormStorageBucketComponent extends Component {
// =attributes
@service intl;
- @tracked scopeFieldDescription = this.intl.t(
- 'resources.storage-bucket.form.scope.help',
- );
-
- @tracked selectedCredentialType = this.args.model.credentialType;
-
- @tracked showDynamicCredentials =
- this.args.model.credentialType === TYPE_CREDENTIAL_DYNAMIC;
-
/**
- * returns an array of available credential types
- * @type {object}
+ * returns an array of available storage bucket plugin types
*/
- get credentials() {
- return TYPES_CREDENTIALS;
+ get pluginTypes() {
+ return TYPES_STORAGE_BUCKET_PLUGIN;
}
// =actions
- /**
- * Allows to update the credential type
- * @param type {string}
- */
- @action
- updateTypeSelection(type) {
- this.selectedCredentialType = type;
- if (type === TYPE_CREDENTIAL_DYNAMIC) {
- this.showDynamicCredentials = true;
- } else {
- this.showDynamicCredentials = false;
- }
- this.args.changeType(type);
- }
-
- updateScopeFieldDescription() {
- if (this.args.model.scopeModel.isGlobal) {
- this.scopeFieldDescription = this.intl.t(
- 'resources.storage-bucket.form.scope.help_global',
- );
- } else {
- this.scopeFieldDescription = this.intl.t(
- 'resources.storage-bucket.form.scope.help_org',
- );
- }
- }
-
@action
updateScope(event) {
const selectedScopeId = event.target.value;
@@ -70,7 +32,6 @@ export default class FormStorageBucketComponent extends Component {
(element) => element.model.id === selectedScopeId,
).model;
this.args.model.scopeModel = selectedScopeModel;
- this.updateScopeFieldDescription();
}
@action
diff --git a/ui/admin/app/components/form/storage-bucket/minio/index.hbs b/ui/admin/app/components/form/storage-bucket/minio/index.hbs
new file mode 100644
index 000000000..df8d383aa
--- /dev/null
+++ b/ui/admin/app/components/form/storage-bucket/minio/index.hbs
@@ -0,0 +1,353 @@
+
+ {{! name }}
+
+ {{t 'form.name.label'}}
+ {{t 'form.name.help'}}
+ {{#if @model.errors.name}}
+
+ {{#each @model.errors.name as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+
+
+ {{! description }}
+
+ {{t 'form.description.label'}}
+ {{t 'form.description.help'}}
+ {{#if @model.errors.description}}
+
+ {{#each @model.errors.description as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+
+
+ {{! scope }}
+ {{#if @model.isNew}}
+
+ {{t 'resources.storage-bucket.form.scope.label'}}
+
+ {{#if @model.scope}}
+ {{#if @model.scope.isGlobal}}
+ {{t 'resources.storage-bucket.form.scope.help_global'}}
+ {{else if @model.scope.isOrg}}
+ {{t 'resources.storage-bucket.form.scope.help_org'}}
+ {{/if}}
+ {{else}}
+ {{t 'resources.storage-bucket.form.scope.help'}}
+ {{/if}}
+
+
+
+ {{#each @scopes as |scope|}}
+
+ {{/each}}
+
+
+ {{else}}
+
+ {{t 'resources.storage-bucket.form.scope.label'}}
+
+ {{#if @model.scopeModel.isGlobal}}
+ {{t 'resources.storage-bucket.form.scope.help_global'}}
+ {{else}}
+ {{t 'resources.storage-bucket.form.scope.help_org'}}
+ {{/if}}
+
+
+ {{/if}}
+
+ {{! Provider / plugin.name }}
+
+ {{t 'titles.provider'}}
+ {{t 'descriptions.choose-a-provider'}}
+
+ {{#each @pluginTypes as |pluginType|}}
+
+ {{t
+ (concat 'resources.storage-bucket.plugin-types.' pluginType)
+ }}
+ {{#if (eq pluginType 'aws')}}
+
+ {{else}}
+
+ {{/if}}
+
+ {{/each}}
+
+
+ {{! endpoint_url }}
+
+ {{t 'resources.storage-bucket.form.endpoint_url.label'}}
+ {{t
+ 'resources.storage-bucket.form.endpoint_url.help'
+ }}
+ {{#if @model.errors.endpoint_url}}
+
+ {{#each @model.errors.endpoint_url as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+
+
+ {{! bucket_name }}
+
+ {{t 'resources.storage-bucket.form.bucket_name.label'}}
+ {{t
+ 'resources.storage-bucket.form.bucket_name.help'
+ }}
+ {{#if @model.errors.bucket_name}}
+
+ {{#each @model.errors.bucket_name as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+
+
+ {{! region }}
+
+ {{t 'resources.storage-bucket.form.region.label'}}
+ {{t
+ 'resources.storage-bucket.form.region.help'
+ }}
+ {{#if @model.errors.region}}
+
+ {{#each @model.errors.region as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+
+
+ {{! Creation }}
+ {{#if @model.isNew}}
+ {{! access_key }}
+
+
+ {{t 'resources.storage-bucket.form.access_key_id.label'}}
+
+ {{t
+ 'resources.storage-bucket.form.access_key_id.help'
+ }}
+ {{#if @model.errors.secrets}}
+
+ {{#each @model.errors.secrets as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+
+
+ {{! secret_access_key }}
+
+ {{t
+ 'resources.storage-bucket.form.secret_access_key.label'
+ }}
+ {{t
+ 'resources.storage-bucket.form.secret_access_key.help'
+ }}
+ {{#if @model.errors.secrets}}
+
+ {{#each @model.errors.secrets as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+
+ {{! Edit }}
+ {{else}}
+ {{! access_key }}
+
+ {{t
+ 'resources.storage-bucket.form.access_key_id.label'
+ }}
+ {{t
+ 'resources.storage-bucket.form.access_key_id.help'
+ }}
+
+
+ {{! secret_access_key }}
+
+ {{t
+ 'resources.storage-bucket.form.secret_access_key.label'
+ }}
+ {{t
+ 'resources.storage-bucket.form.secret_access_key.help'
+ }}
+
+ {{/if}}
+
+ {{! worker_filter }}
+
+ {{t 'resources.storage-bucket.form.worker_filter.label'}}
+
+ {{t 'resources.storage-bucket.form.worker_filter.help'}}
+
+ {{t 'actions.learn-more'}}
+
+
+ {{#if @model.errors.worker_filter}}
+
+ {{#each @model.errors.worker_filter as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+
+
+ {{! disable_credential_rotation }}
+
+ {{t
+ 'resources.storage-bucket.form.disable_credential_rotation.label'
+ }}
+
+ {{t 'resources.storage-bucket.form.disable_credential_rotation.help'}}
+
+ {{t 'actions.learn-more'}}
+
+
+
+
+ {{! Actions }}
+ {{#if (can 'save model' @model)}}
+
+ {{/if}}
+
\ No newline at end of file
diff --git a/ui/admin/app/controllers/scopes/scope/storage-buckets/index.js b/ui/admin/app/controllers/scopes/scope/storage-buckets/index.js
new file mode 100644
index 000000000..ac400bc77
--- /dev/null
+++ b/ui/admin/app/controllers/scopes/scope/storage-buckets/index.js
@@ -0,0 +1,78 @@
+import Controller from '@ember/controller';
+import { inject as service } from '@ember/service';
+import { action } from '@ember/object';
+import { loading } from 'ember-loading';
+import { confirm } from 'core/decorators/confirm';
+import { notifySuccess, notifyError } from 'core/decorators/notify';
+
+export default class ScopesScopeStorageBucketsIndexController extends Controller {
+ // =services
+ @service router;
+
+ // =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,
+ );
+ await this.router.refresh();
+ }
+
+ /**
+ * Deletes the storage bucket.
+ * @param {StorageBucketModel} storageBucket
+ */
+ @action
+ @loading
+ @confirm('resources.storage-bucket.questions.delete-storage-bucket.message', {
+ title: 'resources.storage-bucket.questions.delete-storage-bucket.title',
+ confirm: 'resources.storage-bucket.actions.delete',
+ })
+ @notifyError(({ message }) => message, { catch: true })
+ @notifySuccess('notifications.delete-success')
+ async delete(storageBucket) {
+ await storageBucket.destroyRecord();
+ }
+
+ /**
+ * 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');
+ }
+
+ /**
+ * Updates credentil type
+ * @param {object} storageBucket
+ * @param {string} credentialType
+ */
+ @action
+ changeCredentialType(storageBucket, credentialType) {
+ storageBucket.credentialType = credentialType;
+ }
+
+ /**
+ * Changes the plugin type.
+ * @param {*} pluginType
+ */
+ @action
+ async changePluginType(pluginType) {
+ await this.router.replaceWith({
+ queryParams: { compositeType: pluginType },
+ });
+ }
+}
diff --git a/ui/admin/app/controllers/scopes/scope/storage-buckets/new.js b/ui/admin/app/controllers/scopes/scope/storage-buckets/new.js
new file mode 100644
index 000000000..b3fd630bb
--- /dev/null
+++ b/ui/admin/app/controllers/scopes/scope/storage-buckets/new.js
@@ -0,0 +1,13 @@
+import Controller, { inject as controller } from '@ember/controller';
+import { inject as service } from '@ember/service';
+
+export default class ScopesScopeStorageBucketsNewController extends Controller {
+ @controller('scopes/scope/storage-buckets/index')
+ storageBuckets;
+
+ // =services
+ @service router;
+
+ // =attributes
+ queryParams = ['compositeType'];
+}
diff --git a/ui/admin/app/controllers/scopes/scope/storage-buckets/storage-bucket/index.js b/ui/admin/app/controllers/scopes/scope/storage-buckets/storage-bucket/index.js
new file mode 100644
index 000000000..e60121df4
--- /dev/null
+++ b/ui/admin/app/controllers/scopes/scope/storage-buckets/storage-bucket/index.js
@@ -0,0 +1,6 @@
+import Controller, { inject as controller } from '@ember/controller';
+
+export default class ScopesScopeStorageBucketsStorageBucketIndexController extends Controller {
+ @controller('scopes/scope/storage-buckets/index')
+ storageBuckets;
+}
diff --git a/ui/admin/app/controllers/scopes/scope/targets/target/enable-session-recording/create-storage-bucket.js b/ui/admin/app/controllers/scopes/scope/targets/target/enable-session-recording/create-storage-bucket.js
new file mode 100644
index 000000000..6fc4386bf
--- /dev/null
+++ b/ui/admin/app/controllers/scopes/scope/targets/target/enable-session-recording/create-storage-bucket.js
@@ -0,0 +1,6 @@
+import Controller, { inject as controller } from '@ember/controller';
+
+export default class ScopesScopeTargetsTargetEnableSessionRecordingCreateStorageBucketController extends Controller {
+ @controller('scopes/scope/storage-buckets/index')
+ storageBuckets;
+}
diff --git a/ui/admin/app/routes/scopes/scope/storage-buckets.js b/ui/admin/app/routes/scopes/scope/storage-buckets.js
index 098e00b12..5f8915521 100644
--- a/ui/admin/app/routes/scopes/scope/storage-buckets.js
+++ b/ui/admin/app/routes/scopes/scope/storage-buckets.js
@@ -5,10 +5,6 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
-import { action } from '@ember/object';
-import { loading } from 'ember-loading';
-import { confirm } from 'core/decorators/confirm';
-import { notifySuccess, notifyError } from 'core/decorators/notify';
export default class ScopesScopeStorageBucketsRoute extends Route {
// =services
@@ -18,8 +14,6 @@ export default class ScopesScopeStorageBucketsRoute extends Route {
@service can;
@service router;
- // =attributes
-
// =methods
/**
@@ -44,59 +38,4 @@ export default class ScopesScopeStorageBucketsRoute extends Route {
return this.store.query('storage-bucket', { scope_id, recursive: true });
}
}
-
- // =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');
- }
-
- /**
- * Deletes the storage bucket.
- * @param {StorageBucketModel} storageBucket
- */
- @action
- @loading
- @confirm('resources.storage-bucket.questions.delete-storage-bucket.message', {
- title: 'resources.storage-bucket.questions.delete-storage-bucket.title',
- confirm: 'resources.storage-bucket.actions.delete',
- })
- @notifyError(({ message }) => message, { catch: true })
- @notifySuccess('notifications.delete-success')
- async delete(storageBucket) {
- await storageBucket.destroyRecord();
- }
-
- /**
- * Updates type of credential
- * @param {string} credentialType
- */
- @action
- changeType(storageBucket, credentialType) {
- storageBucket.credentialType = credentialType;
- }
}
diff --git a/ui/admin/app/routes/scopes/scope/storage-buckets/new.js b/ui/admin/app/routes/scopes/scope/storage-buckets/new.js
index be11fca22..39edd73c1 100644
--- a/ui/admin/app/routes/scopes/scope/storage-buckets/new.js
+++ b/ui/admin/app/routes/scopes/scope/storage-buckets/new.js
@@ -17,6 +17,13 @@ export default class ScopesScopeStorageBucketsNewRoute extends Route {
@service can;
@service router;
+ // =attributes
+ queryParams = {
+ compositeType: {
+ refreshModel: true,
+ },
+ };
+
// =methods
/**
@@ -39,8 +46,7 @@ export default class ScopesScopeStorageBucketsNewRoute extends Route {
* creating another, but reuse name/description/other fields if available.
* @return {StorageBucket}
*/
- model() {
- const scopeModel = this.store.peekRecord('scope', 'global');
+ model({ compositeType = TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3 }) {
let name,
description,
scope,
@@ -48,6 +54,7 @@ export default class ScopesScopeStorageBucketsNewRoute extends Route {
bucket_prefix,
region,
worker_filter;
+
if (this.currentModel?.isNew) {
({
name,
@@ -60,9 +67,10 @@ export default class ScopesScopeStorageBucketsNewRoute extends Route {
} = this.currentModel);
this.currentModel.rollbackAttributes();
}
+
const record = this.store.createRecord('storage-bucket', {
type: TYPE_STORAGE_BUCKET_PLUGIN,
- compositeType: TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3,
+ compositeType,
name,
description,
scope,
@@ -71,7 +79,6 @@ export default class ScopesScopeStorageBucketsNewRoute extends Route {
region,
worker_filter,
});
- record.scopeModel = scopeModel;
return record;
}
diff --git a/ui/admin/app/templates/scopes/scope/storage-buckets/index.hbs b/ui/admin/app/templates/scopes/scope/storage-buckets/index.hbs
index 17a2f95ee..98e441da4 100644
--- a/ui/admin/app/templates/scopes/scope/storage-buckets/index.hbs
+++ b/ui/admin/app/templates/scopes/scope/storage-buckets/index.hbs
@@ -69,6 +69,12 @@
@text={{t 'resources.storage-bucket.titles.amazon_s3'}}
@icon='aws-color'
/>
+
+ {{else if B.data.isMinIO}}
+
{{else}}
{{B.data.plugin.name}}
{{/if}}
@@ -113,7 +119,7 @@
{{/if}}
diff --git a/ui/admin/app/templates/scopes/scope/storage-buckets/new.hbs b/ui/admin/app/templates/scopes/scope/storage-buckets/new.hbs
index f4977f758..70c48038e 100644
--- a/ui/admin/app/templates/scopes/scope/storage-buckets/new.hbs
+++ b/ui/admin/app/templates/scopes/scope/storage-buckets/new.hbs
@@ -24,10 +24,14 @@
diff --git a/ui/admin/app/templates/scopes/scope/storage-buckets/storage-bucket/index.hbs b/ui/admin/app/templates/scopes/scope/storage-buckets/storage-bucket/index.hbs
index 27f032ad6..5d9efe15a 100644
--- a/ui/admin/app/templates/scopes/scope/storage-buckets/storage-bucket/index.hbs
+++ b/ui/admin/app/templates/scopes/scope/storage-buckets/storage-bucket/index.hbs
@@ -27,10 +27,14 @@
\ No newline at end of file
diff --git a/ui/admin/app/templates/scopes/scope/targets/target/enable-session-recording/create-storage-bucket.hbs b/ui/admin/app/templates/scopes/scope/targets/target/enable-session-recording/create-storage-bucket.hbs
index f7255d46e..f77cdb0de 100644
--- a/ui/admin/app/templates/scopes/scope/targets/target/enable-session-recording/create-storage-bucket.hbs
+++ b/ui/admin/app/templates/scopes/scope/targets/target/enable-session-recording/create-storage-bucket.hbs
@@ -27,6 +27,11 @@
@submit={{route-action 'save' @model}}
@cancel={{route-action 'cancel' @model}}
@scopes={{this.scopes}}
+ @changeCredentialType={{fn
+ this.storageBuckets.changeCredentialType
+ @model
+ }}
+ @changePluginType={{this.storageBuckets.changePluginType}}
/>
diff --git a/ui/admin/tests/acceptance/storage-buckets/create-test.js b/ui/admin/tests/acceptance/storage-buckets/create-test.js
index 980727821..84fd273b7 100644
--- a/ui/admin/tests/acceptance/storage-buckets/create-test.js
+++ b/ui/admin/tests/acceptance/storage-buckets/create-test.js
@@ -32,6 +32,10 @@ module('Acceptance | storage-buckets | create', function (hooks) {
const ACCESS_KEY_SELECTOR = '[name="access_key_id"]';
const SECRET_KEY_SELECTOR = '[name="secret_access_key"]';
const SCOPE_SELECTOR = '[name=scope]';
+ const CREDENTIAL_TYPE_GROUP_SELECTOR = '[name=credential_type]';
+ const MINIO_PLUGIN_TYPE_SELECTOR = '[value=minio]';
+ const ENDPOINT_URL_SELECTOR = '[name=endpoint_url]';
+ const ENDPOINT_URL_TEXT = 'http://www.hashicorp.com';
const instances = {
scopes: {
@@ -63,13 +67,13 @@ module('Acceptance | storage-buckets | create', function (hooks) {
authenticateSession({});
});
- test('users can create a new storage bucket with global scope', async function (assert) {
+ test('users can create a new storage bucket aws plugin type with global scope', async function (assert) {
const storageBucketCount = getStorageBucketCount();
await visit(urls.storageBuckets);
await click(`[href="${urls.newStorageBucket}"]`);
await fillIn(NAME_FIELD_SELECTOR, NAME_FIELD_TEXT);
- await click('[value="global"]');
+ await select(SCOPE_SELECTOR, 'global');
assert.dom(BUCKET_NAME_FIELD_SELECTOR).isNotDisabled();
assert.dom(BUCKET_PREFIX_FIELD_SELECTOR).isNotDisabled();
@@ -86,7 +90,7 @@ module('Acceptance | storage-buckets | create', function (hooks) {
assert.strictEqual(getStorageBucketCount(), storageBucketCount + 1);
});
- test('users can create a new storage bucket with org scope', async function (assert) {
+ test('users can create a new storage bucket aws plugin type with org scope', async function (assert) {
const storageBucketCount = getStorageBucketCount();
await visit(urls.storageBuckets);
@@ -110,13 +114,17 @@ module('Acceptance | storage-buckets | create', function (hooks) {
assert.strictEqual(getStorageBucketCount(), storageBucketCount + 1);
});
- test('users can create a new storage bucket with dynamic credentials', async function (assert) {
+ test('users can create a new storage bucket aws plugin type with dynamic credentials', async function (assert) {
const storageBucketCount = getStorageBucketCount();
await visit(urls.storageBuckets);
await click(`[href="${urls.newStorageBucket}"]`);
await fillIn(NAME_FIELD_SELECTOR, NAME_FIELD_TEXT);
- assert.dom('.hds-form-radio-card').exists({ count: 2 });
+
+ // There are 2 credential types
+ assert
+ .dom(`${CREDENTIAL_TYPE_GROUP_SELECTOR} .hds-form-radio-card`)
+ .exists({ count: 2 });
await click(DYNAMIC_CREDENTIAL_SELECTOR);
await fillIn(ROLE_ARN_SELECTOR, 'test-arn-id');
@@ -132,13 +140,15 @@ module('Acceptance | storage-buckets | create', function (hooks) {
assert.strictEqual(getStorageBucketCount(), storageBucketCount + 1);
});
- test('users can create a new storage bucket with static credentials', async function (assert) {
+ test('users can create a new storage bucket aws plugin type with static credentials', async function (assert) {
const storageBucketCount = getStorageBucketCount();
await visit(urls.storageBuckets);
await click(`[href="${urls.newStorageBucket}"]`);
await fillIn(NAME_FIELD_SELECTOR, NAME_FIELD_TEXT);
- assert.dom('.hds-form-radio-card').exists({ count: 2 });
+ assert
+ .dom(`${CREDENTIAL_TYPE_GROUP_SELECTOR} .hds-form-radio-card`)
+ .exists({ count: 2 });
await click(STATIC_CREDENTIAL_SELECTOR);
await fillIn(ACCESS_KEY_SELECTOR, 'access_key_id');
@@ -156,6 +166,40 @@ module('Acceptance | storage-buckets | create', function (hooks) {
assert.strictEqual(getStorageBucketCount(), storageBucketCount + 1);
});
+ test('users can create a new storage bucket minio plugin type', async function (assert) {
+ const storageBucketCount = getStorageBucketCount();
+
+ // Navigate to new storage bucket
+ await visit(urls.storageBuckets);
+ await click(`[href="${urls.newStorageBucket}"]`);
+
+ // Fill the form
+ await fillIn(NAME_FIELD_SELECTOR, NAME_FIELD_TEXT);
+ await click(MINIO_PLUGIN_TYPE_SELECTOR);
+ await fillIn(ENDPOINT_URL_SELECTOR, ENDPOINT_URL_TEXT);
+ await fillIn(BUCKET_NAME_FIELD_SELECTOR, 'Test SB');
+ await fillIn(ACCESS_KEY_SELECTOR, 'access_key_id');
+ await fillIn(SECRET_KEY_SELECTOR, 'secret_access_key');
+
+ await click(SAVE_BTN_SELECTOR);
+
+ // Retrieve recently created SB
+ const storageBucket = await this.server.schema.storageBuckets.findBy({
+ name: NAME_FIELD_TEXT,
+ });
+
+ // Assertions
+ assert.strictEqual(storageBucket.name, NAME_FIELD_TEXT);
+ assert.strictEqual(
+ storageBucket.attributes.endpoint_url,
+ ENDPOINT_URL_TEXT,
+ );
+ assert.strictEqual(storageBucket.attributes.role_arn, null);
+ assert.strictEqual(storageBucket.plugin.name, 'minio');
+ assert.strictEqual(storageBucket.type, 'plugin');
+ assert.strictEqual(getStorageBucketCount(), storageBucketCount + 1);
+ });
+
test('user can cancel new storage bucket creation', async function (assert) {
const storageBucketCount = getStorageBucketCount();
await visit(urls.storageBuckets);
diff --git a/ui/admin/tests/unit/controllers/scopes/scope/storage-buckets/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/storage-buckets/index-test.js
new file mode 100644
index 000000000..5ec398adc
--- /dev/null
+++ b/ui/admin/tests/unit/controllers/scopes/scope/storage-buckets/index-test.js
@@ -0,0 +1,17 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'admin/tests/helpers';
+
+module(
+ 'Unit | Controller | scopes/scope/storage-buckets/index',
+ function (hooks) {
+ setupTest(hooks);
+
+ // TODO: Replace this with your real tests.
+ test('it exists', function (assert) {
+ let controller = this.owner.lookup(
+ 'controller:scopes/scope/storage-buckets/index',
+ );
+ assert.ok(controller);
+ });
+ },
+);
diff --git a/ui/admin/tests/unit/controllers/scopes/scope/storage-buckets/new-test.js b/ui/admin/tests/unit/controllers/scopes/scope/storage-buckets/new-test.js
new file mode 100644
index 000000000..5e13d12a6
--- /dev/null
+++ b/ui/admin/tests/unit/controllers/scopes/scope/storage-buckets/new-test.js
@@ -0,0 +1,17 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'admin/tests/helpers';
+
+module(
+ 'Unit | Controller | scopes/scope/storage-buckets/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/storage-buckets/new',
+ );
+ assert.ok(controller);
+ });
+ },
+);
diff --git a/ui/admin/tests/unit/controllers/scopes/scope/storage-buckets/storage-bucket/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/storage-buckets/storage-bucket/index-test.js
new file mode 100644
index 000000000..8115ac5a1
--- /dev/null
+++ b/ui/admin/tests/unit/controllers/scopes/scope/storage-buckets/storage-bucket/index-test.js
@@ -0,0 +1,17 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'admin/tests/helpers';
+
+module(
+ 'Unit | Controller | scopes/scope/storage-buckets/storage-bucket/index',
+ function (hooks) {
+ setupTest(hooks);
+
+ // TODO: Replace this with your real tests.
+ test('it exists', function (assert) {
+ let controller = this.owner.lookup(
+ 'controller:scopes/scope/storage-buckets/storage-bucket/index',
+ );
+ assert.ok(controller);
+ });
+ },
+);
diff --git a/ui/admin/tests/unit/controllers/scopes/scope/targets/target/enable-session-recording/create-storage-bucket-test.js b/ui/admin/tests/unit/controllers/scopes/scope/targets/target/enable-session-recording/create-storage-bucket-test.js
new file mode 100644
index 000000000..554ec6304
--- /dev/null
+++ b/ui/admin/tests/unit/controllers/scopes/scope/targets/target/enable-session-recording/create-storage-bucket-test.js
@@ -0,0 +1,17 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'admin/tests/helpers';
+
+module(
+ 'Unit | Controller | scopes/scope/targets/target/enable-session-recording/create-storage-bucket',
+ function (hooks) {
+ setupTest(hooks);
+
+ // TODO: Replace this with your real tests.
+ test('it exists', function (assert) {
+ let controller = this.owner.lookup(
+ 'controller:scopes/scope/targets/target/enable-session-recording/create-storage-bucket',
+ );
+ assert.ok(controller);
+ });
+ },
+);