fix cn
All checks were successful
Build and Deploy Log Center / build-and-deploy (push) Successful in 1m20s

This commit is contained in:
zyc 2026-02-12 10:53:03 +08:00
parent 61cbcfc4c4
commit 7ee724f8ac
6 changed files with 168 additions and 102 deletions

View File

@ -16,7 +16,7 @@ function App() {
<div className="logo-icon">
<Shield size={16} />
</div>
Log Center
</div>
<nav>
<ul className="nav-menu">
@ -27,7 +27,7 @@ function App() {
end
>
<LayoutDashboard size={16} />
Dashboard
</NavLink>
</li>
<li className="nav-item">
@ -36,7 +36,7 @@ function App() {
className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`}
>
<Bug size={16} />
Bug List
</NavLink>
</li>
<li className="nav-item">
@ -45,7 +45,7 @@ function App() {
className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`}
>
<Wrench size={16} />
Repair Reports
</NavLink>
</li>
</ul>

View File

@ -3,6 +3,18 @@ import { useParams, Link, useLocation } from 'react-router-dom';
import { ArrowLeft, Play, Loader2, FileCode, GitCommit } from 'lucide-react';
import { getBugDetail, triggerRepair, type ErrorLog } from '../api';
const STATUS_LABELS: Record<string, string> = {
NEW: '新发现',
VERIFYING: '验证中',
CANNOT_REPRODUCE: '无法复现',
PENDING_FIX: '待修复',
FIXING: '修复中',
FIXED: '已修复',
VERIFIED: '已验证',
DEPLOYED: '已部署',
FIX_FAILED: '修复失败',
};
export default function BugDetail() {
const { id } = useParams<{ id: string }>();
const location = useLocation();
@ -35,10 +47,10 @@ export default function BugDetail() {
try {
await triggerRepair(bug.id);
setBug({ ...bug, status: 'PENDING_FIX' });
setRepairMessage('Repair triggered successfully');
setRepairMessage('已成功触发修复');
} catch (error) {
console.error('Failed to trigger repair:', error);
setRepairMessage('Failed to trigger repair');
setRepairMessage('触发修复失败');
} finally {
setRepairing(false);
}
@ -53,7 +65,7 @@ export default function BugDetail() {
}
if (!bug) {
return <div className="loading">Bug not found</div>;
return <div className="loading"></div>;
}
const canTriggerRepair = ['NEW', 'FIX_FAILED'].includes(bug.status);
@ -62,7 +74,7 @@ export default function BugDetail() {
<div>
<Link to={`/bugs${backSearch ? `?${backSearch}` : ''}`} className="back-link">
<ArrowLeft size={14} />
Back to Bug List
</Link>
<div className="detail-card">
@ -72,25 +84,27 @@ export default function BugDetail() {
{bug.error_type}: {bug.error_message}
</h2>
<div className="detail-meta">
<span>Project: {bug.project_id}</span>
<span>Env: {bug.environment}</span>
<span>Level: {bug.level}</span>
<span>{bug.project_id}</span>
<span>{bug.environment}</span>
<span>{bug.level}</span>
</div>
</div>
<span className={`status-badge status-${bug.status}`}>{bug.status}</span>
<span className={`status-badge status-${bug.status}`}>
{STATUS_LABELS[bug.status] || bug.status}
</span>
</div>
<div className="detail-section">
<div className="detail-section-title">Location</div>
<div className="detail-section-title"></div>
<div className="detail-section-value">
<FileCode size={14} style={{ display: 'inline', verticalAlign: 'middle', marginRight: '6px' }} />
{bug.file_path} : Line {bug.line_number}
{bug.file_path} : {bug.line_number}
</div>
</div>
{bug.commit_hash && (
<div className="detail-section">
<div className="detail-section-title">Git Info</div>
<div className="detail-section-title">Git </div>
<div className="detail-section-value">
<GitCommit size={14} style={{ display: 'inline', verticalAlign: 'middle', marginRight: '6px' }} />
{bug.commit_hash}
@ -100,7 +114,7 @@ export default function BugDetail() {
)}
<div className="detail-section">
<div className="detail-section-title">Stack Trace</div>
<div className="detail-section-title"></div>
<pre className="code-block error">
{typeof bug.stack_trace === 'string'
? bug.stack_trace
@ -110,7 +124,7 @@ export default function BugDetail() {
{bug.context && Object.keys(bug.context).length > 0 && (
<div className="detail-section">
<div className="detail-section-title">Context</div>
<div className="detail-section-title"></div>
<pre className="code-block accent">
{JSON.stringify(bug.context, null, 2)}
</pre>
@ -128,42 +142,42 @@ export default function BugDetail() {
) : (
<Play size={14} />
)}
{repairing ? 'Triggering...' : 'Trigger Repair'}
{repairing ? '触发中...' : '触发修复'}
</button>
{repairMessage && (
<span style={{
fontSize: '13px',
color: repairMessage.includes('success') ? 'var(--success)' : 'var(--error)'
color: repairMessage.includes('成功') ? '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">
<div className="detail-section-title" style={{ marginBottom: '12px' }}>Metadata</div>
<div className="detail-section-title" style={{ marginBottom: '12px' }}></div>
<table className="meta-table">
<tbody>
<tr>
<td className="meta-label">Bug ID</td>
<td className="meta-label"></td>
<td>{bug.id}</td>
</tr>
<tr>
<td className="meta-label">Fingerprint</td>
<td className="meta-label"></td>
<td className="cell-mono">{bug.fingerprint}</td>
</tr>
<tr>
<td className="meta-label">Retry Count</td>
<td className="meta-label"></td>
<td>{bug.retry_count}</td>
</tr>
<tr>
<td className="meta-label">Reported At</td>
<td className="meta-label"></td>
<td>{new Date(bug.timestamp).toLocaleString()}</td>
</tr>
</tbody>

View File

@ -7,6 +7,18 @@ const STATUSES = [
'FIXING', 'FIXED', 'VERIFIED', 'DEPLOYED', 'FIX_FAILED'
];
const STATUS_LABELS: Record<string, string> = {
NEW: '新发现',
VERIFYING: '验证中',
CANNOT_REPRODUCE: '无法复现',
PENDING_FIX: '待修复',
FIXING: '修复中',
FIXED: '已修复',
VERIFIED: '已验证',
DEPLOYED: '已部署',
FIX_FAILED: '修复失败',
};
export default function BugList() {
const [searchParams, setSearchParams] = useSearchParams();
@ -71,8 +83,8 @@ export default function BugList() {
return (
<div>
<div className="page-header">
<h1 className="page-title">Bug List</h1>
<p className="page-subtitle">All reported errors and their current status</p>
<h1 className="page-title"></h1>
<p className="page-subtitle"></p>
</div>
<div className="project-tabs">
@ -80,7 +92,7 @@ export default function BugList() {
className={`project-tab ${currentProject === '' ? 'active' : ''}`}
onClick={() => updateParams({ project: '' })}
>
All Projects
</button>
{projects.map(p => (
<button
@ -98,7 +110,7 @@ export default function BugList() {
className={`status-tab ${currentStatus === '' ? 'active' : ''}`}
onClick={() => updateParams({ status: '' })}
>
All Status
</button>
{STATUSES.map(s => (
<button
@ -106,7 +118,7 @@ export default function BugList() {
className={`status-tab ${currentStatus === s ? 'active' : ''} status-color-${s}`}
onClick={() => updateParams({ status: s })}
>
{s.replace('_', ' ')}
{STATUS_LABELS[s] || s}
</button>
))}
</div>
@ -118,21 +130,21 @@ export default function BugList() {
</div>
) : bugs.length === 0 ? (
<div className="empty-state">
No bugs found
{currentProject && <span> in <strong>{currentProject}</strong></span>}
{currentStatus && <span> with status <strong>{currentStatus}</strong></span>}
{currentProject && <span><strong>{currentProject}</strong></span>}
{currentStatus && <span><strong>{STATUS_LABELS[currentStatus] || currentStatus}</strong></span>}
</div>
) : (
<>
<table>
<thead>
<tr>
<th>ID</th>
<th>Project</th>
<th>Error Type</th>
<th>File</th>
<th>Status</th>
<th>Time</th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
@ -160,7 +172,7 @@ export default function BugList() {
</td>
<td>
<span className={`status-badge status-${bug.status}`}>
{bug.status}
{STATUS_LABELS[bug.status] || bug.status}
</span>
</td>
<td className="cell-secondary">
@ -177,16 +189,16 @@ export default function BugList() {
onClick={() => updateParams({ page: String(Math.max(1, currentPage - 1)) })}
disabled={currentPage === 1}
>
Previous
</button>
<span className="pagination-info">
Page {currentPage} of {totalPages}
{currentPage} {totalPages}
</span>
<button
onClick={() => updateParams({ page: String(Math.min(totalPages, currentPage + 1)) })}
disabled={currentPage === totalPages}
>
Next
</button>
</div>
)}

View File

@ -2,6 +2,18 @@ import { useState, useEffect } from 'react';
import { Bug, CalendarPlus, TrendingUp, AlertTriangle } from 'lucide-react';
import { getStats, type DashboardStats } from '../api';
const STATUS_LABELS: Record<string, string> = {
NEW: '新发现',
VERIFYING: '验证中',
CANNOT_REPRODUCE: '无法复现',
PENDING_FIX: '待修复',
FIXING: '修复中',
FIXED: '已修复',
VERIFIED: '已验证',
DEPLOYED: '已部署',
FIX_FAILED: '修复失败',
};
export default function Dashboard() {
const [stats, setStats] = useState<DashboardStats | null>(null);
const [loading, setLoading] = useState(true);
@ -29,14 +41,14 @@ export default function Dashboard() {
}
if (!stats) {
return <div className="loading">Failed to load statistics</div>;
return <div className="loading"></div>;
}
return (
<div>
<div className="page-header">
<h1 className="page-title">Dashboard</h1>
<p className="page-subtitle">Overview of your error tracking system</p>
<h1 className="page-title"></h1>
<p className="page-subtitle"></p>
</div>
<div className="stats-grid">
@ -44,28 +56,28 @@ export default function Dashboard() {
<div className="stat-icon accent">
<Bug size={18} />
</div>
<div className="stat-label">Total Bugs</div>
<div className="stat-label"></div>
<div className="stat-value accent">{stats.total_bugs}</div>
</div>
<div className="stat-card">
<div className="stat-icon warning">
<CalendarPlus size={18} />
</div>
<div className="stat-label">Today's New</div>
<div className="stat-label"></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-label"></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-label"></div>
<div className="stat-value error">
{(stats.status_distribution['NEW'] || 0) +
(stats.status_distribution['PENDING_FIX'] || 0)}
@ -75,20 +87,22 @@ export default function Dashboard() {
<div className="table-container">
<div className="table-header">
<h3 className="table-title">Status Distribution</h3>
<h3 className="table-title"></h3>
</div>
<table>
<thead>
<tr>
<th>Status</th>
<th>Count</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{Object.entries(stats.status_distribution).map(([status, count]) => (
<tr key={status}>
<td>
<span className={`status-badge status-${status}`}>{status}</span>
<span className={`status-badge status-${status}`}>
{STATUS_LABELS[status] || status}
</span>
</td>
<td>{count}</td>
</tr>

View File

@ -3,6 +3,18 @@ import { useParams, Link } from 'react-router-dom';
import { ArrowLeft, Bot, FileCode, FlaskConical } from 'lucide-react';
import { getRepairReportDetail, type RepairReport } from '../api';
const STATUS_LABELS: Record<string, string> = {
NEW: '新发现',
VERIFYING: '验证中',
CANNOT_REPRODUCE: '无法复现',
PENDING_FIX: '待修复',
FIXING: '修复中',
FIXED: '已修复',
VERIFIED: '已验证',
DEPLOYED: '已部署',
FIX_FAILED: '修复失败',
};
export default function RepairDetail() {
const { id } = useParams<{ id: string }>();
const [report, setReport] = useState<RepairReport | null>(null);
@ -33,57 +45,59 @@ export default function RepairDetail() {
}
if (!report) {
return <div className="loading">Report not found</div>;
return <div className="loading"></div>;
}
return (
<div>
<Link to="/repairs" className="back-link">
<ArrowLeft size={14} />
Back to Repair Reports
</Link>
<div style={{ marginBottom: '20px' }}>
<div className="title-row">
<h1 className="page-title">Repair Report #{report.id}</h1>
<span className={`status-badge status-${report.status}`}>{report.status}</span>
</div>
</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'}
<h1 className="page-title"> #{report.id}</h1>
<span className={`status-badge status-${report.status}`}>
{STATUS_LABELS[report.status] || report.status}
</span>
</div>
</div>
<div className="card">
<h2><Bot size={16} /> AI Analysis</h2>
<h2></h2>
<div className="info-row">
<span></span>
<strong>{report.project_id}</strong>
</div>
<div className="info-row">
<span></span>
<Link to={`/bugs/${report.error_log_id}`}>#{report.error_log_id}</Link>
</div>
<div className="info-row">
<span></span>
<span>{new Date(report.created_at).toLocaleString()}</span>
</div>
<div className="info-row">
<span></span>
<span className={report.test_passed ? 'test-pass' : 'test-fail'}>
{report.test_passed ? '通过' : '失败'}
</span>
</div>
</div>
<div className="card">
<h2><Bot size={16} /> AI </h2>
<pre className="code-block neutral">{report.ai_analysis}</pre>
</div>
<div className="card">
<h2><FileCode size={16} /> Code Changes</h2>
<pre className="code-block neutral">{report.code_diff || 'No changes recorded'}</pre>
<h2><FileCode size={16} /> </h2>
<pre className="code-block neutral">{report.code_diff || '无变更记录'}</pre>
</div>
<div className="card">
<h2><FlaskConical size={16} /> Test Output</h2>
<h2><FlaskConical size={16} /> </h2>
<pre className="code-block neutral">{report.test_output}</pre>
</div>
</div>

View File

@ -2,6 +2,18 @@ import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { getRepairReports, type RepairReport, getProjects } from '../api';
const STATUS_LABELS: Record<string, string> = {
NEW: '新发现',
VERIFYING: '验证中',
CANNOT_REPRODUCE: '无法复现',
PENDING_FIX: '待修复',
FIXING: '修复中',
FIXED: '已修复',
VERIFIED: '已验证',
DEPLOYED: '已部署',
FIX_FAILED: '修复失败',
};
export default function RepairList() {
const [reports, setReports] = useState<RepairReport[]>([]);
const [loading, setLoading] = useState(true);
@ -52,8 +64,8 @@ export default function RepairList() {
<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>
<h1 className="page-title"></h1>
<p className="page-subtitle">AI </p>
</div>
<div className="filters">
<select
@ -61,7 +73,7 @@ export default function RepairList() {
value={filters.project_id}
onChange={(e) => handleFilterChange('project_id', e.target.value)}
>
<option value="">All Projects</option>
<option value=""></option>
{projects.map((p) => (
<option key={p} value={p}>{p}</option>
))}
@ -76,19 +88,19 @@ export default function RepairList() {
<div className="spinner"></div>
</div>
) : reports.length === 0 ? (
<div className="empty-state">No reports found</div>
<div className="empty-state"></div>
) : (
<table>
<thead>
<tr>
<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>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
@ -101,15 +113,15 @@ export default function RepairList() {
#{report.error_log_id}
</Link>
</td>
<td>{report.modified_files.length} files</td>
<td>{report.modified_files.length} </td>
<td>
<span className={report.test_passed ? 'test-pass' : 'test-fail'}>
{report.test_passed ? 'PASS' : 'FAIL'}
{report.test_passed ? '通过' : '失败'}
</span>
</td>
<td>
<span className={`status-badge status-${report.status}`}>
{report.status}
{STATUS_LABELS[report.status] || report.status}
</span>
</td>
<td className="cell-secondary">
@ -117,7 +129,7 @@ export default function RepairList() {
</td>
<td>
<Link to={`/repairs/${report.id}`} className="btn-link">
View
</Link>
</td>
</tr>
@ -133,16 +145,16 @@ export default function RepairList() {
disabled={filters.page === 1}
onClick={() => setFilters((prev) => ({ ...prev, page: prev.page - 1 }))}
>
Previous
</button>
<span className="pagination-info">
Page {filters.page} of {totalPages}
{filters.page} {totalPages}
</span>
<button
disabled={filters.page === totalPages}
onClick={() => setFilters((prev) => ({ ...prev, page: prev.page + 1 }))}
>
Next
</button>
</div>
)}