UNPKG

vulnmatter-extension

Version:

VS Code extension for CVE vulnerability analysis using the VulnMatter API with X-API-Key. See CHANGELOG.md for release notes.

1,377 lines (1,211 loc) 56.8 kB
<!DOCTYPE html> <html lang="es"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>VulnMatter CVE Analysis</title> <style> :root { --spacing-xs: 4px; --spacing-sm: 8px; --spacing-md: 16px; --spacing-lg: 24px; --spacing-xl: 32px; --border-radius-sm: 12px; --border-radius-md: 16px; --border-radius-lg: 24px; --border-radius-xl: 28px; --shadow-sm: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.08); --shadow-md: 0 4px 6px rgba(0,0,0,0.1), 0 2px 4px rgba(0,0,0,0.06); --shadow-lg: 0 10px 15px rgba(0,0,0,0.1), 0 4px 6px rgba(0,0,0,0.05); --shadow-xl: 0 20px 25px rgba(0,0,0,0.1), 0 10px 10px rgba(0,0,0,0.04); --transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); --material-accent: var(--vscode-textLink-foreground); --surface-container: var(--vscode-editor-background); --surface-container-high: var(--vscode-editor-selectionBackground); } * { box-sizing: border-box; } body { font-family: var(--vscode-font-family, 'Roboto', 'Segoe UI', system-ui, -apple-system, sans-serif); color: var(--vscode-foreground); background: var(--vscode-editor-background); padding: var(--spacing-md); margin: 0; min-height: 100vh; line-height: 1.5; font-weight: 400; } .container { max-width: 1200px; margin: 0 auto; padding: 0 var(--spacing-md); animation: fadeIn 0.2s cubic-bezier(0.4, 0, 0.2, 1); } @keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } .header { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--spacing-xl); padding: var(--spacing-xl); background: var(--surface-container); border-radius: var(--border-radius-xl); box-shadow: var(--shadow-md); position: relative; overflow: hidden; } .header::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 4px; background: linear-gradient(90deg, var(--vscode-textLink-foreground), var(--vscode-textLink-activeForeground)); border-radius: var(--border-radius-xl) var(--border-radius-xl) 0 0; } .header h2 { margin: 0; font-size: 1.5rem; font-weight: 600; background: linear-gradient(135deg, var(--vscode-textLink-foreground), var(--vscode-textLink-activeForeground)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } .lang-switch { background: var(--surface-container-high); border: none; color: var(--vscode-button-secondaryForeground); border-radius: var(--border-radius-lg); padding: 12px 20px; cursor: pointer; transition: var(--transition); font-size: 0.9rem; font-weight: 500; box-shadow: var(--shadow-sm); display: flex; align-items: center; gap: var(--spacing-sm); } .lang-switch:hover { background: var(--vscode-button-secondaryHoverBackground); transform: translateY(-2px); box-shadow: var(--shadow-md); } .section { margin-bottom: var(--spacing-xl); background: var(--surface-container); border-radius: var(--border-radius-lg); overflow: hidden; transition: var(--transition); box-shadow: var(--shadow-sm); border: none; } .section:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); } .section summary { color: var(--vscode-textLink-foreground); font-weight: 600; font-size: 1.1rem; padding: var(--spacing-xl); cursor: pointer; outline: none; background: var(--surface-container-high); transition: var(--transition); user-select: none; display: flex; align-items: center; gap: var(--spacing-md); } .section summary:hover { background: var(--vscode-list-hoverBackground); padding-left: calc(var(--spacing-xl) + 4px); } .section[open] summary { background: linear-gradient(135deg, var(--vscode-textLink-foreground), var(--vscode-textLink-activeForeground)); color: var(--vscode-editor-background); border-radius: 0; } .section-content { padding: var(--spacing-lg); } .form-grid { display: grid; gap: var(--spacing-lg); } .form-group { display: flex; flex-direction: column; gap: var(--spacing-sm); } .form-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--spacing-md); } @media (max-width: 600px) { .form-row { grid-template-columns: 1fr; } } label { font-weight: 500; color: var(--vscode-input-foreground); font-size: 0.9rem; margin-bottom: var(--spacing-xs); } input[type="text"], input[type="password"], textarea { width: 100%; padding: 16px 20px; border: 2px solid var(--vscode-input-border); background-color: var(--vscode-input-background); color: var(--vscode-input-foreground); border-radius: var(--border-radius-md); font-size: 1rem; transition: var(--transition); line-height: 1.5; font-weight: 400; } input:focus, textarea:focus { outline: none; border-color: var(--material-accent); box-shadow: 0 0 0 3px rgba(var(--material-accent), 0.1); transform: translateY(-1px); background-color: var(--surface-container-high); } input::placeholder { color: var(--vscode-input-placeholderForeground); opacity: 0.8; } .status-section { display: flex; align-items: center; gap: var(--spacing-lg); padding: var(--spacing-lg) var(--spacing-xl); background: var(--surface-container-high); border-radius: var(--border-radius-lg); margin-bottom: var(--spacing-xl); box-shadow: var(--shadow-sm); border: none; position: relative; } .status-indicator { width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; position: relative; } .status-ok { background: linear-gradient(135deg, #34d399, #10b981); box-shadow: 0 4px 12px rgba(52, 211, 153, 0.4); } .status-error { background: linear-gradient(135deg, #f87171, #ef4444); box-shadow: 0 4px 12px rgba(248, 113, 113, 0.4); } .status-indicator::after { content: ''; position: absolute; width: 8px; height: 8px; background: rgba(255, 255, 255, 0.9); border-radius: 50%; top: 4px; left: 4px; } .checkbox-grid { display: grid; gap: var(--spacing-md); margin: var(--spacing-lg) 0; } .checkbox-item { display: flex; align-items: center; gap: var(--spacing-lg); padding: var(--spacing-lg) var(--spacing-xl); background: var(--surface-container-high); border-radius: var(--border-radius-md); transition: var(--transition); box-shadow: var(--shadow-sm); border: none; position: relative; overflow: hidden; } .checkbox-item::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 4px; background: transparent; transition: var(--transition); } .checkbox-item:hover { background: var(--vscode-list-hoverBackground); transform: translateX(4px); box-shadow: var(--shadow-md); } .checkbox-item:hover::before { background: var(--material-accent); } .checkbox-item input[type="checkbox"] { width: 18px; height: 18px; margin: 0; cursor: pointer; } .checkbox-label { flex: 1; font-weight: 500; cursor: pointer; } .inline-note { color: var(--vscode-descriptionForeground); font-size: 0.85rem; font-style: italic; } .button-container { display: flex; gap: var(--spacing-md); align-items: center; margin-top: var(--spacing-xl); flex-wrap: wrap; } button { border: none; border-radius: var(--border-radius-lg); cursor: pointer; font-weight: 500; transition: var(--transition); display: flex; align-items: center; justify-content: center; gap: var(--spacing-sm); min-height: 48px; font-size: 1rem; position: relative; overflow: hidden; } .btn-primary { flex: 1; min-width: 160px; background: linear-gradient(135deg, var(--vscode-button-background), var(--vscode-textLink-foreground)); color: var(--vscode-button-foreground); padding: 16px 32px; box-shadow: var(--shadow-sm); font-weight: 600; } .btn-primary::before { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0; border-radius: 50%; background: rgba(255, 255, 255, 0.2); transition: width 0.3s, height 0.3s, top 0.3s, left 0.3s; transform: translate(-50%, -50%); } .btn-primary:hover { background: linear-gradient(135deg, var(--vscode-button-hoverBackground), var(--vscode-textLink-activeForeground)); transform: translateY(-2px); box-shadow: var(--shadow-lg); } .btn-primary:hover::before { width: 300px; height: 300px; } .btn-primary:active { transform: translateY(0px); box-shadow: var(--shadow-md); } .btn-secondary { background: var(--surface-container-high); color: var(--vscode-button-secondaryForeground); border: 2px solid var(--vscode-input-border); padding: 12px 16px; min-width: 48px; height: 48px; box-shadow: var(--shadow-sm); } .btn-secondary:hover { background: var(--vscode-button-secondaryHoverBackground); border-color: var(--material-accent); transform: translateY(-1px); box-shadow: var(--shadow-md); } .btn-secondary:active { transform: scale(0.96); } .config-info { background: var(--vscode-textBlockQuote-background); border-radius: var(--border-radius-md); margin-top: var(--spacing-xl); overflow: hidden; box-shadow: var(--shadow-sm); border: none; position: relative; } .config-info::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 4px; background: linear-gradient(180deg, var(--vscode-textLink-foreground), var(--vscode-textLink-activeForeground)); } .config-info summary { padding: var(--spacing-lg) var(--spacing-xl); background: var(--surface-container-high); cursor: pointer; font-weight: 600; border: none; font-size: 1rem; display: flex; align-items: center; gap: var(--spacing-md); } .config-info-content { padding: var(--spacing-xl); font-size: 0.95rem; line-height: 1.6; } .config-info code { background: var(--vscode-textPreformat-background); padding: 4px 8px; border-radius: var(--border-radius-sm); font-family: var(--vscode-editor-font-family, 'JetBrains Mono', 'Fira Code', 'Consolas', monospace); font-size: 0.9rem; font-weight: 500; } .loading { text-align: center; padding: var(--spacing-xl); display: none; } .loading-spinner { font-size: 2rem; animation: spin 1s linear infinite; } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .results { display: none; } .cve-result { display: flex; justify-content: space-between; align-items: center; padding: var(--spacing-md); margin: var(--spacing-sm) 0; border-radius: var(--border-radius-md); background: var(--vscode-editor-background); border: 1px solid var(--vscode-panel-border); transition: var(--transition); } .cve-result:hover { transform: translateX(4px); border-color: var(--vscode-focusBorder); } .cve-name { font-weight: bold; font-family: var(--vscode-editor-font-family, monospace); } .score { font-weight: bold; padding: var(--spacing-xs) var(--spacing-sm); border-radius: var(--border-radius-sm); font-size: 0.85rem; } .score.high { background: linear-gradient(135deg, #ef4444, #dc2626); color: white; } .score.medium { background: linear-gradient(135deg, #f59e0b, #d97706); color: white; } .score.low { background: linear-gradient(135deg, #10b981, #059669); color: white; } /* Material Design responsive breakpoints */ @media (max-width: 1200px) { .container { max-width: 100%; padding: 0 var(--spacing-xl); } } @media (max-width: 840px) { .form-row { grid-template-columns: 1fr; gap: var(--spacing-md); } .header { padding: var(--spacing-lg) var(--spacing-xl); } } @media (max-width: 600px) { body { padding: var(--spacing-md); } .container { padding: 0; } .header { flex-direction: column; gap: var(--spacing-lg); text-align: center; padding: var(--spacing-xl); border-radius: var(--border-radius-lg); } .header h2 { font-size: 1.5rem; } .button-container { flex-direction: column; gap: var(--spacing-md); } .btn-primary, .btn-secondary { width: 100%; min-width: unset; } .section-content { padding: var(--spacing-lg); } .checkbox-item { padding: var(--spacing-md) var(--spacing-lg); flex-wrap: wrap; } .status-section { padding: var(--spacing-md) var(--spacing-lg); border-radius: var(--border-radius-md); } } @media (max-width: 480px) { .header { padding: var(--spacing-lg); margin-bottom: var(--spacing-lg); } .section-content { padding: var(--spacing-md); } .form-grid { gap: var(--spacing-md); } input, textarea, button { font-size: 16px; /* Prevent zoom on iOS */ padding: 14px 16px; } .btn-primary { padding: 16px 24px; font-size: 16px; } } /* Enhanced interactions */ .checkbox-item input[type="checkbox"] { accent-color: var(--vscode-textLink-foreground); cursor: pointer; } .form-group label { display: flex; align-items: center; gap: var(--spacing-xs); } /* Loading states */ .btn-primary:disabled { opacity: 0.6; cursor: not-allowed; transform: none; } .btn-primary.loading::after { content: ''; width: 16px; height: 16px; border: 2px solid transparent; border-top: 2px solid currentColor; border-radius: 50%; animation: spin 1s linear infinite; margin-left: var(--spacing-sm); } /* Enhanced textarea */ textarea { font-family: var(--vscode-editor-font-family, 'Consolas', monospace); font-size: 0.9rem; } /* Improved focus states */ .checkbox-item:has(:focus-visible) { outline: 2px solid var(--vscode-focusBorder); outline-offset: 2px; } button:focus-visible { outline: 2px solid var(--vscode-focusBorder); outline-offset: 2px; } /* Status animations */ .status-indicator { position: relative; overflow: hidden; } .status-indicator::before { content: ''; position: absolute; top: 50%; left: 50%; width: 100%; height: 100%; border-radius: 50%; transform: translate(-50%, -50%); animation: pulse 2s infinite; opacity: 0.3; } .status-ok::before { background: #4ade80; } .status-error::before { background: #f87171; } @keyframes pulse { 0% { transform: translate(-50%, -50%) scale(1); opacity: 0.3; } 50% { transform: translate(-50%, -50%) scale(1.5); opacity: 0.1; } 100% { transform: translate(-50%, -50%) scale(1); opacity: 0.3; } } @keyframes slideIn { from { opacity: 0; transform: translateX(100%); } to { opacity: 1; transform: translateX(0); } } @keyframes slideOut { from { opacity: 1; transform: translateX(0); } to { opacity: 0; transform: translateX(100%); } } /* Notification styles */ .notification { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); border-left: 4px solid rgba(255, 255, 255, 0.3); } /* Footer styles */ .footer { margin-top: var(--spacing-xl); opacity: 0.8; transition: var(--transition); } .footer:hover { opacity: 1; } /* Material Design hover effects */ input[type="text"]:hover, input[type="password"]:hover, textarea:hover { border-color: var(--material-accent); box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .checkbox-item:hover .checkbox-label { color: var(--material-accent); font-weight: 600; } /* Material Design transitions */ input, button, .checkbox-item, .section { transition: var(--transition); } /* Material Design focus states */ button:focus-visible, input:focus-visible, textarea:focus-visible { outline: 3px solid var(--material-accent); outline-offset: 2px; } /* Floating action button style for primary buttons */ .btn-primary { position: relative; z-index: 1; } /* Material ripple effect */ @keyframes ripple { 0% { transform: scale(0); opacity: 0.6; } 100% { transform: scale(4); opacity: 0; } } /* Enhanced elevation on interaction */ .section:focus-within { box-shadow: var(--shadow-lg); } /* Loading spinner enhancement */ .loading-spinner { display: inline-block; animation: spin 1s linear infinite; margin-right: var(--spacing-sm); } /* Tooltip-like enhancements */ button[title]:hover::after { content: attr(title); position: absolute; background: var(--vscode-editorHoverWidget-background); color: var(--vscode-editorHoverWidget-foreground); border: 1px solid var(--vscode-editorHoverWidget-border); padding: var(--spacing-xs) var(--spacing-sm); border-radius: var(--border-radius-sm); font-size: 0.8rem; white-space: nowrap; z-index: 1000; margin-top: 4px; left: 50%; transform: translateX(-50%); } /* Dark mode enhancements */ @media (prefers-color-scheme: dark) { .section { border: 1px solid rgba(255,255,255,0.1); } .status-ok { box-shadow: 0 0 12px rgba(74, 222, 128, 0.3); } .status-error { box-shadow: 0 0 12px rgba(248, 113, 113, 0.3); } body { background: linear-gradient(135deg, var(--vscode-editor-background) 0%, color-mix(in srgb, var(--vscode-editor-background) 90%, #000) 100%); } } </style> </head> <body> <div class="container"> <!-- Header --> <div class="header"> <h2 id="t-app-title">� VulnMatter Configuration</h2> <button id="langSwitchBtn" class="lang-switch" onclick="switchLang()" title="Cambiar idioma / Switch language"> 🌐 <span id="current-lang">ES/EN</span> </button> </div> <!-- Main Configuration Section --> <details class="section config-section" open> <summary id="t-reqs-title">⚙️ Connection Configuration</summary> <div class="section-content"> <!-- Status Section --> <div class="status-section"> <span id="statusIndicator" class="status-indicator status-error"></span> <div> <strong id="t-status-label">Status:</strong> <span id="configStatus">Not configured</span> <div class="inline-note">Configure your API key to begin</div> </div> </div> <div class="form-grid"> <!-- API Configuration --> <div class="form-group"> <label for="apiKey" id="t-apikey-label">🔑 VulnMatter API Key</label> <input type="password" id="apiKey" placeholder="Enter your VulnMatter X-API-Key"> </div> <div class="form-row"> <div class="form-group"> <label for="apiUrl" id="t-apiurl-label">🌐 API URL</label> <input type="text" id="apiUrl" placeholder="https://api.vulnmatter.com" value="https://api.vulnmatter.com"> </div> <div class="form-group"> <label for="mcpUrl" id="t-mcpurl-label">🔗 MCP URL (SSE)</label> <input type="text" id="mcpUrl" placeholder="https://mcp.singularity-matter.com/sse" value="https://mcp.singularity-matter.com/sse"> </div> </div> <!-- MCP Configuration Options --> <div class="checkbox-grid"> <div class="checkbox-item"> <input type="checkbox" id="mcpFsCheckbox" onchange="toggleMcpFs(this)"> <label for="mcpFsCheckbox" class="checkbox-label" id="t-mcp-label"> Configure MCP Filesystem (Claude) </label> <a href="https://www.claudemcp.com/servers/filesystem" style="color:var(--vscode-textLink-foreground);" target="_blank" title="Documentation">📖</a> <span id="mcpFsStatusText" class="inline-note"></span> </div> <div class="checkbox-item"> <input type="checkbox" id="mcpVSCodeCheckbox" onchange="toggleMcpVSCode(this)"> <label for="mcpVSCodeCheckbox" class="checkbox-label" id="t-mcpvscode-label"> Install MCP VulnMatter (VS Code) </label> <span id="mcpVSCodeStatusText" class="inline-note"></span> </div> <div class="checkbox-item"> <input type="checkbox" id="mcpClaudeCheckbox" onchange="toggleMcpClaude(this)"> <label for="mcpClaudeCheckbox" class="checkbox-label" id="t-mcpclaude-label"> Install MCP VulnMatter (Claude Desktop) </label> <span id="mcpClaudeStatusText" class="inline-note"></span> </div> <div class="checkbox-item"> <input type="checkbox" id="mcpVulnMatterGlobalCheckbox" onchange="toggleMcpVulnMatter(this)"> <label for="mcpVulnMatterGlobalCheckbox" class="checkbox-label"> Enable/Disable VulnMatter (Global) </label> </div> </div> <div class="button-container"> <button class="btn-primary" onclick="saveApiKey()" id="t-save-btn"> 💾 Save Configuration </button> <button class="btn-secondary" onclick="showConfigPath()" title="Show config location"> 📁 </button> <button class="btn-secondary" onclick="showMcpConfig()" title="Show MCP configuration"> 🔧 </button> </div> </div> <details class="config-info"> <summary id="t-info-title">📌 Configuration Information</summary> <div class="config-info-content"> <div style="margin-top:8px;"> • X-API-Key saved to: <code id="configPathDisplay">Loading...</code><br> • User directory: <code id="userHomeDisplay">Loading...</code><br> • API URL: <code id="apiUrlDisplay">Loading...</code><br> </div> </div> </details> </div> </details> <!-- CVE Analysis Section (Hidden) --> <details class="section" style="display:none" data-hidden="true"> <summary id="t-analysis-title">🔍 CVE Analysis</summary> <div class="section-content"> <div class="form-grid"> <div class="form-group"> <label for="cveList" id="t-cves-label">CVEs to analyze (one per line):</label> <textarea id="cveList" rows="6" placeholder="CVE-2023-12345&#10;CVE-2024-67890&#10;CVE-2023-23456" style="resize: vertical; min-height: 120px;"></textarea> </div> <div class="button-container"> <button class="btn-primary" onclick="analyzeCVEs()" id="t-analyze-btn"> 🔍 Analyze CVEs </button> <button class="btn-secondary" onclick="loadExampleCVEs()" title="Load example CVEs"> 📋 </button> <button class="btn-secondary" onclick="clearResults()" title="Clear results"> 🗑️ </button> </div> </div> </div> </details> <!-- Report Generation Section (Hidden) --> <details class="section" style="display:none" data-hidden="true"> <summary id="t-report-title">📊 Generate Report</summary> <div class="section-content"> <div class="form-grid"> <div class="form-group"> <label for="reportPostfix" id="t-postfix-label">Additional parameters (optional):</label> <input type="text" id="reportPostfix" placeholder="format=pdf&detailed=true"> </div> <div class="button-container"> <button class="btn-primary" onclick="generateReport()">📄 Generate Report</button> </div> </div> </div> </details> <div id="loading" class="loading"> <div style="font-size: 2em;">⏳</div> <p id="loadingMessage">Procesando...</p> </div> <div id="results" class="results section" style="display:none" data-hidden="true"> <!-- Sección oculta temporalmente: Resultados del Análisis --> <h3><span class="icon">📋</span>Resultados del Análisis</h3> <div id="resultsContent"></div> </div> <div id="reportResults" class="results section" style="display:none" data-hidden="true"> <!-- Sección oculta temporalmente: Reporte Generado --> <h3><span class="icon">📄</span>Reporte Generado</h3> <div id="reportContent"></div> </div> <details id="productsSection" class="section" style="display:none" data-hidden="true"> <!-- Sección oculta temporalmente: Productos --> <summary id="t-products-title"><span class="icon">🛍️</span>Products</summary> <div style="margin-bottom:8px;"> <button class="btn-primary" onclick="requestProducts()" id="t-refresh-products">🔄 Actualizar Productos</button> </div> <div id="productsList">No hay productos cargados.</div> </details> </div> <script> // Locale toggle helpers (moved from raw text to script to avoid rendering) let currentLocale = null; function switchLang() { // Toggle between 'en' and 'es' - Default is English currentLocale = (!currentLocale || currentLocale.startsWith('en')) ? 'es' : 'en'; applyLocale(currentLocale); } const vscode = acquireVsCodeApi(); // Inline translations to avoid fetch issues inside the Webview sandbox const translations = { es: { appTitle: '🔒 VulnMatter CVE Analysis', reqsTitle: 'Requisitos', mcpLabel: 'Configurar MCP Filesystem (Claude)', mcpVSCodeLabel: 'Instalar MCP VulnMatter (VS Code)', mcpClaudeLabel: 'Instalar MCP VulnMatter (Claude Desktop)', statusLabel: 'Estado:', apiUrlLabel: 'URL API:', apiKeyLabel: 'X-API-Key de VulnMatter:', mcpUrlLabel: 'URL MCP (SSE):', saveBtn: '💾 Guardar Configuración', infoTitle: '📌 Información', analysisTitle: 'Análisis de CVEs', cvesLabel: 'CVEs a analizar (uno por línea):', analyzeBtn: '🔍 Analizar CVEs', exampleBtn: '📋 Ejemplo', clearBtn: '🗑️ Limpiar Resultados', reportTitle: '📊 Generar Reporte', postfixLabel: 'Parámetros adicionales (opcional):', productsTitle: '🛍️ Productos', refreshProducts: '🔄 Actualizar Productos', notConfigured: 'No configurada' }, en: { appTitle: '🔒 VulnMatter CVE Analysis', reqsTitle: 'Requirements', mcpLabel: 'Configure MCP Filesystem (Claude)', mcpVSCodeLabel: 'Install MCP VulnMatter (VS Code)', mcpClaudeLabel: 'Install MCP VulnMatter (Claude Desktop)', statusLabel: 'Status:', apiUrlLabel: 'API URL:', apiKeyLabel: 'VulnMatter X-API-Key:', mcpUrlLabel: 'MCP URL (SSE):', saveBtn: '💾 Save Configuration', infoTitle: '📌 Information', analysisTitle: 'CVE Analysis', cvesLabel: 'CVEs to analyze (one per line):', analyzeBtn: '🔍 Analyze CVEs', exampleBtn: '📋 Example', clearBtn: '🗑️ Clear Results', reportTitle: '📊 Generate Report', postfixLabel: 'Additional parameters (optional):', productsTitle: '🛍️ Products', refreshProducts: '🔄 Refresh Products', notConfigured: 'Not configured' } }; function applyLocale(locale) { const lang = (locale || 'en').startsWith('en') ? 'en' : 'es'; currentLocale = lang; const t = translations[lang] || translations.es; const setText = (id, text) => { const el = document.getElementById(id); if (el && text) el.textContent = text; }; setText('t-app-title', t.appTitle); setText('t-reqs-title', t.reqsTitle); setText('t-mcp-label', t.mcpLabel); setText('t-mcpvscode-label', t.mcpVSCodeLabel); setText('t-mcpclaude-label', t.mcpClaudeLabel); setText('t-status-label', t.statusLabel); setText('t-apiurl-label', t.apiUrlLabel); setText('t-apiurl-label', t.apiUrlLabel); setText('t-apikey-label', t.apiKeyLabel); setText('t-mcpurl-label', t.mcpUrlLabel); setText('t-save-btn', t.saveBtn); setText('t-info-title', t.infoTitle); setText('t-analysis-title', t.analysisTitle); setText('t-cves-label', t.cvesLabel); setText('t-analyze-btn', t.analyzeBtn); setText('t-example-btn', t.exampleBtn); setText('t-clear-btn', t.clearBtn); setText('t-report-title', t.reportTitle); setText('t-postfix-label', t.postfixLabel); setText('t-products-title', t.productsTitle); setText('t-refresh-products', t.refreshProducts); // Status inline text value for "not configured" const cfgStatus = document.getElementById('configStatus'); if (cfgStatus && (!cfgStatus.textContent || cfgStatus.textContent.trim().length === 0 || cfgStatus.textContent === 'No configurada' || cfgStatus.textContent === 'Not configured')) { cfgStatus.textContent = t.notConfigured; } } // Initialize locale on load document.addEventListener('DOMContentLoaded', () => { applyLocale(currentLocale || 'en'); }); function saveApiKey() { const apiUrl = (document.getElementById('apiUrl').value || '').trim() || 'https://api.vulnmatter.com'; const mcpUrl = (document.getElementById('mcpUrl').value || '').trim() || 'https://mcp.singularity-matter.com/sse'; const apiKey = document.getElementById('apiKey').value.trim(); if (!apiKey) { showNotification('Please enter your X-API-Key', 'error'); return; } // Show loading state const saveBtn = document.querySelector('[onclick="saveApiKey()"]'); const originalText = saveBtn.textContent; saveBtn.classList.add('loading'); saveBtn.disabled = true; saveBtn.textContent = 'Saving...'; vscode.postMessage({ command: 'setApiConfig', apiKey: apiKey, apiUrl: apiUrl, mcpUrl: mcpUrl }); // Reset button state after a delay setTimeout(() => { saveBtn.classList.remove('loading'); saveBtn.disabled = false; saveBtn.textContent = originalText; }, 2000); } function refreshStatus() { const statusIndicator = document.getElementById('statusIndicator'); const configStatus = document.getElementById('configStatus'); // Animate refresh statusIndicator.style.animation = 'spin 1s linear'; configStatus.textContent = 'Refreshing...'; vscode.postMessage({ command: 'refreshStatus' }); setTimeout(() => { statusIndicator.style.animation = ''; }, 1000); } function showNotification(message, type = 'info') { // Create notification element const notification = document.createElement('div'); notification.className = `notification notification-${type}`; notification.textContent = message; notification.style.cssText = ` position: fixed; top: 20px; right: 20px; padding: 12px 20px; border-radius: 8px; color: white; font-weight: 500; z-index: 10000; animation: slideIn 0.3s ease; ${type === 'error' ? 'background: #ef4444;' : type === 'success' ? 'background: #10b981;' : 'background: var(--vscode-textLink-foreground);'} `; document.body.appendChild(notification); // Auto remove after 3 seconds setTimeout(() => { notification.style.animation = 'slideOut 0.3s ease'; setTimeout(() => { if (notification.parentNode) { notification.parentNode.removeChild(notification); } }, 300); }, 3000); } function showConfigPath() { vscode.postMessage({ command: 'showConfigPath' }); } function showMcpConfig() { vscode.postMessage({ command: 'showMcpConfig' }); } function toggleMcpFs(checkbox) { const s = document.getElementById('mcpFsStatusText'); if (checkbox.checked) { if (s) s.textContent = 'Configurando...'; vscode.postMessage({ command: 'configureMcpFs' }); } else { if (s) s.textContent = 'Deshabilitado (no se elimina automáticamente)'; } } function toggleMcpVSCode(checkbox) { const s = document.getElementById('mcpVSCodeStatusText'); if (checkbox.checked) { if (s) s.textContent = 'Configurando...'; vscode.postMessage({ command: 'configureMcpVSCode', enabled: true }); } else { if (s) s.textContent = 'Removiendo...'; vscode.postMessage({ command: 'configureMcpVSCode', enabled: false }); } } function toggleMcpClaude(checkbox) { const s = document.getElementById('mcpClaudeStatusText'); if (checkbox.checked) { if (s) s.textContent = 'Configurando...'; vscode.postMessage({ command: 'configureMcpClaude', enabled: true }); } else { if (s) s.textContent = 'Removiendo...'; vscode.postMessage({ command: 'configureMcpClaude', enabled: false }); } } function toggleMcpVulnMatter(checkbox) { if (checkbox.checked) { vscode.postMessage({ command: 'configureMcpVulnMatter', enabled: true }); } else { vscode.postMessage({ command: 'configureMcpVulnMatter', enabled: false }); } } function requestProducts() { vscode.postMessage({ command: 'listProducts' }); } function analyzeCVEs() { const cveText = document.getElementById('cveList').value; if (!cveText.trim()) { alert('Por favor ingresa al menos un CVE'); return; } const cves = cveText.split('\n') .map(line => line.trim()) .filter(line => line && !line.startsWith('#')); vscode.postMessage({ command: 'analyzeCVEs', cves: cves }); } function generateReport() { const cveText = document.getElementById('cveList').value; const postfix = document.getElementById('reportPostfix').value; if (!cveText.trim()) { alert('Por favor ingresa al menos un CVE para el reporte'); return; } const cves = cveText.split('\n') .map(line => line.trim()) .filter(line => line && !line.startsWith('#')); vscode.postMessage({ command: 'generateReport', cves: cves, postfix: postfix }); } function clearResults() { document.getElementById('results').style.display = 'none'; document.getElementById('reportResults').style.display = 'none'; document.getElementById('cveList').value = ''; document.getElementById('reportPostfix').value = ''; } function loadExampleCVEs() { const examples = [ 'CVE-2023-44487', 'CVE-2023-38545', 'CVE-2024-21413', 'CVE-2024-3094', 'CVE-2023-4911' ]; document.getElementById('cveList').value = examples.join('\n'); } function getScoreClass(score) { if (score >= 7) return 'high'; if (score >= 4) return 'medium'; return 'low'; } function updateConfigStatus(hasKey, configPath, userHome) { const statusIndicator = document.getElementById('statusIndicator'); const statusText = document.getElementById('configStatus'); if (hasKey) { statusIndicator.className = 'status-indicator status-ok'; statusText.textContent = 'X-API-Key configurada ✓'; } else { statusIndicator.className = 'status-indicator status-error'; statusText.textContent = 'X-API-Key no configurada'; } if (configPath) { document.getElementById('configPathDisplay').textContent = configPath; } if (userHome) { document.getElementById('userHomeDisplay').textContent = userHome; } } window.addEventListener('message', event => { const message = event.data; switch (message.command) { case 'initConfig': if (message.apiKey) { document.getElementById('apiKey').value = message.apiKey; } if (message.apiUrl) { document.getElementById('apiUrl').value = message.apiUrl; } if (message.mcpUrl) { const mcpEl = document.getElementById('mcpUrl'); if (mcpEl) mcpEl.value = message.mcpUrl; } const urlDisp = document.getElementById('apiUrlDisplay'); if (urlDisp) urlDisp.textContent = (document.getElementById('apiUrl').value || 'https://api.vulnmatter.com'); if (message.locale) { applyLocale(message.locale); } else { applyLocale('en'); } updateConfigStatus(!!message.apiKey, message.configPath, message.userHome); break; case 'apiKeySaved': updateConfigStatus(true, message.configPath); // Also refresh the info panel URL display from current input value const urlDisp2 = document.getElementById('apiUrlDisplay'); const urlIn = document.getElementById('apiUrl'); if (urlDisp2 && urlIn) urlDisp2.textContent = urlIn.value.trim() || 'https://api.vulnmatter.com'; showNotification('Configuration saved successfully!', 'success'); break; case 'mcpFsStatus': const txt = document.getElementById('mcpFsStatusText'); if (message.configured) { txt.textContent = `Configured${message.dir ? ' in ' + message.dir : ''}${message.note ? ' (' + message.note + ')' : ''}`; const cb = document.getElementById('mcpFsCheckbox'); if (cb) cb.checked = true; if (message.justConfigured) { showNotification('MCP Filesystem configured successfully!', 'success'); } } else { txt.textContent = message.note ? message.note : 'Not configured'; const cb = document.getElementById('mcpFsCheckbox'); if (cb) cb.checked = false; } break; case 'mcpVSCodeStatus': const txtVS = document.getElementById('mcpVSCodeStatusText'); if (message.configured) { if (txtVS) txtVS.textContent = `Configured${message.note ? ' (' + message.note + ')' : ''}`; const cbVS = document.getElementById('mcpVSCodeCheckbox'); if (cbVS) cbVS.checked = true; if (message.justConfigured) { showNotification('MCP VulnMatter (VS Code) configured successfully!', 'success'); } } else { if (txtVS) txtVS.textContent = message.note ? message.note : 'Not configured'; const cbVS = document.getElementById('mcpVSCodeCheckbox'); if (cbVS) cbVS.checked = false; } break; case 'mcpClaudeStatus': const txtCl = document.getElementById('mcpClaudeStatusText'); if (message.configured) { if (txtCl) txtCl.textContent = `Configured${message.note ? ' (' + message.note + ')' : ''}`;