diff --git a/ApiTestManager/package.json b/ApiTestManager/package.json index 44d7f78..d24c466 100644 --- a/ApiTestManager/package.json +++ b/ApiTestManager/package.json @@ -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", diff --git a/ApiTestManager/src/APIScenario.js b/ApiTestManager/src/APIScenario.js index 7080b44..7d6e4fd 100644 --- a/ApiTestManager/src/APIScenario.js +++ b/ApiTestManager/src/APIScenario.js @@ -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 = `
API
${dataset.name}
`; me.editor.addNode(dataset.name, 1, 1, x, y, dataset.nodeType, dataset, node); diff --git a/ApiTestManager/src/APIScenarioExecutor.js b/ApiTestManager/src/APIScenarioExecutor.js index 711ac70..a6439ea 100644 --- a/ApiTestManager/src/APIScenarioExecutor.js +++ b/ApiTestManager/src/APIScenarioExecutor.js @@ -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 => { diff --git a/ApiTestManager/src/APITester.js b/ApiTestManager/src/APITester.js index 682c06b..2378cdc 100644 --- a/ApiTestManager/src/APITester.js +++ b/ApiTestManager/src/APITester.js @@ -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; diff --git a/ApiTestManager/src/components/ApiLayoutItemTreeBuilder.js b/ApiTestManager/src/components/ApiLayoutItemTreeBuilder.js new file mode 100644 index 0000000..c8fba9d --- /dev/null +++ b/ApiTestManager/src/components/ApiLayoutItemTreeBuilder.js @@ -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; diff --git a/ApiTestManager/src/components/ApiLayoutItemTreeDataBuilder.js b/ApiTestManager/src/components/ApiLayoutItemTreeDataBuilder.js new file mode 100644 index 0000000..3dec408 --- /dev/null +++ b/ApiTestManager/src/components/ApiLayoutItemTreeDataBuilder.js @@ -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; diff --git a/ApiTestManager/src/components/EditableInput.js b/ApiTestManager/src/components/EditableInput.js index fafaa53..0a0018f 100644 --- a/ApiTestManager/src/components/EditableInput.js +++ b/ApiTestManager/src/components/EditableInput.js @@ -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 ` -
+
${contentHtml}
`; @@ -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 || ''; diff --git a/ApiTestManager/src/components/LayoutGrid.js b/ApiTestManager/src/components/LayoutGrid.js new file mode 100644 index 0000000..3649adb --- /dev/null +++ b/ApiTestManager/src/components/LayoutGrid.js @@ -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 = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `; + + const footerHtml = ` + +
#IDParent항목명(영문)항목설명깊이아이템 유형반복 횟수 반복 참조 필드데이터 길이비고
+ `; + + 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 ` + + + ${item.loutItemSerno} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${ item.loutItemType ==='Field'? `
`:''} + + +
+ + + +
+ + + `; + } + +} + +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; diff --git a/ApiTestManager/src/components/LayoutTreeDataGrid.js b/ApiTestManager/src/components/LayoutTreeDataGrid.js new file mode 100644 index 0000000..ba5df8f --- /dev/null +++ b/ApiTestManager/src/components/LayoutTreeDataGrid.js @@ -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 = $('').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 = $('').appendTo(this.table); + const tr = $('').appendTo(thead); + tr.addClass('layout-grid-row'); + + fields.forEach(field => { + const th = $('').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; diff --git a/ApiTestManager/src/components/LayoutTreeGrid.js b/ApiTestManager/src/components/LayoutTreeGrid.js new file mode 100644 index 0000000..82184b7 --- /dev/null +++ b/ApiTestManager/src/components/LayoutTreeGrid.js @@ -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 = $('
').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 = $('').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(''); + } + + 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 = $('').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.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 = $('
').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 = $('').appendTo(this.table); + const tr = $('').appendTo(thead); + tr.addClass('layout-grid-row'); + + fields.forEach(field => { + const th = $('').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 = $(' - - - - - - - - - - - - - `; + 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 `
${apiRequest.collectionName} > ${apiRequest.name}
@@ -268,41 +109,41 @@ class TCPClientView { ${this.serverManager.renderTcpServers(apiRequest.server)}
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
+
+
-
+
+
+
+
+
+
+
+
-
-
+
-
+
+
+
+
+
+
+
+
+ +
+
+
@@ -345,6 +203,7 @@ class TCPClientView {
+
전체 메시지 길이: diff --git a/ApiTestManager/webpack.config.js b/ApiTestManager/webpack.config.js index 13ce448..cb5057a 100644 --- a/ApiTestManager/webpack.config.js +++ b/ApiTestManager/webpack.config.js @@ -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()], diff --git a/build.gradle b/build.gradle index 5a1636d..f81bdc0 100644 --- a/build.gradle +++ b/build.gradle @@ -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 diff --git a/src/main/resources/static/plugins/apitestmanager/vendors-node_modules_monaco-editor_esm_vs_basic-languages_twig_twig_js.bundle.js b/src/main/resources/static/plugins/apitestmanager/vendors-node_modules_monaco-editor_esm_vs_basic-languages_twig_twig_js.bundle.js new file mode 100644 index 0000000..cedead4 --- /dev/null +++ b/src/main/resources/static/plugins/apitestmanager/vendors-node_modules_monaco-editor_esm_vs_basic-languages_twig_twig_js.bundle.js @@ -0,0 +1,22 @@ +"use strict"; +/* + * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). + * This devtool is neither made for production nor for readable output files. + * It uses "eval()" calls to create a separate source file in the browser devtools. + * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) + * or disable the default devtool with "devtool: false". + * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). + */ +((typeof self !== 'undefined' ? self : this)["webpackChunkAPITestManager"] = (typeof self !== 'undefined' ? self : this)["webpackChunkAPITestManager"] || []).push([["vendors-node_modules_monaco-editor_esm_vs_basic-languages_twig_twig_js"],{ + +/***/ "./node_modules/monaco-editor/esm/vs/basic-languages/twig/twig.js": +/*!************************************************************************!*\ + !*** ./node_modules/monaco-editor/esm/vs/basic-languages/twig/twig.js ***! + \************************************************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ conf: () => (/* binding */ conf),\n/* harmony export */ language: () => (/* binding */ language)\n/* harmony export */ });\n/*!-----------------------------------------------------------------------------\n * Copyright (c) Microsoft Corporation. All rights reserved.\n * Version: 0.47.0(69991d66135e4a1fc1cf0b1ac4ad25d429866a0d)\n * Released under the MIT license\n * https://github.com/microsoft/monaco-editor/blob/main/LICENSE.txt\n *-----------------------------------------------------------------------------*/\n\n\n// src/basic-languages/twig/twig.ts\nvar conf = {\n wordPattern: /(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\$\\^\\&\\*\\(\\)\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\s]+)/g,\n comments: {\n blockComment: [\"{#\", \"#}\"]\n },\n brackets: [\n [\"{#\", \"#}\"],\n [\"{%\", \"%}\"],\n [\"{{\", \"}}\"],\n [\"(\", \")\"],\n [\"[\", \"]\"],\n // HTML\n [\"\"],\n [\"<\", \">\"]\n ],\n autoClosingPairs: [\n { open: \"{# \", close: \" #}\" },\n { open: \"{% \", close: \" %}\" },\n { open: \"{{ \", close: \" }}\" },\n { open: \"[\", close: \"]\" },\n { open: \"(\", close: \")\" },\n { open: '\"', close: '\"' },\n { open: \"'\", close: \"'\" }\n ],\n surroundingPairs: [\n { open: '\"', close: '\"' },\n { open: \"'\", close: \"'\" },\n // HTML\n { open: \"<\", close: \">\" }\n ]\n};\nvar language = {\n defaultToken: \"\",\n tokenPostfix: \"\",\n ignoreCase: true,\n keywords: [\n // (opening) tags\n \"apply\",\n \"autoescape\",\n \"block\",\n \"deprecated\",\n \"do\",\n \"embed\",\n \"extends\",\n \"flush\",\n \"for\",\n \"from\",\n \"if\",\n \"import\",\n \"include\",\n \"macro\",\n \"sandbox\",\n \"set\",\n \"use\",\n \"verbatim\",\n \"with\",\n // closing tags\n \"endapply\",\n \"endautoescape\",\n \"endblock\",\n \"endembed\",\n \"endfor\",\n \"endif\",\n \"endmacro\",\n \"endsandbox\",\n \"endset\",\n \"endwith\",\n // literals\n \"true\",\n \"false\"\n ],\n tokenizer: {\n root: [\n // whitespace\n [/\\s+/],\n // Twig Tag Delimiters\n [/{#/, \"comment.twig\", \"@commentState\"],\n [/{%[-~]?/, \"delimiter.twig\", \"@blockState\"],\n [/{{[-~]?/, \"delimiter.twig\", \"@variableState\"],\n // HTML\n [/)/, [\"delimiter.html\", \"tag.html\", \"\", \"delimiter.html\"]],\n [/(<)(script)/, [\"delimiter.html\", { token: \"tag.html\", next: \"@script\" }]],\n [/(<)(style)/, [\"delimiter.html\", { token: \"tag.html\", next: \"@style\" }]],\n [/(<)((?:[\\w\\-]+:)?[\\w\\-]+)/, [\"delimiter.html\", { token: \"tag.html\", next: \"@otherTag\" }]],\n [/(<\\/)((?:[\\w\\-]+:)?[\\w\\-]+)/, [\"delimiter.html\", { token: \"tag.html\", next: \"@otherTag\" }]],\n [/|>=|<=/, \"operators.twig\"],\n // operators - comparison (words)\n [/(starts with|ends with|matches)(\\s+)/, [\"operators.twig\", \"\"]],\n // operators - containment\n [/(in)(\\s+)/, [\"operators.twig\", \"\"]],\n // operators - test\n [/(is)(\\s+)/, [\"operators.twig\", \"\"]],\n // operators - misc\n [/\\||~|:|\\.{1,2}|\\?{1,2}/, \"operators.twig\"],\n // names\n [\n /[^\\W\\d][\\w]*/,\n {\n cases: {\n \"@keywords\": \"keyword.twig\",\n \"@default\": \"variable.twig\"\n }\n }\n ],\n // numbers\n [/\\d+(\\.\\d+)?/, \"number.twig\"],\n // punctuation\n [/\\(|\\)|\\[|\\]|{|}|,/, \"delimiter.twig\"],\n // strings\n [/\"([^#\"\\\\]*(?:\\\\.[^#\"\\\\]*)*)\"|\\'([^\\'\\\\]*(?:\\\\.[^\\'\\\\]*)*)\\'/, \"string.twig\"],\n // opening double quoted string\n [/\"/, \"string.twig\", \"@stringState\"],\n // misc syntactic constructs\n // These are not operators per se, but for the purposes of lexical analysis we\n // can treat them as such.\n // arrow functions\n [/=>/, \"operators.twig\"],\n // assignment\n [/=/, \"operators.twig\"]\n ],\n /**\n * HTML\n */\n doctype: [\n [/[^>]+/, \"metatag.content.html\"],\n [/>/, \"metatag.html\", \"@pop\"]\n ],\n comment: [\n [/-->/, \"comment.html\", \"@pop\"],\n [/[^-]+/, \"comment.content.html\"],\n [/./, \"comment.content.html\"]\n ],\n otherTag: [\n [/\\/?>/, \"delimiter.html\", \"@pop\"],\n [/\"([^\"]*)\"/, \"attribute.value.html\"],\n [/'([^']*)'/, \"attribute.value.html\"],\n [/[\\w\\-]+/, \"attribute.name.html\"],\n [/=/, \"delimiter.html\"],\n [/[ \\t\\r\\n]+/]\n // whitespace\n ],\n // -- BEGIN
').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 = $('').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(''); + } + + addRow(node, depth) { + const tr = $('
').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.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; diff --git a/ApiTestManager/src/components/LoadTestResultDetail.js b/ApiTestManager/src/components/LoadTestResultDetail.js index 88a9333..43053f4 100644 --- a/ApiTestManager/src/components/LoadTestResultDetail.js +++ b/ApiTestManager/src/components/LoadTestResultDetail.js @@ -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 => { diff --git a/ApiTestManager/src/components/ServerManager.js b/ApiTestManager/src/components/ServerManager.js index 6836d1a..bfd250c 100644 --- a/ApiTestManager/src/components/ServerManager.js +++ b/ApiTestManager/src/components/ServerManager.js @@ -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 => ``).join(''); + const optionsHtml = this.servers.filter(server => server.scheme === 'tcp').map(server => ``).join(''); return ``; } + + 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 = ` + + + + + + + + + + + + ${serverOptions.map(option => ` + + + + + + + + `).join('')} + +
Option KeyOption NameOffsetLengthValue
${option.optionKey}${option.optionName}${option.offset}${option.length} +
+
+ `; + + 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 => ``).join(''); + const optionsHtml = this.servers.filter(server => server.scheme !== 'tcp'). + map(server => ``). + join(''); return ` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - `; + let layoutRoot = ApiLayoutItemTreeBuilder.createTree(this.model.layout.layoutItems); + let replicator = new ApiLayoutItemTreeDataBuilder(10000); + let layoutDataRoot = replicator.createReplicatedTree(layoutRoot); - const footerHtml = ` - -
#항목명(영문)항목설명깊이아이템 유형반복 횟수 반복 참조 필드데이터 타입데이터 길이비고
- `; + 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 ` -
${item.loutItemSerno} - - - - -
-
-
- - - - -
-