diff --git a/Blocktrust.CredentialWorkflow.Web/Components/Features/HttpEndpointInfo/HttpEndpointInfo.razor b/Blocktrust.CredentialWorkflow.Web/Components/Features/HttpEndpointInfo/HttpEndpointInfo.razor new file mode 100644 index 0000000..9a4fbdd --- /dev/null +++ b/Blocktrust.CredentialWorkflow.Web/Components/Features/HttpEndpointInfo/HttpEndpointInfo.razor @@ -0,0 +1,175 @@ +@using Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Triggers +@using Blocktrust.CredentialWorkflow.Web.Services +@using System.Web +@inject NavigationManager NavigationManager +@inject ClipboardService ClipboardService + +
+
+

HTTP Endpoint Reference

+ +
+ + @if (isExpanded) + { +
+ @if (showCopiedMessage) + { +
+ Copied to clipboard! +
+ } + + +
+
+ Endpoint Details + +
+
+
Method: @TriggerInput.Method
+
@DisplayUrl
+
+
+ + +
+
+ cURL Example + +
+
+
@CurlCommand
+
+
+ + +
+
+ Request Schema + +
+
+
@TruncatedJsonSchema
+
+
+
+ } +
+ +@code { + [Parameter] public Guid WorkflowId { get; set; } + [Parameter] public TriggerInputHttpRequest TriggerInput { get; set; } = null!; + + private bool showCopiedMessage; + private bool isExpanded = true; + + private string FullUrl => $"{NavigationManager.BaseUri.TrimEnd('/')}/api/workflow/{WorkflowId}"; + private string DisplayUrl => FullUrl.Length > 60 ? $"{FullUrl[..57]}..." : FullUrl; + + private string CurlCommand + { + get + { + var command = $"curl -X {TriggerInput.Method} \"{FullUrl}\""; + + var requiredParams = TriggerInput.Parameters.Where(p => p.Value.Required); + + // Add query parameters if GET request + if (TriggerInput.Method == "GET" && requiredParams.Any()) + { + var queryParams = requiredParams + .Select(p => $"{p.Key}={HttpUtility.UrlEncode($"example {p.Key}")}"); + command += $"?{string.Join("&", queryParams)}"; + } + + // Add headers and body for POST/PUT + if (TriggerInput.Method is "POST" or "PUT" && requiredParams.Any()) + { + command += " \\\n -H \"Content-Type: application/json\""; + + // Add example body with required parameters + var exampleBody = requiredParams + .ToDictionary( + p => p.Key, + p => $"example {p.Key}" + ); + + var jsonBody = System.Text.Json.JsonSerializer.Serialize(exampleBody, + new System.Text.Json.JsonSerializerOptions { + WriteIndented = true, + PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase + }); + command += $" \\\n -d '{jsonBody}'"; + } + + return command; + } + } + + private string JsonSchema + { + get + { + var schema = new + { + type = "object", + required = TriggerInput.Parameters + .Where(p => p.Value.Required) + .Select(p => p.Key) + .ToList(), + properties = TriggerInput.Parameters.ToDictionary( + p => p.Key, + p => new + { + type = p.Value.Type.ToString().ToLower(), + description = p.Value.Description ?? $"Parameter: {p.Key}", + required = p.Value.Required + } + ) + }; + + return System.Text.Json.JsonSerializer.Serialize(schema, + new System.Text.Json.JsonSerializerOptions { + WriteIndented = true, + PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase + }); + } + } + + private string TruncatedJsonSchema + { + get + { + var fullSchema = JsonSchema; + if (fullSchema.Length <= 300) return fullSchema; + return fullSchema[..300] + "\n ...\n}"; + } + } + private async Task CopyToClipboard(string text) + { + await ClipboardService.CopyTextToClipboard(text); + showCopiedMessage = true; + await InvokeAsync(async () => + { + await Task.Delay(2000); + showCopiedMessage = false; + StateHasChanged(); + }); + } +} \ No newline at end of file diff --git a/Blocktrust.CredentialWorkflow.Web/Components/Features/WorkflowDesigner.razor b/Blocktrust.CredentialWorkflow.Web/Components/Features/WorkflowDesigner.razor index 6296017..10ba24e 100644 --- a/Blocktrust.CredentialWorkflow.Web/Components/Features/WorkflowDesigner.razor +++ b/Blocktrust.CredentialWorkflow.Web/Components/Features/WorkflowDesigner.razor @@ -5,7 +5,7 @@ @using Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Triggers @using Action = Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Actions.Action -
+
@if (showToast) { diff --git a/Blocktrust.CredentialWorkflow.Web/Components/Pages/Designer.razor b/Blocktrust.CredentialWorkflow.Web/Components/Pages/Designer.razor index 47dd009..2279d5d 100644 --- a/Blocktrust.CredentialWorkflow.Web/Components/Pages/Designer.razor +++ b/Blocktrust.CredentialWorkflow.Web/Components/Pages/Designer.razor @@ -2,6 +2,8 @@ @using Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow @using Blocktrust.CredentialWorkflow.Web.Components.Features @using Blocktrust.CredentialWorkflow.Web.Components.Features.PropertyWindow +@using Blocktrust.CredentialWorkflow.Web.Components.Features.HttpEndpointInfo + @using Blocktrust.CredentialWorkflow.Web.Components.Layout @using Blocktrust.CredentialWorkflow.Web.Services @using MediatR @@ -12,6 +14,7 @@ @using Blocktrust.CredentialWorkflow.Core.Domain.Enums @using Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Triggers @using Microsoft.AspNetCore.Authorization + @inject NavigationManager NavigationManager @inject AuthenticationStateProvider AuthenticationStateProvider @inject IMediator Mediator @@ -28,7 +31,7 @@ @if (AppStateService.IsInitialized) { -
+
@if (currentWorkflow is not null) @@ -50,8 +53,9 @@ @bind:event="oninput" @onkeypress="HandleKeyPress"/> }
@@ -108,25 +112,39 @@ } -
+
+ + @if (currentWorkflow?.ProcessFlow?.Triggers.Any() == true && + currentWorkflow.ProcessFlow.Triggers.First().Value.Type == ETriggerType.HttpRequest) + { +
+ +
+ } + +
@if (showToast) { -
+
@toastMessage
} + Workflow="@currentWorkflow" + OnItemSelected="HandleItemSelected" + OnChange="HandleChange"/>
-
- + + +
+
@@ -137,7 +155,6 @@ else
} - @code { [Parameter] public Guid workflowId { get; set; } private readonly CancellationTokenSource cts = new(); diff --git a/Blocktrust.CredentialWorkflow.Web/wwwroot/app.css b/Blocktrust.CredentialWorkflow.Web/wwwroot/app.css index 51d87cc..60da7e2 100644 --- a/Blocktrust.CredentialWorkflow.Web/wwwroot/app.css +++ b/Blocktrust.CredentialWorkflow.Web/wwwroot/app.css @@ -1089,6 +1089,10 @@ video { border-color: rgb(226 232 240 / var(--tw-divide-opacity)); } +.overflow-auto { + overflow: auto; +} + .overflow-hidden { overflow: hidden; } @@ -1177,6 +1181,10 @@ video { border-top-width: 2px; } +.border-r { + border-right-width: 1px; +} + .border-blue-500 { --tw-border-opacity: 1; border-color: rgb(59 130 246 / var(--tw-border-opacity)); @@ -1357,6 +1365,11 @@ video { background-color: rgb(254 249 195 / var(--tw-bg-opacity)); } +.bg-green-50 { + --tw-bg-opacity: 1; + background-color: rgb(240 253 244 / var(--tw-bg-opacity)); +} + .bg-opacity-50 { --tw-bg-opacity: 0.5; } @@ -1875,6 +1888,11 @@ a { background-color: rgb(71 85 105 / var(--tw-bg-opacity)); } +.hover\:bg-gray-200:hover { + --tw-bg-opacity: 1; + background-color: rgb(229 231 235 / var(--tw-bg-opacity)); +} + .hover\:text-blue-700:hover { --tw-text-opacity: 1; color: rgb(29 78 216 / var(--tw-text-opacity)); @@ -1925,6 +1943,11 @@ a { color: rgb(51 65 85 / var(--tw-text-opacity)); } +.hover\:text-gray-700:hover { + --tw-text-opacity: 1; + color: rgb(55 65 81 / var(--tw-text-opacity)); +} + .hover\:opacity-80:hover { opacity: 0.8; }