fix: 일일보고서 권한 체크를 페이지 권한 기반으로 변경 및 Edge 호환성 개선
- reports.py: role==admin 하드코딩 → check_page_access로 변경하여 reports/reports_daily 페이지 권한 보유자도 미리보기/엑셀 내보내기 가능 - page_permissions.py: 동기 헬퍼 함수 check_page_access() 추가 (기존 async API 엔드포인트를 await 없이 호출하는 버그 해결) - reports-daily.html: 에러 핸들링 강화 (401/403 구분), blob download revokeObjectURL 지연 처리 (Edge 호환) - nginx.conf: proxy_read_timeout/proxy_buffering off 추가 - reports.py: JSONResponse+jsonable_encoder로 명시적 직렬화, Content-Disposition에 ASCII 폴백 파일명 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,11 @@ server {
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
|
||||
# 엑셀 생성 등 시간이 걸리는 API 대응
|
||||
proxy_read_timeout 120s;
|
||||
proxy_send_timeout 120s;
|
||||
proxy_buffering off;
|
||||
}
|
||||
|
||||
# 업로드 파일
|
||||
|
||||
@@ -11,17 +11,22 @@
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
|
||||
<!-- 공통 스타일 -->
|
||||
<link rel="stylesheet" href="/static/css/tkqc-common.css">
|
||||
|
||||
<!-- Custom Styles -->
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
.report-card {
|
||||
transition: all 0.2s ease;
|
||||
border-left: 4px solid transparent;
|
||||
}
|
||||
|
||||
.report-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||
border-left-color: #10b981;
|
||||
}
|
||||
|
||||
@@ -29,6 +34,10 @@
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.stats-card:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.issue-row {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
@@ -38,11 +47,11 @@
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<body class="bg-gray-50 min-h-screen">
|
||||
<!-- 공통 헤더가 여기에 자동으로 삽입됩니다 -->
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="container mx-auto px-4 py-8" style="padding-top: 72px;">
|
||||
<main class="container mx-auto px-4 py-8" style="padding-top: 80px;">
|
||||
<!-- 페이지 헤더 -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 mb-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
@@ -294,9 +303,16 @@
|
||||
|
||||
try {
|
||||
const apiUrl = window.API_BASE_URL || '/api';
|
||||
const token = TokenManager.getToken();
|
||||
if (!token) {
|
||||
alert('인증 토큰이 없습니다. 다시 로그인해주세요.');
|
||||
window.location.href = window.authManager ? window.authManager._getLoginUrl() : '/login.html';
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch(`${apiUrl}/reports/daily-preview?project_id=${selectedProjectId}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${TokenManager.getToken()}`
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
@@ -304,11 +320,20 @@
|
||||
previewData = await response.json();
|
||||
displayPreview(previewData);
|
||||
} else {
|
||||
alert('미리보기 로드에 실패했습니다.');
|
||||
const errorText = await response.text().catch(() => '');
|
||||
console.error('미리보기 로드 실패:', response.status, errorText);
|
||||
if (response.status === 401) {
|
||||
alert('인증이 만료되었습니다. 다시 로그인해주세요.');
|
||||
window.location.href = window.authManager ? window.authManager._getLoginUrl() : '/login.html';
|
||||
} else if (response.status === 403) {
|
||||
alert('권한이 없습니다. 품질팀 계정으로 로그인해주세요.');
|
||||
} else {
|
||||
alert(`미리보기 로드에 실패했습니다. (${response.status})`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('미리보기 로드 오류:', error);
|
||||
alert('미리보기 로드 중 오류가 발생했습니다.');
|
||||
alert('미리보기 로드 중 오류가 발생했습니다: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -419,11 +444,18 @@
|
||||
button.disabled = true;
|
||||
|
||||
const apiUrl = window.API_BASE_URL || '/api';
|
||||
const token = TokenManager.getToken();
|
||||
if (!token) {
|
||||
alert('인증 토큰이 없습니다. 다시 로그인해주세요.');
|
||||
window.location.href = window.authManager ? window.authManager._getLoginUrl() : '/login.html';
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch(`${apiUrl}/reports/daily-export`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${TokenManager.getToken()}`
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
project_id: parseInt(selectedProjectId)
|
||||
@@ -444,8 +476,12 @@
|
||||
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(a);
|
||||
|
||||
// Edge 호환: revokeObjectURL을 지연시켜 다운로드가 시작될 시간 확보
|
||||
setTimeout(() => {
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(a);
|
||||
}, 500);
|
||||
|
||||
// 성공 메시지
|
||||
showSuccessMessage('일일보고서가 성공적으로 생성되었습니다!');
|
||||
@@ -456,13 +492,20 @@
|
||||
}
|
||||
|
||||
} else {
|
||||
const error = await response.text();
|
||||
console.error('보고서 생성 실패:', error);
|
||||
alert('보고서 생성에 실패했습니다. 다시 시도해주세요.');
|
||||
const errorText = await response.text().catch(() => '');
|
||||
console.error('보고서 생성 실패:', response.status, errorText);
|
||||
if (response.status === 401) {
|
||||
alert('인증이 만료되었습니다. 다시 로그인해주세요.');
|
||||
window.location.href = window.authManager ? window.authManager._getLoginUrl() : '/login.html';
|
||||
} else if (response.status === 403) {
|
||||
alert('권한이 없습니다. 품질팀 계정으로 로그인해주세요.');
|
||||
} else {
|
||||
alert(`보고서 생성에 실패했습니다. (${response.status})`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('보고서 생성 오류:', error);
|
||||
alert('보고서 생성 중 오류가 발생했습니다.');
|
||||
alert('보고서 생성 중 오류가 발생했습니다: ' + error.message);
|
||||
} finally {
|
||||
const button = document.getElementById('generateReportBtn');
|
||||
button.innerHTML = '<i class="fas fa-download mr-2"></i>일일보고서 생성';
|
||||
|
||||
Reference in New Issue
Block a user