Initial commit
This commit is contained in:
169
backend/templates/admin.html
Normal file
169
backend/templates/admin.html
Normal file
@@ -0,0 +1,169 @@
|
||||
{# backend/templates/admin.html #}
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h3 class="card-title">Admin Page</h3>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div class="mt-2">
|
||||
{% for cat, msg in messages %}
|
||||
<div class="alert alert-{{ cat }} alert-dismissible fade show" role="alert">
|
||||
{{ msg }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:60px">ID</th>
|
||||
<th>Username</th>
|
||||
<th>Email</th>
|
||||
<th style="width:80px">Active</th>
|
||||
<th style="width:260px">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>{{ user.id }}</td>
|
||||
<td>{{ user.username }}</td>
|
||||
<td>{{ user.email }}</td>
|
||||
<td>
|
||||
{% if user.is_active %}
|
||||
<span class="badge bg-success">Yes</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">No</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if not user.is_active %}
|
||||
<a href="{{ url_for('admin.approve_user', user_id=user.id) }}" class="btn btn-success btn-sm me-1">Approve</a>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('admin.delete_user', user_id=user.id) }}"
|
||||
class="btn btn-danger btn-sm me-1"
|
||||
onclick="return confirm('사용자 {{ user.username }} (ID={{ user.id }}) 를 삭제하시겠습니까?');">
|
||||
Delete
|
||||
</a>
|
||||
|
||||
<!-- Change Password 버튼: 모달 오픈 -->
|
||||
<button type="button"
|
||||
class="btn btn-primary btn-sm"
|
||||
data-user-id="{{ user.id }}"
|
||||
data-username="{{ user.username | e }}"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#changePasswordModal">
|
||||
Change Password
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# ========== Change Password Modal ========== #}
|
||||
<div class="modal fade" id="changePasswordModal" tabindex="-1" aria-labelledby="changePasswordModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<form id="changePasswordForm" method="post" action="">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="changePasswordModalLabel">Change Password</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="mb-2">
|
||||
<small class="text-muted">User:</small>
|
||||
<div id="modalUserInfo" class="fw-bold"></div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="newPasswordInput" class="form-label">New password</label>
|
||||
<input id="newPasswordInput" name="new_password" type="password" class="form-control" required minlength="8" placeholder="Enter new password">
|
||||
<div class="form-text">최소 8자 이상을 권장합니다.</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="confirmPasswordInput" class="form-label">Confirm password</label>
|
||||
<input id="confirmPasswordInput" name="confirm_password" type="password" class="form-control" required minlength="8" placeholder="Confirm new password">
|
||||
<div id="pwMismatch" class="invalid-feedback">비밀번호가 일치하지 않습니다.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button id="modalSubmitBtn" type="submit" class="btn btn-primary">Change Password</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# ========== 스크립트: 모달에 사용자 정보 채우기 + 클라이언트 확인 ========== #}
|
||||
{% block scripts %}
|
||||
{{ super() }}
|
||||
<script>
|
||||
(function () {
|
||||
// Bootstrap 5을 사용한다고 가정. data-bs-* 이벤트로 처리.
|
||||
const changePasswordModal = document.getElementById('changePasswordModal');
|
||||
const modalUserInfo = document.getElementById('modalUserInfo');
|
||||
const changePasswordForm = document.getElementById('changePasswordForm');
|
||||
const newPasswordInput = document.getElementById('newPasswordInput');
|
||||
const confirmPasswordInput = document.getElementById('confirmPasswordInput');
|
||||
const pwMismatch = document.getElementById('pwMismatch');
|
||||
|
||||
if (!changePasswordModal) return;
|
||||
|
||||
changePasswordModal.addEventListener('show.bs.modal', function (event) {
|
||||
const button = event.relatedTarget; // 버튼 that triggered the modal
|
||||
const userId = button.getAttribute('data-user-id');
|
||||
const username = button.getAttribute('data-username') || ('ID ' + userId);
|
||||
|
||||
// 표시 텍스트 세팅
|
||||
modalUserInfo.textContent = username + ' (ID: ' + userId + ')';
|
||||
|
||||
// 폼 action 동적 설정: admin.reset_password 라우트 기대
|
||||
// 예: /admin/users/123/reset_password
|
||||
changePasswordForm.action = "{{ url_for('admin.reset_password', user_id=0) }}".replace('/0/', '/' + userId + '/');
|
||||
// 폼 내부 비밀번호 필드 초기화
|
||||
newPasswordInput.value = '';
|
||||
confirmPasswordInput.value = '';
|
||||
confirmPasswordInput.classList.remove('is-invalid');
|
||||
pwMismatch.style.display = 'none';
|
||||
});
|
||||
|
||||
// 폼 제출 전 클라이언트에서 비밀번호 일치 검사
|
||||
changePasswordForm.addEventListener('submit', function (e) {
|
||||
const a = newPasswordInput.value || '';
|
||||
const b = confirmPasswordInput.value || '';
|
||||
if (a.length < 8) {
|
||||
newPasswordInput.focus();
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (a !== b) {
|
||||
e.preventDefault();
|
||||
confirmPasswordInput.classList.add('is-invalid');
|
||||
pwMismatch.style.display = 'block';
|
||||
confirmPasswordInput.focus();
|
||||
return;
|
||||
}
|
||||
// 제출 허용 (서버측에서도 반드시 검증)
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user