Compare commits
4 Commits
0ed048905a
...
2f67587b40
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f67587b40 | ||
|
|
699a1ac127 | ||
|
|
eea7bf0974 | ||
|
|
0babadee5f |
@@ -1,11 +1,34 @@
|
||||
# DDS 트랙 페르소나 UX 리뷰 및 개선 백로그
|
||||
|
||||
> **상태**: 리뷰 완료 · Redmine 등록 진행
|
||||
> **상태**: 리뷰 완료 · Redmine #578 등록 완료
|
||||
>
|
||||
> **범위**: DDS 독립 인스턴스의 홈, 보호 연결, 권한 반영, 지식 검색, 보호 객체 상세, 공통 메뉴/흐름 바
|
||||
>
|
||||
> **대상 파일**: `dds-home.html`, `vpd-policies.html`, `dds-provision.html`, `vector-knowledge.html`, `dds.html`, `fragments/layout.html`
|
||||
|
||||
## Redmine 페이지별 리뷰
|
||||
|
||||
| 이슈 | 페이지 | 검토 초점 |
|
||||
|---|---|---|
|
||||
| #579 | 로그인 | 관리자 콘솔 경계와 최소 안내 |
|
||||
| #580 | 홈 | 상태·다음 행동 중심 대시보드 |
|
||||
| #581 | DDS 보호 연결 | 집행 모드·보호 상태·드리프트 |
|
||||
| #582 | DDS 권한 반영 | 게시 안전성·영향·감사 |
|
||||
| #583 | 지식 검색 | 기본 제품 경로와 직접 비교 분리 |
|
||||
| #584 | 보호 객체 상세 | 실제 접근 근거 탐색 |
|
||||
| #585 | 공통 메뉴·구조 바 | 점진적 공개·용어·접근성 |
|
||||
|
||||
### #581 구현 하위 일감
|
||||
|
||||
| 이슈 | 책임 | 검증 가능한 산출물 |
|
||||
|---|---|---|
|
||||
| #606 | 보안 설계 | Grant 기대값·실제 inventory·게시/검증 이력을 비교하는 상태 전이와 API 계약 |
|
||||
| #607 | UX 설계 | 서비스 경로 기본 화면, 직접 비교 고급 탭, 상태·조치·오류 상태가 담긴 와이어프레임 |
|
||||
| #608 | 개발 | 객체 상태 화면, 드리프트 판정, 직접 비교 분리와 안전한 오류 처리 |
|
||||
| #609 | QA | 정상·미게시·드리프트·검증 실패·만료·inventory 오류 및 접근성 회귀 검증 |
|
||||
|
||||
상세 기준은 [`dds-protection-connection-review`](../dds-protection-connection-review/README.md)에 기록한다. #581은 이 네 일감의 완료 조건을 묶는 설계 기준이며, 화면 구현 자체는 #608에서 시작한다.
|
||||
|
||||
## 1. 검토 방식
|
||||
|
||||
같은 화면을 세 관점에서 검토했다.
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# #606 설계 보완 기준 — 보호 상태·증거 계약
|
||||
|
||||
## 목적
|
||||
|
||||
DDS 보호 객체 × 집행 경로의 상태를 재현 가능하게 계산한다. 이 계약은 #608 구현의 선행 조건이다.
|
||||
|
||||
## 상태 계약
|
||||
|
||||
각 상태 응답에는 아래 세 축을 반드시 포함한다.
|
||||
|
||||
| 축 | 값 |
|
||||
|---|---|
|
||||
| 관측 가능성 | `OBSERVED`, `PARTIAL`, `UNAVAILABLE` |
|
||||
| 선언 동기화 | `MATCHED`, `NOT_PUBLISHED`, `DRIFTED`, `UNKNOWN` |
|
||||
| 실행 검증 | `PASSED`, `FAILED`, `STALE`, `NOT_RUN`, `UNKNOWN` |
|
||||
|
||||
대표 상태는 `확인 불가 → 조치 필요 → 재검증 필요 → 보호 확인` 순으로 결정하되, 근거 축을 감추지 않는다. `PARTIAL`은 보호 확인으로 승격할 수 없다.
|
||||
|
||||
## 기대 명세와 실제 관측
|
||||
|
||||
- 토큰 서비스 경로는 기술 사용자, 객체, DATA GRANT/함수/공통 권한 schema version을 immutable 명세로 남긴다.
|
||||
- 직접 비교 경로는 사용자·Role별 컴파일 결과를 게시 계획 snapshot으로 남긴다.
|
||||
- 명세에는 `protectionObjectId`, 집행 경로, 객체·컬럼 범위, Role/기술 사용자, canonical predicate hash, 작성 시각, 작성자, 승인 참조, 명세 version을 포함한다.
|
||||
- canonicalization/hash 정규화 규칙 자체도 schema version으로 보존한다.
|
||||
- 실제 관측은 catalog capability matrix를 거친다. `DBA_DATA_GRANTS`로 확인되는 grant/object/grantee와 predicate·컬럼 범위를 비교하는 승인된 추가 원천을 구분한다.
|
||||
- 비교할 수 없는 필드는 `PARTIAL`/`UNKNOWN`으로 처리하며 `MATCHED`로 표시하지 않는다.
|
||||
|
||||
## 오류·감사·최신성
|
||||
|
||||
- dictionary 권한 부족, 연결 오류, timeout, 빈 inventory, 실제 Grant 없음은 별도의 안전한 오류 code로 구분한다.
|
||||
- 마지막 관측·마지막 정상 관측·correlation ID·관측 권한 범위를 기록한다.
|
||||
- 토큰 공통 권한 변경은 런타임 반영, 직접 비교 권한 변경은 별도 게시라는 최신성 계산 규칙을 명시한다.
|
||||
- 게시 이력은 명세 version/hash, 실행자, 사유, 승인, DB 결과, rollback 참조를 append-only로 보존한다.
|
||||
|
||||
## 검증 증거
|
||||
|
||||
fixture version, 명세 hash, 집행 경로, 주체 유형, control query, 반환/제외 수, 결과, 시각, 안전한 오류 분류를 기록한다. 검색 0건만으로 권한 차단을 판정하지 않는다.
|
||||
|
||||
## 완료 기준
|
||||
|
||||
상태 전이/우선순위표, API·persistence schema, catalog capability matrix, hash normalizer, fixture manifest와 sample record를 제공한다. #607·#608·#609가 동일한 contract를 소비할 수 있어야 한다.
|
||||
32
docs/design/dds-protection-connection-review/607-ux.md
Normal file
32
docs/design/dds-protection-connection-review/607-ux.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# #607 UX 보완 기준 — 객체 상태와 고급 검증 분리
|
||||
|
||||
## 기본 구조
|
||||
|
||||
- 기본 화면은 토큰 기반 서비스 경로만 보여 준다.
|
||||
- 객체 행에는 대표 상태, 동기화/검증 근거 배지, 마지막 관측·검증, 추천 조치 하나만 둔다.
|
||||
- DDS END USER 직접 비교는 명시적인 `고급 검증` 진입 뒤에만 제공한다.
|
||||
- `dds_demo_*`는 제품 계정이 아닌 `직접 비교용 검증 프로필`로 표기하며 자격증명은 보이지 않는다.
|
||||
|
||||
## 필수 상태
|
||||
|
||||
`보호 확인`, `조치 필요`, `재검증 필요`, `확인 불가`, `반영 중`의 대표 상태와 `드리프트`, `검증 실패`, `관측 일부` 등의 근거 배지를 함께 설계한다. #606의 세 상태 축을 한 값으로 축소하지 않는다.
|
||||
|
||||
객체 0개, 다수 객체 필터·정렬, 오래된 검증, 일부 catalog만 관측됨, inventory 오류, 권한 없는 상세, 390px 화면, 200% 확대, 키보드/스크린리더 탭 상태를 포함한다.
|
||||
|
||||
## 정보 노출
|
||||
|
||||
| 권한 | 제공 정보/행동 |
|
||||
|---|---|
|
||||
| 상태 조회 | 대표 상태, 안전한 사유, 마지막 관측 |
|
||||
| 진단 | 객체명·Role·안전한 diff 요약 |
|
||||
| 게시 | 미리보기, 사유·영향·승인 뒤 게시/회수 |
|
||||
|
||||
raw predicate, catalog 원문, 비밀, 차단 TAG, 원문 청크, DB 오류 원문은 일반 화면에 제공하지 않는다.
|
||||
|
||||
## URL·용어
|
||||
|
||||
DDS canonical route와 기존 `/vpd-policies` redirect/호환 기간을 제안한다. `0건 검색`은 `차단`이라는 상태 문구로 사용하지 않는다.
|
||||
|
||||
## 산출물과 완료 기준
|
||||
|
||||
화면 지도, 상태 매트릭스, 와이어프레임, CTA/빈/오류/처리 상태, 권한별 정보 노출표, 표준 용어, 접근성 명세를 낸다. 운영자는 설명을 열지 않고 상태·관측 시각·다음 행동을 이해하고, 두 집행 경로를 같은 계정이나 같은 기능으로 오해하지 않아야 한다.
|
||||
@@ -0,0 +1,25 @@
|
||||
# #608 개발 보완 기준 — 보호 연결 화면
|
||||
|
||||
## 선행 조건
|
||||
|
||||
#606의 상태·증거 contract와 #607의 UX 설계가 승인된 뒤 시작한다.
|
||||
|
||||
## 구현 범위
|
||||
|
||||
- 보호 객체 × 집행 경로 상태 조회 service/controller/view model
|
||||
- 관측 가능성, 선언 동기화, 실행 검증의 세 상태 축과 대표 상태·근거 배지 렌더링
|
||||
- 기본 토큰 서비스 화면과 고급 DDS END USER 직접 비교 분리, 기본 표면의 `dds_demo_*` 제거
|
||||
- catalog 오류의 안전한 메시지, correlation ID, 마지막 정상 관측, 반영 중 처리
|
||||
- 권한별 안전한 diff 요약과 객체 상세
|
||||
- DDS canonical route 및 기존 URL redirect
|
||||
- token/context/credential/raw predicate/차단 TAG/원문이 browser와 감사 메모에 노출되지 않도록 처리
|
||||
|
||||
## 제약
|
||||
|
||||
- DATA GRANT DDL 정책 변경이나 운영 객체 회수는 이 이슈의 범위가 아니다.
|
||||
- `DBA_DATA_GRANTS`만으로 비교할 수 없는 필드를 정상으로 선언하지 않는다.
|
||||
- connection pool을 사용하거나 도입할 때에는 Context reset/잔류 검증을 포함한다.
|
||||
|
||||
## 완료 기준
|
||||
|
||||
#581의 구현 완료 조건과 #609의 fixture 기반 회귀가 통과한다. 토큰 권한 변경의 런타임 반영과 직접 비교 Grant의 별도 게시 상태를 혼동하지 않아야 한다.
|
||||
30
docs/design/dds-protection-connection-review/609-qa.md
Normal file
30
docs/design/dds-protection-connection-review/609-qa.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# #609 QA 보완 기준 — 증거 기반 회귀 검증
|
||||
|
||||
## 목적
|
||||
|
||||
검색 0건을 권한 차단으로 오판하지 않고 DDS 보호 연결 상태를 검증한다.
|
||||
|
||||
## 필수 fixture
|
||||
|
||||
| fixture | 확인할 사실 |
|
||||
|---|---|
|
||||
| 허용 TAG 문서 | 권한 있는 주체가 결정적으로 1건 이상 본다. |
|
||||
| 거부 TAG 문서 | 같은 조건에서 노출되지 않는다. |
|
||||
| 경계 TAG 문서 | 다중 TAG OR, DENY 우선, 역할 상속이 맞다. |
|
||||
| 권한 없음 주체 | default deny가 안전한 제품 오류 분류로 보인다. |
|
||||
|
||||
fixture manifest/version과 control query를 버전 관리한다.
|
||||
|
||||
## 검증 매트릭스
|
||||
|
||||
- 토큰 서비스와 END USER 직접 비교 각각
|
||||
- `OBSERVED/PARTIAL/UNAVAILABLE`, `MATCHED/NOT_PUBLISHED/DRIFTED/UNKNOWN`, `PASSED/FAILED/STALE/NOT_RUN` 및 대표 상태 우선순위
|
||||
- dictionary 권한 부족, timeout, 빈 inventory, 실제 Grant 없음, publish 중
|
||||
- 권한 변경 뒤 토큰 런타임 반영과 직접 비교의 게시 필요 여부
|
||||
- connection pool 또는 재사용 세션에서 Context가 다음 요청에 잔류하지 않음
|
||||
- credential/token/raw predicate/차단 TAG/DB 오류 원문 비노출
|
||||
- 기본/고급 탭 분리, keyboard/screen reader, 390px, 200% 확대, canonical/legacy URL redirect
|
||||
|
||||
## 증거와 완료 기준
|
||||
|
||||
실행마다 fixture version, 명세 hash, 경로, 주체 유형, control query, 반환/제외 수, 시각, correlation ID를 남긴다. 결과 0건만으로 보호 성공/실패를 판정하는 테스트가 없어야 하며, `PARTIAL`/`UNAVAILABLE`이 정상으로 표시되면 실패다.
|
||||
176
docs/design/dds-protection-connection-review/README.md
Normal file
176
docs/design/dds-protection-connection-review/README.md
Normal file
@@ -0,0 +1,176 @@
|
||||
# DDS 보호 연결 페이지 상세 리뷰
|
||||
|
||||
> **추적성** — Redmine: #581 · 하위 일감: 설계/UX/개발/QA
|
||||
>
|
||||
> **대상** — `/vpd-policies` (사용자 표면명: DDS 보호 연결)
|
||||
>
|
||||
> **목적** — 구현 전, 보호 객체 상태와 DDS 집행 경계를 운영자가 판단할 수 있는지 검토한다.
|
||||
|
||||
> **하위 실행 기준** — [#606 설계](606-security-design.md) · [#607 UX](607-ux.md) · [#608 개발](608-implementation.md) · [#609 QA](609-qa.md)
|
||||
|
||||
## 1. 이 페이지에서 사용자가 끝내야 하는 일
|
||||
|
||||
이 페이지의 1차 사용자는 보안 설계자와 권한 운영자다. 사용자는 다음 네 질문에 답을 얻고 화면을 떠나야 한다.
|
||||
|
||||
1. 어떤 보호 객체가 실제로 DDS에 의해 보호되고 있는가?
|
||||
2. 그 객체에 토큰 기반 서비스 경로와 DDS END USER 직접 비교 경로 중 어느 집행 방식이 선언됐는가? 한 객체가 두 경로를 함께 가질 수 있는가?
|
||||
3. 토큰 경로의 런타임 권한 기준과 직접 비교 경로의 마지막 게시 스냅샷은 각각 최신이며, 최근 검증까지 통과했는가?
|
||||
4. 정상·미게시·드리프트·실패 중 어떤 조치를 해야 하는가?
|
||||
|
||||
현재 화면은 공통 권한, 보호 객체, 역할 규칙, 검증 사용자 설명을 모두 보여 주지만 위 질문에 직접 답하지 못한다.
|
||||
|
||||
## 2. 현재 화면에서 확인된 증거
|
||||
|
||||
| 관찰 | 사용자 영향 |
|
||||
|---|---|
|
||||
| URL은 `/vpd-policies`, 제목은 DDS 보호 연결 | DDS 제품 화면인데 VPD 구현 잔재가 드러나 신뢰를 떨어뜨린다. |
|
||||
| 토큰 Context와 DDS END USER를 한 흐름으로 표현 | 두 identity model이 같은 의미로 읽힌다. |
|
||||
| `dds_demo_both/none/pg/my` 4개 카드가 기본 표면에 있음 | 데모 비교 프로필이 제품 운영 계정처럼 보인다. |
|
||||
| `연결 설정됨`이라는 상태 | DB 연결, 비밀번호 설정, Grant 적용, 검증 통과 중 무엇인지 모호하다. |
|
||||
| 역할·규칙 표가 중심 | 실제 DATA GRANT의 존재, 최신성, 검증 결과를 알 수 없다. |
|
||||
| 상태/차이/게시 이력이 없음 | 운영자가 다음 행동을 고를 수 없다. |
|
||||
|
||||
## 3. 반드시 분리할 두 경로와 게시 경계
|
||||
|
||||
| 구분 | 토큰 기반 서비스 경로 | DDS END USER 직접 비교 |
|
||||
|---|---|---|
|
||||
| 목적 | 제품의 권한 기반 지식 검색 | DDS 선언형 identity/Data Role 동작 검증 |
|
||||
| identity | 기술 사용자 세션 + Bearer로 만든 애플리케이션 Context | DDS `END USER` |
|
||||
| 집행 | 기술 사용자에 연결된 객체별 `DATA GRANT` predicate가 `CB_AGENT_CTX`와 공통 권한 테이블을 요청 시 평가 | `END USER → DATA ROLE → DATA GRANT` |
|
||||
| 권한 변경 반영 | 토큰 해석 뒤 predicate가 공통 권한을 읽으므로, 공통 권한 변경은 다음 요청에 반영된다. 단, Grant/함수/객체 선언 자체는 별도 게시·관찰 대상이다. | 유효 권한을 사용자별 predicate로 컴파일한 뒤 별도 게시한다. 공통 권한 변경만으로 기존 Grant는 갱신되지 않는다. |
|
||||
| 기본 노출 | 기본 화면 | 고급 검증 |
|
||||
| 주 사용 빈도 | 일상 운영 | 정책/장애 조사 |
|
||||
|
||||
Bearer 값을 DB 변수나 사용자명으로 매핑하는 것만으로 순수 DDS END USER Context가 만들어지지는 않는다. 그래서 기본 서비스 경로와 직접 비교 경로는 같은 탭, 같은 카드, 같은 용어로 다루면 안 된다.
|
||||
|
||||
이 표는 **보호 객체가 둘 중 하나만 선택해야 한다**는 뜻이 아니다. 같은 VIEW에 토큰용 `DATA GRANT`와 직접 비교용 `DATA GRANT`가 함께 있을 수 있다. 상태·검증 이력·조치도 객체 단위만이 아니라 `보호 객체 × 집행 경로` 단위로 보관한다.
|
||||
|
||||
## 4. 보호 객체 상태 계약
|
||||
|
||||
`정상/드리프트/검증 실패`를 서로 배타적인 하나의 값으로 두면 운영 사실이 사라진다. 예를 들어 Grant가 드리프트이면서 마지막 검증도 실패할 수 있다. 따라서 서버는 아래 세 축을 독립적으로 반환하고, UI는 대표 상태와 근거 배지를 함께 보여 준다.
|
||||
|
||||
| 상태 축 | 값 | 판정 근거 | 대표 조치 |
|
||||
|---|---|---|---|
|
||||
| 관측 가능성 | `OBSERVED` / `PARTIAL` / `UNAVAILABLE` | DDS catalog·권한 원천·검증 이력 중 필요한 원천을 모두 읽었는지 | 원천 연결/권한 점검 |
|
||||
| 선언 동기화 | `MATCHED` / `NOT_PUBLISHED` / `DRIFTED` / `UNKNOWN` | 기대 명세와 실제 객체·Role·컬럼 범위·비교 가능한 predicate 표현의 차이 | 미리보기·재게시·차이 조사 |
|
||||
| 실행 검증 | `PASSED` / `FAILED` / `STALE` / `NOT_RUN` / `UNKNOWN` | 결정적 허용/거부/경계 시나리오가 현재 명세 이후에 통과했는지 | 검증 실행·접근 근거 조사 |
|
||||
|
||||
대표 상태는 다음 우선순위로 정한다. 다만 보조 축의 값을 숨기지 않는다.
|
||||
|
||||
1. `확인 불가` — 관측 가능성이 `UNAVAILABLE`이거나, 정상 판정에 필요한 비교 원천이 없다.
|
||||
2. `조치 필요` — `NOT_PUBLISHED`, `DRIFTED`, 또는 `FAILED`가 하나 이상 있다. 원인을 배지로 병기한다.
|
||||
3. `재검증 필요` — 선언은 `MATCHED`지만 검증이 `STALE` 또는 `NOT_RUN`이다.
|
||||
4. `보호 확인` — `OBSERVED`, `MATCHED`, `PASSED`가 모두 충족되고, 검증 시각이 마지막 관련 변경보다 뒤다.
|
||||
|
||||
`PARTIAL` 관측은 상세에 부족한 비교 필드를 표시하되, `보호 확인`으로 승격할 수 없다. 역할·권한 규칙의 건수도 보조 지표일 뿐, 이 계약의 정상 판정 근거가 아니다.
|
||||
|
||||
## 5. 기대값·실제값·검증 증거 데이터 계약
|
||||
|
||||
| 데이터 | 최소 필드 | 권위 있는 원천 | 용도 |
|
||||
|---|---|---|---|
|
||||
| 기대 명세 | `protectionObjectId`, 집행 경로, Data Role/기술 사용자, 객체·컬럼 범위, predicate의 canonical hash, 공통 권한/함수/객체 버전, 작성 시각 | **승인된 게시 계획의 immutable snapshot**. 현재 권한 화면의 즉시 조회값이 아니다. | 실제와의 비교, 검증 대상 고정 |
|
||||
| 실제 관측 | Grant 이름, 객체, grantee, 컬럼 범위, 비교 가능한 선언 fingerprint, catalog 조회 시각·권한 범위 | DDS catalog adapter. 현재 `DBA_DATA_GRANTS` 조회만으로 확인할 수 있는 필드와 별도 비교 원천을 명시한다. | 동기화·관측 가능성 판정 |
|
||||
| 게시 이력 | 계획 ID, 명세 version/hash, 실행자, 사유·승인 참조, 전후 version, DB 결과, rollback 참조 | append-only 게시 감사 기록 | 변경 추적/복구 |
|
||||
| 검증 증거 | fixture ID, 경로, 주체 유형, 명세 version/hash, query/control query, 허용·거부·경계 결과, 반환/제외 수, 시각, 안전한 실패 분류, correlation ID | 검증 실행 기록 | 실행 검증·최근성 판정 |
|
||||
|
||||
canonical hash는 비교에 의미 없는 공백·대소문자·정렬 차이로 드리프트가 발생하지 않게 정규화한 명세에서 계산한다. 다만 정규화 규칙의 변경도 schema version으로 기록한다. raw predicate를 모든 운영자에게 보여 주거나 browser에 저장해서는 안 된다.
|
||||
|
||||
`DBA_DATA_GRANTS`는 현재 SQL에서 Grant 이름·객체·grantee를 확인하는 용도로 쓰인다. 이 정보만으로 predicate 또는 컬럼 범위까지 동일하다고 판정할 수 없다. 해당 Database release에서 허용된 추가 catalog/DDL fingerprint 조회가 없으면 관측 상태는 `PARTIAL`이어야 하며, `MATCHED`나 `보호 확인`으로 표시하지 않는다.
|
||||
|
||||
상태 계산이 실패했을 때 0건이나 정상으로 폴백하면 안 된다. `UNAVAILABLE`은 연결 오류, dictionary 권한 부족, timeout을 구분한 안전한 오류 분류와 마지막 정상 관측 시각을 동반한다.
|
||||
|
||||
## 6. 제안 정보 구조
|
||||
|
||||
### 기본: 토큰 기반 서비스 경로
|
||||
|
||||
상단에는 전체 상태와 추천 행동 하나를 둔다. 예시는 `1개 보호 객체 반영 필요 → 변경 미리보기`다.
|
||||
|
||||
그 아래 객체 표는 **토큰 기반 서비스 경로**의 현재 보호 상태만 기본으로 보여 준다. 행을 열면 객체 상세에서 다른 집행 경로와 기술 근거를 볼 수 있다.
|
||||
|
||||
| 보호 객체 | 대표 상태 | 동기화/검증 근거 | 마지막 관측 | 마지막 검증 | 조치 |
|
||||
|---|---|---|---|---|---|
|
||||
|
||||
대표 상태는 `보호 확인`, `조치 필요`, `재검증 필요`, `확인 불가` 중 하나이며, `드리프트`, `검증 실패`, `관측 일부` 같은 근거 배지를 병기한다. 기본 표면에는 기술 SQL, raw predicate, dictionary 원문, 비밀·원문 데이터를 두지 않는다.
|
||||
|
||||
객체가 0개면 보호 범위가 아직 정의되지 않았다는 빈 상태와 `보호 객체 등록` 경로를 보인다. 다수 객체에서는 대표 상태, 이름, 마지막 관측/검증, 집행 경로로 필터·정렬하고, 게시 중인 객체에는 stale 데이터를 정상으로 보이지 않게 `반영 중` 처리를 한다.
|
||||
|
||||
### 고급: DDS END USER 직접 비교
|
||||
|
||||
`dds_demo_*` 프로필은 이 영역에서만 보여 준다. 표준 명칭은 **직접 비교용 검증 프로필**이다. `연결 설정됨` 대신 아래를 분리한다.
|
||||
|
||||
- 자격증명 설정 여부
|
||||
- 직접 연결 가능 여부
|
||||
- Data Role 매핑 여부
|
||||
- 마지막 직접 비교 결과
|
||||
|
||||
직접 비교 탭은 기본 화면과 URL을 분리하거나, 최소한 명시적인 `고급 검증` 진입과 탭 역할·선택 상태를 제공한다. 데모 프로필은 제품 계정처럼 보이지 않게 fixture 목적과 만료/비활성 상태를 함께 표시한다.
|
||||
|
||||
### 객체 상세과 조치 권한
|
||||
|
||||
| 정보 또는 조치 | 일반 보호 상태 조회자 | 진단 권한자 | 게시 권한자 |
|
||||
|---|---|---|---|
|
||||
| 대표 상태·안전한 사유·마지막 관측 | 가능 | 가능 | 가능 |
|
||||
| 객체명·Role·안전한 diff 요약 | 정책상 허용된 범위만 | 가능 | 가능 |
|
||||
| raw predicate·catalog 원문·진단 로그 상관관계 | 불가 | 승인된 별도 화면/로그 | 필요 시 별도 감사 |
|
||||
| 미리보기 | 불가 또는 요약만 | 가능 | 가능 |
|
||||
| 게시·회수 | 불가 | 불가 | 사유·영향 확인·승인/감사 후 가능 |
|
||||
|
||||
위 권한명은 제품에서 구현할 개념적 capability다. 기존 관리자 로그인 권한과 동일하다고 가정하지 않으며, 실제 role mapping은 권한 설계에서 별도로 결정한다.
|
||||
|
||||
## 7. 오류와 누설 방지
|
||||
|
||||
- 일반 운영 화면에는 권한이 없는 객체의 이름, 금지된 TAG, 원문, predicate 상세를 오류 원인으로 노출하지 않는다.
|
||||
- 토큰 경로에서 Grant 정보가 부족하더라도 `권한을 확인할 수 없음`처럼 안전한 상태와 운영자 조치만 표시한다.
|
||||
- 직접 비교 프로필의 비밀번호 누락은 서비스 경로의 보호 실패가 아니라 고급 검증 구성 문제로 분리한다.
|
||||
- inventory 조회 실패는 반드시 조회 시각과 오류 범위를 표시하고, 정상 상태 배지로 덮지 않는다. browser에는 DB 오류 원문 대신 correlation ID와 안전한 조치만 표시한다.
|
||||
- Bearer 원문, END USER 비밀번호, 원문 청크, 차단된 TAG, 전체 predicate는 화면·감사 메모·URL query string에 기록하지 않는다.
|
||||
- 토큰 Context는 요청 단위로 설정·해제되고, connection pool을 도입할 경우 다음 요청에 Context가 잔류하지 않는 검증을 필수로 한다.
|
||||
|
||||
## 8. 검증 증거와 0건 해석
|
||||
|
||||
벡터 검색의 0건은 권한 차단, 관련 문서 부재, embedding 차이, 상위 N 제한 중 무엇 때문인지 알 수 없다. 따라서 `반환 0건`만으로 `검증 실패` 또는 `차단 성공`을 판정하면 안 된다.
|
||||
|
||||
각 집행 경로에는 다음처럼 결정적인 fixture를 둔다.
|
||||
|
||||
| fixture | 목적 | 검증 방식 |
|
||||
|---|---|---|
|
||||
| 허용 TAG 문서 | 올바른 주체가 최소 1건을 볼 수 있음 | fixture 문서 ID를 포함하는 control query 또는 객체 조회를 사용해 1건 이상 반환 확인 |
|
||||
| 거부 TAG 문서 | 허용되지 않은 자료가 노출되지 않음 | 같은 조건에서 fixture ID가 결과에 없는지 확인 |
|
||||
| 경계 TAG 문서 | 다중 TAG OR, DENY 우선, 역할 상속의 경계를 검증 | 기대 결과 집합을 fixture manifest와 비교 |
|
||||
| 권한 없음 주체 | default deny가 예상대로 동작 | 객체 미노출/권한 오류를 제품이 정한 안전한 분류로 확인 |
|
||||
|
||||
검증 이력에는 검색 결과만이 아니라 fixture manifest version, 명세 version/hash, query/control query, 반환 수·제외 수, 주체 유형, 시각, 실행 경로를 저장한다. 검색 관련도 자체를 검증하는 시나리오와 권한 집행을 검증하는 시나리오는 분리한다.
|
||||
|
||||
## 9. URL·용어 이전 기준
|
||||
|
||||
사용자 표면에서 VPD 잔재를 없애려면 제목만 바꾸면 안 된다. DDS canonical route를 먼저 정하고 메뉴·공유 링크·breadcrumb·감사 표기에 같은 명칭을 쓴다. 기존 `/vpd-policies`는 호환 기간 동안 DDS canonical route로 redirect하되, redirect 기간·북마크 영향·운영 로그의 옛 경로 표기 방식을 릴리스 결정으로 남긴다.
|
||||
|
||||
## 10. 페르소나 최종 판정
|
||||
|
||||
| 페르소나 | 최종 요구 |
|
||||
|---|---|
|
||||
| UI/UX 전문가 | 설명을 줄이는 것만으로 부족하다. 객체 상태와 다음 행동을 최상단으로 올려야 한다. |
|
||||
| 보안 설계자 | 두 identity model과 두 반영 방식을 분리하고, immutable 기대 명세·실제 관측·검증 증거를 비교해야 한다. |
|
||||
| 운영 사용자 | 데모 계정이 아니라 객체×집행 경로별 보호 상태, 관측 신선도, 마지막 게시·검증, 즉시 실행할 조치를 원한다. |
|
||||
|
||||
## 11. 구현 착수 게이트와 완료 조건
|
||||
|
||||
다음 항목이 #606 설계와 #607 UX에서 승인되기 전에는 #608 화면 구현을 시작하지 않는다.
|
||||
|
||||
1. 상태 세 축·대표 상태 우선순위·`PARTIAL`의 승격 금지 규칙을 API contract와 fixture로 확정한다.
|
||||
2. 토큰 경로와 직접 비교 경로 각각의 immutable 기대 명세, canonical hash, catalog adapter 범위, 게시/권한 변경의 최신성 규칙을 확정한다.
|
||||
3. 실제 catalog가 비교하지 못하는 필드는 `PARTIAL`로 표기하는 방식을 확정한다.
|
||||
4. 허용·거부·경계·권한 없음 fixture와 control query, 검증 증거 보존 기간을 확정한다.
|
||||
5. 대표 상태/근거 배지/다객체·빈·반영 중/오류 상태와 권한별 상세 노출을 와이어프레임으로 확정한다.
|
||||
6. DDS canonical route와 기존 `/vpd-policies`의 redirect·호환 방식을 릴리스 계획에 기록한다.
|
||||
|
||||
구현 완료 조건은 다음과 같다.
|
||||
|
||||
1. 기본 화면에는 `dds_demo_*` 카드가 없고, 토큰 기반 서비스 경로의 객체 상태와 추천 조치만 보인다.
|
||||
2. 동일 객체의 토큰/직접 비교 상태는 분리해 추적할 수 있으며, 한 객체가 두 경로를 함께 지원해도 혼동되지 않는다.
|
||||
3. 대표 상태는 관측·동기화·검증의 근거 배지와 마지막 관측 시각을 동반하며, 불완전 관측을 정상으로 표시하지 않는다.
|
||||
4. 기대/실제 선언의 차이는 raw predicate를 불필요하게 노출하지 않고 사용자 영향과 함께 확인할 수 있다.
|
||||
5. 0건 검색을 권한 차단으로 단정하지 않으며, fixture 기반 허용·거부·경계 검증이 자동화되어 있다.
|
||||
6. 토큰·비밀번호·차단 TAG·원문·DB 오류 원문이 브라우저와 감사 메모에 누설되지 않는다.
|
||||
7. 사용자 표면에서 VPD route/용어가 노출되지 않고, 기존 URL은 정의된 호환 정책에 따라 처리된다.
|
||||
8. 상태 계산, catalog 오류, drift, 검증 freshness, Context 잔류, 기본/고급 탭 분리에 대한 unit·integration·UI 회귀 검증이 있다.
|
||||
@@ -31,6 +31,11 @@ public class SettingController {
|
||||
model.addAttribute("runtimeErrorTitle", error.title());
|
||||
model.addAttribute("runtimeErrorMessage", error.message());
|
||||
}
|
||||
return "settings";
|
||||
}
|
||||
|
||||
@GetMapping("/settings/database")
|
||||
public String databaseSettings(Model model) {
|
||||
try {
|
||||
model.addAttribute("schemaPreflight", backofficeSchemaService.preflight());
|
||||
} catch (DataAccessException exception) {
|
||||
@@ -38,7 +43,7 @@ public class SettingController {
|
||||
model.addAttribute("schemaPreflightErrorTitle", error.title());
|
||||
model.addAttribute("schemaPreflightErrorMessage", error.message());
|
||||
}
|
||||
return "settings";
|
||||
return "settings-database";
|
||||
}
|
||||
|
||||
@PostMapping("/settings/ords")
|
||||
@@ -51,8 +56,16 @@ public class SettingController {
|
||||
return "redirect:/settings";
|
||||
}
|
||||
|
||||
@PostMapping("/settings/schema/initialize")
|
||||
public String initializeSchema(RedirectAttributes redirectAttributes) {
|
||||
@PostMapping("/settings/database/initialize")
|
||||
public String initializeSchema(
|
||||
@RequestParam String confirmation,
|
||||
RedirectAttributes redirectAttributes
|
||||
) {
|
||||
if (!"DB 준비 실행".equals(confirmation)) {
|
||||
redirectAttributes.addFlashAttribute(
|
||||
"error", "DB 지원 DDL을 실행하려면 확인 문구 ‘DB 준비 실행’을 정확히 입력하세요.");
|
||||
return "redirect:/settings/database";
|
||||
}
|
||||
var results = backofficeSchemaService.initializeSchema();
|
||||
long failures = results.stream()
|
||||
.filter(result -> "FAILED".equalsIgnoreCase(result.status()))
|
||||
@@ -63,6 +76,6 @@ public class SettingController {
|
||||
} else {
|
||||
redirectAttributes.addFlashAttribute("message", "백오피스 지원 테이블 DDL을 실행했습니다.");
|
||||
}
|
||||
return "redirect:/settings";
|
||||
return "redirect:/settings/database";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,6 +358,57 @@ body {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.back-link {
|
||||
color: var(--rw-primary);
|
||||
display: inline-block;
|
||||
font-size: .86rem;
|
||||
font-weight: 800;
|
||||
margin-bottom: .6rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.settings-database-link {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.settings-database-link h2,
|
||||
.settings-danger-zone h2 {
|
||||
font-size: 1rem;
|
||||
font-weight: 800;
|
||||
margin: .3rem 0;
|
||||
}
|
||||
|
||||
.settings-database-link p {
|
||||
color: var(--rw-muted);
|
||||
margin: 0;
|
||||
max-width: 52rem;
|
||||
}
|
||||
|
||||
.settings-danger-zone {
|
||||
background: #fff8e8;
|
||||
border-color: #e7d29c;
|
||||
}
|
||||
|
||||
.settings-impact-list {
|
||||
color: var(--rw-muted);
|
||||
margin: .75rem 0 1rem;
|
||||
padding-left: 1.25rem;
|
||||
}
|
||||
|
||||
.settings-impact-list li + li {
|
||||
margin-top: .3rem;
|
||||
}
|
||||
|
||||
.settings-confirm-form {
|
||||
align-items: end;
|
||||
grid-template-columns: minmax(220px, 1fr) auto;
|
||||
max-width: 46rem;
|
||||
}
|
||||
|
||||
.content-band h2,
|
||||
.section-heading h2 {
|
||||
font-size: 1rem;
|
||||
@@ -1587,6 +1638,10 @@ body {
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.settings-confirm-form {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.effective-tabs {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
104
src/main/resources/templates/settings-database.html
Normal file
104
src/main/resources/templates/settings-database.html
Normal file
@@ -0,0 +1,104 @@
|
||||
<!doctype html>
|
||||
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
|
||||
<head th:replace="~{fragments/layout :: head('DB 준비 상태')}"></head>
|
||||
<body>
|
||||
<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>
|
||||
<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>
|
||||
</details>
|
||||
</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-warning" th:if="${schemaPreflightErrorMessage}">
|
||||
<strong th:text="${schemaPreflightErrorTitle}">DB 준비 상태 확인 불가</strong>
|
||||
<div th:text="${schemaPreflightErrorMessage}">DB 연결을 확인하세요.</div>
|
||||
</div>
|
||||
|
||||
<section class="content-band" th:if="${schemaPreflight}">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<span class="architecture-kicker">읽기 전용 점검</span>
|
||||
<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>
|
||||
</div>
|
||||
<div class="schema-summary">
|
||||
<div><span>현재 연결 사용자</span><strong th:text="${schemaPreflight.currentUser()}">ADMIN</strong></div>
|
||||
<div><span>권장 ORDS schema</span><strong th:text="${schemaPreflight.recommendedOrdsSchema()}">CB_ORDS</strong></div>
|
||||
<div><span>OK</span><strong th:text="${schemaPreflight.okCount()}">0</strong></div>
|
||||
<div><span>WARN</span><strong th:text="${schemaPreflight.warnCount()}">0</strong></div>
|
||||
<div><span>MISSING</span><strong th:text="${schemaPreflight.missingCount()}">0</strong></div>
|
||||
<div><span>ERROR</span><strong th:text="${schemaPreflight.errorCount()}">0</strong></div>
|
||||
</div>
|
||||
<div class="table-responsive mt-3">
|
||||
<table class="table table-sm align-middle schema-preflight-table">
|
||||
<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><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>
|
||||
<td th:text="${check.detail()}">존재합니다.</td>
|
||||
<td><span class="badge" th:classappend="${check.sqlclRequired()} ? ' text-bg-warning' : ' text-bg-info'" th:text="${check.sqlclRequired()} ? 'sqlcl 필요' : '앱 실행 가능'">앱 실행 가능</span><div class="mt-1" th:text="${check.nextAction()}">조치 없음</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="content-band" th:if="${schemaPreflight}">
|
||||
<h2>실행 전 SQL 확인</h2>
|
||||
<div class="schema-preview-grid mt-3">
|
||||
<details><summary>앱에서 실행하는 지원 DDL</summary><pre class="schema-sql-preview" th:text="${schemaPreflight.appDdlPreview()}">CREATE TABLE ...</pre></details>
|
||||
<details><summary>sqlcl로 실행할 VPD/ORDS runtime 순서</summary><pre class="schema-sql-preview" th:text="${schemaPreflight.sqlclScript()}">@sql/adb/...</pre></details>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="content-band settings-danger-zone">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<span class="architecture-kicker">영향 있는 작업</span>
|
||||
<h2>지원 테이블 생성/확인</h2>
|
||||
<p class="section-subtitle">현재 연결 사용자에 백오피스 지원 테이블, 시퀀스, 컬럼을 생성하거나 보강합니다. VPD·ORDS runtime은 실행하지 않습니다.</p>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="settings-impact-list">
|
||||
<li>실행 대상과 SQL은 위 preview에서 먼저 확인합니다.</li>
|
||||
<li>운영 데이터 백업과 DB 권한을 확인한 뒤 실행합니다.</li>
|
||||
<li>실행 결과는 이 화면에 남고, 실패한 SQL을 개별 확인할 수 있습니다.</li>
|
||||
</ul>
|
||||
<form method="post" action="/settings/database/initialize" class="form-grid settings-confirm-form">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||
<label>
|
||||
확인 문구 입력
|
||||
<input class="form-control" name="confirmation" placeholder="DB 준비 실행" required autocomplete="off">
|
||||
<span class="form-hint">실행하려면 <code>DB 준비 실행</code>을 정확히 입력하세요.</span>
|
||||
</label>
|
||||
<button class="btn btn-outline-danger" type="submit">지원 DDL 실행</button>
|
||||
</form>
|
||||
<div class="table-responsive mt-3" th:if="${schemaResults}">
|
||||
<table class="table table-sm align-middle">
|
||||
<thead><tr><th>Object</th><th>Action</th><th>Status</th><th>Message</th><th>SQL</th></tr></thead>
|
||||
<tbody>
|
||||
<tr th:each="result : ${schemaResults}">
|
||||
<td><code th:text="${result.objectName()}">object</code></td><td th:text="${result.action()}">TABLE</td>
|
||||
<td><span class="badge" th:classappend="${result.status() == 'CREATED' || result.status() == 'MERGED' || result.status() == 'UPDATED'} ? ' text-bg-success' : (${result.status() == 'EXISTS'} ? ' text-bg-secondary' : ' text-bg-danger')" th:text="${result.status()}">CREATED</span></td>
|
||||
<td th:text="${result.message()}">생성했습니다.</td>
|
||||
<td><details th:if="${result.sqlText()}"><summary>보기</summary><pre class="table-pre" th:text="${result.sqlText()}">CREATE TABLE ...</pre></details><span class="text-muted" th:unless="${result.sqlText()}">-</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,15 +1,15 @@
|
||||
<!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">
|
||||
<main class="container py-4 settings-page">
|
||||
<div class="page-title">
|
||||
<h1>설정</h1>
|
||||
<p class="context-summary">ORDS 연결값과 백오피스 DB 준비 상태를 확인합니다.</p>
|
||||
<h1>연결 설정</h1>
|
||||
<p class="context-summary">백오피스가 ORDS를 호출할 주소를 관리합니다. DB 지원 DDL은 별도 안전 화면에서 실행합니다.</p>
|
||||
<details class="explanation-details">
|
||||
<summary>설정 화면의 범위 보기</summary>
|
||||
<p>백오피스에서 사용하는 외부 연동 값을 관리합니다. DB 준비 상태에서는 메타데이터, VPD runtime, ORDS runtime, grant를 연결 사용자 기준으로 점검합니다.</p>
|
||||
<summary>이 화면에서 바꾸는 범위 보기</summary>
|
||||
<p>여기서는 ORDS Base URL만 저장합니다. VPD runtime, grant, 지원 테이블 생성처럼 DB에 영향을 주는 작업은 <strong>DB 준비 상태</strong>에서 별도로 확인·실행합니다.</p>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
@@ -19,151 +19,32 @@
|
||||
<strong th:text="${runtimeErrorTitle}">DB 준비 필요</strong>
|
||||
<div th:text="${runtimeErrorMessage}">지원 테이블을 생성하세요.</div>
|
||||
</div>
|
||||
<div class="alert alert-warning" th:if="${schemaPreflightErrorMessage}">
|
||||
<strong th:text="${schemaPreflightErrorTitle}">DB 준비 상태 확인 불가</strong>
|
||||
<div th:text="${schemaPreflightErrorMessage}">DB 연결을 확인하세요.</div>
|
||||
</div>
|
||||
|
||||
<section class="content-band">
|
||||
<h2>ORDS</h2>
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<span class="architecture-kicker">ORDS 연결</span>
|
||||
<h2>ORDS Base URL</h2>
|
||||
<p class="section-subtitle">저장 후 권한 결과 확인과 연동 도구가 이 주소를 사용합니다.</p>
|
||||
</div>
|
||||
</div>
|
||||
<form method="post" action="/settings/ords" class="form-grid">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||
<label class="span-2">
|
||||
Base URL
|
||||
<input class="form-control" name="ordsBaseUrl" th:value="${ordsBaseUrl}" required>
|
||||
<input class="form-control" name="ordsBaseUrl" th:value="${ordsBaseUrl}" required inputmode="url" autocomplete="url">
|
||||
</label>
|
||||
<button class="btn rw-btn-primary" type="submit">저장</button>
|
||||
<button class="btn rw-btn-primary" type="submit">연결 주소 저장</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="content-band mt-3" th:if="${schemaPreflight}">
|
||||
<div class="section-heading">
|
||||
<section class="content-band settings-database-link">
|
||||
<div>
|
||||
<h2>DB 준비 상태</h2>
|
||||
<p>현재 연결 사용자 기준으로 백오피스 메타데이터, VPD runtime, ORDS runtime, grant 상태를 점검합니다.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="schema-summary">
|
||||
<div>
|
||||
<span>현재 연결 사용자</span>
|
||||
<strong th:text="${schemaPreflight.currentUser()}">ADMIN</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>권장 ORDS parsing schema</span>
|
||||
<strong th:text="${schemaPreflight.recommendedOrdsSchema()}">CB_ORDS</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>OK</span>
|
||||
<strong th:text="${schemaPreflight.okCount()}">0</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>WARN</span>
|
||||
<strong th:text="${schemaPreflight.warnCount()}">0</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>MISSING</span>
|
||||
<strong th:text="${schemaPreflight.missingCount()}">0</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>ERROR</span>
|
||||
<strong th:text="${schemaPreflight.errorCount()}">0</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-info mt-3 mb-3" th:if="${schemaPreflight.hasActionNeeded()}">
|
||||
앱에서 실행 가능한 항목은 아래 `지원 테이블 생성/확인` 버튼으로 보강하고, `sqlcl 필요` 항목은 preview 순서대로 DB 권한을 가진 세션에서 실행하세요.
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle schema-preflight-table">
|
||||
<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><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>
|
||||
<td th:text="${check.detail()}">존재합니다.</td>
|
||||
<td>
|
||||
<span class="badge" th:classappend="${check.sqlclRequired()} ? ' text-bg-warning' : ' text-bg-info'"
|
||||
th:text="${check.sqlclRequired()} ? 'sqlcl 필요' : '앱 실행 가능'">앱 실행 가능</span>
|
||||
<div class="mt-1" th:text="${check.nextAction()}">조치 없음</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="content-band mt-3" th:if="${schemaPreflight}">
|
||||
<h2>SQL Preview</h2>
|
||||
<p class="text-muted mb-3">
|
||||
앱 버튼이 실행할 SQL과 sqlcl로 실행할 runtime 준비 순서를 구분해서 확인합니다.
|
||||
</p>
|
||||
<div class="schema-preview-grid">
|
||||
<details open>
|
||||
<summary>앱에서 실행하는 지원 DDL</summary>
|
||||
<pre class="schema-sql-preview" th:text="${schemaPreflight.appDdlPreview()}">CREATE TABLE ...</pre>
|
||||
</details>
|
||||
<details>
|
||||
<summary>sqlcl로 실행할 VPD/ORDS runtime 순서</summary>
|
||||
<pre class="schema-sql-preview" th:text="${schemaPreflight.sqlclScript()}">@sql/adb/...</pre>
|
||||
</details>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="content-band mt-3">
|
||||
<h2>DB 스키마 초기화</h2>
|
||||
<p class="text-muted mb-3">
|
||||
현재 연결된 DB 사용자에 백오피스 지원 테이블, 시퀀스, 컬럼을 생성하거나 보강합니다. VPD context, ORDS enablement, grant는 sqlcl preview 순서를 사용하세요.
|
||||
</p>
|
||||
<form method="post" action="/settings/schema/initialize" class="inline-form">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||
<button class="btn btn-outline-primary" type="submit">지원 테이블 생성/확인</button>
|
||||
</form>
|
||||
<div class="table-responsive mt-3" th:if="${schemaResults}">
|
||||
<table class="table table-sm align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Object</th>
|
||||
<th>Action</th>
|
||||
<th>Status</th>
|
||||
<th>Message</th>
|
||||
<th>SQL</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="result : ${schemaResults}">
|
||||
<td><code th:text="${result.objectName()}">object</code></td>
|
||||
<td th:text="${result.action()}">TABLE</td>
|
||||
<td>
|
||||
<span class="badge"
|
||||
th:classappend="${result.status() == 'CREATED' || result.status() == 'MERGED' || result.status() == 'UPDATED'} ? ' text-bg-success' : (${result.status() == 'EXISTS'} ? ' text-bg-secondary' : ' text-bg-danger')"
|
||||
th:text="${result.status()}">CREATED</span>
|
||||
</td>
|
||||
<td th:text="${result.message()}">생성했습니다.</td>
|
||||
<td>
|
||||
<details th:if="${result.sqlText()}">
|
||||
<summary>보기</summary>
|
||||
<pre class="table-pre" th:text="${result.sqlText()}">CREATE TABLE ...</pre>
|
||||
</details>
|
||||
<span class="text-muted" th:unless="${result.sqlText()}">-</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<span class="architecture-kicker">DB 준비 · 별도 화면</span>
|
||||
<h2>지원 테이블과 VPD runtime 상태를 확인해야 하나요?</h2>
|
||||
<p>현재 연결 사용자, 메타데이터, VPD runtime, ORDS runtime, grant 상태를 점검합니다. 지원 DDL 실행은 확인 문구 입력이 필요합니다.</p>
|
||||
</div>
|
||||
<a class="btn rw-btn-secondary" href="/settings/database">DB 준비 상태 열기</a>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
@@ -113,6 +113,23 @@ class GuidedFlowTemplateTest {
|
||||
.contains("기술 태그 비교 예시");
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectionSettingsAreSeparatedFromDatabasePreparation() throws IOException {
|
||||
String settings = template("settings.html");
|
||||
String database = template("settings-database.html");
|
||||
|
||||
assertThat(settings)
|
||||
.contains("연결 설정")
|
||||
.contains("/settings/database")
|
||||
.doesNotContain("action=\"/settings/database/initialize\"");
|
||||
assertThat(database)
|
||||
.contains("DB 준비 상태")
|
||||
.contains("/settings/database/initialize")
|
||||
.contains("확인 문구 입력")
|
||||
.contains("DB 준비 실행")
|
||||
.doesNotContain("action=\"/settings/schema/initialize\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void effectiveMatrixStartsWithOneUserAndKeepsGroupAndRoleAsSecondaryViews() throws IOException {
|
||||
String html = template("effective-matrix.html");
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.cloudhandson.vpdbackoffice.web;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Locale;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.thymeleaf.context.Context;
|
||||
import org.thymeleaf.spring6.SpringTemplateEngine;
|
||||
import org.thymeleaf.templateresolver.FileTemplateResolver;
|
||||
|
||||
class SettingsTemplateRenderTest {
|
||||
|
||||
@Test
|
||||
void rendersConnectionAndDatabasePreparationPagesSeparately() {
|
||||
var resolver = new FileTemplateResolver();
|
||||
resolver.setPrefix(Path.of("src/main/resources/templates").toAbsolutePath() + "/");
|
||||
resolver.setSuffix(".html");
|
||||
resolver.setTemplateMode("HTML");
|
||||
resolver.setCacheable(false);
|
||||
var engine = new SpringTemplateEngine();
|
||||
engine.setTemplateResolver(resolver);
|
||||
|
||||
var context = new Context(Locale.KOREAN);
|
||||
context.setVariable("_csrf", new CsrfFixture("_csrf", "test-token"));
|
||||
context.setVariable("ordsBaseUrl", "https://ords.example.test/ords");
|
||||
|
||||
String connection = engine.process("settings", context);
|
||||
String database = engine.process("settings-database", context);
|
||||
|
||||
assertThat(connection)
|
||||
.contains("ORDS Base URL")
|
||||
.contains("/settings/database")
|
||||
.doesNotContain("/settings/database/initialize");
|
||||
assertThat(database)
|
||||
.contains("DB 준비 상태")
|
||||
.contains("/settings/database/initialize")
|
||||
.contains("DB 준비 실행");
|
||||
}
|
||||
|
||||
private record CsrfFixture(String parameterName, String token) {
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user