| import { useState } from 'react'; |
| import axios from 'axios'; |
|
|
| const API = ''; |
|
|
| const SEVERITY_COLORS = { |
| Critical: 'var(--critical)', |
| High: 'var(--high)', |
| Medium: 'var(--medium)', |
| Low: 'var(--low)', |
| }; |
|
|
| export default function ReportPanel({ clusters, anomalies, stats }) { |
| const [report, setReport] = useState(null); |
| const [loading, setLoading] = useState(false); |
| const [error, setError] = useState(null); |
|
|
| async function generateReport() { |
| setLoading(true); |
| setError(null); |
| try { |
| const response = await axios.post(`/api/cluster/report`, { |
| clusters, anomalies, stats |
| }); |
| setReport(response.data); |
| } catch (err) { |
| setError('Failed to generate report'); |
| } finally { |
| setLoading(false); |
| } |
| } |
|
|
| function downloadMarkdown() { |
| if (!report) return; |
| const md = buildMarkdown(report); |
| const blob = new Blob([md], { type: 'text/markdown' }); |
| const url = URL.createObjectURL(blob); |
| const a = document.createElement('a'); |
| a.href = url; |
| a.download = 'incident-report.md'; |
| a.click(); |
| URL.revokeObjectURL(url); |
| } |
|
|
| function buildMarkdown(r) { |
| return `# Incident Report |
| |
| ## Executive Summary |
| ${r.executive_summary} |
| |
| **Overall Severity:** ${r.overall_severity} |
| **Affected Systems:** ${r.estimated_affected_systems?.join(', ') || 'Unknown'} |
| |
| ## Top Issues |
| ${r.top_issues?.map(issue => ` |
| ### ${issue.rank}. ${issue.label} — ${issue.severity} |
| - **Impact:** ${issue.impact} |
| - **Suggested Fix:** ${issue.suggested_fix} |
| `).join('\n')} |
| |
| ## Anomaly Assessment |
| ${r.anomaly_assessment} |
| |
| --- |
| *Generated by Error Clustering Engine* |
| `; |
| } |
|
|
| return ( |
| <div className="panel fade-in"> |
| <div className="panel-header"> |
| <span className="panel-title">ROOT CAUSE REPORT</span> |
| <div className="panel-actions"> |
| {report && ( |
| <button className="btn-ghost" onClick={downloadMarkdown}> |
| Download .md |
| </button> |
| )} |
| <button |
| className="btn-primary" |
| onClick={generateReport} |
| disabled={loading} |
| > |
| {loading ? 'Generating...' : report ? 'Regenerate' : 'Generate Report'} |
| </button> |
| </div> |
| </div> |
| |
| {error && ( |
| <div style={{ padding: '12px 16px', color: 'var(--critical)', fontFamily: 'var(--font-mono)', fontSize: 12 }}> |
| ⚠ {error} |
| </div> |
| )} |
| |
| {loading && ( |
| <div style={{ padding: '16px', color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', fontSize: 12 }}> |
| Analyzing clusters and generating report... |
| </div> |
| )} |
| |
| {report && !loading && ( |
| <div className="report-body"> |
| {/* Header */} |
| <div className="report-section"> |
| <div className="report-meta"> |
| <span |
| className="severity-badge" |
| style={{ color: SEVERITY_COLORS[report.overall_severity] }} |
| > |
| {report.overall_severity?.toUpperCase()} SEVERITY |
| </span> |
| {report.estimated_affected_systems?.length > 0 && ( |
| <div className="formats-list" style={{ marginTop: 8 }}> |
| {report.estimated_affected_systems.map(s => ( |
| <div key={s} className="format-badge"> |
| <span className="format-name">{s}</span> |
| </div> |
| ))} |
| </div> |
| )} |
| </div> |
| <p className="report-summary">{report.executive_summary}</p> |
| </div> |
| |
| {/* Top issues */} |
| <div className="report-section"> |
| <div className="report-section-title">TOP ISSUES</div> |
| {report.top_issues?.map(issue => ( |
| <div key={issue.rank} className="report-issue"> |
| <div className="report-issue-header"> |
| <span className="report-rank">#{issue.rank}</span> |
| <span className="report-issue-label">{issue.label}</span> |
| <span |
| className="severity-badge" |
| style={{ color: SEVERITY_COLORS[issue.severity], marginLeft: 'auto' }} |
| > |
| {issue.severity?.toUpperCase()} |
| </span> |
| </div> |
| <div className="report-issue-detail"> |
| <span style={{ color: 'var(--text-dim)' }}>Impact: </span> |
| {issue.impact} |
| </div> |
| <div className="report-issue-detail"> |
| <span style={{ color: 'var(--text-dim)' }}>Fix: </span> |
| {issue.suggested_fix} |
| </div> |
| </div> |
| ))} |
| </div> |
| |
| {/* Anomalies */} |
| {report.anomaly_assessment && ( |
| <div className="report-section"> |
| <div className="report-section-title">ANOMALY ASSESSMENT</div> |
| <p style={{ color: 'var(--text-mid)', fontSize: 13 }}> |
| {report.anomaly_assessment} |
| </p> |
| </div> |
| )} |
| </div> |
| )} |
| |
| {!report && !loading && ( |
| <div style={{ |
| padding: '24px 16px', |
| color: 'var(--text-dim)', |
| fontFamily: 'var(--font-mono)', |
| fontSize: 12, |
| textAlign: 'center' |
| }}> |
| Click "Generate Report" to get an AI-powered incident analysis |
| </div> |
| )} |
| </div> |
| ); |
| } |