- 프로필 드롭다운에서 중복 메뉴 제거 (프로필 관리 + 계정 설정 → 계정 설정) - 관리자 전용 메뉴 추가: - 백업/복원 관리 페이지 (backup-restore.html) - 시스템 로그 페이지 (logs.html) - 관리자 메뉴에 색상 구분 아이콘 적용 - account-settings.html 삭제 (profile.html로 통합)
318 lines
16 KiB
HTML
318 lines
16 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>백업/복원 관리 - Document Server</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
<script src="static/js/auth-guard.js"></script>
|
|
</head>
|
|
<body class="bg-gray-50">
|
|
<!-- 헤더 -->
|
|
<div id="header-container"></div>
|
|
|
|
<!-- 메인 컨텐츠 -->
|
|
<main class="container mx-auto px-4 py-8" x-data="backupRestoreApp()">
|
|
<div class="max-w-4xl mx-auto">
|
|
<!-- 페이지 헤더 -->
|
|
<div class="mb-8">
|
|
<h1 class="text-3xl font-bold text-gray-900 mb-2">백업/복원 관리</h1>
|
|
<p class="text-gray-600">시스템 데이터를 백업하고 복원할 수 있습니다.</p>
|
|
</div>
|
|
|
|
<!-- 백업 섹션 -->
|
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 mb-8">
|
|
<div class="px-6 py-4 border-b border-gray-200">
|
|
<h2 class="text-xl font-semibold text-gray-900 flex items-center">
|
|
<i class="fas fa-download text-blue-500 mr-3"></i>
|
|
데이터 백업
|
|
</h2>
|
|
</div>
|
|
<div class="p-6">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<!-- 전체 백업 -->
|
|
<div class="border border-gray-200 rounded-lg p-4">
|
|
<h3 class="font-semibold text-gray-900 mb-2">전체 백업</h3>
|
|
<p class="text-sm text-gray-600 mb-4">데이터베이스와 업로드된 파일을 모두 백업합니다.</p>
|
|
<button @click="createBackup('full')"
|
|
:disabled="loading"
|
|
class="w-full bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed">
|
|
<i class="fas fa-database mr-2"></i>
|
|
<span x-show="!loading">전체 백업 생성</span>
|
|
<span x-show="loading">백업 중...</span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- 데이터베이스만 백업 -->
|
|
<div class="border border-gray-200 rounded-lg p-4">
|
|
<h3 class="font-semibold text-gray-900 mb-2">데이터베이스 백업</h3>
|
|
<p class="text-sm text-gray-600 mb-4">데이터베이스만 백업합니다 (파일 제외).</p>
|
|
<button @click="createBackup('db')"
|
|
:disabled="loading"
|
|
class="w-full bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed">
|
|
<i class="fas fa-table mr-2"></i>
|
|
<span x-show="!loading">DB 백업 생성</span>
|
|
<span x-show="loading">백업 중...</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 백업 상태 -->
|
|
<div x-show="backupStatus" class="mt-4 p-4 rounded-lg" :class="backupStatus.type === 'success' ? 'bg-green-50 text-green-800' : 'bg-red-50 text-red-800'">
|
|
<div class="flex items-center">
|
|
<i :class="backupStatus.type === 'success' ? 'fas fa-check-circle text-green-500' : 'fas fa-exclamation-circle text-red-500'" class="mr-2"></i>
|
|
<span x-text="backupStatus.message"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 백업 목록 -->
|
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 mb-8">
|
|
<div class="px-6 py-4 border-b border-gray-200">
|
|
<h2 class="text-xl font-semibold text-gray-900 flex items-center">
|
|
<i class="fas fa-history text-green-500 mr-3"></i>
|
|
백업 목록
|
|
</h2>
|
|
</div>
|
|
<div class="p-6">
|
|
<div x-show="backups.length === 0" class="text-center py-8 text-gray-500">
|
|
<i class="fas fa-inbox text-4xl mb-4"></i>
|
|
<p>백업 파일이 없습니다.</p>
|
|
</div>
|
|
|
|
<div x-show="backups.length > 0" class="space-y-4">
|
|
<template x-for="backup in backups" :key="backup.id">
|
|
<div class="flex items-center justify-between p-4 border border-gray-200 rounded-lg">
|
|
<div class="flex items-center space-x-4">
|
|
<div class="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
|
|
<i class="fas fa-file-archive text-blue-600"></i>
|
|
</div>
|
|
<div>
|
|
<h4 class="font-medium text-gray-900" x-text="backup.name"></h4>
|
|
<p class="text-sm text-gray-500">
|
|
<span x-text="backup.type === 'full' ? '전체 백업' : 'DB 백업'"></span>
|
|
• <span x-text="backup.size"></span>
|
|
• <span x-text="backup.date"></span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center space-x-2">
|
|
<button @click="downloadBackup(backup)"
|
|
class="px-3 py-1 text-sm bg-blue-600 text-white rounded hover:bg-blue-700">
|
|
<i class="fas fa-download mr-1"></i>
|
|
다운로드
|
|
</button>
|
|
<button @click="deleteBackup(backup)"
|
|
class="px-3 py-1 text-sm bg-red-600 text-white rounded hover:bg-red-700">
|
|
<i class="fas fa-trash mr-1"></i>
|
|
삭제
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 복원 섹션 -->
|
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200">
|
|
<div class="px-6 py-4 border-b border-gray-200">
|
|
<h2 class="text-xl font-semibold text-gray-900 flex items-center">
|
|
<i class="fas fa-upload text-orange-500 mr-3"></i>
|
|
데이터 복원
|
|
</h2>
|
|
</div>
|
|
<div class="p-6">
|
|
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-4 mb-6">
|
|
<div class="flex items-center">
|
|
<i class="fas fa-exclamation-triangle text-yellow-600 mr-2"></i>
|
|
<span class="text-yellow-800 font-medium">주의사항</span>
|
|
</div>
|
|
<p class="text-yellow-700 text-sm mt-2">
|
|
복원 작업은 현재 데이터를 완전히 덮어씁니다. 복원 전에 반드시 현재 데이터를 백업하세요.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="space-y-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">백업 파일 선택</label>
|
|
<input type="file"
|
|
@change="handleFileSelect"
|
|
accept=".sql,.tar.gz,.zip"
|
|
class="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-medium file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100">
|
|
</div>
|
|
|
|
<div x-show="selectedFile">
|
|
<div class="bg-gray-50 rounded-lg p-4">
|
|
<h4 class="font-medium text-gray-900 mb-2">선택된 파일</h4>
|
|
<p class="text-sm text-gray-600" x-text="selectedFile?.name"></p>
|
|
<p class="text-sm text-gray-500" x-text="selectedFile ? formatFileSize(selectedFile.size) : ''"></p>
|
|
</div>
|
|
</div>
|
|
|
|
<button @click="restoreBackup()"
|
|
:disabled="!selectedFile || loading"
|
|
class="w-full bg-orange-600 text-white px-4 py-3 rounded-lg hover:bg-orange-700 disabled:opacity-50 disabled:cursor-not-allowed font-medium">
|
|
<i class="fas fa-upload mr-2"></i>
|
|
<span x-show="!loading">복원 실행</span>
|
|
<span x-show="loading">복원 중...</span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- 복원 상태 -->
|
|
<div x-show="restoreStatus" class="mt-4 p-4 rounded-lg" :class="restoreStatus.type === 'success' ? 'bg-green-50 text-green-800' : 'bg-red-50 text-red-800'">
|
|
<div class="flex items-center">
|
|
<i :class="restoreStatus.type === 'success' ? 'fas fa-check-circle text-green-500' : 'fas fa-exclamation-circle text-red-500'" class="mr-2"></i>
|
|
<span x-text="restoreStatus.message"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<!-- JavaScript -->
|
|
<script src="static/js/api.js"></script>
|
|
<script src="static/js/header-loader.js"></script>
|
|
<script>
|
|
function backupRestoreApp() {
|
|
return {
|
|
loading: false,
|
|
backups: [],
|
|
selectedFile: null,
|
|
backupStatus: null,
|
|
restoreStatus: null,
|
|
|
|
init() {
|
|
this.loadBackups();
|
|
},
|
|
|
|
async loadBackups() {
|
|
try {
|
|
// 실제 구현에서는 백엔드 API 호출
|
|
// this.backups = await window.api.getBackups();
|
|
|
|
// 임시 데모 데이터
|
|
this.backups = [
|
|
{
|
|
id: '1',
|
|
name: 'backup_2025_09_03_full.tar.gz',
|
|
type: 'full',
|
|
size: '125.4 MB',
|
|
date: '2025년 9월 3일 15:30'
|
|
},
|
|
{
|
|
id: '2',
|
|
name: 'backup_2025_09_02_db.sql',
|
|
type: 'db',
|
|
size: '2.1 MB',
|
|
date: '2025년 9월 2일 10:15'
|
|
}
|
|
];
|
|
} catch (error) {
|
|
console.error('백업 목록 로드 실패:', error);
|
|
}
|
|
},
|
|
|
|
async createBackup(type) {
|
|
this.loading = true;
|
|
this.backupStatus = null;
|
|
|
|
try {
|
|
// 실제 구현에서는 백엔드 API 호출
|
|
// const result = await window.api.createBackup(type);
|
|
|
|
// 임시 시뮬레이션
|
|
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
|
|
this.backupStatus = {
|
|
type: 'success',
|
|
message: `${type === 'full' ? '전체' : 'DB'} 백업이 성공적으로 생성되었습니다.`
|
|
};
|
|
|
|
this.loadBackups();
|
|
} catch (error) {
|
|
this.backupStatus = {
|
|
type: 'error',
|
|
message: '백업 생성 중 오류가 발생했습니다: ' + error.message
|
|
};
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
|
|
downloadBackup(backup) {
|
|
// 실제 구현에서는 백엔드에서 파일 다운로드
|
|
alert(`${backup.name} 다운로드를 시작합니다.`);
|
|
},
|
|
|
|
async deleteBackup(backup) {
|
|
if (!confirm(`${backup.name}을(를) 삭제하시겠습니까?`)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// 실제 구현에서는 백엔드 API 호출
|
|
// await window.api.deleteBackup(backup.id);
|
|
|
|
this.backups = this.backups.filter(b => b.id !== backup.id);
|
|
alert('백업이 삭제되었습니다.');
|
|
} catch (error) {
|
|
alert('백업 삭제 중 오류가 발생했습니다: ' + error.message);
|
|
}
|
|
},
|
|
|
|
handleFileSelect(event) {
|
|
this.selectedFile = event.target.files[0];
|
|
this.restoreStatus = null;
|
|
},
|
|
|
|
async restoreBackup() {
|
|
if (!this.selectedFile) return;
|
|
|
|
if (!confirm('현재 데이터가 모두 삭제되고 백업 데이터로 복원됩니다. 계속하시겠습니까?')) {
|
|
return;
|
|
}
|
|
|
|
this.loading = true;
|
|
this.restoreStatus = null;
|
|
|
|
try {
|
|
// 실제 구현에서는 백엔드 API 호출
|
|
// const formData = new FormData();
|
|
// formData.append('backup_file', this.selectedFile);
|
|
// await window.api.restoreBackup(formData);
|
|
|
|
// 임시 시뮬레이션
|
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
|
|
this.restoreStatus = {
|
|
type: 'success',
|
|
message: '백업이 성공적으로 복원되었습니다. 페이지를 새로고침해주세요.'
|
|
};
|
|
} catch (error) {
|
|
this.restoreStatus = {
|
|
type: 'error',
|
|
message: '복원 중 오류가 발생했습니다: ' + error.message
|
|
};
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
|
|
formatFileSize(bytes) {
|
|
if (bytes === 0) return '0 Bytes';
|
|
const k = 1024;
|
|
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|