인증키 요청 조회, 인증키 상세 조회 디자인 반영
This commit is contained in:
@@ -2,313 +2,281 @@
|
||||
<html xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
|
||||
layout:decorate="~{layout/kjbank_base_layout}">
|
||||
layout:decorate="~{layout/kjbank_title_layout}">
|
||||
<head>
|
||||
<title>API 키 상세</title>
|
||||
<title>API 키 상세</title>
|
||||
</head>
|
||||
<body>
|
||||
<section layout:fragment="title">
|
||||
<div class="page-title-banner">
|
||||
<img th:src="@{/img/img_title_bg.png}" class="title-image">
|
||||
<h1>앱관리</h1>
|
||||
</div>
|
||||
</section>
|
||||
<th:block layout:fragment="contentFragment">
|
||||
<section class="apikey-detail-container">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="register-header">
|
||||
<h1>API Key 상세</h1>
|
||||
<p class="header-description">
|
||||
발급된 API Key의 상세 정보를 확인할 수 있습니다.
|
||||
</p>
|
||||
<!-- Title Bar -->
|
||||
<div class="register-title-bar">
|
||||
<h2 class="register-title">앱 정보</h2>
|
||||
<span class="register-subtitle">등록된 앱의 상세 정보를 확인할 수 있습니다.</span>
|
||||
</div>
|
||||
|
||||
<section class="apikey-detail-container">
|
||||
<!-- App Basic Info Card (Figma Design) -->
|
||||
<div class="app-info-card">
|
||||
<!-- App Icon -->
|
||||
<div class="app-info-icon">
|
||||
<img th:if="${apiKey.appIconFileId != null}"
|
||||
th:src="@{/file/download(fileSn=1,fileId=${apiKey.appIconFileId})}"
|
||||
alt="앱 아이콘"
|
||||
class="app-icon-image">
|
||||
<div th:unless="${apiKey.appIconFileId != null}" class="app-icon-placeholder">
|
||||
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
||||
<circle cx="8.5" cy="8.5" r="1.5"></circle>
|
||||
<polyline points="21 15 16 10 5 21"></polyline>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<!-- App Info -->
|
||||
<div class="app-info-content">
|
||||
<!-- Status Badge -->
|
||||
<span class="app-status-badge"
|
||||
th:classappend="${apiKey.appstatus == '1' ? 'status-approved' : 'status-inactive'}"
|
||||
th:text="${apiKey.appstatus == '1' ? '승인' : '비활성화'}">
|
||||
승인
|
||||
</span>
|
||||
<!-- App Name -->
|
||||
<h3 class="app-info-name" th:text="${apiKey.clientname}">앱 이름</h3>
|
||||
<!-- App Description -->
|
||||
<p class="app-info-description"
|
||||
th:text="${apiKey.appDescription != null ? apiKey.appDescription : '앱 설명이 없습니다.'}">
|
||||
여기에 입력한 앱 설명이 표시됩니다.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Section 1: 앱 기본 정보 -->
|
||||
<div class="detail-section">
|
||||
<h2 class="section-title">앱 정보</h2>
|
||||
<div class="detail-grid">
|
||||
<!-- Credential Info Section (Figma Design: node 985-1365) -->
|
||||
<div class="apikey-info-section">
|
||||
|
||||
<!-- App Icon -->
|
||||
<div class="detail-item full-width">
|
||||
<label class="detail-label">앱 아이콘</label>
|
||||
<div class="detail-value">
|
||||
<div class="app-icon-display" th:if="${apiKey.appIconFileId != null}">
|
||||
<img th:src="@{/file/download(fileSn=1,fileId=${apiKey.appIconFileId})}"
|
||||
alt="앱 아이콘"
|
||||
class="app-icon-image">
|
||||
</div>
|
||||
<div class="app-icon-display" th:unless="${apiKey.appIconFileId != null}">
|
||||
<div class="app-icon-placeholder">
|
||||
<span>📱</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- App Name -->
|
||||
<div class="detail-item">
|
||||
<label class="detail-label">앱 이름</label>
|
||||
<div class="detail-value" th:text="${apiKey.clientname}">
|
||||
My Application
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- App Status -->
|
||||
<div class="detail-item">
|
||||
<label class="detail-label">앱 상태</label>
|
||||
<div class="detail-value">
|
||||
<span class="status-indicator"
|
||||
th:classappend="${apiKey.appstatus == '1' ? 'status-active' : 'status-inactive'}">
|
||||
<span class="status-dot"></span>
|
||||
<span th:text="${apiKey.appstatus == '1' ? '활성화' : '비활성화'}">활성화</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- App Description -->
|
||||
<div class="detail-item full-width">
|
||||
<label class="detail-label">앱 설명</label>
|
||||
<div class="detail-value" th:text="${apiKey.appDescription != null ? apiKey.appDescription : '설명이 없습니다.'}">
|
||||
This is a sample app description.
|
||||
</div>
|
||||
</div>
|
||||
<!-- Client ID -->
|
||||
<div class="info-row info-row-with-action">
|
||||
<label class="info-label">Client ID</label>
|
||||
<div class="info-value">
|
||||
<div class="info-value-box with-copy" th:text="${apiKey.clientid}">K00000001</div>
|
||||
<button type="button" class="btn-copy-action" th:data-secret="${apiKey.clientid}" onclick="copyToClipboardFromButton(this)">
|
||||
+ 복사
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Client Secret -->
|
||||
<div class="info-row">
|
||||
<label class="info-label">Client Secret</label>
|
||||
<div class="info-value">
|
||||
<!-- View Button (shown initially) -->
|
||||
<button type="button" class="btn-view-secret" id="hiddenSecretBox" onclick="showPasswordPrompt()">
|
||||
<span>조회</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<path d="m21 21-4.35-4.35"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<!-- Actual Secret (hidden initially) -->
|
||||
<div id="revealedSecretBox" style="display: none;">
|
||||
<div class="info-row info-row-with-action" style="margin-bottom: 0;">
|
||||
<div class="info-value" style="display: flex; gap: 16px; align-items: center;">
|
||||
<div class="info-value-box with-copy" th:text="${apiKey.clientsecret}">secret-key-67890</div>
|
||||
<button type="button" class="btn-copy-action" th:data-secret="${apiKey.clientsecret}" onclick="copyToClipboardFromButton(this)">
|
||||
+ 복사
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Section 2: 인증 상세 정보 -->
|
||||
<div class="detail-section">
|
||||
<h2 class="section-title">인증 정보</h2>
|
||||
<div class="detail-grid">
|
||||
<!-- Callback URL -->
|
||||
<div class="info-row">
|
||||
<label class="info-label">Callback URL</label>
|
||||
<div class="info-value">
|
||||
<div class="info-value-box" th:text="${apiKey.redirecturi != null ? apiKey.redirecturi : '설정되지 않음'}">
|
||||
http://www.abcd.co.kr
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Client ID -->
|
||||
<div class="detail-item">
|
||||
<label class="detail-label">Client ID</label>
|
||||
<div class="detail-value">
|
||||
<div class="secret-box">
|
||||
<code class="credential-code" th:text="${apiKey.clientid}">client-id-12345</code>
|
||||
<button type="button" class="btn-copy" th:data-secret="${apiKey.clientid}" onclick="copyToClipboardFromButton(this)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||||
</svg>
|
||||
복사
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Client Secret -->
|
||||
<div class="detail-item">
|
||||
<label class="detail-label">Client Secret</label>
|
||||
<div class="detail-value">
|
||||
<!-- Hidden Secret (shown initially) -->
|
||||
<div class="secret-box" id="hiddenSecretBox">
|
||||
<code class="credential-code">••••••••••••••••••••</code>
|
||||
<button type="button" class="btn-copy" onclick="showPasswordPrompt()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
</svg>
|
||||
보기
|
||||
</button>
|
||||
</div>
|
||||
<!-- Actual Secret (hidden initially) -->
|
||||
<div class="secret-box" id="revealedSecretBox" style="display: none;">
|
||||
<code class="credential-code" th:text="${apiKey.clientsecret}">secret-key-67890</code>
|
||||
<button type="button" class="btn-copy" th:data-secret="${apiKey.clientsecret}" onclick="copyToClipboardFromButton(this)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||||
</svg>
|
||||
복사
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Callback URL -->
|
||||
<div class="detail-item full-width">
|
||||
<label class="detail-label">Callback URL</label>
|
||||
<div class="detail-value" th:text="${apiKey.redirecturi != null ? apiKey.redirecturi : '설정되지 않음'}">
|
||||
https://example.com/callback
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- IP Whitelist -->
|
||||
<div class="detail-item full-width" th:if="${apiKey.allowedips != null}">
|
||||
<label class="detail-label">IP 화이트리스트</label>
|
||||
<div class="detail-value">
|
||||
<div class="ip-list">
|
||||
<span class="ip-tag" th:each="ip : ${#strings.arraySplit(apiKey.allowedips, ',')}">
|
||||
[[${ip.trim()}]]
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 등록된 API -->
|
||||
<div class="detail-item full-width">
|
||||
<label class="detail-label">등록된 API</label>
|
||||
<div class="detail-value">
|
||||
<!-- API List -->
|
||||
<div class="api-detail-list">
|
||||
<div class="api-list-item" th:each="api : ${apiKey.apiList}">
|
||||
<span class="api-method method-other">
|
||||
API
|
||||
</span>
|
||||
<span class="api-name" th:text="${api.apiDesc}">API Name</span>
|
||||
<span class="api-status status-active">활성</span>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div class="empty-state" th:if="${apiKey.apiList == null or apiKey.apiList.isEmpty()}">
|
||||
<div class="empty-icon">📦</div>
|
||||
<p>등록된 API가 없습니다.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modified Date -->
|
||||
<div class="detail-item">
|
||||
<label class="detail-label">최종 수정일자</label>
|
||||
<div class="detail-value" th:text="${#temporals.format(apiKey.modifiedon, 'yyyy.MM.dd HH:mm')}">
|
||||
2024.01.01 12:00
|
||||
</div>
|
||||
</div>
|
||||
<!-- IP Whitelist -->
|
||||
<div class="info-row">
|
||||
<label class="info-label">화이트리스트</label>
|
||||
<div class="info-value">
|
||||
<div class="info-value-box" th:text="${apiKey.allowedips != null ? apiKey.allowedips : '설정되지 않음'}">
|
||||
10.0.0.1, 10.0.0.2, 192.168.123.1
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Registered APIs -->
|
||||
<div class="info-row info-row-vertical">
|
||||
<label class="info-label">API 목록</label>
|
||||
<div class="info-value">
|
||||
<div class="api-list-box">
|
||||
<div class="api-list-item-figma" th:each="api : ${apiKey.apiList}">
|
||||
<span class="api-item-name" th:text="${api.apiDesc}">추가된 API 1</span>
|
||||
<span class="api-item-status">승인</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="form-actions">
|
||||
<a sec:authorize="hasRole('ROLE_API_KEY_REQUEST')"
|
||||
class="btn btn-primary"
|
||||
th:href="@{/myapikey/modify/step1(clientId=${apiKey.clientid})}">
|
||||
수정
|
||||
</a>
|
||||
<button type="button"
|
||||
sec:authorize="hasRole('ROLE_API_KEY_REQUEST')"
|
||||
class="btn btn-danger"
|
||||
th:data-client-id="${apiKey.clientid}"
|
||||
onclick="deleteApiKeyFromButton(this)">
|
||||
인증키 삭제
|
||||
</button>
|
||||
<a th:href="@{/myapikey}" class="btn btn-secondary">
|
||||
목록으로
|
||||
</a>
|
||||
<!-- Empty State -->
|
||||
<div class="empty-state" th:if="${apiKey.apiList == null or apiKey.apiList.isEmpty()}" style="padding: 24px;">
|
||||
<p>등록된 API가 없습니다.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
<!-- Modified Date -->
|
||||
<div class="info-row">
|
||||
<label class="info-label">최종 수정일자</label>
|
||||
<div class="info-value">
|
||||
<div class="info-value-box" th:text="${#temporals.format(apiKey.modifiedon, 'yyyy.MM.dd')}">
|
||||
2025.11.22
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="form-actions-center">
|
||||
<a th:href="@{/myapikey}" class="btn btn-submit btn-secondary">
|
||||
목록
|
||||
</a>
|
||||
<button type="button"
|
||||
sec:authorize="hasRole('ROLE_API_KEY_REQUEST')"
|
||||
class="btn btn-submit btn-danger"
|
||||
th:data-client-id="${apiKey.clientid}"
|
||||
onclick="deleteApiKeyFromButton(this)">
|
||||
인증키 삭제
|
||||
</button>
|
||||
<a sec:authorize="hasRole('ROLE_API_KEY_REQUEST')"
|
||||
class="btn btn-submit btn-primary"
|
||||
th:href="@{/myapikey/modify/step1(clientId=${apiKey.clientid})}">
|
||||
수정
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
</th:block>
|
||||
|
||||
<th:block layout:fragment="contentScript">
|
||||
<script th:inline="javascript">
|
||||
/*<![CDATA[*/
|
||||
<script th:inline="javascript">
|
||||
/*<![CDATA[*/
|
||||
|
||||
// Show password prompt for viewing client secret
|
||||
function showPasswordPrompt() {
|
||||
customPopups.showPasswordInput({
|
||||
title: 'Client Secret 조회',
|
||||
message: '보안을 위해 비밀번호를 입력해주세요.',
|
||||
onConfirm: function(password) {
|
||||
// Verify password via AJAX
|
||||
$.ajax({
|
||||
url: /*[[@{/myapikey/verify-password}]]*/ '/myapikey/verify-password',
|
||||
type: 'POST',
|
||||
data: { password: password },
|
||||
headers: {
|
||||
'X-XSRF-TOKEN': /*[[${_csrf.token}]]*/ 'token'
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
// Hide password popup
|
||||
customPopups.hidePasswordInput();
|
||||
// Show password prompt for viewing client secret
|
||||
function showPasswordPrompt() {
|
||||
customPopups.showPasswordInput({
|
||||
title: 'Client Secret 조회',
|
||||
message: '보안을 위해 비밀번호를 입력해주세요.',
|
||||
onConfirm: function(password) {
|
||||
// Verify password via AJAX
|
||||
$.ajax({
|
||||
url: /*[[@{/myapikey/verify-password}]]*/ '/myapikey/verify-password',
|
||||
type: 'POST',
|
||||
data: {password: password},
|
||||
headers: {
|
||||
'X-XSRF-TOKEN': /*[[${_csrf.token}]]*/ 'token'
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
// Hide password popup
|
||||
customPopups.hidePasswordInput();
|
||||
|
||||
// Reveal the client secret
|
||||
$('#hiddenSecretBox').hide();
|
||||
$('#revealedSecretBox').fadeIn(300);
|
||||
} else {
|
||||
// Show error message
|
||||
customPopups.showPasswordError(response.message || '비밀번호가 일치하지 않습니다.');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
customPopups.showPasswordError('오류가 발생했습니다. 다시 시도해주세요.');
|
||||
}
|
||||
});
|
||||
},
|
||||
onCancel: function() {
|
||||
// User cancelled - do nothing
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Copy to clipboard function - called from button with data-secret attribute
|
||||
function copyToClipboardFromButton(button) {
|
||||
var text = $(button).data('secret');
|
||||
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(text).then(function() {
|
||||
alert('클립보드에 복사되었습니다.');
|
||||
}).catch(function(err) {
|
||||
console.error('Failed to copy:', err);
|
||||
fallbackCopy(text);
|
||||
});
|
||||
} else {
|
||||
fallbackCopy(text);
|
||||
// Reveal the client secret
|
||||
$('#hiddenSecretBox').hide();
|
||||
$('#revealedSecretBox').fadeIn(300);
|
||||
} else {
|
||||
// Show error message
|
||||
customPopups.showPasswordError(response.message || '비밀번호가 일치하지 않습니다.');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
customPopups.showPasswordError('오류가 발생했습니다. 다시 시도해주세요.');
|
||||
}
|
||||
});
|
||||
},
|
||||
onCancel: function() {
|
||||
// User cancelled - do nothing
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function fallbackCopy(text) {
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = text;
|
||||
textArea.style.position = 'fixed';
|
||||
textArea.style.left = '-999999px';
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
alert('클립보드에 복사되었습니다.');
|
||||
} catch (err) {
|
||||
alert('복사에 실패했습니다.');
|
||||
}
|
||||
document.body.removeChild(textArea);
|
||||
// Copy to clipboard function - called from button with data-secret attribute
|
||||
function copyToClipboardFromButton(button) {
|
||||
var text = $(button).data('secret');
|
||||
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(text).then(function() {
|
||||
alert('클립보드에 복사되었습니다.');
|
||||
}).catch(function(err) {
|
||||
console.error('Failed to copy:', err);
|
||||
fallbackCopy(text);
|
||||
});
|
||||
} else {
|
||||
fallbackCopy(text);
|
||||
}
|
||||
}
|
||||
|
||||
function fallbackCopy(text) {
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = text;
|
||||
textArea.style.position = 'fixed';
|
||||
textArea.style.left = '-999999px';
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
alert('클립보드에 복사되었습니다.');
|
||||
} catch (err) {
|
||||
alert('복사에 실패했습니다.');
|
||||
}
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
|
||||
// Delete API Key function - called from button with data-client-id attribute
|
||||
function deleteApiKeyFromButton(button) {
|
||||
var clientId = $(button).data('client-id');
|
||||
|
||||
if (!confirm('정말로 이 인증키를 삭제하시겠습니까?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$('.loading-overlay').show();
|
||||
|
||||
const requestData = {
|
||||
clientId: clientId
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: '/myapikey/api_key_delete',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(requestData),
|
||||
headers: {
|
||||
'X-XSRF-TOKEN': /*[[${_csrf.token}]]*/ 'token'
|
||||
}
|
||||
}).done(function(response) {
|
||||
alert(response.msg || 'API Key가 삭제되었습니다.');
|
||||
window.location.href = /*[[@{/myapikey}]]*/ '/myapikey';
|
||||
}).fail(function(jqXHR, textStatus, errorThrown) {
|
||||
alert('API 삭제 요청 중 오류가 발생했습니다: ' + errorThrown);
|
||||
}).always(function() {
|
||||
$('.loading-overlay').hide();
|
||||
});
|
||||
}
|
||||
|
||||
// Delete API Key function - called from button with data-client-id attribute
|
||||
function deleteApiKeyFromButton(button) {
|
||||
var clientId = $(button).data('client-id');
|
||||
|
||||
if (!confirm('정말로 이 인증키를 삭제하시겠습니까?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$('.loading-overlay').show();
|
||||
|
||||
const requestData = {
|
||||
clientId: clientId
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: '/myapikey/api_key_delete',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(requestData),
|
||||
headers: {
|
||||
'X-XSRF-TOKEN': /*[[${_csrf.token}]]*/ 'token'
|
||||
}
|
||||
}).done(function (response) {
|
||||
alert(response.msg || 'API Key가 삭제되었습니다.');
|
||||
window.location.href = /*[[@{/myapikey}]]*/ '/myapikey';
|
||||
}).fail(function (jqXHR, textStatus, errorThrown) {
|
||||
alert('API 삭제 요청 중 오류가 발생했습니다: ' + errorThrown);
|
||||
}).always(function () {
|
||||
$('.loading-overlay').hide();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*]]>*/
|
||||
</script>
|
||||
/*]]>*/
|
||||
</script>
|
||||
|
||||
</th:block>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user