모던 대시보드 패치
- ApexCharts 기반 실시간 모니터링 대시보드 - 어댑터/배치/서버/소켓/트랜잭션 모니터링 컴포넌트 - 처리량 계산 및 데이터 매핑 서비스
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -0,0 +1,21 @@
|
||||
/*!
|
||||
* @svgdotjs/svg.resize.js - An extension for svg.js which allows to resize elements which are selected
|
||||
* @version 2.0.4
|
||||
* https://github.com/svgdotjs/svg.resize.js
|
||||
*
|
||||
* @copyright [object Object]
|
||||
* @license MIT
|
||||
*
|
||||
* BUILT: Fri Sep 13 2024 12:43:14 GMT+0200 (Central European Summer Time)
|
||||
*/
|
||||
|
||||
/*!
|
||||
* @svgdotjs/svg.select.js - An extension of svg.js which allows to select elements with mouse
|
||||
* @version 4.0.1
|
||||
* https://github.com/svgdotjs/svg.select.js
|
||||
*
|
||||
* @copyright Ulrich-Matthias Schäfer
|
||||
* @license MIT
|
||||
*
|
||||
* BUILT: Mon Jul 01 2024 15:04:42 GMT+0200 (Central European Summer Time)
|
||||
*/
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,679 @@
|
||||
@keyframes opaque {
|
||||
0% {
|
||||
opacity: 0
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes resizeanim {
|
||||
|
||||
0%,
|
||||
to {
|
||||
opacity: 0
|
||||
}
|
||||
}
|
||||
|
||||
.apexcharts-canvas {
|
||||
position: relative;
|
||||
direction: ltr !important;
|
||||
user-select: none
|
||||
}
|
||||
|
||||
.apexcharts-canvas ::-webkit-scrollbar {
|
||||
-webkit-appearance: none;
|
||||
width: 6px
|
||||
}
|
||||
|
||||
.apexcharts-canvas ::-webkit-scrollbar-thumb {
|
||||
border-radius: 4px;
|
||||
background-color: rgba(0, 0, 0, .5);
|
||||
box-shadow: 0 0 1px rgba(255, 255, 255, .5);
|
||||
-webkit-box-shadow: 0 0 1px rgba(255, 255, 255, .5)
|
||||
}
|
||||
|
||||
.apexcharts-inner {
|
||||
position: relative
|
||||
}
|
||||
|
||||
.apexcharts-text tspan {
|
||||
font-family: inherit
|
||||
}
|
||||
|
||||
rect.legend-mouseover-inactive,
|
||||
.legend-mouseover-inactive rect,
|
||||
.legend-mouseover-inactive path,
|
||||
.legend-mouseover-inactive circle,
|
||||
.legend-mouseover-inactive line,
|
||||
.legend-mouseover-inactive text.apexcharts-yaxis-title-text,
|
||||
.legend-mouseover-inactive text.apexcharts-yaxis-label {
|
||||
transition: .15s ease all;
|
||||
opacity: .2
|
||||
}
|
||||
|
||||
.apexcharts-legend-text {
|
||||
padding-left: 15px;
|
||||
margin-left: -15px;
|
||||
}
|
||||
|
||||
.apexcharts-series-collapsed {
|
||||
opacity: 0
|
||||
}
|
||||
|
||||
.apexcharts-tooltip {
|
||||
border-radius: 5px;
|
||||
box-shadow: 2px 2px 6px -4px #999;
|
||||
cursor: default;
|
||||
font-size: 14px;
|
||||
left: 62px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
z-index: 12;
|
||||
transition: .15s ease all
|
||||
}
|
||||
|
||||
.apexcharts-tooltip.apexcharts-active {
|
||||
opacity: 1;
|
||||
transition: .15s ease all
|
||||
}
|
||||
|
||||
.apexcharts-tooltip.apexcharts-theme-light {
|
||||
border: 1px solid #e3e3e3;
|
||||
background: rgba(255, 255, 255, .96)
|
||||
}
|
||||
|
||||
.apexcharts-tooltip.apexcharts-theme-dark {
|
||||
color: #fff;
|
||||
background: rgba(30, 30, 30, .8)
|
||||
}
|
||||
|
||||
.apexcharts-tooltip * {
|
||||
font-family: inherit
|
||||
}
|
||||
|
||||
.apexcharts-tooltip-title {
|
||||
padding: 6px;
|
||||
font-size: 15px;
|
||||
margin-bottom: 4px
|
||||
}
|
||||
|
||||
.apexcharts-tooltip.apexcharts-theme-light .apexcharts-tooltip-title {
|
||||
background: #eceff1;
|
||||
border-bottom: 1px solid #ddd
|
||||
}
|
||||
|
||||
.apexcharts-tooltip.apexcharts-theme-dark .apexcharts-tooltip-title {
|
||||
background: rgba(0, 0, 0, .7);
|
||||
border-bottom: 1px solid #333
|
||||
}
|
||||
|
||||
.apexcharts-tooltip-text-goals-value,
|
||||
.apexcharts-tooltip-text-y-value,
|
||||
.apexcharts-tooltip-text-z-value {
|
||||
display: inline-block;
|
||||
margin-left: 5px;
|
||||
font-weight: 600
|
||||
}
|
||||
|
||||
.apexcharts-tooltip-text-goals-label:empty,
|
||||
.apexcharts-tooltip-text-goals-value:empty,
|
||||
.apexcharts-tooltip-text-y-label:empty,
|
||||
.apexcharts-tooltip-text-y-value:empty,
|
||||
.apexcharts-tooltip-text-z-value:empty,
|
||||
.apexcharts-tooltip-title:empty {
|
||||
display: none
|
||||
}
|
||||
|
||||
.apexcharts-tooltip-text-goals-label,
|
||||
.apexcharts-tooltip-text-goals-value {
|
||||
padding: 6px 0 5px
|
||||
}
|
||||
|
||||
.apexcharts-tooltip-goals-group,
|
||||
.apexcharts-tooltip-text-goals-label,
|
||||
.apexcharts-tooltip-text-goals-value {
|
||||
display: flex
|
||||
}
|
||||
|
||||
.apexcharts-tooltip-text-goals-label:not(:empty),
|
||||
.apexcharts-tooltip-text-goals-value:not(:empty) {
|
||||
margin-top: -6px
|
||||
}
|
||||
|
||||
.apexcharts-tooltip-marker {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
position: relative;
|
||||
top: 0;
|
||||
margin-right: 10px;
|
||||
border-radius: 50%
|
||||
}
|
||||
|
||||
.apexcharts-tooltip-series-group {
|
||||
padding: 0 10px;
|
||||
display: none;
|
||||
text-align: left;
|
||||
justify-content: left;
|
||||
align-items: center
|
||||
}
|
||||
|
||||
.apexcharts-tooltip-series-group.apexcharts-active .apexcharts-tooltip-marker {
|
||||
opacity: 1
|
||||
}
|
||||
|
||||
.apexcharts-tooltip-series-group.apexcharts-active,
|
||||
.apexcharts-tooltip-series-group:last-child {
|
||||
padding-bottom: 4px
|
||||
}
|
||||
|
||||
.apexcharts-tooltip-y-group {
|
||||
padding: 6px 0 5px
|
||||
}
|
||||
|
||||
.apexcharts-custom-tooltip,
|
||||
.apexcharts-tooltip-box {
|
||||
padding: 4px 8px
|
||||
}
|
||||
|
||||
.apexcharts-tooltip-boxPlot {
|
||||
display: flex;
|
||||
flex-direction: column-reverse
|
||||
}
|
||||
|
||||
.apexcharts-tooltip-box>div {
|
||||
margin: 4px 0
|
||||
}
|
||||
|
||||
.apexcharts-tooltip-box span.value {
|
||||
font-weight: 700
|
||||
}
|
||||
|
||||
.apexcharts-tooltip-rangebar {
|
||||
padding: 5px 8px
|
||||
}
|
||||
|
||||
.apexcharts-tooltip-rangebar .category {
|
||||
font-weight: 600;
|
||||
color: #777
|
||||
}
|
||||
|
||||
.apexcharts-tooltip-rangebar .series-name {
|
||||
font-weight: 700;
|
||||
display: block;
|
||||
margin-bottom: 5px
|
||||
}
|
||||
|
||||
.apexcharts-xaxistooltip,
|
||||
.apexcharts-yaxistooltip {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
color: #373d3f;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
border-radius: 2px;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
background: #eceff1;
|
||||
border: 1px solid #90a4ae
|
||||
}
|
||||
|
||||
.apexcharts-xaxistooltip {
|
||||
padding: 9px 10px;
|
||||
transition: .15s ease all
|
||||
}
|
||||
|
||||
.apexcharts-xaxistooltip.apexcharts-theme-dark {
|
||||
background: rgba(0, 0, 0, .7);
|
||||
border: 1px solid rgba(0, 0, 0, .5);
|
||||
color: #fff
|
||||
}
|
||||
|
||||
.apexcharts-xaxistooltip:after,
|
||||
.apexcharts-xaxistooltip:before {
|
||||
left: 50%;
|
||||
border: solid transparent;
|
||||
content: " ";
|
||||
height: 0;
|
||||
width: 0;
|
||||
position: absolute;
|
||||
pointer-events: none
|
||||
}
|
||||
|
||||
.apexcharts-xaxistooltip:after {
|
||||
border-color: transparent;
|
||||
border-width: 6px;
|
||||
margin-left: -6px
|
||||
}
|
||||
|
||||
.apexcharts-xaxistooltip:before {
|
||||
border-color: transparent;
|
||||
border-width: 7px;
|
||||
margin-left: -7px
|
||||
}
|
||||
|
||||
.apexcharts-xaxistooltip-bottom:after,
|
||||
.apexcharts-xaxistooltip-bottom:before {
|
||||
bottom: 100%
|
||||
}
|
||||
|
||||
.apexcharts-xaxistooltip-top:after,
|
||||
.apexcharts-xaxistooltip-top:before {
|
||||
top: 100%
|
||||
}
|
||||
|
||||
.apexcharts-xaxistooltip-bottom:after {
|
||||
border-bottom-color: #eceff1
|
||||
}
|
||||
|
||||
.apexcharts-xaxistooltip-bottom:before {
|
||||
border-bottom-color: #90a4ae
|
||||
}
|
||||
|
||||
.apexcharts-xaxistooltip-bottom.apexcharts-theme-dark:after,
|
||||
.apexcharts-xaxistooltip-bottom.apexcharts-theme-dark:before {
|
||||
border-bottom-color: rgba(0, 0, 0, .5)
|
||||
}
|
||||
|
||||
.apexcharts-xaxistooltip-top:after {
|
||||
border-top-color: #eceff1
|
||||
}
|
||||
|
||||
.apexcharts-xaxistooltip-top:before {
|
||||
border-top-color: #90a4ae
|
||||
}
|
||||
|
||||
.apexcharts-xaxistooltip-top.apexcharts-theme-dark:after,
|
||||
.apexcharts-xaxistooltip-top.apexcharts-theme-dark:before {
|
||||
border-top-color: rgba(0, 0, 0, .5)
|
||||
}
|
||||
|
||||
.apexcharts-xaxistooltip.apexcharts-active {
|
||||
opacity: 1;
|
||||
transition: .15s ease all
|
||||
}
|
||||
|
||||
.apexcharts-yaxistooltip {
|
||||
padding: 4px 10px
|
||||
}
|
||||
|
||||
.apexcharts-yaxistooltip.apexcharts-theme-dark {
|
||||
background: rgba(0, 0, 0, .7);
|
||||
border: 1px solid rgba(0, 0, 0, .5);
|
||||
color: #fff
|
||||
}
|
||||
|
||||
.apexcharts-yaxistooltip:after,
|
||||
.apexcharts-yaxistooltip:before {
|
||||
top: 50%;
|
||||
border: solid transparent;
|
||||
content: " ";
|
||||
height: 0;
|
||||
width: 0;
|
||||
position: absolute;
|
||||
pointer-events: none
|
||||
}
|
||||
|
||||
.apexcharts-yaxistooltip:after {
|
||||
border-color: transparent;
|
||||
border-width: 6px;
|
||||
margin-top: -6px
|
||||
}
|
||||
|
||||
.apexcharts-yaxistooltip:before {
|
||||
border-color: transparent;
|
||||
border-width: 7px;
|
||||
margin-top: -7px
|
||||
}
|
||||
|
||||
.apexcharts-yaxistooltip-left:after,
|
||||
.apexcharts-yaxistooltip-left:before {
|
||||
left: 100%
|
||||
}
|
||||
|
||||
.apexcharts-yaxistooltip-right:after,
|
||||
.apexcharts-yaxistooltip-right:before {
|
||||
right: 100%
|
||||
}
|
||||
|
||||
.apexcharts-yaxistooltip-left:after {
|
||||
border-left-color: #eceff1
|
||||
}
|
||||
|
||||
.apexcharts-yaxistooltip-left:before {
|
||||
border-left-color: #90a4ae
|
||||
}
|
||||
|
||||
.apexcharts-yaxistooltip-left.apexcharts-theme-dark:after,
|
||||
.apexcharts-yaxistooltip-left.apexcharts-theme-dark:before {
|
||||
border-left-color: rgba(0, 0, 0, .5)
|
||||
}
|
||||
|
||||
.apexcharts-yaxistooltip-right:after {
|
||||
border-right-color: #eceff1
|
||||
}
|
||||
|
||||
.apexcharts-yaxistooltip-right:before {
|
||||
border-right-color: #90a4ae
|
||||
}
|
||||
|
||||
.apexcharts-yaxistooltip-right.apexcharts-theme-dark:after,
|
||||
.apexcharts-yaxistooltip-right.apexcharts-theme-dark:before {
|
||||
border-right-color: rgba(0, 0, 0, .5)
|
||||
}
|
||||
|
||||
.apexcharts-yaxistooltip.apexcharts-active {
|
||||
opacity: 1
|
||||
}
|
||||
|
||||
.apexcharts-yaxistooltip-hidden {
|
||||
display: none
|
||||
}
|
||||
|
||||
.apexcharts-xcrosshairs,
|
||||
.apexcharts-ycrosshairs {
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: .15s ease all
|
||||
}
|
||||
|
||||
.apexcharts-xcrosshairs.apexcharts-active,
|
||||
.apexcharts-ycrosshairs.apexcharts-active {
|
||||
opacity: 1;
|
||||
transition: .15s ease all
|
||||
}
|
||||
|
||||
.apexcharts-ycrosshairs-hidden {
|
||||
opacity: 0
|
||||
}
|
||||
|
||||
.apexcharts-selection-rect {
|
||||
cursor: move
|
||||
}
|
||||
|
||||
.svg_select_shape {
|
||||
stroke-width: 1;
|
||||
stroke-dasharray: 10 10;
|
||||
stroke: black;
|
||||
stroke-opacity: 0.1;
|
||||
pointer-events: none;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.svg_select_handle {
|
||||
stroke-width: 3;
|
||||
stroke: black;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.svg_select_handle_r {
|
||||
cursor: e-resize;
|
||||
}
|
||||
|
||||
.svg_select_handle_l {
|
||||
cursor: w-resize;
|
||||
}
|
||||
|
||||
.apexcharts-svg.apexcharts-zoomable.hovering-zoom {
|
||||
cursor: crosshair
|
||||
}
|
||||
|
||||
.apexcharts-svg.apexcharts-zoomable.hovering-pan {
|
||||
cursor: move
|
||||
}
|
||||
|
||||
.apexcharts-menu-icon,
|
||||
.apexcharts-pan-icon,
|
||||
.apexcharts-reset-icon,
|
||||
.apexcharts-selection-icon,
|
||||
.apexcharts-toolbar-custom-icon,
|
||||
.apexcharts-zoom-icon,
|
||||
.apexcharts-zoomin-icon,
|
||||
.apexcharts-zoomout-icon {
|
||||
cursor: pointer;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 24px;
|
||||
color: #6e8192;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
.apexcharts-menu-icon svg,
|
||||
.apexcharts-reset-icon svg,
|
||||
.apexcharts-zoom-icon svg,
|
||||
.apexcharts-zoomin-icon svg,
|
||||
.apexcharts-zoomout-icon svg {
|
||||
fill: #6e8192
|
||||
}
|
||||
|
||||
.apexcharts-selection-icon svg {
|
||||
fill: #444;
|
||||
transform: scale(.76)
|
||||
}
|
||||
|
||||
.apexcharts-theme-dark .apexcharts-menu-icon svg,
|
||||
.apexcharts-theme-dark .apexcharts-pan-icon svg,
|
||||
.apexcharts-theme-dark .apexcharts-reset-icon svg,
|
||||
.apexcharts-theme-dark .apexcharts-selection-icon svg,
|
||||
.apexcharts-theme-dark .apexcharts-toolbar-custom-icon svg,
|
||||
.apexcharts-theme-dark .apexcharts-zoom-icon svg,
|
||||
.apexcharts-theme-dark .apexcharts-zoomin-icon svg,
|
||||
.apexcharts-theme-dark .apexcharts-zoomout-icon svg {
|
||||
fill: #f3f4f5
|
||||
}
|
||||
|
||||
.apexcharts-canvas .apexcharts-reset-zoom-icon.apexcharts-selected svg,
|
||||
.apexcharts-canvas .apexcharts-selection-icon.apexcharts-selected svg,
|
||||
.apexcharts-canvas .apexcharts-zoom-icon.apexcharts-selected svg {
|
||||
fill: #008ffb
|
||||
}
|
||||
|
||||
.apexcharts-theme-light .apexcharts-menu-icon:hover svg,
|
||||
.apexcharts-theme-light .apexcharts-reset-icon:hover svg,
|
||||
.apexcharts-theme-light .apexcharts-selection-icon:not(.apexcharts-selected):hover svg,
|
||||
.apexcharts-theme-light .apexcharts-zoom-icon:not(.apexcharts-selected):hover svg,
|
||||
.apexcharts-theme-light .apexcharts-zoomin-icon:hover svg,
|
||||
.apexcharts-theme-light .apexcharts-zoomout-icon:hover svg {
|
||||
fill: #333
|
||||
}
|
||||
|
||||
.apexcharts-menu-icon,
|
||||
.apexcharts-selection-icon {
|
||||
position: relative
|
||||
}
|
||||
|
||||
.apexcharts-reset-icon {
|
||||
margin-left: 5px
|
||||
}
|
||||
|
||||
.apexcharts-menu-icon,
|
||||
.apexcharts-reset-icon,
|
||||
.apexcharts-zoom-icon {
|
||||
transform: scale(.85)
|
||||
}
|
||||
|
||||
.apexcharts-zoomin-icon,
|
||||
.apexcharts-zoomout-icon {
|
||||
transform: scale(.7)
|
||||
}
|
||||
|
||||
.apexcharts-zoomout-icon {
|
||||
margin-right: 3px
|
||||
}
|
||||
|
||||
.apexcharts-pan-icon {
|
||||
transform: scale(.62);
|
||||
position: relative;
|
||||
left: 1px;
|
||||
top: 0
|
||||
}
|
||||
|
||||
.apexcharts-pan-icon svg {
|
||||
fill: #fff;
|
||||
stroke: #6e8192;
|
||||
stroke-width: 2
|
||||
}
|
||||
|
||||
.apexcharts-pan-icon.apexcharts-selected svg {
|
||||
stroke: #008ffb
|
||||
}
|
||||
|
||||
.apexcharts-pan-icon:not(.apexcharts-selected):hover svg {
|
||||
stroke: #333
|
||||
}
|
||||
|
||||
.apexcharts-toolbar {
|
||||
position: absolute;
|
||||
z-index: 11;
|
||||
max-width: 176px;
|
||||
text-align: right;
|
||||
border-radius: 3px;
|
||||
padding: 0 6px 2px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center
|
||||
}
|
||||
|
||||
.apexcharts-menu {
|
||||
background: #fff;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 3px;
|
||||
padding: 3px;
|
||||
right: 10px;
|
||||
opacity: 0;
|
||||
min-width: 110px;
|
||||
transition: .15s ease all;
|
||||
pointer-events: none
|
||||
}
|
||||
|
||||
.apexcharts-menu.apexcharts-menu-open {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
transition: .15s ease all
|
||||
}
|
||||
|
||||
.apexcharts-menu-item {
|
||||
padding: 6px 7px;
|
||||
font-size: 12px;
|
||||
cursor: pointer
|
||||
}
|
||||
|
||||
.apexcharts-theme-light .apexcharts-menu-item:hover {
|
||||
background: #eee
|
||||
}
|
||||
|
||||
.apexcharts-theme-dark .apexcharts-menu {
|
||||
background: rgba(0, 0, 0, .7);
|
||||
color: #fff
|
||||
}
|
||||
|
||||
@media screen and (min-width:768px) {
|
||||
.apexcharts-canvas:hover .apexcharts-toolbar {
|
||||
opacity: 1
|
||||
}
|
||||
}
|
||||
|
||||
.apexcharts-canvas .apexcharts-element-hidden,
|
||||
.apexcharts-datalabel.apexcharts-element-hidden,
|
||||
.apexcharts-hide .apexcharts-series-points {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.apexcharts-hidden-element-shown {
|
||||
opacity: 1;
|
||||
transition: 0.25s ease all;
|
||||
}
|
||||
|
||||
.apexcharts-datalabel,
|
||||
.apexcharts-datalabel-label,
|
||||
.apexcharts-datalabel-value,
|
||||
.apexcharts-datalabels,
|
||||
.apexcharts-pie-label {
|
||||
cursor: default;
|
||||
pointer-events: none
|
||||
}
|
||||
|
||||
.apexcharts-pie-label-delay {
|
||||
opacity: 0;
|
||||
animation-name: opaque;
|
||||
animation-duration: .3s;
|
||||
animation-fill-mode: forwards;
|
||||
animation-timing-function: ease
|
||||
}
|
||||
|
||||
.apexcharts-radialbar-label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.apexcharts-annotation-rect,
|
||||
.apexcharts-area-series .apexcharts-area,
|
||||
.apexcharts-gridline,
|
||||
.apexcharts-line,
|
||||
.apexcharts-point-annotation-label,
|
||||
.apexcharts-radar-series path:not(.apexcharts-marker),
|
||||
.apexcharts-radar-series polygon,
|
||||
.apexcharts-toolbar svg,
|
||||
.apexcharts-tooltip .apexcharts-marker,
|
||||
.apexcharts-xaxis-annotation-label,
|
||||
.apexcharts-yaxis-annotation-label,
|
||||
.apexcharts-zoom-rect,
|
||||
.no-pointer-events {
|
||||
pointer-events: none
|
||||
}
|
||||
|
||||
.apexcharts-tooltip-active .apexcharts-marker {
|
||||
transition: .15s ease all
|
||||
}
|
||||
|
||||
.resize-triggers {
|
||||
animation: 1ms resizeanim;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden
|
||||
}
|
||||
|
||||
.contract-trigger:before,
|
||||
.resize-triggers,
|
||||
.resize-triggers>div {
|
||||
content: " ";
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0
|
||||
}
|
||||
|
||||
.resize-triggers>div {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: #eee;
|
||||
overflow: auto
|
||||
}
|
||||
|
||||
.contract-trigger:before {
|
||||
overflow: hidden;
|
||||
width: 200%;
|
||||
height: 200%
|
||||
}
|
||||
|
||||
.apexcharts-bar-goals-markers {
|
||||
pointer-events: none
|
||||
}
|
||||
|
||||
.apexcharts-bar-shadows {
|
||||
pointer-events: none
|
||||
}
|
||||
|
||||
.apexcharts-rangebar-goals-markers {
|
||||
pointer-events: none
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+38
File diff suppressed because one or more lines are too long
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "en",
|
||||
"options": {
|
||||
"months": [
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December"
|
||||
],
|
||||
"shortMonths": [
|
||||
"Jan",
|
||||
"Feb",
|
||||
"Mar",
|
||||
"Apr",
|
||||
"May",
|
||||
"Jun",
|
||||
"Jul",
|
||||
"Aug",
|
||||
"Sep",
|
||||
"Oct",
|
||||
"Nov",
|
||||
"Dec"
|
||||
],
|
||||
"days": [
|
||||
"Sunday",
|
||||
"Monday",
|
||||
"Tuesday",
|
||||
"Wednesday",
|
||||
"Thursday",
|
||||
"Friday",
|
||||
"Saturday"
|
||||
],
|
||||
"shortDays": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
|
||||
"toolbar": {
|
||||
"exportToSVG": "Download SVG",
|
||||
"exportToPNG": "Download PNG",
|
||||
"exportToCSV": "Download CSV",
|
||||
"menu": "Menu",
|
||||
"selection": "Selection",
|
||||
"selectionZoom": "Selection Zoom",
|
||||
"zoomIn": "Zoom In",
|
||||
"zoomOut": "Zoom Out",
|
||||
"pan": "Panning",
|
||||
"reset": "Reset Zoom"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "ko",
|
||||
"options": {
|
||||
"months": [
|
||||
"1월",
|
||||
"2월",
|
||||
"3월",
|
||||
"4월",
|
||||
"5월",
|
||||
"6월",
|
||||
"7월",
|
||||
"8월",
|
||||
"9월",
|
||||
"10월",
|
||||
"11월",
|
||||
"12월"
|
||||
],
|
||||
"shortMonths": [
|
||||
"1월",
|
||||
"2월",
|
||||
"3월",
|
||||
"4월",
|
||||
"5월",
|
||||
"6월",
|
||||
"7월",
|
||||
"8월",
|
||||
"9월",
|
||||
"10월",
|
||||
"11월",
|
||||
"12월"
|
||||
],
|
||||
"days": [
|
||||
"일요일",
|
||||
"월요일",
|
||||
"화요일",
|
||||
"수요일",
|
||||
"목요일",
|
||||
"금요일",
|
||||
"토요일"
|
||||
],
|
||||
"shortDays": ["일", "월", "화", "수", "목", "금", "토"],
|
||||
"toolbar": {
|
||||
"exportToSVG": "SVG 다운로드",
|
||||
"exportToPNG": "PNG 다운로드",
|
||||
"exportToCSV": "CSV 다운로드",
|
||||
"menu": "메뉴",
|
||||
"selection": "선택",
|
||||
"selectionZoom": "선택영역 확대",
|
||||
"zoomIn": "확대",
|
||||
"zoomOut": "축소",
|
||||
"pan": "패닝",
|
||||
"reset": "원래대로"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,758 @@
|
||||
/* components.css - 공통 컴포넌트 스타일 정의 */
|
||||
|
||||
/* 카드 컴포넌트 */
|
||||
.card {
|
||||
background-color: white;
|
||||
border-radius: var(--rounded-md);
|
||||
box-shadow: var(--shadow);
|
||||
border: 1px solid var(--color-gray-200);
|
||||
padding: 0.75rem;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.75rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid var(--color-gray-200);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-800);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.card-subtitle {
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-gray-600);
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
height: calc(100% - 2.25rem);
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
background-color: white;
|
||||
padding: 1.5rem;
|
||||
border-radius: var(--rounded-lg);
|
||||
box-shadow: var(--shadow);
|
||||
margin-bottom: 0;
|
||||
border: 1px solid var(--color-gray-200);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 버튼 컴포넌트 */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.4rem 0.75rem;
|
||||
border-radius: var(--rounded);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
border: 1px solid transparent;
|
||||
background-color: var(--color-gray-100);
|
||||
color: var(--color-gray-700);
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: var(--color-gray-200);
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.btn i {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.btn.btn-primary {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn.btn-primary:hover {
|
||||
background-color: #20198e;
|
||||
}
|
||||
|
||||
.btn.btn-outline {
|
||||
background-color: transparent;
|
||||
border-color: var(--color-gray-300);
|
||||
}
|
||||
|
||||
.btn.btn-outline:hover {
|
||||
background-color: var(--color-gray-100);
|
||||
}
|
||||
|
||||
.btn.btn-sm {
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
display: flex;
|
||||
border-radius: var(--rounded);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.btn-group .btn {
|
||||
border-radius: 0;
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.btn-group .btn:first-child {
|
||||
border-top-left-radius: var(--rounded);
|
||||
border-bottom-left-radius: var(--rounded);
|
||||
}
|
||||
|
||||
.btn-group .btn:last-child {
|
||||
border-top-right-radius: var(--rounded);
|
||||
border-bottom-right-radius: var(--rounded);
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.btn-group .btn.active {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 필터 및 탭 컴포넌트 */
|
||||
.filter-group {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
padding: 0.24rem 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
border-radius: var(--rounded);
|
||||
background-color: white;
|
||||
color: var(--color-gray-700);
|
||||
border: 1px solid var(--color-gray-300);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.filter-btn:hover {
|
||||
background-color: var(--color-gray-100);
|
||||
}
|
||||
|
||||
.filter-btn.active {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.tran-filter-btn {
|
||||
padding: 0.24rem 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
border-radius: var(--rounded);
|
||||
background-color: white;
|
||||
color: var(--color-gray-700);
|
||||
border: 1px solid var(--color-gray-300);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.tran-filter-btn:hover {
|
||||
background-color: var(--color-gray-100);
|
||||
}
|
||||
|
||||
.tran-filter-btn.active {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.card-controls {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.card-tabs {
|
||||
display: flex;
|
||||
border-bottom: 1px solid var(--color-gray-200);
|
||||
margin-bottom: 0.75rem;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-gray-600);
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tab-btn:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.tab-btn.active {
|
||||
color: var(--color-primary);
|
||||
border-bottom-color: var(--color-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 상태 표시 컴포넌트 */
|
||||
.server-status-badge,
|
||||
.status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: var(--rounded);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-badge.success {
|
||||
background-color: var(--color-success-light);
|
||||
color: var(--color-success-dark);
|
||||
}
|
||||
|
||||
.status-badge.warning {
|
||||
background-color: var(--color-warning-light);
|
||||
color: var(--color-warning-dark);
|
||||
}
|
||||
|
||||
.status-badge.danger {
|
||||
background-color: var(--color-danger-light);
|
||||
color: var(--color-danger-dark);
|
||||
}
|
||||
|
||||
.status-badge.info {
|
||||
background-color: var(--color-info-light);
|
||||
color: var(--color-info-dark);
|
||||
}
|
||||
|
||||
.status-badge i {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
/* 상태 인디케이터 */
|
||||
.instance-status {
|
||||
display: inline-block;
|
||||
width: 0.625rem;
|
||||
height: 0.625rem;
|
||||
border-radius: var(--rounded-full);
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.status-normal {
|
||||
background-color: var(--color-success);
|
||||
}
|
||||
|
||||
.status-warning {
|
||||
background-color: var(--color-warning);
|
||||
}
|
||||
|
||||
.status-error {
|
||||
background-color: var(--color-danger);
|
||||
}
|
||||
|
||||
.status-inactive {
|
||||
background-color: var(--color-gray-400);
|
||||
}
|
||||
|
||||
/* 테이블 컴포넌트 */
|
||||
.instance-table,
|
||||
.transaction-table,
|
||||
.adapter-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.813rem;
|
||||
}
|
||||
|
||||
.instance-table th,
|
||||
.instance-table td,
|
||||
.transaction-table th,
|
||||
.transaction-table td,
|
||||
.adapter-table th,
|
||||
.adapter-table td {
|
||||
padding: 0.75rem 1rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--color-gray-200);
|
||||
}
|
||||
|
||||
.instance-table th,
|
||||
.transaction-table th,
|
||||
.adapter-table th {
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-600);
|
||||
background-color: var(--color-gray-50);
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.transaction-table th {
|
||||
padding: 0.875rem 1rem;
|
||||
border-bottom: 2px solid var(--color-gray-200);
|
||||
}
|
||||
|
||||
.instance-table tr:last-child td,
|
||||
.adapter-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.transaction-table tr:hover {
|
||||
background-color: var(--color-gray-50);
|
||||
}
|
||||
|
||||
.transaction-table .text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.transaction-table .group-name {
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-800);
|
||||
}
|
||||
|
||||
/* 검색 컴포넌트 */
|
||||
.search-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
/* 검색 컴포넌트 */
|
||||
.search-container {
|
||||
position: relative;
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.search-container i {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--color-gray-500);
|
||||
}
|
||||
|
||||
#server-search {
|
||||
width: 100%;
|
||||
padding: 0.3rem 0.75rem 0.3rem 2rem;
|
||||
font-size: 0.813rem;
|
||||
border-radius: var(--rounded);
|
||||
border: 1px solid var(--color-gray-300);
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
#server-search:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.2);
|
||||
}
|
||||
|
||||
/* 차트 컨테이너 */
|
||||
.chart-container {
|
||||
height: 184px;
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
border-radius: var(--rounded-md);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chart-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: var(--color-gray-400);
|
||||
font-size: 1.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 로딩 애니메이션 */
|
||||
.loading {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.loading:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: var(--rounded-md);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.loading:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-top: -12px;
|
||||
margin-left: -12px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid rgba(79, 70, 229, 0.3);
|
||||
border-top-color: var(--color-primary);
|
||||
animation: spin 0.8s linear infinite;
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
/* 메트릭 컴포넌트 */
|
||||
.metric-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.metric {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.metric-label {
|
||||
font-size: 0.6875rem;
|
||||
color: var(--color-gray-500);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-800);
|
||||
font-size: 0.875rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.metric-value.success {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.metric-value.warning {
|
||||
color: var(--color-warning);
|
||||
}
|
||||
|
||||
.metric-value.danger {
|
||||
color: var(--color-danger);
|
||||
}
|
||||
|
||||
.metric-change {
|
||||
font-size: 0.6875rem;
|
||||
color: var(--color-success);
|
||||
margin-left: 0.375rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.clickable {
|
||||
color: var(--color-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.clickable:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 토글 버튼 */
|
||||
.toggle-details {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--color-gray-500);
|
||||
cursor: pointer;
|
||||
padding: 0.25rem;
|
||||
font-size: 1.25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: color 0.2s;
|
||||
border-radius: var(--rounded);
|
||||
}
|
||||
|
||||
.toggle-details:hover {
|
||||
color: var(--color-primary);
|
||||
background-color: var(--color-gray-100);
|
||||
}
|
||||
|
||||
.toggle-details.open {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
/* 테이블 컴포넌트 */
|
||||
.table-container {
|
||||
overflow-x: auto;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
table th,
|
||||
table td {
|
||||
padding: 0.4rem 0.5rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--color-gray-200);
|
||||
}
|
||||
|
||||
table th {
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-700);
|
||||
background-color: var(--color-gray-50);
|
||||
}
|
||||
|
||||
table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
table tr:hover td {
|
||||
background-color: var(--color-gray-50);
|
||||
}
|
||||
|
||||
/* 알림 컴포넌트 */
|
||||
.alert {
|
||||
padding: 0.75rem;
|
||||
border-radius: var(--rounded);
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.alert-icon {
|
||||
margin-right: 0.5rem;
|
||||
font-size: 1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.alert-content {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.alert-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.alert-message {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.alert.alert-info {
|
||||
background-color: var(--color-info-light);
|
||||
color: var(--color-info-dark);
|
||||
}
|
||||
|
||||
.alert.alert-success {
|
||||
background-color: var(--color-success-light);
|
||||
color: var(--color-success-dark);
|
||||
}
|
||||
|
||||
.alert.alert-warning {
|
||||
background-color: var(--color-warning-light);
|
||||
color: var(--color-warning-dark);
|
||||
}
|
||||
|
||||
.alert.alert-danger {
|
||||
background-color: var(--color-danger-light);
|
||||
color: var(--color-danger-dark);
|
||||
}
|
||||
|
||||
/* 로딩 스피너 */
|
||||
.spinner {
|
||||
display: inline-block;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
border: 2px solid rgba(var(--color-primary-rgb), 0.3);
|
||||
border-radius: 50%;
|
||||
border-top-color: var(--color-primary);
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 페이지네이션 */
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.pagination-item {
|
||||
margin: 0 0.25rem;
|
||||
}
|
||||
|
||||
.pagination-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: var(--rounded);
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-gray-700);
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.pagination-link:hover {
|
||||
background-color: var(--color-gray-100);
|
||||
}
|
||||
|
||||
.pagination-link.active {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.pagination-link.disabled {
|
||||
color: var(--color-gray-400);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 툴팁 */
|
||||
.tooltip {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.tooltip-text {
|
||||
visibility: hidden;
|
||||
width: 120px;
|
||||
background-color: var(--color-gray-800);
|
||||
color: white;
|
||||
text-align: center;
|
||||
border-radius: var(--rounded);
|
||||
padding: 0.4rem 0.5rem;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
bottom: 125%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
font-size: 0.75rem;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tooltip-text::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
margin-left: -5px;
|
||||
border-width: 5px;
|
||||
border-style: solid;
|
||||
border-color: var(--color-gray-800) transparent transparent transparent;
|
||||
}
|
||||
|
||||
.tooltip:hover .tooltip-text {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 애니메이션 */
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 새로고침 버튼 애니메이션 */
|
||||
.refresh-btn-spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
/* 컴팩트 모드 추가 스타일 */
|
||||
.compact-mode .card {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.compact-mode .card-header {
|
||||
margin-bottom: 0.5rem;
|
||||
padding-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.compact-mode .card-title {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.compact-mode table th,
|
||||
.compact-mode table td {
|
||||
padding: 0.3rem 0.4rem;
|
||||
}
|
||||
|
||||
.compact-mode .section-title {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
padding-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
/* peak 필터 스타일 */
|
||||
.peak-filter-group {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.peak-filter-btn {
|
||||
padding: 0.24rem 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
border-radius: var(--rounded);
|
||||
background-color: white;
|
||||
color: var(--color-gray-700);
|
||||
border: 1px solid var(--color-gray-300);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.peak-filter-btn:hover {
|
||||
background-color: var(--color-gray-100);
|
||||
}
|
||||
|
||||
.peak-filter-btn.active {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
/* layout.css - 레이아웃 및 구조 스타일 */
|
||||
|
||||
/* 대시보드 메인 그리드 레이아웃 */
|
||||
.dashboard-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr; /* 메인 콘텐츠(2/3), 사이드바(1/3) 비율 */
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.dashboard-main-content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dashboard-sidebar {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
/* 기본 그리드 레이아웃 */
|
||||
.dashboard-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1rem;
|
||||
margin-bottom: 0.2rem;
|
||||
}
|
||||
|
||||
/* 특별한 그리드 레이아웃 - 배치/피크 섹션용 */
|
||||
.dashboard-row.batch-peak-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
/* 특별한 그리드 레이아웃 - 배치/피크 섹션용에서 중첩 방지 */
|
||||
.dashboard-row.batch-peak-row > .dashboard-batch,
|
||||
.dashboard-row.batch-peak-row > .dashboard-peak,
|
||||
.dashboard-row.batch-peak-row > .batch-container,
|
||||
.dashboard-row.batch-peak-row > .peak-container {
|
||||
width: 100%;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 요약 섹션 (1:2 비율) */
|
||||
.dashboard-transactions {
|
||||
margin-bottom: 0; /* 사이드바 내에서 하단 마진 제거 */
|
||||
}
|
||||
|
||||
|
||||
/* 어댑터 섹션 */
|
||||
.dashboard-adapters {
|
||||
margin-bottom: 0; /* 사이드바 내에서 하단 마진 제거 */
|
||||
}
|
||||
|
||||
/* 트랜잭션 섹션 */
|
||||
.dashboard-transactions {
|
||||
margin-bottom: 0; /* 사이드바 내에서 하단 마진 제거 */
|
||||
}
|
||||
|
||||
/* 헤더 */
|
||||
header {
|
||||
background-color: white;
|
||||
padding: 0.8rem 1.5rem;
|
||||
border-radius: var(--rounded-lg);
|
||||
box-shadow: var(--shadow);
|
||||
margin-bottom: 1.2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-title h1 {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
}
|
||||
|
||||
.header-title i {
|
||||
font-size: 1.5rem;
|
||||
color: var(--color-primary);
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
|
||||
.header-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.time-display {
|
||||
background-color: var(--color-gray-100);
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: var(--rounded);
|
||||
font-size: 0.813rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-gray-700);
|
||||
}
|
||||
|
||||
/* 자동 새로고침 컨트롤 */
|
||||
.refresh-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.refresh-label {
|
||||
margin-right: 8px;
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-gray-600);
|
||||
}
|
||||
|
||||
.refresh-status {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.refresh-status.active {
|
||||
background-color: var(--color-success);
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
.refresh-status.inactive {
|
||||
background-color: var(--color-gray-400);
|
||||
}
|
||||
|
||||
/* 푸터 */
|
||||
footer {
|
||||
margin-top: 1.5rem;
|
||||
padding: 1rem 1.5rem 0.5rem 1.5rem;
|
||||
border-top: 1px solid var(--color-gray-200);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer-info {
|
||||
font-size: 0.813rem;
|
||||
color: var(--color-gray-500);
|
||||
}
|
||||
|
||||
.footer-info p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.footer-link {
|
||||
font-size: 0.813rem;
|
||||
color: var(--color-gray-600);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.footer-link:hover {
|
||||
color: var(--color-primary);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 서버-트랜잭션 행 (한 줄에 나오도록) */
|
||||
.dashboard-row.server-transaction-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.server-container, .transaction-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 어댑터 섹션 컨테이너 */
|
||||
.dashboard-adapters-container {
|
||||
width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* 기존 대시보드 그리드 스타일은 비활성화 */
|
||||
.dashboard-grid {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 반응형 디자인 */
|
||||
@media (max-width: 1200px) {
|
||||
.dashboard-row.server-transaction-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* 반응형 디자인 */
|
||||
@media (max-width: 1200px) {
|
||||
.dashboard-grid {
|
||||
grid-template-columns: 1fr; /* 좁은 화면에서는 세로로 배치 */
|
||||
}
|
||||
|
||||
.dashboard-summary {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.dashboard-row.batch-peak-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.header-controls {
|
||||
margin-top: 1rem;
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.server-metrics {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.transaction-table-container,
|
||||
.adapter-table-container {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.card-tabs {
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/* main.css - 메인 CSS 파일로 모든 모듈을 가져옵니다 */
|
||||
|
||||
/* 기본 변수 및 리셋 */
|
||||
@import 'variables.css';
|
||||
|
||||
/* 공통 컴포넌트 */
|
||||
@import 'components.css';
|
||||
|
||||
/* 레이아웃 및 구조 */
|
||||
@import 'layout.css';
|
||||
|
||||
/* 대시보드 섹션별 스타일 */
|
||||
@import 'sections.css';
|
||||
|
||||
/* 유틸리티 및 반응형 */
|
||||
@import 'utils.css';
|
||||
@@ -0,0 +1,52 @@
|
||||
/* sections.css - 대시보드 섹션별 스타일 */
|
||||
|
||||
/* 공통 섹션 스타일 */
|
||||
.dashboard-servers,
|
||||
.dashboard-transactions,
|
||||
.dashboard-adapters,
|
||||
.dashboard-batch,
|
||||
.dashboard-peak,
|
||||
.dashboard-sms-summary {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-800);
|
||||
margin-bottom: 0.5rem;
|
||||
padding-bottom: 0.4rem;
|
||||
border-bottom: 1px solid var(--color-gray-200);
|
||||
}
|
||||
|
||||
/* 대시보드 헤더 스타일 - 서버 JSP로 이동됨 */
|
||||
|
||||
/* 서버 상태 섹션 - 서버 JSP로 이동됨 */
|
||||
|
||||
/* 스파크라인 스타일 - 서버 JSP로 이동됨 */
|
||||
|
||||
/* 요약 섹션 - 서버 JSP로 이동됨 */
|
||||
|
||||
/* 카드 내부 여백 줄임 */
|
||||
.card {
|
||||
padding: 0.6rem;
|
||||
}
|
||||
|
||||
/* 카드 헤더 여백 줄임 */
|
||||
.card-header {
|
||||
margin-bottom: 0.5rem;
|
||||
padding-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
/* 테이블 여백 줄임 */
|
||||
table th,
|
||||
table td {
|
||||
padding: 0.3rem 0.4rem;
|
||||
}
|
||||
|
||||
/* 트랜잭션 현황 섹션 스타일 조정 */
|
||||
.transaction-overview-container .card {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* SMS 섹션 스타일 - 개별 JSP 파일로 이동됨 */
|
||||
@@ -0,0 +1,101 @@
|
||||
/* utils.css - 유틸리티 클래스 및 반응형 디자인 */
|
||||
|
||||
/* 텍스트 정렬 */
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* 반응형 디자인 */
|
||||
@media (max-width: 1200px) {
|
||||
.dashboard-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.dashboard-summary {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.batch-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.header-controls {
|
||||
margin-top: 1rem;
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.server-metrics {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.metric-group {
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.server-filters {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.transaction-filters {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
header {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.header-title h1 {
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
.header-controls {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.transaction-table th,
|
||||
.transaction-table td {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.metric-group {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/* variables.css - 모든 CSS 변수와 기본 리셋 스타일 정의 */
|
||||
|
||||
/* 변수 정의 */
|
||||
:root {
|
||||
/* 기본 색상 */
|
||||
--color-primary: #4f46e5;
|
||||
--color-primary-light: #eef2ff;
|
||||
--color-success: #22c55e;
|
||||
--color-success-light: #dcfce7;
|
||||
--color-warning: #f59e0b;
|
||||
--color-warning-light: #fef3c7;
|
||||
--color-danger: #ef4444;
|
||||
--color-danger-light: #fee2e2;
|
||||
|
||||
/* 배경 및 텍스트 색상 */
|
||||
--color-gray-50: #f9fafb;
|
||||
--color-gray-100: #f3f4f6;
|
||||
--color-gray-200: #e5e7eb;
|
||||
--color-gray-300: #d1d5db;
|
||||
--color-gray-400: #9ca3af;
|
||||
--color-gray-500: #6b7280;
|
||||
--color-gray-600: #4b5563;
|
||||
--color-gray-700: #374151;
|
||||
--color-gray-800: #1f2937;
|
||||
--color-gray-900: #111827;
|
||||
|
||||
/* 테두리 및 그림자 */
|
||||
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
--rounded-sm: 0.125rem;
|
||||
--rounded: 0.25rem;
|
||||
--rounded-md: 0.375rem;
|
||||
--rounded-lg: 0.5rem;
|
||||
--rounded-full: 9999px;
|
||||
}
|
||||
|
||||
/* 기본 리셋 및 공통 스타일 */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Noto Sans KR', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background-color: var(--color-gray-50);
|
||||
color: var(--color-gray-800);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
padding: 0.6rem 1rem;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
color: var(--color-gray-900);
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 1.5rem;
|
||||
color: var(--color-gray-600);
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* 애니메이션 */
|
||||
@keyframes pulse {
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0.4; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
/**
|
||||
* eLink 모니터링 대시보드 API 통신 라이브러리
|
||||
* ModernDashboardController의 API와 통신하는 함수들을 제공합니다.
|
||||
*/
|
||||
|
||||
const ModernDashboardApi = {
|
||||
/**
|
||||
* 컨텍스트 경로
|
||||
*/
|
||||
contextPath: '',
|
||||
|
||||
/**
|
||||
* API 초기화 함수
|
||||
* @param {string} contextPath - 애플리케이션 컨텍스트 경로
|
||||
*/
|
||||
init: function(contextPath) {
|
||||
this.contextPath = contextPath;
|
||||
},
|
||||
|
||||
/**
|
||||
* API 기본 요청 함수
|
||||
* @param {string} type - 요청 타입
|
||||
* @param {Object} params - 추가 파라미터
|
||||
* @param {Function} successCallback - 성공 콜백 함수
|
||||
* @param {Function} errorCallback - 에러 콜백 함수
|
||||
*/
|
||||
_request: function(type, params, successCallback, errorCallback) {
|
||||
// 기본 파라미터 설정
|
||||
const requestParams = {
|
||||
type: type
|
||||
, serviceType : sessionStorage['serviceType']
|
||||
};
|
||||
|
||||
// 추가 파라미터 병합
|
||||
if (params) {
|
||||
Object.assign(requestParams, params);
|
||||
}
|
||||
|
||||
// AJAX 요청 실행
|
||||
$.ajax({
|
||||
url: this.contextPath + '/modern-dashboard-api.do',
|
||||
data: requestParams,
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
if (successCallback) {
|
||||
successCallback(data);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error(`API 요청 실패 (${type}):`, error);
|
||||
if (errorCallback) {
|
||||
errorCallback(xhr, status, error);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 서버 상태 정보 요청
|
||||
* @param {string} serverName - 서버 이름 (선택적)
|
||||
* @param {Function} successCallback - 성공 콜백 함수
|
||||
* @param {Function} errorCallback - 에러 콜백 함수
|
||||
*/
|
||||
getServerStatus: function(serverName, successCallback, errorCallback) {
|
||||
const params = {};
|
||||
if (serverName) {
|
||||
params.serverName = serverName;
|
||||
}
|
||||
this._request('server-status', params, successCallback, errorCallback);
|
||||
},
|
||||
|
||||
/**
|
||||
* 어댑터 상태 정보 요청
|
||||
* @param {string} serverName - 서버 이름 (선택적)
|
||||
* @param {Function} successCallback - 성공 콜백 함수
|
||||
* @param {Function} errorCallback - 에러 콜백 함수
|
||||
*/
|
||||
getAdapterStatus: function(serverName, successCallback, errorCallback) {
|
||||
const params = {};
|
||||
if (serverName) {
|
||||
params.serverName = serverName;
|
||||
}
|
||||
this._request('adapter-status', params, successCallback, errorCallback);
|
||||
},
|
||||
|
||||
/**
|
||||
* 트랜잭션 정보 요청
|
||||
* @param {string} serverName - 서버 이름 (선택적)
|
||||
* @param {Function} successCallback - 성공 콜백 함수
|
||||
* @param {Function} errorCallback - 에러 콜백 함수
|
||||
*/
|
||||
getTransactionInfo: function(serverName, successCallback, errorCallback) {
|
||||
const params = {};
|
||||
if (serverName) {
|
||||
params.serverName = serverName;
|
||||
}
|
||||
this._request('transaction', params, successCallback, errorCallback);
|
||||
},
|
||||
|
||||
/**
|
||||
* 피크 성능 정보 요청
|
||||
* @param {string} type - 피크 타입 (tps 또는 trade)
|
||||
* @param {Function} successCallback - 성공 콜백 함수
|
||||
* @param {Function} errorCallback - 에러 콜백 함수
|
||||
*/
|
||||
getPeakPerformance: function(type, successCallback, errorCallback) {
|
||||
const params = {};
|
||||
if (type) {
|
||||
params.peakType = type; // type을 peakType으로 수정
|
||||
}
|
||||
this._request('peak-performance', params, successCallback, errorCallback);
|
||||
},
|
||||
|
||||
/**
|
||||
* 배치 처리 상태 요청
|
||||
* @param {Function} successCallback - 성공 콜백 함수
|
||||
* @param {Function} errorCallback - 에러 콜백 함수
|
||||
*/
|
||||
getBatchProcess: function(successCallback, errorCallback) {
|
||||
$.ajax({
|
||||
url: this.contextPath + '/modern-dashboard-api.do',
|
||||
data: {
|
||||
type: 'batch-process'
|
||||
, serviceType : sessionStorage['serviceType']
|
||||
},
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
if (response && response.batchData) {
|
||||
// BAP 데이터 파싱
|
||||
const bapData = response.batchData[0];
|
||||
if (bapData) {
|
||||
const bapValues = bapData.procBatch.split(',');
|
||||
bapData.sendSuccess = parseInt(bapValues[0]) || 0;
|
||||
bapData.receiveSuccess = parseInt(bapValues[1]) || 0;
|
||||
bapData.sendError = parseInt(bapValues[2]) || 0;
|
||||
bapData.receiveError = parseInt(bapValues[3]) || 0;
|
||||
bapData.sendWaiting = parseInt(bapValues[4]) || 0;
|
||||
bapData.receiveWaiting = parseInt(bapValues[5]) || 0;
|
||||
}
|
||||
|
||||
// BAT 데이터 파싱
|
||||
const batData = response.batchData[1];
|
||||
if (batData) {
|
||||
const batValues = batData.procBatch.split(',');
|
||||
batData.sendSuccess = parseInt(batValues[0]) || 0;
|
||||
batData.receiveSuccess = parseInt(batValues[1]) || 0;
|
||||
batData.sendError = parseInt(batValues[2]) || 0;
|
||||
batData.receiveError = parseInt(batValues[3]) || 0;
|
||||
batData.sendWaiting = parseInt(batValues[4]) || 0;
|
||||
batData.receiveWaiting = parseInt(batValues[5]) || 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (successCallback) {
|
||||
successCallback(response);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('배치 처리 상태 조회 실패:', error);
|
||||
if (errorCallback) {
|
||||
errorCallback(xhr, status, error);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 인스턴스 상세 정보 요청
|
||||
* @param {string} serverName - 서버 이름 (선택적)
|
||||
* @param {Function} successCallback - 성공 콜백 함수
|
||||
* @param {Function} errorCallback - 에러 콜백 함수
|
||||
*/
|
||||
getInstanceDetail: function(serverName, successCallback, errorCallback) {
|
||||
const params = {};
|
||||
if (serverName) {
|
||||
params.serverName = serverName;
|
||||
}
|
||||
this._request('instance-detail', params, successCallback, errorCallback);
|
||||
},
|
||||
|
||||
/**
|
||||
* 실시간 성능 데이터 요청
|
||||
* @param {Function} successCallback - 성공 콜백 함수
|
||||
* @param {Function} errorCallback - 에러 콜백 함수
|
||||
*/
|
||||
getPerformanceData: function(successCallback, errorCallback) {
|
||||
this._request('performance-data', {}, successCallback, errorCallback);
|
||||
},
|
||||
|
||||
/**
|
||||
* 트랜잭션 시계열 데이터 요청
|
||||
* @param {string} serverName - 서버 이름 (선택적)
|
||||
* @param {number} minutes - 가져올 데이터의 시간 범위(분) (선택적, 기본값 10)
|
||||
* @param {Function} successCallback - 성공 콜백 함수
|
||||
* @param {Function} errorCallback - 에러 콜백 함수
|
||||
*/
|
||||
getTransactionTimeSeries: function(serverName, minutes, successCallback, errorCallback) {
|
||||
const params = {};
|
||||
if (serverName) {
|
||||
params.serverName = serverName;
|
||||
}
|
||||
if (minutes) {
|
||||
params.minutes = minutes;
|
||||
}
|
||||
this._request('transaction-timeseries', params, successCallback, errorCallback);
|
||||
},
|
||||
|
||||
/**
|
||||
* 서버 리소스 시계열 데이터 요청
|
||||
* @param {string} hostname - 호스트명
|
||||
* @param {Function} successCallback - 성공 콜백 함수
|
||||
* @param {Function} errorCallback - 에러 콜백 함수
|
||||
*/
|
||||
getServerResourceData: function(hostname, successCallback, errorCallback) {
|
||||
const params = {
|
||||
hostname: hostname
|
||||
};
|
||||
this._request('server-resource', params, successCallback, errorCallback);
|
||||
},
|
||||
|
||||
/**
|
||||
* 소켓 상태 정보 요청
|
||||
* @param {string} serverName - 서버 이름 (선택적)
|
||||
* @param {string} adapterGroup - 어댑터 그룹 이름 (선택적)
|
||||
* @param {Function} successCallback - 성공 콜백 함수
|
||||
* @param {Function} errorCallback - 에러 콜백 함수
|
||||
*/
|
||||
getSocketStatus: function(serverName, adapterGroup, successCallback, errorCallback) {
|
||||
const params = {};
|
||||
if (serverName) {
|
||||
params.serverName = serverName;
|
||||
}
|
||||
if (adapterGroup) {
|
||||
params.adapterGroup = adapterGroup;
|
||||
}
|
||||
this._request('socket-status', params, successCallback, errorCallback);
|
||||
},
|
||||
|
||||
/**
|
||||
* 소켓 프로세스 정보 요청
|
||||
* @param {string} serverName - 서버 이름 (선택적)
|
||||
* @param {string} adapterGroup - 어댑터 그룹 이름 (선택적)
|
||||
* @param {Function} successCallback - 성공 콜백 함수
|
||||
* @param {Function} errorCallback - 에러 콜백 함수
|
||||
*/
|
||||
getSocketProcess: function(serverName, adapterGroup, successCallback, errorCallback) {
|
||||
const params = {};
|
||||
if (serverName) {
|
||||
params.serverName = serverName;
|
||||
}
|
||||
if (adapterGroup) {
|
||||
params.adapterGroup = adapterGroup;
|
||||
}
|
||||
this._request('socket-process', params, successCallback, errorCallback);
|
||||
},
|
||||
|
||||
/**
|
||||
* 소켓 통합 정보 요청 (상태 + 프로세스)
|
||||
* @param {string} serverName - 서버 이름 (선택적)
|
||||
* @param {string} adapterGroup - 어댑터 그룹 이름 (선택적)
|
||||
* @param {Function} successCallback - 성공 콜백 함수
|
||||
* @param {Function} errorCallback - 에러 콜백 함수
|
||||
*/
|
||||
getSocketCombined: function(serverName, adapterGroup, successCallback, errorCallback) {
|
||||
const params = {};
|
||||
if (serverName) {
|
||||
params.serverName = serverName;
|
||||
}
|
||||
if (adapterGroup) {
|
||||
params.adapterGroup = adapterGroup;
|
||||
}
|
||||
this._request('socket-combined', params, successCallback, errorCallback);
|
||||
},
|
||||
|
||||
/**
|
||||
* 인터페이스 로그 조회
|
||||
* @param {string} interfaceId - 인터페이스 ID
|
||||
* @param {string} startTime - 조회 시작 시간 (YYYYMMDDHHMMSS 형식)
|
||||
* @param {string} endTime - 조회 종료 시간 (YYYYMMDDHHMMSS 형식)
|
||||
* @param {string} serviceType - 서비스 타입
|
||||
* @param {Function} successCallback - 성공 콜백 함수
|
||||
* @param {Function} errorCallback - 에러 콜백 함수
|
||||
*/
|
||||
getInterfaceLogs: function(interfaceId, startTime, endTime, serviceType, successCallback, errorCallback) {
|
||||
const params = {
|
||||
searchEaiSvcName: interfaceId,
|
||||
searchStartTime: startTime,
|
||||
searchEndTime: endTime,
|
||||
searchErrorYn: 'N' // 모든 로그 조회 (Y인 경우 오류만 조회)
|
||||
};
|
||||
|
||||
// 트랜잭션 로그 API 호출
|
||||
$.ajax({
|
||||
url: this.contextPath + '/onl/transaction/tracking/trackingMan.json',
|
||||
type: 'POST',
|
||||
data: Object.assign({cmd: 'LIST', serviceType: serviceType || 'FEP'}, params),
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
if (successCallback) {
|
||||
successCallback(data);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error(`인터페이스 로그 조회 실패:`, error);
|
||||
if (errorCallback) {
|
||||
errorCallback(xhr, status, error);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* eLink 모니터링 대시보드 JavaScript 라이브러리
|
||||
* 모듈화된 대시보드 핵심 기능 제공
|
||||
*/
|
||||
|
||||
// 대시보드 공통 변수
|
||||
var refreshTimer = null;
|
||||
var refreshInterval = 10; // 기본값 10초
|
||||
|
||||
// 서버 목록 데이터를 저장할 전역 변수
|
||||
var serverData = [];
|
||||
|
||||
// 대시보드 이벤트 관리자
|
||||
const DashboardEventManager = {
|
||||
// 이벤트 리스너 저장소
|
||||
listeners: {
|
||||
'refresh': [],
|
||||
'init': [],
|
||||
'update': [],
|
||||
'loading-start': [],
|
||||
'loading-end': []
|
||||
},
|
||||
|
||||
// 이벤트 리스너 등록
|
||||
on: function(eventName, callback) {
|
||||
if (this.listeners[eventName]) {
|
||||
this.listeners[eventName].push(callback);
|
||||
console.log(`이벤트 리스너 등록: ${eventName}, 현재 ${this.listeners[eventName].length}개`);
|
||||
}
|
||||
},
|
||||
|
||||
// 이벤트 발생
|
||||
trigger: function(eventName, data) {
|
||||
console.log(`이벤트 발생: ${eventName}`);
|
||||
if (this.listeners[eventName]) {
|
||||
this.listeners[eventName].forEach(callback => {
|
||||
try {
|
||||
callback(data);
|
||||
} catch (e) {
|
||||
console.error(`이벤트 처리 중 오류 발생: ${eventName}`, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 전역 스코프에 노출
|
||||
window.DashboardEventManager = DashboardEventManager;
|
||||
|
||||
// 대시보드 초기화
|
||||
function initDashboard(contextPath) {
|
||||
console.log('대시보드 초기화 시작');
|
||||
|
||||
// API 초기화
|
||||
ModernDashboardApi.init(contextPath);
|
||||
|
||||
// 초기화 이벤트 발생
|
||||
DashboardEventManager.trigger('init');
|
||||
|
||||
// 수동 새로고침 버튼 이벤트 핸들러
|
||||
$('#refresh-btn').on('click', function() {
|
||||
console.log('수동 새로고침 버튼 클릭');
|
||||
refreshDashboardData();
|
||||
});
|
||||
|
||||
console.log('대시보드 초기화 완료');
|
||||
}
|
||||
|
||||
// 현재 시간 업데이트
|
||||
function updateCurrentTime() {
|
||||
const now = new Date();
|
||||
const timeString = now.toLocaleTimeString();
|
||||
const dateString = now.toLocaleDateString();
|
||||
$('#current-time').text(`${dateString} ${timeString}`);
|
||||
}
|
||||
|
||||
// 대시보드 데이터 새로고침
|
||||
function refreshDashboardData() {
|
||||
console.log("대시보드 데이터 새로고침 시작: " + new Date().toLocaleTimeString());
|
||||
|
||||
// 현재 시간 업데이트
|
||||
updateCurrentTime();
|
||||
|
||||
// 로딩 시작 이벤트 발생
|
||||
DashboardEventManager.trigger('loading-start');
|
||||
|
||||
// 새로고침 이벤트 발생
|
||||
DashboardEventManager.trigger('refresh');
|
||||
|
||||
// 약간의 지연 후 로딩 완료 이벤트 발생 (모든 컴포넌트가 데이터를 로드할 시간 제공)
|
||||
setTimeout(function() {
|
||||
DashboardEventManager.trigger('loading-end');
|
||||
}, 2000);
|
||||
|
||||
console.log("대시보드 데이터 새로고침 완료: " + new Date().toLocaleTimeString());
|
||||
}
|
||||
|
||||
// 자동 새로고침 타이머 시작
|
||||
function startRefreshTimer() {
|
||||
// 기존 타이머가 있으면 중지
|
||||
if (window.refreshTimer) {
|
||||
clearInterval(window.refreshTimer);
|
||||
window.refreshTimer = null;
|
||||
}
|
||||
|
||||
// 새로고침 간격이 0이 아니면 타이머 시작
|
||||
if (window.refreshInterval > 0) {
|
||||
window.refreshTimer = setInterval(refreshDashboardData, window.refreshInterval * 1000);
|
||||
console.log(`자동 새로고침 타이머 시작: ${window.refreshInterval}초 간격`);
|
||||
}
|
||||
}
|
||||
|
||||
// 새로고침 상태 표시 업데이트
|
||||
function updateRefreshStatus() {
|
||||
var statusIndicator = $('.refresh-status');
|
||||
if (window.refreshInterval > 0) {
|
||||
statusIndicator.addClass('active');
|
||||
statusIndicator.removeClass('inactive');
|
||||
} else {
|
||||
statusIndicator.removeClass('active');
|
||||
statusIndicator.addClass('inactive');
|
||||
}
|
||||
}
|
||||
|
||||
// 문서 로드 완료 시 초기화
|
||||
$(document).ready(function() {
|
||||
// 새로고침 간격 버튼 클릭 이벤트
|
||||
$('.refresh-btn').click(function() {
|
||||
// 버튼 활성화 상태 변경
|
||||
$('.refresh-btn').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
|
||||
// 새로고침 간격 설정
|
||||
window.refreshInterval = parseInt($(this).data('interval'));
|
||||
|
||||
// 로컬 스토리지에 저장
|
||||
localStorage.setItem('dashboardRefreshInterval', window.refreshInterval);
|
||||
|
||||
// 타이머 재시작
|
||||
startRefreshTimer();
|
||||
|
||||
// 새로고침 상태 표시 업데이트
|
||||
updateRefreshStatus();
|
||||
});
|
||||
});
|
||||
Vendored
+2
File diff suppressed because one or more lines are too long
@@ -401,6 +401,14 @@
|
||||
w.focus();
|
||||
}
|
||||
|
||||
function goDashboard2() {
|
||||
// You must open from parent window !
|
||||
var width = window.screen.availWidth;
|
||||
var height = window.screen.availHeight;
|
||||
var w = parent.window.open("<%=request.getContextPath()%>/modern-dashboard.do", "RMSDASHBOARD<%=OPS%>", "width=" + width + ",height=" + height + ",left=0,top=0,scrollbars=no,resizable=yes,status=no");
|
||||
w.focus();
|
||||
}
|
||||
|
||||
function menuRender() {
|
||||
<%if(topListSize > 0){%>
|
||||
// 처음 로딩시 제일첫번째 화면 보여주기
|
||||
@@ -472,6 +480,12 @@
|
||||
$('.depth2 a').on("click", function (e) {
|
||||
$(top.document).find(".topMenu").css("height", "80px");
|
||||
});
|
||||
|
||||
$("#newDashboardShow").on("dblclick", function(){
|
||||
<% if(((String)SessionManager.getRoleIdString(request)).indexOf("admin") > -1 || ((String)SessionManager.getRoleIdString(request)).indexOf("eai_monitor") > -1) { %>
|
||||
goDashboard2();
|
||||
<% } %>
|
||||
});
|
||||
});
|
||||
|
||||
function logout() {
|
||||
@@ -535,7 +549,7 @@
|
||||
</h1><!-- /monitoring/images/top_logo.png -->
|
||||
<div class="topmenu_box">
|
||||
<ul>
|
||||
<li style="width:240px;">
|
||||
<li style="width:240px;" id="newDashboardShow">
|
||||
<a style="width:240px;"
|
||||
href="#"><span><%=SessionManager.getUserName(request) %>(<%=SessionManager.getUserId(request) %>)</span>
|
||||
<span onClick="javascript:openColorPopup();"><%=localeMessage.getString("screen.customer") %></span>
|
||||
|
||||
@@ -0,0 +1,415 @@
|
||||
<%@ page language="java" contentType="text/html; charset=utf-8"%>
|
||||
<%@ page import="java.util.*" %>
|
||||
<%@ page import="com.eactive.eai.rms.common.datasource.*" %>
|
||||
|
||||
<div class="dashboard-adapter-summary">
|
||||
<div class="dashboard-header">
|
||||
<div class="header-title-container">
|
||||
<h2 class="section-title">회선 상태</h2>
|
||||
<%--<div class="adapter-actions">
|
||||
<button class="filter-btn view-socket-detail active">상세보기</button>
|
||||
</div>--%>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card adapter-summary-card">
|
||||
<div class="adapter-summary-container">
|
||||
<div class="adapter-summary-header">
|
||||
<div class="adapter-type-column">어댑터 유형</div>
|
||||
<div class="adapter-status-column">
|
||||
<div class="status-label error-label">에러</div>
|
||||
<div class="status-label warning-label">경고</div>
|
||||
<div class="status-label normal-label">정상</div>
|
||||
<div class="status-label inactive-label">정지</div>
|
||||
<div class="status-label total-label">합계</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="adapter-summary-body">
|
||||
<!-- 어댑터 요약 데이터가 동적으로 추가됩니다 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 어댑터 요약 행 템플릿 -->
|
||||
<template id="adapter-summary-row-template">
|
||||
<div class="adapter-summary-row">
|
||||
<div class="adapter-type-column">
|
||||
<div class="adapter-type-info">
|
||||
<span class="adapter-type-name">Socket</span>
|
||||
<span class="status-badge adapter-status-normal">정상</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="adapter-status-column">
|
||||
<div class="status-count error-count">0</div>
|
||||
<div class="status-count warning-count">0</div>
|
||||
<div class="status-count normal-count">0</div>
|
||||
<div class="status-count inactive-count">0</div>
|
||||
<div class="status-count total-count">0</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 소켓 상세 정보 모달 포함 -->
|
||||
<jsp:include page="dashboard-socket-detail.jsp" />
|
||||
|
||||
<style>
|
||||
/* 어댑터 섹션 */
|
||||
.adapter-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.adapter-table-container {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 어댑터 탭 스타일 개선 */
|
||||
.dashboard-adapters .card-tabs {
|
||||
display: flex;
|
||||
border-bottom: 1px solid var(--color-gray-200);
|
||||
background-color: var(--color-gray-50);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.dashboard-adapters .tab-btn {
|
||||
padding: 0.75rem 1.25rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-gray-600);
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
border-bottom: 2px solid transparent;
|
||||
}
|
||||
|
||||
.dashboard-adapters .tab-btn:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.dashboard-adapters .tab-btn.active {
|
||||
color: var(--color-primary);
|
||||
border-bottom-color: var(--color-primary);
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.dashboard-adapters .tab-content {
|
||||
display: none;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.dashboard-adapters .tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 어댑터 섹션 스타일 조정 */
|
||||
.adapters-container .card {
|
||||
height: 100%;
|
||||
min-height: 249px;
|
||||
}
|
||||
|
||||
.dashboard-adapter-summary {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.adapter-summary-card {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.adapter-summary-container {
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
.adapter-summary-header {
|
||||
display: flex;
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
padding-bottom: 0.25rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.adapter-summary-body {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.adapter-summary-row {
|
||||
display: flex;
|
||||
padding: 0.25rem 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.adapter-summary-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.adapter-type-column {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.adapter-type-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.adapter-type-name {
|
||||
font-weight: bold;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.adapter-status-column {
|
||||
flex: 3;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status-label, .status-count {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.15rem 0.4rem;
|
||||
border-radius: 3px;
|
||||
font-size: 0.7rem;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.adapter-status-normal {
|
||||
background-color: rgba(76, 175, 80, 0.15);
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.adapter-status-warning {
|
||||
background-color: rgba(255, 152, 0, 0.15);
|
||||
color: #ff9800;
|
||||
}
|
||||
|
||||
.adapter-status-error {
|
||||
background-color: rgba(229, 57, 53, 0.15);
|
||||
color: #e53935;
|
||||
}
|
||||
|
||||
.adapter-status-inactive {
|
||||
background-color: rgba(158, 158, 158, 0.15);
|
||||
color: #9e9e9e;
|
||||
}
|
||||
|
||||
.error-label, .error-count {
|
||||
color: #e53935;
|
||||
}
|
||||
|
||||
.warning-label, .warning-count {
|
||||
color: #ff9800;
|
||||
}
|
||||
|
||||
.normal-label, .normal-count {
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.inactive-label, .inactive-count {
|
||||
color: #9e9e9e;
|
||||
}
|
||||
|
||||
.total-label, .total-count {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.empty-data-message {
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
color: #757575;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* 어댑터 헤더 스타일 */
|
||||
.adapter-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
/* 대시보드 헤더 스타일 */
|
||||
.dashboard-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
/* 제목과 버튼을 한 줄에 나란히 표시하기 위한 컨테이너 */
|
||||
.header-title-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.adapter-actions {
|
||||
margin-left: 10px; /* 제목과 버튼 사이 간격 */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-btn:hover {
|
||||
background-color: var(--color-gray-200);
|
||||
color: var(--color-gray-800);
|
||||
}
|
||||
|
||||
.filter-btn.active {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #0069d9;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// 어댑터 요약 행 생성 함수
|
||||
function createAdapterSummaryRow(adapterSummary) {
|
||||
var template = document.getElementById('adapter-summary-row-template');
|
||||
var clone = document.importNode(template.content, true);
|
||||
|
||||
// 어댑터 유형 설정
|
||||
clone.querySelector('.adapter-type-name').textContent = adapterSummary.adapterType || '—';
|
||||
|
||||
// 상태 설정
|
||||
var statusBadge = clone.querySelector('.status-badge');
|
||||
statusBadge.className = 'status-badge';
|
||||
|
||||
if (adapterSummary.overallStatus === 'normal') {
|
||||
statusBadge.classList.add('adapter-status-normal');
|
||||
statusBadge.textContent = '정상';
|
||||
} else if (adapterSummary.overallStatus === 'warning') {
|
||||
statusBadge.classList.add('adapter-status-warning');
|
||||
statusBadge.textContent = '경고';
|
||||
} else if (adapterSummary.overallStatus === 'error') {
|
||||
statusBadge.classList.add('adapter-status-error');
|
||||
statusBadge.textContent = '오류';
|
||||
} else if (adapterSummary.overallStatus === 'inactive') {
|
||||
statusBadge.classList.add('adapter-status-inactive');
|
||||
statusBadge.textContent = '정지';
|
||||
} else {
|
||||
statusBadge.classList.add('adapter-status-inactive');
|
||||
statusBadge.textContent = '알 수 없음';
|
||||
}
|
||||
|
||||
// 상태별 카운트 설정
|
||||
clone.querySelector('.error-count').textContent = formatNumber(adapterSummary.errorCount || 0);
|
||||
clone.querySelector('.warning-count').textContent = formatNumber(adapterSummary.warningCount || 0);
|
||||
clone.querySelector('.normal-count').textContent = formatNumber(adapterSummary.normalCount || 0);
|
||||
clone.querySelector('.inactive-count').textContent = formatNumber(adapterSummary.inactiveCount || 0);
|
||||
clone.querySelector('.total-count').textContent = formatNumber(adapterSummary.totalCount || 0);
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
// 어댑터 요약 테이블 업데이트 함수
|
||||
function updateAdapterSummaryTable(data) {
|
||||
if (!data || !data.serverAdapters) return;
|
||||
|
||||
// 어댑터 요약 컨테이너 찾기
|
||||
var summaryBody = document.querySelector('.adapter-summary-body');
|
||||
if (!summaryBody) return;
|
||||
|
||||
// 컨테이너 초기화
|
||||
summaryBody.innerHTML = '';
|
||||
|
||||
// 모든 서버의 어댑터 요약 데이터 통합
|
||||
var allAdapterSummaries = [];
|
||||
data.serverAdapters.forEach(function(serverAdapter) {
|
||||
if (serverAdapter.adapterSummaries && serverAdapter.adapterSummaries.length > 0) {
|
||||
allAdapterSummaries = allAdapterSummaries.concat(serverAdapter.adapterSummaries);
|
||||
}
|
||||
});
|
||||
|
||||
// 어댑터 유형별로 그룹화
|
||||
var adapterTypeMap = {};
|
||||
allAdapterSummaries.forEach(function(summary) {
|
||||
if (!adapterTypeMap[summary.adapterType]) {
|
||||
adapterTypeMap[summary.adapterType] = summary;
|
||||
} else {
|
||||
// 기존 데이터에 카운트 누적
|
||||
var existing = adapterTypeMap[summary.adapterType];
|
||||
existing.errorCount = (existing.errorCount || 0) + (summary.errorCount || 0);
|
||||
existing.warningCount = (existing.warningCount || 0) + (summary.warningCount || 0);
|
||||
existing.normalCount = (existing.normalCount || 0) + (summary.normalCount || 0);
|
||||
existing.inactiveCount = (existing.inactiveCount || 0) + (summary.inactiveCount || 0);
|
||||
existing.totalCount = (existing.totalCount || 0) + (summary.totalCount || 0);
|
||||
|
||||
// 전체 상태 업데이트
|
||||
if (summary.overallStatus === 'error' || existing.overallStatus === 'error') {
|
||||
existing.overallStatus = 'error';
|
||||
} else if (summary.overallStatus === 'warning' || existing.overallStatus === 'warning') {
|
||||
existing.overallStatus = 'warning';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 필터링된 어댑터 유형만 표시 (Socket, Socket2, HTTP)
|
||||
var filteredAdapterTypes = Object.keys(adapterTypeMap).filter(function(type) {
|
||||
return type === 'Socket' || type === 'Socket2' || type === 'HTTP';
|
||||
});
|
||||
|
||||
if (filteredAdapterTypes.length > 0) {
|
||||
// 어댑터 유형별로 행 추가
|
||||
filteredAdapterTypes.forEach(function(type) {
|
||||
var summaryRow = createAdapterSummaryRow(adapterTypeMap[type]);
|
||||
summaryBody.appendChild(summaryRow);
|
||||
});
|
||||
} else {
|
||||
// 데이터가 없는 경우 안내 메시지 표시
|
||||
var emptyRow = document.createElement('div');
|
||||
emptyRow.className = 'empty-data-message';
|
||||
emptyRow.textContent = '표시할 어댑터 데이터가 없습니다.';
|
||||
summaryBody.appendChild(emptyRow);
|
||||
}
|
||||
}
|
||||
|
||||
// 어댑터 요약 데이터 로드 함수
|
||||
function loadAdapterSummaryData() {
|
||||
// 어댑터 요약 데이터 API 호출
|
||||
ModernDashboardApi.getAdapterStatus(null, function(data) {
|
||||
if (data) {
|
||||
updateAdapterSummaryTable(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 문서 로드 완료 시 초기화
|
||||
$(document).ready(function() {
|
||||
// 초기 데이터 로드
|
||||
loadAdapterSummaryData();
|
||||
|
||||
// 소켓 어댑터 상세 보기 버튼 이벤트
|
||||
$('.view-socket-detail').on('click', function() {
|
||||
// 소켓 상세 정보 모달 열기
|
||||
openSocketDetailModal();
|
||||
});
|
||||
|
||||
// DashboardEventManager를 사용하여 새로고침 이벤트 구독
|
||||
if (window.DashboardEventManager) {
|
||||
window.DashboardEventManager.on('refresh', function() {
|
||||
// 어댑터 요약 데이터 새로고침
|
||||
loadAdapterSummaryData();
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,405 @@
|
||||
<%@ page language="java" contentType="text/html; charset=utf-8"%>
|
||||
|
||||
<div class="dashboard-batch">
|
||||
<h2 class="section-title">배치 처리 상태</h2>
|
||||
<div class="card">
|
||||
<div class="batch-grid">
|
||||
<div class="batch-card" id="bapBatchCard" style="display: none">
|
||||
<div class="batch-header">
|
||||
<h4>일괄 전송 처리 (BAP)</h4>
|
||||
</div>
|
||||
<div class="batch-content">
|
||||
<div class="batch-metric">
|
||||
<div class="batch-metric-header">
|
||||
<span class="metric-label">송신</span>
|
||||
<div class="metric-values">
|
||||
<span class="metric-value" id="bap-send-success">0</span>
|
||||
<span class="metric-title">성공</span>
|
||||
</div>
|
||||
<div class="metric-values">
|
||||
<span class="metric-value error" id="bap-send-error">0</span>
|
||||
<span class="metric-title">에러</span>
|
||||
</div>
|
||||
<div class="metric-values">
|
||||
<span class="metric-value warning" id="bap-send-waiting">0</span>
|
||||
<span class="metric-title">대기</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="metric-progress">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill success" id="bap-send-success-bar" style="width: 80%"></div>
|
||||
<div class="progress-fill danger" id="bap-send-error-bar" style="width: 10%"></div>
|
||||
<div class="progress-fill warning" id="bap-send-waiting-bar" style="width: 10%"></div>
|
||||
</div>
|
||||
<div class="progress-info">
|
||||
<span class="progress-label">처리율:</span>
|
||||
<span class="progress-value" id="bap-send-rate">0%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="batch-metric">
|
||||
<div class="batch-metric-header">
|
||||
<span class="metric-label">수신</span>
|
||||
<div class="metric-values">
|
||||
<span class="metric-value" id="bap-receive-success">0</span>
|
||||
<span class="metric-title">성공</span>
|
||||
</div>
|
||||
<div class="metric-values">
|
||||
<span class="metric-value error" id="bap-receive-error">0</span>
|
||||
<span class="metric-title">에러</span>
|
||||
</div>
|
||||
<div class="metric-values">
|
||||
<span class="metric-value warning" id="bap-receive-waiting">0</span>
|
||||
<span class="metric-title">대기</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="metric-progress">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill success" id="bap-receive-success-bar" style="width: 70%"></div>
|
||||
<div class="progress-fill danger" id="bap-receive-error-bar" style="width: 5%"></div>
|
||||
<div class="progress-fill warning" id="bap-receive-waiting-bar" style="width: 25%"></div>
|
||||
</div>
|
||||
<div class="progress-info">
|
||||
<span class="progress-label">처리율:</span>
|
||||
<span class="progress-value" id="bap-receive-rate">0%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="batch-card" id="batBatchCard" style="display: none">
|
||||
<div class="batch-header">
|
||||
<h4>배치 처리 (BAT)</h4>
|
||||
</div>
|
||||
<div class="batch-content">
|
||||
<div class="batch-metric">
|
||||
<div class="batch-metric-header">
|
||||
<span class="metric-label">송신</span>
|
||||
<div class="metric-values">
|
||||
<span class="metric-value" id="bat-send-success">0</span>
|
||||
<span class="metric-title">성공</span>
|
||||
</div>
|
||||
<div class="metric-values">
|
||||
<span class="metric-value error" id="bat-send-error">0</span>
|
||||
<span class="metric-title">에러</span>
|
||||
</div>
|
||||
<div class="metric-values">
|
||||
<span class="metric-value warning" id="bat-send-waiting">0</span>
|
||||
<span class="metric-title">대기</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="metric-progress">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill success" id="bat-send-success-bar" style="width: 75%"></div>
|
||||
<div class="progress-fill danger" id="bat-send-error-bar" style="width: 15%"></div>
|
||||
<div class="progress-fill warning" id="bat-send-waiting-bar" style="width: 10%"></div>
|
||||
</div>
|
||||
<div class="progress-info">
|
||||
<span class="progress-label">처리율:</span>
|
||||
<span class="progress-value" id="bat-send-rate">0%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="batch-metric">
|
||||
<div class="batch-metric-header">
|
||||
<span class="metric-label">수신</span>
|
||||
<div class="metric-values">
|
||||
<span class="metric-value" id="bat-receive-success">0</span>
|
||||
<span class="metric-title">성공</span>
|
||||
</div>
|
||||
<div class="metric-values">
|
||||
<span class="metric-value error" id="bat-receive-error">0</span>
|
||||
<span class="metric-title">에러</span>
|
||||
</div>
|
||||
<div class="metric-values">
|
||||
<span class="metric-value warning" id="bat-receive-waiting">0</span>
|
||||
<span class="metric-title">대기</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="metric-progress">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill success" id="bat-receive-success-bar" style="width: 65%"></div>
|
||||
<div class="progress-fill danger" id="bat-receive-error-bar" style="width: 10%"></div>
|
||||
<div class="progress-fill warning" id="bat-receive-waiting-bar" style="width: 25%"></div>
|
||||
</div>
|
||||
<div class="progress-info">
|
||||
<span class="progress-label">처리율:</span>
|
||||
<span class="progress-value" id="bat-receive-rate">0%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* 배치 처리 섹션 */
|
||||
.batch-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.batch-card {
|
||||
background-color: white;
|
||||
border-radius: var(--rounded-md);
|
||||
border: 1px solid var(--color-gray-200);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.batch-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1.25rem;
|
||||
background-color: var(--color-gray-50);
|
||||
border-bottom: 1px solid var(--color-gray-200);
|
||||
}
|
||||
|
||||
.batch-header h4 {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-800);
|
||||
}
|
||||
|
||||
.batch-date {
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-gray-500);
|
||||
}
|
||||
|
||||
.batch-content {
|
||||
padding: 0.75rem 1.25rem;
|
||||
}
|
||||
|
||||
.batch-metric {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.batch-metric:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.batch-metric-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.batch-metric .metric-label {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-gray-700);
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.batch-metric .metric-values {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-right: 1.5rem;
|
||||
}
|
||||
|
||||
.batch-metric .metric-value {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-800);
|
||||
}
|
||||
|
||||
.batch-metric .metric-value.error {
|
||||
color: var(--color-danger);
|
||||
}
|
||||
|
||||
.batch-metric .metric-value.warning {
|
||||
color: var(--color-warning);
|
||||
}
|
||||
|
||||
.batch-metric .metric-title {
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-gray-500);
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.metric-progress {
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 8px;
|
||||
background-color: var(--color-gray-200);
|
||||
border-radius: var(--rounded-full);
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.progress-fill.success {
|
||||
background-color: var(--color-success);
|
||||
}
|
||||
|
||||
.progress-fill.danger {
|
||||
background-color: var(--color-danger);
|
||||
}
|
||||
|
||||
.progress-fill.warning {
|
||||
background-color: var(--color-warning);
|
||||
}
|
||||
|
||||
.progress-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-gray-600);
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
/* 배치 섹션 스타일 조정 */
|
||||
.batch-container .card {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// 숫자 형식화 함수 (천 단위 콤마 추가)
|
||||
function formatNumber(num) {
|
||||
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||
}
|
||||
|
||||
// 배치 프로세스 업데이트 함수
|
||||
function updateBatchProcess(data) {
|
||||
if (!data || !data.batchData) return;
|
||||
|
||||
// BAP 배치 데이터 업데이트
|
||||
var bapData = data.batchData.filter(function(batch) {
|
||||
return batch.serviceType === 'BAP';
|
||||
})[0];
|
||||
|
||||
if (bapData) {
|
||||
// 송신 데이터 설정
|
||||
$('#bap-send-success').text(formatNumber(bapData.sendSuccess));
|
||||
$('#bap-send-error').text(formatNumber(bapData.sendError));
|
||||
$('#bap-send-waiting').text(formatNumber(bapData.sendWaiting));
|
||||
|
||||
// 총 합계 계산
|
||||
var sendTotal = bapData.sendSuccess + bapData.sendError + bapData.sendWaiting;
|
||||
|
||||
// 바 너비 계산
|
||||
var sendSuccessPercent = sendTotal > 0 ? (bapData.sendSuccess / sendTotal * 100) : 0;
|
||||
var sendErrorPercent = sendTotal > 0 ? (bapData.sendError / sendTotal * 100) : 0;
|
||||
var sendWaitingPercent = sendTotal > 0 ? (bapData.sendWaiting / sendTotal * 100) : 0;
|
||||
|
||||
// 바 너비 설정
|
||||
$('#bap-send-success-bar').css('width', sendSuccessPercent + '%');
|
||||
$('#bap-send-error-bar').css('width', sendErrorPercent + '%');
|
||||
$('#bap-send-waiting-bar').css('width', sendWaitingPercent + '%');
|
||||
|
||||
// 처리율 설정
|
||||
var sendCompletedPercent = sendTotal > 0 ?
|
||||
Math.round((bapData.sendSuccess + bapData.sendError) / sendTotal * 100) : 0;
|
||||
$('#bap-send-rate').text(sendCompletedPercent + '%');
|
||||
|
||||
// 수신 데이터 설정
|
||||
$('#bap-receive-success').text(formatNumber(bapData.receiveSuccess));
|
||||
$('#bap-receive-error').text(formatNumber(bapData.receiveError));
|
||||
$('#bap-receive-waiting').text(formatNumber(bapData.receiveWaiting));
|
||||
|
||||
// 총 합계 계산
|
||||
var receiveTotal = bapData.receiveSuccess + bapData.receiveError + bapData.receiveWaiting;
|
||||
|
||||
// 바 너비 계산
|
||||
var receiveSuccessPercent = receiveTotal > 0 ? (bapData.receiveSuccess / receiveTotal * 100) : 0;
|
||||
var receiveErrorPercent = receiveTotal > 0 ? (bapData.receiveError / receiveTotal * 100) : 0;
|
||||
var receiveWaitingPercent = receiveTotal > 0 ? (bapData.receiveWaiting / receiveTotal * 100) : 0;
|
||||
|
||||
// 바 너비 설정
|
||||
$('#bap-receive-success-bar').css('width', receiveSuccessPercent + '%');
|
||||
$('#bap-receive-error-bar').css('width', receiveErrorPercent + '%');
|
||||
$('#bap-receive-waiting-bar').css('width', receiveWaitingPercent + '%');
|
||||
|
||||
// 처리율 설정
|
||||
var receiveCompletedPercent = receiveTotal > 0 ?
|
||||
Math.round((bapData.receiveSuccess + bapData.receiveError) / receiveTotal * 100) : 0;
|
||||
$('#bap-receive-rate').text(receiveCompletedPercent + '%');
|
||||
document.getElementById("bapBatchCard").style.display = "block";
|
||||
}
|
||||
|
||||
// BAT 배치 데이터 업데이트
|
||||
var batData = data.batchData.filter(function(batch) {
|
||||
return batch.serviceType === 'BAT';
|
||||
})[0];
|
||||
|
||||
if (batData) {
|
||||
// 송신 데이터 설정
|
||||
$('#bat-send-success').text(formatNumber(batData.sendSuccess));
|
||||
$('#bat-send-error').text(formatNumber(batData.sendError));
|
||||
$('#bat-send-waiting').text(formatNumber(batData.sendWaiting));
|
||||
|
||||
// 총 합계 계산
|
||||
var sendTotal = batData.sendSuccess + batData.sendError + batData.sendWaiting;
|
||||
|
||||
// 바 너비 계산
|
||||
var sendSuccessPercent = sendTotal > 0 ? (batData.sendSuccess / sendTotal * 100) : 0;
|
||||
var sendErrorPercent = sendTotal > 0 ? (batData.sendError / sendTotal * 100) : 0;
|
||||
var sendWaitingPercent = sendTotal > 0 ? (batData.sendWaiting / sendTotal * 100) : 0;
|
||||
|
||||
// 바 너비 설정
|
||||
$('#bat-send-success-bar').css('width', sendSuccessPercent + '%');
|
||||
$('#bat-send-error-bar').css('width', sendErrorPercent + '%');
|
||||
$('#bat-send-waiting-bar').css('width', sendWaitingPercent + '%');
|
||||
|
||||
// 처리율 설정
|
||||
var sendCompletedPercent = sendTotal > 0 ?
|
||||
Math.round((batData.sendSuccess + batData.sendError) / sendTotal * 100) : 0;
|
||||
$('#bat-send-rate').text(sendCompletedPercent + '%');
|
||||
|
||||
// 수신 데이터 설정
|
||||
$('#bat-receive-success').text(formatNumber(batData.receiveSuccess));
|
||||
$('#bat-receive-error').text(formatNumber(batData.receiveError));
|
||||
$('#bat-receive-waiting').text(formatNumber(batData.receiveWaiting));
|
||||
|
||||
// 총 합계 계산
|
||||
var receiveTotal = batData.receiveSuccess + batData.receiveError + batData.receiveWaiting;
|
||||
|
||||
// 바 너비 계산
|
||||
var receiveSuccessPercent = receiveTotal > 0 ? (batData.receiveSuccess / receiveTotal * 100) : 0;
|
||||
var receiveErrorPercent = receiveTotal > 0 ? (batData.receiveError / receiveTotal * 100) : 0;
|
||||
var receiveWaitingPercent = receiveTotal > 0 ? (batData.receiveWaiting / receiveTotal * 100) : 0;
|
||||
|
||||
// 바 너비 설정
|
||||
$('#bat-receive-success-bar').css('width', receiveSuccessPercent + '%');
|
||||
$('#bat-receive-error-bar').css('width', receiveErrorPercent + '%');
|
||||
$('#bat-receive-waiting-bar').css('width', receiveWaitingPercent + '%');
|
||||
|
||||
// 처리율 설정
|
||||
var receiveCompletedPercent = receiveTotal > 0 ?
|
||||
Math.round((batData.receiveSuccess + batData.receiveError) / receiveTotal * 100) : 0;
|
||||
$('#bat-receive-rate').text(receiveCompletedPercent + '%');
|
||||
document.getElementById("batBatchCard").style.display = "block";
|
||||
}
|
||||
}
|
||||
|
||||
// 배치 처리 데이터 로드 및 업데이트 함수
|
||||
function loadBatchProcessData() {
|
||||
ModernDashboardApi.getBatchProcess(function(data) {
|
||||
updateBatchProcess(data);
|
||||
});
|
||||
}
|
||||
|
||||
// 초기 로드
|
||||
$(document).ready(function() {
|
||||
loadBatchProcessData();
|
||||
|
||||
// 대시보드 새로고침 이벤트에 배치 처리 데이터 로드 함수 등록
|
||||
DashboardEventManager.on('refresh', function() {
|
||||
loadBatchProcessData();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,9 @@
|
||||
<%@ page language="java" contentType="text/html; charset=utf-8"%>
|
||||
|
||||
<footer>
|
||||
<div class="footer-info">
|
||||
<p>© 2025 eLink Monitoring System</p>
|
||||
</div>
|
||||
<div class="footer-links">
|
||||
</div>
|
||||
</footer>
|
||||
@@ -0,0 +1,102 @@
|
||||
<%@ page language="java" contentType="text/html; charset=utf-8"%>
|
||||
<header>
|
||||
<div class="header-title">
|
||||
<i class="bi bi-speedometer2"></i>
|
||||
<h1>eLink 모니터링 대시보드</h1>
|
||||
<div class="global-loading-indicator" id="global-loading-indicator"></div>
|
||||
</div>
|
||||
<div class="header-controls">
|
||||
<div class="time-display" id="current-time"></div>
|
||||
<div class="refresh-controls">
|
||||
<span class="refresh-status active"></span>
|
||||
<span class="refresh-label">자동 새로고침:</span>
|
||||
<div class="btn-group">
|
||||
<button class="btn refresh-btn btn-sm" data-interval="2">2초</button>
|
||||
<button class="btn refresh-btn btn-sm" data-interval="5">5초</button>
|
||||
<button class="btn refresh-btn btn-sm active" data-interval="10">10초</button>
|
||||
<button class="btn refresh-btn btn-sm" data-interval="0">사용안함</button>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-sm" id="refresh-btn">
|
||||
<i class="bi bi-arrow-clockwise"></i> 수동 새로고침
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<script>
|
||||
// 새로고침 간격 버튼 클릭 이벤트
|
||||
$('.refresh-btn').click(function() {
|
||||
// 버튼 활성화 상태 변경
|
||||
$('.refresh-btn').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
|
||||
// 새로고침 간격 설정
|
||||
refreshInterval = parseInt($(this).data('interval'));
|
||||
|
||||
// 로컬 스토리지에 저장
|
||||
localStorage.setItem('dashboardRefreshInterval', refreshInterval);
|
||||
|
||||
// 타이머 재시작
|
||||
startRefreshTimer();
|
||||
|
||||
// 새로고침 상태 표시 업데이트
|
||||
updateRefreshStatus();
|
||||
});
|
||||
|
||||
// 수동 새로고침 버튼 클릭 이벤트
|
||||
$('#refresh-btn').click(function() {
|
||||
refreshDashboardData();
|
||||
});
|
||||
|
||||
// 전역 로딩 인디케이터 제어 함수
|
||||
function showGlobalLoadingIndicator() {
|
||||
$('#global-loading-indicator').addClass('active');
|
||||
}
|
||||
|
||||
function hideGlobalLoadingIndicator() {
|
||||
$('#global-loading-indicator').removeClass('active');
|
||||
}
|
||||
|
||||
// 문서 로드 완료 시 이벤트 리스너 등록
|
||||
$(document).ready(function() {
|
||||
// DashboardEventManager 이벤트 구독
|
||||
if (window.DashboardEventManager) {
|
||||
// 데이터 로딩 시작 이벤트
|
||||
window.DashboardEventManager.on('loading-start', function() {
|
||||
showGlobalLoadingIndicator();
|
||||
});
|
||||
|
||||
// 데이터 로딩 완료 이벤트
|
||||
window.DashboardEventManager.on('loading-end', function() {
|
||||
hideGlobalLoadingIndicator();
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 전역 로딩 인디케이터 스타일 */
|
||||
.global-loading-indicator {
|
||||
display: inline-block;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin-left: 10px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid rgba(79, 70, 229, 0.2);
|
||||
border-top-color: var(--color-primary);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.global-loading-indicator.active {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,680 @@
|
||||
<%@ page language="java" contentType="text/html; charset=utf-8"%>
|
||||
|
||||
<!-- 인터페이스 로그 조회 모달 -->
|
||||
<div id="interfaceLogDetailModal" class="modal modal-modern">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">인터페이스 로그 조회</h2>
|
||||
<span class="close-modal">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- 인터페이스 정보 요약 -->
|
||||
<div class="interface-info-summary">
|
||||
<div class="info-card">
|
||||
<div class="info-header">
|
||||
<span class="info-label">인터페이스 ID</span>
|
||||
<span class="info-value" id="detail-interface-id">-</span>
|
||||
</div>
|
||||
<div class="info-content">
|
||||
<div class="info-row">
|
||||
<span class="info-label">발생시간</span>
|
||||
<span class="info-value" id="detail-error-time">-</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">인스턴스</span>
|
||||
<span class="info-value" id="detail-instance-name">-</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<div class="info-header">
|
||||
<span class="info-label">오류 메시지</span>
|
||||
</div>
|
||||
<div class="info-content">
|
||||
<div class="error-message" id="detail-error-message">-</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 로그 리스트 -->
|
||||
<div class="log-list-container">
|
||||
<div class="log-list-header">
|
||||
<h3>관련 거래 로그</h3>
|
||||
<div class="log-search-controls">
|
||||
<div class="time-range-selector">
|
||||
<span>조회 시간:</span>
|
||||
<select id="log-time-range">
|
||||
<option value="1" selected>±1분</option>
|
||||
<option value="5">±5분</option>
|
||||
<option value="10">±10분</option>
|
||||
<option value="30">±30분</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn btn-primary refresh-logs-btn">
|
||||
<i class="bi bi-arrow-clockwise"></i> 새로고침
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="log-table-container">
|
||||
<table class="log-table" id="log-grid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="narrow-column">처리번호</th>
|
||||
<th>거래키</th>
|
||||
<th>업무구분</th>
|
||||
<th>서비스명</th>
|
||||
<th>수신시간</th>
|
||||
<th>처리시간</th>
|
||||
<th>상태</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="log-grid-body">
|
||||
<!-- 로그 데이터가 동적으로 추가됩니다 -->
|
||||
<tr>
|
||||
<td colspan="7" class="empty-message">조회 버튼을 클릭하여 관련 로그를 검색하세요.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary close-modal-btn">닫기</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* 모달 기본 스타일 */
|
||||
.modal-modern {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.modal-modern .modal-content {
|
||||
position: relative;
|
||||
background-color: #fff;
|
||||
margin: 2% auto;
|
||||
padding: 0;
|
||||
width: 95%;
|
||||
max-width: 1200px;
|
||||
border: none;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
|
||||
animation: modalSlideUp 0.3s;
|
||||
}
|
||||
|
||||
@keyframes modalSlideUp {
|
||||
from {opacity: 0; transform: translateY(30px);}
|
||||
to {opacity: 1; transform: translateY(0);}
|
||||
}
|
||||
|
||||
.modal-modern .modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1.5rem 2rem;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 16px 16px 0 0;
|
||||
}
|
||||
|
||||
.modal-modern .modal-title {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.modal-modern .close-modal {
|
||||
color: #aaa;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.modal-modern .close-modal:hover {
|
||||
color: #333;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.modal-modern .modal-body {
|
||||
padding: 1.5rem 2rem;
|
||||
max-height: 75vh;
|
||||
overflow-y: auto;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.modal-modern .modal-footer {
|
||||
padding: 1.2rem 2rem;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 1rem;
|
||||
border-radius: 0 0 16px 16px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/* 인터페이스 정보 요약 스타일 */
|
||||
.interface-info-summary {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2fr;
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
background-color: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.info-header {
|
||||
padding: 1rem;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info-header .info-label {
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.info-header .info-value {
|
||||
font-weight: 600;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.info-content {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.info-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.info-row .info-label {
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.info-row .info-value {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
padding: 0.5rem;
|
||||
background-color: rgba(220, 53, 69, 0.1);
|
||||
border-radius: 6px;
|
||||
color: #dc3545;
|
||||
font-family: monospace;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
max-height: 100px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 로그 리스트 스타일 */
|
||||
.log-list-container {
|
||||
background-color: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.log-list-header {
|
||||
padding: 1rem;
|
||||
background-color: #f8f9fa;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.log-list-header h3 {
|
||||
margin: 0;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.log-search-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.time-range-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.time-range-selector select {
|
||||
padding: 0.25rem 0.5rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.log-table-container {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.log-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.log-table th {
|
||||
top: 0;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
padding: 0.75rem;
|
||||
text-align: left;
|
||||
font-weight: 800;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.log-table td {
|
||||
padding: 0.75rem;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.log-table tbody tr:hover {
|
||||
background-color: rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
.log-table .empty-message {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: #888;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* 로딩 인디케이터 */
|
||||
.loading-indicator {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
border: 4px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 50%;
|
||||
border-top: 4px solid #007bff;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 버튼 스타일 */
|
||||
.btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #0069d9;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: #5a6268;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* 오류 상태 표시 */
|
||||
.table-status-badge {
|
||||
display: inline-block;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.table-status-error {
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.table-status-normal {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.log-table th.narrow-column {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.highlight-row {
|
||||
background-color: rgba(0, 123, 255, 0.1);
|
||||
}
|
||||
|
||||
/* 반응형 설정 */
|
||||
@media (max-width: 992px) {
|
||||
.interface-info-summary {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.modal-modern .modal-content {
|
||||
width: 98%;
|
||||
margin: 1% auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
// 모달 요소
|
||||
const modal = document.getElementById('interfaceLogDetailModal');
|
||||
const closeButtons = modal.querySelectorAll('.close-modal, .close-modal-btn');
|
||||
const refreshButton = modal.querySelector('.refresh-logs-btn');
|
||||
const timeRangeSelect = document.getElementById('log-time-range');
|
||||
|
||||
// 데이터 상태
|
||||
let currentInterfaceId = '';
|
||||
let currentErrorTime = '';
|
||||
let currentInstanceName = '';
|
||||
let currentErrorMessage = '';
|
||||
let currentServiceType = '';
|
||||
|
||||
// 모달 닫기 이벤트
|
||||
closeButtons.forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
modal.style.display = 'none';
|
||||
});
|
||||
});
|
||||
|
||||
// 모달 외부 클릭 시 닫기
|
||||
window.addEventListener('click', (event) => {
|
||||
if (event.target === modal) {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// 새로고침 버튼 이벤트
|
||||
refreshButton.addEventListener('click', () => {
|
||||
loadInterfaceLogs();
|
||||
});
|
||||
|
||||
// 로그 시간대 변경 이벤트
|
||||
timeRangeSelect.addEventListener('change', () => {
|
||||
loadInterfaceLogs();
|
||||
});
|
||||
|
||||
// 인터페이스 로그 로드 함수
|
||||
function loadInterfaceLogs() {
|
||||
if (!currentInterfaceId || !currentErrorTime) {
|
||||
showEmptyLogGrid("인터페이스 정보가 올바르지 않습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 로딩 표시
|
||||
showLoadingLogGrid();
|
||||
|
||||
// 시간 범위 계산
|
||||
const timeRangeMinutes = parseInt(timeRangeSelect.value);
|
||||
const errorDate = parseErrorDateTime(currentErrorTime);
|
||||
|
||||
if (!errorDate) {
|
||||
showEmptyLogGrid("시간 형식이 올바르지 않습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 시작 시간과 종료 시간 계산 (±timeRangeMinutes 분)
|
||||
const startTime = new Date(errorDate.getTime() - timeRangeMinutes * 60 * 1000);
|
||||
const endTime = new Date(errorDate.getTime() + timeRangeMinutes * 60 * 1000);
|
||||
|
||||
// API 호출 시간 형식으로 변환 (YYYYMMDDHHMMSS)
|
||||
const startTimeStr = formatDateTimeForApi(startTime);
|
||||
const endTimeStr = formatDateTimeForApi(endTime);
|
||||
|
||||
// ModernDashboardApi를 사용하여 로그 데이터 가져오기
|
||||
ModernDashboardApi.getInterfaceLogs(
|
||||
currentInterfaceId,
|
||||
startTimeStr,
|
||||
endTimeStr,
|
||||
currentServiceType,
|
||||
function(response) {
|
||||
if (response && response.rows) {
|
||||
updateLogGrid(response.rows);
|
||||
} else {
|
||||
showEmptyLogGrid("조회된 로그가 없습니다.");
|
||||
}
|
||||
},
|
||||
function(xhr, status, error) {
|
||||
console.error("로그 데이터 로드 실패:", error);
|
||||
showEmptyLogGrid("로그 데이터를 불러오는 중 오류가 발생했습니다.");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// 로그 그리드 업데이트
|
||||
function updateLogGrid(logs) {
|
||||
const tbody = document.getElementById('log-grid-body');
|
||||
|
||||
if (!logs || logs.length === 0) {
|
||||
showEmptyLogGrid("조회된 로그가 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
|
||||
logs.forEach(log => {
|
||||
const isError = log.ERRYN === 'Y';
|
||||
|
||||
// 시간 형식 변환 (YYYYMMDDHHMMSS -> YYYY-MM-DD HH:MM:SS)
|
||||
const recvTime = formatDateTime(log.MSGDPSTYMS_TMP);
|
||||
const procTime = formatDateTime(log.MSGPRCSSYMS);
|
||||
|
||||
html += '<tr>' +
|
||||
'<td>' + (log.LOGPRCSSSERNO || '-') + '</td>' +
|
||||
'<td>' + (log.KEYMGTMSGCTNT || '-') + '</td>' +
|
||||
'<td>' + (log.EAIBZWKDSTCD_TMP || '-') + '</td>' +
|
||||
'<td>' + (log.EAISVCNAME || '-') + '</td>' +
|
||||
'<td>' + (recvTime || '-') + '</td>' +
|
||||
'<td>' + (procTime || '-') + '</td>' +
|
||||
'<td><span class="table-status-badge ' + (isError ? 'table-status-error' : 'table-status-normal') + '">' +
|
||||
(isError ? '오류' : '정상') + '</span>' +
|
||||
'<input type="hidden" value="' + (log.EAISVCSERNO_TMP || '') + '"></td>' +
|
||||
'</tr>';
|
||||
});
|
||||
|
||||
tbody.innerHTML = html;
|
||||
|
||||
// 현재 인터페이스 ID를 포함한 행 강조 표시
|
||||
highlightRows(currentInterfaceId);
|
||||
}
|
||||
|
||||
// 특정 인터페이스 ID를 포함한 행 강조 표시
|
||||
function highlightRows(interfaceId) {
|
||||
if (!interfaceId) return;
|
||||
|
||||
const tbody = document.getElementById('log-grid-body');
|
||||
const rows = tbody.querySelectorAll('tr');
|
||||
|
||||
rows.forEach(row => {
|
||||
const cells = row.querySelectorAll('td');
|
||||
if (cells.length > 1) {
|
||||
const keyCell = cells[1].textContent;
|
||||
if (keyCell && keyCell.includes(interfaceId)) {
|
||||
row.classList.add('highlight-row');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 로딩 중 표시
|
||||
function showLoadingLogGrid() {
|
||||
const tbody = document.getElementById('log-grid-body');
|
||||
tbody.innerHTML =
|
||||
'<tr>' +
|
||||
'<td colspan="7">' +
|
||||
'<div class="loading-indicator">' +
|
||||
'<div class="loading-spinner"></div>' +
|
||||
'</div>' +
|
||||
'</td>' +
|
||||
'</tr>';
|
||||
}
|
||||
|
||||
// 빈 그리드 표시
|
||||
function showEmptyLogGrid(message) {
|
||||
const tbody = document.getElementById('log-grid-body');
|
||||
tbody.innerHTML =
|
||||
'<tr>' +
|
||||
'<td colspan="7" class="empty-message">' + (message || '조회된 데이터가 없습니다.') + '</td>' +
|
||||
'</tr>';
|
||||
}
|
||||
|
||||
// 시간 문자열 파싱 (예: "0312 10:52:32.009")
|
||||
function parseErrorDateTime(dateTimeStr) {
|
||||
try {
|
||||
if (!dateTimeStr) return new Date();
|
||||
|
||||
// "0312 10:52:32.009" 형식 처리
|
||||
if (dateTimeStr.includes(' ') && dateTimeStr.length >= 16) {
|
||||
const datePart = dateTimeStr.split(' ')[0]; // "0312"
|
||||
const timePart = dateTimeStr.split(' ')[1].split('.')[0]; // "10:52:32"
|
||||
|
||||
const month = parseInt(datePart.substring(0, 2));
|
||||
const day = parseInt(datePart.substring(2, 4));
|
||||
|
||||
const timeParts = timePart.split(':');
|
||||
const hour = parseInt(timeParts[0]);
|
||||
const minute = parseInt(timeParts[1]);
|
||||
const second = parseInt(timeParts[2]);
|
||||
|
||||
// 현재 연도 사용
|
||||
const currentYear = new Date().getFullYear();
|
||||
return new Date(currentYear, month - 1, day, hour, minute, second);
|
||||
}
|
||||
|
||||
// 기존 "2023-03-12 15:30:45" 형식 처리 (이전 코드 유지)
|
||||
const parts = dateTimeStr.split(/[- :]/);
|
||||
if (parts.length >= 6) {
|
||||
return new Date(parts[0], parts[1] - 1, parts[2], parts[3], parts[4], parts[5]);
|
||||
}
|
||||
return new Date();
|
||||
} catch (e) {
|
||||
console.error("날짜 파싱 오류:", e);
|
||||
return new Date();
|
||||
}
|
||||
}
|
||||
|
||||
// API 호출용 날짜 형식 변환 (YYYYMMDDHHMMSS)
|
||||
function formatDateTimeForApi(date) {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0');
|
||||
|
||||
return '' + year + month + day + hours + minutes + seconds;
|
||||
}
|
||||
|
||||
// 화면 표시용 날짜 형식 변환 (YYYY-MM-DD HH:MM:SS)
|
||||
function formatDateTime(dateTimeStr) {
|
||||
if (!dateTimeStr || dateTimeStr.length < 14) return '';
|
||||
|
||||
const year = dateTimeStr.substr(0, 4);
|
||||
const month = dateTimeStr.substr(4, 2);
|
||||
const day = dateTimeStr.substr(6, 2);
|
||||
const hour = dateTimeStr.substr(8, 2);
|
||||
const minute = dateTimeStr.substr(10, 2);
|
||||
const second = dateTimeStr.substr(12, 2);
|
||||
|
||||
return year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second;
|
||||
}
|
||||
|
||||
// 모달 열기 함수 (전역 스코프에 노출)
|
||||
window.openInterfaceLogDetailModal = function(interfaceId, errorTime, instanceName, errorMessage, serviceType) {
|
||||
// 데이터 설정
|
||||
currentInterfaceId = interfaceId || '';
|
||||
currentErrorTime = errorTime || '';
|
||||
currentInstanceName = instanceName || '';
|
||||
currentErrorMessage = errorMessage || '';
|
||||
currentServiceType = serviceType || '';
|
||||
|
||||
// 조회 기준 일/시 분리해서 표시
|
||||
let formattedErrorTime = currentErrorTime;
|
||||
if (currentErrorTime && currentErrorTime.includes(' ')) {
|
||||
const datePart = currentErrorTime.split(' ')[0]; // "0312"
|
||||
const timePart = currentErrorTime.split(' ')[1]; // "10:52:32.009"
|
||||
|
||||
// 월/일 형식으로 변환
|
||||
const month = datePart.substring(0, 2);
|
||||
const day = datePart.substring(2, 4);
|
||||
|
||||
formattedErrorTime = month + '월 ' + day + '일 ' + timePart;
|
||||
}
|
||||
|
||||
// UI 업데이트
|
||||
document.getElementById('detail-interface-id').textContent = currentInterfaceId;
|
||||
document.getElementById('detail-error-time').textContent = formattedErrorTime;
|
||||
document.getElementById('detail-instance-name').textContent = currentInstanceName;
|
||||
document.getElementById('detail-error-message').textContent = currentErrorMessage;
|
||||
|
||||
// 모달 표시
|
||||
modal.style.display = 'block';
|
||||
|
||||
// 로그 데이터 로드
|
||||
loadInterfaceLogs();
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
@@ -0,0 +1,243 @@
|
||||
<%@ page language="java" contentType="text/html; charset=utf-8"%>
|
||||
|
||||
<div class="dashboard-peak">
|
||||
<h2 class="section-title">피크 처리 성능
|
||||
<button class="peak-filter-btn active" id="peakTps">tps</button>
|
||||
<button class="peak-filter-btn" id="peakTrade">trade</button>
|
||||
</h2>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">
|
||||
|
||||
</div>
|
||||
<div class="peak-grid" id="peak-tps-container">
|
||||
<!-- 피크 성능 데이터가 동적으로 추가됩니다 -->
|
||||
</div>
|
||||
|
||||
<div class="peak-grid" id="peak-trade-container" style="display:none">
|
||||
<!-- 피크 성능 데이터가 동적으로 추가됩니다 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 피크 성능 카드 템플릿 -->
|
||||
<template id="peak-card-template">
|
||||
<div class="peak-card">
|
||||
<div class="peak-header">
|
||||
<div class="peak-header-content">
|
||||
<h4 class="peak-service-type">서비스 유형</h4>
|
||||
<span class="peak-tps">tps</span>
|
||||
<span class="peak-date">날짜</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.dashboard-peak {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* FEP와 BAP를 상하로 배치하기 위한 스타일 */
|
||||
.peak-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.3rem;
|
||||
height: 263px; /* 배치 처리 성능 높이와 일관되게 설정 */
|
||||
overflow-y: auto; /* 내용이 많을 경우 스크롤 가능하도록 */
|
||||
}
|
||||
|
||||
.peak-card {
|
||||
margin-bottom: 0.3rem;
|
||||
height: auto; /* 자동 높이 설정 */
|
||||
background-color: white;
|
||||
border-radius: var(--rounded-md);
|
||||
border: 1px solid var(--color-gray-200);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.peak-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
/* 좁은 공간에 맞게 조정 */
|
||||
.peak-header {
|
||||
margin-bottom: 0.1rem;
|
||||
padding: 0.3rem 0.5rem; /* 패딩 더 축소 */
|
||||
border-bottom: 1px solid var(--color-gray-200);
|
||||
}
|
||||
|
||||
.peak-header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.peak-service-type {
|
||||
font-size: 0.85rem;
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-800);
|
||||
}
|
||||
|
||||
.peak-date {
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-gray-500);
|
||||
}
|
||||
|
||||
.peak-content {
|
||||
padding: 0.3rem 0.5rem; /* 패딩 더 축소 */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.peak-value-container {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.peak-value {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
}
|
||||
|
||||
.peak-label {
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-gray-500);
|
||||
}
|
||||
|
||||
/* 차트 높이 조정 */
|
||||
.peak-chart {
|
||||
height: 40px; /* 차트 높이 더 축소 */
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.peak-bar-container {
|
||||
height: 100%;
|
||||
width: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.peak-bar {
|
||||
width: 30px;
|
||||
background-color: var(--color-primary);
|
||||
border-radius: 3px 3px 0 0;
|
||||
}
|
||||
|
||||
/* 피크 섹션 스타일 조정 */
|
||||
.peak-container .card {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.peak-tps {
|
||||
font-size: 1rem;
|
||||
margin-left: auto;
|
||||
margin-right: 8px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
display: flex;
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// 숫자 형식화 함수 (천 단위 콤마 추가)
|
||||
function formatNumber(num) {
|
||||
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||
}
|
||||
|
||||
// 피크 성능 데이터 업데이트 함수
|
||||
function updatePeakPerformance(data) {
|
||||
if (!data || !data.peakTps || !data.peakTrade) return;
|
||||
|
||||
var container = document.getElementById('peak-tps-container');
|
||||
container.innerHTML = '';
|
||||
|
||||
// 최대값 찾기 (스케일링을 위해)
|
||||
var maxValue = 0;
|
||||
data.peakTps.forEach(function(peak) {
|
||||
var value = peak.tps;
|
||||
if (value > maxValue) maxValue = value;
|
||||
});
|
||||
|
||||
// 피크 카드 생성
|
||||
data.peakTps.forEach(function(peak) {
|
||||
var template = document.getElementById('peak-card-template');
|
||||
var clone = document.importNode(template.content, true);
|
||||
|
||||
// 서비스 유형 설정
|
||||
clone.querySelector('.peak-service-type').textContent = peak.serviceType;
|
||||
|
||||
// tps 설정
|
||||
clone.querySelector('.peak-tps').textContent = (0 || peak.tps);
|
||||
|
||||
// 날짜 설정
|
||||
clone.querySelector('.peak-date').textContent = peak.formattedDate || peak.date;
|
||||
|
||||
container.appendChild(clone);
|
||||
});
|
||||
|
||||
var tradeContainer = document.getElementById('peak-trade-container');
|
||||
tradeContainer.innerHTML = '';
|
||||
|
||||
// 피크 카드 생성
|
||||
data.peakTrade.forEach(function(peak) {
|
||||
var template = document.getElementById('peak-card-template');
|
||||
var clone = document.importNode(template.content, true);
|
||||
|
||||
// 서비스 유형 설정
|
||||
clone.querySelector('.peak-service-type').textContent = peak.serviceType;
|
||||
|
||||
// tps 설정
|
||||
clone.querySelector('.peak-tps').textContent = (0 || peak.tps);
|
||||
|
||||
// 날짜 설정
|
||||
clone.querySelector('.peak-date').textContent = peak.formattedDate || peak.date;
|
||||
|
||||
tradeContainer.appendChild(clone);
|
||||
});
|
||||
}
|
||||
|
||||
// 피크 성능 데이터 로드 함수
|
||||
function loadPeakPerformanceData() {
|
||||
// 항상 TPS 데이터 로드 (선택 버튼 제거)
|
||||
ModernDashboardApi.getPeakPerformance('tps', function(data) {
|
||||
updatePeakPerformance(data);
|
||||
});
|
||||
}
|
||||
|
||||
// 서버 필터 버튼 클릭 이벤트
|
||||
$('.peak-filter-btn').click(function() {
|
||||
var type = $(this).prop("id");
|
||||
|
||||
if (type === 'peakTps') {
|
||||
// 전체 표시
|
||||
$("#peakTrade").removeClass('active');
|
||||
$("#peakTps").addClass('active');
|
||||
$("#peak-tps-container").show();
|
||||
$("#peak-trade-container").hide();
|
||||
|
||||
} else if( type === 'peakTrade' ){
|
||||
// 필터링
|
||||
$("#peakTps").removeClass('active');
|
||||
$("#peakTrade").addClass('active');
|
||||
$("#peak-tps-container").hide();
|
||||
$("#peak-trade-container").show();
|
||||
}
|
||||
});
|
||||
|
||||
// 초기 로드
|
||||
$(document).ready(function() {
|
||||
loadPeakPerformanceData();
|
||||
});
|
||||
</script>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,267 @@
|
||||
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
|
||||
<div class="dashboard-sms-summary">
|
||||
<h2 class="section-title">장애통보(SMS)</h2>
|
||||
<div class="card shadow mb-0">
|
||||
<div class="sms-table-container">
|
||||
<table class="sms-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="18%">인스턴스</th>
|
||||
<th width="8%">유형</th>
|
||||
<th width="12%">시간</th>
|
||||
<th width="10%">건수</th>
|
||||
<th width="20%">오류대상</th>
|
||||
<th>메시지</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="smsTableBody">
|
||||
<!-- 데이터가 여기에 동적으로 로드됩니다 -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* SMS 섹션 스타일 */
|
||||
.dashboard-sms-summary .section-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.dashboard-sms-summary .card {
|
||||
border-radius: 0.35rem;
|
||||
box-shadow: 0 0.15rem 1.75rem 0 rgba(58, 59, 69, 0.15);
|
||||
}
|
||||
|
||||
.sms-table-container {
|
||||
max-height: 220px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sms-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.sms-table th {
|
||||
background-color: #f8f9fc;
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
padding: 0.5rem;
|
||||
border-bottom: 1px solid #e3e6f0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.sms-table td {
|
||||
padding: 0.5rem;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.sms-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.sms-table tr:hover {
|
||||
background-color: rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
.error-type-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.error-type-icon.adapter {
|
||||
background-color: #4e73df;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.error-type-icon.interface {
|
||||
background-color: #e74a3b;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.error-type-icon i {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.error-target {
|
||||
font-family: monospace;
|
||||
padding: 0.2rem 0.4rem;
|
||||
background-color: #f8f9fc;
|
||||
border-radius: 0.2rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #e74a3b !important;
|
||||
}
|
||||
|
||||
.empty-message {
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
color: #6c757d;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.occurrence-count {
|
||||
display: inline-block;
|
||||
padding: 0.2rem 0.5rem;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 0.25rem;
|
||||
font-weight: 600;
|
||||
color: #dc3545;
|
||||
min-width: 2.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// SMS 데이터 로드 함수
|
||||
function loadSmsData() {
|
||||
$.ajax({
|
||||
url: 'modern-dashboard-api.do',
|
||||
type: 'GET',
|
||||
data: {
|
||||
type: 'sms-notification'
|
||||
, serviceType: sessionStorage['serviceType']
|
||||
},
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
renderSmsTable(response.data);
|
||||
} else {
|
||||
console.error('SMS 데이터 로드 실패:', response.message);
|
||||
showEmptySmsTable();
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('SMS 데이터 로드 중 오류 발생:', error);
|
||||
showEmptySmsTable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// SMS 테이블 렌더링 함수
|
||||
function renderSmsTable(data) {
|
||||
var $tableBody = $('#smsTableBody');
|
||||
$tableBody.empty();
|
||||
|
||||
if (data && data.length > 0) {
|
||||
// 최대 10개 항목만 표시
|
||||
var displayData = data.slice(0, 10);
|
||||
|
||||
$.each(displayData, function(index, item) {
|
||||
var errorTypeIcon = getErrorTypeIcon(item.errorType);
|
||||
var timeStr = formatTime(item.dateTime);
|
||||
|
||||
// 오류 대상 정보와 스타일 조건부 적용
|
||||
var errorTarget = '';
|
||||
if (item.errorType === 'ADAPTER') {
|
||||
// 어댑터 오류일 경우 - 클릭 불가능
|
||||
errorTarget = '<span class="adapter-id">' + (item.adapterId || '-') + '</span>';
|
||||
} else {
|
||||
// 인터페이스 오류일 경우 - 클릭 가능, data-service-type 속성 추가
|
||||
errorTarget = '<span class="error-target interface-id" data-service-type="' +
|
||||
(item.serviceType || '') + '" data-date-time="' +
|
||||
(item.dateTime || '') + '">' + (item.interfaceId || '-') + '</span>';
|
||||
}
|
||||
|
||||
var row = '<tr>' +
|
||||
'<td>' + (item.instanceName || '-') + '</td>' +
|
||||
'<td>' + errorTypeIcon + '</td>' +
|
||||
'<td class="text-nowrap">' + timeStr + '</td>' +
|
||||
'<td><span class="occurrence-count">' + (item.count || '0') + '</span></td>' +
|
||||
'<td>' + errorTarget + '</td>' +
|
||||
'<td class="text-danger">' + (item.errorContent || '-') + '</td>' +
|
||||
'</tr>';
|
||||
|
||||
$tableBody.append(row);
|
||||
});
|
||||
|
||||
// 인터페이스ID에만 클릭 이벤트 스타일 적용
|
||||
$('.interface-id').css({
|
||||
'cursor': 'pointer',
|
||||
'color': '#007bff',
|
||||
'text-decoration': 'underline'
|
||||
});
|
||||
|
||||
// 인터페이스ID 클릭 이벤트 처리 - 인터페이스 타입에만 적용
|
||||
$('.interface-id').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
var row = $(this).closest('tr');
|
||||
var interfaceId = $(this).text().trim();
|
||||
var instanceName = row.find('td').eq(0).text().trim();
|
||||
var errorTime = $(this).data('date-time');
|
||||
var errorMessage = row.find('td').eq(5).text().trim();
|
||||
var serviceType = $(this).data('service-type');
|
||||
|
||||
// 인터페이스 로그 상세 모달 열기
|
||||
openInterfaceLogDetailModal(interfaceId, errorTime, instanceName, errorMessage, serviceType);
|
||||
});
|
||||
} else {
|
||||
showEmptySmsTable();
|
||||
}
|
||||
}
|
||||
|
||||
// 에러 타입 아이콘 생성
|
||||
function getErrorTypeIcon(errorType) {
|
||||
if (errorType === 'ADAPTER') {
|
||||
return '<div class="error-type-icon adapter" title="어댑터 오류"><i class="bi bi-plug"></i></div>';
|
||||
} else {
|
||||
return '<div class="error-type-icon interface" title="인터페이스 오류"><i class="bi bi-arrow-left-right"></i></div>';
|
||||
}
|
||||
}
|
||||
|
||||
// 오류 대상 정보 가져오기
|
||||
function getErrorTarget(item) {
|
||||
if (item.errorType === 'ADAPTER') {
|
||||
return item.adapterId || '-';
|
||||
} else {
|
||||
return item.interfaceId || '-';
|
||||
}
|
||||
}
|
||||
|
||||
// 시간 포맷팅 (HH:mm:ss)
|
||||
function formatTime(dateTimeStr) {
|
||||
if (!dateTimeStr) return '-';
|
||||
var parts = dateTimeStr.split(' ');
|
||||
if (parts.length >= 2) {
|
||||
var timePart = parts[1];
|
||||
return timePart.substring(0, 8); // HH:mm:ss 부분만 추출
|
||||
}
|
||||
return '-';
|
||||
}
|
||||
|
||||
// 빈 테이블 표시 함수
|
||||
function showEmptySmsTable() {
|
||||
var $tableBody = $('#smsTableBody');
|
||||
$tableBody.empty();
|
||||
$tableBody.append('<tr><td colspan="5" class="empty-message">장애 통보 내역이 없습니다</td></tr>');
|
||||
}
|
||||
|
||||
// 초기 데이터 로드
|
||||
loadSmsData();
|
||||
|
||||
// DashboardEventManager를 사용하여 새로고침 이벤트 구독
|
||||
if (window.DashboardEventManager) {
|
||||
window.DashboardEventManager.on('refresh', function() {
|
||||
console.log('SMS 데이터 갱신 시작');
|
||||
loadSmsData()
|
||||
});
|
||||
|
||||
// 초기화 완료 이벤트 로깅
|
||||
console.log('SMS 컴포넌트 초기화 완료');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,957 @@
|
||||
<%@ page language="java" contentType="text/html; charset=utf-8"%>
|
||||
<%@ page import="java.util.*" %>
|
||||
|
||||
<!-- 소켓 상세 정보 모달 -->
|
||||
<div id="socketDetailModal" class="modal modal-modern">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">회선 상세 정보</h2>
|
||||
<span class="close-modal">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- 탭 메뉴 -->
|
||||
<div class="socket-detail-tabs">
|
||||
<button class="tab-btn active" data-tab="institution-visual">
|
||||
<i class="bi bi-pie-chart"></i> 기관 현황
|
||||
</button>
|
||||
<button class="tab-btn" data-tab="institution-status">
|
||||
<i class="bi bi-building"></i> 기관 상태
|
||||
</button>
|
||||
<button class="tab-btn" data-tab="socket-status">
|
||||
<i class="bi bi-plug"></i> 어댑터 상태
|
||||
</button>
|
||||
<button class="tab-btn" data-tab="socket-process">
|
||||
<i class="bi bi-arrow-left-right"></i> 세션 상태
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 탭 컨텐츠 -->
|
||||
<div class="socket-detail-content">
|
||||
<!-- 기관 연결 현황 시각화 탭 -->
|
||||
<div id="institution-visual" class="tab-content active">
|
||||
<div class="visual-stats-summary">
|
||||
<div class="stats-card total-institutions">
|
||||
<div class="stats-icon"><i class="bi bi-building"></i></div>
|
||||
<div class="stats-details">
|
||||
<div class="stats-number" id="total-institutions-count">0</div>
|
||||
<div class="stats-label">총 기관</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stats-card normal-institutions">
|
||||
<div class="stats-icon"><i class="bi bi-check-circle"></i></div>
|
||||
<div class="stats-details">
|
||||
<div class="stats-number" id="normal-institutions-count">0</div>
|
||||
<div class="stats-label">정상</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stats-card warning-institutions">
|
||||
<div class="stats-icon"><i class="bi bi-exclamation-triangle"></i></div>
|
||||
<div class="stats-details">
|
||||
<div class="stats-number" id="warning-institutions-count">0</div>
|
||||
<div class="stats-label">경고</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stats-card error-institutions">
|
||||
<div class="stats-icon"><i class="bi bi-x-circle"></i></div>
|
||||
<div class="stats-details">
|
||||
<div class="stats-number" id="error-institutions-count">0</div>
|
||||
<div class="stats-label">오류</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stats-card inactive-institutions">
|
||||
<div class="stats-icon"><i class="bi bi-dash-circle"></i></div>
|
||||
<div class="stats-details">
|
||||
<div class="stats-number" id="inactive-institutions-count">0</div>
|
||||
<div class="stats-label">미사용</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="connection-visual-container">
|
||||
<div id="institution-grid" class="institution-grid"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 기관 상태 탭 -->
|
||||
<div id="institution-status" class="tab-content">
|
||||
<div class="institution-status-container">
|
||||
<table class="institution-status-table modal-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>기관명</th>
|
||||
<th>담당자명</th>
|
||||
<th>연락처</th>
|
||||
<th>정상어댑터</th>
|
||||
<th>오류어댑터</th>
|
||||
<th>회선상태</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- 데이터가 동적으로 추가됩니다 -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 회선 상태 탭 -->
|
||||
<div id="socket-status" class="tab-content">
|
||||
<div class="socket-status-container">
|
||||
<table class="socket-status-table modal-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>어댑터 그룹</th>
|
||||
<th>인스턴스명</th>
|
||||
<th>어댑터명</th>
|
||||
<th>어댑터 설명</th>
|
||||
<th>어댑터 유형</th>
|
||||
<th>상태</th>
|
||||
<th>기동 상태</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- 데이터가 동적으로 추가됩니다 -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 회선 연결 상태 탭 -->
|
||||
<div id="socket-process" class="tab-content">
|
||||
<div class="socket-process-container">
|
||||
<table class="socket-process-table modal-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>어댑터 그룹</th>
|
||||
<th>인스턴스명</th>
|
||||
<th>어댑터명</th>
|
||||
<th>어댑터 설명</th>
|
||||
<th>최대 연결</th>
|
||||
<th>현재 연결</th>
|
||||
<th>송신 수</th>
|
||||
<th>수신 수</th>
|
||||
<th>오류 수</th>
|
||||
<th>상태</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- 데이터가 동적으로 추가됩니다 -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary refresh-socket-data">
|
||||
<i class="bi bi-arrow-clockwise" style="color: #ffffff;"></i> 새로고침
|
||||
</button>
|
||||
<button class="btn btn-secondary close-modal-btn">닫기</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* 기존 스타일 강화 */
|
||||
.modal-modern {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.modal-modern .modal-content {
|
||||
position: relative;
|
||||
background-color: #fff;
|
||||
margin: 2% auto;
|
||||
padding: 0;
|
||||
width: 95%;
|
||||
max-width: 1400px;
|
||||
border: none;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
|
||||
animation: modalSlideUp 0.3s;
|
||||
}
|
||||
|
||||
@keyframes modalSlideUp {
|
||||
from {opacity: 0; transform: translateY(30px);}
|
||||
to {opacity: 1; transform: translateY(0);}
|
||||
}
|
||||
|
||||
@keyframes modalFadeIn {
|
||||
from {opacity: 0;}
|
||||
to {opacity: 1;}
|
||||
}
|
||||
|
||||
.modal-modern .modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1.5rem 2rem;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 16px 16px 0 0;
|
||||
}
|
||||
|
||||
.modal-modern .modal-title {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.modal-modern .close-modal {
|
||||
color: #aaa;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.modal-modern .close-modal:hover {
|
||||
color: #333;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.modal-modern .modal-body {
|
||||
padding: 1.5rem 2rem;
|
||||
max-height: 75vh;
|
||||
overflow-y: auto;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.modal-modern .modal-footer {
|
||||
padding: 1.2rem 2rem;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 1rem;
|
||||
border-radius: 0 0 16px 16px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/* 탭 스타일 향상 */
|
||||
.socket-detail-tabs {
|
||||
display: flex;
|
||||
border-bottom: none;
|
||||
margin-bottom: 1.5rem;
|
||||
overflow-x: auto;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 0.5rem;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
padding: 0.9rem 1.5rem;
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
color: #666;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.tab-btn:hover {
|
||||
color: #333;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.tab-btn.active {
|
||||
color: white;
|
||||
background-color: #007bff;
|
||||
box-shadow: 0 4px 8px rgba(0, 123, 255, 0.2);
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
animation: fadeIn 0.3s;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {opacity: 0;}
|
||||
to {opacity: 1;}
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 테이블 스타일 향상 */
|
||||
.modal-table {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
background-color: #fff;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.modal-table th {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
padding: 1.2rem;
|
||||
text-align: left;
|
||||
font-weight: 800;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.modal-table td {
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.modal-table tbody tr {
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.modal-table tbody tr:hover {
|
||||
background-color: rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
.modal-table tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* 버튼 스타일 향상 */
|
||||
.btn {
|
||||
padding: 0.8rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
font-size: inherit;
|
||||
transition: all 0.25s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #0069d9;
|
||||
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: #f1f5f9;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: #e2e8f0;
|
||||
}
|
||||
|
||||
/* 로딩 애니메이션 향상 */
|
||||
.loading-indicator {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
border: 4px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 50%;
|
||||
border-top: 4px solid #007bff;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 통계 카드 스타일 향상 */
|
||||
.visual-stats-summary {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.stats-card {
|
||||
background-color: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||
padding: 1.2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.stats-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.stats-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.stats-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stats-number {
|
||||
font-size: 1.8rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.stats-label {
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
|
||||
/* 카드 색상 */
|
||||
.total-institutions .stats-icon {
|
||||
background-color: rgba(0, 123, 255, 0.3);
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.normal-institutions .stats-icon {
|
||||
background-color: rgba(40, 167, 69, 0.3);
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.warning-institutions .stats-icon {
|
||||
background-color: rgba(255, 193, 7, 0.3);
|
||||
color: #ffc107;
|
||||
}
|
||||
|
||||
.error-institutions .stats-icon {
|
||||
background-color: rgba(220, 53, 69, 0.3);
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.inactive-institutions .stats-icon {
|
||||
background-color: rgba(108, 117, 125, 0.3);
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
/* 기관 카드 그리드 향상 */
|
||||
.connection-visual-container {
|
||||
background-color: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.institution-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.institution-card {
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 12px;
|
||||
padding: 1.2rem;
|
||||
transition: all 0.3s ease;
|
||||
border-left: 5px solid #ccc;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.institution-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.institution-status-indicator {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.institution-name {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1rem;
|
||||
color: #333;
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
.institution-info {
|
||||
display: grid;
|
||||
grid-template-columns: 80px 1fr;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.institution-info-label {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.institution-stats {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0.5rem;
|
||||
margin-top: 1rem;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
padding-top: 0.8rem;
|
||||
}
|
||||
|
||||
.institution-stat {
|
||||
text-align: center;
|
||||
padding: 0.5rem;
|
||||
border-radius: 8px;
|
||||
background-color: rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
/* 상태 배지 스타일 */
|
||||
.status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
padding: 0.4rem 0.8rem;
|
||||
border-radius: 50px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.status-badge-normal {
|
||||
background-color: rgba(40, 167, 69, 0.15);
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.status-badge-warning {
|
||||
background-color: rgba(255, 193, 7, 0.15);
|
||||
color: #ffc107;
|
||||
}
|
||||
|
||||
.status-badge-error {
|
||||
background-color: rgba(220, 53, 69, 0.15);
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.status-badge-inactive {
|
||||
background-color: rgba(108, 117, 125, 0.15);
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
/* 반응형 디자인 */
|
||||
@media (max-width: 1024px) {
|
||||
.visual-stats-summary {
|
||||
grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
|
||||
}
|
||||
|
||||
.modal-modern .modal-content {
|
||||
width: 98%;
|
||||
margin: 1% auto;
|
||||
}
|
||||
|
||||
.modal-modern .modal-body {
|
||||
padding: 1rem 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.socket-detail-tabs {
|
||||
padding: 0.3rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
padding: 0.8rem 1rem;
|
||||
font-size: 0.9rem;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.visual-stats-summary {
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 0.8rem;
|
||||
}
|
||||
|
||||
.stats-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.stats-number {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.institution-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||
}
|
||||
|
||||
.modal-modern .modal-header,
|
||||
.modal-modern .modal-footer {
|
||||
padding: 1rem 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.visual-stats-summary {
|
||||
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||
}
|
||||
|
||||
.institution-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.modal-modern .modal-body {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.institution-card.status-normal {
|
||||
border-left: 5px solid #98e2a6; /* 파스텔 그린 */
|
||||
}
|
||||
|
||||
.institution-card.status-warning {
|
||||
border-left: 5px solid #ffe5a3; /* 파스텔 옐로우 */
|
||||
}
|
||||
|
||||
.institution-card.status-error {
|
||||
border-left: 5px solid #ffb3b3; /* 파스텔 레드 */
|
||||
}
|
||||
|
||||
.institution-card.status-inactive {
|
||||
border-left: 5px solid #d9d9d9; /* 파스텔 그레이 */
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// 소켓 상세 정보 모달 관련 스크립트
|
||||
(function() {
|
||||
// 모달 요소
|
||||
const modal = document.getElementById('socketDetailModal');
|
||||
const closeButtons = modal.querySelectorAll('.close-modal, .close-modal-btn');
|
||||
|
||||
// 탭 관련 요소
|
||||
const tabButtons = modal.querySelectorAll('.tab-btn');
|
||||
const tabContents = modal.querySelectorAll('.tab-content');
|
||||
|
||||
// 테이블 요소
|
||||
const institutionTable = modal.querySelector('.institution-status-table tbody');
|
||||
const statusTable = modal.querySelector('.socket-status-table tbody');
|
||||
const processTable = modal.querySelector('.socket-process-table tbody');
|
||||
|
||||
// 시각화 요소
|
||||
const institutionGrid = document.getElementById('institution-grid');
|
||||
const totalInstitutionsCount = document.getElementById('total-institutions-count');
|
||||
const normalInstitutionsCount = document.getElementById('normal-institutions-count');
|
||||
const warningInstitutionsCount = document.getElementById('warning-institutions-count');
|
||||
const errorInstitutionsCount = document.getElementById('error-institutions-count');
|
||||
const inactiveInstitutionsCount = document.getElementById('inactive-institutions-count');
|
||||
|
||||
// 현재 선택된 서버와 어댑터 그룹
|
||||
let currentServerName = '';
|
||||
let currentAdapterGroup = '';
|
||||
|
||||
// 모달 닫기 이벤트
|
||||
closeButtons.forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
modal.style.display = 'none';
|
||||
});
|
||||
});
|
||||
|
||||
// 모달 외부 클릭 시 닫기
|
||||
window.addEventListener('click', (event) => {
|
||||
if (event.target === modal) {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// 탭 전환 이벤트
|
||||
tabButtons.forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
// 활성 탭 변경
|
||||
tabButtons.forEach(btn => btn.classList.remove('active'));
|
||||
button.classList.add('active');
|
||||
|
||||
// 탭 컨텐츠 변경
|
||||
const tabId = button.getAttribute('data-tab');
|
||||
tabContents.forEach(content => {
|
||||
content.classList.remove('active');
|
||||
if (content.id === tabId) {
|
||||
content.classList.add('active');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 새로고침 버튼 이벤트
|
||||
const refreshButton = modal.querySelector('.refresh-socket-data');
|
||||
refreshButton.addEventListener('click', () => {
|
||||
loadSocketData(currentServerName, currentAdapterGroup);
|
||||
});
|
||||
|
||||
// 소켓 데이터 로드 함수
|
||||
function loadSocketData(serverName, adapterGroup) {
|
||||
// 현재 선택된 서버와 어댑터 그룹 저장
|
||||
currentServerName = serverName || '';
|
||||
currentAdapterGroup = adapterGroup || '';
|
||||
|
||||
// 로딩 표시
|
||||
institutionTable.innerHTML = '<tr><td colspan="6"><div class="loading-indicator"><div class="loading-spinner"></div></div></td></tr>';
|
||||
statusTable.innerHTML = '<tr><td colspan="7"><div class="loading-indicator"><div class="loading-spinner"></div></div></td></tr>';
|
||||
processTable.innerHTML = '<tr><td colspan="10"><div class="loading-indicator"><div class="loading-spinner"></div></div></td></tr>';
|
||||
institutionGrid.innerHTML = '<div class="loading-indicator"><div class="loading-spinner"></div></div>';
|
||||
|
||||
// API 호출
|
||||
ModernDashboardApi.getSocketCombined(serverName, adapterGroup, function(data) {
|
||||
if (data) {
|
||||
// 기관 상태 데이터 표시
|
||||
updateInstitutionStatusTable(data.institutionStatus);
|
||||
|
||||
// 기관 시각화 업데이트
|
||||
updateInstitutionVisualization(data.institutionStatus);
|
||||
|
||||
// 회선 상태 데이터 표시
|
||||
updateSocketStatusTable(data.socketStatus);
|
||||
|
||||
// 회선 연결 상태 데이터 표시
|
||||
updateSocketProcessTable(data.socketProcess);
|
||||
}
|
||||
}, function(xhr, status, error) {
|
||||
// 에러 처리
|
||||
const errorMsg = '<div style="padding: 2rem; text-align: center; color: #dc3545;"><i class="fas fa-exclamation-circle" style="font-size: 3rem;"></i><p style="margin-top: 1rem;">데이터를 불러오는 중 오류가 발생했습니다.</p></div>';
|
||||
|
||||
institutionTable.innerHTML = '<tr><td colspan="6">' + errorMsg + '</td></tr>';
|
||||
statusTable.innerHTML = '<tr><td colspan="7">' + errorMsg + '</td></tr>';
|
||||
processTable.innerHTML = '<tr><td colspan="10">' + errorMsg + '</td></tr>';
|
||||
institutionGrid.innerHTML = errorMsg;
|
||||
console.error('소켓 데이터 로드 실패:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// 기관 상태 테이블 업데이트
|
||||
function updateInstitutionStatusTable(data) {
|
||||
if (!data || data.length === 0) {
|
||||
institutionTable.innerHTML = '<tr><td colspan="6"><div style="padding: 2rem; text-align: center; color: #6c757d;"><i class="fas fa-info-circle" style="font-size: 2rem;"></i><p style="margin-top: 1rem;">표시할 데이터가 없습니다.</p></div></td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
data.forEach(institution => {
|
||||
let statusClass = '';
|
||||
let statusText = '';
|
||||
|
||||
// 상태에 따른 텍스트 및 클래스 설정
|
||||
if (institution.lineStatus === 'UNDEFINED') {
|
||||
statusText = '미사용';
|
||||
statusClass = 'status-badge status-badge-inactive';
|
||||
} else if (institution.lineStatus === 'WARN') {
|
||||
statusText = '경고';
|
||||
statusClass = 'status-badge status-badge-warning';
|
||||
} else if (institution.lineStatus === 'ERROR') {
|
||||
statusText = '오류';
|
||||
statusClass = 'status-badge status-badge-error';
|
||||
} else {
|
||||
statusText = '정상';
|
||||
statusClass = 'status-badge status-badge-normal';
|
||||
}
|
||||
|
||||
html += '<tr>' +
|
||||
'<td>' + (institution.institutionName || '-') + '</td>' +
|
||||
'<td>' + (institution.contactPerson || '-') + '</td>' +
|
||||
'<td>' + (institution.contactNumber || '-') + '</td>' +
|
||||
'<td>' + (institution.normalAdapterCount || '0') + '</td>' +
|
||||
'<td>' + (institution.errorAdapterCount || '0') + '</td>' +
|
||||
'<td><span class="' + statusClass + '">' + statusText + '</span></td>' +
|
||||
'</tr>';
|
||||
});
|
||||
|
||||
institutionTable.innerHTML = html;
|
||||
}
|
||||
|
||||
// 기관 시각화 업데이트
|
||||
function updateInstitutionVisualization(data) {
|
||||
if (!data || data.length === 0) {
|
||||
institutionGrid.innerHTML = '<div style="padding: 2rem; text-align: center; color: #6c757d;"><i class="fas fa-info-circle" style="font-size: 2rem;"></i><p style="margin-top: 1rem;">표시할 데이터가 없습니다.</p></div>';
|
||||
updateStatsCounts(0, 0, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
let normalCount = 0;
|
||||
let warningCount = 0;
|
||||
let errorCount = 0;
|
||||
let inactiveCount = 0;
|
||||
|
||||
data.forEach(institution => {
|
||||
let statusClass = 'status-normal';
|
||||
let statusIcon = '<i class="bi bi-check-circle" style="color: #28a745;"></i>';
|
||||
let statusName = '정상';
|
||||
|
||||
// 상태에 따른 시각화 설정
|
||||
if (institution.lineStatus === 'UNDEFINED') {
|
||||
statusClass = 'status-inactive';
|
||||
statusIcon = '<i class="bi bi-dash-circle" style="color: #6c757d;"></i>';
|
||||
statusName = '미사용';
|
||||
inactiveCount++;
|
||||
} else if (institution.lineStatus === 'WARN') {
|
||||
statusClass = 'status-warning';
|
||||
statusIcon = '<i class="bi bi-exclamation-triangle" style="color: #ffc107;"></i>';
|
||||
statusName = '경고';
|
||||
warningCount++;
|
||||
} else if (institution.lineStatus === 'ERROR') {
|
||||
statusClass = 'status-error';
|
||||
statusIcon = '<i class="bi bi-x-circle" style="color: #dc3545;"></i>';
|
||||
statusName = '오류';
|
||||
errorCount++;
|
||||
} else {
|
||||
normalCount++;
|
||||
}
|
||||
|
||||
html += '<div class="institution-card ' + statusClass + '" data-code="' + institution.institutionCode + '">' +
|
||||
'<div class="institution-status-indicator">' + statusIcon + '</div>' +
|
||||
'<div class="institution-name">' + (institution.institutionName || '미지정 기관') + '</div>' +
|
||||
'<div class="institution-info">' +
|
||||
'<span class="institution-info-label">담당자:</span>' +
|
||||
'<span>' + (institution.contactPerson || '-') + '</span>' +
|
||||
'</div>' +
|
||||
'<div class="institution-info">' +
|
||||
'<span class="institution-info-label">연락처:</span>' +
|
||||
'<span>' + (institution.contactNumber || '-') + '</span>' +
|
||||
'</div>' +
|
||||
'<div class="institution-info">' +
|
||||
'<span class="institution-info-label">상태:</span>' +
|
||||
'<span>' + statusName + '</span>' +
|
||||
'</div>' +
|
||||
'<div class="institution-stats">' +
|
||||
'<div class="institution-stat">' +
|
||||
'<div class="stat-number normal">' + (institution.normalAdapterCount || '0') + '</div>' +
|
||||
'<div class="stat-label">정상 어댑터</div>' +
|
||||
'</div>' +
|
||||
'<div class="institution-stat">' +
|
||||
'<div class="stat-number error">' + (institution.errorAdapterCount || '0') + '</div>' +
|
||||
'<div class="stat-label">오류 어댑터</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
});
|
||||
|
||||
institutionGrid.innerHTML = html;
|
||||
updateStatsCounts(data.length, normalCount, warningCount, errorCount, inactiveCount);
|
||||
|
||||
// 기관 카드 클릭 이벤트 - 해당 기관 정보로 필터링
|
||||
const institutionCards = institutionGrid.querySelectorAll('.institution-card');
|
||||
institutionCards.forEach(card => {
|
||||
card.addEventListener('click', () => {
|
||||
const institutionCode = card.getAttribute('data-code');
|
||||
// 여기에 기관 코드로 필터링하는 로직 추가
|
||||
console.log('기관 선택:', institutionCode);
|
||||
|
||||
// 기관에 해당하는 어댑터로 필터링하는 탭으로 이동
|
||||
const socketStatusTab = document.querySelector('.tab-btn[data-tab="socket-status"]');
|
||||
socketStatusTab.click();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 통계 수치 업데이트
|
||||
function updateStatsCounts(total, normal, warning, error, inactive) {
|
||||
totalInstitutionsCount.textContent = total;
|
||||
normalInstitutionsCount.textContent = normal;
|
||||
warningInstitutionsCount.textContent = warning;
|
||||
errorInstitutionsCount.textContent = error;
|
||||
inactiveInstitutionsCount.textContent = inactive;
|
||||
}
|
||||
|
||||
// 회선 상태 테이블 업데이트
|
||||
function updateSocketStatusTable(data) {
|
||||
if (!data || !data.rows || data.rows.length === 0) {
|
||||
statusTable.innerHTML = '<tr><td colspan="7"><div style="padding: 2rem; text-align: center; color: #6c757d;"><i class="fas fa-info-circle" style="font-size: 2rem;"></i><p style="margin-top: 1rem;">표시할 데이터가 없습니다.</p></div></td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
data.rows.forEach(row => {
|
||||
let statusClass = row.status === 'true' ? 'status-badge status-badge-normal' : 'status-badge status-badge-error';
|
||||
const statusName = row.status === 'true' ? '정상' : '오류';
|
||||
|
||||
html += '<tr>' +
|
||||
'<td>' + (row.adptrBzwkGroupName || '-') + '</td>' +
|
||||
'<td>' + (row.eaiSevrInstncName || '-') + '</td>' +
|
||||
'<td>' + (row.adptrNickName || '-') + '</td>' +
|
||||
'<td>' + (row.adptrBzwkDesc || '-') + '</td>' +
|
||||
'<td>' + (row.adptrType || '-') + '</td>' +
|
||||
'<td><span class="' + statusClass + '">' + statusName + '</span></td>' +
|
||||
'<td>' + (row.starting || '-') + '</td>' +
|
||||
'</tr>';
|
||||
});
|
||||
|
||||
statusTable.innerHTML = html;
|
||||
}
|
||||
|
||||
// 회선 연결 상태 테이블 업데이트
|
||||
function updateSocketProcessTable(data) {
|
||||
if (!data || !data.rows || data.rows.length === 0) {
|
||||
processTable.innerHTML = '<tr><td colspan="10"><div style="padding: 2rem; text-align: center; color: #6c757d;"><i class="fas fa-info-circle" style="font-size: 2rem;"></i><p style="margin-top: 1rem;">표시할 데이터가 없습니다.</p></div></td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
data.rows.forEach(row => {
|
||||
let statusClass = row.status === 'true' ? 'status-badge status-badge-normal' : 'status-badge status-badge-error';
|
||||
const statusName = row.status === 'true' ? '정상' : '오류';
|
||||
|
||||
html += '<tr>' +
|
||||
'<td>' + (row.adptrBzwkGroupName || '-') + '</td>' +
|
||||
'<td>' + (row.eaiSevrInstncName || '-') + '</td>' +
|
||||
'<td>' + (row.adptrBzwkName || '-') + '</td>' +
|
||||
'<td>' + (row.adptrBzwkDesc || '-') + '</td>' +
|
||||
'<td>' + (row.maxConnection || '0') + '</td>' +
|
||||
'<td>' + (row.currentConnection || '0') + '</td>' +
|
||||
'<td>' + (row.sendCount || '0') + '</td>' +
|
||||
'<td>' + (row.recvCount || '0') + '</td>' +
|
||||
'<td>' + (row.errorCount || '0') + '</td>' +
|
||||
'<td><span class="' + statusClass + '">' + statusName + '</span></td>' +
|
||||
'</tr>';
|
||||
});
|
||||
|
||||
processTable.innerHTML = html;
|
||||
}
|
||||
|
||||
// 모달 열기 함수 (전역 스코프에 노출)
|
||||
window.openSocketDetailModal = function(serverName, adapterGroup) {
|
||||
// 모달 표시
|
||||
modal.style.display = 'block';
|
||||
|
||||
// 데이터 로드
|
||||
loadSocketData(serverName, adapterGroup);
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
@@ -0,0 +1,699 @@
|
||||
<%@ page language="java" contentType="text/html; charset=utf-8"%>
|
||||
|
||||
<div class="dashboard-summary">
|
||||
<h2 class="section-title">실시간 트랜잭션 현황</h2>
|
||||
<div class="card">
|
||||
<div class="chart-controls">
|
||||
<div class="btn-group" style="display: none">
|
||||
<button class="btn btn-chart-type" data-type="area">영역</button>
|
||||
<button class="btn btn-chart-type active" data-type="line">선형</button>
|
||||
<button class="btn btn-chart-type" data-type="bar">막대</button>
|
||||
</div>
|
||||
<div class="btn-group ml-2">
|
||||
<button class="btn btn-time-range" data-range="5m">5분</button>
|
||||
<button class="btn btn-time-range active" data-range="10m">10분</button>
|
||||
<button class="btn btn-time-range" data-range="30m">30분</button>
|
||||
<button class="btn btn-time-range" data-range="1h">1시간</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-container">
|
||||
<div id="transaction-chart" class="chart-content">
|
||||
<i class="bi bi-bar-chart-line"></i>
|
||||
<span>트랜잭션 데이터를 로딩 중입니다...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ApexCharts 라이브러리 로드 -->
|
||||
<script src="<%=request.getContextPath()%>/addon/apexcharts/apexcharts.js"></script>
|
||||
|
||||
<!-- 트랜잭션 차트 관련 스크립트 -->
|
||||
<script>
|
||||
// 트랜잭션 시계열 데이터 관리 객체
|
||||
const TransactionTimeSeriesData = {
|
||||
// 서비스별 시계열 데이터를 저장할 맵
|
||||
serviceData: {},
|
||||
|
||||
// 최대 데이터 포인트 수 (60분 간격, 10초마다 갱신 = 360개 포인트)
|
||||
maxDataPoints: 360,
|
||||
|
||||
// 초기화
|
||||
init: function() {
|
||||
// 서비스 목록 초기화
|
||||
this.initServiceTypes();
|
||||
},
|
||||
|
||||
// 서비스 유형 초기화
|
||||
initServiceTypes: function() {
|
||||
// 서버 데이터 배열에서 서비스 타입 추출
|
||||
if (window.serverDataArray) {
|
||||
const uniqueTypes = new Set();
|
||||
serverDataArray.forEach(server => {
|
||||
uniqueTypes.add(server.type);
|
||||
});
|
||||
|
||||
// 각 고유 서비스 타입에 대해 시계열 데이터 초기화
|
||||
uniqueTypes.forEach(serviceType => {
|
||||
if (!this.serviceData[serviceType]) {
|
||||
this.serviceData[serviceType] = {
|
||||
timestamps: [],
|
||||
success: [],
|
||||
fail: [],
|
||||
timeout: [],
|
||||
syserror: []
|
||||
};
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 기본 서비스 타입 (기본값)
|
||||
const defaultTypes = ['EAI', 'FEP', 'MCI', 'MCU', 'BAP'];
|
||||
defaultTypes.forEach(serviceType => {
|
||||
this.serviceData[serviceType] = {
|
||||
timestamps: [],
|
||||
success: [],
|
||||
fail: [],
|
||||
timeout: [],
|
||||
syserror: []
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// 합계 데이터를 위한 TOTAL 서비스 추가
|
||||
this.serviceData['TOTAL'] = {
|
||||
timestamps: [],
|
||||
success: [],
|
||||
fail: [],
|
||||
timeout: [],
|
||||
syserror: []
|
||||
};
|
||||
},
|
||||
|
||||
// 타임스탬프를 Date 객체로 변환
|
||||
ensureDateObject: function(timestamp) {
|
||||
if (timestamp instanceof Date) {
|
||||
return timestamp;
|
||||
} else if (typeof timestamp === 'number') {
|
||||
return new Date(timestamp);
|
||||
} else if (typeof timestamp === 'string') {
|
||||
return new Date(timestamp);
|
||||
}
|
||||
return new Date(); // 기본값으로 현재 시간 반환
|
||||
},
|
||||
|
||||
// 트랜잭션 데이터 업데이트
|
||||
updateData: function(transactions) {
|
||||
if (!transactions || !transactions.length) return;
|
||||
|
||||
const timestamp = new Date();
|
||||
|
||||
// 각 서비스별 데이터 업데이트
|
||||
transactions.forEach(transaction => {
|
||||
const serviceType = transaction.type;
|
||||
|
||||
// 해당 서비스의 시계열 데이터가 없으면 초기화
|
||||
if (!this.serviceData[serviceType]) {
|
||||
this.serviceData[serviceType] = {
|
||||
timestamps: [],
|
||||
success: [],
|
||||
fail: [],
|
||||
timeout: [],
|
||||
syserror: []
|
||||
};
|
||||
}
|
||||
|
||||
const data = this.serviceData[serviceType];
|
||||
|
||||
// 타임스탬프 추가
|
||||
data.timestamps.push(timestamp);
|
||||
|
||||
// 데이터 추가
|
||||
data.success.push(transaction.success || 0);
|
||||
data.fail.push(transaction.fail || 0);
|
||||
data.timeout.push(transaction.timeout || 0);
|
||||
data.syserror.push(transaction.syserror || 0);
|
||||
|
||||
// 최대 데이터 포인트 수 제한
|
||||
if (data.timestamps.length > this.maxDataPoints) {
|
||||
data.timestamps.shift();
|
||||
data.success.shift();
|
||||
data.fail.shift();
|
||||
data.timeout.shift();
|
||||
data.syserror.shift();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 오래된 데이터 정리 (60분 이상 경과된 데이터)
|
||||
cleanupData: function() {
|
||||
const cutoffTime = new Date(new Date() - 60 * 60 * 1000); // 60분 전
|
||||
|
||||
for (const serviceType in this.serviceData) {
|
||||
const data = this.serviceData[serviceType];
|
||||
|
||||
// 각 데이터 시리즈에서 60분 이전 데이터 제거
|
||||
let i = 0;
|
||||
while (i < data.timestamps.length && this.ensureDateObject(data.timestamps[i]) < cutoffTime) {
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i > 0) {
|
||||
data.timestamps = data.timestamps.slice(i);
|
||||
data.success = data.success.slice(i);
|
||||
data.fail = data.fail.slice(i);
|
||||
data.timeout = data.timeout.slice(i);
|
||||
data.syserror = data.syserror.slice(i);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 특정 서비스의 시계열 데이터 가져오기
|
||||
getServiceData: function(serviceType) {
|
||||
return this.serviceData[serviceType] || {
|
||||
timestamps: [],
|
||||
success: [],
|
||||
fail: [],
|
||||
timeout: [],
|
||||
syserror: []
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
// 트랜잭션 차트 관리 객체
|
||||
const TransactionChart = {
|
||||
chart: null,
|
||||
chartType: 'line', // 기본값: 영역 차트
|
||||
timeRange: '10m', // 기본값: 5분
|
||||
|
||||
// 초기화
|
||||
init: function(containerId) {
|
||||
// ApexCharts 로드 확인
|
||||
if (typeof ApexCharts === 'undefined') {
|
||||
console.error('ApexCharts 라이브러리가 로드되지 않았습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
const container = document.getElementById(containerId);
|
||||
if (!container) {
|
||||
console.error('차트 컨테이너를 찾을 수 없습니다: ' + containerId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 기존 차트 제거
|
||||
if (this.chart) {
|
||||
this.chart.destroy();
|
||||
}
|
||||
|
||||
// 새 차트 인스턴스 생성
|
||||
const options = this.getChartOptions();
|
||||
this.chart = new ApexCharts(container, options);
|
||||
this.chart.render();
|
||||
|
||||
// 브라우저 창 크기 변경 시 차트 크기 조정
|
||||
window.addEventListener('resize', () => {
|
||||
if (this.chart) {
|
||||
this.chart.updateOptions({
|
||||
chart: {
|
||||
width: '100%'
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 차트 옵션 가져오기
|
||||
getChartOptions: function() {
|
||||
return {
|
||||
chart: {
|
||||
type: this.chartType,
|
||||
height: 180,
|
||||
toolbar: {
|
||||
show: false
|
||||
},
|
||||
animations: {
|
||||
enabled: true,
|
||||
easing: 'easeinout',
|
||||
speed: 800,
|
||||
animateGradually: {
|
||||
enabled: true,
|
||||
delay: 150
|
||||
},
|
||||
dynamicAnimation: {
|
||||
enabled: true,
|
||||
speed: 350
|
||||
}
|
||||
},
|
||||
fontFamily: 'Noto Sans KR, sans-serif',
|
||||
zoom: {
|
||||
enabled: false
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
stroke: {
|
||||
curve: 'smooth',
|
||||
width: 2
|
||||
},
|
||||
series: [],
|
||||
colors: ['#4CAF50', '#F44336', '#2196F3', '#FF5722', '#9C27B0', '#FF9800'],
|
||||
fill: {
|
||||
type: this.chartType === 'area' ? 'gradient' : 'solid',
|
||||
gradient: {
|
||||
shade: 'light',
|
||||
type: 'vertical',
|
||||
shadeIntensity: 0.3,
|
||||
opacityFrom: 0.7,
|
||||
opacityTo: 0.2,
|
||||
stops: [0, 90, 100]
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
borderColor: '#e0e0e0',
|
||||
strokeDashArray: 3,
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
shared: true,
|
||||
intersect: false,
|
||||
y: {
|
||||
formatter: function(value) {
|
||||
return value.toLocaleString() + ' 건';
|
||||
}
|
||||
},
|
||||
x: {
|
||||
formatter: function(value) {
|
||||
const date = new Date(value);
|
||||
return date.toLocaleTimeString();
|
||||
}
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
labels: {
|
||||
datetimeUTC: false,
|
||||
format: 'HH:mm'
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false
|
||||
},
|
||||
tickAmount: 6
|
||||
},
|
||||
yaxis: {
|
||||
title: {
|
||||
text: '처리 건수',
|
||||
style: {
|
||||
fontWeight: 600
|
||||
}
|
||||
},
|
||||
labels: {
|
||||
formatter: function(value) {
|
||||
return value.toLocaleString();
|
||||
}
|
||||
},
|
||||
forceNiceScale: true,
|
||||
min: undefined,
|
||||
max: undefined
|
||||
},
|
||||
legend: {
|
||||
position: 'top',
|
||||
horizontalAlign: 'right',
|
||||
floating: true,
|
||||
offsetY: -25,
|
||||
offsetX: -5,
|
||||
showForSingleSeries: true,
|
||||
onItemClick: {
|
||||
toggleDataSeries: true
|
||||
},
|
||||
onItemHover: {
|
||||
highlightDataSeries: true
|
||||
},
|
||||
markers: {
|
||||
width: 12,
|
||||
height: 12,
|
||||
strokeWidth: 0,
|
||||
radius: 12,
|
||||
offsetX: -5
|
||||
},
|
||||
itemMargin: {
|
||||
horizontal: 10,
|
||||
vertical: 5
|
||||
}
|
||||
},
|
||||
states: {
|
||||
hover: {
|
||||
filter: {
|
||||
type: 'lighten',
|
||||
value: 0.05
|
||||
}
|
||||
},
|
||||
active: {
|
||||
filter: {
|
||||
type: 'darken',
|
||||
value: 0.1
|
||||
}
|
||||
}
|
||||
},
|
||||
responsive: [
|
||||
{
|
||||
breakpoint: 768,
|
||||
options: {
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
horizontalAlign: 'center',
|
||||
floating: false,
|
||||
offsetY: 0,
|
||||
offsetX: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
|
||||
// 서비스 타입에 따른 색상 맵
|
||||
colorMap: {
|
||||
'EAI': { success: '#4CAF50', error: '#F44336' },
|
||||
'FEP': { success: '#2196F3', error: '#FF5722' },
|
||||
'MCI': { success: '#9C27B0', error: '#FF9800' },
|
||||
'MCU': { success: '#3F51B5', error: '#FFC107' },
|
||||
'BAP': { success: '#00BCD4', error: '#795548' },
|
||||
'TOTAL': { success: '#607D8B', error: '#9E9E9E' }
|
||||
},
|
||||
|
||||
// 서비스 및 데이터 타입에 따른 색상 반환
|
||||
getServiceColor: function(serviceType, dataType) {
|
||||
if (this.colorMap[serviceType] && this.colorMap[serviceType][dataType]) {
|
||||
return this.colorMap[serviceType][dataType];
|
||||
}
|
||||
|
||||
// 기본 색상
|
||||
return dataType === 'success' ? '#66BB6A' : '#EF5350';
|
||||
},
|
||||
|
||||
// 차트 타입 설정
|
||||
setChartType: function(type) {
|
||||
this.chartType = type;
|
||||
this.updateChart();
|
||||
},
|
||||
|
||||
// 시간 범위 설정
|
||||
setTimeRange: function(range) {
|
||||
this.timeRange = range;
|
||||
this.updateChart();
|
||||
},
|
||||
|
||||
// 차트 업데이트
|
||||
updateChart: function() {
|
||||
if (!this.chart) return;
|
||||
|
||||
const series = [];
|
||||
|
||||
// 각 서비스에 대한 시리즈 생성 (TOTAL 제외)
|
||||
for (const serviceType in TransactionTimeSeriesData.serviceData) {
|
||||
if (serviceType === 'TOTAL') continue;
|
||||
|
||||
const serviceData = TransactionTimeSeriesData.serviceData[serviceType];
|
||||
if (!serviceData.timestamps.length) continue;
|
||||
|
||||
// 성공 시리즈
|
||||
series.push({
|
||||
name: serviceType + ' 성공',
|
||||
type: this.chartType,
|
||||
data: serviceData.timestamps.map((time, index) => ({
|
||||
x: TransactionTimeSeriesData.ensureDateObject(time).getTime(),
|
||||
y: serviceData.success[index]
|
||||
})),
|
||||
color: this.getServiceColor(serviceType, 'success')
|
||||
});
|
||||
|
||||
// 에러 시리즈 (실패 + 타임아웃 + 시스템 에러)
|
||||
series.push({
|
||||
name: serviceType + ' 에러',
|
||||
type: this.chartType,
|
||||
data: serviceData.timestamps.map((time, index) => ({
|
||||
x: TransactionTimeSeriesData.ensureDateObject(time).getTime(),
|
||||
y: serviceData.fail[index] + serviceData.timeout[index] + serviceData.syserror[index]
|
||||
})),
|
||||
color: this.getServiceColor(serviceType, 'error')
|
||||
});
|
||||
}
|
||||
|
||||
// 시간 범위에 따른 X축 범위 설정
|
||||
const rangeMinutes = {
|
||||
'5m': 5,
|
||||
'10m': 10,
|
||||
'30m': 30,
|
||||
'1h': 60
|
||||
}[this.timeRange] || 10;
|
||||
|
||||
const startTime = new Date(new Date() - rangeMinutes * 60 * 1000);
|
||||
const endTime = new Date();
|
||||
|
||||
// 차트 옵션 업데이트 - 전체 다시 그리기 방지
|
||||
this.chart.updateOptions({
|
||||
chart: {
|
||||
type: this.chartType
|
||||
},
|
||||
fill: {
|
||||
type: this.chartType === 'area' ? 'gradient' : 'solid'
|
||||
},
|
||||
xaxis: {
|
||||
min: startTime.getTime(),
|
||||
max: endTime.getTime() + 1000
|
||||
}
|
||||
}, false, false); // 차트 다시 그리지 않고 업데이트
|
||||
|
||||
// 시리즈 데이터만 업데이트
|
||||
this.chart.updateSeries(series, false);
|
||||
},
|
||||
|
||||
// 모의 데이터로 차트 업데이트 (테스트용)
|
||||
updateWithMockData: function() {
|
||||
const mockData = TransactionTimeSeriesData.generateMockData();
|
||||
TransactionTimeSeriesData.updateData(mockData);
|
||||
this.updateChart();
|
||||
}
|
||||
};
|
||||
|
||||
// 문서 로드 완료 시 초기화
|
||||
$(document).ready(function() {
|
||||
// 시계열 데이터 관리 객체 초기화
|
||||
TransactionTimeSeriesData.init();
|
||||
|
||||
// 차트 초기화
|
||||
TransactionChart.init('transaction-chart');
|
||||
|
||||
// 차트 컨트롤 이벤트 설정
|
||||
setupChartControls();
|
||||
|
||||
// 초기 데이터 로드
|
||||
loadInitialChartData();
|
||||
|
||||
// 10초마다 오래된 데이터 정리
|
||||
setInterval(() => TransactionTimeSeriesData.cleanupData(), 10000);
|
||||
|
||||
// DashboardEventManager를 사용하여 새로고침 이벤트 구독
|
||||
if (window.DashboardEventManager) {
|
||||
window.DashboardEventManager.on('refresh', function() {
|
||||
// 트랜잭션 데이터 추가 로드
|
||||
loadSummaryTransactionData();
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// 차트 컨트롤 이벤트 설정
|
||||
function setupChartControls() {
|
||||
// 차트 타입 변경
|
||||
$('.btn-chart-type').click(function() {
|
||||
// 기존 활성화 버튼 비활성화
|
||||
$('.btn-chart-type').removeClass('active');
|
||||
|
||||
// 클릭한 버튼 활성화
|
||||
$(this).addClass('active');
|
||||
|
||||
// 차트 타입 변경
|
||||
const chartType = $(this).data('type');
|
||||
TransactionChart.setChartType(chartType);
|
||||
});
|
||||
|
||||
// 시간 범위 변경
|
||||
$('.btn-time-range').click(function() {
|
||||
// 기존 활성화 버튼 비활성화
|
||||
$('.btn-time-range').removeClass('active');
|
||||
|
||||
// 클릭한 버튼 활성화
|
||||
$(this).addClass('active');
|
||||
|
||||
// 시간 범위 변경
|
||||
const timeRange = $(this).data('range');
|
||||
TransactionChart.setTimeRange(timeRange);
|
||||
});
|
||||
}
|
||||
|
||||
// 초기 차트 데이터 로드
|
||||
function loadInitialChartData() {
|
||||
// 시계열 데이터 API 호출
|
||||
ModernDashboardApi.getTransactionTimeSeries(null, 10, function(data) {
|
||||
if (data && data.timeSeriesData) {
|
||||
// 각 서비스별 시계열 데이터 처리
|
||||
for (var serviceType in data.timeSeriesData) {
|
||||
var serviceData = data.timeSeriesData[serviceType];
|
||||
if (!serviceData.timestamps || serviceData.timestamps.length === 0) continue;
|
||||
|
||||
// 기존 데이터 초기화
|
||||
TransactionTimeSeriesData.serviceData[serviceType] = {
|
||||
timestamps: [],
|
||||
success: [],
|
||||
fail: [],
|
||||
timeout: [],
|
||||
syserror: []
|
||||
};
|
||||
|
||||
// API 응답 데이터 복사 및 타임스탬프 변환
|
||||
TransactionTimeSeriesData.serviceData[serviceType].timestamps = serviceData.timestamps.map(time =>
|
||||
TransactionTimeSeriesData.ensureDateObject(time)
|
||||
);
|
||||
TransactionTimeSeriesData.serviceData[serviceType].success = serviceData.success.slice();
|
||||
TransactionTimeSeriesData.serviceData[serviceType].fail = serviceData.fail.slice();
|
||||
TransactionTimeSeriesData.serviceData[serviceType].timeout = serviceData.timeout.slice();
|
||||
TransactionTimeSeriesData.serviceData[serviceType].syserror = serviceData.syserror.slice();
|
||||
}
|
||||
|
||||
// 차트 업데이트
|
||||
TransactionChart.updateChart();
|
||||
} else {
|
||||
// API 응답이 없는 경우 모의 데이터로 테스트
|
||||
TransactionChart.updateWithMockData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 트랜잭션 데이터 로드 및 차트 업데이트 (함수명 변경)
|
||||
function loadSummaryTransactionData() {
|
||||
|
||||
// 현재 선택된 시간 범위 가져오기
|
||||
var activeRange = $('.btn-time-range.active').data('range') || '10m';
|
||||
var minutes = {
|
||||
'5m': 5,
|
||||
'10m': 10,
|
||||
'30m': 30,
|
||||
'1h': 60
|
||||
}[activeRange] || 10;
|
||||
|
||||
ModernDashboardApi.getTransactionTimeSeries(null, minutes, function(data) {
|
||||
if (data && data.timeSeriesData) {
|
||||
// 각 서비스별 시계열 데이터 처리
|
||||
for (var serviceType in data.timeSeriesData) {
|
||||
var serviceData = data.timeSeriesData[serviceType];
|
||||
if (!serviceData.timestamps || serviceData.timestamps.length === 0) continue;
|
||||
|
||||
// 기존 데이터 초기화
|
||||
TransactionTimeSeriesData.serviceData[serviceType] = {
|
||||
timestamps: [],
|
||||
success: [],
|
||||
fail: [],
|
||||
timeout: [],
|
||||
syserror: []
|
||||
};
|
||||
|
||||
// API 응답 데이터 복사 및 타임스탬프 변환
|
||||
TransactionTimeSeriesData.serviceData[serviceType].timestamps = serviceData.timestamps.map(time =>
|
||||
TransactionTimeSeriesData.ensureDateObject(time)
|
||||
);
|
||||
TransactionTimeSeriesData.serviceData[serviceType].success = serviceData.success.slice();
|
||||
TransactionTimeSeriesData.serviceData[serviceType].fail = serviceData.fail.slice();
|
||||
TransactionTimeSeriesData.serviceData[serviceType].timeout = serviceData.timeout.slice();
|
||||
TransactionTimeSeriesData.serviceData[serviceType].syserror = serviceData.syserror.slice();
|
||||
}
|
||||
|
||||
// 차트 업데이트 - 부드러운 업데이트
|
||||
TransactionChart.updateChart();
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- 추가 CSS 스타일 -->
|
||||
<style>
|
||||
.chart-controls {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.chart-controls .btn-group {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.chart-controls .btn {
|
||||
padding: 4px 8px;
|
||||
font-size: 0.75rem;
|
||||
border: 1px solid #e0e0e0;
|
||||
background-color: #fff;
|
||||
color: #666;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.chart-controls .btn:first-child {
|
||||
border-top-left-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
|
||||
.chart-controls .btn:last-child {
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
.chart-controls .btn.active {
|
||||
background-color: #2196F3;
|
||||
color: white;
|
||||
border-color: #2196F3;
|
||||
}
|
||||
|
||||
.chart-controls .ml-2 {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.chart-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 200px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--color-gray-400);
|
||||
}
|
||||
|
||||
.card {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
background-color: #fff;
|
||||
padding: 16px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
/* 범례 스타일 개선 */
|
||||
.apexcharts-legend-series {
|
||||
margin: 3px 8px !important;
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
.apexcharts-legend-text {
|
||||
font-size: 0.85rem !important;
|
||||
color: #333 !important;
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
|
||||
.apexcharts-legend-marker {
|
||||
margin-right: 5px !important;
|
||||
}
|
||||
|
||||
.apexcharts-legend-series.apexcharts-inactive-legend {
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,834 @@
|
||||
<%@ 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>
|
||||
@@ -0,0 +1,275 @@
|
||||
<%@ page language="java" contentType="text/html; charset=utf-8"%>
|
||||
<%@ page import="java.util.List"%>
|
||||
<%@ page import="java.util.ArrayList"%>
|
||||
<%@ page import="java.util.HashMap"%>
|
||||
<%@ page import="java.util.Map"%>
|
||||
<%@ page import="com.eactive.eai.rms.common.context.MonitoringContext"%>
|
||||
<%@ page import="com.eactive.eai.rms.common.util.CommonUtil"%>
|
||||
<%@ page import="com.eactive.eai.rms.common.datasource.DataSourceType"%>
|
||||
<%@ page import="com.eactive.eai.rms.common.datasource.DataSourceTypeManager"%>
|
||||
<%@ page import="com.eactive.eai.common.util.SystemUtil"%>
|
||||
<%@ page import="javax.persistence.criteria.CriteriaBuilder" %>
|
||||
<%@ page import="java.util.Comparator" %>
|
||||
<%@ include file="/jsp/common/include/localemessage.jsp" %>
|
||||
|
||||
<%
|
||||
response.setHeader("Pragma", "No-cache");
|
||||
response.setHeader("Cache-Control", "no-cache");
|
||||
response.setHeader("Expires", "0");
|
||||
request.setCharacterEncoding("utf-8");
|
||||
MonitoringContext monitoringContext = (MonitoringContext)CommonUtil.getBean(request, "monitoringContext");
|
||||
|
||||
String NEW_TITLE = (String) session.getAttribute(MonitoringContext.NEW_DASHBOARD_TITLE);
|
||||
String fepBatchUrl = monitoringContext.getStringProperty("FEP_BATCH_SERVER", "");
|
||||
String eaiBatchUrl = monitoringContext.getStringProperty("EAI_BATCH_SERVER", "");
|
||||
|
||||
// 온라인 데이터 소스 타입 목록 가져오기
|
||||
List<DataSourceType> srvList = DataSourceTypeManager.getOnlineDataSourceTypes();
|
||||
|
||||
// GW 추가
|
||||
if( DataSourceTypeManager.getDataSourceType("FGW") != null ){
|
||||
srvList.add(DataSourceTypeManager.getDataSourceType("FGW"));
|
||||
}
|
||||
|
||||
if( DataSourceTypeManager.getDataSourceType("CGW") != null ){
|
||||
srvList.add(DataSourceTypeManager.getDataSourceType("CGW"));
|
||||
}
|
||||
|
||||
int srvCnt = srvList.size();
|
||||
|
||||
// 서버 타입별 목록을 저장할 맵 생성
|
||||
Map<String, List<DataSourceType>> serverTypeMap = new HashMap<>();
|
||||
|
||||
// 서버타입 맵을 application 스코프에 저장 (모든 컴포넌트에서 접근 가능)
|
||||
application.setAttribute("serverTypeMap", serverTypeMap);
|
||||
|
||||
// 서버 타입별로 분류
|
||||
for (DataSourceType server : srvList) {
|
||||
String serverName = server.getName();
|
||||
|
||||
// 서버 타입에 해당하는 리스트가 없으면 생성
|
||||
if (!serverTypeMap.containsKey(serverName)) {
|
||||
serverTypeMap.put(serverName, new ArrayList<DataSourceType>());
|
||||
}
|
||||
|
||||
// 서버를 해당 타입 리스트에 추가
|
||||
serverTypeMap.get(serverName).add(server);
|
||||
}
|
||||
|
||||
// BAP(일괄전송) 서버 타입 추가
|
||||
DataSourceType bapServer = DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.BAP);
|
||||
if (bapServer != null && !serverTypeMap.containsKey(DataSourceTypeManager.BAP)) {
|
||||
List<DataSourceType> bapList = new ArrayList<>();
|
||||
bapList.add(bapServer);
|
||||
serverTypeMap.put(DataSourceTypeManager.BAP, bapList);
|
||||
}
|
||||
|
||||
// 서버 데이터 생성 (JavaScript 용)
|
||||
StringBuilder serverDataJS = new StringBuilder();
|
||||
serverDataJS.append("var serverDataArray = [");
|
||||
|
||||
boolean first = true;
|
||||
for (Map.Entry<String, List<DataSourceType>> entry : serverTypeMap.entrySet()) {
|
||||
String serverType = entry.getKey();
|
||||
List<DataSourceType> servers = entry.getValue();
|
||||
|
||||
// BAT 서버는 제외
|
||||
if (DataSourceTypeManager.BAT.equals(serverType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (DataSourceType server : servers) {
|
||||
if (!first) {
|
||||
serverDataJS.append(",");
|
||||
}
|
||||
|
||||
String name = server.getName();
|
||||
String text = server.getText();
|
||||
|
||||
serverDataJS.append("{");
|
||||
serverDataJS.append("type:'").append(serverType).append("',");
|
||||
serverDataJS.append("name:'").append(name).append("',");
|
||||
serverDataJS.append("text:'").append(text).append("'");
|
||||
serverDataJS.append("}");
|
||||
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
serverDataJS.append("];");
|
||||
%>
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<title>eLink 모니터링 대시보드</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
|
||||
<!-- 폰트 -->
|
||||
<jsp:include page="/jsp/common/include/fonts.jsp"/>
|
||||
<!-- 아이콘 -->
|
||||
<link href="<%=request.getContextPath()%>/addon/bootstrap-icons-1.11.3/bootstrap-icons.min.css" rel="stylesheet" />
|
||||
<!-- 커스텀 CSS -->
|
||||
<link href="<%=request.getContextPath()%>/dashboard-modern/css/main.css" rel="stylesheet" />
|
||||
|
||||
<!-- jQuery -->
|
||||
<script src="<%=request.getContextPath()%>/js/jquery-3.7.1.min.js"></script>
|
||||
<!-- 커스텀 JavaScript -->
|
||||
<script src="<%=request.getContextPath()%>/dashboard-modern/js/modern-dashboard-api.js"></script>
|
||||
<script src="<%=request.getContextPath()%>/dashboard-modern/js/modern-dashboard.js"></script>
|
||||
|
||||
<!-- API 초기화 (가장 먼저 실행) -->
|
||||
<script>
|
||||
// 페이지 로드 전 API 초기화
|
||||
ModernDashboardApi.init('<%=request.getContextPath()%>');
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 1번 줄 (4:3:1 비율) - 서버 상태, 배치 처리 상태, 피크 처리 성능 */
|
||||
.first-row {
|
||||
grid-template-columns: 4fr 3fr 1fr;
|
||||
}
|
||||
|
||||
/* 2번 줄 (1:1 비율) - 거래 처리 집계, 실시간 트랜잭션 현황 */
|
||||
.second-row {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
/* 3번 줄 (7:3 비율) - 어댑터 상태, 장애통보(SMS) */
|
||||
.third-row {
|
||||
grid-template-columns: 6fr 4fr;
|
||||
}
|
||||
|
||||
/* 컴팩트한 대시보드를 위한 추가 스타일 */
|
||||
.container {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
/* 카드 내부 여백 줄임 */
|
||||
.card {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
/* 테이블 행 높이 줄임 */
|
||||
table tr td, table tr th {
|
||||
padding-top: 0.4rem;
|
||||
padding-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
/* 반응형 디자인 */
|
||||
@media (max-width: 1200px) {
|
||||
.first-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.second-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.third-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- 헤더 영역 -->
|
||||
<jsp:include page="components/dashboard-header.jsp" />
|
||||
|
||||
<!-- 1번 줄 (4:3:1 비율) - 서버 상태, 배치 처리 상태, 피크 처리 성능 -->
|
||||
<div class="dashboard-row first-row">
|
||||
<!-- 서버 상태 섹션 (4/8 비율) -->
|
||||
<div class="server-container">
|
||||
<jsp:include page="components/dashboard-servers.jsp">
|
||||
<jsp:param name="serverTypeMap" value="<%= serverTypeMap %>" />
|
||||
</jsp:include>
|
||||
</div>
|
||||
|
||||
<!-- 배치 처리 상태 섹션 (3/8 비율) -->
|
||||
<div class="batch-container">
|
||||
<jsp:include page="components/dashboard-batch.jsp" />
|
||||
</div>
|
||||
|
||||
<!-- 피크 처리 성능 섹션 (1/8 비율) -->
|
||||
<div class="peak-container">
|
||||
<jsp:include page="components/dashboard-peak.jsp" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2번 줄 (1:1 비율) - 거래 처리 집계, 실시간 트랜잭션 현황 -->
|
||||
<div class="dashboard-row second-row">
|
||||
<!-- 거래 처리 집계 섹션 (좌측) -->
|
||||
<div class="transaction-container" style="overflow-y:auto;">
|
||||
<jsp:include page="components/dashboard-transactions.jsp">
|
||||
<jsp:param name="serverTypeMap" value="<%= serverTypeMap %>" />
|
||||
</jsp:include>
|
||||
</div>
|
||||
|
||||
<!-- 실시간 트랜잭션 현황 섹션 (우측) -->
|
||||
<div class="transaction-overview-container">
|
||||
<jsp:include page="components/dashboard-tran-chart.jsp" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3번 줄 (8:2 비율) - 어댑터 상태, 장애통보(SMS) -->
|
||||
<div class="dashboard-row third-row">
|
||||
<!-- 어댑터 섹션 (8/10 비율) -->
|
||||
<div class="adapters-container">
|
||||
<jsp:include page="components/dashboard-adapter.jsp">
|
||||
<jsp:param name="serverTypeMap" value="<%= serverTypeMap %>" />
|
||||
</jsp:include>
|
||||
</div>
|
||||
|
||||
<!-- 장애통보(SMS) 섹션 (2/10 비율) -->
|
||||
<div class="sms-container">
|
||||
<jsp:include page="components/dashboard-sms.jsp" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 인터페이스 로그 조회 모달 -->
|
||||
<jsp:include page="components/dashboard-interface-detail.jsp" />
|
||||
<!-- 푸터 영역 -->
|
||||
<jsp:include page="components/dashboard-footer.jsp" />
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
// 서버 데이터 초기화
|
||||
<%= serverDataJS.toString() %>
|
||||
|
||||
// 자동 새로고침 타이머 변수 (window 객체에 할당)
|
||||
window.refreshTimer = null;
|
||||
window.refreshInterval = 5; // 기본값 10초
|
||||
|
||||
// 숫자 포맷 유틸리티 함수
|
||||
function formatNumber(num) {
|
||||
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||
}
|
||||
|
||||
// 서버 데이터 설정 및 대시보드 초기화
|
||||
$(document).ready(function() {
|
||||
// 대시보드 초기화
|
||||
initDashboard('<%=request.getContextPath()%>');
|
||||
|
||||
// 현재 시간 표시 및 업데이트
|
||||
updateCurrentTime();
|
||||
setInterval(updateCurrentTime, 1000);
|
||||
|
||||
// 초기 데이터 로드
|
||||
refreshDashboardData();
|
||||
|
||||
// 로컬 스토리지에서 저장된 새로고침 간격 불러오기
|
||||
if (localStorage.getItem('dashboardRefreshInterval')) {
|
||||
window.refreshInterval = parseInt(localStorage.getItem('dashboardRefreshInterval'));
|
||||
|
||||
// 저장된 간격에 해당하는 버튼 활성화
|
||||
$('.refresh-btn').removeClass('active');
|
||||
$('.refresh-btn[data-interval="' + window.refreshInterval + '"]').addClass('active');
|
||||
}
|
||||
|
||||
// modern-dashboard.js의 함수 호출
|
||||
window.startRefreshTimer();
|
||||
window.updateRefreshStatus();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -233,4 +233,16 @@ public class AdapterGroupService extends AbstractDataService<AdapterGroup, Strin
|
||||
|
||||
return query.fetch();
|
||||
}
|
||||
|
||||
public List<AdapterGroup> findByAdptrinsticodeNotEmpty() {
|
||||
QAdapterGroup qAdapterGroup = QAdapterGroup.adapterGroup;
|
||||
|
||||
JPAQuery<AdapterGroup> query = getJPAQueryFactory()
|
||||
.selectFrom(qAdapterGroup)
|
||||
.where(qAdapterGroup.adptrinsticode.isNotNull()
|
||||
.and(qAdapterGroup.adptrinsticode.ne("")))
|
||||
.orderBy(qAdapterGroup.adptrbzwkgroupname.asc());
|
||||
|
||||
return query.fetch();
|
||||
}
|
||||
}
|
||||
|
||||
+10
-12
@@ -1,12 +1,10 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.controller;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Vector;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceType;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
|
||||
import com.eactive.eai.rms.common.util.CommonUtil;
|
||||
import com.eactive.eai.rms.onl.dashboard.service.DashboardErrorService;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
@@ -15,11 +13,11 @@ import org.springframework.ui.ModelMap;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceType;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
|
||||
import com.eactive.eai.rms.common.util.CommonUtil;
|
||||
import com.eactive.eai.rms.onl.dashboard.service.DashboardErrorService;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.util.HashMap;
|
||||
import java.util.Vector;
|
||||
|
||||
@Controller
|
||||
public class DashboardErrorController {
|
||||
|
||||
+17
-29
@@ -1,29 +1,5 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.controller;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Vector;
|
||||
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.web.bind.ServletRequestUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.servlet.View;
|
||||
|
||||
import com.eactive.eai.rms.common.context.MonitoringContext;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceType;
|
||||
@@ -34,11 +10,23 @@ import com.eactive.eai.rms.onl.dashboard.dao.DashboardDAO;
|
||||
import com.eactive.eai.rms.onl.dashboard.service.Dashboard2RepositoryService;
|
||||
import com.eactive.eai.rms.onl.dashboard.service.DashboardService;
|
||||
import com.eactive.eai.rms.onl.tracking.vo.MemStaticsVo;
|
||||
import com.eactive.eai.rms.onl.vo.AdaptersVO;
|
||||
import com.eactive.eai.rms.onl.vo.EAIEngineStatusVo;
|
||||
import com.eactive.eai.rms.onl.vo.HostStatusVo;
|
||||
import com.eactive.eai.rms.onl.vo.RealTimeAdaptersVO;
|
||||
import com.eactive.eai.rms.onl.vo.SmsVO;
|
||||
import com.eactive.eai.rms.onl.vo.*;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.web.bind.ServletRequestUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.servlet.View;
|
||||
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.*;
|
||||
|
||||
|
||||
@Controller
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.dao;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import com.eactive.eai.rms.common.base.SqlMapClientTemplateDao;
|
||||
import com.ibatis.sqlmap.client.event.RowHandler;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Vector;
|
||||
|
||||
@Repository("dashboardDao")
|
||||
public class DashboardDaoImpl extends SqlMapClientTemplateDao
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.dao;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import com.eactive.eai.rms.common.base.SqlMapClientTemplateDao;
|
||||
import com.eactive.eai.rms.common.context.MonitoringContext;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
|
||||
@@ -16,6 +7,14 @@ import com.eactive.eai.rms.common.datasource.DataSourceType;
|
||||
import com.eactive.eai.rms.common.datasource.SpecifiedDataSourceExecutor;
|
||||
import com.eactive.eai.rms.common.util.CommonUtil;
|
||||
import com.ibatis.sqlmap.client.event.RowHandler;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
@Repository("dashboardError")
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.backup;
|
||||
|
||||
import com.eactive.eai.rms.onl.dashboard.modern.service.ThroughputCalculationService;
|
||||
import com.eactive.eai.rms.onl.tracking.vo.DbStaticsVo;
|
||||
import com.eactive.eai.rms.onl.tracking.vo.MemStaticsVo;
|
||||
import com.eactive.eai.rms.onl.vo.MonitorDataVo;
|
||||
import com.eactive.eai.rms.onl.vo.SmsVO;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class BackupContentVO implements Serializable {
|
||||
|
||||
private String backupTimestamp;
|
||||
private RepositoryFields repositoryFields;
|
||||
private DashboardRepository2Fields dashboardRepository2Fields;
|
||||
// private ThroughputFields throughputFields; 백업 필요 없는 저장소
|
||||
|
||||
public BackupContentVO() {
|
||||
this.backupTimestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public static class RepositoryFields implements Serializable {
|
||||
private Map<String, MonitorDataVo> repository;
|
||||
private ConcurrentHashMap<String, MonitorDataVo> repositoryNoExpire;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public static class DashboardRepository2Fields implements Serializable {
|
||||
private Map<String, Map<String, String>> repoEAISevrDstcd;
|
||||
private Map<String, MemStaticsVo> repoMemTransaction;
|
||||
private Map<String, DbStaticsVo> repository;
|
||||
private Map<String, List<String>> repoHostNames;
|
||||
private Map<String, List<String>> repoInstNames;
|
||||
private Map<String, Map<String, List<String>>> repoDataSourceInstNames;
|
||||
private Map<String, String> repoHostAlive;
|
||||
private List<SmsVO> repoSms;
|
||||
private Map<String, String> repoPeakTps;
|
||||
private Map<String, String> repoPeakTrade;
|
||||
private int[] procBatch = new int[6];
|
||||
private int[] procEaiBatch = new int[6];
|
||||
private Map<String, Map<String, String>> itsmDashTranInfo;
|
||||
private Map<String, Map<String, String>> itsmDashPeakdayInfo;
|
||||
private Map<String, List> itsmDashDayTranList;
|
||||
private Map<String, List> itsmDashHourTranList;
|
||||
private Map<String, Map<String, String>> itsmDashBizErrorMap;
|
||||
private Map<String, Map<String, String>> itsmDashTimeoutErrorMap;
|
||||
private Map<String, Map<String, String>> itsmDashConnectErrorMap;
|
||||
private Map<String, Map<String, String>> itsmDashServerInstTotalProcMap;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public static class ThroughputFields {
|
||||
private Map<String, ThroughputCalculationService.TimeSeriesData> timeSeriesMap;
|
||||
private Map<String, ThroughputCalculationService.ServerResourceData> serverResourceMap;
|
||||
private Map<String, Map<String, Integer>> lastAccumulatedValuesMap;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.backup;
|
||||
|
||||
import com.eactive.eai.rms.common.interceptor.InterceptorSkipController;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@Slf4j
|
||||
@Controller
|
||||
@RequiredArgsConstructor
|
||||
public class BackupController implements InterceptorSkipController {
|
||||
|
||||
private final RepositoryBackupService repositoryBackupService;
|
||||
|
||||
@GetMapping("/removeBackupFile.do")
|
||||
public ResponseEntity<String> removeBackupFile() {
|
||||
try {
|
||||
repositoryBackupService.removeBackupFile();
|
||||
return ResponseEntity.ok("OK");
|
||||
} catch (Exception e) {
|
||||
log.error("FILE REMOVE FAIL", e);
|
||||
return ResponseEntity.internalServerError().body(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.backup;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
public abstract class MemStaticsVoMixIn {
|
||||
@JsonIgnore
|
||||
public abstract int getProcTotal();
|
||||
|
||||
@JsonIgnore
|
||||
public abstract int getErrorTotal();
|
||||
|
||||
@JsonIgnore
|
||||
public abstract int getSysErrorTotal();
|
||||
|
||||
@JsonIgnore
|
||||
public abstract int getTimeoutTotal();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.backup;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
|
||||
@JsonTypeInfo(
|
||||
use = JsonTypeInfo.Id.CLASS,
|
||||
include = JsonTypeInfo.As.EXISTING_PROPERTY,
|
||||
property = "category",
|
||||
visible = true
|
||||
)
|
||||
public abstract class MonitorDataVoMixIn {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.backup;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public abstract class MonitorVOMixin {
|
||||
@JsonCreator
|
||||
public MonitorVOMixin(@JsonProperty("eaiServiceCode") String eaiServiceCode) {}
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.backup;
|
||||
|
||||
import com.eactive.eai.common.monitor.MonitorVO;
|
||||
import com.eactive.eai.rms.common.context.MonitoringContext;
|
||||
import com.eactive.eai.rms.onl.dashboard.modern.backup.mapper.Dashboard2RepositoryVoMapper;
|
||||
import com.eactive.eai.rms.onl.dashboard.modern.backup.mapper.RepositoryVoMapper;
|
||||
import com.eactive.eai.rms.onl.dashboard.modern.backup.mapper.ThroughputVoMapper;
|
||||
import com.eactive.eai.rms.onl.dashboard.repository.Dashboard2Repository;
|
||||
import com.eactive.eai.rms.onl.server.repository.Repository;
|
||||
import com.eactive.eai.rms.onl.tracking.vo.MemStaticsVo;
|
||||
import com.eactive.eai.rms.onl.vo.MonitorDataVo;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class RepositoryBackupService implements ApplicationListener<ContextRefreshedEvent> {
|
||||
|
||||
@Value("${inst.Type:ems}")
|
||||
private String instType;
|
||||
|
||||
@Value("${inst.Name:ems-0}")
|
||||
private String instName;
|
||||
|
||||
private static final DateTimeFormatter BACKUP_TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
||||
|
||||
private final Repository repository;
|
||||
private final Dashboard2Repository dashboard2Repository;
|
||||
// private final ThroughputCalculationService throughputCalculationService;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("monitoringContext")
|
||||
private final MonitoringContext monitoringContext;
|
||||
|
||||
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
private final ObjectMapper objectMapper =
|
||||
new ObjectMapper()
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
.addMixIn(MonitorDataVo.class, MonitorDataVoMixIn.class)
|
||||
.addMixIn(MonitorVO.class, MonitorVOMixin.class)
|
||||
.addMixIn(MemStaticsVo.class, MemStaticsVoMixIn.class);
|
||||
|
||||
private final RepositoryVoMapper rMapper = new RepositoryVoMapper();
|
||||
private final Dashboard2RepositoryVoMapper d2rMapper = new Dashboard2RepositoryVoMapper();
|
||||
private final ThroughputVoMapper tMapper = new ThroughputVoMapper();
|
||||
|
||||
private boolean readFlag = false;
|
||||
private boolean backupFlag = true;
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||||
|
||||
String repositoryBackupEnable = monitoringContext.getProperty("repository.backup.enable", "false");
|
||||
|
||||
backupFlag = BooleanUtils.toBoolean(repositoryBackupEnable);
|
||||
|
||||
if (!backupFlag) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
// 백업 디렉토리와 경로 생성
|
||||
String backupDirectory = monitoringContext.getProperty("repository.backup.path", "/log/elink/");
|
||||
final Path backupFilePath = Paths.get(backupDirectory, instType, instName, "repository", "backup.json");
|
||||
|
||||
// 파일이 존재하고 아직 읽지 않은 경우에 진행
|
||||
synchronized (this) {
|
||||
if (Files.exists(backupFilePath) && !readFlag) {
|
||||
String json = new String(Files.readAllBytes(backupFilePath), StandardCharsets.UTF_8);
|
||||
if (StringUtils.isNotBlank(json)) {
|
||||
BackupContentVO backupContentVO = objectMapper.readValue(json, BackupContentVO.class);
|
||||
LocalDateTime fileTime = LocalDateTime.parse(backupContentVO.getBackupTimestamp(), BACKUP_TIMESTAMP_FORMATTER);
|
||||
|
||||
// 백업 파일의 날짜가 오늘보다 이전이면 삭제, 그렇지 않으면 복원 수행
|
||||
if (fileTime.toLocalDate().isBefore(LocalDate.now())) {
|
||||
synchronized (this) {
|
||||
Files.delete(backupFilePath);
|
||||
}
|
||||
} else {
|
||||
rMapper.setVoValuesToObject(backupContentVO.getRepositoryFields(), repository);
|
||||
d2rMapper.setVoValuesToObject(backupContentVO.getDashboardRepository2Fields(), dashboard2Repository);
|
||||
// tMapper.setVoValuesToObject(backupContentVO.getThroughputFields(), throughputCalculationService);
|
||||
readFlag = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 10분마다 BackupContentVO 생성해서 저장함
|
||||
scheduler.scheduleAtFixedRate(() -> {
|
||||
|
||||
if (!backupFlag)
|
||||
return;
|
||||
|
||||
log.info("Repository Data Backup");
|
||||
|
||||
try {
|
||||
// VO
|
||||
BackupContentVO backupContentVO = new BackupContentVO();
|
||||
backupContentVO.setRepositoryFields(rMapper.toVo(repository));
|
||||
backupContentVO.setDashboardRepository2Fields(d2rMapper.toVo(dashboard2Repository));
|
||||
// backupContentVO.setThroughputFields(tMapper.toVo(throughputCalculationService));
|
||||
|
||||
synchronized (this) {
|
||||
// File Write
|
||||
if (!Files.exists(backupFilePath)) {
|
||||
Files.createDirectories(backupFilePath.getParent());
|
||||
Files.createFile(backupFilePath);
|
||||
}
|
||||
|
||||
Files.write(backupFilePath, objectMapper.writerWithDefaultPrettyPrinter().writeValueAsBytes(backupContentVO));
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Repository Backup 데이터 생성 중 오류가 발생했습니다.", e);
|
||||
}
|
||||
|
||||
}, 3, 10, TimeUnit.MINUTES);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Repository Backup 시작 설정 중 오류가 발생했습니다.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
this.backupFlag = false;
|
||||
}
|
||||
|
||||
// 매일 0시에 실행
|
||||
@Scheduled(cron = "0 0 0 * * ?")
|
||||
private void dailyCleanUp() {
|
||||
removeBackupFile();
|
||||
}
|
||||
|
||||
public synchronized void removeBackupFile() {
|
||||
String backupFilePathStr = monitoringContext.getProperty("repository.backup.path", "/log/elink/repository/");
|
||||
backupFilePathStr = backupFilePathStr.endsWith("/") ? backupFilePathStr : backupFilePathStr + "/";
|
||||
backupFilePathStr += instType + "/" + instName + "/backup.json";
|
||||
final Path backupFilePath = Paths.get(backupFilePathStr);
|
||||
if (Files.exists(backupFilePath)) {
|
||||
try {
|
||||
Files.delete(backupFilePath);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.backup.mapper;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public interface BackupMapper<VO, OBJ> {
|
||||
|
||||
VO toVo(OBJ object) throws Exception;
|
||||
|
||||
void setVoValuesToObject(VO vo, OBJ object) throws Exception;
|
||||
|
||||
default <T> T getReflection(Class<?> clazz, String fieldName, Object instance) {
|
||||
try {
|
||||
Field declaredField = clazz.getDeclaredField(fieldName);
|
||||
declaredField.setAccessible(true);
|
||||
return (T) declaredField.get(instance);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error occurred during field access", e);
|
||||
}
|
||||
}
|
||||
|
||||
default void setReflection(Class<?> clazz, String fieldName, Object instance, Object value) {
|
||||
try {
|
||||
Field declaredField = clazz.getDeclaredField(fieldName);
|
||||
declaredField.setAccessible(true);
|
||||
declaredField.set(instance, value);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error occurred during field access", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.backup.mapper;
|
||||
|
||||
import com.eactive.eai.rms.common.util.TypeCastUtils;
|
||||
import com.eactive.eai.rms.onl.dashboard.modern.backup.BackupContentVO;
|
||||
import com.eactive.eai.rms.onl.dashboard.repository.Dashboard2Repository;
|
||||
import org.apache.commons.collections.keyvalue.MultiKey;
|
||||
import org.apache.commons.collections.map.MultiKeyMap;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class Dashboard2RepositoryVoMapper implements BackupMapper<BackupContentVO.DashboardRepository2Fields, Dashboard2Repository> {
|
||||
|
||||
@Override
|
||||
public BackupContentVO.DashboardRepository2Fields toVo(Dashboard2Repository dashboard2Repository) throws Exception {
|
||||
|
||||
BackupContentVO.DashboardRepository2Fields dashboardRepository2Fields = new BackupContentVO.DashboardRepository2Fields();
|
||||
for (Field field : BackupContentVO.DashboardRepository2Fields.class.getDeclaredFields()) {
|
||||
Method method = Arrays.stream(BackupContentVO.DashboardRepository2Fields.class.getDeclaredMethods()).filter(m -> m.getName().equalsIgnoreCase("set" + field.getName())).findAny().orElse(null);
|
||||
if (method != null) {
|
||||
if ("repoMemTransaction".equalsIgnoreCase(field.getName()) || "repository".equalsIgnoreCase(field.getName())) {
|
||||
method.invoke(dashboardRepository2Fields, multiKeyMapToNormalMap(getReflection(Dashboard2Repository.class, field.getName(), dashboard2Repository)));
|
||||
} else {
|
||||
method.invoke(dashboardRepository2Fields, TypeCastUtils.castOrElse(getReflection(Dashboard2Repository.class, field.getName(), dashboard2Repository), field.getType(), null));
|
||||
}
|
||||
}
|
||||
}
|
||||
return dashboardRepository2Fields;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVoValuesToObject(BackupContentVO.DashboardRepository2Fields vo, Dashboard2Repository object) throws Exception {
|
||||
for (Field field : BackupContentVO.DashboardRepository2Fields.class.getDeclaredFields()) {
|
||||
Method method = Arrays.stream(BackupContentVO.DashboardRepository2Fields.class.getDeclaredMethods()).filter(m -> m.getName().equalsIgnoreCase("get" + field.getName())).findAny().orElse(null);
|
||||
if (method != null) {
|
||||
if ("repoMemTransaction".equalsIgnoreCase(field.getName()) || "repository".equalsIgnoreCase(field.getName())) {
|
||||
setReflection(Dashboard2Repository.class, field.getName(), object, normalMapToMultiKeyMap(TypeCastUtils.castOrElse(method.invoke(vo), Map.class, null)));
|
||||
} else {
|
||||
setReflection(Dashboard2Repository.class, field.getName(), object, TypeCastUtils.castOrElse(method.invoke(vo), field.getType(), null));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private <V> Map<String, V> multiKeyMapToNormalMap(MultiKeyMap multiKeyMap) {
|
||||
if (multiKeyMap == null)
|
||||
return null;
|
||||
|
||||
Map<String, V> normalMap = new HashMap<>();
|
||||
|
||||
for (Object key : multiKeyMap.keySet()) {
|
||||
// MultiKey에서 키 배열 가져오기
|
||||
MultiKey multiKey = (MultiKey) key;
|
||||
Object[] keys = multiKey.getKeys();
|
||||
|
||||
// 키를 "_"로 join하여 하나의 문자열로 합성
|
||||
String combinedKey = Arrays.stream(keys)
|
||||
.map(Object::toString)
|
||||
.collect(Collectors.joining("_"));
|
||||
|
||||
// 합성된 키, 값 -> normalMap
|
||||
normalMap.put(combinedKey, (V) multiKeyMap.get(key));
|
||||
}
|
||||
|
||||
return normalMap;
|
||||
}
|
||||
|
||||
public static <V> MultiKeyMap normalMapToMultiKeyMap(Map<String, V> normalMap) {
|
||||
if (normalMap == null)
|
||||
return null;
|
||||
|
||||
MultiKeyMap multiKeyMap = new MultiKeyMap();
|
||||
|
||||
for (Map.Entry<String, V> entry : normalMap.entrySet()) {
|
||||
String combinedKey = entry.getKey();
|
||||
V value = entry.getValue();
|
||||
|
||||
// "_"로 분리하여 subKeys 배열을 얻음
|
||||
String[] subKeys = combinedKey.split("_");
|
||||
multiKeyMap.put(new MultiKey(subKeys), value);
|
||||
}
|
||||
|
||||
return multiKeyMap;
|
||||
}
|
||||
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.backup.mapper;
|
||||
|
||||
import com.eactive.eai.rms.onl.dashboard.modern.backup.BackupContentVO;
|
||||
import com.eactive.eai.rms.onl.server.repository.ExpiringMap;
|
||||
import com.eactive.eai.rms.onl.server.repository.Repository;
|
||||
import com.eactive.eai.rms.onl.vo.MonitorDataVo;
|
||||
import org.apache.commons.collections.keyvalue.MultiKey;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class RepositoryVoMapper implements BackupMapper<BackupContentVO.RepositoryFields, Repository> {
|
||||
|
||||
@Override
|
||||
public BackupContentVO.RepositoryFields toVo(Repository repository) throws Exception {
|
||||
|
||||
BackupContentVO.RepositoryFields result = new BackupContentVO.RepositoryFields();
|
||||
|
||||
Field fRepository = Repository.class.getDeclaredField("repository");
|
||||
fRepository.setAccessible(true);
|
||||
ExpiringMap<MultiKey, MonitorDataVo> vRepository = (ExpiringMap<MultiKey, MonitorDataVo>) fRepository.get(repository);
|
||||
|
||||
Map<String, MonitorDataVo> v2Repository = multiKeyToNormalMap(vRepository);
|
||||
result.setRepository(v2Repository);
|
||||
|
||||
Field fRepositoryNoExpire = Repository.class.getDeclaredField("repositoryNoExpire");
|
||||
fRepositoryNoExpire.setAccessible(true);
|
||||
ConcurrentHashMap<MultiKey, MonitorDataVo> vRepositoryNoExpire = (ConcurrentHashMap<MultiKey, MonitorDataVo>) fRepositoryNoExpire.get(repository);
|
||||
|
||||
ConcurrentHashMap<String, MonitorDataVo> v2RepositoryNoExpire = multiKeyToNormalMap(vRepositoryNoExpire);
|
||||
|
||||
result.setRepositoryNoExpire(v2RepositoryNoExpire);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVoValuesToObject(BackupContentVO.RepositoryFields vo, Repository object) throws Exception {
|
||||
|
||||
ExpiringMap<MultiKey, Object> vRepository = new ExpiringMap<>();
|
||||
vo.getRepository().entrySet().stream()
|
||||
.forEach(e -> {
|
||||
String[] splitted = e.getKey().split("_");
|
||||
vRepository.put(new MultiKey(splitted), e.getValue());
|
||||
});
|
||||
|
||||
Field fRepository = Repository.class.getDeclaredField("repository");
|
||||
fRepository.setAccessible(true);
|
||||
fRepository.set(object, vRepository);
|
||||
|
||||
ConcurrentHashMap<MultiKey, Object> vRepositoryNoExpire = new ConcurrentHashMap<>();
|
||||
vo.getRepositoryNoExpire().entrySet().stream()
|
||||
.forEach(e -> {
|
||||
String[] splitted = e.getKey().split("_");
|
||||
vRepositoryNoExpire.put(new MultiKey(splitted), e.getValue());
|
||||
});
|
||||
|
||||
Field fRepositoryNoExpire = Repository.class.getDeclaredField("repositoryNoExpire");
|
||||
fRepositoryNoExpire.setAccessible(true);
|
||||
fRepositoryNoExpire.set(object, vRepositoryNoExpire);
|
||||
}
|
||||
|
||||
private <T> ConcurrentHashMap<String, T> multiKeyToNormalMap(Map<MultiKey, T> vRepository) {
|
||||
if (vRepository == null)
|
||||
return null;
|
||||
|
||||
return vRepository.entrySet().stream()
|
||||
.collect(Collectors.toMap(
|
||||
e -> Arrays.stream(e.getKey().getKeys())
|
||||
.map(Object::toString)
|
||||
.collect(Collectors.joining("_")),
|
||||
Map.Entry::getValue, (oldVal, newVal) -> newVal, ConcurrentHashMap::new
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.backup.mapper;
|
||||
|
||||
import com.eactive.eai.rms.common.util.TypeCastUtils;
|
||||
import com.eactive.eai.rms.onl.dashboard.modern.backup.BackupContentVO;
|
||||
import com.eactive.eai.rms.onl.dashboard.modern.service.ThroughputCalculationService;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ThroughputVoMapper implements BackupMapper<BackupContentVO.ThroughputFields, ThroughputCalculationService> {
|
||||
|
||||
@Override
|
||||
public BackupContentVO.ThroughputFields toVo(ThroughputCalculationService throughputCalculationService) throws Exception {
|
||||
BackupContentVO.ThroughputFields throughputFields = new BackupContentVO.ThroughputFields();
|
||||
for (Field field : BackupContentVO.ThroughputFields.class.getDeclaredFields()) {
|
||||
Method method = Arrays.stream(BackupContentVO.ThroughputFields.class.getDeclaredMethods()).filter(m -> m.getName().equalsIgnoreCase("set" + field.getName())).findAny().orElse(null);
|
||||
if (method != null)
|
||||
method.invoke(throughputFields, TypeCastUtils.castOrElse(getReflection(ThroughputCalculationService.class, field.getName(), throughputCalculationService), field.getType(), null));
|
||||
}
|
||||
return throughputFields;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVoValuesToObject(BackupContentVO.ThroughputFields vo, ThroughputCalculationService object) throws Exception {
|
||||
for (Field field : BackupContentVO.ThroughputFields.class.getDeclaredFields()) {
|
||||
Method method = Arrays.stream(BackupContentVO.ThroughputFields.class.getDeclaredMethods()).filter(m -> m.getName().equalsIgnoreCase("get" + field.getName())).findAny().orElse(null);
|
||||
if (method != null)
|
||||
setReflection(ThroughputCalculationService.class, field.getName(), object, TypeCastUtils.castOrElse(method.invoke(vo), field.getType(), null));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+955
@@ -0,0 +1,955 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.controller;
|
||||
|
||||
import com.eactive.eai.rms.common.context.MonitoringContext;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceType;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
|
||||
import com.eactive.eai.rms.data.entity.onl.property.PropertyService;
|
||||
import com.eactive.eai.rms.onl.common.service.EaiServerInfoService;
|
||||
import com.eactive.eai.rms.onl.dashboard.dao.DashboardDAO;
|
||||
import com.eactive.eai.rms.onl.dashboard.modern.dto.*;
|
||||
import com.eactive.eai.rms.onl.dashboard.modern.mapper.DashboardDataMapper;
|
||||
import com.eactive.eai.rms.onl.dashboard.modern.mapper.SmsNotificationMapper;
|
||||
import com.eactive.eai.rms.onl.dashboard.modern.service.SocketMonitorService;
|
||||
import com.eactive.eai.rms.onl.dashboard.modern.service.ThroughputCalculationService;
|
||||
import com.eactive.eai.rms.onl.dashboard.modern.service.TransactionSummaryService;
|
||||
import com.eactive.eai.rms.onl.dashboard.service.Dashboard2RepositoryService;
|
||||
import com.eactive.eai.rms.onl.dashboard.service.DashboardService;
|
||||
import com.eactive.eai.rms.onl.tracking.vo.MemStaticsVo;
|
||||
import com.eactive.eai.rms.onl.vo.AdaptersVO;
|
||||
import com.eactive.eai.rms.onl.vo.EAIEngineStatusVo;
|
||||
import com.eactive.eai.rms.onl.vo.HostStatusVo;
|
||||
import com.eactive.eai.rms.onl.vo.SmsVO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* 모던 대시보드 컨트롤러
|
||||
* ASIS 대시보드에서 TOBE 대시보드로 마이그레이션된 API를 제공합니다.
|
||||
*/
|
||||
@Slf4j
|
||||
@Controller
|
||||
public class ModernDashboardController {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("monitoringContext")
|
||||
private transient MonitoringContext monitoringContext;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("eaiServerInfoService")
|
||||
private EaiServerInfoService eaiServerInfoService;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("dashboardService")
|
||||
private DashboardService dashboardService;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("dashboard2RepositoryService")
|
||||
private Dashboard2RepositoryService dashboard2Repository;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("dashboardDao")
|
||||
private DashboardDAO dashboardDao;
|
||||
|
||||
@Autowired
|
||||
private DashboardDataMapper dataMapper;
|
||||
|
||||
@Autowired
|
||||
private SmsNotificationMapper smsNotificationMapper;
|
||||
|
||||
@Autowired
|
||||
private ThroughputCalculationService throughputCalculationService;
|
||||
|
||||
@Autowired
|
||||
private SocketMonitorService socketMonitorService;
|
||||
|
||||
@Autowired
|
||||
private TransactionSummaryService transactionSummaryService;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("propertyService")
|
||||
private PropertyService propertyService;
|
||||
|
||||
/**
|
||||
* 모던 대시보드 메인 페이지
|
||||
*/
|
||||
@RequestMapping(path = "/modern-dashboard.do")
|
||||
public String modernDashboard(HttpServletRequest request) {
|
||||
return "dashboard-modern/modern-dashboard";
|
||||
}
|
||||
|
||||
/**
|
||||
* 서버 상태 정보 제공 API
|
||||
* 전체 서버 및 해당 인스턴스의 상태 정보를 제공합니다.
|
||||
*/
|
||||
@RequestMapping(path = "/modern-dashboard-api.do", params = "type=server-status")
|
||||
@ResponseBody
|
||||
public Map<String, Object> getServerStatus(@RequestParam(required = false) String serverName) {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
try {
|
||||
// 서버 타입 목록 가져오기 (특정 서버 이름이 제공된 경우 해당 서버만 포함)
|
||||
List<DataSourceType> serverTypes;
|
||||
if (serverName != null && !serverName.isEmpty() && !serverName.equals("all") && !serverName.equals("ALL")) {
|
||||
DataSourceType server = DataSourceTypeManager.getDataSourceType(serverName.toUpperCase());
|
||||
serverTypes = server != null ? Collections.singletonList(server) : new ArrayList<>();
|
||||
} else {
|
||||
serverTypes = DataSourceTypeManager.getOnlineDataSourceTypes();
|
||||
|
||||
// GW 추가
|
||||
if( DataSourceTypeManager.getDataSourceType("FGW") != null ){
|
||||
serverTypes.add(DataSourceTypeManager.getDataSourceType("FGW"));
|
||||
}
|
||||
|
||||
if( DataSourceTypeManager.getDataSourceType("CGW") != null ){
|
||||
serverTypes.add(DataSourceTypeManager.getDataSourceType("CGW"));
|
||||
}
|
||||
}
|
||||
|
||||
List<ServerStatusDTO> serverList = new ArrayList<>();
|
||||
|
||||
for (DataSourceType serverType : serverTypes) {
|
||||
// 서버 컨텍스트 설정
|
||||
DataSourceContextHolder.setDataSourceType(serverType);
|
||||
|
||||
String agentMode = propertyService.getPrpty2valById("RMIInfo", "agent.mode");
|
||||
if( agentMode == null || agentMode.isEmpty() ){
|
||||
agentMode = "";
|
||||
}
|
||||
|
||||
// 호스트명으로 인스턴스 그룹화
|
||||
Map<String, List<Map<String, String>>> instancesByHost = new HashMap<String, List<Map<String, String>>>();
|
||||
|
||||
if( "K8S".equals(agentMode) ){
|
||||
List<Map<String, String>> hostList = dashboard2Repository.getDatasourceByHost(serverType.getName());
|
||||
|
||||
for (Map<String, String> instance : hostList) {
|
||||
String hostname = instance.get("HOSTNAME");
|
||||
instancesByHost.computeIfAbsent(hostname, k -> new ArrayList<>()).add(instance);
|
||||
}
|
||||
} else {
|
||||
// 서버에 대한 인스턴스 목록 조회
|
||||
List<Map<String, String>> ipList = eaiServerInfoService.getEaiServerIpListDB();
|
||||
|
||||
for (Map<String, String> instance : ipList) {
|
||||
String hostname = instance.get("HOSTNAME");
|
||||
instancesByHost.computeIfAbsent(hostname, k -> new ArrayList<>()).add(instance);
|
||||
}
|
||||
}
|
||||
|
||||
// 각 호스트별로 서버 상태 DTO 생성
|
||||
for (Map.Entry<String, List<Map<String, String>>> entry : instancesByHost.entrySet()) {
|
||||
String hostname = entry.getKey();
|
||||
List<Map<String, String>> hostInstances = entry.getValue();
|
||||
|
||||
// 서버 상태 DTO 생성
|
||||
ServerStatusDTO serverDTO = new ServerStatusDTO();
|
||||
serverDTO.setName(hostname);
|
||||
serverDTO.setText(hostname + " (" + serverType.getText() + ")");
|
||||
serverDTO.setHostname(hostname);
|
||||
|
||||
// 호스트 상태 조회 및 리소스 사용률 설정
|
||||
HostStatusVo hostVo = dashboardService.findHostStatus(hostInstances);
|
||||
if (hostVo != null) {
|
||||
serverDTO.setCpu(hostVo.getCpu());
|
||||
serverDTO.setMemory(hostVo.getMemory());
|
||||
serverDTO.setDisk(hostVo.getDisk());
|
||||
}
|
||||
|
||||
// 서버 상태 설정
|
||||
if (serverDTO.getCpu() > 90 || serverDTO.getMemory() > 90 || serverDTO.getDisk() > 90) {
|
||||
serverDTO.setStatus("error");
|
||||
} else if (serverDTO.getCpu() > 70 || serverDTO.getMemory() > 70 || serverDTO.getDisk() > 70) {
|
||||
serverDTO.setStatus("warning");
|
||||
} else {
|
||||
serverDTO.setStatus("normal");
|
||||
}
|
||||
|
||||
// 인스턴스 목록 조회 및 설정
|
||||
List<EAIEngineStatusVo> engineStatusList = dashboardService.findEngineStatus(hostInstances, true);
|
||||
List<InstanceDTO> instances = new ArrayList<>();
|
||||
|
||||
for (EAIEngineStatusVo engineStatus : engineStatusList) {
|
||||
InstanceDTO instance = new InstanceDTO();
|
||||
instance.setId(engineStatus.getInstanceName());
|
||||
|
||||
String serverState = StringUtils.defaultString(engineStatus.getServerState());
|
||||
// 인스턴스 상태 설정
|
||||
switch (serverState) {
|
||||
case "0":
|
||||
instance.setStatus("error");
|
||||
break;
|
||||
case "1":
|
||||
instance.setStatus("warning");
|
||||
break;
|
||||
case "2":
|
||||
instance.setStatus("normal");
|
||||
break;
|
||||
default:
|
||||
instance.setStatus("inactive");
|
||||
}
|
||||
|
||||
// 처리량 설정 (초당 처리량)
|
||||
MemStaticsVo memStatics = dashboard2Repository.getMemTradeVoByHost(
|
||||
engineStatus.getHostName(), engineStatus.getInstanceName());
|
||||
if (memStatics != null) {
|
||||
// 처리량 계산 서비스에서 초당 처리량 가져오기
|
||||
instance.setThroughput(throughputCalculationService.getThroughput(
|
||||
engineStatus.getHostName(), engineStatus.getInstanceName()));
|
||||
} else {
|
||||
instance.setThroughput("0/s");
|
||||
}
|
||||
|
||||
instances.add(instance);
|
||||
}
|
||||
|
||||
serverDTO.setInstances(instances);
|
||||
serverList.add(serverDTO);
|
||||
}
|
||||
}
|
||||
|
||||
response.put("servers", serverList);
|
||||
return response;
|
||||
} catch (Exception e) {
|
||||
log.error("서버 상태 정보 조회 중 오류 발생", e);
|
||||
response.put("error", "서버 상태 정보 조회 중 오류가 발생했습니다.");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 어댑터 상태 정보 제공 API
|
||||
* 서버별 어댑터(Socket, HTTP)의 상태 정보를 제공합니다.
|
||||
*/
|
||||
@RequestMapping(path = "/modern-dashboard-api.do", params = "type=adapter-status")
|
||||
@ResponseBody
|
||||
public Map<String, Object> getAdapterStatus(@RequestParam(required = false) String serverName) {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
try {
|
||||
// 서버 타입 목록 가져오기 (특정 서버 이름이 제공된 경우 해당 서버만 포함)
|
||||
List<DataSourceType> servers;
|
||||
if (serverName != null && !serverName.isEmpty() && !serverName.equals("all") && !serverName.equals("ALL")) {
|
||||
DataSourceType server = DataSourceTypeManager.getDataSourceType(serverName.toUpperCase());
|
||||
servers = server != null ? Collections.singletonList(server) : new ArrayList<>();
|
||||
} else {
|
||||
// 온라인 데이터 소스 타입 가져오기 (BAT 서버 제외)
|
||||
servers = DataSourceTypeManager.getOnlineDataSourceTypes().stream()
|
||||
.filter(server -> !DataSourceTypeManager.BAT.equals(server.getName()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// GW 추가
|
||||
if( DataSourceTypeManager.getDataSourceType("FGW") != null ){
|
||||
servers.add(DataSourceTypeManager.getDataSourceType("FGW"));
|
||||
}
|
||||
|
||||
if( DataSourceTypeManager.getDataSourceType("CGW") != null ){
|
||||
servers.add(DataSourceTypeManager.getDataSourceType("CGW"));
|
||||
}
|
||||
}
|
||||
|
||||
List<Map<String, Object>> serverAdapters = new ArrayList<>();
|
||||
|
||||
for (DataSourceType server : servers) {
|
||||
// 서버 컨텍스트 설정
|
||||
DataSourceContextHolder.setDataSourceType(server);
|
||||
|
||||
// 서버에 대한 인스턴스 목록 조회
|
||||
List<Map<String, String>> ipList = eaiServerInfoService.getEaiServerIpListDB();
|
||||
|
||||
// 어댑터 상태 목록 조회 (개별 인스턴스별 상태)
|
||||
List<AdaptersVO> adaptersList = dashboardService.findAdapterStatusList(ipList);
|
||||
|
||||
// 어댑터 상태 집계 정보 생성
|
||||
AdapterStatusSummaryContainerDTO summaryContainer = dataMapper.fromAdaptersVOList(adaptersList);
|
||||
|
||||
// 기존 방식의 어댑터 상태 정보도 함께 제공 (하위 호환성 유지)
|
||||
AdaptersVO adaptersVO = dashboardService.findAdapterStatusTotalList(ipList);
|
||||
List<AdapterStatusDTO> adapterDTOs = dataMapper.fromAdaptersVO(adaptersVO);
|
||||
|
||||
Map<String, Object> serverAdapter = new HashMap<>();
|
||||
serverAdapter.put("serverName", server.getName());
|
||||
serverAdapter.put("serverText", server.getText());
|
||||
serverAdapter.put("adapters", adapterDTOs);
|
||||
serverAdapter.put("adapterSummaries", summaryContainer.getAdapterSummaryList());
|
||||
serverAdapter.put("overallStatus", summaryContainer.getOverallStatus());
|
||||
|
||||
serverAdapters.add(serverAdapter);
|
||||
}
|
||||
|
||||
response.put("serverAdapters", serverAdapters);
|
||||
|
||||
return response;
|
||||
} catch (Exception e) {
|
||||
log.error("어댑터 상태 정보 조회 중 오류 발생", e);
|
||||
response.put("error", "어댑터 상태 정보 조회 중 오류가 발생했습니다.");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 트랜잭션 정보 제공 API
|
||||
* 서버별 트랜잭션 처리 현황(성공, 실패, 타임아웃, 시스템 에러)을 제공합니다.
|
||||
*/
|
||||
@RequestMapping(path = "/modern-dashboard-api.do", params = "type=transaction")
|
||||
@ResponseBody
|
||||
public Map<String, Object> getTransactionInfo(@RequestParam(required = false) String serverName) {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
try {
|
||||
// 서버 타입 목록 가져오기 (특정 서버 이름이 제공된 경우 해당 서버만 포함)
|
||||
List<DataSourceType> servers;
|
||||
if (serverName != null && !serverName.isEmpty() && !serverName.equals("all") && !serverName.equals("ALL")) {
|
||||
DataSourceType server = DataSourceTypeManager.getDataSourceType(serverName.toUpperCase());
|
||||
servers = server != null ? Collections.singletonList(server) : new ArrayList<>();
|
||||
} else {
|
||||
servers = DataSourceTypeManager.getOnlineDataSourceTypes();
|
||||
}
|
||||
|
||||
List<TransactionDTO> transactions = new ArrayList<>();
|
||||
TransactionDTO totalTransaction = new TransactionDTO();
|
||||
totalTransaction.setType("TOTAL");
|
||||
|
||||
for (DataSourceType server : servers) {
|
||||
// 서버 컨텍스트 설정
|
||||
DataSourceContextHolder.setDataSourceType(server);
|
||||
|
||||
// 서버의 트랜잭션 정보 조회
|
||||
MemStaticsVo memStatics = dashboard2Repository.getMemTradeVoByService(server.getName());
|
||||
TransactionDTO transaction = dataMapper.fromMemStaticsVo(memStatics, server.getName());
|
||||
transactions.add(transaction);
|
||||
|
||||
// 전체 트랜잭션 집계에 추가
|
||||
totalTransaction.setSuccess(totalTransaction.getSuccess() + transaction.getSuccess());
|
||||
totalTransaction.setFail(totalTransaction.getFail() + transaction.getFail());
|
||||
totalTransaction.setTimeout(totalTransaction.getTimeout() + transaction.getTimeout());
|
||||
totalTransaction.setSyserror(totalTransaction.getSyserror() + transaction.getSyserror());
|
||||
}
|
||||
|
||||
// 전체 트랜잭션 정보 추가
|
||||
transactions.add(totalTransaction);
|
||||
|
||||
response.put("transactions", transactions);
|
||||
|
||||
return response;
|
||||
} catch (Exception e) {
|
||||
log.error("트랜잭션 정보 조회 중 오류 발생", e);
|
||||
response.put("error", "트랜잭션 정보 조회 중 오류가 발생했습니다.");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 피크 성능 정보 제공 API
|
||||
* 서버별 최대 처리량 및 TPS(초당 트랜잭션 수) 정보를 제공합니다.
|
||||
*/
|
||||
@RequestMapping(path = "/modern-dashboard-api.do", params = "type=peak-performance")
|
||||
@ResponseBody
|
||||
public Map<String, Object> getPeakPerformance(@RequestParam(required = false) String type) {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
try {
|
||||
// 피크 성능 유형 (TPS 또는 거래건수)
|
||||
String peakType = type != null && "trade".equals(type) ? "trade" : "tps";
|
||||
|
||||
List<PeakPerformanceDTO> peakTps = new ArrayList<>();
|
||||
List<PeakPerformanceDTO> peakTrade = new ArrayList<>();
|
||||
|
||||
// 온라인 서비스 피크 정보 조회
|
||||
for (DataSourceType server : DataSourceTypeManager.getOnlineDataSourceTypes()) {
|
||||
String peakInfo;
|
||||
peakInfo = dashboard2Repository.getPeakTps(server.getName());
|
||||
|
||||
if (peakInfo != null && !peakInfo.isEmpty()) {
|
||||
PeakPerformanceDTO dto = dataMapper.fromPeakPerformanceString(peakInfo, peakType);
|
||||
peakTps.add(dto);
|
||||
}
|
||||
|
||||
peakInfo = dashboard2Repository.getPeakTrade(server.getName());
|
||||
|
||||
if (peakInfo != null && !peakInfo.isEmpty()) {
|
||||
PeakPerformanceDTO dto = dataMapper.fromPeakPerformanceString(peakInfo, peakType);
|
||||
peakTrade.add(dto);
|
||||
}
|
||||
}
|
||||
|
||||
// BAP(일괄전송) 서버 피크 정보 조회
|
||||
DataSourceType bapServer = DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.BAP);
|
||||
if (bapServer != null) {
|
||||
String peakInfo;
|
||||
// peakInfo = dashboard2Repository.getPeakTps(bapServer.getName());
|
||||
//
|
||||
// if (peakInfo != null && !peakInfo.isEmpty()) {
|
||||
// PeakPerformanceDTO dto = dataMapper.fromPeakPerformanceString(peakInfo, peakType);
|
||||
// peakTps.add(dto);
|
||||
// }
|
||||
|
||||
peakInfo = dashboard2Repository.getPeakTrade(bapServer.getName());
|
||||
|
||||
if (peakInfo != null && !peakInfo.isEmpty()) {
|
||||
PeakPerformanceDTO dto = dataMapper.fromPeakPerformanceString(peakInfo, peakType);
|
||||
peakTrade.add(dto);
|
||||
}
|
||||
}
|
||||
|
||||
// BAT(배치) 서버 피크 정보 조회
|
||||
DataSourceType batServer = DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.BAT);
|
||||
if (batServer != null) {
|
||||
String peakInfo;
|
||||
// peakInfo = dashboard2Repository.getPeakTps(batServer.getName());
|
||||
//
|
||||
// if (peakInfo != null && !peakInfo.isEmpty()) {
|
||||
// PeakPerformanceDTO dto = dataMapper.fromPeakPerformanceString(peakInfo, peakType);
|
||||
// peakTps.add(dto);
|
||||
// }
|
||||
|
||||
peakInfo = dashboard2Repository.getPeakTrade(batServer.getName());
|
||||
|
||||
if (peakInfo != null && !peakInfo.isEmpty()) {
|
||||
PeakPerformanceDTO dto = dataMapper.fromPeakPerformanceString(peakInfo, peakType);
|
||||
peakTrade.add(dto);
|
||||
}
|
||||
}
|
||||
|
||||
response.put("peakTps", peakTps);
|
||||
response.put("peakTrade", peakTrade);
|
||||
response.put("peakType", peakType);
|
||||
|
||||
return response;
|
||||
} catch (Exception e) {
|
||||
log.error("피크 성능 정보 조회 중 오류 발생", e);
|
||||
response.put("error", "피크 성능 정보 조회 중 오류가 발생했습니다.");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 배치 처리 상태 제공 API
|
||||
* 일괄 전송(BAP) 및 배치(BAT) 처리 상태 정보를 제공합니다.
|
||||
*/
|
||||
@RequestMapping(path = "/modern-dashboard-api.do", params = "type=batch-process")
|
||||
@ResponseBody
|
||||
public Map<String, Object> getBatchProcess() {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
try {
|
||||
List<BatchProcessDTO> batchData = new ArrayList<>();
|
||||
|
||||
// BAP(일괄전송) 배치 처리 정보 조회
|
||||
if( DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.BAP) != null ){
|
||||
String bapBatchInfo = dashboard2Repository.getProcBatch();
|
||||
if (bapBatchInfo != null && !bapBatchInfo.isEmpty()) {
|
||||
BatchProcessDTO bapDTO = dataMapper.fromBatchProcessString(DataSourceTypeManager.BAP, bapBatchInfo);
|
||||
batchData.add(bapDTO);
|
||||
}
|
||||
}
|
||||
|
||||
// BAT(배치) 배치 처리 정보 조회
|
||||
if( DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.BAT) != null ){
|
||||
String batBatchInfo = dashboard2Repository.getProcEaiBatch();
|
||||
if (batBatchInfo != null && !batBatchInfo.isEmpty()) {
|
||||
BatchProcessDTO batDTO = dataMapper.fromBatchProcessString(DataSourceTypeManager.BAT, batBatchInfo);
|
||||
batchData.add(batDTO);
|
||||
}
|
||||
}
|
||||
|
||||
response.put("batchData", batchData);
|
||||
|
||||
return response;
|
||||
} catch (Exception e) {
|
||||
log.error("배치 처리 정보 조회 중 오류 발생", e);
|
||||
response.put("error", "배치 처리 정보 조회 중 오류가 발생했습니다.");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 인스턴스 상세 정보 제공 API
|
||||
* 특정 서버 또는 모든 서버의 인스턴스별 상세 정보를 제공합니다.
|
||||
*/
|
||||
@RequestMapping(path = "/modern-dashboard-api.do", params = "type=instance-detail")
|
||||
@ResponseBody
|
||||
public Map<String, Object> getInstanceDetail(@RequestParam(required = false) String serverName) {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
try {
|
||||
// 서버 타입 목록 가져오기 (특정 서버 이름이 제공된 경우 해당 서버만 포함)
|
||||
List<DataSourceType> servers;
|
||||
if (serverName != null && !serverName.isEmpty() && !serverName.equals("all") && !serverName.equals("ALL")) {
|
||||
DataSourceType server = DataSourceTypeManager.getDataSourceType(serverName.toUpperCase());
|
||||
servers = server != null ? Collections.singletonList(server) : new ArrayList<>();
|
||||
} else {
|
||||
servers = DataSourceTypeManager.getOnlineDataSourceTypes();
|
||||
}
|
||||
|
||||
List<Map<String, Object>> instanceDetails = new ArrayList<>();
|
||||
|
||||
for (DataSourceType server : servers) {
|
||||
// 서버 컨텍스트 설정
|
||||
DataSourceContextHolder.setDataSourceType(server);
|
||||
|
||||
// 서버에 대한 인스턴스 목록 조회
|
||||
List<Map<String, String>> ipList = eaiServerInfoService.getEaiServerIpListDB();
|
||||
|
||||
// 각 인스턴스별 상세 정보 수집
|
||||
for (Map<String, String> instanceInfo : ipList) {
|
||||
String hostName = instanceInfo.get("HOSTNAME");
|
||||
String instanceName = instanceInfo.get("EAISVRINSTNM");
|
||||
|
||||
// 엔진 상태 조회
|
||||
EAIEngineStatusVo engineStatus = new EAIEngineStatusVo();
|
||||
engineStatus.setHostName(hostName);
|
||||
engineStatus.setInstanceName(instanceName);
|
||||
engineStatus.setService(server.getName());
|
||||
|
||||
EAIEngineStatusVo engineStatusResult = dashboardService.findEngineStatus(engineStatus);
|
||||
|
||||
// 어댑터 상태 조회
|
||||
AdaptersVO adaptersVO = new AdaptersVO();
|
||||
adaptersVO.setHostName(hostName);
|
||||
adaptersVO.setInstanceName(instanceName);
|
||||
adaptersVO.setService(server.getName());
|
||||
|
||||
AdaptersVO adaptersResult = dashboardService.findAdapterStatus(adaptersVO);
|
||||
|
||||
// 트랜잭션 정보 조회
|
||||
MemStaticsVo memStatics = dashboard2Repository.getMemTradeVoByHost(hostName, instanceName);
|
||||
|
||||
// DTO로 변환
|
||||
ServerStatusDTO serverDTO = dataMapper.fromEAIEngineStatusVo(engineStatusResult);
|
||||
List<AdapterStatusDTO> adapterDTOs = dataMapper.fromAdaptersVO(adaptersResult);
|
||||
TransactionDTO transactionDTO = dataMapper.fromMemStaticsVo(memStatics, server.getName());
|
||||
|
||||
// 인스턴스 상세 정보 구성
|
||||
Map<String, Object> instanceDetail = new HashMap<>();
|
||||
instanceDetail.put("serverName", server.getName());
|
||||
instanceDetail.put("serverText", server.getText());
|
||||
instanceDetail.put("hostName", hostName);
|
||||
instanceDetail.put("instanceName", instanceName);
|
||||
instanceDetail.put("server", serverDTO);
|
||||
instanceDetail.put("adapters", adapterDTOs);
|
||||
instanceDetail.put("transaction", transactionDTO);
|
||||
|
||||
instanceDetails.add(instanceDetail);
|
||||
}
|
||||
}
|
||||
|
||||
response.put("instanceDetails", instanceDetails);
|
||||
|
||||
return response;
|
||||
} catch (Exception e) {
|
||||
log.error("인스턴스 상세 정보 조회 중 오류 발생", e);
|
||||
response.put("error", "인스턴스 상세 정보 조회 중 오류가 발생했습니다.");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 실시간 성능 데이터 제공 API
|
||||
* 서버 및 어댑터의 최신 성능 데이터를 제공합니다.
|
||||
*/
|
||||
@RequestMapping(path = "/modern-dashboard-api.do", params = "type=performance-data")
|
||||
@ResponseBody
|
||||
public Map<String, Object> getPerformanceData() {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
try {
|
||||
// 서버별 성능 정보 수집
|
||||
List<Map<String, Object>> serverPerformance = new ArrayList<>();
|
||||
|
||||
for (DataSourceType server : DataSourceTypeManager.getOnlineDataSourceTypes()) {
|
||||
// 서버 컨텍스트 설정
|
||||
DataSourceContextHolder.setDataSourceType(server);
|
||||
|
||||
// 서버에 대한 인스턴스 목록 조회
|
||||
List<Map<String, String>> ipList = eaiServerInfoService.getEaiServerIpListDB();
|
||||
|
||||
// 호스트 상태 조회
|
||||
HostStatusVo hostVo = dashboardService.findHostStatus(ipList);
|
||||
|
||||
// 실시간 트랜잭션 정보 조회
|
||||
MemStaticsVo memStatics = dashboard2Repository.getMemTradeVoByService(server.getName());
|
||||
|
||||
// 서버 성능 정보 구성
|
||||
Map<String, Object> performance = new HashMap<>();
|
||||
performance.put("serverName", server.getName());
|
||||
performance.put("serverText", server.getText());
|
||||
performance.put("cpu", hostVo != null ? hostVo.getCpu() : 0);
|
||||
performance.put("memory", hostVo != null ? hostVo.getMemory() : 0);
|
||||
performance.put("disk", hostVo != null ? hostVo.getDisk() : 0);
|
||||
performance.put("procTotal", memStatics != null ? memStatics.getProcTotal() : 0);
|
||||
performance.put("errorTotal", memStatics != null ?
|
||||
(memStatics.getErrorTotal() + memStatics.getTimeoutTotal() + memStatics.getSysErrorTotal()) : 0);
|
||||
|
||||
serverPerformance.add(performance);
|
||||
}
|
||||
|
||||
response.put("serverPerformance", serverPerformance);
|
||||
response.put("timestamp", System.currentTimeMillis());
|
||||
|
||||
return response;
|
||||
} catch (Exception e) {
|
||||
log.error("성능 데이터 조회 중 오류 발생", e);
|
||||
response.put("error", "성능 데이터 조회 중 오류가 발생했습니다.");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 트랜잭션 시계열 데이터 제공 API
|
||||
* 서버별 시간에 따른 트랜잭션 처리 현황(성공, 실패, 타임아웃, 시스템 에러)을 제공합니다.
|
||||
*/
|
||||
@RequestMapping(path = "/modern-dashboard-api.do", params = "type=transaction-timeseries")
|
||||
@ResponseBody
|
||||
public Map<String, Object> getTransactionTimeSeries(
|
||||
@RequestParam(required = false) String serverName,
|
||||
@RequestParam(required = false, defaultValue = "10") int minutes) {
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
try {
|
||||
// 서버 타입 목록 가져오기 (특정 서버 이름이 제공된 경우 해당 서버만 포함)
|
||||
List<DataSourceType> servers;
|
||||
if (serverName != null && !serverName.isEmpty() && !serverName.equals("all") && !serverName.equals("ALL")) {
|
||||
DataSourceType server = DataSourceTypeManager.getDataSourceType(serverName.toUpperCase());
|
||||
servers = server != null ? Collections.singletonList(server) : new ArrayList<>();
|
||||
} else {
|
||||
servers = DataSourceTypeManager.getOnlineDataSourceTypes();
|
||||
}
|
||||
|
||||
// 시계열 데이터 수집 기간 제한 (기본 10분)
|
||||
minutes = Math.min(Math.max(minutes, 1), 60); // 1~60분 범위로 제한
|
||||
|
||||
// 서버별 시계열 데이터 수집
|
||||
Map<String, Map<String, Object>> timeSeriesData = new HashMap<>();
|
||||
|
||||
// 현재 시간 저장
|
||||
long currentTimestamp = System.currentTimeMillis();
|
||||
|
||||
// 현재 실시간 거래 정보 저장용 맵
|
||||
Map<String, MemStaticsVo> currentTransactions = new HashMap<>();
|
||||
|
||||
// 현재 거래 정보 먼저 조회 (실시간 값 추가용)
|
||||
for (DataSourceType server : servers) {
|
||||
String serverType = server.getName();
|
||||
|
||||
// 서버 컨텍스트 설정
|
||||
DataSourceContextHolder.setDataSourceType(server);
|
||||
|
||||
// 서버의 트랜잭션 정보 조회
|
||||
MemStaticsVo memStatics = dashboard2Repository.getMemTradeVoByService(serverType);
|
||||
if (memStatics != null) {
|
||||
currentTransactions.put(serverType, memStatics);
|
||||
}
|
||||
}
|
||||
|
||||
// ThroughputCalculationService에서 시계열 데이터 가져오기
|
||||
for (DataSourceType server : servers) {
|
||||
String serverType = server.getName();
|
||||
|
||||
// 시계열 데이터 조회
|
||||
ThroughputCalculationService.TimeSeriesData seriesData =
|
||||
throughputCalculationService.getTimeSeriesData(serverType, minutes);
|
||||
|
||||
if (seriesData != null) {
|
||||
// 시계열 데이터 저장할 구조 생성
|
||||
Map<String, Object> serviceData = new HashMap<>();
|
||||
|
||||
// 기존 타임스탬프와 데이터 복사
|
||||
List<Long> timestamps = new ArrayList<>(seriesData.getTimestamps());
|
||||
List<Integer> success = new ArrayList<>(seriesData.getSuccess());
|
||||
List<Integer> fail = new ArrayList<>(seriesData.getFail());
|
||||
List<Integer> timeout = new ArrayList<>(seriesData.getTimeout());
|
||||
List<Integer> syserror = new ArrayList<>(seriesData.getSyserror());
|
||||
|
||||
// 현재 값을 시계열에 추가 (마지막 데이터가 없거나 10초 이상 지났을 경우)
|
||||
MemStaticsVo currentStats = currentTransactions.get(serverType);
|
||||
if (currentStats != null) {
|
||||
boolean shouldAddCurrentData = true;
|
||||
|
||||
// 마지막 데이터와 현재 시간이 2초 이상 차이가 나면 현재 값 추가
|
||||
if (!timestamps.isEmpty() && (currentTimestamp - timestamps.get(timestamps.size() - 1)) < 2000) {
|
||||
shouldAddCurrentData = false;
|
||||
}
|
||||
|
||||
if (shouldAddCurrentData) {
|
||||
// 현재 값을 시계열 마지막에 추가
|
||||
timestamps.add(currentTimestamp);
|
||||
|
||||
// 성공 건수 = 전체 - (실패 + 타임아웃 + 시스템에러)
|
||||
int successCount = currentStats.getProcTotal() - currentStats.getErrorTotal()
|
||||
- currentStats.getTimeoutTotal() - currentStats.getSysErrorTotal();
|
||||
|
||||
success.add(Math.max(0, successCount));
|
||||
fail.add(currentStats.getErrorTotal());
|
||||
timeout.add(currentStats.getTimeoutTotal());
|
||||
syserror.add(currentStats.getSysErrorTotal());
|
||||
}
|
||||
}
|
||||
|
||||
// 최종 데이터 저장
|
||||
serviceData.put("timestamps", timestamps);
|
||||
serviceData.put("success", success);
|
||||
serviceData.put("fail", fail);
|
||||
serviceData.put("timeout", timeout);
|
||||
serviceData.put("syserror", syserror);
|
||||
|
||||
// 이 서버의 데이터 저장
|
||||
timeSeriesData.put(serverType, serviceData);
|
||||
} else {
|
||||
// 데이터가 없는 경우 빈 데이터 생성
|
||||
Map<String, Object> emptyData = new HashMap<>();
|
||||
emptyData.put("timestamps", new ArrayList<>());
|
||||
emptyData.put("success", new ArrayList<>());
|
||||
emptyData.put("fail", new ArrayList<>());
|
||||
emptyData.put("timeout", new ArrayList<>());
|
||||
emptyData.put("syserror", new ArrayList<>());
|
||||
timeSeriesData.put(serverType, emptyData);
|
||||
}
|
||||
}
|
||||
|
||||
// 전체 합계 데이터 조회
|
||||
ThroughputCalculationService.TimeSeriesData totalData =
|
||||
throughputCalculationService.getTimeSeriesData("TOTAL", minutes);
|
||||
|
||||
if (totalData != null) {
|
||||
Map<String, Object> totalSeriesData = new HashMap<>();
|
||||
|
||||
// 기존 타임스탬프와 데이터 복사
|
||||
List<Long> timestamps = new ArrayList<>(totalData.getTimestamps());
|
||||
List<Integer> success = new ArrayList<>(totalData.getSuccess());
|
||||
List<Integer> fail = new ArrayList<>(totalData.getFail());
|
||||
List<Integer> timeout = new ArrayList<>(totalData.getTimeout());
|
||||
List<Integer> syserror = new ArrayList<>(totalData.getSyserror());
|
||||
|
||||
// 현재 전체 합계 계산
|
||||
int totalSuccess = 0;
|
||||
int totalFail = 0;
|
||||
int totalTimeout = 0;
|
||||
int totalSyserror = 0;
|
||||
|
||||
for (String serviceType : currentTransactions.keySet()) {
|
||||
MemStaticsVo stats = currentTransactions.get(serviceType);
|
||||
if (stats != null) {
|
||||
totalSuccess += stats.getProcTotal() - stats.getErrorTotal()
|
||||
- stats.getTimeoutTotal() - stats.getSysErrorTotal();
|
||||
totalFail += stats.getErrorTotal();
|
||||
totalTimeout += stats.getTimeoutTotal();
|
||||
totalSyserror += stats.getSysErrorTotal();
|
||||
}
|
||||
}
|
||||
|
||||
// 마지막 데이터와 현재 시간이 2초 이상 차이가 나면 현재 값 추가
|
||||
boolean shouldAddCurrentData = true;
|
||||
if (!timestamps.isEmpty() && (currentTimestamp - timestamps.get(timestamps.size() - 1)) < 2000) {
|
||||
shouldAddCurrentData = false;
|
||||
}
|
||||
|
||||
if (shouldAddCurrentData) {
|
||||
timestamps.add(currentTimestamp);
|
||||
success.add(totalSuccess);
|
||||
fail.add(totalFail);
|
||||
timeout.add(totalTimeout);
|
||||
syserror.add(totalSyserror);
|
||||
}
|
||||
|
||||
totalSeriesData.put("timestamps", timestamps);
|
||||
totalSeriesData.put("success", success);
|
||||
totalSeriesData.put("fail", fail);
|
||||
totalSeriesData.put("timeout", timeout);
|
||||
totalSeriesData.put("syserror", syserror);
|
||||
timeSeriesData.put("TOTAL", totalSeriesData);
|
||||
} else {
|
||||
// 전체 데이터가 없는 경우, 더미 데이터 생성은 제거
|
||||
Map<String, Object> emptyData = new HashMap<>();
|
||||
emptyData.put("timestamps", new ArrayList<>());
|
||||
emptyData.put("success", new ArrayList<>());
|
||||
emptyData.put("fail", new ArrayList<>());
|
||||
emptyData.put("timeout", new ArrayList<>());
|
||||
emptyData.put("syserror", new ArrayList<>());
|
||||
timeSeriesData.put("TOTAL", emptyData);
|
||||
}
|
||||
|
||||
response.put("timeSeriesData", timeSeriesData);
|
||||
response.put("minutes", minutes);
|
||||
response.put("timestamp", System.currentTimeMillis());
|
||||
|
||||
return response;
|
||||
} catch (Exception e) {
|
||||
log.error("트랜잭션 시계열 정보 조회 중 오류 발생", e);
|
||||
response.put("error", "트랜잭션 시계열 정보 조회 중 오류가 발생했습니다.");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 서버 리소스 시계열 데이터 제공 API
|
||||
* 특정 서버의 CPU, 메모리, 디스크 사용량 시계열 데이터를 제공합니다.
|
||||
*/
|
||||
@RequestMapping(path = "/modern-dashboard-api.do", params = "type=server-resource")
|
||||
@ResponseBody
|
||||
public Map<String, Object> getServerResourceData(
|
||||
@RequestParam String hostname,
|
||||
@RequestParam(required = false, defaultValue = "7") int count) {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
try {
|
||||
// 서버 리소스 시계열 데이터 조회
|
||||
ThroughputCalculationService.ServerResourceData resourceData =
|
||||
throughputCalculationService.getServerResourceData(hostname, count);
|
||||
|
||||
// 응답 데이터 구성
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("hostname", hostname);
|
||||
data.put("timestamps", resourceData.getTimestamps());
|
||||
data.put("cpu", resourceData.getCpu());
|
||||
data.put("memory", resourceData.getMemory());
|
||||
data.put("disk", resourceData.getDisk());
|
||||
|
||||
response.put("data", data);
|
||||
return response;
|
||||
} catch (Exception e) {
|
||||
log.error("서버 리소스 시계열 데이터 조회 중 오류 발생", e);
|
||||
response.put("error", "서버 리소스 시계열 데이터 조회 중 오류가 발생했습니다.");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SMS 장애 통보 정보를 조회합니다.
|
||||
* @return SMS 장애 통보 정보
|
||||
*/
|
||||
@RequestMapping(path = "/modern-dashboard-api.do", params = "type=sms-notification")
|
||||
@ResponseBody
|
||||
public Map<String, Object> getSmsNotification() {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
List<SmsVO> totalSmsList = new ArrayList<>();
|
||||
|
||||
try {
|
||||
|
||||
List<DataSourceType> servers = DataSourceTypeManager.getOnlineDataSourceTypes();
|
||||
|
||||
// GW 추가
|
||||
if( DataSourceTypeManager.getDataSourceType("FGW") != null ){
|
||||
servers.add(DataSourceTypeManager.getDataSourceType("FGW"));
|
||||
}
|
||||
|
||||
if( DataSourceTypeManager.getDataSourceType("CGW") != null ){
|
||||
servers.add(DataSourceTypeManager.getDataSourceType("CGW"));
|
||||
}
|
||||
|
||||
for (DataSourceType d : servers) {
|
||||
DataSourceContextHolder.setDataSourceType(d);
|
||||
List ipList = eaiServerInfoService.getEaiServerIpListDB();
|
||||
List<SmsVO> smsList = dashboardService.findSmsList(ipList, d.getName());
|
||||
totalSmsList.addAll(smsList);
|
||||
}
|
||||
|
||||
// SmsVO 목록을 SmsNotificationDto 목록으로 변환
|
||||
List<SmsNotificationDto> smsNotificationDtoList = smsNotificationMapper.mapToSmsNotificationDtoList(totalSmsList);
|
||||
|
||||
result.put("success", true);
|
||||
result.put("data", smsNotificationDtoList);
|
||||
} catch (Exception e) {
|
||||
log.error("SMS 장애 통보 정보 조회 중 오류 발생", e);
|
||||
result.put("success", false);
|
||||
result.put("message", "SMS 장애 통보 정보 조회 중 오류가 발생했습니다.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get socket status data
|
||||
* @param serverName Optional server name filter
|
||||
* @param adapterGroup Optional adapter group filter
|
||||
* @return SocketStatusDTO with all socket status data
|
||||
*/
|
||||
@RequestMapping(value = "/socket-status")
|
||||
@ResponseBody
|
||||
public ResponseEntity<SocketStatusDTO> getSocketStatus(
|
||||
@RequestParam(value = "serverName", required = false) String serverName,
|
||||
@RequestParam(value = "adapterGroup", required = false) String adapterGroup) {
|
||||
|
||||
SocketStatusDTO result = socketMonitorService.getSocketStatus(serverName, adapterGroup);
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get socket process data
|
||||
* @param serverName Optional server name filter
|
||||
* @param adapterGroup Optional adapter group filter
|
||||
* @return SocketProcessDTO with all socket process data
|
||||
*/
|
||||
@RequestMapping(path = "/modern-dashboard-api.do", params = "type=socket-process")
|
||||
@ResponseBody
|
||||
public ResponseEntity<SocketProcessDTO> getSocketProcess(
|
||||
@RequestParam(value = "serverName", required = false) String serverName,
|
||||
@RequestParam(value = "adapterGroup", required = false) String adapterGroup) {
|
||||
|
||||
SocketProcessDTO result = socketMonitorService.getSocketProcess(serverName, adapterGroup);
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get combined socket data (both status and process)
|
||||
* @param serverName Optional server name filter
|
||||
* @param adapterGroup Optional adapter group filter
|
||||
* @return Map containing both socket status and process data
|
||||
*/
|
||||
@RequestMapping(path = "/modern-dashboard-api.do", params = "type=socket-combined")
|
||||
@ResponseBody
|
||||
public ResponseEntity<Map<String, Object>> getCombinedSocketData(
|
||||
@RequestParam(value = "serverName", required = false) String serverName,
|
||||
@RequestParam(value = "adapterGroup", required = false) String adapterGroup) {
|
||||
|
||||
Map<String, Object> result = socketMonitorService.getCombinedSocketData(serverName, adapterGroup);
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 거래현황 시계열 데이터 조회
|
||||
* @param seconds 조회할 초 단위 기간 (기본값 60초, 최대 120초)
|
||||
* @return 시계열 데이터
|
||||
*/
|
||||
@GetMapping(path = "/modern-dashboard-api.do", params = "type=timeseries")
|
||||
public ResponseEntity<Map<String, Object>> getTimeSeriesData(
|
||||
@RequestParam(defaultValue = "60") int seconds) {
|
||||
|
||||
TransactionSummaryService.TimeSeriesData data = transactionSummaryService.getTimeSeriesData(seconds);
|
||||
|
||||
// 전체 기간 동안의 합계 계산
|
||||
Map<String, Integer> totals = data.calculateTotalCounts();
|
||||
|
||||
// 데이터 포인트들을 오류 건수 -> 정상 건수 순으로 정렬
|
||||
List<Map<String, Object>> sortedDataPoints = IntStream.range(0, data.getTimestamps().size())
|
||||
.mapToObj(i -> {
|
||||
Map<String, Object> point = new HashMap<>();
|
||||
point.put("timestamp", data.getTimestamps().get(i));
|
||||
point.put("error", data.getError().get(i));
|
||||
point.put("success", data.getSuccess().get(i));
|
||||
point.put("avgTime", data.getAvgTime().get(i));
|
||||
return point;
|
||||
})
|
||||
.sorted((a, b) -> {
|
||||
// 1차: 오류 건수 내림차순
|
||||
int errorCompare = ((Integer)b.get("error")).compareTo((Integer)a.get("error"));
|
||||
if (errorCompare != 0) return errorCompare;
|
||||
// 2차: 정상 건수 내림차순
|
||||
return ((Integer)b.get("success")).compareTo((Integer)a.get("success"));
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("seconds", seconds);
|
||||
response.put("totalError", totals.get("error"));
|
||||
response.put("totalSuccess", totals.get("success"));
|
||||
response.put("dataPoints", sortedDataPoints);
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 어댑터 상태 정보 DTO
|
||||
* 시스템 내 어댑터의 상태 정보를 전달하기 위한 데이터 객체입니다.
|
||||
*/
|
||||
@Data
|
||||
public class AdapterStatusDTO {
|
||||
private String serverName; // 서버 이름
|
||||
private String instanceId; // 인스턴스 ID
|
||||
private String adapterType; // 어댑터 유형(Socket, HTTP)
|
||||
private String status; // 상태(normal, warning, error, inactive)
|
||||
private int totalCount; // 전체 수
|
||||
private int runningCount; // 실행 중인 수
|
||||
private int stopCount; // 정지된 수
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 어댑터 상태 요약 컨테이너 DTO
|
||||
* 모든 어댑터 유형의 상태 요약 정보를 담는 컨테이너입니다.
|
||||
*/
|
||||
@Data
|
||||
public class AdapterStatusSummaryContainerDTO {
|
||||
private Map<String, AdapterStatusSummaryDTO> adapterSummaries;
|
||||
private String overallStatus; // 전체 상태 (normal, warning, error, inactive)
|
||||
|
||||
public AdapterStatusSummaryContainerDTO() {
|
||||
this.adapterSummaries = new HashMap<>();
|
||||
this.overallStatus = "normal";
|
||||
|
||||
// 기본 어댑터 유형 초기화 (Socket, HTTP만 포함, Socket2는 Socket으로 통합)
|
||||
adapterSummaries.put("Socket", new AdapterStatusSummaryDTO("Socket"));
|
||||
adapterSummaries.put("HTTP", new AdapterStatusSummaryDTO("HTTP"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 어댑터 유형의 상태 요약 정보를 반환합니다.
|
||||
* @param adapterType 어댑터 유형
|
||||
* @return 해당 어댑터 유형의 상태 요약 정보
|
||||
*/
|
||||
public AdapterStatusSummaryDTO getAdapterSummary(String adapterType) {
|
||||
return adapterSummaries.get(adapterType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 모든 어댑터 유형의 상태 요약 정보를 리스트로 반환합니다.
|
||||
* @return 어댑터 상태 요약 정보 리스트
|
||||
*/
|
||||
public List<AdapterStatusSummaryDTO> getAdapterSummaryList() {
|
||||
return new ArrayList<>(adapterSummaries.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 전체 상태를 업데이트합니다.
|
||||
*/
|
||||
public void updateOverallStatus() {
|
||||
boolean hasError = false;
|
||||
boolean hasWarning = false;
|
||||
boolean hasNormal = false;
|
||||
|
||||
for (AdapterStatusSummaryDTO summary : adapterSummaries.values()) {
|
||||
if ("error".equals(summary.getOverallStatus())) {
|
||||
hasError = true;
|
||||
} else if ("warning".equals(summary.getOverallStatus())) {
|
||||
hasWarning = true;
|
||||
} else if ("normal".equals(summary.getOverallStatus())) {
|
||||
hasNormal = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasError) {
|
||||
overallStatus = "error";
|
||||
} else if (hasWarning) {
|
||||
overallStatus = "warning";
|
||||
} else if (hasNormal) {
|
||||
overallStatus = "normal";
|
||||
} else {
|
||||
overallStatus = "inactive";
|
||||
}
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 어댑터 상태 요약 정보 DTO
|
||||
* 어댑터 유형별 상태 집계 정보를 제공합니다.
|
||||
*/
|
||||
@Data
|
||||
public class AdapterStatusSummaryDTO {
|
||||
private String adapterType; // 어댑터 유형(SNA, Socket, Tuxedo, MQ, HTTP, EJB)
|
||||
|
||||
// 상태별 카운트
|
||||
private int errorCount; // 에러 상태 수
|
||||
private int warningCount; // 부분정상(경고) 상태 수
|
||||
private int normalCount; // 정상 상태 수
|
||||
private int inactiveCount; // 정지 상태 수
|
||||
|
||||
// 전체 상태 (UI 표시용)
|
||||
private String overallStatus; // 전체 상태 (normal, warning, error, inactive)
|
||||
|
||||
public AdapterStatusSummaryDTO(String adapterType) {
|
||||
this.adapterType = adapterType;
|
||||
this.errorCount = 0;
|
||||
this.warningCount = 0;
|
||||
this.normalCount = 0;
|
||||
this.inactiveCount = 0;
|
||||
this.overallStatus = "normal";
|
||||
}
|
||||
|
||||
/**
|
||||
* 상태 코드에 따라 카운트를 증가시킵니다.
|
||||
* @param statusCode 상태 코드 (0: 에러, 1: 부분정상, 2: 정상, 3: 정지)
|
||||
*/
|
||||
public void incrementStatusCount(int statusCode) {
|
||||
switch (statusCode) {
|
||||
case 0: // 에러
|
||||
errorCount++;
|
||||
break;
|
||||
case 1: // 부분정상
|
||||
warningCount++;
|
||||
break;
|
||||
case 2: // 정상
|
||||
normalCount++;
|
||||
break;
|
||||
case 3: // 정지
|
||||
inactiveCount++;
|
||||
break;
|
||||
}
|
||||
|
||||
// 전체 상태 업데이트
|
||||
updateOverallStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* 카운트 정보를 기반으로 전체 상태를 업데이트합니다.
|
||||
*/
|
||||
private void updateOverallStatus() {
|
||||
if (errorCount > 0) {
|
||||
overallStatus = "error";
|
||||
} else if (warningCount > 0) {
|
||||
overallStatus = "warning";
|
||||
} else if (normalCount > 0) {
|
||||
overallStatus = "normal";
|
||||
} else {
|
||||
overallStatus = "inactive";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 전체 어댑터 수를 반환합니다.
|
||||
*/
|
||||
public int getTotalCount() {
|
||||
return errorCount + warningCount + normalCount + inactiveCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class AlertMessageDTO {
|
||||
private String type; // 메시지 유형(개발, 운영 등)
|
||||
private String instance; // 인스턴스 ID
|
||||
private String timestamp; // 발생 시간
|
||||
private String message; // 메시지 내용
|
||||
private String severity; // 심각도(error, warning, info)
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.dto;
|
||||
|
||||
/**
|
||||
* 배치 처리 현황 DTO
|
||||
*/
|
||||
public class BatchProcessDTO {
|
||||
private String serviceType; // BAP 또는 BAT
|
||||
private String procBatch; // 원본 CSV 데이터
|
||||
private int sendSuccess; // 송신 성공
|
||||
private int receiveSuccess; // 수신 성공
|
||||
private int sendError; // 송신 에러
|
||||
private int receiveError; // 수신 에러
|
||||
private int sendWaiting; // 송신 대기
|
||||
private int receiveWaiting; // 수신 대기
|
||||
|
||||
public BatchProcessDTO() {
|
||||
// 기본 생성자
|
||||
}
|
||||
|
||||
public String getServiceType() {
|
||||
return serviceType;
|
||||
}
|
||||
|
||||
public void setServiceType(String serviceType) {
|
||||
this.serviceType = serviceType;
|
||||
}
|
||||
|
||||
public String getProcBatch() {
|
||||
return procBatch;
|
||||
}
|
||||
|
||||
public void setProcBatch(String procBatch) {
|
||||
this.procBatch = procBatch;
|
||||
|
||||
// CSV 데이터 파싱
|
||||
if (procBatch != null && !procBatch.isEmpty()) {
|
||||
String[] fields = procBatch.split(",");
|
||||
if (fields.length >= 6) {
|
||||
try {
|
||||
this.sendSuccess = Integer.parseInt(fields[0].trim());
|
||||
this.receiveSuccess = Integer.parseInt(fields[1].trim());
|
||||
this.sendError = Integer.parseInt(fields[2].trim());
|
||||
this.receiveError = Integer.parseInt(fields[3].trim());
|
||||
this.sendWaiting = Integer.parseInt(fields[4].trim());
|
||||
this.receiveWaiting = Integer.parseInt(fields[5].trim());
|
||||
} catch (NumberFormatException e) {
|
||||
// 파싱 실패 시 기본값 유지
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getSendSuccess() {
|
||||
return sendSuccess;
|
||||
}
|
||||
|
||||
public void setSendSuccess(int sendSuccess) {
|
||||
this.sendSuccess = sendSuccess;
|
||||
}
|
||||
|
||||
public int getReceiveSuccess() {
|
||||
return receiveSuccess;
|
||||
}
|
||||
|
||||
public void setReceiveSuccess(int receiveSuccess) {
|
||||
this.receiveSuccess = receiveSuccess;
|
||||
}
|
||||
|
||||
public int getSendError() {
|
||||
return sendError;
|
||||
}
|
||||
|
||||
public void setSendError(int sendError) {
|
||||
this.sendError = sendError;
|
||||
}
|
||||
|
||||
public int getReceiveError() {
|
||||
return receiveError;
|
||||
}
|
||||
|
||||
public void setReceiveError(int receiveError) {
|
||||
this.receiveError = receiveError;
|
||||
}
|
||||
|
||||
public int getSendWaiting() {
|
||||
return sendWaiting;
|
||||
}
|
||||
|
||||
public void setSendWaiting(int sendWaiting) {
|
||||
this.sendWaiting = sendWaiting;
|
||||
}
|
||||
|
||||
public int getReceiveWaiting() {
|
||||
return receiveWaiting;
|
||||
}
|
||||
|
||||
public void setReceiveWaiting(int receiveWaiting) {
|
||||
this.receiveWaiting = receiveWaiting;
|
||||
}
|
||||
|
||||
/**
|
||||
* 총 송신 건수 반환
|
||||
*/
|
||||
public int getTotalSend() {
|
||||
return sendSuccess + sendError + sendWaiting;
|
||||
}
|
||||
|
||||
/**
|
||||
* 총 수신 건수 반환
|
||||
*/
|
||||
public int getTotalReceive() {
|
||||
return receiveSuccess + receiveError + receiveWaiting;
|
||||
}
|
||||
|
||||
/**
|
||||
* 총 처리 건수 반환
|
||||
*/
|
||||
public int getTotal() {
|
||||
return getTotalSend() + getTotalReceive();
|
||||
}
|
||||
|
||||
/**
|
||||
* 송신 오류율 계산 (%)
|
||||
*/
|
||||
public double getSendErrorRate() {
|
||||
if (getTotalSend() == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
return (double) sendError / getTotalSend() * 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* 수신 오류율 계산 (%)
|
||||
*/
|
||||
public double getReceiveErrorRate() {
|
||||
if (getTotalReceive() == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
return (double) receiveError / getTotalReceive() * 100;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 인스턴스 정보 DTO
|
||||
* 서버 인스턴스의 상태 정보를 전달하기 위한 데이터 객체입니다.
|
||||
*/
|
||||
@Data
|
||||
public class InstanceDTO {
|
||||
private String id; // 인스턴스 ID
|
||||
private String status; // 상태(normal, warning, error, inactive)
|
||||
private String throughput; // 처리량(초당 처리 건수)
|
||||
|
||||
/**
|
||||
* 처리량 수치만 추출
|
||||
*/
|
||||
public int getThroughputValue() {
|
||||
if (throughput == null || throughput.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
// "123/s" 형식에서 숫자만 추출
|
||||
return Integer.parseInt(throughput.replaceAll("[^0-9]", ""));
|
||||
} catch (NumberFormatException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 상태에 따른 표시용 텍스트 반환
|
||||
*/
|
||||
public String getStatusText() {
|
||||
switch (status) {
|
||||
case "normal":
|
||||
return "정상";
|
||||
case "warning":
|
||||
return "경고";
|
||||
case "error":
|
||||
return "에러";
|
||||
case "inactive":
|
||||
return "비활성";
|
||||
default:
|
||||
return "알 수 없음";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 기관 연결 상태 모니터링을 위한 DTO
|
||||
*/
|
||||
@Data
|
||||
public class InstitutionStatusDTO {
|
||||
|
||||
/**
|
||||
* 기관 코드
|
||||
*/
|
||||
private String institutionCode;
|
||||
|
||||
/**
|
||||
* 기관명
|
||||
*/
|
||||
private String institutionName;
|
||||
|
||||
/**
|
||||
* 담당자명
|
||||
*/
|
||||
private String contactPerson;
|
||||
|
||||
/**
|
||||
* 담당자 연락처
|
||||
*/
|
||||
private String contactNumber;
|
||||
|
||||
/**
|
||||
* 정상 어댑터 수
|
||||
*/
|
||||
private int normalAdapterCount;
|
||||
|
||||
/**
|
||||
* 오류 어댑터 수
|
||||
*/
|
||||
private int errorAdapterCount;
|
||||
|
||||
/**
|
||||
* 회선 상태 (NORMAL, WARN, ERROR)
|
||||
*/
|
||||
private String lineStatus;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 피크 성능 정보 DTO
|
||||
* 시스템의 최대 성능 기록을 전달하기 위한 데이터 객체입니다.
|
||||
*/
|
||||
@Data
|
||||
public class PeakPerformanceDTO {
|
||||
private String serviceType; // 서비스 유형(EAI, FEP 등)
|
||||
private String date; // 날짜(YYYYMMDD)
|
||||
private int transactionCount; // 처리 건수
|
||||
private int tps; // 초당 트랜잭션 수(Transactions Per Second)
|
||||
|
||||
/**
|
||||
* 날짜를 포맷팅하여 반환 (YYYY-MM-DD 형식)
|
||||
*/
|
||||
public String getFormattedDate() {
|
||||
if (date == null || date.length() < 8) {
|
||||
return date;
|
||||
}
|
||||
|
||||
return date.substring(0, 4) + "-" +
|
||||
date.substring(4, 6) + "-" +
|
||||
date.substring(6, 8);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 서버 상태 정보 DTO
|
||||
* 서버의 상태 정보와 하위 인스턴스 목록을 포함하는 데이터 객체입니다.
|
||||
*/
|
||||
@Data
|
||||
public class ServerStatusDTO {
|
||||
private String name; // 서버 이름
|
||||
private String text; // 서버 표시명
|
||||
private String hostname; // 서버 표시명
|
||||
private String status; // 상태(normal, warning, error)
|
||||
private int cpu; // CPU 사용률(%)
|
||||
private int memory; // 메모리 사용률(%)
|
||||
private int disk; // 디스크 사용률(%)
|
||||
private List<InstanceDTO> instances = new ArrayList<>(); // 인스턴스 목록
|
||||
|
||||
/**
|
||||
* 특정 인스턴스 ID에 해당하는 인스턴스를 찾습니다.
|
||||
* @param instanceId 찾을 인스턴스 ID
|
||||
* @return 해당 인스턴스 또는 null
|
||||
*/
|
||||
public InstanceDTO findInstance(String instanceId) {
|
||||
if (instanceId == null || instances == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (InstanceDTO instance : instances) {
|
||||
if (instanceId.equals(instance.getId())) {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 에러 상태의 인스턴스 수를 반환합니다.
|
||||
*/
|
||||
public int getErrorInstanceCount() {
|
||||
int count = 0;
|
||||
for (InstanceDTO instance : instances) {
|
||||
if ("error".equals(instance.getStatus())) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 경고 상태의 인스턴스 수를 반환합니다.
|
||||
*/
|
||||
public int getWarningInstanceCount() {
|
||||
int count = 0;
|
||||
for (InstanceDTO instance : instances) {
|
||||
if ("warning".equals(instance.getStatus())) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 정상 상태의 인스턴스 수를 반환합니다.
|
||||
*/
|
||||
public int getNormalInstanceCount() {
|
||||
int count = 0;
|
||||
for (InstanceDTO instance : instances) {
|
||||
if ("normal".equals(instance.getStatus())) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 비활성 상태의 인스턴스 수를 반환합니다.
|
||||
*/
|
||||
public int getInactiveInstanceCount() {
|
||||
int count = 0;
|
||||
for (InstanceDTO instance : instances) {
|
||||
if ("inactive".equals(instance.getStatus())) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* SMS 장애 통보 정보를 담는 DTO 클래스
|
||||
*/
|
||||
public class SmsNotificationDto {
|
||||
@JsonFormat(pattern = "MMdd HH:mm:ss.SSS")
|
||||
private LocalDateTime dateTime; // 발생 일시
|
||||
private Integer count; // 발생 건수
|
||||
private String instanceName; // 인스턴스 명
|
||||
private String errorContent; // 에러 내용
|
||||
private String adapterDesc; // 어댑터 설명 (어댑터 오류인 경우)
|
||||
private String adapterId; // 어댑터 ID (어댑터 오류인 경우)
|
||||
private String interfaceId; // 인터페이스 ID (인터페이스 오류인 경우)
|
||||
private String errorType; // 오류 유형 (ADAPTER/INTERFACE)
|
||||
private String serviceType; // 서비스 유형
|
||||
|
||||
// Getters and Setters
|
||||
public LocalDateTime getDateTime() {
|
||||
return dateTime;
|
||||
}
|
||||
|
||||
public void setDateTime(LocalDateTime dateTime) {
|
||||
this.dateTime = dateTime;
|
||||
}
|
||||
|
||||
public Integer getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public void setCount(Integer count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public String getInstanceName() {
|
||||
return instanceName;
|
||||
}
|
||||
|
||||
public void setInstanceName(String instanceName) {
|
||||
this.instanceName = instanceName;
|
||||
}
|
||||
|
||||
public String getErrorContent() {
|
||||
return errorContent;
|
||||
}
|
||||
|
||||
public void setErrorContent(String errorContent) {
|
||||
this.errorContent = errorContent;
|
||||
}
|
||||
|
||||
public String getAdapterDesc() {
|
||||
return adapterDesc;
|
||||
}
|
||||
|
||||
public void setAdapterDesc(String adapterDesc) {
|
||||
this.adapterDesc = adapterDesc;
|
||||
}
|
||||
|
||||
public String getAdapterId() {
|
||||
return adapterId;
|
||||
}
|
||||
|
||||
public void setAdapterId(String adapterId) {
|
||||
this.adapterId = adapterId;
|
||||
}
|
||||
|
||||
public String getInterfaceId() {
|
||||
return interfaceId;
|
||||
}
|
||||
|
||||
public void setInterfaceId(String interfaceId) {
|
||||
this.interfaceId = interfaceId;
|
||||
}
|
||||
|
||||
public String getErrorType() {
|
||||
return errorType;
|
||||
}
|
||||
|
||||
public void setErrorType(String errorType) {
|
||||
this.errorType = errorType;
|
||||
}
|
||||
|
||||
public String getServiceType() {
|
||||
return serviceType;
|
||||
}
|
||||
|
||||
public void setServiceType(String serviceType) {
|
||||
this.serviceType = serviceType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SmsNotificationDto{" +
|
||||
"dateTime=" + dateTime +
|
||||
", count=" + count +
|
||||
", instanceName='" + instanceName + '\'' +
|
||||
", errorContent='" + errorContent + '\'' +
|
||||
", adapterDesc='" + adapterDesc + '\'' +
|
||||
", adapterId='" + adapterId + '\'' +
|
||||
", interfaceId='" + interfaceId + '\'' +
|
||||
", errorType='" + errorType + '\'' +
|
||||
", serviceType='" + serviceType + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Socket Process DTO for modern dashboard
|
||||
*/
|
||||
public class SocketProcessDTO {
|
||||
|
||||
private JsonPageVoDTO jsonPageVo;
|
||||
private int total;
|
||||
private int page;
|
||||
private int records;
|
||||
private List<SocketProcessItemDTO> rows;
|
||||
|
||||
public SocketProcessDTO() {
|
||||
}
|
||||
|
||||
public JsonPageVoDTO getJsonPageVo() {
|
||||
return jsonPageVo;
|
||||
}
|
||||
|
||||
public void setJsonPageVo(JsonPageVoDTO jsonPageVo) {
|
||||
this.jsonPageVo = jsonPageVo;
|
||||
}
|
||||
|
||||
public int getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public void setTotal(int total) {
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
public int getPage() {
|
||||
return page;
|
||||
}
|
||||
|
||||
public void setPage(int page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
public int getRecords() {
|
||||
return records;
|
||||
}
|
||||
|
||||
public void setRecords(int records) {
|
||||
this.records = records;
|
||||
}
|
||||
|
||||
public List<SocketProcessItemDTO> getRows() {
|
||||
return rows;
|
||||
}
|
||||
|
||||
public void setRows(List<SocketProcessItemDTO> rows) {
|
||||
this.rows = rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* JsonPageVo DTO
|
||||
*/
|
||||
public static class JsonPageVoDTO {
|
||||
private int page;
|
||||
private int totalCount;
|
||||
private int rows;
|
||||
private int total;
|
||||
private int startNum;
|
||||
private int endNum;
|
||||
|
||||
public JsonPageVoDTO() {
|
||||
}
|
||||
|
||||
public int getPage() {
|
||||
return page;
|
||||
}
|
||||
|
||||
public void setPage(int page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
public int getTotalCount() {
|
||||
return totalCount;
|
||||
}
|
||||
|
||||
public void setTotalCount(int totalCount) {
|
||||
this.totalCount = totalCount;
|
||||
}
|
||||
|
||||
public int getRows() {
|
||||
return rows;
|
||||
}
|
||||
|
||||
public void setRows(int rows) {
|
||||
this.rows = rows;
|
||||
}
|
||||
|
||||
public int getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public void setTotal(int total) {
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
public int getStartNum() {
|
||||
return startNum;
|
||||
}
|
||||
|
||||
public void setStartNum(int startNum) {
|
||||
this.startNum = startNum;
|
||||
}
|
||||
|
||||
public int getEndNum() {
|
||||
return endNum;
|
||||
}
|
||||
|
||||
public void setEndNum(int endNum) {
|
||||
this.endNum = endNum;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Socket Process Item DTO
|
||||
*/
|
||||
public static class SocketProcessItemDTO {
|
||||
private String maxConnection;
|
||||
private String status;
|
||||
private String eaiSevrInstncName;
|
||||
private String adptrBzwkGroupName;
|
||||
private String currentConnection;
|
||||
private String recvCount;
|
||||
private String adptrBzwkName;
|
||||
private String adptrBzwkDesc;
|
||||
private String sendCount;
|
||||
private String errorCount;
|
||||
|
||||
public SocketProcessItemDTO() {
|
||||
}
|
||||
|
||||
public String getMaxConnection() {
|
||||
return maxConnection;
|
||||
}
|
||||
|
||||
public void setMaxConnection(String maxConnection) {
|
||||
this.maxConnection = maxConnection;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getEaiSevrInstncName() {
|
||||
return eaiSevrInstncName;
|
||||
}
|
||||
|
||||
public void setEaiSevrInstncName(String eaiSevrInstncName) {
|
||||
this.eaiSevrInstncName = eaiSevrInstncName;
|
||||
}
|
||||
|
||||
public String getAdptrBzwkGroupName() {
|
||||
return adptrBzwkGroupName;
|
||||
}
|
||||
|
||||
public void setAdptrBzwkGroupName(String adptrBzwkGroupName) {
|
||||
this.adptrBzwkGroupName = adptrBzwkGroupName;
|
||||
}
|
||||
|
||||
public String getCurrentConnection() {
|
||||
return currentConnection;
|
||||
}
|
||||
|
||||
public void setCurrentConnection(String currentConnection) {
|
||||
this.currentConnection = currentConnection;
|
||||
}
|
||||
|
||||
public String getRecvCount() {
|
||||
return recvCount;
|
||||
}
|
||||
|
||||
public void setRecvCount(String recvCount) {
|
||||
this.recvCount = recvCount;
|
||||
}
|
||||
|
||||
public String getAdptrBzwkName() {
|
||||
return adptrBzwkName;
|
||||
}
|
||||
|
||||
public void setAdptrBzwkName(String adptrBzwkName) {
|
||||
this.adptrBzwkName = adptrBzwkName;
|
||||
}
|
||||
|
||||
public String getAdptrBzwkDesc() {
|
||||
return adptrBzwkDesc;
|
||||
}
|
||||
|
||||
public void setAdptrBzwkDesc(String adptrBzwkDesc) {
|
||||
this.adptrBzwkDesc = adptrBzwkDesc;
|
||||
}
|
||||
|
||||
public String getSendCount() {
|
||||
return sendCount;
|
||||
}
|
||||
|
||||
public void setSendCount(String sendCount) {
|
||||
this.sendCount = sendCount;
|
||||
}
|
||||
|
||||
public String getErrorCount() {
|
||||
return errorCount;
|
||||
}
|
||||
|
||||
public void setErrorCount(String errorCount) {
|
||||
this.errorCount = errorCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Socket Status DTO for modern dashboard
|
||||
*/
|
||||
public class SocketStatusDTO {
|
||||
|
||||
private int total;
|
||||
private int page;
|
||||
private int records;
|
||||
private List<SocketStatusItemDTO> rows;
|
||||
|
||||
public SocketStatusDTO() {
|
||||
}
|
||||
|
||||
public int getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public void setTotal(int total) {
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
public int getPage() {
|
||||
return page;
|
||||
}
|
||||
|
||||
public void setPage(int page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
public int getRecords() {
|
||||
return records;
|
||||
}
|
||||
|
||||
public void setRecords(int records) {
|
||||
this.records = records;
|
||||
}
|
||||
|
||||
public List<SocketStatusItemDTO> getRows() {
|
||||
return rows;
|
||||
}
|
||||
|
||||
public void setRows(List<SocketStatusItemDTO> rows) {
|
||||
this.rows = rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Socket Status Item DTO
|
||||
*/
|
||||
public static class SocketStatusItemDTO {
|
||||
private String status;
|
||||
private String eaiSevrInstncName;
|
||||
private String testMasterYn;
|
||||
private String adptrNickName;
|
||||
private String startStop;
|
||||
private String adptrBzwkGroupName;
|
||||
private String adptrBzwkName;
|
||||
private String adptrType;
|
||||
private String starting;
|
||||
private String adptrBzwkDesc;
|
||||
|
||||
public SocketStatusItemDTO() {
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getEaiSevrInstncName() {
|
||||
return eaiSevrInstncName;
|
||||
}
|
||||
|
||||
public void setEaiSevrInstncName(String eaiSevrInstncName) {
|
||||
this.eaiSevrInstncName = eaiSevrInstncName;
|
||||
}
|
||||
|
||||
public String getTestMasterYn() {
|
||||
return testMasterYn;
|
||||
}
|
||||
|
||||
public void setTestMasterYn(String testMasterYn) {
|
||||
this.testMasterYn = testMasterYn;
|
||||
}
|
||||
|
||||
public String getAdptrNickName() {
|
||||
return adptrNickName;
|
||||
}
|
||||
|
||||
public void setAdptrNickName(String adptrNickName) {
|
||||
this.adptrNickName = adptrNickName;
|
||||
}
|
||||
|
||||
public String getStartStop() {
|
||||
return startStop;
|
||||
}
|
||||
|
||||
public void setStartStop(String startStop) {
|
||||
this.startStop = startStop;
|
||||
}
|
||||
|
||||
public String getAdptrBzwkGroupName() {
|
||||
return adptrBzwkGroupName;
|
||||
}
|
||||
|
||||
public void setAdptrBzwkGroupName(String adptrBzwkGroupName) {
|
||||
this.adptrBzwkGroupName = adptrBzwkGroupName;
|
||||
}
|
||||
|
||||
public String getAdptrBzwkName() {
|
||||
return adptrBzwkName;
|
||||
}
|
||||
|
||||
public void setAdptrBzwkName(String adptrBzwkName) {
|
||||
this.adptrBzwkName = adptrBzwkName;
|
||||
}
|
||||
|
||||
public String getAdptrType() {
|
||||
return adptrType;
|
||||
}
|
||||
|
||||
public void setAdptrType(String adptrType) {
|
||||
this.adptrType = adptrType;
|
||||
}
|
||||
|
||||
public String getStarting() {
|
||||
return starting;
|
||||
}
|
||||
|
||||
public void setStarting(String starting) {
|
||||
this.starting = starting;
|
||||
}
|
||||
|
||||
public String getAdptrBzwkDesc() {
|
||||
return adptrBzwkDesc;
|
||||
}
|
||||
|
||||
public void setAdptrBzwkDesc(String adptrBzwkDesc) {
|
||||
this.adptrBzwkDesc = adptrBzwkDesc;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 트랜잭션 정보 DTO
|
||||
* 시스템 내 처리된 트랜잭션 정보를 전달하기 위한 데이터 객체입니다.
|
||||
*/
|
||||
@Data
|
||||
public class TransactionDTO {
|
||||
private String type; // 서비스 유형(EAI, FEP 등)
|
||||
private int success; // 성공 건수
|
||||
private int fail; // 실패(업무 에러) 건수
|
||||
private int timeout; // 타임아웃 건수
|
||||
private int syserror; // 시스템 에러 건수
|
||||
|
||||
/**
|
||||
* 총 처리 건수 반환
|
||||
*/
|
||||
public int getTotal() {
|
||||
return success + fail + timeout + syserror;
|
||||
}
|
||||
|
||||
/**
|
||||
* 에러 건수 합계 반환 (업무 에러 + 타임아웃 + 시스템 에러)
|
||||
*/
|
||||
public int getTotalError() {
|
||||
return fail + timeout + syserror;
|
||||
}
|
||||
}
|
||||
+354
@@ -0,0 +1,354 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.mapper;
|
||||
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceType;
|
||||
import com.eactive.eai.rms.onl.dashboard.modern.dto.*;
|
||||
import com.eactive.eai.rms.onl.tracking.vo.MemStaticsVo;
|
||||
import com.eactive.eai.rms.onl.vo.AdaptersVO;
|
||||
import com.eactive.eai.rms.onl.vo.EAIEngineStatusVo;
|
||||
import com.eactive.eai.rms.onl.vo.HostStatusVo;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 대시보드 데이터 매퍼
|
||||
* VO 객체를 DTO로 변환하는 역할을 담당합니다.
|
||||
*/
|
||||
@Component
|
||||
public class DashboardDataMapper {
|
||||
|
||||
/**
|
||||
* EAIEngineStatusVo를 ServerStatusDTO로 변환
|
||||
* 서버 상태 정보를 포함합니다.
|
||||
*/
|
||||
public ServerStatusDTO fromEAIEngineStatusVo(EAIEngineStatusVo vo) {
|
||||
ServerStatusDTO dto = new ServerStatusDTO();
|
||||
|
||||
if (vo == null) {
|
||||
return dto;
|
||||
}
|
||||
|
||||
// 서버 기본 정보 설정
|
||||
dto.setName(vo.getServerName() != null ? vo.getServerName() : vo.getHostName());
|
||||
dto.setText(vo.getService());
|
||||
|
||||
// 서버 상태 설정 (serverStateImage: 0=중지/에러, 1=경고, 2=정상)
|
||||
switch (vo.getServerStateImage()) {
|
||||
case 0:
|
||||
dto.setStatus("error");
|
||||
break;
|
||||
case 1:
|
||||
dto.setStatus("warning");
|
||||
break;
|
||||
case 2:
|
||||
dto.setStatus("normal");
|
||||
break;
|
||||
default:
|
||||
dto.setStatus("unknown");
|
||||
}
|
||||
|
||||
// 리소스 사용률 설정
|
||||
if (vo.getHeapSizeCurrent() > 0) {
|
||||
dto.setCpu((int)(100 - (vo.getHeapFreeCurrent() * 100) / vo.getHeapSizeCurrent()));
|
||||
} else {
|
||||
dto.setCpu(0);
|
||||
}
|
||||
dto.setMemory(vo.getHeapFreePercent() > 0 ? 100 - vo.getHeapFreePercent() : 0);
|
||||
dto.setDisk(vo.getJmsFileStoreUseAge());
|
||||
|
||||
// 인스턴스 정보 설정
|
||||
List<InstanceDTO> instances = new ArrayList<>();
|
||||
InstanceDTO instance = new InstanceDTO();
|
||||
instance.setId(vo.getInstanceName());
|
||||
|
||||
// 인스턴스 상태 설정
|
||||
switch (vo.getServerStateImage()) {
|
||||
case 0:
|
||||
instance.setStatus("error");
|
||||
break;
|
||||
case 1:
|
||||
instance.setStatus("warning");
|
||||
break;
|
||||
case 2:
|
||||
instance.setStatus("normal");
|
||||
break;
|
||||
default:
|
||||
instance.setStatus("inactive");
|
||||
}
|
||||
|
||||
// 처리량 정보 설정
|
||||
instance.setThroughput(vo.getPendingRequestCurrentCount() + "/s");
|
||||
|
||||
instances.add(instance);
|
||||
dto.setInstances(instances);
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* HostStatusVo를 ServerStatusDTO로 변환
|
||||
* 호스트 상태 정보를 포함합니다.
|
||||
*/
|
||||
public ServerStatusDTO fromHostStatusVo(HostStatusVo vo) {
|
||||
ServerStatusDTO dto = new ServerStatusDTO();
|
||||
|
||||
if (vo == null) {
|
||||
return dto;
|
||||
}
|
||||
|
||||
dto.setName(vo.getHostName());
|
||||
dto.setText(vo.getHostName());
|
||||
dto.setCpu(vo.getCpu());
|
||||
dto.setMemory(vo.getMemory());
|
||||
dto.setDisk(vo.getDisk());
|
||||
dto.setStatus(getStatusFromUsage(vo.getCpu(), vo.getMemory(), vo.getDisk()));
|
||||
|
||||
// 인스턴스 리스트는 다른 곳에서 채워질 것임
|
||||
dto.setInstances(new ArrayList<>());
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* AdaptersVO를 AdapterStatusDTO 목록으로 변환
|
||||
* Socket, HTTP 어댑터 정보만 포함합니다.
|
||||
*/
|
||||
public List<AdapterStatusDTO> fromAdaptersVO(AdaptersVO vo) {
|
||||
List<AdapterStatusDTO> dtoList = new ArrayList<>();
|
||||
|
||||
if (vo == null) {
|
||||
return dtoList;
|
||||
}
|
||||
|
||||
// Socket 어댑터 상태 (Socket과 Socket2를 하나로 통합)
|
||||
AdapterStatusDTO socketDTO = new AdapterStatusDTO();
|
||||
socketDTO.setServerName(vo.getHostName());
|
||||
socketDTO.setInstanceId(vo.getInstanceName());
|
||||
socketDTO.setAdapterType("Socket");
|
||||
|
||||
// Socket과 Socket2의 상태 중 더 심각한 상태를 선택
|
||||
int socketStatus = vo.getSocket(3);
|
||||
int socket2Status = vo.getSocket2(3);
|
||||
int combinedStatus = Math.min(socketStatus, socket2Status); // 낮은 값이 더 심각한 상태
|
||||
socketDTO.setStatus(getStatusTextFromCode(combinedStatus));
|
||||
|
||||
// Socket과 Socket2의 카운트를 합산
|
||||
socketDTO.setTotalCount(vo.getSocket(0) + vo.getSocket2(0));
|
||||
socketDTO.setRunningCount(vo.getSocket(1) + vo.getSocket2(1));
|
||||
socketDTO.setStopCount(vo.getSocket(2) + vo.getSocket2(2));
|
||||
dtoList.add(socketDTO);
|
||||
|
||||
// HTTP 어댑터 상태
|
||||
AdapterStatusDTO httpDTO = new AdapterStatusDTO();
|
||||
httpDTO.setServerName(vo.getHostName());
|
||||
httpDTO.setInstanceId(vo.getInstanceName());
|
||||
httpDTO.setAdapterType("HTTP");
|
||||
httpDTO.setStatus(getStatusTextFromCode(vo.getHttp(3)));
|
||||
httpDTO.setTotalCount(vo.getHttp(0));
|
||||
httpDTO.setRunningCount(vo.getHttp(1));
|
||||
httpDTO.setStopCount(vo.getHttp(2));
|
||||
dtoList.add(httpDTO);
|
||||
|
||||
return dtoList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 상태 코드를 텍스트로 변환
|
||||
*/
|
||||
private String getStatusTextFromCode(int statusCode) {
|
||||
switch (statusCode) {
|
||||
case 0:
|
||||
return "error";
|
||||
case 1:
|
||||
return "warning";
|
||||
case 2:
|
||||
return "normal";
|
||||
case 3:
|
||||
return "inactive";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MemStaticsVo를 TransactionDTO로 변환
|
||||
* 트랜잭션 통계 정보를 포함합니다.
|
||||
*/
|
||||
public TransactionDTO fromMemStaticsVo(MemStaticsVo vo, String serviceType) {
|
||||
TransactionDTO dto = new TransactionDTO();
|
||||
|
||||
if (vo == null) {
|
||||
return dto;
|
||||
}
|
||||
|
||||
dto.setType(serviceType); // 서비스 타입을 파라미터로 받아서 설정
|
||||
dto.setSuccess(vo.getProcTotal() - vo.getTimeoutTotal() - vo.getErrorTotal() - vo.getSysErrorTotal());
|
||||
dto.setTimeout(vo.getTimeoutTotal());
|
||||
dto.setFail(vo.getErrorTotal());
|
||||
dto.setSyserror(vo.getSysErrorTotal());
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* DB 저장소에서 가져온 피크 성능 문자열을 PeakPerformanceDTO로 변환
|
||||
*/
|
||||
public PeakPerformanceDTO fromPeakPerformanceString(String peakDataStr, String type) {
|
||||
PeakPerformanceDTO dto = new PeakPerformanceDTO();
|
||||
|
||||
if (peakDataStr == null || peakDataStr.isEmpty()) {
|
||||
return dto;
|
||||
}
|
||||
|
||||
String[] fields = peakDataStr.split(",");
|
||||
if (fields.length < 4) {
|
||||
return dto;
|
||||
}
|
||||
|
||||
dto.setServiceType(fields[0]); // 서비스 유형
|
||||
dto.setDate(fields[1]); // 날짜
|
||||
|
||||
try {
|
||||
dto.setTransactionCount(Integer.parseInt(fields[2])); // 처리 건수
|
||||
if( "--".equals(fields[3]) ){
|
||||
dto.setTps(0);
|
||||
} else {
|
||||
dto.setTps(Integer.parseInt(fields[3])); // TPS
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
dto.setTransactionCount(0);
|
||||
dto.setTps(0);
|
||||
}
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 일괄 전송 처리 데이터를 BatchProcessDTO로 변환
|
||||
* @param csvData 형식: 송신성공,수신성공,송신에러,수신에러,송신대기,수신대기
|
||||
*/
|
||||
public BatchProcessDTO fromBatchProcessString(String serviceType, String csvData) {
|
||||
BatchProcessDTO dto = new BatchProcessDTO();
|
||||
dto.setServiceType(serviceType);
|
||||
dto.setProcBatch(csvData); // 원본 CSV 데이터 설정
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 서버 인스턴스 정보를 포함하는 서버 상태 DTO 생성
|
||||
*/
|
||||
public ServerStatusDTO createServerStatus(DataSourceType server, Map<String, Object> serverData) {
|
||||
ServerStatusDTO dto = new ServerStatusDTO();
|
||||
dto.setName(server.getName());
|
||||
dto.setText(server.getText());
|
||||
|
||||
// CPU, 메모리, 디스크 사용률 설정
|
||||
if (serverData.containsKey("cpu")) {
|
||||
dto.setCpu((int) serverData.get("cpu"));
|
||||
}
|
||||
if (serverData.containsKey("memory")) {
|
||||
dto.setMemory((int) serverData.get("memory"));
|
||||
}
|
||||
if (serverData.containsKey("disk")) {
|
||||
dto.setDisk((int) serverData.get("disk"));
|
||||
}
|
||||
|
||||
// 서버 상태 설정
|
||||
dto.setStatus(getStatusFromUsage(dto.getCpu(), dto.getMemory(), dto.getDisk()));
|
||||
|
||||
// 인스턴스 목록 설정
|
||||
List<InstanceDTO> instances = new ArrayList<>();
|
||||
if (serverData.containsKey("instances")) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> instanceDataList = (List<Map<String, Object>>) serverData.get("instances");
|
||||
|
||||
for (Map<String, Object> instanceData : instanceDataList) {
|
||||
InstanceDTO instance = new InstanceDTO();
|
||||
|
||||
if (instanceData.containsKey("id")) {
|
||||
instance.setId((String) instanceData.get("id"));
|
||||
}
|
||||
if (instanceData.containsKey("status")) {
|
||||
instance.setStatus((String) instanceData.get("status"));
|
||||
}
|
||||
if (instanceData.containsKey("throughput")) {
|
||||
instance.setThroughput((String) instanceData.get("throughput"));
|
||||
}
|
||||
|
||||
instances.add(instance);
|
||||
}
|
||||
}
|
||||
|
||||
dto.setInstances(instances);
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용률에 따른 상태 반환
|
||||
*/
|
||||
private String getStatusFromUsage(int cpu, int memory, int disk) {
|
||||
if (cpu > 90 || memory > 90 || disk > 90) {
|
||||
return "error";
|
||||
} else if (cpu > 70 || memory > 70 || disk > 70) {
|
||||
return "warning";
|
||||
} else {
|
||||
return "normal";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 서버 이름과 인스턴스 목록을 기반으로 인스턴스별 상태를 생성
|
||||
*/
|
||||
public List<Map<String, Object>> createInstanceStatusList(String serverName, List<EAIEngineStatusVo> engineStatusList) {
|
||||
List<Map<String, Object>> instanceStatusList = new ArrayList<>();
|
||||
|
||||
for (EAIEngineStatusVo engineStatus : engineStatusList) {
|
||||
Map<String, Object> instanceStatus = new HashMap<>();
|
||||
instanceStatus.put("serverName", serverName);
|
||||
instanceStatus.put("instanceName", engineStatus.getInstanceName());
|
||||
instanceStatus.put("status", getStatusTextFromCode(engineStatus.getServerStateImage()));
|
||||
instanceStatus.put("cpu", engineStatus.getHeapFreePercent() > 0 ? 100 - engineStatus.getHeapFreePercent() : 0);
|
||||
instanceStatus.put("memory", engineStatus.getHeapFreePercent() > 0 ? engineStatus.getHeapFreePercent() : 0);
|
||||
instanceStatus.put("disk", engineStatus.getJmsFileStoreUseAge());
|
||||
instanceStatus.put("throughput", engineStatus.getPendingRequestCurrentCount() + "/s");
|
||||
|
||||
instanceStatusList.add(instanceStatus);
|
||||
}
|
||||
|
||||
return instanceStatusList;
|
||||
}
|
||||
|
||||
/**
|
||||
* AdaptersVO 목록을 AdapterStatusSummaryContainerDTO로 변환
|
||||
* 어댑터 유형별 상태 집계 정보를 제공합니다.
|
||||
*/
|
||||
public AdapterStatusSummaryContainerDTO fromAdaptersVOList(List<AdaptersVO> adaptersList) {
|
||||
AdapterStatusSummaryContainerDTO container = new AdapterStatusSummaryContainerDTO();
|
||||
|
||||
if (adaptersList == null || adaptersList.isEmpty()) {
|
||||
return container;
|
||||
}
|
||||
|
||||
// 각 어댑터의 상태를 집계
|
||||
for (AdaptersVO vo : adaptersList) {
|
||||
if (vo == null) continue;
|
||||
|
||||
// Socket 어댑터 상태 집계 (Socket과 Socket2를 하나로 통합)
|
||||
container.getAdapterSummary("Socket").incrementStatusCount(vo.getSocket(3));
|
||||
container.getAdapterSummary("Socket").incrementStatusCount(vo.getSocket2(3));
|
||||
|
||||
// HTTP 어댑터 상태 집계
|
||||
container.getAdapterSummary("HTTP").incrementStatusCount(vo.getHttp(3));
|
||||
}
|
||||
|
||||
// 전체 상태 업데이트
|
||||
container.updateOverallStatus();
|
||||
|
||||
return container;
|
||||
}
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.mapper;
|
||||
|
||||
import com.eactive.eai.rms.onl.dashboard.modern.dto.SmsNotificationDto;
|
||||
import com.eactive.eai.rms.onl.vo.SmsVO;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* SMS 장애 통보 정보를 DTO로 변환하는 매퍼 클래스
|
||||
*/
|
||||
@Component
|
||||
public class SmsNotificationMapper {
|
||||
|
||||
private static final Pattern DATE_TIME_PATTERN = Pattern.compile("(\\d{4}) (\\d{2}:\\d{2}:\\d{2}\\.\\d{3})");
|
||||
private static final Pattern COUNT_PATTERN = Pattern.compile("\\[(\\d+)\\]");
|
||||
|
||||
/**
|
||||
* SmsVO 목록을 SmsNotificationDto 목록으로 변환합니다.
|
||||
*
|
||||
* @param smsList SmsVO 목록
|
||||
* @return SmsNotificationDto 목록
|
||||
*/
|
||||
public List<SmsNotificationDto> mapToSmsNotificationDtoList(List<SmsVO> smsList) {
|
||||
List<SmsNotificationDto> dtoList = new ArrayList<>();
|
||||
|
||||
if (smsList == null || smsList.isEmpty()) {
|
||||
return dtoList;
|
||||
}
|
||||
|
||||
for (SmsVO smsVO : smsList) {
|
||||
List<String> messages = smsVO.getMessage();
|
||||
|
||||
if (messages != null && !messages.isEmpty()) {
|
||||
for (String msg : messages) {
|
||||
SmsNotificationDto dto = parseMessage(msg);
|
||||
if (dto != null) {
|
||||
dto.setServiceType(smsVO.getService()); // SmsVO의 service 값을 serviceType으로 매핑
|
||||
dtoList.add(dto);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dtoList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 메시지를 파싱하여 SmsNotificationDto 객체를 생성합니다.
|
||||
*
|
||||
* @param message 메시지 문자열
|
||||
* @return SmsNotificationDto 객체
|
||||
*/
|
||||
private SmsNotificationDto parseMessage(String message) {
|
||||
if (message == null || message.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
String[] parts = message.split("\\|");
|
||||
|
||||
// Determine message type based on number of delimiters
|
||||
boolean isAdapterError = parts.length == 3;
|
||||
boolean isInterfaceError = parts.length == 2;
|
||||
|
||||
if (!isAdapterError && !isInterfaceError) {
|
||||
return null;
|
||||
}
|
||||
|
||||
SmsNotificationDto dto = new SmsNotificationDto();
|
||||
|
||||
// Parse the first part (common for both types)
|
||||
String mainPart = parts[0];
|
||||
|
||||
// Extract date and time
|
||||
Matcher dateTimeMatcher = DATE_TIME_PATTERN.matcher(mainPart);
|
||||
if (dateTimeMatcher.find()) {
|
||||
String mmdd = dateTimeMatcher.group(1);
|
||||
String time = dateTimeMatcher.group(2);
|
||||
String year = String.valueOf(LocalDate.now().getYear());
|
||||
String dateTimeStr = year + mmdd + " " + time; // Assuming current year
|
||||
LocalDateTime dateTime = LocalDateTime.parse(dateTimeStr,
|
||||
DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss.SSS"));
|
||||
dto.setDateTime(dateTime);
|
||||
}
|
||||
|
||||
// 당일 데이터가 아닌건은 skip
|
||||
if( !LocalDate.now().equals(dto.getDateTime().toLocalDate()) ){
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract count
|
||||
Matcher countMatcher = COUNT_PATTERN.matcher(mainPart);
|
||||
if (countMatcher.find()) {
|
||||
dto.setCount(Integer.parseInt(countMatcher.group(1)));
|
||||
}
|
||||
|
||||
// Extract instance name (between [] and first space after it)
|
||||
int instanceStart = mainPart.indexOf("]") + 1;
|
||||
int errorStart = mainPart.indexOf(" ", instanceStart);
|
||||
if (instanceStart > 0 && errorStart > instanceStart) {
|
||||
dto.setInstanceName(mainPart.substring(instanceStart, errorStart));
|
||||
}
|
||||
|
||||
// Extract error content
|
||||
if (errorStart > 0) {
|
||||
dto.setErrorContent(mainPart.substring(errorStart).trim());
|
||||
}
|
||||
|
||||
if (isAdapterError) {
|
||||
// Adapter error specific fields
|
||||
dto.setAdapterDesc(parts[1].trim());
|
||||
dto.setAdapterId(parts[2].trim());
|
||||
dto.setErrorType("ADAPTER");
|
||||
} else {
|
||||
// Interface error specific fields
|
||||
dto.setInterfaceId(parts[1].trim());
|
||||
dto.setErrorType("INTERFACE");
|
||||
}
|
||||
|
||||
return dto;
|
||||
|
||||
} catch (Exception e) {
|
||||
// Log error if needed
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SmsNotificationMapper mapper = new SmsNotificationMapper();
|
||||
SmsVO smsVO = new SmsVO();
|
||||
|
||||
// 테스트할 메시지들
|
||||
List<String> messages = Arrays.asList(
|
||||
"0311 09:59:16.537[1]fep11 가용소켓연결없음|BASICTEST101S1",
|
||||
"0311 10:05:39.779[49]fep11 Client커넥션오류|싱글어댑터 Prim|_TST_OU_NET_AsC{ALL}",
|
||||
"0311 10:05:39.780[48]fep11 Client커넥션오류|비동기 표준 아|_TST_OU_NET_AsC{JSON_ALL}",
|
||||
"0311 17:33:04.182[1]fep11 추적관리필드추출에러|BASICTEST101S1"
|
||||
);
|
||||
|
||||
smsVO.setMessage(messages);
|
||||
List<SmsNotificationDto> results = mapper.mapToSmsNotificationDtoList(Arrays.asList(smsVO));
|
||||
|
||||
System.out.println("=== SMS 메시지 파싱 결과 ===");
|
||||
for (SmsNotificationDto dto : results) {
|
||||
System.out.println("\n[메시지 정보]");
|
||||
System.out.println("시간: " + dto.getDateTime());
|
||||
System.out.println("건수: " + dto.getCount());
|
||||
System.out.println("인스턴스: " + dto.getInstanceName());
|
||||
System.out.println("에러내용: " + dto.getErrorContent());
|
||||
System.out.println("에러유형: " + dto.getErrorType());
|
||||
|
||||
if ("ADAPTER".equals(dto.getErrorType())) {
|
||||
System.out.println("어댑터설명: " + dto.getAdapterDesc());
|
||||
System.out.println("어댑터ID: " + dto.getAdapterId());
|
||||
} else {
|
||||
System.out.println("인터페이스ID: " + dto.getInterfaceId());
|
||||
}
|
||||
System.out.println("------------------------");
|
||||
}
|
||||
}
|
||||
}
|
||||
+350
@@ -0,0 +1,350 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.service;
|
||||
|
||||
import com.eactive.eai.data.entity.onl.adapter.AdapterGroup;
|
||||
import com.eactive.eai.data.entity.onl.server.EAIServer;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceType;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
|
||||
import com.eactive.eai.rms.data.entity.onl.adapter.AdapterGroupService;
|
||||
import com.eactive.eai.rms.data.entity.onl.server.EAIServerService;
|
||||
import com.eactive.eai.rms.onl.common.util.DataSetUtil;
|
||||
import com.eactive.eai.rms.onl.common.vo.HostInfomationForm;
|
||||
import com.eactive.eai.rms.onl.dashboard.modern.dto.InstitutionStatusDTO;
|
||||
import com.eactive.eai.rms.onl.dashboard.modern.dto.SocketProcessDTO;
|
||||
import com.eactive.eai.rms.onl.dashboard.modern.dto.SocketStatusDTO;
|
||||
import com.eactive.eai.rms.onl.manage.adapter.adapter.mapper.AdapterGroupUIMapper;
|
||||
import com.eactive.eai.rms.onl.manage.adapter.adapter.ui.AdapterGroupUI;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Service for socket monitoring in modern dashboard
|
||||
*/
|
||||
@Service
|
||||
public class SocketMonitorService {
|
||||
private static final String AGENT_PAGE_STATUS = "adapter_socket2_agent1.jsp";
|
||||
private static final String AGENT_PAGE_PROCESS = "adapter_socket2_agent2.jsp";
|
||||
|
||||
private final EAIServerService eaiServerService;
|
||||
private final AdapterGroupService adapterGroupService;
|
||||
private final AdapterGroupUIMapper adapterGroupUIMapper;
|
||||
|
||||
@Autowired
|
||||
public SocketMonitorService(EAIServerService eaiServerService, AdapterGroupService adapterGroupService, AdapterGroupUIMapper adapterGroupUIMapper) {
|
||||
this.eaiServerService = eaiServerService;
|
||||
this.adapterGroupService = adapterGroupService;
|
||||
this.adapterGroupUIMapper = adapterGroupUIMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get socket status data
|
||||
* @param serverName Optional server name filter
|
||||
* @param adapterGroup Optional adapter group filter
|
||||
* @return SocketStatusDTO with all socket status data
|
||||
*/
|
||||
public SocketStatusDTO getSocketStatus(String serverName, String adapterGroup) {
|
||||
try {
|
||||
// Create a list to store all adapter data from all servers
|
||||
List<HashMap<String, Object>> allAdapterData = new ArrayList<>();
|
||||
|
||||
// Loop through all online data source types
|
||||
for (DataSourceType server : DataSourceTypeManager.getOnlineDataSourceTypes()) {
|
||||
// Set server context
|
||||
DataSourceContextHolder.setDataSourceType(server);
|
||||
|
||||
// Get server list for current data source
|
||||
List<EAIServer> serverList = eaiServerService.selectEaiServerIpList();
|
||||
if (serverList.isEmpty()) {
|
||||
continue; // Skip if no servers found for this data source
|
||||
}
|
||||
|
||||
List<String> urls = new ArrayList<>();
|
||||
for (EAIServer serverMap : serverList) {
|
||||
String serverUrl = "http://" + serverMap.getEaisevrip() + ":"
|
||||
+ serverMap.getSevrlsnportname()
|
||||
+ HostInfomationForm.WEB_AGENT_CONTEXT_PATH
|
||||
+ AGENT_PAGE_STATUS + "?adapterType=SOC"
|
||||
+ "&eaiSvrInstNm="
|
||||
+ serverMap.getEaisevrinstncname();
|
||||
urls.add(serverUrl);
|
||||
}
|
||||
|
||||
// No filtering - get all data
|
||||
DataSetUtil gUtil = new DataSetUtil();
|
||||
List<HashMap<String, Object>> list = gUtil.flushXmlToClient(urls);
|
||||
|
||||
// Add to the combined list
|
||||
allAdapterData.addAll(list);
|
||||
}
|
||||
|
||||
// Reset the data source context
|
||||
DataSourceContextHolder.clearDataSourceType();
|
||||
|
||||
// Convert the result to SocketStatusDTO
|
||||
SocketStatusDTO result = new SocketStatusDTO();
|
||||
result.setTotal(1);
|
||||
result.setPage(1);
|
||||
result.setRecords(allAdapterData.size());
|
||||
|
||||
// Convert HashMap list to SocketStatusItemDTO list
|
||||
List<SocketStatusDTO.SocketStatusItemDTO> rows = new ArrayList<>();
|
||||
for (HashMap<String, Object> item : allAdapterData) {
|
||||
SocketStatusDTO.SocketStatusItemDTO row = new SocketStatusDTO.SocketStatusItemDTO();
|
||||
|
||||
// Map the fields from HashMap to DTO
|
||||
row.setStatus((String) item.get("Status"));
|
||||
row.setEaiSevrInstncName((String) item.get("EaiSevrInstncName"));
|
||||
row.setTestMasterYn((String) item.get("TestMasterYn"));
|
||||
row.setAdptrNickName((String) item.get("AdptrNickName"));
|
||||
row.setStartStop((String) item.get("StartStop"));
|
||||
row.setAdptrBzwkGroupName((String) item.get("AdptrBzwkGroupName"));
|
||||
row.setAdptrBzwkName((String) item.get("AdptrBzwkName"));
|
||||
row.setAdptrType((String) item.get("AdptrType"));
|
||||
row.setStarting((String) item.get("Starting"));
|
||||
row.setAdptrBzwkDesc((String) item.get("AdptrBzwkDesc"));
|
||||
|
||||
rows.add(row);
|
||||
}
|
||||
|
||||
// Sort rows by AdptrBzwkGroupName in ascending order
|
||||
rows.sort(Comparator.comparing(
|
||||
row -> row.getAdptrBzwkGroupName() != null ? row.getAdptrBzwkGroupName() : ""
|
||||
));
|
||||
|
||||
result.setRows(rows);
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to get socket status data", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get socket process data
|
||||
* @param serverName Optional server name filter
|
||||
* @param adapterGroup Optional adapter group filter
|
||||
* @return SocketProcessDTO with all socket process data
|
||||
*/
|
||||
public SocketProcessDTO getSocketProcess(String serverName, String adapterGroup) {
|
||||
try {
|
||||
// Create a list to store all adapter data from all servers
|
||||
List<HashMap<String, Object>> allAdapterData = new ArrayList<>();
|
||||
|
||||
// Loop through all online data source types
|
||||
for (DataSourceType server : DataSourceTypeManager.getOnlineDataSourceTypes()) {
|
||||
// Set server context
|
||||
DataSourceContextHolder.setDataSourceType(server);
|
||||
|
||||
// Get server list for current data source
|
||||
List<EAIServer> serverList = eaiServerService.selectEaiServerIpList();
|
||||
if (serverList.isEmpty()) {
|
||||
continue; // Skip if no servers found for this data source
|
||||
}
|
||||
|
||||
List<String> urls = new ArrayList<>();
|
||||
for (EAIServer serverMap : serverList) {
|
||||
String serverUrl = "http://" + serverMap.getEaisevrip() + ":"
|
||||
+ serverMap.getSevrlsnportname()
|
||||
+ HostInfomationForm.WEB_AGENT_CONTEXT_PATH
|
||||
+ AGENT_PAGE_PROCESS + "?cmd=get+connection+summary"
|
||||
+ "&eaiSvrInstNm=" + serverMap.getEaisevrinstncname();
|
||||
|
||||
urls.add(serverUrl);
|
||||
}
|
||||
|
||||
// No filtering - get all data
|
||||
DataSetUtil gUtil = new DataSetUtil();
|
||||
List<HashMap<String, Object>> list = gUtil.flushXmlToClient(urls);
|
||||
|
||||
// Add to the combined list
|
||||
allAdapterData.addAll(list);
|
||||
}
|
||||
|
||||
// Reset the data source context
|
||||
DataSourceContextHolder.clearDataSourceType();
|
||||
|
||||
// Convert the result to SocketProcessDTO
|
||||
SocketProcessDTO result = new SocketProcessDTO();
|
||||
|
||||
// Create JsonPageVoDTO
|
||||
SocketProcessDTO.JsonPageVoDTO jsonPageVo = new SocketProcessDTO.JsonPageVoDTO();
|
||||
jsonPageVo.setPage(1);
|
||||
jsonPageVo.setTotalCount(allAdapterData.size());
|
||||
jsonPageVo.setRows(10000);
|
||||
jsonPageVo.setTotal(1);
|
||||
jsonPageVo.setStartNum(1);
|
||||
jsonPageVo.setEndNum(10000);
|
||||
|
||||
result.setJsonPageVo(jsonPageVo);
|
||||
result.setTotal(1);
|
||||
result.setPage(1);
|
||||
result.setRecords(allAdapterData.size());
|
||||
|
||||
// Convert HashMap list to SocketProcessItemDTO list
|
||||
List<SocketProcessDTO.SocketProcessItemDTO> rows = new ArrayList<>();
|
||||
for (HashMap<String, Object> item : allAdapterData) {
|
||||
SocketProcessDTO.SocketProcessItemDTO row = new SocketProcessDTO.SocketProcessItemDTO();
|
||||
|
||||
// Map the fields from HashMap to DTO
|
||||
row.setMaxConnection((String) item.get("MaxConnection"));
|
||||
row.setCurrentConnection((String) item.get("CurrentConnection"));
|
||||
row.setStatus((String) item.get("Status"));
|
||||
row.setEaiSevrInstncName((String) item.get("EaiSevrInstncName"));
|
||||
row.setAdptrBzwkGroupName((String) item.get("AdptrBzwkGroupName"));
|
||||
row.setRecvCount((String) item.get("RecvCount"));
|
||||
row.setAdptrBzwkName((String) item.get("AdptrBzwkName"));
|
||||
row.setAdptrBzwkDesc((String) item.get("AdptrBzwkDesc"));
|
||||
row.setSendCount((String) item.get("SendCount"));
|
||||
row.setErrorCount((String) item.get("ErrorCount"));
|
||||
|
||||
rows.add(row);
|
||||
}
|
||||
|
||||
// Sort rows by AdptrBzwkGroupName in ascending order
|
||||
rows.sort(Comparator.comparing(
|
||||
row -> row.getAdptrBzwkGroupName() != null ? row.getAdptrBzwkGroupName() : ""
|
||||
));
|
||||
|
||||
result.setRows(rows);
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to get socket process data", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get institution monitoring status data
|
||||
* @param statusData Socket status data with adapter information
|
||||
* @return List of institution status information
|
||||
*/
|
||||
public List<InstitutionStatusDTO> getInstitutionStatus(SocketStatusDTO statusData) {
|
||||
try {
|
||||
// Set context to access AdapterGroup data
|
||||
DataSourceContextHolder.setDataSourceType(DataSourceTypeManager.getOnlineDataSourceTypes().get(0));
|
||||
|
||||
// Get institutions with non-empty adptrinsticode
|
||||
List<AdapterGroup> adapterGroups = adapterGroupService.findByAdptrinsticodeNotEmpty();
|
||||
List<AdapterGroupUI> adapterGroupUIs = adapterGroups.stream()
|
||||
.map(adapterGroupUIMapper::toVoWithoutChildrenMapping)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Group adapter groups by institution code
|
||||
Map<String, List<AdapterGroupUI>> institutionGroups = adapterGroupUIs.stream()
|
||||
.filter(ag -> StringUtils.isNotBlank(ag.getAdptrinsticode()))
|
||||
.collect(Collectors.groupingBy(AdapterGroupUI::getAdptrinsticode));
|
||||
|
||||
List<InstitutionStatusDTO> result = new ArrayList<>();
|
||||
|
||||
// Process each institution
|
||||
for (Map.Entry<String, List<AdapterGroupUI>> entry : institutionGroups.entrySet()) {
|
||||
String institutionCode = entry.getKey();
|
||||
List<AdapterGroupUI> institutionAdapters = entry.getValue();
|
||||
|
||||
if (institutionAdapters.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use the first adapter's information for institution details
|
||||
AdapterGroupUI firstAdapter = institutionAdapters.get(0);
|
||||
|
||||
// Create a new institution status DTO
|
||||
InstitutionStatusDTO institutionStatus = new InstitutionStatusDTO();
|
||||
institutionStatus.setInstitutionCode(institutionCode);
|
||||
institutionStatus.setInstitutionName(firstAdapter.getAdptrinstiName());
|
||||
institutionStatus.setContactPerson(firstAdapter.getOsidinstibzwkrsempname());
|
||||
institutionStatus.setContactNumber(firstAdapter.getOsidinstitelno());
|
||||
|
||||
// Get adapter group names for this institution
|
||||
List<String> adapterGroupNames = institutionAdapters.stream()
|
||||
.map(AdapterGroupUI::getAdptrbzwkgroupname)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Count normal and error adapters
|
||||
int normalCount = 0;
|
||||
int errorCount = 0;
|
||||
|
||||
// Check status in statusData
|
||||
if (statusData != null && statusData.getRows() != null) {
|
||||
for (SocketStatusDTO.SocketStatusItemDTO statusItem : statusData.getRows()) {
|
||||
if (adapterGroupNames.contains(statusItem.getAdptrBzwkGroupName())) {
|
||||
if ("NORMAL".equals(statusItem.getStatus()) ||
|
||||
"true".equals(statusItem.getStatus()) ||
|
||||
"OK".equals(statusItem.getStatus())) {
|
||||
normalCount++;
|
||||
} else {
|
||||
errorCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
institutionStatus.setNormalAdapterCount(normalCount);
|
||||
institutionStatus.setErrorAdapterCount(errorCount);
|
||||
|
||||
// Determine line status
|
||||
if (normalCount == 0 && errorCount == 0) {
|
||||
institutionStatus.setLineStatus("UNDEFINED");
|
||||
} else if (errorCount == 0 && normalCount > 0) {
|
||||
institutionStatus.setLineStatus("NORMAL");
|
||||
} else if (errorCount > 0 && normalCount > 0) {
|
||||
institutionStatus.setLineStatus("WARN");
|
||||
} else if (errorCount > 0 && normalCount == 0) {
|
||||
institutionStatus.setLineStatus("ERROR");
|
||||
}
|
||||
|
||||
result.add(institutionStatus);
|
||||
}
|
||||
|
||||
// Reset data source context
|
||||
DataSourceContextHolder.clearDataSourceType();
|
||||
|
||||
// Sort the result by status priority: ERROR > WARN > NORMAL > UNDEFINED
|
||||
result.sort((a, b) -> {
|
||||
// Define status priority (higher number = higher priority)
|
||||
Map<String, Integer> statusPriority = new HashMap<>();
|
||||
statusPriority.put("ERROR", 3);
|
||||
statusPriority.put("WARN", 2);
|
||||
statusPriority.put("NORMAL", 1);
|
||||
statusPriority.put("UNDEFINED", 0);
|
||||
|
||||
// Get priority values for comparison
|
||||
int priorityA = statusPriority.getOrDefault(a.getLineStatus(), 0);
|
||||
int priorityB = statusPriority.getOrDefault(b.getLineStatus(), 0);
|
||||
|
||||
// Sort by priority (descending)
|
||||
return Integer.compare(priorityB, priorityA);
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
DataSourceContextHolder.clearDataSourceType();
|
||||
throw new RuntimeException("Failed to get institution status data", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get combined socket data (both status and process)
|
||||
* @param serverName Optional server name filter
|
||||
* @param adapterGroup Optional adapter group filter
|
||||
* @return Map containing both socket status and process data
|
||||
*/
|
||||
public Map<String, Object> getCombinedSocketData(String serverName, String adapterGroup) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
SocketStatusDTO statusData = getSocketStatus(serverName, adapterGroup);
|
||||
SocketProcessDTO processData = getSocketProcess(serverName, adapterGroup);
|
||||
List<InstitutionStatusDTO> institutionStatus = getInstitutionStatus(statusData);
|
||||
|
||||
result.put("socketStatus", statusData);
|
||||
result.put("socketProcess", processData);
|
||||
result.put("institutionStatus", institutionStatus);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+571
@@ -0,0 +1,571 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.service;
|
||||
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceType;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
|
||||
import com.eactive.eai.rms.onl.common.service.EaiServerInfoService;
|
||||
import com.eactive.eai.rms.onl.dashboard.service.Dashboard2RepositoryService;
|
||||
import com.eactive.eai.rms.onl.dashboard.service.DashboardService;
|
||||
import com.eactive.eai.rms.onl.tracking.vo.MemStaticsVo;
|
||||
import com.eactive.eai.rms.onl.vo.HostStatusVo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 처리량 계산 서비스
|
||||
* 각 인스턴스별 초당 처리량을 계산하여 저장합니다.
|
||||
* 시계열 데이터를 유지하여 트렌드 분석에 사용합니다.
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ThroughputCalculationService {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("eaiServerInfoService")
|
||||
private EaiServerInfoService eaiServerInfoService;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("dashboard2RepositoryService")
|
||||
private Dashboard2RepositoryService dashboard2Repository;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("dashboardService")
|
||||
private DashboardService dashboardService;
|
||||
|
||||
// 인스턴스별 처리량 정보를 저장하는 작은 캐시
|
||||
private final ConcurrentHashMap<String, ThroughputCacheInfo> throughputCache = new ConcurrentHashMap<>();
|
||||
|
||||
// 서비스 타입별 시계열 데이터를 저장하는 맵
|
||||
// 키: 서비스타입(EAI, FEP 등), 값: TimeSeriesData 객체
|
||||
private final ConcurrentHashMap<String, TimeSeriesData> timeSeriesMap = new ConcurrentHashMap<>();
|
||||
|
||||
// 서버별 리소스 사용량 시계열 데이터를 저장하는 맵
|
||||
// 키: 호스트명, 값: ServerResourceData 객체
|
||||
private final ConcurrentHashMap<String, ServerResourceData> serverResourceMap = new ConcurrentHashMap<>();
|
||||
|
||||
// 서비스 타입별 마지막 누적 값을 저장하는 맵
|
||||
private final ConcurrentHashMap<String, Map<String, Integer>> lastAccumulatedValuesMap = new ConcurrentHashMap<>();
|
||||
|
||||
// 스케줄러
|
||||
private ScheduledExecutorService scheduler;
|
||||
|
||||
// 시계열 데이터 저장 기간 (분)
|
||||
private static final int TIME_SERIES_RETENTION_MINUTES = 60;
|
||||
|
||||
// 서버 정보 시계열 데이터 저장 기간 (분)
|
||||
private static final int SERVER_STATUS_TIME_SERIES_RETENTION_MINUTES = 2;
|
||||
|
||||
// 시계열 데이터 샘플링 간격 (초)
|
||||
private static final int TIME_SERIES_SAMPLING_SECONDS = 1;
|
||||
|
||||
// 처리량 정보를 담는 내부 클래스 (캐시용)
|
||||
private static class ThroughputCacheInfo {
|
||||
private long timestamp; // 마지막 조회 시간
|
||||
private int totalCount; // 마지막 누적 건수
|
||||
|
||||
public ThroughputCacheInfo(long timestamp, int totalCount) {
|
||||
this.timestamp = timestamp;
|
||||
this.totalCount = totalCount;
|
||||
}
|
||||
}
|
||||
|
||||
// 시계열 데이터를 담는 내부 클래스
|
||||
public static class TimeSeriesData {
|
||||
private List<Long> timestamps = new ArrayList<>(); // 타임스탬프 목록
|
||||
private List<Integer> success = new ArrayList<>(); // 성공 건수
|
||||
private List<Integer> fail = new ArrayList<>(); // 실패 건수
|
||||
private List<Integer> timeout = new ArrayList<>(); // 타임아웃 건수
|
||||
private List<Integer> syserror = new ArrayList<>(); // 시스템 에러 건수
|
||||
|
||||
// 시계열 데이터 추가
|
||||
public void addDataPoint(long timestamp, int successCount, int failCount, int timeoutCount, int syserrorCount) {
|
||||
timestamps.add(timestamp);
|
||||
success.add(successCount);
|
||||
fail.add(failCount);
|
||||
timeout.add(timeoutCount);
|
||||
syserror.add(syserrorCount);
|
||||
|
||||
// 오래된 데이터 정리 (최대 저장 기간을 초과하는 데이터 제거)
|
||||
long cutoffTime = System.currentTimeMillis() - (TIME_SERIES_RETENTION_MINUTES * 60 * 1000);
|
||||
while (!timestamps.isEmpty() && timestamps.get(0) < cutoffTime) {
|
||||
timestamps.remove(0);
|
||||
success.remove(0);
|
||||
fail.remove(0);
|
||||
timeout.remove(0);
|
||||
syserror.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Getter 메서드
|
||||
public List<Long> getTimestamps() {
|
||||
return timestamps;
|
||||
}
|
||||
|
||||
public List<Integer> getSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public List<Integer> getFail() {
|
||||
return fail;
|
||||
}
|
||||
|
||||
public List<Integer> getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
public List<Integer> getSyserror() {
|
||||
return syserror;
|
||||
}
|
||||
}
|
||||
|
||||
// 서버 리소스 사용량 시계열 데이터를 담는 내부 클래스
|
||||
public static class ServerResourceData {
|
||||
private List<Long> timestamps = new ArrayList<>(); // 타임스탬프 목록
|
||||
private List<Integer> cpu = new ArrayList<>(); // CPU 사용률
|
||||
private List<Integer> memory = new ArrayList<>(); // 메모리 사용률
|
||||
private List<Integer> disk = new ArrayList<>(); // 디스크 사용률
|
||||
|
||||
// 시계열 데이터 추가
|
||||
public void addDataPoint(long timestamp, int cpuUsage, int memoryUsage, int diskUsage) {
|
||||
timestamps.add(timestamp);
|
||||
cpu.add(cpuUsage);
|
||||
memory.add(memoryUsage);
|
||||
disk.add(diskUsage);
|
||||
}
|
||||
|
||||
public void removeExpiredDataPoint(){
|
||||
// 오래된 데이터 정리 (최대 저장 기간을 초과하는 데이터 제거)
|
||||
long cutoffTime = System.currentTimeMillis() - (SERVER_STATUS_TIME_SERIES_RETENTION_MINUTES * 60 * 1000);
|
||||
while (!timestamps.isEmpty() && timestamps.get(0) < cutoffTime) {
|
||||
timestamps.remove(0);
|
||||
cpu.remove(0);
|
||||
memory.remove(0);
|
||||
disk.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Getter 메서드
|
||||
public List<Long> getTimestamps() {
|
||||
return timestamps;
|
||||
}
|
||||
|
||||
public List<Integer> getCpu() {
|
||||
return cpu;
|
||||
}
|
||||
|
||||
public List<Integer> getMemory() {
|
||||
return memory;
|
||||
}
|
||||
|
||||
public List<Integer> getDisk() {
|
||||
return disk;
|
||||
}
|
||||
|
||||
// 최근 N개의 데이터 포인트 반환
|
||||
public ServerResourceData getRecentDataPoints(int count) {
|
||||
ServerResourceData result = new ServerResourceData();
|
||||
int size = timestamps.size();
|
||||
int startIndex = Math.max(0, size - count);
|
||||
|
||||
for (int i = startIndex; i < size; i++) {
|
||||
result.timestamps.add(timestamps.get(i));
|
||||
result.cpu.add(cpu.get(i));
|
||||
result.memory.add(memory.get(i));
|
||||
result.disk.add(disk.get(i));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 초기화 메서드
|
||||
* 서비스 로드 시 스케줄러를 시작합니다.
|
||||
*/
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
log.info("처리량 계산 서비스 초기화");
|
||||
|
||||
// 스케줄러 생성
|
||||
scheduler = Executors.newScheduledThreadPool(2);
|
||||
|
||||
// 시계열 데이터 수집 작업 추가 (지정된 간격마다 실행)
|
||||
scheduler.scheduleWithFixedDelay(this::collectTimeSeriesData, 5, TIME_SERIES_SAMPLING_SECONDS, TimeUnit.SECONDS);
|
||||
|
||||
// 서버 리소스 사용량 데이터 수집 작업 추가 (10초마다 실행)
|
||||
scheduler.scheduleWithFixedDelay(this::collectServerResourceData, 3, 10, TimeUnit.SECONDS);
|
||||
|
||||
log.info("처리량 계산 스케줄러 시작됨");
|
||||
|
||||
// 서비스 타입 초기화
|
||||
for (DataSourceType server : DataSourceTypeManager.getOnlineDataSourceTypes()) {
|
||||
timeSeriesMap.putIfAbsent(server.getName(), new TimeSeriesData());
|
||||
}
|
||||
|
||||
// BAT(배치) 서버 추가
|
||||
DataSourceType batServer = DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.BAT);
|
||||
if (batServer != null) {
|
||||
timeSeriesMap.putIfAbsent(batServer.getName(), new TimeSeriesData());
|
||||
}
|
||||
|
||||
// BAP(일괄전송) 서버 추가
|
||||
DataSourceType bapServer = DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.BAP);
|
||||
if (bapServer != null) {
|
||||
timeSeriesMap.putIfAbsent(bapServer.getName(), new TimeSeriesData());
|
||||
}
|
||||
|
||||
// 전체 데이터용 TOTAL 엔트리 추가
|
||||
timeSeriesMap.putIfAbsent("TOTAL", new TimeSeriesData());
|
||||
}
|
||||
|
||||
/**
|
||||
* 종료 메서드
|
||||
* 서비스 종료 시 스케줄러를 정리합니다.
|
||||
*/
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
if (scheduler != null && !scheduler.isShutdown()) {
|
||||
scheduler.shutdown();
|
||||
log.info("처리량 계산 스케줄러 종료됨");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 서버 리소스 사용량 데이터 수집
|
||||
* 10초마다 실행되어 각 서버의 CPU, 메모리, 디스크 사용량을 수집합니다.
|
||||
*/
|
||||
public void collectServerResourceData() {
|
||||
try {
|
||||
// 수집 시간
|
||||
long currentTime = System.currentTimeMillis();
|
||||
|
||||
// 모든 온라인 데이터소스 타입에 대해 처리
|
||||
List<DataSourceType> servers = DataSourceTypeManager.getOnlineDataSourceTypes();
|
||||
|
||||
// GW 추가
|
||||
if( DataSourceTypeManager.getDataSourceType("FGW") != null ){
|
||||
servers.add(DataSourceTypeManager.getDataSourceType("FGW"));
|
||||
}
|
||||
|
||||
if( DataSourceTypeManager.getDataSourceType("CGW") != null ){
|
||||
servers.add(DataSourceTypeManager.getDataSourceType("CGW"));
|
||||
}
|
||||
|
||||
for (DataSourceType server : servers) {
|
||||
// 서버 컨텍스트 설정
|
||||
DataSourceContextHolder.setDataSourceType(server);
|
||||
|
||||
// 서버에 대한 인스턴스 목록 조회
|
||||
List<Map<String, String>> ipList = eaiServerInfoService.getEaiServerIpListDB();
|
||||
|
||||
// 호스트명으로 인스턴스 그룹화
|
||||
Map<String, List<Map<String, String>>> instancesByHost = new HashMap<>();
|
||||
for (Map<String, String> instance : ipList) {
|
||||
String hostname = instance.get("HOSTNAME");
|
||||
instancesByHost.computeIfAbsent(hostname, k -> new ArrayList<>()).add(instance);
|
||||
}
|
||||
|
||||
// 각 호스트별로 리소스 사용량 수집
|
||||
for (Map.Entry<String, List<Map<String, String>>> entry : instancesByHost.entrySet()) {
|
||||
String hostname = entry.getKey();
|
||||
List<Map<String, String>> hostInstances = entry.getValue();
|
||||
|
||||
// 호스트 상태 조회
|
||||
HostStatusVo hostVo = dashboardService.findHostStatus(hostInstances);
|
||||
if (hostVo != null) {
|
||||
|
||||
// 서버 리소스 데이터 객체 가져오기 또는 생성
|
||||
ServerResourceData resourceData = serverResourceMap.computeIfAbsent(hostname, k -> new ServerResourceData());
|
||||
|
||||
// 데이터 포인트 추가
|
||||
resourceData.addDataPoint(currentTime, hostVo.getCpu(), hostVo.getMemory(), hostVo.getDisk());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 기간 만료 데이터 삭제
|
||||
serverResourceMap.values().forEach(ServerResourceData::removeExpiredDataPoint);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("서버 리소스 사용량 데이터 수집 중 오류 발생", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 시계열 데이터 수집
|
||||
* 정해진 간격마다 실행되어 각 서비스 타입별 처리 건수를 수집합니다.
|
||||
*/
|
||||
public void collectTimeSeriesData() {
|
||||
try {
|
||||
// 수집 시간
|
||||
long currentTime = System.currentTimeMillis();
|
||||
|
||||
// 서비스 타입별 현재 누적 데이터
|
||||
Map<String, Integer> currentSuccessByService = new HashMap<>();
|
||||
Map<String, Integer> currentFailByService = new HashMap<>();
|
||||
Map<String, Integer> currentTimeoutByService = new HashMap<>();
|
||||
Map<String, Integer> currentSyserrorByService = new HashMap<>();
|
||||
|
||||
// 전체 합계
|
||||
int totalSuccess = 0;
|
||||
int totalFail = 0;
|
||||
int totalTimeout = 0;
|
||||
int totalSyserror = 0;
|
||||
|
||||
// 모든 온라인 데이터소스 타입에 대해 처리
|
||||
List<DataSourceType> servers = DataSourceTypeManager.getOnlineDataSourceTypes();
|
||||
|
||||
for (DataSourceType server : servers) {
|
||||
// 서버 컨텍스트 설정
|
||||
DataSourceContextHolder.setDataSourceType(server);
|
||||
|
||||
// 서버의 트랜잭션 정보 조회
|
||||
MemStaticsVo memStatics = dashboard2Repository.getMemTradeVoByService(server.getName());
|
||||
|
||||
if (memStatics != null) {
|
||||
// 현재 누적 값 계산
|
||||
int currentSuccess = memStatics.getProcTotal() - memStatics.getTimeoutTotal() - memStatics.getErrorTotal() - memStatics.getSysErrorTotal();
|
||||
int currentFail = memStatics.getErrorTotal();
|
||||
int currentTimeout = memStatics.getTimeoutTotal();
|
||||
int currentSyserror = memStatics.getSysErrorTotal();
|
||||
|
||||
// 서비스별 데이터 저장
|
||||
currentSuccessByService.put(server.getName(), currentSuccess);
|
||||
currentFailByService.put(server.getName(), currentFail);
|
||||
currentTimeoutByService.put(server.getName(), currentTimeout);
|
||||
currentSyserrorByService.put(server.getName(), currentSyserror);
|
||||
|
||||
// 전체 합계에 추가
|
||||
totalSuccess += currentSuccess;
|
||||
totalFail += currentFail;
|
||||
totalTimeout += currentTimeout;
|
||||
totalSyserror += currentSyserror;
|
||||
}
|
||||
}
|
||||
|
||||
// 각 서비스 타입별 시계열 데이터 업데이트
|
||||
for (String serviceType : timeSeriesMap.keySet()) {
|
||||
if (serviceType.equals("TOTAL")) {
|
||||
// TOTAL 데이터는 아래에서 별도로 처리
|
||||
continue;
|
||||
}
|
||||
|
||||
// 현재 누적 값
|
||||
int currentSuccess = currentSuccessByService.getOrDefault(serviceType, 0);
|
||||
int currentFail = currentFailByService.getOrDefault(serviceType, 0);
|
||||
int currentTimeout = currentTimeoutByService.getOrDefault(serviceType, 0);
|
||||
int currentSyserror = currentSyserrorByService.getOrDefault(serviceType, 0);
|
||||
|
||||
// 마지막 누적 값 가져오기
|
||||
Map<String, Integer> lastValues = lastAccumulatedValuesMap.getOrDefault(serviceType, new HashMap<>());
|
||||
|
||||
// 차이 계산
|
||||
int deltaSuccess = currentSuccess - lastValues.getOrDefault("success", 0);
|
||||
int deltaFail = currentFail - lastValues.getOrDefault("fail", 0);
|
||||
int deltaTimeout = currentTimeout - lastValues.getOrDefault("timeout", 0);
|
||||
int deltaSyserror = currentSyserror - lastValues.getOrDefault("syserror", 0);
|
||||
|
||||
// 인스턴스 재기동 감지 (누적치가 감소한 경우)
|
||||
boolean isReset = false;
|
||||
if (deltaSuccess < 0 || deltaFail < 0 || deltaTimeout < 0 || deltaSyserror < 0) {
|
||||
isReset = true;
|
||||
}
|
||||
|
||||
// 시계열 데이터에 추가할 값 결정
|
||||
// 재기동된 경우 건수는 0으로 처리
|
||||
int addSuccess = isReset ? 0 : Math.max(0, deltaSuccess);
|
||||
int addFail = isReset ? 0 : Math.max(0, deltaFail);
|
||||
int addTimeout = isReset ? 0 : Math.max(0, deltaTimeout);
|
||||
int addSyserror = isReset ? 0 : Math.max(0, deltaSyserror);
|
||||
|
||||
// 시계열 데이터 추가
|
||||
TimeSeriesData timeSeriesData = timeSeriesMap.get(serviceType);
|
||||
if (timeSeriesData != null) {
|
||||
timeSeriesData.addDataPoint(currentTime, addSuccess, addFail, addTimeout, addSyserror);
|
||||
}
|
||||
|
||||
// 마지막 누적 값 업데이트 (현재 값으로 항상 갱신)
|
||||
Map<String, Integer> newLastValues = new HashMap<>();
|
||||
newLastValues.put("success", currentSuccess);
|
||||
newLastValues.put("fail", currentFail);
|
||||
newLastValues.put("timeout", currentTimeout);
|
||||
newLastValues.put("syserror", currentSyserror);
|
||||
lastAccumulatedValuesMap.put(serviceType, newLastValues);
|
||||
}
|
||||
|
||||
// TOTAL 시계열 데이터 업데이트
|
||||
TimeSeriesData totalTimeSeriesData = timeSeriesMap.get("TOTAL");
|
||||
if (totalTimeSeriesData != null) {
|
||||
// 마지막 누적 값 가져오기
|
||||
Map<String, Integer> lastTotalValues = lastAccumulatedValuesMap.getOrDefault("TOTAL", new HashMap<>());
|
||||
|
||||
// 차이 계산
|
||||
int deltaSuccess = totalSuccess - lastTotalValues.getOrDefault("success", 0);
|
||||
int deltaFail = totalFail - lastTotalValues.getOrDefault("fail", 0);
|
||||
int deltaTimeout = totalTimeout - lastTotalValues.getOrDefault("timeout", 0);
|
||||
int deltaSyserror = totalSyserror - lastTotalValues.getOrDefault("syserror", 0);
|
||||
|
||||
// 인스턴스 재기동 감지
|
||||
boolean isReset = false;
|
||||
if (deltaSuccess < 0 || deltaFail < 0 || deltaTimeout < 0 || deltaSyserror < 0) {
|
||||
isReset = true;
|
||||
}
|
||||
|
||||
// 시계열 데이터에 추가할 값 결정
|
||||
int addSuccess = isReset ? 0 : Math.max(0, deltaSuccess);
|
||||
int addFail = isReset ? 0 : Math.max(0, deltaFail);
|
||||
int addTimeout = isReset ? 0 : Math.max(0, deltaTimeout);
|
||||
int addSyserror = isReset ? 0 : Math.max(0, deltaSyserror);
|
||||
|
||||
totalTimeSeriesData.addDataPoint(currentTime, addSuccess, addFail, addTimeout, addSyserror);
|
||||
|
||||
// 마지막 누적 값 업데이트
|
||||
Map<String, Integer> newLastTotalValues = new HashMap<>();
|
||||
newLastTotalValues.put("success", totalSuccess);
|
||||
newLastTotalValues.put("fail", totalFail);
|
||||
newLastTotalValues.put("timeout", totalTimeout);
|
||||
newLastTotalValues.put("syserror", totalSyserror);
|
||||
lastAccumulatedValuesMap.put("TOTAL", newLastTotalValues);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("시계열 데이터 수집 중 오류 발생", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 인스턴스의 처리량 조회
|
||||
* 실시간으로 계산하여 반환합니다.
|
||||
* @param hostName 호스트명
|
||||
* @param instanceName 인스턴스명
|
||||
* @return 초당 처리량 문자열 (예: "10.5/s")
|
||||
*/
|
||||
public String getThroughput(String hostName, String instanceName) {
|
||||
try {
|
||||
// 현재 처리 건수 조회
|
||||
MemStaticsVo memStatics = dashboard2Repository.getMemTradeVoByHost(hostName, instanceName);
|
||||
|
||||
if (memStatics != null) {
|
||||
// 현재 시간과 누적 건수
|
||||
long currentTime = System.currentTimeMillis();
|
||||
int currentTotal = memStatics.getProcTotal();
|
||||
String instanceKey = hostName + "_" + instanceName;
|
||||
|
||||
// 이전 조회 데이터 가져오기
|
||||
ThroughputCacheInfo prevInfo = throughputCache.get(instanceKey);
|
||||
|
||||
double throughput = 0.0;
|
||||
boolean isReset = false;
|
||||
|
||||
if (prevInfo != null) {
|
||||
// 경과 시간 계산 (초 단위)
|
||||
double elapsedSeconds = (currentTime - prevInfo.timestamp) / 1000.0;
|
||||
|
||||
// 최소 0.1초 이상 경과했을 때만 계산
|
||||
if (elapsedSeconds >= 0.1) {
|
||||
int processedCount = currentTotal - prevInfo.totalCount;
|
||||
|
||||
// 처리 건수가 음수이면 인스턴스 재기동으로 간주
|
||||
if (processedCount < 0) {
|
||||
processedCount = 0;
|
||||
isReset = true;
|
||||
}
|
||||
|
||||
throughput = processedCount / elapsedSeconds;
|
||||
}
|
||||
}
|
||||
|
||||
// 캐시 업데이트
|
||||
throughputCache.put(instanceKey, new ThroughputCacheInfo(currentTime, currentTotal));
|
||||
|
||||
// 처리량 문자열 반환
|
||||
return String.format("%.1f/s", throughput);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("처리량 계산 중 오류 발생", e);
|
||||
}
|
||||
|
||||
return "0/s";
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 서버의 리소스 사용량 시계열 데이터 조회
|
||||
* @param hostname 호스트명
|
||||
* @param count 조회할 데이터 포인트 수 (최대 7개)
|
||||
* @return 서버 리소스 사용량 시계열 데이터
|
||||
*/
|
||||
public ServerResourceData getServerResourceData(String hostname, int count) {
|
||||
// 데이터 포인트 수 제한 (최대 7개)
|
||||
count = Math.min(count, 7);
|
||||
|
||||
ServerResourceData resourceData = serverResourceMap.get(hostname);
|
||||
if (resourceData != null) {
|
||||
return resourceData.getRecentDataPoints(count);
|
||||
} else {
|
||||
return new ServerResourceData();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 서비스 타입의 시계열 데이터 조회
|
||||
* @param serviceType 서비스 타입 (EAI, FEP 등)
|
||||
* @param minutes 조회할 분 단위 기간 (최대 60분)
|
||||
* @return 시계열 데이터
|
||||
*/
|
||||
public TimeSeriesData getTimeSeriesData(String serviceType, int minutes) {
|
||||
// 기간 제한 (1~60분)
|
||||
minutes = Math.min(Math.max(minutes, 1), TIME_SERIES_RETENTION_MINUTES);
|
||||
|
||||
// 기본적으로 요청된 서비스 타입의 시계열 데이터 사용
|
||||
TimeSeriesData originalData = timeSeriesMap.get(serviceType);
|
||||
|
||||
// 서비스 타입이 없거나 "ALL"인 경우 TOTAL 데이터 사용
|
||||
if (originalData == null || "ALL".equalsIgnoreCase(serviceType) || "all".equals(serviceType)) {
|
||||
originalData = timeSeriesMap.get("TOTAL");
|
||||
}
|
||||
|
||||
// 데이터가 없으면 빈 데이터 반환
|
||||
if (originalData == null) {
|
||||
return new TimeSeriesData();
|
||||
}
|
||||
|
||||
// 기간에 맞게 데이터 필터링
|
||||
TimeSeriesData filteredData = new TimeSeriesData();
|
||||
long cutoffTime = System.currentTimeMillis() - (minutes * 60 * 1000);
|
||||
|
||||
List<Long> originalTimestamps = originalData.getTimestamps();
|
||||
List<Integer> originalSuccess = originalData.getSuccess();
|
||||
List<Integer> originalFail = originalData.getFail();
|
||||
List<Integer> originalTimeout = originalData.getTimeout();
|
||||
List<Integer> originalSyserror = originalData.getSyserror();
|
||||
|
||||
// 최근 데이터부터 시작하여 지정된 기간 내의 데이터만 필터링
|
||||
for (int i = originalTimestamps.size() - 1; i >= 0; i--) {
|
||||
if (originalTimestamps.get(i) >= cutoffTime) {
|
||||
filteredData.timestamps.add(0, originalTimestamps.get(i));
|
||||
filteredData.success.add(0, originalSuccess.get(i));
|
||||
filteredData.fail.add(0, originalFail.get(i));
|
||||
filteredData.timeout.add(0, originalTimeout.get(i));
|
||||
filteredData.syserror.add(0, originalSyserror.get(i));
|
||||
} else {
|
||||
// 시간 범위를 벗어나면 종료
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return filteredData;
|
||||
}
|
||||
}
|
||||
+334
@@ -0,0 +1,334 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.modern.service;
|
||||
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceType;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
|
||||
import com.eactive.eai.rms.onl.common.service.EaiServerInfoService;
|
||||
import com.eactive.eai.rms.onl.dashboard.service.Dashboard2RepositoryService;
|
||||
import com.eactive.eai.rms.onl.server.repository.TransactionRepositoryService;
|
||||
import com.eactive.eai.rms.onl.vo.TransactionTrackingVo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 거래현황 집계 서비스
|
||||
* 각 인터페이스별 10초 단위 처리량을 계산하여 저장합니다.
|
||||
* 시계열 데이터를 유지하여 트렌드 분석에 사용합니다.
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class TransactionSummaryService {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("dashboard2RepositoryService")
|
||||
private Dashboard2RepositoryService dashboard2Repository;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("transactionRepositoryService")
|
||||
private TransactionRepositoryService transactionRepositoryService;
|
||||
|
||||
@Autowired
|
||||
private EaiServerInfoService eaiServerInfoService;
|
||||
|
||||
// 인터페이스별 처리량 정보를 저장하는 맵
|
||||
// 키: 서비스명, 값: 시계열 데이터
|
||||
private final ConcurrentHashMap<String, TimeSeriesData> timeSeriesMap = new ConcurrentHashMap<>();
|
||||
|
||||
// 서비스별 마지막 누적 값을 저장하는 맵
|
||||
private final ConcurrentHashMap<String, Map<String, Integer>> lastAccumulatedValuesMap = new ConcurrentHashMap<>();
|
||||
|
||||
// 스케줄러
|
||||
private ScheduledExecutorService scheduler;
|
||||
|
||||
// 시계열 데이터 저장 기간 (초)
|
||||
private static final int TIME_SERIES_RETENTION_SECONDS = 120;
|
||||
|
||||
// 시계열 데이터 샘플링 간격 (초)
|
||||
private static final int TIME_SERIES_SAMPLING_SECONDS = 10;
|
||||
|
||||
// 시계열 데이터를 담는 내부 클래스
|
||||
public static class TimeSeriesData {
|
||||
private List<Long> timestamps = new ArrayList<>(); // 타임스탬프 목록
|
||||
private List<Integer> success = new ArrayList<>(); // 성공 건수
|
||||
private List<Integer> error = new ArrayList<>(); // 오류 건수
|
||||
private List<Double> avgTime = new ArrayList<>(); // 평균 소요시간
|
||||
|
||||
// 시계열 데이터 추가
|
||||
public void addDataPoint(long timestamp, int successCount, int errorCount, double avgProcessTime) {
|
||||
timestamps.add(timestamp);
|
||||
success.add(successCount);
|
||||
error.add(errorCount);
|
||||
avgTime.add(avgProcessTime);
|
||||
|
||||
// 오래된 데이터 정리 (최대 저장 기간을 초과하는 데이터 제거)
|
||||
long cutoffTime = System.currentTimeMillis() - (TIME_SERIES_RETENTION_SECONDS * 1000);
|
||||
while (!timestamps.isEmpty() && timestamps.get(0) < cutoffTime) {
|
||||
timestamps.remove(0);
|
||||
success.remove(0);
|
||||
error.remove(0);
|
||||
avgTime.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Getter 메서드들
|
||||
public List<Long> getTimestamps() {
|
||||
return timestamps;
|
||||
}
|
||||
|
||||
public List<Integer> getSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public List<Integer> getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public List<Double> getAvgTime() {
|
||||
return avgTime;
|
||||
}
|
||||
|
||||
// 특정 기간 동안의 합계 계산
|
||||
public Map<String, Integer> calculateTotalCounts() {
|
||||
int totalSuccess = 0;
|
||||
int totalError = 0;
|
||||
|
||||
for (int i = 0; i < success.size(); i++) {
|
||||
totalSuccess += success.get(i);
|
||||
totalError += error.get(i);
|
||||
}
|
||||
|
||||
Map<String, Integer> totals = new HashMap<>();
|
||||
totals.put("success", totalSuccess);
|
||||
totals.put("error", totalError);
|
||||
return totals;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 초기화 메서드
|
||||
* 서비스 로드 시 스케줄러를 시작합니다.
|
||||
*/
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
log.info("거래현황 집계 서비스 초기화");
|
||||
|
||||
// 스케줄러 생성
|
||||
scheduler = Executors.newScheduledThreadPool(1);
|
||||
|
||||
// 시계열 데이터 수집 작업 추가 (10초마다 실행)
|
||||
scheduler.scheduleWithFixedDelay(this::collectTimeSeriesData, 0, TIME_SERIES_SAMPLING_SECONDS, TimeUnit.SECONDS);
|
||||
|
||||
log.info("거래현황 집계 스케줄러 시작됨");
|
||||
|
||||
// 전체 데이터용 TOTAL 엔트리 추가
|
||||
timeSeriesMap.putIfAbsent("TOTAL", new TimeSeriesData());
|
||||
}
|
||||
|
||||
/**
|
||||
* 종료 메서드
|
||||
* 서비스 종료 시 스케줄러를 정리합니다.
|
||||
*/
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
if (scheduler != null && !scheduler.isShutdown()) {
|
||||
scheduler.shutdown();
|
||||
log.info("거래현황 집계 스케줄러 종료됨");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 시계열 데이터 수집
|
||||
* 10초마다 실행되어 각 서비스별 처리 건수를 수집합니다.
|
||||
*/
|
||||
public void collectTimeSeriesData() {
|
||||
try {
|
||||
// 수집 시간
|
||||
long currentTime = System.currentTimeMillis();
|
||||
|
||||
// 서비스별 현재 누적 데이터
|
||||
Map<String, Integer> currentSuccessByService = new HashMap<>();
|
||||
Map<String, Integer> currentErrorByService = new HashMap<>();
|
||||
Map<String, Double> currentProcessingTimeByService = new HashMap<>();
|
||||
|
||||
// 전체 합계
|
||||
int totalSuccess = 0;
|
||||
int totalError = 0;
|
||||
double totalProcessingTime = 0.0;
|
||||
int totalCount = 0;
|
||||
|
||||
// 모든 온라인 데이터소스 타입에 대해 처리
|
||||
List<DataSourceType> servers = DataSourceTypeManager.getOnlineDataSourceTypes();
|
||||
log.debug("온라인 DataSource 개수: {}", servers.size());
|
||||
for (DataSourceType ds : servers) {
|
||||
log.debug(" - DataSource: {}", ds.getName());
|
||||
}
|
||||
|
||||
for (DataSourceType server : servers) {
|
||||
try {
|
||||
// 서버 컨텍스트 설정
|
||||
log.debug("DataSource 전환: {}", server.getName());
|
||||
DataSourceContextHolder.setDataSourceType(server);
|
||||
|
||||
// 서버에 대한 인스턴스 목록 조회
|
||||
List<Map<String, String>> serverList = eaiServerInfoService.getEaiServerIpListDB();
|
||||
log.debug(" - 서버 목록 개수: {}", serverList != null ? serverList.size() : 0);
|
||||
|
||||
if (serverList == null || serverList.isEmpty()) {
|
||||
log.debug(" - {} DataSource에 대한 서버 목록이 없습니다. 건너뜁니다.", server.getName());
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Map<String, String> serverInfo : serverList) {
|
||||
// 트랜잭션 정보 조회를 위한 VO 생성
|
||||
TransactionTrackingVo origin = new TransactionTrackingVo();
|
||||
origin.setHostName(serverInfo.get("HOSTNAME"));
|
||||
origin.setInstanceName(serverInfo.get("EAISVRINSTNM"));
|
||||
|
||||
// 트랜잭션 정보 조회
|
||||
TransactionTrackingVo vo = transactionRepositoryService.getTrackingVo(origin);
|
||||
|
||||
if (vo != null) {
|
||||
// 현재 누적 값 계산
|
||||
int currentSuccess = vo.getTotalProc();
|
||||
int currentError = vo.getTotalError();
|
||||
double timeTotal = vo.getTimeTotal();
|
||||
|
||||
// 서비스별 데이터 저장 (서버명_인스턴스명 형식으로 키 생성)
|
||||
String serviceKey = origin.getHostName() + "_" + origin.getInstanceName();
|
||||
currentSuccessByService.put(serviceKey, currentSuccess);
|
||||
currentErrorByService.put(serviceKey, currentError);
|
||||
currentProcessingTimeByService.put(serviceKey, timeTotal);
|
||||
|
||||
// 전체 합계에 추가
|
||||
totalSuccess += currentSuccess;
|
||||
totalError += currentError;
|
||||
totalProcessingTime += timeTotal;
|
||||
totalCount++;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("DataSource {} 처리 중 오류 발생: {}", server.getName(), e.getMessage(), e);
|
||||
} finally {
|
||||
// DataSource 컨텍스트 클리어
|
||||
DataSourceContextHolder.clearDataSourceType();
|
||||
}
|
||||
}
|
||||
|
||||
// 각 서비스 타입별 시계열 데이터 업데이트
|
||||
for (String serviceType : currentSuccessByService.keySet()) {
|
||||
// 현재 누적 값
|
||||
int currentSuccess = currentSuccessByService.get(serviceType);
|
||||
int currentError = currentErrorByService.get(serviceType);
|
||||
double processingTime = currentProcessingTimeByService.get(serviceType);
|
||||
|
||||
// 마지막 누적 값 가져오기
|
||||
Map<String, Integer> lastValues = lastAccumulatedValuesMap.getOrDefault(serviceType, new HashMap<>());
|
||||
|
||||
// 차이 계산
|
||||
int deltaSuccess = currentSuccess - lastValues.getOrDefault("success", 0);
|
||||
int deltaError = currentError - lastValues.getOrDefault("error", 0);
|
||||
|
||||
// 음수 값 방지 (카운터가 리셋된 경우)
|
||||
deltaSuccess = Math.max(0, deltaSuccess);
|
||||
deltaError = Math.max(0, deltaError);
|
||||
|
||||
// 평균 처리시간 계산
|
||||
double avgProcessingTime = (deltaSuccess + deltaError > 0) ? processingTime / (deltaSuccess + deltaError) : 0.0;
|
||||
|
||||
// 시계열 데이터 추가
|
||||
TimeSeriesData timeSeriesData = timeSeriesMap.computeIfAbsent(serviceType, k -> new TimeSeriesData());
|
||||
timeSeriesData.addDataPoint(currentTime, deltaSuccess, deltaError, avgProcessingTime);
|
||||
|
||||
// 마지막 누적 값 업데이트
|
||||
Map<String, Integer> newLastValues = new HashMap<>();
|
||||
newLastValues.put("success", currentSuccess);
|
||||
newLastValues.put("error", currentError);
|
||||
lastAccumulatedValuesMap.put(serviceType, newLastValues);
|
||||
}
|
||||
|
||||
// TOTAL 시계열 데이터 업데이트
|
||||
TimeSeriesData totalTimeSeriesData = timeSeriesMap.get("TOTAL");
|
||||
if (totalTimeSeriesData != null && totalCount > 0) {
|
||||
// 마지막 누적 값 가져오기
|
||||
Map<String, Integer> lastTotalValues = lastAccumulatedValuesMap.getOrDefault("TOTAL", new HashMap<>());
|
||||
|
||||
// 차이 계산
|
||||
int deltaSuccess = totalSuccess - lastTotalValues.getOrDefault("success", 0);
|
||||
int deltaError = totalError - lastTotalValues.getOrDefault("error", 0);
|
||||
|
||||
// 음수 값 방지
|
||||
deltaSuccess = Math.max(0, deltaSuccess);
|
||||
deltaError = Math.max(0, deltaError);
|
||||
|
||||
// 평균 처리시간 계산
|
||||
double avgProcessingTime = (deltaSuccess + deltaError > 0) ? totalProcessingTime / (deltaSuccess + deltaError) : 0.0;
|
||||
|
||||
totalTimeSeriesData.addDataPoint(currentTime, deltaSuccess, deltaError, avgProcessingTime);
|
||||
|
||||
// 마지막 누적 값 업데이트
|
||||
Map<String, Integer> newLastTotalValues = new HashMap<>();
|
||||
newLastTotalValues.put("success", totalSuccess);
|
||||
newLastTotalValues.put("error", totalError);
|
||||
lastAccumulatedValuesMap.put("TOTAL", newLastTotalValues);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("시계열 데이터 수집 중 오류 발생: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 시계열 데이터 조회
|
||||
* @param seconds 조회할 초 단위 기간 (최대 120초)
|
||||
* @return 시계열 데이터
|
||||
*/
|
||||
public TimeSeriesData getTimeSeriesData(int seconds) {
|
||||
// 기간 제한 (1~120초)
|
||||
seconds = Math.min(Math.max(seconds, 1), TIME_SERIES_RETENTION_SECONDS);
|
||||
|
||||
// TOTAL 데이터 사용
|
||||
TimeSeriesData originalData = timeSeriesMap.get("TOTAL");
|
||||
|
||||
// 데이터가 없으면 빈 데이터 반환
|
||||
if (originalData == null) {
|
||||
return new TimeSeriesData();
|
||||
}
|
||||
|
||||
// 기간에 맞게 데이터 필터링
|
||||
TimeSeriesData filteredData = new TimeSeriesData();
|
||||
long cutoffTime = System.currentTimeMillis() - (seconds * 1000);
|
||||
|
||||
List<Long> originalTimestamps = originalData.getTimestamps();
|
||||
List<Integer> originalSuccess = originalData.getSuccess();
|
||||
List<Integer> originalError = originalData.getError();
|
||||
List<Double> originalAvgTime = originalData.getAvgTime();
|
||||
|
||||
// 최근 데이터부터 시작하여 지정된 기간 내의 데이터만 필터링
|
||||
for (int i = originalTimestamps.size() - 1; i >= 0; i--) {
|
||||
if (originalTimestamps.get(i) >= cutoffTime) {
|
||||
filteredData.timestamps.add(0, originalTimestamps.get(i));
|
||||
filteredData.success.add(0, originalSuccess.get(i));
|
||||
filteredData.error.add(0, originalError.get(i));
|
||||
filteredData.avgTime.add(0, originalAvgTime.get(i));
|
||||
} else {
|
||||
// 시간 범위를 벗어나면 종료
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return filteredData;
|
||||
}
|
||||
}
|
||||
+251
-191
@@ -1,142 +1,142 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.repository;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import org.apache.commons.collections.map.MultiKeyMap;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceType;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
|
||||
import com.eactive.eai.rms.onl.tracking.vo.DbStaticsVo;
|
||||
import com.eactive.eai.rms.onl.tracking.vo.MemStaticsVo;
|
||||
import com.eactive.eai.rms.onl.vo.SmsVO;
|
||||
import org.apache.commons.collections.map.MultiKeyMap;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
@Scope(value = "singleton")
|
||||
@Repository("dashboard2Repository")
|
||||
public class Dashboard2Repository implements Serializable {
|
||||
|
||||
/** The Constant serialVersionUID. */
|
||||
private static final long serialVersionUID = -8968729820736492173L;
|
||||
/** The Constant serialVersionUID. */
|
||||
private static final long serialVersionUID = -8968729820736492173L;
|
||||
|
||||
/** The Constant logger. */
|
||||
private static final Logger logger = Logger.getLogger(Dashboard2Repository.class);
|
||||
|
||||
// 서비스 -> Map(EAI 서비스코드,서버구분코드)
|
||||
private HashMap<String,HashMap<String,String>> repoEAISevrDstcd;
|
||||
|
||||
// 대시보드 거래건수 ( 메모리 버전 )
|
||||
private MultiKeyMap repoMemTransaction;
|
||||
|
||||
// 서비스, 호스트 -> vo
|
||||
/** The Constant logger. */
|
||||
private static final Logger logger = Logger.getLogger(Dashboard2Repository.class);
|
||||
|
||||
// 서비스 -> Map(EAI 서비스코드,서버구분코드)
|
||||
private HashMap<String,HashMap<String,String>> repoEAISevrDstcd;
|
||||
|
||||
// 대시보드 거래건수 ( 메모리 버전 )
|
||||
private MultiKeyMap repoMemTransaction;
|
||||
|
||||
// 서비스, 호스트 -> vo
|
||||
private MultiKeyMap repository;
|
||||
|
||||
|
||||
// 서비스 -> 호스트네임 리스트
|
||||
private HashMap<String,List<String>> repoHostNames;
|
||||
|
||||
|
||||
// 호스트명 -> 인스턴스네임 리스트
|
||||
private HashMap<String,List<String>> repoInstNames;
|
||||
|
||||
|
||||
// 호스트명 -> datasource, 인스턴스네임 list
|
||||
private HashMap<String, Map<String, List<String>>> repoDataSourceInstNames;
|
||||
|
||||
// DB 에 저장된 통계정보 최대값.
|
||||
private String maxProcTime;
|
||||
|
||||
|
||||
|
||||
|
||||
// 호스트 telnet 연결 성공 여부 ( ip -> "1/0" )
|
||||
private HashMap<String,String> repoHostAlive;
|
||||
|
||||
|
||||
// SMS 메시지 리스트
|
||||
private List<SmsVO> repoSms;
|
||||
|
||||
// Peak Trade by TPS( service -> date,trade,tps )
|
||||
private HashMap<String,String> repoPeakTps;
|
||||
|
||||
|
||||
// Peak Trade by Trade ( service -> date,trade,tps )
|
||||
private HashMap<String,String> repoPeakTrade;
|
||||
|
||||
|
||||
|
||||
|
||||
// 일괄전송 전송현황 건수 ( 정상송신,정상수신,에러송신,에러수신,대기송신,대기수신)
|
||||
private int[] procBatch = new int[6];
|
||||
private int[] procEaiBatch = new int[6];
|
||||
|
||||
//ITSM 대시보드 연동 거래처리 집계
|
||||
private HashMap<String,HashMap<String,String>> itsmDashTranInfo;
|
||||
|
||||
//ITSM PEAK DAY 정보
|
||||
private HashMap<String,HashMap<String,String>> itsmDashPeakdayInfo;
|
||||
|
||||
|
||||
//ITSM 대시보드 연동 거래처리 집계
|
||||
private HashMap<String,HashMap<String,String>> itsmDashTranInfo;
|
||||
|
||||
//ITSM PEAK DAY 정보
|
||||
private HashMap<String,HashMap<String,String>> itsmDashPeakdayInfo;
|
||||
|
||||
//ITSM 대시보드 연동 날짜별 거래건수
|
||||
private HashMap<String,List> itsmDashDayTranList;
|
||||
|
||||
//ITSM 대시보드 연동 날짜별 거래건수
|
||||
private HashMap<String,List> itsmDashHourTranList;
|
||||
|
||||
//ITSM 대시보드 연동 서비스별 업무에러 MAP
|
||||
private HashMap<String,HashMap<String,String>> itsmDashBizErrorMap;
|
||||
|
||||
//ITSM 대시보드 연동 서비스별 타임아웃에러 MAP
|
||||
private HashMap<String,HashMap<String,String>> itsmDashTimeoutErrorMap;
|
||||
|
||||
//ITSM 통신에러 MAP
|
||||
private HashMap<String,HashMap<String,String>> itsmDashConnectErrorMap;
|
||||
|
||||
//ITSM 대시비도연동 서버 인스턴스별 거래건수
|
||||
private HashMap<String,HashMap<String,String>> itsmDashServerInstTotalProcMap;
|
||||
|
||||
private String itsmDashTranInfoBatch;
|
||||
|
||||
private String itsmDashTranInfoBatch2;
|
||||
|
||||
private HashMap<String, HashMap<String, String>> itsmDashEaiBatchErrorList;
|
||||
|
||||
private HashMap<String, HashMap<String, String>> itsmDashFepBatchErrorList;
|
||||
|
||||
private HashMap<String,List> itsmDashDayTranList;
|
||||
|
||||
//ITSM 대시보드 연동 날짜별 거래건수
|
||||
private HashMap<String,List> itsmDashHourTranList;
|
||||
|
||||
//ITSM 대시보드 연동 서비스별 업무에러 MAP
|
||||
private HashMap<String,HashMap<String,String>> itsmDashBizErrorMap;
|
||||
|
||||
//ITSM 대시보드 연동 서비스별 타임아웃에러 MAP
|
||||
private HashMap<String,HashMap<String,String>> itsmDashTimeoutErrorMap;
|
||||
|
||||
//ITSM 통신에러 MAP
|
||||
private HashMap<String,HashMap<String,String>> itsmDashConnectErrorMap;
|
||||
|
||||
//ITSM 대시비도연동 서버 인스턴스별 거래건수
|
||||
private HashMap<String,HashMap<String,String>> itsmDashServerInstTotalProcMap;
|
||||
|
||||
private String itsmDashTranInfoBatch;
|
||||
|
||||
private String itsmDashTranInfoBatch2;
|
||||
|
||||
private HashMap<String, HashMap<String, String>> itsmDashEaiBatchErrorList;
|
||||
|
||||
private HashMap<String, HashMap<String, String>> itsmDashFepBatchErrorList;
|
||||
|
||||
// 각 서비스별로 집계정보 저장
|
||||
// private Dashboard2ProcVo inTotalVo;
|
||||
// private Dashboard2ProcVo outTotalVo;
|
||||
// private Dashboard2ProcVo commTotalVo;
|
||||
// private Dashboard2ProcVo dmzTotalVo;
|
||||
// private Dashboard2ProcVo batchTotalVo;
|
||||
//
|
||||
//
|
||||
// private String lastTime; // 최종으로 저장된 startTime(모든 서비스에 공통)
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 저장소 를 초기화 한다.
|
||||
*/
|
||||
@PostConstruct
|
||||
public void createRepository() {
|
||||
public void createRepository() {
|
||||
repoEAISevrDstcd = new HashMap<String,HashMap<String,String>>();
|
||||
repoMemTransaction = new MultiKeyMap();
|
||||
repository = new MultiKeyMap();
|
||||
repoHostNames = new HashMap<String,List<String>>();
|
||||
repoInstNames = new HashMap<String,List<String>>();
|
||||
repoHostAlive = new HashMap<String,String>();
|
||||
repoSms = new ArrayList<SmsVO>();
|
||||
repoPeakTps = new HashMap<String,String>();
|
||||
repoPeakTrade = new HashMap<String,String>();
|
||||
|
||||
itsmDashTranInfo = new HashMap<String,HashMap<String,String>>();
|
||||
itsmDashPeakdayInfo = new HashMap<String,HashMap<String,String>>();
|
||||
itsmDashHourTranList = new HashMap<String,List>();
|
||||
itsmDashDayTranList = new HashMap<String,List>();
|
||||
itsmDashBizErrorMap = new HashMap<String,HashMap<String,String>>();
|
||||
itsmDashTimeoutErrorMap = new HashMap<String,HashMap<String,String>>();
|
||||
itsmDashConnectErrorMap = new HashMap<String,HashMap<String,String>>();
|
||||
itsmDashServerInstTotalProcMap = new HashMap<String,HashMap<String,String>>();
|
||||
|
||||
itsmDashEaiBatchErrorList = new HashMap<String,HashMap<String,String>>();
|
||||
itsmDashFepBatchErrorList = new HashMap<String,HashMap<String,String>>();
|
||||
|
||||
}
|
||||
|
||||
repository = new MultiKeyMap();
|
||||
repoHostNames = new HashMap<String,List<String>>();
|
||||
repoInstNames = new HashMap<String,List<String>>();
|
||||
repoDataSourceInstNames = new HashMap<String,Map<String,List<String>>>();
|
||||
repoHostAlive = new HashMap<String,String>();
|
||||
repoSms = new ArrayList<SmsVO>();
|
||||
repoPeakTps = new HashMap<String,String>();
|
||||
repoPeakTrade = new HashMap<String,String>();
|
||||
|
||||
itsmDashTranInfo = new HashMap<String,HashMap<String,String>>();
|
||||
itsmDashPeakdayInfo = new HashMap<String,HashMap<String,String>>();
|
||||
itsmDashHourTranList = new HashMap<String,List>();
|
||||
itsmDashDayTranList = new HashMap<String,List>();
|
||||
itsmDashBizErrorMap = new HashMap<String,HashMap<String,String>>();
|
||||
itsmDashTimeoutErrorMap = new HashMap<String,HashMap<String,String>>();
|
||||
itsmDashConnectErrorMap = new HashMap<String,HashMap<String,String>>();
|
||||
itsmDashServerInstTotalProcMap = new HashMap<String,HashMap<String,String>>();
|
||||
|
||||
itsmDashEaiBatchErrorList = new HashMap<String,HashMap<String,String>>();
|
||||
itsmDashFepBatchErrorList = new HashMap<String,HashMap<String,String>>();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 저장소 데이터를 리셋 한다.
|
||||
*/
|
||||
@@ -146,30 +146,31 @@ public class Dashboard2Repository implements Serializable {
|
||||
repository.clear();
|
||||
repoHostNames.clear();
|
||||
repoInstNames.clear();
|
||||
repoDataSourceInstNames.clear();
|
||||
repoHostAlive.clear();
|
||||
repoSms.clear();
|
||||
repoPeakTps.clear();
|
||||
repoPeakTrade.clear();
|
||||
|
||||
|
||||
for( int i = 0 ; i < 6 ; i++ ) {
|
||||
procBatch[i] = 0 ;
|
||||
procEaiBatch[i] = 0 ;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void initMemTrRepository() {
|
||||
repoMemTransaction.clear();
|
||||
repoMemTransaction = new MultiKeyMap();
|
||||
}
|
||||
|
||||
|
||||
public void checkMemTrRepository() {
|
||||
logger.info("check repoMemTransaction size=" + repoMemTransaction.size() );
|
||||
}
|
||||
|
||||
|
||||
public void removeMemTrRepository(String host, String inst) {
|
||||
repoMemTransaction.remove(host, inst);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 주어진 서비스의 DB 에서 읽어온 EAI서비스코드,서버구분코드 맵을 리턴한다.
|
||||
* @param service
|
||||
@@ -178,7 +179,7 @@ public class Dashboard2Repository implements Serializable {
|
||||
public HashMap<String,String> getRepoEAIServrDstcd(String service) {
|
||||
return repoEAISevrDstcd.get(service);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 주어진 서비스명으로 메모리에서 읽어온 맵을 리턴한다.
|
||||
* @param service
|
||||
@@ -187,12 +188,12 @@ public class Dashboard2Repository implements Serializable {
|
||||
public HashMap<String,String> getItsmDashTranInfo(String service) {
|
||||
return itsmDashTranInfo.get(service);
|
||||
}
|
||||
|
||||
|
||||
public HashMap<String,String> getItsmDashPeakdayInfo(String service) {
|
||||
return itsmDashPeakdayInfo.get(service);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 주어진 서비스명으로 메모리에서 읽어온 리스트을 리턴한다.
|
||||
* @param service
|
||||
@@ -201,35 +202,35 @@ public class Dashboard2Repository implements Serializable {
|
||||
public List itsmDashHourTranList(String service) {
|
||||
return itsmDashHourTranList.get(service);
|
||||
}
|
||||
|
||||
|
||||
public List itsmDashDayTranList(String service) {
|
||||
return itsmDashDayTranList.get(service);
|
||||
}
|
||||
|
||||
|
||||
public HashMap<String, HashMap<String, String>> itsmDashBizErrorMap() {
|
||||
return itsmDashBizErrorMap;
|
||||
}
|
||||
|
||||
|
||||
public HashMap<String, HashMap<String, String>> itsmDashTimeoutErrorMap() {
|
||||
return itsmDashTimeoutErrorMap;
|
||||
}
|
||||
|
||||
|
||||
public HashMap<String, HashMap<String, String>> itsmDashConnectErrorMap() {
|
||||
return itsmDashConnectErrorMap;
|
||||
}
|
||||
|
||||
|
||||
public HashMap<String,String> itsmDashServerInstTotalProcMap(String service) {
|
||||
return itsmDashServerInstTotalProcMap.get(service);
|
||||
}
|
||||
|
||||
|
||||
public HashMap<String,HashMap<String,String>> itsmDashEaiBatchErrorList() {
|
||||
return itsmDashEaiBatchErrorList;
|
||||
}
|
||||
|
||||
|
||||
public HashMap<String,HashMap<String,String>> itsmDashFepBatchErrorList() {
|
||||
return itsmDashFepBatchErrorList;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 주어진 host 명이 포함된 DB 에서 읽어온 EAI서비스코드,서버구분코드 맵을 리턴한다.
|
||||
* @param host
|
||||
@@ -240,19 +241,19 @@ public class Dashboard2Repository implements Serializable {
|
||||
// if( service != null ) {
|
||||
// return getRepoEAIServrDstcd(service.getKey());
|
||||
// }
|
||||
//
|
||||
//
|
||||
// return null ;
|
||||
// }
|
||||
//TODO JUN
|
||||
//TODO JUN
|
||||
public HashMap<String,String> getRepoEAIServrDstcdByHost(String host) {
|
||||
DataSourceType dataSourceType = getServiceTypeByHost(host);
|
||||
if( dataSourceType != null ) {
|
||||
return getRepoEAIServrDstcd(dataSourceType.getName());
|
||||
}
|
||||
|
||||
|
||||
return null ;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 주어진 host 명이 포함된 DB 의 서비스 타입을 찾아서 리턴한다.
|
||||
* @param host
|
||||
@@ -269,10 +270,10 @@ public class Dashboard2Repository implements Serializable {
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// return null ;
|
||||
// }
|
||||
//TODO JUN
|
||||
//TODO JUN
|
||||
public DataSourceType getServiceTypeByHost(String host) {
|
||||
Iterator<String> it = repoHostNames.keySet().iterator();
|
||||
while( it.hasNext() ) {
|
||||
@@ -284,9 +285,9 @@ public class Dashboard2Repository implements Serializable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return null ;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Repository 에 주어진 서비스를 키로 하여 EAI서비스코드,서버구분코드 맵을 추가한다.
|
||||
* @param service
|
||||
@@ -295,7 +296,7 @@ public class Dashboard2Repository implements Serializable {
|
||||
public void setRepoEAISevrDstcd(String service, HashMap<String,String> map) {
|
||||
repoEAISevrDstcd.put(service, map);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Repository 에 주어진 서비스를 키로 하여 메모리 거래현황 맵을 추가한다.
|
||||
* @param service
|
||||
@@ -304,45 +305,45 @@ public class Dashboard2Repository implements Serializable {
|
||||
public void setItsmDashTranInfo(String service, HashMap<String,String> map) {
|
||||
itsmDashTranInfo.put(service, map);
|
||||
}
|
||||
|
||||
|
||||
public void setItsmDashPeakdayInfo(String service, HashMap<String,String> map) {
|
||||
itsmDashPeakdayInfo.put(service, map);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Repository 에 주어진 서비스를 키로 하여 메모리 시간별거래건수 맵을 추가한다.
|
||||
* @param service
|
||||
* @param map
|
||||
*/
|
||||
public void setItsmDashHourTranList(String service, List list) {
|
||||
|
||||
|
||||
itsmDashHourTranList.put(service, list);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Repository 에 주어진 서비스를 키로 하여 메모리 시간별거래건수 맵을 추가한다.
|
||||
* @param service
|
||||
* @param map
|
||||
*/
|
||||
public void setItsmDashDayTranList(String service, List list) {
|
||||
|
||||
|
||||
itsmDashDayTranList.put(service, list);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Repository 에 주어진 서비스를 키로 하여 메모리 업무에러리스트 맵을 추가한다.
|
||||
* @param service
|
||||
* @param map
|
||||
*/
|
||||
public void setItsmDashBizErrorMap(HashMap<String,HashMap<String, String>> map) {
|
||||
|
||||
|
||||
this.itsmDashBizErrorMap = map;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Repository 에 주어진 서비스를 키로 하여 메모리 타임아웃에러리스트 맵을 추가한다.
|
||||
* @param service
|
||||
@@ -351,38 +352,38 @@ public class Dashboard2Repository implements Serializable {
|
||||
public void setItsmDashTimeoutErrorMap(HashMap<String, HashMap<String, String>> map) {
|
||||
this.itsmDashTimeoutErrorMap = map;
|
||||
}
|
||||
|
||||
|
||||
public void setItsmDashConnectErrorMap(HashMap<String, HashMap<String, String>> map) {
|
||||
this.itsmDashConnectErrorMap = map;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public void setItsmDashServerInstTotalProcMap(String service ,HashMap<String,String> map) {
|
||||
|
||||
|
||||
this.itsmDashServerInstTotalProcMap.put(service, map);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public void setItsmDashTranInfoBatch( String batchCount ) {
|
||||
this.itsmDashTranInfoBatch = batchCount ;
|
||||
}
|
||||
|
||||
|
||||
public String getItsmDashTranInfoBatch() {
|
||||
return this.itsmDashTranInfoBatch;
|
||||
}
|
||||
|
||||
|
||||
public void setItsmDashTranInfoBatch2( String batchCount2 ) {
|
||||
this.itsmDashTranInfoBatch2 = batchCount2 ;
|
||||
}
|
||||
|
||||
|
||||
public String getItsmDashTranInfoBatch2() {
|
||||
return this.itsmDashTranInfoBatch2;
|
||||
}
|
||||
|
||||
|
||||
public void setItsmDashEaiBatchErrorList(HashMap<String,HashMap<String,String>> eaiBatchErrorList) {
|
||||
this.itsmDashEaiBatchErrorList = eaiBatchErrorList;
|
||||
}
|
||||
|
||||
|
||||
public void setItsmDashFepBatchErrorList(HashMap<String,HashMap<String,String>> fepBatchErrorList) {
|
||||
this.itsmDashFepBatchErrorList = fepBatchErrorList;
|
||||
}
|
||||
@@ -394,8 +395,8 @@ public class Dashboard2Repository implements Serializable {
|
||||
public MultiKeyMap getRepoMemTr() {
|
||||
return repoMemTransaction;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 저장소 객체를 리턴한다.
|
||||
* @return
|
||||
@@ -403,7 +404,7 @@ public class Dashboard2Repository implements Serializable {
|
||||
public MultiKeyMap getRepository() {
|
||||
return repository;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 서비스 -> 호스트명 리스트를 저장한다.
|
||||
* @param service
|
||||
@@ -412,7 +413,7 @@ public class Dashboard2Repository implements Serializable {
|
||||
public void setHostNames(String service, List<String> list ) {
|
||||
repoHostNames.put(service,list);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 호스트명 -> 인스턴스 리스트를 저장한다.
|
||||
* @param host
|
||||
@@ -421,7 +422,11 @@ public class Dashboard2Repository implements Serializable {
|
||||
public void setInstNames(String host, List<String> list ) {
|
||||
repoInstNames.put(host,list);
|
||||
}
|
||||
|
||||
|
||||
public void setDataSourceInstNames(String host, Map<String, List<String>> putData ) {
|
||||
repoDataSourceInstNames.put(host, putData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Memory 거래현황정보 추가
|
||||
* @param host
|
||||
@@ -447,28 +452,28 @@ public class Dashboard2Repository implements Serializable {
|
||||
vo.setHostName(host);
|
||||
vo.setInstName(inst);
|
||||
}
|
||||
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 주어진 호스트명 에서 수신된 Vo 리스트를 찾아서 리턴한다.
|
||||
* @param host
|
||||
* @return
|
||||
*/
|
||||
public List<MemStaticsVo> getMemTradeVoByHost( String host ) {
|
||||
List<MemStaticsVo> list = new ArrayList<MemStaticsVo>();
|
||||
List<MemStaticsVo> list = new ArrayList<MemStaticsVo>();
|
||||
List<String> insts = getInstNames(host);
|
||||
|
||||
|
||||
if( insts != null ) {
|
||||
for( String inst : insts ) {
|
||||
list.add( getMemTradeVo(host,inst) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 주어진 서비스에서 수신된 전체 Vo 리스트를 찾아서 리턴한다.
|
||||
* @param service
|
||||
@@ -482,86 +487,108 @@ public class Dashboard2Repository implements Serializable {
|
||||
list.addAll( getMemTradeVoByHost(host) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 주어진 서비스에서 수신된 전체 Vo 리스트를 찾아서 리턴한다.
|
||||
* repoDataSourceInstNames 에서 host, instance 명 조회 후 MemTradeVo를 리턴한다.
|
||||
* @param service
|
||||
* @return
|
||||
*/
|
||||
public List<MemStaticsVo> getDashboardMemTradeVoByService(String service) {
|
||||
return Optional.ofNullable(getHostNames(service))
|
||||
.orElse(Collections.emptyList())
|
||||
.stream()
|
||||
.flatMap(host -> getInstListByHostAndService(host, service).stream()
|
||||
.map(instNm -> getMemTradeVo(host, instNm)))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<String> getInstListByHostAndService(String host, String service) {
|
||||
return Optional.ofNullable(repoDataSourceInstNames)
|
||||
.map(m -> m.get(host))
|
||||
.map(serviceMap -> serviceMap.get(service))
|
||||
.orElse(Collections.emptyList());
|
||||
}
|
||||
|
||||
|
||||
public void setTradeVo(String service, String host, DbStaticsVo vo) {
|
||||
repository.put(service, host, vo);
|
||||
}
|
||||
|
||||
|
||||
public DbStaticsVo getTradeVo(String service, String host) {
|
||||
|
||||
|
||||
DbStaticsVo vo = (DbStaticsVo)repository.get(service, host);
|
||||
if( vo == null ) {
|
||||
vo = new DbStaticsVo();
|
||||
vo.init();
|
||||
}
|
||||
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
||||
|
||||
public List<DbStaticsVo> getTradeVo(String service) {
|
||||
List<DbStaticsVo> list = new ArrayList<DbStaticsVo>();
|
||||
|
||||
|
||||
List<String> hostNames = repoHostNames.get(service);
|
||||
if( hostNames != null ) {
|
||||
for( String host : hostNames ) {
|
||||
list.add( getTradeVo(service, host) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if( list.size() == 0 ) {
|
||||
DbStaticsVo vo = new DbStaticsVo();
|
||||
vo.init();
|
||||
list.add(vo);
|
||||
}
|
||||
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
public DbStaticsVo getTotalVo(String service) {
|
||||
List<DbStaticsVo> list = getTradeVo(service);
|
||||
|
||||
|
||||
DbStaticsVo retVo = new DbStaticsVo();
|
||||
retVo.init();
|
||||
for( DbStaticsVo vo : list ) {
|
||||
retVo.add( vo );
|
||||
}
|
||||
|
||||
|
||||
return retVo ;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void setMaxProcTime( String fulltime ) {
|
||||
this.maxProcTime = fulltime ;
|
||||
}
|
||||
|
||||
|
||||
public String getMaxProcTime() {
|
||||
return this.maxProcTime;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public boolean isInitHostRepository() {
|
||||
if( repoHostAlive.size() > 0 )
|
||||
return true ;
|
||||
else
|
||||
return false ;
|
||||
}
|
||||
|
||||
|
||||
public void addHostKey( List<String> listAddr ) {
|
||||
|
||||
|
||||
if( listAddr != null ) {
|
||||
for( String addr : listAddr ) {
|
||||
repoHostAlive.put(addr, "1");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean isHostAlive( String ipaddr, String port ) {
|
||||
String alive = repoHostAlive.get(ipaddr + ":" + port);
|
||||
|
||||
|
||||
// 디폴트는 성공으로 간주하고, 실패시에만 Fail 처리한다.
|
||||
if( "0".equals(alive) ) {
|
||||
return false ;
|
||||
@@ -569,7 +596,7 @@ public class Dashboard2Repository implements Serializable {
|
||||
return true ;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean isHostAlive( String ipaddr ) {
|
||||
boolean foundDead = false ;
|
||||
|
||||
@@ -589,74 +616,74 @@ public class Dashboard2Repository implements Serializable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if( foundDead ) {
|
||||
// 모든 inst 가 실패
|
||||
return false ;
|
||||
}
|
||||
|
||||
|
||||
// 디폴트
|
||||
return true ;
|
||||
}
|
||||
|
||||
|
||||
public void setHostAlive( String ipaddr, String port, boolean alive ) {
|
||||
String aliveVal = "1" ;
|
||||
if( alive == false ) {
|
||||
aliveVal = "0" ;
|
||||
}
|
||||
|
||||
|
||||
repoHostAlive.put(ipaddr + ":" + port, aliveVal );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ip : port 형태의 주소값 리스트를 리턴한다.
|
||||
* @return
|
||||
*/
|
||||
public List<String> getHostIPList() {
|
||||
Iterator it = repoHostAlive.keySet().iterator();
|
||||
|
||||
|
||||
List<String> list = new ArrayList<String>();
|
||||
while( it.hasNext() ) {
|
||||
String key = (String)it.next();
|
||||
list.add( key );
|
||||
}
|
||||
|
||||
|
||||
return list ;
|
||||
}
|
||||
|
||||
|
||||
public List<String> getHostNames( String service ) {
|
||||
return repoHostNames.get(service);
|
||||
}
|
||||
|
||||
|
||||
public List<String> getInstNames( String host ) {
|
||||
return repoInstNames.get(host);
|
||||
}
|
||||
|
||||
|
||||
public void addSms( SmsVO vo ) {
|
||||
repoSms.add( vo );
|
||||
|
||||
|
||||
// 만일 100개가 넘으면 앞에서 부터 50개를 삭제한다.
|
||||
if( repoSms.size() >= 100 ) {
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void setPeakTps( String service, String val ) {
|
||||
repoPeakTps.put(service, val);
|
||||
}
|
||||
|
||||
|
||||
public String getPeakTps( String service ) {
|
||||
return repoPeakTps.get(service);
|
||||
}
|
||||
|
||||
|
||||
public void setPeakTrade( String service, String val ) {
|
||||
repoPeakTrade.put(service, val);
|
||||
}
|
||||
|
||||
|
||||
public String getPeakTrade( String service ) {
|
||||
return repoPeakTrade.get(service);
|
||||
}
|
||||
|
||||
|
||||
public String getProcBatch( ) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for( int i = 0 ; i < this.procBatch.length ; i++ ) {
|
||||
@@ -664,7 +691,7 @@ public class Dashboard2Repository implements Serializable {
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
public String getProcEaiBatch( ) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for( int i = 0 ; i < this.procEaiBatch.length ; i++ ) {
|
||||
@@ -672,7 +699,7 @@ public class Dashboard2Repository implements Serializable {
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
public void setProcBatch( int p1, int p2, int p3, int p4, int p5, int p6 ) {
|
||||
this.procBatch[0] = p1 ;
|
||||
this.procBatch[1] = p2 ;
|
||||
@@ -691,4 +718,37 @@ public class Dashboard2Repository implements Serializable {
|
||||
this.procEaiBatch[5] = p6 ;
|
||||
}
|
||||
|
||||
// 주어진 데이터소스명을 기준으로 hostname list를 리턴한다.
|
||||
public List<Map<String,String>> getDatasourceByHost(String datasourcName){
|
||||
List<String> hostList = repoHostNames.get(datasourcName);
|
||||
List<Map<String,String>> serverInfoList = new ArrayList<Map<String,String>>();
|
||||
|
||||
if( hostList != null ){
|
||||
for(String hostName : hostList) {
|
||||
Map<String, List<String>> dataSourceInst = repoDataSourceInstNames.get(hostName);
|
||||
if( dataSourceInst != null ){
|
||||
List<String> instNameList = dataSourceInst.get(datasourcName);
|
||||
|
||||
if( instNameList != null ){
|
||||
for (String instName : instNameList) {
|
||||
Map<String, String> serverInfo = new HashMap<String, String>();
|
||||
|
||||
serverInfo.put("HOSTNAME", hostName);
|
||||
serverInfo.put("EAISVRINSTNM", instName);
|
||||
|
||||
serverInfoList.add(serverInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return serverInfoList;
|
||||
}
|
||||
|
||||
// 주어진 host 명을 기준으로 map datasource, instance list 를 리턴한다.
|
||||
public Map<String, List<String>> getDatasourceInstListByHostNm(String hostName){
|
||||
return repoDataSourceInstNames.getOrDefault(hostName, new HashMap<String, List<String>>());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+389
-262
File diff suppressed because it is too large
Load Diff
+3
-4
@@ -1,13 +1,12 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Vector;
|
||||
|
||||
import com.eactive.eai.rms.onl.dashboard.dao.DashboardErrorDAO;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.eactive.eai.rms.onl.dashboard.dao.DashboardErrorDAO;
|
||||
import java.util.HashMap;
|
||||
import java.util.Vector;
|
||||
|
||||
@Service("dashboardErrorService")
|
||||
public class DashboardErrorServiceImpl implements DashboardErrorService {
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.eactive.eai.rms.onl.vo.*;
|
||||
|
||||
import com.eactive.eai.rms.onl.vo.AdaptersVO;
|
||||
import com.eactive.eai.rms.onl.vo.EAIEngineStatusVo;
|
||||
import com.eactive.eai.rms.onl.vo.HostStatusVo;
|
||||
import com.eactive.eai.rms.onl.vo.RealTimeAdaptersVO;
|
||||
import com.eactive.eai.rms.onl.vo.SmsVO;
|
||||
import com.eactive.eai.rms.onl.vo.TransactionTrackingVo;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The Interface DashboardService.
|
||||
|
||||
@@ -1,22 +1,16 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
|
||||
import com.eactive.eai.rms.onl.server.repository.Repository;
|
||||
import com.eactive.eai.rms.onl.server.repository.TransactionRepositoryService;
|
||||
import com.eactive.eai.rms.onl.vo.AdaptersVO;
|
||||
import com.eactive.eai.rms.onl.vo.EAIEngineStatusVo;
|
||||
import com.eactive.eai.rms.onl.vo.HostStatusVo;
|
||||
import com.eactive.eai.rms.onl.vo.RealTimeAdaptersVO;
|
||||
import com.eactive.eai.rms.onl.vo.SmsVO;
|
||||
import com.eactive.eai.rms.onl.vo.TransactionTrackingVo;
|
||||
import com.eactive.eai.rms.onl.vo.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The Class DashboardServiceImpl.
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
package com.eactive.eai.rms.onl.dashboard.service;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.Optional;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.eactive.eai.common.util.SystemUtil;
|
||||
import com.eactive.eai.data.entity.onl.property.Prop;
|
||||
import com.eactive.eai.data.entity.onl.property.PropGroup;
|
||||
@@ -23,6 +11,12 @@ import com.eactive.eai.rms.data.entity.onl.property.PropertyGroupService;
|
||||
import com.eactive.eai.rms.onl.dashboard.dao.DashboardErrorDAO;
|
||||
import com.eactive.eai.rms.onl.manage.comm.unifbwk.UnifbwkManService;
|
||||
import com.eactive.eai.rms.onl.manage.comm.unifbwk.UnifbwkUI;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* The Interface DashboardService.
|
||||
|
||||
@@ -64,7 +64,7 @@ public class TransactionSendHandler implements IMessageHandler {
|
||||
// 호스트명, 인스턴스명을 초기화 한다.
|
||||
|
||||
//DataSourceContextHolder.setDataSourceType(DataSourceTypeManager.getFirstDataSourceType());
|
||||
dashboard2Repository.initHostInstNames(false);
|
||||
dashboard2Repository.initHostInstNames(false, null);
|
||||
dashboard2Repository.initEaiSevrDstcd(false);
|
||||
|
||||
// 참고 : 지정된 호스트 이외의 데이터는 버린다.
|
||||
|
||||
Reference in New Issue
Block a user