Socket Client UI 기본 골격 작성

This commit is contained in:
현성필
2024-04-17 22:03:02 +09:00
parent 95924af31a
commit 7d97628d38
27 changed files with 2244 additions and 570 deletions
+55 -29
View File
@@ -28,35 +28,6 @@ class APIClient {
return this.contextPath + this.CLIENT_URL; return this.contextPath + this.CLIENT_URL;
} }
async sendAPIRequest(model, preProcessCallback, consoleOutput) {
try {
let variables = {};
const updatedModel = await this.executePreRequestScript(model, variables, consoleOutput);
let processedRequest = this.replacePlaceholdersWithVariables(updatedModel, variables);
if (preProcessCallback) {
preProcessCallback(processedRequest);
}
const response = await this.makeAjaxRequest(processedRequest, model, consoleOutput);
const responseData = await response.json(); // Parse the JSON response
if (!response.ok) {
const errorMessage = responseData.error || 'Unknown error';
throw new Error(`API 호출 중 오류가 발생했습니다. 사유[${errorMessage}]`);
}
const finalResponse = this.formatResponse(responseData);
return this.executePostRequestScript(model.postRequestScript, variables, processedRequest, finalResponse, consoleOutput).catch(error => {
$('.toast-body').text(error);
$('.toast').toast('show');
return finalResponse; // Return the original response even if there's an error in the post-request script
});
} catch (error) {
throw error;
}
}
formatResponse(data) { formatResponse(data) {
// Format response as needed before post-request script // Format response as needed before post-request script
let response = {}; let response = {};
@@ -99,6 +70,61 @@ class APIClient {
} }
} }
async processPreScriptAndVariables(model, value, consoleOutput){
let variables = {};
const updatedModel = await this.executePreRequestScript(model, variables, consoleOutput);
return this.replacePlaceholderValueWithVariables(updatedModel, value, variables);
}
replacePlaceholderValueWithVariables(model, originalValue, variables) {
let mergedVariables = {};
_.forEach(window.globals, (value, key) => {
mergedVariables[key] = value;
});
if (variables) {
_.forEach(variables, (value, key) => {
mergedVariables[key] = value;
});
}
_.forEach(mergedVariables, (value, key) => {
const placeholder = `{{${key}}}`;
// Replace in path
originalValue = originalValue.replace(new RegExp(placeholder, 'g'), value);
});
return originalValue;
}
async sendAPIRequest(model, preProcessCallback, consoleOutput) {
try {
let variables = {};
const updatedModel = await this.executePreRequestScript(model, variables, consoleOutput);
let processedRequest = this.replacePlaceholdersWithVariables(updatedModel, variables);
if (preProcessCallback) {
preProcessCallback(processedRequest);
}
const response = await this.makeAjaxRequest(processedRequest, model, consoleOutput);
const responseData = await response.json(); // Parse the JSON response
if (!response.ok) {
const errorMessage = responseData.error || 'Unknown error';
throw new Error(`API 호출 중 오류가 발생했습니다. 사유[${errorMessage}]`);
}
const finalResponse = this.formatResponse(responseData);
return this.executePostRequestScript(model.postRequestScript, variables, processedRequest, finalResponse, consoleOutput).catch(error => {
$('.toast-body').text(error);
$('.toast').toast('show');
return finalResponse; // Return the original response even if there's an error in the post-request script
});
} catch (error) {
throw error;
}
}
replacePlaceholdersWithVariables(original, variables) { replacePlaceholdersWithVariables(original, variables) {
let request = JSON.parse(JSON.stringify(original)); let request = JSON.parse(JSON.stringify(original));
+74 -499
View File
@@ -1,9 +1,10 @@
import * as monaco from 'monaco-editor'; import * as monaco from 'monaco-editor';
import APIClient from './APIClient'; import APIClient from './APIClient';
import EditableInput from './components/EditableInput';
import {createPopper} from '@popperjs/core/lib/popper-lite'; import {createPopper} from '@popperjs/core/lib/popper-lite';
import PropertyTable from './components/PropertyTable';
import VariableSnippet from './components/VariableSnippet'; import VariableSnippet from './components/VariableSnippet';
import HTTPClientView from './components/HTTPClientView';
import TCPClientView from './components/TCPClientView';
import ServerManager from './components/ServerManager';
class APITester { class APITester {
@@ -25,6 +26,7 @@ class APITester {
this.popperContent = document.getElementById('popperContent'); this.popperContent = document.getElementById('popperContent');
this.popperInstance = null; this.popperInstance = null;
this.contextPath = contextPath || '/'; this.contextPath = contextPath || '/';
} }
getCollectionListURL() { getCollectionListURL() {
@@ -51,281 +53,6 @@ class APITester {
return this.contextPath + `mgmt/collections/${collectionId}/apis/delete.do`; return this.contextPath + `mgmt/collections/${collectionId}/apis/delete.do`;
} }
renderServers(selectedServer) {
const optionsHtml = this.servers.map(server => `<option value="${server.id}" ${selectedServer === server.id ? 'selected' : ''} data-path="${server.basePath}">${server.name}</option>`).join('');
return `<select class="form-select server" name="server" style="width: 180px; border-radius: 0;" required>
<option value="">서버 선택</option>
${optionsHtml}</select>`;
}
basePath(serverId) {
if (serverId === undefined || serverId === null || serverId === '') {
return '';
}
return this.servers.filter(server => server.id == serverId)[0].basePath; // == : 타입 비교 없이 값만 비교
}
httpApiRequestTemplate(apiRequest, index) {
return `
<div class="tab-pane fade mt-1 api_request" id="api_request_${apiRequest.id}" style="height: 100%;">
<div><span class="collection_name">${apiRequest.collectionName}</span> &gt; <span class="api_name">${apiRequest.name}</span></div>
<div class="d-flex api_request_info">
<div class="form-floating">
<select name="method" class="form-select method" style="width: 90px; border-bottom-right-radius: 0;border-top-right-radius: 0;">
<option value="POST" ${apiRequest.method === 'POST' ? 'selected' : ''}>POST</option>
<option value="GET" ${apiRequest.method === 'GET' ? 'selected' : ''}>GET</option>
<option value="PUT" ${apiRequest.method === 'PUT' ? 'selected' : ''}>PUT</option>
<option value="DELETE" ${apiRequest.method === 'DELETE' ? 'selected' : ''}>DELETE</option>
<option value="CONNECT" ${apiRequest.method === 'CONNECT' ? 'selected' : ''}>CONNECT</option>
<option value="HEAD" ${apiRequest.method === 'HEAD' ? 'selected' : ''}>HEAD</option>
<option value="OPTIONS" ${apiRequest.method === 'OPTIONS' ? 'selected' : ''}>OPTIONS</option>
<option value="TRACE" ${apiRequest.method === 'TRACE' ? 'selected' : ''}>TRACE</option>
<option value="PATCH" ${apiRequest.method === 'PATCH' ? 'selected' : ''}>PATCH</option>
</select>
<label class="form-label must">Method</label>
</div>
<div class="form-floating">
${this.renderServers(apiRequest.server)}
<label class="form-label must">Server</label>
</div>
<div class="form-floating">
<input type="text" name="basePath" class="form-control" style="width: 180px; border-radius: 0" value="${this.basePath(apiRequest.server)}" readonly>
<label class="must">기본 경로</label>
</div>
<div class="form-floating editableInput me-1 path" data-name="path" data-label="Path">
<label>Path</label>
</div>
<button class="btn btn-primary send me-1">Send</button>
<button class="btn btn-primary save" data-index="${index}" style="height: 58px;">저장</button>
</div>
<div class="api_request d-flex flex-column" style="height:100%;">
<div class="api_request_body">
<ul class="nav nav-tabs mt-1" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link btn_request_body_tab" data-bs-toggle="tab"
type="button" role="tab" aria-controls="requestBody" data-bs-target="#requestBodyTab_${apiRequest.id}"
aria-selected="false">Body
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link btn_request_param_tab" data-bs-toggle="tab"
type="button" role="tab" aria-controls="paramTab" data-bs-target="#requestParamTab_${apiRequest.id}"
aria-selected="true">Param
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link btn_request_header_tab" data-bs-toggle="tab"
type="button" role="tab" aria-controls="requestHeader" data-bs-target="#requestHeaderTab_${apiRequest.id}"
aria-selected="false">Header
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link btn_prerequest_tab" data-bs-toggle="tab"
type="button" role="tab" aria-controls="preScrit" data-bs-target="#preRequestTab_${apiRequest.id}"
aria-selected="false">Pre-Request
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link btn_postrequest_tab" data-bs-toggle="tab"
type="button" role="tab" aria-controls="postScript" data-bs-target="#postRequestTab_${apiRequest.id}"
aria-selected="false">Post-Request
</button>
</li>
</ul>
<div class="tab-content" style="height: 100%;">
<div class="tab-pane fade show request_param_tab" role="tabpanel" id="requestParamTab_${apiRequest.id}" aria-labelledby="requestParams-tab">
<div class="query_params">
</div>
</div>
<div class="tab-pane fade request_header_tab" role="tabpanel" id="requestHeaderTab_${apiRequest.id}"
aria-labelledby="requestHeader-tab">
<div class="request_headers">
</div>
</div>
<div class="tab-pane fade request_body_tab" role="tabpanel" id="requestBodyTab_${apiRequest.id}" aria-labelledby="requestBody-tab">
<div class="d-flex flex-column">
<select class="form-select mt-1 request_body_type" name="request_type">
<option value="json">json</option>
<option value="xml">xml</option>
<option value="">text/plain</option>
</select>
<div class="request_body mt-1" style="width:100%;height: 300px; border:1px solid grey;"></div>
</div>
</div>
<div class="tab-pane fade pre_script_tab" role="tabpanel" id="preRequestTab_${apiRequest.id}" aria-labelledby="preScrit-tab">
<div class="d-flex flex-row">
<div style="width:calc(100% - 200px);">
<div class="pre-request mt-1" style="height:300px;border:1px solid grey;"></div>
</div>
<div style="width: 200px;">
<div class="pre-request-variable">
</div>
</div>
</div>
</div>
<div class="tab-pane fade post_script_tab" role="tabpanel" id="postRequestTab_${apiRequest.id}"
aria-labelledby="postScrit-tab">
<div class="d-flex flex-row">
<div style="width:calc(100% - 200px);">
<div class="post-request mt-1" style="height:300px;border:1px solid grey;"></div>
</div>
<div style="width: 200px;">
<div class="post-request-variable">
</div>
</div>
</div>
</div>
</div>
</div>
<div class="api_response" style="flex: 1">
<div class="mt-1">
<span>Response</span>
<span class="response_status"></span>
<span class="response_time"></span>
<span class="response_size"></span>
</div>
<ul class="nav nav-tabs mt-1" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#responseBodyTab_${apiRequest.id}" type="button" role="tab" aria-controls="responseBody" aria-selected="false">Body</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#responseHeaderTab_${apiRequest.id}" type="button" role="tab" aria-controls="responseHeader" aria-selected="false">Header</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#consoleTab_${apiRequest.id}" type="button" role="tab" aria-controls="consoleTab" aria-selected="false">Console</button>
</li>
</ul>
<div class="tab-content border-bottom" style="min-height: 250px;">
<div class="tab-pane fade show active" id="responseBodyTab_${apiRequest.id}" role="tabpanel"
aria-labelledby="responseParams-tab">
<div class="response_body mt-1" style="width:100%;height:250px;border:1px solid grey;"></div>
</div>
<div class="tab-pane fade" id="responseHeaderTab_${apiRequest.id}" role="tabpanel"
aria-labelledby="responseParams-tab">
<table class="table">
<thead>
<tr>
<th scope="col">이름</th>
<th scope="col">값</th>
</tr>
</thead>
<tbody class="response_headers">
</tbody>
</table>
</div>
<div class="tab-pane fade" id="consoleTab_${apiRequest.id}" role="tabpanel"
aria-labelledby="console-tab">
<div class="console mt-1" style="width:100%;height:250px;border:1px solid grey;"></div>
</div>
</div>
</div>
</div>
</div>
`;
}
tcpApiRequestTemplate(apiRequest, index) {
return `
<div class="tab-pane fade mt-1 api_request" id="api_request_${apiRequest.id}" style="height: 100%;">
<div><span class="collection_name">${apiRequest.collectionName}</span> &gt; <span class="api_name">${apiRequest.name}</span></div>
<div class="d-flex api_request_info">
<div class="form-floating">
${this.renderServers(apiRequest.server)}
<label class="form-label must">Server</label>
</div>
<button class="btn btn-primary send me-1">Send</button>
<button class="btn btn-primary save" data-index="${index}" style="height: 58px;">저장</button>
</div>
<div class="api_request d-flex flex-column" style="height:100%;">
<div class="api_request_body">
<ul class="nav nav-tabs mt-1" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link btn_request_body_tab" data-bs-toggle="tab"
type="button" role="tab" aria-controls="requestBody" data-bs-target="#requestBodyTab_${apiRequest.id}"
aria-selected="false">Body
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link btn_prerequest_tab" data-bs-toggle="tab"
type="button" role="tab" aria-controls="preScrit" data-bs-target="#preRequestTab_${apiRequest.id}"
aria-selected="false">Pre-Request
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link btn_postrequest_tab" data-bs-toggle="tab"
type="button" role="tab" aria-controls="postScript" data-bs-target="#postRequestTab_${apiRequest.id}"
aria-selected="false">Post-Request
</button>
</li>
</ul>
<div class="tab-content" style="height: 100%;">
<div class="tab-pane fade request_body_tab" role="tabpanel" id="requestBodyTab_${apiRequest.id}" aria-labelledby="requestBody-tab">
<div class="d-flex flex-column">
<select class="form-select mt-1 request_body_type" name="request_type">
<option value="">text/plain</option>
</select>
<div class="request_body mt-1" style="width:100%;height: 300px; border:1px solid grey;"></div>
</div>
</div>
<div class="tab-pane fade pre_script_tab" role="tabpanel" id="preRequestTab_${apiRequest.id}" aria-labelledby="preScrit-tab">
<div class="d-flex flex-row">
<div style="width:calc(100% - 200px);">
<div class="pre-request mt-1" style="height:300px;border:1px solid grey;"></div>
</div>
<div style="width: 200px;">
<div class="pre-request-variable">
</div>
</div>
</div>
</div>
<div class="tab-pane fade post_script_tab" role="tabpanel" id="postRequestTab_${apiRequest.id}"
aria-labelledby="postScrit-tab">
<div class="d-flex flex-row">
<div style="width:calc(100% - 200px);">
<div class="post-request mt-1" style="height:300px;border:1px solid grey;"></div>
</div>
<div style="width: 200px;">
<div class="post-request-variable">
</div>
</div>
</div>
</div>
</div>
</div>
<div class="api_response" style="flex: 1">
<div class="mt-1">
<span>Response</span>
<span class="response_status"></span>
<span class="response_time"></span>
<span class="response_size"></span>
</div>
<ul class="nav nav-tabs mt-1" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#responseBodyTab_${apiRequest.id}" type="button" role="tab" aria-controls="responseBody" aria-selected="false">Body</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#consoleTab_${apiRequest.id}" type="button" role="tab" aria-controls="consoleTab" aria-selected="false">Console</button>
</li>
</ul>
<div class="tab-content border-bottom" style="min-height: 250px;">
<div class="tab-pane fade show active" id="responseBodyTab_${apiRequest.id}" role="tabpanel"
aria-labelledby="responseParams-tab">
<div class="response_body mt-1" style="width:100%;height:250px;border:1px solid grey;"></div>
</div>
<div class="tab-pane fade" id="consoleTab_${apiRequest.id}" role="tabpanel"
aria-labelledby="console-tab">
<div class="console mt-1" style="width:100%;height:250px;border:1px solid grey;"></div>
</div>
</div>
</div>
</div>
</div>
`;
}
headerTemplate(header, index) { headerTemplate(header, index) {
return ` return `
<tr data-index="${index}"> <tr data-index="${index}">
@@ -402,6 +129,7 @@ class APITester {
$('.toast').toast('show'); $('.toast').toast('show');
} }
} }
this.serverManager = new ServerManager(this.servers);
this.bindEvents(); this.bindEvents();
this.loadCollections(); this.loadCollections();
} }
@@ -424,8 +152,7 @@ class APITester {
{selector: '.delete_api_request', action: this.showDeleteApiRequestModal.bind(this)}, {selector: '.delete_api_request', action: this.showDeleteApiRequestModal.bind(this)},
{selector: '.open_api_request', action: this.openApiRequest.bind(this)}, {selector: '.open_api_request', action: this.openApiRequest.bind(this)},
{selector: '.confirm_delete_api', action: this.deleteApiRequest.bind(this)}, {selector: '.confirm_delete_api', action: this.deleteApiRequest.bind(this)},
{selector: '.close_tab', action: this.closeTab.bind(this)} {selector: '.close_tab', action: this.closeTab.bind(this)}];
];
for (let event of events) { for (let event of events) {
$(document).on('click', event.selector, event.action); $(document).on('click', event.selector, event.action);
@@ -601,7 +328,7 @@ class APITester {
const tabIndex = $('#apiRequestTabContent').children().index($(event.currentTarget).closest('.api_request')); const tabIndex = $('#apiRequestTabContent').children().index($(event.currentTarget).closest('.api_request'));
let model = this.apiRequests[tabIndex]; let model = this.apiRequests[tabIndex];
let target = $('#api_request_' + model.id); let target = $('#api_request_' + model.id);
target.find('input[name="basePath"]').val(this.basePath(model.server)); target.find('input[name="basePath"]').val(this.serverManager.basePath(model.server));
} }
renderHeaders(headers) { renderHeaders(headers) {
@@ -633,12 +360,10 @@ class APITester {
placement: 'bottom', // Popper below the element placement: 'bottom', // Popper below the element
modifiers: [ modifiers: [
{ {
name: 'offset', name: 'offset', options: {
options: {
offset: [0, 8] // Adjust the position if needed offset: [0, 8] // Adjust the position if needed
} }
} }]
]
}); });
} }
@@ -655,87 +380,36 @@ class APITester {
let apiRequestTabContent = $(this.apiTabContentTarget); let apiRequestTabContent = $(this.apiTabContentTarget);
let model = this.apiRequests[index]; let model = this.apiRequests[index];
if (model.type ==='HTTP'){
apiRequestTabContent.append(this.httpApiRequestTemplate(model, index));
let editableInput = new EditableInput({
name: 'path',
value: model.path
});
let queryParamTable = new PropertyTable({
propertyName: 'queryParams',
properties: model.queryParams
});
let headerTable = new PropertyTable({
propertyName: 'headers',
properties: model.headers
});
apiRequestTabContent.find(`#api_request_${model.id}`).find('.api_request_info').find('.path').each(function() {
editableInput.init(this, (value) => {
model.path = value;
model.queryParams = me.parseParam(model.path);
queryParamTable.setProperties(model.queryParams);
$('span.variable').off('mouseover');
$('span.variable').off('mouseout');
document.querySelectorAll('span.variable').forEach(element => {
let text = element.innerText;
element.addEventListener('mouseover', () => me.showPopper(element, text));
element.addEventListener('mouseout', () => me.hidePopper(element));
});
});
$(this).append(`<label>Path</label>`);
$('span.variable').off('mouseover');
$('span.variable').off('mouseout');
});
queryParamTable.init(apiRequestTabContent.find(`#api_request_${model.id}`).find('.query_params')[0], (properties) => {
model.queryParams = properties;
console.log(model.queryParams);
this.buildPath(model);
editableInput.setValue(model.path);
$('span.variable').off('mouseover');
$('span.variable').off('mouseout');
document.querySelectorAll('span.variable').forEach(element => {
let text = element.innerText;
element.addEventListener('mouseover', () => me.showPopper(element, text));
element.addEventListener('mouseout', () => me.hidePopper(element));
});
});
headerTable.init(apiRequestTabContent.find(`#api_request_${model.id}`).find('.request_headers')[0], (properties) => {
model.headers = properties;
$('span.variable').off('mouseover');
$('span.variable').off('mouseout');
document.querySelectorAll('span.variable').forEach(element => {
let text = element.innerText;
element.addEventListener('mouseover', () => me.showPopper(element, text));
element.addEventListener('mouseout', () => me.hidePopper(element));
});
});
}
if (model.type ==='TCP'){
apiRequestTabContent.append(this.tcpApiRequestTemplate(model, index));
}
document.querySelectorAll('.variable').forEach(element => { document.querySelectorAll('.variable').forEach(element => {
let text = element.innerText; let text = element.innerText;
element.addEventListener('mouseover', () => me.showPopper(element, text)); element.addEventListener('mouseover', () => me.showPopper(element, text));
element.addEventListener('mouseout', () => me.hidePopper(element)); element.addEventListener('mouseout', () => me.hidePopper(element));
}); });
let language = model.type === 'HTTP' ? 'json' : 'text';
this.openTab(model.id); let target = null;
let target = $('#api_request_' + model.id); let requestBodyEditor = null;
let language = model.type ==='TCP'? 'text': 'json';
let requestBodyEditor = monaco.editor.create(target.find('.request_body')[0], { if (model.type === 'HTTP') {
value: model.requestBody, let httpClientView = new HTTPClientView(me, this.serverManager, apiRequestTabContent, model);
language: language, httpClientView.init();
automaticLayout: true this.openTab(model.id);
}); target = $('#api_request_' + model.id);
requestBodyEditor = monaco.editor.create(target.find('.request_body')[0], {
value: model.requestBody, language: 'json', automaticLayout: true
});
}
if (model.type === 'TCP') {
const tcpClientView = new TCPClientView(me, this.serverManager, apiRequestTabContent, model);
tcpClientView.init();
this.openTab(model.id);
target = $('#api_request_' + model.id);
requestBodyEditor = monaco.editor.create(target.find('.request_body')[0], {
value: model.requestBody, language: 'text', automaticLayout: false
});
tcpClientView.setRequestBodyEditor(requestBodyEditor);
}
requestBodyEditor.onMouseMove((e) => { requestBodyEditor.onMouseMove((e) => {
let position = e.target.position; let position = e.target.position;
@@ -768,27 +442,19 @@ class APITester {
}); });
let responseBodyEditor = monaco.editor.create(target.find('.response_body')[0], { let responseBodyEditor = monaco.editor.create(target.find('.response_body')[0], {
value: '', value: '', language: language, automaticLayout: true, quickSuggestions: false
language: language,
automaticLayout: true,
quickSuggestions: false
}); });
me.responseEditors.push(responseBodyEditor); me.responseEditors.push(responseBodyEditor);
let consoleEditor = monaco.editor.create(target.find('.console')[0], { let consoleEditor = monaco.editor.create(target.find('.console')[0], {
value: '', value: '', automaticLayout: true, quickSuggestions: false
automaticLayout: true,
quickSuggestions: false
}); });
me.consoleEditors.push(consoleEditor); me.consoleEditors.push(consoleEditor);
let preRequestEditor = monaco.editor.create(target.find('.pre-request')[0], { let preRequestEditor = monaco.editor.create(target.find('.pre-request')[0], {
value: model.preRequestScript, value: model.preRequestScript, language: 'javascript', automaticLayout: true, quickSuggestions: false
language: 'javascript',
automaticLayout: true,
quickSuggestions: false
}); });
const snippet1 = new VariableSnippet(); const snippet1 = new VariableSnippet();
@@ -804,10 +470,7 @@ class APITester {
}); });
let postRequestEditor = monaco.editor.create(target.find('.post-request')[0], { let postRequestEditor = monaco.editor.create(target.find('.post-request')[0], {
value: model.postRequestScript, value: model.postRequestScript, language: 'javascript', automaticLayout: true, quickSuggestions: false
language: 'javascript',
automaticLayout: true,
quickSuggestions: false
}); });
const snippet2 = new VariableSnippet(); const snippet2 = new VariableSnippet();
@@ -874,12 +537,10 @@ class APITester {
} }
$('li .api_request_sortable').draggable({ $('li .api_request_sortable').draggable({
helper: this.customHelper, helper: this.customHelper, start: function(event, ui) {
start: function(event, ui) {
// You can add any additional data or styles you want when dragging starts // You can add any additional data or styles you want when dragging starts
$(this).addClass('dragging'); $(this).addClass('dragging');
}, }, stop: function(event, ui) {
stop: function(event, ui) {
// Clean up, e.g., remove styles or data // Clean up, e.g., remove styles or data
$(this).removeClass('dragging'); $(this).removeClass('dragging');
} }
@@ -907,9 +568,7 @@ class APITester {
const tabIndex = $('#apiRequestTabContent').children().index($(event.currentTarget).closest('.api_request')); const tabIndex = $('#apiRequestTabContent').children().index($(event.currentTarget).closest('.api_request'));
let model = this.apiRequests[tabIndex]; let model = this.apiRequests[tabIndex];
model.headers.push({ model.headers.push({
enabled: true, enabled: true, key: '', value: ''
key: '',
value: ''
}); });
this.renderHeaderView(tabIndex); this.renderHeaderView(tabIndex);
} }
@@ -938,32 +597,14 @@ class APITester {
let queryParam = queryParamArray[i].split('='); let queryParam = queryParamArray[i].split('=');
queryParams.push({ queryParams.push({
enabled: true, enabled: true, key: queryParam[0], value: (queryParam[1] === undefined ? '' : queryParam[1])
key: queryParam[0],
value: (queryParam[1] === undefined ? '' : queryParam[1])
}); });
} }
} }
console.log(queryParams);
return queryParams; return queryParams;
} }
buildPath(model) {
let queryString = model.path.split('?')[0];
if (model.queryParams.length > 0) {
queryString += '?';
}
for (let i = 0; i < model.queryParams.length; i++) {
if (!model.queryParams[i].enabled) {
continue;
}
queryString += model.queryParams[i].key + '=' + model.queryParams[i].value;
if (i < model.queryParams.length - 1) {
queryString += '&';
}
}
model.path = queryString;
}
showLoadingOverlay() { showLoadingOverlay() {
$('#loading-overlay').show(); $('#loading-overlay').show();
} }
@@ -998,11 +639,7 @@ class APITester {
model.responseModel.time = endTime - startTime; model.responseModel.time = endTime - startTime;
} else { } else {
model.responseModel = { model.responseModel = {
status: 0, status: 0, time: 0, size: 0, headers: [], body: ''
time: 0,
size: 0,
headers: [],
body: ''
}; };
} }
@@ -1022,10 +659,7 @@ class APITester {
message = JSON.stringify(message, null, 2); message = JSON.stringify(message, null, 2);
} }
var op = { var op = {
identifier: id, identifier: id, range: range, text: message + '\n', forceMoveMarkers: true
range: range,
text: message + '\n',
forceMoveMarkers: true
}; };
console.executeEdits('my-source', [op]); console.executeEdits('my-source', [op]);
} }
@@ -1033,23 +667,18 @@ class APITester {
loadCollections() { loadCollections() {
const me = this; const me = this;
this.currentAjaxRequest = $.ajax({ this.currentAjaxRequest = $.ajax({
url: this.getCollectionListURL(), url: this.getCollectionListURL(), type: 'GET', contentType: 'application/json', success: function(data) {
type: 'GET',
contentType: 'application/json',
success: function(data) {
me.collections = data.map(collection => { me.collections = data.map(collection => {
if (collection.apis) { if (collection.apis) {
collection.apis = collection.apis.map(api => ({...api, collectionId: collection.id})); collection.apis = collection.apis.map(api => ({...api, collectionId: collection.id}));
} }
return collection; return collection;
}); });
}, }, error: function(response, textStatus, errorThrown) {
error: function(response, textStatus, errorThrown) {
me.hideLoadingOverlay(); me.hideLoadingOverlay();
$('.toast-body').text(response.responseJSON.error); $('.toast-body').text(response.responseJSON.error);
$('.toast').toast('show'); $('.toast').toast('show');
}, }, complete: function() {
complete: function() {
me.renderCollections(); me.renderCollections();
me.hideLoadingOverlay(); me.hideLoadingOverlay();
} }
@@ -1061,16 +690,11 @@ class APITester {
const me = this; const me = this;
me.showLoadingOverlay(); me.showLoadingOverlay();
this.currentAjaxRequest = $.ajax({ this.currentAjaxRequest = $.ajax({
url: this.getCollectionCreateURL(), url: this.getCollectionCreateURL(), type: 'POST', contentType: 'application/json', data: JSON.stringify({name: name}), error: function(response, textStatus, errorThrown) {
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({name: name}),
error: function(response, textStatus, errorThrown) {
me.hideLoadingOverlay(); me.hideLoadingOverlay();
$('.toast-body').text(response.responseJSON.error); $('.toast-body').text(response.responseJSON.error);
$('.toast').toast('show'); $('.toast').toast('show');
}, }, complete: function() {
complete: function() {
me.loadCollections(); me.loadCollections();
me.hideLoadingOverlay(); me.hideLoadingOverlay();
$('#new_collection_modal').modal('hide'); $('#new_collection_modal').modal('hide');
@@ -1082,16 +706,11 @@ class APITester {
const me = this; const me = this;
me.showLoadingOverlay(); me.showLoadingOverlay();
this.currentAjaxRequest = $.ajax({ this.currentAjaxRequest = $.ajax({
url: this.getCollectionUpdateURL(), url: this.getCollectionUpdateURL(), type: 'POST', contentType: 'application/json', data: JSON.stringify({id: id, name: name}), error: function(response, textStatus, errorThrown) {
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({id: id, name: name}),
error: function(response, textStatus, errorThrown) {
me.hideLoadingOverlay(); me.hideLoadingOverlay();
$('.toast-body').text(response.responseJSON.error); $('.toast-body').text(response.responseJSON.error);
$('.toast').toast('show'); $('.toast').toast('show');
}, }, complete: function() {
complete: function() {
me.loadCollections(); me.loadCollections();
me.hideLoadingOverlay(); me.hideLoadingOverlay();
$('#new_collection_modal').modal('hide'); $('#new_collection_modal').modal('hide');
@@ -1104,18 +723,12 @@ class APITester {
const me = this; const me = this;
me.showLoadingOverlay(); me.showLoadingOverlay();
this.currentAjaxRequest = $.ajax({ this.currentAjaxRequest = $.ajax({
url: this.getCollectionDeleteURL(), url: this.getCollectionDeleteURL(), type: 'POST', contentType: 'application/json', data: JSON.stringify({id: collectionId}), success: function() {
type: 'POST', }, error: function(response, textStatus, errorThrown) {
contentType: 'application/json',
data: JSON.stringify({id: collectionId}),
success: function() {
},
error: function(response, textStatus, errorThrown) {
me.hideLoadingOverlay(); me.hideLoadingOverlay();
$('.toast-body').text(response.responseJSON.error); $('.toast-body').text(response.responseJSON.error);
$('.toast').toast('show'); $('.toast').toast('show');
}, }, complete: function() {
complete: function() {
me.loadCollections(); me.loadCollections();
me.hideLoadingOverlay(); me.hideLoadingOverlay();
$('#confirm_delete_modal').modal('hide'); $('#confirm_delete_modal').modal('hide');
@@ -1127,21 +740,15 @@ class APITester {
const me = this; const me = this;
me.showLoadingOverlay(); me.showLoadingOverlay();
this.currentAjaxRequest = $.ajax({ this.currentAjaxRequest = $.ajax({
url: this.getAPISaveURL(model.collectionId), url: this.getAPISaveURL(model.collectionId), type: 'POST', contentType: 'application/json', data: JSON.stringify(model), success: function(data) {
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(model),
success: function(data) {
let message = '저장되었습니다.'; let message = '저장되었습니다.';
$('.toast-body').text(message); $('.toast-body').text(message);
$('.toast').toast('show'); $('.toast').toast('show');
}, }, error: function(response, textStatus, errorThrown) {
error: function(response, textStatus, errorThrown) {
me.hideLoadingOverlay(); me.hideLoadingOverlay();
$('.toast-body').text(response.responseJSON.error); $('.toast-body').text(response.responseJSON.error);
$('.toast').toast('show'); $('.toast').toast('show');
}, }, complete: function() {
complete: function() {
me.hideLoadingOverlay(); me.hideLoadingOverlay();
} }
}); });
@@ -1154,21 +761,15 @@ class APITester {
me.showLoadingOverlay(); me.showLoadingOverlay();
this.currentAjaxRequest = $.ajax({ this.currentAjaxRequest = $.ajax({
url: this.getAPISaveURL(model.collectionId), url: this.getAPISaveURL(model.collectionId), type: 'POST', contentType: 'application/json', data: JSON.stringify(model), success: function(data) {
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(model),
success: function(data) {
let message = '저장되었습니다.'; let message = '저장되었습니다.';
$('.toast-body').text(message); $('.toast-body').text(message);
$('.toast').toast('show'); $('.toast').toast('show');
}, }, error: function(response, textStatus, errorThrown) {
error: function(response, textStatus, errorThrown) {
me.hideLoadingOverlay(); me.hideLoadingOverlay();
$('.toast-body').text(response.responseJSON.error); $('.toast-body').text(response.responseJSON.error);
$('.toast').toast('show'); $('.toast').toast('show');
}, }, complete: function() {
complete: function() {
me.hideLoadingOverlay(); me.hideLoadingOverlay();
} }
}); });
@@ -1190,42 +791,26 @@ class APITester {
})[0]; })[0];
let me = this; let me = this;
let newApi = { let newApi = {
id: '', id: '', server: null, method: 'GET', name: newApiName, path: '', type: newApiType, headers: [], queryParams: [], requestBody: '', preRequestScript: '', postRequestScript: '', collectionId: collectionId, responseModel: {
server: null, status: 0, time: 0, size: 0, headers: [], ody: ''
method: 'GET',
name: newApiName,
path: '',
type : newApiType,
headers: [],
queryParams: [],
requestBody: '',
preRequestScript: '',
postRequestScript: '',
collectionId: collectionId,
responseModel: {
status: 0,
time: 0,
size: 0,
headers: [],
ody: ''
} }
}; };
newApi.collectionName = collection.name; newApi.collectionName = collection.name;
if (newApiType === 'TCP') {
newApi.layout = [];
newApi.layout.push({layoutItems: []});
}
this.currentAjaxRequest = $.ajax({ this.currentAjaxRequest = $.ajax({
url: this.getAPISaveURL(collectionId), url: this.getAPISaveURL(collectionId), type: 'POST', contentType: 'application/json', data: JSON.stringify(newApi), success: function(data) {
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(newApi),
success: function(data) {
newApi.id = data; newApi.id = data;
}, }, error: function(response, textStatus, errorThrown) {
error: function(response, textStatus, errorThrown) {
me.hideLoadingOverlay(); me.hideLoadingOverlay();
$('.toast-body').text(response.responseJSON.error); $('.toast-body').text(response.responseJSON.error);
$('.toast').toast('show'); $('.toast').toast('show');
}, }, complete: function() {
complete: function() {
me.loadCollections(); me.loadCollections();
me.apiRequests.push(newApi); me.apiRequests.push(newApi);
me.currentIndex = me.apiRequests.length - 1; me.currentIndex = me.apiRequests.length - 1;
@@ -1271,11 +856,7 @@ class APITester {
if (!selectedApi.responseModel) { if (!selectedApi.responseModel) {
selectedApi.responseModel = { selectedApi.responseModel = {
status: 0, status: 0, time: 0, size: 0, headers: [], body: ''
time: 0,
size: 0,
headers: [],
body: ''
}; };
} }
@@ -1305,17 +886,11 @@ class APITester {
} }
const me = this; const me = this;
this.currentAjaxRequest = $.ajax({ this.currentAjaxRequest = $.ajax({
url: this.getAPIDeleteURL(collectionId), url: this.getAPIDeleteURL(collectionId), type: 'POST', contentType: 'application/json', data: JSON.stringify({id: apiId}), success: function(data) {
type: 'POST', }, error: function(response, textStatus, errorThrown) {
contentType: 'application/json',
data: JSON.stringify({id: apiId}),
success: function(data) {
},
error: function(response, textStatus, errorThrown) {
$('.toast-body').text(response.responseJSON.error); $('.toast-body').text(response.responseJSON.error);
me.hideLoadingOverlay(); me.hideLoadingOverlay();
}, }, complete: function() {
complete: function() {
me.hideLoadingOverlay(); me.hideLoadingOverlay();
me.loadCollections(); me.loadCollections();
$('#confirm_delete_api_modal').modal('hide'); $('#confirm_delete_api_modal').modal('hide');
@@ -0,0 +1,255 @@
import EditableInput from './EditableInput';
import PropertyTable from './PropertyTable';
class HTTPClientView {
constructor(parent, serverManager, apiRequestTabContent, model, index) {
this.parent = parent;
this.serverManager = serverManager;
this.apiRequestTabContent = apiRequestTabContent;
this.model = model;
this.index = index;
}
init() {
let me = this;
this.apiRequestTabContent.append(this.httpApiRequestTemplate(this.model, this.index));
let editableInput = new EditableInput({
name: 'path',
value: this.model.path
});
let queryParamTable = new PropertyTable({
propertyName: 'queryParams',
properties: this.model.queryParams
});
let headerTable = new PropertyTable({
propertyName: 'headers',
properties: this.model.headers
});
this.apiRequestTabContent.find(`#api_request_${this.model.id}`).find('.api_request_info').find('.path').each(function() {
editableInput.init(this, (value) => {
me.model.path = value;
me.model.queryParams = me.parent.parseParam(me.model.path);
queryParamTable.setProperties(me.model.queryParams);
$('span.variable').off('mouseover');
$('span.variable').off('mouseout');
document.querySelectorAll('span.variable').forEach(element => {
let text = element.innerText;
element.addEventListener('mouseover', () => this.parent.showPopper(element, text));
element.addEventListener('mouseout', () => this.parent.hidePopper(element));
});
});
$(this).append(`<label>Path</label>`);
$('span.variable').off('mouseover');
$('span.variable').off('mouseout');
});
queryParamTable.init(this.apiRequestTabContent.find(`#api_request_${this.model.id}`).find('.query_params')[0], (properties) => {
this.model.queryParams = properties;
console.log(this.model.queryParams);
this.buildPath(this.model);
editableInput.setValue(this.model.path);
$('span.variable').off('mouseover');
$('span.variable').off('mouseout');
document.querySelectorAll('span.variable').forEach(element => {
let text = element.innerText;
element.addEventListener('mouseover', () => this.parent.showPopper(element, text));
element.addEventListener('mouseout', () => this.parent.hidePopper(element));
});
});
headerTable.init(this.apiRequestTabContent.find(`#api_request_${this.model.id}`).find('.request_headers')[0], (properties) => {
this.model.headers = properties;
$('span.variable').off('mouseover');
$('span.variable').off('mouseout');
document.querySelectorAll('span.variable').forEach(element => {
let text = element.innerText;
element.addEventListener('mouseover', () => this.parent.showPopper(element, text));
element.addEventListener('mouseout', () => this.parent.hidePopper(element));
});
});
}
buildPath(model) {
let queryString = model.path.split('?')[0];
if (model.queryParams.length > 0) {
queryString += '?';
}
for (let i = 0; i < model.queryParams.length; i++) {
if (!model.queryParams[i].enabled) {
continue;
}
queryString += model.queryParams[i].key + '=' + model.queryParams[i].value;
if (i < model.queryParams.length - 1) {
queryString += '&';
}
}
model.path = queryString;
}
httpApiRequestTemplate(apiRequest, index) {
return `
<div class="tab-pane fade mt-1 api_request" id="api_request_${apiRequest.id}" style="height: 100%;">
<div><span class="collection_name">${apiRequest.collectionName}</span> &gt; <span class="api_name">${apiRequest.name}</span></div>
<div class="d-flex api_request_info">
<div class="form-floating">
<select name="method" class="form-select method" style="width: 90px; border-bottom-right-radius: 0;border-top-right-radius: 0;">
<option value="POST" ${apiRequest.method === 'POST' ? 'selected' : ''}>POST</option>
<option value="GET" ${apiRequest.method === 'GET' ? 'selected' : ''}>GET</option>
<option value="PUT" ${apiRequest.method === 'PUT' ? 'selected' : ''}>PUT</option>
<option value="DELETE" ${apiRequest.method === 'DELETE' ? 'selected' : ''}>DELETE</option>
<option value="CONNECT" ${apiRequest.method === 'CONNECT' ? 'selected' : ''}>CONNECT</option>
<option value="HEAD" ${apiRequest.method === 'HEAD' ? 'selected' : ''}>HEAD</option>
<option value="OPTIONS" ${apiRequest.method === 'OPTIONS' ? 'selected' : ''}>OPTIONS</option>
<option value="TRACE" ${apiRequest.method === 'TRACE' ? 'selected' : ''}>TRACE</option>
<option value="PATCH" ${apiRequest.method === 'PATCH' ? 'selected' : ''}>PATCH</option>
</select>
<label class="form-label must">Method</label>
</div>
<div class="form-floating">
${this.serverManager.renderHttpServers(apiRequest.server)}
<label class="form-label must">Server</label>
</div>
<div class="form-floating">
<input type="text" name="basePath" class="form-control" style="width: 180px; border-radius: 0" value="${this.serverManager.basePath(apiRequest.server)}" readonly>
<label class="must">기본 경로</label>
</div>
<div class="form-floating editableInput me-1 path" data-name="path" data-label="Path">
<label>Path</label>
</div>
<button class="btn btn-primary send me-1">Send</button>
<button class="btn btn-primary save" data-index="${index}" style="height: 58px;">저장</button>
</div>
<div class="api_request d-flex flex-column" style="height:100%;">
<div class="api_request_body">
<ul class="nav nav-tabs mt-1" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link btn_request_body_tab" data-bs-toggle="tab"
type="button" role="tab" aria-controls="requestBody" data-bs-target="#requestBodyTab_${apiRequest.id}"
aria-selected="false">Body
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link btn_request_param_tab" data-bs-toggle="tab"
type="button" role="tab" aria-controls="paramTab" data-bs-target="#requestParamTab_${apiRequest.id}"
aria-selected="true">Param
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link btn_request_header_tab" data-bs-toggle="tab"
type="button" role="tab" aria-controls="requestHeader" data-bs-target="#requestHeaderTab_${apiRequest.id}"
aria-selected="false">Header
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link btn_prerequest_tab" data-bs-toggle="tab"
type="button" role="tab" aria-controls="preScrit" data-bs-target="#preRequestTab_${apiRequest.id}"
aria-selected="false">Pre-Request
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link btn_postrequest_tab" data-bs-toggle="tab"
type="button" role="tab" aria-controls="postScript" data-bs-target="#postRequestTab_${apiRequest.id}"
aria-selected="false">Post-Request
</button>
</li>
</ul>
<div class="tab-content" style="height: 100%;">
<div class="tab-pane fade show request_param_tab" role="tabpanel" id="requestParamTab_${apiRequest.id}" aria-labelledby="requestParams-tab">
<div class="query_params">
</div>
</div>
<div class="tab-pane fade request_header_tab" role="tabpanel" id="requestHeaderTab_${apiRequest.id}"
aria-labelledby="requestHeader-tab">
<div class="request_headers">
</div>
</div>
<div class="tab-pane fade request_body_tab" role="tabpanel" id="requestBodyTab_${apiRequest.id}" aria-labelledby="requestBody-tab">
<div class="d-flex flex-column">
<select class="form-select mt-1 request_body_type" name="request_type">
<option value="json">json</option>
<option value="xml">xml</option>
<option value="">text/plain</option>
</select>
<div class="request_body mt-1" style="width:100%;height: 300px; border:1px solid grey;"></div>
</div>
</div>
<div class="tab-pane fade pre_script_tab" role="tabpanel" id="preRequestTab_${apiRequest.id}" aria-labelledby="preScrit-tab">
<div class="d-flex flex-row">
<div style="width:calc(100% - 200px);">
<div class="pre-request mt-1" style="height:300px;border:1px solid grey;"></div>
</div>
<div style="width: 200px;">
<div class="pre-request-variable">
</div>
</div>
</div>
</div>
<div class="tab-pane fade post_script_tab" role="tabpanel" id="postRequestTab_${apiRequest.id}"
aria-labelledby="postScrit-tab">
<div class="d-flex flex-row">
<div style="width:calc(100% - 200px);">
<div class="post-request mt-1" style="height:300px;border:1px solid grey;"></div>
</div>
<div style="width: 200px;">
<div class="post-request-variable">
</div>
</div>
</div>
</div>
</div>
</div>
<div class="api_response" style="flex: 1">
<div class="mt-1">
<span>Response</span>
<span class="response_status"></span>
<span class="response_time"></span>
<span class="response_size"></span>
</div>
<ul class="nav nav-tabs mt-1" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#responseBodyTab_${apiRequest.id}" type="button" role="tab" aria-controls="responseBody" aria-selected="false">Body</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#responseHeaderTab_${apiRequest.id}" type="button" role="tab" aria-controls="responseHeader" aria-selected="false">Header</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#consoleTab_${apiRequest.id}" type="button" role="tab" aria-controls="consoleTab" aria-selected="false">Console</button>
</li>
</ul>
<div class="tab-content border-bottom" style="min-height: 250px;">
<div class="tab-pane fade show active" id="responseBodyTab_${apiRequest.id}" role="tabpanel"
aria-labelledby="responseParams-tab">
<div class="response_body mt-1" style="width:100%;height:250px;border:1px solid grey;"></div>
</div>
<div class="tab-pane fade" id="responseHeaderTab_${apiRequest.id}" role="tabpanel"
aria-labelledby="responseParams-tab">
<table class="table">
<thead>
<tr>
<th scope="col">이름</th>
<th scope="col">값</th>
</tr>
</thead>
<tbody class="response_headers">
</tbody>
</table>
</div>
<div class="tab-pane fade" id="consoleTab_${apiRequest.id}" role="tabpanel"
aria-labelledby="console-tab">
<div class="console mt-1" style="width:100%;height:250px;border:1px solid grey;"></div>
</div>
</div>
</div>
</div>
</div>
`;
}
}
export default HTTPClientView;
@@ -0,0 +1,44 @@
class ServerManager {
constructor(servers) {
this.servers = servers ||[];
}
basePath(serverId) {
if (serverId === undefined || serverId === null || serverId === '') {
return '';
}
return this.servers.filter(server => server.id == serverId)[0].basePath; // == : 타입 비교 없이 값만 비교
}
getServer(serverId) {
return this.servers.filter(server => server.id == serverId)[0];
}
renderServers(selectedServer) {
const optionsHtml = this.servers.map(server => `<option value="${server.id}" ${selectedServer === server.id ? 'selected' : ''} data-path="${server.basePath}">${server.name}</option>`).join('');
return `<select class="form-select server" name="server" style="width: 180px; border-radius: 0;" required>
<option value="">서버 선택</option>
${optionsHtml}</select>`;
}
renderTcpServers(selectedServer) {
const optionsHtml = this.servers.filter( server => server.scheme === 'tcp')
.map(server => `<option value="${server.id}" ${selectedServer == server.id ? 'selected' : ''}>${server.name}</option>`).join('');
return `<select class="form-select server" name="server" style="width: 180px; border-radius: 0;" required>
<option value="">서버 선택</option>
${optionsHtml}</select>`;
}
renderHttpServers(selectedServer) {
const optionsHtml = this.servers.filter( server => server.scheme !== 'tcp')
.map(server => `<option value="${server.id}" ${selectedServer === server.id ? 'selected' : ''} data-path="${server.basePath}">${server.name}</option>`).join('');
return `<select class="form-select server" name="server" style="width: 180px; border-radius: 0;" required>
<option value="">서버 선택</option>
${optionsHtml}</select>`;
}
}
export default ServerManager;
@@ -0,0 +1,386 @@
import EditableInput from './EditableInput';
import APIClient from '../APIClient';
class TCPClientView {
constructor(parent, serverManager, apiRequestTabContent, model, index) {
this.parent = parent;
this.serverManager = serverManager;
this.apiRequestTabContent = apiRequestTabContent;
this.model = model;
this.index = index;
this.server = this.serverManager.getServer(this.model.server);
this.apiClient = new APIClient();
}
init() {
this.apiRequestTabContent.append(this.tcpApiRequestTemplate(this.model, this.index));
this.renderApiLayout(this.model.layout);
}
setRequestBodyEditor(editor) {
this.requestBodyEditor = editor;
}
renderApiLayout(layout) {
const requestContainer = this.apiRequestTabContent.find('.request_layout');
requestContainer.empty();
const headerHtml = `
<table class="table layout_table">
<colgroup>
<col style="width: 80px;">
<col style="width: 120px;">
<col style="width: 150px;">
<col style="width: 80px;">
<col style="width: 110px;">
<col style="width: 80px;">
<col style="width: 100px;">
<col style="width: 120px;">
<col style="width: 80px;">
<col>
<col style="width: 120px;">
</colgroup>
<thead>
<tr>
<th scope="col" style="text-align: center;width: 80px;">#</th>
<th scope="col" style="text-align: center;width: 120px;">항목명(영문)</th>
<th scope="col" style="text-align: center;width: 150px;">항목설명</th>
<th scope="col" style="text-align: center;width: 80px;">깊이</th>
<th scope="col" style="text-align: center;width: 110px;">아이템 유형</th>
<th scope="col" style="text-align: center;width: 80px;">반복 횟수 </th>
<th scope="col" style="text-align: center;width: 100px;">반복 참조 필드</th>
<th scope="col" style="text-align: center;width: 120px;">데이터 타입</th>
<th scope="col" style="text-align: center;width: 80px;">데이터 길이</th>
<th scope="col" style="text-align: center;">값</th>
<th scope="col" style="text-align: center;width: 120px;">비고 <button type="button" class="btn btn-sm btn-outline-secondary add-layout-item"><i class="fas fa-plus"></i></button></th>
</tr>
</thead>
<tbody>
`;
const footerHtml = `
</tbody>
</table>
`;
const fieldsHtml = layout.layoutItems ? layout.layoutItems.map((item, index) => {
return this.renderApiLayoutItem(item, index);
}).join('') : '';
requestContainer.append(headerHtml + fieldsHtml + footerHtml);
requestContainer.find('.editable').each((index, editableElement) => {
const editableInput = new EditableInput({
name: editableElement.dataset.name,
value: editableElement.dataset.value
});
editableInput.init(editableElement, (value) => {
_.set(this.model.layout, editableElement.dataset.name, value);
this.buildMessage();
$('span.variable').off('mouseover');
$('span.variable').off('mouseover');
document.querySelectorAll('span.variable').forEach(element => {
let text = element.innerText;
element.addEventListener('mouseover', () => this.parent.showPopper(element, text));
element.addEventListener('mouseout', () => this.parent.hidePopper(element));
});
});
});
let totalLength = 0;
this.model.layout.layoutItems.forEach(item => {
totalLength += parseInt(item.loutItemLength);
});
this.apiRequestTabContent.find('.total_message_length').text(totalLength);
this.setupListeners();
}
handleUIUpdate(event) {
if (event) {
event.preventDefault();
const element = event.currentTarget;
const fieldName = $(element).attr('name');
const newValue = this.getFieldValue(element);
if (fieldName !== '') {
_.set(this.model.layout, fieldName, newValue);
this.buildMessage();
}
}
let totalLength = 0;
this.model.layout.layoutItems.forEach(item => {
totalLength += parseInt(item.loutItemLength);
});
this.apiRequestTabContent.find('.total_message_length').text(totalLength);
}
getFieldValue(element) {
if ($(element).is('select') || $(element).is('input[type="text"]') || $(element).is('input[type="number"]')) {
return $(element).val();
}
if ($(element).is('input[type="checkbox"]')) {
return $(element).is(':checked');
}
}
renderApiLayoutItem(item, index) {
if (item.value === null)
item.value = '';
return `
<tr>
<td style="width: 80px; text-align: center;"><span style="width: 80px; text-align: center;">${item.loutItemSerno}</span></td>
<td><input type="text" class="field" name="layoutItems[${index}].loutItemName" value="${item.loutItemName}"></td>
<td><input type="text" class="field" name="layoutItems[${index}].loutItemDesc" value="${item.loutItemDesc}"></td>
<td><input type="number" class="field" style="width: 80px;" name="layoutItems[${index}].loutItemDepth" min="1" value="${item.loutItemDepth}"></td>
<td>
<select name="layoutItems[${index}].loutItemType" class="field" id="loutItemType${index}">
<option value="Field" ${item.loutItemType === 'Field' ? 'selected' : ''}>Field</option>
<option value="Grid" ${item.loutItemType === 'Grid' ? 'selected' : ''}>Grid</option>
<option value="Group" ${item.loutItemType === 'Group' ? 'selected' : ''}>Group</option>
<option value="Attr" ${item.loutItemType === 'Attr' ? 'selected' : ''}>Attr</option>
</select>
</td>
<td><input type="number" class="field" style="width: 80px;" name="layoutItems[${index}].loutItemOccCnt" min="0" value="${item.loutItemOccCnt}"></td>
<td><input type="text" class="field" name="layoutItems[${index}].loutItemOccRef" value="${item.loutItemOccRef}"></td>
<td>
<select name="layoutItems[${index}].loutItemDataType" class="field" id="loutItemDataType${index}">
<option value="BigDecimal" ${item.loutItemDataType === 'BigDecimal' ? 'selected' : ''}>BigDecimal</option>
<option value="Int" ${item.loutItemDataType === 'Int' ? 'selected' : ''}>Int</option>
<option value="Long" ${item.loutItemDataType === 'Long' ? 'selected' : ''}>Long</option>
<option value="String" ${item.loutItemDataType === 'String' ? 'selected' : ''}>String</option>
</select>
</td>
<td><input type="number" class="field" style="width: 80px;" name="layoutItems[${index}].loutItemLength" min="1" value="${item.loutItemLength}"></td>
<td><!-- input type="text" class="field" name="layoutItems[${index}].value" value="${item.value !== undefined ? item.value : ''}" -->
<div class="editable" data-name="layoutItems[${index}].value" data-value="${item.value !== undefined ? item.value : ''}"></div>
</td>
<td style="width: 120px; text-align: center;">
<div style="width: 120px !important;">
<button type="button" class="btn btn-sm btn-outline-secondary move-up" data-index="${index}"><i class="fas fa-arrow-up"></i></button>
<button type="button" class="btn btn-sm btn-outline-secondary move-down" data-index="${index}"><i class="fas fa-arrow-down"></i></button>
<button type="button" class="btn btn-sm btn-outline-secondary delete" data-index="${index}"><i class="fas fa-trash-alt"></i></button>
</div>
</td>
</tr>
`;
}
recalculateSerno() {
// Reassign loutItemSerno sequentially based on the current order
this.model.layout.layoutItems.forEach((item, index) => {
item.loutItemSerno = index + 1; // Assuming you want to start numbering from 1
});
}
setupListeners() {
const table = this.apiRequestTabContent.find('.layout_table');
table.on('change', 'select.field, input.field', this.handleUIUpdate.bind(this));
table.on('click', '.add-layout-item', event => {
let item = {
'parent': 0,
'level': 1,
'loutName': '',
'loutItemName': '',
'loutItemDesc': '',
'loutItemDepth': 1,
'loutItemType': 'Field',
'loutItemOccCnt': '',
'loutItemOccRef': '',
'loutItemDataType': 'String',
'loutItemLength': 1,
'loutItemDecimal': 0,
'loutItemDefault': '',
'loutItemMaskYn': 'N',
'loutItemMaskOffset': 0,
'loutItemMaskLength': 0,
'parentLoutItemIndex': 0,
'expanded': true,
'isLeaf': true,
'LOUTITEMPATH': '',
'value' : ''
};
this.model.layout.layoutItems.push(item);
this.recalculateSerno();
this.renderApiLayout(this.model.layout);
});
table.on('click', '.move-up', event => {
const index = $(event.currentTarget).data('index');
if (index > 0) {
const temp = this.model.layout.layoutItems[index];
this.model.layout.layoutItems[index] = this.model.layout.layoutItems[index - 1];
this.model.layout.layoutItems[index - 1] = temp;
this.recalculateSerno();
this.renderApiLayout(this.model.layout);
}
});
table.on('click', '.move-down', event => {
const index = $(event.currentTarget).data('index');
if (index < this.model.layout.layoutItems.length - 1) {
const temp = this.model.layout.layoutItems[index];
this.model.layout.layoutItems[index] = this.model.layout.layoutItems[index + 1];
this.model.layout.layoutItems[index + 1] = temp;
this.recalculateSerno();
this.renderApiLayout(this.model.layout);
}
});
table.on('click', '.delete', event => {
const index = $(event.currentTarget).data('index');
this.model.layout.layoutItems.splice(index, 1);
this.recalculateSerno();
this.renderApiLayout(this.model.layout);
});
}
async buildMessage() {
let message = '';
this.requestBodyEditor.setValue(message);
for (const item of this.model.layout.layoutItems) {
let duplicated = item.value;
const value = await this.apiClient.processPreScriptAndVariables(this.model, duplicated, null);
message += this.fillPadding(item.loutItemLength, value);
}
this.requestBodyEditor.setValue(message);
}
fillPadding(dataLength, value) {
value = String(value);
return value.padStart(dataLength, ' ');
}
tcpApiRequestTemplate(apiRequest, index) {
return `
<div class="tab-pane fade mt-1 api_request" id="api_request_${apiRequest.id}" style="height: 100%;">
<div><span class="collection_name">${apiRequest.collectionName}</span> &gt; <span class="api_name">${apiRequest.name}</span></div>
<div class="d-flex api_request_info">
<div class="form-floating me-1">
${this.serverManager.renderTcpServers(apiRequest.server)}
<label class="form-label must">Server</label>
</div>
<div class="form-floating me-1 ${(this.server && this.server.lengthFieldInclude) ? 'show':'hide'}">
<input type="number" style="width: 120px" value="${this.server.lengthFieldOffset}" name="lengthFieldOffset" class="form-control" placeholder="Length Field Offset" readonly>
<label>길이 필드 위치</label>
</div>
<div class="form-floating me-1 ${(this.server && this.server.lengthFieldInclude) ? 'show':'hide'}">
<input type="number" style="width: 120px" value="${this.server.lengthFieldLength}" name="lengthFieldLength" class="form-control" placeholder="Length Field Length" readonly>
<label>길이 필드 길이</label>
</div>
<div class="form-floating me-1 ${(this.server && this.server.messageKeyInclude) ? 'show':'hide'}">
<input type="number" style="width: 120px" value="${this.server.messageKeyOffset}" name="messageKeyOffset" class="form-control" placeholder="Message Key Offset" readonly>
<label>메시지 키 위치</label>
</div>
<div class="form-floating me-1 ${(this.server && this.server.messageKeyInclude) ? 'show':'hide'}">
<input type="number" style="width: 120px" value="${this.server.messageKeyLength}" name="messageKeyLength" class="form-control" placeholder="Message Key Length" readonly>
<label>메시지 키 길이</label>
</div>
<div class="form-floating me-1 path ${(this.server && this.server.messageKeyInclude) ? 'show':'hide'}" data-name="path" data-label="Key">
<input type="text" name="path" class="form-control" style="width: 180px; border-radius: 0" value="${apiRequest.path}">
<label>메시지 키</label>
</div>
<button class="btn btn-primary send me-1">Send</button>
<button class="btn btn-primary save" data-index="${index}" style="height: 58px;">저장</button>
</div>
<div class="api_request d-flex flex-column" style="height:100%;">
<div class="api_request_body">
<ul class="nav nav-tabs mt-1" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link btn_request_body_tab" data-bs-toggle="tab"
type="button" role="tab" aria-controls="requestBody" data-bs-target="#requestBodyTab_${apiRequest.id}"
aria-selected="false">Body
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link btn_prerequest_tab" data-bs-toggle="tab"
type="button" role="tab" aria-controls="preScrit" data-bs-target="#preRequestTab_${apiRequest.id}"
aria-selected="false">Pre-Request
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link btn_postrequest_tab" data-bs-toggle="tab"
type="button" role="tab" aria-controls="postScript" data-bs-target="#postRequestTab_${apiRequest.id}"
aria-selected="false">Post-Request
</button>
</li>
</ul>
<div class="tab-content" style="height: 100%;">
<div class="tab-pane fade request_body_tab" role="tabpanel" id="requestBodyTab_${apiRequest.id}" aria-labelledby="requestBody-tab">
<div class="d-flex flex-column">
<div class="request_layout" style="width:100%; border:1px solid grey; overflow: scroll"></div>
<div class="request_body" style="width:100%;height: 150px; border:1px solid grey;"></div>
</div>
</div>
<div class="tab-pane fade pre_script_tab" role="tabpanel" id="preRequestTab_${apiRequest.id}" aria-labelledby="preScrit-tab">
<div class="d-flex flex-row">
<div style="width:calc(100% - 200px);">
<div class="pre-request mt-1" style="height:300px;border:1px solid grey;"></div>
</div>
<div style="width: 200px;">
<div class="pre-request-variable">
</div>
</div>
</div>
</div>
<div class="tab-pane fade post_script_tab" role="tabpanel" id="postRequestTab_${apiRequest.id}"
aria-labelledby="postScrit-tab">
<div class="d-flex flex-row">
<div style="width:calc(100% - 200px);">
<div class="post-request mt-1" style="height:300px;border:1px solid grey;"></div>
</div>
<div style="width: 200px;">
<div class="post-request-variable">
</div>
</div>
</div>
</div>
</div>
</div>
<div class="api_request_info" >
<span>전체 메시지 길이: </span>
<span class="total_message_length"></span>
</div>
<div class="api_request_info">
</div>
<div class="api_response" style="flex: 1">
<div class="mt-1">
<span>Response</span>
<span class="response_time"></span>
<span class="response_size"></span>
</div>
<ul class="nav nav-tabs mt-1" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#responseBodyTab_${apiRequest.id}" type="button" role="tab" aria-controls="responseBody" aria-selected="false">Body</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#consoleTab_${apiRequest.id}" type="button" role="tab" aria-controls="consoleTab" aria-selected="false">Console</button>
</li>
</ul>
<div class="tab-content border-bottom" style="min-height: 250px;">
<div class="tab-pane fade show active" id="responseBodyTab_${apiRequest.id}" role="tabpanel"
aria-labelledby="responseParams-tab">
<div class="response_body mt-1" style="width:100%;height:250px;border:1px solid grey;"></div>
</div>
<div class="tab-pane fade" id="consoleTab_${apiRequest.id}" role="tabpanel"
aria-labelledby="console-tab">
<div class="console mt-1" style="width:100%;height:250px;border:1px solid grey;"></div>
</div>
</div>
</div>
</div>
</div>
`;
}
}
export default TCPClientView;
@@ -3,12 +3,17 @@ package com.eactive.testmaster.client.controller;
import com.eactive.testmaster.client.dto.ApiCollectionDTO; import com.eactive.testmaster.client.dto.ApiCollectionDTO;
import com.eactive.testmaster.client.dto.ApiRequestDTO; import com.eactive.testmaster.client.dto.ApiRequestDTO;
import com.eactive.testmaster.client.entity.ApiCollection; import com.eactive.testmaster.client.entity.ApiCollection;
import com.eactive.testmaster.client.entity.ApiLayout;
import com.eactive.testmaster.client.entity.ApiRequestInfo; import com.eactive.testmaster.client.entity.ApiRequestInfo;
import com.eactive.testmaster.client.mapper.ApiLayoutMapper;
import com.eactive.testmaster.client.mapper.ApiRequestMapper; import com.eactive.testmaster.client.mapper.ApiRequestMapper;
import com.eactive.testmaster.client.service.ApiLayoutMgmtService;
import com.eactive.testmaster.client.service.ApiRequestMgmtService; import com.eactive.testmaster.client.service.ApiRequestMgmtService;
import com.eactive.testmaster.common.util.SecurityUtil; import com.eactive.testmaster.common.util.SecurityUtil;
import com.eactive.testmaster.user.entity.StaffUser; import com.eactive.testmaster.user.entity.StaffUser;
import com.eactive.testmaster.user.service.UserService; import com.eactive.testmaster.user.service.UserService;
import javax.transaction.Transactional;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@@ -20,13 +25,19 @@ public class ApiRequestMgmtController {
private static final String SUCCESS = "success"; private static final String SUCCESS = "success";
private final ApiRequestMgmtService apiRequestMgmtService; private final ApiRequestMgmtService apiRequestMgmtService;
private final ApiLayoutMgmtService apiLayoutMgmtService;
private final ApiRequestMapper apiRequestMapper; private final ApiRequestMapper apiRequestMapper;
private final ApiLayoutMapper apiLayoutMapper;
private final UserService userService; private final UserService userService;
public ApiRequestMgmtController(ApiRequestMgmtService apiRequestMgmtService, ApiRequestMapper apiRequestMapper, UserService userService) { public ApiRequestMgmtController(ApiRequestMgmtService apiRequestMgmtService, ApiLayoutMgmtService apiLayoutMgmtService, ApiRequestMapper apiRequestMapper, ApiLayoutMapper apiLayoutMapper, UserService userService) {
this.apiRequestMgmtService = apiRequestMgmtService; this.apiRequestMgmtService = apiRequestMgmtService;
this.apiLayoutMgmtService = apiLayoutMgmtService;
this.apiRequestMapper = apiRequestMapper; this.apiRequestMapper = apiRequestMapper;
this.apiLayoutMapper = apiLayoutMapper;
this.userService = userService; this.userService = userService;
} }
@@ -39,7 +50,7 @@ public class ApiRequestMgmtController {
@PostMapping("/mgmt/collections/create.do") @PostMapping("/mgmt/collections/create.do")
public ResponseEntity<String> createApiCollection( public ResponseEntity<String> createApiCollection(
@RequestBody ApiCollectionDTO dto) { @RequestBody ApiCollectionDTO dto) {
ApiCollection collection = apiRequestMapper.map(dto); ApiCollection collection = apiRequestMapper.map(dto);
StaffUser user = userService.findByEsntlId(SecurityUtil.getCurrentUserEsntlId()); StaffUser user = userService.findByEsntlId(SecurityUtil.getCurrentUserEsntlId());
@@ -64,18 +75,28 @@ public class ApiRequestMgmtController {
} }
@PostMapping("/mgmt/collections/{id}/apis/save.do") @PostMapping("/mgmt/collections/{id}/apis/save.do")
public ResponseEntity<String> addApiToCollection(@PathVariable(name = "id") String id, @Transactional
@RequestBody ApiRequestDTO dto) { public ResponseEntity<String> addApiToCollection(@PathVariable(name = "id") String id, @RequestBody ApiRequestDTO dto) {
ApiRequestInfo apiRequestInfo = apiRequestMapper.map(dto); ApiRequestInfo apiRequestInfo = apiRequestMapper.map(dto);
StaffUser user = userService.findByEsntlId(SecurityUtil.getCurrentUserEsntlId()); StaffUser user = userService.findByEsntlId(SecurityUtil.getCurrentUserEsntlId());
apiRequestInfo.setOwner(user); apiRequestInfo.setOwner(user);
String apiId = apiRequestMgmtService.saveApiToCollection(id, apiRequestInfo); String apiId = apiRequestMgmtService.saveApiToCollection(id, apiRequestInfo);
if (dto.getLayout() != null) {
dto.getLayout().setApiRequestId(apiId);
apiLayoutMgmtService.saveLayout(apiLayoutMapper.map(dto.getLayout()));
}
return ResponseEntity.ok(apiId); return ResponseEntity.ok(apiId);
} }
@PostMapping("/mgmt/collections/{id}/apis/delete.do") @PostMapping("/mgmt/collections/{id}/apis/delete.do")
public ResponseEntity<String> removeApiFromCollection(@PathVariable(name = "id") String id, @Transactional
@RequestBody ApiRequestDTO dto) { public ResponseEntity<String> removeApiFromCollection(@PathVariable(name = "id") String id, @RequestBody ApiRequestDTO dto) {
ApiLayout layout = apiLayoutMgmtService.findLayoutByApiRequest(dto.getId());
if (layout != null) {
apiLayoutMgmtService.deleteLayout(layout.getId());
}
apiRequestMgmtService.removeApiFromCollection(id, dto.getId()); apiRequestMgmtService.removeApiFromCollection(id, dto.getId());
return ResponseEntity.ok(SUCCESS); return ResponseEntity.ok(SUCCESS);
} }
@@ -0,0 +1,167 @@
package com.eactive.testmaster.client.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@JsonIgnoreProperties(ignoreUnknown = true)
public class ApiLayoutDTO implements Serializable {
private static final long serialVersionUID = 1L;
@JsonProperty("id")
private String id;
@JsonProperty("APIREQUESTID")
private String apiRequestId;
@JsonProperty("LOUTNAME")
private String loutName;
@JsonProperty("LOUTPTRNNAME")
private String loutPtrnName;
@JsonProperty("LOUTDESC")
private String loutDesc;
@JsonProperty("EAIBZWKDSTCD")
private String eaiBzwkDstCd;
@JsonProperty("UAPPLNAME")
private String uApplName;
@JsonProperty("SYSINTFACNAME")
private String sysIntFacName;
@JsonProperty("AUTHOR")
private String author;
@JsonProperty("EAISEVRDSTCD")
private String eaiSevRdStCd;
@JsonProperty("MODFMGTSTUSDSTCD")
private String modfMgtStusDstCd;
@JsonProperty("USEYN")
private String useYn;
@JsonProperty("VERINFO")
private String verInfo;
@JsonProperty("layoutItems")
List<ApiLayoutItemDTO> layoutItems = new ArrayList<>();
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getApiRequestId() {
return apiRequestId;
}
public void setApiRequestId(String apiRequestId) {
this.apiRequestId = apiRequestId;
}
public String getLoutName() {
return loutName;
}
public void setLoutName(String loutName) {
this.loutName = loutName;
}
public String getLoutPtrnName() {
return loutPtrnName;
}
public void setLoutPtrnName(String loutPtrnName) {
this.loutPtrnName = loutPtrnName;
}
public String getLoutDesc() {
return loutDesc;
}
public void setLoutDesc(String loutDesc) {
this.loutDesc = loutDesc;
}
public String getEaiBzwkDstCd() {
return eaiBzwkDstCd;
}
public void setEaiBzwkDstCd(String eaiBzwkDstCd) {
this.eaiBzwkDstCd = eaiBzwkDstCd;
}
public String getuApplName() {
return uApplName;
}
public void setuApplName(String uApplName) {
this.uApplName = uApplName;
}
public String getSysIntFacName() {
return sysIntFacName;
}
public void setSysIntFacName(String sysIntFacName) {
this.sysIntFacName = sysIntFacName;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getEaiSevRdStCd() {
return eaiSevRdStCd;
}
public void setEaiSevRdStCd(String eaiSevRdStCd) {
this.eaiSevRdStCd = eaiSevRdStCd;
}
public String getModfMgtStusDstCd() {
return modfMgtStusDstCd;
}
public void setModfMgtStusDstCd(String modfMgtStusDstCd) {
this.modfMgtStusDstCd = modfMgtStusDstCd;
}
public String getUseYn() {
return useYn;
}
public void setUseYn(String useYn) {
this.useYn = useYn;
}
public String getVerInfo() {
return verInfo;
}
public void setVerInfo(String verInfo) {
this.verInfo = verInfo;
}
public List<ApiLayoutItemDTO> getLayoutItems() {
return layoutItems;
}
public void setLayoutItems(List<ApiLayoutItemDTO> layoutItems) {
this.layoutItems = layoutItems;
}
}
@@ -0,0 +1,275 @@
package com.eactive.testmaster.client.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.Serializable;
@JsonIgnoreProperties(ignoreUnknown = true)
public class ApiLayoutItemDTO implements Serializable {
private static final long serialVersionUID = 1L;
@JsonProperty("itemId")
private Long itemId;
@JsonProperty("id")
private Long id;
@JsonProperty("parent")
private Long parent;
@JsonProperty("level")
private Integer level;
@JsonProperty("loutName")
private String loutName;
@JsonProperty("loutItemSerno")
private Integer loutItemSerno;
@JsonProperty("loutItemName")
private String loutItemName;
@JsonProperty("loutItemDesc")
private String loutItemDesc;
@JsonProperty("loutItemDepth")
private Integer loutItemDepth;
@JsonProperty("loutItemType")
private String loutItemType;
@JsonProperty("loutItemOccCnt")
private String loutItemOccCnt;
@JsonProperty("loutItemOccRef")
private String loutItemOccRef;
@JsonProperty("loutItemDataType")
private String loutItemDataType;
@JsonProperty("loutItemLength")
private Integer loutItemLength;
@JsonProperty("loutItemDecimal")
private Integer loutItemDecimal;
@JsonProperty("loutItemDefault")
private String loutItemDefault;
@JsonProperty("loutItemMaskYn")
private String loutItemMaskYn;
@JsonProperty("loutItemMaskOffset")
private Integer loutItemMaskOffset;
@JsonProperty("loutItemMaskLength")
private Integer loutItemMaskLength;
@JsonProperty("parentLoutItemIndex")
private Integer parentLoutItemIndex;
@JsonProperty("expanded")
private Boolean expanded;
@JsonProperty("isLeaf")
private Boolean isLeaf;
@JsonProperty("LOUTITEMPATH")
private String loutItemPath;
@JsonProperty(value = "value", defaultValue = "")
private String value;
public Long getItemId() {
return itemId;
}
public void setItemId(Long itemId) {
this.itemId = itemId;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getParent() {
return parent;
}
public void setParent(Long parent) {
this.parent = parent;
}
public Integer getLevel() {
return level;
}
public void setLevel(Integer level) {
this.level = level;
}
public String getLoutName() {
return loutName;
}
public void setLoutName(String loutName) {
this.loutName = loutName;
}
public Integer getLoutItemSerno() {
return loutItemSerno;
}
public void setLoutItemSerno(Integer loutItemSerno) {
this.loutItemSerno = loutItemSerno;
}
public String getLoutItemName() {
return loutItemName;
}
public void setLoutItemName(String loutItemName) {
this.loutItemName = loutItemName;
}
public String getLoutItemDesc() {
return loutItemDesc;
}
public void setLoutItemDesc(String loutItemDesc) {
this.loutItemDesc = loutItemDesc;
}
public Integer getLoutItemDepth() {
return loutItemDepth;
}
public void setLoutItemDepth(Integer loutItemDepth) {
this.loutItemDepth = loutItemDepth;
}
public String getLoutItemType() {
return loutItemType;
}
public void setLoutItemType(String loutItemType) {
this.loutItemType = loutItemType;
}
public String getLoutItemOccCnt() {
return loutItemOccCnt;
}
public void setLoutItemOccCnt(String loutItemOccCnt) {
this.loutItemOccCnt = loutItemOccCnt;
}
public String getLoutItemOccRef() {
return loutItemOccRef;
}
public void setLoutItemOccRef(String loutItemOccRef) {
this.loutItemOccRef = loutItemOccRef;
}
public String getLoutItemDataType() {
return loutItemDataType;
}
public void setLoutItemDataType(String loutItemDataType) {
this.loutItemDataType = loutItemDataType;
}
public Integer getLoutItemLength() {
return loutItemLength;
}
public void setLoutItemLength(Integer loutItemLength) {
this.loutItemLength = loutItemLength;
}
public Integer getLoutItemDecimal() {
return loutItemDecimal;
}
public void setLoutItemDecimal(Integer loutItemDecimal) {
this.loutItemDecimal = loutItemDecimal;
}
public String getLoutItemDefault() {
return loutItemDefault;
}
public void setLoutItemDefault(String loutItemDefault) {
this.loutItemDefault = loutItemDefault;
}
public String getLoutItemMaskYn() {
return loutItemMaskYn;
}
public void setLoutItemMaskYn(String loutItemMaskYn) {
this.loutItemMaskYn = loutItemMaskYn;
}
public Integer getLoutItemMaskOffset() {
return loutItemMaskOffset;
}
public void setLoutItemMaskOffset(Integer loutItemMaskOffset) {
this.loutItemMaskOffset = loutItemMaskOffset;
}
public Integer getLoutItemMaskLength() {
return loutItemMaskLength;
}
public void setLoutItemMaskLength(Integer loutItemMaskLength) {
this.loutItemMaskLength = loutItemMaskLength;
}
public Integer getParentLoutItemIndex() {
return parentLoutItemIndex;
}
public void setParentLoutItemIndex(Integer parentLoutItemIndex) {
this.parentLoutItemIndex = parentLoutItemIndex;
}
public Boolean getExpanded() {
return expanded;
}
public void setExpanded(Boolean expanded) {
this.expanded = expanded;
}
public Boolean getLeaf() {
return isLeaf;
}
public void setLeaf(Boolean leaf) {
isLeaf = leaf;
}
public String getLoutItemPath() {
return loutItemPath;
}
public void setLoutItemPath(String loutItemPath) {
this.loutItemPath = loutItemPath;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
@@ -33,7 +33,7 @@ public class ApiRequestDTO implements Serializable {
private String postRequestScript; private String postRequestScript;
private long sentBytes; //estimated size of the request private ApiLayoutDTO layout;
public String getId() { public String getId() {
return id; return id;
@@ -131,11 +131,11 @@ public class ApiRequestDTO implements Serializable {
this.postRequestScript = postRequestScript; this.postRequestScript = postRequestScript;
} }
// public long getSentBytes() { public ApiLayoutDTO getLayout() {
// return sentBytes; return layout;
// } }
//
// public void setSentBytes(long sentBytes) { public void setLayout(ApiLayoutDTO layout) {
// this.sentBytes = sentBytes; this.layout = layout;
// } }
} }
@@ -0,0 +1,167 @@
package com.eactive.testmaster.client.entity;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
@Entity
@Table(name = "API_LAYOUT")
public class ApiLayout {
@Id
private String id;
@ManyToOne
@JoinColumn(name = "API_REQUEST_INFO_ID")
private ApiRequestInfo apiRequestInfo;
private String loutName;
private String loutPtrnName;
private String loutDesc;
private String eaiBzwkDstCd;
private String uApplName;
private String sysIntFacName;
private String author;
private String eaiSevRdStCd;
private String modfMgtStusDstCd;
private String useYn;
private String verInfo;
@OneToMany(mappedBy = "apiLayout", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private List<ApiLayoutItem> layoutItems = new ArrayList<>();
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public ApiRequestInfo getApiRequestInfo() {
return apiRequestInfo;
}
public void setApiRequestInfo(ApiRequestInfo apiRequestInfo) {
this.apiRequestInfo = apiRequestInfo;
}
public String getLoutName() {
return loutName;
}
public void setLoutName(String loutName) {
this.loutName = loutName;
}
public String getLoutPtrnName() {
return loutPtrnName;
}
public void setLoutPtrnName(String loutPtrnName) {
this.loutPtrnName = loutPtrnName;
}
public String getLoutDesc() {
return loutDesc;
}
public void setLoutDesc(String loutDesc) {
this.loutDesc = loutDesc;
}
public String getEaiBzwkDstCd() {
return eaiBzwkDstCd;
}
public void setEaiBzwkDstCd(String eaiBzwkDstCd) {
this.eaiBzwkDstCd = eaiBzwkDstCd;
}
public String getUApplName() {
return uApplName;
}
public void setUApplName(String uApplName) {
this.uApplName = uApplName;
}
public String getSysIntFacName() {
return sysIntFacName;
}
public void setSysIntFacName(String sysIntFacName) {
this.sysIntFacName = sysIntFacName;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getEaiSevRdStCd() {
return eaiSevRdStCd;
}
public void setEaiSevRdStCd(String eaiSevRdStCd) {
this.eaiSevRdStCd = eaiSevRdStCd;
}
public String getModfMgtStusDstCd() {
return modfMgtStusDstCd;
}
public void setModfMgtStusDstCd(String modfMgtStusDstCd) {
this.modfMgtStusDstCd = modfMgtStusDstCd;
}
public String getUseYn() {
return useYn;
}
public void setUseYn(String useYn) {
this.useYn = useYn;
}
public String getVerInfo() {
return verInfo;
}
public void setVerInfo(String verInfo) {
this.verInfo = verInfo;
}
public List<ApiLayoutItem> getLayoutItems() {
return layoutItems;
}
public void setLayoutItems(List<ApiLayoutItem> layoutItems) {
this.layoutItems = layoutItems;
}
public void addApiLayoutItem(ApiLayoutItem item) {
item.setApiLayout(this);
this.layoutItems.add(item);
}
}
@@ -0,0 +1,270 @@
package com.eactive.testmaster.client.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
@Entity
@Table(name = "API_LAYOUT_ITEM")
public class ApiLayoutItem {
@ManyToOne
@JoinColumn(name = "API_LAYOUT_ID", nullable = false)
private ApiLayout apiLayout; // This is the back-reference to the parent
@Id
@Column(name = "API_LAYOUT_ITEM_ID")
@GeneratedValue(strategy = GenerationType.AUTO)
private Long itemId;
private Long id;
private Long parent;
private Integer level;
private String loutName;
private Integer loutItemSerno;
private String loutItemName;
private String loutItemDesc;
private Integer loutItemDepth;
private String loutItemType;
private String loutItemOccCnt;
private String loutItemOccRef;
private String loutItemDataType;
private Integer loutItemLength;
private Integer loutItemDecimal;
private String loutItemDefault;
private String loutItemMaskYn;
private Integer loutItemMaskOffset;
private Integer loutItemMaskLength;
private Integer parentLoutItemIndex;
private Boolean expanded;
private Boolean isLeaf;
private String loutItemPath;
private String value;
public Long getItemId() {
return itemId;
}
public void setItemId(Long itemId) {
this.itemId = itemId;
}
public ApiLayout getApiLayout() {
return apiLayout;
}
public void setApiLayout(ApiLayout apiLayout) {
this.apiLayout = apiLayout;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getParent() {
return parent;
}
public void setParent(Long parent) {
this.parent = parent;
}
public Integer getLevel() {
return level;
}
public void setLevel(Integer level) {
this.level = level;
}
public String getLoutName() {
return loutName;
}
public void setLoutName(String loutName) {
this.loutName = loutName;
}
public Integer getLoutItemSerno() {
return loutItemSerno;
}
public void setLoutItemSerno(Integer loutItemSerno) {
this.loutItemSerno = loutItemSerno;
}
public String getLoutItemName() {
return loutItemName;
}
public void setLoutItemName(String loutItemName) {
this.loutItemName = loutItemName;
}
public String getLoutItemDesc() {
return loutItemDesc;
}
public void setLoutItemDesc(String loutItemDesc) {
this.loutItemDesc = loutItemDesc;
}
public Integer getLoutItemDepth() {
return loutItemDepth;
}
public void setLoutItemDepth(Integer loutItemDepth) {
this.loutItemDepth = loutItemDepth;
}
public String getLoutItemType() {
return loutItemType;
}
public void setLoutItemType(String loutItemType) {
this.loutItemType = loutItemType;
}
public String getLoutItemOccCnt() {
return loutItemOccCnt;
}
public void setLoutItemOccCnt(String loutItemOccCnt) {
this.loutItemOccCnt = loutItemOccCnt;
}
public String getLoutItemOccRef() {
return loutItemOccRef;
}
public void setLoutItemOccRef(String loutItemOccRef) {
this.loutItemOccRef = loutItemOccRef;
}
public String getLoutItemDataType() {
return loutItemDataType;
}
public void setLoutItemDataType(String loutItemDataType) {
this.loutItemDataType = loutItemDataType;
}
public Integer getLoutItemLength() {
return loutItemLength;
}
public void setLoutItemLength(Integer loutItemLength) {
this.loutItemLength = loutItemLength;
}
public Integer getLoutItemDecimal() {
return loutItemDecimal;
}
public void setLoutItemDecimal(Integer loutItemDecimal) {
this.loutItemDecimal = loutItemDecimal;
}
public String getLoutItemDefault() {
return loutItemDefault;
}
public void setLoutItemDefault(String loutItemDefault) {
this.loutItemDefault = loutItemDefault;
}
public String getLoutItemMaskYn() {
return loutItemMaskYn;
}
public void setLoutItemMaskYn(String loutItemMaskYn) {
this.loutItemMaskYn = loutItemMaskYn;
}
public Integer getLoutItemMaskOffset() {
return loutItemMaskOffset;
}
public void setLoutItemMaskOffset(Integer loutItemMaskOffset) {
this.loutItemMaskOffset = loutItemMaskOffset;
}
public Integer getLoutItemMaskLength() {
return loutItemMaskLength;
}
public void setLoutItemMaskLength(Integer loutItemMaskLength) {
this.loutItemMaskLength = loutItemMaskLength;
}
public Integer getParentLoutItemIndex() {
return parentLoutItemIndex;
}
public void setParentLoutItemIndex(Integer parentLoutItemIndex) {
this.parentLoutItemIndex = parentLoutItemIndex;
}
public Boolean getExpanded() {
return expanded;
}
public void setExpanded(Boolean expanded) {
this.expanded = expanded;
}
public Boolean getLeaf() {
return isLeaf;
}
public void setLeaf(Boolean leaf) {
isLeaf = leaf;
}
public String getLoutItemPath() {
return loutItemPath;
}
public void setLoutItemPath(String loutItemPath) {
this.loutItemPath = loutItemPath;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
@@ -0,0 +1,39 @@
package com.eactive.testmaster.client.mapper;
import com.eactive.testmaster.client.dto.ApiLayoutItemDTO;
import com.eactive.testmaster.client.entity.ApiLayoutItem;
import com.eactive.testmaster.client.repository.ApiLayoutItemRepository;
import com.eactive.testmaster.common.mapper.CommonMapper;
import java.util.List;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Mapper(componentModel = "spring",
uses = {CommonMapper.class},
unmappedTargetPolicy = ReportingPolicy.IGNORE)
@Component
public abstract class ApiLayoutItemMapper {
@Autowired
private ApiLayoutItemRepository apiLayoutItemRepository;
public abstract ApiLayoutItem map(ApiLayoutItemDTO dto);
public abstract ApiLayoutItemDTO map(ApiLayoutItem entity);
public ApiLayoutItem map(Long id) {
if (id == null) {
return null;
}
return apiLayoutItemRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("No ApiLayoutItem found for ID: " + id));
}
// Method to map lists of ApiLayoutItemDTOs to lists of ApiLayoutItem entities
public abstract List<ApiLayoutItem> mapItems(List<ApiLayoutItemDTO> dtos);
// Method to map lists of ApiLayoutItem entities to lists of ApiLayoutItemDTOs
public abstract List<ApiLayoutItemDTO> mapItemDTOs(List<ApiLayoutItem> entities);
}
@@ -0,0 +1,60 @@
package com.eactive.testmaster.client.mapper;
import com.eactive.testmaster.client.dto.ApiLayoutDTO;
import com.eactive.testmaster.client.entity.ApiLayout;
import com.eactive.testmaster.client.entity.ApiRequestInfo;
import com.eactive.testmaster.client.repository.ApiLayoutRepository;
import com.eactive.testmaster.client.repository.ApiRequestRepository;
import com.eactive.testmaster.common.mapper.CommonMapper;
import java.util.List;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ReportingPolicy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Mapper(componentModel = "spring",
uses = {CommonMapper.class, ApiLayoutItemMapper.class},
unmappedTargetPolicy = ReportingPolicy.IGNORE)
@Component
public abstract class ApiLayoutMapper {
@Autowired
private ApiLayoutRepository apiLayoutRepository;
@Autowired
private ApiRequestRepository apiRequestRepository;
@Mapping(target = "apiRequestInfo", source = "apiRequestId")
public abstract ApiLayout map(ApiLayoutDTO dto);
@Mapping(target = "apiRequestId", source = "apiRequestInfo")
public abstract ApiLayoutDTO map(ApiLayout entity);
public ApiRequestInfo mapApiRequestInfo(String id) {
if (id == null) {
return null;
}
return apiRequestRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("No ApiRequest found for ID: " + id));
}
public String mapApiRequestId(ApiRequestInfo entity) {
if (entity == null) {
return null;
}
return entity.getId();
}
public ApiLayout map(String id) {
if (id == null) {
return null;
}
return apiLayoutRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("No ApiLayout found for ID: " + id));
}
// public abstract List<ApiLayout> mapLayouts(List<ApiLayoutDTO> dtos);
public abstract List<ApiLayoutDTO> mapLayoutDTOs(List<ApiLayout> entities);
}
@@ -3,40 +3,73 @@ package com.eactive.testmaster.client.mapper;
import com.eactive.testmaster.client.dto.ApiCollectionDTO; import com.eactive.testmaster.client.dto.ApiCollectionDTO;
import com.eactive.testmaster.client.dto.ApiRequestDTO; import com.eactive.testmaster.client.dto.ApiRequestDTO;
import com.eactive.testmaster.client.entity.ApiCollection; import com.eactive.testmaster.client.entity.ApiCollection;
import com.eactive.testmaster.client.entity.ApiLayout;
import com.eactive.testmaster.client.entity.ApiLayoutItem;
import com.eactive.testmaster.client.entity.ApiRequestInfo; import com.eactive.testmaster.client.entity.ApiRequestInfo;
import com.eactive.testmaster.client.repository.ApiLayoutRepository;
import com.eactive.testmaster.client.repository.ApiRequestRepository; import com.eactive.testmaster.client.repository.ApiRequestRepository;
import com.eactive.testmaster.common.mapper.CommonMapper; import com.eactive.testmaster.common.mapper.CommonMapper;
import com.eactive.testmaster.server.mapper.ServerMapper; import com.eactive.testmaster.server.mapper.ServerMapper;
import java.util.Comparator;
import java.util.List;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.ReportingPolicy; import org.mapstruct.ReportingPolicy;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.List;
@Mapper(componentModel = "spring", @Mapper(componentModel = "spring",
uses = {CommonMapper.class, ServerMapper.class}, uses = {CommonMapper.class, ServerMapper.class},
unmappedTargetPolicy = ReportingPolicy.IGNORE) unmappedTargetPolicy = ReportingPolicy.IGNORE)
@Component @Component
public abstract class ApiRequestMapper { public abstract class ApiRequestMapper {
@Autowired @Autowired
private ApiRequestRepository apiRequestRepository; private ApiRequestRepository apiRequestRepository;
@Autowired
private ApiLayoutRepository apiLayoutRepository;
@Autowired
private ApiLayoutMapper apiLayoutMapper;
public abstract ApiCollection map(ApiCollectionDTO dto); public abstract ApiCollection map(ApiCollectionDTO dto);
public abstract ApiRequestInfo map(ApiRequestDTO dto); public abstract ApiRequestInfo map(ApiRequestDTO dto);
@Mapping(target = "layout", ignore = true)
public abstract ApiRequestDTO map(ApiRequestInfo entity); public abstract ApiRequestDTO map(ApiRequestInfo entity);
public String mapId(ApiRequestInfo entity) {
if (entity == null) {
return null;
}
return entity.getId();
}
public ApiRequestInfo map(String id) { public ApiRequestInfo map(String id) {
if(id == null) { if (id == null) {
return null; return null;
} }
return apiRequestRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("No ApiRequest found for ID: " + id)); return apiRequestRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("No ApiRequest found for ID: " + id));
} }
@AfterMapping
public void handleTypeSpecificMapping(ApiRequestInfo entity, @MappingTarget ApiRequestDTO dto) {
if ("TCP".equals(entity.getType())) {
ApiLayout layout = apiLayoutRepository.findFirstByApiRequestInfo(entity);
if (layout!=null) {
layout.getLayoutItems().sort(Comparator.comparingInt(ApiLayoutItem::getLoutItemSerno));
dto.setLayout(apiLayoutMapper.map(layout));
}
}
}
public abstract List<ApiRequestInfo> mapApis(List<ApiRequestDTO> apis); public abstract List<ApiRequestInfo> mapApis(List<ApiRequestDTO> apis);
public abstract List<ApiCollectionDTO> mapCollections(List<ApiCollection> apis); public abstract List<ApiCollectionDTO> mapCollections(List<ApiCollection> apis);
@@ -0,0 +1,9 @@
package com.eactive.testmaster.client.repository;
import com.eactive.testmaster.client.entity.ApiLayoutItem;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface ApiLayoutItemRepository extends JpaRepository<ApiLayoutItem, Long>, JpaSpecificationExecutor<ApiLayoutItem> {
}
@@ -0,0 +1,11 @@
package com.eactive.testmaster.client.repository;
import com.eactive.testmaster.client.entity.ApiLayout;
import com.eactive.testmaster.client.entity.ApiRequestInfo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface ApiLayoutRepository extends JpaRepository<ApiLayout, String>, JpaSpecificationExecutor<ApiLayout> {
ApiLayout findFirstByApiRequestInfo(ApiRequestInfo apiRequestInfo);
}
@@ -0,0 +1,86 @@
package com.eactive.testmaster.client.service;
import com.eactive.testmaster.client.entity.ApiLayout;
import com.eactive.testmaster.client.entity.ApiRequestInfo;
import com.eactive.testmaster.client.repository.ApiLayoutRepository;
import com.eactive.testmaster.client.repository.ApiRequestRepository;
import com.eactive.testmaster.common.exception.NotFoundException;
import java.util.UUID;
import javax.transaction.Transactional;
import org.springframework.stereotype.Service;
@Service
@Transactional
public class ApiLayoutMgmtService {
private final ApiLayoutRepository apiLayoutRepository;
private final ApiRequestRepository apiRequestRepository;
public ApiLayoutMgmtService(ApiRequestRepository apiRequestRepository, ApiLayoutRepository apiLayoutRepository) {
this.apiRequestRepository = apiRequestRepository;
this.apiLayoutRepository = apiLayoutRepository;
}
public ApiLayout findLayoutByApiRequest(String apiRequestId) {
ApiRequestInfo apiRequestInfo = apiRequestRepository.findById(apiRequestId)
.orElseThrow(() -> new NotFoundException(String.format("ApiRequestInfo with ID %s not found", apiRequestId)));
return apiLayoutRepository.findFirstByApiRequestInfo(apiRequestInfo);
}
public ApiLayout saveLayout(ApiLayout layout) {
if (layout.getId() == null) {
return createLayout(layout);
} else {
return updateLayout(layout.getId(), layout);
}
}
public ApiLayout createLayout(ApiLayout layout) {
layout.setId(UUID.randomUUID().toString());
if (layout.getLayoutItems() != null && !layout.getLayoutItems().isEmpty()) {
layout.getLayoutItems().forEach(layoutItem -> {
layoutItem.setApiLayout(layout);
});
}
return apiLayoutRepository.save(layout);
}
public ApiLayout updateLayout(String layoutId, ApiLayout updatedLayout) {
ApiLayout layout = apiLayoutRepository.findById(layoutId)
.orElseThrow(() -> new NotFoundException(String.format("Layout with ID %s not found", layoutId)));
// Update fields based on the values provided in updatedLayout
layout.setLoutName(updatedLayout.getLoutName());
layout.setLoutDesc(updatedLayout.getLoutDesc());
layout.setLoutPtrnName(updatedLayout.getLoutPtrnName());
layout.setLoutDesc(updatedLayout.getLoutDesc());
layout.setEaiBzwkDstCd(updatedLayout.getEaiBzwkDstCd());
layout.setUApplName(updatedLayout.getUApplName());
layout.setSysIntFacName(updatedLayout.getSysIntFacName());
layout.setAuthor(updatedLayout.getAuthor());
layout.setEaiSevRdStCd(updatedLayout.getEaiSevRdStCd());
layout.setModfMgtStusDstCd(updatedLayout.getModfMgtStusDstCd());
layout.setUseYn(updatedLayout.getUseYn());
layout.setVerInfo(updatedLayout.getVerInfo());
if (updatedLayout.getLayoutItems() != null && !updatedLayout.getLayoutItems().isEmpty()) {
layout.getLayoutItems().clear();
updatedLayout.getLayoutItems().forEach(layoutItem -> {
layoutItem.setApiLayout(layout); // Set back reference to layout
layout.getLayoutItems().add(layoutItem);
});
}
return apiLayoutRepository.save(layout);
}
// Method to delete a layout
public void deleteLayout(String id) {
ApiLayout layout = apiLayoutRepository.findById(id)
.orElseThrow(() -> new NotFoundException(String.format("Layout with ID %s not found", id)));
apiLayoutRepository.delete(layout);
}
}
@@ -85,22 +85,6 @@ public class NettyTcpApiClient implements ApiClient {
ChannelFuture f = b.connect(host, port).sync(); ChannelFuture f = b.connect(host, port).sync();
f.channel().closeFuture().sync(); f.channel().closeFuture().sync();
// Wait for the response
return answer.take(); // This will block until a message is available return answer.take(); // This will block until a message is available
} }
// public static void main(String[] args) {
// NettyTcpApiClient client = new NettyTcpApiClient();
// try {
// String response = client.sendMessage("localhost", 30913, "00340000BASICTST010134567890ABCDEF");
// System.out.println("Server replied: " + response);
// } catch (InterruptedException e) {
// Thread.currentThread().interrupt();
// e.printStackTrace();
// } finally {
// client.shutdown();
// }
// }
} }
@@ -0,0 +1,25 @@
package com.eactive.testmaster.client.service.internal;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
public class FixedLengthFrameDecoder extends ByteToMessageDecoder {
private final int frameLength;
public FixedLengthFrameDecoder(int frameLength) {
if (frameLength <= 0) {
throw new IllegalArgumentException("frameLength must be a positive integer: " + frameLength);
}
this.frameLength = frameLength;
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
while (in.readableBytes() >= frameLength) {
ByteBuf buf = in.readBytes(frameLength);
out.add(buf);
}
}
}
@@ -22,6 +22,18 @@ public class ServerDTO {
private boolean enabled = true; private boolean enabled = true;
private boolean messageKeyInclude = false;
private Integer messageKeyOffset;
private Integer messageKeyLength;
private boolean lengthFieldInclude = false;
private Integer lengthFieldOffset;
private Integer lengthFieldLength;
public Long getId() { public Long getId() {
return id; return id;
} }
@@ -77,4 +89,52 @@ public class ServerDTO {
public void setEnabled(boolean enabled) { public void setEnabled(boolean enabled) {
this.enabled = enabled; this.enabled = enabled;
} }
public boolean isMessageKeyInclude() {
return messageKeyInclude;
}
public void setMessageKeyInclude(boolean messageKeyInclude) {
this.messageKeyInclude = messageKeyInclude;
}
public Integer getMessageKeyOffset() {
return messageKeyOffset;
}
public void setMessageKeyOffset(Integer messageKeyOffset) {
this.messageKeyOffset = messageKeyOffset;
}
public Integer getMessageKeyLength() {
return messageKeyLength;
}
public void setMessageKeyLength(Integer messageKeyLength) {
this.messageKeyLength = messageKeyLength;
}
public boolean isLengthFieldInclude() {
return lengthFieldInclude;
}
public void setLengthFieldInclude(boolean lengthFieldInclude) {
this.lengthFieldInclude = lengthFieldInclude;
}
public Integer getLengthFieldOffset() {
return lengthFieldOffset;
}
public void setLengthFieldOffset(Integer lengthFieldOffset) {
this.lengthFieldOffset = lengthFieldOffset;
}
public Integer getLengthFieldLength() {
return lengthFieldLength;
}
public void setLengthFieldLength(Integer lengthFieldLength) {
this.lengthFieldLength = lengthFieldLength;
}
} }
@@ -23,6 +23,24 @@ public class Server {
@Column(name = "IS_ENABLED", columnDefinition = "boolean default true") @Column(name = "IS_ENABLED", columnDefinition = "boolean default true")
private boolean enabled; private boolean enabled;
@Column(name = "MESSAGE_KEY_INCLUDE", columnDefinition = "boolean default false")
private boolean messageKeyInclude;
@Column(name = "MESSAGE_KEY_OFFSET", columnDefinition = "integer")
private Integer messageKeyOffset;
@Column(name = "MESSAGE_KEY_LENGTH", columnDefinition = "integer")
private Integer messageKeyLength;
@Column(name = "LENGTH_FIELD_INCLUDE", columnDefinition = "boolean default false")
private boolean lengthFieldInclude;
@Column(name = "LENGTH_FIELD_OFFSET", columnDefinition = "integer")
private Integer lengthFieldOffset;
@Column(name = "LENGTH_FIELD_LENGTH", columnDefinition = "integer")
private Integer lengthFieldLength;
public Long getId() { public Long getId() {
return id; return id;
} }
@@ -71,7 +89,6 @@ public class Server {
this.port = port; this.port = port;
} }
public boolean isEnabled() { public boolean isEnabled() {
return enabled; return enabled;
} }
@@ -80,6 +97,54 @@ public class Server {
this.enabled = enabled; this.enabled = enabled;
} }
public boolean isMessageKeyInclude() {
return messageKeyInclude;
}
public void setMessageKeyInclude(boolean messageKeyInclude) {
this.messageKeyInclude = messageKeyInclude;
}
public boolean isLengthFieldInclude() {
return lengthFieldInclude;
}
public void setLengthFieldInclude(boolean lengthFieldInclude) {
this.lengthFieldInclude = lengthFieldInclude;
}
public Integer getMessageKeyOffset() {
return messageKeyOffset;
}
public void setMessageKeyOffset(Integer messageKeyOffset) {
this.messageKeyOffset = messageKeyOffset;
}
public Integer getMessageKeyLength() {
return messageKeyLength;
}
public void setMessageKeyLength(Integer messageKeyLength) {
this.messageKeyLength = messageKeyLength;
}
public Integer getLengthFieldOffset() {
return lengthFieldOffset;
}
public void setLengthFieldOffset(Integer lengthFieldOffset) {
this.lengthFieldOffset = lengthFieldOffset;
}
public Integer getLengthFieldLength() {
return lengthFieldLength;
}
public void setLengthFieldLength(Integer lengthFieldLength) {
this.lengthFieldLength = lengthFieldLength;
}
@Transient @Transient
public String getFullHostAddress() { public String getFullHostAddress() {
return scheme + "://" + hostname + (port == null ? "" : ":" + port) + basePath; return scheme + "://" + hostname + (port == null ? "" : ":" + port) + basePath;
@@ -47,6 +47,12 @@ public class ServerMapper {
dto.setScheme(server.getScheme()); dto.setScheme(server.getScheme());
dto.setBasePath(server.getBasePath()); dto.setBasePath(server.getBasePath());
dto.setEnabled(server.isEnabled()); dto.setEnabled(server.isEnabled());
dto.setLengthFieldInclude(server.isLengthFieldInclude());
dto.setMessageKeyInclude(server.isMessageKeyInclude());
dto.setLengthFieldLength(server.getLengthFieldLength());
dto.setLengthFieldOffset(server.getLengthFieldOffset());
dto.setMessageKeyLength(server.getMessageKeyLength());
dto.setMessageKeyOffset(server.getMessageKeyOffset());
return dto; return dto;
} }
@@ -66,6 +72,12 @@ public class ServerMapper {
server.setScheme(dto.getScheme()); server.setScheme(dto.getScheme());
server.setBasePath(dto.getBasePath()); server.setBasePath(dto.getBasePath());
server.setEnabled(dto.isEnabled()); server.setEnabled(dto.isEnabled());
server.setLengthFieldInclude(dto.isLengthFieldInclude());
server.setMessageKeyInclude(dto.isMessageKeyInclude());
server.setLengthFieldLength(dto.getLengthFieldLength());
server.setLengthFieldOffset(dto.getLengthFieldOffset());
server.setMessageKeyLength(dto.getMessageKeyLength());
server.setMessageKeyOffset(dto.getMessageKeyOffset());
return server; return server;
} }
@@ -39,6 +39,12 @@ public class ServerMgmtService {
server.setBasePath(updated.getBasePath()); server.setBasePath(updated.getBasePath());
server.setPort(updated.getPort()); server.setPort(updated.getPort());
server.setEnabled(updated.isEnabled()); server.setEnabled(updated.isEnabled());
server.setLengthFieldInclude(updated.isLengthFieldInclude());
server.setMessageKeyInclude(updated.isMessageKeyInclude());
server.setMessageKeyLength(updated.getMessageKeyLength());
server.setMessageKeyOffset(updated.getMessageKeyOffset());
server.setLengthFieldLength(updated.getLengthFieldLength());
server.setLengthFieldOffset(updated.getLengthFieldOffset());
serverRepository.save(server); serverRepository.save(server);
+13
View File
@@ -2030,3 +2030,16 @@ footer div.foot_info span {
.query_params .prop-table { .query_params .prop-table {
width: calc(100vw - 240px); width: calc(100vw - 240px);
} }
.layout_table {
width: 1500px;
}
.api_request_info .show {
display: block;
}
.api_request_info .hide {
display: none;
}
File diff suppressed because one or more lines are too long
@@ -27,6 +27,7 @@
<select class="form-select" th:field="*{scheme}" style="border-top-right-radius: 0; border-bottom-right-radius: 0;"> <select class="form-select" th:field="*{scheme}" style="border-top-right-radius: 0; border-bottom-right-radius: 0;">
<option value="http" th:selected="${server.scheme =='http'}">http</option> <option value="http" th:selected="${server.scheme =='http'}">http</option>
<option value="https" th:selected="${server.scheme =='https'}">https</option> <option value="https" th:selected="${server.scheme =='https'}">https</option>
<option value="tcp" th:selected="${server.scheme =='tcp'}">tcp</option>
</select> </select>
<label for="scheme">Scheme</label> <label for="scheme">Scheme</label>
</div> </div>
@@ -54,6 +55,37 @@
</th:block> </th:block>
</div> </div>
</div> </div>
<!-- TCP-specific fields -->
<div id="tcpFields" style="display: none;">
<div class="input-group">
<div class="form-floating mt-2 me-2">
<input type="text" class="form-control" th:field="*{lengthFieldOffset}" placeholder="Length Field Offset">
<label for="lengthFieldOffset">길이 필드 시작 위치</label>
</div>
<div class="form-floating mt-2 me-2">
<input type="text" class="form-control" th:field="*{lengthFieldLength}" placeholder="Length Field Length">
<label for="lengthFieldLength">길이 필드 길이</label>
</div>
<div class="form-check mt-2">
<input class="form-check-input" type="checkbox" th:field="*{lengthFieldInclude}" id="lengthFieldInclude">
<label class="form-check-label" for="lengthFieldInclude">길이 필드 포함</label>
</div>
</div>
<div class="input-group">
<div class="form-floating mt-2 me-2">
<input type="text" class="form-control" th:field="*{messageKeyOffset}" placeholder="Message Key Offset">
<label for="messageKeyOffset">메시지 키 시작 위치</label>
</div>
<div class="form-floating mt-2 me-2">
<input type="text" class="form-control" th:field="*{messageKeyLength}" placeholder="Message Key Length">
<label for="messageKeyLength">메시지 키 길이</label>
</div>
<div class="form-check mt-2">
<input class="form-check-input" type="checkbox" th:field="*{messageKeyInclude}" id="messageKeyInclude">
<label class="form-check-label" for="messageKeyInclude">메시지 키 포함</label>
</div>
</div>
</div>
<div class="form-check mt-2"> <div class="form-check mt-2">
<input type="checkbox" class="form-check-input" th:field="*{enabled}" placeholder="사용 여부"> <input type="checkbox" class="form-check-input" th:field="*{enabled}" placeholder="사용 여부">
<label class="form-check-label" for="enabled">사용?</label> <label class="form-check-label" for="enabled">사용?</label>
@@ -71,6 +103,25 @@
<th:block layout:fragment="contentScript"> <th:block layout:fragment="contentScript">
<script> <script>
$(function () { $(function () {
function toggleTcpFields() {
let scheme = document.querySelector('select[name="scheme"]').value;
console.log(scheme)
let tcpFields = document.getElementById('tcpFields');
if (scheme === 'tcp') {
tcpFields.style.display = 'block';
} else {
tcpFields.style.display = 'none';
}
}
// Run on page load
toggleTcpFields();
// Attach event listener to scheme select element
document.querySelector('select[name="scheme"]').addEventListener('change', toggleTcpFields);
let fnRegister = document.querySelector(".btn_register"); let fnRegister = document.querySelector(".btn_register");
fnRegister.addEventListener("click", function (event) { fnRegister.addEventListener("click", function (event) {
let form = document.getElementById("serverForm"); let form = document.getElementById("serverForm");
@@ -91,4 +142,4 @@
}); });
</script> </script>
</th:block> </th:block>
</html> </html>
@@ -49,9 +49,10 @@
<colgroup> <colgroup>
<col style="width: 5%;"> <col style="width: 5%;">
<col style="width: 5%;"> <col style="width: 5%;">
<col>
<col>
<col style="width: 7%;"> <col style="width: 7%;">
<col> <col style="width: 15%;">
<col>
</colgroup> </colgroup>
<thead> <thead>
<tr> <tr>