<!-- templates -->
<ng-template #poolUsageTpl
let-row="row">
- <cd-usage-bar [totalBytes]="row.size"
- [usedBytes]="row.used"></cd-usage-bar>
+ <cd-usage-bar [total]="row.size"
+ [used]="row.used"></cd-usage-bar>
</ng-template>
<ng-template #activityTmpl
<ng-template #osdUsageTpl
let-row="row">
- <cd-usage-bar [totalBytes]="row.stats.stat_bytes"
- [usedBytes]="row.stats.stat_bytes_used">
+ <cd-usage-bar [total]="row.stats.stat_bytes"
+ [used]="row.stats.stat_bytes_used">
</cd-usage-bar>
</ng-template>
</tab>
i18n>The {selection.hasSingleSelection, select, true {OSD is} false {OSDs are}} not safe to be
{{ actionDescription }}! {{ message }}</cd-alert-panel>
</div>
- <ng-container i18n><strong>OSD {{ osdIds | join }}</strong> will be
+ <ng-container i18n><strong>OSD {{ getSelectedOsdIds() | join }}</strong> will be
<strong>{{ actionDescription }}</strong> if you proceed.</ng-container>
</ng-template>
-
-<ng-template #deleteOsdExtraTpl
- let-form="form">
- <ng-container [formGroup]="form">
- <ng-container formGroupName="child">
- <div class="form-group">
- <div class="custom-control custom-checkbox">
- <input type="checkbox"
- class="custom-control-input"
- name="preserve"
- id="preserve"
- formControlName="preserve"
- autofocus>
- <label class="custom-control-label"
- for="preserve"
- i18n>Preserve OSD ID(s) for replacement.</label>
- </div>
- </div>
- </ng-container>
- </ng-container>
-</ng-template>
<ng-template #poolUsageTpl
let-row="row">
<cd-usage-bar *ngIf="row.stats?.max_avail?.latest"
- [totalBytes]="row.stats.bytes_used.latest + row.stats.max_avail.latest"
- [usedBytes]="row.stats.bytes_used.latest">
+ [total]="row.stats.bytes_used.latest + row.stats.max_avail.latest"
+ [used]="row.stats.bytes_used.latest">
</cd-usage-bar>
</ng-template>
</tab>
[selection]="expandedRow">
</cd-rgw-bucket-details>
</cd-table>
+
+<ng-template #bucketSizeTpl
+ let-row="row">
+ <cd-usage-bar *ngIf="row.bucket_quota.max_size > 0 && row.bucket_quota.enabled; else noSizeQuota"
+ [total]="row.bucket_quota.max_size"
+ [used]="row.bucket_size">
+ </cd-usage-bar>
+
+ <ng-template #noSizeQuota>No Limit</ng-template>
+</ng-template>
+
+<ng-template #bucketObjectTpl
+ let-row="row">
+ <cd-usage-bar *ngIf="row.bucket_quota.max_objects > 0 && row.bucket_quota.enabled; else noObjectQuota"
+ [total]="row.bucket_quota.max_objects"
+ [used]="row.num_objects"
+ [isBinary]="false">
+ </cd-usage-bar>
+
+ <ng-template #noObjectQuota>No Limit</ng-template>
+</ng-template>
import { ModalModule } from 'ngx-bootstrap/modal';
import { TabsModule } from 'ngx-bootstrap/tabs';
+import { of } from 'rxjs';
import {
configureTestBed,
i18nProviders,
PermissionHelper
} from '../../../../testing/unit-test-helper';
+import { RgwBucketService } from '../../../shared/api/rgw-bucket.service';
import { TableActionsComponent } from '../../../shared/datatable/table-actions/table-actions.component';
import { SharedModule } from '../../../shared/shared.module';
import { RgwBucketDetailsComponent } from '../rgw-bucket-details/rgw-bucket-details.component';
describe('RgwBucketListComponent', () => {
let component: RgwBucketListComponent;
let fixture: ComponentFixture<RgwBucketListComponent>;
+ let rgwBucketService: RgwBucketService;
+ let rgwBucketServiceListSpy: jasmine.Spy;
configureTestBed({
declarations: [RgwBucketListComponent, RgwBucketDetailsComponent],
});
beforeEach(() => {
+ rgwBucketService = TestBed.get(RgwBucketService);
+ rgwBucketServiceListSpy = spyOn(rgwBucketService, 'list');
+ rgwBucketServiceListSpy.and.returnValue(of(null));
fixture = TestBed.createComponent(RgwBucketListComponent);
component = fixture.componentInstance;
});
}
});
});
+
+ it('should test if bucket data is tranformed correctly', () => {
+ rgwBucketServiceListSpy.and.returnValue(
+ of([
+ {
+ bucket: 'bucket',
+ owner: 'testid',
+ usage: {
+ 'rgw.main': {
+ size_actual: 4,
+ num_objects: 2
+ },
+ 'rgw.another': {
+ size_actual: 6,
+ num_objects: 6
+ }
+ },
+ bucket_quota: {
+ max_size: 20,
+ max_objects: 10,
+ enabled: true
+ }
+ }
+ ])
+ );
+ fixture.detectChanges();
+ expect(component.buckets).toEqual([
+ {
+ bucket: 'bucket',
+ owner: 'testid',
+ usage: {
+ 'rgw.main': { size_actual: 4, num_objects: 2 },
+ 'rgw.another': { size_actual: 6, num_objects: 6 }
+ },
+ bucket_quota: {
+ max_size: 20,
+ max_objects: 10,
+ enabled: true
+ },
+ bucket_size: 10,
+ num_objects: 8,
+ size_usage: 0.5,
+ object_usage: 0.8
+ }
+ ]);
+ });
+ it('should usage bars only if quota enabled', () => {
+ rgwBucketServiceListSpy.and.returnValue(
+ of([
+ {
+ bucket: 'bucket',
+ owner: 'testid',
+ bucket_quota: {
+ max_size: 1024,
+ max_objects: 10,
+ enabled: true
+ }
+ }
+ ])
+ );
+ 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([
+ {
+ bucket: 'bucket',
+ owner: 'testid',
+ bucket_quota: {
+ max_size: 1024,
+ max_objects: 10,
+ enabled: false
+ }
+ }
+ ])
+ );
+ fixture.detectChanges();
+ const usageBars = fixture.debugElement.nativeElement.querySelectorAll('cd-usage-bar');
+ expect(usageBars.length).toBe(0);
+ });
});
-import { Component, NgZone, ViewChild } from '@angular/core';
+import {
+ ChangeDetectorRef,
+ Component,
+ NgZone,
+ OnInit,
+ TemplateRef,
+ ViewChild
+} from '@angular/core';
import { I18n } from '@ngx-translate/i18n-polyfill';
+import * as _ from 'lodash';
import { BsModalService } from 'ngx-bootstrap/modal';
import { forkJoin as observableForkJoin, Observable, Subscriber } from 'rxjs';
import { CdTableFetchDataContext } from '../../../shared/models/cd-table-fetch-data-context';
import { CdTableSelection } from '../../../shared/models/cd-table-selection';
import { Permission } from '../../../shared/models/permissions';
+import { DimlessBinaryPipe } from '../../../shared/pipes/dimless-binary.pipe';
+import { DimlessPipe } from '../../../shared/pipes/dimless.pipe';
import { AuthStorageService } from '../../../shared/services/auth-storage.service';
import { URLBuilderService } from '../../../shared/services/url-builder.service';
styleUrls: ['./rgw-bucket-list.component.scss'],
providers: [{ provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) }]
})
-export class RgwBucketListComponent extends ListWithDetails {
+export class RgwBucketListComponent extends ListWithDetails implements OnInit {
@ViewChild(TableComponent, { static: true })
table: TableComponent;
+ @ViewChild('bucketSizeTpl', { static: true })
+ bucketSizeTpl: TemplateRef<any>;
+ @ViewChild('bucketObjectTpl', { static: true })
+ bucketObjectTpl: TemplateRef<any>;
permission: Permission;
tableActions: CdTableAction[];
constructor(
private authStorageService: AuthStorageService,
+ private dimlessBinaryPipe: DimlessBinaryPipe,
+ private dimlessPipe: DimlessPipe,
private rgwBucketService: RgwBucketService,
private bsModalService: BsModalService,
private i18n: I18n,
private urlBuilder: URLBuilderService,
public actionLabels: ActionLabelsI18n,
- private ngZone: NgZone
+ private ngZone: NgZone,
+ private changeDetectorRef: ChangeDetectorRef
) {
super();
this.permission = this.authStorageService.getPermissions().rgw;
- this.columns = [
- {
- name: this.i18n('Name'),
- prop: 'bid',
- flexGrow: 1
- },
- {
- name: this.i18n('Owner'),
- prop: 'owner',
- flexGrow: 1
- }
- ];
const getBucketUri = () =>
this.selection.first() && `${encodeURIComponent(this.selection.first().bid)}`;
const addAction: CdTableAction = {
this.timeConditionReached();
}
+ ngOnInit() {
+ this.columns = [
+ {
+ name: this.i18n('Name'),
+ prop: 'bid',
+ flexGrow: 2
+ },
+ {
+ name: this.i18n('Owner'),
+ prop: 'owner',
+ flexGrow: 3
+ },
+ {
+ name: this.i18n('Used Capacity'),
+ prop: 'bucket_size',
+ flexGrow: 0.5,
+ pipe: this.dimlessBinaryPipe
+ },
+ {
+ name: this.i18n('Capacity Limit %'),
+ prop: 'size_usage',
+ cellTemplate: this.bucketSizeTpl,
+ flexGrow: 1
+ },
+ {
+ name: this.i18n('Objects'),
+ prop: 'num_objects',
+ flexGrow: 0.5,
+ pipe: this.dimlessPipe
+ },
+ {
+ name: this.i18n('Object Limit %'),
+ prop: 'object_usage',
+ cellTemplate: this.bucketObjectTpl,
+ flexGrow: 1
+ }
+ ];
+ }
+
+ transformBucketData() {
+ _.forEach(this.buckets, (bucketKey) => {
+ const usageList = bucketKey['usage'];
+ const maxBucketSize = bucketKey['bucket_quota']['max_size'];
+ const maxBucketObjects = bucketKey['bucket_quota']['max_objects'];
+ let totalBucketSize = 0;
+ let numOfObjects = 0;
+ _.forEach(usageList, (usageKey) => {
+ totalBucketSize = totalBucketSize + usageKey.size_actual;
+ numOfObjects = numOfObjects + usageKey.num_objects;
+ });
+ bucketKey['bucket_size'] = totalBucketSize;
+ bucketKey['num_objects'] = numOfObjects;
+ bucketKey['size_usage'] = maxBucketSize > 0 ? totalBucketSize / maxBucketSize : undefined;
+ bucketKey['object_usage'] =
+ maxBucketObjects > 0 ? numOfObjects / maxBucketObjects : undefined;
+ });
+ }
+
timeConditionReached() {
clearTimeout(this.staleTimeout);
this.ngZone.runOutsideAngular(() => {
this.rgwBucketService.list().subscribe(
(resp: object[]) => {
this.buckets = resp;
+ this.transformBucketData();
+ this.changeDetectorRef.detectChanges();
},
() => {
context.error();
<table>
<tr>
<td class="text-left">Used: </td>
- <td class="text-right"><strong> {{ usedBytes | dimlessBinary }}</strong></td>
+ <td class="text-right"><strong> {{ isBinary ? (used | dimlessBinary) : (used | dimless) }}</strong></td>
</tr>
<tr>
<td class="text-left">Free: </td>
- <td class="'text-right"><strong>{{ freeBytes | dimlessBinary }}</strong></td>
+ <td class="'text-right"><strong>{{ isBinary ? (total - used | dimlessBinary) : (total - used | dimless) }}</strong></td>
</tr>
</table>
</ng-template>
})
export class UsageBarComponent implements OnChanges {
@Input()
- totalBytes: number;
+ total: number;
@Input()
- usedBytes: number;
+ used: number;
+ @Input()
+ isBinary = true;
usedPercentage: number;
freePercentage: number;
- freeBytes: number;
constructor() {}
ngOnChanges() {
- this.usedPercentage = Math.round((this.usedBytes / this.totalBytes) * 100);
+ this.usedPercentage = this.total > 0 ? Math.round((this.used / this.total) * 100) : 0;
this.freePercentage = 100 - this.usedPercentage;
- this.freeBytes = this.totalBytes - this.usedBytes;
}
}