a fork of iceshrimp.net but a tweaked frontend to my personal liking. waow
fediverse social-media social iceshrimp fedi
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

[backend/razor] Port Queue Job page to .razor

authored by

pancakes and committed by
Laura Hausmann
b63c54ae 54cee381

+195 -210
+159
Iceshrimp.Backend/Pages/Queue/QueueJob.razor
··· 1 + @page "/queue/job/{Id::guid:required}" 2 + @using System.Text.Json 3 + @using System.Text.Json.Nodes 4 + @using System.Text.Json.Serialization 5 + @using Iceshrimp.Backend.Components.Queue 6 + @using Iceshrimp.Backend.Core.Database.Tables 7 + @using Iceshrimp.Backend.Core.Extensions 8 + @inherits AdminComponentBase 9 + 10 + <QueueHead Title="@($"Job {Id.ToStringLower()}")"/> 11 + 12 + <h2>Job details</h2> 13 + 14 + <table> 15 + <tbody> 16 + <tr> 17 + <td class="width20">ID</td> 18 + <td>@JobDetails.Id.ToStringLower()</td> 19 + </tr> 20 + <tr> 21 + <td>Queue</td> 22 + <td>@JobDetails.Queue</td> 23 + </tr> 24 + <tr> 25 + @{ 26 + var status = JobDetails is { Status: Job.JobStatus.Delayed, RetryCount: 0 } ? "Scheduled" : JobDetails.Status.ToString(); 27 + } 28 + <td>Status</td> 29 + <td class="status-@status.ToLowerInvariant()">@status</td> 30 + </tr> 31 + @if (JobDetails.Status.Equals(Job.JobStatus.Failed)) 32 + { 33 + <tr> 34 + <td>Actions</td> 35 + <td> 36 + <a class="fake-link" onclick="retry('@JobDetails.Id.ToStringLower()')">Retry</a> 37 + </td> 38 + </tr> 39 + } 40 + else if (JobDetails.Status.Equals(Job.JobStatus.Delayed)) 41 + { 42 + var abandonName = JobDetails.RetryCount == 0 ? "Deschedule" : "Abandon"; 43 + <tr> 44 + <td>Actions</td> 45 + <td> 46 + <a class="fake-link" onclick="abandon('@JobDetails.Id.ToStringLower()', event.target)">@abandonName</a> 47 + </td> 48 + </tr> 49 + } 50 + <tr> 51 + <td>Queued at</td> 52 + <td>@JobDetails.QueuedAt.ToLocalTime().ToDisplayStringTz()</td> 53 + </tr> 54 + @if (JobDetails.Status is not Job.JobStatus.Queued and not Job.JobStatus.Delayed) 55 + { 56 + <tr> 57 + <td>Started at</td> 58 + <td>@(JobDetails.StartedAt?.ToLocalTime().ToDisplayStringTz() ?? "<unknown>")</td> 59 + </tr> 60 + } 61 + @if (JobDetails.Status is Job.JobStatus.Completed or Job.JobStatus.Failed) 62 + { 63 + <tr> 64 + <td>Finished at</td> 65 + <td>@(JobDetails.FinishedAt?.ToLocalTime().ToDisplayStringTz() ?? "<unknown>")</td> 66 + </tr> 67 + } 68 + @if (JobDetails.Status == Job.JobStatus.Delayed) 69 + { 70 + <tr> 71 + <td>Delayed until</td> 72 + <td>@(JobDetails.DelayedUntil?.ToLocalTime().ToDisplayStringTz() ?? "<unknown>")</td> 73 + </tr> 74 + } 75 + @if (TimeSpan.FromMilliseconds(JobDetails.Duration).TotalHours <= 72) 76 + { 77 + <tr> 78 + <td>Duration</td> 79 + <td>@JobDetails.Duration.ToDurationDisplayString()</td> 80 + </tr> 81 + } 82 + @if (TimeSpan.FromMilliseconds(JobDetails.QueueDuration).TotalHours <= 72) 83 + { 84 + <tr> 85 + <td>Queue duration</td> 86 + <td>@JobDetails.QueueDuration.ToDurationDisplayString()</td> 87 + </tr> 88 + } 89 + @if (JobDetails.RetryCount > 0) 90 + { 91 + <tr> 92 + <td>Retry count</td> 93 + <td>@JobDetails.RetryCount</td> 94 + </tr> 95 + } 96 + @if (JobDetails is { ExceptionMessage: not null, Exception: null }) 97 + { 98 + <tr> 99 + <td>Exception message</td> 100 + <td>@JobDetails.ExceptionMessage</td> 101 + </tr> 102 + } 103 + @if (JobDetails is { ExceptionSource: not null, Exception: null }) 104 + { 105 + <tr> 106 + <td>Exception source</td> 107 + <td>@JobDetails.ExceptionSource</td> 108 + </tr> 109 + } 110 + </tbody> 111 + </table> 112 + 113 + @if (JobDetails is { StackTrace: not null, Exception: null }) 114 + { 115 + <h3>Exception stack trace</h3> 116 + <pre><code id="exceptionStackTrace">@JobDetails.StackTrace</code></pre> 117 + <button class="button" onclick="copyElementToClipboard('exceptionStackTrace')">Copy to clipboard</button> 118 + } 119 + @if (JobDetails.Exception != null) 120 + { 121 + <h3>Exception details</h3> 122 + <pre><code id="exceptionDetails">@JobDetails.Exception</code></pre> 123 + <button class="button" onclick="copyElementToClipboard('exceptionDetails')">Copy to clipboard</button> 124 + } 125 + 126 + <h3>Job data</h3> 127 + 128 + @{ 129 + var dataOpts = new JsonSerializerOptions { WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; 130 + var payloadOpts = new JsonSerializerOptions { WriteIndented = true }; 131 + 132 + if (Lookup.TryGetValue(JobDetails.Queue, out var payloadKey)) 133 + { 134 + var data = JsonNode.Parse(JobDetails.Data)?.AsObject() ?? 135 + throw new Exception($"Failed to deserialize {JobDetails.Queue} job data"); 136 + var payloadElem = data[payloadKey]; 137 + var payload = payloadElem?.GetValue<string>() ?? 138 + throw new Exception($"Failed to deserialize {JobDetails.Queue} job data"); 139 + var payloadJson = JsonNode.Parse(payload)?.ToJsonString(payloadOpts) ?? 140 + throw new Exception($"Failed to serialize {JobDetails.Queue} job data"); 141 + 142 + data.Remove(payloadKey); 143 + foreach (var item in data.Where(p => p.Value?.GetValueKind() is null or JsonValueKind.Null).ToList()) 144 + data.Remove(item.Key); 145 + 146 + var dataJson = data.ToJsonString(dataOpts); 147 + <pre><code>@dataJson</code></pre> 148 + <h3>Job payload</h3> 149 + <pre><code id="payload">@payloadJson</code></pre> 150 + } 151 + else 152 + { 153 + var json = JsonNode.Parse(JobDetails.Data)?.ToJsonString(payloadOpts) ?? 154 + throw new Exception($"Failed to serialize {JobDetails.Queue} job data"); 155 + <pre><code id="payload">@json</code></pre> 156 + } 157 + } 158 + 159 + <button class="button" onclick="copyElementToClipboard('payload');">Copy to clipboard</button>
+29
Iceshrimp.Backend/Pages/Queue/QueueJob.razor.cs
··· 1 + using Iceshrimp.Backend.Components.Helpers; 2 + using Iceshrimp.Backend.Core.Database; 3 + using Iceshrimp.Backend.Core.Database.Tables; 4 + using Iceshrimp.Backend.Core.Middleware; 5 + using Microsoft.AspNetCore.Components; 6 + using Microsoft.EntityFrameworkCore; 7 + 8 + namespace Iceshrimp.Backend.Pages.Queue; 9 + 10 + public partial class QueueJob(DatabaseContext db) : AdminComponentBase 11 + { 12 + [Parameter] public required Guid Id { get; set; } 13 + 14 + private Job JobDetails { get; set; } = null!; 15 + 16 + public static Dictionary<string, string> Lookup = new() 17 + { 18 + ["inbox"] = "body", 19 + ["deliver"] = "payload", 20 + ["pre-deliver"] = "serializedActivity" 21 + }; 22 + 23 + protected override async Task OnInitializedAsync() 24 + { 25 + JobDetails = await db.Jobs.FirstOrDefaultAsync(p => p.Id == Id) ?? 26 + throw GracefulException.NotFound($"Job {Id} not found"); 27 + } 28 + } 29 +
+7
Iceshrimp.Backend/Pages/Queue/QueueJob.razor.css
··· 1 + pre code { 2 + display: block; 3 + width: 100%; 4 + overflow-x: auto; 5 + text-wrap: nowrap; 6 + line-height: 1; 7 + }
-172
Iceshrimp.Backend/Pages/QueueJob.cshtml
··· 1 - @page "/queue/job/{id::guid:required}" 2 - @using System.Text.Json 3 - @using System.Text.Json.Nodes 4 - @using System.Text.Json.Serialization 5 - @using Iceshrimp.Backend.Core.Database.Tables 6 - @using Iceshrimp.Backend.Core.Extensions 7 - @model QueueJobModel 8 - 9 - @{ 10 - ViewData["title"] = $"Job details - {Model.InstanceName}"; 11 - } 12 - 13 - @section head { 14 - <link rel="stylesheet" href="~/css/queue.css"/> 15 - } 16 - 17 - @section scripts { 18 - <script src="~/js/queue.js"></script> 19 - } 20 - 21 - <h1>Queue Dashboard</h1> 22 - 23 - <button class="button" role="link" data-target="/queue/@Model.Job.Queue" onclick="navigate(event)">Return to job list</button> 24 - 25 - <h2>Job details</h2> 26 - 27 - <table> 28 - <tbody> 29 - <tr> 30 - <td class="width20">ID</td> 31 - <td>@Model.Job.Id.ToStringLower()</td> 32 - </tr> 33 - <tr> 34 - <td>Queue</td> 35 - <td>@Model.Job.Queue</td> 36 - </tr> 37 - <tr> 38 - @{ 39 - var status = Model.Job is { Status: Job.JobStatus.Delayed, RetryCount: 0 } ? "Scheduled" : Model.Job.Status.ToString(); 40 - } 41 - <td>Status</td> 42 - <td class="status-@status.ToLowerInvariant()">@status</td> 43 - </tr> 44 - @if (Model.Job.Status.Equals(Job.JobStatus.Failed)) 45 - { 46 - <tr> 47 - <td>Actions</td> 48 - <td> 49 - <a class="fake-link" onclick="retry('@Model.Job.Id.ToStringLower()')">Retry</a> 50 - </td> 51 - </tr> 52 - } 53 - else if (Model.Job.Status.Equals(Job.JobStatus.Delayed)) 54 - { 55 - var abandonName = Model.Job.RetryCount == 0 ? "Deschedule" : "Abandon"; 56 - <tr> 57 - <td>Actions</td> 58 - <td> 59 - <a class="fake-link" onclick="abandon('@Model.Job.Id.ToStringLower()', event.target)">@abandonName</a> 60 - </td> 61 - </tr> 62 - } 63 - <tr> 64 - <td>Queued at</td> 65 - <td>@Model.Job.QueuedAt.ToLocalTime().ToDisplayStringTz()</td> 66 - </tr> 67 - @if (Model.Job.Status is not Job.JobStatus.Queued and not Job.JobStatus.Delayed) 68 - { 69 - <tr> 70 - <td>Started at</td> 71 - <td>@(Model.Job.StartedAt?.ToLocalTime().ToDisplayStringTz() ?? "<unknown>")</td> 72 - </tr> 73 - } 74 - @if (Model.Job.Status is Job.JobStatus.Completed or Job.JobStatus.Failed) 75 - { 76 - <tr> 77 - <td>Finished at</td> 78 - <td>@(Model.Job.FinishedAt?.ToLocalTime().ToDisplayStringTz() ?? "<unknown>")</td> 79 - </tr> 80 - } 81 - @if (Model.Job.Status == Job.JobStatus.Delayed) 82 - { 83 - <tr> 84 - <td>Delayed until</td> 85 - <td>@(Model.Job.DelayedUntil?.ToLocalTime().ToDisplayStringTz() ?? "<unknown>")</td> 86 - </tr> 87 - } 88 - @if (TimeSpan.FromMilliseconds(Model.Job.Duration).TotalHours <= 72) 89 - { 90 - <tr> 91 - <td>Duration</td> 92 - <td>@Model.Job.Duration.ToDurationDisplayString()</td> 93 - </tr> 94 - } 95 - @if (TimeSpan.FromMilliseconds(Model.Job.QueueDuration).TotalHours <= 72) 96 - { 97 - <tr> 98 - <td>Queue duration</td> 99 - <td>@Model.Job.QueueDuration.ToDurationDisplayString()</td> 100 - </tr> 101 - } 102 - @if (Model.Job.RetryCount > 0) 103 - { 104 - <tr> 105 - <td>Retry count</td> 106 - <td>@Model.Job.RetryCount</td> 107 - </tr> 108 - } 109 - @if (Model.Job is { ExceptionMessage: not null, Exception: null }) 110 - { 111 - <tr> 112 - <td>Exception message</td> 113 - <td>@Model.Job.ExceptionMessage</td> 114 - </tr> 115 - } 116 - @if (Model.Job is { ExceptionSource: not null, Exception: null }) 117 - { 118 - <tr> 119 - <td>Exception source</td> 120 - <td>@Model.Job.ExceptionSource</td> 121 - </tr> 122 - } 123 - </tbody> 124 - </table> 125 - 126 - @if (Model.Job is { StackTrace: not null, Exception: null }) 127 - { 128 - <h3>Exception stack trace</h3> 129 - <pre><code id="exceptionStackTrace">@Model.Job.StackTrace</code></pre> 130 - <button class="button" onclick="copyElementToClipboard('exceptionStackTrace')">Copy to clipboard</button> 131 - } 132 - @if (Model.Job.Exception != null) 133 - { 134 - <h3>Exception details</h3> 135 - <pre><code id="exceptionDetails">@Model.Job.Exception</code></pre> 136 - <button class="button" onclick="copyElementToClipboard('exceptionDetails')">Copy to clipboard</button> 137 - } 138 - 139 - <h3>Job data</h3> 140 - 141 - @{ 142 - var dataOpts = new JsonSerializerOptions { WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; 143 - var payloadOpts = new JsonSerializerOptions { WriteIndented = true }; 144 - 145 - if (Model.Lookup.TryGetValue(Model.Job.Queue, out var payloadKey)) 146 - { 147 - var data = JsonNode.Parse(Model.Job.Data)?.AsObject() ?? 148 - throw new Exception($"Failed to deserialize {Model.Job.Queue} job data"); 149 - var payloadElem = data[payloadKey]; 150 - var payload = payloadElem?.GetValue<string>() ?? 151 - throw new Exception($"Failed to deserialize {Model.Job.Queue} job data"); 152 - var payloadJson = JsonNode.Parse(payload)?.ToJsonString(payloadOpts) ?? 153 - throw new Exception($"Failed to serialize {Model.Job.Queue} job data"); 154 - 155 - data.Remove(payloadKey); 156 - foreach (var item in data.Where(p => p.Value?.GetValueKind() is null or JsonValueKind.Null).ToList()) 157 - data.Remove(item.Key); 158 - 159 - var dataJson = data.ToJsonString(dataOpts); 160 - <pre><code>@dataJson</code></pre> 161 - <h3>Job payload</h3> 162 - <pre><code id="payload">@payloadJson</code></pre> 163 - } 164 - else 165 - { 166 - var json = JsonNode.Parse(Model.Job.Data)?.ToJsonString(payloadOpts) ?? 167 - throw new Exception($"Failed to serialize {Model.Job.Queue} job data"); 168 - <pre><code id="payload">@json</code></pre> 169 - } 170 - } 171 - 172 - <button class="button" onclick="copyElementToClipboard('payload');">Copy to clipboard</button>
-38
Iceshrimp.Backend/Pages/QueueJob.cshtml.cs
··· 1 - using Iceshrimp.Backend.Core.Database; 2 - using Iceshrimp.Backend.Core.Database.Tables; 3 - using Iceshrimp.Backend.Core.Middleware; 4 - using Iceshrimp.Backend.Core.Services; 5 - using Microsoft.AspNetCore.Mvc; 6 - using Microsoft.AspNetCore.Mvc.RazorPages; 7 - using Microsoft.EntityFrameworkCore; 8 - 9 - namespace Iceshrimp.Backend.Pages; 10 - 11 - public class QueueJobModel(DatabaseContext db, MetaService meta) : PageModel 12 - { 13 - private static Dictionary<string, string> _lookup = new() 14 - { 15 - ["inbox"] = "body", 16 - ["deliver"] = "payload", 17 - ["pre-deliver"] = "serializedActivity" 18 - }; 19 - 20 - public Job Job = null!; 21 - public string InstanceName = "Iceshrimp.NET"; 22 - public Dictionary<string, string> Lookup => _lookup; 23 - 24 - public async Task<IActionResult> OnGet([FromRoute] Guid id) 25 - { 26 - if (!Request.Cookies.TryGetValue("admin_session", out var cookie)) 27 - return Redirect("/login"); 28 - if (!await db.Sessions.AnyAsync(p => p.Token == cookie && p.Active && p.User.IsAdmin)) 29 - return Redirect("/login"); 30 - 31 - Request.HttpContext.HideFooter(); 32 - InstanceName = await meta.GetAsync(MetaEntity.InstanceName) ?? InstanceName; 33 - 34 - Job = await db.Jobs.FirstOrDefaultAsync(p => p.Id == id) ?? 35 - throw GracefulException.NotFound($"Job {id} not found"); 36 - return Page(); 37 - } 38 - }