--- 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( url: string, options: { immediate?: boolean; deps?: unknown[] } = {} ) { const { immediate = true, deps = [] } = options; const [data, setData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const fetch = useCallback(async () => { setLoading(true); setError(null); try { const res = await guardiaApi.get(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`) 를 거쳐 호출한다.