import * as monaco from 'monaco-editor'; import APIClient from './APIClient'; import {createPopper} from '@popperjs/core/lib/popper-lite'; import VariableSnippet from './components/VariableSnippet'; import HTTPClientView from './components/HTTPClientView'; import TCPClientView from './components/TCPClientView'; import ServerManager from './components/ServerManager'; import HTTPFlatClientView from './components/HTTPFlatClientView'; class APITester { constructor(contextPath) { this.servers = []; this.apiTabTitleTarget = '#apiRequestTabTitle'; this.apiTabContentTarget = '#apiRequestTabContent'; this.collectionTarget = '#side_menubar'; this.currentIndex = 0; this.apiRequests = []; //탭 열린 API this.collections = []; //API 컬렉션 this.requestEditors = []; //API request body editor this.responseEditors = []; //API response body editor this.consoleEditors = []; //API console editor this.preRequestEditors = []; this.postRequestEditors = []; this.apiClient = new APIClient(); this.popperContent = document.getElementById('popperContent'); this.popperInstance = null; this.contextPath = contextPath || '/'; } getCollectionListURL() { return this.contextPath + 'mgmt/collections/list.do'; } getCollectionCreateURL() { return this.contextPath + 'mgmt/collections/create.do'; } getCollectionUpdateURL() { return this.contextPath + 'mgmt/collections/update.do'; } getCollectionDeleteURL() { return this.contextPath + 'mgmt/collections/delete.do'; } getAPISaveURL(collectionId) { return this.contextPath + `mgmt/collections/${collectionId}/apis/save.do`; } getAPIDeleteURL(collectionId) { return this.contextPath + `mgmt/collections/${collectionId}/apis/delete.do`; } headerTemplate(header, index) { return `
`; } responseHeaderTemplate(key, value) { return ` `; } collectionTemplate(collection, index) { return `
  • `; } collectionApiTemplate(api, apiIndex, collectionId) { return `
  • ${api.name}
  • `; } init(servers) { if (servers) { this.servers = servers; if (this.servers.length === 0) { $('.toast-body').text('서버관리에서 서버를 등록해주세요.'); $('.toast').toast('show'); } } this.serverManager = new ServerManager(this.servers); this.bindEvents(); this.loadCollections(); } bindEvents() { const events = [ {selector: '.btn_add_header', action: this.addHeader.bind(this)}, {selector: '.btn_remove_header', action: this.removeHeader.bind(this)}, {selector: '.send', action: this.handleSendAPIRequest.bind(this)}, {selector: '.save', action: this.saveAPIRequest.bind(this)}, {selector: '.save_collection', action: this.addCollection.bind(this)}, {selector: '#cancel-ajax', action: this.cancelAjaxRequest.bind(this)}, {selector: '#new_collection', action: this.showNewCollectionModal.bind(this)}, {selector: '#new_request', action: this.showNewApiRequestModal.bind(this)}, {selector: '.edit_collection', action: this.showCollectionEditModal.bind(this)}, {selector: '.delete_collection', action: this.showCollectionDeleteModal.bind(this)}, {selector: '.confirm_delete', action: this.removeCollection.bind(this)}, {selector: '.add_new_request', action: this.addAPIRequest.bind(this)}, {selector: '.edit_api_request', action: this.showEditNameModal.bind(this)}, {selector: '.delete_api_request', action: this.showDeleteApiRequestModal.bind(this)}, {selector: '.open_api_request', action: this.openApiRequest.bind(this)}, {selector: '.confirm_delete_api', action: this.deleteApiRequest.bind(this)}, {selector: '.close_tab', action: this.closeTab.bind(this)}]; for (let event of events) { $(document).on('click', event.selector, event.action); } $(document).on('change', '.path', this.parseParam.bind(this)); $(document).on('change', '.request_headers input', this.handleUIUpdate.bind(this)); $(document).on('change', '.api_request_info select, .api_request_info input', this.handleUIUpdate.bind(this)); $(document).on('change', '.api_request_info select', this.handleUpdateServer.bind(this)); window.addEventListener('resize', this.resizeEditor.bind(this)); } resizeEditor() { //FIXME: 창 크기 변경시 자동으로 editor 크기가 조정되지 않아서, 0으로 강제 변경후 다시 layout() 호출 for (let i = 0; i < this.requestEditors.length; i++) { this.requestEditors[i].layout({ width: 0 }); } for (let i = 0; i < this.responseEditors.length; i++) { this.responseEditors[i].layout({ width: 0 }); } for (let i = 0; i < this.preRequestEditors.length; i++) { this.preRequestEditors[i].layout({ width: 0 }); } for (let i = 0; i < this.postRequestEditors.length; i++) { this.postRequestEditors[i].layout({ width: 0 }); } for (let i = 0; i < this.requestEditors.length; i++) { this.requestEditors[i].layout({}); } for (let i = 0; i < this.responseEditors.length; i++) { this.responseEditors[i].layout({}); } for (let i = 0; i < this.preRequestEditors.length; i++) { this.preRequestEditors[i].layout({}); } for (let i = 0; i < this.postRequestEditors.length; i++) { this.postRequestEditors[i].layout({}); } } showCollectionDeleteModal(event) { const collectionId = $(event.currentTarget).closest('button').data('collection'); $('#confirm_delete_modal').find('.confirm_delete').data('id', collectionId); $('#confirm_delete_modal').modal('show'); } getApiRequestModel(apiId) { let foundApi = null; this.collections.forEach(function(collection) { collection.apis.forEach(function(api) { if (api.id === apiId) { foundApi = api; } }); }); return foundApi; } showCollectionEditModal(event) { const me = this; const collectionId = $(event.currentTarget).closest('button').data('collection'); const collection = this.collections.filter(function(collection) { return collection.id === collectionId; })[0]; $('#edit_name_modal').find('input').val(''); $('#edit_name_modal').modal('show'); let saveBtn = $('#edit_name_modal').find('button.change_name'); saveBtn.off('click'); saveBtn.on('click', function() { collection.name = $('#edit_name_modal').find('input').val(); me.saveCollection(collection.id, collection.name); me.renderCollections(); $('#edit_name_modal').modal('hide'); }); } showDeleteApiRequestModal(event) { let collectionId = $(event.currentTarget).closest('li').data('collection'); let apiId = $(event.currentTarget).closest('li').data('id'); $('#confirm_delete_api_modal').find('.confirm_delete_api').data('id', apiId); $('#confirm_delete_api_modal').find('.confirm_delete_api').data('collection', collectionId); $('#confirm_delete_api_modal').modal('show'); } showNewApiRequestModal(event) { if (this.collections.length == 0) { $('.toast-body').text('새로운 컬렉션을 추가해 주세요.'); $('.toast').toast('show'); return; } $('#new_api_request_modal').modal('show'); } showNewCollectionModal(event) { $('#new_collection_modal').find('input').val(''); $('#new_collection_modal').modal('show'); } showEditNameModal(event) { const me = this; const apiId = $(event.currentTarget).closest('li').data('id'); const collectionId = $(event.currentTarget).closest('li').data('collection'); const collection = this.collections.filter(function(collection) { return collection.id === collectionId; })[0]; let apiRequest = collection.apis.filter(function(api) { return api.id === apiId; })[0]; apiRequest.collectionId = collectionId; let saveBtn = $('#edit_name_modal').find('button.change_name'); saveBtn.off('click'); saveBtn.on('click', function() { apiRequest.name = $('#edit_name_modal').find('input').val(); me.changeAPIName(apiRequest); me.renderCollections(); $('#edit_name_modal').modal('hide'); //탭이 열려 있으면, 닫았다가 다시 열어야 함. me.renderTabHeader(); me.renderTabContent(apiRequest.index); }); $('#edit_name_modal').find('input').val(''); $('#edit_name_modal').modal('show'); } getFieldValue(element) { if ($(element).is('select') || $(element).is('input[type="text"]')) { return $(element).val(); } if ($(element).is('input[type="checkbox"]')) { return $(element).is(':checked'); } } handleUIUpdate(event) { if (event) { event.preventDefault(); const element = event.currentTarget; const fieldName = $(element).attr('name'); const newValue = this.getFieldValue(element); const tabIndex = $('#apiRequestTabContent').children().index($(event.currentTarget).closest('.api_request')); let model = this.apiRequests[tabIndex]; if (fieldName !== '') { _.set(model, fieldName, newValue); } } } handleUpdateServer(event) { const tabIndex = $('#apiRequestTabContent').children().index($(event.currentTarget).closest('.api_request')); let model = this.apiRequests[tabIndex]; let target = $('#api_request_' + model.id); target.find('input[name="basePath"]').val(this.serverManager.basePath(model.server)); } renderHeaders(headers) { return headers.map((header, index) => this.headerTemplate(header, index)).join(''); } renderTabHeader() { let apiRequestTabTitle = $(this.apiTabTitleTarget); apiRequestTabTitle.empty(); for (let idx = 0; idx < this.apiRequests.length; idx++) { let li = ``; apiRequestTabTitle.append(li); } } showPopper(element, text) { const variable = text.replace(/{{|}}/g, ''); this.popperContent.style.display = 'block'; const innerDiv = this.popperContent.querySelector('.variable_popper'); innerDiv.textContent = '{{' + variable + '}} = ' + (window.globals[variable] === undefined ? '' : window.globals[variable]); this.popperInstance = createPopper(element, this.popperContent, { placement: 'bottom', // Popper below the element modifiers: [ { name: 'offset', options: { offset: [0, 8] // Adjust the position if needed } }] }); } hidePopper(element) { if (this.popperInstance) { this.popperInstance.destroy(); this.popperInstance = null; } this.popperContent.style.display = 'none'; } renderTabContent(index) { var me = this; let apiRequestTabContent = $(this.apiTabContentTarget); let model = this.apiRequests[index]; document.querySelectorAll('.variable').forEach(element => { let text = element.innerText; element.addEventListener('mouseover', () => me.showPopper(element, text)); element.addEventListener('mouseout', () => me.hidePopper(element)); }); let language = model.type === 'HTTP' ? 'json' : 'text'; let requestBodyEditor = null; let target = null; if (model.type === 'HTTP') { let httpClientView = new HTTPClientView(me, this.serverManager, apiRequestTabContent, model); httpClientView.init(); target = this.openTab(model.id); requestBodyEditor = monaco.editor.create(target.find('.request_body')[0], { value: model.requestBody, language: 'json', automaticLayout: true }); } else if (model.type === 'HTTPFlat') { let httpFlatClientView = new HTTPFlatClientView(me, this.serverManager, apiRequestTabContent, model); httpFlatClientView.init(); target = this.openTab(model.id); requestBodyEditor = monaco.editor.create(target.find('.request_body')[0], { value: model.requestBody, language: 'json', automaticLayout: true }); httpFlatClientView.requestBodyEditor = requestBodyEditor; } else if (model.type === 'TCP') { const tcpClientView = new TCPClientView(me, this.serverManager, apiRequestTabContent, model); tcpClientView.init(); target = this.openTab(model.id); requestBodyEditor = monaco.editor.create(target.find('.request_body')[0], { value: model.requestBody, language: 'text', automaticLayout: true }); tcpClientView.requestBodyEditor = requestBodyEditor; } requestBodyEditor.onMouseMove((e) => { let position = e.target.position; if (position) { let model = requestBodyEditor.getModel(); let word = model.getWordAtPosition(position); let lineContent = model.getLineContent(position.lineNumber); if (word) { if (lineContent.indexOf('{{' + word.word + '}}') > -1) { me.showPopper(e.target.element, word.word); } } else { me.hidePopper(e.target.element); } } }); me.requestEditors.push(requestBodyEditor); target.find('.request_body_tab')[0].addEventListener('shown.bs.tab', () => { me.requestEditors[index].layout(); }); me.requestEditors[index].onDidChangeModelContent((e) => { model.requestBody = requestBodyEditor.getValue(); }); target.find('.request_body_type').on('change', function() { let model = requestBodyEditor.getModel(); monaco.editor.setModelLanguage(model, $(this).val()); }); let responseBodyEditor = monaco.editor.create(target.find('.response_body')[0], { value: '', language: language, automaticLayout: true, quickSuggestions: false }); me.responseEditors.push(responseBodyEditor); let consoleEditor = monaco.editor.create(target.find('.console')[0], { value: '', automaticLayout: true, quickSuggestions: false }); me.consoleEditors.push(consoleEditor); let preRequestEditor = monaco.editor.create(target.find('.pre-request')[0], { value: model.preRequestScript, language: 'javascript', automaticLayout: true, quickSuggestions: false }); const snippet1 = new VariableSnippet(); snippet1.init(target.find('.pre-request-variable')[0], preRequestEditor); me.preRequestEditors.push(preRequestEditor); target.find('.pre-request')[0].addEventListener('shown.bs.tab', () => { me.preRequestEditors[index].layout(); }); me.preRequestEditors[index].onDidChangeModelContent((e) => { model.preRequestScript = preRequestEditor.getValue(); }); let postRequestEditor = monaco.editor.create(target.find('.post-request')[0], { value: model.postRequestScript, language: 'javascript', automaticLayout: true, quickSuggestions: false }); const snippet2 = new VariableSnippet(); snippet2.init(target.find('.post-request-variable')[0], postRequestEditor); me.postRequestEditors.push(postRequestEditor); target.find('.post-request')[0].addEventListener('shown.bs.tab', () => { me.postRequestEditors[index].layout(); }); me.postRequestEditors[index].onDidChangeModelContent((e) => { model.postRequestScript = postRequestEditor.getValue(); }); } renderResponse(tabIndex) { let model = this.apiRequests[tabIndex]; let target = $('#api_request_' + model.id); target.find('.response_headers').empty(); target.find('.response_status').empty(); try { const parsedJson = JSON.parse(model.responseModel.body); const formattedJson = JSON.stringify(parsedJson, null, 2); this.responseEditors[tabIndex].setValue(formattedJson); } catch (e) { this.responseEditors[tabIndex].setValue(model.responseModel.body); } if (model.responseModel && model.responseModel.headers) { const headers = model.responseModel.headers; Object.entries(headers).forEach(([key, value]) => { target.find('.response_headers').append(this.responseHeaderTemplate(key, value)); }); } target.find('.response_status').text('status: ' + model.responseModel.status); target.find('.response_time').text('time: ' + model.responseModel.time + 'ms'); target.find('.response_size').text('size: ' + model.responseModel.size + 'bytes'); } customHelper(event) { // Create and return the custom helper element let name = $(event.target).closest('.api_request_sortable').find('.open_api_request').text().trim(); let apiId = $(event.target).closest('.api_request_sortable').data('id'); return `
    ${name}
    `; } renderCollections() { let select = $('#modal_collection'); let sidemenu = $(this.collectionTarget); select.empty(); sidemenu.empty(); for (let i = 0; i < this.collections.length; i++) { sidemenu.append(this.renderCollection(this.collections[i], i)); let option = $('