feat: align DDS with common permission and vector flow
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
<!doctype html>
|
||||
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
|
||||
<head th:replace="~{fragments/layout :: head('DDS 지식 검색 관리')}"></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>
|
||||
<details class="explanation-details">
|
||||
<summary>이 화면의 큰 흐름 보기</summary>
|
||||
<p>문서는 검색 가능한 청크로 나뉘고 각 청크에 기술 태그가 붙습니다. 권한 관리에서 역할별로 허용할 태그를 등록하면 DDS의 DATA GRANT가 해당 태그가 붙은 청크만 검색 대상으로 남깁니다.</p>
|
||||
<p class="mb-0">VPD 화면과 같은 사용자·그룹·역할·권한 규칙을 사용하지만, 실행 시점에는 Bearer 기반 VPD predicate가 아니라 DDS END USER의 DATA ROLE과 DATA GRANT가 적용됩니다.</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}">
|
||||
벡터 저장소를 확인할 수 없습니다. <span th:text="${runtimeError}"></span>
|
||||
</div>
|
||||
|
||||
<section class="content-band">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<span class="architecture-kicker">1 · CONTENT</span>
|
||||
<h2>지식자료 등록</h2>
|
||||
<p class="section-subtitle">문서 ID가 같으면 기존 청크를 교체합니다. 저장소는 VPD 트랙과 동일한 지식자료 형식을 사용합니다.</p>
|
||||
</div>
|
||||
<span class="badge text-bg-secondary" th:text="${summary == null ? '저장소 확인 필요' : summary.chunkCount() + '개 검색 단위'}">0개 검색 단위</span>
|
||||
</div>
|
||||
<details class="explanation-details">
|
||||
<summary>청킹·임베딩 설정 보기</summary>
|
||||
<ul>
|
||||
<li>문단을 우선 묶고 길이가 길면 문장·공백 기준으로 청크를 나눕니다.</li>
|
||||
<li>기본 로컬 임베딩은 외부 API 없이 흐름을 검증하는 개발용 4차원 벡터입니다.</li>
|
||||
<li>운영에서는 등록과 검색에 같은 모델·차원을 사용해야 합니다.</li>
|
||||
</ul>
|
||||
</details>
|
||||
<form method="post" action="/vector-knowledge/ingest" class="form-grid mt-3">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||
<label>문서 ID<input class="form-control" name="documentId" placeholder="knowledge-security-001" required></label>
|
||||
<label>문서 제목<input class="form-control" name="title" placeholder="DDS 기술 태그 권한 설계" required></label>
|
||||
<label>원문 주소 (선택)<input class="form-control" name="sourceUri" placeholder="kb://security/dds-vector"></label>
|
||||
<label>기술 태그 (쉼표 또는 공백)
|
||||
<input class="form-control" name="techTags" placeholder="SPRING_BOOT ORACLE_DDS" required>
|
||||
<span class="form-hint">태그는 대문자로 정규화됩니다. 권한 규칙의 TAG 값과 일치해야 합니다.</span>
|
||||
</label>
|
||||
<label>검색 단위 길이<input class="form-control" name="chunkSize" type="number" min="80" max="2000" value="600"></label>
|
||||
<label>임베딩 방식
|
||||
<select class="form-select" name="embeddingMode">
|
||||
<option value="DEMO">로컬 임베딩(개발용)</option>
|
||||
<option value="AI" th:disabled="${!aiEmbeddingConfigured}">AI 임베딩 (설정 필요)</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="span-2">문서 본문
|
||||
<textarea class="form-control" name="content" rows="8" placeholder="문서 내용을 붙여 넣으세요." required></textarea>
|
||||
</label>
|
||||
<button class="btn rw-btn-primary" type="submit">지식자료 저장</button>
|
||||
</form>
|
||||
<div class="alert alert-info mt-3 mb-0" th:if="${ingestResult}">
|
||||
<strong th:text="${ingestResult.documentId()}">document</strong>
|
||||
<span th:text="${ingestResult.chunkCount() + '개 검색 단위 / ' + ingestResult.tagCount() + '개 태그 연결 / ' + ingestResult.embeddingModel()}">저장 완료</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="content-band">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<span class="architecture-kicker">2 · POLICY</span>
|
||||
<h2>권한 규칙은 한 곳에서 관리합니다.</h2>
|
||||
<p class="section-subtitle">사용자·그룹·역할·TAG 규칙은 VPD와 같은 권한 관리 화면에서 작성합니다.</p>
|
||||
</div>
|
||||
<a class="btn btn-sm rw-btn-primary" href="/permissions">권한 관리 열기</a>
|
||||
</div>
|
||||
<div class="macro-micro-grid">
|
||||
<div><h3>관리 관점</h3><p>역할에 <code>ALLOW TAG</code>를 여러 개 등록하면 태그 중 하나라도 맞는 청크를 허용합니다. <code>DENY TAG</code>는 허용 후보에서 제외합니다.</p></div>
|
||||
<div><h3>DDS 실행 관점</h3><p><code th:text="${ddsVectorObject}">ADMIN.CB_DDS_VECTOR_SEARCH_DOCUMENTS</code>에 연결된 DATA GRANT가 END USER의 DATA ROLE을 통해 이 규칙을 집행합니다.</p></div>
|
||||
</div>
|
||||
<details class="explanation-details mt-3">
|
||||
<summary>VPD와 DDS의 차이 보기</summary>
|
||||
<p>VPD는 요청마다 권한 테이블을 읽어 predicate를 계산합니다. DDS는 보호 VIEW에 선언한 DATA GRANT와 END USER 매핑을 DB가 적용합니다. 따라서 태그 규칙을 바꾸면 DDS grant 반영 절차도 함께 확인해야 합니다.</p>
|
||||
</details>
|
||||
</section>
|
||||
|
||||
<section class="content-band">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<span class="architecture-kicker">3 · SEARCH</span>
|
||||
<h2>DDS 권한으로 지식 검색</h2>
|
||||
<p class="section-subtitle">선택한 END USER로 직접 접속해 DATA ROLE/DATA GRANT가 통과시킨 청크만 반환합니다.</p>
|
||||
</div>
|
||||
<span class="badge text-bg-primary" th:text="${ddsVectorObject}">DDS view</span>
|
||||
</div>
|
||||
<details class="explanation-details">
|
||||
<summary>검색 처리 방식 보기</summary>
|
||||
<ol>
|
||||
<li>검색어를 등록 때와 같은 방식으로 임베딩합니다.</li>
|
||||
<li>DDS END USER로 보호 VIEW에 연결합니다.</li>
|
||||
<li>DATA ROLE/DATA GRANT가 허용하지 않은 태그 청크는 객체 권한 단계에서 제외합니다.</li>
|
||||
<li>남은 청크를 벡터 관련도 순으로 반환합니다.</li>
|
||||
</ol>
|
||||
</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
|
||||
<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>
|
||||
</select>
|
||||
<span class="form-hint">비밀번호는 서버 환경 변수에서만 읽고 화면에 표시하지 않습니다.</span>
|
||||
</label>
|
||||
<label>결과 수<input class="form-control" name="limit" type="number" min="1" max="100" value="10"></label>
|
||||
<label>임베딩 방식
|
||||
<select class="form-select" name="embeddingMode">
|
||||
<option value="DEMO">로컬 임베딩(개발용)</option>
|
||||
<option value="AI" th:disabled="${!aiEmbeddingConfigured}">AI 임베딩</option>
|
||||
</select>
|
||||
</label>
|
||||
<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>
|
||||
</form>
|
||||
<section id="vector-search-result" class="mt-3" aria-live="polite">
|
||||
<div class="empty-result-guide">END USER와 질문을 선택하면 DDS가 허용한 검색 단위만 표시됩니다.</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section class="content-band">
|
||||
<details class="explanation-details">
|
||||
<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>
|
||||
<div class="summary-tile"><span class="label">태그 연결</span><strong th:text="${summary == null ? '-' : summary.tagCount()}">0</strong></div>
|
||||
</div>
|
||||
</details>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user