]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: carbonize rgw user and bucket form 59429/head
authorNizamudeen A <nia@redhat.com>
Wed, 23 Oct 2024 15:12:21 +0000 (20:42 +0530)
committerNizamudeen A <nia@redhat.com>
Wed, 5 Mar 2025 05:37:40 +0000 (11:07 +0530)
Fixes: https://tracker.ceph.com/issues/70119
Signed-off-by: Nizamudeen A <nia@redhat.com>
28 files changed:
src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/buckets.po.ts
src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/users.po.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/bucket-tag-modal/bucket-tag-modal.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/bucket-tag-modal/bucket-tag-modal.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-tiering-form/rgw-bucket-tiering-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-rate-limit/rgw-rate-limit.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-form/rgw-storage-class-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-capability-modal/rgw-user-capability-modal.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-capability-modal/rgw-user-capability-modal.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-capability-modal/rgw-user-capability-modal.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-s3-key-modal/rgw-user-s3-key-modal.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-s3-key-modal/rgw-user-s3-key-modal.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-subuser-modal/rgw-user-subuser-modal.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-subuser-modal/rgw-user-subuser-modal.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-subuser-modal/rgw-user-subuser-modal.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-swift-key-modal/rgw-user-swift-key-modal.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-swift-key-modal/rgw-user-swift-key-modal.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/copy2clipboard-button/copy2clipboard-button.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/components/help-text/help-text.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/components/help-text/help-text.component.ts

index 9a44051f7b80b28faf2acce0a6e32ef65a4bca7d..86dbe5c8a40917489e793561f50ef0877942ae13 100644 (file)
@@ -37,10 +37,9 @@ export class BucketsPageHelper extends PageHelper {
     cy.get('#owner').should('have.class', 'ng-valid');
 
     if (isLocking) {
-      cy.get('#lock_enabled').click({ force: true });
+      cy.get('#lock_enabled_input').click({ force: true });
       // Select lock mode:
       this.selectLockMode('Compliance');
-      cy.get('#lock_mode').should('have.class', 'ng-valid');
       cy.get('#lock_retention_period_days').type('3');
     }
 
@@ -61,7 +60,7 @@ export class BucketsPageHelper extends PageHelper {
 
     // If object locking is enabled versioning shouldn't be visible
     if (isLocking) {
-      cy.get('input[id=versioning]').should('be.disabled');
+      cy.get('input[name=versioning]').should('be.disabled');
       cy.contains('button', 'Edit Bucket').click();
 
       this.getTableCell(this.columnIndex.name, name)
@@ -84,9 +83,9 @@ export class BucketsPageHelper extends PageHelper {
       return cy.get('@versioningValueCell').should('have.text', this.versioningStateEnabled);
     }
     // Enable versioning
-    cy.get('input[id=versioning]').should('not.be.checked');
-    cy.get('label[for=versioning]').click();
-    cy.get('input[id=versioning]').should('be.checked');
+    cy.get('input[name=versioning]').should('not.be.checked');
+    cy.get('label[for=versioning_input]').click();
+    cy.get('input[name=versioning]').should('be.checked');
     cy.contains('button', 'Edit Bucket').click();
 
     // Check if the owner is updated
@@ -110,8 +109,8 @@ export class BucketsPageHelper extends PageHelper {
     // Disable versioning:
     this.navigateEdit(name, false, true, null, true);
 
-    cy.get('label[for=versioning]').click();
-    cy.get('input[id=versioning]').should('not.be.checked');
+    cy.get('label[for=versioning_input]').click();
+    cy.get('input[name=versioning]').should('not.be.checked');
     cy.contains('button', 'Edit Bucket').wait(WAIT_TIMER).click();
 
     // Check versioning suspended:
@@ -136,10 +135,9 @@ export class BucketsPageHelper extends PageHelper {
       .and('have.class', 'ng-invalid');
 
     // Check that error message was printed under name input field
-    cy.get('#bid + .invalid-feedback').should(
-      'have.text',
-      'Bucket names must be 3 to 63 characters long.'
-    );
+    cy.get('cds-text-label[for=bid]')
+      .find('span.invalid-feedback')
+      .should('have.text', 'Bucket names must be 3 to 63 characters long.');
 
     // Test invalid owner input
     // select some valid option. The owner drop down error message will not appear unless a valid user was selected at
@@ -152,10 +150,12 @@ export class BucketsPageHelper extends PageHelper {
     cy.get('@nameInputField').click();
 
     // Check that owner drop down field was marked invalid in the css
-    cy.get('#owner').should('have.class', 'ng-invalid');
+    cy.get('cds-select[id=owner]').should('have.class', 'ng-invalid');
 
     // Check that error message was printed under owner drop down field
-    cy.get('#owner + .invalid-feedback').should('have.text', 'This field is required.');
+    cy.get('cds-select[id=owner]')
+      .find('.invalid-feedback')
+      .should('have.text', 'This field is required.');
 
     // Clicks the Create Bucket button but the page doesn't move.
     // Done by testing for the breadcrumb
@@ -169,7 +169,7 @@ export class BucketsPageHelper extends PageHelper {
   testInvalidEdit(name: string) {
     this.navigateEdit(name, false, true, null, true);
 
-    cy.get('input[id=versioning]').should('exist').and('not.be.checked');
+    cy.get('input[name=versioning]').should('exist').and('not.be.checked');
 
     // Chooses 'Select a user' rather than a valid owner on Edit Bucket page
     // and checks if it's an invalid input
@@ -180,10 +180,12 @@ export class BucketsPageHelper extends PageHelper {
     cy.contains('button', 'Edit Bucket').click();
 
     // Check that owner drop down field was marked invalid in the css
-    cy.get('#owner').should('have.class', 'ng-invalid');
+    cy.get('cds-select[id=owner]').should('have.class', 'ng-invalid');
 
     // Check that error message was printed under owner drop down field
-    cy.get('#owner + .invalid-feedback').should('have.text', 'This field is required.');
+    cy.get('cds-select[id=owner]')
+      .find('.invalid-feedback')
+      .should('have.text', 'This field is required.');
 
     this.expectBreadcrumbText('Edit');
   }
index bc37393092cfacf6036575ed78c5ba621a91b23d..59062088a24cd2285006831e77a07710642efb10 100644 (file)
@@ -13,7 +13,7 @@ export class UsersPageHelper extends PageHelper {
     // Enter in user_id
     cy.get('#user_id').type(user_id);
     // Show Tenant
-    cy.get('#show_tenant').click({ force: true });
+    cy.get('#show_tenant_input').click({ force: true });
     // Enter in tenant
     cy.get('#tenant').type(tenant);
     // Enter in full name
@@ -24,8 +24,8 @@ export class UsersPageHelper extends PageHelper {
 
     // Enter max buckets
     this.selectOption('max_buckets_mode', 'Custom');
-    cy.get('#max_buckets').should('exist').should('have.value', '1000');
-    cy.get('#max_buckets').click().clear().type(maxbuckets);
+    cy.get('input#max_buckets').should('exist').should('have.value', '1000');
+    cy.get('input#max_buckets').click().clear().type(maxbuckets);
 
     // Click the create button and wait for user to be made
     cy.contains('button', 'Create User').click();
@@ -37,14 +37,14 @@ export class UsersPageHelper extends PageHelper {
     this.navigateEdit(name, false, true, null, true);
 
     // Change the full name field
-    cy.get('#display_name').click().clear().type(new_fullname);
+    cy.get('input#display_name').click().clear({ force: true }).type(new_fullname, { force: true });
 
     // Change the email field
     cy.get('#email').click().clear().type(new_email);
 
     // Change the max buckets field
     this.selectOption('max_buckets_mode', 'Custom');
-    cy.get('#max_buckets').click().clear().type(new_maxbuckets);
+    cy.get('input#max_buckets').click().clear().type(new_maxbuckets);
 
     cy.contains('button', 'Edit User').click();
 
@@ -71,13 +71,17 @@ export class UsersPageHelper extends PageHelper {
       .should('have.class', 'ng-invalid')
       // Try to give user already taken name. Should make field invalid.
       .type(uname);
-    cy.get('#show_tenant').click({ force: true });
+    cy.get('#show_tenant_input').click({ force: true });
     cy.get('#tenant').type(tenant).should('have.class', 'ng-invalid');
-    cy.contains('#tenant + .invalid-feedback', 'The chosen user ID exists in this tenant.');
+    cy.get('cds-text-label[for=tenant]')
+      .find('.invalid-feedback')
+      .should('have.text', 'The chosen user ID exists in this tenant.');
 
     // check that username field is marked invalid if username has been cleared off
     cy.get('#user_id').clear().blur().should('have.class', 'ng-invalid');
-    cy.contains('#user_id + .invalid-feedback', 'This field is required.');
+    cy.get('cds-text-label[for=user_id]')
+      .find('.invalid-feedback')
+      .should('have.text', 'This field is required.');
 
     // Full name
     cy.get('#display_name')
@@ -88,16 +92,23 @@ export class UsersPageHelper extends PageHelper {
       .clear()
       .blur()
       .should('have.class', 'ng-invalid');
-    cy.contains('#display_name + .invalid-feedback', 'This field is required.');
+    cy.get('cds-text-label[for=display_name]')
+      .find('.invalid-feedback')
+      .should('have.text', 'This field is required.');
 
     // put invalid email to make field invalid
     cy.get('#email').type('a').blur().should('have.class', 'ng-invalid');
-    cy.contains('#email + .invalid-feedback', 'This is not a valid email address.');
+    cy.get('cds-text-label[for=email]')
+      .find('.invalid-feedback')
+      .should('have.text', 'This is not a valid email address.');
 
     // put negative max buckets to make field invalid
     this.expectSelectOption('max_buckets_mode', 'Custom');
-    cy.get('#max_buckets').clear().type('-5').blur().should('have.class', 'ng-invalid');
-    cy.contains('#max_buckets + .invalid-feedback', 'The entered value must be >= 1.');
+    cy.get('input#max_buckets').clear().type('-5').blur();
+    cy.get('#max_buckets').should('have.class', 'ng-invalid');
+    cy.get('cds-number[for=max_buckets]')
+      .find('.invalid-feedback')
+      .should('have.text', 'The entered value must be >= 1.');
 
     this.navigateTo();
     this.delete(tenant + '$' + uname, null, null, true, true);
@@ -119,19 +130,27 @@ export class UsersPageHelper extends PageHelper {
       .blur()
       .should('not.have.class', 'ng-pending')
       .should('have.class', 'ng-invalid');
-    cy.contains('#email + .invalid-feedback', 'This is not a valid email address.');
+
+    cy.get('cds-text-label[for=email]')
+      .find('.invalid-feedback')
+      .should('have.text', 'This is not a valid email address.');
 
     // empty the display name field making it invalid
-    cy.get('#display_name').clear().blur().should('have.class', 'ng-invalid');
-    cy.contains('#display_name + .invalid-feedback', 'This field is required.');
+    cy.get('#display_name').clear({ force: true }).blur().should('have.class', 'ng-invalid');
+    cy.get('cds-text-label[for=display_name]')
+      .find('.invalid-feedback')
+      .should('have.text', 'This field is required.');
 
     // put negative max buckets to make field invalid
     this.selectOption('max_buckets_mode', 'Disabled');
-    cy.get('#max_buckets').should('not.exist');
+    cy.get('input#max_buckets').should('not.exist');
     this.selectOption('max_buckets_mode', 'Custom');
-    cy.get('#max_buckets').should('exist').should('have.value', '50');
-    cy.get('#max_buckets').clear().type('-5').blur().should('have.class', 'ng-invalid');
-    cy.contains('#max_buckets + .invalid-feedback', 'The entered value must be >= 1.');
+    cy.get('input#max_buckets').should('exist').should('have.value', '50');
+    cy.get('input#max_buckets').clear().type('-5').blur();
+    cy.get('#max_buckets').should('have.class', 'ng-invalid');
+    cy.get('cds-number[for=max_buckets]')
+      .find('.invalid-feedback')
+      .should('have.text', 'The entered value must be >= 1.');
 
     this.navigateTo();
     this.delete(tenant + '$' + uname, null, null, true, true);
index 3e732e3556894c02bfd6b8ab83ef30ffbad797f6..fe4023f6dfeadf1b98d4357d65129b2154732dd7 100644 (file)
@@ -1,59 +1,75 @@
-<cd-modal [modalRef]="activeModal">
-  <span class="modal-title"
-        i18n>{{ getMode() }} Tag</span>
+<cds-modal size="md"
+           [open]="open"
+           [hasScrollingContent]="false"
+           (overlaySelected)="closeModal()">
 
-    <ng-container class="modal-content">
-      <form class="form"
-            #formDir="ngForm"
-            [formGroup]="form">
-        <div class="modal-body">
-          <!-- Key -->
-          <div class="form-group row">
-            <label class="cd-col-form-label required"
-                   for="key"
-                   i18n>Key</label>
-            <div class="cd-col-form-input">
-              <input type="text"
-                     class="form-control"
-                     formControlName="key"
-                     id="key">
-              <span class="invalid-feedback"
-                    *ngIf="form.showError('key', formDir, 'required')"
-                    i18n>This field is required.</span>
-              <span class="invalid-feedback"
-                    *ngIf="form.showError('key', formDir, 'unique')"
-                    i18n>This key must be unique.</span>
-              <span class="invalid-feedback"
-                    *ngIf="form.showError('key', formDir, 'maxLength')"
-                    i18n>Length of the key must be maximum of 128 characters</span>
-            </div>
-          </div>
+  <cds-modal-header (closeSelect)="closeModal()">
+    <h3 cdsModalHeaderHeading
+        i18n>{{ getMode() }} Tag</h3>
+  </cds-modal-header>
 
-          <!-- Value -->
-          <div class="form-group row">
-            <label class="cd-col-form-label required"
-                   for="value"
-                   i18n>Value</label>
-            <div class="cd-col-form-input">
-              <input id="value"
-                     class="form-control"
-                     type="text"
-                     formControlName="value">
-              <span *ngIf="form.showError('value', formDir, 'required')"
-                    class="invalid-feedback"
-                    i18n>This field is required.</span>
-              <span class="invalid-feedback"
-                    *ngIf="form.showError('value', formDir, 'maxLength')"
-                    i18n>Length of the value must be a maximum of 128 characters</span>
-            </div>
-          </div>
-        </div>
+  <form class="form"
+        #formDir="ngForm"
+        [formGroup]="form">
+    <div cdsModalContent>
+      <!-- Key -->
+      <div class="form-item">
+        <cds-text-label label="Key"
+                        for="key"
+                        cdRequiredField="Key"
+                        [invalid]="form.controls.key.invalid && form.controls.key.dirty"
+                        [invalidText]="keyError">Key
+          <input cdsText
+                 type="text"
+                 id="key"
+                 name="key"
+                 formControlName="key"
+                 [invalid]="form.controls.key.invalid && form.controls.key.dirty"
+                 [autofocus]="true"
+                 modal-primary-focus>
+        </cds-text-label>
+        <ng-template #keyError>
+          <span class="invalid-feedback"
+                *ngIf="form.showError('key', formDir, 'required')"
+                i18n>This field is required.</span>
+          <span class="invalid-feedback"
+                *ngIf="form.showError('key', formDir, 'unique')"
+                i18n>This key must be unique.</span>
+          <span class="invalid-feedback"
+                *ngIf="form.showError('key', formDir, 'maxLength')"
+                i18n>Length of the key must be maximum of 128 characters</span>
+        </ng-template>
+      </div>
 
-        <div class="modal-footer">
-          <cd-form-button-panel (submitActionEvent)="onSubmit()"
-                                [form]="form"
-                                [submitText]="getMode()"></cd-form-button-panel>
-        </div>
-      </form>
-    </ng-container>
-  </cd-modal>
+      <!-- Value -->
+      <div class="form-item">
+        <cds-text-label label="Value"
+                        for="value"
+                        cdRequiredField="Value"
+                        [invalid]="form.controls.value.invalid && form.controls.value.dirty"
+                        [invalidText]="valueError">Value
+          <input cdsText
+                 type="text"
+                 id="value"
+                 name="value"
+                 formControlName="value"
+                 [invalid]="form.controls.value.invalid && form.controls.value.dirty">
+        </cds-text-label>
+        <ng-template #valueError>
+          <span class="invalid-feedback"
+                *ngIf="form.showError('value', formDir, 'required')"
+                i18n>This field is required.</span>
+          <span class="invalid-feedback"
+                *ngIf="form.showError('value', formDir, 'maxLength')"
+                i18n>Length of the value must be a maximum of 128 characters</span>
+        </ng-template>
+      </div>
+
+    </div>
+
+    <cd-form-button-panel (submitActionEvent)="onSubmit()"
+                          [form]="form"
+                          [submitText]="getMode()"
+                          [modalForm]="true"></cd-form-button-panel>
+  </form>
+</cds-modal>
index 5135539e5d4709a3edecf813cd9c857e3b6ea9de..1f7a0418812dd6a25a1027b5ee7029429a164adb 100644 (file)
@@ -1,6 +1,6 @@
 import { Component, EventEmitter, Output } from '@angular/core';
 import { Validators } from '@angular/forms';
-import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+import { BaseModal } from 'carbon-components-angular';
 import _ from 'lodash';
 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
 import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
@@ -12,7 +12,7 @@ import { CdValidators } from '~/app/shared/forms/cd-validators';
   templateUrl: './bucket-tag-modal.component.html',
   styleUrls: ['./bucket-tag-modal.component.scss']
 })
-export class BucketTagModalComponent {
+export class BucketTagModalComponent extends BaseModal {
   @Output()
   submitAction = new EventEmitter();
 
@@ -21,11 +21,8 @@ export class BucketTagModalComponent {
   currentKeyTags: string[];
   storedKey: string;
 
-  constructor(
-    private formBuilder: CdFormBuilder,
-    public activeModal: NgbActiveModal,
-    public actionLabels: ActionLabelsI18n
-  ) {
+  constructor(private formBuilder: CdFormBuilder, public actionLabels: ActionLabelsI18n) {
+    super();
     this.createForm();
   }
 
@@ -62,7 +59,7 @@ export class BucketTagModalComponent {
 
   onSubmit() {
     this.submitAction.emit(this.form.value);
-    this.activeModal.close();
+    this.closeModal();
   }
 
   getMode() {
index df6150d028a6be70144fea78c5f615845818dd32..71097b4527c685c2d27970b5a32083e4b6e3554b 100644 (file)
-<div class="cd-col-form"
+<div cdsCol
+     [columnNumbers]="{md: 4}"
      *cdFormLoading="loading">
   <form name="bucketForm"
         #frm="ngForm"
         [formGroup]="bucketForm"
         novalidate>
-    <div class="card">
-      <div i18n="form title"
-           class="card-header">{{ action | titlecase }} {{ resource | upperFirst }}</div>
 
-      <div class="card-body">
-        <!-- Id -->
-        <div class="form-group row"
-             *ngIf="editing">
-          <label i18n
-                 class="cd-col-form-label"
-                 for="id">Id</label>
-          <div class="cd-col-form-input">
-            <input id="id"
-                   name="id"
-                   class="form-control"
-                   type="text"
-                   formControlName="id"
-                   readonly>
-          </div>
-        </div>
+    <div i18n="form title"
+         class="form-header">{{ action | titlecase }} {{ resource | upperFirst }}</div>
 
-        <!-- Name -->
-        <div class="form-group row">
-          <label class="cd-col-form-label"
-                 [ngClass]="{required: !editing}"
-                 for="bid"
-                 i18n>Name</label>
-          <div class="cd-col-form-input">
-            <input id="bid"
-                   name="bid"
-                   class="form-control"
-                   type="text"
-                   i18n-placeholder
-                   placeholder="Name..."
-                   formControlName="bid"
-                   [readonly]="editing"
-                   [autofocus]="!editing">
-            <span class="invalid-feedback"
-                  *ngIf="bucketForm.showError('bid', frm, 'required')"
-                  i18n>This field is required.</span>
-            <span class="invalid-feedback"
-                  *ngIf="bucketForm.showError('bid', frm, 'bucketNameInvalid')"
-                  i18n>Bucket names can only contain lowercase letters, numbers, periods and hyphens.</span>
-            <span class="invalid-feedback"
-                  *ngIf="bucketForm.showError('bid', frm, 'bucketNameNotAllowed')"
-                  i18n>The chosen name is already in use.</span>
-            <span class="invalid-feedback"
-                  *ngIf="bucketForm.showError('bid', frm, 'containsUpperCase')"
-                  i18n>Bucket names must not contain uppercase characters or underscores.</span>
-            <span class="invalid-feedback"
-                  *ngIf="bucketForm.showError('bid', frm, 'lowerCaseOrNumber')"
-                  i18n>Each label must start and end with a lowercase letter or a number.</span>
-            <span class="invalid-feedback"
-                  *ngIf="bucketForm.showError('bid', frm, 'ipAddress')"
-                  i18n>Bucket names cannot be formatted as IP address.</span>
-            <span class="invalid-feedback"
-                  *ngIf="bucketForm.showError('bid', frm, 'onlyLowerCaseAndNumbers')"
-                  i18n>Bucket labels cannot be empty and can only contain lowercase letters, numbers and hyphens.</span>
-            <span class="invalid-feedback"
-                  *ngIf="bucketForm.showError('bid', frm, 'shouldBeInRange')"
-                  i18n>Bucket names must be 3 to 63 characters long.</span>
-          </div>
-        </div>
+    <!-- Id -->
+    <div class="form-item"
+         *ngIf="editing">
+      <cds-text-label for="id"
+                      i18n>Id
+        <input cdsText
+               id="id"
+               name="id"
+               formControlName="id"
+               readonly>
+      </cds-text-label>
+    </div>
 
-        <!-- Owner -->
-        <div class="form-group row">
-          <label class="cd-col-form-label required"
-                 for="owner"
-                 i18n>Owner</label>
-          <div class="cd-col-form-input">
-            <select id="owner"
-                    name="owner"
-                    class="form-select"
-                    formControlName="owner"
-                    [autofocus]="editing">
-              <option i18n
-                      *ngIf="owners === null"
-                      [ngValue]="null">Loading...</option>
-              <option i18n
-                      *ngIf="owners !== null"
-                      [ngValue]="null">-- Select a user --</option>
-              <option *ngFor="let owner of owners"
-                      [value]="owner">{{ owner }}</option>
-            </select>
-            <span class="invalid-feedback"
-                  *ngIf="bucketForm.showError('owner', frm, 'required')"
-                  i18n>This field is
-              required.</span>
-            <cd-alert-panel type="info"
-                            *ngIf="bucketForm.get('owner').disabled"
-                            spacingClass="me-1 mt-1"
-                            i18n>The bucket is owned by an account. UI does not support changing the ownership of bucket owned by an account.
-            </cd-alert-panel>
-          </div>
-        </div>
+    <!-- Name -->
+    <div class="form-item">
+      <cds-text-label for="bid"
+                      cdRequiredField="Name"
+                      [invalid]="!bucketForm.controls.bid.valid && bucketForm.controls.bid.dirty"
+                      [invalidText]="nameError"
+                      i18n>Name
+        <input cdsText
+               placeholder="Name..."
+               i18n-placeholder
+               id="bid"
+               name="bid"
+               formControlName="bid"
+               [readonly]="editing"
+               [autofocus]="!editing"
+               [invalid]="!bucketForm.controls.bid.valid && bucketForm.controls.bid.dirty">
+      </cds-text-label>
+      <ng-template #nameError>
+        <span class="invalid-feedback"
+              *ngIf="bucketForm.showError('bid', frm, 'required')"
+              i18n>This field is required.</span>
+        <span class="invalid-feedback"
+              *ngIf="bucketForm.showError('bid', frm, 'bucketNameInvalid')"
+              i18n>Bucket names can only contain lowercase letters, numbers, periods and hyphens.</span>
+        <span class="invalid-feedback"
+              *ngIf="bucketForm.showError('bid', frm, 'bucketNameNotAllowed')"
+              i18n>The chosen name is already in use.</span>
+        <span class="invalid-feedback"
+              *ngIf="bucketForm.showError('bid', frm, 'containsUpperCase')"
+              i18n>Bucket names must not contain uppercase characters or underscores.</span>
+        <span class="invalid-feedback"
+              *ngIf="bucketForm.showError('bid', frm, 'lowerCaseOrNumber')"
+              i18n>Each label must start and end with a lowercase letter or a number.</span>
+        <span class="invalid-feedback"
+              *ngIf="bucketForm.showError('bid', frm, 'ipAddress')"
+              i18n>Bucket names cannot be formatted as IP address.</span>
+        <span class="invalid-feedback"
+              *ngIf="bucketForm.showError('bid', frm, 'onlyLowerCaseAndNumbers')"
+              i18n>Bucket labels cannot be empty and can only contain lowercase letters, numbers and hyphens.</span>
+        <span class="invalid-feedback"
+              *ngIf="bucketForm.showError('bid', frm, 'shouldBeInRange')"
+              i18n>Bucket names must be 3 to 63 characters long.</span>
+      </ng-template>
+    </div>
 
-        <!-- Versioning -->
-        <fieldset *ngIf="editing">
-          <legend class="cd-header"
-                  i18n>Versioning</legend>
+    <!-- Owner -->
+    <div class="form-item">
+      <cds-select label="Owner"
+                  for="owner"
+                  formControlName="owner"
+                  name="owner"
+                  id="owner"
+                  [invalidText]="ownerError"
+                  [invalid]="!bucketForm.controls.owner.valid && bucketForm.controls.owner.dirty"
+                  cdRequiredField="Owner"
+                  i18n>Owner
+        <option *ngIf="owners === null"
+                [ngValue]="null">Loading...</option>
+        <option *ngIf="owners !== null"
+                value="">-- Select a user --</option>
+        <option *ngFor="let owner of owners"
+                [value]="owner">{{ owner }}</option>
+      </cds-select>
+      <ng-template #ownerError>
+        <span class="invalid-feedback"
+              *ngIf="bucketForm.showError('owner', frm, 'required')"
+              i18n>This field is required.</span>
+      </ng-template>
+      <cd-alert-panel
+        type="info"
+        *ngIf="bucketForm.get('owner').disabled"
+        spacingClass="me-1 mt-1"
+        i18n>
+          The bucket is owned by an account. UI does not support changing
+          the ownership of bucket owned by an account.
+      </cd-alert-panel>
+    </div>
 
-          <div class="form-group row">
-            <div class="cd-col-form-offset">
-              <div class="custom-control custom-checkbox">
-                <input type="checkbox"
-                       class="custom-control-input"
-                       id="versioning"
-                       name="versioning"
-                       formControlName="versioning"
-                       (change)="setMfaDeleteValidators()">
-                <label class="custom-control-label spacing-03"
-                       for="versioning"
-                       i18n>Enabled</label>
-                <cd-helper>
-                  <span i18n>Enables versioning for the objects in the bucket.</span>
-                </cd-helper>
-              </div>
-            </div>
-          </div>
-        </fieldset>
+    <!-- Versioning -->
+    <fieldset *ngIf="editing">
+      <div class="form-item">
+        <legend class="cd-header"
+                i18n>Versioning</legend>
 
-        <!-- Multi-Factor Authentication -->
-        <fieldset *ngIf="editing">
-          <!-- MFA Delete -->
-          <legend class="cd-header"
-                  i18n>Multi-Factor Authentication</legend>
+        <cds-checkbox name="versioning"
+                      formControlName="versioning"
+                      id="versioning"
+                      (checkedChange)="setMfaDeleteValidators()"
+                      i18n>Enabled
+          <cd-help-text>
+            <span>Enables versioning for the objects in the bucket.</span>
+          </cd-help-text>
+        </cds-checkbox>
+      </div>
+    </fieldset>
 
-          <div class="form-group row">
-            <div class="cd-col-form-offset">
-              <div class="custom-control custom-checkbox">
-                <input type="checkbox"
-                       class="custom-control-input"
-                       id="mfa-delete"
-                       name="mfa-delete"
-                       formControlName="mfa-delete"
-                       (change)="setMfaDeleteValidators()">
-                <label class="custom-control-label"
-                       for="mfa-delete"
-                       i18n>Delete enabled</label>
-                <cd-helper>
-                  <span i18n>Enables MFA (multi-factor authentication) Delete, which requires additional authentication for changing the bucket versioning state.</span>
-                </cd-helper>
-              </div>
-            </div>
-          </div>
-          <div *ngIf="areMfaCredentialsRequired()"
-               class="form-group row">
-            <label i18n
-                   class="cd-col-form-label"
-                   for="mfa-token-serial">Token Serial Number</label>
-            <div class="cd-col-form-input">
-              <input type="text"
-                     id="mfa-token-serial"
-                     name="mfa-token-serial"
-                     formControlName="mfa-token-serial"
-                     class="form-control">
-              <span class="invalid-feedback"
-                    *ngIf="bucketForm.showError('mfa-token-serial', frm, 'required')"
-                    i18n>This field is required.</span>
-            </div>
-          </div>
-          <div *ngIf="areMfaCredentialsRequired()"
-               class="form-group row">
-            <label i18n
-                   class="cd-col-form-label"
-                   for="mfa-token-pin">Token PIN</label>
-            <div class="cd-col-form-input">
-              <input type="text"
-                     id="mfa-token-pin"
-                     name="mfa-token-pin"
-                     formControlName="mfa-token-pin"
-                     class="form-control">
-              <span class="invalid-feedback"
-                    *ngIf="bucketForm.showError('mfa-token-pin', frm, 'required')"
-                    i18n>This field is required.</span>
-            </div>
-          </div>
-        </fieldset>
+    <!-- Multi-Factor Authentication -->
+    <fieldset *ngIf="editing">
+      <!-- MFA Delete -->
+      <div class="form-item">
+        <legend class="cd-header"
+                i18n>Multi-Factor Authentication</legend>
 
-        <!-- Object Locking -->
-        <fieldset *ngIf="!editing || (editing && bucketForm.getValue('lock_enabled'))">
-          <legend class="cd-header"
-                  i18n>Object Locking
-            <cd-help-text>
-              Store objects using a write-once-read-many (WORM) model to prevent objects from being deleted or
-              overwritten for a fixed amount of time or indefinitely.
+        <cds-checkbox name="mfa-delete"
+                      formControlName="mfa-delete"
+                      id="mfa-delete"
+                      (checkedChange)="setMfaDeleteValidators()"
+                      helperText="Enables MFA (multi-factor authentication) Delete, which requires additional authentication for changing the bucket versioning state."
+                      i18n-helperText
+                      i18n>Delete enabled
+        </cds-checkbox>
+      </div>
+      <div *ngIf="areMfaCredentialsRequired()"
+           class="form-item">
+        <cds-text-label for="mfa-token-serial"
+                        cdRequiredField="MFA Serial Number"
+                        [invalid]="!bucketForm.controls['mfa-token-serial'].valid && bucketForm.controls['mfa-token-serial'].dirty"
+                        [invalidText]="mfaSerialError"
+                        i18n>Token Serial Number
+          <input cdsText
+                 id="mfa-token-serial"
+                 name="mfa-token-serial"
+                 formControlName="mfa-token-serial"
+                 [invalid]="!bucketForm.controls['mfa-token-serial'].valid && bucketForm.controls['mfa-token-serial'].dirty">
+        </cds-text-label>
+        <ng-template #mfaSerialError>
+          <span class="invalid-feedback"
+                *ngIf="bucketForm.showError('mfa-token-serial', frm, 'required')"
+                i18n>This field is required.</span>
+        </ng-template>
+      </div>
+      <div *ngIf="areMfaCredentialsRequired()"
+           class="form-item">
+        <cds-text-label for="mfa-token-pin"
+                        cdRequiredField="MFA Token PIN"
+                        [invalid]="!bucketForm.controls['mfa-token-pin'].valid && (bucketForm.controls['mfa-token-pin'].dirty)"
+                        [invalidText]="mfaPinError"
+                        i18n>Token PIN
+          <input cdsText
+                 id="mfa-token-pin"
+                 name="mfa-token-pin"
+                 formControlName="mfa-token-pin"
+                 [invalid]="!bucketForm.controls['mfa-token-pin'].valid && (bucketForm.controls['mfa-token-pin'].dirty)">
+        </cds-text-label>
+        <ng-template #mfaPinError>
+          <span class="invalid-feedback"
+                *ngIf="bucketForm.showError('mfa-token-pin', frm, 'required')"
+                i18n>This field is required.</span>
+        </ng-template>
+      </div>
+    </fieldset>
+
+    <!-- Object Locking -->
+    <fieldset *ngIf="!editing || (editing && bucketForm.getValue('lock_enabled'))">
+      <div class="form-item">
+        <legend class="cd-header"
+                i18n>
+          Object Locking
+          <cd-help-text>
+              Store objects using a write-once-read-many (WORM) model to prevent objects from being deleted or overwritten for a fixed amount of time or indefinitely.
               Object Locking works only in versioned buckets.
-            </cd-help-text>
-          </legend>
-          <!-- Object Locking enable -->
-          <div class="form-group row">
-            <label class="cd-col-form-label pt-0"
-                   for="lock_enabled"
-                   i18n>
-              Enable
-            </label>
-            <div class="cd-col-form-input">
-              <input class="form-check-input"
-                     id="lock_enabled"
-                     formControlName="lock_enabled"
-                     type="checkbox" />
-              <cd-help-text>
-                <span i18n>Enables locking for the objects in the bucket. Locking can only be enabled while creating a bucket.</span>
-              </cd-help-text>
-            </div>
-          </div>
-          <!-- Object Locking mode -->
-          <div *ngIf="bucketForm.getValue('lock_enabled')"
-               class="form-group row">
-            <label class="cd-col-form-label"
-                   for="lock_mode"
-                   i18n>Mode</label>
-            <div class="cd-col-form-input">
-              <select class="form-select"
-                      formControlName="lock_mode"
-                      name="lock_mode"
-                      id="lock_mode">
-                <option i18n
-                        value="COMPLIANCE">
-                  Compliance
-                </option>
-                <option i18n
-                        value="GOVERNANCE">
-                  Governance
-                </option>
-              </select>
-              <cd-help-text>
-                <span *ngIf="bucketForm.getValue('lock_mode') === 'COMPLIANCE'"
-                      i18n>In COMPLIANCE an object version cannot be overwritten or deleted for the duration of the period.
-                </span>
-                <span *ngIf="bucketForm.getValue('lock_mode') === 'GOVERNANCE'"
-                      i18n>In GOVERNANCE mode, users cannot overwrite or delete an object version or alter its lock settings unless they have special permissions.
-                </span>
-              </cd-help-text>
-            </div>
-          </div>
-          <!-- Retention period (days) -->
-          <div *ngIf="bucketForm.getValue('lock_enabled')"
-               class="form-group row">
-            <label class="cd-col-form-label"
-                   for="lock_retention_period_days">
-              <ng-container i18n>Days</ng-container>
-            </label>
-            <div class="cd-col-form-input">
-              <input class="form-control"
-                     type="number"
-                     id="lock_retention_period_days"
-                     formControlName="lock_retention_period_days"
-                     min="1">
-              <cd-help-text>
-                <span i18n>The number of days that you want to specify for the default retention period that will be
-                  applied to new objects placed in this bucket.</span>
-              </cd-help-text>
-              <span class="invalid-feedback"
-                    *ngIf="bucketForm.showError('lock_retention_period_days', frm, 'pattern')"
-                    i18n>The entered value must be a positive integer.</span>
-              <span class="invalid-feedback"
-                    *ngIf="bucketForm.showError('lock_retention_period_days', frm, 'lockDays')"
-                    i18n>Retention Days must be a positive integer.</span>
-            </div>
-          </div>
-          <!-- Alerts -->
-          <div class="form-group row">
-            <div class="cd-col-form-label"></div>
-            <div class="cd-col-form-input">
-              <cd-alert-panel type="info"
-                              *ngIf="bucketForm.getValue('lock_enabled')"
-                              class="me-1"
-                              i18n-title>
-                Bucket Versioning can't be disabled when Object Locking is enabled.
-              </cd-alert-panel>
-              <cd-alert-panel type="warning"
-                              *ngIf="bucketForm.getValue('lock_enabled')">
-                Enabling Object Locking will allow the configuration of GOVERNANCE or COMPLIANCE modes, which will help
-                ensure that an object version cannot be overwritten or deleted for the specified period.
-              </cd-alert-panel>
-            </div>
-          </div>
-        </fieldset>
+          </cd-help-text>
+        </legend>
+        <!-- Object Locking enable -->
+        <cds-checkbox name="lock_enabled"
+                      formControlName="lock_enabled"
+                      id="lock_enabled"
+                      [disabled]="editing"
+                      i18n>Enable
 
-          <!-- Encryption -->
-            <fieldset>
-              <legend class="cd-header"
-                      i18n>Encryption</legend>
-              <div class="form-group row">
-                <label class="cd-col-form-label pt-0"
-                       for="encryption_enabled"
-                       i18n>Enable
-                </label>
-                <div class="cd-col-form-input">
-                <input  class="form-check-input"
-                        id="encryption_enabled"
-                        name="encryption_enabled"
-                        formControlName="encryption_enabled"
-                        type="checkbox"
-                        [attr.disabled]="!kmsConfigured && !s3Configured ? true : null" />
-                  <cd-help-text aria-label="encryption helper">
-                  <span i18n>Enables encryption for the objects in the bucket.
-                        To enable encryption on a bucket you need to set the configuration values for SSE-S3 or SSE-KMS.
-                          To set the configuration values
-                  <a href="#/rgw/configuration"
-                     aria-label="click here">Click here</a></span>
-                  </cd-help-text>
-                </div>
-              </div>
-            <div *ngIf="bucketForm.getValue('encryption_enabled')">
-              <div class="form-group row">
-                <div class="cd-col-form-offset">
-                  <div class="custom-control custom-radio custom-control-inline ps-5">
-                  <input class="form-check-input"
-                         formControlName="encryption_type"
-                         id="sse_S3_enabled"
-                         type="radio"
-                         name="encryption_type"
-                         value="AES256"
-                         [attr.disabled]="!s3Configured ? true : null">
-                  <label class="form-control-label"
-                         [ngClass]="{'text-muted': !s3Configured}"
-                         for="sse_S3_enabled"
-                         i18n>SSE-S3</label>
-                  </div>
-                </div>
-              </div>
+          <cd-help-text>
+            <span>Enables locking for the objects in the bucket. Locking can only be enabled while creating a bucket.</span>
+          </cd-help-text>
+        </cds-checkbox>
+      </div>
 
-            <div class="form-group row">
-              <div class="cd-col-form-offset">
-                <div class="custom-control custom-radio custom-control-inline ps-5">
-                  <input class="form-check-input"
-                         formControlName="encryption_type"
-                         id="kms_enabled"
-                         name="encryption_type"
-                         value="aws:kms"
-                         [attr.disabled]="!kmsConfigured ? true : null"
-                         type="radio">
-                  <label class="form-control-label"
-                         [ngClass]="{'text-muted': !kmsConfigured}"
-                         for="kms_enabled"
-                         i18n>Connect to an external key management service</label>
-                </div>
-              </div>
-            </div>
+      <!-- Object Locking mode -->
+      <div *ngIf="bucketForm.getValue('lock_enabled')"
+           class="form-item">
+        <cds-select label="Mode"
+                    for="lock_mode"
+                    formControlName="lock_mode"
+                    name="lock_mode"
+                    [helperText]="lockModeHelper"
+                    id="lock_mode"
+                    i18n>
+          <option value="COMPLIANCE">Compliance</option>
+          <option value="GOVERNANCE">Governance</option>
+        </cds-select>
+        <ng-template #lockModeHelper>
+          <span *ngIf="bucketForm.getValue('lock_mode') === 'COMPLIANCE'"
+                i18n>
+            In COMPLIANCE an object version cannot be overwritten or deleted for the duration of the period.
+          </span>
+          <span *ngIf="bucketForm.getValue('lock_mode') === 'GOVERNANCE'"
+                i18n>
+            In GOVERNANCE mode, users cannot overwrite or delete an object version or alter its lock settings unless they have special permissions.
+          </span>
+        </ng-template>
+      </div>
+      <!-- Retention period (days) -->
+      <div *ngIf="bucketForm.getValue('lock_enabled')"
+           class="form-item">
+        <cds-number name="lock_retention_period_days"
+                    formControlName="lock_retention_period_days"
+                    id="lock_retention_period_days"
+                    min="1"
+                    label="Retention period (days)"
+                    helperText="The number of days that you want to specify for the default retention period that will be applied to new objects placed in this bucket."
+                    [invalid]="bucketForm.controls.lock_retention_period_days.invalid && (bucketForm.controls.lock_retention_period_days.dirty)"
+                    [invalidText]="retentionPeriodError"
+                    i18n-helperText
+                    i18n-label
+                    i18n></cds-number>
+        <ng-template #retentionPeriodError>
+          <span class="invalid-feedback"
+                *ngIf="bucketForm.showError('lock_retention_period_days', frm, 'pattern')"
+                i18n>The entered value must be a positive integer.</span>
+          <span class="invalid-feedback"
+                *ngIf="bucketForm.showError('lock_retention_period_days', frm, 'lockDays')"
+                i18n>Retention Days must be a positive integer.</span>
+        </ng-template>
+      </div>
+      <!-- Alerts -->
+      <cd-alert-panel
+        type="info"
+        *ngIf="bucketForm.getValue('lock_enabled')"
+        class="me-1"
+        i18n-title>
+          Bucket Versioning can't be disabled when Object Locking is enabled.
+      </cd-alert-panel>
+      <cd-alert-panel
+        type="warning"
+        *ngIf="bucketForm.getValue('lock_enabled')">
+          Enabling Object Locking will allow the configuration of GOVERNANCE or COMPLIANCE modes, which will help ensure that an object version cannot be overwritten or deleted for the specified period.
+      </cd-alert-panel>
+    </fieldset>
 
-            <div *ngIf="bucketForm.getValue('encryption_type') === 'aws:kms'">
-              <div class="form-group row">
-                <label class="cd-col-form-label required"
-                       for="kms_provider"
-                       i18n>KMS Provider</label>
-                <div class="cd-col-form-input">
-                  <select id="kms_provider"
-                          name="kms_provider"
-                          class="form-select"
-                          formControlName="kms_provider"
-                          [autofocus]="editing">
-                    <option i18n
-                            *ngIf="kmsProviders !== null"
-                            [ngValue]="null">-- Select a provider --</option>
-                    <option *ngFor="let provider of kmsProviders"
-                            [value]="provider">{{ provider }}</option>
-                  </select>
-                  <span class="invalid-feedback"
-                        *ngIf="bucketForm.showError('kms_provider', frm, 'required')"
-                        i18n>This field is required.</span>
-                </div>
-              </div>
-            </div>
+    <!-- Encryption -->
+    <fieldset>
+      <div class="form-item">
+        <legend class="cd-header"
+                i18n>Encryption</legend>
+        <cds-checkbox name="encryption_enabled"
+                      formControlName="encryption_enabled"
+                      id="encryption_enabled"
+                      [disabled]="!kmsConfigured && !s3Configured"
+                      i18n>Enable
+          <cd-help-text aria-label="encryption helper">
+            <span>Enables encryption for the objects in the bucket.
+                To enable encryption on a bucket you need to set the configuration values for SSE-S3 or SSE-KMS.
+                To set the configuration values <a href="#/rgw/configuration"
+                                                   i18n-aria-label="click here">Click here</a></span>
+          </cd-help-text>
+        </cds-checkbox>
+      </div>
 
-            <div *ngIf="bucketForm.getValue('encryption_type') === 'aws:kms'">
-              <div class="form-group row">
-                <label class="cd-col-form-label required"
-                       for="keyId"
-                       i18n>Key Id
-                </label>
-                <div class="cd-col-form-input">
-                  <input id="keyId"
-                         name="keyId"
-                         class="form-control"
-                         type="text"
-                         formControlName="keyId">
-                  <span class="invalid-feedback"
-                        *ngIf="bucketForm.showError('keyId', frm, 'required')"
-                        i18n>This field is required.</span>
-                </div>
-              </div>
-            </div>
-          </div>
-        </fieldset>
+      <div *ngIf="bucketForm.getValue('encryption_enabled')">
+        <div class="form-item">
+          <cds-radio-group formControlName="encryption_type">
+          <cds-radio value="AES256"
+                     [disabled]="!s3Configured">SSE-S3</cds-radio>
+          <cds-radio value="aws:kms"
+                     [disabled]="!kmsConfigured">Connect to an external key management service</cds-radio>
+          </cds-radio-group>
+        </div>
 
-        <!-- Replication -->
-        <fieldset>
-          <legend class="cd-header"
-                  i18n>Replication</legend>
-          <div class="form-group row">
-            <label class="cd-col-form-label pt-0"
-                   for="replication"
-                   i18n>
-              Enable
-            </label>
-            <div class="cd-col-form-input"
-                 *ngIf="{status: multisiteStatus$, isDefaultZg: isDefaultZoneGroup$ | async} as multisiteStatus; else loadingTpl">
-              <input type="checkbox"
-                     class="form-check-input"
-                     id="replication"
-                     name="replication"
-                     formControlName="replication"
-                     [attr.disabled]="!multisiteStatus.isDefaultZg && !multisiteStatus.status.available ? true : null">
-              <cd-help-text>
-                <span i18n>Enables replication for the objects in the bucket.</span>
-              </cd-help-text>
-              <div class="mt-1"
-                   *ngIf="!editing">
-                <cd-alert-panel type="info"
-                                class="me-1"
-                                id="replication-info"
-                                i18n>
-                  A bi-directional sync policy group will be created by the dashboard along with flows and pipes.
-                  The pipe id will then be used for applying the replication policy to the bucket.
-                </cd-alert-panel>
-              </div>
-            </div>
+        <div *ngIf="bucketForm.getValue('encryption_type') === 'aws:kms'">
+          <div class="form-item">
+            <cds-select label="KMS Provider"
+                        for="kms_provider"
+                        formControlName="kms_provider"
+                        name="kms_provider"
+                        id="kms_provider"
+                        [invalidText]="kmsProviderError"
+                        [invalid]="!bucketForm.controls.kms_provider.valid && (bucketForm.controls.kms_provider.dirty)"
+                        cdRequiredField="KMS Provider"
+                        i18n>KMS Provider
+              <option *ngIf="kmsProviders === null"
+                      [ngValue]="null">Loading...</option>
+              <option *ngIf="kmsProviders !== null"
+                      [ngValue]="null">-- Select a provider --</option>
+              <option *ngFor="let provider of kmsProviders"
+                      [value]="provider">{{ provider }}</option>
+            </cds-select>
+            <ng-template #kmsProviderError>
+              <span class="invalid-feedback"
+                    *ngIf="bucketForm.showError('kms_provider', frm, 'required')"
+                    i18n>This field is required.</span>
+            </ng-template>
           </div>
-        </fieldset>
 
-        <!-- Tags -->
-        <fieldset>
-          <legend class="cd-header"
-                  i18n>Tags
-            <cd-help-text>Tagging provides a way to categorize storage</cd-help-text>
-          </legend>
-          <span *ngFor="let tag of tags; let i=index;">
-            <ng-container *ngTemplateOutlet="tagTpl; context:{index: i, tag: tag}"></ng-container>
-          </span>
-
-          <div class="row">
-            <div class="col-12">
-              <strong *ngIf="tags.length > 19"
-                       class="text-warning"
-                       i18n>Maximum of 20 tags reached</strong>
-              <button type="button"
-                      id="add-tag"
-                      class="btn btn-light float-end my-3"
-                      [disabled]="tags.length > 19"
-                      (click)="showTagModal()">
-                <i [ngClass]="[icons.add]"></i>
-                <ng-container i18n>Add tag</ng-container>
-              </button>
-            </div>
+          <div class="form-item">
+            <cds-text-label for="keyId"
+                            cdRequiredField="Key Id"
+                            [invalid]="!bucketForm.controls.keyId.valid && (bucketForm.controls.keyId.dirty)"
+                            [invalidText]="keyIdError"
+                            i18n>Key Id
+              <input cdsText
+                     id="keyId"
+                     name="keyId"
+                     formControlName="keyId"
+                     [invalid]="!bucketForm.controls.keyId.valid && (bucketForm.controls.keyId.dirty)">
+            </cds-text-label>
+            <ng-template #keyIdError>
+              <span class="invalid-feedback"
+                    *ngIf="bucketForm.showError('keyId', frm, 'required')"
+                    i18n>This field is required.</span>
+            </ng-template>
           </div>
-        </fieldset>
+        </div>
+      </div>
+    </fieldset>
 
-        <!-- Policies -->
-        <fieldset>
-          <legend class="cd-header"
-                  i18n>Policies
-          </legend>
-          <div class="row">
-            <div class="col-12">
-              <div class="form-group row">
+    <!-- Replication -->
+    <fieldset>
+      <div class="form-item">
+        <legend class="cd-header"
+                i18n>Replication</legend>
+        <ng-container *ngIf="{status: multisiteStatus$, isDefaultZg: isDefaultZoneGroup$ | async} as multisiteStatus; else loadingTpl">
+          <cds-checkbox name="replication"
+                        formControlName="replication"
+                        id="replication"
+                        [disabled]="!multisiteStatus.isDefaultZg && !multisiteStatus.status.available"
+                        i18n-helperText
+                        i18n>Enable
+            <cd-help-text>Enables replication for the objects in the bucket.</cd-help-text>
+          </cds-checkbox>
 
-                <!-- Bucket policy -->
-                <label i18n
-                       class="cd-col-form-label"
-                       for="id">Bucket policy</label>
-                <div class="cd-col-form-input">
-                  <textarea #bucketPolicyTextArea
-                            class="form-control resize-vertical"
-                            id="bucket_policy"
-                            formControlName="bucket_policy"
-                            (change)="textAreaOnChange('bucketPolicyTextArea')">
-                  </textarea>
-                  <span class="invalid-feedback"
-                        *ngIf="bucketForm.showError('bucket_policy', frm, 'invalidJson')"
-                        i18n>Invalid json text.</span>
-                  <button type="button"
-                          id="clear-bucket-policy"
-                          class="btn btn-light my-3"
-                          (click)="clearTextArea('bucket_policy', '{}')"
+          <cd-alert-panel type="info"
+                          class="me-1"
+                          id="replication-info"
+                          spacingClass="mt-1"
+                          *ngIf="!editing"
                           i18n>
-                    <i [ngClass]="[icons.destroy]"></i>
-                    Clear
-                  </button>
-                  <div class="btn-group float-end"
-                       role="group"
-                       aria-label="bucket-policy-helpers">
-                    <button type="button"
-                            id="example-generator-button"
-                            class="btn btn-light my-3"
-                            (click)="openUrl('https://docs.aws.amazon.com/AmazonS3/latest/userguide/example-bucket-policies.html?icmpid=docs_amazons3_console')"
-                            i18n>
-                      <i [ngClass]="[icons.externalUrl]"></i>
-                      Policy examples
-                    </button>
-                    <button type="button"
-                            id="example-generator-button"
-                            class="btn btn-light my-3"
-                            (click)="openUrl('https://awspolicygen.s3.amazonaws.com/policygen.html')"
-                            i18n>
-                      <i [ngClass]="[icons.externalUrl]"></i>
-                      Policy generator
-                    </button>
-                  </div>
-                </div>
-              </div>
+            A bi-directional sync policy group will be created by the dashboard along with flows and pipes.
+            The pipe id will then be used for applying the replication policy to the bucket.
+          </cd-alert-panel>
+        </ng-container>
+      </div>
+    </fieldset>
 
-              <!-- Lifecycle -->
-              <div *ngIf="editing"
-                   class="form-group row">
-                <label i18n
-                       class="cd-col-form-label"
-                       for="id">Lifecycle
-                  <cd-helper>JSON or XML formatted document</cd-helper>
-                </label>
-                <div class="cd-col-form-input">
-                  <textarea #lifecycleTextArea
-                            class="form-control resize-vertical"
-                            id="lifecycle"
-                            formControlName="lifecycle"
-                            (change)="textAreaOnChange('lifecycleTextArea')">
-                  </textarea>
-                  <span class="invalid-feedback"
-                        *ngIf="bucketForm.showError('lifecycle', frm, 'invalidJson')"
-                        i18n>Invalid json text.</span>
-                  <span class="invalid-feedback"
-                        *ngIf="bucketForm.showError('lifecycle', frm, 'invalidXml')"
-                        i18n>Invalid xml text.</span>
-                  <button type="button"
-                          id="clear-lifecycle"
-                          class="btn btn-light my-3"
-                          (click)="clearTextArea('lifecycle', '{}')"
-                          i18n>
-                    <i [ngClass]="[icons.destroy]"></i>
-                    Clear
-                  </button>
-                  <div class="btn-group float-end"
-                       role="group"
-                       aria-label="bucket-policy-helpers">
-                    <button type="button"
-                            id="lifecycle-examples-button"
-                            class="btn btn-light my-3"
-                            (click)="openUrl('https://docs.aws.amazon.com/cli/latest/reference/s3api/put-bucket-lifecycle.html#examples')"
-                            i18n>
-                      <i [ngClass]="[icons.externalUrl]"></i>
-                      Policy examples
-                    </button>
-                  </div>
-                </div>
-              </div>
+    <!-- Tags -->
+    <fieldset>
+      <legend class="cd-header"
+              i18n>Tags
+        <cd-help-text>Tagging provides a way to categorize storage</cd-help-text>
+      </legend>
+      <span *ngFor="let tag of tags; let i=index;">
+        <ng-container *ngTemplateOutlet="tagTpl; context:{index: i, tag: tag}"></ng-container>
+      </span>
 
-              <div class="form-group row">
+      <div cdsRow>
+        <div cdsCol>
+          <cds-tooltip [description]="tags.length > 19 ? 'Maximum of 20 tags reached' : ''"
+                       [highContrast]="true"
+                       [caret]="true">
+            <button cdsButton="tertiary"
+                    id="add-tag"
+                    [disabled]="tags.length > 19"
+                    (click)="showTagModal()">
+              <ng-container i18n>Add tag</ng-container>
+              <svg cdsIcon="add"
+                   size="32"
+                   class="cds--btn__icon"></svg>
+            </button>
+          </cds-tooltip>
+        </div>
+      </div>
+    </fieldset>
 
-                <!-- ACL -->
-                <label class="cd-col-form-label"
-                       i18n>ACL
-                  <cd-helper>Any changes to the ACL will overwrite previous one.
-                    You can choose any of the available options to modify the spcified user group.</cd-helper>
-                </label>
-                <div class="cd-col-form-input">
-                  <div class="input-group">
-                    <span class="input-group-text"
-                          for="grantee"
-                          i18n>Grantee
-                      <cd-helper>Select a grantee (user group) to modify it's permisions</cd-helper>
-                    </span>
-                    <select id="grantee"
-                            name="grantee"
-                            class="form-input form-select"
-                            formControlName="grantee"
-                            (change)="onSelectionFilter()">
-                      <option *ngFor="let item of grantees"
-                              [value]="item"
-                              i18n>{{ item }}</option>
-                    </select>
-                    <span class="invalid-feedback"
-                          *ngIf="bucketForm.showError('grantee', frm, 'required')"
-                          i18n>This field is required.</span>
-                    <span class="input-group-text"
-                          for="aclPermission"
-                          i18n>Permissions
-                      <cd-helper>Select the permision to give to the selected grantee.
-                        Regardless, the owner of the bucket will always have
-                        FULL CONTROL access</cd-helper>
-                    </span>
-                    <select id="aclPermission"
-                            name="aclPermission"
-                            class="form-input form-select"
-                            formControlName="aclPermission">
-                      <option *ngFor="let permission of aclPermissions"
-                              [value]="permission"
-                              i18n>{{ permission }}
-                      </option>
-                    </select>
-                    <span class="invalid-feedback"
-                          *ngIf="bucketForm.showError('aclPermission', frm, 'required')"
-                          i18n>This field is required.</span>
-                  </div>
-                </div>
-              </div>
-            </div>
+    <!-- Policies -->
+    <fieldset>
+      <div class="form-item">
+        <legend class="cd-header"
+                i18n>Policies
+        </legend>
+
+        <!-- Bucket policy -->
+        <cds-textarea-label for="id"
+                            [invalid]="!bucketForm.controls.bucket_policy.valid && (bucketForm.controls.bucket_policy.dirty)"
+                            [invalidText]="bucketPolicyError"
+                            i18n>Bucket Policy
+          <textarea cdsTextArea
+                    class="textarea-field"
+                    id="bucket_policy"
+                    formControlName="bucket_policy"
+                    (change)="textAreaOnChange('bucket_policy')"
+                    cols="200"
+                    #bucketPolicyTextArea></textarea>
+        </cds-textarea-label>
+        <ng-template #bucketPolicyError>
+          <span class="invalid-feedback"
+                *ngIf="bucketForm.showError('bucket_policy', frm, 'invalidJson')"
+                i18n>Invalid json text.</span>
+        </ng-template>
+        <div cdsRow>
+          <div cdsCol>
+            <cds-button-set class="mt-1">
+              <button cdsButton="tertiary"
+                      id="example-generator-button"
+                      (click)="openUrl('https://docs.aws.amazon.com/AmazonS3/latest/userguide/example-bucket-policies.html?icmpid=docs_amazons3_console')"
+                      i18n>Policy examples
+                <svg cdsIcon="launch"
+                     size="16"
+                     class="cds--btn__icon"></svg>
+              </button>
+              <button cdsButton="tertiary"
+                      id="example-generator-button"
+                      (click)="openUrl('https://awspolicygen.s3.amazonaws.com/policygen.html')"
+                      i18n>Policy generator
+                <svg cdsIcon="launch"
+                     size="16"
+                     class="cds--btn__icon"></svg>
+              </button>
+            </cds-button-set>
+          </div>
+          <div cdsCol>
+            <cds-button-set class="float-end mt-1">
+              <button cdsButton="tertiary"
+                      id="clear-bucket-policy"
+                      (click)="clearTextArea('bucket_policy', '{}')"
+                      i18n>Clear
+                <svg cdsIcon="close"
+                     size="32"
+                     class="cds--btn__icon"></svg>
+              </button>
+            </cds-button-set>
           </div>
-        </fieldset>
+        </div>
+      </div>
 
-        <!--Advanced-->
-        <cd-form-advanced-fieldset>
-          <!-- Placement target -->
-          <div class="form-group row"
-               *ngIf="!editing">
-            <label class="cd-col-form-label"
-                   for="placement-target"
-                   i18n>Placement target</label>
-            <div class="cd-col-form-input">
-              <select id="placement-target"
-                      name="placement-target"
-                      formControlName="placement-target"
-                      class="form-select">
-                <option i18n
-                        *ngIf="placementTargets === null"
-                        [ngValue]="null">Loading...</option>
-                <option i18n
-                        *ngIf="placementTargets !== null"
-                        [ngValue]="null">-- Select a placement target --</option>
-                <option *ngFor="let placementTarget of placementTargets"
-                        [value]="placementTarget.name">{{placementTarget.description }}</option>
-              </select>
-              <cd-help-text>
-                <span i18n>
-                  When creating a bucket, a placement target can be provided as part of the LocationConstraint to override the default placement targets from the user and zonegroup.
-                </span>
-              </cd-help-text>
-            </div>
+      <!-- Lifecycle -->
+      <div *ngIf="editing"
+           class="form-item">
+        <cds-textarea-label for="id"
+                            [invalid]="!bucketForm.controls.lifecycle.valid && bucketForm.controls.lifecycle.dirty"
+                            [invalidText]="lifecycleError"
+                            i18n>Lifecycle
+          <textarea cdsTextArea
+                    class="textarea-field"
+                    id="lifecycle"
+                    formControlName="lifecycle"
+                    (change)="textAreaOnChange('lifecycle')"
+                    cols="200"
+                    #lifecycleTextArea></textarea>
+          <cd-help-text>JSON or XML formatted document</cd-help-text>
+        </cds-textarea-label>
+        <ng-template #lifecycleError>
+          <span class="invalid-feedback"
+                *ngIf="bucketForm.showError('lifecycle', frm, 'invalidJson')"
+                i18n>Invalid json text.</span>
+          <span class="invalid-feedback"
+                *ngIf="bucketForm.showError('lifecycle', frm, 'invalidXml')"
+                i18n>Invalid xml text.</span>
+        </ng-template>
+        <div cdsRow>
+          <div cdsCol>
+            <cds-button-set class="mt-1">
+              <button cdsButton="tertiary"
+                      id="lifecycle-examples-button"
+                      (click)="openUrl('https://docs.aws.amazon.com/cli/latest/reference/s3api/put-bucket-lifecycle.html#examples')"
+                      i18n>Policy examples
+                <svg cdsIcon="launch"
+                     size="16"
+                     class="cds--btn__icon"></svg>
+              </button>
+            </cds-button-set>
           </div>
-          <!-- Bucket Rate Limit -->
+          <div cdsCol>
+            <cds-button-set class="float-end mt-1">
+              <button cdsButton="tertiary"
+                      id="clear-lifecycle"
+                      (click)="clearTextArea('lifecycle', '{}')"
+                      i18n>Clear
+                <svg cdsIcon="close"
+                     size="32"
+                     class="cds--btn__icon"></svg>
+              </button>
+            </cds-button-set>
+          </div>
+        </div>
+      </div>
+
+      <div class="form-item">
 
-          <cd-rgw-rate-limit [type]="'bucket'"
-                             [isEditing]="this.editing"
-                             [allowBid]="this.bucketForm.getValue('bid')"
-                             (rateLimitFormGroup)="rateLimitFormInit($event)"></cd-rgw-rate-limit>
-        </cd-form-advanced-fieldset>
+        <!-- ACL -->
+        <cds-text-label i18n>ACL
+          <cd-help-text>Any changes to the ACL will overwrite previous one.
+            You can choose any of the available options to modify the spcified user group.</cd-help-text>
+        </cds-text-label>
+        <div cdsRow
+             class="form-item-append">
+          <div cdsCol>
+            <cds-select id="grantee"
+                        name="grantee"
+                        label="Grantee"
+                        formControlName="grantee"
+                        helperText="Select a grantee (user group) to modify it's permisions"
+                        [invalid]="!bucketForm.controls.grantee.valid && (bucketForm.controls.grantee.dirty)"
+                        [invalidText]="granteeError"
+                        (valueChange)="onSelectionFilter()"
+                        i18n>Grantee
+              <option *ngFor="let item of grantees"
+                      [value]="item">{{ item }}</option>
+            </cds-select>
+            <ng-template #granteeError>
+              <span class="invalid-feedback"
+                    *ngIf="bucketForm.showError('grantee', frm, 'required')"
+                    i18n>This field is required.</span>
+            </ng-template>
+          </div>
+          <div cdsCol>
+            <cds-select id="aclPermission"
+                        name="aclPermission"
+                        [invalid]="!bucketForm.controls.aclPermission.valid && (bucketForm.controls.aclPermission.dirty)"
+                        [invalidText]="aclPermissionError"
+                        label="Permissions"
+                        helperText="Select the permision to give to the selected grantee. Regardless, the owner of the bucket will always have FULL CONTROL access"
+                        formControlName="aclPermission">
+              <option *ngFor="let permission of aclPermissions"
+                      [value]="permission"
+                      i18n>{{ permission }}</option>
+            </cds-select>
+            <ng-template #aclPermissionError>
+              <span class="invalid-feedback"
+                    *ngIf="bucketForm.showError('aclPermission', frm, 'required')"
+                    i18n>This field is required.</span>
+            </ng-template>
+          </div>
+        </div>
       </div>
+    </fieldset>
 
-      <div class="card-footer">
-        <cd-form-button-panel (submitActionEvent)="submit()"
-                              [form]="bucketForm"
-                              [submitText]="(action | titlecase) + ' ' + (resource | upperFirst)"
-                              wrappingClass="text-right"></cd-form-button-panel>
+    <!--Advanced-->
+    <cd-form-advanced-fieldset>
+      <!-- Placement target -->
+      <div class="form-item"
+           *ngIf="!editing">
+        <cds-select label="Placement target"
+                    for="placement-target"
+                    formControlName="placement-target"
+                    name="placement-target"
+                    id="placement-target"
+                    helperText="When creating a bucket, a placement target can be provided as part of the LocationConstraint to override the default placement targets from the user and zonegroup."
+                    i18n-helperText
+                    i18n>Placement target
+          <option *ngIf="placementTargets === null"
+                  [ngValue]="null">Loading...</option>
+          <option *ngIf="placementTargets !== null"
+                  [ngValue]="null">-- Select a placement target --</option>
+          <option *ngFor="let placementTarget of placementTargets"
+                  [value]="placementTarget.name">{{ placementTarget.description }}</option>
+        </cds-select>
       </div>
-    </div>
+
+      <!-- Bucket Rate Limit -->
+      <cd-rgw-rate-limit [type]="'bucket'"
+                         [isEditing]="this.editing"
+                         [allowBid]="this.bucketForm.getValue('bid')"
+                         (rateLimitFormGroup)="rateLimitFormInit($event)">
+      </cd-rgw-rate-limit>
+    </cd-form-advanced-fieldset>
+
+    <cd-form-button-panel (submitActionEvent)="submit()"
+                          [form]="bucketForm"
+                          [submitText]="(action | titlecase) + ' ' + (resource | upperFirst)"
+                          wrappingClass="text-right"></cd-form-button-panel>
   </form>
 </div>
 
 <ng-template #tagTpl
              let-tag="tag"
              let-index="index">
-  <div class="input-group my-2">
+  <div class="form-item form-item-append">
     <ng-container *ngFor="let config of tagConfig">
       <input type="text"
              id="tag-{{config.attribute}}-{{index}}"
-             class="form-control"
+             cdsText
              [ngbTooltip]="config.attribute"
              [value]="tag[config.attribute]"
-             disabled
              readonly>
     </ng-container>
 
     <!-- Tag actions -->
-    <button type="button"
-            class="btn btn-light"
-            id="tag-edit-{{index}}"
-            i18n-ngbTooltip
-            ngbTooltip="Edit"
-            (click)="showTagModal(index)">
-      <i [ngClass]="[icons.edit]"></i>
-    </button>
-    <button type="button"
-            class="btn btn-light"
-            id="tag-delete-{{index}}"
-            i18n-ngbTooltip
-            ngbTooltip="Delete"
-            (click)="deleteTag(index)">
-      <i [ngClass]="[icons.trash]"></i>
-    </button>
+    <cds-icon-button kind="tertiary"
+                     size="md"
+                     (click)="showTagModal(index)">
+      <svg cdsIcon="edit"
+           size="32"
+           class="cds--btn__icon"></svg>
+    </cds-icon-button>
+    <cds-icon-button kind="danger"
+                     size="md"
+                     (click)="deleteTag(index)">
+      <svg cdsIcon="trash-can"
+           size="32"
+           class="cds--btn__icon"></svg>
+    </cds-icon-button>
   </div>
 </ng-template>
 
 <ng-template #loadingTpl>
-  <div class="cd-col-form-input">
-    <cd-loading-panel i18n>Checking multi-site status...</cd-loading-panel>
-  </div>
+  <cd-loading-panel i18n>Checking multi-site status...</cd-loading-panel>
 </ng-template>
 
index 24090aba90597601d3704becdcdf796691fe1901..0de5bbdef563c7e76c362121063a22e33cf75ff4 100644 (file)
@@ -19,6 +19,8 @@ import { RgwBucketMfaDelete } from '../models/rgw-bucket-mfa-delete';
 import { RgwBucketVersioning } from '../models/rgw-bucket-versioning';
 import { RgwBucketFormComponent } from './rgw-bucket-form.component';
 import { RgwRateLimitComponent } from '../rgw-rate-limit/rgw-rate-limit.component';
+import { CheckboxModule, SelectModule } from 'carbon-components-angular';
+import { NO_ERRORS_SCHEMA } from '@angular/core';
 
 describe('RgwBucketFormComponent', () => {
   let component: RgwBucketFormComponent;
@@ -36,8 +38,11 @@ describe('RgwBucketFormComponent', () => {
       ReactiveFormsModule,
       RouterTestingModule,
       SharedModule,
-      ToastrModule.forRoot()
-    ]
+      ToastrModule.forRoot(),
+      SelectModule,
+      CheckboxModule
+    ],
+    schemas: [NO_ERRORS_SCHEMA]
   });
 
   beforeEach(() => {
index 53fdf5273b8beda9315b5a653d1046714df75662..6aac20881e83c59c93a5e4cd09ab5198792cb505 100644 (file)
@@ -23,7 +23,6 @@ import { CdForm } from '~/app/shared/forms/cd-form';
 import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
 import { CdValidators } from '~/app/shared/forms/cd-validators';
-import { ModalService } from '~/app/shared/services/modal.service';
 import { NotificationService } from '~/app/shared/services/notification.service';
 import { rgwBucketEncryptionModel } from '../models/rgw-bucket-encryption';
 import { RgwBucketMfaDelete } from '../models/rgw-bucket-mfa-delete';
@@ -41,6 +40,7 @@ import { map, switchMap } from 'rxjs/operators';
 import { TextAreaXmlFormatterService } from '~/app/shared/services/text-area-xml-formatter.service';
 import { RgwRateLimitComponent } from '../rgw-rate-limit/rgw-rate-limit.component';
 import { RgwRateLimitConfig } from '../models/rgw-rate-limit';
+import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
 
 @Component({
   selector: 'cd-rgw-bucket-form',
@@ -96,7 +96,7 @@ export class RgwBucketFormComponent extends CdForm implements OnInit, AfterViewC
     private formBuilder: CdFormBuilder,
     private rgwBucketService: RgwBucketService,
     private rgwSiteService: RgwSiteService,
-    private modalService: ModalService,
+    private modalService: ModalCdsService,
     private rgwUserService: RgwUserService,
     private notificationService: NotificationService,
     private textAreaJsonFormatterService: TextAreaJsonFormatterService,
@@ -498,7 +498,7 @@ export class RgwBucketFormComponent extends CdForm implements OnInit, AfterViewC
 
   showTagModal(index?: number) {
     const modalRef = this.modalService.show(BucketTagModalComponent);
-    const modalComponent = modalRef.componentInstance as BucketTagModalComponent;
+    const modalComponent = modalRef as BucketTagModalComponent;
     modalComponent.currentKeyTags = this.tags.map((item) => item.key);
 
     if (_.isNumber(index)) {
index 045896ee19a47e1b0d9bf4039d0dd82e1014dca7..e704670e829e14f4c70f5eca082ce2beeb439430 100644 (file)
@@ -2,15 +2,12 @@
            [open]="open"
            (overlaySelected)="closeModal()">
   <cds-modal-header (closeSelect)="closeModal()"
-                    i18n>{{editing ? 'Edit' : 'Create'}} Tiering configuration</cds-modal-header>
+                    i18n>{{editing ? 'Edit' : 'Create'}} Tiering configuration
+    <cd-help-text [formAllFieldsRequired]="true"></cd-help-text>
+  </cds-modal-header>
 
 <ng-container *cdFormLoading="loading">
   <section cdsModalContent>
-    <legend>
-      <cd-help-text i18n>
-        All fields are required, except where marked optional.
-      </cd-help-text>
-    </legend>
     <cd-alert-panel
       *ngIf="(snapScheduleModuleStatus$ | async) === false"
       type="info"
index 3a99a1b305297e1e64f4bf3fbf2bdc79c30dd514..4777d78b5ad4c7580e9dec91b085e3a55d20af73 100644 (file)
@@ -1,5 +1,6 @@
 <fieldset *ngIf="type">
-  <legend i18n>
+  <legend class="cd-header"
+          i18n>
     Rate Limit
     <cd-help-text *ngIf="type === 'user'">
       The User Rate Limit controls the max read/write operations and data per minute for each user.
         </div>
       </div>
     </div>
+
     <!-- Enabled -->
-    <div class="form-group row">
-      <div class="cd-col-form-offset">
-        <div class="custom-control custom-checkbox">
-          <input
-            class="custom-control-input"
-            id="rate_limit_enabled"
-            type="checkbox"
-            formControlName="rate_limit_enabled"
-          />
-          <label class="custom-control-label"
-                 for="rate_limit_enabled"
-                 i18n>Enabled</label>
-          <cd-help-text i18n>Toggle to enable or disable the rate limit settings.</cd-help-text>
-        </div>
-      </div>
+    <div class="form-item">
+      <cds-checkbox id="rate_limit_enabled"
+                    formControlName="rate_limit_enabled">
+        <ng-container i18n>Enabled</ng-container>
+        <cd-help-text i18n>Toggle to enable or disable the rate limit settings.</cd-help-text>
+      </cds-checkbox>
     </div>
-    <!-- Unlimited size -->
-    <div class="form-group row"
+
+    <!-- Unlimited read ops -->
+    <div class="form-item"
          *ngIf="form.controls.rate_limit_enabled.value">
-      <div class="cd-col-form-offset">
-        <div class="custom-control custom-checkbox">
-          <input
-            class="custom-control-input"
-            id="rate_limit_max_readOps_unlimited"
-            type="checkbox"
-            formControlName="rate_limit_max_readOps_unlimited"
-          />
-          <label class="custom-control-label"
-                 for="rate_limit_max_readOps_unlimited"
-                 i18n>Unlimited read ops
-          </label>
-          <cd-help-text i18n>Select this box to allow unlimited read operations.</cd-help-text>
-        </div>
-      </div>
+      <cds-checkbox id="rate_limit_max_readOps_unlimited"
+                    formControlName="rate_limit_max_readOps_unlimited">
+        <ng-container i18n>Unlimited read ops</ng-container>
+        <cd-help-text i18n>Select this box to allow unlimited read operations.</cd-help-text>
+      </cds-checkbox>
     </div>
 
-    <!-- Maximum size -->
+    <!-- Maximum read ops -->
     <div
-      class="form-group row"
+      class="form-item"
       *ngIf="
         form.controls.rate_limit_enabled.value && !form.getValue('rate_limit_max_readOps_unlimited')
       "
     >
-      <label class="cd-col-form-label required"
-             for="rate_limit_max_readOps"
-             i18n>Max. read ops</label>
-      <div class="cd-col-form-input">
-        <input
-          id="rate_limit_max_readOps"
-          class="form-control"
-          type="number"
-          formControlName="rate_limit_max_readOps"
-        />
-        <cd-help-text>Limits the number of read operations per minute for a user.</cd-help-text>
+      <cds-number id="rate_limit_max_readOps"
+                  formControlName="rate_limit_max_readOps"
+                  label="Maximum read ops"
+                  i18n-label
+                  cdRequiredField="Maximum read ops"
+                  helperText="Limits the number of read operations per minute for a user."
+                  i18n-helperText
+                  [invalid]="form.controls.rate_limit_max_readOps.invalid && form.controls.rate_limit_max_readOps.dirty"
+                  [invalidText]="rateLimitMaxReadOpsError">
+      </cds-number>
+
+      <ng-template #rateLimitMaxReadOpsError>
         <span
           class="invalid-feedback"
           *ngIf="form.showError('rate_limit_max_readOps', frm, 'required')"
           i18n
           >Enter a positive number.</span
         >
-      </div>
+      </ng-template>
     </div>
+
     <!-- Unlimited Write Ops -->
-    <div class="form-group row"
+    <div class="form-item"
          *ngIf="form.controls.rate_limit_enabled.value">
-      <div class="cd-col-form-offset">
-        <div class="custom-control custom-checkbox">
-          <input
-            class="custom-control-input"
-            id="rate_limit_max_writeOps_unlimited"
-            type="checkbox"
-            formControlName="rate_limit_max_writeOps_unlimited"
-          />
-          <label class="custom-control-label"
-                 for="rate_limit_max_writeOps_unlimited"
-                 i18n>Unlimited write ops
-          </label>
-          <cd-help-text i18n>Select this box to allow unlimited write operations.</cd-help-text>
-        </div>
-      </div>
+      <cds-checkbox id="rate_limit_max_writeOps_unlimited"
+                    formControlName="rate_limit_max_writeOps_unlimited">
+        <ng-container i18n>Unlimited write ops</ng-container>
+        <cd-help-text i18n>Select this box to allow unlimited write operations.</cd-help-text>
+      </cds-checkbox>
     </div>
+
     <!-- Maximum Write Ops -->
     <div
-      class="form-group row"
+      class="form-item"
       *ngIf="
         form.controls.rate_limit_enabled.value &&
         !form.getValue('rate_limit_max_writeOps_unlimited')
       "
     >
-      <label class="cd-col-form-label required"
-             for="rate_limit_max_writeOps"
-             i18n>Max. write ops</label>
-      <div class="cd-col-form-input">
-        <input
-          id="rate_limit_max_writeOps"
-          class="form-control"
-          type="number"
-          formControlName="rate_limit_max_writeOps"
-        />
-        <cd-help-text>Limits the number of write operations per minute for a user.</cd-help-text>
+      <cds-number id="rate_limit_max_writeOps"
+                  formControlName="rate_limit_max_writeOps"
+                  label="Maximum write ops"
+                  i18n-label
+                  cdRequiredField="Maximum write ops"
+                  helperText="Limits the number of write operations per minute for a user."
+                  i18n-helperText
+                  [invalid]="form.controls.rate_limit_max_writeOps.invalid && form.controls.rate_limit_max_writeOps.dirty"
+                  [invalidText]="rateLimitMaxWriteOpsError">
+      </cds-number>
+
+      <ng-template #rateLimitMaxWriteOpsError>
         <span
           class="invalid-feedback"
           *ngIf="form.showError('rate_limit_max_writeOps', frm, 'required')"
           i18n
           >Enter a positive number.</span
         >
-      </div>
+      </ng-template>
     </div>
+
     <!-- Unlimited Read Bytes -->
-    <div class="form-group row"
+    <div class="form-item"
          *ngIf="form.controls.rate_limit_enabled.value">
-      <div class="cd-col-form-offset">
-        <div class="custom-control custom-checkbox">
-          <input
-            class="custom-control-input"
-            id="rate_limit_max_readBytes_unlimited"
-            type="checkbox"
-            formControlName="rate_limit_max_readBytes_unlimited"
-          />
-          <label class="custom-control-label"
-                 for="rate_limit_max_readBytes_unlimited"
-                 i18n>Unlimited read bytes
-          </label>
-          <cd-help-text i18n>Select this box to allow unlimited read bytes.</cd-help-text>
-        </div>
-      </div>
+      <cds-checkbox id="rate_limit_max_readBytes_unlimited"
+                    formControlName="rate_limit_max_readBytes_unlimited">
+        <ng-container i18n>Unlimited read bytes</ng-container>
+        <cd-help-text i18n>Select this box to allow unlimited read bytes.</cd-help-text>
+      </cds-checkbox>
     </div>
+
     <!-- Maximum Read Bytes -->
     <div
-      class="form-group row"
+      class="form-item"
       *ngIf="
         form.controls.rate_limit_enabled.value &&
         !form.getValue('rate_limit_max_readBytes_unlimited')
       "
     >
-      <label class="cd-col-form-label required"
-             for="rate_limit_max_readBytes"
-             i18n>Max. read bytes</label
-      >
-      <div class="cd-col-form-input">
-        <input
-          id="rate_limit_max_readBytes"
-          class="form-control"
-          type="text"
-          defaultUnit="b"
-          formControlName="rate_limit_max_readBytes"
-          cdDimlessBinaryPerMinute
-        />
-        <cd-help-text>Limits the number of read bytes per minute for a user.</cd-help-text>
+      <cds-text-label labelInputID="rate_limit_max_readBytes"
+                      cdRequiredField="Maximum read bytes"
+                      helperText="Limits the number of read bytes per minute for a user."
+                      i18n-helperText
+                      [invalid]="form.controls.rate_limit_max_readBytes.invalid && form.controls.rate_limit_max_readBytes.dirty"
+                      [invalidText]="maxReadBytesError">
+        <input cdsText
+               id="rate_limit_max_readBytes"
+               formControlName="rate_limit_max_readBytes"
+               defaultUnit="b"
+               [invalid]="form.controls.rate_limit_max_readBytes.invalid && form.controls.rate_limit_max_readBytes.dirty"
+               cdDimlessBinaryPerMinute>
+      </cds-text-label>
+
+      <ng-template #maxReadBytesError>
         <span
           class="invalid-feedback"
           *ngIf="form.showError('rate_limit_max_readBytes', frm, 'required')"
           class="invalid-feedback"
           *ngIf="form.showError('rate_limit_max_readBytes', frm, 'min')"
           i18n>Enter a positive number.</span>
-      </div>
+      </ng-template>
     </div>
+
     <!-- Unlimited Write Bytes -->
-    <div class="form-group row"
+    <div class="form-item"
          *ngIf="form.controls.rate_limit_enabled.value">
-      <div class="cd-col-form-offset">
-        <div class="custom-control custom-checkbox">
-          <input
-            class="custom-control-input"
-            id="rate_limit_max_writeBytes_unlimited"
-            type="checkbox"
-            formControlName="rate_limit_max_writeBytes_unlimited"
-          />
-          <label class="custom-control-label"
-                 for="rate_limit_max_writeBytes_unlimited"
-                 i18n>Unlimited write bytes
-          </label>
-          <cd-help-text i18n>Select this box to allow unlimited write bytes.</cd-help-text>
-        </div>
-      </div>
+      <cds-checkbox id="rate_limit_max_writeBytes_unlimited"
+                    formControlName="rate_limit_max_writeBytes_unlimited">
+        <ng-container i18n>Unlimited write bytes</ng-container>
+        <cd-help-text i18n>Select this box to allow unlimited write bytes.</cd-help-text>
+      </cds-checkbox>
     </div>
+
     <!-- Maximum Write Bytes -->
     <div
-      class="form-group row"
+      class="form-item"
       *ngIf="
         form.controls.rate_limit_enabled.value &&
         !form.getValue('rate_limit_max_writeBytes_unlimited')
       ">
-      <label class="cd-col-form-label required"
-             for="rate_limit_max_writeBytes"
-             i18n>Max. write bytes</label>
-      <div class="cd-col-form-input">
-        <input
-          id="rate_limit_max_writeBytes"
-          class="form-control"
-          type="text"
-          defaultUnit="b"
-          formControlName="rate_limit_max_writeBytes"
-          cdDimlessBinaryPerMinute
-        />
-        <cd-help-text>Limits the number of write bytes per minute for a user.</cd-help-text>
+      <cds-text-label labelInputID="rate_limit_max_writeBytes"
+                      cdRequiredField="Maximum write bytes"
+                      helperText="Limits the number of write bytes per minute for a user."
+                      i18n-helperText
+                      [invalid]="form.controls.rate_limit_max_writeBytes.invalid && form.controls.rate_limit_max_writeBytes.dirty"
+                      [invalidText]="maxWriteBytesError">
+        <input cdsText
+               id="rate_limit_max_writeBytes"
+               formControlName="rate_limit_max_writeBytes"
+               defaultUnit="b"
+               [invalid]="form.controls.rate_limit_max_writeBytes.invalid && form.controls.rate_limit_max_writeBytes.dirty"
+               cdDimlessBinaryPerMinute>
+      </cds-text-label>
+
+      <ng-template #maxWriteBytesError>
         <span
           class="invalid-feedback"
-          *ngIf="form.showError('rate_limit_max_writeBytes', frm, 'required')"
+          *ngIf="form.showError('rate_limit_max_readBytes', frm, 'required')"
           i18n>This field is required.</span>
         <span
           class="invalid-feedback"
-          *ngIf="form.showError('rate_limit_max_writeBytes', frm, 'rateByteMaxSize')"
+          *ngIf="form.showError('rate_limit_max_readBytes', frm, 'rateByteMaxSize')"
           i18n>The value is not valid.</span>
         <span
           class="invalid-feedback"
-          *ngIf="form.showError('rate_limit_max_writeBytes', frm, 'min')"
+          *ngIf="form.showError('rate_limit_max_readBytes', frm, 'min')"
           i18n>Enter a positive number.</span>
-      </div>
+      </ng-template>
     </div>
   </form>
 </fieldset>
index 35710457f441d1b94d72e288623d916383b7a4be..3d930d5bdc2d75e20300f5a1da303a8493d5fd07 100644 (file)
@@ -8,12 +8,9 @@
       <div i18n="form title"
            class="form-header">
         {{ action | titlecase }} {{ resource | upperFirst }}
+
+        <cd-help-text [formAllFieldsRequired]="true"></cd-help-text>
       </div>
-      <legend>
-        <cd-help-text i18n>
-          All fields are required, except where marked optional.
-        </cd-help-text>
-      </legend>
       <div class="form-item form-item-append"
            cdsRow>
         <div cdsCol>
index d94e2b94497e1b0cdad710c644592fea3c5203a6..2490fbdd175a322e3b88b3c883668165896645dd 100644 (file)
@@ -1,70 +1,78 @@
-<cd-modal [modalRef]="activeModal">
-  <ng-container i18n="form title"
-                class="modal-title">{{ action | titlecase }} {{ resource | upperFirst }}</ng-container>
+<cds-modal size="md"
+           [open]="open"
+           [hasScrollingContent]="false"
+           (overlaySelected)="closeModal()">
+  <cds-modal-header (closeSelect)="closeModal()">
+    <h3 cdsModalHeaderHeading
+        i18n>{{ action | titlecase }} {{ resource | upperFirst }}</h3>
+
+    <cd-help-text [formAllFieldsRequired]="true"></cd-help-text>
+  </cds-modal-header>
 
-  <ng-container class="modal-content">
     <form #frm="ngForm"
           [formGroup]="formGroup"
           novalidate>
-      <div class="modal-body">
+      <div cdsModalContent>
         <!-- Type -->
-        <div class="form-group row">
-          <label class="cd-col-form-label"
-                 [ngClass]="{'required': !editing}"
-                 for="type"
-                 i18n>Type</label>
-          <div class="cd-col-form-input">
-            <input id="type"
-                   class="form-control"
-                   type="text"
-                   *ngIf="editing"
+        <div class="form-item">
+          <cds-text-label for="type"
+                          [invalid]="formGroup.controls.type.invalid && formGroup.controls.type.dirty"
+                          [invalidText]="typeError"
+                          *ngIf="editing"
+                          i18n>Type
+            <input cdsText
+                   id="type"
+                   name="type"
+                   formControlName="type"
                    [readonly]="true"
-                   formControlName="type">
-            <select id="type"
-                    class="form-select"
-                    formControlName="type"
-                    *ngIf="!editing"
-                    autofocus>
-              <option i18n
-                      *ngIf="types !== null"
-                      [ngValue]="null">-- Select a type --</option>
-              <option *ngFor="let type of types"
-                      [value]="type">{{ type }}</option>
-            </select>
+                   modal-primary-focus>
+          </cds-text-label>
+
+          <cds-select *ngIf="!editing"
+                      label="Type"
+                      i18n-label
+                      for="type"
+                      formControlName="type"
+                      [invalid]="formGroup.controls.type.invalid && formGroup.controls.type.dirty"
+                      [invalidText]="typeError">
+            <option value="">--- Select a type ---</option>
+            <option *ngFor="let type of types"
+                    [value]="type">{{ type }}</option>
+          </cds-select>
+          <ng-template #typeError>
             <span class="invalid-feedback"
                   *ngIf="formGroup.showError('type', frm, 'required')"
                   i18n>This field is required.</span>
-          </div>
+          </ng-template>
         </div>
 
         <!-- Permission -->
-        <div class="form-group row">
-          <label class="cd-col-form-label required"
-                 for="perm"
-                 i18n>Permission</label>
-          <div class="cd-col-form-input">
-            <select id="perm"
-                    class="form-select"
-                    formControlName="perm">
-              <option i18n
-                      [ngValue]="null">-- Select a permission --</option>
-              <option *ngFor="let perm of ['read', 'write', '*']"
-                      [value]="perm">
-                {{ perm }}
-              </option>
-            </select>
+        <div class="form-item">
+          <cds-select label="Permission"
+                      i18n-label
+                      for="perm"
+                      formControlName="perm"
+                      [invalid]="formGroup.controls.perm.invalid && formGroup.controls.perm.dirty"
+                      [invalidText]="permError">
+            <option value="">--- Select a permission ---</option>
+            <option *ngFor="let perm of ['read', 'write', '*']"
+                    [value]="perm">
+              {{ perm }}
+            </option>
+          </cds-select>
+
+          <ng-template #permError>
             <span class="invalid-feedback"
                   *ngIf="formGroup.showError('perm', frm, 'required')"
                   i18n>This field is required.</span>
-          </div>
+          </ng-template>
         </div>
       </div>
 
-      <div class="modal-footer">
-        <cd-form-button-panel (submitActionEvent)="onSubmit()"
-                              [form]="formGroup"
-                              [submitText]="(action | titlecase) + ' ' + (resource | upperFirst)"></cd-form-button-panel>
-      </div>
-    </form>
-  </ng-container>
-</cd-modal>
+    <cd-form-button-panel (submitActionEvent)="onSubmit()"
+                          [form]="formGroup"
+                          [submitText]="(action | titlecase) + ' ' + (resource | upperFirst)"
+                          [modalForm]="true"></cd-form-button-panel>
+
+  </form>
+</cds-modal>
index e270fb25481f70b690004df19b2c00ad7dfa63fc..0ac9dbdd065ba9c7e1be0568c32d3c9508a97a16 100644 (file)
@@ -2,11 +2,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { ReactiveFormsModule } from '@angular/forms';
 import { RouterTestingModule } from '@angular/router/testing';
 
-import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
-
 import { SharedModule } from '~/app/shared/shared.module';
 import { configureTestBed } from '~/testing/unit-test-helper';
 import { RgwUserCapabilityModalComponent } from './rgw-user-capability-modal.component';
+import { SelectModule } from 'carbon-components-angular';
 
 describe('RgwUserCapabilityModalComponent', () => {
   let component: RgwUserCapabilityModalComponent;
@@ -14,8 +13,7 @@ describe('RgwUserCapabilityModalComponent', () => {
 
   configureTestBed({
     declarations: [RgwUserCapabilityModalComponent],
-    imports: [ReactiveFormsModule, SharedModule, RouterTestingModule],
-    providers: [NgbActiveModal]
+    imports: [ReactiveFormsModule, SharedModule, RouterTestingModule, SelectModule]
   });
 
   beforeEach(() => {
index 3a3c9ac46599b081d6a5549966f6d8f71d4d2f8c..7d0745c5278b055c4d1e9cc56d772375037f27e7 100644 (file)
@@ -1,7 +1,6 @@
 import { Component, EventEmitter, Output } from '@angular/core';
 import { Validators } from '@angular/forms';
 
-import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
 import _ from 'lodash';
 
 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
@@ -9,13 +8,14 @@ import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
 import { RgwUserCapabilities } from '../models/rgw-user-capabilities';
 import { RgwUserCapability } from '../models/rgw-user-capability';
+import { BaseModal } from 'carbon-components-angular';
 
 @Component({
   selector: 'cd-rgw-user-capability-modal',
   templateUrl: './rgw-user-capability-modal.component.html',
   styleUrls: ['./rgw-user-capability-modal.component.scss']
 })
-export class RgwUserCapabilityModalComponent {
+export class RgwUserCapabilityModalComponent extends BaseModal {
   /**
    * The event that is triggered when the 'Add' or 'Update' button
    * has been pressed.
@@ -29,11 +29,8 @@ export class RgwUserCapabilityModalComponent {
   resource: string;
   action: string;
 
-  constructor(
-    private formBuilder: CdFormBuilder,
-    public activeModal: NgbActiveModal,
-    public actionLabels: ActionLabelsI18n
-  ) {
+  constructor(private formBuilder: CdFormBuilder, public actionLabels: ActionLabelsI18n) {
+    super();
     this.resource = $localize`capability`;
     this.createForm();
   }
@@ -87,6 +84,6 @@ export class RgwUserCapabilityModalComponent {
   onSubmit() {
     const capability: RgwUserCapability = this.formGroup.value;
     this.submitAction.emit(capability);
-    this.activeModal.close();
+    this.closeModal();
   }
 }
index 65bc0dee00b8d2082e343a754b8ab6bfe2dddbc2..f63f028c5c65a86171627418032b34f88ee3594f 100644 (file)
-<div class="cd-col-form"
+<div cdsCol
+     [columnNumbers]="{md: 4}"
      *cdFormLoading="loading">
   <form #frm="ngForm"
         [formGroup]="userForm"
         novalidate>
-    <div class="card">
-      <div i18n="form title"
-           class="card-header">{{ action | titlecase }} {{ resource | upperFirst }}</div>
-
-      <div class="card-body">
-        <!-- User ID -->
-        <div class="form-group row">
-          <label class="cd-col-form-label"
-                 [ngClass]="{'required': !editing}"
-                 for="user_id"
-                 i18n>User ID</label>
-          <div class="cd-col-form-input">
-            <input id="user_id"
-                   class="form-control"
-                   type="text"
-                   formControlName="user_id"
-                   [readonly]="editing">
-            <span class="invalid-feedback"
-                  *ngIf="userForm.showError('user_id', frm, 'required')"
-                  i18n>This field is required.</span>
-            <span class="invalid-feedback"
-                  *ngIf="userForm.showError('user_id', frm, 'pattern')"
-                  i18n>The value is not valid.</span>
-            <span class="invalid-feedback"
-                  *ngIf="!userForm.getValue('show_tenant') && userForm.showError('user_id', frm, 'notUnique')"
-                  i18n>The chosen user ID is already in use.</span>
-          </div>
-        </div>
+    <div i18n="form title"
+         class="form-header">{{ action | titlecase }} {{ resource | upperFirst }}</div>
+
+    <!-- User ID -->
+    <div class="form-item">
+      <cds-text-label for="user_id"
+                      i18n
+                      cdRequiredField="User ID"
+                      [invalid]="userForm.controls.user_id.invalid && (userForm.controls.user_id.dirty)"
+                      [invalidText]="userError">User ID
+        <input cdsText
+               formControlName="user_id"
+               name="user_id"
+               id="user_id"
+               [invalid]="userForm.controls.user_id.invalid && (userForm.controls.user_id.dirty)"
+               [readonly]="editing"
+               [autofocus]="!editing"/>
+      </cds-text-label>
+      <ng-template #userError>
+        <span class="invalid-feedback"
+              *ngIf="userForm.showError('user_id', frm, 'required')"
+              i18n>This field is required.</span>
+        <span class="invalid-feedback"
+              *ngIf="userForm.showError('user_id', frm, 'pattern')"
+              i18n>The value is not valid.</span>
+        <span class="invalid-feedback"
+              *ngIf="!userForm.getValue('show_tenant') && userForm.showError('user_id', frm, 'notUnique')"
+              i18n>The chosen user ID is already in use.</span>
+      </ng-template>
+    </div>
 
-        <!-- Show Tenant -->
-        <div class="form-group row">
-          <div class="cd-col-form-offset">
-            <div class="custom-control custom-checkbox">
-              <input class="custom-control-input"
-                     id="show_tenant"
-                     type="checkbox"
-                     (click)="updateFieldsWhenTenanted()"
-                     formControlName="show_tenant"
-                     [readonly]="true">
-              <label class="custom-control-label spacing-03"
-                     for="show_tenant"
-                     i18n>Show Tenant</label>
-            </div>
-          </div>
-        </div>
+      <!-- Show Tenant -->
+      <div class="form-item">
+        <cds-checkbox formControlName="show_tenant"
+                      id="show_tenant"
+                      [readonly]="true"
+                      (checkedChange)="updateFieldsWhenTenanted()">Show Tenant
+        </cds-checkbox>
+      </div>
 
-        <!-- Tenant -->
-        <div class="form-group row"
-             *ngIf="userForm.getValue('show_tenant')">
-          <label class="cd-col-form-label"
-                 for="tenant"
-                 i18n>Tenant</label>
-          <div class="cd-col-form-input">
-            <input id="tenant"
-                   class="form-control"
-                   type="text"
-                   formControlName="tenant"
-                   [readonly]="editing"
-                   autofocus>
-            <span class="invalid-feedback"
-                  *ngIf="userForm.showError('tenant', frm, 'pattern')"
-                  i18n>The value is not valid.</span>
-            <span class="invalid-feedback"
-                  *ngIf="userForm.showError('tenant', frm, 'notUnique')"
-                  i18n>The chosen user ID exists in this tenant.</span>
-          </div>
-        </div>
+    <!-- Tenant -->
+    <div class="form-item"
+         *ngIf="userForm.getValue('show_tenant')">
+      <cds-text-label for="tenant"
+                      i18n
+                      [invalid]="userForm.controls.tenant.invalid && (userForm.controls.tenant.dirty)"
+                      [invalidText]="tenantError">Tenant
+        <input cdsText
+               formControlName="tenant"
+               name="tenant"
+               id="tenant"
+               [invalid]="userForm.controls.tenant.invalid && (userForm.controls.tenant.dirty)"
+               [readonly]="editing"
+               autofocus/>
+      </cds-text-label>
+      <ng-template #tenantError>
+        <span class="invalid-feedback"
+              *ngIf="userForm.showError('tenant', frm, 'pattern')"
+              i18n>The value is not valid.</span>
+        <span class="invalid-feedback"
+              *ngIf="userForm.showError('tenant', frm, 'notUnique')"
+              i18n>The chosen user ID exists in this tenant.</span>
+      </ng-template>
+    </div>
 
-        <!-- Full name -->
-        <div class="form-group row">
-          <label class="cd-col-form-label"
-                 [ngClass]="{'required': !editing}"
-                 for="display_name"
-                 i18n>Full name</label>
-          <div class="cd-col-form-input">
-            <input id="display_name"
-                   class="form-control"
-                   type="text"
-                   formControlName="display_name">
-            <span class="invalid-feedback"
-                  *ngIf="userForm.showError('display_name', frm, 'pattern')"
-                  i18n>The value is not valid.</span>
-            <span class="invalid-feedback"
-                  *ngIf="userForm.showError('display_name', frm, 'required')"
-                  i18n>This field is required.</span>
-          </div>
-        </div>
+    <!-- Full name -->
+    <div class="form-item">
+      <cds-text-label  for="display_name"
+                       i18n
+                       cdRequiredField="Full name"
+                       [invalid]="userForm.controls.display_name.invalid && (userForm.controls.display_name.dirty)"
+                       [invalidText]="displayNameError">Full name
+        <input cdsText
+               formControlName="display_name"
+               name="display_name"
+               id="display_name"
+               [invalid]="userForm.controls.display_name.invalid && (userForm.controls.display_name.dirty)"
+               [readonly]="editing"/>
+      </cds-text-label>
+      <ng-template #displayNameError>
+        <span class="invalid-feedback"
+              *ngIf="userForm.showError('display_name', frm, 'required')"
+              i18n>This field is required.</span>
+        <span class="invalid-feedback"
+              *ngIf="userForm.showError('display_name', frm, 'pattern')"
+              i18n>The value is not valid.</span>
+      </ng-template>
+    </div>
 
-        <!-- Email address -->
-        <div class="form-group row">
-          <label class="cd-col-form-label"
-                 for="email"
-                 i18n>Email address</label>
-          <div class="cd-col-form-input">
-            <input id="email"
-                   class="form-control"
-                   type="text"
-                   formControlName="email">
-            <span class="invalid-feedback"
-                  *ngIf="userForm.showError('email', frm, 'email')"
-                  i18n>This is not a valid email address.</span>
-            <span class="invalid-feedback"
-                  *ngIf="userForm.showError('email', frm, 'notUnique')"
-                  i18n>The chosen email address is already in use.</span>
-          </div>
-        </div>
+    <!-- Email address -->
+    <div class="form-item">
+      <cds-text-label for="email"
+                      i18n
+                      [invalid]="userForm.controls.email.invalid && (userForm.controls.email.dirty)"
+                      [invalidText]="emailError">Email address
+        <input cdsText
+               formControlName="email"
+               name="email"
+               id="email"
+               [invalid]="userForm.controls.email.invalid && (userForm.controls.email.dirty)"/>
+      </cds-text-label>
+      <ng-template #emailError>
+        <span class="invalid-feedback"
+              *ngIf="userForm.showError('email', frm, 'email')"
+              i18n>This is not a valid email address.</span>
+        <span class="invalid-feedback"
+              *ngIf="userForm.showError('email', frm, 'notUnique')"
+              i18n>The chosen email address is already in use.</span>
+      </ng-template>
+    </div>
 
-        <!-- Max. buckets -->
-        <div class="form-group row">
-          <label class="cd-col-form-label"
-                 for="max_buckets_mode"
-                 i18n>Max. buckets</label>
-          <div class="cd-col-form-input">
-            <select class="form-select"
-                    formControlName="max_buckets_mode"
-                    name="max_buckets_mode"
-                    id="max_buckets_mode"
-                    (change)="onMaxBucketsModeChange($event.target.value)">
-              <option i18n
-                      value="-1">Disabled</option>
-              <option i18n
-                      value="0">Unlimited</option>
-              <option i18n
-                      value="1">Custom</option>
-            </select>
-          </div>
-        </div>
-        <div *ngIf="1 == userForm.get('max_buckets_mode').value"
-             class="form-group row">
-          <label class="cd-col-form-label"></label>
-          <div class="cd-col-form-input">
-            <input id="max_buckets"
-                   class="form-control"
-                   type="number"
-                   formControlName="max_buckets"
-                   min="1">
-            <span class="invalid-feedback"
-                  *ngIf="userForm.showError('max_buckets', frm, 'required')"
-                  i18n>This field is required.</span>
-            <span class="invalid-feedback"
-                  *ngIf="userForm.showError('max_buckets', frm, 'min')"
-                  i18n>The entered value must be >= 1.</span>
-          </div>
-        </div>
+    <!-- Max. buckets -->
+    <div class="form-item">
+      <cds-select label="Maximum buckets"
+                  for="max_buckets_mode"
+                  formControlName="max_buckets_mode"
+                  id="max_buckets_mode"
+                  (valueChange)="onMaxBucketsModeChange($event)">
+        <option value="-1"
+                i18n>Disabled</option>
+        <option value="0"
+                i18n>Unlimited</option>
+        <option value="1"
+                i18n>Custom</option>
+      </cds-select>
+    </div>
+    <div *ngIf="1 == userForm.get('max_buckets_mode').value"
+         class="form-item">
+      <cds-number for="max_buckets"
+                  formControlName="max_buckets"
+                  id="max_buckets"
+                  min="1"
+                  [invalid]="userForm.controls.max_buckets.invalid && (userForm.controls.max_buckets.dirty)"
+                  [invalidText]="maxBucketsError"></cds-number>
+      <ng-template #maxBucketsError>
+        <span class="invalid-feedback"
+              *ngIf="userForm.showError('max_buckets', frm, 'required')"
+              i18n>This field is required.</span>
+        <span class="invalid-feedback"
+              i18n>The entered value must be >= 1.</span>
+      </ng-template>
+    </div>
 
-        <!-- Suspended -->
-        <div class="form-group row">
-          <div class="cd-col-form-offset">
-            <div class="custom-control custom-checkbox">
-              <input class="custom-control-input"
-                     id="suspended"
-                     type="checkbox"
-                     formControlName="suspended">
-              <label class="custom-control-label spacing-03"
-                     for="suspended"
-                     i18n>Suspended</label>
-              <cd-helper i18n>Suspending the user disables the user and subuser.</cd-helper>
-            </div>
-          </div>
-        </div>
+    <!-- Suspended -->
+    <div class="form-item">
+      <cds-checkbox formControlName="suspended"
+                    id="suspended"
+                    i18n>Suspended
+        <cd-help-text>Suspending the user disables the user and subuser.</cd-help-text>
+      </cds-checkbox>
+    </div>
 
-        <!-- System User -->
-        <div class="form-group row">
-          <div class="cd-col-form-offset">
-            <div class="custom-control custom-checkbox">
-              <input class="custom-control-input"
-                     id="system"
-                     type="checkbox"
-                     formControlName="system">
-              <label class="custom-control-label spacing-03"
-                     for="system"
-                     i18n>System user</label>
-              <cd-helper i18n>System users are distinct from regular users, they are used by the RGW service to perform
-                administrative tasks, manage buckets and objects</cd-helper>
-            </div>
-          </div>
-        </div>
+    <!-- System User -->
+    <div class="form-item">
+      <cds-checkbox formControlName="system"
+                    id="system"
+                    i18n>System user
+        <cd-help-text>System users are distinct from regular users, they are used by the RGW service to perform administrative tasks, manage buckets and objects</cd-help-text>
+      </cds-checkbox>
+    </div>
 
-        <!-- S3 key -->
-        <fieldset *ngIf="!editing">
-          <legend i18n>S3 key</legend>
-
-          <!-- Auto-generate key -->
-          <div class="form-group row">
-            <div class="cd-col-form-offset">
-              <div class="custom-control custom-checkbox">
-                <input class="custom-control-input"
-                       id="generate_key"
-                       type="checkbox"
-                       formControlName="generate_key">
-                <label class="custom-control-label spacing-03"
-                       for="generate_key"
-                       i18n>Auto-generate key</label>
-              </div>
-            </div>
-          </div>
+    <!-- S3 key -->
+    <fieldset *ngIf="!editing">
+      <legend i18n
+              class="cd-header">S3 key</legend>
 
-          <!-- Access key -->
-          <div class="form-group row"
-               *ngIf="!editing && !userForm.getValue('generate_key')">
-            <label class="cd-col-form-label required"
-                   for="access_key"
-                   i18n>Access key</label>
-            <div class="cd-col-form-input">
-              <div class="input-group">
-                <input id="access_key"
-                       class="form-control"
-                       type="password"
-                       formControlName="access_key">
-                <button type="button"
-                        class="btn btn-light"
-                        cdPasswordButton="access_key">
-                </button>
-                <cd-copy-2-clipboard-button source="access_key">
-                </cd-copy-2-clipboard-button>
-              </div>
-              <span class="invalid-feedback"
-                    *ngIf="userForm.showError('access_key', frm, 'required')"
-                    i18n>This field is required.</span>
-            </div>
-          </div>
+      <!-- Auto-generate key -->
+      <div class="form-item">
+        <cds-checkbox formControlName="generate_key"
+                      id="generate_key"
+                      i18n>Auto-generate key</cds-checkbox>
+      </div>
 
-          <!-- Secret key -->
-          <div class="form-group row"
-               *ngIf="!editing && !userForm.getValue('generate_key')">
-            <label class="cd-col-form-label required"
-                   for="secret_key"
-                   i18n>Secret key</label>
-            <div class="cd-col-form-input">
-              <div class="input-group">
-                <input id="secret_key"
-                       class="form-control"
-                       type="password"
-                       formControlName="secret_key">
-                <button type="button"
-                        class="btn btn-light"
-                        cdPasswordButton="secret_key">
-                </button>
-                <cd-copy-2-clipboard-button source="secret_key">
-                </cd-copy-2-clipboard-button>
-              </div>
-              <span class="invalid-feedback"
-                    *ngIf="userForm.showError('secret_key', frm, 'required')"
-                    i18n>This field
-                is required.</span>
+      <!-- Access key -->
+      <div class="form-item form-item-append"
+           *ngIf="!editing && !userForm.getValue('generate_key')">
+        <cds-password-label for="access_key"
+                            i18n
+                            cdRequiredField="Access key"
+                            [invalid]="userForm.controls.access_key.invalid && (userForm.controls.access_key.dirty)"
+                            [invalidText]="accessKeyError">Access key
+          <input cdsPassword
+                 type="password"
+                 formControlName="access_key"
+                 name="access_key"
+                 id="access_key"
+                 [invalid]="userForm.controls.access_key.invalid && (userForm.controls.access_key.dirty)"/>
+        </cds-password-label>
+        <cd-copy-2-clipboard-button source="access_key"
+                                    class="mt-4">
+        </cd-copy-2-clipboard-button>
+        <ng-template #accessKeyError>
+          <span class="invalid-feedback"
+                *ngIf="userForm.showError('access_key', frm, 'required')"
+                i18n>This field is required.</span>
+        </ng-template>
+      </div>
+
+      <!-- Secret key -->
+      <div class="form-item form-item-append"
+           *ngIf="!editing && !userForm.getValue('generate_key')">
+        <cds-password-label for="secret"
+                            i18n
+                            cdRequiredField="Secret key"
+                            [invalid]="userForm.controls.secret_key.invalid && (userForm.controls.secret_key.dirty)"
+                            [invalidText]="secretKeyError">Secret key
+          <input cdsPassword
+                 type="password"
+                 formControlName="secret_key"
+                 name="secret_key"
+                 id="secret_key"
+                 [invalid]="userForm.controls.secret_key.invalid && (userForm.controls.secret_key.dirty)"/>
+        </cds-password-label>
+        <cd-copy-2-clipboard-button source="secret_key"
+                                    class="mt-4">
+        </cd-copy-2-clipboard-button>
+        <ng-template #secretKeyError>
+          <span class="invalid-feedback"
+                *ngIf="userForm.showError('secret_key', frm, 'required')"
+                i18n>This field is required.</span>
+        </ng-template>
+      </div>
+    </fieldset>
+
+    <!-- Subusers -->
+    <fieldset *ngIf="editing">
+      <div class="form-item">
+      <legend i18n
+              class="cd-header">Subusers</legend>
+      <span *ngIf="subusers.length === 0"
+            class="no-border">
+        <span class="form-text text-muted"
+              i18n>There are no subusers.</span>
+      </span>
+
+      <ng-container *ngFor="let subuser of subusers; let i=index;">
+
+      <div cdsRow
+           class="form-item-append"
+           [ngClass]="{'form-item': i > 0}">
+        <div cdsCol>
+          <cds-text-label>Subuser id
+            <input cdsText
+                   [value]="subuser.id"
+                   readonly>
+          </cds-text-label>
+        </div>
+        <div cdsCol>
+          <cds-text-label>Permissions
+            <input cdsText
+                   [value]="('full-control' === subuser.permissions) ? 'full' : subuser.permissions"
+                   readonly>
+          </cds-text-label>
+        </div>
+        <cds-icon-button kind="tertiary"
+                         size="md"
+                         title="Edit"
+                         class="mt-4 tc_showSubuserButton"
+                         (click)="showSubuserModal(i)">
+          <svg cdsIcon="edit"
+               size="32"
+               class="cds--btn__icon"></svg>
+        </cds-icon-button>
+        <cds-icon-button kind="danger"
+                         size="md"
+                         title="Delete"
+                         class="mt-4 tc_deleteSubuserButton"
+                         (click)="deleteSubuser(i)">
+          <svg cdsIcon="trash-can"
+               size="32"
+               class="cds--btn__icon"></svg>
+        </cds-icon-button>
+        </div>
+      </ng-container>
+      <div cdsRow
+           class="form-item">
+        <div cdsCol>
+          <button cdsButton="tertiary"
+                  type="button"
+                  class="tc_addSubuserButton"
+                  (click)="showSubuserModal()">
+            <ng-container i18n>{{ actionLabels.CREATE | titlecase }}
+              {{ subuserLabel | upperFirst }}</ng-container>
+            <svg cdsIcon="add"
+                 size="32"
+                 class="cds--btn__icon"></svg>
+          </button>
+        </div>
+      </div>
+      </div>
+    </fieldset>
+
+    <!-- Keys -->
+    <fieldset *ngIf="editing">
+      <div class="form-item">
+        <legend i18n
+                class="cd-header">Keys</legend>
+
+        <!-- S3 keys -->
+        <h6>S3</h6>
+        <span *ngIf="s3Keys.length === 0"
+              class="no-border">
+          <span class="form-text text-muted"
+                i18n>There are no keys.</span>
+        </span>
+
+        <span *ngFor="let key of s3Keys; let i=index;">
+          <div class="form-item-append"
+               cdsRow>
+            <div cdsCol>
+              <cds-text-label>
+                <input cdsText
+                       [value]="key.user"
+                       readonly>
+              </cds-text-label>
             </div>
+            <cds-icon-button kind="tertiary"
+                             size="md"
+                             title="Show"
+                             class="mt-2 tc_showS3KeyButton"
+                             (click)="showS3KeyModal(i)">
+              <svg cdsIcon="view"
+                   size="16"
+                   class="cds--btn__icon"></svg>
+            </cds-icon-button>
+            <cds-icon-button kind="danger"
+                             size="md"
+                             title="Delete"
+                             class="mt-2 tc_deleteS3KeyButton"
+                             (click)="deleteS3Key(i)">
+              <svg cdsIcon="trash-can"
+                   size="32"
+                   class="cds--btn__icon"></svg>
+            </cds-icon-button>
           </div>
-        </fieldset>
-
-        <!-- Subusers -->
-        <fieldset *ngIf="editing">
-          <legend i18n>Subusers</legend>
-          <div class="row">
-            <div class="cd-col-form-offset">
-              <span *ngIf="subusers.length === 0"
-                    class="no-border">
-                <span class="form-text text-muted"
-                      i18n>There are no subusers.</span>
-              </span>
-
-              <span *ngFor="let subuser of subusers; let i=index;">
-                <div class="input-group">
-                  <span class="input-group-text">
-                    <i class="{{ icons.user }}"></i>
-                  </span>
-                  <input type="text"
-                         class="cd-form-control"
-                         value="{{ subuser.id }}"
-                         readonly>
-                  <span class="input-group-text">
-                    <i class="{{ icons.share }}"></i>
-                  </span>
-                  <input type="text"
-                         class="cd-form-control"
-                         value="{{ ('full-control' === subuser.permissions) ? 'full' : subuser.permissions }}"
-                         readonly>
-                  <button type="button"
-                          class="btn btn-light tc_showSubuserButton"
-                          i18n-ngbTooltip
-                          ngbTooltip="Edit"
-                          (click)="showSubuserModal(i)">
-                    <i [ngClass]="[icons.edit]"></i>
-                  </button>
-                  <button type="button"
-                          class="btn btn-light tc_deleteSubuserButton"
-                          i18n-ngbTooltip
-                          ngbTooltip="Delete"
-                          (click)="deleteSubuser(i)">
-                    <i [ngClass]="[icons.destroy]"></i>
-                  </button>
-                </div>
-                <span class="form-text text-muted"></span>
-              </span>
-
-              <div class="row my-2">
-                <div class="col-12">
-                  <button type="button"
-                          class="btn btn-light float-end tc_addSubuserButton"
-                          (click)="showSubuserModal()">
-                    <i [ngClass]="[icons.add]"></i>
-                    <ng-container i18n>{{ actionLabels.CREATE | titlecase }}
-                      {{ subuserLabel | upperFirst }}</ng-container>
-                  </button>
-                </div>
-              </div>
-              <span class="help-block"></span>
-            </div>
+          <span class="form-text text-muted"></span>
+        </span>
+
+        <div class="form-item"
+             cdsRow>
+          <div cdsCol>
+            <button type="button"
+                    cdsButton="tertiary"
+                    class="tc_addS3KeyButton"
+                    (click)="showS3KeyModal()">
+              <ng-container i18n>{{ actionLabels.CREATE | titlecase }}
+                {{ s3keyLabel | upperFirst }}</ng-container>
+              <svg cdsIcon="add"
+                   size="32"
+                   class="cds--btn__icon"></svg>
+            </button>
           </div>
-        </fieldset>
-
-        <!-- Keys -->
-        <fieldset *ngIf="editing">
-          <legend i18n>Keys</legend>
-
-          <!-- S3 keys -->
-          <div class="form-group row">
-            <label class="cd-col-form-label"
-                   i18n>S3</label>
-            <div class="cd-col-form-input">
-              <span *ngIf="s3Keys.length === 0"
-                    class="no-border">
-                <span class="form-text text-muted"
-                      i18n>There are no keys.</span>
-              </span>
-
-              <span *ngFor="let key of s3Keys; let i=index;">
-                <div class="input-group">
-                  <div class="input-group-text">
-                    <i class="{{ icons.key }}"></i>
-                  </div>
-                  <input type="text"
-                         class="cd-form-control"
-                         value="{{ key.user }}"
-                         readonly>
-                  <button type="button"
-                          class="btn btn-light tc_showS3KeyButton"
-                          i18n-ngbTooltip
-                          ngbTooltip="Show"
-                          (click)="showS3KeyModal(i)">
-                    <i [ngClass]="[icons.show]"></i>
-                  </button>
-                  <button type="button"
-                          class="btn btn-light tc_deleteS3KeyButton"
-                          i18n-ngbTooltip
-                          ngbTooltip="Delete"
-                          (click)="deleteS3Key(i)">
-                    <i [ngClass]="[icons.destroy]"></i>
-                  </button>
-                </div>
-                <span class="form-text text-muted"></span>
-              </span>
-
-              <div class="row my-2">
-                <div class="col-12">
-                  <button type="button"
-                          class="btn btn-light float-end tc_addS3KeyButton"
-                          (click)="showS3KeyModal()">
-                    <i [ngClass]="[icons.add]"></i>
-                    <ng-container i18n>{{ actionLabels.CREATE | titlecase }}
-                      {{ s3keyLabel | upperFirst }}</ng-container>
-                  </button>
-                </div>
-              </div>
-
-              <span class="help-block"></span>
-            </div>
+        </div>
 
-            <hr>
-          </div>
+        <hr>
+      </div>
 
-          <!-- Swift keys -->
-          <div class="form-group row">
-            <label class="cd-col-form-label"
-                   i18n>Swift</label>
-
-            <div class="cd-col-form-input">
-              <span *ngIf="swiftKeys.length === 0"
-                    class="no-border">
-                <span class="form-text text-muted"
-                      i18n>There are no keys.</span>
-              </span>
-
-              <span *ngFor="let key of swiftKeys; let i=index;">
-                <div class="input-group">
-                  <span class="input-group-text">
-                    <i class="{{ icons.key }}"></i>
-                  </span>
-                  <input type="text"
-                         class="cd-form-control"
-                         value="{{ key.user }}"
-                         readonly>
-                  <button type="button"
-                          class="btn btn-light tc_showSwiftKeyButton"
-                          i18n-ngbTooltip
-                          ngbTooltip="Show"
-                          (click)="showSwiftKeyModal(i)">
-                    <i [ngClass]="[icons.show]"></i>
-                  </button>
-                </div>
-                <span class="form-text text-muted"></span>
-              </span>
+      <!-- Swift keys -->
+      <div class="form-item">
+
+        <h6>Swift</h6>
+        <span *ngIf="swiftKeys.length === 0"
+              class="no-border">
+          <span class="form-text text-muted"
+                i18n>There are no keys.</span>
+        </span>
+
+        <span *ngFor="let key of swiftKeys; let i=index;">
+          <div class="form-item-append"
+               cdsRow>
+            <div cdsCol>
+              <cds-text-label>
+                <input cdsText
+                       [value]="key.user"
+                       readonly>
+              </cds-text-label>
             </div>
+            <cds-icon-button kind="tertiary"
+                             size="md"
+                             title="Show"
+                             class="mt-2 tc_showSwiftKeyButton"
+                             (click)="showSwiftKeyModal(i)">
+              <svg cdsIcon="view"
+                   size="16"
+                   class="cds--btn__icon"></svg>
+            </cds-icon-button>
           </div>
-        </fieldset>
-
-        <!-- Capabilities -->
-        <fieldset *ngIf="editing">
-          <legend i18n>Capabilities</legend>
-
-          <div class="form-group row">
-            <div class="cd-col-form-offset">
-              <span *ngIf="capabilities.length === 0"
-                    class="no-border">
-                <span class="form-text text-muted"
-                      i18n>There are no capabilities.</span>
-              </span>
-
-              <span *ngFor="let cap of capabilities; let i=index;">
-                <div class="input-group">
-                  <div class="input-group-text">
-                    <i class="{{ icons.share }}"></i>
-                  </div>
-                  <input type="text"
-                         class="cd-form-control"
-                         value="{{ cap.type }}:{{ cap.perm }}"
-                         readonly>
-                  <button type="button"
-                          class="btn btn-light tc_editCapButton"
-                          i18n-ngbTooltip
-                          ngbTooltip="Edit"
-                          (click)="showCapabilityModal(i)">
-                    <i [ngClass]="[icons.edit]"></i>
-                  </button>
-                  <button type="button"
-                          class="btn btn-light tc_deleteCapButton"
-                          i18n-ngbTooltip
-                          ngbTooltip="Delete"
-                          (click)="deleteCapability(i)">
-                    <i [ngClass]="[icons.destroy]"></i>
-                  </button>
-                </div>
-                <span class="form-text text-muted"></span>
-              </span>
-
-              <div class="row my-2">
-                <div class="col-12">
-                  <button type="button"
-                          class="btn btn-light float-end tc_addCapButton"
-                          [disabled]="capabilities | pipeFunction:hasAllCapabilities"
-                          i18n-ngbTooltip
-                          ngbTooltip="All capabilities are already added."
-                          [disableTooltip]="!(capabilities | pipeFunction:hasAllCapabilities)"
-                          triggers="pointerenter:pointerleave"
-                          (click)="showCapabilityModal()">
-                    <i [ngClass]="[icons.add]"></i>
-                    <ng-container i18n>{{ actionLabels.ADD | titlecase }}
-                      {{ capabilityLabel | upperFirst }}</ng-container>
-                  </button>
-                </div>
-              </div>
-              <span class="help-block"></span>
+        </span>
+      </div>
+    </fieldset>
+
+    <!-- Capabilities -->
+    <fieldset *ngIf="editing">
+
+      <div class="form-item">
+        <legend i18n
+                class="cd-header">Capabilities</legend>
+        <span *ngIf="capabilities.length === 0"
+              class="no-border">
+          <span class="form-text text-muted"
+                i18n>There are no capabilities.</span>
+        </span>
+
+        <span *ngFor="let cap of capabilities; let i=index;">
+          <div class="form-item-append"
+               [ngClass]="{'form-item': i > 0}"
+               cdsRow>
+            <div cdsCol>
+              <cds-text-label i18n>Type
+                <input cdsText
+                       [value]="cap.type"
+                       readonly>
+              </cds-text-label>
             </div>
-          </div>
-        </fieldset>
-
-        <!-- User quota -->
-        <fieldset>
-          <legend i18n>User quota</legend>
-
-          <!-- Enabled -->
-          <div class="form-group row">
-            <div class="cd-col-form-offset">
-              <div class="custom-control custom-checkbox">
-                <input class="custom-control-input"
-                       id="user_quota_enabled"
-                       type="checkbox"
-                       formControlName="user_quota_enabled">
-                <label class="custom-control-label spacing-03"
-                       for="user_quota_enabled"
-                       i18n>Enabled</label>
-              </div>
+            <div cdsCol>
+              <cds-text-label i18n>Permission
+                <input cdsText
+                       [value]="cap.perm"
+                       readonly>
+              </cds-text-label>
             </div>
+            <cds-icon-button kind="tertiary"
+                             size="md"
+                             title="Edit"
+                             class="mt-4 tc_editCapButton"
+                             (click)="showCapabilityModal(i)">
+              <svg cdsIcon="edit"
+                   size="32"
+                   class="cds--btn__icon"></svg>
+            </cds-icon-button>
+            <cds-icon-button kind="danger"
+                             size="md"
+                             title="Delete"
+                             class="mt-4 tc_deleteCapButton"
+                             (click)="deleteCapability(i)">
+              <svg cdsIcon="trash-can"
+                   size="32"
+                   class="cds--btn__icon"></svg>
+            </cds-icon-button>
           </div>
-
-          <!-- Unlimited size -->
-          <div class="form-group row"
-               *ngIf="userForm.controls.user_quota_enabled.value">
-            <div class="cd-col-form-offset">
-              <div class="custom-control custom-checkbox">
-                <input class="custom-control-input"
-                       id="user_quota_max_size_unlimited"
-                       type="checkbox"
-                       formControlName="user_quota_max_size_unlimited">
-                <label class="custom-control-label spacing-03"
-                       for="user_quota_max_size_unlimited"
-                       i18n>Unlimited size</label>
-              </div>
-            </div>
+          <span class="form-text text-muted"></span>
+        </span>
+
+        <div class="form-item"
+             cdsRow>
+          <div cdsCol>
+            <button type="button"
+                    class="tc_addCapButton"
+                    cdsButton="tertiary"
+                    [disabled]="capabilities | pipeFunction:hasAllCapabilities"
+                    i18n-ngbTooltip
+                    ngbTooltip="All capabilities are already added."
+                    [disableTooltip]="!(capabilities | pipeFunction:hasAllCapabilities)"
+                    triggers="pointerenter:pointerleave"
+                    (click)="showCapabilityModal()">
+              <ng-container i18n>{{ actionLabels.ADD | titlecase }}
+                {{ capabilityLabel | upperFirst }}</ng-container>
+              <svg cdsIcon="add"
+                   size="32"
+                   class="cds--btn__icon"></svg>
+            </button>
           </div>
+        </div>
+      </div>
+    </fieldset>
+
+    <!-- User quota -->
+    <fieldset>
+      <div class="form-item">
+        <legend i18n
+                class="cd-header">User quota</legend>
+
+      <!-- Enabled -->
+        <cds-checkbox formControlName="user_quota_enabled"
+                      id="user_quota_enabled"
+                      i18n>Enabled
+        </cds-checkbox>
+      </div>
 
-          <!-- Maximum size -->
-          <div class="form-group row"
-               *ngIf="userForm.controls.user_quota_enabled.value && !userForm.getValue('user_quota_max_size_unlimited')">
-            <label class="cd-col-form-label required"
-                   for="user_quota_max_size"
-                   i18n>Max. size</label>
-            <div class="cd-col-form-input">
-              <input id="user_quota_max_size"
-                     class="form-control"
-                     type="text"
-                     formControlName="user_quota_max_size"
-                     cdDimlessBinary>
-              <span class="invalid-feedback"
-                    *ngIf="userForm.showError('user_quota_max_size', frm, 'required')"
-                    i18n>This field is required.</span>
-              <span class="invalid-feedback"
-                    *ngIf="userForm.showError('user_quota_max_size', frm, 'quotaMaxSize')"
-                    i18n>The value is not valid.</span>
-              <span *ngIf="userForm.showError('user_quota_max_size', formDir, 'pattern')"
-                    class="invalid-feedback"
-                    i18n>Size must be a number or in a valid format. eg: 5 GiB</span>
-            </div>
-          </div>
+      <!-- Unlimited size -->
+      <div class="form-item"
+           *ngIf="userForm.controls.user_quota_enabled.value">
+        <cds-checkbox formControlName="user_quota_max_size_unlimited"
+                      id="user_quota_max_size_unlimited"
+                      i18n>Unlimited size</cds-checkbox>
+      </div>
 
-          <!-- Unlimited objects -->
-          <div class="form-group row"
-               *ngIf="userForm.controls.user_quota_enabled.value">
-            <div class="cd-col-form-offset">
-              <div class="custom-control custom-checkbox">
-                <input class="custom-control-input"
-                       id="user_quota_max_objects_unlimited"
-                       type="checkbox"
-                       formControlName="user_quota_max_objects_unlimited">
-                <label class="custom-control-label spacing-03"
-                       for="user_quota_max_objects_unlimited"
-                       i18n>Unlimited objects</label>
-              </div>
-            </div>
-          </div>
+      <!-- Maximum size -->
+      <div class="form-item"
+           *ngIf="userForm.controls.user_quota_enabled.value && !userForm.getValue('user_quota_max_size_unlimited')">
+        <cds-text-label for="user_quota_max_size"
+                        i18n
+                        cdRequiredField="Maximum size"
+                        [invalid]="userForm.controls.user_quota_max_size.invalid && (userForm.controls.user_quota_max_size.dirty)"
+                        [invalidText]="quotaMaxSizeError">Maximum size
+          <input cdsText
+                 formControlName="user_quota_max_size"
+                 name="user_quota_max_size"
+                 id="user_quota_max_size"
+                 [invalid]="userForm.controls.user_quota_max_size.invalid && (userForm.controls.user_quota_max_size.dirty)"
+                 cdDimlessBinary/>
+        </cds-text-label>
+        <ng-template #quotaMaxSizeError>
+          <span class="invalid-feedback"
+                *ngIf="userForm.showError('user_quota_max_size', frm, 'required')"
+                i18n>This field is required.</span>
+          <span class="invalid-feedback"
+                *ngIf="userForm.showError('user_quota_max_size', frm, 'quotaMaxSize')"
+                i18n>The value is not valid.</span>
+          <span *ngIf="userForm.showError('user_quota_max_size', formDir, 'pattern')"
+                class="invalid-feedback"
+                i18n>Size must be a number or in a valid format. eg: 5 GiB</span>
+        </ng-template>
+      </div>
 
-          <!-- Maximum objects -->
-          <div class="form-group row"
-               *ngIf="userForm.controls.user_quota_enabled.value && !userForm.getValue('user_quota_max_objects_unlimited')">
-            <label class="cd-col-form-label required"
-                   for="user_quota_max_objects"
-                   i18n>Max. objects</label>
-            <div class="cd-col-form-input">
-              <input id="user_quota_max_objects"
-                     class="form-control"
-                     type="number"
-                     formControlName="user_quota_max_objects"
-                     min="0">
-              <span class="invalid-feedback"
-                    *ngIf="userForm.showError('user_quota_max_objects', frm, 'required')"
-                    i18n>This field is required.</span>
-              <span class="invalid-feedback"
-                    *ngIf="userForm.showError('user_quota_max_objects', frm, 'min')"
-                    i18n>The entered value must be >= 0.</span>
-            </div>
-          </div>
-        </fieldset>
-
-        <!-- Bucket quota -->
-        <fieldset>
-          <legend i18n>Bucket quota</legend>
-
-          <!-- Enabled -->
-          <div class="form-group row">
-            <div class="cd-col-form-offset">
-              <div class="custom-control custom-checkbox">
-                <input class="custom-control-input"
-                       id="bucket_quota_enabled"
-                       type="checkbox"
-                       formControlName="bucket_quota_enabled">
-                <label class="custom-control-label spacing-03"
-                       for="bucket_quota_enabled"
-                       i18n>Enabled</label>
-              </div>
-            </div>
-          </div>
+      <!-- Unlimited objects -->
+      <div class="form-item"
+           *ngIf="userForm.controls.user_quota_enabled.value">
+        <cds-checkbox formControlName="user_quota_max_objects_unlimited"
+                      id="user_quota_max_objects_unlimited"
+                      i18n>Unlimited objects</cds-checkbox>
+      </div>
 
-          <!-- Unlimited size -->
-          <div class="form-group row"
-               *ngIf="userForm.controls.bucket_quota_enabled.value">
-            <div class="cd-col-form-offset">
-              <div class="custom-control custom-checkbox">
-                <input class="custom-control-input"
-                       id="bucket_quota_max_size_unlimited"
-                       type="checkbox"
-                       formControlName="bucket_quota_max_size_unlimited">
-                <label class="custom-control-label spacing-03"
-                       for="bucket_quota_max_size_unlimited"
-                       i18n>Unlimited size</label>
-              </div>
-            </div>
-          </div>
+      <!-- Maximum objects -->
+      <div class="form-item"
+           *ngIf="userForm.controls.user_quota_enabled.value && !userForm.getValue('user_quota_max_objects_unlimited')">
+        <cds-number for="user_quota_max_objects"
+                    formControlName="user_quota_max_objects"
+                    id="user_quota_max_objects"
+                    [min]="0"
+                    [invalid]="userForm.controls.user_quota_max_objects.invalid && (userForm.controls.user_quota_max_objects.dirty)"
+                    [invalidText]="maxObjectsError"
+                    label="Maximum Objects"
+                    cdRequiredField="Maximum Objects"></cds-number>
+        <ng-template #maxObjectsError>
+          <span class="invalid-feedback"
+                *ngIf="userForm.showError('user_quota_max_objects', frm, 'required')"
+                i18n>This field is required.</span>
+          <span class="invalid-feedback"
+                *ngIf="userForm.showError('user_quota_max_objects', frm, 'min')"
+                i18n>The entered value must be >= 0.</span>
+        </ng-template>
+      </div>
+    </fieldset>
+
+    <!-- Bucket quota -->
+    <fieldset>
+      <div class="form-item">
+        <legend i18n
+                class="cd-header">Bucket quota</legend>
+
+      <!-- Enabled -->
+        <cds-checkbox formControlName="bucket_quota_enabled"
+                      id="bucket_quota_enabled"
+                      i18n>Enabled</cds-checkbox>
+      </div>
 
-          <!-- Maximum size -->
-          <div class="form-group row"
-               *ngIf="userForm.controls.bucket_quota_enabled.value && !userForm.getValue('bucket_quota_max_size_unlimited')">
-            <label class="cd-col-form-label required"
-                   for="bucket_quota_max_size"
-                   i18n>Max. size</label>
-            <div class="cd-col-form-input">
-              <input id="bucket_quota_max_size"
-                     class="form-control"
-                     type="text"
-                     formControlName="bucket_quota_max_size"
-                     cdDimlessBinary>
-              <span class="invalid-feedback"
-                    *ngIf="userForm.showError('bucket_quota_max_size', frm, 'required')"
-                    i18n>This field is required.</span>
-              <span class="invalid-feedback"
-                    *ngIf="userForm.showError('bucket_quota_max_size', frm, 'quotaMaxSize')"
-                    i18n>The value is not valid.</span>
-              <span *ngIf="userForm.showError('bucket_quota_max_size', formDir, 'pattern')"
-                    class="invalid-feedback"
-                    i18n>Size must be a number or in a valid format. eg: 5 GiB</span>
-            </div>
-          </div>
+      <!-- Unlimited size -->
+      <div class="form-item"
+           *ngIf="userForm.controls.bucket_quota_enabled.value">
+        <cds-checkbox formControlName="bucket_quota_max_size_unlimited"
+                      id="bucket_quota_max_size_unlimited"
+                      i18n>Unlimited size</cds-checkbox>
+      </div>
 
-          <!-- Unlimited objects -->
-          <div class="form-group row"
-               *ngIf="userForm.controls.bucket_quota_enabled.value">
-            <div class="cd-col-form-offset">
-              <div class="custom-control custom-checkbox">
-                <input class="custom-control-input"
-                       id="bucket_quota_max_objects_unlimited"
-                       type="checkbox"
-                       formControlName="bucket_quota_max_objects_unlimited">
-                <label class="custom-control-label spacing-03"
-                       for="bucket_quota_max_objects_unlimited"
-                       i18n>Unlimited objects</label>
-              </div>
-            </div>
-          </div>
+      <!-- Maximum size -->
+      <div class="form-item"
+           *ngIf="userForm.controls.bucket_quota_enabled.value && !userForm.getValue('bucket_quota_max_size_unlimited')">
+        <cds-text-label for="bucket_quota_max_size"
+                        i18n
+                        cdRequiredField="Maximum size"
+                        [invalid]="userForm.controls.bucket_quota_max_size.invalid && (userForm.controls.bucket_quota_max_size.dirty)"
+                        [invalidText]="bucketQuotaMaxSizeError">Maximum size
+          <input cdsText
+                 formControlName="bucket_quota_max_size"
+                 name="bucket_quota_max_size"
+                 id="bucket_quota_max_size"
+                 [invalid]="userForm.controls.bucket_quota_max_size.invalid && (userForm.controls.bucket_quota_max_size.dirty)"
+                 cdDimlessBinary/>
+        </cds-text-label>
+        <ng-template #bucketQuotaMaxSizeError>
+          <span class="invalid-feedback"
+                *ngIf="userForm.showError('bucket_quota_max_size', frm, 'required')"
+                i18n>This field is required.</span>
+          <span class="invalid-feedback"
+                *ngIf="userForm.showError('bucket_quota_max_size', frm, 'quotaMaxSize')"
+                i18n>The value is not valid.</span>
+          <span *ngIf="userForm.showError('bucket_quota_max_size', formDir, 'pattern')"
+                class="invalid-feedback"
+                i18n>Size must be a number or in a valid format. eg: 5 GiB</span>
+        </ng-template>
+      </div>
 
-          <!-- Maximum objects -->
-          <div class="form-group row"
-               *ngIf="userForm.controls.bucket_quota_enabled.value && !userForm.getValue('bucket_quota_max_objects_unlimited')">
-            <label class="cd-col-form-label required"
-                   for="bucket_quota_max_objects"
-                   i18n>Max. objects</label>
-            <div class="cd-col-form-input">
-              <input id="bucket_quota_max_objects"
-                     class="form-control"
-                     type="number"
-                     formControlName="bucket_quota_max_objects"
-                     min="0">
-              <span class="invalid-feedback"
-                    *ngIf="userForm.showError('bucket_quota_max_objects', frm, 'required')"
-                    i18n>This field is required.</span>
-              <span class="invalid-feedback"
-                    *ngIf="userForm.showError('bucket_quota_max_objects', frm, 'min')"
-                    i18n>Enter a positive number.</span>
-            </div>
-          </div>
-        </fieldset>
-
-      <!-- Advanced Section -->
-      <cd-form-advanced-fieldset>
-        <!-- User Rate Limit -->
-        <cd-rgw-rate-limit [type]="'user'"
-                           [isEditing]="this.editing"
-                           [id]="uid"
-                           (rateLimitFormGroup)="rateLimitFormInit($event)">
-        </cd-rgw-rate-limit>
-      </cd-form-advanced-fieldset>
+      <!-- Unlimited objects -->
+      <div class="form-item"
+           *ngIf="userForm.controls.bucket_quota_enabled.value">
+        <cds-checkbox formControlName="bucket_quota_max_objects_unlimited"
+                      id="bucket_quota_max_objects_unlimited"
+                      i18n>Unlimited objects</cds-checkbox>
       </div>
 
-      <div class="card-footer">
-        <cd-form-button-panel (submitActionEvent)="onSubmit()"
-                              [form]="userForm"
-                              [submitText]="(action | titlecase) + ' ' + (resource | upperFirst)"
-                              wrappingClass="text-right"></cd-form-button-panel>
+      <!-- Maximum objects -->
+      <div class="form-item"
+           *ngIf="userForm.controls.bucket_quota_enabled.value && !userForm.getValue('bucket_quota_max_objects_unlimited')">
+        <cds-number for="bucket_quota_max_objects"
+                    formControlName="bucket_quota_max_objects"
+                    id="bucket_quota_max_objects"
+                    [min]="0"
+                    [invalid]="userForm.controls.bucket_quota_max_objects.invalid && ( userForm.controls.bucket_quota_max_objects.dirty)"
+                    [invalidText]="bucketMaxObjectsError"
+                    label="Maximum objects"
+                    cdRequiredField="Maximum objects"></cds-number>
+        <ng-template #bucketMaxObjectsError>
+          <span class="invalid-feedback"
+                *ngIf="userForm.showError('bucket_quota_max_objects', frm, 'required')"
+                i18n>This field is required.</span>
+          <span class="invalid-feedback"
+                *ngIf="userForm.showError('bucket_quota_max_objects', frm, 'min')"
+                i18n>The entered value must be >= 0.</span>
+        </ng-template>
       </div>
-    </div>
+    </fieldset>
+
+    <!-- Advanced Section -->
+    <cd-form-advanced-fieldset>
+      <!-- User Rate Limit -->
+      <cd-rgw-rate-limit [type]="'user'"
+                         [isEditing]="this.editing"
+                         [id]="uid"
+                         (rateLimitFormGroup)="rateLimitFormInit($event)">
+      </cd-rgw-rate-limit>
+    </cd-form-advanced-fieldset>
+
+    <cd-form-button-panel (submitActionEvent)="onSubmit()"
+                          [form]="userForm"
+                          [submitText]="(action | titlecase) + ' ' + (resource | upperFirst)"
+                          wrappingClass="text-right"></cd-form-button-panel>
   </form>
 </div>
index 15b9e29320d1885b10f4daf118918504b6b647c9..933c0094823c4c0f05ad4d7277ede50218c66e95 100644 (file)
@@ -22,6 +22,7 @@ import { DUE_TIMER } from '~/app/shared/forms/cd-validators';
 import { FormatterService } from '~/app/shared/services/formatter.service';
 import { RgwRateLimitComponent } from '../rgw-rate-limit/rgw-rate-limit.component';
 import { By } from '@angular/platform-browser';
+import { CheckboxModule, NumberModule, SelectModule } from 'carbon-components-angular';
 
 describe('RgwUserFormComponent', () => {
   let component: RgwUserFormComponent;
@@ -39,7 +40,10 @@ describe('RgwUserFormComponent', () => {
       SharedModule,
       ToastrModule.forRoot(),
       NgbTooltipModule,
-      PipesModule
+      PipesModule,
+      CheckboxModule,
+      NumberModule,
+      SelectModule
     ]
   });
 
@@ -408,12 +412,10 @@ describe('RgwUserFormComponent', () => {
   it('should call showCapabilityModal', () => {
     const modalShowSpy = spyOn(component['modalService'], 'show').and.callFake(() => {
       modalRef = {
-        componentInstance: {
-          setEditing: jest.fn(),
-          setValues: jest.fn(),
-          setCapabilities: jest.fn(),
-          submitAction: { subscribe: jest.fn() }
-        }
+        setEditing: jest.fn(),
+        setValues: jest.fn(),
+        setCapabilities: jest.fn(),
+        submitAction: { subscribe: jest.fn() }
       };
       return modalRef;
     });
@@ -424,9 +426,7 @@ describe('RgwUserFormComponent', () => {
   it('should call showSwiftKeyModal', () => {
     const modalShowSpy = spyOn(component['modalService'], 'show').and.callFake(() => {
       modalRef = {
-        componentInstance: {
-          setValues: jest.fn()
-        }
+        setValues: jest.fn()
       };
       return modalRef;
     });
@@ -440,12 +440,10 @@ describe('RgwUserFormComponent', () => {
   it('should call showS3KeyModal', () => {
     const modalShowSpy = spyOn(component['modalService'], 'show').and.callFake(() => {
       modalRef = {
-        componentInstance: {
-          setValues: jest.fn(),
-          setViewing: jest.fn(),
-          setUserCandidates: jest.fn(),
-          submitAction: { subscribe: jest.fn() }
-        }
+        setValues: jest.fn(),
+        setViewing: jest.fn(),
+        setUserCandidates: jest.fn(),
+        submitAction: { subscribe: jest.fn() }
       };
       return modalRef;
     });
@@ -605,13 +603,11 @@ describe('RgwUserFormComponent', () => {
   it('should call showSubuserModal', () => {
     const modalShowSpy = spyOn(component['modalService'], 'show').and.callFake(() => {
       modalRef = {
-        componentInstance: {
-          setValues: jest.fn(),
-          setViewing: jest.fn(),
-          setEditing: jest.fn(),
-          setUserCandidates: jest.fn(),
-          submitAction: { subscribe: jest.fn() }
-        }
+        setValues: jest.fn(),
+        setViewing: jest.fn(),
+        setEditing: jest.fn(),
+        setUserCandidates: jest.fn(),
+        submitAction: { subscribe: jest.fn() }
       };
       return modalRef;
     });
@@ -630,39 +626,35 @@ describe('RgwUserFormComponent', () => {
       ];
       let spy = spyOn(component['modalService'], 'show').and.callFake(() => {
         return (modalRef = {
-          componentInstance: {
-            setEditing: jest.fn(),
-            setValues: jest.fn(),
-            setCapabilities: jest.fn(),
-            setSubusers: jest.fn(),
-            setUserCandidates: jest.fn(),
-            submitAction: { subscribe: jest.fn() }
-          }
+          setEditing: jest.fn(),
+          setValues: jest.fn(),
+          setCapabilities: jest.fn(),
+          setSubusers: jest.fn(),
+          setUserCandidates: jest.fn(),
+          submitAction: { subscribe: jest.fn() }
         });
       });
       spyOn(component, 'getUID').and.returnValue('dashboard');
       component.showSubuserModal(index);
       expect(spy).toHaveBeenCalledTimes(1);
-      expect(modalRef.componentInstance.setEditing).toHaveBeenCalledTimes(1);
-      expect(modalRef.componentInstance.setValues).toHaveBeenCalledWith(
+      expect(modalRef.setEditing).toHaveBeenCalledTimes(1);
+      expect(modalRef.setValues).toHaveBeenCalledWith(
         'dashboard',
         component.subusers[index].id,
         component.subusers[index].permissions
       );
-      expect(modalRef.componentInstance.submitAction.subscribe).toHaveBeenCalled();
+      expect(modalRef.submitAction.subscribe).toHaveBeenCalled();
     });
 
     it('should handle "Add" scenario when index is not provided', () => {
       let spy = spyOn(component['modalService'], 'show').and.callFake(() => {
         return (modalRef = {
-          componentInstance: {
-            setEditing: jest.fn(),
-            setValues: jest.fn(),
-            setCapabilities: jest.fn(),
-            setSubusers: jest.fn(),
-            setUserCandidates: jest.fn(),
-            submitAction: { subscribe: jest.fn() }
-          }
+          setEditing: jest.fn(),
+          setValues: jest.fn(),
+          setCapabilities: jest.fn(),
+          setSubusers: jest.fn(),
+          setUserCandidates: jest.fn(),
+          submitAction: { subscribe: jest.fn() }
         });
       });
       component.subusers = [
@@ -671,10 +663,10 @@ describe('RgwUserFormComponent', () => {
       spyOn(component, 'getUID').and.returnValue('dashboard');
       component.showSubuserModal();
       expect(spy).toHaveBeenCalledTimes(1);
-      expect(modalRef.componentInstance.setEditing).toHaveBeenCalledWith(false);
-      expect(modalRef.componentInstance.setValues).toHaveBeenCalledWith('dashboard');
-      expect(modalRef.componentInstance.setSubusers).toHaveBeenCalledWith(component.subusers);
-      expect(modalRef.componentInstance.submitAction.subscribe).toHaveBeenCalled();
+      expect(modalRef.setEditing).toHaveBeenCalledWith(false);
+      expect(modalRef.setValues).toHaveBeenCalledWith('dashboard');
+      expect(modalRef.setSubusers).toHaveBeenCalledWith(component.subusers);
+      expect(modalRef.submitAction.subscribe).toHaveBeenCalled();
     });
   });
 });
index 5973d6a658e2c1074742f348a9774ea734f02b4b..9cf8741e3539449883695cd6a521c0f893a78c6a 100644 (file)
@@ -14,7 +14,6 @@ import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
 import { CdValidators } from '~/app/shared/forms/cd-validators';
 import { FormatterService } from '~/app/shared/services/formatter.service';
-import { ModalService } from '~/app/shared/services/modal.service';
 import { NotificationService } from '~/app/shared/services/notification.service';
 import { RgwUserCapabilities } from '../models/rgw-user-capabilities';
 import { RgwUserCapability } from '../models/rgw-user-capability';
@@ -27,6 +26,7 @@ import { RgwUserSubuserModalComponent } from '../rgw-user-subuser-modal/rgw-user
 import { RgwUserSwiftKeyModalComponent } from '../rgw-user-swift-key-modal/rgw-user-swift-key-modal.component';
 import { RgwRateLimitComponent } from '../rgw-rate-limit/rgw-rate-limit.component';
 import { RgwRateLimitConfig } from '../models/rgw-rate-limit';
+import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
 
 @Component({
   selector: 'cd-rgw-user-form',
@@ -58,7 +58,7 @@ export class RgwUserFormComponent extends CdForm implements OnInit {
     private route: ActivatedRoute,
     private router: Router,
     private rgwUserService: RgwUserService,
-    private modalService: ModalService,
+    private modalService: ModalCdsService,
     private notificationService: NotificationService,
     public actionLabels: ActionLabelsI18n
   ) {
@@ -522,15 +522,15 @@ export class RgwUserFormComponent extends CdForm implements OnInit {
     if (_.isNumber(index)) {
       // Edit
       const subuser = this.subusers[index];
-      modalRef.componentInstance.setEditing();
-      modalRef.componentInstance.setValues(uid, subuser.id, subuser.permissions);
+      modalRef.setEditing();
+      modalRef.setValues(uid, subuser.id, subuser.permissions);
     } else {
       // Add
-      modalRef.componentInstance.setEditing(false);
-      modalRef.componentInstance.setValues(uid);
-      modalRef.componentInstance.setSubusers(this.subusers);
+      modalRef.setEditing(false);
+      modalRef.setValues(uid);
+      modalRef.setSubusers(this.subusers);
     }
-    modalRef.componentInstance.submitAction.subscribe((subuser: RgwUserSubuser) => {
+    modalRef.submitAction.subscribe((subuser: RgwUserSubuser) => {
       this.setSubuser(subuser, index);
     });
   }
@@ -544,14 +544,14 @@ export class RgwUserFormComponent extends CdForm implements OnInit {
     if (_.isNumber(index)) {
       // View
       const key = this.s3Keys[index];
-      modalRef.componentInstance.setViewing();
-      modalRef.componentInstance.setValues(key.user, key.access_key, key.secret_key);
+      modalRef.setViewing();
+      modalRef.setValues(key.user, key.access_key, key.secret_key);
     } else {
       // Add
       const candidates = this._getS3KeyUserCandidates();
-      modalRef.componentInstance.setViewing(false);
-      modalRef.componentInstance.setUserCandidates(candidates);
-      modalRef.componentInstance.submitAction.subscribe((key: RgwUserS3Key) => {
+      modalRef.setViewing(false);
+      modalRef.setUserCandidates(candidates);
+      modalRef.submitAction.subscribe((key: RgwUserS3Key) => {
         this.setS3Key(key);
       });
     }
@@ -564,7 +564,7 @@ export class RgwUserFormComponent extends CdForm implements OnInit {
   showSwiftKeyModal(index: number) {
     const modalRef = this.modalService.show(RgwUserSwiftKeyModalComponent);
     const key = this.swiftKeys[index];
-    modalRef.componentInstance.setValues(key.user, key.secret_key);
+    modalRef.setValues(key.user, key.secret_key);
   }
 
   /**
@@ -576,14 +576,14 @@ export class RgwUserFormComponent extends CdForm implements OnInit {
     if (_.isNumber(index)) {
       // Edit
       const cap = this.capabilities[index];
-      modalRef.componentInstance.setEditing();
-      modalRef.componentInstance.setValues(cap.type, cap.perm);
+      modalRef.setEditing();
+      modalRef.setValues(cap.type, cap.perm);
     } else {
       // Add
-      modalRef.componentInstance.setEditing(false);
-      modalRef.componentInstance.setCapabilities(this.capabilities);
+      modalRef.setEditing(false);
+      modalRef.setCapabilities(this.capabilities);
     }
-    modalRef.componentInstance.submitAction.subscribe((cap: RgwUserCapability) => {
+    modalRef.submitAction.subscribe((cap: RgwUserCapability) => {
       this.setCapability(cap, index);
     });
   }
index 3dc7de71aae795b3f1fd272e43a281e63facbe1f..c179f379508407f2f0ecce9bdad04b7097c1f026 100644 (file)
-<cd-modal [modalRef]="activeModal">
-  <ng-container i18n="form title"
-                class="modal-title">{{ action | titlecase }} {{ resource | upperFirst }}</ng-container>
+<cds-modal size="md"
+           [open]="open"
+           [hasScrollingContent]="false"
+           (overlaySelected)="closeModal()">
+  <cds-modal-header (closeSelect)="closeModal()">
+    <ng-container i18n="form title"
+                  class="modal-title">{{ action | titlecase }} {{ resource | upperFirst }}</ng-container>
 
-  <ng-container class="modal-content">
-    <form #frm="ngForm"
-          [formGroup]="formGroup"
-          novalidate>
-      <div class="modal-body">
+    <cd-help-text [formAllFieldsRequired]="true"></cd-help-text>
+  </cds-modal-header>
 
-        <!-- Username -->
-        <div class="form-group row">
-          <label class="cd-col-form-label"
-                 [ngClass]="{'required': !viewing}"
-                 for="user"
-                 i18n>Username</label>
-          <div class="cd-col-form-input">
-            <input id="user"
-                   class="form-control"
-                   type="text"
-                   *ngIf="viewing"
-                   [readonly]="true"
-                   formControlName="user">
-            <select id="user"
-                    class="form-control"
+  <form #frm="ngForm"
+        [formGroup]="formGroup"
+        novalidate>
+    <div cdsModalContent>
+
+      <!-- Username -->
+      <div class="form-item">
+        <cds-text-label for="user"
+                        [invalid]="formGroup.controls.user.invalid && formGroup.controls.user.dirty"
+                        [invalidText]="userError"
+                        *ngIf="viewing"
+                        i18n>Username
+          <input cdsText
+                 id="user"
+                 name="user"
+                 formControlName="user"
+                 readonly
+                 [readonly]="true"
+                 modal-primary-focus>
+        </cds-text-label>
+
+        <cds-select *ngIf="!viewing"
+                    label="Username"
+                    i18n-label
+                    for="user"
                     formControlName="user"
-                    *ngIf="!viewing"
-                    autofocus>
-              <option i18n
-                      *ngIf="userCandidates !== null"
-                      [ngValue]="null">-- Select a username --</option>
-              <option *ngFor="let userCandidate of userCandidates"
-                      [value]="userCandidate">{{ userCandidate }}</option>
-            </select>
-            <span class="invalid-feedback"
-                  *ngIf="formGroup.showError('user', frm, 'required')"
-                  i18n>This field is required.</span>
-          </div>
-        </div>
+                    [invalid]="formGroup.controls.user.invalid && formGroup.controls.user.dirty"
+                    [invalidText]="userError">
+          <option value="">--- Select a username ---</option>
+          <option *ngFor="let userCandidate of userCandidates"
+                  [value]="userCandidate">{{ userCandidate }}</option>
+        </cds-select>
 
-        <!-- Auto-generate key -->
-        <div class="form-group row"
-             *ngIf="!viewing">
-          <div class="cd-col-form-offset">
-            <div class="custom-control custom-checkbox">
-              <input class="custom-control-input"
-                     id="generate_key"
-                     type="checkbox"
-                     formControlName="generate_key">
-              <label class="custom-control-label"
-                     for="generate_key"
-                     i18n>Auto-generate key</label>
-            </div>
-          </div>
-        </div>
+        <ng-template #userError>
+          <span class="invalid-feedback"
+                *ngIf="formGroup.showError('user', frm, 'required')"
+                i18n>This field is required.</span>
+        </ng-template>
+      </div>
 
-        <!-- Access key -->
-        <div class="form-group row"
-             *ngIf="!formGroup.getValue('generate_key')">
-          <label class="cd-col-form-label"
-                 [ngClass]="{'required': !viewing}"
-                 for="access_key"
-                 i18n>Access key</label>
-          <div class="cd-col-form-input">
-            <div class="input-group">
-              <input id="access_key"
-                     class="form-control"
-                     type="password"
-                     [readonly]="viewing"
-                     formControlName="access_key">
-              <button type="button"
-                      class="btn btn-light"
-                      cdPasswordButton="access_key">
-              </button>
-              <cd-copy-2-clipboard-button source="access_key">
-              </cd-copy-2-clipboard-button>
-            </div>
-            <span class="invalid-feedback"
-                  *ngIf="formGroup.showError('access_key', frm, 'required')"
-                  i18n>This field is required.</span>
-          </div>
-        </div>
+      <!-- Auto-generate key -->
+      <div class="form-item"
+           *ngIf="!viewing">
+        <cds-checkbox id="generate_key"
+                      formControlName="generate_key"
+                      i18n>Auto-generate key
+        </cds-checkbox>
+      </div>
 
-        <!-- Secret key -->
-        <div class="form-group row"
-             *ngIf="!formGroup.getValue('generate_key')">
-          <label class="cd-col-form-label"
-                 [ngClass]="{'required': !viewing}"
-                 for="secret_key"
-                 i18n>Secret key</label>
-          <div class="cd-col-form-input">
-            <div class="input-group">
-              <input id="secret_key"
-                     class="form-control"
-                     type="password"
-                     [readonly]="viewing"
-                     formControlName="secret_key">
-              <button type="button"
-                      class="btn btn-light"
-                      cdPasswordButton="secret_key">
-              </button>
-              <cd-copy-2-clipboard-button source="secret_key">
-              </cd-copy-2-clipboard-button>
-            </div>
-            <span class="invalid-feedback"
-                  *ngIf="formGroup.showError('secret_key', frm, 'required')"
-                  i18n>This field is required.</span>
-          </div>
-        </div>
+      <!-- Access key -->
+      <div class="form-item form-item-append"
+           *ngIf="!formGroup.getValue('generate_key')">
+        <cds-password-label for="access_key"
+                            [invalid]="formGroup.controls.access_key.invalid && formGroup.controls.access_key.dirty"
+                            [invalidText]="accessKeyError"
+                            i18n>Access key
+          <input cdsPassword
+                 id="access_key"
+                 name="access_key"
+                 formControlName="access_key"
+                 [readonly]="viewing">
+        </cds-password-label>
+        <cd-copy-2-clipboard-button source="access_key"
+                                    class="mt-4">
+        </cd-copy-2-clipboard-button>
 
+        <ng-template #accessKeyError>
+          <span class="invalid-feedback"
+                *ngIf="formGroup.showError('access_key', frm, 'required')"
+                i18n>This field is required.</span>
+        </ng-template>
       </div>
 
-      <div class="modal-footer">
-        <cd-form-button-panel (submitActionEvent)="onSubmit()"
-                              [form]="formGroup"
-                              [submitText]="(action | titlecase) + ' ' + (resource | upperFirst)"
-                              [showSubmit]="!viewing"></cd-form-button-panel>
+      <!-- Secret key -->
+      <div class="form-item form-item-append"
+           *ngIf="!formGroup.getValue('generate_key')">
+        <cds-password-label for="secret_key"
+                            [invalid]="formGroup.controls.secret_key.invalid && formGroup.controls.secret_key.dirty"
+                            [invalidText]="secretKeyError"
+                            i18n>Secret key
+          <input cdsPassword
+                 id="secret_key"
+                 name="secret_key"
+                 formControlName="secret_key"
+                 [invalid]="formGroup.controls.secret_key.invalid && formGroup.controls.secret_key.dirty"
+                 [readonly]="viewing">
+        </cds-password-label>
+
+        <cd-copy-2-clipboard-button source="secret_key"
+                                    class="mt-4">
+        </cd-copy-2-clipboard-button>
+
+        <ng-template #secretKeyError>
+          <span class="invalid-feedback"
+                *ngIf="formGroup.showError('secret_key', frm, 'required')"
+                i18n>This field is required.</span>
+        </ng-template>
       </div>
-    </form>
-  </ng-container>
-</cd-modal>
+
+    </div>
+
+    <cd-form-button-panel (submitActionEvent)="onSubmit()"
+                          [form]="formGroup"
+                          [submitText]="(action | titlecase) + ' ' + (resource | upperFirst)"
+                          [showSubmit]="!viewing"
+                          [modalForm]="true"></cd-form-button-panel>
+  </form>
+</cds-modal>
index 23566e87c44aebdd7934cb9293596eb451490d52..af51325ce88dfa19115431f0b70f5a769a8ea67d 100644 (file)
@@ -1,7 +1,6 @@
 import { Component, EventEmitter, Output } from '@angular/core';
 import { Validators } from '@angular/forms';
 
-import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
 import _ from 'lodash';
 
 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
@@ -9,13 +8,14 @@ import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
 import { CdValidators } from '~/app/shared/forms/cd-validators';
 import { RgwUserS3Key } from '../models/rgw-user-s3-key';
+import { BaseModal } from 'carbon-components-angular';
 
 @Component({
   selector: 'cd-rgw-user-s3-key-modal',
   templateUrl: './rgw-user-s3-key-modal.component.html',
   styleUrls: ['./rgw-user-s3-key-modal.component.scss']
 })
-export class RgwUserS3KeyModalComponent {
+export class RgwUserS3KeyModalComponent extends BaseModal {
   /**
    * The event that is triggered when the 'Add' button as been pressed.
    */
@@ -28,11 +28,8 @@ export class RgwUserS3KeyModalComponent {
   resource: string;
   action: string;
 
-  constructor(
-    private formBuilder: CdFormBuilder,
-    public activeModal: NgbActiveModal,
-    public actionLabels: ActionLabelsI18n
-  ) {
+  constructor(private formBuilder: CdFormBuilder, public actionLabels: ActionLabelsI18n) {
+    super();
     this.resource = $localize`S3 Key`;
     this.createForm();
   }
@@ -79,6 +76,6 @@ export class RgwUserS3KeyModalComponent {
   onSubmit() {
     const key: RgwUserS3Key = this.formGroup.value;
     this.submitAction.emit(key);
-    this.activeModal.close();
+    this.closeModal();
   }
 }
index e04dc4cd551323f7b440f2edabc63071e13c8c05..5b1a2c3c6ca5babbb1a12fd7c20463f712097cdf 100644 (file)
-<cd-modal [modalRef]="bsModalRef">
-  <ng-container i18n="form title"
-                class="modal-title">{{ action | titlecase }} {{ resource | upperFirst }}</ng-container>
-  <ng-container class="modal-content">
-    <form #frm="ngForm"
-          [formGroup]="formGroup"
-          novalidate>
-      <div class="modal-body">
+<cds-modal size="md"
+           [open]="open"
+           [hasScrollingContent]="false"
+           (overlaySelected)="closeModal()">
+  <cds-modal-header (closeSelect)="closeModal()">
+    <h3 cdsModalHeaderHeading
+        i18n>{{ action | titlecase }} {{ resource | upperFirst }}</h3>
 
-        <!-- Username -->
-        <div class="form-group row">
-          <label class="cd-col-form-label"
-                 for="uid"
-                 i18n>Username</label>
-          <div class="cd-col-form-input">
-            <input id="uid"
-                   class="form-control"
-                   type="text"
-                   formControlName="uid"
-                   [readonly]="true">
-          </div>
-        </div>
+    <cd-help-text [formAllFieldsRequired]="true"></cd-help-text>
+  </cds-modal-header>
 
-        <!-- Subuser -->
-        <div class="form-group row">
-          <label class="cd-col-form-label"
-                 [ngClass]="{'required': !editing}"
-                 for="subuid"
-                 i18n>Subuser</label>
-          <div class="cd-col-form-input">
-            <input id="subuid"
-                   class="form-control"
-                   type="text"
-                   formControlName="subuid"
-                   [readonly]="editing"
-                   autofocus>
-            <span class="invalid-feedback"
-                  *ngIf="formGroup.showError('subuid', frm, 'required')"
-                  i18n>This field is required.</span>
-            <span class="invalid-feedback"
-                  *ngIf="formGroup.showError('subuid', frm, 'subuserIdExists')"
-                  i18n>The chosen subuser ID is already in use.</span>
-          </div>
-        </div>
+  <form #frm="ngForm"
+        [formGroup]="formGroup"
+        novalidate>
+    <div cdsModalContent>
+      <!-- Username -->
+      <div class="form-item">
+        <cds-text-label for="uid"
+                        i18n>Username
+          <input cdsText
+                 id="uid"
+                 name="uid"
+                 formControlName="uid"
+                 readonly
+                 modal-primary-focus>
+        </cds-text-label>
+      </div>
 
-        <!-- Permission -->
-        <div class="form-group row">
-          <label class="cd-col-form-label required"
-                 for="perm"
-                 i18n>Permission</label>
-          <div class="cd-col-form-input">
-            <select id="perm"
-                    class="form-select"
-                    formControlName="perm">
-              <option i18n
-                      [ngValue]="null">-- Select a permission --</option>
-              <option *ngFor="let perm of ['read', 'write']"
-                      [value]="perm">
-                {{ perm }}
-              </option>
-              <option i18n
-                      value="read-write">read, write</option>
-              <option i18n
-                      value="full-control">full</option>
-            </select>
-            <span class="invalid-feedback"
-                  *ngIf="formGroup.showError('perm', frm, 'required')"
-                  i18n>This field is required.</span>
-          </div>
+      <!-- Subuser -->
+      <div class="form-item">
+        <cds-text-label for="subuid"
+                        [invalid]="formGroup.controls.subuid.invalid && formGroup.controls.subuid.dirty"
+                        [invalidText]="subuserHelper">Subuser
+          <input cdsText
+                 id="subuid"
+                 name="subuid"
+                 formControlName="subuid"
+                 [readonly]="editing"
+                 autofocus>
+        </cds-text-label>
+        <ng-template #subuserHelper>
+          <span class="invalid-feedback"
+                *ngIf="formGroup.showError('subuid', frm, 'required')"
+                i18n>This field is required.</span>
+          <span class="invalid-feedback"
+                *ngIf="formGroup.showError('subuid', frm, 'subuserIdExists')"
+                i18n>The chosen subuser ID is already in use.</span>
+        </ng-template>
+      </div>
+
+      <!-- Permission -->
+      <div class="form-item">
+        <cds-select label="Permission"
+                    i18n-label
+                    for="perm"
+                    formControlName="perm"
+                    [invalid]="formGroup.controls.perm.invalid && formGroup.controls.perm.dirty"
+                    [invalidText]="permError">
+          <option value="">--- Select a permission ---</option>
+          <option *ngFor="let perm of ['read', 'write']"
+                  [value]="perm">
+            {{ perm }}
+          </option>
+          <option i18n
+                  value="read-write">read, write</option>
+          <option i18n
+                  value="full-control">full</option>
+        </cds-select>
+
+        <ng-template #permError>
+          <span class="invalid-feedback"
+                *ngIf="formGroup.showError('perm', frm, 'required')"
+                i18n>This field is required.</span>
+        </ng-template>
+      </div>
+
+      <!-- Swift key -->
+      <fieldset *ngIf="!editing">
+        <legend i18n>Swift key</legend>
+
+        <!-- Auto-generate key -->
+        <div class="form-item">
+          <cds-checkbox id="generate_secret"
+                        formControlName="generate_secret"
+                        i18n>Auto-generate key
+          </cds-checkbox>
         </div>
 
-        <!-- Swift key -->
-        <fieldset *ngIf="!editing">
-          <legend i18n>Swift key</legend>
+        <!-- Secret key -->
+        <div class="form-item form-item-append"
+             *ngIf="!editing && !formGroup.getValue('generate_secret')">
+          <cds-password-label for="secret_key"
+                              [invalid]="formGroup.controls.secret_key.invalid && formGroup.controls.secret_key.dirty"
+                              [invalidText]="secretKeyError"
+                              i18n>Secret key
+            <input cdsPassword
+                   id="secret_key"
+                   name="secret_key"
+                   formControlName="secret_key"
+                   [invalid]="formGroup.controls.secret_key.invalid && formGroup.controls.secret_key.dirty"
+                   [autofocus]="true">
+          </cds-password-label>
 
-          <!-- Auto-generate key -->
-          <div class="form-group row">
-            <div class="cd-col-form-offset">
-              <div class="custom-control custom-checkbox">
-                <input class="custom-control-input"
-                       id="generate_secret"
-                       type="checkbox"
-                       formControlName="generate_secret">
-                <label class="custom-control-label"
-                       for="generate_secret"
-                       i18n>Auto-generate secret</label>
-              </div>
-            </div>
-          </div>
+          <cd-copy-2-clipboard-button source="secret_key"
+                                      class="mt-4">
+          </cd-copy-2-clipboard-button>
 
-          <!-- Secret key -->
-          <div class="form-group row"
-               *ngIf="!editing && !formGroup.getValue('generate_secret')">
-            <label class="cd-col-form-label required"
-                   for="secret_key"
-                   i18n>Secret key</label>
-            <div class="cd-col-form-input">
-              <div class="input-group">
-                <input id="secret_key"
-                       class="form-control"
-                       type="password"
-                       formControlName="secret_key">
-                <button type="button"
-                        class="btn btn-light"
-                        cdPasswordButton="secret_key">
-                </button>
-                <cd-copy-2-clipboard-button source="secret_key">
-                </cd-copy-2-clipboard-button>
-              </div>
-              <span class="invalid-feedback"
-                    *ngIf="formGroup.showError('secret_key', frm, 'required')"
-                    i18n>This field is required.</span>
-            </div>
-          </div>
+          <ng-template #secretKeyError>
+            <span class="invalid-feedback"
+                  *ngIf="formGroup.showError('secret_key', frm, 'required')"
+                  i18n>This field is required.</span>
+          </ng-template>
+        </div>
 
-        </fieldset>
+      </fieldset>
+    </div>
+    <cd-form-button-panel (submitActionEvent)="onSubmit()"
+                          [form]="formGroup"
+                          [submitText]="(action | titlecase) + ' ' + (resource | upperFirst)"
+                          [modalForm]="true"></cd-form-button-panel>
 
-      </div>
-      <div class="modal-footer">
-        <cd-form-button-panel (submitActionEvent)="onSubmit()"
-                              [form]="formGroup"
-                              [submitText]="(action | titlecase) + ' ' + (resource | upperFirst)"></cd-form-button-panel>
-      </div>
-    </form>
-  </ng-container>
-</cd-modal>
+  </form>
+</cds-modal>
index d4843aa9dd2012998ef71967f08ff5432a1dbd75..b201918f7f24e731a798017a31d790759acce0f7 100644 (file)
@@ -2,11 +2,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { FormControl, ReactiveFormsModule } from '@angular/forms';
 import { RouterTestingModule } from '@angular/router/testing';
 
-import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
-
 import { SharedModule } from '~/app/shared/shared.module';
 import { configureTestBed } from '~/testing/unit-test-helper';
 import { RgwUserSubuserModalComponent } from './rgw-user-subuser-modal.component';
+import { SelectModule } from 'carbon-components-angular';
 
 describe('RgwUserSubuserModalComponent', () => {
   let component: RgwUserSubuserModalComponent;
@@ -14,8 +13,7 @@ describe('RgwUserSubuserModalComponent', () => {
 
   configureTestBed({
     declarations: [RgwUserSubuserModalComponent],
-    imports: [ReactiveFormsModule, SharedModule, RouterTestingModule],
-    providers: [NgbActiveModal]
+    imports: [ReactiveFormsModule, SharedModule, RouterTestingModule, SelectModule]
   });
 
   beforeEach(() => {
index 32aef91fea40d59e07994311f3bc2a12e9bab95e..5335b4954fc51c27e33d202f88b118e0c0f16252 100644 (file)
@@ -1,7 +1,6 @@
 import { Component, EventEmitter, Output } from '@angular/core';
 import { AbstractControl, ValidationErrors, ValidatorFn, Validators } from '@angular/forms';
 
-import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
 import _ from 'lodash';
 
 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
@@ -9,13 +8,14 @@ import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
 import { CdValidators, isEmptyInputValue } from '~/app/shared/forms/cd-validators';
 import { RgwUserSubuser } from '../models/rgw-user-subuser';
+import { BaseModal } from 'carbon-components-angular';
 
 @Component({
   selector: 'cd-rgw-user-subuser-modal',
   templateUrl: './rgw-user-subuser-modal.component.html',
   styleUrls: ['./rgw-user-subuser-modal.component.scss']
 })
-export class RgwUserSubuserModalComponent {
+export class RgwUserSubuserModalComponent extends BaseModal {
   /**
    * The event that is triggered when the 'Add' or 'Update' button
    * has been pressed.
@@ -29,11 +29,8 @@ export class RgwUserSubuserModalComponent {
   resource: string;
   action: string;
 
-  constructor(
-    private formBuilder: CdFormBuilder,
-    public bsModalRef: NgbActiveModal,
-    private actionLabels: ActionLabelsI18n
-  ) {
+  constructor(private formBuilder: CdFormBuilder, private actionLabels: ActionLabelsI18n) {
+    super();
     this.resource = $localize`Subuser`;
     this.createForm();
   }
@@ -42,7 +39,7 @@ export class RgwUserSubuserModalComponent {
     this.formGroup = this.formBuilder.group({
       uid: [null],
       subuid: [null, [Validators.required, this.subuserValidator()]],
-      perm: [null, [Validators.required]],
+      perm: ['full-control', [Validators.required]],
       // Swift key
       generate_secret: [true],
       secret_key: [null, [CdValidators.requiredIf({ generate_secret: false })]]
@@ -125,6 +122,6 @@ export class RgwUserSubuserModalComponent {
     subuser.generate_secret = values.generate_secret;
     subuser.secret_key = values.secret_key;
     this.submitAction.emit(subuser);
-    this.bsModalRef.close();
+    this.closeModal();
   }
 }
index ee64fc8a15c2f38f5e6271e4cacd9e4c5dde68b6..3fede298cc8f506068dab224051aa84e68852f2c 100644 (file)
@@ -1,52 +1,44 @@
-<cd-modal [modalRef]="activeModal">
-  <ng-container i18n="form title"
-                class="modal-title">{{ action | titlecase }} {{ resource | upperFirst }}</ng-container>
+<cds-modal size="md"
+           [open]="open"
+           [hasScrollingContent]="false"
+           (overlaySelected)="closeModal()">
+  <cds-modal-header (closeSelect)="closeModal()">
+    <h3 cdsModalHeaderHeading
+        i18n>{{ action | titlecase }} {{ resource | upperFirst }}</h3>
+  </cds-modal-header>
 
-  <ng-container class="modal-content">
-    <div class="modal-body">
-      <form novalidate>
-        <!-- Username -->
-        <div class="form-group row">
-          <label class="cd-col-form-label"
-                 for="user"
-                 i18n>Username</label>
-          <div class="cd-col-form-input">
-            <input id="user"
-                   name="user"
-                   class="form-control"
-                   type="text"
-                   [readonly]="true"
-                   [(ngModel)]="user">
-          </div>
-        </div>
+  <form novalidate>
+    <div cdsModalContent>
+      <!-- Username -->
+      <div class="form-item">
+        <cds-text-label for="user"
+                        i18n>Username
+          <input cdsText
+                 id="user"
+                 name="user"
+                 readonly
+                 [(ngModel)]="user"
+                 modal-primary-focus>
+        </cds-text-label>
+      </div>
 
-        <!-- Secret key -->
-        <div class="form-group row">
-          <label class="cd-col-form-label"
-                 for="secret_key"
-                 i18n>Secret key</label>
-          <div class="cd-col-form-input">
-            <div class="input-group">
-              <input id="secret_key"
-                     name="secret_key"
-                     class="form-control"
-                     type="password"
-                     [(ngModel)]="secret_key"
-                     [readonly]="true">
-              <button type="button"
-                      class="btn btn-light"
-                      cdPasswordButton="secret_key">
-              </button>
-              <cd-copy-2-clipboard-button source="secret_key">
-              </cd-copy-2-clipboard-button>
-            </div>
-          </div>
-        </div>
-      </form>
+      <!-- Secret key -->
+      <div class="form-item form-item-append">
+        <cds-password-label for="secret_key"
+                            i18n>Secret key
+          <input cdsPassword
+                 id="secret_key"
+                 name="secret_key"
+                 [(ngModel)]="secret_key"
+                 readonly>
+        </cds-password-label>
+        <cd-copy-2-clipboard-button source="secret_key"
+                                    class="mt-4">
+        </cd-copy-2-clipboard-button>
+      </div>
     </div>
-
-    <div class="modal-footer">
-      <cd-back-button (backAction)="activeModal.close()"></cd-back-button>
-    </div>
-  </ng-container>
-</cd-modal>
+    <cd-form-button-panel [showSubmit]="false"
+                          (cancel)="closeModal()"
+                          [modalForm]="true"></cd-form-button-panel>
+  </form>
+</cds-modal>
index 7bd63bcc02bd38bbca1e9bd2690f89a2b1efc537..6bec757d3e0199f63005d9fa1bdb3475a551554a 100644 (file)
@@ -1,6 +1,6 @@
 import { Component } from '@angular/core';
 
-import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+import { BaseModal } from 'carbon-components-angular';
 
 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
 
@@ -9,13 +9,14 @@ import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
   templateUrl: './rgw-user-swift-key-modal.component.html',
   styleUrls: ['./rgw-user-swift-key-modal.component.scss']
 })
-export class RgwUserSwiftKeyModalComponent {
+export class RgwUserSwiftKeyModalComponent extends BaseModal {
   user: string;
   secret_key: string;
   resource: string;
   action: string;
 
-  constructor(public activeModal: NgbActiveModal, public actionLabels: ActionLabelsI18n) {
+  constructor(public actionLabels: ActionLabelsI18n) {
+    super();
     this.resource = $localize`Swift Key`;
     this.action = this.actionLabels.SHOW;
   }
index a32d76e7f56ed6a6f00a1fb9673f4c0f92567ea2..f65ca55cb768bb7445307b765a74ea9b503b0b7c 100644 (file)
@@ -82,7 +82,8 @@ import {
   TabsModule,
   AccordionModule,
   TagModule,
-  TooltipModule
+  TooltipModule,
+  ComboBoxModule
 } from 'carbon-components-angular';
 import { CephSharedModule } from '../shared/ceph-shared.module';
 import { RgwUserAccountsComponent } from './rgw-user-accounts/rgw-user-accounts.component';
@@ -126,13 +127,10 @@ import { RgwRateLimitDetailsComponent } from './rgw-rate-limit-details/rgw-rate-
     SelectModule,
     NumberModule,
     TabsModule,
-    IconModule,
-    SelectModule,
     RadioModule,
-    SelectModule,
-    NumberModule,
     TagModule,
-    TooltipModule
+    TooltipModule,
+    ComboBoxModule
   ],
   exports: [
     RgwDaemonListComponent,
index 8f335822a779c608cb25c718da1bf5a622d92504..a46d9dc151672b1733c5e8ea65fc4b961f0f83a5 100644 (file)
@@ -83,6 +83,7 @@ import { ProgressComponent } from './progress/progress.component';
 
 // Icons
 import InfoIcon from '@carbon/icons/es/information/16';
+import CopyIcon from '@carbon/icons/es/copy/32';
 
 @NgModule({
   imports: [
@@ -207,6 +208,6 @@ import InfoIcon from '@carbon/icons/es/information/16';
 })
 export class ComponentsModule {
   constructor(private iconService: IconService) {
-    this.iconService.registerAll([InfoIcon]);
+    this.iconService.registerAll([InfoIcon, CopyIcon]);
   }
 }
index 655364eefc0c6c6a935f3862f9a20339d1afc815..8581a51109aca0dfc5d4753fdd79e2217e533c12 100644 (file)
@@ -5,11 +5,13 @@
    *ngIf="showIconOnly; else withButtonTpl"></i>
 
 <ng-template #withButtonTpl>
-  <button (click)="onClick()"
-          type="button"
-          class="btn btn-light"
-          i18n-title
-          title="Copy to Clipboard">
-    <i [ngClass]="[icons.clipboard]"></i>
-  </button>
+
+  <cds-icon-button kind="tertiary"
+                   size="md"
+                   title="Copy to Clipboard"
+                   (click)="onClick()">
+    <svg cdsIcon="copy"
+         size="32"
+         class="cds--btn__icon"></svg>
+  </cds-icon-button>
 </ng-template>
index e8d0d6e3d19aead9e2d6a1da1ff814abeed339d4..65b889b060aa048f2eb21ed8f8bd07991420b943 100644 (file)
@@ -1,3 +1,10 @@
   <div class="form-text text-muted">
-    <ng-content></ng-content>
+    <ng-container *ngIf="formAllFieldsRequired; else contentTpl"
+                  i18n>
+      All fields are required, except where marked optional.
+    </ng-container>
+
+    <ng-template #contentTpl>
+      <ng-content></ng-content>
+    </ng-template>
   </div>
index 60fed74aa556035cc968b64954bcc035c8056fd4..9ed820677af3111f966f7a0d44ff12b0fff2fbde 100644 (file)
@@ -1,8 +1,11 @@
-import { Component } from '@angular/core';
+import { Component, Input } from '@angular/core';
 
 @Component({
   selector: 'cd-help-text',
   templateUrl: './help-text.component.html',
   styleUrls: ['./help-text.component.scss']
 })
-export class HelpTextComponent {}
+export class HelpTextComponent {
+  @Input()
+  formAllFieldsRequired = false;
+}