v1.4.3: 관리자 UI·홈·미디어 개선

- 관리자 라이트 테마 격리, 대시보드 활성 링크, 로그인 우측 정렬
- 대시보드 통계 추이 차트·툴팁, 홈 Latest/Featured 보정
- 미디어 종류·미사용 필터, 비디오 프레임 썸네일
- NAS 운영 업데이트 절차 문서 추가
This commit is contained in:
2026-05-21 18:30:50 +09:00
parent 6919669330
commit 10c5a099fc
15 changed files with 523 additions and 84 deletions

View File

@@ -10,6 +10,7 @@ const form = reactive({
const pending = ref(false)
const errorMessage = ref('')
const showPassword = ref(false)
const emailInput = ref(null)
const passwordInput = ref(null)
@@ -20,6 +21,12 @@ const { data: bootstrapStatus } = await useFetch('/api/auth/bootstrap-status', {
})
})
/**
* 관리자 로그인 제출 가능 여부
* @returns {boolean} 제출 가능 여부
*/
const canSubmitAdminLogin = computed(() => Boolean(form.email.trim()) && Boolean(form.password))
/**
* 브라우저/비밀번호 관리자 자동완성 값을 로그인 폼 상태에 반영한다.
* @returns {void}
@@ -85,64 +92,76 @@ onMounted(() => {
</script>
<template>
<main class="admin-login flex min-h-screen items-center justify-center bg-[#f5f5f2] px-5 text-ink">
<section class="admin-login__panel w-full max-w-sm border border-line bg-paper p-8">
<p class="admin-login__eyebrow text-xs font-semibold uppercase text-muted">
Admin
</p>
<h1 class="admin-login__title mt-2 text-3xl font-semibold">
로그인
</h1>
<p
v-if="bootstrapStatus?.needsAdminSetup"
class="mt-3 rounded border border-[#ff4f2e]/30 bg-[#ff4f2e]/10 px-3 py-2 text-xs text-[#b63a23]"
>
등록된 관리자가 없습니다.
<NuxtLink class="font-semibold underline-offset-2 hover:underline" to="/signup">
관리자 등록으로 이동
</NuxtLink>
</p>
<form class="admin-login__form mt-8 grid gap-4" @submit.prevent="submitLogin">
<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">
{{ errorMessage }}
<main class="admin-login min-h-screen bg-[#0a0b0d] text-[#f5f7fa] [color-scheme:dark]">
<div class="mx-auto flex min-h-screen w-full max-w-[1280px] items-center justify-end px-5 py-12 sm:px-10 lg:px-16">
<section class="admin-login__panel w-full max-w-[430px] p-5 sm:p-8">
<p class="admin-login__eyebrow text-right text-xs font-semibold uppercase text-[#9ba3af]">
Admin
</p>
<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"
<h1 class="admin-login__title text-right mt-2 text-2xl font-semibold leading-tight">
관리자 로그인
</h1>
<p class="mt-2 text-right text-sm text-[#9ba3af]">
관리자 계정으로 로그인해 콘텐츠와 사이트 설정을 관리하세요.
</p>
<p
v-if="bootstrapStatus?.needsAdminSetup"
class="mt-5 rounded-[10px] border border-[#b03b43]/50 bg-[#b03b43]/10 px-3 py-2 text-right text-xs text-[#e5acb1]"
>
{{ pending ? '확인 중' : '로그인' }}
</button>
</form>
</section>
등록된 관리자가 없습니다.
<NuxtLink class="font-semibold text-[#7eb8ff] underline-offset-2 hover:underline" to="/signup">
관리자 등록으로 이동
</NuxtLink>
</p>
<form class="admin-login__form mt-8 space-y-5" @submit.prevent="submitLogin">
<label class="admin-login__field block space-y-1.5">
<span class="admin-login__label text-xs text-[#d8dee6]">이메일</span>
<input
ref="emailInput"
v-model="form.email"
class="auth-form-input admin-login__input h-10 w-full rounded-[8px] border border-[#1a212a] bg-transparent px-3 text-sm outline-none transition-colors focus:border-[#2f6feb]"
type="email"
autocomplete="username"
required
@change="syncAdminLoginAutofill"
@focus="syncAdminLoginAutofill"
@input="syncAdminLoginAutofill"
>
</label>
<label class="admin-login__field block space-y-1.5">
<span class="admin-login__label text-xs text-[#d8dee6]">비밀번호</span>
<span class="flex items-center rounded-[8px] border border-[#1a212a] transition-colors focus-within:border-[#2f6feb]">
<input
ref="passwordInput"
v-model="form.password"
class="auth-form-input admin-login__input h-10 min-w-0 flex-1 bg-transparent px-3 text-sm outline-none"
:type="showPassword ? 'text' : 'password'"
autocomplete="current-password"
required
@change="syncAdminLoginAutofill"
@focus="syncAdminLoginAutofill"
@input="syncAdminLoginAutofill"
>
<AuthPasswordVisibilityToggle v-model="showPassword" />
</span>
</label>
<p v-if="errorMessage" class="admin-login__error text-right text-xs text-[#e5acb1]" aria-live="polite">
{{ errorMessage }}
</p>
<button
class="admin-login__button ml-auto block h-9 rounded-[8px] bg-[#2f6feb] px-8 text-xs font-medium text-white transition-opacity hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40"
type="submit"
:disabled="pending || !canSubmitAdminLogin"
>
{{ pending ? '확인 중' : '로그인' }}
</button>
</form>
<NuxtLink class="mt-6 flex justify-end text-xs text-[#9ba3af] hover:opacity-80" to="/">
사이트로 돌아가기
</NuxtLink>
</section>
</div>
</main>
</template>