From 3597b4106c1bd3eb806a65ea21d42168f98e42b7 Mon Sep 17 00:00:00 2001 From: Verum Date: Fri, 20 Mar 2026 17:03:08 +0700 Subject: [PATCH] =?UTF-8?q?=D0=A2=D0=B5=D1=81=D1=82=20=D0=B1=D0=B5=D0=B7?= =?UTF-8?q?=20=D0=BA=D0=BE=D0=B4=D0=B5=D0=BA=D0=B0=D1=81=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 ++- public/assets/app.js | 51 ++++++++++++++++++++++++++++++++++++ public/index.html | 6 ++++- server.js | 57 +++++++++++++++++++++++++++++++++++++++++ src/services/backups.js | 41 +++++++++++++++++++++++++++++ 5 files changed, 156 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8b5231b..1cbdd53 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "dotenv": "^16.3.1", "cors": "^2.8.5", "express-session": "^1.17.3", - "bcryptjs": "^2.4.3" + "bcryptjs": "^2.4.3", + "multer": "^1.4.5-lts.1" }, "devDependencies": { "nodemon": "^3.0.1" diff --git a/public/assets/app.js b/public/assets/app.js index 9c66407..62a2b1c 100644 --- a/public/assets/app.js +++ b/public/assets/app.js @@ -1342,6 +1342,57 @@ } } + async uploadBackup() { + const fileInput = document.getElementById('backupFileInput'); + const file = fileInput.files[0]; + + if (!file) { + this.showToast('No file selected', 'error'); + return; + } + + if (!file.name.endsWith('.tar.gz')) { + this.showToast('Only .tar.gz files are supported', 'error'); + fileInput.value = ''; + return; + } + + try { + // Show loading state + const uploadBtn = Array.from(document.querySelectorAll('button')).find(btn => btn.textContent.includes('Upload archive')); + const originalText = uploadBtn.textContent; + uploadBtn.disabled = true; + uploadBtn.textContent = 'Uploading...'; + + const formData = new FormData(); + formData.append('file', file); + + const response = await fetch('/api/backups/upload', { + method: 'POST', + body: formData, + }); + + const result = await response.json(); + + if (!response.ok) { + throw new Error(result.error || 'Failed to upload backup'); + } + + this.showToast('Backup uploaded successfully', 'success'); + fileInput.value = ''; + await this.loadBackups(); + } catch (err) { + this.showToast(err.message, 'error'); + fileInput.value = ''; + } finally { + const uploadBtn = Array.from(document.querySelectorAll('button')).find(btn => btn.textContent.includes('Upload') || btn.textContent.includes('Uploading')); + if (uploadBtn) { + uploadBtn.disabled = false; + uploadBtn.textContent = 'Upload archive'; + } + } + } + async loadSettings() { try { const response = await fetch('/api/settings'); diff --git a/public/index.html b/public/index.html index e41e80e..abf922b 100644 --- a/public/index.html +++ b/public/index.html @@ -330,7 +330,11 @@ SELECT * FROM users LIMIT 10;">

Backups

Archives contain the SQL dump and, if enabled, the application snapshot.

- +
+ + + +