Add delete after transfer option and enhanced job/config deletion UX

This commit is contained in:
StarFleetCPTN committed 2025-03-08 11:07:02 -08:00
1 parent f4c8c2121c
commit 81557840dd
11 files changed
+659 -23

No files matched your search

+1
View File
@@ -91,6 +91,7 @@ type TransferConfig struct {
ArchivePath string `form:"archive_path"`
ArchiveEnabled bool `gorm:"default:false" form:"archive_enabled"`
RcloneFlags string `form:"rclone_flags"`
DeleteAfterTransfer bool `gorm:"default:false" form:"delete_after_transfer"`
CreatedBy uint
User User `gorm:"foreignkey:CreatedBy"`
CreatedAt time.Time
@@ -0,0 +1,19 @@
package migrations
import (
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
// AddDeleteAfterTransferColumn adds the delete_after_transfer column to transfer_configs table
func AddDeleteAfterTransferColumn() *gormigrate.Migration {
return &gormigrate.Migration{
ID: "add_delete_after_transfer_column",
Migrate: func(tx *gorm.DB) error {
return tx.Exec("ALTER TABLE transfer_configs ADD COLUMN delete_after_transfer BOOLEAN NOT NULL DEFAULT false").Error
},
Rollback: func(tx *gorm.DB) error {
return tx.Exec("ALTER TABLE transfer_configs DROP COLUMN delete_after_transfer").Error
},
}
}
+16
View File
@@ -0,0 +1,16 @@
package migrations
import (
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
// InitMigrations initializes the migrations
func InitMigrations() *gormigrate.Gormigrate {
migrations := []*gormigrate.Migration{
// ... existing migrations
AddDeleteAfterTransferColumn(),
}
return gormigrate.New(db, gormigrate.DefaultOptions, migrations)
}
+25 -3
View File
@@ -161,7 +161,7 @@ func (s *Scheduler) executeJob(jobID uint) {
"--config", configPath,
"size",
"--include", job.Config.FilePattern,
job.Config.SourcePath,
fmt.Sprintf("source_%d:%s", job.Config.ID, job.Config.SourcePath),
}
// Get the rclone path from the environment variable or use the default path
rclonePath := os.Getenv("RCLONE_PATH")
@@ -169,9 +169,16 @@ func (s *Scheduler) executeJob(jobID uint) {
rclonePath = "rclone"
}
output, err := exec.Command(rclonePath, sizeArgs...).CombinedOutput()
fmt.Printf("Running rclone size: %v\nOutput: %s\n", sizeArgs, output)
fmt.Printf("Running rclone size: %s %s\nOutput: %s\n", rclonePath, strings.Join(sizeArgs, " "), output)
if err != nil {
fmt.Printf("Error running rclone size: %v\nOutput: %s\n", err, output)
// Update job history with error
history.Status = "failed"
history.ErrorMessage = fmt.Sprintf("Size calculation error: %v\nOutput: %s", err, string(output))
history.EndTime = &startTime // Use start time as end time for a quick failure
if err := s.db.UpdateJobHistory(history); err != nil {
fmt.Printf("Error updating job history for job %d: %v\n", jobID, err)
}
return
}
@@ -232,7 +239,7 @@ func (s *Scheduler) executeJob(jobID uint) {
// Prepare moveto command for transfer
transferArgs := []string{
"--config", configPath,
"moveto",
"copyto",
"--progress",
"--stats-one-line",
"--verbose",
@@ -317,6 +324,21 @@ func (s *Scheduler) executeJob(jobID uint) {
fmt.Sprintf("Archive error for file %s: %v", file, archiveErr))
}
}
if job.Config.DeleteAfterTransfer {
fmt.Printf("Deleting file %s for job %d\n", file, jobID)
deleteArgs := []string{
"--config", configPath,
"deletefile",
sourcePath, }
deleteCmd := exec.Command(rclonePath, deleteArgs...)
deleteOutput, deleteErr := deleteCmd.CombinedOutput()
fmt.Printf("Output for file %s: %s\n", file, string(deleteOutput))
if deleteErr != nil {
fmt.Printf("Error deleting file %s for job %d: %v\n", file, jobID, deleteErr)
transferErrors = append(transferErrors,
fmt.Sprintf("Delete error for file %s: %v", file, deleteErr))
}
}
}
}
+42 -2
View File
@@ -248,8 +248,48 @@ func (h *Handlers) HandleAPIDeleteJob(c *gin.Context) {
// HandleAPIRunJob handles the API run job request
func (h *Handlers) HandleAPIRunJob(c *gin.Context) {
// Implementation will be moved from the old handlers.go
c.JSON(http.StatusOK, gin.H{"message": "API run job handler stub"})
id := c.Param("id")
userID := c.GetUint("userID")
var job db.Job
if err := h.DB.First(&job, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Job not found"})
return
}
// Check if user owns this job
if job.CreatedBy != userID {
// Check if user is admin
isAdmin, exists := c.Get("isAdmin")
if !exists || isAdmin != true {
c.JSON(http.StatusForbidden, gin.H{"error": "You do not have permission to run this job"})
return
}
}
// Determine job name for response
jobName := job.Name
if jobName == "" {
// If job name is empty, try to get config name
var config db.TransferConfig
if err := h.DB.First(&config, job.ConfigID).Error; err == nil {
jobName = config.Name
} else {
jobName = fmt.Sprintf("Job #%d", job.ID)
}
}
// Run the job immediately using the scheduler
if err := h.Scheduler.RunJobNow(job.ID); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to run job: " + err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "Job started successfully",
"jobId": job.ID,
"jobName": jobName,
})
}
// HandleAPIHistory handles the API history request
+27 -7
View File
@@ -1,6 +1,7 @@
package handlers
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
@@ -274,7 +275,8 @@ func (h *Handlers) HandleRunJob(c *gin.Context) {
var job db.Job
if err := h.DB.First(&job, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Job not found"})
c.Header("Content-Type", "text/html")
c.String(http.StatusNotFound, "<script>window.notyfInstance.error('Job not found')</script>")
return
}
@@ -283,19 +285,37 @@ func (h *Handlers) HandleRunJob(c *gin.Context) {
// Check if user is admin
isAdmin, exists := c.Get("isAdmin")
if !exists || isAdmin != true {
c.JSON(http.StatusForbidden, gin.H{"error": "You do not have permission to run this job"})
c.Header("Content-Type", "text/html")
c.String(http.StatusForbidden, "<script>window.notyfInstance.error('You do not have permission to run this job')</script>")
return
}
}
// Determine job name for response
jobName := job.Name
if jobName == "" {
// If job name is empty, try to get config name
var config db.TransferConfig
if err := h.DB.First(&config, job.ConfigID).Error; err == nil {
jobName = config.Name
} else {
jobName = fmt.Sprintf("Job #%d", job.ID)
}
}
// Run the job immediately using the scheduler
if err := h.Scheduler.RunJobNow(job.ID); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to run job: " + err.Error()})
c.Header("Content-Type", "text/html")
errorMsg := fmt.Sprintf("<script>window.notyfInstance.error('Failed to run job: %s')</script>", err.Error())
c.String(http.StatusInternalServerError, errorMsg)
return
}
c.JSON(http.StatusOK, gin.H{
"message": "Job started successfully",
"jobId": job.ID,
})
// Set custom header with job name for HTMX to use in the toast notification
c.Header("HX-Job-Name", jobName)
c.Header("Content-Type", "text/html")
// Return HTML with JavaScript to trigger the notification
successScript := fmt.Sprintf("<script>window.notyfInstance.success('Job \"%s\" has been started successfully')</script>", jobName)
c.String(http.StatusOK, successScript)
}