Compare commits
3 Commits
8830f07be1
...
f15b453aad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f15b453aad | ||
|
|
699909212f | ||
|
|
2a038662e0 |
@@ -67,18 +67,19 @@ body { margin: 0; background: var(--dds-bg); color: var(--dds-ink); font-family:
|
|||||||
.journey-card-compact h2 { margin: 0 0 .25rem; }
|
.journey-card-compact h2 { margin: 0 0 .25rem; }
|
||||||
.journey-card-compact strong { font-size: .8rem; }
|
.journey-card-compact strong { font-size: .8rem; }
|
||||||
.compact-context { padding: .75rem; }
|
.compact-context { padding: .75rem; }
|
||||||
.architecture-strip:has(.dds-product-nav) { background: transparent; border: 0; display: block; margin: 0 0 1.25rem; padding: 0; }
|
|
||||||
.dds-product-nav { background: #fff; border: 1px solid var(--dds-line); border-radius: 10px; display: flex; gap: .25rem; overflow-x: auto; padding: .3rem; }
|
|
||||||
.dds-product-nav a { border-radius: 7px; color: var(--dds-muted); font-size: .86rem; font-weight: 700; padding: .5rem .75rem; text-decoration: none; white-space: nowrap; }
|
|
||||||
.dds-product-nav a:hover { background: #f1f4fb; color: var(--dds-ink); }
|
|
||||||
.dds-product-nav a.active { background: #e8edff; color: var(--dds-accent-dark); }
|
|
||||||
.page-title .context-summary { font-size: .95rem; max-width: 700px; }
|
.page-title .context-summary { font-size: .95rem; max-width: 700px; }
|
||||||
.explanation-details > summary { color: #43516a; font-size: .86rem; }
|
.explanation-details > summary { color: #43516a; font-size: .86rem; }
|
||||||
.explanation-details > summary::marker { color: var(--dds-accent); }
|
.explanation-details > summary::marker { color: var(--dds-accent); }
|
||||||
|
.knowledge-entry-form { display: grid; gap: 1rem; margin-top: 1rem; max-width: 48rem; }
|
||||||
|
.knowledge-entry-form > label { color: var(--dds-muted); display: block; font-size: .875rem; font-weight: 600; }
|
||||||
|
.knowledge-entry-form .form-control, .knowledge-entry-form .form-select { margin-top: .4rem; width: 100%; }
|
||||||
.registration-advanced-options { background: #f8faff; border: 1px solid var(--dds-line); border-radius: 8px; padding: .85rem 1rem; }
|
.registration-advanced-options { background: #f8faff; border: 1px solid var(--dds-line); border-radius: 8px; padding: .85rem 1rem; }
|
||||||
.registration-advanced-options > summary { color: #43516a; cursor: pointer; font-size: .875rem; font-weight: 700; }
|
.registration-advanced-options > summary { color: #43516a; cursor: pointer; font-size: .875rem; font-weight: 700; }
|
||||||
.registration-advanced-options > p { color: var(--dds-muted); font-size: .82rem; margin: .7rem 0; }
|
.registration-advanced-options > p { color: var(--dds-muted); font-size: .82rem; margin: .7rem 0; }
|
||||||
.registration-advanced-grid { align-items: end; }
|
.knowledge-options-grid { align-items: end; display: grid; gap: 1rem; grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||||
|
.knowledge-options-grid label { color: var(--dds-muted); font-size: .875rem; font-weight: 600; }
|
||||||
|
.knowledge-options-grid .form-control, .knowledge-options-grid .form-select { margin-top: .4rem; width: 100%; }
|
||||||
|
.knowledge-options-grid .full-row { grid-column: 1 / -1; }
|
||||||
.registration-checkbox { align-items: center; color: var(--dds-ink) !important; display: flex; gap: .5rem; }
|
.registration-checkbox { align-items: center; color: var(--dds-ink) !important; display: flex; gap: .5rem; }
|
||||||
.registration-checkbox input { height: 1rem; width: 1rem; }
|
.registration-checkbox input { height: 1rem; width: 1rem; }
|
||||||
.registration-actions { border-top: 1px solid var(--dds-line); padding-top: 1rem; }
|
.registration-actions { border-top: 1px solid var(--dds-line); padding-top: 1rem; }
|
||||||
@@ -92,6 +93,9 @@ body { margin: 0; background: var(--dds-bg); color: var(--dds-ink); font-family:
|
|||||||
.registration-result dd { font-size: .9rem; font-weight: 700; margin: .2rem 0 0; }
|
.registration-result dd { font-size: .9rem; font-weight: 700; margin: .2rem 0 0; }
|
||||||
.advanced-workflow > details > summary { color: var(--dds-ink); cursor: pointer; font-size: 1rem; font-weight: 700; }
|
.advanced-workflow > details > summary { color: var(--dds-ink); cursor: pointer; font-size: 1rem; font-weight: 700; }
|
||||||
.advanced-workflow .section-subtitle { margin: .4rem 0 0; }
|
.advanced-workflow .section-subtitle { margin: .4rem 0 0; }
|
||||||
|
.form-grid > .btn { justify-self: start; min-width: 7.5rem; width: auto; }
|
||||||
|
.wizard-progress, .wizard-step-number { display: none; }
|
||||||
|
.permission-wizard .wizard-panel-heading { align-items: flex-start; }
|
||||||
.protection-summary { border-top: 3px solid #3157d5; }
|
.protection-summary { border-top: 3px solid #3157d5; }
|
||||||
.protection-status-card { background: #fbfcff; border: 1px solid #dbe3f4; border-radius: 14px; margin-top: 1rem; padding: 1.25rem; }
|
.protection-status-card { background: #fbfcff; border: 1px solid #dbe3f4; border-radius: 14px; margin-top: 1rem; padding: 1.25rem; }
|
||||||
.protection-status-card.status-danger { background: #fffafa; border-color: #f2c8c8; }
|
.protection-status-card.status-danger { background: #fffafa; border-color: #f2c8c8; }
|
||||||
@@ -129,6 +133,7 @@ body { margin: 0; background: var(--dds-bg); color: var(--dds-ink); font-family:
|
|||||||
.panel { padding: 19px; }
|
.panel { padding: 19px; }
|
||||||
.hero { padding-top: 30px; }
|
.hero { padding-top: 30px; }
|
||||||
.protection-status-topline, .protection-status-footer { align-items: flex-start; flex-direction: column; }
|
.protection-status-topline, .protection-status-footer { align-items: flex-start; flex-direction: column; }
|
||||||
|
.knowledge-options-grid { grid-template-columns: 1fr; }
|
||||||
|
.knowledge-options-grid .full-row { grid-column: auto; }
|
||||||
.registration-result { flex-direction: column; }
|
.registration-result { flex-direction: column; }
|
||||||
.dds-product-nav { border-radius: 8px; }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,24 +17,6 @@
|
|||||||
|
|
||||||
<div class="alert alert-warning" th:if="${runtimeError}">DDS 관리 데이터를 불러오지 못했습니다. <span th:text="${runtimeError}"></span></div>
|
<div class="alert alert-warning" th:if="${runtimeError}">DDS 관리 데이터를 불러오지 못했습니다. <span th:text="${runtimeError}"></span></div>
|
||||||
|
|
||||||
<section class="journey-grid" aria-label="DDS 권한 적용 네 단계">
|
|
||||||
<a class="journey-card journey-card-compact" href="/permissions"><div><h2>권한 관리</h2><strong>열기 →</strong></div></a>
|
|
||||||
<a class="journey-card journey-card-compact" href="/dds-protection"><div><h2>보호 정책 관리</h2><strong>열기 →</strong></div></a>
|
|
||||||
<a class="journey-card journey-card-compact" href="/dds-provision"><div><h2>정책 적용</h2><strong>열기 →</strong></div></a>
|
|
||||||
<a class="journey-card journey-card-compact" href="/vector-knowledge"><div><h2>지식자료 관리</h2><strong>열기 →</strong></div></a>
|
|
||||||
<a class="journey-card journey-card-compact" href="/dds"><div><h2>보호 대상 관리</h2><strong>열기 →</strong></div></a>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="content-band compact-context">
|
|
||||||
<details class="explanation-details m-0">
|
|
||||||
<summary>도움말</summary>
|
|
||||||
<div class="macro-micro-grid mt-3">
|
|
||||||
<div><span class="architecture-kicker">MACRO · 전체 관점</span><h2>관리 기준은 하나입니다.</h2><p>누가 어떤 지식자료를 볼 수 있는지는 사용자·그룹·역할·권한 규칙에서 설명합니다. VPD와 DDS는 같은 기준을 서로 다른 DB 보호 방식으로 집행합니다.</p></div>
|
|
||||||
<div><span class="architecture-kicker">MICRO · 실행 관점</span><h2>객체별 DATA GRANT가 최종 집행합니다.</h2><p>토큰 경로는 <code>Bearer → CB_AGENT_CTX → DATA GRANT predicate → 보호 VIEW</code>, 직접 비교는 <code>END USER → DATA ROLE → DATA GRANT → 보호 VIEW</code>입니다. 권한이 없으면 객체가 보이지 않아 <code>ORA-00942</code>가 될 수 있습니다.</p></div>
|
|
||||||
</div>
|
|
||||||
</details>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="summary-grid" aria-label="현재 DDS 관리 현황">
|
<section class="summary-grid" aria-label="현재 DDS 관리 현황">
|
||||||
<a class="summary-tile" href="/permissions"><span class="label">역할</span><strong th:text="${#lists.size(roles)}">0</strong></a>
|
<a class="summary-tile" href="/permissions"><span class="label">역할</span><strong th:text="${#lists.size(roles)}">0</strong></a>
|
||||||
<a class="summary-tile" href="/permissions"><span class="label">권한 규칙</span><strong th:text="${#lists.size(permissions)}">0</strong></a>
|
<a class="summary-tile" href="/permissions"><span class="label">권한 규칙</span><strong th:text="${#lists.size(permissions)}">0</strong></a>
|
||||||
@@ -42,9 +24,9 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="content-band">
|
<section class="content-band">
|
||||||
<div class="section-heading"><div><h2>현재 보호 대상</h2></div></div>
|
<div class="section-heading"><div><h2>현재 보호 상태</h2><p class="section-subtitle">지식자료 검색에 적용되는 보호 정책</p></div><a class="btn btn-sm rw-btn-primary" href="/dds-protection">정책 상태 확인</a></div>
|
||||||
<div class="table-responsive"><table class="table table-sm align-middle"><thead><tr><th>대상</th><th>보호 방식</th><th>검증</th></tr></thead><tbody>
|
<div class="table-responsive"><table class="table table-sm align-middle"><thead><tr><th>보호 대상</th><th>상태</th><th></th></tr></thead><tbody>
|
||||||
<tr><td><code th:text="${vectorObject}">ADMIN.CB_DDS_VECTOR_SEARCH_DOCUMENTS</code></td><td>객체별 분류값 DATA GRANT + 토큰 Context + VECTOR_DISTANCE</td><td><a href="/vector-knowledge">지식 검색</a></td></tr>
|
<tr><td>지식자료 검색</td><td>접근 태그 정책 적용</td><td><a href="/vector-knowledge">권한 검색</a></td></tr>
|
||||||
</tbody></table></div>
|
</tbody></table></div>
|
||||||
<details class="explanation-details mt-3">
|
<details class="explanation-details mt-3">
|
||||||
<summary>기술 상세</summary>
|
<summary>기술 상세</summary>
|
||||||
|
|||||||
@@ -12,61 +12,43 @@
|
|||||||
<script defer src="/js/app.js"></script>
|
<script defer src="/js/app.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav th:fragment="nav" class="navbar rw-nav navbar-expand-lg">
|
<nav th:fragment="nav" class="navbar rw-nav navbar-expand-lg" data-product-nav>
|
||||||
<div class="container">
|
<div class="container rw-nav-top">
|
||||||
<a class="navbar-brand" href="/" th:text="${backofficeTrack == 'DDS' ? 'DDS 권한 운영' : 'VPD Backoffice'}">DDS 권한 운영</a>
|
<a class="navbar-brand" href="/">DDS 권한 운영</a>
|
||||||
<span class="badge rounded-pill text-bg-primary ms-2" th:if="${backofficeTrack == 'DDS'}">Deep Data Security</span>
|
<span class="badge rounded-pill text-bg-primary">Deep Data Security</span>
|
||||||
<div class="rw-menu">
|
<div class="rw-menu" aria-label="주요 메뉴">
|
||||||
<div class="rw-menu-group">
|
<button class="rw-menu-trigger" type="button" data-submenu-trigger="access" aria-controls="submenu-access" aria-expanded="false">권한 관리</button>
|
||||||
<button class="rw-menu-trigger" type="button" aria-expanded="false" th:text="${backofficeTrack == 'DDS' ? '권한 관리' : '1. 권한 설계'}">1. 권한 설계</button>
|
<button class="rw-menu-trigger" type="button" data-submenu-trigger="protection" aria-controls="submenu-protection" aria-expanded="false">보호 정책 관리</button>
|
||||||
<div class="rw-menu-panel">
|
<button class="rw-menu-trigger" type="button" data-submenu-trigger="knowledge" aria-controls="submenu-knowledge" aria-expanded="false">지식자료 관리</button>
|
||||||
<a class="nav-link" href="/users" th:text="${backofficeTrack == 'DDS' ? '사용자 관리' : '사용자'}">사용자</a>
|
<button class="rw-menu-trigger" type="button" data-submenu-trigger="verification" aria-controls="submenu-verification" aria-expanded="false">접근 검증</button>
|
||||||
<a class="nav-link" href="/groups" th:text="${backofficeTrack == 'DDS' ? '그룹 관리' : '그룹'}">그룹</a>
|
|
||||||
<a class="nav-link" href="/roles" th:text="${backofficeTrack == 'DDS' ? '역할 관리' : '역할'}">역할</a>
|
|
||||||
<a class="nav-link" href="/permissions" th:text="${backofficeTrack == 'DDS' ? '접근 규칙' : '데이터 권한 규칙'}">데이터 권한 규칙</a>
|
|
||||||
<a class="nav-link" href="/effective-matrix" th:text="${backofficeTrack == 'DDS' ? '권한 현황' : '사용자별 최종 권한'}">사용자별 최종 권한</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="rw-menu-group">
|
|
||||||
<button class="rw-menu-trigger" type="button" aria-expanded="false"
|
|
||||||
th:text="${backofficeTrack == 'DDS' ? '보호 정책 관리' : '2. 보호·검증'}">2. 보호·검증</button>
|
|
||||||
<div class="rw-menu-panel">
|
|
||||||
<a class="nav-link" th:href="${backofficeTrack == 'DDS' ? '/dds-protection' : '/vpd-policies'}"
|
|
||||||
th:text="${backofficeTrack == 'DDS' ? '정책 상태' : 'DB 보호 연결'}">DB 보호 연결</a>
|
|
||||||
<a class="nav-link" href="/dds-provision" th:if="${backofficeTrack == 'DDS'}">정책 적용</a>
|
|
||||||
<a class="nav-link" href="/tokens" th:if="${backofficeTrack != 'DDS'}">검증 세션 발급</a>
|
|
||||||
<a class="nav-link" href="/probe" th:if="${backofficeTrack != 'DDS'}">권한 결과 확인</a>
|
|
||||||
<a class="nav-link" href="/dds" th:if="${backofficeTrack == 'DDS'}">보호 대상 관리</a>
|
|
||||||
<a class="nav-link" href="/dds" th:if="${backofficeTrack != 'DDS'}">DDS 직접 조회</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="rw-menu-group">
|
|
||||||
<button class="rw-menu-trigger" type="button" aria-expanded="false" th:text="${backofficeTrack == 'DDS' ? '지식자료 관리' : '연동 도구'}">연동 도구</button>
|
|
||||||
<div class="rw-menu-panel">
|
|
||||||
<a class="nav-link" href="/objects" th:if="${backofficeTrack != 'DDS'}">ORDS 조회 대상</a>
|
|
||||||
<a class="nav-link" href="/ords-handlers" th:if="${backofficeTrack != 'DDS'}">ORDS 핸들러</a>
|
|
||||||
<a class="nav-link" href="/vector-knowledge" th:text="${backofficeTrack == 'DDS' ? '자료 등록 및 검색' : '지식 검색 관리'}">지식 검색 관리</a>
|
|
||||||
<a class="nav-link" href="/mcp-chatbot" th:if="${backofficeTrack != 'DDS'}">Chatbot</a>
|
|
||||||
<a class="nav-link" href="/mcp-reasoning" th:if="${backofficeTrack != 'DDS'}">Reasoning</a>
|
|
||||||
<a class="nav-link" href="/mcp-sse" th:if="${backofficeTrack != 'DDS'}">SSE 서비스</a>
|
|
||||||
<a class="nav-link" href="/mcp-client-demo" th:if="${backofficeTrack != 'DDS'}">MCP Client</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="rw-menu-group">
|
|
||||||
<button class="rw-menu-trigger" type="button" aria-expanded="false" th:text="${backofficeTrack == 'DDS' ? '접근 검증' : '운영·고급'}">운영·고급</button>
|
|
||||||
<div class="rw-menu-panel">
|
|
||||||
<a class="nav-link" href="/dds-protection#direct-check-heading" th:if="${backofficeTrack == 'DDS'}">직접 접근 검증</a>
|
|
||||||
<a class="nav-link" href="/operation-status" th:if="${backofficeTrack != 'DDS'}">운영 상태</a>
|
|
||||||
<a class="nav-link" href="/vpd-filter-policies" th:if="${backofficeTrack != 'DDS'}">별도 Filter 관리</a>
|
|
||||||
<a class="nav-link" href="/settings" th:if="${backofficeTrack != 'DDS'}">설정</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<form method="post" action="/logout" class="ms-auto">
|
<form method="post" action="/logout" class="ms-auto">
|
||||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||||
<button class="btn btn-sm btn-outline-secondary" type="submit">로그아웃</button>
|
<button class="btn btn-sm btn-outline-secondary" type="submit">로그아웃</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="rw-submenu-bar" data-submenu-bar hidden>
|
||||||
|
<div class="container">
|
||||||
|
<div id="submenu-access" class="rw-submenu" data-submenu-panel="access" role="navigation" aria-label="권한 관리 메뉴" hidden>
|
||||||
|
<a class="nav-link" href="/users">사용자 관리</a>
|
||||||
|
<a class="nav-link" href="/groups">그룹 관리</a>
|
||||||
|
<a class="nav-link" href="/roles">역할 관리</a>
|
||||||
|
<a class="nav-link" href="/permissions">접근 규칙</a>
|
||||||
|
<a class="nav-link" href="/effective-matrix">권한 현황</a>
|
||||||
|
</div>
|
||||||
|
<div id="submenu-protection" class="rw-submenu" data-submenu-panel="protection" role="navigation" aria-label="보호 정책 관리 메뉴" hidden>
|
||||||
|
<a class="nav-link" href="/dds-protection">정책 상태</a>
|
||||||
|
<a class="nav-link" href="/dds-provision">정책 적용</a>
|
||||||
|
<a class="nav-link" href="/dds">보호 대상 관리</a>
|
||||||
|
</div>
|
||||||
|
<div id="submenu-knowledge" class="rw-submenu" data-submenu-panel="knowledge" role="navigation" aria-label="지식자료 관리 메뉴" hidden>
|
||||||
|
<a class="nav-link" href="/vector-knowledge">자료 등록 및 검색</a>
|
||||||
|
</div>
|
||||||
|
<div id="submenu-verification" class="rw-submenu" data-submenu-panel="verification" role="navigation" aria-label="접근 검증 메뉴" hidden>
|
||||||
|
<a class="nav-link" href="/dds-protection#direct-check-heading">직접 접근 검증</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div th:fragment="trackNotice" th:if="${backofficeTrack == 'DDS'}" class="container pt-3">
|
<div th:fragment="trackNotice" th:if="${backofficeTrack == 'DDS'}" class="container pt-3">
|
||||||
@@ -75,14 +57,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section th:fragment="architectureStrip(activeLayer)" class="architecture-strip" aria-label="권한 설정부터 결과 확인까지의 흐름">
|
<section th:fragment="architectureStrip(activeLayer)" th:unless="${backofficeTrack == 'DDS'}" class="architecture-strip" aria-label="권한 설정부터 결과 확인까지의 흐름">
|
||||||
<nav th:if="${backofficeTrack == 'DDS'}" class="dds-product-nav" aria-label="DDS 권한 운영 작업">
|
|
||||||
<a href="/permissions" th:classappend="${activeLayer == 'permission'} ? ' active'">권한 관리</a>
|
|
||||||
<a href="/dds-protection" th:classappend="${activeLayer == 'protection'} ? ' active'">보호 정책 관리</a>
|
|
||||||
<a href="/dds-provision" th:classappend="${activeLayer == 'publish'} ? ' active'">정책 적용</a>
|
|
||||||
<a href="/vector-knowledge" th:classappend="${activeLayer == 'search'} ? ' active'">지식자료 관리</a>
|
|
||||||
</nav>
|
|
||||||
<th:block th:unless="${backofficeTrack == 'DDS'}">
|
|
||||||
<div class="architecture-step" th:classappend="${activeLayer == 'permission'} ? ' active'">
|
<div class="architecture-step" th:classappend="${activeLayer == 'permission'} ? ' active'">
|
||||||
<span class="architecture-kicker">1 · WHO / WHAT</span>
|
<span class="architecture-kicker">1 · WHO / WHAT</span>
|
||||||
<strong>권한 설계</strong>
|
<strong>권한 설계</strong>
|
||||||
@@ -106,7 +81,6 @@
|
|||||||
<strong>결과 확인</strong>
|
<strong>결과 확인</strong>
|
||||||
<p th:if="${backofficeTrack != 'DDS'}">보호 정책을 통과한 데이터만 실제 결과로 확인합니다.</p>
|
<p th:if="${backofficeTrack != 'DDS'}">보호 정책을 통과한 데이터만 실제 결과로 확인합니다.</p>
|
||||||
</div>
|
</div>
|
||||||
</th:block>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<details th:if="${backofficeTrack == 'DDS'}" class="explanation-details architecture-explanation">
|
<details th:if="${backofficeTrack == 'DDS'}" class="explanation-details architecture-explanation">
|
||||||
|
|||||||
@@ -27,28 +27,27 @@
|
|||||||
<div class="section-heading">
|
<div class="section-heading">
|
||||||
<div>
|
<div>
|
||||||
<h2>자료 등록</h2>
|
<h2>자료 등록</h2>
|
||||||
<p class="section-subtitle">제목, 원문, 접근 태그를 입력합니다.</p>
|
<p class="section-subtitle">제목, 접근 태그, 문서 내용을 입력합니다.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<details class="explanation-details">
|
<details class="explanation-details">
|
||||||
<summary>도움말</summary>
|
<summary>도움말</summary>
|
||||||
<p class="mb-0">접근 태그는 이 자료를 볼 수 있는 권한 범위입니다. 권한 관리에 등록된 TAG 값과 일치하는 태그를 입력하세요. 등록 후 문서는 검색 단위로 나뉘고 임베딩이 생성됩니다.</p>
|
<p class="mb-0">접근 태그는 이 자료를 볼 수 있는 권한 범위입니다. 권한 관리에 등록된 TAG 값과 일치하는 태그를 입력하세요. 등록 후 문서는 검색 단위로 나뉘고 임베딩이 생성됩니다.</p>
|
||||||
</details>
|
</details>
|
||||||
<form method="post" action="/vector-knowledge/ingest" class="form-grid mt-3">
|
<form method="post" action="/vector-knowledge/ingest" class="knowledge-entry-form">
|
||||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||||
<label>자료 제목<input class="form-control" name="title" placeholder="예: DDS 기술 태그 권한 설계" required></label>
|
<label>자료 제목<input class="form-control" name="title" placeholder="예: DDS 기술 태그 권한 설계" required></label>
|
||||||
<label>원문 주소 <span class="text-muted fw-normal">(선택)</span><input class="form-control" name="sourceUri" placeholder="예: https://kb.example.com/dds-vector"></label>
|
<label>접근 태그
|
||||||
<label class="span-2">접근 태그
|
|
||||||
<input class="form-control" name="techTags" placeholder="예: SALES, INTERNAL" required>
|
<input class="form-control" name="techTags" placeholder="예: SALES, INTERNAL" required>
|
||||||
<span class="form-hint">태그가 하나라도 허용되면 자료를 검색할 수 있습니다.</span>
|
<span class="form-hint">태그가 하나라도 허용되면 자료를 검색할 수 있습니다.</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="span-2">문서 본문
|
<label>문서 본문
|
||||||
<textarea class="form-control" name="content" rows="8" placeholder="문서 내용을 붙여 넣으세요." required></textarea>
|
<textarea class="form-control" name="content" rows="8" placeholder="문서 내용을 붙여 넣으세요." required></textarea>
|
||||||
</label>
|
</label>
|
||||||
<details class="span-2 registration-advanced-options">
|
<details class="registration-advanced-options">
|
||||||
<summary>고급 설정</summary>
|
<summary>고급 설정</summary>
|
||||||
<p>기본 처리 방식을 변경하거나 기존 자료를 교체할 때만 사용합니다.</p>
|
<p>기본 처리 방식을 변경하거나 기존 자료를 교체할 때만 사용합니다.</p>
|
||||||
<div class="form-grid registration-advanced-grid">
|
<div class="knowledge-options-grid">
|
||||||
<label>검색 단위 길이
|
<label>검색 단위 길이
|
||||||
<input class="form-control" name="chunkSize" type="number" min="80" max="2000" value="600">
|
<input class="form-control" name="chunkSize" type="number" min="80" max="2000" value="600">
|
||||||
</label>
|
</label>
|
||||||
@@ -58,16 +57,16 @@
|
|||||||
<option value="AI" th:disabled="${!aiEmbeddingConfigured}">AI 임베딩</option>
|
<option value="AI" th:disabled="${!aiEmbeddingConfigured}">AI 임베딩</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label class="span-2">교체할 자료 ID
|
<label class="full-row">교체할 자료 ID
|
||||||
<input class="form-control" name="documentId" placeholder="기존 등록 결과에 표시된 자료 ID">
|
<input class="form-control" name="documentId" placeholder="기존 등록 결과에 표시된 자료 ID">
|
||||||
<span class="form-hint">입력한 자료의 검색 단위를 새 내용으로 교체합니다.</span>
|
<span class="form-hint">입력한 자료의 검색 단위를 새 내용으로 교체합니다.</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="span-2 registration-checkbox">
|
<label class="full-row registration-checkbox">
|
||||||
<input type="checkbox" name="replaceExisting" value="true"> 기존 자료를 교체합니다.
|
<input type="checkbox" name="replaceExisting" value="true"> 기존 자료를 교체합니다.
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
<div class="span-2 registration-actions"><button class="btn rw-btn-primary" type="submit">등록</button></div>
|
<div class="registration-actions"><button class="btn rw-btn-primary" type="submit">등록</button></div>
|
||||||
</form>
|
</form>
|
||||||
<section class="registration-result mt-3" th:if="${ingestResult}" aria-live="polite">
|
<section class="registration-result mt-3" th:if="${ingestResult}" aria-live="polite">
|
||||||
<div>
|
<div>
|
||||||
@@ -110,18 +109,18 @@
|
|||||||
<summary>도움말</summary>
|
<summary>도움말</summary>
|
||||||
<p class="mb-0">토큰은 검색하는 사용자를 확인하기 위한 임시 값이며 저장하지 않습니다. 회수되었거나 만료된 토큰은 사용할 수 없습니다.</p>
|
<p class="mb-0">토큰은 검색하는 사용자를 확인하기 위한 임시 값이며 저장하지 않습니다. 회수되었거나 만료된 토큰은 사용할 수 없습니다.</p>
|
||||||
</details>
|
</details>
|
||||||
<form hx-post="/vector-knowledge/token-search" hx-target="#vector-token-search-result" hx-swap="innerHTML" class="form-grid mt-3">
|
<form hx-post="/vector-knowledge/token-search" hx-target="#vector-token-search-result" hx-swap="innerHTML" class="knowledge-entry-form">
|
||||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||||
<label class="span-2">Bearer 토큰
|
<label>Bearer 토큰
|
||||||
<input class="form-control" name="bearerToken" type="password" autocomplete="off" placeholder="발급된 임시 토큰을 붙여 넣으세요." required>
|
<input class="form-control" name="bearerToken" type="password" autocomplete="off" placeholder="발급된 임시 토큰을 붙여 넣으세요." required>
|
||||||
<span class="form-hint">요청 처리 후 저장하지 않습니다. 회수·만료된 토큰은 거부됩니다.</span>
|
<span class="form-hint">요청 처리 후 저장하지 않습니다. 회수·만료된 토큰은 거부됩니다.</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="span-2">검색 질문
|
<label>검색 질문
|
||||||
<textarea class="form-control" name="query" rows="3" placeholder="예: 세일즈 파이프라인 후속 조치 기준" required></textarea>
|
<textarea class="form-control" name="query" rows="3" placeholder="예: 세일즈 파이프라인 후속 조치 기준" required></textarea>
|
||||||
</label>
|
</label>
|
||||||
<details class="span-2 registration-advanced-options">
|
<details class="registration-advanced-options">
|
||||||
<summary>검색 옵션</summary>
|
<summary>검색 옵션</summary>
|
||||||
<div class="form-grid registration-advanced-grid mt-3">
|
<div class="knowledge-options-grid mt-3">
|
||||||
<label>결과 수<input class="form-control" name="limit" type="number" min="1" max="100" value="10"></label>
|
<label>결과 수<input class="form-control" name="limit" type="number" min="1" max="100" value="10"></label>
|
||||||
<label>임베딩 방식
|
<label>임베딩 방식
|
||||||
<select class="form-select" name="embeddingMode">
|
<select class="form-select" name="embeddingMode">
|
||||||
@@ -131,7 +130,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
<div class="span-2 registration-actions"><button class="btn rw-btn-primary" type="submit">검색</button></div>
|
<div class="registration-actions"><button class="btn rw-btn-primary" type="submit">검색</button></div>
|
||||||
</form>
|
</form>
|
||||||
<section id="vector-token-search-result" class="mt-3" aria-live="polite">
|
<section id="vector-token-search-result" class="mt-3" aria-live="polite">
|
||||||
<div class="empty-result-guide">토큰과 검색 질문을 입력하면 허용된 자료만 표시합니다.</div>
|
<div class="empty-result-guide">토큰과 검색 질문을 입력하면 허용된 자료만 표시합니다.</div>
|
||||||
@@ -146,7 +145,7 @@
|
|||||||
<summary>도움말</summary>
|
<summary>도움말</summary>
|
||||||
<p class="mb-0">업무 사용자 토큰 경로와 별도로, DDS END USER와 DATA GRANT가 직접 적용되는 결과를 비교합니다. 운영 검색에는 권한 검색을 사용하세요.</p>
|
<p class="mb-0">업무 사용자 토큰 경로와 별도로, DDS END USER와 DATA GRANT가 직접 적용되는 결과를 비교합니다. 운영 검색에는 권한 검색을 사용하세요.</p>
|
||||||
</details>
|
</details>
|
||||||
<form hx-post="/vector-knowledge/search" hx-target="#vector-search-result" hx-swap="innerHTML" class="form-grid mt-3">
|
<form hx-post="/vector-knowledge/search" hx-target="#vector-search-result" hx-swap="innerHTML" class="knowledge-entry-form">
|
||||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||||
<label>검증 사용자
|
<label>검증 사용자
|
||||||
<select class="form-select" name="userKey" required>
|
<select class="form-select" name="userKey" required>
|
||||||
@@ -162,10 +161,10 @@
|
|||||||
<option value="AI" th:disabled="${!aiEmbeddingConfigured}">AI 임베딩</option>
|
<option value="AI" th:disabled="${!aiEmbeddingConfigured}">AI 임베딩</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label class="span-2">검색 질문
|
<label>검색 질문
|
||||||
<textarea class="form-control" name="query" rows="3" placeholder="예: 기술 태그로 지식자료 검색 권한을 제한하는 방법" required></textarea>
|
<textarea class="form-control" name="query" rows="3" placeholder="예: 기술 태그로 지식자료 검색 권한을 제한하는 방법" required></textarea>
|
||||||
</label>
|
</label>
|
||||||
<div class="span-2 registration-actions"><button class="btn rw-btn-secondary" type="submit">검증 실행</button></div>
|
<div class="registration-actions"><button class="btn rw-btn-secondary" type="submit">검증 실행</button></div>
|
||||||
</form>
|
</form>
|
||||||
<section id="vector-search-result" class="mt-3" aria-live="polite">
|
<section id="vector-search-result" class="mt-3" aria-live="polite">
|
||||||
<div class="empty-result-guide">검증 사용자와 검색 질문을 입력하면 보호 결과를 표시합니다.</div>
|
<div class="empty-result-guide">검증 사용자와 검색 질문을 입력하면 보호 결과를 표시합니다.</div>
|
||||||
|
|||||||
@@ -104,6 +104,16 @@ UX-1부터 시작한다. 기존 데이터 모델과 VPD 로직은 바꾸지 않
|
|||||||
| 별도 Filter 관리 | 고급 접근 조건 | 예외 정책 기능 |
|
| 별도 Filter 관리 | 고급 접근 조건 | 예외 정책 기능 |
|
||||||
| 운영 상태 / 연결 설정 | 운영 현황 / 시스템 설정 | 운영성·연동 설정 기능 |
|
| 운영 상태 / 연결 설정 | 운영 현황 / 시스템 설정 | 운영성·연동 설정 기능 |
|
||||||
|
|
||||||
상단 메뉴는 DDS와 같은 네 업무 영역인 `권한 관리 · 보호 정책 관리 · 지식자료 관리 · 접근 검증`으로 구성한다. ORDS·MCP·Filter·시스템 설정은 기본 제품 메뉴에서 빼고 관련 화면의 고급 링크에서만 연다. 첫 줄에는 제품명·1차 메뉴·로그아웃을 함께 둔다. 1차 메뉴를 누르면 하위 메뉴가 팝업으로 뜨지 않고 바로 아래 전폭 줄에 표시된다. 현재 페이지가 속한 메뉴는 처음부터 아래 줄을 열어 둔다. 절차나 구현 방식이 아니라 사용자가 하려는 일을 메뉴명으로 쓴다. 메뉴와 기본 화면에는 `1.`, `2.`, 단계 레일, `01/02` 배지를 두지 않는다. 순서가 실제 입력 제약인 wizard에서만 단계 번호를 사용한다.
|
상단 메뉴는 DDS와 같은 네 업무 영역인 `권한 관리 · 보호 정책 관리 · 지식자료 관리 · 접근 검증`으로 구성한다. ORDS·MCP·Filter·시스템 설정은 기본 제품 메뉴에서 빼고 관련 화면의 고급 링크에서만 연다. 첫 줄에는 제품명·1차 메뉴·로그아웃을 함께 둔다. 1차 메뉴를 누르면 하위 메뉴가 팝업으로 뜨지 않고 바로 아래 전폭 줄에 표시된다. 현재 페이지가 속한 메뉴는 처음부터 아래 줄을 열어 둔다. 절차나 구현 방식이 아니라 사용자가 하려는 일을 메뉴명으로 쓴다. 메뉴와 기본 화면, wizard에도 `1.`, `2.`, 단계 레일, `01/02` 배지를 두지 않는다. 선택된 입력 영역은 역할·대상·접근 범위처럼 의미 있는 이름으로만 표시한다.
|
||||||
|
|
||||||
기본 페이지는 제목과 현재 설정·주요 행동만 노출한다. VPD/ORDS 처리 원리, 배경 설명, 주의사항은 상단의 공통 `도움말` 버튼을 눌렀을 때만 펼친다. 이 규칙으로 제품 화면이 관리 매뉴얼처럼 보이는 문제를 줄이고, 설정 자체를 전면에 둔다.
|
기본 페이지는 제목과 현재 설정·주요 행동만 노출한다. VPD/ORDS 처리 원리, 배경 설명, 주의사항은 상단의 공통 `도움말` 버튼을 눌렀을 때만 펼친다. 이 규칙으로 제품 화면이 관리 매뉴얼처럼 보이는 문제를 줄이고, 설정 자체를 전면에 둔다.
|
||||||
|
|
||||||
|
## 9. 화면 캡처 기반 정리 — 2026-07-01
|
||||||
|
|
||||||
|
로그인된 브라우저로 홈, 권한 주체·규칙·현황, 보호 상태·대상, 검증 세션·접근 검증·운영 현황, 지식자료·대화형 검색·검색 해석, 고급·설정 화면을 데스크톱 기준으로 캡처해 확인했다. MCP SSE와 Java client 화면은 데이터 조회 대기 때문에 브라우저 캡처를 완료하지 못했으며, 동일 템플릿·공통 레이아웃과 정적 화면 구조를 별도로 점검했다.
|
||||||
|
|
||||||
|
- 1차 메뉴는 첫 줄, 현재 업무 영역의 하위 메뉴는 둘째 줄에 나타나는 구조를 실제 사용자 관리 화면에서 확인했다.
|
||||||
|
- 기본 화면에 남아 있던 단계 번호를 역할·대상·접근 범위 같은 작업명으로 바꾸고, 검증 세션·접근 검증·연동 점검에서도 순번을 제거한다.
|
||||||
|
- 접근 규칙 목록은 ID·고정 ACTION·삭제 경고를 기본 열에서 빼고, 필요한 삭제 영향만 행의 `관리` 상세에서 확인하게 한다.
|
||||||
|
- 보호 상태의 동작 설명은 상단 도움말로 옮기고, 기본 화면은 보호 대상과 적용 작업부터 보이게 한다.
|
||||||
|
- 운영 현황은 긴 ORDS·VPD 값 때문에 오른쪽 열이 밀리지 않도록 고정 열 폭과 줄바꿈을 적용한다.
|
||||||
|
|||||||
@@ -647,7 +647,6 @@ body {
|
|||||||
.wizard-panel-heading {
|
.wizard-panel-heading {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: .75rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.wizard-panel-heading h3 {
|
.wizard-panel-heading h3 {
|
||||||
@@ -1369,6 +1368,55 @@ body {
|
|||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.permission-list-table {
|
||||||
|
min-width: 760px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-list-table th:nth-child(1) { width: 15%; }
|
||||||
|
.permission-list-table th:nth-child(2) { width: 19%; }
|
||||||
|
.permission-list-table th:nth-child(3) { width: 10%; }
|
||||||
|
.permission-list-table th:nth-child(4) { width: 24%; }
|
||||||
|
.permission-list-table th:nth-child(5) { width: 16%; }
|
||||||
|
.permission-list-table th:nth-child(6) { width: 16%; }
|
||||||
|
|
||||||
|
.row-management {
|
||||||
|
min-width: 10rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-management > summary {
|
||||||
|
color: var(--rw-primary);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: .84rem;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-management[open] {
|
||||||
|
background: var(--rw-surface-muted);
|
||||||
|
border: 1px solid var(--rw-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: .65rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation-status-table {
|
||||||
|
table-layout: fixed;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation-status-table th,
|
||||||
|
.operation-status-table td,
|
||||||
|
.operation-status-table code {
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation-status-table th:nth-child(1) { width: 5%; }
|
||||||
|
.operation-status-table th:nth-child(2) { width: 22%; }
|
||||||
|
.operation-status-table th:nth-child(3) { width: 29%; }
|
||||||
|
.operation-status-table th:nth-child(4) { width: 20%; }
|
||||||
|
.operation-status-table th:nth-child(5) { width: 10%; }
|
||||||
|
.operation-status-table th:nth-child(6) { width: 9%; }
|
||||||
|
.operation-status-table th:nth-child(7) { width: 5%; }
|
||||||
|
|
||||||
.schema-summary {
|
.schema-summary {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: .75rem;
|
gap: .75rem;
|
||||||
|
|||||||
@@ -36,17 +36,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mcp-service-grid">
|
<div class="mcp-service-grid">
|
||||||
<div class="mcp-service-item">
|
<div class="mcp-service-item">
|
||||||
<span>1</span>
|
<span>연결 시작</span>
|
||||||
<strong><code>initialize</code></strong>
|
<strong><code>initialize</code></strong>
|
||||||
<small>기본 MCP serverInfo와 tools capability를 확인합니다.</small>
|
<small>기본 MCP serverInfo와 tools capability를 확인합니다.</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="mcp-service-item">
|
<div class="mcp-service-item">
|
||||||
<span>2</span>
|
<span>도구 목록</span>
|
||||||
<strong><code>tools/list</code></strong>
|
<strong><code>tools/list</code></strong>
|
||||||
<small>현재 보호 객체에서 생성된 ORDS query tool과 설명/schema를 조회합니다.</small>
|
<small>현재 보호 객체에서 생성된 ORDS query tool과 설명/schema를 조회합니다.</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="mcp-service-item">
|
<div class="mcp-service-item">
|
||||||
<span>3</span>
|
<span>도구 호출</span>
|
||||||
<strong><code>tools/call</code></strong>
|
<strong><code>tools/call</code></strong>
|
||||||
<small>tool 선택은 reasoning 이후 MCP client가 수행하므로 이 화면에서는 호출하지 않습니다.</small>
|
<small>tool 선택은 reasoning 이후 MCP client가 수행하므로 이 화면에서는 호출하지 않습니다.</small>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<section class="content-band">
|
<section class="content-band">
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-sm align-middle">
|
<table class="table table-sm align-middle operation-status-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Health</th>
|
<th>Health</th>
|
||||||
|
|||||||
@@ -22,11 +22,11 @@
|
|||||||
<p class="section-description">저장 전 적용 대상과 행 범위를 검토한 뒤 VPD 권한을 반영합니다.</p>
|
<p class="section-description">저장 전 적용 대상과 행 범위를 검토한 뒤 VPD 권한을 반영합니다.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="wizard-progress" aria-label="권한 추가 단계">
|
<div class="wizard-progress" aria-label="권한 추가 단계">
|
||||||
<button class="wizard-step-indicator active" type="button" data-wizard-target="1">1 역할</button>
|
<button class="wizard-step-indicator active" type="button" data-wizard-target="1">역할</button>
|
||||||
<button class="wizard-step-indicator" type="button" data-wizard-target="2">2 객체</button>
|
<button class="wizard-step-indicator" type="button" data-wizard-target="2">대상</button>
|
||||||
<button class="wizard-step-indicator" type="button" data-wizard-target="3">3 행 규칙</button>
|
<button class="wizard-step-indicator" type="button" data-wizard-target="3">접근 범위</button>
|
||||||
<button class="wizard-step-indicator" type="button" data-wizard-target="4">4 컬럼</button>
|
<button class="wizard-step-indicator" type="button" data-wizard-target="4">표시 예외</button>
|
||||||
<button class="wizard-step-indicator" type="button" data-wizard-target="5">5 검토</button>
|
<button class="wizard-step-indicator" type="button" data-wizard-target="5">검토</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form method="post" action="/permissions" class="permission-wizard" data-permission-wizard>
|
<form method="post" action="/permissions" class="permission-wizard" data-permission-wizard>
|
||||||
@@ -61,7 +61,6 @@
|
|||||||
|
|
||||||
<div class="wizard-panel active" data-wizard-step="1">
|
<div class="wizard-panel active" data-wizard-step="1">
|
||||||
<div class="wizard-panel-heading">
|
<div class="wizard-panel-heading">
|
||||||
<span class="wizard-step-number">1</span>
|
|
||||||
<div>
|
<div>
|
||||||
<h3>역할 선택</h3>
|
<h3>역할 선택</h3>
|
||||||
<p>권한을 부여하거나 거부할 역할을 선택합니다.</p>
|
<p>권한을 부여하거나 거부할 역할을 선택합니다.</p>
|
||||||
@@ -96,7 +95,6 @@
|
|||||||
|
|
||||||
<div class="wizard-panel" data-wizard-step="2">
|
<div class="wizard-panel" data-wizard-step="2">
|
||||||
<div class="wizard-panel-heading">
|
<div class="wizard-panel-heading">
|
||||||
<span class="wizard-step-number">2</span>
|
|
||||||
<div>
|
<div>
|
||||||
<h3>보호 객체 선택</h3>
|
<h3>보호 객체 선택</h3>
|
||||||
<p>등록된 보호 객체 또는 DB 스키마 객체를 선택합니다.</p>
|
<p>등록된 보호 객체 또는 DB 스키마 객체를 선택합니다.</p>
|
||||||
@@ -138,7 +136,6 @@
|
|||||||
|
|
||||||
<div class="wizard-panel" data-wizard-step="3">
|
<div class="wizard-panel" data-wizard-step="3">
|
||||||
<div class="wizard-panel-heading">
|
<div class="wizard-panel-heading">
|
||||||
<span class="wizard-step-number">3</span>
|
|
||||||
<div>
|
<div>
|
||||||
<h3>권한 효과와 행 규칙</h3>
|
<h3>권한 효과와 행 규칙</h3>
|
||||||
<p>허용/거부 방향과 VPD 행 필터 조건을 설정합니다.</p>
|
<p>허용/거부 방향과 VPD 행 필터 조건을 설정합니다.</p>
|
||||||
@@ -186,7 +183,6 @@
|
|||||||
|
|
||||||
<div class="wizard-panel" data-wizard-step="4">
|
<div class="wizard-panel" data-wizard-step="4">
|
||||||
<div class="wizard-panel-heading">
|
<div class="wizard-panel-heading">
|
||||||
<span class="wizard-step-number">4</span>
|
|
||||||
<div>
|
<div>
|
||||||
<h3>권한별 원문 표시 예외</h3>
|
<h3>권한별 원문 표시 예외</h3>
|
||||||
<p>이 역할이 이미 볼 수 있는 행에서 마스킹을 제외할 컬럼을 여러 개 선택합니다.</p>
|
<p>이 역할이 이미 볼 수 있는 행에서 마스킹을 제외할 컬럼을 여러 개 선택합니다.</p>
|
||||||
@@ -211,7 +207,6 @@
|
|||||||
|
|
||||||
<div class="wizard-panel" data-wizard-step="5">
|
<div class="wizard-panel" data-wizard-step="5">
|
||||||
<div class="wizard-panel-heading">
|
<div class="wizard-panel-heading">
|
||||||
<span class="wizard-step-number">5</span>
|
|
||||||
<div>
|
<div>
|
||||||
<h3>저장 전 검토</h3>
|
<h3>저장 전 검토</h3>
|
||||||
<p>저장될 권한과 예상 effective policy를 확인합니다.</p>
|
<p>저장될 권한과 예상 effective policy를 확인합니다.</p>
|
||||||
@@ -245,44 +240,39 @@
|
|||||||
<section class="content-band">
|
<section class="content-band">
|
||||||
<h2>접근 규칙 목록</h2>
|
<h2>접근 규칙 목록</h2>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-sm align-middle">
|
<table class="table table-sm align-middle permission-list-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
|
||||||
<th>역할</th>
|
<th>역할</th>
|
||||||
<th>테이블/뷰</th>
|
<th>보호 대상</th>
|
||||||
<th>Action</th>
|
<th>효과</th>
|
||||||
<th>Effect</th>
|
<th>접근 범위</th>
|
||||||
<th>행 규칙</th>
|
<th>표시 예외</th>
|
||||||
<th>적용 필터</th>
|
<th>관리</th>
|
||||||
<th>원문 표시 허용 컬럼(여러 개)</th>
|
|
||||||
<th>삭제 영향</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr th:each="permission : ${permissions}"
|
<tr th:each="permission : ${permissions}"
|
||||||
th:with="lastPermission=${lastPermissionByPermissionId[permission.permissionId()]}">
|
th:with="lastPermission=${lastPermissionByPermissionId[permission.permissionId()]}">
|
||||||
<td th:text="${permission.permissionId()}">100</td>
|
|
||||||
<td th:text="${permission.roleName()}">HR_DEPT_ROLE</td>
|
<td th:text="${permission.roleName()}">HR_DEPT_ROLE</td>
|
||||||
<td th:text="${permission.objectName()}">CB_V_SEARCH_DOCUMENTS</td>
|
<td th:text="${permission.objectName()}">CB_V_SEARCH_DOCUMENTS</td>
|
||||||
<td th:text="${permission.action()}">SELECT</td>
|
|
||||||
<td>
|
<td>
|
||||||
<span class="badge"
|
<span class="badge"
|
||||||
th:classappend="${permission.permissionEffect() == 'DENY'} ? ' text-bg-danger' : ' text-bg-success'"
|
th:classappend="${permission.permissionEffect() == 'DENY'} ? ' text-bg-danger' : ' text-bg-success'"
|
||||||
th:text="${permission.permissionEffect()}">ALLOW</span>
|
th:text="${permission.permissionEffect()}">ALLOW</span>
|
||||||
</td>
|
</td>
|
||||||
<td th:text="${permission.rules()} ?: '-'">ALL</td>
|
<td><pre class="table-pre" th:text="${permission.filterPreview()} ?: ${permission.rules()} ?: '-'">ALL ROWS</pre></td>
|
||||||
<td><pre class="table-pre" th:text="${permission.filterPreview()} ?: '-'">ALL ROWS</pre></td>
|
|
||||||
<td th:text="${permission.visibleColumns()} ?: '-'">CONTENTS</td>
|
<td th:text="${permission.visibleColumns()} ?: '-'">CONTENTS</td>
|
||||||
<td class="delete-impact">
|
|
||||||
<span>역할이 이 TABLE/VIEW의 SELECT 권한을 잃습니다.</span>
|
|
||||||
<strong th:text="${permission.roleName() + ' -> ' + permission.objectName()}">ROLE -> OBJECT</strong>
|
|
||||||
<small th:if="${lastPermission}" class="text-danger">이 객체의 마지막 권한입니다. 삭제 후 보호 객체가 비활성화됩니다.</small>
|
|
||||||
<small th:unless="${lastPermission}">같은 객체에 다른 역할 권한이 남아 있습니다.</small>
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
<form method="post" action="/permissions/delete" class="inline-form">
|
<details class="row-management">
|
||||||
|
<summary>관리</summary>
|
||||||
|
<div class="delete-impact">
|
||||||
|
<span>이 역할은 이 대상의 SELECT 권한을 잃습니다.</span>
|
||||||
|
<strong th:text="${permission.roleName() + ' → ' + permission.objectName()}">ROLE → OBJECT</strong>
|
||||||
|
<small th:if="${lastPermission}" class="text-danger">마지막 권한입니다. 삭제 후 보호 대상이 비활성화될 수 있습니다.</small>
|
||||||
|
<small th:unless="${lastPermission}">같은 대상에 다른 역할 권한이 남아 있습니다.</small>
|
||||||
|
</div>
|
||||||
|
<form method="post" action="/permissions/delete" class="inline-form mt-2">
|
||||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||||
<input type="hidden" name="permissionId" th:value="${permission.permissionId()}">
|
<input type="hidden" name="permissionId" th:value="${permission.permissionId()}">
|
||||||
<label class="delete-confirm">
|
<label class="delete-confirm">
|
||||||
@@ -291,10 +281,11 @@
|
|||||||
</label>
|
</label>
|
||||||
<button class="btn btn-sm btn-outline-danger" type="submit">삭제</button>
|
<button class="btn btn-sm btn-outline-danger" type="submit">삭제</button>
|
||||||
</form>
|
</form>
|
||||||
|
</details>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr th:if="${#lists.isEmpty(permissions)}">
|
<tr th:if="${#lists.isEmpty(permissions)}">
|
||||||
<td colspan="10" class="text-muted">등록된 권한이 없습니다.</td>
|
<td colspan="6" class="text-muted">등록된 접근 규칙이 없습니다.</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
<form hx-post="/probe" hx-target="#probe-result" hx-swap="innerHTML" class="form-grid probe-form">
|
<form hx-post="/probe" hx-target="#probe-result" hx-swap="innerHTML" class="form-grid probe-form">
|
||||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||||
<label class="span-2">
|
<label class="span-2">
|
||||||
1. 발급받은 토큰 원문
|
발급받은 토큰 원문
|
||||||
<input class="form-control" name="bearerToken" type="password" autocomplete="off"
|
<input class="form-control" name="bearerToken" type="password" autocomplete="off"
|
||||||
placeholder="토큰 발급 직후 복사한 값을 붙여 넣으세요">
|
placeholder="토큰 발급 직후 복사한 값을 붙여 넣으세요">
|
||||||
<span class="form-hint">원문은 DB에 저장되지 않습니다. 아래 검증 세션 사용자를 선택하면 이 입력은 무시되고 실행 중 발급·완료 즉시 폐기됩니다. 둘 다 비우면 invalid token 결과를 확인할 수 있습니다.</span>
|
<span class="form-hint">원문은 DB에 저장되지 않습니다. 아래 검증 세션 사용자를 선택하면 이 입력은 무시되고 실행 중 발급·완료 즉시 폐기됩니다. 둘 다 비우면 invalid token 결과를 확인할 수 있습니다.</span>
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
<span class="form-hint">ORDS 호출에 사용할 10분 검증 세션을 만들고 완료 후 바로 폐기합니다.</span>
|
<span class="form-hint">ORDS 호출에 사용할 10분 검증 세션을 만들고 완료 후 바로 폐기합니다.</span>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
2. 확인할 데이터
|
확인할 데이터
|
||||||
<select class="form-select" name="objectId" required>
|
<select class="form-select" name="objectId" required>
|
||||||
<option th:each="object : ${objects}"
|
<option th:each="object : ${objects}"
|
||||||
th:value="${object.objectId()}"
|
th:value="${object.objectId()}"
|
||||||
@@ -77,18 +77,18 @@
|
|||||||
</select>
|
</select>
|
||||||
<span class="form-hint">자료 등록 때 사용한 방식·차원과 맞춰야 합니다. AI 설정이 없으면 로컬 임베딩을 사용하세요.</span>
|
<span class="form-hint">자료 등록 때 사용한 방식·차원과 맞춰야 합니다. AI 설정이 없으면 로컬 임베딩을 사용하세요.</span>
|
||||||
</label>
|
</label>
|
||||||
<button class="btn rw-btn-primary probe-submit" type="submit">3. 접근 검증 실행</button>
|
<button class="btn rw-btn-primary probe-submit" type="submit">접근 검증 실행</button>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="probe-result" class="content-band" aria-live="polite">
|
<section id="probe-result" class="content-band" aria-live="polite">
|
||||||
<div class="empty-result-guide">
|
<div class="empty-result-guide">
|
||||||
<strong>결과는 세 가지 순서로 설명합니다.</strong>
|
<strong>검증 결과</strong>
|
||||||
<ol>
|
<ul>
|
||||||
<li>토큰이 어떤 사용자와 역할로 해석됐는지</li>
|
<li>토큰이 어떤 사용자와 역할로 해석됐는지</li>
|
||||||
<li>VPD 적용 후 실제로 몇 행이 보였는지</li>
|
<li>VPD 적용 후 실제로 몇 행이 보였는지</li>
|
||||||
<li>예상과 다를 때 어디를 확인해야 하는지</li>
|
<li>예상과 다를 때 어디를 확인해야 하는지</li>
|
||||||
</ol>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
<h1>역할</h1>
|
<h1>역할</h1>
|
||||||
<details class="explanation-details">
|
<details class="explanation-details">
|
||||||
<summary>도움말</summary>
|
<summary>도움말</summary>
|
||||||
<p>사용자에게 부여할 역할을 관리합니다. 표시 보호 등급 상한은 허용된 행에서 원문으로 볼 수 있는 컬럼 등급의 참고값이며, 행 접근 권한은 권한 규칙과 VPD에서 결정합니다.</p>
|
<p th:if="${backofficeTrack == 'DDS'}">역할은 사용자와 접근 태그 규칙을 연결하는 기준입니다. 실제 접근 범위는 접근 규칙에서 관리합니다.</p>
|
||||||
|
<p th:unless="${backofficeTrack == 'DDS'}">사용자에게 부여할 역할을 관리합니다. 표시 보호 등급 상한은 허용된 행에서 원문으로 볼 수 있는 컬럼 등급의 참고값이며, 행 접근 권한은 권한 규칙과 VPD에서 결정합니다.</p>
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -19,7 +20,8 @@
|
|||||||
<h2>역할 추가</h2>
|
<h2>역할 추가</h2>
|
||||||
<form method="post" action="/roles" class="form-grid">
|
<form method="post" action="/roles" class="form-grid">
|
||||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||||
<label>
|
<input th:if="${backofficeTrack == 'DDS'}" type="hidden" name="maxSensitivityLevel" value="PUBLIC">
|
||||||
|
<label th:unless="${backofficeTrack == 'DDS'}">
|
||||||
역할명
|
역할명
|
||||||
<input class="form-control" name="roleName" placeholder="HR_DEPT_ROLE" required>
|
<input class="form-control" name="roleName" placeholder="HR_DEPT_ROLE" required>
|
||||||
</label>
|
</label>
|
||||||
@@ -49,7 +51,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th>ID</th>
|
||||||
<th>역할명</th>
|
<th>역할명</th>
|
||||||
<th>표시 보호 등급 상한</th>
|
<th th:unless="${backofficeTrack == 'DDS'}">표시 보호 등급 상한</th>
|
||||||
<th>삭제 영향</th>
|
<th>삭제 영향</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -60,7 +62,7 @@
|
|||||||
hasDependencies=${impact != null && (!#lists.isEmpty(impact.directUsers()) || !#lists.isEmpty(impact.groups()) || impact.permissionCount() > 0)}">
|
hasDependencies=${impact != null && (!#lists.isEmpty(impact.directUsers()) || !#lists.isEmpty(impact.groups()) || impact.permissionCount() > 0)}">
|
||||||
<td th:text="${role.roleId()}">10</td>
|
<td th:text="${role.roleId()}">10</td>
|
||||||
<td th:text="${role.roleName()}">HR_DEPT_ROLE</td>
|
<td th:text="${role.roleName()}">HR_DEPT_ROLE</td>
|
||||||
<td>
|
<td th:unless="${backofficeTrack == 'DDS'}">
|
||||||
<span class="badge text-bg-light" th:title="${'허용된 행의 컬럼 표시 상한: ' + role.maxSensitivityLevel()}"
|
<span class="badge text-bg-light" th:title="${'허용된 행의 컬럼 표시 상한: ' + role.maxSensitivityLevel()}"
|
||||||
th:text="${role.maxSensitivityLevel()}">PUBLIC</span>
|
th:text="${role.maxSensitivityLevel()}">PUBLIC</span>
|
||||||
<form method="post" action="/roles/max-sensitivity" class="inline-form">
|
<form method="post" action="/roles/max-sensitivity" class="inline-form">
|
||||||
@@ -111,7 +113,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr th:if="${#lists.isEmpty(roles)}">
|
<tr th:if="${#lists.isEmpty(roles)}">
|
||||||
<td colspan="5" class="text-muted">등록된 역할이 없습니다.</td>
|
<td th:colspan="${backofficeTrack == 'DDS'} ? 4 : 5" class="text-muted">등록된 역할이 없습니다.</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
<section class="content-band">
|
<section class="content-band">
|
||||||
<div class="section-heading">
|
<div class="section-heading">
|
||||||
<div>
|
<div>
|
||||||
<h2>1. 검증할 사용자 선택</h2>
|
<h2>검증 사용자 선택</h2>
|
||||||
<p class="section-subtitle">토큰에는 사용자의 직접 역할과 그룹 상속 역할이 연결됩니다. 권한 자체를 토큰에 복사하지 않으므로 이후 권한 변경도 동적으로 반영됩니다.</p>
|
<p class="section-subtitle">토큰에는 사용자의 직접 역할과 그룹 상속 역할이 연결됩니다. 권한 자체를 토큰에 복사하지 않으므로 이후 권한 변경도 동적으로 반영됩니다.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
용도 메모
|
용도 메모
|
||||||
<input class="form-control" name="description" maxlength="200" placeholder="예: HR 권한 확인">
|
<input class="form-control" name="description" maxlength="200" placeholder="예: HR 권한 확인">
|
||||||
</label>
|
</label>
|
||||||
<button class="btn rw-btn-primary" type="submit">2. 검증 세션 발급</button>
|
<button class="btn rw-btn-primary" type="submit">검증 세션 발급</button>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<h1>보호 상태</h1>
|
<h1>보호 상태</h1>
|
||||||
<details class="explanation-details">
|
<details class="explanation-details">
|
||||||
<summary>도움말</summary>
|
<summary>도움말</summary>
|
||||||
<p>권한 화면에서 만든 사용자·그룹·역할·행·열 규칙을 Oracle VPD가 실제 TABLE/VIEW에 적용하도록 연결합니다.</p>
|
<p>권한 화면에서 만든 사용자·그룹·역할·행·열 규칙을 Oracle VPD가 실제 TABLE/VIEW에 적용하도록 연결합니다. Policy는 어느 객체의 어떤 SQL에 어떤 Filter function을 붙일지 정하고, 실제 허용 조건은 권한 규칙과 Filter가 계산합니다.</p>
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -22,32 +22,8 @@
|
|||||||
<section class="content-band">
|
<section class="content-band">
|
||||||
<div class="section-heading">
|
<div class="section-heading">
|
||||||
<div>
|
<div>
|
||||||
<span class="architecture-kicker">기본 동작</span>
|
<h2>보호 대상</h2>
|
||||||
<h2>권한체계 자동 적용</h2>
|
<p class="section-subtitle">VPD 적용과 검증 준비 상태를 확인합니다.</p>
|
||||||
</div>
|
|
||||||
<a class="btn btn-sm rw-btn-secondary" href="/permissions">권한 규칙 확인</a>
|
|
||||||
</div>
|
|
||||||
<div class="policy-apply-flow" aria-label="VPD 적용 계층">
|
|
||||||
<span>사용자·그룹·역할</span>
|
|
||||||
<strong>→</strong>
|
|
||||||
<span>객체별 행·열 권한</span>
|
|
||||||
<strong>→</strong>
|
|
||||||
<span>동적 VPD</span>
|
|
||||||
<strong>→</strong>
|
|
||||||
<span>허용된 데이터만 반환</span>
|
|
||||||
</div>
|
|
||||||
<p class="text-muted mb-0"><code>CB_AGENT_DOC_VPD_FILTER</code>가 요청마다 권한체계를 읽습니다. 일반 변경은 <a href="/permissions">권한 규칙</a>에서 합니다.</p>
|
|
||||||
<details class="explanation-details mt-3">
|
|
||||||
<summary>Policy와 Filter의 역할 구분 보기</summary>
|
|
||||||
<p>Policy는 “어느 객체의 어떤 SQL에 어떤 Filter function을 붙일지”를 정합니다. 행의 실제 허용/차단 조건은 권한 규칙과 Filter function이 계산하고, 컬럼 표시 보호는 별도 설정입니다.</p>
|
|
||||||
</details>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="content-band">
|
|
||||||
<div class="section-heading">
|
|
||||||
<div>
|
|
||||||
<h2>보호할 수 있는 DB 객체</h2>
|
|
||||||
<p class="section-subtitle">VPD 적용 상태와 권한·검증 경로를 확인합니다.</p>
|
|
||||||
<details class="explanation-details">
|
<details class="explanation-details">
|
||||||
<summary>객체 상태 항목 설명 보기</summary>
|
<summary>객체 상태 항목 설명 보기</summary>
|
||||||
<p>VPD가 붙었는지, 권한 규칙과 검증 경로가 준비됐는지 한곳에서 확인합니다. 아래 기본 적용에서는 객체만 선택하면 됩니다.</p>
|
<p>VPD가 붙었는지, 권한 규칙과 검증 경로가 준비됐는지 한곳에서 확인합니다. 아래 기본 적용에서는 객체만 선택하면 됩니다.</p>
|
||||||
|
|||||||
@@ -59,6 +59,28 @@ class GuidedFlowTemplateTest {
|
|||||||
.doesNotContain("1 · CONTENT", "2 · ACCESS POLICY", "3 · SEARCH");
|
.doesNotContain("1 · CONTENT", "2 · ACCESS POLICY", "3 · SEARCH");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void productFormsUseTaskNamesInsteadOfVisibleStepNumbers() throws IOException {
|
||||||
|
String permissions = template("permissions.html");
|
||||||
|
String tokens = template("tokens.html");
|
||||||
|
String probe = template("probe.html");
|
||||||
|
String client = template("mcp-client-demo.html");
|
||||||
|
|
||||||
|
assertThat(permissions)
|
||||||
|
.contains("data-wizard-target=\"1\">역할")
|
||||||
|
.contains("data-wizard-target=\"2\">대상")
|
||||||
|
.doesNotContain(">1 역할<", ">2 객체<", "wizard-step-number");
|
||||||
|
assertThat(tokens)
|
||||||
|
.contains("검증 사용자 선택", "검증 세션 발급")
|
||||||
|
.doesNotContain("1. 검증할 사용자 선택", "2. 검증 세션 발급");
|
||||||
|
assertThat(probe)
|
||||||
|
.contains("접근 검증 실행", "<strong>검증 결과</strong>")
|
||||||
|
.doesNotContain("1. 발급받은 토큰 원문", "2. 확인할 데이터", "3. 접근 검증 실행");
|
||||||
|
assertThat(client)
|
||||||
|
.contains("연결 시작", "도구 목록", "도구 호출")
|
||||||
|
.doesNotContain("<span>1</span>", "<span>2</span>", "<span>3</span>");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void probeUsesOneTokenInputAndHidesTechnicalExchangeByDefault() throws IOException {
|
void probeUsesOneTokenInputAndHidesTechnicalExchangeByDefault() throws IOException {
|
||||||
String probe = template("probe.html");
|
String probe = template("probe.html");
|
||||||
|
|||||||
Reference in New Issue
Block a user