- Flask Blueprint 아키텍처로 전환 (dashboard, upload, backup, status) - app.py 681줄 95줄로 축소 (86% 감소) - HTML 템플릿 모듈화 (base.html + 기능별 templates) - CSS/JS 파일 분리 (common + 기능별 파일) - 대시보드 기능 추가 (통계, 주간 예보, 방문객 추이) - 파일 업로드 웹 인터페이스 구현 - 백업/복구 관리 UI 구현 - Docker 배포 환경 개선 - .gitignore 업데이트 (uploads, backups, cache 등)
188 lines
5.1 KiB
JavaScript
188 lines
5.1 KiB
JavaScript
/* ===== 파일 업로드 JavaScript ===== */
|
|
|
|
const FILE_LIST = [];
|
|
|
|
/**
|
|
* 파일 업로드 UI를 초기화합니다.
|
|
*/
|
|
function initializeUploadUI() {
|
|
setupDropZone();
|
|
setupFileButton();
|
|
checkSystemStatus();
|
|
}
|
|
|
|
/**
|
|
* 드롭존을 설정합니다.
|
|
*/
|
|
function setupDropZone() {
|
|
const dropZone = document.getElementById('drop-zone');
|
|
if (!dropZone) return;
|
|
|
|
dropZone.addEventListener('dragover', (e) => {
|
|
e.preventDefault();
|
|
dropZone.classList.add('dragover');
|
|
});
|
|
|
|
dropZone.addEventListener('dragleave', () => {
|
|
dropZone.classList.remove('dragover');
|
|
});
|
|
|
|
dropZone.addEventListener('drop', (e) => {
|
|
e.preventDefault();
|
|
dropZone.classList.remove('dragover');
|
|
handleFiles(e.dataTransfer.files);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 파일 선택 버튼을 설정합니다.
|
|
*/
|
|
function setupFileButton() {
|
|
const fileSelectBtn = document.getElementById('file-select-btn');
|
|
if (!fileSelectBtn) return;
|
|
|
|
fileSelectBtn.addEventListener('click', () => {
|
|
const input = document.createElement('input');
|
|
input.type = 'file';
|
|
input.multiple = true;
|
|
input.accept = '.xlsx,.xls,.csv';
|
|
input.addEventListener('change', (e) => {
|
|
handleFiles(e.target.files);
|
|
});
|
|
input.click();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 파일들을 처리합니다.
|
|
* @param {FileList} files - 선택된 파일들
|
|
*/
|
|
function handleFiles(files) {
|
|
for (let file of files) {
|
|
FILE_LIST.push(file);
|
|
}
|
|
updateFileList();
|
|
}
|
|
|
|
/**
|
|
* 파일 목록을 화면에 업데이트합니다.
|
|
*/
|
|
function updateFileList() {
|
|
const fileListDiv = document.getElementById('file-list');
|
|
let html = '';
|
|
|
|
FILE_LIST.forEach((file, index) => {
|
|
html += `
|
|
<div class="file-item">
|
|
<div>
|
|
<div class="file-name">
|
|
<i class="bi bi-file-earmark"></i> ${file.name}
|
|
</div>
|
|
<small style="color: #999;">${formatFileSize(file.size)} MB</small>
|
|
</div>
|
|
<i class="bi bi-x-circle file-item-remove" onclick="removeFile(${index})"></i>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
fileListDiv.innerHTML = html;
|
|
}
|
|
|
|
/**
|
|
* 파일을 목록에서 제거합니다.
|
|
* @param {number} index - 제거할 파일의 인덱스
|
|
*/
|
|
function removeFile(index) {
|
|
FILE_LIST.splice(index, 1);
|
|
updateFileList();
|
|
}
|
|
|
|
/**
|
|
* 파일 목록을 비웁니다.
|
|
*/
|
|
function clearFileList() {
|
|
FILE_LIST.length = 0;
|
|
updateFileList();
|
|
document.getElementById('upload-result').innerHTML = '';
|
|
}
|
|
|
|
/**
|
|
* 파일들을 업로드합니다.
|
|
*/
|
|
async function uploadFiles() {
|
|
if (FILE_LIST.length === 0) {
|
|
showAlert('업로드할 파일을 선택하세요.', 'warning');
|
|
return;
|
|
}
|
|
|
|
const formData = new FormData();
|
|
FILE_LIST.forEach(file => {
|
|
formData.append('files', file);
|
|
});
|
|
|
|
document.getElementById('upload-progress').style.display = 'block';
|
|
document.getElementById('upload-btn').disabled = true;
|
|
|
|
try {
|
|
const response = await fetch('/api/upload', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
let resultHtml = data.success ?
|
|
'<div class="alert alert-success">' :
|
|
'<div class="alert alert-warning">';
|
|
|
|
resultHtml += '<strong>업로드 완료!</strong><br>';
|
|
|
|
data.files.forEach(file => {
|
|
const icon = file.status === 'success' ? '✓' : '✗';
|
|
resultHtml += `${icon} ${file.filename}: ${file.message}<br>`;
|
|
});
|
|
|
|
resultHtml += '</div>';
|
|
document.getElementById('upload-result').innerHTML = resultHtml;
|
|
|
|
showAlert('업로드가 완료되었습니다.', 'success');
|
|
|
|
setTimeout(() => {
|
|
clearFileList();
|
|
// 대시보드 새로고침
|
|
if (typeof loadDashboard === 'function') {
|
|
loadDashboard();
|
|
}
|
|
}, 2000);
|
|
} catch (error) {
|
|
showAlert('업로드 실패: ' + error.message, 'danger');
|
|
} finally {
|
|
document.getElementById('upload-progress').style.display = 'none';
|
|
document.getElementById('upload-btn').disabled = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 시스템 상태를 확인합니다.
|
|
*/
|
|
async function checkSystemStatus() {
|
|
try {
|
|
const data = await apiCall('/api/status');
|
|
|
|
const dbStatus = document.getElementById('db-status');
|
|
const uploadStatus = document.getElementById('upload-folder-status');
|
|
|
|
if (dbStatus) {
|
|
dbStatus.textContent = data.database ? '연결됨' : '연결 안됨';
|
|
dbStatus.className = `badge ${data.database ? 'bg-success' : 'bg-danger'}`;
|
|
}
|
|
|
|
if (uploadStatus) {
|
|
uploadStatus.textContent = data.upload_folder ? '정상' : '오류';
|
|
uploadStatus.className = `badge ${data.upload_folder ? 'bg-success' : 'bg-danger'}`;
|
|
}
|
|
} catch (error) {
|
|
console.error('시스템 상태 확인 실패:', error);
|
|
}
|
|
}
|