Swagger UI 응답 처리 개선:
eapim-portal CI / build (push) Has been cancelled
eapim-portal Test / test (push) Has been cancelled

- 응답 렌더링 중 "응답지우기" 클릭 시 패널 복구 방지 로직 추가
- "Clear" 버튼 동작 확장: 사용자 정의 응답 그리드 제거 처리
This commit is contained in:
Rinjae
2026-08-26 19:28:38 +09:00
parent 28017fd83c
commit 52707998d4
7 changed files with 180 additions and 57 deletions
+90 -47
View File
@@ -1247,7 +1247,7 @@ hr {
transition: all 0.3s ease;
}
.mobile-drawer .drawer-welcome .btn-drawer-login:hover {
background: rgb(0%, 25.7647058824%, 63.5294117647%);
background: rgb(0, 65.7, 162);
}
.mobile-drawer .drawer-welcome.authenticated {
flex-direction: row;
@@ -2574,7 +2574,7 @@ hr {
color: #FFFFFF;
}
.btn-success:hover {
background: rgb(32.662665066%, 78.1608643457%, 41.762304922%);
background: rgb(83.2897959184, 199.3102040816, 106.493877551);
transform: translateY(-3px);
}
.btn-danger {
@@ -2582,7 +2582,7 @@ hr {
color: #FFFFFF;
}
.btn-danger:hover {
background: rgb(100%, 27.7647058824%, 27.7647058824%);
background: rgb(255, 70.8, 70.8);
transform: translateY(-3px);
}
.btn-ghost {
@@ -2848,7 +2848,7 @@ hr {
.action-btn-delete:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1);
background: rgb(100%, 34.862745098%, 34.862745098%);
background: rgb(255, 88.9, 88.9);
}
.action-btn-delete:active {
transform: translateY(0);
@@ -2966,7 +2966,7 @@ hr {
background: #a4d6ea;
}
.btn-input-action.btn-change:hover {
background: rgb(51.6323529412%, 78.2079831933%, 88.8382352941%);
background: rgb(131.6625, 199.4303571429, 226.5375);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1);
}
@@ -3041,7 +3041,7 @@ hr {
border: none;
}
.btn-action-primary:hover {
background: rgb(12.4992826399%, 36.3615494978%, 80.6771879484%);
background: rgb(31.8731707317, 92.7219512195, 205.7268292683);
transform: translateY(-2px);
color: #fff;
}
@@ -3091,7 +3091,7 @@ hr {
}
.status-badge.status-processing {
background: rgba(255, 217, 61, 0.1);
color: rgb(86.7450980392%, 69.7537901759%, 0%);
color: rgb(221.2, 177.8721649485, 0);
}
.status-badge.status-failed {
background: rgba(255, 107, 107, 0.1);
@@ -3131,7 +3131,7 @@ hr {
}
.status-badge-header.status-processing {
background: rgba(255, 217, 61, 0.1);
color: rgb(86.7450980392%, 69.7537901759%, 0%);
color: rgb(221.2, 177.8721649485, 0);
}
.badge-sm {
@@ -4319,7 +4319,7 @@ select.form-control {
.file-upload-wrapper .file-remove-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1);
background: rgb(100%, 34.862745098%, 34.862745098%);
background: rgb(255, 88.9, 88.9);
}
.file-upload-wrapper .file-remove-btn:active {
transform: translateY(0);
@@ -4640,7 +4640,7 @@ select.form-control {
transition: all 0.3s ease;
}
.form-actions--with-withdrawal .withdrawal-link:hover {
background: rgb(82.4349376114%, 91.2174688057%, 95.2709447415%);
background: rgb(210.2090909091, 232.6045454545, 242.9409090909);
}
.form-actions--with-withdrawal .withdrawal-link img {
width: 22px;
@@ -4795,7 +4795,7 @@ select.form-control {
text-decoration: underline;
}
.notice-content-box a:hover {
color: rgb(0%, 25.7647058824%, 63.5294117647%);
color: rgb(0, 65.7, 162);
}
.form-row--content .form-label-wrapper {
@@ -5733,7 +5733,7 @@ select.form-control {
font-size: 16px;
}
.drawer-logout-btn:hover {
background: rgb(100%, 27.7647058824%, 27.7647058824%);
background: rgb(255, 70.8, 70.8);
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(75, 155, 255, 0.15);
}
@@ -6447,7 +6447,7 @@ select.form-control {
color: #64748b;
}
.list-table-btn--default:hover {
background-color: rgb(91.512605042%, 91.512605042%, 90.6638655462%);
background-color: rgb(233.3571428571, 233.3571428571, 231.1928571429);
}
.list-table-btn--primary {
background-color: #ecf0fa;
@@ -6455,7 +6455,7 @@ select.form-control {
color: #2a69de;
}
.list-table-btn--primary:hover {
background-color: rgb(85.0049019608%, 88.1617647059%, 96.0539215686%);
background-color: rgb(216.7625, 224.8125, 244.9375);
}
.list-table-btn--secondary {
background-color: #f5f5f4;
@@ -6463,7 +6463,7 @@ select.form-control {
color: #64748b;
}
.list-table-btn--secondary:hover {
background-color: rgb(91.512605042%, 91.512605042%, 90.6638655462%);
background-color: rgb(233.3571428571, 233.3571428571, 231.1928571429);
}
.list-table-btn--danger {
background-color: #fbe7e9;
@@ -6471,7 +6471,7 @@ select.form-control {
color: #bb1026;
}
.list-table-btn--danger:hover {
background-color: rgb(97.081232493%, 82.487394958%, 83.9467787115%);
background-color: rgb(247.5571428571, 210.3428571429, 214.0642857143);
}
.table-pagination {
@@ -7121,7 +7121,7 @@ select.form-control {
.alert.alert-error {
background: rgba(255, 107, 107, 0.1);
border: 1px solid rgba(255, 107, 107, 0.3);
color: rgb(100%, 27.7647058824%, 27.7647058824%);
color: rgb(255, 70.8, 70.8);
align-items: center;
}
.alert.alert-error svg {
@@ -7135,7 +7135,7 @@ select.form-control {
.alert.alert-success {
background: rgba(107, 207, 127, 0.1);
border: 1px solid rgba(107, 207, 127, 0.3);
color: rgb(24.12484994%, 74.3849539816%, 34.1768707483%);
color: rgb(61.5183673469, 189.6816326531, 87.1510204082);
}
.alert.alert-info {
background: rgba(0, 73, 180, 0.1);
@@ -10979,11 +10979,54 @@ body.index-page-body {
color: #6b7280;
}
/* 하단 중앙 "API 사용 신청" 버튼 */
/* API 사용 신청은 클라이언트 신청 흐름에서 진행됨을 안내 */
.api-apply-footer {
display: flex;
align-items: center;
gap: 28px;
width: 100%;
margin-top: 48px;
padding: 32px 44px;
border: 1px solid #d5e6f7;
border-radius: 16px;
background: linear-gradient(100deg, #eaf4fd, #dfedfb);
}
.api-apply-footer__body {
flex: 1;
min-width: 0;
}
.api-apply-footer__title {
margin: 0;
color: #0d3264;
font-size: 22px;
font-weight: 700;
}
.api-apply-footer__guide {
margin: 6px 0 0;
color: #64748B;
font-size: 15px;
}
.api-apply-footer__action {
flex: none;
margin-left: auto;
padding: 15px 30px;
font-size: 15px;
white-space: nowrap;
}
@media (max-width: 768px) {
.api-apply-footer {
flex-direction: column;
align-items: flex-start;
padding: 24px;
}
.api-apply-footer__title {
font-size: 20px;
}
.api-apply-footer__action {
justify-content: center;
margin-top: 32px;
width: 100%;
margin-left: 0;
}
}
.api-overview-card {
@@ -11506,10 +11549,10 @@ body.index-page-body {
line-height: 20px;
}
.login-button:hover {
background: rgb(10.0588235294%, 27.568627451%, 68.1764705882%);
background: rgb(25.65, 70.3, 173.85);
}
.login-button:active {
background: rgb(9.5294117647%, 26.1176470588%, 64.5882352941%);
background: rgb(24.3, 66.6, 164.7);
}
.login-button:disabled {
opacity: 0.6;
@@ -11552,10 +11595,10 @@ body.index-page-body {
border-bottom-right-radius: 8px;
}
.login-links-container .link-btn:hover {
background: rgb(86.5137254902%, 89.3529411765%, 96.4509803922%);
background: rgb(220.61, 227.85, 245.95);
}
.login-links-container .link-btn:active {
background: rgb(80.4784313725%, 84.5882352941%, 94.862745098%);
background: rgb(205.22, 215.7, 241.9);
}
.login-alert {
@@ -12064,12 +12107,12 @@ body.index-page-body {
}
.auth-request-button:hover,
.auth-verify-button:hover {
background: rgb(14.6320689023%, 60.3648891332%, 92.1600879604%);
background: rgb(37.3117757009, 153.9304672897, 235.0082242991);
transform: none !important;
}
.auth-request-button:active,
.auth-verify-button:active {
background: rgb(8.3967014843%, 57.3774601429%, 91.4307494961%);
background: rgb(21.411588785, 146.3125233645, 233.148411215);
}
.auth-request-button:disabled,
.auth-verify-button:disabled {
@@ -12118,10 +12161,10 @@ body.index-page-body {
background: #f0f2f5;
}
.account-recovery-card .form-actions .cancel-button:hover {
background: rgb(88.4117647059%, 89.9568627451%, 92.2745098039%);
background: rgb(225.45, 229.39, 235.3);
}
.account-recovery-card .form-actions .cancel-button:active {
background: rgb(82.7058823529%, 85.0117647059%, 88.4705882353%);
background: rgb(210.9, 216.78, 225.6);
}
.account-recovery-card .form-actions .submit-button {
color: #FFFFFF;
@@ -12131,7 +12174,7 @@ body.index-page-body {
background: rgb(6, 54, 125);
}
.account-recovery-card .form-actions .submit-button:active {
background: rgb(0%, 25.7647058824%, 63.5294117647%);
background: rgb(0, 65.7, 162);
}
.account-recovery-card .form-actions .submit-button:disabled {
opacity: 0.6;
@@ -12367,7 +12410,7 @@ body.index-page-body {
transition: color 0.3s ease;
}
.result-info-box .info-text .info-link:hover {
color: rgb(0%, 25.7647058824%, 63.5294117647%);
color: rgb(0, 65.7, 162);
}
@media (max-width: 576px) {
.result-info-box .info-text {
@@ -17567,7 +17610,7 @@ input[type=checkbox]:checked + .custom-checkbox {
transition: background 0.2s ease;
}
.btn-copy-action:hover {
background: rgb(51.6323529412%, 78.2079831933%, 88.8382352941%);
background: rgb(131.6625, 199.4303571429, 226.5375);
}
@media (max-width: 768px) {
.btn-copy-action {
@@ -17594,7 +17637,7 @@ input[type=checkbox]:checked + .custom-checkbox {
transition: background 0.2s ease;
}
.btn-view-secret:hover {
background: rgb(12.5057724024%, 59.377680044%, 91.9648158329%);
background: rgb(31.8897196262, 151.4130841121, 234.5102803738);
}
.btn-view-secret svg {
width: 20px;
@@ -17779,7 +17822,7 @@ input[type=checkbox]:checked + .custom-checkbox {
border-radius: 8px;
}
.btn-copy-action:hover {
background: rgb(51.6323529412%, 78.2079831933%, 88.8382352941%);
background: rgb(131.6625, 199.4303571429, 226.5375);
}
.btn-view-secret {
width: 100% !important;
@@ -17795,7 +17838,7 @@ input[type=checkbox]:checked + .custom-checkbox {
height: 16px;
}
.btn-view-secret:hover {
background: rgb(12.5057724024%, 59.377680044%, 91.9648158329%);
background: rgb(31.8897196262, 151.4130841121, 234.5102803738);
}
#revealedSecretBox {
width: 100%;
@@ -18068,7 +18111,7 @@ input[type=checkbox]:checked + .custom-checkbox {
flex-shrink: 0;
}
.detail-wrap .dt-btn-copy:hover {
background: rgb(74.3529411765%, 90.2296918768%, 100%);
background: rgb(189.6, 230.0857142857, 255);
}
.detail-wrap .dt-btn-copy svg {
color: #2a69de;
@@ -18249,7 +18292,7 @@ input[type=checkbox]:checked + .custom-checkbox {
transition: background 0.2s ease;
}
.detail-wrap .dt-btn-gray:hover {
background: rgb(66.9250773994%, 71.9364293086%, 75.9455108359%);
background: rgb(170.6589473684, 183.4378947368, 193.6610526316);
}
.detail-wrap .dt-btn-red {
width: 156px;
@@ -18267,7 +18310,7 @@ input[type=checkbox]:checked + .custom-checkbox {
transition: background 0.2s ease;
}
.detail-wrap .dt-btn-red:hover {
background: rgb(100%, 27.4868759774%, 25.1921568627%);
background: rgb(255, 70.0915337423, 64.24);
}
.detail-wrap .dt-btn-blue {
width: 156px;
@@ -19841,7 +19884,7 @@ input[type=checkbox]:checked + .custom-checkbox {
}
}
.btn-inquiry-list:hover {
background: rgb(84.6615515772%, 85.8414322251%, 88.2011935209%);
background: rgb(215.8869565217, 218.8956521739, 224.9130434783);
}
.btn-inquiry-list:active {
transform: scale(0.98);
@@ -19874,7 +19917,7 @@ input[type=checkbox]:checked + .custom-checkbox {
}
}
.btn-inquiry-edit:hover {
background: rgb(0%, 27.1960784314%, 67.0588235294%);
background: rgb(0, 69.35, 171);
}
.btn-inquiry-edit:active {
transform: scale(0.98);
@@ -19907,7 +19950,7 @@ input[type=checkbox]:checked + .custom-checkbox {
}
}
.btn-inquiry-delete:hover {
background: rgb(85.4839910648%, 16.2218912882%, 22.8577810871%);
background: rgb(217.9841772152, 41.3658227848, 58.2873417722);
}
.btn-inquiry-delete:active {
transform: scale(0.98);
@@ -19979,7 +20022,7 @@ input[type=checkbox]:checked + .custom-checkbox {
margin-left: 8px;
}
.file-upload-inline .btn-remove-file-inline:hover {
background: rgb(82.1236038719%, 14.2293373045%, 20.7341772152%);
background: rgb(209.4151898734, 36.2848101266, 52.8721518987);
}
.file-upload-inline .btn-remove-file-inline svg {
width: 12px;
@@ -20011,7 +20054,7 @@ input[type=checkbox]:checked + .custom-checkbox {
}
}
.file-upload-inline .btn-file-attach:hover {
background: rgb(14.6320689023%, 60.3648891332%, 92.1600879604%);
background: rgb(37.3117757009, 153.9304672897, 235.0082242991);
}
.file-upload-inline .btn-file-attach svg {
width: 22px;
@@ -20071,7 +20114,7 @@ input[type=checkbox]:checked + .custom-checkbox {
border: none;
}
.inquiry-form-container .form-actions .btn-secondary:hover {
background: rgb(84.6615515772%, 85.8414322251%, 88.2011935209%);
background: rgb(215.8869565217, 218.8956521739, 224.9130434783);
}
.inquiry-form-container .form-actions .btn-primary {
background: #0049b4;
@@ -20079,7 +20122,7 @@ input[type=checkbox]:checked + .custom-checkbox {
border: none;
}
.inquiry-form-container .form-actions .btn-primary:hover {
background: rgb(0%, 27.1960784314%, 67.0588235294%);
background: rgb(0, 69.35, 171);
}
.inquiry-form-container .file-upload-inline .file-input-display {
min-height: 50px;
@@ -20868,7 +20911,7 @@ input[type=checkbox]:checked + .custom-checkbox {
cursor: pointer;
}
.djb-board-write-container .form-actions .btn-submit:hover {
background-color: rgb(13.193687231%, 38.3816355811%, 85.1592539455%);
background-color: rgb(33.643902439, 97.8731707317, 217.156097561);
}
@media (max-width: 768px) {
.djb-board-write-container .form-actions .btn-submit {
@@ -21404,7 +21447,7 @@ input[type=checkbox]:checked + .custom-checkbox {
transition: all 0.3s ease;
}
.org-file-remove:hover {
background: rgb(100%, 27.7647058824%, 27.7647058824%);
background: rgb(255, 70.8, 70.8);
}
.org-file-notice {
@@ -22445,7 +22488,7 @@ input[type=checkbox]:checked + .custom-checkbox {
}
.status-indicator.status-active {
background-color: rgba(107, 207, 127, 0.1);
color: rgb(32.662665066%, 78.1608643457%, 41.762304922%);
color: rgb(83.2897959184, 199.3102040816, 106.493877551);
}
.status-indicator.status-active .status-dot {
background-color: #6BCF7F;
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -26,6 +26,9 @@
var startTs = 0;
var targetOpblock = null;
// Execute/응답 렌더 사이 또는 응답 렌더 직후에 "응답지우기"를 누른 경우,
// 지연된 렌더 작업이 지워진 패널을 다시 추가하지 않도록 세대 번호를 사용한다.
var responseRevision = 0;
// Execute 클릭 후에만 true. spec 로딩 등 실행 외 응답이 responseInterceptor 로
// 들어와도 그리드를 만들지 않기 위한 게이트 (실행 전 UI 미노출 보장).
var pendingExecute = false;
@@ -266,6 +269,7 @@
} catch (err) { console.error("djb validation error:", err); }
}
responseRevision += 1;
startTs = now();
pendingExecute = true;
var grid = gridFor(targetOpblock);
@@ -277,6 +281,24 @@
scrollToGrid(targetOpblock);
}
// Swagger UI의 "Clear"(.btn-clear)는 기본 라이브 응답만 제거한다. 포탈에서
// 별도 추가한 응답 그리드도 같은 operation 안에서 함께 제거한다.
function onClearResponseClick(e) {
var t = e.target;
if (!t || !t.closest) return;
var btn = t.closest(".btn.btn-clear");
if (!btn) return;
responseRevision += 1;
pendingExecute = false;
var opblock = btn.closest(".opblock");
if (opblock) {
var grids = opblock.querySelectorAll(".djb-response-grid");
grids.forEach(function (grid) { grid.remove(); });
}
if (targetOpblock === opblock) targetOpblock = null;
}
/**
* @param {Object} res Swagger UI responseInterceptor 결과
* { status, statusText, headers, text, data, ... }
@@ -290,6 +312,7 @@
if (!pendingExecute) return;
if (res.url && res.url.indexOf("swagger.json") !== -1) return;
pendingExecute = false;
var renderRevision = responseRevision;
var ms = Math.max(0, Math.round(now() - startTs));
var status = res.status || 0;
var statusText = res.statusText || "";
@@ -301,6 +324,8 @@
// Swagger(React) 가 응답 렌더로 opblock-body 를 재조정한 뒤 append 되도록 지연.
setTimeout(function () {
// "응답지우기"가 클릭된 뒤 도착한 응답은 다시 표시하지 않는다.
if (renderRevision !== responseRevision) return;
var grid = gridFor(op);
if (!grid) return;
var kind = statusKind(status);
@@ -358,6 +383,7 @@
root.__djbRespBound = true;
// capture 단계: native 응답 흐름보다 먼저 타겟/타이머 확보 + 검증 차단
root.addEventListener("click", onExecuteClick, true);
root.addEventListener("click", onClearResponseClick, true);
// window.ui 준비 시 네트워크 오류 감시 등록
watchNetworkErrors(global.ui);
}
@@ -926,11 +926,59 @@
}
}
/* 하단 중앙 "API 사용 신청" 버튼 */
/* API 사용 신청은 클라이언트 신청 흐름에서 진행됨을 안내 */
.api-apply-footer {
display: flex;
align-items: center;
gap: 28px;
width: 100%;
margin-top: $spacing-3xl;
padding: $spacing-xl 44px;
border: 1px solid #d5e6f7;
border-radius: $border-radius-xl;
background: linear-gradient(100deg, #eaf4fd, #dfedfb);
&__body {
flex: 1;
min-width: 0;
}
&__title {
margin: 0;
color: #0d3264;
font-size: 22px;
font-weight: $font-weight-bold;
}
&__guide {
margin: 6px 0 0;
color: $text-gray;
font-size: 15px;
}
&__action {
flex: none;
margin-left: auto;
padding: 15px 30px;
font-size: 15px;
white-space: nowrap;
}
@media (max-width: $breakpoint-sm) {
flex-direction: column;
align-items: flex-start;
padding: $spacing-lg;
&__title {
font-size: $font-size-lg;
}
&__action {
justify-content: center;
margin-top: $spacing-xl;
width: 100%;
margin-left: 0;
}
}
}
.api-overview-card {
@@ -153,9 +153,15 @@
</div>
</div>
<!-- 하단 중앙 API 사용 신청 -->
<!-- API 사용 신청은 클라이언트 신청 흐름에서 진행 -->
<div class="api-apply-footer">
<button type="button" class="btn-action-primary api-apply-btn">API 사용 신청</button>
<div class="api-apply-footer__body">
<h3 class="api-apply-footer__title">
<span th:text="${apiSpecInfo.apiName}">API</span> 사용을 신청해 보세요
</h3>
<p class="api-apply-footer__guide">클라이언트 신청 단계에서 사용할 API를 선택하고 신청할 수 있습니다.</p>
</div>
<button type="button" class="btn-action-primary api-apply-btn api-apply-footer__action">API 사용 신청 하러 가기</button>
</div>
</div>