committed by
GitHub
258 changed files with 10052 additions and 300 deletions
@ -0,0 +1,64 @@ |
|||||
|
@using System.Globalization |
||||
|
@using Microsoft.Extensions.Hosting |
||||
|
@using Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme.Bundling |
||||
|
@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Bundling |
||||
|
@using Volo.Abp.Localization |
||||
|
@inject IHostEnvironment Env |
||||
|
@{ |
||||
|
var rtl = CultureHelper.IsRtl ? "rtl" : string.Empty; |
||||
|
} |
||||
|
|
||||
|
<!DOCTYPE html> |
||||
|
<html lang="@CultureInfo.CurrentCulture.Name" dir="@rtl"> |
||||
|
|
||||
|
<head> |
||||
|
<meta charset="utf-8" /> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
||||
|
<title>Elsa Studio (Blazor Server)</title> |
||||
|
<base href="/" /> |
||||
|
|
||||
|
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" /> |
||||
|
<AbpStyles BundleName="@BlazorMudBlazorBasicThemeBundles.Styles.Global" /> |
||||
|
|
||||
|
@* Elsa Studio UI styles (RCL static assets) + offline icon-font fallback. *@ |
||||
|
<link href="/_content/CodeBeam.MudBlazor.Extensions/MudExtensions.min.css" rel="stylesheet" /> |
||||
|
<link href="/_content/Radzen.Blazor/css/material-base.css" rel="stylesheet" /> |
||||
|
<link href="/_content/Elsa.Studio.Shell/css/shell.css" rel="stylesheet" /> |
||||
|
<link href="/_content/Elsa.Studio.Workflows.Designer/designer.css" rel="stylesheet" /> |
||||
|
<link href="/_content/Elsa.Studio.Workflows.Designer/designer.v1.css" rel="stylesheet" /> |
||||
|
<link href="/_content/Elsa.Studio.Workflows.Designer/designer.v2.css" rel="stylesheet" /> |
||||
|
<link href="/elsa-studio-icons.css" rel="stylesheet" /> |
||||
|
|
||||
|
<HeadOutlet @rendermode="InteractiveServer" /> |
||||
|
|
||||
|
</head> |
||||
|
<body> |
||||
|
|
||||
|
<Routes @rendermode="InteractiveServer" /> |
||||
|
|
||||
|
<AbpScripts BundleName="@BlazorMudBlazorBasicThemeBundles.Scripts.Global" /> |
||||
|
|
||||
|
@* Monaco + Blazor Server bootstrap (mirrors the official Elsa Studio Server host). *@ |
||||
|
<script src="/_content/MudBlazor/MudBlazor.min.js"></script> |
||||
|
<script src="/_content/CodeBeam.MudBlazor.Extensions/MudExtensions.min.js"></script> |
||||
|
<script src="/_content/Radzen.Blazor/Radzen.Blazor.js"></script> |
||||
|
<script src="/_content/BlazorMonaco/jsInterop.js"></script> |
||||
|
<script src="/_content/BlazorMonaco/lib/monaco-editor/min/vs/loader.js"></script> |
||||
|
<script src="/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.js"></script> |
||||
|
<script src="/_framework/blazor.web.js"></script> |
||||
|
|
||||
|
<div id="blazor-error-ui"> |
||||
|
@if (Env.IsDevelopment()) |
||||
|
{ |
||||
|
<text>An unhandled exception has occurred. See browser dev tools for details.</text> |
||||
|
} |
||||
|
else if (Env.IsStaging() || Env.IsProduction()) |
||||
|
{ |
||||
|
<text>An error has occurred. This application may no longer respond until reloaded.</text> |
||||
|
} |
||||
|
<a href="" class="reload">Reload</a> |
||||
|
<a class="dismiss">✖</a> |
||||
|
</div> |
||||
|
|
||||
|
</body> |
||||
|
</html> |
||||
@ -0,0 +1,137 @@ |
|||||
|
@page "/" |
||||
|
@using Microsoft.AspNetCore.Components.Authorization |
||||
|
@inject AuthenticationStateProvider AuthenticationStateProvider |
||||
|
@inject NavigationManager Navigation |
||||
|
|
||||
|
<MudGrid Class="mb-3"> |
||||
|
<MudItem xs="12" md="6"> |
||||
|
<MudCard Style="height: 100%;"> |
||||
|
<MudCardContent> |
||||
|
@if (!IsAuthenticated) |
||||
|
{ |
||||
|
<MudText Typo="Typo.h5" Class="mb-2">欢迎使用 Elsa 工作室</MudText> |
||||
|
<MudText Typo="Typo.body1" Class="mud-text-secondary mb-2"> |
||||
|
以 Blazor Server 模式集成的 Elsa 工作流工作室:服务端渲染与交互,接口调用在服务端完成,凭据不出浏览器。 |
||||
|
</MudText> |
||||
|
<MudButton Href="/Account/Login" Variant="Variant.Filled" Color="Color.Primary" Class="mb-4" |
||||
|
StartIcon="@Icons.Material.Filled.Login">登录</MudButton> |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
<MudText Typo="Typo.h5" Class="mb-2">欢迎回来</MudText> |
||||
|
<MudText Typo="Typo.body1" Class="mud-text-secondary mb-3"> |
||||
|
通过左侧菜单进入 <strong>Elsa 工作室</strong>:管理工作流定义与实例,打开设计器进行可视化编排。 |
||||
|
</MudText> |
||||
|
<MudButton StartIcon="@Icons.Material.Filled.AccountTree" Variant="Variant.Text" Color="Color.Primary" |
||||
|
OnClick="GoDefinitions">工作流定义</MudButton> |
||||
|
} |
||||
|
</MudCardContent> |
||||
|
</MudCard> |
||||
|
</MudItem> |
||||
|
<MudItem xs="12" md="6"> |
||||
|
<MudCard Style="height: 100%;"> |
||||
|
<MudCardContent> |
||||
|
<MudText Typo="Typo.h6" Color="Color.Primary" Class="mb-2"> |
||||
|
<MudIcon Icon="@Icons.Material.Filled.Info" Class="me-1" />关于 |
||||
|
</MudText> |
||||
|
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-3"> |
||||
|
服务端 Blazor Server(SignalR)承载 Elsa Studio;认证走外部 OIDC AuthServer 授权码 + PKCE; |
||||
|
Studio 页面以 ABP 子页面呈现(路由在菜单中定义)。 |
||||
|
</MudText> |
||||
|
<MudDivider Class="my-2" /> |
||||
|
<MudText Typo="Typo.body2" Class="mud-text-secondary"> |
||||
|
集成模块:LINGYUN.Abp.ElsaNext.Studio.Blazor |
||||
|
</MudText> |
||||
|
</MudCardContent> |
||||
|
</MudCard> |
||||
|
</MudItem> |
||||
|
</MudGrid> |
||||
|
|
||||
|
<MudGrid> |
||||
|
<MudItem xs="12" lg="4"> |
||||
|
<MudCard Style="height: 100%;"> |
||||
|
<MudCardHeader> |
||||
|
<MudText Typo="Typo.h6">More from ABP.IO</MudText> |
||||
|
</MudCardHeader> |
||||
|
<MudCardContent Class="pt-0"> |
||||
|
<MudList T="string" Dense="true"> |
||||
|
<MudListItem Icon="@Icons.Material.Filled.Check" IconColor="Color.Primary" |
||||
|
Href="https://github.com/abpframework/abp/releases" Target="_blank"> |
||||
|
Latest Release Logs |
||||
|
</MudListItem> |
||||
|
<MudListItem Icon="@Icons.Material.Filled.Check" IconColor="Color.Primary" |
||||
|
Href="https://abp.io/video-courses/essentials" Target="_blank"> |
||||
|
Video Courses |
||||
|
</MudListItem> |
||||
|
<MudListItem Icon="@Icons.Material.Filled.Check" IconColor="Color.Primary" |
||||
|
Href="https://docs.abp.io/en/abp/latest/Samples/Index" Target="_blank"> |
||||
|
Samples |
||||
|
</MudListItem> |
||||
|
<MudListItem Icon="@Icons.Material.Filled.Check" IconColor="Color.Primary" |
||||
|
Href="https://abp.io/books" Target="_blank"> |
||||
|
Books |
||||
|
</MudListItem> |
||||
|
<MudListItem Icon="@Icons.Material.Filled.Check" IconColor="Color.Primary" |
||||
|
Href="https://abp.io/faq" Target="_blank"> |
||||
|
FAQ |
||||
|
</MudListItem> |
||||
|
</MudList> |
||||
|
</MudCardContent> |
||||
|
</MudCard> |
||||
|
</MudItem> |
||||
|
<MudItem xs="12" lg="8"> |
||||
|
<MudCard Style="height: 100%;"> |
||||
|
<MudCardHeader> |
||||
|
<MudText Typo="Typo.h6">Follow us on Social Media</MudText> |
||||
|
</MudCardHeader> |
||||
|
<MudCardContent Class="pt-0"> |
||||
|
<MudGrid Spacing="0"> |
||||
|
<MudItem xs="12" sm="6"> |
||||
|
<MudList T="string" Dense="true"> |
||||
|
<MudListItem Href="https://twitter.com/abpframework" Target="_blank" |
||||
|
Icon="@Icons.Custom.Brands.Twitter" IconColor="Color.Primary"> |
||||
|
X.com (Twitter) |
||||
|
</MudListItem> |
||||
|
<MudListItem Href="https://community.abp.io/discord" Target="_blank" |
||||
|
Icon="@Icons.Custom.Brands.Discord" IconColor="Color.Primary"> |
||||
|
Discord |
||||
|
</MudListItem> |
||||
|
<MudListItem Href="https://stackoverflow.com/questions/tagged/abp" Target="_blank" |
||||
|
Icon="@Icons.Custom.Brands.StackOverflow" IconColor="Color.Primary"> |
||||
|
Stack Overflow |
||||
|
</MudListItem> |
||||
|
</MudList> |
||||
|
</MudItem> |
||||
|
<MudItem xs="12" sm="6"> |
||||
|
<MudList T="string" Dense="true"> |
||||
|
<MudListItem Href="@("https://www.youtube.com/@Volosoft")" Target="_blank" |
||||
|
Icon="@Icons.Custom.Brands.YouTube" IconColor="Color.Error"> |
||||
|
YouTube |
||||
|
</MudListItem> |
||||
|
<MudListItem Href="https://www.instagram.com/abpframework/" Target="_blank" |
||||
|
Icon="@Icons.Custom.Brands.Instagram" IconColor="Color.Secondary"> |
||||
|
Instagram |
||||
|
</MudListItem> |
||||
|
<MudListItem Href="https://github.com/abpframework/abp" Target="_blank" |
||||
|
Icon="@Icons.Custom.Brands.GitHub" IconColor="Color.Dark"> |
||||
|
GitHub |
||||
|
</MudListItem> |
||||
|
</MudList> |
||||
|
</MudItem> |
||||
|
</MudGrid> |
||||
|
</MudCardContent> |
||||
|
</MudCard> |
||||
|
</MudItem> |
||||
|
</MudGrid> |
||||
|
|
||||
|
@code { |
||||
|
private bool IsAuthenticated; |
||||
|
|
||||
|
protected override async Task OnInitializedAsync() |
||||
|
{ |
||||
|
var state = await AuthenticationStateProvider.GetAuthenticationStateAsync(); |
||||
|
IsAuthenticated = state.User.Identity?.IsAuthenticated == true; |
||||
|
} |
||||
|
|
||||
|
private void GoDefinitions() => Navigation.NavigateTo("/workflows/definitions"); |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
@inject NavigationManager Navigation |
||||
|
|
||||
|
@code |
||||
|
{ |
||||
|
protected override void OnInitialized() |
||||
|
{ |
||||
|
var returnUrl = Uri.EscapeDataString(Navigation.Uri); |
||||
|
Navigation.NavigateTo($"/Account/Login?returnUrl={returnUrl}", forceLoad: true); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
@using Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme.Themes.Basic |
||||
|
@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing |
||||
|
@using Microsoft.Extensions.Options |
||||
|
@inject IOptions<AbpRouterOptions> RouterOptions |
||||
|
<Router AppAssembly="typeof(Program).Assembly" AdditionalAssemblies="RouterOptions.Value.AdditionalAssemblies"> |
||||
|
<Found Context="routeData"> |
||||
|
<AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)"> |
||||
|
<NotAuthorized> |
||||
|
<LoginRedirect /> |
||||
|
</NotAuthorized> |
||||
|
</AuthorizeRouteView> |
||||
|
</Found> |
||||
|
</Router> |
||||
@ -0,0 +1,12 @@ |
|||||
|
@using System.Net.Http |
||||
|
@using Microsoft.AspNetCore.Authorization |
||||
|
@using Microsoft.AspNetCore.Components.Authorization |
||||
|
@using Microsoft.AspNetCore.Components.Forms |
||||
|
@using Microsoft.AspNetCore.Components.Routing |
||||
|
@using Microsoft.AspNetCore.Components.Web |
||||
|
@using Microsoft.AspNetCore.Components.Web.Virtualization |
||||
|
@using static Microsoft.AspNetCore.Components.Web.RenderMode |
||||
|
@using Microsoft.JSInterop |
||||
|
@using MudBlazor |
||||
|
@using Volo.Abp.MudBlazorUI |
||||
|
@using Volo.Abp.MudBlazorUI.Components |
||||
@ -0,0 +1,42 @@ |
|||||
|
using Microsoft.AspNetCore.Authorization; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.MicroService.WorkflowService; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Root <see cref="IAuthorizationPolicyProvider"/> for Shells hosting. Elsa 3.8.0-rc2 endpoints
|
||||
|
/// declare FastEndpoints permission policies named "epPolicy:<EndpointType>" that CShells
|
||||
|
/// registers inside the shell's own container, so ASP.NET's root policy provider (ABP's) cannot
|
||||
|
/// resolve them. ABP authenticates /elsa/api with the AuthServer JWT and every validated token
|
||||
|
/// carries Elsa's "all permissions" claim (see <see cref="WorkflowManagementNextBlazorServerModule"/>),
|
||||
|
/// so epPolicy names are bridged to that grant; other policy names fall back to the defaults.
|
||||
|
/// </summary>
|
||||
|
public sealed class ElsaShellPermissionPolicyProvider : IAuthorizationPolicyProvider |
||||
|
{ |
||||
|
// Allow-all policy (always-true assertion; AuthorizationPolicy rejects zero requirements).
|
||||
|
private static readonly AuthorizationPolicy AllowAll = new AuthorizationPolicyBuilder().RequireAssertion(_ => true).Build(); |
||||
|
private readonly DefaultAuthorizationPolicyProvider _default = new(Options.Create(new AuthorizationOptions())); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public Task<AuthorizationPolicy?> GetPolicyAsync(string policyName) |
||||
|
{ |
||||
|
if (!policyName.StartsWith("epPolicy:", StringComparison.Ordinal)) |
||||
|
return _default.GetPolicyAsync(policyName); |
||||
|
|
||||
|
var policy = new AuthorizationPolicyBuilder() |
||||
|
.RequireAuthenticatedUser() |
||||
|
.RequireAssertion(context => |
||||
|
context.User.HasClaim(Elsa.PermissionNames.ClaimType, Elsa.PermissionNames.All)) |
||||
|
.Build(); |
||||
|
|
||||
|
return Task.FromResult<AuthorizationPolicy?>(policy); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public Task<AuthorizationPolicy?> GetDefaultPolicyAsync() => Task.FromResult<AuthorizationPolicy?>(AllowAll); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public Task<AuthorizationPolicy?> GetFallbackPolicyAsync() => Task.FromResult<AuthorizationPolicy?>(AllowAll); |
||||
|
} |
||||
@ -0,0 +1,130 @@ |
|||||
|
using Consul; |
||||
|
using Elsa.Studio.Authentication.Abstractions.ComponentProviders; |
||||
|
using Elsa.Studio.Authentication.Abstractions.Contracts; |
||||
|
using Elsa.Studio.Authentication.Abstractions.Models; |
||||
|
using Elsa.Studio.Authentication.OpenIdConnect.BlazorServer.Components; |
||||
|
using Elsa.Studio.Authentication.OpenIdConnect.BlazorServer.Services; |
||||
|
using Elsa.Studio.Authentication.OpenIdConnect.Contracts; |
||||
|
using Elsa.Studio.Authentication.OpenIdConnect.Models; |
||||
|
using Elsa.Studio.Authentication.OpenIdConnect.Services; |
||||
|
using Elsa.Studio.Contracts; |
||||
|
using Microsoft.AspNetCore.Authentication.Cookies; |
||||
|
using Microsoft.AspNetCore.Authentication.JwtBearer; |
||||
|
using Microsoft.AspNetCore.Authentication.OpenIdConnect; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.DependencyInjection.Extensions; |
||||
|
using Polly; |
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Net.Http; |
||||
|
|
||||
|
namespace LINGYUN.Abp.MicroService.WorkflowService.Extensions; |
||||
|
|
||||
|
public static class ElsaStudioServiceCollectionExtensions |
||||
|
{ |
||||
|
internal const string AnonymousHttpClientName = "Elsa.Studio.Authentication.OpenIdConnect.BlazorServer.Anonymous"; |
||||
|
|
||||
|
public static IServiceCollection AddOpenIdConnectAuth( |
||||
|
this IServiceCollection services, |
||||
|
Action<OidcOptions> configure, |
||||
|
Func<IAsyncPolicy<HttpResponseMessage>> configureRetryPolicy = null) |
||||
|
{ |
||||
|
var options = new OidcOptions(); |
||||
|
configure(options); |
||||
|
services.AddSingleton<StudioAuthenticationProviderRegistration>(new StudioAuthenticationProviderRegistration(StudioAuthenticationProvider.OpenIdConnect)); |
||||
|
// Ensure we always request the minimal identity scopes.
|
||||
|
var configuredScopes = options.AuthenticationScopes?.Where(s => !string.IsNullOrWhiteSpace(s)).Distinct(StringComparer.OrdinalIgnoreCase).ToArray() ?? Array.Empty<string>(); |
||||
|
if (configuredScopes.Length == 0) |
||||
|
configuredScopes = ["openid", "profile", "offline_access"]; |
||||
|
options.AuthenticationScopes = configuredScopes; |
||||
|
|
||||
|
// Set Blazor Server defaults for callback paths if not explicitly specified.
|
||||
|
options.CallbackPath ??= "/signin-oidc"; |
||||
|
options.SignedOutCallbackPath ??= "/signout-callback-oidc"; |
||||
|
|
||||
|
// Register core services
|
||||
|
services.AddHttpContextAccessor(); |
||||
|
services.AddSingleton(options); |
||||
|
services.AddScoped<ITokenProvider, ServerTokenProvider>(); |
||||
|
services.AddScoped<IHttpConnectionOptionsConfigurator, OidcHttpConnectionOptionsConfigurator>(); |
||||
|
|
||||
|
// Shared token refresh service used by both session refresh and backend API token acquisition
|
||||
|
services.AddScoped<TokenRefreshService>(); |
||||
|
|
||||
|
// Cookie authentication events for automatic session token refresh (standard ASP.NET Core pattern)
|
||||
|
services.AddScoped<AuthCookieEvents>(); |
||||
|
|
||||
|
// Configure ASP.NET Core authentication with cookie and OIDC
|
||||
|
services.AddAuthentication(authOptions => |
||||
|
{ |
||||
|
authOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; |
||||
|
authOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; |
||||
|
}) |
||||
|
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, cookieOptions => |
||||
|
{ |
||||
|
cookieOptions.Cookie.Name = "ElsaStudio.Auth"; |
||||
|
cookieOptions.Cookie.HttpOnly = true; |
||||
|
cookieOptions.Cookie.SecurePolicy = Microsoft.AspNetCore.Http.CookieSecurePolicy.Always; |
||||
|
cookieOptions.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax; |
||||
|
cookieOptions.ExpireTimeSpan = TimeSpan.FromHours(8); |
||||
|
cookieOptions.SlidingExpiration = true; |
||||
|
cookieOptions.LoginPath = "/Account/Login"; |
||||
|
cookieOptions.AccessDeniedPath = "/Account/Login"; |
||||
|
|
||||
|
// Use custom events for automatic token refresh on every request validation
|
||||
|
cookieOptions.EventsType = typeof(AuthCookieEvents); |
||||
|
}) |
||||
|
.AddAbpOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, oidcOptions => |
||||
|
{ |
||||
|
oidcOptions.Authority = options.Authority; |
||||
|
oidcOptions.ClientId = options.ClientId; |
||||
|
oidcOptions.ClientSecret = options.ClientSecret; |
||||
|
oidcOptions.ResponseType = options.ResponseType; |
||||
|
oidcOptions.UsePkce = options.UsePkce; |
||||
|
oidcOptions.SaveTokens = options.SaveTokens; |
||||
|
oidcOptions.CallbackPath = options.CallbackPath; |
||||
|
oidcOptions.SignedOutCallbackPath = options.SignedOutCallbackPath; |
||||
|
oidcOptions.RequireHttpsMetadata = options.RequireHttpsMetadata; |
||||
|
oidcOptions.GetClaimsFromUserInfoEndpoint = options.GetClaimsFromUserInfoEndpoint; |
||||
|
oidcOptions.PushedAuthorizationBehavior = PushedAuthorizationBehavior.Disable; |
||||
|
|
||||
|
// Configure scopes
|
||||
|
oidcOptions.Scope.Clear(); |
||||
|
foreach (var scope in options.AuthenticationScopes) |
||||
|
{ |
||||
|
oidcOptions.Scope.Add(scope); |
||||
|
} |
||||
|
|
||||
|
// Map token response properties to enable token refresh
|
||||
|
oidcOptions.MapInboundClaims = false; |
||||
|
|
||||
|
if (!string.IsNullOrWhiteSpace(options.MetadataAddress)) |
||||
|
{ |
||||
|
oidcOptions.MetadataAddress = options.MetadataAddress; |
||||
|
} |
||||
|
|
||||
|
// Configure token validation parameters
|
||||
|
oidcOptions.TokenValidationParameters = new() |
||||
|
{ |
||||
|
NameClaimType = options.NameClaimType, |
||||
|
RoleClaimType = options.RoleClaimType, |
||||
|
ValidateIssuer = true |
||||
|
}; |
||||
|
}); |
||||
|
|
||||
|
// Add authorization services
|
||||
|
services.AddAuthorizationCore(); |
||||
|
|
||||
|
// Use an OIDC-aware unauthorized component that initiates a challenge.
|
||||
|
services.AddScoped<IUnauthorizedComponentProvider, UnauthorizedComponentProvider<ChallengeToLogin>>(); |
||||
|
services.AddScoped<ILoginMethodCatalog, DirectOpenIdConnectLoginMethodCatalog>(); |
||||
|
services.AddScoped<ILoginMethodComponentProvider, DirectOpenIdConnectLoginMethodComponentProvider>(); |
||||
|
|
||||
|
// HTTP client for token refresh requests with retry policy
|
||||
|
var retryPolicy = configureRetryPolicy?.Invoke() ?? Elsa.Studio.Authentication.OpenIdConnect.BlazorServer.Extensions.ServiceCollectionExtensions.DefaultRetryPolicy; |
||||
|
services.AddHttpClient(AnonymousHttpClientName) |
||||
|
.AddPolicyHandler(retryPolicy); |
||||
|
|
||||
|
return services; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"culture": "en", |
||||
|
"texts": { |
||||
|
"Menu:Logout": "Logout" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"culture": "zh-Hans", |
||||
|
"texts": { |
||||
|
"Menu:Logout": "退出登录" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
using Localization.Resources.AbpUi; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.UI.Navigation; |
||||
|
|
||||
|
namespace LINGYUN.Abp.MicroService.WorkflowService.Navigation; |
||||
|
|
||||
|
public class UserMenuContributor : IMenuContributor |
||||
|
{ |
||||
|
public virtual Task ConfigureMenuAsync(MenuConfigurationContext context) |
||||
|
{ |
||||
|
if (context.Menu.Name != StandardMenus.User) |
||||
|
{ |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
var uiResource = context.GetLocalizer<AbpUiResource>(); |
||||
|
|
||||
|
context.Menu.AddItem(new ApplicationMenuItem("Auth.Logout", uiResource["Menu:Logout"], url: "~/Account/Logout", icon: "fa fa-power-off", order: int.MaxValue - 1000)); |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
@page "/Account/Login" |
||||
|
@model LINGYUN.Abp.MicroService.WorkflowService.Pages.Auth.LoginModel |
||||
|
@{ |
||||
|
ViewData["Title"] = "Login"; |
||||
|
} |
||||
|
<p>Redirecting to the identity provider…</p> |
||||
@ -0,0 +1,21 @@ |
|||||
|
using Microsoft.AspNetCore.Authentication; |
||||
|
using Microsoft.AspNetCore.Authentication.OpenIdConnect; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Microsoft.AspNetCore.Mvc.RazorPages; |
||||
|
|
||||
|
namespace LINGYUN.Abp.MicroService.WorkflowService.Pages.Auth; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Triggers the standard OIDC authorization-code flow: signing in challenges the "oidc" scheme so
|
||||
|
/// the user is redirected to the external AuthServer, then back to this host's cookie.
|
||||
|
/// </summary>
|
||||
|
public class LoginModel : PageModel |
||||
|
{ |
||||
|
public IActionResult OnGet(string returnUrl = null) |
||||
|
{ |
||||
|
var redirectUri = string.IsNullOrWhiteSpace(returnUrl) ? "/" : returnUrl; |
||||
|
return Challenge( |
||||
|
new AuthenticationProperties { RedirectUri = redirectUri }, |
||||
|
OpenIdConnectDefaults.AuthenticationScheme); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
@page "/Account/Logout" |
||||
|
@model LINGYUN.Abp.MicroService.WorkflowService.Pages.Auth.LogoutModel |
||||
|
@{ |
||||
|
ViewData["Title"] = "Logout"; |
||||
|
} |
||||
|
<p>Signing out…</p> |
||||
@ -0,0 +1,22 @@ |
|||||
|
using Microsoft.AspNetCore.Authentication; |
||||
|
using Microsoft.AspNetCore.Authentication.Cookies; |
||||
|
using Microsoft.AspNetCore.Authentication.OpenIdConnect; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Microsoft.AspNetCore.Mvc.RazorPages; |
||||
|
|
||||
|
namespace LINGYUN.Abp.MicroService.WorkflowService.Pages.Auth; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Signs out the local cookie and ends the OIDC session at the external AuthServer.
|
||||
|
/// </summary>
|
||||
|
public class LogoutModel : PageModel |
||||
|
{ |
||||
|
public IActionResult OnGet(string returnUrl = null) |
||||
|
{ |
||||
|
var redirectUri = string.IsNullOrWhiteSpace(returnUrl) ? "/" : returnUrl; |
||||
|
return SignOut( |
||||
|
new AuthenticationProperties { RedirectUri = redirectUri }, |
||||
|
CookieAuthenticationDefaults.AuthenticationScheme, |
||||
|
OpenIdConnectDefaults.AuthenticationScheme); |
||||
|
} |
||||
|
} |
||||
@ -1,42 +0,0 @@ |
|||||
@page "/" |
|
||||
@inject IConfiguration Configuration; |
|
||||
@{ |
|
||||
var serverUrl = Configuration["Elsa:Server:BaseUrl"]; |
|
||||
} |
|
||||
<!DOCTYPE html> |
|
||||
<html lang="en"> |
|
||||
<head> |
|
||||
<meta charset="utf-8"/> |
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> |
|
||||
<title>Elsa Workflows</title> |
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/assets/images/favicon-32x32.png"> |
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/assets/images/favicon-16x16.png"> |
|
||||
<link rel="stylesheet" href="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/assets/fonts/inter/inter.css"> |
|
||||
<link rel="stylesheet" href="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/elsa-workflows-studio.css"> |
|
||||
<script src="/_content/Elsa.Designer.Components.Web/monaco-editor/min/vs/loader.js"></script> |
|
||||
<script type="module" src="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/elsa-workflows-studio.esm.js"></script> |
|
||||
</head> |
|
||||
<body> |
|
||||
<elsa-studio-root server-url="@serverUrl" base-path="" monaco-lib-path="./_content/Elsa.Designer.Components.Web/monaco-editor/min"> |
|
||||
<elsa-studio-dashboard></elsa-studio-dashboard> |
|
||||
</elsa-studio-root> |
|
||||
</body> |
|
||||
|
|
||||
<script type="module"> |
|
||||
// Import publicly exposed services. |
|
||||
import { WebhooksPlugin, WorkflowSettingsPlugin } from "/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/index.esm.js"; |
|
||||
|
|
||||
// Get a handle to the elsa-studio-root element. |
|
||||
const elsaStudioRoot = document.querySelector('elsa-studio-root'); |
|
||||
|
|
||||
// Configure Elsa during the 'initializing' event. |
|
||||
elsaStudioRoot.addEventListener('initializing', e => { |
|
||||
const elsa = e.detail; |
|
||||
elsa.pluginManager.registerPlugins([ |
|
||||
WebhooksPlugin, |
|
||||
WorkflowSettingsPlugin |
|
||||
]); |
|
||||
}); |
|
||||
</script> |
|
||||
|
|
||||
</html> |
|
||||
@ -1,2 +1,3 @@ |
|||||
@using Microsoft.Extensions.Configuration |
@using Microsoft.AspNetCore.Authorization |
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers |
@using Microsoft.AspNetCore.Http |
||||
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers |
||||
|
|||||
@ -0,0 +1,8 @@ |
|||||
|
{ |
||||
|
"version": "10.6.0", |
||||
|
"name": "aspire-workflow-service", |
||||
|
"private": true, |
||||
|
"dependencies": { |
||||
|
"@abp/aspnetcore.components.server.basictheme": "10.6.0" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,24 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net10.0</TargetFramework> |
||||
|
<AssemblyName>LINGYUN.Abp.ElsaNext.AI</AssemblyName> |
||||
|
<PackageId>LINGYUN.Abp.ElsaNext.AI</PackageId> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Elsa.AI.Copilot" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.ElsaNext\LINGYUN.Abp.ElsaNext.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,23 @@ |
|||||
|
using Elsa.Features.Services; |
||||
|
using LINGYUN.Abp.ElsaNext.AI.Extensions; |
||||
|
using Microsoft.Extensions.Configuration; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.AI; |
||||
|
|
||||
|
[DependsOn(typeof(AbpElsaNextModule))] |
||||
|
public class AbpElsaNextAIModule : AbpModule |
||||
|
{ |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
var configuration = context.Configuration.GetSection("Elsa:AI"); |
||||
|
PreConfigure<IModule>(elsa => |
||||
|
{ |
||||
|
elsa.UseAbpAI(ai => |
||||
|
{ |
||||
|
ai.ConfigureOptions = options => configuration.GetSection("Host").Bind(options); |
||||
|
ai.ConfigureCopilotOptions = options => configuration.GetSection("Copilot").Bind(options); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using Elsa.Extensions; |
||||
|
using Elsa.Features.Services; |
||||
|
using LINGYUN.Abp.ElsaNext.AI.Features; |
||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.AI.Extensions; |
||||
|
|
||||
|
public static class ModuleExtensions |
||||
|
{ |
||||
|
public static IModule UseAbpAI(this IModule module, Action<AbpAIFeature>? configure = null) |
||||
|
{ |
||||
|
return module.Use(configure); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
using Elsa.AI.Copilot.Options; |
||||
|
using Elsa.AI.Host.Features; |
||||
|
using Elsa.AI.Host.Options; |
||||
|
using Elsa.AI.Host.Services; |
||||
|
using Elsa.Extensions; |
||||
|
using Elsa.Features.Abstractions; |
||||
|
using Elsa.Features.Services; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.AI.Features; |
||||
|
|
||||
|
public class AbpAIFeature(IModule module) : FeatureBase(module) |
||||
|
{ |
||||
|
public Action<AIHostOptions>? ConfigureOptions { get; set; } |
||||
|
public Action<CopilotOptions>? ConfigureCopilotOptions { get; set; } |
||||
|
public Action<AIToolEnablementService>? ConfigureToolEnablement { get; set; } |
||||
|
|
||||
|
public override void Configure() |
||||
|
{ |
||||
|
Module.AddFastEndpointsAssembly<AIFeature>(); |
||||
|
} |
||||
|
|
||||
|
public override void Apply() |
||||
|
{ |
||||
|
Services.AddAIHostServices(ConfigureOptions); |
||||
|
Services.AddCopilotAIProvider(ConfigureCopilotOptions); |
||||
|
if (ConfigureToolEnablement != null) |
||||
|
{ |
||||
|
Services.AddSingleton(ConfigureToolEnablement); |
||||
|
} |
||||
|
Module.AddFastEndpointsFromModule(); |
||||
|
} |
||||
|
|
||||
|
public AbpAIFeature EnableWorkflowProposalTools() |
||||
|
{ |
||||
|
ConfigureToolEnablement += enablement => |
||||
|
{ |
||||
|
enablement.Enable("workflows.proposeCreate"); |
||||
|
enablement.Enable("workflows.proposeUpdate"); |
||||
|
}; |
||||
|
return this; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,49 @@ |
|||||
|
using Elsa.Studio.Extensions; |
||||
|
using LINGYUN.Abp.ElsaNext.Agents.Blazor.Navigation; |
||||
|
using LINGYUN.Abp.ElsaNext.Localization; |
||||
|
using LINGYUN.Abp.ElsaNext.Studio.Blazor; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.UI.Navigation; |
||||
|
using Volo.Abp.VirtualFileSystem; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.Agents.Blazor; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpElsaNextAgentsModule), |
||||
|
typeof(AbpElsaNextStudioBlazorModule))] |
||||
|
public class AbpElsaNextAgentsBlazorModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
var configuration = context.Configuration.GetSection("Elsa:Studio"); |
||||
|
|
||||
|
Configure<AbpRouterOptions>(options => |
||||
|
{ |
||||
|
options.AdditionalAssemblies.Add(typeof(Elsa.Studio.Agents.Feature).Assembly); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpNavigationOptions>(options => |
||||
|
{ |
||||
|
options.MenuContributors.Add(new ElsaStudioAgentsMenuContributor()); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpVirtualFileSystemOptions>(options => |
||||
|
{ |
||||
|
options.FileSets.AddEmbedded<AbpElsaNextAgentsBlazorModule>("LINGYUN.Abp.ElsaNext.Agents.Blazor"); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.Resources |
||||
|
.Get<ElsaNextResource>() |
||||
|
.AddVirtualJson("/Localization/Resources/AgentsBlazor"); |
||||
|
}); |
||||
|
|
||||
|
var elsaNextStudioBlazoeOptions = context.Services.ExecutePreConfiguredActions<AbpElsaNextStudioBlazorOptions>(); |
||||
|
|
||||
|
context.Services.AddAgentsModule(elsaNextStudioBlazoeOptions.BackendApiConfig); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,29 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk.Razor"> |
||||
|
|
||||
|
<Import Project="..\..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net10.0</TargetFramework> |
||||
|
<AssemblyName>LINGYUN.Abp.ElsaNext.Agents.Blazor</AssemblyName> |
||||
|
<PackageId>LINGYUN.Abp.ElsaNext.Agents.Blazor</PackageId> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<Content Remove="Localization\Resources\AgentsBlazor\*.json" /> |
||||
|
<EmbeddedResource Include="Localization\Resources\AgentsBlazor\*.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Elsa.Studio.Agents" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.ElsaNext.Agents\LINGYUN.Abp.ElsaNext.Agents.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.ElsaNext.Studio.Blazor\LINGYUN.Abp.ElsaNext.Studio.Blazor.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,7 @@ |
|||||
|
{ |
||||
|
"culture": "en", |
||||
|
"texts": { |
||||
|
"Menu:Agents": "Agents", |
||||
|
"Agents": "Agents" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
{ |
||||
|
"culture": "zh-Hans", |
||||
|
"texts": { |
||||
|
"Menu:Agents": "代理", |
||||
|
"Agents": "代理" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
using LINGYUN.Abp.ElsaNext.Localization; |
||||
|
using LINGYUN.Abp.ElsaNext.Studio.Blazor.Navigation; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.UI.Navigation; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.Agents.Blazor.Navigation; |
||||
|
|
||||
|
public class ElsaStudioAgentsMenuContributor : IMenuContributor |
||||
|
{ |
||||
|
public Task ConfigureMenuAsync(MenuConfigurationContext context) |
||||
|
{ |
||||
|
if (context.Menu.Name == StandardMenus.Main) |
||||
|
{ |
||||
|
var menu = context.Menu; |
||||
|
var l = context.GetLocalizer<ElsaNextResource>(); |
||||
|
|
||||
|
var group = menu.GetMenuItemOrNull(ElsaStudioMenus.GroupName); |
||||
|
group?.AddItem(new ApplicationMenuItem( |
||||
|
ElsaStudioMenus.GroupName + ".Agents", |
||||
|
l["Menu:Agents"], |
||||
|
url: "/ai/agents", |
||||
|
icon: "fa fa-bots", |
||||
|
order: 5)); |
||||
|
} |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,5 @@ |
|||||
|
@using Microsoft.AspNetCore.Authorization |
||||
|
@using Microsoft.AspNetCore.Components.Web |
||||
|
@using Elsa.Studio.Components |
||||
|
@using Elsa.Studio.Localization |
||||
|
@using MudBlazor |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,27 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net10.0</TargetFramework> |
||||
|
<AssemblyName>LINGYUN.Abp.ElsaNext.Agents</AssemblyName> |
||||
|
<PackageId>LINGYUN.Abp.ElsaNext.Agents</PackageId> |
||||
|
<ImplicitUsings>enable</ImplicitUsings> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Elsa.Agents" /> |
||||
|
<PackageReference Include="Elsa.Agents.Api" /> |
||||
|
<PackageReference Include="Elsa.Agents.OpenAI" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.ElsaNext\LINGYUN.Abp.ElsaNext.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,22 @@ |
|||||
|
using Elsa.Agents; |
||||
|
using Elsa.Extensions; |
||||
|
using Elsa.Features.Services; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.Agents; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpElsaNextModule))] |
||||
|
public class AbpElsaNextAgentsModule : AbpModule |
||||
|
{ |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
PreConfigure<IModule>(elsa => |
||||
|
{ |
||||
|
elsa.UseAgents(); |
||||
|
elsa.UseAgentActivities(); |
||||
|
|
||||
|
elsa.UseAgentsApi(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,24 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net10.0</TargetFramework> |
||||
|
<AssemblyName>LINGYUN.Abp.ElsaNext.Alterations</AssemblyName> |
||||
|
<PackageId>LINGYUN.Abp.ElsaNext.Alterations</PackageId> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Elsa.Alterations" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.ElsaNext\LINGYUN.Abp.ElsaNext.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,28 @@ |
|||||
|
using Elsa.Alterations.Extensions; |
||||
|
using Elsa.Features.Contracts; |
||||
|
using Elsa.Features.Services; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.Alterations; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpElsaNextModule))] |
||||
|
public class AbpElsaNextAlterationsModule : AbpModule |
||||
|
{ |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
PreConfigure<IModule>(elsa => |
||||
|
{ |
||||
|
elsa.UseAlterations(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public override void OnApplicationInitialization(ApplicationInitializationContext context) |
||||
|
{ |
||||
|
// TODO: 强制启用Studio中的Alterations?
|
||||
|
var registry = context.ServiceProvider.GetRequiredService<IInstalledFeatureRegistry>(); |
||||
|
registry.Add(new("Alterations.ShellFeatures.Alterations", "Elsa", "Alterations.ShellFeatures.Alterations")); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,29 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net10.0</TargetFramework> |
||||
|
<AssemblyName>LINGYUN.Abp.ElsaNext.BlobStoring</AssemblyName> |
||||
|
<PackageId>LINGYUN.Abp.ElsaNext.BlobStoring</PackageId> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<Content Remove="LINGYUN\Abp\ElsaNext\BlobStoring\Localization\**\*.json" /> |
||||
|
<EmbeddedResource Include="LINGYUN\Abp\ElsaNext\BlobStoring\Localization\**\*.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.BlobStoring" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.ElsaNext\LINGYUN.Abp.ElsaNext.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,38 @@ |
|||||
|
using Elsa.Features.Services; |
||||
|
using LINGYUN.Abp.ElsaNext.BlobStoring.Extensions; |
||||
|
using LINGYUN.Abp.ElsaNext.Localization; |
||||
|
using Volo.Abp.BlobStoring; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.VirtualFileSystem; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.BlobStoring; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpBlobStoringModule), |
||||
|
typeof(AbpElsaNextModule))] |
||||
|
public class AbpElsaNextBlobStoringModule : AbpModule |
||||
|
{ |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
PreConfigure<IModule>(elsa => |
||||
|
{ |
||||
|
elsa.UseAbpBlobStoring(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
Configure<AbpVirtualFileSystemOptions>(options => |
||||
|
{ |
||||
|
options.FileSets.AddEmbedded<AbpElsaNextBlobStoringModule>(); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.Resources |
||||
|
.Get<ElsaNextResource>() |
||||
|
.AddVirtualJson("/LINGYUN/Abp/ElsaNext/BlobStoring/Localization/Resources"); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
using Elsa.Workflows; |
||||
|
using Elsa.Workflows.Attributes; |
||||
|
using Elsa.Workflows.Models; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.BlobStoring; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.BlobStoring.Activities; |
||||
|
|
||||
|
public abstract class BlobActivity : CodeActivity |
||||
|
{ |
||||
|
[Input(Description = "Path of the blob.")] |
||||
|
public Input<string> Path { get; set; } = default!; |
||||
|
|
||||
|
protected virtual IBlobContainer<ElsaBlobContainer> GetBlobContainer(ActivityExecutionContext context) |
||||
|
{ |
||||
|
return context.GetRequiredService<IBlobContainer<ElsaBlobContainer>>(); |
||||
|
} |
||||
|
|
||||
|
protected async virtual ValueTask OnErrorCompletedAsync(ActivityCompletedContext context) |
||||
|
{ |
||||
|
await context.TargetContext.CompleteActivityAsync(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public abstract class BlobActivity<TResult> : CodeActivity<TResult> |
||||
|
{ |
||||
|
[Input(Description = "Path of the blob.")] |
||||
|
public Input<string> Path { get; set; } = default!; |
||||
|
|
||||
|
protected virtual IBlobContainer<ElsaBlobContainer> GetBlobContainer(ActivityExecutionContext context) |
||||
|
{ |
||||
|
return context.GetRequiredService<IBlobContainer<ElsaBlobContainer>>(); |
||||
|
} |
||||
|
|
||||
|
protected async virtual ValueTask OnErrorCompletedAsync(ActivityCompletedContext context) |
||||
|
{ |
||||
|
await context.TargetContext.CompleteActivityAsync(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
using Elsa.Extensions; |
||||
|
using Elsa.Workflows; |
||||
|
using Elsa.Workflows.Attributes; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.BlobStoring.Activities; |
||||
|
|
||||
|
[Activity("Elsa", "BlobStoring", "Delete a blob.", Kind = ActivityKind.Task)] |
||||
|
public class DeleteBlob : BlobActivity<bool> |
||||
|
{ |
||||
|
protected async override ValueTask ExecuteAsync(ActivityExecutionContext context) |
||||
|
{ |
||||
|
var cancellationToken = context.CancellationToken; |
||||
|
var path = Path.Get(context); |
||||
|
var blobContainer = GetBlobContainer(context); |
||||
|
var result = await blobContainer.DeleteAsync(path, cancellationToken); |
||||
|
|
||||
|
Result.Set(context, result); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
using Elsa.Extensions; |
||||
|
using Elsa.Workflows; |
||||
|
using Elsa.Workflows.Attributes; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.BlobStoring.Activities; |
||||
|
|
||||
|
[Activity("Elsa", "BlobStoring", "Open a blob.", Kind = ActivityKind.Task)] |
||||
|
public class ReadBlob : BlobActivity<Stream> |
||||
|
{ |
||||
|
[Port] public IActivity? Error { get; set; } |
||||
|
|
||||
|
protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) |
||||
|
{ |
||||
|
var cancellationToken = context.CancellationToken; |
||||
|
var path = Path.Get(context); |
||||
|
var blobContainer = GetBlobContainer(context); |
||||
|
|
||||
|
var logger = context.GetRequiredService<ILogger<ReadBlob>>(); |
||||
|
try |
||||
|
{ |
||||
|
var data = await blobContainer.GetAsync(path, cancellationToken); |
||||
|
|
||||
|
Result.Set(context, data); |
||||
|
await context.CompleteActivityAsync(); |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
logger.LogWarning(e, "Error while open a blob."); |
||||
|
context.AddExecutionLogEntry("Error", e.Message, payload: new |
||||
|
{ |
||||
|
e.StackTrace |
||||
|
}); |
||||
|
await context.ScheduleActivityAsync(Error, OnErrorCompletedAsync); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,99 @@ |
|||||
|
using Elsa.Extensions; |
||||
|
using Elsa.Workflows; |
||||
|
using Elsa.Workflows.Attributes; |
||||
|
using Elsa.Workflows.Models; |
||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using System; |
||||
|
using System.Collections; |
||||
|
using System.IO; |
||||
|
using System.IO.Compression; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.BlobStoring; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.BlobStoring.Activities; |
||||
|
|
||||
|
[Activity("Elsa", "BlobStoring", "Save a blob.", Kind = ActivityKind.Task)] |
||||
|
public class SaveBlob : BlobActivity |
||||
|
{ |
||||
|
[Input(Description = "Blob exists whether overwrite.")] |
||||
|
public Input<bool> Overwrite { get; set; } = null!; |
||||
|
|
||||
|
[Input(Description = "The file data to save. This can be a stream, binary data, a string, a form file or a collection of files.")] |
||||
|
public Input<object> Data { get; set; } = null!; |
||||
|
|
||||
|
[Port] public IActivity? Error { get; set; } |
||||
|
|
||||
|
protected async override ValueTask ExecuteAsync(ActivityExecutionContext context) |
||||
|
{ |
||||
|
var cancellationToken = context.CancellationToken; |
||||
|
var data = await ResolveAsStreamAsync(Data.Get(context), cancellationToken); |
||||
|
var overwrite = Overwrite.GetOrDefault(context); |
||||
|
var path = Path.Get(context); |
||||
|
var blobContainer = GetBlobContainer(context); |
||||
|
|
||||
|
var logger = context.GetRequiredService<ILogger<SaveBlob>>(); |
||||
|
try |
||||
|
{ |
||||
|
await blobContainer.SaveAsync(path, data, overwrite, cancellationToken); |
||||
|
await context.CompleteActivityAsync(); |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
logger.LogWarning(e, "Error while save a blob."); |
||||
|
context.AddExecutionLogEntry("Error", e.Message, payload: new |
||||
|
{ |
||||
|
e.StackTrace |
||||
|
}); |
||||
|
await context.ScheduleActivityAsync(Error, OnErrorCompletedAsync); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task<Stream> ResolveAsStreamAsync(object data, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
if (data is Stream stream) |
||||
|
return stream; |
||||
|
|
||||
|
if (data is byte[] bytes) |
||||
|
return new MemoryStream(bytes); |
||||
|
|
||||
|
if (data is IFormFile formFile) |
||||
|
return formFile.OpenReadStream(); |
||||
|
|
||||
|
if (data is string stringData) |
||||
|
return new MemoryStream(Encoding.UTF8.GetBytes(stringData)); |
||||
|
|
||||
|
if (data is IEnumerable enumerable) |
||||
|
{ |
||||
|
var files = enumerable.Cast<object>().ToList(); |
||||
|
return files.Count == 1 ? await ResolveAsStreamAsync(files[0], cancellationToken) : await CreateZipArchiveAsync(files, cancellationToken); |
||||
|
} |
||||
|
|
||||
|
throw new NotSupportedException($"The provided data type is not supported: {data.GetType().Name}"); |
||||
|
} |
||||
|
|
||||
|
private async Task<Stream> CreateZipArchiveAsync(IEnumerable files, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var currentFileIndex = 0; |
||||
|
var zipStream = new MemoryStream(); |
||||
|
var zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Create, true); |
||||
|
|
||||
|
foreach (var file in files) |
||||
|
{ |
||||
|
var entryName = $"file-{currentFileIndex}.bin"; |
||||
|
var entry = zipArchive.CreateEntry(entryName); |
||||
|
var fileStream = await ResolveAsStreamAsync(file, cancellationToken); |
||||
|
await using var entryStream = entry.Open(); |
||||
|
await fileStream.CopyToAsync(entryStream, cancellationToken); |
||||
|
await entryStream.FlushAsync(cancellationToken); |
||||
|
entryStream.Close(); |
||||
|
currentFileIndex++; |
||||
|
} |
||||
|
|
||||
|
zipStream.Seek(0, SeekOrigin.Begin); |
||||
|
return zipStream; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
using Volo.Abp.BlobStoring; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.BlobStoring; |
||||
|
|
||||
|
[BlobContainerName("workflow")] |
||||
|
public class ElsaBlobContainer |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using Elsa.Features.Services; |
||||
|
using LINGYUN.Abp.ElsaNext.BlobStoring.Features; |
||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.BlobStoring.Extensions; |
||||
|
|
||||
|
public static class ModuleExtensions |
||||
|
{ |
||||
|
public static IModule UseAbpBlobStoring(this IModule configuration, Action<BlobStoringFeature>? configure = null) |
||||
|
{ |
||||
|
configuration.Configure(configure); |
||||
|
return configuration; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using Elsa.Extensions; |
||||
|
using Elsa.Features.Abstractions; |
||||
|
using Elsa.Features.Services; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.BlobStoring.Features; |
||||
|
|
||||
|
public class BlobStoringFeature : FeatureBase |
||||
|
{ |
||||
|
public BlobStoringFeature(IModule module) : base(module) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public override void Configure() |
||||
|
{ |
||||
|
Module.AddActivitiesFrom<BlobStoringFeature>(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
{ |
||||
|
"culture": "en", |
||||
|
"texts": { |
||||
|
"Path of the blob.": "Path of the blob.", |
||||
|
"Blob exists whether overwrite.": "Blob exists whether overwrite.", |
||||
|
"The file data to save. This can be a stream, binary data, a string, a form file or a collection of files.": "The file data to save. This can be a stream, binary data, a string, a form file or a collection of files.", |
||||
|
"BlobStoring": "Blob Storing", |
||||
|
"Delete Blob": "Delete a blob.", |
||||
|
"Read Blob.": "Read a blob.", |
||||
|
"Save Blob.": "Save a blob." |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
{ |
||||
|
"culture": "zh-Hans", |
||||
|
"texts": { |
||||
|
"Path of the blob.": "文件存储路径。", |
||||
|
"Blob exists whether overwrite.": "覆盖已有文件。", |
||||
|
"The file data to save. This can be a stream, binary data, a string, a form file or a collection of files.": "要保存的文件数据。可以是流、二进制数据、字符串、表单文件或一组文件。", |
||||
|
"BlobStoring": "对象存储", |
||||
|
"Delete Blob": "删除文件", |
||||
|
"Read Blob": "打开文件", |
||||
|
"Save Blob": "保存文件" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,29 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net10.0</TargetFramework> |
||||
|
<AssemblyName>LINGYUN.Abp.ElsaNext.Email</AssemblyName> |
||||
|
<PackageId>LINGYUN.Abp.ElsaNext.Email</PackageId> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<Content Remove="LINGYUN\Abp\ElsaNext\Email\Localization\Resources\**\*.json" /> |
||||
|
<EmbeddedResource Include="LINGYUN\Abp\ElsaNext\Email\Localization\Resources\**\*.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Emailing" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.ElsaNext\LINGYUN.Abp.ElsaNext.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,38 @@ |
|||||
|
using Elsa.Features.Services; |
||||
|
using LINGYUN.Abp.ElsaNext.Email.Extensions; |
||||
|
using LINGYUN.Abp.ElsaNext.Localization; |
||||
|
using Volo.Abp.Emailing; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.VirtualFileSystem; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.Email; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpEmailingModule), |
||||
|
typeof(AbpElsaNextModule))] |
||||
|
public class AbpElsaNextEmailModule : AbpModule |
||||
|
{ |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
PreConfigure<IModule>(elsa => |
||||
|
{ |
||||
|
elsa.UseAbpEmail(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
Configure<AbpVirtualFileSystemOptions>(options => |
||||
|
{ |
||||
|
options.FileSets.AddEmbedded<AbpElsaNextEmailModule>(); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.Resources |
||||
|
.Get<ElsaNextResource>() |
||||
|
.AddVirtualJson("/LINGYUN/Abp/ElsaNext/Email/Localization/Resources"); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,274 @@ |
|||||
|
using Elsa.Extensions; |
||||
|
using Elsa.Workflows; |
||||
|
using Elsa.Workflows.Attributes; |
||||
|
using Elsa.Workflows.Models; |
||||
|
using Elsa.Workflows.UIHints; |
||||
|
using LINGYUN.Abp.ElsaNext.Email.Contracts; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using System; |
||||
|
using System.Collections; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Dynamic; |
||||
|
using System.IO; |
||||
|
using System.Net.Mail; |
||||
|
using System.Net.Mime; |
||||
|
using System.Text; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Emailing; |
||||
|
using Volo.Abp.Json; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.Email.Activities; |
||||
|
|
||||
|
[Activity("Elsa", "Email", "Send an email message.", Kind = ActivityKind.Task)] |
||||
|
public class SendEmail : Activity |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The sender's email address.
|
||||
|
/// </summary>
|
||||
|
[Input(Description = "The sender's email address.")] |
||||
|
public Input<string?> From { get; set; } = null!; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The recipients email addresses.
|
||||
|
/// </summary>
|
||||
|
[Input(Description = "The recipients email addresses.", UIHint = InputUIHints.MultiText)] |
||||
|
public Input<ICollection<string>> To { get; set; } = null!; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The CC recipient email addresses.
|
||||
|
/// </summary>
|
||||
|
[Input( |
||||
|
Description = "The CC recipient email addresses.", |
||||
|
UIHint = InputUIHints.MultiText, |
||||
|
Category = "More")] |
||||
|
public Input<ICollection<string>> Cc { get; set; } = null!; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The BCC recipients email addresses.
|
||||
|
/// </summary>
|
||||
|
[Input( |
||||
|
Description = "The BCC recipients email addresses.", |
||||
|
UIHint = InputUIHints.MultiText, |
||||
|
Category = "More")] |
||||
|
public Input<ICollection<string>> Bcc { get; set; } = null!; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The subject of the email message.
|
||||
|
/// </summary>
|
||||
|
[Input(Description = "The subject of the email message.")] |
||||
|
public Input<string?> Subject { get; set; } = null!; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The attachments to send with the email message.
|
||||
|
/// </summary>
|
||||
|
[Input( |
||||
|
Description = "The attachments to send with the email message. Can be (an array of) a fully-qualified file path, URL, stream, byte array or instances of EmailAttachment.", |
||||
|
UIHint = InputUIHints.MultiLine |
||||
|
)] |
||||
|
public Input<object?> Attachments { get; set; } = null!; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The body of the email message.
|
||||
|
/// </summary>
|
||||
|
[Input( |
||||
|
Description = "The body of the email message.", |
||||
|
UIHint = InputUIHints.MultiLine |
||||
|
)] |
||||
|
public Input<string> Body { get; set; } = null!; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The activity to execute when an error occurs while trying to send the email.
|
||||
|
/// </summary>
|
||||
|
[Port] public IActivity? Error { get; set; } |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) |
||||
|
{ |
||||
|
var cancellationToken = context.CancellationToken; |
||||
|
var message = new MailMessage(); |
||||
|
var from = From.GetOrDefault(context); |
||||
|
|
||||
|
if (MailAddress.TryCreate(from, out var fromAddress)) |
||||
|
{ |
||||
|
message.Sender = fromAddress; |
||||
|
message.From = fromAddress; |
||||
|
} |
||||
|
message.Subject = Subject.GetOrDefault(context) ?? ""; |
||||
|
|
||||
|
await AddAttachmentsAsync(context, message, cancellationToken); |
||||
|
|
||||
|
message.Body = Body.GetOrDefault(context); |
||||
|
|
||||
|
SetRecipientsEmailAddresses(message.To, GetAddresses(context, To)); |
||||
|
SetRecipientsEmailAddresses(message.CC, GetAddresses(context, Cc)); |
||||
|
SetRecipientsEmailAddresses(message.Bcc, GetAddresses(context, Bcc)); |
||||
|
|
||||
|
var emailSender = context.GetRequiredService<IEmailSender>(); |
||||
|
var logger = context.GetRequiredService<ILogger<SendEmail>>(); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
await emailSender.SendAsync(message); |
||||
|
await context.CompleteActivityAsync(); |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
logger.LogWarning(e, "Error while sending email message"); |
||||
|
context.AddExecutionLogEntry("Error", e.Message, payload: new |
||||
|
{ |
||||
|
e.StackTrace |
||||
|
}); |
||||
|
await context.ScheduleActivityAsync(Error, OnErrorCompletedAsync); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async ValueTask OnErrorCompletedAsync(ActivityCompletedContext context) => await context.TargetContext.CompleteActivityAsync(); |
||||
|
|
||||
|
private static ICollection<string> GetAddresses(ActivityExecutionContext context, Input<ICollection<string>> input) |
||||
|
{ |
||||
|
return input.GetOrDefault(context) ?? new List<string>(0); |
||||
|
} |
||||
|
|
||||
|
private async Task AddAttachmentsAsync(ActivityExecutionContext context, MailMessage mailMessage, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
var attachments = Attachments.GetOrDefault(context); |
||||
|
|
||||
|
if (attachments == null || attachments is string s && string.IsNullOrWhiteSpace(s)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var index = 0; |
||||
|
var attachmentObjects = InterpretAttachmentsModel(attachments); |
||||
|
|
||||
|
foreach (var attachmentObject in attachmentObjects) |
||||
|
{ |
||||
|
switch (attachmentObject) |
||||
|
{ |
||||
|
case Uri url: |
||||
|
await AttachOnlineFileAsync(context, mailMessage, url, cancellationToken); |
||||
|
break; |
||||
|
case string path when path?.Contains("://") == true: |
||||
|
await AttachOnlineFileAsync(context, mailMessage, new Uri(path), cancellationToken); |
||||
|
break; |
||||
|
case string path when !string.IsNullOrWhiteSpace(path): |
||||
|
AttachLocalFile(mailMessage, path); |
||||
|
break; |
||||
|
case byte[] bytes: |
||||
|
{ |
||||
|
var fileName = $"Attachment-{++index}"; |
||||
|
var byteStream = new MemoryStream(bytes.Length); |
||||
|
await byteStream.WriteAsync(bytes, cancellationToken); |
||||
|
mailMessage.Attachments.Add(new Attachment(byteStream, new ContentType { Name = fileName })); |
||||
|
break; |
||||
|
} |
||||
|
case Stream stream: |
||||
|
{ |
||||
|
var fileName = $"Attachment-{++index}"; |
||||
|
mailMessage.Attachments.Add(new Attachment(stream, new ContentType { Name = fileName })); |
||||
|
break; |
||||
|
} |
||||
|
case Attachment attachment: |
||||
|
{ |
||||
|
attachment.Name ??= $"Attachment-{++index}"; |
||||
|
mailMessage.Attachments.Add(attachment); |
||||
|
break; |
||||
|
} |
||||
|
case EmailAttachment emailAttachment when emailAttachment.File != null: |
||||
|
{ |
||||
|
var fileName = emailAttachment.Name ?? $"Attachment-{++index}"; |
||||
|
var emailAttachmentStream = new MemoryStream(emailAttachment.File.Length); |
||||
|
await emailAttachmentStream.WriteAsync(emailAttachment.File, cancellationToken); |
||||
|
mailMessage.Attachments.Add(new Attachment(emailAttachmentStream, new ContentType { Name = fileName })); |
||||
|
break; |
||||
|
} |
||||
|
case ExpandoObject expandoObject: |
||||
|
{ |
||||
|
var dictionary = new Dictionary<string, object>(expandoObject!, StringComparer.OrdinalIgnoreCase); |
||||
|
var fileName = dictionary.GetValue<string>("FileName") ?? $"Attachment-{++index}"; |
||||
|
var contentType = dictionary.GetValue<string>("ContentType") ?? "application/octet-stream"; |
||||
|
var parsedContentType = new ContentType(contentType); |
||||
|
var content = dictionary.GetValue<object>("Content"); |
||||
|
|
||||
|
if (content is byte[] bytes) |
||||
|
{ |
||||
|
var byteStream = new MemoryStream(bytes.Length); |
||||
|
await byteStream.WriteAsync(bytes, cancellationToken); |
||||
|
mailMessage.Attachments.Add(new Attachment(byteStream, new ContentType { Name = fileName })); |
||||
|
} |
||||
|
else if (content is Stream stream) |
||||
|
{ |
||||
|
mailMessage.Attachments.Add(new Attachment(stream, new ContentType { Name = fileName })); |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
default: |
||||
|
{ |
||||
|
var jsonSerializer = context.GetRequiredService<IJsonSerializer>(); |
||||
|
var json = jsonSerializer.Serialize(attachmentObject); |
||||
|
var fileName = $"Attachment-{++index}"; |
||||
|
|
||||
|
var jsonBytes = Encoding.UTF8.GetBytes(json); |
||||
|
var byteStream = new MemoryStream(jsonBytes.Length); |
||||
|
await byteStream.WriteAsync(jsonBytes, cancellationToken); |
||||
|
mailMessage.Attachments.Add(new Attachment(byteStream, new ContentType { Name = fileName })); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void AttachLocalFile(MailMessage mailMessage, string path) |
||||
|
{ |
||||
|
var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read); |
||||
|
mailMessage.Attachments.Add(new Attachment(stream, new ContentType{ Name = Path.GetFileName(path) })); |
||||
|
} |
||||
|
|
||||
|
private async static Task AttachOnlineFileAsync(ActivityExecutionContext context, MailMessage mailMessage, Uri url, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
var fileName = Path.GetFileName(url.LocalPath); |
||||
|
var downloader = context.GetRequiredService<IDownloader>(); |
||||
|
var response = await downloader.DownloadAsync(url, cancellationToken); |
||||
|
var contentStream = await response.Content.ReadAsStreamAsync(cancellationToken); |
||||
|
var contentType = response.Content.Headers.ContentType?.MediaType ?? "application/binary"; |
||||
|
mailMessage.Attachments.Add(new Attachment(contentStream, new ContentType(contentType) { Name = fileName })); |
||||
|
} |
||||
|
|
||||
|
private static IEnumerable InterpretAttachmentsModel(object attachments) |
||||
|
{ |
||||
|
if (attachments is byte[] bytes) |
||||
|
{ |
||||
|
return new[] |
||||
|
{ |
||||
|
bytes |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
return attachments is string text |
||||
|
? new[] |
||||
|
{ |
||||
|
text |
||||
|
} |
||||
|
: attachments as IEnumerable ?? new[] |
||||
|
{ |
||||
|
attachments |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
private static void SetRecipientsEmailAddresses(MailAddressCollection list, IEnumerable<string>? addresses) |
||||
|
{ |
||||
|
if (addresses == null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
foreach (var addresse in addresses) |
||||
|
{ |
||||
|
if (MailAddress.TryCreate(addresse, out var mailAddress)) |
||||
|
{ |
||||
|
list.Add(mailAddress); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
using System; |
||||
|
using System.Net.Http; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.Email.Contracts; |
||||
|
/// <summary>
|
||||
|
/// Download files from the Internet.
|
||||
|
/// </summary>
|
||||
|
public interface IDownloader |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Download whatever is returned at the specified URL.
|
||||
|
/// </summary>
|
||||
|
Task<HttpResponseMessage> DownloadAsync(Uri url, CancellationToken cancellationToken = default); |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using Elsa.Features.Services; |
||||
|
using LINGYUN.Abp.ElsaNext.Email.Features; |
||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.Email.Extensions; |
||||
|
|
||||
|
public static class ModuleExtensions |
||||
|
{ |
||||
|
public static IModule UseAbpEmail(this IModule configuration, Action<EmailFeature>? configure = null) |
||||
|
{ |
||||
|
configuration.Configure(configure); |
||||
|
return configuration; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
using Elsa.Extensions; |
||||
|
using Elsa.Features.Abstractions; |
||||
|
using Elsa.Features.Services; |
||||
|
using LINGYUN.Abp.ElsaNext.Email.Contracts; |
||||
|
using LINGYUN.Abp.ElsaNext.Email.Services; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using System; |
||||
|
using System.Net.Http; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.Email.Features; |
||||
|
|
||||
|
public class EmailFeature : FeatureBase |
||||
|
{ |
||||
|
public Action<IServiceProvider, HttpClient> ConfigureDownloaderHttpClient { get; set; } = (_, _) => { }; |
||||
|
|
||||
|
public EmailFeature(IModule module) : base(module) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public override void Configure() |
||||
|
{ |
||||
|
Module.AddActivitiesFrom<EmailFeature>(); |
||||
|
} |
||||
|
|
||||
|
public override void Apply() |
||||
|
{ |
||||
|
Services |
||||
|
.AddHttpClient<IDownloader, DefaultDownloader>(ConfigureDownloaderHttpClient); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
{ |
||||
|
"culture": "en", |
||||
|
"texts": { |
||||
|
"The sender's email address.": "The sender's email address.", |
||||
|
"The recipients email addresses.": "The recipients email addresses.", |
||||
|
"The CC recipient email addresses.": "The CC recipient email addresses.", |
||||
|
"The BCC recipients email addresses.": "The BCC recipients email addresses.", |
||||
|
"The subject of the email message.": "The subject of the email message.", |
||||
|
"The attachments to send with the email message. Can be (an array of) a fully-qualified file path, URL, stream, byte array or instances of EmailAttachment.": "The attachments to send with the email message. Can be (an array of) a fully-qualified file path, URL, stream, byte array or instances of EmailAttachment.", |
||||
|
"The body of the email message.": "The body of the email message." |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
{ |
||||
|
"culture": "zh-Hans", |
||||
|
"texts": { |
||||
|
"The sender's email address.": "发件人的电子邮件地址。", |
||||
|
"The recipients email addresses.": "收件人的电子邮件地址。", |
||||
|
"The CC recipient email addresses.": "CC收件人电子邮件地址。", |
||||
|
"The BCC recipients email addresses.": "BCC收件人邮箱地址。", |
||||
|
"The subject of the email message.": "邮件主题。", |
||||
|
"The attachments to send with the email message. Can be (an array of) a fully-qualified file path, URL, stream, byte array or instances of EmailAttachment.": "随邮件发送的附件。可以是(一个数组)完全限定的文件路径、URL、流、字节数组,或 EmailAttachment 类的实例。", |
||||
|
"The body of the email message.": "邮件正文。" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
using LINGYUN.Abp.ElsaNext.Email.Contracts; |
||||
|
using System; |
||||
|
using System.Net.Http; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.Email.Services; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Download files from the Internet.
|
||||
|
/// </summary>
|
||||
|
public class DefaultDownloader : IDownloader |
||||
|
{ |
||||
|
private readonly HttpClient _httpClient; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Constructor.
|
||||
|
/// </summary>
|
||||
|
public DefaultDownloader(HttpClient httpClient) |
||||
|
{ |
||||
|
_httpClient = httpClient; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Download whatever is returned at the specified URL.
|
||||
|
/// </summary>
|
||||
|
public async Task<HttpResponseMessage> DownloadAsync(Uri url, CancellationToken cancellationToken = default) => await _httpClient.GetAsync(url, cancellationToken); |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,25 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net10.0</TargetFramework> |
||||
|
<AssemblyName>LINGYUN.Abp.ElsaNext.EntityFrameworkCore</AssemblyName> |
||||
|
<PackageId>LINGYUN.Abp.ElsaNext.EntityFrameworkCore</PackageId> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Elsa.Persistence.EFCore" VersionOverride="$(ElsaNextPackageVersion)" /> |
||||
|
<PackageReference Include="Volo.Abp.EntityFrameworkCore" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.ElsaNext\LINGYUN.Abp.ElsaNext.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,57 @@ |
|||||
|
using Elsa.Alterations.Extensions; |
||||
|
using Elsa.Extensions; |
||||
|
using Elsa.Features.Services; |
||||
|
using Elsa.Persistence.EFCore.Modules.Alterations; |
||||
|
using Elsa.Persistence.EFCore.Modules.Identity; |
||||
|
using Elsa.Persistence.EFCore.Modules.Labels; |
||||
|
using Elsa.Persistence.EFCore.Modules.Management; |
||||
|
using Elsa.Persistence.EFCore.Modules.Runtime; |
||||
|
using Elsa.Persistence.EFCore.Modules.Tenants; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.EntityFrameworkCore; |
||||
|
|
||||
|
[DependsOn(typeof(AbpElsaNextModule))] |
||||
|
public class AbpElsaNextEntityFrameworkCoreModule : AbpModule |
||||
|
{ |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
var labelFeature = context.Services.GetPreConfigureActions<EFCoreLabelPersistenceFeature>(); |
||||
|
var alterationsFeature = context.Services.GetPreConfigureActions<EFCoreAlterationsPersistenceFeature>(); |
||||
|
var workflowManagementFeature = context.Services.GetPreConfigureActions<WorkflowManagementPersistenceFeature>(); |
||||
|
var workflowRuntimeFeature = context.Services.GetPreConfigureActions<EFCoreWorkflowRuntimePersistenceFeature>(); |
||||
|
|
||||
|
PreConfigure<IModule>(elsa => |
||||
|
{ |
||||
|
elsa.UseLabels(label => |
||||
|
{ |
||||
|
label.UseEntityFrameworkCore(efCore => |
||||
|
{ |
||||
|
labelFeature.Configure(efCore); |
||||
|
}); |
||||
|
}); |
||||
|
elsa.UseAlterations(alterations => |
||||
|
{ |
||||
|
alterations.UseEntityFrameworkCore(efCore => |
||||
|
{ |
||||
|
alterationsFeature.Configure(efCore); |
||||
|
}); |
||||
|
}); |
||||
|
elsa.UseWorkflowManagement(management => |
||||
|
{ |
||||
|
management.UseEntityFrameworkCore(efCore => |
||||
|
{ |
||||
|
workflowManagementFeature.Configure(efCore); |
||||
|
}); |
||||
|
}); |
||||
|
elsa.UseWorkflowRuntime(runtime => |
||||
|
{ |
||||
|
runtime.UseEntityFrameworkCore(efCore => |
||||
|
{ |
||||
|
workflowRuntimeFeature.Configure(efCore); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,24 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net10.0</TargetFramework> |
||||
|
<AssemblyName>LINGYUN.Abp.ElsaNext.Labels</AssemblyName> |
||||
|
<PackageId>LINGYUN.Abp.ElsaNext.Labels</PackageId> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Elsa.Labels" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.ElsaNext\LINGYUN.Abp.ElsaNext.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,17 @@ |
|||||
|
using Elsa.Extensions; |
||||
|
using Elsa.Features.Services; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.Labels; |
||||
|
|
||||
|
[DependsOn(typeof(AbpElsaNextModule))] |
||||
|
public class AbpElsaNextLabelsModule : AbpModule |
||||
|
{ |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
PreConfigure<IModule>(elsa => |
||||
|
{ |
||||
|
elsa.UseLabels(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,26 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net10.0</TargetFramework> |
||||
|
<AssemblyName>LINGYUN.Abp.ElsaNext.Notifications</AssemblyName> |
||||
|
<PackageId>LINGYUN.Abp.ElsaNext.Notifications</PackageId> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<Content Remove="LINGYUN\Abp\ElsaNext\Notifications\Localization\**\*.json" /> |
||||
|
<EmbeddedResource Include="LINGYUN\Abp\ElsaNext\Notifications\Localization\**\*.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\realtime-notifications\LINGYUN.Abp.Notifications\LINGYUN.Abp.Notifications.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.ElsaNext\LINGYUN.Abp.ElsaNext.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,41 @@ |
|||||
|
using Elsa.Features.Services; |
||||
|
using LINGYUN.Abp.ElsaNext.Localization; |
||||
|
using LINGYUN.Abp.ElsaNext.Notifications.Extensions; |
||||
|
using LINGYUN.Abp.Notifications; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.VirtualFileSystem; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.Notifications; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpNotificationsModule), |
||||
|
typeof(AbpElsaNextModule))] |
||||
|
public class AbpElsaNextNotificationsModule : AbpModule |
||||
|
{ |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
PreConfigure<IModule>(elsa => |
||||
|
{ |
||||
|
elsa.UseAbpNotifications(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
Configure<AbpVirtualFileSystemOptions>(options => |
||||
|
{ |
||||
|
options.FileSets.AddEmbedded<AbpElsaNextNotificationsModule>(); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.Resources |
||||
|
.Get<ElsaNextResource>() |
||||
|
.AddVirtualJson("/LINGYUN/Abp/ElsaNext/Notifications/Localization/Resources"); |
||||
|
}); |
||||
|
|
||||
|
context.Services.AddNotificationHandlersFrom<AbpElsaNextNotificationsModule>(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,136 @@ |
|||||
|
using Elsa.Extensions; |
||||
|
using Elsa.Workflows; |
||||
|
using Elsa.Workflows.Attributes; |
||||
|
using Elsa.Workflows.Models; |
||||
|
using Elsa.Workflows.UIHints; |
||||
|
using LINGYUN.Abp.ElsaNext.Notifications.UIHints; |
||||
|
using LINGYUN.Abp.ElsaNext.UIHints; |
||||
|
using LINGYUN.Abp.Notifications; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
using Volo.Abp.Timing; |
||||
|
using INotificationSender = LINGYUN.Abp.Notifications.INotificationSender; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.Notifications.Activities; |
||||
|
|
||||
|
[Activity("Elsa", "Notifications", "Send an notification.", Kind = ActivityKind.Task)] |
||||
|
public class SendNotification : CodeActivity |
||||
|
{ |
||||
|
[Input( |
||||
|
Description = "The name of the registered notification.", |
||||
|
UIHint = NotificationsUIHints.NotificationPicker)] |
||||
|
public Input<string> NotificationName { get; set; } = default!; |
||||
|
|
||||
|
[Input( |
||||
|
Description = "The notification title.", |
||||
|
UIHint = InputUIHints.MultiLine |
||||
|
)] |
||||
|
public Input<string?> Title { get; set; } = default!; |
||||
|
|
||||
|
[Input( |
||||
|
Description = "The notification content.", |
||||
|
UIHint = InputUIHints.MultiLine |
||||
|
)] |
||||
|
public Input<string?> Content { get; set; } = default!; |
||||
|
|
||||
|
[Input( |
||||
|
Description = "The notification description.", |
||||
|
UIHint = InputUIHints.MultiLine |
||||
|
)] |
||||
|
public Input<string?> Description { get; set; } = default!; |
||||
|
|
||||
|
[Input( |
||||
|
Description = "The notification template name.", |
||||
|
UIHint = InputUIHints.MultiLine |
||||
|
)] |
||||
|
public Input<string?> Template { get; set; } = default!; |
||||
|
|
||||
|
[Input( |
||||
|
Description = "The notification additional attributes.", |
||||
|
UIHint = InputUIHints.Dictionary |
||||
|
)] |
||||
|
public Input<Dictionary<string, object?>> Propertites { get; set; } = default!; |
||||
|
|
||||
|
[Input( |
||||
|
Description = "The tenant id for sending the notification.", |
||||
|
UIHint = AbpElsaUIHints.TenantPicker)] |
||||
|
public Input<Guid?> TenantId { get; set; } = null!; |
||||
|
|
||||
|
[Input( |
||||
|
Description = "The recipients email addresses.", |
||||
|
UIHint = AbpElsaUIHints.UserPickerMultiple)] |
||||
|
public Input<ICollection<Guid>> To { get; set; } = null!; |
||||
|
|
||||
|
[Input( |
||||
|
Description = "The notifications severity.", |
||||
|
Options = new[] { "Success", "Info", "Warn", "Error", "Fatal" }, |
||||
|
DefaultValue = "Info", |
||||
|
UIHint = InputUIHints.DropDown)] |
||||
|
public Input<string?> Severity { get; set; } = null!; |
||||
|
|
||||
|
[Output(Description = "The notification id.")] |
||||
|
public Output<string?> NotificationId { get; set; } = null!; |
||||
|
|
||||
|
protected async override ValueTask ExecuteAsync(ActivityExecutionContext context) |
||||
|
{ |
||||
|
var notificationSender = context.GetRequiredService<INotificationSender>(); |
||||
|
var currentTenant = context.GetRequiredService<ICurrentTenant>(); |
||||
|
var clock = context.GetRequiredService<IClock>(); |
||||
|
|
||||
|
var tenantId = TenantId.GetOrDefault(context, () => currentTenant.Id); |
||||
|
var userIdentifiers = GetUserIdentifiers(context); |
||||
|
var severity = GetNotificationSeverity(context); |
||||
|
var propertites = Propertites.GetOrDefault(context); |
||||
|
var notificationName = NotificationName.Get(context); |
||||
|
var template = Template.GetOrDefault(context); |
||||
|
if (!string.IsNullOrWhiteSpace(template)) |
||||
|
{ |
||||
|
var notificationTemplate = new NotificationTemplate(template, data: propertites); |
||||
|
var notificationTemplateId = await notificationSender.SendNofiterAsync( |
||||
|
notificationName, |
||||
|
notificationTemplate, |
||||
|
userIdentifiers, |
||||
|
tenantId, |
||||
|
severity); |
||||
|
NotificationId.Set(context, notificationTemplateId); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var title = Title.Get(context); |
||||
|
var message = Content.Get(context); |
||||
|
var description = Description.GetOrDefault(context) ?? ""; |
||||
|
|
||||
|
var notificationData = new NotificationData(); |
||||
|
notificationData.WriteStandardData(title!, message!, clock.Now, "elsa-workflow", description); |
||||
|
var notificationId = await notificationSender.SendNofiterAsync( |
||||
|
notificationName, |
||||
|
notificationData, |
||||
|
userIdentifiers, |
||||
|
tenantId, |
||||
|
severity); |
||||
|
NotificationId.Set(context, notificationId); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private NotificationSeverity GetNotificationSeverity(ActivityExecutionContext context) |
||||
|
{ |
||||
|
var severitySet = Severity.GetOrDefault(context); |
||||
|
if (!string.IsNullOrWhiteSpace(severitySet)) |
||||
|
{ |
||||
|
if (Enum.TryParse<NotificationSeverity>(severitySet, true, out var severity)) |
||||
|
{ |
||||
|
return severity; |
||||
|
} |
||||
|
} |
||||
|
return NotificationSeverity.Info; |
||||
|
} |
||||
|
|
||||
|
private IEnumerable<UserIdentifier>? GetUserIdentifiers(ActivityExecutionContext context) |
||||
|
{ |
||||
|
var userIdentifiers = To.GetOrDefault(context); |
||||
|
return userIdentifiers?.Select(to => new UserIdentifier(to, to.ToString())); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using Elsa.Features.Services; |
||||
|
using LINGYUN.Abp.ElsaNext.Notifications.Features; |
||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.Notifications.Extensions; |
||||
|
|
||||
|
public static class ModuleExtensions |
||||
|
{ |
||||
|
public static IModule UseAbpNotifications(this IModule configuration, Action<NotificationsFeature>? configure = null) |
||||
|
{ |
||||
|
configuration.Configure(configure); |
||||
|
return configuration; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using Elsa.Extensions; |
||||
|
using Elsa.Features.Abstractions; |
||||
|
using Elsa.Features.Services; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.Notifications.Features; |
||||
|
|
||||
|
public class NotificationsFeature : FeatureBase |
||||
|
{ |
||||
|
public NotificationsFeature(IModule module) : base(module) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public override void Configure() |
||||
|
{ |
||||
|
Module.AddActivitiesFrom<NotificationsFeature>(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
using Elsa.Mediator.Contracts; |
||||
|
using Elsa.Workflows.Notifications; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Logging.Abstractions; |
||||
|
using System; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.Notifications.Handlers; |
||||
|
|
||||
|
public class ActivityNotificationHandler : |
||||
|
INotificationHandler<ActivityExecuting>, |
||||
|
INotificationHandler<ActivityExecuted>, |
||||
|
INotificationHandler<ActivityCancelled>, |
||||
|
INotificationHandler<ActivityCompleted> |
||||
|
{ |
||||
|
public ILogger<ActivityNotificationHandler> Logger { protected get; set; } |
||||
|
public ActivityNotificationHandler() |
||||
|
{ |
||||
|
Logger = NullLogger<ActivityNotificationHandler>.Instance; |
||||
|
} |
||||
|
|
||||
|
public async virtual Task HandleAsync(ActivityExecuting notification, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
Logger.LogDebug("The activity notification: {notification} has been triggered.", notification.ToString()); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task HandleAsync(ActivityExecuted notification, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
Logger.LogDebug("The activity notification: {notification} has been triggered.", notification.ToString()); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task HandleAsync(ActivityCancelled notification, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
Logger.LogDebug("The activity notification: {notification} has been triggered.", notification.ToString()); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task HandleAsync(ActivityCompleted notification, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
Logger.LogDebug("The activity notification: {notification} has been triggered.", notification.ToString()); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,53 @@ |
|||||
|
using Elsa.Mediator.Contracts; |
||||
|
using Elsa.Workflows.Notifications; |
||||
|
using Elsa.Workflows.Runtime.Notifications; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Logging.Abstractions; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.Notifications.Handlers; |
||||
|
|
||||
|
public class WorkflowNotificationHandler : |
||||
|
INotificationHandler<WorkflowExecuting>, |
||||
|
INotificationHandler<WorkflowStarted>, |
||||
|
INotificationHandler<WorkflowFinished>, |
||||
|
INotificationHandler<WorkflowExecuted>, |
||||
|
INotificationHandler<WorkflowCancelling>, |
||||
|
INotificationHandler<WorkflowCancelled> |
||||
|
{ |
||||
|
public ILogger<WorkflowNotificationHandler> Logger { protected get; set; } |
||||
|
public WorkflowNotificationHandler() |
||||
|
{ |
||||
|
Logger = NullLogger<WorkflowNotificationHandler>.Instance; |
||||
|
} |
||||
|
public async virtual Task HandleAsync(WorkflowExecuting notification, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
Logger.LogDebug("The workflow notification: {notification} has been triggered.", notification.ToString()); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task HandleAsync(WorkflowStarted notification, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
Logger.LogDebug("The workflow notification: {notification} has been triggered.", notification.ToString()); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task HandleAsync(WorkflowFinished notification, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
Logger.LogDebug("The workflow notification: {notification} has been triggered.", notification.ToString()); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task HandleAsync(WorkflowExecuted notification, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
Logger.LogDebug("The workflow notification: {notification} has been triggered.", notification.ToString()); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task HandleAsync(WorkflowCancelling notification, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
Logger.LogDebug("The workflow notification: {notification} has been triggered.", notification.ToString()); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task HandleAsync(WorkflowCancelled notification, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
Logger.LogDebug("The workflow notification: {notification} has been triggered.", notification.ToString()); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
{ |
||||
|
"culture": "en", |
||||
|
"texts": { |
||||
|
"Notifications": "Notifications", |
||||
|
"Send Notification": "Send Notification", |
||||
|
"Send an notification.": "Send an notification.", |
||||
|
"The name of the registered notification.": "The name of the registered notification.", |
||||
|
"The notification title.": "The notification title.", |
||||
|
"The notification content.": "The notification content.", |
||||
|
"The notification description.": "The notification description.", |
||||
|
"The notification template name.": "The notification template name.", |
||||
|
"The notification additional attributes.": "The notification additional attributes.", |
||||
|
"The tenant id for sending the notification.": "The tenant id for sending the notification.", |
||||
|
"The recipients email addresses.": "The recipients email addresses.", |
||||
|
"The notifications severity.": "The notifications severity.", |
||||
|
"The notification id.": "The notification id." |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
{ |
||||
|
"culture": "zh-Hans", |
||||
|
"texts": { |
||||
|
"Notifications": "消息通知", |
||||
|
"Send Notification": "发送通知", |
||||
|
"Send an notification.": "发送通知。", |
||||
|
"The name of the registered notification.": "已注册的通知名称。", |
||||
|
"The notification title.": "通知标题。", |
||||
|
"The notification content.": "通知内容。", |
||||
|
"The notification description.": "通知描述。", |
||||
|
"The notification template name.": "通知模板,使用模板消息时标题、内容、描述内容无效。", |
||||
|
"The notification additional attributes.": "通知属性。", |
||||
|
"The tenant id for sending the notification.": "将要发送通知的租户Id。", |
||||
|
"The recipients email addresses.": "接收通知的用户Id集合。", |
||||
|
"The notifications severity.": "通知严重程度。", |
||||
|
"The notification id.": "通知Id。" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
namespace LINGYUN.Abp.ElsaNext.Notifications.UIHints; |
||||
|
|
||||
|
public static class NotificationsUIHints |
||||
|
{ |
||||
|
public const string NotificationPicker = "notification-picker"; |
||||
|
} |
||||
@ -0,0 +1,49 @@ |
|||||
|
using Elsa.Studio.Secrets.Extensions; |
||||
|
using LINGYUN.Abp.ElsaNext.Localization; |
||||
|
using LINGYUN.Abp.ElsaNext.Secrets.Blazor.Navigation; |
||||
|
using LINGYUN.Abp.ElsaNext.Studio.Blazor; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.UI.Navigation; |
||||
|
using Volo.Abp.VirtualFileSystem; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.Secrets.Blazor; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpElsaNextSecretsModule), |
||||
|
typeof(AbpElsaNextStudioBlazorModule))] |
||||
|
public class AbpElsaNextSecretsBlazorModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
var configuration = context.Configuration.GetSection("Elsa:Studio"); |
||||
|
|
||||
|
Configure<AbpRouterOptions>(options => |
||||
|
{ |
||||
|
options.AdditionalAssemblies.Add(typeof(Elsa.Studio.Secrets.Feature).Assembly); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpNavigationOptions>(options => |
||||
|
{ |
||||
|
options.MenuContributors.Add(new ElsaStudioSecretsMenuContributor()); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpVirtualFileSystemOptions>(options => |
||||
|
{ |
||||
|
options.FileSets.AddEmbedded<AbpElsaNextSecretsBlazorModule>("LINGYUN.Abp.ElsaNext.Secrets.Blazor"); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.Resources |
||||
|
.Get<ElsaNextResource>() |
||||
|
.AddVirtualJson("/Localization/Resources/SecretsBlazor"); |
||||
|
}); |
||||
|
|
||||
|
var elsaNextStudioBlazoeOptions = context.Services.ExecutePreConfiguredActions<AbpElsaNextStudioBlazorOptions>(); |
||||
|
|
||||
|
context.Services.AddSecretsModule(elsaNextStudioBlazoeOptions.BackendApiConfig); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,29 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk.Razor"> |
||||
|
|
||||
|
<Import Project="..\..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net10.0</TargetFramework> |
||||
|
<AssemblyName>LINGYUN.Abp.ElsaNext.Secrets.Blazor</AssemblyName> |
||||
|
<PackageId>LINGYUN.Abp.ElsaNext.Secrets.Blazor</PackageId> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<Content Remove="Localization\Resources\SecretsBlazor\*.json" /> |
||||
|
<EmbeddedResource Include="Localization\Resources\SecretsBlazor\*.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Elsa.Studio.Secrets" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.ElsaNext.Secrets\LINGYUN.Abp.ElsaNext.Secrets.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.ElsaNext.Studio.Blazor\LINGYUN.Abp.ElsaNext.Studio.Blazor.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,7 @@ |
|||||
|
{ |
||||
|
"culture": "en", |
||||
|
"texts": { |
||||
|
"Menu:Secrets": "Secrets", |
||||
|
"Secrets": "Secrets" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
{ |
||||
|
"culture": "zh-Hans", |
||||
|
"texts": { |
||||
|
"Menu:Secrets": "机密", |
||||
|
"Secrets": "机密" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
using LINGYUN.Abp.ElsaNext.Localization; |
||||
|
using LINGYUN.Abp.ElsaNext.Studio.Blazor.Navigation; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.UI.Navigation; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.Secrets.Blazor.Navigation; |
||||
|
|
||||
|
public class ElsaStudioSecretsMenuContributor : IMenuContributor |
||||
|
{ |
||||
|
public Task ConfigureMenuAsync(MenuConfigurationContext context) |
||||
|
{ |
||||
|
if (context.Menu.Name == StandardMenus.Main) |
||||
|
{ |
||||
|
var menu = context.Menu; |
||||
|
var l = context.GetLocalizer<ElsaNextResource>(); |
||||
|
|
||||
|
var group = menu.GetMenuItemOrNull(ElsaStudioMenus.GroupName); |
||||
|
group?.AddItem(new ApplicationMenuItem( |
||||
|
ElsaStudioMenus.GroupName + ".Secrets", |
||||
|
l["Menu:Secrets"], |
||||
|
url: "/security/secrets", |
||||
|
icon: "fa fa-key", |
||||
|
order: 6)); |
||||
|
} |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue