172 lines
No EOL
5.4 KiB
Text
172 lines
No EOL
5.4 KiB
Text
@page "/queue/job/{id::guid:required}"
|
|
@using System.Text.Json
|
|
@using System.Text.Json.Nodes
|
|
@using System.Text.Json.Serialization
|
|
@using Iceshrimp.Backend.Core.Database.Tables
|
|
@using Iceshrimp.Backend.Core.Extensions
|
|
@model QueueJobModel
|
|
|
|
@{
|
|
ViewData["title"] = $"Job details - {Model.InstanceName}";
|
|
}
|
|
|
|
@section head {
|
|
<link rel="stylesheet" href="~/css/queue.css"/>
|
|
}
|
|
|
|
@section scripts {
|
|
<script src="~/js/queue.js"></script>
|
|
}
|
|
|
|
<h1>Queue Dashboard</h1>
|
|
|
|
<button role="link" data-target="/queue/@Model.Job.Queue" onclick="navigate(event)">Return to job list</button>
|
|
|
|
<h2>Job details</h2>
|
|
|
|
<table>
|
|
<tbody>
|
|
<tr>
|
|
<td class="width20">ID</td>
|
|
<td>@Model.Job.Id.ToStringLower()</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Queue</td>
|
|
<td>@Model.Job.Queue</td>
|
|
</tr>
|
|
<tr>
|
|
@{
|
|
var status = Model.Job is { Status: Job.JobStatus.Delayed, RetryCount: 0 } ? "Scheduled" : Model.Job.Status.ToString();
|
|
}
|
|
<td>Status</td>
|
|
<td class="status-@status.ToLowerInvariant()">@status</td>
|
|
</tr>
|
|
@if (Model.Job.Status.Equals(Job.JobStatus.Failed))
|
|
{
|
|
<tr>
|
|
<td>Actions</td>
|
|
<td>
|
|
<a class="fake-link" onclick="retry('@Model.Job.Id.ToStringLower()')">Retry</a>
|
|
</td>
|
|
</tr>
|
|
}
|
|
else if (Model.Job.Status.Equals(Job.JobStatus.Delayed))
|
|
{
|
|
var abandonName = Model.Job.RetryCount == 0 ? "Deschedule" : "Abandon";
|
|
<tr>
|
|
<td>Actions</td>
|
|
<td>
|
|
<a class="fake-link" onclick="abandon('@Model.Job.Id.ToStringLower()', event.target)">@abandonName</a>
|
|
</td>
|
|
</tr>
|
|
}
|
|
<tr>
|
|
<td>Queued at</td>
|
|
<td>@Model.Job.QueuedAt.ToLocalTime().ToDisplayStringTz()</td>
|
|
</tr>
|
|
@if (Model.Job.Status is not Job.JobStatus.Queued and not Job.JobStatus.Delayed)
|
|
{
|
|
<tr>
|
|
<td>Started at</td>
|
|
<td>@(Model.Job.StartedAt?.ToLocalTime().ToDisplayStringTz() ?? "<unknown>")</td>
|
|
</tr>
|
|
}
|
|
@if (Model.Job.Status is Job.JobStatus.Completed or Job.JobStatus.Failed)
|
|
{
|
|
<tr>
|
|
<td>Finished at</td>
|
|
<td>@(Model.Job.FinishedAt?.ToLocalTime().ToDisplayStringTz() ?? "<unknown>")</td>
|
|
</tr>
|
|
}
|
|
@if (Model.Job.Status == Job.JobStatus.Delayed)
|
|
{
|
|
<tr>
|
|
<td>Delayed until</td>
|
|
<td>@(Model.Job.DelayedUntil?.ToLocalTime().ToDisplayStringTz() ?? "<unknown>")</td>
|
|
</tr>
|
|
}
|
|
@if (TimeSpan.FromMilliseconds(Model.Job.Duration).TotalHours <= 72)
|
|
{
|
|
<tr>
|
|
<td>Duration</td>
|
|
<td>@Model.Job.Duration.ToDurationDisplayString()</td>
|
|
</tr>
|
|
}
|
|
@if (TimeSpan.FromMilliseconds(Model.Job.QueueDuration).TotalHours <= 72)
|
|
{
|
|
<tr>
|
|
<td>Queue duration</td>
|
|
<td>@Model.Job.QueueDuration.ToDurationDisplayString()</td>
|
|
</tr>
|
|
}
|
|
@if (Model.Job.RetryCount > 0)
|
|
{
|
|
<tr>
|
|
<td>Retry count</td>
|
|
<td>@Model.Job.RetryCount</td>
|
|
</tr>
|
|
}
|
|
@if (Model.Job is { ExceptionMessage: not null, Exception: null })
|
|
{
|
|
<tr>
|
|
<td>Exception message</td>
|
|
<td>@Model.Job.ExceptionMessage</td>
|
|
</tr>
|
|
}
|
|
@if (Model.Job is { ExceptionSource: not null, Exception: null })
|
|
{
|
|
<tr>
|
|
<td>Exception source</td>
|
|
<td>@Model.Job.ExceptionSource</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
|
|
@if (Model.Job is { StackTrace: not null, Exception: null })
|
|
{
|
|
<h3>Exception stack trace</h3>
|
|
<pre><code id="exceptionStackTrace">@Model.Job.StackTrace</code></pre>
|
|
<button onclick="copyElementToClipboard('exceptionStackTrace')">Copy to clipboard</button>
|
|
}
|
|
@if (Model.Job.Exception != null)
|
|
{
|
|
<h3>Exception details</h3>
|
|
<pre><code id="exceptionDetails">@Model.Job.Exception</code></pre>
|
|
<button onclick="copyElementToClipboard('exceptionDetails')">Copy to clipboard</button>
|
|
}
|
|
|
|
<h3>Job data</h3>
|
|
|
|
@{
|
|
var dataOpts = new JsonSerializerOptions { WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull };
|
|
var payloadOpts = new JsonSerializerOptions { WriteIndented = true };
|
|
|
|
if (Model.Lookup.TryGetValue(Model.Job.Queue, out var payloadKey))
|
|
{
|
|
var data = JsonNode.Parse(Model.Job.Data)?.AsObject() ??
|
|
throw new Exception($"Failed to deserialize {Model.Job.Queue} job data");
|
|
var payloadElem = data[payloadKey];
|
|
var payload = payloadElem?.GetValue<string>() ??
|
|
throw new Exception($"Failed to deserialize {Model.Job.Queue} job data");
|
|
var payloadJson = JsonNode.Parse(payload)?.ToJsonString(payloadOpts) ??
|
|
throw new Exception($"Failed to serialize {Model.Job.Queue} job data");
|
|
|
|
data.Remove(payloadKey);
|
|
foreach (var item in data.Where(p => p.Value?.GetValueKind() is null or JsonValueKind.Null).ToList())
|
|
data.Remove(item.Key);
|
|
|
|
var dataJson = data.ToJsonString(dataOpts);
|
|
<pre><code>@dataJson</code></pre>
|
|
<h3>Job payload</h3>
|
|
<pre><code id="payload">@payloadJson</code></pre>
|
|
}
|
|
else
|
|
{
|
|
var json = JsonNode.Parse(Model.Job.Data)?.ToJsonString(payloadOpts) ??
|
|
throw new Exception($"Failed to serialize {Model.Job.Queue} job data");
|
|
<pre><code id="payload">@json</code></pre>
|
|
}
|
|
}
|
|
|
|
<button onclick="copyElementToClipboard('payload');">Copy to clipboard</button> |