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 |
|||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers |
|||
@using Microsoft.AspNetCore.Authorization |
|||
@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