feat: add functionality to requeue failed jobs via API and UI interface
This commit is contained in:
@@ -7,3 +7,8 @@ require (
|
||||
github.com/go-sql-driver/mysql v1.8.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
golang.org/x/sys v0.4.0 // indirect
|
||||
)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -195,6 +195,25 @@ func GetJobReports(limit, offset int, statusFilter string) ([]JobReport, int, er
|
||||
return reports, total, nil
|
||||
}
|
||||
|
||||
func GetJobReportByID(id int) (JobReport, error) {
|
||||
var r JobReport
|
||||
var targetRes, ffmpegFlags, errMsg sql.NullString
|
||||
err := DB.QueryRow(`SELECT id, file_path, media_type, status, original_size, encoded_size, size_saved, processing_time, target_resolution, ffmpeg_flags, error_message, created_at FROM job_reports WHERE id = ?`, id).
|
||||
Scan(&r.ID, &r.FilePath, &r.MediaType, &r.Status, &r.OriginalSize, &r.EncodedSize, &r.SizeSaved, &r.ProcessingTime, &targetRes, &ffmpegFlags, &errMsg, &r.CreatedAt)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
if targetRes.Valid { r.TargetResolution = targetRes.String }
|
||||
if ffmpegFlags.Valid { r.FFmpegFlags = ffmpegFlags.String }
|
||||
if errMsg.Valid { r.ErrorMessage = errMsg.String }
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func DeleteJobReport(id int) error {
|
||||
_, err := DB.Exec(`DELETE FROM job_reports WHERE id = ?`, id)
|
||||
return err
|
||||
}
|
||||
|
||||
type DashboardStats struct {
|
||||
TotalSavedSpace int64
|
||||
FilesEncoded int
|
||||
|
||||
@@ -130,3 +130,39 @@ func (s *Server) handleDeleteWatchFolder(w http.ResponseWriter, r *http.Request)
|
||||
s.wm.Reload()
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func (s *Server) handleRequeueJob(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
id, err := strconv.Atoi(strings.TrimPrefix(r.URL.Path, "/api/jobs/requeue/"))
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
report, err := db.GetJobReportByID(id)
|
||||
if err != nil {
|
||||
http.Error(w, "Job report not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if report.Status != "failed" {
|
||||
http.Error(w, "Can only requeue failed jobs", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err = db.AddJob(report.FilePath, report.MediaType, 5, report.TargetResolution, report.FFmpegFlags, report.OriginalSize)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete the failed report so it doesn't show up in history as failed anymore
|
||||
_ = db.DeleteJobReport(id)
|
||||
|
||||
s.qm.NotifySSE("queue_updated", nil)
|
||||
s.qm.NotifySSE("job_added", nil)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
@@ -132,6 +132,7 @@ func (s *Server) routes() {
|
||||
s.mux.HandleFunc("/api/status", s.handleStatus)
|
||||
s.mux.HandleFunc("/api/jobs/bump/", s.handleBumpJob)
|
||||
s.mux.HandleFunc("/api/jobs/cancel/", s.handleCancelJob)
|
||||
s.mux.HandleFunc("/api/jobs/requeue/", s.handleRequeueJob)
|
||||
|
||||
s.mux.HandleFunc("/api/folders", s.handleGetWatchFolders)
|
||||
s.mux.HandleFunc("/api/folders/add", s.handleAddWatchFolder)
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
<th>Saved</th>
|
||||
<th>Time</th>
|
||||
<th>Date</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -46,10 +47,15 @@
|
||||
<td style="color: var(--accent);">{{formatBytes .SizeSaved}}</td>
|
||||
<td>{{formatDuration .ProcessingTime}}</td>
|
||||
<td>{{.CreatedAt.Format "Jan 02, 15:04:05"}}</td>
|
||||
<td>
|
||||
{{if eq .Status "failed"}}
|
||||
<button class="btn btn-sm btn-outline" onclick="requeueJob({{.ID}})">Requeue</button>
|
||||
{{end}}
|
||||
</td>
|
||||
</tr>
|
||||
{{else}}
|
||||
<tr>
|
||||
<td colspan="8" style="text-align: center; color: var(--text-secondary);">No job reports found.</td>
|
||||
<td colspan="9" style="text-align: center; color: var(--text-secondary);">No job reports found.</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
@@ -110,5 +116,19 @@ function filterHistory() {
|
||||
// Update the record count
|
||||
document.getElementById("record-count").textContent = filter ? visibleCount : "{{.Total}}";
|
||||
}
|
||||
|
||||
async function requeueJob(id) {
|
||||
if (!confirm("Requeue this failed job?")) return;
|
||||
try {
|
||||
const res = await fetch('/api/jobs/requeue/' + id, { method: 'POST' });
|
||||
if (res.ok) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
alert("Failed to requeue job: " + await res.text());
|
||||
}
|
||||
} catch (e) {
|
||||
alert("Error: " + e.message);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{{end}}
|
||||
|
||||
Reference in New Issue
Block a user