2025-09-24 17:11:35 +09:00
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html lang="ko">
|
|
|
|
|
<head>
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
|
<title>REST API 관리 대시보드</title>
|
|
|
|
|
<link rel="stylesheet" href="/css/style.css">
|
|
|
|
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
<!-- 로딩 스피너 -->
|
|
|
|
|
<div id="loading" class="loading-overlay">
|
|
|
|
|
<div class="spinner"></div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 로그인 화면 -->
|
|
|
|
|
<div id="loginScreen" class="login-screen">
|
|
|
|
|
<div class="login-container">
|
|
|
|
|
<div class="login-header">
|
|
|
|
|
<h1><i class="fas fa-server"></i> REST API 관리</h1>
|
|
|
|
|
<p>API 키 발급 및 관리 시스템</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="login-tabs">
|
|
|
|
|
<button class="tab-btn active" onclick="showLoginTab()">로그인</button>
|
|
|
|
|
<button class="tab-btn" onclick="showRegisterTab()">회원가입</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 로그인 폼 -->
|
|
|
|
|
<form id="loginForm" class="auth-form">
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label for="loginUsername">사용자명</label>
|
|
|
|
|
<input type="text" id="loginUsername" required>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label for="loginPassword">비밀번호</label>
|
|
|
|
|
<input type="password" id="loginPassword" required>
|
|
|
|
|
</div>
|
|
|
|
|
<button type="submit" class="btn btn-primary">
|
|
|
|
|
<i class="fas fa-sign-in-alt"></i> 로그인
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
|
|
|
|
|
|
|
|
|
<!-- 회원가입 폼 -->
|
|
|
|
|
<form id="registerForm" class="auth-form" style="display: none;">
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label for="registerUsername">사용자명</label>
|
|
|
|
|
<input type="text" id="registerUsername" required>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label for="registerEmail">이메일</label>
|
|
|
|
|
<input type="email" id="registerEmail" required>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label for="registerPassword">비밀번호</label>
|
|
|
|
|
<input type="password" id="registerPassword" required minlength="8">
|
|
|
|
|
<small>최소 8자 이상</small>
|
|
|
|
|
</div>
|
|
|
|
|
<button type="submit" class="btn btn-primary">
|
|
|
|
|
<i class="fas fa-user-plus"></i> 회원가입
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
|
|
|
|
|
|
|
|
|
<div class="login-footer">
|
|
|
|
|
<p>기본 관리자 계정: admin / admin123!</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 메인 대시보드 -->
|
|
|
|
|
<div id="dashboard" class="dashboard" style="display: none;">
|
|
|
|
|
<!-- 헤더 -->
|
|
|
|
|
<header class="header">
|
|
|
|
|
<div class="header-left">
|
|
|
|
|
<h1><i class="fas fa-server"></i> REST API 대시보드</h1>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="header-right">
|
|
|
|
|
<span class="user-info">
|
|
|
|
|
<i class="fas fa-user"></i>
|
|
|
|
|
<span id="currentUser"></span>
|
|
|
|
|
<span id="userRole" class="role-badge"></span>
|
|
|
|
|
</span>
|
|
|
|
|
<button id="logoutBtn" class="btn btn-secondary">
|
|
|
|
|
<i class="fas fa-sign-out-alt"></i> 로그아웃
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
<!-- 사이드바 -->
|
|
|
|
|
<nav class="sidebar">
|
|
|
|
|
<ul class="nav-menu">
|
|
|
|
|
<li><a href="#" class="nav-link active" data-section="overview">
|
|
|
|
|
<i class="fas fa-chart-dashboard"></i> 개요
|
|
|
|
|
</a></li>
|
|
|
|
|
<li><a href="#" class="nav-link" data-section="api-keys">
|
|
|
|
|
<i class="fas fa-key"></i> API 키 관리
|
|
|
|
|
</a></li>
|
|
|
|
|
<li><a href="#" class="nav-link" data-section="data-management">
|
|
|
|
|
<i class="fas fa-database"></i> 데이터 관리
|
|
|
|
|
</a></li>
|
|
|
|
|
<li><a href="#" class="nav-link" data-section="api-docs">
|
|
|
|
|
<i class="fas fa-book"></i> API 문서
|
|
|
|
|
</a></li>
|
|
|
|
|
<li><a href="#" class="nav-link" data-section="api-tester">
|
|
|
|
|
<i class="fas fa-play-circle"></i> API 테스터
|
|
|
|
|
</a></li>
|
|
|
|
|
<li class="admin-only"><a href="#" class="nav-link" data-section="users">
|
|
|
|
|
<i class="fas fa-users"></i> 사용자 관리
|
|
|
|
|
</a></li>
|
|
|
|
|
<li class="admin-only"><a href="#" class="nav-link" data-section="api-logs">
|
|
|
|
|
<i class="fas fa-history"></i> API 로그
|
|
|
|
|
</a></li>
|
|
|
|
|
<li class="admin-only"><a href="#" class="nav-link" data-section="monitoring">
|
|
|
|
|
<i class="fas fa-chart-line"></i> 모니터링
|
|
|
|
|
</a></li>
|
|
|
|
|
</ul>
|
|
|
|
|
</nav>
|
|
|
|
|
|
|
|
|
|
<!-- 메인 콘텐츠 -->
|
|
|
|
|
<main class="main-content">
|
|
|
|
|
<!-- 개요 섹션 -->
|
|
|
|
|
<section id="overview-section" class="content-section active">
|
|
|
|
|
<h2>대시보드 개요</h2>
|
|
|
|
|
<div class="stats-grid">
|
|
|
|
|
<div class="stat-card">
|
|
|
|
|
<div class="stat-icon">
|
|
|
|
|
<i class="fas fa-key"></i>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-info">
|
|
|
|
|
<h3 id="totalKeys">-</h3>
|
|
|
|
|
<p>총 API 키</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-card">
|
|
|
|
|
<div class="stat-icon">
|
|
|
|
|
<i class="fas fa-chart-line"></i>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-info">
|
|
|
|
|
<h3 id="totalRequests">-</h3>
|
|
|
|
|
<p>총 요청 수</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-card admin-only">
|
|
|
|
|
<div class="stat-icon">
|
|
|
|
|
<i class="fas fa-users"></i>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-info">
|
|
|
|
|
<h3 id="totalUsers">-</h3>
|
|
|
|
|
<p>총 사용자</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-card">
|
|
|
|
|
<div class="stat-icon">
|
|
|
|
|
<i class="fas fa-clock"></i>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-info">
|
|
|
|
|
<h3 id="activeKeysToday">-</h3>
|
|
|
|
|
<p>오늘 활성 키</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="quick-actions">
|
|
|
|
|
<h3>빠른 작업</h3>
|
|
|
|
|
<div class="action-buttons">
|
|
|
|
|
<button class="btn btn-primary" onclick="showSection('api-keys')">
|
|
|
|
|
<i class="fas fa-plus"></i> API 키 생성
|
|
|
|
|
</button>
|
|
|
|
|
<button class="btn btn-secondary" onclick="showSection('data-management')">
|
|
|
|
|
<i class="fas fa-database"></i> 데이터 관리
|
|
|
|
|
</button>
|
|
|
|
|
<button class="btn btn-info" onclick="testApiConnection()">
|
|
|
|
|
<i class="fas fa-plug"></i> API 연결 테스트
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- API 키 관리 섹션 -->
|
|
|
|
|
<section id="api-keys-section" class="content-section api-keys-section">
|
|
|
|
|
<div class="section-header">
|
|
|
|
|
<h2>API 키 관리</h2>
|
|
|
|
|
<div class="header-actions">
|
|
|
|
|
<button class="copy-all-keys-btn" onclick="copyAllApiKeys()" title="모든 API 키 복사">
|
|
|
|
|
<i class="fas fa-copy"></i> 모든 키 복사
|
|
|
|
|
</button>
|
|
|
|
|
<button class="btn btn-primary" onclick="showCreateKeyModal()">
|
|
|
|
|
<i class="fas fa-plus"></i> 새 API 키 생성
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="api-keys-list">
|
|
|
|
|
<div class="table-container">
|
|
|
|
|
<table id="apiKeysTable" class="data-table">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th>키 이름</th>
|
|
|
|
|
<th>API 키</th>
|
|
|
|
|
<th>권한</th>
|
|
|
|
|
<th>사용 횟수</th>
|
|
|
|
|
<th>마지막 사용</th>
|
|
|
|
|
<th>생성일</th>
|
|
|
|
|
<th>상태</th>
|
|
|
|
|
<th>작업</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
<!-- API 키 목록이 여기에 동적으로 추가됩니다 -->
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- 데이터 관리 섹션 -->
|
|
|
|
|
<section id="data-management-section" class="content-section">
|
|
|
|
|
<div class="section-header">
|
|
|
|
|
<h2>데이터 관리</h2>
|
|
|
|
|
<button class="btn btn-primary" onclick="showCreateDataModal()">
|
|
|
|
|
<i class="fas fa-plus"></i> 새 데이터 추가
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="info-box">
|
|
|
|
|
<h4><i class="fas fa-info-circle"></i> 데이터 관리 기능 안내</h4>
|
|
|
|
|
<p><strong>데이터 추가</strong>는 <code>API_DATA</code> 테이블에 샘플 데이터를 저장하는 기능입니다.</p>
|
|
|
|
|
<ul>
|
|
|
|
|
<li><strong>용도</strong>: REST API 테스트용 데이터 생성</li>
|
|
|
|
|
<li><strong>API 엔드포인트</strong>: <code>GET/POST/PUT/DELETE /api/data</code></li>
|
|
|
|
|
<li><strong>데이터 구조</strong>: 이름, 설명, 데이터 값, 생성/수정 시간</li>
|
|
|
|
|
<li><strong>활용</strong>: 외부 시스템에서 API 키를 사용하여 이 데이터에 접근 가능</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="data-list">
|
|
|
|
|
<div class="table-container">
|
|
|
|
|
<table id="dataTable" class="data-table">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th>ID</th>
|
|
|
|
|
<th>이름</th>
|
|
|
|
|
<th>설명</th>
|
|
|
|
|
<th>데이터 값</th>
|
|
|
|
|
<th>생성일</th>
|
|
|
|
|
<th>수정일</th>
|
|
|
|
|
<th>작업</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
<!-- 데이터 목록이 여기에 동적으로 추가됩니다 -->
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- API 문서 섹션 -->
|
|
|
|
|
<section id="api-docs-section" class="content-section">
|
|
|
|
|
<h2>API 문서</h2>
|
|
|
|
|
<div class="api-docs">
|
|
|
|
|
<!-- API 키 사용법 -->
|
|
|
|
|
<div class="endpoint-group">
|
|
|
|
|
<h3>🔑 API 키 사용법</h3>
|
|
|
|
|
<div class="api-usage-info">
|
|
|
|
|
<p><strong>모든 데이터 API 요청에는 API 키가 필요합니다.</strong></p>
|
|
|
|
|
<div class="usage-methods">
|
|
|
|
|
<h4>방법 1: HTTP 헤더 (권장)</h4>
|
|
|
|
|
<pre><code>X-API-Key: ak_your_api_key_here</code></pre>
|
|
|
|
|
|
|
|
|
|
<h4>방법 2: 쿼리 파라미터</h4>
|
|
|
|
|
<pre><code>GET /api/data?api_key=ak_your_api_key_here</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 인증 API -->
|
|
|
|
|
<div class="endpoint-group">
|
|
|
|
|
<h3>🔐 인증 API</h3>
|
|
|
|
|
<div class="endpoint">
|
|
|
|
|
<div class="endpoint-header">
|
|
|
|
|
<span class="method post">POST</span>
|
|
|
|
|
<span class="path">/auth/login</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="endpoint-description">
|
|
|
|
|
<p>사용자 로그인</p>
|
|
|
|
|
<pre><code>{
|
|
|
|
|
"username": "사용자명",
|
|
|
|
|
"password": "비밀번호"
|
|
|
|
|
}</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="endpoint">
|
|
|
|
|
<div class="endpoint-header">
|
|
|
|
|
<span class="method post">POST</span>
|
|
|
|
|
<span class="path">/auth/api-keys</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="endpoint-description">
|
|
|
|
|
<p>API 키 생성 (JWT 토큰 필요)</p>
|
|
|
|
|
<pre><code>{
|
|
|
|
|
"keyName": "키 이름",
|
|
|
|
|
"permissions": ["read", "write", "delete"]
|
|
|
|
|
}</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- USER_INFO_ORA 테이블 API -->
|
|
|
|
|
<div class="endpoint-group">
|
|
|
|
|
<h3>👤 사용자 정보 API (USER_INFO_ORA)</h3>
|
|
|
|
|
|
|
|
|
|
<!-- 조회 -->
|
|
|
|
|
<div class="endpoint">
|
|
|
|
|
<div class="endpoint-header">
|
|
|
|
|
<span class="method get">GET</span>
|
|
|
|
|
<span class="path">/api/users</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="endpoint-description">
|
|
|
|
|
<p>모든 사용자 조회</p>
|
|
|
|
|
<p><strong>헤더:</strong> <code>X-API-Key: your-api-key</code></p>
|
|
|
|
|
<p><strong>응답 예시:</strong></p>
|
|
|
|
|
<pre><code>[
|
|
|
|
|
{
|
|
|
|
|
"USER_ID": "user001",
|
|
|
|
|
"USER_NAME": "홍길동",
|
|
|
|
|
"DEPT_CODE": "IT001",
|
|
|
|
|
"REG_DATE": "2024-01-15"
|
|
|
|
|
}
|
|
|
|
|
]</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 단일 조회 -->
|
|
|
|
|
<div class="endpoint">
|
|
|
|
|
<div class="endpoint-header">
|
|
|
|
|
<span class="method get">GET</span>
|
|
|
|
|
<span class="path">/api/users/{userId}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="endpoint-description">
|
|
|
|
|
<p>특정 사용자 조회</p>
|
|
|
|
|
<p><strong>헤더:</strong> <code>X-API-Key: your-api-key</code></p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 생성 -->
|
|
|
|
|
<div class="endpoint">
|
|
|
|
|
<div class="endpoint-header">
|
|
|
|
|
<span class="method post">POST</span>
|
|
|
|
|
<span class="path">/api/users</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="endpoint-description">
|
|
|
|
|
<p>새 사용자 생성</p>
|
|
|
|
|
<p><strong>헤더:</strong> <code>X-API-Key: your-api-key</code></p>
|
|
|
|
|
<p><strong>요청 본문:</strong></p>
|
|
|
|
|
<pre><code>{
|
|
|
|
|
"USER_ID": "user002",
|
|
|
|
|
"USER_NAME": "김철수",
|
|
|
|
|
"DEPT_CODE": "HR001"
|
|
|
|
|
}</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 수정 -->
|
|
|
|
|
<div class="endpoint">
|
|
|
|
|
<div class="endpoint-header">
|
|
|
|
|
<span class="method put">PUT</span>
|
|
|
|
|
<span class="path">/api/users/{userId}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="endpoint-description">
|
|
|
|
|
<p>사용자 정보 수정</p>
|
|
|
|
|
<p><strong>헤더:</strong> <code>X-API-Key: your-api-key</code></p>
|
|
|
|
|
<p><strong>요청 본문:</strong></p>
|
|
|
|
|
<pre><code>{
|
|
|
|
|
"USER_NAME": "김철수(수정)",
|
|
|
|
|
"DEPT_CODE": "IT002"
|
|
|
|
|
}</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 삭제 -->
|
|
|
|
|
<div class="endpoint">
|
|
|
|
|
<div class="endpoint-header">
|
|
|
|
|
<span class="method delete">DELETE</span>
|
|
|
|
|
<span class="path">/api/users/{userId}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="endpoint-description">
|
|
|
|
|
<p>사용자 삭제</p>
|
|
|
|
|
<p><strong>헤더:</strong> <code>X-API-Key: your-api-key</code></p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 언어별 사용 예시 -->
|
|
|
|
|
<div class="endpoint-group">
|
|
|
|
|
<h3>💻 언어별 사용 예시</h3>
|
|
|
|
|
|
|
|
|
|
<!-- Java 예시 -->
|
|
|
|
|
<div class="code-example">
|
|
|
|
|
<h4><i class="fab fa-java"></i> Java (Spring Boot)</h4>
|
|
|
|
|
<div class="code-tabs">
|
|
|
|
|
<div class="tab-buttons">
|
|
|
|
|
<button class="tab-btn active" onclick="showTab('java-insert')">INSERT</button>
|
|
|
|
|
<button class="tab-btn" onclick="showTab('java-select')">SELECT</button>
|
|
|
|
|
<button class="tab-btn" onclick="showTab('java-update')">UPDATE</button>
|
|
|
|
|
<button class="tab-btn" onclick="showTab('java-delete')">DELETE</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div id="java-insert" class="tab-content active">
|
|
|
|
|
<pre><code>// RestTemplate 사용
|
|
|
|
|
@Service
|
|
|
|
|
public class UserService {
|
|
|
|
|
|
|
|
|
|
private static final String API_KEY = "ak_your_api_key_here";
|
|
|
|
|
private static final String BASE_URL = "http://localhost:5577/api";
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
private RestTemplate restTemplate;
|
|
|
|
|
|
|
|
|
|
public ResponseEntity<String> createUser(UserDto user) {
|
|
|
|
|
HttpHeaders headers = new HttpHeaders();
|
|
|
|
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
|
|
|
|
headers.set("X-API-Key", API_KEY);
|
|
|
|
|
|
|
|
|
|
HttpEntity<UserDto> request = new HttpEntity<>(user, headers);
|
|
|
|
|
|
|
|
|
|
return restTemplate.postForEntity(
|
|
|
|
|
BASE_URL + "/users",
|
|
|
|
|
request,
|
|
|
|
|
String.class
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DTO 클래스
|
|
|
|
|
public class UserDto {
|
|
|
|
|
private String USER_ID;
|
|
|
|
|
private String USER_NAME;
|
|
|
|
|
private String DEPT_CODE;
|
|
|
|
|
|
|
|
|
|
// getters and setters
|
|
|
|
|
}</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div id="java-select" class="tab-content">
|
|
|
|
|
<pre><code>// 모든 사용자 조회
|
|
|
|
|
public List<UserDto> getAllUsers() {
|
|
|
|
|
HttpHeaders headers = new HttpHeaders();
|
|
|
|
|
headers.set("X-API-Key", API_KEY);
|
|
|
|
|
|
|
|
|
|
HttpEntity<String> entity = new HttpEntity<>(headers);
|
|
|
|
|
|
|
|
|
|
ResponseEntity<UserDto[]> response = restTemplate.exchange(
|
|
|
|
|
BASE_URL + "/users",
|
|
|
|
|
HttpMethod.GET,
|
|
|
|
|
entity,
|
|
|
|
|
UserDto[].class
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return Arrays.asList(response.getBody());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 특정 사용자 조회
|
|
|
|
|
public UserDto getUserById(String userId) {
|
|
|
|
|
HttpHeaders headers = new HttpHeaders();
|
|
|
|
|
headers.set("X-API-Key", API_KEY);
|
|
|
|
|
|
|
|
|
|
HttpEntity<String> entity = new HttpEntity<>(headers);
|
|
|
|
|
|
|
|
|
|
return restTemplate.exchange(
|
|
|
|
|
BASE_URL + "/users/" + userId,
|
|
|
|
|
HttpMethod.GET,
|
|
|
|
|
entity,
|
|
|
|
|
UserDto.class
|
|
|
|
|
).getBody();
|
|
|
|
|
}</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div id="java-update" class="tab-content">
|
|
|
|
|
<pre><code>// 사용자 정보 수정
|
|
|
|
|
public ResponseEntity<String> updateUser(String userId, UserDto user) {
|
|
|
|
|
HttpHeaders headers = new HttpHeaders();
|
|
|
|
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
|
|
|
|
headers.set("X-API-Key", API_KEY);
|
|
|
|
|
|
|
|
|
|
HttpEntity<UserDto> request = new HttpEntity<>(user, headers);
|
|
|
|
|
|
|
|
|
|
return restTemplate.exchange(
|
|
|
|
|
BASE_URL + "/users/" + userId,
|
|
|
|
|
HttpMethod.PUT,
|
|
|
|
|
request,
|
|
|
|
|
String.class
|
|
|
|
|
);
|
|
|
|
|
}</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div id="java-delete" class="tab-content">
|
|
|
|
|
<pre><code>// 사용자 삭제
|
|
|
|
|
public ResponseEntity<String> deleteUser(String userId) {
|
|
|
|
|
HttpHeaders headers = new HttpHeaders();
|
|
|
|
|
headers.set("X-API-Key", API_KEY);
|
|
|
|
|
|
|
|
|
|
HttpEntity<String> entity = new HttpEntity<>(headers);
|
|
|
|
|
|
|
|
|
|
return restTemplate.exchange(
|
|
|
|
|
BASE_URL + "/users/" + userId,
|
|
|
|
|
HttpMethod.DELETE,
|
|
|
|
|
entity,
|
|
|
|
|
String.class
|
|
|
|
|
);
|
|
|
|
|
}</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- React 예시 -->
|
|
|
|
|
<div class="code-example">
|
|
|
|
|
<h4><i class="fab fa-react"></i> React (JavaScript)</h4>
|
|
|
|
|
<div class="code-tabs">
|
|
|
|
|
<div class="tab-buttons">
|
|
|
|
|
<button class="tab-btn active" onclick="showTab('react-insert')">INSERT</button>
|
|
|
|
|
<button class="tab-btn" onclick="showTab('react-select')">SELECT</button>
|
|
|
|
|
<button class="tab-btn" onclick="showTab('react-update')">UPDATE</button>
|
|
|
|
|
<button class="tab-btn" onclick="showTab('react-delete')">DELETE</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div id="react-insert" class="tab-content active">
|
|
|
|
|
<pre><code>// API 설정
|
|
|
|
|
const API_KEY = 'ak_your_api_key_here';
|
|
|
|
|
const BASE_URL = 'http://localhost:5577/api';
|
|
|
|
|
|
|
|
|
|
// 사용자 생성
|
|
|
|
|
const createUser = async (userData) => {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`${BASE_URL}/users`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
'X-API-Key': API_KEY
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify(userData)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new Error('사용자 생성 실패');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return await response.json();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error:', error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// React 컴포넌트에서 사용
|
|
|
|
|
const UserForm = () => {
|
|
|
|
|
const handleSubmit = async (e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
|
|
|
|
const userData = {
|
|
|
|
|
USER_ID: 'user003',
|
|
|
|
|
USER_NAME: '이영희',
|
|
|
|
|
DEPT_CODE: 'SALES001'
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await createUser(userData);
|
|
|
|
|
alert('사용자가 생성되었습니다!');
|
|
|
|
|
} catch (error) {
|
|
|
|
|
alert('생성 실패: ' + error.message);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<form onSubmit={handleSubmit}>
|
|
|
|
|
{/* 폼 내용 */}
|
|
|
|
|
</form>
|
|
|
|
|
);
|
|
|
|
|
};</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div id="react-select" class="tab-content">
|
|
|
|
|
<pre><code>// 모든 사용자 조회
|
|
|
|
|
const getAllUsers = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`${BASE_URL}/users`, {
|
|
|
|
|
method: 'GET',
|
|
|
|
|
headers: {
|
|
|
|
|
'X-API-Key': API_KEY
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new Error('사용자 조회 실패');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return await response.json();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error:', error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// React Hook 사용
|
|
|
|
|
const UserList = () => {
|
|
|
|
|
const [users, setUsers] = useState([]);
|
|
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const fetchUsers = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const userData = await getAllUsers();
|
|
|
|
|
setUsers(userData);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('사용자 로드 실패:', error);
|
|
|
|
|
} finally {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
fetchUsers();
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
if (loading) return <div>로딩 중...</div>;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<ul>
|
|
|
|
|
{users.map(user => (
|
|
|
|
|
<li key={user.USER_ID}>
|
|
|
|
|
{user.USER_NAME} ({user.DEPT_CODE})
|
|
|
|
|
</li>
|
|
|
|
|
))}
|
|
|
|
|
</ul>
|
|
|
|
|
);
|
|
|
|
|
};</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div id="react-update" class="tab-content">
|
|
|
|
|
<pre><code>// 사용자 정보 수정
|
|
|
|
|
const updateUser = async (userId, userData) => {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`${BASE_URL}/users/${userId}`, {
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
'X-API-Key': API_KEY
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify(userData)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new Error('사용자 수정 실패');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return await response.json();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error:', error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 사용 예시
|
|
|
|
|
const handleUpdate = async () => {
|
|
|
|
|
const updatedData = {
|
|
|
|
|
USER_NAME: '이영희(수정)',
|
|
|
|
|
DEPT_CODE: 'IT003'
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await updateUser('user003', updatedData);
|
|
|
|
|
alert('사용자 정보가 수정되었습니다!');
|
|
|
|
|
} catch (error) {
|
|
|
|
|
alert('수정 실패: ' + error.message);
|
|
|
|
|
}
|
|
|
|
|
};</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div id="react-delete" class="tab-content">
|
|
|
|
|
<pre><code>// 사용자 삭제
|
|
|
|
|
const deleteUser = async (userId) => {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`${BASE_URL}/users/${userId}`, {
|
|
|
|
|
method: 'DELETE',
|
|
|
|
|
headers: {
|
|
|
|
|
'X-API-Key': API_KEY
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new Error('사용자 삭제 실패');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return await response.json();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error:', error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 삭제 확인 후 실행
|
|
|
|
|
const handleDelete = async (userId) => {
|
|
|
|
|
if (window.confirm('정말 삭제하시겠습니까?')) {
|
|
|
|
|
try {
|
|
|
|
|
await deleteUser(userId);
|
|
|
|
|
alert('사용자가 삭제되었습니다!');
|
|
|
|
|
// 목록 새로고침
|
|
|
|
|
fetchUsers();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
alert('삭제 실패: ' + error.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Python 예시 -->
|
|
|
|
|
<div class="code-example">
|
|
|
|
|
<h4><i class="fab fa-python"></i> Python (requests)</h4>
|
|
|
|
|
<pre><code>import requests
|
|
|
|
|
import json
|
|
|
|
|
|
|
|
|
|
API_KEY = 'ak_your_api_key_here'
|
|
|
|
|
BASE_URL = 'http://localhost:5577/api'
|
|
|
|
|
|
|
|
|
|
class UserAPI:
|
|
|
|
|
def __init__(self):
|
|
|
|
|
self.headers = {
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
'X-API-Key': API_KEY
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def create_user(self, user_data):
|
|
|
|
|
"""사용자 생성"""
|
|
|
|
|
response = requests.post(
|
|
|
|
|
f'{BASE_URL}/users',
|
|
|
|
|
headers=self.headers,
|
|
|
|
|
json=user_data
|
|
|
|
|
)
|
|
|
|
|
return response.json()
|
|
|
|
|
|
|
|
|
|
def get_all_users(self):
|
|
|
|
|
"""모든 사용자 조회"""
|
|
|
|
|
response = requests.get(
|
|
|
|
|
f'{BASE_URL}/users',
|
|
|
|
|
headers={'X-API-Key': API_KEY}
|
|
|
|
|
)
|
|
|
|
|
return response.json()
|
|
|
|
|
|
|
|
|
|
def get_user(self, user_id):
|
|
|
|
|
"""특정 사용자 조회"""
|
|
|
|
|
response = requests.get(
|
|
|
|
|
f'{BASE_URL}/users/{user_id}',
|
|
|
|
|
headers={'X-API-Key': API_KEY}
|
|
|
|
|
)
|
|
|
|
|
return response.json()
|
|
|
|
|
|
|
|
|
|
def update_user(self, user_id, user_data):
|
|
|
|
|
"""사용자 정보 수정"""
|
|
|
|
|
response = requests.put(
|
|
|
|
|
f'{BASE_URL}/users/{user_id}',
|
|
|
|
|
headers=self.headers,
|
|
|
|
|
json=user_data
|
|
|
|
|
)
|
|
|
|
|
return response.json()
|
|
|
|
|
|
|
|
|
|
def delete_user(self, user_id):
|
|
|
|
|
"""사용자 삭제"""
|
|
|
|
|
response = requests.delete(
|
|
|
|
|
f'{BASE_URL}/users/{user_id}',
|
|
|
|
|
headers={'X-API-Key': API_KEY}
|
|
|
|
|
)
|
|
|
|
|
return response.json()
|
|
|
|
|
|
|
|
|
|
# 사용 예시
|
|
|
|
|
api = UserAPI()
|
|
|
|
|
|
|
|
|
|
# 사용자 생성
|
|
|
|
|
new_user = {
|
|
|
|
|
'USER_ID': 'user004',
|
|
|
|
|
'USER_NAME': '박민수',
|
|
|
|
|
'DEPT_CODE': 'DEV001'
|
|
|
|
|
}
|
|
|
|
|
result = api.create_user(new_user)</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- cURL 예시 -->
|
|
|
|
|
<div class="code-example">
|
|
|
|
|
<h4><i class="fas fa-terminal"></i> cURL</h4>
|
|
|
|
|
<pre><code># 사용자 생성 (INSERT)
|
|
|
|
|
curl -X POST http://localhost:5577/api/users \
|
|
|
|
|
-H "Content-Type: application/json" \
|
|
|
|
|
-H "X-API-Key: ak_your_api_key_here" \
|
|
|
|
|
-d '{
|
|
|
|
|
"USER_ID": "user005",
|
|
|
|
|
"USER_NAME": "최수진",
|
|
|
|
|
"DEPT_CODE": "MKT001"
|
|
|
|
|
}'
|
|
|
|
|
|
|
|
|
|
# 모든 사용자 조회 (SELECT)
|
|
|
|
|
curl -X GET http://localhost:5577/api/users \
|
|
|
|
|
-H "X-API-Key: ak_your_api_key_here"
|
|
|
|
|
|
|
|
|
|
# 특정 사용자 조회
|
|
|
|
|
curl -X GET http://localhost:5577/api/users/user005 \
|
|
|
|
|
-H "X-API-Key: ak_your_api_key_here"
|
|
|
|
|
|
|
|
|
|
# 사용자 정보 수정 (UPDATE)
|
|
|
|
|
curl -X PUT http://localhost:5577/api/users/user005 \
|
|
|
|
|
-H "Content-Type: application/json" \
|
|
|
|
|
-H "X-API-Key: ak_your_api_key_here" \
|
|
|
|
|
-d '{
|
|
|
|
|
"USER_NAME": "최수진(수정)",
|
|
|
|
|
"DEPT_CODE": "MKT002"
|
|
|
|
|
}'
|
|
|
|
|
|
|
|
|
|
# 사용자 삭제 (DELETE)
|
|
|
|
|
curl -X DELETE http://localhost:5577/api/users/user005 \
|
|
|
|
|
-H "X-API-Key: ak_your_api_key_here"</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 기존 데이터 API -->
|
|
|
|
|
<div class="endpoint-group">
|
|
|
|
|
<h3>📊 기본 데이터 API</h3>
|
|
|
|
|
<div class="endpoint">
|
|
|
|
|
<div class="endpoint-header">
|
|
|
|
|
<span class="method get">GET</span>
|
|
|
|
|
<span class="path">/api/data</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="endpoint-description">
|
|
|
|
|
<p>모든 데이터 조회 (API 키 필요)</p>
|
|
|
|
|
<p>헤더: <code>X-API-Key: your-api-key</code></p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="endpoint">
|
|
|
|
|
<div class="endpoint-header">
|
|
|
|
|
<span class="method post">POST</span>
|
|
|
|
|
<span class="path">/api/data</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="endpoint-description">
|
|
|
|
|
<p>데이터 생성 (API 키 필요)</p>
|
|
|
|
|
<pre><code>{
|
|
|
|
|
"name": "데이터 이름",
|
|
|
|
|
"description": "설명",
|
|
|
|
|
"dataValue": "값"
|
|
|
|
|
}</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- API 테스터 섹션 -->
|
|
|
|
|
<section id="api-tester-section" class="content-section">
|
|
|
|
|
<h2>API 테스터</h2>
|
|
|
|
|
<p class="section-description">브라우저에서 직접 REST API를 테스트해보세요.</p>
|
|
|
|
|
|
|
|
|
|
<!-- API 키 선택 -->
|
|
|
|
|
<div class="api-tester-form">
|
|
|
|
|
<div class="form-row">
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label for="testApiKey">API 키 선택:</label>
|
|
|
|
|
<select id="testApiKey" class="form-control">
|
|
|
|
|
<option value="">API 키를 선택하세요</option>
|
|
|
|
|
</select>
|
|
|
|
|
<small>또는 직접 입력:</small>
|
|
|
|
|
<input type="text" id="customApiKey" class="form-control" placeholder="API 키를 직접 입력하세요">
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 요청 설정 -->
|
|
|
|
|
<div class="form-row">
|
|
|
|
|
<div class="form-group method-group">
|
|
|
|
|
<label for="testMethod">HTTP 메소드:</label>
|
|
|
|
|
<select id="testMethod" class="form-control">
|
|
|
|
|
<option value="GET">GET</option>
|
|
|
|
|
<option value="POST">POST</option>
|
|
|
|
|
<option value="PUT">PUT</option>
|
|
|
|
|
<option value="DELETE">DELETE</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group url-group">
|
|
|
|
|
<label for="testUrl">엔드포인트:</label>
|
|
|
|
|
<div class="url-input">
|
|
|
|
|
<span class="base-url">http://localhost:5577</span>
|
|
|
|
|
<input type="text" id="testUrl" class="form-control" placeholder="/api/data" value="/api/data">
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 요청 본문 (POST/PUT용) -->
|
|
|
|
|
<div class="form-group" id="requestBodyGroup" style="display: none;">
|
|
|
|
|
<label for="testBody">요청 본문 (JSON):</label>
|
|
|
|
|
<textarea id="testBody" class="form-control" rows="8" placeholder='{"name": "테스트", "description": "설명", "dataValue": "값"}'></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 테스트 버튼 -->
|
|
|
|
|
<div class="form-actions">
|
|
|
|
|
<button onclick="sendApiRequest()" class="btn btn-primary">
|
|
|
|
|
<i class="fas fa-play"></i> API 호출
|
|
|
|
|
</button>
|
|
|
|
|
<button onclick="clearApiTest()" class="btn btn-secondary">
|
|
|
|
|
<i class="fas fa-eraser"></i> 초기화
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 응답 결과 -->
|
|
|
|
|
<div class="api-response" id="apiResponse" style="display: none;">
|
|
|
|
|
<h3>응답 결과</h3>
|
|
|
|
|
<div class="response-info">
|
|
|
|
|
<span class="response-status" id="responseStatus"></span>
|
|
|
|
|
<span class="response-time" id="responseTime"></span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="response-headers">
|
|
|
|
|
<h4>응답 헤더</h4>
|
|
|
|
|
<pre id="responseHeaders"></pre>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="response-body">
|
|
|
|
|
<h4>응답 본문</h4>
|
|
|
|
|
<pre id="responseBody"></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 빠른 테스트 예제 -->
|
|
|
|
|
<div class="quick-tests">
|
2025-09-25 16:23:32 +09:00
|
|
|
<h3>빠른 테스트 예제 - User CRUD</h3>
|
2025-09-24 17:11:35 +09:00
|
|
|
<div class="test-examples">
|
2025-09-25 16:23:32 +09:00
|
|
|
<button onclick="loadExample('getAllUsers')" class="btn btn-info btn-sm">
|
|
|
|
|
<i class="fas fa-users"></i> 모든 사용자 조회 (GET)
|
2025-09-24 17:11:35 +09:00
|
|
|
</button>
|
2025-09-25 16:23:32 +09:00
|
|
|
<button onclick="loadExample('getUser')" class="btn btn-primary btn-sm">
|
|
|
|
|
<i class="fas fa-user"></i> 특정 사용자 조회 (GET)
|
2025-09-24 17:11:35 +09:00
|
|
|
</button>
|
2025-09-25 16:23:32 +09:00
|
|
|
<button onclick="loadExample('createUser')" class="btn btn-success btn-sm">
|
|
|
|
|
<i class="fas fa-user-plus"></i> 사용자 생성 (POST)
|
2025-09-24 17:11:35 +09:00
|
|
|
</button>
|
2025-09-25 16:23:32 +09:00
|
|
|
<button onclick="loadExample('updateUser')" class="btn btn-warning btn-sm">
|
|
|
|
|
<i class="fas fa-user-edit"></i> 사용자 수정 (PUT)
|
2025-09-24 17:11:35 +09:00
|
|
|
</button>
|
2025-09-25 16:23:32 +09:00
|
|
|
<button onclick="loadExample('deleteUser')" class="btn btn-danger btn-sm">
|
|
|
|
|
<i class="fas fa-user-times"></i> 사용자 삭제 (DELETE)
|
2025-09-24 17:11:35 +09:00
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- 사용자 관리 섹션 (관리자 전용) -->
|
|
|
|
|
<section id="users-section" class="content-section admin-only">
|
|
|
|
|
<h2>사용자 관리</h2>
|
|
|
|
|
<div class="table-container">
|
|
|
|
|
<table id="usersTable" class="data-table">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th>ID</th>
|
|
|
|
|
<th>사용자명</th>
|
|
|
|
|
<th>이메일</th>
|
|
|
|
|
<th>역할</th>
|
|
|
|
|
<th>상태</th>
|
|
|
|
|
<th>가입일</th>
|
|
|
|
|
<th>마지막 로그인</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
<!-- 사용자 목록이 여기에 동적으로 추가됩니다 -->
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- API 로그 섹션 (관리자 전용) -->
|
|
|
|
|
<section id="api-logs-section" class="content-section admin-only">
|
|
|
|
|
<h2>API 호출 로그</h2>
|
|
|
|
|
|
|
|
|
|
<!-- 필터 -->
|
|
|
|
|
<div class="log-filters">
|
|
|
|
|
<div class="filter-row">
|
|
|
|
|
<div class="filter-group">
|
|
|
|
|
<label>시작 날짜:</label>
|
|
|
|
|
<input type="date" id="startDate" class="filter-input">
|
|
|
|
|
</div>
|
|
|
|
|
<div class="filter-group">
|
|
|
|
|
<label>종료 날짜:</label>
|
|
|
|
|
<input type="date" id="endDate" class="filter-input">
|
|
|
|
|
</div>
|
|
|
|
|
<div class="filter-group">
|
|
|
|
|
<label>메소드:</label>
|
|
|
|
|
<select id="methodFilter" class="filter-input">
|
|
|
|
|
<option value="">전체</option>
|
|
|
|
|
<option value="GET">GET</option>
|
|
|
|
|
<option value="POST">POST</option>
|
|
|
|
|
<option value="PUT">PUT</option>
|
|
|
|
|
<option value="DELETE">DELETE</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="filter-group">
|
|
|
|
|
<label>엔드포인트:</label>
|
|
|
|
|
<input type="text" id="endpointFilter" placeholder="엔드포인트 검색" class="filter-input">
|
|
|
|
|
</div>
|
|
|
|
|
<div class="filter-group">
|
|
|
|
|
<label>사용자:</label>
|
|
|
|
|
<input type="text" id="usernameFilter" placeholder="사용자명 검색" class="filter-input">
|
|
|
|
|
</div>
|
|
|
|
|
<button onclick="loadApiLogs()" class="btn btn-primary">
|
|
|
|
|
<i class="fas fa-search"></i> 검색
|
|
|
|
|
</button>
|
|
|
|
|
<button onclick="clearLogFilters()" class="btn btn-secondary">
|
|
|
|
|
<i class="fas fa-times"></i> 초기화
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 로그 통계 -->
|
|
|
|
|
<div class="log-stats">
|
|
|
|
|
<div class="stat-card">
|
|
|
|
|
<div class="stat-info">
|
|
|
|
|
<h3 id="totalApiRequests">-</h3>
|
|
|
|
|
<p>총 API 요청</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-card">
|
|
|
|
|
<div class="stat-info">
|
|
|
|
|
<h3 id="todayApiRequests">-</h3>
|
|
|
|
|
<p>오늘 요청</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 로그 테이블 -->
|
|
|
|
|
<div class="table-container">
|
|
|
|
|
<table id="apiLogsTable" class="data-table">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th>시간</th>
|
|
|
|
|
<th>메소드</th>
|
|
|
|
|
<th>엔드포인트</th>
|
|
|
|
|
<th>사용자</th>
|
|
|
|
|
<th>API 키</th>
|
|
|
|
|
<th>상태</th>
|
|
|
|
|
<th>응답시간</th>
|
|
|
|
|
<th>IP</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
<!-- 로그 목록이 여기에 동적으로 추가됩니다 -->
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 페이지네이션 -->
|
|
|
|
|
<div class="pagination" id="logPagination">
|
|
|
|
|
<!-- 페이지네이션이 여기에 동적으로 추가됩니다 -->
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- 모니터링 섹션 (관리자 전용) -->
|
|
|
|
|
<section id="monitoring-section" class="content-section admin-only">
|
|
|
|
|
<h2>시스템 모니터링</h2>
|
|
|
|
|
<div class="monitoring-grid">
|
|
|
|
|
<div class="monitor-card">
|
|
|
|
|
<h3>서버 상태</h3>
|
|
|
|
|
<div id="serverStatus" class="status-indicator">
|
|
|
|
|
<i class="fas fa-circle"></i> 확인 중...
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="monitor-card">
|
|
|
|
|
<h3>데이터베이스 연결</h3>
|
|
|
|
|
<div id="dbStatus" class="status-indicator">
|
|
|
|
|
<i class="fas fa-circle"></i> 확인 중...
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
</main>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 모달들 -->
|
|
|
|
|
<!-- API 키 생성 모달 -->
|
|
|
|
|
<div id="createKeyModal" class="modal">
|
|
|
|
|
<div class="modal-content">
|
|
|
|
|
<div class="modal-header">
|
|
|
|
|
<h3>새 API 키 생성</h3>
|
|
|
|
|
<span class="close" onclick="closeModal('createKeyModal')">×</span>
|
|
|
|
|
</div>
|
|
|
|
|
<form id="createKeyForm">
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label for="keyName">키 이름</label>
|
|
|
|
|
<input type="text" id="keyName" required>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label>권한 선택</label>
|
|
|
|
|
<div class="checkbox-group">
|
|
|
|
|
<label><input type="checkbox" value="read"> 읽기</label>
|
|
|
|
|
<label><input type="checkbox" value="write"> 쓰기</label>
|
|
|
|
|
<label><input type="checkbox" value="delete"> 삭제</label>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="modal-actions">
|
|
|
|
|
<button type="button" class="btn btn-secondary" onclick="closeModal('createKeyModal')">취소</button>
|
|
|
|
|
<button type="submit" class="btn btn-primary">생성</button>
|
|
|
|
|
</div>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 데이터 생성 모달 -->
|
|
|
|
|
<div id="createDataModal" class="modal">
|
|
|
|
|
<div class="modal-content">
|
|
|
|
|
<div class="modal-header">
|
|
|
|
|
<h3>새 데이터 추가</h3>
|
|
|
|
|
<span class="close" onclick="closeModal('createDataModal')">×</span>
|
|
|
|
|
</div>
|
|
|
|
|
<form id="createDataForm">
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label for="dataName">이름</label>
|
|
|
|
|
<input type="text" id="dataName" required>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label for="dataDescription">설명</label>
|
|
|
|
|
<textarea id="dataDescription" rows="3"></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label for="dataValue">데이터 값</label>
|
|
|
|
|
<textarea id="dataValue" rows="5" placeholder='{"key": "value"}'></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="modal-actions">
|
|
|
|
|
<button type="button" class="btn btn-secondary" onclick="closeModal('createDataModal')">취소</button>
|
|
|
|
|
<button type="submit" class="btn btn-primary">추가</button>
|
|
|
|
|
</div>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 알림 토스트 -->
|
|
|
|
|
<div id="toast" class="toast"></div>
|
|
|
|
|
|
|
|
|
|
<script src="/js/app.js"></script>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|