--- /dev/null
+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;
+}
--- /dev/null
+<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>
--- /dev/null
+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();
+ });
+});
--- /dev/null
+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;
+ }
+}
[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"
TreeviewModule
} from 'carbon-components-angular';
import { CephSharedModule } from '../shared/ceph-shared.module';
+import { RgwUserAccountsComponent } from './rgw-user-accounts/rgw-user-accounts.component';
@NgModule({
imports: [
RgwMultisiteSyncPolicyDetailsComponent,
RgwMultisiteSyncFlowModalComponent,
RgwMultisiteSyncPipeModalComponent,
- RgwMultisiteTabsComponent
+ RgwMultisiteTabsComponent,
+ RgwUserAccountsComponent
],
providers: [TitleCasePipe]
})
}
]
},
+ {
+ path: 'accounts',
+ data: { breadcrumbs: 'Accounts' },
+ children: [{ path: '', component: RgwUserAccountsComponent }]
+ },
{
path: 'roles',
data: {
name: 'Users',
url: '/rgw/user'
},
+ {
+ name: 'Accounts',
+ url: '/rgw/accounts'
+ },
{
name: 'Roles',
url: '/rgw/roles'
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,
(this.isRgwRoute = [
this.rgwBuckerUrlPrefix,
this.rgwUserUrlPrefix,
- this.rgwRoleUrlPrefix
+ this.rgwRoleUrlPrefix,
+ this.rgwAccountsUrlPrefix
].some((urlPrefix) => this.router.url.startsWith(urlPrefix)))
)
);
--- /dev/null
+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);
+ });
+});
--- /dev/null
+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 });
+ }
+}