Merge pull request #10887 from jwangyangls/filter-artifact-mutiple

Filter artifact by type/tag  and change error setting
This commit is contained in:
jwangyangls 2020-03-04 11:50:30 +08:00 committed by GitHub
commit 3a6b675dbd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 294 additions and 115 deletions

View File

@ -30,34 +30,54 @@
<div class="row tag-row"> <div class="row tag-row">
<div> <div>
<div class="row flex-items-xs-right rightPos"> <div class="row flex-items-xs-right rightPos">
<div id="filterArea"> <div id="filterArea" *ngIf="!depth">
<div class='filterLabelPiece' *ngIf="!withAdmiral" [hidden]="!openLabelFilterPiece" <div class='filterLabelPiece' *ngIf="(openLabelFilterPiece &&filterByType ==='label.id')"
[style.left.px]='32'> [style.left.px]='96'>
<hbr-label-piece *ngIf="showlabel" [hidden]='!filterOneLabel' [label]="filterOneLabel" <hbr-label-piece *ngIf="showlabel" [hidden]='!filterOneLabel' [label]="filterOneLabel"
[labelWidth]="130"></hbr-label-piece> [labelWidth]="130"></hbr-label-piece>
</div> </div>
<div class="flex-xs-middle"> <div class="flex-xs-middle">
<hbr-filter [readonly]="'readonly'" [withDivider]="true" <div class="execution-select">
filterPlaceholder="{{'ARTIFACT.FILTER_FOR_ARTIFACTS' | translate}}" <div class="select filter-tag" [hidden]="!openSelectFilterPiece">
(filterEvt)="doSearchArtifactByFilter($event)" (openFlag)="openFlagEvent($event)" <clr-select-container>
[currentValue]="lastFilteredTagName"></hbr-filter> <select clrSelect [(ngModel)]="filterByType" (change)="selectFilterType($event)" >
<div class="label-filter-panel" *ngIf="!withAdmiral" [hidden]="!openLabelFilterPanel"> <option *ngFor="let filter of mutipleFilter" value="{{filter.filterBy}}">{{ filter.filterByShowText | translate}}</option>
<a class="filterClose" (click)="closeFilter()">&times;</a> </select>
<label </clr-select-container>
class="filterLabelHeader filter-dark">{{'REPOSITORY.FILTER_ARTIFACT_BY_LABEL' | translate}}</label>
<div class="form-group"><input clrInput type="text" placeholder="Filter labels"
[(ngModel)]="filterName" (keyup)="handleInputFilter()"></div>
<div [hidden]='imageFilterLabels.length' class="no-labels">{{'LABEL.NO_LABELS' | translate }}
</div> </div>
<div [hidden]='!imageFilterLabels.length' class="has-label"> <div class="flex-xs-middle">
<button type="button" class="labelBtn" *ngFor='let label of imageFilterLabels' <hbr-filter [withDivider]="true" [readonly]="isFilterReadonly"
[hidden]="!label.show" (click)="rightFilterLabel(label)"> filterPlaceholder="{{'ARTIFACT.FILTER_FOR_ARTIFACTS' | translate}}"
<clr-icon shape="check" class='pull-left' [hidden]='!label.iconsShow'></clr-icon> (filterEvt)="doSearchArtifactByFilter($event)" (openFlag)="openFlagEvent($event)"
<div class='labelDiv'> [currentValue]="lastFilteredTagName">
<hbr-label-piece [label]="label.label" [labelWidth]="160"></hbr-label-piece> </hbr-filter>
</div> <div [hidden]="!openSelectFilterPiece" class="label-filter-panel list-filter">
</button> <div *ngFor="let filter of mutipleFilter" >
<ul class="list-unstyled" *ngIf="filterByType ===filter.filterBy">
<li class="cursor-pointer" (click)="selectFilter(item.showItem, item.filterText)" *ngFor="let item of filter.listItem">{{item.showItem | translate}}</li>
</ul>
</div>
</div> </div>
<div class="label-filter-panel" *ngIf="!withAdmiral" [hidden]="!(openLabelFilterPanel&&filterByType==='label.id')">
<a class="filterClose" (click)="closeFilter()">&times;</a>
<label
class="filterLabelHeader filter-dark">{{'REPOSITORY.FILTER_ARTIFACT_BY_LABEL' | translate}}</label>
<div class="form-group"><input clrInput type="text" placeholder="Filter labels"
[(ngModel)]="filterName" (keyup)="handleInputFilter()"></div>
<div [hidden]='imageFilterLabels.length' class="no-labels">{{'LABEL.NO_LABELS' | translate }}
</div>
<div [hidden]='!imageFilterLabels.length' class="has-label">
<button type="button" class="labelBtn" *ngFor='let label of imageFilterLabels'
[hidden]="!label.show" (click)="rightFilterLabel(label)">
<clr-icon shape="check" class='pull-left' [hidden]='!label.iconsShow'></clr-icon>
<div class='labelDiv'>
<hbr-label-piece [label]="label.label" [labelWidth]="160"></hbr-label-piece>
</div>
</button>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -136,11 +156,11 @@
<clr-dg-column *ngIf="!withAdmiral">{{'REPOSITORY.LABELS' | translate}}</clr-dg-column> <clr-dg-column *ngIf="!withAdmiral">{{'REPOSITORY.LABELS' | translate}}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="pushComparator">{{'REPOSITORY.PUSH_TIME' | translate}}</clr-dg-column> <clr-dg-column [clrDgSortBy]="pushComparator">{{'REPOSITORY.PUSH_TIME' | translate}}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="pullComparator">{{'REPOSITORY.PULL_TIME' | translate}}</clr-dg-column> <clr-dg-column [clrDgSortBy]="pullComparator">{{'REPOSITORY.PULL_TIME' | translate}}</clr-dg-column>
<clr-dg-placeholder>{{'TAG.PLACEHOLDER' | translate }}</clr-dg-placeholder> <clr-dg-placeholder>{{'ARTIFACT.PLACEHOLDER' | translate }}</clr-dg-placeholder>
<clr-dg-row *ngFor="let artifact of artifactList" [clrDgItem]="artifact" > <clr-dg-row *ngFor="let artifact of artifactList" [clrDgItem]="artifact" >
<clr-dg-cell class="truncated flex-max-width"> <clr-dg-cell class="truncated flex-max-width">
<div class="cell white-normal"> <div class="cell white-normal">
<img class="artifact-icon" <img class="artifact-icon" [title]="artifact.type"
[src]="artifact.type==='IMAGE'||artifact.type==='CHART'||artifact.type ==='CNAB'?'images/artifact-'+artifact.type.toLowerCase()+'.svg':'images/artifact-default.svg'" /> [src]="artifact.type==='IMAGE'||artifact.type==='CHART'||artifact.type ==='CNAB'?'images/artifact-'+artifact.type.toLowerCase()+'.svg':'images/artifact-default.svg'" />
&nbsp; &nbsp; &nbsp; &nbsp;
<a href="javascript:void(0)" class="max-width-100" (click)="goIntoArtifactSummaryPage(artifact)" <a href="javascript:void(0)" class="max-width-100" (click)="goIntoArtifactSummaryPage(artifact)"
@ -216,11 +236,6 @@
{{artifact.size?sizeTransform(artifact.size): ""}} {{artifact.size?sizeTransform(artifact.size): ""}}
</div> </div>
</clr-dg-cell> </clr-dg-cell>
<!-- <clr-dg-cell class="truncated" title="docker pull {{registryUrl}}/{{repoName}}:{{artifact.name}}">
<div class="cell">
<hbr-copy-input #copyInput (onCopyError)="onCpError($event)" iconMode="true" defaultValue="docker pull {{registryUrl}}/{{repoName}}:{{artifact.name}}"></hbr-copy-input>
</div>
</clr-dg-cell> -->
<clr-dg-cell> <clr-dg-cell>
<div class="cell"> <div class="cell">
<hbr-vulnerability-bar [scanner]="handleScanOverview(artifact.scan_overview)?.scanner" <hbr-vulnerability-bar [scanner]="handleScanOverview(artifact.scan_overview)?.scanner"

View File

@ -389,3 +389,27 @@ clr-datagrid {
margin-left: -3px; margin-left: -3px;
} }
.execution-select {
height: 24px;
display: flex;
justify-content: flex-end;
.filter-tag {
float: left;
margin-top: 8px;
.clr-form-control {
margin-top: 0;
}
}
}
.list-filter {
width:7rem;
margin-left: 0.8rem;
padding: 0;
li {
padding-left: 1rem;
&:hover {
background-color: #eee;
}
}
}

View File

@ -265,7 +265,14 @@ describe("ArtifactListTabComponent (inline template)", () => {
publishInfo: () => {} publishInfo: () => {}
}; };
const mockArtifactService = { const mockArtifactService = {
getArtifactList: () => { TriggerArtifactChan$: {
subscribe: (fn) => {
}
}
};
const mockNewArtifactService = {
listArtifactsResponse: () => {
if (filtereName === 'sha256:3e33e3e3') { if (filtereName === 'sha256:3e33e3e3') {
return of( return of(
{ {
@ -280,14 +287,9 @@ describe("ArtifactListTabComponent (inline template)", () => {
).pipe(delay(0)); ).pipe(delay(0));
} }
}, },
TriggerArtifactChan$: {
subscribe: (fn) => {
}
},
deleteArtifact: () => of (null) deleteArtifact: () => of (null)
} };
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [
@ -320,7 +322,7 @@ describe("ArtifactListTabComponent (inline template)", () => {
{ provide: ErrorHandler, useValue: mockErrorHandler }, { provide: ErrorHandler, useValue: mockErrorHandler },
{ provide: ActivatedRoute, useValue: mockActivatedRoute }, { provide: ActivatedRoute, useValue: mockActivatedRoute },
{ provide: OperationService, useValue: mockOperationService }, { provide: OperationService, useValue: mockOperationService },
{ provide: NewArtifactService, useValue: null }, { provide: NewArtifactService, useValue: mockNewArtifactService },
] ]
}).compileComponents(); }).compileComponents();
})); }));

View File

@ -61,7 +61,7 @@ import {
} from "../../../../../../lib/entities/shared.const"; } from "../../../../../../lib/entities/shared.const";
import { operateChanges, OperateInfo, OperationState } from "../../../../../../lib/components/operation/operate"; import { operateChanges, OperateInfo, OperationState } from "../../../../../../lib/components/operation/operate";
import { errorHandler } from "../../../../../../lib/utils/shared/shared.utils"; import { errorHandler } from "../../../../../../lib/utils/shared/shared.utils";
import { ArtifactFront as Artifact } from "../../../artifact/artifact"; import { ArtifactFront as Artifact, mutipleFilter } from "../../../artifact/artifact";
import { Project } from "../../../../project"; import { Project } from "../../../../project";
import { ArtifactService as NewArtifactService } from "../../../../../../../ng-swagger-gen/services/artifact.service"; import { ArtifactService as NewArtifactService } from "../../../../../../../ng-swagger-gen/services/artifact.service";
export interface LabelState { export interface LabelState {
@ -159,9 +159,12 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
triggerSub: Subscription; triggerSub: Subscription;
labelNameFilterSub: Subscription; labelNameFilterSub: Subscription;
stickLabelNameFilterSub: Subscription; stickLabelNameFilterSub: Subscription;
mutipleFilter = clone(mutipleFilter);
filterByType: string = this.mutipleFilter[0].filterBy;
openSelectFilterPiece = false;
constructor( constructor(
private errorHandlerService: ErrorHandler, private errorHandlerService: ErrorHandler,
private retagService: RetagService,
private userPermissionService: UserPermissionService, private userPermissionService: UserPermissionService,
private labelService: LabelService, private labelService: LabelService,
private artifactService: ArtifactService, private artifactService: ArtifactService,
@ -169,7 +172,6 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
private translateService: TranslateService, private translateService: TranslateService,
private operationService: OperationService, private operationService: OperationService,
private channel: ChannelService, private channel: ChannelService,
private projectService: ProjectService,
private activatedRoute: ActivatedRoute, private activatedRoute: ActivatedRoute,
private scanningService: ScanningResultService, private scanningService: ScanningResultService,
private router: Router, private router: Router,
@ -275,8 +277,8 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
let len = this.lastFilteredTagName.length ? this.lastFilteredTagName.length * 6 + 60 : 115; let len = this.lastFilteredTagName.length ? this.lastFilteredTagName.length * 6 + 60 : 115;
return len > 210 ? 210 : len; return len > 210 ? 210 : len;
} }
doSearchArtifactByFilter(tagName) { doSearchArtifactByFilter(filterWords) {
this.lastFilteredTagName = tagName; this.lastFilteredTagName = filterWords;
this.currentPage = 1; this.currentPage = 1;
let st: State = this.currentState; let st: State = this.currentState;
@ -286,13 +288,8 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
st.page.size = this.pageSize; st.page.size = this.pageSize;
st.page.from = 0; st.page.from = 0;
st.page.to = this.pageSize - 1; st.page.to = this.pageSize - 1;
let selectedLab = this.imageFilterLabels.find(label => label.iconsShow === true);
if (selectedLab) {
st.filters = [{ property: 'name', value: this.lastFilteredTagName }, { property: 'labels.id', value: selectedLab.label.id }];
} else {
st.filters = [{ property: 'name', value: this.lastFilteredTagName }];
}
st.filters = [{ property: this.filterByType, value: this.lastFilteredTagName }];
this.clrLoad(st); this.clrLoad(st);
} }
doSearchArtifactNames(artifactName: string) { doSearchArtifactNames(artifactName: string) {
@ -306,13 +303,11 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
st.page.size = this.pageSize; st.page.size = this.pageSize;
st.page.from = 0; st.page.from = 0;
st.page.to = this.pageSize - 1; st.page.to = this.pageSize - 1;
st.filters = [{ property: this.filterByType, value: this.lastFilteredTagName }];
let selectedLab = this.imageFilterLabels.find(label => label.iconsShow === true); let selectedLab = this.imageFilterLabels.find(label => label.iconsShow === true);
if (selectedLab) { if (selectedLab) {
st.filters = [{ property: 'name', value: this.lastFilteredTagName }, { property: 'labels.id', value: selectedLab.label.id }]; st.filters.push({ property: this.filterByType, value: selectedLab.label.id });
} else {
st.filters = [{ property: 'name', value: this.lastFilteredTagName }];
} }
this.clrLoad(st); this.clrLoad(st);
} }
// todo // todo
@ -341,31 +336,42 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
this.currentState = state; this.currentState = state;
// Pagination // Pagination
let params = new HttpParams(); let params: any = {};
params = params.set('with_label', 'true');
params = params.set('with_scan_overview', 'true');
params = params.set('with_signature', 'true');
params = params.set('with_immutable_status', 'true');
if (pageNumber && this.pageSize) { if (pageNumber && this.pageSize) {
params = params.set('page', pageNumber + '').set('page_size', this.pageSize + ''); params.page = pageNumber;
params.pageSize = this.pageSize;
} }
if (sortBy) { if (sortBy) {
params = params.set('sort', sortBy); params.sort = sortBy;
} }
if (state.filters && state.filters.length) { if (state.filters && state.filters.length) {
state.filters.forEach(item => { state.filters.forEach(item => {
params = params.set(item.property, item.value); params[item.property] = item.value;
}); });
} }
if (this.artifactDigest) { if (this.artifactDigest) {
this.artifactService.getArtifactFromDigest(this.projectName, this.repoName, this.artifactDigest).subscribe( const artifactParam: NewArtifactService.GetArtifactParams = {
repositoryName: this.repoName,
projectName: this.projectName,
reference: this.artifactDigest,
withImmutableStatus: true,
withLabel: true,
withScanOverview: true,
withSignature: true,
withTag: true
};
this.newArtifactService.getArtifact(artifactParam).subscribe(
res => { res => {
let observableLists: Observable<Artifact>[] = []; let observableLists: Observable<Artifact>[] = [];
this.totalCount = res.references.length; this.totalCount = res.references.length;
res.references.forEach((child, index) => { res.references.forEach((child, index) => {
if (index >= (pageNumber - 1) * this.pageSize && index < pageNumber * this.pageSize) { if (index >= (pageNumber - 1) * this.pageSize && index < pageNumber * this.pageSize) {
observableLists.push(this.artifactService.getArtifactFromDigest(this.projectName, this.repoName, let childParams: NewArtifactService.GetArtifactParams = {
child.child_digest)); repositoryName: this.repoName,
projectName: this.projectName,
reference: child.child_digest
};
observableLists.push(this.newArtifactService.getArtifact(childParams));
} }
}); });
forkJoin(observableLists).pipe(finalize(() => { forkJoin(observableLists).pipe(finalize(() => {
@ -381,7 +387,17 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
} }
); );
} else { } else {
this.artifactService.getArtifactList(this.projectName, this.repoName, params) let listArtifactParams: NewArtifactService.ListArtifactsParams = {
projectName: this.projectName,
repositoryName: this.repoName,
withImmutableStatus: true,
withLabel: true,
withScanOverview: true,
withSignature: true,
withTag: true
};
Object.assign(listArtifactParams, params);
this.newArtifactService.listArtifactsResponse(listArtifactParams)
.pipe(finalize(() => this.loading = false)) .pipe(finalize(() => this.loading = false))
.subscribe(res => { .subscribe(res => {
if (res.headers) { if (res.headers) {
@ -400,7 +416,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
} }
refresh() { refresh() {
this.doSearchArtifactNames(""); this.doSearchArtifactNames(this.lastFilteredTagName);
} }
getArtifactAnnotationsArray(artifactList: Artifact[]) { getArtifactAnnotationsArray(artifactList: Artifact[]) {
artifactList.forEach(artifact => { artifactList.forEach(artifact => {
@ -466,10 +482,14 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
selectLabel(labelInfo: LabelState): void { selectLabel(labelInfo: LabelState): void {
if (!this.inprogress) { if (!this.inprogress) {
this.inprogress = true; this.inprogress = true;
let labelId = labelInfo.label.id;
this.selectedRow = this.selectedTag; this.selectedRow = this.selectedTag;
let params: NewArtifactService.AddLabelParams = {
this.artifactService.addLabelToImages(this.projectName, this.repoName, this.selectedRow[0].digest, labelId).subscribe(res => { projectName: this.projectName,
repositoryName: this.repoName,
reference: this.selectedRow[0].digest,
label: labelInfo.label
};
this.newArtifactService.addLabel(params).subscribe(res => {
this.refresh(); this.refresh();
// set the selected label in front // set the selected label in front
@ -500,7 +520,13 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
this.inprogress = true; this.inprogress = true;
let labelId = labelInfo.label.id; let labelId = labelInfo.label.id;
this.selectedRow = this.selectedTag; this.selectedRow = this.selectedTag;
this.artifactService.deleteLabelToImages(this.projectName, this.repoName, this.selectedRow[0].digest, labelId).subscribe(res => { let params: NewArtifactService.RemoveLabelParams = {
projectName: this.projectName,
repositoryName: this.repoName,
reference: this.selectedRow[0].digest,
labelId: labelId
};
this.newArtifactService.removeLabel(params).subscribe(res => {
this.refresh(); this.refresh();
// insert the unselected label to groups with the same icons // insert the unselected label to groups with the same icons
@ -595,6 +621,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
if (isOpen) { if (isOpen) {
this.openLabelFilterPanel = true; this.openLabelFilterPanel = true;
this.openLabelFilterPiece = true; this.openLabelFilterPiece = true;
this.openSelectFilterPiece = true;
this.filterName = ''; this.filterName = '';
// redisplay all labels // redisplay all labels
this.imageFilterLabels.forEach(data => { this.imageFilterLabels.forEach(data => {
@ -607,6 +634,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
} else { } else {
this.openLabelFilterPanel = false; this.openLabelFilterPanel = false;
this.openLabelFilterPiece = false; this.openLabelFilterPiece = false;
this.openSelectFilterPiece = false;
} }
} }
@ -707,6 +735,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
artifactList.forEach(artifact => { artifactList.forEach(artifact => {
this.deleteArtifactobservableLists.push(this.delOperate(artifact)); this.deleteArtifactobservableLists.push(this.delOperate(artifact));
}); });
this.loading = true;
forkJoin(...this.deleteArtifactobservableLists).subscribe((items) => { forkJoin(...this.deleteArtifactobservableLists).subscribe((items) => {
// if delete one success refresh list // if delete one success refresh list
if (items.some(item => !item)) { if (items.some(item => !item)) {
@ -718,35 +747,6 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
} }
} }
} }
findArtifactFromIndex(artifactList: Artifact[]) {
if (artifactList.every(artifact1 => !artifact1.references)) {
artifactList.forEach(artifact => {
this.deleteArtifactobservableLists.push(this.delOperate(artifact));
});
forkJoin(...this.deleteArtifactobservableLists).subscribe((items) => {
// if delete one success refresh list
if (items.some(item => !item)) {
this.selectedRow = [];
let st: ClrDatagridStateInterface = { page: {from: 0, to: this.pageSize - 1, size: this.pageSize} };
this.clrLoad(st);
}
});
} else {
let observArr: Observable<Artifact>[] = [];
artifactList.forEach(artifact => {
this.deleteArtifactobservableLists.push(this.delOperate(artifact));
if (artifact.references) {
artifact.references.forEach(reference => {
observArr.push(this.artifactService.getArtifactFromDigest(this.projectName, this.repoName, reference.child_digest));
});
}
});
forkJoin(observArr).subscribe((res) => {
this.findArtifactFromIndex(res);
});
}
}
delOperate(artifact: Artifact): Observable<any> | null { delOperate(artifact: Artifact): Observable<any> | null {
// init operation info // init operation info
@ -767,8 +767,13 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
// operateChanges(operMessage, OperationState.failure, wrongInfo); // operateChanges(operMessage, OperationState.failure, wrongInfo);
// }); // });
// } else { // } else {
return this.artifactService let params: NewArtifactService.DeleteArtifactParams = {
.deleteArtifact(this.projectName, this.repoName, artifact.digest) projectName: this.projectName,
repositoryName: this.repoName,
reference: artifact.digest
};
return this.newArtifactService
.deleteArtifact(params)
.pipe(map( .pipe(map(
response => { response => {
this.translateService.get("BATCH.DELETED_SUCCESS") this.translateService.get("BATCH.DELETED_SUCCESS")
@ -898,5 +903,38 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
const linkUrl = ['harbor', 'projects', this.projectId, 'repositories', this.repoName, 'depth', depth]; const linkUrl = ['harbor', 'projects', this.projectId, 'repositories', this.repoName, 'depth', depth];
this.router.navigate(linkUrl); this.router.navigate(linkUrl);
} }
selectFilterType() {
this.lastFilteredTagName = '';
if (this.filterByType === 'label.id') {
this.openLabelFilterPanel = true;
this.openLabelFilterPiece = true;
} else {
this.openLabelFilterPiece = false;
this.filterOneLabel = this.initFilter;
this.showlabel = false;
this.imageFilterLabels.forEach(data => {
data.iconsShow = false;
});
}
this.doSearchArtifactNames("");
}
selectFilter(showItem: string, filterItem: string) {
this.lastFilteredTagName = filterItem;
this.currentPage = 1;
let st: State = this.currentState;
if (!st) {
st = { page: {} };
}
st.page.size = this.pageSize;
st.page.from = 0;
st.page.to = this.pageSize - 1;
st.filters = [{ property: this.filterByType, value: filterItem }];
this.clrLoad(st);
}
get isFilterReadonly() {
return this.filterByType === 'label.id' ? 'readonly' : null;
}
} }

View File

@ -4,7 +4,8 @@
<clr-icon class="rotate-90 arrow-back" shape="arrow" size="36" (click)="goBack()"></clr-icon> <clr-icon class="rotate-90 arrow-back" shape="arrow" size="36" (click)="goBack()"></clr-icon>
</div> </div>
<div class="title-block"> <div class="title-block">
<h2 sub-header-title class="custom-h2">{{artifactDigest ? artifactDigest : showCurrentTitle | slice:0:15}}</h2> <h2 sub-header-title class="custom-h2" *ngIf="!artifactDigest">{{showCurrentTitle}}</h2>
<h2 sub-header-title class="custom-h2" *ngIf="artifactDigest">{{artifactDigest | slice:1:15}}</h2>
</div> </div>
</div> </div>
</section> </section>

View File

@ -4,3 +4,46 @@ export interface ArtifactFront extends Artifact {
annotationsArray?: string[]; annotationsArray?: string[];
} }
export const mutipleFilter = [
{
filterBy: 'type',
filterByShowText: 'Type',
listItem: [
{
filterText: 'IMAGE',
showItem: 'ARTIFACT.IMAGE',
},
{
filterText: 'CHART',
showItem: 'ARTIFACT.CHART',
},
{
filterText: 'CNAB',
showItem: 'ARTIFACT.CNAB',
}
]
},
{
filterBy: 'tags',
filterByShowText: 'Tags',
listItem: [
{
filterText: '*',
showItem: 'ARTIFACT.TAGGED',
},
{
filterText: 'nil',
showItem: 'ARTIFACT.UNTAGGED',
},
{
filterText: '',
showItem: 'ARTIFACT.ALL',
}
]
},
{
filterBy: 'label.id',
filterByShowText: 'Label',
listItem: []
},
];

View File

@ -120,7 +120,7 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit, OnDestroy
} }
goIntoRepo(repoEvt: NewRepository): void { goIntoRepo(repoEvt: NewRepository): void {
let linkUrl = ['harbor', 'projects', repoEvt.project_id, 'repositories', repoEvt.name.split('/')[1]]; let linkUrl = ['harbor', 'projects', repoEvt.project_id, 'repositories', repoEvt.name.split(`${this.projectName}/`)[1]];
this.router.navigate(linkUrl); this.router.navigate(linkUrl);
} }
@ -219,11 +219,13 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit, OnDestroy
operMessage.name = 'OPERATION.DELETE_REPO'; operMessage.name = 'OPERATION.DELETE_REPO';
operMessage.data.id = repo.id; operMessage.data.id = repo.id;
operMessage.state = OperationState.progressing; operMessage.state = OperationState.progressing;
repo.name = repo.name.split(`${this.projectName}/`)[1];
operMessage.data.name = repo.name; operMessage.data.name = repo.name;
this.operationService.publishInfo(operMessage); this.operationService.publishInfo(operMessage);
return this.newRepoService return this.newRepoService
.deleteRepository({ .deleteRepository({
repositoryName: repo.name.split('/')[1], repositoryName: repo.name,
projectName: this.projectName projectName: this.projectName
}) })
.pipe(map( .pipe(map(

View File

@ -996,8 +996,15 @@
"ARTIFACT": { "ARTIFACT": {
"FILTER_FOR_ARTIFACTS": "Filter Artifacts", "FILTER_FOR_ARTIFACTS": "Filter Artifacts",
"ADDITIONS": "Additions", "ADDITIONS": "Additions",
"ANNOTATION": "Annotation", "ANNOTATION": "Annotations",
"EXTRA_PROPERTIES": "Extra Attributes" "EXTRA_PROPERTIES": "Extra Attributes",
"IMAGE": "IMAGE",
"CHART": "CHART",
"CNAB": "CNAB",
"TAGGED": "Tagged",
"UNTAGGED": "Untagged",
"ALL": "All",
"PLACEHOLDER": "We couldn't find any artifactds!"
}, },
"TAG": { "TAG": {
"CREATION_TIME_PREFIX": "Create on", "CREATION_TIME_PREFIX": "Create on",

View File

@ -995,8 +995,15 @@
"ARTIFACT": { "ARTIFACT": {
"FILTER_FOR_ARTIFACTS": "Filter Artifacts", "FILTER_FOR_ARTIFACTS": "Filter Artifacts",
"ADDITIONS": "Additions", "ADDITIONS": "Additions",
"ANNOTATION": "Annotation", "ANNOTATION": "Annotations",
"EXTRA_PROPERTIES": "Extra Attributes" "EXTRA_PROPERTIES": "Extra Attributes",
"IMAGE": "IMAGE",
"CHART": "CHART",
"CNAB": "CNAB",
"TAGGED": "Tagged",
"UNTAGGED": "Untagged",
"ALL": "All",
"PLACEHOLDER": "We couldn't find any artifacts!"
}, },
"TAG": { "TAG": {
"CREATION_TIME_PREFIX": "Create on", "CREATION_TIME_PREFIX": "Create on",

View File

@ -968,8 +968,15 @@
"ARTIFACT": { "ARTIFACT": {
"FILTER_FOR_ARTIFACTS": "Filter Artifacts", "FILTER_FOR_ARTIFACTS": "Filter Artifacts",
"ADDITIONS": "Additions", "ADDITIONS": "Additions",
"ANNOTATION": "Annotation", "ANNOTATION": "Annotations",
"EXTRA_PROPERTIES": "Extra Attributes" "EXTRA_PROPERTIES": "Extra Attributes",
"IMAGE": "IMAGE",
"CHART": "CHART",
"CNAB": "CNAB",
"TAGGED": "Tagged",
"UNTAGGED": "Untagged",
"ALL": "All",
"PLACEHOLDER": "We couldn't find any artifacts!"
}, },
"TAG": { "TAG": {
"CREATION_TIME_PREFIX": "Créer le", "CREATION_TIME_PREFIX": "Créer le",

View File

@ -991,8 +991,15 @@
"ARTIFACT": { "ARTIFACT": {
"FILTER_FOR_ARTIFACTS": "Filter Artifacts", "FILTER_FOR_ARTIFACTS": "Filter Artifacts",
"ADDITIONS": "Additions", "ADDITIONS": "Additions",
"ANNOTATION": "Annotation", "ANNOTATION": "Annotations",
"EXTRA_PROPERTIES": "Extra Attributes" "EXTRA_PROPERTIES": "Extra Attributes",
"IMAGE": "IMAGE",
"CHART": "CHART",
"CNAB": "CNAB",
"TAGGED": "Tagged",
"UNTAGGED": "Untagged",
"ALL": "All",
"PLACEHOLDER": "We couldn't find any artifacts!"
}, },
"TAG": { "TAG": {
"CREATION_TIME_PREFIX": "Criado em", "CREATION_TIME_PREFIX": "Criado em",

View File

@ -995,8 +995,15 @@
"ARTIFACT": { "ARTIFACT": {
"FILTER_FOR_ARTIFACTS": "Filter Artifacts", "FILTER_FOR_ARTIFACTS": "Filter Artifacts",
"ADDITIONS": "Additions", "ADDITIONS": "Additions",
"ANNOTATION": "Annotation", "ANNOTATION": "Annotations",
"EXTRA_PROPERTIES": "Extra Attributes" "EXTRA_PROPERTIES": "Extra Attributes",
"IMAGE": "IMAGE",
"CHART": "CHART",
"CNAB": "CNAB",
"TAGGED": "Tagged",
"UNTAGGED": "Untagged",
"ALL": "All",
"PLACEHOLDER": "We couldn't find any artifacts!"
}, },
"TAG": { "TAG": {
"CREATION_TIME_PREFIX": "Oluştur", "CREATION_TIME_PREFIX": "Oluştur",

View File

@ -996,7 +996,14 @@
"FILTER_FOR_ARTIFACTS": "Filter Artifacts", "FILTER_FOR_ARTIFACTS": "Filter Artifacts",
"ADDITIONS": "其他", "ADDITIONS": "其他",
"ANNOTATION": "注解", "ANNOTATION": "注解",
"EXTRA_PROPERTIES": "额外属性" "EXTRA_PROPERTIES": "额外属性",
"IMAGE": "镜像",
"CHART": "CHART",
"CNAB": "CNAB",
"TAGGED": "含有 Tag",
"UNTAGGED": "不含有 Tag",
"ALL": "所有",
"PLACEHOLDER": "未发现任何 artifacts!"
}, },
"TAG": { "TAG": {
"CREATION_TIME_PREFIX": "创建时间:", "CREATION_TIME_PREFIX": "创建时间:",

View File

@ -42,7 +42,7 @@ export class ImageNameInputComponent implements OnInit, OnDestroy {
ngOnInit(): void { ngOnInit(): void {
this.proNameChecker this.proNameChecker
.pipe(debounceTime(200)) .pipe(debounceTime(200))
.pipe(distinctUntilChanged(), .pipe(
switchMap(name => { switchMap(name => {
this.noProjectInfo = ""; this.noProjectInfo = "";
this.selectedProjectList = []; this.selectedProjectList = [];

View File

@ -42,6 +42,12 @@ export const errorHandler = function (error: any): string {
if (!error) { if (!error) {
return "UNKNOWN_ERROR"; return "UNKNOWN_ERROR";
} }
// oci standard
if (error.errors && error.errors instanceof Array && error.errors.length) {
return error.errors.reduce((preError, currentError, index) => {
return preError ? `${preError},${currentError.message}` : currentError.message;
}, '');
}
// Not a standard error return Basically not used cover unknown error // Not a standard error return Basically not used cover unknown error
try { try {
return JSON.parse(error.error).message; return JSON.parse(error.error).message;
@ -50,6 +56,12 @@ export const errorHandler = function (error: any): string {
if (typeof error.error === "string") { if (typeof error.error === "string") {
return error.error; return error.error;
} }
// oci standard
if (error.error && error.error.errors && error.error.errors instanceof Array && error.error.errors.length) {
return error.error.errors.reduce((preError, currentError, index) => {
return preError ? `${preError},${currentError.message}` : currentError.message;
}, '');
}
if (error.error && error.error.message) { if (error.error && error.error.message) {
if (typeof error.error.message === "string") { if (typeof error.error.message === "string") {
// handle docker client response error // handle docker client response error