import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import type { ServiceInfo, ServiceApi, CreateServiceRequest, UpdateServiceRequest, } from '../../types/service'; import { getServices, createService, updateService, deleteService, getServiceApis, } from '../../services/serviceService'; import Button from '../../components/ui/Button'; import Badge from '../../components/ui/Badge'; const HEALTH_DOT: Record = { UP: 'bg-green-500', DOWN: 'bg-red-500', UNKNOWN: 'bg-gray-400', }; const METHOD_COLOR: Record = { GET: 'bg-green-100 text-green-800', POST: 'bg-blue-100 text-blue-800', PUT: 'bg-orange-100 text-orange-800', DELETE: 'bg-red-100 text-red-800', }; const formatRelativeTime = (dateStr: string | null): string => { if (!dateStr) return '-'; const diff = Date.now() - new Date(dateStr).getTime(); const seconds = Math.floor(diff / 1000); if (seconds < 60) return `${seconds}초 전`; const minutes = Math.floor(seconds / 60); if (minutes < 60) return `${minutes}분 전`; const hours = Math.floor(minutes / 60); if (hours < 24) return `${hours}시간 전`; const days = Math.floor(hours / 24); return `${days}일 전`; }; const ServicesPage = () => { const navigate = useNavigate(); const [services, setServices] = useState([]); const [selectedService, setSelectedService] = useState(null); const [serviceApis, setServiceApis] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [isServiceModalOpen, setIsServiceModalOpen] = useState(false); const [editingService, setEditingService] = useState(null); const [serviceCode, setServiceCode] = useState(''); const [serviceName, setServiceName] = useState(''); const [serviceUrl, setServiceUrl] = useState(''); const [serviceDescription, setServiceDescription] = useState(''); const [healthCheckUrl, setHealthCheckUrl] = useState(''); const [healthCheckInterval, setHealthCheckInterval] = useState(60); const [serviceIsActive, setServiceIsActive] = useState(true); const fetchServices = async () => { try { setLoading(true); const res = await getServices(); if (res.success && res.data) { setServices(res.data); } else { setError(res.message || '서비스 목록을 불러오는데 실패했습니다.'); } } catch { setError('서비스 목록을 불러오는데 실패했습니다.'); } finally { setLoading(false); } }; const fetchApis = async (serviceId: number) => { try { const res = await getServiceApis(serviceId); if (res.success && res.data) { setServiceApis(res.data); } } catch { setServiceApis([]); } }; useEffect(() => { fetchServices(); }, []); const handleSelectService = (service: ServiceInfo) => { if (selectedService?.serviceId === service.serviceId) { setSelectedService(null); setServiceApis([]); } else { setSelectedService(service); fetchApis(service.serviceId); } }; const handleOpenCreateService = () => { setEditingService(null); setServiceCode(''); setServiceName(''); setServiceUrl(''); setServiceDescription(''); setHealthCheckUrl(''); setHealthCheckInterval(60); setServiceIsActive(true); setIsServiceModalOpen(true); }; const handleOpenEditService = (service: ServiceInfo) => { setEditingService(service); setServiceCode(service.serviceCode); setServiceName(service.serviceName); setServiceUrl(service.serviceUrl || ''); setServiceDescription(service.description || ''); setHealthCheckUrl(service.healthCheckUrl || ''); setHealthCheckInterval(service.healthCheckInterval); setServiceIsActive(service.isActive); setIsServiceModalOpen(true); }; const handleCloseServiceModal = () => { setIsServiceModalOpen(false); setEditingService(null); setError(null); }; const handleServiceSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(null); try { if (editingService) { const req: UpdateServiceRequest = { serviceName, serviceUrl: serviceUrl || undefined, description: serviceDescription || undefined, healthCheckUrl: healthCheckUrl || undefined, healthCheckInterval, isActive: serviceIsActive, }; const res = await updateService(editingService.serviceId, req); if (!res.success) { setError(res.message || '서비스 수정에 실패했습니다.'); return; } } else { const req: CreateServiceRequest = { serviceCode, serviceName, serviceUrl: serviceUrl || undefined, description: serviceDescription || undefined, healthCheckUrl: healthCheckUrl || undefined, healthCheckInterval, }; const res = await createService(req); if (!res.success) { setError(res.message || '서비스 생성에 실패했습니다.'); return; } } handleCloseServiceModal(); await fetchServices(); } catch { setError(editingService ? '서비스 수정에 실패했습니다.' : '서비스 생성에 실패했습니다.'); } }; const handleDeleteService = async (service: ServiceInfo) => { if (!window.confirm(`'${service.serviceName}' 서비스를 삭제하시겠습니까?`)) return; try { const res = await deleteService(service.serviceId); if (!res.success) { setError(res.message || '서비스 삭제에 실패했습니다.'); return; } if (selectedService?.serviceId === service.serviceId) { setSelectedService(null); setServiceApis([]); } await fetchServices(); } catch { setError('서비스 삭제에 실패했습니다.'); } }; if (loading) { return
로딩 중...
; } return (

Services

{error && !isServiceModalOpen && (
{error}
)}
{services.map((service) => { const dot = HEALTH_DOT[service.healthStatus] ?? HEALTH_DOT.UNKNOWN; const healthVariant = service.healthStatus === 'UP' ? 'success' : service.healthStatus === 'DOWN' ? 'danger' : 'default'; const isSelected = selectedService?.serviceId === service.serviceId; return ( handleSelectService(service)} className={`cursor-pointer ${ isSelected ? 'bg-[var(--color-primary-subtle)]' : 'hover:bg-[var(--color-bg-base)]' }`} > ); })} {services.length === 0 && ( )}
Code Name URL Health Status Response Time Last Checked Active Actions
{service.serviceCode} {service.serviceName} {service.serviceUrl || '-'} {service.healthStatus} {service.healthResponseTime != null ? `${service.healthResponseTime}ms` : '-'} {formatRelativeTime(service.healthCheckedAt)} {service.isActive ? 'Active' : 'Inactive'}
등록된 서비스가 없습니다.
{selectedService && (

APIs for {selectedService.serviceName}

{serviceApis.map((api) => ( ))} {serviceApis.length === 0 && ( )}
Method Path Name Domain Section Description Active
{api.apiMethod} {api.apiPath} {api.apiName} {api.apiDomain || '-'} {api.apiSection || '-'} {api.description || '-'} {api.isActive ? 'Active' : 'Inactive'}
등록된 API가 없습니다.
)} {isServiceModalOpen && (

{editingService ? '서비스 수정' : '서비스 생성'}

{error && (
{error}
)}
setServiceCode(e.target.value)} disabled={!!editingService} required className="w-full border border-[var(--color-border-strong)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] rounded-lg px-3 py-2 focus:ring-2 focus:ring-[var(--color-primary)] focus:outline-none disabled:opacity-50" />
setServiceName(e.target.value)} required className="w-full border border-[var(--color-border-strong)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] rounded-lg px-3 py-2 focus:ring-2 focus:ring-[var(--color-primary)] focus:outline-none" />
setServiceUrl(e.target.value)} className="w-full border border-[var(--color-border-strong)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] rounded-lg px-3 py-2 focus:ring-2 focus:ring-[var(--color-primary)] focus:outline-none" />