fix #490: align vpd policy apply hierarchy
This commit is contained in:
44
docs/design/490-vpd-policy-apply-hierarchy/README.md
Normal file
44
docs/design/490-vpd-policy-apply-hierarchy/README.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Redmine #490 - VPD Policy/Filter/적용 계층 정리 설계
|
||||||
|
|
||||||
|
## 프로젝트 개요
|
||||||
|
|
||||||
|
VPD Backoffice는 Oracle Database VPD/ORDS 기능을 백오피스 권한 테이블로 제어하기 위한 Spring Boot 관리 도구다. VPD 설정 화면은 Oracle DB의 TABLE/VIEW에 VPD policy를 적용하고, policy는 filter function을 참조한다. ORDS는 VPD가 적용된 TABLE/VIEW를 HTTP API로 서빙하는 별도 레이어다.
|
||||||
|
|
||||||
|
## 목표
|
||||||
|
|
||||||
|
`VPD 설정` 화면에서 개별 적용과 벌크 적용이 같은 계층으로 보이게 정리한다.
|
||||||
|
|
||||||
|
정리할 계층:
|
||||||
|
|
||||||
|
1. `Filter Function`: 행 predicate를 반환하는 PL/SQL 함수
|
||||||
|
2. `Policy Template`: policy name, filter function, statement type, enabled/check option 조합
|
||||||
|
3. `TABLE/VIEW 적용`: 선택한 policy template을 개별 객체 또는 스키마 객체 목록에 적용
|
||||||
|
|
||||||
|
## 문제
|
||||||
|
|
||||||
|
현재 벌크 적용 폼은 `VPD Filter Function`과 `Filter predicate`를 직접 입력받는다. 이 표현은 filter function을 TABLE/VIEW에 직접 붙이는 것처럼 보이고, 개별 적용의 `Policy / Filter` 선택 흐름과도 다르다.
|
||||||
|
|
||||||
|
## 설계
|
||||||
|
|
||||||
|
- 개별 적용과 벌크 적용 모두 `Policy Template` 선택을 중심으로 한다.
|
||||||
|
- 선택한 template에서 `policyName`, `functionKey`, statement type, enabled, update check 값을 hidden field와 checkbox로 동기화한다.
|
||||||
|
- 벌크 적용 backend는 선택한 `policyName`을 명시적으로 받아 각 대상 TABLE/VIEW에 같은 policy name으로 적용한다.
|
||||||
|
- filter function 자동 생성 입력은 이번 화면에서 제거한다. filter/policy 관리는 `Filter Policy 관리` 메뉴에서 수행한다.
|
||||||
|
- 선택한 template의 policy name, filter function, statement types를 preview로 표시한다.
|
||||||
|
|
||||||
|
## 완료 기준
|
||||||
|
|
||||||
|
- VPD 설정 화면에서 개별 적용과 벌크 적용 모두 `적용할 Policy Template`을 선택한다.
|
||||||
|
- 벌크 적용 화면에서 `VPD Filter Function`, `Filter predicate` 직접 입력이 사라진다.
|
||||||
|
- 선택한 template의 policy/function/statement/enabled/check option preview가 표시된다.
|
||||||
|
- controller/service가 벌크 적용 시 선택된 policy name과 function key를 사용한다.
|
||||||
|
- 기존 개별 적용 동작은 유지된다.
|
||||||
|
|
||||||
|
## 검증
|
||||||
|
|
||||||
|
- `mvn test`
|
||||||
|
- Playwright 화면 확인:
|
||||||
|
- `/vpd-policies` 개별 적용에 policy template preview 표시
|
||||||
|
- `/vpd-policies` 벌크 적용에 policy template select 표시
|
||||||
|
- 벌크 적용에 filter function 직접 선택/textarea가 없음
|
||||||
|
- 390px 모바일 body overflow 없음
|
||||||
@@ -139,6 +139,7 @@ public class VpdPolicyService {
|
|||||||
String schemaOwner,
|
String schemaOwner,
|
||||||
boolean includeTables,
|
boolean includeTables,
|
||||||
boolean includeViews,
|
boolean includeViews,
|
||||||
|
String policyNameValue,
|
||||||
String functionKey,
|
String functionKey,
|
||||||
String functionOwnerValue,
|
String functionOwnerValue,
|
||||||
String functionNameValue,
|
String functionNameValue,
|
||||||
@@ -160,6 +161,9 @@ public class VpdPolicyService {
|
|||||||
throw new AppException("선택한 스키마에서 VPD 적용 대상 TABLE/VIEW를 찾을 수 없습니다: " + owner);
|
throw new AppException("선택한 스키마에서 VPD 적용 대상 TABLE/VIEW를 찾을 수 없습니다: " + owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String bulkPolicyName = policyNameValue == null || policyNameValue.isBlank()
|
||||||
|
? COMMON_POLICY_NAME
|
||||||
|
: requiredIdentifier(policyNameValue, "Policy name");
|
||||||
FunctionRef functionRef = parseFunctionRef(defaultFunctionKey(functionKey));
|
FunctionRef functionRef = parseFunctionRef(defaultFunctionKey(functionKey));
|
||||||
String filterPredicate = filterPredicateValue == null ? "" : filterPredicateValue.trim();
|
String filterPredicate = filterPredicateValue == null ? "" : filterPredicateValue.trim();
|
||||||
if (functionRef == null && filterPredicate.isBlank()) {
|
if (functionRef == null && filterPredicate.isBlank()) {
|
||||||
@@ -197,8 +201,7 @@ public class VpdPolicyService {
|
|||||||
int skipped = 0;
|
int skipped = 0;
|
||||||
int failed = 0;
|
int failed = 0;
|
||||||
for (VpdSchemaObjectOption target : targets) {
|
for (VpdSchemaObjectOption target : targets) {
|
||||||
String policyName = generatedPolicyName(target.objectName());
|
if (mapper.findAnyPolicy(target.owner(), target.objectName(), bulkPolicyName) != null) {
|
||||||
if (mapper.findAnyPolicy(target.owner(), target.objectName(), policyName) != null) {
|
|
||||||
skipped++;
|
skipped++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -206,7 +209,7 @@ public class VpdPolicyService {
|
|||||||
addPolicy(
|
addPolicy(
|
||||||
target.owner(),
|
target.owner(),
|
||||||
target.objectName(),
|
target.objectName(),
|
||||||
policyName,
|
bulkPolicyName,
|
||||||
functionOwner,
|
functionOwner,
|
||||||
packageName == null ? functionName : packageName + "." + functionName,
|
packageName == null ? functionName : packageName + "." + functionName,
|
||||||
statementTypes,
|
statementTypes,
|
||||||
@@ -538,10 +541,6 @@ public class VpdPolicyService {
|
|||||||
return generated.length() > 128 ? generated.substring(0, 128) : generated;
|
return generated.length() > 128 ? generated.substring(0, 128) : generated;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String generatedPolicyName(String objectName) {
|
|
||||||
return COMMON_POLICY_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
private FunctionRef parseFunctionRef(String functionKey) {
|
private FunctionRef parseFunctionRef(String functionKey) {
|
||||||
if (functionKey == null || functionKey.isBlank()) {
|
if (functionKey == null || functionKey.isBlank()) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -184,6 +184,7 @@ public class VpdPolicyController {
|
|||||||
@RequestParam String schemaOwner,
|
@RequestParam String schemaOwner,
|
||||||
@RequestParam(defaultValue = "false") boolean includeTables,
|
@RequestParam(defaultValue = "false") boolean includeTables,
|
||||||
@RequestParam(defaultValue = "false") boolean includeViews,
|
@RequestParam(defaultValue = "false") boolean includeViews,
|
||||||
|
@RequestParam(required = false) String policyName,
|
||||||
@RequestParam(required = false) String functionKey,
|
@RequestParam(required = false) String functionKey,
|
||||||
@RequestParam(required = false) String functionOwner,
|
@RequestParam(required = false) String functionOwner,
|
||||||
@RequestParam(required = false) String functionName,
|
@RequestParam(required = false) String functionName,
|
||||||
@@ -193,7 +194,7 @@ public class VpdPolicyController {
|
|||||||
@RequestParam(required = false) String filterPredicate,
|
@RequestParam(required = false) String filterPredicate,
|
||||||
RedirectAttributes redirectAttributes
|
RedirectAttributes redirectAttributes
|
||||||
) {
|
) {
|
||||||
bulkApplyPolicyInternal(schemaOwner, includeTables, includeViews, functionKey, functionOwner, functionName,
|
bulkApplyPolicyInternal(schemaOwner, includeTables, includeViews, policyName, functionKey, functionOwner, functionName,
|
||||||
statementTypes, enabled, updateCheck, filterPredicate, redirectAttributes);
|
statementTypes, enabled, updateCheck, filterPredicate, redirectAttributes);
|
||||||
return "redirect:/vpd-policies";
|
return "redirect:/vpd-policies";
|
||||||
}
|
}
|
||||||
@@ -202,6 +203,7 @@ public class VpdPolicyController {
|
|||||||
String schemaOwner,
|
String schemaOwner,
|
||||||
boolean includeTables,
|
boolean includeTables,
|
||||||
boolean includeViews,
|
boolean includeViews,
|
||||||
|
String policyName,
|
||||||
String functionKey,
|
String functionKey,
|
||||||
String functionOwner,
|
String functionOwner,
|
||||||
String functionName,
|
String functionName,
|
||||||
@@ -216,6 +218,7 @@ public class VpdPolicyController {
|
|||||||
schemaOwner,
|
schemaOwner,
|
||||||
includeTables,
|
includeTables,
|
||||||
includeViews,
|
includeViews,
|
||||||
|
policyName,
|
||||||
functionKey,
|
functionKey,
|
||||||
functionOwner,
|
functionOwner,
|
||||||
functionName,
|
functionName,
|
||||||
|
|||||||
@@ -520,6 +520,57 @@ body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.policy-apply-flow {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: .4rem;
|
||||||
|
margin-bottom: .75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.policy-apply-flow span {
|
||||||
|
background: var(--rw-surface-muted);
|
||||||
|
border: 1px solid var(--rw-border);
|
||||||
|
border-radius: 999px;
|
||||||
|
color: var(--rw-text);
|
||||||
|
font-size: .82rem;
|
||||||
|
font-weight: 800;
|
||||||
|
min-height: 2rem;
|
||||||
|
padding: .35rem .7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.policy-apply-flow strong {
|
||||||
|
color: var(--rw-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.policy-template-preview dl {
|
||||||
|
display: grid;
|
||||||
|
gap: .75rem;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.policy-template-preview dl > div {
|
||||||
|
background: var(--rw-surface-muted);
|
||||||
|
border: 1px solid var(--rw-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
min-width: 0;
|
||||||
|
padding: .7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.policy-template-preview dt {
|
||||||
|
color: var(--rw-muted);
|
||||||
|
font-size: .74rem;
|
||||||
|
font-weight: 800;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.policy-template-preview dd {
|
||||||
|
font-weight: 700;
|
||||||
|
margin: .25rem 0 0;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
.bulk-apply-summary {
|
.bulk-apply-summary {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ function syncPolicyTemplate(select) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const option = select.options[select.selectedIndex];
|
const option = select.options[select.selectedIndex];
|
||||||
|
const hasTemplate = Boolean(option?.dataset.policyName);
|
||||||
const policyName = form.querySelector('[data-policy-template-field="policyName"]');
|
const policyName = form.querySelector('[data-policy-template-field="policyName"]');
|
||||||
const functionKey = form.querySelector('[data-policy-template-field="functionKey"]');
|
const functionKey = form.querySelector('[data-policy-template-field="functionKey"]');
|
||||||
const enabled = form.querySelector('[data-policy-template-field="enabled"]');
|
const enabled = form.querySelector('[data-policy-template-field="enabled"]');
|
||||||
@@ -45,18 +46,31 @@ function syncPolicyTemplate(select) {
|
|||||||
functionKey.value = option?.dataset.functionKey || '';
|
functionKey.value = option?.dataset.functionKey || '';
|
||||||
}
|
}
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
enabled.checked = option?.dataset.enabled === 'true';
|
enabled.checked = hasTemplate ? option?.dataset.enabled === 'true' : true;
|
||||||
}
|
}
|
||||||
if (updateCheck) {
|
if (updateCheck) {
|
||||||
updateCheck.checked = option?.dataset.updateCheck === 'true';
|
updateCheck.checked = hasTemplate ? option?.dataset.updateCheck === 'true' : false;
|
||||||
}
|
}
|
||||||
const selectedStatements = (option?.dataset.statementTypes || '')
|
const selectedStatements = (hasTemplate ? option?.dataset.statementTypes || '' : 'SELECT')
|
||||||
.split(',')
|
.split(',')
|
||||||
.map((value) => value.trim())
|
.map((value) => value.trim())
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
form.querySelectorAll('[data-policy-template-statement]').forEach((checkbox) => {
|
form.querySelectorAll('[data-policy-template-statement]').forEach((checkbox) => {
|
||||||
checkbox.checked = selectedStatements.includes(checkbox.value);
|
checkbox.checked = selectedStatements.includes(checkbox.value);
|
||||||
});
|
});
|
||||||
|
const preview = (name, value) => {
|
||||||
|
const target = form.querySelector(`[data-policy-template-preview="${name}"]`);
|
||||||
|
if (target) {
|
||||||
|
target.textContent = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
preview('policyName', option?.dataset.policyName || '선택 전');
|
||||||
|
preview('functionKey', option?.dataset.functionKey || '선택 전');
|
||||||
|
preview('statementTypes', selectedStatements.join(', ') || 'SELECT');
|
||||||
|
preview(
|
||||||
|
'options',
|
||||||
|
`Enabled: ${enabled?.checked ? 'YES' : 'NO'} / Check: ${updateCheck?.checked ? 'YES' : 'NO'}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeMenuGroup(group) {
|
function closeMenuGroup(group) {
|
||||||
|
|||||||
@@ -23,7 +23,14 @@
|
|||||||
<h2>VPD 적용</h2>
|
<h2>VPD 적용</h2>
|
||||||
<a class="btn btn-sm rw-btn-primary" href="/vpd-filter-policies">Filter Policy 관리</a>
|
<a class="btn btn-sm rw-btn-primary" href="/vpd-filter-policies">Filter Policy 관리</a>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-muted mb-0">VPD는 개별 보호 객체 적용을 기본으로 하고, 같은 정책을 스키마 TABLE/VIEW에 확장할 때만 벌크 적용을 사용합니다.</p>
|
<div class="policy-apply-flow" aria-label="VPD 적용 계층">
|
||||||
|
<span>Filter Function</span>
|
||||||
|
<strong>→</strong>
|
||||||
|
<span>Policy Template</span>
|
||||||
|
<strong>→</strong>
|
||||||
|
<span>TABLE/VIEW 적용</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-muted mb-0">VPD는 개별 보호 객체 적용을 기본으로 하고, 같은 Policy Template을 스키마 TABLE/VIEW에 확장할 때만 벌크 적용을 사용합니다.</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="content-band">
|
<section class="content-band">
|
||||||
@@ -192,9 +199,9 @@
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label class="span-2">
|
<label class="span-2">
|
||||||
적용할 Policy / Filter
|
적용할 Policy Template
|
||||||
<select class="form-select" data-policy-template-select required>
|
<select class="form-select" data-policy-template-select required>
|
||||||
<option value="">등록된 policy/filter 조합 선택</option>
|
<option value="">등록된 policy template 선택</option>
|
||||||
<option th:each="template : ${formOptions.policyTemplates()}"
|
<option th:each="template : ${formOptions.policyTemplates()}"
|
||||||
th:value="${template.label()}"
|
th:value="${template.label()}"
|
||||||
th:data-policy-name="${template.policyName()}"
|
th:data-policy-name="${template.policyName()}"
|
||||||
@@ -207,6 +214,14 @@
|
|||||||
</label>
|
</label>
|
||||||
<input type="hidden" name="policyName" data-policy-template-field="policyName">
|
<input type="hidden" name="policyName" data-policy-template-field="policyName">
|
||||||
<input type="hidden" name="functionKey" data-policy-template-field="functionKey">
|
<input type="hidden" name="functionKey" data-policy-template-field="functionKey">
|
||||||
|
<aside class="policy-template-preview span-2" data-policy-template-summary>
|
||||||
|
<dl>
|
||||||
|
<div><dt>Policy</dt><dd data-policy-template-preview="policyName">선택 전</dd></div>
|
||||||
|
<div><dt>Filter Function</dt><dd data-policy-template-preview="functionKey">선택 전</dd></div>
|
||||||
|
<div><dt>Statements</dt><dd data-policy-template-preview="statementTypes">SELECT</dd></div>
|
||||||
|
<div><dt>Options</dt><dd data-policy-template-preview="options">Enabled: YES / Check: NO</dd></div>
|
||||||
|
</dl>
|
||||||
|
</aside>
|
||||||
<div>
|
<div>
|
||||||
Statement Types
|
Statement Types
|
||||||
<div class="checkbox-row">
|
<div class="checkbox-row">
|
||||||
@@ -228,7 +243,7 @@
|
|||||||
data-policy-template-field="updateCheck">
|
data-policy-template-field="updateCheck">
|
||||||
<label class="form-check-label" for="vpd-update-check">INSERT/UPDATE에도 predicate check 적용</label>
|
<label class="form-check-label" for="vpd-update-check">INSERT/UPDATE에도 predicate check 적용</label>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-muted small span-2 mb-0">Policy/Filter 조합은 Filter Policy 관리에서 등록/수정합니다. 여기서는 선택한 조합을 대상 TABLE/VIEW에 적용합니다.</p>
|
<p class="text-muted small span-2 mb-0">Policy Template은 policy name과 filter function의 조합입니다. Filter Function은 Filter Policy 관리에서 등록/수정하고, 여기서는 선택한 template을 대상 TABLE/VIEW에 적용합니다.</p>
|
||||||
<button class="btn rw-btn-primary" type="submit">개별 객체에 VPD 적용</button>
|
<button class="btn rw-btn-primary" type="submit">개별 객체에 VPD 적용</button>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
@@ -257,39 +272,52 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<label class="span-2">
|
<label class="span-2">
|
||||||
VPD Filter Function
|
적용할 Policy Template
|
||||||
<select class="form-select" name="functionKey">
|
<select class="form-select" data-policy-template-select required>
|
||||||
<option value="">기본 권한 함수가 없으면 Filter predicate로 벌크 function 자동 생성</option>
|
<option value="">등록된 policy template 선택</option>
|
||||||
<option th:each="function : ${formOptions.functions()}"
|
<option th:each="template : ${formOptions.policyTemplates()}"
|
||||||
th:value="${function.value()}"
|
th:value="${template.label()}"
|
||||||
th:text="${function.functionName() == 'CB_AGENT_DOC_VPD_FILTER' ? function.label() + ' / 기본 권한 테이블 필터' : function.label()}"
|
th:data-policy-name="${template.policyName()}"
|
||||||
th:selected="${function.value() == formOptions.defaultPermissionFunctionKey()}"></option>
|
th:data-function-key="${template.functionKey()}"
|
||||||
|
th:data-statement-types="${template.statementTypes()}"
|
||||||
|
th:data-enabled="${template.enabledValue()}"
|
||||||
|
th:data-update-check="${template.updateCheckValue()}"
|
||||||
|
th:text="${template.label()}"></option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
<input type="hidden" name="policyName" data-policy-template-field="policyName">
|
||||||
|
<input type="hidden" name="functionKey" data-policy-template-field="functionKey">
|
||||||
|
<aside class="policy-template-preview span-2" data-policy-template-summary>
|
||||||
|
<dl>
|
||||||
|
<div><dt>Policy</dt><dd data-policy-template-preview="policyName">선택 전</dd></div>
|
||||||
|
<div><dt>Filter Function</dt><dd data-policy-template-preview="functionKey">선택 전</dd></div>
|
||||||
|
<div><dt>Statements</dt><dd data-policy-template-preview="statementTypes">SELECT</dd></div>
|
||||||
|
<div><dt>Options</dt><dd data-policy-template-preview="options">Enabled: YES / Check: NO</dd></div>
|
||||||
|
</dl>
|
||||||
|
</aside>
|
||||||
<div>
|
<div>
|
||||||
Statement Types
|
Statement Types
|
||||||
<div class="checkbox-row">
|
<div class="checkbox-row">
|
||||||
<label class="form-check" th:each="statement : ${formOptions.statementTypes()}">
|
<label class="form-check" th:each="statement : ${formOptions.statementTypes()}">
|
||||||
<input class="form-check-input" type="checkbox" name="statementTypes"
|
<input class="form-check-input" type="checkbox" name="statementTypes"
|
||||||
th:value="${statement}" th:checked="${statement == 'SELECT'}">
|
th:value="${statement}" th:checked="${statement == 'SELECT'}"
|
||||||
|
data-policy-template-statement>
|
||||||
<span class="form-check-label" th:text="${statement}">SELECT</span>
|
<span class="form-check-label" th:text="${statement}">SELECT</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check align-self-end">
|
<div class="form-check align-self-end">
|
||||||
<input class="form-check-input" id="bulk-vpd-enabled" type="checkbox" name="enabled" value="true" checked>
|
<input class="form-check-input" id="bulk-vpd-enabled" type="checkbox" name="enabled" value="true" checked
|
||||||
|
data-policy-template-field="enabled">
|
||||||
<label class="form-check-label" for="bulk-vpd-enabled">등록 즉시 활성화</label>
|
<label class="form-check-label" for="bulk-vpd-enabled">등록 즉시 활성화</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check span-2">
|
<div class="form-check span-2">
|
||||||
<input class="form-check-input" id="bulk-vpd-update-check" type="checkbox" name="updateCheck" value="true">
|
<input class="form-check-input" id="bulk-vpd-update-check" type="checkbox" name="updateCheck" value="true"
|
||||||
|
data-policy-template-field="updateCheck">
|
||||||
<label class="form-check-label" for="bulk-vpd-update-check">INSERT/UPDATE에도 predicate check 적용</label>
|
<label class="form-check-label" for="bulk-vpd-update-check">INSERT/UPDATE에도 predicate check 적용</label>
|
||||||
</div>
|
</div>
|
||||||
<label class="span-2">
|
<p class="text-muted small span-2 mb-0">벌크 적용은 선택한 Policy Template을 스키마의 TABLE/VIEW 목록에 적용합니다. Filter Function을 직접 선택하거나 predicate를 입력하지 않습니다.</p>
|
||||||
Filter predicate
|
|
||||||
<textarea class="form-control" id="bulk-vpd-filter-predicate" name="filterPredicate" rows="4"
|
|
||||||
placeholder="기존 Function을 선택하지 않으면 이 predicate로 스키마 공통 function을 생성합니다."></textarea>
|
|
||||||
</label>
|
|
||||||
<button class="btn rw-btn-primary" type="submit">스키마 TABLE/VIEW에 VPD 일괄 적용</button>
|
<button class="btn rw-btn-primary" type="submit">스키마 TABLE/VIEW에 VPD 일괄 적용</button>
|
||||||
</form>
|
</form>
|
||||||
</details>
|
</details>
|
||||||
|
|||||||
Reference in New Issue
Block a user