Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/app/features/upload/upload.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ <h3 (click)="UpdateSort('FORMAT')">Format {{ sortingOptions['FORMAT'] ? '▴' :
[style]="collapsed ? 'display: none' : ''" class="files-container">
<ng-container *ngFor="let item of GetSBOMsOfType('VALID'); let i = index">
<div class="altColors">
<div *ngIf="getAlias(item)?.includes(GetFilter()) && ValidSBOMFormat(item)">
<div *ngIf="(getAlias(item)?.includes(GetFilter()) || item?.toString()?.includes(GetFilter())) && ValidSBOMFormat(item)">
<div class="sbom-row flex">
<div (dblclick)="ViewSBOM(item)" class="sbom">
<input [value]="item" class="sbom-checkbox" type="checkbox"/>
<app-menu [data]="item" style="width: 90%" text="{{ getAlias(item) }}"></app-menu>
<div class="sbom" (dblclick)="ViewSBOM(item)">
<input type="checkbox" [value]="item" class="sbom-checkbox" />
<app-menu text="{{ getAlias(item) }}" [data]="item" style="width: 90%" title="{{ getAlias(item) }}"></app-menu>
</div>
<div class="sbom-view">
{{ GetSBOMInfo(item).format }}
Expand Down
5 changes: 4 additions & 1 deletion src/app/features/upload/upload.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ export class UploadComponent implements OnInit {

const files = event.dataTransfer?.files;
if (files && files.length > 0) {
const filePaths = Array.from(files).map((file) => (file as any).path);
const filePaths = Array.from(files).map((file: any) => (file.path as string));
this.sbomService.AddFiles(filePaths);
}
}
Expand All @@ -324,6 +324,9 @@ export class UploadComponent implements OnInit {
if (searchInput) {
searchInput.value = '';
}
// Re-enable all formats and schemas so nothing stays hidden
Object.keys(this.GetSBOMFormat()).forEach((key: string) => this.sbomService.SetSBOMFormat(key, true));
Object.keys(this.GetSBOMSchemas()).forEach((key: string) => this.sbomService.SetSBOMSchema(key, true));
}

UpdateSearch(event: any) {
Expand Down
14 changes: 7 additions & 7 deletions src/app/shared/components/toast/toast.component.html
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<div
#toastElement
[ngClass]="type"
aria-atomic="true"
aria-live="assertive"
class="toast fade toast-width mt-2"
[ngClass]="type"
role="alert"
aria-live="assertive"
aria-atomic="true"
>
<div class="toast-header">
<div class="toast-header" [ngClass]="type">
<strong class="me-auto">{{ title }}</strong>
<button (click)="hide()" aria-label="Close" class="btn-close" type="button"></button>
<button type="button" class="btn-close" aria-label="Close" (click)="hide()"></button>
</div>
<div class="toast-body">{{ message }}</div>
</div>
<div class="toast-body" [ngClass]="type">{{ message }}</div>
</div>
34 changes: 29 additions & 5 deletions src/app/shared/components/toast/toast.component.scss
Original file line number Diff line number Diff line change
@@ -1,15 +1,39 @@
.warning {
background-color: #F44336;
}

.warning {
background-color: #FFC107;
}

.info {
background-color: #4CAF50;
}


.error {
background-color: #F44336;
}

.toast-header {

.toast-header.info {
background-color: #4CAF50;
}

.toast-header.warning {
background-color: #FFC107;
}

.toast-header.error {
background-color: #F44336;
}

.toast-body {

.toast-body.info {
background-color: #E8F5E9;
}

.toast-body.warning {
background-color: #FFF8E1;
}

.toast-body.error {
background-color: #FDE0DE;
}
Original file line number Diff line number Diff line change
@@ -1,58 +1,72 @@
<app-modal (close)="Close()" [opened]="opened">
<div title>
Generate SBOM
</div>
<div *ngIf="this.status === 1" content style="display: flex; flex-direction: column; align-items: center;">
<app-spinner height="64px" style="height: 64px;"/>
<div>Waiting for Project Selection...</div>
</div>
<div *ngIf="this.status === 2" content style="display: flex; flex-direction: column; align-items: center;">
<app-spinner height="64px" style="height: 64px;"/>
<div>Zipping Project Contents...</div>
</div>
<div *ngIf="this.status === 4" content style="display: flex; flex-direction: column; align-items: center;">
<app-spinner height="64px" style="height: 64px;"/>
<div>Generating SBOM...</div>
</div>
<div *ngIf="this.status === 3" content style="display: flex; gap: 15px; flex-direction: column;">
<div>
<div>Name:</div>
<input [(ngModel)]="options.name"/>
<app-modal [opened]="opened" (close)="Close()">
<div title>
Generate SBOM
</div>
<div>
<div>Schema:</div>
<select [(ngModel)]="options.schema">
<option value=""></option>
<option *ngFor="let option of choices | keyvalue" [value]="option.key">{{ option.key }}</option>
</select>
<div content *ngIf="this.status === 1" style="display: flex; flex-direction: column; align-items: center;">
<app-spinner height="64px" style="height: 64px;"/>
<div>Waiting for Project Selection...</div>
</div>
<div>
<div>Format:</div>
<select [(ngModel)]="options.format">
<option value=""></option>
<option *ngFor="let option of choices[options.schema]" [value]="option">{{ option }}</option>
</select>
<!--
<div content *ngIf="this.status === 5" style="display: flex; flex-direction: column; gap: 12px; align-items: center;">
<div style="font-weight: 600;">Select Source</div>
<div style="display: flex; gap: 12px;">
<div class="btn-modal button flex center hover" (click)="SelectFolderAndZip()">
<span>Select folder and zip</span>
</div>
<label class="btn-modal button flex center hover" style="cursor: pointer;">
<span>Use existing .zip</span>
<input type="file" accept=".zip" style="display: none;" (change)="OnZipFileSelected($event)" />
</label>
</div>
</div>
<div>
<div>Type:</div>
<select [(ngModel)]="options.type">
<option value=""></option>
<option *ngFor="let option of types" [value]="option">{{ option }}</option>
</select>
-->
<div content *ngIf="this.status === 2" style="display: flex; flex-direction: column; align-items: center;">
<app-spinner height="64px" style="height: 64px;"/>
<div>Zipping Project Contents...</div>
</div>
<div *ngIf="options.type === 'OSI'">
<div>OSI Tools:</div>
<app-multiselect-dropdown (checkboxChange)="OSIToolChange($event)" [options]="osiTools" style="width: 400px"
title="Filter Tools">
</app-multiselect-dropdown>
<div content *ngIf="this.status === 4" style="display: flex; flex-direction: column; align-items: center;">
<app-spinner height="64px" style="height: 64px;"/>
<div>Generating SBOM...</div>
</div>
</div>
<div actions style=" display: flex; gap: 15px;">
<div (click)="Generate()" *ngIf="this.zippedFileData !== undefined" class="btn-modal button flex center hover">
<span>Generate</span>
<div content style="display: flex; gap: 15px; flex-direction: column;" *ngIf="this.status === 3">
<div>
<div>Name:</div>
<input [(ngModel)]="options.name" />
</div>
<div>
<div>Schema:</div>
<select [(ngModel)]="options.schema">
<option value=""></option>
<option *ngFor="let option of choices | keyvalue" [value]="option.key">{{ option.key }}</option>
</select>
</div>
<div>
<div>Format:</div>
<select [(ngModel)]="options.format">
<option value=""></option>
<option *ngFor="let option of choices[options.schema]" [value]="option">{{ option }}</option>
</select>
</div>
<div>
<div>Type:</div>
<select [(ngModel)]="options.type">
<option value=""></option>
<option *ngFor="let option of types" [value]="option">{{ option }}</option>
</select>
</div>
<div *ngIf="options.type === 'OSI'">
<div>OSI Tools:</div>
<app-multiselect-dropdown title="Filter Tools" [options]="osiTools" style="width: 400px"
(checkboxChange)="OSIToolChange($event)">
</app-multiselect-dropdown>
</div>
</div>
<div (click)="Close()" class="btn-modal button flex center hover">
<span>Cancel</span>
<div actions style=" display: flex; gap: 15px;">
<div class="btn-modal button flex center hover" (click)="Generate()" *ngIf="this.zippedFileData !== undefined">
<span>Generate</span>
</div>
<div class="btn-modal button flex center hover" (click)="Close()">
<span>Cancel</span>
</div>
</div>
</div>
</app-modal>
</app-modal>
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {SVIPService} from 'src/app/shared/services/SVIP.service';
import {Subject} from 'rxjs';
import {SbomService} from 'src/app/shared/services/sbom.service';
import {ToastService} from 'src/app/shared/services/toast.service';
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
import { SVIPService } from 'src/app/shared/services/SVIP.service';
import { Subject } from 'rxjs';
import { SbomService } from 'src/app/shared/services/sbom.service';
import { ToastService } from 'src/app/shared/services/toast.service';

@Component({
selector: 'app-generate-modal',
templateUrl: './generate-modal.component.html',
styleUrls: ['./generate-modal.component.css'],
standalone: false
styleUrls: ['./generate-modal.component.css']
})
export class GenerateModalComponent implements OnInit {
public options: {
Expand All @@ -23,56 +22,59 @@ export class GenerateModalComponent implements OnInit {
type: '',
};

public choices: { [key: string]: string[] } = {
public choices: {[key: string]: string[]} = {
'CDX14': ['JSON', 'XML'],
'SPDX23': ['TAGVALUE', 'JSON'],
}

public types: string[] = ['OSI', 'PARSERS'];

public osiTools: { [name: string]: boolean } = {};
public osiTools: {[name: string]: boolean} = {};

@Input() opened: boolean = false;
@Output() close = new EventEmitter<Boolean>();
private openedSubject = new Subject<boolean>();

public status: GenerationStatus = GenerationStatus.NULL;
public zippedFileData: any;
private openedSubject = new Subject<boolean>();
// Pre-zipped upload temporarily disabled; keep flag for future use
// public useExistingZip: boolean = false;
// public selectingSource: boolean = true; // when re-enabled, use to gate source step

constructor(private service: SVIPService, private sbomService: SbomService, private toast: ToastService) {
}
constructor(private service: SVIPService, private sbomService: SbomService, private toast: ToastService) {}

ngOnInit(): void {
this.openedSubject.subscribe((value) => {
if (!value)
if(!value)
return;

this.zippedFileData = undefined;
this.status = GenerationStatus.GENERATING;
this.zippedFileData = undefined;
// Reverted to original flow; previous pre-zip source selection kept below as comments
this.status = GenerationStatus.GENERATING;

this.service.getProjectDirectory().then((result) => {
this.status = GenerationStatus.ZIPPING;
this.service.getProjectDirectory().then((result) => {
this.status = GenerationStatus.ZIPPING;

this.service.zipFileDirectory(result).then((data) => {
this.service.zipFileDirectory(result).then((data) => {

//TODO: Prompt user beforehand on OSI or Parsers so don't need to upload project if don't have to OR the backend should be reworked for parsers
this.service.uploadProject(data, 'osi').then((tools: any) => {
this.service.uploadProject(data, 'osi').then((tools: any) => {

tools.forEach((tool: any) => {
this.osiTools[tool] = true;
})
tools.forEach((tool: any) => {
this.osiTools[tool] = true;
})

this.zippedFileData = data;
this.status = GenerationStatus.PROJECT_INFO;
this.zippedFileData = data;
this.status = GenerationStatus.PROJECT_INFO;

}).catch((error) => {
this.Close();
})
}).catch((error) => {
this.Close();
})
}).catch((error) => {
this.Close();
})
}).catch((error) => {
this.Close();
})
});
}

Expand All @@ -99,12 +101,12 @@ export class GenerateModalComponent implements OnInit {
this.options.format,
this.options.type,
tools).then((data: any) => {
this.sbomService.addSBOMbyID(data);
this.Close();
}).catch(() => {
this.toast.showErrorToast("SBOM Generation", "Failed");
this.Close();
})
this.sbomService.addSBOMbyID(data);
this.Close();
}).catch(() => {
this.toast.showErrorToast("SBOM Generation", "Failed");
this.Close();
})


}
Expand All @@ -113,6 +115,38 @@ export class GenerateModalComponent implements OnInit {
this.osiTools[event.name] = event.value;
}

// --- Begin: Pre-zipped flow (commented out) ---
// SelectFolderAndZip() {
// this.status = GenerationStatus.ZIPPING;
// this.service.getProjectDirectory().then((result) => {
// this.service.zipFileDirectory(result).then((data) => {
// this.service.uploadProject(data, 'osi').then((tools: any) => {
// tools.forEach((tool: any) => {
// this.osiTools[tool] = true;
// })
// this.zippedFileData = data;
// this.status = GenerationStatus.PROJECT_INFO;
// }).catch(() => { this.Close(); })
// }).catch(() => { this.Close(); })
// }).catch(() => { this.Close(); })
// }
//
// OnZipFileSelected(event: any) {
// const files: FileList = event.target.files;
// if (!files || files.length === 0)
// return;
// const zip = files[0];
// this.status = GenerationStatus.GENERATING;
// this.service.uploadProject(zip, 'osi').then((tools: any) => {
// tools.forEach((tool: any) => {
// this.osiTools[tool] = true;
// })
// this.zippedFileData = zip;
// this.status = GenerationStatus.PROJECT_INFO;
// }).catch(() => { this.Close(); })
// }
// --- End: Pre-zipped flow (commented out) ---


Close() {
this.status = GenerationStatus.NULL;
Expand All @@ -127,4 +161,5 @@ enum GenerationStatus {
ZIPPING,
PROJECT_INFO,
GENERATING,
// SELECTING_SOURCE, // disabled
}
Loading