"use client"; import { useCallback, useEffect, useState } from "react"; import { api } from "@/lib/api"; import type { DaemonConfig } from "@/lib/api"; // #329 — admin/page.tsx에서 추출 export function DaemonPanel({ isAdmin }: { isAdmin: boolean }) { const [config, setConfig] = useState(null); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [running, setRunning] = useState(null); const [result, setResult] = useState(null); // Editable fields const [scanEnabled, setScanEnabled] = useState(false); const [scanInterval, setScanInterval] = useState(60); const [processEnabled, setProcessEnabled] = useState(false); const [processInterval, setProcessInterval] = useState(60); const [processLimit, setProcessLimit] = useState(10); const load = useCallback(() => { setLoading(true); api.getDaemonConfig().then((cfg) => { setConfig(cfg); setScanEnabled(cfg.scan_enabled); setScanInterval(cfg.scan_interval_min); setProcessEnabled(cfg.process_enabled); setProcessInterval(cfg.process_interval_min); setProcessLimit(cfg.process_limit); }).catch(console.error).finally(() => setLoading(false)); }, []); useEffect(() => { load(); }, [load]); const handleSave = async () => { setSaving(true); setResult(null); try { await api.updateDaemonConfig({ scan_enabled: scanEnabled, scan_interval_min: scanInterval, process_enabled: processEnabled, process_interval_min: processInterval, process_limit: processLimit, }); setResult("설정 저장 완료"); load(); } catch (e: unknown) { setResult(e instanceof Error ? e.message : "저장 실패"); } finally { setSaving(false); } }; const handleRunScan = async () => { setRunning("scan"); setResult(null); try { const res = await api.runDaemonScan(); setResult(`채널 스캔 완료: 신규 ${res.new_videos}개 영상`); load(); } catch (e: unknown) { setResult(e instanceof Error ? e.message : "스캔 실패"); } finally { setRunning(null); } }; const handleRunProcess = async () => { setRunning("process"); setResult(null); try { const res = await api.runDaemonProcess(processLimit); setResult(`영상 처리 완료: ${res.restaurants_extracted}개 식당 추출`); load(); } catch (e: unknown) { setResult(e instanceof Error ? e.message : "처리 실패"); } finally { setRunning(null); } }; if (loading) return

로딩 중...

; return (
{/* Schedule Config */}

스케줄 설정

데몬이 실행 중일 때, 아래 설정에 따라 자동으로 채널 스캔 및 영상 처리를 수행합니다.

{/* Scan config */}

채널 스캔

setScanInterval(Number(e.target.value))} disabled={!isAdmin} min={1} className="border rounded px-3 py-1.5 text-sm w-32" />
{config?.last_scan_at && (

마지막 스캔: {config.last_scan_at}

)}
{/* Process config */}

영상 처리

setProcessInterval(Number(e.target.value))} disabled={!isAdmin} min={1} className="border rounded px-3 py-1.5 text-sm w-32" />
setProcessLimit(Number(e.target.value))} disabled={!isAdmin} min={1} max={50} className="border rounded px-3 py-1.5 text-sm w-32" />
{config?.last_process_at && (

마지막 처리: {config.last_process_at}

)}
{isAdmin && (
)}
{/* Manual Triggers */}

수동 실행

스케줄과 관계없이 즉시 실행합니다. 처리 시간이 걸릴 수 있습니다.

{isAdmin && ( <> )}
{result && (

{result}

)}
{/* Config updated_at */} {config?.updated_at && (

설정 수정일: {config.updated_at}

)}
); }