]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: RGW user accounts UI
authorNaman Munet <namanmunet@li-ff83bccc-26af-11b2-a85c-a4b04bfb1003.ibm.com>
Mon, 9 Dec 2024 11:40:17 +0000 (17:10 +0530)
committerNaman Munet <naman.munet@ibm.com>
Wed, 22 Jan 2025 11:35:33 +0000 (17:05 +0530)
--> Integrated list endpoint

Fixes: https://tracker.ceph.com/issues/69140
Signed-off-by: Naman Munet <namanmunet@li-ff83bccc-26af-11b2-a85c-a4b04bfb1003.ibm.com>
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-user-accounts.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts/rgw-user-accounts.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts/rgw-user-accounts.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts/rgw-user-accounts.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts/rgw-user-accounts.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-tabs/rgw-user-tabs.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts
src/pybind/mgr/dashboard/frontend/src/app/core/context/context.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user-accounts.service.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user-accounts.service.ts [new file with mode: 0644]

diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-user-accounts.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-user-accounts.ts
new file mode 100644 (file)
index 0000000..2cc0504
--- /dev/null
@@ -0,0 +1,21 @@
+export interface Accounts {
+  id: string;
+  tenant: string;
+  name: string;
+  email: string;
+  quota: Quota;
+  bucket_quota: Quota;
+  max_users: number;
+  max_roles: number;
+  max_groups: number;
+  max_buckets: number;
+  max_access_keys: number;
+}
+
+interface Quota {
+  enabled: boolean;
+  check_on_raw: boolean;
+  max_size: number;
+  max_size_kb: number;
+  max_objects: number;
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts/rgw-user-accounts.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts/rgw-user-accounts.component.html
new file mode 100644 (file)
index 0000000..b10972a
--- /dev/null
@@ -0,0 +1,24 @@
+<cd-rgw-user-tabs></cd-rgw-user-tabs>
+<legend i18n>
+  User Accounts
+  <cd-help-text>
+    This feature allows administrators to assign unique credentials to individual users or applications,
+    ensuring granular access control and improved security across the cluster.
+  </cd-help-text>
+</legend>
+<cd-table #table
+          [autoReload]="false"
+          [data]="accounts"
+          [columns]="columns"
+          columnMode="flex"
+          selectionType="multiClick"
+          [hasDetails]="false"
+          (updateSelection)="updateSelection($event)"
+          identifier="id"
+          (fetchData)="getAccountsList($event)">
+  <cd-table-actions class="table-actions"
+                    [permission]="permission"
+                    [selection]="selection"
+                    [tableActions]="tableActions">
+  </cd-table-actions>
+</cd-table>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts/rgw-user-accounts.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts/rgw-user-accounts.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts/rgw-user-accounts.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts/rgw-user-accounts.component.spec.ts
new file mode 100644 (file)
index 0000000..d6bdd8a
--- /dev/null
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { RgwUserAccountsComponent } from './rgw-user-accounts.component';
+
+describe('RgwUserAccountsComponent', () => {
+  let component: RgwUserAccountsComponent;
+  let fixture: ComponentFixture<RgwUserAccountsComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [RgwUserAccountsComponent]
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(RgwUserAccountsComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts/rgw-user-accounts.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts/rgw-user-accounts.component.ts
new file mode 100644 (file)
index 0000000..64686d2
--- /dev/null
@@ -0,0 +1,101 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+import { Permission } from '~/app/shared/models/permissions';
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { Accounts } from '../models/rgw-user-accounts';
+import { RgwUserAccountsService } from '~/app/shared/api/rgw-user-accounts.service';
+
+@Component({
+  selector: 'cd-rgw-user-accounts',
+  templateUrl: './rgw-user-accounts.component.html',
+  styleUrls: ['./rgw-user-accounts.component.scss']
+})
+export class RgwUserAccountsComponent implements OnInit {
+  @ViewChild(TableComponent, { static: true })
+  table: TableComponent;
+  permission: Permission;
+  tableActions: CdTableAction[] = [];
+  columns: CdTableColumn[] = [];
+  accounts: Accounts[] = [];
+  selection: CdTableSelection = new CdTableSelection();
+
+  constructor(
+    private authStorageService: AuthStorageService,
+    public actionLabels: ActionLabelsI18n,
+    private rgwUserAccountsService: RgwUserAccountsService
+  ) {}
+
+  ngOnInit() {
+    this.permission = this.authStorageService.getPermissions().rgw;
+    this.columns = [
+      {
+        name: $localize`Account Id`,
+        prop: 'id',
+        flexGrow: 1
+      },
+      {
+        name: $localize`Tenant`,
+        prop: 'tenant',
+        flexGrow: 1
+      },
+      {
+        name: $localize`Full name`,
+        prop: 'name',
+        flexGrow: 1
+      },
+      {
+        name: $localize`Email address`,
+        prop: 'email',
+        flexGrow: 1
+      },
+      {
+        name: $localize`Max Users`,
+        prop: 'max_users',
+        flexGrow: 1
+      },
+      {
+        name: $localize`Max Roles`,
+        prop: 'max_roles',
+        flexGrow: 1
+      },
+      {
+        name: $localize`Max Groups`,
+        prop: 'max_groups',
+        flexGrow: 1
+      },
+      {
+        name: $localize`Max. buckets`,
+        prop: 'max_buckets',
+        flexGrow: 1
+      },
+      {
+        name: $localize`Max Access Keys`,
+        prop: 'max_access_keys',
+        flexGrow: 1
+      }
+    ];
+  }
+
+  getAccountsList(context: CdTableFetchDataContext) {
+    this.rgwUserAccountsService.list(true).subscribe({
+      next: (accounts) => {
+        this.accounts = accounts;
+      },
+      error: () => {
+        if (context) {
+          context.error();
+        }
+      }
+    });
+  }
+
+  updateSelection(selection: CdTableSelection) {
+    this.selection = selection;
+  }
+}
index 8ad1ac19309e7cb0f233758f08032de29a1328d9..92069bb5785bba5a1f03b27815b27c2cc352cafc 100644 (file)
@@ -7,6 +7,14 @@
        [routerLinkActiveOptions]="{exact: true}"
        i18n>Users</a>
   </li>
+  <li class="nav-item">
+    <a class="nav-link"
+       routerLink="/rgw/accounts"
+       routerLinkActive="active"
+       ariaCurrentWhenActive="page"
+       [routerLinkActiveOptions]="{exact: true}"
+       i18n>Accounts</a>
+  </li>
   <li class="nav-item">
     <a class="nav-link"
        routerLink="/rgw/roles"
index 6d3ec47e81993389f1c562f6ff805ee79c6c35f2..caee3d69b13a55487a916aaa86928019f62b3e91 100644 (file)
@@ -76,6 +76,7 @@ import {
   TreeviewModule
 } from 'carbon-components-angular';
 import { CephSharedModule } from '../shared/ceph-shared.module';
+import { RgwUserAccountsComponent } from './rgw-user-accounts/rgw-user-accounts.component';
 
 @NgModule({
   imports: [
@@ -154,7 +155,8 @@ import { CephSharedModule } from '../shared/ceph-shared.module';
     RgwMultisiteSyncPolicyDetailsComponent,
     RgwMultisiteSyncFlowModalComponent,
     RgwMultisiteSyncPipeModalComponent,
-    RgwMultisiteTabsComponent
+    RgwMultisiteTabsComponent,
+    RgwUserAccountsComponent
   ],
   providers: [TitleCasePipe]
 })
@@ -184,6 +186,11 @@ const routes: Routes = [
       }
     ]
   },
+  {
+    path: 'accounts',
+    data: { breadcrumbs: 'Accounts' },
+    children: [{ path: '', component: RgwUserAccountsComponent }]
+  },
   {
     path: 'roles',
     data: {
@@ -194,6 +201,10 @@ const routes: Routes = [
           name: 'Users',
           url: '/rgw/user'
         },
+        {
+          name: 'Accounts',
+          url: '/rgw/accounts'
+        },
         {
           name: 'Roles',
           url: '/rgw/roles'
index 178f230c931f697fa018294f205eef658944e019..7263491891e891428fc7b11ece1cc8ab987bc123 100644 (file)
@@ -26,12 +26,14 @@ export class ContextComponent implements OnInit, OnDestroy {
   private rgwUserUrlPrefix = '/rgw/user';
   private rgwRoleUrlPrefix = '/rgw/roles';
   private rgwBuckerUrlPrefix = '/rgw/bucket';
+  private rgwAccountsUrlPrefix = '/rgw/accounts';
   permissions: Permissions;
   featureToggleMap$: FeatureTogglesMap$;
   isRgwRoute =
     document.location.href.includes(this.rgwUserUrlPrefix) ||
     document.location.href.includes(this.rgwBuckerUrlPrefix) ||
-    document.location.href.includes(this.rgwRoleUrlPrefix);
+    document.location.href.includes(this.rgwRoleUrlPrefix) ||
+    document.location.href.includes(this.rgwAccountsUrlPrefix);
 
   constructor(
     private authStorageService: AuthStorageService,
@@ -53,7 +55,8 @@ export class ContextComponent implements OnInit, OnDestroy {
             (this.isRgwRoute = [
               this.rgwBuckerUrlPrefix,
               this.rgwUserUrlPrefix,
-              this.rgwRoleUrlPrefix
+              this.rgwRoleUrlPrefix,
+              this.rgwAccountsUrlPrefix
             ].some((urlPrefix) => this.router.url.startsWith(urlPrefix)))
         )
     );
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user-accounts.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user-accounts.service.spec.ts
new file mode 100644 (file)
index 0000000..6ea9571
--- /dev/null
@@ -0,0 +1,83 @@
+import { TestBed } from '@angular/core/testing';
+
+import { RgwUserAccountsService } from './rgw-user-accounts.service';
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+import { Accounts } from '~/app/ceph/rgw/models/rgw-user-accounts';
+
+const mockAccountData: Accounts[] = [
+  {
+    id: 'RGW80617806988089685',
+    tenant: '',
+    name: '',
+    email: '',
+    quota: {
+      enabled: false,
+      check_on_raw: false,
+      max_size: -1,
+      max_size_kb: 0,
+      max_objects: -1
+    },
+    bucket_quota: {
+      enabled: false,
+      check_on_raw: false,
+      max_size: -1,
+      max_size_kb: 0,
+      max_objects: -1
+    },
+    max_users: 1000,
+    max_roles: 1000,
+    max_groups: 1000,
+    max_buckets: 1000,
+    max_access_keys: 4
+  },
+  {
+    id: 'RGW12444466134482748',
+    tenant: '',
+    name: '',
+    email: '',
+    quota: {
+      enabled: false,
+      check_on_raw: false,
+      max_size: -1,
+      max_size_kb: 0,
+      max_objects: -1
+    },
+    bucket_quota: {
+      enabled: false,
+      check_on_raw: false,
+      max_size: -1,
+      max_size_kb: 0,
+      max_objects: -1
+    },
+    max_users: 1000,
+    max_roles: 1000,
+    max_groups: 1000,
+    max_buckets: 1000,
+    max_access_keys: 4
+  }
+];
+
+describe('RgwUserAccountsService', () => {
+  let service: RgwUserAccountsService;
+  let httpTesting: HttpTestingController;
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      providers: [RgwUserAccountsService],
+      imports: [HttpClientTestingModule]
+    });
+    service = TestBed.inject(RgwUserAccountsService);
+    httpTesting = TestBed.inject(HttpTestingController);
+  });
+
+  it('should be created', () => {
+    expect(service).toBeTruthy();
+  });
+
+  it('should fetch detailed list of accounts', () => {
+    service.list(true).subscribe();
+    const req = httpTesting.expectOne('api/rgw/accounts?detailed=true');
+    expect(req.request.method).toBe('GET');
+    req.flush(mockAccountData);
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user-accounts.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user-accounts.service.ts
new file mode 100644 (file)
index 0000000..0a67222
--- /dev/null
@@ -0,0 +1,20 @@
+import { HttpClient, HttpParams } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class RgwUserAccountsService {
+  private url = 'api/rgw/accounts';
+
+  constructor(private http: HttpClient) {}
+
+  list(detailed?: boolean): Observable<any> {
+    let params = new HttpParams();
+    if (detailed) {
+      params = params.append('detailed', detailed);
+    }
+    return this.http.get(this.url, { params });
+  }
+}