From 252876f81a642a342a0c7b2539597ab33f97f644 Mon Sep 17 00:00:00 2001 From: repair-agent Date: Tue, 17 Mar 2026 18:21:59 +0800 Subject: [PATCH] build UI --- web/index.html | 2 +- web/src/index.css | 394 ++++++++++++++++++++++++++++++--- web/src/pages/BugDetail.tsx | 221 ++++++------------ web/src/pages/BugList.tsx | 3 +- web/src/pages/Dashboard.tsx | 10 + web/src/pages/RepairDetail.tsx | 111 +++------- web/src/pages/RepairList.tsx | 3 +- 7 files changed, 475 insertions(+), 269 deletions(-) diff --git a/web/index.html b/web/index.html index 59646d0..03682df 100644 --- a/web/index.html +++ b/web/index.html @@ -6,7 +6,7 @@ - + 日志中台 - Log Center diff --git a/web/src/index.css b/web/src/index.css index 87df131..a76c38a 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -1,31 +1,36 @@ /* ============================================ - Log Center - Modern Minimalist Design System + Log Center - OLED Dark Design System + Style: Dark Mode (OLED) per ui-ux-pro-max + Colors: #020617 bg / #0F172A primary / #22C55E CTA + Fonts: Fira Sans + Fira Code ============================================ */ :root { - --bg-primary: #09090b; - --bg-secondary: #0f0f11; - --bg-card: #18181b; - --bg-surface: #1f1f23; - --bg-hover: #27272a; + --bg-primary: #020617; + --bg-secondary: #0F172A; + --bg-card: #1E293B; + --bg-surface: #1E293B; + --bg-hover: #334155; --accent: #3b82f6; --accent-hover: #2563eb; --accent-muted: rgba(59, 130, 246, 0.12); --indigo: #6366f1; - --text-primary: #fafafa; - --text-secondary: #a1a1aa; - --text-tertiary: #71717a; + --text-primary: #F8FAFC; + --text-secondary: #94A3B8; + --text-tertiary: #64748B; --success: #22c55e; --success-muted: rgba(34, 197, 94, 0.12); --warning: #f59e0b; --warning-muted: rgba(245, 158, 11, 0.12); --error: #ef4444; --error-muted: rgba(239, 68, 68, 0.12); - --border: #27272a; - --border-subtle: #1f1f23; + --border: rgba(148, 163, 184, 0.1); + --border-subtle: rgba(148, 163, 184, 0.06); --radius-sm: 6px; --radius-md: 8px; --radius-lg: 12px; + --glow-accent: 0 0 12px rgba(59, 130, 246, 0.15); + --glow-success: 0 0 12px rgba(34, 197, 94, 0.15); } * { @@ -35,7 +40,7 @@ } body { - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + font-family: 'Fira Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: var(--bg-primary); color: var(--text-primary); line-height: 1.5; @@ -128,6 +133,7 @@ a:hover { .nav-link.active { background: var(--accent-muted); color: var(--accent); + box-shadow: var(--glow-accent); } .nav-link svg { @@ -185,7 +191,8 @@ a:hover { } .stat-card:hover { - border-color: #3f3f46; + border-color: rgba(148, 163, 184, 0.2); + box-shadow: 0 2px 16px rgba(0, 0, 0, 0.3); } .stat-label { @@ -203,10 +210,10 @@ a:hover { letter-spacing: -0.02em; } -.stat-value.accent { color: var(--accent); } -.stat-value.success { color: var(--success); } -.stat-value.warning { color: var(--warning); } -.stat-value.error { color: var(--error); } +.stat-value.accent { color: var(--accent); text-shadow: 0 0 10px rgba(59, 130, 246, 0.3); } +.stat-value.success { color: var(--success); text-shadow: 0 0 10px rgba(34, 197, 94, 0.3); } +.stat-value.warning { color: var(--warning); text-shadow: 0 0 10px rgba(245, 158, 11, 0.3); } +.stat-value.error { color: var(--error); text-shadow: 0 0 10px rgba(239, 68, 68, 0.3); } .stat-icon { display: flex; @@ -288,7 +295,7 @@ td a:hover { } .cell-mono { - font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace; + font-family: 'Fira Code', 'SF Mono', 'Cascadia Code', monospace; font-size: 12px; color: var(--text-secondary); } @@ -464,7 +471,7 @@ td a:hover { } .status-tab:hover { - border-color: #3f3f46; + border-color: rgba(148, 163, 184, 0.2); color: var(--text-secondary); } @@ -642,7 +649,7 @@ td a:hover { } .detail-section-value { - font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace; + font-family: 'Fira Code', 'SF Mono', 'Cascadia Code', monospace; font-size: 13px; color: var(--text-secondary); } @@ -654,7 +661,7 @@ td a:hover { border: 1px solid var(--border); border-radius: var(--radius-md); padding: 16px; - font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace; + font-family: 'Fira Code', 'SF Mono', 'Cascadia Code', monospace; font-size: 12px; line-height: 1.6; overflow-x: auto; @@ -680,7 +687,7 @@ td a:hover { border: 1px solid var(--border); border-radius: var(--radius-md); padding: 16px; - font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace; + font-family: 'Fira Code', 'SF Mono', 'Cascadia Code', monospace; font-size: 12px; line-height: 1.6; overflow-x: auto; @@ -750,7 +757,7 @@ td a:hover { .btn-secondary:hover:not(:disabled) { background: var(--bg-hover); - border-color: #3f3f46; + border-color: rgba(148, 163, 184, 0.2); } .btn-secondary:disabled { @@ -923,7 +930,7 @@ td a:hover { .pagination button:hover:not(:disabled) { background: var(--bg-hover); - border-color: #3f3f46; + border-color: rgba(148, 163, 184, 0.2); } .pagination button:disabled { @@ -1285,6 +1292,8 @@ td a:hover { display: flex; flex-direction: column; gap: 8px; + text-decoration: none; + color: inherit; } .mobile-card-item:last-child { @@ -1326,7 +1335,7 @@ td a:hover { } .mobile-card-file { - font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace; + font-family: 'Fira Code', 'SF Mono', 'Cascadia Code', monospace; font-size: 11px; color: var(--text-secondary); word-break: break-all; @@ -1671,7 +1680,7 @@ td a:hover { } .project-card:hover { - border-color: #3f3f46; + border-color: rgba(148, 163, 184, 0.2); } .project-card-disabled { @@ -1809,6 +1818,7 @@ td a:hover { .repair-toggle-on .repair-toggle-track { background: var(--success); + box-shadow: var(--glow-success); } .repair-toggle-off .repair-toggle-track { @@ -2000,3 +2010,335 @@ td a:hover { grid-template-columns: 1fr; } } + +/* ============ Button Color Variants ============ */ + +.trigger-repair-btn.btn-success { + background: var(--success); +} + +.trigger-repair-btn.btn-success:hover:not(:disabled) { + background: #16a34a; +} + +.trigger-repair-btn.btn-error { + background: var(--error); +} + +.trigger-repair-btn.btn-error:hover:not(:disabled) { + background: #dc2626; +} + +.trigger-repair-btn.btn-warning { + background: var(--warning); +} + +.trigger-repair-btn.btn-warning:hover:not(:disabled) { + background: #d97706; +} + +/* ============ Detail Card Variants ============ */ + +.detail-card--error { + border-left: 3px solid var(--error); +} + +.detail-card--warning { + border-left: 3px solid var(--warning); +} + +.detail-card--accent { + border-left: 3px solid var(--accent); +} + +/* ============ Card Variants ============ */ + +.card--error { + border-left: 3px solid var(--error); +} + +.card--warning { + border-left: 3px solid var(--warning); +} + +.card--accent { + border-left: 3px solid var(--accent); +} + +/* ============ Severity Badge ============ */ + +.severity-badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 4px 10px; + border-radius: 12px; + font-size: 13px; + font-weight: 600; + color: #fff; +} + +.severity-badge--high { + background: var(--error); +} + +.severity-badge--mid { + background: var(--warning); +} + +.severity-badge--low { + background: var(--success); +} + +/* ============ Severity Section ============ */ + +.severity-section { + background: var(--bg-secondary); + padding: 12px; + border-radius: 6px; + margin-top: 12px; +} + +.severity-section--critical { + background: rgba(239, 68, 68, 0.08); + border-left: 3px solid var(--error); +} + +.severity-section--mid { + border-left: 3px solid var(--warning); +} + +.severity-section--low { + border-left: 3px solid var(--success); +} + +.severity-section .detail-section-title { + margin-bottom: 4px; +} + +.severity-section-desc { + font-size: 13px; + color: var(--text-secondary); +} + +/* ============ PR Info Section ============ */ + +.pr-section { + background: var(--bg-secondary); + padding: 12px; + border-radius: 6px; + margin-top: 16px; +} + +.pr-section .detail-section-title { + margin-bottom: 8px; +} + +.pr-section-row { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; +} + +.pr-section-id { + font-size: 14px; +} + +.pr-section-link { + display: inline-flex; + align-items: center; + gap: 4px; +} + +.pr-section-rejections { + font-size: 13px; + color: var(--warning); +} + +/* ============ Action Messages ============ */ + +.action-message { + font-size: 13px; +} + +.action-message--success { + color: var(--success); +} + +.action-message--error { + color: var(--error); +} + +/* ============ Action Hint ============ */ + +.action-hint { + font-size: 13px; + color: var(--text-tertiary); +} + +/* ============ Detail Header Right ============ */ + +.detail-header-badges { + display: flex; + align-items: center; + gap: 8px; +} + +/* ============ Reject Modal ============ */ + +.reject-modal-content { + max-width: 500px; +} + +.reject-modal-desc { + margin: 0 0 12px 0; + font-size: 14px; + color: var(--text-secondary); +} + +.reject-templates { + margin-bottom: 12px; +} + +.reject-templates-label { + font-size: 13px; + color: var(--text-tertiary); + margin-bottom: 6px; + display: block; +} + +.reject-templates-list { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.reject-template-btn { + font-size: 12px; + padding: 4px 8px; + background: var(--bg-secondary); + border: 1px solid var(--border); + border-radius: 4px; + cursor: pointer; + color: var(--text-secondary); + transition: all 0.15s ease; +} + +.reject-template-btn:hover { + background: var(--bg-hover); + color: var(--text-primary); + border-color: rgba(148, 163, 184, 0.2); +} + +.reject-textarea { + width: 100%; + min-height: 120px; + padding: 8px; + font-size: 14px; + border-radius: 4px; + border: 1px solid var(--border); + background: var(--bg-secondary); + color: var(--text-primary); + resize: vertical; + font-family: inherit; + outline: none; + transition: border-color 0.15s ease; + box-sizing: border-box; +} + +.reject-textarea:focus { + border-color: var(--accent); +} + +/* ============ Failure Hint (in table) ============ */ + +.failure-hint { + font-size: 11px; + color: var(--error); + margin-top: 4px; + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* ============ Failure Cell (in repair table) ============ */ + +.failure-cell { + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 12px; + color: var(--error); +} + +/* ============ Failure Cell Wide (in repair history) ============ */ + +.failure-cell-wide { + max-width: 300px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* ============ Detail Error Title ============ */ + +.detail-title--error { + color: var(--error); +} + +/* ============ Error Alert Card ============ */ + +.alert-card-header { + margin-bottom: 8px; + display: flex; + align-items: center; + gap: 6px; +} + +.alert-card-header--error { + color: var(--error); +} + +.alert-card-header--warning { + color: var(--warning); +} + +/* ============ Review Section ============ */ + +.review-section-desc { + font-size: 14px; + color: var(--text-secondary); + margin-bottom: 12px; +} + +.review-actions { + display: flex; + gap: 12px; + flex-wrap: wrap; + align-items: center; +} + +/* ============ Report Header ============ */ + +.report-header { + margin-bottom: 20px; +} + +/* ============ Reduced Motion ============ */ + +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } + + .spinner { + animation: none; + border-top-color: var(--accent); + opacity: 0.7; + } +} diff --git a/web/src/pages/BugDetail.tsx b/web/src/pages/BugDetail.tsx index e97ddff..083eb7c 100644 --- a/web/src/pages/BugDetail.tsx +++ b/web/src/pages/BugDetail.tsx @@ -28,6 +28,12 @@ const REJECT_REASON_TEMPLATES = [ '需要补充异常处理', ]; +function severityLevel(s: number) { + if (s >= 8) return 'high'; + if (s >= 5) return 'mid'; + return 'low'; +} + export default function BugDetail() { const { id } = useParams<{ id: string }>(); const location = useLocation(); @@ -37,19 +43,16 @@ export default function BugDetail() { const [repairMessage, setRepairMessage] = useState(''); const [repairHistory, setRepairHistory] = useState([]); - // PR 操作相关状态 const [mergingPR, setMergingPR] = useState(false); const [closingPR, setClosingPR] = useState(false); const [prMessage, setPRMessage] = useState(''); const [showRejectModal, setShowRejectModal] = useState(false); const [rejectReason, setRejectReason] = useState(''); - // 人工审核操作状态(无 PR 时使用) const [approving, setApproving] = useState(false); const [rejecting, setRejecting] = useState(false); const [reviewMessage, setReviewMessage] = useState(''); - // 重试操作状态 const [retrying, setRetrying] = useState(false); const [retryMessage, setRetryMessage] = useState(''); @@ -101,10 +104,10 @@ export default function BugDetail() { try { await mergePR(bug.id); setBug({ ...bug, status: 'FIXED', merged_at: new Date().toISOString() }); - setPRMessage('✅ PR 已成功合并'); + setPRMessage('PR 已成功合并'); } catch (error: any) { console.error('Failed to merge PR:', error); - setPRMessage(`❌ 合并失败: ${error.response?.data?.detail || error.message}`); + setPRMessage(`合并失败: ${error.response?.data?.detail || error.message}`); } finally { setMergingPR(false); } @@ -112,7 +115,7 @@ export default function BugDetail() { const handleClosePR = async () => { if (!bug || !rejectReason.trim()) { - setPRMessage('❌ 请输入拒绝原因'); + setPRMessage('请输入拒绝原因'); return; } setClosingPR(true); @@ -125,12 +128,12 @@ export default function BugDetail() { rejection_count: (bug.rejection_count || 0) + 1, last_rejected_at: new Date().toISOString() }); - setPRMessage('✅ PR 已拒绝,Bug 将重新修复'); + setPRMessage('PR 已拒绝,Bug 将重新修复'); setShowRejectModal(false); setRejectReason(''); } catch (error: any) { console.error('Failed to close PR:', error); - setPRMessage(`❌ 关闭失败: ${error.response?.data?.detail || error.message}`); + setPRMessage(`关闭失败: ${error.response?.data?.detail || error.message}`); } finally { setClosingPR(false); } @@ -143,10 +146,10 @@ export default function BugDetail() { try { await retryFix(bug.id); setBug({ ...bug, status: 'NEW', failure_reason: null }); - setRetryMessage('✅ Bug 已重置,repair agent 将重新扫描'); + setRetryMessage('Bug 已重置,repair agent 将重新扫描'); } catch (error: any) { console.error('Failed to retry:', error); - setRetryMessage(`❌ 重试失败: ${error.response?.data?.detail || error.message}`); + setRetryMessage(`重试失败: ${error.response?.data?.detail || error.message}`); } finally { setRetrying(false); } @@ -212,10 +215,12 @@ export default function BugDetail() { const isPendingReview = bug.status === 'PENDING_FIX'; const canOperatePR = hasPR && isPendingReview; const canManualReview = !hasPR && isPendingReview; - // PR 信息只在 PENDING_FIX 或 FIXED 状态时显示 const shouldShowPR = hasPR && (isPendingReview || bug.status === 'FIXED'); const canRetry = bug.status === 'FIX_FAILED'; + const msgIsSuccess = (msg: string) => + msg.includes('成功') || msg.includes('确认') || msg.includes('驳回') || msg.includes('已重置') || msg.includes('已拒绝'); + return (
@@ -226,7 +231,7 @@ export default function BugDetail() {
-

+

{bug.error_type}: {bug.error_message}

@@ -236,19 +241,9 @@ export default function BugDetail() { 级别:{bug.level}
-
+
{bug.severity != null && bug.status !== 'NEW' && ( - = 8 ? 'var(--error)' : bug.severity >= 5 ? 'var(--warning)' : 'var(--success)', - }}> + 等级 {bug.severity}/10 )} @@ -260,35 +255,29 @@ export default function BugDetail() { {/* 严重等级说明 */} {bug.severity != null && bug.severity_reason && bug.status !== 'NEW' && ( -
= 8 ? 'rgba(239,68,68,0.08)' : 'var(--bg-secondary)', - padding: '12px', - borderRadius: '6px', - marginTop: '12px', - borderLeft: `3px solid ${bug.severity >= 8 ? 'var(--error)' : bug.severity >= 5 ? 'var(--warning)' : 'var(--success)'}`, - }}> -
+
= 8 ? 'critical' : severityLevel(bug.severity)}`}> +
严重等级:{bug.severity}/10 {bug.severity >= 8 ? '(需人工审核)' : ''}
-
+
{bug.severity_reason}
)} - {/* PR 信息显示 - 仅在 PENDING_FIX 或 FIXED 状态时显示 */} + {/* PR 信息显示 */} {shouldShowPR && ( -
-
Pull Request
-
- +
+
Pull Request
+
+ PR #{bug.pr_number} | {bug.branch_name || 'fix branch'} - + 查看 PR {((bug.rejection_count ?? 0) > 0) && ( - + 已拒绝 {bug.rejection_count ?? 0} 次 )} @@ -361,29 +350,22 @@ export default function BugDetail() { )} {/* 操作按钮区 */} -
+
{/* PR 操作按钮 */} {canOperatePR && ( <> - - )} - {/* 重新尝试按钮 - 仅在修复失败时显示 */} + {/* 重新尝试按钮 */} {canRetry && ( )} {/* 消息显示 */} {prMessage && ( - + {prMessage} )} - {repairMessage && ( - + {repairMessage} )} - {retryMessage && ( - + {retryMessage} )} - {reviewMessage && ( - + {reviewMessage} )} {!canTriggerRepair && !repairing && !hasPR && !canRetry && !canManualReview && ( - + {!isRuntime ? 'CI/CD 和部署错误暂不支持自动修复' : '仅"新发现"或"修复失败"状态的缺陷可触发修复'} @@ -501,8 +452,8 @@ export default function BugDetail() {
{bug.failure_reason && ( -
-
+
+
修复失败原因
@@ -511,8 +462,8 @@ export default function BugDetail() { )} {bug.rejection_reason && ( -
-
+
+
上次拒绝原因
@@ -521,7 +472,7 @@ export default function BugDetail() { )}
-
元数据
+
元数据
@@ -552,7 +503,7 @@ export default function BugDetail() { {repairHistory.length > 0 && (
-
+
修复历史 ({repairHistory.length} 次尝试)
@@ -582,7 +533,7 @@ export default function BugDetail() { {report.test_passed ? '通过' : '失败'} -
+ {report.failure_reason || '-'} @@ -603,48 +554,23 @@ export default function BugDetail() { {/* 拒绝原因模态框 */} {showRejectModal && ( -
-
-

{hasPR ? '拒绝修复' : '驳回修复'}

-

+

{ setShowRejectModal(false); setRejectReason(''); }}> +
e.stopPropagation()}> +

{hasPR ? '拒绝修复' : '驳回修复'}

+

请说明{hasPR ? '拒绝' : '驳回'}原因,Agent 将根据您的反馈重新修复:

-
-