mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-27 02:58:05 +01:00
Support accessory recursion for artifact list (#18366)
1. Update accessory UI to support recursion Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
parent
321d8a0885
commit
53d86f872e
@ -546,7 +546,6 @@
|
|||||||
ngProjectAs="clr-dg-row-detail"
|
ngProjectAs="clr-dg-row-detail"
|
||||||
*ngIf="artifact?.accessories?.length">
|
*ngIf="artifact?.accessories?.length">
|
||||||
<sub-accessories
|
<sub-accessories
|
||||||
(deleteAccessory)="deleteAccessory($event)"
|
|
||||||
[projectName]="projectName"
|
[projectName]="projectName"
|
||||||
[repositoryName]="repoName"
|
[repositoryName]="repoName"
|
||||||
[artifactDigest]="artifact?.digest"
|
[artifactDigest]="artifact?.digest"
|
||||||
|
@ -189,6 +189,7 @@ export class ArtifactListTabComponent
|
|||||||
);
|
);
|
||||||
copiedHiddenArray: boolean[] = [];
|
copiedHiddenArray: boolean[] = [];
|
||||||
private _hasViewInit: boolean = false;
|
private _hasViewInit: boolean = false;
|
||||||
|
deleteAccessorySub: Subscription;
|
||||||
constructor(
|
constructor(
|
||||||
private errorHandlerService: ErrorHandler,
|
private errorHandlerService: ErrorHandler,
|
||||||
private artifactService: ArtifactService,
|
private artifactService: ArtifactService,
|
||||||
@ -242,6 +243,14 @@ export class ArtifactListTabComponent
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (!this.deleteAccessorySub) {
|
||||||
|
this.deleteAccessorySub = this.eventService.subscribe(
|
||||||
|
HarborEvent.DELETE_ACCESSORY,
|
||||||
|
(a: Accessory) => {
|
||||||
|
this.deleteAccessory(a);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
@ -253,6 +262,10 @@ export class ArtifactListTabComponent
|
|||||||
this.updateArtifactSub.unsubscribe();
|
this.updateArtifactSub.unsubscribe();
|
||||||
this.updateArtifactSub = null;
|
this.updateArtifactSub = null;
|
||||||
}
|
}
|
||||||
|
if (this.deleteAccessorySub) {
|
||||||
|
this.deleteAccessorySub.unsubscribe();
|
||||||
|
this.deleteAccessorySub = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
get withNotary(): boolean {
|
get withNotary(): boolean {
|
||||||
return this.appConfigService.getConfig()?.with_notary;
|
return this.appConfigService.getConfig()?.with_notary;
|
||||||
|
@ -16,10 +16,14 @@
|
|||||||
</button>
|
</button>
|
||||||
</clr-dg-action-overflow>
|
</clr-dg-action-overflow>
|
||||||
<clr-dg-cell class="relative">
|
<clr-dg-cell class="relative">
|
||||||
|
<!--it will cause a ExpressionChangedAfterItHasBeenCheckedError when read datagrid['el']?.nativeElement?.offsetHeight so
|
||||||
|
use OnPush Strategy to avoid ExpressionChangedAfterItHasBeenCheckedError-->
|
||||||
<hr
|
<hr
|
||||||
class="y-dash-line"
|
class="y-dash-line"
|
||||||
*ngIf="i === displayedAccessories?.length - 1 && viewInit"
|
[hidden]="i !== displayedAccessories?.length - 1"
|
||||||
[style.height.px]="dashLineHeight" />
|
[style.height.px]="
|
||||||
|
datagrid['el']?.nativeElement?.offsetHeight || 0
|
||||||
|
" />
|
||||||
<hr class="x-dash-line" />
|
<hr class="x-dash-line" />
|
||||||
<div class="cell">
|
<div class="cell">
|
||||||
<div
|
<div
|
||||||
@ -55,6 +59,17 @@
|
|||||||
{{ a.creation_time | harborDatetime : 'short' }}
|
{{ a.creation_time | harborDatetime : 'short' }}
|
||||||
</div>
|
</div>
|
||||||
</clr-dg-cell>
|
</clr-dg-cell>
|
||||||
|
<ng-container
|
||||||
|
ngProjectAs="clr-dg-row-detail"
|
||||||
|
*ngIf="a?.accessories?.length">
|
||||||
|
<sub-accessories
|
||||||
|
[projectName]="projectName"
|
||||||
|
[repositoryName]="repositoryName"
|
||||||
|
[artifactDigest]="a?.digest"
|
||||||
|
[total]="a?.accessoryNumber"
|
||||||
|
[accessories]="a?.accessories"
|
||||||
|
*clrIfExpanded></sub-accessories>
|
||||||
|
</ng-container>
|
||||||
</clr-dg-row>
|
</clr-dg-row>
|
||||||
<clr-dg-footer *ngIf="total > pageSize">
|
<clr-dg-footer *ngIf="total > pageSize">
|
||||||
<clr-dg-pagination
|
<clr-dg-pagination
|
||||||
|
@ -64,6 +64,9 @@ describe('SubAccessoriesComponent', () => {
|
|||||||
listAccessories() {
|
listAccessories() {
|
||||||
return of(page2).pipe(delay(0));
|
return of(page2).pipe(delay(0));
|
||||||
},
|
},
|
||||||
|
listAccessoriesResponse() {
|
||||||
|
return of({}).pipe(delay(0));
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let component: SubAccessoriesComponent;
|
let component: SubAccessoriesComponent;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
|
AfterViewInit,
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
ChangeDetectorRef,
|
||||||
Component,
|
Component,
|
||||||
EventEmitter,
|
|
||||||
Input,
|
Input,
|
||||||
OnInit,
|
OnInit,
|
||||||
Output,
|
|
||||||
ViewChild,
|
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import {
|
import {
|
||||||
clone,
|
clone,
|
||||||
@ -22,8 +22,15 @@ import { ErrorHandler } from '../../../../../../../../shared/units/error-handler
|
|||||||
import { finalize } from 'rxjs/operators';
|
import { finalize } from 'rxjs/operators';
|
||||||
import { SafeUrl } from '@angular/platform-browser';
|
import { SafeUrl } from '@angular/platform-browser';
|
||||||
import { ArtifactService } from '../../../../artifact.service';
|
import { ArtifactService } from '../../../../artifact.service';
|
||||||
import { AccessoryQueryParams, artifactDefault } from '../../../../artifact';
|
import {
|
||||||
import { ClrDatagrid } from '@clr/angular';
|
AccessoryFront,
|
||||||
|
AccessoryQueryParams,
|
||||||
|
artifactDefault,
|
||||||
|
} from '../../../../artifact';
|
||||||
|
import {
|
||||||
|
EventService,
|
||||||
|
HarborEvent,
|
||||||
|
} from '../../../../../../../../services/event-service/event.service';
|
||||||
|
|
||||||
export const ACCESSORY_PAGE_SIZE: number = 5;
|
export const ACCESSORY_PAGE_SIZE: number = 5;
|
||||||
|
|
||||||
@ -31,8 +38,9 @@ export const ACCESSORY_PAGE_SIZE: number = 5;
|
|||||||
selector: 'sub-accessories',
|
selector: 'sub-accessories',
|
||||||
templateUrl: 'sub-accessories.component.html',
|
templateUrl: 'sub-accessories.component.html',
|
||||||
styleUrls: ['./sub-accessories.component.scss'],
|
styleUrls: ['./sub-accessories.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush, // use OnPush Strategy to avoid ExpressionChangedAfterItHasBeenCheckedError
|
||||||
})
|
})
|
||||||
export class SubAccessoriesComponent implements OnInit {
|
export class SubAccessoriesComponent implements OnInit, AfterViewInit {
|
||||||
@Input()
|
@Input()
|
||||||
projectName: string;
|
projectName: string;
|
||||||
@Input()
|
@Input()
|
||||||
@ -41,32 +49,29 @@ export class SubAccessoriesComponent implements OnInit {
|
|||||||
artifactDigest: string;
|
artifactDigest: string;
|
||||||
@Input()
|
@Input()
|
||||||
accessories: Accessory[] = [];
|
accessories: Accessory[] = [];
|
||||||
@Output()
|
|
||||||
deleteAccessory: EventEmitter<Accessory> = new EventEmitter<Accessory>();
|
|
||||||
currentPage: number = 1;
|
currentPage: number = 1;
|
||||||
@Input()
|
@Input()
|
||||||
total: number = 0;
|
total: number = 0;
|
||||||
pageSize: number = ACCESSORY_PAGE_SIZE;
|
pageSize: number = ACCESSORY_PAGE_SIZE;
|
||||||
page: number = 1;
|
page: number = 1;
|
||||||
displayedAccessories: Accessory[] = [];
|
displayedAccessories: AccessoryFront[] = [];
|
||||||
loading: boolean = false;
|
loading: boolean = false;
|
||||||
@ViewChild('datagrid')
|
|
||||||
datagrid: ClrDatagrid;
|
|
||||||
viewInit: boolean = false;
|
|
||||||
constructor(
|
constructor(
|
||||||
private activatedRoute: ActivatedRoute,
|
private activatedRoute: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private newArtifactService: NewArtifactService,
|
private newArtifactService: NewArtifactService,
|
||||||
private artifactService: ArtifactService,
|
private artifactService: ArtifactService,
|
||||||
private errorHandlerService: ErrorHandler
|
private errorHandlerService: ErrorHandler,
|
||||||
|
private cdf: ChangeDetectorRef,
|
||||||
|
private event: EventService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
ngAfterViewInit(): void {
|
||||||
|
this.cdf.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.displayedAccessories = clone(this.accessories);
|
this.displayedAccessories = clone(this.accessories);
|
||||||
// avoid ng checking error
|
|
||||||
setTimeout(() => {
|
|
||||||
this.viewInit = true;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
size(size: number) {
|
size(size: number) {
|
||||||
return formatSize(size.toString());
|
return formatSize(size.toString());
|
||||||
@ -103,13 +108,14 @@ export class SubAccessoriesComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
delete(a: Accessory) {
|
delete(a: Accessory) {
|
||||||
this.deleteAccessory.emit(a);
|
this.event.publish(HarborEvent.DELETE_ACCESSORY, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
clrLoad() {
|
clrLoad() {
|
||||||
if (this.currentPage === 1) {
|
if (this.currentPage === 1) {
|
||||||
this.displayedAccessories = clone(this.accessories);
|
this.displayedAccessories = clone(this.accessories);
|
||||||
this.getIconFromBackend();
|
this.getIconFromBackend();
|
||||||
|
this.getAccessoriesAsync(this.displayedAccessories);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
@ -126,7 +132,9 @@ export class SubAccessoriesComponent implements OnInit {
|
|||||||
.subscribe(
|
.subscribe(
|
||||||
res => {
|
res => {
|
||||||
this.displayedAccessories = res;
|
this.displayedAccessories = res;
|
||||||
|
this.cdf.detectChanges();
|
||||||
this.getIconFromBackend();
|
this.getIconFromBackend();
|
||||||
|
this.getAccessoriesAsync(this.displayedAccessories);
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
this.errorHandlerService.error(error);
|
this.errorHandlerService.error(error);
|
||||||
@ -139,14 +147,37 @@ export class SubAccessoriesComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get dashLineHeight() {
|
// get accessories
|
||||||
if (
|
getAccessoriesAsync(artifacts: AccessoryFront[]) {
|
||||||
this.datagrid &&
|
if (artifacts && artifacts.length) {
|
||||||
this.datagrid['el'] &&
|
artifacts.forEach(item => {
|
||||||
this.datagrid['el']?.nativeElement?.offsetHeight
|
const listTagParams: NewArtifactService.ListAccessoriesParams =
|
||||||
) {
|
{
|
||||||
return this.datagrid['el'].nativeElement?.offsetHeight;
|
projectName: this.projectName,
|
||||||
|
repositoryName: dbEncodeURIComponent(
|
||||||
|
this.repositoryName
|
||||||
|
),
|
||||||
|
reference: item.digest,
|
||||||
|
page: 1,
|
||||||
|
pageSize: ACCESSORY_PAGE_SIZE,
|
||||||
|
};
|
||||||
|
this.newArtifactService
|
||||||
|
.listAccessoriesResponse(listTagParams)
|
||||||
|
.subscribe(res => {
|
||||||
|
if (res.headers) {
|
||||||
|
let xHeader: string =
|
||||||
|
res.headers.get('x-total-count');
|
||||||
|
if (xHeader) {
|
||||||
|
item.accessoryNumber = Number.parseInt(
|
||||||
|
xHeader,
|
||||||
|
10
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item.accessories = res.body;
|
||||||
|
this.cdf.detectChanges();
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,9 @@ export interface ArtifactFront extends Artifact {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AccessoryFront extends Accessory {
|
export interface AccessoryFront extends Accessory {
|
||||||
pullCommand?: string;
|
coSigned?: string;
|
||||||
tagNumber?: number;
|
accessoryNumber?: number;
|
||||||
scan_overview?: any;
|
accessories?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const multipleFilter: Array<{
|
export const multipleFilter: Array<{
|
||||||
|
@ -79,4 +79,5 @@ export enum HarborEvent {
|
|||||||
STOP_SCAN_ARTIFACT = 'stopScanArtifact',
|
STOP_SCAN_ARTIFACT = 'stopScanArtifact',
|
||||||
UPDATE_VULNERABILITY_INFO = 'UpdateVulnerabilityInfo',
|
UPDATE_VULNERABILITY_INFO = 'UpdateVulnerabilityInfo',
|
||||||
REFRESH_EXPORT_JOBS = 'refreshExportJobs',
|
REFRESH_EXPORT_JOBS = 'refreshExportJobs',
|
||||||
|
DELETE_ACCESSORY = 'deleteAccessory',
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user