OpenAPI 스펙 내보내기(UI/DL) 기능 추가
- 내보내기 활성화 여부 설정 키 추가(EXPORT_ENABLED_KEY) - JSP/JS: 내보내기 버튼 및 다운로드 로직 연동 - YAML/JSON 포맷 저장 기능 구현
This commit is contained in:
@@ -349,8 +349,9 @@
|
||||
}
|
||||
function fieldRow(label, controlHtml, opts) {
|
||||
opts = opts || {};
|
||||
const rowSpacing = opts.bordered ? 'px-3 py-3' : 'mb-3';
|
||||
return `
|
||||
<div class="grid grid-cols-12 items-start gap-3 mb-3">
|
||||
<div class="grid grid-cols-12 items-start gap-3 ${rowSpacing}">
|
||||
<label class="col-span-3 pt-1.5 text-sm text-slate-600 ${opts.required ? 'font-medium' : ''}">
|
||||
${escapeHtml(label)}${opts.required ? ' <span class="text-red-500">*</span>' : ''}
|
||||
${opts.locked ? '<span class="ml-1 text-slate-400 text-xs">🔒</span>' : ''}
|
||||
@@ -1023,6 +1024,7 @@
|
||||
root.innerHTML =
|
||||
card(
|
||||
sectionTitle('포탈 게시 정보', '개발자포탈 공개 설정. 기존 API 스펙 화면의 값을 불러와 적용합니다.') +
|
||||
'<div class="border border-slate-200 rounded-lg divide-y divide-slate-200">' +
|
||||
fieldRow('API 그룹',
|
||||
`<div class="flex flex-col gap-1">
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
@@ -1030,9 +1032,9 @@
|
||||
<button type="button" id="btn-select-group" class="px-2 py-1 text-sm rounded border border-slate-300 hover:bg-slate-50">+ 선택</button>
|
||||
</div>
|
||||
<div class="text-xs text-slate-400">API 그룹은 apiGroupMan(API 그룹 관리)에서도 관리됩니다.</div>
|
||||
</div>`) +
|
||||
fieldRow('공개 여부', `<label class="inline-flex items-center gap-2 text-sm">${checkbox(p.displayYn === 'Y', 'data-portal-yn')} 공개</label>`) +
|
||||
fieldRow('공개 권한',
|
||||
</div>`, { bordered: true }) +
|
||||
fieldRow('포탈 게시 활성화', `<label class="inline-flex items-center gap-2 text-sm">${checkbox(p.displayYn === 'Y', 'data-portal-yn')} 활성화</label>`, { bordered: true }) +
|
||||
fieldRow('역할별 공개 제한',
|
||||
`<div class="flex flex-col gap-1">
|
||||
<div class="flex gap-4">
|
||||
<label class="inline-flex items-center gap-1 text-sm">${checkbox(!!roles.ROLE_USER, 'data-role="ROLE_USER"')} 개인사용자</label>
|
||||
@@ -1040,7 +1042,7 @@
|
||||
<label class="inline-flex items-center gap-1 text-sm">${checkbox(!!roles.ROLE_CORP_MANAGER, 'data-role="ROLE_CORP_MANAGER"')} 법인관리자</label>
|
||||
</div>
|
||||
<div class="text-xs text-slate-400">전부 선택 시 로그인 사용자 전체에게 공개됩니다.</div>
|
||||
</div>`) +
|
||||
</div>`, { bordered: true }) +
|
||||
(showOrg ? fieldRow('공개 법인',
|
||||
`<div class="flex flex-col gap-1">
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
@@ -1048,7 +1050,7 @@
|
||||
<button type="button" id="btn-select-org" class="px-2 py-1 text-sm rounded border border-slate-300 hover:bg-slate-50">+ 선택</button>
|
||||
</div>
|
||||
<div class="text-xs text-slate-400">특정 법인에게만 공개하고 싶을 때 공개 법인을 선택하시면 됩니다.</div>
|
||||
</div>`) : '') +
|
||||
</div>`, { bordered: true }) : '') +
|
||||
fieldRow('응답 유형',
|
||||
'<div>' +
|
||||
['sample', 'mock', 'gw'].map(v =>
|
||||
@@ -1060,20 +1062,23 @@
|
||||
+ `<div class="mt-1 text-xs text-slate-500">호출 방식: <b>${useProxyOn() ? '포탈 프록시 경유' : '직접 호출'}</b>`
|
||||
+ (useProxyOn() ? ' (<code class="font-mono">/api/call-api</code> 경유, 인증 헤더 자동 주입)' : ' (브라우저 → 대상 직접 호출, 대상 CORS 허용 필요)') + `</div>`) +
|
||||
(rt === 'gw' ? `<div class="mt-1 text-xs text-slate-400">GW 주소는 포탈 설정(PTL_PROPERTY, 그룹 Portal)의 <code class="font-mono">djb.gateway.base-url</code> 값을 사용합니다.</div>` : '') +
|
||||
'</div>') +
|
||||
(rt === 'mock' ? fieldRow('Mock Server URL', input(p.mockUrl || '', 'data-portal="mockUrl"', { placeholder: 'https://mock.example.com/api/v1/foo' }), { hint: 'Mock 응답을 제공할 전체 호출 URL(경로 포함). 응답 유형이 Mock 일 때 이 주소로 그대로 호출하며, 어댑터/오퍼레이션 경로를 뒤에 덧붙이지 않습니다.' }) : '')
|
||||
'</div>', { bordered: true }) +
|
||||
(rt === 'mock' ? fieldRow('Mock Server URL', input(p.mockUrl || '', 'data-portal="mockUrl"', { placeholder: 'https://mock.example.com/api/v1/foo' }), { hint: 'Mock 응답을 제공할 전체 호출 URL(경로 포함). 응답 유형이 Mock 일 때 이 주소로 그대로 호출하며, 어댑터/오퍼레이션 경로를 뒤에 덧붙이지 않습니다.', bordered: true }) : '') +
|
||||
'</div>'
|
||||
) +
|
||||
card(
|
||||
sectionTitle('설명 (API 소개 페이지)', 'API 소개 페이지에만 표시됩니다. OpenAPI 스펙 파일에는 포함되지 않습니다.') +
|
||||
'<div id="info-desc-editor">' + (state.data.info.description.value || '') + '</div>'
|
||||
) +
|
||||
// 내보내기 카드는 PTL_PROPERTY(djb.openapi.export.enabled=true) 일 때만 노출. 기본 미노출.
|
||||
((window.DJB_CTX && window.DJB_CTX.exportEnabled) ?
|
||||
card(
|
||||
sectionTitle('내보내기', 'OpenAPI 스펙을 파일로 저장합니다.') +
|
||||
`<div class="flex gap-2">
|
||||
<button data-export="yaml" class="px-4 py-2 text-sm rounded bg-brand-600 text-white hover:bg-brand-700">YAML 다운로드</button>
|
||||
<button data-export="json" class="px-4 py-2 text-sm rounded border border-brand-300 text-brand-700 hover:bg-brand-50">JSON 다운로드</button>
|
||||
</div>`
|
||||
);
|
||||
) : '');
|
||||
initInfoDescEditor(); // 설명 리치 에디터 (API 소개 페이지 전용)
|
||||
}
|
||||
|
||||
@@ -1653,7 +1658,7 @@
|
||||
$('#btn-prev').addEventListener('click', () => goStep(state.step - 1));
|
||||
$('#btn-next').addEventListener('click', () => {
|
||||
if (state.step < STEPS.length) goStep(state.step + 1);
|
||||
else toast('모든 단계를 완료했습니다. 우측 미리보기 또는 내보내기를 사용하세요.');
|
||||
else saveSpec(false); // 마지막 단계의 "완료" → 저장 동작
|
||||
});
|
||||
|
||||
$('#btn-toggle-preview').addEventListener('click', () => setPreviewVisibility(false));
|
||||
@@ -1686,7 +1691,10 @@
|
||||
toast('변경을 취소했습니다');
|
||||
});
|
||||
|
||||
$('#btn-export').addEventListener('click', () => $('#export-menu').classList.toggle('hidden'));
|
||||
// 내보내기 버튼은 djb.openapi.export.enabled=true 일 때만 DOM 에 존재 → 없으면 바인딩 생략
|
||||
var bExport = $('#btn-export');
|
||||
if (bExport) {
|
||||
bExport.addEventListener('click', () => $('#export-menu').classList.toggle('hidden'));
|
||||
document.addEventListener('click', e => {
|
||||
if (!e.target.closest('#btn-export') && !e.target.closest('#export-menu')) $('#export-menu').classList.add('hidden');
|
||||
});
|
||||
@@ -1694,6 +1702,7 @@
|
||||
const b = e.target.closest('[data-export]');
|
||||
if (b) { downloadSpec(b.dataset.export); $('#export-menu').classList.add('hidden'); }
|
||||
});
|
||||
}
|
||||
|
||||
var bSave = $('#btn-save'); if (bSave) bSave.addEventListener('click', () => saveSpec(false));
|
||||
var bRegen = $('#btn-regen'); if (bRegen) bRegen.addEventListener('click', regenerate);
|
||||
@@ -2000,8 +2009,10 @@
|
||||
state.data.msgSpec = state.data.msgSpec || { request: '', response: '' };
|
||||
if (res.apiRequestSpec) state.data.msgSpec.request = decodeEntities(res.apiRequestSpec);
|
||||
if (res.apiResponseSpec) state.data.msgSpec.response = decodeEntities(res.apiResponseSpec);
|
||||
// 저장본이 아니면(자동생성/최초 생성) 추가자료(예제·설명자료)를 프론트에서 스키마 기준 생성
|
||||
if ((res.source || '') !== 'saved') frontAutoGenerate();
|
||||
// 저장본이 아니면(최초 작성) 자동생성 여부를 confirm 으로 확인 후 진행
|
||||
if ((res.source || '') !== 'saved') {
|
||||
if (window.confirm('저장된 스펙이 없습니다. 게이트웨이 레이아웃 기반으로 예제·설명자료를 자동 생성할까요?')) frontAutoGenerate();
|
||||
}
|
||||
})
|
||||
.catch(e => console.error('loadInitialData', e));
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
gwAddress: "${gwAddress}",
|
||||
useProxy: ${useProxy},
|
||||
tokenUseProxy: ${tokenUseProxy},
|
||||
exportEnabled: ${exportEnabled},
|
||||
orgPopupUrl: "<c:url value='/onl/transaction/apim/apiSpecMan.view'/>",
|
||||
groupPopupUrl: "<c:url value='/onl/transaction/apim/djbApiSpecMan.view'/>?cmd=GROUP_POPUP",
|
||||
jsonUrl: "<c:url value='/onl/transaction/apim/djbApiSpecMan.json'/>"
|
||||
@@ -93,6 +94,8 @@
|
||||
<button id="btn-newwin" class="hidden px-3 py-1.5 text-sm rounded border border-slate-300 bg-white hover:bg-slate-50">새창으로 띄우기</button>
|
||||
<button id="btn-regen" class="px-3 py-1.5 text-sm rounded border border-slate-300 bg-white hover:bg-slate-50">자동 생성(초기화)</button>
|
||||
<button id="btn-save" class="px-3 py-1.5 text-sm rounded bg-emerald-600 text-white hover:bg-emerald-700">저장</button>
|
||||
<%-- 내보내기 버튼은 PTL_PROPERTY(djb.openapi.export.enabled=true) 일 때만 노출. 기본 미노출. --%>
|
||||
<c:if test="${exportEnabled}">
|
||||
<div class="relative">
|
||||
<button id="btn-export" class="px-3 py-1.5 text-sm rounded bg-brand-600 text-white hover:bg-brand-700">내보내기 ▾</button>
|
||||
<div id="export-menu" class="hidden absolute right-0 mt-1 w-44 bg-white border border-slate-200 rounded shadow-lg z-40">
|
||||
@@ -100,6 +103,7 @@
|
||||
<button data-export="json" class="block w-full text-left px-3 py-2 text-sm hover:bg-slate-50">JSON 다운로드</button>
|
||||
</div>
|
||||
</div>
|
||||
</c:if>
|
||||
<button id="btn-close" class="px-3 py-1.5 text-sm rounded border border-slate-300 bg-white hover:bg-slate-50">닫기</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -75,6 +75,8 @@ public class DjbApiSpecController extends OnlBaseAnnotationController {
|
||||
// 테스트베드 프록시 사용 여부(포탈 DjbTestbedGatewayProperty 와 동일 키). 마법사에 호출/인증 프록시 경유 안내용.
|
||||
private static final String USE_PROXY_KEY = "djb.gateway.use-proxy";
|
||||
private static final String TOKEN_USE_PROXY_KEY = "djb.gateway.token-use-proxy";
|
||||
// OpenAPI 스펙 내보내기(YAML/JSON 다운로드) UI 노출 여부. 기본 false(사용안함).
|
||||
private static final String EXPORT_ENABLED_KEY = "djb.openapi.export.enabled";
|
||||
// "PortalMock" 은 포탈이 요청 origin 으로 치환하는 sentinel 이라 admin 미리보기용 서버 주소로는 부적합 → 대체값 사용.
|
||||
private static final String GW_PREVIEW_FALLBACK = "http://127.0.0.1:39310";
|
||||
|
||||
@@ -91,14 +93,20 @@ public class DjbApiSpecController extends OnlBaseAnnotationController {
|
||||
// 테스트베드 프록시(/api/call-api) 사용 여부 — mock/gw 호출·인증 프록시 경유 안내 표기용
|
||||
model.addAttribute("useProxy", resolveBoolProperty(USE_PROXY_KEY, true));
|
||||
model.addAttribute("tokenUseProxy", resolveBoolProperty(TOKEN_USE_PROXY_KEY, true));
|
||||
// 내보내기 UI 노출 여부(djb.openapi.export.enabled) — 미설정 시 false(사용안함)로 생성
|
||||
model.addAttribute("exportEnabled", resolveBoolProperty(
|
||||
EXPORT_ENABLED_KEY, false, "OpenAPI 스펙 내보내기(YAML/JSON) UI 노출 여부(true/false)"));
|
||||
return "/onl/transaction/apim/djbApiSpecManPopup";
|
||||
}
|
||||
|
||||
/** 포탈 그룹 프로퍼티(불리언, 레거시 Y/N 포함)를 읽어 boolean 으로 해석. 미설정/파싱불가 시 default. */
|
||||
private boolean resolveBoolProperty(String key, boolean def) {
|
||||
return resolveBoolProperty(key, def, "테스트베드 프록시(/api/call-api) 사용 여부(true/false)");
|
||||
}
|
||||
|
||||
private boolean resolveBoolProperty(String key, boolean def, String description) {
|
||||
String v = portalPropertyService.getOrCreateProperty(
|
||||
"Portal", key, def ? "true" : "false",
|
||||
"테스트베드 프록시(/api/call-api) 사용 여부(true/false)");
|
||||
"Portal", key, def ? "true" : "false", description);
|
||||
if (v == null) {
|
||||
return def;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user