[UX] #577 present VPD as a product console

This commit is contained in:
devmrko
2026-07-01 08:06:03 +09:00
parent 45621e1b7d
commit be14b4c797
27 changed files with 203 additions and 263 deletions

View File

@@ -1,4 +1,4 @@
# VPD Backoffice 전 페이지 페르소나 UX 리뷰와 개선 로드맵 # VPD 권한 운영 전 페이지 페르소나 UX 리뷰와 개선 로드맵
> **상태**: Review complete · UX-1/UX-2 implementation in progress > **상태**: Review complete · UX-1/UX-2 implementation in progress
> **작성**: [AI] UX Facilitator · **최종수정**: 2026-06-30 > **작성**: [AI] UX Facilitator · **최종수정**: 2026-06-30
@@ -87,3 +87,23 @@ UX-1부터 시작한다. 기존 데이터 모델과 VPD 로직은 바꾸지 않
- 브라우저 검증을 우회한 POST도 역할 누락·객체 형식·행 규칙 오류를 flash 오류로 돌려준다. 서버의 `PermissionService` 검증은 그대로 최종 방어선으로 유지한다. - 브라우저 검증을 우회한 POST도 역할 누락·객체 형식·행 규칙 오류를 flash 오류로 돌려준다. 서버의 `PermissionService` 검증은 그대로 최종 방어선으로 유지한다.
검증 범위는 `GuidedFlowTemplateTest`, `PermissionServiceTest`, 전체 Maven 테스트 및 배포 후 `/permissions` HTTP 확인이다. 검증 범위는 `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; 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 { .content-band > .explanation-details:first-child {
margin-top: 0; margin-top: 0;
} }
@@ -1845,7 +1878,7 @@ body {
color: var(--rw-text); color: var(--rw-text);
display: grid; display: grid;
gap: .65rem; gap: .65rem;
grid-template-columns: auto minmax(0, 1fr) auto; grid-template-columns: minmax(0, 1fr) auto;
min-width: 0; min-width: 0;
padding: .8rem; padding: .8rem;
text-decoration: none; text-decoration: none;
@@ -1862,20 +1895,6 @@ body {
border-color: color-mix(in srgb, var(--rw-primary) 35%, var(--rw-border)); 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 strong,
.dashboard-action small { .dashboard-action small {
display: block; display: block;
@@ -1938,57 +1957,6 @@ body {
text-decoration: none; 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 { .dashboard-details {
background: var(--rw-surface); background: var(--rw-surface);
border: 1px solid var(--rw-border); border: 1px solid var(--rw-border);
@@ -2382,7 +2350,6 @@ body {
} }
.dashboard-action-grid, .dashboard-action-grid,
.workflow-steps,
.dashboard-state dl { .dashboard-state dl {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
<head th:fragment="head(title)"> <head th:fragment="head(title)">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <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="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="/css/app.css" rel="stylesheet"> <link href="/css/app.css" rel="stylesheet">
<script src="https://unpkg.com/htmx.org@2.0.4"></script> <script src="https://unpkg.com/htmx.org@2.0.4"></script>
@@ -13,44 +13,49 @@
<body> <body>
<nav th:fragment="nav" class="navbar rw-nav navbar-expand-lg"> <nav th:fragment="nav" class="navbar rw-nav navbar-expand-lg">
<div class="container"> <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">
<div class="rw-menu-group"> <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"> <div class="rw-menu-panel">
<a class="nav-link" href="/users">사용자</a> <a class="nav-link" href="/users">사용자</a>
<a class="nav-link" href="/groups">그룹</a> <a class="nav-link" href="/groups">그룹</a>
<a class="nav-link" href="/roles">역할</a> <a class="nav-link" href="/roles">역할</a>
<a class="nav-link" href="/permissions">데이터 권한 규칙</a> <a class="nav-link" href="/permissions">접근 정책</a>
<a class="nav-link" href="/effective-matrix">사용자별 최종 권한</a> <a class="nav-link" href="/effective-matrix">사용자별 접근 확인</a>
</div> </div>
</div> </div>
<div class="rw-menu-group"> <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"> <div class="rw-menu-panel">
<a class="nav-link" href="/vpd-policies">DB 보호 연결</a> <a class="nav-link" href="/vpd-policies">보호 상태</a>
<a class="nav-link" href="/tokens">검증 세션 발급</a> <a class="nav-link" href="/objects">조회 대상</a>
<a class="nav-link" href="/probe">권한 결과 확인</a>
</div> </div>
</div> </div>
<div class="rw-menu-group"> <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"> <div class="rw-menu-panel">
<a class="nav-link" href="/objects">ORDS 조회 대상</a> <a class="nav-link" href="/tokens">검증 세션</a>
<a class="nav-link" href="/ords-handlers">ORDS 핸들러</a> <a class="nav-link" href="/probe">접근 검증</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>
</div> </div>
</div> </div>
<div class="rw-menu-group"> <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"> <div class="rw-menu-panel">
<a class="nav-link" href="/operation-status">운영 상태</a> <a class="nav-link" href="/vector-knowledge">지식 검색</a>
<a class="nav-link" href="/vpd-filter-policies">별도 Filter 관리</a> <a class="nav-link" href="/mcp-chatbot">대화형 검색</a>
<a class="nav-link" href="/settings">설정</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> </div>
</div> </div>
@@ -61,30 +66,5 @@
</div> </div>
</nav> </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> </body>
</html> </html>

View File

@@ -8,7 +8,7 @@
<span th:text="${object.owner() + '.' + object.objectName()}">ADMIN.TABLE_NAME</span> <span th:text="${object.owner() + '.' + object.objectName()}">ADMIN.TABLE_NAME</span>
<span class="text-muted"> / 기본 ORDS Handler PL/SQL</span> <span class="text-muted"> / 기본 ORDS Handler PL/SQL</span>
</h3> </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> <pre th:text="${source}">source</pre>
</section> </section>
</div> </div>

View File

@@ -164,7 +164,7 @@ ROWNUM &lt;= :row_limit</pre>
</section> </section>
<div class="alert alert-light mb-0" th:if="${result.successLike() and !result.hasSqlTrace()}"> <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> </div>
<section class="next-action-card"> <section class="next-action-card">

View File

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

View File

@@ -1,14 +1,13 @@
<!doctype html> <!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org"> <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"> <body class="login-body">
<main class="login-shell"> <main class="login-shell">
<section class="login-panel"> <section class="login-panel">
<div class="login-brand"> <div class="login-brand">
<span class="login-mark">VPD</span> <span class="login-mark">VPD</span>
<div> <div>
<h1>VPD Backoffice</h1> <h1>VPD 권한 운영 콘솔</h1>
<p>권한, 토큰, ORDS 검증을 관리합니다.</p>
</div> </div>
</div> </div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,6 +5,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
class GuidedFlowTemplateTest { class GuidedFlowTemplateTest {
@@ -12,18 +13,43 @@ class GuidedFlowTemplateTest {
@Test @Test
void dashboardPrioritizesActionsAndKeepsDetailCollapsed() throws IOException { void dashboardPrioritizesActionsAndKeepsDetailCollapsed() throws IOException {
String html = template("dashboard.html"); String html = template("dashboard.html");
String layout = template("fragments/layout.html");
assertThat(html) assertThat(html)
.contains("th:replace=\"~{fragments/layout :: head('VPD 권한 백오피스')}\"") .contains("th:replace=\"~{fragments/layout :: head('VPD 권한 운영')}\"")
.contains("누가 어떤 데이터를 볼 수 있는지 설정하고 확인하세요") .contains("권한 관리")
.contains("권한 결과 확인") .contains("접근 검증")
.contains("권한 규칙 관리") .contains("접근 정책")
.contains("권한 적용 4단계")
.contains("workflow-steps")
.contains("현재 상태") .contains("현재 상태")
.contains("작동 방식과 등록 대상 보기") .contains("상세 현황")
.contains("<details class=\"dashboard-details\">") .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 @Test
@@ -129,7 +155,7 @@ class GuidedFlowTemplateTest {
String database = template("settings-database.html"); String database = template("settings-database.html");
assertThat(settings) assertThat(settings)
.contains("연결 설정") .contains("시스템 설정")
.contains("/settings/database") .contains("/settings/database")
.doesNotContain("action=\"/settings/database/initialize\""); .doesNotContain("action=\"/settings/database/initialize\"");
assertThat(database) assertThat(database)