배치관리 중간커밋
This commit is contained in:
585
docs/batch.html
Normal file
585
docs/batch.html
Normal file
@@ -0,0 +1,585 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user