diff --git a/src/main/java/com/eactive/httpmockserver/client/service/ApiRequestMgmtService.java b/src/main/java/com/eactive/httpmockserver/client/service/ApiRequestMgmtService.java index 4d5de51..1dbe86e 100644 --- a/src/main/java/com/eactive/httpmockserver/client/service/ApiRequestMgmtService.java +++ b/src/main/java/com/eactive/httpmockserver/client/service/ApiRequestMgmtService.java @@ -30,6 +30,11 @@ public class ApiRequestMgmtService { } public void deleteApiCollection(String id) { + ApiCollection collection = apiCollectionRepository.findById(id).orElseThrow(() -> new NotFoundException("api collection[" + id + "] not found")); + + if (collection.getApis().size()>0){ + throw new RuntimeException("api collection[" + id + "] is not empty"); + } apiCollectionRepository.deleteById(id); } diff --git a/src/main/java/com/eactive/httpmockserver/common/exception/CustomGlobalExceptionHandler.java b/src/main/java/com/eactive/httpmockserver/common/exception/CustomGlobalExceptionHandler.java index 98d2710..859a045 100644 --- a/src/main/java/com/eactive/httpmockserver/common/exception/CustomGlobalExceptionHandler.java +++ b/src/main/java/com/eactive/httpmockserver/common/exception/CustomGlobalExceptionHandler.java @@ -21,4 +21,13 @@ public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler return new ResponseEntity<>(errors, HttpStatus.NOT_FOUND); } + @ExceptionHandler(RuntimeException.class) + public ResponseEntity customHandleRuntime(Exception ex, WebRequest request) { + CustomErrorResponse errors = new CustomErrorResponse(); + errors.setTimestamp(LocalDateTime.now()); + errors.setError(ex.getMessage()); + errors.setStatus(HttpStatus.BAD_REQUEST.value()); + return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST); + } + } diff --git a/src/main/resources/static/css/custom.css b/src/main/resources/static/css/custom.css index 74d0e51..1f3c47f 100644 --- a/src/main/resources/static/css/custom.css +++ b/src/main/resources/static/css/custom.css @@ -1547,4 +1547,19 @@ figure div span { .prod { background-color : #49cc90 !important; color : white !important; -} \ No newline at end of file +} + +.reset-button { + /* Reset button styles */ + background: none; + border: none; + color: inherit; + cursor: pointer; + padding: 0; + font: inherit; + outline: inherit; + + /* Style the icon */ + display: inline-block; + margin-right: 5px; /* Adjust this value as per your requirement */ +} diff --git a/src/main/resources/templates/page/apiCollection.html b/src/main/resources/templates/page/apiCollection.html index 4c4c0c9..7a266c5 100644 --- a/src/main/resources/templates/page/apiCollection.html +++ b/src/main/resources/templates/page/apiCollection.html @@ -40,6 +40,23 @@ + diff --git a/src/main/resources/templates/page/apiTester.html b/src/main/resources/templates/page/apiTester.html index 6e9cffb..d290c8e 100644 --- a/src/main/resources/templates/page/apiTester.html +++ b/src/main/resources/templates/page/apiTester.html @@ -5,7 +5,7 @@
-
@@ -269,18 +276,25 @@ }); $('#new_request').on('click', function () { + if (collections.length == 0) { + alert('새로운 컬렉션을 추가해 주세요.'); + return; + } $('#new_api_request_modal').modal('show'); }); $('#new_api_request_modal').find('.add_new_request').on('click', function () { controller.addAPIRequest($('#new_api_request_modal').find('select').val()); - }); $('#new_collection_modal').find('.save').on('click', function () { controller.addCollection($('#new_collection_modal').find('input').val()); }); + $('#confirm_delete_modal').find('button.confirm_delete').on('click', function () { + controller.removeCollection($(this).data('id')); + }); + controller.loadCollections(); }, renderParam: function (tabIdx) { @@ -293,8 +307,8 @@ template.find('input[name="key"]').val(model.queryParams[i].key); template.find('input[name="value"]').val(model.queryParams[i].value); - if(model.queryParams[i].enabled){ - template.find('input[name="enabled"]').prop("checked",model.queryParams[i].enabled); + if (model.queryParams[i].enabled) { + template.find('input[name="enabled"]').prop("checked", model.queryParams[i].enabled); } template.find('button').attr("data-id", i); @@ -325,8 +339,8 @@ let model = apiRequests[tabIdx]; for (let i = 0; i < model.headers.length; i++) { let template = $('#inputTemplate').children().find('tr').clone(); - if(model.headers[i].enabled){ - template.find('input[name="enabled"]').prop("checked",model.headers[i].enabled); + if (model.headers[i].enabled) { + template.find('input[name="enabled"]').prop("checked", model.headers[i].enabled); } template.find('input[name="key"]').val(model.headers[i].key); template.find('input[name="value"]').val(model.headers[i].value); @@ -345,14 +359,14 @@ target.find('.request_headers').append(template); } }, - renderTabHeader : function(){ + renderTabHeader: function () { let apiRequestTabTitle = $('#apiRequestTabTitle'); apiRequestTabTitle.empty(); for (let idx = 0; idx < apiRequests.length; idx++) { let $span = $('') - .html(' '); + .html(' '); - $span.on('click', function(event) { + $span.on('click', function (event) { event.stopPropagation(); controller.removeAPIRequest(idx); }); @@ -374,7 +388,7 @@ apiRequestTabTitle.append(li); } }, - renderTabContent: function(idx){ + renderTabContent: function (idx) { let apiRequestTabContent = $('#apiRequestTabContent'); let model = apiRequests[idx]; @@ -384,13 +398,13 @@ template.attr('id', 'api_request_' + idx); template.attr("data-id", idx); template.attr('class', 'tab-pane fade mt-1' + (idx === this.currentIdx ? ' show active' : '')); - template.find('.btn_request_param_tab').attr('data-bs-target', "#requestParamTab_"+ idx); - template.find('.btn_request_header_tab').attr('data-bs-target', "#requestHeaderTab_"+ idx); - template.find('.btn_request_body_tab').attr('data-bs-target', "#requestBodyTab_"+ idx); + template.find('.btn_request_param_tab').attr('data-bs-target', "#requestParamTab_" + idx); + template.find('.btn_request_header_tab').attr('data-bs-target', "#requestHeaderTab_" + idx); + template.find('.btn_request_body_tab').attr('data-bs-target', "#requestBodyTab_" + idx); - template.find('.request_param_tab').attr('id', "requestParamTab_"+ idx); - template.find('.request_header_tab').attr('id', "requestHeaderTab_"+ idx); - template.find('.request_body_tab').attr('id', "requestBodyTab_"+ idx); + template.find('.request_param_tab').attr('id', "requestParamTab_" + idx); + template.find('.request_header_tab').attr('id', "requestHeaderTab_" + idx); + template.find('.request_body_tab').attr('id', "requestBodyTab_" + idx); apiRequestTabContent.append(template); let target = $('#api_request_' + idx); @@ -435,7 +449,7 @@ let queryParamArray = queryString.split('&'); for (let i = 0; i < queryParamArray.length; i++) { let queryParam = queryParamArray[i].split('='); - controller.addQueryParam(idx, queryParam[0], queryParam[1]===undefined?'' : queryParam[1]); + controller.addQueryParam(idx, queryParam[0], queryParam[1] === undefined ? '' : queryParam[1]); } } }); @@ -445,7 +459,7 @@ }); let me = this; - require(['vs/editor/editor.main'], function() { + require(['vs/editor/editor.main'], function () { let requestBodyEditor = monaco.editor.create(target.find(".request_body")[0], { value: '', language: 'json', @@ -503,8 +517,13 @@ target.find('.response_headers').empty(); target.find(".response_status").empty(); - this.responses[tabIdx].setValue(model.responseModel.body); - + try { + const parsedJson = JSON.parse(model.responseModel.body); + const formattedJson = JSON.stringify(parsedJson, null, 2); + this.responses[tabIdx].setValue(formattedJson); + }catch(e){ + this.responses[tabIdx].setValue(model.responseModel.body); + } for (let i = 0; i < model.responseModel.headers.length; i++) { let template = $('#responseHeaderTemplate').children().find('tr').clone(); template.find('input[name="key"]').val(model.responseModel.headers[i].key); @@ -512,31 +531,43 @@ target.find('.response_headers').append(template); } - //show response status 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"); }, - renderCollections: function(){ + renderCollections: function () { let select = $('#modal_collection'); - let sidemenu = $('#side_menubar'); + let sidemenu = $('#side_menubar'); select.empty(); sidemenu.empty(); for (let i = 0; i < collections.length; i++) { let template = $('#collectionTemplate').children().clone(); + template.attr('id', 'collection_' + i); template.find('button.new_collection').text(collections[i].name); - template.find('button').attr('data-bs-target', '#collection_' + i); - template.find('.collapse').attr('id', 'collection_' +i); + template.find('button.new_collection').attr('data-bs-target', '#btn_collection_' + i); + template.find('button.delete_collection').attr('data-id', collections[i].id); + template.find('button.delete_collection').on('click', function () { + $('#confirm_delete_modal').find('.confirm_delete').attr('data-id', $(this).attr('data-id')); + $('#confirm_delete_modal').modal('show'); + }); + + template.find('.collapse').attr('id', 'btn_collection_' + i); for (let j = 0; j < collections[i].apis.length; j++) { let collectionApiTemplate = $('#collectionApiTemplate').children().clone(); let link = collectionApiTemplate.find('a'); + let btn = collectionApiTemplate.find('button.delete_api_request'); link.text(collections[i].apis[j].name); link.attr('data-collection', collections[i].id); link.attr('data-id', collections[i].apis[j].id); - link.on('click', function(){ - controller.openApiRequest($(this).attr('data-collection'),$(this).attr('data-id')); + link.on('click', function () { + controller.openApiRequest($(this).attr('data-collection'), $(this).attr('data-id')); + }) + btn.attr('data-collection', collections[i].id); + btn.attr('data-id', collections[i].apis[j].id); + btn.on('click', function () { + controller.deleteApiRequest($(this).attr('data-collection'), $(this).attr('data-id')); }) template.find('.collapse ul').append(collectionApiTemplate); } @@ -586,7 +617,7 @@ queryString += '?'; } for (let i = 0; i < model.queryParams.length; i++) { - if(!model.queryParams[i].enabled){ + if (!model.queryParams[i].enabled) { continue; } queryString += model.queryParams[i].key + '=' + model.queryParams[i].value; @@ -609,18 +640,28 @@ hideLoadingOverlay: function () { $("#loading-overlay").hide(); }, - openApiRequest: function(collectionId, apiId) { + openApiRequest: function (collectionId, apiId) { let selectedApi = collections.filter(function (collection) { return collection.id === collectionId; })[0].apis.filter(function (api) { return api.id === apiId; })[0]; - if (selectedApi !== undefined){ - let tab = apiRequests.filter(function(api){ + if (selectedApi !== undefined) { + let tab = apiRequests.filter(function (api) { return api.id === selectedApi.id; })[0]; - if (tab === undefined){ + if (selectedApi.responseModel === undefined) { + selectedApi.responseModel = { + status: 0, + time: 0, + size: 0, + headers: [], + body: '' + }; + } + + if (tab === undefined) { apiRequests.push(selectedApi); view.currentIdx = apiRequests.length - 1; view.renderTabHeader(); @@ -628,6 +669,32 @@ } } }, + deleteApiRequest: function (collectionId, apiId) { + this.currentAjaxRequest = $.ajax({ + url: '/mgmt/collections/{id}/apis/delete.do'.replace('{id}', collectionId), + type: 'POST', + contentType: 'application/json', + data: JSON.stringify({id: apiId}), + success: function (data) { + }, + error: function (response, textStatus, errorThrown) { + controller.hideLoadingOverlay(); + }, + complete: function () { + controller.hideLoadingOverlay(); + + //get the api with apiIdx index from apiRequests + let apiIdx = apiRequests.findIndex(function (api) { + return api.id === apiId; + }); + + apiRequests.splice(apiIdx, 1); + view.render(); + $('#apiRequestTabTitle li:eq(0) button').tab('show'); + controller.loadCollections(); + } + }); + }, send: function (tabIdx) { let model = apiRequests[tabIdx]; let target = $('#api_request_' + tabIdx); @@ -648,8 +715,7 @@ model.responseModel.headers = data.headers; model.responseModel.size = data.size; }, - error: function (jqXHR, textStatus, errorThrown) { - console.error(textStatus, errorThrown); + error: function (response, textStatus, errorThrown) { controller.hideLoadingOverlay(); }, complete: function () { @@ -661,7 +727,7 @@ }); } }, - loadCollections: function(){ + loadCollections: function () { controller.showLoadingOverlay(); this.currentAjaxRequest = $.ajax({ url: '/mgmt/collections/list.do', @@ -670,8 +736,7 @@ success: function (data) { collections = data; }, - error: function (jqXHR, textStatus, errorThrown) { - console.error(textStatus, errorThrown); + error: function (response, textStatus, errorThrown) { controller.hideLoadingOverlay(); }, complete: function () { @@ -688,15 +753,15 @@ contentType: 'application/json', data: JSON.stringify({name: collection}), success: function (data) { - $('#new_collection_modal').modal('hide'); + }, - error: function (jqXHR, textStatus, errorThrown) { - console.error(textStatus, errorThrown); + error: function (response, textStatus, errorThrown) { controller.hideLoadingOverlay(); }, complete: function () { controller.loadCollections(); controller.hideLoadingOverlay(); + $('#new_collection_modal').modal('hide'); } }); }, @@ -709,20 +774,23 @@ data: JSON.stringify({id: id}), success: function () { }, - error: function (jqXHR, textStatus, errorThrown) { - console.error(textStatus, errorThrown); + error: function (response, textStatus, errorThrown) { + let error = response.responseJSON; controller.hideLoadingOverlay(); + alert(error.error); }, complete: function () { controller.loadCollections(); controller.hideLoadingOverlay(); + $('#confirm_delete_modal').modal('hide'); } }); }, addAPIRequest: function (collectionId) { + let name = 'New Request'; let newApi = { - id : '', + id: '', server: null, method: 'GET', name: name, @@ -745,24 +813,21 @@ contentType: 'application/json', data: JSON.stringify(newApi), success: function (data) { - console.log(data); - newApi.id = data.id; + newApi.id = data; }, - error: function (jqXHR, textStatus, errorThrown) { - console.error(textStatus, errorThrown); + error: function (response, textStatus, errorThrown) { controller.hideLoadingOverlay(); }, complete: function () { controller.loadCollections(); - controller.hideLoadingOverlay(); apiRequests.push(newApi); view.currentIdx = apiRequests.length - 1; view.renderTabHeader(); view.renderTabContent(view.currentIdx); + controller.hideLoadingOverlay(); $('#new_api_request_modal').modal('hide'); - controller.loadCollections(); } }); },