harbor/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.ts

249 lines
7.8 KiB
TypeScript

import {
AfterViewInit,
Component,
Input,
OnDestroy,
OnInit,
} from '@angular/core';
import { ClrDatagridStateInterface, ClrLoadingState } from '@clr/angular';
import { finalize } from 'rxjs/operators';
import { AdditionLink } from '../../../../../../../../ng-swagger-gen/models/addition-link';
import {
ScannerVo,
UserPermissionService,
USERSTATICPERMISSION,
} from '../../../../../../shared/services';
import { ErrorHandler } from '../../../../../../shared/units/error-handler';
import {
dbEncodeURIComponent,
downloadJson,
getPageSizeFromLocalStorage,
PageSizeMapKeys,
SBOM_SCAN_STATUS,
setPageSizeToLocalStorage,
} from '../../../../../../shared/units/utils';
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';
import { severityText } from '../../../../../left-side-nav/interrogation-services/vulnerability-database/security-hub.interface';
import { AppConfigService } from 'src/app/services/app-config.service';
import {
ArtifactSbom,
ArtifactSbomPackageItem,
getArtifactSbom,
} from '../../artifact';
import { ArtifactService } from 'ng-swagger-gen/services';
import { ScanTypes } from 'src/app/shared/entities/shared.const';
import { ArtifactListPageService } from '../../artifact-list-page/artifact-list-page.service';
@Component({
selector: 'hbr-artifact-sbom',
templateUrl: './artifact-sbom.component.html',
styleUrls: ['./artifact-sbom.component.scss'],
})
export class ArtifactSbomComponent implements OnInit, OnDestroy {
@Input()
projectName: string;
@Input()
projectId: number;
@Input()
repoName: string;
@Input()
sbomDigest: string;
@Input() artifact: Artifact;
artifactSbom: ArtifactSbom;
loading: boolean = false;
hasScannerSupportSBOM: boolean = false;
downloadSbomBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
hasSbomPermission: boolean = false;
hasShowLoading: boolean = false;
sub: Subscription;
hasViewInitWithDelay: boolean = false;
pageSize: number = getPageSizeFromLocalStorage(
PageSizeMapKeys.ARTIFACT_SBOM_COMPONENT,
25
);
readonly severityText = severityText;
constructor(
private errorHandler: ErrorHandler,
private appConfigService: AppConfigService,
private artifactService: ArtifactService,
private artifactListPageService: ArtifactListPageService,
private userPermissionService: UserPermissionService,
private eventService: EventService,
private session: SessionService
) {}
ngOnInit() {
this.artifactListPageService.init(this.projectId);
this.getSbom();
this.getSbomPermission();
if (!this.sub) {
this.sub = this.eventService.subscribe(
HarborEvent.UPDATE_SBOM_INFO,
(artifact: Artifact) => {
if (artifact?.digest === this.artifact?.digest) {
if (artifact.sbom_overview) {
const sbomDigest = Object.values(
artifact.sbom_overview
)?.[0]?.sbom_digest;
if (sbomDigest) {
this.sbomDigest = sbomDigest;
}
}
this.getSbom();
}
}
);
}
setTimeout(() => {
this.hasViewInitWithDelay = true;
}, 0);
}
ngOnDestroy() {
if (this.sub) {
this.sub.unsubscribe();
this.sub = null;
}
}
getSbom() {
if (this.sbomDigest) {
if (!this.hasShowLoading) {
this.loading = true;
this.hasShowLoading = true;
}
const sbomAdditionParams = <ArtifactService.GetAdditionParams>{
repositoryName: dbEncodeURIComponent(this.repoName),
reference: this.sbomDigest,
projectName: this.projectName,
addition: ScanTypes.SBOM,
};
this.artifactService
.getAddition(sbomAdditionParams)
.pipe(
finalize(() => {
this.loading = false;
this.hasShowLoading = false;
})
)
.subscribe(
res => {
if (res) {
this.artifactSbom = getArtifactSbom(
JSON.parse(res)
);
} else {
this.loading = false;
this.hasShowLoading = false;
}
},
error => {
this.errorHandler.error(error);
}
);
}
}
getSbomPermission(): void {
const permissions = [
{
resource: USERSTATICPERMISSION.REPOSITORY_TAG_SBOM_JOB.KEY,
action: USERSTATICPERMISSION.REPOSITORY_TAG_SBOM_JOB.VALUE.READ,
},
];
this.userPermissionService
.hasProjectPermissions(this.projectId, permissions)
.subscribe(
(results: Array<boolean>) => {
this.hasSbomPermission = results[0];
// only has label permission
},
error => this.errorHandler.error(error)
);
}
refresh(): void {
this.getSbom();
}
hasGeneratedSbom(): boolean {
return this.hasViewInitWithDelay;
}
isSystemAdmin(): boolean {
const account = this.session.getCurrentUser();
return account && account.has_admin_role;
}
getScannerInfo(scanner: ScannerVo): string {
if (scanner) {
if (scanner.name && scanner.version) {
return `${scanner.name}@${scanner.version}`;
}
if (scanner.name && !scanner.version) {
return `${scanner.name}`;
}
}
return '';
}
isRunningState(): boolean {
return (
this.hasViewInitWithDelay &&
this.artifact.sbom_overview &&
(this.artifact.sbom_overview.scan_status ===
SBOM_SCAN_STATUS.PENDING ||
this.artifact.sbom_overview.scan_status ===
SBOM_SCAN_STATUS.RUNNING)
);
}
downloadSbom() {
this.downloadSbomBtnState = ClrLoadingState.LOADING;
if (
this.artifact?.sbom_overview?.scan_status ===
SBOM_SCAN_STATUS.SUCCESS
) {
downloadJson(
this.artifactSbom.sbomJsonRaw,
`${this.artifactSbom.sbomName}.json`
);
}
this.downloadSbomBtnState = ClrLoadingState.DEFAULT;
}
canDownloadSbom(): boolean {
this.hasScannerSupportSBOM =
this.artifactListPageService.hasScannerSupportSBOM();
return (
this.hasScannerSupportSBOM &&
//this.hasSbomPermission &&
this.sbomDigest &&
this.downloadSbomBtnState !== ClrLoadingState.LOADING &&
this.artifactSbom !== undefined
);
}
artifactSbomPackages(): ArtifactSbomPackageItem[] {
return this.artifactSbom?.sbomPackage?.packages ?? [];
}
load(state: ClrDatagridStateInterface) {
if (state?.page?.size) {
setPageSizeToLocalStorage(
PageSizeMapKeys.ARTIFACT_SBOM_COMPONENT,
state.page.size
);
}
}
}