From ade032241aa03d2140147676f29efd2839a57dc8 Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Thu, 21 Dec 2023 18:12:58 +0000 Subject: [PATCH] backups: upload and restore backup in-app --- api.go | 26 ++++++++++++++++++++++++-- html/admin.html | 1 + router.go | 1 + ts/modules/common.ts | 9 +++++++++ ts/modules/settings.ts | 17 ++++++++++++++++- 5 files changed, 51 insertions(+), 3 deletions(-) diff --git a/api.go b/api.go index 97d54a9..9ff95c8 100644 --- a/api.go +++ b/api.go @@ -611,8 +611,7 @@ func (app *appContext) GetBackups(gc *gin.Context) { // @Summary Restore a backup file stored locally to the server. // @Param fname path string true "backup filename" -// @Router /backups/restore/{fname} [get] -// @Produce octet-stream +// @Router /backups/restore/{fname} [post] // @Produce json // @Failure 400 {object} boolResponse // @Security Bearer @@ -632,3 +631,26 @@ func (app *appContext) RestoreLocalBackup(gc *gin.Context) { LOADBAK = fullpath app.restart(gc) } + +// @Summary Restore a backup file uploaded by the user. +// @Param file formData file true ".bak file" +// @Router /backups/restore [post] +// @Produce json +// @Failure 400 {object} boolResponse +// @Security Bearer +// @tags Other +func (app *appContext) RestoreBackup(gc *gin.Context) { + file, err := gc.FormFile("backups-file") + if err != nil { + app.err.Printf("Failed to get file from form data: %v\n", err) + respondBool(400, false, gc) + return + } + app.debug.Printf("Got uploaded file \"%s\"\n", file.Filename) + path := app.config.Section("backups").Key("path").String() + fullpath := filepath.Join(path, "jfa-go-upload-bak-"+time.Now().Local().Format(BACKUP_DATEFMT)+BACKUP_SUFFIX) + gc.SaveUploadedFile(file, fullpath) + app.debug.Printf("Saved to \"%s\"\n", fullpath) + LOADBAK = fullpath + app.restart(gc) +} diff --git a/html/admin.html b/html/admin.html index cad852b..1a9486a 100644 --- a/html/admin.html +++ b/html/admin.html @@ -336,6 +336,7 @@
+
diff --git a/router.go b/router.go index fa49348..0a0cc9c 100644 --- a/router.go +++ b/router.go @@ -212,6 +212,7 @@ func (app *appContext) loadRoutes(router *gin.Engine) { api.GET(p+"/backups/:fname", app.GetBackup) api.GET(p+"/backups", app.GetBackups) api.POST(p+"/backups/restore/:fname", app.RestoreLocalBackup) + api.POST(p+"/backups/restore", app.RestoreBackup) if telegramEnabled || discordEnabled || matrixEnabled { api.GET(p+"/telegram/pin", app.TelegramGetPin) api.GET(p+"/telegram/verified/:pin", app.TelegramVerified) diff --git a/ts/modules/common.ts b/ts/modules/common.ts index 9de70fd..7ce95e8 100644 --- a/ts/modules/common.ts +++ b/ts/modules/common.ts @@ -56,6 +56,15 @@ export const _download = (url: string, fname: string): void => { req.send(); }; +export const _upload = (url: string, formData: FormData): void => { + let req = new XMLHttpRequest(); + if (window.URLBase) { url = window.URLBase + url; } + req.open("POST", url, true); + req.setRequestHeader("Authorization", "Bearer " + window.token); + // req.setRequestHeader('Content-Type', 'multipart/form-data'); + req.send(formData); +}; + export const _post = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, response?: boolean, statusHandler?: (req: XMLHttpRequest) => void): void => { let req = new XMLHttpRequest(); req.open("POST", window.URLBase + url, true); diff --git a/ts/modules/settings.ts b/ts/modules/settings.ts index 4244abf..2dc1e10 100644 --- a/ts/modules/settings.ts +++ b/ts/modules/settings.ts @@ -1,4 +1,4 @@ -import { _get, _post, _delete, _download, toggleLoader, addLoader, removeLoader, insertText, toClipboard, toDateString } from "../modules/common.js"; +import { _get, _post, _delete, _download, _upload, toggleLoader, addLoader, removeLoader, insertText, toClipboard, toDateString } from "../modules/common.js"; import { Marked } from "@ts-stack/markdown"; import { stripMarkdown } from "../modules/stripmd.js"; @@ -830,6 +830,21 @@ export class settingsList { }; this._backupSortDirection.onclick = () => this.setBackupSort(!(this._backupSortAscending)); const advancedEnableToggle = document.getElementById("settings-advanced-enabled") as HTMLInputElement; + + const filedlg = document.getElementById("backups-file") as HTMLInputElement; + document.getElementById("settings-backups-upload").onclick = () => { + filedlg.click(); + }; + filedlg.addEventListener("change", () => { + if (filedlg.files.length == 0) return; + const form = new FormData(); + form.append("backups-file", filedlg.files[0], filedlg.files[0].name); + _upload("/backups/restore", form); + window.modals.backups.close(); + window.modals.settingsRefresh.modal.querySelector("span.heading").textContent = window.lang.strings("settingsRestarting"); + window.modals.settingsRefresh.show(); + }); + advancedEnableToggle.onchange = () => { document.dispatchEvent(new CustomEvent("settings-advancedState", { detail: advancedEnableToggle.checked })); const parent = advancedEnableToggle.parentElement;