Netty 서버 상태 추적 및 목업 라우트 관리 기능 개선

This commit is contained in:
Rinjae
2026-02-24 17:37:05 +09:00
parent 9d54f81c0e
commit 51a8cb5327
10 changed files with 260 additions and 36 deletions
@@ -14,25 +14,66 @@
</div>
<div class="card-body">
<div class="d-flex justify-content-between">
<form role="form" class="row g-3" name="searchForm" th:action="@{/mgmt/routes/list_view.do}" method="get" th:object="${modelSearch}">
<form role="form" class="row g-3 align-items-center" name="searchForm" th:action="@{/mgmt/routes/list_view.do}" method="get" th:object="${modelSearch}">
<input name="page" type="hidden" value="1"/>
<div class="col-auto">
<select class="form-select" name="group" th:value="${modelSearch.group}">
<option value="" th:selected="${#strings.isEmpty(modelSearch.group)}">선택</option>
<input name="size" type="hidden" th:value="${pageable.pageSize}"/>
<input name="sort" type="hidden" th:value="${#strings.defaultString(param.sort, 'id,desc')}"/>
<!-- 그룹 목록 반복 -->
<option th:each="group : ${groupList}"
th:value="${group.id}"
th:text="${group.name}"
th:selected="${modelSearch.group} == ${group.id}">
</option>
</select>
<!-- Group 복수 선택 드롭다운 -->
<div class="col-auto">
<div class="dropdown multi-select-dropdown">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" data-bs-auto-close="outside" id="groupDropdownBtn">
Group
</button>
<ul class="dropdown-menu p-2" style="min-width: 200px;">
<li th:each="group : ${groupList}">
<label class="dropdown-item d-flex align-items-center" style="cursor: pointer;">
<input type="checkbox" class="form-check-input me-2 group-check"
th:value="${group.id}"
th:checked="${modelSearch.group != null AND #lists.contains(modelSearch.group, '' + group.id)}"/>
<span th:text="${group.name}"></span>
</label>
</li>
</ul>
</div>
</div>
<!-- Method 복수 선택 드롭다운 -->
<div class="col-auto">
<div class="dropdown multi-select-dropdown">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" data-bs-auto-close="outside" id="methodDropdownBtn">
Method
</button>
<ul class="dropdown-menu p-2" style="min-width: 180px;">
<li th:each="m : ${methodList}">
<label class="dropdown-item d-flex align-items-center" style="cursor: pointer;">
<input type="checkbox" class="form-check-input me-2 method-check"
th:value="${m}"
th:checked="${modelSearch.method != null AND #lists.contains(modelSearch.method, m)}"/>
<span th:text="${m}"></span>
</label>
</li>
</ul>
</div>
</div>
<div class="col-auto">
<input class="form-control" name="name" placeholder="이름" type="text" size="35" th:title="#{title.search}+ '' + #{input.input}"
th:value="${modelSearch.name}"
maxlength="255"/>
</div>
<!-- 정렬 드롭다운 -->
<div class="col-auto">
<select class="form-select" id="sortSelect">
<option value="id,desc" th:selected="${#strings.defaultString(param.sort, 'id,desc') == 'id,desc'}">최신순</option>
<option value="group,asc" th:selected="${param.sort == 'group,asc'}">Group</option>
<option value="method,asc" th:selected="${param.sort == 'method,asc'}">Method</option>
<option value="pathPattern,asc" th:selected="${param.sort == 'pathPattern,asc'}">Path</option>
<option value="name,asc" th:selected="${param.sort == 'name,asc'}">이름</option>
</select>
</div>
<div class="col-auto">
<button type="button" class="btn btn-primary btn_search" th:value="#{button.inquire}"><i class="fa-sharp fa-solid fa-magnifying-glass"></i> [[#{title.inquire}]]
</button>
@@ -41,10 +82,10 @@
<button type="button" class="btn btn-primary btn_delete"><i class="fa-sharp fa-solid fa-trash"></i> [[#{title.delete}]]</button>
</div>
<div class="col-auto">
<button type="button" class="btn btn-primary btn_add"><i class="fa-sharp fa-solid fa-plus"></i> [[#{button.create}]]</button><!-- 등록 -->
<button type="button" class="btn btn-primary btn_add"><i class="fa-sharp fa-solid fa-plus"></i> [[#{button.create}]]</button>
</div>
<div class="col-auto">
<button type="button" class="btn btn-primary btn_add_group"><i class="fa-sharp fa-solid fa-plus"></i>그룹 관리</button><!-- 등록 -->
<button type="button" class="btn btn-primary btn_add_group"><i class="fa-sharp fa-solid fa-plus"></i>그룹 관리</button>
</div>
</form>
</div>
@@ -123,7 +164,7 @@
</tr>
<!-- 글이 없는 경우 -->
<tr th:if="${page.content.size() == 0 }">
<td colspan="6" th:text="#{common.nodata.msg}"></td>
<td colspan="7" th:text="#{common.nodata.msg}"></td>
</tr>
</tbody>
</table>
@@ -133,7 +174,17 @@
</form>
</div>
<div class="card-footer">
<div th:replace="~{fragment/pagination :: pagination(jsFunction='fn_select_page')}"></div>
<div class="d-flex justify-content-between align-items-center">
<div th:replace="~{fragment/pagination :: pagination(jsFunction='fn_select_page')}"></div>
<div class="d-flex align-items-center">
<label class="me-2" style="white-space: nowrap;">페이지당:</label>
<select class="form-select form-select-sm" id="pageSizeSelect" style="width: auto;">
<option value="20" th:selected="${pageable.pageSize == 20}">20</option>
<option value="50" th:selected="${pageable.pageSize == 50}">50</option>
<option value="100" th:selected="${pageable.pageSize == 100}">100</option>
</select>
</div>
</div>
</div>
</div>
</div>
@@ -145,9 +196,12 @@
<th:block layout:fragment="contentScript">
<script>
function fn_select_page(pageNo) {
function fn_select_page(pageNo, size) {
document.searchForm.page.value = pageNo;
document.searchForm.submit();
if (size) {
document.searchForm.size.value = size;
}
syncFiltersAndSubmit();
}
function fnSelectItem(id) {
@@ -159,17 +213,91 @@
document.cloneForm.submit();
}
$(function () {
let fnSearch = document.querySelector(".btn_search");
let fnDelete = document.querySelector(".btn_delete");
let fnAdd = document.querySelector(".btn_add");
let fnAddGroup = document.querySelector(".btn_add_group");
function updateDropdownLabel(checkClass, btnId, defaultLabel) {
var checks = document.querySelectorAll('.' + checkClass + ':checked');
var btn = document.getElementById(btnId);
if (checks.length > 0) {
btn.textContent = defaultLabel + ' (' + checks.length + ')';
} else {
btn.textContent = defaultLabel;
}
}
fnSearch.addEventListener("click", function () {
document.searchForm.page.value = 1;
document.searchForm.submit();
function syncFiltersAndSubmit() {
var form = document.searchForm;
// 기존 group, method hidden input 제거
var oldGroups = form.querySelectorAll('input[name="group"]');
oldGroups.forEach(function(el) { el.remove(); });
var oldMethods = form.querySelectorAll('input[name="method"]');
oldMethods.forEach(function(el) { el.remove(); });
// 선택된 group 값 추가
var groupChecks = document.querySelectorAll('.group-check:checked');
groupChecks.forEach(function(cb) {
var input = document.createElement('input');
input.type = 'hidden';
input.name = 'group';
input.value = cb.value;
form.appendChild(input);
});
// 선택된 method 값 추가
var methodChecks = document.querySelectorAll('.method-check:checked');
methodChecks.forEach(function(cb) {
var input = document.createElement('input');
input.type = 'hidden';
input.name = 'method';
input.value = cb.value;
form.appendChild(input);
});
// sort 값 동기화
form.sort.value = document.getElementById('sortSelect').value;
form.submit();
}
$(function () {
var fnSearch = document.querySelector(".btn_search");
var fnDelete = document.querySelector(".btn_delete");
var fnAdd = document.querySelector(".btn_add");
var fnAddGroup = document.querySelector(".btn_add_group");
// localStorage에서 페이지 크기 복원
var savedPageSize = localStorage.getItem('mockRouteListPageSize');
if (savedPageSize) {
var pageSizeSelect = document.getElementById('pageSizeSelect');
pageSizeSelect.value = savedPageSize;
// 현재 서버 페이지 크기와 다르면 반영
if (document.searchForm.size.value !== savedPageSize) {
document.searchForm.size.value = savedPageSize;
}
}
// 드롭다운 라벨 초기화
updateDropdownLabel('group-check', 'groupDropdownBtn', 'Group');
updateDropdownLabel('method-check', 'methodDropdownBtn', 'Method');
// 체크박스 변경 시 라벨 업데이트
document.querySelectorAll('.group-check').forEach(function(cb) {
cb.addEventListener('change', function() {
updateDropdownLabel('group-check', 'groupDropdownBtn', 'Group');
});
});
document.querySelectorAll('.method-check').forEach(function(cb) {
cb.addEventListener('change', function() {
updateDropdownLabel('method-check', 'methodDropdownBtn', 'Method');
});
});
// 조회 버튼
fnSearch.addEventListener("click", function () {
document.searchForm.page.value = 1;
syncFiltersAndSubmit();
});
// 삭제 버튼
fnDelete.addEventListener("click", function () {
var checkField = document.listForm.checkField;
var id = document.listForm.checkId;
@@ -206,6 +334,21 @@
fnAddGroup.addEventListener("click", function () {
location.href = '[[@{/mgmt/routes/group/list_view.do}]]';
});
// 정렬 변경 시 자동 submit
document.getElementById('sortSelect').addEventListener('change', function() {
document.searchForm.page.value = 1;
syncFiltersAndSubmit();
});
// 페이지 크기 변경
document.getElementById('pageSizeSelect').addEventListener('change', function() {
var newSize = this.value;
localStorage.setItem('mockRouteListPageSize', newSize);
document.searchForm.size.value = newSize;
document.searchForm.page.value = 1;
syncFiltersAndSubmit();
});
})
</script>
@@ -59,6 +59,46 @@
<label class="form-check-label" for="active">Active</label>
</div>
<!-- Message Format Settings -->
<div class="card mt-3">
<div class="card-header">Message Format</div>
<div class="card-body">
<div class="row">
<div class="col-md-3">
<div class="form-floating">
<input type="number" class="form-control"
th:field="*{prefixSize}" min="0" max="20"/>
<label for="prefixSize" class="form-label">Prefix Size</label>
</div>
</div>
<div class="col-md-3">
<div class="form-check mt-3">
<input type="checkbox" class="form-check-input"
th:field="*{includePrefixLength}" id="includePrefixLength"/>
<label class="form-check-label" for="includePrefixLength">Include Prefix in Length</label>
</div>
</div>
<div class="col-md-3">
<div class="form-floating">
<input type="number" class="form-control"
th:field="*{serviceIdLength}" min="0" max="100"/>
<label for="serviceIdLength" class="form-label">Service ID Length</label>
</div>
</div>
<div class="col-md-3">
<div class="form-floating">
<select class="form-select" th:field="*{serviceIdPosition}">
<option value="NONE">None</option>
<option value="START">Start</option>
<option value="END">End</option>
</select>
<label for="serviceIdPosition" class="form-label">Service ID Position</label>
</div>
</div>
</div>
</div>
</div>
<!-- Script Editor -->
<div class="card mt-3">
<div class="card-header">Server Script</div>
@@ -92,6 +92,8 @@
<td class="text-center">
<span th:class="${row.active ? 'badge bg-success' : 'badge bg-danger'}"
th:text="${row.active ? 'Active' : 'Inactive'}"></span>
<span th:if="${runningStatus[row.port]}" class="badge bg-primary">Started</span>
<span th:unless="${runningStatus[row.port]}" class="badge bg-secondary">Stopped</span>
</td>
<td class="text-center">
<span th:text="${#temporals.format(row.lastModified, 'yyyy-MM-dd HH:mm:ss')}"></span>