feat: tkuser 통합 관리 서비스 + 전체 시스템 SSO 쿠키 인증 통합
- tkuser 서비스 신규 추가 (API + Web) - 사용자/권한/프로젝트/부서/작업자/작업장/설비/작업/휴가 통합 관리 - 작업장 탭: 공장→작업장 드릴다운 네비게이션 + 구역지도 클릭 연동 - 작업 탭: 공정(work_types)→작업(tasks) 계층 관리 - 휴가 탭: 유형 관리 + 연차 배정(근로기준법 자동계산) - 전 시스템 SSO 쿠키 인증으로 통합 (.technicalkorea.net 공유) - System 2: 작업 이슈 리포트 기능 강화 - System 3: tkuser API 연동, 페이지 권한 체계 적용 - docker-compose에 tkuser-api, tkuser-web 서비스 추가 - ARCHITECTURE.md, DEPLOYMENT.md 문서 작성 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,25 +6,29 @@ function getApiBaseUrl() {
|
||||
const hostname = window.location.hostname;
|
||||
const protocol = window.location.protocol;
|
||||
const port = window.location.port;
|
||||
|
||||
|
||||
console.log('🌐 감지된 환경:', { hostname, protocol, port });
|
||||
|
||||
// 🔗 nginx 프록시를 통한 접근 (권장)
|
||||
// nginx가 /api/ 요청을 백엔드로 프록시하므로 포트 없이 접근
|
||||
if (hostname.startsWith('192.168.') || hostname.startsWith('10.') || hostname.startsWith('172.') ||
|
||||
hostname === 'localhost' || hostname === '127.0.0.1' ||
|
||||
hostname.includes('.local') || hostname.includes('hyungi')) {
|
||||
|
||||
// 현재 웹서버의 도메인/IP를 그대로 사용하되 API 포트(config.api.port)로 직접 연결
|
||||
const baseUrl = `${protocol}//${hostname}:${config.api.port}${config.api.path}`;
|
||||
|
||||
console.log('✅ nginx 프록시 사용:', baseUrl);
|
||||
|
||||
// 🔗 외부 도메인 (Cloudflare Tunnel) - Gateway nginx가 /api/를 프록시
|
||||
if (hostname.includes('technicalkorea.net')) {
|
||||
const baseUrl = `${protocol}//${hostname}${config.api.path}`;
|
||||
console.log('✅ Gateway 프록시 사용:', baseUrl);
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
// 🚨 백업: 직접 접근 (nginx 프록시 실패시에만)
|
||||
console.warn('⚠️ 직접 API 접근 (백업 모드)');
|
||||
return `${protocol}//${hostname}:${config.api.port}${config.api.path}`;
|
||||
|
||||
// 🔗 로컬/내부 네트워크 - API 포트 직접 접근
|
||||
if (hostname.startsWith('192.168.') || hostname.startsWith('10.') || hostname.startsWith('172.') ||
|
||||
hostname === 'localhost' || hostname === '127.0.0.1' ||
|
||||
hostname.includes('.local') || hostname.includes('hyungi')) {
|
||||
const baseUrl = `${protocol}//${hostname}:${config.api.port}${config.api.path}`;
|
||||
console.log('✅ 로컬 직접 접근:', baseUrl);
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
// 🚨 기타: 포트 없이 상대 경로
|
||||
const baseUrl = `${protocol}//${hostname}${config.api.path}`;
|
||||
console.log('✅ 기본 프록시 사용:', baseUrl);
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
// API 설정
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// ES6 모듈 의존성 제거 - 브라우저 호환성 개선
|
||||
|
||||
// API 설정 (window 객체에서 가져오기)
|
||||
const API_BASE_URL = window.API_BASE_URL || 'http://localhost:20005/api';
|
||||
const API_BASE_URL = window.API_BASE_URL || 'http://localhost:30005/api';
|
||||
|
||||
// 인증 관련 함수들 (직접 구현)
|
||||
function getToken() {
|
||||
|
||||
@@ -7,7 +7,7 @@ export const config = {
|
||||
// API 관련 설정
|
||||
api: {
|
||||
// 로컬 개발 및 Docker 환경에서 사용하는 API 서버 포트
|
||||
port: 20005,
|
||||
port: 30005,
|
||||
// API의 기본 경로
|
||||
path: '/api',
|
||||
},
|
||||
|
||||
@@ -1115,7 +1115,7 @@ async function loadWorkplaceMap(categoryId, layoutImagePath, workplaces) {
|
||||
mapCtx = mapCanvas.getContext('2d');
|
||||
|
||||
// 이미지 URL 생성
|
||||
const baseUrl = window.API_BASE_URL || 'http://localhost:20005';
|
||||
const baseUrl = window.API_BASE_URL || 'http://localhost:30005';
|
||||
const apiBaseUrl = baseUrl.replace('/api', ''); // /api 제거
|
||||
const fullImageUrl = layoutImagePath.startsWith('http')
|
||||
? layoutImagePath
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* category_type=nonconformity 고정 필터
|
||||
*/
|
||||
|
||||
const API_BASE = window.API_BASE_URL || 'http://localhost:20005/api';
|
||||
const API_BASE = window.API_BASE_URL || 'http://localhost:30005/api';
|
||||
const CATEGORY_TYPE = 'nonconformity';
|
||||
|
||||
// 상태 한글 변환
|
||||
@@ -110,7 +110,7 @@ function renderIssues(issues) {
|
||||
return;
|
||||
}
|
||||
|
||||
const baseUrl = (window.API_BASE_URL || 'http://localhost:20005').replace('/api', '');
|
||||
const baseUrl = (window.API_BASE_URL || 'http://localhost:30005').replace('/api', '');
|
||||
|
||||
issueList.innerHTML = issues.map(issue => {
|
||||
const reportDate = new Date(issue.report_date).toLocaleString('ko-KR', {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* category_type=safety 고정 필터
|
||||
*/
|
||||
|
||||
const API_BASE = window.API_BASE_URL || 'http://localhost:20005/api';
|
||||
const API_BASE = window.API_BASE_URL || 'http://localhost:30005/api';
|
||||
const CATEGORY_TYPE = 'safety';
|
||||
|
||||
// 상태 한글 변환
|
||||
@@ -110,7 +110,7 @@ function renderIssues(issues) {
|
||||
return;
|
||||
}
|
||||
|
||||
const baseUrl = (window.API_BASE_URL || 'http://localhost:20005').replace('/api', '');
|
||||
const baseUrl = (window.API_BASE_URL || 'http://localhost:30005').replace('/api', '');
|
||||
|
||||
issueList.innerHTML = issues.map(issue => {
|
||||
const reportDate = new Date(issue.report_date).toLocaleString('ko-KR', {
|
||||
|
||||
@@ -1672,7 +1672,7 @@ async function loadWorkplaceMap(categoryId, layoutImagePath) {
|
||||
mapCtx = mapCanvas.getContext('2d');
|
||||
|
||||
// 이미지 URL 생성
|
||||
const baseUrl = window.API_BASE_URL || 'http://localhost:20005';
|
||||
const baseUrl = window.API_BASE_URL || 'http://localhost:30005';
|
||||
const apiBaseUrl = baseUrl.replace('/api', ''); // /api 제거
|
||||
const fullImageUrl = layoutImagePath.startsWith('http')
|
||||
? layoutImagePath
|
||||
|
||||
@@ -304,7 +304,7 @@ async function loadWorkplaceMap() {
|
||||
// 레이아웃 이미지가 있으면 표시
|
||||
if (selectedCategory && selectedCategory.layout_image) {
|
||||
// API_BASE_URL에서 /api 제거하고 이미지 경로 생성
|
||||
const baseUrl = (window.API_BASE_URL || 'http://localhost:20005').replace('/api', '');
|
||||
const baseUrl = (window.API_BASE_URL || 'http://localhost:30005').replace('/api', '');
|
||||
const fullImageUrl = selectedCategory.layout_image.startsWith('http')
|
||||
? selectedCategory.layout_image
|
||||
: `${baseUrl}${selectedCategory.layout_image}`;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
class WorkAnalysisAPIClient {
|
||||
constructor() {
|
||||
this.baseURL = window.API_BASE_URL || 'http://localhost:20005/api';
|
||||
this.baseURL = window.API_BASE_URL || 'http://localhost:30005/api';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -88,7 +88,7 @@ async function loadLayoutMapData() {
|
||||
// 이미지 경로를 전체 URL로 변환
|
||||
const fullImageUrl = category.layout_image.startsWith('http')
|
||||
? category.layout_image
|
||||
: `${window.API_BASE_URL || 'http://localhost:20005/api'}${category.layout_image}`.replace('/api/', '/');
|
||||
: `${window.API_BASE_URL || 'http://localhost:30005/api'}${category.layout_image}`.replace('/api/', '/');
|
||||
|
||||
currentImageDiv.innerHTML = `
|
||||
<img src="${fullImageUrl}" style="max-width: 100%; max-height: 300px; border-radius: 4px;" alt="현재 레이아웃 이미지">
|
||||
@@ -210,7 +210,7 @@ async function uploadLayoutImage() {
|
||||
formData.append('image', file);
|
||||
|
||||
// 업로드 요청
|
||||
const response = await fetch(`${window.API_BASE_URL || 'http://localhost:20005/api'}/workplaces/categories/${currentCategoryId}/layout-image`, {
|
||||
const response = await fetch(`${window.API_BASE_URL || 'http://localhost:30005/api'}/workplaces/categories/${currentCategoryId}/layout-image`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('sso_token')}`
|
||||
@@ -224,7 +224,7 @@ async function uploadLayoutImage() {
|
||||
window.showToast('이미지가 성공적으로 업로드되었습니다.', 'success');
|
||||
|
||||
// 이미지 경로를 전체 URL로 변환
|
||||
const fullImageUrl = `${window.API_BASE_URL || 'http://localhost:20005/api'}${result.data.image_path}`.replace('/api/', '/');
|
||||
const fullImageUrl = `${window.API_BASE_URL || 'http://localhost:30005/api'}${result.data.image_path}`.replace('/api/', '/');
|
||||
|
||||
// 이미지를 캔버스에 로드
|
||||
loadImageToCanvas(fullImageUrl);
|
||||
|
||||
@@ -147,7 +147,7 @@ async function updateLayoutPreview(category) {
|
||||
// 이미지 경로를 전체 URL로 변환
|
||||
const fullImageUrl = category.layout_image.startsWith('http')
|
||||
? category.layout_image
|
||||
: `${window.API_BASE_URL || 'http://localhost:20005/api'}${category.layout_image}`.replace('/api/', '/');
|
||||
: `${window.API_BASE_URL || 'http://localhost:30005/api'}${category.layout_image}`.replace('/api/', '/');
|
||||
|
||||
// Canvas 컨테이너 생성
|
||||
previewDiv.innerHTML = `
|
||||
@@ -267,7 +267,7 @@ async function loadWorkplaceMapThumbnail(workplace) {
|
||||
if (workplace.layout_image) {
|
||||
const fullImageUrl = workplace.layout_image.startsWith('http')
|
||||
? workplace.layout_image
|
||||
: `${window.API_BASE_URL || 'http://localhost:20005/api'}${workplace.layout_image}`.replace('/api/', '/');
|
||||
: `${window.API_BASE_URL || 'http://localhost:30005/api'}${workplace.layout_image}`.replace('/api/', '/');
|
||||
|
||||
// 설비 정보 로드
|
||||
let equipmentCount = 0;
|
||||
@@ -327,7 +327,7 @@ async function loadWorkplaceMapThumbnail(workplace) {
|
||||
|
||||
const fullImageUrl = category.layout_image.startsWith('http')
|
||||
? category.layout_image
|
||||
: `${window.API_BASE_URL || 'http://localhost:20005/api'}${category.layout_image}`.replace('/api/', '/');
|
||||
: `${window.API_BASE_URL || 'http://localhost:30005/api'}${category.layout_image}`.replace('/api/', '/');
|
||||
|
||||
// 캔버스 생성
|
||||
const canvasId = `thumbnail-canvas-${workplace.workplace_id}`;
|
||||
@@ -1019,7 +1019,7 @@ async function openWorkplaceMapModal(workplaceId) {
|
||||
if (preview && workplace.layout_image) {
|
||||
const fullImageUrl = workplace.layout_image.startsWith('http')
|
||||
? workplace.layout_image
|
||||
: `${window.API_BASE_URL || 'http://localhost:20005/api'}${workplace.layout_image}`.replace('/api/', '/');
|
||||
: `${window.API_BASE_URL || 'http://localhost:30005/api'}${workplace.layout_image}`.replace('/api/', '/');
|
||||
preview.innerHTML = `<img src="${fullImageUrl}" alt="작업장 레이아웃" style="max-width: 100%; border-radius: 4px;">`;
|
||||
|
||||
// 캔버스 초기화
|
||||
@@ -1126,7 +1126,7 @@ async function uploadWorkplaceLayout() {
|
||||
try {
|
||||
showToast('이미지 업로드 중...', 'info');
|
||||
|
||||
const response = await fetch(`${window.API_BASE_URL || 'http://localhost:20005/api'}/workplaces/${window.currentWorkplaceMapId}/layout-image`, {
|
||||
const response = await fetch(`${window.API_BASE_URL || 'http://localhost:30005/api'}/workplaces/${window.currentWorkplaceMapId}/layout-image`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('sso_token')}`
|
||||
@@ -1148,7 +1148,7 @@ async function uploadWorkplaceLayout() {
|
||||
if (preview && result.data.image_path) {
|
||||
const fullImageUrl = result.data.image_path.startsWith('http')
|
||||
? result.data.image_path
|
||||
: `${window.API_BASE_URL || 'http://localhost:20005/api'}${result.data.image_path}`.replace('/api/', '/');
|
||||
: `${window.API_BASE_URL || 'http://localhost:30005/api'}${result.data.image_path}`.replace('/api/', '/');
|
||||
preview.innerHTML = `<img src="${fullImageUrl}" alt="작업장 레이아웃" style="max-width: 100%; border-radius: 4px;">`;
|
||||
|
||||
// 캔버스 초기화 (설비 영역 편집용)
|
||||
@@ -1678,7 +1678,7 @@ async function openFullscreenEquipmentEditor() {
|
||||
// 이미지 URL 생성
|
||||
const fullImageUrl = workplace.layout_image.startsWith('http')
|
||||
? workplace.layout_image
|
||||
: `${window.API_BASE_URL || 'http://localhost:20005/api'}${workplace.layout_image}`.replace('/api/', '/');
|
||||
: `${window.API_BASE_URL || 'http://localhost:30005/api'}${workplace.layout_image}`.replace('/api/', '/');
|
||||
|
||||
// 캔버스 초기화
|
||||
initFullscreenCanvas(fullImageUrl);
|
||||
|
||||
@@ -25,7 +25,7 @@ class WorkplaceUtils {
|
||||
* API URL 생성
|
||||
*/
|
||||
getApiBaseUrl() {
|
||||
return window.API_BASE_URL || 'http://localhost:20005/api';
|
||||
return window.API_BASE_URL || 'http://localhost:30005/api';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -129,7 +129,7 @@ async function loadMapImage() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
|
||||
const baseUrl = (window.API_BASE_URL || 'http://localhost:20005').replace('/api', '');
|
||||
const baseUrl = (window.API_BASE_URL || 'http://localhost:30005').replace('/api', '');
|
||||
const fullImageUrl = selectedCategory.layout_image.startsWith('http')
|
||||
? selectedCategory.layout_image
|
||||
: `${baseUrl}${selectedCategory.layout_image}`;
|
||||
@@ -563,7 +563,7 @@ async function initDetailMap(workplace) {
|
||||
|
||||
// 작업장에 레이아웃 이미지가 있는지 확인
|
||||
if (workplace.layout_image) {
|
||||
const baseUrl = (window.API_BASE_URL || 'http://localhost:20005').replace('/api', '');
|
||||
const baseUrl = (window.API_BASE_URL || 'http://localhost:30005').replace('/api', '');
|
||||
const imageUrl = workplace.layout_image.startsWith('http')
|
||||
? workplace.layout_image
|
||||
: `${baseUrl}${workplace.layout_image}`;
|
||||
|
||||
Reference in New Issue
Block a user