972819b492
- ApexCharts 기반 실시간 모니터링 대시보드 - 어댑터/배치/서버/소켓/트랜잭션 모니터링 컴포넌트 - 처리량 계산 및 데이터 매핑 서비스
680 lines
21 KiB
Plaintext
680 lines
21 KiB
Plaintext
<%@ page language="java" contentType="text/html; charset=utf-8"%>
|
|
|
|
<!-- 인터페이스 로그 조회 모달 -->
|
|
<div id="interfaceLogDetailModal" class="modal modal-modern">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h2 class="modal-title">인터페이스 로그 조회</h2>
|
|
<span class="close-modal">×</span>
|
|
</div>
|
|
<div class="modal-body">
|
|
<!-- 인터페이스 정보 요약 -->
|
|
<div class="interface-info-summary">
|
|
<div class="info-card">
|
|
<div class="info-header">
|
|
<span class="info-label">인터페이스 ID</span>
|
|
<span class="info-value" id="detail-interface-id">-</span>
|
|
</div>
|
|
<div class="info-content">
|
|
<div class="info-row">
|
|
<span class="info-label">발생시간</span>
|
|
<span class="info-value" id="detail-error-time">-</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">인스턴스</span>
|
|
<span class="info-value" id="detail-instance-name">-</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="info-card">
|
|
<div class="info-header">
|
|
<span class="info-label">오류 메시지</span>
|
|
</div>
|
|
<div class="info-content">
|
|
<div class="error-message" id="detail-error-message">-</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 로그 리스트 -->
|
|
<div class="log-list-container">
|
|
<div class="log-list-header">
|
|
<h3>관련 거래 로그</h3>
|
|
<div class="log-search-controls">
|
|
<div class="time-range-selector">
|
|
<span>조회 시간:</span>
|
|
<select id="log-time-range">
|
|
<option value="1" selected>±1분</option>
|
|
<option value="5">±5분</option>
|
|
<option value="10">±10분</option>
|
|
<option value="30">±30분</option>
|
|
</select>
|
|
</div>
|
|
<button class="btn btn-primary refresh-logs-btn">
|
|
<i class="bi bi-arrow-clockwise"></i> 새로고침
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="log-table-container">
|
|
<table class="log-table" id="log-grid">
|
|
<thead>
|
|
<tr>
|
|
<th class="narrow-column">처리번호</th>
|
|
<th>거래키</th>
|
|
<th>업무구분</th>
|
|
<th>서비스명</th>
|
|
<th>수신시간</th>
|
|
<th>처리시간</th>
|
|
<th>상태</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="log-grid-body">
|
|
<!-- 로그 데이터가 동적으로 추가됩니다 -->
|
|
<tr>
|
|
<td colspan="7" class="empty-message">조회 버튼을 클릭하여 관련 로그를 검색하세요.</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary close-modal-btn">닫기</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
/* 모달 기본 스타일 */
|
|
.modal-modern {
|
|
display: none;
|
|
position: fixed;
|
|
z-index: 1000;
|
|
left: 0;
|
|
top: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
overflow: auto;
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
backdrop-filter: blur(4px);
|
|
}
|
|
|
|
.modal-modern .modal-content {
|
|
position: relative;
|
|
background-color: #fff;
|
|
margin: 2% auto;
|
|
padding: 0;
|
|
width: 95%;
|
|
max-width: 1200px;
|
|
border: none;
|
|
border-radius: 16px;
|
|
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
|
|
animation: modalSlideUp 0.3s;
|
|
}
|
|
|
|
@keyframes modalSlideUp {
|
|
from {opacity: 0; transform: translateY(30px);}
|
|
to {opacity: 1; transform: translateY(0);}
|
|
}
|
|
|
|
.modal-modern .modal-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 1.5rem 2rem;
|
|
border-bottom: 1px solid #e0e0e0;
|
|
background-color: #f8f9fa;
|
|
border-radius: 16px 16px 0 0;
|
|
}
|
|
|
|
.modal-modern .modal-title {
|
|
margin: 0;
|
|
font-size: 1.5rem;
|
|
font-weight: 600;
|
|
color: #333;
|
|
}
|
|
|
|
.modal-modern .close-modal {
|
|
color: #aaa;
|
|
font-size: 28px;
|
|
font-weight: bold;
|
|
cursor: pointer;
|
|
transition: color 0.2s;
|
|
width: 36px;
|
|
height: 36px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border-radius: 50%;
|
|
background-color: rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
.modal-modern .close-modal:hover {
|
|
color: #333;
|
|
background-color: rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.modal-modern .modal-body {
|
|
padding: 1.5rem 2rem;
|
|
max-height: 75vh;
|
|
overflow-y: auto;
|
|
background-color: #f8f9fa;
|
|
}
|
|
|
|
.modal-modern .modal-footer {
|
|
padding: 1.2rem 2rem;
|
|
border-top: 1px solid #e0e0e0;
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 1rem;
|
|
border-radius: 0 0 16px 16px;
|
|
background-color: #fff;
|
|
}
|
|
|
|
/* 인터페이스 정보 요약 스타일 */
|
|
.interface-info-summary {
|
|
display: grid;
|
|
grid-template-columns: 1fr 2fr;
|
|
gap: 1.5rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.info-card {
|
|
background-color: #fff;
|
|
border-radius: 12px;
|
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.info-header {
|
|
padding: 1rem;
|
|
background-color: #007bff;
|
|
color: white;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.info-header .info-label {
|
|
font-weight: 600;
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.info-header .info-value {
|
|
font-weight: 600;
|
|
font-size: 1.1rem;
|
|
}
|
|
|
|
.info-content {
|
|
padding: 1rem;
|
|
}
|
|
|
|
.info-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.info-row:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.info-row .info-label {
|
|
color: #666;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.info-row .info-value {
|
|
font-weight: 600;
|
|
color: #333;
|
|
}
|
|
|
|
.error-message {
|
|
padding: 0.5rem;
|
|
background-color: rgba(220, 53, 69, 0.1);
|
|
border-radius: 6px;
|
|
color: #dc3545;
|
|
font-family: monospace;
|
|
white-space: pre-wrap;
|
|
word-break: break-all;
|
|
max-height: 100px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
/* 로그 리스트 스타일 */
|
|
.log-list-container {
|
|
background-color: #fff;
|
|
border-radius: 12px;
|
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.log-list-header {
|
|
padding: 1rem;
|
|
background-color: #f8f9fa;
|
|
border-bottom: 1px solid #e0e0e0;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.log-list-header h3 {
|
|
margin: 0;
|
|
font-size: 1.2rem;
|
|
font-weight: 600;
|
|
color: #333;
|
|
}
|
|
|
|
.log-search-controls {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.time-range-selector {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.time-range-selector select {
|
|
padding: 0.25rem 0.5rem;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
background-color: #fff;
|
|
}
|
|
|
|
.log-table-container {
|
|
max-height: 400px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.log-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
.log-table th {
|
|
top: 0;
|
|
background-color: #007bff;
|
|
color: white;
|
|
padding: 0.75rem;
|
|
text-align: left;
|
|
font-weight: 800;
|
|
z-index: 10;
|
|
}
|
|
|
|
.log-table td {
|
|
padding: 0.75rem;
|
|
border-bottom: 1px solid #e0e0e0;
|
|
}
|
|
|
|
.log-table tbody tr:hover {
|
|
background-color: rgba(0, 0, 0, 0.03);
|
|
}
|
|
|
|
.log-table .empty-message {
|
|
text-align: center;
|
|
padding: 2rem;
|
|
color: #888;
|
|
font-style: italic;
|
|
}
|
|
|
|
/* 로딩 인디케이터 */
|
|
.loading-indicator {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
height: 100px;
|
|
}
|
|
|
|
.loading-spinner {
|
|
border: 4px solid rgba(0, 0, 0, 0.1);
|
|
border-radius: 50%;
|
|
border-top: 4px solid #007bff;
|
|
width: 30px;
|
|
height: 30px;
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
@keyframes spin {
|
|
0% { transform: rotate(0deg); }
|
|
100% { transform: rotate(360deg); }
|
|
}
|
|
|
|
/* 버튼 스타일 */
|
|
.btn {
|
|
padding: 0.5rem 1rem;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-weight: 500;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.btn-primary {
|
|
background-color: #007bff;
|
|
color: white;
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
background-color: #0069d9;
|
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
.btn-secondary {
|
|
background-color: #6c757d;
|
|
color: white;
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background-color: #5a6268;
|
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
/* 오류 상태 표시 */
|
|
.table-status-badge {
|
|
display: inline-block;
|
|
padding: 0.25rem 0.5rem;
|
|
border-radius: 4px;
|
|
font-size: 0.875rem;
|
|
font-weight: 500;
|
|
text-align: center;
|
|
min-width: 60px;
|
|
}
|
|
|
|
.table-status-error {
|
|
background-color: #dc3545;
|
|
color: white;
|
|
}
|
|
|
|
.table-status-normal {
|
|
background-color: #28a745;
|
|
color: white;
|
|
}
|
|
|
|
.log-table th.narrow-column {
|
|
width: 80px;
|
|
}
|
|
|
|
.highlight-row {
|
|
background-color: rgba(0, 123, 255, 0.1);
|
|
}
|
|
|
|
/* 반응형 설정 */
|
|
@media (max-width: 992px) {
|
|
.interface-info-summary {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.modal-modern .modal-content {
|
|
width: 98%;
|
|
margin: 1% auto;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
(function() {
|
|
// 모달 요소
|
|
const modal = document.getElementById('interfaceLogDetailModal');
|
|
const closeButtons = modal.querySelectorAll('.close-modal, .close-modal-btn');
|
|
const refreshButton = modal.querySelector('.refresh-logs-btn');
|
|
const timeRangeSelect = document.getElementById('log-time-range');
|
|
|
|
// 데이터 상태
|
|
let currentInterfaceId = '';
|
|
let currentErrorTime = '';
|
|
let currentInstanceName = '';
|
|
let currentErrorMessage = '';
|
|
let currentServiceType = '';
|
|
|
|
// 모달 닫기 이벤트
|
|
closeButtons.forEach(button => {
|
|
button.addEventListener('click', () => {
|
|
modal.style.display = 'none';
|
|
});
|
|
});
|
|
|
|
// 모달 외부 클릭 시 닫기
|
|
window.addEventListener('click', (event) => {
|
|
if (event.target === modal) {
|
|
modal.style.display = 'none';
|
|
}
|
|
});
|
|
|
|
// 새로고침 버튼 이벤트
|
|
refreshButton.addEventListener('click', () => {
|
|
loadInterfaceLogs();
|
|
});
|
|
|
|
// 로그 시간대 변경 이벤트
|
|
timeRangeSelect.addEventListener('change', () => {
|
|
loadInterfaceLogs();
|
|
});
|
|
|
|
// 인터페이스 로그 로드 함수
|
|
function loadInterfaceLogs() {
|
|
if (!currentInterfaceId || !currentErrorTime) {
|
|
showEmptyLogGrid("인터페이스 정보가 올바르지 않습니다.");
|
|
return;
|
|
}
|
|
|
|
// 로딩 표시
|
|
showLoadingLogGrid();
|
|
|
|
// 시간 범위 계산
|
|
const timeRangeMinutes = parseInt(timeRangeSelect.value);
|
|
const errorDate = parseErrorDateTime(currentErrorTime);
|
|
|
|
if (!errorDate) {
|
|
showEmptyLogGrid("시간 형식이 올바르지 않습니다.");
|
|
return;
|
|
}
|
|
|
|
// 시작 시간과 종료 시간 계산 (±timeRangeMinutes 분)
|
|
const startTime = new Date(errorDate.getTime() - timeRangeMinutes * 60 * 1000);
|
|
const endTime = new Date(errorDate.getTime() + timeRangeMinutes * 60 * 1000);
|
|
|
|
// API 호출 시간 형식으로 변환 (YYYYMMDDHHMMSS)
|
|
const startTimeStr = formatDateTimeForApi(startTime);
|
|
const endTimeStr = formatDateTimeForApi(endTime);
|
|
|
|
// ModernDashboardApi를 사용하여 로그 데이터 가져오기
|
|
ModernDashboardApi.getInterfaceLogs(
|
|
currentInterfaceId,
|
|
startTimeStr,
|
|
endTimeStr,
|
|
currentServiceType,
|
|
function(response) {
|
|
if (response && response.rows) {
|
|
updateLogGrid(response.rows);
|
|
} else {
|
|
showEmptyLogGrid("조회된 로그가 없습니다.");
|
|
}
|
|
},
|
|
function(xhr, status, error) {
|
|
console.error("로그 데이터 로드 실패:", error);
|
|
showEmptyLogGrid("로그 데이터를 불러오는 중 오류가 발생했습니다.");
|
|
}
|
|
);
|
|
}
|
|
|
|
// 로그 그리드 업데이트
|
|
function updateLogGrid(logs) {
|
|
const tbody = document.getElementById('log-grid-body');
|
|
|
|
if (!logs || logs.length === 0) {
|
|
showEmptyLogGrid("조회된 로그가 없습니다.");
|
|
return;
|
|
}
|
|
|
|
let html = '';
|
|
|
|
logs.forEach(log => {
|
|
const isError = log.ERRYN === 'Y';
|
|
|
|
// 시간 형식 변환 (YYYYMMDDHHMMSS -> YYYY-MM-DD HH:MM:SS)
|
|
const recvTime = formatDateTime(log.MSGDPSTYMS_TMP);
|
|
const procTime = formatDateTime(log.MSGPRCSSYMS);
|
|
|
|
html += '<tr>' +
|
|
'<td>' + (log.LOGPRCSSSERNO || '-') + '</td>' +
|
|
'<td>' + (log.KEYMGTMSGCTNT || '-') + '</td>' +
|
|
'<td>' + (log.EAIBZWKDSTCD_TMP || '-') + '</td>' +
|
|
'<td>' + (log.EAISVCNAME || '-') + '</td>' +
|
|
'<td>' + (recvTime || '-') + '</td>' +
|
|
'<td>' + (procTime || '-') + '</td>' +
|
|
'<td><span class="table-status-badge ' + (isError ? 'table-status-error' : 'table-status-normal') + '">' +
|
|
(isError ? '오류' : '정상') + '</span>' +
|
|
'<input type="hidden" value="' + (log.EAISVCSERNO_TMP || '') + '"></td>' +
|
|
'</tr>';
|
|
});
|
|
|
|
tbody.innerHTML = html;
|
|
|
|
// 현재 인터페이스 ID를 포함한 행 강조 표시
|
|
highlightRows(currentInterfaceId);
|
|
}
|
|
|
|
// 특정 인터페이스 ID를 포함한 행 강조 표시
|
|
function highlightRows(interfaceId) {
|
|
if (!interfaceId) return;
|
|
|
|
const tbody = document.getElementById('log-grid-body');
|
|
const rows = tbody.querySelectorAll('tr');
|
|
|
|
rows.forEach(row => {
|
|
const cells = row.querySelectorAll('td');
|
|
if (cells.length > 1) {
|
|
const keyCell = cells[1].textContent;
|
|
if (keyCell && keyCell.includes(interfaceId)) {
|
|
row.classList.add('highlight-row');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// 로딩 중 표시
|
|
function showLoadingLogGrid() {
|
|
const tbody = document.getElementById('log-grid-body');
|
|
tbody.innerHTML =
|
|
'<tr>' +
|
|
'<td colspan="7">' +
|
|
'<div class="loading-indicator">' +
|
|
'<div class="loading-spinner"></div>' +
|
|
'</div>' +
|
|
'</td>' +
|
|
'</tr>';
|
|
}
|
|
|
|
// 빈 그리드 표시
|
|
function showEmptyLogGrid(message) {
|
|
const tbody = document.getElementById('log-grid-body');
|
|
tbody.innerHTML =
|
|
'<tr>' +
|
|
'<td colspan="7" class="empty-message">' + (message || '조회된 데이터가 없습니다.') + '</td>' +
|
|
'</tr>';
|
|
}
|
|
|
|
// 시간 문자열 파싱 (예: "0312 10:52:32.009")
|
|
function parseErrorDateTime(dateTimeStr) {
|
|
try {
|
|
if (!dateTimeStr) return new Date();
|
|
|
|
// "0312 10:52:32.009" 형식 처리
|
|
if (dateTimeStr.includes(' ') && dateTimeStr.length >= 16) {
|
|
const datePart = dateTimeStr.split(' ')[0]; // "0312"
|
|
const timePart = dateTimeStr.split(' ')[1].split('.')[0]; // "10:52:32"
|
|
|
|
const month = parseInt(datePart.substring(0, 2));
|
|
const day = parseInt(datePart.substring(2, 4));
|
|
|
|
const timeParts = timePart.split(':');
|
|
const hour = parseInt(timeParts[0]);
|
|
const minute = parseInt(timeParts[1]);
|
|
const second = parseInt(timeParts[2]);
|
|
|
|
// 현재 연도 사용
|
|
const currentYear = new Date().getFullYear();
|
|
return new Date(currentYear, month - 1, day, hour, minute, second);
|
|
}
|
|
|
|
// 기존 "2023-03-12 15:30:45" 형식 처리 (이전 코드 유지)
|
|
const parts = dateTimeStr.split(/[- :]/);
|
|
if (parts.length >= 6) {
|
|
return new Date(parts[0], parts[1] - 1, parts[2], parts[3], parts[4], parts[5]);
|
|
}
|
|
return new Date();
|
|
} catch (e) {
|
|
console.error("날짜 파싱 오류:", e);
|
|
return new Date();
|
|
}
|
|
}
|
|
|
|
// API 호출용 날짜 형식 변환 (YYYYMMDDHHMMSS)
|
|
function formatDateTimeForApi(date) {
|
|
const year = date.getFullYear();
|
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
const day = String(date.getDate()).padStart(2, '0');
|
|
const hours = String(date.getHours()).padStart(2, '0');
|
|
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
|
|
return '' + year + month + day + hours + minutes + seconds;
|
|
}
|
|
|
|
// 화면 표시용 날짜 형식 변환 (YYYY-MM-DD HH:MM:SS)
|
|
function formatDateTime(dateTimeStr) {
|
|
if (!dateTimeStr || dateTimeStr.length < 14) return '';
|
|
|
|
const year = dateTimeStr.substr(0, 4);
|
|
const month = dateTimeStr.substr(4, 2);
|
|
const day = dateTimeStr.substr(6, 2);
|
|
const hour = dateTimeStr.substr(8, 2);
|
|
const minute = dateTimeStr.substr(10, 2);
|
|
const second = dateTimeStr.substr(12, 2);
|
|
|
|
return year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second;
|
|
}
|
|
|
|
// 모달 열기 함수 (전역 스코프에 노출)
|
|
window.openInterfaceLogDetailModal = function(interfaceId, errorTime, instanceName, errorMessage, serviceType) {
|
|
// 데이터 설정
|
|
currentInterfaceId = interfaceId || '';
|
|
currentErrorTime = errorTime || '';
|
|
currentInstanceName = instanceName || '';
|
|
currentErrorMessage = errorMessage || '';
|
|
currentServiceType = serviceType || '';
|
|
|
|
// 조회 기준 일/시 분리해서 표시
|
|
let formattedErrorTime = currentErrorTime;
|
|
if (currentErrorTime && currentErrorTime.includes(' ')) {
|
|
const datePart = currentErrorTime.split(' ')[0]; // "0312"
|
|
const timePart = currentErrorTime.split(' ')[1]; // "10:52:32.009"
|
|
|
|
// 월/일 형식으로 변환
|
|
const month = datePart.substring(0, 2);
|
|
const day = datePart.substring(2, 4);
|
|
|
|
formattedErrorTime = month + '월 ' + day + '일 ' + timePart;
|
|
}
|
|
|
|
// UI 업데이트
|
|
document.getElementById('detail-interface-id').textContent = currentInterfaceId;
|
|
document.getElementById('detail-error-time').textContent = formattedErrorTime;
|
|
document.getElementById('detail-instance-name').textContent = currentInstanceName;
|
|
document.getElementById('detail-error-message').textContent = currentErrorMessage;
|
|
|
|
// 모달 표시
|
|
modal.style.display = 'block';
|
|
|
|
// 로그 데이터 로드
|
|
loadInterfaceLogs();
|
|
};
|
|
})();
|
|
</script> |