├── .editorconfig ├── .erb ├── configs │ ├── .eslintrc │ ├── webpack.config.base.ts │ ├── webpack.config.eslint.ts │ ├── webpack.config.main.prod.ts │ ├── webpack.config.preload.dev.ts │ ├── webpack.config.renderer.dev.dll.ts │ ├── webpack.config.renderer.dev.ts │ ├── webpack.config.renderer.prod.ts │ ├── webpack.config.threads.dev.ts │ └── webpack.paths.ts ├── flatpak │ ├── com.scanoss.sbom-workbench.metainfo.xml │ ├── com.scanoss.sbom-workbench.png │ └── sbom-workbench-screenshot-01.png ├── img │ ├── workbench_1.c77c358.png │ └── workbench_1.c77c359.png ├── mocks │ └── fileMock.js ├── override │ ├── example.config.override.json │ └── example.package.override.js └── scripts │ ├── .eslintrc │ ├── check-build-exists.ts │ ├── check-native-dep.js │ ├── check-node-env.js │ ├── check-port-in-use.js │ ├── clean.js │ ├── delete-source-maps.js │ ├── electron-rebuild.js │ ├── link-modules.ts │ ├── notarize.js │ └── override.js ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .github ├── actions │ └── setup-build │ │ └── action.yml └── workflows │ ├── publish.yml │ ├── reuse.yml │ ├── test.yml │ └── version-tag.yml ├── .gitignore ├── .husky └── pre-commit ├── .readthedocs.yaml ├── .reuse └── dep5 ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── CODE_OF_CONDUCT.md ├── COLLAB_WORKSPACE.md ├── CONTRIBUTING.md ├── LICENSE ├── LICENSES ├── CC0-1.0.txt ├── GPL-2.0-only.txt ├── MIT.txt └── OFL-1.1.txt ├── README.md ├── SBOM.json ├── appimage-fix.js ├── assets ├── assets.d.ts ├── data │ ├── ComponentCatalogPackages.ts │ ├── licenses-spdx.json │ ├── licenses.ts │ ├── scanoss-crypto-algorithm-rules.json │ └── scanoss-crypto-library-rules.json ├── entitlements.mac.plist ├── exportTemplates │ └── template.html ├── fonts │ ├── Inter-Black.ttf │ ├── Inter-Bold.ttf │ ├── Inter-ExtraBold.ttf │ ├── Inter-ExtraLight.ttf │ ├── Inter-Light.ttf │ ├── Inter-Medium.ttf │ ├── Inter-Regular.ttf │ ├── Inter-SemiBold.ttf │ ├── Inter-Thin.ttf │ ├── Inter-VariableFont_slnt,wght.ttf │ ├── SpaceMono-Bold.ttf │ ├── SpaceMono-BoldItalic.ttf │ ├── SpaceMono-Italic.ttf │ └── SpaceMono-Regular.ttf ├── i18n │ ├── README.md │ ├── en │ │ ├── AppMenu.json │ │ ├── Button.json │ │ ├── Common.json │ │ ├── Dialog.json │ │ ├── Table.json │ │ ├── Title.json │ │ └── Tooltip.json │ ├── es │ │ ├── AppMenu.json │ │ ├── Button.json │ │ ├── Common.json │ │ ├── Dialog.json │ │ ├── Table.json │ │ ├── Title.json │ │ └── Tooltip.json │ ├── jp │ │ ├── AppMenu.json │ │ ├── Button.json │ │ ├── Common.json │ │ ├── Dialog.json │ │ ├── Table.json │ │ ├── Title.json │ │ └── Tooltip.json │ └── zh │ │ ├── AppMenu.json │ │ ├── Button.json │ │ ├── Common.json │ │ ├── Dialog.json │ │ ├── Table.json │ │ ├── Title.json │ │ └── Tooltip.json ├── icon.icns ├── icon.ico ├── icon.png ├── icon.svg ├── icons │ ├── 1024x1024.png │ ├── 128x128.png │ ├── 16x16.png │ ├── 24x24.png │ ├── 256x256.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 512x512.png │ ├── 64x64.png │ └── 96x96.png ├── imgs │ ├── change_workspace.svg │ ├── component-default.svg │ ├── component-empty.svg │ ├── component-recognized.svg │ └── filter-icon.svg └── logos │ └── scanoss_white.png ├── docs ├── Makefile ├── make.bat └── source │ ├── SCANOSSDocsLogo.jpg │ ├── conf.py │ ├── detected-first.png │ ├── detected-last.png │ ├── export-sbom.png │ ├── home-screenshot.png │ ├── identified.png │ ├── index.rst │ ├── project-settings.png │ ├── reports-first.png │ ├── reports-last.png │ ├── requirements-docs.txt │ ├── scanosslogo.jpg │ └── workbench_1.c77c358.png ├── package-lock.json ├── package.json ├── release └── app │ ├── package-lock.json │ └── package.json ├── samples └── scanoss-crypto-rules.json ├── src ├── __mocks__ │ └── original-fs.js ├── __tests__ │ ├── components │ │ ├── ProjectList.test.tsx │ │ └── __snapshots__ │ │ │ └── ProjectList.test.tsx.snap │ └── export │ │ ├── CSV │ │ ├── cryptography-csv.test.ts │ │ └── vulnerability-csv.test.ts │ │ ├── ExportRepositoryMock.ts │ │ ├── SPDXLite │ │ └── spdx.test.ts │ │ ├── helper │ │ └── exportHelper.test.ts │ │ ├── mocks │ │ └── vulnerability.model.mock.ts │ │ └── settings │ │ └── settings.test.ts ├── api │ ├── Response.ts │ ├── api.ts │ ├── dto.ts │ ├── handlers │ │ ├── app.handler.ts │ │ ├── component.handler.ts │ │ ├── cryptography.handler.ts │ │ ├── dependency.handler.ts │ │ ├── file.handler.ts │ │ ├── formats.handler.ts │ │ ├── inventory.handler.ts │ │ ├── license.handler.ts │ │ ├── project.handler.ts │ │ ├── report.handler.ts │ │ ├── results.handler.ts │ │ ├── search.handler.ts │ │ ├── userSetting.handler.ts │ │ ├── vulnerability.handler.ts │ │ └── workspace.handler.ts │ ├── index.ts │ ├── ipc-channels.ts │ ├── middlewares │ │ ├── lock.middleware.ts │ │ ├── project.open.permissions.middleware.ts │ │ ├── refresh.middleware.ts │ │ ├── unlock.middleware.ts │ │ └── writeAccess.middleware.ts │ ├── services │ │ ├── base.service.ts │ │ ├── component.service.ts │ │ ├── cryptography.service.ts │ │ ├── dependency.service.ts │ │ ├── export.service.ts │ │ ├── file.service.ts │ │ ├── inventory.service.ts │ │ ├── license.service.ts │ │ ├── obligations.service.ts │ │ ├── project.service.ts │ │ ├── report.service.ts │ │ ├── results.service.ts │ │ ├── search.service.ts │ │ ├── userSetting.service.ts │ │ ├── vulnerability.service.ts │ │ └── workspace.service.ts │ └── types.ts ├── config │ ├── AppConfigDefault.ts │ ├── AppConfigModule.ts │ └── IAppConfig.ts ├── main │ ├── adapters │ │ ├── ComponentAdapter.ts │ │ └── ResultAdapter.ts │ ├── batch │ │ ├── Accept.ts │ │ ├── Batch.ts │ │ ├── BatchFactory.ts │ │ ├── Filter │ │ │ ├── Filter.ts │ │ │ ├── FilterAND.ts │ │ │ ├── FilterNOT.ts │ │ │ ├── FilterOR.ts │ │ │ ├── FilterTrue.ts │ │ │ └── GenericFilter.ts │ │ ├── Identified.ts │ │ ├── Ignore.ts │ │ └── Restore.ts │ ├── broadcastManager │ │ └── BroadcastManager.ts │ ├── helpers │ │ ├── ComponentHelper.ts │ │ ├── DependencyHelper.ts │ │ ├── FileHelper.ts │ │ ├── InventoryHelper.ts │ │ ├── LicenseHelper.ts │ │ ├── SemVer.ts │ │ └── UtilHelper.ts │ ├── main.ts │ ├── menu.ts │ ├── migration │ │ ├── AppMigration.ts │ │ ├── Migration.ts │ │ ├── ProjectMigration.ts │ │ └── scripts │ │ │ ├── 0190.ts │ │ │ ├── 0193.ts │ │ │ ├── 0200.ts │ │ │ ├── 0210.ts │ │ │ ├── 0220.ts │ │ │ ├── 0230.ts │ │ │ ├── 1.12.0.ts │ │ │ ├── 1.12.3.ts │ │ │ ├── 1.12.4.ts │ │ │ ├── 1.13.0.ts │ │ │ ├── 1.15.0.ts │ │ │ ├── 1.16.1.ts │ │ │ ├── 1.17.0.ts │ │ │ ├── 1.18.0.ts │ │ │ ├── 110.ts │ │ │ ├── 120.ts │ │ │ ├── 140.ts │ │ │ ├── 150.ts │ │ │ ├── 151.ts │ │ │ ├── 180.ts │ │ │ ├── 183.ts │ │ │ └── 184.ts │ ├── model │ │ ├── Connection.ts │ │ ├── Model.ts │ │ ├── UtilModel.ts │ │ ├── adapters │ │ │ ├── adapter.ts │ │ │ ├── component │ │ │ │ ├── detectedComponentAdapter.ts │ │ │ │ └── identifiedComponentAdapter.ts │ │ │ ├── dependency │ │ │ │ └── declaredComponentAdapter.ts │ │ │ └── report │ │ │ │ └── detectedLicenseSummaryAdapter.ts │ │ ├── entity │ │ │ ├── ComponentVersion.ts │ │ │ ├── ComponentVulnerability.ts │ │ │ ├── Cryptography.ts │ │ │ ├── Dependency.ts │ │ │ ├── ExportControl.ts │ │ │ ├── LocalCryptography.ts │ │ │ └── Vulnerability.ts │ │ ├── hooks │ │ │ └── after │ │ │ │ └── afterHook.ts │ │ ├── interfaces │ │ │ ├── IInsertResult.ts │ │ │ ├── ModelTypes.ts │ │ │ ├── component │ │ │ │ ├── IComponentLicense.ts │ │ │ │ ├── IComponentLicenseReliable.ts │ │ │ │ └── INewComponent.ts │ │ │ └── report │ │ │ │ ├── DataRecord.ts │ │ │ │ ├── DecisionData.ts │ │ │ │ ├── ExportComponentData.ts │ │ │ │ ├── ExportCryptographyData.ts │ │ │ │ └── VulnerabilityExportData.ts │ │ ├── project │ │ │ ├── ProjectModel.ts │ │ │ └── models │ │ │ │ ├── ComponentModel.ts │ │ │ │ ├── CryptographyModel.ts │ │ │ │ ├── DependencyModel.ts │ │ │ │ ├── ExportControlModel.ts │ │ │ │ ├── FileModel.ts │ │ │ │ ├── InventoryModel.ts │ │ │ │ ├── LicenseModel.ts │ │ │ │ ├── LocalCryptographyModel.ts │ │ │ │ ├── ProjectKnowledgeModel.ts │ │ │ │ ├── ReportModel.ts │ │ │ │ ├── ResultModel.ts │ │ │ │ └── VulnerabilityModel.ts │ │ ├── queryBuilder │ │ │ ├── QueryBuilder.ts │ │ │ ├── QueryBuilderAND.ts │ │ │ ├── QueryBuilderCompId.ts │ │ │ ├── QueryBuilderCreator.ts │ │ │ ├── QueryBuilderCustom.ts │ │ │ ├── QueryBuilderFilIdIN.ts │ │ │ ├── QueryBuilderFilePathIN.ts │ │ │ ├── QueryBuilderFilename.ts │ │ │ ├── QueryBuilderIN.ts │ │ │ ├── QueryBuilderMD5FileIn.ts │ │ │ ├── QueryBuilderSource.ts │ │ │ ├── QueryBuilderStatus.ts │ │ │ └── QueryBuilderUsage.ts │ │ ├── querys_db.ts │ │ └── workspace │ │ │ ├── WorkspaceModel.ts │ │ │ └── models │ │ │ ├── GroupKeywordModel.ts │ │ │ └── LockModel.ts │ ├── modules │ │ ├── export │ │ │ ├── DataProviders │ │ │ │ ├── BaseDataProvider.ts │ │ │ │ ├── ComponentDataProvider.ts │ │ │ │ ├── CryptographyDataProvider.ts │ │ │ │ ├── DependencyDataProvider.ts │ │ │ │ ├── LicenseDataProvider.ts │ │ │ │ └── SummaryDataProvider.ts │ │ │ ├── ExportDTO.ts │ │ │ ├── Format.ts │ │ │ ├── IExportResult.ts │ │ │ ├── ReportData.ts │ │ │ ├── Repository │ │ │ │ ├── ExportRepository.ts │ │ │ │ └── ExportRepositorySqliteImp.ts │ │ │ ├── format │ │ │ │ ├── CSV │ │ │ │ │ ├── Cryptography-csv.ts │ │ │ │ │ ├── SBOM-csv.ts │ │ │ │ │ └── Vulnerability-csv.ts │ │ │ │ ├── CycloneDX │ │ │ │ │ ├── CycloneDX.ts │ │ │ │ │ ├── CycloneDXDetected.ts │ │ │ │ │ └── CycloneDXIdentified.ts │ │ │ │ ├── HtmlSummary.ts │ │ │ │ ├── Raw.ts │ │ │ │ ├── SPDXLite │ │ │ │ │ ├── SpdxDocument.ts │ │ │ │ │ ├── SpdxLite.ts │ │ │ │ │ ├── SpdxLiteDetected.ts │ │ │ │ │ └── SpdxLiteIdentified.ts │ │ │ │ ├── Settings │ │ │ │ │ ├── Settings.ts │ │ │ │ │ ├── identification-tree │ │ │ │ │ │ ├── decision-node.ts │ │ │ │ │ │ ├── decision-tree.ts │ │ │ │ │ │ ├── folder.ts │ │ │ │ │ │ └── leaf.ts │ │ │ │ │ ├── processors │ │ │ │ │ │ ├── bom-folder-processor.ts │ │ │ │ │ │ ├── bom-leaf-proccessor.ts │ │ │ │ │ │ ├── bom-processor.ts │ │ │ │ │ │ └── processor.ts │ │ │ │ │ └── types │ │ │ │ │ │ └── index.ts │ │ │ │ └── Wfp.ts │ │ │ └── helpers │ │ │ │ └── exportHelper.ts │ │ ├── projectKnowledge │ │ │ ├── ProjectKnowledgeExtractor.ts │ │ │ └── projectKnowledgeAdapters │ │ │ │ └── projectKnowledgeAdapter.ts │ │ ├── report │ │ │ └── components │ │ │ │ ├── ComponentReport.ts │ │ │ │ ├── ComponentReportVisitor.ts │ │ │ │ ├── Report.ts │ │ │ │ ├── ReportComponentDetected.ts │ │ │ │ └── ReportComponentIndentified.ts │ │ └── searchEngine │ │ │ ├── indexer │ │ │ ├── IIndexer.ts │ │ │ └── Indexer.ts │ │ │ └── searcher │ │ │ ├── ISearcher.ts │ │ │ └── Searcher.ts │ ├── preload.ts │ ├── services │ │ ├── ComponentService.ts │ │ ├── CryptographyService.ts │ │ ├── DependencyService.ts │ │ ├── ExportControlService.ts │ │ ├── FileService.ts │ │ ├── InventoryService.ts │ │ ├── LicenseService.ts │ │ ├── ModelProvider.ts │ │ ├── ProjectService.ts │ │ ├── ReportService.ts │ │ ├── RescanService.ts │ │ ├── ResultService.ts │ │ ├── TreeService.ts │ │ ├── UserSettingService.ts │ │ ├── VulnerabilityService.ts │ │ ├── WorkspaceService.ts │ │ └── utils │ │ │ ├── cryptography.ts │ │ │ ├── hookAfter.ts │ │ │ ├── httpUtil.ts │ │ │ ├── inventoryServiceUtil.ts │ │ │ ├── vulnerability.ts │ │ │ └── workspace.ts │ ├── task │ │ ├── IndexTreeTask │ │ │ ├── CodeIndexTreeTask.ts │ │ │ ├── IndexTreeTask.ts │ │ │ └── WFPIndexTreeTask.ts │ │ ├── Task.ts │ │ ├── componentCatalog │ │ │ ├── SearchComponentTask.ts │ │ │ ├── SearchComponentVersionTask.ts │ │ │ ├── adapters │ │ │ │ ├── CompSearchResponseAdapter.ts │ │ │ │ └── CompoVerSearchResponseAdapter.ts │ │ │ ├── builders │ │ │ │ ├── CompSearchRequestBuilder.ts │ │ │ │ └── CompVerSearchRequestBuilder.ts │ │ │ └── iComponentCatalog │ │ │ │ ├── IComponentResult.ts │ │ │ │ ├── IComponentVersionResult.ts │ │ │ │ ├── ISearchComponent.ts │ │ │ │ └── ISearchComponentVersion.ts │ │ ├── cryptography │ │ │ ├── AddCryptographyTask.ts │ │ │ └── ICryptographyTask.ts │ │ ├── decompress │ │ │ └── DecompressTask.ts │ │ ├── export │ │ │ └── Export.ts │ │ ├── grpc │ │ │ ├── gRPCConnection │ │ │ │ └── gRPCConnection.ts │ │ │ └── scanoss │ │ │ │ └── api │ │ │ │ ├── common │ │ │ │ └── v2 │ │ │ │ │ ├── scanoss-common_grpc_pb.d.ts │ │ │ │ │ ├── scanoss-common_grpc_pb.js │ │ │ │ │ ├── scanoss-common_pb.d.ts │ │ │ │ │ └── scanoss-common_pb.js │ │ │ │ ├── components │ │ │ │ └── v2 │ │ │ │ │ ├── scanoss-components_grpc_pb.d.ts │ │ │ │ │ ├── scanoss-components_grpc_pb.js │ │ │ │ │ ├── scanoss-components_pb.d.ts │ │ │ │ │ └── scanoss-components_pb.js │ │ │ │ ├── dependencies │ │ │ │ └── v2 │ │ │ │ │ ├── scanoss-dependencies_grpc_pb.d.ts │ │ │ │ │ ├── scanoss-dependencies_grpc_pb.js │ │ │ │ │ ├── scanoss-dependencies_pb.d.ts │ │ │ │ │ └── scanoss-dependencies_pb.js │ │ │ │ ├── scanning │ │ │ │ └── v2 │ │ │ │ │ ├── scanoss-scanning_grpc_pb.d.ts │ │ │ │ │ ├── scanoss-scanning_grpc_pb.js │ │ │ │ │ ├── scanoss-scanning_pb.d.ts │ │ │ │ │ └── scanoss-scanning_pb.js │ │ │ │ └── vulnerabilities │ │ │ │ └── v2 │ │ │ │ ├── scanoss-vulnerabilities_grpc_pb.d.ts │ │ │ │ ├── scanoss-vulnerabilities_grpc_pb.js │ │ │ │ ├── scanoss-vulnerabilities_pb.d.ts │ │ │ │ └── scanoss-vulnerabilities_pb.js │ │ ├── inventory │ │ │ ├── AutoAccept.ts │ │ │ └── AutoAcceptResult.ts │ │ ├── reuseIdentification │ │ │ └── ReuseIdentificationTask.ts │ │ ├── scanner │ │ │ ├── BaseScannerTask.ts │ │ │ ├── adapter │ │ │ │ ├── BaseScannerInputAdapter.ts │ │ │ │ ├── CodeScannerInputAdapter.ts │ │ │ │ ├── IScannerInputAdapter.ts │ │ │ │ ├── WFPResumeScannerInputAdapter.ts │ │ │ │ └── WFPScannerInputAdapter.ts │ │ │ ├── cryptography │ │ │ │ ├── CryptographyTask.ts │ │ │ │ ├── LocalCryptographyTask.ts │ │ │ │ └── definitions │ │ │ │ │ └── CryptoDef.ts │ │ │ ├── dependency │ │ │ │ ├── DependencyTask.ts │ │ │ │ └── ReScanDependencyTask.ts │ │ │ ├── dispatcher │ │ │ │ ├── CodeDispatcher.ts │ │ │ │ ├── IDispatch.ts │ │ │ │ └── WFPDispatcher.ts │ │ │ ├── rescan │ │ │ │ ├── CodeReScanTask.ts │ │ │ │ ├── RescanTask.ts │ │ │ │ └── WFPRescanTask.ts │ │ │ ├── resume │ │ │ │ ├── ResumeScanTask.ts │ │ │ │ └── WFPResumeTask.ts │ │ │ ├── scan │ │ │ │ ├── CodeScanTask.ts │ │ │ │ ├── ScanTask.ts │ │ │ │ └── WFPScanTask.ts │ │ │ ├── scannerPipeline │ │ │ │ ├── CodeScannerPipelineTask.ts │ │ │ │ ├── ScannerPipeline.ts │ │ │ │ └── WFPScannerPipeLineTask.ts │ │ │ ├── scannerPipelineFactory │ │ │ │ └── ScannerPipelineFactory.ts │ │ │ ├── types.ts │ │ │ └── vulnerability │ │ │ │ └── VulnerabilitiesTask.ts │ │ ├── search │ │ │ ├── indexTask │ │ │ │ └── IndexTask.ts │ │ │ └── searchTask │ │ │ │ ├── ISearchResult.ts │ │ │ │ ├── ISearchTask.ts │ │ │ │ └── SearchTask.ts │ │ └── vulnerability │ │ │ ├── AddVulnerabilityTask.ts │ │ │ ├── IVulnerabilities │ │ │ └── IGetVulnerabilities.ts │ │ │ └── builders │ │ │ └── GetVulnerabilityRequestBuilder.ts │ ├── threads │ │ ├── scanner.ts │ │ └── scanner │ │ │ └── DecompressThread.ts │ ├── util.ts │ ├── utils │ │ ├── TransformData.ts │ │ └── utils.ts │ └── workspace │ │ ├── Metadata.ts │ │ ├── Project.ts │ │ ├── ProjectZipper.ts │ │ ├── Workspace.ts │ │ ├── WsUtils │ │ └── WsUtils.ts │ │ ├── filtering.ts │ │ ├── filtering │ │ ├── AbstractFilter.ts │ │ └── defaultFilter.ts │ │ ├── filters │ │ ├── ProjectFilter.ts │ │ ├── ProjectFilterName.ts │ │ ├── ProjectFilterPath.ts │ │ └── ProjectFilterUUID.ts │ │ ├── isPseudoBinary.ts │ │ ├── tree │ │ ├── File.ts │ │ ├── Folder.ts │ │ ├── Node.ts │ │ ├── Tree.ts │ │ ├── blackList │ │ │ ├── BlackListAbstract.ts │ │ │ ├── BlackListDependencies.ts │ │ │ ├── BlackListKeyWordIndex.ts │ │ │ └── ExtensionFilter.ts │ │ ├── treeViewModes │ │ │ ├── TreeViewDefault.ts │ │ │ ├── TreeViewFilter.ts │ │ │ ├── TreeViewFilterNotPrune.ts │ │ │ ├── TreeViewFilterPrune.ts │ │ │ ├── TreeViewMode.ts │ │ │ └── TreeViewModeCreator.ts │ │ └── visitor │ │ │ ├── DependencyHighlighterVisitor.ts │ │ │ ├── FilterNodesByStatusVisitor.ts │ │ │ ├── HighlightNodesByStatusVisitor.ts │ │ │ ├── OnlyKeepDependencyVisitor.test.ts │ │ │ ├── OnlyKeepDependencyVisitor.ts │ │ │ └── Visitor.ts │ │ ├── utils_db.ts │ │ └── workspaceModel │ │ ├── IWorkspaceModel.ts │ │ └── WorkspaceFileModel.ts ├── renderer │ ├── App.global.scss │ ├── App.tsx │ ├── __mocks__ │ │ └── dialog-controller.ts │ ├── components │ │ ├── ConditionalLink │ │ │ └── ConditionalLink.tsx │ │ ├── Info │ │ │ └── Info.tsx │ │ ├── LicenseSelector │ │ │ └── LicenseSelector.tsx │ │ ├── Loader │ │ │ └── Loader.tsx │ │ ├── SearchBox │ │ │ └── SearchBox.tsx │ │ └── TableCellActions │ │ │ └── TableCellActions.tsx │ ├── context │ │ ├── AppProvider.tsx │ │ ├── DialogProvider.tsx │ │ ├── WorkbenchProvider.tsx │ │ └── types.ts │ ├── controllers │ │ ├── dialog-controller.ts │ │ ├── home-controller.ts │ │ └── workbench-controller.ts │ ├── features │ │ ├── about │ │ │ ├── LicensesText.tsx │ │ │ └── index.tsx │ │ ├── workbench │ │ │ ├── Workbench.scss │ │ │ ├── components │ │ │ │ ├── AppBar │ │ │ │ │ ├── AppBar.scss │ │ │ │ │ └── AppBar.tsx │ │ │ │ ├── BaseCard │ │ │ │ │ ├── BaseCard.scss │ │ │ │ │ └── BaseCard.tsx │ │ │ │ ├── Breadcrumb │ │ │ │ │ └── Breadcrumb.tsx │ │ │ │ ├── CodeViewer │ │ │ │ │ └── CodeViewer.tsx │ │ │ │ ├── ComponentCard │ │ │ │ │ ├── ComponentCard.scss │ │ │ │ │ └── ComponentCard.tsx │ │ │ │ ├── ComponentInfo │ │ │ │ │ ├── ComponentInfo.scss │ │ │ │ │ └── ComponentInfo.tsx │ │ │ │ ├── DependencyManifestFileCard │ │ │ │ │ ├── DependencyManifestFileCard.scss │ │ │ │ │ └── DependencyManifestFileCard.tsx │ │ │ │ ├── EmptyMessagePlaceholder │ │ │ │ │ └── EmptyMessagePlaceholder.tsx │ │ │ │ ├── FileToolbar │ │ │ │ │ ├── FileToolbar.scss │ │ │ │ │ └── FileToolbar.tsx │ │ │ │ ├── FileTree │ │ │ │ │ ├── FileTree.scss │ │ │ │ │ └── FileTree.tsx │ │ │ │ ├── IconComponent │ │ │ │ │ ├── IconComponent.scss │ │ │ │ │ └── IconComponent.tsx │ │ │ │ ├── InventoryCard │ │ │ │ │ ├── InventoryCard.scss │ │ │ │ │ └── InventoryCard.tsx │ │ │ │ ├── InventorySelectorDialog │ │ │ │ │ ├── InventorySelectorDialog.scss │ │ │ │ │ └── InventorySelectorDialog.tsx │ │ │ │ ├── KeywordGroupDialog │ │ │ │ │ ├── KeywordGroupDialog.scss │ │ │ │ │ └── KeywordGroupDialog.tsx │ │ │ │ ├── KeywordGroupMenu │ │ │ │ │ ├── KeywordGroupMenu.scss │ │ │ │ │ └── KeywordGroupMenu.tsx │ │ │ │ ├── Label │ │ │ │ │ ├── Label.scss │ │ │ │ │ └── Label.tsx │ │ │ │ ├── MainPanel │ │ │ │ │ └── MainPanel.tsx │ │ │ │ ├── MainSidebar │ │ │ │ │ └── MainSidebar.tsx │ │ │ │ ├── MatchCard │ │ │ │ │ ├── MatchCard.scss │ │ │ │ │ └── MatchCard.tsx │ │ │ │ ├── MatchInfoCard │ │ │ │ │ ├── MatchInfoCard.scss │ │ │ │ │ └── MatchInfoCard.tsx │ │ │ │ ├── MenuButton │ │ │ │ │ └── MenuButton.tsx │ │ │ │ ├── NoMatchFound │ │ │ │ │ ├── NoMatchFound.scss │ │ │ │ │ └── NoMatchFound.tsx │ │ │ │ ├── Pill │ │ │ │ │ ├── Pill.scss │ │ │ │ │ └── Pill.tsx │ │ │ │ ├── RecognizedCard │ │ │ │ │ ├── RecognizedCard.scss │ │ │ │ │ └── RecognizedCard.tsx │ │ │ │ ├── SearchPanel │ │ │ │ │ └── SearchPanel.tsx │ │ │ │ ├── Title │ │ │ │ │ ├── Title.scss │ │ │ │ │ └── Title.tsx │ │ │ │ ├── TreeNode │ │ │ │ │ └── TreeNode.tsx │ │ │ │ └── WorkbenchFilters │ │ │ │ │ ├── WorkbenchFilters.scss │ │ │ │ │ └── WorkbenchFilters.tsx │ │ │ ├── index.tsx │ │ │ └── pages │ │ │ │ ├── detected │ │ │ │ ├── Detected.tsx │ │ │ │ ├── components │ │ │ │ │ └── FilterSnackbar.tsx │ │ │ │ └── pages │ │ │ │ │ ├── ComponentDetail │ │ │ │ │ ├── ComponentDetail.scss │ │ │ │ │ ├── ComponentDetail.tsx │ │ │ │ │ └── components │ │ │ │ │ │ ├── ActionButton │ │ │ │ │ │ └── ActionButton.tsx │ │ │ │ │ │ ├── TabNavigation │ │ │ │ │ │ └── TabNavigation.tsx │ │ │ │ │ │ └── VersionSelector │ │ │ │ │ │ └── VersionSelector.tsx │ │ │ │ │ ├── ComponentList │ │ │ │ │ ├── Component.tsx │ │ │ │ │ ├── ComponentList.scss │ │ │ │ │ ├── ComponentList.tsx │ │ │ │ │ ├── Dependency.tsx │ │ │ │ │ └── components │ │ │ │ │ │ ├── EmptyResult │ │ │ │ │ │ └── EmptyResult.tsx │ │ │ │ │ │ ├── FileList.tsx │ │ │ │ │ │ ├── IdentifiedList.tsx │ │ │ │ │ │ ├── ScanResults.scss │ │ │ │ │ │ └── ScanResults.tsx │ │ │ │ │ ├── Dependency │ │ │ │ │ ├── Dependency.scss │ │ │ │ │ ├── Dependency.tsx │ │ │ │ │ └── components │ │ │ │ │ │ ├── ActionButton │ │ │ │ │ │ └── ActionButton.tsx │ │ │ │ │ │ ├── CodeViewSelector │ │ │ │ │ │ └── CodeViewSelector.tsx │ │ │ │ │ │ ├── DependencyTree │ │ │ │ │ │ ├── DependencyTree.scss │ │ │ │ │ │ └── DependencyTree.tsx │ │ │ │ │ │ ├── FilterSelector │ │ │ │ │ │ └── FilterSelector.tsx │ │ │ │ │ │ ├── NoLocalFile │ │ │ │ │ │ └── NoLocalFile.tsx │ │ │ │ │ │ └── TabNavigation │ │ │ │ │ │ └── TabNavigation.tsx │ │ │ │ │ ├── Editor │ │ │ │ │ ├── CodeViewerManager.ts │ │ │ │ │ ├── Editor.scss │ │ │ │ │ ├── Editor.tsx │ │ │ │ │ └── components │ │ │ │ │ │ └── NoLocalFile │ │ │ │ │ │ └── NoLocalFile.tsx │ │ │ │ │ └── FileViewer │ │ │ │ │ └── FileViewer.tsx │ │ │ │ ├── identified │ │ │ │ ├── Identified.tsx │ │ │ │ └── pages │ │ │ │ │ ├── IdentifiedList │ │ │ │ │ ├── IdentifiedList.scss │ │ │ │ │ └── IdentifiedList.tsx │ │ │ │ │ ├── InventoryDetail │ │ │ │ │ ├── InventoryDetail.scss │ │ │ │ │ ├── InventoryDetail.tsx │ │ │ │ │ └── components │ │ │ │ │ │ └── FileList.tsx │ │ │ │ │ └── InventoryList │ │ │ │ │ └── InventoryList.tsx │ │ │ │ ├── report │ │ │ │ ├── Report.tsx │ │ │ │ ├── components │ │ │ │ │ ├── CryptoChart.tsx │ │ │ │ │ ├── CryptographyCard.tsx │ │ │ │ │ ├── CryptographyDataTable.tsx │ │ │ │ │ ├── DependenciesCard.tsx │ │ │ │ │ ├── DependenciesDataTable.tsx │ │ │ │ │ ├── IdentificationProgress.tsx │ │ │ │ │ ├── LicensesChart.tsx │ │ │ │ │ ├── LicensesObligations.tsx │ │ │ │ │ ├── LicensesTable.tsx │ │ │ │ │ ├── MatchesChart.tsx │ │ │ │ │ ├── MatchesForLicense.tsx │ │ │ │ │ ├── ObligationsDataTable.tsx │ │ │ │ │ ├── OssVsOriginalProgressBar.tsx │ │ │ │ │ ├── ProgressChart.tsx │ │ │ │ │ └── VulnerabilitiesCard.tsx │ │ │ │ └── pages │ │ │ │ │ ├── crypthographies │ │ │ │ │ ├── CrypthographyReport.tsx │ │ │ │ │ ├── CryptographyReport.scss │ │ │ │ │ └── components │ │ │ │ │ │ ├── CryptoChart.tsx │ │ │ │ │ │ └── LocalCryptographyTable.tsx │ │ │ │ │ ├── scan │ │ │ │ │ ├── DetectedReport.tsx │ │ │ │ │ ├── IdentifiedReport.tsx │ │ │ │ │ ├── ScanReport.scss │ │ │ │ │ ├── ScanReport.tsx │ │ │ │ │ └── components │ │ │ │ │ │ ├── ExportButton.scss │ │ │ │ │ │ ├── ExportButton.tsx │ │ │ │ │ │ └── Navigation.tsx │ │ │ │ │ └── vulnerabilities │ │ │ │ │ ├── VulnerabilitiesReport.scss │ │ │ │ │ └── VulnerabilitiesReport.tsx │ │ │ │ └── search │ │ │ │ └── Search.tsx │ │ └── workspace │ │ │ ├── components │ │ │ └── ProxyConfigSetup.tsx │ │ │ ├── domain.ts │ │ │ ├── encode.ts │ │ │ ├── index.tsx │ │ │ ├── mappers.ts │ │ │ └── pages │ │ │ ├── Components │ │ │ ├── AddProjectButton │ │ │ │ └── AddProjectButton.tsx │ │ │ ├── CircularComponent.tsx │ │ │ ├── ProjectList.tsx │ │ │ └── WorskpaceSelector │ │ │ │ └── WorkspaceSelector.tsx │ │ │ ├── ProjectDrop │ │ │ ├── ProjectDrop.scss │ │ │ └── ProjectDrop.tsx │ │ │ ├── ProjectScan │ │ │ ├── ProjectScan.scss │ │ │ └── ProjectScan.tsx │ │ │ ├── ProjectSettings │ │ │ ├── ProjectSettings.scss │ │ │ └── ProjectSettings.tsx │ │ │ └── Workspace │ │ │ ├── Workspace.scss │ │ │ └── Workspace.tsx │ ├── hooks │ │ ├── useApi.tsx │ │ ├── useBatch.tsx │ │ ├── useContextual.tsx │ │ ├── useMode.tsx │ │ ├── usePagination.tsx │ │ └── useSearchParams.tsx │ ├── index.ejs │ ├── index.html │ ├── index.tsx │ ├── preload.d.ts │ ├── store │ │ ├── component-store │ │ │ ├── componentSlice.ts │ │ │ └── componentThunks.ts │ │ ├── dependency-store │ │ │ ├── dependencyMiddleware.ts │ │ │ ├── dependencySlice.ts │ │ │ └── dependencyThunks.ts │ │ ├── inventory-store │ │ │ ├── inventoryMiddleware.ts │ │ │ ├── inventorySlice.ts │ │ │ └── inventoryThunks.ts │ │ ├── navigation-store │ │ │ ├── navigationSlice.ts │ │ │ └── navigationThunks.ts │ │ ├── report-store │ │ │ ├── reportSlice.ts │ │ │ └── reportThunks.ts │ │ ├── rootMiddleware.ts │ │ ├── rootReducer.ts │ │ ├── store.ts │ │ ├── workbench-store │ │ │ ├── workbenchMiddleware.ts │ │ │ ├── workbenchSlice.ts │ │ │ └── workbenchThunks.ts │ │ └── workspace-store │ │ │ ├── workspaceSlice.ts │ │ │ └── workspaceThunks.ts │ ├── theme │ │ ├── Dialog.global.scss │ │ ├── Layout.global.scss │ │ ├── Main.global.scss │ │ ├── Panel.global.scss │ │ ├── SplitPane.global.scss │ │ ├── Utils.global.scss │ │ ├── fonts.scss │ │ └── variables.scss │ └── ui │ │ ├── Input.tsx │ │ └── dialog │ │ ├── AlertDialog.tsx │ │ ├── ComponentDialog.tsx │ │ ├── ComponentSearcherDialog.scss │ │ ├── ComponentSearcherDialog.tsx │ │ ├── ConfirmDialog.tsx │ │ ├── DependencyDialog.tsx │ │ ├── ImportProjectSourceDialog.tsx │ │ ├── InventoryDialog.tsx │ │ ├── LicenseDialog.tsx │ │ ├── PreLoadInventoryDialog.scss │ │ ├── PreLoadInventoryDialog.tsx │ │ ├── ProgressDialog.tsx │ │ ├── ProjectSelectorDialog.scss │ │ ├── ProjectSelectorDialog.tsx │ │ ├── ReportDialog.tsx │ │ ├── SettingsDialog.tsx │ │ └── WorkspaceAddDialog.tsx └── shared │ ├── adapters │ ├── crypto.adapter.ts │ ├── report.adapter.ts │ └── types.ts │ ├── i18n │ └── index.ts │ └── utils │ ├── FamilyToken.ts │ ├── file-utils.ts │ ├── filetree-utils.js │ ├── scan-util.ts │ ├── search-utils.ts │ └── utils.js ├── tools └── get_next_version.sh └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.erb/configs/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off", 4 | "global-require": "off", 5 | "import/no-dynamic-require": "off" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.eslint.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/no-unresolved: off, import/no-self-import: off */ 2 | 3 | module.exports = require('./webpack.config.renderer.dev').default; 4 | -------------------------------------------------------------------------------- /.erb/configs/webpack.paths.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | 3 | const rootPath = path.join(__dirname, '../..'); 4 | 5 | const dllPath = path.join(__dirname, '../dll'); 6 | 7 | const srcPath = path.join(rootPath, 'src'); 8 | const srcMainPath = path.join(srcPath, 'main'); 9 | const srcRendererPath = path.join(srcPath, 'renderer'); 10 | 11 | const releasePath = path.join(rootPath, 'release'); 12 | const appPath = path.join(releasePath, 'app'); 13 | const appPackagePath = path.join(appPath, 'package.json'); 14 | const appNodeModulesPath = path.join(appPath, 'node_modules'); 15 | const srcNodeModulesPath = path.join(srcPath, 'node_modules'); 16 | 17 | const distPath = path.join(appPath, 'dist'); 18 | const distMainPath = path.join(distPath, 'main'); 19 | const distRendererPath = path.join(distPath, 'renderer'); 20 | 21 | const buildPath = path.join(releasePath, 'build'); 22 | 23 | export default { 24 | rootPath, 25 | dllPath, 26 | srcPath, 27 | srcMainPath, 28 | srcRendererPath, 29 | releasePath, 30 | appPath, 31 | appPackagePath, 32 | appNodeModulesPath, 33 | srcNodeModulesPath, 34 | distPath, 35 | distMainPath, 36 | distRendererPath, 37 | buildPath, 38 | }; 39 | -------------------------------------------------------------------------------- /.erb/flatpak/com.scanoss.sbom-workbench.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/.erb/flatpak/com.scanoss.sbom-workbench.png -------------------------------------------------------------------------------- /.erb/flatpak/sbom-workbench-screenshot-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/.erb/flatpak/sbom-workbench-screenshot-01.png -------------------------------------------------------------------------------- /.erb/img/workbench_1.c77c358.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/.erb/img/workbench_1.c77c358.png -------------------------------------------------------------------------------- /.erb/img/workbench_1.c77c359.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/.erb/img/workbench_1.c77c359.png -------------------------------------------------------------------------------- /.erb/mocks/fileMock.js: -------------------------------------------------------------------------------- 1 | export default 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /.erb/override/example.config.override.json: -------------------------------------------------------------------------------- 1 | { 2 | "APP_NAME": "CUSTOM SBOM Analyzer", 3 | "ORGANIZATION_NAME": "My Organization", 4 | "ORGANIZATION_URL": "https://www.myorg.com", 5 | "ABOUT_MESSAGE": "This is a rebranded SBOM Workbech made for My Organization", 6 | "DEFAULT_WORKSPACE_NAME": "sbom-workspace", 7 | "FF_ENABLE_AUTO_ACCEPT_AFTER_SCAN": true, 8 | "FF_EXPORT_FORMAT_OPTIONS": ["CSV","RAW","WFP"] 9 | } 10 | -------------------------------------------------------------------------------- /.erb/override/example.package.override.js: -------------------------------------------------------------------------------- 1 | const { execSync } = require('child_process'); 2 | 3 | try { 4 | /* Replace original package.json values with rebranding values */ 5 | execSync(`npm pkg set name='sbom-workbench-custom'`); 6 | execSync(`npm pkg set productName='CUSTOM SBOM Workbench' build.productName='CUSTOM SBOM Workbench'`); 7 | execSync(`npm pkg set description='CUSTOM SBOM Workbench'`); 8 | execSync(`npm pkg set homepage='https://github.com/scanoss/sbom-workbench#readme'`); 9 | execSync(`npm pkg set bugs.url='https://github.com/scanoss/sbom-workbench#readme'`); 10 | execSync(`npm pkg set repository.url='git+https://github.com/scanoss/sbom-workbench/'`); 11 | execSync(`npm pkg set build.appId="com.custom.workbench"`); 12 | execSync('npm pkg set build.artifactName="sbom-workbench-custom-\\${version}-\\${os}-\\${arch}.\\${ext}"'); 13 | } catch (e) { 14 | console.error(`Error on rebranding\n`, e); 15 | } 16 | -------------------------------------------------------------------------------- /.erb/scripts/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off", 4 | "global-require": "off", 5 | "import/no-dynamic-require": "off", 6 | "import/no-extraneous-dependencies": "off" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.erb/scripts/check-build-exists.ts: -------------------------------------------------------------------------------- 1 | // Check if the renderer and main bundles are built 2 | import path from 'path'; 3 | import chalk from 'chalk'; 4 | import fs from 'fs'; 5 | import webpackPaths from '../configs/webpack.paths'; 6 | 7 | const mainPath = path.join(webpackPaths.distMainPath, 'main.js'); 8 | const rendererPath = path.join(webpackPaths.distRendererPath, 'renderer.js'); 9 | 10 | if (!fs.existsSync(mainPath)) { 11 | throw new Error( 12 | chalk.whiteBright.bgRed.bold( 13 | 'The main process is not built yet. Build it by running "npm run build:main"' 14 | ) 15 | ); 16 | } 17 | 18 | if (!fs.existsSync(rendererPath)) { 19 | throw new Error( 20 | chalk.whiteBright.bgRed.bold( 21 | 'The renderer process is not built yet. Build it by running "npm run build:renderer"' 22 | ) 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /.erb/scripts/check-node-env.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | export default function checkNodeEnv(expectedEnv) { 4 | if (!expectedEnv) { 5 | throw new Error('"expectedEnv" not set'); 6 | } 7 | 8 | if (process.env.NODE_ENV !== expectedEnv) { 9 | console.log( 10 | chalk.whiteBright.bgRed.bold( 11 | `"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config` 12 | ) 13 | ); 14 | process.exit(2); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.erb/scripts/check-port-in-use.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import detectPort from 'detect-port'; 3 | 4 | const port = process.env.PORT || '1212'; 5 | 6 | detectPort(port, (err, availablePort) => { 7 | if (port !== String(availablePort)) { 8 | throw new Error( 9 | chalk.whiteBright.bgRed.bold( 10 | `Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 npm start` 11 | ) 12 | ); 13 | } else { 14 | process.exit(0); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /.erb/scripts/clean.js: -------------------------------------------------------------------------------- 1 | import { rimrafSync } from 'rimraf'; 2 | import process from 'process'; 3 | import webpackPaths from '../configs/webpack.paths'; 4 | 5 | const args = process.argv.slice(2); 6 | const commandMap = { 7 | dist: webpackPaths.distPath, 8 | release: webpackPaths.releasePath, 9 | dll: webpackPaths.dllPath, 10 | }; 11 | 12 | args.forEach((x) => { 13 | const pathToRemove = commandMap[x]; 14 | if (pathToRemove !== undefined) { 15 | rimrafSync(pathToRemove); 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /.erb/scripts/delete-source-maps.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { rimrafSync } from 'rimraf'; 3 | import webpackPaths from '../configs/webpack.paths'; 4 | 5 | function deleteSourceMaps() { 6 | rimrafSync(path.join(webpackPaths.distMainPath, '*.js.map'), {glob: true}); 7 | rimrafSync(path.join(webpackPaths.distRendererPath, '*.js.map'), {glob: true}); 8 | } 9 | 10 | module.exports = deleteSourceMaps; 11 | -------------------------------------------------------------------------------- /.erb/scripts/electron-rebuild.js: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import fs from 'fs'; 3 | import { dependencies } from '../../release/app/package.json'; 4 | import webpackPaths from '../configs/webpack.paths'; 5 | 6 | if ( 7 | Object.keys(dependencies || {}).length > 0 && 8 | fs.existsSync(webpackPaths.appNodeModulesPath) 9 | ) { 10 | const electronRebuildCmd = 11 | '../../node_modules/.bin/electron-rebuild --force --types prod,dev,optional --module-dir .'; 12 | const cmd = 13 | process.platform === 'win32' 14 | ? electronRebuildCmd.replace(/\//g, '\\') 15 | : electronRebuildCmd; 16 | execSync(cmd, { 17 | cwd: webpackPaths.appPath, 18 | stdio: 'inherit', 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /.erb/scripts/link-modules.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import webpackPaths from '../configs/webpack.paths'; 3 | 4 | const { srcNodeModulesPath } = webpackPaths; 5 | const { appNodeModulesPath } = webpackPaths; 6 | 7 | if (!fs.existsSync(srcNodeModulesPath) && fs.existsSync(appNodeModulesPath)) { 8 | fs.symlinkSync(appNodeModulesPath, srcNodeModulesPath, 'junction'); 9 | } 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Coverage directory used by tools like istanbul 11 | coverage 12 | .eslintcache 13 | 14 | # Dependency directory 15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 16 | node_modules 17 | 18 | # OSX 19 | .DS_Store 20 | 21 | release/app/dist 22 | release/build 23 | .erb/dll 24 | 25 | .idea 26 | npm-debug.log.* 27 | *.css.d.ts 28 | *.sass.d.ts 29 | *.scss.d.ts 30 | 31 | # eslint ignores hidden directories by default: 32 | # https://github.com/eslint/eslint/issues/8429 33 | !.erb 34 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.exe binary 3 | *.png binary 4 | *.jpg binary 5 | *.jpeg binary 6 | *.ico binary 7 | *.icns binary 8 | *.eot binary 9 | *.otf binary 10 | *.ttf binary 11 | *.woff binary 12 | *.woff2 binary 13 | -------------------------------------------------------------------------------- /.github/actions/setup-build/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Setup and Build' 2 | description: 'Common setup and build steps for all platforms' 3 | 4 | inputs: 5 | token: 6 | description: 'GitHub token for accessing private repositories' 7 | required: true 8 | 9 | runs: 10 | using: "composite" 11 | steps: 12 | 13 | - name: Get crypto rules 14 | uses: actions/checkout@v3 15 | with: 16 | repository: ${{env.CRYPTO_RULES_SOURCE_DIR }} 17 | path: platform-deployment 18 | token: ${{ inputs.token }} 19 | ref: main 20 | 21 | - name: Copy crypto rules 22 | shell: bash 23 | run: | 24 | cp ${{ env.CRYPTO_RULES_PATH }}/${{ env.CRYPTO_LIBRARIES_SOURCE }} ${{ env.TARGET_DIR }}/${{ env.CRYPTO_LIBRARIES_TARGET }} 25 | cp ${{ env.CRYPTO_RULES_PATH }}/${{ env.CRYPTO_ALGORITHMS_SOURCE }} ${{ env.TARGET_DIR }}/${{ env.CRYPTO_ALGORITHMS_TARGET }} 26 | 27 | - name: Install Node and NPM 28 | uses: actions/setup-node@v3 29 | with: 30 | node-version: 22 31 | cache: npm 32 | 33 | - name: Install dependencies 34 | shell: bash 35 | run: | 36 | npm install --legacy-peer-deps 37 | npm run postinstall 38 | npm run build 39 | -------------------------------------------------------------------------------- /.github/workflows/reuse.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Free Software Foundation Europe e.V. 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | name: REUSE Compliance Check 6 | 7 | on: [push, pull_request] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: REUSE Compliance Check 15 | uses: fsfe/reuse-action@v1.1.1 16 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | pull_request: 8 | branches: 9 | - '*' 10 | 11 | jobs: 12 | release: 13 | runs-on: ${{ matrix.os }} 14 | 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest] 18 | 19 | steps: 20 | - name: Check out Git repository 21 | uses: actions/checkout@v4 22 | 23 | - name: Install Node.js, NPM and Yarn 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: 22 27 | 28 | - name: npm install 29 | run: | 30 | npm install --frozen-lockfile --legacy-peer-deps 31 | 32 | - name: npm test 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | run: | 36 | npm run package 37 | npm run test 38 | -------------------------------------------------------------------------------- /.github/workflows/version-tag.yml: -------------------------------------------------------------------------------- 1 | name: Repo Version Tagging 2 | # This workflow will read the version details from the repo and apply a branch 3 | 4 | on: 5 | workflow_dispatch: 6 | inputs: 7 | run_for_real: 8 | required: true 9 | default: false 10 | type: boolean 11 | description: "Apply next tag (or Dry Run)" 12 | 13 | concurrency: production 14 | 15 | jobs: 16 | version-tagging: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | fetch-depth: '0' 22 | token: ${{ secrets.SC_GH_TAG_TOKEN }} 23 | 24 | - name: Determine Tag 25 | id: taggerVersion 26 | run: | 27 | app_version=$(tools/get_next_version.sh) 28 | echo "New Proposed tag: $app_version" 29 | echo "package_app_version=$app_version" >> $GITHUB_ENV 30 | 31 | - name: Apply Tag 32 | if: ${{ inputs.run_for_real }} 33 | id: taggerApply 34 | run: | 35 | echo "Applying tag ${{env.package_app_version}} ..." 36 | git tag "${{env.package_app_version}}" 37 | echo "Pushing changes..." 38 | git push --tags 39 | 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Coverage directory used by tools like istanbul 11 | coverage 12 | .eslintcache 13 | 14 | # Dependency directory 15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 16 | node_modules 17 | 18 | # OSX 19 | .DS_Store 20 | 21 | release/app/dist 22 | release/build 23 | .erb/dll 24 | 25 | .idea 26 | npm-debug.log.* 27 | *.css.d.ts 28 | *.sass.d.ts 29 | *.scss.d.ts 30 | 31 | # Workbench 32 | *.worker.js 33 | *.worker.js.LICENSE.txt 34 | override.json 35 | 36 | # Documentation 37 | build/ -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the OS, Python version and other tools you might need 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: '3.12' 13 | # You can also specify other tool versions: 14 | # nodejs: "19" 15 | # rust: "1.64" 16 | # golang: "1.19" 17 | 18 | # Build documentation in the "docs/" directory with Sphinx 19 | sphinx: 20 | configuration: docs/source/conf.py 21 | 22 | # Optionally build your docs in additional formats such as PDF and ePub 23 | # formats: 24 | # - pdf 25 | # - epub 26 | 27 | # Optional but recommended, declare the Python requirements required 28 | # to build your documentation 29 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 30 | python: 31 | install: 32 | - requirements: docs/source/requirements-docs.txt 33 | -------------------------------------------------------------------------------- /.reuse/dep5: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: SBOM Workbench 3 | Upstream-Contact: SCANOSS 4 | Source: https://github.com/scanoss/sbom-workbench 5 | 6 | 7 | Files: src/* assets/* .* *.md *.json *.js *.lock 8 | Copyright: 2021 SBOM Workbench 9 | License: GPL-2.0-only 10 | 11 | Files: .erb/* .vscode/* 12 | Copyright: Electron React Boilerplate 13 | License: MIT 14 | 15 | Files: assets/fonts/Inter-* 16 | Copyright: Rasmus 17 | License: OFL-1.1 18 | 19 | Files: assets/fonts/SpaceMono-* 20 | Copyright: Colophon 21 | License: OFL-1.1 22 | 23 | Files: docs/* .readthedocs.yaml 24 | Copyright: 2024 SBOM Workbench 25 | License: GPL-2.0-only 26 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "EditorConfig.EditorConfig"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Electron: Main", 6 | "type": "node", 7 | "request": "launch", 8 | "protocol": "inspector", 9 | "runtimeExecutable": "npm", 10 | "runtimeArgs": ["run", "start"], 11 | "env": { 12 | "MAIN_ARGS": "--inspect=5858 --remote-debugging-port=9223" 13 | } 14 | }, 15 | { 16 | "name": "Electron: Renderer", 17 | "type": "chrome", 18 | "request": "attach", 19 | "port": 9223, 20 | "webRoot": "${workspaceFolder}", 21 | "timeout": 15000 22 | } 23 | ], 24 | "compounds": [ 25 | { 26 | "name": "Electron: All", 27 | "configurations": ["Electron: Main", "Electron: Renderer"] 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | ".eslintrc": "jsonc", 4 | ".prettierrc": "jsonc", 5 | ".eslintignore": "ignore" 6 | }, 7 | 8 | "eslint.validate": [ 9 | "javascript", 10 | "javascriptreact", 11 | "html", 12 | "typescriptreact" 13 | ], 14 | 15 | "javascript.validate.enable": false, 16 | "javascript.format.enable": false, 17 | "typescript.format.enable": false, 18 | 19 | "search.exclude": { 20 | ".git": true, 21 | ".eslintcache": true, 22 | ".erb/dll": true, 23 | "release/{build,app/dist}": true, 24 | "node_modules": true, 25 | "npm-debug.log.*": true, 26 | "test/**/__snapshots__": true, 27 | "package-lock.json": true, 28 | "*.{css,sass,scss}.d.ts": true 29 | }, 30 | "i18next.i18nPaths": "c:/Users/TUDAI/Documents/scanoss/audit-workbench/assets/i18n,c:/Users/TUDAI/Documents/scanoss/audit-workbench/src/shared/i18n,c:/Users/TUDAI/Documents/scanoss/audit-workbench/release/build/win-unpacked/locales,c:/Users/TUDAI/Documents/scanoss/audit-workbench/release/build/win-unpacked/resources/assets/i18n" 31 | } 32 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /appimage-fix.js: -------------------------------------------------------------------------------- 1 | const child_process = require('child_process'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | 5 | const appName = 'scanoss-workbench'; 6 | 7 | function isLinux(targets) { 8 | const re = /AppImage|snap|deb|rpm|freebsd|pacman/i; 9 | return !!targets.find((target) => re.test(target.name)); 10 | } 11 | 12 | async function afterPack({ targets, appOutDir }) { 13 | if (!isLinux(targets)) return; 14 | const scriptPath = path.join(appOutDir, appName); 15 | const script = `#!/bin/bash\n"\${BASH_SOURCE%/*}"/${appName}.bin "$@" --no-sandbox`; 16 | new Promise((resolve) => { 17 | const child = child_process.exec(`mv ${appName} ${appName}.bin`, { cwd: appOutDir }); 18 | child.on('exit', () => { 19 | resolve(); 20 | }); 21 | }).then(() => { 22 | fs.writeFileSync(scriptPath, script); 23 | child_process.exec(`chmod +x ${appName}`, { cwd: appOutDir }); 24 | }); 25 | } 26 | 27 | module.exports = afterPack; 28 | -------------------------------------------------------------------------------- /assets/assets.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' { 2 | const content: any; 3 | export default content; 4 | } 5 | 6 | declare module '*.png' { 7 | const content: any; 8 | export default content; 9 | } 10 | 11 | declare module '*.jpg' { 12 | const content: any; 13 | export default content; 14 | } 15 | -------------------------------------------------------------------------------- /assets/data/ComponentCatalogPackages.ts: -------------------------------------------------------------------------------- 1 | export const packages = [ 2 | 'angular', 3 | 'apache', 4 | 'apple', 5 | 'bitbucket', 6 | 'cargo', 7 | 'composer', 8 | 'cpan', 9 | 'deb', 10 | 'drupal', 11 | 'eclipse', 12 | 'gem', 13 | 'gitee', 14 | 'github', 15 | 'gitlab', 16 | 'gnome', 17 | 'gnu', 18 | 'golang', 19 | 'googlesource', 20 | 'isc', 21 | 'java2s', 22 | 'jquery', 23 | 'kernel', 24 | 'maven', 25 | 'mozilla', 26 | 'nasm', 27 | 'nmap', 28 | 'npm', 29 | 'nuget', 30 | 'postgresql', 31 | 'pypi', 32 | 'rpm', 33 | 'slf4j', 34 | 'sourceforge', 35 | 'stackoverflow', 36 | 'sudo', 37 | 'videolan', 38 | 'wordpress', 39 | 'zlib', 40 | ]; 41 | -------------------------------------------------------------------------------- /assets/data/scanoss-crypto-library-rules.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /assets/entitlements.mac.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-unsigned-executable-memory 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.cs.disable-library-validation 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /assets/fonts/Inter-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/assets/fonts/Inter-Black.ttf -------------------------------------------------------------------------------- /assets/fonts/Inter-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/assets/fonts/Inter-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/Inter-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/assets/fonts/Inter-ExtraBold.ttf -------------------------------------------------------------------------------- /assets/fonts/Inter-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/assets/fonts/Inter-ExtraLight.ttf -------------------------------------------------------------------------------- /assets/fonts/Inter-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/assets/fonts/Inter-Light.ttf -------------------------------------------------------------------------------- /assets/fonts/Inter-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/assets/fonts/Inter-Medium.ttf -------------------------------------------------------------------------------- /assets/fonts/Inter-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/assets/fonts/Inter-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/Inter-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/assets/fonts/Inter-SemiBold.ttf -------------------------------------------------------------------------------- /assets/fonts/Inter-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/assets/fonts/Inter-Thin.ttf -------------------------------------------------------------------------------- /assets/fonts/Inter-VariableFont_slnt,wght.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/assets/fonts/Inter-VariableFont_slnt,wght.ttf -------------------------------------------------------------------------------- /assets/fonts/SpaceMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/assets/fonts/SpaceMono-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/SpaceMono-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/assets/fonts/SpaceMono-BoldItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/SpaceMono-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/assets/fonts/SpaceMono-Italic.ttf -------------------------------------------------------------------------------- /assets/fonts/SpaceMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/assets/fonts/SpaceMono-Regular.ttf -------------------------------------------------------------------------------- /assets/i18n/en/Table.json: -------------------------------------------------------------------------------- 1 | { 2 | "Header": { 3 | "Name": "Name", 4 | "Date": "Date", 5 | "TotalFiles": "Total Files", 6 | "Component": "Component", 7 | "Severity": "Severity", 8 | "CVE": "CVE", 9 | "Source": "Source", 10 | "Published": "Published", 11 | "Modified": "Modified", 12 | "Vendor": "Vendor", 13 | "Version": "Version", 14 | "License": "License", 15 | "Copyleft": "Copyleft", 16 | "IncompatibleLicenses": "Incompatible Licenses", 17 | "PURL": "PURL", 18 | "URL": "URL", 19 | "LocalFile": "Local File", 20 | "Algorithm": "Algorithm", 21 | "Strength ": "Strength", 22 | "File": "File", 23 | "Dependencies ": "Dependencies", 24 | "Cryptography": "Cryptography", 25 | "Type": "Type", 26 | "Detected": "Detected" 27 | }, 28 | "Version": "Version", 29 | "License": "License" 30 | } 31 | -------------------------------------------------------------------------------- /assets/i18n/es/Table.json: -------------------------------------------------------------------------------- 1 | { 2 | "Header": { 3 | "Name": "Nombre", 4 | "Date": "Fecha", 5 | "TotalFiles": "Cantidad de ficheros", 6 | "Component": "Componente", 7 | "Severity": "Severidad", 8 | "CVE": "CVE", 9 | "Source": "Fuente", 10 | "Published": "Publicado", 11 | "Modified": "Modificado", 12 | "Vendor": "Proveedor", 13 | "Version": "Versión", 14 | "License": "Licencia", 15 | "Copyleft": "Copyleft", 16 | "IncompatibleLicenses": "Licencias incompatibles", 17 | "PURL": "PURL", 18 | "URL": "URL", 19 | "LocalFile": "Fichero local", 20 | "Algorithm": "Algoritmo", 21 | "Strength ": "Strength", 22 | "File": "Fichero", 23 | "Dependencies ": "Dependencias", 24 | "Cryptography": "Criptografia", 25 | "Type": "Tipo", 26 | "Detected": "Detectado" 27 | }, 28 | "Version": "Versión", 29 | "License": "Licencia" 30 | } 31 | -------------------------------------------------------------------------------- /assets/i18n/jp/Table.json: -------------------------------------------------------------------------------- 1 | { 2 | "Header": { 3 | "Name": "名前", 4 | "Date": "日付", 5 | "TotalFiles": "全ファイル", 6 | "Component": "コンポーネント", 7 | "Severity": "深刻度合", 8 | "CVE": "CVE", 9 | "Source": "ソース", 10 | "Published": "Published", 11 | "Modified": "Modified", 12 | "Vendor": "ベンダー", 13 | "Version": "バージョン", 14 | "License": "ライセンス", 15 | "Copyleft": "コピーレフト", 16 | "IncompatibleLicenses": "互換性のないライセンス", 17 | "PURL": "PURL", 18 | "URL": "URL", 19 | "LocalFile": "ローカルファイル", 20 | "Algorithm": "Algorithm", 21 | "Type": "Type", 22 | "Detected": "Detected" 23 | }, 24 | "Version": "バージョン", 25 | "License": "ライセンス" 26 | } 27 | -------------------------------------------------------------------------------- /assets/i18n/zh/AppMenu.json: -------------------------------------------------------------------------------- 1 | { 2 | "File": "&文件", 3 | "NewProject": "&新项目", 4 | "ImportProjectWithSource": "&Import project with source", 5 | "ImportProject": "&导入项目", 6 | "Settings": "&设置", 7 | "Close": "&关闭", 8 | "ManageSearchIndexGroups": "&Manage search index groups", 9 | "Edit": "&编辑", 10 | "View": "&查看", 11 | "Reload": "&重新加载", 12 | "ToggleFullScreen": "切换全屏", 13 | "ToggleDeveloperTools": "切换开发人员工具", 14 | "Help": "帮助", 15 | "About": "关于", 16 | "Quit": "退出", 17 | "AcceptAllDependencies": "接受所有依赖。", 18 | "DismissAllDependencies": "取消所有依赖。", 19 | "RestoreAllDependencies": "恢复所有依赖。", 20 | "MarkFileAsOriginal": "将文件标记为自研文件。", 21 | "RestoreFile": "恢复文件", 22 | "AcceptAll_nofilter": "接受全部", 23 | "AcceptAll_filter": "接受所有过滤的文件。", 24 | "IdentifyAllAs_nofilter": "将所有文件标识为……", 25 | "IdentifyAllAs_filter": "将所有筛选的文件标识为……", 26 | "MarkAllAsOriginal_nofilter": "将所有文件标记为自研文件", 27 | "MarkAllAsOriginal_filter": "将所有过滤后的文件标记为自研文件", 28 | "RestoreAll_nofilter": "恢复所有文件", 29 | "RestoreAll_filter": "恢复所有过滤的文件", 30 | "ExpandCollapse": "展开/折叠", 31 | "ExpandAll": "展开所有", 32 | "ExpandToMatches": "展开以匹配", 33 | "CollapseAll": "折叠所有", 34 | "IdentifySelectedAs": "将所选文件标识为……", 35 | "MarkSelectedAsOriginal": "将所选文件标记为自研文件", 36 | "OpenTranslationManagement": "打开翻译管理" 37 | } 38 | -------------------------------------------------------------------------------- /assets/i18n/zh/Button.json: -------------------------------------------------------------------------------- 1 | { 2 | "NewProject": "新项目", 3 | "ImportProject": "导入项目", 4 | "ImportProjectWithSource": "Import project with source", 5 | "Continue": "继续", 6 | "Save": "保存", 7 | "Cancel": "取消", 8 | "Close": "关闭", 9 | "Accept": "接受", 10 | "Open": "打开", 11 | "OK": "OK", 12 | "Identify": "识别", 13 | "IdentifyAllWithCount": "识别全部 ({{count}})", 14 | "MarkAllAsOriginalWithCount": "标记所有为自研 ({{count}})", 15 | "RestoreAllWithCount": "恢复所有 ({{count}})", 16 | "AcceptAllWithCount": "接受所有 ({{count}})", 17 | "DismissAllPendingWithCount": "取消所有待定 ({{count}})", 18 | "ClearFilters": "清除过滤器", 19 | "ShowMore": "展示更多", 20 | "MoreDetails": "更多细节", 21 | "PostToSbomLedger": "发送到SBOM ledger", 22 | "StartIdentification": "开始识别", 23 | "Detected": "已检测", 24 | "Identified": "已识别", 25 | "Export": "导出", 26 | "ExportWithLabel": "导出 {{label}}", 27 | "PAUSE": "暂停", 28 | "Create": "创建", 29 | "StandarSearch": "标准搜索", 30 | "AdvancedSearch": "高级搜索", 31 | "AddToTheCatalog": "添加到目录", 32 | "Add": "添加", 33 | "AcceptAll": "接受全部", 34 | "DismissAll": "取消全部", 35 | "RestoreAll": "恢复全部", 36 | "Delete&Scan": "删除和扫描", 37 | "Delete": "删除", 38 | "Keep": "保留", 39 | "Overwrite": "覆盖", 40 | "Yes": "是" 41 | } 42 | -------------------------------------------------------------------------------- /assets/i18n/zh/Table.json: -------------------------------------------------------------------------------- 1 | { 2 | "Header": { 3 | "Name": "名称", 4 | "Date": "日期", 5 | "TotalFiles": "全部文件", 6 | "Component": "组件", 7 | "Severity": "严重程度", 8 | "CVE": "CVE", 9 | "Source": "来源", 10 | "Published": "已发布", 11 | "Modified": "已修改", 12 | "Vendor": "提供者", 13 | "Version": "版本", 14 | "License": "许可证", 15 | "Copyleft": "Copyleft", 16 | "IncompatibleLicenses": "不兼容的许可证", 17 | "PURL": "PURL", 18 | "URL": "URL", 19 | "Algorithm": "Algorithm", 20 | "Type": "Type", 21 | "Detected": "Detected" 22 | }, 23 | "Version": "版本", 24 | "License": "许可证" 25 | } 26 | -------------------------------------------------------------------------------- /assets/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/assets/icon.icns -------------------------------------------------------------------------------- /assets/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/assets/icon.ico -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/assets/icon.png -------------------------------------------------------------------------------- /assets/icons/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/assets/icons/1024x1024.png -------------------------------------------------------------------------------- /assets/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/assets/icons/128x128.png -------------------------------------------------------------------------------- /assets/icons/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/assets/icons/16x16.png -------------------------------------------------------------------------------- /assets/icons/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/assets/icons/24x24.png -------------------------------------------------------------------------------- /assets/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/assets/icons/256x256.png -------------------------------------------------------------------------------- /assets/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/assets/icons/32x32.png -------------------------------------------------------------------------------- /assets/icons/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/assets/icons/48x48.png -------------------------------------------------------------------------------- /assets/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/assets/icons/512x512.png -------------------------------------------------------------------------------- /assets/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/assets/icons/64x64.png -------------------------------------------------------------------------------- /assets/icons/96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/assets/icons/96x96.png -------------------------------------------------------------------------------- /assets/imgs/filter-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/logos/scanoss_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/assets/logos/scanoss_white.png -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/source/SCANOSSDocsLogo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/docs/source/SCANOSSDocsLogo.jpg -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | # -- Project information ----------------------------------------------------- 7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 8 | 9 | project = 'SBOM Workbench' 10 | copyright = '2024, Scan Open Source Solutions SL' 11 | author = 'Scan Open Source Solutions SL' 12 | 13 | # -- General configuration --------------------------------------------------- 14 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 15 | 16 | extensions = ['sphinx_rtd_theme'] 17 | 18 | templates_path = ['_templates'] 19 | exclude_patterns = [] 20 | 21 | 22 | 23 | # -- Options for HTML output ------------------------------------------------- 24 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 25 | 26 | html_theme = 'sphinx_rtd_theme' 27 | html_static_path = ['_static'] 28 | html_logo = 'scanosslogo.jpg' 29 | -------------------------------------------------------------------------------- /docs/source/detected-first.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/docs/source/detected-first.png -------------------------------------------------------------------------------- /docs/source/detected-last.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/docs/source/detected-last.png -------------------------------------------------------------------------------- /docs/source/export-sbom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/docs/source/export-sbom.png -------------------------------------------------------------------------------- /docs/source/home-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/docs/source/home-screenshot.png -------------------------------------------------------------------------------- /docs/source/identified.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/docs/source/identified.png -------------------------------------------------------------------------------- /docs/source/project-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/docs/source/project-settings.png -------------------------------------------------------------------------------- /docs/source/reports-first.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/docs/source/reports-first.png -------------------------------------------------------------------------------- /docs/source/reports-last.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/docs/source/reports-last.png -------------------------------------------------------------------------------- /docs/source/requirements-docs.txt: -------------------------------------------------------------------------------- 1 | sphinx_rtd_theme -------------------------------------------------------------------------------- /docs/source/scanosslogo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/docs/source/scanosslogo.jpg -------------------------------------------------------------------------------- /docs/source/workbench_1.c77c358.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/docs/source/workbench_1.c77c358.png -------------------------------------------------------------------------------- /release/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scanoss-workbench", 3 | "version": "1.18.3", 4 | "description": "Desktop version to use SCANOSS OSS in your projects", 5 | "license": "GPL-2.0-only", 6 | "author": { 7 | "name": "SCANOSS", 8 | "email": "info@scanoss.com", 9 | "url": "https://www.scanoss.com/" 10 | }, 11 | "main": "./dist/main/main.js", 12 | "scripts": { 13 | "electron-rebuild": "node -r ts-node/register ../../.erb/scripts/electron-rebuild.js", 14 | "postinstall": "npm run electron-rebuild && npm run link-modules", 15 | "link-modules": "node -r ts-node/register ../../.erb/scripts/link-modules.ts" 16 | }, 17 | "dependencies": { 18 | "@cyclonedx/cyclonedx-library": "^6.10.0", 19 | "flexsearch": "^0.8.151", 20 | "sqlite3": "^5.1.6" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/__mocks__/original-fs.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/src/__mocks__/original-fs.js -------------------------------------------------------------------------------- /src/__tests__/components/ProjectList.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { configure } from '@testing-library/react'; 3 | import renderer from 'react-test-renderer'; 4 | import ProjectList from '../../renderer/features/workspace/pages/Components/ProjectList'; 5 | 6 | const props = { 7 | projects: [], 8 | searchQuery: '', 9 | onProjectClick: jest.fn(), 10 | onProjectDelete: jest.fn(), 11 | onProjectRestore: jest.fn(), 12 | onProjectCreate: jest.fn(), 13 | onProjectImport: jest.fn(), 14 | onProjectExport: jest.fn(), 15 | onProjectRescan: jest.fn(), 16 | }; 17 | 18 | describe('ProjectList', () => { 19 | it('should render an empty container', () => { 20 | const component = renderer.create(); 21 | const tree = component.toJSON(); 22 | expect(tree).toMatchSnapshot(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/__tests__/components/__snapshots__/ProjectList.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ProjectList should render an empty container 1`] = ` 4 |
7 |
10 |

11 | Common:NoProjectsFound 12 |

13 |

14 |

15 |
16 | `; 17 | -------------------------------------------------------------------------------- /src/api/Response.ts: -------------------------------------------------------------------------------- 1 | export interface ResponseData { 2 | data?: any; 3 | message?: string; 4 | } 5 | 6 | export enum ResponseStatus { 7 | OK = 'ok', 8 | FAIL = 'fail', 9 | } 10 | 11 | export class Response { 12 | static ok(response: ResponseData = null) { 13 | return { 14 | status: ResponseStatus.OK, 15 | ...response, 16 | }; 17 | } 18 | 19 | static fail(response: ResponseData = null) { 20 | return { 21 | status: ResponseStatus.FAIL, 22 | ...response, 23 | }; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/api/handlers/cryptography.handler.ts: -------------------------------------------------------------------------------- 1 | import log from 'electron-log'; 2 | import api from '../api'; 3 | import { cryptographyService } from '../../main/services/CryptographyService'; 4 | import { IpcChannels } from '../ipc-channels'; 5 | import { Response } from '../Response'; 6 | import { CryptographyGetAllDTO } from '../dto'; 7 | 8 | api.handle(IpcChannels.CRYPTOGRAPHY_UPDATE, async (event) => { 9 | try { 10 | await cryptographyService.update(); 11 | return Response.ok({ message: 'Cryptography updated successfully'}); 12 | } catch (error: any) { 13 | log.error('[Cryptography Update]: ', error); 14 | return Response.fail({ message: error.message }); 15 | } 16 | }); 17 | 18 | api.handle(IpcChannels.CRYPTOGRAPHY_GET_ALL, async (event, { type }: CryptographyGetAllDTO) => { 19 | try { 20 | const data = await cryptographyService.getAll(type); 21 | return Response.ok({ message: 'Cryptography retrieved successfully', data }); 22 | } catch (error: any) { 23 | log.error('[Cryptography Get All]: ', error); 24 | return Response.fail({ message: error.message }); 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /src/api/handlers/formats.handler.ts: -------------------------------------------------------------------------------- 1 | import log from 'electron-log'; 2 | import { NewExportDTO } from 'api/dto'; 3 | import api from '../api'; 4 | import { IpcChannels } from '../ipc-channels'; 5 | import { Response } from '../Response'; 6 | import { Export } from '../../main/task/export/Export'; 7 | 8 | 9 | const pathLib = require('path'); 10 | 11 | const crypto = require('crypto'); 12 | 13 | api.handle(IpcChannels.EXPORT, async (_event, params: NewExportDTO) => { 14 | try { 15 | const exportTask = new Export(); 16 | exportTask.setFormat(params); 17 | const exportResults = await exportTask.run(params.path); 18 | return Response.ok({ message: 'File exported successfully', data: exportResults }); 19 | } catch (e: any) { 20 | log.error(`Unable to export: ${params.format}`, e, params); 21 | return Response.fail({ message: e.message }); 22 | } 23 | }); 24 | 25 | -------------------------------------------------------------------------------- /src/api/handlers/results.handler.ts: -------------------------------------------------------------------------------- 1 | import api from '../api'; 2 | import { IpcChannels } from '../ipc-channels'; 3 | import { resultService } from '../../main/services/ResultService'; 4 | 5 | api.handle(IpcChannels.RESULTS_GET, async (event, arg: string) => { 6 | const result = await resultService.getFromPath(arg); 7 | if (result) 8 | return { 9 | status: 'ok', 10 | message: 'Results succesfully retrieved', 11 | data: result, 12 | }; 13 | return { status: 'error', message: 'Files were not successfully retrieved' }; 14 | }); 15 | -------------------------------------------------------------------------------- /src/api/handlers/search.handler.ts: -------------------------------------------------------------------------------- 1 | import log from 'electron-log'; 2 | import { IpcChannels } from '../ipc-channels'; 3 | import { SearchTask } from '../../main/task/search/searchTask/SearchTask'; 4 | import { ISearchTask } from '../../main/task/search/searchTask/ISearchTask'; 5 | import { ipcMain } from 'electron'; 6 | 7 | let search = null; 8 | 9 | ipcMain.on(IpcChannels.SEARCH_ENGINE_SEARCH, async (event, params: ISearchTask) => { 10 | if (search) { 11 | search.finish(); 12 | } 13 | search = new SearchTask(); 14 | search 15 | .run(params) 16 | .then((response) => { 17 | search = null; 18 | event.sender.send(IpcChannels.SEARCH_ENGINE_SEARCH_RESPONSE, response); 19 | return true; 20 | }) 21 | .catch((error: Error) => { 22 | log.error('[SEARCH ENGINE]: ', error.message); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/api/handlers/userSetting.handler.ts: -------------------------------------------------------------------------------- 1 | import api from '../api'; 2 | import { Response } from '../Response'; 3 | import { IpcChannels } from '../ipc-channels'; 4 | import { IWorkspaceCfg } from '../types'; 5 | import { userSettingService } from '../../main/services/UserSettingService'; 6 | 7 | api.handle(IpcChannels.USER_SETTING_SET, async (event, conf: Partial) => { 8 | try { 9 | const settings = userSettingService.set(conf); 10 | await userSettingService.save(); 11 | return Response.ok({ message: 'Node from path retrieve succesfully', data: settings }); 12 | } catch (e: any) { 13 | return Response.fail({ message: e.message }); 14 | } 15 | }); 16 | 17 | api.handle(IpcChannels.USER_SETTING_GET, async (event) => { 18 | try { 19 | const settings: Partial = userSettingService.get(); 20 | return Response.ok({ message: 'Node from path retrieve succesfully', data: settings }); 21 | } catch (e: any) { 22 | return Response.fail({ message: e.message }); 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /src/api/handlers/vulnerability.handler.ts: -------------------------------------------------------------------------------- 1 | import { VulnerabilitiesGetAllDTO } from '@api/dto'; 2 | import log from 'electron-log'; 3 | import { vulnerabilityService } from '../../main/services/VulnerabilityService'; 4 | import { IpcChannels } from '../ipc-channels'; 5 | import { Response } from '../Response'; 6 | import api from '../api'; 7 | 8 | api.handle(IpcChannels.VULNERABILITY_GET_ALL, async (event, { type }: VulnerabilitiesGetAllDTO) => { 9 | try { 10 | const vulnerabilities = await vulnerabilityService.getAll(type); 11 | return Response.ok({ message: 'Vulnerabilities fetched successfully', data: vulnerabilities }); 12 | } catch (error: any) { 13 | log.error('[ Vulnerability get all ]: ', error); 14 | return Response.fail({ message: error.message }); 15 | } 16 | }); 17 | 18 | api.handle(IpcChannels.VULNERABILITY_UPDATE, async (event) => { 19 | try { 20 | const data = await vulnerabilityService.update(); 21 | return Response.ok({ message: 'Vulnerabilities updated successfully', data }); 22 | } catch (error: any) { 23 | log.error('[ Vulnerability update ]: ', error); 24 | return Response.fail({ message: error.message }); 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /src/api/middlewares/refresh.middleware.ts: -------------------------------------------------------------------------------- 1 | import { modelProvider } from '../../main/services/ModelProvider'; 2 | import { workspace } from '../../main/workspace/Workspace'; 3 | 4 | /** 5 | * Middleware for refreshing the lock timer on the currently opened project. 6 | * 7 | * It ensures that the lock on the project remains active, preventing other 8 | * or users from accessing it concurrently. 9 | */ 10 | export async function refreshMiddleware() { 11 | try { 12 | await modelProvider.workspace.openDb(); 13 | 14 | const p = workspace.getOpenedProjects()[0]; 15 | const lock = await modelProvider.workspace.lock.updateTimer({ projectPath: p.metadata.getName() }); 16 | } finally { 17 | await modelProvider.workspace.destroy(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/api/middlewares/unlock.middleware.ts: -------------------------------------------------------------------------------- 1 | import os from 'os'; 2 | import { modelProvider } from '../../main/services/ModelProvider'; 3 | import { workspace } from '../../main/workspace/Workspace'; 4 | 5 | /** 6 | * Middleware for unlocking a project in the workspace. 7 | * This function attempts to unlock the opened project. 8 | */ 9 | export async function unlockMiddleware() { 10 | try { 11 | await modelProvider.workspace.openDb(); 12 | 13 | const p = workspace.getOpenedProjects()[0]; 14 | if (!p) return; 15 | 16 | const { username } = os.userInfo(); 17 | const hostname = os.hostname(); 18 | 19 | const projectLock = await modelProvider.workspace.lock.get(p.getProjectName(), username, hostname); 20 | 21 | // Unlock project only if user has assigned the project 22 | if (projectLock) { 23 | await modelProvider.workspace.lock.delete(p.getProjectName(), username, hostname); 24 | } 25 | } finally { 26 | await modelProvider.workspace.destroy(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/api/services/base.service.ts: -------------------------------------------------------------------------------- 1 | import { ResponseStatus } from '../Response'; 2 | 3 | export class BaseService { 4 | public response(response) { 5 | if (response.status === ResponseStatus.FAIL) { 6 | throw new Error(response.message); 7 | } 8 | return response.data; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/api/services/cryptography.service.ts: -------------------------------------------------------------------------------- 1 | import { IpcChannels } from '@api/ipc-channels'; 2 | import { CryptographyGetAllDTO } from '@api/dto'; 3 | import { CryptographyResponseDTO } from '@api/types'; 4 | import { BaseService } from './base.service'; 5 | 6 | class CryptographyService extends BaseService { 7 | public async update(): Promise { 8 | const response = await window.electron.ipcRenderer.invoke(IpcChannels.CRYPTOGRAPHY_UPDATE); 9 | return this.response(response); 10 | } 11 | 12 | public async getAll(sourceType: CryptographyGetAllDTO): Promise { 13 | const response = await window.electron.ipcRenderer.invoke(IpcChannels.CRYPTOGRAPHY_GET_ALL, sourceType); 14 | return this.response(response); 15 | } 16 | } 17 | 18 | export const cryptographyService = new CryptographyService(); 19 | 20 | -------------------------------------------------------------------------------- /src/api/services/export.service.ts: -------------------------------------------------------------------------------- 1 | import { NewExportDTO } from '@api/dto'; 2 | import { IpcChannels } from '../ipc-channels'; 3 | import { BaseService } from './base.service'; 4 | import { ExportDTO } from '../../main/modules/export/ExportDTO'; 5 | 6 | class Export extends BaseService { 7 | public async export(newExport: NewExportDTO): Promise { 8 | const response = await window.electron.ipcRenderer.invoke(IpcChannels.EXPORT, newExport); 9 | return this.response(response); 10 | } 11 | 12 | public async notarizeSBOM(args: string | null = null): Promise { 13 | const response = await window.electron.ipcRenderer.invoke(IpcChannels.EXPORT_NOTARIZE_SBOM, args); 14 | return this.response(response); 15 | } 16 | } 17 | 18 | export const exportService = new Export(); 19 | -------------------------------------------------------------------------------- /src/api/services/file.service.ts: -------------------------------------------------------------------------------- 1 | import { IpcChannels } from '../ipc-channels'; 2 | import { BaseService } from './base.service'; 3 | import { FileDTO, GetFileDTO } from "../dto"; 4 | 5 | 6 | 7 | class FileService extends BaseService { 8 | public async getFileContent(args: string): Promise { 9 | const response = await window.electron.ipcRenderer.invoke(IpcChannels.FILE_GET_CONTENT, args); 10 | return response; 11 | } 12 | 13 | public async get(params: GetFileDTO): Promise { 14 | const response = await window.electron.ipcRenderer.invoke(IpcChannels.FILE_GET, params); 15 | return this.response(response); 16 | } 17 | 18 | public async ignored(files: number[]): Promise { 19 | const response = await window.electron.ipcRenderer.invoke(IpcChannels.IGNORED_FILES, files); 20 | return this.response(response); 21 | } 22 | 23 | public async getRemoteFileContent(fileHash: string): Promise { 24 | const response = await window.electron.ipcRenderer.invoke(IpcChannels.FILE_GET_REMOTE_CONTENT, fileHash); 25 | return this.response(response); 26 | } 27 | } 28 | export const fileService = new FileService(); 29 | -------------------------------------------------------------------------------- /src/api/services/license.service.ts: -------------------------------------------------------------------------------- 1 | import { LicenseDTO, NewLicenseDTO } from "../dto"; 2 | import { IpcChannels } from '../ipc-channels'; 3 | import { BaseService } from './base.service'; 4 | 5 | class LicenseService extends BaseService { 6 | public async get(id: number): Promise { 7 | const response = await window.electron.ipcRenderer.invoke(IpcChannels.LICENSE_GET, id); 8 | return this.response(response); 9 | } 10 | 11 | public async getAll(): Promise> { 12 | const response = await window.electron.ipcRenderer.invoke(IpcChannels.LICENSE_GET_ALL); 13 | return this.response(response); 14 | } 15 | 16 | public async create(license: NewLicenseDTO): Promise { 17 | const response = await window.electron.ipcRenderer.invoke(IpcChannels.LICENSE_CREATE, license); 18 | return this.response(response); 19 | } 20 | 21 | } 22 | 23 | export const licenseService = new LicenseService(); 24 | -------------------------------------------------------------------------------- /src/api/services/results.service.ts: -------------------------------------------------------------------------------- 1 | import { IpcChannels } from '../ipc-channels'; 2 | import { BaseService } from './base.service'; 3 | 4 | class ResultService extends BaseService { 5 | public async get(path: string): Promise { 6 | const response = await window.electron.ipcRenderer.invoke(IpcChannels.RESULTS_GET, path); 7 | return this.response(response); 8 | } 9 | } 10 | 11 | export const resultService = new ResultService(); 12 | -------------------------------------------------------------------------------- /src/api/services/search.service.ts: -------------------------------------------------------------------------------- 1 | import { IpcChannels } from '../ipc-channels'; 2 | import { BaseService } from './base.service'; 3 | import { ISearchTask } from "../../main/task/search/searchTask/ISearchTask"; 4 | 5 | class SearchService extends BaseService { 6 | public async search(params: ISearchTask): Promise { 7 | const response = await window.electron.ipcRenderer.invoke(IpcChannels.SEARCH_ENGINE_SEARCH, params); 8 | return this.response(response); 9 | } 10 | } 11 | 12 | export const searchService = new SearchService(); 13 | -------------------------------------------------------------------------------- /src/api/services/userSetting.service.ts: -------------------------------------------------------------------------------- 1 | import { IpcChannels } from '../ipc-channels'; 2 | import { IWorkspaceCfg } from '../types'; 3 | import { BaseService } from './base.service'; 4 | 5 | class UserSettingService extends BaseService { 6 | public async set(setting): Promise { 7 | const response = await window.electron.ipcRenderer.invoke(IpcChannels.USER_SETTING_SET, setting); 8 | return this.response(response); 9 | } 10 | 11 | public async get(): Promise { 12 | const response = await window.electron.ipcRenderer.invoke(IpcChannels.USER_SETTING_GET); 13 | return this.response(response); 14 | } 15 | } 16 | 17 | export const userSettingService = new UserSettingService(); 18 | -------------------------------------------------------------------------------- /src/api/services/vulnerability.service.ts: -------------------------------------------------------------------------------- 1 | import { VulnerabilitiesGetAllDTO } from '@api/dto'; 2 | import { IpcChannels } from '@api/ipc-channels'; 3 | import { ComponentVulnerability } from 'main/model/entity/ComponentVulnerability'; 4 | import { BaseService } from './base.service'; 5 | 6 | class VulnerabilityService extends BaseService { 7 | public async getAll(params: VulnerabilitiesGetAllDTO): Promise { 8 | const response = await window.electron.ipcRenderer.invoke(IpcChannels.VULNERABILITY_GET_ALL, params); 9 | return this.response(response); 10 | } 11 | 12 | public async update(): Promise { 13 | const response = await window.electron.ipcRenderer.invoke(IpcChannels.VULNERABILITY_UPDATE); 14 | return this.response(response); 15 | } 16 | } 17 | 18 | export const vulnerabilityService = new VulnerabilityService(); 19 | -------------------------------------------------------------------------------- /src/config/AppConfigModule.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | /* eslint-disable no-empty */ 3 | /* eslint-disable global-require */ 4 | import { AppConfigDefault } from './AppConfigDefault'; 5 | import { IAppConfig } from './IAppConfig'; 6 | 7 | let conf = AppConfigDefault; 8 | let overrideConf = {}; 9 | 10 | console.info('%c[ CONFIGMODULE ]: Applying config override', 'color: yellow'); 11 | try { 12 | overrideConf = require('./override.json'); 13 | } catch (e) { 14 | console.info('%c[ CONFIGMODULE ]: Missing override.json file', 'color: red'); 15 | } 16 | 17 | conf = { ...conf, ...overrideConf }; 18 | const AppConfig: IAppConfig = conf; 19 | 20 | export default AppConfig; 21 | -------------------------------------------------------------------------------- /src/config/IAppConfig.ts: -------------------------------------------------------------------------------- 1 | import { ExportFormat } from '../api/types'; 2 | 3 | export interface IAppConfig { 4 | APP_NAME: string; 5 | ORGANIZATION_NAME: string; 6 | ORGANIZATION_URL: string; 7 | DEFAULT_WORKSPACE_NAME: string; 8 | ORGANIZATION_EMAIL: string; 9 | 10 | ABOUT_MESSAGE: string; 11 | DEFAULT_SETTING_NAME: string; 12 | 13 | DEFAULT_SERVICE_CHUNK_LIMIT: number; 14 | 15 | OSSKB_HOST: string; 16 | API_URL: string; 17 | API_KEY: string; 18 | 19 | API_CONTENT_PATH: string; 20 | API_SCAN_PATH: string; 21 | 22 | DEFAULT_IP_gRPC: string; 23 | DEFAULT_PORT_gRPC: number; 24 | 25 | DEFAULT_MULTIUSER_LOCK_TIMEOUT: number; 26 | 27 | // feature flags 28 | FF_ENABLE_COMPONENT_LOGO: boolean; 29 | FF_ENABLE_WORKBENCH_FILTERS: boolean; 30 | FF_EXPORT_FORMAT_OPTIONS: Array; 31 | FF_ENABLE_AUTO_ACCEPT_AFTER_SCAN: boolean; 32 | FF_ENABLE_API_CONNECTION_SETTINGS: boolean; 33 | FF_ENABLE_SCAN_VULNERABILITY: boolean; 34 | FF_ENABLE_SCAN_CRYPTOGRAPHY: boolean; 35 | FF_ENABLE_SETTINGS_HINT: boolean; 36 | 37 | SEARCH_ENGINE_DEFAULT_LIMIT: number; 38 | MIN_VERSION_SUPPORTED: string; 39 | } 40 | -------------------------------------------------------------------------------- /src/main/adapters/ResultAdapter.ts: -------------------------------------------------------------------------------- 1 | export class ResultAdapter { 2 | public adapt(result: any) { 3 | const response = result.reduce((acc, curr) => { 4 | if (!acc[curr.resultId]) { 5 | const aux = { 6 | id: curr.id, 7 | file_path: curr.file_path, 8 | url: curr.url, 9 | lines: curr.lines, 10 | oss_lines: curr.oss_lines, 11 | matched: curr.matched, 12 | file: curr.file, 13 | type: curr.type, 14 | md5_file: curr.md5_file, 15 | url_hash: curr.url_hash, 16 | purl: curr.purl, 17 | version: curr.version, 18 | latest: curr.latest, 19 | identified: curr.identified, 20 | ignored: curr.ignored, 21 | file_url: curr.file_url, 22 | license: [], 23 | }; 24 | aux.license.push(curr.spdxid); 25 | acc[curr.resultId] = aux; 26 | } else { 27 | acc[curr.resultId].license.push(curr.spdxid); 28 | } 29 | return acc; 30 | }, {}); 31 | 32 | return Object.values(response); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/batch/BatchFactory.ts: -------------------------------------------------------------------------------- 1 | import { IBatchInventory, InventoryAction } from '../../api/types'; 2 | import { Accept } from './Accept'; 3 | import { Batch } from './Batch'; 4 | import { Identified } from './Identified'; 5 | import { Ignore } from './Ignore'; 6 | import { Restore } from './Restore'; 7 | 8 | export class BatchFactory { 9 | public create(param: IBatchInventory): Batch { 10 | switch (param.action) { 11 | case InventoryAction.RESTORE: 12 | return new Restore(param); 13 | case InventoryAction.IDENTIFY: 14 | return new Identified(param, param.data.inventory); 15 | case InventoryAction.IGNORE: 16 | return new Ignore(param); 17 | case InventoryAction.ACCEPT: 18 | return new Accept(param, param.data.inventories, param.data.notes); 19 | default: 20 | return null; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/batch/Filter/Filter.ts: -------------------------------------------------------------------------------- 1 | export abstract class Filter { 2 | public abstract isValid(data: any): boolean; 3 | } 4 | -------------------------------------------------------------------------------- /src/main/batch/Filter/FilterAND.ts: -------------------------------------------------------------------------------- 1 | import { Filter } from './Filter'; 2 | 3 | export class FilterAND extends Filter { 4 | private Filter1: Filter; 5 | 6 | private Filter2: Filter; 7 | 8 | constructor(Filter1: Filter, Filter2: Filter) { 9 | super(); 10 | this.Filter1 = Filter1; 11 | this.Filter2 = Filter2; 12 | } 13 | 14 | public isValid(data: any): boolean { 15 | return this.Filter1.isValid(data) && this.Filter2.isValid(data); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/batch/Filter/FilterNOT.ts: -------------------------------------------------------------------------------- 1 | import { Filter } from './Filter'; 2 | 3 | export class FilterNOT extends Filter { 4 | private filter: Filter; 5 | 6 | constructor(filter: Filter) { 7 | super(); 8 | this.filter = filter; 9 | } 10 | 11 | public isValid(data: any): boolean { 12 | return !this.filter.isValid(data); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/batch/Filter/FilterOR.ts: -------------------------------------------------------------------------------- 1 | import { Filter } from './Filter'; 2 | 3 | export class FilterOR extends Filter { 4 | private filter1: Filter; 5 | 6 | private filter2: Filter; 7 | 8 | constructor(filter1: Filter, filter2: Filter) { 9 | super(); 10 | this.filter1 = filter1; 11 | this.filter2 = filter2; 12 | } 13 | 14 | public isValid(data: any): boolean { 15 | if (this.filter1.isValid(data) || this.filter2.isValid(data)) { 16 | return true; 17 | } 18 | 19 | return false; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/batch/Filter/FilterTrue.ts: -------------------------------------------------------------------------------- 1 | import { Filter } from "./Filter"; 2 | 3 | export class FilterTrue extends Filter { 4 | isValid(data: any): boolean { 5 | return true; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/batch/Filter/GenericFilter.ts: -------------------------------------------------------------------------------- 1 | import { Filter } from './Filter'; 2 | 3 | export class GenericFilter extends Filter { 4 | private value: any; 5 | 6 | private key: any; 7 | 8 | constructor(key: any, value: any) { 9 | super(); 10 | this.key = key; 11 | this.value = value; 12 | } 13 | 14 | public isValid(data: any): boolean { 15 | if (data[this.key] === this.value) { 16 | return true; 17 | } 18 | 19 | return false; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/broadcastManager/BroadcastManager.ts: -------------------------------------------------------------------------------- 1 | class BroadcastManager { 2 | private event: Electron.WebContents; 3 | 4 | public set(event: Electron.WebContents){ 5 | this.event = event; 6 | } 7 | 8 | public get():Electron.WebContents{ 9 | return this.event; 10 | } 11 | 12 | } 13 | 14 | export const broadcastManager = new BroadcastManager(); 15 | -------------------------------------------------------------------------------- /src/main/helpers/FileHelper.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { modelProvider } from '../services/ModelProvider'; 3 | 4 | class FileHelper { 5 | 6 | /** 7 | * @Brief Get objects with file path and database id 8 | * @Return Object with file path and database id 9 | * */ 10 | public async getPathFileId(): Promise> { 11 | const files = await modelProvider.model.file.getAll(null); 12 | const pathFileId = files.reduce((acc, file) => { 13 | acc[file.path] = file.id; 14 | return acc; 15 | }, {}); 16 | return pathFileId; 17 | } 18 | 19 | public async fileExist(file: string): Promise { 20 | return fs.promises 21 | .access(file, fs.constants.F_OK) 22 | .then(() => true) 23 | .catch(() => false); 24 | } 25 | } 26 | 27 | export const fileHelper = new FileHelper(); 28 | -------------------------------------------------------------------------------- /src/main/helpers/InventoryHelper.ts: -------------------------------------------------------------------------------- 1 | class InventoryHelper { 2 | public AddComponentIdToInventory(components: any, inventories: any) { 3 | inventories.forEach((inventory) => { 4 | const index = this.getComponentId(components, inventory.purl, inventory.version); 5 | inventory.cvid = components[index].compid; 6 | inventory.component = components[index]; 7 | }); 8 | return inventories; 9 | } 10 | 11 | private getComponentId(components: any, purl: string, version: string): number { 12 | return components.findIndex((c) => { 13 | return c.purl === purl && c.version === version; 14 | }); 15 | return null; 16 | } 17 | } 18 | export const inventoryHelper = new InventoryHelper(); 19 | -------------------------------------------------------------------------------- /src/main/helpers/LicenseHelper.ts: -------------------------------------------------------------------------------- 1 | class LicenseHelper { 2 | public licenseNameToSPDXID(licenseName: string) { 3 | const key = licenseName.trim().toLowerCase().replace(/ /g, '-'); 4 | const SPDXID = `LicenseRef-${key}`; 5 | 6 | return SPDXID; 7 | } 8 | 9 | public getStringOfLicenseNameFromArray(data: any) { 10 | let licenses = ''; 11 | data.forEach((license: any) => { 12 | if (!licenses.includes(license.name) && license.name !== '') licenses += `${license.name},`; 13 | }); 14 | return licenses.slice(0, -1); 15 | } 16 | 17 | public splitLicensesByOperator(licenses:string, operator: RegExp): Array { 18 | return licenses 19 | .split(operator) 20 | .map((license: string) => license.trim()) 21 | .filter(Boolean); 22 | } 23 | } 24 | 25 | export const licenseHelper = new LicenseHelper(); 26 | -------------------------------------------------------------------------------- /src/main/helpers/SemVer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief Compare two SemVer versions 3 | * @param value to create resulting array 4 | * @param filter filter to be applied over the data 5 | * @returns 0 if the versions are equal 6 | * 1 if v1 is greater than v2 7 | * -1 if v1 is less than v2 8 | */ 9 | 10 | export function SemVerCompareVersion(ver1: string, ver2: string): number { 11 | const v1 = ver1.split('.').map((num) => parseInt(num, 10)); 12 | const v2 = ver2.split('.').map((num) => parseInt(num, 10)); 13 | 14 | for (let i = 0; i < v1.length; i += 1) { 15 | if (v1[i] > v2[i]) return 1; 16 | if (v1[i] < v2[i]) return -1; 17 | } 18 | return 0; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/migration/AppMigration.ts: -------------------------------------------------------------------------------- 1 | import { Migration } from './Migration'; 2 | import { wsMigration1120 } from './scripts/1.12.0'; 3 | import { appMigration1123 } from './scripts/1.12.3'; 4 | import { appMigration1124 } from './scripts/1.12.4'; 5 | import { appMigration1150 } from './scripts/1.15.0'; 6 | 7 | export class AppMigration extends Migration { 8 | private scripts: Record void>>; 9 | 10 | private wsPath: string; 11 | 12 | constructor(appVersion: string, wsPath: string) { 13 | super(appVersion); 14 | this.wsPath = wsPath; 15 | this.scripts = { 16 | '0.11.1': [], // Oldest compatible version 17 | '1.12.0': [wsMigration1120], 18 | '1.12.3': [appMigration1123], 19 | '1.12.4': [appMigration1124], 20 | '1.15.0': [appMigration1150] 21 | }; 22 | } 23 | 24 | public getScripts(): Record any>> { 25 | return this.scripts; 26 | } 27 | 28 | public getPath() { 29 | return this.wsPath; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/migration/scripts/0190.ts: -------------------------------------------------------------------------------- 1 | import sqlite3 from 'sqlite3'; 2 | import log from 'electron-log'; 3 | import { Queries } from '../../model/querys_db'; 4 | 5 | export async function projectMigration190(projectPath: string): Promise { 6 | log.info('Migration 1.9.0 In progress...'); 7 | await addCryptographyTable(projectPath); 8 | } 9 | async function addCryptographyTable(projectPath:string): Promise { 10 | const query = new Queries(); 11 | return new Promise((resolve, reject) => { 12 | try { 13 | const db: any = new sqlite3.Database( 14 | `${projectPath}/scan_db`, 15 | sqlite3.OPEN_READWRITE, 16 | (err: any) => { 17 | if (err) log.error(err); 18 | db.run(query.CRYPTOGRAPHY_TABLE); 19 | db.close(); 20 | resolve(); 21 | }, 22 | ); 23 | } catch (e) { 24 | reject(e); 25 | } 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/migration/scripts/0200.ts: -------------------------------------------------------------------------------- 1 | import sqlite3 from 'sqlite3'; 2 | import log from 'electron-log'; 3 | 4 | export async function dbLicenseMigration0200(projectPath): Promise { 5 | log.info('%c[ MIGRATION ] IN PROGRESS...', 'color: green'); 6 | return new Promise((resolve, reject) => { 7 | try { 8 | const db: any = new sqlite3.Database(`${projectPath}/scan_db`, sqlite3.OPEN_READWRITE, (err: any) => { 9 | if (err) log.error(err); 10 | 11 | db.run( 12 | "INSERT INTO licenses (spdxid,name,fulltext,url,official) VALUES ('LicenseRef-unknown','Unknown','Unknown License','',1);" 13 | ); 14 | db.run( 15 | "INSERT INTO licenses (spdxid,name,fulltext,url,official) VALUES ('LicenseRef-basic-proprietary-commercial','Basic Proprietary Commercial','[PASTE YOUR LICENSE HERE]','',1)" 16 | ); 17 | log.info('%c[ MIGRATION ] FINISHED', 'color: green'); 18 | resolve(); 19 | }); 20 | } catch (e) { 21 | reject(e); 22 | } 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/migration/scripts/0210.ts: -------------------------------------------------------------------------------- 1 | import log from 'electron-log'; 2 | import { Metadata } from '../../workspace/Metadata'; 3 | 4 | export async function metadataMigration0210(projectPath): Promise { 5 | log.info('%c[ MIGRATION ] IN PROGRESS...', 'color: green'); 6 | 7 | try { 8 | const mt = await Metadata.readFromPath(projectPath); 9 | if (mt.getSource() !== 'IMPORTED') { 10 | mt.setSource('LOCAL'); 11 | mt.save(); 12 | } 13 | } catch (e) { 14 | log.error(e); 15 | throw e; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/migration/scripts/1.12.0.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import log from 'electron-log'; 3 | import * as os from 'os'; 4 | import { IWorkspaceCfg } from '../../../api/types'; 5 | import { userSettingService } from '../../services/UserSettingService'; 6 | import AppConfig from '../../../config/AppConfigModule'; 7 | 8 | const path = require('path'); 9 | 10 | export async function wsMigration1120(): Promise { 11 | try { 12 | const oldWsConfigPath = path.join(os.homedir(), AppConfig.DEFAULT_WORKSPACE_NAME, 'workspaceCfg.json'); 13 | const oldWorkspaceConfig = await fs.promises.readFile(oldWsConfigPath, 'utf8'); 14 | const oldConfig: IWorkspaceCfg = JSON.parse(oldWorkspaceConfig); 15 | const currentSettings = userSettingService.get(); 16 | const updatedSettings = { ...currentSettings, ...oldConfig, VERSION: '1.12.0' }; 17 | userSettingService.set(updatedSettings); 18 | await userSettingService.save(); 19 | await fs.promises.unlink(oldWsConfigPath); 20 | } catch (e: any) { 21 | console.log(e); 22 | log.info('Workspace config not found'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/migration/scripts/1.13.0.ts: -------------------------------------------------------------------------------- 1 | import log from 'electron-log'; 2 | import sqlite3 from 'sqlite3'; 3 | import { modelProvider } from '../../services/ModelProvider'; 4 | 5 | export async function projectMigration1130(projectPath: string): Promise { 6 | log.info('%cProject migration 1.13.0 in progress...', 'color:green'); 7 | modelProvider.openModeProjectModel = sqlite3.OPEN_READWRITE; 8 | await modelProvider.init(projectPath); 9 | await modelProvider.model.destroy(); 10 | log.info('%cProject migration 1.13.0 finished', 'color:green'); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/migration/scripts/151.ts: -------------------------------------------------------------------------------- 1 | import { userSettingService } from '../../services/UserSettingService'; 2 | 3 | export async function wsMigration151(): Promise { 4 | userSettingService.setSetting('PAC', ''); 5 | userSettingService.setSetting('VERSION', '1.5.2'); 6 | await userSettingService.save(); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/migration/scripts/180.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import log from 'electron-log'; 3 | 4 | export async function migration180(projectPath: string): Promise { 5 | log.info('Migration 1.8.0 In progress...'); 6 | const dicc = await hasDictionary(projectPath); 7 | if (dicc) await updateDictionary(projectPath); 8 | log.info('Migration 1.8.0 finished...'); 9 | } 10 | 11 | async function hasDictionary(projectPath: string): Promise { 12 | try { 13 | await fs.promises.access(`${projectPath}/dictionary`); 14 | return true; 15 | } catch (error) { 16 | return false; 17 | } 18 | } 19 | 20 | async function updateDictionary(projectPath: string) { 21 | const files = await fs.promises.readdir(`${projectPath}/dictionary`, { withFileTypes: true }); 22 | for (const filename of files) { 23 | await fs.promises.rename(`${projectPath}/dictionary/${filename.name}`, `${projectPath}/dictionary/${fileNameAdapter(filename.name)}`); 24 | } 25 | } 26 | 27 | function fileNameAdapter(filePath: string): string { 28 | const map : Record = { 29 | 'reg.cfg.json': 'cfg.json', 'reg.cfg.map.ctx.json': 'ctx.json', 'reg.cfg.map.json': 'map.json', 'reg.json': 'reg.json', 30 | }; 31 | return map[filePath]; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/migration/scripts/183.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { userSettingService } from '../../services/UserSettingService'; 4 | 5 | export async function wsMigration183(): Promise { 6 | const apis = userSettingService.get().APIS; 7 | const newApis = apis.map((item) => { 8 | return { 9 | ...item, 10 | URL: item.URL.replace(/\/scan\/direct\/?$/ig, ''), 11 | }; 12 | }); 13 | 14 | userSettingService.setSetting('APIS', newApis); 15 | userSettingService.setSetting('VERSION', '1.8.3'); 16 | await userSettingService.save(); 17 | } 18 | 19 | export async function projectMigration183(projectPath): Promise { 20 | const metadataPath = path.join(projectPath, 'metadata.json'); 21 | const metadataRaw = await fs.promises.readFile(metadataPath, 'utf-8'); 22 | const metadata = JSON.parse(metadataRaw); 23 | 24 | if (metadata?.api) { 25 | metadata.api = metadata.api.replace(/\/scan\/direct\/?$/ig, ''); 26 | await fs.promises.writeFile(metadataPath, JSON.stringify(metadata, null, 2), 'utf-8'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/migration/scripts/184.ts: -------------------------------------------------------------------------------- 1 | import { userSettingService } from '../../services/UserSettingService'; 2 | import { workspace } from '../../workspace/Workspace'; 3 | 4 | export async function wsMigration184(): Promise { 5 | updateProjectPathsOnWorkspace(); 6 | userSettingService.setSetting('VERSION', '1.8.4'); 7 | await userSettingService.save(); 8 | 9 | } 10 | function updateProjectPathsOnWorkspace() { 11 | const projects = workspace.getProjects(); 12 | projects.forEach((p) => { 13 | p.setMyPath(p.getProjectName()); 14 | }); 15 | workspace.setProjectList(projects); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/model/Model.ts: -------------------------------------------------------------------------------- 1 | import log from 'electron-log'; 2 | import sqlite3 from 'sqlite3'; 3 | import { QueryBuilder } from './queryBuilder/QueryBuilder'; 4 | 5 | export class Model { 6 | 7 | public static readonly entityMapper = {}; 8 | public getEntityMapper():Record{ 9 | return Model.entityMapper; 10 | } 11 | public getSQL(queryBuilder:QueryBuilder , SQLquery:string, entityMapper:Record){ 12 | let SQL = SQLquery; 13 | const filter = queryBuilder?.getSQL(entityMapper) 14 | ? `WHERE ${queryBuilder.getSQL(entityMapper).toString()}` 15 | : ''; 16 | const params = queryBuilder?.getFilters() ? queryBuilder.getFilters() : []; 17 | SQL = SQLquery.replace('#FILTER', filter); 18 | return { SQL, params }; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/model/UtilModel.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | class UtilModel { 4 | // READ RESULTS FROM A FILE 5 | public async readFile(path: string) { 6 | return new Promise((resolve, reject) => { 7 | fs.readFile(path, async (err: any, jsonString: any) => { 8 | if (err) reject(err); 9 | const result: any = JSON.parse(jsonString); 10 | resolve(result); 11 | }); 12 | }); 13 | } 14 | 15 | public async read(path: string) { 16 | return new Promise((resolve, reject) => { 17 | fs.readFile(path, async (err: any, result: any) => { 18 | if (err) reject(err); 19 | resolve(result); 20 | }); 21 | }); 22 | } 23 | 24 | getTimeStamp() { 25 | const time = new Date(); 26 | const timeStamp = `${time.toISOString().split('.')[0]}Z`; 27 | return timeStamp; 28 | } 29 | } 30 | 31 | export const utilModel = new UtilModel(); 32 | -------------------------------------------------------------------------------- /src/main/model/adapters/adapter.ts: -------------------------------------------------------------------------------- 1 | export interface ModelAdapter { 2 | run(input: T1): Promise; 3 | } -------------------------------------------------------------------------------- /src/main/model/adapters/report/detectedLicenseSummaryAdapter.ts: -------------------------------------------------------------------------------- 1 | 2 | import { LicenseReport } from "main/services/ReportService"; 3 | import { ModelAdapter } from "../adapter"; 4 | 5 | 6 | // Report 7 | export interface LicenseReportEntry { 8 | spdxid: string; //spdxid 9 | componentLicenseCount: number; 10 | dependencyLicenseCount: number; 11 | } 12 | 13 | export class DetectedLicenseSummaryAdapter implements ModelAdapter,Array> { 14 | async run (input: Array){ 15 | return input.map((l: LicenseReportEntry)=>{ l 16 | return { label: l.spdxid, value: l.componentLicenseCount + l.dependencyLicenseCount } 17 | }); 18 | } 19 | } 20 | 21 | export const detectedLicenseSummaryAdapter = new DetectedLicenseSummaryAdapter(); -------------------------------------------------------------------------------- /src/main/model/entity/ComponentVersion.ts: -------------------------------------------------------------------------------- 1 | export class ComponentVersion { 2 | private licenseIds: Array; 3 | 4 | id?: number; 5 | 6 | name: string; 7 | 8 | description?: string; 9 | 10 | version: string; 11 | 12 | url?: string; 13 | 14 | source: ComponentSource; 15 | 16 | purl: string; 17 | 18 | reliableLicense?: string; 19 | 20 | // TODO:licenses: Array 21 | 22 | public setLicenseIds(licensesIds: Array) { 23 | this.licenseIds = licensesIds; 24 | } 25 | 26 | public getLicenseIds(): Array { 27 | return this.licenseIds; 28 | } 29 | } 30 | 31 | export enum ComponentSource { 32 | ENGINE = 'engine', 33 | MANUAL = 'manual', 34 | } 35 | -------------------------------------------------------------------------------- /src/main/model/entity/ComponentVulnerability.ts: -------------------------------------------------------------------------------- 1 | import { Vulnerability } from './Vulnerability'; 2 | import { ComponentVersion } from './ComponentVersion'; 3 | 4 | export class ComponentVulnerability { 5 | purl: string; 6 | 7 | version: string; 8 | 9 | rejectAt: string; 10 | 11 | vulnerability?: Vulnerability; 12 | 13 | componentVersion?: ComponentVersion; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/model/entity/Cryptography.ts: -------------------------------------------------------------------------------- 1 | export class Cryptography { 2 | purl: string; 3 | 4 | version: string; 5 | 6 | algorithms: Array; 7 | 8 | hints: Array; 9 | } 10 | 11 | export interface Hint { 12 | id: string; 13 | name: string; 14 | description: string; 15 | category: string; 16 | url?: string; 17 | purl?: string; 18 | } 19 | 20 | export interface Algorithms { 21 | algorithm: string; 22 | strength: string; 23 | } 24 | 25 | export interface CryptographicItem { 26 | name: string; // filePath | pkg@version 27 | type: string; // algorithm | library 28 | values: Array; 29 | } 30 | -------------------------------------------------------------------------------- /src/main/model/entity/Dependency.ts: -------------------------------------------------------------------------------- 1 | export class Dependency { 2 | dependencyId: number; 3 | 4 | fileId: number; 5 | 6 | purl: string; 7 | 8 | version: string; 9 | 10 | scope: string; 11 | 12 | rejectedAt: string; 13 | 14 | licenses: Array; 15 | 16 | component: string; 17 | 18 | originalVersion: string; 19 | 20 | originalLicense: Array; 21 | } 22 | 23 | export interface ModelDependencyManifest { 24 | fileId: number; 25 | path: string; 26 | identified: number; 27 | ignored: number; 28 | pending: number; 29 | } -------------------------------------------------------------------------------- /src/main/model/entity/ExportControl.ts: -------------------------------------------------------------------------------- 1 | export interface ExportControlSummary { 2 | categorySummary: Record; 3 | total: number; 4 | } 5 | -------------------------------------------------------------------------------- /src/main/model/entity/LocalCryptography.ts: -------------------------------------------------------------------------------- 1 | import { Algorithms } from './Cryptography'; 2 | 3 | export interface LocalCryptography { 4 | file: string; 5 | algorithms : Array; 6 | } 7 | -------------------------------------------------------------------------------- /src/main/model/entity/Vulnerability.ts: -------------------------------------------------------------------------------- 1 | import { ComponentVulnerability } from './ComponentVulnerability'; 2 | 3 | export class Vulnerability { 4 | external_id: string; 5 | 6 | cve: string; 7 | 8 | source: string; 9 | 10 | severity: string; 11 | 12 | published: string; 13 | 14 | modified: string; 15 | 16 | summary: string; 17 | 18 | components?: Array; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/model/hooks/after/afterHook.ts: -------------------------------------------------------------------------------- 1 | import { ModelAdapter } from 'main/model/adapters/adapter'; 2 | 3 | export function After(adapter: ModelAdapter) { 4 | return function (target, key, descriptor) { 5 | const originalMethod = descriptor.value; 6 | 7 | descriptor.value = async function (...args):Promise { 8 | const input = await originalMethod.apply(this, args); 9 | return adapter.run(input); 10 | }; 11 | 12 | return descriptor; 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/model/interfaces/IInsertResult.ts: -------------------------------------------------------------------------------- 1 | export interface IInsertResult { 2 | [key: string]: Array 3 | } 4 | 5 | export interface IResultLicense { 6 | name: string; 7 | patent_hints?: string; 8 | copyleft?: string; 9 | checklist_url?: string; 10 | incompatible_with?: string; 11 | osadl_updated?: Date; 12 | source: string; 13 | url: string; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/model/interfaces/ModelTypes.ts: -------------------------------------------------------------------------------- 1 | import { Vulnerability } from "../entity/Vulnerability"; 2 | 3 | 4 | export interface ComponentVulnerability { 5 | purl: string; 6 | vulnerabilitiesList: Array<{ 7 | id: string; 8 | 9 | cve: string; 10 | 11 | source: string; 12 | 13 | severity: string; 14 | 15 | published: string; 16 | 17 | modified: string; 18 | 19 | summary: string; 20 | }>; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/model/interfaces/component/IComponentLicense.ts: -------------------------------------------------------------------------------- 1 | export interface IComponentLicense { 2 | id: number, 3 | license: Array, 4 | } 5 | -------------------------------------------------------------------------------- /src/main/model/interfaces/component/IComponentLicenseReliable.ts: -------------------------------------------------------------------------------- 1 | export interface IComponentLicenseReliable { 2 | cvid: number, 3 | source: string, 4 | reliableLicense:string, 5 | ranking: number 6 | } 7 | -------------------------------------------------------------------------------- /src/main/model/interfaces/component/INewComponent.ts: -------------------------------------------------------------------------------- 1 | export interface INewComponent { 2 | name: string; 3 | description?: string; 4 | version: string; 5 | url?: string; 6 | source: componentSource; 7 | purl: string; 8 | licenses: Array; 9 | } 10 | 11 | export enum componentSource { 12 | ENGINE = 'engine', 13 | MANUAL = 'manual' 14 | } 15 | -------------------------------------------------------------------------------- /src/main/model/interfaces/report/DataRecord.ts: -------------------------------------------------------------------------------- 1 | export interface DataRecord { 2 | inventory_id: number; 3 | path: string; 4 | usage: string; 5 | detected_component: string; 6 | concluded_component: string; 7 | detected_purl: string; 8 | concluded_purl: string; 9 | detected_version: string; 10 | concluded_version: string; 11 | latest_version: string; 12 | detected_license: string; 13 | concluded_license: string; 14 | url: string; 15 | } -------------------------------------------------------------------------------- /src/main/model/interfaces/report/DecisionData.ts: -------------------------------------------------------------------------------- 1 | export interface DecisionData { 2 | identifiedAs: string; 3 | original: string; 4 | path: string; 5 | type: string; 6 | identified: number; 7 | ignored: number; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/model/interfaces/report/ExportComponentData.ts: -------------------------------------------------------------------------------- 1 | export interface ExportComponentData { 2 | component: string; 3 | vendor: string | null; 4 | purl: string; 5 | version: string; 6 | detected_licenses: string; 7 | concluded_licenses: string; 8 | url: string; 9 | url_hash: string | null; 10 | download_url: string | null; 11 | unique_detected_licenses?: Array; 12 | unique_concluded_licenses?: Array; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/model/interfaces/report/ExportCryptographyData.ts: -------------------------------------------------------------------------------- 1 | import { CryptographicItem } from '../../entity/Cryptography'; 2 | 3 | export class ExportCryptographyData { 4 | localCryptography: Array; 5 | 6 | componentCryptography: Array; 7 | } 8 | -------------------------------------------------------------------------------- /src/main/model/interfaces/report/VulnerabilityExportData.ts: -------------------------------------------------------------------------------- 1 | export interface AffectedComponent { 2 | purl: string; 3 | versions: string[]; 4 | } 5 | 6 | export interface VulnerabilityExportData { 7 | cve: string; 8 | severity: string; 9 | rejectAt: string; 10 | summary: string; 11 | published: string; 12 | modified: string; 13 | source: string; 14 | affectedComponents:Array; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/model/queryBuilder/QueryBuilder.ts: -------------------------------------------------------------------------------- 1 | export abstract class QueryBuilder { 2 | public abstract getSQL(entityMapper?: Record): string; 3 | 4 | public abstract getFilters(): any[]; 5 | } 6 | -------------------------------------------------------------------------------- /src/main/model/queryBuilder/QueryBuilderAND.ts: -------------------------------------------------------------------------------- 1 | import { QueryBuilder } from './QueryBuilder'; 2 | 3 | export class QueryBuilderAND extends QueryBuilder { 4 | private builders: QueryBuilder[]; 5 | 6 | constructor() { 7 | super(); 8 | this.builders = []; 9 | } 10 | 11 | public getSQL(entityMapper: Record): string { 12 | const partialSQL = this.builders 13 | .map((e) => e.getSQL(entityMapper)) 14 | .join(' AND '); 15 | return partialSQL; 16 | } 17 | 18 | public getFilters(): any[] { 19 | const filters = []; 20 | this.builders.forEach((e) => { 21 | if (e.getFilters() != null) { 22 | filters.push(e.getFilters()[0]); 23 | } 24 | }); 25 | return filters; 26 | } 27 | 28 | public add(builder: QueryBuilder) { 29 | this.builders.push(builder); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/model/queryBuilder/QueryBuilderCompId.ts: -------------------------------------------------------------------------------- 1 | import { QueryBuilder } from './QueryBuilder'; 2 | 3 | export class QueryBuilderCompId extends QueryBuilder { 4 | private value: any; 5 | 6 | constructor(value: any) { 7 | super(); 8 | this.value = value; 9 | } 10 | 11 | public getSQL(entityMapper: Record): string { 12 | return `comp.id IN (${this.value.toString()})`; 13 | } 14 | 15 | public getFilters(): any[] { 16 | return null; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/model/queryBuilder/QueryBuilderCustom.ts: -------------------------------------------------------------------------------- 1 | import { QueryBuilder } from './QueryBuilder'; 2 | 3 | export class QueryBuilderCustom extends QueryBuilder { 4 | private value: string; 5 | 6 | private operator: string; 7 | 8 | private key: string; 9 | 10 | constructor(key: string, operator: string, value: string) { 11 | super(); 12 | this.value = value; 13 | this.operator = operator; 14 | this.key = key; 15 | } 16 | 17 | public getSQL(entityMapper: Record): string { 18 | return `${entityMapper[this.key]} ${this.operator} ?`; 19 | } 20 | 21 | public getFilters(): any[] { 22 | return [this.value]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/model/queryBuilder/QueryBuilderFilIdIN.ts: -------------------------------------------------------------------------------- 1 | import { QueryBuilder } from './QueryBuilder'; 2 | 3 | export class QueryBuilderFileIdIn extends QueryBuilder { 4 | private value: any; 5 | 6 | constructor(value: Array) { 7 | super(); 8 | this.value = value; 9 | } 10 | 11 | public getSQL(entityMapper: Record): string { 12 | return `f.fileId IN (${this.value.toString()})`; 13 | } 14 | 15 | public getFilters(): any[] { 16 | return null; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/model/queryBuilder/QueryBuilderFilePathIN.ts: -------------------------------------------------------------------------------- 1 | import { QueryBuilder } from './QueryBuilder'; 2 | 3 | export class QueryBuilderFIlePathIN extends QueryBuilder { 4 | private value: any; 5 | 6 | constructor(value: any) { 7 | super(); 8 | this.value = value as Array; 9 | } 10 | 11 | public getSQL(entityMapper: Record): string { 12 | return `f.path IN (${String(this.value)})`; 13 | } 14 | 15 | public getFilters(): any[] { 16 | return null; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/model/queryBuilder/QueryBuilderFilename.ts: -------------------------------------------------------------------------------- 1 | import { QueryBuilder } from './QueryBuilder'; 2 | 3 | export class QueryBuilderFilename extends QueryBuilder { 4 | private filename: string; 5 | 6 | constructor(filename: string) { 7 | super(); 8 | this.filename = filename; 9 | } 10 | 11 | public getSQL(entityMapper: Record): string { 12 | return `${entityMapper.path} LIKE ?`; 13 | } 14 | 15 | public getFilters(): any[] { 16 | const term = `%${this.filename.replaceAll('*', '%')}%`; 17 | return [term]; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/model/queryBuilder/QueryBuilderIN.ts: -------------------------------------------------------------------------------- 1 | import { QueryBuilder } from './QueryBuilder'; 2 | 3 | export class QueryBuilderIN extends QueryBuilder { 4 | private builders: QueryBuilder[]; 5 | 6 | constructor() { 7 | super(); 8 | this.builders = []; 9 | } 10 | 11 | public getSQL(entityMapper: Record): string { 12 | const partialSQL = this.builders.map((e) => e.getSQL(entityMapper)); 13 | return partialSQL[0]; 14 | } 15 | 16 | public getFilters(): any[] { 17 | return this.builders[0].getFilters(); 18 | } 19 | 20 | public add(queryBuilder: QueryBuilder): void { 21 | this.builders.push(queryBuilder); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/model/queryBuilder/QueryBuilderMD5FileIn.ts: -------------------------------------------------------------------------------- 1 | import { QueryBuilder } from './QueryBuilder'; 2 | 3 | export class QueryBuilderMD5FileIn extends QueryBuilder { 4 | private value: any; 5 | 6 | constructor(value: any) { 7 | super(); 8 | this.value = value as Array; 9 | } 10 | 11 | public getSQL(entityMapper: Record): string { 12 | const md5Files = this.value.map(function(file) { return `'${ file }'`; }).join(", "); 13 | return `r.md5_file IN (${md5Files})`; 14 | } 15 | 16 | public getFilters(): any[] { 17 | return null; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/model/queryBuilder/QueryBuilderSource.ts: -------------------------------------------------------------------------------- 1 | import { QueryBuilder } from "./QueryBuilder"; 2 | 3 | // TO DO: DO IT FOR HER 4 | export class QueryBuilderSource extends QueryBuilder { 5 | private value: string; 6 | 7 | constructor(value: string) { 8 | super(); 9 | this.value = value; 10 | } 11 | 12 | public getSQL(entityMapper: Record): string { 13 | return `comp.source=?`; 14 | } 15 | 16 | public getFilters(): string[] { 17 | return [this.value]; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/model/queryBuilder/QueryBuilderStatus.ts: -------------------------------------------------------------------------------- 1 | import { FileStatusType } from '../../../api/types'; 2 | import { QueryBuilder } from './QueryBuilder'; 3 | 4 | export class QueryBuilderStatus extends QueryBuilder { 5 | private value: FileStatusType; 6 | 7 | constructor(value: FileStatusType) { 8 | super(); 9 | this.value = value; 10 | } 11 | 12 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 13 | public getSQL(_queryAdapter: Record): string { 14 | if (this.value === FileStatusType.IDENTIFIED) return 'f.identified=1'; 15 | if (this.value === FileStatusType.ORIGINAL) return 'f.ignored=1'; 16 | if (this.value === FileStatusType.NOMATCH) return `f.type='NO-MATCH' AND f.ignored=0 AND f.identified=0`; 17 | if (this.value === FileStatusType.FILTERED) return `f.type='FILTERED' AND f.ignored=0 AND f.identified=0`; 18 | return `f.identified=0 AND f.ignored=0 AND f.type='MATCH'`; 19 | } 20 | 21 | public getFilters(): any[] { 22 | return null; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/model/queryBuilder/QueryBuilderUsage.ts: -------------------------------------------------------------------------------- 1 | import { QueryBuilder } from './QueryBuilder'; 2 | 3 | export class QueryBuilderUsage extends QueryBuilder { 4 | private value: string; 5 | 6 | constructor(value: string) { 7 | super(); 8 | this.value = value; 9 | } 10 | 11 | public getSQL(entityMapper: Record): string { 12 | return `r.idtype=?`; 13 | } 14 | 15 | public getFilters(): any[]{ 16 | return [this.value]; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/modules/export/DataProviders/BaseDataProvider.ts: -------------------------------------------------------------------------------- 1 | import { ExportSource } from '../../../../api/types'; 2 | 3 | export class BaseDataProvider { 4 | protected source: ExportSource; 5 | 6 | constructor(source: ExportSource) { 7 | this.source = source; 8 | } 9 | 10 | protected getSource() { 11 | return this.source; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/modules/export/ExportDTO.ts: -------------------------------------------------------------------------------- 1 | import { IExportResult } from './IExportResult'; 2 | 3 | export type ExportDTO = IExportResult; 4 | -------------------------------------------------------------------------------- /src/main/modules/export/IExportResult.ts: -------------------------------------------------------------------------------- 1 | import { ExportResultsInfo, ExportStatusCode } from '../../../api/types'; 2 | 3 | export interface IExportResult { 4 | success: boolean; 5 | message: string; 6 | extension: string; 7 | file: string; 8 | statusCode: ExportStatusCode; 9 | info: ExportResultsInfo; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/modules/export/ReportData.ts: -------------------------------------------------------------------------------- 1 | export interface ReportData { 2 | components: T; 3 | invalidPurls: Array; 4 | } 5 | -------------------------------------------------------------------------------- /src/main/modules/export/Repository/ExportRepository.ts: -------------------------------------------------------------------------------- 1 | import { ExportComponentData } from '../../../model/interfaces/report/ExportComponentData'; 2 | import { DecisionData } from '../../../model/interfaces/report/DecisionData'; 3 | import { ComponentVulnerability } from '../../../model/entity/ComponentVulnerability'; 4 | import { LicenseDTO } from '../../../../api/dto'; 5 | import { ExportCryptographyData } from '../../../model/interfaces/report/ExportCryptographyData'; 6 | 7 | export interface ExportRepository { 8 | getIdentifiedData(): Promise; 9 | getDetectedData(): Promise; 10 | getRawData(); 11 | getWfpData(): Promise; 12 | getDecisionData(): Promise>; 13 | getDetectedVulnerability(): Promise>; 14 | getIdentifiedVulnerability(): Promise>; 15 | getAllLicensesWithFullText(): Promise>; // TODO:Change type 16 | getCBOMDetectedData(): Promise; 17 | getCBOMIdentifiedData(): Promise; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/modules/export/format/CycloneDX/CycloneDXDetected.ts: -------------------------------------------------------------------------------- 1 | import { ExportComponentData } from 'main/model/interfaces/report/ExportComponentData'; 2 | import { CycloneDX } from './CycloneDX'; 3 | import { isValidPurl } from '../../helpers/exportHelper'; 4 | import { ReportData } from '../../ReportData'; 5 | 6 | export class CycloneDXDetected extends CycloneDX { 7 | protected getUniqueComponents(data: ExportComponentData[]): ReportData { 8 | const uniqueComponents = new Map(); 9 | const invalidPurls: Array = []; 10 | data.forEach((comp) => { 11 | if (isValidPurl(comp.purl)) { 12 | const key = `${comp.purl}@${comp.version}`; 13 | uniqueComponents.set(key, { 14 | ...comp, 15 | unique_detected_licenses: comp.detected_licenses ? comp.detected_licenses?.split(' AND ') : [], 16 | unique_concluded_licenses: [], 17 | }); 18 | } else { 19 | invalidPurls.push(comp.purl); 20 | } 21 | }); 22 | return { 23 | components: Array.from(uniqueComponents.values()) as ExportComponentData[], 24 | invalidPurls, 25 | }; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/modules/export/format/Raw.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-restricted-syntax */ 2 | import { Format } from '../Format'; 3 | import { ExportStatusCode } from '../../../../api/types'; 4 | 5 | export class Raw extends Format { 6 | constructor() { 7 | super(); 8 | this.extension = '.json'; 9 | } 10 | 11 | // @override 12 | public async generate() { 13 | const data = await this.export.getRawData(); 14 | const out = {}; 15 | for (const [key, obj] of Object.entries(data)) { 16 | let vKey = key; 17 | if (key.charAt(0) === '/') vKey = key.substring(1); 18 | out[vKey] = obj; 19 | } 20 | return { 21 | report: JSON.stringify(out, undefined, 2), 22 | status: { 23 | code: ExportStatusCode.SUCCESS, 24 | info: { 25 | invalidPurls: [], 26 | }, 27 | }, 28 | }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/modules/export/format/SPDXLite/SpdxDocument.ts: -------------------------------------------------------------------------------- 1 | interface ExternalRef { 2 | referenceCategory: string; 3 | referenceLocator: string; 4 | referenceType: string; 5 | } 6 | 7 | interface Checksum { 8 | algorithm: string; 9 | checksumValue: string; 10 | } 11 | 12 | interface CreationInfo { 13 | creators: string[]; 14 | created: string; 15 | comment: string; 16 | } 17 | 18 | interface Package { 19 | name: string; 20 | SPDXID: string; 21 | versionInfo: string; 22 | downloadLocation: string; 23 | filesAnalyzed: boolean; 24 | supplier: string; 25 | homepage: string; 26 | licenseDeclared: string; 27 | licenseConcluded: string; 28 | copyrightText: string; 29 | externalRefs: ExternalRef[]; 30 | checksums: Checksum[]; 31 | } 32 | 33 | interface ExtractedLicenseInfo { 34 | licenseId: string; 35 | name: string; 36 | extractedText: string; 37 | comment: string; 38 | } 39 | 40 | interface SPDXDocument { 41 | spdxVersion: string; 42 | dataLicense: string; 43 | SPDXID: string; 44 | name: string; 45 | documentNamespace: string; 46 | creationInfo: CreationInfo; 47 | packages: Package[]; 48 | documentDescribes: string[]; 49 | hasExtractedLicensingInfos: ExtractedLicenseInfo[]; 50 | } 51 | -------------------------------------------------------------------------------- /src/main/modules/export/format/Settings/identification-tree/decision-node.ts: -------------------------------------------------------------------------------- 1 | import { BomProcessor } from '../processors/bom-processor'; 2 | import { Bom } from '../types'; 3 | /** 4 | * @brief Abstract base class representing a node in a tree structure 5 | * 6 | * @details This class defines the interface for both folder and leaf nodes 7 | * in a hierarchical tree structure. 8 | */ 9 | export default abstract class DecisionNode { 10 | /** 11 | * @brief Gets the path of the node 12 | * @return The full path of the node as a string 13 | */ 14 | public abstract getPath(): string; 15 | 16 | /** 17 | * @brief Generates settings using the provided processor 18 | * @param processor The settings processor to use 19 | * @return SCANOSS Settings file 20 | */ 21 | public abstract generateBom(processor: BomProcessor): Bom; 22 | 23 | /** 24 | * @brief Retrieves the current settings of the node 25 | * @return The current settings 26 | */ 27 | public abstract getBom(): Bom; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/modules/export/format/Settings/processors/processor.ts: -------------------------------------------------------------------------------- 1 | import { IProcessor } from './bom-processor'; 2 | import { Folder } from '../identification-tree/folder'; 3 | import { Leaf } from '../identification-tree/leaf'; 4 | 5 | /** 6 | * @brief Abstract base class implementing the IProcessor interface 7 | * 8 | * @details This abstract class serves as a base for concrete processor 9 | * implementations. It implements the IProcessor interface but leaves 10 | * the actual processing logic to be defined by derived classes. 11 | */ 12 | export abstract class Processor implements IProcessor { 13 | abstract processFolder(node: Folder): void; 14 | abstract processLeaf(node: Leaf): void; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/modules/export/format/Settings/types/index.ts: -------------------------------------------------------------------------------- 1 | export interface Bom { 2 | include: BomItem[]; 3 | remove: BomItem[]; 4 | replace: ReplaceBomItem[]; 5 | } 6 | 7 | export interface BomItem { 8 | purl: string; 9 | path?: string; 10 | } 11 | 12 | export interface ReplaceBomItem extends BomItem { 13 | replace_with: string; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/modules/export/format/Wfp.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-async-promise-executor */ 2 | 3 | import { Format } from '../Format'; 4 | import { ExportStatusCode } from '../../../../api/types'; 5 | 6 | export class Wfp extends Format { 7 | constructor() { 8 | super(); 9 | this.extension = '.wfp'; 10 | } 11 | 12 | public async generate() { 13 | const data = await this.export.getWfpData(); 14 | return { 15 | report: data, 16 | status: { 17 | code: ExportStatusCode.SUCCESS, 18 | info: { 19 | invalidPurls: [], 20 | }, 21 | }, 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/modules/report/components/ComponentReport.ts: -------------------------------------------------------------------------------- 1 | import { ComponentReportResponse } from "../../../../api/types"; 2 | import { ReportComponentDetected } from "./ReportComponentDetected"; 3 | import { ReportComponentIdentified } from "./ReportComponentIndentified"; 4 | 5 | 6 | export abstract class ComponentReport { 7 | public abstract getIdentified(r: ReportComponentIdentified):Promise; 8 | public abstract getDetected(r: ReportComponentDetected):Promise; 9 | } -------------------------------------------------------------------------------- /src/main/modules/report/components/ReportComponentIndentified.ts: -------------------------------------------------------------------------------- 1 | import { modelProvider } from "../../../services/ModelProvider"; 2 | import { ComponentReport } from "./ComponentReport"; 3 | import { Report } from "./Report"; 4 | import { ReportComponent } from "../../../services/ReportService"; 5 | import { ComponentReportResponse } from "../../../../api/types"; 6 | 7 | export class ReportComponentIdentified extends Report { 8 | 9 | public async generate(v: ComponentReport):Promise { 10 | return v.getIdentified(this); 11 | } 12 | 13 | public async getComponentFileCountMapper(): Promise> { 14 | const componentFileCount = await modelProvider.model.component.getIdentifiedComponentFileCount(this.license); 15 | return this.fileCountComponentMapper(componentFileCount); 16 | } 17 | 18 | public async getComponents():Promise> { 19 | let indentifiedComponents = await modelProvider.model.component.getIdentifiedComponents(); 20 | if (this.license) { 21 | indentifiedComponents = this.filterComponentsByLicense(indentifiedComponents); 22 | } 23 | return indentifiedComponents; 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/main/modules/searchEngine/indexer/IIndexer.ts: -------------------------------------------------------------------------------- 1 | export interface IIndexer { 2 | fileId: number; 3 | path: string; 4 | } 5 | 6 | export interface IfileIdex { 7 | files: Array; 8 | } -------------------------------------------------------------------------------- /src/main/modules/searchEngine/searcher/ISearcher.ts: -------------------------------------------------------------------------------- 1 | import { ISearchTask } from "../../../task/search/searchTask/ISearchTask"; 2 | 3 | export type ISearcher = ISearchTask ; 4 | -------------------------------------------------------------------------------- /src/main/services/utils/cryptography.ts: -------------------------------------------------------------------------------- 1 | import { NewComponentDTO } from '@api/types'; 2 | import { cryptographyService } from '../CryptographyService'; 3 | 4 | export async function AddCrypto(data: NewComponentDTO | NewComponentDTO[]) { 5 | const components = Array.isArray(data) ? data : [data]; 6 | await cryptographyService.importFromComponents(components); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/services/utils/hookAfter.ts: -------------------------------------------------------------------------------- 1 | import log from 'electron-log'; 2 | 3 | export function After(callback: (data: any) => Promise, { dispatch } = { dispatch: false }) { 4 | return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { 5 | const next = descriptor.value; 6 | 7 | // Your logic before the function call 8 | descriptor.value = async function (...args: any[]) { 9 | const result = await next.apply(this, args); 10 | 11 | try { 12 | await callback(args[0]); 13 | } catch (e: any) { 14 | log.error('AfterHook', e); 15 | if (dispatch) { 16 | throw new Error(e); 17 | } 18 | } 19 | 20 | return result; 21 | }; 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/services/utils/httpUtil.ts: -------------------------------------------------------------------------------- 1 | import { workspace } from "../../../main/workspace/Workspace"; 2 | import { HttpProxyConfig } from "scanoss"; 3 | import { userSettingService } from "../UserSettingService"; 4 | 5 | export const getHttpConfig = (): HttpProxyConfig => { 6 | const project = workspace.getOpenedProjects()[0]; 7 | const { 8 | DEFAULT_API_INDEX, 9 | APIS, 10 | HTTP_PROXY, 11 | HTTPS_PROXY, 12 | PAC_PROXY, 13 | NO_PROXY, 14 | CA_CERT, 15 | IGNORE_CERT_ERRORS, 16 | } = userSettingService.get(); 17 | 18 | const cfg:HttpProxyConfig = { 19 | PAC_PROXY: PAC_PROXY, 20 | HTTP_PROXY: HTTP_PROXY, 21 | HTTPS_PROXY: HTTPS_PROXY, 22 | NO_PROXY: NO_PROXY, 23 | API_KEY: project.getApi() ? project.getApiKey() : APIS[DEFAULT_API_INDEX].API_KEY, 24 | CA_CERT: CA_CERT, 25 | IGNORE_CERT_ERRORS: IGNORE_CERT_ERRORS 26 | } 27 | 28 | return cfg; 29 | }; -------------------------------------------------------------------------------- /src/main/services/utils/inventoryServiceUtil.ts: -------------------------------------------------------------------------------- 1 | import { Inventory } from '../../../api/types'; 2 | 3 | /** 4 | * @param inventory Inventory to be created 5 | * @param results list of results 6 | * @return Array list of inventories grouped by usage 7 | */ 8 | export function getInventoriesGroupedByUsage(inventory: Partial, results: any): Array> { 9 | const inventories = new Map>(); 10 | results.forEach((r) => { 11 | if (!inventories.has(r.type)) { // create a new inventory and set usage 12 | const inv = {...inventory, usage:r.type , files: [r.id] }; 13 | inventories.set(r.type, inv); 14 | } else inventories.get(r.type).files.push(r.id); 15 | }); 16 | return Array.from(inventories.values()); 17 | } 18 | 19 | /** 20 | * @param results List of results 21 | * @return get a list of unique results 22 | */ 23 | export function getUniqueResults(results: any) { 24 | const uniqueResults = new Map(); 25 | results.forEach((r)=>{ uniqueResults.set(r.id, r) }); 26 | return uniqueResults; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/services/utils/vulnerability.ts: -------------------------------------------------------------------------------- 1 | import { NewComponentDTO } from '@api/types'; 2 | import { vulnerabilityService } from '../VulnerabilityService'; 3 | 4 | export async function AddVulnerability(data: NewComponentDTO | NewComponentDTO[]) { 5 | try { 6 | const components = Array.isArray(data) ? data : [data]; 7 | vulnerabilityService.updateFromComponents(components); 8 | } catch (e: any) { 9 | throw new Error(e); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/task/IndexTreeTask/IndexTreeTask.ts: -------------------------------------------------------------------------------- 1 | import i18next from 'i18next'; 2 | import { Project } from '../../workspace/Project'; 3 | import { Scanner } from '../scanner/types'; 4 | import { ScannerStage } from '../../../api/types'; 5 | import { Tree } from "../../workspace/tree/Tree"; 6 | 7 | 8 | export abstract class IndexTreeTask implements Scanner.IPipelineTask { 9 | protected project: Project; 10 | 11 | constructor(project: Project) { 12 | this.project = project; 13 | } 14 | 15 | public abstract run(): Promise; 16 | public abstract buildTree(files: Array):Promise; 17 | 18 | public getStageProperties(): Scanner.StageProperties { 19 | return { 20 | name: ScannerStage.INDEX, 21 | label: i18next.t('Title:Indexing'), 22 | isCritical: true, 23 | }; 24 | } 25 | 26 | public abstract setTreeSummary(tree: Tree); 27 | 28 | 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/task/Task.ts: -------------------------------------------------------------------------------- 1 | export interface ITask { 2 | run(params: UseCasePort): Promise; 3 | } 4 | -------------------------------------------------------------------------------- /src/main/task/componentCatalog/adapters/CompSearchResponseAdapter.ts: -------------------------------------------------------------------------------- 1 | import { IComponentResult } from '../iComponentCatalog/IComponentResult'; 2 | import * as ComponentMessages from '../../grpc/scanoss/api/components/v2/scanoss-components_pb'; 3 | 4 | export class CompSearchResponseAdapter { 5 | public static convert(response: ComponentMessages.CompSearchResponse): Array { 6 | const output: Array = []; 7 | response.getComponentsList().forEach((comp) => { 8 | output.push({ component: comp.getComponent(), purl: comp.getPurl(), url: comp.getUrl() }); 9 | }); 10 | return output; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/task/componentCatalog/adapters/CompoVerSearchResponseAdapter.ts: -------------------------------------------------------------------------------- 1 | import * as ComponentMessages from '../../grpc/scanoss/api/components/v2/scanoss-components_pb'; 2 | import { IComponentVersionResult, ILicense, IVersion } from '../iComponentCatalog/IComponentVersionResult'; 3 | 4 | export class CompoVerSearchResponseAdapter { 5 | public static convert(response: ComponentMessages.CompVersionResponse): IComponentVersionResult { 6 | const comp = response.getComponent(); 7 | const output: IComponentVersionResult = { 8 | component: comp.getComponent(), 9 | url: comp.getUrl(), 10 | purl: comp.getPurl(), 11 | versions: [], 12 | }; 13 | comp.getVersionsList().forEach((v) => { 14 | const aux: IVersion = { version: v.getVersion(), licenses: [] }; 15 | v.getLicensesList().forEach((l) => { 16 | const auxLicense: ILicense = { 17 | name: l.getName(), 18 | spdxId: l.getSpdxId(), 19 | isSpdxApproved: l.getIsSpdxApproved(), 20 | url: l.getUrl(), 21 | }; 22 | aux.licenses.push(auxLicense); 23 | }); 24 | output.versions.push(aux); 25 | }); 26 | return output; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/task/componentCatalog/builders/CompSearchRequestBuilder.ts: -------------------------------------------------------------------------------- 1 | import { ISearchComponent } from '../iComponentCatalog/ISearchComponent'; 2 | import * as ComponentMessages from '../../grpc/scanoss/api/components/v2/scanoss-components_pb'; 3 | 4 | export class CompSearchRequestBuilder { 5 | public static build(raw: ISearchComponent): ComponentMessages.CompSearchRequest { 6 | const req = new ComponentMessages.CompSearchRequest(); 7 | if (raw.search) req.setSearch(raw.search); 8 | if (raw.vendor) req.setVendor(raw.vendor); 9 | if (raw.component) req.setComponent(raw.component); 10 | if (raw.package) req.setPackage(raw.package); 11 | if (raw.limit) req.setLimit(raw.limit); 12 | return req; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/task/componentCatalog/builders/CompVerSearchRequestBuilder.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/extensions 2 | import { ISearchComponentVersion } from '../iComponentCatalog/ISearchComponentVersion'; 3 | import * as ComponentMessages from '../../grpc/scanoss/api/components/v2/scanoss-components_pb'; 4 | 5 | export class CompVerSearchRequestBuilder { 6 | public static build(raw: ISearchComponentVersion) { 7 | const req = new ComponentMessages.CompVersionRequest(); 8 | if (raw.purl) req.setPurl(raw.purl); 9 | else throw new Error('Purl is required'); 10 | if (raw.limit) req.setLimit(raw.limit); 11 | return req; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/task/componentCatalog/iComponentCatalog/IComponentResult.ts: -------------------------------------------------------------------------------- 1 | export interface IComponentResult { 2 | component: string; 3 | purl: string; 4 | url: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/main/task/componentCatalog/iComponentCatalog/IComponentVersionResult.ts: -------------------------------------------------------------------------------- 1 | export interface IComponentVersionResult { 2 | component: string; 3 | url: string; 4 | purl: string; 5 | versions: Array; 6 | } 7 | 8 | export interface IVersion { 9 | version: string; 10 | licenses: Array; 11 | } 12 | 13 | export interface ILicense { 14 | name: string; 15 | spdxId: string; 16 | isSpdxApproved: boolean; 17 | url: string; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/task/componentCatalog/iComponentCatalog/ISearchComponent.ts: -------------------------------------------------------------------------------- 1 | export interface ISearchComponent { 2 | search?: string; 3 | vendor?: string; 4 | component?: string; 5 | package?: string; 6 | limit?: number; 7 | offset?: number; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/task/componentCatalog/iComponentCatalog/ISearchComponentVersion.ts: -------------------------------------------------------------------------------- 1 | export interface ISearchComponentVersion { 2 | purl: string; 3 | limit?: number; 4 | } 5 | -------------------------------------------------------------------------------- /src/main/task/cryptography/ICryptographyTask.ts: -------------------------------------------------------------------------------- 1 | export interface ICryptographyTask { 2 | components: Array; 3 | 4 | token: string; 5 | 6 | force?: boolean; 7 | } 8 | -------------------------------------------------------------------------------- /src/main/task/grpc/scanoss/api/common/v2/scanoss-common_grpc_pb.d.ts: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO 2 | -------------------------------------------------------------------------------- /src/main/task/grpc/scanoss/api/common/v2/scanoss-common_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /src/main/task/grpc/scanoss/api/scanning/v2/scanoss-scanning_pb.d.ts: -------------------------------------------------------------------------------- 1 | // package: scanoss.api.scanning.v2 2 | // file: scanoss/api/scanning/v2/scanoss-scanning.proto 3 | 4 | import * as jspb from "google-protobuf"; 5 | import * as scanoss_api_common_v2_scanoss_common_pb from "../../../../scanoss/api/common/v2/scanoss-common_pb"; 6 | 7 | -------------------------------------------------------------------------------- /src/main/task/grpc/scanoss/api/scanning/v2/scanoss-scanning_pb.js: -------------------------------------------------------------------------------- 1 | // source: scanoss/api/scanning/v2/scanoss-scanning.proto 2 | /** 3 | * @fileoverview 4 | * @enhanceable 5 | * @suppress {messageConventions} JS Compiler reports an error if a variable or 6 | * field starts with 'MSG_' and isn't a translatable message. 7 | * @public 8 | */ 9 | // GENERATED CODE -- DO NOT EDIT! 10 | 11 | var jspb = require('google-protobuf'); 12 | var goog = jspb; 13 | var global = Function('return this')(); 14 | 15 | var scanoss_api_common_v2_scanoss$common_pb = require('../../../../scanoss/api/common/v2/scanoss-common_pb.js'); 16 | goog.object.extend(proto, scanoss_api_common_v2_scanoss$common_pb); 17 | -------------------------------------------------------------------------------- /src/main/task/inventory/AutoAcceptResult.ts: -------------------------------------------------------------------------------- 1 | export interface AutoAcceptResult { 2 | success: boolean; 3 | message: string; 4 | componentsAccepted: number; 5 | componentsRejected: number; 6 | } 7 | -------------------------------------------------------------------------------- /src/main/task/scanner/adapter/BaseScannerInputAdapter.ts: -------------------------------------------------------------------------------- 1 | import { ScannerInput } from 'scanoss'; 2 | import { IScannerInputAdapter } from './IScannerInputAdapter'; 3 | import { Project } from '../../../workspace/Project'; 4 | 5 | type EngineFlags = { 6 | engineFlags?: number; 7 | } | Record; 8 | 9 | export abstract class BaseScannerInputAdapter implements IScannerInputAdapter { 10 | protected project:Project; 11 | 12 | private readonly DOWNLOAD_URL_FLAG = 1024; 13 | 14 | protected getEngineFlags(): EngineFlags { 15 | if (this.project.getApiKey()) return { engineFlags: this.DOWNLOAD_URL_FLAG }; 16 | return {}; 17 | } 18 | 19 | public abstract adapterToScannerInput(filesToScan: Record): Promise>; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/task/scanner/adapter/IScannerInputAdapter.ts: -------------------------------------------------------------------------------- 1 | import { ScannerInput } from 'scanoss'; 2 | import { Project } from '../../../workspace/Project'; 3 | 4 | export interface IScannerInputAdapter { 5 | adapterToScannerInput(filesToScan: Record) : Promise> 6 | } 7 | -------------------------------------------------------------------------------- /src/main/task/scanner/adapter/WFPResumeScannerInputAdapter.ts: -------------------------------------------------------------------------------- 1 | import { ScannerInput } from 'scanoss'; 2 | import { IScannerInputAdapter } from './IScannerInputAdapter'; 3 | import { Project } from '../../../workspace/Project'; 4 | import { BaseScannerInputAdapter } from './BaseScannerInputAdapter'; 5 | 6 | export class WFPResumeScannerInputAdapter extends BaseScannerInputAdapter implements IScannerInputAdapter { 7 | constructor(project:Project) { 8 | super(); 9 | this.project = project; 10 | } 11 | 12 | async adapterToScannerInput(filesToScan: Record): Promise> { 13 | const pendingFiles = Object.keys(this.project.filesToScan); 14 | const totalFiles = this.project.getTree().getRootFolder().getFiles().map((fileItem) => fileItem.path); 15 | const scannedFiles = totalFiles.filter((path) => !pendingFiles.includes(path)); 16 | 17 | // @Override 18 | const scannerInput:Array = [{ 19 | fileList: scannedFiles, 20 | wfpPath: this.project.getScanRoot(), 21 | ...this.getEngineFlags(), 22 | }]; 23 | return scannerInput; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/task/scanner/adapter/WFPScannerInputAdapter.ts: -------------------------------------------------------------------------------- 1 | import { ScannerInput } from 'scanoss'; 2 | import { IScannerInputAdapter } from './IScannerInputAdapter'; 3 | import { Project } from '../../../workspace/Project'; 4 | import { BaseScannerInputAdapter } from './BaseScannerInputAdapter'; 5 | 6 | export class WFPScannerInputAdapter extends BaseScannerInputAdapter implements IScannerInputAdapter { 7 | constructor(project:Project) { 8 | super(); 9 | this.project = project; 10 | } 11 | 12 | async adapterToScannerInput(filesToScan: Record): Promise> { 13 | // @Override 14 | const scannerInput:Array = [{ 15 | fileList: [], 16 | wfpPath: this.project.getScanRoot(), 17 | ...this.getEngineFlags(), 18 | }]; 19 | return scannerInput; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/task/scanner/cryptography/definitions/CryptoDef.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/src/main/task/scanner/cryptography/definitions/CryptoDef.ts -------------------------------------------------------------------------------- /src/main/task/scanner/dispatcher/CodeDispatcher.ts: -------------------------------------------------------------------------------- 1 | import { IDispatch } from "./IDispatch"; 2 | import { Project } from "../../../workspace/Project"; 3 | 4 | export class CodeDispatcher implements IDispatch { 5 | dispatch(project:Project,file: string): void { 6 | delete project.filesToScan[`${project.getScanRoot()}${file}`]; 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/task/scanner/dispatcher/IDispatch.ts: -------------------------------------------------------------------------------- 1 | import { Project } from "../../../workspace/Project"; 2 | 3 | export interface IDispatch { 4 | dispatch(project:Project,file :string):void; 5 | } 6 | -------------------------------------------------------------------------------- /src/main/task/scanner/dispatcher/WFPDispatcher.ts: -------------------------------------------------------------------------------- 1 | import { IDispatch } from "./IDispatch"; 2 | import { Project } from "../../../workspace/Project"; 3 | 4 | export class WFPDispatcher implements IDispatch { 5 | dispatch(project: Project, file: string): void { 6 | delete project.filesToScan[file]; 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/task/scanner/rescan/CodeReScanTask.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { rescanService } from '../../../services/RescanService'; 3 | import { RescanTask } from './RescanTask'; 4 | import { CodeDispatcher } from '../dispatcher/CodeDispatcher'; 5 | import { CodeScannerInputAdapter } from '../adapter/CodeScannerInputAdapter'; 6 | import { Project } from '../../../workspace/Project'; 7 | import { utilModel } from '../../../model/UtilModel'; 8 | 9 | export class CodeReScanTask extends RescanTask { 10 | constructor(project: Project) { 11 | super(project, new CodeDispatcher(), new CodeScannerInputAdapter(project)); 12 | } 13 | 14 | public async reScan(): Promise { 15 | const resultPath = `${this.project.getMyPath()}/result.json`; 16 | const result: Record = await utilModel.readFile(resultPath); 17 | for (const [key, value] of Object.entries(result)) { 18 | if (!key.startsWith('/')) { 19 | result[`/${key}`] = value; 20 | delete result[key]; 21 | } 22 | } 23 | await fs.promises.writeFile(resultPath, JSON.stringify(result, null, 2)); 24 | await rescanService.reScan(this.project.getTree().getRootFolder().getFiles(), resultPath, this.project.getMyPath()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/task/scanner/rescan/WFPRescanTask.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { rescanService } from "../../../services/RescanService"; 3 | import { RescanTask } from "./RescanTask"; 4 | import { WFPDispatcher } from "../dispatcher/WFPDispatcher"; 5 | import { WFPScannerInputAdapter } from "../adapter/WFPScannerInputAdapter"; 6 | import { Project } from "../../../workspace/Project"; 7 | import {utilModel} from "../../../model/UtilModel"; 8 | 9 | export class WFPRescanTask extends RescanTask { 10 | 11 | constructor(project: Project) { 12 | super(project, new WFPDispatcher(), new WFPScannerInputAdapter(project)); 13 | } 14 | 15 | public async reScan(): Promise { 16 | const resultPath = `${this.project.getMyPath()}/result.json`; 17 | const result: Record = await utilModel.readFile(resultPath); 18 | for (const [key, value] of Object.entries(result)) { 19 | if(!key.startsWith("/")) { 20 | result[`/${key}`] = value; 21 | delete result[key]; 22 | } 23 | } 24 | await fs.promises.writeFile(resultPath,JSON.stringify(result,null,2)); 25 | await rescanService.reScanWFP( 26 | this.project.getTree().getRootFolder().getFiles(), 27 | resultPath 28 | ); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/task/scanner/resume/ResumeScanTask.ts: -------------------------------------------------------------------------------- 1 | import i18next from 'i18next'; 2 | import { ScannerStage, ScanState } from '../../../../api/types'; 3 | import { Scanner } from '../types'; 4 | import { CodeScanTask } from "../scan/CodeScanTask"; 5 | 6 | 7 | export class ResumeScanTask extends CodeScanTask { 8 | 9 | // @Override 10 | public getStageProperties(): Scanner.StageProperties { 11 | return { 12 | name: ScannerStage.RESUME, 13 | label: i18next.t('Title:Scanning'), 14 | isCritical: true, 15 | }; 16 | } 17 | 18 | 19 | // @Override 20 | public async set(): Promise { 21 | await this.project.open(); 22 | this.project.processedFiles = this.project.filesSummary.include - Object.keys(this.project.filesToScan).length; 23 | const scanState: ScanState = this.project.metadata.getScannerState(); 24 | if (scanState !== ScanState.SCANNING && scanState !== ScanState.RESCANNING) 25 | throw new Error('Cannot resume project'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/task/scanner/resume/WFPResumeTask.ts: -------------------------------------------------------------------------------- 1 | import {ScanState} from "../../../../api/types"; 2 | import {WFPDispatcher} from "../dispatcher/WFPDispatcher"; 3 | import {WFPResumeScannerInputAdapter} from "../adapter/WFPResumeScannerInputAdapter"; 4 | import {Project} from "../../../workspace/Project"; 5 | import {ScanTask} from "../scan/ScanTask"; 6 | 7 | 8 | export class WFPResumeTask extends ScanTask { 9 | 10 | constructor(project: Project) { 11 | super(project,new WFPDispatcher(),new WFPResumeScannerInputAdapter()); 12 | } 13 | 14 | // @Override 15 | public async set(): Promise { 16 | await this.project.open(); 17 | this.project.processedFiles = this.project.filesSummary.include - Object.keys(this.project.filesToScan).length; 18 | const scanState: ScanState = this.project.metadata.getScannerState(); 19 | if (scanState !== ScanState.SCANNING && scanState !== ScanState.RESCANNING) 20 | throw new Error('Cannot resume project'); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/task/scanner/scan/CodeScanTask.ts: -------------------------------------------------------------------------------- 1 | import { ScanTask } from './ScanTask'; 2 | import { CodeDispatcher } from '../dispatcher/CodeDispatcher'; 3 | import { CodeScannerInputAdapter } from '../adapter/CodeScannerInputAdapter'; 4 | import { Project } from '../../../workspace/Project'; 5 | 6 | export class CodeScanTask extends ScanTask { 7 | constructor(project: Project) { 8 | super(project, new CodeDispatcher(), new CodeScannerInputAdapter(project)); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/task/scanner/scan/WFPScanTask.ts: -------------------------------------------------------------------------------- 1 | import { ScanTask } from './ScanTask'; 2 | import { WFPDispatcher } from '../dispatcher/WFPDispatcher'; 3 | import { WFPScannerInputAdapter } from '../adapter/WFPScannerInputAdapter'; 4 | import { Project } from '../../../workspace/Project'; 5 | 6 | export class WFPScanTask extends ScanTask { 7 | constructor(project: Project) { 8 | super(project, new WFPDispatcher(), new WFPScannerInputAdapter(project)); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/task/scanner/scannerPipelineFactory/ScannerPipelineFactory.ts: -------------------------------------------------------------------------------- 1 | import { Scanner } from "../types"; 2 | import { ScannerPipeline } from "../scannerPipeline/ScannerPipeline"; 3 | import { CodeScannerPipelineTask } from "../scannerPipeline/CodeScannerPipelineTask"; 4 | import { WFPScannerPipeLineTask } from "../scannerPipeline/WFPScannerPipeLineTask"; 5 | 6 | export class ScannerPipelineFactory { 7 | 8 | public static getScannerPipeline(source: Scanner.ScannerSource):ScannerPipeline { 9 | switch (source) { 10 | case Scanner.ScannerSource.CODE: 11 | return new CodeScannerPipelineTask(); 12 | break; 13 | case Scanner.ScannerSource.WFP: 14 | return new WFPScannerPipeLineTask(); 15 | break; 16 | default: 17 | return null; 18 | break; 19 | } 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/task/scanner/types.ts: -------------------------------------------------------------------------------- 1 | import { ScannerStage } from '../../../api/types'; 2 | import { ITask } from '../Task'; 3 | 4 | // eslint-disable-next-line @typescript-eslint/no-namespace 5 | export namespace Scanner { 6 | export enum ScannerMode { 7 | SCAN, 8 | RESCAN, 9 | RESUME, 10 | } 11 | 12 | export enum ScannerType { 13 | CODE, 14 | DEPENDENCIES, 15 | VULNERABILITIES, 16 | UNZIP, 17 | CRYPTOGRAPHY, 18 | } 19 | 20 | export enum ScannerSource { 21 | CODE, 22 | WFP, 23 | IMPORTED, 24 | } 25 | 26 | export interface ScannerConfig { 27 | mode?: ScannerMode; 28 | type?: ScannerType[]; 29 | source?: ScannerSource; 30 | obfuscate?: boolean; 31 | hpsm?: boolean; 32 | } 33 | 34 | export interface StageProperties { 35 | name: ScannerStage; 36 | label: string; 37 | isCritical: boolean; 38 | } 39 | 40 | export interface IPipelineTask extends ITask { 41 | getStageProperties(): StageProperties; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/task/search/searchTask/ISearchResult.ts: -------------------------------------------------------------------------------- 1 | export interface ISearchResult { 2 | id: number; 3 | type: string; 4 | path: string; 5 | usage: string; 6 | status: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/main/task/search/searchTask/ISearchTask.ts: -------------------------------------------------------------------------------- 1 | export interface ISearchTask { 2 | query: string; 3 | params?: { 4 | offset?: number; 5 | limit?: number; 6 | suggest?: boolean; 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/task/vulnerability/IVulnerabilities/IGetVulnerabilities.ts: -------------------------------------------------------------------------------- 1 | export interface IGetVulnerabilities { 2 | purls: Array< 3 | { 4 | purl: string, 5 | requirement?: string; 6 | } 7 | >; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/task/vulnerability/builders/GetVulnerabilityRequestBuilder.ts: -------------------------------------------------------------------------------- 1 | import * as VulnerabilitiesMessages from '../../grpc/scanoss/api/vulnerabilities/v2/scanoss-vulnerabilities_pb'; 2 | import { IGetVulnerabilities } from '../IVulnerabilities/IGetVulnerabilities'; 3 | 4 | export class GetVulnerabilityRequestBuilder { 5 | public static build(raw: IGetVulnerabilities): VulnerabilitiesMessages.VulnerabilityRequest { 6 | const request = new VulnerabilitiesMessages.VulnerabilityRequest(); 7 | for (const item of raw.purls) { 8 | const purl = new VulnerabilitiesMessages.VulnerabilityRequest.Purls(); 9 | purl.setPurl(item.purl); 10 | if (item?.requirement) purl.setRequirement(item.requirement); 11 | request.addPurls(purl); 12 | } 13 | return request; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/threads/scanner.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Entry point of scanner subprocess 3 | */ 4 | import { DecompressThread } from './scanner/DecompressThread'; 5 | 6 | 7 | process.parentPort.once('message', async (e) => { 8 | // const [port] = e.ports; 9 | const { action, data } = e.data; 10 | if (action === 'DECOMPRESS') { 11 | const decompressThread = new DecompressThread(data); 12 | const success = await decompressThread.run(); 13 | process.parentPort.postMessage({ 14 | event: success ? 'success' : 'error', 15 | data: success 16 | }); 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /src/main/util.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/prefer-default-export: off, import/no-mutable-exports: off */ 2 | import { URL } from 'url'; 3 | import path from 'path'; 4 | 5 | export let resolveHtmlPath: (htmlFileName: string) => string; 6 | 7 | if (process.env.NODE_ENV === 'development') { 8 | const port = process.env.PORT || 1212; 9 | resolveHtmlPath = (htmlFileName: string) => { 10 | const url = new URL(`http://localhost:${port}`); 11 | url.pathname = htmlFileName; 12 | return url.href; 13 | }; 14 | } else { 15 | resolveHtmlPath = (htmlFileName: string) => { 16 | return `file://${path.resolve(__dirname, '../renderer/', htmlFileName)}`; 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/utils/utils.ts: -------------------------------------------------------------------------------- 1 | const fs = require('fs').promises; 2 | 3 | export async function fileExists(filePath: string):Promise { 4 | try { 5 | await fs.access(filePath, fs.constants.F_OK); 6 | return true; 7 | } catch (err) { 8 | return false; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/workspace/WsUtils/WsUtils.ts: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | class WsUtils { 4 | public async fileExist(file: string): Promise { 5 | return fs.promises 6 | .access(file, fs.constants.F_OK) 7 | .then(() => true) 8 | .catch(() => false); 9 | } 10 | } 11 | 12 | export const wsUtils = new WsUtils(); 13 | -------------------------------------------------------------------------------- /src/main/workspace/filtering/AbstractFilter.ts: -------------------------------------------------------------------------------- 1 | export class AbstractFilter { 2 | // path: string | undefined; 3 | condition: string; 4 | 5 | value: string; 6 | 7 | ftype: string; 8 | 9 | constructor(condition: string, value: string) { 10 | this.condition = condition; 11 | this.value = value; 12 | this.ftype = 'NONE'; 13 | } 14 | 15 | evaluate(path: string): boolean { 16 | return true; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/workspace/filters/ProjectFilter.ts: -------------------------------------------------------------------------------- 1 | import { Project } from '../Project'; 2 | 3 | export abstract class ProjectFilter { 4 | public abstract isValid(project: Project): boolean; 5 | 6 | public abstract getParam():any; 7 | } 8 | -------------------------------------------------------------------------------- /src/main/workspace/filters/ProjectFilterName.ts: -------------------------------------------------------------------------------- 1 | import { Project } from '../Project'; 2 | import { ProjectFilter } from './ProjectFilter'; 3 | 4 | export class ProjectFilterName extends ProjectFilter { 5 | private name: string; 6 | 7 | constructor(name: string) { 8 | super(); 9 | this.name = name; 10 | } 11 | 12 | public getParam(): any { 13 | return this.name; 14 | } 15 | 16 | public isValid(project: Project): boolean { 17 | return project.getProjectName() === this.name; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/workspace/filters/ProjectFilterPath.ts: -------------------------------------------------------------------------------- 1 | import { Project } from '../Project'; 2 | import { ProjectFilter } from './ProjectFilter'; 3 | 4 | export class ProjectFilterPath extends ProjectFilter { 5 | private path: string; 6 | 7 | constructor(path: string) { 8 | super(); 9 | this.path = path; 10 | } 11 | 12 | public getParam(): any { 13 | return this.path; 14 | } 15 | 16 | public isValid(project: Project): boolean { 17 | return project.getWorkRoot() === this.path; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/workspace/filters/ProjectFilterUUID.ts: -------------------------------------------------------------------------------- 1 | import { Project } from '../Project'; 2 | import { ProjectFilter } from './ProjectFilter'; 3 | 4 | export class ProjectFilterUUID extends ProjectFilter { 5 | private uuid: string; 6 | 7 | constructor(uuid: string) { 8 | super(); 9 | this.uuid = uuid; 10 | } 11 | 12 | public getParam(): any{ 13 | return this.uuid; 14 | } 15 | 16 | public isValid(project: Project): boolean { 17 | return project.getUUID() === this.uuid; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/workspace/tree/blackList/BlackListAbstract.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-cycle 2 | import Node from '../Node'; 3 | 4 | export abstract class BlackListAbstract { 5 | public abstract evaluate(node: Node): boolean; 6 | } 7 | -------------------------------------------------------------------------------- /src/main/workspace/tree/blackList/BlackListDependencies.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import { NameFilter, AbstractFilter } from '../../filtering'; 3 | import { BlackListAbstract } from './BlackListAbstract'; 4 | import Node, { NodeStatus } from '../Node'; 5 | 6 | export class BlackListDependencies extends BlackListAbstract { 7 | private filters: Array = []; 8 | 9 | public constructor(path: string) { 10 | super(); 11 | this.load(path); 12 | } 13 | 14 | private load(path: string) { 15 | const file = fs.readFileSync(path, 'utf8'); 16 | const f = JSON.parse(file); 17 | const { filters } = f; 18 | 19 | let i: number; 20 | for (i = 0; i < filters.length; i += 1) { 21 | if (filters[i].ftype === 'NAME' && filters[i].scope === 'FOLDER') this.filters.push(new NameFilter(filters[i].condition, filters[i].value, filters[i].scope)); 22 | } 23 | } 24 | 25 | public evaluate(node: Node): boolean { 26 | return this.filters.some((filter) => !filter.evaluate(node.getPath())); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/workspace/tree/blackList/ExtensionFilter.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { BlackListAbstract } from './BlackListAbstract'; 3 | import Node from '../Node'; 4 | 5 | // TODO: Change name on filters 6 | export class ExtensionFilter extends BlackListAbstract { 7 | // The file extensions must include the dot. ie .zip 8 | private fileExtensions: Array; 9 | 10 | constructor(fileExtensions: Array) { 11 | super(); 12 | this.fileExtensions = fileExtensions; 13 | } 14 | 15 | evaluate(node: Node): boolean { 16 | if (node.getType() === 'folder') return false; 17 | const nodeFileExtension = path.extname(node.getPath()); 18 | if ( 19 | this.fileExtensions.some( 20 | (supportedFormat) => supportedFormat === nodeFileExtension 21 | ) 22 | ) 23 | return false; 24 | return true; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/workspace/tree/treeViewModes/TreeViewDefault.ts: -------------------------------------------------------------------------------- 1 | import Node from '../Node'; 2 | import { TreeViewMode } from './TreeViewMode'; 3 | 4 | export class TreeViewDefault extends TreeViewMode { 5 | public async getTree(node: Node): Promise { 6 | return node; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/workspace/tree/treeViewModes/TreeViewFilter.ts: -------------------------------------------------------------------------------- 1 | import { QueryBuilderCreator } from '../../../model/queryBuilder/QueryBuilderCreator'; 2 | import { modelProvider } from '../../../services/ModelProvider'; 3 | import Node from '../Node'; 4 | import { TreeViewMode } from './TreeViewMode'; 5 | 6 | export abstract class TreeViewFilter extends TreeViewMode { 7 | protected filter: any; 8 | 9 | constructor(filter: any) { 10 | super(); 11 | this.filter = filter; 12 | } 13 | 14 | public async getFiles(): Promise> { 15 | let files: any = await modelProvider.model.result.getAll(QueryBuilderCreator.create({ ...this.filter, path: null })); 16 | files = files.reduce((acc: any, curr: any) => { 17 | if (!acc[curr.path]) acc[curr.path] = curr.id; 18 | return acc; 19 | }, {}); 20 | return files; 21 | } 22 | 23 | public abstract getTree(node: Node): Promise; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/workspace/tree/treeViewModes/TreeViewFilterNotPrune.ts: -------------------------------------------------------------------------------- 1 | import Node from '../Node'; 2 | import { TreeViewFilter } from './TreeViewFilter'; 3 | import { DependencyHighlighterVisitor } from '../visitor/DependencyHighlighterVisitor'; 4 | import { FilterNodesByStatusVisitor } from '../visitor/FilterNodesByStatusVisitor'; 5 | import { HighlightNodesByStatusVisitor } from '../visitor/HighlightNodesByStatusVisitor'; 6 | 7 | export class TreeViewFilterNotPrune extends TreeViewFilter { 8 | public async getTree(node: Node): Promise { 9 | // TODO: Adjust all the filters to the visitor pattern. 10 | if (this.filter?.usage === 'dependency') { 11 | const root = node.getClone(); 12 | root.accept(new DependencyHighlighterVisitor()); 13 | if (this.filter?.status) root.accept(new HighlightNodesByStatusVisitor(this.filter.status.toUpperCase())); 14 | return root; 15 | } 16 | 17 | const files = await this.getFiles(); 18 | const tree = node.getClone(); 19 | tree.filter(files); 20 | 21 | return tree; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/workspace/tree/treeViewModes/TreeViewFilterPrune.ts: -------------------------------------------------------------------------------- 1 | import Folder from '../Folder'; 2 | import Node from '../Node'; 3 | import { TreeViewFilter } from './TreeViewFilter'; 4 | import { OnlyKeepDependencyVisitor } from '../visitor/OnlyKeepDependencyVisitor'; 5 | import { FilterNodesByStatusVisitor } from '../visitor/FilterNodesByStatusVisitor'; 6 | 7 | export class TreeViewFilterPrune extends TreeViewFilter { 8 | public async getTree(node: Node): Promise { 9 | // TODO: Adjust all the filters to the visitor pattern. 10 | if (this.filter?.usage === 'dependency') { 11 | const root = node.getClone(); 12 | root.accept(new OnlyKeepDependencyVisitor()); 13 | if (this.filter?.status) root.accept(new FilterNodesByStatusVisitor(this.filter.status.toUpperCase())); 14 | return root; 15 | } 16 | 17 | const files = await this.getFiles(); 18 | let tree = node.getClonePath(files); 19 | if (!tree) { 20 | tree = new Folder('', node.getLabel()); 21 | } 22 | return tree; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/workspace/tree/treeViewModes/TreeViewMode.ts: -------------------------------------------------------------------------------- 1 | import Node from '../Node'; 2 | 3 | export abstract class TreeViewMode { 4 | public abstract getTree(node: Node): Promise; 5 | } 6 | -------------------------------------------------------------------------------- /src/main/workspace/tree/treeViewModes/TreeViewModeCreator.ts: -------------------------------------------------------------------------------- 1 | import { ComponentSource, FileTreeViewMode } from '../../../../api/types'; 2 | import { TreeViewMode } from './TreeViewMode'; 3 | import { TreeViewDefault } from './TreeViewDefault'; 4 | import { TreeViewFilterNotPrune } from './TreeViewFilterNotPrune'; 5 | import { TreeViewFilterPrune } from './TreeViewFilterPrune'; 6 | 7 | export class TreeViewModeCreator { 8 | public static create(filter: any, treeViewMode: FileTreeViewMode): TreeViewMode { 9 | if ( 10 | !filter || 11 | (filter.source === ComponentSource.ENGINE && Object.keys(filter).length === 1) || 12 | (filter.path && Object.keys(filter).length === 1) 13 | ) 14 | return new TreeViewDefault(); 15 | if (treeViewMode === FileTreeViewMode.DEFAULT) return new TreeViewFilterNotPrune(filter); 16 | return new TreeViewFilterPrune(filter); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/workspace/tree/visitor/FilterNodesByStatusVisitor.ts: -------------------------------------------------------------------------------- 1 | import { Visitor } from './Visitor'; 2 | import Folder from '../Folder'; 3 | import File from '../File'; 4 | import { FileStatusType } from '@api/types'; 5 | import { NodeStatus } from '../Node'; 6 | 7 | export type keep = boolean; 8 | 9 | export class FilterNodesByStatusVisitor implements Visitor { 10 | private statusToKeep: NodeStatus; 11 | 12 | constructor(statusToKeep: NodeStatus) { 13 | this.statusToKeep = statusToKeep; 14 | } 15 | 16 | VisitFile(file: File): keep { 17 | return file.getStatus() === this.statusToKeep 18 | } 19 | 20 | VisitFolder(folder: Folder): keep { 21 | const children = folder.getChildren(); 22 | 23 | for (let i = 0; i < children.length; i++) { 24 | const child = children[i]; 25 | if (child.accept(this) === false) { 26 | folder.removeChild(child); 27 | } 28 | } 29 | 30 | // If the current folder is empty, it should be deleted from the parent. return false for that. 31 | if (folder.isEmpty()) return false; 32 | return true; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/workspace/tree/visitor/OnlyKeepDependencyVisitor.ts: -------------------------------------------------------------------------------- 1 | import { Visitor } from './Visitor'; 2 | import Folder from '../Folder'; 3 | import File from '../File'; 4 | 5 | export type hasDependency = boolean; 6 | 7 | export class OnlyKeepDependencyVisitor implements Visitor { 8 | VisitFile(file: File): hasDependency { 9 | return file.isDependency(); 10 | } 11 | 12 | VisitFolder(folder: Folder): hasDependency { 13 | const children = folder.getChildren(); 14 | 15 | for (let i = 0; i < children.length; i++) { 16 | const child = children[i]; 17 | if (child.accept(this) === false) { 18 | folder.removeChild(child); 19 | } 20 | } 21 | 22 | // If the current folder is empty, it should be deleted from the parent. return false for that. 23 | if (folder.isEmpty()) return false; 24 | return true; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/workspace/tree/visitor/Visitor.ts: -------------------------------------------------------------------------------- 1 | import Folder from '../Folder'; 2 | import File from '../File'; 3 | 4 | export interface Visitor { 5 | VisitFolder(folder: Folder): T; 6 | VisitFile(file: File): T; 7 | } 8 | -------------------------------------------------------------------------------- /src/main/workspace/utils_db.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-async-promise-executor */ 2 | 3 | import fs from 'fs'; 4 | 5 | class UtilsDb { 6 | // READ RESULTS FROM A FILE 7 | async readFile(path: string) { 8 | return new Promise(async (resolve, reject) => { 9 | fs.readFile(path, async (err: any, jsonString: any) => { 10 | if (err) reject(err); 11 | const result: any = JSON.parse(jsonString); 12 | resolve(result); 13 | }); 14 | }); 15 | } 16 | } 17 | export { UtilsDb }; 18 | -------------------------------------------------------------------------------- /src/main/workspace/workspaceModel/IWorkspaceModel.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { IWorkspaceCfg } from '../../../api/types'; 3 | 4 | export interface IWorkspaceModel { 5 | 6 | 7 | getWSConfig(path: string); 8 | 9 | setWSConfig(path: string, config: Partial); 10 | 11 | setPath(path:string); 12 | } 13 | -------------------------------------------------------------------------------- /src/renderer/__mocks__/dialog-controller.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/src/renderer/__mocks__/dialog-controller.ts -------------------------------------------------------------------------------- /src/renderer/components/ConditionalLink/ConditionalLink.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | 3 | export const ConditionalLink = ({ children, to, disabled, ...props }) => ((!disabled && to) 4 | ? {children} 5 | : { children }); 6 | -------------------------------------------------------------------------------- /src/renderer/components/Info/Info.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; 3 | 4 | export interface InfoProps { 5 | message: string; 6 | icon?: any; 7 | } 8 | 9 | const Info = ({ message, icon }: InfoProps) => { 10 | 11 | 12 | return ( 13 |
14 |
15 | {icon || } 16 |

17 | { message } 18 |

19 |
20 |
21 | ); 22 | }; 23 | 24 | Info.defaultProps = { icon: null}; 25 | 26 | export default Info; 27 | -------------------------------------------------------------------------------- /src/renderer/components/Loader/Loader.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { CircularProgress } from '@mui/material'; 3 | 4 | export interface LoaderProps { 5 | message?: string; 6 | size?: number; 7 | } 8 | 9 | const Loader = ({ message, size }: LoaderProps) => { 10 | 11 | return ( 12 |
13 |
14 | 15 |

16 | { message } 17 |

18 |
19 |
20 | ); 21 | }; 22 | 23 | Loader.defaultProps = { message: 'Loading', size: 34 }; 24 | 25 | export default Loader; 26 | -------------------------------------------------------------------------------- /src/renderer/components/TableCellActions/TableCellActions.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { TableCell } from '@mui/material'; 3 | 4 | const TableCellActions = ({ children }) => { 5 | return ( 6 | 7 |
{children}
8 |
9 | ); 10 | }; 11 | 12 | export default TableCellActions; 13 | -------------------------------------------------------------------------------- /src/renderer/context/types.ts: -------------------------------------------------------------------------------- 1 | import { Inventory, ProjectAccessMode } from '@api/types'; 2 | import { Scanner } from '../../main/task/scanner/types'; 3 | 4 | export enum DIALOG_ACTIONS { 5 | OK = 'ok', 6 | CANCEL = 'cancel', 7 | NEW = 'new', 8 | } 9 | 10 | export interface DialogResponse { 11 | action: DIALOG_ACTIONS | string; 12 | data?: any | null; 13 | } 14 | export interface InventorySelectorResponse { 15 | action: DIALOG_ACTIONS; 16 | inventory?: Inventory | null; 17 | } 18 | 19 | export interface LoaderController { 20 | present: ({ message }?: { message?: React.ReactNode }) => void; 21 | finish: ({ message }: { message?: React.ReactNode }) => void; 22 | dismiss: ({ delay }?: { delay?: number }) => void; 23 | } 24 | 25 | export interface InventoryForm { 26 | id?: number; 27 | component: string; 28 | version: string; 29 | spdxid: string; 30 | url: string; 31 | purl: string; 32 | usage: string; 33 | notes: string; 34 | } 35 | 36 | export interface IScan { 37 | projectName?: string; 38 | path: string; 39 | action: string; 40 | source?: Scanner.ScannerSource, 41 | mode?: ProjectAccessMode, 42 | } 43 | 44 | -------------------------------------------------------------------------------- /src/renderer/controllers/dialog-controller.ts: -------------------------------------------------------------------------------- 1 | import { IpcChannels } from '@api/ipc-channels'; 2 | 3 | class DialogController { 4 | public async showOpenDialog(options): Promise { 5 | return window.electron.ipcRenderer.invoke(IpcChannels.DIALOG_SHOW_OPEN_DIALOG, options); 6 | } 7 | 8 | public showSaveDialog(options): Promise { 9 | return window.electron.ipcRenderer.invoke(IpcChannels.DIALOG_SHOW_SAVE_DIALOG, options); 10 | } 11 | 12 | public showError(title: string, content: string): Promise { 13 | return window.electron.ipcRenderer.invoke(IpcChannels.DIALOG_SHOW_ERROR_BOX, title, content); 14 | } 15 | } 16 | 17 | export const dialogController = new DialogController(); 18 | -------------------------------------------------------------------------------- /src/renderer/controllers/home-controller.ts: -------------------------------------------------------------------------------- 1 | import { projectService } from '@api/services/project.service'; 2 | import { workspaceService } from '@api/services/workspace.service'; 3 | import { INewProject } from '@api/types'; 4 | 5 | export const scan = async (project: INewProject) => { 6 | await projectService.create(project); 7 | }; 8 | 9 | export const resume = async (path: string) => { 10 | const response = await projectService.resume(path); 11 | }; 12 | 13 | export const rescan = async (path: string) => { 14 | await projectService.rescan(path); 15 | }; 16 | 17 | export const open = async (path: string) => { 18 | const response = await projectService.load(path); 19 | return response; 20 | }; 21 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/Workbench.scss: -------------------------------------------------------------------------------- 1 | .workbench-layout { 2 | display: grid; 3 | grid-template-rows: auto 1fr; 4 | height: 100%; 5 | } 6 | 7 | .Pane2 { 8 | overflow: hidden; 9 | } 10 | 11 | .workbench { 12 | height: 100%; 13 | background-color: var(--background-color-primary); 14 | position: relative; 15 | } 16 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/components/BaseCard/BaseCard.scss: -------------------------------------------------------------------------------- 1 | .base-card { 2 | width: 100%; 3 | height: 100%; 4 | 5 | .base-card__button { 6 | width: 100%; 7 | height: 100%; 8 | } 9 | 10 | .base-card__content { 11 | padding-top: 0; 12 | padding-bottom: 0 !important; 13 | height: 100%; 14 | width: 100%; 15 | font-size: 16px; 16 | 17 | display: flex; 18 | flex-direction: column; 19 | justify-content: space-between; 20 | 21 | 22 | .base-card__files { 23 | border-top: 1px solid #E4E4E7; 24 | padding: 10px 0; 25 | display: flex; 26 | 27 | &.pending .info-count.pending { opacity: 1; } 28 | &.original .info-count.ignored { opacity: 1; } 29 | &.identified .info-count.identified { opacity: 1; } 30 | 31 | .info-count { 32 | display: flex; 33 | align-items: center; 34 | margin-right: 10px; 35 | font-size: 16px; 36 | font-weight: 400; 37 | } 38 | } 39 | } 40 | 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/components/DependencyManifestFileCard/DependencyManifestFileCard.scss: -------------------------------------------------------------------------------- 1 | .dependency-card-content{ 2 | 3 | height: 65px; 4 | 5 | .content{ 6 | height: 100%; 7 | justify-content: center; 8 | align-items: center; 9 | display: flex; 10 | line-break: anywhere; 11 | >span{ 12 | font-size: 13px; 13 | color: #3a3a3a; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/components/DependencyManifestFileCard/DependencyManifestFileCard.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { DependencyManifestFile } from '@api/types'; 3 | import { Button, Tooltip } from '@mui/material'; 4 | import GavelIcon from '@mui/icons-material/Gavel'; 5 | import path from 'path'; 6 | 7 | interface DependencyManifestFileCardProps { 8 | dependencyManifestFile: DependencyManifestFile; 9 | } 10 | 11 | const DependencyManifestFileCard = ({ dependencyManifestFile }: DependencyManifestFileCardProps) => { 12 | // const dependencyName = /[^/]+$/.exec(dependencyManifestFile.path); 13 | const dependencyName = dependencyManifestFile.path; 14 | return ( 15 | 16 |
17 |
18 | {dependencyName} 19 |
20 |
21 |
22 | ); 23 | }; 24 | 25 | export default DependencyManifestFileCard; 26 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/components/EmptyMessagePlaceholder/EmptyMessagePlaceholder.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Box, Typography } from '@mui/material'; 3 | 4 | const EmptyMessagePlaceholder = ({ children }) => { 5 | 6 | return ( 7 | 15 | 24 | {children} 31 | 32 | 33 | ); 34 | }; 35 | 36 | export default EmptyMessagePlaceholder; 37 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/components/FileToolbar/FileToolbar.scss: -------------------------------------------------------------------------------- 1 | .label-card { 2 | background-color: #ffff; 3 | width: 100%; 4 | display: flex; 5 | align-items: center; 6 | border-radius: 4px; 7 | box-shadow: 1px 0px 2px rgba(0, 0, 0, 0.06), 0px 1px 3px rgba(0, 0, 0, 0.1); 8 | 9 | @media screen and (max-width: $md) { 10 | width: 100%; 11 | margin-top: 20px; 12 | } 13 | 14 | .label-card-content { 15 | width: 100%; 16 | display: flex; 17 | justify-content: space-between; 18 | align-items: center; 19 | padding: 6px 12px; 20 | gap: 10px; 21 | 22 | .MuiListItemText-root { 23 | margin: 0 6px; 24 | 25 | .MuiListItemText-primary { 26 | font-size: 0.85rem; 27 | font-weight: 500; 28 | } 29 | 30 | .MuiListItemText-secondary { 31 | @extend .selectable; 32 | font-size: 0.75rem; 33 | 34 | -webkit-line-clamp: 1; 35 | -webkit-box-orient: vertical; 36 | overflow: hidden; 37 | text-overflow: ellipsis; 38 | display: -webkit-box; 39 | word-break: break-all; 40 | } 41 | } 42 | 43 | .actions { 44 | opacity: 0.5; 45 | display: flex; 46 | gap: 2px; 47 | } 48 | } 49 | 50 | &:hover .actions {opacity: 1;} 51 | } 52 | 53 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/components/IconComponent/IconComponent.scss: -------------------------------------------------------------------------------- 1 | #IconComponent { 2 | figure { 3 | text-align: center; 4 | font-size: 12px; 5 | margin: 5px auto; 6 | 7 | img { 8 | width: 100%; 9 | height: 100%; 10 | background-image: url('/assets/imgs/component-default.svg'); 11 | background-size: contain; 12 | background-repeat: no-repeat; 13 | } 14 | } 15 | } 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/components/IconComponent/IconComponent.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import componentDefault from '../../../../../../assets/imgs/component-default.svg'; 3 | import AppConfig from '../../../../../config/AppConfigModule'; 4 | 5 | const IconComponent = ({ name, size }) => { 6 | return ( 7 |
8 |
9 | {AppConfig.FF_ENABLE_COMPONENT_LOGO ? ( 10 | component logo { 17 | event.target.style.backgroundImage = 'none'; 18 | }} 19 | onError={(event: any) => { 20 | event.target.src = componentDefault; 21 | event.onerror = null; 22 | }} 23 | /> 24 | ) : ( 25 | logo 26 | )} 27 |
28 |
29 | ); 30 | }; 31 | 32 | export default IconComponent; 33 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/components/InventorySelectorDialog/InventorySelectorDialog.scss: -------------------------------------------------------------------------------- 1 | #InventorySelectorDialog { 2 | 3 | .list-groups { 4 | margin-top: 16px; 5 | } 6 | 7 | .usage-card { 8 | margin-bottom: 12px; 9 | cursor: pointer; 10 | box-sizing: border-box; 11 | border: transparent 2px solid; 12 | overflow: hidden; 13 | 14 | display: flex; 15 | justify-content: space-between; 16 | align-items: center; 17 | 18 | padding-right: 10px; 19 | 20 | .icon { 21 | opacity: 0; 22 | transition: opacity 200ms ease; 23 | } 24 | 25 | &:hover .icon, &.selected .icon { 26 | opacity: .8; 27 | } 28 | 29 | &.selected { 30 | border: #60A5FA 2px solid; 31 | } 32 | 33 | .usage-card-content { 34 | padding: 10px; 35 | font-size: 24px; 36 | font-weight: 400; 37 | text-transform: capitalize; 38 | 39 | border-left: 10px solid $indentified-green; 40 | } 41 | 42 | 43 | 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/components/Label/Label.scss: -------------------------------------------------------------------------------- 1 | @mixin label-boxie { 2 | font-size: 14px; 3 | font-weight: 500; 4 | 5 | -webkit-line-clamp: 2; 6 | -webkit-box-orient: vertical; 7 | overflow: hidden; 8 | text-overflow: ellipsis; 9 | display: -webkit-box; 10 | } 11 | 12 | .label-boxie-gray { 13 | @include label-boxie; 14 | color: $ignored-gray; 15 | } 16 | 17 | .label-boxie-black { 18 | @include label-boxie; 19 | color: $nomatch-black; 20 | } 21 | 22 | .label-boxie-orange { 23 | @include label-boxie; 24 | color: $pending-orange; 25 | } 26 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/components/Label/Label.tsx: -------------------------------------------------------------------------------- 1 | import { Tooltip } from '@mui/material'; 2 | import React from 'react'; 3 | 4 | interface LabelType { 5 | label: string | null; 6 | textColor: string | null; 7 | tooltip?: boolean; 8 | } 9 | 10 | const Label = ({ label, textColor, tooltip }: LabelType) => { 11 | return ( 12 | <> 13 | {tooltip ? ( 14 | 15 | {label} 16 | 17 | ) : ( 18 | {label} 19 | )} 20 | 21 | ); 22 | }; 23 | 24 | Label.defaultProps = { 25 | tooltip: false, 26 | }; 27 | 28 | export default Label; 29 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/components/MenuButton/MenuButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { IconButton, Menu, MenuItem } from '@mui/material'; 3 | import { useTranslation } from 'react-i18next'; 4 | 5 | const MenuButton = () => { 6 | const { t } = useTranslation(); 7 | 8 | const [anchorEl, setAnchorEl] = React.useState(null); 9 | const open = Boolean(anchorEl); 10 | 11 | const handleClick = (event: React.MouseEvent) => { 12 | setAnchorEl(event.currentTarget); 13 | }; 14 | 15 | const handleClose = () => { 16 | setAnchorEl(null); 17 | }; 18 | 19 | return ( 20 | <> 21 | 22 | 23 | 24 | 30 | {t('APPMenu:IdentifyAllAs', { context: 'nofilter'})} 31 | {t('APPMenu:MarkAllAsOriginal', { context: 'nofilter'})} 32 | 33 | 34 | ); 35 | }; 36 | 37 | export default MenuButton; 38 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/components/NoMatchFound/NoMatchFound.scss: -------------------------------------------------------------------------------- 1 | .no-match-container { 2 | border: 1px solid gray; 3 | border-style: dashed; 4 | padding: 0 12px; 5 | background-color: #ffffff7a; 6 | display: flex; 7 | align-items: center; 8 | border-radius: 4px; 9 | box-shadow: 1px 0px 2px rgba(0, 0, 0, 0.06), 0px 1px 3px rgba(0, 0, 0, 0.1); 10 | 11 | .no-match-content { 12 | width: 100%; 13 | display: flex; 14 | align-items: center; 15 | justify-content: space-between; 16 | 17 | .no-match-title { 18 | font-size: 16px; 19 | margin: 0; 20 | } 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/components/NoMatchFound/NoMatchFound.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button } from '@mui/material'; 3 | import { useTranslation } from 'react-i18next'; 4 | import useMode from '@hooks/useMode'; 5 | 6 | const NoMatchFound = ({ identifyHandler, showLabel }) => { 7 | const { t } = useTranslation(); 8 | const { props } = useMode(); 9 | 10 | return ( 11 |
12 |
13 | {showLabel &&

{t('Title:NoMatchFound')}

} 14 | 17 |
18 |
19 | ); 20 | }; 21 | 22 | export default NoMatchFound; 23 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/components/Pill/Pill.scss: -------------------------------------------------------------------------------- 1 | @mixin pill { 2 | border-radius: 10%; 3 | padding: 3px; 4 | font-size: 15px; 5 | } 6 | 7 | .pending-pill { 8 | @include pill; 9 | border: 1px solid $pending-orange; 10 | color: $pending-orange; 11 | } 12 | 13 | .identified-pill { 14 | @include pill; 15 | border: 1px solid $indentified-green; 16 | color: $indentified-green; 17 | } 18 | 19 | .ignored-pill { 20 | @include pill; 21 | border: 1px solid $ignored-gray; 22 | color: $ignored-gray; 23 | } 24 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/components/Pill/Pill.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-nested-ternary */ 2 | import React from 'react'; 3 | 4 | interface PillType { 5 | state: string; 6 | } 7 | 8 | const Pill = ({ state }: PillType) => { 9 | return ( 10 |
21 | {state} 22 |
23 | ); 24 | }; 25 | 26 | export default Pill; 27 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/components/Title/Title.scss: -------------------------------------------------------------------------------- 1 | .title-component-vendor { 2 | font-size: 26px; 3 | font-weight: 600; 4 | color: $blue-scanoss; 5 | } 6 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/components/Title/Title.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-nested-ternary */ 2 | import React from 'react'; 3 | 4 | interface TitleType { 5 | title: string; 6 | } 7 | 8 | const Title = ({ title }: TitleType) => { 9 | return {title}; 10 | }; 11 | 12 | export default Title; 13 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/pages/detected/Detected.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, Routes } from 'react-router-dom'; 3 | import FilterSnackbar from './components/FilterSnackbar'; 4 | import { ComponentDetail } from './pages/ComponentDetail/ComponentDetail'; 5 | import { ComponentList } from './pages/ComponentList/ComponentList'; 6 | import FileViewer from './pages/FileViewer/FileViewer'; 7 | 8 | const Detected = () => ( 9 | <> 10 | 11 | } /> 12 | } /> 13 | } /> 14 | 15 | 16 | 17 | 18 | ); 19 | 20 | export default Detected; 21 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/pages/detected/pages/ComponentList/components/EmptyResult/EmptyResult.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import componentEmpty from '@assets/imgs/component-empty.svg'; 3 | import { Box, Typography } from '@mui/material'; 4 | 5 | const EmptyResult = ({ children }) => { 6 | return ( 7 | 15 | 24 | components empty icon 25 | {children} 31 | 32 | 33 | 34 | ); 35 | }; 36 | 37 | export default EmptyResult; 38 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/pages/detected/pages/Dependency/Dependency.scss: -------------------------------------------------------------------------------- 1 | #Dependency { 2 | display: grid; 3 | 4 | .tabs { 5 | .MuiTab-root { 6 | min-width: 120px; 7 | 8 | &:first-child { 9 | min-width: 60px; 10 | } 11 | } 12 | } 13 | 14 | .app-header { 15 | padding-bottom: 0; 16 | 17 | .search-box { 18 | width: 650px; 19 | margin-bottom: 16px; 20 | display: flex; 21 | gap: 1em; 22 | 23 | .search-bar { 24 | width: 55%; 25 | } 26 | 27 | .filter-box { 28 | width: 40%; 29 | } 30 | 31 | } 32 | .subheader { 33 | display: flex; 34 | justify-content: space-between; 35 | align-items: center; 36 | 37 | margin-top: 16px; 38 | } 39 | } 40 | 41 | .dependencies-tree-header { 42 | display: flex; 43 | justify-content: space-between; 44 | align-items: center; 45 | } 46 | } 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/pages/detected/pages/Dependency/components/NoLocalFile/NoLocalFile.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import InsertDriveFileOutlinedIcon from '@mui/icons-material/InsertDriveFileOutlined'; 3 | 4 | const NoLocalFile = () => ( 5 |
6 | 7 |

This project was imported.

Source file can't be displayed.

8 |
9 | ); 10 | 11 | export default NoLocalFile; 12 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/pages/detected/pages/Editor/CodeViewerManager.ts: -------------------------------------------------------------------------------- 1 | import * as monaco from 'monaco-editor'; 2 | 3 | export class CodeViewerManager { 4 | public static readonly LEFT: string = 'left'; 5 | 6 | public static readonly RIGHT: string = 'right'; 7 | 8 | public static readonly MAIN: string = 'main'; 9 | 10 | private mosaic: Map; 11 | 12 | constructor() { 13 | this.mosaic = new Map(); 14 | this.mosaic.set(CodeViewerManager.LEFT, null); 15 | this.mosaic.set(CodeViewerManager.RIGHT, null); 16 | this.mosaic.set(CodeViewerManager.MAIN, null); 17 | } 18 | 19 | public set(key: string, editor: monaco.editor.IStandaloneCodeEditor | null): void { 20 | this.mosaic.set(key, editor); 21 | } 22 | 23 | public get(key: string): monaco.editor.IStandaloneCodeEditor | null { 24 | return this.mosaic.get(key); 25 | } 26 | } 27 | 28 | const CodeViewerManagerInstance = new CodeViewerManager(); 29 | 30 | export default CodeViewerManagerInstance; 31 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/pages/detected/pages/Editor/components/NoLocalFile/NoLocalFile.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import InsertDriveFileOutlinedIcon from '@mui/icons-material/InsertDriveFileOutlined'; 3 | import { useTranslation } from 'react-i18next'; 4 | 5 | const NoLocalFile = () => { 6 | const { t } = useTranslation(); 7 | 8 |
9 | 10 |

{t('ProjectImportedHint')}

11 |
12 | }; 13 | 14 | export default NoLocalFile; 15 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/pages/detected/pages/FileViewer/FileViewer.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { useSelector } from 'react-redux'; 3 | import { selectWorkbench } from '@store/workbench-store/workbenchSlice'; 4 | import { selectNavigationState } from '@store/navigation-store/navigationSlice'; 5 | import DependencyViewer from '../Dependency/Dependency'; 6 | import Editor from '../Editor/Editor'; 7 | 8 | enum FileType { 9 | code, 10 | dependencies, 11 | } 12 | 13 | const FileViewer = () => { 14 | const { dependencies } = useSelector(selectWorkbench); 15 | const { node } = useSelector(selectNavigationState); 16 | 17 | const file = node?.type === 'file' ? node.path : null; 18 | const [fileType, setFileType] = React.useState(null); 19 | 20 | useEffect(() => { 21 | const dep = dependencies.includes(file); 22 | setFileType(dep ? FileType.dependencies : FileType.code); 23 | }, [file]); 24 | 25 | return ( 26 | <> 27 | {fileType === FileType.code && } 28 | {fileType === FileType.dependencies && } 29 | 30 | ); 31 | }; 32 | 33 | export default FileViewer; 34 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/pages/identified/Identified.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, Routes } from 'react-router-dom'; 3 | import { InventoryDetail } from './pages/InventoryDetail/InventoryDetail'; 4 | import { InventoryList } from './pages/InventoryList/InventoryList'; 5 | import { IdentifiedList } from './pages/IdentifiedList/IdentifiedList'; 6 | 7 | const Identified = () => ( 8 | 9 | } /> 10 | } /> 11 | } /> 12 | 13 | ); 14 | 15 | export default Identified; 16 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/pages/identified/pages/IdentifiedList/IdentifiedList.scss: -------------------------------------------------------------------------------- 1 | .identified-list { 2 | display: grid; 3 | grid-template-columns: repeat(auto-fill, 200px); 4 | grid-row-gap: 1.5em; 5 | grid-column-gap: 1.5em; 6 | overflow-y: scroll; 7 | //padding-bottom: 10px; 8 | } 9 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/pages/identified/pages/InventoryDetail/InventoryDetail.scss: -------------------------------------------------------------------------------- 1 | .identified-info-card { 2 | position: relative; 3 | width: 75%; 4 | max-width: 600px; 5 | min-width: 400px; 6 | min-height: 130px; 7 | border-radius: 4px; 8 | padding: 16px; 9 | background-color: #ffff; 10 | box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.1), 0px 1px 2px 0px rgba(0, 0, 0, 0.06); 11 | 12 | .identified { 13 | margin-bottom: 25px; 14 | } 15 | 16 | .actions { 17 | display: flex; 18 | position: absolute; 19 | top: 8px; 20 | right: 8px; 21 | } 22 | 23 | .info { 24 | margin: 0px 25px 20px 0; 25 | 26 | h4 { 27 | margin: 0; 28 | } 29 | 30 | .notes { 31 | font-style: italic; 32 | } 33 | } 34 | 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/pages/report/Report.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Navigate, Route, Routes } from 'react-router-dom'; 3 | import VulnerabilitiesReport from './pages/vulnerabilities/VulnerabilitiesReport'; 4 | import ScanReport from './pages/scan/ScanReport'; 5 | import CryptographyReport from './pages/crypthographies/CrypthographyReport'; 6 | 7 | const Reports = () => ( 8 | 9 | } /> 10 | } /> 11 | } /> 12 | } /> 13 | 14 | ); 15 | 16 | export default Reports; 17 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/pages/report/components/CryptoChart.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { Chart } from 'chart.js' 3 | 4 | const CryptoChart = ({ data }) => { 5 | const chartRef = React.createRef(); 6 | 7 | useEffect( () => { 8 | const chart = new Chart(chartRef.current, { 9 | type: 'bar', 10 | data: { 11 | labels: data.map(d => d.label), 12 | datasets: [{ 13 | label: 'example', 14 | data: data.map(d => d.value), 15 | borderWidth: 0, 16 | backgroundColor: [ 17 | 'rgb(255, 99, 132)', 18 | 'rgb(54, 162, 235)', 19 | 'rgb(255, 205, 86)', 20 | 'rgb(103,86,255)', 21 | 'rgb(97,255,86)', // TODO: estos se tienen q generar automaticamente 22 | ], 23 | }] 24 | }, 25 | options: { 26 | plugins: { 27 | legend: { position: 'right'} 28 | } 29 | } 30 | }); 31 | 32 | return () => chart.destroy(); 33 | 34 | }, [data]); 35 | 36 | return ( 37 | 38 | ); 39 | }; 40 | 41 | export default CryptoChart; 42 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/pages/report/components/CryptographyCard.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const CryptographyCard = ({ data }) => ( 4 |
7 |
8 |
9 | 10 | {data.sbom + data.local} 11 | 12 | 13 | crypto algorithms detected 14 | 15 |
16 |
17 |
18 | ); 19 | 20 | export default CryptographyCard; 21 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/pages/report/components/DependenciesCard.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const DependenciesCard = ({ data }) => ( 4 |
7 |
8 |
9 | 10 | {data.total || ' - '} 11 | 12 | 13 | {data.files?.length > 0 14 | ? (<>found in {data.files?.length} manifest files) 15 | : (<>No manifest files found)} 16 | 17 |
18 |
19 |
20 | ); 21 | 22 | export default DependenciesCard; 23 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/pages/report/components/LicensesTable.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/no-static-element-interactions */ 2 | /* eslint-disable jsx-a11y/click-events-have-key-events */ 3 | import React from 'react'; 4 | import { getColor } from '@shared/utils/utils'; 5 | 6 | const LicensesTable = ({ data, selectLicense, matchedLicenseSelected }) => { 7 | return ( 8 |
9 | {data.map((item, index) => { 10 | return ( 11 |
selectLicense(item.label)} 13 | key={index} 14 | className={`license-list-item ${ 15 | matchedLicenseSelected && matchedLicenseSelected.label === item.label ? 'license-list-item-selected' : '' 16 | }`} 17 | > 18 | 19 | {item.label} 20 |
21 | ); 22 | })} 23 |
24 | ); 25 | }; 26 | 27 | export default LicensesTable; 28 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/pages/report/components/VulnerabilitiesCard.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const VulnerabilitiesCard = ({ data }) => ( 4 |
7 |
8 |
9 | 10 | {data?.critical} 11 | 12 | Critical 13 |
14 |
15 | 16 | {data?.high} 17 | 18 | High 19 |
20 |
21 | 22 | {data?.medium} 23 | 24 | Medium 25 |
26 |
27 | 28 | {data?.low} 29 | 30 | Low 31 |
32 |
33 |
34 | ); 35 | 36 | export default VulnerabilitiesCard; 37 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/pages/report/pages/crypthographies/CryptographyReport.scss: -------------------------------------------------------------------------------- 1 | #CryptographyReportPage { 2 | display: grid; 3 | 4 | 5 | .app-header { 6 | padding-bottom: 10px; 7 | position: unset; 8 | z-index: 10; 9 | 10 | .subheader { 11 | display: flex; 12 | justify-content: space-between; 13 | align-items: center; 14 | } 15 | } 16 | 17 | .form-row.filter { 18 | .form-group { 19 | min-width: 280px; 20 | max-width: 600px; 21 | 22 | &.filter-algorithm { 23 | width: auto; 24 | 25 | .MuiAutocomplete-option { 26 | padding-top: 0; 27 | padding-bottom: 0; 28 | } 29 | 30 | .tag:not(.option) { 31 | padding: 0; 32 | } 33 | 34 | .tag.option { 35 | font-size: 12px; 36 | } 37 | } 38 | } 39 | } 40 | 41 | .algorithms { 42 | display: flex; 43 | gap: 4px; 44 | flex-wrap: wrap; 45 | 46 | .tag { 47 | padding: 2px 8px; 48 | border-radius: 4px; 49 | font-weight: 400; 50 | background: #e4e4e7; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/pages/report/pages/scan/components/ExportButton.scss: -------------------------------------------------------------------------------- 1 | #ExportButton { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/renderer/features/workbench/pages/search/Search.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, Routes } from 'react-router-dom'; 3 | import { useTranslation } from 'react-i18next'; 4 | import FileViewer from '../detected/pages/FileViewer/FileViewer'; 5 | import EmptyMessagePlaceholder from '../../components/EmptyMessagePlaceholder/EmptyMessagePlaceholder'; 6 | 7 | const Search = () => { 8 | const { t } = useTranslation(); 9 | return ( 10 | <> 11 | 12 | {t('UseLeftPanelForSearch')} 16 | } 17 | /> 18 | } /> 19 | 20 | 21 | ); 22 | }; 23 | 24 | export default Search; 25 | -------------------------------------------------------------------------------- /src/renderer/features/workspace/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, Routes } from 'react-router-dom'; 3 | import ProjectDrop from './pages/ProjectDrop/ProjectDrop'; 4 | import ProjectScan from './pages/ProjectScan/ProjectScan'; 5 | import ProjectSettings from './pages/ProjectSettings/ProjectSettings'; 6 | import Workspace from './pages/Workspace/Workspace'; 7 | 8 | const WorkspaceModule = () => { 9 | return ( 10 | 11 | } /> 12 | } /> 13 | } /> 14 | } /> 15 | 16 | ); 17 | }; 18 | 19 | export default WorkspaceModule; 20 | -------------------------------------------------------------------------------- /src/renderer/features/workspace/pages/ProjectDrop/ProjectDrop.scss: -------------------------------------------------------------------------------- 1 | #ProjectDrop { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/renderer/features/workspace/pages/ProjectDrop/ProjectDrop.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { IconButton, Link } from '@mui/material'; 3 | import ArrowBackIcon from '@mui/icons-material/ArrowBack'; 4 | import { useNavigate } from 'react-router-dom'; 5 | import { AppContext, IAppContext } from '@context/AppProvider'; 6 | 7 | const ProjectDrop = () => { 8 | const navigate = useNavigate(); 9 | const { newProject } = useContext(AppContext) as IAppContext; 10 | 11 | const onSelectProjectHandler = () => { 12 | newProject(); 13 | }; 14 | 15 | return <> 16 |
17 |
18 |
19 |

20 | navigate(-1)} component="span" size="large"> 21 | 22 | 23 | New Project 24 |

25 | {/*

New Project

*/} 26 |
27 |
28 |
29 | Select folder 30 |
31 |
32 | ; 33 | }; 34 | 35 | export default ProjectDrop; 36 | -------------------------------------------------------------------------------- /src/renderer/features/workspace/pages/ProjectScan/ProjectScan.scss: -------------------------------------------------------------------------------- 1 | #ProjectScan { 2 | display: grid; 3 | 4 | header.app-header { 5 | h1 { 6 | margin: 0; 7 | } 8 | } 9 | 10 | .app-content { 11 | display: flex; 12 | flex-direction: column; 13 | justify-content: center; 14 | align-items: center; 15 | 16 | .circular-progress-container { 17 | display: flex; 18 | flex-direction: column; 19 | justify-content: center; 20 | align-items: center; 21 | } 22 | 23 | .progressbar { 24 | min-height: 30px; 25 | } 26 | 27 | .stage-label { 28 | font-weight: 600; 29 | text-transform: uppercase; 30 | margin-bottom: 60px; 31 | text-align: center; 32 | padding-top: 10px; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/renderer/hooks/useApi.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | export default function useApi() { 4 | const [data, setData] = useState(null); 5 | const [error, setError] = useState(null); 6 | const [loading, setLoading] = useState(false); 7 | 8 | const execute = async (call: () => Promise) => { 9 | try { 10 | setError(false); 11 | setLoading(true); 12 | const response = await call(); 13 | setData(response); 14 | } catch (err) { 15 | setError(err); 16 | } finally { 17 | setLoading(false); 18 | } 19 | }; 20 | 21 | return { data, error, loading, execute }; 22 | } 23 | -------------------------------------------------------------------------------- /src/renderer/hooks/useMode.tsx: -------------------------------------------------------------------------------- 1 | import { selectIsReadOnly } from '@store/workbench-store/workbenchSlice'; 2 | import { useSelector } from 'react-redux'; 3 | 4 | export default function useMode() { 5 | const isReadOnly = useSelector(selectIsReadOnly); 6 | 7 | return { 8 | isReadOnly, 9 | props: { disabled: isReadOnly }, 10 | menu: { enabled: !isReadOnly } 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /src/renderer/hooks/usePagination.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | const usePagination = (pageSize = 80) => { 4 | const [page, setPage] = useState(1); 5 | const limit = page * pageSize; 6 | 7 | const paginate = () => { 8 | setPage(page + 1); 9 | }; 10 | 11 | const onScroll = (e) => { 12 | const isBottom = e.target.scrollHeight - e.target.scrollTop <= e.target.clientHeight + 50; 13 | if (isBottom) { 14 | paginate(); 15 | } 16 | }; 17 | 18 | return { 19 | page, 20 | limit, 21 | paginate, 22 | onScroll, 23 | }; 24 | }; 25 | 26 | export default usePagination; 27 | -------------------------------------------------------------------------------- /src/renderer/hooks/useSearchParams.tsx: -------------------------------------------------------------------------------- 1 | import { useLocation } from 'react-router-dom'; 2 | import React from 'react'; 3 | 4 | const useSearchParams = () => { 5 | const { search } = useLocation(); 6 | return React.useMemo(() => new URLSearchParams(search), [search]); 7 | }; 8 | 9 | export default useSearchParams; 10 | -------------------------------------------------------------------------------- /src/renderer/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 13 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /src/renderer/index.tsx: -------------------------------------------------------------------------------- 1 | import App from './App'; 2 | 3 | const app = new App(); 4 | app.setup(); 5 | 6 | /* 7 | TODO: workaround to solve Monaco bug on Chromium 114. 8 | See: https://github.com/microsoft/vscode/pull/183325 9 | */ 10 | 11 | // Save a reference to the original ResizeObserver 12 | const OriginalResizeObserver = window.ResizeObserver; 13 | 14 | // Create a new ResizeObserver constructor 15 | // @ts-ignore 16 | window.ResizeObserver = function (callback) { 17 | const wrappedCallback = (entries, observer) => { 18 | window.requestAnimationFrame(() => { 19 | callback(entries, observer); 20 | }); 21 | }; 22 | 23 | // Create an instance of the original ResizeObserver 24 | // with the wrapped callback 25 | return new OriginalResizeObserver(wrappedCallback); 26 | }; 27 | 28 | // Copy over static methods, if any 29 | for (const staticMethod in OriginalResizeObserver) { 30 | // eslint-disable-next-line no-prototype-builtins 31 | if (OriginalResizeObserver.hasOwnProperty(staticMethod)) { 32 | window.ResizeObserver[staticMethod] = OriginalResizeObserver[staticMethod]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/renderer/store/dependency-store/dependencyMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { createListenerMiddleware, isAnyOf } from '@reduxjs/toolkit'; 2 | import { acceptAll, getAll, rejectAll, restoreAll } from '@store/dependency-store/dependencyThunks'; 3 | import { RootState } from '@store/rootReducer'; 4 | 5 | export const dependencyMiddleware = createListenerMiddleware(); 6 | 7 | dependencyMiddleware.startListening({ 8 | matcher: isAnyOf(acceptAll.fulfilled, rejectAll.fulfilled, restoreAll.fulfilled), 9 | effect: async (action, listenerApi) => { 10 | const state = listenerApi.getState() as RootState; 11 | listenerApi.dispatch(getAll(state.navigation.node.path)); 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /src/renderer/store/inventory-store/inventoryMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { createListenerMiddleware, isAnyOf } from '@reduxjs/toolkit'; 2 | import { createInventory, updateInventory } from '@store/inventory-store/inventoryThunks'; 3 | import { setRecentComponent } from '@store/component-store/componentSlice'; 4 | 5 | export const inventoryMiddleware = createListenerMiddleware(); 6 | 7 | inventoryMiddleware.startListening({ 8 | matcher: isAnyOf(createInventory.fulfilled, updateInventory.fulfilled), 9 | effect: async (action, listenerApi) => { 10 | const { purl } = action.meta.arg; 11 | listenerApi.dispatch(setRecentComponent(purl)); 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /src/renderer/store/inventory-store/inventorySlice.ts: -------------------------------------------------------------------------------- 1 | import { createListenerMiddleware, createSlice } from '@reduxjs/toolkit'; 2 | import { createInventory } from './inventoryThunks'; 3 | 4 | export interface InventoryState { 5 | loading: boolean; 6 | } 7 | 8 | const initialState: InventoryState = { 9 | loading: false, 10 | }; 11 | 12 | const inventorySlice = createSlice({ 13 | name: 'inventory', 14 | initialState, 15 | reducers: { }, 16 | extraReducers: { 17 | [createInventory.pending.type]: (state) => { 18 | state.loading = true; 19 | }, 20 | [createInventory.fulfilled.type]: (state) => { 21 | state.loading = false; 22 | }, 23 | [createInventory.rejected.type]: (state) => { 24 | state.loading = false; 25 | }, 26 | }, 27 | }); 28 | 29 | // actions 30 | 31 | export default inventorySlice.reducer; 32 | -------------------------------------------------------------------------------- /src/renderer/store/navigation-store/navigationThunks.ts: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk } from '@reduxjs/toolkit'; 2 | import { projectService } from '@api/services/project.service'; 3 | 4 | /* 5 | export const setFilter = createAsyncThunk('navigation/setFilter', async (filter: any) => { 6 | console.log(filter); 7 | const response = await projectService.setFilter(filter); 8 | return response; 9 | }); 10 | */ 11 | -------------------------------------------------------------------------------- /src/renderer/store/report-store/reportThunks.ts: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk } from '@reduxjs/toolkit'; 2 | import { reportService } from '@api/services/report.service'; 3 | import { vulnerabilityService } from '@api/services/vulnerability.service'; 4 | import { cryptographyService } from '@api/services/cryptography.service'; 5 | 6 | export const getReport = createAsyncThunk('report/getReport', async () => { 7 | const summary = await reportService.getSummary(); 8 | const detected = await reportService.detected(); 9 | const identified = await reportService.identified(); 10 | 11 | return { summary, detected, identified }; 12 | }); 13 | 14 | export const forceUpdate = createAsyncThunk('report/forceUpdate', async () => { 15 | // workaround: second IPC call is lost in promise All 16 | const delayedPromise = new Promise((resolve, reject) => { 17 | setTimeout(async () => { 18 | try { 19 | const response = await cryptographyService.update(); 20 | resolve(response); 21 | } catch (e) { 22 | reject(e); 23 | } 24 | }, 100); 25 | }); 26 | 27 | const promises = [vulnerabilityService.update(), delayedPromise]; 28 | const all = await Promise.allSettled(promises); 29 | return all; 30 | }); 31 | -------------------------------------------------------------------------------- /src/renderer/store/rootReducer.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from '@reduxjs/toolkit'; 2 | import workspaceReducer from './workspace-store/workspaceSlice'; 3 | import workbenchReducer from './workbench-store/workbenchSlice'; 4 | import inventoryReducer from './inventory-store/inventorySlice'; 5 | import navigationReducer from './navigation-store/navigationSlice'; 6 | import dependencyReducer from './dependency-store/dependencySlice'; 7 | import componentReducer from './component-store/componentSlice'; 8 | import reportReducer from './report-store/reportSlice'; 9 | 10 | const rootReducer = combineReducers({ 11 | workspace: workspaceReducer, 12 | workbench: workbenchReducer, 13 | component: componentReducer, 14 | inventory: inventoryReducer, 15 | dependency: dependencyReducer, 16 | navigation: navigationReducer, 17 | report: reportReducer, 18 | 19 | }); 20 | 21 | export type RootState = ReturnType; 22 | 23 | export default rootReducer; 24 | -------------------------------------------------------------------------------- /src/renderer/store/store.ts: -------------------------------------------------------------------------------- 1 | import { Action, configureStore, ThunkAction } from '@reduxjs/toolkit'; 2 | import { inventoryMiddleware } from '@store/inventory-store/inventoryMiddleware'; 3 | import { rootMiddleware } from '@store/rootMiddleware'; 4 | import { dependencyMiddleware } from '@store/dependency-store/dependencyMiddleware'; 5 | import rootReducer, { RootState } from './rootReducer'; 6 | import { workbenchMiddleware } from './workbench-store/workbenchMiddleware'; 7 | 8 | const store = configureStore({ 9 | reducer: rootReducer, 10 | middleware: (getDefaultMiddleware) => 11 | getDefaultMiddleware() 12 | .prepend(rootMiddleware.middleware) 13 | .prepend(workbenchMiddleware.middleware) 14 | .prepend(inventoryMiddleware.middleware) 15 | .prepend(dependencyMiddleware.middleware), 16 | }); 17 | 18 | export type AppDispatch = typeof store.dispatch; 19 | export type AppThunk = ThunkAction>; 20 | 21 | export default store; 22 | -------------------------------------------------------------------------------- /src/renderer/store/workbench-store/workbenchMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { createListenerMiddleware, isAnyOf } from '@reduxjs/toolkit'; 2 | import { dialogController } from '../../controllers/dialog-controller'; 3 | import { loadProject, loadProjectSettings } from './workbenchThunks'; 4 | 5 | export const workbenchMiddleware = createListenerMiddleware(); 6 | 7 | workbenchMiddleware.startListening({ 8 | matcher: isAnyOf(loadProject.fulfilled), 9 | effect: async (action, listenerApi) => { 10 | listenerApi.dispatch(loadProjectSettings()); 11 | }, 12 | }); 13 | 14 | workbenchMiddleware.startListening({ 15 | matcher: isAnyOf(loadProject.rejected), 16 | effect: async ({ error }, listenerApi) => { 17 | listenerApi.cancelActiveListeners(); 18 | await dialogController.showError('Open Project Error', error.message); 19 | window.history.back(); 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /src/renderer/theme/Panel.global.scss: -------------------------------------------------------------------------------- 1 | .panel { 2 | height: 100%; 3 | width: 100%; 4 | 5 | display: grid; 6 | grid-template-rows: auto 1fr; 7 | 8 | .panel-title h4 { 9 | margin: 0; 10 | } 11 | 12 | .panel-body { 13 | position: relative; 14 | } 15 | } 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/renderer/theme/variables.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --color-primary: #6366F1; 3 | --color-primary-lighten: #E0E7FF; 4 | --text-color-primary: #505050; 5 | 6 | --color-status-pending: #F97316; 7 | --color-status-ignored: #71717A; 8 | --color-status-identified: #22C55E; 9 | 10 | --background-color-primary: #e4e4e7; 11 | 12 | --color-font: #27272A; 13 | --color-component: #3B82F6; 14 | 15 | --appbar-height: 94px; 16 | } 17 | 18 | .color-primary { 19 | color: var(--color-primary); 20 | } 21 | 22 | .bg-primary { 23 | background-color: var(--color-primary-lighten) !important; 24 | } 25 | 26 | 27 | 28 | // Colors 29 | $pending-orange: #f97316; 30 | $indentified-green: #22c55e; 31 | $ignored-gray: #a1a1aa; 32 | $nomatch-black: #27272a; 33 | $blue-scanoss: #3B82F6; 34 | 35 | // Screen sizes 36 | $default-width: 1280px; 37 | $md: 780px; 38 | 39 | $search-box-width: 480px; 40 | -------------------------------------------------------------------------------- /src/renderer/ui/dialog/ProgressDialog.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Box, Dialog, DialogContent, LinearProgress } from '@mui/material'; 3 | 4 | interface ProgressDialogProps { 5 | open: boolean; 6 | message: React.ReactNode; 7 | loader: boolean; 8 | } 9 | 10 | export const ProgressDialog = (props: ProgressDialogProps) => { 11 | const { open, message, loader } = props; 12 | 13 | return ( 14 | 25 | 30 | {loader ? : } 31 | 39 | {message} 40 | 41 | 42 | 43 | ); 44 | }; 45 | 46 | export default ProgressDialog; 47 | -------------------------------------------------------------------------------- /src/renderer/ui/dialog/ProjectSelectorDialog.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanoss/sbom-workbench/03a3d598cff0f3753faeed2a2116c5c3a1655084/src/renderer/ui/dialog/ProjectSelectorDialog.scss -------------------------------------------------------------------------------- /src/shared/adapters/crypto.adapter.ts: -------------------------------------------------------------------------------- 1 | import { CryptoAlgorithm } from 'scanoss'; 2 | 3 | /** 4 | * Normalize crypto algorithms and convert them to lower case from an array of algorithm objects. 5 | * @param {Array} algorithms - The array of algorithm objects. 6 | * @returns {Array} algorithms - The array of normalized algorithm objects. 7 | */ 8 | export const normalizeCryptoAlgorithms = (algorithms: Array): Array => { 9 | const algorithmsMapper = new Map(); 10 | algorithms.forEach((a) => { 11 | const algorithmsToLowerCase = a.algorithm.toLocaleLowerCase(); 12 | algorithmsMapper.set(algorithmsToLowerCase, { ...a, algorithm: algorithmsToLowerCase }); 13 | }); 14 | 15 | return Array.from(algorithmsMapper.values()); 16 | }; 17 | -------------------------------------------------------------------------------- /src/shared/adapters/types.ts: -------------------------------------------------------------------------------- 1 | import { Algorithms } from 'main/model/entity/Cryptography'; 2 | import { LocalCryptography } from 'main/model/entity/LocalCryptography'; 3 | 4 | interface GroupedCryptography { 5 | purl: string; 6 | versions: Array; 7 | algorithms: Array 8 | } 9 | 10 | export interface CryptoReportData { 11 | files: Array; 12 | components: Array; 13 | } 14 | -------------------------------------------------------------------------------- /src/shared/utils/FamilyToken.ts: -------------------------------------------------------------------------------- 1 | class FamilyToken { 2 | private families: Map>; 3 | 4 | constructor() { 5 | this.families = new Map>(); 6 | this.setDefaults(); 7 | } 8 | 9 | public addFamily(newFamily: Array): void { 10 | const familyToLowerCase = newFamily.map((element) => { 11 | return element.toLowerCase(); 12 | }); 13 | const family = new Set(familyToLowerCase); 14 | newFamily.forEach((keyWord) => { 15 | const aux = keyWord.toLowerCase(); 16 | if (!this.families.has(aux)) this.families.set(aux, family); 17 | }); 18 | } 19 | 20 | public getFamily(keyWord: string): Array { 21 | const families = this.families.get(keyWord.toLowerCase()); 22 | if (families !== undefined) return Array.from(families).filter((f) => f !== keyWord); 23 | return null; 24 | } 25 | 26 | private setDefaults() { 27 | // this.addFamily(['license', 'licenses', 'licensed', 'licence']); 28 | } 29 | } 30 | 31 | export const familyToken = new FamilyToken(); 32 | -------------------------------------------------------------------------------- /src/shared/utils/scan-util.ts: -------------------------------------------------------------------------------- 1 | import { Component, ComponentGroup } from '../../api/types'; 2 | import React from 'react'; 3 | 4 | export function mapFiles(files: any[]): any[] { 5 | return files.map((file) => mapFile(file)); 6 | } 7 | 8 | export function mapFile(file: any): any[] { 9 | const getStatus = (file) => (file.ignored === 1 ? 'ignored' : file.identified === 1 ? 'identified' : 'pending'); 10 | 11 | return { 12 | ...file, 13 | status: getStatus(file), 14 | }; 15 | } 16 | 17 | export function sortComponents(components: Component[]) { 18 | components.sort( 19 | (a, b) => 20 | b.summary?.pending + 21 | b.summary?.ignored + 22 | b.summary?.identified - 23 | (a.summary?.pending + a.summary?.ignored + a.summary?.identified) 24 | ); 25 | } 26 | 27 | export default { 28 | mapFiles, 29 | }; 30 | --------------------------------------------------------------------------------