Files
eapim-admin/WebContent/jsp/dashboard-modern/components/dashboard-interface-detail.jsp
T
youna 972819b492 모던 대시보드 패치
- ApexCharts 기반 실시간 모니터링 대시보드
- 어댑터/배치/서버/소켓/트랜잭션 모니터링 컴포넌트
- 처리량 계산 및 데이터 매핑 서비스
2025-11-14 17:59:06 +09:00

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">&times;</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>