release: 2026-04-01 (14건 커밋) #133

병합
HYOJIN develop 에서 main 로 14 commits 를 머지했습니다 2026-04-01 16:57:58 +09:00
5개의 변경된 파일63개의 추가작업 그리고 57개의 파일을 삭제
Showing only changes of commit bfd86c9eff - Show all commits

파일 보기

@ -10,6 +10,12 @@
- 섹션 간 직접 이동
- 메인화면 카드 높이 동기화 (CSS Grid)
### 수정
- S&P Collector 다크모드 미적용 및 라벨 디자인 통일 (#122)
- 실행이력상세/재수집이력상세 API 호출 로그 다크모드 적용
- 개별 호출 로그 필터/테이블 다크모드 적용
- 작업관리 스케줄 라벨 rounded-full 디자인 통일
## [2026-03-31]
### 추가

파일 보기

@ -71,11 +71,11 @@ export default function ApiLogSection({ stepExecutionId, summary }: ApiLogSectio
className={`px-2.5 py-1 text-xs rounded-full font-medium transition-colors ${
status === key
? key === 'ERROR'
? 'bg-red-100 text-red-700'
? 'bg-red-500/15 text-red-500'
: key === 'SUCCESS'
? 'bg-emerald-100 text-emerald-700'
: 'bg-blue-100 text-blue-700'
: 'bg-gray-100 text-gray-500 hover:bg-gray-200'
? 'bg-emerald-500/15 text-emerald-500'
: 'bg-blue-500/15 text-blue-500'
: 'bg-wing-card text-wing-muted hover:bg-wing-hover'
}`}
>
{label} ({count.toLocaleString()})
@ -92,7 +92,7 @@ export default function ApiLogSection({ stepExecutionId, summary }: ApiLogSectio
<>
<div className="overflow-x-auto">
<table className="w-full text-xs text-left">
<thead className="bg-blue-100 text-blue-700 sticky top-0">
<thead className="bg-blue-500/15 text-blue-500 sticky top-0">
<tr>
<th className="px-2 py-1.5 font-medium">#</th>
<th className="px-2 py-1.5 font-medium">URI</th>
@ -104,24 +104,24 @@ export default function ApiLogSection({ stepExecutionId, summary }: ApiLogSectio
<th className="px-2 py-1.5 font-medium"></th>
</tr>
</thead>
<tbody className="divide-y divide-blue-100">
<tbody className="divide-y divide-blue-500/15">
{logData.content.map((log, idx) => {
const isError = (log.statusCode != null && log.statusCode >= 400) || log.errorMessage;
return (
<tr
key={log.logId}
className={isError ? 'bg-red-50' : 'bg-white hover:bg-blue-50'}
className={isError ? 'bg-red-500/10' : 'bg-wing-surface hover:bg-blue-500/10'}
>
<td className="px-2 py-1.5 text-blue-500">{page * 10 + idx + 1}</td>
<td className="px-2 py-1.5 max-w-[200px]">
<div className="flex items-center gap-0.5">
<span className="font-mono text-blue-900 truncate" title={log.requestUri}>
<span className="font-mono text-wing-text truncate" title={log.requestUri}>
{log.requestUri}
</span>
<CopyButton text={log.requestUri} />
</div>
</td>
<td className="px-2 py-1.5 font-semibold text-blue-900">{log.httpMethod}</td>
<td className="px-2 py-1.5 font-semibold text-wing-text">{log.httpMethod}</td>
<td className="px-2 py-1.5">
<span className={`font-semibold ${
log.statusCode == null ? 'text-gray-400'
@ -132,10 +132,10 @@ export default function ApiLogSection({ stepExecutionId, summary }: ApiLogSectio
{log.statusCode ?? '-'}
</span>
</td>
<td className="px-2 py-1.5 text-right text-blue-900">
<td className="px-2 py-1.5 text-right text-wing-text">
{log.responseTimeMs?.toLocaleString() ?? '-'}
</td>
<td className="px-2 py-1.5 text-right text-blue-900">
<td className="px-2 py-1.5 text-right text-wing-text">
{log.responseCount?.toLocaleString() ?? '-'}
</td>
<td className="px-2 py-1.5 text-blue-600 whitespace-nowrap">

파일 보기

@ -96,32 +96,32 @@ function StepCard({ step, jobName, jobExecutionId }: StepCardProps) {
{/* API 호출 정보: apiLogSummary가 있으면 개별 로그 리스트, 없으면 기존 apiCallInfo 요약 */}
{step.apiLogSummary ? (
<div className="mt-4 rounded-lg bg-blue-50 border border-blue-200 p-3">
<div className="mt-4 rounded-lg bg-blue-500/10 border border-blue-500/20 p-3">
<p className="text-xs font-medium text-blue-700 mb-2">API </p>
<div className="grid grid-cols-3 sm:grid-cols-6 gap-2">
<div className="rounded bg-white px-2 py-1.5 text-center">
<div className="rounded bg-wing-surface px-2 py-1.5 text-center">
<p className="text-sm font-bold text-wing-text">{step.apiLogSummary.totalCalls.toLocaleString()}</p>
<p className="text-[10px] text-wing-muted"> </p>
</div>
<div className="rounded bg-white px-2 py-1.5 text-center">
<div className="rounded bg-wing-surface px-2 py-1.5 text-center">
<p className="text-sm font-bold text-emerald-600">{step.apiLogSummary.successCount.toLocaleString()}</p>
<p className="text-[10px] text-wing-muted"></p>
</div>
<div className="rounded bg-white px-2 py-1.5 text-center">
<div className="rounded bg-wing-surface px-2 py-1.5 text-center">
<p className={`text-sm font-bold ${step.apiLogSummary.errorCount > 0 ? 'text-red-500' : 'text-wing-text'}`}>
{step.apiLogSummary.errorCount.toLocaleString()}
</p>
<p className="text-[10px] text-wing-muted"></p>
</div>
<div className="rounded bg-white px-2 py-1.5 text-center">
<div className="rounded bg-wing-surface px-2 py-1.5 text-center">
<p className="text-sm font-bold text-blue-600">{Math.round(step.apiLogSummary.avgResponseMs).toLocaleString()}</p>
<p className="text-[10px] text-wing-muted">(ms)</p>
</div>
<div className="rounded bg-white px-2 py-1.5 text-center">
<div className="rounded bg-wing-surface px-2 py-1.5 text-center">
<p className="text-sm font-bold text-red-500">{step.apiLogSummary.maxResponseMs.toLocaleString()}</p>
<p className="text-[10px] text-wing-muted">(ms)</p>
</div>
<div className="rounded bg-white px-2 py-1.5 text-center">
<div className="rounded bg-wing-surface px-2 py-1.5 text-center">
<p className="text-sm font-bold text-emerald-500">{step.apiLogSummary.minResponseMs.toLocaleString()}</p>
<p className="text-[10px] text-wing-muted">(ms)</p>
</div>
@ -132,7 +132,7 @@ function StepCard({ step, jobName, jobExecutionId }: StepCardProps) {
)}
</div>
) : step.apiCallInfo && (
<div className="mt-4 rounded-lg bg-blue-50 border border-blue-200 p-3">
<div className="mt-4 rounded-lg bg-blue-500/10 border border-blue-500/20 p-3">
<p className="text-xs font-medium text-blue-700 mb-2">API </p>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2 text-xs">
<div>
@ -163,7 +163,7 @@ function StepCard({ step, jobName, jobExecutionId }: StepCardProps) {
)}
{step.exitMessage && (
<div className="mt-4 rounded-lg bg-red-50 border border-red-200 p-3">
<div className="mt-4 rounded-lg bg-red-500/10 border border-red-500/20 p-3">
<p className="text-xs font-medium text-red-700 mb-1">Exit Message</p>
<p className="text-xs text-red-600 whitespace-pre-wrap break-words">
{step.exitMessage}
@ -468,7 +468,7 @@ function FailedRecordsToggle({ records, jobName, jobExecutionId }: { records: Fa
};
return (
<div className="mt-4 rounded-lg bg-red-50 border border-red-200 p-3">
<div className="mt-4 rounded-lg bg-red-500/10 border border-red-500/20 p-3">
<div className="flex items-center justify-between">
<button
onClick={() => setOpen((v) => !v)}
@ -522,7 +522,7 @@ function FailedRecordsToggle({ records, jobName, jobExecutionId }: { records: Fa
<div className="mt-2">
<div className="overflow-x-auto">
<table className="w-full text-xs text-left">
<thead className="bg-red-100 text-red-700">
<thead className="bg-red-500/20 text-red-700">
<tr>
<th className="px-2 py-1.5 font-medium">Record Key</th>
<th className="px-2 py-1.5 font-medium"> </th>
@ -531,11 +531,11 @@ function FailedRecordsToggle({ records, jobName, jobExecutionId }: { records: Fa
<th className="px-2 py-1.5 font-medium"> </th>
</tr>
</thead>
<tbody className="divide-y divide-red-100">
<tbody className="divide-y divide-red-500/20">
{pagedRecords.map((record) => (
<tr
key={record.id}
className="bg-white hover:bg-red-50"
className="bg-wing-surface hover:bg-red-500/10"
>
<td className="px-2 py-1.5 font-mono text-red-900">
{record.recordKey}
@ -581,19 +581,19 @@ function FailedRecordsToggle({ records, jobName, jobExecutionId }: { records: Fa
{/* 재수집 확인 다이얼로그 */}
{showConfirm && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
<div className="bg-white rounded-xl shadow-2xl p-6 w-full max-w-md mx-4">
<div className="bg-wing-surface rounded-xl shadow-2xl p-6 w-full max-w-md mx-4">
<h3 className="text-lg font-semibold text-wing-text mb-2">
</h3>
<p className="text-sm text-wing-muted mb-3">
{failedRecords.length} IMO에 .
</p>
<div className="bg-gray-50 rounded-lg p-3 mb-4 max-h-40 overflow-y-auto">
<div className="bg-wing-card rounded-lg p-3 mb-4 max-h-40 overflow-y-auto">
<div className="flex flex-wrap gap-1">
{failedRecords.map((r) => (
<span
key={r.id}
className="inline-flex px-2 py-0.5 text-xs font-mono bg-red-100 text-red-700 rounded"
className="inline-flex px-2 py-0.5 text-xs font-mono bg-red-500/20 text-red-700 rounded"
>
{r.recordKey}
</span>
@ -604,7 +604,7 @@ function FailedRecordsToggle({ records, jobName, jobExecutionId }: { records: Fa
<button
onClick={() => setShowConfirm(false)}
disabled={retrying}
className="px-4 py-2 text-sm font-medium text-wing-muted bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors disabled:opacity-50"
className="px-4 py-2 text-sm font-medium text-wing-muted bg-wing-card hover:bg-wing-hover rounded-lg transition-colors disabled:opacity-50"
>
</button>
@ -630,7 +630,7 @@ function FailedRecordsToggle({ records, jobName, jobExecutionId }: { records: Fa
{/* 일괄 RESOLVED 확인 다이얼로그 */}
{showResolveConfirm && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
<div className="bg-white rounded-xl shadow-2xl p-6 w-full max-w-md mx-4">
<div className="bg-wing-surface rounded-xl shadow-2xl p-6 w-full max-w-md mx-4">
<h3 className="text-lg font-semibold text-wing-text mb-2">
RESOLVED
</h3>
@ -642,7 +642,7 @@ function FailedRecordsToggle({ records, jobName, jobExecutionId }: { records: Fa
<button
onClick={() => setShowResolveConfirm(false)}
disabled={resolving}
className="px-4 py-2 text-sm font-medium text-wing-muted bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors disabled:opacity-50"
className="px-4 py-2 text-sm font-medium text-wing-muted bg-wing-card hover:bg-wing-hover rounded-lg transition-colors disabled:opacity-50"
>
</button>
@ -668,7 +668,7 @@ function FailedRecordsToggle({ records, jobName, jobExecutionId }: { records: Fa
{/* 재시도 초기화 확인 다이얼로그 */}
{showResetConfirm && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
<div className="bg-white rounded-xl shadow-2xl p-6 w-full max-w-md mx-4">
<div className="bg-wing-surface rounded-xl shadow-2xl p-6 w-full max-w-md mx-4">
<h3 className="text-lg font-semibold text-wing-text mb-2">
</h3>
@ -680,7 +680,7 @@ function FailedRecordsToggle({ records, jobName, jobExecutionId }: { records: Fa
<button
onClick={() => setShowResetConfirm(false)}
disabled={resetting}
className="px-4 py-2 text-sm font-medium text-wing-muted bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors disabled:opacity-50"
className="px-4 py-2 text-sm font-medium text-wing-muted bg-wing-card hover:bg-wing-hover rounded-lg transition-colors disabled:opacity-50"
>
</button>

파일 보기

@ -376,11 +376,11 @@ export default function Jobs() {
<div className="flex items-center gap-2 pt-0.5">
{job.scheduleCron ? (
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-emerald-100 text-emerald-700">
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-semibold bg-emerald-500/15 text-emerald-500">
</span>
) : (
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-wing-card text-wing-muted">
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-wing-card text-wing-muted">
</span>
)}
@ -478,11 +478,11 @@ export default function Jobs() {
</td>
<td className="px-4 py-3">
{job.scheduleCron ? (
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-emerald-100 text-emerald-700">
<span className="inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-semibold bg-emerald-100 text-emerald-700">
</span>
) : (
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-wing-card text-wing-muted">
<span className="inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-semibold bg-wing-card text-wing-muted">
</span>
)}

파일 보기

@ -75,32 +75,32 @@ function StepCard({ step, jobName, jobExecutionId }: { step: StepExecutionDto; j
{/* API 호출 로그 요약 (batch_api_log 기반) */}
{summary && (
<div className="mt-4 rounded-lg bg-blue-50 border border-blue-200 p-3">
<div className="mt-4 rounded-lg bg-blue-500/10 border border-blue-500/20 p-3">
<p className="text-xs font-medium text-blue-700 mb-2">API </p>
<div className="grid grid-cols-3 sm:grid-cols-6 gap-2">
<div className="rounded bg-white px-2 py-1.5 text-center">
<div className="rounded bg-wing-surface px-2 py-1.5 text-center">
<p className="text-sm font-bold text-wing-text">{summary.totalCalls.toLocaleString()}</p>
<p className="text-[10px] text-wing-muted"> </p>
</div>
<div className="rounded bg-white px-2 py-1.5 text-center">
<div className="rounded bg-wing-surface px-2 py-1.5 text-center">
<p className="text-sm font-bold text-emerald-600">{summary.successCount.toLocaleString()}</p>
<p className="text-[10px] text-wing-muted"></p>
</div>
<div className="rounded bg-white px-2 py-1.5 text-center">
<div className="rounded bg-wing-surface px-2 py-1.5 text-center">
<p className={`text-sm font-bold ${summary.errorCount > 0 ? 'text-red-500' : 'text-wing-text'}`}>
{summary.errorCount.toLocaleString()}
</p>
<p className="text-[10px] text-wing-muted"></p>
</div>
<div className="rounded bg-white px-2 py-1.5 text-center">
<div className="rounded bg-wing-surface px-2 py-1.5 text-center">
<p className="text-sm font-bold text-blue-600">{Math.round(summary.avgResponseMs).toLocaleString()}</p>
<p className="text-[10px] text-wing-muted">(ms)</p>
</div>
<div className="rounded bg-white px-2 py-1.5 text-center">
<div className="rounded bg-wing-surface px-2 py-1.5 text-center">
<p className="text-sm font-bold text-red-500">{summary.maxResponseMs.toLocaleString()}</p>
<p className="text-[10px] text-wing-muted">(ms)</p>
</div>
<div className="rounded bg-white px-2 py-1.5 text-center">
<div className="rounded bg-wing-surface px-2 py-1.5 text-center">
<p className="text-sm font-bold text-emerald-500">{summary.minResponseMs.toLocaleString()}</p>
<p className="text-[10px] text-wing-muted">(ms)</p>
</div>
@ -118,7 +118,7 @@ function StepCard({ step, jobName, jobExecutionId }: { step: StepExecutionDto; j
)}
{step.exitMessage && (
<div className="mt-4 rounded-lg bg-red-50 border border-red-200 p-3">
<div className="mt-4 rounded-lg bg-red-500/10 border border-red-500/20 p-3">
<p className="text-xs font-medium text-red-700 mb-1">Exit Message</p>
<p className="text-xs text-red-600 whitespace-pre-wrap break-words">
{step.exitMessage}
@ -361,7 +361,7 @@ export default function RecollectDetail() {
<h2 className="text-lg font-semibold text-red-600 mb-3">
</h2>
<pre className="text-sm text-wing-text font-mono bg-red-50 border border-red-200 px-4 py-3 rounded-lg whitespace-pre-wrap break-words max-h-64 overflow-y-auto">
<pre className="text-sm text-wing-text font-mono bg-red-500/10 border border-red-500/20 px-4 py-3 rounded-lg whitespace-pre-wrap break-words max-h-64 overflow-y-auto">
{history.failureReason}
</pre>
</div>
@ -536,7 +536,7 @@ function FailedRecordsToggle({ records, jobName, jobExecutionId }: { records: Fa
};
return (
<div className="mt-4 rounded-lg bg-red-50 border border-red-200 p-3">
<div className="mt-4 rounded-lg bg-red-500/10 border border-red-500/20 p-3">
<div className="flex items-center justify-between">
<button
onClick={() => setOpen((v) => !v)}
@ -590,7 +590,7 @@ function FailedRecordsToggle({ records, jobName, jobExecutionId }: { records: Fa
<div className="mt-2">
<div className="overflow-x-auto">
<table className="w-full text-xs text-left">
<thead className="bg-red-100 text-red-700">
<thead className="bg-red-500/20 text-red-700">
<tr>
<th className="px-2 py-1.5 font-medium">Record Key</th>
<th className="px-2 py-1.5 font-medium"> </th>
@ -599,11 +599,11 @@ function FailedRecordsToggle({ records, jobName, jobExecutionId }: { records: Fa
<th className="px-2 py-1.5 font-medium"> </th>
</tr>
</thead>
<tbody className="divide-y divide-red-100">
<tbody className="divide-y divide-red-500/20">
{pagedRecords.map((record) => (
<tr
key={record.id}
className="bg-white hover:bg-red-50"
className="bg-wing-surface hover:bg-red-500/10"
>
<td className="px-2 py-1.5 font-mono text-red-900">
{record.recordKey}
@ -649,19 +649,19 @@ function FailedRecordsToggle({ records, jobName, jobExecutionId }: { records: Fa
{/* 재수집 확인 다이얼로그 */}
{showConfirm && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
<div className="bg-white rounded-xl shadow-2xl p-6 w-full max-w-md mx-4">
<div className="bg-wing-surface rounded-xl shadow-2xl p-6 w-full max-w-md mx-4">
<h3 className="text-lg font-semibold text-wing-text mb-2">
</h3>
<p className="text-sm text-wing-muted mb-3">
{failedRecords.length} IMO에 .
</p>
<div className="bg-gray-50 rounded-lg p-3 mb-4 max-h-40 overflow-y-auto">
<div className="bg-wing-card rounded-lg p-3 mb-4 max-h-40 overflow-y-auto">
<div className="flex flex-wrap gap-1">
{failedRecords.map((r) => (
<span
key={r.id}
className="inline-flex px-2 py-0.5 text-xs font-mono bg-red-100 text-red-700 rounded"
className="inline-flex px-2 py-0.5 text-xs font-mono bg-red-500/20 text-red-700 rounded"
>
{r.recordKey}
</span>
@ -672,7 +672,7 @@ function FailedRecordsToggle({ records, jobName, jobExecutionId }: { records: Fa
<button
onClick={() => setShowConfirm(false)}
disabled={retrying}
className="px-4 py-2 text-sm font-medium text-wing-muted bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors disabled:opacity-50"
className="px-4 py-2 text-sm font-medium text-wing-muted bg-wing-card hover:bg-wing-hover rounded-lg transition-colors disabled:opacity-50"
>
</button>
@ -698,7 +698,7 @@ function FailedRecordsToggle({ records, jobName, jobExecutionId }: { records: Fa
{/* 일괄 RESOLVED 확인 다이얼로그 */}
{showResolveConfirm && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
<div className="bg-white rounded-xl shadow-2xl p-6 w-full max-w-md mx-4">
<div className="bg-wing-surface rounded-xl shadow-2xl p-6 w-full max-w-md mx-4">
<h3 className="text-lg font-semibold text-wing-text mb-2">
RESOLVED
</h3>
@ -710,7 +710,7 @@ function FailedRecordsToggle({ records, jobName, jobExecutionId }: { records: Fa
<button
onClick={() => setShowResolveConfirm(false)}
disabled={resolving}
className="px-4 py-2 text-sm font-medium text-wing-muted bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors disabled:opacity-50"
className="px-4 py-2 text-sm font-medium text-wing-muted bg-wing-card hover:bg-wing-hover rounded-lg transition-colors disabled:opacity-50"
>
</button>
@ -736,7 +736,7 @@ function FailedRecordsToggle({ records, jobName, jobExecutionId }: { records: Fa
{/* 재시도 초기화 확인 다이얼로그 */}
{showResetConfirm && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
<div className="bg-white rounded-xl shadow-2xl p-6 w-full max-w-md mx-4">
<div className="bg-wing-surface rounded-xl shadow-2xl p-6 w-full max-w-md mx-4">
<h3 className="text-lg font-semibold text-wing-text mb-2">
</h3>
@ -748,7 +748,7 @@ function FailedRecordsToggle({ records, jobName, jobExecutionId }: { records: Fa
<button
onClick={() => setShowResetConfirm(false)}
disabled={resetting}
className="px-4 py-2 text-sm font-medium text-wing-muted bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors disabled:opacity-50"
className="px-4 py-2 text-sm font-medium text-wing-muted bg-wing-card hover:bg-wing-hover rounded-lg transition-colors disabled:opacity-50"
>
</button>