Files
elink-test-master/src/main/resources/templates/page/apiTester.html
T
2023-05-09 15:35:05 +09:00

462 lines
20 KiB
HTML

<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{/layout/api_tester_layout}">
<body>
<section layout:fragment="contentFragment">
<div id="loading-overlay" style="display: none;">
<div class="d-flex justify-content-center align-items-center"
style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 9999;">
<div class="text-center">
<span class="sr-only">Loading...</span>
<br/>
<button id="cancel-ajax" class="btn btn-danger mt-3">Cancel</button>
</div>
</div>
</div>
<div class="container">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
<div class="api_request">
<div class="d-flex align-items-center">
<div class="form-floating col-md-2 me-1">
<select name="method" class="form-select method">
<option value="POST">POST</option>
<option value="GET">GET</option>
<option value="PUT">PUT</option>
<option value="DELETE">DELETE</option>
<option value="CONNECT">CONNECT</option>
<option value="HEAD">HEAD</option>
<option value="OPTIONS">OPTIONS</option>
<option value="TRACE">TRACE</option>
<option value="PATCH">PATCH</option>
</select>
<label for="method" class="form-label must">Method</label>
</div>
<div class="form-floating col-md-3 me-1">
<select name="server" class="form-select server">
<option th:each="server : ${servers}" th:value="${server.id}"
th:text="${server.name}"></option>
</select>
<label for="method" class="form-label must">Server</label>
</div>
<div class="input-group col-md">
<div class="form-floating col-md">
<input type="text" name="path" class="form-control path" placeholder="호출할 경로 (/부터 입력)"
required>
<label for="path" class="must">Path</label>
</div>
<button class="btn btn-primary send">Send</button>
</div>
</div>
<ul class="nav nav-tabs mt-1" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" data-bs-toggle="tab"
data-bs-target="#requestParamTab" type="button" role="tab" aria-controls="paramTab"
aria-selected="true">Param
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" data-bs-toggle="tab"
data-bs-target="#requestHeaderTab" type="button" role="tab"
aria-controls="requestHeader"
aria-selected="false">Header
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" data-bs-toggle="tab"
data-bs-target="#requestBodyTab" type="button" role="tab" aria-controls="requestBody"
aria-selected="false">Body
</button>
</li>
</ul>
<div class="tab-content border-bottom" style="min-height: 300px;">
<div class="tab-pane fade show active" id="requestParamTab" role="tabpanel"
aria-labelledby="requestParams-tab">
<table class="table" id="requestMatcherParamTable">
<thead>
<tr>
<th scope="col">이름</th>
<th></th>
<th>
<button type="button" class="btn btn-secondary btn-sm btn_add_query">추가
</button>
</th>
</tr>
</thead>
<tbody class="query_params">
</tbody>
</table>
</div>
<div class="tab-pane fade" id="requestHeaderTab" role="tabpanel"
aria-labelledby="requestHeader-tab">
<table class="table">
<thead>
<tr>
<th scope="col">이름</th>
<th scope="col"></th>
<th>
<button type="button" class="btn btn-secondary btn-sm btn_add_header">추가
</button>
</th>
</tr>
</thead>
<tbody class="request_headers">
</tbody>
</table>
</div>
<div class="tab-pane fade" id="requestBodyTab" role="tabpanel"
aria-labelledby="requestBody-tab">
<textarea class="form-control request_body" style="height: 200px;"></textarea>
</div>
</div>
<div class="mt-1">
<span>Response</span>
<span class="response_status"></span>
<span class="response_time"></span>
<span class="response_size"></span>
</div>
<ul class="nav nav-tabs mt-1" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" data-bs-toggle="tab"
data-bs-target="#responseBodyTab" type="button" role="tab" aria-controls="responseBody"
aria-selected="false">Body
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" data-bs-toggle="tab"
data-bs-target="#responseHeaderTab" type="button" role="tab"
aria-controls="responseHeader"
aria-selected="false">Header
</button>
</li>
</ul>
<div class="tab-content border-bottom" style="min-height: 250px;">
<div class="tab-pane fade show active" id="responseBodyTab" role="tabpanel"
aria-labelledby="requestParams-tab">
<textarea style="height: 200px;" id="responseBody"></textarea>
</div>
<div class="tab-pane fade" id="responseHeaderTab" role="tabpanel"
aria-labelledby="requestParams-tab">
<table class="table">
<thead>
<tr>
<th scope="col">이름</th>
<th scope="col"></th>
</tr>
</thead>
<tbody class="response_headers">
</tbody>
</table>
</div>
</div>
</div>
</div>
<div id="inputTemplate" style="visibility: hidden; height: 0;">
<table>
<tbody>
<tr>
<td>
<input class="form-control" type="text" name="key"/>
</td>
<td>
<input class="form-control" type="text" name="value"/>
</td>
<td>
<button type="button" class="btn btn-secondary btn-sm btn_remove">제거</button>
</td>
</tr>
</tbody>
</table>
</div>
<div id="responseHeaderTemplate" style="visibility: hidden; height: 0;">
<table>
<tbody>
<tr>
<td>
<input class="form-control" type="text" name="key" readonly/>
</td>
<td>
<input class="form-control" type="text" name="value" readonly/>
</td>
</tr>
</tbody>
</table>
</div>
</section>
</body>
<th:block layout:fragment="contentScript">
<script>
$(function () {
let model = {
server: null,
method: 'POST',
path: '',
headers: [],
queryParams: [],
requestBody: ''
};
let responseModel = {
status: 0,
time: 0,
size: 0,
headers: [],
body: ''
};
let view = {
requestBody: null,
responseBody: null,
init: function () {
$('.btn_add_header').click(function () {
controller.addHeader();
});
$('.btn_add_query').click(function () {
controller.addQueryParam('', '');
});
$("#cancel-ajax").on("click", function () {
if (controller.currentAjaxRequest) {
controller.currentAjaxRequest.abort();
controller.hideLoadingOverlay();
}
});
model.server = $('.api_request').find('.server').val();
model.method = $('.api_request').find('.method').val();
$('.api_request').find('.server').on('change', function () {
model.server = $(this).val();
});
$('.api_request').find('.method').on('change', function () {
model.method = $(this).val();
});
$('.api_request').find('.path').on('input', function () {
model.path = $(this).val();
model.queryParams = [];
let queryString = model.path.split('?')[1];
if (queryString) {
let queryParamArray = queryString.split('&');
for (let i = 0; i < queryParamArray.length; i++) {
let queryParam = queryParamArray[i].split('=');
controller.addQueryParam(queryParam[0], queryParam[1]);
}
}
});
$('.api_request').find('.request_headers').on('click', 'button', function () {
if ($(this).hasClass('btn_remove')) {
controller.removeHeader($(this).attr("data-id"));
}
});
$('.api_request').find('.query_params').on('click', 'button', function () {
if ($(this).hasClass('btn_remove')) {
controller.removeQueryParam($(this).attr("data-id"));
}
});
$('.api_request').find('.send').click(function () {
controller.send();
});
this.requestBody = CodeMirror.fromTextArea($('.api_request').find(".request_body")[0], {
lineNumbers: true,
mode: "application/json",
extraKeys: {
"Ctrl-Enter": function (cm) {
cm.setOption("fullScreen", !cm.getOption("fullScreen"));
},
"Esc": function (cm) {
if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
}
}
});
this.requestBody.on('change', function(cm, change) {
model.requestBody = cm.getValue();
})
this.responseBody = CodeMirror.fromTextArea(document.getElementById('responseBody'), {
lineNumbers: true,
mode: "application/json",
readOnly: true,
noCursor: true,
extraKeys: {
"Ctrl-Enter": function (cm) {
cm.setOption("fullScreen", !cm.getOption("fullScreen"));
},
"Esc": function (cm) {
if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
}
}
});
this.render();
},
render: function () {
$('.api_request').find('.request_headers').empty();
$('.api_request').find('.query_params').empty();
$('.api_request').find('.path').val(model.path);
for (let i = 0; i < model.headers.length; i++) {
let template = $('#inputTemplate').children().find('tr').clone();
template.find('input[name="key"]').val(model.headers[i].key);
template.find('input[name="value"]').val(model.headers[i].value);
template.find('button').attr("data-id", i);
template.find('input[name="key"]').on('input', function () {
model.headers[i].key = $(this).val();
});
template.find('input[name="value"]').on('input', function () {
model.headers[i].value = $(this).val();
});
$('.api_request').find('.request_headers').append(template);
}
for (let i = 0; i < model.queryParams.length; i++) {
let template = $('#inputTemplate').children().find('tr').clone();
template.find('input[name="key"]').val(model.queryParams[i].key);
template.find('input[name="value"]').val(model.queryParams[i].value);
template.find('button').attr("data-id", i);
template.find('input[name="key"]').on('focusout', function () {
model.queryParams[i].key = $(this).val();
controller.buildPath();
});
template.find('input[name="value"]').on('focusout', function () {
model.queryParams[i].value = $(this).val();
controller.buildPath();
});
$('.api_request').find('.query_params').append(template);
}
},
renderResponse: function () {
$('.api_request').find('.response_headers').empty();
$('.api_request').find(".response_status").empty();
view.responseBody.setValue(responseModel.body);
for (let i = 0; i < responseModel.headers.length; i++) {
let template = $('#responseHeaderTemplate').children().find('tr').clone();
template.find('input[name="key"]').val(responseModel.headers[i].key);
template.find('input[name="value"]').val(responseModel.headers[i].value);
$('.api_request').find('.response_headers').append(template);
}
//show response status
$('.api_request').find(".response_status").text("status: " + responseModel.status);
$('.api_request').find(".response_time").text("time: " + responseModel.time + "ms");
$('.api_request').find(".response_size").text("size: " + responseModel.size + "bytes");
}
}
let controller = {
currentAjaxRequest: null,
init: function () {
view.init();
},
addHeader: function () {
model.headers.push({
key: '',
value: ''
});
view.render();
},
removeHeader: function (idx) {
model.headers.splice(idx, 1);
view.render();
},
addQueryParam: function (key, value) {
model.queryParams.push({
key: key,
value: value
});
view.render();
},
buildPath: function () {
let queryString = model.path.split('?')[0];
if (model.queryParams.length > 0) {
queryString += '?';
}
for (let i = 0; i < model.queryParams.length; i++) {
queryString += model.queryParams[i].key + '=' + model.queryParams[i].value;
if (i < model.queryParams.length - 1) {
queryString += '&';
}
}
model.path = queryString;
view.render();
},
removeQueryParam: function (idx) {
model.queryParams.splice(idx, 1);
this.buildPath();
view.render();
},
showLoadingOverlay: function () {
$("#loading-overlay").show();
},
hideLoadingOverlay: function () {
$("#loading-overlay").hide();
},
send: function () {
console.log(model);
$('.api_request').find('.response_headers').empty();
view.responseBody.setValue('');
let startTime = new Date().getTime();
if (model.path !== '') {
controller.showLoadingOverlay();
this.currentAjaxRequest = $.ajax({
url: '/api/test.do',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(model),
success: function (data) {
responseModel.status = data.status;
responseModel.body = data.body;
responseModel.headers = data.headers;
responseModel.size = data.size;
},
error: function (jqXHR, textStatus, errorThrown) {
console.error(textStatus, errorThrown);
controller.hideLoadingOverlay();
},
complete: function () {
let endTime = new Date().getTime();
responseModel.time = endTime - startTime;
view.renderResponse();
controller.hideLoadingOverlay();
}
});
}
},
addCollection: function () {
},
removeCollection: function () {
},
addAPIRequest: function () {
},
removeAPIRequest: function () {
}
}
controller.init();
});
</script>
</th:block>
</html>