mirror of
https://github.com/ceph/ceph
synced 2025-03-25 11:48:05 +00:00
mgr/dashboard: Display users current quota usage
Fixes: https://tracker.ceph.com/issues/45965 Signed-off-by: Avan Thakkar <athakkar@redhat.com>
This commit is contained in:
parent
ba56112835
commit
9456884c29
@ -62,8 +62,8 @@ class RgwTestCase(DashboardTestCase):
|
||||
cls._radosgw_admin_cmd(['user', 'rm', '--uid=teuth-test-user', '--purge-data'])
|
||||
super(RgwTestCase, cls).tearDownClass()
|
||||
|
||||
def get_rgw_user(self, uid):
|
||||
return self._get('/api/rgw/user/{}'.format(uid))
|
||||
def get_rgw_user(self, uid, stats=True):
|
||||
return self._get('/api/rgw/user/{}?stats={}'.format(uid, stats))
|
||||
|
||||
|
||||
class RgwApiCredentialsTest(RgwTestCase):
|
||||
@ -510,6 +510,13 @@ class RgwUserTest(RgwTestCase):
|
||||
self.assertStatus(200)
|
||||
self._assert_user_data(data)
|
||||
self.assertEqual(data['user_id'], 'admin')
|
||||
self.assertTrue(data['stats'])
|
||||
self.assertIsInstance(data['stats'], dict)
|
||||
# Test without stats.
|
||||
data = self.get_rgw_user('admin', False)
|
||||
self.assertStatus(200)
|
||||
self._assert_user_data(data)
|
||||
self.assertEqual(data['user_id'], 'admin')
|
||||
|
||||
def test_list(self):
|
||||
data = self._get('/api/rgw/user')
|
||||
|
@ -364,9 +364,10 @@ class RgwUser(RgwRESTController):
|
||||
marker = result['marker']
|
||||
return users
|
||||
|
||||
def get(self, uid, daemon_name=None):
|
||||
# type: (str, Optional[str]) -> dict
|
||||
result = self.proxy(daemon_name, 'GET', 'user', {'uid': uid})
|
||||
def get(self, uid, daemon_name=None, stats=True) -> dict:
|
||||
query_params = '?stats' if stats else ''
|
||||
result = self.proxy(daemon_name, 'GET', 'user{}'.format(query_params),
|
||||
{'uid': uid, 'stats': stats})
|
||||
if not self._keys_allowed():
|
||||
del result['keys']
|
||||
del result['swift_keys']
|
||||
|
@ -27,7 +27,8 @@
|
||||
[used]="row.bucket_size">
|
||||
</cd-usage-bar>
|
||||
|
||||
<ng-template #noSizeQuota>No Limit</ng-template>
|
||||
<ng-template #noSizeQuota
|
||||
i18n>No Limit</ng-template>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #bucketObjectTpl
|
||||
@ -38,5 +39,6 @@
|
||||
[isBinary]="false">
|
||||
</cd-usage-bar>
|
||||
|
||||
<ng-template #noObjectQuota>No Limit</ng-template>
|
||||
<ng-template #noObjectQuota
|
||||
i18n>No Limit</ng-template>
|
||||
</ng-template>
|
||||
|
@ -33,14 +33,16 @@ describe('RgwBucketListComponent', () => {
|
||||
beforeEach(() => {
|
||||
rgwBucketService = TestBed.inject(RgwBucketService);
|
||||
rgwBucketServiceListSpy = spyOn(rgwBucketService, 'list');
|
||||
rgwBucketServiceListSpy.and.returnValue(of(null));
|
||||
rgwBucketServiceListSpy.and.returnValue(of([]));
|
||||
fixture = TestBed.createComponent(RgwBucketListComponent);
|
||||
component = fixture.componentInstance;
|
||||
spyOn(component, 'timeConditionReached').and.stub();
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
fixture.detectChanges();
|
||||
expect(component).toBeTruthy();
|
||||
expect(rgwBucketServiceListSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should test all TableActions combinations', () => {
|
||||
@ -109,7 +111,8 @@ describe('RgwBucketListComponent', () => {
|
||||
}
|
||||
])
|
||||
);
|
||||
fixture.detectChanges();
|
||||
component.getBucketList(null);
|
||||
expect(rgwBucketServiceListSpy).toHaveBeenCalledTimes(2);
|
||||
expect(component.buckets).toEqual([
|
||||
{
|
||||
bucket: 'bucket',
|
||||
@ -130,6 +133,7 @@ describe('RgwBucketListComponent', () => {
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should usage bars only if quota enabled', () => {
|
||||
rgwBucketServiceListSpy.and.returnValue(
|
||||
of([
|
||||
@ -144,10 +148,13 @@ describe('RgwBucketListComponent', () => {
|
||||
}
|
||||
])
|
||||
);
|
||||
component.getBucketList(null);
|
||||
expect(rgwBucketServiceListSpy).toHaveBeenCalledTimes(2);
|
||||
fixture.detectChanges();
|
||||
const usageBars = fixture.debugElement.nativeElement.querySelectorAll('cd-usage-bar');
|
||||
expect(usageBars.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should not show any usage bars if quota disabled', () => {
|
||||
rgwBucketServiceListSpy.and.returnValue(
|
||||
of([
|
||||
@ -162,6 +169,8 @@ describe('RgwBucketListComponent', () => {
|
||||
}
|
||||
])
|
||||
);
|
||||
component.getBucketList(null);
|
||||
expect(rgwBucketServiceListSpy).toHaveBeenCalledTimes(2);
|
||||
fixture.detectChanges();
|
||||
const usageBars = fixture.debugElement.nativeElement.querySelectorAll('cd-usage-bar');
|
||||
expect(usageBars.length).toBe(0);
|
||||
|
@ -1,11 +1,4 @@
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
NgZone,
|
||||
OnInit,
|
||||
TemplateRef,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { Component, NgZone, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||
|
||||
import _ from 'lodash';
|
||||
import { forkJoin as observableForkJoin, Observable, Subscriber } from 'rxjs';
|
||||
@ -60,39 +53,13 @@ export class RgwBucketListComponent extends ListWithDetails implements OnInit {
|
||||
private modalService: ModalService,
|
||||
private urlBuilder: URLBuilderService,
|
||||
public actionLabels: ActionLabelsI18n,
|
||||
private ngZone: NgZone,
|
||||
private changeDetectorRef: ChangeDetectorRef
|
||||
private ngZone: NgZone
|
||||
) {
|
||||
super();
|
||||
this.permission = this.authStorageService.getPermissions().rgw;
|
||||
const getBucketUri = () =>
|
||||
this.selection.first() && `${encodeURIComponent(this.selection.first().bid)}`;
|
||||
const addAction: CdTableAction = {
|
||||
permission: 'create',
|
||||
icon: Icons.add,
|
||||
routerLink: () => this.urlBuilder.getCreate(),
|
||||
name: this.actionLabels.CREATE,
|
||||
canBePrimary: (selection: CdTableSelection) => !selection.hasSelection
|
||||
};
|
||||
const editAction: CdTableAction = {
|
||||
permission: 'update',
|
||||
icon: Icons.edit,
|
||||
routerLink: () => this.urlBuilder.getEdit(getBucketUri()),
|
||||
name: this.actionLabels.EDIT
|
||||
};
|
||||
const deleteAction: CdTableAction = {
|
||||
permission: 'delete',
|
||||
icon: Icons.destroy,
|
||||
click: () => this.deleteAction(),
|
||||
disable: () => !this.selection.hasSelection,
|
||||
name: this.actionLabels.DELETE,
|
||||
canBePrimary: (selection: CdTableSelection) => selection.hasMultiSelection
|
||||
};
|
||||
this.tableActions = [addAction, editAction, deleteAction];
|
||||
this.timeConditionReached();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.permission = this.authStorageService.getPermissions().rgw;
|
||||
this.columns = [
|
||||
{
|
||||
name: $localize`Name`,
|
||||
@ -129,6 +96,31 @@ export class RgwBucketListComponent extends ListWithDetails implements OnInit {
|
||||
flexGrow: 0.8
|
||||
}
|
||||
];
|
||||
const getBucketUri = () =>
|
||||
this.selection.first() && `${encodeURIComponent(this.selection.first().bid)}`;
|
||||
const addAction: CdTableAction = {
|
||||
permission: 'create',
|
||||
icon: Icons.add,
|
||||
routerLink: () => this.urlBuilder.getCreate(),
|
||||
name: this.actionLabels.CREATE,
|
||||
canBePrimary: (selection: CdTableSelection) => !selection.hasSelection
|
||||
};
|
||||
const editAction: CdTableAction = {
|
||||
permission: 'update',
|
||||
icon: Icons.edit,
|
||||
routerLink: () => this.urlBuilder.getEdit(getBucketUri()),
|
||||
name: this.actionLabels.EDIT
|
||||
};
|
||||
const deleteAction: CdTableAction = {
|
||||
permission: 'delete',
|
||||
icon: Icons.destroy,
|
||||
click: () => this.deleteAction(),
|
||||
disable: () => !this.selection.hasSelection,
|
||||
name: this.actionLabels.DELETE,
|
||||
canBePrimary: (selection: CdTableSelection) => selection.hasMultiSelection
|
||||
};
|
||||
this.tableActions = [addAction, editAction, deleteAction];
|
||||
this.timeConditionReached();
|
||||
}
|
||||
|
||||
transformBucketData() {
|
||||
@ -171,7 +163,6 @@ export class RgwBucketListComponent extends ListWithDetails implements OnInit {
|
||||
(resp: object[]) => {
|
||||
this.buckets = resp;
|
||||
this.transformBucketData();
|
||||
this.changeDetectorRef.detectChanges();
|
||||
},
|
||||
() => {
|
||||
context.error();
|
||||
|
@ -19,3 +19,26 @@
|
||||
[selection]="expandedRow">
|
||||
</cd-rgw-user-details>
|
||||
</cd-table>
|
||||
|
||||
<ng-template #userSizeTpl
|
||||
let-row="row">
|
||||
<cd-usage-bar *ngIf="row.user_quota.max_size > 0 && row.user_quota.enabled; else noSizeQuota"
|
||||
[total]="row.user_quota.max_size"
|
||||
[used]="row.stats.size_actual">
|
||||
</cd-usage-bar>
|
||||
|
||||
<ng-template #noSizeQuota
|
||||
i18n>No Limit</ng-template>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #userObjectTpl
|
||||
let-row="row">
|
||||
<cd-usage-bar *ngIf="row.user_quota.max_objects > 0 && row.user_quota.enabled; else noObjectQuota"
|
||||
[total]="row.user_quota.max_objects"
|
||||
[used]="row.stats.num_objects"
|
||||
[isBinary]="false">
|
||||
</cd-usage-bar>
|
||||
|
||||
<ng-template #noObjectQuota
|
||||
i18n>No Limit</ng-template>
|
||||
</ng-template>
|
||||
|
@ -4,6 +4,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import { RgwUserService } from '~/app/shared/api/rgw-user.service';
|
||||
import { TableActionsComponent } from '~/app/shared/datatable/table-actions/table-actions.component';
|
||||
import { SharedModule } from '~/app/shared/shared.module';
|
||||
import { configureTestBed, PermissionHelper } from '~/testing/unit-test-helper';
|
||||
@ -12,6 +15,8 @@ import { RgwUserListComponent } from './rgw-user-list.component';
|
||||
describe('RgwUserListComponent', () => {
|
||||
let component: RgwUserListComponent;
|
||||
let fixture: ComponentFixture<RgwUserListComponent>;
|
||||
let rgwUserService: RgwUserService;
|
||||
let rgwUserServiceListSpy: jasmine.Spy;
|
||||
|
||||
configureTestBed({
|
||||
declarations: [RgwUserListComponent],
|
||||
@ -20,13 +25,18 @@ describe('RgwUserListComponent', () => {
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
rgwUserService = TestBed.inject(RgwUserService);
|
||||
rgwUserServiceListSpy = spyOn(rgwUserService, 'list');
|
||||
rgwUserServiceListSpy.and.returnValue(of([]));
|
||||
fixture = TestBed.createComponent(RgwUserListComponent);
|
||||
component = fixture.componentInstance;
|
||||
spyOn(component, 'timeConditionReached').and.stub();
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
fixture.detectChanges();
|
||||
expect(component).toBeTruthy();
|
||||
expect(rgwUserServiceListSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should test all TableActions combinations', () => {
|
||||
@ -70,4 +80,87 @@ describe('RgwUserListComponent', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should test if rgw-user data is tranformed correctly', () => {
|
||||
rgwUserServiceListSpy.and.returnValue(
|
||||
of([
|
||||
{
|
||||
user_id: 'testid',
|
||||
stats: {
|
||||
size_actual: 6,
|
||||
num_objects: 6
|
||||
},
|
||||
user_quota: {
|
||||
max_size: 20,
|
||||
max_objects: 10,
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
])
|
||||
);
|
||||
component.getUserList(null);
|
||||
expect(rgwUserServiceListSpy).toHaveBeenCalledTimes(2);
|
||||
expect(component.users).toEqual([
|
||||
{
|
||||
user_id: 'testid',
|
||||
stats: {
|
||||
size_actual: 6,
|
||||
num_objects: 6
|
||||
},
|
||||
user_quota: {
|
||||
max_size: 20,
|
||||
max_objects: 10,
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should usage bars only if quota enabled', () => {
|
||||
rgwUserServiceListSpy.and.returnValue(
|
||||
of([
|
||||
{
|
||||
user_id: 'testid',
|
||||
stats: {
|
||||
size_actual: 6,
|
||||
num_objects: 6
|
||||
},
|
||||
user_quota: {
|
||||
max_size: 1024,
|
||||
max_objects: 10,
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
])
|
||||
);
|
||||
component.getUserList(null);
|
||||
expect(rgwUserServiceListSpy).toHaveBeenCalledTimes(2);
|
||||
fixture.detectChanges();
|
||||
const usageBars = fixture.debugElement.nativeElement.querySelectorAll('cd-usage-bar');
|
||||
expect(usageBars.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should not show any usage bars if quota disabled', () => {
|
||||
rgwUserServiceListSpy.and.returnValue(
|
||||
of([
|
||||
{
|
||||
user_id: 'testid',
|
||||
stats: {
|
||||
size_actual: 6,
|
||||
num_objects: 6
|
||||
},
|
||||
user_quota: {
|
||||
max_size: 1024,
|
||||
max_objects: 10,
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
])
|
||||
);
|
||||
component.getUserList(null);
|
||||
expect(rgwUserServiceListSpy).toHaveBeenCalledTimes(2);
|
||||
fixture.detectChanges();
|
||||
const usageBars = fixture.debugElement.nativeElement.querySelectorAll('cd-usage-bar');
|
||||
expect(usageBars.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Component, NgZone, ViewChild } from '@angular/core';
|
||||
import { Component, NgZone, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||
|
||||
import * as _ from 'lodash';
|
||||
import { forkJoin as observableForkJoin, Observable, Subscriber } from 'rxjs';
|
||||
|
||||
import { RgwUserService } from '~/app/shared/api/rgw-user.service';
|
||||
@ -27,9 +28,13 @@ const BASE_URL = 'rgw/user';
|
||||
styleUrls: ['./rgw-user-list.component.scss'],
|
||||
providers: [{ provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) }]
|
||||
})
|
||||
export class RgwUserListComponent extends ListWithDetails {
|
||||
export class RgwUserListComponent extends ListWithDetails implements OnInit {
|
||||
@ViewChild(TableComponent, { static: true })
|
||||
table: TableComponent;
|
||||
@ViewChild('userSizeTpl', { static: true })
|
||||
userSizeTpl: TemplateRef<any>;
|
||||
@ViewChild('userObjectTpl', { static: true })
|
||||
userObjectTpl: TemplateRef<any>;
|
||||
permission: Permission;
|
||||
tableActions: CdTableAction[];
|
||||
columns: CdTableColumn[] = [];
|
||||
@ -47,6 +52,9 @@ export class RgwUserListComponent extends ListWithDetails {
|
||||
private ngZone: NgZone
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.permission = this.authStorageService.getPermissions().rgw;
|
||||
this.columns = [
|
||||
{
|
||||
@ -85,6 +93,18 @@ export class RgwUserListComponent extends ListWithDetails {
|
||||
'-1': $localize`Disabled`,
|
||||
0: $localize`Unlimited`
|
||||
}
|
||||
},
|
||||
{
|
||||
name: $localize`Capacity Limit %`,
|
||||
prop: 'size_usage',
|
||||
cellTemplate: this.userSizeTpl,
|
||||
flexGrow: 0.8
|
||||
},
|
||||
{
|
||||
name: $localize`Object Limit %`,
|
||||
prop: 'object_usage',
|
||||
cellTemplate: this.userObjectTpl,
|
||||
flexGrow: 0.8
|
||||
}
|
||||
];
|
||||
const getUserUri = () =>
|
||||
|
@ -7940,6 +7940,11 @@ paths:
|
||||
name: daemon_name
|
||||
schema:
|
||||
type: string
|
||||
- default: true
|
||||
in: query
|
||||
name: stats
|
||||
schema:
|
||||
type: boolean
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
|
Loading…
Reference in New Issue
Block a user