security: 보안 강제 시스템 구축 + 하드코딩 비밀번호 제거
보안 감사 결과 CRITICAL 2건, HIGH 5건 발견 → 수정 완료 + 자동화 구축. [보안 수정] - issue-view.js: 하드코딩 비밀번호 → crypto.getRandomValues() 랜덤 생성 - pushSubscriptionController.js: ntfy 비밀번호 → process.env.NTFY_SUB_PASSWORD - DEPLOY-GUIDE.md/PROGRESS.md/migration SQL: 평문 비밀번호 → placeholder - docker-compose.yml/.env.example: NTFY_SUB_PASSWORD 환경변수 추가 [보안 강제 시스템 - 신규] - scripts/security-scan.sh: 8개 규칙 (CRITICAL 2, HIGH 4, MEDIUM 2) 3모드(staged/all/diff), severity, .securityignore, MEDIUM 임계값 - .githooks/pre-commit: 로컬 빠른 피드백 - .githooks/pre-receive-server.sh: Gitea 서버 최종 차단 bypass 거버넌스([SECURITY-BYPASS: 사유] + 사용자 제한 + 로그) - SECURITY-CHECKLIST.md: 10개 카테고리 자동/수동 구분 - docs/SECURITY-GUIDE.md: 운영자 가이드 (워크플로우, bypass, FAQ) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
624
system1-factory/web/public/js/tbm/api.js
Normal file
624
system1-factory/web/public/js/tbm/api.js
Normal file
@@ -0,0 +1,624 @@
|
||||
/**
|
||||
* TBM - API Client
|
||||
* TBM 관련 모든 API 호출을 관리
|
||||
*/
|
||||
|
||||
class TbmAPI {
|
||||
constructor() {
|
||||
this.state = window.TbmState;
|
||||
this.utils = window.TbmUtils;
|
||||
console.log('[TbmAPI] 초기화 완료');
|
||||
}
|
||||
|
||||
/**
|
||||
* 초기 데이터 로드 (작업자, 프로젝트, 안전 체크리스트, 공정, 작업, 작업장)
|
||||
*/
|
||||
async loadInitialData() {
|
||||
try {
|
||||
// 현재 로그인한 사용자 정보 가져오기
|
||||
const userInfo = JSON.parse(localStorage.getItem('sso_user') || '{}');
|
||||
this.state.currentUser = userInfo;
|
||||
|
||||
// 병렬로 데이터 로드
|
||||
await Promise.all([
|
||||
this.loadWorkers(),
|
||||
this.loadProjects(),
|
||||
this.loadSafetyChecks(),
|
||||
this.loadWorkTypes(),
|
||||
this.loadTasks(),
|
||||
this.loadWorkplaces(),
|
||||
this.loadWorkplaceCategories()
|
||||
]);
|
||||
|
||||
} catch (error) {
|
||||
console.error(' 초기 데이터 로드 오류:', error);
|
||||
window.showToast?.('데이터를 불러오는 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 작업자 목록 로드 (생산팀 소속만)
|
||||
*/
|
||||
async loadWorkers() {
|
||||
try {
|
||||
const response = await window.apiCall('/workers?limit=1000&department_id=1');
|
||||
if (response) {
|
||||
let workers = Array.isArray(response) ? response : (response.data || []);
|
||||
// 활성 상태인 작업자만 필터링
|
||||
workers = workers.filter(w => w.status === 'active' && w.employment_status === 'employed');
|
||||
this.state.allWorkers = workers;
|
||||
return workers;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(' 작업자 로딩 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 프로젝트 목록 로드 (활성 프로젝트만)
|
||||
*/
|
||||
async loadProjects() {
|
||||
try {
|
||||
const response = await window.apiCall('/projects?is_active=1');
|
||||
if (response) {
|
||||
const projects = Array.isArray(response) ? response : (response.data || []);
|
||||
this.state.allProjects = projects.filter(p =>
|
||||
p.is_active === 1 || p.is_active === true || p.is_active === '1'
|
||||
);
|
||||
return this.state.allProjects;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(' 프로젝트 로딩 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 안전 체크리스트 로드
|
||||
*/
|
||||
async loadSafetyChecks() {
|
||||
try {
|
||||
const response = await window.apiCall('/tbm/safety-checks');
|
||||
if (response && response.success) {
|
||||
this.state.allSafetyChecks = response.data;
|
||||
return this.state.allSafetyChecks;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(' 안전 체크리스트 로딩 오류:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 공정(Work Types) 목록 로드
|
||||
*/
|
||||
async loadWorkTypes() {
|
||||
try {
|
||||
const response = await window.apiCall('/daily-work-reports/work-types');
|
||||
if (response && response.success) {
|
||||
this.state.allWorkTypes = response.data || [];
|
||||
return this.state.allWorkTypes;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(' 공정 로딩 오류:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 작업(Tasks) 목록 로드
|
||||
*/
|
||||
async loadTasks() {
|
||||
try {
|
||||
const response = await window.apiCall('/tasks/active/list');
|
||||
if (response && response.success) {
|
||||
this.state.allTasks = response.data || [];
|
||||
return this.state.allTasks;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(' 작업 로딩 오류:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 작업장 목록 로드
|
||||
*/
|
||||
async loadWorkplaces() {
|
||||
try {
|
||||
const response = await window.apiCall('/workplaces?is_active=true');
|
||||
if (response && response.success) {
|
||||
this.state.allWorkplaces = response.data || [];
|
||||
return this.state.allWorkplaces;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(' 작업장 로딩 오류:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 작업장 카테고리 로드
|
||||
*/
|
||||
async loadWorkplaceCategories() {
|
||||
try {
|
||||
const response = await window.apiCall('/workplaces/categories/active/list');
|
||||
if (response && response.success) {
|
||||
this.state.allWorkplaceCategories = response.data || [];
|
||||
return this.state.allWorkplaceCategories;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(' 작업장 카테고리 로딩 오류:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 오늘의 TBM만 로드 (TBM 입력 탭용)
|
||||
*/
|
||||
async loadTodayOnlyTbm() {
|
||||
const today = this.utils.getTodayKST();
|
||||
|
||||
try {
|
||||
const response = await window.apiCall(`/tbm/sessions/date/${today}`);
|
||||
|
||||
if (response && response.success) {
|
||||
this.state.todaySessions = response.data || [];
|
||||
} else {
|
||||
this.state.todaySessions = [];
|
||||
}
|
||||
return this.state.todaySessions;
|
||||
} catch (error) {
|
||||
console.error(' 오늘 TBM 조회 오류:', error);
|
||||
window.showToast?.('오늘 TBM을 불러오는 중 오류가 발생했습니다.', 'error');
|
||||
this.state.todaySessions = [];
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 최근 TBM을 날짜별로 그룹화하여 로드
|
||||
*/
|
||||
async loadRecentTbmGroupedByDate() {
|
||||
try {
|
||||
const today = new Date();
|
||||
const dates = [];
|
||||
|
||||
// 최근 N일의 날짜 생성
|
||||
for (let i = 0; i < this.state.loadedDaysCount; i++) {
|
||||
const date = new Date(today);
|
||||
date.setDate(date.getDate() - i);
|
||||
const dateStr = date.toISOString().split('T')[0];
|
||||
dates.push(dateStr);
|
||||
}
|
||||
|
||||
// 각 날짜의 TBM 로드
|
||||
this.state.dateGroupedSessions = {};
|
||||
this.state.allLoadedSessions = [];
|
||||
|
||||
const promises = dates.map(date => window.apiCall(`/tbm/sessions/date/${date}`));
|
||||
const results = await Promise.all(promises);
|
||||
|
||||
results.forEach((response, index) => {
|
||||
const date = dates[index];
|
||||
if (response && response.success && response.data && response.data.length > 0) {
|
||||
const sessions = response.data;
|
||||
|
||||
if (sessions.length > 0) {
|
||||
this.state.dateGroupedSessions[date] = sessions;
|
||||
this.state.allLoadedSessions = this.state.allLoadedSessions.concat(sessions);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return this.state.dateGroupedSessions;
|
||||
|
||||
} catch (error) {
|
||||
console.error(' TBM 날짜별 로드 오류:', error);
|
||||
window.showToast?.('TBM을 불러오는 중 오류가 발생했습니다.', 'error');
|
||||
this.state.dateGroupedSessions = {};
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 날짜의 TBM 세션 목록 로드
|
||||
*/
|
||||
async loadTbmSessionsByDate(date) {
|
||||
try {
|
||||
const response = await window.apiCall(`/tbm/sessions/date/${date}`);
|
||||
|
||||
if (response && response.success) {
|
||||
this.state.allSessions = response.data || [];
|
||||
} else {
|
||||
this.state.allSessions = [];
|
||||
}
|
||||
return this.state.allSessions;
|
||||
} catch (error) {
|
||||
console.error(' TBM 세션 조회 오류:', error);
|
||||
window.showToast?.('TBM 세션을 불러오는 중 오류가 발생했습니다.', 'error');
|
||||
this.state.allSessions = [];
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TBM 세션 생성
|
||||
*/
|
||||
async createTbmSession(sessionData) {
|
||||
try {
|
||||
const response = await window.apiCall('/tbm/sessions', 'POST', sessionData);
|
||||
if (!response || !response.success) {
|
||||
throw new Error(response?.message || '세션 생성 실패');
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error(' TBM 세션 생성 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TBM 세션 정보 조회
|
||||
*/
|
||||
async getSession(sessionId) {
|
||||
try {
|
||||
const response = await window.apiCall(`/tbm/sessions/${sessionId}`);
|
||||
if (!response || !response.success) {
|
||||
throw new Error(response?.message || '세션 조회 실패');
|
||||
}
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(' TBM 세션 조회 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TBM 팀원 조회
|
||||
*/
|
||||
async getTeamMembers(sessionId) {
|
||||
try {
|
||||
const response = await window.apiCall(`/tbm/sessions/${sessionId}/team`);
|
||||
if (!response || !response.success) {
|
||||
throw new Error(response?.message || '팀원 조회 실패');
|
||||
}
|
||||
return response.data || [];
|
||||
} catch (error) {
|
||||
console.error(' TBM 팀원 조회 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TBM 팀원 일괄 추가
|
||||
*/
|
||||
async addTeamMembers(sessionId, members) {
|
||||
try {
|
||||
const response = await window.apiCall(
|
||||
`/tbm/sessions/${sessionId}/team/batch`,
|
||||
'POST',
|
||||
{ members }
|
||||
);
|
||||
if (!response || !response.success) {
|
||||
const err = new Error(response?.message || '팀원 추가 실패');
|
||||
if (response && response.duplicates) err.duplicates = response.duplicates;
|
||||
throw err;
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error(' TBM 팀원 추가 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TBM 팀원 전체 삭제
|
||||
*/
|
||||
async clearTeamMembers(sessionId) {
|
||||
try {
|
||||
const response = await window.apiCall(`/tbm/sessions/${sessionId}/team/clear`, 'DELETE');
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error(' TBM 팀원 삭제 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TBM 안전 체크 조회
|
||||
*/
|
||||
async getSafetyChecks(sessionId) {
|
||||
try {
|
||||
const response = await window.apiCall(`/tbm/sessions/${sessionId}/safety`);
|
||||
return response?.data || [];
|
||||
} catch (error) {
|
||||
console.error(' 안전 체크 조회 오류:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TBM 안전 체크 (필터링된) 조회
|
||||
*/
|
||||
async getFilteredSafetyChecks(sessionId) {
|
||||
try {
|
||||
const response = await window.apiCall(`/tbm/sessions/${sessionId}/safety-checks/filtered`);
|
||||
if (!response || !response.success) {
|
||||
throw new Error(response?.message || '체크리스트를 불러올 수 없습니다.');
|
||||
}
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(' 필터링된 안전 체크 조회 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TBM 안전 체크 저장
|
||||
*/
|
||||
async saveSafetyChecks(sessionId, records) {
|
||||
try {
|
||||
const response = await window.apiCall(
|
||||
`/tbm/sessions/${sessionId}/safety`,
|
||||
'POST',
|
||||
{ records }
|
||||
);
|
||||
if (!response || !response.success) {
|
||||
throw new Error(response?.message || '저장 실패');
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error(' 안전 체크 저장 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TBM 세션 완료 처리
|
||||
*/
|
||||
async completeTbmSession(sessionId, endTime) {
|
||||
try {
|
||||
const response = await window.apiCall(
|
||||
`/tbm/sessions/${sessionId}/complete`,
|
||||
'POST',
|
||||
{ end_time: endTime }
|
||||
);
|
||||
if (!response || !response.success) {
|
||||
throw new Error(response?.message || '완료 처리 실패');
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error(' TBM 완료 처리 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 작업 인계 저장
|
||||
*/
|
||||
async saveHandover(handoverData) {
|
||||
try {
|
||||
const response = await window.apiCall('/tbm/handovers', 'POST', handoverData);
|
||||
if (!response || !response.success) {
|
||||
throw new Error(response?.message || '인계 요청 실패');
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error(' 작업 인계 저장 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 카테고리별 작업장 로드
|
||||
*/
|
||||
async loadWorkplacesByCategory(categoryId) {
|
||||
try {
|
||||
const response = await window.apiCall(`/workplaces?category_id=${categoryId}`);
|
||||
if (!response || !response.success || !response.data) {
|
||||
return [];
|
||||
}
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(' 작업장 로드 오류:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 작업장 지도 영역 로드
|
||||
*/
|
||||
async loadMapRegions(categoryId) {
|
||||
try {
|
||||
const response = await window.apiCall(`/workplaces/categories/${categoryId}/map-regions`);
|
||||
if (response && response.success) {
|
||||
this.state.mapRegions = response.data || [];
|
||||
return this.state.mapRegions;
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error(' 지도 영역 로드 오류:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TBM 세션 삭제
|
||||
*/
|
||||
async deleteSession(sessionId) {
|
||||
try {
|
||||
const response = await window.apiCall(`/tbm/sessions/${sessionId}`, 'DELETE');
|
||||
if (!response || !response.success) {
|
||||
throw new Error(response?.message || '삭제 실패');
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error(' TBM 세션 삭제 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TBM 팀원 분할 배정
|
||||
*/
|
||||
async splitAssignment(sessionId, splitData) {
|
||||
try {
|
||||
const response = await window.apiCall(
|
||||
`/tbm/sessions/${sessionId}/team/split`,
|
||||
'POST',
|
||||
splitData
|
||||
);
|
||||
if (!response || !response.success) {
|
||||
throw new Error(response?.message || '분할 실패');
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error(' 분할 배정 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TBM 팀원 단일 추가/수정 (POST /team)
|
||||
*/
|
||||
async updateTeamMember(sessionId, memberData) {
|
||||
try {
|
||||
const response = await window.apiCall(
|
||||
`/tbm/sessions/${sessionId}/team`,
|
||||
'POST',
|
||||
memberData
|
||||
);
|
||||
if (!response || !response.success) {
|
||||
throw new Error(response?.message || '팀원 수정 실패');
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error(' 팀원 수정 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TBM 인원 이동 (분할→이동 / 빼오기)
|
||||
*/
|
||||
async transfer(transferData) {
|
||||
try {
|
||||
const response = await window.apiCall('/tbm/transfers', 'POST', transferData);
|
||||
if (!response || !response.success) {
|
||||
throw new Error(response?.message || '이동 실패');
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error(' TBM 이동 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 작업 생성
|
||||
*/
|
||||
async createTask(taskData) {
|
||||
try {
|
||||
const response = await window.apiCall('/tasks', 'POST', taskData);
|
||||
if (!response || !response.success) {
|
||||
throw new Error(response?.message || '작업 생성 실패');
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error(' 작업 생성 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 활성 작업장 전체 목록 (active/list)
|
||||
*/
|
||||
async loadActiveWorkplacesList() {
|
||||
try {
|
||||
const response = await window.apiCall('/workplaces/active/list');
|
||||
if (response && response.success) {
|
||||
return response.data || [];
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error(' 활성 작업장 목록 오류:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 당일 배정 현황 조회
|
||||
*/
|
||||
async loadTodayAssignments(date) {
|
||||
try {
|
||||
const response = await window.apiCall(`/tbm/sessions/date/${date}/assignments`);
|
||||
if (response && response.success) {
|
||||
return response.data || [];
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error(' 배정 현황 조회 오류:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 날짜의 TBM 세션 조회 (raw - 상태 변경 없음)
|
||||
*/
|
||||
async fetchSessionsByDate(date) {
|
||||
try {
|
||||
const response = await window.apiCall(`/tbm/sessions/date/${date}`);
|
||||
if (response && response.success) {
|
||||
return response.data || [];
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error(' TBM 세션 조회 오류:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TBM 세션 완료 처리 (근태 정보 포함)
|
||||
*/
|
||||
async completeTbmWithAttendance(sessionId, attendanceData) {
|
||||
try {
|
||||
const response = await window.apiCall(
|
||||
`/tbm/sessions/${sessionId}/complete`,
|
||||
'POST',
|
||||
{ attendance: attendanceData }
|
||||
);
|
||||
if (!response || !response.success) {
|
||||
throw new Error(response?.message || '완료 처리 실패');
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error(' TBM 완료 처리 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 전역 인스턴스 생성
|
||||
window.TbmAPI = new TbmAPI();
|
||||
|
||||
// 하위 호환성: 기존 함수들
|
||||
window.loadInitialData = () => window.TbmAPI.loadInitialData();
|
||||
window.loadTodayOnlyTbm = () => window.TbmAPI.loadTodayOnlyTbm();
|
||||
window.loadTodayTbm = () => window.TbmAPI.loadRecentTbmGroupedByDate();
|
||||
window.loadAllTbm = () => {
|
||||
window.TbmState.loadedDaysCount = 30;
|
||||
return window.TbmAPI.loadRecentTbmGroupedByDate();
|
||||
};
|
||||
window.loadRecentTbmGroupedByDate = () => window.TbmAPI.loadRecentTbmGroupedByDate();
|
||||
window.loadTbmSessionsByDate = (date) => window.TbmAPI.loadTbmSessionsByDate(date);
|
||||
window.loadWorkplaceCategories = () => window.TbmAPI.loadWorkplaceCategories();
|
||||
window.loadWorkplacesByCategory = (categoryId) => window.TbmAPI.loadWorkplacesByCategory(categoryId);
|
||||
|
||||
// 더 많은 날짜 로드
|
||||
window.loadMoreTbmDays = async function() {
|
||||
window.TbmState.loadedDaysCount += 7;
|
||||
await window.TbmAPI.loadRecentTbmGroupedByDate();
|
||||
window.showToast?.(`최근 ${window.TbmState.loadedDaysCount}일의 TBM을 로드했습니다.`, 'success');
|
||||
};
|
||||
window.deleteTbmSession = (sessionId) => window.TbmAPI.deleteSession(sessionId);
|
||||
window.fetchSessionsByDate = (date) => window.TbmAPI.fetchSessionsByDate(date);
|
||||
|
||||
console.log('[Module] tbm/api.js 로드 완료');
|
||||
Reference in New Issue
Block a user