From f5ba461e68af6807aea627f494a383dff06fc12c Mon Sep 17 00:00:00 2001 From: ndigirigijohn Date: Sat, 8 Feb 2025 11:39:44 +0300 Subject: [PATCH] improved logs UI --- .../Components/Pages/Log.razor | 353 ++++++++++-------- .../wwwroot/app.css | 85 +++++ 2 files changed, 292 insertions(+), 146 deletions(-) diff --git a/Blocktrust.CredentialWorkflow.Web/Components/Pages/Log.razor b/Blocktrust.CredentialWorkflow.Web/Components/Pages/Log.razor index 9793df8..79fa257 100644 --- a/Blocktrust.CredentialWorkflow.Web/Components/Pages/Log.razor +++ b/Blocktrust.CredentialWorkflow.Web/Components/Pages/Log.razor @@ -1,34 +1,35 @@ @page "/logs" +@using System.Text.Json @using Blocktrust.CredentialWorkflow.Core.Commands.WorkflowOutcome.GetWorkflowOutcomes @using Blocktrust.CredentialWorkflow.Web.Components.Layout @using Blocktrust.CredentialWorkflow.Web.Services @using MediatR @using Microsoft.AspNetCore.Components.Authorization -@using Blocktrust.CredentialWorkflow.Core.Domain.Common @using Blocktrust.CredentialWorkflow.Core.Domain.Enums @using Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Actions @using Blocktrust.CredentialWorkflow.Core.Domain.Workflow @using Microsoft.AspNetCore.Authorization -@using System.Text.Json -@using System.Text.Json.Nodes @inject AuthenticationStateProvider AuthenticationStateProvider @inject IMediator Mediator @inject AppStateService AppStateService @inject ILogger Logger +@inject IJSRuntime JSRuntime @rendermode @(new InteractiveServerRenderMode(prerender: false)) @attribute [Authorize()] @layout AppLayout + Blocktrust Credential Workflow Platform - Logs @if (AppStateService.IsInitialized) { -
+
-
- @foreach (var workflow in AppStateService.WorkflowSummaries.OrderByDescending(w => w.UpdatedUtc)) { @@ -37,170 +38,189 @@
-
+
@if (workflowOutcomes.Any()) { - - - - - - - - - - @foreach (var workflowOutcome in workflowOutcomes) - { - - - - - - @if (expandedWorkflowOutcomeId == workflowOutcome.WorkflowOutcomeId) - { - - + + } + } + +
StateStartedEnded
- - @workflowOutcome.WorkflowOutcomeState - - @workflowOutcome.StartedUtc?.ToString("g")@workflowOutcome.EndedUtc?.ToString("g")
-
- @if (workflowOutcome.WorkflowOutcomeState == EWorkflowOutcomeState.Success) - { - @if (!string.IsNullOrEmpty(workflowOutcome.ActionOutcomesJson)) - { -
-

Action Workflow Outcome

-
- @{ - var actionOutcomes = ParseAndFormatJson(workflowOutcome.ActionOutcomesJson); - foreach (var outcome in actionOutcomes) +
+
+ + + + + + + + + + @foreach (var workflowOutcome in workflowOutcomes) + { + + + + + + @if (expandedWorkflowOutcomeId == workflowOutcome.WorkflowOutcomeId) + { + + - - } - } - -
StateStartedEnded
+ + @workflowOutcome.WorkflowOutcomeState + + @workflowOutcome.StartedUtc?.ToString("g")@workflowOutcome.EndedUtc?.ToString("g")
+
+ @if (!string.IsNullOrEmpty(workflowOutcome.ActionOutcomesJson)) + { +
+
+

Action Outcomes

+ +
+
+ @foreach (var outcome in ParseAndFormatJson(workflowOutcome.ActionOutcomesJson)) { -
-
-
- Outcome ID: - @outcome.OutcomeId -
-
- Action ID: - @outcome.ActionId +
+
+
+
Outcome ID
+
@outcome.OutcomeId
-
-
-
- Started: - @DateTime.Parse(outcome.StartedUtc).ToString("g") -
-
- Ended: - @DateTime.Parse(outcome.EndedUtc).ToString("g") +
+
Action ID
+
@outcome.ActionId
+ @if (!string.IsNullOrEmpty(outcome.OutcomeJson)) {
- Outcome JSON: -
- @{ - var formattedJson = FormatJson(outcome.OutcomeJson); - @foreach (var line in formattedJson.Split('\n')) - { -
@line
- } - } +
+
Outcome JSON
+ +
+
+
@TruncateText(FormatJson(outcome.OutcomeJson), 200)
}
} - } +
-
- } + } - @if (!string.IsNullOrEmpty(workflowOutcome.ExecutionContext)) - { -
-

Execution Context

-
+ @if (!string.IsNullOrEmpty(workflowOutcome.ExecutionContext)) + { +
+
+

Execution Context

+ +
@{ - var formattedContext = FormatJson(workflowOutcome.ExecutionContext); - @foreach (var line in formattedContext.Split('\n')) - { -
@line
- } + var context = ParseExecutionContext(workflowOutcome.ExecutionContext); } -
-
- } - } - else if (workflowOutcome.WorkflowOutcomeState == EWorkflowOutcomeState.NotStarted) - { -

This operation has not started yet.

- } - else - { - @if (!string.IsNullOrEmpty(workflowOutcome.ActionOutcomesJson)) - { -
-

Action Workflow Outcome

-
- @{ - var formattedJson = FormatJson(workflowOutcome.ActionOutcomesJson); - @foreach (var line in formattedJson.Split('\n')) +
+
+
Method
+
@context.Method
+
+ + @if (context.QueryParameters.Any()) { -
@line
+
+
Query Parameters
+
+ + + + + + + + + + @foreach (var param in context.QueryParameters) + { + + + + + + } + +
KeyValue
@param.Key +
@TruncateText(param.Value, 50)
+
+ @if (param.Value.Length > 50) + { + + } +
+
+
} - } -
-
- } - @if (!string.IsNullOrEmpty(workflowOutcome.ErrorJson)) - { -
-

Error

-
- @{ - var formattedError = FormatJson(workflowOutcome.ErrorJson); - @foreach (var line in formattedError.Split('\n')) + @if (!string.IsNullOrEmpty(context.Body)) { -
@line
+
+
+
Body
+ +
+
+
@TruncateText(FormatJson(context.Body), 200)
+
+
} - } +
-
- } + } - @if (!string.IsNullOrEmpty(workflowOutcome.ExecutionContext)) - { -
-

Execution Context

-
- @{ - var formattedContext = FormatJson(workflowOutcome.ExecutionContext); - @foreach (var line in formattedContext.Split('\n')) - { -
@line
- } - } + @if (!string.IsNullOrEmpty(workflowOutcome.ErrorJson)) + { +
+
+

Error

+ +
+
+
@TruncateText(FormatJson(workflowOutcome.ErrorJson), 200)
+
-
- } - } -
-
+ } +
+
+
+
} else { -

No outcomes found for the selected workflow.

+
+

No outcomes found for the selected workflow.

+
}
@@ -210,6 +230,7 @@ else

Loading...

} + @code { private readonly CancellationTokenSource cts = new CancellationTokenSource(); private Guid? selectedWorkflowId; @@ -252,11 +273,11 @@ else private string GetOutcomeStateColor(EWorkflowOutcomeState state) => state switch { - EWorkflowOutcomeState.Success => "text-green-600", - EWorkflowOutcomeState.FailedWithErrors => "text-red-600", - EWorkflowOutcomeState.NotStarted => "text-gray-600", - EWorkflowOutcomeState.Running => "text-blue-600", - _ => "text-gray-600" + EWorkflowOutcomeState.Success => "bg-green-100 text-green-800", + EWorkflowOutcomeState.FailedWithErrors => "bg-red-100 text-red-800", + EWorkflowOutcomeState.NotStarted => "bg-slate-100 text-slate-800", + EWorkflowOutcomeState.Running => "bg-blue-100 text-blue-800", + _ => "bg-slate-100 text-slate-800" }; private void ToggleOutcomeDetails(Guid workflowOutcomeId) @@ -282,6 +303,13 @@ else public string? OutcomeJson { get; set; } } + private class ExecutionContext + { + public string Method { get; set; } = ""; + public Dictionary QueryParameters { get; set; } = new(); + public string Body { get; set; } = ""; + } + private List ParseAndFormatJson(string json) { try @@ -295,6 +323,19 @@ else } } + private ExecutionContext ParseExecutionContext(string json) + { + try + { + return JsonSerializer.Deserialize(json) ?? new ExecutionContext(); + } + catch (Exception ex) + { + Logger.LogError(ex, "Failed to parse execution context"); + return new ExecutionContext(); + } + } + private string FormatJson(string json) { try @@ -309,4 +350,24 @@ else return json; } } + + private string TruncateText(string text, int maxLength) + { + if (string.IsNullOrEmpty(text) || text.Length <= maxLength) + return text; + + return $"{text[..maxLength]}..."; + } + + private async Task CopyToClipboard(string text) + { + try + { + await JSRuntime.InvokeVoidAsync("navigator.clipboard.writeText", text); + } + catch (Exception ex) + { + Logger.LogError(ex, "Failed to copy text to clipboard"); + } + } } \ No newline at end of file diff --git a/Blocktrust.CredentialWorkflow.Web/wwwroot/app.css b/Blocktrust.CredentialWorkflow.Web/wwwroot/app.css index e14a027..0df5b33 100644 --- a/Blocktrust.CredentialWorkflow.Web/wwwroot/app.css +++ b/Blocktrust.CredentialWorkflow.Web/wwwroot/app.css @@ -897,6 +897,15 @@ video { min-width: 100%; } +.min-w-0 { + min-width: 0px; +} + +.min-w-fit { + min-width: -moz-fit-content; + min-width: fit-content; +} + .max-w-md { max-width: 28rem; } @@ -905,10 +914,18 @@ video { max-width: 56rem; } +.max-w-full { + max-width: 100%; +} + .flex-1 { flex: 1 1 0%; } +.shrink-0 { + flex-shrink: 0; +} + .flex-grow { flex-grow: 1; } @@ -960,6 +977,10 @@ video { grid-template-columns: repeat(2, minmax(0, 1fr)); } +.grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); +} + .flex-row { flex-direction: row; } @@ -1012,6 +1033,10 @@ video { gap: 2rem; } +.gap-2 { + gap: 0.5rem; +} + .space-x-2 > :not([hidden]) ~ :not([hidden]) { --tw-space-x-reverse: 0; margin-right: calc(0.5rem * var(--tw-space-x-reverse)); @@ -1054,6 +1079,12 @@ video { margin-bottom: calc(1.25rem * var(--tw-space-y-reverse)); } +.space-y-6 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(1.5rem * var(--tw-space-y-reverse)); +} + .divide-y > :not([hidden]) ~ :not([hidden]) { --tw-divide-y-reverse: 0; border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); @@ -1065,6 +1096,11 @@ video { border-color: rgb(229 231 235 / var(--tw-divide-opacity)); } +.divide-slate-200 > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: 1; + border-color: rgb(226 232 240 / var(--tw-divide-opacity)); +} + .self-end { align-self: flex-end; } @@ -1085,6 +1121,12 @@ video { overflow-y: auto; } +.truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .whitespace-nowrap { white-space: nowrap; } @@ -1093,6 +1135,14 @@ video { white-space: pre; } +.whitespace-pre-wrap { + white-space: pre-wrap; +} + +.break-all { + word-break: break-all; +} + .rounded { border-radius: 0.25rem; } @@ -1147,6 +1197,10 @@ video { border-top-width: 2px; } +.border-b { + border-bottom-width: 1px; +} + .border-blue-500 { --tw-border-opacity: 1; border-color: rgb(59 130 246 / var(--tw-border-opacity)); @@ -1322,6 +1376,11 @@ video { background-color: rgb(254 242 242 / var(--tw-bg-opacity)); } +.bg-slate-100 { + --tw-bg-opacity: 1; + background-color: rgb(241 245 249 / var(--tw-bg-opacity)); +} + .bg-opacity-50 { --tw-bg-opacity: 0.5; } @@ -1415,6 +1474,11 @@ video { padding-bottom: 2rem; } +.px-2\.5 { + padding-left: 0.625rem; + padding-right: 0.625rem; +} + .pl-4 { padding-left: 1rem; } @@ -1825,6 +1889,16 @@ a { background-color: rgb(71 85 105 / var(--tw-bg-opacity)); } +.hover\:bg-slate-100:hover { + --tw-bg-opacity: 1; + background-color: rgb(241 245 249 / var(--tw-bg-opacity)); +} + +.hover\:bg-slate-50:hover { + --tw-bg-opacity: 1; + background-color: rgb(248 250 252 / var(--tw-bg-opacity)); +} + .hover\:text-blue-700:hover { --tw-text-opacity: 1; color: rgb(29 78 216 / var(--tw-text-opacity)); @@ -1870,6 +1944,11 @@ a { color: rgb(239 68 68 / var(--tw-text-opacity)); } +.hover\:text-slate-700:hover { + --tw-text-opacity: 1; + color: rgb(51 65 85 / var(--tw-text-opacity)); +} + .focus\:border-slate-400:focus { --tw-border-opacity: 1; border-color: rgb(148 163 184 / var(--tw-border-opacity)); @@ -1918,6 +1997,12 @@ a { opacity: 1; } +@media (min-width: 640px) { + .sm\:grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + @media (min-width: 768px) { .md\:col-span-2 { grid-column: span 2 / span 2;