ERP-node/docs/batch.html

585 lines
21 KiB
HTML
Raw Normal View History

2025-09-24 10:46:55 +09:00
<!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', Arial, sans-serif;
margin: 20px;
background-color: #f8f9fa;
color: #333;
line-height: 1.6;
}
.main-container {
background: white;
border-radius: 8px;
max-width: 1400px;
margin: 0 auto;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
text-align: center;
font-size: 24px;
font-weight: bold;
}
.input-section {
padding: 20px;
background-color: #f8f9fa;
border-bottom: 2px solid #e9ecef;
}
.input-group {
margin-bottom: 15px;
}
.input-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #495057;
}
.input-group input, .input-group textarea {
width: 100%;
padding: 10px;
border: 1px solid #ced4da;
border-radius: 4px;
font-size: 14px;
}
.input-group textarea {
height: 60px;
resize: vertical;
}
.mapping-container {
display: flex;
padding: 20px;
gap: 20px;
min-height: 500px;
}
.db-section {
flex: 1;
border: 2px solid #dee2e6;
border-radius: 8px;
background: white;
}
.db-header {
background-color: #007bff;
color: white;
padding: 15px;
font-weight: bold;
text-align: center;
font-size: 18px;
}
.from-section .db-header {
background-color: #28a745;
}
.to-section .db-header {
background-color: #dc3545;
}
.selection-area {
padding: 20px;
}
.select-group {
margin-bottom: 20px;
}
.select-group label {
display: block;
margin-bottom: 8px;
font-weight: bold;
color: #495057;
}
.select-group select {
width: 100%;
padding: 10px;
border: 1px solid #ced4da;
border-radius: 4px;
font-size: 14px;
background-color: white;
}
.columns-area {
margin-top: 20px;
min-height: 200px;
}
.table-info {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 15px;
margin-bottom: 15px;
}
.table-name {
font-weight: bold;
color: #007bff;
margin-bottom: 10px;
font-size: 16px;
}
.column-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.column-item {
padding: 10px 15px;
background-color: white;
border: 2px solid #dee2e6;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
}
.column-item:hover {
border-color: #007bff;
box-shadow: 0 2px 4px rgba(0,123,255,0.2);
}
.column-item.selected {
border-color: #007bff;
background-color: #e3f2fd;
font-weight: bold;
}
.column-item.mapped {
border-color: #28a745;
background-color: #d4edda;
}
.column-type {
font-size: 12px;
color: #6c757d;
font-style: italic;
}
.mapping-display {
margin-top: 20px;
padding: 15px;
background-color: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 4px;
}
.mapping-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid #dee2e6;
}
.mapping-item:last-child {
border-bottom: none;
}
.mapping-arrow {
color: #007bff;
font-weight: bold;
margin: 0 10px;
}
.remove-mapping {
background-color: #dc3545;
color: white;
border: none;
border-radius: 3px;
padding: 4px 8px;
cursor: pointer;
font-size: 12px;
}
.save-button {
width: 100%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 15px;
font-size: 16px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
}
.save-button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
.instruction {
background-color: #d1ecf1;
border: 1px solid #bee5eb;
border-radius: 4px;
padding: 10px;
margin-bottom: 15px;
font-size: 14px;
color: #0c5460;
}
</style>
</head>
<body>
<div class="main-container">
<div class="header">
배치관리 매핑 시스템
</div>
<div class="input-section">
<div class="input-group">
<label for="cronSchedule">실행주기 (크론탭 형식)</label>
<input type="text" id="cronSchedule" placeholder="예: 0 12 * * * (매일 12시)" value="1 11 3 * *">
</div>
<div class="input-group">
<label for="description">비고</label>
<textarea id="description" placeholder="하루한번 12시에 실행하는 인사정보 배치 등등...">하루한번 12시에 실행하는 인사정보 배치</textarea>
</div>
</div>
<div class="mapping-container">
<div class="db-section from-section">
<div class="db-header">FROM (원본 데이터베이스)</div>
<div class="selection-area">
<div class="instruction">
1단계: 컨넥션을 선택하세요 → 2단계: 테이블을 선택하세요 → 3단계: 컬럼을 클릭해서 매핑하세요
</div>
<div class="select-group">
<label for="fromConnection">컨넥션 선택</label>
<select id="fromConnection">
<option value="">컨넥션을 선택하세요</option>
<option value="oracle_db">Oracle_DB</option>
<option value="mes_db">MES_DB</option>
<option value="plm_db">PLM_DB</option>
<option value="erp_db">ERP_DB</option>
</select>
</div>
<div class="select-group">
<label for="fromTable">테이블 선택</label>
<select id="fromTable" disabled>
<option value="">먼저 컨넥션을 선택하세요</option>
</select>
</div>
<div class="columns-area" id="fromColumns">
<!-- 동적으로 컬럼들이 표시될 영역 -->
</div>
</div>
</div>
<div class="db-section to-section">
<div class="db-header">TO (대상 데이터베이스)</div>
<div class="selection-area">
<div class="instruction">
FROM에서 컬럼을 선택한 후, 여기서 대상 컬럼을 클릭하면 매핑됩니다
</div>
<div class="select-group">
<label for="toConnection">컨넥션 선택</label>
<select id="toConnection">
<option value="">컨넥션을 선택하세요</option>
<option value="oracle_db">Oracle_DB</option>
<option value="mes_db">MES_DB</option>
<option value="plm_db">PLM_DB</option>
<option value="erp_db">ERP_DB</option>
</select>
</div>
<div class="select-group">
<label for="toTable">테이블 선택</label>
<select id="toTable" disabled>
<option value="">먼저 컨넥션을 선택하세요</option>
</select>
</div>
<div class="columns-area" id="toColumns">
<!-- 동적으로 컬럼들이 표시될 영역 -->
</div>
</div>
</div>
</div>
<div class="mapping-display" id="mappingDisplay" style="margin: 20px; display: none;">
<h4>컬럼 매핑 현황</h4>
<div id="mappingList">
<!-- 매핑된 컬럼들이 표시될 영역 -->
</div>
</div>
<button class="save-button" onclick="saveMapping()">
배치 매핑 저장
</button>
</div>
<script>
// 샘플 데이터 - 실제로는 서버에서 가져올 데이터
const sampleData = {
oracle_db: {
employee: [
{name: 'user_id', type: 'VARCHAR2(20)'},
{name: 'user_name', type: 'VARCHAR2(100)'},
{name: 'department', type: 'VARCHAR2(50)'},
{name: 'email', type: 'VARCHAR2(200)'},
{name: 'created_date', type: 'DATE'}
],
department: [
{name: 'dept_id', type: 'VARCHAR2(10)'},
{name: 'dept_name', type: 'VARCHAR2(100)'},
{name: 'manager_id', type: 'VARCHAR2(20)'}
]
},
mes_db: {
user_info: [
{name: 'user_id', type: 'VARCHAR(20)'},
{name: 'user_name', type: 'VARCHAR(100)'},
{name: 'position', type: 'VARCHAR(50)'},
{name: 'phone', type: 'VARCHAR(20)'},
{name: 'hire_date', type: 'DATETIME'}
],
project: [
{name: 'project_id', type: 'VARCHAR(20)'},
{name: 'project_name', type: 'VARCHAR(200)'},
{name: 'start_date', type: 'DATETIME'},
{name: 'end_date', type: 'DATETIME'}
]
},
plm_db: {
product: [
{name: 'product_id', type: 'VARCHAR(30)'},
{name: 'product_name', type: 'VARCHAR(200)'},
{name: 'category', type: 'VARCHAR(50)'},
{name: 'price', type: 'DECIMAL(10,2)'}
]
},
erp_db: {
customer: [
{name: 'customer_id', type: 'VARCHAR(20)'},
{name: 'customer_name', type: 'VARCHAR(200)'},
{name: 'address', type: 'TEXT'},
{name: 'contact', type: 'VARCHAR(100)'}
]
}
};
let selectedFromColumn = null;
let mappings = [];
// 컨넥션 선택 이벤트 처리
document.getElementById('fromConnection').addEventListener('change', function() {
loadTables('from', this.value);
});
document.getElementById('toConnection').addEventListener('change', function() {
loadTables('to', this.value);
});
// 테이블 선택 이벤트 처리
document.getElementById('fromTable').addEventListener('change', function() {
loadColumns('from', document.getElementById('fromConnection').value, this.value);
});
document.getElementById('toTable').addEventListener('change', function() {
loadColumns('to', document.getElementById('toConnection').value, this.value);
});
// 테이블 목록 로드
function loadTables(side, connectionValue) {
const tableSelect = document.getElementById(side + 'Table');
tableSelect.innerHTML = '<option value="">테이블을 선택하세요</option>';
tableSelect.disabled = false;
if (connectionValue && sampleData[connectionValue]) {
Object.keys(sampleData[connectionValue]).forEach(tableName => {
const option = document.createElement('option');
option.value = tableName;
option.textContent = tableName.toUpperCase();
tableSelect.appendChild(option);
});
}
// 컬럼 영역 초기화
document.getElementById(side + 'Columns').innerHTML = '';
}
// 컬럼 목록 로드
function loadColumns(side, connectionValue, tableName) {
const columnsArea = document.getElementById(side + 'Columns');
if (!connectionValue || !tableName || !sampleData[connectionValue] || !sampleData[connectionValue][tableName]) {
columnsArea.innerHTML = '';
return;
}
const columns = sampleData[connectionValue][tableName];
columnsArea.innerHTML = `
<div class="table-info">
<div class="table-name">${tableName.toUpperCase()} 테이블</div>
<div class="column-list">
${columns.map(col => `
<div class="column-item" onclick="handleColumnClick('${side}', '${connectionValue}', '${tableName}', '${col.name}', '${col.type}')">
<div>${col.name}</div>
<div class="column-type">${col.type}</div>
</div>
`).join('')}
</div>
</div>
`;
}
// 컬럼 클릭 처리
function handleColumnClick(side, connection, table, columnName, columnType) {
if (side === 'from') {
// FROM 컬럼 선택
document.querySelectorAll('#fromColumns .column-item').forEach(item => {
item.classList.remove('selected');
});
event.target.closest('.column-item').classList.add('selected');
selectedFromColumn = {
side: 'from',
connection: connection,
table: table,
column: columnName,
type: columnType
};
} else if (side === 'to' && selectedFromColumn) {
// TO 컬럼 선택하여 매핑 생성
const mapping = {
from: selectedFromColumn,
to: {
side: 'to',
connection: connection,
table: table,
column: columnName,
type: columnType
}
};
// 중복 매핑 체크
const existingMapping = mappings.find(m =>
m.from.column === mapping.from.column &&
m.to.column === mapping.to.column
);
if (!existingMapping) {
mappings.push(mapping);
updateMappingDisplay();
updateColumnStyles();
}
// FROM 선택 해제
document.querySelectorAll('#fromColumns .column-item').forEach(item => {
item.classList.remove('selected');
});
selectedFromColumn = null;
}
}
// 매핑 표시 업데이트
function updateMappingDisplay() {
const mappingDisplay = document.getElementById('mappingDisplay');
const mappingList = document.getElementById('mappingList');
if (mappings.length === 0) {
mappingDisplay.style.display = 'none';
return;
}
mappingDisplay.style.display = 'block';
mappingList.innerHTML = mappings.map((mapping, index) => `
<div class="mapping-item">
<span>${mapping.from.table}.${mapping.from.column} (${mapping.from.type})</span>
<span class="mapping-arrow"></span>
<span>${mapping.to.table}.${mapping.to.column} (${mapping.to.type})</span>
<button class="remove-mapping" onclick="removeMapping(${index})">삭제</button>
</div>
`).join('');
}
// 컬럼 스타일 업데이트
function updateColumnStyles() {
// 모든 컬럼 아이템에서 mapped 클래스 제거
document.querySelectorAll('.column-item').forEach(item => {
item.classList.remove('mapped');
});
// 매핑된 컬럼들에 스타일 적용
mappings.forEach(mapping => {
const fromColumns = document.querySelectorAll('#fromColumns .column-item');
const toColumns = document.querySelectorAll('#toColumns .column-item');
fromColumns.forEach(item => {
if (item.textContent.includes(mapping.from.column)) {
item.classList.add('mapped');
}
});
toColumns.forEach(item => {
if (item.textContent.includes(mapping.to.column)) {
item.classList.add('mapped');
}
});
});
}
// 매핑 삭제
function removeMapping(index) {
mappings.splice(index, 1);
updateMappingDisplay();
updateColumnStyles();
}
// 매핑 저장
function saveMapping() {
const cronSchedule = document.getElementById('cronSchedule').value;
const description = document.getElementById('description').value;
if (!cronSchedule) {
alert('실행주기를 입력해주세요.');
return;
}
if (mappings.length === 0) {
alert('최소 하나 이상의 컬럼 매핑을 설정해주세요.');
return;
}
const batchConfig = {
cronSchedule: cronSchedule,
description: description,
mappings: mappings,
createdAt: new Date().toISOString()
};
// 실제로는 서버로 전송
console.log('저장될 배치 설정:', batchConfig);
alert('배치 매핑이 성공적으로 저장되었습니다!\n\n' +
`실행주기: ${cronSchedule}\n` +
`매핑 개수: ${mappings.length}개\n` +
`설명: ${description}`);
}
</script>
</body>
</html>