fix #424: add filter and policy management lists
This commit is contained in:
@@ -56,6 +56,45 @@ public class VpdPolicyService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void saveFilterFunction(String functionOwnerValue, String functionNameValue, String filterPredicateValue) {
|
||||||
|
String currentUser = jdbcTemplate.queryForObject("SELECT USER FROM dual", String.class);
|
||||||
|
String functionOwner = functionOwnerValue == null || functionOwnerValue.isBlank()
|
||||||
|
? currentUser
|
||||||
|
: requiredIdentifier(functionOwnerValue, "Function owner");
|
||||||
|
if (currentUser == null || !currentUser.equalsIgnoreCase(functionOwner)) {
|
||||||
|
throw new AppException("Filter function 등록/수정은 현재 연결 사용자 스키마에만 가능합니다. 현재 사용자: "
|
||||||
|
+ currentUser + ", Function owner: " + functionOwner);
|
||||||
|
}
|
||||||
|
String functionName = requiredIdentifier(functionNameValue, "Function name");
|
||||||
|
String filterPredicate = filterPredicateValue == null ? "" : filterPredicateValue.trim();
|
||||||
|
if (filterPredicate.isBlank()) {
|
||||||
|
throw new AppException("Filter predicate는 필수입니다.");
|
||||||
|
}
|
||||||
|
createFilterFunction(functionName, filterPredicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void replacePolicy(String oldObjectKey, String oldPolicyName, VpdPolicyCreateCommand command) {
|
||||||
|
String[] objectParts = oldObjectKey == null ? new String[0] : oldObjectKey.split("\\.", 2);
|
||||||
|
if (objectParts.length != 2) {
|
||||||
|
throw new AppException("수정할 Policy 대상 형식이 올바르지 않습니다: " + oldObjectKey);
|
||||||
|
}
|
||||||
|
String objectOwner = requiredIdentifier(objectParts[0], "Object owner");
|
||||||
|
String objectName = requiredIdentifier(objectParts[1], "Object name");
|
||||||
|
String policyName = requiredIdentifier(oldPolicyName, "Policy name");
|
||||||
|
jdbcTemplate.update("""
|
||||||
|
BEGIN
|
||||||
|
DBMS_RLS.DROP_POLICY(
|
||||||
|
object_schema => ?,
|
||||||
|
object_name => ?,
|
||||||
|
policy_name => ?
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
""", objectOwner, objectName, policyName);
|
||||||
|
createPolicy(command);
|
||||||
|
}
|
||||||
|
|
||||||
public VpdBulkApplyResult bulkApplySchema(
|
public VpdBulkApplyResult bulkApplySchema(
|
||||||
String schemaOwner,
|
String schemaOwner,
|
||||||
boolean includeTables,
|
boolean includeTables,
|
||||||
|
|||||||
@@ -86,6 +86,67 @@ public class VpdPolicyController {
|
|||||||
return "redirect:/vpd-filter-policies";
|
return "redirect:/vpd-filter-policies";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/vpd-filter-policies/filters")
|
||||||
|
public String saveFilter(
|
||||||
|
@RequestParam(required = false) String functionOwner,
|
||||||
|
@RequestParam String functionName,
|
||||||
|
@RequestParam String filterPredicate,
|
||||||
|
RedirectAttributes redirectAttributes
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
vpdPolicyService.saveFilterFunction(functionOwner, functionName, filterPredicate);
|
||||||
|
redirectAttributes.addFlashAttribute("successMessage", "Filter function을 저장했습니다: " + functionName);
|
||||||
|
} catch (AppException exception) {
|
||||||
|
redirectAttributes.addFlashAttribute("errorMessage", exception.getMessage());
|
||||||
|
} catch (DataAccessException exception) {
|
||||||
|
RuntimeErrorMessage message = RuntimeErrorMessages.dataAccess(exception);
|
||||||
|
redirectAttributes.addFlashAttribute("errorMessage", message.message());
|
||||||
|
}
|
||||||
|
return "redirect:/vpd-filter-policies";
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/vpd-filter-policies/replace")
|
||||||
|
public String replaceFilterPolicy(
|
||||||
|
@RequestParam String oldObjectKey,
|
||||||
|
@RequestParam String oldPolicyName,
|
||||||
|
@RequestParam String objectKey,
|
||||||
|
@RequestParam String policyName,
|
||||||
|
@RequestParam(required = false) String functionKey,
|
||||||
|
@RequestParam(required = false) String functionOwner,
|
||||||
|
@RequestParam(required = false) String functionName,
|
||||||
|
@RequestParam(defaultValue = "SELECT") List<String> statementTypes,
|
||||||
|
@RequestParam(defaultValue = "false") boolean enabled,
|
||||||
|
@RequestParam(defaultValue = "false") boolean updateCheck,
|
||||||
|
@RequestParam(required = false) String filterPredicate,
|
||||||
|
RedirectAttributes redirectAttributes
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
String[] objectParts = objectKey.split("\\.", 2);
|
||||||
|
if (objectParts.length != 2) {
|
||||||
|
throw new AppException("조회 대상 형식이 올바르지 않습니다: " + objectKey);
|
||||||
|
}
|
||||||
|
vpdPolicyService.replacePolicy(oldObjectKey, oldPolicyName, new VpdPolicyCreateCommand(
|
||||||
|
objectParts[0],
|
||||||
|
objectParts[1],
|
||||||
|
policyName,
|
||||||
|
functionKey,
|
||||||
|
functionOwner,
|
||||||
|
functionName,
|
||||||
|
String.join(",", statementTypes),
|
||||||
|
enabled,
|
||||||
|
updateCheck,
|
||||||
|
filterPredicate
|
||||||
|
));
|
||||||
|
redirectAttributes.addFlashAttribute("successMessage", "Policy를 수정했습니다: " + policyName);
|
||||||
|
} catch (AppException exception) {
|
||||||
|
redirectAttributes.addFlashAttribute("errorMessage", exception.getMessage());
|
||||||
|
} catch (DataAccessException exception) {
|
||||||
|
RuntimeErrorMessage message = RuntimeErrorMessages.dataAccess(exception);
|
||||||
|
redirectAttributes.addFlashAttribute("errorMessage", message.message());
|
||||||
|
}
|
||||||
|
return "redirect:/vpd-filter-policies";
|
||||||
|
}
|
||||||
|
|
||||||
private void createPolicyInternal(
|
private void createPolicyInternal(
|
||||||
String objectKey,
|
String objectKey,
|
||||||
String policyName,
|
String policyName,
|
||||||
|
|||||||
@@ -18,9 +18,81 @@
|
|||||||
|
|
||||||
<section class="content-band">
|
<section class="content-band">
|
||||||
<div class="section-heading">
|
<div class="section-heading">
|
||||||
<h2>Filter Policy 등록</h2>
|
<h2>Filter 등록/수정</h2>
|
||||||
<a class="btn btn-sm rw-btn-secondary" href="/vpd-policies">적용 현황 보기</a>
|
<a class="btn btn-sm rw-btn-secondary" href="/vpd-policies">적용 현황 보기</a>
|
||||||
</div>
|
</div>
|
||||||
|
<form method="post" action="/vpd-filter-policies/filters" class="form-grid">
|
||||||
|
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||||
|
<label>
|
||||||
|
Function Owner
|
||||||
|
<select class="form-select" name="functionOwner">
|
||||||
|
<option value="">현재 연결 사용자</option>
|
||||||
|
<option th:each="owner : ${formOptions.owners()}" th:value="${owner}" th:text="${owner}"></option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Function 이름
|
||||||
|
<input class="form-control" name="functionName" list="filter-function-names"
|
||||||
|
placeholder="예: BOARD_POSTS_FILTER" required>
|
||||||
|
</label>
|
||||||
|
<label class="span-2">
|
||||||
|
Filter predicate
|
||||||
|
<textarea class="form-control" id="filter-only-predicate" name="filterPredicate" rows="4"
|
||||||
|
placeholder="예: dept_code = SYS_CONTEXT(''CB_AGENT_CTX'', ''DEPT_CODE'')" required></textarea>
|
||||||
|
</label>
|
||||||
|
<div class="question-presets span-2" aria-label="Filter predicate 예시">
|
||||||
|
<button class="btn rw-btn-secondary question-preset" type="button" data-target="filter-only-predicate" data-question="1=0">전체 차단</button>
|
||||||
|
<button class="btn rw-btn-secondary question-preset" type="button" data-target="filter-only-predicate" data-question="1=1">전체 허용</button>
|
||||||
|
<button class="btn rw-btn-secondary question-preset" type="button" data-target="filter-only-predicate" data-question="dept_code = SYS_CONTEXT('CB_AGENT_CTX', 'DEPT_CODE')">부서 일치</button>
|
||||||
|
<button class="btn rw-btn-secondary question-preset" type="button" data-target="filter-only-predicate" data-question="owner_emp_no = SYS_CONTEXT('CB_AGENT_CTX', 'EMP_NO')">본인 소유</button>
|
||||||
|
</div>
|
||||||
|
<button class="btn rw-btn-primary" type="submit">Filter 저장</button>
|
||||||
|
</form>
|
||||||
|
<datalist id="filter-function-names">
|
||||||
|
<option th:each="function : ${formOptions.functions()}" th:value="${function.functionName()}"></option>
|
||||||
|
</datalist>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="content-band">
|
||||||
|
<div class="section-heading">
|
||||||
|
<h2>Filter 목록</h2>
|
||||||
|
<span class="badge text-bg-secondary" th:text="${#lists.size(formOptions.functions())}">0</span>
|
||||||
|
</div>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm align-middle">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Function</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>수정</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr th:each="function : ${formOptions.functions()}">
|
||||||
|
<td><code th:text="${function.value()}">ADMIN.FILTER</code></td>
|
||||||
|
<td th:text="${function.objectType()}">FUNCTION</td>
|
||||||
|
<td>
|
||||||
|
<form method="post" action="/vpd-filter-policies/filters" class="inline-form">
|
||||||
|
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||||
|
<input type="hidden" name="functionOwner" th:value="${function.owner()}">
|
||||||
|
<input type="hidden" name="functionName" th:value="${function.functionName()}">
|
||||||
|
<input class="form-control form-control-sm" name="filterPredicate" placeholder="새 predicate">
|
||||||
|
<button class="btn btn-sm rw-btn-secondary" type="submit">저장</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr th:if="${#lists.isEmpty(formOptions.functions())}">
|
||||||
|
<td colspan="3" class="text-muted">등록된 VPD filter function 후보가 없습니다.</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="content-band">
|
||||||
|
<div class="section-heading">
|
||||||
|
<h2>Policy 등록</h2>
|
||||||
|
</div>
|
||||||
<form method="post" action="/vpd-filter-policies" class="form-grid">
|
<form method="post" action="/vpd-filter-policies" class="form-grid">
|
||||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||||
<label>
|
<label>
|
||||||
@@ -99,6 +171,55 @@
|
|||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="content-band">
|
||||||
|
<div class="section-heading">
|
||||||
|
<h2>Policy 목록/수정</h2>
|
||||||
|
<span class="badge text-bg-secondary" th:text="${#lists.size(policies)}">0</span>
|
||||||
|
</div>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm align-middle">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Object</th>
|
||||||
|
<th>Policy</th>
|
||||||
|
<th>Function</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>수정</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr th:each="policy : ${policies}">
|
||||||
|
<td><code th:text="${policy.objectDisplayName()}">ADMIN.TABLE</code></td>
|
||||||
|
<td><code th:text="${policy.policyName()}">POLICY</code></td>
|
||||||
|
<td><code th:text="${policy.functionDisplayName()}">ADMIN.FILTER</code></td>
|
||||||
|
<td th:text="${policy.enabled()}">YES</td>
|
||||||
|
<td>
|
||||||
|
<form method="post" action="/vpd-filter-policies/replace" class="inline-form">
|
||||||
|
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||||
|
<input type="hidden" name="oldObjectKey" th:value="${policy.objectDisplayName()}">
|
||||||
|
<input type="hidden" name="oldPolicyName" th:value="${policy.policyName()}">
|
||||||
|
<input type="hidden" name="objectKey" th:value="${policy.objectDisplayName()}">
|
||||||
|
<input class="form-control form-control-sm" name="policyName" th:value="${policy.policyName()}">
|
||||||
|
<select class="form-select form-select-sm" name="functionKey">
|
||||||
|
<option th:each="function : ${formOptions.functions()}"
|
||||||
|
th:value="${function.value()}"
|
||||||
|
th:text="${function.value()}"
|
||||||
|
th:selected="${function.value() == policy.functionDisplayName()}"></option>
|
||||||
|
</select>
|
||||||
|
<input type="hidden" name="enabled" value="true">
|
||||||
|
<input type="hidden" name="statementTypes" th:value="${policy.statementTypes()} ?: 'SELECT'">
|
||||||
|
<button class="btn btn-sm rw-btn-secondary" type="submit">수정</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr th:if="${#lists.isEmpty(policies)}">
|
||||||
|
<td colspan="5" class="text-muted">등록된 VPD policy가 없습니다.</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="content-band">
|
<section class="content-band">
|
||||||
<div class="section-heading">
|
<div class="section-heading">
|
||||||
<h2>스키마 벌크 적용</h2>
|
<h2>스키마 벌크 적용</h2>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<main class="container py-4">
|
<main class="container py-4">
|
||||||
<div class="page-title">
|
<div class="page-title">
|
||||||
<h1>VPD 설정</h1>
|
<h1>VPD 설정</h1>
|
||||||
<p>보호 객체에 Oracle VPD policy를 등록하고, policy function/filter predicate를 확인합니다.</p>
|
<p>보호 객체에 적용된 Oracle VPD policy와 policy function/filter predicate를 확인합니다.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="alert alert-warning" th:if="${runtimeError}">
|
<div class="alert alert-warning" th:if="${runtimeError}">
|
||||||
@@ -18,204 +18,10 @@
|
|||||||
|
|
||||||
<section class="content-band">
|
<section class="content-band">
|
||||||
<div class="section-heading">
|
<div class="section-heading">
|
||||||
<h2>Policy 등록</h2>
|
<h2>적용 현황</h2>
|
||||||
|
<a class="btn btn-sm rw-btn-primary" href="/vpd-filter-policies">Filter Policy 관리</a>
|
||||||
</div>
|
</div>
|
||||||
<form method="post" action="/vpd-policies" class="form-grid">
|
<p class="text-muted mb-0">이 화면은 현재 DB에 적용된 VPD policy와 policy function/filter 상세를 조회합니다. 등록과 벌크 적용은 Filter Policy 관리에서 수행합니다.</p>
|
||||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
|
||||||
<label>
|
|
||||||
VPD 적용 대상
|
|
||||||
<select class="form-select" name="objectKey" required>
|
|
||||||
<option th:each="object : ${objects}"
|
|
||||||
th:value="${object.owner() + '.' + object.objectName()}"
|
|
||||||
th:text="${object.displayName() + ' / ' + object.ordsPath()}"></option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Policy 이름
|
|
||||||
<select class="form-select" name="policyName" required>
|
|
||||||
<option th:each="policyName : ${formOptions.policyNames()}"
|
|
||||||
th:value="${policyName}"
|
|
||||||
th:text="${policyName}"></option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label class="span-2">
|
|
||||||
기존 Function 선택
|
|
||||||
<select class="form-select" name="functionKey">
|
|
||||||
<option value="">Filter predicate로 새 function 자동 생성</option>
|
|
||||||
<option th:each="function : ${formOptions.functions()}"
|
|
||||||
th:value="${function.value()}"
|
|
||||||
th:text="${function.label()}"></option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Function Owner
|
|
||||||
<select class="form-select" name="functionOwner">
|
|
||||||
<option value="">현재 연결 사용자</option>
|
|
||||||
<option th:each="owner : ${formOptions.owners()}"
|
|
||||||
th:value="${owner}"
|
|
||||||
th:text="${owner}"></option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Function 이름
|
|
||||||
<select class="form-select" name="functionName">
|
|
||||||
<option value="">POLICY_NAME_FILTER 자동 생성</option>
|
|
||||||
<option th:each="function : ${formOptions.functions()}"
|
|
||||||
th:value="${function.functionName()}"
|
|
||||||
th:text="${function.functionName()}"></option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<div>
|
|
||||||
Statement Types
|
|
||||||
<div class="checkbox-row">
|
|
||||||
<label class="form-check" th:each="statement : ${formOptions.statementTypes()}">
|
|
||||||
<input class="form-check-input"
|
|
||||||
type="checkbox"
|
|
||||||
name="statementTypes"
|
|
||||||
th:value="${statement}"
|
|
||||||
th:checked="${statement == 'SELECT'}">
|
|
||||||
<span class="form-check-label" th:text="${statement}">SELECT</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-check align-self-end">
|
|
||||||
<input class="form-check-input" id="vpd-enabled" type="checkbox" name="enabled" value="true" checked>
|
|
||||||
<label class="form-check-label" for="vpd-enabled">등록 즉시 활성화</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check span-2">
|
|
||||||
<input class="form-check-input" id="vpd-update-check" type="checkbox" name="updateCheck" value="true">
|
|
||||||
<label class="form-check-label" for="vpd-update-check">INSERT/UPDATE에도 predicate check 적용</label>
|
|
||||||
</div>
|
|
||||||
<label class="span-2">
|
|
||||||
Filter predicate
|
|
||||||
<textarea class="form-control" id="vpd-filter-predicate" name="filterPredicate" rows="4"
|
|
||||||
placeholder="예: dept_code = SYS_CONTEXT(''CB_AGENT_CTX'', ''DEPT_CODE'') 비우면 기존 Function 이름으로 ADD_POLICY만 실행합니다."></textarea>
|
|
||||||
</label>
|
|
||||||
<div class="question-presets span-2" aria-label="Filter predicate 예시">
|
|
||||||
<button class="btn rw-btn-secondary question-preset" type="button"
|
|
||||||
data-target="vpd-filter-predicate"
|
|
||||||
data-question="1=0">
|
|
||||||
전체 차단
|
|
||||||
</button>
|
|
||||||
<button class="btn rw-btn-secondary question-preset" type="button"
|
|
||||||
data-target="vpd-filter-predicate"
|
|
||||||
data-question="1=1">
|
|
||||||
전체 허용
|
|
||||||
</button>
|
|
||||||
<button class="btn rw-btn-secondary question-preset" type="button"
|
|
||||||
data-target="vpd-filter-predicate"
|
|
||||||
data-question="dept_code = SYS_CONTEXT('CB_AGENT_CTX', 'DEPT_CODE')">
|
|
||||||
부서 일치
|
|
||||||
</button>
|
|
||||||
<button class="btn rw-btn-secondary question-preset" type="button"
|
|
||||||
data-target="vpd-filter-predicate"
|
|
||||||
data-question="owner_emp_no = SYS_CONTEXT('CB_AGENT_CTX', 'EMP_NO')">
|
|
||||||
본인 소유
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<button class="btn rw-btn-primary" type="submit">Policy 등록</button>
|
|
||||||
</form>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="content-band">
|
|
||||||
<div class="section-heading">
|
|
||||||
<h2>스키마 벌크 적용</h2>
|
|
||||||
</div>
|
|
||||||
<form method="post" action="/vpd-policies/bulk" class="form-grid">
|
|
||||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
|
||||||
<label>
|
|
||||||
Schema
|
|
||||||
<select class="form-select" name="schemaOwner" required>
|
|
||||||
<option th:each="owner : ${formOptions.schemaOwners()}"
|
|
||||||
th:value="${owner}"
|
|
||||||
th:text="${owner}"></option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<div>
|
|
||||||
Object Types
|
|
||||||
<div class="checkbox-row">
|
|
||||||
<label class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" name="includeTables" value="true" checked>
|
|
||||||
<span class="form-check-label">TABLE</span>
|
|
||||||
</label>
|
|
||||||
<label class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" name="includeViews" value="true" checked>
|
|
||||||
<span class="form-check-label">VIEW</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<label class="span-2">
|
|
||||||
기존 Function 선택
|
|
||||||
<select class="form-select" name="functionKey">
|
|
||||||
<option value="">Filter predicate로 벌크 function 자동 생성</option>
|
|
||||||
<option th:each="function : ${formOptions.functions()}"
|
|
||||||
th:value="${function.value()}"
|
|
||||||
th:text="${function.label()}"></option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Function Owner
|
|
||||||
<select class="form-select" name="functionOwner">
|
|
||||||
<option value="">현재 연결 사용자</option>
|
|
||||||
<option th:each="owner : ${formOptions.owners()}"
|
|
||||||
th:value="${owner}"
|
|
||||||
th:text="${owner}"></option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Function 이름
|
|
||||||
<select class="form-select" name="functionName">
|
|
||||||
<option value="">SCHEMA_BULK_FILTER 자동 생성</option>
|
|
||||||
<option th:each="function : ${formOptions.functions()}"
|
|
||||||
th:value="${function.functionName()}"
|
|
||||||
th:text="${function.functionName()}"></option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<div>
|
|
||||||
Statement Types
|
|
||||||
<div class="checkbox-row">
|
|
||||||
<label class="form-check" th:each="statement : ${formOptions.statementTypes()}">
|
|
||||||
<input class="form-check-input"
|
|
||||||
type="checkbox"
|
|
||||||
name="statementTypes"
|
|
||||||
th:value="${statement}"
|
|
||||||
th:checked="${statement == 'SELECT'}">
|
|
||||||
<span class="form-check-label" th:text="${statement}">SELECT</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-check align-self-end">
|
|
||||||
<input class="form-check-input" id="vpd-bulk-enabled" type="checkbox" name="enabled" value="true" checked>
|
|
||||||
<label class="form-check-label" for="vpd-bulk-enabled">등록 즉시 활성화</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check span-2">
|
|
||||||
<input class="form-check-input" id="vpd-bulk-update-check" type="checkbox" name="updateCheck" value="true">
|
|
||||||
<label class="form-check-label" for="vpd-bulk-update-check">INSERT/UPDATE에도 predicate check 적용</label>
|
|
||||||
</div>
|
|
||||||
<label class="span-2">
|
|
||||||
Filter predicate
|
|
||||||
<textarea class="form-control" id="vpd-bulk-filter-predicate" name="filterPredicate" rows="4"
|
|
||||||
placeholder="기존 Function을 선택하지 않으면 이 predicate로 스키마 공통 function을 생성합니다."></textarea>
|
|
||||||
</label>
|
|
||||||
<div class="question-presets span-2" aria-label="Bulk filter predicate 예시">
|
|
||||||
<button class="btn rw-btn-secondary question-preset" type="button"
|
|
||||||
data-target="vpd-bulk-filter-predicate"
|
|
||||||
data-question="1=0">
|
|
||||||
전체 차단
|
|
||||||
</button>
|
|
||||||
<button class="btn rw-btn-secondary question-preset" type="button"
|
|
||||||
data-target="vpd-bulk-filter-predicate"
|
|
||||||
data-question="dept_code = SYS_CONTEXT('CB_AGENT_CTX', 'DEPT_CODE')">
|
|
||||||
부서 일치
|
|
||||||
</button>
|
|
||||||
<button class="btn rw-btn-secondary question-preset" type="button"
|
|
||||||
data-target="vpd-bulk-filter-predicate"
|
|
||||||
data-question="owner_emp_no = SYS_CONTEXT('CB_AGENT_CTX', 'EMP_NO')">
|
|
||||||
본인 소유
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<button class="btn rw-btn-primary" type="submit">스키마 전체 적용</button>
|
|
||||||
</form>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="content-band">
|
<section class="content-band">
|
||||||
@@ -310,17 +116,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
<script>
|
|
||||||
document.querySelectorAll('.question-preset').forEach((button) => {
|
|
||||||
button.addEventListener('click', () => {
|
|
||||||
const target = document.getElementById(button.dataset.target || '');
|
|
||||||
if (!target) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
target.value = button.dataset.question || '';
|
|
||||||
target.focus();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user