mirror of
https://github.com/goharbor/harbor.git
synced 2025-01-14 11:41:31 +01:00
Refactor artifact-list-tab component (#17542)
Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
parent
acf68d3533
commit
13b3233faf
@ -3,58 +3,8 @@
|
||||
#confirmationDialog
|
||||
(confirmAction)="confirmDeletion($event)">
|
||||
</confirmation-dialog>
|
||||
<clr-modal
|
||||
class="hidden-tag"
|
||||
[(clrModalOpen)]="showTagManifestOpened"
|
||||
[clrModalStaticBackdrop]="staticBackdrop"
|
||||
[clrModalClosable]="closable">
|
||||
<h3 class="modal-title">{{ manifestInfoTitle | translate }}</h3>
|
||||
<div class="modal-body">
|
||||
<div class="row col-md-12">
|
||||
<textarea class="clr-textarea w-100" rows="2" #digestTarget>{{
|
||||
digestId
|
||||
}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span class="copy-failed" [hidden]="!copyFailed">{{
|
||||
'TAG.COPY_ERROR' | translate
|
||||
}}</span>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
[ngxClipboard]="digestTarget"
|
||||
(cbOnSuccess)="onSuccess($event)"
|
||||
(cbOnError)="onError($event)">
|
||||
{{ 'BUTTON.COPY' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</clr-modal>
|
||||
<clr-modal
|
||||
class="hidden-tag"
|
||||
[(clrModalOpen)]="retagDialogOpened"
|
||||
[clrModalStaticBackdrop]="staticBackdrop">
|
||||
<h3 class="modal-title">{{ 'REPOSITORY.RETAG' | translate }}</h3>
|
||||
<div class="modal-body retag-modal-body">
|
||||
<div class="row col-md-12">
|
||||
<hbr-image-name-input #imageNameInput></hbr-image-name-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
[disabled]="
|
||||
imageNameInput.projectName.invalid ||
|
||||
imageNameInput.repoName.invalid ||
|
||||
imageNameInput.notExist ||
|
||||
imageNameInput.checkingName
|
||||
"
|
||||
class="btn btn-primary"
|
||||
(click)="onRetag()">
|
||||
{{ 'BUTTON.CONFIRM' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</clr-modal>
|
||||
<app-copy-artifact></app-copy-artifact>
|
||||
<app-copy-digest></app-copy-digest>
|
||||
<div class="row tag-row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<clr-datagrid
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ArtifactListTabComponent } from './artifact-list-tab.component';
|
||||
import { of } from 'rxjs';
|
||||
import { delay } from 'rxjs/operators';
|
||||
@ -343,7 +343,7 @@ describe('ArtifactListTabComponent (inline template)', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [SharedTestingModule],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
declarations: [ArtifactListTabComponent],
|
||||
providers: [
|
||||
{
|
||||
|
@ -95,6 +95,8 @@ import { ArtifactListPageService } from '../../artifact-list-page.service';
|
||||
import { ACCESSORY_PAGE_SIZE } from './sub-accessories/sub-accessories.component';
|
||||
import { Accessory } from 'ng-swagger-gen/models/accessory';
|
||||
import { Tag } from '../../../../../../../../../ng-swagger-gen/models/tag';
|
||||
import { CopyArtifactComponent } from './copy-artifact/copy-artifact.component';
|
||||
import { CopyDigestComponent } from './copy-digest/copy-digest.component';
|
||||
|
||||
export interface LabelState {
|
||||
iconsShow: boolean;
|
||||
@ -120,17 +122,10 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
||||
registryUrl: string;
|
||||
artifactList: ArtifactFront[] = [];
|
||||
availableTime = AVAILABLE_TIME;
|
||||
showTagManifestOpened: boolean;
|
||||
retagDialogOpened: boolean;
|
||||
manifestInfoTitle: string;
|
||||
digestId: string;
|
||||
staticBackdrop = true;
|
||||
closable = false;
|
||||
lastFilteredTagName: string;
|
||||
inprogress: boolean;
|
||||
openLabelFilterPanel: boolean;
|
||||
openLabelFilterPiece: boolean;
|
||||
retagSrcImage: string;
|
||||
showlabel: boolean;
|
||||
|
||||
pullComparator: Comparator<Artifact> = new CustomComparator<Artifact>(
|
||||
@ -143,7 +138,6 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
|
||||
loading = true;
|
||||
copyFailed = false;
|
||||
selectedRow: Artifact[] = [];
|
||||
labelListOpen = false;
|
||||
selectedTag: Artifact[];
|
||||
@ -164,10 +158,10 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
||||
@ViewChild('confirmationDialog')
|
||||
confirmationDialog: ConfirmationDialogComponent;
|
||||
|
||||
@ViewChild('imageNameInput')
|
||||
imageNameInput: ImageNameInputComponent;
|
||||
|
||||
@ViewChild('digestTarget') textInput: ElementRef;
|
||||
@ViewChild(CopyArtifactComponent)
|
||||
copyArtifactComponent: CopyArtifactComponent;
|
||||
@ViewChild(CopyDigestComponent)
|
||||
copyDigestComponent: CopyDigestComponent;
|
||||
pageSize: number = getPageSizeFromLocalStorage(
|
||||
PageSizeMapKeys.ARTIFACT_LIST_TAB_COMPONENT
|
||||
);
|
||||
@ -846,47 +840,10 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
||||
|
||||
retag() {
|
||||
if (this.selectedRow && this.selectedRow.length && !this.depth) {
|
||||
this.retagDialogOpened = true;
|
||||
this.imageNameInput.imageNameForm.reset({
|
||||
repoName: this.repoName,
|
||||
projectName: null,
|
||||
});
|
||||
this.imageNameInput.notExist = false;
|
||||
this.retagSrcImage =
|
||||
this.repoName + ':' + this.selectedRow[0].digest;
|
||||
this.copyArtifactComponent.retag(this.selectedRow[0].digest);
|
||||
}
|
||||
}
|
||||
|
||||
onRetag() {
|
||||
let params: NewArtifactService.CopyArtifactParams = {
|
||||
projectName: this.imageNameInput.projectName.value,
|
||||
repositoryName: dbEncodeURIComponent(
|
||||
this.imageNameInput.repoName.value
|
||||
),
|
||||
from: `${this.projectName}/${this.repoName}@${this.selectedRow[0].digest}`,
|
||||
};
|
||||
this.newArtifactService
|
||||
.CopyArtifact(params)
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
this.imageNameInput.form.reset();
|
||||
this.retagDialogOpened = false;
|
||||
})
|
||||
)
|
||||
.subscribe(
|
||||
response => {
|
||||
this.translateService
|
||||
.get('RETAG.MSG_SUCCESS')
|
||||
.subscribe((res: string) => {
|
||||
this.errorHandlerService.info(res);
|
||||
});
|
||||
},
|
||||
error => {
|
||||
this.errorHandlerService.error(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
deleteArtifact() {
|
||||
if (this.selectedRow && this.selectedRow.length && !this.depth) {
|
||||
let artifactNames: string[] = [];
|
||||
@ -1059,10 +1016,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
||||
|
||||
showDigestId() {
|
||||
if (this.selectedRow && this.selectedRow.length === 1 && !this.depth) {
|
||||
this.manifestInfoTitle = 'REPOSITORY.COPY_DIGEST_ID';
|
||||
this.digestId = this.selectedRow[0].digest;
|
||||
this.showTagManifestOpened = true;
|
||||
this.copyFailed = false;
|
||||
this.copyDigestComponent.showDigestId(this.selectedRow[0].digest);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1080,21 +1034,6 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
onSuccess($event: any): void {
|
||||
this.copyFailed = false;
|
||||
// Directly close dialog
|
||||
this.showTagManifestOpened = false;
|
||||
}
|
||||
|
||||
onError($event: any): void {
|
||||
// Show error
|
||||
this.copyFailed = true;
|
||||
// Select all text
|
||||
if (this.textInput) {
|
||||
this.textInput.nativeElement.select();
|
||||
}
|
||||
}
|
||||
|
||||
// Get vulnerability scanning status
|
||||
scanStatus(artifact: Artifact): string {
|
||||
if (artifact) {
|
||||
|
@ -0,0 +1,25 @@
|
||||
<clr-modal
|
||||
class="hidden-tag"
|
||||
[(clrModalOpen)]="retagDialogOpened"
|
||||
[clrModalStaticBackdrop]="true">
|
||||
<h3 class="modal-title">{{ 'REPOSITORY.RETAG' | translate }}</h3>
|
||||
<div class="modal-body retag-modal-body">
|
||||
<div class="row col-md-12">
|
||||
<hbr-image-name-input #imageNameInput></hbr-image-name-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
[disabled]="
|
||||
imageNameInput.projectName.invalid ||
|
||||
imageNameInput.repoName.invalid ||
|
||||
imageNameInput.notExist ||
|
||||
imageNameInput.checkingName
|
||||
"
|
||||
class="btn btn-primary"
|
||||
(click)="onRetag()">
|
||||
{{ 'BUTTON.CONFIRM' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</clr-modal>
|
@ -0,0 +1,57 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CopyArtifactComponent } from './copy-artifact.component';
|
||||
import { SharedTestingModule } from '../../../../../../../../shared/shared.module';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { ArtifactService } from 'ng-swagger-gen/services/artifact.service';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
describe('CopyArtifactComponent', () => {
|
||||
let component: CopyArtifactComponent;
|
||||
let fixture: ComponentFixture<CopyArtifactComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
declarations: [CopyArtifactComponent],
|
||||
imports: [SharedTestingModule, RouterTestingModule],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(CopyArtifactComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should open modal', async () => {
|
||||
component.retag(`sha256@test`);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const modal = fixture.nativeElement.querySelector(`clr-modal`);
|
||||
expect(modal).toBeTruthy();
|
||||
});
|
||||
it('should call retag API', async () => {
|
||||
const artifactService: ArtifactService =
|
||||
TestBed.inject(ArtifactService);
|
||||
const spy: jasmine.Spy = spyOn(
|
||||
artifactService,
|
||||
`CopyArtifact`
|
||||
).and.returnValue(of(null));
|
||||
component.retagDialogOpened = true;
|
||||
component.imageNameInput.imageNameForm.reset({
|
||||
repoName: `test`,
|
||||
projectName: `test`,
|
||||
});
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const btn: HTMLButtonElement =
|
||||
fixture.nativeElement.querySelector(`.btn-primary`);
|
||||
btn.click();
|
||||
fixture.detectChanges();
|
||||
expect(spy.calls.count()).toEqual(1);
|
||||
});
|
||||
});
|
@ -0,0 +1,76 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { dbEncodeURIComponent } from '../../../../../../../../shared/units/utils';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
import { ArtifactService } from 'ng-swagger-gen/services/artifact.service';
|
||||
import { ImageNameInputComponent } from '../../../../../../../../shared/components/image-name-input/image-name-input.component';
|
||||
import { Project } from '../../../../../../project';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ErrorHandler } from '../../../../../../../../shared/units/error-handler';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-copy-artifact',
|
||||
templateUrl: './copy-artifact.component.html',
|
||||
styleUrls: ['./copy-artifact.component.scss'],
|
||||
})
|
||||
export class CopyArtifactComponent implements OnInit {
|
||||
retagDialogOpened: boolean = false;
|
||||
projectName: string;
|
||||
repoName: string;
|
||||
@ViewChild('imageNameInput')
|
||||
imageNameInput: ImageNameInputComponent;
|
||||
digest: string;
|
||||
constructor(
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private artifactService: ArtifactService,
|
||||
private errorHandlerService: ErrorHandler,
|
||||
private translateService: TranslateService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
const resolverData = this.activatedRoute.snapshot?.parent?.parent?.data;
|
||||
if (resolverData) {
|
||||
this.projectName = (<Project>resolverData['projectResolver']).name;
|
||||
}
|
||||
this.repoName = this.activatedRoute.snapshot?.parent?.params['repo'];
|
||||
}
|
||||
|
||||
onRetag() {
|
||||
let params: ArtifactService.CopyArtifactParams = {
|
||||
projectName: this.imageNameInput.projectName.value,
|
||||
repositoryName: dbEncodeURIComponent(
|
||||
this.imageNameInput.repoName.value
|
||||
),
|
||||
from: `${this.projectName}/${this.repoName}@${this.digest}`,
|
||||
};
|
||||
this.artifactService
|
||||
.CopyArtifact(params)
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
this.imageNameInput.form.reset();
|
||||
this.retagDialogOpened = false;
|
||||
})
|
||||
)
|
||||
.subscribe({
|
||||
next: response => {
|
||||
this.translateService
|
||||
.get('RETAG.MSG_SUCCESS')
|
||||
.subscribe((res: string) => {
|
||||
this.errorHandlerService.info(res);
|
||||
});
|
||||
},
|
||||
error: error => {
|
||||
this.errorHandlerService.error(error);
|
||||
},
|
||||
});
|
||||
}
|
||||
retag(digest: string) {
|
||||
this.retagDialogOpened = true;
|
||||
this.imageNameInput.imageNameForm.reset({
|
||||
repoName: this.repoName,
|
||||
projectName: null,
|
||||
});
|
||||
this.digest = digest;
|
||||
this.imageNameInput.notExist = false;
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<clr-modal
|
||||
class="hidden-tag"
|
||||
[(clrModalOpen)]="showTagManifestOpened"
|
||||
[clrModalStaticBackdrop]="true"
|
||||
[clrModalClosable]="false">
|
||||
<h3 class="modal-title">{{ 'REPOSITORY.COPY_DIGEST_ID' | translate }}</h3>
|
||||
<div class="modal-body">
|
||||
<div class="row col-md-12">
|
||||
<textarea class="clr-textarea w-100" rows="2" #digestTarget>{{
|
||||
digestId
|
||||
}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span class="copy-failed" [hidden]="!copyFailed">{{
|
||||
'TAG.COPY_ERROR' | translate
|
||||
}}</span>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
[ngxClipboard]="digestTarget"
|
||||
(cbOnSuccess)="onSuccess($event)"
|
||||
(cbOnError)="onError($event)">
|
||||
{{ 'BUTTON.COPY' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</clr-modal>
|
@ -0,0 +1,32 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { CopyDigestComponent } from './copy-digest.component';
|
||||
import { SharedTestingModule } from '../../../../../../../../shared/shared.module';
|
||||
|
||||
describe('CopyDigestComponent', () => {
|
||||
let component: CopyDigestComponent;
|
||||
let fixture: ComponentFixture<CopyDigestComponent>;
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [CopyDigestComponent],
|
||||
imports: [SharedTestingModule],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(CopyDigestComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should show right digest', async () => {
|
||||
const digest: string = 'sha256@test';
|
||||
component.showDigestId(digest);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const textArea: HTMLTextAreaElement =
|
||||
fixture.nativeElement.querySelector(`textarea`);
|
||||
expect(textArea.textContent).toEqual(digest);
|
||||
});
|
||||
});
|
@ -0,0 +1,33 @@
|
||||
import { Component, ElementRef, ViewChild } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-copy-digest',
|
||||
templateUrl: './copy-digest.component.html',
|
||||
styleUrls: ['./copy-digest.component.scss'],
|
||||
})
|
||||
export class CopyDigestComponent {
|
||||
showTagManifestOpened: boolean = false;
|
||||
digestId: string;
|
||||
@ViewChild('digestTarget') textInput: ElementRef;
|
||||
copyFailed: boolean = false;
|
||||
constructor() {}
|
||||
onSuccess($event: any): void {
|
||||
this.copyFailed = false;
|
||||
// Directly close dialog
|
||||
this.showTagManifestOpened = false;
|
||||
}
|
||||
|
||||
onError($event: any): void {
|
||||
// Show error
|
||||
this.copyFailed = true;
|
||||
// Select all text
|
||||
if (this.textInput) {
|
||||
this.textInput.nativeElement.select();
|
||||
}
|
||||
}
|
||||
showDigestId(digest: string) {
|
||||
this.digestId = digest;
|
||||
this.showTagManifestOpened = true;
|
||||
this.copyFailed = false;
|
||||
}
|
||||
}
|
@ -21,6 +21,8 @@ import { HistogramChartComponent } from './vulnerability-scanning/histogram-char
|
||||
import { ArtifactInfoComponent } from './artifact-list-page/artifact-list/artifact-info/artifact-info.component';
|
||||
import { SubAccessoriesComponent } from './artifact-list-page/artifact-list/artifact-list-tab/sub-accessories/sub-accessories.component';
|
||||
import { ArtifactListPageService } from './artifact-list-page/artifact-list-page.service';
|
||||
import { CopyArtifactComponent } from './artifact-list-page/artifact-list/artifact-list-tab/copy-artifact/copy-artifact.component';
|
||||
import { CopyDigestComponent } from './artifact-list-page/artifact-list/artifact-list-tab/copy-digest/copy-digest.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@ -82,6 +84,8 @@ const routes: Routes = [
|
||||
HistogramChartComponent,
|
||||
ArtifactInfoComponent,
|
||||
SubAccessoriesComponent,
|
||||
CopyArtifactComponent,
|
||||
CopyDigestComponent,
|
||||
],
|
||||
imports: [RouterModule.forChild(routes), SharedModule],
|
||||
providers: [
|
||||
|
Loading…
Reference in New Issue
Block a user