feat: v3.1 대시보드 추가 (React + FastAPI)
Media Engine Control Panel — 6탭 웹 대시보드 [백엔드] FastAPI (dashboard/backend/) - server.py: 포트 8080, CORS, React SPA 서빙 - api_overview.py: KPI 카드 + 파이프라인 상태 + 활동 로그 - api_content.py: 칸반 보드 + 승인/거부 + 수동 트리거 - api_analytics.py: 방문자 추이 + 플랫폼/코너별 성과 - api_novels.py: 소설 목록/생성/에피소드 관리 - api_settings.py: engine.json CRUD - api_connections.py: AI 서비스 연결 관리 + 키 저장 - api_tools.py: 기능별 AI 도구 선택 - api_cost.py: 구독 현황 + API 사용량 추적 - api_logs.py: 시스템 로그 필터/검색 [프론트엔드] React + Vite + Tailwind + Recharts (dashboard/frontend/) - Overview: KPI 카드 + 파이프라인 + 코너별 바차트 + 활동 로그 - Content: 4열 칸반 보드 + 상세 모달 + 승인/거부 - Analytics: LineChart 방문자 추이 + 플랫폼별 성과 - Novel: 소설 목록 + 에피소드 테이블 + 새 소설 생성 폼 - Settings: 5개 서브탭 (AI연결/도구선택/배포채널/품질/비용관리) - Logs: 필터/검색 시스템 로그 뷰어 [디자인] CNN 다크+골드 테마 - 배경 #0a0a0d + 액센트 #c8a84e - 모바일 반응형 (Tailscale 외부 접속 대응) [실행] - dashboard/start.bat 더블클릭 → http://localhost:8080 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import React, { useState } from 'react'
|
||||
import { LayoutDashboard, FileText, BarChart2, BookOpen, Settings, ScrollText } from 'lucide-react'
|
||||
import Overview from './pages/Overview.jsx'
|
||||
import Content from './pages/Content.jsx'
|
||||
import Analytics from './pages/Analytics.jsx'
|
||||
import Novel from './pages/Novel.jsx'
|
||||
import SettingsPage from './pages/Settings.jsx'
|
||||
import Logs from './pages/Logs.jsx'
|
||||
|
||||
const TABS = [
|
||||
{ id: 'overview', label: '개요', icon: LayoutDashboard, component: Overview },
|
||||
{ id: 'content', label: '콘텐츠', icon: FileText, component: Content },
|
||||
{ id: 'analytics', label: '분석', icon: BarChart2, component: Analytics },
|
||||
{ id: 'novel', label: '소설', icon: BookOpen, component: Novel },
|
||||
{ id: 'settings', label: '설정', icon: Settings, component: SettingsPage },
|
||||
{ id: 'logs', label: '로그', icon: ScrollText, component: Logs },
|
||||
]
|
||||
|
||||
export default function App() {
|
||||
const [activeTab, setActiveTab] = useState('overview')
|
||||
const [systemStatus, setSystemStatus] = useState('ok') // ok | warn | error
|
||||
|
||||
const ActiveComponent = TABS.find(t => t.id === activeTab)?.component || Overview
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-screen bg-bg text-text overflow-hidden">
|
||||
{/* 헤더 */}
|
||||
<header className="flex items-center justify-between px-4 md:px-6 py-3 border-b border-border bg-card flex-shrink-0">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-accent font-bold text-base md:text-lg tracking-tight">
|
||||
The 4th Path
|
||||
</span>
|
||||
<span className="hidden md:inline text-subtext text-xs">· Control Panel</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`w-2 h-2 rounded-full ${systemStatus === 'ok' ? 'bg-success' : systemStatus === 'warn' ? 'bg-warning' : 'bg-error'}`}></span>
|
||||
<span className="text-xs text-subtext hidden sm:inline">
|
||||
{systemStatus === 'ok' ? 'System OK' : systemStatus === 'warn' ? '경고' : '오류'}
|
||||
</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* 탭 네비게이션 */}
|
||||
<nav className="flex border-b border-border bg-card flex-shrink-0 overflow-x-auto">
|
||||
{TABS.map(tab => {
|
||||
const Icon = tab.icon
|
||||
const isActive = activeTab === tab.id
|
||||
return (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
className={`flex items-center gap-1.5 px-3 md:px-5 py-3 text-xs md:text-sm font-medium whitespace-nowrap transition-colors border-b-2 ${
|
||||
isActive
|
||||
? 'border-accent text-accent'
|
||||
: 'border-transparent text-subtext hover:text-text'
|
||||
}`}
|
||||
>
|
||||
<Icon size={15} />
|
||||
<span className="hidden sm:inline">{tab.label}</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
|
||||
{/* 메인 컨텐츠 */}
|
||||
<main className="flex-1 overflow-y-auto">
|
||||
<ActiveComponent />
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user