Incremental work on dashboard + API
This commit is contained in:
parent
b4bf08e15c
commit
c9199bb335
|
|
@ -1,13 +1,15 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using Elsa.Client.Comparers;
|
||||
using NodaTime;
|
||||
|
||||
namespace Elsa.Client.Models
|
||||
{
|
||||
[DataContract]
|
||||
public class WorkflowInstance
|
||||
{
|
||||
private HashSet<BlockingActivity> _blockingActivities = new HashSet<BlockingActivity>(BlockingActivityEqualityComparer.Instance);
|
||||
|
||||
private HashSet<BlockingActivity> _blockingActivities = new(BlockingActivityEqualityComparer.Instance);
|
||||
|
||||
public WorkflowInstance()
|
||||
{
|
||||
Variables = new Variables();
|
||||
|
|
@ -17,27 +19,30 @@ namespace Elsa.Client.Models
|
|||
PostScheduledActivities = new Stack<ScheduledActivity>();
|
||||
}
|
||||
|
||||
public int Id { get; set; }
|
||||
public string WorkflowInstanceId { get; set; } = default!;
|
||||
public string WorkflowDefinitionId { get; set; } = default!;
|
||||
public int Version { get; set; }
|
||||
public WorkflowStatus Status { get; set; }
|
||||
public string? CorrelationId { get; set; }
|
||||
public string? ContextId { get; set; }
|
||||
public Instant CreatedAt { get; set; }
|
||||
public Variables Variables { get; set; }
|
||||
public object? Output { get; set; }
|
||||
public ICollection<ActivityInstance> Activities { get; set; }
|
||||
[DataMember(Order = 1)] public int Id { get; set; }
|
||||
[DataMember(Order = 2)] public string WorkflowInstanceId { get; set; } = default!;
|
||||
[DataMember(Order = 3)] public string WorkflowDefinitionId { get; set; } = default!;
|
||||
[DataMember(Order = 4)] public int Version { get; set; }
|
||||
[DataMember(Order = 5)] public WorkflowStatus Status { get; set; }
|
||||
[DataMember(Order = 6)] public string? CorrelationId { get; set; }
|
||||
[DataMember(Order = 7)] public string? ContextId { get; set; }
|
||||
[DataMember(Order = 8)] public Instant CreatedAt { get; set; }
|
||||
[DataMember(Order = 9)] public Instant? LastExecutedAt { get; set; }
|
||||
[DataMember(Order = 10)] public Instant? LastBurstAt { get; set; }
|
||||
[DataMember(Order = 11)] public Instant? CompletedAt { get; set; }
|
||||
[DataMember(Order = 12)] public Variables Variables { get; set; }
|
||||
[DataMember(Order = 13)] public object? Output { get; set; }
|
||||
[DataMember(Order = 14)] public ICollection<ActivityInstance> Activities { get; set; }
|
||||
|
||||
public HashSet<BlockingActivity> BlockingActivities
|
||||
[DataMember(Order = 15)] public HashSet<BlockingActivity> BlockingActivities
|
||||
{
|
||||
get => _blockingActivities;
|
||||
set => _blockingActivities = new HashSet<BlockingActivity>(value, BlockingActivityEqualityComparer.Instance);
|
||||
}
|
||||
|
||||
public ICollection<ExecutionLogEntry> ExecutionLog { get; set; }
|
||||
public WorkflowFault? Fault { get; set; }
|
||||
public Stack<ScheduledActivity> ScheduledActivities { get; set; }
|
||||
public Stack<ScheduledActivity> PostScheduledActivities { get; set; }
|
||||
|
||||
[DataMember(Order = 16)] public ICollection<ExecutionLogEntry> ExecutionLog { get; set; }
|
||||
[DataMember(Order = 17)] public WorkflowFault? Fault { get; set; }
|
||||
[DataMember(Order = 18)] public Stack<ScheduledActivity> ScheduledActivities { get; set; }
|
||||
[DataMember(Order = 19)] public Stack<ScheduledActivity> PostScheduledActivities { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
{
|
||||
public interface ICompositeActivity<in TBuilder> where TBuilder: ICompositeActivityBuilder
|
||||
{
|
||||
void Build(TBuilder compositeActivity);
|
||||
void Build(TBuilder workflow);
|
||||
}
|
||||
|
||||
public interface ICompositeActivity : ICompositeActivity<ICompositeActivityBuilder>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ namespace Elsa.Builders
|
|||
bool DeleteCompletedInstances { get; }
|
||||
bool IsEnabled { get; }
|
||||
IWorkflowBuilder WithWorkflowDefinitionId(string value);
|
||||
IWorkflowBuilder WithDisplayName(string value);
|
||||
IWorkflowBuilder WithDescription(string value);
|
||||
IWorkflowBuilder WithVersion(int value);
|
||||
IWorkflowBuilder AsSingleton();
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ namespace Elsa.Models
|
|||
public string? CorrelationId { get; set; }
|
||||
public string? ContextId { get; set; }
|
||||
public Instant CreatedAt { get; set; }
|
||||
public Instant? LastExecutedAt { get; set; }
|
||||
public Instant? LastBurstAt { get; set; }
|
||||
public Instant? CompletedAt { get; set; }
|
||||
public Variables Variables { get; set; }
|
||||
public object? Output { get; set; }
|
||||
public ICollection<ActivityInstance> Activities { get; set; }
|
||||
|
|
|
|||
|
|
@ -45,6 +45,12 @@ namespace Elsa.Builders
|
|||
return this;
|
||||
}
|
||||
|
||||
public IWorkflowBuilder WithDisplayName(string value)
|
||||
{
|
||||
DisplayName = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IWorkflowBuilder WithDescription(string? value)
|
||||
{
|
||||
Description = value;
|
||||
|
|
@ -123,7 +129,12 @@ namespace Elsa.Builders
|
|||
|
||||
public IWorkflowBlueprint Build(IWorkflow workflow, string activityIdPrefix)
|
||||
{
|
||||
WithId(workflow.GetType().Name);
|
||||
var workflowTypeName = workflow.GetType().Name;
|
||||
|
||||
Name ??= workflowTypeName;
|
||||
DisplayName ??= workflowTypeName;
|
||||
|
||||
WithId(workflowTypeName);
|
||||
workflow.Build(this);
|
||||
return BuildBlueprint(activityIdPrefix);
|
||||
}
|
||||
|
|
@ -139,7 +150,7 @@ namespace Elsa.Builders
|
|||
public IWorkflowBlueprint BuildBlueprint(string activityIdPrefix)
|
||||
{
|
||||
var compositeRoot = Build(activityIdPrefix);
|
||||
|
||||
|
||||
return new WorkflowBlueprint(
|
||||
ActivityId,
|
||||
Version,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
||||
<link rel="stylesheet" href="/tailwind.css">
|
||||
<link href="_content/Blazored.Modal/blazored-modal.css" rel="stylesheet" />
|
||||
<link href="ElsaDashboard.Application.Server.styles.css" rel="stylesheet"/>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@@ryangjchandler/spruce@2.0.1/dist/spruce.umd.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.js"></script>
|
||||
|
|
@ -45,5 +46,6 @@ else
|
|||
<script src="_framework/blazor.webassembly.js"></script>
|
||||
}
|
||||
<script src="_content/ElsaDashboard.Application/store.js"></script>
|
||||
<script src="_content/Blazored.Modal/blazored.modal.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
<Router AppAssembly="@typeof(MenuItem).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
|
||||
</Found>
|
||||
<NotFound>
|
||||
<LayoutView Layout="@typeof(MainLayout)">
|
||||
<p>Sorry, there's nothing at this address.</p>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
<CascadingBlazoredModal>
|
||||
<Router AppAssembly="@typeof(MenuItem).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
|
||||
</Found>
|
||||
<NotFound>
|
||||
<LayoutView Layout="@typeof(MainLayout)">
|
||||
<p>Sorry, there's nothing at this address.</p>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
</CascadingBlazoredModal>
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Blazored.Modal" Version="5.1.0" />
|
||||
<PackageReference Include="Humanizer.Core" Version="2.8.26" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using ElsaDashboard.Application.Services;
|
||||
using Blazored.Modal;
|
||||
using ElsaDashboard.Application.Services;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
|
|
@ -7,7 +8,9 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
{
|
||||
public static IServiceCollection AddElsaDashboardUI(this IServiceCollection services)
|
||||
{
|
||||
return services.AddScoped<IFlyoutPanelService, FlyoutPanelService>();
|
||||
return services
|
||||
.AddBlazoredModal()
|
||||
.AddScoped<IFlyoutPanelService, FlyoutPanelService>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<TailwindModal></TailwindModal>
|
||||
|
|
@ -60,7 +60,10 @@
|
|||
<tbody class="bg-white divide-y divide-gray-100">
|
||||
@foreach (var workflowInstance in WorkflowInstances.Items)
|
||||
{
|
||||
var displayName = workflowInstance.Id;
|
||||
var workflowBlueprint = WorkflowBlueprints[(workflowInstance.WorkflowDefinitionId, workflowInstance.Version)];
|
||||
var displayName = workflowBlueprint.DisplayName;
|
||||
var statusColor = GetStatusColor(workflowInstance.Status);
|
||||
var viewUrl = $"workflow-instances/{workflowInstance.WorkflowInstanceId}/viewer";
|
||||
<tr>
|
||||
<td class="px-6 py-3 whitespace-no-wrap text-sm leading-5 font-medium text-gray-900">
|
||||
<a href="@($"workflow-instances/{workflowInstance.WorkflowInstanceId}/viewer")" class="truncate hover:text-gray-600">
|
||||
|
|
@ -69,140 +72,48 @@
|
|||
</td>
|
||||
<td class="px-6 py-3 whitespace-no-wrap text-sm leading-5 font-medium text-gray-900 text-left">
|
||||
<a href="@($"workflow-registry/{workflowInstance.WorkflowDefinitionId}/viewer")" class="truncate hover:text-gray-600">
|
||||
Hello World
|
||||
@displayName
|
||||
</a>
|
||||
</td>
|
||||
<td class="hidden md:table-cell px-6 py-3 whitespace-no-wrap text-sm leading-5 text-gray-500 text-right">
|
||||
1
|
||||
@workflowInstance.Version
|
||||
</td>
|
||||
<td class="hidden md:table-cell px-6 py-3 whitespace-no-wrap text-sm leading-5 text-gray-500 text-right">
|
||||
<div class="flex items-center space-x-3 lg:pl-2">
|
||||
<div class="flex-shrink-0 w-2.5 h-2.5 rounded-full bg-pink-600"></div>
|
||||
<span>Completed</span>
|
||||
<div class="flex-shrink-0 w-2.5 h-2.5 rounded-full bg-@statusColor-600"></div>
|
||||
<span>@workflowInstance.Status</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="hidden md:table-cell px-6 py-3 whitespace-no-wrap text-sm leading-5 text-gray-500 text-left">
|
||||
Today
|
||||
@workflowInstance.CreatedAt
|
||||
</td>
|
||||
<td class="hidden md:table-cell px-6 py-3 whitespace-no-wrap text-sm leading-5 text-gray-500 text-left">
|
||||
Today
|
||||
@(workflowInstance.CompletedAt != null ? workflowInstance.CompletedAt.ToString() : "-")
|
||||
</td>
|
||||
<td class="pr-6">
|
||||
<div x-data="{ open: false }" x-on:keydown.escape="open = false" x-on:click.away="open = false" class="relative flex justify-end items-center">
|
||||
<button x-on:click="open = !open" id="project-options-menu-0" aria-has-popup="true" type="button" class="w-8 h-8 inline-flex items-center justify-center text-gray-400 rounded-full bg-transparent hover:text-gray-500 focus:outline-none focus:text-gray-500 focus:bg-gray-100 transition ease-in-out duration-150">
|
||||
<!-- Heroicon name: dots-vertical -->
|
||||
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z"/>
|
||||
<ContextMenu>
|
||||
<ContextMenuItem Url="@viewUrl">
|
||||
<!-- Heroicon name: pencil-alt -->
|
||||
<svg class="mr-3 h-5 w-5 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"/>
|
||||
<path fill-rule="evenodd" d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div x-show="open"
|
||||
x-transition:enter="transition ease-out duration-100"
|
||||
x-transition:enter-start="transform opacity-0 scale-95"
|
||||
x-transition:leave="transition ease-in duration-75"
|
||||
x-transition:leave-start="transform opacity-100 scale-100"
|
||||
x-transition:leave-end="transform opacity-0 scale-95"
|
||||
class="z-10 mx-3 origin-top-right absolute right-7 top-0 w-48 mt-1 rounded-md shadow-lg">
|
||||
<div class="rounded-md bg-white shadow-xs" role="menu" aria-orientation="vertical" aria-labelledby="project-options-menu-0">
|
||||
<div class="py-1">
|
||||
<a href="@($"workflow-instances/{workflowInstance.WorkflowInstanceId}/viewer")" class="group flex items-center px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">
|
||||
<!-- Heroicon name: pencil-alt -->
|
||||
<svg class="mr-3 h-5 w-5 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"/>
|
||||
<path fill-rule="evenodd" d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
Edit
|
||||
</a>
|
||||
<a href="#" class="group flex items-center px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">
|
||||
<!-- Heroicon name: duplicate -->
|
||||
<svg class="mr-3 h-5 w-5 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M7 9a2 2 0 012-2h6a2 2 0 012 2v6a2 2 0 01-2 2H9a2 2 0 01-2-2V9z"/>
|
||||
<path d="M5 3a2 2 0 00-2 2v6a2 2 0 002 2V5h8a2 2 0 00-2-2H5z"/>
|
||||
</svg>
|
||||
Duplicate
|
||||
</a>
|
||||
<a href="#" class="group flex items-center px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">
|
||||
<!-- Heroicon name: user-add -->
|
||||
<svg class="mr-3 h-5 w-5 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M8 9a3 3 0 100-6 3 3 0 000 6zM8 11a6 6 0 016 6H2a6 6 0 016-6zM16 7a1 1 0 10-2 0v1h-1a1 1 0 100 2h1v1a1 1 0 102 0v-1h1a1 1 0 100-2h-1V7z"/>
|
||||
</svg>
|
||||
Share
|
||||
</a>
|
||||
</div>
|
||||
<div class="border-t border-gray-100"></div>
|
||||
<div class="py-1">
|
||||
<a href="#" class="group flex items-center px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">
|
||||
<!-- Heroicon name: trash -->
|
||||
<svg class="mr-3 h-5 w-5 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
Delete
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
View
|
||||
</ContextMenuItem>
|
||||
<ContextMenuDivider/>
|
||||
<ContextMenuItem OnClick="@(() => OnDeleteWorkflowInstanceClick(workflowInstance))">
|
||||
<!-- Heroicon name: trash -->
|
||||
<svg class="mr-3 h-5 w-5 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
Delete
|
||||
</ContextMenuItem>
|
||||
</ContextMenu>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6">
|
||||
<div class="flex-1 flex justify-between sm:hidden">
|
||||
<a href="#" class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm leading-5 font-medium rounded-md text-gray-700 bg-white hover:text-gray-500 focus:outline-none focus:shadow-outline-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
|
||||
Previous
|
||||
</a>
|
||||
<a href="#" class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm leading-5 font-medium rounded-md text-gray-700 bg-white hover:text-gray-500 focus:outline-none focus:shadow-outline-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
|
||||
Next
|
||||
</a>
|
||||
</div>
|
||||
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<p class="text-sm leading-5 text-gray-700">
|
||||
Showing
|
||||
<span class="font-medium">1</span>
|
||||
to
|
||||
<span class="font-medium">10</span>
|
||||
of
|
||||
<span class="font-medium">97</span>
|
||||
results
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<nav class="relative z-0 inline-flex shadow-sm">
|
||||
<a href="#" class="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-500 hover:text-gray-400 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-500 transition ease-in-out duration-150" aria-label="Previous">
|
||||
<svg class="h-5 w-5" x-description="Heroicon name: chevron-left" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="#" class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
|
||||
1
|
||||
</a>
|
||||
<a href="#" class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
|
||||
2
|
||||
</a>
|
||||
<a href="#" class="hidden md:inline-flex -ml-px relative items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
|
||||
3
|
||||
</a>
|
||||
<span class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700">
|
||||
...
|
||||
</span>
|
||||
<a href="#" class="hidden md:inline-flex -ml-px relative items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
|
||||
8
|
||||
</a>
|
||||
<a href="#" class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
|
||||
9
|
||||
</a>
|
||||
<a href="#" class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
|
||||
10
|
||||
</a>
|
||||
<a href="#" class="-ml-px relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-500 hover:text-gray-400 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-500 transition ease-in-out duration-150" aria-label="Next">
|
||||
<svg class="h-5 w-5" x-description="Heroicon name: chevron-right" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Pager Page="@WorkflowInstances.Page" PageSize="@WorkflowInstances.PageSize" TotalCount="@WorkflowInstances.TotalCount"/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,5 +1,11 @@
|
|||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Blazored.Modal;
|
||||
using Blazored.Modal.Services;
|
||||
using Elsa.Client.Models;
|
||||
using ElsaDashboard.Application.Shared;
|
||||
using ElsaDashboard.Shared.Rpc;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
|
|
@ -8,11 +14,45 @@ namespace ElsaDashboard.Application.Pages.WorkflowInstances
|
|||
partial class List
|
||||
{
|
||||
[Inject] private IWorkflowInstanceService WorkflowInstanceService { get; set; } = default!;
|
||||
[Inject] private IWorkflowRegistryService WorkflowRegistryService { get; set; } = default!;
|
||||
[Inject] private IModalService ModalService { get; set; } = default!;
|
||||
private PagedList<WorkflowInstance> WorkflowInstances { get; set; } = new();
|
||||
private IDictionary<(string, int), WorkflowBlueprint> WorkflowBlueprints { get; set; } = new Dictionary<(string, int), WorkflowBlueprint>();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
WorkflowInstances = await WorkflowInstanceService.ListAsync();
|
||||
var workflowBlueprintsTask = WorkflowRegistryService.ListAsync();
|
||||
var workflowInstancesTask = WorkflowInstanceService.ListAsync();
|
||||
|
||||
await Task.WhenAll(workflowBlueprintsTask, workflowInstancesTask);
|
||||
|
||||
WorkflowBlueprints = workflowBlueprintsTask.Result.Items.ToDictionary(x => (x.Id, x.Version));
|
||||
WorkflowInstances = workflowInstancesTask.Result;
|
||||
}
|
||||
|
||||
private async Task OnDeleteWorkflowInstanceClick(WorkflowInstance workflowInstance)
|
||||
{
|
||||
var options = new ModalOptions { UseCustomLayout = true };
|
||||
var parameters = new ModalParameters();
|
||||
parameters.Add("Title", "Are you sure?");
|
||||
var result = await ModalService.Show<DeleteDialog>("Are you sure?").Result;
|
||||
|
||||
if (result.Cancelled)
|
||||
return;
|
||||
|
||||
// Delete.
|
||||
}
|
||||
|
||||
private static string GetStatusColor(WorkflowStatus status) =>
|
||||
status switch
|
||||
{
|
||||
WorkflowStatus.Idle => "gray",
|
||||
WorkflowStatus.Running => "pink",
|
||||
WorkflowStatus.Suspended => "blue",
|
||||
WorkflowStatus.Finished => "green",
|
||||
WorkflowStatus.Faulted => "red",
|
||||
WorkflowStatus.Cancelled => "yellow",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(status), status, null)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<div x-data="{ open: false }" x-on:keydown.escape="open = false" x-on:click.away="open = false" class="relative flex justify-end items-center">
|
||||
<button x-on:click="open = !open" id="project-options-menu-0" aria-has-popup="true" type="button" class="w-8 h-8 inline-flex items-center justify-center text-gray-400 rounded-full bg-transparent hover:text-gray-500 focus:outline-none focus:text-gray-500 focus:bg-gray-100 transition ease-in-out duration-150">
|
||||
<!-- Heroicon name: dots-vertical -->
|
||||
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div x-show="open"
|
||||
x-transition:enter="transition ease-out duration-100"
|
||||
x-transition:enter-start="transform opacity-0 scale-95"
|
||||
x-transition:leave="transition ease-in duration-75"
|
||||
x-transition:leave-start="transform opacity-100 scale-100"
|
||||
x-transition:leave-end="transform opacity-0 scale-95"
|
||||
class="z-10 mx-3 origin-top-right absolute right-7 top-0 w-48 mt-1 rounded-md shadow-lg">
|
||||
<div class="rounded-md bg-white shadow-xs" role="menu" aria-orientation="vertical" aria-labelledby="project-options-menu-0">
|
||||
<div class="py-1">
|
||||
@ChildContent
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace ElsaDashboard.Application.Shared
|
||||
{
|
||||
partial class ContextMenu
|
||||
{
|
||||
[Parameter]public RenderFragment ChildContent { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<div class="border-t border-gray-100"></div>
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<a @onclick:preventDefault @onclick="@OnClick" href="#" class="group flex items-center px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">
|
||||
@if (ChildContent != null)
|
||||
{
|
||||
@ChildContent
|
||||
}
|
||||
else
|
||||
{
|
||||
<svg class="mr-3 h-5 w-5 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="@Icon"/>
|
||||
</svg>
|
||||
@Text
|
||||
}
|
||||
</a>
|
||||
|
||||
@code
|
||||
{
|
||||
[Inject]
|
||||
private NavigationManager NavigationManager { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Text { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public string Icon { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? Url { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback OnClick { get; set; }
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
if (Url != null)
|
||||
OnClick = new EventCallbackFactory().Create(this, Navigate);
|
||||
}
|
||||
|
||||
private void Navigate() => NavigationManager.NavigateTo(Url!);
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
@if (Page != null)
|
||||
{
|
||||
<div class="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6">
|
||||
<div class="flex-1 flex justify-between sm:hidden">
|
||||
@if (Page > 0)
|
||||
{
|
||||
<a href="#" class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm leading-5 font-medium rounded-md text-gray-700 bg-white hover:text-gray-500 focus:outline-none focus:shadow-outline-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
|
||||
Previous
|
||||
</a>
|
||||
}
|
||||
@if (Page < PageCount)
|
||||
{
|
||||
<a href="#" class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm leading-5 font-medium rounded-md text-gray-700 bg-white hover:text-gray-500 focus:outline-none focus:shadow-outline-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
|
||||
Next
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<p class="text-sm leading-5 text-gray-700">
|
||||
Showing
|
||||
<span class="font-medium">@From</span>
|
||||
to
|
||||
<span class="font-medium">@To</span>
|
||||
of
|
||||
<span class="font-medium">@TotalCount</span>
|
||||
results
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<nav class="relative z-0 inline-flex shadow-sm">
|
||||
@if (Page > 0)
|
||||
{
|
||||
<a href="#" class="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-500 hover:text-gray-400 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-500 transition ease-in-out duration-150" aria-label="Previous">
|
||||
<svg class="h-5 w-5" x-description="Heroicon name: chevron-left" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
</a>
|
||||
}
|
||||
@for (var i = 1; i <= PageCount; i++)
|
||||
{
|
||||
var isCurrent = Page + 1 == i;
|
||||
if (isCurrent)
|
||||
{
|
||||
<span class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700">
|
||||
@i
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a href="#" class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
|
||||
@i
|
||||
</a>
|
||||
}
|
||||
}
|
||||
@if (Page < PageCount)
|
||||
{
|
||||
<a href="#" class="-ml-px relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-500 hover:text-gray-400 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-500 transition ease-in-out duration-150" aria-label="Next">
|
||||
<svg class="h-5 w-5" x-description="Heroicon name: chevron-right" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
</a>
|
||||
}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace ElsaDashboard.Application.Shared
|
||||
{
|
||||
partial class Pager
|
||||
{
|
||||
[Parameter] public int? Page { get; set; }
|
||||
[Parameter] public int? PageSize { get; set; }
|
||||
[Parameter] public int TotalCount { get; set; }
|
||||
|
||||
private int From => Page!.Value * PageSize!.Value + 1;
|
||||
private int To => Math.Min(From + PageSize!.Value, TotalCount);
|
||||
private int PageCount => TotalCount / PageSize!.Value;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<div class="fixed z-10 inset-0 overflow-y-auto">
|
||||
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
||||
|
||||
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
|
||||
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
|
||||
</div>
|
||||
|
||||
<!-- This element is to trick the browser into centering the modal contents. -->
|
||||
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span>
|
||||
<div class="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6" role="dialog" aria-modal="true" aria-labelledby="modal-headline">
|
||||
<div class="hidden sm:block absolute top-0 right-0 pt-4 pr-4">
|
||||
<button type="button" class="bg-white rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
<span class="sr-only">Close</span>
|
||||
<!-- Heroicon name: x -->
|
||||
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="sm:flex sm:items-start">
|
||||
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<!-- Heroicon name: exclamation -->
|
||||
<svg class="h-6 w-6 text-red-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900" id="modal-headline">
|
||||
Deactivate account
|
||||
</h3>
|
||||
<div class="mt-2">
|
||||
<p class="text-sm text-gray-500">
|
||||
Are you sure you want to deactivate your account? All of your data will be permanently removed from our servers forever. This action cannot be undone.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<button type="button" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">
|
||||
Deactivate
|
||||
</button>
|
||||
<button type="button" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:w-auto sm:text-sm">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
||||
[CascadingParameter]
|
||||
BlazoredModalInstance BlazoredModal { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Message { get; set; }
|
||||
|
||||
async Task Close() => await BlazoredModal.Close(ModalResult.Ok(true));
|
||||
async Task Cancel() => await BlazoredModal.Cancel();
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@using Blazored.Modal
|
||||
@using Blazored.Modal.Services
|
||||
@using ElsaDashboard.Application
|
||||
@using ElsaDashboard.Application.Models
|
||||
@using ElsaDashboard.Application.Shared
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@
|
|||
<PackageReference Include="Grpc.AspNetCore.Server" Version="2.33.1" />
|
||||
<PackageReference Include="Grpc.AspNetCore.Web" Version="2.33.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="NodaTime.Serialization.Protobuf" Version="2.0.0" />
|
||||
<PackageReference Include="protobuf-net.Grpc.AspNetCore" Version="1.0.136" />
|
||||
<PackageReference Include="protobuf-net.NodaTime" Version="3.0.73" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -17,9 +17,10 @@ namespace ElsaDashboard.Backend.Extensions
|
|||
public static IServiceCollection AddElsaDashboardBackend(this IServiceCollection services, Action<ElsaClientOptions>? configure = default)
|
||||
{
|
||||
RuntimeTypeModel.Default.AddElsaGrpcSurrogates();
|
||||
RuntimeTypeModel.Default.AddNodaTime();
|
||||
services.AddCors();
|
||||
services.AddElsaClient(configure);
|
||||
services.AddCodeFirstGrpc(options => options.ResponseCompressionLevel = CompressionLevel.Optimal);
|
||||
services.AddCodeFirstGrpc(options => options.ResponseCompressionLevel = CompressionLevel.Fastest);
|
||||
services.AddScoped<IActivityService, ActivityService>();
|
||||
services.AddScoped<IWorkflowDefinitionService, WorkflowDefinitionService>();
|
||||
services.AddScoped<IWorkflowRegistryService, WorkflowRegistryService>();
|
||||
|
|
|
|||
|
|
@ -10,10 +10,7 @@ namespace ElsaDashboard.Backend.Rpc
|
|||
{
|
||||
private readonly IElsaClient _elsaClient;
|
||||
public WorkflowInstanceService(IElsaClient elsaClient) => _elsaClient = elsaClient;
|
||||
|
||||
public Task<PagedList<WorkflowInstance>> ListAsync(int page = 0, int pageSize = 50, CallContext context = default) =>
|
||||
_elsaClient.WorkflowInstanceses.ListAsync(page, pageSize, context.CancellationToken);
|
||||
|
||||
public Task<PagedList<WorkflowInstance>> ListAsync(int page = 0, int pageSize = 50, CallContext context = default) => _elsaClient.WorkflowInstanceses.ListAsync(page, pageSize, context.CancellationToken);
|
||||
public Task<WorkflowInstance?> GetById(string id, CallContext context = default) => _elsaClient.WorkflowInstanceses.GetByIdAsync(id, context.CancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,8 @@ namespace ElsaDashboard.Shared.Surrogates
|
|||
private static readonly IDictionary<Type, Type> SurrogateMapping = new Dictionary<Type, Type>
|
||||
{
|
||||
[typeof(ActivityPropertyInfo)] = typeof(ActivityPropertyDescriptorSurrogate),
|
||||
[typeof(VersionOptions)] = typeof(VersionOptionsSurrogate)
|
||||
[typeof(VersionOptions)] = typeof(VersionOptionsSurrogate),
|
||||
[typeof(WorkflowInstance)] = typeof(WorkflowInstanceSurrogate)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -21,9 +22,9 @@ namespace ElsaDashboard.Shared.Surrogates
|
|||
/// </summary>
|
||||
public static RuntimeTypeModel AddElsaGrpcSurrogates(this RuntimeTypeModel runtimeTypeModel)
|
||||
{
|
||||
foreach (var map in SurrogateMapping)
|
||||
foreach (var map in SurrogateMapping)
|
||||
runtimeTypeModel.Add(map.Key, false).SetSurrogate(map.Value);
|
||||
|
||||
|
||||
return runtimeTypeModel;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
using Elsa.Client.Models;
|
||||
using Newtonsoft.Json;
|
||||
using NodaTime;
|
||||
using NodaTime.Serialization.JsonNet;
|
||||
using ProtoBuf;
|
||||
|
||||
namespace ElsaDashboard.Shared.Surrogates
|
||||
{
|
||||
[ProtoContract(IgnoreListHandling = true)]
|
||||
public class WorkflowInstanceSurrogate
|
||||
{
|
||||
private static readonly JsonSerializerSettings SerializerSettings;
|
||||
|
||||
static WorkflowInstanceSurrogate()
|
||||
{
|
||||
SerializerSettings = new JsonSerializerSettings
|
||||
{
|
||||
TypeNameHandling = TypeNameHandling.Auto
|
||||
}.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
|
||||
}
|
||||
|
||||
public WorkflowInstanceSurrogate(WorkflowInstance value)
|
||||
{
|
||||
Value = Serialize(value);
|
||||
}
|
||||
|
||||
[ProtoMember(1)] public string Value { get; }
|
||||
|
||||
public static implicit operator WorkflowInstance(WorkflowInstanceSurrogate surrogate) => Deserialize(surrogate.Value)!;
|
||||
public static implicit operator WorkflowInstanceSurrogate(WorkflowInstance source) => new(source);
|
||||
|
||||
private static WorkflowInstance Deserialize(string json) => JsonConvert.DeserializeObject<WorkflowInstance>(json, SerializerSettings)!;
|
||||
private static string Serialize(WorkflowInstance value) => JsonConvert.SerializeObject(value, SerializerSettings)!;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Grpc.Net.Client.Web" Version="2.33.1" />
|
||||
<PackageReference Include="protobuf-net.NodaTime" Version="3.0.73" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ namespace ElsaDashboard.WebAssembly.Extensions
|
|||
public static IServiceCollection AddElsaDashboardBackend(this IServiceCollection services)
|
||||
{
|
||||
RuntimeTypeModel.Default.AddElsaGrpcSurrogates();
|
||||
RuntimeTypeModel.Default.AddNodaTime();
|
||||
return services
|
||||
.AddGrpcClient<IActivityService>()
|
||||
.AddGrpcClient<IWorkflowDefinitionService>();
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@
|
|||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
<!-- <ItemGroup>-->
|
||||
<!-- <FrameworkReference Include="Microsoft.AspNetCore.App" />-->
|
||||
<!-- </ItemGroup>-->
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using AutoMapper;
|
||||
using Elsa.Models;
|
||||
using Elsa.Server.Api.Endpoints.WorkflowRegistry;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
|
|
|
|||
50
src/server/Elsa.Server.Api/Models/WorkflowInstance.cs
Normal file
50
src/server/Elsa.Server.Api/Models/WorkflowInstance.cs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using Elsa.Comparers;
|
||||
using Elsa.Models;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NodaTime;
|
||||
|
||||
namespace Elsa.Server.Api.Models
|
||||
{
|
||||
[DataContract]
|
||||
public class WorkflowInstanceModel
|
||||
{
|
||||
private HashSet<BlockingActivity> _blockingActivities = new(BlockingActivityEqualityComparer.Instance);
|
||||
|
||||
public WorkflowInstanceModel()
|
||||
{
|
||||
Variables = new Variables();
|
||||
Activities = new List<ActivityInstance>();
|
||||
ExecutionLog = new List<ExecutionLogEntry>();
|
||||
ScheduledActivities = new Stack<ScheduledActivity>();
|
||||
PostScheduledActivities = new Stack<ScheduledActivity>();
|
||||
}
|
||||
|
||||
[DataMember(Order = 1)] public int Id { get; set; }
|
||||
[DataMember(Order = 2)] public string WorkflowInstanceId { get; set; } = default!;
|
||||
[DataMember(Order = 3)] public string WorkflowDefinitionId { get; set; } = default!;
|
||||
[DataMember(Order = 4)] public int Version { get; set; }
|
||||
[DataMember(Order = 5)] public WorkflowStatus Status { get; set; }
|
||||
[DataMember(Order = 6)] public string? CorrelationId { get; set; }
|
||||
[DataMember(Order = 7)] public string? ContextId { get; set; }
|
||||
[DataMember(Order = 8)] public Instant CreatedAt { get; set; }
|
||||
[DataMember(Order = 9)] public Instant? LastExecutedAt { get; set; }
|
||||
[DataMember(Order = 10)] public Instant? LastBurstAt { get; set; }
|
||||
[DataMember(Order = 11)] public Instant? CompletedAt { get; set; }
|
||||
[DataMember(Order = 12)] public Variables Variables { get; set; }
|
||||
[DataMember(Order = 13)] public JObject? Output { get; set; }
|
||||
[DataMember(Order = 14)] public ICollection<ActivityInstance> Activities { get; set; }
|
||||
|
||||
[DataMember(Order = 15)] public HashSet<BlockingActivity> BlockingActivities
|
||||
{
|
||||
get => _blockingActivities;
|
||||
set => _blockingActivities = new HashSet<BlockingActivity>(value, BlockingActivityEqualityComparer.Instance);
|
||||
}
|
||||
|
||||
[DataMember(Order = 16)] public ICollection<ExecutionLogEntry> ExecutionLog { get; set; }
|
||||
[DataMember(Order = 17)] public WorkflowFault? Fault { get; set; }
|
||||
[DataMember(Order = 18)] public Stack<ScheduledActivity> ScheduledActivities { get; set; }
|
||||
[DataMember(Order = 19)] public Stack<ScheduledActivity> PostScheduledActivities { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ namespace Elsa.Server.Host.Workflows
|
|||
public void Build(IWorkflowBuilder workflow)
|
||||
{
|
||||
workflow
|
||||
.WithDisplayName("Hello World!")
|
||||
.HttpRequestReceived("/hello-world")
|
||||
.WriteHttpResponse(HttpStatusCode.OK, "Hello World!", "text/plain");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue