Iceshrimp.NET/Iceshrimp.Backend/Pages/Queue.cshtml

281 lines
No EOL
10 KiB
Text
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@page "/queue/{queue?}/{pagination:int?}/{status?}"
@inject QueueService QueueSvc
@using Iceshrimp.Backend.Core.Database.Tables
@using Iceshrimp.Backend.Core.Extensions
@using Iceshrimp.Backend.Core.Services
@model QueueModel
@{
ViewData["title"] = $"Queue dashboard - {Model.InstanceName}";
}
@section head {
<link rel="stylesheet" href="~/css/queue.css"/>
}
@section scripts {
<script src="~/js/queue.js"></script>
@if (Model.Queue == null)
{
<script src="~/js/queue-index.js"></script>
}
}
<h1>Queue Dashboard</h1>
<button role="link" data-target="/queue" onclick="navigate(event)">overview</button>
@foreach (var queue in QueueSvc.QueueNames)
{
//asd
<button role="link" data-target="/queue/@queue" onclick="navigate(event)">@queue</button>
}
<br/>
@if (Model.Queue == null)
{
<p>Please pick a queue.</p>
<form onsubmit="return lookupJob(event)">
<input type="text" id="lookup" placeholder="Lookup job by id" minlength="36" maxlength="36" class="inline"/>
<button type="submit">Submit</button>
</form>
<h3>Queue status &mdash; <span id="update-status" class="status-delayed">Connecting...</span></h3>
<table class="auto-table" id="queue-status">
<thead>
<th>Name</th>
@foreach (var status in Enum.GetValues<Job.JobStatus>())
{
var name = Enum.GetName(status);
<th class="justify-right">@name</th>
}
</thead>
<tbody>
@foreach (var queue in Model.QueueStatuses ?? throw new Exception("Model.QueueStatuses must not be null here"))
{
var isScheduled = QueueModel.ScheduledQueues.Contains(queue.Name);
var scheduled = isScheduled ? queue.JobCounts[Job.JobStatus.Delayed].ToString() : "-";
var delayed = !isScheduled ? queue.JobCounts[Job.JobStatus.Delayed].ToString() : "-";
<tr>
<td>
<a href="/queue/@queue.Name" class="color-fg">@queue.Name</a>
</td>
@foreach (var status in Enum.GetValues<Job.JobStatus>())
{
var name = Enum.GetName(status)!.ToLowerInvariant();
if (status == Job.JobStatus.Delayed)
{
if (!isScheduled)
{
<td class="justify-right">
<a href="/queue/@queue.Name/1/delayed" class="status-@name">@delayed</a>
</td>
}
else
{
<td class="justify-right status-@name">@delayed</td>
}
}
else if (status == Job.JobStatus.Queued && isScheduled)
{
<td class="justify-right">
(<a href="/queue/@queue.Name/1/delayed" class="status-scheduled">@scheduled</a>)
<a href="/queue/@queue.Name/1/queued" class="status-queued">@queue.JobCounts[status]</a>
</td>
}
else
{
<td class="justify-right">
<a href="/queue/@queue.Name/1/@name" class="status-@name">@queue.JobCounts[status]</a>
</td>
}
}
</tr>
}
</tbody>
</table>
<h3>Recent jobs</h3>
<table class="auto-table" id="recent-jobs">
<thead>
<th class="width0">ID</th>
<th>Queue</th>
<th>Status</th>
<th>Actions</th>
</thead>
<tbody>
@foreach (var job in Model.Jobs)
{
await RenderJobAsync(job, true);
}
</tbody>
</table>
<h3>Top delayed deliver job targets</h3>
<table class="auto-table" id="top-delayed">
<thead>
<th class="width0">Instance</th>
<th>Count</th>
</thead>
<tbody>
@foreach (var instance in Model.TopDelayed)
{
<tr>
<td>@instance.Host</td>
<td><i>@instance.Count jobs</i></td>
</tr>
}
</tbody>
</table>
@if (Model.TopDelayed is [])
{
<i>No delayed jobs found.</i>
}
var last = Model.Jobs.FirstOrDefault();
var lastUpdated = last != null ? new DateTimeOffset(last.LastUpdatedAt).ToUnixTimeMilliseconds() : 0;
<div class="display-none" id="last-updated">@lastUpdated</div>
}
else
{
var delayedStr = QueueModel.ScheduledQueues.Contains(Model.Queue) ? "scheduled" : "delayed";
if (Model.Filter == null)
{
<p>Listing @Model.TotalCount <b>@Model.Queue</b> jobs, out of which <span class="status-running">@Model.RunningCount</span> are <span class="status-running">running</span>, <span class="status-queued">@Model.QueuedCount</span> are <span class="status-queued">queued</span> and <span class="status-@delayedStr">@Model.DelayedCount</span> are <span class="status-@delayedStr">@delayedStr</span>.</p>
}
else
{
var filterStr = Model.Filter.Value == Job.JobStatus.Delayed && QueueModel.ScheduledQueues.Contains(Model.Queue)
? "scheduled"
: Model.Filter.Value.ToString().ToLowerInvariant();
<p>
Listing @Model.TotalCount <span class="status-@filterStr">@filterStr</span> <b>@Model.Queue</b> jobs.
@if (Model.Filter is Job.JobStatus.Failed)
{
<span>Batch retry: <a class="fake-link" onclick="retryAllFailed('@Model.Queue')">all failed</a> / <a class="fake-link" onclick="retryAllOnPage('@Model.Queue')">all on this page</a></span>
}
</p>
}
<table class="auto-table">
<thead>
<th class="width0">ID</th>
<th>Status</th>
<th>Actions</th>
</thead>
<tbody>
@foreach (var job in Model.Jobs)
{
await RenderJobAsync(job);
}
</tbody>
</table>
<div class="flex">
@if (Model.PrevPage != null)
{
if (Model.Filter.HasValue)
{
<button role="link" data-target="/queue/@Model.Queue/@Model.PrevPage/@Model.Filter.Value.ToString().ToLowerInvariant()" onclick="navigate"> Previous page</button>
}
else
{
<button role="link" data-target="/queue/@Model.Queue/@Model.PrevPage" onclick="navigate(event)"> Previous page</button>
}
}
else
{
<button disabled> Previous page</button>
}
@if (Model.NextPage != null)
{
if (Model.Filter.HasValue)
{
<button role="link" data-target="/queue/@Model.Queue/@Model.NextPage/@Model.Filter.Value.ToString().ToLowerInvariant()" onclick="navigate(event)">Next page </button>
}
else
{
<button role="link" data-target="/queue/@Model.Queue/@Model.NextPage" onclick="navigate(event)">Next page </button>
}
}
else
{
<button disabled>Next page </button>
}
<select onchange="filter('@Model.Queue')" id="filter" class="inline-flex">
@if (Model.Filter == null)
{
<option value="all" selected>All</option>
}
else
{
<option value="all">All</option>
}
@foreach (var status in Enum.GetValues<Job.JobStatus>())
{
var statusStr = status == Job.JobStatus.Delayed && QueueModel.ScheduledQueues.Contains(Model.Queue)
? "Scheduled"
: status.ToString();
if (Model.Filter.Equals(status))
{
<option value="@status.ToString().ToLowerInvariant()" selected>@statusStr</option>
}
else
{
<option value="@status.ToString().ToLowerInvariant()">@statusStr</option>
}
}
</select>
<form onsubmit="return lookupJob(event)" class="inline-flex flex-grow">
<input type="text" id="lookup" placeholder="Lookup job by id" minlength="36" maxlength="36" class="flex-grow"/>
</form>
</div>
}
@{
async Task RenderJobAsync(Job job, bool withQueue = false)
{
var id = job.Id.ToStringLower();
var additional = job.Status switch
{
Job.JobStatus.Delayed when job.RetryCount is 0 => $"for {job.DelayedUntil?.ToLocalTime().ToDisplayStringTz() ?? "<unknown>"}",
// Separated for readability
Job.JobStatus.Queued => $"for {job.QueueDuration.ToDurationDisplayString()}",
Job.JobStatus.Running => $"for {job.Duration.ToDurationDisplayString()}",
Job.JobStatus.Delayed => $"until {job.DelayedUntil?.ToLocalTime().ToDisplayStringTz() ?? "<unknown>"}",
Job.JobStatus.Completed => $"at {job.FinishedAt?.ToLocalTime().ToDisplayStringTz() ?? "<unknown>"}",
Job.JobStatus.Failed => $"at {job.FinishedAt?.ToLocalTime().ToDisplayStringTz() ?? "<unknown>"}",
_ => throw new ArgumentOutOfRangeException()
};
var classes = Model.Last != null && new DateTimeOffset(job.LastUpdatedAt).ToUnixTimeMilliseconds() > Model.Last
? "new-item"
: "";
var status = job is { Status: Job.JobStatus.Delayed, RetryCount: 0 } ? "Scheduled" : job.Status.ToString();
<tr class="@classes">
@if (withQueue)
{
<td class="uuid-abbrev">@id[..8]...@id[24..]</td>
<td>@job.Queue</td>
}
else
{
<td class="uuid">@id</td>
}
<td class="status-@status.ToLowerInvariant()">
<b>@status</b> <small>@additional</small>
<td>
<a href="/queue/job/@id">View details</a>
</td>
</tr>
}
}