added backup restore functionality, refactor of backup test
This commit is contained in:
parent
17f29cb175
commit
e64b610fed
6 changed files with 271 additions and 25 deletions
149
main.go
149
main.go
|
@ -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
17
static/backup.js
Normal 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
21
static/backup.ts
Normal 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);
|
69
templates/backup/restore.html
Normal file
69
templates/backup/restore.html
Normal 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}}
|
|
@ -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>
|
||||||
|
|
4
todo.md
4
todo.md
|
@ -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~~
|
||||||
|
|
Loading…
Add table
Reference in a new issue