'use client'; import React, { useEffect, useState } from 'react'; import { useParams } from 'next/navigation'; import styled from 'styled-components'; import Link from 'next/link'; import { PageTitle, LabelMeta, SectionTitle, TechBar, TechBarFill, Timeline, TimelineItem, TimeStamp, TimelineContent } from '@/components/ui/base'; import { API_URL } from '@/lib/config'; import { SISTER_ROLES } from '@/lib/sisters'; const Breadcrumb = styled.div` font-family: var(--font-mono); font-size: 11px; color: var(--text-secondary); margin-bottom: var(--space-md); a { color: var(--text-secondary); text-decoration: none; &:hover { color: var(--text-primary); } } `; const DetailGrid = styled.div` display: grid; grid-template-columns: 280px 1fr; gap: var(--space-xxl); align-items: start; @media (max-width: 1199px) { grid-template-columns: 1fr; gap: var(--space-xl); } `; const MetaPanel = styled.div` display: flex; flex-direction: column; gap: var(--space-lg); `; const MetaCard = styled.div` border: 1px solid var(--border-color); padding: var(--space-lg); `; const StatusRow = styled.div` display: flex; align-items: center; gap: var(--space-sm); `; const StatusDot = styled.div<{ $on: boolean }>` width: 8px; height: 8px; border-radius: 50%; background: ${({ $on }) => $on ? '#FFF' : '#555'}; ${({ $on }) => $on && `box-shadow: 0 0 8px rgba(255,255,255,0.3);`} `; const ConfigPre = styled.pre` background: #0d0d0d; border: 1px solid var(--border-color); padding: var(--space-md); font-family: var(--font-mono); font-size: 11px; color: #8888aa; overflow: auto; max-height: 400px; white-space: pre-wrap; word-break: break-word; line-height: 1.6; `; const TabBar = styled.div` display: flex; gap: var(--space-lg); border-bottom: 1px solid var(--border-color); margin-bottom: var(--space-lg); `; const TabBtn = styled.button<{ $active: boolean }>` font-size: 13px; font-weight: 500; color: ${({ $active }) => $active ? 'var(--text-primary)' : 'var(--text-secondary)'}; background: transparent; border: none; border-bottom: 1px solid ${({ $active }) => $active ? 'var(--text-primary)' : 'transparent'}; padding: var(--space-sm) 0; cursor: pointer; transition: color 0.15s, border-color 0.15s; &:hover { color: var(--text-primary); } `; const SISTER_DISPLAY: Record = { harang: '하랑이', narang: '나랑이', darang: '다랑이', erang: '이랑이', }; export default function SisterDetailPage() { const { name } = useParams<{ name: string }>(); const [sisterInfo, setSisterInfo] = useState(null); const [configData, setConfigData] = useState(null); const [activity, setActivity] = useState([]); const [tab, setTab] = useState('overview'); const [loading, setLoading] = useState(true); useEffect(() => { Promise.allSettled([ fetch(`${API_URL}/api/sisters`), fetch(`${API_URL}/api/sisters/${name}/config`), fetch(`${API_URL}/api/sisters/${name}/activity`), ]).then(([sRes, cRes, aRes]) => { if (sRes.status === 'fulfilled' && sRes.value.ok) { sRes.value.json().then((d: any[]) => setSisterInfo(d.find((s) => s.name === name))); } if (cRes.status === 'fulfilled' && cRes.value.ok) cRes.value.json().then(setConfigData); if (aRes.status === 'fulfilled' && aRes.value.ok) { aRes.value.json().then((d: any) => setActivity(d.items ?? [])); } }).finally(() => setLoading(false)); }, [name]); const isActive = sisterInfo?.status === 'online' || sisterInfo?.status === 'working'; return ( <> 자매 노드 관리 / {SISTER_DISPLAY[name] ?? name}
{SISTER_DISPLAY[name] ?? name} / {SISTER_ROLES[name] ?? 'UNKNOWN'} STATUS:{sisterInfo?.status?.toUpperCase() ?? 'UNKNOWN'}
{loading ? (
LOADING...
) : ( {isActive ? 'ACTIVE' : 'STANDBY'}
LXC: {sisterInfo?.lxcId ?? '--'}
LAST_SEEN: {sisterInfo?.lastSeen ? new Date(sisterInfo.lastSeen).toLocaleString() : '--'}
ROLE: {SISTER_ROLES[name] ?? '--'}
{configData?.description && ( DESC
{configData.description}
)}
{[ { id: 'overview', label: '개요' }, { id: 'config', label: '설정' }, ].map((t) => ( setTab(t.id)}> {t.label} ))} {tab === 'overview' && ( <> RECENT ACTIVITY {activity.length} ENTRIES {activity.slice(0, 8).map((item) => ( {new Date(item.createdAt).toLocaleString('ko-KR', { hour12: false })} {item.detail ?? item.action} ))} {activity.length === 0 && ( 활동 기록 없음 )} )} {tab === 'config' && ( <> SOUL.md / AGENTS.md {configData?.raw || '(설정 파일 없음)'} )}
)} ); }