load tester beta
This commit is contained in:
Generated
+768
-32
File diff suppressed because it is too large
Load Diff
@@ -16,9 +16,11 @@
|
||||
"@babel/core": "^7.23.7",
|
||||
"@babel/preset-env": "^7.23.8",
|
||||
"babel-loader": "^9.1.3",
|
||||
"css-loader": "^6.9.0",
|
||||
"css-loader": "^6.10.0",
|
||||
"expose-loader": "^4.1.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"less": "^4.2.0",
|
||||
"less-loader": "^12.2.0",
|
||||
"monaco-editor-webpack-plugin": "^7.1.0",
|
||||
"rimraf": "^5.0.5",
|
||||
"style-loader": "^3.3.4",
|
||||
@@ -28,9 +30,15 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@stomp/stompjs": "^7.0.0",
|
||||
"drawflow": "^0.0.59",
|
||||
"dropzone": "^6.0.0-beta.2",
|
||||
"golden-layout": "^2.6.0",
|
||||
"jqueryui": "^1.11.1",
|
||||
"monaco-editor": "^0.45.0",
|
||||
"monaco-editor-workers": "^0.45.0"
|
||||
"monaco-editor-workers": "^0.45.0",
|
||||
"sockjs-client": "^1.6.1",
|
||||
"split-grid": "^1.0.11",
|
||||
"x-data-spreadsheet": "^1.1.9"
|
||||
}
|
||||
}
|
||||
|
||||
+164
-167
@@ -8,159 +8,156 @@
|
||||
|
||||
class APIClient {
|
||||
|
||||
CLIENT_URL = 'mgmt/api/test.do';
|
||||
CLIENT_URL = 'mgmt/api/test.do';
|
||||
|
||||
constructor() {
|
||||
window.globals = {};
|
||||
this.variables = {};
|
||||
this.abortController = null;
|
||||
this.contextPath = document.querySelector('meta[name="context-path"]').getAttribute('content');
|
||||
constructor() {
|
||||
window.globals = {};
|
||||
this.variables = {};
|
||||
this.abortController = null;
|
||||
this.contextPath = document.querySelector('meta[name="context-path"]').getAttribute('content');
|
||||
|
||||
if (!this.contextPath) {
|
||||
this.contextPath = '/';
|
||||
if (!this.contextPath) {
|
||||
this.contextPath = '/';
|
||||
}
|
||||
|
||||
if (!this.contextPath.endsWith('/')) {
|
||||
this.contextPath += '/';
|
||||
}
|
||||
}
|
||||
|
||||
getClientUrl() {
|
||||
return this.contextPath + this.CLIENT_URL;
|
||||
}
|
||||
|
||||
async sendAPIRequest(model, preProcessCallback, consoleOutput) {
|
||||
try {
|
||||
const updatedModel = await this.executePreRequestScript(model, consoleOutput);
|
||||
let processedRequest = this.replacePlaceholdersWithVariables(updatedModel);
|
||||
|
||||
if (preProcessCallback) {
|
||||
preProcessCallback(processedRequest);
|
||||
}
|
||||
const response = await this.makeAjaxRequest(processedRequest, model, consoleOutput);
|
||||
const responseData = await response.json(); // Parse the JSON response
|
||||
|
||||
if (!response.ok) {
|
||||
const errorMessage = responseData.error || 'Unknown error';
|
||||
throw new Error(`API 호출 중 오류가 발생했습니다. 사유[${errorMessage}]`);
|
||||
}
|
||||
|
||||
const finalResponse = this.formatResponse(responseData);
|
||||
return this.executePostRequestScript(model.postRequestScript, finalResponse, consoleOutput).catch(error => {
|
||||
$('.toast-body').text(error);
|
||||
$('.toast').toast('show');
|
||||
return finalResponse; // Return the original response even if there's an error in the post-request script
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
formatResponse(data) {
|
||||
// Format response as needed before post-request script
|
||||
let response = {};
|
||||
response.body = data.body;
|
||||
response.status = data.status;
|
||||
response.headers = {};
|
||||
response.size = data.size;
|
||||
_.forEach(data.headers, (header) => {
|
||||
response.headers[header.key] = header.value;
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
abort() {
|
||||
if (this.abortController) {
|
||||
this.abortController.abort(); // abort the fetch
|
||||
this.abortController = null; // reset the controller
|
||||
}
|
||||
}
|
||||
|
||||
makeAjaxRequest(processedRequest, model) {
|
||||
this.abortController = new AbortController();
|
||||
const {signal} = this.abortController;
|
||||
|
||||
try {
|
||||
return fetch(this.getClientUrl(), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-XSRF-TOKEN': $('meta[name="_csrf"]').attr('content')
|
||||
},
|
||||
body: JSON.stringify(processedRequest),
|
||||
signal: signal // pass the signal to the fetch
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.name !== 'AbortError') {
|
||||
console.error('Network request failed:', error);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
replacePlaceholdersWithVariables(original) {
|
||||
let request = JSON.parse(JSON.stringify(original));
|
||||
|
||||
let mergedVariables = {};
|
||||
_.forEach(window.globals, (value, key) => {
|
||||
mergedVariables[key] = value;
|
||||
});
|
||||
|
||||
if (this.variables) {
|
||||
_.forEach(this.variables, (value, key) => {
|
||||
mergedVariables[key] = value;
|
||||
});
|
||||
}
|
||||
|
||||
_.forEach(mergedVariables, (value, key) => {
|
||||
const placeholder = `{{${key}}}`;
|
||||
// Replace in path
|
||||
request.path = request.path.replace(new RegExp(placeholder, 'g'), value);
|
||||
|
||||
// Replace in requestBody
|
||||
if (request.requestBody) {
|
||||
request.requestBody = request.requestBody.replace(new RegExp(placeholder, 'g'), value);
|
||||
}
|
||||
|
||||
// Replace in headers
|
||||
if (request.headers) {
|
||||
let replaced = JSON.stringify(request.headers).replace(new RegExp(placeholder, 'g'), value);
|
||||
request.headers = JSON.parse(replaced);
|
||||
}
|
||||
|
||||
// Replace in queryParams
|
||||
if (request.queryParams) {
|
||||
let replaced = JSON.stringify(request.queryParams).replace(new RegExp(placeholder, 'g'), value);
|
||||
request.queryParams = JSON.parse(replaced);
|
||||
}
|
||||
});
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
executePreRequestScript(model, consoleOutput) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let script = model.preRequestScript;
|
||||
let resultFrame = document.getElementById('preRequest');
|
||||
|
||||
this.variables = {};
|
||||
|
||||
window.preRequestComplete = (updatedModel, error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(updatedModel);
|
||||
}
|
||||
};
|
||||
|
||||
if (!this.contextPath.endsWith('/')) {
|
||||
this.contextPath += '/';
|
||||
}
|
||||
}
|
||||
|
||||
getClientUrl() {
|
||||
return this.contextPath + this.CLIENT_URL;
|
||||
}
|
||||
|
||||
async sendAPIRequest(model, preProcessCallback, consoleOutput) {
|
||||
try {
|
||||
const updatedModel = await this.executePreRequestScript(model, consoleOutput);
|
||||
let processedRequest = this.replacePlaceholdersWithVariables(updatedModel);
|
||||
|
||||
if (preProcessCallback) {
|
||||
preProcessCallback(processedRequest);
|
||||
}
|
||||
const response = await this.makeAjaxRequest(processedRequest, model, consoleOutput);
|
||||
const responseData = await response.json(); // Parse the JSON response
|
||||
|
||||
if (!response.ok) {
|
||||
const errorMessage = responseData.error || 'Unknown error';
|
||||
throw new Error(`API 호출 중 오류가 발생했습니다. 사유[${errorMessage}]`);
|
||||
}
|
||||
|
||||
const finalResponse = this.formatResponse(responseData);
|
||||
return this.executePostRequestScript(model.postRequestScript, finalResponse, consoleOutput)
|
||||
.catch(error => {
|
||||
$('.toast-body').text(error);
|
||||
$('.toast').toast('show');
|
||||
return finalResponse; // Return the original response even if there's an error in the post-request script
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
formatResponse(data) {
|
||||
// Format response as needed before post-request script
|
||||
let response = {};
|
||||
response.body = data.body;
|
||||
response.status = data.status;
|
||||
response.headers = {};
|
||||
response.size = data.size;
|
||||
_.forEach(data.headers, (header) => {
|
||||
response.headers[header.key] = header.value;
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
abort() {
|
||||
if (this.abortController) {
|
||||
this.abortController.abort(); // abort the fetch
|
||||
this.abortController = null; // reset the controller
|
||||
}
|
||||
}
|
||||
|
||||
makeAjaxRequest(processedRequest, model) {
|
||||
this.abortController = new AbortController();
|
||||
const {signal} = this.abortController;
|
||||
|
||||
try {
|
||||
return fetch(this.getClientUrl(), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-XSRF-TOKEN': $('meta[name="_csrf"]').attr('content')
|
||||
},
|
||||
body: JSON.stringify(processedRequest),
|
||||
signal: signal // pass the signal to the fetch
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.name !== 'AbortError') {
|
||||
console.error('Network request failed:', error);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
replacePlaceholdersWithVariables(original) {
|
||||
let request = JSON.parse(JSON.stringify(original));
|
||||
|
||||
let mergedVariables = {};
|
||||
_.forEach(window.globals, (value, key) => {
|
||||
mergedVariables[key] = value;
|
||||
});
|
||||
|
||||
if (this.variables) {
|
||||
_.forEach(this.variables, (value, key) => {
|
||||
mergedVariables[key] = value;
|
||||
});
|
||||
}
|
||||
|
||||
_.forEach(mergedVariables, (value, key) => {
|
||||
const placeholder = `{{${key}}}`;
|
||||
// Replace in path
|
||||
request.path = request.path.replace(new RegExp(placeholder, 'g'), value);
|
||||
|
||||
// Replace in requestBody
|
||||
if (request.requestBody) {
|
||||
request.requestBody = request.requestBody.replace(new RegExp(placeholder, 'g'), value);
|
||||
}
|
||||
|
||||
// Replace in headers
|
||||
if (request.headers) {
|
||||
let replaced = JSON.stringify(request.headers).replace(new RegExp(placeholder, 'g'), value);
|
||||
request.headers = JSON.parse(replaced);
|
||||
}
|
||||
|
||||
// Replace in queryParams
|
||||
if (request.queryParams) {
|
||||
let replaced = JSON.stringify(request.queryParams).replace(new RegExp(placeholder, 'g'), value);
|
||||
request.queryParams = JSON.parse(replaced);
|
||||
}
|
||||
});
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
|
||||
executePreRequestScript(model, consoleOutput) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let script = model.preRequestScript;
|
||||
let resultFrame = document.getElementById('preRequest');
|
||||
|
||||
this.variables = {};
|
||||
|
||||
window.preRequestComplete = (updatedModel, error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(updatedModel);
|
||||
}
|
||||
};
|
||||
|
||||
window.temp_variable = this.variables;
|
||||
window.currentRequest = model;
|
||||
window.consoleOutput = consoleOutput;
|
||||
resultFrame.srcdoc = `<script>
|
||||
window.temp_variable = this.variables;
|
||||
window.currentRequest = model;
|
||||
window.consoleOutput = consoleOutput;
|
||||
resultFrame.srcdoc = `<script>
|
||||
var oldLog = console.log;
|
||||
console.log = function(message) {
|
||||
if(window.parent.consoleOutput)
|
||||
@@ -179,23 +176,23 @@ class APIClient {
|
||||
window.parent.preRequestComplete(null, error);
|
||||
}
|
||||
</script>`;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
executePostRequestScript(script, response, consoleOutput) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let resultFrame = document.getElementById('postRequest');
|
||||
window.response = response;
|
||||
window.temp_variable = this.variables;
|
||||
window.postRequestComplete = (response, error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(response);
|
||||
}
|
||||
};
|
||||
window.consoleOutput = consoleOutput;
|
||||
resultFrame.srcdoc = `<script>
|
||||
executePostRequestScript(script, response, consoleOutput) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let resultFrame = document.getElementById('postRequest');
|
||||
window.response = response;
|
||||
window.temp_variable = this.variables;
|
||||
window.postRequestComplete = (response, error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(response);
|
||||
}
|
||||
};
|
||||
window.consoleOutput = consoleOutput;
|
||||
resultFrame.srcdoc = `<script>
|
||||
var oldLog = console.log;
|
||||
console.log = function(message) {
|
||||
if(window.parent.consoleOutput)
|
||||
@@ -212,8 +209,8 @@ class APIClient {
|
||||
window.parent.postRequestComplete(window.response, error.toString());
|
||||
}
|
||||
</script>`;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default APIClient;
|
||||
|
||||
+321
-321
@@ -1,368 +1,367 @@
|
||||
// require.config({paths: {'vs': '/plugins/vs'}});
|
||||
import * as monaco from 'monaco-editor';
|
||||
import APIScenarioExecutor from "./APIScenarioExecutor";
|
||||
import Drawflow from "drawflow";
|
||||
import APIScenarioExecutor from './APIScenarioExecutor';
|
||||
import Drawflow from 'drawflow';
|
||||
|
||||
class APIScenario {
|
||||
|
||||
constructor(apiTester, contextPath) {
|
||||
this.scenarioList = [];
|
||||
this.currentScenario = null;
|
||||
this.apiTester = apiTester;
|
||||
this.contextPath = contextPath || '/';
|
||||
}
|
||||
constructor(apiTester, contextPath) {
|
||||
this.scenarioList = [];
|
||||
this.currentScenario = null;
|
||||
this.apiTester = apiTester;
|
||||
this.contextPath = contextPath || '/';
|
||||
}
|
||||
|
||||
getAPIScenarioListURL() {
|
||||
return this.contextPath + 'mgmt/api_scenario/list.do';
|
||||
}
|
||||
getAPIScenarioListURL() {
|
||||
return this.contextPath + 'mgmt/api_scenario/list.do';
|
||||
}
|
||||
|
||||
getAPIScenarioCreateURL() {
|
||||
return this.contextPath + 'mgmt/api_scenario/create.do';
|
||||
}
|
||||
getAPIScenarioCreateURL() {
|
||||
return this.contextPath + 'mgmt/api_scenario/create.do';
|
||||
}
|
||||
|
||||
getAPIScenarioUpdateURL() {
|
||||
return this.contextPath + 'mgmt/api_scenario/update.do';
|
||||
}
|
||||
getAPIScenarioUpdateURL() {
|
||||
return this.contextPath + 'mgmt/api_scenario/update.do';
|
||||
}
|
||||
|
||||
getAPIScenarioDeleteURL() {
|
||||
return this.contextPath + 'mgmt/api_scenario/delete.do';
|
||||
}
|
||||
getAPIScenarioDeleteURL() {
|
||||
return this.contextPath + 'mgmt/api_scenario/delete.do';
|
||||
}
|
||||
|
||||
init() {
|
||||
this.loadScenarioList();
|
||||
this.outputEditor = monaco.editor.create(document.getElementById('output'), {
|
||||
value: '',
|
||||
automaticLayout: true
|
||||
});
|
||||
this.bindEvents();
|
||||
init() {
|
||||
this.loadScenarioList();
|
||||
this.outputEditor = monaco.editor.create(document.getElementById('output'), {
|
||||
value: '',
|
||||
automaticLayout: true
|
||||
});
|
||||
this.bindEvents();
|
||||
|
||||
let id = document.getElementById('drawflow');
|
||||
this.editor = new Drawflow(id);
|
||||
this.editor.reroute = true;
|
||||
this.editor.reroute_fix_curvature = true;
|
||||
this.editor.force_first_input = true;
|
||||
this.editor.start();
|
||||
// this.editor.on('contextmenu', (event) => {
|
||||
// let node = this.editor.getNodeFromId(event.target.id);
|
||||
// })
|
||||
let id = document.getElementById('drawflow');
|
||||
this.editor = new Drawflow(id);
|
||||
this.editor.reroute = true;
|
||||
this.editor.reroute_fix_curvature = true;
|
||||
this.editor.force_first_input = true;
|
||||
this.editor.start();
|
||||
// this.editor.on('contextmenu', (event) => {
|
||||
// let node = this.editor.getNodeFromId(event.target.id);
|
||||
// })
|
||||
|
||||
this.showOutput = true;
|
||||
}
|
||||
this.showOutput = true;
|
||||
}
|
||||
|
||||
loadScenarioList(callback) {
|
||||
const me = this;
|
||||
this.currentAjaxRequest = $.ajax({
|
||||
url: this.getAPIScenarioListURL(),
|
||||
type: 'GET',
|
||||
contentType: 'application/json',
|
||||
success: function (data) {
|
||||
me.scenarioList = data;
|
||||
let drawflowData = {drawflow: {Home: {data: {}}}};
|
||||
data.forEach((scenario) => {
|
||||
let scenarioData = null;
|
||||
try {
|
||||
scenarioData = JSON.parse(scenario.scenario)
|
||||
} catch (e) {
|
||||
scenarioData = {};
|
||||
}
|
||||
drawflowData.drawflow[scenario.id] = {data: scenarioData};
|
||||
});
|
||||
|
||||
me.editor.import(drawflowData);
|
||||
me.editor.changeModule('Home');
|
||||
},
|
||||
error: function (response, textStatus, errorThrown) {
|
||||
me.hideLoadingOverlay();
|
||||
$('.toast-body').text(response.responseJSON.error);
|
||||
$('.toast').toast('show');
|
||||
},
|
||||
complete: function () {
|
||||
me.renderScenarioList();
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
me.hideLoadingOverlay();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getScenarioList() {
|
||||
return this.scenarioList;
|
||||
}
|
||||
|
||||
openScenario(event) {
|
||||
event.preventDefault();
|
||||
let scenarioId = $(event.target).closest('li').data('id');
|
||||
this.openScenarioById(scenarioId);
|
||||
}
|
||||
|
||||
openScenarioById(scenarioId) {
|
||||
document.querySelectorAll(".api-scenario-list li").forEach((node) => {
|
||||
node.classList.remove('selected');
|
||||
loadScenarioList(callback) {
|
||||
const me = this;
|
||||
this.currentAjaxRequest = $.ajax({
|
||||
url: this.getAPIScenarioListURL(),
|
||||
type: 'GET',
|
||||
contentType: 'application/json',
|
||||
success: function(data) {
|
||||
me.scenarioList = data;
|
||||
let drawflowData = {drawflow: {Home: {data: {}}}};
|
||||
data.forEach((scenario) => {
|
||||
let scenarioData = null;
|
||||
try {
|
||||
scenarioData = JSON.parse(scenario.scenario);
|
||||
} catch (e) {
|
||||
scenarioData = {};
|
||||
}
|
||||
drawflowData.drawflow[scenario.id] = {data: scenarioData};
|
||||
});
|
||||
|
||||
//find li by data-id and add class selected
|
||||
document.querySelector(`.api-scenario-list li[data-id="${scenarioId}"]`).classList.add('selected');
|
||||
me.editor.import(drawflowData);
|
||||
me.editor.changeModule('Home');
|
||||
},
|
||||
error: function(response, textStatus, errorThrown) {
|
||||
me.hideLoadingOverlay();
|
||||
$('.toast-body').text(response.responseJSON.error);
|
||||
$('.toast').toast('show');
|
||||
},
|
||||
complete: function() {
|
||||
me.renderScenarioList();
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
me.hideLoadingOverlay();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.editor.changeModule(scenarioId);
|
||||
this.currentScenario = scenarioId;
|
||||
}
|
||||
getScenarioList() {
|
||||
return this.scenarioList;
|
||||
}
|
||||
|
||||
getScenario(scenarioId) {
|
||||
return this.getScenarioList().find((scenario) => scenario.id === scenarioId);
|
||||
}
|
||||
openScenario(event) {
|
||||
event.preventDefault();
|
||||
let scenarioId = $(event.target).closest('li').data('id');
|
||||
this.openScenarioById(scenarioId);
|
||||
}
|
||||
|
||||
showLoadingOverlay() {
|
||||
$("#loading-overlay").show();
|
||||
}
|
||||
openScenarioById(scenarioId) {
|
||||
document.querySelectorAll('.api-scenario-list li').forEach((node) => {
|
||||
node.classList.remove('selected');
|
||||
});
|
||||
|
||||
hideLoadingOverlay() {
|
||||
$("#loading-overlay").hide();
|
||||
}
|
||||
//find li by data-id and add class selected
|
||||
document.querySelector(`.api-scenario-list li[data-id="${scenarioId}"]`).classList.add('selected');
|
||||
|
||||
showNewScenarioModal() {
|
||||
$('#new_scenario_modal').find('input').val('');
|
||||
$('#new_scenario_modal').modal('show');
|
||||
}
|
||||
this.editor.changeModule(scenarioId);
|
||||
this.currentScenario = scenarioId;
|
||||
}
|
||||
|
||||
createScenario() {
|
||||
const name = $('#new_scenario_name').val();
|
||||
const me = this;
|
||||
me.showLoadingOverlay();
|
||||
this.currentAjaxRequest = $.ajax({
|
||||
url: this.getAPIScenarioCreateURL(),
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({name: name, scenario: "{}", description: ""}),
|
||||
success: function (data) {
|
||||
me.loadScenarioList(() => {
|
||||
me.openScenarioById(data.id);
|
||||
let start = `
|
||||
getScenario(scenarioId) {
|
||||
return this.getScenarioList().find((scenario) => scenario.id === scenarioId);
|
||||
}
|
||||
|
||||
showLoadingOverlay() {
|
||||
$('#loading-overlay').show();
|
||||
}
|
||||
|
||||
hideLoadingOverlay() {
|
||||
$('#loading-overlay').hide();
|
||||
}
|
||||
|
||||
showNewScenarioModal() {
|
||||
$('#new_scenario_modal').find('input').val('');
|
||||
$('#new_scenario_modal').modal('show');
|
||||
}
|
||||
|
||||
createScenario() {
|
||||
const name = $('#new_scenario_name').val();
|
||||
const me = this;
|
||||
me.showLoadingOverlay();
|
||||
this.currentAjaxRequest = $.ajax({
|
||||
url: this.getAPIScenarioCreateURL(),
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({name: name, scenario: '{}', description: ''}),
|
||||
success: function(data) {
|
||||
me.loadScenarioList(() => {
|
||||
me.openScenarioById(data.id);
|
||||
let start = `
|
||||
<div>
|
||||
<div><i class="fas fa-play"></i> Start </div>
|
||||
</div>
|
||||
`;
|
||||
me.editor.addNode('Start', 0, 1, 100, 100, 'start', {}, start);
|
||||
me.saveScenario();
|
||||
});
|
||||
},
|
||||
error: function (response, textStatus, errorThrown) {
|
||||
me.hideLoadingOverlay();
|
||||
$('.toast-body').text(response.responseJSON.error);
|
||||
$('.toast').toast('show');
|
||||
},
|
||||
complete: function () {
|
||||
me.hideLoadingOverlay();
|
||||
$('#new_scenario_modal').modal('hide');
|
||||
}
|
||||
me.editor.addNode('Start', 0, 1, 100, 100, 'start', {}, start);
|
||||
me.saveScenario();
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function(response, textStatus, errorThrown) {
|
||||
me.hideLoadingOverlay();
|
||||
$('.toast-body').text(response.responseJSON.error);
|
||||
$('.toast').toast('show');
|
||||
},
|
||||
complete: function() {
|
||||
me.hideLoadingOverlay();
|
||||
$('#new_scenario_modal').modal('hide');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
saveScenario() {
|
||||
if (this.currentScenario !== null) {
|
||||
let data = this.editor.export();
|
||||
console.log(data.drawflow[this.currentScenario].data);
|
||||
let updatedScenario = this.getScenario(this.currentScenario);
|
||||
updatedScenario.scenario = JSON.stringify(data.drawflow[this.currentScenario].data);
|
||||
saveScenario() {
|
||||
if (this.currentScenario !== null) {
|
||||
let data = this.editor.export();
|
||||
console.log(data.drawflow[this.currentScenario].data);
|
||||
let updatedScenario = this.getScenario(this.currentScenario);
|
||||
updatedScenario.scenario = JSON.stringify(data.drawflow[this.currentScenario].data);
|
||||
|
||||
const me = this;
|
||||
me.showLoadingOverlay();
|
||||
this.currentAjaxRequest = $.ajax({
|
||||
url: this.getAPIScenarioUpdateURL(),
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(updatedScenario),
|
||||
error: function (response, textStatus, errorThrown) {
|
||||
me.hideLoadingOverlay();
|
||||
$('.toast-body').text(response.responseJSON.error);
|
||||
$('.toast').toast('show');
|
||||
},
|
||||
complete: function () {
|
||||
me.hideLoadingOverlay();
|
||||
$('.toast-body').text('저장되었습니다.');
|
||||
$('.toast').toast('show');
|
||||
}
|
||||
});
|
||||
const me = this;
|
||||
me.showLoadingOverlay();
|
||||
this.currentAjaxRequest = $.ajax({
|
||||
url: this.getAPIScenarioUpdateURL(),
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(updatedScenario),
|
||||
error: function(response, textStatus, errorThrown) {
|
||||
me.hideLoadingOverlay();
|
||||
$('.toast-body').text(response.responseJSON.error);
|
||||
$('.toast').toast('show');
|
||||
},
|
||||
complete: function() {
|
||||
me.hideLoadingOverlay();
|
||||
$('.toast-body').text('저장되었습니다.');
|
||||
$('.toast').toast('show');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
showDeleteModal(event) {
|
||||
const id = $(event.currentTarget).closest('li').data('id');
|
||||
$('#confirm_delete_scenario_modal').find('.confirm_delete_scenario').data('id', id);
|
||||
$('#confirm_delete_scenario_modal').modal('show');
|
||||
}
|
||||
showDeleteModal(event) {
|
||||
const id = $(event.currentTarget).closest('li').data('id');
|
||||
$('#confirm_delete_scenario_modal').find('.confirm_delete_scenario').data('id', id);
|
||||
$('#confirm_delete_scenario_modal').modal('show');
|
||||
}
|
||||
|
||||
removeScenario(event) {
|
||||
const id = $(event.currentTarget).data('id');
|
||||
const me = this;
|
||||
me.showLoadingOverlay();
|
||||
this.currentAjaxRequest = $.ajax({
|
||||
url: this.getAPIScenarioDeleteURL(),
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({id: id}),
|
||||
error: function (response, textStatus, errorThrown) {
|
||||
me.hideLoadingOverlay();
|
||||
$('.toast-body').text(response.responseJSON.error);
|
||||
$('.toast').toast('show');
|
||||
},
|
||||
complete: function () {
|
||||
me.hideLoadingOverlay();
|
||||
$('#confirm_delete_scenario_modal').modal('hide');
|
||||
$('.toast-body').text('삭제되었습니다.');
|
||||
$('.toast').toast('show');
|
||||
me.editor.changeModule('Home');
|
||||
me.loadScenarioList(() => {
|
||||
if (me.getScenarioList().length > 0)
|
||||
me.openScenarioById(me.getScenarioList()[0].id);
|
||||
removeScenario(event) {
|
||||
const id = $(event.currentTarget).data('id');
|
||||
const me = this;
|
||||
me.showLoadingOverlay();
|
||||
this.currentAjaxRequest = $.ajax({
|
||||
url: this.getAPIScenarioDeleteURL(),
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({id: id}),
|
||||
error: function(response, textStatus, errorThrown) {
|
||||
me.hideLoadingOverlay();
|
||||
$('.toast-body').text(response.responseJSON.error);
|
||||
$('.toast').toast('show');
|
||||
},
|
||||
complete: function() {
|
||||
me.hideLoadingOverlay();
|
||||
$('#confirm_delete_scenario_modal').modal('hide');
|
||||
$('.toast-body').text('삭제되었습니다.');
|
||||
$('.toast').toast('show');
|
||||
me.editor.changeModule('Home');
|
||||
me.loadScenarioList(() => {
|
||||
if (me.getScenarioList().length > 0) {
|
||||
me.openScenarioById(me.getScenarioList()[0].id);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
toggleOutput() {
|
||||
// Define your selectors as constants for easy changes and better readability
|
||||
const scenarioContentSelector = '.api-scenario-content';
|
||||
const scenarioOutputSelector = '.api-scenario-output';
|
||||
const scenarioOutputBodySelector = '.api-scenario-output-body';
|
||||
const drawflowSelector = '#drawflow';
|
||||
const scenarioEditorSelector = '.scenario_editor';
|
||||
|
||||
// Ensure parentHeight is a number
|
||||
let parentHeight = parseInt($(scenarioContentSelector).css('height'), 10);
|
||||
|
||||
// Toggle the showOutput state
|
||||
this.showOutput = !this.showOutput;
|
||||
|
||||
if (!this.showOutput) {
|
||||
$(scenarioOutputSelector).css('flex', '0');
|
||||
$(scenarioOutputBodySelector).hide();
|
||||
|
||||
let editorHeight = $(scenarioEditorSelector).css('height');
|
||||
$(drawflowSelector).css('height', editorHeight);
|
||||
$(scenarioOutputSelector).removeClass('halfHeight');
|
||||
$(drawflowSelector).removeClass('halfHeight');
|
||||
} else {
|
||||
$(scenarioOutputSelector).css('flex', '1');
|
||||
$(scenarioOutputSelector).addClass('halfHeight');
|
||||
$(scenarioOutputBodySelector).show();
|
||||
$(drawflowSelector).addClass('halfHeight');
|
||||
$(drawflowSelector).css('height', parentHeight / 2);
|
||||
}
|
||||
|
||||
toggleOutput() {
|
||||
// Define your selectors as constants for easy changes and better readability
|
||||
const scenarioContentSelector = '.api-scenario-content';
|
||||
const scenarioOutputSelector = '.api-scenario-output';
|
||||
const scenarioOutputBodySelector = '.api-scenario-output-body';
|
||||
const drawflowSelector = '#drawflow';
|
||||
const scenarioEditorSelector = '.scenario_editor';
|
||||
// Adjustments for when parentHeight isn't a valid number
|
||||
if (isNaN(parentHeight)) {
|
||||
console.error('parentHeight is not a number. Check the height of the api-scenario-content element.');
|
||||
// Fallback or additional error handling can be placed here
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure parentHeight is a number
|
||||
let parentHeight = parseInt($(scenarioContentSelector).css('height'), 10);
|
||||
resizeEditor() {
|
||||
//FIXME: 창 크기 변경시 자동으로 editor 크기가 조정되지 않아서, 0으로 강제 변경후 다시 layout() 호출
|
||||
this.outputEditor.layout({
|
||||
width: 0
|
||||
});
|
||||
this.outputEditor.layout({});
|
||||
|
||||
// Toggle the showOutput state
|
||||
this.showOutput = !this.showOutput;
|
||||
}
|
||||
|
||||
if (!this.showOutput) {
|
||||
$(scenarioOutputSelector).css('flex', '0');
|
||||
$(scenarioOutputBodySelector).hide();
|
||||
runScript() {
|
||||
console.log(this.editor.export().drawflow);
|
||||
let graph = APIScenarioExecutor.extractGraphFromStart(this.editor.export().drawflow[this.currentScenario].data);
|
||||
this.executor = new APIScenarioExecutor(graph, this.apiTester, this.outputEditor);
|
||||
}
|
||||
|
||||
let editorHeight = $(scenarioEditorSelector).css('height');
|
||||
$(drawflowSelector).css('height', editorHeight);
|
||||
$(scenarioOutputSelector).removeClass('halfHeight');
|
||||
$(drawflowSelector).removeClass('halfHeight');
|
||||
} else {
|
||||
$(scenarioOutputSelector).css('flex', '1');
|
||||
$(scenarioOutputSelector).addClass('halfHeight');
|
||||
$(scenarioOutputBodySelector).show();
|
||||
$(drawflowSelector).addClass('halfHeight');
|
||||
$(drawflowSelector).css('height', parentHeight / 2);
|
||||
}
|
||||
runScenarioAll() {
|
||||
if (!this.executor) {
|
||||
this.runScript();
|
||||
}
|
||||
if (!this.executor.isDone) {
|
||||
this.executor.executeAll();
|
||||
} else {
|
||||
this.executor.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Adjustments for when parentHeight isn't a valid number
|
||||
if (isNaN(parentHeight)) {
|
||||
console.error('parentHeight is not a number. Check the height of the api-scenario-content element.');
|
||||
// Fallback or additional error handling can be placed here
|
||||
}
|
||||
runScenarioStep() {
|
||||
if (!this.executor) {
|
||||
this.runScript();
|
||||
}
|
||||
if (!this.executor.isDone) {
|
||||
this.executor.executeCurrentNode();
|
||||
this.executor.moveToNextNode();
|
||||
} else {
|
||||
this.executor.reset();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
resizeEditor() {
|
||||
//FIXME: 창 크기 변경시 자동으로 editor 크기가 조정되지 않아서, 0으로 강제 변경후 다시 layout() 호출
|
||||
this.outputEditor.layout({
|
||||
width: 0
|
||||
});
|
||||
this.outputEditor.layout({});
|
||||
resetScenario() {
|
||||
|
||||
}
|
||||
|
||||
pauseScenario() {
|
||||
|
||||
}
|
||||
|
||||
stopScenario() {
|
||||
if (this.executor) {
|
||||
this.executor.reset();
|
||||
}
|
||||
this.outputEditor.setValue('');
|
||||
}
|
||||
|
||||
runScript() {
|
||||
console.log(this.editor.export().drawflow);
|
||||
let graph = APIScenarioExecutor.extractGraphFromStart(this.editor.export().drawflow[this.currentScenario].data);
|
||||
this.executor = new APIScenarioExecutor(graph, this.apiTester, this.outputEditor);
|
||||
}
|
||||
|
||||
runScenarioAll() {
|
||||
if (!this.executor) {
|
||||
this.runScript();
|
||||
}
|
||||
if (!this.executor.isDone) {
|
||||
this.executor.executeAll();
|
||||
} else {
|
||||
this.executor.reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
runScenarioStep() {
|
||||
if (!this.executor) {
|
||||
this.runScript();
|
||||
}
|
||||
if (!this.executor.isDone) {
|
||||
this.executor.executeCurrentNode();
|
||||
this.executor.moveToNextNode();
|
||||
} else {
|
||||
this.executor.reset();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
resetScenario() {
|
||||
|
||||
}
|
||||
|
||||
pauseScenario() {
|
||||
|
||||
}
|
||||
|
||||
stopScenario() {
|
||||
if (this.executor)
|
||||
this.executor.reset();
|
||||
this.outputEditor.setValue('');
|
||||
}
|
||||
|
||||
addStart() {
|
||||
let start = `
|
||||
addStart() {
|
||||
let start = `
|
||||
<div>
|
||||
<div><i class="fas fa-play"></i> Start </div>
|
||||
</div>
|
||||
`;
|
||||
this.editor.addNode('Start', 0, 1, 100, 100, 'start', {}, start);
|
||||
this.editor.addNode('Start', 0, 1, 100, 100, 'start', {}, start);
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
const events = [
|
||||
{type: 'click', selector: '.open_scenario', action: this.openScenario.bind(this)},
|
||||
{type: 'click', selector: '.export', action: this.runScript.bind(this)},
|
||||
{type: 'click', selector: '.toggle_output', action: this.toggleOutput.bind(this)},
|
||||
{type: 'click', selector: '.run_scenario_step', action: this.runScenarioStep.bind(this)},
|
||||
{type: 'click', selector: '.run_scenario_all', action: this.runScenarioAll.bind(this)},
|
||||
{type: 'click', selector: '.reset_scenario', action: this.resetScenario.bind(this)},
|
||||
{type: 'click', selector: '.pause_scenario', action: this.pauseScenario.bind(this)},
|
||||
{type: 'click', selector: '.stop_scenario', action: this.stopScenario.bind(this)},
|
||||
{type: 'click', selector: '#new_scenario', action: this.showNewScenarioModal.bind(this)},
|
||||
{type: 'click', selector: '.create_scenario', action: this.createScenario.bind(this)},
|
||||
{type: 'click', selector: '.save_scenario', action: this.saveScenario.bind(this)},
|
||||
// {type: 'click', selector: '.add_start', action: this.addStart.bind(this)},
|
||||
{type: 'click', selector: '.delete_scenario', action: this.showDeleteModal.bind(this)},
|
||||
{type: 'click', selector: '.confirm_delete_scenario', action: this.removeScenario.bind(this)},
|
||||
{type: 'click', selector: '.stop_scenario', action: this.stopScenario.bind(this)}
|
||||
];
|
||||
|
||||
for (let event of events) {
|
||||
$(document).on(event.type, event.selector, event.action);
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
const events = [
|
||||
{type: 'click', selector: '.open_scenario', action: this.openScenario.bind(this)},
|
||||
{type: 'click', selector: '.export', action: this.runScript.bind(this)},
|
||||
{type: 'click', selector: '.toggle_output', action: this.toggleOutput.bind(this)},
|
||||
{type: 'click', selector: '.run_scenario_step', action: this.runScenarioStep.bind(this)},
|
||||
{type: 'click', selector: '.run_scenario_all', action: this.runScenarioAll.bind(this)},
|
||||
{type: 'click', selector: '.reset_scenario', action: this.resetScenario.bind(this)},
|
||||
{type: 'click', selector: '.pause_scenario', action: this.pauseScenario.bind(this)},
|
||||
{type: 'click', selector: '.stop_scenario', action: this.stopScenario.bind(this)},
|
||||
{type: 'click', selector: '#new_scenario', action: this.showNewScenarioModal.bind(this)},
|
||||
{type: 'click', selector: '.create_scenario', action: this.createScenario.bind(this)},
|
||||
{type: 'click', selector: '.save_scenario', action: this.saveScenario.bind(this)},
|
||||
// {type: 'click', selector: '.add_start', action: this.addStart.bind(this)},
|
||||
{type: 'click', selector: '.delete_scenario', action: this.showDeleteModal.bind(this)},
|
||||
{type: 'click', selector: '.confirm_delete_scenario', action: this.removeScenario.bind(this)},
|
||||
{type: 'click', selector: '.stop_scenario', action: this.stopScenario.bind(this)}
|
||||
];
|
||||
window.addEventListener('resize', this.resizeEditor.bind(this));
|
||||
var me = this;
|
||||
|
||||
for (let event of events) {
|
||||
$(document).on(event.type, event.selector, event.action);
|
||||
}
|
||||
$('#drawflow').droppable({
|
||||
drop: function(event, ui) {
|
||||
const x = ui.offset.left - $(this).offset().left;
|
||||
const y = ui.offset.top - $(this).offset().top;
|
||||
|
||||
window.addEventListener('resize', this.resizeEditor.bind(this));
|
||||
var me = this;
|
||||
const dataset = ui.draggable[0].dataset;
|
||||
var node = `<div><div class="title-box">API</div><div class="box">${dataset.name}</div></div>`;
|
||||
|
||||
$('#drawflow').droppable({
|
||||
drop: function (event, ui) {
|
||||
const x = ui.offset.left - $(this).offset().left;
|
||||
const y = ui.offset.top - $(this).offset().top;
|
||||
me.editor.addNode(dataset.name, 1, 1, x, y, dataset.nodeType, dataset, node);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const dataset = ui.draggable[0].dataset;
|
||||
var node = `<div><div class="title-box">API</div><div class="box">${dataset.name}</div></div>`;
|
||||
|
||||
me.editor.addNode(dataset.name, 1, 1, x, y, dataset.nodeType, dataset, node);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
scenarioListTemplate(scenario) {
|
||||
return `
|
||||
scenarioListTemplate(scenario) {
|
||||
return `
|
||||
<li class="d-flex justify-content-between " data-id="${scenario.id}">
|
||||
<a href="#" class="d-inline-flex align-items-center rounded open_scenario">${scenario.name}</a>
|
||||
<div class="d-flex justify-content-between">
|
||||
@@ -371,19 +370,20 @@ class APIScenario {
|
||||
</div>
|
||||
</li>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
renderScenarioList() {
|
||||
let scenarioList = this.getScenarioList();
|
||||
let scenarioListHtml = scenarioList.map((scenario) => this.scenarioListTemplate(scenario)).join('');
|
||||
$('.api-scenario-list').html(scenarioListHtml);
|
||||
let nodes = document.querySelectorAll(".api-scenario-list li");
|
||||
nodes.forEach((node) => {
|
||||
if ($(node).data('id') === this.currentScenario) {
|
||||
node.classList.add('selected');
|
||||
}
|
||||
});
|
||||
}
|
||||
renderScenarioList() {
|
||||
let scenarioList = this.getScenarioList();
|
||||
let scenarioListHtml = scenarioList.map((scenario) => this.scenarioListTemplate(scenario)).join('');
|
||||
$('.api-scenario-list').html(scenarioListHtml);
|
||||
|
||||
let nodes = document.querySelectorAll('.api-scenario-list li');
|
||||
nodes.forEach((node) => {
|
||||
if ($(node).data('id') === this.currentScenario) {
|
||||
node.classList.add('selected');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default APIScenario;
|
||||
|
||||
@@ -2,232 +2,227 @@ import * as monaco from 'monaco-editor';
|
||||
import APIClient from './APIClient';
|
||||
|
||||
class APIScenarioExecutor {
|
||||
constructor(graph, apiTester, outputEditor) {
|
||||
this.graph = graph; // The graph data
|
||||
this.apiClient = new APIClient();
|
||||
this.apiTester = apiTester;
|
||||
this.outputEditor = outputEditor;
|
||||
this.reset();
|
||||
constructor(graph, apiTester, outputEditor) {
|
||||
this.graph = graph; // The graph data
|
||||
this.apiClient = new APIClient();
|
||||
this.apiTester = apiTester;
|
||||
this.outputEditor = outputEditor;
|
||||
this.reset();
|
||||
}
|
||||
|
||||
// Helper method to find the start node ID
|
||||
findStartNodeId() {
|
||||
for (let nodeId in this.graph) {
|
||||
if (this.graph[nodeId].class === 'start') {
|
||||
return nodeId;
|
||||
}
|
||||
}
|
||||
return null; // Return null if no start node is found
|
||||
}
|
||||
|
||||
// Helper method to find the start node ID
|
||||
findStartNodeId() {
|
||||
for (let nodeId in this.graph) {
|
||||
if (this.graph[nodeId].class === 'start') {
|
||||
return nodeId;
|
||||
}
|
||||
}
|
||||
return null; // Return null if no start node is found
|
||||
}
|
||||
reset() {
|
||||
this.currentNodeId = this.findStartNodeId(); // Initialize at the 'start' node
|
||||
this.visitedNodes = new Set(); // Keep track of visited nodes
|
||||
this.isRunning = false;
|
||||
this.isDone = false;
|
||||
|
||||
reset() {
|
||||
this.currentNodeId = this.findStartNodeId(); // Initialize at the 'start' node
|
||||
this.visitedNodes = new Set(); // Keep track of visited nodes
|
||||
this.isRunning = false;
|
||||
this.isDone = false;
|
||||
let allNodes = document.querySelector('#drawflow').querySelectorAll('.drawflow-node');
|
||||
allNodes.forEach(node => {
|
||||
node.classList.remove('current-execution');
|
||||
node.classList.remove('result-success');
|
||||
node.classList.remove('result-error');
|
||||
node.classList.remove('result-abort');
|
||||
});
|
||||
}
|
||||
|
||||
let allNodes = document.querySelector('#drawflow').querySelectorAll('.drawflow-node');
|
||||
allNodes.forEach(node => {
|
||||
node.classList.remove('current-execution');
|
||||
node.classList.remove('result-success');
|
||||
node.classList.remove('result-error');
|
||||
node.classList.remove('result-abort');
|
||||
});
|
||||
}
|
||||
executeCurrentNode() {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log(this.currentNodeId);
|
||||
|
||||
executeCurrentNode() {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log(this.currentNodeId)
|
||||
let allNodes = document.querySelector('#drawflow').querySelectorAll('.drawflow-node');
|
||||
allNodes.forEach(node => {
|
||||
node.classList.remove('current-execution');
|
||||
});
|
||||
|
||||
let allNodes = document.querySelector('#drawflow').querySelectorAll('.drawflow-node');
|
||||
allNodes.forEach(node => {
|
||||
node.classList.remove('current-execution');
|
||||
});
|
||||
if (!this.currentNodeId) {
|
||||
console.log('No current node to execute.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.currentNodeId) {
|
||||
console.log('No current node to execute.');
|
||||
return;
|
||||
}
|
||||
const currentNode = this.graph[this.currentNodeId];
|
||||
|
||||
const currentNode = this.graph[this.currentNodeId];
|
||||
if (currentNode) {
|
||||
this.isRunning = true;
|
||||
this.visitedNodes.add(this.currentNodeId); // Mark this node as visited
|
||||
|
||||
if (currentNode) {
|
||||
this.isRunning = true;
|
||||
this.visitedNodes.add(this.currentNodeId); // Mark this node as visited
|
||||
|
||||
let nodeElement = document.querySelector('#drawflow').querySelector(`.drawflow-node[id="node-${this.currentNodeId}"]`);
|
||||
if (nodeElement) {
|
||||
nodeElement.classList.add('current-execution');
|
||||
} else {
|
||||
console.error('Node with ID ' + this.currentNodeId + ' not found');
|
||||
}
|
||||
|
||||
if (currentNode.class === 'api') {
|
||||
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 => {
|
||||
this.isRunning = false;
|
||||
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');
|
||||
$('.toast-body').text(error.message);
|
||||
$('.toast').toast('show');
|
||||
this.appendLog(error.message);
|
||||
reject(error);
|
||||
});
|
||||
console.log(`===============================================================\nExecuting API request for node: ${currentNode.id} - ${currentNode.name}`);
|
||||
} else if (currentNode.class === 'start') {
|
||||
this.appendLog('Starting API scenario\n');
|
||||
nodeElement.classList.add('result-success');
|
||||
resolve();
|
||||
} else {
|
||||
console.log(`Executing script for node: ${currentNode.id} - ${currentNode.name}`);
|
||||
resolve();
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
appendLog(text) {
|
||||
var range = new monaco.Range(this.outputEditor.getModel().getLineCount(), 1, this.outputEditor.getModel().getLineCount(), 1);
|
||||
var id = {major: 1, minor: 1};
|
||||
var op = {identifier: id, range: range, text: text + '\n\n', forceMoveMarkers: true};
|
||||
this.outputEditor.executeEdits("my-source", [op]);
|
||||
}
|
||||
|
||||
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}`;
|
||||
this.appendLog(log);
|
||||
}
|
||||
|
||||
logHttpResponse(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);
|
||||
}
|
||||
|
||||
parseRequestHeaders(headers) {
|
||||
let parsedHeaders = '';
|
||||
headers.forEach(header => {
|
||||
if (header.enabled) {
|
||||
parsedHeaders += `${header.key}: ${header.value}\n`;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return parsedHeaders;
|
||||
}
|
||||
|
||||
parseHeaders(headers) {
|
||||
//convert object to string with \n separation
|
||||
//sort header by key
|
||||
|
||||
let parsedHeaders = '';
|
||||
const sortedKeys = Object.keys(headers).sort(); // Sorting the keys alphabetically
|
||||
sortedKeys.forEach(key => {
|
||||
parsedHeaders += `${key}: ${headers[key]}\n`;
|
||||
});
|
||||
return parsedHeaders;
|
||||
}
|
||||
|
||||
|
||||
// Method to move to the next node
|
||||
moveToNextNode() {
|
||||
const currentNode = this.graph[this.currentNodeId];
|
||||
console.log(currentNode);
|
||||
|
||||
if (currentNode && currentNode.connections.length > 0) {
|
||||
// Find the next unvisited node
|
||||
const nextNode = currentNode.connections.find(nodeId => !this.visitedNodes.has(nodeId));
|
||||
if (nextNode) {
|
||||
this.currentNodeId = nextNode;
|
||||
} else {
|
||||
console.log('No more unvisited connections to move to.');
|
||||
this.currentNodeId = null; // No more nodes to visit
|
||||
}
|
||||
let nodeElement = document.querySelector('#drawflow').querySelector(`.drawflow-node[id="node-${this.currentNodeId}"]`);
|
||||
if (nodeElement) {
|
||||
nodeElement.classList.add('current-execution');
|
||||
} else {
|
||||
console.log('Current node has no connections or does not exist.');
|
||||
this.currentNodeId = null; // No more nodes to visit
|
||||
this.isDone = true;
|
||||
console.error('Node with ID ' + this.currentNodeId + ' not found');
|
||||
}
|
||||
}
|
||||
|
||||
async executeAll() {
|
||||
while (this.currentNodeId) {
|
||||
console.log(this.currentNodeId);
|
||||
try {
|
||||
await this.executeCurrentNode();
|
||||
} catch (error) {
|
||||
console.error('Error executing node: ', error);
|
||||
// Handle error or break loop if necessary
|
||||
if (currentNode.class === 'api') {
|
||||
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 => {
|
||||
this.isRunning = false;
|
||||
this.logHttpResponse(response);
|
||||
if (response.status === 200 || response.status === 201) {
|
||||
nodeElement.classList.add('result-success');
|
||||
} else {
|
||||
nodeElement.classList.add('result-error');
|
||||
}
|
||||
await this.moveToNextNodeWithDelay(250);
|
||||
resolve();
|
||||
}).catch(error => {
|
||||
nodeElement.classList.add('result-abort');
|
||||
$('.toast-body').text(error.message);
|
||||
$('.toast').toast('show');
|
||||
this.appendLog(error.message);
|
||||
reject(error);
|
||||
});
|
||||
console.log(`===============================================================\nExecuting API request for node: ${currentNode.id} - ${currentNode.name}`);
|
||||
} else if (currentNode.class === 'start') {
|
||||
this.appendLog('Starting API scenario\n');
|
||||
nodeElement.classList.add('result-success');
|
||||
resolve();
|
||||
} else {
|
||||
console.log(`Executing script for node: ${currentNode.id} - ${currentNode.name}`);
|
||||
resolve();
|
||||
}
|
||||
console.log('Completed execution of all API nodes.');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
moveToNextNodeWithDelay(delay) {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
this.moveToNextNode();
|
||||
resolve();
|
||||
}, delay);
|
||||
}
|
||||
|
||||
appendLog(text) {
|
||||
var range = new monaco.Range(this.outputEditor.getModel().getLineCount(), 1, this.outputEditor.getModel().getLineCount(), 1);
|
||||
var id = {major: 1, minor: 1};
|
||||
var op = {identifier: id, range: range, text: text + '\n\n', forceMoveMarkers: true};
|
||||
this.outputEditor.executeEdits('my-source', [op]);
|
||||
}
|
||||
|
||||
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}`;
|
||||
this.appendLog(log);
|
||||
}
|
||||
|
||||
logHttpResponse(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);
|
||||
}
|
||||
|
||||
parseRequestHeaders(headers) {
|
||||
let parsedHeaders = '';
|
||||
headers.forEach(header => {
|
||||
if (header.enabled) {
|
||||
parsedHeaders += `${header.key}: ${header.value}\n`;
|
||||
}
|
||||
});
|
||||
|
||||
return parsedHeaders;
|
||||
}
|
||||
|
||||
parseHeaders(headers) {
|
||||
//convert object to string with \n separation
|
||||
//sort header by key
|
||||
|
||||
let parsedHeaders = '';
|
||||
const sortedKeys = Object.keys(headers).sort(); // Sorting the keys alphabetically
|
||||
sortedKeys.forEach(key => {
|
||||
parsedHeaders += `${key}: ${headers[key]}\n`;
|
||||
});
|
||||
return parsedHeaders;
|
||||
}
|
||||
|
||||
// Method to move to the next node
|
||||
moveToNextNode() {
|
||||
const currentNode = this.graph[this.currentNodeId];
|
||||
console.log(currentNode);
|
||||
|
||||
if (currentNode && currentNode.connections.length > 0) {
|
||||
// Find the next unvisited node
|
||||
const nextNode = currentNode.connections.find(nodeId => !this.visitedNodes.has(nodeId));
|
||||
if (nextNode) {
|
||||
this.currentNodeId = nextNode;
|
||||
} else {
|
||||
console.log('No more unvisited connections to move to.');
|
||||
this.currentNodeId = null; // No more nodes to visit
|
||||
}
|
||||
} else {
|
||||
console.log('Current node has no connections or does not exist.');
|
||||
this.currentNodeId = null; // No more nodes to visit
|
||||
this.isDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
async executeAll() {
|
||||
while (this.currentNodeId) {
|
||||
console.log(this.currentNodeId);
|
||||
try {
|
||||
await this.executeCurrentNode();
|
||||
} catch (error) {
|
||||
console.error('Error executing node: ', error);
|
||||
// Handle error or break loop if necessary
|
||||
}
|
||||
await this.moveToNextNodeWithDelay(250);
|
||||
}
|
||||
console.log('Completed execution of all API nodes.');
|
||||
}
|
||||
|
||||
moveToNextNodeWithDelay(delay) {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
this.moveToNextNode();
|
||||
resolve();
|
||||
}, delay);
|
||||
});
|
||||
}
|
||||
|
||||
static extractGraphFromStart(graphData) {
|
||||
console.log(graphData);
|
||||
let graph = {};
|
||||
|
||||
// Helper function to recursively build the graph
|
||||
function buildGraph(nodeId) {
|
||||
const node = graphData[nodeId];
|
||||
if (!node || graph[nodeId]) {
|
||||
// If the node doesn't exist or has already been visited, stop the recursion
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize the node in the graph
|
||||
graph[nodeId] = {
|
||||
id: node.id,
|
||||
name: node.name,
|
||||
class: node.class,
|
||||
data: node.data,
|
||||
connections: []
|
||||
};
|
||||
|
||||
// If the node has outputs, recursively add connected nodes
|
||||
if (node.outputs) {
|
||||
Object.values(node.outputs).forEach(output => {
|
||||
output.connections.forEach(connection => {
|
||||
graph[nodeId].connections.push(connection.node);
|
||||
buildGraph(connection.node); // Recurse
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static extractGraphFromStart(graphData) {
|
||||
console.log(graphData)
|
||||
let graph = {};
|
||||
|
||||
// Helper function to recursively build the graph
|
||||
function buildGraph(nodeId) {
|
||||
const node = graphData[nodeId];
|
||||
if (!node || graph[nodeId]) {
|
||||
// If the node doesn't exist or has already been visited, stop the recursion
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize the node in the graph
|
||||
graph[nodeId] = {
|
||||
id: node.id,
|
||||
name: node.name,
|
||||
class: node.class,
|
||||
data: node.data,
|
||||
connections: []
|
||||
};
|
||||
|
||||
// If the node has outputs, recursively add connected nodes
|
||||
if (node.outputs) {
|
||||
Object.values(node.outputs).forEach(output => {
|
||||
output.connections.forEach(connection => {
|
||||
graph[nodeId].connections.push(connection.node);
|
||||
buildGraph(connection.node); // Recurse
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Find the start node
|
||||
for (let nodeId in graphData) {
|
||||
if (graphData[nodeId].class === 'start') {
|
||||
buildGraph(nodeId); // Start building the graph from the 'start' node
|
||||
break; // Assuming there's only one start node
|
||||
}
|
||||
}
|
||||
|
||||
return graph;
|
||||
// Find the start node
|
||||
for (let nodeId in graphData) {
|
||||
if (graphData[nodeId].class === 'start') {
|
||||
buildGraph(nodeId); // Start building the graph from the 'start' node
|
||||
break; // Assuming there's only one start node
|
||||
}
|
||||
}
|
||||
|
||||
return graph;
|
||||
}
|
||||
}
|
||||
|
||||
export default APIScenarioExecutor;
|
||||
|
||||
+1065
-1061
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,261 @@
|
||||
import LoadTester from './components/LoadTester';
|
||||
import LoadTestResult from './components/LoadTestResult';
|
||||
|
||||
class LoadTestManagerModel {
|
||||
constructor() {
|
||||
this.testList = [];
|
||||
this.scenarios = [];
|
||||
}
|
||||
|
||||
getTestList() {
|
||||
return this.testList;
|
||||
}
|
||||
|
||||
getScenarioList() {
|
||||
return this.scenarios;
|
||||
}
|
||||
}
|
||||
|
||||
class LoadTestManagerView {
|
||||
constructor(model, controller) {
|
||||
this.model = model;
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
render(element) {
|
||||
this.element = element;
|
||||
this.element.innerHTML = `<div></div>`;
|
||||
}
|
||||
|
||||
testListTemplate(test) {
|
||||
return `
|
||||
<li class="d-flex justify-content-between " data-id="${test.id}">
|
||||
<a href="#" class="d-inline-flex align-items-center rounded open_load_test">${test.name}</a>
|
||||
<div class="d-flex justify-content-between">
|
||||
<button class="p-1 reset-button delete_load_test"><i class="fa fa-trash-can"></i></button>
|
||||
</div>
|
||||
</li>
|
||||
`;
|
||||
}
|
||||
|
||||
renderTestList() {
|
||||
const testList = this.model.getTestList();
|
||||
let testListHtml = testList.map((test) => this.testListTemplate(test)).join('');
|
||||
$('.load_test_list').html(testListHtml);
|
||||
let nodes = document.querySelectorAll('.load_test_list li');
|
||||
nodes.forEach((node) => {
|
||||
if (this.controller.currentTest !== null && $(node).data('id') === this.controller.currentTest.id) {
|
||||
node.classList.add('selected');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class LoadTestManagerController {
|
||||
constructor(contextPath, model, testResult) {
|
||||
this.contextPath = contextPath;
|
||||
this.model = model;
|
||||
this.view = new LoadTestManagerView(this.model, this);
|
||||
this.testResult = testResult;
|
||||
}
|
||||
|
||||
async init(element) {
|
||||
console.log('LoadTestManagerController.init() called');
|
||||
await this.loadScenarioList();
|
||||
this.currentTest = new LoadTester(this.contextPath, this.model.scenarios);
|
||||
this.currentTest.init(document.getElementById('load-tester-view'));
|
||||
this.view.render(element);
|
||||
this.bindEvents();
|
||||
await this.loadTestList();
|
||||
}
|
||||
|
||||
showLoadingOverlay() {
|
||||
$('#loading-overlay').show();
|
||||
}
|
||||
|
||||
hideLoadingOverlay() {
|
||||
$('#loading-overlay').hide();
|
||||
}
|
||||
|
||||
getLoadTestListURL() {
|
||||
return this.contextPath + 'mgmt/load_test/list.do';
|
||||
}
|
||||
|
||||
getLoadTestCreateURL() {
|
||||
return this.contextPath + 'mgmt/load_test/create.do';
|
||||
}
|
||||
|
||||
getLoadTestUpdateURL() {
|
||||
return this.contextPath + 'mgmt/load_test/update.do';
|
||||
}
|
||||
|
||||
getLoadTestDeleteURL() {
|
||||
return this.contextPath + 'mgmt/load_test/delete.do';
|
||||
}
|
||||
|
||||
openTest(event) {
|
||||
console.log('openTest');
|
||||
let testId = $(event.target).closest('li').data('id');
|
||||
document.querySelectorAll('.load_test_list li').forEach((node) => {
|
||||
node.classList.remove('selected');
|
||||
});
|
||||
document.querySelector(`.load_test_list li[data-id="${testId}"]`).classList.add('selected');
|
||||
|
||||
let currentTestModel = this.model.getTestList().find((test) => test.id === testId);
|
||||
this.currentTest.openLoadTest(currentTestModel);
|
||||
this.testResult.showResult(testId);
|
||||
}
|
||||
|
||||
showNewTestModal() {
|
||||
$('#new_load_test_modal').find('input').val('');
|
||||
$('#new_load_test_modal').modal('show');
|
||||
}
|
||||
|
||||
showDeleteModal(event) {
|
||||
const id = $(event.currentTarget).closest('li').data('id');
|
||||
$('#confirm_delete_modal').find('.confirm_delete_load_test').data('id', id);
|
||||
$('#confirm_delete_modal').modal('show');
|
||||
}
|
||||
|
||||
async loadTestList(callback) {
|
||||
this.showLoadingOverlay();
|
||||
const response = await fetch(this.getLoadTestListURL(), {
|
||||
method: 'GET', headers: {
|
||||
'Content-Type': 'application/json', 'X-XSRF-TOKEN': $('meta[name="_csrf"]').attr('content')
|
||||
}
|
||||
});
|
||||
this.model.testList = await response.json();
|
||||
this.view.renderTestList();
|
||||
this.hideLoadingOverlay();
|
||||
}
|
||||
|
||||
async loadScenarioList() {
|
||||
const response = await fetch(this.contextPath + 'mgmt/api_scenario/list.do', {
|
||||
method: 'GET', headers: {
|
||||
'Content-Type': 'application/json', 'X-XSRF-TOKEN': $('meta[name="_csrf"]').attr('content')
|
||||
}
|
||||
});
|
||||
this.model.scenarios = await response.json();
|
||||
}
|
||||
|
||||
createLoadTest() {
|
||||
const testName = $('#new_load_test_modal').find('input').val();
|
||||
if (testName) {
|
||||
const me = this;
|
||||
me.showLoadingOverlay();
|
||||
this.currentAjaxRequest = $.ajax({
|
||||
url: this.getLoadTestCreateURL(),
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({name: testName}),
|
||||
success: function(data) {
|
||||
me.loadTestList(() => {
|
||||
});
|
||||
},
|
||||
error: function(response, textStatus, errorThrown) {
|
||||
me.hideLoadingOverlay();
|
||||
$('.toast-body').text(response.responseJSON.error);
|
||||
$('.toast').toast('show');
|
||||
},
|
||||
complete: function() {
|
||||
me.hideLoadingOverlay();
|
||||
$('#new_load_test_modal').modal('hide');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
saveLoadTest() {
|
||||
if (this.currentTest !== null) {
|
||||
|
||||
const me = this;
|
||||
me.showLoadingOverlay();
|
||||
this.currentAjaxRequest = $.ajax({
|
||||
url: this.getLoadTestUpdateURL(),
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(this.currentTest.getLoadTest()),
|
||||
error: function(response, textStatus, errorThrown) {
|
||||
me.hideLoadingOverlay();
|
||||
$('.toast-body').text(response.responseJSON.error);
|
||||
$('.toast').toast('show');
|
||||
},
|
||||
complete: function() {
|
||||
me.hideLoadingOverlay();
|
||||
$('.toast-body').text('저장되었습니다.');
|
||||
$('.toast').toast('show');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
removeLoadTest(event) {
|
||||
const id = $(event.currentTarget).data('id');
|
||||
const me = this;
|
||||
me.showLoadingOverlay();
|
||||
this.currentAjaxRequest = $.ajax({
|
||||
url: this.getLoadTestDeleteURL(),
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({id: id}),
|
||||
error: function(response, textStatus, errorThrown) {
|
||||
me.hideLoadingOverlay();
|
||||
$('.toast-body').text(response.responseJSON.error);
|
||||
$('.toast').toast('show');
|
||||
},
|
||||
complete: function() {
|
||||
me.hideLoadingOverlay();
|
||||
$('#confirm_delete_modal').modal('hide');
|
||||
$('.toast-body').text('삭제되었습니다.');
|
||||
$('.toast').toast('show');
|
||||
me.loadTestList(() => {
|
||||
if (me.model.getTestList().length > 0) {
|
||||
//me.openT(me.getScenarioList()[0].id);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
resetLoadTest() {
|
||||
this.testResult.reset();
|
||||
}
|
||||
|
||||
showTestReport() {
|
||||
this.testResult.showTestRpeort();
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
const events = [
|
||||
{type: 'click', selector: '#new_load_test', action: this.showNewTestModal.bind(this)},
|
||||
{type: 'click', selector: '.create_load_test', action: this.createLoadTest.bind(this)},
|
||||
{type: 'click', selector: '.save_load_test', action: this.saveLoadTest.bind(this)},
|
||||
{type: 'click', selector: '.reset_load_test', action: this.resetLoadTest.bind(this)},
|
||||
{type: 'click', selector: '.show_test_report', action: this.showTestReport.bind(this)},
|
||||
{type: 'click', selector: '.delete_load_test', action: this.showDeleteModal.bind(this)},
|
||||
{type: 'click', selector: '.open_load_test', action: this.openTest.bind(this)},
|
||||
{type: 'click', selector: '.confirm_delete_load_test', action: this.removeLoadTest.bind(this)}];
|
||||
|
||||
for (let event of events) {
|
||||
$(document).on(event.type, event.selector, event.action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LoadTestManager {
|
||||
constructor(contextPath) {
|
||||
this.contextPath = contextPath;
|
||||
this.model = new LoadTestManagerModel();
|
||||
this.testResult = new LoadTestResult(this.contextPath, document.getElementById('load-tester-result-view'));
|
||||
this.testResult.init();
|
||||
this.controller = new LoadTestManagerController(this.contextPath, this.model, this.testResult);
|
||||
}
|
||||
|
||||
init() {
|
||||
console.log('LoadTestManager.init() called');
|
||||
let element = document.getElementById('load_test_menubar');
|
||||
this.controller.init(element);
|
||||
}
|
||||
}
|
||||
|
||||
export default LoadTestManager;
|
||||
@@ -16,9 +16,9 @@ class EditableInputModel {
|
||||
}
|
||||
|
||||
class EditableInputView {
|
||||
constructor(model, editableInput) {
|
||||
constructor(model, controller) {
|
||||
this.model = model;
|
||||
this.editableInput = editableInput;
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
render(element) {
|
||||
@@ -39,9 +39,9 @@ class EditableInputView {
|
||||
|
||||
attachEventListeners() {
|
||||
this.editableDiv.addEventListener('input', (event) => {
|
||||
this.editableInput.controller.handleInput(this.editableDiv);
|
||||
if (this.editableInput.controller.additionalCallback) {
|
||||
this.editableInput.controller.additionalCallback(this.model.getValue());
|
||||
this.controller.handleInput(this.editableDiv);
|
||||
if (this.controller.additionalCallback) {
|
||||
this.controller.additionalCallback(this.model.getValue());
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -99,9 +99,9 @@ class EditableInputView {
|
||||
|
||||
|
||||
class EditableInputController {
|
||||
constructor(model, view) {
|
||||
constructor(model) {
|
||||
this.model = model;
|
||||
this.view = view;
|
||||
this.view = new EditableInputView(this.model, this);
|
||||
}
|
||||
|
||||
init(element, callback) {
|
||||
@@ -208,16 +208,17 @@ class EditableInputController {
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
}
|
||||
|
||||
updateUI() {
|
||||
this.view.updateUI();
|
||||
}
|
||||
}
|
||||
|
||||
class EditableInput {
|
||||
constructor(options = {}) {
|
||||
// Initialize the model with the value
|
||||
this.model = new EditableInputModel(options.value || '');
|
||||
|
||||
// Create the view and controller, passing the model to them
|
||||
this.view = new EditableInputView(this.model, this);
|
||||
this.controller = new EditableInputController(this.model, this.view);
|
||||
this.controller = new EditableInputController(this.model);
|
||||
|
||||
// Additional properties as required
|
||||
this.name = options.name || '';
|
||||
@@ -239,7 +240,7 @@ class EditableInput {
|
||||
setValue(newValue) {
|
||||
// Delegate to the model and update the view
|
||||
this.model.setValue(newValue);
|
||||
this.view.updateUI();
|
||||
this.controller.updateUI();
|
||||
}
|
||||
|
||||
// Any additional methods required for interaction with this component
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import {Stomp} from '@stomp/stompjs';
|
||||
import SockJS from 'sockjs-client';
|
||||
import LoadTestResultList from './LoadTestResultList';
|
||||
import LoadTestResultDetail from './LoadTestResultDetail';
|
||||
|
||||
class LoadTestResultModel {
|
||||
constructor(testId) {
|
||||
this.testId = testId;
|
||||
}
|
||||
}
|
||||
|
||||
class LoadTestResultView {
|
||||
constructor(model, element, controller) {
|
||||
this.element = element;
|
||||
this.model = model;
|
||||
this.controller = controller;
|
||||
|
||||
this.testResultListElement = document.createElement('div');
|
||||
this.testResultListElement.id = 'loadTestResultList';
|
||||
this.testResultListElement.style.width = '250px';
|
||||
this.testResultListElement.style.border = '1px solid #e0e0e0';
|
||||
this.testResultListElement.style.padding = '5px';
|
||||
this.testResultListElement.style.overflow = 'hidden';
|
||||
this.testResultListElement.style.height = `${window.innerHeight - 160}px`;
|
||||
this.testResultListElement.style.maxHeight = '100%';
|
||||
this.testResultListElement.classList.add('load-test-result-container');
|
||||
this.element.appendChild(this.testResultListElement);
|
||||
|
||||
this.testResultDetailElement = document.createElement('div');
|
||||
this.testResultDetailElement.id = 'loadTestResultDetail';
|
||||
this.testResultDetailElement.style.width = 'calc(100% - 250px)';
|
||||
this.testResultDetailElement.style.border = '1px solid #e0e0e0';
|
||||
this.testResultDetailElement.classList.add('load-test-result-detail');
|
||||
this.testResultDetailElement.style.display = 'flex';
|
||||
this.testResultDetailElement.style.height = `${window.innerHeight - 160}px`;
|
||||
this.testResultDetailElement.style.maxHeight = '100%'; //it seems not working
|
||||
this.element.appendChild(this.testResultDetailElement);
|
||||
}
|
||||
|
||||
resize() {
|
||||
this.testResultListElement.style.height = `${window.innerHeight - 160}px`;
|
||||
this.testResultDetailElement.style.height = `${window.innerHeight - 160}px`;
|
||||
}
|
||||
}
|
||||
|
||||
class LoadTestResultController {
|
||||
constructor(contextPath, element, model) {
|
||||
this.contextPath = contextPath || '/';
|
||||
this.model = model;
|
||||
this.view = new LoadTestResultView(this.model, element, this);
|
||||
this.subscription = null;
|
||||
this.loadTestResultList = null;
|
||||
}
|
||||
|
||||
init() {
|
||||
console.log('LoadTestResultController init');
|
||||
//initialize websocket
|
||||
this.socket = new SockJS(this.contextPath + 'load-test-result');
|
||||
this.client = Stomp.over(this.socket);
|
||||
this.client.connect({}, () => {
|
||||
console.log('connected');
|
||||
});
|
||||
this.client.activate();
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
window.addEventListener('resize', this.resize.bind(this));
|
||||
}
|
||||
|
||||
resize() {
|
||||
this.view.resize();
|
||||
}
|
||||
|
||||
showResult(testId) {
|
||||
if (this.subscription) {
|
||||
this.subscription.unsubscribe();
|
||||
this.subscription = null; // Clear the subscription after unsubscribing
|
||||
}
|
||||
this.model.testId = testId;
|
||||
this.subscription = this.client.subscribe(`/topic/${this.model.testId}`, (message) => {
|
||||
this.loadTestResultList.addTestResult(JSON.parse(message.body));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class LoadTestResult {
|
||||
constructor(contextPath, element) {
|
||||
this.contextPath = contextPath || '/';
|
||||
this.element = element;
|
||||
}
|
||||
|
||||
init() {
|
||||
this.model = new LoadTestResultModel();
|
||||
this.controller = new LoadTestResultController(this.contextPath, this.element, this.model);
|
||||
this.controller.init();
|
||||
this.view = this.controller.view;
|
||||
this.loadTestResultDetail = new LoadTestResultDetail(this.contextPath, this.view.testResultDetailElement);
|
||||
this.loadTestResultList = new LoadTestResultList(this.contextPath, this.view.testResultListElement, this.loadTestResultDetail);
|
||||
this.controller.loadTestResultList = this.loadTestResultList;
|
||||
}
|
||||
|
||||
showResult(testId) {
|
||||
this.controller.showResult(testId);
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.loadTestResultList.reset();
|
||||
this.loadTestResultDetail.reset();
|
||||
}
|
||||
|
||||
showTestReport() {
|
||||
//show test report modal
|
||||
}
|
||||
}
|
||||
|
||||
export default LoadTestResult;
|
||||
@@ -0,0 +1,117 @@
|
||||
import * as monaco from 'monaco-editor';
|
||||
|
||||
class LoadTestResultDetailModel {
|
||||
constructor() {
|
||||
this.testResult = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class LoadTestResultDetailView {
|
||||
constructor(element, model, controller) {
|
||||
this.element = element;
|
||||
this.editorContainer = document.createElement('div');
|
||||
this.editorContainer.style.flex = '1';
|
||||
this.element.appendChild(this.editorContainer);
|
||||
|
||||
this.model = model;
|
||||
this.controller = controller;
|
||||
this.outputEditor = monaco.editor.create(this.editorContainer, {
|
||||
value: '',
|
||||
automaticLayout: true,
|
||||
quickSuggestions: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class LoadTestResultDetailController {
|
||||
constructor(element, model) {
|
||||
this.element = element;
|
||||
this.model = model;
|
||||
this.view = new LoadTestResultDetailView(element, model, this);
|
||||
}
|
||||
|
||||
showDetail(result) {
|
||||
this.view.outputEditor.setValue('');
|
||||
this.logHttpRequest(result.request);
|
||||
this.logHttpResponse(result.response);
|
||||
}
|
||||
|
||||
reset(){
|
||||
this.view.outputEditor.setValue('');
|
||||
}
|
||||
|
||||
appendLog(text) {
|
||||
var range = new monaco.Range(this.view.outputEditor.getModel().getLineCount(), 1, this.view.outputEditor.getModel().getLineCount(), 1);
|
||||
var id = {major: 1, minor: 1};
|
||||
var op = {identifier: id, range: range, text: text + '\n\n', forceMoveMarkers: true};
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
parseRequestHeaders(headers) {
|
||||
let parsedHeaders = '';
|
||||
headers.forEach(header => {
|
||||
if (header.enabled) {
|
||||
parsedHeaders += `${header.key}: ${header.value}\n`;
|
||||
}
|
||||
});
|
||||
|
||||
return parsedHeaders;
|
||||
}
|
||||
|
||||
parseHeaders(headers) {
|
||||
let parsedHeaders = '';
|
||||
headers.forEach(header => {
|
||||
parsedHeaders += `${header.key}: ${header.value}\n`;
|
||||
});
|
||||
return parsedHeaders;
|
||||
}
|
||||
|
||||
resizeEditor() {
|
||||
//FIXME: 창 크기 변경시 자동으로 editor 크기가 조정되지 않아서, 0으로 강제 변경후 다시 layout() 호출
|
||||
this.view.outputEditor.layout({
|
||||
width: 0
|
||||
});
|
||||
this.view.outputEditor.layout({});
|
||||
|
||||
}
|
||||
|
||||
init() {
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
window.addEventListener('resize', this.resizeEditor.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
class LoadTestResultDetail {
|
||||
constructor(contextPath, element) {
|
||||
this.contextPath = contextPath || '/';
|
||||
this.model = new LoadTestResultDetailModel();
|
||||
this.controller = new LoadTestResultDetailController(element, this.model);
|
||||
this.controller.init();
|
||||
}
|
||||
|
||||
showDetail(result) {
|
||||
this.controller.showDetail(result);
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.controller.reset();
|
||||
}
|
||||
}
|
||||
|
||||
export default LoadTestResultDetail;
|
||||
@@ -0,0 +1,102 @@
|
||||
class LoadTestResultListModel {
|
||||
constructor() {
|
||||
this.testResults = [];
|
||||
}
|
||||
}
|
||||
|
||||
class LoadTestResultListView {
|
||||
constructor(element, model, controller) {
|
||||
this.element = element;
|
||||
const ul = document.createElement('ul');
|
||||
ul.style.overflowY = 'scroll';
|
||||
ul.style.padding = '0';
|
||||
ul.style.height = 'inherit';
|
||||
ul.style.maxHeight = '100%';
|
||||
this.element.appendChild(ul);
|
||||
this.model = model;
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
addTestResult(result) {
|
||||
const ul = this.element.querySelector('ul');
|
||||
const li = document.createElement('li');
|
||||
li.style.border = '1px solid #e0e0e0';
|
||||
li.style.marginBottom = '5px';
|
||||
li.style.padding = '4px';
|
||||
li.textContent = result.request.name; // Assuming `result` is a string or has a `.toString()` method that returns a meaningful representation
|
||||
|
||||
li.addEventListener('click', () =>{
|
||||
const allLi = ul.querySelectorAll('li');
|
||||
allLi.forEach(li => {
|
||||
li.classList.remove('selected');
|
||||
});
|
||||
|
||||
// Add "selected" class to the clicked li
|
||||
li.classList.add('selected');
|
||||
this.controller.showDetail(result);
|
||||
} );
|
||||
|
||||
ul.appendChild(li);
|
||||
}
|
||||
}
|
||||
|
||||
class LoadTestResultListController {
|
||||
constructor(model, element, detail) {
|
||||
this.model = model;
|
||||
this.view = new LoadTestResultListView(element, model, this);
|
||||
this.detail = detail;
|
||||
}
|
||||
|
||||
init() {
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
showDetail(result) {
|
||||
this.detail.showDetail(result);
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
const events = [
|
||||
// {type: 'click'}
|
||||
];
|
||||
|
||||
for (let event of events) {
|
||||
$(document).off(event.type, event.selector).on(event.type, event.selector, event.action);
|
||||
}
|
||||
}
|
||||
|
||||
addTestResult(result) {
|
||||
this.model.testResults.push(result);
|
||||
this.view.addTestResult(result);
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.model.testResults = [];
|
||||
this.view.element.querySelector('ul').innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
class LoadTestResultList {
|
||||
|
||||
constructor(contextPath, element, detail) {
|
||||
this.contextPath = contextPath || '/';
|
||||
this.model = new LoadTestResultListModel();
|
||||
this.detail = detail;
|
||||
this.controller = new LoadTestResultListController(this.model, element, detail);
|
||||
}
|
||||
|
||||
init() {
|
||||
this.controller.init();
|
||||
}
|
||||
|
||||
addTestResult(result) {
|
||||
this.controller.addTestResult(result);
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.controller.reset();
|
||||
}
|
||||
}
|
||||
|
||||
export default LoadTestResultList;
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
class LoadTestModel {
|
||||
constructor(loadTest) {
|
||||
this.scenarios = [];
|
||||
|
||||
if (loadTest) {
|
||||
this.loadTest = loadTest;
|
||||
} else {
|
||||
this.loadTest = {
|
||||
scenarioId: null, threadCount: 1, loopCount: 0, rampUpTime: 0, duration: 0, testData: null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
getScenarioList() {
|
||||
return this.scenarios;
|
||||
}
|
||||
}
|
||||
|
||||
class LoadTestView {
|
||||
constructor(model, controller) {
|
||||
this.model = model;
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
renderScenarioList() {
|
||||
const scenarioList = this.model.getScenarioList();
|
||||
return `<div class="form-floating me-2" style="width: 250px; height: 60px;">
|
||||
<select class="form-select" name="scenarioId">
|
||||
<option value="" selected>시나리오 선택</option>
|
||||
${scenarioList.map(s => `<option value="${s.id}" ${s.id === this.model.loadTest.scenarioId ? 'selected' : ''}>${s.name}</option>`).join('')}
|
||||
</select>
|
||||
<label for="scenario_list">시나리오 선택</label>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
render(element) {
|
||||
this.element = element;
|
||||
|
||||
// Assuming the container's HTML is defined here as before
|
||||
const html = `
|
||||
${this.renderScenarioList()}
|
||||
<div class="form-floating me-2" style="width: 100px; height: 60px;">
|
||||
<input type="number" class="form-control" name="user_count" min="1" value="${this.model.loadTest.threadCount}">
|
||||
<label for="user_count">사용자 수</label>
|
||||
</div>
|
||||
<div class="form-floating me-2" style="width: 90px; height: 60px;">
|
||||
<input type="number" class="form-control" name="repeat_count" min="0" value="${this.model.loadTest.loopCount}">
|
||||
<label for="repeat_count">반복 횟수</label>
|
||||
</div>
|
||||
<div class="form-floating me-2" style="width: 130px; height: 60px;">
|
||||
<input type="number" class="form-control" name="ramp_up" min="0" value="${this.model.loadTest.rampUpTime}">
|
||||
<label for="ramp_up">ramp-up (ms)</label>
|
||||
</div>
|
||||
<div class="form-floating me-2" style="width: 120px; height: 60px;">
|
||||
<input type="number" class="form-control" name="duration" min="0" value="${this.model.loadTest.duration}">
|
||||
<label for="duration">수행 시간(분)</label>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary me-2 start_test" style="height: 60px;">시작</button>
|
||||
<button type="button" class="btn btn-primary me-2 stop_test" style="height: 60px;">정지</button>
|
||||
<button type="button" class="btn btn-primary me-2 save_load_test" style="height: 60px;">저장</button>
|
||||
<button type="button" class="btn btn-primary me-2 reset_load_test" style="height: 60px;">초기화</button>
|
||||
<!-- <button type="button" class="btn btn-primary me-2 show_test_report" style="height: 60px;">통계</button>-->
|
||||
`;
|
||||
|
||||
this.element.innerHTML = html;
|
||||
console.log('LoadTestView.render() called');
|
||||
}
|
||||
|
||||
updateView() {
|
||||
document.querySelector('select[name="scenarioId"]').value = this.model.loadTest.scenarioId || '';
|
||||
document.querySelector('input[name="user_count"]').value = this.model.loadTest.threadCount || 1;
|
||||
document.querySelector('input[name="repeat_count"]').value = this.model.loadTest.loopCount || 0;
|
||||
document.querySelector('input[name="ramp_up"]').value = this.model.loadTest.rampUpTime || 0;
|
||||
document.querySelector('input[name="duration"]').value = this.model.loadTest.duration || 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class LoadTestController {
|
||||
constructor(contextPath, model) {
|
||||
this.contextPath = contextPath || '/';
|
||||
this.model = model;
|
||||
this.view = new LoadTestView(this.model, this);
|
||||
}
|
||||
|
||||
getStartLoadTestUrl() {
|
||||
return this.contextPath + 'startLoadTest';
|
||||
}
|
||||
|
||||
getStopLoadTestUrl() {
|
||||
return this.contextPath + 'stopLoadTest';
|
||||
}
|
||||
|
||||
init(element) {
|
||||
// Initialize the view
|
||||
this.view.render(element);
|
||||
}
|
||||
|
||||
startTest() {
|
||||
this.updateLoadTest();
|
||||
return fetch(this.getStartLoadTestUrl(), {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(this.model.loadTest),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-XSRF-TOKEN': $('meta[name="_csrf"]').attr('content')
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
stopTest() {
|
||||
return fetch(this.getStopLoadTestUrl(), {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(this.model.loadTest),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-XSRF-TOKEN': $('meta[name="_csrf"]').attr('content')
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateLoadTest() {
|
||||
this.model.loadTest.scenarioId = document.querySelector('select[name="scenarioId"]').value;
|
||||
this.model.loadTest.threadCount = document.querySelector('input[name="user_count"]').value;
|
||||
this.model.loadTest.loopCount = document.querySelector('input[name="repeat_count"]').value;
|
||||
this.model.loadTest.rampUpTime = document.querySelector('input[name="ramp_up"]').value;
|
||||
this.model.loadTest.duration = document.querySelector('input[name="duration"]').value;
|
||||
}
|
||||
|
||||
showLoadTest(loadTest) {
|
||||
this.model.loadTest = loadTest;
|
||||
this.view.updateView();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class LoadTester {
|
||||
constructor(contextPath, scenarios) {
|
||||
this.contextPath = contextPath || '/';
|
||||
this.model = new LoadTestModel();
|
||||
|
||||
if (scenarios) {
|
||||
this.model.scenarios = scenarios;
|
||||
} else {
|
||||
this.model.scenarios = [];
|
||||
}
|
||||
this.controller = new LoadTestController(this.contextPath, this.model);
|
||||
|
||||
}
|
||||
|
||||
init(element) {
|
||||
this.controller.init(element);
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
const events = [
|
||||
{type: 'click', selector: '.start_test', action: this.controller.startTest.bind(this.controller)},
|
||||
{type: 'click', selector: '.stop_test', action: this.controller.stopTest.bind(this.controller)}
|
||||
];
|
||||
|
||||
for (let event of events) {
|
||||
$(document).off(event.type, event.selector).on(event.type, event.selector, event.action);
|
||||
}
|
||||
console.log('LoadTester.bindEvents() called');
|
||||
}
|
||||
|
||||
getLoadTest() {
|
||||
//set current value from input
|
||||
this.controller.updateLoadTest();
|
||||
return this.model.loadTest;
|
||||
}
|
||||
|
||||
openLoadTest(loadTest) {
|
||||
this.controller.showLoadTest(loadTest);
|
||||
}
|
||||
}
|
||||
|
||||
export default LoadTester;
|
||||
@@ -0,0 +1,10 @@
|
||||
const column = [
|
||||
{name: 'name', type: 'text', label: '이름'},
|
||||
{name: 'numberOfSamples', type: 'text', label: '#'},
|
||||
{name: 'average', type: 'text', label: '평균'},
|
||||
{name: 'min', type: 'text', label: '최소'},
|
||||
{name: 'max', type: 'text', label: '최대'},
|
||||
{name: 'errorRate', type: 'text', label: '에러 %'},
|
||||
{name: 'throughput', type: 'text', label: 'Throughput'}
|
||||
];
|
||||
|
||||
@@ -23,9 +23,9 @@ class PropertyTableModel {
|
||||
}
|
||||
|
||||
class PropertyTableView {
|
||||
constructor(model, propertyTable) {
|
||||
constructor(model, controller) {
|
||||
this.model = model;
|
||||
this.propertyTable = propertyTable;
|
||||
this.controller = controller;
|
||||
this.modelViewList = [];
|
||||
}
|
||||
|
||||
@@ -138,9 +138,9 @@ class PropertyTableView {
|
||||
|
||||
|
||||
class PropertyTableController {
|
||||
constructor(model, view) {
|
||||
constructor(model) {
|
||||
this.model = model;
|
||||
this.view = view;
|
||||
this.view = new PropertyTableView(this.model);
|
||||
}
|
||||
|
||||
init(element, callback) {
|
||||
@@ -176,18 +176,16 @@ class PropertyTableController {
|
||||
properties[index][key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
updateUI() {
|
||||
this.view.updateUI();
|
||||
}
|
||||
}
|
||||
|
||||
class PropertyTable {
|
||||
constructor(options = {}) {
|
||||
// Initialize the model with properties
|
||||
this.model = new PropertyTableModel(options.properties || []);
|
||||
|
||||
// Create the view and controller, passing the model to them
|
||||
this.view = new PropertyTableView(this.model, this);
|
||||
this.controller = new PropertyTableController(this.model, this.view);
|
||||
|
||||
// Additional properties
|
||||
this.controller = new PropertyTableController(this.model);
|
||||
this.propertyName = options.propertyName || '';
|
||||
}
|
||||
|
||||
@@ -205,7 +203,7 @@ class PropertyTable {
|
||||
setProperties(newProperties) {
|
||||
// Update the model and refresh the view
|
||||
this.model.setProperties(newProperties);
|
||||
this.view.updateUI();
|
||||
this.controller.updateUI();
|
||||
}
|
||||
|
||||
addProperty(property) {
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
class Column {
|
||||
constructor(name, label, style = {}) {
|
||||
this.name = name;
|
||||
this.label = label;
|
||||
this.style = style;
|
||||
}
|
||||
|
||||
// Method to convert style object to string; can be overridden or extended
|
||||
styleToString() {
|
||||
return Object.entries(this.style).map(([key, value]) => `${key}: ${value};`).join(' ');
|
||||
}
|
||||
|
||||
// Render header cell (th)
|
||||
renderHeader() {
|
||||
const th = document.createElement('th');
|
||||
th.textContent = this.name;
|
||||
// Apply styles
|
||||
Object.entries(this.style).forEach(([key, value]) => {
|
||||
th.style[key] = value;
|
||||
});
|
||||
return th;
|
||||
}
|
||||
|
||||
// Render data cell (td)
|
||||
renderCell(data) {
|
||||
const td = document.createElement('td');
|
||||
td.textContent = data;
|
||||
// Apply styles
|
||||
Object.entries(this.style).forEach(([key, value]) => {
|
||||
td.style[key] = value;
|
||||
});
|
||||
return td;
|
||||
}
|
||||
}
|
||||
|
||||
class NumberColumn extends Column {
|
||||
constructor(header) {
|
||||
super(header, {'text-align': 'right'}); // Default style for number columns
|
||||
}
|
||||
}
|
||||
|
||||
class Columns {
|
||||
constructor(columnJson) {
|
||||
this.columns = columnJson.map(def => {
|
||||
let style = def.style || {};
|
||||
switch (def.type) {
|
||||
case 'number':
|
||||
return new NumberColumn(def.name, def.label || def.name, style);
|
||||
case 'text':
|
||||
default:
|
||||
return new Column(def.name, def.label || def.name, style);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getColumns() {
|
||||
return this.columns;
|
||||
}
|
||||
}
|
||||
|
||||
export default Columns;
|
||||
@@ -0,0 +1,91 @@
|
||||
import Columns from './Columns';
|
||||
|
||||
class TableModel {
|
||||
constructor(columns, rows) {
|
||||
this.columns = columns; // Array of Columns objects
|
||||
this.rows = rows;
|
||||
}
|
||||
|
||||
updateRows(newRows) {
|
||||
this.rows = newRows;
|
||||
}
|
||||
}
|
||||
|
||||
class TableView {
|
||||
constructor(model) {
|
||||
this.model = model;
|
||||
this.table = document.createElement('table');
|
||||
this.table.style.border = '1px solid black';
|
||||
this.tbody = document.createElement('tbody'); // Keep tbody as a member for updates
|
||||
this.table.appendChild(this.renderTableHeader());
|
||||
this.table.appendChild(this.tbody); // Append tbody to the table
|
||||
}
|
||||
|
||||
render() {
|
||||
this.renderTableBody(); // Initial rendering of the table body
|
||||
return this.table;
|
||||
}
|
||||
|
||||
renderTableHeader() {
|
||||
const thead = document.createElement('thead');
|
||||
const tr = document.createElement('tr');
|
||||
thead.appendChild(tr);
|
||||
|
||||
this.model.columns.getColumns().forEach(column => {
|
||||
tr.appendChild(column.renderHeader());
|
||||
});
|
||||
|
||||
return thead;
|
||||
}
|
||||
|
||||
renderTableBody() {
|
||||
// Clear existing tbody content
|
||||
while (this.tbody.firstChild) {
|
||||
this.tbody.removeChild(this.tbody.firstChild);
|
||||
}
|
||||
|
||||
// Populate tbody with new rows
|
||||
this.model.rows.forEach(row => {
|
||||
const tr = document.createElement('tr');
|
||||
this.model.columns.getColumns().forEach(column => {
|
||||
tr.appendChild(column.renderCell(row[column.header]));
|
||||
});
|
||||
this.tbody.appendChild(tr);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class TableController {
|
||||
constructor(model) {
|
||||
this.model = model;
|
||||
this.view = new TableView(this.model);
|
||||
}
|
||||
|
||||
updateView() {
|
||||
const tableDiv = document.getElementById('tableDiv');
|
||||
// Clear the previous table view
|
||||
while (tableDiv.firstChild) {
|
||||
tableDiv.removeChild(tableDiv.firstChild);
|
||||
}
|
||||
tableDiv.appendChild(this.view.render());
|
||||
}
|
||||
|
||||
updateRows(newRows) {
|
||||
this.model.updateRows(newRows);
|
||||
this.view.renderTableBody(); // Re-render the table body
|
||||
}
|
||||
}
|
||||
|
||||
class Table {
|
||||
constructor(columnJson) {
|
||||
const columns = new Columns(columnJson);
|
||||
this.model = new TableModel(columns, []);
|
||||
this.controller = new TableController(this.model);
|
||||
}
|
||||
|
||||
updateRows(data) {
|
||||
this.controller.updateRows(data);
|
||||
}
|
||||
}
|
||||
|
||||
export default Table;
|
||||
+41
-28
@@ -1,39 +1,52 @@
|
||||
import APITester from './APITester';
|
||||
import APIScenario from "./APIScenario";
|
||||
import APIScenario from './APIScenario';
|
||||
import LoadTestManager from './LoadTestManager';
|
||||
|
||||
window.MonacoEnvironment = {
|
||||
getWorkerUrl: function (moduleId, label) {
|
||||
let contextPath = document.querySelector('meta[name="context-path"]').getAttribute('content');
|
||||
if (!contextPath) {
|
||||
contextPath = '/';
|
||||
}
|
||||
if (!contextPath.endsWith('/')) {
|
||||
contextPath += '/';
|
||||
}
|
||||
|
||||
if (label === 'json') {
|
||||
return contextPath + 'plugins/apitestmanager/json.worker.bundle.js';
|
||||
}
|
||||
if (label === 'css' || label === 'scss' || label === 'less') {
|
||||
return contextPath + 'plugins/apitestmanager/css.worker.bundle.js';
|
||||
}
|
||||
if (label === 'html' || label === 'handlebars' || label === 'razor') {
|
||||
return contextPath + 'plugins/apitestmanager/html.worker.bundle.js';
|
||||
}
|
||||
if (label === 'typescript' || label === 'javascript') {
|
||||
return contextPath + 'plugins/apitestmanager/ts.worker.bundle.js';
|
||||
}
|
||||
return contextPath + 'plugins/apitestmanager/editor.worker.bundle.js';
|
||||
getWorkerUrl: function(moduleId, label) {
|
||||
let contextPath = document.querySelector(
|
||||
'meta[name="context-path"]').getAttribute('content');
|
||||
if (!contextPath) {
|
||||
contextPath = '/';
|
||||
}
|
||||
|
||||
if (!contextPath.endsWith('/')) {
|
||||
contextPath += '/';
|
||||
}
|
||||
|
||||
if (label === 'json') {
|
||||
return contextPath + 'plugins/apitestmanager/json.worker.bundle.js';
|
||||
}
|
||||
if (label === 'css' || label === 'scss' || label === 'less') {
|
||||
return contextPath + 'plugins/apitestmanager/css.worker.bundle.js';
|
||||
}
|
||||
if (label === 'html' || label === 'handlebars' || label === 'razor') {
|
||||
return contextPath + 'plugins/apitestmanager/html.worker.bundle.js';
|
||||
}
|
||||
if (label === 'typescript' || label === 'javascript') {
|
||||
return contextPath + 'plugins/apitestmanager/ts.worker.bundle.js';
|
||||
}
|
||||
return contextPath + 'plugins/apitestmanager/editor.worker.bundle.js';
|
||||
}
|
||||
};
|
||||
|
||||
export default class APITestManager {
|
||||
|
||||
constructor(servers, options = {}) {
|
||||
this.apiTester = new APITester(options.contextPath);
|
||||
this.apiTester.init(servers);
|
||||
this.apiScenario = new APIScenario(this.apiTester, options.contextPath);
|
||||
this.apiScenario.init();
|
||||
constructor(options = {}) {
|
||||
if (options.type === undefined) {
|
||||
options.type = 'api';
|
||||
}
|
||||
|
||||
if (options.type === 'api') {
|
||||
this.apiTester = new APITester(options.contextPath);
|
||||
this.apiTester.init(options.servers);
|
||||
this.apiScenario = new APIScenario(this.apiTester, options.contextPath);
|
||||
this.apiScenario.init();
|
||||
} else if (options.type === 'load') {
|
||||
this.loadTesterManager = new LoadTestManager(options.contextPath);
|
||||
this.loadTesterManager.init();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,6 +22,14 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.less$/,
|
||||
use: [
|
||||
'style-loader', // Creates `style` nodes from JS strings
|
||||
'css-loader', // Translates CSS into CommonJS
|
||||
'less-loader' // Compiles Less to CSS
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ['style-loader', 'css-loader']
|
||||
|
||||
Reference in New Issue
Block a user