@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riligar/elysia-backup",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Elysia plugin for R2/S3 backup with a built-in UI dashboard",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
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
- if (path === '/backup/login' || path === '/backup/auth/login' || path === '/backup/auth/logout') {
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-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">
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-blue-600 bg-blue-50 hover:bg-blue-100 transition-colors select-none"
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-blue-200/50 origin-left transition-all duration-0 ease-linear" :style="'width: ' + progress + '%'"></div>
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
- <h6 class="text-xs font-bold tracking-widest text-gray-500 uppercase mb-2 flex items-center gap-2">
10
- <i data-lucide="shield-check" class="w-4 h-4"></i>
11
- System Administration
12
- </h6>
13
- <h1 class="text-4xl font-bold text-gray-900 tracking-tight flex items-center gap-3">
14
- Backup Manager
15
- </h1>
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-gray-200/50 rounded-xl">
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-gray-900 shadow-sm' : 'text-gray-500 hover:text-gray-700'"
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-gray-900 shadow-sm' : 'text-gray-500 hover:text-gray-700'"
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-gray-900 shadow-sm' : 'text-gray-500 hover:text-gray-700'"
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
- <div class="w-16 h-16 bg-gray-900 rounded-full flex items-center justify-center mx-auto mb-4">
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-gray-900 focus:border-transparent outline-none transition-all font-medium"
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-gray-900 focus:border-transparent outline-none transition-all font-medium"
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-gray-900 focus:border-transparent outline-none transition-all font-medium tracking-widest text-center text-lg"
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-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"
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-gray-900 hover:bg-gray-800 text-white font-bold py-2.5 px-5 rounded-lg transition-all flex items-center gap-2"
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-gray-900 focus:border-transparent outline-none transition-all font-medium tracking-widest text-center text-lg"
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-gray-900 focus:border-transparent outline-none transition-all font-medium tracking-widest text-center text-lg"
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-gray-900 focus:border-transparent outline-none transition-all font-medium">
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-gray-900 focus:border-transparent outline-none transition-all font-medium">
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-gray-900 focus:border-transparent outline-none transition-all font-medium">
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-gray-900 focus:border-transparent outline-none transition-all font-medium">
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-gray-900 focus:border-transparent outline-none transition-all font-medium">
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-gray-900 focus:border-transparent outline-none transition-all font-medium">
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-gray-900 focus:border-transparent outline-none transition-all font-medium">
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-gray-900 hover:bg-gray-800 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">
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>