972819b492
- ApexCharts 기반 실시간 모니터링 대시보드 - 어댑터/배치/서버/소켓/트랜잭션 모니터링 컴포넌트 - 처리량 계산 및 데이터 매핑 서비스
835 lines
29 KiB
Plaintext
835 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<>();
|
|
priorityMap.put("MCI", 0);
|
|
priorityMap.put("EAI", 1);
|
|
priorityMap.put("FEP", 2);
|
|
priorityMap.put("CGW", 3);
|
|
priorityMap.put("FGW", 4);
|
|
priorityMap.put("API", 5);
|
|
priorityMap.put("APIGW", 6);
|
|
priorityMap.put("ONL", 7);
|
|
|
|
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>
|