feat: 완전한 사용자 관리 및 로그 모니터링 시스템 구현
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
- 시스템 관리자/관리자 권한별 대시보드 기능 추가 - 사용자 관리 페이지: 계정 생성, 역할 변경, 사용자 삭제 - 시스템 로그 페이지: 로그인 로그, 시스템 오류 로그 조회 - 로그 모니터링 대시보드: 실시간 통계, 최근 활동, 오류 모니터링 - 프론트엔드 ErrorBoundary 및 오류 로깅 시스템 통합 - 계정 설정 페이지: 프로필 업데이트, 비밀번호 변경 - 3단계 권한 시스템 (system/admin/user) 완전 구현 - 시스템 관리자 계정 생성 기능 (hyungi/000000) - 로그인 페이지 테스트 계정 안내 제거 - API 오류 수정: CORS, 이메일 검증, User 모델 import 등
This commit is contained in:
@@ -3,6 +3,12 @@ import SimpleLogin from './SimpleLogin';
|
||||
import BOMWorkspacePage from './pages/BOMWorkspacePage';
|
||||
import NewMaterialsPage from './pages/NewMaterialsPage';
|
||||
import SystemSettingsPage from './pages/SystemSettingsPage';
|
||||
import AccountSettingsPage from './pages/AccountSettingsPage';
|
||||
import UserManagementPage from './pages/UserManagementPage';
|
||||
import SystemLogsPage from './pages/SystemLogsPage';
|
||||
import LogMonitoringPage from './pages/LogMonitoringPage';
|
||||
import ErrorBoundary from './components/ErrorBoundary';
|
||||
import errorLogger from './utils/errorLogger';
|
||||
import './App.css';
|
||||
|
||||
function App() {
|
||||
@@ -12,6 +18,7 @@ function App() {
|
||||
const [currentPage, setCurrentPage] = useState('dashboard');
|
||||
const [pageParams, setPageParams] = useState({});
|
||||
const [selectedProject, setSelectedProject] = useState(null);
|
||||
const [showUserMenu, setShowUserMenu] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// 저장된 토큰 확인
|
||||
@@ -44,6 +51,20 @@ function App() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 사용자 메뉴 외부 클릭 시 닫기
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
if (showUserMenu && !event.target.closest('.user-menu-container')) {
|
||||
setShowUserMenu(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, [showUserMenu]);
|
||||
|
||||
// 로그인 성공 시 호출될 함수
|
||||
const handleLoginSuccess = () => {
|
||||
const userData = localStorage.getItem('user_data');
|
||||
@@ -82,16 +103,42 @@ function App() {
|
||||
|
||||
// 관리자 전용 기능
|
||||
const getAdminFeatures = () => {
|
||||
if (user?.role !== 'admin') return [];
|
||||
const features = [];
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'system-settings',
|
||||
title: '⚙️ 시스템 설정',
|
||||
description: '사용자 계정 관리',
|
||||
color: '#dc2626'
|
||||
}
|
||||
];
|
||||
// 시스템 관리자 전용 기능
|
||||
if (user?.role === 'system') {
|
||||
features.push(
|
||||
{
|
||||
id: 'user-management',
|
||||
title: '👥 사용자 관리',
|
||||
description: '계정 생성, 역할 변경, 사용자 삭제',
|
||||
color: '#dc2626',
|
||||
badge: '시스템 관리자'
|
||||
},
|
||||
{
|
||||
id: 'system-logs',
|
||||
title: '📊 시스템 로그',
|
||||
description: '로그인 기록, 시스템 오류 로그 조회',
|
||||
color: '#7c3aed',
|
||||
badge: '시스템 관리자'
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// 관리자 이상 공통 기능
|
||||
if (user?.role === 'admin' || user?.role === 'system') {
|
||||
features.push(
|
||||
{
|
||||
id: 'log-monitoring',
|
||||
title: '📈 로그 모니터링',
|
||||
description: '사용자 활동 로그 및 오류 모니터링',
|
||||
color: '#059669',
|
||||
badge: user?.role === 'system' ? '시스템 관리자' : '관리자'
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return features;
|
||||
};
|
||||
|
||||
// 페이지 렌더링 함수
|
||||
@@ -118,24 +165,147 @@ function App() {
|
||||
🏭 TK-MP BOM 관리 시스템
|
||||
</h1>
|
||||
<p style={{ color: '#718096', fontSize: '14px', margin: '4px 0 0 0' }}>
|
||||
{user?.full_name || user?.username}님 환영합니다
|
||||
{user?.name || user?.username}님 환영합니다
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
style={{
|
||||
background: '#e2e8f0',
|
||||
color: '#4a5568',
|
||||
border: 'none',
|
||||
borderRadius: '6px',
|
||||
padding: '10px 16px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '14px',
|
||||
fontWeight: '600'
|
||||
}}
|
||||
>
|
||||
로그아웃
|
||||
</button>
|
||||
|
||||
{/* 사용자 메뉴 */}
|
||||
<div className="user-menu-container" style={{ position: 'relative' }}>
|
||||
<button
|
||||
onClick={() => setShowUserMenu(!showUserMenu)}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
background: '#f8f9fa',
|
||||
border: '1px solid #e9ecef',
|
||||
borderRadius: '8px',
|
||||
padding: '8px 12px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '14px',
|
||||
fontWeight: '500',
|
||||
color: '#495057',
|
||||
transition: 'all 0.2s ease'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.target.style.background = '#e9ecef';
|
||||
e.target.style.borderColor = '#dee2e6';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.target.style.background = '#f8f9fa';
|
||||
e.target.style.borderColor = '#e9ecef';
|
||||
}}
|
||||
>
|
||||
<div style={{
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
borderRadius: '50%',
|
||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: 'white',
|
||||
fontSize: '14px',
|
||||
fontWeight: '600'
|
||||
}}>
|
||||
{(user?.name || user?.username || 'U').charAt(0).toUpperCase()}
|
||||
</div>
|
||||
<div style={{ textAlign: 'left' }}>
|
||||
<div style={{ fontSize: '14px', fontWeight: '600', color: '#2d3748' }}>
|
||||
{user?.name || user?.username}
|
||||
</div>
|
||||
<div style={{ fontSize: '12px', color: '#6c757d' }}>
|
||||
{user?.role === 'system' ? '시스템 관리자' :
|
||||
user?.role === 'admin' ? '관리자' : '사용자'}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{
|
||||
fontSize: '12px',
|
||||
color: '#6c757d',
|
||||
transform: showUserMenu ? 'rotate(180deg)' : 'rotate(0deg)',
|
||||
transition: 'transform 0.2s ease'
|
||||
}}>
|
||||
▼
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* 드롭다운 메뉴 */}
|
||||
{showUserMenu && (
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: '100%',
|
||||
right: 0,
|
||||
marginTop: '4px',
|
||||
background: 'white',
|
||||
border: '1px solid #e9ecef',
|
||||
borderRadius: '8px',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
|
||||
minWidth: '200px',
|
||||
zIndex: 1000,
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
<div style={{ padding: '12px 16px', borderBottom: '1px solid #f1f3f4' }}>
|
||||
<div style={{ fontSize: '14px', fontWeight: '600', color: '#2d3748' }}>
|
||||
{user?.name || user?.username}
|
||||
</div>
|
||||
<div style={{ fontSize: '12px', color: '#6c757d', marginTop: '2px' }}>
|
||||
{user?.email || '이메일 없음'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowUserMenu(false);
|
||||
navigateToPage('account-settings');
|
||||
}}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '12px 16px',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
textAlign: 'left',
|
||||
cursor: 'pointer',
|
||||
fontSize: '14px',
|
||||
color: '#495057',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
transition: 'background-color 0.2s ease'
|
||||
}}
|
||||
onMouseEnter={(e) => e.target.style.background = '#f8f9fa'}
|
||||
onMouseLeave={(e) => e.target.style.background = 'none'}
|
||||
>
|
||||
⚙️ 계정 설정
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowUserMenu(false);
|
||||
handleLogout();
|
||||
}}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '12px 16px',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
textAlign: 'left',
|
||||
cursor: 'pointer',
|
||||
fontSize: '14px',
|
||||
color: '#dc3545',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
transition: 'background-color 0.2s ease',
|
||||
borderTop: '1px solid #f1f3f4'
|
||||
}}
|
||||
onMouseEnter={(e) => e.target.style.background = '#fff5f5'}
|
||||
onMouseLeave={(e) => e.target.style.background = 'none'}
|
||||
>
|
||||
🚪 로그아웃
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 메인 콘텐츠 */}
|
||||
@@ -275,14 +445,14 @@ function App() {
|
||||
</p>
|
||||
<div style={{ marginBottom: '12px' }}>
|
||||
<span style={{
|
||||
background: '#fef7e0',
|
||||
color: '#92400e',
|
||||
padding: '2px 8px',
|
||||
background: feature.badge === '시스템 관리자' ? '#fef2f2' : '#fef7e0',
|
||||
color: feature.badge === '시스템 관리자' ? '#dc2626' : '#92400e',
|
||||
padding: '4px 8px',
|
||||
borderRadius: '12px',
|
||||
fontSize: '12px',
|
||||
fontWeight: '600'
|
||||
}}>
|
||||
관리자 전용
|
||||
{feature.badge} 전용
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
@@ -403,6 +573,42 @@ function App() {
|
||||
/>
|
||||
);
|
||||
|
||||
case 'account-settings':
|
||||
return (
|
||||
<AccountSettingsPage
|
||||
onNavigate={navigateToPage}
|
||||
user={user}
|
||||
onUserUpdate={(updatedUser) => {
|
||||
setUser(updatedUser);
|
||||
localStorage.setItem('user_data', JSON.stringify(updatedUser));
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'user-management':
|
||||
return (
|
||||
<UserManagementPage
|
||||
onNavigate={navigateToPage}
|
||||
user={user}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'system-logs':
|
||||
return (
|
||||
<SystemLogsPage
|
||||
onNavigate={navigateToPage}
|
||||
user={user}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'log-monitoring':
|
||||
return (
|
||||
<LogMonitoringPage
|
||||
onNavigate={navigateToPage}
|
||||
user={user}
|
||||
/>
|
||||
);
|
||||
|
||||
default:
|
||||
return (
|
||||
<div style={{ padding: '32px', textAlign: 'center' }}>
|
||||
@@ -451,9 +657,11 @@ function App() {
|
||||
|
||||
// 메인 애플리케이션
|
||||
return (
|
||||
<div style={{ minHeight: '100vh', background: '#f7fafc' }}>
|
||||
{renderCurrentPage()}
|
||||
</div>
|
||||
<ErrorBoundary errorContext={{ user, currentPage, pageParams }}>
|
||||
<div style={{ minHeight: '100vh', background: '#f7fafc' }}>
|
||||
{renderCurrentPage()}
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user