468804494d
plan ds-processing-ui-6an (시안 choice 채택: 안2 1차 + 안5/6 지원): - GET /api/queue/overview — 머신(GPU/맥미니/맥북) 귀속 라이브 집계 5쿼리, 마이그레이션 0. summarize 풀 완료 실적은 documents.ai_model_version 조인으로 맥북/맥미니 분리, 보류(deferred_until)=맥북 카드 귀속, state=active/deferred/idle. raw 모델명 비노출 - 홈: 처리 머신 보드(3열 카드 + 지금 처리 중 제목) + ETA 라인(유입 우세 시 null 명시), 기존 stage 테이블은 details 접힘으로 강등 (구조 개편) - 전 페이지: 상태 스트립(처리중·대기·실패·맥북 칩) + 우측 드로어(QueueDrawer, dialog a11y) — 공유 60s 폴링 store, 경량 fetch(401 강제 logout 부수효과 회피) - tests: 판정부 30건 (귀속/풀 분리/state 9케이스/ETA 경계/trend 버킷/계약 shape) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
107 lines
4.5 KiB
Svelte
107 lines
4.5 KiB
Svelte
<script lang="ts">
|
|
// 처리 현황 드로어 (안6 라이트) — 전 페이지 상태 스트립 클릭 시 우측에서 열림.
|
|
// 머신 미니카드 3 + ETA 한 줄 + 실패 합계 + 홈 링크 축약본. 상세는 홈 보드가 담당.
|
|
// 데이터 = queueOverview store 공유 (60s 폴링, 실패 시 null → 안내문으로 degrade).
|
|
// 열림 상태는 uiState 단일 drawer slot('queue') — 사이드바 드로어와 동시 오픈 차단.
|
|
import { X } from 'lucide-svelte';
|
|
import { ui } from '$lib/stores/uiState.svelte';
|
|
import { queueOverview } from '$lib/stores/queueOverview';
|
|
import {
|
|
MACHINE_STATE_LABEL, machineChipClass, machineDotClass, formatRate, etaPhrase,
|
|
} from '$lib/utils/queueDisplay';
|
|
import IconButton from '$lib/components/ui/IconButton.svelte';
|
|
|
|
let open = $derived(ui.isDrawerOpen('queue'));
|
|
let data = $derived($queueOverview);
|
|
|
|
function close() {
|
|
ui.closeDrawer();
|
|
}
|
|
|
|
// ESC 닫기 — 레이아웃 전역 핸들러(ui.handleEscape)와 중복돼도 무해(멱등).
|
|
// modal stack 이 열려 있으면 modal 우선 (전역 우선순위와 동일).
|
|
function onWindowKeydown(e: KeyboardEvent) {
|
|
if (e.key === 'Escape' && open && ui.modalStack.length === 0) close();
|
|
}
|
|
</script>
|
|
|
|
<svelte:window onkeydown={onWindowKeydown} />
|
|
|
|
{#if open}
|
|
<div class="fixed inset-0 z-drawer">
|
|
<!-- 스크림 — 클릭 시 닫기 -->
|
|
<button
|
|
type="button"
|
|
onclick={close}
|
|
class="absolute inset-0 bg-scrim transition-opacity"
|
|
aria-label="드로어 닫기"
|
|
></button>
|
|
|
|
<!-- 패널 — div + role="dialog" (aside 는 interactive role 불가, a11y 경고) -->
|
|
<div
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-label="처리 현황"
|
|
class="absolute right-0 top-0 bottom-0 w-rail max-w-full bg-sidebar shadow-xl overflow-y-auto"
|
|
>
|
|
<div class="flex items-center justify-between px-4 h-12 border-b border-default">
|
|
<span class="text-sm font-bold text-text">처리 현황</span>
|
|
<IconButton icon={X} size="sm" aria-label="닫기" onclick={close} />
|
|
</div>
|
|
|
|
<div class="p-4 space-y-3">
|
|
{#if data}
|
|
<!-- 머신 미니카드 3 -->
|
|
{#each data.machines as m (m.key)}
|
|
<div class="bg-surface border border-default rounded-lg px-3.5 py-2.5">
|
|
<div class="flex items-center justify-between gap-2">
|
|
<span class="flex items-center gap-2 text-[13px] font-semibold text-text min-w-0">
|
|
<span class="w-2 h-2 rounded-full shrink-0 {machineDotClass(m.state)}"></span>
|
|
<span class="truncate">{m.label}</span>
|
|
</span>
|
|
<span class="text-[10px] font-bold rounded-full px-2 py-0.5 shrink-0 {machineChipClass(m.state)}">
|
|
{MACHINE_STATE_LABEL[m.state]}
|
|
</span>
|
|
</div>
|
|
<div class="text-[11px] text-dim mt-1 tabular-nums">
|
|
대기 <strong class="text-text">{m.pending.toLocaleString()}</strong>
|
|
· 오늘 <strong class="text-text">{m.done_today.toLocaleString()}</strong>건 처리
|
|
</div>
|
|
</div>
|
|
{/each}
|
|
|
|
<!-- ETA 한 줄 (안5 라이트 — 추정치) -->
|
|
<div
|
|
class="text-[11px] text-dim leading-relaxed tabular-nums"
|
|
title="현재 페이스 기반 추정치 — 유입 변동 시 달라질 수 있습니다"
|
|
>
|
|
요약 대기 <strong class="text-text">{data.summarize_eta.pending.toLocaleString()}건</strong>
|
|
— 소화 {formatRate(data.summarize_eta.done_rate_1h)}/h
|
|
· 유입 {formatRate(data.summarize_eta.inflow_rate_1h)}/h
|
|
{#if data.summarize_eta.eta_minutes != null}
|
|
· <span class="text-accent font-semibold">{etaPhrase(data.summarize_eta.eta_minutes)}</span>
|
|
{:else}
|
|
· 유입 우세(백필 중)
|
|
{/if}
|
|
</div>
|
|
|
|
<!-- 실패 합계 -->
|
|
{#if data.totals.failed > 0}
|
|
<div class="text-[11px] font-semibold text-error bg-error/10 rounded-md px-2.5 py-1.5 tabular-nums">
|
|
실패 {data.totals.failed.toLocaleString()}건 — 확인 필요
|
|
</div>
|
|
{/if}
|
|
{:else}
|
|
<p class="text-xs text-dim">처리 현황을 불러오지 못했습니다.</p>
|
|
{/if}
|
|
|
|
<a
|
|
href="/"
|
|
onclick={close}
|
|
class="block text-xs text-accent font-semibold hover:underline pt-1"
|
|
>홈에서 자세히 →</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|