style(design): FINDING-010 — Crawl.tsx 하드코딩 hex 를 @theme 토큰으로 단일화 [subagent]
Crawl 이 dead-600/ink-400/cost-ai 와 동일한 값을 raw hex 로 재복제해 토큰 변경 시 어긋나는 이중 정의 상태였다. 종료사유 팔레트를 상태 토큰(ok/run/warn/dead)+신규 neutral-300/400 으로 치환, ReferenceLine· 막대·폴백 hex 전부 var() 참조로. src 내 raw hex 0 확인. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
433b973505
commit
10ea8ddd45
@ -34,6 +34,10 @@
|
||||
--color-dead-600: #d03b3b;
|
||||
--color-dead-50: #fdeaea;
|
||||
|
||||
/* 중립 계열(차트 보조 — 유휴/종료 등 '무해한 상태') */
|
||||
--color-neutral-400: #898781;
|
||||
--color-neutral-300: #c3c2b7;
|
||||
|
||||
/* 차트 시리즈(엔터티 고정) */
|
||||
--color-naver: #008a43;
|
||||
--color-coupang: #f0455b;
|
||||
|
||||
@ -10,15 +10,16 @@ import type { BotStatsRes, IpSessionStatsRes } from "../api/types";
|
||||
import { Card, Empty, ErrorNote, Loading } from "../components/ui";
|
||||
import { dateShort, durationSec, hhmm } from "../lib/format";
|
||||
|
||||
// 종료 사유 — 의미 기반 상태색(정상 회전=초록 계열, 차단=적색, 포트사망=주황, 중립=회색/파랑).
|
||||
// 종료 사유 — 의미 기반 상태색. 색은 전부 index.css @theme 토큰 참조(단일 소스) —
|
||||
// raw hex 재정의 금지(토큰을 바꾸면 여기도 함께 바뀌어야 한다).
|
||||
const REASONS: Record<string, { label: string; color: string; desc: string }> = {
|
||||
budget: { label: "예산 선제 회전", color: "#0ca30c", desc: "정상 — 차단 전에 IP 교체(평판 보존)" },
|
||||
window: { label: "시간창 만료", color: "#2a78d6", desc: "정상 — sticky 10분 주기 교체" },
|
||||
idle: { label: "유휴 정리", color: "#898781", desc: "정상 — 한동안 검색 없어 브라우저 회수" },
|
||||
shutdown: { label: "종료", color: "#c3c2b7", desc: "정상 — 워커 재시작/종료" },
|
||||
rotate: { label: "기타 회전", color: "#9aa1ac", desc: "웜업 재시도 등" },
|
||||
proxy_error: { label: "포트 사망", color: "#ec835a", desc: "주의 — 프록시 전송 실패로 교체" },
|
||||
block: { label: "차단됨", color: "#d03b3b", desc: "위험 — 예산 안에서도 차단(예산 하향 검토)" },
|
||||
budget: { label: "예산 선제 회전", color: "var(--color-ok-600)", desc: "정상 — 차단 전에 IP 교체(평판 보존)" },
|
||||
window: { label: "시간창 만료", color: "var(--color-run-600)", desc: "정상 — sticky 10분 주기 교체" },
|
||||
idle: { label: "유휴 정리", color: "var(--color-neutral-400)", desc: "정상 — 한동안 검색 없어 브라우저 회수" },
|
||||
shutdown: { label: "종료", color: "var(--color-neutral-300)", desc: "정상 — 워커 재시작/종료" },
|
||||
rotate: { label: "기타 회전", color: "var(--color-ink-400)", desc: "웜업 재시도 등" },
|
||||
proxy_error: { label: "포트 사망", color: "var(--color-warn-600)", desc: "주의 — 프록시 전송 실패로 교체" },
|
||||
block: { label: "차단됨", color: "var(--color-dead-600)", desc: "위험 — 예산 안에서도 차단(예산 하향 검토)" },
|
||||
};
|
||||
|
||||
const RANGES = [
|
||||
@ -41,7 +42,7 @@ export default function Crawl() {
|
||||
});
|
||||
|
||||
const reasons = Object.entries(ip.data?.by_reason ?? {})
|
||||
.map(([k, v]) => ({ key: k, ...(REASONS[k] ?? { label: k, color: "#9aa1ac", desc: "" }), value: v }))
|
||||
.map(([k, v]) => ({ key: k, ...(REASONS[k] ?? { label: k, color: "var(--color-ink-400)", desc: "" }), value: v }))
|
||||
.sort((a, b) => b.value - a.value);
|
||||
const totalSessions = reasons.reduce((s, r) => s + r.value, 0);
|
||||
const blockCount = ip.data?.by_reason?.block ?? 0;
|
||||
@ -122,8 +123,8 @@ export default function Crawl() {
|
||||
<Tooltip formatter={(v: number) => [`${v}건`, "세션 수"]} labelFormatter={(l) => `IP당 ${l}회 요청`} />
|
||||
<Bar dataKey="count" fill="var(--color-cost-ai)" radius={[4, 4, 0, 0]} maxBarSize={36} isAnimationActive={false} />
|
||||
{ip.data.block_min_requests != null && (
|
||||
<ReferenceLine x={ip.data.block_min_requests} stroke="#d03b3b" strokeDasharray="4 3"
|
||||
label={{ value: `차단 시작 ${ip.data.block_min_requests}회`, position: "top", fill: "#d03b3b", fontSize: 11, fontWeight: 600 }} />
|
||||
<ReferenceLine x={ip.data.block_min_requests} stroke="var(--color-dead-600)" strokeDasharray="4 3"
|
||||
label={{ value: `차단 시작 ${ip.data.block_min_requests}회`, position: "top", fill: "var(--color-dead-600)", fontSize: 11, fontWeight: 600 }} />
|
||||
)}
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
@ -147,7 +148,7 @@ export default function Crawl() {
|
||||
<XAxis dataKey="x" tick={{ fill: "var(--color-ink-400)" }} tickLine={false} axisLine={{ stroke: "var(--color-line-200)" }} minTickGap={40} />
|
||||
<YAxis allowDecimals={false} tick={{ fill: "var(--color-ink-400)" }} tickLine={false} axisLine={false} />
|
||||
<Tooltip formatter={(v: number) => [`${v}건`, "차단"]} />
|
||||
<Bar dataKey="count" fill="#d03b3b" radius={[4, 4, 0, 0]} maxBarSize={24} isAnimationActive={false} />
|
||||
<Bar dataKey="count" fill="var(--color-dead-600)" radius={[4, 4, 0, 0]} maxBarSize={24} isAnimationActive={false} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
)}
|
||||
@ -202,7 +203,7 @@ export default function Crawl() {
|
||||
</thead>
|
||||
<tbody>
|
||||
{ip.data.sessions.map((s, i) => {
|
||||
const r = REASONS[s.end_reason] ?? { label: s.end_reason, color: "#9aa1ac" };
|
||||
const r = REASONS[s.end_reason] ?? { label: s.end_reason, color: "var(--color-ink-400)" };
|
||||
return (
|
||||
<tr key={i} className="border-b border-line-100">
|
||||
<td className="tnum py-1.5 pr-2 text-ink-500">{dateShort(s.created_at)}</td>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user