585 lines
21 KiB
HTML
585 lines
21 KiB
HTML
<!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> |