zioinfo-mail/docs/API_명세서.html
DESKTOP-TKLFCPR\ython 11c670f2a0 refactor: 101.79.17.164 → zioinfo.co.kr 전체 도메인 변환 + Manager UI 배포
- 37개 파일 IP → zioinfo.co.kr 치환 (소스/매뉴얼/설정/하네스)
- Manager DrConsole/NetworkConsole/CsapConsole 빌드 + /var/www/manager/ 배포
- 테스트: Manager HTTP 200, ITSM 신규 API 7개 전체 200

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 10:09:17 +09:00

1296 lines
76 KiB
HTML

<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GUARDiA ITSM v2.0 — API 명세서</title>
<style>
:root {
--bg: #0f172a;
--surface: #1e293b;
--surface2: #263349;
--border: #334155;
--text: #e2e8f0;
--text-muted: #94a3b8;
--primary: #3b82f6;
--primary-dim: #1d4ed8;
--green: #22c55e;
--yellow: #eab308;
--red: #ef4444;
--orange: #f97316;
--purple: #a855f7;
--cyan: #06b6d4;
--get: #22c55e;
--post: #3b82f6;
--patch: #f97316;
--put: #a855f7;
--delete: #ef4444;
--sidebar-w: 280px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { background: var(--bg); color: var(--text); font-family: 'Segoe UI', system-ui, sans-serif; font-size: 14px; line-height: 1.6; }
/* Layout */
#app { display: flex; min-height: 100vh; }
#sidebar {
width: var(--sidebar-w);
background: var(--surface);
border-right: 1px solid var(--border);
position: fixed; top: 0; left: 0; height: 100vh;
overflow-y: auto; z-index: 100;
display: flex; flex-direction: column;
}
#main { margin-left: var(--sidebar-w); flex: 1; padding: 24px 32px; max-width: 1100px; }
/* Sidebar */
.sidebar-header {
padding: 20px 16px 12px;
border-bottom: 1px solid var(--border);
position: sticky; top: 0; background: var(--surface); z-index: 10;
}
.sidebar-logo { font-size: 16px; font-weight: 700; color: var(--primary); margin-bottom: 4px; }
.sidebar-sub { font-size: 11px; color: var(--text-muted); }
#search {
width: 100%; margin-top: 10px; padding: 7px 10px;
background: var(--surface2); border: 1px solid var(--border); border-radius: 6px;
color: var(--text); font-size: 13px; outline: none;
}
#search:focus { border-color: var(--primary); }
#search::placeholder { color: var(--text-muted); }
.sidebar-stats { padding: 8px 16px; font-size: 11px; color: var(--text-muted); border-bottom: 1px solid var(--border); display: flex; gap: 12px; }
.stat-badge { background: var(--surface2); border-radius: 10px; padding: 2px 8px; }
.nav-section { padding: 6px 0; border-bottom: 1px solid var(--border); }
.nav-group-title {
padding: 6px 16px 2px; font-size: 10px; font-weight: 600;
color: var(--text-muted); text-transform: uppercase; letter-spacing: .8px;
}
.nav-item {
display: flex; align-items: center; justify-content: space-between;
padding: 5px 16px; cursor: pointer; border-radius: 0;
transition: background .15s; text-decoration: none; color: var(--text);
}
.nav-item:hover, .nav-item.active { background: var(--surface2); color: var(--primary); }
.nav-item-name { font-size: 12.5px; }
.nav-count { font-size: 10px; background: var(--surface2); border-radius: 10px; padding: 1px 6px; color: var(--text-muted); }
/* Top bar */
#topbar {
display: flex; align-items: center; gap: 12px;
margin-bottom: 24px; padding-bottom: 16px;
border-bottom: 1px solid var(--border); flex-wrap: wrap;
}
#topbar h1 { font-size: 22px; font-weight: 700; color: var(--text); }
.version-badge { background: var(--primary-dim); color: #fff; border-radius: 12px; padding: 2px 10px; font-size: 12px; }
.filter-row { display: flex; gap: 8px; flex-wrap: wrap; }
.filter-btn {
padding: 4px 12px; border-radius: 20px; border: 1px solid var(--border);
background: var(--surface); color: var(--text-muted); cursor: pointer; font-size: 12px;
transition: all .15s;
}
.filter-btn:hover { border-color: var(--primary); color: var(--primary); }
.filter-btn.active { background: var(--primary); border-color: var(--primary); color: #fff; }
.filter-btn.get.active { background: var(--get); border-color: var(--get); }
.filter-btn.post.active { background: var(--post); border-color: var(--post); }
.filter-btn.patch.active { background: var(--patch); border-color: var(--patch); }
.filter-btn.put.active { background: var(--purple); border-color: var(--purple); }
.filter-btn.delete.active { background: var(--delete); border-color: var(--delete); }
/* Sections */
.section { margin-bottom: 32px; scroll-margin-top: 20px; }
.section-header {
display: flex; align-items: center; gap: 10px; cursor: pointer;
padding: 12px 16px; background: var(--surface); border-radius: 8px;
border: 1px solid var(--border); margin-bottom: 2px;
user-select: none;
}
.section-header:hover { border-color: var(--primary); }
.section-title { font-size: 15px; font-weight: 600; flex: 1; }
.section-tag { font-size: 11px; color: var(--text-muted); font-family: monospace; }
.section-count { font-size: 11px; background: var(--surface2); border-radius: 10px; padding: 2px 8px; color: var(--text-muted); }
.chevron { transition: transform .2s; color: var(--text-muted); font-size: 12px; }
.section-header.collapsed .chevron { transform: rotate(-90deg); }
.section-body { display: none; border: 1px solid var(--border); border-top: none; border-radius: 0 0 8px 8px; overflow: hidden; }
.section-header:not(.collapsed) + .section-body { display: block; }
/* API Table */
.api-table { width: 100%; border-collapse: collapse; }
.api-table th {
background: var(--surface2); padding: 8px 12px; text-align: left;
font-size: 11px; color: var(--text-muted); text-transform: uppercase;
letter-spacing: .5px; border-bottom: 1px solid var(--border);
}
.api-row { border-bottom: 1px solid var(--border); transition: background .1s; }
.api-row:last-child { border-bottom: none; }
.api-row:hover { background: var(--surface2); }
.api-row.hidden { display: none; }
.api-row td { padding: 9px 12px; vertical-align: middle; }
/* Method badge */
.method {
display: inline-block; padding: 2px 8px; border-radius: 4px;
font-size: 11px; font-weight: 700; font-family: monospace; min-width: 52px; text-align: center;
}
.GET { background: rgba(34,197,94,.15); color: var(--get); }
.POST { background: rgba(59,130,246,.15); color: var(--post); }
.PATCH { background: rgba(249,115,22,.15); color: var(--patch); }
.PUT { background: rgba(168,85,247,.15); color: var(--purple); }
.DELETE { background: rgba(239,68,68,.15); color: var(--delete); }
/* Path */
.path-cell { display: flex; align-items: center; gap: 8px; }
.path-text { font-family: monospace; font-size: 13px; color: #cbd5e1; }
.path-param { color: var(--orange); }
.copy-btn {
opacity: 0; cursor: pointer; border: none; background: none;
color: var(--text-muted); font-size: 13px; padding: 2px 4px; border-radius: 3px;
transition: opacity .15s;
}
.api-row:hover .copy-btn { opacity: 1; }
.copy-btn:hover { background: var(--surface); color: var(--primary); }
/* Auth badge */
.auth-badge { font-size: 11px; padding: 2px 8px; border-radius: 10px; white-space: nowrap; }
.auth-yes { background: rgba(239,68,68,.12); color: #fca5a5; }
.auth-no { background: rgba(34,197,94,.12); color: #86efac; }
.auth-sig { background: rgba(234,179,8,.12); color: #fde047; }
.auth-admin { background: rgba(168,85,247,.12); color: #d8b4fe; }
/* Desc cell */
.desc-text { color: var(--text-muted); font-size: 13px; }
/* Info cards */
.info-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; margin-bottom: 24px; }
.info-card {
background: var(--surface); border: 1px solid var(--border); border-radius: 8px;
padding: 14px 16px;
}
.info-card-val { font-size: 28px; font-weight: 700; color: var(--primary); }
.info-card-label { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
/* Feature table */
.feature-section { margin-bottom: 24px; }
.feature-section h3 { font-size: 14px; color: var(--text-muted); margin-bottom: 8px; padding-left: 4px; }
.feature-table { width: 100%; border-collapse: collapse; }
.feature-table th {
background: var(--surface2); padding: 8px 12px; text-align: left;
font-size: 11px; color: var(--text-muted); border-bottom: 1px solid var(--border);
}
.feature-table td { padding: 8px 12px; border-bottom: 1px solid var(--border); font-size: 13px; }
.feature-table tr:last-child td { border-bottom: none; }
.feature-table tr:hover td { background: var(--surface2); }
.feature-num { color: var(--text-muted); font-size: 12px; width: 36px; }
.api-cnt { text-align: right; color: var(--primary); font-weight: 600; font-size: 12px; }
/* Security table */
.sec-table { width: 100%; border-collapse: collapse; }
.sec-table th { background: rgba(239,68,68,.1); padding: 8px 12px; text-align: left; font-size: 11px; color: var(--text-muted); border-bottom: 1px solid var(--border); }
.sec-table td { padding: 8px 12px; border-bottom: 1px solid var(--border); font-size: 13px; }
.sec-table tr:hover td { background: var(--surface2); }
.sec-rule { color: var(--red); font-weight: 600; }
/* Code block */
.code-block {
background: #0a0f1e; border: 1px solid var(--border); border-radius: 6px;
padding: 12px 16px; font-family: monospace; font-size: 12px;
color: #94a3b8; overflow-x: auto; margin: 8px 0;
}
/* No result */
#no-result { display: none; text-align: center; padding: 40px; color: var(--text-muted); }
/* Scroll top */
#scroll-top {
position: fixed; bottom: 24px; right: 24px;
background: var(--primary); color: #fff; border: none; border-radius: 50%;
width: 40px; height: 40px; cursor: pointer; font-size: 18px;
box-shadow: 0 4px 12px rgba(0,0,0,.4); opacity: 0; transition: opacity .3s;
z-index: 200;
}
#scroll-top.visible { opacity: 1; }
/* Toast */
#toast {
position: fixed; bottom: 70px; right: 24px;
background: var(--green); color: #fff; border-radius: 6px;
padding: 8px 16px; font-size: 13px; opacity: 0;
transition: opacity .3s; pointer-events: none; z-index: 300;
}
#toast.show { opacity: 1; }
/* Tabs */
.tab-row { display: flex; gap: 0; border-bottom: 1px solid var(--border); margin-bottom: 20px; }
.tab {
padding: 10px 20px; cursor: pointer; font-size: 13px; color: var(--text-muted);
border-bottom: 2px solid transparent; transition: all .15s;
}
.tab:hover { color: var(--text); }
.tab.active { color: var(--primary); border-bottom-color: var(--primary); }
.tab-content { display: none; }
.tab-content.active { display: block; }
/* Responsive */
@media (max-width: 768px) {
#sidebar { transform: translateX(-100%); }
#main { margin-left: 0; padding: 16px; }
}
</style>
</head>
<body>
<div id="app">
<!-- ===== SIDEBAR ===== -->
<nav id="sidebar">
<div class="sidebar-header">
<div class="sidebar-logo">⚡ GUARDiA ITSM</div>
<div class="sidebar-sub">API 명세서 v2.0.0</div>
<input id="search" type="search" placeholder="🔍 API 검색 (경로, 설명)..." autocomplete="off">
</div>
<div class="sidebar-stats">
<span class="stat-badge" id="stat-total">602 APIs</span>
<span class="stat-badge" id="stat-shown">73 tags</span>
</div>
<div id="nav-list"></div>
</nav>
<!-- ===== MAIN ===== -->
<main id="main">
<div id="topbar">
<div>
<h1>GUARDiA ITSM API 명세서</h1>
<div style="color:var(--text-muted);font-size:12px;margin-top:4px">Copyright © 2026 (주)지오정보기술 · Base URL: <code style="color:var(--cyan)">http://&lt;host&gt;:8001</code></div>
</div>
<span class="version-badge">v2.0.0</span>
</div>
<!-- Tabs -->
<div class="tab-row">
<div class="tab active" onclick="switchTab('overview')">개요</div>
<div class="tab" onclick="switchTab('api')">API 목록</div>
<div class="tab" onclick="switchTab('security')">보안 제약</div>
<div class="tab" onclick="switchTab('codes')">응답 코드</div>
</div>
<!-- TAB: OVERVIEW -->
<div id="tab-overview" class="tab-content active">
<div class="info-grid">
<div class="info-card"><div class="info-card-val">602</div><div class="info-card-label">총 API 수</div></div>
<div class="info-card"><div class="info-card-val">73</div><div class="info-card-label">도메인 태그</div></div>
<div class="info-card"><div class="info-card-val">71</div><div class="info-card-label">기능 영역</div></div>
<div class="info-card"><div class="info-card-val">1,000+</div><div class="info-card-label">지원 공공기관</div></div>
</div>
<div id="feature-tables"></div>
</div>
<!-- TAB: API LIST -->
<div id="tab-api" class="tab-content">
<div class="filter-row" style="margin-bottom:16px">
<button class="filter-btn active" onclick="filterMethod('ALL',this)">ALL</button>
<button class="filter-btn get" onclick="filterMethod('GET',this)">GET</button>
<button class="filter-btn post" onclick="filterMethod('POST',this)">POST</button>
<button class="filter-btn patch" onclick="filterMethod('PATCH',this)">PATCH</button>
<button class="filter-btn put" onclick="filterMethod('PUT',this)">PUT</button>
<button class="filter-btn delete" onclick="filterMethod('DELETE',this)">DELETE</button>
<button class="filter-btn" onclick="filterAuth('no-auth',this)" id="btn-noauth">인증 불필요</button>
</div>
<div id="api-sections"></div>
<div id="no-result">검색 결과가 없습니다.</div>
</div>
<!-- TAB: SECURITY -->
<div id="tab-security" class="tab-content">
<h2 style="margin-bottom:16px;font-size:16px">보안 제약 (불변 규칙)</h2>
<table class="sec-table">
<thead><tr><th>규칙</th><th>내용</th></tr></thead>
<tbody>
<tr><td class="sec-rule">외부 API 금지</td><td>on-premise Ollama만 허용. 외부 API 호출 절대 금지</td></tr>
<tr><td class="sec-rule">자격증명 보호</td><td>IP, SSH계정, 비밀번호를 API 응답 / 메신저 / 에러메시지에 절대 노출 금지</td></tr>
<tr><td class="sec-rule">암호화 필수</td><td>os_pw_enc 컬럼 AES-256-GCM 암호화 저장 (IV 12B + 암호문 + GCM Tag 16B)</td></tr>
<tr><td class="sec-rule">root 금지</td><td>root SSH 직접 접속 금지 — opsagent 전용 계정 사용</td></tr>
<tr><td class="sec-rule">에러 응답</td><td>스택트레이스 미노출 — SR ID + 요약 메시지만 전달</td></tr>
<tr><td class="sec-rule">ServerOut 스키마</td><td>ip_addr, ssh_user, os_pw_enc 컬럼 API 응답에서 완전 제외</td></tr>
<tr><td class="sec-rule">감사 로그</td><td>모든 명령/결과 TB_AUDIT_LOG SHA-256 해시체인 기록</td></tr>
<tr><td class="sec-rule">MFA</td><td>관리자/sm 역할 MFA 활성화 권고 (TOTP)</td></tr>
</tbody>
</table>
<h2 style="margin:24px 0 12px;font-size:16px">RBAC 역할</h2>
<table class="sec-table">
<thead><tr><th>역할</th><th>코드</th><th>권한 범위</th></tr></thead>
<tbody>
<tr><td>관리자</td><td><code>admin</code></td><td>전체</td></tr>
<tr><td>PM</td><td><code>pm</code></td><td>승인 / 보고 / SLA / 프로젝트</td></tr>
<tr><td>SM 엔지니어</td><td><code>sm</code></td><td>SR 처리 / 배포 / SSH</td></tr>
<tr><td>개발자</td><td><code>dev</code></td><td>코드리뷰 / Vibe CD</td></tr>
<tr><td>고객</td><td><code>customer</code></td><td>포털 SR 접수 / 조회</td></tr>
<tr><td>뷰어</td><td><code>viewer</code></td><td>읽기 전용</td></tr>
</tbody>
</table>
<h2 style="margin:24px 0 12px;font-size:16px">인증 방식</h2>
<div class="code-block">
Authorization: Bearer &lt;JWT 토큰&gt;
Content-Type: application/json
# 토큰 발급
POST /api/auth/login → { "username": "...", "password": "..." }
# MFA 2단계
POST /api/auth/login/mfa → { "token": "&lt;TOTP 6자리&gt;" }
# 토큰 만료: 24시간 (TRIAL_DURATION_DAYS 환경변수)
</div>
<h2 style="margin:24px 0 12px;font-size:16px">감사 로그 해시체인</h2>
<div class="code-block">hash = SHA256(prev_hash + actor + action + detail + timestamp)
GET /api/audit/verify → 전체 무결성 검증
GET /api/audit/verify/{a}/{b} → 구간 검증</div>
</div>
<!-- TAB: RESPONSE CODES -->
<div id="tab-codes" class="tab-content">
<h2 style="margin-bottom:16px;font-size:16px">공통 HTTP 응답 코드</h2>
<table class="sec-table">
<thead><tr><th style="width:80px">코드</th><th style="width:160px">의미</th><th>설명</th></tr></thead>
<tbody>
<tr><td style="color:var(--green);font-weight:700">200</td><td>OK</td><td>조회 성공</td></tr>
<tr><td style="color:var(--green);font-weight:700">201</td><td>Created</td><td>생성 성공</td></tr>
<tr><td style="color:var(--green);font-weight:700">204</td><td>No Content</td><td>삭제 성공 (응답 바디 없음)</td></tr>
<tr><td style="color:var(--yellow);font-weight:700">400</td><td>Bad Request</td><td>입력값 오류</td></tr>
<tr><td style="color:var(--red);font-weight:700">401</td><td>Unauthorized</td><td>인증 실패 또는 토큰 만료</td></tr>
<tr><td style="color:var(--red);font-weight:700">403</td><td>Forbidden</td><td>권한 없음 (RBAC)</td></tr>
<tr><td style="color:var(--yellow);font-weight:700">404</td><td>Not Found</td><td>리소스 없음</td></tr>
<tr><td style="color:var(--yellow);font-weight:700">409</td><td>Conflict</td><td>중복 또는 상태 충돌</td></tr>
<tr><td style="color:var(--yellow);font-weight:700">422</td><td>Unprocessable Entity</td><td>Pydantic 유효성 오류</td></tr>
<tr><td style="color:var(--red);font-weight:700">429</td><td>Too Many Requests</td><td>Rate Limit 초과</td></tr>
<tr><td style="color:var(--red);font-weight:700">500</td><td>Internal Server Error</td><td>서버 오류 (상세 로그는 서버에만 기록)</td></tr>
</tbody>
</table>
<h2 style="margin:24px 0 12px;font-size:16px">에러 응답 형식</h2>
<div class="code-block">{
"detail": "SR-1234: 상태 전이 불가 (현재: closed)",
"error_code": "SR_INVALID_STATE",
"sr_id": "SR-1234"
}
# 에러 코드 전체 목록: GET /api/admin/errors/codes</div>
<h2 style="margin:24px 0 12px;font-size:16px">SLA 기준</h2>
<table class="sec-table">
<thead><tr><th>우선순위</th><th>응답</th><th>해결</th></tr></thead>
<tbody>
<tr><td style="color:var(--red)">CRITICAL</td><td>15분</td><td>2시간</td></tr>
<tr><td style="color:var(--orange)">HIGH</td><td>1시간</td><td>4시간</td></tr>
<tr><td style="color:var(--yellow)">MEDIUM</td><td>4시간</td><td>24시간</td></tr>
<tr><td style="color:var(--green)">LOW</td><td>24시간</td><td>72시간</td></tr>
</tbody>
</table>
</div>
</main>
</div>
<button id="scroll-top" onclick="window.scrollTo({top:0,behavior:'smooth'})"></button>
<div id="toast">✓ 복사됨</div>
<script>
// ============================================================
// DATA
// ============================================================
const FEATURES = [
{ cat: 'ITSM 핵심', items: [
{ n:1, name:'SR 서비스 요청', desc:'접수/조회/상태전이/SLA/AI제안/대량처리', cnt:9 },
{ n:2, name:'승인 워크플로우', desc:'PM 승인/위임/기한연장/전자서명', cnt:6 },
{ n:3, name:'엔지니어 배정', desc:'자동배정/워크로드/가용엔지니어 조회', cnt:3 },
{ n:4, name:'첨부파일', desc:'SR 첨부파일 업로드/다운로드/삭제', cnt:4 },
{ n:5, name:'작업 실행', desc:'에이전트리스 배포 단계별 실행/시뮬레이션', cnt:4 },
{ n:6, name:'인시던트', desc:'인시던트 CRUD/자동RCA/SR연계/통계', cnt:11 },
{ n:7, name:'문제관리', desc:'문제레코드/RCA/해결방법/known-error', cnt:13 },
{ n:8, name:'변경관리', desc:'RFC/CAB투표/동결기간/이행/롤백', cnt:18 },
{ n:9, name:'온콜', desc:'온콜일정/에스컬레이션/순환설정', cnt:12 },
]},
{ cat: 'CMDB / 자산', items: [
{ n:10, name:'CMDB CI', desc:'CI 등록/조회/변경이력/관계도', cnt:17 },
{ n:11, name:'기관 관리', desc:'기관 CRUD/담당자 관리/서버 목록', cnt:10 },
]},
{ cat: 'AI / 자동화', items: [
{ n:12, name:'AI 자연어 명령', desc:'메신저 한줄 명령 → sLLM 파싱 → 자동 실행', cnt:1 },
{ n:13, name:'이상 탐지', desc:'메트릭 수집/룰기반/시뮬레이션/이벤트 관리', cnt:15 },
{ n:14, name:'예측 유지보수', desc:'수명예측/임계값/배치분석/헬스', cnt:9 },
{ n:15, name:'AI 에이전트', desc:'에이전트 CRUD/메시지/승인/파인튜닝', cnt:21 },
{ n:16, name:'오케스트레이터', desc:'워크플로우 실행/재시도/통계', cnt:7 },
{ n:17, name:'학습 루프', desc:'재발감지/KB효과추적/패턴마이닝/임계값', cnt:14 },
{ n:18, name:'챗봇', desc:'세션 관리/Ollama 연동/히스토리', cnt:7 },
{ n:19, name:'KB 에이전트', desc:'KB 자동생성/분석/품질관리', cnt:6 },
]},
{ cat: 'SI 프로젝트 관리', items: [
{ n:20, name:'SI 프로젝트', desc:'CRUD/단계전환/체크리스트/RTM/SM전환', cnt:11 },
{ n:21, name:'WBS', desc:'WBS CRUD/Gantt/진척률/대량등록', cnt:8 },
{ n:22, name:'요구사항', desc:'요구사항 CRUD/확인/통계', cnt:7 },
{ n:23, name:'이슈', desc:'이슈 CRUD/SR생성/해결', cnt:8 },
{ n:24, name:'리스크', desc:'리스크 매트릭스/이슈전환', cnt:7 },
{ n:25, name:'마일스톤/산출물', desc:'마일스톤/산출물 CRUD/승인', cnt:12 },
{ n:26, name:'테스트', desc:'테스트계획/케이스/결함/실행이력', cnt:18 },
{ n:27, name:'산출물', desc:'산출물 제출/검토', cnt:8 },
{ n:28, name:'SI 보고서', desc:'일간/주간/월간/발송', cnt:5 },
{ n:29, name:'포트폴리오', desc:'KPI/리소스 가용성/스킬 관리', cnt:7 },
]},
{ cat: '보안 / 컴플라이언스', items: [
{ n:30, name:'인증', desc:'JWT/MFA/OAuth2/비밀번호변경/계정잠금', cnt:16 },
{ n:31, name:'감사로그', desc:'해시체인/무결성검증/내보내기', cnt:8 },
{ n:32, name:'PAM 특권접근', desc:'세션승인/명령기록/체크인아웃', cnt:12 },
{ n:33, name:'LDAP', desc:'디렉터리 동기화/인증/그룹매핑', cnt:9 },
{ n:34, name:'SIEM', desc:'보안이벤트 수집/배치처리', cnt:5 },
{ n:35, name:'취약점 스캔', desc:'CVE조회/패치관리/CVSS계산', cnt:12 },
{ n:36, name:'컴플라이언스', desc:'룰스캔/결과조회/엑셀리포트', cnt:6 },
]},
{ cat: '운영 / 인프라', items: [
{ n:37, name:'쉘 스크립트', desc:'스크립트 라이브러리 CRUD/사용횟수', cnt:6 },
{ n:38, name:'SSH 원격 실행', desc:'명령 실행/스크립트 실행', cnt:3 },
{ n:39, name:'SSL 인증서', desc:'만료점검/갱신/이력', cnt:6 },
{ n:40, name:'작업 타임테이블', desc:'계획/실행/Excel 다운로드', cnt:7 },
{ n:41, name:'PM 점검', desc:'점검템플릿/일정/결과보고', cnt:13 },
{ n:42, name:'Scouter APM', desc:'에이전트배포/메트릭/서비스/xlog', cnt:7 },
{ n:43, name:'위상도', desc:'CI 관계 그래프/헬스', cnt:4 },
{ n:44, name:'FinOps', desc:'예산/비용/최적화 권고', cnt:10 },
{ n:45, name:'용량 관리', desc:'계획/알림/트렌드', cnt:10 },
{ n:46, name:'인프라 확장', desc:'K8s/ERP/Zero-Trust', cnt:7 },
]},
{ cat: '플랫폼 / 연동', items: [
{ n:47, name:'메신저봇', desc:'웹훅/슬래시 명령(/sr /status /bulk)', cnt:2 },
{ n:48, name:'게이트웨이', desc:'Jira/Confluence/외부연동/웹훅', cnt:14 },
{ n:49, name:'그룹웨어', desc:'전자결재 연동/콜백', cnt:4 },
{ n:50, name:'서비스 카탈로그', desc:'셀프서비스 CRUD/요청', cnt:8 },
{ n:51, name:'포털', desc:'고객용 SR접수/공지/FAQ', cnt:8 },
{ n:52, name:'멀티테넌트', desc:'테넌트 CRUD/쿼터 관리', cnt:7 },
{ n:53, name:'라이선스', desc:'활성화/체험판/이력', cnt:6 },
{ n:54, name:'푸시알림', desc:'PWA Web Push/구독 관리', cnt:4 },
{ n:55, name:'알림', desc:'이메일/메신저 테스트/로그', cnt:4 },
{ n:56, name:'보고서', desc:'월별/예약/내보내기', cnt:7 },
{ n:57, name:'메트릭', desc:'Prometheus/Grafana 연동', cnt:6 },
{ n:58, name:'분석 통계', desc:'SR추이/배포통계/엔지니어 워크로드', cnt:10 },
{ n:59, name:'배치 작업', desc:'스케줄러/수동실행/로그스트림', cnt:11 },
]},
{ cat: '개발자 / 관리자', items: [
{ n:60, name:'코드리뷰', desc:'AI 코드리뷰/결함조회/빠른스캔', cnt:6 },
{ n:61, name:'Vibe CD', desc:'GitOps 배포/영향분석/PM승인/Jenkins', cnt:15 },
{ n:62, name:'관리자', desc:'About/백업복원/헬스체크/에러코드', cnt:7 },
{ n:63, name:'온보딩', desc:'8단계 초기설정 챗봇 가이드', cnt:7 },
{ n:64, name:'WebSocket', desc:'실시간 이벤트/채널 브로드캐스트', cnt:2 },
{ n:65, name:'타임라인', desc:'전체 변경 타임라인/요약', cnt:2 },
{ n:66, name:'지식베이스', desc:'문서 검색/SR 유사문서 제안', cnt:3 },
{ n:67, name:'평점', desc:'SR 완료 후 고객 만족도', cnt:2 },
{ n:68, name:'공개 체크리스트', desc:'운영 상태 페이지(Statuspage)', cnt:4 },
{ n:69, name:'프로젝트', desc:'배포 프로젝트 CRUD', cnt:5 },
]},
];
const TAGS = [
{ id:'auth', label:'인증', tag:'auth', apis:[
{m:'POST', p:'/api/auth/login', d:'ID/PW 로그인, JWT 반환', a:'no'},
{m:'POST', p:'/api/auth/login/mfa', d:'MFA 코드 검증 후 최종 토큰 반환', a:'no'},
{m:'POST', p:'/api/auth/logout', d:'토큰 무효화', a:'yes'},
{m:'GET', p:'/api/auth/me', d:'현재 로그인 사용자 정보', a:'yes'},
{m:'POST', p:'/api/auth/change-password', d:'비밀번호 변경', a:'yes'},
{m:'POST', p:'/api/auth/mfa/setup', d:'MFA TOTP 시드 생성', a:'yes'},
{m:'POST', p:'/api/auth/mfa/enable', d:'MFA 활성화 (코드 검증)', a:'yes'},
{m:'POST', p:'/api/auth/mfa/disable', d:'MFA 비활성화', a:'yes'},
{m:'GET', p:'/api/auth/mfa/status', d:'MFA 활성화 여부 조회', a:'yes'},
{m:'GET', p:'/api/auth/users', d:'전체 사용자 목록', a:'admin'},
{m:'GET', p:'/api/auth/admin/users/{user_id}/lock-status', d:'계정 잠금 상태 조회', a:'admin'},
{m:'POST', p:'/api/auth/admin/users/{user_id}/unlock', d:'계정 잠금 해제', a:'admin'},
{m:'POST', p:'/api/auth/admin/users/{user_id}/mfa-reset', d:'MFA 강제 초기화', a:'admin'},
{m:'GET', p:'/api/auth/oauth/providers', d:'OAuth2 제공자 목록', a:'no'},
{m:'GET', p:'/api/auth/oauth/{provider}/start', d:'OAuth2 인증 시작 (리다이렉트)', a:'no'},
{m:'GET', p:'/api/auth/oauth/{provider}/callback', d:'OAuth2 콜백, JWT 반환', a:'no'},
]},
{ id:'dashboard', label:'대시보드', tag:'dashboard', apis:[
{m:'GET', p:'/api/dashboard/me', d:'내 SR/배정/SLA 현황 요약', a:'yes'},
{m:'GET', p:'/api/dashboard/stats/trend', d:'7일 SR 추이 (JSON)', a:'yes'},
{m:'GET', p:'/api/dashboard/events', d:'SSE 실시간 이벤트 스트림', a:'yes'},
]},
{ id:'tasks', label:'SR 서비스요청', tag:'tasks', apis:[
{m:'GET', p:'/api/tasks', d:'SR 목록 (필터: status/priority/category)', a:'yes'},
{m:'POST', p:'/api/tasks', d:'SR 신규 접수', a:'yes'},
{m:'GET', p:'/api/tasks/{sr_id}', d:'SR 상세 조회', a:'yes'},
{m:'PATCH', p:'/api/tasks/{sr_id}/status', d:'SR 상태 전이', a:'yes'},
{m:'GET', p:'/api/tasks/stats', d:'SR 통계', a:'yes'},
{m:'GET', p:'/api/tasks/sla/violations', d:'SLA 위반 목록', a:'yes'},
{m:'GET', p:'/api/tasks/{sr_id}/sla', d:'SR 개별 SLA 현황', a:'yes'},
{m:'GET', p:'/api/tasks/{sr_id}/ai-suggestion', d:'AI 처리방법 제안 (Ollama)', a:'yes'},
{m:'POST', p:'/api/tasks/bulk', d:'SR 대량 상태 변경', a:'admin'},
]},
{ id:'attachments', label:'첨부파일', tag:'attachments', apis:[
{m:'POST', p:'/api/tasks/{sr_id}/attachments', d:'파일 업로드 (multipart)', a:'yes'},
{m:'GET', p:'/api/tasks/{sr_id}/attachments', d:'첨부파일 목록', a:'yes'},
{m:'GET', p:'/api/tasks/{sr_id}/attachments/{att_id}/download', d:'파일 다운로드', a:'yes'},
{m:'DELETE', p:'/api/tasks/{sr_id}/attachments/{att_id}', d:'첨부파일 삭제', a:'yes'},
]},
{ id:'work', label:'작업 실행', tag:'work', apis:[
{m:'GET', p:'/api/work/{sr_id}', d:'작업 단계 목록 조회', a:'yes'},
{m:'POST', p:'/api/work/{sr_id}/step', d:'작업 단계 실행 (SSH/SFTP)', a:'yes'},
{m:'POST', p:'/api/work/{sr_id}/simulate', d:'배포 시뮬레이션 (드라이런)', a:'yes'},
{m:'POST', p:'/api/work/{sr_id}/complete', d:'작업 완료 처리', a:'yes'},
]},
{ id:'approvals', label:'승인 워크플로우', tag:'approvals', apis:[
{m:'GET', p:'/api/approvals/{sr_id}', d:'SR 승인 목록 조회', a:'yes'},
{m:'POST', p:'/api/approvals/{sr_id}', d:'승인 요청 생성', a:'yes'},
{m:'POST', p:'/api/approvals/{approval_id}/sign', d:'승인/거절 처리 (전자서명)', a:'yes'},
{m:'POST', p:'/api/approvals/{approval_id}/delegate', d:'승인 위임', a:'yes'},
{m:'POST', p:'/api/approvals/{approval_id}/extend-deadline', d:'승인 기한 연장', a:'yes'},
{m:'GET', p:'/api/approvals/pending/overdue', d:'기한 초과 승인 목록', a:'admin'},
]},
{ id:'assign', label:'엔지니어 배정', tag:'assign', apis:[
{m:'GET', p:'/api/assign/engineers', d:'가용 엔지니어 목록', a:'yes'},
{m:'GET', p:'/api/assign/workload', d:'엔지니어별 워크로드', a:'yes'},
{m:'POST', p:'/api/assign/{sr_id}', d:'SR에 엔지니어 자동 배정', a:'admin'},
]},
{ id:'cmdb', label:'CMDB 자산', tag:'cmdb', apis:[
{m:'GET', p:'/api/cmdb/ci', d:'CI 목록 (필터: type/status)', a:'yes'},
{m:'POST', p:'/api/cmdb/ci', d:'CI 등록', a:'admin'},
{m:'GET', p:'/api/cmdb/ci/{ci_id}', d:'CI 상세 조회', a:'yes'},
{m:'PATCH', p:'/api/cmdb/ci/{ci_id}', d:'CI 수정', a:'admin'},
{m:'DELETE', p:'/api/cmdb/ci/{ci_id}', d:'CI 삭제', a:'admin'},
{m:'GET', p:'/api/cmdb/ci/{ci_id}/history', d:'CI 변경 이력', a:'yes'},
{m:'GET', p:'/api/cmdb/ci/{ci_id}/relations', d:'CI 관계 목록', a:'yes'},
{m:'POST', p:'/api/cmdb/ci/relations', d:'CI 관계 생성', a:'admin'},
{m:'DELETE', p:'/api/cmdb/ci/relations/{relation_id}', d:'CI 관계 삭제', a:'admin'},
{m:'GET', p:'/api/cmdb/ci/stats', d:'CI 통계', a:'yes'},
{m:'POST', p:'/api/cmdb/ci/import-servers', d:'서버 목록 일괄 임포트', a:'admin'},
{m:'GET', p:'/api/cmdb/servers', d:'서버 자산 목록', a:'yes'},
{m:'POST', p:'/api/cmdb/servers', d:'서버 등록', a:'admin'},
{m:'GET', p:'/api/cmdb/servers/{server_id}', d:'서버 상세 (보안필드 제외)', a:'yes'},
{m:'PATCH', p:'/api/cmdb/servers/{server_id}', d:'서버 정보 수정', a:'admin'},
{m:'GET', p:'/api/cmdb/institutions', d:'기관 목록 (CMDB 뷰)', a:'yes'},
{m:'GET', p:'/api/cmdb/institutions/{inst_code}/servers', d:'기관별 서버 목록', a:'yes'},
]},
{ id:'institutions', label:'기관 관리', tag:'institutions', apis:[
{m:'GET', p:'/api/institutions', d:'기관 목록', a:'yes'},
{m:'POST', p:'/api/institutions', d:'기관 등록', a:'admin'},
{m:'GET', p:'/api/institutions/{inst_code}', d:'기관 상세', a:'yes'},
{m:'PATCH', p:'/api/institutions/{inst_code}', d:'기관 정보 수정', a:'admin'},
{m:'DELETE', p:'/api/institutions/{inst_code}', d:'기관 삭제', a:'admin'},
{m:'GET', p:'/api/institutions/{inst_code}/servers', d:'기관 소속 서버 목록', a:'yes'},
{m:'GET', p:'/api/institutions/{inst_code}/contacts', d:'기관 담당자 목록', a:'yes'},
{m:'POST', p:'/api/institutions/{inst_code}/contacts', d:'담당자 등록', a:'admin'},
{m:'PATCH', p:'/api/institutions/{inst_code}/contacts/{contact_id}', d:'담당자 수정', a:'admin'},
{m:'DELETE', p:'/api/institutions/{inst_code}/contacts/{contact_id}', d:'담당자 삭제', a:'admin'},
]},
{ id:'kb', label:'지식베이스', tag:'kb/kb-agent', apis:[
{m:'GET', p:'/api/kb', d:'KB 전체 검색', a:'yes'},
{m:'GET', p:'/api/kb/list', d:'KB 목록', a:'yes'},
{m:'GET', p:'/api/kb/suggest/{sr_id}', d:'SR 유사 KB 자동 제안', a:'yes'},
{m:'POST', p:'/api/kb-agent/analyze/{sr_id}', d:'SR → KB 자동 생성 분석', a:'yes'},
{m:'GET', p:'/api/kb-agent/candidates', d:'KB 생성 후보 목록', a:'yes'},
{m:'POST', p:'/api/kb-agent/kb', d:'KB 저장', a:'yes'},
{m:'PUT', p:'/api/kb-agent/kb/{kb_id}', d:'KB 수정', a:'yes'},
{m:'POST', p:'/api/kb-agent/run', d:'KB 에이전트 수동 실행', a:'admin'},
{m:'GET', p:'/api/kb-agent/stats', d:'KB 에이전트 통계', a:'yes'},
]},
{ id:'nlcmd', label:'AI 자연어 명령', tag:'nlcmd', apis:[
{m:'POST', p:'/api/nlcmd', d:'자연어 명령 처리 (Ollama sLLM 파싱 → 실행)', a:'yes'},
]},
{ id:'incidents', label:'인시던트', tag:'incidents', apis:[
{m:'GET', p:'/api/incidents', d:'인시던트 목록', a:'yes'},
{m:'POST', p:'/api/incidents', d:'인시던트 생성', a:'yes'},
{m:'GET', p:'/api/incidents/{incident_id}', d:'인시던트 상세', a:'yes'},
{m:'PATCH', p:'/api/incidents/{incident_id}', d:'인시던트 수정', a:'yes'},
{m:'PATCH', p:'/api/incidents/{incident_id}/status', d:'상태 변경', a:'yes'},
{m:'POST', p:'/api/incidents/{incident_id}/auto-rca', d:'AI 자동 RCA 분석', a:'yes'},
{m:'POST', p:'/api/incidents/{incident_id}/close', d:'인시던트 종료', a:'yes'},
{m:'POST', p:'/api/incidents/{incident_id}/link-sr', d:'SR 연계', a:'yes'},
{m:'DELETE', p:'/api/incidents/{incident_id}/link-sr/{sr_id}', d:'SR 연계 해제', a:'yes'},
{m:'GET', p:'/api/incidents/{incident_id}/srs', d:'연계 SR 목록', a:'yes'},
{m:'GET', p:'/api/incidents/stats', d:'인시던트 통계', a:'yes'},
]},
{ id:'problem', label:'문제관리', tag:'problem', apis:[
{m:'GET', p:'/api/problem/', d:'문제 목록', a:'yes'},
{m:'POST', p:'/api/problem/', d:'문제 생성', a:'yes'},
{m:'GET', p:'/api/problem/{prb_id}', d:'문제 상세', a:'yes'},
{m:'PATCH',p:'/api/problem/{prb_id}', d:'문제 수정', a:'yes'},
{m:'POST', p:'/api/problem/{prb_id}/rca', d:'RCA 등록', a:'yes'},
{m:'POST', p:'/api/problem/{prb_id}/auto-rca', d:'AI 자동 RCA', a:'yes'},
{m:'POST', p:'/api/problem/{prb_id}/workaround', d:'임시 해결방법 등록', a:'yes'},
{m:'POST', p:'/api/problem/{prb_id}/resolve', d:'영구 해결 처리', a:'yes'},
{m:'POST', p:'/api/problem/{prb_id}/close', d:'종료', a:'yes'},
{m:'POST', p:'/api/problem/{prb_id}/notes', d:'메모 추가', a:'yes'},
{m:'GET', p:'/api/problem/{prb_id}/notes', d:'메모 목록', a:'yes'},
{m:'GET', p:'/api/problem/known-errors', d:'Known Error DB', a:'yes'},
{m:'GET', p:'/api/problem/stats', d:'통계', a:'yes'},
]},
{ id:'change', label:'변경관리', tag:'change', apis:[
{m:'GET', p:'/api/change/rfc', d:'RFC 목록', a:'yes'},
{m:'POST', p:'/api/change/rfc', d:'RFC 생성', a:'yes'},
{m:'GET', p:'/api/change/rfc/{rfc_id}', d:'RFC 상세', a:'yes'},
{m:'PATCH', p:'/api/change/rfc/{rfc_id}', d:'RFC 수정', a:'yes'},
{m:'POST', p:'/api/change/rfc/{rfc_id}/submit', d:'RFC 제출 (CAB 검토 요청)', a:'yes'},
{m:'POST', p:'/api/change/rfc/{rfc_id}/vote', d:'CAB 투표', a:'yes'},
{m:'GET', p:'/api/change/rfc/{rfc_id}/votes', d:'투표 결과 조회', a:'yes'},
{m:'POST', p:'/api/change/rfc/{rfc_id}/decide', d:'CAB 최종 결정', a:'admin'},
{m:'POST', p:'/api/change/rfc/{rfc_id}/schedule', d:'이행 일정 예약', a:'yes'},
{m:'POST', p:'/api/change/rfc/{rfc_id}/start', d:'이행 시작', a:'yes'},
{m:'POST', p:'/api/change/rfc/{rfc_id}/complete', d:'이행 완료', a:'yes'},
{m:'POST', p:'/api/change/rfc/{rfc_id}/fail', d:'이행 실패/롤백', a:'yes'},
{m:'GET', p:'/api/change/calendar', d:'변경 캘린더', a:'yes'},
{m:'GET', p:'/api/change/freeze', d:'동결 기간 목록', a:'yes'},
{m:'POST', p:'/api/change/freeze', d:'동결 기간 등록', a:'admin'},
{m:'GET', p:'/api/change/freeze/check', d:'현재 동결 여부 확인', a:'yes'},
{m:'DELETE', p:'/api/change/freeze/{freeze_id}', d:'동결 기간 삭제', a:'admin'},
{m:'GET', p:'/api/change/stats', d:'변경 통계', a:'yes'},
]},
{ id:'oncall', label:'온콜', tag:'oncall', apis:[
{m:'GET', p:'/api/oncall', d:'온콜 일정 목록', a:'yes'},
{m:'POST', p:'/api/oncall', d:'온콜 일정 등록', a:'admin'},
{m:'PATCH', p:'/api/oncall/{oncall_id}', d:'온콜 수정', a:'admin'},
{m:'DELETE', p:'/api/oncall/{oncall_id}', d:'온콜 삭제', a:'admin'},
{m:'POST', p:'/api/oncall/bulk', d:'대량 일정 등록', a:'admin'},
{m:'GET', p:'/api/oncall/today', d:'오늘 온콜 담당자', a:'yes'},
{m:'GET', p:'/api/oncall/week', d:'이번 주 온콜 일정', a:'yes'},
{m:'GET', p:'/api/oncall/on-duty', d:'현재 온콜 담당자', a:'yes'},
{m:'POST', p:'/api/oncall/escalate', d:'에스컬레이션 트리거', a:'yes'},
{m:'GET', p:'/api/oncall/rotate/config', d:'순환 설정 조회', a:'yes'},
{m:'PUT', p:'/api/oncall/rotate/config', d:'순환 설정 저장', a:'admin'},
{m:'POST', p:'/api/oncall/rotate/trigger', d:'순환 즉시 실행', a:'admin'},
]},
{ id:'audit', label:'감사로그', tag:'audit', apis:[
{m:'GET', p:'/api/audit', d:'감사 로그 목록 (필터: actor/action/from/to)', a:'admin'},
{m:'GET', p:'/api/audit/{log_id}', d:'로그 단건 조회', a:'admin'},
{m:'POST', p:'/api/audit/record', d:'로그 직접 기록 (내부용)', a:'admin'},
{m:'GET', p:'/api/audit/entity/{entity_type}/{entity_id}', d:'엔티티별 로그', a:'yes'},
{m:'GET', p:'/api/audit/stats', d:'감사 통계', a:'admin'},
{m:'GET', p:'/api/audit/export', d:'로그 내보내기 (CSV)', a:'admin'},
{m:'GET', p:'/api/audit/verify', d:'전체 해시체인 무결성 검증', a:'admin'},
{m:'GET', p:'/api/audit/verify/{from_id}/{to_id}', d:'구간 무결성 검증', a:'admin'},
]},
{ id:'notifications', label:'알림', tag:'notifications', apis:[
{m:'GET', p:'/api/notifications/config', d:'알림 설정 조회', a:'admin'},
{m:'GET', p:'/api/notifications/log', d:'알림 발송 이력', a:'admin'},
{m:'POST', p:'/api/notifications/test-email', d:'이메일 테스트 발송', a:'admin'},
{m:'POST', p:'/api/notifications/test-messenger', d:'메신저 테스트 발송', a:'admin'},
]},
{ id:'timetable', label:'작업 타임테이블', tag:'timetable', apis:[
{m:'GET', p:'/api/timetable', d:'타임테이블 목록', a:'yes'},
{m:'POST', p:'/api/timetable', d:'타임테이블 생성', a:'yes'},
{m:'GET', p:'/api/timetable/{timetable_id}', d:'타임테이블 상세', a:'yes'},
{m:'PATCH', p:'/api/timetable/{timetable_id}', d:'타임테이블 수정', a:'yes'},
{m:'DELETE', p:'/api/timetable/{timetable_id}', d:'타임테이블 삭제', a:'admin'},
{m:'GET', p:'/api/timetable/export/excel', d:'Excel 다운로드 (.xlsx)', a:'yes'},
{m:'GET', p:'/api/timetable/stats', d:'타임테이블 통계', a:'yes'},
]},
{ id:'pm', label:'PM 점검', tag:'pm', apis:[
{m:'GET', p:'/api/pm/templates', d:'점검 템플릿 목록', a:'yes'},
{m:'POST', p:'/api/pm/templates', d:'템플릿 생성', a:'yes'},
{m:'PATCH', p:'/api/pm/templates/{template_id}', d:'템플릿 수정', a:'yes'},
{m:'DELETE', p:'/api/pm/templates/{template_id}', d:'템플릿 삭제', a:'admin'},
{m:'GET', p:'/api/pm/schedules', d:'점검 일정 목록', a:'yes'},
{m:'POST', p:'/api/pm/schedules', d:'일정 등록', a:'yes'},
{m:'PATCH', p:'/api/pm/schedules/{schedule_id}', d:'일정 수정', a:'yes'},
{m:'DELETE', p:'/api/pm/schedules/{schedule_id}', d:'일정 삭제', a:'admin'},
{m:'POST', p:'/api/pm/schedules/{schedule_id}/trigger', d:'점검 즉시 실행', a:'yes'},
{m:'GET', p:'/api/pm/results/{timetable_id}', d:'점검 결과 목록', a:'yes'},
{m:'POST', p:'/api/pm/results/{timetable_id}/init', d:'결과 초기화', a:'yes'},
{m:'PATCH', p:'/api/pm/results/{result_id}', d:'결과 항목 수정', a:'yes'},
{m:'GET', p:'/api/pm/results/{timetable_id}/report', d:'결과 보고서 생성', a:'yes'},
]},
{ id:'ssh', label:'쉘스크립트/SSH', tag:'shell-scripts/ssh', apis:[
{m:'GET', p:'/api/shell-scripts', d:'스크립트 라이브러리 목록', a:'yes'},
{m:'POST', p:'/api/shell-scripts', d:'스크립트 등록', a:'yes'},
{m:'GET', p:'/api/shell-scripts/{script_id}', d:'스크립트 상세', a:'yes'},
{m:'PATCH', p:'/api/shell-scripts/{script_id}', d:'스크립트 수정', a:'yes'},
{m:'DELETE', p:'/api/shell-scripts/{script_id}', d:'스크립트 삭제', a:'admin'},
{m:'POST', p:'/api/shell-scripts/{script_id}/increment-use', d:'사용 횟수 증가', a:'yes'},
{m:'POST', p:'/api/ssh/exec', d:'SSH 명령 직접 실행', a:'yes'},
{m:'POST', p:'/api/ssh/script', d:'SSH 스크립트 실행', a:'yes'},
{m:'GET', p:'/api/ssh/scripts', d:'최근 실행 스크립트 이력', a:'yes'},
]},
{ id:'ssl', label:'SSL 인증서', tag:'ssl', apis:[
{m:'GET', p:'/api/ssl/summary', d:'전체 SSL 현황 요약', a:'yes'},
{m:'GET', p:'/api/ssl/expiring', d:'만료 임박 인증서 목록 (30일 내)', a:'yes'},
{m:'POST', p:'/api/ssl/check/{server_id}', d:'서버 SSL 즉시 점검', a:'yes'},
{m:'GET', p:'/api/ssl/history/{server_id}', d:'인증서 이력', a:'yes'},
{m:'POST', p:'/api/ssl/renew/{server_id}', d:'인증서 갱신 요청', a:'yes'},
{m:'POST', p:'/api/ssl/renew-callback', d:'갱신 완료 콜백 (ACME)', a:'no'},
]},
{ id:'messenger', label:'메신저봇', tag:'messenger', apis:[
{m:'POST', p:'/api/messenger/webhook', d:'메신저 웹훅 수신 (카카오워크/네이버웍스/슬랙)', a:'sig'},
{m:'POST', p:'/api/messenger/bot/command', d:'슬래시 명령 처리 (/sr /status /license /bulk)', a:'sig'},
]},
{ id:'incidents2', label:'인시던트 통계', tag:'', apis:[] },
{ id:'rating', label:'평점', tag:'rating', apis:[
{m:'POST', p:'/api/rating/{sr_id}', d:'SR 완료 후 만족도 평가 (1~5점)', a:'yes'},
{m:'GET', p:'/api/rating/{sr_id}', d:'SR 평가 조회', a:'yes'},
]},
{ id:'batch', label:'배치 작업', tag:'batch', apis:[
{m:'GET', p:'/api/batch/jobs', d:'배치 작업 목록', a:'admin'},
{m:'POST', p:'/api/batch/jobs', d:'배치 작업 등록', a:'admin'},
{m:'GET', p:'/api/batch/jobs/{job_id}', d:'작업 상세', a:'admin'},
{m:'PATCH', p:'/api/batch/jobs/{job_id}', d:'작업 수정', a:'admin'},
{m:'DELETE', p:'/api/batch/jobs/{job_id}', d:'작업 삭제', a:'admin'},
{m:'POST', p:'/api/batch/jobs/{job_id}/run', d:'즉시 실행', a:'admin'},
{m:'POST', p:'/api/batch/jobs/{job_id}/enable', d:'활성화', a:'admin'},
{m:'POST', p:'/api/batch/jobs/{job_id}/disable', d:'비활성화', a:'admin'},
{m:'GET', p:'/api/batch/jobs/{job_id}/runs', d:'실행 이력 목록', a:'admin'},
{m:'GET', p:'/api/batch/runs/{run_id}', d:'실행 결과 상세', a:'admin'},
{m:'GET', p:'/api/batch/runs/{run_id}/stream', d:'실행 로그 스트림 (SSE)', a:'admin'},
]},
{ id:'analytics', label:'분석/통계', tag:'analytics', apis:[
{m:'GET', p:'/api/analytics/sr/trend', d:'SR 발생 추이', a:'yes'},
{m:'GET', p:'/api/analytics/sr/resolution-time', d:'SR 처리 시간 분포', a:'yes'},
{m:'GET', p:'/api/analytics/deploy/summary', d:'배포 요약 통계', a:'yes'},
{m:'GET', p:'/api/analytics/deploy/trend', d:'배포 추이', a:'yes'},
{m:'GET', p:'/api/analytics/deploy/by-project', d:'프로젝트별 배포 통계', a:'yes'},
{m:'GET', p:'/api/analytics/engineer/overview', d:'엔지니어 종합 현황', a:'yes'},
{m:'GET', p:'/api/analytics/engineer/workload', d:'엔지니어 워크로드 상세', a:'yes'},
{m:'GET', p:'/api/analytics/admin/cache/info', d:'캐시 현황', a:'admin'},
{m:'POST', p:'/api/analytics/admin/cache/flush', d:'캐시 초기화', a:'admin'},
{m:'GET', p:'/api/analytics/admin/ratelimit/info', d:'Rate Limit 현황', a:'admin'},
]},
{ id:'anomaly', label:'이상 탐지', tag:'anomaly', apis:[
{m:'POST', p:'/api/anomaly/metrics', d:'메트릭 단건 수집', a:'yes'},
{m:'POST', p:'/api/anomaly/metrics/batch', d:'메트릭 배치 수집', a:'yes'},
{m:'GET', p:'/api/anomaly/metrics/{source}', d:'소스별 메트릭 조회', a:'yes'},
{m:'POST', p:'/api/anomaly/detect', d:'이상 탐지 즉시 실행', a:'yes'},
{m:'GET', p:'/api/anomaly/events', d:'이상 이벤트 목록', a:'yes'},
{m:'GET', p:'/api/anomaly/events/{event_id}', d:'이상 이벤트 상세', a:'yes'},
{m:'PATCH', p:'/api/anomaly/events/{event_id}/acknowledge', d:'이상 확인 처리', a:'yes'},
{m:'PATCH', p:'/api/anomaly/events/{event_id}/resolve', d:'이상 해결 처리', a:'yes'},
{m:'PATCH', p:'/api/anomaly/events/{event_id}/false-positive', d:'오탐 처리', a:'yes'},
{m:'GET', p:'/api/anomaly/rules', d:'탐지 룰 목록', a:'yes'},
{m:'POST', p:'/api/anomaly/rules', d:'룰 등록', a:'admin'},
{m:'DELETE', p:'/api/anomaly/rules/{rule_id}', d:'룰 삭제', a:'admin'},
{m:'PATCH', p:'/api/anomaly/rules/{rule_id}/toggle', d:'룰 활성화/비활성화', a:'admin'},
{m:'POST', p:'/api/anomaly/simulate', d:'이상 탐지 시뮬레이션', a:'admin'},
{m:'GET', p:'/api/anomaly/summary', d:'이상 탐지 요약', a:'yes'},
]},
{ id:'predictive', label:'예측 유지보수', tag:'predictive', apis:[
{m:'POST', p:'/api/predictive/analyze/{source}', d:'소스별 예측 분석 실행', a:'yes'},
{m:'POST', p:'/api/predictive/batch', d:'전체 배치 예측', a:'admin'},
{m:'GET', p:'/api/predictive/health/{source}', d:'소스 헬스 예측', a:'yes'},
{m:'GET', p:'/api/predictive/lifecycle', d:'전체 수명주기 목록', a:'yes'},
{m:'GET', p:'/api/predictive/lifecycle/{source}', d:'소스별 수명 예측', a:'yes'},
{m:'POST', p:'/api/predictive/simulate', d:'예측 시뮬레이션', a:'admin'},
{m:'GET', p:'/api/predictive/stats', d:'예측 통계', a:'yes'},
{m:'GET', p:'/api/predictive/thresholds', d:'임계값 목록', a:'yes'},
{m:'PUT', p:'/api/predictive/thresholds/{metric_type}', d:'임계값 수정', a:'admin'},
]},
{ id:'codereview', label:'코드리뷰', tag:'code-review', apis:[
{m:'POST', p:'/api/code-review', d:'AI 코드리뷰 요청 (Ollama 분석)', a:'yes'},
{m:'GET', p:'/api/code-review/{review_id}', d:'리뷰 결과 조회', a:'yes'},
{m:'GET', p:'/api/code-review/{review_id}/findings', d:'결함 목록', a:'yes'},
{m:'GET', p:'/api/code-review/project/{project_id}', d:'프로젝트 리뷰 이력', a:'yes'},
{m:'GET', p:'/api/code-review/projects/list', d:'리뷰 대상 프로젝트 목록', a:'yes'},
{m:'POST', p:'/api/code-review/quick-scan', d:'빠른 보안 스캔', a:'yes'},
]},
{ id:'chatbot', label:'챗봇', tag:'chatbot', apis:[
{m:'POST', p:'/api/chatbot/message', d:'메시지 전송 (Ollama 응답)', a:'yes'},
{m:'GET', p:'/api/chatbot/sessions', d:'세션 목록', a:'yes'},
{m:'GET', p:'/api/chatbot/sessions/{session_key}', d:'세션 상세', a:'yes'},
{m:'DELETE', p:'/api/chatbot/sessions/{session_key}', d:'세션 삭제', a:'yes'},
{m:'POST', p:'/api/chatbot/sessions/{session_key}/reset', d:'대화 초기화', a:'yes'},
{m:'GET', p:'/api/chatbot/history/{session_key}', d:'대화 이력', a:'yes'},
{m:'GET', p:'/api/chatbot/stats', d:'챗봇 사용 통계', a:'yes'},
]},
{ id:'orchestrator', label:'오케스트레이터', tag:'orchestrator', apis:[
{m:'GET', p:'/api/orchestrator/templates', d:'워크플로우 템플릿 목록', a:'yes'},
{m:'POST', p:'/api/orchestrator/workflows', d:'워크플로우 인스턴스 실행', a:'yes'},
{m:'GET', p:'/api/orchestrator/workflows', d:'실행 중인 워크플로우 목록', a:'yes'},
{m:'GET', p:'/api/orchestrator/workflows/{instance_id}', d:'워크플로우 상태 조회', a:'yes'},
{m:'DELETE', p:'/api/orchestrator/workflows/{instance_id}', d:'워크플로우 취소', a:'admin'},
{m:'POST', p:'/api/orchestrator/workflows/{instance_id}/retry', d:'실패 단계 재시도', a:'yes'},
{m:'GET', p:'/api/orchestrator/stats', d:'오케스트레이터 통계', a:'yes'},
]},
{ id:'learning', label:'학습 루프', tag:'learning', apis:[
{m:'POST', p:'/api/learning/feedback', d:'처리결과 피드백 등록', a:'yes'},
{m:'GET', p:'/api/learning/feedback', d:'피드백 목록', a:'yes'},
{m:'POST', p:'/api/learning/feedback/{feedback_id}/check', d:'피드백 처리 확인', a:'yes'},
{m:'POST', p:'/api/learning/detect-recurrence', d:'재발 패턴 탐지', a:'yes'},
{m:'GET', p:'/api/learning/patterns', d:'학습된 패턴 목록', a:'yes'},
{m:'GET', p:'/api/learning/patterns/{pattern_id}', d:'패턴 상세', a:'yes'},
{m:'GET', p:'/api/learning/stats', d:'학습 통계', a:'yes'},
{m:'GET', p:'/api/learning/thresholds', d:'적응형 임계값 목록', a:'yes'},
{m:'POST', p:'/api/learning/thresholds/calibrate', d:'특정 임계값 보정', a:'admin'},
{m:'POST', p:'/api/learning/thresholds/calibrate-all', d:'전체 임계값 보정', a:'admin'},
{m:'POST', p:'/api/learning/thresholds/outcome', d:'임계값 결과 기록', a:'yes'},
{m:'POST', p:'/api/learning/thresholds/missed', d:'놓친 탐지 기록', a:'yes'},
{m:'GET', p:'/api/learning/lessons', d:'학습 교훈 목록', a:'yes'},
{m:'POST', p:'/api/learning/mine', d:'패턴 마이닝 즉시 실행', a:'admin'},
]},
{ id:'license', label:'라이선스', tag:'license', apis:[
{m:'GET', p:'/api/license/status', d:'라이선스 현황', a:'yes'},
{m:'POST', p:'/api/license/activate', d:'라이선스 키 활성화', a:'admin'},
{m:'POST', p:'/api/license/trial', d:'체험판 키 발급 (days 파라미터)', a:'no'},
{m:'POST', p:'/api/license/verify', d:'라이선스 키 검증', a:'no'},
{m:'GET', p:'/api/license/history', d:'라이선스 이력', a:'admin'},
{m:'DELETE', p:'/api/license/', d:'라이선스 초기화', a:'admin'},
]},
{ id:'si-projects', label:'SI 프로젝트', tag:'si-projects', apis:[
{m:'GET', p:'/api/si/projects', d:'SI 프로젝트 목록', a:'yes'},
{m:'POST', p:'/api/si/projects', d:'프로젝트 생성', a:'yes'},
{m:'GET', p:'/api/si/projects/{project_id}', d:'프로젝트 상세', a:'yes'},
{m:'PATCH', p:'/api/si/projects/{project_id}', d:'프로젝트 수정', a:'yes'},
{m:'DELETE', p:'/api/si/projects/{project_id}', d:'프로젝트 삭제', a:'admin'},
{m:'PATCH', p:'/api/si/projects/{project_id}/phase', d:'단계 전환', a:'yes'},
{m:'GET', p:'/api/si/projects/{project_id}/summary', d:'프로젝트 요약 대시보드', a:'yes'},
{m:'GET', p:'/api/si/projects/{project_id}/checklist', d:'완료 체크리스트', a:'yes'},
{m:'PATCH', p:'/api/si/projects/{project_id}/checklist/{checklist_id}', d:'체크리스트 항목 업데이트', a:'yes'},
{m:'GET', p:'/api/si/projects/{project_id}/rtm', d:'요구사항 추적 매트릭스', a:'yes'},
{m:'POST', p:'/api/si/projects/{project_id}/convert-to-sm', d:'SI → SM 전환', a:'yes'},
]},
{ id:'si-wbs', label:'SI WBS', tag:'si-wbs', apis:[
{m:'GET', p:'/api/si/projects/{project_id}/wbs', d:'WBS 목록', a:'yes'},
{m:'POST', p:'/api/si/projects/{project_id}/wbs', d:'WBS 항목 생성', a:'yes'},
{m:'POST', p:'/api/si/projects/{project_id}/wbs/bulk', d:'WBS 대량 등록', a:'yes'},
{m:'GET', p:'/api/si/projects/{project_id}/wbs/gantt', d:'Gantt 차트 데이터', a:'yes'},
{m:'GET', p:'/api/si/projects/{project_id}/wbs/{item_id}', d:'WBS 항목 상세', a:'yes'},
{m:'PATCH', p:'/api/si/projects/{project_id}/wbs/{item_id}', d:'WBS 항목 수정', a:'yes'},
{m:'DELETE', p:'/api/si/projects/{project_id}/wbs/{item_id}', d:'WBS 항목 삭제', a:'yes'},
{m:'PATCH', p:'/api/si/projects/{project_id}/wbs/{item_id}/progress', d:'진척률 업데이트', a:'yes'},
]},
{ id:'si-tests', label:'SI 테스트', tag:'si-tests', apis:[
{m:'GET', p:'/api/si/projects/{project_id}/test-plans', d:'테스트 계획 목록', a:'yes'},
{m:'POST', p:'/api/si/projects/{project_id}/test-plans', d:'계획 생성', a:'yes'},
{m:'GET', p:'/api/si/projects/{project_id}/test-plans/{plan_id}', d:'계획 상세', a:'yes'},
{m:'PATCH', p:'/api/si/projects/{project_id}/test-plans/{plan_id}', d:'계획 수정', a:'yes'},
{m:'DELETE', p:'/api/si/projects/{project_id}/test-plans/{plan_id}', d:'계획 삭제', a:'yes'},
{m:'GET', p:'/api/si/projects/{project_id}/test-plans/{plan_id}/cases', d:'테스트 케이스 목록', a:'yes'},
{m:'POST', p:'/api/si/projects/{project_id}/test-plans/{plan_id}/cases', d:'케이스 등록', a:'yes'},
{m:'PATCH', p:'/api/si/projects/{project_id}/test-plans/{plan_id}/cases/{tc_id}', d:'케이스 수정', a:'yes'},
{m:'DELETE', p:'/api/si/projects/{project_id}/test-plans/{plan_id}/cases/{tc_id}', d:'케이스 삭제', a:'yes'},
{m:'POST', p:'/api/si/test-cases/{tc_id}/execute', d:'테스트 실행', a:'yes'},
{m:'GET', p:'/api/si/test-cases/{tc_id}/executions', d:'실행 이력', a:'yes'},
{m:'GET', p:'/api/si/projects/{project_id}/defects', d:'결함 목록', a:'yes'},
{m:'POST', p:'/api/si/projects/{project_id}/defects', d:'결함 등록', a:'yes'},
{m:'GET', p:'/api/si/projects/{project_id}/defects/stats', d:'결함 통계', a:'yes'},
{m:'GET', p:'/api/si/projects/{project_id}/defects/{defect_id}', d:'결함 상세', a:'yes'},
{m:'PATCH', p:'/api/si/projects/{project_id}/defects/{defect_id}', d:'결함 수정', a:'yes'},
{m:'PATCH', p:'/api/si/projects/{project_id}/defects/{defect_id}/fix', d:'수정 완료 처리', a:'yes'},
{m:'PATCH', p:'/api/si/projects/{project_id}/defects/{defect_id}/verify', d:'검증 완료 처리', a:'yes'},
]},
{ id:'pam', label:'PAM 특권접근', tag:'pam', apis:[
{m:'POST', p:'/api/pam/sessions', d:'특권 세션 요청', a:'yes'},
{m:'GET', p:'/api/pam/sessions', d:'세션 목록', a:'admin'},
{m:'GET', p:'/api/pam/sessions/{session_id}', d:'세션 상세', a:'yes'},
{m:'POST', p:'/api/pam/sessions/{session_id}/approve', d:'세션 승인', a:'yes'},
{m:'POST', p:'/api/pam/sessions/{session_id}/reject', d:'세션 거절', a:'yes'},
{m:'POST', p:'/api/pam/sessions/{session_id}/checkout', d:'자격증명 체크아웃', a:'yes'},
{m:'POST', p:'/api/pam/sessions/{session_id}/checkin', d:'자격증명 반납', a:'yes'},
{m:'POST', p:'/api/pam/sessions/{session_id}/execute', d:'명령 실행 (기록됨)', a:'yes'},
{m:'GET', p:'/api/pam/sessions/{session_id}/commands', d:'명령 실행 이력', a:'admin'},
{m:'POST', p:'/api/pam/sessions/{session_id}/terminate', d:'세션 강제 종료', a:'admin'},
{m:'GET', p:'/api/pam/policies', d:'PAM 정책 목록', a:'admin'},
{m:'GET', p:'/api/pam/stats', d:'PAM 통계', a:'admin'},
]},
{ id:'ldap', label:'LDAP', tag:'ldap', apis:[
{m:'GET', p:'/api/ldap/config', d:'LDAP 설정 조회', a:'admin'},
{m:'PUT', p:'/api/ldap/config', d:'LDAP 설정 저장', a:'admin'},
{m:'GET', p:'/api/ldap/status', d:'LDAP 연결 상태', a:'admin'},
{m:'POST', p:'/api/ldap/test', d:'LDAP 연결 테스트', a:'admin'},
{m:'POST', p:'/api/ldap/authenticate', d:'LDAP 인증 테스트', a:'admin'},
{m:'GET', p:'/api/ldap/users', d:'LDAP 사용자 목록', a:'admin'},
{m:'POST', p:'/api/ldap/sync/{username}', d:'사용자 동기화', a:'admin'},
{m:'GET', p:'/api/ldap/group-map', d:'그룹-역할 매핑 조회', a:'admin'},
{m:'PUT', p:'/api/ldap/group-map', d:'그룹-역할 매핑 저장', a:'admin'},
]},
{ id:'vuln', label:'취약점 스캔', tag:'vuln_scan', apis:[
{m:'POST', p:'/api/vuln/scan', d:'취약점 스캔 실행', a:'yes'},
{m:'GET', p:'/api/vuln/scans', d:'스캔 결과 목록', a:'yes'},
{m:'GET', p:'/api/vuln/scans/{scan_id}', d:'스캔 결과 상세', a:'yes'},
{m:'POST', p:'/api/vuln/scans/{scan_id}/patch', d:'패치 적용 처리', a:'yes'},
{m:'GET', p:'/api/vuln/cve/{cve_id}', d:'CVE 상세 조회', a:'yes'},
{m:'POST', p:'/api/vuln/cvss', d:'CVSS 점수 계산', a:'yes'},
{m:'GET', p:'/api/vuln/patches', d:'패치 목록', a:'yes'},
{m:'GET', p:'/api/vuln/patch-stats', d:'패치 통계', a:'yes'},
{m:'GET', p:'/api/vuln/overdue-patches', d:'기한 초과 패치 목록', a:'yes'},
{m:'GET', p:'/api/vuln/policies', d:'패치 정책 목록', a:'yes'},
{m:'POST', p:'/api/vuln/quick-check', d:'빠른 보안 체크', a:'yes'},
{m:'GET', p:'/api/vuln/stats', d:'취약점 통계', a:'yes'},
]},
{ id:'compliance', label:'컴플라이언스', tag:'compliance', apis:[
{m:'GET', p:'/api/compliance/rules', d:'컴플라이언스 룰 목록', a:'yes'},
{m:'POST', p:'/api/compliance/scan', d:'전체 스캔 실행', a:'admin'},
{m:'POST', p:'/api/compliance/scan/file', d:'파일 업로드 스캔', a:'admin'},
{m:'GET', p:'/api/compliance/results', d:'스캔 결과 목록', a:'yes'},
{m:'GET', p:'/api/compliance/report/html', d:'HTML 보고서', a:'yes'},
{m:'GET', p:'/api/compliance/report/excel', d:'Excel 보고서 다운로드', a:'yes'},
]},
{ id:'vibe', label:'Vibe CD 배포', tag:'vibe', apis:[
{m:'GET', p:'/api/vibe', d:'배포 세션 목록', a:'yes'},
{m:'POST', p:'/api/vibe', d:'배포 세션 생성', a:'yes'},
{m:'GET', p:'/api/vibe/{session_id}', d:'세션 상세', a:'yes'},
{m:'PATCH', p:'/api/vibe/{session_id}/status', d:'세션 상태 변경', a:'yes'},
{m:'POST', p:'/api/vibe/{session_id}/build', d:'빌드 실행 (Jenkins)', a:'yes'},
{m:'GET', p:'/api/vibe/{session_id}/build/stream', d:'빌드 로그 스트림 (SSE)', a:'yes'},
{m:'POST', p:'/api/vibe/{session_id}/impact-analysis', d:'AI 영향 분석', a:'yes'},
{m:'POST', p:'/api/vibe/{session_id}/request-approval', d:'PM 승인 요청', a:'yes'},
{m:'GET', p:'/api/vibe/{session_id}/approval-status', d:'승인 상태 조회', a:'yes'},
{m:'PATCH', p:'/api/vibe/{session_id}/approve', d:'승인 처리', a:'yes'},
{m:'PATCH', p:'/api/vibe/{session_id}/reject', d:'거절 처리', a:'yes'},
{m:'POST', p:'/api/vibe/{session_id}/deploy', d:'배포 실행', a:'yes'},
{m:'POST', p:'/api/vibe/sonar-result', d:'SonarQube 결과 수신', a:'no'},
{m:'POST', p:'/api/vibe/callback', d:'배포 완료 콜백', a:'no'},
{m:'GET', p:'/api/vibe/jenkins/health', d:'Jenkins 연결 상태', a:'yes'},
]},
{ id:'admin', label:'관리자', tag:'admin', apis:[
{m:'GET', p:'/api/admin/about', d:'버전/빌드/오픈소스 정보 (GS인증 요건)', a:'no'},
{m:'GET', p:'/api/admin/health', d:'종합 헬스체크 (DB/Ollama/디스크/라이선스)', a:'no'},
{m:'POST', p:'/api/admin/backup', d:'DB+설정+업로드 ZIP 백업', a:'admin'},
{m:'GET', p:'/api/admin/backups', d:'백업 파일 목록', a:'admin'},
{m:'GET', p:'/api/admin/backups/{filename}/download', d:'백업 다운로드', a:'admin'},
{m:'POST', p:'/api/admin/restore/{filename}', d:'백업 복원', a:'admin'},
{m:'GET', p:'/api/admin/errors/codes', d:'17개 에러코드 및 해결방법', a:'no'},
]},
{ id:'onboarding', label:'온보딩', tag:'onboarding', apis:[
{m:'GET', p:'/api/onboarding/steps', d:'8단계 온보딩 목록', a:'yes'},
{m:'GET', p:'/api/onboarding/status', d:'현재 사용자 진행 상태', a:'yes'},
{m:'POST', p:'/api/onboarding/step', d:'단계 진행', a:'yes'},
{m:'POST', p:'/api/onboarding/complete', d:'온보딩 완료 처리', a:'yes'},
{m:'POST', p:'/api/onboarding/dismiss', d:'온보딩 건너뛰기', a:'yes'},
{m:'POST', p:'/api/onboarding/reset', d:'온보딩 초기화', a:'admin'},
{m:'POST', p:'/api/onboarding/message', d:'Ollama AI 온보딩 답변', a:'yes'},
]},
{ id:'portal', label:'포털', tag:'portal', apis:[
{m:'GET', p:'/api/portal/stats', d:'포털 통계', a:'yes'},
{m:'GET', p:'/api/portal/announcements', d:'공지사항 목록', a:'yes'},
{m:'GET', p:'/api/portal/catalog', d:'서비스 카탈로그 (고객뷰)', a:'yes'},
{m:'POST', p:'/api/portal/sr', d:'고객 SR 접수', a:'yes'},
{m:'GET', p:'/api/portal/sr', d:'내 SR 목록', a:'yes'},
{m:'GET', p:'/api/portal/sr/{sr_id}', d:'내 SR 상세', a:'yes'},
{m:'POST', p:'/api/portal/sr/{sr_id}/rate', d:'SR 만족도 평가', a:'yes'},
{m:'POST', p:'/api/portal/faq/suggest', d:'FAQ 제안 (AI 답변)', a:'yes'},
]},
{ id:'agents', label:'AI 에이전트', tag:'agents', apis:[
{m:'GET', p:'/api/agents', d:'에이전트 목록', a:'admin'},
{m:'POST', p:'/api/agents', d:'에이전트 생성', a:'admin'},
{m:'GET', p:'/api/agents/{agent_id}', d:'에이전트 상세', a:'admin'},
{m:'PATCH', p:'/api/agents/{agent_id}', d:'에이전트 수정', a:'admin'},
{m:'DELETE', p:'/api/agents/{agent_id}', d:'에이전트 삭제', a:'admin'},
{m:'POST', p:'/api/agents/{agent_id}/pause', d:'에이전트 일시정지', a:'admin'},
{m:'POST', p:'/api/agents/{agent_id}/resume', d:'에이전트 재개', a:'admin'},
{m:'POST', p:'/api/agents/{agent_id}/heartbeat', d:'하트비트 갱신', a:'yes'},
{m:'GET', p:'/api/agents/{agent_id}/tasks', d:'에이전트 작업 목록', a:'yes'},
{m:'POST', p:'/api/agents/{agent_id}/tasks', d:'에이전트 작업 생성', a:'admin'},
{m:'GET', p:'/api/agents/{agent_id}/messages', d:'에이전트 메시지', a:'yes'},
{m:'POST', p:'/api/agents/{agent_id}/messages', d:'에이전트 메시지 발송', a:'yes'},
{m:'PATCH', p:'/api/agents/messages/{msg_id}/read', d:'메시지 읽음 처리', a:'yes'},
{m:'GET', p:'/api/agents/approvals', d:'에이전트 승인 목록', a:'yes'},
{m:'PATCH', p:'/api/agents/approvals/{approval_id}/review', d:'에이전트 승인 처리', a:'yes'},
{m:'GET', p:'/api/agents/orgchart', d:'에이전트 조직도', a:'yes'},
{m:'GET', p:'/api/agents/stats', d:'에이전트 통계', a:'yes'},
{m:'GET', p:'/api/agents/llm/health', d:'LLM(Ollama) 헬스', a:'admin'},
{m:'POST', p:'/api/agents/llm/pull', d:'LLM 모델 다운로드', a:'admin'},
{m:'POST', p:'/api/agents/finetune/start', d:'LLM 파인튜닝 시작', a:'admin'},
{m:'GET', p:'/api/agents/finetune/status', d:'파인튜닝 상태', a:'admin'},
]},
{ id:'gateway', label:'게이트웨이/외부연동', tag:'gateway', apis:[
{m:'GET', p:'/api/gateway/integrations', d:'외부 연동 목록', a:'admin'},
{m:'POST', p:'/api/gateway/integrations', d:'연동 등록', a:'admin'},
{m:'GET', p:'/api/gateway/integrations/{int_id}', d:'연동 상세', a:'admin'},
{m:'PUT', p:'/api/gateway/integrations/{int_id}', d:'연동 수정', a:'admin'},
{m:'DELETE', p:'/api/gateway/integrations/{int_id}', d:'연동 삭제', a:'admin'},
{m:'POST', p:'/api/gateway/integrations/{int_id}/test', d:'연동 테스트', a:'admin'},
{m:'POST', p:'/api/gateway/send/{int_id}', d:'메시지 발송', a:'yes'},
{m:'POST', p:'/api/gateway/webhook/{webhook_key}', d:'외부 웹훅 수신', a:'no'},
{m:'GET', p:'/api/gateway/logs', d:'연동 로그', a:'admin'},
{m:'GET', p:'/api/gateway/stats', d:'연동 통계', a:'admin'},
{m:'GET', p:'/api/gateway/jira/projects', d:'Jira 프로젝트 목록', a:'yes'},
{m:'POST', p:'/api/gateway/jira/sync/{sr_id}', d:'SR → Jira 동기화', a:'yes'},
{m:'GET', p:'/api/gateway/jira/status/{jira_key}', d:'Jira 이슈 상태 조회', a:'yes'},
{m:'POST', p:'/api/gateway/confluence/publish', d:'Confluence 페이지 발행', a:'yes'},
]},
{ id:'misc-ops', label:'운영 기타', tag:'various', apis:[
{m:'GET', p:'/api/metrics/prometheus', d:'Prometheus 메트릭 (text/plain)', a:'no'},
{m:'GET', p:'/api/metrics/health', d:'헬스 메트릭', a:'no'},
{m:'GET', p:'/api/metrics/summary', d:'메트릭 요약', a:'yes'},
{m:'POST', p:'/api/metrics/query', d:'커스텀 메트릭 쿼리', a:'yes'},
{m:'GET', p:'/api/topology/graph', d:'전체 인프라 위상도 (D3.js)', a:'yes'},
{m:'GET', p:'/api/topology/graph/{ci_id}', d:'CI 중심 위상도', a:'yes'},
{m:'GET', p:'/api/topology/health', d:'위상도 헬스 현황', a:'yes'},
{m:'GET', p:'/api/timeline', d:'전체 변경 타임라인', a:'yes'},
{m:'GET', p:'/api/timeline/summary', d:'타임라인 요약', a:'yes'},
{m:'POST', p:'/api/ws/broadcast', d:'특정 채널 브로드캐스트', a:'admin'},
{m:'GET', p:'/api/ws/status', d:'WebSocket 서버 상태', a:'yes'},
{m:'GET', p:'/api/push/vapid-key', d:'Web Push VAPID 공개키', a:'no'},
{m:'POST', p:'/api/push/subscribe', d:'브라우저 구독 등록', a:'yes'},
{m:'DELETE',p:'/api/push/subscribe', d:'구독 해제', a:'yes'},
{m:'POST', p:'/api/push/test', d:'테스트 푸시 발송', a:'yes'},
{m:'GET', p:'/api/public/status', d:'시스템 상태 페이지', a:'no'},
{m:'GET', p:'/api/public/checklist', d:'운영 체크리스트 목록', a:'no'},
{m:'POST', p:'/api/public/checklist/{check_id}/check', d:'체크리스트 항목 확인', a:'yes'},
{m:'GET', p:'/api/scouter/status', d:'Scouter 연결 상태', a:'yes'},
{m:'GET', p:'/api/scouter/servers', d:'모니터링 서버 목록', a:'yes'},
{m:'POST', p:'/api/scouter/agent/deploy', d:'Scouter 에이전트 배포', a:'yes'},
{m:'GET', p:'/api/report/list', d:'보고서 목록', a:'yes'},
{m:'GET', p:'/api/report/generate', d:'보고서 즉시 생성', a:'yes'},
{m:'POST', p:'/api/report/schedule', d:'보고서 자동 발송 예약', a:'yes'},
{m:'GET', p:'/api/finops/summary', d:'비용 요약', a:'yes'},
{m:'GET', p:'/api/finops/recommendations', d:'비용 절감 권고', a:'yes'},
{m:'GET', p:'/api/capacity/dashboard', d:'용량 대시보드', a:'yes'},
{m:'GET', p:'/api/capacity/alerts', d:'용량 경보 목록', a:'yes'},
{m:'POST', p:'/api/tenants', d:'테넌트 생성', a:'admin'},
{m:'GET', p:'/api/tenants', d:'테넌트 목록', a:'admin'},
{m:'GET', p:'/api/tenants/current', d:'현재 테넌트 정보', a:'yes'},
{m:'GET', p:'/api/infra/k8s/nodes', d:'K8s 노드 목록', a:'yes'},
{m:'GET', p:'/api/infra/k8s/pods', d:'K8s 파드 목록', a:'yes'},
{m:'POST', p:'/api/infra/zero-trust/verify', d:'Zero Trust 접근 검증', a:'yes'},
{m:'POST', p:'/api/groupware/send-approval', d:'전자결재 요청 발송', a:'yes'},
{m:'GET', p:'/api/catalog/', d:'서비스 카탈로그 목록', a:'yes'},
{m:'POST', p:'/api/catalog/{service_id}/request', d:'서비스 셀프 요청 (SR 자동 생성)', a:'yes'},
{m:'POST', p:'/api/siem/events', d:'보안 이벤트 단건 수집', a:'sig'},
{m:'POST', p:'/api/siem/events/batch', d:'이벤트 배치 수집', a:'sig'},
{m:'GET', p:'/api/perf/results', d:'성능 테스트 결과 목록', a:'yes'},
{m:'POST', p:'/api/perf/run', d:'부하 테스트 실행', a:'yes'},
{m:'GET', p:'/api/si/projects/{pid}/report/daily', d:'SI 일일 보고서', a:'yes'},
{m:'GET', p:'/api/portfolio/dashboard', d:'포트폴리오 대시보드', a:'yes'},
{m:'GET', p:'/api/portfolio/kpi', d:'KPI 현황', a:'yes'},
]},
];
// Remove empty dummy entries
const SECTIONS = TAGS.filter(t => t.apis && t.apis.length > 0);
// ============================================================
// RENDER
// ============================================================
let activeMethod = 'ALL';
let activeAuth = null;
let searchQ = '';
function authLabel(a) {
if (a === 'no') return '<span class="auth-badge auth-no">인증불필요</span>';
if (a === 'sig') return '<span class="auth-badge auth-sig">서명검증</span>';
if (a === 'admin') return '<span class="auth-badge auth-admin">admin</span>';
return '<span class="auth-badge auth-yes">필요</span>';
}
function highlightPath(p, q) {
const parts = p.split(/(\{[^}]+\})/);
let html = parts.map(pt =>
pt.startsWith('{') ? `<span class="path-param">${pt}</span>` : pt
).join('');
if (q) {
const re = new RegExp(`(${q.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')})`, 'gi');
html = html.replace(re, '<mark style="background:#fbbf2440;color:#fbbf24">$1</mark>');
}
return html;
}
function buildNav() {
const nav = document.getElementById('nav-list');
const groups = [
{ title:'ITSM 핵심', ids:['auth','dashboard','tasks','attachments','work','approvals','assign','incidents','problem','change','oncall','audit','rating'] },
{ title:'CMDB / 자산', ids:['cmdb','institutions'] },
{ title:'AI / 자동화', ids:['nlcmd','anomaly','predictive','agents','orchestrator','learning','chatbot','kb'] },
{ title:'SI 프로젝트', ids:['si-projects','si-wbs','si-tests'] },
{ title:'보안', ids:['pam','ldap','vuln','compliance'] },
{ title:'운영', ids:['ssh','ssl','timetable','pm','batch','analytics','vibe','codereview'] },
{ title:'플랫폼', ids:['messenger','gateway','license','onboarding','portal'] },
{ title:'기타', ids:['admin','misc-ops','notifications'] },
];
groups.forEach(g => {
const matching = SECTIONS.filter(s => g.ids.includes(s.id));
if (!matching.length) return;
const section = document.createElement('div');
section.className = 'nav-section';
section.innerHTML = `<div class="nav-group-title">${g.title}</div>` +
matching.map(s => `
<a class="nav-item" id="nav-${s.id}" onclick="scrollTo('${s.id}')" href="javascript:void(0)">
<span class="nav-item-name">${s.label}</span>
<span class="nav-count">${s.apis.length}</span>
</a>
`).join('');
nav.appendChild(section);
});
}
function buildFeatureTables() {
const container = document.getElementById('feature-tables');
container.innerHTML = FEATURES.map(cat => `
<div class="feature-section">
<h3>${cat.cat}</h3>
<table class="feature-table">
<thead><tr><th style="width:36px">#</th><th>기능</th><th>주요 기능</th><th style="width:60px;text-align:right">API</th></tr></thead>
<tbody>
${cat.items.map(f => `
<tr>
<td class="feature-num">${f.n}</td>
<td style="font-weight:500">${f.name}</td>
<td style="color:var(--text-muted)">${f.desc}</td>
<td class="api-cnt">${f.cnt}</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
`).join('');
}
function buildApiSections() {
const container = document.getElementById('api-sections');
container.innerHTML = SECTIONS.map((sec, i) => `
<div class="section" id="sec-${sec.id}">
<div class="section-header ${i > 0 ? 'collapsed':''}" onclick="toggleSection(this)">
<span class="section-title">${sec.label}</span>
<span class="section-tag">${sec.tag}</span>
<span class="section-count">${sec.apis.length} APIs</span>
<span class="chevron">▼</span>
</div>
<div class="section-body">
<table class="api-table">
<thead>
<tr>
<th style="width:80px">Method</th>
<th>Path</th>
<th>설명</th>
<th style="width:100px">인증</th>
</tr>
</thead>
<tbody>
${sec.apis.map(api => `
<tr class="api-row"
data-method="${api.m}"
data-auth="${api.a}"
data-path="${api.p.toLowerCase()}"
data-desc="${api.d}">
<td><span class="method ${api.m}">${api.m}</span></td>
<td>
<div class="path-cell">
<span class="path-text">${highlightPath(api.p, '')}</span>
<button class="copy-btn" onclick="copyPath('${api.p}')" title="복사">⎘</button>
</div>
</td>
<td><span class="desc-text">${api.d}</span></td>
<td>${authLabel(api.a)}</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</div>
`).join('');
}
// ============================================================
// INTERACTIONS
// ============================================================
function switchTab(name) {
document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.getElementById('tab-'+name).classList.add('active');
event.target.classList.add('active');
}
function toggleSection(header) {
header.classList.toggle('collapsed');
}
function scrollTo(id) {
// Switch to API tab first
document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.getElementById('tab-api').classList.add('active');
document.querySelectorAll('.tab')[1].classList.add('active');
setTimeout(() => {
const el = document.getElementById('sec-' + id);
if (el) {
const header = el.querySelector('.section-header');
if (header && header.classList.contains('collapsed')) header.classList.remove('collapsed');
el.scrollIntoView({ behavior:'smooth', block:'start' });
}
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
const navEl = document.getElementById('nav-' + id);
if (navEl) navEl.classList.add('active');
}, 50);
}
function filterMethod(method, btn) {
activeMethod = method;
document.querySelectorAll('.filter-btn:not(#btn-noauth)').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
applyFilter();
}
function filterAuth(type, btn) {
if (activeAuth === type) {
activeAuth = null;
btn.classList.remove('active');
} else {
activeAuth = type;
btn.classList.add('active');
}
applyFilter();
}
function applyFilter() {
const q = searchQ.toLowerCase();
let total = 0, shown = 0;
document.querySelectorAll('.api-row').forEach(row => {
const m = row.dataset.method;
const a = row.dataset.auth;
const p = row.dataset.path;
const d = row.dataset.desc.toLowerCase();
const methodOk = activeMethod === 'ALL' || m === activeMethod;
const authOk = !activeAuth || (activeAuth === 'no-auth' && a === 'no');
const searchOk = !q || p.includes(q) || d.includes(q);
const visible = methodOk && authOk && searchOk;
row.classList.toggle('hidden', !visible);
total++;
if (visible) shown++;
// highlight path
if (q) {
const pathEl = row.querySelector('.path-text');
const rawPath = row.dataset.path.replace(/^\/api/, '/api'); // get original
// rebuild with original case
const apis_flat = SECTIONS.flatMap(s => s.apis);
const orig = apis_flat.find(a => a.p.toLowerCase() === row.dataset.path);
if (orig) pathEl.innerHTML = highlightPath(orig.p, q);
} else {
const pathEl = row.querySelector('.path-text');
const orig = SECTIONS.flatMap(s => s.apis).find(a => a.p.toLowerCase() === row.dataset.path);
if (orig) pathEl.innerHTML = highlightPath(orig.p, '');
}
});
document.getElementById('stat-total').textContent = shown + ' APIs';
document.getElementById('stat-shown').textContent = '표시중';
const noResult = document.getElementById('no-result');
noResult.style.display = shown === 0 ? 'block' : 'none';
}
function copyPath(path) {
navigator.clipboard.writeText(path).catch(() => {});
const toast = document.getElementById('toast');
toast.classList.add('show');
setTimeout(() => toast.classList.remove('show'), 1500);
}
// Search
document.getElementById('search').addEventListener('input', function() {
searchQ = this.value.trim();
// Switch to API tab
document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.getElementById('tab-api').classList.add('active');
document.querySelectorAll('.tab')[1].classList.add('active');
// Expand all if searching
if (searchQ) {
document.querySelectorAll('.section-header').forEach(h => h.classList.remove('collapsed'));
}
applyFilter();
});
// Scroll to top
window.addEventListener('scroll', () => {
document.getElementById('scroll-top').classList.toggle('visible', window.scrollY > 300);
});
// ============================================================
// INIT
// ============================================================
buildNav();
buildFeatureTables();
buildApiSections();
</script>
</body>
</html>