guardia-manager/.claude/skills/manager-integration/SKILL.md
DESKTOP-TKLFCPRython 10cc76d6e6 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

204 lines
5.6 KiB
Markdown

---
name: manager-integration
description: >
GUARDiA Manager와 외부 시스템(GUARDiA ITSM, Gitea, Ollama) 연동 코드를 구현하는 스킬.
API 클라이언트, TypeScript 타입, React 훅을 생성한다.
트리거: API 연동 구현, axios 클라이언트 작성, GUARDiA API 호출, Gitea 연동,
Ollama 모델 조회, 데이터 페칭 훅 작성 요청 시.
---
# GUARDiA Manager 연동 구현 스킬
## 연동 대상 API 목록
### GUARDiA ITSM (http://zioinfo.co.kr:8001)
| 엔드포인트 | 용도 | 필요 권한 |
|-----------|------|---------|
| `POST /api/auth/login` | 로그인 | 없음 |
| `GET /api/dashboard` | 대시보드 통계 | 로그인 |
| `GET /api/tasks` | SR 목록 | 로그인 |
| `GET /api/incidents` | 인시던트 목록 | 로그인 |
| `GET /api/cmdb/servers` | 서버 자산 목록 | 로그인 |
| `GET /api/tenant` | 테넌트 목록 | admin |
| `GET /api/external/keys` | API Key 목록 | admin |
| `GET /api/audit` | 감사 로그 | admin |
| `GET /api/metrics` | Prometheus 메트릭 | 로그인 |
### Gitea (http://zioinfo.co.kr:3000/api/v1)
| 엔드포인트 | 용도 |
|-----------|------|
| `GET /repos/search` | 저장소 목록 |
| `GET /repos/{user}/{repo}/commits` | 최신 커밋 |
| `GET /repos/{user}/{repo}/hooks` | 웹훅 목록 |
### Ollama (http://localhost:11434)
| 엔드포인트 | 용도 |
|-----------|------|
| `GET /api/tags` | 설치된 모델 목록 |
| `GET /api/ps` | 로드된 모델 |
---
## API 클라이언트 구현
### guardiaClient.ts
```typescript
// frontend/src/api/guardiaClient.ts
import axios from 'axios';
const BASE = import.meta.env.VITE_GUARDIA_API ?? 'http://zioinfo.co.kr:8001';
export const guardiaApi = axios.create({ baseURL: BASE });
// 요청 인터셉터: JWT 자동 주입
guardiaApi.interceptors.request.use((config) => {
const token = sessionStorage.getItem('guardia_token');
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
});
// 응답 인터셉터: 401 → 로그인 리다이렉트
guardiaApi.interceptors.response.use(
res => res,
err => {
if (err.response?.status === 401) {
sessionStorage.removeItem('guardia_token');
window.location.href = '/login';
}
return Promise.reject(err);
}
);
```
### types.ts — 핵심 타입 정의
```typescript
// frontend/src/api/types.ts
export interface User {
id: number;
username: string;
display_name: string;
role: 'admin' | 'pm' | 'engineer' | 'customer';
is_active: boolean;
}
export interface Server {
id: number;
hostname: string;
ip_addr: string; // ServerOut 스키마에서 제외됨 — 실제 응답에 없을 수 있음
os_type: string;
status: 'running' | 'stopped' | 'error' | 'unknown';
inst_id?: number;
}
export interface SRRequest {
id: number;
sr_id: string;
title: string;
status: string;
priority: 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW';
requested_by: string;
created_at: string;
}
export interface DashboardStats {
total_sr: number;
open_sr: number;
critical_incidents: number;
sla_achievement: number;
server_count: number;
running_servers: number;
}
export interface APIKey {
id: number;
name: string;
scopes: string;
is_active: boolean;
use_count: number;
last_used_at: string | null;
expires_at: string | null;
}
export interface AuditLog {
id: number;
action: string;
entity_type: string;
entity_id: string;
username: string;
ip_hash: string;
created_at: string;
}
export interface SystemResources {
cpu_percent: number;
memory: { total_gb: number; used_gb: number; percent: number };
disk: { total_gb: number; used_gb: number; percent: number };
}
export interface ServiceStatus {
[serviceName: string]: 'active' | 'inactive' | 'failed' | 'unknown';
}
```
### React 훅 — useGuardiaApi.ts
```typescript
// frontend/src/hooks/useGuardiaApi.ts
import { useState, useEffect, useCallback } from 'react';
import { guardiaApi } from '../api/guardiaClient';
export function useGuardiaApi<T>(
url: string,
options: { immediate?: boolean; deps?: unknown[] } = {}
) {
const { immediate = true, deps = [] } = options;
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetch = useCallback(async () => {
setLoading(true); setError(null);
try {
const res = await guardiaApi.get<T>(url);
setData(res.data);
} catch (e: any) {
setError(e.response?.data?.detail ?? '오류가 발생했습니다.');
} finally {
setLoading(false);
}
}, [url]);
useEffect(() => { if (immediate) fetch(); }, [fetch, immediate, ...deps]);
return { data, loading, error, refetch: fetch };
}
```
---
## 환경변수 설정
```typescript
// frontend/src/config/env.ts
export const ENV = {
GUARDIA_API: import.meta.env.VITE_GUARDIA_API ?? 'http://zioinfo.co.kr:8001',
MANAGER_API: import.meta.env.VITE_MANAGER_API ?? 'http://localhost:8002',
GITEA_API: import.meta.env.VITE_GITEA_API ?? 'http://zioinfo.co.kr:3000/api/v1',
GITEA_USER: import.meta.env.VITE_GITEA_USER ?? 'zio',
} as const;
```
---
## 주의사항
- **ServerOut 스키마**: GUARDiA ITSM은 보안 정책으로 `ip_addr`, `ssh_user`, `os_pw_enc` 필드를 API 응답에서 제외한다. 이 필드를 프론트엔드에 표시하려면 ITSM 쪽에 별도 admin 엔드포인트가 필요하다.
- **인증 토큰 저장**: `localStorage` 금지. `sessionStorage` 또는 메모리 변수 사용.
- **Gitea CORS**: Gitea API를 직접 호출하면 CORS 오류 발생 가능. Manager Backend의 프록시 라우터(`/api/proxy/gitea`) 를 거쳐 호출한다.