사용자 Secret 관리 기능 추가:
- 사용자 지정 Secret 필드 추가(WebhookRequest, DTO, Mapper) - 사용자 Secret 평문 조회/갱신 로직 및 비밀번호 재인증 처리 - Secret 입력 UI 개선 및 스타일 수정
This commit is contained in:
@@ -8,11 +8,13 @@
|
||||
* - 기선택: window.API_SELECTOR_SELECTED / 목록 URL: window.API_SELECTOR_LIST_URL (fragment 인라인 주입)
|
||||
* - "이전" 버튼: 호출 페이지의 #btnPrevStep (없으면 스킵)
|
||||
* - 카트/모달: fragment `apiSelectorPopups` 를 pagePopups 슬롯에서 호출(body 직속)
|
||||
* - 페이징: #apiPagination (PAGE_SIZE 건/페이지) — 카테고리/검색은 재조회 없이 클라이언트에서 처리
|
||||
*
|
||||
* design(figma s2) 인라인 스크립트 대비 패치 3건:
|
||||
* design(figma s2) 인라인 스크립트 대비 패치 4건:
|
||||
* 1) 모달 열 때마다 updateModalList() 재빌드 — 세션 복원 직후(카드 렌더 전) 빈 모달 방지
|
||||
* 2) 모달 리스트를 DOM 체크박스가 아닌 selectedApis Set 기준으로 생성 — 미렌더/타 카테고리 누락 방지
|
||||
* 3) 제출/이전 시 DOM에 없는 선택분을 hidden input으로 주입 — 카테고리 필터 상태 전송 유실 방지
|
||||
* 4) 클라이언트 페이징 — 카드는 현재 페이지분만 DOM 렌더, 검색/전체선택/모달은 필터된 전체 목록 기준으로 동작
|
||||
*/
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const form = document.getElementById('apiSelectorForm');
|
||||
@@ -20,16 +22,21 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
return; // 모듈 미사용 페이지
|
||||
}
|
||||
|
||||
const PAGE_SIZE = 12;
|
||||
|
||||
// DOM Elements
|
||||
const searchInput = document.getElementById('apiSearch');
|
||||
const menuTitles = document.querySelectorAll('.s2-category-tab');
|
||||
const apiCardGrid = document.getElementById('apiCardGrid');
|
||||
const loadingState = document.getElementById('loadingState');
|
||||
const emptyState = document.getElementById('emptyState');
|
||||
const paginationEl = document.getElementById('apiPagination');
|
||||
|
||||
let currentFilter = ''; // Empty string means "all"
|
||||
let currentServiceName = '전체';
|
||||
let allApis = [];
|
||||
let allApis = []; // 현재 카테고리 조회 결과 전체
|
||||
let filteredApis = []; // allApis 에 검색어까지 적용한 결과(페이징 대상)
|
||||
let currentPage = 1;
|
||||
let selectedApis = new Set();
|
||||
|
||||
// Restore selected APIs from session (fragment 인라인 주입)
|
||||
@@ -44,8 +51,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
function loadApis(groupId) {
|
||||
loadingState.style.display = 'block';
|
||||
emptyState.style.display = 'none';
|
||||
|
||||
document.querySelectorAll('.s2-api-card').forEach(card => card.remove());
|
||||
clearCards();
|
||||
|
||||
const baseUrl = window.API_SELECTOR_LIST_URL || '/apis/for_request';
|
||||
let url = baseUrl;
|
||||
@@ -56,16 +62,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
fetch(url).then(response => response.json()).then(apis => {
|
||||
allApis = apis;
|
||||
loadingState.style.display = 'none';
|
||||
|
||||
if (apis.length === 0) {
|
||||
emptyState.style.display = 'block';
|
||||
document.getElementById('apiResultCount').textContent = '0';
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('apiResultCount').textContent = apis.length;
|
||||
renderApiCards(apis);
|
||||
updateSelectAllUI();
|
||||
applySearch();
|
||||
}).catch(error => {
|
||||
console.error('Failed to load APIs:', error);
|
||||
loadingState.style.display = 'none';
|
||||
@@ -73,10 +70,49 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
emptyState.querySelector('p').textContent = '다시 시도해주세요.';
|
||||
emptyState.style.display = 'block';
|
||||
document.getElementById('apiResultCount').textContent = '0';
|
||||
renderPagination(0);
|
||||
});
|
||||
}
|
||||
|
||||
// Render API cards
|
||||
// 렌더된 카드만 제거(로딩/빈 상태 엘리먼트는 유지)
|
||||
function clearCards() {
|
||||
document.querySelectorAll('.s2-api-card').forEach(card => card.remove());
|
||||
}
|
||||
|
||||
// 검색어 기준으로 allApis → filteredApis 재계산 후 1페이지부터 렌더
|
||||
function applySearch() {
|
||||
const term = searchInput ? searchInput.value.toLowerCase().trim() : '';
|
||||
filteredApis = !term ? allApis : allApis.filter(function(api) {
|
||||
const name = (api.apiName || '').toLowerCase();
|
||||
const desc = (api.apiSimpleDescription || '').toLowerCase();
|
||||
return name.includes(term) || desc.includes(term);
|
||||
});
|
||||
goToPage(1);
|
||||
}
|
||||
|
||||
// 지정 페이지로 이동 — 해당 페이지분만 렌더(재조회 없음)
|
||||
function goToPage(page) {
|
||||
const totalPages = Math.max(1, Math.ceil(filteredApis.length / PAGE_SIZE));
|
||||
currentPage = Math.min(Math.max(1, page), totalPages);
|
||||
|
||||
clearCards();
|
||||
document.getElementById('apiResultCount').textContent = filteredApis.length;
|
||||
|
||||
if (filteredApis.length === 0) {
|
||||
emptyState.style.display = 'block';
|
||||
renderPagination(0);
|
||||
updateSelectAllUI();
|
||||
return;
|
||||
}
|
||||
emptyState.style.display = 'none';
|
||||
|
||||
const start = (currentPage - 1) * PAGE_SIZE;
|
||||
renderApiCards(filteredApis.slice(start, start + PAGE_SIZE));
|
||||
renderPagination(filteredApis.length);
|
||||
updateSelectAllUI();
|
||||
}
|
||||
|
||||
// Render API cards (현재 페이지분)
|
||||
function renderApiCards(apis) {
|
||||
const fragment = document.createDocumentFragment();
|
||||
|
||||
@@ -207,24 +243,25 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
updateSelectAllCheckboxState();
|
||||
}
|
||||
|
||||
// Update select all checkbox state based on visible cards
|
||||
// Update select all checkbox state — 현재 페이지가 아닌 필터된 전체 목록 기준(페이징 무관)
|
||||
function updateSelectAllCheckboxState() {
|
||||
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
||||
const visibleCards = Array.from(document.querySelectorAll('.s2-api-card')).filter(card => card.style.display !== 'none');
|
||||
if (!selectAllCheckbox) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (visibleCards.length === 0) {
|
||||
if (filteredApis.length === 0) {
|
||||
selectAllCheckbox.checked = false;
|
||||
selectAllCheckbox.indeterminate = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const visibleCheckboxes = visibleCards.map(card => card.querySelector('.s2-api-checkbox'));
|
||||
const checkedCount = visibleCheckboxes.filter(cb => cb.checked).length;
|
||||
const checkedCount = filteredApis.filter(api => selectedApis.has(api.apiId)).length;
|
||||
|
||||
if (checkedCount === 0) {
|
||||
selectAllCheckbox.checked = false;
|
||||
selectAllCheckbox.indeterminate = false;
|
||||
} else if (checkedCount === visibleCheckboxes.length) {
|
||||
} else if (checkedCount === filteredApis.length) {
|
||||
selectAllCheckbox.checked = true;
|
||||
selectAllCheckbox.indeterminate = false;
|
||||
} else {
|
||||
@@ -233,7 +270,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
}
|
||||
|
||||
// Update modal selected APIs list — selectedApis Set 기준 (패치 2)
|
||||
// Update modal selected APIs list — selectedApis Set 기준(패치 2), 이름은 allApis 우선 조회(패치 4)
|
||||
function updateModalList() {
|
||||
const modalSelectedList = document.getElementById('modalSelectedList');
|
||||
modalSelectedList.innerHTML = '';
|
||||
@@ -244,8 +281,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
|
||||
selectedApis.forEach(function(apiId) {
|
||||
const apiData = allApis.find(a => a.apiId === apiId);
|
||||
const card = document.querySelector('.s2-api-card[data-api-id="' + apiId + '"]');
|
||||
const apiName = card ? card.querySelector('.s2-api-card-title').textContent : apiId;
|
||||
const apiName = apiData ? apiData.apiName
|
||||
: (card ? card.querySelector('.s2-api-card-title').textContent : apiId);
|
||||
|
||||
const apiPill = document.createElement('div');
|
||||
apiPill.className = 's2-api-pill';
|
||||
@@ -270,28 +309,69 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
}
|
||||
|
||||
// Search functionality
|
||||
// Pagination 컨트롤 렌더 — fragment/pagination.html 과 동일 마크업/클래스 재사용(전역 _pagination.scss 적용)
|
||||
function renderPagination(totalItems) {
|
||||
if (!paginationEl) {
|
||||
return;
|
||||
}
|
||||
paginationEl.innerHTML = '';
|
||||
|
||||
const totalPages = Math.ceil(totalItems / PAGE_SIZE);
|
||||
if (totalPages <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ICON_FIRST = '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M0 19V5H2.30769V19H0ZM15 19L4.61538 12L15 5V19Z" fill="currentColor"/><path d="M24 19L15 12L24 5V19Z" fill="currentColor"/></svg><span class="blind">처음 페이지</span>';
|
||||
const ICON_PREV = '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M16 5L16 19L5 12L16 5Z" fill="currentColor"/></svg><span class="blind">이전 페이지</span>';
|
||||
const ICON_NEXT = '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8 19V5L19 12L8 19Z" fill="currentColor"/></svg><span class="blind">다음 페이지</span>';
|
||||
const ICON_LAST = '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M24 5L24 19L21.6923 19L21.6923 5L24 5ZM9 5L19.3846 12L9 19L9 5Z" fill="currentColor"/><path d="M1.22392e-06 5L9 12L0 19L1.22392e-06 5Z" fill="currentColor"/></svg><span class="blind">마지막 페이지</span>';
|
||||
|
||||
function navLink(cls, iconHtml, targetPage, disabled) {
|
||||
const a = document.createElement('a');
|
||||
a.href = '#';
|
||||
a.className = cls + (disabled ? ' disabled' : '');
|
||||
a.innerHTML = iconHtml;
|
||||
if (!disabled) {
|
||||
a.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
goToPage(targetPage);
|
||||
});
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
function numLink(p) {
|
||||
const a = document.createElement('a');
|
||||
a.href = '#';
|
||||
const isCurrent = p === currentPage;
|
||||
a.className = 'page-num' + (isCurrent ? ' page-current' : '');
|
||||
a.textContent = String(p);
|
||||
if (!isCurrent) {
|
||||
a.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
goToPage(p);
|
||||
});
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
paginationEl.appendChild(navLink('page-first', ICON_FIRST, 1, currentPage === 1));
|
||||
paginationEl.appendChild(navLink('page-prev', ICON_PREV, currentPage - 1, currentPage === 1));
|
||||
|
||||
const windowStart = Math.max(1, Math.min(currentPage - 2, totalPages - 4));
|
||||
const windowEnd = Math.min(totalPages, windowStart + 4);
|
||||
for (let p = Math.max(1, windowStart); p <= windowEnd; p++) {
|
||||
paginationEl.appendChild(numLink(p));
|
||||
}
|
||||
|
||||
paginationEl.appendChild(navLink('page-next', ICON_NEXT, currentPage + 1, currentPage === totalPages));
|
||||
paginationEl.appendChild(navLink('page-last', ICON_LAST, totalPages, currentPage === totalPages));
|
||||
}
|
||||
|
||||
// Search functionality — 클라이언트 필터(재조회 없음), 필터 변경 시 1페이지로 리셋
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('input', function() {
|
||||
const searchTerm = this.value.toLowerCase();
|
||||
|
||||
const apiCards = document.querySelectorAll('.s2-api-card');
|
||||
let visibleCount = 0;
|
||||
apiCards.forEach(function(card) {
|
||||
const apiName = card.getAttribute('data-name');
|
||||
const apiDesc = card.getAttribute('data-desc');
|
||||
const matchesSearch = apiName.includes(searchTerm) || apiDesc.includes(searchTerm);
|
||||
|
||||
if (matchesSearch) {
|
||||
card.style.display = '';
|
||||
visibleCount++;
|
||||
} else {
|
||||
card.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('apiResultCount').textContent = visibleCount;
|
||||
updateSelectAllCheckboxState();
|
||||
applySearch();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -307,11 +387,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
currentFilter = groupId;
|
||||
currentServiceName = this.textContent.trim();
|
||||
|
||||
loadApis(groupId);
|
||||
|
||||
if (searchInput) {
|
||||
searchInput.value = '';
|
||||
}
|
||||
loadApis(groupId);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -398,18 +477,26 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
});
|
||||
|
||||
// Select All checkbox event
|
||||
// Select All checkbox event — 현재 페이지가 아닌 필터된 전체 목록 대상(페이징 무관)
|
||||
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
||||
if (selectAllCheckbox) {
|
||||
selectAllCheckbox.addEventListener('change', function() {
|
||||
const isChecked = this.checked;
|
||||
const visibleCards = Array.from(document.querySelectorAll('.s2-api-card')).filter(card => card.style.display !== 'none');
|
||||
|
||||
visibleCards.forEach(function(card) {
|
||||
filteredApis.forEach(function(api) {
|
||||
if (isChecked) {
|
||||
selectedApis.add(api.apiId);
|
||||
} else {
|
||||
selectedApis.delete(api.apiId);
|
||||
}
|
||||
});
|
||||
|
||||
// 현재 페이지에 실제 렌더된 카드만 체크 상태 동기화
|
||||
document.querySelectorAll('.s2-api-card').forEach(function(card) {
|
||||
const checkbox = card.querySelector('.s2-api-checkbox');
|
||||
if (checkbox) {
|
||||
checkbox.checked = isChecked;
|
||||
updateCardSelection(checkbox);
|
||||
card.classList.toggle('selected', isChecked);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user