mirror of
https://github.com/goharbor/harbor.git
synced 2024-09-24 19:42:52 +02:00
Add stop scan functionality (#15528)
Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
parent
c5003f38ba
commit
9e9c4a03bb
@ -13,10 +13,10 @@
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-2 flex-200">
|
||||
<div class="btn-scan-right btn-scan margin-top-16px">
|
||||
<button id="scan-now" class="btn btn-primary btn-scan" (click)="scanNow()"
|
||||
[disabled]="!scanAvailable">
|
||||
<span *ngIf="scanAvailable">{{ 'CONFIG.SCANNING.SCAN_NOW' | translate }}</span>
|
||||
<span *ngIf="!scanAvailable">{{ 'CONFIG.SCANNING.SCAN' | translate }}</span>
|
||||
<button id="scan" class="btn btn-primary btn-scan" (click)="scanOrStop()"
|
||||
[disabled]="onSubmitting || !hasDefaultScanner">
|
||||
<span *ngIf="!isOnScanning()">{{ 'CONFIG.SCANNING.SCAN_NOW' | translate }}</span>
|
||||
<span *ngIf="isOnScanning()">{{ 'VULNERABILITY.STOP_NOW' | translate }}</span>
|
||||
</button>
|
||||
<span [hidden]="!isOnScanning()" class="spinner spinner-inline margin-left-5 v-mid"></span>
|
||||
</div>
|
||||
|
@ -4,9 +4,10 @@ import { ScanAllRepoService } from "./scanAll.service";
|
||||
import { of } from "rxjs";
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
|
||||
import { ScanningMetrics, Triggers } from "../../config/config";
|
||||
import { ErrorHandler } from "../../../../shared/units/error-handler";
|
||||
import { Scanner } from "../scanner/scanner";
|
||||
import { SharedTestingModule } from "../../../../shared/shared.module";
|
||||
import { delay } from "rxjs/operators";
|
||||
import { ScanAllService } from "../../../../../../ng-swagger-gen/services/scan-all.service";
|
||||
|
||||
let component: VulnerabilityConfigComponent;
|
||||
let fixture: ComponentFixture<VulnerabilityConfigComponent>;
|
||||
@ -51,9 +52,10 @@ let fakedScanAllRepoService = {
|
||||
return of(null);
|
||||
}
|
||||
};
|
||||
let fakedErrorHandler = {
|
||||
info() {
|
||||
return null;
|
||||
|
||||
const fakedScanAllService = {
|
||||
stopScanAll() {
|
||||
return of(null);
|
||||
}
|
||||
};
|
||||
|
||||
@ -70,8 +72,8 @@ describe('VulnerabilityConfigComponent', () => {
|
||||
VulnerabilityConfigComponent
|
||||
],
|
||||
providers: [
|
||||
{provide: ErrorHandler, useValue: fakedErrorHandler},
|
||||
{provide: ScanAllRepoService, useValue: fakedScanAllRepoService},
|
||||
{provide: ScanAllService, useValue: fakedScanAllService},
|
||||
// open auto detect
|
||||
{ provide: ComponentFixtureAutoDetect, useValue: true }
|
||||
]
|
||||
@ -94,7 +96,7 @@ describe('VulnerabilityConfigComponent', () => {
|
||||
expect(component.scanAvailable).toBeFalsy();
|
||||
});
|
||||
it('will trigger scan now and get manual metrics', () => {
|
||||
const button = fixture.nativeElement.querySelector('#scan-now');
|
||||
const button = fixture.nativeElement.querySelector('#scan');
|
||||
button.click();
|
||||
const ele = fixture.nativeElement.querySelector('.finished');
|
||||
expect(ele.style.width).toEqual('80px');
|
||||
@ -104,5 +106,11 @@ describe('VulnerabilityConfigComponent', () => {
|
||||
component.scanningMetrics.ongoing = false;
|
||||
component.hasDefaultScanner = true;
|
||||
expect(component.scanAvailable).toBeTruthy();
|
||||
const spnManual: HTMLSpanElement = fixture.nativeElement.querySelector(".float-left");
|
||||
expect(spnManual.innerText).toEqual('CONFIG.SCANNING.SCHEDULED');
|
||||
const scanBtn: HTMLButtonElement = fixture.nativeElement.querySelector("#scan");
|
||||
scanBtn.click();
|
||||
const spnSchedule: HTMLSpanElement = fixture.nativeElement.querySelector(".float-left");
|
||||
expect(spnSchedule.innerText).toEqual('CONFIG.SCANNING.MANUAL');
|
||||
});
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import { finalize } from "rxjs/operators";
|
||||
import { ScanningMetrics, Triggers } from '../../config/config';
|
||||
import { ErrorHandler } from '../../../../shared/units/error-handler';
|
||||
@ -9,9 +9,11 @@ import { CronScheduleComponent } from "../../../../shared/components/cron-schedu
|
||||
import { DATABASE_UPDATED_PROPERTY, VULNERABILITY_SCAN_STATUS } from "../../../../shared/units/utils";
|
||||
import { DatePipe } from "@angular/common";
|
||||
import { errorHandler } from "../../../../shared/units/shared.utils";
|
||||
import { ScanAllService } from "../../../../../../ng-swagger-gen/services/scan-all.service";
|
||||
|
||||
const SCHEDULE_TYPE_NONE = "None";
|
||||
const TIMEOUT: number = 5000;
|
||||
|
||||
@Component({
|
||||
selector: 'vulnerability-config',
|
||||
templateUrl: './vulnerability-config.component.html',
|
||||
@ -42,18 +44,23 @@ export class VulnerabilityConfigComponent implements OnInit, OnDestroy {
|
||||
onGettingUpdatedTimeStr: boolean;
|
||||
hasDefaultScanner: boolean = false;
|
||||
private _timeout: any;
|
||||
|
||||
constructor(
|
||||
private scanningService: ScanAllRepoService,
|
||||
private newScanAllService: ScanAllService,
|
||||
private errorHandlerEntity: ErrorHandler,
|
||||
private translate: TranslateService,
|
||||
) { }
|
||||
) {
|
||||
}
|
||||
|
||||
get scanningMetrics(): ScanningMetrics {
|
||||
return this._scanningMetrics;
|
||||
}
|
||||
|
||||
set scanningMetrics(metrics: ScanningMetrics) {
|
||||
this._scanningMetrics = metrics;
|
||||
}
|
||||
|
||||
get scanAvailable(): boolean {
|
||||
return !this.onSubmitting
|
||||
&& !this.gettingMetrics
|
||||
@ -66,6 +73,7 @@ export class VulnerabilityConfigComponent implements OnInit, OnDestroy {
|
||||
this.getLabelCurrent = res;
|
||||
});
|
||||
}
|
||||
|
||||
getSchedule() {
|
||||
this.onGoing = true;
|
||||
this.scanningService.getSchedule()
|
||||
@ -78,6 +86,7 @@ export class VulnerabilityConfigComponent implements OnInit, OnDestroy {
|
||||
this.errorHandlerEntity.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
getScanners() {
|
||||
this.onGettingUpdatedTimeStr = true;
|
||||
this.scanningService.getScanners()
|
||||
@ -108,6 +117,7 @@ export class VulnerabilityConfigComponent implements OnInit, OnDestroy {
|
||||
this.onGettingUpdatedTimeStr = false;
|
||||
});
|
||||
}
|
||||
|
||||
getScannerMetadata(uid: string) {
|
||||
this.scanningService.getScannerMetadata(uid)
|
||||
.pipe(finalize(() => this.onGettingUpdatedTimeStr = false))
|
||||
@ -133,6 +143,7 @@ export class VulnerabilityConfigComponent implements OnInit, OnDestroy {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.getScanText();
|
||||
this.getScanners();
|
||||
@ -144,10 +155,12 @@ export class VulnerabilityConfigComponent implements OnInit, OnDestroy {
|
||||
this._timeout = null;
|
||||
}
|
||||
}
|
||||
|
||||
isOnScanning(): boolean {
|
||||
return this.scanningMetrics
|
||||
&& this.scanningMetrics.ongoing;
|
||||
}
|
||||
|
||||
getMetrics() {
|
||||
this.gettingMetrics = true;
|
||||
this.scanningService.getMetrics()
|
||||
@ -169,12 +182,14 @@ export class VulnerabilityConfigComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getI18nKey(str: string): string {
|
||||
if (str && this.i18nKeyMap[str]) {
|
||||
return this.i18nKeyMap[str];
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
errorWidth() {
|
||||
if (this.scanningMetrics
|
||||
&& this.scanningMetrics.metrics
|
||||
@ -185,6 +200,7 @@ export class VulnerabilityConfigComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
return '0';
|
||||
}
|
||||
|
||||
finishedWidth() {
|
||||
if (this.scanningMetrics
|
||||
&& this.scanningMetrics.metrics
|
||||
@ -195,6 +211,7 @@ export class VulnerabilityConfigComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
return '0';
|
||||
}
|
||||
|
||||
runningWidth() {
|
||||
if (this.scanningMetrics
|
||||
&& this.scanningMetrics.metrics
|
||||
@ -205,6 +222,7 @@ export class VulnerabilityConfigComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
return '0';
|
||||
}
|
||||
|
||||
abortWidth() {
|
||||
if (this.scanningMetrics
|
||||
&& this.scanningMetrics.metrics
|
||||
@ -215,6 +233,7 @@ export class VulnerabilityConfigComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
return '0';
|
||||
}
|
||||
|
||||
scanNow(): void {
|
||||
if (this.onSubmitting) {
|
||||
return; // Aoid duplicated submitting
|
||||
@ -238,7 +257,7 @@ export class VulnerabilityConfigComponent implements OnInit, OnDestroy {
|
||||
, error => {
|
||||
if (error && error.status && error.status === 412) {
|
||||
this.translate.get("CONFIG.SCANNING.TRIGGER_SCAN_ALL_FAIL",
|
||||
{ error: '' + errorHandler(error) }).subscribe((res: string) => {
|
||||
{error: '' + errorHandler(error)}).subscribe((res: string) => {
|
||||
this.errorHandlerEntity.error(res);
|
||||
});
|
||||
} else {
|
||||
@ -288,22 +307,47 @@ export class VulnerabilityConfigComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
isError(status: string): boolean {
|
||||
return status === VULNERABILITY_SCAN_STATUS.ERROR;
|
||||
}
|
||||
|
||||
isFinished(status: string): boolean {
|
||||
return status === VULNERABILITY_SCAN_STATUS.SUCCESS;
|
||||
}
|
||||
|
||||
isInProgress(status: string): boolean {
|
||||
return status === VULNERABILITY_SCAN_STATUS.RUNNING;
|
||||
}
|
||||
|
||||
isAborted(status: string): boolean {
|
||||
return status === VULNERABILITY_SCAN_STATUS.STOPPED;
|
||||
}
|
||||
|
||||
isManual() {
|
||||
return this.scanningMetrics && this.scanningMetrics.trigger === Triggers.MANUAL;
|
||||
}
|
||||
|
||||
isSchedule() {
|
||||
return this.scanningMetrics && this.scanningMetrics.trigger === Triggers.SCHEDULE;
|
||||
}
|
||||
|
||||
scanOrStop() {
|
||||
if (this.isOnScanning()) {
|
||||
this.stopNow();
|
||||
} else {
|
||||
this.scanNow();
|
||||
}
|
||||
}
|
||||
|
||||
stopNow() {
|
||||
this.onSubmitting = true;
|
||||
this.newScanAllService.stopScanAll()
|
||||
.pipe(finalize(() => this.onSubmitting = false))
|
||||
.subscribe(res => {
|
||||
this.errorHandlerEntity.info('CONFIG.SCANNING.STOP_SCAN_ALL_SUCCESS');
|
||||
}, error => {
|
||||
this.errorHandlerEntity.error(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -92,6 +92,11 @@ export const INITIAL_ACCESSES: FrontAccess[] = [
|
||||
"resource": "scan",
|
||||
"action": "create",
|
||||
"checked": true
|
||||
},
|
||||
{
|
||||
"resource": "scan",
|
||||
"action": "stop",
|
||||
"checked": true
|
||||
}
|
||||
];
|
||||
|
||||
@ -108,7 +113,8 @@ export const ACTION_RESOURCE_I18N_MAP = {
|
||||
'tag': 'REPLICATION.TAG',
|
||||
'artifact-label': 'SYSTEM_ROBOT.ARTIFACT_LABEL',
|
||||
'scan': 'SYSTEM_ROBOT.SCAN',
|
||||
'scanner-pull': 'SYSTEM_ROBOT.SCANNER_PULL'
|
||||
'scanner-pull': 'SYSTEM_ROBOT.SCANNER_PULL',
|
||||
'stop': 'SYSTEM_ROBOT.STOP'
|
||||
};
|
||||
|
||||
export enum ExpirationType {
|
||||
|
@ -11,14 +11,16 @@
|
||||
<clr-dg-action-bar>
|
||||
<div class="clr-row center">
|
||||
<div class="ml-05">
|
||||
<button id="scan-btn" (click)="scanNow()" type="button" class="btn btn-secondary" [clrLoading]="scanBtnState" [disabled]="!(hasEnabledScanner && hasScanningPermission && !onSendingScanCommand)">
|
||||
<clr-icon shape="shield-check" size="16"></clr-icon>
|
||||
<span>{{'VULNERABILITY.SCAN_NOW' | translate}}</span>
|
||||
<button id="scan-btn" (click)="scanOrStop()" type="button" class="btn btn-secondary" [clrLoading]="scanBtnState" [disabled]="!canScan()">
|
||||
<clr-icon shape="shield-check" size="16" *ngIf="!isRunningState()"></clr-icon>
|
||||
<clr-icon shape="stop" size="16" *ngIf="isRunningState()"></clr-icon>
|
||||
<span *ngIf="!isRunningState()">{{'VULNERABILITY.SCAN_NOW' | translate}}</span>
|
||||
<span *ngIf="isRunningState()">{{'VULNERABILITY.STOP_NOW' | translate}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="ml-1">
|
||||
<div [hidden]="!shouldShowBar()">
|
||||
<hbr-vulnerability-bar [summary]="handleScanOverview(artifact?.scan_overview)" [inputScanner]="scanner"
|
||||
<hbr-vulnerability-bar (submitStopFinish)="submitStopFinish($event)" [summary]="handleScanOverview(artifact?.scan_overview)" [inputScanner]="scanner"
|
||||
(submitFinish)="submitFinish($event)" [projectName]="projectName" [repoName]="repoName"
|
||||
[artifactDigest]="digest">
|
||||
</hbr-vulnerability-bar>
|
||||
|
@ -15,7 +15,6 @@ import {
|
||||
} from "../../../../../../shared/services";
|
||||
import { AdditionLink } from "../../../../../../../../ng-swagger-gen/models/addition-link";
|
||||
import { ErrorHandler } from "../../../../../../shared/units/error-handler";
|
||||
import { ChannelService } from "../../../../../../shared/services/channel.service";
|
||||
import { SessionService } from "../../../../../../shared/services/session.service";
|
||||
import { SessionUser } from "../../../../../../shared/entities/session-user";
|
||||
import { delay } from "rxjs/operators";
|
||||
@ -66,13 +65,6 @@ describe('ArtifactVulnerabilitiesComponent', () => {
|
||||
return of(true);
|
||||
}
|
||||
};
|
||||
const fakedChannelService = {
|
||||
ArtifactDetail$: {
|
||||
subscribe() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
const mockedUser: SessionUser = {
|
||||
user_id: 1,
|
||||
username: 'admin',
|
||||
@ -120,7 +112,6 @@ describe('ArtifactVulnerabilitiesComponent', () => {
|
||||
{provide: AdditionsService, useValue: fakedAdditionsService},
|
||||
{provide: UserPermissionService, useValue: fakedUserPermissionService},
|
||||
{provide: ScanningResultService, useValue: fakedScanningResultService},
|
||||
{provide: ChannelService, useValue: fakedChannelService},
|
||||
{provide: SessionService, useValue: fakedSessionService},
|
||||
{provide: ProjectService, useValue: fakedProjectService},
|
||||
],
|
||||
@ -160,4 +151,10 @@ describe('ArtifactVulnerabilitiesComponent', () => {
|
||||
const cells = firstRow.querySelectorAll("clr-dg-cell");
|
||||
expect(cells[cells.length - 1].innerText).toEqual("TAG_RETENTION.YES");
|
||||
});
|
||||
|
||||
it("scan button should show the right text", async () => {
|
||||
fixture.autoDetectChanges(true);
|
||||
const scanBtn: HTMLButtonElement = fixture.nativeElement.querySelector("#scan-btn");
|
||||
expect(scanBtn.innerText).toContain("VULNERABILITY.SCAN_NOW");
|
||||
});
|
||||
});
|
||||
|
@ -14,11 +14,11 @@ import {
|
||||
} from "../../../../../../shared/services";
|
||||
import { ErrorHandler } from "../../../../../../shared/units/error-handler";
|
||||
import { SEVERITY_LEVEL_MAP, VULNERABILITY_SEVERITY } from "../../../../../../shared/units/utils";
|
||||
import { ChannelService } from "../../../../../../shared/services/channel.service";
|
||||
import { ResultBarChartComponent } from "../../vulnerability-scanning/result-bar-chart.component";
|
||||
import { Subscription } from "rxjs";
|
||||
import { Artifact } from "../../../../../../../../ng-swagger-gen/models/artifact";
|
||||
import { SessionService } from "../../../../../../shared/services/session.service";
|
||||
import { EventService, HarborEvent } from "../../../../../../services/event-service/event.service";
|
||||
|
||||
@Component({
|
||||
selector: 'hbr-artifact-vulnerabilities',
|
||||
@ -49,6 +49,7 @@ export class ArtifactVulnerabilitiesComponent implements OnInit, OnDestroy {
|
||||
cvssSort: ClrDatagridComparatorInterface<VulnerabilityItem>;
|
||||
hasScanningPermission: boolean = false;
|
||||
onSendingScanCommand: boolean = false;
|
||||
onSendingStopCommand: boolean = false;
|
||||
hasShowLoading: boolean = false;
|
||||
@ViewChild(ResultBarChartComponent)
|
||||
resultBarChartComponent: ResultBarChartComponent;
|
||||
@ -60,7 +61,7 @@ export class ArtifactVulnerabilitiesComponent implements OnInit, OnDestroy {
|
||||
private additionsService: AdditionsService,
|
||||
private userPermissionService: UserPermissionService,
|
||||
private scanningService: ScanningResultService,
|
||||
private channel: ChannelService,
|
||||
private eventService: EventService,
|
||||
private session: SessionService,
|
||||
private projectService: ProjectService,
|
||||
private systemInfoService: SystemInfoService,
|
||||
@ -86,8 +87,10 @@ export class ArtifactVulnerabilitiesComponent implements OnInit, OnDestroy {
|
||||
this.getScanningPermission();
|
||||
this.getProjectScanner();
|
||||
if (!this.sub) {
|
||||
this.sub = this.channel.ArtifactDetail$.subscribe(tag => {
|
||||
this.sub = this.eventService.subscribe(HarborEvent.UPDATE_VULNERABILITY_INFO, (artifact: Artifact) => {
|
||||
if (artifact?.digest === this.artifact?.digest) {
|
||||
this.getVulnerabilities();
|
||||
}
|
||||
});
|
||||
}
|
||||
setTimeout(() => {
|
||||
@ -190,20 +193,27 @@ export class ArtifactVulnerabilitiesComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
scanNow() {
|
||||
this.onSendingScanCommand = true;
|
||||
this.channel.publishScanEvent(this.repoName + "/" + this.digest);
|
||||
this.eventService.publish(HarborEvent.START_SCAN_ARTIFACT, this.repoName + "/" + this.digest);
|
||||
}
|
||||
submitFinish(e: boolean) {
|
||||
this.onSendingScanCommand = e;
|
||||
}
|
||||
submitStopFinish(e: boolean) {
|
||||
this.onSendingStopCommand = e;
|
||||
}
|
||||
shouldShowBar(): boolean {
|
||||
return this.hasViewInitWithDelay && this.resultBarChartComponent
|
||||
&& (this.resultBarChartComponent.queued || this.resultBarChartComponent.scanning || this.resultBarChartComponent.error);
|
||||
&& (this.resultBarChartComponent.queued
|
||||
|| this.resultBarChartComponent.scanning
|
||||
|| this.resultBarChartComponent.error
|
||||
|| this.resultBarChartComponent.stopped);
|
||||
}
|
||||
hasScanned(): boolean {
|
||||
return this.hasViewInitWithDelay && this.resultBarChartComponent
|
||||
&& !(this.resultBarChartComponent.completed
|
||||
|| this.resultBarChartComponent.error
|
||||
|| this.resultBarChartComponent.queued
|
||||
|| this.resultBarChartComponent.stopped
|
||||
|| this.resultBarChartComponent.scanning);
|
||||
}
|
||||
handleScanOverview(scanOverview: any): any {
|
||||
@ -256,4 +266,23 @@ export class ArtifactVulnerabilitiesComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
return "";
|
||||
}
|
||||
isRunningState(): boolean {
|
||||
return this.hasViewInitWithDelay && this.resultBarChartComponent
|
||||
&& (this.resultBarChartComponent.queued || this.resultBarChartComponent.scanning);
|
||||
}
|
||||
|
||||
scanOrStop() {
|
||||
if (this.isRunningState()) {
|
||||
this.stopNow();
|
||||
} else {
|
||||
this.scanNow();
|
||||
}
|
||||
}
|
||||
stopNow() {
|
||||
this.onSendingStopCommand = true;
|
||||
this.eventService.publish(HarborEvent.STOP_SCAN_ARTIFACT, this.repoName + "/" + this.digest);
|
||||
}
|
||||
canScan(): boolean {
|
||||
return this.hasEnabledScanner && this.hasScanningPermission && !this.onSendingScanCommand;
|
||||
}
|
||||
}
|
||||
|
@ -96,6 +96,12 @@
|
||||
<span>{{'VULNERABILITY.SCAN_NOW' | translate}}</span>
|
||||
</button>
|
||||
|
||||
<button id="stop-scan" [clrLoading]="stopBtnState" type="button" class="btn btn-secondary scan-btn"
|
||||
[disabled]="!(canStopScan() && hasScanImagePermission )" (click)="stopNow()">
|
||||
<clr-icon shape="stop" size="16"></clr-icon>
|
||||
<span>{{'VULNERABILITY.STOP_NOW' | translate}}</span>
|
||||
</button>
|
||||
|
||||
<clr-dropdown class="btn btn-link" *ngIf="!depth">
|
||||
<span clrDropdownTrigger id="artifact-list-action" class="btn pl-0">
|
||||
{{'BUTTON.ACTIONS' | translate}}
|
||||
@ -257,7 +263,7 @@
|
||||
<span *ngIf="!hasVul(artifact)">
|
||||
{{'ARTIFACT.SCAN_UNSUPPORTED' | translate}}
|
||||
</span>
|
||||
<hbr-vulnerability-bar (scanFinished)="scanFinished($event)" *ngIf="hasVul(artifact)" [inputScanner]="handleScanOverview(artifact.scan_overview)?.scanner"
|
||||
<hbr-vulnerability-bar (submitStopFinish)="submitStopFinish($event)" (scanFinished)="scanFinished($event)" *ngIf="hasVul(artifact)" [inputScanner]="handleScanOverview(artifact.scan_overview)?.scanner"
|
||||
(submitFinish)="submitFinish($event)" [projectName]="projectName" [repoName]="repoName"
|
||||
[artifactDigest]="artifact.digest" [summary]="handleScanOverview(artifact.scan_overview)">
|
||||
</hbr-vulnerability-bar>
|
||||
|
@ -22,7 +22,6 @@ import { ConfirmationDialogComponent } from "../../../../../../../shared/compone
|
||||
import { ImageNameInputComponent } from "../../../../../../../shared/components/image-name-input/image-name-input.component";
|
||||
import { CopyInputComponent } from "../../../../../../../shared/components/push-image/copy-input.component";
|
||||
import { ErrorHandler } from "../../../../../../../shared/units/error-handler";
|
||||
import { ChannelService } from "../../../../../../../shared/services/channel.service";
|
||||
import { OperationService } from "../../../../../../../shared/components/operation/operation.service";
|
||||
import { ArtifactService as NewArtifactService } from "../../../../../../../../../ng-swagger-gen/services/artifact.service";
|
||||
import { Tag } from "../../../../../../../../../ng-swagger-gen/models/tag";
|
||||
@ -307,8 +306,6 @@ describe("ArtifactListTabComponent (inline template)", () => {
|
||||
CopyInputComponent
|
||||
],
|
||||
providers: [
|
||||
ErrorHandler,
|
||||
ChannelService,
|
||||
ArtifactDefaultService,
|
||||
{ provide: Router, useValue: mockRouter },
|
||||
{ provide: ArtifactService, useValue: mockNewArtifactService },
|
||||
|
@ -36,7 +36,6 @@ import { CopyInputComponent } from "../../../../../../../shared/components/push-
|
||||
import { ErrorHandler } from "../../../../../../../shared/units/error-handler";
|
||||
import { ArtifactService } from "../../../artifact.service";
|
||||
import { OperationService } from "../../../../../../../shared/components/operation/operation.service";
|
||||
import { ChannelService } from "../../../../../../../shared/services/channel.service";
|
||||
import { ConfirmationButtons, ConfirmationState, ConfirmationTargets } from "../../../../../../../shared/entities/shared.const";
|
||||
import { operateChanges, OperateInfo, OperationState } from "../../../../../../../shared/components/operation/operate";
|
||||
import { artifactDefault, ArtifactFront as Artifact, ArtifactFront, artifactPullCommands, mutipleFilter } from '../../../artifact';
|
||||
@ -52,6 +51,7 @@ import { ConfirmationAcknowledgement } from "../../../../../../global-confirmati
|
||||
import { UN_LOGGED_PARAM } from "../../../../../../../account/sign-in/sign-in.service";
|
||||
import { Label } from "../../../../../../../../../ng-swagger-gen/models/label";
|
||||
import { LabelService } from "../../../../../../../../../ng-swagger-gen/services/label.service";
|
||||
import { EventService, HarborEvent } from "../../../../../../../services/event-service/event.service";
|
||||
|
||||
export interface LabelState {
|
||||
iconsShow: boolean;
|
||||
@ -142,6 +142,9 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
||||
hasEnabledScanner: boolean;
|
||||
scanBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||
onSendingScanCommand: boolean;
|
||||
onSendingStopScanCommand: boolean = false;
|
||||
onStopScanArtifactsLength: number = 0;
|
||||
scanStoppedArtifactLength: number = 0;
|
||||
|
||||
artifactDigest: string;
|
||||
depth: string;
|
||||
@ -157,6 +160,8 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
||||
|
||||
scanFinishedArtifactLength: number = 0;
|
||||
onScanArtifactsLength: number = 0;
|
||||
stopBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||
updateArtifactSub: Subscription;
|
||||
constructor(
|
||||
private errorHandlerService: ErrorHandler,
|
||||
private userPermissionService: UserPermissionService,
|
||||
@ -165,7 +170,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
||||
private newArtifactService: NewArtifactService,
|
||||
private translateService: TranslateService,
|
||||
private operationService: OperationService,
|
||||
private channel: ChannelService,
|
||||
private eventService: EventService,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private scanningService: ScanningResultService,
|
||||
private router: Router,
|
||||
@ -186,6 +191,17 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
this.init();
|
||||
});
|
||||
if (!this.updateArtifactSub) {
|
||||
this.updateArtifactSub = this.eventService.subscribe(HarborEvent.UPDATE_VULNERABILITY_INFO, (artifact: Artifact) => {
|
||||
if (this.artifactList && this.artifactList.length) {
|
||||
this.artifactList.forEach(item => {
|
||||
if (item.digest === artifact.digest) {
|
||||
item.scan_overview = artifact.scan_overview;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
ngOnDestroy() {
|
||||
if (this.triggerSub) {
|
||||
@ -200,6 +216,10 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
||||
this.stickLabelNameFilterSub.unsubscribe();
|
||||
this.stickLabelNameFilterSub = null;
|
||||
}
|
||||
if (this.updateArtifactSub) {
|
||||
this.updateArtifactSub.unsubscribe();
|
||||
this.updateArtifactSub = null;
|
||||
}
|
||||
}
|
||||
init() {
|
||||
this.hasInit = true;
|
||||
@ -921,12 +941,21 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
return VULNERABILITY_SCAN_STATUS.NOT_SCANNED;
|
||||
}
|
||||
// Whether show the 'scan now' menu
|
||||
// if has running job, return false
|
||||
canScanNow(): boolean {
|
||||
if (!this.hasScanImagePermission) { return false; }
|
||||
if (this.onSendingScanCommand) { return false; }
|
||||
let st: string = this.scanStatus(this.selectedRow[0]);
|
||||
return st !== VULNERABILITY_SCAN_STATUS.RUNNING;
|
||||
if (this.selectedRow && this.selectedRow.length) {
|
||||
let flag: boolean = true;
|
||||
this.selectedRow.forEach(item => {
|
||||
const st: string = this.scanStatus(item);
|
||||
if (this.isRunningState(st)) {
|
||||
flag = false;
|
||||
}
|
||||
});
|
||||
return flag;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
getImagePermissionRule(projectId: number): void {
|
||||
const permissions = [
|
||||
@ -958,7 +987,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
||||
this.onSendingScanCommand = true;
|
||||
this.selectedRow.forEach((data: any) => {
|
||||
let digest = data.digest;
|
||||
this.channel.publishScanEvent(this.repoName + "/" + digest);
|
||||
this.eventService.publish(HarborEvent.START_SCAN_ARTIFACT, this.repoName + "/" + digest);
|
||||
});
|
||||
}
|
||||
selectedRowHasVul(): boolean {
|
||||
@ -972,11 +1001,18 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
submitFinish(e: boolean) {
|
||||
this.scanFinishedArtifactLength += 1;
|
||||
// all selected scan action has start
|
||||
// all selected scan action has started
|
||||
if (this.scanFinishedArtifactLength === this.onScanArtifactsLength) {
|
||||
this.onSendingScanCommand = e;
|
||||
}
|
||||
}
|
||||
submitStopFinish(e: boolean) {
|
||||
this.scanStoppedArtifactLength += 1;
|
||||
// all selected scan action has stopped
|
||||
if (this.scanStoppedArtifactLength === this.onStopScanArtifactsLength) {
|
||||
this.onSendingScanCommand = e;
|
||||
}
|
||||
}
|
||||
// pull command
|
||||
onCpError($event: any): void {
|
||||
this.copyInput.setPullCommendShow();
|
||||
@ -1108,7 +1144,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
||||
if (res.headers) {
|
||||
let xHeader: string = res.headers.get("x-total-count");
|
||||
if (xHeader) {
|
||||
item.tagNumber = Number.parseInt(xHeader);
|
||||
item.tagNumber = Number.parseInt(xHeader, 10);
|
||||
}
|
||||
}
|
||||
item.tags = res.body;
|
||||
@ -1117,4 +1153,35 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
}
|
||||
// return true if all selected rows are in "running" state
|
||||
canStopScan(): boolean {
|
||||
if (this.onSendingStopScanCommand) { return false; }
|
||||
if (this.selectedRow && this.selectedRow.length) {
|
||||
let flag: boolean = true;
|
||||
this.selectedRow.forEach(item => {
|
||||
const st: string = this.scanStatus(item);
|
||||
if (!this.isRunningState(st)) {
|
||||
flag = false;
|
||||
}
|
||||
});
|
||||
return flag;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
isRunningState(state: string): boolean {
|
||||
return state === VULNERABILITY_SCAN_STATUS.RUNNING ||
|
||||
state === VULNERABILITY_SCAN_STATUS.PENDING ||
|
||||
state === VULNERABILITY_SCAN_STATUS.SCHEDULED;
|
||||
}
|
||||
stopNow() {
|
||||
if (this.selectedRow && this.selectedRow.length) {
|
||||
this.scanStoppedArtifactLength = 0;
|
||||
this.onStopScanArtifactsLength = this.selectedRow.length;
|
||||
this.onSendingStopScanCommand = true;
|
||||
this.selectedRow.forEach((data: any) => {
|
||||
let digest = data.digest;
|
||||
this.eventService.publish(HarborEvent.STOP_SCAN_ARTIFACT, this.repoName + "/" + digest);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import { delay } from 'rxjs/operators';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { SystemInfo, SystemInfoDefaultService, SystemInfoService, } from "../../../../../../shared/services";
|
||||
import { ArtifactDefaultService, ArtifactService } from "../../artifact.service";
|
||||
import { ChannelService } from "../../../../../../shared/services/channel.service";
|
||||
import { ErrorHandler } from "../../../../../../shared/units/error-handler";
|
||||
import { RepositoryService as NewRepositoryService } from "../../../../../../../../ng-swagger-gen/services/repository.service";
|
||||
import { SharedTestingModule } from "../../../../../../shared/shared.module";
|
||||
@ -35,9 +34,6 @@ describe('ArtifactListComponent (inline template)', () => {
|
||||
},
|
||||
snapshot: { data: null }
|
||||
};
|
||||
let mockChannelService = {
|
||||
scanCommand$: of(1)
|
||||
};
|
||||
let mockSystemInfo: SystemInfo = {
|
||||
'with_notary': true,
|
||||
'with_admiral': false,
|
||||
@ -72,7 +68,6 @@ describe('ArtifactListComponent (inline template)', () => {
|
||||
],
|
||||
providers: [
|
||||
{ provide: ErrorHandler, useValue: fakedErrorHandler },
|
||||
{ provide: ChannelService, useValue: mockChannelService },
|
||||
{ provide: SystemInfoService, useClass: SystemInfoDefaultService },
|
||||
{ provide: ArtifactService, useClass: ArtifactDefaultService },
|
||||
{ provide: ActivatedRoute, useValue: mockActivatedRoute },
|
||||
|
@ -15,6 +15,9 @@
|
||||
<div *ngIf="completed" class="bar-state bar-state-chart">
|
||||
<hbr-result-tip-histogram [scanner]="getScanner()" [vulnerabilitySummary]="summary"></hbr-result-tip-histogram>
|
||||
</div>
|
||||
<div *ngIf="stopped" class="bar-state">
|
||||
<span class="label stopped">{{'VULNERABILITY.STATE.STOPPED' | translate}}</span>
|
||||
</div>
|
||||
<div *ngIf="otherStatus" class="bar-state">
|
||||
<span class="label not-scan">{{'VULNERABILITY.STATE.OTHER_STATUS' | translate}}</span>
|
||||
</div>
|
||||
|
@ -6,7 +6,6 @@ import { HistogramChartComponent } from "./histogram-chart/histogram-chart.compo
|
||||
import { JobLogDefaultService, JobLogService, ScanningResultDefaultService, ScanningResultService, } from "../../../../../shared/services";
|
||||
import { VULNERABILITY_SCAN_STATUS } from "../../../../../shared/units/utils";
|
||||
import { ErrorHandler } from "../../../../../shared/units/error-handler";
|
||||
import { ChannelService } from "../../../../../shared/services/channel.service";
|
||||
import { ArtifactDefaultService, ArtifactService } from "../artifact.service";
|
||||
import { NativeReportSummary } from "../../../../../../../ng-swagger-gen/models/native-report-summary";
|
||||
import { SharedTestingModule } from "../../../../../shared/shared.module";
|
||||
@ -40,7 +39,6 @@ describe('ResultBarChartComponent (inline template)', () => {
|
||||
HistogramChartComponent],
|
||||
providers: [
|
||||
ErrorHandler,
|
||||
ChannelService,
|
||||
ArtifactDefaultService,
|
||||
{ provide: ArtifactService, useValue: ArtifactDefaultService },
|
||||
{ provide: ScanningResultService, useValue: ScanningResultDefaultService },
|
||||
@ -61,15 +59,14 @@ describe('ResultBarChartComponent (inline template)', () => {
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should show "not scanned" if status is STOPPED', () => {
|
||||
it('should show "scan stopped" if status is STOPPED', () => {
|
||||
component.summary.scan_status = VULNERABILITY_SCAN_STATUS.STOPPED;
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
let el: HTMLElement = fixture.nativeElement.querySelector('span');
|
||||
expect(el).toBeTruthy();
|
||||
expect(el.textContent).toEqual('VULNERABILITY.STATE.OTHER_STATUS');
|
||||
expect(el.textContent).toEqual('VULNERABILITY.STATE.STOPPED');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output, } from '@angular/core';
|
||||
import { Subscription, timer } from "rxjs";
|
||||
import { finalize } from "rxjs/operators";
|
||||
import { ScannerVo, ScanningResultService } from "../../../../../shared/services";
|
||||
import { ScannerVo } from "../../../../../shared/services";
|
||||
import { ErrorHandler } from "../../../../../shared/units/error-handler";
|
||||
import { ChannelService } from "../../../../../shared/services/channel.service";
|
||||
import { clone, CURRENT_BASE_HREF, dbEncodeURIComponent, VULNERABILITY_SCAN_STATUS } from "../../../../../shared/units/utils";
|
||||
import { ArtifactService } from "../../../../../../../ng-swagger-gen/services/artifact.service";
|
||||
import { Artifact } from "../../../../../../../ng-swagger-gen/models/artifact";
|
||||
import { NativeReportSummary } from "../../../../../../../ng-swagger-gen/models/native-report-summary";
|
||||
import { EventService, HarborEvent } from "../../../../../services/event-service/event.service";
|
||||
import { ScanService } from "../../../../../../../ng-swagger-gen/services/scan.service";
|
||||
|
||||
|
||||
const STATE_CHECK_INTERVAL: number = 3000; // 3s
|
||||
@ -25,20 +26,25 @@ export class ResultBarChartComponent implements OnInit, OnDestroy {
|
||||
@Input() artifactDigest: string = "";
|
||||
@Input() summary: NativeReportSummary;
|
||||
onSubmitting: boolean = false;
|
||||
onStopping: boolean = false;
|
||||
retryCounter: number = 0;
|
||||
stateCheckTimer: Subscription;
|
||||
scanSubscription: Subscription;
|
||||
stopSubscription: Subscription;
|
||||
timerHandler: any;
|
||||
@Output()
|
||||
submitFinish: EventEmitter<boolean> = new EventEmitter<boolean>();
|
||||
// if sending stop scan request is finished, emit to farther component
|
||||
@Output()
|
||||
submitStopFinish: EventEmitter<boolean> = new EventEmitter<boolean>();
|
||||
@Output()
|
||||
scanFinished: EventEmitter<Artifact> = new EventEmitter<Artifact>();
|
||||
|
||||
constructor(
|
||||
private artifactService: ArtifactService,
|
||||
private scanningService: ScanningResultService,
|
||||
private scanService: ScanService,
|
||||
private errorHandler: ErrorHandler,
|
||||
private channel: ChannelService,
|
||||
private eventService: EventService,
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
@ -50,13 +56,23 @@ export class ResultBarChartComponent implements OnInit, OnDestroy {
|
||||
this.getSummary();
|
||||
});
|
||||
}
|
||||
this.scanSubscription = this.channel.scanCommand$.subscribe((artifactDigest: string) => {
|
||||
if (!this.scanSubscription) {
|
||||
this.scanSubscription = this.eventService.subscribe( HarborEvent.START_SCAN_ARTIFACT, (artifactDigest: string) => {
|
||||
let myFullTag: string = this.repoName + "/" + this.artifactDigest;
|
||||
if (myFullTag === artifactDigest) {
|
||||
this.scanNow();
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!this.stopSubscription) {
|
||||
this.stopSubscription = this.eventService.subscribe( HarborEvent.STOP_SCAN_ARTIFACT, (artifactDigest: string) => {
|
||||
let myFullTag: string = this.repoName + "/" + this.artifactDigest;
|
||||
if (myFullTag === artifactDigest) {
|
||||
this.stopScan();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.stateCheckTimer) {
|
||||
@ -65,6 +81,11 @@ export class ResultBarChartComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
if (this.scanSubscription) {
|
||||
this.scanSubscription.unsubscribe();
|
||||
this.scanSubscription = null;
|
||||
}
|
||||
if (!this.stopSubscription) {
|
||||
this.stopSubscription.unsubscribe();
|
||||
this.stopSubscription = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,8 +112,11 @@ export class ResultBarChartComponent implements OnInit, OnDestroy {
|
||||
public get scanning(): boolean {
|
||||
return this.status === VULNERABILITY_SCAN_STATUS.RUNNING;
|
||||
}
|
||||
public get stopped(): boolean {
|
||||
return this.status === VULNERABILITY_SCAN_STATUS.STOPPED;
|
||||
}
|
||||
public get otherStatus(): boolean {
|
||||
return !(this.completed || this.error || this.queued || this.scanning);
|
||||
return !(this.completed || this.error || this.queued || this.scanning || this.stopped);
|
||||
}
|
||||
|
||||
scanNow(): void {
|
||||
@ -109,8 +133,11 @@ export class ResultBarChartComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.onSubmitting = true;
|
||||
|
||||
this.scanningService.startVulnerabilityScanning(this.projectName, dbEncodeURIComponent(this.repoName), this.artifactDigest)
|
||||
.pipe(finalize(() => this.submitFinish.emit(false)))
|
||||
this.scanService.scanArtifact({
|
||||
projectName: this.projectName,
|
||||
reference: this.artifactDigest,
|
||||
repositoryName: dbEncodeURIComponent(this.repoName)
|
||||
}).pipe(finalize(() => this.submitFinish.emit(false)))
|
||||
.subscribe(() => {
|
||||
this.onSubmitting = false;
|
||||
// Forcely change status to queued after successful submitting
|
||||
@ -157,7 +184,7 @@ export class ResultBarChartComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
this.scanFinished.emit(artifact);
|
||||
}
|
||||
this.channel.ArtifactDetail$.next(artifact);
|
||||
this.eventService.publish(HarborEvent.UPDATE_VULNERABILITY_INFO, artifact);
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
this.retryCounter++;
|
||||
@ -186,4 +213,38 @@ export class ResultBarChartComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
return this.inputScanner;
|
||||
}
|
||||
stopScan() {
|
||||
if (this.onStopping) {
|
||||
// Avoid duplicated stopping command
|
||||
console.log("duplicated stopping command");
|
||||
return;
|
||||
}
|
||||
if (!this.repoName || !this.artifactDigest) {
|
||||
console.log("bad repository or artifact");
|
||||
return;
|
||||
}
|
||||
this.onStopping = true;
|
||||
|
||||
this.scanService.stopScanArtifact({
|
||||
projectName: this.projectName,
|
||||
reference: this.artifactDigest,
|
||||
repositoryName: dbEncodeURIComponent(this.repoName)
|
||||
}).pipe(
|
||||
finalize(() => {
|
||||
this.submitStopFinish.emit(false);
|
||||
this.onStopping = false;
|
||||
}))
|
||||
.subscribe(() => {
|
||||
// Start check status util the job is done
|
||||
if (!this.stateCheckTimer) {
|
||||
// Avoid duplicated subscribing
|
||||
this.stateCheckTimer = timer(STATE_CHECK_INTERVAL, STATE_CHECK_INTERVAL).subscribe(() => {
|
||||
this.getSummary();
|
||||
});
|
||||
}
|
||||
this.errorHandler.info('VULNERABILITY.TRIGGER_STOP_SUCCESS');
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -169,3 +169,6 @@ hbr-vulnerability-bar {
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
.stopped {
|
||||
border-color: #cccc15;
|
||||
}
|
||||
|
@ -76,5 +76,8 @@ export class EventService {
|
||||
export enum HarborEvent {
|
||||
SCROLL = 'scroll',
|
||||
SCROLL_TO_POSITION = 'scrollToPosition',
|
||||
REFRESH_PROJECT_INFO = 'refreshProjectInfo'
|
||||
REFRESH_PROJECT_INFO = 'refreshProjectInfo',
|
||||
START_SCAN_ARTIFACT = 'startScanArtifact',
|
||||
STOP_SCAN_ARTIFACT = 'stopScanArtifact',
|
||||
UPDATE_VULNERABILITY_INFO = 'UpdateVulnerabilityInfo'
|
||||
}
|
||||
|
@ -1,12 +1,9 @@
|
||||
import { waitForAsync, ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { ImageNameInputComponent } from "./image-name-input.component";
|
||||
import { ErrorHandler } from "../../units/error-handler/error-handler";
|
||||
import { ProjectDefaultService, ProjectService } from "../../services";
|
||||
import { Project } from "../../../base/project/project-config/project-policy-config/project";
|
||||
import { of } from "rxjs";
|
||||
import { HttpResponse } from "@angular/common/http";
|
||||
import { ChannelService } from "../../services/channel.service";
|
||||
import { CURRENT_BASE_HREF } from "../../units/utils";
|
||||
import { SharedTestingModule } from "../../shared.module";
|
||||
|
||||
describe("ImageNameInputComponent (inline template)", () => {
|
||||
@ -35,8 +32,6 @@ describe("ImageNameInputComponent (inline template)", () => {
|
||||
ImageNameInputComponent
|
||||
],
|
||||
providers: [
|
||||
ErrorHandler,
|
||||
ChannelService,
|
||||
{ provide: ProjectService, useClass: ProjectDefaultService }
|
||||
]
|
||||
});
|
||||
|
@ -1,31 +0,0 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Subject } from "rxjs";
|
||||
import { ArtifactFront as Artifact } from "../../base/project/repository/artifact/artifact";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ChannelService {
|
||||
|
||||
// Declare for publishing scan event
|
||||
scanCommandSource = new Subject<string>();
|
||||
scanCommand$ = this.scanCommandSource.asObservable();
|
||||
|
||||
publishScanEvent(tagId: string): void {
|
||||
this.scanCommandSource.next(tagId);
|
||||
}
|
||||
ArtifactDetail$ = new Subject<Artifact>();
|
||||
}
|
@ -965,6 +965,7 @@
|
||||
"OIDC_ADMIN_GROUP_INFO": "Spezifiziere den Namen einer OIDC Administratorengruppe. Alle Mitglieder dieser Gruppe haben in Harbor administrative Berechtigungen. Falls dies nicht gewünscht ist, kann das Feld leer gelassen werden."
|
||||
},
|
||||
"SCANNING": {
|
||||
"STOP_SCAN_ALL_SUCCESS": "Trigger stopping scan all successfully!",
|
||||
"TRIGGER_SCAN_ALL_SUCCESS": "Scan erfolgreich gestartet!",
|
||||
"TRIGGER_SCAN_ALL_FAIL": "Fehler beim Start des Scans: {{error}}",
|
||||
"TITLE": "Schwachstellen-Scanning",
|
||||
@ -1035,7 +1036,8 @@
|
||||
"OTHER_STATUS": "Nicht gescannt",
|
||||
"QUEUED": "In Warteschlange",
|
||||
"ERROR": "Log anzeigen",
|
||||
"SCANNING": "Scanning"
|
||||
"SCANNING": "Scanning",
|
||||
"STOPPED": "Scan stopped"
|
||||
},
|
||||
"GRID": {
|
||||
"PLACEHOLDER": "Es wurden keine Scan-Ergebnisse gefunden!",
|
||||
@ -1078,7 +1080,9 @@
|
||||
"SCAN_NOW": "Scan",
|
||||
"SCAN_BY": "SCAN BY {{scanner}}",
|
||||
"REPORTED_BY": "Reported by {{scanner}}",
|
||||
"NO_SCANNER": "NO SCANNER"
|
||||
"NO_SCANNER": "NO SCANNER",
|
||||
"TRIGGER_STOP_SUCCESS": "Trigger stopping scan successfully",
|
||||
"STOP_NOW": "Stop Scan"
|
||||
},
|
||||
"PUSH_IMAGE": {
|
||||
"TITLE": "Push Befehl",
|
||||
@ -1697,6 +1701,7 @@
|
||||
"FINAL_PROJECT_NAME_TIP": "Der zusammengesetzte Robot-Account-Name besteht aus dem Prefix, dem Projektnamen, einem plus und dem Inhalt des Eingabefeldes.",
|
||||
"FINAL_SYSTEM_NAME_TIP": "Der zusammengesetzte systemweite Robot-Account-Name besteht aus dem Prefix und dem Inhalt des Eingabefeldes.",
|
||||
"PUSH_AND_PULL": "Push",
|
||||
"PUSH_PERMISSION_TOOLTIP": "Push permission can not work alone, it must work with pull permission"
|
||||
"PUSH_PERMISSION_TOOLTIP": "Push permission can not work alone, it must work with pull permission",
|
||||
"STOP": "Stop"
|
||||
}
|
||||
}
|
||||
|
@ -965,6 +965,7 @@
|
||||
"OIDC_ADMIN_GROUP_INFO": "Specify an OIDC admin group name. All OIDC users in this group will have harbor admin privilege. Keep it blank if you do not want to."
|
||||
},
|
||||
"SCANNING": {
|
||||
"STOP_SCAN_ALL_SUCCESS": "Trigger stopping scan all successfully!",
|
||||
"TRIGGER_SCAN_ALL_SUCCESS": "Trigger scan all successfully!",
|
||||
"TRIGGER_SCAN_ALL_FAIL": "Failed to trigger scan all with error: {{error}}",
|
||||
"TITLE": "Vulnerability Scanning",
|
||||
@ -1035,7 +1036,8 @@
|
||||
"OTHER_STATUS": "Not Scanned",
|
||||
"QUEUED": "Queued",
|
||||
"ERROR": "View Log",
|
||||
"SCANNING": "Scanning"
|
||||
"SCANNING": "Scanning",
|
||||
"STOPPED": "Scan stopped"
|
||||
},
|
||||
"GRID": {
|
||||
"PLACEHOLDER": "We couldn't find any scanning results!",
|
||||
@ -1078,7 +1080,9 @@
|
||||
"SCAN_NOW": "Scan",
|
||||
"SCAN_BY": "SCAN BY {{scanner}}",
|
||||
"REPORTED_BY": "Reported by {{scanner}}",
|
||||
"NO_SCANNER": "NO SCANNER"
|
||||
"NO_SCANNER": "NO SCANNER",
|
||||
"TRIGGER_STOP_SUCCESS": "Trigger stopping scan successfully",
|
||||
"STOP_NOW": "Stop Scan"
|
||||
},
|
||||
"PUSH_IMAGE": {
|
||||
"TITLE": "Push Command",
|
||||
@ -1697,6 +1701,7 @@
|
||||
"FINAL_PROJECT_NAME_TIP": "The final project robot name consists of the prefix,the project name,a plus mark and the current input value",
|
||||
"FINAL_SYSTEM_NAME_TIP": "The final system robot name consists of the prefix and the current input value",
|
||||
"PUSH_AND_PULL": "Push",
|
||||
"PUSH_PERMISSION_TOOLTIP": "Push permission can not work alone, it must work with pull permission"
|
||||
"PUSH_PERMISSION_TOOLTIP": "Push permission can not work alone, it must work with pull permission",
|
||||
"STOP": "Stop"
|
||||
}
|
||||
}
|
||||
|
@ -966,6 +966,7 @@
|
||||
"OIDC_ADMIN_GROUP_INFO": "Specify an OIDC admin group name. All OIDC users in this group will have harbor admin privilege. Keep it blank if you do not want to."
|
||||
},
|
||||
"SCANNING": {
|
||||
"STOP_SCAN_ALL_SUCCESS": "Trigger stopping scan all successfully!",
|
||||
"TRIGGER_SCAN_ALL_SUCCESS": "Trigger scan all successfully!",
|
||||
"TRIGGER_SCAN_ALL_FAIL": "Failed to trigger scan all with error: {{error}}",
|
||||
"TITLE": "Vulnerability Scanning",
|
||||
@ -1036,7 +1037,8 @@
|
||||
"OTHER_STATUS": "Not Scanned",
|
||||
"QUEUED": "Queued",
|
||||
"ERROR": "View Log",
|
||||
"SCANNING": "Scanning"
|
||||
"SCANNING": "Scanning",
|
||||
"STOPPED": "Scan stopped"
|
||||
},
|
||||
"GRID": {
|
||||
"PLACEHOLDER": "We couldn't find any scanning results!",
|
||||
@ -1079,7 +1081,9 @@
|
||||
"SCAN_NOW": "Scan",
|
||||
"SCAN_BY": "SCAN BY {{scanner}}",
|
||||
"REPORTED_BY": "Reported by {{scanner}}",
|
||||
"NO_SCANNER": "NO SCANNER"
|
||||
"NO_SCANNER": "NO SCANNER",
|
||||
"TRIGGER_STOP_SUCCESS": "Trigger stopping scan successfully",
|
||||
"STOP_NOW": "Stop Scan"
|
||||
},
|
||||
"PUSH_IMAGE": {
|
||||
"TITLE": "Push Command",
|
||||
@ -1696,6 +1700,7 @@
|
||||
"FINAL_PROJECT_NAME_TIP": "The final project robot name consists of the prefix,the project name,a plus mark and the current input value",
|
||||
"FINAL_SYSTEM_NAME_TIP": "The final system robot name consists of the prefix and the current input value",
|
||||
"PUSH_AND_PULL": "Push",
|
||||
"PUSH_PERMISSION_TOOLTIP": "Push permission can not work alone, it must work with pull permission"
|
||||
"PUSH_PERMISSION_TOOLTIP": "Push permission can not work alone, it must work with pull permission",
|
||||
"STOP": "Stop"
|
||||
}
|
||||
}
|
||||
|
@ -938,6 +938,7 @@
|
||||
"OIDC_ADMIN_GROUP_INFO": "Specify an OIDC admin group name. All OIDC users in this group will have harbor admin privilege. Keep it blank if you do not want to."
|
||||
},
|
||||
"SCANNING": {
|
||||
"STOP_SCAN_ALL_SUCCESS": "Trigger stopping scan all successfully!",
|
||||
"TRIGGER_SCAN_ALL_SUCCESS": "Déclenchement d'analyse globale avec succès !",
|
||||
"TRIGGER_SCAN_ALL_FAIL": "Echec du déclenchement d'analyse globale avec des erreurs : {{error}}",
|
||||
"TITLE": "Analyse de vulnérabilité",
|
||||
@ -1008,7 +1009,8 @@
|
||||
"OTHER_STATUS": "Non Analysé",
|
||||
"QUEUED": "En fil d'attente",
|
||||
"ERROR": "Voir le Log",
|
||||
"SCANNING": "En cours d'analyse"
|
||||
"SCANNING": "En cours d'analyse",
|
||||
"STOPPED": "Scan stopped"
|
||||
},
|
||||
"GRID": {
|
||||
"PLACEHOLDER": "Nous n'avons pas trouvé de résultats d'analyse !",
|
||||
@ -1051,7 +1053,9 @@
|
||||
"SCAN_NOW": "Analyser",
|
||||
"SCAN_BY": "SCAN BY {{scanner}}",
|
||||
"REPORTED_BY": "Reported by {{scanner}}",
|
||||
"NO_SCANNER": "NO SCANNER"
|
||||
"NO_SCANNER": "NO SCANNER",
|
||||
"TRIGGER_STOP_SUCCESS": "Trigger stopping scan successfully",
|
||||
"STOP_NOW": "Stop Scan"
|
||||
},
|
||||
"PUSH_IMAGE": {
|
||||
"TITLE": "Push Command",
|
||||
@ -1665,6 +1669,7 @@
|
||||
"FINAL_PROJECT_NAME_TIP": "The final project robot name consists of the prefix,the project name,a plus mark and the current input value",
|
||||
"FINAL_SYSTEM_NAME_TIP": "The final system robot name consists of the prefix and the current input value",
|
||||
"PUSH_AND_PULL": "Push",
|
||||
"PUSH_PERMISSION_TOOLTIP": "Push permission can not work alone, it must work with pull permission"
|
||||
"PUSH_PERMISSION_TOOLTIP": "Push permission can not work alone, it must work with pull permission",
|
||||
"STOP": "Stop"
|
||||
}
|
||||
}
|
||||
|
@ -961,6 +961,7 @@
|
||||
"OIDC_ADMIN_GROUP_INFO": "Informe o nome do grupo OIDC que será considerado administrativo. Todos os usuários deste grupo obterão privilégios de adiministração no Harbor. Deixe vazio para ser ignorado."
|
||||
},
|
||||
"SCANNING": {
|
||||
"STOP_SCAN_ALL_SUCCESS": "Trigger stopping scan all successfully!",
|
||||
"TRIGGER_SCAN_ALL_SUCCESS": "Disparo de análise geral efetuado com sucesso!",
|
||||
"TRIGGER_SCAN_ALL_FAIL": "Falha ao disparar análise geral com erro: {{error}}",
|
||||
"TITLE": "Análise de vulnerabilidades",
|
||||
@ -1031,7 +1032,8 @@
|
||||
"OTHER_STATUS": "Não analisado",
|
||||
"QUEUED": "Solicitado",
|
||||
"ERROR": "Ver erros...",
|
||||
"SCANNING": "Analisando"
|
||||
"SCANNING": "Analisando",
|
||||
"STOPPED": "Scan stopped"
|
||||
},
|
||||
"GRID": {
|
||||
"PLACEHOLDER": "Não foi possível encontrar nenhum resultado de análise!",
|
||||
@ -1074,7 +1076,9 @@
|
||||
"SCAN_NOW": "Analisar",
|
||||
"SCAN_BY": "SCAN BY {{scanner}}",
|
||||
"REPORTED_BY": "Relatado por {{scanner}}",
|
||||
"NO_SCANNER": "NO SCANNER"
|
||||
"NO_SCANNER": "NO SCANNER",
|
||||
"TRIGGER_STOP_SUCCESS": "Trigger stopping scan successfully",
|
||||
"STOP_NOW": "Stop Scan"
|
||||
},
|
||||
"PUSH_IMAGE": {
|
||||
"TITLE": "Comando Push",
|
||||
@ -1693,7 +1697,8 @@
|
||||
"FINAL_PROJECT_NAME_TIP": "Para evitar conflitos entre projetos, o nome completo será composto pelo nome do projeto concatenado a um marcador e o valor aqui informado.",
|
||||
"FINAL_SYSTEM_NAME_TIP": "Este valor será concatenado ao prefixo do projeto.",
|
||||
"PUSH_AND_PULL": "Push",
|
||||
"PUSH_PERMISSION_TOOLTIP": "Permissões de envio (push) presume também a permissão e recebimento (pull)."
|
||||
"PUSH_PERMISSION_TOOLTIP": "Permissões de envio (push) presume também a permissão e recebimento (pull).",
|
||||
"STOP": "Stop"
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -965,6 +965,7 @@
|
||||
"OIDC_ADMIN_GROUP_INFO": "Specify an OIDC admin group name. All OIDC users in this group will have harbor admin privilege. Keep it blank if you do not want to."
|
||||
},
|
||||
"SCANNING": {
|
||||
"STOP_SCAN_ALL_SUCCESS": "Trigger stopping scan all successfully!",
|
||||
"TRIGGER_SCAN_ALL_SUCCESS": "Tümünü başarılı bir şekilde tara!",
|
||||
"TRIGGER_SCAN_ALL_FAIL": "Tüm taramayı hatayla tetikleyemedi:{{error}}",
|
||||
"TITLE": "Güvenlik açığı taraması",
|
||||
@ -1035,7 +1036,8 @@
|
||||
"OTHER_STATUS": "Taranmadı",
|
||||
"QUEUED": "Sıraya alındı",
|
||||
"ERROR": "Günlüğü Görüntüle",
|
||||
"SCANNING": "Taranıyor"
|
||||
"SCANNING": "Taranıyor",
|
||||
"STOPPED": "Scan stopped"
|
||||
},
|
||||
"GRID": {
|
||||
"PLACEHOLDER": "Herhangi bir tarama sonucu bulamadık!",
|
||||
@ -1078,7 +1080,9 @@
|
||||
"SCAN_NOW": "Tara",
|
||||
"SCAN_BY": "SCAN BY {{scanner}}",
|
||||
"REPORTED_BY": "Reported by {{scanner}}",
|
||||
"NO_SCANNER": "NO SCANNER"
|
||||
"NO_SCANNER": "NO SCANNER",
|
||||
"TRIGGER_STOP_SUCCESS": "Trigger stopping scan successfully",
|
||||
"STOP_NOW": "Stop Scan"
|
||||
},
|
||||
"PUSH_IMAGE": {
|
||||
"TITLE": "Push Command",
|
||||
@ -1697,6 +1701,7 @@
|
||||
"FINAL_PROJECT_NAME_TIP": "The final project robot name consists of the prefix,the project name,a plus mark and the current input value",
|
||||
"FINAL_SYSTEM_NAME_TIP": "The final system robot name consists of the prefix and the current input value",
|
||||
"PUSH_AND_PULL": "Push",
|
||||
"PUSH_PERMISSION_TOOLTIP": "Push permission can not work alone, it must work with pull permission"
|
||||
"PUSH_PERMISSION_TOOLTIP": "Push permission can not work alone, it must work with pull permission",
|
||||
"STOP": "Stop"
|
||||
}
|
||||
}
|
||||
|
@ -966,6 +966,7 @@
|
||||
"OIDC_ADMIN_GROUP_INFO": "OIDC管理员组名称。所有该组内用户都会有管理员权限,此属性可以为空。"
|
||||
},
|
||||
"SCANNING": {
|
||||
"STOP_SCAN_ALL_SUCCESS": "停止扫描所有镜像任务成功!",
|
||||
"TRIGGER_SCAN_ALL_SUCCESS": "启动扫描所有镜像任务成功!",
|
||||
"TRIGGER_SCAN_ALL_FAIL": "启动扫描所有镜像任务失败:{{error}}",
|
||||
"TITLE": "缺陷扫描",
|
||||
@ -1036,7 +1037,8 @@
|
||||
"OTHER_STATUS": "未扫描",
|
||||
"QUEUED": "已入队列",
|
||||
"ERROR": "查看日志",
|
||||
"SCANNING": "扫描中"
|
||||
"SCANNING": "扫描中",
|
||||
"STOPPED": "扫描中止"
|
||||
},
|
||||
"GRID": {
|
||||
"PLACEHOLDER": "没有扫描结果!",
|
||||
@ -1079,7 +1081,9 @@
|
||||
"SCAN_NOW": "扫描",
|
||||
"SCAN_BY": "使用 {{scanner}} 进行扫描",
|
||||
"REPORTED_BY": "结果由 {{scanner}} 提供",
|
||||
"NO_SCANNER": "无扫描器"
|
||||
"NO_SCANNER": "无扫描器",
|
||||
"TRIGGER_STOP_SUCCESS": "停止扫描成功",
|
||||
"STOP_NOW": "停止扫描"
|
||||
},
|
||||
"PUSH_IMAGE": {
|
||||
"TITLE": "推送命令",
|
||||
@ -1695,6 +1699,7 @@
|
||||
"FINAL_PROJECT_NAME_TIP": "项目级机器人的最终名称由前缀,项目名称,一个加号以及当前输入值组成",
|
||||
"FINAL_SYSTEM_NAME_TIP": "系统级机器人的最终名称由前缀和当前输入值组成",
|
||||
"PUSH_AND_PULL": "推送",
|
||||
"PUSH_PERMISSION_TOOLTIP": "推送权限无法单独工作,请在选择推送权限的时,确保已经勾选了拉取权限"
|
||||
"PUSH_PERMISSION_TOOLTIP": "推送权限无法单独工作,请在选择推送权限的时,确保已经勾选了拉取权限",
|
||||
"STOP": "停止"
|
||||
}
|
||||
}
|
||||
|
@ -961,6 +961,7 @@
|
||||
"OIDC_ADMIN_GROUP_INFO": "Specify an OIDC admin group name. All OIDC users in this group will have harbor admin privilege. Keep it blank if you do not want to."
|
||||
},
|
||||
"SCANNING":{
|
||||
"STOP_SCAN_ALL_SUCCESS": "Trigger stopping scan all successfully!",
|
||||
"TRIGGER_SCAN_ALL_SUCCESS": "啟動掃描所有鏡像任務成功!",
|
||||
"TRIGGER_SCAN_ALL_FAIL": "啟動掃描所有鏡像任務失敗:{{error}}",
|
||||
"TITLE": "缺陷掃描",
|
||||
@ -1031,7 +1032,8 @@
|
||||
"OTHER_STATUS": "未掃描",
|
||||
"QUEUED": "已入隊列",
|
||||
"ERROR": "查看日誌",
|
||||
"SCANNING": "掃描中"
|
||||
"SCANNING": "掃描中",
|
||||
"STOPPED": "Scan stopped"
|
||||
},
|
||||
"GRID":{
|
||||
"PLACEHOLDER": "沒有掃描結果!",
|
||||
@ -1074,7 +1076,9 @@
|
||||
"SCAN_NOW":"掃描",
|
||||
"SCAN_BY": "SCAN BY {{scanner}}",
|
||||
"REPORTED_BY": "Reported by {{scanner}}",
|
||||
"NO_SCANNER": "NO SCANNER"
|
||||
"NO_SCANNER": "NO SCANNER",
|
||||
"TRIGGER_STOP_SUCCESS": "Trigger stopping scan successfully",
|
||||
"STOP_NOW": "Stop Scan"
|
||||
},
|
||||
"PUSH_IMAGE":{
|
||||
"TITLE": "推送鏡像的Docker命令",
|
||||
@ -1682,6 +1686,7 @@
|
||||
"FINAL_PROJECT_NAME_TIP": "The final project robot name consists of the prefix,the project name,a plus mark and the current input value",
|
||||
"FINAL_SYSTEM_NAME_TIP": "The final system robot name consists of the prefix and the current input value",
|
||||
"PUSH_AND_PULL": "Push",
|
||||
"PUSH_PERMISSION_TOOLTIP": "Push permission can not work alone, it must work with pull permission"
|
||||
"PUSH_PERMISSION_TOOLTIP": "Push permission can not work alone, it must work with pull permission",
|
||||
"STOP": "Stop"
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user