모던 대시보드 패치
- ApexCharts 기반 실시간 모니터링 대시보드 - 어댑터/배치/서버/소켓/트랜잭션 모니터링 컴포넌트 - 처리량 계산 및 데이터 매핑 서비스
This commit is contained in:
@@ -0,0 +1,934 @@
|
||||
<%@ 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-servers">
|
||||
<div class="dashboard-header">
|
||||
<h2 class="section-title">서버 상태</h2>
|
||||
|
||||
<div class="server-filters">
|
||||
<div class="filter-group">
|
||||
<button class="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;
|
||||
%>
|
||||
<button class="filter-btn" data-type="<%= serverType.toLowerCase() %>"><%= serverTypeText %></button>
|
||||
<% }
|
||||
} %>
|
||||
</div>
|
||||
<div class="search-container">
|
||||
<i class="bi bi-search"></i>
|
||||
<input type="text" id="server-search" placeholder="서버 검색..." />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%
|
||||
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;
|
||||
|
||||
// 서버 타입별 보더 색상 클래스
|
||||
String borderColorClass = "";
|
||||
if (serverType.equalsIgnoreCase("FEP")) {
|
||||
borderColorClass = "border-primary";
|
||||
} else if (serverType.equalsIgnoreCase("EAI")) {
|
||||
borderColorClass = "border-success";
|
||||
} else if (serverType.equalsIgnoreCase("EMS")) {
|
||||
borderColorClass = "border-purple";
|
||||
} else if (serverType.equalsIgnoreCase("EDI")) {
|
||||
borderColorClass = "border-orange";
|
||||
}
|
||||
%>
|
||||
<div class="server-section" id="server-section-<%= serverType.toLowerCase() %>">
|
||||
<div class="card server-card-container <%= borderColorClass %>">
|
||||
<div class="server-section-header">
|
||||
<h3 class="server-section-title"><%= serverTypeText %> 서버</h3>
|
||||
<span class="server-status-badge badge-success <%= serverType.toLowerCase() %>-status-badge">0 / 0 정상</span>
|
||||
</div>
|
||||
<div class="server-grid server-grid-<%= serverType.toLowerCase() %>" id="<%= serverType.toLowerCase() %>-servers-container">
|
||||
<!-- 여기에 <%= serverTypeText %> 서버 카드가 동적으로 추가됩니다 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% }
|
||||
} %>
|
||||
</div>
|
||||
|
||||
<!-- 서버 카드 템플릿 -->
|
||||
<template id="server-card-template">
|
||||
<div class="server-card">
|
||||
<div class="server-header">
|
||||
<h5 class="server-name">서버 이름</h5>
|
||||
<span class="server-status-badge badge-success">정상</span>
|
||||
</div>
|
||||
<div class="server-metrics">
|
||||
<!-- 메트릭 헤더 행 -->
|
||||
<div class="metrics-row">
|
||||
<!-- CPU 메트릭 -->
|
||||
<div class="server-metric">
|
||||
<div class="metric-header">
|
||||
<span class="metric-name"><i class="bi bi-cpu" style="color: #0dcaf0; font-size: 1.1rem;"></i></span>
|
||||
<span class="metric-value cpu-value">0%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 메모리 메트릭 -->
|
||||
<div class="server-metric">
|
||||
<div class="metric-header">
|
||||
<span class="metric-name"><i class="bi bi-memory" style="color: #6f42c1; font-size: 1.1rem;"></i></span>
|
||||
<span class="metric-value memory-value">0%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 디스크 메트릭 -->
|
||||
<div class="server-metric">
|
||||
<div class="metric-header">
|
||||
<span class="metric-name"><i class="bi bi-hdd" style="color: #fd7e14; font-size: 1.1rem;"></i></span>
|
||||
<span class="metric-value disk-value">0%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 스파크라인 행 -->
|
||||
<div class="sparklines-row">
|
||||
<div class="sparkline cpu-sparkline">
|
||||
<div class="sparkline-bar success" style="height: 10%"></div>
|
||||
<div class="sparkline-bar success" style="height: 15%"></div>
|
||||
<div class="sparkline-bar success" style="height: 20%"></div>
|
||||
<div class="sparkline-bar success" style="height: 15%"></div>
|
||||
<div class="sparkline-bar success" style="height: 25%"></div>
|
||||
<div class="sparkline-bar success" style="height: 20%"></div>
|
||||
<div class="sparkline-bar success" style="height: 15%"></div>
|
||||
</div>
|
||||
|
||||
<div class="sparkline memory-sparkline">
|
||||
<div class="sparkline-bar success" style="height: 20%"></div>
|
||||
<div class="sparkline-bar success" style="height: 25%"></div>
|
||||
<div class="sparkline-bar success" style="height: 30%"></div>
|
||||
<div class="sparkline-bar success" style="height: 25%"></div>
|
||||
<div class="sparkline-bar success" style="height: 35%"></div>
|
||||
<div class="sparkline-bar success" style="height: 30%"></div>
|
||||
<div class="sparkline-bar success" style="height: 25%"></div>
|
||||
</div>
|
||||
|
||||
<div class="sparkline disk-sparkline">
|
||||
<div class="sparkline-bar success" style="height: 30%"></div>
|
||||
<div class="sparkline-bar success" style="height: 30%"></div>
|
||||
<div class="sparkline-bar success" style="height: 30%"></div>
|
||||
<div class="sparkline-bar success" style="height: 30%"></div>
|
||||
<div class="sparkline-bar success" style="height: 30%"></div>
|
||||
<div class="sparkline-bar success" style="height: 30%"></div>
|
||||
<div class="sparkline-bar success" style="height: 30%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 인스턴스 테이블 -->
|
||||
<div class="server-instances">
|
||||
<table class="instance-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>인스턴스 ID</th>
|
||||
<th>상태</th>
|
||||
<th class="text-right">처리량</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="instance-table-body">
|
||||
<!-- 인스턴스 데이터가 동적으로 추가됩니다 -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 인스턴스 행 템플릿 -->
|
||||
<template id="instance-row-template">
|
||||
<tr>
|
||||
<td>
|
||||
<span class="instance-status status-normal"></span>
|
||||
<span class="instance-id">인스턴스 ID</span>
|
||||
</td>
|
||||
<td class="instance-status-text">정상</td>
|
||||
<td class="text-right instance-throughput">0/s</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
/* 대시보드 헤더 스타일 */
|
||||
.dashboard-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.5rem;
|
||||
padding-bottom: 0.4rem;
|
||||
border-bottom: 1px solid var(--color-gray-200);
|
||||
}
|
||||
|
||||
.dashboard-header .section-title {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
border-bottom: none;
|
||||
flex-shrink: 0;
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
|
||||
.dashboard-header .server-filters {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.dashboard-header .filter-group {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dashboard-header .search-container {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* 서버 상태 섹션 */
|
||||
.server-section {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.server-section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.server-section-title {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-800);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.server-filters {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
display: flex;
|
||||
gap: 0.4rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.server-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.server-card {
|
||||
background-color: white;
|
||||
border-radius: var(--rounded-md);
|
||||
box-shadow: var(--shadow);
|
||||
border: 1px solid var(--color-gray-200);
|
||||
overflow: hidden;
|
||||
transition: all 0.2s ease;
|
||||
height: 100%;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.server-card:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.server-header {
|
||||
padding: 1rem 1.25rem;
|
||||
background-color: var(--color-gray-50);
|
||||
border-bottom: 1px solid var(--color-gray-200);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.server-name {
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
color: var(--color-gray-800);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.server-metrics {
|
||||
padding: 1rem 1.25rem;
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.server-metric {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.metric-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.metric-name {
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-gray-500);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.server-instances {
|
||||
padding: 0 16px 16px 16px;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
max-height: 150px;
|
||||
}
|
||||
|
||||
/* 스파크라인 스타일 */
|
||||
.sparkline {
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 2px;
|
||||
margin-top: 0.5rem;
|
||||
background-color: var(--color-gray-50);
|
||||
border-radius: var(--rounded-sm);
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
.sparkline-bar {
|
||||
flex: 1;
|
||||
background-color: var(--color-primary);
|
||||
border-radius: 1px 1px 0 0;
|
||||
}
|
||||
|
||||
.sparkline-bar.success {
|
||||
background-color: var(--color-success);
|
||||
}
|
||||
|
||||
.sparkline-bar.warning {
|
||||
background-color: var(--color-warning);
|
||||
}
|
||||
|
||||
.sparkline-bar.danger {
|
||||
background-color: var(--color-danger);
|
||||
}
|
||||
|
||||
/* 요약 섹션 */
|
||||
.status-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.status-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 1.5rem;
|
||||
border-radius: var(--rounded-lg);
|
||||
background-color: white;
|
||||
box-shadow: var(--shadow);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.status-card:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.status-card.success {
|
||||
border-left: 4px solid var(--color-success);
|
||||
}
|
||||
|
||||
.status-card.warning {
|
||||
border-left: 4px solid var(--color-warning);
|
||||
}
|
||||
|
||||
.status-card.danger {
|
||||
border-left: 4px solid var(--color-danger);
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
font-size: 2rem;
|
||||
margin-right: 1.25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status-icon.success {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.status-icon.warning {
|
||||
color: var(--color-warning);
|
||||
}
|
||||
|
||||
.status-icon.danger {
|
||||
color: var(--color-danger);
|
||||
}
|
||||
|
||||
.status-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-gray-500);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.status-value {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
}
|
||||
|
||||
/* 서버 섹션 스타일 - 기존 스타일 유지 */
|
||||
.server-section {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.server-card-container {
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
min-height: 296px;
|
||||
}
|
||||
|
||||
.server-section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.server-section-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-800);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 서버 타입별 보더 색상 */
|
||||
.border-primary {
|
||||
border-top: 2px solid #0d6efd;
|
||||
}
|
||||
|
||||
.border-success {
|
||||
border-top: 2px solid #198754;
|
||||
}
|
||||
|
||||
.border-purple {
|
||||
border-top: 2px solid #6f42c1;
|
||||
}
|
||||
|
||||
.border-orange {
|
||||
border-top: 2px solid #fd7e14;
|
||||
}
|
||||
|
||||
/* 서버 그리드 - 한 줄에 3개씩 표시 */
|
||||
.server-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
/* 반응형 처리 - 화면 크기에 따라 그리드 조정 */
|
||||
@media screen and (max-width: 1200px) {
|
||||
.server-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.server-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* 서버 카드 높이 축소 */
|
||||
.server-card {
|
||||
padding: 0.5rem;
|
||||
border: 1px solid var(--color-gray-200);
|
||||
border-radius: var(--rounded-sm);
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.server-header {
|
||||
padding: 0.4rem 0.5rem;
|
||||
margin-bottom: 0.3rem;
|
||||
border-bottom: 1px solid var(--color-gray-100);
|
||||
}
|
||||
|
||||
.server-name {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
/* 메트릭 스타일 조정 */
|
||||
.server-metrics {
|
||||
padding: 0.3rem 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.0rem;
|
||||
}
|
||||
|
||||
.metrics-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.sparklines-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.server-metric {
|
||||
flex: 1;
|
||||
min-width: 0; /* 오버플로우 방지 */
|
||||
}
|
||||
|
||||
.metric-header {
|
||||
margin-bottom: 0.2rem;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sparkline {
|
||||
flex: 1;
|
||||
min-width: 0; /* 오버플로우 방지 */
|
||||
}
|
||||
|
||||
.metric-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
/* 인스턴스 테이블 스타일 */
|
||||
.server-instances {
|
||||
padding: 0.3rem 0;
|
||||
max-height: 120px;
|
||||
}
|
||||
|
||||
.instance-table {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.instance-table th,
|
||||
.instance-table td {
|
||||
padding: 0.2rem 0.3rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 인스턴스 상태 점 스타일 */
|
||||
.instance-status {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
/* 오류 상태일 때 인스턴스 ID 색상 변경 */
|
||||
tr:has(.status-error) .instance-id,
|
||||
tr:has(.status-inactive) .instance-id {
|
||||
color: #dc3545;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// 서버 카드 생성 함수
|
||||
function createServerCard(server, containerId) {
|
||||
var template = document.getElementById('server-card-template');
|
||||
var container = document.getElementById(containerId);
|
||||
|
||||
if (!container) return null;
|
||||
|
||||
// 템플릿 복제
|
||||
var clone = document.importNode(template.content, true);
|
||||
|
||||
// 서버 이름 설정
|
||||
clone.querySelector('.server-name').textContent = server.text || server.name;
|
||||
|
||||
// 컨테이너에 추가
|
||||
container.appendChild(clone);
|
||||
|
||||
return container.lastElementChild;
|
||||
}
|
||||
|
||||
// 인스턴스 행 생성 함수
|
||||
function createInstanceRow(instanceData) {
|
||||
var template = document.getElementById('instance-row-template');
|
||||
var clone = document.importNode(template.content, true);
|
||||
|
||||
// 인스턴스 ID 설정
|
||||
clone.querySelector('.instance-id').textContent = instanceData.id;
|
||||
|
||||
// 상태 설정
|
||||
var statusClass = 'status-normal';
|
||||
var statusText = '정상';
|
||||
|
||||
if (instanceData.status === 'warning') {
|
||||
statusClass = 'status-warning';
|
||||
statusText = '경고';
|
||||
} else if (instanceData.status === 'error' || instanceData.status === 'inactive') {
|
||||
statusClass = 'status-error';
|
||||
statusText = instanceData.status === 'inactive' ? '비활성' : '오류';
|
||||
}
|
||||
|
||||
clone.querySelector('.instance-status').className = 'instance-status ' + statusClass;
|
||||
clone.querySelector('.instance-status-text').textContent = statusText;
|
||||
|
||||
// 처리량 설정
|
||||
if (instanceData.throughput) {
|
||||
clone.querySelector('.instance-throughput').textContent = instanceData.throughput;
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
// 서버 상태 업데이트 함수
|
||||
function updateServerStatus(data) {
|
||||
if (!data || !data.servers) return;
|
||||
|
||||
var normalCount = 0;
|
||||
var warningCount = 0;
|
||||
var errorCount = 0;
|
||||
|
||||
// 서버 타입별 상태 카운트
|
||||
var typeStatusCount = {};
|
||||
|
||||
// 각 서버 상태 업데이트
|
||||
data.servers.forEach(function(server) {
|
||||
// 서버 타입 추출 (서버 이름에서 괄호 안의 텍스트 추출)
|
||||
var serverTypeMatch = server.text.match(/\(([^)]+)\)/);
|
||||
var serverType = serverTypeMatch ? serverTypeMatch[1] : "unknown";
|
||||
|
||||
// 서버 이름에서 타입 정보 제거
|
||||
var displayName = server.name;
|
||||
|
||||
// 서버 상태
|
||||
var serverStatus = server.status;
|
||||
|
||||
// 서버 타입별 상태 카운트 초기화
|
||||
if (!typeStatusCount[serverType]) {
|
||||
typeStatusCount[serverType] = {
|
||||
normal: 0,
|
||||
warning: 0,
|
||||
error: 0,
|
||||
total: 0
|
||||
};
|
||||
}
|
||||
|
||||
typeStatusCount[serverType].total++;
|
||||
|
||||
// 전체 상태 카운트 업데이트
|
||||
if (serverStatus === 'normal') {
|
||||
normalCount++;
|
||||
typeStatusCount[serverType].normal++;
|
||||
} else if (serverStatus === 'warning') {
|
||||
warningCount++;
|
||||
typeStatusCount[serverType].warning++;
|
||||
} else if (serverStatus === 'error') {
|
||||
errorCount++;
|
||||
typeStatusCount[serverType].error++;
|
||||
}
|
||||
|
||||
// 서버 타입 소문자로 변환 (ID용)
|
||||
var serverTypeId = serverType.toLowerCase().replace(/\s+/g, '-');
|
||||
|
||||
// 서버 카드 찾기 및 업데이트
|
||||
var serverContainerId = serverTypeId + '-servers-container';
|
||||
var serverContainer = document.getElementById(serverContainerId);
|
||||
|
||||
if (!serverContainer) return;
|
||||
|
||||
// 서버 카드가 없으면 생성
|
||||
var serverCard = document.querySelector('#' + serverContainerId + ' .server-card[data-server-name="' + server.name + '"]');
|
||||
if (!serverCard) {
|
||||
serverCard = createServerCard(server, serverContainerId);
|
||||
if (serverCard) {
|
||||
serverCard.setAttribute('data-server-name', server.name);
|
||||
}
|
||||
}
|
||||
|
||||
if (!serverCard) return;
|
||||
|
||||
// 서버 이름 업데이트
|
||||
var serverNameElement = serverCard.querySelector('.server-name');
|
||||
if (serverNameElement) {
|
||||
serverNameElement.textContent = displayName;
|
||||
}
|
||||
|
||||
// 서버 상태 업데이트
|
||||
var statusBadge = serverCard.querySelector('.server-status-badge');
|
||||
statusBadge.className = 'server-status-badge';
|
||||
|
||||
if (serverStatus === 'normal') {
|
||||
statusBadge.classList.add('badge-success');
|
||||
statusBadge.textContent = '정상';
|
||||
} else if (serverStatus === 'warning') {
|
||||
statusBadge.classList.add('badge-warning');
|
||||
statusBadge.textContent = '경고';
|
||||
} else if (serverStatus === 'error') {
|
||||
statusBadge.classList.add('badge-danger');
|
||||
statusBadge.textContent = '오류';
|
||||
}
|
||||
|
||||
// CPU, 메모리, 디스크 업데이트
|
||||
updateResourceValue(serverCard, 'cpu', server.cpu);
|
||||
updateResourceValue(serverCard, 'memory', server.memory);
|
||||
updateResourceValue(serverCard, 'disk', server.disk);
|
||||
|
||||
// 인스턴스 테이블 업데이트
|
||||
var instanceTableBody = serverCard.querySelector('.instance-table-body');
|
||||
instanceTableBody.innerHTML = '';
|
||||
|
||||
if (server.instances && server.instances.length > 0) {
|
||||
server.instances.forEach(function(instance) {
|
||||
instanceTableBody.appendChild(createInstanceRow(instance));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 요약 정보 업데이트
|
||||
if (document.getElementById('normal-servers')) {
|
||||
document.getElementById('normal-servers').textContent = normalCount;
|
||||
}
|
||||
if (document.getElementById('warning-servers')) {
|
||||
document.getElementById('warning-servers').textContent = warningCount;
|
||||
}
|
||||
if (document.getElementById('error-servers')) {
|
||||
document.getElementById('error-servers').textContent = errorCount;
|
||||
}
|
||||
|
||||
// 서버 타입별 상태 배지 업데이트
|
||||
for (var type in typeStatusCount) {
|
||||
var typeId = type.toLowerCase().replace(/\s+/g, '-');
|
||||
var badgeElement = document.querySelector('.' + typeId + '-status-badge');
|
||||
if (badgeElement) {
|
||||
var countData = typeStatusCount[type];
|
||||
badgeElement.textContent = countData.normal + ' / ' + countData.total + ' 정상';
|
||||
badgeElement.className = typeId + '-status-badge server-status-badge';
|
||||
|
||||
if (countData.error > 0) {
|
||||
badgeElement.classList.add('badge-danger');
|
||||
} else if (countData.warning > 0) {
|
||||
badgeElement.classList.add('badge-warning');
|
||||
} else {
|
||||
badgeElement.classList.add('badge-success');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 리소스 값 업데이트 함수 (CPU, 메모리, 디스크)
|
||||
function updateResourceValue(serverCard, resourceType, value) {
|
||||
var valueElement = serverCard.querySelector('.' + resourceType + '-value');
|
||||
if (!valueElement) return;
|
||||
|
||||
valueElement.textContent = value + '%';
|
||||
valueElement.className = 'metric-value ' + resourceType + '-value';
|
||||
|
||||
// 값에 따라 클래스 추가
|
||||
if (value >= 90) {
|
||||
valueElement.classList.add('danger');
|
||||
} else if (value >= 70) {
|
||||
valueElement.classList.add('warning');
|
||||
} else {
|
||||
valueElement.classList.add('success');
|
||||
}
|
||||
|
||||
// 스파크라인 업데이트
|
||||
var sparkline = serverCard.querySelector('.' + resourceType + '-sparkline');
|
||||
if (!sparkline) return;
|
||||
|
||||
// 서버 이름 가져오기
|
||||
var serverName = serverCard.getAttribute('data-server-name');
|
||||
if (!serverName) return;
|
||||
|
||||
// 서버 리소스 시계열 데이터 가져오기
|
||||
ModernDashboardApi.getServerResourceData(serverName, function(data) {
|
||||
if (data && data.data) {
|
||||
var resourceData = data.data;
|
||||
var resourceValues = resourceData[resourceType];
|
||||
|
||||
// 스파크라인 업데이트
|
||||
sparkline.innerHTML = '';
|
||||
|
||||
if (resourceValues && resourceValues.length > 0) {
|
||||
resourceValues.forEach(function(val) {
|
||||
var bar = document.createElement('div');
|
||||
bar.className = 'sparkline-bar';
|
||||
bar.style.height = val + '%';
|
||||
|
||||
// 값에 따라 클래스 추가
|
||||
if (val >= 90) {
|
||||
bar.classList.add('danger');
|
||||
} else if (val >= 70) {
|
||||
bar.classList.add('warning');
|
||||
} else {
|
||||
bar.classList.add('success');
|
||||
}
|
||||
|
||||
sparkline.appendChild(bar);
|
||||
});
|
||||
} else {
|
||||
// 데이터가 없는 경우 기본 스파크라인 생성
|
||||
for (var i = 0; i < 7; i++) {
|
||||
var bar = document.createElement('div');
|
||||
bar.className = 'sparkline-bar success';
|
||||
bar.style.height = value + '%';
|
||||
sparkline.appendChild(bar);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// API 응답이 없는 경우 기본 스파크라인 생성
|
||||
sparkline.innerHTML = '';
|
||||
for (var i = 0; i < 7; i++) {
|
||||
var bar = document.createElement('div');
|
||||
bar.className = 'sparkline-bar success';
|
||||
bar.style.height = value + '%';
|
||||
sparkline.appendChild(bar);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 서버 데이터 로드 함수
|
||||
function loadServerData() {
|
||||
console.log("서버 데이터 로드 시작: " + new Date().toLocaleTimeString());
|
||||
ModernDashboardApi.getServerStatus(null, function(data) {
|
||||
updateServerStatus(data);
|
||||
console.log("서버 데이터 로드 완료: " + new Date().toLocaleTimeString());
|
||||
});
|
||||
}
|
||||
|
||||
// 서버 필터 버튼 클릭 이벤트
|
||||
$('.filter-btn').click(function() {
|
||||
var type = $(this).data('type');
|
||||
|
||||
if (type === 'all') {
|
||||
// 전체 표시
|
||||
$('.server-section').show();
|
||||
$('.filter-btn').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
$(".tran-filter-btn[data-type='all']").click();
|
||||
} else {
|
||||
// 필터링
|
||||
$('.server-section').hide();
|
||||
$('#server-section-' + type).show();
|
||||
$('.filter-btn').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
$(".tran-filter-btn[data-type='" + type + "']").click();
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
// 서버 검색 기능
|
||||
$('#server-search').on('input', function() {
|
||||
var searchText = $(this).val().toLowerCase();
|
||||
|
||||
if (searchText.length > 0) {
|
||||
$('.server-card').each(function() {
|
||||
var serverName = $(this).find('.server-name').text().toLowerCase();
|
||||
if (serverName.indexOf(searchText) > -1) {
|
||||
$(this).show();
|
||||
} else {
|
||||
$(this).hide();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$('.server-card').show();
|
||||
}
|
||||
});
|
||||
|
||||
// 초기 로드
|
||||
$(document).ready(function() {
|
||||
// 서버 데이터 로드
|
||||
loadServerData();
|
||||
|
||||
// 이벤트 리스너 등록 - 중앙 이벤트 관리자 사용
|
||||
if (window.DashboardEventManager) {
|
||||
// 초기화 이벤트 리스너
|
||||
window.DashboardEventManager.on('init', function() {
|
||||
console.log("서버 상태 초기화 이벤트 수신");
|
||||
// 이미 초기화되었으므로 추가 작업 필요 없음
|
||||
});
|
||||
|
||||
// 새로고침 이벤트 리스너
|
||||
window.DashboardEventManager.on('refresh', function() {
|
||||
console.log("서버 상태 새로고침 이벤트 수신");
|
||||
loadServerData();
|
||||
});
|
||||
} else {
|
||||
console.error("DashboardEventManager가 정의되지 않았습니다. modern-dashboard.js가 먼저 로드되었는지 확인하세요.");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user