Add server driven pagination to registries page (#14581)

Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
Will Sun 2021-04-07 14:35:53 +08:00 committed by GitHub
parent 66e0246f81
commit 85c08d62a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 107 additions and 48 deletions

View File

@ -193,6 +193,9 @@ export class CreateEditEndpointComponent
this.onGoing = false;
// Reset data
if (this.targetForm && this.targetForm.controls && this.targetForm.controls.targetName) {
this.targetForm.controls.targetName.reset();
}
this.target = this.initEndpoint();
this.initVal = this.initEndpoint();
this.formValues = null;

View File

@ -12,21 +12,21 @@
</div>
</div>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<clr-datagrid [clrDgLoading]="loading" [(clrDgSelected)]="selectedRow">
<clr-datagrid (clrDgRefresh)="retrieve($event)" [clrDgLoading]="loading" [(clrDgSelected)]="selectedRow">
<clr-dg-action-bar>
<button id="add" type="button" class="btn btn-secondary" (click)="openModal()"><clr-icon shape="plus" size="16"></clr-icon>&nbsp;{{'DESTINATION.NEW_ENDPOINT' | translate}}</button>
<button id="edit" type="button" class="btn btn-secondary" [disabled]="!(selectedRow.length ===1)" (click)="editTargets(selectedRow)" ><clr-icon shape="pencil" size="16"></clr-icon>&nbsp;{{'DESTINATION.EDIT' | translate}}</button>
<button id="delete" type="button" class="btn btn-secondary" [disabled]="!selectedRow.length" (click)="deleteTargets(selectedRow)"><clr-icon shape="times" size="16"></clr-icon>&nbsp;{{'DESTINATION.DELETE' | translate}}</button>
</clr-dg-action-bar>
<clr-dg-column [clrDgField]="'name'" class="flex-min-width">{{'DESTINATION.NAME' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'status'" class="flex-min-width">{{'DESTINATION.STATUS' | translate}}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="'name'" class="flex-min-width">{{'DESTINATION.NAME' | translate}}</clr-dg-column>
<clr-dg-column class="flex-min-width">{{'DESTINATION.STATUS' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'url'" class="flex-min-width">{{'DESTINATION.URL' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'type'">{{'DESTINATION.PROVIDER' | translate}}</clr-dg-column>
<clr-dg-column>{{'DESTINATION.PROVIDER' | translate}}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="'insecure'">{{'CONFIG.VERIFY_REMOTE_CERT' | translate }}</clr-dg-column>
<clr-dg-column [clrDgField]="'credential.type'">{{'DESTINATION.AUTHENTICATION' | translate }}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="creationTimeComparator">{{'DESTINATION.CREATION_TIME' | translate}}</clr-dg-column>
<clr-dg-column>{{'DESTINATION.AUTHENTICATION' | translate }}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="'creation_time'">{{'DESTINATION.CREATION_TIME' | translate}}</clr-dg-column>
<clr-dg-placeholder>{{'DESTINATION.PLACEHOLDER' | translate }}</clr-dg-placeholder>
<clr-dg-row *clrDgItems="let t of targets" [clrDgItem]='t'>
<clr-dg-row *ngFor="let t of targets" [clrDgItem]='t'>
<clr-dg-cell class="flex-min-width">{{t.name}}</clr-dg-cell>
<clr-dg-cell class="flex-min-width">
<span *ngIf="t.status === 'healthy';else elseBlock" class="label label-success">{{'SCANNER.HEALTHY' | translate}}</span>
@ -53,15 +53,15 @@
<clr-dg-cell>{{t.creation_time | harborDatetime: 'short'}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
<clr-dg-pagination #pagination [clrDgPageSize]="15">
<clr-dg-pagination #pagination [clrDgPageSize]="pageSize" [(clrDgPage)]="page" [clrDgTotalItems]="total">
<clr-dg-page-size [clrPageSizeOptions]="[15,25,50]">{{"PAGINATION.PAGE_SIZE" | translate}}</clr-dg-page-size>
<span *ngIf="targets?.length">{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'DESTINATION.OF' | translate}}</span>
{{targets?.length}} {{'DESTINATION.ITEMS' | translate}}
<span *ngIf="total">{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'DESTINATION.OF' | translate}}</span>
{{total}} {{'DESTINATION.ITEMS' | translate}}
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div>
</div>
<confirmation-dialog #confirmationDialog (confirmAction)="confirmDeletion($event)"></confirmation-dialog>
<hbr-create-edit-endpoint (reload)="reload($event)"></hbr-create-edit-endpoint>
<hbr-create-edit-endpoint (reload)="refreshTargets()"></hbr-create-edit-endpoint>
</div>

View File

@ -3,15 +3,16 @@ import { NO_ERRORS_SCHEMA } from "@angular/core";
import { EndpointComponent } from "./endpoint.component";
import { CreateEditEndpointComponent } from "./create-edit-endpoint/create-edit-endpoint.component";
import { ErrorHandler } from "../../../shared/units/error-handler";
import { Endpoint } from "../../../shared/services";
import { OperationService } from "../../../shared/components/operation/operation.service";
import { click } from "../../../shared/units/utils";
import { of } from "rxjs";
import { HttpClient } from "@angular/common/http";
import { HttpClient, HttpHeaders, HttpResponse } from "@angular/common/http";
import { AppConfigService } from '../../../services/app-config.service';
import { SharedTestingModule } from "../../../shared/shared.module";
import { ADAPTERS_MAP, EndpointService } from "../../../shared/services/endpoint.service";
import { delay } from "rxjs/operators";
import { RegistryService } from "../../../../../ng-swagger-gen/services/registry.service";
import { Registry } from "../../../../../ng-swagger-gen/models/registry";
describe("EndpointComponent (inline template)", () => {
let adapterInfoMockData = {
@ -235,7 +236,7 @@ describe("EndpointComponent (inline template)", () => {
return of(adapterInfoMockData).pipe(delay(0));
}
};
let mockData: Endpoint[] = [
let mockData: Registry[] = [
{
id: 1,
credential: {
@ -303,7 +304,7 @@ describe("EndpointComponent (inline template)", () => {
const mockedEndpointService = {
getEndpoints(targetName: string) {
if (targetName) {
const endpoints: Endpoint[] = [];
const endpoints: Registry[] = [];
mockData.forEach( item => {
if (item.name.indexOf(targetName) !== -1) {
endpoints.push(item);
@ -321,7 +322,7 @@ describe("EndpointComponent (inline template)", () => {
},
getEndpoint(endPointId: number | string) {
if (endPointId) {
let endpoint: Endpoint;
let endpoint: Registry;
mockData.forEach( item => {
if (item.id === endPointId) {
endpoint = item;
@ -338,6 +339,28 @@ describe("EndpointComponent (inline template)", () => {
return adapter;
}
};
const mockRegistryService = {
listRegistriesResponse(param?: RegistryService.ListRegistriesParams) {
if (param && param.q) {
const endpoints: Registry[] = [];
mockData.forEach( item => {
if (param.q.indexOf(item.name) !== -1) {
endpoints.push(item);
}
});
const response: HttpResponse<Array<Registry>> = new HttpResponse<Array<Registry>>({
headers: new HttpHeaders({'x-total-count': endpoints.length.toString()}),
body: endpoints
});
return of(response).pipe(delay(0));
}
const res: HttpResponse<Array<Registry>> = new HttpResponse<Array<Registry>>({
headers: new HttpHeaders({'x-total-count': '3'}),
body: mockData
});
return of(res).pipe(delay(0));
}
};
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [SharedTestingModule],
@ -351,6 +374,7 @@ describe("EndpointComponent (inline template)", () => {
{ provide: OperationService },
{ provide: HttpClient, useValue: fakedHttp },
{ provide: AppConfigService, useValue: mockAppConfigService },
{ provide: RegistryService, useValue: mockRegistryService },
],
schemas: [
NO_ERRORS_SCHEMA

View File

@ -20,7 +20,6 @@ import {
import { Subscription, Observable, forkJoin, throwError as observableThrowError } from "rxjs";
import { TranslateService } from "@ngx-translate/core";
import { Comparator } from "../../../shared/services";
import { Endpoint } from "../../../shared/services";
import { ErrorHandler } from "../../../shared/units/error-handler";
import { map, catchError, finalize } from "rxjs/operators";
import { ConfirmationDialogComponent } from "../../../shared/components/confirmation-dialog";
@ -30,13 +29,16 @@ import {
ConfirmationButtons
} from "../../../shared/entities/shared.const";
import { CreateEditEndpointComponent } from "./create-edit-endpoint/create-edit-endpoint.component";
import { CustomComparator } from "../../../shared/units/utils";
import { CustomComparator, DEFAULT_PAGE_SIZE, getSortingString } from "../../../shared/units/utils";
import { operateChanges, OperateInfo, OperationState } from "../../../shared/components/operation/operate";
import { OperationService } from "../../../shared/components/operation/operation.service";
import { errorHandler } from "../../../shared/units/shared.utils";
import { ConfirmationMessage } from "../../global-confirmation-dialog/confirmation-message";
import { ConfirmationAcknowledgement } from "../../global-confirmation-dialog/confirmation-state-message";
import { EndpointService, HELM_HUB } from "../../../shared/services/endpoint.service";
import { RegistryService } from "../../../../../ng-swagger-gen/services/registry.service";
import { ClrDatagridStateInterface } from "@clr/angular";
import { Registry } from "../../../../../ng-swagger-gen/models/registry";
@Component({
@ -51,23 +53,23 @@ export class EndpointComponent implements OnInit, OnDestroy {
@ViewChild("confirmationDialog")
confirmationDialogComponent: ConfirmationDialogComponent;
targets: Endpoint[];
target: Endpoint;
targets: Registry[];
target: Registry;
targetName: string;
subscription: Subscription;
loading: boolean = false;
loading: boolean = true;
creationTimeComparator: Comparator<Endpoint> = new CustomComparator<Endpoint>(
creationTimeComparator: Comparator<Registry> = new CustomComparator<Registry>(
"creation_time",
"date"
);
timerHandler: any;
selectedRow: Endpoint[] = [];
selectedRow: Registry[] = [];
get initEndpoint(): Endpoint {
get initEndpoint(): Registry {
return {
credential: {
access_key: "",
@ -82,15 +84,18 @@ export class EndpointComponent implements OnInit, OnDestroy {
};
}
constructor(private endpointService: EndpointService,
pageSize: number = DEFAULT_PAGE_SIZE;
page: number = 1;
total: number = 0;
constructor(private endpointService: RegistryService,
private errorHandlerEntity: ErrorHandler,
private translateService: TranslateService,
private operationService: OperationService) {
private operationService: OperationService,
private oldEndpointService: EndpointService) {
}
ngOnInit(): void {
this.targetName = "";
this.retrieve();
}
ngOnDestroy(): void {
@ -98,14 +103,42 @@ export class EndpointComponent implements OnInit, OnDestroy {
this.subscription.unsubscribe();
}
}
retrieve(): void {
this.loading = true;
retrieve(state?: ClrDatagridStateInterface): void {
this.selectedRow = [];
this.endpointService.getEndpoints(this.targetName).pipe(finalize(() => {
let q: string = '';
if (state && state.filters && state.filters.length) {
this.targetName = '';
q = encodeURIComponent(`${state.filters[0].property}=~${state.filters[0].value}`);
} else if (this.targetName) {
q = `name=~${this.targetName}`;
}
if (state && state.page) {
this.pageSize = state.page.size;
}
let sort: string;
if (state && state.sort && state.sort.by) {
sort = getSortingString(state);
} else { // sort by creation_time desc by default
sort = `-creation_time`;
}
this.loading = true;
this.endpointService.listRegistriesResponse({
q: q,
pageSize: this.pageSize,
page: this.page,
sort: sort
}).pipe(finalize(() => {
this.loading = false;
}))
.subscribe(targets => {
this.targets = targets || [];
.subscribe(response => {
// Get total count
if (response.headers) {
let xHeader: string = response.headers.get("X-Total-Count");
if (xHeader) {
this.total = parseInt(xHeader, 0);
}
}
this.targets = response.body || [];
}, error => {
this.errorHandlerEntity.error(error);
});
@ -113,24 +146,25 @@ export class EndpointComponent implements OnInit, OnDestroy {
doSearchTargets(targetName: string) {
this.targetName = targetName;
this.page = 1;
this.total = 0;
this.selectedRow = [];
this.retrieve();
}
refreshTargets() {
this.retrieve();
}
reload($event: any) {
this.targetName = "";
this.page = 1;
this.total = 0;
this.selectedRow = [];
this.retrieve();
}
openModal() {
this.createEditEndpointComponent.openCreateEditTarget(true);
this.target = this.initEndpoint;
}
editTargets(targets: Endpoint[]) {
editTargets(targets: Registry[]) {
if (targets && targets.length === 1) {
let target = targets[0];
let editable = true;
@ -142,7 +176,7 @@ export class EndpointComponent implements OnInit, OnDestroy {
}
}
deleteTargets(targets: Endpoint[]) {
deleteTargets(targets: Registry[]) {
if (targets && targets.length) {
let targetNames: string[] = [];
targets.forEach(target => {
@ -163,7 +197,7 @@ export class EndpointComponent implements OnInit, OnDestroy {
if (message &&
message.source === ConfirmationTargets.TARGET &&
message.state === ConfirmationState.CONFIRMED) {
let targetLists: Endpoint[] = message.data;
let targetLists: Registry[] = message.data;
if (targetLists && targetLists.length) {
let observableLists: any[] = [];
targetLists.forEach(target => {
@ -171,8 +205,7 @@ export class EndpointComponent implements OnInit, OnDestroy {
});
forkJoin(...observableLists)
.pipe(finalize(() => {
this.selectedRow = [];
this.reload(true);
this.refreshTargets();
}))
.subscribe((item) => {
}, error => {
@ -181,7 +214,7 @@ export class EndpointComponent implements OnInit, OnDestroy {
}
}
}
delOperate(target: Endpoint): Observable<any> {
delOperate(target: Registry): Observable<any> {
// init operation info
let operMessage = new OperateInfo();
operMessage.name = 'OPERATION.DELETE_REGISTRY';
@ -189,10 +222,9 @@ export class EndpointComponent implements OnInit, OnDestroy {
operMessage.state = OperationState.progressing;
operMessage.data.name = target.name;
this.operationService.publishInfo(operMessage);
return this.endpointService
.deleteEndpoint(target.id)
.pipe(map(
return this.endpointService.deleteRegistry({
id: target.id
}).pipe(map(
response => {
this.translateService.get('BATCH.DELETED_SUCCESS')
.subscribe(res => {
@ -209,7 +241,7 @@ export class EndpointComponent implements OnInit, OnDestroy {
));
}
getAdapterText(adapter: string): string {
return this.endpointService.getAdapterText(adapter);
return this.oldEndpointService.getAdapterText(adapter);
}
isHelmHub(str: string): boolean {
return str === HELM_HUB;