namespace marlin.web.Controllers; using core.models; using Data; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Util; [Route("jobs")] public class JobsController(ApplicationDbContext dbContext) : Controller { // list jobs (pending, active, cancelled, success, failed), filter to vcs, filter to recipe public record JobListFilter(JobStatus? Status = null, Guid? Vcs = null, string? Ref = null, string? Recipe = null); [HttpGet("list")] public async Task List([FromQuery] JobListFilter filter, CancellationToken ct) { var results = await dbContext.Jobs .WhereIf(filter.Status != null, x => x.Status == filter.Status) .WhereIf(filter.Vcs != null, x => x.SourceId == filter.Vcs) .WhereIf(filter.Ref != null, x => x.Ref == filter.Ref) .WhereIf(filter.Recipe != null, x => x.Recipe == filter.Recipe) .OrderByDescending(x => x.CreatedAt) .Include(x => x.Source) .Include(x => x.Agent) .ToListAsync(ct); return this.View(results); } public record JobSpawnDetails( ICollection Sources, Guid? Vcs = null, string? Ref = null, string? Commit = null, string? Recipe = null); [HttpGet("spawn")] public async Task Spawn(CancellationToken ct) { var sources = await dbContext.Sources .Include(x => x.Branches) .ThenInclude(x => x.Commit) .ToListAsync(ct); var details = new JobSpawnDetails(sources); return this.View(details); } public record JobSpawnRequest(Guid Vcs, string Ref, string Commit, string Recipe); [HttpPost("spawn")] public async Task SpawnPost(JobSpawnRequest request, CancellationToken ct) { Job job = new() { SourceId = request.Vcs, Ref = request.Ref, Commit = request.Commit, Recipe = request.Recipe, CreatedAt = DateTime.UtcNow, CreatedReason = "user created", Status = JobStatus.Pending }; await dbContext.Jobs.AddAsync(job, ct); await dbContext.SaveChangesAsync(ct); return this.RedirectToAction("Details", new { id = job.Id }); } // cancel job // details job (recipe, status, agent, target, plan, log, artifacts) [HttpGet("details/{id}")] public async Task Details(Guid id) { var job = await dbContext.Jobs .Include(x => x.Agent) .Include(x => x.Source) .Include(x => x.StatusUpdates) .FirstOrDefaultAsync(x => x.Id == id); if (job == null) return this.NotFound(); return this.View(job); } }