9545d69dc4
존재하지 않는 DB 테이블을 참조하여 발생하던 대시보드 오류를 해결. K8S 환경 확인을 위한 DB 조회 로직을 제거, 일반 환경에서 항상 정상 동작하도록 수정.
832 lines
29 KiB
Plaintext
832 lines
29 KiB
Plaintext
<%@ page language="java" contentType="text/html; charset=utf-8"%>
|
|
<%@ page import="java.util.*" %>
|
|
<%@ page import="com.eactive.eai.rms.common.datasource.*" %>
|
|
<%@ page import="java.util.stream.Collectors" %>
|
|
|
|
<div class="dashboard-transactions">
|
|
<div class="dashboard-header">
|
|
<div class="header-title-container">
|
|
<h2 class="section-title">거래처리 집계(일누적)</h2>
|
|
</div>
|
|
</div>
|
|
<div class="card shadow mb-0" style="overflow-y:auto;">
|
|
<div class="transaction-filters">
|
|
<div class="filter-group">
|
|
<button class="tran-filter-btn active" data-type="all">전체</button>
|
|
<%
|
|
// serverTypeMap 변수를 가져오기
|
|
Map<String, List<DataSourceType>> serverTypeMap =
|
|
(Map<String, List<DataSourceType>>)request.getAttribute("serverTypeMap");
|
|
|
|
if (serverTypeMap == null) {
|
|
// application 스코프에서 가져오기 시도
|
|
serverTypeMap = (Map<String, List<DataSourceType>>)application.getAttribute("serverTypeMap");
|
|
}
|
|
|
|
Map<String, Integer> priorityMap = new HashMap<String, Integer>();
|
|
priorityMap.put("MCI", 0);
|
|
priorityMap.put("EAI", 1);
|
|
priorityMap.put("FEP", 2);
|
|
priorityMap.put("CGW", 3);
|
|
priorityMap.put("FGW", 4);
|
|
|
|
Map<String,List<DataSourceType>> sortedMap = serverTypeMap.entrySet().stream()
|
|
.sorted(Comparator.comparingInt(e -> priorityMap.getOrDefault(e.getKey(), Integer.MAX_VALUE)))
|
|
.collect(Collectors.toMap(
|
|
Map.Entry::getKey
|
|
, Map.Entry::getValue
|
|
,(e1, e2) -> e1
|
|
, LinkedHashMap::new
|
|
));
|
|
|
|
if (sortedMap != null) {
|
|
// 서버 타입별 필터 버튼 생성
|
|
for (Map.Entry<String, List<DataSourceType>> entry : sortedMap.entrySet()) {
|
|
String serverType = entry.getKey();
|
|
List<DataSourceType> servers = entry.getValue();
|
|
|
|
// BAT 서버는 제외
|
|
if (DataSourceTypeManager.BAT.equals(serverType) || DataSourceTypeManager.BAP.equals(serverType)) {
|
|
continue;
|
|
}
|
|
|
|
String serverTypeText = servers.size() > 0 ? servers.get(0).getText() : serverType;
|
|
|
|
if( !serverTypeText.endsWith("GW") ) {
|
|
%>
|
|
<button class="tran-filter-btn" data-type="<%= serverType.toLowerCase() %>"><%= serverTypeText %></button>
|
|
<% }
|
|
}
|
|
} %>
|
|
</div>
|
|
</div>
|
|
<div class="transaction-table-container">
|
|
<table class="transaction-table">
|
|
<thead>
|
|
<tr>
|
|
<th style="width: 1%"></th>
|
|
<th>업무그룹</th>
|
|
<th>정상처리</th>
|
|
<th>업무에러</th>
|
|
<th>타임아웃</th>
|
|
<th>통신에러</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="transaction-table-body">
|
|
<!-- 거래 처리 데이터가 동적으로 추가됩니다 -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 거래 처리 행 템플릿 -->
|
|
<template id="transaction-row-template">
|
|
<tr>
|
|
<td>
|
|
<span class="instance-status status-normal"></span>
|
|
</td>
|
|
<td class="group-name">업무그룹</td>
|
|
<td>
|
|
<div class="metric">
|
|
<div class="metric-value">
|
|
<span class="clickable transaction-count">0</span>
|
|
<span class="metric-change transaction-change" style="visibility: hidden;"><i class="bi bi-arrow-up-short"></i>0</span>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="metric">
|
|
<div class="metric-value">
|
|
<span class="clickable error-count">0</span>
|
|
<span class="metric-change error-change" style="visibility: hidden;"><i class="bi bi-arrow-up-short"></i>0</span>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="metric">
|
|
<div class="metric-value">
|
|
<span class="clickable timeout-count">0</span>
|
|
<span class="metric-change timeout-change" style="visibility: hidden;"><i class="bi bi-arrow-up-short"></i>0</span>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="metric">
|
|
<div class="metric-value">
|
|
<span class="clickable syserror-count">0</span>
|
|
<span class="metric-change syserror-change" style="visibility: hidden;"><i class="bi bi-arrow-up-short"></i>0</span>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</template>
|
|
|
|
<script>
|
|
// 데이터 로딩 상태 플래그
|
|
var isTransactionDataLoading = false;
|
|
|
|
// 이전 데이터 저장 객체
|
|
var previousTransactionData = {};
|
|
|
|
// 숫자 형식화 함수 (천 단위 콤마 추가)
|
|
function formatNumber(num) {
|
|
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
}
|
|
|
|
// 기존 행 업데이트 함수 수정
|
|
function updateExistingRow(row, transactionData) {
|
|
// 상태 업데이트
|
|
var statusElement = row.querySelector('.instance-status');
|
|
statusElement.className = 'instance-status';
|
|
|
|
// 정상 처리와 오류 비율에 따라 색상 계산
|
|
var totalCount = (transactionData.count || 0);
|
|
var errorCount = (transactionData.error || 0) + (transactionData.timeout || 0) + (transactionData.syserror || 0);
|
|
var totalTransactions = totalCount + errorCount;
|
|
|
|
// 색상 설정
|
|
if (totalTransactions > 0) {
|
|
var successRatio = totalCount / totalTransactions;
|
|
var statusColor = calculateStatusColor(successRatio);
|
|
statusElement.style.backgroundColor = statusColor;
|
|
} else {
|
|
// 거래가 없는 경우 회색으로 표시
|
|
statusElement.style.backgroundColor = '#9e9e9e';
|
|
}
|
|
|
|
// 이전 데이터 가져오기
|
|
var prevData = previousTransactionData[transactionData.name] || {
|
|
count: 0,
|
|
error: 0,
|
|
timeout: 0,
|
|
syserror: 0
|
|
};
|
|
|
|
// 정상처리 건수 업데이트
|
|
var transactionCount = row.querySelector('.transaction-count');
|
|
transactionCount.textContent = formatNumber(transactionData.count || 0);
|
|
|
|
// 증가량 표시
|
|
var transactionChange = row.querySelector('.transaction-change');
|
|
var countDiff = (transactionData.count || 0) - prevData.count;
|
|
if (countDiff > 0) {
|
|
transactionChange.innerHTML = '<i class="bi bi-arrow-up-short"></i>' + formatNumber(countDiff);
|
|
transactionChange.style.visibility = 'visible';
|
|
|
|
// 2초 후 사라지게 설정
|
|
setTimeout(function() {
|
|
transactionChange.style.visibility = 'hidden';
|
|
}, 2000);
|
|
} else {
|
|
transactionChange.style.visibility = 'hidden';
|
|
}
|
|
|
|
// 업무에러 업데이트
|
|
var errorCount = row.querySelector('.error-count');
|
|
errorCount.textContent = formatNumber(transactionData.error || 0);
|
|
|
|
// 증가량 표시
|
|
var errorChange = row.querySelector('.error-change');
|
|
var errorDiff = (transactionData.error || 0) - prevData.error;
|
|
if (errorDiff > 0) {
|
|
errorChange.innerHTML = '<i class="bi bi-arrow-up-short"></i>' + formatNumber(errorDiff);
|
|
errorChange.style.visibility = 'visible';
|
|
|
|
// 2초 후 사라지게 설정
|
|
setTimeout(function() {
|
|
errorChange.style.visibility = 'hidden';
|
|
}, 2000);
|
|
} else {
|
|
errorChange.style.visibility = 'hidden';
|
|
}
|
|
|
|
// 타임아웃 업데이트
|
|
var timeoutCount = row.querySelector('.timeout-count');
|
|
timeoutCount.textContent = formatNumber(transactionData.timeout || 0);
|
|
|
|
// 증가량 표시
|
|
var timeoutChange = row.querySelector('.timeout-change');
|
|
var timeoutDiff = (transactionData.timeout || 0) - prevData.timeout;
|
|
if (timeoutDiff > 0) {
|
|
timeoutChange.innerHTML = '<i class="bi bi-arrow-up-short"></i>' + formatNumber(timeoutDiff);
|
|
timeoutChange.style.visibility = 'visible';
|
|
|
|
// 2초 후 사라지게 설정
|
|
setTimeout(function() {
|
|
timeoutChange.style.visibility = 'hidden';
|
|
}, 2000);
|
|
} else {
|
|
timeoutChange.style.visibility = 'hidden';
|
|
}
|
|
|
|
// 통신에러 업데이트
|
|
var syserrorCount = row.querySelector('.syserror-count');
|
|
syserrorCount.textContent = formatNumber(transactionData.syserror || 0);
|
|
|
|
// 증가량 표시
|
|
var syserrorChange = row.querySelector('.syserror-change');
|
|
var syserrorDiff = (transactionData.syserror || 0) - prevData.syserror;
|
|
if (syserrorDiff > 0) {
|
|
syserrorChange.innerHTML = '<i class="bi bi-arrow-up-short"></i>' + formatNumber(syserrorDiff);
|
|
syserrorChange.style.visibility = 'visible';
|
|
|
|
// 2초 후 사라지게 설정
|
|
setTimeout(function() {
|
|
syserrorChange.style.visibility = 'hidden';
|
|
}, 2000);
|
|
} else {
|
|
syserrorChange.style.visibility = 'hidden';
|
|
}
|
|
|
|
// 현재 데이터를 이전 데이터로 저장
|
|
previousTransactionData[transactionData.name] = {
|
|
count: transactionData.count || 0,
|
|
error: transactionData.error || 0,
|
|
timeout: transactionData.timeout || 0,
|
|
syserror: transactionData.syserror || 0
|
|
};
|
|
}
|
|
|
|
// 거래 행 생성 함수 수정
|
|
function createTransactionRow(transactionData) {
|
|
var template = document.getElementById('transaction-row-template');
|
|
var clone = document.importNode(template.content, true);
|
|
|
|
// 업무그룹 이름 설정
|
|
clone.querySelector('.group-name').textContent = transactionData.name;
|
|
|
|
// 정상 처리와 오류 비율에 따라 색상 계산
|
|
var totalCount = (transactionData.count || 0);
|
|
var errorCount = (transactionData.error || 0) + (transactionData.timeout || 0) + (transactionData.syserror || 0);
|
|
var totalTransactions = totalCount + errorCount;
|
|
|
|
// 상태 설정
|
|
var statusElement = clone.querySelector('.instance-status');
|
|
|
|
// 색상 설정
|
|
if (totalTransactions > 0) {
|
|
var successRatio = totalCount / totalTransactions;
|
|
var statusColor = calculateStatusColor(successRatio);
|
|
statusElement.style.backgroundColor = statusColor;
|
|
} else {
|
|
// 거래가 없는 경우 회색으로 표시
|
|
statusElement.style.backgroundColor = '#9e9e9e';
|
|
}
|
|
|
|
// 정상처리 건수 설정
|
|
clone.querySelector('.transaction-count').textContent = formatNumber(transactionData.count || 0);
|
|
|
|
// 업무에러 설정
|
|
clone.querySelector('.error-count').textContent = formatNumber(transactionData.error || 0);
|
|
|
|
// 타임아웃 설정
|
|
clone.querySelector('.timeout-count').textContent = formatNumber(transactionData.timeout || 0);
|
|
|
|
// 통신에러 설정
|
|
clone.querySelector('.syserror-count').textContent = formatNumber(transactionData.syserror || 0);
|
|
|
|
// 현재 데이터를 이전 데이터로 저장
|
|
previousTransactionData[transactionData.name] = {
|
|
count: transactionData.count || 0,
|
|
error: transactionData.error || 0,
|
|
timeout: transactionData.timeout || 0,
|
|
syserror: transactionData.syserror || 0
|
|
};
|
|
|
|
return clone;
|
|
}
|
|
|
|
// 거래 건수 테이블 업데이트 함수
|
|
function updateTransactionTable(data) {
|
|
if (!data || !data.transactions) return;
|
|
|
|
var tableBody = document.getElementById('transaction-table-body');
|
|
|
|
// 합계 값 초기화
|
|
var totalCount = 0;
|
|
var totalError = 0;
|
|
var totalTimeout = 0;
|
|
var totalSyserror = 0;
|
|
|
|
// 현재 테이블에 있는 행들 저장
|
|
var currentRows = {}; // 현재 행들을 저장할 객체
|
|
|
|
// 현재 테이블의 상태를 저장
|
|
Array.from(tableBody.querySelectorAll('tr:not(.total-row)')).forEach(function(row) {
|
|
var transactionType = row.querySelector('.group-name').textContent;
|
|
currentRows[transactionType] = row;
|
|
});
|
|
|
|
// 합계 행 제거 (나중에 다시 추가)
|
|
var totalRow = tableBody.querySelector('.total-row');
|
|
if (totalRow) {
|
|
tableBody.removeChild(totalRow);
|
|
}
|
|
|
|
// 거래 데이터 행 업데이트 또는 추가
|
|
data.transactions.forEach(function(transaction) {
|
|
// TOTAL 항목은 표에 표시하지 않고 마지막에 별도로 처리
|
|
// APIGW는 표시하고, CGW, FGW는 제외
|
|
if (transaction.type === 'TOTAL' || (transaction.type.endsWith("GW") && transaction.type !== 'APIGW') ) {
|
|
return;
|
|
}
|
|
|
|
// 각 거래별 데이터 설정
|
|
var transactionData = {
|
|
name: transaction.type,
|
|
status: 'normal',
|
|
count: transaction.success || 0,
|
|
error: transaction.fail || 0,
|
|
timeout: transaction.timeout || 0,
|
|
syserror: transaction.syserror || 0
|
|
};
|
|
|
|
// 상태 설정: 에러가 많으면 경고 또는 오류 상태로 표시
|
|
if ((transaction.fail + transaction.timeout + transaction.syserror) > 50) {
|
|
transactionData.status = 'error';
|
|
} else if ((transaction.fail + transaction.timeout + transaction.syserror) > 10) {
|
|
transactionData.status = 'warning';
|
|
}
|
|
|
|
// 이미 존재하는 행인지 확인하여 업데이트 또는 새로 생성
|
|
if (currentRows[transaction.type]) {
|
|
updateExistingRow(currentRows[transaction.type], transactionData);
|
|
} else {
|
|
// 새 행 생성 및 추가
|
|
var newRow = createTransactionRow(transactionData);
|
|
tableBody.appendChild(newRow);
|
|
}
|
|
|
|
// 합계 계산
|
|
totalCount += transactionData.count;
|
|
totalError += transactionData.error;
|
|
totalTimeout += transactionData.timeout;
|
|
totalSyserror += transactionData.syserror;
|
|
});
|
|
|
|
// 사라진 행 제거 (현재 데이터에 없는 행)
|
|
var remainingTypes = data.transactions
|
|
.filter(t => t.type !== 'TOTAL')
|
|
.map(t => t.type);
|
|
|
|
for (var type in currentRows) {
|
|
if (!remainingTypes.includes(type)) {
|
|
tableBody.removeChild(currentRows[type]);
|
|
}
|
|
}
|
|
|
|
// 합계 행 생성 및 추가
|
|
totalRow = document.createElement('tr');
|
|
totalRow.className = 'total-row';
|
|
|
|
// 상태 아이콘 셀
|
|
var statusCell = document.createElement('td');
|
|
totalRow.appendChild(statusCell);
|
|
|
|
// 업무그룹 셀
|
|
var nameCell = document.createElement('td');
|
|
nameCell.className = 'group-name';
|
|
nameCell.textContent = '합계';
|
|
totalRow.appendChild(nameCell);
|
|
|
|
// 정상처리 셀
|
|
var countCell = document.createElement('td');
|
|
var countMetric = document.createElement('div');
|
|
countMetric.className = 'metric';
|
|
|
|
var countValue = document.createElement('div');
|
|
countValue.className = 'metric-value';
|
|
|
|
var countSpan = document.createElement('span');
|
|
countSpan.className = 'transaction-count';
|
|
countSpan.textContent = formatNumber(totalCount);
|
|
|
|
// 가짜 metric-change span 추가 (정상처리)
|
|
var countChangeSpan = document.createElement('span');
|
|
countChangeSpan.className = 'metric-change transaction-change';
|
|
countChangeSpan.style.visibility = 'hidden';
|
|
countChangeSpan.innerHTML = '<i class="bi bi-arrow-up-short"></i>0';
|
|
|
|
countValue.appendChild(countSpan);
|
|
countValue.appendChild(countChangeSpan); // 가짜 span 추가
|
|
countMetric.appendChild(countValue);
|
|
countCell.appendChild(countMetric);
|
|
totalRow.appendChild(countCell);
|
|
|
|
// 업무에러 셀
|
|
var errorCell = document.createElement('td');
|
|
var errorMetric = document.createElement('div');
|
|
errorMetric.className = 'metric';
|
|
|
|
var errorValue = document.createElement('div');
|
|
errorValue.className = 'metric-value';
|
|
|
|
var errorSpan = document.createElement('span');
|
|
errorSpan.className = 'error-count';
|
|
errorSpan.textContent = formatNumber(totalError);
|
|
|
|
// 가짜 metric-change span 추가 (업무에러)
|
|
var errorChangeSpan = document.createElement('span');
|
|
errorChangeSpan.className = 'metric-change error-change';
|
|
errorChangeSpan.style.visibility = 'hidden';
|
|
errorChangeSpan.innerHTML = '<i class="bi bi-arrow-up-short"></i>0';
|
|
|
|
errorValue.appendChild(errorSpan);
|
|
errorValue.appendChild(errorChangeSpan); // 가짜 span 추가
|
|
errorMetric.appendChild(errorValue);
|
|
errorCell.appendChild(errorMetric);
|
|
totalRow.appendChild(errorCell);
|
|
|
|
// 타임아웃 셀
|
|
var timeoutCell = document.createElement('td');
|
|
var timeoutMetric = document.createElement('div');
|
|
timeoutMetric.className = 'metric';
|
|
|
|
var timeoutValue = document.createElement('div');
|
|
timeoutValue.className = 'metric-value';
|
|
|
|
var timeoutSpan = document.createElement('span');
|
|
timeoutSpan.className = 'timeout-count';
|
|
timeoutSpan.textContent = formatNumber(totalTimeout);
|
|
|
|
// 가짜 metric-change span 추가 (타임아웃)
|
|
var timeoutChangeSpan = document.createElement('span');
|
|
timeoutChangeSpan.className = 'metric-change timeout-change';
|
|
timeoutChangeSpan.style.visibility = 'hidden';
|
|
timeoutChangeSpan.innerHTML = '<i class="bi bi-arrow-up-short"></i>0';
|
|
|
|
timeoutValue.appendChild(timeoutSpan);
|
|
timeoutValue.appendChild(timeoutChangeSpan); // 가짜 span 추가
|
|
timeoutMetric.appendChild(timeoutValue);
|
|
timeoutCell.appendChild(timeoutMetric);
|
|
totalRow.appendChild(timeoutCell);
|
|
|
|
// 통신에러 셀
|
|
var syserrorCell = document.createElement('td');
|
|
var syserrorMetric = document.createElement('div');
|
|
syserrorMetric.className = 'metric';
|
|
|
|
var syserrorValue = document.createElement('div');
|
|
syserrorValue.className = 'metric-value';
|
|
|
|
var syserrorSpan = document.createElement('span');
|
|
syserrorSpan.className = 'syserror-count';
|
|
syserrorSpan.textContent = formatNumber(totalSyserror);
|
|
|
|
// 가짜 metric-change span 추가 (통신에러)
|
|
var syserrorChangeSpan = document.createElement('span');
|
|
syserrorChangeSpan.className = 'metric-change syserror-change';
|
|
syserrorChangeSpan.style.visibility = 'hidden';
|
|
syserrorChangeSpan.innerHTML = '<i class="bi bi-arrow-up-short"></i>0';
|
|
|
|
syserrorValue.appendChild(syserrorSpan);
|
|
syserrorValue.appendChild(syserrorChangeSpan); // 가짜 span 추가
|
|
syserrorMetric.appendChild(syserrorValue);
|
|
syserrorCell.appendChild(syserrorMetric);
|
|
totalRow.appendChild(syserrorCell);
|
|
|
|
// 테이블에 합계 행 추가
|
|
tableBody.appendChild(totalRow);
|
|
}
|
|
|
|
// 트랜잭션 데이터 로드 함수
|
|
function loadTransactionData(type) {
|
|
// 이미 로딩 중이면 중복 요청 방지
|
|
if (isTransactionDataLoading) {
|
|
return;
|
|
}
|
|
|
|
// 로딩 상태 플래그 설정
|
|
isTransactionDataLoading = true;
|
|
|
|
// 전역 로딩 이벤트 발생 (DashboardEventManager 사용)
|
|
if (window.DashboardEventManager) {
|
|
window.DashboardEventManager.trigger('loading-start');
|
|
}
|
|
|
|
// 필터 조건을 파라미터로 API 호출
|
|
ModernDashboardApi.getTransactionInfo(type, function(data) {
|
|
// 데이터 처리 완료 후 UI 업데이트
|
|
// 테이블 업데이트
|
|
updateTransactionTable(data);
|
|
|
|
// 전역 로딩 완료 이벤트 발생
|
|
if (window.DashboardEventManager) {
|
|
window.DashboardEventManager.trigger('loading-end');
|
|
}
|
|
|
|
// 로딩 상태 플래그 해제
|
|
isTransactionDataLoading = false;
|
|
});
|
|
}
|
|
|
|
// 트랜잭션 필터 버튼 클릭 이벤트
|
|
$('.tran-filter-btn').click(function() {
|
|
// 모든 버튼 비활성화
|
|
$('.tran-filter-btn').removeClass('active');
|
|
|
|
// 클릭한 버튼 활성화
|
|
$(this).addClass('active');
|
|
|
|
// 선택한 타입에 따라 트랜잭션 데이터 필터링 로직 추가
|
|
var type = $(this).data('type');
|
|
loadTransactionData(type);
|
|
});
|
|
|
|
// 실제 서비스에서 표시할 거래 유형 목록 (정렬 순서 포함)
|
|
var preferredTransactionOrder = ['MCI', 'EAI', 'FEP', 'MCU', 'APIGW'];
|
|
|
|
// refreshDashboardData 함수가 호출될 때 거래 처리 테이블이 초기화되지 않도록
|
|
// modern-dashboard.js 파일의 refreshDashboardData 함수를 확장합니다.
|
|
$(document).ready(function() {
|
|
// DashboardEventManager를 사용하여 새로고침 이벤트 구독
|
|
if (window.DashboardEventManager) {
|
|
window.DashboardEventManager.on('refresh', function() {
|
|
// 트랜잭션 정보 조회 - 현재 선택된 필터 사용
|
|
var activeType = $('.tran-filter-btn.active').data('type') || 'all';
|
|
console.log('거래처리 집계 데이터 갱신 시작: ' + activeType);
|
|
loadTransactionData(activeType);
|
|
});
|
|
|
|
// 초기화 완료 이벤트 로깅
|
|
console.log('거래처리 집계 컴포넌트 초기화 완료');
|
|
}
|
|
|
|
// 페이지 로드 시 데이터 로드
|
|
var activeType = $('.tran-filter-btn.active').data('type') || 'all';
|
|
loadTransactionData(activeType);
|
|
});
|
|
|
|
// 성공 비율에 따른 색상 계산 함수
|
|
function calculateStatusColor(successRatio) {
|
|
// 성공 비율이 0.5(50%)일 때 주황색(#ff9800)
|
|
// 성공 비율이 1.0(100%)에 가까울수록 초록색(#4caf50)
|
|
// 성공 비율이 0.0(0%)에 가까울수록 빨간색(#e53935)
|
|
|
|
// 정확히 50%에 가까울 때는 주황색
|
|
if (Math.abs(successRatio - 0.5) < 0.05) {
|
|
return '#ff9800'; // 주황색 (50:50 비율 근처)
|
|
} else if (successRatio > 0.5) {
|
|
// 50% ~ 100% 사이: 주황색에서 초록색으로 매우 급격한 변화
|
|
// 55% 이상이면 거의 바로 초록색으로 변화
|
|
var greenRatio;
|
|
|
|
if (successRatio > 0.65) {
|
|
// 65% 이상이면 완전한 초록색
|
|
greenRatio = 1;
|
|
} else {
|
|
// 50%~65% 사이에서 급격하게 변화
|
|
greenRatio = (successRatio - 0.5) * 6.67; // 0.5에서 0.65 사이를 0에서 1로 매핑
|
|
}
|
|
|
|
// 주황색(#ff9800)에서 초록색(#4caf50)으로 보간
|
|
var r = Math.round(255 - (255 - 76) * greenRatio);
|
|
var g = Math.round(152 + (175 - 152) * greenRatio);
|
|
var b = Math.round(0 + (80 - 0) * greenRatio);
|
|
|
|
return 'rgb(' + r + ',' + g + ',' + b + ')';
|
|
} else {
|
|
// 0% ~ 50% 사이: 빨간색에서 주황색으로 매우 급격한 변화
|
|
// 45% 이하면 거의 바로 빨간색으로 변화
|
|
var orangeRatio;
|
|
|
|
if (successRatio < 0.35) {
|
|
// 35% 이하면 완전한 빨간색
|
|
orangeRatio = 0;
|
|
} else {
|
|
// 35%~50% 사이에서 급격하게 변화
|
|
orangeRatio = (successRatio - 0.35) * 6.67; // 0.35에서 0.5 사이를 0에서 1로 매핑
|
|
}
|
|
|
|
// 빨간색(#e53935)에서 주황색(#ff9800)으로 보간
|
|
var r = Math.round(229 + (255 - 229) * orangeRatio);
|
|
var g = Math.round(57 + (152 - 57) * orangeRatio);
|
|
var b = Math.round(53 + (0 - 53) * orangeRatio);
|
|
|
|
return 'rgb(' + r + ',' + g + ',' + b + ')';
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
/* 트랜잭션 섹션 */
|
|
.dashboard-transactions .section-title {
|
|
font-size: 1.1rem;
|
|
font-weight: 600;
|
|
margin-bottom: 0;
|
|
padding-bottom: 0;
|
|
border-bottom: none;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.dashboard-transactions .card {
|
|
border-radius: 0.35rem;
|
|
box-shadow: 0 0.15rem 1.75rem 0 rgba(58, 59, 69, 0.15);
|
|
overflow: hidden;
|
|
height: 242px;
|
|
}
|
|
|
|
.transaction-filters {
|
|
display: flex;
|
|
justify-content: flex-start;
|
|
align-items: center;
|
|
padding: 0.75rem 1rem;
|
|
border-bottom: 1px solid #e3e6f0;
|
|
}
|
|
|
|
.filter-group {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.filter-btn:hover {
|
|
background-color: #e9ecef;
|
|
color: #495057;
|
|
}
|
|
|
|
.filter-btn.active {
|
|
background-color: var(--color-primary);
|
|
color: white;
|
|
border-color: var(--color-primary);
|
|
}
|
|
|
|
.transaction-table-container {
|
|
max-height: 300px;
|
|
overflow-y: auto;
|
|
position: relative;
|
|
}
|
|
|
|
.transaction-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: 0.85rem;
|
|
table-layout: fixed;
|
|
}
|
|
|
|
.transaction-table th {
|
|
background-color: #f8f9fc;
|
|
font-weight: 600;
|
|
text-align: center;
|
|
padding: 0.75rem 1rem;
|
|
border-bottom: 1px solid #e3e6f0;
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 10;
|
|
}
|
|
|
|
.transaction-table td {
|
|
padding: 0.75rem 1rem;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.transaction-table td:nth-child(1) {
|
|
text-align: left;
|
|
}
|
|
|
|
.transaction-table td:nth-child(2) {
|
|
text-align: center;
|
|
}
|
|
|
|
.transaction-table td:nth-child(n+3) {
|
|
text-align: center;
|
|
}
|
|
|
|
.transaction-table tr:last-child td {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.transaction-table tr:hover {
|
|
background-color: rgba(0, 0, 0, 0.02);
|
|
}
|
|
|
|
.transaction-table tr.total-row {
|
|
background-color: rgba(200, 200, 200, 0.2);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.transaction-table tr.total-row td {
|
|
border-bottom: 2px solid #9e9e9e;
|
|
}
|
|
|
|
.transaction-table tr.total-row .group-name {
|
|
color: #616161;
|
|
}
|
|
|
|
/* 인스턴스 상태 표시 */
|
|
.instance-status {
|
|
display: inline-block;
|
|
width: 10px;
|
|
height: 10px;
|
|
border-radius: 50%;
|
|
margin-right: 5px;
|
|
}
|
|
|
|
.status-normal {
|
|
background-color: #4caf50;
|
|
}
|
|
|
|
.status-warning {
|
|
background-color: #ff9800;
|
|
}
|
|
|
|
.status-error {
|
|
background-color: #e53935;
|
|
}
|
|
|
|
/* 메트릭 스타일 */
|
|
.metric {
|
|
display: flex;
|
|
flex-direction: column;
|
|
position: relative;
|
|
min-height: 24px;
|
|
}
|
|
|
|
.metric-value {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
position: relative;
|
|
}
|
|
|
|
.clickable {
|
|
cursor: pointer;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.transaction-count {
|
|
color: #4caf50;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.error-count {
|
|
color: #e53935;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.timeout-count {
|
|
color: #ff9800;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.syserror-count {
|
|
color: #e53935;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.metric-change {
|
|
font-size: 0.75rem;
|
|
color: #4caf50;
|
|
font-weight: 500;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
transition: opacity 0.3s ease;
|
|
position: relative;
|
|
margin-left: 2px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.error-change {
|
|
color: #e53935;
|
|
}
|
|
|
|
.timeout-change {
|
|
color: #ff9800;
|
|
}
|
|
|
|
.syserror-change {
|
|
color: #e53935;
|
|
}
|
|
|
|
.metric-change i {
|
|
font-size: 1rem;
|
|
margin-right: 0;
|
|
}
|
|
|
|
/* 그룹 이름 스타일 */
|
|
.group-name {
|
|
text-align: center;
|
|
}
|
|
|
|
/* 대시보드 헤더 스타일 */
|
|
.dashboard-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
|
|
/* 제목과 버튼을 한 줄에 나란히 표시하기 위한 컨테이너 */
|
|
.header-title-container {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
/* 트랜지션 효과 제거 */
|
|
#transaction-table-body {
|
|
/* transition: opacity 0.2s ease; */
|
|
}
|
|
</style>
|