API 그룹 팝업 JSP 파일 추가 - 예제 코드 및 UI 구조 작성

Summernote 에디터 스타일 CSS 추가 - 텍스트 및 컴포넌트 스타일 정의
OpenAPI POC JS 파일 추가 - API 스펙 빌더 및 스텝별 렌더 구현
This commit is contained in:
Rinjae
2026-07-13 14:25:09 +09:00
parent d7dcf9a81c
commit cbf3066a56
27 changed files with 4405 additions and 91 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,334 @@
/**
* Editor Content Styles (editor-content.css)
*
* Summernote 에디터로 작성된 HTML 콘텐츠의 공통 스타일
* 관리자포탈/개발자포탈 양쪽에서 동일하게 사용
*
* 사용법:
* - 관리자포탈: Summernote .note-editable에 .editor-content 클래스 추가
* - 개발자포탈: 콘텐츠 wrapper에 .editor-content 클래스 추가
*
* @version 1.0.0
* @date 2025-12-30
*/
/* ==========================================================================
CSS Reset + 기본 설정
========================================================================== */
.editor-content {
all: revert;
font-family: 'Noto Sans KR', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
font-size: 16px !important;
font-weight: 400 !important;
line-height: 1.8 !important;
color: #1A1A2E !important;
word-wrap: break-word;
text-align: left !important;
}
/* ==========================================================================
헤딩 (Headings)
========================================================================== */
.editor-content h1 {
font-size: 20px !important;
font-weight: 700 !important;
margin: 32px 0 16px !important;
line-height: 1.4 !important;
color: #1A1A2E !important;
}
.editor-content h1:first-child {
margin-top: 0 !important;
}
.editor-content h2 {
font-size: 18px !important;
font-weight: 700 !important;
margin: 24px 0 8px !important;
line-height: 1.4 !important;
color: #1A1A2E !important;
}
.editor-content h3 {
font-size: 16px !important;
font-weight: 600 !important;
margin: 16px 0 8px !important;
line-height: 1.4 !important;
color: #1A1A2E !important;
}
.editor-content h4,
.editor-content h5,
.editor-content h6 {
font-size: 16px !important;
font-weight: 600 !important;
margin: 16px 0 8px !important;
line-height: 1.4 !important;
color: #1A1A2E !important;
}
/* ==========================================================================
단락 (Paragraphs)
========================================================================== */
.editor-content p {
margin-bottom: 16px !important;
line-height: 1.8 !important;
font-size: 16px !important;
}
.editor-content p:last-child {
margin-bottom: 0 !important;
}
/* ==========================================================================
리스트 (Lists) - 글로벌 reset 대응
========================================================================== */
.editor-content ul {
list-style-type: disc !important;
padding-left: 32px !important;
margin: 16px 0 !important;
}
.editor-content ol {
list-style-type: decimal !important;
padding-left: 32px !important;
margin: 16px 0 !important;
}
.editor-content li {
margin-bottom: 8px !important;
line-height: 1.8 !important;
font-size: 16px !important;
}
.editor-content li:last-child {
margin-bottom: 0 !important;
}
/* 중첩 리스트 */
.editor-content ul ul {
list-style-type: circle !important;
margin: 8px 0 !important;
}
.editor-content ul ul ul {
list-style-type: square !important;
}
.editor-content ol ol {
list-style-type: lower-alpha !important;
margin: 8px 0 !important;
}
.editor-content ol ol ol {
list-style-type: lower-roman !important;
}
/* ==========================================================================
테이블 (Tables)
========================================================================== */
.editor-content table {
width: 100% !important;
border-collapse: collapse !important;
margin: 24px 0 !important;
border: 1px solid #E2E8F0 !important;
border-radius: 8px !important;
overflow: hidden !important;
font-size: 14px !important;
}
.editor-content th,
.editor-content td {
border: 1px solid #E2E8F0 !important;
padding: 8px 16px !important;
text-align: left !important;
vertical-align: top !important;
}
.editor-content th {
background: #EFF6FF !important;
font-weight: 600 !important;
color: #1A1A2E !important;
}
.editor-content tr:hover {
background: #F8FAFC !important;
}
/* ==========================================================================
링크 (Links)
========================================================================== */
.editor-content a {
color: #0049b4 !important;
text-decoration: underline !important;
transition: color 0.3s ease !important;
}
.editor-content a:hover {
color: #003080 !important;
}
/* ==========================================================================
인용문 (Blockquote)
========================================================================== */
.editor-content blockquote {
border-left: 4px solid #0049b4 !important;
padding: 16px 24px !important;
margin: 24px 0 !important;
background: #F8FAFC !important;
font-style: italic !important;
color: #64748B !important;
border-radius: 0 8px 8px 0 !important;
}
.editor-content blockquote p {
margin-bottom: 0 !important;
}
/* ==========================================================================
이미지 (Images)
========================================================================== */
.editor-content img {
max-width: 100% !important;
height: auto !important;
border-radius: 8px !important;
margin: 16px 0 !important;
display: block !important;
}
/* ==========================================================================
코드 (Code)
========================================================================== */
.editor-content code {
background: #F8FAFC !important;
padding: 2px 6px !important;
border-radius: 4px !important;
font-family: 'Fira Code', 'Courier New', monospace !important;
font-size: 0.9em !important;
color: #0049b4 !important;
}
.editor-content pre {
background: #F8FAFC !important;
border: 1px solid #E2E8F0 !important;
border-radius: 8px !important;
padding: 16px !important;
overflow-x: auto !important;
margin: 16px 0 !important;
}
.editor-content pre code {
background: transparent !important;
padding: 0 !important;
color: #1A1A2E !important;
}
/* ==========================================================================
구분선 (Horizontal Rule)
========================================================================== */
.editor-content hr {
border: none !important;
border-top: 1px solid #E2E8F0 !important;
margin: 32px 0 !important;
}
/* ==========================================================================
텍스트 강조 (Text Emphasis)
========================================================================== */
.editor-content strong,
.editor-content b {
font-weight: 700 !important;
}
.editor-content em,
.editor-content i {
font-style: italic !important;
}
.editor-content u {
text-decoration: underline !important;
}
.editor-content s,
.editor-content strike,
.editor-content del {
text-decoration: line-through !important;
}
.editor-content mark {
background-color: #FEF3C7 !important;
padding: 0 2px !important;
}
.editor-content sub {
vertical-align: sub !important;
font-size: 0.8em !important;
}
.editor-content sup {
vertical-align: super !important;
font-size: 0.8em !important;
}
/* ==========================================================================
반응형 (Responsive)
========================================================================== */
@media (max-width: 768px) {
.editor-content {
font-size: 14px !important;
}
.editor-content h1 {
font-size: 18px !important;
}
.editor-content h2 {
font-size: 16px !important;
}
.editor-content h3,
.editor-content h4,
.editor-content h5,
.editor-content h6 {
font-size: 14px !important;
}
.editor-content p,
.editor-content li {
font-size: 14px !important;
}
.editor-content table {
font-size: 12px !important;
}
.editor-content th,
.editor-content td {
padding: 4px 8px !important;
}
.editor-content ul,
.editor-content ol {
padding-left: 24px !important;
}
.editor-content blockquote {
padding: 8px 16px !important;
}
.editor-content pre {
padding: 8px !important;
}
}
+310
View File
@@ -0,0 +1,310 @@
// 계좌이체 API 모의 데이터
// locked: true 인 필드는 게이트웨이가 자동 주입한 값(회색 + 자물쇠)
// locked: false / 미지정 인 필드는 사용자가 자유롭게 편집
window.SAMPLE_DATA = (function () {
return {
info: {
title: { value: '계좌이체 API', locked: false },
version: { value: '1.0.0', locked: false },
summary: { value: '', locked: false },
description: { value: '', locked: false },
termsOfService: { value: '', locked: false },
contact: {
name: { value: 'DJB API Team', locked: false },
email: { value: 'api@djb.co.kr', locked: false },
url: { value: '', locked: false }
},
license: {
name: { value: '', locked: false },
url: { value: '', locked: false }
}
},
tags: [
{ name: 'transfer', description: '계좌이체 관련 API' }
],
externalDocs: {
description: { value: '', locked: false },
url: { value: '', locked: false }
},
servers: [
{
url: { value: 'https://api-dev.djb.co.kr', locked: true },
env: 'development',
description: { value: '개발 환경', locked: false }
},
{
url: { value: 'https://api-stg.djb.co.kr', locked: true },
env: 'staging',
description: { value: '스테이징 환경', locked: false }
},
{
url: { value: 'https://api.djb.co.kr', locked: true },
env: 'production',
description: { value: '운영 환경', locked: false }
}
],
serverVariables: [
{
name: { value: 'basePath', locked: true },
default: { value: '/v1', locked: false },
enumStr: { value: '/v1,/v2', locked: false },
description: { value: 'API 버전 경로', locked: false }
}
],
operation: {
method: { value: 'POST', locked: true },
path: { value: '/v1/accounts/transfer', locked: true },
operationId: { value: 'transferAccount', locked: false },
summary: { value: '', locked: false },
description: { value: '', locked: false },
tags: { value: ['transfer'], locked: false },
deprecated: { value: false, locked: false }
},
// 파라미터 (Path/Query/Header/Cookie)
parameters: [
{
in: 'header',
name: { value: 'X-Request-Id', locked: true },
type: { value: 'string', locked: true },
required: { value: true, locked: true },
format: { value: 'uuid', locked: false },
maxLength: { value: '', locked: false },
pattern: { value: '', locked: false },
description: { value: '', locked: false },
example: { value: '', locked: false }
}
],
// Request Body 스키마 (중첩 3단)
requestBody: {
mediaType: 'application/json',
required: { value: true, locked: true },
schema: [
{
name: { value: 'transactionId', locked: true },
type: { value: 'string', locked: true },
required: { value: true, locked: true },
format: { value: '', locked: false },
maxLength: { value: '40', locked: false },
pattern: { value: '^tx-[0-9a-zA-Z-]+$', locked: false },
description: { value: '', locked: false },
example: { value: 'tx-20260522-001', locked: false },
children: null
},
{
name: { value: 'sender', locked: true },
type: { value: 'object', locked: true },
required: { value: true, locked: true },
format: { value: '', locked: false },
maxLength: { value: '', locked: false },
pattern: { value: '', locked: false },
description: { value: '송금인 정보', locked: false },
example: { value: '', locked: false },
children: [
{
name: { value: 'accountNumber', locked: true },
type: { value: 'string', locked: true },
required: { value: true, locked: true },
format: { value: '', locked: false },
maxLength: { value: '20', locked: false },
pattern: { value: '', locked: false },
description: { value: '', locked: false },
example: { value: '110-1234-567890', locked: false },
children: null
},
{
name: { value: 'bankCode', locked: true },
type: { value: 'string', locked: true },
required: { value: true, locked: true },
format: { value: '', locked: false },
maxLength: { value: '3', locked: false },
pattern: { value: '', locked: false },
description: { value: '', locked: false },
example: { value: '088', locked: false },
children: null
},
{
name: { value: 'holderName', locked: true },
type: { value: 'string', locked: true },
required: { value: true, locked: true },
format: { value: '', locked: false },
maxLength: { value: '60', locked: false },
pattern: { value: '', locked: false },
description: { value: '', locked: false },
example: { value: '김철수', locked: false },
children: null
}
]
},
{
name: { value: 'receiver', locked: true },
type: { value: 'object', locked: true },
required: { value: true, locked: true },
format: { value: '', locked: false },
maxLength: { value: '', locked: false },
pattern: { value: '', locked: false },
description: { value: '수취인 정보', locked: false },
example: { value: '', locked: false },
children: [
{
name: { value: 'accountNumber', locked: true },
type: { value: 'string', locked: true },
required: { value: true, locked: true },
format: { value: '', locked: false },
maxLength: { value: '20', locked: false },
pattern: { value: '', locked: false },
description: { value: '', locked: false },
example: { value: '111-2345-678901', locked: false },
children: null
},
{
name: { value: 'bankCode', locked: true },
type: { value: 'string', locked: true },
required: { value: true, locked: true },
format: { value: '', locked: false },
maxLength: { value: '3', locked: false },
pattern: { value: '', locked: false },
description: { value: '', locked: false },
example: { value: '020', locked: false },
children: null
},
{
name: { value: 'holderName', locked: true },
type: { value: 'string', locked: true },
required: { value: true, locked: true },
format: { value: '', locked: false },
maxLength: { value: '60', locked: false },
pattern: { value: '', locked: false },
description: { value: '', locked: false },
example: { value: '이영희', locked: false },
children: null
}
]
},
{
name: { value: 'amount', locked: true },
type: { value: 'object', locked: true },
required: { value: true, locked: true },
format: { value: '', locked: false },
maxLength: { value: '', locked: false },
pattern: { value: '', locked: false },
description: { value: '이체 금액', locked: false },
example: { value: '', locked: false },
children: [
{
name: { value: 'value', locked: true },
type: { value: 'integer', locked: true },
required: { value: true, locked: true },
format: { value: 'int64', locked: false },
maxLength: { value: '', locked: false },
pattern: { value: '', locked: false },
description: { value: '', locked: false },
example: { value: '50000', locked: false },
children: null
},
{
name: { value: 'currency', locked: true },
type: { value: 'string', locked: true },
required: { value: true, locked: true },
format: { value: '', locked: false },
maxLength: { value: '3', locked: false },
pattern: { value: '^[A-Z]{3}$', locked: false },
description: { value: 'ISO 4217 통화코드', locked: false },
example: { value: 'KRW', locked: false },
children: null
}
]
},
{
name: { value: 'memo', locked: true },
type: { value: 'string', locked: true },
required: { value: false, locked: true },
format: { value: '', locked: false },
maxLength: { value: '100', locked: false },
pattern: { value: '', locked: false },
description: { value: '', locked: false },
example: { value: '5월 회비', locked: false },
children: null
}
]
},
// 응답 스키마 (상태코드 별)
responses: {
'200': {
description: { value: '이체 성공', locked: false },
schema: [
{ name: { value: 'transactionId', locked: true }, type: { value: 'string', locked: true }, required: { value: true, locked: true }, format: { value: '', locked: false }, maxLength: { value: '', locked: false }, pattern: { value: '', locked: false }, description: { value: '', locked: false }, example: { value: 'tx-20260522-001', locked: false }, children: null },
{ name: { value: 'status', locked: true }, type: { value: 'string', locked: true }, required: { value: true, locked: true }, format: { value: '', locked: false }, maxLength: { value: '', locked: false }, pattern: { value: '', locked: false }, description: { value: '', locked: false }, example: { value: 'COMPLETED', locked: false }, children: null },
{ name: { value: 'completedAt', locked: true }, type: { value: 'string', locked: true }, required: { value: true, locked: true }, format: { value: 'date-time', locked: false }, maxLength: { value: '', locked: false }, pattern: { value: '', locked: false }, description: { value: '', locked: false }, example: { value: '2026-05-22T10:00:00Z', locked: false }, children: null },
{
name: { value: 'balance', locked: true }, type: { value: 'object', locked: true }, required: { value: true, locked: true }, format: { value: '', locked: false }, maxLength: { value: '', locked: false }, pattern: { value: '', locked: false }, description: { value: '잔액 정보', locked: false }, example: { value: '', locked: false },
children: [
{ name: { value: 'before', locked: true }, type: { value: 'integer', locked: true }, required: { value: true, locked: true }, format: { value: 'int64', locked: false }, maxLength: { value: '', locked: false }, pattern: { value: '', locked: false }, description: { value: '', locked: false }, example: { value: '1000000', locked: false }, children: null },
{ name: { value: 'after', locked: true }, type: { value: 'integer', locked: true }, required: { value: true, locked: true }, format: { value: 'int64', locked: false }, maxLength: { value: '', locked: false }, pattern: { value: '', locked: false }, description: { value: '', locked: false }, example: { value: '950000', locked: false }, children: null }
]
}
]
},
'400': {
description: { value: '잘못된 요청', locked: false },
schema: [
{
name: { value: 'error', locked: true }, type: { value: 'object', locked: true }, required: { value: true, locked: true }, format: { value: '', locked: false }, maxLength: { value: '', locked: false }, pattern: { value: '', locked: false }, description: { value: '', locked: false }, example: { value: '', locked: false },
children: [
{ name: { value: 'code', locked: true }, type: { value: 'string', locked: true }, required: { value: true, locked: true }, format: { value: '', locked: false }, maxLength: { value: '', locked: false }, pattern: { value: '', locked: false }, description: { value: '', locked: false }, example: { value: 'INVALID_AMOUNT', locked: false }, children: null },
{ name: { value: 'message', locked: true }, type: { value: 'string', locked: true }, required: { value: true, locked: true }, format: { value: '', locked: false }, maxLength: { value: '', locked: false }, pattern: { value: '', locked: false }, description: { value: '', locked: false }, example: { value: '금액이 올바르지 않습니다', locked: false }, children: null },
{ name: { value: 'details', locked: true }, type: { value: 'array', locked: true }, required: { value: false, locked: true }, format: { value: '', locked: false }, maxLength: { value: '', locked: false }, pattern: { value: '', locked: false }, description: { value: '', locked: false }, example: { value: '', locked: false }, itemsType: { value: 'string', locked: false }, children: null }
]
}
]
},
'401': { description: { value: '인증 실패', locked: false }, schema: [] },
'500': { description: { value: '서버 오류', locked: false }, schema: [] }
},
// 보안 스킴
securitySchemes: [
{
key: 'ApiKeyAuth',
type: 'apiKey',
locked: true,
name: { value: 'X-API-Key', locked: true },
in: { value: 'header', locked: true },
description: { value: '', locked: false }
}
],
globalSecurity: ['ApiKeyAuth'],
// 예제
examples: {
request: {
'application/json': {
default: '{\n "transactionId": "tx-20260522-001",\n "sender": {\n "accountNumber": "110-1234-567890",\n "bankCode": "088",\n "holderName": "김철수"\n },\n "receiver": {\n "accountNumber": "111-2345-678901",\n "bankCode": "020",\n "holderName": "이영희"\n },\n "amount": {\n "value": 50000,\n "currency": "KRW"\n },\n "memo": "5월 회비"\n}'
}
},
response: {
'200': { body: '{\n "transactionId": "tx-20260522-001",\n "status": "COMPLETED"\n}', headers: [ { name: 'X-RateLimit-Remaining', type: 'integer', description: '남은 요청 횟수', example: '99' } ] }
}
},
// 문서 옵션
docOptions: {
theme: 'light',
lang: 'ko',
includeExamples: true,
tryItOut: true,
defaultExpandDepth: -1,
layout: 'BaseLayout',
tagFilter: [],
sideToc: true
}
};
})();
+103
View File
@@ -0,0 +1,103 @@
/* DJB OpenAPI Editor POC — Tailwind 보강 커스텀 */
html, body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Apple SD Gothic Neo",
"Malgun Gothic", "Noto Sans KR", system-ui, sans-serif;
}
/* === 잠금 행/카드 좌측 띠 === */
.locked-row td:first-child {
position: relative;
}
.locked-row td:first-child::before {
content: '';
position: absolute;
left: 0;
top: 6px;
bottom: 6px;
width: 3px;
background: #cbd5e1;
border-radius: 2px;
}
.locked-card {
position: relative;
background: linear-gradient(to right, #f8fafc 0%, #ffffff 4px) #ffffff;
}
.locked-card::before {
content: '';
position: absolute;
left: 0; top: 8px; bottom: 8px;
width: 3px;
background: #cbd5e1;
border-radius: 0 2px 2px 0;
}
/* === 스키마 테이블 — 가독성 보조 === */
.schema-table th { white-space: nowrap; }
.schema-table tbody tr:hover { background: #fafbfc; }
.schema-table input,
.schema-table select {
font-size: 12.5px;
}
.schema-table input[type="text"] {
padding-top: 4px;
padding-bottom: 4px;
}
/* === 스텝퍼 항목 hover === */
.step-item:hover { background: #f8fafc; }
/* === 미리보기 패널: Swagger UI 크기 미세조정 === */
#preview-swagger .swagger-ui {
font-size: 13px;
}
#preview-swagger .swagger-ui .info { margin: 18px 0; }
#preview-swagger .swagger-ui .info .title { font-size: 22px; }
#preview-swagger .swagger-ui .scheme-container { padding: 12px 0; box-shadow: none; }
#preview-swagger .swagger-ui .opblock { margin: 0 0 12px 0; }
/* === 다크 테마 (POC 시뮬레이션 — Swagger 자체 다크는 미지원이라 컨테이너만) === */
body.theme-dark #preview-swagger { background: #1e293b; }
/* === 토스트 애니메이션 === */
#toast { transition: opacity 0.2s; }
/* === Monaco 컨테이너가 hidden 일 때 layout 깨짐 방지 === */
#preview-monaco {
overflow: hidden;
}
/* === Stepper 6단 grid 셀 좌우 라인 (마지막 셀 제외) === */
#stepper li {
position: relative;
}
#stepper li:not(:last-child)::after {
content: '';
position: absolute;
top: 50%;
right: -8px;
width: 16px;
height: 1px;
background: #e2e8f0;
}
/* === 보안 스킴 카드 — 잠금 표시 === */
.locked-card {
background: #fafbfc;
}
/* === 칩(태그) 미세 조정 === */
.tag-chip { transition: background 0.15s; }
/* === Export menu fade === */
#export-menu { animation: fadeIn 0.12s ease-out; }
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-4px); }
to { opacity: 1; transform: translateY(0); }
}
/* === 1600px 기준 폼 패널 내부 가로 여유 확보 === */
#form-content { max-width: 100%; }
/* === readonly input 위에서 텍스트 커서 안 보이게 === */
input[readonly] { user-select: text; }