diff --git a/WebContent/jsp/onl/admin/inflow/inflowGroupControlManDetail.jsp b/WebContent/jsp/onl/admin/inflow/inflowGroupControlManDetail.jsp
index 5c8d399..d0e7dab 100644
--- a/WebContent/jsp/onl/admin/inflow/inflowGroupControlManDetail.jsp
+++ b/WebContent/jsp/onl/admin/inflow/inflowGroupControlManDetail.jsp
@@ -96,6 +96,57 @@
.interface-empty { padding:30px 20px; text-align:center; color:#999; font-size:12px; }
.interface-empty i { vertical-align:middle; margin-right:4px; font-size:18px; }
+/* 탭 스타일 */
+.detail-tabs { display:flex; gap:0; border-bottom:2px solid rgba(0,128,128,0.3); margin-bottom:20px; }
+.detail-tab { padding:10px 24px; font-size:13px; font-weight:bold; color:#666; cursor:pointer; border:none; background:transparent; border-bottom:2px solid transparent; margin-bottom:-2px; transition:all 0.2s; }
+.detail-tab:hover { color:rgba(0,128,128,0.8); background:rgba(0,128,128,0.05); }
+.detail-tab.active { color:rgba(0,128,128,1); border-bottom-color:rgba(0,128,128,0.8); background:rgba(0,128,128,0.05); }
+.detail-tab i { vertical-align:middle; margin-right:4px; font-size:18px; }
+.tab-content { display:none; }
+.tab-content.active { display:block; }
+
+/* 버킷 현황 스타일 */
+.bucket-status-wrap { margin-top:10px; }
+.bucket-refresh-bar { display:flex; justify-content:space-between; align-items:center; margin-bottom:15px; }
+.bucket-refresh-bar .last-refresh { font-size:11px; color:#999; }
+.bucket-server-list { display:flex; flex-wrap:wrap; gap:15px; }
+.bucket-server-card { flex:0 0 calc(33.333% - 10px); min-width:280px; border:1px solid #ddd; border-radius:5px; overflow:hidden; }
+.bucket-server-header { display:flex; justify-content:space-between; align-items:center; padding:10px 15px; background:#f9f9f9; border-bottom:1px solid #eee; }
+.bucket-server-name { font-weight:bold; font-size:13px; }
+.bucket-server-ip { font-size:11px; color:#888; font-family:Consolas, monospace; }
+.bucket-server-status { font-size:11px; padding:3px 8px; border-radius:10px; }
+.bucket-server-status.online { background:#e8f5e9; color:#2e7d32; }
+.bucket-server-status.offline { background:#ffebee; color:#c62828; }
+.bucket-server-status.no_data { background:#fff3e0; color:#ef6c00; }
+.bucket-server-body { padding:15px; }
+.bucket-info-grid { display:grid; grid-template-columns:repeat(auto-fit, minmax(280px, 1fr)); gap:15px; }
+.bucket-card { background:#fafafa; border:1px solid #eee; border-radius:4px; padding:12px 15px; }
+.bucket-card-header { display:flex; justify-content:space-between; align-items:center; margin-bottom:10px; }
+.bucket-type { font-size:12px; font-weight:bold; color:#333; }
+.bucket-type-badge { font-size:10px; padding:2px 6px; border-radius:3px; background:#e3f2fd; color:#1565c0; }
+.bucket-progress-wrap { margin-bottom:8px; }
+.bucket-progress-bar { height:8px; background:#e0e0e0; border-radius:4px; overflow:hidden; }
+.bucket-progress-fill { height:100%; border-radius:4px; transition:width 0.3s ease; }
+.bucket-progress-fill.low { background:linear-gradient(90deg, #4caf50, #66bb6a); }
+.bucket-progress-fill.medium { background:linear-gradient(90deg, #ff9800, #ffa726); }
+.bucket-progress-fill.high { background:linear-gradient(90deg, #f44336, #ef5350); }
+.bucket-stats { display:flex; justify-content:space-between; font-size:11px; color:#666; }
+.bucket-stats-value { font-family:Consolas, monospace; }
+.bucket-empty-msg { padding:30px; text-align:center; color:#999; font-size:12px; }
+.bucket-empty-msg i { font-size:36px; display:block; margin-bottom:10px; color:#ddd; }
+.bucket-error-msg { color:#c62828; font-size:12px; }
+
+/* 전체 합산 요약 */
+.bucket-summary { background:linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); border:1px solid #dee2e6; border-radius:5px; padding:15px 20px; margin-bottom:20px; }
+.bucket-summary-title { font-size:13px; font-weight:bold; color:#333; margin-bottom:12px; display:flex; align-items:center; gap:6px; }
+.bucket-summary-title i { font-size:18px; color:rgba(0,128,128,0.8); }
+.bucket-summary-grid { display:flex; gap:30px; flex-wrap:wrap; }
+.bucket-summary-item { display:flex; flex-direction:column; }
+.bucket-summary-label { font-size:11px; color:#666; margin-bottom:4px; }
+.bucket-summary-value { font-size:18px; font-weight:bold; color:#333; font-family:Consolas, monospace; }
+.bucket-summary-value .unit { font-size:12px; font-weight:normal; color:#888; margin-left:4px; }
+.bucket-summary-sub { font-size:11px; color:#888; margin-top:2px; }
+
/* 도움말 모달 */
.help-modal-overlay { display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.4); z-index:9999; }
.help-modal { position:fixed; top:50%; left:50%; transform:translate(-50%,-50%); background:#fff; border-radius:5px; box-shadow:0 4px 20px rgba(0,0,0,0.3); width:700px; max-height:80vh; overflow-y:auto; }
@@ -412,6 +463,174 @@ function renderIfPagination() {
});
}
+// 탭 전환 기능
+function switchTab(tabName) {
+ $('.detail-tab').removeClass('active');
+ $('.detail-tab[data-tab="' + tabName + '"]').addClass('active');
+ $('.tab-content').removeClass('active');
+ $('#tab' + tabName.charAt(0).toUpperCase() + tabName.slice(1) + 'Content').addClass('active');
+
+ if (tabName === 'status' && isDetail) {
+ loadBucketStatus();
+ }
+}
+
+// 버킷 현황 조회
+function loadBucketStatus() {
+ var groupId = $('input[name=groupId]').val();
+ if (!groupId) return;
+
+ $('#bucketServerList').html('
hourglass_empty조회 중...
');
+
+ $.ajax({
+ type: 'POST',
+ url: url,
+ data: { cmd: 'LIST_BUCKET_STATUS', groupId: groupId },
+ dataType: 'json',
+ success: function(result) {
+ renderBucketStatus(result.servers || []);
+ $('#lastRefreshTime').text(formatDateTime(new Date()));
+ },
+ error: function(e) {
+ $('#bucketServerList').html('error_outline조회 실패: ' + e.statusText + '
');
+ }
+ });
+}
+
+function formatDateTime(date) {
+ var h = date.getHours();
+ var m = date.getMinutes();
+ var s = date.getSeconds();
+ return (h < 10 ? '0' + h : h) + ':' + (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
+}
+
+function renderBucketStatus(servers) {
+ var container = $('#bucketServerList');
+ container.empty();
+
+ if (servers.length === 0) {
+ container.html('dns등록된 서버가 없습니다
');
+ $('#bucketSummary').hide();
+ return;
+ }
+
+ // 전체 합산 계산
+ var onlineCount = 0;
+ var perSecondCapacity = 0;
+ var thresholdCapacity = 0;
+ var thresholdTimeUnit = '';
+
+ $.each(servers, function(i, server) {
+ if (server.status === 'online' && server.data && server.data.buckets) {
+ onlineCount++;
+ $.each(server.data.buckets, function(j, bucket) {
+ if (bucket.type === 'perSecond') {
+ perSecondCapacity += bucket.capacity || 0;
+ } else if (bucket.type === 'threshold') {
+ thresholdCapacity += bucket.capacity || 0;
+ if (!thresholdTimeUnit) thresholdTimeUnit = bucket.timeUnit;
+ }
+ });
+ }
+ });
+
+ // 요약 영역 표시
+ if (onlineCount > 0) {
+ $('#summaryInstanceCount').html(onlineCount + '개');
+
+ var configPerSecond = parseInt($('input[name=thresholdPerSecond]').val()) || 0;
+ var configThreshold = parseInt($('input[name=threshold]').val()) || 0;
+
+ if (configPerSecond > 0) {
+ $('#summaryPerSecond').html(formatNumber(perSecondCapacity) + 'req/sec');
+ $('#summaryPerSecondCalc').text(formatNumber(configPerSecond) + ' × ' + onlineCount + ' 인스턴스');
+ $('#summaryPerSecondWrap').show();
+ } else {
+ $('#summaryPerSecondWrap').hide();
+ }
+
+ if (configThreshold > 0) {
+ var timeUnitLabel = getTimeUnitText(thresholdTimeUnit);
+ $('#summaryThreshold').html(formatNumber(thresholdCapacity) + 'req/' + timeUnitLabel + '');
+ $('#summaryThresholdCalc').text(formatNumber(configThreshold) + ' × ' + onlineCount + ' 인스턴스');
+ $('#summaryThresholdWrap').show();
+ } else {
+ $('#summaryThresholdWrap').hide();
+ }
+
+ $('#bucketSummary').show();
+ } else {
+ $('#bucketSummary').hide();
+ }
+
+ $.each(servers, function(i, server) {
+ var statusClass = server.status || 'offline';
+ var statusText = { online: '정상', offline: '오프라인', no_data: '데이터 없음' };
+
+ var card = '' +
+ '' +
+ '
' + renderBucketBody(server) + '
' +
+ '
';
+ container.append(card);
+ });
+}
+
+function renderBucketBody(server) {
+ if (server.status === 'offline') {
+ return '' + (server.message || '서버 연결 실패') + '
';
+ }
+ if (server.status === 'no_data') {
+ return 'info_outline ' + (server.message || '그룹이 로드되지 않음') + '
';
+ }
+
+ var data = server.data;
+ if (!data || !data.buckets || data.buckets.length === 0) {
+ return '버킷 정보 없음
';
+ }
+
+ var html = '';
+ $.each(data.buckets, function(j, bucket) {
+ var typeLabel = bucket.type === 'perSecond' ? '초당 임계치' : '추가 임계치';
+ var timeUnitText = getTimeUnitText(bucket.timeUnit);
+ var usagePercent = bucket.usagePercent || 0;
+ var progressClass = usagePercent < 50 ? 'low' : (usagePercent < 80 ? 'medium' : 'high');
+
+ html += '
' +
+ '' +
+ '
' +
+ '
' +
+ '사용량: ' + usagePercent.toFixed(1) + '%' +
+ '잔여: ' + formatNumber(bucket.availableTokens) + ' / ' + formatNumber(bucket.capacity) + '' +
+ '
' +
+ '
';
+ });
+ html += '
';
+ return html;
+}
+
+function getTimeUnitText(timeUnit) {
+ var units = { SEC: '초', MIN: '분', HOU: '시간', DAY: '일', MON: '월' };
+ return units[timeUnit] || timeUnit || '-';
+}
+
+function formatNumber(num) {
+ if (num === undefined || num === null) return '-';
+ return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
+}
+
function selectInterface() {
if (!ifModalState.selectedItem) {
alert('API를 선택하여 주십시오.');
@@ -520,10 +739,7 @@ $(document).ready(function() {
success: function(args) {
showAlert("<%= localeMessage.getString("common.saveMsg") %>", {
type: 'success',
- title: '저장 완료',
- onClose: function() {
- goNav(returnUrl);
- }
+ title: '저장 완료'
});
},
error: function(e) {
@@ -611,6 +827,22 @@ $(document).ready(function() {
buttonControl(isDetail);
titleControl(isDetail);
+ // 탭 전환 이벤트
+ $('.detail-tab').click(function() {
+ var tabName = $(this).data('tab');
+ switchTab(tabName);
+ });
+
+ // 현황 탭 표시 (상세 조회 시에만)
+ if (isDetail) {
+ $('#tabStatus').show();
+ }
+
+ // 버킷 새로고침 버튼
+ $('#btn_refresh_bucket').click(function() {
+ loadBucketStatus();
+ });
+
// 도움말 모달
$("#btn_help").click(function() {
$("#helpModal").fadeIn(200);
@@ -650,6 +882,18 @@ $(document).ready(function() {
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 마지막 갱신: -
+
+
+
+
+
+
functions 전체 인스턴스 합산
+
+
+ 활성 인스턴스
+ 0개
+
+
+ 초당 임계치 (전체)
+ 0req/sec
+
+
+
+ 추가 임계치 (전체)
+ 0req
+
+
+
+
+
+
+
+ hourglass_empty
+ 버킷 현황을 조회하려면 새로고침 버튼을 클릭하세요
+
+
+
+
diff --git a/src/main/java/com/eactive/eai/rms/onl/manage/inflow/group/InflowGroupControlManController.java b/src/main/java/com/eactive/eai/rms/onl/manage/inflow/group/InflowGroupControlManController.java
index a14f5ea..ab4fe96 100644
--- a/src/main/java/com/eactive/eai/rms/onl/manage/inflow/group/InflowGroupControlManController.java
+++ b/src/main/java/com/eactive/eai/rms/onl/manage/inflow/group/InflowGroupControlManController.java
@@ -163,6 +163,20 @@ public class InflowGroupControlManController extends OnlBaseAnnotationController
return new ModelAndView("jsonView", resultMap);
}
+ /**
+ * 그룹 버킷 상태 조회 (GW 서버별 현황)
+ */
+ @RequestMapping(value = "/onl/admin/inflow/inflowGroupControlMan.json", params = "cmd=LIST_BUCKET_STATUS")
+ public ModelAndView getBucketStatus(HttpServletRequest request, HttpServletResponse response,
+ @RequestParam("groupId") String groupId) {
+ List