Compare commits

..

3 Commits

Author SHA1 Message Date
devmrko
be14b4c797 [UX] #577 present VPD as a product console 2026-07-01 08:06:03 +09:00
devmrko
45621e1b7d ui: reshape DDS navigation as management console 2026-07-01 08:04:39 +09:00
devmrko
12b7e00cef ui: adopt product language for DDS console 2026-07-01 07:57:52 +09:00
37 changed files with 336 additions and 380 deletions

View File

@@ -67,6 +67,14 @@ body { margin: 0; background: var(--dds-bg); color: var(--dds-ink); font-family:
.journey-card-compact h2 { margin: 0 0 .25rem; }
.journey-card-compact strong { font-size: .8rem; }
.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; }
.explanation-details > summary { color: #43516a; font-size: .86rem; }
.explanation-details > summary::marker { color: var(--dds-accent); }
.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.status-danger { background: #fffafa; border-color: #f2c8c8; }
@@ -104,4 +112,5 @@ body { margin: 0; background: var(--dds-bg); color: var(--dds-ink); font-family:
.panel { padding: 19px; }
.hero { padding-top: 30px; }
.protection-status-topline, .protection-status-footer { align-items: flex-start; flex-direction: column; }
.dds-product-nav { border-radius: 8px; }
}

View File

@@ -1,33 +1,33 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('DDS 권한 백오피스')}"></head>
<head th:replace="~{fragments/layout :: head('DDS 권한 운영')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container py-4">
<header class="page-title guided-hero">
<span class="architecture-kicker">DEEP DATA SECURITY · INDEPENDENT TRACK</span>
<h1>DDS 권한 관리</h1>
<p class="context-summary">권한 설계 · 보호 연결 · 검색 검증</p>
<span class="architecture-kicker">DEEP DATA SECURITY</span>
<h1>DDS 권한 운영</h1>
<p class="context-summary">권한 관리 · 보호 정책 관리 · 지식자료 관리 · 접근 검증</p>
<details class="explanation-details">
<summary>이 인스턴스가 하는 일</summary>
<p>8083 DDS 인스턴스는 8082 VPD 인스턴스와 별도로 실행됩니다. 두 화면은 사용자·그룹·역할·권한 규칙을 같은 관리 흐름으로 보여주며, DDS는 보호 객체별 DATA GRANT로 그 규칙을 집행합니다.</p>
<p class="mb-0">일상적인 권한 변경은 권한 관리에서 하고, DDS 보호 객체와 실제 결과는 이 인스턴스에서 확인합니다.</p>
<summary>도움말</summary>
<p>사용자·그룹·역할·접근 규칙을 기준으로 지식자료의 접근 범위를 운영합니다. DDS는 보호 객체별 DATA GRANT로 이 기준을 집행합니다.</p>
<p class="mb-0">일상적인 권한 변경은 권한 관리에서 하고, 이 공간에서는 보호 상태와 실제 접근 결과를 확인합니다.</p>
</details>
</header>
<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"><span class="journey-number">1</span><div><h2>권한 설계</h2><strong>열기 →</strong></div></a>
<a class="journey-card journey-card-compact" href="/dds-protection"><span class="journey-number">2</span><div><h2>DDS 보호 상태</h2><strong>열기 →</strong></div></a>
<a class="journey-card journey-card-compact" href="/dds-provision"><span class="journey-number">3</span><div><h2>DDS 권한 연결</h2><strong>열기 →</strong></div></a>
<a class="journey-card journey-card-compact" href="/vector-knowledge"><span class="journey-number">4</span><div><h2>권한 기반 검색</h2><strong>열기 →</strong></div></a>
<a class="journey-card journey-card-compact" href="/dds"><span class="journey-number">5</span><div><h2>보호 객체 확인</h2><strong>열기 →</strong></div></a>
<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>
<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>
@@ -38,17 +38,17 @@
<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(permissions)}">0</strong></a>
<a class="summary-tile" href="/dds"><span class="label">보조 직접 비교 사용자</span><strong th:text="${configuredUserCount}">0</strong></a>
<a class="summary-tile" href="/dds"><span class="label">직접 접근 검증 프로필</span><strong th:text="${configuredUserCount}">0</strong></a>
</section>
<section class="content-band">
<div class="section-heading"><div><h2>현재 DDS 검증 대상</h2></div></div>
<div class="section-heading"><div><h2>현재 보호 대상</h2></div></div>
<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>
</tbody></table></div>
<details class="explanation-details mt-3">
<summary>검증 대상 설명 보</summary>
<p class="mb-0">시나리오는 업무 분류값이 붙은 벡터 지식자료 검색입니다. 원본 테이블은 DDS 권한 동작을 비교하는 보조 기술 검증에만 사용합니다.</p>
<summary>술 상세</summary>
<p class="mb-0">대상은 업무 분류값이 붙은 벡터 지식자료 검색입니다. 원본 테이블은 직접 접근 비교를 위한 고급 검증에만 사용합니다.</p>
</details>
</section>
</main>

View File

@@ -1,25 +1,25 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('DDS 권한 반영')}"></head>
<head th:replace="~{fragments/layout :: head('정책 적용')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container py-4">
<div class="page-title">
<span class="architecture-kicker">DDS · PUBLISH</span>
<h1>애플리케이션 권한을 DDS에 반영</h1>
<p class="context-summary">사용자·그룹·역할·테이블 권한을 계산한 뒤 DDS DATA GRANT로 게시합니다.</p>
<h1>보호 정책 적용</h1>
<p class="context-summary">사용자·그룹·역할의 접근 규칙을 검토한 뒤 DDS 보호 규칙에 반영합니다.</p>
<details class="explanation-details">
<summary>VPD와 DDS의 반영 차이 보기</summary>
<summary>도움말</summary>
<p>이 화면은 직접 DDS END USER 비교 경로와 일반 객체의 선언형 Grant를 미리 보고 게시하는 단계입니다. 토큰 기반 벡터 경로는 별도 객체별 Data Grant predicate가 요청 시 공통 권한 테이블을 다시 평가합니다.</p>
<p class="mb-0">그룹 자체를 DDS 그룹으로 복사하지는 않습니다. 애플리케이션 그룹의 역할 상속을 계산해 직접 비교용 DATA ROLE Grant에 반영하고, 토큰 경로에서는 같은 effective role 계산을 predicate 함수가 사용합니다.</p>
</details>
</div>
<section th:replace="~{fragments/layout :: architectureStrip('protection')}"></section>
<section th:replace="~{fragments/layout :: architectureStrip('publish')}"></section>
<section class="content-band">
<details class="explanation-details">
<summary>VPD 권한과 DDS 객체의 매핑 기준</summary>
<summary>도움말</summary>
<div class="table-responsive mt-3">
<table class="table table-sm align-middle mb-0">
<thead><tr><th>공통 권한 관리</th><th>DDS에서의 표현</th><th>반영 시점</th></tr></thead>
@@ -43,23 +43,23 @@
<section class="content-band">
<div class="section-heading">
<div>
<span class="architecture-kicker">1 · COMPILE</span>
<h2>현재 유효 권한 미리보기</h2>
<p class="section-subtitle">직접 부여된 역할과 활성 그룹에서 상속된 역할을 합쳐 DDS Grant를 만듭니다.</p>
<span class="architecture-kicker">변경 검토</span>
<h2>게시 전 변경 내용</h2>
<p class="section-subtitle">직접 부여된 역할과 활성 그룹 역할을 합쳐 보호 규칙을 만듭니다.</p>
</div>
<span class="badge text-bg-secondary" th:text="${plan.mappedUserCount() + '명 매핑'}">0명 매핑</span>
</div>
<div class="alert alert-info" th:if="${plan.publishableCount() == 0}">
게시할 수 있는 ALLOW 권한이 없습니다. 보호 객체 매핑과 권한 규칙을 먼저 확인하세요.
게시할 접근 허용 규칙이 없습니다. 보호 객체 매핑과 접근 규칙을 먼저 확인하세요.
</div>
<form method="post" action="/dds-provision/publish" class="mb-3" th:if="${plan.publishableCount() > 0}">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
<button class="btn rw-btn-primary" type="submit"
onclick="return confirm('현재 미리보기대로 DDS DATA GRANT를 교체하고 권한 없는 Grant를 회수할까요?')">미리보기대로 DDS에 게시</button>
onclick="return confirm('현재 미리보기대로 보호 규칙을 게시하고 더 이상 허용되지 않는 규칙을 회수할까요?')">변경 내용 게시</button>
</form>
<div class="table-responsive">
<table class="table table-sm align-middle">
<thead><tr><th>애플리케이션 주체</th><th>애플리케이션 객체</th><th>DDS 주체/객체</th><th>판정</th><th>predicate</th><th>컬럼 제외</th></tr></thead>
<thead><tr><th>접근 주체</th><th>관리 대상</th><th>보호 대상</th><th>판정</th><th>접근 조건</th><th>제한 컬럼</th></tr></thead>
<tbody>
<tr th:each="grant : ${plan.grants()}">
<td><strong th:text="${grant.userLabel()}">사용자</strong><small class="d-block text-muted" th:text="${'userId=' + grant.applicationUserId() + ' · ' + grant.dataRole()}">mapping</small></td>
@@ -73,14 +73,14 @@
<td><code th:text="${grant.predicate() ?: '-'}">predicate</code></td>
<td th:text="${grant.excludedColumns() ?: '없음'}">없음</td>
</tr>
<tr th:if="${#lists.isEmpty(plan.grants())}"><td colspan="6" class="text-muted">매핑된 유효 권한에서 DDS Grant 대상을 찾지 못했습니다.</td></tr>
<tr th:if="${#lists.isEmpty(plan.grants())}"><td colspan="6" class="text-muted">현재 접근 규칙에서 게시할 보호 대상을 찾지 못했습니다.</td></tr>
</tbody>
</table>
</div>
</section>
<section class="content-band" th:if="${plan.hasWarnings()}">
<div class="section-heading"><div><h2>반영 전 확인할 항목</h2><p class="section-subtitle">지원되지 않는 객체나 매핑 누락은 게시하지 않고 경고로 남깁니다. DDS 객체 매핑이 없는 기존 Grant도 이 화면에서는 건드리지 않습니다.</p></div></div>
<div class="section-heading"><div><h2>게시 전 확인할 항목</h2><p class="section-subtitle">지원되지 않는 객체나 매핑 누락은 게시하지 않고 경고로 남깁니다.</p></div></div>
<ul>
<li th:each="warning : ${plan.warnings()}" th:text="${warning}">경고</li>
</ul>
@@ -88,7 +88,7 @@
<section class="content-band" th:if="${plan.publishableCount() > 0}">
<details class="explanation-details">
<summary>게시되는 SQL 보</summary>
<summary>술 상세</summary>
<div th:each="grant : ${plan.grants()}" th:if="${grant.publishable()}" class="mt-3">
<strong th:text="${grant.grantName()}">GRANT</strong>
<pre class="code-block" th:text="${grant.sql()}">CREATE OR REPLACE DATA GRANT ...</pre>

View File

@@ -1,35 +1,32 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('DDS 지식 권한 구조')}"></head>
<head th:replace="~{fragments/layout :: head('보호 대상 관리')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container py-4">
<header class="hero">
<span class="eyebrow">DEEP DATA SECURITY · KNOWLEDGE POLICY</span>
<h1>업무 사용자의 지식 접근 범위를 DDS로 제한합니다.</h1>
<p class="lead">이 화면의 보호 대상은 PG나 MY 원본이 아닙니다. 벡터화된 지식 청크를 담은 하나의 보호 VIEW와 그 안의 업무 분류값을 대상으로 합니다.</p>
<h1>보호 대상 관리</h1>
<p class="lead">벡터화된 지식자료와 접근 분류값을 기준으로 접근 범위를 관리합니다.</p>
<details class="explanation">
<summary>세일즈 시나리오 설명</summary>
<summary>도움말</summary>
<p>세일즈 사용자의 Bearer 토큰은 공통 권한 테이블의 <code>agent_sales</code>로 매핑됩니다. 역할에 <code>ALLOW TAG SALES</code>가 있으면 보호 VIEW의 <code>TECH_TAG</code> 값에 SALES가 포함된 청크만 DDS Data Grant를 통과합니다.</p>
<p class="mb-0">문서가 벡터로 검색된다는 점은 검색 방식이고, 어떤 사용자가 볼 수 있는지는 청크에 저장된 분류값과 권한 규칙이 결정합니다.</p>
</details>
</header>
<section th:replace="~{fragments/layout :: architectureStrip('token')}"></section>
<section th:replace="~{fragments/layout :: architectureStrip('search')}"></section>
<section class="flow-grid" aria-label="DDS 지식 접근 흐름">
<article class="flow-card">
<span class="flow-number">1</span>
<div><span class="eyebrow">IDENTITY</span><h2>업무 사용자 토큰</h2><p>Bearer 토큰을 세일즈 업무 사용자로 매핑합니다.</p></div>
</article>
<article class="flow-card">
<span class="flow-number">2</span>
<div><span class="eyebrow">POLICY</span><h2>객체별 DATA GRANT</h2><p>보호 VIEW의 행 조건과 컬럼 범위를 선언합니다.</p></div>
<div><span class="eyebrow">POLICY</span><h2>보호 규칙</h2><p>보호 VIEW의 접근 조건과 컬럼 범위를 선언합니다.</p></div>
</article>
<article class="flow-card">
<span class="flow-number">3</span>
<div><span class="flow-number result-number">3</span><span class="eyebrow">VALUE</span><h2>TECH_TAG 값</h2><p>SALES가 포함된 청크만 남깁니다.</p></div>
<div><span class="eyebrow">VALUE</span><h2>접근 분류값</h2><p>허용된 분류값이 붙은 자료만 남깁니다.</p></div>
</article>
</section>
@@ -37,7 +34,7 @@
<div class="panel-heading">
<div>
<span class="eyebrow">BUSINESS SCENARIO</span>
<h2>세일즈 사용자의 지식 검색</h2>
<h2>업무 사용자 검색 예시</h2>
<p>권한 관리에서 정의한 사용자·역할·규칙이 벡터 지식 검색 결과에 어떻게 적용되는지 확인합니다.</p>
</div>
<a class="btn btn-primary" href="/vector-knowledge">토큰 지식 검색 열기</a>
@@ -45,7 +42,7 @@
<div class="matrix-grid">
<div class="matrix-card"><strong>업무 사용자</strong><span>agent_sales</span><small>SALES 부서 · Bearer 토큰으로 식별</small></div>
<div class="matrix-card"><strong>역할</strong><span>SALES_KNOWLEDGE_ROLE</span><small>세일즈 지식 접근 역할</small></div>
<div class="matrix-card"><strong>규칙</strong><span>ALLOW TAG SALES</span><small>TECH_TAG 컬럼 값에 SALES 포함</small></div>
<div class="matrix-card"><strong>접근 규칙</strong><span>SALES 허용</span><small>접근 분류값에 SALES 포함</small></div>
<div class="matrix-card"><strong>결과</strong><span>청크 28004</span><small>세일즈 파이프라인 운영 가이드만 반환</small></div>
</div>
</section>
@@ -54,7 +51,7 @@
<div class="panel-heading">
<div>
<span class="eyebrow">PROTECTED OBJECT</span>
<h2>테이블·컬럼·값 단위의 보호 범위</h2>
<h2>보호 범위</h2>
<p>지식 검색 VIEW와 그 안의 분류 컬럼 값을 기준으로 판단합니다.</p>
</div>
</div>
@@ -62,8 +59,8 @@
<table class="table align-middle">
<thead><tr><th>구성</th><th>현재 예제</th><th>권한 판단</th></tr></thead>
<tbody>
<tr><td>보호 객체</td><td><code th:text="${vectorObject}">ADMIN.CB_DDS_VECTOR_SEARCH_DOCUMENTS</code></td><td>객체별 DATA GRANT</td></tr>
<tr><td>행 판단 컬럼</td><td><code>TECH_TAG</code></td><td>값에 SALES 포함 여부</td></tr>
<tr><td>보호 객체</td><td><code th:text="${vectorObject}">ADMIN.CB_DDS_VECTOR_SEARCH_DOCUMENTS</code></td><td>객체별 보호 규칙</td></tr>
<tr><td>접근 분류 컬럼</td><td><code>TECH_TAG</code></td><td>허용된 분류값 포함 여부</td></tr>
<tr><td>업무 규칙</td><td><code>ALLOW TAG SALES</code></td><td>공통 CB_PERMISSION_RULE에서 조회</td></tr>
<tr><td>검색 컬럼</td><td><code>EMBEDDING</code></td><td>거리 계산에 사용하며 응답에는 반환하지 않음</td></tr>
</tbody>
@@ -75,14 +72,14 @@
<div class="panel-heading">
<div>
<span class="eyebrow">IMPLEMENTATION</span>
<h2>추가·수정할 때의 기준</h2>
<h2>보호 범위 변경 기준</h2>
</div>
</div>
<ol>
<li>문서 청킹·임베딩 시 청크에 업무 분류값을 저장합니다. 예: <code>SALES</code>, <code>HR</code>, <code>FINANCE</code>.</li>
<li>권한 관리에서 역할에 허용할 분류값을 <code>TAG</code> 규칙으로 등록합니다.</li>
<li>보호 VIEW별 Data Grant predicate가 해당 컬럼 값을 공통 권한체계와 비교합니다.</li>
<li>세일즈 토큰, 다른 부서 토큰, invalid token으로 결과를 회귀 검증합니다.</li>
<li>허용·차단·만료된 토큰으로 결과를 확인합니다.</li>
</ol>
<p class="mb-0"><a href="/permissions">권한 관리</a>에서 규칙을 확인하고, <a href="/vector-knowledge">지식 검색</a>에서 실제 반환 청크를 확인하세요.</p>
</section>

View File

@@ -4,7 +4,7 @@
<div th:fragment="directComparison" class="table-responsive">
<form hx-post="/dds-protection/verify/direct" hx-target="#direct-comparison-result" hx-swap="innerHTML" class="mb-3">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
<button class="btn btn-sm rw-btn-primary" type="submit">직접 비교 fixture 검증 실행</button>
<button class="btn btn-sm rw-btn-primary" type="submit">직접 접근 기준 검증 실행</button>
</form>
<table class="table table-sm align-middle protection-direct-table">
<thead><tr><th>검증 프로필</th><th>보호 상태</th><th>권한 반영</th><th>검색 검증</th><th>조치</th></tr></thead>

View File

@@ -30,7 +30,7 @@
</div>
<div class="empty-result-guide" th:if="${searchResult.success() and !searchResult.hasRows()}">DATA GRANT를 통과한 검색 단위가 없습니다.</div>
<details class="technical-details mt-3">
<summary>DDS 연결 정보 보기</summary>
<summary>기술 정보 보기</summary>
<p class="mb-0">SESSION_USER: <code th:text="${searchResult.sessionUser() ?: '-'}">-</code> · ORA_END_USER_CONTEXT: <code th:text="${searchResult.endUser() ?: '-'}">-</code></p>
</details>
</div>

View File

@@ -3,7 +3,7 @@
<head th:fragment="head(title)">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title th:text="${title}">DDS 백오피스</title>
<title th:text="${title}">DDS 권한 운영</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="/css/app.css" rel="stylesheet">
<link th:if="${backofficeTrack == 'DDS'}" href="/css/dds.css" rel="stylesheet">
@@ -14,37 +14,38 @@
<body>
<nav th:fragment="nav" class="navbar rw-nav navbar-expand-lg">
<div class="container">
<a class="navbar-brand" href="/" th:text="${backofficeTrack == 'DDS' ? 'DDS Backoffice' : 'VPD Backoffice'}">DDS Backoffice</a>
<span class="badge rounded-pill text-bg-primary ms-2" th:if="${backofficeTrack == 'DDS'}">독립 DDS 트랙 · 8083</span>
<a class="navbar-brand" href="/" th:text="${backofficeTrack == 'DDS' ? 'DDS 권한 운영' : 'VPD Backoffice'}">DDS 권한 운영</a>
<span class="badge rounded-pill text-bg-primary ms-2" th:if="${backofficeTrack == 'DDS'}">Deep Data Security</span>
<div class="rw-menu">
<div class="rw-menu-group">
<button class="rw-menu-trigger" type="button" aria-expanded="false">1. 권한 설계</button>
<button class="rw-menu-trigger" type="button" aria-expanded="false" th:text="${backofficeTrack == 'DDS' ? '권한 관리' : '1. 권한 설계'}">1. 권한 설계</button>
<div class="rw-menu-panel">
<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>
<a class="nav-link" href="/users" th:text="${backofficeTrack == 'DDS' ? '사용자 관리' : '사용자'}">사용자</a>
<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. DDS 보호·검증' : '2. 보호·검증'}">2. 보호·검증</button>
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' ? 'DDS 보호 연결' : 'DB 보호 연결'}">DB 보호 연결</a>
<a class="nav-link" href="/dds-provision" th:if="${backofficeTrack == 'DDS'}">DDS 권한 반영</a>
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">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">연동 도구</button>
<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">지식 검색 관리</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>
@@ -52,8 +53,9 @@
</div>
</div>
<div class="rw-menu-group">
<button class="rw-menu-trigger" type="button" aria-expanded="false">운영·고급</button>
<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>
@@ -69,11 +71,18 @@
<div th:fragment="trackNotice" th:if="${backofficeTrack == 'DDS'}" class="container pt-3">
<div class="alert alert-info mb-0">
<strong>DDS 독립 데모</strong> · VPD와 같은 관리 흐름을 사용하며, 실제 보호는 객체별 <code>DATA GRANT</code>가 담당합니다. 토큰 경로는 <code>Bearer → CB_AGENT_CTX → predicate</code>, 직접 비교는 <code>END USER → DATA ROLE → DATA GRANT</code>니다.
<strong>DDS 보호 운영</strong> · 실제 보호는 객체별 <code>DATA GRANT</code>가 담당합니다. 업무 사용자 검색은 <code>Bearer → CB_AGENT_CTX → 접근 조건</code>, 직접 접근 비교는 <code>END USER → DATA ROLE → DATA GRANT</code> 경로로 동작합니다.
</div>
</div>
<section th:fragment="architectureStrip(activeLayer)" 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'">
<span class="architecture-kicker">1 · WHO / WHAT</span>
<strong>권한 설계</strong>
@@ -82,13 +91,13 @@
<div class="architecture-arrow"></div>
<div class="architecture-step" th:classappend="${activeLayer == 'vpd' or activeLayer == 'protection'} ? ' active'">
<span class="architecture-kicker" th:text="${backofficeTrack == 'DDS' ? '2 · ENFORCE' : '2 · ENFORCE'}">2 · ENFORCE</span>
<strong th:text="${backofficeTrack == 'DDS' ? 'DDS 보호 연결' : 'DB 보호 연결'}">DB 보호 연결</strong>
<strong th:text="${backofficeTrack == 'DDS' ? '보호 상태' : 'DB 보호 연결'}">DB 보호 연결</strong>
<p th:if="${backofficeTrack != 'DDS'}" th:text="${'VPD가 저장된 권한체계를 매번 읽어 DB에서 행을 자동 제한합니다.'}">DB 보호 정책이 권한을 적용합니다.</p>
</div>
<div class="architecture-arrow"></div>
<div class="architecture-step" th:classappend="${activeLayer == 'token'} ? ' active'">
<span class="architecture-kicker">3 · IDENTITY</span>
<strong th:text="${backofficeTrack == 'DDS' ? 'END USER 컨텍스트' : '검증 세션'}">검증 세션</strong>
<strong th:text="${backofficeTrack == 'DDS' ? '업무 사용자 식별' : '검증 세션'}">검증 세션</strong>
<p th:if="${backofficeTrack != 'DDS'}">확인할 사용자를 나타내는 일회성 토큰을 준비합니다.</p>
</div>
<div class="architecture-arrow"></div>
@@ -97,10 +106,11 @@
<strong>결과 확인</strong>
<p th:if="${backofficeTrack != 'DDS'}">보호 정책을 통과한 데이터만 실제 결과로 확인합니다.</p>
</div>
</th:block>
</section>
<details th:if="${backofficeTrack == 'DDS'}" class="explanation-details architecture-explanation">
<summary>DDS 적용 흐름 설명 보기</summary>
<summary>보호 방식 기술 상세 보기</summary>
<ol>
<li>권한 관리에서 사용자·그룹·역할과 TAG 규칙을 정의합니다.</li>
<li>DDS 보호 VIEW/TABLE에 객체별 DATA GRANT를 연결합니다.</li>

View File

@@ -3,15 +3,15 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>DDS Permission Console 로그인</title>
<title>DDS 권한 운영 콘솔 로그인</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="/css/dds.css" rel="stylesheet">
</head>
<body class="login-page">
<main class="login-card">
<span class="eyebrow">DEEP DATA SECURITY</span>
<h1>DDS Permission Console</h1>
<p>VPD와 분리된 DDS 전용 인스턴스입니다.</p>
<h1>DDS 권한 운영 콘솔</h1>
<p>관리자 전용 보호 정책 운영</p>
<div class="alert alert-danger" th:if="${param.error}">관리자 인증에 실패했습니다.</div>
<div class="alert alert-success" th:if="${param.logout}">로그아웃되었습니다.</div>
<form method="post" action="/login">

View File

@@ -1,21 +1,21 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('DDS 지식 검색 관리')}"></head>
<head th:replace="~{fragments/layout :: head('지식자료 관리')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container py-4">
<div class="page-title">
<span class="architecture-kicker">DDS · KNOWLEDGE ACCESS</span>
<h1>지식자료를 등록하고 DDS 권한으로 검색합니다.</h1>
<p class="context-summary">문서 → 청크·임베딩 → 기술 태그 → 권한 규칙 → DDS DATA GRANT → 검색 결과</p>
<h1>지식자료 관리</h1>
<p class="context-summary">등록 · 접근 규칙 · 업무 사용자 검색 · 고급 검증</p>
<details class="explanation-details">
<summary>이 화면의 큰 흐름 보기</summary>
<p>문서는 검색 가능한 청크로 나뉘고 각 청크에 업무 접근 분류값(<code>TECH_TAG</code>)이 붙습니다. 권한 관리에서 역할별 허용 값을 등록하면 DDS의 DATA GRANT가 해당 값이 붙은 청크만 검색 대상으로 남깁니다.</p>
<p class="mb-0">VPD 화면과 같은 사용자·그룹·역할·권한 규칙을 사용합니다. 아래에는 DDS END USER를 직접 선택하는 비교 경로와, 하나의 DDS 기술 사용자에 Bearer 토큰을 전달해 공통 권한을 평가하는 제품형 경로를 함께 둡니다.</p>
<summary>도움말</summary>
<p>문서는 검색 단위로 나뉘고 각 단위에 접근 분류값(<code>TECH_TAG</code>)이 연결됩니다. 역할별 허용 값을 등록하면 보호 규칙이 허용된 분류값만 검색 대상으로 남깁니다.</p>
<p class="mb-0">기본 경로는 Bearer 토큰으로 업무 사용자를 식별합니다. 직접 접근 비교는 고급 검증에서만 사용합니다.</p>
</details>
</div>
<section th:replace="~{fragments/layout :: architectureStrip('ords')}"></section>
<section th:replace="~{fragments/layout :: architectureStrip('search')}"></section>
<div class="alert alert-success" th:if="${message}" th:text="${message}"></div>
<div class="alert alert-danger" th:if="${errorMessage}" th:text="${errorMessage}"></div>
@@ -26,17 +26,17 @@
<section class="content-band">
<div class="section-heading">
<div>
<span class="architecture-kicker">1 · CONTENT</span>
<span class="architecture-kicker">지식자료</span>
<h2>지식자료 등록</h2>
<p class="section-subtitle">문서 ID가 같으면 기존 청크를 교체합니다. 저장소는 VPD 트랙과 동일한 지식자료 형식을 사용합니다.</p>
<p class="section-subtitle">문서 ID가 같으면 기존 검색 단위를 교체합니다.</p>
</div>
<span class="badge text-bg-secondary" th:text="${summary == null ? '저장소 확인 필요' : summary.chunkCount() + '개 검색 단위'}">0개 검색 단위</span>
</div>
<details class="explanation-details">
<summary>청킹·임베딩 설정 보기</summary>
<summary>도움말</summary>
<ul>
<li>문단을 우선 묶고 길이가 길면 문장·공백 기준으로 청크를 나눕니다.</li>
<li>기본 로컬 임베딩은 외부 API 없이 흐름을 검증하는 개발용 4차원 벡터입니다.</li>
<li>기본 로컬 임베딩은 외부 API 없이 흐름을 확인하는 개발용 4차원 벡터입니다.</li>
<li>운영에서는 등록과 검색에 같은 모델·차원을 사용해야 합니다.</li>
</ul>
</details>
@@ -70,21 +70,21 @@
<section class="content-band">
<div class="section-heading">
<div>
<span class="architecture-kicker">2 · POLICY</span>
<span class="architecture-kicker">접근 규칙</span>
<h2>권한 규칙은 한 곳에서 관리합니다.</h2>
<p class="section-subtitle">사용자·그룹·역할·TAG 규칙은 VPD와 같은 권한 관리 화면에서 작성합니다.</p>
<p class="section-subtitle">사용자·그룹·역할·접근 분류 규칙을 한 곳에서 관리합니다.</p>
</div>
<div class="d-flex gap-2">
<a class="btn btn-sm rw-btn-secondary" href="/permissions">권한 관리 열기</a>
<a class="btn btn-sm rw-btn-primary" href="/dds-provision">DDS 권한 반영</a>
<a class="btn btn-sm rw-btn-primary" href="/dds-provision">권한 게시</a>
</div>
</div>
<div class="macro-micro-grid">
<div><h3>관리 관점</h3><p>를 들어 세일즈 역할에 <code>ALLOW TAG SALES</code>를 등록하면 SALES 값이 붙은 청크만 허용합니다. <code>DENY TAG</code> 허용 후보에서 제외니다.</p></div>
<div><h3>DDS 실행 관점</h3><p><code th:text="${ddsVectorObject}">ADMIN.CB_DDS_VECTOR_SEARCH_DOCUMENTS</code>객체별 DATA GRANT predicate가 토큰으로 식별된 업무 사용자와 청크의 <code>TECH_TAG</code> 값을 비교해 이 규칙을 집행합니다.</p></div>
<div><h3>접근 규칙</h3><p>: 세일즈 역할에 SALES 접근 분류값을 허용하면 SALES 자료만 검색됩니다. 거부 규칙은 허용 대상에서 제외니다.</p></div>
<div><h3>보호 방식</h3><p><code th:text="${ddsVectorObject}">ADMIN.CB_DDS_VECTOR_SEARCH_DOCUMENTS</code>보호 규칙이 업무 사용자와 접근 분류값을 비교합니다.</p></div>
</div>
<details class="explanation-details mt-3">
<summary>VPD와 DDS의 차이 보기</summary>
<summary>도움말</summary>
<p>VPD는 요청마다 권한 테이블을 읽어 predicate를 계산합니다. DDS의 직접 비교 경로는 보호 VIEW에 선언한 DATA GRANT와 END USER 매핑을 사용합니다. 토큰 경로에서는 토큰이 공통 사용자 Context를 만들고, 보호 VIEW의 객체별 DATA GRANT predicate가 같은 권한 테이블을 다시 평가합니다.</p>
</details>
</section>
@@ -92,21 +92,21 @@
<section class="content-band">
<div class="section-heading">
<div>
<span class="architecture-kicker">3 · TOKEN CONTEXT</span>
<h2>Bearer 토큰으로 같은 DDS Data Grant 적용</h2>
<p class="section-subtitle">고객별 DDS 계정을 만들지 않고 하나의 기술 사용자 세션에서 토큰으로 업무 사용자를 식별합니다.</p>
<span class="architecture-kicker">업무 사용자 검색</span>
<h2>업무 사용자 검색</h2>
<p class="section-subtitle">하나의 기술 사용자 연결에서 Bearer 토큰으로 업무 사용자를 식별합니다.</p>
</div>
<span class="badge text-bg-secondary">객체별 DATA GRANT</span>
</div>
<details class="explanation-details">
<summary>토큰 경계 설명 보기</summary>
<summary>도움말</summary>
<ol>
<li>서버가 설정한 하나의 DDS 기술 사용자로 연결합니다.</li>
<li>Bearer 토큰을 <code>CB_AGENT_CTX_PKG.SET_USER_BY_BEARER</code>에 전달합니다.</li>
<li><code>CB_DDS_VECTOR_SEARCH_DOCUMENTS</code>의 객체별 DATA GRANT predicate가 <code>CB_APP_USER</code>, 역할·그룹, <code>CB_PERMISSION</code>, <code>CB_PERMISSION_RULE</code>을 조회합니다.</li>
<li>ALLOW TAG는 OR로 합치고 DENY TAG는 최종 결과에서 제외합니다.</li>
</ol>
<p class="mb-0">세일즈 예제에서는 <code>agent_sales</code> 역할이 <code>ALLOW TAG SALES</code>를 가지고, 청크의 <code>TECH_TAG</code>에 SALES가 포함된 지식만 남습니다. 토큰은 DATA GRANT 문법의 바인드 파라미터가 아니라 Context를 만드는 입력이며 저장하지 않습니다.</p>
<p class="mb-0">토큰은 보호 규칙의 입력값이 아니라 업무 사용자 Context를 만드는 이며 저장하지 않습니다.</p>
</details>
<form hx-post="/vector-knowledge/token-search" hx-target="#vector-token-search-result" hx-swap="innerHTML" class="form-grid mt-3">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
@@ -124,24 +124,24 @@
<label class="span-2">검색 질문
<textarea class="form-control" name="query" rows="3" placeholder="예: 세일즈 파이프라인 후속 조치 기준" required></textarea>
</label>
<button class="btn rw-btn-primary" type="submit">토큰 권한으로 검색</button>
<button class="btn rw-btn-primary" type="submit">권한 검색</button>
</form>
<section id="vector-token-search-result" class="mt-3" aria-live="polite">
<div class="empty-result-guide">Bearer 토큰과 질문을 입력하면 공통 권한체계를 반영한 DDS Data Grant 결과를 표시합니다.</div>
<div class="empty-result-guide">Bearer 토큰과 질문을 입력하면 허용된 지식자료만 표시합니다.</div>
</section>
</section>
<section class="content-band">
<div class="section-heading">
<div>
<span class="architecture-kicker">4 · DIRECT CHECK</span>
<h2>DDS END USER 직접 조회 비교</h2>
<p class="section-subtitle">저수준 DDS 검증을 위해 선택한 END USER로 직접 접속해 DATA ROLE/DATA GRANT 결과를 확인합니다.</p>
<span class="architecture-kicker">고급 검증</span>
<h2>고급: 직접 접근 비교</h2>
<p class="section-subtitle">DDS 선언형 보호 규칙 점검용 고급 검증</p>
</div>
<span class="badge text-bg-primary" th:text="${ddsVectorObject}">DDS view</span>
</div>
<details class="explanation-details">
<summary>검색 처리 방식 보기</summary>
<summary>도움말</summary>
<ol>
<li>검색어를 등록 때와 같은 방식으로 임베딩합니다.</li>
<li>DDS END USER로 보호 VIEW에 연결합니다.</li>
@@ -151,7 +151,7 @@
</details>
<form hx-post="/vector-knowledge/search" hx-target="#vector-search-result" hx-swap="innerHTML" class="form-grid mt-3">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
<label>DDS END USER
<label>직접 접근 검증 프로필
<select class="form-select" name="userKey" required>
<option th:each="user : ${ddsUsers}" th:value="${user.key()}" th:selected="${user.key() == 'both'}"
th:text="${user.label() + ' · ' + user.username()}"></option>
@@ -168,16 +168,16 @@
<label class="span-2">검색 질문
<textarea class="form-control" name="query" rows="3" placeholder="예: 기술 태그로 지식자료 검색 권한을 제한하는 방법" required></textarea>
</label>
<button class="btn rw-btn-primary" type="submit">DDS 권한으로 검색</button>
<button class="btn rw-btn-primary" type="submit">직접 접근 비교</button>
</form>
<section id="vector-search-result" class="mt-3" aria-live="polite">
<div class="empty-result-guide">END USER와 질문을 선택하면 DDS가 허용 검색 단위만 표시니다.</div>
<div class="empty-result-guide">검증 프로필과 질문을 선택하면 허용 검색 단위만 표시니다.</div>
</section>
</section>
<section class="content-band">
<details class="explanation-details">
<summary>저장 현황 보기</summary>
<summary>저장 현황</summary>
<div class="summary-grid mt-3">
<div class="summary-tile"><span class="label">문서</span><strong th:text="${summary == null ? '-' : summary.documentCount()}">0</strong></div>
<div class="summary-tile"><span class="label">검색 단위</span><strong th:text="${summary == null ? '-' : summary.chunkCount()}">0</strong></div>

View File

@@ -1,12 +1,12 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('DDS 보호 상태')}"></head>
<head th:replace="~{fragments/layout :: head('보호 정책 관리')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container py-4">
<header class="page-title">
<span class="architecture-kicker">DDS · PROTECTION STATUS</span>
<h1>DDS 보호 상태</h1>
<h1>보호 정책 관리</h1>
<p class="context-summary">지식 검색에 쓰는 보호 객체가 현재 권한 기준대로 연결되어 있는지 확인합니다.</p>
</header>
@@ -16,7 +16,7 @@
<div class="alert alert-danger" th:if="${errorMessage}" th:text="${errorMessage}"></div>
<form method="post" action="/dds-protection/setup" class="mb-3">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
<button class="btn btn-sm rw-btn-secondary" type="submit">DDS 검증 이력·fixture 구성 확인</button>
<button class="btn btn-sm rw-btn-secondary" type="submit">검증 기준 자료 구성 확인</button>
</form>
<section class="content-band protection-summary" aria-labelledby="service-path-heading">
@@ -24,7 +24,7 @@
<div>
<span class="architecture-kicker">기본 서비스 경로</span>
<h2 id="service-path-heading">토큰 기반 지식 검색</h2>
<p class="section-subtitle">일상 운영에서는 이 경로만 확인하면 됩니다. 직접 DDS END USER 비교는 아래 고급 검증에 분리했습니다.</p>
<p class="section-subtitle">일상 운영에서는 이 경로만 확인하면 됩니다. 직접 접근 비교는 아래 고급 검증에 분리했습니다.</p>
</div>
<span class="badge text-bg-danger" th:if="${protection.actionRequiredCount() > 0}"
th:text="${protection.actionRequiredCount() + '개 조치 필요'}">조치 필요</span>
@@ -67,7 +67,7 @@
<a class="btn rw-btn-secondary" href="/vector-knowledge">지식 검색 열기</a>
<form method="post" action="/dds-protection/verify/service" class="d-inline">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
<button class="btn rw-btn-primary" type="submit">결정적 보호 검증 실행</button>
<button class="btn rw-btn-primary" type="submit">보호 기준 검증 실행</button>
</form>
</div>
</footer>
@@ -79,7 +79,7 @@
<div>
<span class="architecture-kicker">권한 변경</span>
<h2>권한을 바꾸려면</h2>
<p class="section-subtitle">권한 규칙을 먼저 수정하고, 직접 비교 경로의 변경은 DDS 권한 반영에서 미리 확인합니다.</p>
<p class="section-subtitle">접근 규칙을 먼저 수정하고, 직접 접근 비교 경로의 변경은 권한 게시에서 미리 확인합니다.</p>
</div>
<div class="d-flex gap-2">
<a class="btn btn-sm rw-btn-secondary" href="/permissions">권한 규칙 열기</a>
@@ -87,18 +87,18 @@
</div>
</div>
<details class="explanation-details m-0">
<summary>두 집행 경로의 차이 보기</summary>
<summary>도움말</summary>
<div class="protection-path-comparison">
<div><strong>토큰 기반 서비스</strong><span>기술 사용자 연결에서 Bearer 토큰으로 업무 사용자를 식별합니다. 객체별 DATA GRANT가 현재 공통 권한을 평가합니다.</span></div>
<div><strong>DDS END USER 직접 비교</strong><span>DATA ROLE과 게시된 DATA GRANT를 직접 확인하는 고급 진단 경로입니다. 권한 변경 뒤에는 별도 반영이 필요합니다.</span></div>
<div><strong>업무 사용자 검색</strong><span>기술 사용자 연결에서 Bearer 토큰으로 업무 사용자를 식별합니다. 보호 규칙이 현재 접근 권한을 평가합니다.</span></div>
<div><strong>직접 접근 비교</strong><span>게시된 DATA ROLE과 DATA GRANT를 직접 확인하는 고급 진단 경로 · 권한 변경 뒤 별도 게시 필요</span></div>
</div>
</details>
</section>
<section class="content-band">
<details class="advanced-protection" aria-labelledby="direct-check-heading">
<summary id="direct-check-heading">고급 검증: DDS END USER 직접 비교 보기</summary>
<p class="section-subtitle mt-3">아래 항목은 제품 계정 목록이 아니라 DDS 선언형 권한을 비교하기 위한 검증 프로필입니다. 평소 운영에서는 열 필요습니다.</p>
<summary id="direct-check-heading">고급 검증: 직접 접근 비교 보기</summary>
<p class="section-subtitle mt-3">제품 계정 목록이 아 DDS 선언형 보호 규칙 비교용 검증 프로필 · 평소 운영에서는 열 필요 없</p>
<button class="btn btn-sm rw-btn-secondary mt-2" type="button"
hx-get="/dds-protection/direct" hx-target="#direct-comparison-result" hx-swap="innerHTML">
고급 검증 불러오기

View File

@@ -1,4 +1,4 @@
# VPD Backoffice 전 페이지 페르소나 UX 리뷰와 개선 로드맵
# VPD 권한 운영 전 페이지 페르소나 UX 리뷰와 개선 로드맵
> **상태**: Review complete · UX-1/UX-2 implementation in progress
> **작성**: [AI] UX Facilitator · **최종수정**: 2026-06-30
@@ -87,3 +87,23 @@ UX-1부터 시작한다. 기존 데이터 모델과 VPD 로직은 바꾸지 않
- 브라우저 검증을 우회한 POST도 역할 누락·객체 형식·행 규칙 오류를 flash 오류로 돌려준다. 서버의 `PermissionService` 검증은 그대로 최종 방어선으로 유지한다.
검증 범위는 `GuidedFlowTemplateTest`, `PermissionServiceTest`, 전체 Maven 테스트 및 배포 후 `/permissions` HTTP 확인이다.
## 8. 제품 표면 명칭 정리 — VPD 권한 운영
`VPD Backoffice`는 구현 방식과 내부 운영 도구 성격만 드러내므로 제품 표면에서 제거한다. 제품명은 **VPD 권한 운영**으로 통일한다. VPD는 보호 방식의 이름으로 유지하되, ORDS·Handler·Filter·MCP 같은 구현 용어는 고급 운영 또는 기술 상세에서만 사용한다.
| 기존 표현 | 제품 표면 표현 | 비고 |
|---|---|---|
| VPD Backoffice | VPD 권한 운영 | 상단, 로그인, 브라우저 제목 |
| 권한 관리 | 접근 정책 | 역할별 데이터 범위 설정 |
| DB 보호 연결 / VPD 설정 | 보호 상태 | DB 객체 보호 정책의 확인·연결 |
| 권한 결과 확인 | 접근 검증 | 검증 세션으로 실제 조회 실행 |
| ORDS 조회 대상 | 조회 대상 | ORDS는 화면 상세에서만 표기 |
| ORDS 핸들러 | 조회 연동 | 고급 운영 메뉴 |
| 지식 검색 관리 | 지식 검색 | 등록·정책·검색 기능 |
| 별도 Filter 관리 | 고급 접근 조건 | 예외 정책 기능 |
| 운영 상태 / 연결 설정 | 운영 현황 / 시스템 설정 | 운영성·연동 설정 기능 |
상단 메뉴는 `권한 관리 · 데이터 보호 · 접근 검증 · 지식 검색 · 시스템 관리` 순서로 재구성한다. 절차나 구현 방식이 아니라 사용자가 하려는 일을 메뉴명으로 쓴다. 메뉴와 기본 화면에는 `1.`, `2.`, 단계 레일, `01/02` 배지를 두지 않는다. 순서가 실제 입력 제약인 wizard에서만 단계 번호를 사용한다.
기본 페이지는 제목과 현재 설정·주요 행동만 노출한다. VPD/ORDS 처리 원리, 배경 설명, 주의사항은 상단의 공통 `도움말` 버튼을 눌렀을 때만 펼친다. 이 규칙으로 제품 화면이 관리 매뉴얼처럼 보이는 문제를 줄이고, 설정 자체를 전면에 둔다.

View File

@@ -253,6 +253,39 @@ body {
margin-bottom: 0;
}
/* Product pages keep help available without turning the title area into a manual. */
.page-title > .explanation-details {
background: transparent;
border: 0;
margin-top: .55rem;
padding: 0;
}
.page-title > .explanation-details > summary {
background: var(--rw-surface-muted);
border: 1px solid var(--rw-border);
border-radius: 999px;
display: inline-flex;
padding: .35rem .7rem;
}
.page-title > .explanation-details[open] {
background: var(--rw-surface-muted);
border: 1px solid var(--rw-border);
border-radius: 8px;
padding: .65rem .8rem;
}
.page-title > .explanation-details[open] > summary {
background: transparent;
border: 0;
border-bottom: 1px solid var(--rw-border);
border-radius: 0;
display: block;
margin-bottom: .65rem;
padding: 0 0 .55rem;
}
.content-band > .explanation-details:first-child {
margin-top: 0;
}
@@ -1845,7 +1878,7 @@ body {
color: var(--rw-text);
display: grid;
gap: .65rem;
grid-template-columns: auto minmax(0, 1fr) auto;
grid-template-columns: minmax(0, 1fr) auto;
min-width: 0;
padding: .8rem;
text-decoration: none;
@@ -1862,20 +1895,6 @@ body {
border-color: color-mix(in srgb, var(--rw-primary) 35%, var(--rw-border));
}
.dashboard-action-icon {
align-items: center;
background: var(--rw-surface);
border: 1px solid var(--rw-border);
border-radius: 999px;
color: var(--rw-primary);
display: inline-flex;
font-size: .72rem;
font-weight: 900;
height: 2rem;
justify-content: center;
width: 2rem;
}
.dashboard-action strong,
.dashboard-action small {
display: block;
@@ -1938,57 +1957,6 @@ body {
text-decoration: none;
}
.dashboard-workflow {
margin-bottom: 1.25rem;
}
.dashboard-section-heading {
margin-bottom: .7rem;
}
.workflow-steps {
display: grid;
gap: .6rem;
grid-template-columns: repeat(4, minmax(0, 1fr));
list-style: none;
margin: 0;
padding: 0;
}
.workflow-steps a {
border-top: 2px solid var(--rw-border-strong);
color: var(--rw-text);
display: grid;
gap: .2rem;
grid-template-columns: auto minmax(0, 1fr);
padding: .65rem 0;
text-decoration: none;
}
.workflow-steps a:hover,
.workflow-steps a:focus {
border-color: var(--rw-primary);
color: var(--rw-primary);
}
.workflow-steps span {
color: var(--rw-primary);
font-size: .76rem;
font-weight: 900;
grid-row: span 2;
margin-right: .1rem;
}
.workflow-steps strong {
font-size: .88rem;
}
.workflow-steps small {
color: var(--rw-muted);
font-size: .76rem;
line-height: 1.3;
}
.dashboard-details {
background: var(--rw-surface);
border: 1px solid var(--rw-border);
@@ -2382,7 +2350,6 @@ body {
}
.dashboard-action-grid,
.workflow-steps,
.dashboard-state dl {
grid-template-columns: 1fr;
}

View File

@@ -1,21 +1,20 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('VPD 권한 백오피스')}"></head>
<head th:replace="~{fragments/layout :: head('VPD 권한 운영')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container py-4 dashboard-page">
<header class="dashboard-hero">
<div>
<span class="architecture-kicker">권한 관리</span>
<h1>누가 어떤 데이터를 볼 수 있는지 설정하고 확인하세요.</h1>
<p>권한 규칙을 변경한 뒤, 검증 세션에서 실제 조회 결과를 확인할 수 있습니다.</p>
<span class="architecture-kicker">VPD 권한 운영</span>
<h1>권한 관리</h1>
</div>
<div class="dashboard-hero-actions" aria-label="주요 작업">
<a class="btn rw-btn-primary" href="/probe">권한 결과 확인</a>
<a class="btn rw-btn-secondary" href="/permissions">권한 규칙 관리</a>
<a class="btn rw-btn-primary" href="/probe">접근 검증</a>
<a class="btn rw-btn-secondary" href="/permissions">접근 정책</a>
</div>
<details class="explanation-details dashboard-explanation">
<summary>이 화면의 전체 흐름 보기</summary>
<summary>도움말</summary>
<p>사용자·그룹·역할에 권한을 연결하면 Oracle VPD가 요청할 때마다 그 규칙을 읽어 허용된 행만 반환합니다. 별도 SQL 필터를 만드는 일은 예외적인 고급 작업입니다.</p>
</details>
</header>
@@ -28,18 +27,14 @@
<section class="dashboard-command-center" aria-label="주요 권한 작업">
<div class="dashboard-primary-action">
<span class="architecture-kicker">지금 할 일</span>
<h2>권한을 설정하거나 적용 결과를 확인하세요.</h2>
<p>권한 규칙에서 설정하고, 검증 세션에서 실제 조회 결과를 확인합니다.</p>
<h2>주요 작업</h2>
<div class="dashboard-action-grid">
<a class="dashboard-action" href="/permissions">
<span class="dashboard-action-icon" aria-hidden="true">01</span>
<span><strong>권한 규칙 관리</strong><small>역할별 객체·행·컬럼 접근을 설정합니다.</small></span>
<span><strong>접근 정책</strong><small>역할별 객체·행·컬럼 접근을 설정합니다.</small></span>
<span class="dashboard-action-arrow" aria-hidden="true"></span>
</a>
<a class="dashboard-action dashboard-action-emphasis" href="/probe">
<span class="dashboard-action-icon" aria-hidden="true">02</span>
<span><strong>권한 결과 확인</strong><small>토큰으로 실제 DB 조회 결과를 검증합니다.</small></span>
<span><strong>접근 검증</strong><small>검증 세션으로 실제 DB 조회 결과를 확인합니다.</small></span>
<span class="dashboard-action-arrow" aria-hidden="true"></span>
</a>
</div>
@@ -55,21 +50,8 @@
</aside>
</section>
<section class="dashboard-workflow" aria-label="권장 권한 적용 순서">
<div class="dashboard-section-heading">
<span class="architecture-kicker">권장 순서</span>
<h2>권한 적용 4단계</h2>
</div>
<ol class="workflow-steps">
<li><a href="/permissions"><span>1</span><strong>권한 설계</strong><small>사용자·역할·데이터 범위</small></a></li>
<li><a href="/vpd-policies"><span>2</span><strong>DB 보호 연결</strong><small>VPD 정책 적용</small></a></li>
<li><a href="/tokens"><span>3</span><strong>검증 세션</strong><small>사용자 토큰 발급</small></a></li>
<li><a href="/probe"><span>4</span><strong>결과 확인</strong><small>ORDS 실제 조회</small></a></li>
</ol>
</section>
<details class="dashboard-details">
<summary>작동 방식과 등록 대상 보기</summary>
<summary>상세 현황</summary>
<div class="dashboard-details-body">
<section class="macro-micro-grid" aria-label="권한 적용 방식">
<div>
@@ -90,7 +72,7 @@
<h2>현재 검증 가능한 데이터</h2>
<p class="section-subtitle">권한 규칙과 ORDS 경로가 등록된 보호 객체입니다.</p>
</div>
<a class="btn btn-sm rw-btn-primary" href="/probe">결과 확인</a>
<a class="btn btn-sm rw-btn-primary" href="/probe">접근 검증</a>
</div>
<div class="table-responsive">
<table class="table table-sm align-middle">

View File

@@ -1,14 +1,13 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('유효 권한 매트릭스')}"></head>
<head th:replace="~{fragments/layout :: head('사용자별 접근 확인')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container py-4">
<div class="page-title">
<h1>유효 권한 확인</h1>
<p class="context-summary">한 사용자가 어떤 역할 경로를 거쳐 어떤 보호 객체를 볼 수 있는지 확인합니다.</p>
<h1>사용자별 접근 확인</h1>
<details class="explanation-details">
<summary>이 화면의 권한 근거 보기</summary>
<summary>도움말</summary>
<p>직접 역할과 그룹 상속 역할을 합쳐 최종 권한을 계산합니다. 여기의 결과는 토큰을 발급해 ORDS/VPD 조회를 검증하기 전 확인하는 설계 근거입니다.</p>
</details>
</div>

View File

@@ -3,7 +3,7 @@
<head th:fragment="head(title)">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title th:text="${title}">VPD 백오피스</title>
<title th:text="${title == 'VPD 권한 운영' ? title : title + ' · VPD 권한 운영'}">VPD 권한 운영</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="/css/app.css" rel="stylesheet">
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
@@ -13,44 +13,49 @@
<body>
<nav th:fragment="nav" class="navbar rw-nav navbar-expand-lg">
<div class="container">
<a class="navbar-brand" href="/">VPD Backoffice</a>
<a class="navbar-brand" href="/">VPD 권한 운영</a>
<div class="rw-menu">
<div class="rw-menu-group">
<button class="rw-menu-trigger" type="button" aria-expanded="false">1. 권한 설계</button>
<button class="rw-menu-trigger" type="button" aria-expanded="false">권한 관리</button>
<div class="rw-menu-panel">
<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>
<a class="nav-link" href="/permissions">접근 정책</a>
<a class="nav-link" href="/effective-matrix">사용자별 접근 확인</a>
</div>
</div>
<div class="rw-menu-group">
<button class="rw-menu-trigger" type="button" aria-expanded="false">2. 보호·검증</button>
<button class="rw-menu-trigger" type="button" aria-expanded="false">데이터 보호</button>
<div class="rw-menu-panel">
<a class="nav-link" href="/vpd-policies">DB 보호 연결</a>
<a class="nav-link" href="/tokens">검증 세션 발급</a>
<a class="nav-link" href="/probe">권한 결과 확인</a>
<a class="nav-link" href="/vpd-policies">보호 상태</a>
<a class="nav-link" href="/objects">조회 대상</a>
</div>
</div>
<div class="rw-menu-group">
<button class="rw-menu-trigger" type="button" aria-expanded="false">연동 도구</button>
<button class="rw-menu-trigger" type="button" aria-expanded="false">접근 검증</button>
<div class="rw-menu-panel">
<a class="nav-link" href="/objects">ORDS 조회 대상</a>
<a class="nav-link" href="/ords-handlers">ORDS 핸들러</a>
<a class="nav-link" href="/vector-knowledge">지식 검색 관리</a>
<a class="nav-link" href="/mcp-chatbot">Chatbot</a>
<a class="nav-link" href="/mcp-reasoning">Reasoning</a>
<a class="nav-link" href="/mcp-sse">SSE 서비스</a>
<a class="nav-link" href="/mcp-client-demo">MCP Client</a>
<a class="nav-link" href="/tokens">검증 세션</a>
<a class="nav-link" href="/probe">접근 검증</a>
</div>
</div>
<div class="rw-menu-group">
<button class="rw-menu-trigger" type="button" aria-expanded="false">운영·고급</button>
<button class="rw-menu-trigger" type="button" aria-expanded="false">지식 검색</button>
<div class="rw-menu-panel">
<a class="nav-link" href="/operation-status">운영 상태</a>
<a class="nav-link" href="/vpd-filter-policies">별도 Filter 관리</a>
<a class="nav-link" href="/settings">설정</a>
<a class="nav-link" href="/vector-knowledge">지식 검색</a>
<a class="nav-link" href="/mcp-chatbot">대화형 검색</a>
<a class="nav-link" href="/mcp-reasoning">검색 해석</a>
</div>
</div>
<div class="rw-menu-group">
<button class="rw-menu-trigger" type="button" aria-expanded="false">시스템 관리</button>
<div class="rw-menu-panel">
<a class="nav-link" href="/ords-handlers">조회 연동</a>
<a class="nav-link" href="/mcp-sse">MCP 연동</a>
<a class="nav-link" href="/mcp-client-demo">연동 점검</a>
<a class="nav-link" href="/operation-status">운영 현황</a>
<a class="nav-link" href="/vpd-filter-policies">고급 접근 조건</a>
<a class="nav-link" href="/settings">시스템 설정</a>
</div>
</div>
</div>
@@ -61,30 +66,5 @@
</div>
</nav>
<section th:fragment="architectureStrip(activeLayer)" class="architecture-strip" aria-label="권한 설정부터 결과 확인까지의 흐름">
<div class="architecture-step" th:classappend="${activeLayer == 'permission'} ? ' active'">
<span class="architecture-kicker">1 · WHO / WHAT</span>
<strong>권한 설계</strong>
<p>누가 어떤 데이터의 어느 행과 컬럼을 볼지 정합니다.</p>
</div>
<div class="architecture-arrow"></div>
<div class="architecture-step" th:classappend="${activeLayer == 'vpd'} ? ' active'">
<span class="architecture-kicker">2 · ENFORCE</span>
<strong>DB 보호 연결</strong>
<p>VPD가 저장된 권한체계를 매번 읽어 DB에서 행을 자동 제한합니다.</p>
</div>
<div class="architecture-arrow"></div>
<div class="architecture-step" th:classappend="${activeLayer == 'token'} ? ' active'">
<span class="architecture-kicker">3 · IDENTITY</span>
<strong>검증 세션</strong>
<p>확인할 사용자를 나타내는 일회성 토큰을 준비합니다.</p>
</div>
<div class="architecture-arrow"></div>
<div class="architecture-step" th:classappend="${activeLayer == 'ords'} ? ' active'">
<span class="architecture-kicker">4 · EVIDENCE</span>
<strong>결과 확인</strong>
<p>ORDS를 호출해 그 사용자에게 실제로 보이는 데이터만 확인합니다.</p>
</div>
</section>
</body>
</html>

View File

@@ -8,7 +8,7 @@
<span th:text="${object.owner() + '.' + object.objectName()}">ADMIN.TABLE_NAME</span>
<span class="text-muted"> / 기본 ORDS Handler PL/SQL</span>
</h3>
<p class="form-hint">이 소스는 VPD context를 설정한 뒤 한 테이블을 SELECT하는 기본 구성입니다. 실제 수정·저장은 <a href="/ords-handlers">ORDS 핸들러</a> 화면에서 parsing schema 권한으로 진행하세요.</p>
<p class="form-hint">이 소스는 VPD context를 설정한 뒤 한 테이블을 SELECT하는 기본 구성입니다. 실제 수정·저장은 <a href="/ords-handlers">조회 연동</a> 화면에서 parsing schema 권한으로 진행하세요.</p>
<pre th:text="${source}">source</pre>
</section>
</div>

View File

@@ -164,7 +164,7 @@ ROWNUM &lt;= :row_limit</pre>
</section>
<div class="alert alert-light mb-0" th:if="${result.successLike() and !result.hasSqlTrace()}">
이 Handler는 SQL trace 정보를 반환하지 않았고 백오피스 DB에서도 기본 VPD predicate를 조회하지 못했습니다. <a href="/ords-handlers">ORDS 핸들러</a>에서 trace가 포함된 소스를 적용하고, <code>CB_AGENT_DOC_VPD_FILTER</code> 실행 권한을 확인하세요.
이 Handler는 SQL trace 정보를 반환하지 않았고 운영 DB에서도 기본 VPD predicate를 조회하지 못했습니다. <a href="/ords-handlers">조회 연동</a>에서 trace가 포함된 소스를 적용하고, <code>CB_AGENT_DOC_VPD_FILTER</code> 실행 권한을 확인하세요.
</div>
<section class="next-action-card">

View File

@@ -1,14 +1,13 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('그룹 관리')}"></head>
<head th:replace="~{fragments/layout :: head('그룹')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container py-4">
<div class="page-title">
<h1>그룹 관리</h1>
<p class="context-summary">사용자를 묶고 역할을 한 번에 상속시키는 관리 화면입니다.</p>
<h1>그룹</h1>
<details class="explanation-details">
<summary>그룹 권한 흐름 설명 보기</summary>
<summary>도움말</summary>
<p>사용자 그룹을 만들고 그룹에 사용자와 역할을 부여합니다. 권한은 역할에 연결되고, 사용자는 직접 역할과 그룹 역할을 함께 상속합니다.</p>
</details>
</div>

View File

@@ -1,14 +1,13 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('로그인')}"></head>
<head th:replace="~{fragments/layout :: head('VPD 권한 운영')}"></head>
<body class="login-body">
<main class="login-shell">
<section class="login-panel">
<div class="login-brand">
<span class="login-mark">VPD</span>
<div>
<h1>VPD Backoffice</h1>
<p>권한, 토큰, ORDS 검증을 관리합니다.</p>
<h1>VPD 권한 운영 콘솔</h1>
</div>
</div>

View File

@@ -1,14 +1,13 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('MCP Chatbot')}"></head>
<head th:replace="~{fragments/layout :: head('대화형 검색')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container page-shell">
<section class="page-heading compact-heading">
<h1 class="h3">MCP Chatbot</h1>
<p class="context-summary">질문을 분석해 알맞은 MCP 도구와 VPD/ORDS 결과를 답변으로 연결합니다.</p>
<h1 class="h3">대화형 검색</h1>
<details class="explanation-details">
<summary>Chatbot 처리 흐름 보기</summary>
<summary>도움말</summary>
<p>질문을 MCP tool로 라우팅하고, 권한 태그·VPD 행 제한·ORDS 결과를 답변으로 정리합니다.</p>
</details>
</section>

View File

@@ -1,15 +1,14 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('MCP Client')}"></head>
<head th:replace="~{fragments/layout :: head('연동 점검')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container page-shell">
<section class="page-heading">
<h1>MCP Client</h1>
<p class="context-summary">Java client가 MCP 연결과 사용 가능한 도구 계약을 확인합니다.</p>
<h1>연동 점검</h1>
<details class="explanation-details">
<summary>Client 연동 범위 보기</summary>
<p>백오피스 Java client가 MCP message endpoint의 initialize/tools/list 계약을 확인합니다. 실제 tool 선택과 호출은 reasoning 결과를 받은 MCP client가 담당합니다.</p>
<summary>도움말</summary>
<p>연동 Java client가 MCP message endpoint의 initialize/tools/list 계약을 확인합니다. 실제 tool 선택과 호출은 reasoning 결과를 받은 MCP client가 담당합니다.</p>
</details>
</section>

View File

@@ -1,14 +1,13 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('MCP Reasoning')}"></head>
<head th:replace="~{fragments/layout :: head('검색 해석')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container py-4">
<div class="page-title">
<h1>MCP Reasoning</h1>
<p class="context-summary">질문에 맞는 MCP tool을 자동 선택하고 결과를 설명합니다.</p>
<h1>검색 해석</h1>
<details class="explanation-details">
<summary>Reasoning 처리 흐름 보기</summary>
<summary>도움말</summary>
<p>질문을 근거로 MCP tool을 고르고, Bearer Token으로 VPD/ORDS 결과를 조회한 뒤 모델이 설명합니다.</p>
</details>
</div>

View File

@@ -1,14 +1,13 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('MCP SSE')}"></head>
<head th:replace="~{fragments/layout :: head('MCP 연동')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container page-shell">
<section class="page-heading">
<h1>MCP SSE</h1>
<p class="context-summary">외부 MCP client가 ORDS 조회 도구를 호출하는 SSE 연결 정보를 제공합니다.</p>
<h1>MCP 연동</h1>
<details class="explanation-details">
<summary>SSE와 context path 설명 보기</summary>
<summary>도움말</summary>
<p>보호 객체 ORDS 조회를 외부 MCP client에서 호출할 수 있도록 context path별 SSE transport로 제공합니다.</p>
</details>
</section>

View File

@@ -1,14 +1,13 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('ORDS 조회 Handler 생성')}"></head>
<head th:replace="~{fragments/layout :: head('조회 대상')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container py-4">
<div class="page-title">
<h1>ORDS 조회 대상 등록</h1>
<p class="context-summary">DB 객체를 조회 Handler에 연결하는 화면입니다. 접근 권한은 별도로 관리합니다.</p>
<h1>조회 대상</h1>
<details class="explanation-details">
<summary>이 화면의 역할 설명 보기</summary>
<summary>도움말</summary>
<p>이 화면은 DB 객체를 HTTP로 연결할 경로만 등록합니다. 누가 어떤 행을 볼 수 있는지는 권한 규칙과 VPD에서 결정합니다.</p>
</details>
</div>
@@ -21,8 +20,6 @@
</details>
</div>
<section th:replace="~{fragments/layout :: architectureStrip('ords')}"></section>
<div class="alert alert-success" th:if="${message}" th:text="${message}"></div>
<div class="alert alert-danger" th:if="${errorMessage}" th:text="${errorMessage}"></div>

View File

@@ -1,14 +1,13 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('운영 상태')}"></head>
<head th:replace="~{fragments/layout :: head('운영 현황')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container py-4">
<div class="page-title">
<h1>운영 상태</h1>
<p class="context-summary">보호 객체별 연결 상태와 최근 검증 결과를 한눈에 봅니다.</p>
<h1>운영 현황</h1>
<details class="explanation-details">
<summary>운영 상태 항목 설명 보기</summary>
<summary>도움말</summary>
<p>보호 객체별 ORDS handler, VPD policy, 권한 rule, 최근 검증 상태를 확인합니다. 이상이 있을 때는 해당 객체의 권한·VPD·ORDS 순서로 점검하세요.</p>
</details>
</div>

View File

@@ -1,20 +1,19 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('ORDS 핸들러')}"></head>
<head th:replace="~{fragments/layout :: head('조회 연동')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container py-4">
<div class="page-title">
<h1>ORDS 핸들러</h1>
<p class="context-summary">토큰을 DB 권한 컨텍스트로 바꾸는 Handler를 확인·수정합니다.</p>
<h1>조회 연동</h1>
<details class="explanation-details">
<summary>Handler 처리 흐름 설명 보기</summary>
<p>Bearer Token을 DB 컨텍스트로 변환한 뒤 VPD가 권한체계를 적용합니다. 아래 소스는 등록된 ORDS Handler가 실제로 실행하는 기술 세부 내용입니다. 실행 결과의 “토큰 적용 후 SQL”은 권한 결과 확인 메뉴에서 확인할 수 있습니다.</p>
<summary>도움말</summary>
<p>Bearer Token을 DB 컨텍스트로 변환한 뒤 VPD가 권한체계를 적용합니다. 아래 소스는 등록된 ORDS Handler가 실제로 실행하는 기술 세부 내용입니다. 실행 결과의 “토큰 적용 후 SQL”은 접근 검증 메뉴에서 확인할 수 있습니다.</p>
</details>
</div>
<div class="alert alert-info">
권한별로 VPD가 붙인 실제 행 조건을 확인하려면 <a href="/probe">권한 결과 확인</a>에서 이 Handler의 보호 객체를 실행하세요. SQL trace를 반환하도록 갱신된 Handler는 같은 ORDS 세션의 <code>CB_AGENT_DOC_VPD_FILTER</code> predicate를 함께 표시합니다.
권한별로 VPD가 붙인 실제 행 조건을 확인하려면 <a href="/probe">접근 검증</a>에서 이 Handler의 보호 객체를 실행하세요. SQL trace를 반환하도록 갱신된 Handler는 같은 ORDS 세션의 <code>CB_AGENT_DOC_VPD_FILTER</code> predicate를 함께 표시합니다.
</div>
<div class="alert alert-success" th:if="${message}" th:text="${message}"></div>

View File

@@ -1,27 +1,24 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('권한 관리')}"></head>
<head th:replace="~{fragments/layout :: head('접근 정책')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container py-4">
<div class="page-title">
<h1>권한 관리</h1>
<p class="context-summary">역할이 어떤 객체의 어느 행과 컬럼을 볼 수 있는지 정의합니다.</p>
<h1>접근 정책</h1>
<details class="explanation-details">
<summary>권한 규칙과 VPD의 관계 보기</summary>
<summary>도움말</summary>
<p>VPD policy function이 참조할 사용자, 역할, 행 규칙, 컬럼 원문 허용 규칙을 저장합니다. 이 화면에서 만든 규칙이 기본 Filter의 동적 입력이 됩니다.</p>
</details>
</div>
<section th:replace="~{fragments/layout :: architectureStrip('permission')}"></section>
<div class="alert alert-success" th:if="${message}" th:text="${message}"></div>
<div class="alert alert-danger" th:if="${error}" th:text="${error}"></div>
<section class="content-band">
<div class="section-heading">
<div>
<h2>권한 규칙</h2>
<h2>접근 정책</h2>
<p class="section-description">저장 전 적용 대상과 행 범위를 검토한 뒤 VPD 권한을 반영합니다.</p>
</div>
<div class="wizard-progress" aria-label="권한 추가 단계">
@@ -246,7 +243,7 @@
</section>
<section class="content-band">
<h2>권한 목록</h2>
<h2>접근 정책 목록</h2>
<div class="table-responsive">
<table class="table table-sm align-middle">
<thead>

View File

@@ -1,20 +1,17 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('권한 결과 확인')}"></head>
<head th:replace="~{fragments/layout :: head('접근 검증')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container py-4">
<div class="page-title">
<h1>권한 결과 확인</h1>
<p class="context-summary">토큰을 사용해 실제로 보이는 행과 컬럼을 확인합니다.</p>
<h1>접근 검증</h1>
<details class="explanation-details">
<summary>검증 단계 설명 보기</summary>
<summary>도움말</summary>
<p>한 사용자의 토큰으로 실제 데이터를 요청해, 설계한 권한이 DB에서 그대로 적용되는지 확인합니다.</p>
</details>
</div>
<section th:replace="~{fragments/layout :: architectureStrip('ords')}"></section>
<section class="content-band guided-check-intro">
<div>
<span class="architecture-kicker">이번 단계에서 확인하는 것</span>
@@ -70,7 +67,7 @@
벡터 검색어 (평문)
<textarea class="form-control" name="requestBody" rows="3" required disabled
placeholder="예: Oracle VPD에서 ORDS 권한을 적용하는 방법"></textarea>
<span class="form-hint">검색어를 평문으로 입력하면 백오피스가 같은 임베딩 방식으로 벡터화해 전용 ORDS Handler에 전달합니다.</span>
<span class="form-hint">검색어를 평문으로 입력하면 시스템이 같은 임베딩 방식으로 벡터화해 전용 조회 경로에 전달합니다.</span>
</label>
<label class="vector-probe-input" data-vector-probe-input hidden>
임베딩 방식
@@ -80,7 +77,7 @@
</select>
<span class="form-hint">자료 등록 때 사용한 방식·차원과 맞춰야 합니다. AI 설정이 없으면 로컬 임베딩을 사용하세요.</span>
</label>
<button class="btn rw-btn-primary probe-submit" type="submit">3. 권한 결과 확인</button>
<button class="btn rw-btn-primary probe-submit" type="submit">3. 접근 검증 실행</button>
</form>
</section>

View File

@@ -1,14 +1,13 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('역할 관리')}"></head>
<head th:replace="~{fragments/layout :: head('역할')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container py-4">
<div class="page-title">
<h1>역할 관리</h1>
<p class="context-summary">권한 규칙을 묶어 사용자·그룹에 부여하는 단위입니다.</p>
<h1>역할</h1>
<details class="explanation-details">
<summary>역할과 표시 보호 상한 설명 보기</summary>
<summary>도움말</summary>
<p>사용자에게 부여할 역할을 관리합니다. 표시 보호 등급 상한은 허용된 행에서 원문으로 볼 수 있는 컬럼 등급의 참고값이며, 행 접근 권한은 권한 규칙과 VPD에서 결정합니다.</p>
</details>
</div>

View File

@@ -5,12 +5,11 @@
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container py-4 settings-page">
<div class="page-title">
<a class="back-link" href="/settings">연결 설정으로 돌아가기</a>
<a class="back-link" href="/settings">시스템 설정으로 돌아가기</a>
<h1>DB 준비 상태</h1>
<p class="context-summary">DB에 영향을 주는 지원 DDL과 VPD·ORDS runtime 준비 상태를 확인합니다.</p>
<details class="explanation-details">
<summary>실행 전 반드시 확인할 내용</summary>
<p><strong>지원 테이블 생성/확인</strong>은 현재 연결 사용자의 백오피스 메타데이터를 생성하거나 보강합니다. VPD context, ORDS enablement, grant는 여기서 실행하지 않으며, 아래 sqlcl preview 순서를 따릅니다.</p>
<summary>도움말</summary>
<p><strong>지원 테이블 생성/확인</strong>은 현재 연결 사용자의 운영 메타데이터를 생성하거나 보강합니다. VPD context, ORDS enablement, grant는 여기서 실행하지 않으며, 아래 sqlcl preview 순서를 따릅니다.</p>
</details>
</div>
@@ -28,7 +27,7 @@
<h2>현재 DB 준비 상태</h2>
<p class="section-subtitle">현재 연결 사용자 기준으로 메타데이터, VPD runtime, ORDS runtime, grant 상태를 점검합니다.</p>
</div>
<a class="btn btn-sm rw-btn-secondary" href="/operation-status">운영 상태 보기</a>
<a class="btn btn-sm rw-btn-secondary" href="/operation-status">운영 현황 보기</a>
</div>
<div class="schema-summary">
<div><span>현재 연결 사용자</span><strong th:text="${schemaPreflight.currentUser()}">ADMIN</strong></div>
@@ -43,7 +42,7 @@
<thead><tr><th>Layer</th><th>Item</th><th>Type</th><th>Status</th><th>Detail</th><th>Next Action</th></tr></thead>
<tbody>
<tr th:each="check : ${schemaPreflight.checks()}">
<td th:text="${check.layer()}">Backoffice Metadata</td>
<td th:text="${check.layer()}">운영 메타데이터</td>
<td><code th:text="${check.itemName()}">CB_APP_USER</code></td>
<td th:text="${check.objectType()}">TABLE</td>
<td><span class="badge" th:classappend="${check.ok()} ? ' text-bg-success' : (${check.warning()} ? ' text-bg-warning' : (${check.missing()} ? ' text-bg-secondary' : ' text-bg-danger'))" th:text="${check.status()}">OK</span></td>
@@ -68,7 +67,7 @@
<div>
<span class="architecture-kicker">영향 있는 작업</span>
<h2>지원 테이블 생성/확인</h2>
<p class="section-subtitle">현재 연결 사용자에 백오피스 지원 테이블, 시퀀스, 컬럼을 생성하거나 보강합니다. VPD·ORDS runtime은 실행하지 않습니다.</p>
<p class="section-subtitle">현재 연결 사용자에 운영 지원 테이블, 시퀀스, 컬럼을 생성하거나 보강합니다. VPD·ORDS runtime은 실행하지 않습니다.</p>
</div>
</div>
<ul class="settings-impact-list">

View File

@@ -1,14 +1,13 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('연결 설정')}"></head>
<head th:replace="~{fragments/layout :: head('시스템 설정')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container py-4 settings-page">
<div class="page-title">
<h1>연결 설정</h1>
<p class="context-summary">백오피스가 ORDS를 호출할 주소를 관리합니다. DB 지원 DDL은 별도 안전 화면에서 실행합니다.</p>
<h1>시스템 설정</h1>
<details class="explanation-details">
<summary>이 화면에서 바꾸는 범위 보기</summary>
<summary>도움말</summary>
<p>여기서는 ORDS Base URL만 저장합니다. VPD runtime, grant, 지원 테이블 생성처럼 DB에 영향을 주는 작업은 <strong>DB 준비 상태</strong>에서 별도로 확인·실행합니다.</p>
</details>
</div>
@@ -25,7 +24,7 @@
<div>
<span class="architecture-kicker">ORDS 연결</span>
<h2>ORDS Base URL</h2>
<p class="section-subtitle">저장 후 권한 결과 확인과 연동 도구가 이 주소를 사용합니다.</p>
<p class="section-subtitle">저장 후 접근 검증과 조회 연동이 이 주소를 사용합니다.</p>
</div>
</div>
<form method="post" action="/settings/ords" class="form-grid">

View File

@@ -1,28 +1,25 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('검증 세션 발급')}"></head>
<head th:replace="~{fragments/layout :: head('검증 세션')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container py-4">
<div class="page-title">
<h1>검증 세션 발급</h1>
<p class="context-summary">선택한 사용자의 접근 권한을 확인할 일회성 Bearer Token을 발급합니다.</p>
<h1>검증 세션</h1>
<details class="explanation-details">
<summary>토큰 보관 방식 설명 보기</summary>
<summary>도움말</summary>
<p>검증할 사용자를 나타내는 토큰을 만들고, 한 번만 표시되는 원문을 다음 단계에서 사용합니다. 원문은 저장하지 않고 해시와 식별용 prefix만 보관합니다.</p>
</details>
</div>
<section th:replace="~{fragments/layout :: architectureStrip('token')}"></section>
<div class="alert alert-success" th:if="${message}" th:text="${message}"></div>
<section class="issued-token-card" th:if="${issued}" th:attr="data-issued-key-id=${issued.keyId()}">
<span class="architecture-kicker">발급 완료 · 지금 한 번만 표시됩니다</span>
<h2>이 토큰 원문을 복사한 뒤 권한 결과 확인으로 이동하세요.</h2>
<h2>이 토큰 원문을 복사한 뒤 접근 검증으로 이동하세요.</h2>
<code class="token-value" id="issued-token-value" th:text="${issued.plainToken()}"></code>
<div class="action-stack mt-3">
<button class="btn rw-btn-primary" type="button" data-copy-source="issued-token-value">토큰 원문 복사</button>
<a class="btn rw-btn-secondary" href="/probe">권한 결과 확인으로 이동</a>
<a class="btn rw-btn-secondary" href="/probe">접근 검증으로 이동</a>
</div>
<p class="form-hint mb-0">화면을 떠난 뒤에는 원문을 다시 볼 수 없습니다. 잃어버리면 기존 토큰을 복구하지 말고 새로 발급하세요.</p>
</section>

View File

@@ -1,14 +1,13 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('사용자 관리')}"></head>
<head th:replace="~{fragments/layout :: head('사용자')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container py-4">
<div class="page-title">
<h1>사용자 관리</h1>
<p class="context-summary">토큰과 권한의 기준이 되는 백오피스 사용자를 관리합니다.</p>
<h1>사용자</h1>
<details class="explanation-details">
<summary>사용자와 DB 계정의 차이 보기</summary>
<summary>도움말</summary>
<p>Bearer Token과 권한 매핑에 사용할 내부 사용자를 관리합니다. 여기의 사용자는 Oracle DB schema 계정이 아니라 권한체계에서 식별하는 application user입니다.</p>
</details>
</div>

View File

@@ -1,20 +1,17 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('지식 검색 관리')}"></head>
<head th:replace="~{fragments/layout :: head('지식 검색')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container py-4">
<div class="page-title">
<h1>지식 검색 관리</h1>
<p class="context-summary">지식자료를 등록하고 접근 정책에 따라 검색 결과를 제공합니다.</p>
<h1>지식 검색</h1>
<details class="explanation-details">
<summary>검색 정책과 처리 방식</summary>
<summary>도움말</summary>
<p>문서는 검색 단위로 나누고 각 단위를 임베딩으로 변환합니다. 기술 태그는 접근 정책의 <code>TAG</code> 조건으로 사용되며, 검색 시 DB 보호 정책이 허용된 지식자료만 남깁니다. <code>Bearer Token</code>은 검증 세션에만 사용되고 검색이 끝나면 폐기됩니다.</p>
</details>
</div>
<section th:replace="~{fragments/layout :: architectureStrip('ords')}"></section>
<div class="alert alert-success" th:if="${message}" th:text="${message}"></div>
<div class="alert alert-danger" th:if="${errorMessage}" th:text="${errorMessage}"></div>
<div class="alert alert-warning" th:if="${runtimeError}">
@@ -25,7 +22,6 @@
<section class="content-band">
<div class="section-heading">
<div>
<span class="architecture-kicker">1 · CONTENT</span>
<h2>지식자료 등록</h2>
<p class="section-subtitle">같은 문서 ID로 저장하면 최신 내용으로 갱신됩니다.</p>
</div>
@@ -86,7 +82,6 @@
<section class="content-band">
<div class="section-heading">
<div>
<span class="architecture-kicker">2 · ACCESS POLICY</span>
<h2>접근 정책 설정</h2>
<p class="section-subtitle">역할별 기술 태그 접근 규칙은 권한 관리에서 관리합니다.</p>
</div>
@@ -115,7 +110,6 @@ AND NOT (DENY TAG C OR DENY TAG D)</pre>
<section class="content-band">
<div class="section-heading">
<div>
<span class="architecture-kicker">3 · SEARCH</span>
<h2>권한 기반 지식 검색</h2>
<p class="section-subtitle">검색 권한 주체에 대한 검증용 세션을 사용하고 완료 후 폐기합니다.</p>
</div>

View File

@@ -1,15 +1,14 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('별도 Filter 고급 관리')}"></head>
<head th:replace="~{fragments/layout :: head('고급 접근 조건')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container py-4">
<div class="page-title">
<span class="badge text-bg-warning">고급 설정</span>
<h1 class="mt-2">별도 Filter 관리</h1>
<p class="context-summary">기본 권한체계로 표현할 수 없는 객체 전용 조건만 관리합니다.</p>
<h1 class="mt-2">고급 접근 조건</h1>
<details class="explanation-details">
<summary>별도 Filter를 사용하는 기준 보기</summary>
<summary>도움말</summary>
<p>일반 권한은 이 화면에서 만들지 않습니다. 권한체계로 표현할 수 없는 객체 전용 조건이 있을 때만 별도 함수를 추가합니다.</p>
</details>
</div>

View File

@@ -1,20 +1,17 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('VPD 설정')}"></head>
<head th:replace="~{fragments/layout :: head('보호 상태')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container py-4">
<div class="page-title">
<h1>DB 보호 연결</h1>
<p class="context-summary">권한체계를 실제 TABLE/VIEW에 연결하고 적용 상태를 확인합니다.</p>
<h1>보호 상태</h1>
<details class="explanation-details">
<summary>VPD 보호 연결의 의미 보기</summary>
<summary>도움말</summary>
<p>권한 화면에서 만든 사용자·그룹·역할·행·열 규칙을 Oracle VPD가 실제 TABLE/VIEW에 적용하도록 연결합니다.</p>
</details>
</div>
<section th:replace="~{fragments/layout :: architectureStrip('vpd')}"></section>
<div class="alert alert-warning" th:if="${runtimeError}">
<strong th:text="${runtimeError.title()}">조회할 수 없습니다.</strong>
<span th:text="${runtimeError.message()}">message</span>
@@ -111,7 +108,7 @@
</label>
<label class="form-check vpd-support-toggle">
<input class="form-check-input" type="checkbox" data-vpd-target-include-support>
<span class="form-check-label">백오피스 지원 테이블 포함</span>
<span class="form-check-label">운영 지원 테이블 포함</span>
</label>
<button class="btn btn-sm rw-btn-secondary" type="button" data-vpd-target-reset>초기화</button>
<div class="vpd-target-count" aria-live="polite">
@@ -130,7 +127,7 @@
<th>VPD 상태</th>
<th>Policy</th>
<th>Filter</th>
<th>백오피스 권한 테이블</th>
<th>운영 권한 테이블</th>
<th>검증용 ORDS 경로</th>
</tr>
</thead>

View File

@@ -5,6 +5,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import org.junit.jupiter.api.Test;
class GuidedFlowTemplateTest {
@@ -12,18 +13,43 @@ class GuidedFlowTemplateTest {
@Test
void dashboardPrioritizesActionsAndKeepsDetailCollapsed() throws IOException {
String html = template("dashboard.html");
String layout = template("fragments/layout.html");
assertThat(html)
.contains("th:replace=\"~{fragments/layout :: head('VPD 권한 백오피스')}\"")
.contains("누가 어떤 데이터를 볼 수 있는지 설정하고 확인하세요")
.contains("권한 결과 확인")
.contains("권한 규칙 관리")
.contains("권한 적용 4단계")
.contains("workflow-steps")
.contains("th:replace=\"~{fragments/layout :: head('VPD 권한 운영')}\"")
.contains("권한 관리")
.contains("접근 검증")
.contains("접근 정책")
.contains("현재 상태")
.contains("작동 방식과 등록 대상 보기")
.contains("상세 현황")
.contains("<details class=\"dashboard-details\">")
.doesNotContain("journey-card");
.doesNotContain("journey-card")
.doesNotContain("dashboard-workflow")
.doesNotContain(">01<", ">02<");
assertThat(layout)
.contains("VPD 권한 운영")
.contains("데이터 보호")
.contains("접근 검증")
.contains("지식 검색")
.contains("시스템 관리")
.doesNotContain("VPD Backoffice");
}
@Test
void productPagesKeepBackgroundGuidanceBehindAConsistentHelpControl() throws IOException {
for (String page : List.of(
"permissions.html", "vpd-policies.html", "tokens.html", "probe.html", "objects.html",
"effective-matrix.html", "users.html", "groups.html", "roles.html", "vector-knowledge.html",
"operation-status.html", "settings.html", "settings-database.html", "ords-handlers.html",
"vpd-filter-policies.html", "mcp-chatbot.html", "mcp-reasoning.html", "mcp-client-demo.html",
"mcp-sse.html")) {
assertThat(template(page))
.contains("<summary>도움말</summary>")
.doesNotContain("context-summary")
.doesNotContain("architectureStrip");
}
assertThat(template("vector-knowledge.html"))
.doesNotContain("1 · CONTENT", "2 · ACCESS POLICY", "3 · SEARCH");
}
@Test
@@ -129,7 +155,7 @@ class GuidedFlowTemplateTest {
String database = template("settings-database.html");
assertThat(settings)
.contains("연결 설정")
.contains("시스템 설정")
.contains("/settings/database")
.doesNotContain("action=\"/settings/database/initialize\"");
assertThat(database)