Add job detail page with activity log and cancel support
build-and-push / test (push) Successful in 34s
build-and-push / build-and-push (push) Successful in 1m8s

Operations are now clickable from the host detail page, linking to
/ops/{id} which shows the operation info, host link, duration, and
activity log filtered to that operation. Active operations can be
cancelled, which transitions the host to failed and releases the lock.
SSE activity events now include operation_id for real-time filtering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-14 10:37:18 -04:00
parent 5ff1cff7d4
commit 1317ff6369
9 changed files with 275 additions and 5 deletions
+22
View File
@@ -61,6 +61,28 @@ func (s *Operations) ListByHost(ctx context.Context, hostID int64) ([]model.Oper
return out, rows.Err()
}
func (s *Operations) Get(ctx context.Context, id int64) (*model.Operation, error) {
row := s.DB.QueryRowContext(ctx, `
SELECT id, host_id, kind, state, COALESCE(image_id, 0), started_at, completed_at, COALESCE(error_message, '')
FROM operations WHERE id = ?
`, id)
var op model.Operation
var startedAt string
var completedAt sql.NullString
if err := row.Scan(&op.ID, &op.HostID, &op.Kind, &op.State, &op.ImageID, &startedAt, &completedAt, &op.ErrorMessage); err != nil {
if err == sql.ErrNoRows {
return nil, ErrNotFound
}
return nil, fmt.Errorf("get operation: %w", err)
}
op.StartedAt, _ = time.Parse(time.RFC3339, startedAt)
if completedAt.Valid {
t, _ := time.Parse(time.RFC3339, completedAt.String)
op.CompletedAt = &t
}
return &op, nil
}
func (s *Operations) GetActive(ctx context.Context, hostID int64) (*model.Operation, error) {
row := s.DB.QueryRowContext(ctx, `
SELECT id, host_id, kind, state, COALESCE(image_id, 0), started_at, completed_at, COALESCE(error_message, '')