58
src/main/resources/templates/dashboard.html
Normal file
58
src/main/resources/templates/dashboard.html
Normal file
@@ -0,0 +1,58 @@
|
||||
<!doctype html>
|
||||
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
|
||||
<head th:replace="~{fragments/layout :: head('VPD 백오피스')}"></head>
|
||||
<body>
|
||||
<nav th:replace="~{fragments/layout :: nav}"></nav>
|
||||
<main class="container py-4">
|
||||
<div class="page-title">
|
||||
<h1>VPD 권한 백오피스</h1>
|
||||
<p>보호 객체, Bearer Token, ORDS 검증 상태를 확인합니다.</p>
|
||||
</div>
|
||||
|
||||
<section class="summary-grid">
|
||||
<a class="summary-tile" href="/permissions">
|
||||
<span class="label">보호 객체</span>
|
||||
<strong th:text="${#lists.size(objects)}">0</strong>
|
||||
</a>
|
||||
<a class="summary-tile" href="/permissions">
|
||||
<span class="label">역할</span>
|
||||
<strong th:text="${#lists.size(roles)}">0</strong>
|
||||
</a>
|
||||
<a class="summary-tile" href="/tokens">
|
||||
<span class="label">토큰</span>
|
||||
<strong th:text="${#lists.size(tokens)}">0</strong>
|
||||
</a>
|
||||
</section>
|
||||
|
||||
<section class="content-band">
|
||||
<div class="section-heading">
|
||||
<h2>최근 보호 객체</h2>
|
||||
<a class="btn btn-sm btn-outline-primary" href="/probe">검증 실행</a>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Owner</th>
|
||||
<th>Object</th>
|
||||
<th>ORDS Path</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="object : ${objects}">
|
||||
<td th:text="${object.owner()}">ADMIN</td>
|
||||
<td th:text="${object.objectName()}">CB_V_SEARCH_DOCUMENTS</td>
|
||||
<td><code th:text="${object.ordsPath()}">search/documents</code></td>
|
||||
<td><span class="badge text-bg-success">enabled</span></td>
|
||||
</tr>
|
||||
<tr th:if="${#lists.isEmpty(objects)}">
|
||||
<td colspan="4" class="text-muted">등록된 보호 객체가 없습니다.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
14
src/main/resources/templates/error.html
Normal file
14
src/main/resources/templates/error.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<!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="alert alert-danger">
|
||||
<strong>처리할 수 없습니다.</strong>
|
||||
<span th:text="${errorMessage} ?: '요청 처리 중 오류가 발생했습니다.'"></span>
|
||||
</div>
|
||||
<a class="btn btn-outline-primary" href="/">대시보드</a>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
29
src/main/resources/templates/fragments/layout.html
Normal file
29
src/main/resources/templates/fragments/layout.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<!doctype html>
|
||||
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
|
||||
<head th:fragment="head(title)">
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title th:text="${title}">VPD 백오피스</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="/css/app.css" rel="stylesheet">
|
||||
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
||||
<script defer src="https://unpkg.com/alpinejs@3.14.8/dist/cdn.min.js"></script>
|
||||
<script defer src="/js/app.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav th:fragment="nav" class="navbar navbar-expand-lg bg-body border-bottom">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/">VPD Backoffice</a>
|
||||
<div class="navbar-nav">
|
||||
<a class="nav-link" href="/permissions">권한</a>
|
||||
<a class="nav-link" href="/tokens">토큰</a>
|
||||
<a class="nav-link" href="/probe">ORDS 검증</a>
|
||||
</div>
|
||||
<form method="post" action="/logout" class="ms-auto">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||
<button class="btn btn-sm btn-outline-secondary" type="submit">로그아웃</button>
|
||||
</form>
|
||||
</div>
|
||||
</nav>
|
||||
</body>
|
||||
</html>
|
||||
39
src/main/resources/templates/fragments/probe-result.html
Normal file
39
src/main/resources/templates/fragments/probe-result.html
Normal file
@@ -0,0 +1,39 @@
|
||||
<!doctype html>
|
||||
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
|
||||
<body>
|
||||
<div th:fragment="result">
|
||||
<div class="section-heading">
|
||||
<h2>검증 결과</h2>
|
||||
<span class="badge" th:classappend="${result.status().name() == 'SUCCESS'} ? ' text-bg-success' : ' text-bg-warning'"
|
||||
th:text="${result.status()}">SUCCESS</span>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning" th:if="${result.errorCode()}">
|
||||
<strong th:text="${result.errorCode()}">ERROR</strong>
|
||||
<span th:text="${result.errorMessage()}">message</span>
|
||||
</div>
|
||||
|
||||
<div class="meta-row">
|
||||
<span>Rows: <strong th:text="${result.rowCount()}">0</strong></span>
|
||||
<span th:if="${!#lists.isEmpty(result.maskedColumns())}">
|
||||
Masked: <code th:text="${#strings.listJoin(result.maskedColumns(), ', ')}"></code>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive" th:if="${!#lists.isEmpty(result.rows())}">
|
||||
<table class="table table-sm table-striped align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th th:each="column : ${result.columns()}" th:text="${column}">column</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="row : ${result.rows()}">
|
||||
<td th:each="column : ${result.columns()}" th:text="${row[column]}">value</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
47
src/main/resources/templates/permissions.html
Normal file
47
src/main/resources/templates/permissions.html
Normal file
@@ -0,0 +1,47 @@
|
||||
<!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>
|
||||
<p>역할별 보호 객체 접근과 행 규칙을 저장합니다.</p>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-success" th:if="${message}" th:text="${message}"></div>
|
||||
|
||||
<section class="content-band">
|
||||
<form method="post" action="/permissions" class="form-grid">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||
<label>
|
||||
역할
|
||||
<select class="form-select" name="roleId" required>
|
||||
<option th:each="role : ${roles}" th:value="${role.roleId()}" th:text="${role.roleName()}"></option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
보호 객체
|
||||
<select class="form-select" name="objectId" required>
|
||||
<option th:each="object : ${objects}" th:value="${object.objectId()}" th:text="${object.displayName()}"></option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
행 규칙
|
||||
<select class="form-select" name="ruleType">
|
||||
<option value="ALL">ALL</option>
|
||||
<option value="REGION">REGION</option>
|
||||
<option value="MY_DEPT">MY_DEPT</option>
|
||||
<option value="SELF">SELF</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
규칙 값
|
||||
<input class="form-control" name="ruleValue" placeholder="APAC 또는 HR">
|
||||
</label>
|
||||
<button class="btn btn-primary" type="submit">저장</button>
|
||||
</form>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
44
src/main/resources/templates/probe.html
Normal file
44
src/main/resources/templates/probe.html
Normal file
@@ -0,0 +1,44 @@
|
||||
<!doctype html>
|
||||
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
|
||||
<head th:replace="~{fragments/layout :: head('ORDS 검증')}"></head>
|
||||
<body>
|
||||
<nav th:replace="~{fragments/layout :: nav}"></nav>
|
||||
<main class="container py-4">
|
||||
<div class="page-title">
|
||||
<h1>ORDS 검증</h1>
|
||||
<p>Bearer Token과 보호 객체를 선택해 VPD/Redaction 적용 결과를 확인합니다.</p>
|
||||
</div>
|
||||
|
||||
<section class="content-band">
|
||||
<form hx-post="/probe" hx-target="#probe-result" hx-swap="innerHTML" class="form-grid">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||
<label>
|
||||
Token ID
|
||||
<select class="form-select" name="keyId" required>
|
||||
<option th:each="token : ${tokens}" th:value="${token.keyId()}" th:text="${token.keyPrefix()}"></option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Bearer Token 원문
|
||||
<input class="form-control" name="bearerToken" type="password" autocomplete="off" required>
|
||||
</label>
|
||||
<label>
|
||||
보호 객체
|
||||
<select class="form-select" name="objectId" required>
|
||||
<option th:each="object : ${objects}" th:value="${object.objectId()}" th:text="${object.displayName()}"></option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Limit
|
||||
<input class="form-control" name="limit" type="number" min="1" max="500" value="50">
|
||||
</label>
|
||||
<button class="btn btn-primary" type="submit">호출</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section id="probe-result" class="content-band">
|
||||
<div class="text-muted">검증 결과가 여기에 표시됩니다.</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
79
src/main/resources/templates/tokens.html
Normal file
79
src/main/resources/templates/tokens.html
Normal file
@@ -0,0 +1,79 @@
|
||||
<!doctype html>
|
||||
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
|
||||
<head th:replace="~{fragments/layout :: head('Bearer Token')}"></head>
|
||||
<body>
|
||||
<nav th:replace="~{fragments/layout :: nav}"></nav>
|
||||
<main class="container py-4">
|
||||
<div class="page-title">
|
||||
<h1>Bearer Token</h1>
|
||||
<p>토큰은 발급 직후 한 번만 원문을 표시합니다.</p>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-success" th:if="${message}" th:text="${message}"></div>
|
||||
<div class="alert alert-warning" th:if="${issued}">
|
||||
<div class="fw-semibold">새 토큰이 발급되었습니다. 이 값은 다시 표시되지 않습니다.</div>
|
||||
<code class="token-value" th:text="${issued.plainToken()}"></code>
|
||||
</div>
|
||||
|
||||
<section class="content-band">
|
||||
<h2>토큰 발급</h2>
|
||||
<form method="post" action="/tokens" class="form-grid">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||
<label>
|
||||
사용자
|
||||
<select class="form-select" name="userId" required>
|
||||
<option th:each="user : ${users}" th:value="${user.userId()}" th:text="${user.username()}"></option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
만료 시각
|
||||
<input class="form-control" name="expiresAt" placeholder="2026-12-31T23:59:59+09:00" required>
|
||||
</label>
|
||||
<label>
|
||||
설명
|
||||
<input class="form-control" name="description" maxlength="200">
|
||||
</label>
|
||||
<button class="btn btn-primary" type="submit">발급</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="content-band">
|
||||
<h2>토큰 목록</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>User</th>
|
||||
<th>Prefix</th>
|
||||
<th>Expires</th>
|
||||
<th>Revoked</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="token : ${tokens}">
|
||||
<td th:text="${token.keyId()}">1</td>
|
||||
<td th:text="${token.username()}">user</td>
|
||||
<td><code th:text="${token.keyPrefix()}">vpd_live_x</code></td>
|
||||
<td th:text="${token.expiresAt()}">2026</td>
|
||||
<td th:text="${token.revokedAt()} ?: '-'">-</td>
|
||||
<td>
|
||||
<form method="post" action="/tokens/revoke" class="inline-form">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||
<input type="hidden" name="keyId" th:value="${token.keyId()}">
|
||||
<input type="hidden" name="reason" value="manual">
|
||||
<button class="btn btn-sm btn-outline-danger" type="submit" th:disabled="${token.revokedAt() != null}">회수</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<tr th:if="${#lists.isEmpty(tokens)}">
|
||||
<td colspan="6" class="text-muted">등록된 토큰이 없습니다.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user