플랫 전분 기본 처리 반영
This commit is contained in:
@@ -35,7 +35,7 @@
|
|||||||
"dropzone": "^6.0.0-beta.2",
|
"dropzone": "^6.0.0-beta.2",
|
||||||
"golden-layout": "^2.6.0",
|
"golden-layout": "^2.6.0",
|
||||||
"jqueryui": "^1.11.1",
|
"jqueryui": "^1.11.1",
|
||||||
"monaco-editor": "^0.45.0",
|
"monaco-editor": "^0.47.0",
|
||||||
"monaco-editor-workers": "^0.45.0",
|
"monaco-editor-workers": "^0.45.0",
|
||||||
"sockjs-client": "^1.6.1",
|
"sockjs-client": "^1.6.1",
|
||||||
"split-grid": "^1.0.11",
|
"split-grid": "^1.0.11",
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ class APIScenario {
|
|||||||
openScenario(event) {
|
openScenario(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
let scenarioId = $(event.target).closest('li').data('id');
|
let scenarioId = $(event.target).closest('li').data('id');
|
||||||
|
this.stopScenario();
|
||||||
this.openScenarioById(scenarioId);
|
this.openScenarioById(scenarioId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,6 +309,7 @@ class APIScenario {
|
|||||||
stopScenario() {
|
stopScenario() {
|
||||||
if (this.executor) {
|
if (this.executor) {
|
||||||
this.executor.reset();
|
this.executor.reset();
|
||||||
|
this.executor = null;
|
||||||
}
|
}
|
||||||
this.outputEditor.setValue('');
|
this.outputEditor.setValue('');
|
||||||
}
|
}
|
||||||
@@ -353,6 +355,7 @@ class APIScenario {
|
|||||||
const y = ui.offset.top - $(this).offset().top;
|
const y = ui.offset.top - $(this).offset().top;
|
||||||
|
|
||||||
const dataset = ui.draggable[0].dataset;
|
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>`;
|
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);
|
me.editor.addNode(dataset.name, 1, 1, x, y, dataset.nodeType, dataset, node);
|
||||||
|
|||||||
@@ -66,14 +66,27 @@ class APIScenarioExecutor {
|
|||||||
let model = JSON.parse(JSON.stringify(this.apiTester.getApiRequestModel(currentNode.data.id)));
|
let model = JSON.parse(JSON.stringify(this.apiTester.getApiRequestModel(currentNode.data.id)));
|
||||||
model.responseModel = {};
|
model.responseModel = {};
|
||||||
this.appendLog(`===============================================================\nExecuting API [${model.name}]`);
|
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.isRunning = false;
|
||||||
|
if (model.type === 'TCP') {
|
||||||
|
this.logTcpResponse(response);
|
||||||
|
nodeElement.classList.add('result-success');
|
||||||
|
} else {
|
||||||
this.logHttpResponse(response);
|
this.logHttpResponse(response);
|
||||||
if (response.status === 200 || response.status === 201) {
|
if (response.status === 200 || response.status === 201) {
|
||||||
nodeElement.classList.add('result-success');
|
nodeElement.classList.add('result-success');
|
||||||
} else {
|
} else {
|
||||||
nodeElement.classList.add('result-error');
|
nodeElement.classList.add('result-error');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
nodeElement.classList.add('result-abort');
|
nodeElement.classList.add('result-abort');
|
||||||
@@ -105,7 +118,13 @@ class APIScenarioExecutor {
|
|||||||
|
|
||||||
logHttpRequest(model) {
|
logHttpRequest(model) {
|
||||||
console.log(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);
|
this.appendLog(log);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,6 +133,11 @@ class APIScenarioExecutor {
|
|||||||
this.appendLog(log);
|
this.appendLog(log);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logTcpResponse(response) {
|
||||||
|
let log = `[Response]\n${response.body}\n`;
|
||||||
|
this.appendLog(log);
|
||||||
|
}
|
||||||
|
|
||||||
parseRequestHeaders(headers) {
|
parseRequestHeaders(headers) {
|
||||||
let parsedHeaders = '';
|
let parsedHeaders = '';
|
||||||
headers.forEach(header => {
|
headers.forEach(header => {
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ class APITester {
|
|||||||
width: 0
|
width: 0
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < this.responseEditors.length; i++) {
|
for (let i = 0; i < this.responseEditors.length; i++) {
|
||||||
this.responseEditors[i].layout({
|
this.responseEditors[i].layout({
|
||||||
width: 0
|
width: 0
|
||||||
@@ -387,14 +388,13 @@ class APITester {
|
|||||||
});
|
});
|
||||||
let language = model.type === 'HTTP' ? 'json' : 'text';
|
let language = model.type === 'HTTP' ? 'json' : 'text';
|
||||||
|
|
||||||
let target = null;
|
|
||||||
let requestBodyEditor = null;
|
let requestBodyEditor = null;
|
||||||
|
let target = null;
|
||||||
|
|
||||||
if (model.type === 'HTTP') {
|
if (model.type === 'HTTP') {
|
||||||
let httpClientView = new HTTPClientView(me, this.serverManager, apiRequestTabContent, model);
|
let httpClientView = new HTTPClientView(me, this.serverManager, apiRequestTabContent, model);
|
||||||
httpClientView.init();
|
httpClientView.init();
|
||||||
this.openTab(model.id);
|
target = this.openTab(model.id);
|
||||||
target = $('#api_request_' + model.id);
|
|
||||||
requestBodyEditor = monaco.editor.create(target.find('.request_body')[0], {
|
requestBodyEditor = monaco.editor.create(target.find('.request_body')[0], {
|
||||||
value: model.requestBody, language: 'json', automaticLayout: true
|
value: model.requestBody, language: 'json', automaticLayout: true
|
||||||
});
|
});
|
||||||
@@ -403,12 +403,13 @@ class APITester {
|
|||||||
if (model.type === 'TCP') {
|
if (model.type === 'TCP') {
|
||||||
const tcpClientView = new TCPClientView(me, this.serverManager, apiRequestTabContent, model);
|
const tcpClientView = new TCPClientView(me, this.serverManager, apiRequestTabContent, model);
|
||||||
tcpClientView.init();
|
tcpClientView.init();
|
||||||
this.openTab(model.id);
|
target = this.openTab(model.id);
|
||||||
target = $('#api_request_' + model.id);
|
|
||||||
requestBodyEditor = monaco.editor.create(target.find('.request_body')[0], {
|
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) => {
|
requestBodyEditor.onMouseMove((e) => {
|
||||||
@@ -428,7 +429,8 @@ class APITester {
|
|||||||
});
|
});
|
||||||
|
|
||||||
me.requestEditors.push(requestBodyEditor);
|
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();
|
me.requestEditors[index].layout();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -553,6 +555,7 @@ class APITester {
|
|||||||
}
|
}
|
||||||
|
|
||||||
openTab(id) {
|
openTab(id) {
|
||||||
|
const target = $(`#api_request_${id}`);
|
||||||
try {
|
try {
|
||||||
$(`#api_request_tab_${id}`).tab('show'); // API request 탭 활성화
|
$(`#api_request_tab_${id}`).tab('show'); // API request 탭 활성화
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -562,6 +565,7 @@ class APITester {
|
|||||||
$(`#api_request_${id}`).find('.btn_request_body_tab').tab('show'); // Body 탭 활성화
|
$(`#api_request_${id}`).find('.btn_request_body_tab').tab('show'); // Body 탭 활성화
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
}
|
}
|
||||||
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
addHeader(event) {
|
addHeader(event) {
|
||||||
@@ -601,7 +605,6 @@ class APITester {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log(queryParams);
|
|
||||||
return queryParams;
|
return queryParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -791,18 +794,32 @@ class APITester {
|
|||||||
})[0];
|
})[0];
|
||||||
let me = this;
|
let me = this;
|
||||||
let newApi = {
|
let newApi = {
|
||||||
id: '', server: null, method: 'GET', name: newApiName, path: '', type: newApiType, headers: [], queryParams: [], requestBody: '', preRequestScript: '', postRequestScript: '', collectionId: collectionId, responseModel: {
|
id: '',
|
||||||
status: 0, time: 0, size: 0, headers: [], ody: ''
|
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;
|
newApi.collectionName = collection.name;
|
||||||
|
|
||||||
if (newApiType === 'TCP') {
|
if (newApiType === 'TCP') {
|
||||||
newApi.layout = [];
|
newApi.layout = { layoutItems : []};
|
||||||
newApi.layout.push({layoutItems: []});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.currentAjaxRequest = $.ajax({
|
this.currentAjaxRequest = $.ajax({
|
||||||
url: this.getAPISaveURL(collectionId), type: 'POST', contentType: 'application/json', data: JSON.stringify(newApi), success: function(data) {
|
url: this.getAPISaveURL(collectionId), type: 'POST', contentType: 'application/json', data: JSON.stringify(newApi), success: function(data) {
|
||||||
newApi.id = 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;
|
||||||
@@ -16,9 +16,10 @@ class EditableInputModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class EditableInputView {
|
class EditableInputView {
|
||||||
constructor(model, controller) {
|
constructor(model, controller, options) {
|
||||||
this.model = model;
|
this.model = model;
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
|
this.options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
render(element) {
|
render(element) {
|
||||||
@@ -30,8 +31,17 @@ class EditableInputView {
|
|||||||
|
|
||||||
generateInputHtml() {
|
generateInputHtml() {
|
||||||
let contentHtml = this.model.getValue() ? this.renderContent(this.parseContent(this.model.getValue())) : ZERO_WIDTH_SPACE;
|
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 `
|
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}
|
${contentHtml}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -132,9 +142,9 @@ class EditableInputView {
|
|||||||
|
|
||||||
|
|
||||||
class EditableInputController {
|
class EditableInputController {
|
||||||
constructor(model) {
|
constructor(model, options) {
|
||||||
this.model = model;
|
this.model = model;
|
||||||
this.view = new EditableInputView(this.model, this);
|
this.view = new EditableInputView(this.model, this, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
init(element, callback) {
|
init(element, callback) {
|
||||||
@@ -251,7 +261,7 @@ class EditableInput {
|
|||||||
constructor(options = {}) {
|
constructor(options = {}) {
|
||||||
// Initialize the model with the value
|
// Initialize the model with the value
|
||||||
this.model = new EditableInputModel(options.value || '');
|
this.model = new EditableInputModel(options.value || '');
|
||||||
this.controller = new EditableInputController(this.model);
|
this.controller = new EditableInputController(this.model, options);
|
||||||
|
|
||||||
// Additional properties as required
|
// Additional properties as required
|
||||||
this.name = options.name || '';
|
this.name = options.name || '';
|
||||||
|
|||||||
@@ -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,10 +33,18 @@ class LoadTestResultDetailController {
|
|||||||
|
|
||||||
showDetail(result) {
|
showDetail(result) {
|
||||||
this.view.outputEditor.setValue('');
|
this.view.outputEditor.setValue('');
|
||||||
|
console.log(result);
|
||||||
|
if (result.request.type === 'TCP') {
|
||||||
|
this.logTcpRequest(result.request);
|
||||||
|
this.logTcpResponse(result.response);
|
||||||
|
|
||||||
|
} else {
|
||||||
this.logHttpRequest(result.request);
|
this.logHttpRequest(result.request);
|
||||||
this.logHttpResponse(result.response);
|
this.logHttpResponse(result.response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
reset(){
|
reset(){
|
||||||
this.view.outputEditor.setValue('');
|
this.view.outputEditor.setValue('');
|
||||||
}
|
}
|
||||||
@@ -48,18 +56,32 @@ class LoadTestResultDetailController {
|
|||||||
this.view.outputEditor.executeEdits('my-source', [op]);
|
this.view.outputEditor.executeEdits('my-source', [op]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
logHttpRequest(request) {
|
logHttpRequest(request) {
|
||||||
console.log({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}`;
|
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);
|
this.appendLog(log);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logTcpRequest(model) {
|
||||||
|
console.log(model);
|
||||||
|
let log = `[TCP API Request]\n[Request Body]\n${model.requestBody}`;
|
||||||
|
this.appendLog(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
logHttpResponse(response) {
|
logHttpResponse(response) {
|
||||||
console.log({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`;
|
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);
|
this.appendLog(log);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logTcpResponse(response) {
|
||||||
|
console.log({response});
|
||||||
|
let log = `[Response]\n${response.body}\n`;
|
||||||
|
this.appendLog(log);
|
||||||
|
}
|
||||||
|
|
||||||
parseRequestHeaders(headers) {
|
parseRequestHeaders(headers) {
|
||||||
let parsedHeaders = '';
|
let parsedHeaders = '';
|
||||||
headers.forEach(header => {
|
headers.forEach(header => {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import EditableInput from './EditableInput';
|
||||||
|
|
||||||
class ServerManager {
|
class ServerManager {
|
||||||
constructor(servers) {
|
constructor(servers) {
|
||||||
this.servers = servers ||[];
|
this.servers = servers || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
basePath(serverId) {
|
basePath(serverId) {
|
||||||
@@ -23,17 +25,93 @@ class ServerManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderTcpServers(selectedServer) {
|
renderTcpServers(selectedServer) {
|
||||||
const optionsHtml = this.servers.filter( server => server.scheme === 'tcp')
|
const optionsHtml = this.servers.filter(server => server.scheme === 'tcp').map(server => `<option value="${server.id}" ${selectedServer == server.id ? 'selected' : ''}>${server.name}</option>`).join('');
|
||||||
.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>
|
return `<select class="form-select server" name="server" style="width: 180px; border-radius: 0;" required>
|
||||||
<option value="">서버 선택</option>
|
<option value="">서버 선택</option>
|
||||||
${optionsHtml}</select>`;
|
${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) {
|
renderHttpServers(selectedServer) {
|
||||||
const optionsHtml = this.servers.filter( server => server.scheme !== 'tcp')
|
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('');
|
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>
|
return `<select class="form-select server" name="server" style="width: 180px; border-radius: 0;" required>
|
||||||
<option value="">서버 선택</option>
|
<option value="">서버 선택</option>
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import EditableInput from './EditableInput';
|
import LayoutGrid from './LayoutGrid';
|
||||||
import APIClient from '../APIClient';
|
import LayoutTreeGrid from './LayoutTreeGrid';
|
||||||
|
import LayoutTreeDataGrid from './LayoutTreeDataGrid';
|
||||||
|
import * as monaco from 'monaco-editor';
|
||||||
|
import ApiLayoutItemTreeBuilder from './ApiLayoutItemTreeBuilder';
|
||||||
|
import ApiLayoutItemTreeDataBuilder from './ApiLayoutItemTreeDataBuilder';
|
||||||
|
|
||||||
class TCPClientView {
|
class TCPClientView {
|
||||||
|
|
||||||
@@ -11,255 +14,93 @@ class TCPClientView {
|
|||||||
this.model = model;
|
this.model = model;
|
||||||
this.index = index;
|
this.index = index;
|
||||||
this.server = this.serverManager.getServer(this.model.server);
|
this.server = this.serverManager.getServer(this.model.server);
|
||||||
this.apiClient = new APIClient();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
console.log('TCPClientView init');
|
||||||
this.apiRequestTabContent.append(this.tcpApiRequestTemplate(this.model, this.index));
|
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) {
|
let layoutJson = JSON.stringify(this.model.layout, null, 2);
|
||||||
this.requestBodyEditor = editor;
|
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');
|
const layoutDataTreeOptions = [
|
||||||
requestContainer.empty();
|
{name: 'loutItemSerno', width: 100, isFirst: true, label: '#'},
|
||||||
|
{name: 'loutItemName', width: 100, align: 'center', label: '항목명(영문)'},
|
||||||
const headerHtml = `
|
{name: 'loutItemDesc', width: 100, align: 'center', label: '항목설명'},
|
||||||
<table class="table layout_table">
|
{name: 'loutItemDepth', width: 60, align: 'center', label: '깊이'},
|
||||||
<colgroup>
|
{name: 'loutItemType', width: 100, align: 'center', label: '아이템 유형'},
|
||||||
<col style="width: 80px;">
|
{name: 'loutItemOccCnt', width: 100, align: 'center', label: '반복 횟수'},
|
||||||
<col style="width: 120px;">
|
{name: 'loutItemOccRef', width: 120, align: 'center', label: '반복 참조 필드'},
|
||||||
<col style="width: 150px;">
|
{name: 'loutItemLength', width: 100, align: 'center', label: '데이터 길이'},
|
||||||
<col style="width: 80px;">
|
{name: 'value', width: 200, align: 'left', label: '값', type: 'textfield'}
|
||||||
<col style="width: 110px;">
|
];
|
||||||
<col style="width: 80px;">
|
|
||||||
<col style="width: 100px;">
|
let layoutRoot = ApiLayoutItemTreeBuilder.createTree(this.model.layout.layoutItems);
|
||||||
<col style="width: 120px;">
|
let replicator = new ApiLayoutItemTreeDataBuilder(10000);
|
||||||
<col style="width: 80px;">
|
let layoutDataRoot = replicator.createReplicatedTree(layoutRoot);
|
||||||
<col>
|
|
||||||
<col style="width: 120px;">
|
console.log(this.apiRequestTabContent.find(`#api_request_${this.model.id}`).find('.request_layout_data_tree'));
|
||||||
</colgroup>
|
console.log(this.apiRequestTabContent.find(`#api_request_${this.model.id}`).find('.request_layout_tree'));
|
||||||
<thead>
|
console.log(this.apiRequestTabContent.find(`#api_request_${this.model.id}`).find('.total_message_length'));
|
||||||
<tr>
|
|
||||||
<th scope="col" style="text-align: center;width: 80px;">#</th>
|
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'));
|
||||||
<th scope="col" style="text-align: center;width: 120px;">항목명(영문)</th>
|
layoutTreeDataGrid.init( async (finalMessage) => {
|
||||||
<th scope="col" style="text-align: center;width: 150px;">항목설명</th>
|
this.requestBodyEditor.setValue(finalMessage);
|
||||||
<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>
|
let layoutTreeGrid = new LayoutTreeGrid(this.apiRequestTabContent.find(`#api_request_${this.model.id}`).find('.request_layout_tree'), layoutRoot, layoutDataTreeOptions, (root) => {
|
||||||
<th scope="col" style="text-align: center;width: 100px;">반복 참조 필드</th>
|
let replicator = new ApiLayoutItemTreeDataBuilder(10000);
|
||||||
<th scope="col" style="text-align: center;width: 120px;">데이터 타입</th>
|
let layoutDataRoot = replicator.createReplicatedTree(root);
|
||||||
<th scope="col" style="text-align: center;width: 80px;">데이터 길이</th>
|
layoutTreeDataGrid.rebuild(layoutDataRoot);
|
||||||
<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>
|
let layoutGrid = new LayoutGrid(this.model.layout);
|
||||||
</thead>
|
layoutGrid.init(this.apiRequestTabContent.find(`#api_request_${this.model.id}`), (model) => {
|
||||||
<tbody>
|
model.items.forEach((item) => {
|
||||||
`;
|
item.children = [];
|
||||||
|
});
|
||||||
const footerHtml = `
|
let updatedRoot = ApiLayoutItemTreeBuilder.createTree(model.items);
|
||||||
</tbody>
|
layoutTreeGrid.rebuild(updatedRoot, (root) => {
|
||||||
</table>
|
let replicator = new ApiLayoutItemTreeDataBuilder(10000);
|
||||||
`;
|
let layoutDataRoot = replicator.createReplicatedTree(root);
|
||||||
|
layoutTreeDataGrid.rebuild(layoutDataRoot);
|
||||||
const fieldsHtml = layout.layoutItems ? layout.layoutItems.map((item, index) => {
|
|
||||||
return this.renderApiLayoutItem(item, index);
|
|
||||||
}).join('') : '';
|
|
||||||
|
|
||||||
requestContainer.append(headerHtml + fieldsHtml + footerHtml);
|
|
||||||
|
|
||||||
requestContainer.find('.editable').each((index, editableElement) => {
|
|
||||||
const editableInput = new EditableInput({
|
|
||||||
name: editableElement.dataset.name,
|
|
||||||
value: editableElement.dataset.value
|
|
||||||
});
|
});
|
||||||
|
|
||||||
editableInput.init(editableElement, (value) => {
|
|
||||||
_.set(this.model.layout, editableElement.dataset.name, value);
|
|
||||||
this.buildMessage();
|
|
||||||
$('span.variable').off('mouseover');
|
|
||||||
$('span.variable').off('mouseover');
|
$('span.variable').off('mouseover');
|
||||||
|
$('span.variable').off('mouseout');
|
||||||
document.querySelectorAll('span.variable').forEach(element => {
|
document.querySelectorAll('span.variable').forEach(element => {
|
||||||
let text = element.innerText;
|
let text = element.innerText;
|
||||||
element.addEventListener('mouseover', () => this.parent.showPopper(element, text));
|
element.addEventListener('mouseover', () => this.parent.showPopper(element, text));
|
||||||
element.addEventListener('mouseout', () => this.parent.hidePopper(element));
|
element.addEventListener('mouseout', () => this.parent.hidePopper(element));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.addEventListener('resize', this.resizeEditor.bind(this));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
resizeEditor() {
|
||||||
|
this.requestBodyEditor.layout({
|
||||||
|
width: 0
|
||||||
});
|
});
|
||||||
|
this.requestBodyEditor.layout({});
|
||||||
let totalLength = 0;
|
this.layoutJsonEditor.layout({
|
||||||
this.model.layout.layoutItems.forEach(item => {
|
width: 0
|
||||||
totalLength += parseInt(item.loutItemLength);
|
|
||||||
});
|
});
|
||||||
this.apiRequestTabContent.find('.total_message_length').text(totalLength);
|
this.layoutJsonEditor.layout({});
|
||||||
this.setupListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
handleUIUpdate(event) {
|
|
||||||
if (event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const element = event.currentTarget;
|
|
||||||
const fieldName = $(element).attr('name');
|
|
||||||
const newValue = this.getFieldValue(element);
|
|
||||||
if (fieldName !== '') {
|
|
||||||
_.set(this.model.layout, fieldName, newValue);
|
|
||||||
this.buildMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let totalLength = 0;
|
|
||||||
this.model.layout.layoutItems.forEach(item => {
|
|
||||||
totalLength += parseInt(item.loutItemLength);
|
|
||||||
});
|
|
||||||
this.apiRequestTabContent.find('.total_message_length').text(totalLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
getFieldValue(element) {
|
|
||||||
if ($(element).is('select') || $(element).is('input[type="text"]') || $(element).is('input[type="number"]')) {
|
|
||||||
return $(element).val();
|
|
||||||
}
|
|
||||||
if ($(element).is('input[type="checkbox"]')) {
|
|
||||||
return $(element).is(':checked');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderApiLayoutItem(item, index) {
|
|
||||||
if (item.value === null)
|
|
||||||
item.value = '';
|
|
||||||
|
|
||||||
return `
|
|
||||||
<tr>
|
|
||||||
<td style="width: 80px; text-align: center;"><span style="width: 80px; text-align: center;">${item.loutItemSerno}</span></td>
|
|
||||||
<td><input type="text" class="field" name="layoutItems[${index}].loutItemName" value="${item.loutItemName}"></td>
|
|
||||||
<td><input type="text" class="field" name="layoutItems[${index}].loutItemDesc" value="${item.loutItemDesc}"></td>
|
|
||||||
<td><input type="number" class="field" style="width: 80px;" name="layoutItems[${index}].loutItemDepth" min="1" value="${item.loutItemDepth}"></td>
|
|
||||||
<td>
|
|
||||||
<select name="layoutItems[${index}].loutItemType" class="field" id="loutItemType${index}">
|
|
||||||
<option value="Field" ${item.loutItemType === 'Field' ? 'selected' : ''}>Field</option>
|
|
||||||
<option value="Grid" ${item.loutItemType === 'Grid' ? 'selected' : ''}>Grid</option>
|
|
||||||
<option value="Group" ${item.loutItemType === 'Group' ? 'selected' : ''}>Group</option>
|
|
||||||
<option value="Attr" ${item.loutItemType === 'Attr' ? 'selected' : ''}>Attr</option>
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
<td><input type="number" class="field" style="width: 80px;" name="layoutItems[${index}].loutItemOccCnt" min="0" value="${item.loutItemOccCnt}"></td>
|
|
||||||
<td><input type="text" class="field" name="layoutItems[${index}].loutItemOccRef" value="${item.loutItemOccRef}"></td>
|
|
||||||
<td>
|
|
||||||
<select name="layoutItems[${index}].loutItemDataType" class="field" id="loutItemDataType${index}">
|
|
||||||
<option value="BigDecimal" ${item.loutItemDataType === 'BigDecimal' ? 'selected' : ''}>BigDecimal</option>
|
|
||||||
<option value="Int" ${item.loutItemDataType === 'Int' ? 'selected' : ''}>Int</option>
|
|
||||||
<option value="Long" ${item.loutItemDataType === 'Long' ? 'selected' : ''}>Long</option>
|
|
||||||
<option value="String" ${item.loutItemDataType === 'String' ? 'selected' : ''}>String</option>
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
<td><input type="number" class="field" style="width: 80px;" name="layoutItems[${index}].loutItemLength" min="1" value="${item.loutItemLength}"></td>
|
|
||||||
<td><!-- input type="text" class="field" name="layoutItems[${index}].value" value="${item.value !== undefined ? item.value : ''}" -->
|
|
||||||
<div class="editable" data-name="layoutItems[${index}].value" data-value="${item.value !== undefined ? item.value : ''}"></div>
|
|
||||||
</td>
|
|
||||||
<td style="width: 120px; text-align: center;">
|
|
||||||
<div style="width: 120px !important;">
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary move-up" data-index="${index}"><i class="fas fa-arrow-up"></i></button>
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary move-down" data-index="${index}"><i class="fas fa-arrow-down"></i></button>
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary delete" data-index="${index}"><i class="fas fa-trash-alt"></i></button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
`;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
recalculateSerno() {
|
|
||||||
// Reassign loutItemSerno sequentially based on the current order
|
|
||||||
this.model.layout.layoutItems.forEach((item, index) => {
|
|
||||||
item.loutItemSerno = index + 1; // Assuming you want to start numbering from 1
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setupListeners() {
|
|
||||||
const table = this.apiRequestTabContent.find('.layout_table');
|
|
||||||
table.on('change', 'select.field, input.field', this.handleUIUpdate.bind(this));
|
|
||||||
|
|
||||||
table.on('click', '.add-layout-item', event => {
|
|
||||||
let item = {
|
|
||||||
'parent': 0,
|
|
||||||
'level': 1,
|
|
||||||
'loutName': '',
|
|
||||||
'loutItemName': '',
|
|
||||||
'loutItemDesc': '',
|
|
||||||
'loutItemDepth': 1,
|
|
||||||
'loutItemType': 'Field',
|
|
||||||
'loutItemOccCnt': '',
|
|
||||||
'loutItemOccRef': '',
|
|
||||||
'loutItemDataType': 'String',
|
|
||||||
'loutItemLength': 1,
|
|
||||||
'loutItemDecimal': 0,
|
|
||||||
'loutItemDefault': '',
|
|
||||||
'loutItemMaskYn': 'N',
|
|
||||||
'loutItemMaskOffset': 0,
|
|
||||||
'loutItemMaskLength': 0,
|
|
||||||
'parentLoutItemIndex': 0,
|
|
||||||
'expanded': true,
|
|
||||||
'isLeaf': true,
|
|
||||||
'LOUTITEMPATH': '',
|
|
||||||
'value' : ''
|
|
||||||
};
|
|
||||||
this.model.layout.layoutItems.push(item);
|
|
||||||
this.recalculateSerno();
|
|
||||||
this.renderApiLayout(this.model.layout);
|
|
||||||
});
|
|
||||||
|
|
||||||
table.on('click', '.move-up', event => {
|
|
||||||
const index = $(event.currentTarget).data('index');
|
|
||||||
if (index > 0) {
|
|
||||||
const temp = this.model.layout.layoutItems[index];
|
|
||||||
this.model.layout.layoutItems[index] = this.model.layout.layoutItems[index - 1];
|
|
||||||
this.model.layout.layoutItems[index - 1] = temp;
|
|
||||||
this.recalculateSerno();
|
|
||||||
this.renderApiLayout(this.model.layout);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table.on('click', '.move-down', event => {
|
|
||||||
const index = $(event.currentTarget).data('index');
|
|
||||||
if (index < this.model.layout.layoutItems.length - 1) {
|
|
||||||
const temp = this.model.layout.layoutItems[index];
|
|
||||||
this.model.layout.layoutItems[index] = this.model.layout.layoutItems[index + 1];
|
|
||||||
this.model.layout.layoutItems[index + 1] = temp;
|
|
||||||
this.recalculateSerno();
|
|
||||||
this.renderApiLayout(this.model.layout);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table.on('click', '.delete', event => {
|
|
||||||
const index = $(event.currentTarget).data('index');
|
|
||||||
this.model.layout.layoutItems.splice(index, 1);
|
|
||||||
this.recalculateSerno();
|
|
||||||
this.renderApiLayout(this.model.layout);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async buildMessage() {
|
|
||||||
let message = '';
|
|
||||||
this.requestBodyEditor.setValue(message);
|
|
||||||
|
|
||||||
for (const item of this.model.layout.layoutItems) {
|
|
||||||
let duplicated = item.value;
|
|
||||||
const value = await this.apiClient.processPreScriptAndVariables(this.model, duplicated, null);
|
|
||||||
message += this.fillPadding(item.loutItemLength, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.requestBodyEditor.setValue(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
fillPadding(dataLength, value) {
|
|
||||||
value = String(value);
|
|
||||||
return value.padStart(dataLength, ' ');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tcpApiRequestTemplate(apiRequest, index) {
|
tcpApiRequestTemplate(apiRequest, index) {
|
||||||
|
console.log('tcpApiRequestTemplate');
|
||||||
return `
|
return `
|
||||||
<div class="tab-pane fade mt-1 api_request" id="api_request_${apiRequest.id}" style="height: 100%;">
|
<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> > <span class="api_name">${apiRequest.name}</span></div>
|
<div><span class="collection_name">${apiRequest.collectionName}</span> > <span class="api_name">${apiRequest.name}</span></div>
|
||||||
@@ -268,41 +109,41 @@ class TCPClientView {
|
|||||||
${this.serverManager.renderTcpServers(apiRequest.server)}
|
${this.serverManager.renderTcpServers(apiRequest.server)}
|
||||||
<label class="form-label must">Server</label>
|
<label class="form-label must">Server</label>
|
||||||
</div>
|
</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 send me-1">Send</button>
|
||||||
<button class="btn btn-primary save" data-index="${index}" style="height: 58px;">저장</button>
|
<button class="btn btn-primary save" data-index="${index}" style="height: 58px;">저장</button>
|
||||||
</div>
|
</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 d-flex flex-column" style="height:100%;">
|
||||||
<div class="api_request_body">
|
<div class="api_request_body">
|
||||||
<ul class="nav nav-tabs mt-1" role="tablist">
|
<ul class="nav nav-tabs mt-1" role="tablist">
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link btn_request_body_tab" data-bs-toggle="tab"
|
<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}"
|
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>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link btn_prerequest_tab" data-bs-toggle="tab"
|
<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
|
aria-selected="false">Pre-Request
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
@@ -314,13 +155,30 @@ class TCPClientView {
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="tab-content" style="height: 100%;">
|
<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="d-flex flex-column">
|
||||||
<div class="request_layout" style="width:100%; border:1px solid grey; overflow: scroll"></div>
|
<div class="request_layout" style="border:1px solid grey; overflow: scroll"></div>
|
||||||
<div class="request_body" style="width:100%;height: 150px; border:1px solid grey;"></div>
|
|
||||||
</div>
|
</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 class="d-flex flex-row">
|
||||||
<div style="width:calc(100% - 200px);">
|
<div style="width:calc(100% - 200px);">
|
||||||
<div class="pre-request mt-1" style="height:300px;border:1px solid grey;"></div>
|
<div class="pre-request mt-1" style="height:300px;border:1px solid grey;"></div>
|
||||||
@@ -345,6 +203,7 @@ class TCPClientView {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="request_body mt-1" style="height: 150px; border:1px solid grey;"></div>
|
||||||
<div class="api_request_info" >
|
<div class="api_request_info" >
|
||||||
<span>전체 메시지 길이: </span>
|
<span>전체 메시지 길이: </span>
|
||||||
<span class="total_message_length"></span>
|
<span class="total_message_length"></span>
|
||||||
|
|||||||
@@ -30,22 +30,30 @@ module.exports = {
|
|||||||
'less-loader' // Compiles Less to CSS
|
'less-loader' // Compiles Less to CSS
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
test: /\.(woff|woff2|eot|ttf|otf)$/i,
|
||||||
|
type: 'asset/resource',
|
||||||
|
generator: {
|
||||||
|
filename: 'fonts/[name][ext]',
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
use: ['style-loader', 'css-loader']
|
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/',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
//,
|
||||||
|
// {
|
||||||
|
// test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
|
||||||
|
// use: [
|
||||||
|
// {
|
||||||
|
// loader: 'file-loader',
|
||||||
|
// options: {
|
||||||
|
// name: '[name].[ext]',
|
||||||
|
// outputPath: 'fonts/',
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
plugins: [new MonacoWebpackPlugin()],
|
plugins: [new MonacoWebpackPlugin()],
|
||||||
|
|||||||
+2
-2
@@ -10,8 +10,8 @@ jar.enabled = false
|
|||||||
|
|
||||||
group = 'com.eactive'
|
group = 'com.eactive'
|
||||||
version = '2.0.0'
|
version = '2.0.0'
|
||||||
sourceCompatibility = '11'
|
sourceCompatibility = '1.8'
|
||||||
targetCompatibility = 11
|
targetCompatibility = '1.8'
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
annotationProcessor
|
annotationProcessor
|
||||||
|
|||||||
+22
File diff suppressed because one or more lines are too long
+22
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user