v1.3.5: 관리자 로그인·대시보드 차트·통계 보관 정리
운영 HTTP에서 관리자 세션이 유지되지 않던 문제를 쿠키 공통화로 수정하고, 통계 클라이언트 분리·조회 오류·기간별 차트를 보강했다. 방문자 해시는 32일 초과분만 정리하고 일별 집계는 누적 보관한다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -10,12 +10,9 @@ const form = reactive({
|
||||
|
||||
const pending = ref(false)
|
||||
const errorMessage = ref('')
|
||||
const emailInput = ref(null)
|
||||
const passwordInput = ref(null)
|
||||
|
||||
/**
|
||||
* 로그인 제출 가능 여부(이메일·비밀번호가 모두 채워졌는지)
|
||||
* @returns {boolean} 제출 가능 여부
|
||||
*/
|
||||
const canSubmitAdminLogin = computed(() => Boolean(form.email.trim()) && Boolean(form.password))
|
||||
const { data: bootstrapStatus } = await useFetch('/api/auth/bootstrap-status', {
|
||||
default: () => ({
|
||||
hasUsers: true,
|
||||
@@ -23,26 +20,68 @@ const { data: bootstrapStatus } = await useFetch('/api/auth/bootstrap-status', {
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* 브라우저/비밀번호 관리자 자동완성 값을 로그인 폼 상태에 반영한다.
|
||||
* @returns {void}
|
||||
*/
|
||||
const syncAdminLoginAutofill = () => {
|
||||
const emailValue = emailInput.value?.value || ''
|
||||
const passwordValue = passwordInput.value?.value || ''
|
||||
|
||||
if (emailValue && emailValue !== form.email) {
|
||||
form.email = emailValue
|
||||
}
|
||||
if (passwordValue && passwordValue !== form.password) {
|
||||
form.password = passwordValue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 관리자 로그인 제출
|
||||
* @returns {Promise<void>} 로그인 처리 결과
|
||||
*/
|
||||
const submitLogin = async () => {
|
||||
syncAdminLoginAutofill()
|
||||
|
||||
pending.value = true
|
||||
errorMessage.value = ''
|
||||
|
||||
try {
|
||||
await $fetch('/admin/api/auth/login', {
|
||||
method: 'POST',
|
||||
body: form
|
||||
body: {
|
||||
email: form.email.trim(),
|
||||
password: form.password
|
||||
},
|
||||
credentials: 'include'
|
||||
})
|
||||
|
||||
if (import.meta.client) {
|
||||
window.location.assign('/admin')
|
||||
return
|
||||
}
|
||||
|
||||
await navigateTo('/admin')
|
||||
} catch {
|
||||
errorMessage.value = '이메일 또는 비밀번호를 확인해 주세요.'
|
||||
} catch (error) {
|
||||
const statusCode = Number(error?.statusCode || error?.response?.status || 0)
|
||||
|
||||
if (statusCode === 401) {
|
||||
errorMessage.value = '이메일 또는 비밀번호를 확인해 주세요.'
|
||||
} else if (statusCode >= 500) {
|
||||
errorMessage.value = '서버 또는 데이터베이스 연결을 확인해 주세요.'
|
||||
} else {
|
||||
errorMessage.value = '로그인에 실패했습니다. 잠시 후 다시 시도해 주세요.'
|
||||
}
|
||||
} finally {
|
||||
pending.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
syncAdminLoginAutofill()
|
||||
window.setTimeout(syncAdminLoginAutofill, 100)
|
||||
window.setTimeout(syncAdminLoginAutofill, 500)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -68,21 +107,29 @@ const submitLogin = async () => {
|
||||
<label class="admin-login__field grid gap-2 text-sm">
|
||||
<span class="admin-login__label font-medium">이메일</span>
|
||||
<input
|
||||
ref="emailInput"
|
||||
v-model="form.email"
|
||||
class="admin-login__input rounded border border-line bg-white px-3 py-2"
|
||||
type="email"
|
||||
autocomplete="username"
|
||||
required
|
||||
@change="syncAdminLoginAutofill"
|
||||
@focus="syncAdminLoginAutofill"
|
||||
@input="syncAdminLoginAutofill"
|
||||
>
|
||||
</label>
|
||||
<label class="admin-login__field grid gap-2 text-sm">
|
||||
<span class="admin-login__label font-medium">비밀번호</span>
|
||||
<input
|
||||
ref="passwordInput"
|
||||
v-model="form.password"
|
||||
class="admin-login__input rounded border border-line bg-white px-3 py-2"
|
||||
type="password"
|
||||
autocomplete="current-password"
|
||||
required
|
||||
@change="syncAdminLoginAutofill"
|
||||
@focus="syncAdminLoginAutofill"
|
||||
@input="syncAdminLoginAutofill"
|
||||
>
|
||||
</label>
|
||||
<p v-if="errorMessage" class="admin-login__error text-sm text-red-600">
|
||||
@@ -91,7 +138,7 @@ const submitLogin = async () => {
|
||||
<button
|
||||
class="admin-login__button rounded bg-[#15171a] px-4 py-2 text-sm font-semibold text-white disabled:cursor-not-allowed disabled:opacity-50"
|
||||
type="submit"
|
||||
:disabled="pending || !canSubmitAdminLogin"
|
||||
:disabled="pending"
|
||||
>
|
||||
{{ pending ? '확인 중' : '로그인' }}
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user