간단 소스 편집기
허용:
css/
,
page/
· 확장자:
html/css/js
페이지 추가
새로고침
파일 목록
css
css/app.js
css/app.js
css/style.css
css/style.css
page
page/account
계정 수동 생성 ❓
page/account/account_create.html
무료 계정 ❓
page/account/free_account_list.html
무료 계정 > 상세 정보 (탭형) ❓
page/account/free_account_view_tab.html
유료 계정 ❓
page/account/paid_account_list.html
유료 계정 상세 정보 ❓
page/account/paid_account_view.html
유료 계정 > 상세 정보 (탭형) ❓
page/account/paid_account_view_tab.html
page/main
대시보드 ❓
page/main/main.html
page/ops
관리자 추가 ❓
page/ops/admin_form.html
관리자 리스트 ❓
page/ops/admin_list.html
관리자 수정 ❓
page/ops/admin_modify.html
API 로그 ❓
page/ops/api_log.html
메일발송 로그 ❓
page/ops/mail_log.html
SMS 발송 로그 ❓
page/ops/sms_log.html
page/payment
page/payment/customer_pay_log.html
page/payment/customer_pay_log.html
page/payment/customer_refund_list.html
page/payment/customer_refund_list.html
도메인 SSL 연장 로그 ❓
page/payment/domain_log.html
결제 리스트 ❓
page/payment/pay_log.html
포인트 사용 로그 ❓
page/payment/point_log.html
환불 등록 ❓
page/payment/refund_form.html
환불 리스트 ❓
page/payment/refund_list.html
세금/현금영수증 ❓
page/payment/tax_doc.html
page/payment/user_pay_log.html
page/payment/user_pay_log.html
page/payment/user_refund_log.html
page/payment/user_refund_log.html
page/product
상품 등록 ❓
page/product/product_form.html
상품 리스트 ❓
page/product/product_list.html
상품 상세 ❓
page/product/product_view.html
page/settle
계정 정산 리스트 ❓
page/settle/month_list.html
page/settle/settle_log.html
page/settle/settle_log.html
page/sidebar_menu.html
page/sidebar_menu.html
page/sidebar_menu.html
page/site
FAQ 등록 ❓
page/site/faq_form.html
FAQ ❓
page/site/faq_list.html
공지사항 등록 ❓
page/site/notice_form.html
공지사항 ❓
page/site/notice_list.html
공지사항 상세 ❓
page/site/notice_view.html
페이지 추가 ❓
page/site/page_form.html
페이지 관리 ❓
page/site/page_list.html
페이지 수정 ❓
page/site/page_modify.html
팝업창 수정 ❓
page/site/popup_form.html
팝업창 ❓
page/site/popup_list.html
page/site/popup_modify.html
page/site/popup_modify.html
1:1 문의 상세 ❓
page/site/qna_form.html
1:1 문의 ❓
page/site/qna_list.html
SEO 관리 ❓
page/site/seo.html
page/stats
page/stats/account.html
page/stats/account.html
page/stats/sales.html
page/stats/sales.html
page/stats/visit.html
page/stats/visit.html
편집
page/ops/api_log.html
<!-- /page/ops/api_log.html --> <style> /* 검색영역 전용: 세로 1열(상하 1레코드) + 라벨/입력 좌우 분리 */ .filter-stack { display:flex; flex-direction:column; gap:.65rem; } .filter-rowline { display:flex; gap:12px; align-items:flex-start; } .filter-rowline .filter-label { width:140px; padding-top:6px; font-weight:600; color:#374151; white-space:nowrap; } .filter-rowline .filter-input { flex:1; min-width:0; text-align:left; } .chk-row { display:flex; flex-wrap:wrap; gap:.55rem .9rem; } .chk-row .form-check { margin:0; } .filter-actions { display:flex; justify-content:center; margin-top:12px; } .filter-actions .btn { min-width:180px; font-weight:700; } .table-apilog td, .table-apilog th { white-space:nowrap; } .code-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size:.9rem; } .subnote { font-size:.85rem; color:#6b7280; } /* value 영역은 좌측정렬(대표님 규칙) */ .text-leftcell { text-align:left !important; } /* ===== 레이어 팝업(기존 tax_doc 톤) ===== */ .ui-layer-backdrop{ position:fixed; inset:0; background:rgba(0,0,0,.45); display:none; z-index:1050; } .ui-layer{ position:fixed; left:50%; top:50%; transform:translate(-50%,-50%); width:min(980px, calc(100vw - 24px)); max-height:calc(100vh - 24px); overflow:auto; display:none; z-index:1060; } .table-apidetail td, .table-apidetail th { white-space:nowrap; } .code-block{ background:#0b1220; color:#e5e7eb; border:1px solid rgba(255,255,255,.08); border-radius:10px; padding:12px; max-height:260px; overflow:auto; } .code-block .muted{ color:#9ca3af; } </style> <div class="d-flex align-items-center justify-content-between mb-3"> <div> <h1 class="h4 mb-1">API 로그 <button type="button" class="btn btn-link btn-sm p-0 ms-2 align-baseline" onclick="openPageHelp(this)" data-title="API 로그"> ❓ </button> </h1> <div class="text-muted">API 포인트(소셜 로그인/AI 튜터/영상번역/PG 결제 등)별 호출 결과와 처리 내역을 조회합니다.</div> </div> </div> <div class="card mb-3 filter-card"> <div class="card-body"> <div class="filter-stack"> <!-- 키워드 검색 --> <div class="filter-rowline"> <div class="filter-label">키워드 검색</div> <div class="filter-input"> <div class="d-flex gap-2 flex-wrap"> <select class="form-select" style="max-width:180px;" id="kwField"> <option value="account_id">계정 아이디</option> <option value="endpoint">엔드포인트</option> <option value="request_id">요청 ID</option> <option value="client_ip">클라이언트 IP</option> </select> <input type="text" class="form-control" placeholder="검색어를 입력하세요" id="kwText" maxlength="80" style="max-width:600px;" /> </div> </div> </div> <!-- 기간 --> <div class="filter-rowline"> <div class="filter-label">호출기간</div> <div class="filter-input"> <div class="d-flex gap-2 flex-wrap"> <input type="date" class="form-control" style="max-width:200px;" id="useFrom" /> <div class="d-flex align-items-center text-muted">~</div> <input type="date" class="form-control" style="max-width:200px;" id="useTo" /> <div class="text-muted d-flex align-items-center">* 기본값은 빈 값입니다.</div> </div> </div> </div> <!-- API 구분(포인트) --> <div class="filter-rowline"> <div class="filter-label">API 구분</div> <div class="filter-input"> <div class="chk-row" data-group="api_point"> <div class="form-check"> <input class="form-check-input" type="checkbox" id="ap_all" checked> <label class="form-check-label" for="ap_all">전체</label> </div> <div class="form-check"> <input class="form-check-input" type="checkbox" id="ap_social" checked> <label class="form-check-label" for="ap_social">소셜 로그인</label> </div> <div class="form-check"> <input class="form-check-input" type="checkbox" id="ap_ai" checked> <label class="form-check-label" for="ap_ai">AI 튜터</label> </div> <div class="form-check"> <input class="form-check-input" type="checkbox" id="ap_trans" checked> <label class="form-check-label" for="ap_trans">영상번역</label> </div> <div class="form-check"> <input class="form-check-input" type="checkbox" id="ap_pg" checked> <label class="form-check-label" for="ap_pg">PG 결제</label> </div> <div class="form-check"> <input class="form-check-input" type="checkbox" id="ap_other" checked> <label class="form-check-label" for="ap_other">기타</label> </div> </div> </div> </div> <!-- 처리상태 --> <div class="filter-rowline"> <div class="filter-label">처리상태</div> <div class="filter-input"> <div class="chk-row" data-group="status"> <div class="form-check"> <input class="form-check-input" type="checkbox" id="st_all" checked> <label class="form-check-label" for="st_all">전체</label> </div> <div class="form-check"> <input class="form-check-input" type="checkbox" id="st_ok" checked> <label class="form-check-label" for="st_ok">성공(2xx)</label> </div> <div class="form-check"> <input class="form-check-input" type="checkbox" id="st_fail" checked> <label class="form-check-label" for="st_fail">실패(4xx/5xx)</label> </div> </div> </div> </div> <!-- 과금 --> <div class="filter-rowline"> <div class="filter-label">과금</div> <div class="filter-input"> <div class="chk-row" data-group="billing"> <div class="form-check"> <input class="form-check-input" type="checkbox" id="bi_all" checked> <label class="form-check-label" for="bi_all">전체</label> </div> <div class="form-check"> <input class="form-check-input" type="checkbox" id="bi_paid" checked> <label class="form-check-label" for="bi_paid">유료</label> </div> <div class="form-check"> <input class="form-check-input" type="checkbox" id="bi_free" checked> <label class="form-check-label" for="bi_free">무료</label> </div> </div> </div> </div> <!-- 버튼 --> <div class="filter-actions"> <div class="d-flex gap-2"> <button class="btn btn-primary" type="button">검색</button> <button class="btn btn-outline-secondary" type="button">조건 초기화</button> </div> </div> </div> </div> </div> <!-- 리스트 --> <div class="card"> <div class="card-header d-flex align-items-center justify-content-between flex-wrap gap-2"> <div class="text-muted"> 검색결과 <span class="fw-semibold">24</span>건 / 총 <span class="fw-semibold">9,840</span>건 <span class="ms-2 subnote">* 1행 = 1회 API 호출(요청) 단위</span> </div> <div class="d-flex align-items-center gap-2"> <div class="text-muted small">리스팅 개수</div> <select class="form-select form-select-sm" style="width:120px;"> <option value="10">10</option> <option value="20" selected>20</option> <option value="30">30</option> <option value="50">50</option> </select> </div> </div> <div class="card-body"> <div class="table-responsive"> <table class="table table-hover align-middle mb-0 table-apilog"> <thead> <tr class="text-muted text-center"> <th style="width:70px;">번호</th> <th style="width:130px;">API 구분</th> <th>계정 아이디</th> <th>호출시각</th> <th style="width:90px;">메서드</th> <th class="text-leftcell">엔드포인트</th> <th style="width:110px;">상태코드</th> <th style="width:120px;">응답시간</th> <th style="width:110px;">과금</th> <th style="width:90px;">상세</th> </tr> </thead> <tbody class="text-center"> <tr> <td class="fw-semibold">10</td> <td><span class="badge text-bg-light">PG 결제</span></td> <td class="code-mono">academy01</td> <td>2026-02-13 10:33:21</td> <td><span class="badge text-bg-secondary">POST</span></td> <td class="code-mono text-leftcell">/v1/payments/confirm</td> <td><span class="badge text-bg-success">200</span></td> <td class="code-mono">182ms</td> <td class="text-end">₩120</td> <td><button class="btn btn-outline-secondary btn-sm" type="button" onclick="openApiLayer('API-202602-0010')">상세</button></td> </tr> <tr> <td class="fw-semibold">9</td> <td><span class="badge text-bg-light">소셜 로그인</span></td> <td class="code-mono">demo002</td> <td>2026-02-12 18:05:10</td> <td><span class="badge text-bg-secondary">GET</span></td> <td class="code-mono text-leftcell">/auth/kakao/callback</td> <td><span class="badge text-bg-danger">401</span></td> <td class="code-mono">94ms</td> <td><span class="badge text-bg-secondary">무료</span></td> <td><button class="btn btn-outline-secondary btn-sm" type="button" onclick="openApiLayer('API-202602-0009')">상세</button></td> </tr> <tr> <td class="fw-semibold">8</td> <td><span class="badge text-bg-light">영상번역</span></td> <td class="code-mono">shop777</td> <td>2026-02-11 09:41:03</td> <td><span class="badge text-bg-secondary">POST</span></td> <td class="code-mono text-leftcell">/v1/video/translate</td> <td><span class="badge text-bg-success">202</span></td> <td class="code-mono">1,420ms</td> <td class="text-end">₩980</td> <td><button class="btn btn-outline-secondary btn-sm" type="button" onclick="openApiLayer('API-202602-0008')">상세</button></td> </tr> <tr> <td class="fw-semibold">7</td> <td><span class="badge text-bg-light">AI 튜터</span></td> <td class="code-mono">demo001</td> <td>2026-02-10 22:19:44</td> <td><span class="badge text-bg-secondary">POST</span></td> <td class="code-mono text-leftcell">/v1/ai/tutor/chat</td> <td><span class="badge text-bg-danger">500</span></td> <td class="code-mono">2,803ms</td> <td class="text-end">₩210</td> <td><button class="btn btn-outline-secondary btn-sm" type="button" onclick="openApiLayer('API-202602-0007')">상세</button></td> </tr> <tr> <td class="fw-semibold">6</td> <td><span class="badge text-bg-light">기타</span></td> <td class="code-mono">academy01</td> <td>2026-02-09 08:31:47</td> <td><span class="badge text-bg-secondary">GET</span></td> <td class="code-mono text-leftcell">/v1/system/health</td> <td><span class="badge text-bg-success">200</span></td> <td class="code-mono">22ms</td> <td><span class="badge text-bg-secondary">무료</span></td> <td><button class="btn btn-outline-secondary btn-sm" type="button" onclick="openApiLayer('API-202602-0006')">상세</button></td> </tr> </tbody> </table> </div> <nav class="mt-3 d-flex justify-content-center" aria-label="pagination"> <ul class="pagination pagination-sm mb-0"> <li class="page-item disabled"><a class="page-link" href="#">이전</a></li> <li class="page-item active"><a class="page-link" href="#">1</a></li> <li class="page-item"><a class="page-link" href="#">2</a></li> <li class="page-item"><a class="page-link" href="#">3</a></li> <li class="page-item"><a class="page-link" href="#">다음</a></li> </ul> </nav> </div> </div> <!-- ===== 상세 레이어(API 호출 상세) ===== --> <div id="apiBackdrop" class="ui-layer-backdrop" onclick="closeApiLayer()"></div> <div id="apiLayer" class="ui-layer"> <div class="card"> <div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2"> <div class="fw-semibold">API 호출 상세</div> <div class="d-flex gap-2"> <button class="btn btn-outline-secondary btn-sm" type="button" onclick="closeApiLayer()">닫기</button> </div> </div> <div class="card-body"> <!-- 1) 상단 요약: 한 줄 배치 --> <div class="d-flex align-items-center justify-content-between flex-wrap gap-2 mb-3"> <div class="d-flex flex-wrap gap-3 align-items-center"> <div class="text-muted small">요청 ID <span class="ms-1 code-mono text-dark" id="d_reqId">-</span> /</div> <div class="text-muted small">구분 <span class="ms-1 text-dark" id="d_point">-</span> /</div> <div class="text-muted small">계정 <span class="ms-1 code-mono text-dark" id="d_accountId">-</span> /</div> <div class="text-muted small">호출시각 <span class="ms-1 text-dark" id="d_calledAt">-</span> /</div> <div class="text-muted small">IP <span class="ms-1 code-mono text-dark" id="d_ip">-</span></div> </div> <div class="text-muted small"> <span class="badge text-bg-secondary" id="d_methodBadge">METHOD</span> <span class="ms-1 code-mono text-dark" id="d_endpoint">-</span> <span class="ms-2">· 상태 <span class="badge text-bg-success" id="d_statusBadge">200</span></span> <span class="ms-2">· 응답 <span class="fw-semibold" id="d_ms">0ms</span></span> <span class="ms-2">· 과금 <span class="fw-semibold" id="d_cost">-</span></span> </div> </div> <!-- 2) 키워드 검색(프로토타입) --> <div class="d-flex align-items-center justify-content-between flex-wrap gap-2 mb-2"> <div class="text-muted"> 항목 <span class="fw-semibold" id="d_totalRow">0</span>개 </div> <div class="d-flex gap-2 flex-wrap align-items-center"> <input id="d_kw" type="text" class="form-control form-control-sm" style="width:min(320px, 70vw);" placeholder="키 검색 (예: authorization, user_id, error)"> <button class="btn btn-primary btn-sm" type="button" onclick="filterDetailRows()">검색</button> <button class="btn btn-outline-secondary btn-sm" type="button" onclick="resetDetailRows()">초기화</button> </div> </div> <!-- 3) 항목 리스트 --> <div class="table-responsive"> <table class="table table-hover align-middle mb-0 table-apidetail"> <thead class="text-muted text-center"> <tr> <th style="width:160px;">구분</th> <th class="text-leftcell">값</th> <th style="width:110px;">에러로그</th> </tr> </thead> <tbody id="d_rows" class="text-center"> <!-- JS로 채움 --> </tbody> </table> </div> <!-- 4) 페이지네이션(프로토타입) --> <nav class="mt-3 d-flex justify-content-center" aria-label="pagination"> <ul class="pagination pagination-sm mb-0" id="d_pager"> <!-- JS로 채움 --> </ul> </nav> </div> </div> </div> <script> // ===== 샘플 데이터(프로토타입) ===== var API_DETAIL = { "API-202602-0010": { reqId: "API-202602-0010", point: "PG 결제", accountId: "academy01", calledAt: "2026-02-13 10:33:21", ip: "211.12.34.56", method: "POST", endpoint: "/v1/payments/confirm", status: 200, ms: 182, cost: "₩120", rows: [ {k:"Request Headers", v:"authorization: Bearer ***\\ncontent-type: application/json\\nuser-agent: backoffice-proxy"}, {k:"Request Body", v:"{\\n \"order_id\": \"ORD-20260213-00021\",\\n \"amount\": 12000,\\n \"currency\": \"KRW\"\\n}"}, {k:"Response Body", v:"{\\n \"result\": \"OK\",\\n \"payment_key\": \"pay_***\"\\n}"}, {k:"Vendor", v:"toss_payments"}, {k:"Trace", v:"trace_id: 8f3c... / span_id: 1a2b..."}, {k:"Error", v:"-"} ] }, "API-202602-0009": { reqId: "API-202602-0009", point: "소셜 로그인", accountId: "demo002", calledAt: "2026-02-12 18:05:10", ip: "118.22.90.11", method: "GET", endpoint: "/auth/kakao/callback", status: 401, ms: 94, cost: "무료", rows: [ {k:"Request Headers", v:"cookie: sid=***\\nuser-agent: Mozilla/5.0"}, {k:"Query", v:"code=***&state=***"}, {k:"Response Body", v:"{\\n \"error\": \"unauthorized\",\\n \"message\": \"invalid state\"\\n}"}, {k:"Error", v:"invalid state parameter"}, {k:"Vendor", v:"kakao"}, {k:"Trace", v:"trace_id: 0c9d... / span_id: 77aa..."} ] }, "API-202602-0008": { reqId: "API-202602-0008", point: "영상번역", accountId: "shop777", calledAt: "2026-02-11 09:41:03", ip: "175.9.10.23", method: "POST", endpoint: "/v1/video/translate", status: 202, ms: 1420, cost: "₩980", rows: [ {k:"Request Headers", v:"content-type: application/json\\nauthorization: Bearer ***"}, {k:"Request Body", v:"{\\n \"video_id\": \"VID-7781\",\\n \"from\": \"ko\",\\n \"to\": \"en\"\\n}"}, {k:"Response Body", v:"{\\n \"job_id\": \"JOB-20260211-991\",\\n \"status\": \"queued\"\\n}"}, {k:"Vendor", v:"translate_provider_x"}, {k:"Error", v:"-"} ] }, "API-202602-0007": { reqId: "API-202602-0007", point: "AI 튜터", accountId: "demo001", calledAt: "2026-02-10 22:19:44", ip: "59.3.120.7", method: "POST", endpoint: "/v1/ai/tutor/chat", status: 500, ms: 2803, cost: "₩210", rows: [ {k:"Request Headers", v:"content-type: application/json\\nauthorization: Bearer ***"}, {k:"Request Body", v:"{\\n \"session_id\": \"S-1002\",\\n \"message\": \"...\"\\n}"}, {k:"Response Body", v:"{\\n \"error\": \"internal_error\",\\n \"message\": \"upstream timeout\"\\n}"}, {k:"Error", v:"upstream timeout"}, {k:"Vendor", v:"llm_provider_y"}, {k:"Trace", v:"trace_id: f1a2... / span_id: 9bcd..."} ] }, "API-202602-0006": { reqId: "API-202602-0006", point: "기타", accountId: "academy01", calledAt: "2026-02-09 08:31:47", ip: "127.0.0.1", method: "GET", endpoint: "/v1/system/health", status: 200, ms: 22, cost: "무료", rows: [ {k:"Response Body", v:"{\\n \"ok\": true,\\n \"ts\": \"2026-02-09T08:31:47+09:00\"\\n}"}, {k:"Error", v:"-"} ] } }; // ===== 레이어 내부 상태(검색/페이지네이션) ===== var _currentDetail = null; var _detailAllRows = []; var _detailFiltered = []; var _detailPage = 1; var _detailPageSize = 10; function el(id){ return document.getElementById(id); } function badgeForStatus(st){ var cls = 'text-bg-success'; if(st >= 400) cls = 'text-bg-danger'; return '<span class="badge ' + cls + '">' + st + '</span>'; } function renderPager(total, page, pageSize){ var totalPages = Math.max(1, Math.ceil(total / pageSize)); if(page > totalPages) page = totalPages; var html = ''; html += '<li class="page-item ' + (page<=1?'disabled':'') + '">' + '<a class="page-link" href="#" onclick="return gotoDetailPage(' + (page-1) + ')">이전</a></li>'; var start = Math.max(1, page - 2); var end = Math.min(totalPages, start + 4); start = Math.max(1, end - 4); for(var p=start; p<=end; p++){ html += '<li class="page-item ' + (p===page?'active':'') + '">' + '<a class="page-link" href="#" onclick="return gotoDetailPage(' + p + ')">' + p + '</a></li>'; } html += '<li class="page-item ' + (page>=totalPages?'disabled':'') + '">' + '<a class="page-link" href="#" onclick="return gotoDetailPage(' + (page+1) + ')">다음</a></li>'; el('d_pager').innerHTML = html; } function renderDetailRows(rows){ var rowsEl = el('d_rows'); rowsEl.innerHTML = ''; var start = (_detailPage - 1) * _detailPageSize; var slice = (rows || []).slice(start, start + _detailPageSize); slice.forEach(function(r, idx){ var isErr = (r.k === 'Error' && r.v && r.v !== '-' ); var valHtml = ''; var v = (r.v || ''); if(v.length > 0){ valHtml = '<div class="code-block code-mono text-start"><span class="muted"></span>' + escapeHtml(v).replace(/\n/g,'<br>') + '</div>'; }else{ valHtml = '<span class="text-muted">-</span>'; } var errBtn = isErr ? '<button class="btn btn-outline-secondary btn-sm" type="button" onclick="openApiErrorLog(' + (start+idx+1) + ')">에러로그</button>' : '<span class="text-muted">-</span>'; rowsEl.insertAdjacentHTML('beforeend', '<tr>' + '<td class="fw-semibold">' + escapeHtml(r.k) + '</td>' + '<td class="text-leftcell">' + valHtml + '</td>' + '<td>' + errBtn + '</td>' + '</tr>' ); }); renderPager((rows||[]).length, _detailPage, _detailPageSize); } function gotoDetailPage(p){ if(p < 1) p = 1; _detailPage = p; renderDetailRows(_detailFiltered); return false; } function filterDetailRows(){ if(!_currentDetail) return; var kw = (el('d_kw').value || '').trim().toLowerCase(); _detailPage = 1; if(!kw){ _detailFiltered = _detailAllRows.slice(); renderDetailRows(_detailFiltered); return; } _detailFiltered = _detailAllRows.filter(function(r){ var a = (r.k || '').toLowerCase(); var b = (r.v || '').toLowerCase(); return (a.indexOf(kw) !== -1) || (b.indexOf(kw) !== -1); }); renderDetailRows(_detailFiltered); } function resetDetailRows(){ el('d_kw').value = ''; _detailPage = 1; _detailFiltered = _detailAllRows.slice(); renderDetailRows(_detailFiltered); } // 에러로그 버튼(프로토타입) function openApiErrorLog(no){ alert('프로토타입: 에러로그 레이어를 띄웁니다. (row #' + no + ')'); return false; } function openApiLayer(reqId){ var d = API_DETAIL[reqId] || null; if(!d){ alert('프로토타입: 상세 데이터가 없습니다.'); return; } _currentDetail = d; el('d_reqId').textContent = d.reqId; el('d_point').textContent = d.point; el('d_accountId').textContent = d.accountId; el('d_calledAt').textContent = d.calledAt; el('d_ip').textContent = d.ip; el('d_methodBadge').textContent = d.method; el('d_endpoint').textContent = d.endpoint; el('d_statusBadge').outerHTML = '<span class="badge ' + (d.status>=400?'text-bg-danger':'text-bg-success') + '" id="d_statusBadge">' + d.status + '</span>'; el('d_ms').textContent = (d.ms||0) + 'ms'; el('d_cost').textContent = d.cost || '-'; el('d_totalRow').textContent = (d.rows ? d.rows.length : 0); // 검색/페이지 상태 초기화 el('d_kw').value = ''; _detailAllRows = (d.rows || []).slice(); _detailFiltered = _detailAllRows.slice(); _detailPage = 1; renderDetailRows(_detailFiltered); el('apiBackdrop').style.display='block'; el('apiLayer').style.display='block'; document.body.style.overflow='hidden'; } function closeApiLayer(){ el('apiBackdrop').style.display='none'; el('apiLayer').style.display='none'; document.body.style.overflow=''; } function escapeHtml(s){ return String(s) .replace(/&/g,'&') .replace(/</g,'<') .replace(/>/g,'>') .replace(/"/g,'"') .replace(/'/g,'''); } document.addEventListener('keydown', function(e){ if(e.key==='Escape'){ closeApiLayer(); } }); // 도움말(프로토타입용 안전 처리) function openPageHelp(btn){ var title = (btn && btn.getAttribute('data-title')) ? btn.getAttribute('data-title') : '도움말'; alert('프로토타입 도움말: ' + title); } </script>
저장
페이지 추가
디렉토리
page (root)
page/account
page/main
page/marketing
page/ops
page/payment
page/product
page/settle
page/site
page/stats
* page/ 하위 1레벨 폴더만 선택
파일명
.html
* 영문 소문자/숫자/_/- 만, 최대 32자
비밀번호