ITSM static (app.js + index.html): - 사이드바: AI플랫폼·분석KPI·클라우드·외부연동·SaaS 5개 그룹 추가 - 23개 신규 뷰 핸들러 (rag_search, kpi_dashboard, bi_dashboard, jira_sync 등) - 액션 헬퍼 함수 20개+ (재계산, 파인튜닝, 보고서 생성, 데이터 기여 등) Manager 5개 신규 페이지: - KpiDashboard.tsx: KPI 신호등 대시보드 + 재계산 - BiAnalytics.tsx: SR트렌드·카테고리파이·MTTR·엔지니어워크로드 - BillingManage.tsx: 구독플랜·사용량·청구서 이력 - IntegrationHub.tsx: Jira/Slack/ServiceNow/ERP/Kakao/SSO 탭 - AiPlatform.tsx: AI인사이트·LearningLoop·예측·벤치마킹 Messenger 신규 탭: - insights.tsx: AI 주간 인사이트 + SLA 예측 + 이상 감지 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
142 lines
5.1 KiB
TypeScript
142 lines
5.1 KiB
TypeScript
import { NavLink } from 'react-router-dom'
|
|
import { useState } from 'react'
|
|
|
|
interface NavItem { label: string; path?: string; icon: string; children?: NavItem[] }
|
|
|
|
const NAV: NavItem[] = [
|
|
{ label: '대시보드', icon: '📊', path: '/' },
|
|
{ label: '인프라 관리', icon: '🖥️', children: [
|
|
{ label: '서버 목록', icon: '', path: '/servers' },
|
|
{ label: 'CMDB 현황', icon: '', path: '/cmdb' },
|
|
]},
|
|
{ label: '배포/CI-CD', icon: '🚀', children: [
|
|
{ label: '배포 이력', icon: '', path: '/deployments' },
|
|
{ label: '저장소 목록',icon: '', path: '/repos' },
|
|
]},
|
|
{ label: '사용자/테넌트',icon: '👥', children: [
|
|
{ label: '사용자 관리',icon: '', path: '/users' },
|
|
{ label: '기관 관리', icon: '', path: '/institutions' },
|
|
]},
|
|
{ label: '보안', icon: '🔒', children: [
|
|
{ label: 'API Key', icon: '', path: '/api-keys' },
|
|
{ label: '감사 로그', icon: '', path: '/audit' },
|
|
]},
|
|
{ label: 'AI / LLM', icon: '🤖', path: '/llm' },
|
|
{ label: '시스템 설정', icon: '⚙️', children: [
|
|
{ label: '환경변수', icon: '', path: '/config/env' },
|
|
{ label: 'Nginx 관리', icon: '', path: '/config/nginx' },
|
|
]},
|
|
{ label: '알림/리포트', icon: '🔔', path: '/notifications' },
|
|
{ label: '스크랩핑 봇', icon: '🕷️', path: '/scraping' },
|
|
{ label: '라이선스 관리',icon: '🪪', path: '/licenses' },
|
|
{ label: '데이터 연동', icon: '🔄', path: '/export-import' },
|
|
{ label: '운영 관제', icon: '🛰️', children: [
|
|
{ label: 'DR 재해복구', icon: '', path: '/dr' },
|
|
{ label: '네트워크 장비', icon: '', path: '/network' },
|
|
{ label: 'CSAP 점검', icon: '', path: '/csap' },
|
|
]},
|
|
// ── GUARDiA 확장 v3 ──
|
|
{ label: '분석 · KPI', icon: '📈', children: [
|
|
{ label: 'KPI 대시보드', icon: '', path: '/kpi' },
|
|
{ label: 'BI 대시보드', icon: '', path: '/bi' },
|
|
]},
|
|
{ label: 'AI 플랫폼', icon: '🧠', path: '/ai-platform' },
|
|
{ label: '외부 연동', icon: '🔗', path: '/integrations' },
|
|
{ label: '구독 · 과금', icon: '💰', path: '/billing' },
|
|
]
|
|
|
|
/* Variant 스타일 색상 상수 */
|
|
const V = {
|
|
navy: '#003366',
|
|
blue: '#005A8C',
|
|
cyan: '#00A0C8',
|
|
cyanLt: 'rgba(0,160,200,.10)',
|
|
cyanAct: 'rgba(0,160,200,.15)',
|
|
muted: '#64748B',
|
|
text: '#334155',
|
|
border: '#E2E8F0',
|
|
bg: '#F8FAFC',
|
|
}
|
|
|
|
function NavGroup({ item }: { item: NavItem }) {
|
|
const [open, setOpen] = useState(true)
|
|
|
|
if (!item.children) {
|
|
return (
|
|
<NavLink to={item.path!} style={({ isActive }) => ({
|
|
display: 'flex', alignItems: 'center', gap: 8,
|
|
padding: '8px 16px', fontSize: 13,
|
|
color: isActive ? V.navy : V.muted,
|
|
background: isActive ? V.cyanAct : 'transparent',
|
|
borderLeft: isActive ? `3px solid ${V.cyan}` : '3px solid transparent',
|
|
fontWeight: isActive ? 700 : 400,
|
|
transition: 'all .15s',
|
|
textDecoration: 'none',
|
|
})}>
|
|
<span style={{ width: 18, textAlign: 'center' }}>{item.icon}</span>
|
|
{item.label}
|
|
</NavLink>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<button onClick={() => setOpen(o => !o)} style={{
|
|
width: '100%', display: 'flex', alignItems: 'center', gap: 8,
|
|
padding: '8px 16px', fontSize: 13, color: V.navy, background: 'none',
|
|
border: 'none', cursor: 'pointer', fontWeight: 700,
|
|
letterSpacing: '-.01em',
|
|
}}>
|
|
<span style={{ width: 18, textAlign: 'center' }}>{item.icon}</span>
|
|
{item.label}
|
|
<span style={{
|
|
marginLeft: 'auto', fontSize: 9, color: V.cyan,
|
|
transition: 'transform .2s',
|
|
transform: open ? 'rotate(180deg)' : 'rotate(0)',
|
|
}}>▾</span>
|
|
</button>
|
|
{open && item.children.map(c => (
|
|
<NavLink key={c.path} to={c.path!} style={({ isActive }) => ({
|
|
display: 'flex', alignItems: 'center',
|
|
padding: '7px 16px 7px 42px', fontSize: 12.5,
|
|
color: isActive ? V.navy : V.muted,
|
|
background: isActive ? V.cyanAct : 'transparent',
|
|
borderLeft: isActive ? `3px solid ${V.cyan}` : '3px solid transparent',
|
|
fontWeight: isActive ? 600 : 400,
|
|
transition: 'all .15s',
|
|
textDecoration: 'none',
|
|
})}>
|
|
{c.label}
|
|
</NavLink>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function Sidebar() {
|
|
return (
|
|
<aside style={{
|
|
width: 'var(--sidebar-w)',
|
|
background: '#ffffff',
|
|
borderRight: `1px solid ${V.border}`,
|
|
height: '100%',
|
|
display: 'flex', flexDirection: 'column', overflowY: 'auto',
|
|
boxShadow: '2px 0 12px rgba(0,51,102,.06)',
|
|
}}>
|
|
{/* 사이드바 헤더 */}
|
|
<div style={{
|
|
padding: '16px 16px 12px',
|
|
borderBottom: `1px solid ${V.border}`,
|
|
}}>
|
|
<div style={{ fontSize: 10, fontWeight: 700, color: V.cyan,
|
|
letterSpacing: '.1em', textTransform: 'uppercase', marginBottom: 2 }}>
|
|
Navigation
|
|
</div>
|
|
</div>
|
|
<div style={{ padding: '8px 0', flex: 1 }}>
|
|
{NAV.map((item, i) => <NavGroup key={i} item={item} />)}
|
|
</div>
|
|
</aside>
|
|
)
|
|
}
|