@riligar/elysia-backup 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.js +20 -1
- package/src/views/components/ActionArea.js +1 -1
- package/src/views/components/FilesTab.js +2 -2
- package/src/views/components/Head.js +13 -0
- package/src/views/components/Header.js +14 -12
- package/src/views/components/LoginCard.js +5 -7
- package/src/views/components/SecuritySection.js +3 -3
- package/src/views/components/SettingsTab.js +8 -8
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -281,7 +281,8 @@ export const r2Backup = initialConfig => app => {
|
|
|
281
281
|
|
|
282
282
|
const path = context.path
|
|
283
283
|
|
|
284
|
-
|
|
284
|
+
// Skip auth for login, logout, and static assets
|
|
285
|
+
if (path === '/backup/login' || path === '/backup/auth/login' || path === '/backup/auth/logout' || path === '/backup/favicon.ico' || path === '/backup/logo.png') {
|
|
285
286
|
return
|
|
286
287
|
}
|
|
287
288
|
|
|
@@ -601,6 +602,24 @@ export const r2Backup = initialConfig => app => {
|
|
|
601
602
|
}
|
|
602
603
|
)
|
|
603
604
|
|
|
605
|
+
// Static Assets: Favicon
|
|
606
|
+
.get('/favicon.ico', () => {
|
|
607
|
+
const faviconPath = new URL('./assets/favicon.ico', import.meta.url).pathname
|
|
608
|
+
const content = readFileSync(faviconPath)
|
|
609
|
+
return new Response(content, {
|
|
610
|
+
headers: { 'Content-Type': 'image/x-icon', 'Cache-Control': 'public, max-age=86400' },
|
|
611
|
+
})
|
|
612
|
+
})
|
|
613
|
+
|
|
614
|
+
// Static Assets: Logo
|
|
615
|
+
.get('/logo.png', () => {
|
|
616
|
+
const logoPath = new URL('./assets/logo.png', import.meta.url).pathname
|
|
617
|
+
const content = readFileSync(logoPath)
|
|
618
|
+
return new Response(content, {
|
|
619
|
+
headers: { 'Content-Type': 'image/png', 'Cache-Control': 'public, max-age=86400' },
|
|
620
|
+
})
|
|
621
|
+
})
|
|
622
|
+
|
|
604
623
|
// UI: Dashboard
|
|
605
624
|
.get('/', () => {
|
|
606
625
|
const jobStatus = getJobStatus()
|
|
@@ -14,7 +14,7 @@ export const ActionArea = () => `
|
|
|
14
14
|
<button
|
|
15
15
|
@click="runBackup()"
|
|
16
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-
|
|
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-primary-500 rounded-xl hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 disabled:opacity-70 disabled:cursor-not-allowed">
|
|
18
18
|
<span x-show="!loading" class="flex items-center gap-2">
|
|
19
19
|
<i data-lucide="play-circle" class="w-5 h-5"></i>
|
|
20
20
|
Start Backup Process
|
|
@@ -57,10 +57,10 @@ export const FilesTab = () => `
|
|
|
57
57
|
x-data="holdButton(() => restoreFile(file.key))"
|
|
58
58
|
@mousedown="start()" @touchstart.prevent="start()"
|
|
59
59
|
@mouseup="stop()" @mouseleave="stop()" @touchend="stop()"
|
|
60
|
-
class="relative overflow-hidden px-4 py-2 rounded-lg text-xs font-bold uppercase tracking-wider text-
|
|
60
|
+
class="relative overflow-hidden px-4 py-2 rounded-lg text-xs font-bold uppercase tracking-wider text-primary-600 bg-primary-50 hover:bg-primary-100 transition-colors select-none"
|
|
61
61
|
title="Hold 3s to Restore"
|
|
62
62
|
>
|
|
63
|
-
<div class="absolute inset-0 bg-
|
|
63
|
+
<div class="absolute inset-0 bg-primary-200/50 origin-left transition-all duration-0 ease-linear" :style="'width: ' + progress + '%'"></div>
|
|
64
64
|
<span class="relative z-10 flex items-center gap-2">
|
|
65
65
|
<i data-lucide="rotate-ccw" class="w-3 h-3"></i>
|
|
66
66
|
<span x-text="progress > 0 ? 'Hold...' : 'Restore'"></span>
|
|
@@ -8,6 +8,7 @@ export const Head = ({ title = 'Backup Manager' }) => `
|
|
|
8
8
|
<meta charset="UTF-8">
|
|
9
9
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
10
10
|
<title>${title}</title>
|
|
11
|
+
<link rel="icon" type="image/x-icon" href="/backup/favicon.ico">
|
|
11
12
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
12
13
|
<script>
|
|
13
14
|
tailwind.config = {
|
|
@@ -17,6 +18,18 @@ export const Head = ({ title = 'Backup Manager' }) => `
|
|
|
17
18
|
sans: ['Montserrat', 'sans-serif'],
|
|
18
19
|
},
|
|
19
20
|
colors: {
|
|
21
|
+
primary: {
|
|
22
|
+
50: '#E8F4FD',
|
|
23
|
+
100: '#C5E3FA',
|
|
24
|
+
200: '#9DD0F6',
|
|
25
|
+
300: '#6DB9F1',
|
|
26
|
+
400: '#47A7ED',
|
|
27
|
+
500: '#209CEE',
|
|
28
|
+
600: '#1A8AD8',
|
|
29
|
+
700: '#1574B8',
|
|
30
|
+
800: '#115E95',
|
|
31
|
+
900: '#0C4A77',
|
|
32
|
+
},
|
|
20
33
|
gray: {
|
|
21
34
|
50: '#F9FAFB',
|
|
22
35
|
100: '#F3F4F6',
|
|
@@ -5,33 +5,35 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export const Header = ({ hasAuth }) => `
|
|
7
7
|
<div class="flex flex-col md:flex-row md:items-end justify-between mb-16 gap-6">
|
|
8
|
-
<div>
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
<div class="flex items-center gap-4">
|
|
9
|
+
<img src="/backup/logo.png" alt="Backup Manager" class="w-12 h-12 rounded-lg">
|
|
10
|
+
<div>
|
|
11
|
+
<h6 class="text-xs font-bold tracking-widest text-gray-500 uppercase mb-1 flex items-center gap-2">
|
|
12
|
+
System Administration
|
|
13
|
+
</h6>
|
|
14
|
+
<h1 class="text-3xl font-bold text-gray-900 tracking-tight">
|
|
15
|
+
Backup Manager
|
|
16
|
+
</h1>
|
|
17
|
+
</div>
|
|
16
18
|
</div>
|
|
17
19
|
|
|
18
20
|
<div class="flex items-center gap-4">
|
|
19
21
|
<!-- Tabs -->
|
|
20
|
-
<div class="flex p-1 bg-
|
|
22
|
+
<div class="flex p-1 bg-primary-100/50 rounded-xl">
|
|
21
23
|
<button @click="activeTab = 'dashboard'; $nextTick(() => lucide.createIcons())"
|
|
22
|
-
:class="activeTab === 'dashboard' ? 'bg-white text-
|
|
24
|
+
:class="activeTab === 'dashboard' ? 'bg-white text-primary-700 shadow-sm' : 'text-gray-500 hover:text-primary-600'"
|
|
23
25
|
class="px-6 py-2.5 rounded-lg text-sm font-semibold transition-all duration-200 flex items-center gap-2">
|
|
24
26
|
<i data-lucide="layout-dashboard" class="w-4 h-4"></i>
|
|
25
27
|
Overview
|
|
26
28
|
</button>
|
|
27
29
|
<button @click="activeTab = 'files'; fetchFiles()"
|
|
28
|
-
:class="activeTab === 'files' ? 'bg-white text-
|
|
30
|
+
:class="activeTab === 'files' ? 'bg-white text-primary-700 shadow-sm' : 'text-gray-500 hover:text-primary-600'"
|
|
29
31
|
class="px-6 py-2.5 rounded-lg text-sm font-semibold transition-all duration-200 flex items-center gap-2">
|
|
30
32
|
<i data-lucide="folder-open" class="w-4 h-4"></i>
|
|
31
33
|
Files & Restore
|
|
32
34
|
</button>
|
|
33
35
|
<button @click="activeTab = 'settings'; $nextTick(() => lucide.createIcons())"
|
|
34
|
-
:class="activeTab === 'settings' ? 'bg-white text-
|
|
36
|
+
:class="activeTab === 'settings' ? 'bg-white text-primary-700 shadow-sm' : 'text-gray-500 hover:text-primary-600'"
|
|
35
37
|
class="px-6 py-2.5 rounded-lg text-sm font-semibold transition-all duration-200 flex items-center gap-2">
|
|
36
38
|
<i data-lucide="settings" class="w-4 h-4"></i>
|
|
37
39
|
Settings
|
|
@@ -9,9 +9,7 @@ export const LoginCard = ({ totpEnabled }) => `
|
|
|
9
9
|
<div class="bg-white rounded-2xl border border-gray-100 shadow-[0_8px_30px_rgba(0,0,0,0.08)] overflow-hidden">
|
|
10
10
|
<!-- Header -->
|
|
11
11
|
<div class="p-10 text-center border-b border-gray-100">
|
|
12
|
-
<
|
|
13
|
-
<i data-lucide="shield-check" class="w-8 h-8 text-white"></i>
|
|
14
|
-
</div>
|
|
12
|
+
<img src="/backup/logo.png" alt="Backup Manager" class="w-20 h-20 mx-auto mb-4 rounded-xl">
|
|
15
13
|
<h1 class="text-2xl font-bold text-gray-900 mb-2">Backup Manager</h1>
|
|
16
14
|
<p class="text-sm text-gray-500">Access Control Panel</p>
|
|
17
15
|
</div>
|
|
@@ -38,7 +36,7 @@ export const LoginCard = ({ totpEnabled }) => `
|
|
|
38
36
|
type="text"
|
|
39
37
|
x-model="username"
|
|
40
38
|
required
|
|
41
|
-
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-
|
|
39
|
+
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-primary-500 focus:border-transparent outline-none transition-all font-medium"
|
|
42
40
|
placeholder="Enter your username"
|
|
43
41
|
autofocus
|
|
44
42
|
>
|
|
@@ -56,7 +54,7 @@ export const LoginCard = ({ totpEnabled }) => `
|
|
|
56
54
|
type="password"
|
|
57
55
|
x-model="password"
|
|
58
56
|
required
|
|
59
|
-
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-
|
|
57
|
+
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-primary-500 focus:border-transparent outline-none transition-all font-medium"
|
|
60
58
|
placeholder="Enter your password"
|
|
61
59
|
>
|
|
62
60
|
</div>
|
|
@@ -76,7 +74,7 @@ export const LoginCard = ({ totpEnabled }) => `
|
|
|
76
74
|
pattern="[0-9]*"
|
|
77
75
|
maxlength="6"
|
|
78
76
|
:required="totpEnabled"
|
|
79
|
-
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-
|
|
77
|
+
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-primary-500 focus:border-transparent outline-none transition-all font-medium tracking-widest text-center text-lg"
|
|
80
78
|
placeholder="000000"
|
|
81
79
|
>
|
|
82
80
|
</div>
|
|
@@ -90,7 +88,7 @@ export const LoginCard = ({ totpEnabled }) => `
|
|
|
90
88
|
<button
|
|
91
89
|
type="submit"
|
|
92
90
|
:disabled="loading"
|
|
93
|
-
class="w-full bg-
|
|
91
|
+
class="w-full bg-primary-500 hover:bg-primary-600 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"
|
|
94
92
|
>
|
|
95
93
|
<span x-show="!loading" class="flex items-center gap-2">
|
|
96
94
|
<span>Sign In</span>
|
|
@@ -24,7 +24,7 @@ export const SecuritySection = () => `
|
|
|
24
24
|
<div x-show="!totpEnabled && !showTotpSetup">
|
|
25
25
|
<button
|
|
26
26
|
@click="generateTotp()"
|
|
27
|
-
class="bg-
|
|
27
|
+
class="bg-primary-500 hover:bg-primary-600 text-white font-bold py-2.5 px-5 rounded-lg transition-all flex items-center gap-2"
|
|
28
28
|
>
|
|
29
29
|
<i data-lucide="plus" class="w-4 h-4"></i>
|
|
30
30
|
Enable 2FA
|
|
@@ -73,7 +73,7 @@ export const SecuritySection = () => `
|
|
|
73
73
|
pattern="[0-9]*"
|
|
74
74
|
maxlength="6"
|
|
75
75
|
placeholder="000000"
|
|
76
|
-
class="flex-grow bg-gray-50 border border-gray-200 rounded-lg px-4 py-3 text-gray-900 focus:ring-2 focus:ring-
|
|
76
|
+
class="flex-grow bg-gray-50 border border-gray-200 rounded-lg px-4 py-3 text-gray-900 focus:ring-2 focus:ring-primary-500 focus:border-transparent outline-none transition-all font-medium tracking-widest text-center text-lg"
|
|
77
77
|
>
|
|
78
78
|
<button
|
|
79
79
|
@click="verifyTotp()"
|
|
@@ -131,7 +131,7 @@ export const SecuritySection = () => `
|
|
|
131
131
|
pattern="[0-9]*"
|
|
132
132
|
maxlength="6"
|
|
133
133
|
placeholder="000000"
|
|
134
|
-
class="flex-grow bg-gray-50 border border-gray-200 rounded-lg px-4 py-3 text-gray-900 focus:ring-2 focus:ring-
|
|
134
|
+
class="flex-grow bg-gray-50 border border-gray-200 rounded-lg px-4 py-3 text-gray-900 focus:ring-2 focus:ring-primary-500 focus:border-transparent outline-none transition-all font-medium tracking-widest text-center text-lg"
|
|
135
135
|
>
|
|
136
136
|
<button
|
|
137
137
|
@click="disableTotp()"
|
|
@@ -12,16 +12,16 @@ export const SettingsTab = () => `
|
|
|
12
12
|
<div class="space-y-6">
|
|
13
13
|
<div>
|
|
14
14
|
<label class="block text-sm font-semibold text-gray-700 mb-2">Endpoint URL</label>
|
|
15
|
-
<input type="text" x-model="configForm.endpoint" class="w-full bg-gray-50 border border-gray-200 rounded-lg px-4 py-3 text-gray-900 focus:ring-2 focus:ring-
|
|
15
|
+
<input type="text" x-model="configForm.endpoint" class="w-full bg-gray-50 border border-gray-200 rounded-lg px-4 py-3 text-gray-900 focus:ring-2 focus:ring-primary-500 focus:border-transparent outline-none transition-all font-medium">
|
|
16
16
|
</div>
|
|
17
17
|
<div class="grid grid-cols-2 gap-6">
|
|
18
18
|
<div>
|
|
19
19
|
<label class="block text-sm font-semibold text-gray-700 mb-2">Bucket Name</label>
|
|
20
|
-
<input type="text" x-model="configForm.bucket" class="w-full bg-gray-50 border border-gray-200 rounded-lg px-4 py-3 text-gray-900 focus:ring-2 focus:ring-
|
|
20
|
+
<input type="text" x-model="configForm.bucket" class="w-full bg-gray-50 border border-gray-200 rounded-lg px-4 py-3 text-gray-900 focus:ring-2 focus:ring-primary-500 focus:border-transparent outline-none transition-all font-medium">
|
|
21
21
|
</div>
|
|
22
22
|
<div>
|
|
23
23
|
<label class="block text-sm font-semibold text-gray-700 mb-2">Prefix (Folder)</label>
|
|
24
|
-
<input type="text" x-model="configForm.prefix" class="w-full bg-gray-50 border border-gray-200 rounded-lg px-4 py-3 text-gray-900 focus:ring-2 focus:ring-
|
|
24
|
+
<input type="text" x-model="configForm.prefix" class="w-full bg-gray-50 border border-gray-200 rounded-lg px-4 py-3 text-gray-900 focus:ring-2 focus:ring-primary-500 focus:border-transparent outline-none transition-all font-medium">
|
|
25
25
|
</div>
|
|
26
26
|
</div>
|
|
27
27
|
<div>
|
|
@@ -31,13 +31,13 @@ export const SettingsTab = () => `
|
|
|
31
31
|
</div>
|
|
32
32
|
<div>
|
|
33
33
|
<label class="block text-sm font-semibold text-gray-700 mb-2">Allowed Extensions (comma separated)</label>
|
|
34
|
-
<input type="text" x-model="configForm.extensions" placeholder=".db, .sqlite" class="w-full bg-gray-50 border border-gray-200 rounded-lg px-4 py-3 text-gray-900 focus:ring-2 focus:ring-
|
|
34
|
+
<input type="text" x-model="configForm.extensions" placeholder=".db, .sqlite" class="w-full bg-gray-50 border border-gray-200 rounded-lg px-4 py-3 text-gray-900 focus:ring-2 focus:ring-primary-500 focus:border-transparent outline-none transition-all font-medium">
|
|
35
35
|
</div>
|
|
36
36
|
<div>
|
|
37
37
|
<label class="block text-sm font-semibold text-gray-700 mb-2">Cron Schedule</label>
|
|
38
38
|
<div class="flex gap-4 items-center">
|
|
39
39
|
<div class="relative flex-grow">
|
|
40
|
-
<input type="text" x-model="configForm.cronSchedule" placeholder="0 0 * * *" class="w-full bg-gray-50 border border-gray-200 rounded-lg px-4 py-3 text-gray-900 focus:ring-2 focus:ring-
|
|
40
|
+
<input type="text" x-model="configForm.cronSchedule" placeholder="0 0 * * *" class="w-full bg-gray-50 border border-gray-200 rounded-lg px-4 py-3 text-gray-900 focus:ring-2 focus:ring-primary-500 focus:border-transparent outline-none transition-all font-medium">
|
|
41
41
|
<div class="absolute right-3 top-1/2 -translate-y-1/2 text-xs text-gray-500">
|
|
42
42
|
<a href="https://crontab.guru/" target="_blank" class="underline hover:text-gray-800">Help</a>
|
|
43
43
|
</div>
|
|
@@ -68,17 +68,17 @@ export const SettingsTab = () => `
|
|
|
68
68
|
<div class="grid grid-cols-2 gap-6">
|
|
69
69
|
<div>
|
|
70
70
|
<label class="block text-sm font-semibold text-gray-700 mb-2">Access Key ID</label>
|
|
71
|
-
<input type="text" x-model="configForm.accessKeyId" class="w-full bg-gray-50 border border-gray-200 rounded-lg px-4 py-3 text-gray-900 focus:ring-2 focus:ring-
|
|
71
|
+
<input type="text" x-model="configForm.accessKeyId" class="w-full bg-gray-50 border border-gray-200 rounded-lg px-4 py-3 text-gray-900 focus:ring-2 focus:ring-primary-500 focus:border-transparent outline-none transition-all font-medium">
|
|
72
72
|
</div>
|
|
73
73
|
<div>
|
|
74
74
|
<label class="block text-sm font-semibold text-gray-700 mb-2">Secret Access Key</label>
|
|
75
|
-
<input type="password" x-model="configForm.secretAccessKey" class="w-full bg-gray-50 border border-gray-200 rounded-lg px-4 py-3 text-gray-900 focus:ring-2 focus:ring-
|
|
75
|
+
<input type="password" x-model="configForm.secretAccessKey" class="w-full bg-gray-50 border border-gray-200 rounded-lg px-4 py-3 text-gray-900 focus:ring-2 focus:ring-primary-500 focus:border-transparent outline-none transition-all font-medium">
|
|
76
76
|
</div>
|
|
77
77
|
</div>
|
|
78
78
|
</div>
|
|
79
79
|
|
|
80
80
|
<div class="pt-4 flex justify-end">
|
|
81
|
-
<button type="submit" class="bg-
|
|
81
|
+
<button type="submit" class="bg-primary-500 hover:bg-primary-600 text-white font-bold py-3 px-8 rounded-xl transition-all shadow-lg hover:shadow-xl transform hover:-translate-y-0.5 flex items-center gap-2">
|
|
82
82
|
<i data-lucide="save" class="w-4 h-4"></i>
|
|
83
83
|
Save Changes
|
|
84
84
|
</button>
|