비밀번호 초기화 페이지

This commit is contained in:
현성필
2024-05-27 11:15:45 +09:00
parent 9b53b9272c
commit c79b4c5535
13 changed files with 772 additions and 153 deletions
@@ -0,0 +1,86 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/base_layout}">
<body>
<section layout:fragment="contentFragment">
<div class="container">
<div class="mt-2">
<h3 class="text-center">비밀번호 초기화</h3>
</div>
<div class="container-md container-sm d-flex flex-column flex-wrap align-items-center justify-content-center">
<div class="card col-lg-4 col-md-8">
<div class="card-body">
<form name="passwordResetRequest" id="passwordResetRequest" role="form" th:action="@{/forgot_password.do}" method="post">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
<div class="form-floating">
<input id="email" type="text" class="form-control mb-3" name="email" maxlength="30" required/>
<label class="form-label" for="email">User ID</label>
</div>
</form>
<div th:if="${param.expired != null}">
<p>비밀번호 초기화 링크가 만료되었습니다.</p>
</div>
<div th:if="${#strings.isEmpty(error) == false}">
<p th:text="${error}"></p>
</div>
</div>
<div class="card-footer">
<div class="d-flex flex-wrap align-items-center justify-content-center">
<button type="button" class="btn btn-primary btn_reset">초기화 요청</button>
</div>
</div>
</div>
<form th:if="${param.success != null}" class="col-lg-4 col-md-8" name="resetPassword" role="form" th:action="@{/reset_password.do}" method="get">
<div class="card mt-2 ">
<div class="card-body">
<div class="form-floating">
<input id="resetToken" type="text" class="form-control mb-3" name="token" placeholder="Reset Token" required/>
<label class="form-label">Token</label>
</div>
</div>
<div class="card-footer">
<div class="d-flex flex-wrap align-items-center justify-content-center">
<button type="submit" class="btn btn-primary">초기화 페이지 열기</button>
</div>
</div>
</div>
</form>
</div>
<div class="mt-2 text-center">
<span class="text-muted"><a th:href="@{/login}">로그인</a></span>
</div>
</div>
</section>
</body>
<th:block layout:fragment="contentScript">
<script>
$(function() {
document.getElementById('passwordResetRequest').email.focus();
let btnReset = document.querySelector('.btn_reset');
btnReset.addEventListener('click', function(event) {
let form = document.getElementById('passwordResetRequest');
if (!form.checkValidity()) {
event.preventDefault();
event.stopPropagation();
} else {
form.submit();
}
form.classList.add('was-validated');
});
});
</script>
</th:block>
</html>
@@ -0,0 +1,80 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/base_layout}">
<body>
<section layout:fragment="contentFragment">
<div class="container">
<div class="mt-2">
<h3 class="text-center">비밀번호 초기화</h3>
</div>
<div class="container-md container-sm d-flex flex-wrap align-items-center justify-content-center">
<div class="card col-lg-4 col-md-8">
<div class="card-body">
<form name="passwordResetRequest" id="passwordResetRequest" role="form" th:action="@{/reset_password.do}" method="post" th:object="${passwordReset}">
<input type="hidden" name="token" th:value="${tokenValue}">
<div class="form-floating">
<input class="form-control mb-3" th:classappend="${#fields.hasErrors('password')}? 'is-invalid'" name="password" id="password" th:field="*{password}" type="password"
size="20" value="" maxlength="100" required>
<label for="password" class="must" th:text="#{comUssUmt.userManagePasswordUpdt.pass}"></label>
<th:block th:each="err : ${#fields.errors('password')}">
<div th:text="${err}" class="invalid-feedback"></div>
</th:block>
</div>
<!-- 비밀번호확인 -->
<div class="form-floating">
<input class="form-control mb-3" th:classappend="${#fields.hasErrors('password2')}? 'is-invalid'" name="password2" id="password2" th:field="*{password2}"
type="password" size="20" value="" maxlength="100" required>
<label for="password2" class="must" th:text="#{comUssUmt.userManagePasswordUpdt.passConfirm}"></label>
<th:block th:each="err : ${#fields.errors('password2')}">
<div th:text="${err}" class="invalid-feedback"></div>
</th:block>
</div>
</form>
<div th:if="${param.success != null}">
<p>비밀번호 초기화 되었습니다.</p>
</div>
</div>
<div class="card-footer">
<div class="d-flex flex-wrap align-items-center justify-content-center">
<button type="button" class="btn btn-primary btn_reset">저장</button>
</div>
</div>
</div>
</div>
<div class="mt-2 text-center">
<span class="text-muted"><a th:href="@{/login}">로그인</a></span>
</div>
</div>
</section>
</body>
<th:block layout:fragment="contentScript">
<script>
$(function() {
document.getElementById('passwordResetRequest').password.focus();
let btnReset = document.querySelector('.btn_reset');
btnReset.addEventListener('click', function(event) {
let form = document.getElementById('passwordResetRequest');
if (!form.checkValidity()) {
event.preventDefault();
event.stopPropagation();
} else {
form.submit();
}
form.classList.add('was-validated');
});
});
</script>
</th:block>
</html>
@@ -0,0 +1,90 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/default_layout}">
<body>
<section layout:fragment="contentFragment">
<div class="container-fluid">
<div class="card mt-2">
<div class="card-header">
<h3>사용자 비밀 번호 변경</h3>
</div>
<div class="card-body">
<form name="passwordChangeRequest" id="passwordChangeRequest" class="form-horizontal form-horizontal_l" method="post" th:action="@{/mgmt/users/update_staff_password.do}"
th:object="${passwordChangeRequest}">
<!-- 사용자 이름 -->
<input type="hidden" th:field="*{id}">
<div class="form-floating">
<input class="form-control mb-3" name="username" id="username" th:field="*{username}" type="text" size="20" value="" maxlength="100" readonly>
<label for="username" class="must">이름</label>
</div>
<!-- 비밀번호 -->
<div class="form-floating">
<input class="form-control mb-3" th:classappend="${#fields.hasErrors('newPassword')}? 'is-invalid'" name="newPassword" id="newPassword" th:field="*{newPassword}" type="password"
size="20" value="" maxlength="100" required>
<label for="newPassword" class="must" th:text="#{comUssUmt.userManagePasswordUpdt.pass}"></label>
<th:block th:each="err : ${#fields.errors('newPassword')}">
<div th:text="${err}" class="invalid-feedback"></div>
</th:block>
</div>
<!-- 비밀번호확인 -->
<div class="form-floating">
<input class="form-control mb-3" th:classappend="${#fields.hasErrors('newPassword2')}? 'is-invalid'" name="newPassword2" id="newPassword2" th:field="*{newPassword2}"
type="password" size="20" value="" maxlength="100" required>
<label for="newPassword2" class="must" th:text="#{comUssUmt.userManagePasswordUpdt.passConfirm}"></label>
<th:block th:each="err : ${#fields.errors('newPassword2')}">
<div th:text="${err}" class="invalid-feedback"></div>
</th:block>
</div>
</form>
</div>
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" style="position: absolute; top: 0; right: 0;">
<div class="toast-header">
<strong class="mr-auto">알림</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body">
</div>
</div>
<div class="card-footer">
<button type="button" class="btn btn-primary btn_update"><i class="fa-sharp fa-solid fa-floppy-disk"></i> 저장</button>
<a th:href="@{/mgmt/users/list_view.do}" class="btn btn-primary" th:title="#{button.cancel}"><i class="fa-sharp fa-solid fa-cancel"></i> [[#{button.cancel}]]</a>
</div>
</div>
</div>
</section>
</body>
<th:block layout:fragment="contentScript">
<script>
$(function () {
document.getElementById("passwordChangeRequest").newPassword.focus();
let btnUpdate = document.querySelector(".btn_update");
btnUpdate.addEventListener("click", function (event) {
let form = document.getElementById("passwordChangeRequest");
if (!form.checkValidity()) {
event.preventDefault()
event.stopPropagation()
} else {
form.submit();
}
form.classList.add('was-validated');
});
})
</script>
<script th:if="${resultMsg != null}">
$(function () {
var resultMsg = '[[#{${resultMsg}}]]';
if (resultMsg !== "") {
$('.toast-body').text(resultMsg);
$('.toast').toast('show');
}
})
</script>
</th:block>
</html>
@@ -6,106 +6,109 @@
<body>
<section layout:fragment="contentFragment">
<div class="container-fluid">
<div class="card mt-2">
<div class="card-header">
<h3>사용자 목록</h1>
<div class="container-fluid">
<div class="card mt-2">
<div class="card-header">
<h3>사용자 목록</h3>
</div>
<div class="card-body">
<div class="d-flex justify-content-between">
<form role="form" class="row g-3" name="searchForm" th:action="@{/mgmt/users/list_view.do}" method="get" th:object="${modelSearch}">
<input name="page" type="hidden" value="1"/>
<div class="col-auto">
<input class="form-control" name="username" placeholder="사용자 이름" type="text" size="35" th:title="#{title.search}+ '' + #{input.input}"
th:value="${modelSearch.username}"
maxlength="255"/>
</div>
<div class="card-body">
<div class="d-flex justify-content-between">
<form role="form" class="row g-3" name="searchForm" th:action="@{/mgmt/users/list_view.do}" method="get" th:object="${modelSearch}">
<input name="page" type="hidden" value="1"/>
<div class="col-auto">
<input class="form-control" name="username" placeholder="사용자 이름" type="text" size="35" th:title="#{title.search}+ '' + #{input.input}"
th:value="${modelSearch.username}"
maxlength="255"/>
</div>
<div class="col-auto">
<button type="button" class="btn btn-primary btn_search" th:value="#{button.inquire}"><i class="fa-sharp fa-solid fa-magnifying-glass"></i> [[#{title.inquire}]]
</button>
</div>
<div class="col-auto">
<button type="button" class="btn btn-primary btn_delete"><i class="fa-sharp fa-solid fa-trash"></i> [[#{title.delete}]]</button>
</div>
<div class="col-auto">
<button type="button" class="btn btn-primary btn_add"><i class="fa-sharp fa-solid fa-plus"></i> [[#{button.create}]]</button><!-- 등록 -->
</div>
</form>
</div>
</div>
</div>
<div class="card mt-2">
<div class="card-body">
<form name="listForm">
<input name="id" type="hidden"/>
<input name="checkedIdForDel" type="hidden"/>
<input id="csrf" type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
<table class="table table-hover pt-table">
<colgroup>
<col style="width: 5%;">
<col style="width: 5%;">
<col>
<col>
<col style="width: 7%;">
<col>
</colgroup>
<thead>
<tr>
<th><div class="text-center">번호</div></th>
<th>
<div class="text-center">
<input type="checkbox" name="checkAll" class="form-check-input" onclick="fncCheckAll()" th:title="#{input.selectAll.title}">
</div>
</th>
<th><div class="text-center">ID</div></th>
<th><div class="text-center">이름</div></th>
<th><div class="text-center">상태</div></th>
<th><div class="text-center">비고</div></th>
</tr>
</thead>
<tbody>
<tr th:each="row, status : ${page.content}">
<td class="text-center" th:text="${page.getNumber() * page.getSize() + status.count}"></td>
<td>
<div class="d-flex justify-content-center">
<input type="checkbox" name="checkField" class="form-check-input" title="선택"/>
<input name="checkId" type="hidden" th:value="${row.esntlId}"/>
</div>
</td>
<td>
<span th:text="${row.userId}"></span>
</td>
<td>
<div class="d-flex justify-content-center">
<span th:text="${row.username}"></span>
</div>
</td>
<td>
<div class="d-flex justify-content-center">
<span th:text="${row.status.toString()}"></span>
</div>
</td>
<td>
<div class="d-flex justify-content-end">
<button type="button" class="btn btn-primary btn-sm me-2" th:onclick="fnSelectItem([[${row.esntlId}]])" th:value="#{button.update}"><i class="fa-sharp fa-solid fa-edit"></i>
[[#{button.update}]]
</button>
</div>
</td>
</tr>
<!-- 글이 없는 경우 -->
<tr th:if="${page.content.size() == 0 }">
<td colspan="5" th:text="#{common.nodata.msg}"></td>
</tr>
</tbody>
</table>
</form>
</div>
<div class="card-footer">
<div th:replace="~{fragment/pagination :: pagination(jsFunction='fn_select_page')}"></div>
<div class="col-auto">
<button type="button" class="btn btn-primary btn_search" th:value="#{button.inquire}"><i class="fa-sharp fa-solid fa-magnifying-glass"></i> [[#{title.inquire}]]
</button>
</div>
<div class="col-auto">
<button type="button" class="btn btn-primary btn_delete"><i class="fa-sharp fa-solid fa-trash"></i> [[#{title.delete}]]</button>
</div>
<div class="col-auto">
<button type="button" class="btn btn-primary btn_add"><i class="fa-sharp fa-solid fa-plus"></i> [[#{button.create}]]</button><!-- 등록 -->
</div>
</form>
</div>
</div>
</div>
<div class="card mt-2">
<div class="card-body">
<form name="listForm">
<input name="id" type="hidden"/>
<input name="checkedIdForDel" type="hidden"/>
<input id="csrf" type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
<table class="table table-hover pt-table">
<colgroup>
<col style="width: 5%;">
<col style="width: 5%;">
<col>
<col>
<col style="width: 7%;">
<col>
</colgroup>
<thead>
<tr>
<th><div class="text-center">번호</div></th>
<th>
<div class="text-center">
<input type="checkbox" name="checkAll" class="form-check-input" onclick="fncCheckAll()" th:title="#{input.selectAll.title}">
</div>
</th>
<th><div class="text-center">ID</div></th>
<th><div class="text-center">이름</div></th>
<th><div class="text-center">상태</div></th>
<th><div class="text-center">비고</div></th>
</tr>
</thead>
<tbody>
<tr th:each="row, status : ${page.content}">
<td class="text-center" th:text="${page.getNumber() * page.getSize() + status.count}"></td>
<td>
<div class="d-flex justify-content-center">
<input type="checkbox" name="checkField" class="form-check-input" title="선택"/>
<input name="checkId" type="hidden" th:value="${row.esntlId}"/>
</div>
</td>
<td>
<span th:text="${row.userId}"></span>
</td>
<td>
<div class="d-flex justify-content-center">
<span th:text="${row.username}"></span>
</div>
</td>
<td>
<div class="d-flex justify-content-center">
<span th:text="${row.status.toString()}"></span>
</div>
</td>
<td>
<div class="d-flex justify-content-end">
<button type="button" class="btn btn-primary btn-sm me-2" th:onclick="fnSelectItem([[${row.esntlId}]])" th:value="#{button.update}"><i class="fa-sharp fa-solid fa-edit"></i>
[[#{button.update}]]
</button>
<button type="submit" class="btn btn-primary btn-sm me-2" th:onclick="fnUpdatePassword([[${row.esntlId}]])" ><i class="fa-sharp fa-solid fa-edit"></i>
비밀번호 변경
</button>
</div>
</td>
</tr>
<!-- 글이 없는 경우 -->
<tr th:if="${page.content.size() == 0 }">
<td colspan="5" th:text="#{common.nodata.msg}"></td>
</tr>
</tbody>
</table>
</form>
</div>
<div class="card-footer">
<div th:replace="~{fragment/pagination :: pagination(jsFunction='fn_select_page')}"></div>
</div>
</div>
</div>
</section>
@@ -113,59 +116,67 @@
<th:block layout:fragment="contentScript">
<script>
function fn_select_page(pageNo) {
document.searchForm.page.value = pageNo;
document.searchForm.submit();
<script>
function fn_select_page(pageNo) {
document.searchForm.page.value = pageNo;
document.searchForm.submit();
}
function fnUpdatePassword(id) {
document.listForm.method = 'POST';
document.listForm.action = '[[@{/mgmt/users/staff_password_reset_view.do}]]';
document.listForm.id.value = id;
document.listForm.submit();
}
function fnSelectItem(id) {
location.href = "[[@{/mgmt/users/update_view.do}]]" + "?userId=" + id;
}
$(function () {
let fnSearch = document.querySelector(".btn_search");
let fnDelete = document.querySelector(".btn_delete");
let fnAdd = document.querySelector(".btn_add");
fnSearch.addEventListener("click", function () {
document.searchForm.page.value = 1;
document.searchForm.submit();
});
fnDelete.addEventListener("click", function () {
var checkField = document.listForm.checkField;
var id = document.listForm.checkId;
var checkedIds = "";
var checkedCount = 0;
if (checkField) {
if (checkField.length > 1) {
for (var i = 0; i < checkField.length; i++) {
if (checkField[i].checked) {
checkedIds += ((checkedCount == 0 ? "" : ",") + id[i].value);
checkedCount++;
}
}
} else {
if (checkField.checked) {
checkedIds = id.value;
}
}
}
function fnSelectItem(id) {
location.href = "[[@{/mgmt/users/update_view.do}]]" + "?userId=" + id;
if (checkedIds.length > 0) {
if (confirm("[[#{common.delete.msg}]]")) {
document.listForm.checkedIdForDel.value = checkedIds;
document.listForm.method = 'POST';
document.listForm.action = '[[@{/mgmt/users/delete.do}]]';
document.listForm.submit();
}
}
$(function () {
let fnSearch = document.querySelector(".btn_search");
let fnDelete = document.querySelector(".btn_delete");
let fnAdd = document.querySelector(".btn_add");
});
fnSearch.addEventListener("click", function () {
document.searchForm.page.value = 1;
document.searchForm.submit();
});
fnDelete.addEventListener("click", function () {
var checkField = document.listForm.checkField;
var id = document.listForm.checkId;
var checkedIds = "";
var checkedCount = 0;
if (checkField) {
if (checkField.length > 1) {
for (var i = 0; i < checkField.length; i++) {
if (checkField[i].checked) {
checkedIds += ((checkedCount == 0 ? "" : ",") + id[i].value);
checkedCount++;
}
}
} else {
if (checkField.checked) {
checkedIds = id.value;
}
}
}
if (checkedIds.length > 0) {
if (confirm("[[#{common.delete.msg}]]")) {
document.listForm.checkedIdForDel.value = checkedIds;
document.listForm.method = 'POST';
document.listForm.action = '[[@{/mgmt/users/delete.do}]]';
document.listForm.submit();
}
}
});
fnAdd.addEventListener("click", function () {
location.href = '[[@{/mgmt/users/create_view.do}]]';
});
})
</script>
fnAdd.addEventListener("click", function () {
location.href = '[[@{/mgmt/users/create_view.do}]]';
});
})
</script>
</th:block>