@@ -124,6 +235,56 @@
{{ 'OPERATION.FAILED' | translate }}
+
+
+
+
+
+
+
+
+
+ {{
+ item.data.name
+ }}{{
+ item.timeDiff | translate
+ }}
+ {{ item.data.errorInf }}
+
+
diff --git a/src/portal/src/app/shared/components/operation/operation.component.ts b/src/portal/src/app/shared/components/operation/operation.component.ts
index d2a94cab3..2b19d6aaf 100644
--- a/src/portal/src/app/shared/components/operation/operation.component.ts
+++ b/src/portal/src/app/shared/components/operation/operation.component.ts
@@ -1,5 +1,10 @@
import { Component, OnInit, OnDestroy, HostListener } from '@angular/core';
-import { OperationService } from './operation.service';
+import {
+ downloadCVEs,
+ EventState,
+ ExportJobStatus,
+ OperationService,
+} from './operation.service';
import { forkJoin, Subscription } from 'rxjs';
import {
OperateInfo,
@@ -9,11 +14,19 @@ import {
import { SlideInOutAnimation } from '../../_animations/slide-in-out.animation';
import { TranslateService } from '@ngx-translate/core';
import { SessionService } from '../../services/session.service';
-
+import { ScanDataExportService } from '../../../../../ng-swagger-gen/services/scan-data-export.service';
+import {
+ EventService,
+ HarborEvent,
+} from '../../../services/event-service/event.service';
+import { MessageHandlerService } from '../../services/message-handler.service';
+import { HarborDatetimePipe } from '../../pipes/harbor-datetime.pipe';
+const STAY_TIME: number = 5000;
const OPERATION_KEY: string = 'operation';
const MAX_NUMBER: number = 500;
const MAX_SAVING_TIME: number = 1000 * 60 * 60 * 24 * 30; // 30 days
-
+const TIMEOUT = 7000;
+const FILE_NAME_PREFIX: string = 'csv_file_';
@Component({
selector: 'hbr-operation-model',
templateUrl: './operation.component.html',
@@ -21,8 +34,10 @@ const MAX_SAVING_TIME: number = 1000 * 60 * 60 * 24 * 30; // 30 days
animations: [SlideInOutAnimation],
})
export class OperationComponent implements OnInit, OnDestroy {
+ fileNamePrefix: string = FILE_NAME_PREFIX;
batchInfoSubscription: Subscription;
resultLists: OperateInfo[] = [];
+ exportJobs: OperateInfo[] = [];
animationState = 'out';
private _newMessageCount: number = 0;
private _timeoutInterval;
@@ -42,12 +57,22 @@ export class OperationComponent implements OnInit, OnDestroy {
);
}
}
-
+ timeout;
constructor(
private session: SessionService,
private operationService: OperationService,
- private translate: TranslateService
+ private translate: TranslateService,
+ private scanDataExportService: ScanDataExportService,
+ private event: EventService,
+ private msgHandler: MessageHandlerService
) {
+ this.event.subscribe(HarborEvent.REFRESH_EXPORT_JOBS, () => {
+ if (this.animationState === 'out') {
+ this._newMessageCount += 1;
+ }
+ this.refreshExportJobs();
+ });
+
this.batchInfoSubscription = operationService.operationInfo$.subscribe(
data => {
if (this.animationState === 'out') {
@@ -91,7 +116,7 @@ export class OperationComponent implements OnInit, OnDestroy {
if (!this._timeoutInterval) {
this._timeoutInterval = setTimeout(() => {
this.animationState = 'out';
- }, 5000);
+ }, STAY_TIME);
}
}
@@ -117,6 +142,7 @@ export class OperationComponent implements OnInit, OnDestroy {
init() {
if (this.session.getCurrentUser()) {
+ this.refreshExportJobs();
const operationInfosString: string = localStorage.getItem(
`${OPERATION_KEY}-${this.session.getCurrentUser().user_id}`
);
@@ -163,6 +189,10 @@ export class OperationComponent implements OnInit, OnDestroy {
clearInterval(this._timeoutInterval);
this._timeoutInterval = null;
}
+ if (this.timeout) {
+ clearTimeout(this.timeout);
+ this.timeout = null;
+ }
}
toggleTitle(errorSpan: any) {
@@ -207,6 +237,16 @@ export class OperationComponent implements OnInit, OnDestroy {
daysAgo
);
});
+ this.exportJobs.forEach(data => {
+ const timeDiff: number = new Date().getTime() - +data.timeStamp;
+ data.timeDiff = this.calculateTime(
+ timeDiff,
+ secondsAgo,
+ minutesAgo,
+ hoursAgo,
+ daysAgo
+ );
+ });
}
calculateTime(
@@ -227,4 +267,92 @@ export class OperationComponent implements OnInit, OnDestroy {
return s;
}
}
+ refreshExportJobs() {
+ if (this.session.getCurrentUser()) {
+ this.scanDataExportService
+ .getScanDataExportExecutionList({
+ userName: this.session?.getCurrentUser()?.username,
+ })
+ .subscribe(res => {
+ if (res?.items) {
+ this.exportJobs = [];
+ let flag: boolean = false;
+ res.items.forEach(item => {
+ const info: OperateInfo = {
+ name: 'CVE_EXPORT.EXPORT_TITLE',
+ state: this.MapStatus(item.status),
+ data: {
+ hasFile: item.file_present,
+ name: `${FILE_NAME_PREFIX}${new HarborDatetimePipe().transform(
+ item.start_time,
+ 'yyyyMMddHHss'
+ )}`,
+ id: item.id,
+ errorInf:
+ item.status === ExportJobStatus.ERROR
+ ? item.status_text
+ : null,
+ },
+ timeStamp: new Date(item.start_time).getTime(),
+ timeDiff: 'OPERATION.SECOND_AGO',
+ };
+ this.exportJobs.push(info);
+ if (this.isRunningState(item.status)) {
+ flag = true;
+ }
+ });
+ if (flag) {
+ this.timeout = setTimeout(() => {
+ this.refreshExportJobs();
+ }, TIMEOUT);
+ }
+ }
+ });
+ }
+ }
+
+ isRunningState(state: string): boolean {
+ if (state) {
+ return (
+ state === ExportJobStatus.RUNNING ||
+ state === ExportJobStatus.PENDING ||
+ state === ExportJobStatus.SCHEDULED
+ );
+ }
+ return false;
+ }
+ MapStatus(originStatus: string): string {
+ if (originStatus) {
+ if (this.isRunningState(originStatus)) {
+ return EventState.PROGRESSING;
+ }
+ if (originStatus === ExportJobStatus.STOPPED) {
+ return EventState.INTERRUPT;
+ }
+ if (originStatus === ExportJobStatus.SUCCESS) {
+ return EventState.SUCCESS;
+ }
+ if (originStatus === ExportJobStatus.ERROR) {
+ return EventState.FAILURE;
+ }
+ }
+ return EventState.FAILURE;
+ }
+ download(info: OperateInfo) {
+ if (info?.data?.id && info?.data?.name) {
+ this.scanDataExportService
+ .downloadScanData({
+ executionId: +info.data.id,
+ })
+ .subscribe(
+ res => {
+ downloadCVEs(res, info.data.name);
+ this.refreshExportJobs();
+ },
+ error => {
+ this.msgHandler.error(error);
+ }
+ );
+ }
+ }
}
diff --git a/src/portal/src/app/shared/components/operation/operation.service.ts b/src/portal/src/app/shared/components/operation/operation.service.ts
index 41b997da3..809214990 100644
--- a/src/portal/src/app/shared/components/operation/operation.service.ts
+++ b/src/portal/src/app/shared/components/operation/operation.service.ts
@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
-import { Observable, Subject } from 'rxjs';
+import { Subject } from 'rxjs';
import { OperateInfo } from './operate';
@Injectable({
@@ -15,3 +15,30 @@ export class OperationService {
this.operationInfoSource.next(data);
}
}
+
+export function downloadCVEs(data, filename) {
+ let url = window.URL.createObjectURL(data);
+ let a = document.createElement('a');
+ document.body.appendChild(a);
+ a.setAttribute('style', 'display: none');
+ a.href = url;
+ a.download = filename;
+ a.click();
+ window.URL.revokeObjectURL(url);
+ a.remove();
+}
+export enum EventState {
+ SUCCESS = 'success',
+ FAILURE = 'failure',
+ INTERRUPT = 'interrupt',
+ PROGRESSING = 'progressing',
+}
+
+export enum ExportJobStatus {
+ PENDING = 'Pending',
+ RUNNING = 'Running',
+ STOPPED = 'Stopped',
+ ERROR = 'Error',
+ SUCCESS = 'Success',
+ SCHEDULED = 'Scheduled',
+}
diff --git a/src/portal/src/i18n/lang/de-de-lang.json b/src/portal/src/i18n/lang/de-de-lang.json
index 993c4a284..0493cf001 100644
--- a/src/portal/src/i18n/lang/de-de-lang.json
+++ b/src/portal/src/i18n/lang/de-de-lang.json
@@ -1753,5 +1753,19 @@
"SKIP_DATABASE_TOOLTIP": "Skip to log audit log in the database, only available when audit log forward endpoint is configured",
"STOP_GC_SUCCESS": "Trigger stopping GC operation successfully",
"STOP_PURGE_SUCCESS": "Trigger stopping purging operation successfully"
+ },
+ "CVE_EXPORT": {
+ "EXPORT_SOME_PROJECTS": "Export CVEs - {{number}} project(s)",
+ "EXPORT_ALL_PROJECTS": "Export CVEs - All projects",
+ "ALL_PROJECTS": "All projects",
+ "EXPORT_TITLE": "Export CVE",
+ "EXPORT_SUBTITLE": "Set exporting conditions",
+ "EXPORT_CVE_FILTER_HELP_TEXT": "Enter multiple comma separated cveIds",
+ "CVE_IDS": "CVE IDs",
+ "EXPORT_BUTTON": "EXPORT",
+ "JOB_NAME": "Job Name",
+ "JOB_NAME_REQUIRED": "Job name is required",
+ "JOB_NAME_EXISTING": "Job name already exists",
+ "TRIGGER_EXPORT_SUCCESS": "Trigger exporting CVEs successfully!"
}
}
diff --git a/src/portal/src/i18n/lang/en-us-lang.json b/src/portal/src/i18n/lang/en-us-lang.json
index 55852c29a..40b97c3bd 100644
--- a/src/portal/src/i18n/lang/en-us-lang.json
+++ b/src/portal/src/i18n/lang/en-us-lang.json
@@ -1753,5 +1753,19 @@
"SKIP_DATABASE_TOOLTIP": "Skip to log audit log in the database, only available when audit log forward endpoint is configured",
"STOP_GC_SUCCESS": "Trigger stopping GC operation successfully",
"STOP_PURGE_SUCCESS": "Trigger stopping purging operation successfully"
+ },
+ "CVE_EXPORT": {
+ "EXPORT_SOME_PROJECTS": "Export CVEs - {{number}} project(s)",
+ "EXPORT_ALL_PROJECTS": "Export CVEs - All projects",
+ "ALL_PROJECTS": "All projects",
+ "EXPORT_TITLE": "Export CVE",
+ "EXPORT_SUBTITLE": "Set exporting conditions",
+ "EXPORT_CVE_FILTER_HELP_TEXT": "Enter multiple comma separated cveIds",
+ "CVE_IDS": "CVE IDs",
+ "EXPORT_BUTTON": "EXPORT",
+ "JOB_NAME": "Job Name",
+ "JOB_NAME_REQUIRED": "Job name is required",
+ "JOB_NAME_EXISTING": "Job name already exists",
+ "TRIGGER_EXPORT_SUCCESS": "Trigger exporting CVEs successfully!"
}
}
diff --git a/src/portal/src/i18n/lang/es-es-lang.json b/src/portal/src/i18n/lang/es-es-lang.json
index 9bb425e74..94bc4d619 100644
--- a/src/portal/src/i18n/lang/es-es-lang.json
+++ b/src/portal/src/i18n/lang/es-es-lang.json
@@ -1752,5 +1752,19 @@
"SKIP_DATABASE_TOOLTIP": "Skip to log audit log in the database, only available when audit log forward endpoint is configured",
"STOP_GC_SUCCESS": "Trigger stopping GC operation successfully",
"STOP_PURGE_SUCCESS": "Trigger stopping purging operation successfully"
+ },
+ "CVE_EXPORT": {
+ "EXPORT_SOME_PROJECTS": "Export CVEs - {{number}} project(s)",
+ "EXPORT_ALL_PROJECTS": "Export CVEs - All projects",
+ "ALL_PROJECTS": "All projects",
+ "EXPORT_TITLE": "Export CVE",
+ "EXPORT_SUBTITLE": "Set exporting conditions",
+ "EXPORT_CVE_FILTER_HELP_TEXT": "Enter multiple comma separated cveIds",
+ "CVE_IDS": "CVE IDs",
+ "EXPORT_BUTTON": "EXPORT",
+ "JOB_NAME": "Job Name",
+ "JOB_NAME_REQUIRED": "Job name is required",
+ "JOB_NAME_EXISTING": "Job name already exists",
+ "TRIGGER_EXPORT_SUCCESS": "Trigger exporting CVEs successfully!"
}
}
diff --git a/src/portal/src/i18n/lang/fr-fr-lang.json b/src/portal/src/i18n/lang/fr-fr-lang.json
index d7c05355b..882c48f0a 100644
--- a/src/portal/src/i18n/lang/fr-fr-lang.json
+++ b/src/portal/src/i18n/lang/fr-fr-lang.json
@@ -1722,5 +1722,19 @@
"SKIP_DATABASE_TOOLTIP": "Skip to log audit log in the database, only available when audit log forward endpoint is configured",
"STOP_GC_SUCCESS": "Trigger stopping GC operation successfully",
"STOP_PURGE_SUCCESS": "Trigger stopping purging operation successfully"
+ },
+ "CVE_EXPORT": {
+ "EXPORT_SOME_PROJECTS": "Export CVEs - {{number}} project(s)",
+ "EXPORT_ALL_PROJECTS": "Export CVEs - All projects",
+ "ALL_PROJECTS": "All projects",
+ "EXPORT_TITLE": "Export CVE",
+ "EXPORT_SUBTITLE": "Set exporting conditions",
+ "EXPORT_CVE_FILTER_HELP_TEXT": "Enter multiple comma separated cveIds",
+ "CVE_IDS": "CVE IDs",
+ "EXPORT_BUTTON": "EXPORT",
+ "JOB_NAME": "Job Name",
+ "JOB_NAME_REQUIRED": "Job name is required",
+ "JOB_NAME_EXISTING": "Job name already exists",
+ "TRIGGER_EXPORT_SUCCESS": "Trigger exporting CVEs successfully!"
}
}
diff --git a/src/portal/src/i18n/lang/pt-br-lang.json b/src/portal/src/i18n/lang/pt-br-lang.json
index 217e48756..5a7136e74 100644
--- a/src/portal/src/i18n/lang/pt-br-lang.json
+++ b/src/portal/src/i18n/lang/pt-br-lang.json
@@ -1749,5 +1749,19 @@
"SKIP_DATABASE_TOOLTIP": "Skip to log audit log in the database, only available when audit log forward endpoint is configured",
"STOP_GC_SUCCESS": "Trigger stopping GC operation successfully",
"STOP_PURGE_SUCCESS": "Trigger stopping purging operation successfully"
+ },
+ "CVE_EXPORT": {
+ "EXPORT_SOME_PROJECTS": "Export CVEs - {{number}} project(s)",
+ "EXPORT_ALL_PROJECTS": "Export CVEs - All projects",
+ "ALL_PROJECTS": "All projects",
+ "EXPORT_TITLE": "Export CVE",
+ "EXPORT_SUBTITLE": "Set exporting conditions",
+ "EXPORT_CVE_FILTER_HELP_TEXT": "Enter multiple comma separated cveIds",
+ "CVE_IDS": "CVE IDs",
+ "EXPORT_BUTTON": "EXPORT",
+ "JOB_NAME": "Job Name",
+ "JOB_NAME_REQUIRED": "Job name is required",
+ "JOB_NAME_EXISTING": "Job name already exists",
+ "TRIGGER_EXPORT_SUCCESS": "Trigger exporting CVEs successfully!"
}
}
diff --git a/src/portal/src/i18n/lang/tr-tr-lang.json b/src/portal/src/i18n/lang/tr-tr-lang.json
index e77ac2ad7..b265767d0 100644
--- a/src/portal/src/i18n/lang/tr-tr-lang.json
+++ b/src/portal/src/i18n/lang/tr-tr-lang.json
@@ -1753,5 +1753,19 @@
"SKIP_DATABASE_TOOLTIP": "Skip to log audit log in the database, only available when audit log forward endpoint is configured",
"STOP_GC_SUCCESS": "Trigger stopping GC operation successfully",
"STOP_PURGE_SUCCESS": "Trigger stopping purging operation successfully"
+ },
+ "CVE_EXPORT": {
+ "EXPORT_SOME_PROJECTS": "Export CVEs - {{number}} project(s)",
+ "EXPORT_ALL_PROJECTS": "Export CVEs - All projects",
+ "ALL_PROJECTS": "All projects",
+ "EXPORT_TITLE": "Export CVE",
+ "EXPORT_SUBTITLE": "Set exporting conditions",
+ "EXPORT_CVE_FILTER_HELP_TEXT": "Enter multiple comma separated cveIds",
+ "CVE_IDS": "CVE IDs",
+ "EXPORT_BUTTON": "EXPORT",
+ "JOB_NAME": "Job Name",
+ "JOB_NAME_REQUIRED": "Job name is required",
+ "JOB_NAME_EXISTING": "Job name already exists",
+ "TRIGGER_EXPORT_SUCCESS": "Trigger exporting CVEs successfully!"
}
}
diff --git a/src/portal/src/i18n/lang/zh-cn-lang.json b/src/portal/src/i18n/lang/zh-cn-lang.json
index 6d9c0eb75..9f1629b02 100644
--- a/src/portal/src/i18n/lang/zh-cn-lang.json
+++ b/src/portal/src/i18n/lang/zh-cn-lang.json
@@ -1751,5 +1751,19 @@
"SKIP_DATABASE_TOOLTIP": "开启此项将不会在数据库中记录日志,需先配置日志转发端点",
"STOP_GC_SUCCESS": "成功触发停止垃圾回收的操作",
"STOP_PURGE_SUCCESS": "成功触发停止清理日志的操作"
+ },
+ "CVE_EXPORT": {
+ "EXPORT_SOME_PROJECTS": "导出 CVEs - {{number}} 个项目",
+ "EXPORT_ALL_PROJECTS": "导出 CVEs - 全部项目",
+ "ALL_PROJECTS": "全部项目",
+ "EXPORT_TITLE": "导出 CVE",
+ "EXPORT_SUBTITLE": "设置导出条件",
+ "EXPORT_CVE_FILTER_HELP_TEXT": "使用逗号分割 cveIds",
+ "CVE_IDS": "CVE IDs",
+ "EXPORT_BUTTON": "导出",
+ "JOB_NAME": "任务名称",
+ "JOB_NAME_REQUIRED": "任务名称为必填项",
+ "JOB_NAME_EXISTING": "任务名称已存在",
+ "TRIGGER_EXPORT_SUCCESS": "触发导出 CVEs 任务成功!"
}
}
diff --git a/src/portal/src/i18n/lang/zh-tw-lang.json b/src/portal/src/i18n/lang/zh-tw-lang.json
index ac1428d83..4afe6733d 100644
--- a/src/portal/src/i18n/lang/zh-tw-lang.json
+++ b/src/portal/src/i18n/lang/zh-tw-lang.json
@@ -1744,5 +1744,19 @@
"SKIP_DATABASE_TOOLTIP": "Skip to log audit log in the database, only available when audit log forward endpoint is configured",
"STOP_GC_SUCCESS": "Trigger stopping GC operation successfully",
"STOP_PURGE_SUCCESS": "Trigger stopping purging operation successfully"
+ },
+ "CVE_EXPORT": {
+ "EXPORT_SOME_PROJECTS": "Export CVEs - {{number}} project(s)",
+ "EXPORT_ALL_PROJECTS": "Export CVEs - All projects",
+ "ALL_PROJECTS": "All projects",
+ "EXPORT_TITLE": "Export CVE",
+ "EXPORT_SUBTITLE": "Set exporting conditions",
+ "EXPORT_CVE_FILTER_HELP_TEXT": "Enter multiple comma separated cveIds",
+ "CVE_IDS": "CVE IDs",
+ "EXPORT_BUTTON": "EXPORT",
+ "JOB_NAME": "Job Name",
+ "JOB_NAME_REQUIRED": "Job name is required",
+ "JOB_NAME_EXISTING": "Job name already exists",
+ "TRIGGER_EXPORT_SUCCESS": "Trigger exporting CVEs successfully!"
}
}