From 26c262494524e8de14b0a26863cc442ef68adcfc Mon Sep 17 00:00:00 2001 From: Volker Theile Date: Tue, 6 Mar 2018 14:27:21 +0100 Subject: [PATCH] mgr/dashboard: Add RGW user and bucket lists (read-only) Signed-off-by: Volker Theile --- .../frontend/src/app/app-routing.module.ts | 14 +- .../rgw-bucket-details.component.html | 101 ++++++++++++++ .../rgw-bucket-details.component.scss | 0 .../rgw-bucket-details.component.spec.ts | 34 +++++ .../rgw-bucket-details.component.ts | 22 +++ .../rgw-bucket-list.component.html | 21 +++ .../rgw-bucket-list.component.scss | 0 .../rgw-bucket-list.component.spec.ts | 54 ++++++++ .../rgw-bucket-list.component.ts | 45 +++++++ .../rgw-daemon-details.component.spec.ts | 2 - .../rgw-daemon-details.component.ts | 7 +- .../rgw-daemon-list.component.html | 4 +- .../rgw-daemon-list.component.spec.ts | 9 +- .../rgw-daemon-list.component.ts | 8 +- .../rgw-user-details.component.html | 125 ++++++++++++++++++ .../rgw-user-details.component.scss | 0 .../rgw-user-details.component.spec.ts | 38 ++++++ .../rgw-user-details.component.ts | 37 ++++++ .../rgw-user-list.component.html | 20 +++ .../rgw-user-list.component.scss | 0 .../rgw-user-list.component.spec.ts | 40 ++++++ .../rgw-user-list/rgw-user-list.component.ts | 60 +++++++++ .../frontend/src/app/ceph/rgw/rgw.module.ts | 20 ++- .../navigation/navigation.component.html | 29 ++-- .../frontend/src/app/shared/api/api.module.ts | 4 + .../app/shared/api/rgw-bucket.service.spec.ts | 21 +++ .../src/app/shared/api/rgw-bucket.service.ts | 72 ++++++++++ .../src/app/shared/api/rgw-daemon.service.ts | 12 +- .../app/shared/api/rgw-user.service.spec.ts | 21 +++ .../src/app/shared/api/rgw-user.service.ts | 43 ++++++ .../datatable/table/table.component.html | 7 + .../shared/datatable/table/table.component.ts | 2 + .../src/app/shared/enum/cell-template.enum.ts | 1 + 33 files changed, 832 insertions(+), 41 deletions(-) create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user.service.ts diff --git a/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts index 1b3c15bb28540..5c8e025abd99e 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts @@ -15,7 +15,9 @@ import { DashboardComponent } from './ceph/dashboard/dashboard/dashboard.compone import { PerformanceCounterComponent } from './ceph/performance-counter/performance-counter/performance-counter.component'; +import { RgwBucketListComponent } from './ceph/rgw/rgw-bucket-list/rgw-bucket-list.component'; import { RgwDaemonListComponent } from './ceph/rgw/rgw-daemon-list/rgw-daemon-list.component'; +import { RgwUserListComponent } from './ceph/rgw/rgw-user-list/rgw-user-list.component'; import { LoginComponent } from './core/auth/login/login.component'; import { NotFoundComponent } from './core/not-found/not-found.component'; import { AuthGuardService } from './shared/services/auth-guard.service'; @@ -27,10 +29,20 @@ const routes: Routes = [ { path: 'login', component: LoginComponent }, { path: 'hosts', component: HostsComponent, canActivate: [AuthGuardService] }, { - path: 'rgw', + path: 'rgw/daemon', component: RgwDaemonListComponent, canActivate: [AuthGuardService] }, + { + path: 'rgw/user', + component: RgwUserListComponent, + canActivate: [AuthGuardService] + }, + { + path: 'rgw/bucket', + component: RgwBucketListComponent, + canActivate: [AuthGuardService] + }, { path: 'block/iscsi', component: IscsiComponent, canActivate: [AuthGuardService] }, { path: 'block/rbd', component: RbdListComponent, canActivate: [AuthGuardService] }, { path: 'rbd/add', component: RbdFormComponent, canActivate: [AuthGuardService] }, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.html new file mode 100644 index 0000000000000..306d6de9d02bb --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.html @@ -0,0 +1,101 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Name{{ bucket.bucket }}
ID{{ bucket.id }}
Owner{{ bucket.owner }}
Index type{{ bucket.index_type }}
Placement rule{{ bucket.placement_rule }}
Marker{{ bucket.marker }}
Maximum marker{{ bucket.max_marker }}
Version{{ bucket.ver }}
Master version{{ bucket.master_ver }}
Modification time{{ bucket.mtime }}
Zonegroup{{ bucket.zonegroup }}
+ + +
+ Bucket quota + + + + + + + + + + + + + + + + + +
Enabled{{ bucket.bucket_quota.enabled ? "Yes" : "No" }}
Maximum sizeUnlimited + {{ bucket.bucket_quota.max_size | dimless }} +
Maximum objectsUnlimited + {{ bucket.bucket_quota.max_objects }} +
+
+
+
+
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.scss new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.spec.ts new file mode 100644 index 0000000000000..443ea95daa6c5 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.spec.ts @@ -0,0 +1,34 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TabsModule } from 'ngx-bootstrap/tabs'; + +import { CdTableSelection } from '../../../shared/models/cd-table-selection'; +import { SharedModule } from '../../../shared/shared.module'; +import { RgwBucketDetailsComponent } from './rgw-bucket-details.component'; + +describe('RgwBucketDetailsComponent', () => { + let component: RgwBucketDetailsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ RgwBucketDetailsComponent ], + imports: [ + SharedModule, + TabsModule.forRoot() + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(RgwBucketDetailsComponent); + component = fixture.componentInstance; + component.selection = new CdTableSelection(); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.ts new file mode 100644 index 0000000000000..bc8bc418d0fd2 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.ts @@ -0,0 +1,22 @@ +import { Component, Input, OnChanges } from '@angular/core'; + +import { CdTableSelection } from '../../../shared/models/cd-table-selection'; + +@Component({ + selector: 'cd-rgw-bucket-details', + templateUrl: './rgw-bucket-details.component.html', + styleUrls: ['./rgw-bucket-details.component.scss'] +}) +export class RgwBucketDetailsComponent implements OnChanges { + bucket: any; + + @Input() selection: CdTableSelection; + + constructor() {} + + ngOnChanges() { + if (this.selection.hasSelection) { + this.bucket = this.selection.first(); + } + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.html new file mode 100644 index 0000000000000..b7b5f3d45358f --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.html @@ -0,0 +1,21 @@ + + + + + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.scss new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.spec.ts new file mode 100644 index 0000000000000..53d1f853de28f --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.spec.ts @@ -0,0 +1,54 @@ +import { HttpClientModule } from '@angular/common/http'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { BsDropdownModule } from 'ngx-bootstrap'; +import { TabsModule } from 'ngx-bootstrap/tabs'; + +import { RgwBucketService } from '../../../shared/api/rgw-bucket.service'; +import { DataTableModule } from '../../../shared/datatable/datatable.module'; +import { SharedModule } from '../../../shared/shared.module'; +import { RgwBucketDetailsComponent } from '../rgw-bucket-details/rgw-bucket-details.component'; +import { RgwBucketListComponent } from './rgw-bucket-list.component'; + +describe('RgwBucketListComponent', () => { + let component: RgwBucketListComponent; + let fixture: ComponentFixture; + + const fakeService = { + list: () => { + return new Promise(function(resolve, reject) { + return []; + }); + } + }; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + RgwBucketListComponent, + RgwBucketDetailsComponent + ], + imports: [ + HttpClientModule, + RouterTestingModule, + BsDropdownModule.forRoot(), + TabsModule.forRoot(), + DataTableModule, + SharedModule + ], + providers: [{ provide: RgwBucketService, useValue: fakeService }] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(RgwBucketListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts new file mode 100644 index 0000000000000..49e124216e5c1 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts @@ -0,0 +1,45 @@ +import { Component, ViewChild } from '@angular/core'; + +import { RgwBucketService } from '../../../shared/api/rgw-bucket.service'; +import { TableComponent } from '../../../shared/datatable/table/table.component'; +import { CdTableColumn } from '../../../shared/models/cd-table-column'; +import { CdTableSelection } from '../../../shared/models/cd-table-selection'; + +@Component({ + selector: 'cd-rgw-bucket-list', + templateUrl: './rgw-bucket-list.component.html', + styleUrls: ['./rgw-bucket-list.component.scss'] +}) +export class RgwBucketListComponent { + @ViewChild('table') table: TableComponent; + + columns: CdTableColumn[] = []; + buckets: object[] = []; + selection: CdTableSelection = new CdTableSelection(); + + constructor(private rgwBucketService: RgwBucketService) { + this.columns = [ + { + name: 'Name', + prop: 'bucket', + flexGrow: 1 + }, + { + name: 'Owner', + prop: 'owner', + flexGrow: 1 + } + ]; + } + + getBucketList() { + this.rgwBucketService.list() + .subscribe((resp: object[]) => { + this.buckets = resp; + }); + } + + updateSelection(selection: CdTableSelection) { + this.selection = selection; + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-details/rgw-daemon-details.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-details/rgw-daemon-details.component.spec.ts index e5686b056af49..13fd3f0a88bf9 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-details/rgw-daemon-details.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-details/rgw-daemon-details.component.spec.ts @@ -35,9 +35,7 @@ describe('RgwDaemonDetailsComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(RgwDaemonDetailsComponent); component = fixture.componentInstance; - component.selection = new CdTableSelection(); - fixture.detectChanges(); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-details/rgw-daemon-details.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-details/rgw-daemon-details.component.ts index f3587a0a58a1e..de24cd779899d 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-details/rgw-daemon-details.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-details/rgw-daemon-details.component.ts @@ -29,8 +29,9 @@ export class RgwDaemonDetailsComponent implements OnChanges { if (_.isEmpty(this.serviceId)) { return; } - this.rgwDaemonService.get(this.serviceId).then(resp => { - this.metadata = resp['rgw_metadata']; - }); + this.rgwDaemonService.get(this.serviceId) + .subscribe((resp) => { + this.metadata = resp['rgw_metadata']; + }); } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.html index 64b703fd98e41..f480f5548d64e 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.html @@ -1,8 +1,10 @@ diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.spec.ts index f4f08f4c52807..f5dbd4785ee3f 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.spec.ts @@ -1,10 +1,8 @@ import { HttpClientModule } from '@angular/common/http'; -import { HttpClientTestingModule } from '@angular/common/http/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TabsModule } from 'ngx-bootstrap/tabs'; -import { DataTableModule } from '../../../shared/datatable/datatable.module'; import { SharedModule } from '../../../shared/shared.module'; import { PerformanceCounterModule } from '../../performance-counter/performance-counter.module'; import { RgwDaemonDetailsComponent } from '../rgw-daemon-details/rgw-daemon-details.component'; @@ -16,10 +14,11 @@ describe('RgwDaemonListComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ RgwDaemonListComponent, RgwDaemonDetailsComponent ], + declarations: [ + RgwDaemonListComponent, + RgwDaemonDetailsComponent + ], imports: [ - DataTableModule, - HttpClientTestingModule, HttpClientModule, TabsModule.forRoot(), PerformanceCounterModule, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.ts index affe3962cd8cd..4f1d710b4d84e 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.ts @@ -12,9 +12,9 @@ import { CephShortVersionPipe } from '../../../shared/pipes/ceph-short-version.p }) export class RgwDaemonListComponent { - columns: Array = []; - daemons: Array = []; - selection = new CdTableSelection(); + columns: CdTableColumn[] = []; + daemons: object[] = []; + selection: CdTableSelection = new CdTableSelection(); constructor(private rgwDaemonService: RgwDaemonService, cephShortVersionPipe: CephShortVersionPipe) { @@ -40,7 +40,7 @@ export class RgwDaemonListComponent { getDaemonList() { this.rgwDaemonService.list() - .then((resp) => { + .subscribe((resp: object[]) => { this.daemons = resp; }); } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.html new file mode 100644 index 0000000000000..f05ff79497685 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.html @@ -0,0 +1,125 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Username{{ user.user_id }}
Full name{{ user.display_name }}
Email address{{ user.email }}
Suspended{{ user.suspended ? "Yes" : "No" }}
Maximum buckets{{ user.max_buckets }}
Subusers +
+ {{ subuser.id }} ({{ subuser.permissions }}) +
+
Capabilities +
+ {{ cap.type }} ({{ cap.perm }}) +
+
+ + +
+ User quota + + + + + + + + + + + + + + + + + +
Enabled{{ user.user_quota.enabled ? "Yes" : "No" }}
Maximum sizeUnlimited + {{ user.user_quota.max_size | dimlessBinary }} +
Maximum objectsUnlimited + {{ user.user_quota.max_objects }} +
+
+ + +
+ Bucket quota + + + + + + + + + + + + + + + + + +
Enabled{{ user.bucket_quota.enabled ? "Yes" : "No" }}
Maximum sizeUnlimited + {{ user.bucket_quota.max_size | dimlessBinary }} +
Maximum objectsUnlimited + {{ user.bucket_quota.max_objects }} +
+
+
+
+
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.scss new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.spec.ts new file mode 100644 index 0000000000000..938f0f419f7d6 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.spec.ts @@ -0,0 +1,38 @@ +import { HttpClientModule } from '@angular/common/http'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TabsModule } from 'ngx-bootstrap/tabs'; + +import { CdTableSelection } from '../../../shared/models/cd-table-selection'; +import { SharedModule } from '../../../shared/shared.module'; +import { RgwUserDetailsComponent } from './rgw-user-details.component'; + +describe('RgwUserDetailsComponent', () => { + let component: RgwUserDetailsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ RgwUserDetailsComponent ], + imports: [ + HttpClientTestingModule, + HttpClientModule, + SharedModule, + TabsModule.forRoot() + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(RgwUserDetailsComponent); + component = fixture.componentInstance; + component.selection = new CdTableSelection(); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.ts new file mode 100644 index 0000000000000..1c21ce94176b8 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.ts @@ -0,0 +1,37 @@ +import { Component, Input, OnChanges } from '@angular/core'; + +import * as _ from 'lodash'; + +import { RgwUserService } from '../../../shared/api/rgw-user.service'; +import { CdTableSelection } from '../../../shared/models/cd-table-selection'; + +@Component({ + selector: 'cd-rgw-user-details', + templateUrl: './rgw-user-details.component.html', + styleUrls: ['./rgw-user-details.component.scss'] +}) +export class RgwUserDetailsComponent implements OnChanges { + user: any; + + @Input() selection: CdTableSelection; + + constructor(private rgwUserService: RgwUserService) {} + + ngOnChanges() { + if (this.selection.hasSelection) { + this.user = this.selection.first(); + + // Sort subusers and capabilities. + this.user.subusers = _.sortBy(this.user.subusers, 'id'); + this.user.caps = _.sortBy(this.user.caps, 'type'); + + // Load the user/bucket quota of the selected user. + if (this.user.tenant === '') { + this.rgwUserService.getQuota(this.user.user_id) + .subscribe((resp: object) => { + _.extend(this.user, resp); + }); + } + } + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.html new file mode 100644 index 0000000000000..66434a0e66605 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.html @@ -0,0 +1,20 @@ + + + + + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.scss new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.spec.ts new file mode 100644 index 0000000000000..1b0c18b8550db --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.spec.ts @@ -0,0 +1,40 @@ +import { HttpClientModule } from '@angular/common/http'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BsDropdownModule } from 'ngx-bootstrap'; +import { TabsModule } from 'ngx-bootstrap/tabs'; + +import { SharedModule } from '../../../shared/shared.module'; +import { RgwUserDetailsComponent } from '../rgw-user-details/rgw-user-details.component'; +import { RgwUserListComponent } from './rgw-user-list.component'; + +describe('RgwUserListComponent', () => { + let component: RgwUserListComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + RgwUserListComponent, + RgwUserDetailsComponent + ], + imports: [ + HttpClientModule, + BsDropdownModule.forRoot(), + TabsModule.forRoot(), + SharedModule + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(RgwUserListComponent); + 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-list/rgw-user-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.ts new file mode 100644 index 0000000000000..14bb8232fa485 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.ts @@ -0,0 +1,60 @@ +import { Component } from '@angular/core'; + +import { RgwUserService } from '../../../shared/api/rgw-user.service'; +import { CellTemplate } from '../../../shared/enum/cell-template.enum'; +import { CdTableColumn } from '../../../shared/models/cd-table-column'; +import { CdTableSelection } from '../../../shared/models/cd-table-selection'; + +@Component({ + selector: 'cd-rgw-user-list', + templateUrl: './rgw-user-list.component.html', + styleUrls: ['./rgw-user-list.component.scss'] +}) +export class RgwUserListComponent { + + columns: CdTableColumn[] = []; + users: object[] = []; + selection: CdTableSelection = new CdTableSelection(); + + constructor(private rgwUserService: RgwUserService) { + this.columns = [ + { + name: 'Username', + prop: 'user_id', + flexGrow: 1 + }, + { + name: 'Full name', + prop: 'display_name', + flexGrow: 1 + }, + { + name: 'Email address', + prop: 'email', + flexGrow: 1 + }, + { + name: 'Suspended', + prop: 'suspended', + flexGrow: 1, + cellTransformation: CellTemplate.checkIcon + }, + { + name: 'Max. buckets', + prop: 'max_buckets', + flexGrow: 1 + } + ]; + } + + getUserList() { + this.rgwUserService.list() + .subscribe((resp: object[]) => { + this.users = resp; + }); + } + + updateSelection(selection: CdTableSelection) { + this.selection = selection; + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts index 14577d9c71ff4..6e5aaa5b9d0cd 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts @@ -5,12 +5,18 @@ import { TabsModule } from 'ngx-bootstrap/tabs'; import { SharedModule } from '../../shared/shared.module'; import { PerformanceCounterModule } from '../performance-counter/performance-counter.module'; +import { RgwBucketDetailsComponent } from './rgw-bucket-details/rgw-bucket-details.component'; +import { RgwBucketListComponent } from './rgw-bucket-list/rgw-bucket-list.component'; import { RgwDaemonDetailsComponent } from './rgw-daemon-details/rgw-daemon-details.component'; import { RgwDaemonListComponent } from './rgw-daemon-list/rgw-daemon-list.component'; +import { RgwUserDetailsComponent } from './rgw-user-details/rgw-user-details.component'; +import { RgwUserListComponent } from './rgw-user-list/rgw-user-list.component'; @NgModule({ entryComponents: [ - RgwDaemonDetailsComponent + RgwDaemonDetailsComponent, + RgwBucketDetailsComponent, + RgwUserDetailsComponent ], imports: [ CommonModule, @@ -20,11 +26,19 @@ import { RgwDaemonListComponent } from './rgw-daemon-list/rgw-daemon-list.compon ], exports: [ RgwDaemonListComponent, - RgwDaemonDetailsComponent + RgwDaemonDetailsComponent, + RgwBucketListComponent, + RgwBucketDetailsComponent, + RgwUserListComponent, + RgwUserDetailsComponent ], declarations: [ RgwDaemonListComponent, - RgwDaemonDetailsComponent + RgwDaemonDetailsComponent, + RgwBucketListComponent, + RgwBucketDetailsComponent, + RgwUserListComponent, + RgwUserDetailsComponent ] }) export class RgwModule { } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html index 5ed75f6296b0e..54cde66a9075c 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html @@ -162,15 +162,10 @@ --> -
  • - Object Gateway - -
  • - - + + --> diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/api.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/api.module.ts index 61b1c32e7561b..26656cdd0a03a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/api.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/api.module.ts @@ -11,7 +11,9 @@ import { OsdService } from './osd.service'; import { PoolService } from './pool.service'; import { RbdMirroringService } from './rbd-mirroring.service'; import { RbdService } from './rbd.service'; +import { RgwBucketService } from './rgw-bucket.service'; import { RgwDaemonService } from './rgw-daemon.service'; +import { RgwUserService } from './rgw-user.service'; import { TablePerformanceCounterService } from './table-performance-counter.service'; import { TcmuIscsiService } from './tcmu-iscsi.service'; @@ -29,7 +31,9 @@ import { TcmuIscsiService } from './tcmu-iscsi.service'; PoolService, RbdService, RbdMirroringService, + RgwBucketService, RgwDaemonService, + RgwUserService, TablePerformanceCounterService, TcmuIscsiService ] diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.spec.ts new file mode 100644 index 0000000000000..dadf2f1775867 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.spec.ts @@ -0,0 +1,21 @@ +import { HttpClientModule } from '@angular/common/http'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { inject, TestBed } from '@angular/core/testing'; + +import { RgwBucketService } from './rgw-bucket.service'; + +describe('RgwBucketService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [RgwBucketService], + imports: [HttpClientTestingModule, HttpClientModule] + }); + }); + + it( + 'should be created', + inject([RgwBucketService], (service: RgwBucketService) => { + expect(service).toBeTruthy(); + }) + ); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.ts new file mode 100644 index 0000000000000..adcfc4a0fe933 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.ts @@ -0,0 +1,72 @@ +import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import 'rxjs/add/observable/forkJoin'; +import 'rxjs/add/observable/of'; +import { Observable } from 'rxjs/Observable'; + +import * as _ from 'lodash'; + +@Injectable() +export class RgwBucketService { + + private url = '/api/rgw/proxy/bucket'; + + constructor(private http: HttpClient) { } + + list() { + return this.http.get(this.url) + .flatMap((buckets: string[]) => { + if (buckets.length > 0) { + return Observable.forkJoin( + buckets.map((bucket: string) => { + return this.get(bucket); + }) + ); + } + return Observable.of([]); + }); + } + + get(bucket: string) { + let params = new HttpParams(); + params = params.append('bucket', bucket); + return this.http.get(this.url, { params: params }); + } + + create(bucket: string, uid: string) { + const body = JSON.stringify({ + 'bucket': bucket, + 'uid': uid + }); + return this.http.post(`/api/rgw/bucket`, body); + } + + update(bucketId: string, bucket: string, uid: string) { + let params = new HttpParams(); + params = params.append('bucket', bucket); + params = params.append('bucket-id', bucketId as string); + params = params.append('uid', uid); + return this.http.put(this.url, null, { params: params }); + } + + delete(bucket: string, purgeObjects = true) { + let params = new HttpParams(); + params = params.append('bucket', bucket); + params = params.append('purge-objects', purgeObjects ? 'true' : 'false'); + return this.http.delete(this.url, { params: params }); + } + + find(bucket: string) { + let params = new HttpParams(); + params = params.append('bucket', bucket); + return this.http.get(this.url, { params: params }) + .flatMap((resp: object | null) => { + // Make sure we have received valid data. + if ((null === resp) || (!_.isObjectLike(resp))) { + return Observable.of([]); + } + // Return an array to be able to support wildcard searching someday. + return Observable.of([resp]); + }); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-daemon.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-daemon.service.ts index 907537ef2d9e5..0e1ffba6000b4 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-daemon.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-daemon.service.ts @@ -9,18 +9,10 @@ export class RgwDaemonService { constructor(private http: HttpClient) { } list() { - return this.http.get(this.url) - .toPromise() - .then((resp: any) => { - return resp; - }); + return this.http.get(this.url); } get(id: string) { - return this.http.get(`${this.url}/${id}`) - .toPromise() - .then((resp: any) => { - return resp; - }); + return this.http.get(`${this.url}/${id}`); } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user.service.spec.ts new file mode 100644 index 0000000000000..2942eff5f6661 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user.service.spec.ts @@ -0,0 +1,21 @@ +import { HttpClientModule } from '@angular/common/http'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { inject, TestBed } from '@angular/core/testing'; + +import { RgwUserService } from './rgw-user.service'; + +describe('RgwUserService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [RgwUserService], + imports: [HttpClientTestingModule, HttpClientModule] + }); + }); + + it( + 'should be created', + inject([RgwUserService], (service: RgwUserService) => { + expect(service).toBeTruthy(); + }) + ); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user.service.ts new file mode 100644 index 0000000000000..9585fdfa501a5 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user.service.ts @@ -0,0 +1,43 @@ +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import 'rxjs/add/observable/forkJoin'; +import 'rxjs/add/observable/of'; +import { Observable } from 'rxjs/Observable'; + +@Injectable() +export class RgwUserService { + + private url = '/api/rgw/proxy/user'; + + constructor(private http: HttpClient) { } + + list() { + return this.enumerate() + .flatMap((uids: string[]) => { + if (uids.length > 0) { + return Observable.forkJoin( + uids.map((uid: string) => { + return this.get(uid); + }) + ); + } + return Observable.of([]); + }); + } + + enumerate() { + return this.http.get('/api/rgw/proxy/metadata/user'); + } + + get(uid: string) { + let params = new HttpParams(); + params = params.append('uid', uid); + return this.http.get(this.url, { params: params }); + } + + getQuota(uid: string) { + let params = new HttpParams(); + params = params.append('uid', uid); + return this.http.get(`${this.url}?quota`, {params: params}); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.html index 51dcb0b7f7df9..d9ecb65439a66 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.html @@ -114,6 +114,13 @@ {{ value }} + + + + + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.ts index 7264799099a0e..aa89fb4747a93 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.ts @@ -36,6 +36,7 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O @ViewChild('tableCellBoldTpl') tableCellBoldTpl: TemplateRef; @ViewChild('sparklineTpl') sparklineTpl: TemplateRef; @ViewChild('routerLinkTpl') routerLinkTpl: TemplateRef; + @ViewChild('checkIconTpl') checkIconTpl: TemplateRef; @ViewChild('perSecondTpl') perSecondTpl: TemplateRef; @ViewChild('executingTpl') executingTpl: TemplateRef; @@ -188,6 +189,7 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O _addTemplates() { this.cellTemplates.bold = this.tableCellBoldTpl; + this.cellTemplates.checkIcon = this.checkIconTpl; this.cellTemplates.sparkline = this.sparklineTpl; this.cellTemplates.routerLink = this.routerLinkTpl; this.cellTemplates.perSecond = this.perSecondTpl; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/cell-template.enum.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/cell-template.enum.ts index c02e7ff7de92f..28740a53674de 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/cell-template.enum.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/cell-template.enum.ts @@ -2,6 +2,7 @@ export enum CellTemplate { bold = 'bold', sparkline = 'sparkline', perSecond = 'perSecond', + checkIcon = 'checkIcon', routerLink = 'routerLink', executing = 'executing' } -- 2.39.5