]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Storage Class Management 61466/head
authorDnyaneshwari <dnyaneshwari@li-9c9fbecc-2d5c-11b2-a85c-e2a7cc8a424f.ibm.com>
Fri, 17 Jan 2025 10:06:50 +0000 (15:36 +0530)
committerDnyaneshwari <dnyaneshwari@li-9c9fbecc-2d5c-11b2-a85c-e2a7cc8a424f.ibm.com>
Wed, 5 Feb 2025 05:45:48 +0000 (11:15 +0530)
Fixes: https://tracker.ceph.com/issues/69606
Signed-off-by: Dnyaneshwari Talwekar <dtalweka@redhat.com>
12 files changed:
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-storage-class.model.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-details/rgw-storage-class-details.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-details/rgw-storage-class-details.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-details/rgw-storage-class-details.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-details/rgw-storage-class-details.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-list/rgw-storage-class-list.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-list/rgw-storage-class-list.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-list/rgw-storage-class-list.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-list/rgw-storage-class-list.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts
src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html
src/pybind/mgr/dashboard/frontend/src/styles/themes/_content.scss

diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-storage-class.model.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-storage-class.model.ts
new file mode 100644 (file)
index 0000000..2fd9ede
--- /dev/null
@@ -0,0 +1,52 @@
+export interface ZoneGroupDetails {
+  default_zonegroup: string;
+  zonegroups: ZoneGroup[];
+}
+
+export interface StorageClass {
+  storage_class: string;
+  endpoint: string;
+  region: string;
+  placement_target: string;
+}
+
+export interface StorageClassDetails {
+  target_path: string;
+  access_key: string;
+  secret: string;
+  multipart_min_part_size: number;
+  multipart_sync_threshold: number;
+  host_style: string;
+}
+export interface ZoneGroup {
+  name: string;
+  placement_targets: Target[];
+}
+
+export interface S3Details {
+  endpoint: string;
+  access_key: string;
+  storage_class: string;
+  target_path: string;
+  target_storage_class: string;
+  region: string;
+  secret: string;
+  multipart_min_part_size: number;
+  multipart_sync_threshold: number;
+  host_style: boolean;
+}
+
+export interface TierTarget {
+  val: {
+    storage_class: string;
+    tier_type: string;
+    s3: S3Details;
+  };
+}
+
+export interface Target {
+  name: string;
+  tier_targets: TierTarget[];
+}
+
+export const CLOUD_TIER = 'cloud-s3';
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-details/rgw-storage-class-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-details/rgw-storage-class-details.component.html
new file mode 100644 (file)
index 0000000..1f724fc
--- /dev/null
@@ -0,0 +1,114 @@
+<ng-container *ngIf="!!selection">
+  <cds-tabs type="contained"
+            theme="light">
+    <cds-tab heading="Details">
+      <table
+        class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md"
+        data-testid="rgw-storage-details"
+      >
+        <tbody>
+          <tr>
+            <td class="bold">
+              Target Path
+              <cd-helper class="text-pre-wrap">
+                <span i18n>
+                  The target path specifies a prefix to which the source bucket-name/object-name is
+                  appended.
+                </span>
+              </cd-helper>
+            </td>
+            <td>{{ selection?.target_path }}</td>
+          </tr>
+          <tr>
+            <td class="bold">
+              Access key
+              <cd-helper class="text-pre-wrap">
+                <span i18n>
+                  Access key is the remote cloud S3 access key used for a specific connection.
+                </span>
+              </cd-helper>
+            </td>
+            <td>
+              <div cdsCol
+                   [columnNumbers]="{ md: 4 }"
+                   class="d-flex">
+                <input
+                  cdsPassword
+                  type="password"
+                  readonly
+                  id="access_key"
+                  [value]="selection?.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>
+            </td>
+          </tr>
+          <tr>
+            <td class="bold">
+              Secret key
+              <cd-helper class="text-pre-wrap">
+                <span i18n> Secret is the secret key for the remote cloud S3 service. </span>
+              </cd-helper>
+            </td>
+            <td>
+              <div cdsCol
+                   [columnNumbers]="{ md: 4 }"
+                   class="d-flex">
+                <input
+                  cdsPassword
+                  type="password"
+                  readonly
+                  id="secret"
+                  [value]="selection?.secret"
+                />
+                <button type="button"
+                        class="btn btn-light"
+                        cdPasswordButton="secret"></button>
+                <cd-copy-2-clipboard-button source="secret"> </cd-copy-2-clipboard-button>
+              </div>
+            </td>
+          </tr>
+          <tr>
+            <td
+                class="bold">
+              Host Style
+              <cd-helper class="text-pre-wrap">
+                <span i18n>The URL format for accessing the remote S3 endpoint: 'Path' for a path-based URL
+                  or 'Virtual' for a domain-based URL.</span>
+              </cd-helper>
+            </td>
+            <td>{{ selection?.host_style }}</td>
+          </tr>
+          <tr>
+            <td
+                class="bold">
+              Multipart Minimum Part Size
+              <cd-helper class="text-pre-wrap">
+                <span i18n>
+                  Minimum parts size to use when transitioning objects using multipart upload.
+                </span>
+              </cd-helper>
+            </td>
+            <td>{{ selection?.multipart_min_part_size }}</td>
+          </tr>
+          <tr>
+            <td
+                class="bold">
+              Multipart Sync Threshold
+              <cd-helper class="text-pre-wrap">
+                <span i18n>
+                  Objects this size or larger will be transitioned to the cloud using multipart
+                  upload.
+                </span>
+              </cd-helper>
+            </td>
+            <td>{{ selection?.multipart_sync_threshold }}</td>
+          </tr>
+        </tbody>
+      </table>
+    </cds-tab>
+  </cds-tabs>
+</ng-container>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-details/rgw-storage-class-details.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-details/rgw-storage-class-details.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-details/rgw-storage-class-details.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-details/rgw-storage-class-details.component.spec.ts
new file mode 100644 (file)
index 0000000..90541d4
--- /dev/null
@@ -0,0 +1,47 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { RgwStorageClassDetailsComponent } from './rgw-storage-class-details.component';
+import { StorageClassDetails } from '../models/rgw-storage-class.model';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { SharedModule } from '~/app/shared/shared.module';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { RouterTestingModule } from '@angular/router/testing';
+
+describe('RgwStorageClassDetailsComponent', () => {
+  let component: RgwStorageClassDetailsComponent;
+  let fixture: ComponentFixture<RgwStorageClassDetailsComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [
+        BrowserAnimationsModule,
+        SharedModule,
+        HttpClientTestingModule,
+        RouterTestingModule
+      ],
+      declarations: [RgwStorageClassDetailsComponent]
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(RgwStorageClassDetailsComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  it('should update storageDetails when selection input changes', () => {
+    const mockSelection: StorageClassDetails = {
+      access_key: 'TestAccessKey',
+      secret: 'TestSecret',
+      target_path: '/test/path',
+      multipart_min_part_size: 100,
+      multipart_sync_threshold: 200,
+      host_style: 'path'
+    };
+    component.selection = mockSelection;
+    component.ngOnChanges();
+    expect(component.storageDetails).toEqual(mockSelection);
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-details/rgw-storage-class-details.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-details/rgw-storage-class-details.component.ts
new file mode 100644 (file)
index 0000000..7494e6d
--- /dev/null
@@ -0,0 +1,28 @@
+import { Component, Input, OnChanges } from '@angular/core';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { StorageClassDetails } from '../models/rgw-storage-class.model';
+
+@Component({
+  selector: 'cd-rgw-storage-class-details',
+  templateUrl: './rgw-storage-class-details.component.html',
+  styleUrls: ['./rgw-storage-class-details.component.scss']
+})
+export class RgwStorageClassDetailsComponent implements OnChanges {
+  @Input()
+  selection: StorageClassDetails;
+  columns: CdTableColumn[] = [];
+  storageDetails: StorageClassDetails;
+
+  ngOnChanges() {
+    if (this.selection) {
+      this.storageDetails = {
+        access_key: this.selection.access_key,
+        secret: this.selection.secret,
+        target_path: this.selection.target_path,
+        multipart_min_part_size: this.selection.multipart_min_part_size,
+        multipart_sync_threshold: this.selection.multipart_sync_threshold,
+        host_style: this.selection.host_style
+      };
+    }
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-list/rgw-storage-class-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-list/rgw-storage-class-list.component.html
new file mode 100644 (file)
index 0000000..3aeebfc
--- /dev/null
@@ -0,0 +1,14 @@
+<cd-table
+  [data]="storageClassList"
+  columnMode="flex"
+  [columns]="columns"
+  (fetchData)="loadStorageClass()"
+  selectionType="single"
+  [hasDetails]="true"
+  (setExpandedRow)="setExpandedRow($event)"
+  (updateSelection)="updateSelection($event)"
+>
+  <cd-rgw-storage-class-details *cdTableDetail
+                                [selection]="expandedRow">
+  </cd-rgw-storage-class-details>
+</cd-table>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-list/rgw-storage-class-list.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-list/rgw-storage-class-list.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-list/rgw-storage-class-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-list/rgw-storage-class-list.component.spec.ts
new file mode 100644 (file)
index 0000000..24f472c
--- /dev/null
@@ -0,0 +1,24 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { RgwStorageClassListComponent } from './rgw-storage-class-list.component';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+
+describe('RgwStorageClassListComponent', () => {
+  let component: RgwStorageClassListComponent;
+  let fixture: ComponentFixture<RgwStorageClassListComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [HttpClientTestingModule],
+      declarations: [RgwStorageClassListComponent]
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(RgwStorageClassListComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-list/rgw-storage-class-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-list/rgw-storage-class-list.component.ts
new file mode 100644 (file)
index 0000000..b1d4107
--- /dev/null
@@ -0,0 +1,106 @@
+import { Component, OnInit } from '@angular/core';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+
+import { RgwZonegroupService } from '~/app/shared/api/rgw-zonegroup.service';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import {
+  StorageClass,
+  CLOUD_TIER,
+  ZoneGroup,
+  TierTarget,
+  Target,
+  ZoneGroupDetails
+} from '../models/rgw-storage-class.model';
+
+@Component({
+  selector: 'cd-rgw-storage-class-list',
+  templateUrl: './rgw-storage-class-list.component.html',
+  styleUrls: ['./rgw-storage-class-list.component.scss']
+})
+export class RgwStorageClassListComponent extends ListWithDetails implements OnInit {
+  columns: CdTableColumn[];
+  selection = new CdTableSelection();
+  tableActions: CdTableAction[];
+  storageClassList: StorageClass[] = [];
+
+  constructor(private rgwZonegroupService: RgwZonegroupService) {
+    super();
+  }
+
+  ngOnInit() {
+    this.columns = [
+      {
+        name: $localize`Zone Group`,
+        prop: 'zonegroup_name',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Placement Target`,
+        prop: 'placement_target',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Storage Class`,
+        prop: 'storage_class',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Target Region`,
+        prop: 'region',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Target Endpoint`,
+        prop: 'endpoint',
+        flexGrow: 2
+      }
+    ];
+  }
+
+  loadStorageClass(): Promise<void> {
+    return new Promise((resolve, reject) => {
+      this.rgwZonegroupService.getAllZonegroupsInfo().subscribe(
+        (data: ZoneGroupDetails) => {
+          this.storageClassList = [];
+
+          const tierObj = data.zonegroups.flatMap((zoneGroup: ZoneGroup) =>
+            zoneGroup.placement_targets
+              .filter((target: Target) => target.tier_targets)
+              .flatMap((target: Target) =>
+                target.tier_targets
+                  .filter((tierTarget: TierTarget) => tierTarget.val.tier_type === CLOUD_TIER)
+                  .map((tierTarget: TierTarget) => {
+                    return this.getTierTargets(tierTarget, zoneGroup.name, target.name);
+                  })
+              )
+          );
+          this.storageClassList.push(...tierObj);
+          resolve();
+        },
+        (error) => {
+          reject(error);
+        }
+      );
+    });
+  }
+
+  getTierTargets(tierTarget: TierTarget, zoneGroup: string, targetName: string) {
+    if (tierTarget.val.tier_type !== CLOUD_TIER) return null;
+    return {
+      zonegroup_name: zoneGroup,
+      placement_target: targetName,
+      storage_class: tierTarget.val.storage_class,
+      ...tierTarget.val.s3
+    };
+  }
+
+  updateSelection(selection: CdTableSelection) {
+    this.selection = selection;
+  }
+
+  setExpandedRow(expandedRow: any) {
+    super.setExpandedRow(expandedRow);
+  }
+}
index f75c86607a9a9e3d1ff04ab90551e76f52739ad6..9817ed51dfe73990c0521db97cf7008c9043d318 100644 (file)
@@ -63,6 +63,7 @@ import { RgwMultisiteSyncPolicyDetailsComponent } from './rgw-multisite-sync-pol
 import { RgwMultisiteSyncFlowModalComponent } from './rgw-multisite-sync-flow-modal/rgw-multisite-sync-flow-modal.component';
 import { RgwMultisiteSyncPipeModalComponent } from './rgw-multisite-sync-pipe-modal/rgw-multisite-sync-pipe-modal.component';
 import { RgwMultisiteTabsComponent } from './rgw-multisite-tabs/rgw-multisite-tabs.component';
+import { RgwStorageClassListComponent } from './rgw-storage-class-list/rgw-storage-class-list.component';
 import {
   ButtonModule,
   GridModule,
@@ -75,12 +76,14 @@ import {
   CheckboxModule,
   TreeviewModule,
   SelectModule,
-  NumberModule
+  NumberModule,
+  TabsModule
 } from 'carbon-components-angular';
 import { CephSharedModule } from '../shared/ceph-shared.module';
 import { RgwUserAccountsComponent } from './rgw-user-accounts/rgw-user-accounts.component';
 import { RgwUserAccountsFormComponent } from './rgw-user-accounts-form/rgw-user-accounts-form.component';
 import { RgwUserAccountsDetailsComponent } from './rgw-user-accounts-details/rgw-user-accounts-details.component';
+import { RgwStorageClassDetailsComponent } from './rgw-storage-class-details/rgw-storage-class-details.component';
 
 @NgModule({
   imports: [
@@ -110,7 +113,8 @@ import { RgwUserAccountsDetailsComponent } from './rgw-user-accounts-details/rgw
     InputModule,
     CheckboxModule,
     SelectModule,
-    NumberModule
+    NumberModule,
+    TabsModule
   ],
   exports: [
     RgwDaemonListComponent,
@@ -119,7 +123,8 @@ import { RgwUserAccountsDetailsComponent } from './rgw-user-accounts-details/rgw
     RgwBucketListComponent,
     RgwBucketDetailsComponent,
     RgwUserListComponent,
-    RgwUserDetailsComponent
+    RgwUserDetailsComponent,
+    RgwStorageClassListComponent
   ],
   declarations: [
     RgwDaemonListComponent,
@@ -164,7 +169,9 @@ import { RgwUserAccountsDetailsComponent } from './rgw-user-accounts-details/rgw
     RgwMultisiteTabsComponent,
     RgwUserAccountsComponent,
     RgwUserAccountsFormComponent,
-    RgwUserAccountsDetailsComponent
+    RgwUserAccountsDetailsComponent,
+    RgwStorageClassListComponent,
+    RgwStorageClassDetailsComponent
   ],
   providers: [TitleCasePipe]
 })
@@ -315,6 +322,11 @@ const routes: Routes = [
       }
     ]
   },
+  {
+    path: 'tiering',
+    data: { breadcrumbs: 'Tiering' },
+    children: [{ path: '', component: RgwStorageClassListComponent }]
+  },
   {
     path: 'nfs',
     canActivateChild: [FeatureTogglesGuardService, ModuleStatusGuardService],
index 0bcb5278f917bed0c93b05102bb5cc63ea460285..a544bbcdb436cf3cee1a8aa52be7c0bbf32e54df 100644 (file)
                             i18n-title
                             [useRouter]="true"
                             class="tc_submenuitem tc_submenuitem_rgw_users"><span i18n>Users</span></cds-sidenav-item>
+          <cds-sidenav-item route="/rgw/tiering"
+                            title="Tiering"
+                            i18n-title
+                            [useRouter]="true"
+                            class="tc_submenuitem tc_submenuitem_rgw_tiering"><span i18n>Tiering</span></cds-sidenav-item>
           <cds-sidenav-item route="/rgw/multisite"
                             title="Multi-site"
                             i18n-title
index 0725b63dbfd09338c08e48d8b6a8d9f9b668df24..fcf3dd4aebba8c2228a9dc24a931ea5edbc3b77c 100644 (file)
@@ -35,7 +35,8 @@ $content-theme: map-merge(
     text-disabled: vv.$gray-500,
     icon-secondary: vv.$gray-800,
     field-01: colors.$gray-10,
-    interactive: vv.$primary
+    interactive: vv.$primary,
+    border-interactive: vv.$primary
   )
 );