TCP 서버 모드 추가

This commit is contained in:
현성필
2025-01-21 13:19:21 +09:00
parent e23684d833
commit cb9201ddd2
106 changed files with 17710 additions and 0 deletions
@@ -0,0 +1,128 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/default_layout}">
<body>
<section layout:fragment="contentFragment">
<script th:src="@{/plugins/vs/loader.js}"></script>
<script>
require.config({paths: {'vs': '[[@{/plugins/vs}]]'}});
</script>
<div class="container-fluid mt-2">
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" style="position: absolute; top: 0; left: 0;">
<div class="toast-header">
<strong class="mr-auto">Alert</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body">
</div>
</div>
<form name="nettyServerForm" id="nettyServerForm"
th:action="${nettyServer.id == null}?@{/mgmt/socket/register.do}:@{/mgmt/socket/update.do}"
th:object='${nettyServer}' method="post">
<div class="card col-md">
<div class="card-header">
<h3 th:if="${nettyServer.id == null}">Create Netty Server</h3>
<h3 th:if="${nettyServer.id != null}">Edit Netty Server</h3>
</div>
<div class="card-body">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
<input type="hidden" name="id" th:value="${nettyServer.id}">
<!-- Server Name -->
<div class="form-floating mt-2">
<input type="text" class="form-control"
th:classappend="${#fields.hasErrors('name')}? 'is-invalid'"
th:field="*{name}" required/>
<label for="name" class="form-label must">Server Name</label>
<th:block th:each="err : ${#fields.errors('name')}">
<div th:text="${err}" class="invalid-feedback"></div>
</th:block>
</div>
<!-- Port Number -->
<div class="form-floating mt-2">
<input type="number" class="form-control"
th:classappend="${#fields.hasErrors('port')}? 'is-invalid'"
th:field="*{port}" required
min="1024" max="65535"/>
<label for="port" class="form-label must">Port</label>
<th:block th:each="err : ${#fields.errors('port')}">
<div th:text="${err}" class="invalid-feedback"></div>
</th:block>
</div>
<!-- Active Status -->
<div class="form-check mt-3">
<input type="checkbox" class="form-check-input"
th:field="*{active}" id="active"/>
<label class="form-check-label" for="active">Active</label>
</div>
<!-- Script Editor -->
<div class="card mt-3">
<div class="card-header">Server Script</div>
<div class="card-body">
<div id="script_editor" style="width:100%;height:400px;border:1px solid grey;"></div>
<textarea class="form-control" style="display: none;"
name="script" th:field="*{script}"></textarea>
</div>
</div>
</div>
<div class="card-footer">
<button type="button" class="btn btn-primary btn_list">
<i class="fa-solid fa-list"></i> List
</button>
<button type="button" class="btn btn-primary btn_register">
<i class="fa-sharp fa-solid fa-floppy-disk"></i> Save
</button>
</div>
</div>
</form>
</div>
</section>
</body>
<th:block layout:fragment="contentScript">
<script>
$(function () {
let scriptEditor;
require(['vs/editor/editor.main'], function () {
const defaultScript = `function processJson(message) {
//process message;
return "response_message";
}`;
scriptEditor = monaco.editor.create(document.getElementById('script_editor'), {
value: $('textarea[name="script"]').val() || ($('input[name="id"]').val() ? '' : defaultScript),
theme: 'vs-light',
language: 'javascript',
automaticLayout: true
});
scriptEditor.onDidChangeModelContent(function (e) {
$('textarea[name="script"]').val(scriptEditor.getValue());
});
});
let fnRegister = document.querySelector(".btn_register");
fnRegister.addEventListener("click", function (event) {
let form = document.getElementById("nettyServerForm");
if (!form.checkValidity()) {
event.preventDefault();
event.stopPropagation();
} else {
form.submit();
}
form.classList.add('was-validated');
});
let fnList = document.querySelector(".btn_list");
fnList.addEventListener("click", function () {
location.href = "[[@{/mgmt/socket/list_view.do}]]";
});
});
</script>
</th:block>
</html>
@@ -0,0 +1,247 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/default_layout}">
<body>
<section layout:fragment="contentFragment">
<div class="container-fluid">
<div class="card mt-4">
<div class="card-header">
<h5><span style="font-weight: bold">Netty Server List</span></h5>
</div>
<div class="card-body">
<div class="d-flex justify-content-between">
<form role="form" class="row g-3" name="searchForm" th:action="@{/mgmt/socket/list_view.do}" method="get" th:object="${modelSearch}">
<input name="page" type="hidden" value="1"/>
<div class="col-auto">
<input class="form-control" name="name" placeholder="Server Name" type="text" size="35" th:title="#{title.search}+ '' + #{input.input}"
th:value="${modelSearch.name}"
maxlength="255"/>
</div>
<div class="col-auto">
<input class="form-control" name="port" placeholder="Port" type="number" size="10" th:title="#{title.search}+ '' + #{input.input}"
th:value="${modelSearch.port}"
min="1024" max="65535"/>
</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>
</div>
<div class="col-auto">
<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>
</div>
</form>
</div>
</div>
</div>
<div class="card mt-4">
<div class="card-body">
<form name="listForm">
<input name="id" type="hidden"/>
<input name="checkedIdForDel" type="hidden"/>
<input id="csrf" type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
<table class="table table-hover pt-table">
<colgroup>
<col style="width: 5%;">
<col style="width: 5%;">
<col style="width: 20%;">
<col style="width: 15%;">
<col style="width: 15%;">
<col style="width: 20%;">
<col style="width: 20%;">
</colgroup>
<thead>
<tr>
<th><div class="text-center">No.</div></th>
<th>
<div class="text-center">
<input type="checkbox" name="checkAll" class="form-check-input" onclick="fncCheckAll()" th:title="#{input.selectAll.title}">
</div>
</th>
<th><div class="text-center">Name</div></th>
<th><div class="text-center">Port</div></th>
<th><div class="text-center">Status</div></th>
<th><div class="text-center">Last Modified</div></th>
<th><div class="text-center">Actions</div></th>
</tr>
</thead>
<tbody>
<tr th:each="row, status : ${page.content}">
<td class="text-center" th:text="${page.getNumber() * page.getSize() + status.count}"></td>
<td>
<div class="d-flex justify-content-center">
<input type="checkbox" name="checkField" class="form-check-input" title="Select"/>
<input name="checkId" type="hidden" th:value="${row.id}"/>
</div>
</td>
<td>
<span th:text="${row.name}"></span>
</td>
<td class="text-center">
<span th:text="${row.port}"></span>
</td>
<td class="text-center">
<span th:class="${row.active ? 'badge bg-success' : 'badge bg-danger'}"
th:text="${row.active ? 'Active' : 'Inactive'}"></span>
</td>
<td class="text-center">
<span th:text="${#temporals.format(row.lastModified, 'yyyy-MM-dd HH:mm:ss')}"></span>
</td>
<td>
<div class="d-flex justify-content-center">
<button type="button" class="btn btn-primary btn-sm me-2"
th:onclick="fnStartServer([[${row.id}]])"
th:disabled="${!row.active}">
<i class="fa-solid fa-play"></i> Start
</button>
<button type="button" class="btn btn-danger btn-sm me-2"
th:onclick="fnStopServer([[${row.id}]])">
<i class="fa-solid fa-stop"></i> Stop
</button>
<button type="button" class="btn btn-secondary btn-sm me-2"
th:onclick="fnSelectItem([[${row.id}]])"
th:value="#{button.update}">
<i class="fa-sharp fa-solid fa-edit"></i> Edit
</button>
</div>
</td>
</tr>
<tr th:if="${page.content.size() == 0}">
<td colspan="7" th:text="#{common.nodata.msg}"></td>
</tr>
</tbody>
</table>
</form>
</div>
<div class="card-footer">
<div th:replace="~{fragment/pagination :: pagination(jsFunction='fn_select_page')}"></div>
</div>
</div>
</div>
</section>
</body>
<th:block layout:fragment="contentScript">
<script>
function fn_select_page(pageNo) {
document.searchForm.page.value = pageNo;
document.searchForm.submit();
}
function fnSelectItem(id) {
location.href = "[[@{/mgmt/socket/update_view.do}]]" + "?id=" + id;
}
function fnStartServer(id) {
if (!confirm('Start the server?')) return;
const csrfToken = document.getElementById('csrf').value;
const csrfParameter = document.getElementById('csrf').name;
const formData = new FormData();
formData.append(csrfParameter, csrfToken);
fetch(`[[@{/mgmt/socket/servers/}]]${id}/start`, {
method: 'POST',
headers: {
[csrfParameter]: csrfToken
},
body: formData,
credentials: 'same-origin'
}).then(response => {
if (response.ok) {
location.reload();
} else {
alert('Failed to start server');
}
}).catch(error => {
console.error('Error:', error);
alert('Failed to start server');
});
}
function fnStopServer(id) {
if (!confirm('Stop the server?')) return;
const csrfToken = document.getElementById('csrf').value;
const csrfParameter = document.getElementById('csrf').name;
const formData = new FormData();
formData.append(csrfParameter, csrfToken);
fetch(`[[@{/mgmt/socket/servers/}]]${id}/stop`, {
method: 'POST',
headers: {
[csrfParameter]: csrfToken
},
body: formData,
credentials: 'same-origin'
}).then(response => {
if (response.ok) {
location.reload();
} else {
alert('Failed to stop server');
}
}).catch(error => {
console.error('Error:', error);
alert('Failed to stop server');
});
}
$(function () {
let fnSearch = document.querySelector(".btn_search");
let fnDelete = document.querySelector(".btn_delete");
let fnAdd = document.querySelector(".btn_add");
fnSearch.addEventListener("click", function () {
document.searchForm.page.value = 1;
document.searchForm.submit();
});
fnDelete.addEventListener("click", function () {
var checkField = document.listForm.checkField;
var id = document.listForm.checkId;
var checkedIds = "";
var checkedCount = 0;
if (checkField) {
if (checkField.length > 1) {
for (var i = 0; i < checkField.length; i++) {
if (checkField[i].checked) {
checkedIds += ((checkedCount == 0 ? "" : ",") + id[i].value);
checkedCount++;
}
}
} else {
if (checkField.checked) {
checkedIds = id.value;
}
}
}
if (checkedIds.length > 0) {
if (confirm("[[#{common.delete.msg}]]")) {
document.listForm.checkedIdForDel.value = checkedIds;
document.listForm.method = 'POST';
document.listForm.action = '[[@{/mgmt/socket/delete.do}]]';
document.listForm.submit();
}
}
});
fnAdd.addEventListener("click", function () {
location.href = '[[@{/mgmt/socket/create_view.do}]]';
});
});
</script>
</th:block>
</html>