fix #424: add schema bulk VPD policy apply
This commit is contained in:
@@ -0,0 +1,14 @@
|
|||||||
|
package com.cloudhandson.vpdbackoffice.domain.vpd;
|
||||||
|
|
||||||
|
public record VpdBulkApplyResult(
|
||||||
|
int total,
|
||||||
|
int created,
|
||||||
|
int skipped,
|
||||||
|
int failed
|
||||||
|
) {
|
||||||
|
|
||||||
|
public String summary() {
|
||||||
|
return "VPD bulk 적용 완료: 대상 " + total + "개, 등록 " + created
|
||||||
|
+ "개, 건너뜀 " + skipped + "개, 실패 " + failed + "개";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import java.util.List;
|
|||||||
|
|
||||||
public record VpdPolicyFormOptions(
|
public record VpdPolicyFormOptions(
|
||||||
List<String> policyNames,
|
List<String> policyNames,
|
||||||
|
List<String> schemaOwners,
|
||||||
List<String> owners,
|
List<String> owners,
|
||||||
List<VpdFunctionOption> functions,
|
List<VpdFunctionOption> functions,
|
||||||
List<String> statementTypes
|
List<String> statementTypes
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.cloudhandson.vpdbackoffice.domain.vpd;
|
||||||
|
|
||||||
|
public record VpdSchemaObjectOption(
|
||||||
|
String owner,
|
||||||
|
String objectName,
|
||||||
|
String objectType
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.cloudhandson.vpdbackoffice.mapper;
|
package com.cloudhandson.vpdbackoffice.mapper;
|
||||||
|
|
||||||
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdFunctionOption;
|
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdFunctionOption;
|
||||||
|
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdSchemaObjectOption;
|
||||||
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyView;
|
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyView;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
@@ -13,16 +14,30 @@ public interface VpdPolicyMapper {
|
|||||||
|
|
||||||
List<String> findPolicyNameOptions();
|
List<String> findPolicyNameOptions();
|
||||||
|
|
||||||
|
List<String> findSchemaOwnerOptions();
|
||||||
|
|
||||||
List<String> findOwnerOptions();
|
List<String> findOwnerOptions();
|
||||||
|
|
||||||
List<VpdFunctionOption> findFunctionOptions();
|
List<VpdFunctionOption> findFunctionOptions();
|
||||||
|
|
||||||
|
List<VpdSchemaObjectOption> findSchemaObjects(
|
||||||
|
@Param("owner") String owner,
|
||||||
|
@Param("includeTablesYn") String includeTablesYn,
|
||||||
|
@Param("includeViewsYn") String includeViewsYn
|
||||||
|
);
|
||||||
|
|
||||||
VpdPolicyView findPolicy(
|
VpdPolicyView findPolicy(
|
||||||
@Param("objectOwner") String objectOwner,
|
@Param("objectOwner") String objectOwner,
|
||||||
@Param("objectName") String objectName,
|
@Param("objectName") String objectName,
|
||||||
@Param("policyName") String policyName
|
@Param("policyName") String policyName
|
||||||
);
|
);
|
||||||
|
|
||||||
|
VpdPolicyView findAnyPolicy(
|
||||||
|
@Param("objectOwner") String objectOwner,
|
||||||
|
@Param("objectName") String objectName,
|
||||||
|
@Param("policyName") String policyName
|
||||||
|
);
|
||||||
|
|
||||||
String findFunctionSource(
|
String findFunctionSource(
|
||||||
@Param("owner") String owner,
|
@Param("owner") String owner,
|
||||||
@Param("objectName") String objectName,
|
@Param("objectName") String objectName,
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
package com.cloudhandson.vpdbackoffice.service;
|
package com.cloudhandson.vpdbackoffice.service;
|
||||||
|
|
||||||
|
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdBulkApplyResult;
|
||||||
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdFunctionSource;
|
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdFunctionSource;
|
||||||
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyCreateCommand;
|
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyCreateCommand;
|
||||||
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyDetail;
|
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyDetail;
|
||||||
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyExplanation;
|
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyExplanation;
|
||||||
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyFormOptions;
|
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyFormOptions;
|
||||||
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyView;
|
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyView;
|
||||||
|
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdSchemaObjectOption;
|
||||||
import com.cloudhandson.vpdbackoffice.mapper.VpdPolicyMapper;
|
import com.cloudhandson.vpdbackoffice.mapper.VpdPolicyMapper;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import org.springframework.dao.DataAccessException;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@@ -36,6 +39,7 @@ public class VpdPolicyService {
|
|||||||
public VpdPolicyFormOptions formOptions() {
|
public VpdPolicyFormOptions formOptions() {
|
||||||
return new VpdPolicyFormOptions(
|
return new VpdPolicyFormOptions(
|
||||||
mapper.findPolicyNameOptions(),
|
mapper.findPolicyNameOptions(),
|
||||||
|
mapper.findSchemaOwnerOptions(),
|
||||||
mapper.findOwnerOptions(),
|
mapper.findOwnerOptions(),
|
||||||
mapper.findFunctionOptions(),
|
mapper.findFunctionOptions(),
|
||||||
List.of("SELECT", "INSERT", "UPDATE", "DELETE", "INDEX")
|
List.of("SELECT", "INSERT", "UPDATE", "DELETE", "INDEX")
|
||||||
@@ -47,10 +51,97 @@ public class VpdPolicyService {
|
|||||||
List.of(),
|
List.of(),
|
||||||
List.of(),
|
List.of(),
|
||||||
List.of(),
|
List.of(),
|
||||||
|
List.of(),
|
||||||
List.of("SELECT", "INSERT", "UPDATE", "DELETE", "INDEX")
|
List.of("SELECT", "INSERT", "UPDATE", "DELETE", "INDEX")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public VpdBulkApplyResult bulkApplySchema(
|
||||||
|
String schemaOwner,
|
||||||
|
boolean includeTables,
|
||||||
|
boolean includeViews,
|
||||||
|
String functionKey,
|
||||||
|
String functionOwnerValue,
|
||||||
|
String functionNameValue,
|
||||||
|
String statementTypesValue,
|
||||||
|
boolean enabled,
|
||||||
|
boolean updateCheck,
|
||||||
|
String filterPredicateValue
|
||||||
|
) {
|
||||||
|
String owner = requiredIdentifier(schemaOwner, "Schema");
|
||||||
|
if (!includeTables && !includeViews) {
|
||||||
|
throw new AppException("TABLE 또는 VIEW 중 하나 이상 선택해야 합니다.");
|
||||||
|
}
|
||||||
|
List<VpdSchemaObjectOption> targets = mapper.findSchemaObjects(
|
||||||
|
owner,
|
||||||
|
includeTables ? "Y" : "N",
|
||||||
|
includeViews ? "Y" : "N"
|
||||||
|
);
|
||||||
|
if (targets.isEmpty()) {
|
||||||
|
throw new AppException("선택한 스키마에서 VPD 적용 대상 TABLE/VIEW를 찾을 수 없습니다: " + owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
FunctionRef functionRef = parseFunctionRef(functionKey);
|
||||||
|
String filterPredicate = filterPredicateValue == null ? "" : filterPredicateValue.trim();
|
||||||
|
if (functionRef == null && filterPredicate.isBlank()) {
|
||||||
|
throw new AppException("벌크 적용은 기존 Function을 선택하거나 Filter predicate를 입력해야 합니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
String currentUser = jdbcTemplate.queryForObject("SELECT USER FROM dual", String.class);
|
||||||
|
String functionOwner;
|
||||||
|
String packageName;
|
||||||
|
String functionName;
|
||||||
|
if (functionRef != null) {
|
||||||
|
functionOwner = functionRef.owner();
|
||||||
|
packageName = functionRef.packageName();
|
||||||
|
functionName = functionRef.functionName();
|
||||||
|
} else {
|
||||||
|
functionOwner = functionOwnerValue == null || functionOwnerValue.isBlank()
|
||||||
|
? currentUser
|
||||||
|
: requiredIdentifier(functionOwnerValue, "Function owner");
|
||||||
|
packageName = null;
|
||||||
|
functionName = functionNameValue == null || functionNameValue.isBlank()
|
||||||
|
? generatedFunctionName(owner + "_BULK_POLICY")
|
||||||
|
: requiredIdentifier(functionNameValue, "Function name");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!filterPredicate.isBlank()) {
|
||||||
|
if (currentUser == null || !currentUser.equalsIgnoreCase(functionOwner)) {
|
||||||
|
throw new AppException("필터 함수 자동 생성은 현재 연결 사용자 스키마에만 가능합니다. 현재 사용자: "
|
||||||
|
+ currentUser + ", Function owner: " + functionOwner);
|
||||||
|
}
|
||||||
|
createFilterFunction(functionName, filterPredicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
String statementTypes = normalizeStatementTypes(statementTypesValue);
|
||||||
|
int created = 0;
|
||||||
|
int skipped = 0;
|
||||||
|
int failed = 0;
|
||||||
|
for (VpdSchemaObjectOption target : targets) {
|
||||||
|
String policyName = generatedPolicyName(target.objectName());
|
||||||
|
if (mapper.findAnyPolicy(target.owner(), target.objectName(), policyName) != null) {
|
||||||
|
skipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
addPolicy(
|
||||||
|
target.owner(),
|
||||||
|
target.objectName(),
|
||||||
|
policyName,
|
||||||
|
functionOwner,
|
||||||
|
packageName == null ? functionName : packageName + "." + functionName,
|
||||||
|
statementTypes,
|
||||||
|
enabled,
|
||||||
|
updateCheck
|
||||||
|
);
|
||||||
|
created++;
|
||||||
|
} catch (DataAccessException exception) {
|
||||||
|
failed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new VpdBulkApplyResult(targets.size(), created, skipped, failed);
|
||||||
|
}
|
||||||
|
|
||||||
public VpdFunctionSource findFunctionSource(String owner, String packageName, String functionName) {
|
public VpdFunctionSource findFunctionSource(String owner, String packageName, String functionName) {
|
||||||
String normalizedOwner = requiredIdentifier(owner, "Function owner");
|
String normalizedOwner = requiredIdentifier(owner, "Function owner");
|
||||||
String normalizedFunction = requiredIdentifier(functionName, "Function name");
|
String normalizedFunction = requiredIdentifier(functionName, "Function name");
|
||||||
@@ -106,6 +197,28 @@ public class VpdPolicyService {
|
|||||||
createFilterFunction(functionName, filterPredicate);
|
createFilterFunction(functionName, filterPredicate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addPolicy(
|
||||||
|
objectOwner,
|
||||||
|
objectName,
|
||||||
|
policyName,
|
||||||
|
functionOwner,
|
||||||
|
packageName == null ? functionName : packageName + "." + functionName,
|
||||||
|
statementTypes,
|
||||||
|
command.enabled(),
|
||||||
|
command.updateCheck()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addPolicy(
|
||||||
|
String objectOwner,
|
||||||
|
String objectName,
|
||||||
|
String policyName,
|
||||||
|
String functionOwner,
|
||||||
|
String policyFunction,
|
||||||
|
String statementTypes,
|
||||||
|
boolean enabled,
|
||||||
|
boolean updateCheck
|
||||||
|
) {
|
||||||
jdbcTemplate.update("""
|
jdbcTemplate.update("""
|
||||||
BEGIN
|
BEGIN
|
||||||
DBMS_RLS.ADD_POLICY(
|
DBMS_RLS.ADD_POLICY(
|
||||||
@@ -120,12 +233,12 @@ public class VpdPolicyService {
|
|||||||
policy_type => DBMS_RLS.DYNAMIC
|
policy_type => DBMS_RLS.DYNAMIC
|
||||||
);
|
);
|
||||||
END;
|
END;
|
||||||
""".formatted(command.updateCheck() ? "TRUE" : "FALSE", command.enabled() ? "TRUE" : "FALSE"),
|
""".formatted(updateCheck ? "TRUE" : "FALSE", enabled ? "TRUE" : "FALSE"),
|
||||||
objectOwner,
|
objectOwner,
|
||||||
objectName,
|
objectName,
|
||||||
policyName,
|
policyName,
|
||||||
functionOwner,
|
functionOwner,
|
||||||
packageName == null ? functionName : packageName + "." + functionName,
|
policyFunction,
|
||||||
statementTypes);
|
statementTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,6 +414,11 @@ 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) {
|
||||||
|
String name = objectName + "_POLICY";
|
||||||
|
return name.length() > 128 ? name.substring(0, 128) : 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;
|
||||||
|
|||||||
@@ -80,6 +80,43 @@ public class VpdPolicyController {
|
|||||||
return "redirect:/vpd-policies";
|
return "redirect:/vpd-policies";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/vpd-policies/bulk")
|
||||||
|
public String bulkApplyPolicy(
|
||||||
|
@RequestParam String schemaOwner,
|
||||||
|
@RequestParam(defaultValue = "false") boolean includeTables,
|
||||||
|
@RequestParam(defaultValue = "false") boolean includeViews,
|
||||||
|
@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 {
|
||||||
|
var result = vpdPolicyService.bulkApplySchema(
|
||||||
|
schemaOwner,
|
||||||
|
includeTables,
|
||||||
|
includeViews,
|
||||||
|
functionKey,
|
||||||
|
functionOwner,
|
||||||
|
functionName,
|
||||||
|
String.join(",", statementTypes),
|
||||||
|
enabled,
|
||||||
|
updateCheck,
|
||||||
|
filterPredicate
|
||||||
|
);
|
||||||
|
redirectAttributes.addFlashAttribute("successMessage", result.summary());
|
||||||
|
} catch (AppException exception) {
|
||||||
|
redirectAttributes.addFlashAttribute("errorMessage", exception.getMessage());
|
||||||
|
} catch (DataAccessException exception) {
|
||||||
|
RuntimeErrorMessage message = RuntimeErrorMessages.dataAccess(exception);
|
||||||
|
redirectAttributes.addFlashAttribute("errorMessage", message.message());
|
||||||
|
}
|
||||||
|
return "redirect:/vpd-policies";
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/vpd-policies/function-source")
|
@GetMapping("/vpd-policies/function-source")
|
||||||
public String functionSource(
|
public String functionSource(
|
||||||
@RequestParam String owner,
|
@RequestParam String owner,
|
||||||
|
|||||||
@@ -56,6 +56,20 @@
|
|||||||
ORDER BY 1
|
ORDER BY 1
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="findSchemaOwnerOptions" resultType="string">
|
||||||
|
SELECT DISTINCT o.owner
|
||||||
|
FROM all_objects o
|
||||||
|
JOIN all_users u ON u.username = o.owner
|
||||||
|
WHERE o.object_type IN ('TABLE', 'VIEW')
|
||||||
|
AND NVL(u.oracle_maintained, 'N') = 'N'
|
||||||
|
AND o.owner NOT IN ('SYS', 'SYSTEM', 'ORDS_METADATA', 'ORDS_PUBLIC_USER')
|
||||||
|
AND o.owner NOT LIKE 'APEX\_%' ESCAPE '\'
|
||||||
|
AND o.owner NOT LIKE 'C##%'
|
||||||
|
AND o.object_name NOT LIKE 'BIN$%'
|
||||||
|
ORDER BY o.owner
|
||||||
|
FETCH FIRST 200 ROWS ONLY
|
||||||
|
</select>
|
||||||
|
|
||||||
<select id="findOwnerOptions" resultType="string">
|
<select id="findOwnerOptions" resultType="string">
|
||||||
SELECT USER FROM dual
|
SELECT USER FROM dual
|
||||||
UNION
|
UNION
|
||||||
@@ -127,6 +141,18 @@
|
|||||||
FETCH FIRST 300 ROWS ONLY
|
FETCH FIRST 300 ROWS ONLY
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="findSchemaObjects" resultType="com.cloudhandson.vpdbackoffice.domain.vpd.VpdSchemaObjectOption">
|
||||||
|
SELECT owner, object_name, object_type
|
||||||
|
FROM all_objects
|
||||||
|
WHERE owner = UPPER(#{owner,jdbcType=VARCHAR})
|
||||||
|
AND object_name NOT LIKE 'BIN$%'
|
||||||
|
AND (
|
||||||
|
(#{includeTablesYn,jdbcType=VARCHAR} = 'Y' AND object_type = 'TABLE')
|
||||||
|
OR (#{includeViewsYn,jdbcType=VARCHAR} = 'Y' AND object_type = 'VIEW')
|
||||||
|
)
|
||||||
|
ORDER BY object_type, object_name
|
||||||
|
</select>
|
||||||
|
|
||||||
<select id="findPolicy" resultType="com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyView">
|
<select id="findPolicy" resultType="com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyView">
|
||||||
SELECT <include refid="policyColumns"><property name="alias" value="p."/></include>
|
SELECT <include refid="policyColumns"><property name="alias" value="p."/></include>
|
||||||
FROM all_policies p
|
FROM all_policies p
|
||||||
@@ -142,6 +168,14 @@
|
|||||||
)
|
)
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="findAnyPolicy" resultType="com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyView">
|
||||||
|
SELECT <include refid="policyColumns"><property name="alias" value="p."/></include>
|
||||||
|
FROM all_policies p
|
||||||
|
WHERE p.object_owner = UPPER(#{objectOwner,jdbcType=VARCHAR})
|
||||||
|
AND p.object_name = UPPER(#{objectName,jdbcType=VARCHAR})
|
||||||
|
AND p.policy_name = UPPER(#{policyName,jdbcType=VARCHAR})
|
||||||
|
</select>
|
||||||
|
|
||||||
<select id="findFunctionSource" resultType="string">
|
<select id="findFunctionSource" resultType="string">
|
||||||
SELECT LISTAGG(text, '') WITHIN GROUP (ORDER BY line)
|
SELECT LISTAGG(text, '') WITHIN GROUP (ORDER BY line)
|
||||||
FROM all_source
|
FROM all_source
|
||||||
|
|||||||
@@ -117,6 +117,107 @@
|
|||||||
</form>
|
</form>
|
||||||
</section>
|
</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 class="content-band">
|
<section class="content-band">
|
||||||
<div class="section-heading">
|
<div class="section-heading">
|
||||||
<h2>VPD Policies</h2>
|
<h2>VPD Policies</h2>
|
||||||
|
|||||||
Reference in New Issue
Block a user