Skip to content

Commit f3ef816

Browse files
committed
feat: Implement initial login and dashboard UI components with associated scripts and favicon.
1 parent d021734 commit f3ef816

File tree

16 files changed

+1090
-1037
lines changed

16 files changed

+1090
-1037
lines changed

.DS_Store

8 KB
Binary file not shown.

src/.DS_Store

6 KB
Binary file not shown.

src/assets/favicon.ico

15 KB
Binary file not shown.

src/assets/logo.png

14 KB
Loading

src/views/DashboardPage.js

Lines changed: 19 additions & 840 deletions
Large diffs are not rendered by default.

src/views/LoginPage.js

Lines changed: 9 additions & 197 deletions
Original file line numberDiff line numberDiff line change
@@ -1,211 +1,23 @@
11
/**
2-
* Login page component
3-
* Uses template string approach for Alpine.js compatibility
2+
* Login page component - Modular version
3+
* Composes login components
44
* @param {{ totpEnabled: boolean }} props
55
* @returns {string} HTML string
66
*/
7+
import { Head } from './components/Head.js'
8+
import { LoginCard } from './components/LoginCard.js'
9+
import { loginAppScript } from './scripts/loginApp.js'
10+
711
export const LoginPage = ({ totpEnabled = false }) => `
812
<!DOCTYPE html>
913
<html lang="en">
1014
<head>
11-
<meta charset="UTF-8">
12-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
13-
<title>Login - Backup Manager</title>
14-
<script src="https://cdn.tailwindcss.com"></script>
15-
<script>
16-
tailwind.config = {
17-
theme: {
18-
extend: {
19-
fontFamily: {
20-
sans: ['Montserrat', 'sans-serif'],
21-
},
22-
colors: {
23-
gray: {
24-
50: '#F9FAFB',
25-
100: '#F3F4F6',
26-
200: '#E5E7EB',
27-
300: '#D1D5DB',
28-
400: '#9CA3AF',
29-
500: '#6B7280',
30-
600: '#4B5563',
31-
700: '#374151',
32-
800: '#1F2937',
33-
900: '#111827',
34-
}
35-
}
36-
}
37-
}
38-
}
39-
</script>
40-
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
41-
<script src="https://unpkg.com/lucide@latest"></script>
42-
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
43-
<style>
44-
[x-cloak] { display: none !important; }
45-
</style>
15+
${Head({ title: 'Login - Backup Manager' })}
4616
</head>
4717
<body class="bg-gray-50 min-h-screen flex items-center justify-center p-6 antialiased">
48-
<div class="w-full max-w-md" x-data="loginApp()">
49-
<!-- Login Card -->
50-
<div class="bg-white rounded-2xl border border-gray-100 shadow-[0_8px_30px_rgba(0,0,0,0.08)] overflow-hidden">
51-
<!-- Header -->
52-
<div class="p-10 text-center border-b border-gray-100">
53-
<div class="w-16 h-16 bg-gray-900 rounded-full flex items-center justify-center mx-auto mb-4">
54-
<i data-lucide="shield-check" class="w-8 h-8 text-white"></i>
55-
</div>
56-
<h1 class="text-2xl font-bold text-gray-900 mb-2">Backup Manager</h1>
57-
<p class="text-sm text-gray-500">Access Control Panel</p>
58-
</div>
59-
60-
<!-- Form -->
61-
<div class="p-10">
62-
<form @submit.prevent="login" class="space-y-6">
63-
<!-- Error Message -->
64-
<div x-show="error" x-cloak class="bg-red-50 border border-red-200 rounded-lg p-4">
65-
<div class="flex items-center gap-3">
66-
<i data-lucide="alert-circle" class="w-5 h-5 text-red-600"></i>
67-
<span class="text-sm text-red-800 font-medium" x-text="error"></span>
68-
</div>
69-
</div>
70-
71-
<!-- Username -->
72-
<div>
73-
<label class="block text-sm font-semibold text-gray-700 mb-2">Username</label>
74-
<div class="relative">
75-
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
76-
<i data-lucide="user" class="w-5 h-5 text-gray-400"></i>
77-
</div>
78-
<input
79-
type="text"
80-
x-model="username"
81-
required
82-
class="w-full bg-gray-50 border border-gray-200 rounded-lg pl-12 pr-4 py-3 text-gray-900 focus:ring-2 focus:ring-gray-900 focus:border-transparent outline-none transition-all font-medium"
83-
placeholder="Enter your username"
84-
autofocus
85-
>
86-
</div>
87-
</div>
88-
89-
<!-- Password -->
90-
<div>
91-
<label class="block text-sm font-semibold text-gray-700 mb-2">Password</label>
92-
<div class="relative">
93-
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
94-
<i data-lucide="lock" class="w-5 h-5 text-gray-400"></i>
95-
</div>
96-
<input
97-
type="password"
98-
x-model="password"
99-
required
100-
class="w-full bg-gray-50 border border-gray-200 rounded-lg pl-12 pr-4 py-3 text-gray-900 focus:ring-2 focus:ring-gray-900 focus:border-transparent outline-none transition-all font-medium"
101-
placeholder="Enter your password"
102-
>
103-
</div>
104-
</div>
105-
106-
<!-- TOTP Code (only shown when TOTP is enabled) -->
107-
<div x-show="totpEnabled" x-cloak>
108-
<label class="block text-sm font-semibold text-gray-700 mb-2">Authenticator Code</label>
109-
<div class="relative">
110-
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
111-
<i data-lucide="smartphone" class="w-5 h-5 text-gray-400"></i>
112-
</div>
113-
<input
114-
type="text"
115-
x-model="totpCode"
116-
inputmode="numeric"
117-
pattern="[0-9]*"
118-
maxlength="6"
119-
:required="totpEnabled"
120-
class="w-full bg-gray-50 border border-gray-200 rounded-lg pl-12 pr-4 py-3 text-gray-900 focus:ring-2 focus:ring-gray-900 focus:border-transparent outline-none transition-all font-medium tracking-widest text-center text-lg"
121-
placeholder="000000"
122-
>
123-
</div>
124-
<p class="text-xs text-gray-500 mt-2 flex items-center gap-1">
125-
<i data-lucide="info" class="w-3 h-3"></i>
126-
Enter the 6-digit code from your authenticator app
127-
</p>
128-
</div>
129-
130-
<!-- Submit Button -->
131-
<button
132-
type="submit"
133-
:disabled="loading"
134-
class="w-full bg-gray-900 hover:bg-gray-800 text-white font-bold py-3.5 px-6 rounded-xl transition-all shadow-lg hover:shadow-xl transform hover:-translate-y-0.5 flex items-center justify-center gap-2 disabled:opacity-70 disabled:cursor-not-allowed disabled:transform-none"
135-
>
136-
<span x-show="!loading" class="flex items-center gap-2">
137-
<span>Sign In</span>
138-
<i data-lucide="arrow-right" class="w-5 h-5"></i>
139-
</span>
140-
<span x-show="loading" class="flex items-center gap-2">
141-
<i data-lucide="loader-2" class="w-5 h-5 animate-spin"></i>
142-
<span>Authenticating...</span>
143-
</span>
144-
</button>
145-
</form>
146-
</div>
147-
</div>
148-
149-
<!-- Footer -->
150-
<div class="text-center mt-6 text-sm text-gray-500">
151-
<i data-lucide="info" class="w-4 h-4 inline-block mr-1"></i>
152-
Secure connection required for production use
153-
</div>
154-
</div>
155-
156-
<script>
157-
document.addEventListener('alpine:init', () => {
158-
Alpine.data('loginApp', () => ({
159-
username: '',
160-
password: '',
161-
totpCode: '',
162-
totpEnabled: ${totpEnabled},
163-
loading: false,
164-
error: '',
165-
166-
init() {
167-
this.$nextTick(() => lucide.createIcons());
168-
},
169-
170-
async login() {
171-
this.loading = true;
172-
this.error = '';
173-
174-
try {
175-
const payload = {
176-
username: this.username,
177-
password: this.password
178-
};
179-
180-
if (this.totpEnabled && this.totpCode) {
181-
payload.totpCode = this.totpCode;
182-
}
183-
184-
const response = await fetch('/backup/auth/login', {
185-
method: 'POST',
186-
headers: { 'Content-Type': 'application/json' },
187-
body: JSON.stringify(payload)
188-
});
189-
190-
const data = await response.json();
18+
${LoginCard({ totpEnabled })}
19119
192-
if (response.ok && data.status === 'success') {
193-
window.location.href = '/backup';
194-
} else {
195-
this.error = data.message || 'Invalid credentials';
196-
this.$nextTick(() => lucide.createIcons());
197-
}
198-
} catch (err) {
199-
this.error = 'Connection failed. Please try again.';
200-
this.$nextTick(() => lucide.createIcons());
201-
} finally {
202-
this.loading = false;
203-
this.$nextTick(() => lucide.createIcons());
204-
}
205-
}
206-
}));
207-
});
208-
</script>
20+
${loginAppScript({ totpEnabled })}
20921
</body>
21022
</html>
21123
`

src/views/components/ActionArea.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* Action area component with backup button and activity logs
3+
* @returns {string} HTML string
4+
*/
5+
export const ActionArea = () => `
6+
<div class="bg-white rounded-2xl border border-gray-100 shadow-[0_2px_8px_rgba(0,0,0,0.04)] overflow-hidden">
7+
<div class="p-10 flex flex-col items-center justify-center text-center">
8+
<div class="w-16 h-16 bg-gray-50 rounded-full flex items-center justify-center mb-6 text-gray-400">
9+
<i data-lucide="save" class="w-8 h-8"></i>
10+
</div>
11+
<h3 class="text-xl font-bold text-gray-900 mb-2">Trigger Manual Backup</h3>
12+
<p class="text-gray-500 max-w-md mb-8 leading-relaxed">Initiate a backup of your local directory to the configured R2 bucket. This will upload all files recursively.</p>
13+
14+
<button
15+
@click="runBackup()"
16+
:disabled="loading"
17+
class="group relative inline-flex items-center justify-center px-8 py-3.5 text-base font-semibold text-white transition-all duration-200 bg-gray-900 rounded-xl hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-900 disabled:opacity-70 disabled:cursor-not-allowed">
18+
<span x-show="!loading" class="flex items-center gap-2">
19+
<i data-lucide="play-circle" class="w-5 h-5"></i>
20+
Start Backup Process
21+
</span>
22+
<span x-show="loading" class="flex items-center gap-2">
23+
<i data-lucide="loader-2" class="w-5 h-5 animate-spin"></i>
24+
Processing...
25+
</span>
26+
</button>
27+
</div>
28+
29+
<!-- Logs -->
30+
<div class="bg-gray-50 border-t border-gray-100 p-8">
31+
<h4 class="text-xs font-bold text-gray-400 uppercase tracking-wider mb-4">Activity Log</h4>
32+
<div class="space-y-3">
33+
<template x-for="log in logs" :key="log.id">
34+
<div class="flex items-start gap-4 text-sm">
35+
<span class="font-mono text-xs text-gray-400 mt-0.5" x-text="log.time"></span>
36+
<div class="flex items-center gap-2">
37+
<i :data-lucide="log.type === 'error' ? 'alert-circle' : 'info'"
38+
:class="log.type === 'error' ? 'text-red-600' : 'text-gray-400'"
39+
class="w-4 h-4"></i>
40+
<span :class="log.type === 'error' ? 'text-red-600 font-medium' : 'text-gray-600'" x-text="log.message"></span>
41+
</div>
42+
</div>
43+
</template>
44+
<div x-show="logs.length === 0" class="text-gray-400 text-sm italic">No recent activity recorded.</div>
45+
</div>
46+
</div>
47+
</div>
48+
`

0 commit comments

Comments
 (0)