관리자 페이지 추가 및 버그 수정

- 관리자 대시보드 추가 (/admin/)
  - 통계: 총 포스트, 공개/비공개, 삭제된 포스트, 회원 수
- 포스트 관리 추가 (/admin/posts)
  - 목록, 검색, 필터링, 페이지네이션
  - 포스트 수정, 삭제, 복구 기능
- 회원 관리 추가 (/admin/members)
  - 회원 목록, 추가, 수정, 삭제
  - 비밀번호 재설정
- 버그 수정
  - g.is_login, g.user_info 기본값 설정
  - index 페이지 빈 포스트 처리
- 관리자 권한: admin, wixon, javamon
- README.md 프로젝트 문서 추가

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
javamon117
2026-01-05 15:30:49 +09:00
parent 73a13852bf
commit d49a33cc6c
10 changed files with 1407 additions and 4 deletions

View File

@@ -0,0 +1,139 @@
{% extends 'admin/base_admin.html' %}
{% block content %}
<div class="page__header">
<h1>회원 관리</h1>
<a href="/admin/members/add" class="uk-button uk-button-primary">
<span uk-icon="icon: plus"></span> 회원 추가
</a>
</div>
<!-- Members Table -->
<div class="admin__table">
<table class="uk-table uk-table-striped uk-table-hover uk-table-middle">
<thead>
<tr>
<th style="width: 80px;">번호</th>
<th>아이디</th>
<th>이름</th>
<th style="width: 200px;">관리</th>
</tr>
</thead>
<tbody>
{% if members %}
{% for member in members %}
<tr>
<td>{{ member.mb_idx }}</td>
<td>
{{ member.mb_id }}
{% if member.mb_id in ['admin', 'wixon'] %}
<span class="uk-label uk-label-warning" style="margin-left: 5px;">관리자</span>
{% endif %}
</td>
<td>{{ member.mb_name }}</td>
<td>
<a href="/admin/members/{{ member.mb_idx }}" class="uk-button uk-button-small uk-button-default">
<span uk-icon="icon: pencil; ratio: 0.8"></span> 수정
</a>
<button type="button" class="uk-button uk-button-small uk-button-secondary" onclick="resetPassword({{ member.mb_idx }})">
<span uk-icon="icon: lock; ratio: 0.8"></span> 비밀번호
</button>
{% if member.mb_id not in ['admin', 'wixon'] %}
<button type="button" class="uk-button uk-button-small uk-button-danger" onclick="deleteMember({{ member.mb_idx }}, '{{ member.mb_id }}')">
<span uk-icon="icon: trash; ratio: 0.8"></span> 삭제
</button>
{% endif %}
</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="4" class="uk-text-center uk-text-muted">
등록된 회원이 없습니다.
</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
{% endblock %}
{% block scripts %}
<script>
function resetPassword(mb_idx) {
Swal.fire({
title: '새 비밀번호 입력',
input: 'password',
inputPlaceholder: '새 비밀번호를 입력하세요 (4자 이상)',
showCancelButton: true,
confirmButtonColor: '#901438',
cancelButtonColor: '#6c757d',
confirmButtonText: '변경',
cancelButtonText: '취소',
inputValidator: (value) => {
if (!value) return '비밀번호를 입력해주세요';
if (value.length < 4) return '비밀번호는 4자 이상이어야 합니다';
}
}).then((result) => {
if (result.isConfirmed) {
$.ajax({
url: "/admin/members/" + mb_idx + "/reset-password",
type: "POST",
contentType: "application/json",
data: JSON.stringify({password: result.value}),
success: function(res) {
if (res.success) {
Swal.fire({
title: '완료',
text: res.message,
icon: 'success'
});
} else {
Swal.fire('오류', res.message, 'error');
}
},
error: function(xhr) {
var msg = xhr.responseJSON ? xhr.responseJSON.message : '비밀번호 변경 중 오류가 발생했습니다.';
Swal.fire('오류', msg, 'error');
}
});
}
});
}
function deleteMember(mb_idx, mb_id) {
Swal.fire({
title: '회원을 삭제하시겠습니까?',
html: '<strong>' + mb_id + '</strong> 회원을 삭제합니다.<br><span class="uk-text-danger">이 작업은 되돌릴 수 없습니다!</span>',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#dc3545',
cancelButtonColor: '#6c757d',
confirmButtonText: '삭제',
cancelButtonText: '취소'
}).then((result) => {
if (result.isConfirmed) {
$.ajax({
url: "/admin/members/" + mb_idx + "/delete",
type: "DELETE",
success: function(res) {
if (res.success) {
Swal.fire({
title: '삭제 완료',
text: res.message,
icon: 'success'
}).then(() => location.reload());
} else {
Swal.fire('오류', res.message, 'error');
}
},
error: function(xhr) {
var msg = xhr.responseJSON ? xhr.responseJSON.message : '삭제 중 오류가 발생했습니다.';
Swal.fire('오류', msg, 'error');
}
});
}
});
}
</script>
{% endblock %}