add UI
All checks were successful
Build and Deploy Log Center / build-and-deploy (push) Successful in 1m42s
All checks were successful
Build and Deploy Log Center / build-and-deploy (push) Successful in 1m42s
This commit is contained in:
parent
2d03f01ecc
commit
20a3b0b374
10
web/package-lock.json
generated
10
web/package-lock.json
generated
@ -9,6 +9,7 @@
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"axios": "^1.13.4",
|
||||
"lucide-react": "^0.563.0",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-router-dom": "^7.13.0"
|
||||
@ -2872,6 +2873,15 @@
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-react": {
|
||||
"version": "0.563.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.563.0.tgz",
|
||||
"integrity": "sha512-8dXPB2GI4dI8jV4MgUDGBeLdGk8ekfqVZ0BdLcrRzocGgG75ltNEmWS+gE7uokKF/0oSUuczNDT+g9hFJ23FkA==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.13.4",
|
||||
"lucide-react": "^0.563.0",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-router-dom": "^7.13.0"
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { BrowserRouter, Routes, Route, NavLink } from 'react-router-dom';
|
||||
import { LayoutDashboard, Bug, Wrench, Shield } from 'lucide-react';
|
||||
import Dashboard from './pages/Dashboard';
|
||||
import BugList from './pages/BugList';
|
||||
import BugDetail from './pages/BugDetail';
|
||||
@ -12,7 +13,10 @@ function App() {
|
||||
<div className="app">
|
||||
<aside className="sidebar">
|
||||
<div className="logo">
|
||||
🛡️ <span>Log Center</span>
|
||||
<div className="logo-icon">
|
||||
<Shield size={16} />
|
||||
</div>
|
||||
Log Center
|
||||
</div>
|
||||
<nav>
|
||||
<ul className="nav-menu">
|
||||
@ -22,7 +26,8 @@ function App() {
|
||||
className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`}
|
||||
end
|
||||
>
|
||||
📊 Dashboard
|
||||
<LayoutDashboard size={16} />
|
||||
Dashboard
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
@ -30,7 +35,8 @@ function App() {
|
||||
to="/bugs"
|
||||
className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`}
|
||||
>
|
||||
🐛 Bug List
|
||||
<Bug size={16} />
|
||||
Bug List
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
@ -38,7 +44,8 @@ function App() {
|
||||
to="/repairs"
|
||||
className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`}
|
||||
>
|
||||
🔧 Repair Reports
|
||||
<Wrench size={16} />
|
||||
Repair Reports
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@ -70,8 +70,11 @@ export const getBugDetail = (id: number) => api.get<ErrorLog>(`/api/v1/bugs/${id
|
||||
|
||||
export const getProjects = () => api.get<{ projects: string[] }>('/api/v1/projects');
|
||||
|
||||
export const updateTaskStatus = (taskId: number, status: string) =>
|
||||
api.put(`/api/v1/tasks/${taskId}/status`, { status });
|
||||
export const updateTaskStatus = (taskId: number, status: string, message?: string) =>
|
||||
api.put(`/api/v1/tasks/${taskId}/status`, { status, message });
|
||||
|
||||
export const triggerRepair = (bugId: number) =>
|
||||
updateTaskStatus(bugId, 'PENDING_FIX', 'Triggered from UI');
|
||||
|
||||
export const getRepairReports = (params: {
|
||||
page?: number;
|
||||
@ -82,4 +85,3 @@ export const getRepairReports = (params: {
|
||||
export const getRepairReportDetail = (id: number) => api.get<RepairReport>(`/api/v1/repair/reports/${id}`);
|
||||
|
||||
export default api;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,14 +1,16 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useParams, Link, useLocation } from 'react-router-dom';
|
||||
import { getBugDetail, type ErrorLog } from '../api';
|
||||
import { ArrowLeft, Play, Loader2, FileCode, GitCommit } from 'lucide-react';
|
||||
import { getBugDetail, triggerRepair, type ErrorLog } from '../api';
|
||||
|
||||
export default function BugDetail() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const location = useLocation();
|
||||
const [bug, setBug] = useState<ErrorLog | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [repairing, setRepairing] = useState(false);
|
||||
const [repairMessage, setRepairMessage] = useState('');
|
||||
|
||||
// Preserve search params from the list page for back navigation
|
||||
const backSearch = location.state?.fromSearch || '';
|
||||
|
||||
useEffect(() => {
|
||||
@ -26,6 +28,22 @@ export default function BugDetail() {
|
||||
fetchBug();
|
||||
}, [id]);
|
||||
|
||||
const handleTriggerRepair = async () => {
|
||||
if (!bug) return;
|
||||
setRepairing(true);
|
||||
setRepairMessage('');
|
||||
try {
|
||||
await triggerRepair(bug.id);
|
||||
setBug({ ...bug, status: 'PENDING_FIX' });
|
||||
setRepairMessage('Repair triggered successfully');
|
||||
} catch (error) {
|
||||
console.error('Failed to trigger repair:', error);
|
||||
setRepairMessage('Failed to trigger repair');
|
||||
} finally {
|
||||
setRepairing(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="loading">
|
||||
@ -38,10 +56,13 @@ export default function BugDetail() {
|
||||
return <div className="loading">Bug not found</div>;
|
||||
}
|
||||
|
||||
const canTriggerRepair = ['NEW', 'FIX_FAILED'].includes(bug.status);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Link to={`/bugs${backSearch ? `?${backSearch}` : ''}`} className="back-link">
|
||||
← Back to Bug List
|
||||
<ArrowLeft size={14} />
|
||||
Back to Bug List
|
||||
</Link>
|
||||
|
||||
<div className="detail-card">
|
||||
@ -52,33 +73,35 @@ export default function BugDetail() {
|
||||
</h2>
|
||||
<div className="detail-meta">
|
||||
<span>Project: {bug.project_id}</span>
|
||||
<span>Environment: {bug.environment}</span>
|
||||
<span>Env: {bug.environment}</span>
|
||||
<span>Level: {bug.level}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span className={`status-badge status-${bug.status}`}>{bug.status}</span>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '24px' }}>
|
||||
<h4 style={{ marginBottom: '12px', color: 'var(--text-secondary)' }}>Location</h4>
|
||||
<p style={{ fontFamily: 'monospace', fontSize: '14px' }}>
|
||||
📁 {bug.file_path} : Line {bug.line_number}
|
||||
</p>
|
||||
<div className="detail-section">
|
||||
<div className="detail-section-title">Location</div>
|
||||
<div className="detail-section-value">
|
||||
<FileCode size={14} style={{ display: 'inline', verticalAlign: 'middle', marginRight: '6px' }} />
|
||||
{bug.file_path} : Line {bug.line_number}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{bug.commit_hash && (
|
||||
<div style={{ marginBottom: '24px' }}>
|
||||
<h4 style={{ marginBottom: '12px', color: 'var(--text-secondary)' }}>Git Info</h4>
|
||||
<p style={{ fontFamily: 'monospace', fontSize: '14px' }}>
|
||||
Commit: {bug.commit_hash}
|
||||
{bug.version && ` | Version: ${bug.version}`}
|
||||
</p>
|
||||
<div className="detail-section">
|
||||
<div className="detail-section-title">Git Info</div>
|
||||
<div className="detail-section-value">
|
||||
<GitCommit size={14} style={{ display: 'inline', verticalAlign: 'middle', marginRight: '6px' }} />
|
||||
{bug.commit_hash}
|
||||
{bug.version && ` | v${bug.version}`}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ marginBottom: '24px' }}>
|
||||
<h4 style={{ marginBottom: '12px', color: 'var(--text-secondary)' }}>Stack Trace</h4>
|
||||
<pre className="stack-trace">
|
||||
<div className="detail-section">
|
||||
<div className="detail-section-title">Stack Trace</div>
|
||||
<pre className="code-block error">
|
||||
{typeof bug.stack_trace === 'string'
|
||||
? bug.stack_trace
|
||||
: JSON.stringify(bug.stack_trace, null, 2)}
|
||||
@ -86,33 +109,61 @@ export default function BugDetail() {
|
||||
</div>
|
||||
|
||||
{bug.context && Object.keys(bug.context).length > 0 && (
|
||||
<div>
|
||||
<h4 style={{ marginBottom: '12px', color: 'var(--text-secondary)' }}>Context</h4>
|
||||
<pre className="stack-trace" style={{ color: 'var(--accent)' }}>
|
||||
<div className="detail-section">
|
||||
<div className="detail-section-title">Context</div>
|
||||
<pre className="code-block accent">
|
||||
{JSON.stringify(bug.context, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="actions-bar">
|
||||
<button
|
||||
className="trigger-repair-btn"
|
||||
onClick={handleTriggerRepair}
|
||||
disabled={!canTriggerRepair || repairing}
|
||||
>
|
||||
{repairing ? (
|
||||
<Loader2 size={14} className="spinner" />
|
||||
) : (
|
||||
<Play size={14} />
|
||||
)}
|
||||
{repairing ? 'Triggering...' : 'Trigger Repair'}
|
||||
</button>
|
||||
{repairMessage && (
|
||||
<span style={{
|
||||
fontSize: '13px',
|
||||
color: repairMessage.includes('success') ? 'var(--success)' : 'var(--error)'
|
||||
}}>
|
||||
{repairMessage}
|
||||
</span>
|
||||
)}
|
||||
{!canTriggerRepair && !repairing && (
|
||||
<span style={{ fontSize: '13px', color: 'var(--text-tertiary)' }}>
|
||||
Only NEW or FIX_FAILED bugs can be triggered
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="detail-card">
|
||||
<h4 style={{ marginBottom: '16px' }}>Metadata</h4>
|
||||
<table>
|
||||
<div className="detail-section-title" style={{ marginBottom: '12px' }}>Metadata</div>
|
||||
<table className="meta-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style={{ color: 'var(--text-secondary)' }}>Bug ID</td>
|
||||
<td className="meta-label">Bug ID</td>
|
||||
<td>{bug.id}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={{ color: 'var(--text-secondary)' }}>Fingerprint</td>
|
||||
<td style={{ fontFamily: 'monospace', fontSize: '13px' }}>{bug.fingerprint}</td>
|
||||
<td className="meta-label">Fingerprint</td>
|
||||
<td className="cell-mono">{bug.fingerprint}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={{ color: 'var(--text-secondary)' }}>Retry Count</td>
|
||||
<td className="meta-label">Retry Count</td>
|
||||
<td>{bug.retry_count}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={{ color: 'var(--text-secondary)' }}>Reported At</td>
|
||||
<td className="meta-label">Reported At</td>
|
||||
<td>{new Date(bug.timestamp).toLocaleString()}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@ -15,7 +15,6 @@ export default function BugList() {
|
||||
const [totalPages, setTotalPages] = useState(1);
|
||||
const [projects, setProjects] = useState<string[]>([]);
|
||||
|
||||
// Read filters from URL params, default status to NEW
|
||||
const currentProject = searchParams.get('project') || '';
|
||||
const currentStatus = searchParams.get('status') ?? 'NEW';
|
||||
const currentPage = parseInt(searchParams.get('page') || '1', 10);
|
||||
@ -30,7 +29,6 @@ export default function BugList() {
|
||||
next.delete(key);
|
||||
}
|
||||
}
|
||||
// Reset page when changing filters
|
||||
if ('project' in updates || 'status' in updates) {
|
||||
next.delete('page');
|
||||
}
|
||||
@ -77,7 +75,6 @@ export default function BugList() {
|
||||
<p className="page-subtitle">All reported errors and their current status</p>
|
||||
</div>
|
||||
|
||||
{/* Project breadcrumb navigation */}
|
||||
<div className="project-tabs">
|
||||
<button
|
||||
className={`project-tab ${currentProject === '' ? 'active' : ''}`}
|
||||
@ -96,7 +93,6 @@ export default function BugList() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Status filter tabs */}
|
||||
<div className="status-tabs">
|
||||
<button
|
||||
className={`status-tab ${currentStatus === '' ? 'active' : ''}`}
|
||||
@ -146,7 +142,6 @@ export default function BugList() {
|
||||
<Link
|
||||
to={`/bugs/${bug.id}`}
|
||||
state={{ fromSearch: searchParams.toString() }}
|
||||
style={{ color: 'var(--accent)' }}
|
||||
>
|
||||
#{bug.id}
|
||||
</Link>
|
||||
@ -159,8 +154,8 @@ export default function BugList() {
|
||||
{bug.project_id}
|
||||
</button>
|
||||
</td>
|
||||
<td style={{ color: 'var(--error)' }}>{bug.error_type}</td>
|
||||
<td style={{ fontFamily: 'monospace', fontSize: '13px' }}>
|
||||
<td className="cell-error">{bug.error_type}</td>
|
||||
<td className="cell-mono">
|
||||
{bug.file_path}:{bug.line_number}
|
||||
</td>
|
||||
<td>
|
||||
@ -168,7 +163,7 @@ export default function BugList() {
|
||||
{bug.status}
|
||||
</span>
|
||||
</td>
|
||||
<td style={{ color: 'var(--text-secondary)', fontSize: '14px' }}>
|
||||
<td className="cell-secondary">
|
||||
{new Date(bug.timestamp).toLocaleString()}
|
||||
</td>
|
||||
</tr>
|
||||
@ -184,7 +179,7 @@ export default function BugList() {
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
<span style={{ padding: '8px 16px', color: 'var(--text-secondary)' }}>
|
||||
<span className="pagination-info">
|
||||
Page {currentPage} of {totalPages}
|
||||
</span>
|
||||
<button
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Bug, CalendarPlus, TrendingUp, AlertTriangle } from 'lucide-react';
|
||||
import { getStats, type DashboardStats } from '../api';
|
||||
|
||||
export default function Dashboard() {
|
||||
@ -40,18 +41,30 @@ export default function Dashboard() {
|
||||
|
||||
<div className="stats-grid">
|
||||
<div className="stat-card">
|
||||
<div className="stat-icon accent">
|
||||
<Bug size={18} />
|
||||
</div>
|
||||
<div className="stat-label">Total Bugs</div>
|
||||
<div className="stat-value accent">{stats.total_bugs}</div>
|
||||
</div>
|
||||
<div className="stat-card">
|
||||
<div className="stat-label">Today's New Bugs</div>
|
||||
<div className="stat-icon warning">
|
||||
<CalendarPlus size={18} />
|
||||
</div>
|
||||
<div className="stat-label">Today's New</div>
|
||||
<div className="stat-value warning">{stats.today_bugs}</div>
|
||||
</div>
|
||||
<div className="stat-card">
|
||||
<div className="stat-icon success">
|
||||
<TrendingUp size={18} />
|
||||
</div>
|
||||
<div className="stat-label">Fix Rate</div>
|
||||
<div className="stat-value success">{stats.fix_rate}%</div>
|
||||
</div>
|
||||
<div className="stat-card">
|
||||
<div className="stat-icon error">
|
||||
<AlertTriangle size={18} />
|
||||
</div>
|
||||
<div className="stat-label">Pending Fix</div>
|
||||
<div className="stat-value error">
|
||||
{(stats.status_distribution['NEW'] || 0) +
|
||||
|
||||
@ -1,87 +1,91 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { ArrowLeft, Bot, FileCode, FlaskConical } from 'lucide-react';
|
||||
import { getRepairReportDetail, type RepairReport } from '../api';
|
||||
|
||||
function RepairDetail() {
|
||||
export default function RepairDetail() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const [report, setReport] = useState<RepairReport | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
fetchDetail(parseInt(id));
|
||||
const fetchDetail = async () => {
|
||||
try {
|
||||
const res = await getRepairReportDetail(parseInt(id));
|
||||
setReport(res.data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
fetchDetail();
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
const fetchDetail = async (reportId: number) => {
|
||||
try {
|
||||
const res = await getRepairReportDetail(reportId);
|
||||
setReport(res.data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="loading">
|
||||
<div className="spinner"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (loading) return <div className="loading">Loading...</div>;
|
||||
if (!report) return <div className="error">Report not found</div>;
|
||||
if (!report) {
|
||||
return <div className="loading">Report not found</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bug-detail-page">
|
||||
<div className="header">
|
||||
<div className="breadcrumb">
|
||||
<Link to="/repairs">Repair Reports</Link> / #{report.id}
|
||||
</div>
|
||||
<div>
|
||||
<Link to="/repairs" className="back-link">
|
||||
<ArrowLeft size={14} />
|
||||
Back to Repair Reports
|
||||
</Link>
|
||||
|
||||
<div style={{ marginBottom: '20px' }}>
|
||||
<div className="title-row">
|
||||
<h1>Repair Report #{report.id}</h1>
|
||||
<span className={`status-badge ${report.status}`}>{report.status}</span>
|
||||
<h1 className="page-title">Repair Report #{report.id}</h1>
|
||||
<span className={`status-badge status-${report.status}`}>{report.status}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="detail-grid" style={{ gridTemplateColumns: '1fr' }}>
|
||||
<div className="card">
|
||||
<h2>Basic Info</h2>
|
||||
<div className="info-row">
|
||||
<span>Project:</span> <strong>{report.project_id}</strong>
|
||||
</div>
|
||||
<div className="info-row">
|
||||
<span>Bug ID:</span> <Link to={`/bugs/${report.error_log_id}`}>#{report.error_log_id}</Link>
|
||||
</div>
|
||||
<div className="info-row">
|
||||
<span>Created At:</span> {new Date(report.created_at).toLocaleString()}
|
||||
</div>
|
||||
<div className="info-row">
|
||||
<span>Test Result:</span>
|
||||
<span style={{ color: report.test_passed ? '#10b981' : '#ef4444', fontWeight: 'bold', marginLeft: '8px' }}>
|
||||
{report.test_passed ? 'PASS' : 'FAIL'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="card">
|
||||
<h2>Basic Info</h2>
|
||||
<div className="info-row">
|
||||
<span>Project</span>
|
||||
<strong>{report.project_id}</strong>
|
||||
</div>
|
||||
<div className="info-row">
|
||||
<span>Bug ID</span>
|
||||
<Link to={`/bugs/${report.error_log_id}`}>#{report.error_log_id}</Link>
|
||||
</div>
|
||||
<div className="info-row">
|
||||
<span>Created At</span>
|
||||
<span>{new Date(report.created_at).toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="info-row">
|
||||
<span>Test Result</span>
|
||||
<span className={report.test_passed ? 'test-pass' : 'test-fail'}>
|
||||
{report.test_passed ? 'PASS' : 'FAIL'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card">
|
||||
<h2>🤖 AI Analysis</h2>
|
||||
<div className="stack-trace">
|
||||
<pre style={{ whiteSpace: 'pre-wrap' }}>{report.ai_analysis}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card">
|
||||
<h2><Bot size={16} /> AI Analysis</h2>
|
||||
<pre className="code-block neutral">{report.ai_analysis}</pre>
|
||||
</div>
|
||||
|
||||
<div className="card">
|
||||
<h2>📝 Code Changes</h2>
|
||||
<div className="stack-trace">
|
||||
<pre>{report.code_diff || 'No changes recorded'}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card">
|
||||
<h2><FileCode size={16} /> Code Changes</h2>
|
||||
<pre className="code-block neutral">{report.code_diff || 'No changes recorded'}</pre>
|
||||
</div>
|
||||
|
||||
<div className="card">
|
||||
<h2>🧪 Test Output</h2>
|
||||
<div className="stack-trace">
|
||||
<pre>{report.test_output}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card">
|
||||
<h2><FlaskConical size={16} /> Test Output</h2>
|
||||
<pre className="code-block neutral">{report.test_output}</pre>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default RepairDetail;
|
||||
|
||||
@ -2,7 +2,7 @@ import { useState, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { getRepairReports, type RepairReport, getProjects } from '../api';
|
||||
|
||||
function RepairList() {
|
||||
export default function RepairList() {
|
||||
const [reports, setReports] = useState<RepairReport[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [projects, setProjects] = useState<string[]>([]);
|
||||
@ -13,146 +13,139 @@ function RepairList() {
|
||||
const [totalPages, setTotalPages] = useState(1);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchProjects = async () => {
|
||||
try {
|
||||
const res = await getProjects();
|
||||
setProjects(res.data.projects);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
fetchProjects();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchReports = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await getRepairReports({
|
||||
page: filters.page,
|
||||
project_id: filters.project_id || undefined,
|
||||
});
|
||||
setReports(res.data.items);
|
||||
setTotalPages(res.data.total_pages);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
fetchReports();
|
||||
}, [filters]);
|
||||
|
||||
const fetchProjects = async () => {
|
||||
try {
|
||||
const res = await getProjects();
|
||||
setProjects(res.data.projects);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchReports = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await getRepairReports({
|
||||
page: filters.page,
|
||||
project_id: filters.project_id || undefined,
|
||||
});
|
||||
setReports(res.data.items);
|
||||
setTotalPages(res.data.total_pages);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFilterChange = (key: string, value: any) => {
|
||||
const handleFilterChange = (key: string, value: string) => {
|
||||
setFilters((prev) => ({ ...prev, [key]: value, page: 1 }));
|
||||
};
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'FIXED': return '#10b981';
|
||||
case 'FIX_FAILED': return '#ef4444';
|
||||
default: return '#6b7280';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bug-list-page">
|
||||
<div className="header">
|
||||
<h1>🔧 Repair Reports</h1>
|
||||
<div className="filters">
|
||||
<select
|
||||
value={filters.project_id}
|
||||
onChange={(e) => handleFilterChange('project_id', e.target.value)}
|
||||
>
|
||||
<option value="">All Projects</option>
|
||||
{projects.map((p) => (
|
||||
<option key={p} value={p}>
|
||||
{p}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div>
|
||||
<div className="page-header">
|
||||
<div className="title-row">
|
||||
<div>
|
||||
<h1 className="page-title">Repair Reports</h1>
|
||||
<p className="page-subtitle">AI-powered bug repair attempts and their results</p>
|
||||
</div>
|
||||
<div className="filters">
|
||||
<select
|
||||
className="filter-select"
|
||||
value={filters.project_id}
|
||||
onChange={(e) => handleFilterChange('project_id', e.target.value)}
|
||||
>
|
||||
<option value="">All Projects</option>
|
||||
{projects.map((p) => (
|
||||
<option key={p} value={p}>{p}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Project</th>
|
||||
<th>Bug ID</th>
|
||||
<th>Modified Files</th>
|
||||
<th>Test Passed</th>
|
||||
<th>Status</th>
|
||||
<th>Date</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{loading ? (
|
||||
{loading ? (
|
||||
<div className="loading">
|
||||
<div className="spinner"></div>
|
||||
</div>
|
||||
) : reports.length === 0 ? (
|
||||
<div className="empty-state">No reports found</div>
|
||||
) : (
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td colSpan={8} className="text-center">Loading...</td>
|
||||
<th>ID</th>
|
||||
<th>Project</th>
|
||||
<th>Bug ID</th>
|
||||
<th>Modified Files</th>
|
||||
<th>Test Result</th>
|
||||
<th>Status</th>
|
||||
<th>Date</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
) : reports.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={8} className="text-center">No reports found</td>
|
||||
</tr>
|
||||
) : (
|
||||
reports.map((report) => (
|
||||
</thead>
|
||||
<tbody>
|
||||
{reports.map((report) => (
|
||||
<tr key={report.id}>
|
||||
<td>#{report.id}</td>
|
||||
<td>{report.project_id}</td>
|
||||
<td>
|
||||
<Link to={`/bugs/${report.error_log_id}`}>#{report.error_log_id}</Link>
|
||||
<Link to={`/bugs/${report.error_log_id}`}>
|
||||
#{report.error_log_id}
|
||||
</Link>
|
||||
</td>
|
||||
<td>{report.modified_files.length} files</td>
|
||||
<td>
|
||||
<span style={{ color: report.test_passed ? '#10b981' : '#ef4444' }}>
|
||||
<span className={report.test_passed ? 'test-pass' : 'test-fail'}>
|
||||
{report.test_passed ? 'PASS' : 'FAIL'}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
className="status-badge"
|
||||
style={{ backgroundColor: getStatusColor(report.status) }}
|
||||
>
|
||||
<span className={`status-badge status-${report.status}`}>
|
||||
{report.status}
|
||||
</span>
|
||||
</td>
|
||||
<td>{new Date(report.created_at).toLocaleString()}</td>
|
||||
<td className="cell-secondary">
|
||||
{new Date(report.created_at).toLocaleString()}
|
||||
</td>
|
||||
<td>
|
||||
<Link to={`/repairs/${report.id}`} className="btn-link">
|
||||
View
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="pagination">
|
||||
<button
|
||||
disabled={filters.page === 1}
|
||||
onClick={() => setFilters((prev) => ({ ...prev, page: prev.page - 1 }))}
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
<span>
|
||||
Page {filters.page} of {totalPages}
|
||||
</span>
|
||||
<button
|
||||
disabled={filters.page === totalPages}
|
||||
onClick={() => setFilters((prev) => ({ ...prev, page: prev.page + 1 }))}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
{totalPages > 1 && (
|
||||
<div className="pagination">
|
||||
<button
|
||||
disabled={filters.page === 1}
|
||||
onClick={() => setFilters((prev) => ({ ...prev, page: prev.page - 1 }))}
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
<span className="pagination-info">
|
||||
Page {filters.page} of {totalPages}
|
||||
</span>
|
||||
<button
|
||||
disabled={filters.page === totalPages}
|
||||
onClick={() => setFilters((prev) => ({ ...prev, page: prev.page + 1 }))}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default RepairList;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user