feat: add LMS web client

This commit is contained in:
2026-07-15 13:41:00 +09:00
commit 05ff2644e8
308 changed files with 487923 additions and 0 deletions
@@ -0,0 +1,87 @@
import { Terminal } from 'lucide-react'
type ModuleLogEntry = {
time: string
level: 'INFO' | 'WARN' | 'ERROR'
source: string
message: string
}
type Props = {
dark: boolean
borderSoft: string
textMid: string
selectedModuleState: 'running' | 'stopped' | 'uninstalled' | null
moduleLogsLoading: boolean
moduleLogMessage: string | null
visibleModuleLogEntries: ModuleLogEntry[]
moduleLogScrollRef: React.RefObject<HTMLDivElement | null>
onScroll: () => void
}
function WorkspaceLogView({
dark,
borderSoft,
textMid,
selectedModuleState,
moduleLogsLoading,
moduleLogMessage,
visibleModuleLogEntries,
moduleLogScrollRef,
onScroll
}: Props): React.JSX.Element {
return (
<section
className={['min-w-0 overflow-hidden', dark ? 'bg-[#1f1f1f]' : 'bg-[#f7f8fb]'].join(' ')}
style={{ gridColumn: '4 / span 3', gridRow: 2 }}
>
<div className={['flex h-full min-h-0 flex-col', dark ? 'bg-[#1f1f1f]' : 'bg-[#f7f8fb]'].join(' ')}>
<div className={['flex h-11 items-center justify-between border-b px-4', borderSoft].join(' ')}>
<div className="flex items-center gap-2">
<Terminal size={15} className={dark ? 'text-white/58' : 'text-[#687284]'} />
<span className={['text-[12px] font-bold', textMid].join(' ')}>실시간 로그</span>
</div>
<span className={['rounded px-2 py-1 font-mono text-[10px]', dark ? 'bg-white/7 text-white/45' : 'bg-[#e8ebf2] text-[#687284]'].join(' ')}>
{selectedModuleState === 'running' ? 'STREAMING' : 'IDLE'}
</span>
</div>
<div
ref={moduleLogScrollRef}
onScroll={onScroll}
className={['thin-y-scrollbar min-h-0 flex-1 overflow-auto p-4 font-mono text-[12px] leading-5', dark ? 'bg-[#171717] text-white/68' : 'bg-[#f9fafc] text-[#374151]'].join(' ')}
>
{moduleLogsLoading ? (
<div className={['grid h-full place-items-center font-sans text-[12px] font-semibold', dark ? 'text-white/42' : 'text-[#7b8494]'].join(' ')}>
로그 불러오는 중
</div>
) : visibleModuleLogEntries.length === 0 ? (
<div className={['grid h-full place-items-center font-sans text-[12px] font-semibold', dark ? 'text-white/42' : 'text-[#7b8494]'].join(' ')}>
{moduleLogMessage ?? '표시할 로그 없음'}
</div>
) : visibleModuleLogEntries.map((entry, index) => (
<div key={`${entry.time}-${entry.source}-${index}`} className="flex min-w-0 gap-1.5">
<span className={dark ? 'w-[52px] shrink-0 text-white/28' : 'w-[52px] shrink-0 text-[#9aa3b2]'}>{entry.time}</span>
<span
className={[
'w-[44px] shrink-0',
entry.level === 'ERROR'
? 'text-[#ef4444]'
: entry.level === 'WARN'
? 'text-[#f59e0b]'
: dark ? 'text-[#5eead4]' : 'text-[#0f766e]'
].join(' ')}
>
[{entry.level}]
</span>
<span className={['min-w-0 flex-1 whitespace-pre-wrap break-words', dark ? 'text-white/72' : 'text-[#2f3747]'].join(' ')}>
{entry.message}
</span>
</div>
))}
</div>
</div>
</section>
)
}
export default WorkspaceLogView