refactor(gateway): gateway↔system1 분리 — gateway=문짝, system1-web=독립
gateway에서 system1 프록시 제거, 대시보드+로그인+공유JS만 담당. system1-web에 /auth/, /ai-api/ 프록시 이관. tkds-web 제거(gateway 흡수). notification-bell URL tkfb→tkds, system3 로그인 URL tkds/dashboard로 변경. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
108
ARCHITECTURE.md
Normal file
108
ARCHITECTURE.md
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
# TK Factory Services — 시스템 아키텍처
|
||||||
|
|
||||||
|
## 전체 구조
|
||||||
|
|
||||||
|
21개 컨테이너, Docker Compose 기반, Cloudflare Tunnel로 외부 노출.
|
||||||
|
|
||||||
|
```
|
||||||
|
[Cloudflare Tunnel] → tk-cloudflared
|
||||||
|
├── tkds.technicalkorea.net → tk-gateway:80 (로그인 + 대시보드 + 공유JS)
|
||||||
|
├── tkfb.technicalkorea.net → tk-system1-web:80 (공장관리)
|
||||||
|
├── tkreport.technicalkorea.net → tk-system2-web:80 (신고)
|
||||||
|
├── tkqc.technicalkorea.net → tk-system3-web:80 (부적합관리)
|
||||||
|
├── tkuser.technicalkorea.net → tk-tkuser-web:80 (통합관리)
|
||||||
|
├── tkpurchase.technicalkorea.net → tk-tkpurchase-web:80 (구매관리)
|
||||||
|
├── tksafety.technicalkorea.net → tk-tksafety-web:80 (안전관리)
|
||||||
|
└── tksupport.technicalkorea.net → tk-tksupport-web:80 (행정지원)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 서비스 목록
|
||||||
|
|
||||||
|
| 서비스 | 컨테이너명 | 로컬 포트 | 내부 포트 | 역할 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| **Gateway** | tk-gateway | 30000 | 80 | 로그인/대시보드/공유JS + SSO·API 프록시 |
|
||||||
|
| **System1 API** | tk-system1-api | 30005 | 3005 | 공장관리 백엔드 (Node.js) |
|
||||||
|
| **System1 Web** | tk-system1-web | 30080 | 80 | 공장관리 프론트엔드 (nginx) |
|
||||||
|
| **System1 FastAPI** | tk-system1-fastapi | 30008 | 8000 | FastAPI Bridge |
|
||||||
|
| **System2 API** | tk-system2-api | 30105 | 3005 | 신고 백엔드 (Node.js) |
|
||||||
|
| **System2 Web** | tk-system2-web | 30180 | 80 | 신고 프론트엔드 (nginx) |
|
||||||
|
| **System3 API** | tk-system3-api | 30200 | 8000 | 부적합관리 백엔드 (FastAPI) |
|
||||||
|
| **System3 Web** | tk-system3-web | 30280 | 80 | 부적합관리 프론트엔드 (nginx) |
|
||||||
|
| **User API** | tk-tkuser-api | 30300 | 3000 | 사용자관리 백엔드 (Node.js) |
|
||||||
|
| **User Web** | tk-tkuser-web | 30380 | 80 | 사용자관리 프론트엔드 (nginx) |
|
||||||
|
| **Purchase API** | tk-tkpurchase-api | 30400 | 3000 | 구매관리 백엔드 (Node.js) |
|
||||||
|
| **Purchase Web** | tk-tkpurchase-web | 30480 | 80 | 구매관리 프론트엔드 (nginx) |
|
||||||
|
| **Safety API** | tk-tksafety-api | 30500 | 3000 | 안전관리 백엔드 (Node.js) |
|
||||||
|
| **Safety Web** | tk-tksafety-web | 30580 | 80 | 안전관리 프론트엔드 (nginx) |
|
||||||
|
| **Support API** | tk-tksupport-api | 30600 | 3000 | 행정지원 백엔드 (Node.js) |
|
||||||
|
| **Support Web** | tk-tksupport-web | 30680 | 80 | 행정지원 프론트엔드 (nginx) |
|
||||||
|
| **SSO Auth** | tk-sso-auth | 30050 | 3000 | SSO 인증 서비스 (Node.js) |
|
||||||
|
| **MariaDB** | tk-mariadb | 30306 | 3306 | 메인 데이터베이스 |
|
||||||
|
| **Redis** | tk-redis | — | 6379 | 세션/캐시 |
|
||||||
|
| **phpMyAdmin** | tk-phpmyadmin | 30880 | 80 | DB 관리 도구 |
|
||||||
|
| **Cloudflared** | tk-cloudflared | — | — | Cloudflare Tunnel 에이전트 |
|
||||||
|
|
||||||
|
## 요청 흐름
|
||||||
|
|
||||||
|
```
|
||||||
|
브라우저 → Cloudflare DNS → Cloudflare Tunnel → cloudflared 컨테이너
|
||||||
|
→ 서브도메인별 라우팅 → 해당 web 컨테이너 (nginx)
|
||||||
|
→ /api/ → 해당 API 컨테이너
|
||||||
|
→ /auth/ → sso-auth 컨테이너
|
||||||
|
→ /uploads/ → API 컨테이너 (파일 서빙)
|
||||||
|
→ 나머지 → 정적 파일 (SPA fallback)
|
||||||
|
```
|
||||||
|
|
||||||
|
## SSO 인증 흐름
|
||||||
|
|
||||||
|
1. 사용자가 아무 서비스 접근 → 프론트엔드 JS가 `sso_token` 쿠키 확인
|
||||||
|
2. 토큰 없음/만료 → `tkds.technicalkorea.net/dashboard`로 리다이렉트 (redirect 파라미터 포함)
|
||||||
|
3. 로그인 폼 제출 → `POST /auth/login` → sso-auth가 JWT 발급
|
||||||
|
4. 쿠키 설정: `sso_token`, `sso_user`, `sso_refresh_token` (domain=`.technicalkorea.net`)
|
||||||
|
5. redirect 파라미터가 있으면 원래 페이지로, 없으면 대시보드 표시
|
||||||
|
6. 각 서비스의 API는 `Authorization: Bearer <token>` 헤더로 인증 검증
|
||||||
|
|
||||||
|
## CORS 관리
|
||||||
|
|
||||||
|
- **sso-auth**: `sso-auth-service/config/` — 모든 `*.technicalkorea.net` 서브도메인 허용
|
||||||
|
- **system1-factory API**: `system1-factory/api/config/cors.js` — 허용 origin 명시적 관리
|
||||||
|
- 로컬 네트워크(192.168.x.x)는 자동 허용
|
||||||
|
|
||||||
|
## 공유 JS
|
||||||
|
|
||||||
|
Gateway(`/shared/`)에서 서빙:
|
||||||
|
- `notification-bell.js` — 알림 벨 UI, 모든 서비스에서 로딩
|
||||||
|
- `nav-header.js` — 공통 네비게이션 헤더
|
||||||
|
|
||||||
|
각 서비스의 core.js에서 동적 로딩:
|
||||||
|
```
|
||||||
|
프로덕션: https://tkds.technicalkorea.net/shared/notification-bell.js
|
||||||
|
로컬: http://localhost:30000/shared/notification-bell.js
|
||||||
|
```
|
||||||
|
|
||||||
|
## 신규 서비스 추가 체크리스트
|
||||||
|
|
||||||
|
1. `<service>/api/` + `<service>/web/` 디렉토리 생성 (Dockerfile 포함)
|
||||||
|
2. `docker-compose.yml`에 api + web 서비스 추가 (포트 할당)
|
||||||
|
3. `cloudflared.depends_on`에 web 서비스 추가
|
||||||
|
4. Cloudflare Tunnel 대시보드에서 서브도메인 → 컨테이너 라우팅 추가
|
||||||
|
5. `sso-auth-service/config/`에 새 origin 추가
|
||||||
|
6. `system1-factory/api/config/cors.js`에 새 origin 추가 (API 호출 시)
|
||||||
|
7. 알림 벨 사용 시: core.js에 `_loadNotificationBell()` 함수 추가
|
||||||
|
8. 로그인 리다이렉트: `tkds.technicalkorea.net/dashboard?redirect=` 패턴 사용
|
||||||
|
|
||||||
|
## 배포 절차
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 로컬에서 push
|
||||||
|
git push
|
||||||
|
|
||||||
|
# NAS에서 pull + rebuild
|
||||||
|
ssh hyungi@100.71.132.52 "cd /volume1/docker/tk-factory-services && \
|
||||||
|
git pull && \
|
||||||
|
export PATH=\$PATH:/volume2/@appstore/ContainerManager/usr/bin && \
|
||||||
|
docker compose up -d --build <서비스명>"
|
||||||
|
```
|
||||||
|
|
||||||
|
전체 재시작: `docker compose up -d --build`
|
||||||
|
특정 서비스만: `docker compose up -d --build gateway system1-web`
|
||||||
@@ -130,6 +130,7 @@ services:
|
|||||||
- ./system1-factory/web:/usr/share/nginx/html:ro
|
- ./system1-factory/web:/usr/share/nginx/html:ro
|
||||||
depends_on:
|
depends_on:
|
||||||
- system1-api
|
- system1-api
|
||||||
|
- sso-auth
|
||||||
networks:
|
networks:
|
||||||
- tk-network
|
- tk-network
|
||||||
|
|
||||||
@@ -417,22 +418,7 @@ services:
|
|||||||
# =================================================================
|
# =================================================================
|
||||||
|
|
||||||
# =================================================================
|
# =================================================================
|
||||||
# Dashboard (tkds)
|
# Gateway (로그인 + 대시보드 + 공유JS)
|
||||||
# =================================================================
|
|
||||||
|
|
||||||
tkds-web:
|
|
||||||
build:
|
|
||||||
context: ./tkds/web
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
container_name: tk-tkds-web
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- "30780:80"
|
|
||||||
networks:
|
|
||||||
- tk-network
|
|
||||||
|
|
||||||
# =================================================================
|
|
||||||
# Gateway
|
|
||||||
# =================================================================
|
# =================================================================
|
||||||
|
|
||||||
gateway:
|
gateway:
|
||||||
@@ -445,9 +431,7 @@ services:
|
|||||||
- "30000:80"
|
- "30000:80"
|
||||||
depends_on:
|
depends_on:
|
||||||
- sso-auth
|
- sso-auth
|
||||||
- system1-web
|
- system1-api
|
||||||
- system2-web
|
|
||||||
- system3-web
|
|
||||||
networks:
|
networks:
|
||||||
- tk-network
|
- tk-network
|
||||||
|
|
||||||
@@ -485,7 +469,7 @@ services:
|
|||||||
- TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN}
|
- TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN}
|
||||||
depends_on:
|
depends_on:
|
||||||
- gateway
|
- gateway
|
||||||
- tkds-web
|
- system1-web
|
||||||
- system2-web
|
- system2-web
|
||||||
- system3-web
|
- system3-web
|
||||||
- tkpurchase-web
|
- tkpurchase-web
|
||||||
|
|||||||
@@ -344,7 +344,7 @@
|
|||||||
if (hostname.includes('technicalkorea.net')) {
|
if (hostname.includes('technicalkorea.net')) {
|
||||||
return protocol + '//' + name + '.technicalkorea.net';
|
return protocol + '//' + name + '.technicalkorea.net';
|
||||||
}
|
}
|
||||||
var ports = { tkfb: 30000, tkreport: 30180, tkqc: 30280, tkuser: 30380, tkpurchase: 30480, tksafety: 30580, tksupport: 30680 };
|
var ports = { tkfb: 30080, tkreport: 30180, tkqc: 30280, tkuser: 30380, tkpurchase: 30480, tksafety: 30580, tksupport: 30680 };
|
||||||
return protocol + '//' + hostname + ':' + (ports[name] || 30000);
|
return protocol + '//' + hostname + ':' + (ports[name] || 30000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,240 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ko">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>로그인 - TK 공장관리</title>
|
|
||||||
<style>
|
|
||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
||||||
background: #f0f2f5;
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
.login-box {
|
|
||||||
background: white;
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 40px;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 400px;
|
|
||||||
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
|
|
||||||
}
|
|
||||||
.login-box h1 {
|
|
||||||
text-align: center;
|
|
||||||
color: #1a56db;
|
|
||||||
font-size: 22px;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
|
||||||
.login-box .sub {
|
|
||||||
text-align: center;
|
|
||||||
color: #6b7280;
|
|
||||||
font-size: 13px;
|
|
||||||
margin-bottom: 28px;
|
|
||||||
}
|
|
||||||
.form-group { margin-bottom: 16px; }
|
|
||||||
.form-group label {
|
|
||||||
display: block;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #374151;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
|
||||||
.form-group input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px 14px;
|
|
||||||
border: 1px solid #d1d5db;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 14px;
|
|
||||||
outline: none;
|
|
||||||
transition: border-color 0.2s;
|
|
||||||
}
|
|
||||||
.form-group input:focus {
|
|
||||||
border-color: #1a56db;
|
|
||||||
box-shadow: 0 0 0 3px rgba(26,86,219,0.1);
|
|
||||||
}
|
|
||||||
.btn-submit {
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px;
|
|
||||||
background: #1a56db;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 500;
|
|
||||||
cursor: pointer;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
.btn-submit:hover { background: #1e40af; }
|
|
||||||
.btn-submit:disabled { background: #93c5fd; cursor: not-allowed; }
|
|
||||||
.error-msg {
|
|
||||||
color: #dc2626;
|
|
||||||
font-size: 13px;
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 12px;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="login-box">
|
|
||||||
<h1>TK 공장관리 시스템</h1>
|
|
||||||
<p class="sub">통합 로그인</p>
|
|
||||||
|
|
||||||
<form id="loginForm" onsubmit="handleLogin(event)">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="username">사용자명</label>
|
|
||||||
<input type="text" id="username" name="username" required autofocus autocomplete="username">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="password">비밀번호</label>
|
|
||||||
<input type="password" id="password" name="password" required autocomplete="current-password">
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn-submit" id="submitBtn">로그인</button>
|
|
||||||
<p class="error-msg" id="errorMsg"></p>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// SSO 쿠키 유틸리티
|
|
||||||
var ssoCookie = {
|
|
||||||
set: function(name, value, days) {
|
|
||||||
var cookie = name + '=' + encodeURIComponent(value) + '; path=/';
|
|
||||||
if (days) cookie += '; max-age=' + (days * 86400);
|
|
||||||
if (window.location.hostname.includes('technicalkorea.net')) {
|
|
||||||
cookie += '; domain=.technicalkorea.net; secure; samesite=lax';
|
|
||||||
}
|
|
||||||
document.cookie = cookie;
|
|
||||||
},
|
|
||||||
get: function(name) {
|
|
||||||
var match = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]*)'));
|
|
||||||
return match ? decodeURIComponent(match[1]) : null;
|
|
||||||
},
|
|
||||||
remove: function(name) {
|
|
||||||
var cookie = name + '=; path=/; max-age=0';
|
|
||||||
if (window.location.hostname.includes('technicalkorea.net')) {
|
|
||||||
cookie += '; domain=.technicalkorea.net; secure; samesite=lax';
|
|
||||||
}
|
|
||||||
document.cookie = cookie;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
async function handleLogin(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
var btn = document.getElementById('submitBtn');
|
|
||||||
var errEl = document.getElementById('errorMsg');
|
|
||||||
errEl.style.display = 'none';
|
|
||||||
btn.disabled = true;
|
|
||||||
btn.textContent = '로그인 중...';
|
|
||||||
|
|
||||||
try {
|
|
||||||
var res = await fetch('/auth/login', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
username: document.getElementById('username').value,
|
|
||||||
password: document.getElementById('password').value
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
var data = await res.json();
|
|
||||||
|
|
||||||
if (!res.ok || !data.success) {
|
|
||||||
throw new Error(data.error || '로그인에 실패했습니다');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 쿠키에 토큰 저장 (서브도메인 공유)
|
|
||||||
ssoCookie.set('sso_token', data.access_token, 7);
|
|
||||||
ssoCookie.set('sso_user', JSON.stringify(data.user), 7);
|
|
||||||
if (data.refresh_token) {
|
|
||||||
ssoCookie.set('sso_refresh_token', data.refresh_token, 30);
|
|
||||||
}
|
|
||||||
|
|
||||||
// localStorage에도 저장 (같은 도메인 내 호환성)
|
|
||||||
localStorage.setItem('sso_token', data.access_token);
|
|
||||||
localStorage.setItem('sso_user', JSON.stringify(data.user));
|
|
||||||
if (data.refresh_token) {
|
|
||||||
localStorage.setItem('sso_refresh_token', data.refresh_token);
|
|
||||||
}
|
|
||||||
|
|
||||||
// redirect 파라미터가 있으면 해당 URL로, 없으면 포털로
|
|
||||||
var redirect = new URLSearchParams(location.search).get('redirect');
|
|
||||||
if (redirect && isSafeRedirect(redirect)) {
|
|
||||||
window.location.href = redirect;
|
|
||||||
} else {
|
|
||||||
window.location.href = '/';
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
errEl.textContent = err.message;
|
|
||||||
errEl.style.display = '';
|
|
||||||
} finally {
|
|
||||||
btn.disabled = false;
|
|
||||||
btn.textContent = '로그인';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 안전한 리다이렉트인지 확인 (같은 도메인 상대 경로 또는 *.technicalkorea.net)
|
|
||||||
function isSafeRedirect(url) {
|
|
||||||
if (!url) return false;
|
|
||||||
// 상대 경로
|
|
||||||
if (/^\/[a-zA-Z0-9]/.test(url) && !url.includes('://') && !url.includes('//')) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// technicalkorea.net 서브도메인 절대 URL
|
|
||||||
try {
|
|
||||||
var parsed = new URL(url);
|
|
||||||
return parsed.hostname.endsWith('.technicalkorea.net') || parsed.hostname === 'technicalkorea.net';
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 토큰 만료 확인
|
|
||||||
function isTokenValid(token) {
|
|
||||||
try {
|
|
||||||
var payload = JSON.parse(atob(token.split('.')[1]));
|
|
||||||
return payload.exp > Math.floor(Date.now() / 1000);
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// logout=1 파라미터가 있으면 모든 인증 데이터 정리
|
|
||||||
var isLogout = new URLSearchParams(location.search).get('logout') === '1';
|
|
||||||
if (isLogout) {
|
|
||||||
ssoCookie.remove('sso_token');
|
|
||||||
ssoCookie.remove('sso_user');
|
|
||||||
ssoCookie.remove('sso_refresh_token');
|
|
||||||
['sso_token','sso_user','sso_refresh_token','token','user','access_token',
|
|
||||||
'currentUser','current_user','userInfo','userPageAccess'].forEach(function(k) {
|
|
||||||
localStorage.removeItem(k);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 이미 로그인 되어있으면 포털로 (쿠키 또는 localStorage 체크 + 만료 확인)
|
|
||||||
var existingToken = isLogout ? null : (ssoCookie.get('sso_token') || localStorage.getItem('sso_token'));
|
|
||||||
if (existingToken && existingToken !== 'undefined' && existingToken !== 'null') {
|
|
||||||
if (isTokenValid(existingToken)) {
|
|
||||||
// 쿠키 재설정 (localStorage에만 있고 쿠키가 없는 경우 대비)
|
|
||||||
var existingUser = ssoCookie.get('sso_user') || localStorage.getItem('sso_user');
|
|
||||||
var existingRefresh = ssoCookie.get('sso_refresh_token') || localStorage.getItem('sso_refresh_token');
|
|
||||||
ssoCookie.set('sso_token', existingToken, 7);
|
|
||||||
if (existingUser) ssoCookie.set('sso_user', existingUser, 7);
|
|
||||||
if (existingRefresh) ssoCookie.set('sso_refresh_token', existingRefresh, 30);
|
|
||||||
|
|
||||||
var redirect = new URLSearchParams(location.search).get('redirect');
|
|
||||||
window.location.href = (redirect && isSafeRedirect(redirect)) ? redirect : '/';
|
|
||||||
} else {
|
|
||||||
// 만료된 토큰 정리
|
|
||||||
ssoCookie.remove('sso_token');
|
|
||||||
ssoCookie.remove('sso_user');
|
|
||||||
ssoCookie.remove('sso_refresh_token');
|
|
||||||
localStorage.removeItem('sso_token');
|
|
||||||
localStorage.removeItem('sso_user');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,254 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ko">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>TK 공장관리 시스템</title>
|
|
||||||
<style>
|
|
||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
||||||
background: #f0f2f5;
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.header {
|
|
||||||
background: #1a56db;
|
|
||||||
color: white;
|
|
||||||
padding: 16px 24px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
.header h1 { font-size: 20px; font-weight: 600; }
|
|
||||||
.user-info {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
.user-info span { opacity: 0.9; }
|
|
||||||
.btn-logout {
|
|
||||||
background: rgba(255,255,255,0.2);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 6px 14px;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
.btn-logout:hover { background: rgba(255,255,255,0.3); }
|
|
||||||
.container {
|
|
||||||
max-width: 900px;
|
|
||||||
margin: 40px auto;
|
|
||||||
padding: 0 20px;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
.welcome {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 36px;
|
|
||||||
}
|
|
||||||
.welcome h2 { font-size: 26px; color: #1f2937; margin-bottom: 8px; }
|
|
||||||
.welcome p { color: #6b7280; font-size: 15px; }
|
|
||||||
.systems {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
.system-card {
|
|
||||||
background: white;
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 28px;
|
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
||||||
transition: transform 0.2s, box-shadow 0.2s;
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: none;
|
|
||||||
color: inherit;
|
|
||||||
border: 2px solid transparent;
|
|
||||||
}
|
|
||||||
.system-card:hover {
|
|
||||||
transform: translateY(-4px);
|
|
||||||
box-shadow: 0 8px 25px rgba(0,0,0,0.12);
|
|
||||||
}
|
|
||||||
.system-card.s1 { border-top: 4px solid #1a56db; }
|
|
||||||
.system-card.s2 { border-top: 4px solid #dc2626; }
|
|
||||||
.system-card.s3 { border-top: 4px solid #059669; }
|
|
||||||
.system-icon { font-size: 36px; margin-bottom: 14px; }
|
|
||||||
.system-card h3 { font-size: 18px; color: #1f2937; margin-bottom: 6px; }
|
|
||||||
.system-card p { color: #6b7280; font-size: 13px; line-height: 1.5; }
|
|
||||||
.system-card .badge {
|
|
||||||
display: inline-block;
|
|
||||||
margin-top: 12px;
|
|
||||||
padding: 3px 10px;
|
|
||||||
border-radius: 12px;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.s1 .badge { background: #dbeafe; color: #1d4ed8; }
|
|
||||||
.s2 .badge { background: #fee2e2; color: #dc2626; }
|
|
||||||
.s3 .badge { background: #d1fae5; color: #059669; }
|
|
||||||
.footer {
|
|
||||||
text-align: center;
|
|
||||||
padding: 20px;
|
|
||||||
color: #9ca3af;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
.login-prompt {
|
|
||||||
text-align: center;
|
|
||||||
padding: 60px 20px;
|
|
||||||
}
|
|
||||||
.login-prompt h2 { margin-bottom: 16px; color: #1f2937; }
|
|
||||||
.btn-login {
|
|
||||||
display: inline-block;
|
|
||||||
background: #1a56db;
|
|
||||||
color: white;
|
|
||||||
padding: 12px 32px;
|
|
||||||
border-radius: 8px;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
.btn-login:hover { background: #1e40af; }
|
|
||||||
.no-access {
|
|
||||||
opacity: 0.5;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="header">
|
|
||||||
<h1>TK 공장관리 시스템</h1>
|
|
||||||
<div class="user-info" id="userInfo" style="display:none">
|
|
||||||
<span id="userName"></span>
|
|
||||||
<span id="userRole"></span>
|
|
||||||
<button class="btn-logout" onclick="logout()">로그아웃</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<!-- 로그인 전 -->
|
|
||||||
<div class="login-prompt" id="loginPrompt">
|
|
||||||
<h2>시스템에 접속하려면 로그인하세요</h2>
|
|
||||||
<a href="/login" class="btn-login">로그인</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 로그인 후 -->
|
|
||||||
<div id="dashboard" style="display:none">
|
|
||||||
<div class="welcome">
|
|
||||||
<h2 id="welcomeText">환영합니다</h2>
|
|
||||||
<p>사용할 시스템을 선택하세요</p>
|
|
||||||
</div>
|
|
||||||
<div class="systems">
|
|
||||||
<a href="/pages/dashboard.html" class="system-card s1" id="card-s1">
|
|
||||||
<div class="system-icon">🏭</div>
|
|
||||||
<h3>공장관리</h3>
|
|
||||||
<p>작업보고, 근태관리, TBM, 순회점검, 장비관리 등 현장 운영 전반</p>
|
|
||||||
<span class="badge">System 1</span>
|
|
||||||
</a>
|
|
||||||
<a id="card-s2-link" class="system-card s2" id="card-s2">
|
|
||||||
<div class="system-icon">🚨</div>
|
|
||||||
<h3>신고 시스템</h3>
|
|
||||||
<p>안전/부적합 이슈 신고, 처리현황 추적, 부적합 자동 연동</p>
|
|
||||||
<span class="badge">System 2</span>
|
|
||||||
</a>
|
|
||||||
<a id="card-s3-link" class="system-card s3" id="card-s3">
|
|
||||||
<div class="system-icon">📋</div>
|
|
||||||
<h3>부적합 관리</h3>
|
|
||||||
<p>부적합 이슈 접수, 처리, 리포트 생성, 프로젝트별 현황 관리</p>
|
|
||||||
<span class="badge">System 3</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer">TK Factory Services v1.0</div>
|
|
||||||
|
|
||||||
<script src="/shared/nav-header.js"></script>
|
|
||||||
<script>
|
|
||||||
var ssoCookie = {
|
|
||||||
get: function(name) {
|
|
||||||
var match = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]*)'));
|
|
||||||
return match ? decodeURIComponent(match[1]) : null;
|
|
||||||
},
|
|
||||||
remove: function(name) {
|
|
||||||
var cookie = name + '=; path=/; max-age=0';
|
|
||||||
if (window.location.hostname.includes('technicalkorea.net')) {
|
|
||||||
cookie += '; domain=.technicalkorea.net; secure; samesite=lax';
|
|
||||||
}
|
|
||||||
document.cookie = cookie;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function getToken() {
|
|
||||||
return ssoCookie.get('sso_token') || localStorage.getItem('sso_token');
|
|
||||||
}
|
|
||||||
function getUser() {
|
|
||||||
var raw = ssoCookie.get('sso_user') || localStorage.getItem('sso_user');
|
|
||||||
try { return JSON.parse(raw); } catch(e) { return null; }
|
|
||||||
}
|
|
||||||
|
|
||||||
function init() {
|
|
||||||
var token = getToken();
|
|
||||||
var user = getUser();
|
|
||||||
|
|
||||||
if (token && user) {
|
|
||||||
showDashboard(user);
|
|
||||||
} else {
|
|
||||||
document.getElementById('loginPrompt').style.display = '';
|
|
||||||
document.getElementById('dashboard').style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function showDashboard(user) {
|
|
||||||
document.getElementById('loginPrompt').style.display = 'none';
|
|
||||||
document.getElementById('dashboard').style.display = '';
|
|
||||||
document.getElementById('userInfo').style.display = 'flex';
|
|
||||||
document.getElementById('userName').textContent = user.name || user.username;
|
|
||||||
document.getElementById('userRole').textContent = '(' + (user.role || '') + ')';
|
|
||||||
document.getElementById('welcomeText').textContent =
|
|
||||||
(user.name || user.username) + '님, 환영합니다';
|
|
||||||
|
|
||||||
// 접근 권한에 따라 카드 비활성화
|
|
||||||
const access = user.system_access || {};
|
|
||||||
if (access.system1 === false) document.getElementById('card-s1').classList.add('no-access');
|
|
||||||
if (access.system2 === false) document.getElementById('card-s2').classList.add('no-access');
|
|
||||||
if (access.system3 === false) document.getElementById('card-s3').classList.add('no-access');
|
|
||||||
}
|
|
||||||
|
|
||||||
function logout() {
|
|
||||||
ssoCookie.remove('sso_token');
|
|
||||||
ssoCookie.remove('sso_user');
|
|
||||||
ssoCookie.remove('sso_refresh_token');
|
|
||||||
['sso_token','sso_user','sso_refresh_token','token','user','access_token','currentUser','current_user','userInfo','userPageAccess'].forEach(function(k) {
|
|
||||||
localStorage.removeItem(k);
|
|
||||||
});
|
|
||||||
fetch('/auth/logout', { method: 'POST' }).catch(function(){});
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 서브도메인 링크 설정
|
|
||||||
function setupSystemLinks() {
|
|
||||||
var hostname = window.location.hostname;
|
|
||||||
var protocol = window.location.protocol;
|
|
||||||
var s2Link, s3Link;
|
|
||||||
|
|
||||||
if (hostname.includes('technicalkorea.net')) {
|
|
||||||
s2Link = protocol + '//tkreport.technicalkorea.net';
|
|
||||||
s3Link = protocol + '//tkqc.technicalkorea.net';
|
|
||||||
} else {
|
|
||||||
// 개발 환경: 포트 기반
|
|
||||||
s2Link = protocol + '//' + hostname + ':30180';
|
|
||||||
s3Link = protocol + '//' + hostname + ':30280';
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('card-s2-link').href = s2Link;
|
|
||||||
document.getElementById('card-s3-link').href = s3Link;
|
|
||||||
}
|
|
||||||
setupSystemLinks();
|
|
||||||
|
|
||||||
init();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -2,29 +2,32 @@ server {
|
|||||||
listen 80;
|
listen 80;
|
||||||
server_name _;
|
server_name _;
|
||||||
|
|
||||||
client_max_body_size 50M;
|
|
||||||
|
|
||||||
# ===== Gateway 자체 페이지 (포털, 로그인) =====
|
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
|
|
||||||
# 로그인 페이지
|
# 대시보드 (로그인 포함)
|
||||||
location = /login {
|
location = /dashboard {
|
||||||
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
||||||
add_header Pragma "no-cache";
|
add_header Pragma "no-cache";
|
||||||
try_files /login.html =404;
|
try_files /dashboard.html =404;
|
||||||
}
|
}
|
||||||
|
|
||||||
# 대시보드 → tkds로 리다이렉트 (북마크 깨짐 방지)
|
location = / {
|
||||||
location = /dashboard {
|
return 302 /dashboard$is_args$args;
|
||||||
return 301 $scheme://tkds.technicalkorea.net/dashboard;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# 공유 JS/CSS (nav-header 등)
|
# 레거시 /login → /dashboard 리다이렉트
|
||||||
|
location = /login {
|
||||||
|
return 302 /dashboard$is_args$args;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 공유 JS (notification-bell.js, nav-header.js)
|
||||||
location /shared/ {
|
location /shared/ {
|
||||||
alias /usr/share/nginx/html/shared/;
|
alias /usr/share/nginx/html/shared/;
|
||||||
|
expires 1h;
|
||||||
|
add_header Cache-Control "public, no-transform";
|
||||||
}
|
}
|
||||||
|
|
||||||
# ===== SSO Auth API =====
|
# SSO Auth 프록시
|
||||||
location /auth/ {
|
location /auth/ {
|
||||||
proxy_pass http://sso-auth:3000/api/auth/;
|
proxy_pass http://sso-auth:3000/api/auth/;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
@@ -33,7 +36,7 @@ server {
|
|||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
# ===== System 1 API 프록시 =====
|
# System1 API 프록시 (대시보드 배너용: 알림/TBM/휴가 카운트)
|
||||||
location /api/ {
|
location /api/ {
|
||||||
proxy_pass http://system1-api:3005/api/;
|
proxy_pass http://system1-api:3005/api/;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
@@ -43,50 +46,9 @@ server {
|
|||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
# ===== System 1 업로드 파일 =====
|
# Health check
|
||||||
location /uploads/ {
|
|
||||||
proxy_pass http://system1-api:3005/uploads/;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
# ===== System 1 FastAPI Bridge =====
|
|
||||||
location /fastapi/ {
|
|
||||||
proxy_pass http://system1-fastapi:8000/;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
|
||||||
|
|
||||||
# ===== AI Service API (맥미니 home-service-proxy 경유) =====
|
|
||||||
location /ai-api/ {
|
|
||||||
resolver 8.8.8.8 valid=300s ipv6=off;
|
|
||||||
set $ai_upstream https://ai.hyungi.net;
|
|
||||||
rewrite ^/ai-api/(.*) /api/ai/$1 break;
|
|
||||||
proxy_pass $ai_upstream;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Host ai.hyungi.net;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_ssl_server_name on;
|
|
||||||
proxy_read_timeout 180s;
|
|
||||||
proxy_send_timeout 180s;
|
|
||||||
}
|
|
||||||
|
|
||||||
# ===== System 1 Web (나머지 모든 경로) =====
|
|
||||||
location / {
|
|
||||||
proxy_pass http://system1-web:80;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
|
||||||
|
|
||||||
# ===== Health Check =====
|
|
||||||
location /health {
|
location /health {
|
||||||
|
access_log off;
|
||||||
return 200 '{"status":"ok","service":"gateway"}';
|
return 200 '{"status":"ok","service":"gateway"}';
|
||||||
add_header Content-Type application/json;
|
add_header Content-Type application/json;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,12 +13,14 @@ const logger = require('../utils/logger');
|
|||||||
* 허용된 Origin 목록
|
* 허용된 Origin 목록
|
||||||
*/
|
*/
|
||||||
const allowedOrigins = [
|
const allowedOrigins = [
|
||||||
'https://tkfb.technicalkorea.net', // Gateway (프로덕션)
|
'https://tkfb.technicalkorea.net', // System 1 (공장관리)
|
||||||
|
'https://tkds.technicalkorea.net', // Gateway/Dashboard
|
||||||
'https://tkreport.technicalkorea.net', // System 2
|
'https://tkreport.technicalkorea.net', // System 2
|
||||||
'https://tkqc.technicalkorea.net', // System 3
|
'https://tkqc.technicalkorea.net', // System 3
|
||||||
'https://tkuser.technicalkorea.net', // User Management
|
'https://tkuser.technicalkorea.net', // User Management
|
||||||
'https://tkpurchase.technicalkorea.net', // Purchase Management
|
'https://tkpurchase.technicalkorea.net', // Purchase Management
|
||||||
'https://tksafety.technicalkorea.net', // Safety Management
|
'https://tksafety.technicalkorea.net', // Safety Management
|
||||||
|
'https://tksupport.technicalkorea.net', // Support Management
|
||||||
'http://localhost:20000', // 웹 UI (로컬)
|
'http://localhost:20000', // 웹 UI (로컬)
|
||||||
'http://localhost:30080', // 웹 UI (Docker)
|
'http://localhost:30080', // 웹 UI (Docker)
|
||||||
'http://localhost:3005', // API 서버
|
'http://localhost:3005', // API 서버
|
||||||
|
|||||||
@@ -46,6 +46,46 @@ server {
|
|||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# SSO Auth 프록시 (gateway에서 이관)
|
||||||
|
location /auth/ {
|
||||||
|
proxy_pass http://sso-auth:3000/api/auth/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
# AI Service 프록시 (gateway에서 이관)
|
||||||
|
location /ai-api/ {
|
||||||
|
resolver 8.8.8.8 valid=300s ipv6=off;
|
||||||
|
set $ai_upstream https://ai.hyungi.net;
|
||||||
|
rewrite ^/ai-api/(.*) /api/ai/$1 break;
|
||||||
|
proxy_pass $ai_upstream;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host ai.hyungi.net;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_ssl_server_name on;
|
||||||
|
proxy_read_timeout 180s;
|
||||||
|
proxy_send_timeout 180s;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 레거시 /login, /dashboard → gateway(tkds) 리다이렉트
|
||||||
|
location = /login {
|
||||||
|
return 302 $scheme://tkds.technicalkorea.net/dashboard$is_args$args;
|
||||||
|
}
|
||||||
|
location = /dashboard {
|
||||||
|
return 301 $scheme://tkds.technicalkorea.net/dashboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
location /health {
|
||||||
|
access_log off;
|
||||||
|
return 200 '{"status":"ok","service":"system1-web"}';
|
||||||
|
add_header Content-Type application/json;
|
||||||
|
}
|
||||||
|
|
||||||
# Static files (new Tailwind UI)
|
# Static files (new Tailwind UI)
|
||||||
location /static/ {
|
location /static/ {
|
||||||
expires 1h;
|
expires 1h;
|
||||||
|
|||||||
@@ -288,6 +288,6 @@ async function initAuth() {
|
|||||||
/* ===== 알림 벨 ===== */
|
/* ===== 알림 벨 ===== */
|
||||||
function _loadNotificationBell() {
|
function _loadNotificationBell() {
|
||||||
const s = document.createElement('script');
|
const s = document.createElement('script');
|
||||||
s.src = (location.hostname.includes('technicalkorea.net') ? 'https://tkfb.technicalkorea.net' : location.protocol + '//' + location.hostname + ':30000') + '/shared/notification-bell.js?v=2';
|
s.src = (location.hostname.includes('technicalkorea.net') ? 'https://tkds.technicalkorea.net' : location.protocol + '//' + location.hostname + ':30000') + '/shared/notification-bell.js?v=3';
|
||||||
document.head.appendChild(s);
|
document.head.appendChild(s);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ if ('serviceWorker' in navigator) {
|
|||||||
window._loadNotificationBell = function() {
|
window._loadNotificationBell = function() {
|
||||||
var h = window.location.hostname;
|
var h = window.location.hostname;
|
||||||
var s = document.createElement('script');
|
var s = document.createElement('script');
|
||||||
s.src = (h.includes('technicalkorea.net') ? 'https://tkfb.technicalkorea.net' : window.location.protocol + '//' + h + ':30000') + '/shared/notification-bell.js?v=2';
|
s.src = (h.includes('technicalkorea.net') ? 'https://tkds.technicalkorea.net' : window.location.protocol + '//' + h + ':30000') + '/shared/notification-bell.js?v=3';
|
||||||
document.head.appendChild(s);
|
document.head.appendChild(s);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ function _getLoginUrl() {
|
|||||||
const hostname = window.location.hostname;
|
const hostname = window.location.hostname;
|
||||||
const t = Date.now();
|
const t = Date.now();
|
||||||
if (hostname.includes('technicalkorea.net')) {
|
if (hostname.includes('technicalkorea.net')) {
|
||||||
return window.location.protocol + '//tkfb.technicalkorea.net/login?redirect=' + encodeURIComponent(window.location.href) + '&_t=' + t;
|
return window.location.protocol + '//tkds.technicalkorea.net/dashboard?redirect=' + encodeURIComponent(window.location.href) + '&_t=' + t;
|
||||||
}
|
}
|
||||||
return window.location.protocol + '//' + hostname + ':30000/login?redirect=' + encodeURIComponent(window.location.href) + '&_t=' + t;
|
return window.location.protocol + '//' + hostname + ':30000/dashboard?redirect=' + encodeURIComponent(window.location.href) + '&_t=' + t;
|
||||||
}
|
}
|
||||||
|
|
||||||
// API 기본 설정 (통합 환경 지원)
|
// API 기본 설정 (통합 환경 지원)
|
||||||
|
|||||||
@@ -389,9 +389,9 @@ class App {
|
|||||||
redirectToLogin() {
|
redirectToLogin() {
|
||||||
const hostname = window.location.hostname;
|
const hostname = window.location.hostname;
|
||||||
if (hostname.includes('technicalkorea.net')) {
|
if (hostname.includes('technicalkorea.net')) {
|
||||||
window.location.href = window.location.protocol + '//tkfb.technicalkorea.net/login?redirect=' + encodeURIComponent(window.location.href);
|
window.location.href = window.location.protocol + '//tkds.technicalkorea.net/dashboard?redirect=' + encodeURIComponent(window.location.href);
|
||||||
} else {
|
} else {
|
||||||
window.location.href = window.location.protocol + '//' + hostname + ':30000/login?redirect=' + encodeURIComponent(window.location.href);
|
window.location.href = window.location.protocol + '//' + hostname + ':30000/dashboard?redirect=' + encodeURIComponent(window.location.href);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,7 +401,7 @@ class App {
|
|||||||
_loadNotificationBell() {
|
_loadNotificationBell() {
|
||||||
var h = window.location.hostname;
|
var h = window.location.hostname;
|
||||||
var s = document.createElement('script');
|
var s = document.createElement('script');
|
||||||
s.src = (h.includes('technicalkorea.net') ? 'https://tkfb.technicalkorea.net' : window.location.protocol + '//' + h + ':30000') + '/shared/notification-bell.js?v=2';
|
s.src = (h.includes('technicalkorea.net') ? 'https://tkds.technicalkorea.net' : window.location.protocol + '//' + h + ':30000') + '/shared/notification-bell.js?v=3';
|
||||||
document.head.appendChild(s);
|
document.head.appendChild(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -668,9 +668,9 @@ class CommonHeader {
|
|||||||
localStorage.removeItem('sso_user');
|
localStorage.removeItem('sso_user');
|
||||||
var hostname = window.location.hostname;
|
var hostname = window.location.hostname;
|
||||||
if (hostname.includes('technicalkorea.net')) {
|
if (hostname.includes('technicalkorea.net')) {
|
||||||
window.location.href = window.location.protocol + '//tkfb.technicalkorea.net/login';
|
window.location.href = window.location.protocol + '//tkds.technicalkorea.net/dashboard';
|
||||||
} else {
|
} else {
|
||||||
window.location.href = window.location.protocol + '//' + hostname + ':30000/login';
|
window.location.href = window.location.protocol + '//' + hostname + ':30000/dashboard';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
FROM nginx:alpine
|
|
||||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
|
||||||
COPY dashboard.html /usr/share/nginx/html/dashboard.html
|
|
||||||
EXPOSE 80
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name _;
|
|
||||||
|
|
||||||
location = /dashboard {
|
|
||||||
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
|
||||||
add_header Pragma "no-cache";
|
|
||||||
root /usr/share/nginx/html;
|
|
||||||
try_files /dashboard.html =404;
|
|
||||||
}
|
|
||||||
|
|
||||||
location = / {
|
|
||||||
return 302 /dashboard$is_args$args;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /auth/ {
|
|
||||||
proxy_pass http://sso-auth:3000/api/auth/;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /api/ {
|
|
||||||
proxy_pass http://system1-api:3005/api/;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /health {
|
|
||||||
access_log off;
|
|
||||||
return 200 '{"status":"ok","service":"tkds"}';
|
|
||||||
add_header Content-Type application/json;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -156,6 +156,6 @@ function initAuth() {
|
|||||||
/* ===== 알림 벨 ===== */
|
/* ===== 알림 벨 ===== */
|
||||||
function _loadNotificationBell() {
|
function _loadNotificationBell() {
|
||||||
const s = document.createElement('script');
|
const s = document.createElement('script');
|
||||||
s.src = (location.hostname.includes('technicalkorea.net') ? 'https://tkfb.technicalkorea.net' : location.protocol + '//' + location.hostname + ':30000') + '/shared/notification-bell.js?v=2';
|
s.src = (location.hostname.includes('technicalkorea.net') ? 'https://tkds.technicalkorea.net' : location.protocol + '//' + location.hostname + ':30000') + '/shared/notification-bell.js?v=3';
|
||||||
document.head.appendChild(s);
|
document.head.appendChild(s);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,6 +147,6 @@ function initAuth() {
|
|||||||
/* ===== 알림 벨 ===== */
|
/* ===== 알림 벨 ===== */
|
||||||
function _loadNotificationBell() {
|
function _loadNotificationBell() {
|
||||||
const s = document.createElement('script');
|
const s = document.createElement('script');
|
||||||
s.src = (location.hostname.includes('technicalkorea.net') ? 'https://tkfb.technicalkorea.net' : location.protocol + '//' + location.hostname + ':30000') + '/shared/notification-bell.js?v=2';
|
s.src = (location.hostname.includes('technicalkorea.net') ? 'https://tkds.technicalkorea.net' : location.protocol + '//' + location.hostname + ':30000') + '/shared/notification-bell.js?v=3';
|
||||||
document.head.appendChild(s);
|
document.head.appendChild(s);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,6 +140,6 @@ function initAuth() {
|
|||||||
/* ===== 알림 벨 ===== */
|
/* ===== 알림 벨 ===== */
|
||||||
function _loadNotificationBell() {
|
function _loadNotificationBell() {
|
||||||
const s = document.createElement('script');
|
const s = document.createElement('script');
|
||||||
s.src = (location.hostname.includes('technicalkorea.net') ? 'https://tkfb.technicalkorea.net' : location.protocol + '//' + location.hostname + ':30000') + '/shared/notification-bell.js?v=2';
|
s.src = (location.hostname.includes('technicalkorea.net') ? 'https://tkds.technicalkorea.net' : location.protocol + '//' + location.hostname + ':30000') + '/shared/notification-bell.js?v=3';
|
||||||
document.head.appendChild(s);
|
document.head.appendChild(s);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -192,6 +192,6 @@ async function init() {
|
|||||||
/* ===== 알림 벨 ===== */
|
/* ===== 알림 벨 ===== */
|
||||||
function _loadNotificationBell() {
|
function _loadNotificationBell() {
|
||||||
const s = document.createElement('script');
|
const s = document.createElement('script');
|
||||||
s.src = (location.hostname.includes('technicalkorea.net') ? 'https://tkfb.technicalkorea.net' : location.protocol + '//' + location.hostname + ':30000') + '/shared/notification-bell.js?v=2';
|
s.src = (location.hostname.includes('technicalkorea.net') ? 'https://tkds.technicalkorea.net' : location.protocol + '//' + location.hostname + ':30000') + '/shared/notification-bell.js?v=3';
|
||||||
document.head.appendChild(s);
|
document.head.appendChild(s);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user