[TEST] Cloudflare Tunnel 대응 및 리비전 증분 계산 수정
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled

🌐 Nginx 프록시 설정 (테스트용):
- nginx-proxy.conf: /api 요청을 백엔드로 프록시
- docker-compose.proxy.yml: 프록시 서버 설정
- VITE_API_URL=/api 환경변수 설정으로 단일 도메인 접속

🎨 UI 텍스트 변경 (테스트용):
- LoginPage: TK-MP System → BOM 테스트 서버
- LoginPage: 통합 프로젝트 관리 시스템 → BOM 분류 시스템 v1.0
- LogMonitoringPage: 탭 네비게이션 추가 (로그인/활동/시스템 로그)
- SystemSettingsPage: 활동 로그 모니터링 기능 개선

🔧 백엔드 수정 (테스트용):
- files.py: 리비전 증분 계산 로직 수정 (전체 재분류 → 차이분만 분류)
- create_system_admin.py: database_url → get_database_url() 수정

⚠️ 주의: 이 커밋은 테스트 환경에서의 변경사항입니다.
This commit is contained in:
hyungi
2025-09-10 08:49:19 +09:00
parent fe3fd76112
commit 04299542b5
7 changed files with 548 additions and 14 deletions

View File

@@ -3,6 +3,7 @@ import api from '../api';
import { reportError, logUserActionError } from '../utils/errorLogger';
const LogMonitoringPage = ({ onNavigate, user }) => {
const [activeTab, setActiveTab] = useState('login-logs'); // 'login-logs', 'activity-logs', 'system-logs'
const [stats, setStats] = useState({
totalUsers: 0,
activeUsers: 0,
@@ -11,6 +12,7 @@ const LogMonitoringPage = ({ onNavigate, user }) => {
recentErrors: 0
});
const [recentActivity, setRecentActivity] = useState([]);
const [activityLogs, setActivityLogs] = useState([]);
const [frontendErrors, setFrontendErrors] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [message, setMessage] = useState({ type: '', text: '' });
@@ -22,10 +24,26 @@ const LogMonitoringPage = ({ onNavigate, user }) => {
return () => clearInterval(interval);
}, []);
const loadActivityLogs = async () => {
try {
const response = await api.get('/auth/logs/system?limit=50');
if (response.data.success) {
setActivityLogs(response.data.logs);
}
} catch (error) {
console.error('활동 로그 로딩 실패:', error);
}
};
const loadDashboardData = async () => {
try {
setIsLoading(true);
// 활동 로그도 함께 로드
if (activeTab === 'activity-logs') {
await loadActivityLogs();
}
// 병렬로 데이터 로드
const [usersResponse, loginLogsResponse] = await Promise.all([
api.get('/auth/users'),
@@ -158,9 +176,60 @@ const LogMonitoringPage = ({ onNavigate, user }) => {
<h1 style={{ fontSize: '24px', fontWeight: '700', color: '#2d3748', margin: 0 }}>
📈 로그 모니터링
</h1>
<p style={{ color: '#6c757d', fontSize: '14px', margin: '4px 0 0 0' }}>
실시간 시스템 활동 오류 모니터링
</p>
</div>
</div>
{/* 탭 네비게이션 */}
<div style={{ background: 'white', borderBottom: '1px solid #e9ecef', padding: '0 32px' }}>
<div style={{ display: 'flex', gap: '0' }}>
<button
onClick={() => setActiveTab('login-logs')}
style={{
padding: '12px 24px',
border: 'none',
background: activeTab === 'login-logs' ? '#4299e1' : 'transparent',
color: activeTab === 'login-logs' ? 'white' : '#4a5568',
borderRadius: '8px 8px 0 0',
cursor: 'pointer',
fontWeight: '600',
fontSize: '14px',
borderBottom: activeTab === 'login-logs' ? '2px solid #4299e1' : '2px solid transparent'
}}
>
🔐 로그인 로그
</button>
<button
onClick={() => setActiveTab('activity-logs')}
style={{
padding: '12px 24px',
border: 'none',
background: activeTab === 'activity-logs' ? '#4299e1' : 'transparent',
color: activeTab === 'activity-logs' ? 'white' : '#4a5568',
borderRadius: '8px 8px 0 0',
cursor: 'pointer',
fontWeight: '600',
fontSize: '14px',
borderBottom: activeTab === 'activity-logs' ? '2px solid #4299e1' : '2px solid transparent'
}}
>
📊 활동 로그
</button>
<button
onClick={() => setActiveTab('system-logs')}
style={{
padding: '12px 24px',
border: 'none',
background: activeTab === 'system-logs' ? '#4299e1' : 'transparent',
color: activeTab === 'system-logs' ? 'white' : '#4a5568',
borderRadius: '8px 8px 0 0',
cursor: 'pointer',
fontWeight: '600',
fontSize: '14px',
borderBottom: activeTab === 'system-logs' ? '2px solid #4299e1' : '2px solid transparent'
}}
>
🖥 시스템 로그
</button>
</div>
</div>

View File

@@ -46,8 +46,8 @@ const LoginPage = () => {
<div className="login-container">
<div className="login-card">
<div className="login-header">
<h1>🚀 TK-MP System</h1>
<p>통합 프로젝트 관리 시스템</p>
<h1>BOM 테스트 서버</h1>
<p>BOM 분류 시스템 v1.0</p>
</div>
<form onSubmit={handleSubmit} className="login-form">
@@ -105,7 +105,7 @@ const LoginPage = () => {
<div className="login-footer">
<p>계정이 없으신가요? 관리자에게 문의하세요.</p>
<div className="system-info">
<small>TK-MP Project Management System v2.0</small>
<small>BOM 분류 시스템 v1.0</small>
</div>
</div>
</div>

View File

@@ -6,6 +6,9 @@ const SystemSettingsPage = ({ onNavigate, user }) => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [showCreateForm, setShowCreateForm] = useState(false);
const [activeTab, setActiveTab] = useState('users'); // 'users', 'login-logs', 'activity-logs'
const [loginLogs, setLoginLogs] = useState([]);
const [activityLogs, setActivityLogs] = useState([]);
const [newUser, setNewUser] = useState({
username: '',
email: '',
@@ -16,7 +19,12 @@ const SystemSettingsPage = ({ onNavigate, user }) => {
useEffect(() => {
loadUsers();
}, []);
if (activeTab === 'login-logs') {
loadLoginLogs();
} else if (activeTab === 'activity-logs') {
loadActivityLogs();
}
}, [activeTab]);
const loadUsers = async () => {
try {
@@ -33,6 +41,36 @@ const SystemSettingsPage = ({ onNavigate, user }) => {
}
};
const loadLoginLogs = async () => {
try {
setLoading(true);
const response = await api.get('/auth/logs/login?limit=50');
if (response.data.success) {
setLoginLogs(response.data.logs);
}
} catch (err) {
console.error('로그인 로그 로딩 실패:', err);
setError('로그인 로그를 불러오는데 실패했습니다.');
} finally {
setLoading(false);
}
};
const loadActivityLogs = async () => {
try {
setLoading(true);
const response = await api.get('/auth/logs/system?limit=50');
if (response.data.success) {
setActivityLogs(response.data.logs);
}
} catch (err) {
console.error('활동 로그 로딩 실패:', err);
setError('활동 로그를 불러오는데 실패했습니다.');
} finally {
setLoading(false);
}
};
const handleCreateUser = async (e) => {
e.preventDefault();
@@ -104,6 +142,23 @@ const SystemSettingsPage = ({ onNavigate, user }) => {
}
};
const getActivityTypeColor = (activityType) => {
switch (activityType) {
case 'FILE_UPLOAD':
return '#10b981'; // 초록색
case '파일 정보 수정':
return '#f59e0b'; // 주황색
case '엑셀 내보내기':
return '#3b82f6'; // 파란색
case '자재 목록 조회':
return '#8b5cf6'; // 보라색
case 'LOGIN':
return '#6b7280'; // 회색
default:
return '#6b7280';
}
};
// 관리자 권한 확인
if (user?.role !== 'admin') {
return (
@@ -143,9 +198,63 @@ const SystemSettingsPage = ({ onNavigate, user }) => {
시스템 설정
</h1>
<p style={{ color: '#718096', fontSize: '16px' }}>
사용자 계정 관리 시스템 설정
사용자 계정 관리 시스템 로그 모니터링
</p>
</div>
</div>
{/* 탭 네비게이션 */}
<div style={{ marginBottom: '24px', borderBottom: '2px solid #e2e8f0' }}>
<div style={{ display: 'flex', gap: '0' }}>
<button
onClick={() => setActiveTab('users')}
style={{
padding: '12px 24px',
border: 'none',
background: activeTab === 'users' ? '#4299e1' : 'transparent',
color: activeTab === 'users' ? 'white' : '#4a5568',
borderRadius: '8px 8px 0 0',
cursor: 'pointer',
fontWeight: '600',
fontSize: '14px',
borderBottom: activeTab === 'users' ? '2px solid #4299e1' : '2px solid transparent'
}}
>
👥 사용자 관리
</button>
<button
onClick={() => setActiveTab('login-logs')}
style={{
padding: '12px 24px',
border: 'none',
background: activeTab === 'login-logs' ? '#4299e1' : 'transparent',
color: activeTab === 'login-logs' ? 'white' : '#4a5568',
borderRadius: '8px 8px 0 0',
cursor: 'pointer',
fontWeight: '600',
fontSize: '14px',
borderBottom: activeTab === 'login-logs' ? '2px solid #4299e1' : '2px solid transparent'
}}
>
🔐 로그인 로그
</button>
<button
onClick={() => setActiveTab('activity-logs')}
style={{
padding: '12px 24px',
border: 'none',
background: activeTab === 'activity-logs' ? '#4299e1' : 'transparent',
color: activeTab === 'activity-logs' ? 'white' : '#4a5568',
borderRadius: '8px 8px 0 0',
cursor: 'pointer',
fontWeight: '600',
fontSize: '14px',
borderBottom: activeTab === 'activity-logs' ? '2px solid #4299e1' : '2px solid transparent'
}}
>
📊 활동 로그
</button>
</div>
<button
onClick={() => onNavigate('dashboard')}
style={{
@@ -356,12 +465,13 @@ const SystemSettingsPage = ({ onNavigate, user }) => {
</div>
)}
{/* 사용자 목록 */}
{/* 탭별 콘텐츠 */}
{loading ? (
<div style={{ textAlign: 'center', padding: '40px' }}>
<div style={{ fontSize: '16px', color: '#718096' }}>로딩 ...</div>
</div>
) : (
) : activeTab === 'users' ? (
// 사용자 관리 탭
<div style={{ overflowX: 'auto' }}>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
@@ -446,7 +556,105 @@ const SystemSettingsPage = ({ onNavigate, user }) => {
</tbody>
</table>
</div>
)}
) : activeTab === 'login-logs' ? (
// 로그인 로그 탭
<div style={{ overflowX: 'auto' }}>
<h3 style={{ marginBottom: '16px', color: '#2d3748' }}>🔐 로그인 로그</h3>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr style={{ borderBottom: '2px solid #e2e8f0' }}>
<th style={{ padding: '12px', textAlign: 'left', fontWeight: '600', color: '#4a5568' }}>
사용자명
</th>
<th style={{ padding: '12px', textAlign: 'left', fontWeight: '600', color: '#4a5568' }}>
IP 주소
</th>
<th style={{ padding: '12px', textAlign: 'center', fontWeight: '600', color: '#4a5568' }}>
상태
</th>
<th style={{ padding: '12px', textAlign: 'left', fontWeight: '600', color: '#4a5568' }}>
로그인 시간
</th>
</tr>
</thead>
<tbody>
{loginLogs.map((log, index) => (
<tr key={index} style={{ borderBottom: '1px solid #e2e8f0' }}>
<td style={{ padding: '12px', fontWeight: '500' }}>
{log.username}
</td>
<td style={{ padding: '12px', color: '#4a5568' }}>
{log.ip_address}
</td>
<td style={{ padding: '12px', textAlign: 'center' }}>
<span style={{
background: log.status === 'success' ? '#48bb78' : '#f56565',
color: 'white',
padding: '4px 8px',
borderRadius: '12px',
fontSize: '12px'
}}>
{log.status === 'success' ? '성공' : '실패'}
</span>
</td>
<td style={{ padding: '12px', color: '#4a5568' }}>
{new Date(log.login_time).toLocaleString('ko-KR')}
</td>
</tr>
))}
</tbody>
</table>
</div>
) : activeTab === 'activity-logs' ? (
// 활동 로그 탭
<div style={{ overflowX: 'auto' }}>
<h3 style={{ marginBottom: '16px', color: '#2d3748' }}>📊 활동 로그</h3>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr style={{ borderBottom: '2px solid #e2e8f0' }}>
<th style={{ padding: '12px', textAlign: 'left', fontWeight: '600', color: '#4a5568' }}>
사용자명
</th>
<th style={{ padding: '12px', textAlign: 'left', fontWeight: '600', color: '#4a5568' }}>
활동 유형
</th>
<th style={{ padding: '12px', textAlign: 'left', fontWeight: '600', color: '#4a5568' }}>
상세 내용
</th>
<th style={{ padding: '12px', textAlign: 'left', fontWeight: '600', color: '#4a5568' }}>
시간
</th>
</tr>
</thead>
<tbody>
{activityLogs.map((log, index) => (
<tr key={index} style={{ borderBottom: '1px solid #e2e8f0' }}>
<td style={{ padding: '12px', fontWeight: '500' }}>
{log.username}
</td>
<td style={{ padding: '12px' }}>
<span style={{
background: getActivityTypeColor(log.activity_type),
color: 'white',
padding: '4px 8px',
borderRadius: '12px',
fontSize: '12px'
}}>
{log.activity_type}
</span>
</td>
<td style={{ padding: '12px', color: '#4a5568' }}>
{log.activity_description}
</td>
<td style={{ padding: '12px', color: '#4a5568' }}>
{new Date(log.created_at).toLocaleString('ko-KR')}
</td>
</tr>
))}
</tbody>
</table>
</div>
) : null}
</div>
</div>
);