diff --git a/air.toml b/.air.toml similarity index 100% rename from air.toml rename to .air.toml diff --git a/.gitignore b/.gitignore index f03090d..621d00e 100644 --- a/.gitignore +++ b/.gitignore @@ -49,4 +49,12 @@ components/*.go data/ # Ignore the tmp directory -tmp/ \ No newline at end of file +tmp/ + +# Ignore the configs directory +configs/ + +# Ignore Dirs +source/ +destination/ +archive/ \ No newline at end of file diff --git a/components/config_form.templ b/components/config_form.templ index 35dea43..0e9341e 100644 --- a/components/config_form.templ +++ b/components/config_form.templ @@ -60,6 +60,7 @@ func getInitialData(config *db.TransferConfig) string { destPassiveMode := true archivePath := "" archiveEnabled := false + deleteAfterTransfer := false rcloneFlags := "" if config != nil { @@ -104,6 +105,7 @@ func getInitialData(config *db.TransferConfig) string { destPassiveMode = config.DestPassiveMode archivePath = config.ArchivePath archiveEnabled = config.ArchiveEnabled + deleteAfterTransfer = config.DeleteAfterTransfer rcloneFlags = config.RcloneFlags } @@ -147,6 +149,7 @@ func getInitialData(config *db.TransferConfig) string { // Existing fields archivePath: '%s', archiveEnabled: %v, + deleteAfterTransfer: %v, rcloneFlags: '%s', loading: false, validate() { @@ -209,7 +212,7 @@ func getInitialData(config *db.TransferConfig) string { filePattern, outputPattern, destinationType, destinationPath, destHost, destPort, destUser, destPassword, destKeyFile, destBucket, destRegion, destAccessKey, destSecretKey, destEndpoint, destShare, destDomain, destPassiveMode, - archivePath, archiveEnabled, rcloneFlags) + archivePath, archiveEnabled, deleteAfterTransfer, rcloneFlags) } templ ConfigForm(ctx context.Context, data ConfigFormData) { @@ -1303,6 +1306,25 @@ templ ConfigForm(ctx context.Context, data ConfigFormData) { +
+
+ + + +
+

+ + Original files will be permanently deleted from source after successful transfer + and archiving +

+
+
diff --git a/components/configs.templ b/components/configs.templ index ed4111f..f685849 100644 --- a/components/configs.templ +++ b/components/configs.templ @@ -6,12 +6,204 @@ import ( "github.com/starfleetcptn/gomft/internal/db" ) +// Dialog component for confirmation dialogs +templ ConfigDialog(id string, title string, message string, confirmClass string, confirmText string, action string, configID uint, configName string) { + +} + +script hideConfigDialog(id string) { + document.getElementById(id).classList.add("hidden"); +} + +script showConfigDialog(id string) { + document.getElementById(id).classList.remove("hidden"); +} + +script triggerConfigDelete(dialogId string, configID uint, configName string) { + // Hide the dialog + document.getElementById(dialogId).classList.add("hidden"); + + // Store data in a way that's accessible to event handlers + window.lastDeletedConfig = { + id: configID, + name: configName + }; + + // Add custom marker to track this deletion + window.currentlyDeletingConfig = true; +} + type ConfigsData struct { Configs []db.TransferConfig } templ Configs(ctx context.Context, data ConfigsData) { @LayoutWithContext("Transfer Configurations", ctx) { + +
@@ -58,11 +250,20 @@ templ Configs(ctx context.Context, data ConfigsData) { Edit + + @ConfigDialog( + fmt.Sprintf("delete-config-dialog-%d", config.ID), + "Delete Configuration", + fmt.Sprintf("Are you sure you want to delete the configuration '%s'? This cannot be undone.", config.Name), + "btn-danger", + "Delete", + "delete", + config.ID, + config.Name, + ) + +
+
+
+} + +script hideJobDialog(id string) { + document.getElementById(id).classList.add("hidden"); +} + +script showJobDialog(id string) { + document.getElementById(id).classList.remove("hidden"); +} + +script triggerJobDelete(dialogId string, jobID uint, jobName string) { + // Hide the dialog + document.getElementById(dialogId).classList.add("hidden"); + + // Add debugging info + console.log(`Job deletion triggered for: ${jobName} (ID: ${jobID})`); + + // Store data in a way that's accessible to event handlers + window.lastDeletedJob = { + id: jobID, + name: jobName + }; + + // Add custom marker to track this deletion + window.currentlyDeletingJob = true; +} + type JobsData struct { Jobs []db.Job } templ Jobs(ctx context.Context, data JobsData) { @LayoutWithContext("Transfer Jobs", ctx) { + +
@@ -68,15 +299,34 @@ templ Jobs(ctx context.Context, data JobsData) { }
+ Edit + + @JobDialog( + fmt.Sprintf("delete-job-dialog-%d", job.ID), + "Delete Job", + fmt.Sprintf("Are you sure you want to delete the job '%s'? This cannot be undone.", determineJobName(job)), + "btn-danger", + "Delete", + "delete", + job.ID, + determineJobName(job), + )
} +} + +// Helper function to determine the job name (reuse this logic to keep it consistent) +func determineJobName(job db.Job) string { + if job.Name != "" { + return job.Name + } + return job.Config.Name } \ No newline at end of file diff --git a/components/layout.templ b/components/layout.templ index c9d96f3..6ad74bb 100644 --- a/components/layout.templ +++ b/components/layout.templ @@ -47,6 +47,42 @@ templ LayoutWithContext(title string, ctx context.Context) { + + + + ") 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, "") 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("", 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("", jobName) + c.String(http.StatusOK, successScript) } \ No newline at end of file