한글뷰어 기능수정 Ver.01

This commit is contained in:
koj729
2026-06-19 17:58:47 +09:00
parent 9268e4e6bc
commit 83b6e891ab
49 changed files with 8741 additions and 446 deletions

View File

@@ -0,0 +1,118 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>관리자 화면 기능 설계 명세표</title>
<style>
body {
font-family: 'Malgun Gothic', '맑은 고딕', sans-serif;
background-color: #f1f5f9;
padding: 20px;
}
table {
width: 100%;
border-collapse: collapse;
background-color: #ffffff;
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
border-radius: 8px;
overflow: hidden;
}
th {
background-color: #0f172a;
color: #ffffff;
font-weight: bold;
padding: 12px;
text-align: left;
border: 1px solid #cbd5e1;
}
td {
padding: 12px;
border: 1px solid #cbd5e1;
color: #334155;
}
tr:nth-child(even) {
background-color: #f8fafc;
}
</style>
</head>
<body>
<h2>🖥️ 관리자 화면(Admin Panel) 필수 기능 설계 명세표</h2>
<p>※ 이 파일을 Excel에서 <strong>[파일] -> [열기]</strong>로 실행하거나, 표 전체를 복사하여 Excel 시트에 붙여넣으시면 서식과 셀 구조가 그대로 유지되어 열립니다.</p>
<table>
<thead>
<tr>
<th style="width: 15%;">대분류</th>
<th style="width: 20%;">메뉴명</th>
<th style="width: 35%;">상세 기능 명세</th>
<th style="width: 30%;">추가 고려사항 (고급 기능)</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>프로젝트 관리</strong></td>
<td>프로젝트 등록 및 설정</td>
<td>신규 현장/공구의 생성 및 수정, 프로젝트별 용량 제한(storage_byte) 설정 및 잠금 토글(is_active) 관리</td>
<td>비공개/보안(Secret) 폴더에 대한 일반 사용자의 접근 신청 결재 및 임시 승인 처리</td>
</tr>
<tr>
<td><strong>프로젝트 관리</strong></td>
<td>실시간 배너 공지</td>
<td>대시보드 상단 마르퀴 띠배너 공지사항(banner_notice) 등록 및 소켓 기반 실시간 송출 제어</td>
<td>배너 공지 예약 노출 설정 및 노출/송출 이력 관리</td>
</tr>
<tr>
<td><strong>사용자 권한 관리</strong></td>
<td>사용자 계정 제어</td>
<td>사용자 계정 등록, 정보(부서/직급) 수정, 비밀번호 재설정 및 퇴사자 계정 잠금(is_resigned) 처리</td>
<td>외부 SSO(Sentinel) 계정 연동 및 중복 로그인 차단 옵션 관리</td>
</tr>
<tr>
<td><strong>사용자 권한 관리</strong></td>
<td>프로젝트 권한 배정</td>
<td>현장별 참여 유저 목록 배정 및 권한 등급(Master/Sub-Master/Worker/Viewer) 마우스 클릭 지정</td>
<td>폴더 수준의 세부 접근 제어 리스트(ACL) 추가 연동</td>
</tr>
<tr>
<td><strong>서버 및 리소스 모니터링</strong></td>
<td>용량 분석 현황</td>
<td>현장별 누적 스토리지 사용량, 남은 용량 및 파일/폴더 개수 대시보드 시각화</td>
<td>스토리지 임계값(예: 90%) 도달 시 관리자 자동 경고 알림</td>
</tr>
<tr>
<td><strong>서버 및 리소스 모니터링</strong></td>
<td>압축 다운로드 관리</td>
<td>비동기 폴더 압축 다운로드(BullMQ) 작업의 진행 현황 모니터링 및 대기열 제어</td>
<td>임시 보존 기간이 지난 압축 임시파일 자동/일괄 영구 삭제를 통한 디스크 확보</td>
</tr>
<tr>
<td><strong>시스템 감사 및 통계</strong></td>
<td>감사 로그 조회</td>
<td>파일 삭제, 이동, 다운로드 등 민감한 조작 행위(tb_log)의 날짜/유저/활동별 필터 검색</td>
<td>감사 로그 이력 보고서 인쇄 및 엑셀 백업 다운로드</td>
</tr>
<tr>
<td><strong>시스템 감사 및 통계</strong></td>
<td>행동 분석 및 통계</td>
<td>사용자 UI 클릭 로그(tb_click_log) 기반 최다 접근 메뉴 및 최다 조회 도면/문서 사용 현황 분석</td>
<td>AI 요약 서비스(Gemini API) 호출 제한량(Quota) 관리 및 비용 통제</td>
</tr>
<tr>
<td><strong>실시간 접속자</strong></td>
<td>실시간 접속 현황</td>
<td>현재 소켓 연결된 동시 접속 유저 목록 및 접속 IP와 현재 탐색 중인 아카이브 경로 실시간 표출</td>
<td>특정 유저 강제 소켓 연결 끊기(Kick) 기능</td>
</tr>
<tr>
<td><strong>시스템 환경 및 정책 관리</strong></td>
<td>보관 및 삭제 정책 설정</td>
<td>기존 pageRenderer.js에 하드코딩된 '최소 유지 파일 개수(3개)' 및 '보존 기간(15일)' 임계치를 DB화하여, 관리자 화면에서 프로젝트별/글로벌 동적 설정 기능 제공</td>
<td>tb_project 테이블에 limit_file_count 및 limit_days 컬럼을 연동하여 프론트엔드가 변경된 기준값으로 실시간 바인딩되도록 API 구조 개선</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@@ -0,0 +1,343 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PROJECT MASTER ver 4.0 기능명세서</title>
<!-- Google Fonts - Inter & Noto Sans KR -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&family=Noto+Sans+KR:wght@300;400;500;700&display=swap" rel="stylesheet">
<style>
:root {
--primary: #10b981;
--primary-dark: #047857;
--primary-light: #ecfdf5;
--bg: #f8fafc;
--card-bg: #ffffff;
--text-main: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
--shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
}
body {
font-family: 'Inter', 'Noto Sans KR', sans-serif;
background-color: var(--bg);
color: var(--text-main);
line-height: 1.7;
margin: 0;
padding: 0;
}
.container {
max-width: 1000px;
margin: 40px auto;
padding: 40px;
background-color: var(--card-bg);
border-radius: 16px;
box-shadow: var(--shadow-lg);
border: 1px solid var(--border);
}
/* Header Styling */
header {
border-bottom: 2px solid var(--border);
padding-bottom: 24px;
margin-bottom: 40px;
position: relative;
}
h1 {
font-size: 2.2rem;
font-weight: 700;
margin: 0 0 8px 0;
background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.subtitle {
font-size: 1.1rem;
color: var(--text-muted);
margin: 0;
}
/* Typography & Structure */
h2 {
font-size: 1.6rem;
font-weight: 700;
color: var(--primary-dark);
margin-top: 40px;
margin-bottom: 20px;
display: flex;
align-items: center;
border-left: 5px solid var(--primary);
padding-left: 12px;
}
h3 {
font-size: 1.25rem;
font-weight: 600;
color: var(--text-main);
margin-top: 30px;
margin-bottom: 12px;
border-bottom: 1px dashed var(--border);
padding-bottom: 6px;
}
p, li {
font-size: 1rem;
color: var(--text-muted);
}
ul {
padding-left: 20px;
margin-bottom: 20px;
}
li {
margin-bottom: 8px;
}
/* Premium Table Styles */
table {
width: 100%;
border-collapse: collapse;
margin: 24px 0;
font-size: 0.95rem;
box-shadow: var(--shadow);
border-radius: 8px;
overflow: hidden;
border: 1px solid var(--border);
}
th {
background-color: var(--primary-dark);
color: #ffffff;
font-weight: 600;
text-align: left;
padding: 14px 16px;
}
td {
padding: 14px 16px;
border-bottom: 1px solid var(--border);
background-color: #ffffff;
color: var(--text-muted);
}
tr:last-child td {
border-bottom: none;
}
tr:nth-child(even) td {
background-color: #f8fafc;
}
tr:hover td {
background-color: var(--primary-light);
color: var(--primary-dark);
transition: all 0.2s ease;
}
/* Mermaid Wrapper */
.diagram-container {
background-color: #f8fafc;
border: 1px solid var(--border);
border-radius: 12px;
padding: 24px;
margin: 24px 0;
display: flex;
justify-content: center;
box-shadow: inset 0 2px 4px 0 rgb(0 0 0 / 0.05);
}
.mermaid {
width: 100%;
display: flex;
justify-content: center;
}
/* Highlight Boxes (Alerts) */
.alert {
background-color: var(--primary-light);
border-left: 4px solid var(--primary);
border-radius: 4px 8px 8px 4px;
padding: 16px 20px;
margin: 24px 0;
color: var(--primary-dark);
}
.alert-title {
font-weight: 700;
margin-bottom: 4px;
}
/* Responsive Design */
@media (max-width: 768px) {
.container {
margin: 0;
padding: 20px;
border-radius: 0;
box-shadow: none;
border: none;
}
h1 {
font-size: 1.8rem;
}
table {
display: block;
overflow-x: auto;
}
}
</style>
<!-- Mermaid.js CDN for dynamic rendering of charts -->
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
<script>
mermaid.initialize({
startOnLoad: true,
theme: 'neutral',
securityLevel: 'loose'
});
</script>
</head>
<body>
<div class="container">
<header>
<h1>PROJECT MASTER ver 4.0 기능명세서</h1>
<p class="subtitle">시스템 아키텍처, 기능 명세 및 운영 가이드라인</p>
</header>
<h2>1. 시스템 아키텍처 및 연동 구조</h2>
<p>본 시스템은 실시간 협업과 대용량 건설 산출물 관리를 위해 웹소켓, 비동기 작업 큐 및 분산 저장소를 포함하는 모던 웹 아키텍처로 구성되어 있습니다.</p>
<div class="diagram-container">
<div class="mermaid">
graph TD
Client[Browser / Client] <-->|HTTP / Axios| NodeServer[Node.js Express Server]
Client <-->|Websocket / Socket.io| NodeServer
NodeServer <-->|SQL Queries| Postgres[(PostgreSQL DB)]
NodeServer <-->|PubSub / Queue| Redis[(Redis Server)]
NodeServer <-->|Presigned URL / PUT| MinIO[(MinIO Object Storage)]
</div>
</div>
<h3>기술 스택 (Tech Stack)</h3>
<ul>
<li><strong>프론트엔드 (Frontend)</strong>: HTML5, Vanilla JavaScript, Vanilla CSS, Axios, Socket.io-client, OpenLayers (GIS), Cesium (3D 모델)</li>
<li><strong>백엔드 (Backend)</strong>: Node.js (Express), Socket.io, BullMQ (비동기 작업 큐), Winston (로깅)</li>
<li><strong>데이터베이스 (Database)</strong>: PostgreSQL (ver4 스키마), Redis (소켓 및 작업 큐 세션 관리)</li>
<li><strong>오브젝트 스토리지 (Storage)</strong>: MinIO / AWS S3 (Presigned URL 및 원격 정적 파일 보관)</li>
</ul>
<h2>2. 사용자 권한 체계 (User Permission System)</h2>
<p>사용자 정보 및 소속 프로젝트별로 세분화된 권한 관리를 지원하며, UI 및 API 호출 수준에서 차단 및 필터링이 적용됩니다.</p>
<table>
<thead>
<tr>
<th>권한 레벨</th>
<th>그룹/명칭</th>
<th>주요 권한 범위 및 설명</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Super / Dev</strong></td>
<td>super / dev</td>
<td>시스템 전체 관리 및 개발자 전용 모달 접근 권한. 비활성화된 프로젝트 우회 접근 지원.</td>
</tr>
<tr>
<td><strong>Master</strong></td>
<td>관리자</td>
<td>프로젝트 전반의 산출물 수정/삭제/관리, 개별 폴더/파일별 권한 제어 및 대량 압축 다운로드 권한.</td>
</tr>
<tr>
<td><strong>Sub-Master</strong></td>
<td>부관리자</td>
<td>신규 폴더 생성 및 삭제, 과업개요 정보 수정, 사용자 권한 배정 및 변경 처리.</td>
</tr>
<tr>
<td><strong>Security Worker</strong></td>
<td>보안참여자</td>
<td>보안 설정이 적용된 비밀 폴더 및 파일에 대한 읽기/쓰기 권한 부여 및 관리.</td>
</tr>
<tr>
<td><strong>Worker</strong></td>
<td>일반참여자</td>
<td>일반 산출물 업로드/다운로드, 개인 메모 작성 및 수정, AI 연동 문서 요약(Gemini) 기능 활용.</td>
</tr>
<tr>
<td><strong>Viewer</strong></td>
<td>참관자</td>
<td>스토리지 조회 및 개별 파일 다운로드만 가능 (활동 로그 노출 방지 및 파일 추가/수정 버튼 숨김).</td>
</tr>
</tbody>
</table>
<h2>3. 핵심 모듈 명세 (Core Module Specifications)</h2>
<h3>3.1 아카이브 모듈 (Archive Module)</h3>
<p>프로젝트별 모든 산출물(도면, 공문, 과업 문서 등)을 관리하는 핵심 가상 스토리지 시스템입니다.</p>
<ul>
<li><strong>폴더 구조 제어</strong>: 폴더 생성(createFolder), 이름 변경(renameTarget), 다른 폴더로의 계층식 위치 이동(relocateTarget)을 실시간 지원합니다.</li>
<li><strong>S3 Presigned URL 업로드</strong>: 대용량 업로드 안전성을 위해 서버의 자원을 거치지 않고, 스토리지로부터 Presigned URL을 직접 발급받아 브라우저가 직접 Put Object를 수행합니다.</li>
<li><strong>백그라운드 ZIP 압축 다운로드</strong>: 대량의 파일이나 폴더 통째 다운로드 시, BullMQ 큐에 작업을 위임하여 서버 부하를 최소화하고 완료 시 다운로드 링크를 팝업으로 제공합니다.</li>
<li><strong>가상 휴지통</strong>: 삭제된 파일은 즉시 지워지지 않고 휴지통으로 이동하며, 소유자 및 관리자의 승인을 통해 복원하거나 영구 삭제할 수 있습니다.</li>
<li><strong>뷰어 및 메모 연동</strong>: HWP/DWG 등 다양한 확장자 파일을 PDF로 자동 변환하여 브라우저에서 즉시 열람하는 통합 뷰어를 제공하며, 파일별 텍스트 메모 및 AI 요약 서비스를 연동합니다.</li>
</ul>
<h3>3.2 과업개요 모듈 (Overview Module)</h3>
<p>계약 사항 및 상세 마일스톤 정보, 지도 개요도를 하나의 통합 카드뷰 대시보드로 제공합니다.</p>
<ul>
<li><strong>계약 및 사업 정보</strong>: 계약 금액(외화 금액 포함), Commencement Date, 준공 예정일, 공동 수급사 구성 지분율 및 대표인 정보를 보관합니다.</li>
<li><strong>개요도 드래그앤드롭 업로드</strong>: 현장 위치도 및 종합 개요도를 별도 파일 선택창 없이 드래그하여 직관적으로 변경 및 등록할 수 있습니다.</li>
<li><strong>과업 일정 캘린더</strong>: 연월 단위의 마일스톤 일정을 생성/관리하고 주요 진척도를 실시간 모니터링합니다.</li>
<li><strong>시차 연동 실시간 시계</strong>: 해외 프로젝트의 경우 해당 현장 국가의 표준시 시차 데이터를 바탕으로 실시간 현지 시각을 헤더에 동적으로 출력합니다.</li>
</ul>
<h3>3.3 공문 모듈 (Official Document Module)</h3>
<p>프로젝트 관계 기관 간 송수신된 공문 번호, 날짜, 수신처 목록과 문서를 체계적으로 매핑 및 보관합니다.</p>
<ul>
<li>공문 정보 관리 (발송일, 제목, 수발신처, 문서 번호).</li>
<li>공문 매핑 첨부파일 연계 보관 및 1-클릭 다운로드 서비스.</li>
</ul>
<h3>3.4 GSIM (GIS 기반 위치 모델 모듈)</h3>
<p>건설 현장의 실제 위경도 좌표 및 고도(Height) 값과 연계하여 모델 및 데이터를 공간 정보와 매핑합니다.</p>
<ul>
<li>2D(OpenLayers) 지도 및 3D(Cesium) 입체 모델 뷰어 탑재.</li>
<li>줌 레벨에 최적화된 마커 클러스터링(Clustering) 기법으로 대량의 포인트를 성능 저하 없이 표시.</li>
</ul>
<h2>4. 실시간 동기화 및 로그 모듈</h2>
<h3>4.1 Websocket 다중 접속자 커서 (Socket.io)</h3>
<p>프로젝트 협업 효율성을 극대화하기 위해 다중 유저 동시 접속 시 각 유저의 Client 마우스 움직임을 추적하여 화면상에 실시간으로 커서 위치와 소속/이름표를 그려줍니다.</p>
<h3>4.2 실시간 로그 및 클릭 통계</h3>
<ul>
<li><strong>푸터 실시간 한 줄 로그</strong>: 파일 생성, 삭제, 변환 등 유저가 수행한 핵심 행위가 푸터의 띠 배너 형태로 전 유저에게 실시간 브로드캐스트됩니다.</li>
<li><strong>클릭 활동 추적</strong>: 향후 화면 분석 및 건설 관리 통계용으로 유저가 누르는 주요 탭, 버튼 등의 액션을 수집하여 <code>tb_click_log</code> 테이블에 실시간으로 기록합니다.</li>
</ul>
<h2>5. 안정성 및 장애 예방 설계 (Reliability & Robustness)</h2>
<div class="alert">
<div class="alert-title">💡 최신 패치 반영 사항 (System Resilience)</div>
<ul>
<li><strong>캐시 잠김 해제 미들웨어</strong>: GET 요청 중 정적 자원을 제외한 비동기 동적 API 요청에 대하여 브라우저 304 캐시를 완전히 강제 무효화함으로써, 이전 에러 데이터에 의한 화면 중단 현상을 원천 방지하였습니다.</li>
<li><strong>서버 Crash 방어 널가드 (Null-Guard)</strong>: DB 조회 쿼리 등에서 일시적인 커넥션 유실이나 스키마 에러가 나더라도 백엔드 프로세스가 통째로 죽지 않도록, 주요 API 핸들러 전반에 예외 위임(try-catch) 및 Null 체크 조건문을 완비하였습니다.</li>
<li><strong>클라이언트 실시간 디버거</strong>: 사용자 브라우저 상에서 유발된 자바스크립트 크래시 및 미처리 거부(Unhandled Rejection) 오류를 백엔드 콘솔로 자동 수집/출력해 주는 에러 추적 스크립트를 내장하였습니다.</li>
</ul>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,377 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PM_ver4 통합 관리자 메뉴정의서</title>
<!-- Google Fonts - Inter & Noto Sans KR -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Noto+Sans+KR:wght@300;400;500;700&display=swap"
rel="stylesheet">
<style>
:root {
--primary: #1e5149;
/* Forest Green */
--primary-dark: #142e29;
--primary-light: #e9eeed;
--border: #d2dcdb;
--bg: #f4f7f6;
--card-bg: #ffffff;
--text-main: #1f2937;
--text-muted: #4b746d;
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -2px rgba(0, 0, 0, 0.05);
}
body {
font-family: 'Inter', 'Noto Sans KR', sans-serif;
background-color: var(--bg);
color: var(--text-main);
line-height: 1.6;
margin: 0;
padding: 40px 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background-color: var(--card-bg);
border-radius: 12px;
padding: 40px;
box-shadow: var(--shadow);
border: 1px solid var(--border);
}
header {
border-bottom: 2px solid var(--border);
padding-bottom: 20px;
margin-bottom: 30px;
}
h1 {
font-size: 2rem;
font-weight: 700;
color: var(--primary-dark);
margin: 0 0 8px 0;
}
.subtitle {
font-size: 1.05rem;
color: var(--text-muted);
margin: 0;
}
/* Filter Controls */
.filter-controls {
display: flex;
gap: 10px;
margin-bottom: 24px;
flex-wrap: wrap;
}
.filter-btn {
background-color: #ffffff;
border: 1px solid var(--border);
color: var(--text-main);
padding: 8px 16px;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
}
.filter-btn:hover,
.filter-btn.active {
background-color: var(--primary);
color: #ffffff;
border-color: var(--primary);
}
/* Menu Definition Table */
table {
width: 100%;
border-collapse: collapse;
font-size: 0.88rem;
margin-top: 10px;
border-radius: 8px;
overflow: hidden;
border: 1px solid var(--border);
}
th {
background-color: var(--primary-dark);
color: #ffffff;
font-weight: 600;
text-align: left;
padding: 12px 16px;
font-size: 0.9rem;
}
td {
padding: 14px 16px;
border-bottom: 1px solid var(--border);
vertical-align: top;
color: var(--text-main);
}
tr:last-child td {
border-bottom: none;
}
tr:nth-child(even) td {
background-color: #f9fafb;
}
tr.hidden {
display: none;
}
/* Badges */
.badge-cat {
display: inline-block;
background-color: var(--primary-light);
color: var(--primary);
padding: 2px 8px;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 700;
}
.badge-table {
font-family: monospace;
background-color: #f1f5f9;
color: #334155;
padding: 2px 6px;
border-radius: 4px;
font-size: 0.8rem;
border: 1px solid #e2e8f0;
display: inline-block;
margin-bottom: 4px;
}
.menu-id {
font-family: monospace;
font-weight: 600;
color: #0369a1;
font-size: 0.8rem;
}
/* Rules list style */
.rules-list {
margin: 0;
padding-left: 16px;
font-size: 0.82rem;
color: #4b5563;
}
.rules-list li {
margin-bottom: 4px;
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>PM_ver4 통합 관리자 메뉴정의서</h1>
<p class="subtitle">관리자 화면(Admin Panel) 1단계 및 2단계 메뉴 기능 명세 및 테이블 매핑 테이블</p>
</header>
<!-- Filter Buttons -->
<div class="filter-controls">
<button class="filter-btn active" onclick="filterMenu('all')">전체보기</button>
<button class="filter-btn" onclick="filterMenu('dashboard')">1. Dashboards</button>
<button class="filter-btn" onclick="filterMenu('project')">2. 프로젝트 관리</button>
<button class="filter-btn" onclick="filterMenu('user')">3. 사용자 관리</button>
<button class="filter-btn" onclick="filterMenu('system')">4. 시스템 감사 및 환경</button>
</div>
<!-- Menu Table -->
<table>
<thead>
<tr>
<th style="width: 15%;">1단계 메뉴</th>
<th style="width: 15%;">2단계 메뉴</th>
<th style="width: 15%;">메뉴 ID / Hash</th>
<th style="width: 25%;">주요 기능 명세 (Functional Specifications)</th>
<th style="width: 15%;">관련 DB 테이블</th>
<th style="width: 15%;">비고 및 비즈니스 규칙</th>
</tr>
</thead>
<tbody id="menu-tbody">
<!-- Row 1 -->
<tr data-category="dashboard">
<td><strong>1. Dashboards</strong></td>
<td><span class="badge-cat">종합 용량 및 접속자</span></td>
<td class="menu-id">menu-dashboard<br>#dashboard</td>
<td>
• 전체 디스크 용량, 실시간 소켓 접속자 수, Redis 대기 작업 수 요약 노출.<br>
• 현장별 사용 용량, 백분율, 파일 수량 프로그레스 바 표시.<br>
• 실시간 소켓 연결 정보 그리드 및 사용자 강제퇴장(Kick) 기능.
</td>
<td>
<span class="badge-table">tb_project</span><br>
<span class="badge-table">tb_data</span><br>
<span class="badge-table">Redis (Queue)</span>
</td>
<td>소켓 세션 맵과 실시간 동기화하여 퇴장 처리 즉시 실행.</td>
</tr>
<!-- Row 2 -->
<tr data-category="project">
<td><strong>2. 프로젝트 관리</strong></td>
<td><span class="badge-cat">프로젝트 관리</span></td>
<td class="menu-id">menu-project-mgmt<br>#project-mgmt</td>
<td>
• 프로젝트 목록 조회 및 신규 등록/수정/삭제 모달 팝업.<br>
• 프로젝트 구분 카테고리 지정.<br>
• 행 클릭 시 우측에 참여 사용자 등급 조회 및 즉각 수정/배정제외.<br>
• 미배정 사용자 다중 선택 일괄 추가 배정 팝업.
</td>
<td>
<span class="badge-table">tb_project</span><br>
<span class="badge-table">code_detail</span><br>
<span class="badge-table">tb_permission</span><br>
<span class="badge-table">tb_user</span>
</td>
<td>
<ul class="rules-list">
<li>등록 시 ID 직접 입력, 수정 시 ID 비활성.</li>
<li><strong>삭제 제한</strong>: 관련 테이블(tb_data, tb_official_doc_file, tb_banner_notice 등)에 현장 ID
사용 이력이 있으면 삭제 불가능.</li>
</ul>
</td>
</tr>
<!-- Row 3 -->
<tr data-category="project">
<td><strong>2. 프로젝트 관리</strong></td>
<td><span class="badge-cat">실시간 배너 공지</span></td>
<td class="menu-id">menu-banner-notice<br>#banner-notice</td>
<td>
• 배너 공지사항 작성 및 등록 (대상 현장, 송출 기간, 편집용 등록일 지정).<br>
• 송출 상태(송출중, 예약됨, 만료) 및 등록일 범위 필터 검색 기능.<br>
• 진행 중인 공지 개별 즉시 송출중지 처리.
</td>
<td>
<span class="badge-table">tb_banner_notice</span><br>
<span class="badge-table">code_detail</span><br>
<span class="badge-table">tb_project</span>
</td>
<td>시작일/종료일 경과 여부에 따라 송출 상태 실시간 계산 렌더링.</td>
</tr>
<!-- Row 4 -->
<tr data-category="user">
<td><strong>3. 사용자 관리</strong></td>
<td><span class="badge-cat">사용자 관리</span></td>
<td class="menu-id">menu-user-mgmt<br>#user-mgmt</td>
<td>
• 전체 사용자 정보 조회 및 신규 계정 등록/정보 수정/삭제 모달.<br>
• 권한 그룹 및 재직/퇴직잠금 상태 배지 설정.<br>
• 행 클릭 시 해당 유저의 프로젝트 참여 목록(권한 등급 포함) 우측 연동 리스트업.
</td>
<td>
<span class="badge-table">tb_user</span><br>
<span class="badge-table">code_detail</span><br>
<span class="badge-table">tb_permission</span><br>
<span class="badge-table">tb_project</span>
</td>
<td><strong>삭제 제한</strong>: 권한 테이블(tb_permission)에 프로젝트 참여 권한 정보가 존재하면 계정 삭제 불가능.</td>
</tr>
<!-- Row 5 -->
<tr data-category="system">
<td><strong>4. 시스템 감사 및 환경</strong></td>
<td><span class="badge-cat">감사 로그 조회</span></td>
<td class="menu-id">menu-audit-logs<br>#audit-logs</td>
<td>
• 파일 삭제, 이동, 다운로드 등 보안 감사 대상 활동 조회.<br>
• 유저 ID 검색 및 액션 타입(activity) 필터 검색 기능.
</td>
<td>
<span class="badge-table">tb_log</span>
</td>
<td>감사 추적용 조회 전용 화면.</td>
</tr>
<!-- Row 6 -->
<tr data-category="system">
<td><strong>4. 시스템 감사 및 환경</strong></td>
<td><span class="badge-cat">보관 및 삭제 정책 설정</span></td>
<td class="menu-id">menu-delete-policy<br>#delete-policy</td>
<td>
• 시스템 공통 자동 보존 및 파일 삭제 임계 기준 설정 폼.<br>
• 폼 데이터 변경 시 작동 시나리오 문구 동적 요약 안내.<br>
• 정기 자동 삭제 스케줄러 배치 구동 이력 로그 리스트업.
</td>
<td>
<span class="badge-table">tb_system_policy</span><br>
<span class="badge-table">tb_auto_clean_log</span>
</td>
<td>
<ul class="rules-list">
<li>설정값 변경 저장 시 로그 이력의 대상에는 'SYSTEM' 기입.</li>
<li>배치는 수동 변경 값을 즉각 바인딩하여 다음 기동 시 적용.</li>
</ul>
</td>
</tr>
<!-- Row 7 -->
<tr data-category="system">
<td><strong>4. 시스템 감사 및 환경</strong></td>
<td><span class="badge-cat">공통 코드 관리</span></td>
<td class="menu-id">menu-code-mgmt<br>#code-mgmt</td>
<td>
• 대분류 코드 마스터 등록/수정/삭제 모달.<br>
• 대분류 선택 시 하단에 소속 소분류 세부 코드 실시간 필터 로드.<br>
• 소분류 코드 등록/수정/삭제 모달.<br>
• 소분류 base_code (대분류_소분류 결합 코드) 자동 완성 저장.
</td>
<td>
<span class="badge-table">code_master</span><br>
<span class="badge-table">code_detail</span>
</td>
<td>
<ul class="rules-list">
<li>대분류 미선택 시 하단 세부코드 등록 차단 및 경고.</li>
<li><strong>삭제 제한</strong>: 대분류 코드에 속한 하위 소분류 세부 코드(code_detail)가 존재하면 대분류 삭제 불가능.</li>
</ul>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Interactive Filtering Script -->
<script>
function filterMenu(category) {
// Update active filter button styling
const buttons = document.querySelectorAll('.filter-btn');
buttons.forEach(btn => btn.classList.remove('active'));
// Highlight clicked button
const clickedBtn = event.currentTarget || event.target;
if (clickedBtn && clickedBtn.classList) {
clickedBtn.classList.add('active');
}
// Show/Hide rows
const rows = document.querySelectorAll('#menu-tbody tr');
rows.forEach(row => {
if (category === 'all' || row.getAttribute('data-category') === category) {
row.classList.remove('hidden');
} else {
row.classList.add('hidden');
}
});
}
</script>
</body>
</html>

View File

@@ -0,0 +1,424 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PM 프로젝트 워크플로우 및 데이터 흐름도</title>
<!-- Google Fonts - Inter & Noto Sans KR -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&family=Noto+Sans+KR:wght@300;400;500;700&display=swap"
rel="stylesheet">
<!-- Mermaid.js 라이브러리 (CDN) -->
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
<script>
mermaid.initialize({
startOnLoad: true,
theme: 'default',
securityLevel: 'loose',
themeVariables: {
fontFamily: 'Inter, Noto Sans KR, sans-serif',
primaryColor: '#f43f5e',
primaryTextColor: '#fff',
lineColor: '#cbd5e1'
}
});
</script>
<style>
:root {
--primary: #f43f5e;
--primary-dark: #be123c;
--primary-light: #fff1f2;
--bg: #f8fafc;
--card-bg: #ffffff;
--text-main: #0f172a;
--text-muted: #475569;
--border: #e2e8f0;
--shadow: 0 4px 6px -1px rgb(0 0 0 / 0.05), 0 2px 4px -2px rgb(0 0 0 / 0.05);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.05), 0 4px 6px -4px rgb(0 0 0 / 0.05);
}
body {
font-family: 'Inter', 'Noto Sans KR', sans-serif;
background-color: var(--bg);
color: var(--text-main);
line-height: 1.7;
margin: 0;
padding: 0;
}
.container {
max-width: 1200px;
margin: 40px auto;
padding: 40px;
background-color: var(--card-bg);
border-radius: 16px;
box-shadow: var(--shadow-lg);
border: 1px solid var(--border);
}
/* Header Styling */
header {
border-bottom: 2px solid var(--border);
padding-bottom: 24px;
margin-bottom: 30px;
}
h1 {
font-size: 2.2rem;
font-weight: 700;
margin: 0 0 8px 0;
background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.subtitle {
font-size: 1.1rem;
color: var(--text-muted);
margin: 0;
}
h2 {
font-size: 1.5rem;
color: #1e293b;
border-left: 5px solid var(--primary);
padding-left: 12px;
margin-top: 40px;
margin-bottom: 20px;
font-weight: 700;
}
h3 {
font-size: 1.2rem;
color: #334155;
margin-top: 30px;
margin-bottom: 15px;
font-weight: 600;
}
p,
li {
color: var(--text-muted);
font-size: 1rem;
}
ul {
padding-left: 20px;
}
li {
margin-bottom: 8px;
}
/* Card / Diagram Area Style */
.diagram-card {
background: #ffffff;
border: 1px solid var(--border);
border-radius: 12px;
padding: 24px;
margin: 20px 0 35px 0;
box-shadow: var(--shadow);
overflow-x: auto;
}
.mermaid {
display: flex;
justify-content: center;
align-items: center;
margin: 0 auto;
}
/* Highlight text */
code {
font-family: monospace;
background-color: #f1f5f9;
color: #e11d48;
padding: 2px 6px;
border-radius: 4px;
font-size: 0.9rem;
}
.badge {
display: inline-block;
background-color: var(--primary-light);
color: var(--primary-dark);
padding: 4px 10px;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 600;
margin-bottom: 10px;
}
/* Responsive */
@media (max-width: 768px) {
.container {
margin: 0;
padding: 20px;
border-radius: 0;
box-shadow: none;
border: none;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>PM 프로젝트 워크플로우 및 데이터 흐름도</h1>
<p class="subtitle">로컬 개발 인프라 및 핵심 기능(업로드/압축)에 대한 시각화 명세서</p>
</header>
<p>본 문서는 <code>PM_ver4</code> 프로젝트의 시스템 아키텍처, 기능별 워크플로우 및 엔티티 간 데이터 흐름을 시각적으로 구현하여 이해를 돕기 위한 HTML 문서입니다.</p>
<!-- 1. 전체 아키텍처 -->
<h2>1. 전체 시스템 아키텍처 및 데이터 흐름도</h2>
<p>사용자 브라우저에서 출발하여 웹 서버(Express), PostgreSQL, Redis 및 로컬 MinIO(S3) 스토리지까지 연결되는 데이터 흐름도입니다.</p>
<div class="diagram-card">
<div class="mermaid">
graph LR
%% 1. 사용자 영역 (가장 좌측 배치)
subgraph Client [사용자 브라우저]
UI["HTML CSS JS views"]
SocketClient["Socket.io Client"]
end
%% 2. 백엔드 웹서버 영역 (중앙 배치)
subgraph WebServer [백엔드 서버 Port 6565]
Express["Express App"]
Router["Router / Controller"]
AuthMid["SSO / Login Bypass"]
SocketServer["Socket.io Server"]
BullQueue["BullMQ Producer"]
S3SDK["aws-sdk s3 - MinIO 연동"]
PGDriver["pg - PostgreSQL 연동"]
end
%% 3. 백그라운드 워커 영역 (중앙 하단 배치)
subgraph BackgroundWorker [비동기 워커 프로세스]
Worker["BullMQ Worker - 압축 수행"]
ExternalCLI["programs - pdf_thumb / encryp"]
end
%% 4. 인프라 영역 (가장 우측 배치)
subgraph Infrastructure [Docker 가상 환경]
PostgresDB["PostgreSQL DB"]
RedisDB["Redis Broker"]
MinIOStorage["MinIO S3"]
end
%% 좌측 (Client) -> 중앙 (WebServer) 흐름
UI -->|HTTP Request| Express
Express --> UI
SocketClient ---|Socket Event| SocketServer
%% 웹서버 내부 로직 흐름
Express --> AuthMid
AuthMid --> Router
%% 중앙 (WebServer) -> 우측 (Infrastructure) 데이터 흐름
Router -->|Query| PGDriver
PGDriver --> PostgresDB
Router -->|Push Job| BullQueue
BullQueue --> RedisDB
Router -->|Generate URL| S3SDK
S3SDK --> MinIOStorage
%% 좌측 (Client) -> 우측 (Infrastructure) 다이렉트 업로드 흐름
UI -->|Direct Upload| MinIOStorage
%% 워커 동작 흐름 (중앙 하단 -> 우측 및 외부)
Worker -->|Pop Job| RedisDB
Worker -->|Execute CLI| ExternalCLI
Worker -->|Read Write Files| MinIOStorage
Worker -->|Update Status| PGDriver
</div>
</div>
<!-- 2. 주요 기능별 핵심 워크플로우 -->
<h2>2. 주요 기능별 핵심 워크플로우</h2>
<h3>2.1 파일 업로드 워크플로우 <span class="badge">Presigned URL 방식</span></h3>
<p>서버 리소스를 보존하기 위해 브라우저에서 스토리지(MinIO)로 파일을 직접 올릴 수 있게 구현된 프로세스입니다.</p>
<div class="diagram-card">
<div class="mermaid">
sequenceDiagram
autonumber
actor User as 사용자 (브라우저)
participant Server as 백엔드 서버 (Node.js)
participant MinIO as 로컬 MinIO 스토리지
participant DB as 데이터베이스 (PostgreSQL)
User->>Server: 1. 업로드 링크 요청 (POST /:projectId/archive/generateUploadUrl)
Note over Server: Bucket명 변환 미들웨어 작동<br>(PM_TEST_01 -> pm-test-01)
Server->>MinIO: 2. S3 SDK를 통한 Presigned PUT URL 요청
MinIO-->>Server: 3. 제한 시간 설정된 Presigned URL 반환
Server-->>User: 4. Presigned URL 전달
User->>MinIO: 5. 해당 Presigned URL로 직접 파일 업로드 (HTTP PUT)
Note over User,MinIO: 브라우저에서 스토리지로 다이렉트 전송
MinIO-->>User: 6. 업로드 완료 응답 (HTTP 200 OK)
User->>Server: 7. 업로드 정보 DB 반영 요청
Server->>DB: 8. tb_data / _test_tb_data 테이블에 파일 정보 INSERT
DB-->>Server: 9. 삽입 성공
Server-->>User: 10. 업로드 최종 완료 처리
</div>
</div>
<h3>2.2 폴더 비동기 압축 및 다운로드 워크플로우 <span class="badge">BullMQ + Redis 방식</span></h3>
<p>오래 걸리는 압축 다운로드 작업을 백그라운드에서 비동기로 수행하고, 소켓으로 클라이언트에 완료 알림을 제공하는 워크플로우입니다.</p>
<div class="diagram-card">
<div class="mermaid">
sequenceDiagram
autonumber
actor User as 사용자 (브라우저)
participant Server as 백엔드 서버 (Node.js)
participant Redis as Redis (6379)
participant Worker as 백그라운드 Worker
participant MinIO as 로컬 MinIO 스토리지
participant DB as 데이터베이스 (PostgreSQL)
User->>Server: 1. 폴더 압축 다운로드 요청
Server->>DB: 2. tb_download_folder에 'PENDING' 상태로 이력 기록
Server->>Redis: 3. BullMQ를 통해 압축 Job 발행 (Push)
Server-->>User: 4. 압축 작업 시작 안내 응답 (즉시 반환, 화면 안멈춤)
loop Worker 감시
Worker->>Redis: 5. 대기 중인 Job 가져오기 (Pop)
end
Note over Worker: 6. 폴더 내 모든 파일 탐색 및<br>zip 압축 파일 로컬 생성
Worker->>MinIO: 7. 생성된 zip 파일을 S3 스토리지에 업로드
Worker->>DB: 8. tb_download_folder 상태를 'COMPLETED'로 갱신,<br>zip_key 및 만료일(expire_date) 기록
Note over Server,User: 소켓(Socket.io) 또는 폴링 감시
Server->>User: 9. 압축 완료 알림 전송 (Socket.io)
User->>Server: 10. 내 다운로드 리스트 요청 (GET /getMyDownloadList)
Server->>DB: 11. 완료된 zip 다운로드 정보 조회
DB-->>Server: 12. zip 키값 반환
Server-->>User: 13. MinIO zip 다운로드 다운로드 링크 반환
</div>
</div>
<!-- 3. ERD 맵핑 관계 -->
<h2>3. 주요 데이터 스키마 간 맵핑 관계 (Core ERD)</h2>
<p>시스템 비즈니스 로직의 핵심이 되는 6개 주요 테이블 간의 참조 관계와 주요 필드 중심의 ERD 다이어그램입니다.</p>
<div class="diagram-card">
<div class="mermaid">
erDiagram
tb_user {
varchar user_id PK
varchar user_nm
varchar company
varchar dept
varchar group
}
tb_project {
varchar project_id PK
varchar user_id FK
varchar project_nm
varchar short_nm
boolean overview
boolean official_doc
boolean gsim
}
tb_data {
integer data_id PK
varchar project_id FK
varchar user_id FK
boolean is_folder
bigint data_size
text object_key
bigint popup_size
bigint preview_size
text ai_summary
timestamp create_date
}
tb_download_folder {
integer download_id PK
varchar project_id FK
varchar user_id FK
varchar status
text zip_key
timestamp expire_date
boolean made
}
tb_official_doc_file {
integer doc_id PK
varchar project_id FK
varchar uploader FK
varchar doc_number
varchar doc_date
text doc_title
text doc_title_summary
text doc_content_summary
bigint popup_size
bigint preview_size
}
tb_click_log {
integer click_log_id PK
varchar project_id FK
varchar user_id FK
varchar activity
varchar user_ip
text_array path_arr
int_array data_id_arr
}
%% Relationships
tb_user ||--o{ tb_project : "manages"
tb_project ||--o{ tb_data : "contains"
tb_user ||--o{ tb_data : "creates"
tb_project ||--o{ tb_download_folder : "contains"
tb_user ||--o{ tb_download_folder : "requests"
tb_project ||--o{ tb_official_doc_file : "contains"
tb_user ||--o{ tb_official_doc_file : "uploads"
tb_project ||--o{ tb_click_log : "records"
tb_user ||--o{ tb_click_log : "performs"
</div>
</div>
<!-- 4. 사용자 관리 업무 흐름 -->
<h2>4. 사용자 관리 업무 흐름</h2>
<p>신규 사용자 계정을 생성한 뒤, 프로젝트 현장을 등록하고, 해당 현장에 사용자를 배정하여 시스템 기능을 가동하는 일련의 흐름입니다.</p>
<div class="diagram-card">
<div class="mermaid">
graph TD
A["👤 1. 사용자 등록 (tb_user)<br>사용자 아이디, 패스워드, 이름, 회사/소속부서 등록"] --> B["🏗️ 2. 프로젝트 등록 (tb_project)<br>현장 고유
ID, 공식 명칭, 카테고리 구분, 용량제한 설정"]
B --> C["🔗 3. 프로젝트 사용자 등록 (tb_permission)<br>등록된 사용자를 특정 현장에 권한 등급(lev)과 함께 배정 및 연결"]
C --> D["💻 4. 시스템 사용 (tb_data / tb_official_doc_file)<br>배정받은 권한 레벨(Owner, Sub-Master, Worker, Viewer)에
맞춰 시스템 기능 활용"]
%% Styling
style A fill:#fff1f2,stroke:#f43f5e,stroke-width:2px;
style B fill:#fff1f2,stroke:#f43f5e,stroke-width:2px;
style C fill:#fff1f2,stroke:#f43f5e,stroke-width:2px;
style D fill:#be123c,stroke:#be123c,stroke-width:2px,color:#fff;
</div>
</div>
</div>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,793 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>통합 관리자 대시보드 구현 계획 및 테이블 명세 가이드</title>
<!-- Google Fonts - Inter & Noto Sans KR -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Noto+Sans+KR:wght@300;400;500;700&display=swap" rel="stylesheet">
<!-- Mermaid JS for Dynamic Diagrams -->
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
<script>
mermaid.initialize({
startOnLoad: true,
theme: 'forest',
themeVariables: {
primaryColor: '#1e5149',
primaryTextColor: '#fff',
lineColor: '#1e5149',
secondaryColor: '#142e29'
}
});
</script>
<style>
:root {
--primary: #1e5149;
--primary-dark: #142e29;
--primary-light: #e9eeed;
--primary-border: #d2dcdb;
--accent: #4db251;
--bg: #f4f7f6;
--card-bg: #ffffff;
--text-main: #1f2937;
--text-muted: #4b5563;
--code-bg: #1e293b;
--code-text: #f8fafc;
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -2px rgba(0, 0, 0, 0.05);
--sidebar-width: 280px;
}
* {
box-sizing: border-box;
}
body {
font-family: 'Inter', 'Noto Sans KR', sans-serif;
background-color: var(--bg);
color: var(--text-main);
line-height: 1.7;
margin: 0;
padding: 0;
display: flex;
}
/* Sidebar Navigation */
aside {
width: var(--sidebar-width);
background: linear-gradient(180deg, var(--primary-dark) 0%, #0c1a18 100%);
color: #ffffff;
height: 100vh;
position: fixed;
top: 0;
left: 0;
overflow-y: auto;
border-right: 1px solid rgba(255, 255, 255, 0.1);
padding: 24px;
z-index: 100;
}
aside h2 {
font-size: 1.15rem;
font-weight: 700;
margin-bottom: 24px;
color: var(--accent);
display: flex;
align-items: center;
gap: 8px;
border-bottom: 1px solid rgba(255, 255, 255, 0.15);
padding-bottom: 12px;
}
aside ul {
list-style: none;
padding: 0;
margin: 0;
}
aside li {
margin-bottom: 8px;
}
aside a {
color: #b3c5c2;
text-decoration: none;
font-size: 0.9rem;
display: block;
padding: 8px 12px;
border-radius: 6px;
transition: all 0.2s ease;
}
aside a:hover, aside li.active a {
color: #ffffff;
background-color: rgba(255, 255, 255, 0.08);
padding-left: 16px;
}
aside .category-title {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #628781;
margin: 16px 0 8px 12px;
font-weight: 700;
}
/* Main Content */
main {
margin-left: var(--sidebar-width);
flex-grow: 1;
padding: 40px 50px;
max-width: 1000px;
}
header {
border-bottom: 2px solid var(--primary-border);
padding-bottom: 24px;
margin-bottom: 40px;
}
header h1 {
font-size: 2.2rem;
font-weight: 700;
margin: 0 0 10px 0;
color: var(--primary);
}
header .subtitle {
font-size: 1.1rem;
color: var(--text-muted);
margin: 0;
}
/* Content Sections */
section {
background-color: var(--card-bg);
border-radius: 12px;
padding: 30px;
margin-bottom: 30px;
box-shadow: var(--shadow);
border: 1px solid var(--primary-border);
}
h2.section-title {
font-size: 1.4rem;
font-weight: 700;
color: var(--primary);
margin-top: 0;
margin-bottom: 20px;
border-left: 4px solid var(--accent);
padding-left: 12px;
}
h3 {
font-size: 1.15rem;
font-weight: 600;
color: var(--primary-dark);
margin-top: 24px;
margin-bottom: 12px;
}
p {
margin: 0 0 16px 0;
color: var(--text-main);
}
/* Callouts (GitHub alerts) */
.callout {
border-left: 4px solid;
padding: 16px;
margin-bottom: 20px;
border-radius: 0 8px 8px 0;
}
.callout-note {
background-color: #f0f7ff;
border-color: #0066cc;
color: #004080;
}
.callout-important {
background-color: #fff8f8;
border-color: #ef4444;
color: #991b1b;
}
.callout-title {
font-weight: 700;
margin-bottom: 6px;
display: flex;
align-items: center;
gap: 6px;
}
/* Tables */
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
font-size: 0.9rem;
border-radius: 8px;
overflow: hidden;
border: 1px solid var(--primary-border);
}
th {
background-color: var(--primary);
color: #ffffff;
font-weight: 600;
text-align: left;
padding: 12px 16px;
}
td {
padding: 12px 16px;
border-bottom: 1px solid var(--primary-border);
color: var(--text-main);
vertical-align: top;
}
tr:last-child td {
border-bottom: none;
}
tr:nth-child(even) td {
background-color: #f9fafb;
}
/* Code Blocks */
pre {
background-color: var(--code-bg);
color: var(--code-text);
padding: 18px;
border-radius: 8px;
font-family: 'Consolas', 'Courier New', Courier, monospace;
font-size: 0.85rem;
overflow-x: auto;
margin: 16px 0;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
}
code {
font-family: 'Consolas', 'Courier New', Courier, monospace;
background-color: #f1f5f9;
color: #0f172a;
padding: 2px 6px;
border-radius: 4px;
font-size: 0.85rem;
}
pre code {
background-color: transparent;
color: inherit;
padding: 0;
border-radius: 0;
font-size: inherit;
}
.method-badge {
display: inline-block;
padding: 3px 8px;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 700;
color: #ffffff;
margin-right: 6px;
}
.method-get { background-color: #0ea5e9; }
.method-post { background-color: #22c55e; }
.method-put { background-color: #f59e0b; }
.method-delete { background-color: #ef4444; }
.api-url {
font-weight: 600;
font-family: monospace;
}
/* List Styling */
ul {
padding-left: 20px;
margin-bottom: 16px;
}
li {
margin-bottom: 6px;
}
/* Diagram Container */
.diagram-container {
display: flex;
justify-content: center;
margin: 24px 0;
background-color: #ffffff;
padding: 20px;
border-radius: 8px;
border: 1px solid var(--primary-border);
}
</style>
</head>
<body>
<!-- Sidebar Navigation -->
<aside>
<h2>📁 PM_ver4 Admin Guide</h2>
<ul>
<li><a href="#overview">가이드 소개</a></li>
</ul>
<div class="category-title">01. 시스템 아키텍처</div>
<ul>
<li><a href="#architecture">전체 구조 & 로직</a></li>
<li><a href="#migration">단계별 구현 계획</a></li>
</ul>
<div class="category-title">02. 데이터 모델 (ERD)</div>
<ul>
<li><a href="#schema-design">스키마 구조 & 관계</a></li>
</ul>
<div class="category-title">03. 화면별 데이터 매핑</div>
<ul>
<li><a href="#screen-dashboard">1. 종합 용량 및 접속자</a></li>
<li><a href="#screen-project">2. 프로젝트 관리</a></li>
<li><a href="#screen-banner">3. 실시간 배너 공지</a></li>
<li><a href="#screen-user">4. 사용자 관리</a></li>
<li><a href="#screen-audit">5. 감사 로그 조회</a></li>
<li><a href="#screen-policy">6. 자동 삭제 정책 설정</a></li>
<li><a href="#screen-codes">7. 공통 코드 관리</a></li>
</ul>
<div class="category-title">04. 백엔드 API 설계</div>
<ul>
<li><a href="#api-endpoints">핵심 CRUD API 목록</a></li>
</ul>
</aside>
<!-- Main Content -->
<main>
<header id="overview">
<h1>통합 관리자 대시보드 구현 계획 및 테이블 명세 가이드</h1>
<p class="subtitle">관리자 화면(Admin Panel) 마이그레이션 및 데이터베이스 연동 설계서</p>
</header>
<!-- Section 1 -->
<section id="architecture">
<h2 class="section-title">1. 전체 시스템 아키텍처 및 구현 계획</h2>
<p>본 설계서는 <code>관리자화면_통합대시보드_UI제안.html</code> 정적 파일의 사용자 동작 시뮬레이션을 프로덕션 급 서버 인프라에 안착하기 위한 설계 명세입니다. 시스템 환경은 Node.js / PostgreSQL / MinIO S3 및 Redis Queue 아키텍처를 준수합니다.</p>
<div class="diagram-container">
<div class="mermaid">
graph TD
Client[Browser Admin Page] -->|REST API Requests| Backend[Express.js Web Server]
Client -->|WebSocket Event Channel| SocketServer[Socket.io Server]
Backend -->|SQL Query / PG client| DB[(PostgreSQL Database)]
Backend -->|Enqueue / Monitor| Redis[(Redis / BullMQ Queue)]
SocketServer -->|Active Session Cache| Memory[Node.js Process Memory]
</div>
</div>
<div class="callout callout-note">
<div class="callout-title"> 백엔드 연동 방식</div>
<div class="callout-content">
본 시스템은 관리 업무의 연속성 확보 및 동시성 처리를 위해 REST API 채널 외에도 Socket.io 서버 채널을 동시에 가용하여 실시간 웹소켓 통신을 처리하도록 구성됩니다.
</div>
</div>
</section>
<!-- Section 2 -->
<section id="migration">
<h2 class="section-title">구현 단계별 개발 로드맵</h2>
<h3>[1단계] 데이터베이스 스키마 확장 및 마이그레이션</h3>
<ul>
<li>공통 코드 분류 및 세부 값 제어를 위한 <code>code_master</code><code>code_detail</code> 테이블 신설.</li>
<li><strong>기존 테이블 활용 (컬럼 추가 없음)</strong>: 기존에 존재하는 프로젝트 테이블(<code>tb_project</code>)의 <code>category</code> 컬럼과 사용자 테이블(<code>tb_user</code>)의 <code>"group"</code> 컬럼을 <code>code_detail.base_code</code> 외래키 참조 구조로 그대로 연동하여 불필요한 테이블 팽창 및 컬럼 추가를 차단합니다.</li>
<li>시스템 공통 자동 보존 및 삭제 임계치를 독립적으로 제어하기 위한 <strong><code>tb_system_policy</code> (시스템 공통 정책 테이블)</strong> 신설.</li>
<li>실시간 배너 공지 이력 보관을 위한 <code>tb_banner_notice</code> 및 정기 자동삭제 실행 결과를 기록하기 위한 <code>tb_auto_clean_log</code> 신설.</li>
</ul>
<h3>[2단계] 백엔드 RESTful API 및 WebSocket 핸들러 개발</h3>
<ul>
<li><strong>REST API</strong>: 각 기능 및 공통 코드 데이터의 CRUD API Endpoint 구축 (Express Controller 연동).</li>
<li><strong>WebSocket</strong>: <code>socket.js</code>에 접속한 클라이언트 세션을 <code>users</code> 메모리 맵에 보관 및 관리자가 강제퇴장(<code>forcedLogout</code>) 이벤트를 호출하면 해당 클라이언트 소켓의 접속을 끊고 세션을 제거하도록 구현.</li>
</ul>
<h3>[3단계] 프론트엔드 어드민 대시보드 UI 연동</h3>
<ul>
<li>프로토타입 디자인 컨셉(Pretendard 서체, Forest Green 테마 색상)을 CSS 변수로 그대로 반영.</li>
<li>모달 다이얼로그와 탭 렌더링에 실시간 API 데이터 통신 로직 바인딩.</li>
</ul>
</section>
<!-- Section 3 -->
<section id="schema-design">
<h2 class="section-title">2. 데이터베이스 스키마 설계 (ERD)</h2>
<p>기존에 이미 보유한 <code>tb_project.category</code><code>tb_user."group"</code> 구조를 활용한 관계 정의는 다음과 같습니다.</p>
<div class="diagram-container">
<div class="mermaid">
classDiagram
class code_master {
+main_code : VARCHAR(30) PK
+main_code_nm : VARCHAR(100)
+use_yn : CHAR(1)
+rmk : VARCHAR(255)
}
class code_detail {
+main_code : VARCHAR(30) PK/FK
+sub_code : VARCHAR(30) PK
+base_code : VARCHAR(61) UNIQUE
+code_nm : VARCHAR(100)
+sort_ord : INT
+use_yn : CHAR(1)
+rmk : VARCHAR(255)
}
class tb_project {
+project_id : VARCHAR(50) PK
+project_nm : VARCHAR(100)
+category : VARCHAR(50) FK
+limit_storage : INT
+is_active : BOOLEAN
}
class tb_system_policy {
+policy_id : INT PK
+policy_key : VARCHAR(50) UNIQUE
+limit_file_count : INT
+limit_days : INT
+is_active : BOOLEAN
+upd_date : TIMESTAMP
}
class tb_user {
+user_id : VARCHAR(50) PK
+user_nm : VARCHAR(50)
+user_pw : VARCHAR(255)
+company : VARCHAR(50)
+dept : VARCHAR(50)
+position : VARCHAR(50)
+group : VARCHAR(50) FK
+is_resigned : BOOLEAN
}
class tb_permission {
+project_id : VARCHAR(50) PK/FK
+user_id : VARCHAR(50) PK/FK
+lev : INT
}
class tb_banner_notice {
+banner_id : SERIAL PK
+project_id : VARCHAR(50) FK
+reg_date : DATE
+start_date : DATE
+end_date : DATE
+notice_text : TEXT
+status_code : VARCHAR(61) FK
}
class tb_log {
+log_id : SERIAL PK
+project_id : VARCHAR(50)
+activity : VARCHAR(100)
+user_id : VARCHAR(50)
+user_ip : VARCHAR(50)
+log_date : TIMESTAMP
+path_arr : TEXT[]
}
code_master "1" --> "0..*" code_detail
code_detail "1" --> "0..*" tb_project : "category 참조"
code_detail "1" --> "0..*" tb_user : "group 참조"
code_detail "1" --> "0..*" tb_banner_notice : "status_code 참조"
tb_project "1" --> "0..*" tb_permission
tb_user "1" --> "0..*" tb_permission
tb_project "1" --> "0..*" tb_banner_notice
</div>
</div>
</section>
<!-- Section 4 -->
<section id="screen-dashboard">
<h2 class="section-title">3. 화면별 연동 테이블 및 데이터베이스 상세 매핑</h2>
<h3>📊 화면 1: 종합 용량 및 접속자 현황 (Dashboard)</h3>
<p><strong>주요 데이터 흐름 및 활용 테이블:</strong></p>
<ul>
<li><strong>전체 사용 용량 KPI & 현장별 게이지바</strong>:
<code>tb_project</code> 테이블의 한도 용량(<code>storage_byte</code>)과 <code>tb_data</code> 테이블의 실 누적 용량(<code>data_size</code>) 합산 결과 및 파일 건수를 <code>COUNT()</code>하여 렌더링.
</li>
<li><strong>실시간 접속자 세션 목록</strong>:
데이터베이스를 경유하지 않고 Node.js 메인 프로세스(<code>socket.js</code>)의 메모리 세션 해시맵에서 직접 연결 상태(ID, IP, 위치 경로)를 추출.
</li>
<li><strong>대기 중인 압축작업</strong>:
Redis(BullMQ Queue) 대기열 API를 조회하여 보류 중인 백그라운드 압축 다운로드 개수 파악.
</li>
</ul>
</section>
<section id="screen-project">
<h3>🏗️ 화면 2: 프로젝트 관리 (Project Management)</h3>
<p><strong>주요 데이터 흐름 및 활용 테이블:</strong></p>
<ul>
<li><strong>좌측 프로젝트 그리드</strong>: <code>tb_project</code> 테이블 전체 데이터와 <code>code_detail</code>의 카테고리 코드 한글 명칭(TDC, GPD 등)을 <code>tb_project.category</code> 외래키 관계를 조인하여 렌더링.</li>
<li><strong>우측 참여 권한 사용자 목록</strong>: <code>tb_permission</code> $\bowtie$ <code>tb_user</code> 조인을 통해 해당 현장에 기속된 사용자 리스트업 및 등급 인라인 셀렉트 업데이트.</li>
<li><strong>사용자 배정 추가 모달</strong>: <code>tb_user</code>의 전체 사용자 데이터와 현재 프로젝트에 속해 있지 않은 유저 차집합 연산으로 가용 유저 목록 구성.</li>
</ul>
</section>
<section id="screen-banner">
<h3>📢 화면 3: 실시간 배너 공지 (Banner Notice)</h3>
<p><strong>주요 데이터 흐름 및 활용 테이블:</strong></p>
<ul>
<li><strong>공지사항 이력 및 등록 폼</strong>: <code>tb_banner_notice</code> 테이블 CRUD 매핑.</li>
<li><strong>송출 상태 계산</strong>:
오늘 날짜 기준 <code>start_date</code><code>end_date</code> 조건을 체크하여 공통코드 매핑 상태(<code>NOTICE_STATUS_active</code>, <code>NOTICE_STATUS_scheduled</code>, <code>NOTICE_STATUS_expired</code>)를 동적 할당.
</li>
<li><strong>검색 필터</strong>: 송출상태 및 등록일 범위(From ~ To) 기준 <code>WHERE status_code = ? AND reg_date BETWEEN ? AND ?</code> 쿼리 연동.</li>
</ul>
</section>
<section id="screen-user">
<h3>👥 화면 4: 사용자 관리 (User Management)</h3>
<p><strong>주요 데이터 흐름 및 활용 테이블:</strong></p>
<ul>
<li><strong>좌측 계정 목록</strong>: <code>tb_user</code> 테이블 및 <code>code_detail</code> 권한그룹(기존 <code>"group"</code> 컬럼 재사용) 매핑 렌더링.</li>
<li><strong>우측 참여 프로젝트 목록</strong>: <code>tb_permission</code> $\bowtie$ <code>tb_project</code> 조인을 활용하여 선택 유저가 소속된 모든 프로젝트 ID 및 현장명 출력.</li>
</ul>
</section>
<section id="screen-audit">
<h3>🔎 화면 5: 감사 로그 조회 (Audit Logs)</h3>
<p><strong>주요 데이터 흐름 및 활용 테이블:</strong></p>
<ul>
<li><strong>시스템 감사 이력 목록</strong>:
파일 삭제/이동/다운로드 이벤트를 적재하는 <code>tb_log</code> 테이블을 필터 검색 쿼리(사용자 ID, 액션타입)와 연동하여 리스트 렌더링.
</li>
</ul>
</section>
<section id="screen-policy">
<h3>⚙️ 화면 6: 자동 보존 및 파일 삭제 정책 설정 (Delete Policy)</h3>
<p><strong>주요 데이터 흐름 및 활용 테이블:</strong></p>
<ul>
<li><strong>시스템 공통 자동 삭제 정책 설정 폼</strong>:
프로젝트 단위 설정을 탈피하고, 신설된 <code>tb_system_policy</code>의 단일 글로벌 레코드를 제어.
</li>
<li><strong>자동 삭제 정기 실행 이력</strong>:
정기 스케줄러 배치 구동 시의 로그 기록을 <code>tb_auto_clean_log</code>에서 리스트업. 설정값 저장 및 기록 적재 시 프로젝트 ID 대신 <code>'SYSTEM'</code> 기록 식별자를 사용.
</li>
</ul>
</section>
<section id="screen-codes">
<h3>🔑 화면 7: 공통 코드 관리 (Common Code Management)</h3>
<p><strong>주요 데이터 흐름 및 활용 테이블:</strong></p>
<ul>
<li><strong>상단 마스터 대분류</strong>: <code>code_master</code> 테이블 CRUD.</li>
<li><strong>하단 상세 소분류</strong>: <code>code_detail</code> 테이블 CRUD.
<ul>
<li>대분류 선택이 없을 시 소분류 등록 폼 진입 차단 유효성 제어.</li>
<li>소분류의 <code>base_code</code> 컬럼은 <code>main_code || '_' || sub_code</code> 조합으로 자동 동기화.</li>
<li>대분류 삭제 시 외래키 <code>ON DELETE CASCADE</code> 명세를 통하여 소분류 레코드가 데이터베이스에서 연쇄적으로 자동 청소되도록 설정.</li>
</ul>
</li>
</ul>
</section>
<!-- Section 5 -->
<section id="api-endpoints">
<h2 class="section-title">4. 핵심 백엔드 CRUD API Endpoint 설계안</h2>
<p>프론트엔드-백엔드 간 통신을 위해 구성되어야 할 RESTful API 리스트입니다.</p>
<h3>1. 프로젝트 관리 API (<code>/api/admin/projects</code>)</h3>
<table>
<thead>
<tr>
<th style="width: 15%;">메소드</th>
<th style="width: 35%;">엔드포인트</th>
<th style="width: 50%;">설명</th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="method-badge method-get">GET</span></td>
<td class="api-url">/</td>
<td>전체 프로젝트 및 카테고리 정보 조회</td>
</tr>
<tr>
<td><span class="method-badge method-post">POST</span></td>
<td class="api-url">/</td>
<td>신규 프로젝트 등록</td>
</tr>
<tr>
<td><span class="method-badge method-put">PUT</span></td>
<td class="api-url">/:id</td>
<td>특정 프로젝트 내용(카테고리 category 값, 용량제한, 활성화) 갱신</td>
</tr>
<tr>
<td><span class="method-badge method-delete">DELETE</span></td>
<td class="api-url">/:id</td>
<td>프로젝트 영구 삭제 (참여 권한 및 메타데이터 자동 CASCADE)</td>
</tr>
</tbody>
</table>
<h3>2. 프로젝트 권한 배정 API (<code>/api/admin/permissions</code>)</h3>
<table>
<thead>
<tr>
<th style="width: 15%;">메소드</th>
<th style="width: 35%;">엔드포인트</th>
<th style="width: 50%;">설명</th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="method-badge method-get">GET</span></td>
<td class="api-url">/project/:projectId</td>
<td>특정 현장에 참여 중인 유저 목록 조회</td>
</tr>
<tr>
<td><span class="method-badge method-post">POST</span></td>
<td class="api-url">/assign</td>
<td>현장에 특정 사용자 권한 신규 부여 (다중 배정)</td>
</tr>
<tr>
<td><span class="method-badge method-put">PUT</span></td>
<td class="api-url">/update</td>
<td>배정 유저의 권한 등급(lev) 수정</td>
</tr>
<tr>
<td><span class="method-badge method-delete">DELETE</span></td>
<td class="api-url">/remove</td>
<td>현장 참여 권한 배정 제외 (매핑 행 삭제)</td>
</tr>
</tbody>
</table>
<h3>3. 실시간 배너 공지 API (<code>/api/admin/banners</code>)</h3>
<table>
<thead>
<tr>
<th style="width: 15%;">메소드</th>
<th style="width: 35%;">엔드포인트</th>
<th style="width: 50%;">설명</th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="method-badge method-get">GET</span></td>
<td class="api-url">/</td>
<td>배너 송출 이력 조회 (상태, 날짜검색 필터 지원)</td>
</tr>
<tr>
<td><span class="method-badge method-post">POST</span></td>
<td class="api-url">/</td>
<td>신규 배너 공지 작성 및 등록</td>
</tr>
<tr>
<td><span class="method-badge method-put">PUT</span></td>
<td class="api-url">/stop/:id</td>
<td>공지 수동 송출 중지 (상태를 expired로 강제 업데이트)</td>
</tr>
</tbody>
</table>
<h3>4. 사용자 관리 API (<code>/api/admin/users</code>)</h3>
<table>
<thead>
<tr>
<th style="width: 15%;">메소드</th>
<th style="width: 35%;">엔드포인트</th>
<th style="width: 50%;">설명</th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="method-badge method-get">GET</span></td>
<td class="api-url">/</td>
<td>전체 계정 정보 및 권한그룹 조회</td>
</tr>
<tr>
<td><span class="method-badge method-get">GET</span></td>
<td class="api-url">/:id/permissions</td>
<td>해당 유저가 참여 권한을 지닌 프로젝트 목록 조회</td>
</tr>
<tr>
<td><span class="method-badge method-post">POST</span></td>
<td class="api-url">/</td>
<td>신규 사용자 계정 등록 (패스워드 bcrypt 암호화)</td>
</tr>
<tr>
<td><span class="method-badge method-put">PUT</span></td>
<td class="api-url">/:id</td>
<td>사용자 정보(권한 그룹 group 포함) 및 재직 상태 갱신</td>
</tr>
<tr>
<td><span class="method-badge method-delete">DELETE</span></td>
<td class="api-url">/:id</td>
<td>사용자 계정 삭제 (권한 정보 및 설정 연쇄 삭제)</td>
</tr>
</tbody>
</table>
<h3>5. 공통 코드 관리 API (<code>/api/admin/common-codes</code>)</h3>
<table>
<thead>
<tr>
<th style="width: 15%;">메소드</th>
<th style="width: 35%;">엔드포인트</th>
<th style="width: 50%;">설명</th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="method-badge method-get">GET</span></td>
<td class="api-url">/masters</td>
<td>대분류 마스터 코드 목록 조회</td>
</tr>
<tr>
<td><span class="method-badge method-post">POST</span></td>
<td class="api-url">/masters</td>
<td>신규 대분류 등록</td>
</tr>
<tr>
<td><span class="method-badge method-put">PUT</span></td>
<td class="api-url">/masters/:code</td>
<td>대분류 수정</td>
</tr>
<tr>
<td><span class="method-badge method-delete">DELETE</span></td>
<td class="api-url">/masters/:code</td>
<td>대분류 삭제 (하위 소분류 자동 연쇄 삭제)</td>
</tr>
<tr>
<td><span class="method-badge method-get">GET</span></td>
<td class="api-url">/details/:mainCode</td>
<td>선택된 대분류에 해당하는 소분류 정렬 조회</td>
</tr>
<tr>
<td><span class="method-badge method-post">POST</span></td>
<td class="api-url">/details</td>
<td>신규 소분류 등록 (base_code 자동 연산 생성)</td>
</tr>
<tr>
<td><span class="method-badge method-put">PUT</span></td>
<td class="api-url">/details/:mainCode/:subCode</td>
<td>소분류 수정 (명칭, 정렬순서, 사용여부)</td>
</tr>
<tr>
<td><span class="method-badge method-delete">DELETE</span></td>
<td class="api-url">/details/:mainCode/:subCode</td>
<td>소분류 삭제</td>
</tr>
</tbody>
</table>
<h3>6. 시스템 공통 보존 정책 API (<code>/api/admin/system-policy</code>)</h3>
<table>
<thead>
<tr>
<th style="width: 15%;">메소드</th>
<th style="width: 35%;">엔드포인트</th>
<th style="width: 50%;">설명</th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="method-badge method-get">GET</span></td>
<td class="api-url">/</td>
<td>시스템 글로벌 보존 정책 조회</td>
</tr>
<tr>
<td><span class="method-badge method-post">POST</span></td>
<td class="api-url">/update</td>
<td>글로벌 보존 정책 값 갱신 및 저장</td>
</tr>
</tbody>
</table>
</section>
</main>
</body>
</html>

View File

@@ -0,0 +1,586 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PM_ver4 통합 관리자 어플리케이션 화면설계서</title>
<!-- Google Fonts - Inter & Noto Sans KR -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Noto+Sans+KR:wght@300;400;500;700&display=swap" rel="stylesheet">
<style>
:root {
--primary: #1e5149;
--primary-dark: #142e29;
--primary-soft: #e9eeed;
--border: #d2dcdb;
--accent: #4db251;
--bg: #f4f7f6;
--card-bg: #ffffff;
--text-main: #1f2937;
--text-muted: #4b5563;
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -2px rgba(0, 0, 0, 0.05);
--sidebar-width: 280px;
/* UI Status Colors */
--color-active: #4db251;
--bg-active: #eef8ee;
--color-inactive: #a5b9b6;
--bg-inactive: #f1f5f9;
--color-danger: #ef4444;
--bg-danger: #fee2e2;
--color-warning: #ff9800;
--bg-warning: #fff5e6;
}
* {
box-sizing: border-box;
}
body {
font-family: 'Inter', 'Noto Sans KR', sans-serif;
background-color: var(--bg);
color: var(--text-main);
line-height: 1.7;
margin: 0;
padding: 0;
display: flex;
}
/* Sidebar Navigation */
aside {
width: var(--sidebar-width);
background: linear-gradient(180deg, var(--primary-dark) 0%, #0c1a18 100%);
color: #ffffff;
height: 100vh;
position: fixed;
top: 0;
left: 0;
overflow-y: auto;
border-right: 1px solid rgba(255, 255, 255, 0.1);
padding: 24px;
z-index: 100;
}
aside h2 {
font-size: 1.15rem;
font-weight: 700;
margin-bottom: 24px;
color: var(--accent);
display: flex;
align-items: center;
gap: 8px;
border-bottom: 1px solid rgba(255, 255, 255, 0.15);
padding-bottom: 12px;
}
aside ul {
list-style: none;
padding: 0;
margin: 0;
}
aside li {
margin-bottom: 8px;
}
aside a {
color: #b3c5c2;
text-decoration: none;
font-size: 0.9rem;
display: block;
padding: 8px 12px;
border-radius: 6px;
transition: all 0.2s ease;
}
aside a:hover, aside li.active a {
color: #ffffff;
background-color: rgba(255, 255, 255, 0.08);
padding-left: 16px;
}
aside .category-title {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #628781;
margin: 16px 0 8px 12px;
font-weight: 700;
}
/* Main Content */
main {
margin-left: var(--sidebar-width);
flex-grow: 1;
padding: 40px 50px;
max-width: 1000px;
}
header {
border-bottom: 2px solid var(--border);
padding-bottom: 24px;
margin-bottom: 40px;
}
header h1 {
font-size: 2.2rem;
font-weight: 700;
margin: 0 0 10px 0;
color: var(--primary);
}
header .subtitle {
font-size: 1.1rem;
color: var(--text-muted);
margin: 0;
}
/* Content Sections */
section {
background-color: var(--card-bg);
border-radius: 12px;
padding: 30px;
margin-bottom: 30px;
box-shadow: var(--shadow);
border: 1px solid var(--border);
}
h2.section-title {
font-size: 1.4rem;
font-weight: 700;
color: var(--primary);
margin-top: 0;
margin-bottom: 20px;
border-left: 4px solid var(--accent);
padding-left: 12px;
}
h3 {
font-size: 1.2rem;
font-weight: 600;
color: var(--primary-dark);
margin-top: 24px;
margin-bottom: 12px;
border-bottom: 1px solid #f1f5f9;
padding-bottom: 8px;
}
h4 {
font-size: 1.05rem;
font-weight: 600;
color: var(--primary);
margin-top: 16px;
margin-bottom: 8px;
}
p {
margin: 0 0 16px 0;
color: var(--text-main);
}
/* Color Chips */
.color-palette {
display: flex;
flex-wrap: wrap;
gap: 16px;
margin: 20px 0;
}
.color-chip {
flex: 1 1 200px;
border: 1px solid var(--border);
border-radius: 8px;
overflow: hidden;
background-color: #ffffff;
box-shadow: var(--shadow);
}
.color-box {
height: 60px;
width: 100%;
}
.color-details {
padding: 10px 12px;
}
.color-name {
font-weight: 700;
font-size: 0.85rem;
margin-bottom: 2px;
}
.color-value {
font-family: monospace;
font-size: 0.8rem;
color: var(--text-muted);
}
/* Layout Preview map */
.layout-preview {
background-color: #1e293b;
color: #cbd5e1;
padding: 20px;
border-radius: 8px;
font-family: monospace;
font-size: 0.8rem;
white-space: pre;
overflow-x: auto;
margin: 20px 0;
line-height: 1.4;
}
/* Status Badge */
.status-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.8rem;
font-weight: 600;
}
.status-badge-active { background-color: var(--bg-active); color: var(--color-active); }
.status-badge-inactive { background-color: var(--bg-inactive); color: var(--color-inactive); }
.status-badge-danger { background-color: var(--bg-danger); color: var(--color-danger); }
.status-badge-warning { background-color: var(--bg-warning); color: var(--color-warning); }
/* Tables */
table {
width: 100%;
border-collapse: collapse;
margin: 16px 0;
font-size: 0.88rem;
border-radius: 8px;
overflow: hidden;
border: 1px solid var(--border);
}
th {
background-color: var(--primary-soft);
color: var(--primary-dark);
font-weight: 600;
text-align: left;
padding: 10px 14px;
border-bottom: 2px solid var(--border);
}
td {
padding: 10px 14px;
border-bottom: 1px solid var(--border);
color: var(--text-main);
}
tr:last-child td {
border-bottom: none;
}
/* Lists */
ul {
padding-left: 20px;
margin-bottom: 16px;
}
li {
margin-bottom: 6px;
}
.spec-card {
border: 1px solid var(--border);
border-radius: 8px;
padding: 16px;
margin-bottom: 16px;
background-color: #f9fafb;
}
</style>
</head>
<body>
<!-- Sidebar Navigation -->
<aside>
<h2>📁 PM_ver4 Spec</h2>
<ul>
<li><a href="#overview">설계서 소개</a></li>
</ul>
<div class="category-title">01. 디자인 시스템</div>
<ul>
<li><a href="#design-system">디자인 가이드라인</a></li>
</ul>
<div class="category-title">02. 공통 프레임</div>
<ul>
<li><a href="#layout">App Frame 레이아웃</a></li>
</ul>
<div class="category-title">03. 화면별 UI/UX 명세</div>
<ul>
<li><a href="#screen-dashboard">📊 종합 용량 및 접속자</a></li>
<li><a href="#screen-project">🏗️ 프로젝트 관리</a></li>
<li><a href="#screen-banner">📢 실시간 배너 공지</a></li>
<li><a href="#screen-user">👥 사용자 관리</a></li>
<li><a href="#screen-audit">🔎 감사 로그 조회</a></li>
<li><a href="#screen-policy">⚙️ 보관 및 삭제 설정</a></li>
<li><a href="#screen-codes">🔑 공통 코드 관리</a></li>
</ul>
</aside>
<!-- Main Content -->
<main>
<header id="overview">
<h1>PM_ver4 통합 관리자 어플리케이션 화면설계서</h1>
<p class="subtitle">UI/UX Specification - 대시보드 및 관리자 화면 기능 명세서</p>
</header>
<!-- Section 1 -->
<section id="design-system">
<h2 class="section-title">1. 공통 UI 가이드라인 및 디자인 시스템</h2>
<p>관리자 패널의 모든 화면 레이아웃, 컴포넌트 명세, 사용자 액션 및 데이터 정합성 검증 규칙은 본 시스템의 디자인 규칙을 준수합니다.</p>
<h3>① 색상 토큰 (Color Tokens)</h3>
<div class="color-palette">
<div class="color-chip">
<div class="color-box" style="background-color: #1e5149;"></div>
<div class="color-details">
<div class="color-name">Primary Forest Green</div>
<div class="color-value">#1e5149</div>
</div>
</div>
<div class="color-chip">
<div class="color-box" style="background-color: #142e29;"></div>
<div class="color-details">
<div class="color-name">Dark Teal Sidebar</div>
<div class="color-value">#142e29</div>
</div>
</div>
<div class="color-chip">
<div class="color-box" style="background-color: #d2dcdb;"></div>
<div class="color-details">
<div class="color-name">Light Green Gray Border</div>
<div class="color-value">#d2dcdb</div>
</div>
</div>
<div class="color-chip">
<div class="color-box" style="background-color: #e9eeed;"></div>
<div class="color-details">
<div class="color-name">Soft Accent Green BG</div>
<div class="color-value">#e9eeed</div>
</div>
</div>
</div>
<h3>② 서체 (Typography)</h3>
<ul>
<li><strong>서체 패밀리</strong>: <code>'Pretendard Variable', 'Pretendard', 'Inter', 'Noto Sans KR', sans-serif</code></li>
<li><strong>글꼴 크기 명세</strong>:
<ul>
<li>페이지 메인 타이틀: <code>1.25rem</code> (700 Bold)</li>
<li>카드 타이틀: <code>1.05rem</code> (700 Bold)</li>
<li>일반 본문 및 테이블 데이터: <code>0.875rem</code> (500 Medium / 600 Semi-Bold)</li>
<li>Muted 보조 텍스트: <code>0.8rem</code> / <code>0.75rem</code></li>
</ul>
</li>
</ul>
<h3>③ 공통 그리드 & 테이블 (Table Grid Rules)</h3>
<ul>
<li><strong>순번 표시 (NO)</strong>: 모든 테이블 그리드의 1열은 데이터 인덱스 번호(<code>NO</code>)를 필수 노출합니다.</li>
<li><strong>가로 구분선</strong>: 답답한 디자인을 방지하기 위해 세로 테두리(Vertical Borders)는 일절 노출하지 않으며, 가로 행 구분선만 노출합니다.</li>
<li><strong>행 선택 인터랙션</strong>: 마우스 호버 시 <code>var(--primary-soft)</code> 배경색을 지정하며, 클릭 행 활성화 시 텍스트 두께 변경으로 인한 줄높이 왜곡이 없도록 패딩 상속(inherit) 속성을 적용하여 균일한 행 높이를 유지합니다.</li>
</ul>
<h3>④ 공통 모달 팝업 (Modal Overlay Rules)</h3>
<ul>
<li><strong>백드롭</strong>: <code>rgba(20, 30, 29, 0.6)</code> 반투명 딤 및 <code>backdrop-filter: blur(4px)</code> 효과로 모달 포커스를 고정합니다.</li>
<li><strong>트랜지션</strong>: <code>fade-in</code><code>slide-up (translateY 30px to 0)</code>이 일괄 적용되어 매끄러운 팝업 동작을 보장합니다.</li>
</ul>
</section>
<!-- Section 2 -->
<section id="layout">
<h2 class="section-title">2. 레이아웃 구조 설계 (App Frame Layout)</h2>
<p>LNB, 상단 헤더, 그리고 메인 콘텐츠 탭 영역의 수평/수직 분할 아키텍처는 다음과 같습니다.</p>
<div class="layout-preview">
+-------------------------------------------------------------------------+
| LNB (좌측 사이드바) | Main Header (상단 헤더) |
| 📁 PM_ver4 Admin | [Header Title] [Admin Profile] |
|-------------------------+-----------------------------------------------|
| - Dashboards | Main Content (메인 콘텐츠 탭 영역) |
| 📊 종합 용량/접속자 | |
| - 프로젝트 관리 | +-----------------------------------------+ |
| 🏗️ 프로젝트 관리 | | 카드 1 (필터 / 테이블 리스트) | |
| 📢 실시간 배너 공지 | +-----------------------------------------+ |
| - 사용자 및 권한 | | 카드 2 (상세정보 뷰 / 팝업 연동 리스트) | |
| 👥 사용자 관리 | +-----------------------------------------+ |
| - 시스템 감사 및 환경 | |
| 🔎 감사 로그 조회 | |
| ⚙️ 자동 삭제 설정 | |
| 🔑 공통 코드 관리 | |
+-------------------------------------------------------------------------+</div>
</section>
<!-- Section 3 -->
<section id="screen-dashboard">
<h2 class="section-title">3. 화면별 상세 UI 및 기능 설계</h2>
<h3>📊 화면 1: 종합 용량 및 접속자 현황 (Dashboard)</h3>
<p>상단 3열 KPI 요약 카드와 하단 스토리지 프로그레스바 및 실시간 소켓 접속자 테이블 구조입니다.</p>
<div class="spec-card">
<h4>① 주요 UI 컴포넌트</h4>
<ul>
<li><strong>스토리지 KPI 카드</strong>: 전체 현장의 총 한도 용량 대비 누적 사용 용량 실시간 합산 표시 (예: <code>💾 9.70 GB / 20 GB</code>).</li>
<li><strong>접속자 KPI 카드</strong>: 현재 소켓 서버 연결 세션 수 노출.</li>
<li><strong>압축작업 KPI 카드</strong>: Redis(BullMQ) 내 대기중인 압축 건수.</li>
<li><strong>현장별 스토리지 사용 현황</strong>: 각 프로젝트 ID/명칭, 게이지바 및 사용량 정보(GB / 백분율% / 파일수량개) 동시 렌더링.</li>
</ul>
<h4>② 실시간 접속 현황 테이블 사양</h4>
<table>
<thead>
<tr>
<th style="width: 10%;">NO</th>
<th style="width: 25%;">사용자 ID</th>
<th style="width: 25%;">접속 IP</th>
<th style="width: 25%;">현재 조회 경로</th>
<th style="width: 15%;">작업</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>admin_test</td>
<td>127.0.0.1</td>
<td><code>/PM_TEST_01/archive</code></td>
<td><span class="status-badge status-badge-danger" style="cursor: pointer;">강제퇴장</span></td>
</tr>
</tbody>
</table>
</div>
</section>
<section id="screen-project">
<h3>🏗️ 화면 2: 프로젝트 관리 (Project Management)</h3>
<p>좌측 프로젝트 목록 그리드와 우측 프로젝트별 참여자 및 배정 통제 그리드 구조입니다.</p>
<div class="spec-card">
<h4>① 프로젝트 목록 테이블 사양 (좌측 카드)</h4>
<p>출력 컬럼: <code>NO</code> | <code>프로젝트 ID</code> | <code>현장명</code> | <code>카테고리</code> | <code>용량 제한(GB)</code> | <code>상태</code> | <code>관리(수정/삭제)</code></p>
<ul>
<li>행 클릭 시, 우측의 '참여 권한 사용자 목록'이 해당 프로젝트 정보로 자동 리바인딩됩니다.</li>
<li><strong>삭제 제한</strong>: 관련 테이블(tb_data, tb_official_doc_file, tb_banner_notice 등)에 현장 ID 사용 이력이 있으면 삭제 불가능하며 경고 메시지가 발생합니다.</li>
<li><strong>신규 프로젝트 등록 및 수정 모달 (projectModalOverlay)</strong>: 프로젝트 ID(수정 시 Readonly), 프로젝트명, 단축명, 카테고리 Select, 스토리지 제한(GB), 상태를 편집합니다.</li>
</ul>
<h4>② 참여 권한 사용자 목록 (우측 카드 - 병합 영역)</h4>
<p>출력 컬럼: <code>NO</code> | <code>사용자 ID</code> | <code>이름</code> | <code>부서/직급</code> | <code>권한 등급</code> | <code>작업(배정제외)</code></p>
<ul>
<li><strong>권한 등급 변경</strong>: 인라인 셀렉터(Admin, Sub-Master, Worker, Viewer)로 권한 레벨 즉시 업데이트.</li>
<li><strong>사용자 배정 추가 팝업 모달 (assignModalOverlay)</strong>: 현재 현장에 미배정된 사용자들을 체크박스로 다중 선택하여 일괄 추가합니다. 또한 목록 선택식을 지원하기 위해 우측 하단 배정 대기 목록에서도 '즉시 배정' 단축 버튼을 제공합니다.</li>
</ul>
</div>
</section>
<section id="screen-banner">
<h3>📢 화면 3: 실시간 배너 공지 (Banner Notice)</h3>
<p>상단 배너 등록 폼 카드 및 하단 이력 검색 조건 필터와 이력 목록 그리드 구조입니다.</p>
<div class="spec-card">
<h4>① 배너 공지 등록 폼</h4>
<ul>
<li>입력 필드: 대상 프로젝트 선택(특정 현장 또는 전체 현장 'all' 매핑), 등록일(임의 편집 지원), 시작일, 종료일, 공지 자막 텍스트.</li>
<li>송출 등록 제출 시, 오늘 일자와 비교하여 즉시 이력에 추가되고 상태 배지가 실시간 부여됩니다.</li>
</ul>
<h4>② 이력 목록 필터 및 이력 테이블</h4>
<ul>
<li><strong>검색 필터</strong>: 송출 상태(전체, 송출중, 예약됨, 만료) 및 등록일(from ~ to) 날짜 범위 지정 검색.</li>
<li><strong>테이블 명세</strong>: <code>NO</code> | <code>등록일</code> | <code>대상 프로젝트</code> | <code>공지 내용</code> | <code>시작일</code> | <code>종료일</code> | <code>송출 상태</code> | <code>작업</code></li>
<li><strong>송출 중지 통제</strong>: 아직 기간이 유효한 행(송출중, 예약됨)에만 <code>[송출 중지]</code> 버튼이 노출 및 활성화되며, 이미 만료된 이력은 <code>[중지 완료]</code> 비활성 텍스트로 대체하여 이중 제어를 차단합니다.</li>
</ul>
</div>
</section>
<section id="screen-user">
<h3>👥 화면 4: 사용자 관리 (User Management)</h3>
<p>좌측 사용자 마스터 리스트 및 우측 선택된 사용자의 참여 권한 프로젝트 리스트업 구조입니다.</p>
<div class="spec-card">
<h4>① 사용자 계정 목록 (좌측 카드)</h4>
<p>출력 컬럼: <code>NO</code> | <code>아이디</code> | <code>이름</code> | <code>소속/직급</code> | <code>그룹</code> | <code>상태</code> | <code>관리(수정/삭제)</code></p>
<ul>
<li>행 클릭 시, 해당 사용자가 참여하고 있는 프로젝트 리스트가 우측 카드에 즉시 바인딩됩니다.</li>
<li><strong>삭제 제한</strong>: 권한 테이블(tb_permission)에 현장 배정/참여 권한 정보가 등록되어 있으면 삭제가 불가능하며 경고 메시지가 발생합니다.</li>
<li><strong>사용자 등록 및 수정 모달 (userModalOverlay)</strong>: 아이디(수정 시 Readonly), 패스워드, 이름, 회사명, 부서, 직급, 권한 그룹 지정 select, 재직 상태(재직/퇴직잠금) 지정.</li>
</ul>
<h4>② 권한부여 프로젝트 목록 (우측 카드)</h4>
<p>출력 컬럼: <code>NO</code> | <code>프로젝트 ID</code> | <code>프로젝트명</code> | <code>부여 권한 등급</code></p>
</div>
</section>
<section id="screen-audit">
<h3>🔎 화면 5: 감사 로그 조회 (Audit Logs)</h3>
<p>파일 조작 중요 이벤트(삭제, 이동, 다운로드) 목록 및 검색 조회 화면입니다.</p>
<div class="spec-card">
<h4>① 감사 로그 목록 사양</h4>
<p>출력 컬럼: <code>NO</code> | <code>일시</code> | <code>프로젝트</code> | <code>사용자 ID</code> | <code>접속 IP</code> | <code>조작 액션</code> | <code>조작 대상 경로(코드박스 스타일)</code></p>
<ul>
<li>필터링 항목: 사용자 ID 검색 입력란, 조작 액션 Dropdown, 검색 기능.</li>
</ul>
</div>
</section>
<section id="screen-policy">
<h3>⚙️ 화면 6: 자동 보관 및 파일 삭제 정책 설정 (Delete Policy)</h3>
<p>시스템 글로벌 일괄 정책 설정 영역, 실시간 예정 시나리오 요약, 그리고 배치 처리 이력 구조입니다.</p>
<div class="spec-card">
<h4>① 시스템 공통 자동 삭제 정책 설정 폼 [글로벌 정책 공통화]</h4>
<ul>
<li><strong>입력 필드</strong>: 정책 활성화 여부(Toggle/Select), 최소 유지 파일 개수 기준(숫자 입력), 자동 삭제 제한 기한(일) (숫자 입력).</li>
<li><strong>글로벌 통합</strong>: 기존의 프로젝트 개별 Dropdown은 완전히 배제하고, 전체 현장에 동일하게 일괄 반영합니다.</li>
</ul>
<h4>② 보존 정책 실시간 요약 (Dynamic Summary)</h4>
<ul>
<li>폼의 입력값을 변경하는 즉시 요약 영역 텍스트가 시나리오 문구로 동적 조합되어 나타납니다.</li>
<li>예: <code>"현재 전체 공통 설정에 따라, 각 현장의 보관 파일 수가 100개 미만이고 30일이 지나면 자동 삭제 배치 스케줄러가 작동합니다."</code></li>
</ul>
<h4>③ 자동 삭제 처리 이력 테이블 사양</h4>
<p>출력 컬럼: <code>NO</code> | <code>자동 처리 일자</code> | <code>프로젝트 ID</code> | <code>삭제 처리 폴더 경로</code> | <code>적용 기준</code> | <code>처리 결과(성공 배지)</code></p>
<ul>
<li>정책 값 저장 완료 시 이력 로그의 대상 프로젝트 ID 자리에는 <code>'SYSTEM'</code>이 기입됩니다.</li>
</ul>
</div>
</section>
<section id="screen-codes">
<h3>🔑 화면 7: 공통 코드 관리 (Common Code Management)</h3>
<p>대분류 마스터 및 세부 코드 리스트가 배치되는 상하 2단 수직 정렬 레이아웃 구조입니다.</p>
<div class="spec-card">
<h4>① 대분류 코드 마스터 (상단 카드)</h4>
<p>출력 컬럼: <code>NO</code> | <code>대분류 코드</code> | <code>대분류 코드명</code> | <code>사용</code> | <code>관리(수정/삭제)</code></p>
<ul>
<li>행을 선택(click)하면 해당 행이 하이라이트(selected)되며, 하단의 세부 코드 그리드가 동적으로 새로고침됩니다.</li>
<li><strong>대분류 등록 및 수정 모달 (codeMasterModalOverlay)</strong>: 대분류 코드, 명칭, 사용여부, 비고 설명 입력.</li>
</ul>
<h4>② 세부 소분류 코드 목록 (하단 카드)</h4>
<p>출력 컬럼: <code>NO</code> | <code>소분류 코드</code> | <code>조합 코드 (base_code)</code> | <code>코드 명칭</code> | <code>정렬 순서</code> | <code>사용</code> | <code>관리(수정/삭제)</code></p>
<ul>
<li><strong>유효성 방어 차단</strong>: 상단 대분류 테이블에서 행을 클릭하여 선택하지 않은 상태에서는 하단의 <code>[ 세부코드 등록]</code> 버튼이 강제 비활성화(disabled)되며, 팝업 접근 시 안내 팝업 및 경고 텍스트(<code>"상단에서 대분류 코드를 선택해 주세요."</code>)를 노출합니다.</li>
<li><strong>조합 코드(base_code)</strong>: 소분류 생성 완료 제출 시, <code>대분류코드_소분류코드</code> 형태로 자동 결합되어 저장됩니다.</li>
<li><strong>삭제 제한 (RESTRICT)</strong>: 마스터 대분류 코드를 삭제할 경우, 하위 세부 코드(code_detail)가 존재하면 대분류 삭제가 차단되고 경고 메시지를 노출합니다. (세부 코드가 먼저 삭제되어 비어있을 때만 대분류 삭제 가능)</li>
</ul>
</div>
</section>
</main>
</body>
</html>