--- /dev/null
+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';
--- /dev/null
+<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>
--- /dev/null
+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);
+ });
+});
--- /dev/null
+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
+ };
+ }
+ }
+}
--- /dev/null
+<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>
--- /dev/null
+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();
+ });
+});
--- /dev/null
+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);
+ }
+}
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,
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: [
InputModule,
CheckboxModule,
SelectModule,
- NumberModule
+ NumberModule,
+ TabsModule
],
exports: [
RgwDaemonListComponent,
RgwBucketListComponent,
RgwBucketDetailsComponent,
RgwUserListComponent,
- RgwUserDetailsComponent
+ RgwUserDetailsComponent,
+ RgwStorageClassListComponent
],
declarations: [
RgwDaemonListComponent,
RgwMultisiteTabsComponent,
RgwUserAccountsComponent,
RgwUserAccountsFormComponent,
- RgwUserAccountsDetailsComponent
+ RgwUserAccountsDetailsComponent,
+ RgwStorageClassListComponent,
+ RgwStorageClassDetailsComponent
],
providers: [TitleCasePipe]
})
}
]
},
+ {
+ path: 'tiering',
+ data: { breadcrumbs: 'Tiering' },
+ children: [{ path: '', component: RgwStorageClassListComponent }]
+ },
{
path: 'nfs',
canActivateChild: [FeatureTogglesGuardService, ModuleStatusGuardService],
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
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
)
);