From 7e9600c4296a98a0d092b178dd6c56631aaab6ee Mon Sep 17 00:00:00 2001 From: "Williams, Tyler J" Date: Fri, 26 Jun 2026 11:01:32 -0400 Subject: [PATCH 1/3] purl builder added to workbench --- docs/_static_html/README.md | 42 + docs/_static_html/sbom-workbench.html | 1673 +++++++++++++++---------- 2 files changed, 1065 insertions(+), 650 deletions(-) create mode 100644 docs/_static_html/README.md diff --git a/docs/_static_html/README.md b/docs/_static_html/README.md new file mode 100644 index 00000000..ed325dff --- /dev/null +++ b/docs/_static_html/README.md @@ -0,0 +1,42 @@ +# CyTRICS Single-Page SBOM Editor + +This is a single-file, browser-based SBOM HTML editor. + +## PURL builder placement + +The PURL builder remains collapsed by default. After selecting a software entry and clicking **Build Related PURL**, the builder opens immediately below the entry title/action row and above the editable software fields. Selecting another software entry closes it automatically. + +## How to use + +1. Open `sbom-workbench.html` in a browser. +2. Load a CyTRICS JSON SBOM. +3. Search the software section by filename, product name, UUID, SHA-1, SHA-256, MD5, install path, or PURL. +4. Select a source software/file entry. +5. Use **Create related PURL component** to manually build a Package URL. +6. Click **Create software entry + relationship**. +7. Export the updated SBOM. + +## What the PURL action does + +For the selected source software entry, the tool creates a new CyTRICS `software[]` component: + +- `UUID`: newly generated UUID +- `softwareType`: `["software package"]` +- `name[]`: includes a product name and a `package URL (purl)` entry +- `version`: the manually entered PURL version, if provided +- `notHashable`: `true` +- `relationshipAssertion`: `Root` + +It also creates a new `relationships[]` entry: + +- `xUUID`: selected source file/software UUID +- `yUUID`: newly generated PURL software UUID +- `relationship`: selected relationship value, such as `Uses`, `Contains`, `Downloads`, or `Mounted on` + +## Notes + +- The full software list is intentionally not rendered until you search, which is better for large SBOMs. +- Export normalizes the document toward CyTRICS 1.0.1 shape and removes legacy top-level fields such as `properties`, `systems`, `analysisData`, `observations`, and `starRelationships`. +- This single page uses CDN-hosted React/Babel dependencies like the uploaded HTML prototype, so it needs browser access to those CDNs unless you vendor the dependencies locally. + + diff --git a/docs/_static_html/sbom-workbench.html b/docs/_static_html/sbom-workbench.html index 764d3356..92fd3da9 100644 --- a/docs/_static_html/sbom-workbench.html +++ b/docs/_static_html/sbom-workbench.html @@ -3,7 +3,7 @@ -SBOM Workbench +CyTRICS SBOM Co-editor @@ -35,54 +35,10 @@ --modified: #fcb317; --added: #84c342; --removed: #ff5468; - --btn-primary-bg: #fcb317; - --accent-contrast: #1a1407; - --accent-hover: #ffc640; - --scroll-thumb: #2a3040; - --scroll-thumb-h: #3a4256; - --btn-hover-bg: #232a3c; - --btn-hover-border: #475068; - --btn-active-bg: #1a2030; - --danger-border: #4a2530; - --danger-hover-bg: #2a1820; - --danger-hover-border: #663040; - --sel-bg: #1a2538; - --blue-text: #6f9bf0; - --grid-line: rgba(255,255,255,0.015); --mono: 'IBM Plex Mono', ui-monospace, 'JetBrains Mono', SFMono-Regular, Menlo, monospace; --sans: 'IBM Plex Sans', system-ui, -apple-system, sans-serif; --serif: 'IBM Plex Serif', serif; } - [data-theme="light"] { - --bg: #f3f4f7; - --bg-1: #ffffff; - --bg-2: #eceef3; - --bg-3: #e2e5ec; - --border: #d6dae2; - --border-2: #bac2cf; - --fg: #1b2330; - --fg-2: #48536a; - --fg-3: #7c869b; - --accent: #9c6400; - --accent-2: #2b56b3; - --accent-3: #4a8a17; - --danger: #cc2440; - --warn: #b45309; - --modified: #b07300; - --added: #4a8a17; - --removed: #cc2440; - --scroll-thumb: #c5cbd6; - --scroll-thumb-h: #aab2c2; - --btn-hover-bg: #dde1e9; - --btn-hover-border: #9aa5b6; - --btn-active-bg: #d2d7e1; - --danger-border: #e3b3bc; - --danger-hover-bg: #f7e3e7; - --danger-hover-border: #d98a98; - --sel-bg: #dbe6f7; - --blue-text: #2b56b3; - --grid-line: rgba(0,0,0,0.025); - } * { box-sizing: border-box; } html, body { height: 100%; margin: 0; } body { @@ -99,8 +55,8 @@ /* Scrollbar */ ::-webkit-scrollbar { width: 10px; height: 10px; } ::-webkit-scrollbar-track { background: transparent; } - ::-webkit-scrollbar-thumb { background: var(--scroll-thumb); border-radius: 4px; } - ::-webkit-scrollbar-thumb:hover { background: var(--scroll-thumb-h); } + ::-webkit-scrollbar-thumb { background: #2a3040; border-radius: 4px; } + ::-webkit-scrollbar-thumb:hover { background: #3a4256; } /* Reusable utility classes */ .mono { font-family: var(--mono); } @@ -134,17 +90,17 @@ transition: background 0.1s, border-color 0.1s; user-select: none; } - .btn:hover { background: var(--btn-hover-bg); border-color: var(--btn-hover-border); } - .btn:active { background: var(--btn-active-bg); } + .btn:hover { background: #232a3c; border-color: #475068; } + .btn:active { background: #1a2030; } .btn:disabled { opacity: 0.4; cursor: not-allowed; } .btn-primary { - background: var(--btn-primary-bg); - border-color: var(--btn-primary-bg); - color: var(--accent-contrast); + background: var(--accent); + border-color: var(--accent); + color: #1a1407; } - .btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); } - .btn-danger { color: var(--danger); border-color: var(--danger-border); } - .btn-danger:hover { background: var(--danger-hover-bg); border-color: var(--danger-hover-border); } + .btn-primary:hover { background: #ffc640; border-color: #ffc640; } + .btn-danger { color: var(--danger); border-color: #4a2530; } + .btn-danger:hover { background: #2a1820; border-color: #663040; } .btn-ghost { background: transparent; border-color: transparent; @@ -222,7 +178,7 @@ padding: 1px 5px; border-radius: 2px; } - .tab.active .badge { background: var(--btn-primary-bg); color: var(--accent-contrast); } + .tab.active .badge { background: var(--accent); color: #1a1407; } /* List */ .list-row { @@ -236,7 +192,7 @@ transition: background 0.05s; } .list-row:hover { background: var(--bg-2); } - .list-row.selected { background: var(--sel-bg); } + .list-row.selected { background: #1a2538; } .list-row.selected::before { content: ''; position: absolute; left: 0; @@ -257,7 +213,7 @@ } .badge-yellow { background: rgba(252, 179, 23, 0.15); color: var(--accent); border-color: rgba(252, 179, 23, 0.3); } .badge-green { background: rgba(132, 195, 66, 0.15); color: var(--accent-3); border-color: rgba(132, 195, 66, 0.3); } - .badge-blue { background: rgba(51, 102, 204, 0.18); color: var(--blue-text); border-color: rgba(51, 102, 204, 0.35); } + .badge-blue { background: rgba(51, 102, 204, 0.18); color: #6f9bf0; border-color: rgba(51, 102, 204, 0.35); } .badge-red { background: rgba(255, 84, 104, 0.15); color: var(--danger); border-color: rgba(255, 84, 104, 0.3); } /* Modal */ @@ -294,6 +250,11 @@ font-size: 15px; letter-spacing: 0.01em; } + .app-brand-mark { + color: var(--accent); + font-family: var(--mono); + font-weight: 600; + } /* Op log row */ .op-row { @@ -360,8 +321,8 @@ /* Subtle textures */ .grid-bg { background-image: - linear-gradient(var(--grid-line) 1px, transparent 1px), - linear-gradient(90deg, var(--grid-line) 1px, transparent 1px); + linear-gradient(rgba(255,255,255,0.015) 1px, transparent 1px), + linear-gradient(90deg, rgba(255,255,255,0.015) 1px, transparent 1px); background-size: 24px 24px; } @@ -441,32 +402,72 @@ .kvp .k { color: var(--fg-3); font-size: 11px; min-width: 80px; } .kvp .v { font-family: var(--mono); font-size: 12px; color: var(--fg); } - /* Suggestion combobox */ - .suggest-wrap { position: relative; flex: 1; min-width: 0; } - .suggest-pop { - position: absolute; - top: calc(100% + 2px); left: 0; right: 0; - background: var(--bg-1); - border: 1px solid var(--border-2); + + /* Schema-driven PURL builder */ + .purl-definition-card { + background: var(--bg-2); + border: 1px solid var(--border); border-radius: 3px; - z-index: 120; - max-height: 180px; - overflow-y: auto; - box-shadow: 0 10px 28px rgba(0,0,0,0.45); + padding: 10px; + } + .purl-definition-title { + display: flex; + align-items: baseline; + gap: 8px; + flex-wrap: wrap; + margin-bottom: 4px; + } + .purl-requirement { font-family: var(--mono); + font-size: 9px; + text-transform: uppercase; + letter-spacing: 0.05em; + padding: 1px 5px; + border-radius: 2px; + border: 1px solid var(--border-2); + } + .purl-required { color: var(--danger); border-color: rgba(255,84,104,.4); background: rgba(255,84,104,.08); } + .purl-optional { color: var(--accent-3); border-color: rgba(132,195,66,.35); background: rgba(132,195,66,.08); } + .purl-prohibited { color: var(--fg-3); border-color: var(--border); background: var(--bg); } + .purl-validation { + border-radius: 3px; + padding: 8px 10px; font-size: 11px; + margin-top: 8px; } - .suggest-item { padding: 5px 8px; cursor: pointer; white-space: nowrap; - overflow: hidden; text-overflow: ellipsis; } - .suggest-item.hi { background: var(--sel-bg); } - - /* Inline validation note */ - .vnote { + .purl-validation.error { border: 1px solid rgba(255,84,104,.45); background: rgba(255,84,104,.08); color: #ff9aa7; } + .purl-validation.warn { border: 1px solid rgba(255,121,0,.4); background: rgba(255,121,0,.08); color: #ffb36b; } + .purl-validation.ok { border: 1px solid rgba(132,195,66,.35); background: rgba(132,195,66,.08); color: #b7dd8d; } + .purl-example { + display: block; + width: 100%; + text-align: left; + font-family: var(--mono); font-size: 10px; - color: var(--warn); + color: var(--fg-2); + background: var(--bg); + border: 1px solid var(--border); + padding: 5px 7px; + margin-top: 4px; + border-radius: 2px; + cursor: pointer; + word-break: break-all; + } + .purl-example:hover { border-color: var(--accent); color: var(--fg); } + .purl-qualifier-row { + display: grid; + grid-template-columns: minmax(120px, .7fr) minmax(180px, 1.3fr) 28px; + gap: 6px; + align-items: start; + margin-bottom: 6px; + } + .purl-help { + font-size: 10px; + color: var(--fg-3); margin-top: 3px; - font-family: var(--mono); + line-height: 1.35; } + .purl-toolbar { display:flex; gap:6px; align-items:center; flex-wrap:wrap; } /* Wave/blink */ @keyframes blink { 50% { opacity: 0.3; } } @@ -478,7 +479,7 @@