Files
ai-server/templates/admin.html
Hyungi Ahn 1e098999c1 feat: AI 서버 관리 페이지 Phase 3 보안 강화 - JWT 인증 시스템
🔐 JWT 기반 로그인 시스템:
- 로그인 페이지: 아름다운 애니메이션과 보안 정보 표시
- JWT 토큰: 24시간 또는 30일 (Remember Me) 만료 설정
- 비밀번호 암호화: bcrypt 해싱으로 안전한 저장
- 계정 잠금: 5회 실패 시 15분 자동 잠금

👥 사용자 계정 관리:
- admin/admin123 (관리자 권한)
- hyungi/hyungi123 (시스템 권한)
- 역할 기반 접근 제어 (RBAC)

🛡️ 보안 기능:
- 토큰 자동 검증 및 만료 처리
- 감사 로그: 로그인/로그아웃/관리 작업 추적
- 안전한 세션 관리 및 토큰 정리
- 클라이언트 사이드 토큰 검증

🎨 UI/UX 개선:
- 로그인 페이지: 그라디언트 배경, 플로팅 아이콘 애니메이션
- 사용자 메뉴: 헤더에 사용자명과 로그아웃 버튼 표시
- 보안 표시: SSL, 세션 타임아웃, JWT 인증 정보
- 반응형 디자인 및 다크모드 지원

🔧 기술 구현:
- FastAPI HTTPBearer 보안 스키마
- PyJWT 토큰 생성/검증
- bcrypt 비밀번호 해싱
- 클라이언트-서버 토큰 동기화

새 파일:
- templates/login.html: 로그인 페이지 HTML
- static/login.css: 로그인 페이지 스타일
- static/login.js: 로그인 JavaScript 로직
- server/auth.py: JWT 인증 시스템 (실제 서버용)

수정된 파일:
- test_admin.py: 테스트 서버에 JWT 인증 추가
- static/admin.js: JWT 토큰 기반 API 요청으로 변경
- templates/admin.html: 사용자 메뉴 및 로그아웃 버튼 추가
- static/admin.css: 사용자 메뉴 스타일 추가

보안 레벨: Phase 1 (API Key) → Phase 3 (JWT + 감사로그)
2025-08-18 15:24:01 +09:00

249 lines
11 KiB
HTML

<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Server Admin Dashboard</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<link href="/static/admin.css" rel="stylesheet">
</head>
<body>
<div class="admin-layout">
<!-- Header -->
<header class="admin-header">
<div class="header-content">
<h1><i class="fas fa-robot"></i> AI Server Admin</h1>
<div class="header-info">
<span class="server-status online">
<i class="fas fa-circle"></i> Online
</span>
<span class="current-time" id="current-time"></span>
<div class="user-menu">
<span class="user-info" id="user-info">
<i class="fas fa-user"></i>
<span id="username">Loading...</span>
</span>
<button class="logout-btn" onclick="logout()">
<i class="fas fa-sign-out-alt"></i>
Logout
</button>
</div>
</div>
</div>
</header>
<!-- Main Content -->
<main class="admin-main">
<!-- System Status Dashboard -->
<section class="dashboard-section">
<h2><i class="fas fa-tachometer-alt"></i> System Status</h2>
<div class="status-grid">
<div class="status-card">
<div class="card-header">
<i class="fas fa-server"></i>
<h3>AI Server</h3>
</div>
<div class="card-content">
<div class="status-value" id="server-status">Loading...</div>
<div class="status-detail">Port: {{ server_port }}</div>
</div>
</div>
<div class="status-card">
<div class="card-header">
<i class="fas fa-brain"></i>
<h3>Ollama</h3>
</div>
<div class="card-content">
<div class="status-value" id="ollama-status">Loading...</div>
<div class="status-detail" id="ollama-host">{{ ollama_host }}</div>
</div>
</div>
<div class="status-card">
<div class="card-header">
<i class="fas fa-microchip"></i>
<h3>Active Model</h3>
</div>
<div class="card-content">
<div class="status-value" id="active-model">Loading...</div>
<div class="status-detail">Base Model</div>
</div>
</div>
<div class="status-card">
<div class="card-header">
<i class="fas fa-chart-line"></i>
<h3>API Calls</h3>
</div>
<div class="card-content">
<div class="status-value" id="api-calls">Loading...</div>
<div class="status-detail">Today</div>
</div>
</div>
</div>
</section>
<!-- Model Management -->
<section class="dashboard-section">
<h2><i class="fas fa-cogs"></i> Model Management</h2>
<div class="models-container">
<div class="models-header">
<button class="btn btn-primary" onclick="refreshModels()">
<i class="fas fa-sync"></i> Refresh
</button>
<button class="btn btn-success" onclick="openModelDownload()">
<i class="fas fa-download"></i> Download Model
</button>
</div>
<div class="models-table">
<table>
<thead>
<tr>
<th>Model Name</th>
<th>Size</th>
<th>Status</th>
<th>Last Used</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="models-tbody">
<tr>
<td colspan="5" class="loading">Loading models...</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
<!-- System Monitoring (Phase 2) -->
<section class="dashboard-section">
<h2><i class="fas fa-chart-line"></i> System Monitoring</h2>
<div class="monitoring-container">
<div class="monitoring-grid">
<div class="monitor-card">
<div class="card-header">
<i class="fas fa-microchip"></i>
<h3>CPU Usage</h3>
</div>
<div class="card-content">
<div class="progress-circle" id="cpu-progress">
<span class="progress-text" id="cpu-text">--</span>
</div>
<div class="monitor-details">
<span id="cpu-cores">-- cores</span>
</div>
</div>
</div>
<div class="monitor-card">
<div class="card-header">
<i class="fas fa-memory"></i>
<h3>Memory Usage</h3>
</div>
<div class="card-content">
<div class="progress-circle" id="memory-progress">
<span class="progress-text" id="memory-text">--</span>
</div>
<div class="monitor-details">
<span id="memory-details">-- / -- GB</span>
</div>
</div>
</div>
<div class="monitor-card">
<div class="card-header">
<i class="fas fa-hdd"></i>
<h3>Disk Usage</h3>
</div>
<div class="card-content">
<div class="progress-circle" id="disk-progress">
<span class="progress-text" id="disk-text">--</span>
</div>
<div class="monitor-details">
<span id="disk-details">-- / -- GB</span>
</div>
</div>
</div>
<div class="monitor-card">
<div class="card-header">
<i class="fas fa-thermometer-half"></i>
<h3>GPU Status</h3>
</div>
<div class="card-content">
<div class="progress-circle" id="gpu-progress">
<span class="progress-text" id="gpu-text">--</span>
</div>
<div class="monitor-details">
<span id="gpu-details">No GPU detected</span>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- API Key Management -->
<section class="dashboard-section">
<h2><i class="fas fa-key"></i> API Key Management</h2>
<div class="api-keys-container">
<div class="api-keys-header">
<button class="btn btn-success" onclick="generateApiKey()">
<i class="fas fa-plus"></i> Generate New Key
</button>
</div>
<div class="api-keys-list" id="api-keys-list">
<div class="loading">Loading API keys...</div>
</div>
</div>
</section>
</main>
</div>
<!-- Model Download Modal -->
<div id="model-download-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3><i class="fas fa-download"></i> Download Model</h3>
<button class="close-btn" onclick="closeModal('model-download-modal')">&times;</button>
</div>
<div class="modal-body">
<div id="available-models-list">
<div class="loading">Loading available models...</div>
</div>
</div>
</div>
</div>
<!-- Model Delete Confirmation Modal -->
<div id="model-delete-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3><i class="fas fa-trash"></i> Delete Model</h3>
<button class="close-btn" onclick="closeModal('model-delete-modal')">&times;</button>
</div>
<div class="modal-body">
<p>Are you sure you want to delete this model?</p>
<div class="model-delete-info">
<strong id="delete-model-name">Model Name</strong>
<p>This action cannot be undone.</p>
</div>
<div class="modal-actions">
<button class="btn btn-danger" id="confirm-delete-btn">
<i class="fas fa-trash"></i> Delete
</button>
<button class="btn btn-secondary" onclick="closeModal('model-delete-modal')">
Cancel
</button>
</div>
</div>
</div>
</div>
<!-- Scripts -->
<script src="/static/admin.js"></script>
</body>
</html>