[Fixed] Fix bug for 2.0 and add case for trivy

Signed-off-by: Yogi_Wang <yawang@vmware.com>
1.add case for trivy
2.vunerbility refresh bug
3.scan mutiple artifact
4.fix global search bug
5.disable delete tag btn when remove immutable tag
6.cancel selectRow when add label or remove label;fix #11195
7.fix cron tootip
This commit is contained in:
Yogi_Wang 2020-03-24 10:56:18 +08:00
parent d05817c8a2
commit cba4490a5a
16 changed files with 102 additions and 49 deletions

View File

@ -91,7 +91,7 @@
[(clrDgSelected)]="selectedRow">
<clr-dg-action-bar>
<button [clrLoading]="scanBtnState" type="button" class="btn btn-secondary scan-btn"
[disabled]="!(canScanNow() && selectedRowHasVul() && selectedRow.length==1 && hasEnabledScanner && hasScanImagePermission )" (click)="scanNow()">
[disabled]="!(canScanNow() && selectedRowHasVul() && hasEnabledScanner && hasScanImagePermission )" (click)="scanNow()">
<clr-icon shape="shield-check" size="16"></clr-icon>&nbsp;{{'VULNERABILITY.SCAN_NOW' | translate}}
</button>
@ -110,7 +110,7 @@
(click)="addLabels()">
{{'REPOSITORY.ADD_LABELS' | translate}}
</button>
<clr-dropdown-menu>
<clr-dropdown-menu [hidden]="!selectedRow.length">
<div class="filter-grid">
<label
class="dropdown-header">{{'REPOSITORY.ADD_LABEL_TO_IMAGE' | translate}}</label>
@ -208,17 +208,17 @@
<tr>
<th class="left tag-header-color">
{{'REPOSITORY.TAGS_COUNT' | translate | uppercase}}</th>
<th class="left tag-header-color">
{{'REPOSITORY.PUSH_TIME' | translate | uppercase}}</th>
<th class="left tag-header-color">
{{'REPOSITORY.PULL_TIME' | translate | uppercase}}</th>
<th class="left tag-header-color">
{{'REPOSITORY.PUSH_TIME' | translate | uppercase}}</th>
</tr>
</thead>
<tbody class="tag-tbody">
<tr class="tag-tr" *ngFor="let tag of artifact.tags">
<td class="left tag-body-color">{{tag.name}}</td>
<td class="left tag-body-color">{{tag.pull_time === availableTime ? '':(tag.pull_time | date: 'short')}}</td>
<td class="left tag-body-color">{{tag.push_time | date: 'short'}}</td>
<td class="left tag-body-color">{{tag.pull_time | date: 'short'}}</td>
</tr>
</tbody>
</table>

View File

@ -162,6 +162,10 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
openSelectFilterPiece = false;
// could Pagination filter
filters: string[];
scanFiinishArtifactLength: number = 0;
onScanArtifactsLength: number = 0;
constructor(
private errorHandlerService: ErrorHandler,
private userPermissionService: UserPermissionService,
@ -731,6 +735,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
forkJoin(...this.deleteArtifactobservableLists).subscribe((deleteResult) => {
let deleteSuccessList = [];
let deleteErrorList = [];
this.deleteArtifactobservableLists = [];
deleteResult.forEach(result => {
if (!result) {
// delete success
@ -739,9 +744,9 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
deleteErrorList.push(result);
}
});
this.selectedRow = [];
if (deleteSuccessList.length === deleteResult.length) {
// all is success
this.selectedRow = [];
let st: ClrDatagridStateInterface = { page: {from: 0, to: this.pageSize - 1, size: this.pageSize} };
this.clrLoad(st);
} else if (deleteErrorList.length === deleteResult.length) {
@ -752,7 +757,6 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
// some artifact delete success but it has error delete things
this.errorHandlerService.error(deleteErrorList[deleteErrorList.length - 1].error);
// if delete one success refresh list
this.selectedRow = [];
let st: ClrDatagridStateInterface = { page: {from: 0, to: this.pageSize - 1, size: this.pageSize} };
this.clrLoad(st);
}
@ -871,10 +875,16 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
}
// Trigger scan
scanNow(): void {
if (this.selectedRow && this.selectedRow.length === 1) {
this.onSendingScanCommand = true;
this.channel.publishScanEvent(this.repoName + "/" + this.selectedRow[0].digest);
if (!this.selectedRow.length) {
return;
}
this.scanFiinishArtifactLength = 0;
this.onScanArtifactsLength = this.selectedRow.length;
this.onSendingScanCommand = true;
this.selectedRow.forEach((data: any) => {
let digest = data.digest;
this.channel.publishScanEvent(this.repoName + "/" + digest);
});
}
selectedRowHasVul(): boolean {
return !!(this.selectedRow
@ -886,7 +896,11 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
return !!(artifact && artifact.addition_links && artifact.addition_links[ADDITIONS.VULNERABILITIES]);
}
submitFinish(e: boolean) {
this.onSendingScanCommand = e;
this.scanFiinishArtifactLength += 1;
// all selected scan action has start
if (this.scanFiinishArtifactLength === this.onScanArtifactsLength) {
this.onSendingScanCommand = e;
}
}
// pull command
onCpError($event: any): void {

View File

@ -91,7 +91,10 @@ export class ArtifactVulnerabilitiesComponent implements OnInit, OnDestroy {
this.hasShowLoading = true;
}
this.additionsService.getDetailByLink(this.vulnerabilitiesLink.href)
.pipe(finalize(() => this.loading = false))
.pipe(finalize(() => {
this.loading = false;
this.hasShowLoading = false;
}))
.subscribe(
res => {
this.scan_overview = res;

View File

@ -22,7 +22,7 @@
<artifact-common-properties [artifactDetails]="artifact"></artifact-common-properties>
<!-- tags -->
<artifact-tag [artifactDetails]="artifact" [projectName]="projectName" [repositoryName]="repositoryName"
<artifact-tag [artifactDetails]="artifact" [projectName]="projectName" [projectId]="projectId" [repositoryName]="repositoryName"
(refreshArtifact)="refreshArtifact()"></artifact-tag>
<!-- Additions -->

View File

@ -4,7 +4,7 @@
<button type="button" class="btn btn-secondary" (click)="addTag()">
<clr-icon shape="plus" size="16"></clr-icon>&nbsp;{{'TAG.ADD_TAG' | translate}}
</button>
<button type="button" class="btn btn-secondary" [disabled]="!(selectedRow.length>=1)" (click)="removeTag()">
<button type="button" class="btn btn-secondary" [disabled]="!(selectedRow.length>=1&& !hasImmutableOnTag() && hasDeleteTagPermission)" (click)="removeTag()">
<clr-icon shape="trash" size="16"></clr-icon>&nbsp;{{'TAG.REMOVE_TAG' | translate}}
</button>
<form #tagForm="ngForm" [hidden]="!newTagformShow" class="label-form stack-block-label">
@ -46,7 +46,7 @@
<span *ngIf="tag.immutable" class="label label-info ml-8">{{'REPOSITORY.IMMUTABLE' | translate}}</span>
</div>
</clr-dg-cell>
<clr-dg-cell [ngSwitch]="tag.signature != null">
<clr-dg-cell [ngSwitch]="tag.signed">
<div class="cell">
<clr-icon shape="check-circle" *ngSwitchCase="true" size="20" class="color-green"></clr-icon>
<clr-icon shape="times-circle" *ngSwitchCase="false" size="16" class="color-red"></clr-icon>

View File

@ -10,6 +10,7 @@ import { ErrorHandler } from "../../../../../lib/utils/error-handler";
import { ArtifactService } from '../../../../../../ng-swagger-gen/services/artifact.service';
import { OperationService } from "../../../../../lib/components/operation/operation.service";
import { CURRENT_BASE_HREF } from "../../../../../lib/utils/utils";
import { USERSTATICPERMISSION, UserPermissionService, UserPermissionDefaultService } from '../../../../../lib/services';
describe('ArtifactTagComponent', () => {
@ -25,6 +26,11 @@ describe('ArtifactTagComponent', () => {
const config: IServiceConfig = {
repositoryBaseEndpoint: CURRENT_BASE_HREF + "/repositories/testing"
};
let userPermissionService;
const permissions = [
{ resource: USERSTATICPERMISSION.REPOSITORY_TAG.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG.VALUE.DELETE },
];
let mockHasDeleteImagePermission: boolean = true;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
@ -41,6 +47,7 @@ describe('ArtifactTagComponent', () => {
{ provide: SERVICE_CONFIG, useValue: config },
{ provide: mockErrorHandler, useValue: ErrorHandler },
{ provide: ArtifactService, useValue: mockArtifactService },
{ provide: UserPermissionService, useClass: UserPermissionDefaultService },
{ provide: OperationService },
]
})
@ -50,6 +57,11 @@ describe('ArtifactTagComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(ArtifactTagComponent);
component = fixture.componentInstance;
userPermissionService = fixture.debugElement.injector.get(UserPermissionService);
spyOn(userPermissionService, "hasProjectPermissions")
.withArgs(component.projectId, permissions)
.and.returnValue(of([
mockHasDeleteImagePermission]));
fixture.detectChanges();
});

View File

@ -17,7 +17,10 @@ import { errorHandler } from "../../../../../lib/utils/shared/shared.utils";
import { ArtifactFront as Artifact } from "../artifact";
import { ArtifactService } from '../../../../../../ng-swagger-gen/services/artifact.service';
import { Tag } from '../../../../../../ng-swagger-gen/models/tag';
import {
UserPermissionService, USERSTATICPERMISSION
} from "../../../../../lib/services";
class InitTag {
name = "";
}
@ -30,6 +33,7 @@ class InitTag {
export class ArtifactTagComponent implements OnInit {
@Input() artifactDetails: Artifact;
@Input() projectName: string;
@Input() projectId: number;
@Input() repositoryName: string;
@Output() refreshArtifact = new EventEmitter();
newTagName = new InitTag();
@ -43,15 +47,25 @@ export class ArtifactTagComponent implements OnInit {
availableTime = AVAILABLE_TIME;
@ViewChild("confirmationDialog", { static: false })
confirmationDialog: ConfirmationDialogComponent;
hasDeleteTagPermission: boolean;
constructor(
private operationService: OperationService,
private artifactService: ArtifactService,
private translateService: TranslateService,
private userPermissionService: UserPermissionService,
private errorHandlerService: ErrorHandler
) { }
ngOnInit() {
this.getImagePermissionRule(this.projectId);
}
getImagePermissionRule(projectId: number): void {
const permissions = [
{ resource: USERSTATICPERMISSION.REPOSITORY_TAG.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG.VALUE.DELETE }
];
this.userPermissionService.hasProjectPermissions(this.projectId, permissions).subscribe((results: Array<boolean>) => {
this.hasDeleteTagPermission = results[0];
}, error => this.errorHandlerService.error(error));
}
addTag() {
@ -166,4 +180,8 @@ export class ArtifactTagComponent implements OnInit {
this.openTag = !this.openTag;
this.newTagformShow = false;
}
hasImmutableOnTag(): boolean {
return this.selectedRow.some((artifact) => artifact.immutable);
}
}

View File

@ -68,8 +68,9 @@ export class ListRepositoryROComponent implements OnInit, OnDestroy {
public gotoLink(projectId: number, repoName: string): void {
this.searchTrigger.closeSearch(true);
let linkUrl = ['harbor', 'tags', projectId, repoName];
let projectName = repoName.split('/')[0];
let repositorieName = projectName ? repoName.split(`${projectName}/`)[1] : repoName;
let linkUrl = ['harbor', 'projects', projectId, 'repositories', repositorieName ];
this.router.navigate(linkUrl);
}

View File

@ -693,7 +693,7 @@
"ADD_LABEL_TO_IMAGE": "Add labels to this image",
"FILTER_BY_LABEL": "Filter images by label",
"FILTER_ARTIFACT_BY_LABEL": "Filter actifact by label",
"ADD_LABELS": "Add labels",
"ADD_LABELS": "Add Labels",
"RETAG": "Copy",
"ACTION": "ACTION",
"DEPLOY": "DEPLOY",

View File

@ -694,7 +694,7 @@
"ADD_LABEL_TO_IMAGE": "Add labels to this image",
"FILTER_BY_LABEL": "Filter images by label",
"FILTER_ARTIFACT_BY_LABEL": "Filter actifact by label",
"ADD_LABELS": "Add labels",
"ADD_LABELS": "Add Labels",
"RETAG": "Copy",
"ACTION": "ACTION",
"DEPLOY": "DEPLOY",

View File

@ -680,7 +680,7 @@
"ADD_LABEL_TO_IMAGE": "Add labels to this image",
"FILTER_BY_LABEL": "Filter images by label",
"FILTER_ARTIFACT_BY_LABEL": "Filter actifact by label",
"ADD_LABELS": "Add labels",
"ADD_LABELS": "Add Labels",
"RETAG": "Copy",
"ACTION": "ACTION",
"DEPLOY": "DEPLOY",

View File

@ -693,7 +693,7 @@
"ADD_LABEL_TO_IMAGE": "Adicionar labels a essa imagem",
"FILTER_BY_LABEL": "Filtrar imagens por label",
"FILTER_ARTIFACT_BY_LABEL": "Filter actifact by label",
"ADD_LABELS": "Adicionar labels",
"ADD_LABELS": "Adicionar Labels",
"RETAG": "Copy",
"ACTION": "AÇÃO",
"DEPLOY": "DEPLOY",

View File

@ -2,29 +2,33 @@
<div class="normal-wrapper">
<span [style.width]="buttonMarginLeft" class="font-style">{{ labelCurrent | translate }}</span>
<span>{{(originScheduleType ? 'SCHEDULE.'+ originScheduleType.toUpperCase(): "") | translate}}</span>
<a [hidden]="originScheduleType!==SCHEDULE_TYPE.HOURLY" href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right">
<clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon>
<span class="tooltip-content">{{'CONFIG.TOOLTIP.HOURLY_CRON' | translate}}</span>
<a [hidden]="originScheduleType!==SCHEDULE_TYPE.HOURLY" href="javascript:void(0)" role="tooltip"
aria-haspopup="true" class="tooltip tooltip-top-right">
<clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon>
<span class="tooltip-content">{{'CONFIG.TOOLTIP.HOURLY_CRON' | translate}}</span>
</a>
<a [hidden]="originScheduleType!==SCHEDULE_TYPE.WEEKLY" href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right">
<clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon>
<span class="tooltip-content">{{'CONFIG.TOOLTIP.WEEKLY_CRON' | translate}}</span>
<a [hidden]="originScheduleType!==SCHEDULE_TYPE.WEEKLY" href="javascript:void(0)" role="tooltip"
aria-haspopup="true" class="tooltip tooltip-top-right">
<clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon>
<span class="tooltip-content">{{'CONFIG.TOOLTIP.WEEKLY_CRON' | translate}}</span>
</a>
<a [hidden]="originScheduleType!==SCHEDULE_TYPE.DAILY" href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right">
<clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon>
<span class="tooltip-content">{{'CONFIG.TOOLTIP.DAILY_CRON' | translate}}</span>
<a [hidden]="originScheduleType!==SCHEDULE_TYPE.DAILY" href="javascript:void(0)" role="tooltip" aria-haspopup="true"
class="tooltip tooltip-top-right">
<clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon>
<span class="tooltip-content">{{'CONFIG.TOOLTIP.DAILY_CRON' | translate}}</span>
</a>
<span [hidden]="originScheduleType!==SCHEDULE_TYPE.CUSTOM">{{ "SCHEDULE.CRON" | translate }} :</span>
<span [hidden]="originScheduleType!==SCHEDULE_TYPE.CUSTOM">{{ oriCron }}</span>
</div>
<button [style.margin-left]="buttonMarginLeft" [disabled]="disabled" class="btn btn-primary " (click)="editSchedule()" id="editSchedule">
<button [style.margin-left]="buttonMarginLeft" [disabled]="disabled" class="btn btn-primary " (click)="editSchedule()"
id="editSchedule">
{{ "BUTTON.EDIT" | translate }}
</button>
</div>
<div class="setting-wrapper flex-layout" *ngIf="isEditMode">
<span [style.width]="buttonMarginLeft" class="font-style">{{ labelEdit | translate }}</span>
<div class="select select-schedule clr-select-wrapper">
<select name="selectPolicy" id="selectPolicy" [(ngModel)]="scheduleType">
<select name="selectPolicy" id="selectPolicy" [(ngModel)]="scheduleType">
<option [value]="SCHEDULE_TYPE.NONE">{{'SCHEDULE.NONE' | translate}}</option>
<option [value]="SCHEDULE_TYPE.HOURLY">{{'SCHEDULE.HOURLY' | translate}}</option>
<option [value]="SCHEDULE_TYPE.DAILY">{{'SCHEDULE.DAILY' | translate}}</option>
@ -34,27 +38,26 @@
</div>
<span class="required" [hidden]="scheduleType!==SCHEDULE_TYPE.CUSTOM">{{ "SCHEDULE.CRON" | translate }}</span>
<div [hidden]="scheduleType!==SCHEDULE_TYPE.CUSTOM" class="cron-input">
<label for="targetCron" aria-haspopup="true" role="tooltip" [class.invalid]="dateInvalid" class="tooltip tooltip-validation tooltip-md tooltip-top-left cron-label">
<input type="text" (blur)="blurInvalid()" (input)="inputInvalid()" name=targetCron id="targetCron" #cronStringInput="ngModel" required class="clr-input form-control"
[(ngModel)]="cronString">
<span class="tooltip-content" *ngIf="dateInvalid">
{{'TOOLTIP.CRON_REQUIRED' | translate }}
</span>
<label for="targetCron" aria-haspopup="true" role="tooltip" [class.clr-error]="dateInvalid"
class="tooltip tooltip-validation tooltip-md tooltip-top-left cron-label">
<input type="text" (blur)="blurInvalid()" (input)="inputInvalid()" name="targetCron" id="targetCron"
#cronStringInput="ngModel" required class="clr-input form-control" [(ngModel)]="cronString">
<clr-control-error *ngIf="dateInvalid">{{'TOOLTIP.CRON_REQUIRED' | translate}}</clr-control-error>
</label>
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-lg tooltip-right top-7 cron-tooltip">
<clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon>
<div class="tooltip-content table-box">
<cron-tooltip></cron-tooltip>
</div>
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true"
class="tooltip tooltip-lg tooltip-right top-7 cron-tooltip">
<clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon>
<div class="tooltip-content table-box">
<cron-tooltip></cron-tooltip>
</div>
</a>
</div>
<div class="confirm-button">
<button [style.margin-left]="buttonMarginLeft" class="btn btn-primary "
(click)="save()" id="config-save">
{{ "BUTTON.SAVE" | translate }}
<button [style.margin-left]="buttonMarginLeft" class="btn btn-primary " (click)="save()" id="config-save">
{{ "BUTTON.SAVE" | translate }}
</button>
<button class="btn btn-primary " (click)="isEditMode=false">
{{ "BUTTON.CANCEL" | translate }}
</button>
</div>
</div>
</div>

View File

@ -47,6 +47,7 @@
.cron-tooltip {
color: gray;
cursor: default;
position: absolute;
.table-box {
width: 20rem;
}

View File

@ -248,7 +248,7 @@ Add Labels To Tag
Retry Element Click xpath=//clr-dg-row[contains(.,'${tagName}')]//label
Capture Page Screenshot add_${labelName}.png
Retry Element Click xpath=//clr-dg-action-bar//clr-dropdown//span
Retry Element Click xpath=//clr-dropdown-menu//clr-dropdown//button[contains(.,'Add labels')]
Retry Element Click xpath=//clr-dropdown-menu//clr-dropdown//button[contains(.,'Add Labels')]
Retry Element Click xpath=//clr-dropdown//div//label[contains(.,'${labelName}')]
Retry Wait Until Page Contains Element xpath=//clr-dg-row//label[contains(.,'${labelName}')]

View File

@ -76,6 +76,7 @@ Switch To Scanners Page
Should Display The Default Clair Scanner
Retry Wait Until Page Contains Element //clr-datagrid//clr-dg-row//clr-dg-cell[contains(.,'Clair')]//span[contains(.,'Default')]
Clair Is Immutable Scanner
Retry Element Click //clr-dg-row[contains(.,'Clair')]//clr-radio-wrapper/label
Retry Double Keywords When Error Retry Element Click ${scanner_action_xpath} Retry Wait Until Page Contains Element ${delete_scanner_action_xpath}