---
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`) 를 거쳐 호출한다.
