added backup restore functionality, refactor of backup test

This commit is contained in:
BroodjeAap 2023-01-30 20:21:05 +00:00
parent 17f29cb175
commit e64b610fed
6 changed files with 271 additions and 25 deletions

149
main.go
View file

@ -197,7 +197,8 @@ func (web *Web) initRouter() {
web.router.GET("/backup/view", web.backupView) web.router.GET("/backup/view", web.backupView)
web.router.GET("/backup/create", web.backupCreate) web.router.GET("/backup/create", web.backupCreate)
web.router.GET("/backup/test/:id", web.backupTest) web.router.POST("/backup/test", web.backupTest)
web.router.POST("/backup/restore", web.backupRestore)
web.router.SetTrustedProxies(nil) web.router.SetTrustedProxies(nil)
} }
@ -223,6 +224,7 @@ func (web *Web) initTemplates() {
web.templates.Add("backupView", template.Must(template.ParseFS(templatesFS, "base.html", "backup/view.html"))) web.templates.Add("backupView", template.Must(template.ParseFS(templatesFS, "base.html", "backup/view.html")))
web.templates.Add("backupTest", template.Must(template.ParseFS(templatesFS, "base.html", "backup/test.html"))) web.templates.Add("backupTest", template.Must(template.ParseFS(templatesFS, "base.html", "backup/test.html")))
web.templates.Add("backupRestore", template.Must(template.ParseFS(templatesFS, "base.html", "backup/restore.html")))
web.templates.Add("500", template.Must(template.ParseFS(templatesFS, "base.html", "500.html"))) web.templates.Add("500", template.Must(template.ParseFS(templatesFS, "base.html", "500.html")))
} }
@ -973,12 +975,12 @@ func (web *Web) createBackup(backupPath string) error {
// backupTest (/backup/test) tests the selected backup file // backupTest (/backup/test) tests the selected backup file
func (web *Web) backupTest(c *gin.Context) { func (web *Web) backupTest(c *gin.Context) {
importID, err := strconv.Atoi(c.Param("id")) importID, err := strconv.Atoi(c.PostForm("id"))
if err != nil { if err != nil {
c.AbortWithError(http.StatusBadRequest, err) c.AbortWithError(http.StatusBadRequest, err)
return return
} }
if importID < 0 { if importID < -1 {
c.Redirect(http.StatusSeeOther, "/backup/view") c.Redirect(http.StatusSeeOther, "/backup/view")
return return
} }
@ -995,45 +997,160 @@ func (web *Web) backupTest(c *gin.Context) {
return return
} }
backupFullPath := ""
var backup Backup
if importID >= 0 {
backupFullPath, err = web.backupFromFile(importID, &backup)
} else { // uploaded backup file
backupFullPath, err = web.backupFromUpload(c, &backup)
}
if err != nil {
c.HTML(http.StatusOK, "backupTest", gin.H{"Error": err})
return
}
c.HTML(http.StatusOK, "backupTest", gin.H{
"Backup": backup,
"BackupPath": backupFullPath,
})
}
func (web *Web) backupFromFile(importID int, backup *Backup) (string, error) {
backupPath := viper.GetString("database.backup.path") backupPath := viper.GetString("database.backup.path")
backupDir, err := filepath.Abs(filepath.Dir(backupPath)) backupDir, err := filepath.Abs(filepath.Dir(backupPath))
if err != nil { if err != nil {
c.HTML(http.StatusOK, "backupTest", gin.H{"Error": err}) return "", err
return
} }
filesInBackupDir, err := ioutil.ReadDir(backupDir) filesInBackupDir, err := ioutil.ReadDir(backupDir)
if err != nil { if err != nil {
c.HTML(http.StatusOK, "backupTest", gin.H{"Error": err}) return "", err
return
} }
if importID >= len(filesInBackupDir) { if importID >= len(filesInBackupDir) {
c.Redirect(http.StatusSeeOther, "/backup/view") return "", err
return
} }
backupFileName := filesInBackupDir[importID] backupFileName := filesInBackupDir[importID]
backupFullPath := filepath.Join(backupDir, backupFileName.Name()) backupFullPath := filepath.Join(backupDir, backupFileName.Name())
backupFile, err := os.Open(backupFullPath) backupFile, err := os.Open(backupFullPath)
if err != nil { if err != nil {
c.HTML(http.StatusOK, "backupTest", gin.H{"Error": err}) return "", err
return
} }
defer backupFile.Close() defer backupFile.Close()
backupReader, err := gzip.NewReader(backupFile) backupReader, err := gzip.NewReader(backupFile)
if err != nil { if err != nil {
c.HTML(http.StatusOK, "backupTest", gin.H{"Error": err}) return "", err
return }
defer backupReader.Close()
rawBytes, err := io.ReadAll(backupReader)
err = json.Unmarshal(rawBytes, backup)
if err != nil {
return "", err
}
return backupFullPath, nil
}
func (web *Web) backupFromUpload(c *gin.Context, backup *Backup) (string, error) {
upload, err := c.FormFile("upload")
if err != nil {
return "", err
}
backupFullPath := upload.Filename + " (Uploaded)"
uploadFile, err := upload.Open()
if err != nil {
return "", err
}
defer uploadFile.Close()
backupReader, err := gzip.NewReader(uploadFile)
if err != nil {
return "", err
} }
defer backupReader.Close() defer backupReader.Close()
rawBytes, err := io.ReadAll(backupReader) rawBytes, err := io.ReadAll(backupReader)
var backup Backup err = json.Unmarshal(rawBytes, &backup)
json.Unmarshal(rawBytes, &backup) if err != nil {
return "", err
}
return backupFullPath, nil
}
c.HTML(http.StatusOK, "backupTest", gin.H{ // backupRestore (/backup/restore/:id) either restores the filesInBackupDir[id] file or from an uploaded file
func (web *Web) backupRestore(c *gin.Context) {
importID, err := strconv.Atoi(c.PostForm("id"))
if err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
if importID < -1 {
c.Redirect(http.StatusSeeOther, "/backup/view")
return
}
if !viper.IsSet("database.backup") {
c.HTML(http.StatusOK, "backupRestore", gin.H{"Error": "database.backup not set"})
return
}
if !viper.IsSet("database.backup.schedule") {
c.HTML(http.StatusOK, "backupRestore", gin.H{"Error": "database.backup.schedule not set"})
return
}
if !viper.IsSet("database.backup.path") {
c.HTML(http.StatusOK, "backupRestore", gin.H{"Error": "database.backup.path not set"})
return
}
backupFullPath := ""
var backup Backup
if importID >= 0 {
backupFullPath, err = web.backupFromFile(importID, &backup)
} else { // uploaded backup file
backupFullPath, err = web.backupFromUpload(c, &backup)
}
if err != nil {
c.HTML(http.StatusOK, "backupRestore", gin.H{"Error": err})
return
}
err = web.db.Transaction(func(tx *gorm.DB) error {
delete := tx.Where("1 = 1").Delete(&Watch{})
if delete.Error != nil {
return err
}
watches := tx.Create(&backup.Watches)
if watches.Error != nil {
return err
}
filters := tx.Create(&backup.Filters)
if filters.Error != nil {
return err
}
connections := tx.Create(&backup.Connections)
if connections.Error != nil {
return err
}
values := tx.Create(&backup.Values)
if values.Error != nil {
return err
}
return nil
})
if err != nil {
c.HTML(http.StatusOK, "backupRestore", gin.H{"Error": err})
return
}
c.HTML(http.StatusOK, "backupRestore", gin.H{
"Backup": backup, "Backup": backup,
"BackupPath": backupFullPath, "BackupPath": backupFullPath,
}) })

17
static/backup.js Normal file
View file

@ -0,0 +1,17 @@
function testSubmit() {
var form = document.getElementById("uploadForm");
form.action = "/backup/test";
form.submit();
}
function restoreSubmit() {
var form = document.getElementById("uploadForm");
form.action = "/backup/restore";
form.submit();
}
function initUploadSubmit() {
var testSubmitInput = document.getElementById("testSubmit");
testSubmitInput.onclick = testSubmit;
var restoreSubmitInput = document.getElementById("restoreSubmit");
restoreSubmitInput.onclick = restoreSubmit;
}
document.addEventListener('DOMContentLoaded', initUploadSubmit, false);

21
static/backup.ts Normal file
View file

@ -0,0 +1,21 @@
function testSubmit() {
let form = document.getElementById("uploadForm") as HTMLFormElement;
form.action = "/backup/test";
form.submit();
}
function restoreSubmit() {
let form = document.getElementById("uploadForm") as HTMLFormElement;
form.action = "/backup/restore";
form.submit();
}
function initUploadSubmit(){
let testSubmitInput = document.getElementById("testSubmit") as HTMLInputElement;
testSubmitInput.onclick = testSubmit;
let restoreSubmitInput = document.getElementById("restoreSubmit") as HTMLInputElement;
restoreSubmitInput.onclick = restoreSubmit;
}
document.addEventListener('DOMContentLoaded', initUploadSubmit, false);

View file

@ -0,0 +1,69 @@
{{define "title"}}
GoWatch Backup Restore
{{end}}
{{define "content"}}
<div class="container row">
<div class="row h3 justify-content-center {{ if .Error }} text-danger {{ else }} text-success {{ end }}">
Restore: {{ .BackupPath }}
</div>
{{ if .Error }}
<div class="row h3 justify-content-center">
{{ .Error }}
</div>
{{ end }}
<table class="table table-striped table-hover caption-top">
<caption class="h3">Watches</caption>
<thead>
<tr class="table-dark">
<th>ID</th>
<th>Name</th>
</tr>
</thead>
<tbody>
{{ range $watch := .Backup.Watches }}
<tr>
<td class="h5">{{ $watch.ID }}</td>
<td class="h5">{{ $watch.Name }}</td>
</tr>
{{ end }}
</tbody>
</table>
<div class="row h4 justify-content-center">
Stored Values: {{ len .Backup.Values }}
</div>
<table class="table table-striped table-hover caption-top">
<caption class="h3">Filters</caption>
<thead>
<tr class="table-dark">
<th>ID</th>
<th>WatchID</th>
<th>Name</th>
<th>Type</th>
<th>Var1</th>
<th>Var2</th>
<th>Var3</th>
<th>X/Y</th>
</tr>
</thead>
<tbody>
{{ range $filter := .Backup.Filters }}
<tr>
<td class="h5">{{ $filter.ID }}</td>
<td class="h5">{{ $filter.WatchID }}</td>
<td class="h5">{{ $filter.Name }}</td>
<td class="h5">{{ $filter.Type }}</td>
<td class="h5">{{ $filter.Var1 }}</td>
<td class="h5">{{ $filter.Var2 }}</td>
<td class="h5">{{ $filter.Var3 }}</td>
<td class="h5">{{ $filter.X }}/{{ $filter.Y }}</td>
</tr>
{{ end }}
</tbody>
</table>
<div class="row h4 justify-content-center">
FilterConnections: {{ len .Backup.Connections }}
</div>
</div>
{{end}}

View file

@ -1,6 +1,11 @@
{{define "title"}} {{define "title"}}
GoWatch Backups GoWatch Backups
{{end}} {{end}}
{{define "head"}}
<script src="/static/backup.js"></script>
{{ end }}
{{define "content"}} {{define "content"}}
<div class="container row"> <div class="container row">
@ -22,21 +27,38 @@ GoWatch Backups
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr>
<td>
<form id="uploadForm" action="/backup/restore" enctype="multipart/form-data" method="POST">
<input type="hidden" value="-1" name="id">
<input class="form-control" type="file" id="upload" name="upload">
</form>
</td>
<td>
<input id="testSubmit" type="submit" class="btn btn-success" value="Test">
</td>
<td>
<input id="restoreSubmit" type="submit" class="btn btn-danger" value="Restore">
</td>
<td></td>
</tr>
{{ range $i, $backup := .Backups }} {{ range $i, $backup := .Backups }}
<tr> <tr>
<td class="h5">{{ $backup }}</td> <td class="h5">{{ $backup }}</td>
<td> <td>
<a href="/backup/test/{{ $i }}" class="btn btn-success"> <form action="/backup/test" enctype="multipart/form-data" method="POST">
Test <input type="hidden" value="{{ $i }}" name="id">
</a> <button class="btn btn-success">Test</button>
</form>
</td> </td>
<td> <td>
<a class="btn btn-danger"> <form action="/backup/restore" enctype="multipart/form-data" method="POST">
Restore <input type="hidden" value="{{ $i }}" name="id">
</a> <button class="btn btn-danger">Restore</button>
</form>
</td> </td>
<td> <td>
<a class="btn btn-secondary"> <a href="/backup/download/{{ $i }}" class="btn btn-secondary">
Download Download
</a> </a>
</td> </td>

View file

@ -7,7 +7,7 @@
- diagram.ts - diagram.ts
- ~~scheduled backup~~ - ~~scheduled backup~~
- ~~test~~ - ~~test~~
- restore - ~~restore~~
- delete - delete
- download - download
- restore from upload - ~~restore from upload~~