플랫 전분 기본 처리 반영

This commit is contained in:
현성필
2024-05-07 13:43:48 +09:00
parent a2aecb03e7
commit ac88b6388e
17 changed files with 1415 additions and 300 deletions
+1 -1
View File
@@ -35,7 +35,7 @@
"dropzone": "^6.0.0-beta.2",
"golden-layout": "^2.6.0",
"jqueryui": "^1.11.1",
"monaco-editor": "^0.45.0",
"monaco-editor": "^0.47.0",
"monaco-editor-workers": "^0.45.0",
"sockjs-client": "^1.6.1",
"split-grid": "^1.0.11",
+3
View File
@@ -92,6 +92,7 @@ class APIScenario {
openScenario(event) {
event.preventDefault();
let scenarioId = $(event.target).closest('li').data('id');
this.stopScenario();
this.openScenarioById(scenarioId);
}
@@ -308,6 +309,7 @@ class APIScenario {
stopScenario() {
if (this.executor) {
this.executor.reset();
this.executor = null;
}
this.outputEditor.setValue('');
}
@@ -353,6 +355,7 @@ class APIScenario {
const y = ui.offset.top - $(this).offset().top;
const dataset = ui.draggable[0].dataset;
console.log(dataset);
var node = `<div><div class="title-box">API</div><div class="box">${dataset.name}</div></div>`;
me.editor.addNode(dataset.name, 1, 1, x, y, dataset.nodeType, dataset, node);
+29 -5
View File
@@ -66,14 +66,27 @@ class APIScenarioExecutor {
let model = JSON.parse(JSON.stringify(this.apiTester.getApiRequestModel(currentNode.data.id)));
model.responseModel = {};
this.appendLog(`===============================================================\nExecuting API [${model.name}]`);
this.apiClient.sendAPIRequest(model, this.logHttpRequest.bind(this)).then(response => {
let request = null;
if (model.type === 'TCP') {
request = this.logTcpRequest;
}else {
request = this.logHttpRequest;
}
this.apiClient.sendAPIRequest(model, request.bind(this)).then(response => {
this.isRunning = false;
this.logHttpResponse(response);
if (response.status === 200 || response.status === 201) {
if (model.type === 'TCP') {
this.logTcpResponse(response);
nodeElement.classList.add('result-success');
} else {
nodeElement.classList.add('result-error');
this.logHttpResponse(response);
if (response.status === 200 || response.status === 201) {
nodeElement.classList.add('result-success');
} else {
nodeElement.classList.add('result-error');
}
}
resolve();
}).catch(error => {
nodeElement.classList.add('result-abort');
@@ -105,7 +118,13 @@ class APIScenarioExecutor {
logHttpRequest(model) {
console.log(model);
let log = `[API Request]\n${model.method} ${model.path} HTTP/1.1\n[Request Headers]\n${this.parseRequestHeaders(model.headers)}\n[Request Body]\n${model.requestBody}`;
let log = `[HTTP API Request]\n${model.method} ${model.path} HTTP/1.1\n[Request Headers]\n${this.parseRequestHeaders(model.headers)}\n[Request Body]\n${model.requestBody}`;
this.appendLog(log);
}
logTcpRequest(model) {
console.log(model);
let log = `[TCP API Request]\n[Request Body]\n${model.requestBody}`;
this.appendLog(log);
}
@@ -114,6 +133,11 @@ class APIScenarioExecutor {
this.appendLog(log);
}
logTcpResponse(response) {
let log = `[Response]\n${response.body}\n`;
this.appendLog(log);
}
parseRequestHeaders(headers) {
let parsedHeaders = '';
headers.forEach(header => {
+31 -14
View File
@@ -175,6 +175,7 @@ class APITester {
width: 0
});
}
for (let i = 0; i < this.responseEditors.length; i++) {
this.responseEditors[i].layout({
width: 0
@@ -387,14 +388,13 @@ class APITester {
});
let language = model.type === 'HTTP' ? 'json' : 'text';
let target = null;
let requestBodyEditor = null;
let target = null;
if (model.type === 'HTTP') {
let httpClientView = new HTTPClientView(me, this.serverManager, apiRequestTabContent, model);
httpClientView.init();
this.openTab(model.id);
target = $('#api_request_' + model.id);
target = this.openTab(model.id);
requestBodyEditor = monaco.editor.create(target.find('.request_body')[0], {
value: model.requestBody, language: 'json', automaticLayout: true
});
@@ -403,12 +403,13 @@ class APITester {
if (model.type === 'TCP') {
const tcpClientView = new TCPClientView(me, this.serverManager, apiRequestTabContent, model);
tcpClientView.init();
this.openTab(model.id);
target = $('#api_request_' + model.id);
target = this.openTab(model.id);
requestBodyEditor = monaco.editor.create(target.find('.request_body')[0], {
value: model.requestBody, language: 'text', automaticLayout: false
value: model.requestBody, language: 'text', automaticLayout: true
});
tcpClientView.setRequestBodyEditor(requestBodyEditor);
tcpClientView.requestBodyEditor = requestBodyEditor;
}
requestBodyEditor.onMouseMove((e) => {
@@ -428,7 +429,8 @@ class APITester {
});
me.requestEditors.push(requestBodyEditor);
target.find('.request_body')[0].addEventListener('shown.bs.tab', () => {
target.find('.request_body_tab')[0].addEventListener('shown.bs.tab', () => {
me.requestEditors[index].layout();
});
@@ -553,6 +555,7 @@ class APITester {
}
openTab(id) {
const target = $(`#api_request_${id}`);
try {
$(`#api_request_tab_${id}`).tab('show'); // API request 탭 활성화
} catch (e) {
@@ -562,6 +565,7 @@ class APITester {
$(`#api_request_${id}`).find('.btn_request_body_tab').tab('show'); // Body 탭 활성화
} catch (e) {
}
return target;
}
addHeader(event) {
@@ -601,7 +605,6 @@ class APITester {
});
}
}
console.log(queryParams);
return queryParams;
}
@@ -791,18 +794,32 @@ class APITester {
})[0];
let me = this;
let newApi = {
id: '', server: null, method: 'GET', name: newApiName, path: '', type: newApiType, headers: [], queryParams: [], requestBody: '', preRequestScript: '', postRequestScript: '', collectionId: collectionId, responseModel: {
status: 0, time: 0, size: 0, headers: [], ody: ''
id: '',
server: null,
method: 'GET',
name: newApiName,
path: '',
type: newApiType,
headers: [],
queryParams: [],
requestBody: '',
preRequestScript: '',
postRequestScript: '',
collectionId: collectionId,
responseModel: {
status: 0,
time: 0,
size: 0,
headers: [],
body: ''
}
};
newApi.collectionName = collection.name;
if (newApiType === 'TCP') {
newApi.layout = [];
newApi.layout.push({layoutItems: []});
newApi.layout = { layoutItems : []};
}
this.currentAjaxRequest = $.ajax({
url: this.getAPISaveURL(collectionId), type: 'POST', contentType: 'application/json', data: JSON.stringify(newApi), success: function(data) {
newApi.id = data;
@@ -0,0 +1,200 @@
class ApiLayoutItemTreeBuilder {
static createTree(layoutItems) {
layoutItems.sort((a, b) => a.loutItemSerno - b.loutItemSerno);
const rootId = layoutItems.length === 0 ? 0 : parseInt(layoutItems[0].parent);
const root = {id: rootId, children: [], loutItemSerno: 0, loutItemName: 'Root', loutItemDesc: 'Root', loutItemType: '', loutItemDepth: 0, parent: null};
const nodeMap = new Map();
nodeMap.set(rootId, root);
for (const item of layoutItems) {
if (['Grid', 'Group'].includes(item.loutItemType)) {
if (item.loutItemOccCnt !== null) {
if (item.loutItemOccCnt === '*') {
let refItem = layoutItems.find(p => p.loutItemName === item.loutItemOccRef);
if (refItem) {
item.childOccCnt = parseInt(refItem.value, 10);
}
} else {
item.childOccCnt = parseInt(item.loutItemOccCnt, 10);
}
}
}
if (nodeMap.has(parseInt(item.parent))) {
nodeMap.get(parseInt(item.parent)).children.push(item);
console.log(item.id + ' to ' + item.parent);
} else {
console.log('Parent not found: ' + item.parent);
}
nodeMap.set(item.id, item);
}
return root;
}
static moveNode(root, node, moveUp) {
const parent = this.getParentNode(root, node);
if (parent === null) {
// Node is the root node, cannot be moved
return;
}
const siblings = parent.children;
const currentIndex = siblings.indexOf(node);
if (currentIndex === -1) {
// Node not found in parent's children list
return;
}
const newIndex = moveUp ? currentIndex - 1 : currentIndex + 1;
if (newIndex >= 0 && newIndex < siblings.length) {
// Swap the positions of the current node and the node at the new index
[siblings[currentIndex], siblings[newIndex]] = [siblings[newIndex], siblings[currentIndex]];
}
}
static getParentNode(root, node) {
if (node.parent === null) {
return null; // Node is the root node
}
return this.getNodeById(root, node.parent);
}
static getNodeIndex(parent, node) {
return parent.children.indexOf(node);
}
static getNodeById(node, id) {
if (node.id == id) {
return node;
}
for (const child of node.children) {
const found = this.getNodeById(child, id);
if (found !== null) {
return found;
}
}
return null;
}
static findParent(root, node) {
if (root === null || node === null) {
return null; // Invalid input
}
if (root.children.includes(node)) {
return root; // The root is the parent of the node
}
for (const child of root.children) {
const parent = this.findParent(child, node);
if (parent !== null) {
return parent;
}
}
return null; // Node not found in the tree
}
static printTree(root) {
console.log('====================================');
this.printNode(root, 0);
console.log('====================================');
}
static printNode(node, level) {
if (node === null) {
return;
}
// Print indentation based on the level
let indent = '';
for (let i = 0; i < level; i++) {
indent += ' ';
}
// Print the node information
console.log(`${indent}${node.id}: ${node.loutName}[${node.loutItemType}, Level: ${node.loutItemDepth}, Serno: ${node.loutItemSerno}]`);
// Recursively print the children
if (node.children.length > 0) {
for (const child of node.children) {
this.printNode(child, level + 1);
}
}
}
static moveNodeDepth(root, node, newDepth) {
if (node === null || root === null || newDepth < 0) {
// Invalid input
return;
}
const parent = this.getParentNode(root, node);
if (parent === null) {
// Node is the root node, cannot be moved to a different level
return;
}
const currentDepth = node.loutItemDepth;
const nodeIndex = this.getNodeIndex(parent, node);
const nextSibling = this.findClosestSibling(parent.children, nodeIndex);
parent.children.splice(nodeIndex, 1);
if (newDepth > currentDepth) {
// Moving to a higher depth
if (nextSibling !== null) {
nextSibling.children.push(node);
node.parent = nextSibling.id;
node.loutItemDepth = newDepth;
this.adjustChildrenDepth(node, newDepth + 1);
} else {
// No next sibling found, add the node back to the original parent
parent.children.push(node);
}
} else {
// Moving to a lower depth
const newParent = this.findParent(root, parent);
if (newParent !== null) {
newParent.children.push(node);
node.parent = newParent.id;
node.loutItemDepth = newDepth;
this.adjustChildrenDepth(node, newDepth + 1);
} else {
// No parent found at the desired level, add the node back to the original parent
parent.children.push(node);
}
}
}
static adjustChildrenDepth(node, depth) {
for (const child of node.children) {
child.loutItemDepth = depth;
this.adjustChildrenDepth(child, depth + 1);
}
}
static findNextSibling(siblings) {
if (siblings === null || siblings.length === 0) {
return null;
}
return siblings[0];
}
static findClosestSibling(siblings, nodeIndex) {
if (siblings === null || siblings.length === 0) {
return null;
}
if (nodeIndex < 1 || nodeIndex >= siblings.length) {
return this.findNextSibling(siblings);
}
return siblings[nodeIndex - 1];
}
}
export default ApiLayoutItemTreeBuilder;
@@ -0,0 +1,51 @@
class ApiLayoutItemTreeDataBuilder {
constructor(startId = 10000) {
this.idCounter = this.generateId(startId);
this.nodeMap = new Map();
}
* generateId(start) {
let id = start;
while (true) {
yield id++;
}
}
cloneNode(node, parent) {
// Create a shallow copy of the node, except for the children array
let newNode = {
...node,
id: this.idCounter.next().value, // Update with a new unique ID
parent: parent ? parent.id : undefined // Set the new parent's ID if parent exists
};
// Remove children property to handle it separately
delete newNode.children;
this.nodeMap.set(newNode.id, newNode);
return newNode;
}
replicateTree(node, parent = null) {
const newNode = this.cloneNode(node, parent);
newNode.children = []; // Initialize children array
if (node.children && node.children.length > 0) {
node.children.forEach(child => {
const childCount = node.childOccCnt || 1;
for (let i = 0; i < childCount; i++) {
const childReplicas = this.replicateTree(child, newNode);
newNode.children = newNode.children.concat(childReplicas);
}
});
}
return [newNode]; // Return an array containing only the new singular parent node with replicated children
}
createReplicatedTree(originalTree) {
// console.log({originalTree});
return this.replicateTree(originalTree)[0]; // Return the first element to maintain the tree structure
}
}
export default ApiLayoutItemTreeDataBuilder;
+15 -5
View File
@@ -16,9 +16,10 @@ class EditableInputModel {
}
class EditableInputView {
constructor(model, controller) {
constructor(model, controller, options) {
this.model = model;
this.controller = controller;
this.options = options;
}
render(element) {
@@ -30,8 +31,17 @@ class EditableInputView {
generateInputHtml() {
let contentHtml = this.model.getValue() ? this.renderContent(this.parseContent(this.model.getValue())) : ZERO_WIDTH_SPACE;
let styleString = '';
if (this.options.style && typeof this.options.style === 'object') {
const styles = Object.entries(this.options.style);
styleString = styles.map(([prop, value]) => `${prop}: ${value};`).join(' ');
}
let style = styleString ? `style="${styleString}"` : '';
return `
<div contenteditable="true" class="editableInputContent form-control" data-value="${this.model.getValue()}">
<div contenteditable="true" ${style} class="editableInputContent form-control" data-value="${this.model.getValue()}">
${contentHtml}
</div>
`;
@@ -132,9 +142,9 @@ class EditableInputView {
class EditableInputController {
constructor(model) {
constructor(model, options) {
this.model = model;
this.view = new EditableInputView(this.model, this);
this.view = new EditableInputView(this.model, this, options);
}
init(element, callback) {
@@ -251,7 +261,7 @@ class EditableInput {
constructor(options = {}) {
// Initialize the model with the value
this.model = new EditableInputModel(options.value || '');
this.controller = new EditableInputController(this.model);
this.controller = new EditableInputController(this.model, options);
// Additional properties as required
this.name = options.name || '';
+287
View File
@@ -0,0 +1,287 @@
import EditableInput from './EditableInput';
class LayoutGridModel {
constructor(items) {
this.items = items;
}
}
class LayoutGridView {
constructor(model, controller) {
this.model = model;
this.controller = controller;
}
render() {
this.renderApiLayout();
}
renderApiLayout() {
const requestContainer = this.targetContent.find('.request_layout');
requestContainer.empty();
const headerHtml = `
<table class="layout_table" style="border-collapse: collapse; border: 1px gray solid;">
<colgroup>
<col style="width: 50px;">
<col style="width: 80px;">
<col style="width: 80px;">
<col style="width: 120px;">
<col style="width: 150px;">
<col style="width: 60px;">
<col style="width: 110px;">
<col style="width: 80px;">
<col style="width: 100px;">
<col style="width: 80px;">
<col style="width: 120px;">
<col style="width: 120px;">
</colgroup>
<thead>
<tr>
<th scope="col" style="border: 1px gray solid; text-align: center;width: 80px;">#</th>
<th scope="col" style="border: 1px gray solid; text-align: center;width: 40px;">ID</th>
<th scope="col" style="border: 1px gray solid; text-align: center;width: 40px;">Parent</th>
<th scope="col" style="border: 1px gray solid; text-align: center;width: 120px;">항목명(영문)</th>
<th scope="col" style="border: 1px gray solid; text-align: center;width: 150px;">항목설명</th>
<th scope="col" style="border: 1px gray solid; text-align: center;width: 60px;">깊이</th>
<th scope="col" style="border: 1px gray solid; text-align: center;width: 110px;">아이템 유형</th>
<th scope="col" style="border: 1px gray solid; text-align: center;width: 80px;">반복 횟수 </th>
<th scope="col" style="border: 1px gray solid; text-align: center;width: 100px;">반복 참조 필드</th>
<th scope="col" style="border: 1px gray solid; text-align: center;width: 80px;">데이터 길이</th>
<th scope="col" style="border: 1px gray solid; text-align: center;width: 120px;">값</th>
<th scope="col" style="border: 1px gray solid; 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 = this.model.items ? this.model.items.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,
style : {width : '120px'}
});
editableInput.init(editableElement, async (value) => {
_.set(this.model, editableElement.dataset.name, value);
await this.controller.additionalCallback(this.model);
});
});
let totalLength = 0;
this.model.items.forEach(item => {
totalLength += parseInt(item.loutItemLength);
});
this.setupListeners();
}
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');
}
}
recalculateSerno() {
// Reassign loutItemSerno sequentially based on the current order
this.model.items.forEach((item, index) => {
item.loutItemSerno = index + 1; // Assuming you want to start numbering from 1
});
}
async 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, fieldName, newValue);
await this.controller.additionalCallback(this.model);
}
}
let totalLength = 0;
this.model.items.forEach(item => {
totalLength += parseInt(item.loutItemLength);
});
}
getLastId (){
let lastId = 0;
this.model.items.forEach(item => {
if (item.id > lastId) {
lastId = item.id;
}
});
return lastId + 1;
}
setupListeners() {
const table = this.targetContent.find('.layout_table');
table.on('change', 'select.field, input.field', this.handleUIUpdate.bind(this));
table.on('click', '.add-layout-item', async(event) => {
let item = {
'id' : this.getLastId(),
'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': '',
'loutItemSerno': 0
};
this.model.items.push(item);
this.recalculateSerno();
this.renderApiLayout();
await this.controller.additionalCallback(this.model);
});
table.on('click', '.move-up', async (event) => {
const index = $(event.currentTarget).data('index');
if (index > 0) {
const temp = this.model.items[index];
this.model.items[index] = this.model.items[index - 1];
this.model.items[index - 1] = temp;
this.recalculateSerno();
this.renderApiLayout();
await this.controller.additionalCallback(this.model);
}
});
table.on('click', '.move-down', async (event) => {
const index = $(event.currentTarget).data('index');
if (index < this.model.items.length - 1) {
const temp = this.model.items[index];
this.model.items[index] = this.model.items[index + 1];
this.model.items[index + 1] = temp;
this.recalculateSerno();
this.renderApiLayout();
await this.controller.additionalCallback(this.model);
}
});
table.on('click', '.delete', async (event) => {
const index = $(event.currentTarget).data('index');
this.model.items.splice(index, 1);
this.recalculateSerno();
this.renderApiLayout();
await this.controller.additionalCallback(this.model);
});
}
renderApiLayoutItem(item, index) {
if (item.value === null) {
item.value = '';
}
return `
<tr class="layout-grid-row">
<td class="layout-grid-cell" style="width: 80px; text-align: center;">
<span style="width: 80px; text-align: center;">${item.loutItemSerno}</span>
</td>
<td class="layout-grid-cell">
<input type="text" class="form-control" name="items[${index}].id" value="${item.id}">
</td>
<td class="layout-grid-cell">
<input type="text" class="form-control" name="items[${index}].parent" value="${item.parent}">
</td>
<td class="layout-grid-cell">
<input type="text" class="form-control" name="items[${index}].loutItemName" value="${item.loutItemName}">
</td>
<td class="layout-grid-cell">
<input type="text" class="form-control" name="items[${index}].loutItemDesc" value="${item.loutItemDesc}">
</td>
<td class="layout-grid-cell">
<input type="number" class="form-control" style="width: 60px;" name="items[${index}].loutItemDepth" min="1" value="${item.loutItemDepth}">
</td>
<td class="layout-grid-cell">
<select name="items[${index}].loutItemType" class="form-select">
<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 class="layout-grid-cell">
<input type="text" class="form-control" style="width: 80px;" name="items[${index}].loutItemOccCnt" min="0" value="${item.loutItemOccCnt}">
</td>
<td class="layout-grid-cell">
<input type="text" class="form-control" name="items[${index}].loutItemOccRef" value="${item.loutItemOccRef}"></td>
<td class="layout-grid-cell">
<input type="number" class="form-control" style="width: 80px;" name="items[${index}].loutItemLength" min="1" value="${item.loutItemLength}">
</td>
<td class="layout-grid-cell">
${ item.loutItemType ==='Field'? `<div class="editable" data-name="items[${index}].value" data-value="${item.value}"></div>`:''}
</td>
<td class="layout-grid-cell" 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>
`;
}
}
class LayoutGridController {
constructor(model) {
this.model = model;
this.view = new LayoutGridView(this.model, this);
}
init(targetContent, callback) {
this.additionalCallback = callback;
this.view.targetContent = targetContent;
this.view.render();
}
}
class LayoutGrid {
constructor(layout) {
this.model = new LayoutGridModel(layout.layoutItems);
this.controller = new LayoutGridController(this.model);
}
init(targetContent, callback) {
this.controller.init(targetContent, callback);
}
}
export default LayoutGrid;
@@ -0,0 +1,281 @@
import EditableInput from './EditableInput';
import APIClient from '../APIClient';
class LayoutTreeDataGridModel {
constructor(root, apiRequest) {
this.dataRoot = root;
this.apiRequest = apiRequest;
console.log('LayoutTreeDataGridModel', this.dataRoot);
}
getNodePath(node, targetId, path = '') {
// Check if the current node is the target node
if (node.id === targetId) {
return path;
}
// If the current node has children, iterate over them
if (node.children) {
for (let i = 0; i < node.children.length; i++) {
// Construct the path to the current child
const currentPath = path ? `${path}.children[${i}]` : `children[${i}]`;
// Recursively call findPath on the child
const resultPath = this.getNodePath(node.children[i], targetId, currentPath);
// If resultPath is not null, the target has been found in the subtree
if (resultPath) {
return resultPath;
}
}
}
// Return null if the target is not found in this subtree
return null;
}
}
class LayoutTreeDataGridView {
constructor(selector) {
console.log({selector});
this.table = $('<table>').appendTo($(selector));
this.table.addClass('layout_table');
this.table.css({'width': 'inherit', 'border-collapse': 'collapse', 'border': '1px gray solid'});
console.log(this.table);
}
addHeaders(fields) {
const thead = $('<thead>').appendTo(this.table);
const tr = $('<tr>').appendTo(thead);
tr.addClass('layout-grid-row');
fields.forEach(field => {
const th = $('<th>').html(field.label).appendTo(tr);
th.css({'width': field.width + 'px', 'border': '1px gray solid', 'text-align': 'center'});
});
}
toggleChildrenVisibility(nodeId, isExpanded) {
const selector = `.treegrid-parent-${nodeId}`;
$(selector).each((_, el) => {
$(el).toggle(!isExpanded);
if (isExpanded) { // If we are hiding the children
const childNodeId = $(el).data('nodeId'); // Assume data-nodeId is set when rows are created
this.toggleChildrenVisibility(childNodeId, true); // Recursively hide all descendants
const expander = $(`.treegrid-${nodeId} .treegrid-expander`);
expander.html(isExpanded ? '+' : '-');
}
});
}
setupExpander(cell, callback) {
const expander = $('<span>').addClass('treegrid-expander').html('-').appendTo(cell);
expander.click(callback);
}
}
class LayoutTreeGridController {
constructor(model, view, options, totalMessageLengthSelector) {
this.model = model;
this.view = view;
this.options = options;
this.apiClient = new APIClient();
this.totalMessageLengthSelector = totalMessageLengthSelector;
}
init(callback) {
this.view.addHeaders(this.options);
this.processNode(this.model.dataRoot, 0);
this.setupEditableFields();
this.callback = callback;
}
async rebuild(dataRoot) {
this.model.dataRoot = dataRoot;
this.view.table.children().remove();
this.view.addHeaders(this.options);
this.processNode(this.model.dataRoot, 0);
this.setupEditableFields();
this.callback(await this.concatenateFieldValues(this.model.dataRoot));
}
getIndent(depth) {
return new Array(depth + 1).join('<span class="treegrid-indent"></span>');
}
async fillPadding(value, dataLength) {
let paddedValue = await this.apiClient.processPreScriptAndVariables(this.model.apiRequest, value.toString());
while (paddedValue.length < dataLength) {
paddedValue = ' ' + paddedValue; // Prepend a space character to the string
}
return paddedValue;
}
async concatenateFieldValues(node) {
let concatenatedValues = '';
// Process the current node if it is of type 'Field'
if (node.loutItemType === 'Field') {
concatenatedValues += await this.fillPadding(node.value, node.loutItemLength) || '';
}
// Recursively process each child
if (node.children && node.children.length > 0) {
for (const child of node.children) {
concatenatedValues += await this.concatenateFieldValues(child);
}
}
return concatenatedValues;
}
sumFieldLoutItemLengths() {
const queue = [this.model.dataRoot];
let sum = 0;
while (queue.length > 0) {
const node = queue.shift();
if (node.loutItemType === 'Field') {
sum += parseInt(node.loutItemLength) || 0; // Add the loutItemLength of the node, or 0 if undefined
}
if (node.children && node.children.length > 0) {
for (const child of node.children) {
queue.push(child);
}
}
}
return sum;
}
setupEditableFields() {
this.view.table.find('.editable').each((index, editableElement) => {
const editableInput = new EditableInput({
name: editableElement.dataset.name,
value: editableElement.dataset.value,
path: editableElement.dataset.path
});
editableInput.init(editableElement, async (value) => {
console.log(editableElement.dataset.path);
console.log(value);
_.set(this.model.dataRoot, editableElement.dataset.path, value);
console.log(this.model.dataRoot);
this.callback(await this.concatenateFieldValues(this.model.dataRoot));
});
});
this.totalMessageLengthSelector.text(this.sumFieldLoutItemLengths());
}
addCell(row, node, fieldInfo, depth) {
const td = $('<td>').appendTo(row);
const padding = fieldInfo.isFirst ? this.getIndent(depth) : '';
const nodePath = this.model.getNodePath(this.model.dataRoot, node.id) + '.value';
if (node.loutItemType === 'Field' && fieldInfo.type === 'textfield') {
const div = $('<div>');
div.addClass('editable');
div.appendTo(td);
div.attr('data-name', fieldInfo.name);
div.attr('data-value', node[fieldInfo.name]);
div.attr('data-path', nodePath);
} else {
td.html(padding); // Default to just displaying the content
}
if (fieldInfo.width) {
td.css({'width': fieldInfo.width + 'px', 'border': '1px gray solid'});
}
if (fieldInfo.align) {
td.css({'text-align': fieldInfo.align});
}
return td;
}
addRow(node, depth) {
const tr = $('<tr>').addClass('treegrid-' + node.id).data('nodeId', node.id); // Storing node ID for easy access later
if (depth === 0) {
tr.css({'display': 'none'});
}
if (node.parent) {
const parentSelector = `.treegrid-${node.parent}`;
const lastChildSelector = `.treegrid-parent-${node.parent}`;
if ($(this.view.table).find(lastChildSelector).length > 0) {
const lastChild = $(this.view.table).find(lastChildSelector).last();
tr.insertAfter(lastChild);
} else {
tr.insertAfter($(this.view.table).find(parentSelector));
}
tr.addClass('treegrid-parent-' + node.parent);
} else {
tr.appendTo(this.view.table);
}
return tr;
}
//processNode in bfs
processNode(node, depth) {
const queue = [{node, depth}];
while (queue.length > 0) {
const {node, depth} = queue.shift();
const tr = this.addRow(node, depth);
this.options.forEach((fieldInfo) => {
const content = node[fieldInfo.name];
const td = this.addCell(tr, node, fieldInfo, depth);
if (fieldInfo.isFirst) {
if (node.children && node.children.length > 0) {
this.view.setupExpander($(tr.children()[0]), () => this.handleExpanderClick(node.id));
}
}
if (fieldInfo.type !== 'textfield') {
td.append(content);
}
});
if (node.children && node.children.length > 0) {
for (const child of node.children) {
queue.push({node: child, depth: depth + 1});
}
}
}
}
handleExpanderClick(nodeId) {
const expander = $(`.treegrid-${nodeId} .treegrid-expander`);
const isExpanded = expander.html() === '-';
expander.html(isExpanded ? '+' : '-');
this.view.toggleChildrenVisibility(nodeId, isExpanded);
}
}
class LayoutTreeDataGrid {
constructor(selector, root, apiRequest, options, totalMessageLengthSelector) {
this.model = new LayoutTreeDataGridModel(root, apiRequest);
this.view = new LayoutTreeDataGridView(selector);
this.controller = new LayoutTreeGridController(this.model, this.view, options, totalMessageLengthSelector);
}
init(callback) {
this.controller.init(callback);
}
rebuild(dataRoot) {
this.controller.rebuild(dataRoot);
}
}
export default LayoutTreeDataGrid;
@@ -0,0 +1,231 @@
import EditableInput from './EditableInput';
class LayoutTreeGridModel {
constructor(root) {
this.root = root;
this.rootId = root.id;
}
setRoot(newRoot) {
this.root = newRoot;
}
getNodePath(node, targetId, path = "") {
// Check if the current node is the target node
if (node.id === targetId) {
return path;
}
// If the current node has children, iterate over them
if (node.children) {
for (let i = 0; i < node.children.length; i++) {
// Construct the path to the current child
const currentPath = path ? `${path}.children[${i}]` : `children[${i}]`;
// Recursively call findPath on the child
const resultPath = this.getNodePath(node.children[i], targetId, currentPath);
// If resultPath is not null, the target has been found in the subtree
if (resultPath) {
return resultPath;
}
}
}
// Return null if the target is not found in this subtree
return null;
}
}
class LayoutTreeGridView {
constructor(selector) {
this.selector = selector;
this.table = $('<table>').appendTo($(this.selector));
this.table.addClass('layout_table');
this.table.css({'width': 'inherit', 'border-collapse': 'collapse', 'border': '1px gray solid'});
this.init();
}
init() {
this.table.children().remove();
}
clear() {
this.init();
}
addHeaders(fields) {
const thead = $('<thead>').appendTo(this.table);
const tr = $('<tr>').appendTo(thead);
tr.addClass('layout-grid-row');
fields.forEach(field => {
const th = $('<th>').html(field.label).appendTo(tr);
th.css({'width': field.width + 'px', 'border': '1px gray solid', 'text-align': 'center'});
});
}
toggleChildrenVisibility(nodeId, isExpanded) {
const selector = `.treegrid-parent-${nodeId}`;
$(selector).each((_, el) => {
$(el).toggle(!isExpanded);
if (isExpanded) { // If we are hiding the children
const childNodeId = $(el).data('nodeId'); // Assume data-nodeId is set when rows are created
this.toggleChildrenVisibility(childNodeId, true); // Recursively hide all descendants
const expander = $(`.treegrid-${nodeId} .treegrid-expander`);
expander.html(isExpanded ? '+' : '-');
}
});
}
setupExpander(cell, callback) {
const expander = $('<span>').addClass('treegrid-expander').html('-').appendTo(cell);
expander.click(callback);
}
}
class LayoutTreeGridController {
constructor(model, view, options, callback) {
this.model = model;
this.view = view;
this.options = options;
this.callback = callback;
this.init();
}
init() {
this.view.addHeaders(this.options);
this.processNode(this.model.root, 0);
this.view.table.find('.editable').each((index, editableElement) => {
const editableInput = new EditableInput({
name: editableElement.dataset.name,
value: editableElement.dataset.value,
path: editableElement.dataset.path
});
editableInput.init(editableElement, async(value) => {
_.set(this.model.root, editableElement.dataset.path, value);
this.callback(this.model.root);
});
});
}
getIndent(depth) {
return new Array(depth + 1).join('<span class="treegrid-indent"></span>');
}
addRow(node, depth) {
const tr = $('<tr>').addClass('treegrid-' + node.id).data('nodeId', node.id); // Storing node ID for easy access later
if (depth === 0) {
tr.css({'display': 'none'});
}
if (node.parent || (node.parent !== null && parseInt(node.parent) !== this.model.rootId)) {
const parentSelector = `.treegrid-${node.parent}`;
const lastChildSelector = `.treegrid-parent-${node.parent}`;
if ($(this.view.table).find(lastChildSelector).length > 0) {
const lastChild = $(this.view.table).find(lastChildSelector).last();
console.log('lastChild: ', lastChild);
tr.insertAfter(lastChild);
} else {
console.log('parentSelector: ', parentSelector);
tr.insertAfter($(this.view.table).find(parentSelector));
}
tr.addClass('treegrid-parent-' + node.parent);
} else {
console.log('no parent');
tr.appendTo(this.view.table);
}
console.log('addRow, node: ', node.id, ' parent: ', node.parent);
console.log('addRow tr: ', tr);
return tr;
}
addCell(row, node, fieldInfo, depth) {
const td = $('<td>').appendTo(row);
const padding = fieldInfo.isFirst ? this.getIndent(depth) : '';
const nodePath = this.model.getNodePath(this.model.root, node.id) + ".value";
if (node.loutItemType === 'Field' && fieldInfo.type === 'textfield') {
const div = $('<div>');
div.addClass('editable');
div.appendTo(td);
div.attr('data-name', fieldInfo.name);
div.attr('data-value', node[fieldInfo.name]);
div.attr('data-path', nodePath);
} else {
td.html(padding); // Default to just displaying the content
}
if (fieldInfo.width) {
td.css({'width': fieldInfo.width + 'px', 'border': '1px gray solid'});
}
if (fieldInfo.align) {
td.css({'text-align': fieldInfo.align});
}
return td;
}
processNode(node, depth) {
console.log('node: ', node.id);
const tr = this.addRow(node, depth);
this.options.forEach((fieldInfo) => {
const content = node[fieldInfo.name];
const td = this.addCell(tr, node, fieldInfo, depth);
if (fieldInfo.isFirst) {
if (node.children && node.children.length > 0) {
this.view.setupExpander($(tr.children()[0]), () => this.handleExpanderClick(node.id));
}
}
if (fieldInfo.type !== 'textfield') {
td.append(content);
}
});
// Recursively process each child node if any
if (node.children && node.children.length > 0) {
node.children.forEach(child => {
this.processNode(child, depth + 1); // Increment depth for each child
});
}
}
handleExpanderClick(nodeId) {
const expander = $(`.treegrid-${nodeId} .treegrid-expander`);
const isExpanded = expander.html() === '-';
expander.html(isExpanded ? '+' : '-');
this.view.toggleChildrenVisibility(nodeId, isExpanded);
}
rebuild(newRoot) {
this.model.setRoot(newRoot);
this.view.clear();
this.init();
}
}
class LayoutTreeGrid {
constructor(selector, root, options, callback) {
this.model = new LayoutTreeGridModel(root);
this.view = new LayoutTreeGridView(selector);
this.controller = new LayoutTreeGridController(this.model, this.view, options, callback);
}
rebuild(root, callback) {
this.controller.rebuild(root);
callback(root);
}
}
export default LayoutTreeGrid;
@@ -33,8 +33,16 @@ class LoadTestResultDetailController {
showDetail(result) {
this.view.outputEditor.setValue('');
this.logHttpRequest(result.request);
this.logHttpResponse(result.response);
console.log(result);
if (result.request.type === 'TCP') {
this.logTcpRequest(result.request);
this.logTcpResponse(result.response);
} else {
this.logHttpRequest(result.request);
this.logHttpResponse(result.response);
}
}
reset(){
@@ -48,18 +56,32 @@ class LoadTestResultDetailController {
this.view.outputEditor.executeEdits('my-source', [op]);
}
logHttpRequest(request) {
console.log({request});
let log = `[API Request]\n${request.method} ${request.path} HTTP/1.1\n[Request Headers]\n${this.parseRequestHeaders(request.headers)}\n[Request Body]\n${request.requestBody}`;
this.appendLog(log);
}
logTcpRequest(model) {
console.log(model);
let log = `[TCP API Request]\n[Request Body]\n${model.requestBody}`;
this.appendLog(log);
}
logHttpResponse(response) {
console.log({response});
let log = `[Response]\nHTTP/1.1 ${response.status}\n[Response Headers]\n${this.parseHeaders(response.headers)}\n[Response Body]\n${response.body}\n`;
this.appendLog(log);
}
logTcpResponse(response) {
console.log({response});
let log = `[Response]\n${response.body}\n`;
this.appendLog(log);
}
parseRequestHeaders(headers) {
let parsedHeaders = '';
headers.forEach(header => {
+83 -5
View File
@@ -1,6 +1,8 @@
import EditableInput from './EditableInput';
class ServerManager {
constructor(servers) {
this.servers = servers ||[];
this.servers = servers || [];
}
basePath(serverId) {
@@ -23,17 +25,93 @@ class ServerManager {
}
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('');
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>`;
}
getHeader(headers, key) {
if (headers === undefined || headers === null) {
return '';
}
// Filter the headers to find the matching key
const filteredHeaders = headers.filter(header => header.key === key);
// Check if any header was found that matches the key
if (filteredHeaders.length === 0) {
console.log('new header');
let newHeader = { key: key, value: '', enabled : true };
headers.push(newHeader);
return newHeader;
}
// Return the value of the first matching header
return filteredHeaders[0];
}
renderTcpServerOptions(target, serverOptions, headers, parent) {
console.log(headers);
let tableHTML = `
<table class="table table-bordered">
<thead>
<tr>
<th>Option Key</th>
<th>Option Name</th>
<th>Offset</th>
<th>Length</th>
<th>Value</th>
</tr>
</thead>
<tbody>
${serverOptions.map(option => `
<tr>
<td><span>${option.optionKey}</span></td>
<td><span>${option.optionName}</span></td>
<td><span>${option.offset}</span></td>
<td><span>${option.length}</span></td>
<td>
<div class="editable" data-name="${option.optionKey}" data-value="${this.getHeader(headers, option.optionKey).value}"></div>
</td>
</tr>
`).join('')}
</tbody>
</table>
`;
const container = document.querySelector(target);
container.innerHTML += tableHTML;
container.querySelectorAll('.editable').forEach(editableElement => {
const editableInput = new EditableInput({
name: editableElement.dataset.name,
value: editableElement.dataset.value
});
editableInput.init(editableElement, (value) => {
let header = this.getHeader(headers, editableElement.dataset.name);
header.value = value;
console.log({value});
console.log(header);
$('span.variable').off('mouseover');
$('span.variable').off('mouseout');
document.querySelectorAll('span.variable').forEach(element => {
let text = element.innerText;
element.addEventListener('mouseover', () => parent.showPopper(element, text));
element.addEventListener('mouseout', () => parent.hidePopper(element));
});
});
});
}
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('');
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>
+113 -254
View File
@@ -1,6 +1,9 @@
import EditableInput from './EditableInput';
import APIClient from '../APIClient';
import LayoutGrid from './LayoutGrid';
import LayoutTreeGrid from './LayoutTreeGrid';
import LayoutTreeDataGrid from './LayoutTreeDataGrid';
import * as monaco from 'monaco-editor';
import ApiLayoutItemTreeBuilder from './ApiLayoutItemTreeBuilder';
import ApiLayoutItemTreeDataBuilder from './ApiLayoutItemTreeDataBuilder';
class TCPClientView {
@@ -11,255 +14,93 @@ class TCPClientView {
this.model = model;
this.index = index;
this.server = this.serverManager.getServer(this.model.server);
this.apiClient = new APIClient();
}
init() {
console.log('TCPClientView init');
this.apiRequestTabContent.append(this.tcpApiRequestTemplate(this.model, this.index));
this.renderApiLayout(this.model.layout);
}
if (this.server && this.server.headers === undefined) {
this.server.headers = [];
}
if (this.server) {
this.serverManager.renderTcpServerOptions('.server_options', this.server.serverOptions, this.model.headers, this.parent);
}
setRequestBodyEditor(editor) {
this.requestBodyEditor = editor;
}
let layoutJson = JSON.stringify(this.model.layout, null, 2);
this.layoutJsonEditor = monaco.editor.create(this.apiRequestTabContent.find(`#api_request_${this.model.id}`).find('.layout-json')[0], {
value: layoutJson, language: 'json', automaticLayout: true, quickSuggestions: false
});
renderApiLayout(layout) {
const requestContainer = this.apiRequestTabContent.find('.request_layout');
requestContainer.empty();
const layoutDataTreeOptions = [
{name: 'loutItemSerno', width: 100, isFirst: true, label: '#'},
{name: 'loutItemName', width: 100, align: 'center', label: '항목명(영문)'},
{name: 'loutItemDesc', width: 100, align: 'center', label: '항목설명'},
{name: 'loutItemDepth', width: 60, align: 'center', label: '깊이'},
{name: 'loutItemType', width: 100, align: 'center', label: '아이템 유형'},
{name: 'loutItemOccCnt', width: 100, align: 'center', label: '반복 횟수'},
{name: 'loutItemOccRef', width: 120, align: 'center', label: '반복 참조 필드'},
{name: 'loutItemLength', width: 100, align: 'center', label: '데이터 길이'},
{name: 'value', width: 200, align: 'left', label: '값', type: 'textfield'}
];
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>
`;
let layoutRoot = ApiLayoutItemTreeBuilder.createTree(this.model.layout.layoutItems);
let replicator = new ApiLayoutItemTreeDataBuilder(10000);
let layoutDataRoot = replicator.createReplicatedTree(layoutRoot);
const footerHtml = `
</tbody>
</table>
`;
console.log(this.apiRequestTabContent.find(`#api_request_${this.model.id}`).find('.request_layout_data_tree'));
console.log(this.apiRequestTabContent.find(`#api_request_${this.model.id}`).find('.request_layout_tree'));
console.log(this.apiRequestTabContent.find(`#api_request_${this.model.id}`).find('.total_message_length'));
const fieldsHtml = layout.layoutItems ? layout.layoutItems.map((item, index) => {
return this.renderApiLayoutItem(item, index);
}).join('') : '';
let layoutTreeDataGrid = new LayoutTreeDataGrid(this.apiRequestTabContent.find(`#api_request_${this.model.id}`).find('.request_layout_data_tree'), layoutDataRoot, this.model, layoutDataTreeOptions, this.apiRequestTabContent.find(`#api_request_${this.model.id}`).find('.total_message_length'));
layoutTreeDataGrid.init( async (finalMessage) => {
this.requestBodyEditor.setValue(finalMessage);
});
requestContainer.append(headerHtml + fieldsHtml + footerHtml);
let layoutTreeGrid = new LayoutTreeGrid(this.apiRequestTabContent.find(`#api_request_${this.model.id}`).find('.request_layout_tree'), layoutRoot, layoutDataTreeOptions, (root) => {
let replicator = new ApiLayoutItemTreeDataBuilder(10000);
let layoutDataRoot = replicator.createReplicatedTree(root);
layoutTreeDataGrid.rebuild(layoutDataRoot);
});
requestContainer.find('.editable').each((index, editableElement) => {
const editableInput = new EditableInput({
name: editableElement.dataset.name,
value: editableElement.dataset.value
let layoutGrid = new LayoutGrid(this.model.layout);
layoutGrid.init(this.apiRequestTabContent.find(`#api_request_${this.model.id}`), (model) => {
model.items.forEach((item) => {
item.children = [];
});
let updatedRoot = ApiLayoutItemTreeBuilder.createTree(model.items);
layoutTreeGrid.rebuild(updatedRoot, (root) => {
let replicator = new ApiLayoutItemTreeDataBuilder(10000);
let layoutDataRoot = replicator.createReplicatedTree(root);
layoutTreeDataGrid.rebuild(layoutDataRoot);
});
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));
});
$('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));
});
});
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>
`;
window.addEventListener('resize', this.resizeEditor.bind(this));
}
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
resizeEditor() {
this.requestBodyEditor.layout({
width: 0
});
}
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);
this.requestBodyEditor.layout({});
this.layoutJsonEditor.layout({
width: 0
});
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, ' ');
this.layoutJsonEditor.layout({});
}
tcpApiRequestTemplate(apiRequest, index) {
console.log('tcpApiRequestTemplate');
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>
@@ -268,41 +109,41 @@ class TCPClientView {
${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="d-flex mt-2 server_options">
</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
aria-selected="false">Layout
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link btn_layout_json_tab" data-bs-toggle="tab"
type="button" role="tab" aria-controls="layoutJson" data-bs-target="#requestLayoutJson_${apiRequest.id}"
aria-selected="false">Layout JSON
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link btn_layout_tree_tab" data-bs-toggle="tab"
type="button" role="tab" aria-controls="layoutTree" data-bs-target="#requestLayoutTreeTab_${apiRequest.id}"
aria-selected="false">Layout Tree
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link btn_request_body_data_tab" data-bs-toggle="tab"
type="button" role="tab" aria-controls="requestBodyData" data-bs-target="#requestBodyDataTab_${apiRequest.id}"
aria-selected="false">Layout Data
</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}"
type="button" role="tab" aria-controls="preScript" data-bs-target="#preRequestTab_${apiRequest.id}"
aria-selected="false">Pre-Request
</button>
</li>
@@ -314,13 +155,30 @@ class TCPClientView {
</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="tab-pane fade request_layout_json_tab" role="tabpanel" id="requestLayoutJson_${apiRequest.id}" aria-labelledby="requestLayoutJson-tab">
<div class="d-flex flex-row">
<div style="width:calc(100%);">
<div class="layout-json mt-1" style="height:300px;border:1px solid grey;"></div>
</div>
</div>
</div>
<div class="tab-pane show 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 class="request_layout" style="border:1px solid grey; overflow: scroll"></div>
</div>
</div>
<div class="tab-pane fade pre_script_tab" role="tabpanel" id="preRequestTab_${apiRequest.id}" aria-labelledby="preScrit-tab">
<div class="tab-pane show fade request_layout_tree_tab" role="tabpanel" id="requestLayoutTreeTab_${apiRequest.id}" aria-labelledby="requestLayoutTree-tab">
<div class="d-flex flex-column">
<div class="request_layout_tree" style="border:1px solid grey; overflow: scroll"></div>
</div>
</div>
<div class="tab-pane show fade request_body_data_tab" role="tabpanel" id="requestBodyDataTab_${apiRequest.id}" aria-labelledby="requestBodyData-tab">
<div class="d-flex flex-column">
<div class="request_layout_data_tree" style="border:1px solid grey; overflow: scroll"></div>
</div>
</div>
<div class="tab-pane fade pre_script_tab" role="tabpanel" id="preRequestTab_${apiRequest.id}" aria-labelledby="preScript-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>
@@ -345,6 +203,7 @@ class TCPClientView {
</div>
</div>
</div>
<div class="request_body mt-1" style="height: 150px; border:1px solid grey;"></div>
<div class="api_request_info" >
<span>전체 메시지 길이: </span>
<span class="total_message_length"></span>
+20 -12
View File
@@ -31,21 +31,29 @@ module.exports = {
]
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
generator: {
filename: 'fonts/[name][ext]',
},
},
{
test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts/',
},
},
],
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
//,
// {
// test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
// use: [
// {
// loader: 'file-loader',
// options: {
// name: '[name].[ext]',
// outputPath: 'fonts/',
// },
// },
// ],
// }
]
},
plugins: [new MonacoWebpackPlugin()],
+2 -2
View File
@@ -10,8 +10,8 @@ jar.enabled = false
group = 'com.eactive'
version = '2.0.0'
sourceCompatibility = '11'
targetCompatibility = 11
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
configurations {
annotationProcessor