- 시스템 관리자 전용 웹페이지 구현 (system.html) - 깔끔한 흰색 배경의 올드스쿨 스타일 적용 - 반응형 그리드 레이아웃으로 카드 배치 개선 - ES6 모듈 방식으로 JavaScript 구조 개선 - 이벤트 리스너 방식으로 버튼 클릭 처리 변경 - 시스템 상태, 사용자 통계, 계정 관리 기능 구현 - 시스템 로그 조회 기능 추가 - 나머지 관리 기능들 스켈레톤 구현 (개발 중 상태) - 인코딩 문제 해결을 위한 영어 로그 메시지 적용 - hyungi 계정을 system 권한으로 설정 - JWT 토큰에 role 필드 추가 - 시스템 전용 API 엔드포인트 구현 주요 변경사항: - web-ui/pages/dashboard/system.html: 시스템 관리자 전용 페이지 - web-ui/css/system-dashboard.css: 시스템 대시보드 전용 스타일 - web-ui/js/system-dashboard.js: 시스템 대시보드 로직 - api.hyungi.net/controllers/systemController.js: 시스템 API 컨트롤러 - api.hyungi.net/routes/systemRoutes.js: 시스템 API 라우트 - api.hyungi.net/controllers/authController.js: 시스템 권한 로그인 처리 - api.hyungi.net/services/auth.service.js: JWT 토큰에 role 필드 추가
908 lines
32 KiB
JavaScript
908 lines
32 KiB
JavaScript
// System Dashboard JavaScript
|
|
console.log('🚀 system-dashboard.js loaded');
|
|
|
|
import { apiRequest } from './api-helper.js';
|
|
import { getCurrentUser } from './auth.js';
|
|
|
|
console.log('📦 modules imported successfully');
|
|
|
|
// 전역 변수
|
|
let systemData = {
|
|
users: [],
|
|
logs: [],
|
|
systemStatus: {}
|
|
};
|
|
|
|
// Initialize on page load
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
console.log('📄 DOM loaded, starting initialization');
|
|
initializeSystemDashboard();
|
|
setupEventListeners();
|
|
});
|
|
|
|
// Setup event listeners
|
|
function setupEventListeners() {
|
|
// Add event listeners to all data-action buttons
|
|
const actionButtons = document.querySelectorAll('[data-action]');
|
|
|
|
actionButtons.forEach(button => {
|
|
const action = button.getAttribute('data-action');
|
|
|
|
switch(action) {
|
|
case 'account-management':
|
|
button.addEventListener('click', openAccountManagement);
|
|
console.log('✅ Account management button listener added');
|
|
break;
|
|
case 'system-logs':
|
|
button.addEventListener('click', openSystemLogs);
|
|
console.log('✅ System logs button listener added');
|
|
break;
|
|
case 'database-management':
|
|
button.addEventListener('click', openDatabaseManagement);
|
|
console.log('✅ Database management button listener added');
|
|
break;
|
|
case 'system-settings':
|
|
button.addEventListener('click', openSystemSettings);
|
|
console.log('✅ System settings button listener added');
|
|
break;
|
|
case 'backup-management':
|
|
button.addEventListener('click', openBackupManagement);
|
|
console.log('✅ Backup management button listener added');
|
|
break;
|
|
case 'monitoring':
|
|
button.addEventListener('click', openMonitoring);
|
|
console.log('✅ Monitoring button listener added');
|
|
break;
|
|
case 'close-modal':
|
|
button.addEventListener('click', () => closeModal('account-modal'));
|
|
console.log('✅ Modal close button listener added');
|
|
break;
|
|
}
|
|
});
|
|
|
|
console.log(`🎯 Total ${actionButtons.length} event listeners setup completed`);
|
|
}
|
|
|
|
// Initialize system dashboard
|
|
async function initializeSystemDashboard() {
|
|
try {
|
|
console.log('🚀 Starting system dashboard initialization...');
|
|
|
|
// Load user info
|
|
await loadUserInfo();
|
|
console.log('✅ User info loaded');
|
|
|
|
// Load system status
|
|
await loadSystemStatus();
|
|
console.log('✅ System status loaded');
|
|
|
|
// Load user statistics
|
|
await loadUserStats();
|
|
console.log('✅ User statistics loaded');
|
|
|
|
// Load recent activities
|
|
await loadRecentActivities();
|
|
console.log('✅ Recent activities loaded');
|
|
|
|
// Setup auto-refresh (every 30 seconds)
|
|
setInterval(refreshSystemStatus, 30000);
|
|
|
|
console.log('🎉 System dashboard initialization completed');
|
|
} catch (error) {
|
|
console.error('❌ System dashboard initialization error:', error);
|
|
showNotification('Error loading system dashboard', 'error');
|
|
}
|
|
}
|
|
|
|
// 사용자 정보 로드
|
|
async function loadUserInfo() {
|
|
try {
|
|
const user = getCurrentUser();
|
|
if (user && user.name) {
|
|
document.getElementById('user-name').textContent = user.name;
|
|
}
|
|
} catch (error) {
|
|
console.error('사용자 정보 로드 오류:', error);
|
|
}
|
|
}
|
|
|
|
// 시스템 상태 로드
|
|
async function loadSystemStatus() {
|
|
try {
|
|
// 서버 상태 확인
|
|
const serverStatus = await checkServerStatus();
|
|
updateServerStatus(serverStatus);
|
|
|
|
// 데이터베이스 상태 확인
|
|
const dbStatus = await checkDatabaseStatus();
|
|
updateDatabaseStatus(dbStatus);
|
|
|
|
// 시스템 알림 확인
|
|
const alerts = await getSystemAlerts();
|
|
updateSystemAlerts(alerts);
|
|
|
|
} catch (error) {
|
|
console.error('시스템 상태 로드 오류:', error);
|
|
}
|
|
}
|
|
|
|
// 서버 상태 확인
|
|
async function checkServerStatus() {
|
|
try {
|
|
const response = await apiRequest('/api/system/status', 'GET');
|
|
return response.success ? 'online' : 'offline';
|
|
} catch (error) {
|
|
return 'offline';
|
|
}
|
|
}
|
|
|
|
// 데이터베이스 상태 확인
|
|
async function checkDatabaseStatus() {
|
|
try {
|
|
const response = await apiRequest('/api/system/db-status', 'GET');
|
|
return response;
|
|
} catch (error) {
|
|
return { status: 'error', connections: 0 };
|
|
}
|
|
}
|
|
|
|
// 시스템 알림 가져오기
|
|
async function getSystemAlerts() {
|
|
try {
|
|
const response = await apiRequest('/api/system/alerts', 'GET');
|
|
return response.alerts || [];
|
|
} catch (error) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// 서버 상태 업데이트
|
|
function updateServerStatus(status) {
|
|
const serverCheckTime = document.getElementById('server-check-time');
|
|
const statusElements = document.querySelectorAll('.status-value');
|
|
|
|
if (serverCheckTime) {
|
|
serverCheckTime.textContent = new Date().toLocaleTimeString('ko-KR');
|
|
}
|
|
|
|
// 서버 상태 표시 업데이트 로직 추가
|
|
}
|
|
|
|
// 데이터베이스 상태 업데이트
|
|
function updateDatabaseStatus(dbStatus) {
|
|
const dbConnections = document.getElementById('db-connections');
|
|
if (dbConnections && dbStatus.connections !== undefined) {
|
|
dbConnections.textContent = dbStatus.connections;
|
|
}
|
|
}
|
|
|
|
// 시스템 알림 업데이트
|
|
function updateSystemAlerts(alerts) {
|
|
const systemAlerts = document.getElementById('system-alerts');
|
|
if (systemAlerts) {
|
|
systemAlerts.textContent = alerts.length;
|
|
systemAlerts.className = `status-value ${alerts.length > 0 ? 'warning' : 'online'}`;
|
|
}
|
|
}
|
|
|
|
// 사용자 통계 로드
|
|
async function loadUserStats() {
|
|
try {
|
|
const response = await apiRequest('/api/system/users/stats', 'GET');
|
|
|
|
if (response.success) {
|
|
const activeUsers = document.getElementById('active-users');
|
|
const totalUsers = document.getElementById('total-users');
|
|
|
|
if (activeUsers) activeUsers.textContent = response.data.active || 0;
|
|
if (totalUsers) totalUsers.textContent = response.data.total || 0;
|
|
}
|
|
} catch (error) {
|
|
console.error('사용자 통계 로드 오류:', error);
|
|
}
|
|
}
|
|
|
|
// 최근 활동 로드
|
|
async function loadRecentActivities() {
|
|
try {
|
|
const response = await apiRequest('/api/system/recent-activities', 'GET');
|
|
|
|
if (response.success && response.data) {
|
|
displayRecentActivities(response.data);
|
|
}
|
|
} catch (error) {
|
|
console.error('최근 활동 로드 오류:', error);
|
|
displayDefaultActivities();
|
|
}
|
|
}
|
|
|
|
// 최근 활동 표시
|
|
function displayRecentActivities(activities) {
|
|
const container = document.getElementById('recent-activities');
|
|
|
|
if (!container) return;
|
|
|
|
if (!activities || activities.length === 0) {
|
|
container.innerHTML = '<p style="text-align: center; color: #7f8c8d; padding: 2rem;">최근 활동이 없습니다.</p>';
|
|
return;
|
|
}
|
|
|
|
const html = activities.map(activity => `
|
|
<div class="activity-item">
|
|
<div class="activity-icon">
|
|
<i class="fas ${getActivityIcon(activity.type)}"></i>
|
|
</div>
|
|
<div class="activity-info">
|
|
<h4>${activity.title}</h4>
|
|
<p>${activity.description}</p>
|
|
</div>
|
|
<div class="activity-time">
|
|
${formatTimeAgo(activity.created_at)}
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
// 기본 활동 표시 (데이터 로드 실패 시)
|
|
function displayDefaultActivities() {
|
|
const container = document.getElementById('recent-activities');
|
|
if (!container) return;
|
|
|
|
const defaultActivities = [
|
|
{
|
|
type: 'system',
|
|
title: '시스템 시작',
|
|
description: '시스템이 정상적으로 시작되었습니다.',
|
|
created_at: new Date().toISOString()
|
|
}
|
|
];
|
|
|
|
displayRecentActivities(defaultActivities);
|
|
}
|
|
|
|
// 활동 타입에 따른 아이콘 반환
|
|
function getActivityIcon(type) {
|
|
const icons = {
|
|
'login': 'fa-sign-in-alt',
|
|
'user_create': 'fa-user-plus',
|
|
'user_update': 'fa-user-edit',
|
|
'user_delete': 'fa-user-minus',
|
|
'system': 'fa-cog',
|
|
'database': 'fa-database',
|
|
'backup': 'fa-download',
|
|
'error': 'fa-exclamation-triangle'
|
|
};
|
|
|
|
return icons[type] || 'fa-info-circle';
|
|
}
|
|
|
|
// 시간 포맷팅 (몇 분 전, 몇 시간 전 등)
|
|
function formatTimeAgo(dateString) {
|
|
const now = new Date();
|
|
const date = new Date(dateString);
|
|
const diffInSeconds = Math.floor((now - date) / 1000);
|
|
|
|
if (diffInSeconds < 60) {
|
|
return '방금 전';
|
|
} else if (diffInSeconds < 3600) {
|
|
return `${Math.floor(diffInSeconds / 60)}분 전`;
|
|
} else if (diffInSeconds < 86400) {
|
|
return `${Math.floor(diffInSeconds / 3600)}시간 전`;
|
|
} else {
|
|
return `${Math.floor(diffInSeconds / 86400)}일 전`;
|
|
}
|
|
}
|
|
|
|
// 시스템 상태 새로고침
|
|
async function refreshSystemStatus() {
|
|
try {
|
|
await loadSystemStatus();
|
|
await loadUserStats();
|
|
} catch (error) {
|
|
console.error('시스템 상태 새로고침 오류:', error);
|
|
}
|
|
}
|
|
|
|
// Open account management
|
|
function openAccountManagement() {
|
|
console.log('🎯 Account management button clicked');
|
|
const modal = document.getElementById('account-modal');
|
|
const content = document.getElementById('account-management-content');
|
|
|
|
console.log('Modal element:', modal);
|
|
console.log('Content element:', content);
|
|
|
|
if (modal && content) {
|
|
console.log('✅ Modal and content elements found, loading content...');
|
|
// Load account management content
|
|
loadAccountManagementContent(content);
|
|
modal.style.display = 'block';
|
|
console.log('✅ Modal displayed');
|
|
} else {
|
|
console.error('❌ Modal or content element not found');
|
|
}
|
|
}
|
|
|
|
// 계정 관리 컨텐츠 로드
|
|
async function loadAccountManagementContent(container) {
|
|
try {
|
|
container.innerHTML = `
|
|
<div class="loading-spinner">
|
|
<i class="fas fa-spinner fa-spin"></i> 로딩 중...
|
|
</div>
|
|
`;
|
|
|
|
// 사용자 목록 로드
|
|
const response = await apiRequest('/api/system/users', 'GET');
|
|
|
|
if (response.success) {
|
|
displayAccountManagement(container, response.data);
|
|
} else {
|
|
throw new Error(response.error || '사용자 목록을 불러올 수 없습니다.');
|
|
}
|
|
} catch (error) {
|
|
console.error('계정 관리 컨텐츠 로드 오류:', error);
|
|
container.innerHTML = `
|
|
<div class="error-message">
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
<p>계정 정보를 불러오는 중 오류가 발생했습니다.</p>
|
|
<button class="btn btn-primary" onclick="loadAccountManagementContent(document.getElementById('account-management-content'))">
|
|
다시 시도
|
|
</button>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
// 계정 관리 화면 표시
|
|
function displayAccountManagement(container, users) {
|
|
const html = `
|
|
<div class="account-management">
|
|
<div class="account-header">
|
|
<h4><i class="fas fa-users"></i> 사용자 계정 관리</h4>
|
|
<button class="btn btn-primary" onclick="openCreateUserForm()">
|
|
<i class="fas fa-plus"></i> 새 사용자
|
|
</button>
|
|
</div>
|
|
|
|
<div class="account-filters">
|
|
<input type="text" id="user-search" placeholder="사용자 검색..." onkeyup="filterUsers()">
|
|
<select id="role-filter" onchange="filterUsers()">
|
|
<option value="">모든 권한</option>
|
|
<option value="system">시스템</option>
|
|
<option value="admin">관리자</option>
|
|
<option value="leader">그룹장</option>
|
|
<option value="user">사용자</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="users-table">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>사용자명</th>
|
|
<th>이름</th>
|
|
<th>권한</th>
|
|
<th>상태</th>
|
|
<th>마지막 로그인</th>
|
|
<th>작업</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="users-tbody">
|
|
${generateUsersTableRows(users)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
container.innerHTML = html;
|
|
systemData.users = users;
|
|
}
|
|
|
|
// 사용자 테이블 행 생성
|
|
function generateUsersTableRows(users) {
|
|
if (!users || users.length === 0) {
|
|
return '<tr><td colspan="7" style="text-align: center; padding: 2rem;">등록된 사용자가 없습니다.</td></tr>';
|
|
}
|
|
|
|
return users.map(user => `
|
|
<tr data-user-id="${user.user_id}">
|
|
<td>${user.user_id}</td>
|
|
<td>${user.username}</td>
|
|
<td>${user.name || '-'}</td>
|
|
<td>
|
|
<span class="role-badge role-${user.role}">
|
|
${getRoleDisplayName(user.role)}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<span class="status-badge ${user.is_active ? 'active' : 'inactive'}">
|
|
${user.is_active ? '활성' : '비활성'}
|
|
</span>
|
|
</td>
|
|
<td>${user.last_login_at ? formatDate(user.last_login_at) : '없음'}</td>
|
|
<td class="action-buttons">
|
|
<button class="btn-small btn-edit" onclick="editUser(${user.user_id})" title="수정">
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
<button class="btn-small btn-delete" onclick="deleteUser(${user.user_id})" title="삭제">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
`).join('');
|
|
}
|
|
|
|
// 권한 표시명 반환
|
|
function getRoleDisplayName(role) {
|
|
const roleNames = {
|
|
'system': '시스템',
|
|
'admin': '관리자',
|
|
'leader': '그룹장',
|
|
'user': '사용자'
|
|
};
|
|
return roleNames[role] || role;
|
|
}
|
|
|
|
// 날짜 포맷팅
|
|
function formatDate(dateString) {
|
|
if (!dateString) return '-';
|
|
const date = new Date(dateString);
|
|
return date.toLocaleString('ko-KR');
|
|
}
|
|
|
|
// 시스템 로그 열기
|
|
function openSystemLogs() {
|
|
console.log('시스템 로그 버튼 클릭됨');
|
|
const modal = document.getElementById('account-modal');
|
|
const content = document.getElementById('account-management-content');
|
|
|
|
if (modal && content) {
|
|
loadSystemLogsContent(content);
|
|
modal.style.display = 'block';
|
|
}
|
|
}
|
|
|
|
// 시스템 로그 컨텐츠 로드
|
|
async function loadSystemLogsContent(container) {
|
|
try {
|
|
container.innerHTML = `
|
|
<div class="system-logs">
|
|
<h4><i class="fas fa-file-alt"></i> 시스템 로그</h4>
|
|
<div class="log-filters">
|
|
<select id="log-type-filter">
|
|
<option value="">모든 로그</option>
|
|
<option value="login">로그인</option>
|
|
<option value="activity">활동</option>
|
|
<option value="error">오류</option>
|
|
</select>
|
|
<input type="date" id="log-date-filter">
|
|
<button class="btn btn-primary" onclick="filterLogs()">
|
|
<i class="fas fa-search"></i> 검색
|
|
</button>
|
|
</div>
|
|
<div class="logs-container">
|
|
<div class="loading-spinner">
|
|
<i class="fas fa-spinner fa-spin"></i> 로그 로딩 중...
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// 로그 데이터 로드
|
|
await loadLogsData();
|
|
|
|
} catch (error) {
|
|
console.error('시스템 로그 로드 오류:', error);
|
|
container.innerHTML = `
|
|
<div class="error-message">
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
<p>시스템 로그를 불러오는 중 오류가 발생했습니다.</p>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
// 로그 데이터 로드
|
|
async function loadLogsData() {
|
|
try {
|
|
const response = await apiRequest('/api/system/logs/activity', 'GET');
|
|
const logsContainer = document.querySelector('.logs-container');
|
|
|
|
if (response.success && response.data) {
|
|
displayLogs(response.data, logsContainer);
|
|
} else {
|
|
logsContainer.innerHTML = '<p>로그 데이터가 없습니다.</p>';
|
|
}
|
|
} catch (error) {
|
|
console.error('로그 데이터 로드 오류:', error);
|
|
document.querySelector('.logs-container').innerHTML = '<p>로그 데이터를 불러올 수 없습니다.</p>';
|
|
}
|
|
}
|
|
|
|
// 로그 표시
|
|
function displayLogs(logs, container) {
|
|
if (!logs || logs.length === 0) {
|
|
container.innerHTML = '<p>표시할 로그가 없습니다.</p>';
|
|
return;
|
|
}
|
|
|
|
const html = `
|
|
<table class="logs-table">
|
|
<thead>
|
|
<tr>
|
|
<th>시간</th>
|
|
<th>유형</th>
|
|
<th>사용자</th>
|
|
<th>내용</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
${logs.map(log => `
|
|
<tr>
|
|
<td>${formatDate(log.created_at)}</td>
|
|
<td><span class="log-type ${log.type}">${log.type}</span></td>
|
|
<td>${log.username || '-'}</td>
|
|
<td>${log.description}</td>
|
|
</tr>
|
|
`).join('')}
|
|
</tbody>
|
|
</table>
|
|
`;
|
|
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
// 로그 필터링
|
|
function filterLogs() {
|
|
console.log('로그 필터링 실행');
|
|
// 실제 구현은 추후 추가
|
|
showNotification('로그 필터링 기능은 개발 중입니다.', 'info');
|
|
}
|
|
|
|
// 데이터베이스 관리 열기
|
|
function openDatabaseManagement() {
|
|
console.log('데이터베이스 관리 버튼 클릭됨');
|
|
showNotification('데이터베이스 관리 기능은 개발 중입니다.', 'info');
|
|
}
|
|
|
|
// 시스템 설정 열기
|
|
function openSystemSettings() {
|
|
console.log('시스템 설정 버튼 클릭됨');
|
|
showNotification('시스템 설정 기능은 개발 중입니다.', 'info');
|
|
}
|
|
|
|
// 백업 관리 열기
|
|
function openBackupManagement() {
|
|
console.log('백업 관리 버튼 클릭됨');
|
|
showNotification('백업 관리 기능은 개발 중입니다.', 'info');
|
|
}
|
|
|
|
// 모니터링 열기
|
|
function openMonitoring() {
|
|
console.log('모니터링 버튼 클릭됨');
|
|
showNotification('모니터링 기능은 개발 중입니다.', 'info');
|
|
}
|
|
|
|
// 모달 닫기
|
|
function closeModal(modalId) {
|
|
const modal = document.getElementById(modalId);
|
|
if (modal) {
|
|
modal.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
// 로그아웃
|
|
function logout() {
|
|
if (confirm('로그아웃 하시겠습니까?')) {
|
|
localStorage.removeItem('token');
|
|
localStorage.removeItem('user');
|
|
window.location.href = '/';
|
|
}
|
|
}
|
|
|
|
// 알림 표시
|
|
function showNotification(message, type = 'info') {
|
|
// 간단한 알림 표시 (나중에 토스트 라이브러리로 교체 가능)
|
|
const notification = document.createElement('div');
|
|
notification.className = `notification notification-${type}`;
|
|
notification.textContent = message;
|
|
|
|
document.body.appendChild(notification);
|
|
|
|
setTimeout(() => {
|
|
notification.remove();
|
|
}, 5000);
|
|
}
|
|
|
|
// 사용자 편집
|
|
async function editUser(userId) {
|
|
try {
|
|
// 사용자 정보 가져오기
|
|
const response = await apiRequest(`/api/system/users`, 'GET');
|
|
if (!response.success) {
|
|
throw new Error('사용자 정보를 가져올 수 없습니다.');
|
|
}
|
|
|
|
const user = response.data.find(u => u.user_id === userId);
|
|
if (!user) {
|
|
throw new Error('해당 사용자를 찾을 수 없습니다.');
|
|
}
|
|
|
|
// 편집 폼 표시
|
|
showUserEditForm(user);
|
|
|
|
} catch (error) {
|
|
console.error('사용자 편집 오류:', error);
|
|
showNotification('사용자 정보를 불러오는 중 오류가 발생했습니다.', 'error');
|
|
}
|
|
}
|
|
|
|
// 사용자 편집 폼 표시
|
|
function showUserEditForm(user) {
|
|
const formHtml = `
|
|
<div class="user-edit-form">
|
|
<h4><i class="fas fa-user-edit"></i> 사용자 정보 수정</h4>
|
|
<form id="edit-user-form">
|
|
<div class="form-group">
|
|
<label for="edit-username">사용자명</label>
|
|
<input type="text" id="edit-username" value="${user.username}" disabled>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="edit-name">이름</label>
|
|
<input type="text" id="edit-name" value="${user.name || ''}" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="edit-email">이메일</label>
|
|
<input type="email" id="edit-email" value="${user.email || ''}">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="edit-role">권한</label>
|
|
<select id="edit-role" required>
|
|
<option value="system" ${user.role === 'system' ? 'selected' : ''}>시스템</option>
|
|
<option value="admin" ${user.role === 'admin' ? 'selected' : ''}>관리자</option>
|
|
<option value="leader" ${user.role === 'leader' ? 'selected' : ''}>그룹장</option>
|
|
<option value="user" ${user.role === 'user' ? 'selected' : ''}>사용자</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="edit-is-active">상태</label>
|
|
<select id="edit-is-active" required>
|
|
<option value="1" ${user.is_active ? 'selected' : ''}>활성</option>
|
|
<option value="0" ${!user.is_active ? 'selected' : ''}>비활성</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="edit-worker-id">작업자 ID</label>
|
|
<input type="number" id="edit-worker-id" value="${user.worker_id || ''}">
|
|
</div>
|
|
<div class="form-actions">
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="fas fa-save"></i> 저장
|
|
</button>
|
|
<button type="button" class="btn btn-secondary" onclick="closeModal('account-modal')">
|
|
<i class="fas fa-times"></i> 취소
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
`;
|
|
|
|
const container = document.getElementById('account-management-content');
|
|
container.innerHTML = formHtml;
|
|
|
|
// 폼 제출 이벤트 리스너
|
|
document.getElementById('edit-user-form').addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
await updateUser(user.user_id);
|
|
});
|
|
}
|
|
|
|
// 사용자 정보 업데이트
|
|
async function updateUser(userId) {
|
|
try {
|
|
const formData = {
|
|
name: document.getElementById('edit-name').value,
|
|
email: document.getElementById('edit-email').value || null,
|
|
role: document.getElementById('edit-role').value,
|
|
access_level: document.getElementById('edit-role').value,
|
|
is_active: parseInt(document.getElementById('edit-is-active').value),
|
|
worker_id: document.getElementById('edit-worker-id').value || null
|
|
};
|
|
|
|
const response = await apiRequest(`/api/system/users/${userId}`, 'PUT', formData);
|
|
|
|
if (response.success) {
|
|
showNotification('사용자 정보가 성공적으로 업데이트되었습니다.', 'success');
|
|
closeModal('account-modal');
|
|
// 계정 관리 다시 로드
|
|
setTimeout(() => openAccountManagement(), 500);
|
|
} else {
|
|
throw new Error(response.error || '업데이트에 실패했습니다.');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('사용자 업데이트 오류:', error);
|
|
showNotification('사용자 정보 업데이트 중 오류가 발생했습니다.', 'error');
|
|
}
|
|
}
|
|
|
|
// 사용자 삭제
|
|
async function deleteUser(userId) {
|
|
try {
|
|
// 사용자 정보 가져오기
|
|
const response = await apiRequest(`/api/system/users`, 'GET');
|
|
if (!response.success) {
|
|
throw new Error('사용자 정보를 가져올 수 없습니다.');
|
|
}
|
|
|
|
const user = response.data.find(u => u.user_id === userId);
|
|
if (!user) {
|
|
throw new Error('해당 사용자를 찾을 수 없습니다.');
|
|
}
|
|
|
|
// 삭제 확인
|
|
if (!confirm(`정말로 사용자 '${user.username}'를 삭제하시겠습니까?\n\n이 작업은 되돌릴 수 없습니다.`)) {
|
|
return;
|
|
}
|
|
|
|
// 사용자 삭제 요청
|
|
const deleteResponse = await apiRequest(`/api/system/users/${userId}`, 'DELETE');
|
|
|
|
if (deleteResponse.success) {
|
|
showNotification('사용자가 성공적으로 삭제되었습니다.', 'success');
|
|
// 계정 관리 다시 로드
|
|
setTimeout(() => {
|
|
const container = document.getElementById('account-management-content');
|
|
if (container) {
|
|
loadAccountManagementContent(container);
|
|
}
|
|
}, 500);
|
|
} else {
|
|
throw new Error(deleteResponse.error || '삭제에 실패했습니다.');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('사용자 삭제 오류:', error);
|
|
showNotification('사용자 삭제 중 오류가 발생했습니다.', 'error');
|
|
}
|
|
}
|
|
|
|
// 새 사용자 생성 폼 열기
|
|
function openCreateUserForm() {
|
|
const formHtml = `
|
|
<div class="user-create-form">
|
|
<h4><i class="fas fa-user-plus"></i> 새 사용자 생성</h4>
|
|
<form id="create-user-form">
|
|
<div class="form-group">
|
|
<label for="create-username">사용자명 *</label>
|
|
<input type="text" id="create-username" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="create-password">비밀번호 *</label>
|
|
<input type="password" id="create-password" required minlength="6">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="create-name">이름 *</label>
|
|
<input type="text" id="create-name" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="create-email">이메일</label>
|
|
<input type="email" id="create-email">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="create-role">권한 *</label>
|
|
<select id="create-role" required>
|
|
<option value="">권한 선택</option>
|
|
<option value="system">시스템</option>
|
|
<option value="admin">관리자</option>
|
|
<option value="leader">그룹장</option>
|
|
<option value="user">사용자</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="create-worker-id">작업자 ID</label>
|
|
<input type="number" id="create-worker-id">
|
|
</div>
|
|
<div class="form-actions">
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="fas fa-plus"></i> 생성
|
|
</button>
|
|
<button type="button" class="btn btn-secondary" onclick="loadAccountManagementContent(document.getElementById('account-management-content'))">
|
|
<i class="fas fa-arrow-left"></i> 돌아가기
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
`;
|
|
|
|
const container = document.getElementById('account-management-content');
|
|
container.innerHTML = formHtml;
|
|
|
|
// 폼 제출 이벤트 리스너
|
|
document.getElementById('create-user-form').addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
await createUser();
|
|
});
|
|
}
|
|
|
|
// 새 사용자 생성
|
|
async function createUser() {
|
|
try {
|
|
const formData = {
|
|
username: document.getElementById('create-username').value,
|
|
password: document.getElementById('create-password').value,
|
|
name: document.getElementById('create-name').value,
|
|
email: document.getElementById('create-email').value || null,
|
|
role: document.getElementById('create-role').value,
|
|
access_level: document.getElementById('create-role').value,
|
|
worker_id: document.getElementById('create-worker-id').value || null
|
|
};
|
|
|
|
const response = await apiRequest('/api/system/users', 'POST', formData);
|
|
|
|
if (response.success) {
|
|
showNotification('새 사용자가 성공적으로 생성되었습니다.', 'success');
|
|
// 계정 관리 목록으로 돌아가기
|
|
setTimeout(() => {
|
|
const container = document.getElementById('account-management-content');
|
|
loadAccountManagementContent(container);
|
|
}, 500);
|
|
} else {
|
|
throw new Error(response.error || '사용자 생성에 실패했습니다.');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('사용자 생성 오류:', error);
|
|
showNotification('사용자 생성 중 오류가 발생했습니다.', 'error');
|
|
}
|
|
}
|
|
|
|
// 사용자 필터링
|
|
function filterUsers() {
|
|
const searchTerm = document.getElementById('user-search').value.toLowerCase();
|
|
const roleFilter = document.getElementById('role-filter').value;
|
|
const rows = document.querySelectorAll('#users-tbody tr');
|
|
|
|
rows.forEach(row => {
|
|
const username = row.cells[1].textContent.toLowerCase();
|
|
const name = row.cells[2].textContent.toLowerCase();
|
|
const role = row.querySelector('.role-badge').textContent.toLowerCase();
|
|
|
|
const matchesSearch = username.includes(searchTerm) || name.includes(searchTerm);
|
|
const matchesRole = !roleFilter || role.includes(roleFilter);
|
|
|
|
row.style.display = matchesSearch && matchesRole ? '' : 'none';
|
|
});
|
|
}
|
|
|
|
// 모달 관련 함수들만 전역으로 노출 (동적으로 생성되는 HTML에서 사용)
|
|
window.closeModal = closeModal;
|
|
window.editUser = editUser;
|
|
window.deleteUser = deleteUser;
|
|
window.openCreateUserForm = openCreateUserForm;
|
|
window.filterUsers = filterUsers;
|
|
window.filterLogs = filterLogs;
|
|
|
|
// 테스트용 전역 함수
|
|
window.testFunction = function() {
|
|
console.log('🧪 테스트 함수 호출됨!');
|
|
alert('테스트 함수가 정상적으로 작동합니다!');
|
|
};
|
|
|
|
console.log('🌐 전역 함수들 노출 완료');
|
|
|
|
// 모달 외부 클릭 시 닫기
|
|
window.onclick = function(event) {
|
|
const modals = document.querySelectorAll('.modal');
|
|
modals.forEach(modal => {
|
|
if (event.target === modal) {
|
|
modal.style.display = 'none';
|
|
}
|
|
});
|
|
};
|