From cb00d98fd73f9eb33e2bc6216ffe0b490abcc583 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 02:55:53 +0000 Subject: [PATCH 01/20] feat: Add CVE conversion triage page This change introduces a new triage page to visualize and compare CVE conversion data across different stages and environments. - Added `gcp/website/triage_handlers.py` to handle triage requests and proxy GCS file access. - Registered the new blueprint in `gcp/website/main.py`. - Created the frontend template `gcp/website/frontend3/src/templates/triage.html` with a 3-column layout. - Implemented frontend logic in `gcp/website/frontend3/src/triage.js` to fetch and display JSON data from GCS or the API. - Added styles in `gcp/website/frontend3/src/triage.scss`. - Updated Webpack configuration to include the new triage entry point. - Verified with unit tests and frontend screenshots. Co-authored-by: jess-lowe <86962800+jess-lowe@users.noreply.github.com> --- .../frontend3/src/templates/triage.html | 52 +++++++ gcp/website/frontend3/src/triage.js | 141 ++++++++++++++++++ gcp/website/frontend3/src/triage.scss | 71 +++++++++ gcp/website/frontend3/webpack.dev.js | 9 +- gcp/website/frontend3/webpack.prod.js | 9 +- gcp/website/main.py | 2 + gcp/website/triage_handlers.py | 52 +++++++ 7 files changed, 334 insertions(+), 2 deletions(-) create mode 100644 gcp/website/frontend3/src/templates/triage.html create mode 100644 gcp/website/frontend3/src/triage.js create mode 100644 gcp/website/frontend3/src/triage.scss create mode 100644 gcp/website/triage_handlers.py diff --git a/gcp/website/frontend3/src/templates/triage.html b/gcp/website/frontend3/src/templates/triage.html new file mode 100644 index 00000000000..a4d309ff4af --- /dev/null +++ b/gcp/website/frontend3/src/templates/triage.html @@ -0,0 +1,52 @@ +{% extends 'base.html' %} +{% set active_section = 'triage' %} + +{% block content %} +
+

CVE Conversion Triager

+ +
+ + +
+ +
+ {% for i in range(1, 4) %} +
+
+ +
+ +
+
+
+ +
Select a source to view content
+
+
+ {% endfor %} +
+
+{% endblock %} diff --git a/gcp/website/frontend3/src/triage.js b/gcp/website/frontend3/src/triage.js new file mode 100644 index 00000000000..eb249303e28 --- /dev/null +++ b/gcp/website/frontend3/src/triage.js @@ -0,0 +1,141 @@ +import "./triage.scss"; +import "@material/web/textfield/filled-text-field.js"; +import "@material/web/button/filled-button.js"; +import "@material/web/progress/circular-progress.js"; + +document.addEventListener("DOMContentLoaded", () => { + const vulnIdInput = document.getElementById("vuln-id-input"); + const loadBtn = document.getElementById("load-btn"); + const columns = document.querySelectorAll(".triage-column"); + + // Map selection values to their respective endpoints/paths + const sourceConfigMap = { + // Test Instance + "test-nvd": { + bucket: "osv-test-cve-osv-conversion", + pathTemplate: "nvd-osv/{id}.json", + }, + "test-cve5": { + bucket: "osv-test-cve-osv-conversion", + pathTemplate: "cve5/{id}.json", + }, + "test-osv": { + bucket: "osv-test-cve-osv-conversion", + pathTemplate: "osv-output/{id}.json", + }, + "test-nvd-metrics": { + bucket: "osv-test-cve-osv-conversion", + pathTemplate: "nvd-osv/{id}.metrics.json", + }, + "test-cve5-metrics": { + bucket: "osv-test-cve-osv-conversion", + pathTemplate: "cve5/{id}.metrics.json", + }, + // Prod Instance + "prod-nvd": { + bucket: "cve-osv-conversion", + pathTemplate: "nvd-osv/{id}.json", + }, + "prod-cve5": { + bucket: "cve-osv-conversion", + pathTemplate: "cve5/{id}.json", + }, + "prod-osv": { + bucket: "cve-osv-conversion", + pathTemplate: "osv-output/{id}.json", + }, + "prod-nvd-metrics": { + bucket: "cve-osv-conversion", + pathTemplate: "nvd-osv/{id}.metrics.json", + }, + "prod-cve5-metrics": { + bucket: "cve-osv-conversion", + pathTemplate: "cve5/{id}.metrics.json", + }, + // API + "api-test": { + urlTemplate: "https://api.test.osv.dev/v1/vulns/{id}", + }, + "api-prod": { + urlTemplate: "https://api.osv.dev/v1/vulns/{id}", + }, + }; + + async function fetchData(sourceKey, vulnId) { + if (!sourceKey || !vulnId) return null; + + const config = sourceConfigMap[sourceKey]; + let url; + + if (config.bucket) { + const path = config.pathTemplate.replace("{id}", vulnId); + url = `/triage/proxy?bucket=${config.bucket}&path=${encodeURIComponent(path)}`; + } else if (config.urlTemplate) { + url = config.urlTemplate.replace("{id}", vulnId); + } else { + return Promise.reject("Invalid configuration"); + } + + const response = await fetch(url); + if (!response.ok) { + if (response.status === 404) { + throw new Error("Not Found"); + } + throw new Error(`Error: ${response.statusText}`); + } + return response.json(); + } + + function updateColumn(column) { + const select = column.querySelector(".source-select"); + const contentPre = column.querySelector(".json-content"); + const spinner = column.querySelector(".loading-spinner"); + const sourceKey = select.value; + const vulnId = vulnIdInput.value.trim(); + + if (!sourceKey) { + contentPre.textContent = "Select a source to view content"; + return; + } + + if (!vulnId) { + contentPre.textContent = "Please enter a Vulnerability ID"; + return; + } + + spinner.classList.remove("hidden"); + contentPre.textContent = ""; + + fetchData(sourceKey, vulnId) + .then((data) => { + contentPre.textContent = JSON.stringify(data, null, 2); + }) + .catch((error) => { + contentPre.textContent = error.message; + }) + .finally(() => { + spinner.classList.add("hidden"); + }); + } + + loadBtn.addEventListener("click", () => { + columns.forEach((col) => updateColumn(col)); + }); + + // Also handle Enter key on the input field + vulnIdInput.addEventListener("keyup", (e) => { + if (e.key === "Enter") { + columns.forEach((col) => updateColumn(col)); + } + }); + + // Individual column updates when dropdown changes + columns.forEach((col) => { + const select = col.querySelector(".source-select"); + select.addEventListener("change", () => { + if (vulnIdInput.value.trim()) { + updateColumn(col); + } + }); + }); +}); diff --git a/gcp/website/frontend3/src/triage.scss b/gcp/website/frontend3/src/triage.scss new file mode 100644 index 00000000000..01bd04b14bf --- /dev/null +++ b/gcp/website/frontend3/src/triage.scss @@ -0,0 +1,71 @@ +.triage-container { + padding: 20px; + max-width: 100%; + box-sizing: border-box; +} + +.input-section { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 20px; +} + +#vuln-id-input { + width: 300px; +} + +.triage-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 20px; +} + +.triage-column { + border: 1px solid #ccc; + border-radius: 4px; + display: flex; + flex-direction: column; + height: 80vh; +} + +.column-header { + padding: 10px; + background-color: #f5f5f5; + border-bottom: 1px solid #ccc; +} + +.select-wrapper { + margin-top: 5px; +} + +.source-select { + width: 100%; + padding: 5px; +} + +.content-display { + flex-grow: 1; + padding: 10px; + overflow: auto; + position: relative; /* For spinner positioning */ +} + +.json-content { + white-space: pre-wrap; + word-wrap: break-word; + font-family: monospace; + font-size: 12px; + margin: 0; +} + +.loading-spinner { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.hidden { + display: none; +} diff --git a/gcp/website/frontend3/webpack.dev.js b/gcp/website/frontend3/webpack.dev.js index fb148bdf3ed..b1523cbf340 100644 --- a/gcp/website/frontend3/webpack.dev.js +++ b/gcp/website/frontend3/webpack.dev.js @@ -9,6 +9,7 @@ module.exports = { entry: { main: './src/index.js', linter: './src/linter.js', + triage: './src/triage.js', }, output: { path: path.resolve(__dirname, '../dist'), @@ -35,7 +36,7 @@ module.exports = { plugins: [ new CopyPlugin({ patterns: [ - { from: './src/templates', to: '.', globOptions: { ignore: ['**/base.html'] } }, + { from: './src/templates', to: '.', globOptions: { ignore: ['**/base.html', '**/triage.html'] } }, { from: './img/*', to: 'static/img/[name][ext]' }, ], }), @@ -51,6 +52,12 @@ module.exports = { chunks: ['linter'], excludeChunks: ['main'], }), + new HtmlWebpackPlugin({ + filename: 'triage.html', + template: './src/templates/triage.html', + chunks: ['triage'], + excludeChunks: ['main', 'linter'], + }), new MiniCssExtractPlugin({ filename: 'static/[name].css' }), diff --git a/gcp/website/frontend3/webpack.prod.js b/gcp/website/frontend3/webpack.prod.js index 4cf6c4779ad..8c322a558c2 100644 --- a/gcp/website/frontend3/webpack.prod.js +++ b/gcp/website/frontend3/webpack.prod.js @@ -9,6 +9,7 @@ module.exports = { entry: { main: './src/index.js', linter: './src/linter.js', + triage: './src/triage.js', }, output: { path: path.resolve(__dirname, '../dist'), @@ -35,7 +36,7 @@ module.exports = { plugins: [ new CopyPlugin({ patterns: [ - { from: './src/templates/*.html', to: '[name].html' }, + { from: './src/templates/*.html', to: '[name].html', globOptions: { ignore: ['**/base.html', '**/triage.html'] } }, { from: './img/*', to: 'static/img/[name][ext]' }, ], }), @@ -51,6 +52,12 @@ module.exports = { chunks: ['linter'], excludeChunks: ['main'], }), + new HtmlWebpackPlugin({ + filename: 'triage.html', + template: './src/templates/triage.html', + chunks: ['triage'], + excludeChunks: ['main', 'linter'], + }), new MiniCssExtractPlugin({ filename: 'static/[name].[contenthash].css' }), diff --git a/gcp/website/main.py b/gcp/website/main.py index 3ae677aad5f..afea5997d73 100644 --- a/gcp/website/main.py +++ b/gcp/website/main.py @@ -22,6 +22,7 @@ import frontend_handlers import handlers import linter_api +import triage_handlers import osv.logs ndb_client = ndb.Client() @@ -46,6 +47,7 @@ def create_app(): flask_app.register_blueprint(handlers.blueprint) flask_app.register_blueprint(frontend_handlers.blueprint) flask_app.register_blueprint(linter_api.blueprint) + flask_app.register_blueprint(triage_handlers.blueprint) flask_app.config['TEMPLATES_AUTO_RELOAD'] = True flask_app.config['COMPRESS_MIMETYPES'] = ['text/html'] diff --git a/gcp/website/triage_handlers.py b/gcp/website/triage_handlers.py new file mode 100644 index 00000000000..94310874540 --- /dev/null +++ b/gcp/website/triage_handlers.py @@ -0,0 +1,52 @@ +"""Triage handlers.""" +import logging + +from flask import Blueprint, request, jsonify, render_template +from google.cloud import storage + +blueprint = Blueprint('triage_handlers', __name__) + +ALLOWED_BUCKETS = { + 'cve-osv-conversion', + 'osv-test-cve-osv-conversion' +} + +_STORAGE_CLIENT = None + +def get_storage_client(): + """Get storage client.""" + global _STORAGE_CLIENT # pylint: disable=global-statement + if _STORAGE_CLIENT is None: + _STORAGE_CLIENT = storage.Client() + return _STORAGE_CLIENT + +@blueprint.route('/triage') +def triage_index(): + """Triage index.""" + return render_template('triage.html') + +@blueprint.route('/triage/proxy') +def triage_proxy(): + """Proxy to fetch files from GCS buckets securely.""" + bucket_name = request.args.get('bucket') + path = request.args.get('path') + + if not bucket_name or not path: + return jsonify({'error': 'Missing bucket or path parameters'}), 400 + + if bucket_name not in ALLOWED_BUCKETS: + return jsonify({'error': 'Invalid bucket'}), 403 + + try: + bucket = get_storage_client().bucket(bucket_name) + blob = bucket.blob(path) + + if not blob.exists(): + return jsonify({'error': 'File not found'}), 404 + + content = blob.download_as_text() + return content, 200, {'Content-Type': 'application/json'} + + except Exception as e: # pylint: disable=broad-exception-caught + logging.error('Error fetching from GCS: %s', e) + return jsonify({'error': 'Internal server error'}), 500 From 623ea94de88db38f8205eaf2d355b12f1cd2e537 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 02:58:10 +0000 Subject: [PATCH 02/20] feat: Add CVE conversion triage page This change introduces a new triage page to visualize and compare CVE conversion data across different stages and environments. - Added `gcp/website/triage_handlers.py` to handle triage requests and proxy GCS file access. - Registered the new blueprint in `gcp/website/main.py`. - Created the frontend template `gcp/website/frontend3/src/templates/triage.html` with a 3-column layout. - Implemented frontend logic in `gcp/website/frontend3/src/triage.js` to fetch and display JSON data from GCS or the API. - Added styles in `gcp/website/frontend3/src/triage.scss`. - Updated Webpack configuration to include the new triage entry point. - Verified with unit tests and frontend screenshots. Co-authored-by: jess-lowe <86962800+jess-lowe@users.noreply.github.com> From 8025f2d0eae995da7a75bb9c2fcae9b68638af79 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 03:06:19 +0000 Subject: [PATCH 03/20] feat: Add CVE conversion triage page This change introduces a new triage page to visualize and compare CVE conversion data across different stages and environments. - Added `gcp/website/triage_handlers.py` to handle triage requests and proxy GCS file access. - Registered the new blueprint in `gcp/website/main.py`. - Created the frontend template `gcp/website/frontend3/src/templates/triage.html` with a 3-column layout. - Implemented frontend logic in `gcp/website/frontend3/src/triage.js` to fetch and display JSON data from GCS or the API. - Added styles in `gcp/website/frontend3/src/triage.scss`. - Updated Webpack configuration to include the new triage entry point. - Verified with unit tests and frontend screenshots. Co-authored-by: jess-lowe <86962800+jess-lowe@users.noreply.github.com> --- gcp/website/triage_handlers.py | 56 +++++++++++++++++----------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/gcp/website/triage_handlers.py b/gcp/website/triage_handlers.py index 94310874540..07ac3f3b018 100644 --- a/gcp/website/triage_handlers.py +++ b/gcp/website/triage_handlers.py @@ -6,47 +6,47 @@ blueprint = Blueprint('triage_handlers', __name__) -ALLOWED_BUCKETS = { - 'cve-osv-conversion', - 'osv-test-cve-osv-conversion' -} +ALLOWED_BUCKETS = {'cve-osv-conversion', 'osv-test-cve-osv-conversion'} _STORAGE_CLIENT = None + def get_storage_client(): - """Get storage client.""" - global _STORAGE_CLIENT # pylint: disable=global-statement - if _STORAGE_CLIENT is None: - _STORAGE_CLIENT = storage.Client() - return _STORAGE_CLIENT + """Get storage client.""" + global _STORAGE_CLIENT # pylint: disable=global-statement + if _STORAGE_CLIENT is None: + _STORAGE_CLIENT = storage.Client() + return _STORAGE_CLIENT + @blueprint.route('/triage') def triage_index(): - """Triage index.""" - return render_template('triage.html') + """Triage index.""" + return render_template('triage.html') + @blueprint.route('/triage/proxy') def triage_proxy(): - """Proxy to fetch files from GCS buckets securely.""" - bucket_name = request.args.get('bucket') - path = request.args.get('path') + """Proxy to fetch files from GCS buckets securely.""" + bucket_name = request.args.get('bucket') + path = request.args.get('path') - if not bucket_name or not path: - return jsonify({'error': 'Missing bucket or path parameters'}), 400 + if not bucket_name or not path: + return jsonify({'error': 'Missing bucket or path parameters'}), 400 - if bucket_name not in ALLOWED_BUCKETS: - return jsonify({'error': 'Invalid bucket'}), 403 + if bucket_name not in ALLOWED_BUCKETS: + return jsonify({'error': 'Invalid bucket'}), 403 - try: - bucket = get_storage_client().bucket(bucket_name) - blob = bucket.blob(path) + try: + bucket = get_storage_client().bucket(bucket_name) + blob = bucket.blob(path) - if not blob.exists(): - return jsonify({'error': 'File not found'}), 404 + if not blob.exists(): + return jsonify({'error': 'File not found'}), 404 - content = blob.download_as_text() - return content, 200, {'Content-Type': 'application/json'} + content = blob.download_as_text() + return content, 200, {'Content-Type': 'application/json'} - except Exception as e: # pylint: disable=broad-exception-caught - logging.error('Error fetching from GCS: %s', e) - return jsonify({'error': 'Internal server error'}), 500 + except Exception as e: # pylint: disable=broad-exception-caught + logging.error('Error fetching from GCS: %s', e) + return jsonify({'error': 'Internal server error'}), 500 From db6c42241c16748a6ef89be534480c29a1940c69 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 03:13:08 +0000 Subject: [PATCH 04/20] feat: Add CVE conversion triage page This change introduces a new triage page to visualize and compare CVE conversion data across different stages and environments. - Added `gcp/website/triage_handlers.py` to handle triage requests and proxy GCS file access. - Registered the new blueprint in `gcp/website/main.py`. - Created the frontend template `gcp/website/frontend3/src/templates/triage.html` with a 3-column layout. - Implemented frontend logic in `gcp/website/frontend3/src/triage.js` to fetch and display JSON data from GCS or the API. - Added styles in `gcp/website/frontend3/src/triage.scss`. - Updated Webpack configuration to include the new triage entry point. - Verified with unit tests and frontend screenshots. Co-authored-by: jess-lowe <86962800+jess-lowe@users.noreply.github.com> --- gcp/website/frontend3/src/templates/triage.html | 2 +- gcp/website/frontend3/src/triage.scss | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/gcp/website/frontend3/src/templates/triage.html b/gcp/website/frontend3/src/templates/triage.html index a4d309ff4af..90d839d1d6d 100644 --- a/gcp/website/frontend3/src/templates/triage.html +++ b/gcp/website/frontend3/src/templates/triage.html @@ -6,7 +6,7 @@

CVE Conversion Triager

- +
diff --git a/gcp/website/frontend3/src/triage.scss b/gcp/website/frontend3/src/triage.scss index 01bd04b14bf..9743747aa2a 100644 --- a/gcp/website/frontend3/src/triage.scss +++ b/gcp/website/frontend3/src/triage.scss @@ -11,8 +11,18 @@ margin-bottom: 20px; } -#vuln-id-input { +.standard-input { width: 300px; + padding: 10px; + font-size: 16px; + border: 1px solid #ccc; + border-radius: 4px; +} + +#load-btn { + padding: 10px 20px; + font-size: 16px; + cursor: pointer; } .triage-grid { From 48a23bd876c5073a293a4f9c811cccbbe1b47d2b Mon Sep 17 00:00:00 2001 From: Jess Lowe Date: Wed, 25 Feb 2026 04:02:54 +0000 Subject: [PATCH 05/20] Progress --- .../frontend3/src/templates/triage.html | 8 +- gcp/website/frontend3/src/triage.js | 60 +++++++++++++- gcp/website/frontend3/src/triage.scss | 83 +++++++++++++------ gcp/website/triage_handlers.py | 21 ++++- 4 files changed, 137 insertions(+), 35 deletions(-) diff --git a/gcp/website/frontend3/src/templates/triage.html b/gcp/website/frontend3/src/templates/triage.html index 90d839d1d6d..b28da26f566 100644 --- a/gcp/website/frontend3/src/templates/triage.html +++ b/gcp/website/frontend3/src/templates/triage.html @@ -6,8 +6,8 @@

CVE Conversion Triager

- - + + Load
@@ -18,6 +18,10 @@

CVE Conversion Triager