980 lines
33 KiB
Plaintext
980 lines
33 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-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<String, Integer>();
|
|
priorityMap.put("MCI", 0);
|
|
priorityMap.put("EAI", 1);
|
|
priorityMap.put("FEP", 2);
|
|
priorityMap.put("CGW", 3);
|
|
priorityMap.put("FGW", 4);
|
|
priorityMap.put("APIGW", 5);
|
|
priorityMap.put("BAP", 6);
|
|
|
|
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;
|
|
}
|
|
|
|
/* 서버 그리드 - 한 줄에 2개씩 표시 */
|
|
.server-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 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.5rem 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.0rem;
|
|
border-top: 1px solid var(--color-gray-200);
|
|
border-bottom: 1px solid var(--color-gray-200);
|
|
}
|
|
|
|
.metrics-row,
|
|
.sparklines-row {
|
|
display: flex;
|
|
justify-content: space-around;
|
|
gap: 0;
|
|
}
|
|
|
|
.sparklines-row {
|
|
padding-top: 0.5rem;
|
|
}
|
|
|
|
.server-metric {
|
|
flex: 1;
|
|
min-width: 0; /* 오버플로우 방지 */
|
|
text-align: center;
|
|
padding: 0 0.5rem;
|
|
border-right: 1px solid var(--color-gray-200);
|
|
}
|
|
|
|
.server-metric:last-child {
|
|
border-right: none;
|
|
}
|
|
|
|
.metric-header {
|
|
margin-bottom: 0.2rem;
|
|
display: flex;
|
|
flex-direction: column; /* 아이콘과 값을 수직으로 정렬 */
|
|
justify-content: center; /* 수직 중앙 정렬 */
|
|
align-items: center; /* 수평 중앙 정렬 */
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.sparkline {
|
|
flex: 1;
|
|
min-width: 0;
|
|
padding: 0.25rem 0.5rem;
|
|
border-right: 1px solid var(--color-gray-200);
|
|
}
|
|
|
|
.sparkline:last-child {
|
|
border-right: none;
|
|
}
|
|
|
|
.metric-name {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-bottom: 0.2rem; /* 아이콘과 값 사이 간격 조정 */
|
|
}
|
|
|
|
.metric-value {
|
|
margin-left: 0; /* 이전 margin-left 제거 */
|
|
font-size: 1.1rem; /* 값 폰트 크기 약간 키움 */
|
|
font-weight: 600; /* 값 폰트 굵기 */
|
|
}
|
|
|
|
/* 인스턴스 테이블 스타일 */
|
|
.server-instances {
|
|
padding: 0.5rem 0.5rem 0.5rem 0.5rem; /* 패딩 조정 */
|
|
max-height: 120px;
|
|
}
|
|
|
|
.instance-table {
|
|
width: 100%; /* 테이블 전체 너비 */
|
|
font-size: 0.85rem; /* 폰트 크기 약간 키움 */
|
|
border-collapse: collapse;
|
|
table-layout: fixed;
|
|
}
|
|
|
|
.instance-table th {
|
|
background-color: var(--color-gray-100); /* 헤더 배경색 */
|
|
font-weight: 600; /* 헤더 폰트 굵기 */
|
|
padding: 0.4rem 0.5rem; /* 헤더 패딩 조정 */
|
|
border-bottom: 1px solid var(--color-gray-200); /* 헤더 아래 보더 */
|
|
text-align: center; /* 모든 헤더 가운데 정렬 */
|
|
|
|
width: 33.3333%; /* 인스턴스 th 각 영역 균등하게, 위쪽 요소와 맞춤 */
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.instance-table td {
|
|
padding: 0.4rem 0.5rem; /* 셀 패딩 조정 */
|
|
white-space: nowrap;
|
|
border-bottom: 1px solid var(--color-gray-100); /* 셀 아래 보더 */
|
|
vertical-align: middle;
|
|
text-align: center; /* 모든 셀 내용 가운데 정렬 */
|
|
white-space: nowrap; /* 줄바꿈 방지 */
|
|
overflow: hidden; /* 칸을 넘어가는 글자 숨김 */
|
|
text-overflow: ellipsis; /* 넘어가는 글자를 ...으로 표시 */
|
|
}
|
|
|
|
.instance-table tbody tr:nth-child(odd) {
|
|
background-color: var(--color-gray-50); /* 홀수 행 배경색 */
|
|
}
|
|
|
|
.instance-table tbody tr:hover {
|
|
background-color: var(--color-gray-100); /* 호버 효과 */
|
|
}
|
|
|
|
/* 인스턴스 상태 점 스타일 */
|
|
.instance-status {
|
|
display: inline-block;
|
|
width: 10px; /* 크기 키움 */
|
|
height: 10px; /* 크기 키움 */
|
|
border-radius: 50%;
|
|
margin-right: 6px; /* 간격 조정 */
|
|
vertical-align: middle; /* 텍스트와 세로 정렬 */
|
|
}
|
|
|
|
/* 오류 상태일 때 인스턴스 ID 색상 변경 - :has()는 구형 브라우저에서 문제를 일으킬 수 있어 주석 처리
|
|
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> |