feat(nav): 임직원(OPS) 로그인 시 하단 관리자·토큰 모달, README 반영

- 좌측 하단 구분선+관리자: OPS 세션에서도 토큰으로 /admin 인증 가능
- OPS+관리자 시 사용자 현황관리·로그오프·로그아웃 정리
- nav-item-admin-entry 스타일(슬레이트 톤)
- README: 관리자 UI·학습 등록·성공 사례 버튼 동작, partials 구조 보강

Made-with: Cursor
This commit is contained in:
2026-04-08 17:31:26 +09:00
parent fa338dfadf
commit 747caa9c99
3 changed files with 35 additions and 5 deletions

View File

@@ -64,6 +64,10 @@
- 관리자 토큰으로 강의 삭제 가능
- 초기 샘플 데이터 시드
- `resources/lecture`에 있는 `.pptx`를 최초 실행 시 자동 등록
- **관리자 인증·바로가기(UI)**
- **좌측 메뉴 하단 `관리자`**: 운영(OPS)에서 **이메일 인증**으로 로그인한 임직원에게도 표시됩니다. 클릭하면 모달에서 `ADMIN_TOKEN`을 입력해 검증한 뒤 `/admin`으로 이동합니다(`POST /api/admin/validate-token`). 이메일 미로그인 환경에서도 동일한 하단 항목으로 관리자 인증을 할 수 있습니다. 관리자 세션이 잡히면 같은 영역에 **사용자 현황관리**·**로그오프**(관리자 세션 종료) 등이 이어서 보입니다.
- **학습센터** (`/learning`): 관리자 쿠키가 있을 때 상단 오른쪽 **학습 등록**으로 통합 관리 화면(`/admin`)에 들어갈 수 있습니다. 이메일(OPS) 로그인과 동시에 있어도 버튼이 숨겨지지 않습니다.
- **AI 성공 사례** (`/ai-cases`): 관리자일 때 상단 **사례 등록·관리**로 편집 화면(`/ai-cases/write`)에 진입합니다(동일하게 OPS 로그인 중에도 표시).
---
@@ -83,7 +87,8 @@ ai_platform/
│ └─ styles.css # 전역 스타일
├─ views/
│ ├─ partials/
│ │ ─ nav.ejs # 좌측 공통 네비게이션
│ │ ─ nav.ejs # 좌측 공통 네비게이션(하단 관리자·토큰 모달 연동)
│ │ └─ admin-token-modal.ejs # 관리자 토큰 입력 모달(`openAdminTokenModal`)
│ ├─ learning-viewer.ejs # 학습센터 뷰어 (일반 사용자)
│ ├─ learning-admin.ejs # 학습센터 관리 (업로드·삭제·썸네일)
│ ├─ chat.ejs # 채팅
@@ -379,6 +384,7 @@ ADMIN_TOKEN=my-secret PAGE_SIZE=12 npm start
- `ADMIN_TOKEN` 미지정 시 기본값: `xavis-admin`
- `PAGE_SIZE` 미지정 시 기본값: `8`
- `.env` 파일이 있으면 `dotenv`로 자동 로드
- 브라우저에서는 좌측 메뉴 하단 **관리자** → 모달에 위 토큰을 입력해 세션 쿠키를 발급받는 방식으로 `/admin`에 진입할 수 있습니다(임직원 이메일 로그인 여부와 동일한 흐름).
### PostgreSQL 연결 설정

View File

@@ -100,13 +100,29 @@ body {
font-size: 11px;
}
/* 이메일(OPS) 인증 사용자: 관리자 버튼 위 로그아웃 */
/* 이메일(OPS) 인증: 하단 관리자(토큰) — 참조 디자인 뮤트 슬레이트 톤 */
.nav-item.nav-item-admin-entry {
color: #8e9aaf;
font-size: 12px;
font-weight: 500;
}
.nav-item.nav-item-admin-entry:hover {
background: #f3f4f6;
color: #64748b;
}
/* 이메일(OPS) 인증 사용자: 로그아웃 */
.nav-item.nav-item-ops-logout {
color: #111827;
font-weight: 600;
font-size: 13px;
}
.nav-separator.nav-separator-footer-admin {
margin-top: 4px;
}
.nav-footer {
margin-top: auto;
padding-top: 8px;

View File

@@ -30,10 +30,19 @@
<a href="/ai-cases" class="nav-item <%= activeMenu === 'ai-cases' ? 'active' : '' %>">성공사례</a>
<div class="nav-footer">
<% var _opsLoggedIn = typeof opsUserEmail !== 'undefined' && opsUserEmail; %>
<% var _admin = typeof adminMode !== 'undefined' && adminMode; %>
<% if (_opsLoggedIn) { %>
<% if (_admin) { %>
<a href="/admin/users" class="nav-item <%= activeMenu === 'admin-users' ? 'active' : '' %>">사용자 현황관리</a>
<div class="nav-separator"></div>
<a href="/admin/logout" class="nav-item nav-item-ghost" title="관리자 세션 종료">로그오프</a>
<a href="/logout" class="nav-item nav-item-ops-logout" title="이메일 인증 세션 종료">로그아웃</a>
<% } else { %>
<% if (typeof adminMode !== 'undefined' && adminMode) { %>
<% } else { %>
<div class="nav-separator nav-separator-footer-admin"></div>
<button type="button" class="nav-item nav-item-admin-entry" onclick="openAdminTokenModal()" title="관리자 토큰으로 인증">관리자</button>
<a href="/logout" class="nav-item nav-item-ops-logout" title="이메일 인증 세션 종료">로그아웃</a>
<% } %>
<% } else if (_admin) { %>
<a href="/admin/users" class="nav-item <%= activeMenu === 'admin-users' ? 'active' : '' %>">사용자 현황관리</a>
<div class="nav-separator"></div>
<a href="/admin/logout" class="nav-item nav-item-ghost" title="관리자 세션 종료">로그오프</a>
@@ -41,7 +50,6 @@
<div class="nav-separator"></div>
<button type="button" class="nav-item nav-item-ghost" onclick="openAdminTokenModal()" title="관리자 모드">관리자</button>
<% } %>
<% } %>
</div>
</aside>
<script>