feat(issues): Processing Issues page retry UI with a sequential client queue
Per issue card: a Retry button — error-severity issues previously had no action at all (only warning/info got Dismiss). Right of the Errors stat card: Retry all, which builds a queue of every issue currently on the page and runs them strictly one at a time through the per-issue retry endpoint. The browser is the queue — resource use is bounded to a single book in flight, and no batch endpoint exists to maintain. While the queue runs, the button shows an n/m counter and cards tint via client-local state classes (the sanctioned island shape; the cards themselves stay templ-rendered). After the queue drains, the page swaps in the new /admin/libraries/:id/issues/content templ partial — the unlinked-books htmx.ajax pattern with a reload fallback — so resolved issues drop off server-side while failures remain listed, with a summary toast and per-item error toasts carrying the real failure reasons. CSP discipline holds throughout: Alpine @click component methods, no inline handlers, no js:-prefixed hx-vals.
This commit is contained in:
1 parent
0f1d0abbb0
commit
248bb7816d
7 files changed
+579
-248
No files matched your search
@@ -1413,6 +1413,67 @@ func registerFrontendRoutes(cfg *Config) {
|
||||
return c.HTML(http.StatusOK, buf.String())
|
||||
}))
|
||||
|
||||
// Content partial for the retry queue: after the client drains its
|
||||
// sequential retry queue it swaps this in over #issues-content so
|
||||
// resolved issues drop off server-side (unlinked_books matches
|
||||
// partial precedent). Same family-lens gate as the page.
|
||||
frontendProtected.GET("/admin/libraries/:id/issues/content", handlers.AdminMiddleware(func(c *echo.Context) error {
|
||||
libraryID := c.Param("id")
|
||||
libUUID, err := uuid.Parse(libraryID)
|
||||
if err != nil {
|
||||
return renderErrorPage(c, "Invalid library ID", "invalid_id")
|
||||
}
|
||||
visibleSet, _, visErr := visibleLibraryIDsFor(c, cfg)
|
||||
if visErr != nil {
|
||||
return renderErrorPage(c, "Error checking library access", "visibility_error")
|
||||
}
|
||||
if !visibleSet[libraryID] {
|
||||
return renderErrorPage(c, "Library not accessible", "library_not_accessible")
|
||||
}
|
||||
stats, err := cfg.Queries.GetProcessingIssueStats(c.Request().Context(), uuidToPGType(libUUID))
|
||||
if err != nil {
|
||||
return renderErrorPage(c, "Error loading stats", "stats_load_error")
|
||||
}
|
||||
dbIssues, err := cfg.Queries.ListProcessingIssuesByLibrary(c.Request().Context(), uuidToPGType(libUUID))
|
||||
if err != nil {
|
||||
return renderErrorPage(c, "Error loading issues", "issues_load_error")
|
||||
}
|
||||
issues := make([]templates.ProcessingIssueData, len(dbIssues))
|
||||
for i, issue := range dbIssues {
|
||||
issueUUID, _ := uuid.FromBytes(issue.ID.Bytes[0:16])
|
||||
itemID := ""
|
||||
if issue.MediaItemID.Valid {
|
||||
itemID = uuid.UUID(issue.MediaItemID.Bytes).String()
|
||||
}
|
||||
title := issue.Title
|
||||
if title == "" {
|
||||
title = issue.FilePath
|
||||
}
|
||||
issues[i] = templates.ProcessingIssueData{
|
||||
ID: issueUUID.String(),
|
||||
MediaItemID: itemID,
|
||||
Title: title,
|
||||
FilePath: issue.FilePath,
|
||||
FormatGroup: issue.FormatGroup,
|
||||
LibraryTypeName: issue.LibraryTypeName,
|
||||
IssueType: issue.IssueType,
|
||||
IssueDescription: issue.IssueDescription,
|
||||
Severity: issue.Severity,
|
||||
CreatedAt: issue.CreatedAt.Time,
|
||||
}
|
||||
}
|
||||
statsData := templates.IssueStats{
|
||||
ErrorCount: stats.ErrorCount,
|
||||
WarningCount: stats.WarningCount,
|
||||
InfoCount: stats.InfoCount,
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := templates.ProcessingIssuesContent(libraryID, issues, statsData).Render(c.Request().Context(), &buf); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.HTML(http.StatusOK, buf.String())
|
||||
}))
|
||||
|
||||
// Admin hash conflicts page: content-duplicate groups flagged during hash
|
||||
// backfill or rescan, resolved by keeping all copies or merging into one.
|
||||
frontendProtected.GET("/admin/hash-conflicts", handlers.AdminMiddleware(func(c *echo.Context) error {
|
||||
|
||||
Reference in new issue
Block a user