<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('행 접근 규칙')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container py-4">
  <div class="page-title">
    <h1>행 접근 규칙</h1>
    <details class="explanation-details">
      <summary>도움말</summary>
      <p>이 화면은 <strong>행 접근 규칙</strong>만 저장합니다. 저장값은 최종 SQL이 아니라 <code>CB_AGENT_DOC_VPD_FILTER</code>가 읽어서 대상 테이블의 WHERE predicate로 바꾸는 매핑 데이터입니다.</p>
      <p>컬럼 원문/마스킹은 이 화면에서 처리하지 않습니다. 게임 사용자 식별자, 결제금액 같은 민감 컬럼 표시는 <a href="/masking-rules">컬럼 마스킹</a>의 ASO/Data Redaction 정책에서 관리합니다.</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>

  <section class="content-band">
    <div class="section-heading">
      <div>
        <h2>새 행 접근 규칙</h2>
        <p class="section-description">저장 전 적용 대상과 행 범위를 검토한 뒤 행 접근 규칙을 반영합니다.</p>
      </div>
      <div class="wizard-progress" aria-label="권한 추가 단계">
        <button class="wizard-step-indicator active" type="button" data-wizard-target="1">역할</button>
        <button class="wizard-step-indicator" type="button" data-wizard-target="2">대상</button>
        <button class="wizard-step-indicator" type="button" data-wizard-target="3">접근 범위</button>
        <button class="wizard-step-indicator" type="button" data-wizard-target="4">검토</button>
      </div>
    </div>
    <form method="post" action="/permissions" class="permission-wizard" data-permission-wizard>
      <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
      <div class="wizard-validation" data-wizard-validation role="alert" hidden></div>
      <aside class="wizard-summary" aria-live="polite">
        <div>
          <span>역할</span>
          <strong data-wizard-summary="role">선택 전</strong>
        </div>
        <div>
          <span>객체</span>
          <strong data-wizard-summary="object">선택 전</strong>
        </div>
        <div>
          <span>효과</span>
          <strong data-wizard-summary="effect">ALLOW</strong>
        </div>
        <div>
          <span>행 규칙</span>
          <strong data-wizard-summary="rules">ALL</strong>
        </div>
        <div>
          <span>적용 대상</span>
          <strong data-wizard-summary="affected">역할 선택 후 확인</strong>
        </div>
        <div>
          <span>저장 상태</span>
          <strong data-wizard-summary="readiness">역할과 객체를 선택하세요</strong>
        </div>
      </aside>

      <div class="wizard-panel active" data-wizard-step="1">
        <div class="wizard-panel-heading">
          <div>
            <h3>역할 선택</h3>
            <p>권한을 부여하거나 거부할 역할을 선택합니다.</p>
          </div>
        </div>
        <label>
          역할
          <select class="form-select" name="roleId" required>
            <option value="" selected disabled>권한을 적용할 역할 선택</option>
            <option th:each="role : ${roles}"
                    th:value="${role.roleId()}"
                    th:text="${role.roleName()}"
                    th:attr="data-max-sensitivity=${role.maxSensitivityLevel()},
                             data-direct-users=${#strings.listJoin(directUsersByRole[role.roleId()], '|')},
                             data-groups=${#strings.listJoin(groupsByRole[role.roleId()], '|')},
                             data-group-users=${#strings.listJoin(groupUsersByRole[role.roleId()], '|')}"></option>
          </select>
        </label>
        <details class="explanation-details">
          <summary>역할 선택 기준 보기</summary>
          <p class="wizard-hint">선택한 역할의 표시 보호 등급 상한(참고)도 preview에 반영됩니다. 행을 볼 수 있는 권한과는 별개입니다.</p>
        </details>
        <div class="effective-preview">
          <div class="field-block-title">선택 역할 영향도</div>
          <dl>
            <div><dt>직접 사용자</dt><dd data-preview="directUsers">-</dd></div>
            <div><dt>역할 보유 그룹</dt><dd data-preview="roleGroups">-</dd></div>
            <div><dt>그룹 상속 사용자</dt><dd data-preview="groupUsers">-</dd></div>
          </dl>
        </div>
      </div>

      <div class="wizard-panel" data-wizard-step="2">
        <div class="wizard-panel-heading">
          <div>
            <h3>보호 객체 선택</h3>
            <p>등록된 보호 객체 또는 DB 스키마 객체를 선택합니다.</p>
          </div>
        </div>
        <label>
          보호 객체
          <select class="form-select" name="objectRef" required>
            <option value="" selected disabled>권한을 적용할 보호 객체 선택</option>
            <optgroup label="등록된 보호 객체">
              <option th:each="object : ${objects}"
                      th:value="${'protected:' + object.objectId()}"
                      th:attr="data-object-display=${object.displayName()},
                               data-columns=${#strings.listJoin(columnsByObject[object.objectId()], ',')},
                               data-maskable-columns=${#strings.listJoin(maskableColumnsByObject[object.objectId()], ',')},
                               data-maskable-labels=${#strings.listJoin(maskableColumnLabelsByObject[object.objectId()], '|')}"
                      th:text="${object.displayName()}"></option>
            </optgroup>
            <optgroup label="DB 스키마 객체">
              <option th:each="dbObject : ${dbObjects}"
                      th:value="${'db:' + dbObject.value()}"
                      th:attr="data-object-display=${dbObject.value()}"
                      th:text="${dbObject.label()}"></option>
            </optgroup>
          </select>
        </label>
        <details class="explanation-details">
          <summary>객체 선택 기준 보기</summary>
          <p class="wizard-hint">DB 스키마 객체를 선택하면 저장 시 보호 객체가 자동 등록됩니다.</p>
        </details>
        <div class="effective-preview">
          <div class="field-block-title">선택 객체 컬럼 (표시 보호 참고)</div>
          <dl>
            <div><dt>전체 컬럼</dt><dd data-preview="objectColumns">-</dd></div>
            <div><dt>표시 보호 대상</dt><dd data-preview="maskableColumns">-</dd></div>
          </dl>
        </div>
      </div>

      <div class="wizard-panel" data-wizard-step="3">
        <div class="wizard-panel-heading">
          <div>
            <h3>권한 효과와 행 규칙</h3>
            <p>허용/거부 방향과 행 필터 조건을 설정합니다.</p>
          </div>
        </div>
        <label>
          권한 효과
          <select class="form-select" name="permissionEffect" required>
            <option value="ALLOW">허용</option>
            <option value="DENY">거부</option>
          </select>
        </label>
        <div>
          <div class="field-block-title">행 규칙</div>
          <p class="rule-safety-note"><strong>주의:</strong> <code>ALL</code>은 전체 행을 뜻합니다. <code>ALL</code>과 다른 조건 규칙은 한 권한에 함께 저장할 수 없습니다.</p>
          <div id="rowRuleList" class="rule-list">
            <div class="rule-row">
              <select class="form-select rule-column-select" name="ruleColumn">
                <option value="">객체 컬럼 선택</option>
              </select>
              <select class="form-select rule-type-select" name="ruleType">
                <option value="ALL">ALL</option>
                <optgroup label="게임 데이터 · 담당자 범위">
                  <option value="OWN_CONTRACT">담당 게임 서비스</option>
                  <option value="OWN_CUSTOMER">담당 게임 사용자</option>
                </optgroup>
                <optgroup label="게임 데이터 · 채널 범위">
                  <option value="CHANNEL_CONTRACT">토큰 채널 게임 서비스</option>
                  <option value="CHANNEL_CUSTOMER">토큰 채널 게임 사용자</option>
                </optgroup>
                <optgroup label="게임 데이터 · 토큰 식별">
                  <option value="TOKEN_SUBJECT">토큰 사용자 본인</option>
                </optgroup>
                <optgroup label="SQL · 정적 조건">
                  <option value="STATIC_SQL">정적 SQL 조건식</option>
                </optgroup>
              </select>
              <input class="form-control" name="ruleValue" maxlength="1000" placeholder="비교 값">
              <button class="btn btn-outline-secondary" type="button" data-rule-add>추가</button>
            </div>
          </div>
          <details class="explanation-details">
            <summary>행 규칙의 두 가지 적용 방식 보기</summary>
            <p class="wizard-hint"><strong>조건 코드</strong>는 토큰 context와 DB에 등록된 업무 관계를 바탕으로 행 접근 함수가 해석합니다. 관계가 없는 게임 데이터 객체에는 조건 코드를 억지로 적용하지 말고, <strong>정적 SQL 조건식</strong>으로 <code>GAME_ID = 'CZN'</code>처럼 실제 컬럼을 사용하세요. 한 권한 안의 규칙은 모두 AND로 좁혀지고, 서로 다른 역할의 ALLOW 권한은 OR로 합쳐집니다.</p>
            <p class="wizard-hint"><strong>컬럼 원문/마스킹은 제외했습니다.</strong> 행 접근 필터는 행만 남기고, ASO/Data Redaction이 허용된 행 안에서 컬럼을 원문 또는 마스킹으로 반환합니다.</p>
          </details>
          <details class="explanation-details">
            <summary>조건 코드가 실제 VPD WHERE 조각으로 바뀌는 방식</summary>
            <p class="wizard-hint">이 표는 저장 규칙이 <code>CB_AGENT_DOC_VPD_FILTER</code>에서 어떤 predicate로 변환되는지 설명하기 위한 예시입니다. 실제 컬럼 존재 여부와 대상 객체는 DB에서 다시 검증됩니다.</p>
            <div class="table-responsive">
              <table class="table table-sm align-middle">
                <thead><tr><th>저장 조건 코드</th><th>업무 의미</th><th>VPD filter가 만드는 조건</th></tr></thead>
                <tbody>
                <tr>
                  <td><code>ALL</code></td>
                  <td>해당 객체의 전체 행 허용</td>
                  <td><code>1 = 1</code></td>
                </tr>
                <tr>
                  <td><code>TOKEN_SUBJECT</code></td>
                  <td>토큰으로 식별된 운영 사용자 범위</td>
                  <td>대상 객체가 토큰 주체 식별 컬럼을 가질 때만 DB 행 접근 함수가 생성</td>
                </tr>
                <tr>
                  <td><code>OWN_CONTRACT</code></td>
                  <td>토큰 사용자가 담당하는 게임 서비스 범위</td>
                  <td>등록된 업무 관계가 있을 때만 DB 행 접근 함수가 생성</td>
                </tr>
                <tr>
                  <td><code>CHANNEL_CONTRACT</code></td>
                  <td>토큰 채널에 속한 게임 서비스 범위</td>
                  <td>등록된 업무 관계가 있을 때만 DB 행 접근 함수가 생성</td>
                </tr>
                <tr>
                  <td><code>OWN_CUSTOMER</code> / <code>CHANNEL_CUSTOMER</code></td>
                  <td>게임 서비스·사용자 기준으로 연결되는 게임 로그·판매·환불 데이터 범위</td>
                  <td>대상 테이블의 실제 키 관계를 DB 행 접근 함수가 검증한 뒤 생성</td>
                </tr>
                <tr>
                  <td><code>STATIC_SQL</code></td>
                  <td>현재 객체 컬럼으로 표현한 고정 조건</td>
                  <td><code>GAME_ID = 'CZN'</code>처럼 검증된 현재 객체 컬럼 조건</td>
                </tr>
                </tbody>
              </table>
            </div>
          </details>
        </div>
      </div>

      <div class="wizard-panel" data-wizard-step="4">
        <div class="wizard-panel-heading">
          <div>
            <h3>저장 전 검토</h3>
            <p>저장될 행 접근 규칙과 VPD predicate 변환 예시를 확인합니다. 컬럼 마스킹은 ASO 화면에서 별도로 설정합니다.</p>
          </div>
        </div>
        <div class="policy-preview" data-policy-preview>
          <dl>
            <div><dt>역할</dt><dd data-preview="role">-</dd></div>
            <div><dt>표시 보호 등급 상한(참고)</dt><dd data-preview="sensitivity">-</dd></div>
            <div><dt>보호 객체</dt><dd data-preview="object">-</dd></div>
            <div><dt>적용 대상</dt><dd data-preview="affectedPrincipals">-</dd></div>
            <div><dt>객체 컬럼</dt><dd data-preview="objectColumnsFinal">-</dd></div>
            <div><dt>행 접근 업무 의미</dt><dd data-preview="rowPolicy">-</dd></div>
            <div><dt>VPD predicate 예상</dt><dd data-preview="predicatePreview">-</dd></div>
            <div><dt>컬럼 마스킹</dt><dd data-preview="columnPolicy">-</dd></div>
            <div class="policy-preview-emphasis"><dt>저장 영향</dt><dd data-preview="saveGuard">-</dd></div>
            <div><dt>되돌리기</dt><dd>저장 후 아래 권한 목록에서 삭제할 수 있습니다. 이 객체의 마지막 권한을 삭제하면 보호 객체가 비활성화될 수 있습니다.</dd></div>
          </dl>
        </div>
      </div>

      <div class="wizard-actions">
        <button class="btn btn-outline-secondary" type="button" data-wizard-prev disabled>이전</button>
        <button class="btn btn-outline-primary" type="button" data-wizard-next>다음</button>
        <button class="btn btn-primary" type="submit" data-wizard-submit hidden>저장</button>
      </div>
    </form>
  </section>

  <section class="content-band">
    <h2>행 접근 규칙 목록</h2>
    <p class="section-subtitle">업무 의미를 먼저 보고, 세부 저장값과 VPD predicate 변환 예시는 행의 상세에서 확인하세요. 컬럼 원문/마스킹은 컬럼 마스킹에서 관리합니다.</p>
    <div class="table-responsive">
      <table class="table table-sm align-middle permission-list-table">
        <thead>
        <tr>
          <th>역할</th>
          <th>보호 대상</th>
          <th>효과</th>
          <th>업무 접근 범위</th>
          <th>컬럼 마스킹</th>
          <th>관리</th>
        </tr>
        </thead>
        <tbody>
        <tr th:each="permission : ${permissions}"
            th:with="lastPermission=${lastPermissionByPermissionId[permission.permissionId()]}">
          <td th:text="${permission.roleName()}">HR_DEPT_ROLE</td>
          <td th:text="${permission.objectName()}">CB_V_SEARCH_DOCUMENTS</td>
          <td>
            <span class="badge"
                  th:classappend="${permission.permissionEffect() == 'DENY'} ? ' text-bg-danger' : ' text-bg-success'"
                  th:text="${permission.permissionEffect()}">ALLOW</span>
          </td>
          <td><pre class="table-pre" th:text="${permission.businessRuleSummary()}">전체 행</pre></td>
          <td>
            <span class="badge text-bg-light">ASO 화면에서 관리</span>
            <div class="text-muted small" th:text="${permission.columnControlSummary()}">컬럼 원문/마스킹은 컬럼 마스킹에서 관리</div>
          </td>
          <td>
            <details class="row-management">
              <summary>관리</summary>
              <div class="delete-impact">
                <span>저장 규칙</span>
                <pre class="table-pre mt-1" th:text="${permission.storedRuleSummary()}">CUST_ID OWN_CUSTOMER</pre>
                <span>VPD predicate 변환 예시</span>
                <pre class="table-pre mt-1" th:text="${permission.vpdMappingSummary()}">EXISTS (...)</pre>
              </div>
              <div class="delete-impact">
                <span>이 역할은 이 대상의 SELECT 권한을 잃습니다.</span>
                <strong th:text="${permission.roleName() + ' → ' + permission.objectName()}">ROLE → OBJECT</strong>
                <small th:if="${lastPermission}" class="text-danger">마지막 권한입니다. 삭제 후 보호 대상이 비활성화될 수 있습니다.</small>
                <small th:unless="${lastPermission}">같은 대상에 다른 역할 권한이 남아 있습니다.</small>
              </div>
              <form method="post" action="/permissions/delete" class="inline-form mt-2">
                <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
                <input type="hidden" name="permissionId" th:value="${permission.permissionId()}">
                <label class="delete-confirm">
                  <input type="checkbox" name="confirmImpact" value="true" required>
                  영향 확인
                </label>
                <button class="btn btn-sm btn-outline-danger" type="submit">삭제</button>
              </form>
            </details>
          </td>
        </tr>
        <tr th:if="${#lists.isEmpty(permissions)}">
          <td colspan="6" class="text-muted">등록된 행 접근 규칙이 없습니다.</td>
        </tr>
        </tbody>
      </table>
    </div>
  </section>
</main>
</body>
</html>
