feat: implement Phase 4 SvelteKit frontend + backend enhancements
Backend: - Add dashboard API (today stats, inbox count, law alerts, pipeline status) - Add /api/documents/tree endpoint for sidebar domain/sub_group tree - Migrate auth to HttpOnly cookie for refresh token (XSS defense) - Add /api/auth/logout endpoint (cookie cleanup) - Register dashboard router in main.py Frontend (SvelteKit + Tailwind CSS v4): - api.ts: fetch wrapper with refresh queue pattern, 401 single retry, forced logout on refresh failure - Auth store: login/logout/refresh with memory-based access token - UI store: toast system, sidebar state - Login page with TOTP support - Dashboard with 4 stat widgets + recent documents - Document list with hybrid search (debounce, URL query state, mode select) - Document detail with format-aware viewer (markdown/PDF/HWP/Synology/fallback) - Metadata panel (AI summary, tags, processing history) - Inbox triage UI (batch select, confirm dialog, domain override) - Settings page (password change, TOTP status) Infrastructure: - Enable frontend service in docker-compose - Caddy path routing (/api/* → fastapi, / → frontend) + gzip Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
89
frontend/src/routes/login/+page.svelte
Normal file
89
frontend/src/routes/login/+page.svelte
Normal file
@@ -0,0 +1,89 @@
|
||||
<script>
|
||||
import { goto } from '$app/navigation';
|
||||
import { login } from '$lib/stores/auth';
|
||||
import { addToast } from '$lib/stores/ui';
|
||||
|
||||
let username = '';
|
||||
let password = '';
|
||||
let totpCode = '';
|
||||
let needsTotp = false;
|
||||
let loading = false;
|
||||
let error = '';
|
||||
|
||||
async function handleLogin() {
|
||||
error = '';
|
||||
loading = true;
|
||||
try {
|
||||
await login(username, password, needsTotp ? totpCode : undefined);
|
||||
goto('/');
|
||||
} catch (err) {
|
||||
if (err.detail?.includes('TOTP')) {
|
||||
needsTotp = true;
|
||||
error = 'TOTP 코드를 입력하세요';
|
||||
} else {
|
||||
error = err.detail || '로그인 실패';
|
||||
}
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="min-h-screen flex items-center justify-center px-4">
|
||||
<div class="w-full max-w-sm">
|
||||
<h1 class="text-2xl font-bold mb-1">hyungi Document Server</h1>
|
||||
<p class="text-[var(--text-dim)] text-sm mb-8">로그인</p>
|
||||
|
||||
<form onsubmit={(e) => { e.preventDefault(); handleLogin(); }} class="space-y-4">
|
||||
<div>
|
||||
<label for="username" class="block text-sm text-[var(--text-dim)] mb-1">아이디</label>
|
||||
<input
|
||||
id="username"
|
||||
type="text"
|
||||
bind:value={username}
|
||||
class="w-full px-3 py-2 bg-[var(--bg)] border border-[var(--border)] rounded-lg text-[var(--text)] focus:border-[var(--accent)] outline-none"
|
||||
autocomplete="username"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="password" class="block text-sm text-[var(--text-dim)] mb-1">비밀번호</label>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
bind:value={password}
|
||||
class="w-full px-3 py-2 bg-[var(--bg)] border border-[var(--border)] rounded-lg text-[var(--text)] focus:border-[var(--accent)] outline-none"
|
||||
autocomplete="current-password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if needsTotp}
|
||||
<div>
|
||||
<label for="totp" class="block text-sm text-[var(--text-dim)] mb-1">TOTP 코드</label>
|
||||
<input
|
||||
id="totp"
|
||||
type="text"
|
||||
bind:value={totpCode}
|
||||
maxlength="6"
|
||||
inputmode="numeric"
|
||||
pattern="[0-9]*"
|
||||
class="w-full px-3 py-2 bg-[var(--bg)] border border-[var(--border)] rounded-lg text-[var(--text)] focus:border-[var(--accent)] outline-none tracking-widest text-center text-lg"
|
||||
autocomplete="one-time-code"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if error}
|
||||
<p class="text-[var(--error)] text-sm">{error}</p>
|
||||
{/if}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
class="w-full py-2.5 bg-[var(--accent)] hover:bg-[var(--accent-hover)] text-white rounded-lg font-medium disabled:opacity-50 transition-colors"
|
||||
>
|
||||
{loading ? '로그인 중...' : '로그인'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user