diff --git a/Directory.Packages.props b/Directory.Packages.props index 3892a028e..036abb4fa 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,7 +2,7 @@ 10.0.0 2.17.0 - 3.6.0 + 3.8.0-rc2 10.6.0 10.6.0 10.0.9 @@ -14,6 +14,7 @@ + @@ -28,8 +29,14 @@ + + + + + + @@ -48,6 +55,7 @@ + @@ -148,6 +156,7 @@ + @@ -162,9 +171,9 @@ + - @@ -227,20 +236,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + - + + + + + + + - - - + + + + + + + @@ -278,6 +343,7 @@ + @@ -320,6 +386,7 @@ + @@ -392,6 +459,7 @@ + diff --git a/aspnet-core/LINGYUN.MicroService.Aspire.slnx b/aspnet-core/LINGYUN.MicroService.Aspire.slnx index b00ca5382..b0b61f0cf 100644 --- a/aspnet-core/LINGYUN.MicroService.Aspire.slnx +++ b/aspnet-core/LINGYUN.MicroService.Aspire.slnx @@ -130,8 +130,8 @@ - + @@ -552,6 +552,19 @@ + + + + + + + + + + + + + diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer.DbMigrator/appsettings.json b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer.DbMigrator/appsettings.json index 6a90cf896..b1f35fb39 100644 --- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer.DbMigrator/appsettings.json +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer.DbMigrator/appsettings.json @@ -7,7 +7,10 @@ "VueAdmin": { "ClientId": "vue-admin-client", "ClientSecret": "1q2w3e*", - "RootUrls": [ "http://localhost:5666" ] + "RootUrls": [ + "http://localhost:5666", + "http://127.0.0.1:5666" + ] }, "InternalService": { "ClientId": "InternalServiceClient", @@ -17,16 +20,34 @@ "ClientId": "vue-oauth-client", "RootUrls": [ "http://localhost:5666", + "http://127.0.0.1:5666", "http://localhost:30000", + "http://127.0.0.1:30000", "http://localhost:30010", + "http://127.0.0.1:30010", "http://localhost:30015", + "http://127.0.0.1:30015", "http://localhost:30020", + "http://127.0.0.1:30020", "http://localhost:30025", + "http://127.0.0.1:30025", "http://localhost:30030", + "http://127.0.0.1:30030", "http://localhost:30040", + "http://127.0.0.1:30040", "http://localhost:30045", + "http://127.0.0.1:30045", + "http://localhost:30050", + "http://127.0.0.1:30050", + "http://localhost:30060", + "http://127.0.0.1:30060" + ] + }, + "ElsaStudioClient": { + "ClientId": "elsa-studio-client", + "RootUrls": [ "http://localhost:30050", - "http://localhost:30060" + "http://127.0.0.1:30050" ] } } diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer.EntityFrameworkCore/DataSeeds/OpenIddictDataSeeder.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer.EntityFrameworkCore/DataSeeds/OpenIddictDataSeeder.cs index 6bd44bf85..66d9fc538 100644 --- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer.EntityFrameworkCore/DataSeeds/OpenIddictDataSeeder.cs +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer.EntityFrameworkCore/DataSeeds/OpenIddictDataSeeder.cs @@ -576,5 +576,51 @@ public class OpenIddictDataSeeder : OpenIddictDataSeedContributorBase, ITransien }; await PermissionDataSeeder.SeedAsync(ClientPermissionValueProvider.ProviderName, oauthClientId, oauthClientPermissions); } + + var elsaStudioClientId = configurationSection["ElsaStudioClient:ClientId"]; + if (!elsaStudioClientId.IsNullOrWhiteSpace()) + { + Logger.LogInformation("Seeding application {elsaStudioClientId}...", elsaStudioClientId); + + var elsaStudioClientSecret = configurationSection["ElsaStudioClient:ClientSecret"]; + var elsaStudioClientRootUrls = configurationSection.GetSection("ElsaStudioClient:RootUrls").Get>() ?? []; + + var elsaStudioClientRedirectUrls = new List(); + var elsaStudioClientPostLogoutRedirectUrls = new List(); + elsaStudioClientRootUrls.ForEach(url => + { + elsaStudioClientRedirectUrls.Add(url.EnsureEndsWith('/')); + elsaStudioClientRedirectUrls.Add(url.EnsureEndsWith('/') + "signin-oidc"); + elsaStudioClientRedirectUrls.Add(url.EnsureEndsWith('/') + "swagger/oauth2-redirect.html"); + + elsaStudioClientPostLogoutRedirectUrls.Add(url.EnsureEndsWith('/')); + elsaStudioClientPostLogoutRedirectUrls.Add(url.EnsureEndsWith('/') + "signout-oidc"); + elsaStudioClientPostLogoutRedirectUrls.Add(url.EnsureEndsWith('/') + "signout-callback-oidc"); + }); + + await CreateOrUpdateApplicationAsync( + OpenIddictConstants.ApplicationTypes.Web, + elsaStudioClientId, + !elsaStudioClientSecret.IsNullOrWhiteSpace() + ? OpenIddictConstants.ClientTypes.Confidential + : OpenIddictConstants.ClientTypes.Public, + OpenIddictConstants.ConsentTypes.Implicit, + "Abp Elsa Studio Client", + elsaStudioClientSecret, + [OpenIddictConstants.GrantTypes.AuthorizationCode, + OpenIddictConstants.GrantTypes.RefreshToken, + OpenIddictConstants.GrantTypes.Password], + [OpenIddictConstants.Scopes.OpenId, + OpenIddictConstants.Scopes.Email, + OpenIddictConstants.Scopes.Roles, + OpenIddictConstants.Scopes.Address, + OpenIddictConstants.Scopes.Phone, + OpenIddictConstants.Scopes.Profile, + OpenIddictConstants.Scopes.OfflineAccess, + scope, + "workflow-service"], + elsaStudioClientRedirectUrls, + elsaStudioClientPostLogoutRedirectUrls); + } } } diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Components/App.razor b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Components/App.razor new file mode 100644 index 000000000..bb6788876 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Components/App.razor @@ -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; +} + + + + + + + + Elsa Studio (Blazor Server) + + + + + + @* Elsa Studio UI styles (RCL static assets) + offline icon-font fallback. *@ + + + + + + + + + + + + + + + + + + @* Monaco + Blazor Server bootstrap (mirrors the official Elsa Studio Server host). *@ + + + + + + + + +
+ @if (Env.IsDevelopment()) + { + An unhandled exception has occurred. See browser dev tools for details. + } + else if (Env.IsStaging() || Env.IsProduction()) + { + An error has occurred. This application may no longer respond until reloaded. + } + Reload + ✖ +
+ + + diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Components/Index.razor b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Components/Index.razor new file mode 100644 index 000000000..a1cf0e819 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Components/Index.razor @@ -0,0 +1,137 @@ +@page "/" +@using Microsoft.AspNetCore.Components.Authorization +@inject AuthenticationStateProvider AuthenticationStateProvider +@inject NavigationManager Navigation + + + + + + @if (!IsAuthenticated) + { + 欢迎使用 Elsa 工作室 + + 以 Blazor Server 模式集成的 Elsa 工作流工作室:服务端渲染与交互,接口调用在服务端完成,凭据不出浏览器。 + + 登录 + } + else + { + 欢迎回来 + + 通过左侧菜单进入 Elsa 工作室:管理工作流定义与实例,打开设计器进行可视化编排。 + + 工作流定义 + } + + + + + + + + 关于 + + + 服务端 Blazor Server(SignalR)承载 Elsa Studio;认证走外部 OIDC AuthServer 授权码 + PKCE; + Studio 页面以 ABP 子页面呈现(路由在菜单中定义)。 + + + + 集成模块:LINGYUN.Abp.ElsaNext.Studio.Blazor + + + + + + + + + + + More from ABP.IO + + + + + Latest Release Logs + + + Video Courses + + + Samples + + + Books + + + FAQ + + + + + + + + + Follow us on Social Media + + + + + + + X.com (Twitter) + + + Discord + + + Stack Overflow + + + + + + + YouTube + + + Instagram + + + GitHub + + + + + + + + + +@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("/elsa/workflows/definitions"); +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Components/LoginRedirect.razor b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Components/LoginRedirect.razor new file mode 100644 index 000000000..edd773c0d --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Components/LoginRedirect.razor @@ -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); + } +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Components/Routes.razor b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Components/Routes.razor new file mode 100644 index 000000000..abe6f6e0f --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Components/Routes.razor @@ -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 RouterOptions + + + + + + + + + diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Components/_Imports.razor b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Components/_Imports.razor new file mode 100644 index 000000000..3c085a7eb --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Components/_Imports.razor @@ -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 diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/ElsaShellPermissionPolicyProvider.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/ElsaShellPermissionPolicyProvider.cs new file mode 100644 index 000000000..b02b46750 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/ElsaShellPermissionPolicyProvider.cs @@ -0,0 +1,42 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Options; +using System; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.MicroService.WorkflowService; + +/// +/// Root 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 ), +/// so epPolicy names are bridged to that grant; other policy names fall back to the defaults. +/// +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())); + + /// + public Task 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(policy); + } + + /// + public Task GetDefaultPolicyAsync() => Task.FromResult(AllowAll); + + /// + public Task GetFallbackPolicyAsync() => Task.FromResult(AllowAll); +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Extensions/ElsaStudioServiceCollectionExtensions.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Extensions/ElsaStudioServiceCollectionExtensions.cs new file mode 100644 index 000000000..fd6855625 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Extensions/ElsaStudioServiceCollectionExtensions.cs @@ -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 configure, + Func> configureRetryPolicy = null) + { + var options = new OidcOptions(); + configure(options); + services.AddSingleton(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(); + 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(); + services.AddScoped(); + + // Shared token refresh service used by both session refresh and backend API token acquisition + services.AddScoped(); + + // Cookie authentication events for automatic session token refresh (standard ASP.NET Core pattern) + services.AddScoped(); + + // 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>(); + services.AddScoped(); + services.AddScoped(); + + // 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; + } +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/LINGYUN.Abp.MicroService.WorkflowService.csproj b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/LINGYUN.Abp.MicroService.WorkflowService.csproj index df5920d1a..73a27bf88 100644 --- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/LINGYUN.Abp.MicroService.WorkflowService.csproj +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/LINGYUN.Abp.MicroService.WorkflowService.csproj @@ -7,20 +7,26 @@ LINGYUN.Abp.MicroService.WorkflowService
+ + + + + + - - - - - + + + + + + - - + @@ -31,14 +37,16 @@ - + - + + + + - @@ -62,23 +70,28 @@ - - - - - - + + + + + + + + + + + diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Localization/Resources/en.json b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Localization/Resources/en.json new file mode 100644 index 000000000..55cc4d025 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Localization/Resources/en.json @@ -0,0 +1,6 @@ +{ + "culture": "en", + "texts": { + "Menu:Logout": "Logout" + } +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Localization/Resources/zh-Hans.json b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Localization/Resources/zh-Hans.json new file mode 100644 index 000000000..e0c5360eb --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Localization/Resources/zh-Hans.json @@ -0,0 +1,6 @@ +{ + "culture": "zh-Hans", + "texts": { + "Menu:Logout": "退出登录" + } +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Navigation/UserMenuContributor.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Navigation/UserMenuContributor.cs new file mode 100644 index 000000000..7aee5f4ae --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Navigation/UserMenuContributor.cs @@ -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(); + + 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; + } +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Pages/Account/Login.cshtml b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Pages/Account/Login.cshtml new file mode 100644 index 000000000..7e20249e0 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Pages/Account/Login.cshtml @@ -0,0 +1,6 @@ +@page "/Account/Login" +@model LINGYUN.Abp.MicroService.WorkflowService.Pages.Auth.LoginModel +@{ + ViewData["Title"] = "Login"; +} +

Redirecting to the identity provider…

diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Pages/Account/Login.cshtml.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Pages/Account/Login.cshtml.cs new file mode 100644 index 000000000..971e55c84 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Pages/Account/Login.cshtml.cs @@ -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; + +/// +/// 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. +/// +public class LoginModel : PageModel +{ + public IActionResult OnGet(string returnUrl = null) + { + var redirectUri = string.IsNullOrWhiteSpace(returnUrl) ? "/" : returnUrl; + return Challenge( + new AuthenticationProperties { RedirectUri = redirectUri }, + OpenIdConnectDefaults.AuthenticationScheme); + } +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Pages/Account/Logout.cshtml b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Pages/Account/Logout.cshtml new file mode 100644 index 000000000..d45471480 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Pages/Account/Logout.cshtml @@ -0,0 +1,6 @@ +@page "/Account/Logout" +@model LINGYUN.Abp.MicroService.WorkflowService.Pages.Auth.LogoutModel +@{ + ViewData["Title"] = "Logout"; +} +

Signing out…

diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Pages/Account/Logout.cshtml.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Pages/Account/Logout.cshtml.cs new file mode 100644 index 000000000..cdaf35550 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Pages/Account/Logout.cshtml.cs @@ -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; + +/// +/// Signs out the local cookie and ends the OIDC session at the external AuthServer. +/// +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); + } +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Pages/_Host.cshtml b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Pages/_Host.cshtml deleted file mode 100644 index 4b74acc72..000000000 --- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Pages/_Host.cshtml +++ /dev/null @@ -1,42 +0,0 @@ -@page "/" -@inject IConfiguration Configuration; -@{ - var serverUrl = Configuration["Elsa:Server:BaseUrl"]; -} - - - - - - Elsa Workflows - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Pages/_ViewImports.cshtml b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Pages/_ViewImports.cshtml index ca9c771ea..1ac695742 100644 --- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Pages/_ViewImports.cshtml +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Pages/_ViewImports.cshtml @@ -1,2 +1,3 @@ -@using Microsoft.Extensions.Configuration -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers \ No newline at end of file +@using Microsoft.AspNetCore.Authorization +@using Microsoft.AspNetCore.Http +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Program.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Program.cs index 42b511467..4d1d94064 100644 --- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Program.cs +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/Program.cs @@ -1,20 +1,25 @@ -using LINGYUN.Abp.Identity.Session.AspNetCore; +using Elsa.Extensions; +using LINGYUN.Abp.Identity.Session.AspNetCore; using LINGYUN.Abp.MicroService.WorkflowService; +using LINGYUN.Abp.MicroService.WorkflowService.Components; using LINGYUN.Abp.Serilog.Enrichers.Application; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; using Serilog; using System; using System.IO; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing; using Volo.Abp.IO; using Volo.Abp.Modularity.PlugIns; Log.Information("Starting WorkflowService Host..."); var builder = WebApplication.CreateBuilder(args); +builder.WebHost.UseStaticWebAssets(); builder.Host.AddAppSettingsSecretsJson() .UseAutofac() .ConfigureAppConfiguration((context, config) => @@ -27,7 +32,7 @@ builder.Host.AddAppSettingsSecretsJson() .UseSerilog((context, provider, config) => { config.ReadFrom.Configuration(context.Configuration); - }); + }, writeToProviders: true); builder.AddServiceDefaults(); @@ -49,18 +54,19 @@ await app.InitializeApplicationAsync(); app.MapDefaultEndpoints(); app.UseForwardedHeaders(); -// 本地化 -app.UseMapRequestLocalization(); +app.UseCors(); app.UseCorrelationId(); -app.MapAbpStaticAssets(); +app.UseMapRequestLocalization(); app.UseRouting(); -app.UseCors(); +app.UseStaticFiles(); +app.MapAbpStaticAssets(); app.UseAuthentication(); app.UseJwtTokenMiddleware(); app.UseMultiTenancy(); app.UseAbpSession(); app.UseDynamicClaims(); app.UseAuthorization(); +app.UseAntiforgery(); app.UseSwagger(); app.UseAbpSwaggerUI(options => { @@ -70,9 +76,15 @@ app.UseAbpSwaggerUI(options => options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]); options.OAuthScopes(configuration["AuthServer:Audience"]); }); +app.UseWorkflowsApi(); // Use Elsa API endpoints. +app.UseWorkflows(); // Use Elsa middleware for HTTP Endpoint activities. app.UseAuditing(); app.UseAbpSerilogEnrichers(); -app.UseConfiguredEndpoints(); -app.UseHttpActivities(); +app.UseConfiguredEndpoints(builder => +{ + builder.MapRazorComponents() + .AddInteractiveServerRenderMode() + .AddAdditionalAssemblies(builder.ServiceProvider.GetRequiredService>().Value.AdditionalAssemblies.ToArray()); +}); await app.RunAsync(); \ No newline at end of file diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/WorkflowServiceModule.Configure.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/WorkflowServiceModule.Configure.cs index 7fe8f244e..10bda73f8 100644 --- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/WorkflowServiceModule.Configure.cs +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/WorkflowServiceModule.Configure.cs @@ -1,11 +1,21 @@ using DotNetCore.CAP; -using Elsa; -using Elsa.Options; -using Elsa.Rebus.RabbitMq; -using LINGYUN.Abp.BackgroundTasks; +using Elsa.Agents; +using Elsa.Extensions; +using Elsa.Features.Services; +using Elsa.Persistence.EFCore.Extensions; +using Elsa.Persistence.EFCore.Modules.Management; +using Elsa.Persistence.EFCore.Modules.Runtime; +using Elsa.Secrets.Persistence.EFCore.Extensions; +using Elsa.Secrets.Persistence.EFCore.Sqlite.Extensions; +using Elsa.Studio.Authentication.OpenIdConnect.HttpMessageHandlers; +using Elsa.Studio.Workflows.Designer.Extensions; +using LINGYUN.Abp.ElsaNext.Studio.Blazor; using LINGYUN.Abp.Localization.CultureMap; using LINGYUN.Abp.LocalizationManagement; +using LINGYUN.Abp.MicroService.WorkflowService.Extensions; +using LINGYUN.Abp.MicroService.WorkflowService.Navigation; using LINGYUN.Abp.Serilog.Enrichers.UniqueId; +using Localization.Resources.AbpUi; using Medallion.Threading; using Medallion.Threading.Redis; using Microsoft.AspNetCore.Authentication.JwtBearer; @@ -17,15 +27,17 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Logging; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi; -using Quartz; using StackExchange.Redis; using Swashbuckle.AspNetCore.SwaggerGen; using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Security.Claims; using System.Text.Encodings.Web; using System.Text.Unicode; +using Volo.Abp.AspNetCore.Components.Web; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc.AntiForgery; using Volo.Abp.Auditing; @@ -38,18 +50,11 @@ using Volo.Abp.Json.SystemTextJson; using Volo.Abp.Localization; using Volo.Abp.MultiTenancy; using Volo.Abp.PermissionManagement; -using Volo.Abp.Quartz; using Volo.Abp.Security.Claims; using Volo.Abp.Threading; using Volo.Abp.Timing; +using Volo.Abp.UI.Navigation; using Volo.Abp.VirtualFileSystem; -using ConsoleStartup = Elsa.Activities.Console.Startup; -using HttpStartup = Elsa.Activities.Http.Startup; -using JavaScriptStartup = Elsa.Scripting.JavaScript.Startup; -using TemporalQuartzStartup = Elsa.Activities.Temporal.Quartz.Startup; -using UserTaskStartup = Elsa.Activities.UserTask.Startup; -using WebhooksListEndpoint = Elsa.Webhooks.Api.Endpoints.List; -using WebhooksStartup = Elsa.Activities.Webhooks.Startup; namespace LINGYUN.Abp.MicroService.WorkflowService; @@ -105,102 +110,109 @@ public partial class WorkflowServiceModule private void PreConfigureQuartz(IConfiguration configuration) { - PreConfigure(options => - { - // 如果使用持久化存储, 则配置quartz持久层 - if (configuration.GetSection("Quartz:UsePersistentStore").Get()) - { - var settings = configuration.GetSection("Quartz:Properties").Get>(); - if (settings != null) - { - foreach (var setting in settings) - { - options.Properties[setting.Key] = setting.Value; - } - } + //PreConfigure(options => + //{ + // // 如果使用持久化存储, 则配置quartz持久层 + // if (configuration.GetSection("Quartz:UsePersistentStore").Get()) + // { + // var settings = configuration.GetSection("Quartz:Properties").Get>(); + // if (settings != null) + // { + // foreach (var setting in settings) + // { + // options.Properties[setting.Key] = setting.Value; + // } + // } - options.Configurator += (config) => - { - config.UsePersistentStore(store => - { - store.UseProperties = false; - store.UseNewtonsoftJsonSerializer(); - }); - }; - } - }); + // options.Configurator += (config) => + // { + // config.UsePersistentStore(store => + // { + // store.UseProperties = false; + // store.UseNewtonsoftJsonSerializer(); + // }); + // }; + // } + //}); } private void ConfigureBackgroundTasks(IServiceCollection services, IConfiguration configuration) { - Configure(options => - { - options.NodeName = services.GetApplicationName(); - }); + //Configure(options => + //{ + // options.NodeName = services.GetApplicationName(); + //}); } private void PreConfigureElsa(IServiceCollection services, IConfiguration configuration) { - var elsaSection = configuration.GetSection("Elsa"); - var startups = new[] + PreConfigure(options => + { + options.IsBlazorWebApp = true; + }); + PreConfigure(elsa => + { + elsa.UseWorkflowManagement(management => { - typeof(ConsoleStartup), - typeof(HttpStartup), - typeof(UserTaskStartup), - typeof(TemporalQuartzStartup), - typeof(JavaScriptStartup), - typeof(WebhooksStartup), - }; + management.UseEntityFrameworkCore(ef => ef.UseSqlite()); + }); - PreConfigure(elsa => - { - elsa - .AddActivitiesFrom() - .AddWorkflowsFrom() - .AddFeatures(startups, configuration) - .ConfigureWorkflowChannels(options => elsaSection.GetSection("WorkflowChannels").Bind(options)) - .UseRabbitMq(elsaSection["Rebus:RabbitMQ:Connection"]); - - elsa.DistributedLockingOptionsBuilder - .UseProviderFactory(sp => name => - { - var provider = sp.GetRequiredService(); + elsa.UseWorkflowRuntime(runtime => + { + runtime.UseEntityFrameworkCore(ef => ef.UseSqlite()); + }); - return provider.CreateLock(name); - }); - }); + elsa.UseAgentPersistence(agent => + { + agent.UseEntityFrameworkCore(ef => ef.UseSqlite()); + }); - services.AddNotificationHandlersFrom(); + elsa.UseSecrets(secret => + { + secret.UseEntityFrameworkCore(ef => ef.UseSqlite()); + }); + + elsa.UseQuartz(quartz => + { + quartz.UseSqlite(); + }); + + elsa.UseScheduling(scheduling => + { + scheduling.UseQuartzScheduler(); + }); + }); - PreConfigure(mvcBuilder => + PreConfigure(options => { - mvcBuilder.AddApplicationPartIfNotExists(typeof(WebhooksListEndpoint).Assembly); + options.BackendApiConfig.ConfigureHttpClientBuilder = options => + { + options.AuthenticationHandler = typeof(OidcAuthenticatingApiHttpMessageHandler); + options.ConfigureHttpClient = (_, client) => + { + // Set a long time out to simplify debugging both Elsa Studio and the Elsa Server backend. + client.Timeout = TimeSpan.FromHours(1); + }; + }; }); + + services.AddRazorComponents() + .AddInteractiveServerComponents(options => + { + options.RootComponents.RegisterCustomElsaStudioElements(); + }); } - private void ConfigureEndpoints(IServiceCollection services) + private void ConfigureElsa(IServiceCollection services, IConfiguration configuration) { - // 不需要 - //Configure(options => + //Configure(options => //{ - // options.EndpointConfigureActions.Add( - // (context) => - // { - // context.Endpoints.MapFallbackToPage("/_Host"); - // }); + // options.WithFeature(); //}); - var preActions = services.GetPreConfigureActions(); - - services.AddAbpApiVersioning(options => - { - options.ReportApiVersions = true; - options.AssumeDefaultVersionWhenUnspecified = true; - //options.ApiVersionReader = new HeaderApiVersionReader("api-version"); //Supports header too - //options.ApiVersionReader = new MediaTypeApiVersionReader(); //Supports accept header too - }, mvcOptions => + services.AddOpenIdConnectAuth(options => { - mvcOptions.ConfigureAbp(preActions.Configure()); + configuration.GetSection("Authentication:OpenIdConnect").Bind(options); }); } @@ -296,6 +308,16 @@ public partial class WorkflowServiceModule { options.ExposeIntegrationServices = true; }); + + Configure(options => + { + options.MenuContributors.Add(new UserMenuContributor()); + }); + + Configure(options => + { + options.AppAssembly = typeof(WorkflowServiceModule).Assembly; + }); } private void ConfigureVirtualFileSystem() @@ -411,6 +433,9 @@ public partial class WorkflowServiceModule { options.Languages.Add(new LanguageInfo("en", "en", "English")); options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文")); + + options.Resources.Get() + .AddVirtualJson("/Localization/Resources"); }); Configure(options => @@ -438,7 +463,7 @@ public partial class WorkflowServiceModule options.AutoValidate = false; }); - services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + services.AddAuthentication() .AddAbpJwtBearer(options => { configuration.GetSection("AuthServer").Bind(options); @@ -454,7 +479,20 @@ public partial class WorkflowServiceModule { options.TokenValidationParameters.ValidAudiences = validAudiences; } - }); + + options.Events ??= new JwtBearerEvents(); + var previousOnTokenValidated = options.Events.OnTokenValidated; + options.Events.OnTokenValidated = async context => + { + await previousOnTokenValidated(context); + if (context.Principal?.Identity?.IsAuthenticated == true) + { + context.Principal.AddIdentity(new ClaimsIdentity( + new[] { new Claim(Elsa.PermissionNames.ClaimType, Elsa.PermissionNames.All) })); + } + }; + }) + ; services .AddDataProtection() diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/WorkflowServiceModule.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/WorkflowServiceModule.cs index dc23b98f5..87063ec32 100644 --- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/WorkflowServiceModule.cs +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/WorkflowServiceModule.cs @@ -2,16 +2,17 @@ using LINGYUN.Abp.AspNetCore.Mvc.Wrapper; using LINGYUN.Abp.AuditLogging.Elasticsearch; using LINGYUN.Abp.Authorization.OrganizationUnits; -using LINGYUN.Abp.BackgroundTasks.DistributedLocking; -using LINGYUN.Abp.BackgroundTasks.Quartz; using LINGYUN.Abp.BlobStoring.BlobManagement; using LINGYUN.Abp.Claims.Mapping; using LINGYUN.Abp.Data.DbMigrator; using LINGYUN.Abp.Dynamic.Definitions; -using LINGYUN.Abp.Elsa; -using LINGYUN.Abp.Elsa.Activities; -using LINGYUN.Abp.Elsa.EntityFrameworkCore.PostgreSql; -using LINGYUN.Abp.Elsa.Notifications; +using LINGYUN.Abp.ElsaNext.Agents.Blazor; +using LINGYUN.Abp.ElsaNext.Secrets.Blazor; +using LINGYUN.Abp.ElsaNext.Server; +using LINGYUN.Abp.ElsaNext.Studio.Blazor; +using LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor; +using LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor; +using LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor; using LINGYUN.Abp.Emailing.Platform; using LINGYUN.Abp.EventBus.CAP; using LINGYUN.Abp.ExceptionHandling.Emailing; @@ -19,20 +20,19 @@ using LINGYUN.Abp.Http.Client.Wrapper; using LINGYUN.Abp.Identity.Session.AspNetCore; using LINGYUN.Abp.Localization.CultureMap; using LINGYUN.Abp.LocalizationManagement.EntityFrameworkCore; -using LINGYUN.Abp.Quartz.PostgresSqlInstaller; using LINGYUN.Abp.Saas.EntityFrameworkCore; using LINGYUN.Abp.Serilog.Enrichers.Application; using LINGYUN.Abp.Serilog.Enrichers.UniqueId; using LINGYUN.Abp.Sms.Platform; -using LINGYUN.Abp.TaskManagement.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; using Volo.Abp; using Volo.Abp.AspNetCore.Authentication.JwtBearer; +using Volo.Abp.AspNetCore.Authentication.OpenIdConnect; +using Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme; using Volo.Abp.AspNetCore.MultiTenancy; using Volo.Abp.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc.NewtonsoftJson; using Volo.Abp.AspNetCore.Serilog; using Volo.Abp.Autofac; using Volo.Abp.Caching.StackExchangeRedis; @@ -52,29 +52,33 @@ namespace LINGYUN.Abp.MicroService.WorkflowService; typeof(AbpAuditLoggingElasticsearchModule), typeof(AbpAspNetCoreSerilogModule), typeof(AbpBlobStoringBlobManagementModule), - typeof(AbpElsaModule), - typeof(AbpElsaServerModule), - typeof(AbpElsaActivitiesModule), - typeof(AbpElsaNotificationsModule), + typeof(AbpElsaNextServerModule), + typeof(AbpElsaNextStudioBlazorModule), + typeof(AbpElsaNextAgentsBlazorModule), + typeof(AbpElsaNextSecretsBlazorModule), + typeof(AbpElsaNextStudioDashboardBlazorModule), + typeof(AbpElsaNextStudioDiagnosticsOpenTelemetryBlazorModule), + typeof(AbpElsaNextStudioDiagnosticsStructuredLogsBlazorModule), typeof(AbpEmailingExceptionHandlingModule), typeof(AbpHttpClientIdentityModelWebModule), typeof(AbpAspNetCoreMultiTenancyModule), - typeof(AbpBackgroundTasksQuartzModule), - typeof(AbpBackgroundTasksDistributedLockingModule), - typeof(AbpQuartzPostgresSqlInstallerModule), - typeof(TaskManagementEntityFrameworkCoreModule), + //typeof(AbpBackgroundTasksQuartzModule), + //typeof(AbpBackgroundTasksDistributedLockingModule), + //typeof(AbpQuartzPostgresSqlInstallerModule), + //typeof(TaskManagementEntityFrameworkCoreModule), typeof(AbpFeatureManagementEntityFrameworkCoreModule), typeof(AbpPermissionManagementEntityFrameworkCoreModule), typeof(AbpSettingManagementEntityFrameworkCoreModule), typeof(AbpSaasEntityFrameworkCoreModule), typeof(AbpLocalizationManagementEntityFrameworkCoreModule), - typeof(AbpElsaEntityFrameworkCorePostgreSqlModule), typeof(AbpAuthorizationOrganizationUnitsModule), typeof(AbpAspNetCoreAuthenticationJwtBearerModule), + typeof(AbpAspNetCoreAuthenticationOpenIdConnectModule), typeof(AbpTextTemplatingScribanModule), typeof(AbpDataDbMigratorModule), typeof(AbpCachingStackExchangeRedisModule), typeof(AbpAspNetCoreMvcModule), + typeof(AbpAspNetCoreComponentsServerMudBlazorBasicThemeModule), typeof(AbpSwashbuckleModule), typeof(AbpCAPEventBusModule), typeof(AbpLocalizationCultureMapModule), @@ -83,7 +87,6 @@ namespace LINGYUN.Abp.MicroService.WorkflowService; typeof(AbpSmsPlatformModule), typeof(AbpEmailingPlatformModule), typeof(AbpClaimsMappingModule), - typeof(AbpAspNetCoreMvcNewtonsoftModule), typeof(AbpAspNetCoreHttpOverridesModule), typeof(AbpDynamicDefinitionsModule), typeof(AbpIdentitySessionAspNetCoreModule), @@ -120,15 +123,13 @@ public partial class WorkflowServiceModule : AbpModule ConfigureAuditing(configuration); ConfigureIdentity(configuration); ConfigureMultiTenancy(configuration); - ConfigureEndpoints(context.Services); ConfigureMvc(context.Services, configuration); ConfigureCors(context.Services, configuration); + ConfigureElsa(context.Services, configuration); ConfigureSwagger(context.Services, configuration); ConfigureBlobStoring(context.Services, configuration); ConfigureDistributedLock(context.Services, configuration); ConfigureBackgroundTasks(context.Services, configuration); ConfigureSecurity(context.Services, configuration, hostingEnvironment.IsDevelopment()); - - context.Services.AddRazorPages(); } } diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/appsettings.Development.json b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/appsettings.Development.json index 9e4f85d16..4c1ded63c 100644 --- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/appsettings.Development.json +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/appsettings.Development.json @@ -33,71 +33,28 @@ } }, "Elsa": { - "Features": { - "DefaultPersistence": { - "Enabled": true, - "ConnectionStringIdentifier": "Default", - "YesSql": { - "MySql": { - "Enabled": true - } - }, - "EntityFrameworkCore": { - "MySql": { - "Enabled": true - } - } - }, - "Console": true, - "Http": true, - "Email": true, - "TemporalQuartz": true, - "JavaScriptActivities": true, - "UserTask": true, - "Conductor": true, - "Telnyx": true, - "BlobStoring": true, - "Emailing": true, - "Notification": true, - "Sms": true, - "IM": true, - "PublishWebhook": true, - "Webhooks": { - "Enabled": true, - "ConnectionStringIdentifier": "Default", - "YesSql": { - "MySql": { - "Enabled": true - } - }, - "EntityFrameworkCore": { - "MySql": { - "Enabled": true - } - } - }, - "WorkflowSettings": { - "Enabled": true, - "ConnectionStringIdentifier": "Default", - "YesSql": { - "MySql": { - "Enabled": true - } - }, - "EntityFrameworkCore": { - "MySql": { - "Enabled": true - } - } - } + "Http": { + "BaseUrl": "http://localhost:30050" }, - "Rebus": { - "RabbitMQ": { - "Connection": "amqp://admin:123456@localhost:5672/" - } + "Secrets": { + "EncryptionKey": "Q0hBTkdFX01FX1RPX0FfU0VDVVJFX1JBTkRPTV9LRVk=" }, "Server": { - "BaseUrl": "http://localhost:30035" + "EnableRealTimeWorkflows": true + }, + "Studio": { + "Backend": { + "Url": "http://localhost:30050/elsa/api" + }, + "DesignerOptions": { + "UseReactFlow": false + }, + "Shell": { + "DisableAuthorization": true + }, + "Presentation": { + "Theme": "classic" + } } }, "RemoteServices": { @@ -159,8 +116,26 @@ "ValidAudiences": [ "lingyun-abp-application" ], "MapInboundClaims": false, "RequireHttpsMetadata": false, - "ElsaClientId": "InternalServiceClient", - "SwaggerClientId": "vue-oauth-client" + "ClientId": "elsa-studio-client", + "SwaggerClientId": "elsa-studio-client" + }, + "Authentication": { + "OpenIdConnect": { + "Authority": "http://localhost:44385", + "ClientId": "elsa-studio-client", + "ClientSecret": null, + "AuthenticationScopes": [ + "openid", + "profile", + "roles", + "email", + "offline_access", + "workflow-service" + ], + "MapInboundClaims": false, + "RequireHttpsMetadata": false, + "SaveTokens": true + } }, "Logging": { "Serilog": { diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/package.json b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/package.json new file mode 100644 index 000000000..32c7e2da9 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.WorkflowService/package.json @@ -0,0 +1,8 @@ +{ + "version": "10.6.0", + "name": "aspire-workflow-service", + "private": true, + "dependencies": { + "@abp/aspnetcore.components.server.basictheme": "10.6.0" + } +} \ No newline at end of file diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents.Blazor/AbpElsaNextAgentsBlazorModule.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents.Blazor/AbpElsaNextAgentsBlazorModule.cs new file mode 100644 index 000000000..694366812 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents.Blazor/AbpElsaNextAgentsBlazorModule.cs @@ -0,0 +1,49 @@ +using Elsa.Studio.Extensions; +using LINGYUN.Abp.ElsaNext.Agents.Blazor.Navigation; +using LINGYUN.Abp.ElsaNext.Studio.Blazor; +using LINGYUN.Abp.ElsaNext.Studio.Translations.Localization; +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(options => + { + options.AdditionalAssemblies.Add(typeof(Elsa.Studio.Agents.Feature).Assembly); + }); + + Configure(options => + { + options.MenuContributors.Add(new ElsaStudioAgentsMenuContributor()); + }); + + Configure(options => + { + options.FileSets.AddEmbedded("LINGYUN.Abp.ElsaNext.Agents.Blazor"); + }); + + Configure(options => + { + options.Resources + .Get() + .AddVirtualJson("/Localization/Resources/AgentsBlazor"); + }); + + var elsaNextStudioBlazoeOptions = context.Services.ExecutePreConfiguredActions(); + + context.Services.AddAgentsModule(elsaNextStudioBlazoeOptions.BackendApiConfig); + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents.Blazor/FodyWeavers.xml b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents.Blazor/FodyWeavers.xml new file mode 100644 index 000000000..5d6962159 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents.Blazor/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents.Blazor/FodyWeavers.xsd b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents.Blazor/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents.Blazor/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents.Blazor/LINGYUN.Abp.ElsaNext.Agents.Blazor.csproj b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents.Blazor/LINGYUN.Abp.ElsaNext.Agents.Blazor.csproj new file mode 100644 index 000000000..86d30d0d3 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents.Blazor/LINGYUN.Abp.ElsaNext.Agents.Blazor.csproj @@ -0,0 +1,29 @@ + + + + + + + net10.0 + LINGYUN.Abp.ElsaNext.Agents.Blazor + LINGYUN.Abp.ElsaNext.Agents.Blazor + false + false + false + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents.Blazor/Localization/Resources/AgentsBlazor/en.json b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents.Blazor/Localization/Resources/AgentsBlazor/en.json new file mode 100644 index 000000000..9dc5c9c95 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents.Blazor/Localization/Resources/AgentsBlazor/en.json @@ -0,0 +1,7 @@ +{ + "culture": "en", + "texts": { + "Menu:Agents": "Agents", + "Agents": "Agents" + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents.Blazor/Localization/Resources/AgentsBlazor/zh-Hans.json b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents.Blazor/Localization/Resources/AgentsBlazor/zh-Hans.json new file mode 100644 index 000000000..db9837dee --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents.Blazor/Localization/Resources/AgentsBlazor/zh-Hans.json @@ -0,0 +1,7 @@ +{ + "culture": "zh-Hans", + "texts": { + "Menu:Agents": "代理列表", + "Agents": "代理列表" + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents.Blazor/Navigation/ElsaStudioAgentsMenuContributor.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents.Blazor/Navigation/ElsaStudioAgentsMenuContributor.cs new file mode 100644 index 000000000..7ad2d5057 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents.Blazor/Navigation/ElsaStudioAgentsMenuContributor.cs @@ -0,0 +1,28 @@ +using LINGYUN.Abp.ElsaNext.Studio.Blazor.Navigation; +using LINGYUN.Abp.ElsaNext.Studio.Translations.Localization; +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(); + + 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; + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents.Blazor/_Imports.razor b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents.Blazor/_Imports.razor new file mode 100644 index 000000000..6a4064616 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents.Blazor/_Imports.razor @@ -0,0 +1,5 @@ +@using Microsoft.AspNetCore.Authorization +@using Microsoft.AspNetCore.Components.Web +@using Elsa.Studio.Components +@using Elsa.Studio.Localization +@using MudBlazor diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents/FodyWeavers.xml b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents/FodyWeavers.xml new file mode 100644 index 000000000..5d6962159 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents/FodyWeavers.xsd b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents/LINGYUN.Abp.ElsaNext.Agents.csproj b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents/LINGYUN.Abp.ElsaNext.Agents.csproj new file mode 100644 index 000000000..a29230927 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents/LINGYUN.Abp.ElsaNext.Agents.csproj @@ -0,0 +1,27 @@ + + + + + + + net10.0 + LINGYUN.Abp.ElsaNext.Agents + LINGYUN.Abp.ElsaNext.Agents + enable + false + false + false + + + + + + + + + + + + + + diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents/LINGYUN/Abp/ElsaNext/Agents/AbpElsaNextAgentsModule.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents/LINGYUN/Abp/ElsaNext/Agents/AbpElsaNextAgentsModule.cs new file mode 100644 index 000000000..070175c09 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Agents/LINGYUN/Abp/ElsaNext/Agents/AbpElsaNextAgentsModule.cs @@ -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(elsa => + { + elsa.UseAgents(); + elsa.UseAgentActivities(); + + elsa.UseAgentsApi(); + }); + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.EntityFrameworkCore/FodyWeavers.xml b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.EntityFrameworkCore/FodyWeavers.xml new file mode 100644 index 000000000..5d6962159 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.EntityFrameworkCore/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.EntityFrameworkCore/FodyWeavers.xsd b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.EntityFrameworkCore/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.EntityFrameworkCore/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.EntityFrameworkCore/LINGYUN.Abp.ElsaNext.EntityFrameworkCore.csproj b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.EntityFrameworkCore/LINGYUN.Abp.ElsaNext.EntityFrameworkCore.csproj new file mode 100644 index 000000000..aec49e3a9 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.EntityFrameworkCore/LINGYUN.Abp.ElsaNext.EntityFrameworkCore.csproj @@ -0,0 +1,25 @@ + + + + + + + net10.0 + LINGYUN.Abp.ElsaNext.EntityFrameworkCore + LINGYUN.Abp.ElsaNext.EntityFrameworkCore + false + false + false + + + + + + + + + + + + + diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.EntityFrameworkCore/LINGYUN/Abp/ElsaNext/EntityFrameworkCore/AbpElsaNextEntityFrameworkCoreModule.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.EntityFrameworkCore/LINGYUN/Abp/ElsaNext/EntityFrameworkCore/AbpElsaNextEntityFrameworkCoreModule.cs new file mode 100644 index 000000000..01db2eb08 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.EntityFrameworkCore/LINGYUN/Abp/ElsaNext/EntityFrameworkCore/AbpElsaNextEntityFrameworkCoreModule.cs @@ -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(); + var alterationsFeature = context.Services.GetPreConfigureActions(); + var workflowManagementFeature = context.Services.GetPreConfigureActions(); + var workflowRuntimeFeature = context.Services.GetPreConfigureActions(); + + PreConfigure(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); + }); + }); + }); + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets.Blazor/AbpElsaNextSecretsBlazorModule.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets.Blazor/AbpElsaNextSecretsBlazorModule.cs new file mode 100644 index 000000000..d29a8380e --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets.Blazor/AbpElsaNextSecretsBlazorModule.cs @@ -0,0 +1,49 @@ +using Elsa.Studio.Secrets.Extensions; +using LINGYUN.Abp.ElsaNext.Secrets.Blazor.Navigation; +using LINGYUN.Abp.ElsaNext.Studio.Blazor; +using LINGYUN.Abp.ElsaNext.Studio.Translations.Localization; +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(options => + { + options.AdditionalAssemblies.Add(typeof(Elsa.Studio.Secrets.Feature).Assembly); + }); + + Configure(options => + { + options.MenuContributors.Add(new ElsaStudioSecretsMenuContributor()); + }); + + Configure(options => + { + options.FileSets.AddEmbedded("LINGYUN.Abp.ElsaNext.Secrets.Blazor"); + }); + + Configure(options => + { + options.Resources + .Get() + .AddVirtualJson("/Localization/Resources/SecretsBlazor"); + }); + + var elsaNextStudioBlazoeOptions = context.Services.ExecutePreConfiguredActions(); + + context.Services.AddSecretsModule(elsaNextStudioBlazoeOptions.BackendApiConfig); + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets.Blazor/FodyWeavers.xml b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets.Blazor/FodyWeavers.xml new file mode 100644 index 000000000..5d6962159 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets.Blazor/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets.Blazor/FodyWeavers.xsd b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets.Blazor/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets.Blazor/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets.Blazor/LINGYUN.Abp.ElsaNext.Secrets.Blazor.csproj b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets.Blazor/LINGYUN.Abp.ElsaNext.Secrets.Blazor.csproj new file mode 100644 index 000000000..19bbec564 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets.Blazor/LINGYUN.Abp.ElsaNext.Secrets.Blazor.csproj @@ -0,0 +1,29 @@ + + + + + + + net10.0 + LINGYUN.Abp.ElsaNext.Secrets.Blazor + LINGYUN.Abp.ElsaNext.Secrets.Blazor + false + false + false + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets.Blazor/Localization/Resources/SecretsBlazor/en.json b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets.Blazor/Localization/Resources/SecretsBlazor/en.json new file mode 100644 index 000000000..6a3ff85f2 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets.Blazor/Localization/Resources/SecretsBlazor/en.json @@ -0,0 +1,7 @@ +{ + "culture": "en", + "texts": { + "Menu:Secrets": "Secrets", + "Secrets": "Secrets" + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets.Blazor/Localization/Resources/SecretsBlazor/zh-Hans.json b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets.Blazor/Localization/Resources/SecretsBlazor/zh-Hans.json new file mode 100644 index 000000000..7efeeccb2 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets.Blazor/Localization/Resources/SecretsBlazor/zh-Hans.json @@ -0,0 +1,7 @@ +{ + "culture": "zh-Hans", + "texts": { + "Menu:Secrets": "机密列表", + "Secrets": "机密列表" + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets.Blazor/Navigation/ElsaStudioSecretsMenuContributor.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets.Blazor/Navigation/ElsaStudioSecretsMenuContributor.cs new file mode 100644 index 000000000..7640d0fa4 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets.Blazor/Navigation/ElsaStudioSecretsMenuContributor.cs @@ -0,0 +1,28 @@ +using LINGYUN.Abp.ElsaNext.Studio.Blazor.Navigation; +using LINGYUN.Abp.ElsaNext.Studio.Translations.Localization; +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(); + + 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; + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets.Blazor/_Imports.razor b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets.Blazor/_Imports.razor new file mode 100644 index 000000000..6a4064616 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets.Blazor/_Imports.razor @@ -0,0 +1,5 @@ +@using Microsoft.AspNetCore.Authorization +@using Microsoft.AspNetCore.Components.Web +@using Elsa.Studio.Components +@using Elsa.Studio.Localization +@using MudBlazor diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets/FodyWeavers.xml b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets/FodyWeavers.xml new file mode 100644 index 000000000..5d6962159 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets/FodyWeavers.xsd b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets/LINGYUN.Abp.ElsaNext.Secrets.csproj b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets/LINGYUN.Abp.ElsaNext.Secrets.csproj new file mode 100644 index 000000000..f04d857e5 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets/LINGYUN.Abp.ElsaNext.Secrets.csproj @@ -0,0 +1,26 @@ + + + + + + + net10.0 + LINGYUN.Abp.ElsaNext.Secrets + LINGYUN.Abp.ElsaNext.Secrets + enable + false + false + false + + + + + + + + + + + + + diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets/LINGYUN/Abp/ElsaNext/Secrets/AbpElsaNextSecretsModule.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets/LINGYUN/Abp/ElsaNext/Secrets/AbpElsaNextSecretsModule.cs new file mode 100644 index 000000000..ee6dfebf7 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Secrets/LINGYUN/Abp/ElsaNext/Secrets/AbpElsaNextSecretsModule.cs @@ -0,0 +1,24 @@ +using Elsa.Extensions; +using Elsa.Features.Services; +using Microsoft.Extensions.Configuration; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.ElsaNext.Secrets; + +[DependsOn( + typeof(AbpElsaNextModule))] +public class AbpElsaNextSecretsModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Configuration.GetSection("Elsa"); + PreConfigure(elsa => + { + elsa.UseSecrets(secret => + { + secret.ConfigureOptions = options => configuration.GetSection("Secrets").Bind(options); + }); + elsa.UseSecretsJavaScript(); + }); + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Server/LINGYUN.Abp.ElsaNext.Server.csproj b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Server/LINGYUN.Abp.ElsaNext.Server.csproj index 29df45ebb..3080d0fe9 100644 --- a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Server/LINGYUN.Abp.ElsaNext.Server.csproj +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Server/LINGYUN.Abp.ElsaNext.Server.csproj @@ -1,4 +1,4 @@ - + @@ -7,16 +7,20 @@ net10.0 LINGYUN.Abp.ElsaNext.Server LINGYUN.Abp.ElsaNext.Server + enable false false false - Enable - - + + + + + + diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Server/LINGYUN/Abp/ElsaNext/Server/AbpElsaNextServerModule.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Server/LINGYUN/Abp/ElsaNext/Server/AbpElsaNextServerModule.cs index 409295960..818e1e517 100644 --- a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Server/LINGYUN/Abp/ElsaNext/Server/AbpElsaNextServerModule.cs +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Server/LINGYUN/Abp/ElsaNext/Server/AbpElsaNextServerModule.cs @@ -1,5 +1,6 @@ -using Elsa.Extensions; +using Elsa.Extensions; using Elsa.Features.Services; +using Microsoft.Extensions.Configuration; using Volo.Abp.Modularity; namespace LINGYUN.Abp.ElsaNext.Server; @@ -9,27 +10,40 @@ public class AbpElsaNextServerModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) { + var configuration = context.Configuration.GetSection("Elsa"); + PreConfigure(elsa => { // see: https://v3.elsaworkflows.io/docs/installation/elsa-server - // Expose Elsa API endpoints. - elsa.UseWorkflowsApi(); - - // Setup a SignalR hub for real-time updates from the server. - elsa.UseRealTimeWorkflows(); + // Use timer activities. + elsa.UseScheduling(); - // Enable C# workflow expressions - elsa.UseCSharp(); + // Enable JavaScript workflow expressions. + elsa.UseJavaScript(); // Enable Liquid workflow expressions. elsa.UseLiquid(); + // Enable C# workflow expressions + elsa.UseCSharp(); + // Enable HTTP activities. - elsa.UseHttp(); + elsa.UseHttp(http => + { + http.ConfigureHttpOptions = options => configuration.GetSection("Http").Bind(options); + }); - // Use timer activities. - elsa.UseScheduling(); + // Expose Elsa API endpoints. + elsa.UseWorkflowsApi(); + + // Operational dashboard API + elsa.UseDashboardApi(); + + if (configuration["Server:EnableRealTimeWorkflows"] != "false") + { + elsa.UseRealTimeWorkflows(); + } // Register custom activities from the application, if any. elsa.AddActivitiesFrom(); diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/AbpElsaNextStudioBlazorModule.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/AbpElsaNextStudioBlazorModule.cs new file mode 100644 index 000000000..97afcca36 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/AbpElsaNextStudioBlazorModule.cs @@ -0,0 +1,97 @@ +using Elsa.Studio.Core.BlazorServer.Extensions; +using Elsa.Studio.Extensions; +using Elsa.Studio.Localization.BlazorServer.Extensions; +using Elsa.Studio.Localization.Models; +using Elsa.Studio.Models; +using Elsa.Studio.Settings.Extensions; +using Elsa.Studio.Shell.Extensions; +using Elsa.Studio.Workflows.Designer.Options; +using Elsa.Studio.Workflows.Extensions; +using LINGYUN.Abp.ElsaNext.Localization; +using LINGYUN.Abp.ElsaNext.Studio.Blazor.Navigation; +using LINGYUN.Abp.ElsaNext.Studio.Translations; +using LINGYUN.Abp.ElsaNext.Studio.Translations.Localization; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor; +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.Studio.Blazor; + +[DependsOn( + typeof(AbpElsaNextModule), + typeof(AbpElsaNextStudioTranslationsModule), + typeof(AbpLocalizationModule), + typeof(AbpUiNavigationModule), + typeof(AbpVirtualFileSystemModule), + typeof(AbpAspNetCoreComponentsWebThemingMudBlazorModule))] +public class AbpElsaNextStudioBlazorModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Configuration.GetSection("Elsa:Studio"); + + PreConfigure(options => + { + options.BackendApiConfig = new BackendApiConfig + { + ConfigureBackendOptions = backend => configuration.GetSection("Backend").Bind(backend), + }; + }); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Configuration.GetSection("Elsa:Studio"); + + Configure(options => + { + options.MenuContributors.Add(new ElsaStudioMenuContributor()); + }); + + Configure(options => + { + options.AdditionalAssemblies.Add(typeof(Elsa.Studio.Settings.Feature).Assembly); + options.AdditionalAssemblies.Add(typeof(Elsa.Studio.Workflows.Feature).Assembly); + }); + + Configure(options => + { + options.FileSets.AddEmbedded("LINGYUN.Abp.ElsaNext.Studio.Blazor"); + }); + + Configure(options => + { + options.Resources + .Get() + .AddBaseTypes(typeof(ElsaNextResource)) + .AddVirtualJson("/Localization/Resources/ElsaStudioBlazor"); + }); + + context.Services.AddCore(options => configuration.GetSection("Presentation").Bind(options)); + context.Services.AddShell(options => configuration.GetSection("Shell").Bind(options)); + + var elsaNextStudioBlazoeOptions = context.Services.ExecutePreConfiguredActions(); + + context.Services.AddRemoteBackend(elsaNextStudioBlazoeOptions.BackendApiConfig); + + context.Services.AddWorkflowsModule(); + context.Services.AddSettingsModule(); + + Configure(configuration.GetSection("DesignerOptions")); + + var abpLocalizationOptions = context.Services.ExecutePreConfiguredActions(); + context.Services.AddLocalizationModule(new LocalizationConfig + { + ConfigureLocalizationOptions = options => + { + options.SupportedCultures = abpLocalizationOptions.Languages.Select(x => x.CultureName).ToArray(); + } + }); + context.Services.AddElsaStudioTranslations(); + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/AbpElsaNextStudioBlazorOptions.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/AbpElsaNextStudioBlazorOptions.cs new file mode 100644 index 000000000..866ba636f --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/AbpElsaNextStudioBlazorOptions.cs @@ -0,0 +1,12 @@ +using Elsa.Studio.Models; + +namespace LINGYUN.Abp.ElsaNext.Studio.Blazor; + +public class AbpElsaNextStudioBlazorOptions +{ + public BackendApiConfig BackendApiConfig { get; set; } + public AbpElsaNextStudioBlazorOptions() + { + BackendApiConfig = new BackendApiConfig(); + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/FodyWeavers.xml b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/FodyWeavers.xml new file mode 100644 index 000000000..5d6962159 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/FodyWeavers.xsd b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/LINGYUN.Abp.ElsaNext.Studio.Blazor.csproj b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/LINGYUN.Abp.ElsaNext.Studio.Blazor.csproj new file mode 100644 index 000000000..d5469c201 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/LINGYUN.Abp.ElsaNext.Studio.Blazor.csproj @@ -0,0 +1,43 @@ + + + + + + + net10.0 + enable + enable + LINGYUN.Abp.ElsaNext.Studio.Blazor + LINGYUN.Abp.ElsaNext.Studio.Blazor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/Localization/Resources/ElsaStudioBlazor/en.json b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/Localization/Resources/ElsaStudioBlazor/en.json new file mode 100644 index 000000000..275e41d10 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/Localization/Resources/ElsaStudioBlazor/en.json @@ -0,0 +1,9 @@ +{ + "culture": "en", + "texts": { + "Menu:ElsaStudio": "Elsa Studio", + "Menu:WorkflowDefinitions": "Workflow Definitions", + "Menu:WorkflowInstances": "Workflow Instances", + "Menu:Settings": "Settings" + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/Localization/Resources/ElsaStudioBlazor/zh-Hans.json b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/Localization/Resources/ElsaStudioBlazor/zh-Hans.json new file mode 100644 index 000000000..e148e3606 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/Localization/Resources/ElsaStudioBlazor/zh-Hans.json @@ -0,0 +1,9 @@ +{ + "culture": "zh-Hans", + "texts": { + "Menu:ElsaStudio": "Elsa 工作室", + "Menu:WorkflowDefinitions": "工作流定义", + "Menu:WorkflowInstances": "工作流实例", + "Menu:Settings": "设置" + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/Navigation/ElsaStudioMenuContributor.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/Navigation/ElsaStudioMenuContributor.cs new file mode 100644 index 000000000..cdb729a7b --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/Navigation/ElsaStudioMenuContributor.cs @@ -0,0 +1,49 @@ +using LINGYUN.Abp.ElsaNext.Studio.Translations.Localization; +using Volo.Abp.UI.Navigation; + +namespace LINGYUN.Abp.ElsaNext.Studio.Blazor.Navigation; + +public class ElsaStudioMenuContributor : IMenuContributor +{ + public Task ConfigureMenuAsync(MenuConfigurationContext context) + { + if (context.Menu.Name == StandardMenus.Main) + { + AddStudioMenu(context); + } + + return Task.CompletedTask; + } + + private static void AddStudioMenu(MenuConfigurationContext context) + { + var l = context.GetLocalizer(); + + var elsaStudioMenu = new ApplicationMenuItem( + ElsaStudioMenus.GroupName, + l["Menu:ElsaStudio"], + icon: "fa fa-diagram-project"); + + elsaStudioMenu.AddItem(new ApplicationMenuItem( + ElsaStudioMenus.GroupName + ".WorkflowDefinitions", + l["Menu:WorkflowDefinitions"], + url: "/workflows/definitions", + icon: "fa fa-list", + order: 1)); + elsaStudioMenu.AddItem(new ApplicationMenuItem( + ElsaStudioMenus.GroupName + ".WorkflowInstances", + l["Menu:WorkflowInstances"], + url: "/workflows/instances", + icon: "fa fa-cogs", + order: 2)); + + elsaStudioMenu.AddItem(new ApplicationMenuItem( + ElsaStudioMenus.GroupName + ".Settings", + l["Menu:Settings"], + url: "/settings", + icon: "fa fa-gear", + order: 99)); + + context.Menu.AddItem(elsaStudioMenu); + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/Navigation/ElsaStudioMenus.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/Navigation/ElsaStudioMenus.cs new file mode 100644 index 000000000..4116114d7 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/Navigation/ElsaStudioMenus.cs @@ -0,0 +1,6 @@ +namespace LINGYUN.Abp.ElsaNext.Studio.Blazor.Navigation; + +public static class ElsaStudioMenus +{ + public const string GroupName = "ElsaStudio"; +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/Properties/launchSettings.json b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/Properties/launchSettings.json new file mode 100644 index 000000000..c882c2461 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/Properties/launchSettings.json @@ -0,0 +1,26 @@ +{ + "iisSettings": { + "iisExpress": { + "applicationUrl": "http://localhost:34746", + "sslPort": 0 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5130", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/_Imports.razor b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/_Imports.razor new file mode 100644 index 000000000..81f24c47f --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Blazor/_Imports.razor @@ -0,0 +1,12 @@ +@using Microsoft.AspNetCore.Authorization +@using Microsoft.AspNetCore.Components +@using Microsoft.AspNetCore.Components.Rendering +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.JSInterop +@using MudBlazor +@using Elsa.Studio.Components +@using Elsa.Studio.Workflows.Components.WorkflowDefinitionEditor +@using Elsa.Studio.Workflows.Components.WorkflowInstanceViewer +@using Elsa.Studio.Workflows.Components.WorkflowDefinitionList +@using Elsa.Studio.Workflows.Components.WorkflowInstanceList diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/AbpElsaNextStudioDashboardBlazorModule.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/AbpElsaNextStudioDashboardBlazorModule.cs new file mode 100644 index 000000000..a6b1447c1 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/AbpElsaNextStudioDashboardBlazorModule.cs @@ -0,0 +1,61 @@ +using Elsa.Extensions; +using Elsa.Features.Services; +using Elsa.Studio.Dashboard.Extensions; +using LINGYUN.Abp.ElsaNext.Studio.Blazor; +using LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor.Extensions; +using LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor.Navigation; +using LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor.Services; +using LINGYUN.Abp.ElsaNext.Studio.Translations.Localization; +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.Studio.Dashboard.Blazor; + +[DependsOn(typeof(AbpElsaNextStudioBlazorModule))] +public class AbpElsaNextStudioDashboardBlazorModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(elsa => + { + elsa.UseWorkflowRuntimeDashboard(); + }); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Configuration.GetSection("Elsa:Studio"); + + Configure(options => + { + options.AdditionalAssemblies.Add(typeof(AbpElsaNextStudioDashboardBlazorModule).Assembly); + }); + + Configure(options => + { + options.MenuContributors.Add(new ElsaStudioDashboardMenuContributor()); + }); + + Configure(options => + { + options.FileSets.AddEmbedded("LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor"); + }); + Configure(options => + { + options.Resources + .Get() + .AddVirtualJson("/Localization/Resources/ElsaStudioDashboardBlazor"); + }); + + var elsaNextStudioBlazoeOptions = context.Services.ExecutePreConfiguredActions(); + + context.Services.AddDashboardModule(elsaNextStudioBlazoeOptions.BackendApiConfig); + context.Services.AddDashboardWidgets(); + + context.Services.AddScoped(); + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Extensions/DashboardWidgetServiceCollectionExtensions.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Extensions/DashboardWidgetServiceCollectionExtensions.cs new file mode 100644 index 000000000..3e577d00e --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Extensions/DashboardWidgetServiceCollectionExtensions.cs @@ -0,0 +1,18 @@ +using Elsa.Studio.Dashboard.Widgets; +using LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor.Widgets; +using Microsoft.Extensions.DependencyInjection; + +namespace LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor.Extensions; + +public static class DashboardWidgetServiceCollectionExtensions +{ + public static IServiceCollection AddDashboardWidgets(this IServiceCollection services) + { + return services + .AddDashboardWidget("dashboard.workflow.metrics", DashboardWidgetZones.Metrics, 100, "Workflow metrics", payloadKind: "WorkflowInstances") + .AddDashboardWidget("dashboard.needs-attention", DashboardWidgetZones.Findings, 100, "Needs attention") + .AddDashboardWidget("dashboard.workflow.trend", DashboardWidgetZones.Trend, 100, "Workflow trends", payloadKind: "WorkflowTrends") + .AddDashboardWidget("dashboard.workflow.recent-activity", DashboardWidgetZones.Activity, 100, "Recent activity", payloadKind: "RecentActivity") + .AddDashboardWidget("dashboard.workflow.hotspots", DashboardWidgetZones.SecondaryPanels, 100, "Workflow hotspots", payloadKind: "WorkflowHotspots"); + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/FodyWeavers.xml b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/FodyWeavers.xml new file mode 100644 index 000000000..5d6962159 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/FodyWeavers.xsd b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor.csproj b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor.csproj new file mode 100644 index 000000000..2eb8232f1 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor.csproj @@ -0,0 +1,31 @@ + + + + + + + net10.0 + enable + enable + LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor + LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor + + + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Localization/Resources/ElsaStudioDashboardBlazor/en.json b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Localization/Resources/ElsaStudioDashboardBlazor/en.json new file mode 100644 index 000000000..754e8edd1 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Localization/Resources/ElsaStudioDashboardBlazor/en.json @@ -0,0 +1,6 @@ +{ + "culture": "en", + "texts": { + "Menu:Dashboard": "Dashboard" + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Localization/Resources/ElsaStudioDashboardBlazor/zh-Hans.json b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Localization/Resources/ElsaStudioDashboardBlazor/zh-Hans.json new file mode 100644 index 000000000..5a64b49ad --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Localization/Resources/ElsaStudioDashboardBlazor/zh-Hans.json @@ -0,0 +1,6 @@ +{ + "culture": "zh-Hans", + "texts": { + "Menu:Dashboard": "仪表盘" + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Navigation/ElsaStudioDashboardMenuContributor.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Navigation/ElsaStudioDashboardMenuContributor.cs new file mode 100644 index 000000000..41b1da731 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Navigation/ElsaStudioDashboardMenuContributor.cs @@ -0,0 +1,31 @@ +using LINGYUN.Abp.ElsaNext.Studio.Blazor.Navigation; +using LINGYUN.Abp.ElsaNext.Studio.Translations.Localization; +using Volo.Abp.UI.Navigation; + +namespace LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor.Navigation; + +/// +/// Adds the dashboard page to the "ElsaStudio" menu group (created by the Studio.Blazor module; +/// created here too when absent so this module stays self-contained). +/// +public class ElsaStudioDashboardMenuContributor : IMenuContributor +{ + public Task ConfigureMenuAsync(MenuConfigurationContext context) + { + if (context.Menu.Name == StandardMenus.Main) + { + var menu = context.Menu; + var l = context.GetLocalizer(); + + var group = menu.GetMenuItemOrNull(ElsaStudioMenus.GroupName); + group?.AddItem(new ApplicationMenuItem( + ElsaStudioMenus.GroupName + ".Dashboard", + l["Menu:Dashboard"], + url: "/dashboard", + icon: "fa fa-chart-line", + order: 0)); + } + + return Task.CompletedTask; + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Pages/Dashboard.razor b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Pages/Dashboard.razor new file mode 100644 index 000000000..c57663582 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Pages/Dashboard.razor @@ -0,0 +1,92 @@ +@page "/dashboard" +@attribute [Authorize] +@inherits StudioComponentBase +@inject ILocalizer Localizer + + + + + +@Localizer["Dashboard"] + + + + + @Localizer["Dashboard"] + + @BackendLabel + @if (_snapshot != null) + { + var runtime = _snapshot.Overview.Runtime.Status; + @Text.RuntimeStatusLabel(runtime) + } + else + { + @StatusLabel + } + @LastRefreshedLabel + + + + + + @foreach (var option in DashboardRangeMapper.Options) + { + @option.Label + } + + + + + + + + @if (!string.IsNullOrWhiteSpace(_message)) + { + @_message + } + + @if (_loading && _snapshot == null) + { + + @for (var i = 0; i < 6; i++) + { + + + + } + + + } + else if (_snapshot == null) + { + + + @StatusLabel + @(_message ?? @Localizer["Dashboard data is unavailable from the selected backend."]) + + } + else + { + + + + + + + + + + + + + + + + + + + + + } + diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Pages/Dashboard.razor.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Pages/Dashboard.razor.cs new file mode 100644 index 000000000..85015cb15 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Pages/Dashboard.razor.cs @@ -0,0 +1,211 @@ +using Elsa.Studio.Contracts; +using Elsa.Studio.Dashboard.Models; +using Elsa.Studio.Dashboard.Services; +using Elsa.Studio.Dashboard.Widgets; +using Elsa.Studio.Localization; +using LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor.Services; +using Microsoft.AspNetCore.Components; +using MudBlazor; + +namespace LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor.Pages; + +/// +/// Elsa Studio dashboard page (official Elsa.Studio.Dashboard content hosted under +/// /elsa/workflows/dashboard). The dashboard service reads the Elsa dashboard API with the +/// circuit session token; the widgets (metrics/trend/activity/...) come from the official +/// dashboard modules. +/// +public partial class Dashboard : IAsyncDisposable +{ + private CancellationTokenSource? _loadCancellationTokenSource; + private DashboardSnapshot? _snapshot; + private DashboardLoadStatus _status = DashboardLoadStatus.Unavailable; + private string _selectedRange = DashboardRangeKeys.TwentyFourHours; + private string? _message; + private bool _loading; + private bool _disposed; + private bool _subscribedToFeatureInitialized; + private DateTimeOffset? _lastRefreshedAt; + + [Inject] private IDashboardService DashboardService { get; set; } = null!; + [Inject] private IDashboardWidgetRegistry WidgetRegistry { get; set; } = null!; + [Inject] private IEnumerable Widgets { get; set; } = []; + [Inject] private NavigationManager NavigationManager { get; set; } = null!; + [Inject] private IFeatureService FeatureService { get; set; } = null!; + [Inject] private ElsaStudioDashboardText Text { get; set; } = null!; + + private DashboardWidgetContext WidgetContext => new( + _selectedRange, + _loading, + _lastRefreshedAt, + _status, + _message, + _snapshot, + RefreshAsync, + NavigationManager); + + private string BackendLabel + { + get + { + if (_snapshot == null) + return Localizer["Selected backend"]; + + var overview = _snapshot.Overview; + var backendName = string.IsNullOrWhiteSpace(overview.BackendName) ? Localizer["Backend"] : overview.BackendName; + return string.IsNullOrWhiteSpace(overview.EnvironmentName) ? backendName : $"{backendName} / {overview.EnvironmentName}"; + } + } + + private string LastRefreshedLabel => _lastRefreshedAt == null ? Localizer["Not refreshed yet"] : $"{Localizer["Refreshed"]} {Text.RelativeTimestamp(_lastRefreshedAt)}"; + + private string StatusLabel => _status switch + { + DashboardLoadStatus.Unauthorized => Localizer["No access"], + DashboardLoadStatus.BackendDisconnected => Localizer["Backend disconnected"], + DashboardLoadStatus.Failed => Localizer["Refresh failed"], + DashboardLoadStatus.Loaded => Localizer["Loaded"], + _ => Localizer["Dashboard unavailable"] + }; + + private Severity AlertSeverity => _status switch + { + DashboardLoadStatus.Unauthorized => Severity.Warning, + DashboardLoadStatus.Loaded => Severity.Info, + _ => Severity.Error + }; + + private IReadOnlyCollection GetWidgets(string zone) => + Widgets + .Concat(WidgetRegistry.List()) + .DistinctBy(x => x.Id) + .Where(x => x.Zone == zone && x.IsVisible(WidgetContext)) + .OrderBy(x => x.Order) + .ThenBy(x => x.Id, StringComparer.Ordinal) + .ToList(); + + protected override async Task OnInitializedAsync() + { + // Features register their widgets into the (singleton) widget registry during + // InitializeFeaturesAsync; the official host triggers this from its own startup tasks, so + // do it here to be safe when hosting the page standalone in an ABP layout. + try + { + await FeatureService.InitializeFeaturesAsync(); + } + catch (Exception) + { + // Remote feature discovery is unavailable (e.g. backend feature API down); the page + // still renders the DI-registered widgets. + } + await RefreshAsync(); + } + + protected override void OnAfterRender(bool firstRender) + { + if (!firstRender || _disposed) + return; + + FeatureService.Initialized += OnFeatureServiceInitialized; + _subscribedToFeatureInitialized = true; + } + + private void OnFeatureServiceInitialized() + { + _ = RefreshWidgetsAfterFeatureInitializationAsync(); + } + + private async Task RefreshWidgetsAfterFeatureInitializationAsync() + { + if (_disposed) + return; + + try + { + await InvokeAsync(StateHasChanged); + } + catch (InvalidOperationException) when (_disposed) + { + } + } + + private async Task OnRangeChangedAsync(string? range) + { + var selectedRange = DashboardRangeMapper.Normalize(range); + + if (_selectedRange == selectedRange) + return; + + _selectedRange = selectedRange; + await RefreshAsync(); + } + + private async Task RefreshAsync() + { + await LoadAsync(_selectedRange); + } + + private async Task LoadAsync(string range) + { + await CancelCurrentLoadAsync(); + + var cancellationTokenSource = new CancellationTokenSource(); + _loadCancellationTokenSource = cancellationTokenSource; + _loading = true; + _message = null; + + try + { + var result = await DashboardService.LoadAsync(range, cancellationToken: cancellationTokenSource.Token); + _status = result.Status; + + if (result.Snapshot != null) + { + _snapshot = result.Snapshot; + _lastRefreshedAt = DateTimeOffset.UtcNow; + _message = null; + } + else + { + _message = result.Message; + } + } + catch (OperationCanceledException) when (cancellationTokenSource.IsCancellationRequested) + { + } + catch (Exception e) + { + _status = DashboardLoadStatus.Failed; + _message = e.Message; + } + finally + { + if (ReferenceEquals(_loadCancellationTokenSource, cancellationTokenSource)) + { + _loading = false; + _loadCancellationTokenSource = null; + } + + cancellationTokenSource.Dispose(); + } + } + + private async Task CancelCurrentLoadAsync() + { + if (_loadCancellationTokenSource == null) + return; + + await _loadCancellationTokenSource.CancelAsync(); + _loadCancellationTokenSource = null; + } + + public async ValueTask DisposeAsync() + { + _disposed = true; + + if (_subscribedToFeatureInitialized) + FeatureService.Initialized -= OnFeatureServiceInitialized; + + await CancelCurrentLoadAsync(); + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Services/ElsaStudioDashboardText.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Services/ElsaStudioDashboardText.cs new file mode 100644 index 000000000..dd967a3e7 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Services/ElsaStudioDashboardText.cs @@ -0,0 +1,120 @@ +using Elsa.Studio.Dashboard.Models; +using Elsa.Studio.Localization; +using LINGYUN.Abp.ElsaNext.Studio.Translations; +using System.Globalization; + +namespace LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor.Services; + +/// +/// Culture-aware text and metric formatting for the dashboard widgets. The official Elsa Studio +/// dashboard widgets hard-code their English copy and format numbers with the ambient thread +/// culture, so this module renders localized copies that resolve every label through +/// (which follows the ABP UI culture captured on the circuit) and format +/// numbers with that same culture. +/// +public class ElsaStudioDashboardText +{ + private readonly ILocalizer _localizer; + + public ElsaStudioDashboardText(ILocalizer localizer) + { + _localizer = localizer; + } + + /// The UI culture captured at prerender (fallback: current thread culture). + public CultureInfo Culture => CultureInfo.CurrentUICulture; + + /// Localized text for the given key (falls back to the key itself). + public string this[string key] => _localizer[key]; + + /// Localized capability label of the "needs attention" backend capability. + public string CapabilityLabel(DashboardCapabilityStatus? capability) + { + return capability?.Status switch + { + "Available" => _localizer["Available"], + "Unauthorized" => _localizer["No access"], + "NotInstalled" => _localizer["Not installed"], + _ => _localizer["Unavailable"] + }; + } + + /// Localized label of the runtime status chip shown in the dashboard header. + public string RuntimeStatusLabel(string? status) + { + return status switch + { + DashboardRuntimeStatusKeys.AcceptingWork => _localizer["Accepting work"], + DashboardRuntimeStatusKeys.Paused => _localizer["Paused"], + DashboardRuntimeStatusKeys.Draining => _localizer["Draining"], + _ => _localizer["Unavailable"] + }; + } + + /// Humanized caption for the selected dashboard range. + public string RangeCaption(string selectedRange) + { + return selectedRange switch + { + DashboardRangeKeys.OneHour => _localizer["Last hour"], + DashboardRangeKeys.SevenDays => _localizer["Last 7 days"], + _ => _localizer["Last 24 hours"] + }; + } + + public string Count(long value) => value.ToString("N0", Culture); + + public string Count(int value) => Count((long)value); + + public string Duration(TimeSpan? value) + { + if (value == null) + return "N/A"; + + var duration = value.Value; + + if (duration.TotalMilliseconds < 1000) + return $"{duration.TotalMilliseconds:N0} ms"; + + if (duration.TotalMinutes < 1) + return $"{duration.TotalSeconds:N1} s"; + + if (duration.TotalHours < 1) + return $"{duration.TotalMinutes:N1} min"; + + return $"{duration.TotalHours:N1} h"; + } + + public string DateTime(DateTimeOffset? value) + { + return value == null ? "N/A" : value.Value.ToLocalTime().ToString("g", Culture); + } + + public string RelativeTimestamp(DateTimeOffset? value) + { + if (value == null) + return "N/A"; + + var elapsed = DateTimeOffset.UtcNow - value.Value.ToUniversalTime(); + + if (elapsed.TotalSeconds < 60) + return _localizer["Just now"]; + + if (elapsed.TotalMinutes < 60) + return $"{elapsed.TotalMinutes:N0} {_localizer["min ago"]}"; + + if (elapsed.TotalHours < 24) + return $"{elapsed.TotalHours:N0} {_localizer["h ago"]}"; + + return $"{elapsed.TotalDays:N0} {_localizer["d ago"]}"; + } + + /// Trend chart x-axis label for a bucket timestamp. + public string TrendLabel(DateTimeOffset value, string granularity) + { + var local = value.ToLocalTime(); + return granularity == DashboardTrendGranularity.Day + ? local.ToString(Culture.Name.StartsWith("zh", StringComparison.OrdinalIgnoreCase) ? "M月d日" : "MMM d", Culture) + : local.ToString("HH:mm", Culture); + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Widgets/ExecutionTrendWidget.razor b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Widgets/ExecutionTrendWidget.razor new file mode 100644 index 000000000..42e19cb6f --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Widgets/ExecutionTrendWidget.razor @@ -0,0 +1,59 @@ +@using LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor.Services + +@inject ElsaStudioDashboardText Text + +@if (Context.Snapshot != null) +{ + var response = Context.Snapshot.Trend; + + + + @Text["Execution trend"] + + @RangeCaption(response) + + + + @if (Context.Loading) + { + + } + else if (response.Buckets.Count == 0) + { + @Text["No workflow activity in this range."] + } + else + { +
+ +
+ } +
+} + +@code { + [Parameter] public DashboardWidgetContext Context { get; set; } = null!; + [Parameter] public DashboardWidgetDescriptor Descriptor { get; set; } = null!; + + private string RangeCaption(DashboardTrendResponse response) => + $"{Text.DateTime(response.From)} - {Text.DateTime(response.To)}"; + + private string[] Labels(DashboardTrendResponse response) => response.Buckets + .Select(x => Text.TrendLabel(x.From, response.Granularity)) + .ToArray(); + + private List> Series(DashboardTrendResponse response) => + [ + new() { Name = Text["Started"], Data = response.Buckets.Select(x => (double)x.CreatedOrStarted).ToArray() }, + new() { Name = Text["Finished"], Data = response.Buckets.Select(x => (double)x.Finished).ToArray() }, + new() { Name = Text["Faulted"], Data = response.Buckets.Select(x => (double)x.Faulted).ToArray() }, + new() { Name = Text["Suspended"], Data = response.Buckets.Select(x => (double)x.Suspended).ToArray() } + ]; +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Widgets/HotspotsWidget.razor b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Widgets/HotspotsWidget.razor new file mode 100644 index 000000000..07e8b72d0 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Widgets/HotspotsWidget.razor @@ -0,0 +1,49 @@ +@using LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor.Services + +@inject ElsaStudioDashboardText Text + +@if (Context.Snapshot?.Hotspots != null) +{ + var response = Context.Snapshot.Hotspots; + + + + @Text["Workflow hotspots"] + @MetricLabel(response.Metric) + + + @if (response.Items.Count == 0) + { + @Text["No hotspots in this range."] + } + else + { + + + @Text["Workflow"] + @Text["Value"] + @Text["Avg duration"] + + + + + @DisplayName(context) + @context.DefinitionId + + + @Text.Count(context.Value) + @Text.Duration(context.AverageDuration) + + + } + +} + +@code { + [Parameter] public DashboardWidgetContext Context { get; set; } = null!; + [Parameter] public DashboardWidgetDescriptor Descriptor { get; set; } = null!; + + private string MetricLabel(string? metric) => string.IsNullOrWhiteSpace(metric) ? string.Empty : Text[metric]; + + private static string DisplayName(DashboardHotspot item) => string.IsNullOrWhiteSpace(item.WorkflowName) ? item.DefinitionId : item.WorkflowName!; +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Widgets/NeedsAttentionWidget.razor b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Widgets/NeedsAttentionWidget.razor new file mode 100644 index 000000000..b26457288 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Widgets/NeedsAttentionWidget.razor @@ -0,0 +1,42 @@ +@using LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor.Services + +@inject ElsaStudioDashboardText Text + +@if (Context.Snapshot != null) +{ + var findings = Context.Snapshot.NeedsAttention.Findings; + var capability = Context.Snapshot.NeedsAttention.Capability; + + + + @Text["Needs attention"] + @Text.CapabilityLabel(capability) + + + @if (findings.Count == 0) + { + + @Text["No active findings in the selected range."] + + } + else + { + + @foreach (var item in findings.OrderBy(x => x.Priority).Take(8)) + { + + + @item.Message + @item.Severity + + + } + + } + +} + +@code { + [Parameter] public DashboardWidgetContext Context { get; set; } = null!; + [Parameter] public DashboardWidgetDescriptor Descriptor { get; set; } = null!; +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Widgets/OperationalHealthWidget.razor b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Widgets/OperationalHealthWidget.razor new file mode 100644 index 000000000..0d6146d80 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Widgets/OperationalHealthWidget.razor @@ -0,0 +1,46 @@ +@using LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor.Services + +@inject ElsaStudioDashboardText Text + +@if (Context.Snapshot != null) +{ + var metrics = Context.Snapshot.Overview.WorkflowInstances; + var healthMetrics = new[] + { + new HealthMetric(Text["Running"], Text.Count(metrics.Running), Text["Current active instances"], Icons.Material.Outlined.PlayCircle, Color.Info), + new HealthMetric(Text["Completed"], Text.Count(metrics.Completed), Text.RangeCaption(Context.SelectedRange), Icons.Material.Outlined.CheckCircle, Color.Success), + new HealthMetric(Text["Faulted"], Text.Count(metrics.Faulted), Text.RangeCaption(Context.SelectedRange), Icons.Material.Outlined.ErrorOutline, metrics.Faulted > 0 ? Color.Error : Color.Default), + new HealthMetric(Text["Suspended"], Text.Count(metrics.Suspended), Text["Current suspended instances"], Icons.Material.Outlined.PauseCircle, metrics.Suspended > 0 ? Color.Warning : Color.Default), + new HealthMetric(Text["Avg duration"], Text.Duration(metrics.AverageDuration), Text.RangeCaption(Context.SelectedRange), Icons.Material.Outlined.Timer, Color.Default), + new HealthMetric(Text["Needs attention"], Text.Count(Context.Snapshot.NeedsAttention.Findings.Count), Text["Prioritized findings"], Icons.Material.Outlined.ReportProblem, Context.Snapshot.NeedsAttention.Findings.Count > 0 ? Color.Warning : Color.Success) + }; + +
+ @Text["Operational health"] + +
+ @foreach (var metric in healthMetrics) + { +
+ @MetricContent(metric) +
+ } +
+
+} + +@code { + [Parameter] public DashboardWidgetContext Context { get; set; } = null!; + [Parameter] public DashboardWidgetDescriptor Descriptor { get; set; } = null!; + + private RenderFragment MetricContent => metric => @ + + @metric.Label + + + @metric.Value + @metric.Caption + ; + + private sealed record HealthMetric(string Label, string Value, string Caption, string Icon, Color Color); +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Widgets/RecentActivityWidget.razor b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Widgets/RecentActivityWidget.razor new file mode 100644 index 000000000..6975eb713 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/Widgets/RecentActivityWidget.razor @@ -0,0 +1,101 @@ +@using LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor.Services +@inject NavigationManager NavigationManager +@inject ElsaStudioDashboardText Text + +@if (Context.Snapshot != null) +{ + var items = Context.Snapshot.RecentActivity.Items; + + + + @Text["Recent workflow activity"] + @items.Count @(items.Count == 1 ? Text["item"] : Text["items"]) + + + + + @Text["Workflow / instance"] + @Text["Status"] + @Text["Sub status"] + @Text["Incidents"] + @Text["Duration"] + @Text["Updated"] + + + + + + @DisplayName(context) + @context.InstanceId + + + + @LocalizedStatus(context.Status) + + + @LocalizedStatus(context.SubStatus) + + @Text.Count(context.IncidentCount) + @Text.Duration(context.Duration) + @Text.RelativeTimestamp(context.UpdatedAt ?? context.FinishedAt ?? context.CreatedAt) + + + + + + + + @Text["No workflow instances changed in this range."] + + + +} + +@code { + [Parameter] public DashboardWidgetContext Context { get; set; } = null!; + [Parameter] public DashboardWidgetDescriptor Descriptor { get; set; } = null!; + + private void OnRowClick(TableRowClickEventArgs args) + { + if (args.Item == null) + return; + + NavigationManager.NavigateTo(InstanceUrl(args.Item)); + } + + // Official deep links target the standalone Studio host routes; this ABP host hosts the + // instance viewer at /elsa/workflows/instances/{id}, so build that link directly. + private static string InstanceUrl(DashboardRecentActivityItem item) => $"workflows/instances/{item.InstanceId}"; + + private string LocalizedStatus(string? status) => string.IsNullOrWhiteSpace(status) ? string.Empty : Text[status]; + + private static string DisplayName(DashboardRecentActivityItem item) => string.IsNullOrWhiteSpace(item.WorkflowName) ? item.DefinitionId : item.WorkflowName!; + + private static string GetRowClass(DashboardRecentActivityItem item, int rowNumber) + { + return item.SubStatus is "Faulted" or "Interrupted" || item.IncidentCount > 0 ? "dashboard-row-warning" : string.Empty; + } + + private static Color StatusColor(string status) + { + return status switch + { + "Running" => Color.Info, + "Finished" or "Completed" => Color.Success, + "Faulted" => Color.Error, + _ => Color.Default + }; + } + + private static Color SubStatusColor(string subStatus) + { + return subStatus switch + { + "Faulted" => Color.Error, + "Interrupted" or "Suspended" => Color.Warning, + "Finished" or "Completed" => Color.Success, + _ => Color.Default + }; + } +} + diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/_Imports.razor b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/_Imports.razor new file mode 100644 index 000000000..ce03c82ed --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/_Imports.razor @@ -0,0 +1,9 @@ +@using Microsoft.AspNetCore.Authorization +@using Microsoft.AspNetCore.Components.Web +@using Elsa.Studio.Components +@using Elsa.Studio.Dashboard.Components +@using Elsa.Studio.Dashboard.Models +@using Elsa.Studio.Dashboard.Services +@using Elsa.Studio.Dashboard.Widgets +@using Elsa.Studio.Localization +@using MudBlazor diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/wwwroot/dashboard.css b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/wwwroot/dashboard.css new file mode 100644 index 000000000..d561acea3 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/wwwroot/dashboard.css @@ -0,0 +1,208 @@ +/* Elsa Studio Dashboard visuals for the localized dashboard page/widgets (this module). + The ABP Blazor theme layout does not load Razor Class Library scoped-css bundles, so these + rules are global (class names are dashboard-specific) and injected by the page via . + Mirrors the official Elsa.Studio.Dashboard / Elsa.Studio.Workflows.Dashboard scoped css. */ + +.dashboard-shell { + width: 100%; + max-width: none; + min-height: 100%; + padding: 24px 32px 32px; + box-sizing: border-box; + background: var(--mud-palette-background); +} + +.dashboard-header { + gap: 12px 16px; + margin-bottom: 14px; +} + +.dashboard-muted { + color: var(--mud-palette-text-secondary); +} + +.dashboard-panel { + border-radius: 8px; + background: var(--mud-palette-surface); +} + +.dashboard-shell .dashboard-panel { + border-radius: 8px; + background: var(--mud-palette-surface); +} + +.dashboard-empty-state { + text-align: center; +} + +.dashboard-metrics { + margin-bottom: 8px; +} + +.dashboard-card-link { + color: inherit; + display: block; + height: 100%; +} + +.dashboard-shell .dashboard-card-link { + color: inherit; + display: block; + height: 100%; +} + +.dashboard-shell .dashboard-metric-card { + min-height: 104px; + height: 100%; + padding: 12px; + transition: border-color 120ms ease, background-color 120ms ease; +} + +.dashboard-shell .dashboard-metric-card-clickable:hover, +.dashboard-shell .dashboard-metric-card-clickable:focus-within { + border-color: var(--mud-palette-primary); + background: rgba(var(--mud-palette-primary-rgb), 0.04); +} + +.dashboard-shell .dashboard-metric-value { + line-height: 1.1; + margin-top: 6px; +} + +.dashboard-main-stack { + margin-top: 8px; +} + +.dashboard-activity-grid { + margin-top: 0; +} + +.dashboard-shell .dashboard-section { + padding: 14px; +} + +.dashboard-shell .dashboard-section-header { + margin-bottom: 10px; +} + +.dashboard-shell .dashboard-chart-panel { + min-height: 320px; +} + +.dashboard-shell .dashboard-finding-list { + margin: -6px; +} + +.dashboard-shell .dashboard-finding-row { + width: 100%; + min-width: 0; +} + +.dashboard-shell .dashboard-finding-row .mud-typography { + min-width: 0; +} + +.dashboard-shell .dashboard-row-warning { + background: rgba(var(--mud-palette-warning-rgb), 0.08); +} + +.dashboard-shell .dashboard-diagnostics-block { + border: 1px solid var(--mud-palette-lines-default); + border-radius: 8px; + padding: 12px; +} + +.dashboard-shell .dashboard-diagnostics-value { + min-height: 28px; +} + +.dashboard-shell .mud-table-cell { + padding: 6px 10px; +} + +/* Operational health metrics (localized copy of the official metrics widget) */ + +.dashboard-operational-health { + margin-bottom: 8px; +} + +.dashboard-operational-health-heading { + margin-bottom: 14px; +} + +.dashboard-operational-health-metrics { + display: grid; + gap: 10px; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); +} + +.dashboard-operational-health-metric { + background: var(--mud-palette-surface); + border: 1px solid var(--mud-palette-lines-default); + border-radius: 8px; + min-width: 0; + padding: 12px 14px; + transition: border-color 120ms ease, background-color 120ms ease; +} + +.dashboard-operational-health-metric:hover, +.dashboard-operational-health-metric:focus-within { + border-color: var(--mud-palette-primary); +} + +.dashboard-operational-health-value { + line-height: 1.1; + margin-top: 8px; +} + +.dashboard-operational-health-caption { + color: var(--mud-palette-text-secondary); +} + +/* Execution trend chart (localized copy of the official trend widget) */ + +.dashboard-trend-chart-frame { + width: 100%; + min-width: 0; + height: 260px; +} + +.dashboard-trend-chart-frame .dashboard-trend-chart { + width: 100% !important; + min-width: 0; +} + +.dashboard-trend-chart-frame .dashboard-trend-chart svg { + width: 100% !important; + max-width: 100%; +} + +.dashboard-trend-chart-frame .mud-chart-legend { + width: 100%; + justify-content: center; + margin-top: 6px; +} + + + +@media (max-width: 768px) { + .dashboard-shell { + padding: 16px; + } + + .dashboard-header { + align-items: flex-start; + gap: 10px; + } + + .dashboard-shell .dashboard-section { + padding: 12px; + } +} + + +@media (max-width: 720px) { + .dashboard-trend-chart-frame { + height: 260px; + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor/AbpElsaNextStudioDiagnosticsOpenTelemetryBlazorModule.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor/AbpElsaNextStudioDiagnosticsOpenTelemetryBlazorModule.cs new file mode 100644 index 000000000..4982b782d --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor/AbpElsaNextStudioDiagnosticsOpenTelemetryBlazorModule.cs @@ -0,0 +1,87 @@ +using Elsa.Diagnostics.OpenTelemetry.Extensions; +using Elsa.Extensions; +using Elsa.Features.Services; +using Elsa.OpenTelemetry.Middleware; +using Elsa.Studio.Diagnostics.OpenTelemetry.Dashboard.Extensions; +using Elsa.Studio.Diagnostics.OpenTelemetry.Extensions; +using Elsa.Workflows.Telemetry; +using LINGYUN.Abp.ElsaNext.Studio.Blazor; +using LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor.Extensions; +using LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor.Navigation; +using LINGYUN.Abp.ElsaNext.Studio.Translations.Localization; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore; +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.Studio.Diagnostics.OpenTelemetry.Blazor; + +[DependsOn( + typeof(AbpAspNetCoreModule), + typeof(AbpElsaNextStudioBlazorModule))] +public class AbpElsaNextStudioDiagnosticsOpenTelemetryBlazorModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(elsa => + { + elsa.UseWorkflows(workflows => + { + workflows.WithDefaultWorkflowExecutionPipeline(pipeline => + pipeline.UseWorkflowExecutionTracing()); + workflows.WithDefaultActivityExecutionPipeline(pipeline => + pipeline.UseActivityExecutionTracing()); + }); + elsa.UseOpenTelemetry(); + elsa.UseOpenTelemetryDiagnostics(); + }); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Configuration.GetSection("Elsa:Studio"); + + Configure(options => + { + options.AdditionalAssemblies.Add(typeof(Elsa.Studio.Diagnostics.OpenTelemetry.Feature).Assembly); + }); + + Configure(options => + { + options.MenuContributors.Add(new ElsaStudioOpenTelemetryMenuContributor()); + }); + + Configure(options => + { + options.FileSets.AddEmbedded("LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor"); + }); + + Configure(options => + { + options.Resources + .Get() + .AddVirtualJson("/Localization/Resources/ElsaStudioOpenTelemetryBlazor"); + }); + + var elsaNextStudioBlazoeOptions = context.Services.ExecutePreConfiguredActions(); + + context.Services.AddOpenTelemetryDiagnosticsModule(elsaNextStudioBlazoeOptions.BackendApiConfig); + context.Services.AddOpenTelemetryDashboardModule(); + context.Services.AddOpenTelemetryDashboardWidget(); + context.Services.AddOpenTelemetry() + .WithTracing(tracing => tracing.AddSource(WorkflowInstrumentation.ActivitySourceName)) + .WithMetrics(metrics => metrics.AddMeter(WorkflowInstrumentation.MeterName)); + + Configure(options => + { + options.EndpointConfigureActions.Add((context) => + { + context.Endpoints.MapOpenTelemetryHub(); + }); + }); + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor/Extensions/OpenTelemetryWidgetServiceCollectionExtensions.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor/Extensions/OpenTelemetryWidgetServiceCollectionExtensions.cs new file mode 100644 index 000000000..82919c87c --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor/Extensions/OpenTelemetryWidgetServiceCollectionExtensions.cs @@ -0,0 +1,20 @@ +using Elsa.Studio.Dashboard.Widgets; +using Elsa.Studio.Diagnostics.OpenTelemetry.Dashboard.UI.Dashboard; +using Microsoft.Extensions.DependencyInjection; + +namespace LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor.Extensions; + +public static class OpenTelemetryWidgetServiceCollectionExtensions +{ + public static IServiceCollection AddOpenTelemetryDashboardWidget(this IServiceCollection services) + { + return services + .AddDashboardWidget( + "diagnostics.open-telemetry", + DashboardWidgetZones.DiagnosticsStatus, + 300, + "OpenTelemetry", + requiredBackendCapability: "OpenTelemetry", + payloadKind: "OpenTelemetry.StorageDiagnostics"); + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor/FodyWeavers.xml b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor/FodyWeavers.xml new file mode 100644 index 000000000..00e1d9a1c --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor/FodyWeavers.xsd b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor.csproj b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor.csproj new file mode 100644 index 000000000..159b69241 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor.csproj @@ -0,0 +1,34 @@ + + + + + + + net10.0 + enable + enable + LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor + LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor/Localization/Resources/ElsaStudioOpenTelemetryBlazor/en.json b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor/Localization/Resources/ElsaStudioOpenTelemetryBlazor/en.json new file mode 100644 index 000000000..53b64a571 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor/Localization/Resources/ElsaStudioOpenTelemetryBlazor/en.json @@ -0,0 +1,7 @@ +{ + "culture": "en", + "texts": { + "Menu:OpenTelemetry": "Open Telemetry", + "OpenTelemetry": "Open Telemetry" + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor/Localization/Resources/ElsaStudioOpenTelemetryBlazor/zh-Hans.json b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor/Localization/Resources/ElsaStudioOpenTelemetryBlazor/zh-Hans.json new file mode 100644 index 000000000..37c1c6529 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor/Localization/Resources/ElsaStudioOpenTelemetryBlazor/zh-Hans.json @@ -0,0 +1,7 @@ +{ + "culture": "zh-Hans", + "texts": { + "Menu:OpenTelemetry": "Open Telemetry", + "OpenTelemetry": "Open Telemetry" + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor/Navigation/ElsaStudioOpenTelemetryMenuContributor.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor/Navigation/ElsaStudioOpenTelemetryMenuContributor.cs new file mode 100644 index 000000000..4991f2027 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor/Navigation/ElsaStudioOpenTelemetryMenuContributor.cs @@ -0,0 +1,27 @@ +using LINGYUN.Abp.ElsaNext.Studio.Blazor.Navigation; +using LINGYUN.Abp.ElsaNext.Studio.Translations.Localization; +using Volo.Abp.UI.Navigation; + +namespace LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor.Navigation; + +public class ElsaStudioOpenTelemetryMenuContributor : IMenuContributor +{ + public Task ConfigureMenuAsync(MenuConfigurationContext context) + { + if (context.Menu.Name == StandardMenus.Main) + { + var menu = context.Menu; + var l = context.GetLocalizer(); + + var group = menu.GetMenuItemOrNull(ElsaStudioMenus.GroupName); + group?.AddItem(new ApplicationMenuItem( + ElsaStudioMenus.GroupName + ".OpenTelemetry", + l["Menu:OpenTelemetry"], + url: "/diagnostics/opentelemetry", + icon: "fa fa-x-ray", + order: 4)); + } + + return Task.CompletedTask; + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor/_Imports.razor b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor/_Imports.razor new file mode 100644 index 000000000..6a4064616 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor/_Imports.razor @@ -0,0 +1,5 @@ +@using Microsoft.AspNetCore.Authorization +@using Microsoft.AspNetCore.Components.Web +@using Elsa.Studio.Components +@using Elsa.Studio.Localization +@using MudBlazor diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/AbpElsaNextStudioDiagnosticsStructuredLogsBlazorModule.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/AbpElsaNextStudioDiagnosticsStructuredLogsBlazorModule.cs new file mode 100644 index 000000000..31d7a6b56 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/AbpElsaNextStudioDiagnosticsStructuredLogsBlazorModule.cs @@ -0,0 +1,74 @@ +using Elsa.Diagnostics.StructuredLogs.Extensions; +using Elsa.Extensions; +using Elsa.Features.Services; +using Elsa.Studio.Diagnostics.StructuredLogs.Dashboard.Extensions; +using Elsa.Studio.Diagnostics.StructuredLogs.Extensions; +using LINGYUN.Abp.ElsaNext.Studio.Blazor; +using LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor.Extensions; +using LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor.Navigation; +using LINGYUN.Abp.ElsaNext.Studio.Translations.Localization; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore; +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.Studio.Diagnostics.StructuredLogs.Blazor; + +[DependsOn( + typeof(AbpAspNetCoreModule), + typeof(AbpElsaNextStudioBlazorModule))] +public class AbpElsaNextStudioDiagnosticsStructuredLogsBlazorModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(elsa => + { + elsa.UseStructuredLogs(); + }); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Configuration.GetSection("Elsa:Studio"); + + Configure(options => + { + // options.AdditionalAssemblies.Add(typeof(Elsa.Studio.Diagnostics.StructuredLogs.Feature).Assembly); + options.AdditionalAssemblies.Add(typeof(AbpElsaNextStudioDiagnosticsStructuredLogsBlazorModule).Assembly); + }); + + Configure(options => + { + options.MenuContributors.Add(new ElsaStudioStructuredLogsMenuContributor()); + }); + + Configure(options => + { + options.FileSets.AddEmbedded("LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor"); + }); + Configure(options => + { + options.Resources + .Get() + .AddVirtualJson("/Localization/Resources/ElsaStudioStructuredLogsBlazor"); + }); + + var elsaNextStudioBlazoeOptions = context.Services.ExecutePreConfiguredActions(); + + context.Services.AddStructuredLogsModule(elsaNextStudioBlazoeOptions.BackendApiConfig); + context.Services.AddStructuredLogsDashboardModule(); + context.Services.AddStructuredLogsDashboardWidget(); + + Configure(options => + { + options.EndpointConfigureActions.Add((context) => + { + context.Endpoints.MapStructuredLogsHub(); + }); + }); + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/Extensions/OpenTelemetryWidgetServiceCollectionExtensions.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/Extensions/OpenTelemetryWidgetServiceCollectionExtensions.cs new file mode 100644 index 000000000..9836b3a9e --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/Extensions/OpenTelemetryWidgetServiceCollectionExtensions.cs @@ -0,0 +1,20 @@ +using Elsa.Studio.Dashboard.Widgets; +using Elsa.Studio.Diagnostics.StructuredLogs.Dashboard.UI.Dashboard; +using Microsoft.Extensions.DependencyInjection; + +namespace LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor.Extensions; + +public static class OpenTelemetryWidgetServiceCollectionExtensions +{ + public static IServiceCollection AddStructuredLogsDashboardWidget(this IServiceCollection services) + { + return services + .AddDashboardWidget( + "diagnostics.structured-logs", + DashboardWidgetZones.DiagnosticsStatus, + 100, + "Structured logs", + requiredBackendCapability: "StructuredLogs", + payloadKind: "Diagnostics.StructuredLogs"); + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/FodyWeavers.xml b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/FodyWeavers.xml new file mode 100644 index 000000000..00e1d9a1c --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/FodyWeavers.xsd b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor.csproj b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor.csproj new file mode 100644 index 000000000..e402b4ebe --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor.csproj @@ -0,0 +1,33 @@ + + + + + + + net10.0 + enable + enable + LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor + LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor + + + + + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/Localization/Resources/ElsaStudioStructuredLogsBlazor/en.json b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/Localization/Resources/ElsaStudioStructuredLogsBlazor/en.json new file mode 100644 index 000000000..f83ce9422 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/Localization/Resources/ElsaStudioStructuredLogsBlazor/en.json @@ -0,0 +1,7 @@ +{ + "culture": "en", + "texts": { + "Menu:StructuredLogs": "Structured logs", + "Logs": "Structured logs" + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/Localization/Resources/ElsaStudioStructuredLogsBlazor/zh-Hans.json b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/Localization/Resources/ElsaStudioStructuredLogsBlazor/zh-Hans.json new file mode 100644 index 000000000..f5026c284 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/Localization/Resources/ElsaStudioStructuredLogsBlazor/zh-Hans.json @@ -0,0 +1,7 @@ +{ + "culture": "zh-Hans", + "texts": { + "Menu:StructuredLogs": "结构化日志", + "Logs": "结构化日志" + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/Navigation/ElsaStudioStructuredLogsMenuContributor.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/Navigation/ElsaStudioStructuredLogsMenuContributor.cs new file mode 100644 index 000000000..051bed750 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/Navigation/ElsaStudioStructuredLogsMenuContributor.cs @@ -0,0 +1,31 @@ +using LINGYUN.Abp.ElsaNext.Studio.Blazor.Navigation; +using LINGYUN.Abp.ElsaNext.Studio.Translations.Localization; +using Volo.Abp.UI.Navigation; + +namespace LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor.Navigation; + +/// +/// Adds the structured-logs page to the "ElsaStudio" menu group (created by the Studio.Blazor +/// module; created here too when absent so this module stays self-contained). +/// +public class ElsaStudioStructuredLogsMenuContributor : IMenuContributor +{ + public Task ConfigureMenuAsync(MenuConfigurationContext context) + { + if (context.Menu.Name == StandardMenus.Main) + { + var menu = context.Menu; + var l = context.GetLocalizer(); + + var group = menu.GetMenuItemOrNull(ElsaStudioMenus.GroupName); + group?.AddItem(new ApplicationMenuItem( + ElsaStudioMenus.GroupName + ".StructuredLogs", + l["Menu:StructuredLogs"], + url: "/diagnostics/structured-logs", + icon: "fa fa-receipt", + order: 4)); + } + + return Task.CompletedTask; + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/Pages/Logs.razor b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/Pages/Logs.razor new file mode 100644 index 000000000..e3508a41f --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/Pages/Logs.razor @@ -0,0 +1,34 @@ +@page "/diagnostics/structured-logs" +@attribute [Authorize] +@inherits StudioComponentBase +@inject ILocalizer Localizer + +@using Elsa.Studio.Diagnostics.StructuredLogs.UI.Pages + + + + + + +@Localizer["Logs"] + +@if (_interactive) +{ + +} + +@code { + private bool _interactive; + + protected override void OnAfterRender(bool firstRender) + { + if (!firstRender || _interactive) + return; + + // The official viewer subscribes to the SignalR live hub on init; mounting it only after + // the circuit is interactive keeps the static SSR prerender from hanging on that first + // frame (same approach as the workflow instance viewer). + _interactive = true; + StateHasChanged(); + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/_Imports.razor b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/_Imports.razor new file mode 100644 index 000000000..6a4064616 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/_Imports.razor @@ -0,0 +1,5 @@ +@using Microsoft.AspNetCore.Authorization +@using Microsoft.AspNetCore.Components.Web +@using Elsa.Studio.Components +@using Elsa.Studio.Localization +@using MudBlazor diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/wwwroot/structuredLogs-overrides.css b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/wwwroot/structuredLogs-overrides.css new file mode 100644 index 000000000..1c75fa7c3 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor/wwwroot/structuredLogs-overrides.css @@ -0,0 +1,51 @@ +/* Overrides for the official structured-logs viewer: rows grow with the message up to a few + clamped lines instead of inflating the whole list (or forcing horizontal scrolling in auto + mode). Loaded after the official structuredLogs.css by the module page. */ + +.structured-logs-surface .structured-log-row { + width: auto; + grid-template-columns: 24px 18px 96px 42px minmax(120px, 190px) minmax(190px, 330px) minmax(120px, 220px) minmax(0, 1fr); +} + +.structured-logs-surface .structured-log-message { + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 4; + overflow: hidden; + overflow-wrap: anywhere; + white-space: pre-wrap; +} + +.structured-log-exception { + max-height: 96px; + overflow: auto; +} + +@media (max-width: 1280px) { + .structured-logs-surface .structured-log-row { + grid-template-columns: 24px 18px 96px 42px minmax(120px, 190px) minmax(160px, 1fr); + } + + .structured-logs-surface .structured-log-source, + .structured-logs-surface .structured-log-category, + .structured-logs-surface .structured-log-correlation { + display: none; + } +} + +@media (max-width: 960px) { + .structured-logs-surface .structured-log-row { + grid-template-columns: 24px 18px 92px 40px minmax(160px, 1fr); + width: auto; + } + + .structured-logs-surface .structured-log-source, + .structured-logs-surface .structured-log-category, + .structured-logs-surface .structured-log-correlation { + display: none; + } + + .structured-log-exception { + grid-column: 5; + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Translations/AbpElsaNextStudioTranslationsModule.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Translations/AbpElsaNextStudioTranslationsModule.cs new file mode 100644 index 000000000..0a1ee0b5c --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Translations/AbpElsaNextStudioTranslationsModule.cs @@ -0,0 +1,37 @@ +using LINGYUN.Abp.ElsaNext.Studio.Translations.Localization; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; + +namespace LINGYUN.Abp.ElsaNext.Studio.Translations; + +/// +/// ABP-hosted localization for the Elsa Studio UI copy. Registers the texts extracted from the +/// official Elsa.Studio.Translations resx (en/zh-Hans only) as ABP virtual-JSON resources, +/// so any ABP server-side consumer can translate Elsa Studio UI copy through the standard .NET +/// IStringLocalizer<ElsaStudioTranslationsResource> API. Also provides +/// , an Elsa ILocalizationProvider bridge that +/// performs translation via that .NET-standard localizer. +/// +[DependsOn(typeof(AbpLocalizationModule))] +public class AbpElsaNextStudioTranslationsModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded("LINGYUN.Abp.ElsaNext.Studio.Translations"); + }); + + Configure(options => + { + options.Resources + .Add("en") + .AddVirtualJson("/Localization/Resources/ElsaStudio"); + }); + + // When this module runs inside a host that renders Elsa Studio on the server (Blazor Server + // integration), Elsa's ILocalizer resolves copy through our ABP-backed provider. + context.Services.AddElsaStudioTranslationsIfNotRegistered(); + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Translations/ElsaStudioLocalizationProvider.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Translations/ElsaStudioLocalizationProvider.cs new file mode 100644 index 000000000..fc047cd93 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Translations/ElsaStudioLocalizationProvider.cs @@ -0,0 +1,23 @@ +using Elsa.Studio.Localization; +using LINGYUN.Abp.ElsaNext.Studio.Translations.Localization; +using Microsoft.Extensions.Localization; + +namespace LINGYUN.Abp.ElsaNext.Studio.Translations; + +public class ElsaStudioLocalizationProvider : ILocalizationProvider +{ + private readonly IStringLocalizer _localizer; + + + public ElsaStudioLocalizationProvider( + IStringLocalizer localizer) + { + _localizer = localizer; + } + + public string? GetTranslation(string key) + { + var localized = _localizer[key]; + return localized.ResourceNotFound ? null : localized.Value; + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Translations/ElsaStudioTranslationsServiceCollectionExtensions.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Translations/ElsaStudioTranslationsServiceCollectionExtensions.cs new file mode 100644 index 000000000..fa4d0a74e --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Translations/ElsaStudioTranslationsServiceCollectionExtensions.cs @@ -0,0 +1,39 @@ +using Elsa.Studio.Localization; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace LINGYUN.Abp.ElsaNext.Studio.Translations; + +/// +/// Registers the Elsa Studio translations infrastructure into an ABP server-side host. +/// +public static class ElsaStudioTranslationsServiceCollectionExtensions +{ + /// + /// Registers the circuit-culture holder and - an + /// Elsa that translates via the ABP-registered + /// IStringLocalizer<ElsaStudioTranslationsResource> - as the + /// (scoped, so the provider can see the per-circuit + /// culture). The registration is additive (TryAdd), so a host that configures its own provider + /// later keeps its own; call instead to force-replace. + /// + public static IServiceCollection AddElsaStudioTranslationsIfNotRegistered(this IServiceCollection services) + { + services.TryAddScoped(); + + return services; + } + + /// + /// Force-registers as the single + /// , removing every other registration (including Elsa's + /// default DefaultLocalizationProvider). + /// + public static IServiceCollection AddElsaStudioTranslations(this IServiceCollection services) + { + services.RemoveAll(); + services.AddScoped(); + + return services; + } +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Translations/FodyWeavers.xml b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Translations/FodyWeavers.xml new file mode 100644 index 000000000..5d6962159 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Translations/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Translations/FodyWeavers.xsd b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Translations/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Translations/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Translations/LINGYUN.Abp.ElsaNext.Studio.Translations.csproj b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Translations/LINGYUN.Abp.ElsaNext.Studio.Translations.csproj new file mode 100644 index 000000000..5fd293f89 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Translations/LINGYUN.Abp.ElsaNext.Studio.Translations.csproj @@ -0,0 +1,29 @@ + + + + + + + net10.0 + enable + enable + LINGYUN.Abp.ElsaNext.Studio.Translations + LINGYUN.Abp.ElsaNext.Studio.Translations + LINGYUN.Abp.ElsaNext.Studio.Translations + ABP-hosted localization for the Elsa Studio UI (en/zh-Hans). Registers the Elsa + Studio UI copy extracted from the official Elsa.Studio.Translations resx as ABP virtual-JSON + resources, so any ABP server-side consumer can translate Elsa Studio copy with the standard + IStringLocalizer API, and exposes an Elsa ILocalizationProvider bridge backed by it. + + + + + + + + + + + + + diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Translations/Localization/ElsaStudioResource.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Translations/Localization/ElsaStudioResource.cs new file mode 100644 index 000000000..b3f08e328 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Translations/Localization/ElsaStudioResource.cs @@ -0,0 +1,14 @@ +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.ElsaNext.Studio.Translations.Localization; + +/// +/// ABP localization resource for the Elsa Studio UI copy. Texts live in +/// Localization/Resources/ElsaStudioTranslations/{en,zh-Hans}.json, extracted from the +/// official Elsa.Studio.Translations resx so server-side consumers translate Elsa Studio +/// copy through the standard .NET IStringLocalizer API (ABP's localization implementation). +/// +[LocalizationResourceName("ElsaStudio")] +public class ElsaStudioResource +{ +} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Translations/Localization/Resources/ElsaStudio/en.json b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Translations/Localization/Resources/ElsaStudio/en.json new file mode 100644 index 000000000..81d7aec89 --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Translations/Localization/Resources/ElsaStudio/en.json @@ -0,0 +1,584 @@ +{ + "culture": "en", + "texts": { + "14 days": "14 days", + "180 days": "180 days", + "30 days": "30 days", + "7 days": "7 days", + "90 days": "90 days", + "A brief description of the agent.": "A brief description of the agent.", + "A brief description of the secret.": "A brief description of the secret.", + "A brief description of the workflow.": "A brief description of the workflow.", + "A brief description of this input.": "A brief description of this input.", + "A brief description of this output.": "A brief description of this output.", + "A custom activity category to assign this workflow-as-an-activity to.": "A custom activity category to assign this workflow-as-an-activity to.", + "A custom category by which to group this input.": "A custom category by which to group this input.", + "A custom category by which to group this output.": "A custom category by which to group this output.", + "A default value.": "A default value.", + "A description about the role and purpose of this agent.": "A description about the role and purpose of this agent.", + "A description about the role and purpose of this input variable.": "A description about the role and purpose of this input variable.", + "A description about the secret.": "A description about the secret.", + "A description of this activity to be displayed in the designer.": "A description of this activity to be displayed in the designer.", + "A technical name for the function": "A technical name for the function that this agent perform. For example: WriteCode", + "Acknowledge Datasource Event": "Acknowledge Datasource Event", + "Activation strategy": "Activation strategy", + "Activities": "Activities", + "Activity": "Activity", + "Activity Instance ID": "Activity Instance ID", + "Add": "Add", + "Add input": "Add input", + "Add output": "Add output", + "Add variable": "Add variable", + "Agent": "Agent", + "Agents": "Agents", + "Aggregation": "Aggregation", + "Allow this workflow to be used as an activity.": "Allow this workflow to be used as an activity.", + "API Key": "API Key", + "API Keys": "API Keys", + "Apply": "Apply", + "Auto-update consuming workflows": "Auto-update consuming workflows", + "Auto-save": "Auto-save", + "Branching": "Branching", + "Break": "Break", + "Bulk actions": "Bulk actions", + "Bulk Dispatch Workflows": "Bulk Dispatch Workflows", + "Cancel": "Cancel", + "Category": "Category", + "Check this option if the variable holds an array of the selected type.": "Check this option if the variable holds an array of the selected type.", + "Clear": "Clear", + "Click to open a new workflow tab": "Click to open a new workflow tab", + "Close": "Close", + "Close workflow": "Close workflow", + "Common": "Common", + "Complete": "Complete", + "Completed": "Completed", + "Composition": "Composition", + "Connection Strings": "Connection Strings", + "Console": "Console", + "Container": "Container", + "Correlate": "Correlate", + "Correlation ID": "Correlation ID", + "Create Agent": "Create Agent", + "Create API Key": "Create API Key", + "Create Secret": "Create Secret", + "Create Service": "Create Service", + "Created": "Created", + "CreatedAt": "CreatedAt", + "Create workflow": "Create workflow", + "Cron": "Cron", + "Dashboard": "Dashboard", + "Date": "Date", + "Date Filters": "Date Filters", + "Decision": "Decision", + "Default configuration": "Default configuration", + "Default value": "Default value", + "Definition ID": "Definition ID", + "Definition version": "Definition version", + "Definition version ID": "Definition version ID", + "Definitions": "Definitions", + "Delay": "Delay", + "Delete": "Delete", + "Description": "Description", + "Details": "Details", + "Dispatch Workflow": "Dispatch Workflow", + "Display name": "Display name", + "Display text": "Display text", + "Download File": "Download File", + "Duration": "Duration", + "Edit": "Edit", + "Email": "Email", + "End": "End", + "Enter a list of possible outcomes for this workflow.": "Enter a list of possible outcomes for this workflow.", + "Event": "Event", + "Events": "Events", + "Excel Parser": "Excel Parser", + "Execution Settings": "Execution Settings", + "Executions": "Executions", + "Expires": "Expires", + "Expires In": "Expires In", + "Export": "Export", + "Export the current workflow as a JSON file": "Export the current workflow as a JSON file.", + "Export this version as a JSON file.": "Export this version as a JSON file.", + "Fault": "Fault", + "Finish": "Finish", + "Finished": "Finished", + "FinishedAt": "FinishedAt", + "Flow": "Flow", + "For": "For", + "Fork (flow)": "Fork (flow)", + "Frequency Penalty": "Frequency Penalty", + "Function": "Function", + "Function Name": "Function Name", + "General": "General", + "Has Incidents": "Has Incidents", + "HTTP": "HTTP", + "HTTP Endpoint": "HTTP Endpoint", + "HTTP File Response": "HTTP File Response", + "HTTP Request": "HTTP Request", + "HTTP Request (flow)": "HTTP Request (flow)", + "HTTP Response": "HTTP Response", + "ID": "ID", + "The ID of the activity.": "The ID of the activity.", + "If": "If", + "Import": "Import", + "Import a JSON file containing workflow data to override the current workflow\u0027s contents.": "Import a JSON file containing workflow data to override the current workflow\u0027s contents.", + "Incident Strategy": "Incident Strategy", + "IncidentCount": "IncidentCount", + "Incidents": "Incidents", + "Info": "Info", + "Information": "Information", + "Input": "Input", + "Input Properties": "Input Properties", + "Input/output": "Input/output", + "Inputs": "Inputs", + "Instances": "Instances", + "int": "int", + "Is": "Is", + "Is After": "Is After", + "Is array": "Is array", + "Is Before": "Is Before", + "Is Not": "Is Not", + "Is on or After": "Is on or After", + "Is on or Before": "Is on or Before", + "Join": "Join", + "Journal": "Journal", + "Latest version": "Latest version", + "Loading": "Loading", + "Log Persistence Strategy": "Log Persistence Strategy", + "Looping": "Looping", + "Manage all the things": "Manage all the things", + "Max Tokens": "Max Tokens", + "Metadata": "Metadata", + "The name of the activity. Named activities can be referenced by other activities.": "The name of the activity. Named activities can be referenced by other activities.", + "Never": "Never", + "No": "No", + "No agents found": "No agents found", + "No API keys found": "No API keys found", + "No inputs": "No inputs", + "No outcomes associated with this execution.": "No outcomes associated with this execution.", + "No output associated with this execution.": "No output associated with this execution.", + "No outputs": "No outputs", + "No secrets found": "No secrets found", + "No services found": "No services found", + "No state associated with this execution.": "No state associated with this execution.", + "No variables": "No variables", + "No workflow definitions found": "No workflow definitions found", + "No workflow instances found": "No workflow instances found", + "object": "object", + "Ok": "Ok", + "Open File": "Open File", + "OpenAIChatCompletion": "OpenAIChatCompletion", + "OpenAITextToImage": "OpenAITextToImage", + "Outcomes": "Outcomes", + "Output": "Output", + "Output Properties": "Output Properties", + "Outputs": "Outputs", + "Parser": "Parser", + "Log Persistence": "Log Persistence", + "Plugins": "Plugins", + "Presence Penalty": "Presence Penalty", + "Primitives": "Primitives", + "Prompt Template": "Prompt Template", + "Properties": "Properties", + "Publish": "Publish", + "Publish Event": "Publish Event", + "Published": "Published", + "Published version": "Published version", + "Publish the current workflow": "Publish the current workflow", + "Read Data from Configuration / Runtime": "Read Data from Configuration / Runtime", + "Read Data from Database": "Read Data from Database", + "Rollback to this version": "Rollback to this version", + "Rows Per Page": "Rows Per Page", + "Run": "Run", + "Run Asynchronously": "Run Asynchronously", + "Run JavaScript": "Run JavaScript", + "Run Python": "Run Python", + "Run Task": "Run Task", + "Run workflow": "Run workflow", + "Save": "Save", + "Save File": "Save File", + "Scheduling": "Scheduling", + "Scope": "Scope", + "Scripting": "Scripting", + "Search": "Search...", + "Search on name, id, definition id, definition version id, correlation id": "Search on name, id, definition id, definition version id, correlation id", + "Search on name, description, id, definition id": "Search on name, description, id, definition id", + "Secret": "Secret", + "Secrets": "Secrets", + "Send Email": "Send Email", + "Services": "Services", + "Set Name": "Set Name", + "Set Output": "Set Output", + "Set Variable": "Set Variable", + "Set Workflow Context Parameter": "Set Workflow Context Parameter", + "Settings": "Settings", + "Show accumulated time": "Show accumulated time", + "Show events scoped to current container": "Show events scoped to current container", + "Show incidents": "Show incidents", + "Show relative time": "Show relative time", + "Showing all events": "Showing all events", + "Showing incidents": "Showing incidents", + "Start": "Start", + "Start at": "Start at", + "Start of workflow": "Start of workflow", + "Started": "Started", + "State": "State", + "Status": "Status", + "Storage": "Storage", + "string": "string", + "Sub status": "Sub status", + "Sub-Workflow": "Sub-Workflow", + "Sub-Workflow Inputs": "Sub-Workflow Inputs", + "Sub-Workflow Outputs": "Sub-Workflow Outputs", + "SubStatus": "SubStatus", + "Switch": "Switch", + "Switch (flow)": "Switch (flow)", + "Task": "Task", + "Temperature": "Temperature", + "The .NET type of the input variable.": "The .NET type of the input variable.", + "The .NET type of the output variable.": "The .NET type of the output variable.", + "The description of the output variable.": "The description of the output variable.", + "The lifetime of this secret": "The lifetime of this secret", + "The maximum number": "The maximum number of tokens to generate. Leave empty for maximum.", + "The name of the agent.": "The name of the agent.", + "The name of the function that the agent will execute.": "The name of the function that the agent will execute.", + "The name of the new agent.": "The name of the new agent.", + "The name of the new secret.": "The name of the new secret.", + "The name of the new workflow.": "The name of the new workflow.", + "The name of the variable.": "The name of the variable.", + "The penalty for repeating": "The penalty for repeating any one token. A higher presence penalty will reduce the likelihood of token repetition, fostering more varied text generation.", + "The penalty for repeating any one token": "The penalty for repeating any one token. A higher frequency penalty will reduce the likelihood of token repetition, fostering more varied text generation.", + "The plugins that the agent can use.": "The plugins that the agent can use.", + "The scope of the secret helps the UI to filter available options from dropdown lists.": "The scope of the secret helps the UI to filter available options from dropdown lists.", + "The secret value.": "The secret value.", + "The services that the agent requires.": "The services that the agent requires.", + "The settings of the service in JSON format.": "The settings of the service in JSON format.", + "The system prompt template that the agent will use.": "The system prompt template that the agent will use.", + "The technical name of the API key.": "The technical name of the API key.", + "The technical name of the input variable": "The technical name of the input variable. When referencing this variable in the prompt template, use this name.", + "The technical name of the input.": "The technical name of the input.", + "The technical name of the service.": "The technical name of the service.", + "The temperature of the model": "The temperature of the model. Higher values mean the model will take more risks. Try 0.1 for results that are very close to what the model was trained on. Try 0.9 for very creative results.", + "The type of the service.": "The type of the service.", + "The user-friendly name of this input.": "The user-friendly name of this input.", + "The user-friendly name of this output.": "The user-friendly name of this output.", + "The value of the API key.": "The value of the API key.", + "There are currently no inputs defined.": "There are currently no inputs defined.", + "There are currently no outputs defined.": "There are currently no outputs defined.", + "There are currently no variables defined.": "There are currently no variables defined.", + "This activity does not have any common properties.": "This activity does not have any common properties.", + "This activity does not have any input properties.": "This activity does not have any input properties.", + "This activity does not have any output properties.": "This activity does not have any output properties.", + "This input is an array": "This input is an array", + "This output is an array": "This output is an array", + "This variable is an array": "This variable is an array", + "Time": "Time", + "Timer": "Timer", + "Top Probability determines": "Top Probability determines the cumulative probability threshold for token selection during text generation. Lower values will result in less diversity, making the model pick from the most probable tokens only. Higher values will allow for more diverse outputs, as the model will consider a wider range of tokens.", + "TopP": "TopP", + "Trigger workflow": "Trigger workflow", + "Type": "Type", + "UI Hint": "UI Hint", + "Unable to make changes as the workflow or application is currently in read-only mode": "Unable to make changes as the workflow or application is currently in read-only mode", + "Unpublish": "Unpublish", + "Unpublish the current workflow": "Unpublish the current workflow.", + "Updated": "Updated", + "UpdatedAt": "UpdatedAt", + "Usable as activity": "Usable as activity", + "Use JSON Response": "Use JSON Response", + "Use this version": "Use this version", + "Used": "Used", + "Used to define the initial instructions": "Used to define the initial instructions or context that the Semantic Kernel agent will use to generate responses. This template guides the agent\u0027s behavior and helps it understand the role it needs to play or the tasks it needs to perform.", + "Value": "Value", + "Variables": "Variables", + "Version": "Version", + "Version history": "Version history", + "View": "View", + "Webhooks": "Webhooks", + "When checked, the workflow will be executed asynchronously.": "When checked, the workflow will be executed asynchronously.", + "When you publish": "When you publish a new version, all of the consuming workflows will be updated to point to the new version of this workflow.", + "Whether to show the description in the designer.": "Whether to show the description in the designer.", + "Workflow Context": "Workflow Context", + "Workflow Context Settings": "Workflow Context Settings", + "Workflow Contexts": "Workflow Contexts", + "Workflow definitions Editor": "Workflow definitions Editor", + "Workflow instances": "Workflow instances", + "The name of the workflow.": "The name of the workflow.", + "Workflows": "Workflows", + "Write Line": "Write Line", + "Yes": "Yes", + "You are running in read-only mode. Workflow operations are disabled": "You are running in read-only mode. Workflow operations are disabled", + "You can find documentation and examples here": "You can find documentation and examples here", + "Incident handling strategy": "Incident handling strategy", + "A checkbox": "A checkbox", + "A code editor": "A code editor", + "A dropdown list": "A dropdown list", + "A JSON editor": "A JSON editor", + "A list of checkboxes": "A list of checkboxes", + "A list of radio buttons": "A list of radio buttons", + "A single line of text input": "A single line of text input", + "A variable picker": "A variable picker", + "A variable with this name already exists in the current scope.": "A variable with this name already exists in the current scope.", + "A workflow definition picker": "A workflow definition picker", + "A workflow output definition picker": "A workflow output definition picker", + "A workflow with this name already exists.": "A workflow with this name already exists.", + "An input for multiple words, like a tagging input": "An input for multiple words, like a tagging input", + "An input with this name already exists.": "An input with this name already exists.", + "An outcome picker": "An outcome picker", + "Are you sure you want to cancel all running workflow instances of this workflow definition?": "Are you sure you want to cancel all running workflow instances of this workflow definition?", + "Are you sure you want to cancel the selected workflow instances?": "Are you sure you want to cancel the selected workflow instances?", + "Are you sure you want to cancel this workflow instance?": "Are you sure you want to cancel this workflow instance?", + "Are you sure you want to delete the selected input?": "Are you sure you want to delete the selected input?", + "Are you sure you want to delete the selected output?": "Are you sure you want to delete the selected output?", + "Are you sure you want to delete the selected variable?": "Are you sure you want to delete the selected variable?", + "Are you sure you want to delete the selected versions?": "Are you sure you want to delete the selected versions?", + "Are you sure you want to delete the selected workflow instances?": "Are you sure you want to delete the selected workflow instances?", + "Are you sure you want to delete the selected workflows?": "Are you sure you want to delete the selected workflows?", + "Are you sure you want to delete this version?": "Are you sure you want to delete this version?", + "Are you sure you want to delete this workflow instance?": "Are you sure you want to delete this workflow instance?", + "Are you sure you want to delete this workflow?": "Are you sure you want to delete this workflow?", + "Are you sure you want to publish the selected workflows?": "Are you sure you want to publish the selected workflows?", + "Are you sure you want to unpublish the selected workflows?": "Are you sure you want to unpublish the selected workflows?", + "Cancel running workflow instances?": "Cancel running workflow instances?", + "Cancel selected workflow instances?": "Cancel selected workflow instances?", + "Cancel workflow instance?": "Cancel workflow instance?", + "Checkbox": "Checkbox", + "Checklist": "Checklist", + "Code editor": "Code editor", + "{0} consuming workflow(s) updated": "{0} consuming workflow(s) updated", + "Create input": "Create input", + "Create output": "Create output", + "Create variable": "Create variable", + "Default": "Default", + "Default Strategy": "Default Strategy", + "Delete selected input?": "Delete selected input?", + "Delete selected output?": "Delete selected output?", + "Delete selected variable?": "Delete selected variable?", + "Delete selected versions": "Delete selected versions", + "Delete selected workflow instances?": "Delete selected workflow instances?", + "Delete selected workflows?": "Delete selected workflows?", + "Delete version": "Delete version", + "Delete workflow instance?": "Delete workflow instance?", + "Delete workflow?": "Delete workflow?", + "Dropdown": "Dropdown", + "Edit output": "Edit output", + "Edit variable": "Edit variable", + "Edit Workflow Definition": "Edit Workflow Definition", + "Enter the expression that will be used to determine the log persistence strategy for this property.": "Enter the expression that will be used to determine the log persistence strategy for this property.", + "Failed to import {0} workflow definitions. Errors: {1}": "Failed to import {0} workflow definitions. Errors: {1}", + "imported successfully.": "imported successfully.", + "Inherit": "Inherit", + "JSON editor": "JSON editor", + "Message": "Message", + "Multi line": "Multi line", + "Multi text": "Multi text", + "Multiple lines of text input": "Multiple lines of text input", + "New workflow": "New workflow", + "No workflows found to import.": "No workflows found to import.", + "No workflows were imported.": "No workflows were imported.", + "None": "None", + "Not executed": "Not executed", + "Not usable as activity.": "Not usable as activity.", + "One workflow consuming a published workflow has been updated": "One workflow consuming a published workflow has been updated", + "One workflow is already published": "One workflow is already published", + "One workflow is already unpublished": "One workflow is already unpublished", + "One workflow is not found": "One workflow is not found", + "One workflow is published": "One workflow is published", + "One workflow is unpublished": "One workflow is unpublished", + "Outcome picker": "Outcome picker", + "Output picker": "Output picker", + "Please enter a name for the input.": "Please enter a name for the input.", + "Please enter a name for the output.": "Please enter a name for the output.", + "Please enter a name for the variable.": "Please enter a name for the variable.", + "Please enter a name for the workflow.": "Please enter a name for the workflow.", + "Publish selected workflows?": "Publish selected workflows?", + "Radio list": "Radio list", + "Readonly": "Readonly", + "Show description": "Show description", + "Single line": "Single line", + "Successfully imported": "Successfully imported", + "Successfully imported 1 workflow definition.": "Successfully imported 1 workflow definition.", + "Successfully imported one instance": "Successfully imported one instance", + "Successfully started workflow": "Successfully started workflow", + "The commit strategy to use that controls when the workflow state should be committed.": "The commit strategy to use that controls when the workflow state should be committed.", + "The ID of the variable.": "The ID of the variable.", + "The read-only workflows will not be affected.": "The read-only workflows will not be affected.", + "The text to display in the designer.": "The text to display in the designer.", + "The workflow cannot be started": "The workflow cannot be started", + "Timestamp": "Timestamp", + "Unpublish selected workflows?": "Unpublish selected workflows?", + "Upload JSON and/or ZIP files containing workflow data to import.": "Upload JSON and/or ZIP files containing workflow data to import.", + "Variable picker": "Variable picker", + "When checked, this activity can trigger this workflow.": "When checked, this activity can trigger this workflow.", + "When checked, this activity is the start of this workflow.": "When checked, this activity is the start of this workflow.", + "Workflow definition picker": "Workflow definition picker", + "workflow definitions.": "workflow definitions.", + "Workflow published": "Workflow published", + "Workflow published with Not Found activities": "Workflow published with Not Found activities", + "Workflow retracted": "Workflow retracted", + "Workflow saved": "Workflow saved", + "Workflow unpublished": "Workflow unpublished", + "Workflow was already published": "Workflow was already published", + "workflows are already published": "workflows are already published", + "workflows are already unpublished": "workflows are already unpublished", + "workflows are not found": "workflows are not found", + "workflows are published": "workflows are published", + "workflows are unpublished": "workflows are unpublished", + "workflows consuming published workflows have been updated": "workflows consuming published workflows have been updated", + "Delete version {0}": "Delete version {0}", + "Successfully imported {0} instances": "Successfully imported {0} instances", + "Successfully imported {0} workflow definitions.": "Successfully imported {0} workflow definitions.", + "{0} workflows are already published": "{0} workflows are already published", + "{0} workflows are already unpublished": "{0} workflows are already unpublished", + "{0} workflows are not found": "{0} workflows are not found", + "{0} workflows are published": "{0} workflows are published", + "{0} workflows are unpublished": "{0} workflows are unpublished", + "{0} workflows consuming published workflows have been updated": "{0} workflows consuming published workflows have been updated", + "{0} {1} imported successfully.": "{0} {1} imported successfully.", + "Failed to import {0} {1}.": "Failed to import {0} {1}.", + "Auto layout": "Auto layout", + "Center": "Center", + "Zoom to fit the screen": "Zoom to fit the screen", + "A function to compute the correlation ID to associate a dispatched workflow with.": "A function to compute the correlation ID to associate a dispatched workflow with.", + "A list of expected outcomes to handle.": "A list of expected outcomes to handle.", + "A list of expected status codes to handle.": "A list of expected status codes to handle.", + "A value that allows to add the Authorization header without validation.": "A value that allows to add the Authorization header without validation.", + "Always allow the creation": "Always allow the creation", + "Branches": "Branches", + "Break out of a loop.": "Break out of a loop.", + "Cancelled": "Cancelled", + "Condition": "Condition", + "Content Type": "Content Type", + "Correlation ID Function": "Correlation ID Function", + "Default Item Input Key": "Default Item Input Key", + "Disable Authorization Header Validation": "Disable Authorization Header Validation", + "Executes javaScript code": "Executes javaScript code", + "Executing": "Executing", + "Expected Status Codes": "Expected Status Codes", + "Method": "Method", + "Mode": "Mode", + "Request Content.": "Request Content.", + "Result": "Result", + "Running": "Running", + "Supported Methods": "Supported Methods", + "The Authorization header value to send with the request. For example: Bearer {some-access-token}": "The Authorization header value to send with the request. For example: Bearer {some-access-token}", + "The content to send with the request. Can be a string, an object, a byte array or a stream.": "The content to send with the request. Can be a string, an object, a byte array or a stream.", + "The content type to use when sending the request.": "The content type to use when sending the request.", + "The data source to use for dispatching the workflows.": "The data source to use for dispatching the workflows.", + "The default key to use for the input name when sending the current item to the dispatched workflow. Will not be used if the Items field contain a list of dictionaries": "The default key to use for the input name when sending the current item to the dispatched workflow. Will not be used if the Items field contain a list of dictionaries", + "The definition ID of the workflows to dispatch.": "The definition ID of the workflows to dispatch.", + "The HTTP method to use when sending the request.": "The HTTP method to use when sending the request.", + "The HTTP methods to accept.": "The HTTP methods to accept.", + "The join mode determines whether this activity should continue as soon as one inbound path comes in (Wait Any), or once all inbound paths have executed (Wait All).": "The join mode determines whether this activity should continue as soon as one inbound path comes in (Wait Any), or once all inbound paths have executed (Wait All).", + "The outcome or set of outcomes to complete this activity with.": "The outcome or set of outcomes to complete this activity with.", + "The path to associate with the workflow.": "The path to associate with the workflow.", + "The switch mode determines whether the first match should be scheduled, or all matches.": "The switch mode determines whether the first match should be scheduled, or all matches.", + "The URL to download the file from.": "The URL to download the file from.", + "Time Duration": "Time Duration", + "Trigger execution at a specific time in the future.": "Trigger execution at a specific time in the future.", + "URL": "URL", + "Validation Result": "Validation Result", + "Workflow Definition": "Workflow Definition", + "Add case": "Add case", + "Data": "Data", + "Label": "Label", + "String7": "Result", + "Zoom to fit": "Zoom to fit", + "Always allow the creation of a new workflow instance.": "Always allow the creation of a new workflow instance.", + "Assign the current value to the specified variable.": "Assign the current value to the specified variable.", + "Code": "Code", + "Controls whether the end step is upper/lowerbound inclusive or exclusive. True (inclusive) by default.": "Controls whether the end step is upper/lowerbound inclusive or exclusive. True (inclusive) by default.", + "Current Value": "Current Value", + "Interval": "Interval", + "Outer Bound Inclusive": "Outer Bound Inclusive", + "Payload": "Payload", + "Step": "Step", + "The end step.": "The end step.", + "The items to iterate through.": "The items to iterate through.", + "The set of values to iterate.": "The set of values to iterate.", + "The start step.": "The start step.", + "The step size. To count down, enter a negative number.": "The step size. To count down, enter a negative number.", + "Variable": "Variable", + "Provider Type": "Provider Type", + "Configuration": "Configuration", + "Exclude": "Exclude", + "Include": "Include", + "Load": "Load", + "Pending": "Pending", + "min ago": "min ago", + "Loaded": "Loaded", + "Operational health": "Operational health", + "Available": "Available", + "Prioritized findings": "Prioritized findings", + "Unavailable": "Unavailable", + "h ago": "h ago", + "No hotspots in this range.": "No hotspots in this range.", + "Faults": "Faults", + "Dashboard unavailable": "Dashboard unavailable", + "Last hour": "Last hour", + "Last 24 hours": "Last 24 hours", + "Current suspended instances": "Current suspended instances", + "Dashboard data is unavailable from the selected backend.": "Dashboard data is unavailable from the selected backend.", + "Execution trend": "Execution trend", + "items": "items", + "Accepting work": "Accepting work", + "Avg duration": "Avg duration", + "Refreshed": "Refreshed", + "Suspended": "Suspended", + "Backend": "Backend", + "Draining": "Draining", + "Last 7 days": "Last 7 days", + "Selected backend": "Selected backend", + "Not installed": "Not installed", + "Paused": "Paused", + "Needs attention": "Needs attention", + "Refresh failed": "Refresh failed", + "Not refreshed yet": "Not refreshed yet", + "No workflow activity in this range.": "No workflow activity in this range.", + "No access": "No access", + "Current active instances": "Current active instances", + "Workflow hotspots": "Workflow hotspots", + "Interrupted": "Interrupted", + "Actions": "Actions", + "Search on Id, Definition Id, Definition Version Id, Correlation Id": "Search on Id, Definition Id, Definition Version Id, Correlation Id", + "Refresh dashboard": "Refresh dashboard", + "Search on Id, Name, Description, Definition Id": "Search on Id, Name, Description, Definition Id", + "Backend disconnected": "Backend disconnected", + "No workflow instances changed in this range.": "No workflow instances changed in this range.", + "Workflow": "Workflow", + "d ago": "d ago", + "item": "item", + "Open workflow instance": "Open workflow instance", + "Just now": "Just now", + "Workflow / instance": "Workflow / instance", + "Name": "Name", + "No active findings in the selected range.": "No active findings in the selected range.", + "Faulted": "Faulted", + "Recent workflow activity": "Recent workflow activity", + "Logs": "Logs", + "To": "To", + "discarded locally": "discarded locally", + "Auto scroll": "Auto scroll", + "Source": "Source", + "Reconnect": "Reconnect", + "Auto": "Auto", + "Copy selected": "Copy selected", + "Structured Logs": "Structured Logs", + "Wrap messages": "Wrap messages", + "The live structured log subscription is disconnected.": "The live structured log subscription is disconnected.", + "Workflow instance": "Workflow instance", + "Open details": "Open details", + "Minimum level": "Minimum level", + "All sources": "All sources", + "Compact rows": "Compact rows", + "Tenant": "Tenant", + "Compact": "Compact", + "Span ID": "Span ID", + "From": "From", + "rows": "rows", + "Text": "Text", + "Trace ID": "Trace ID", + "Copy visible": "Copy visible", + "Wrap": "Wrap" + } +} \ No newline at end of file diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Translations/Localization/Resources/ElsaStudio/zh-Hans.json b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Translations/Localization/Resources/ElsaStudio/zh-Hans.json new file mode 100644 index 000000000..1326b680b --- /dev/null +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Translations/Localization/Resources/ElsaStudio/zh-Hans.json @@ -0,0 +1,584 @@ +{ + "culture": "zh-Hans", + "texts": { + "180 days": "180 天", + "30 days": "30 天", + "7 days": "7 天", + "90 days": "90 天", + "A brief description of the workflow.": "工作流的简要描述。", + "A brief description of the agent.": "代理的简要描述。", + "A default value.": "默认值。", + "Activity": "活动", + "Activity Instance ID": "活动实例ID", + "Add input": "添加输入", + "Add variable": "添加变量", + "Aggregation": "聚合", + "Allow this workflow to be used as an activity.": "允许该工作流作为一个活动。", + "Bulk actions": "批量操作", + "Bulk Dispatch Workflows": "批量调度工作流", + "Cancel": "取消", + "Clear": "清空", + "Click to open a new workflow tab": "点击打开一个新的工作流标签页", + "Close": "关闭", + "Close workflow": "关闭工作流", + "Common": "公共", + "Break": "跳出", + "Composition": "混合", + "Console": "控制台", + "Container": "容器", + "Correlate": "关联", + "Correlation ID": "关联ID", + "Create API Key": "创建 API Key", + "Create Service": "创建 Service", + "CreatedAt": "创建于", + "Cron": "Cron表达式", + "Dashboard": "仪表盘", + "Date": "日期", + "Date Filters": "日期筛选器", + "Decision": "决策", + "Default configuration": "默认配置", + "Default value": "默认值", + "Definition version ID": "工作流定义版本ID", + "Definitions": "工作流定义列表", + "Delete": "删除", + "Description": "描述", + "Dispatch Workflow": "调度工作流", + "Display name": "显示名称", + "Display text": "显示文本", + "Edit": "编辑", + "End": "结束", + "Event": "事件", + "Excel Parser": "Excel 解析器", + "Executions": "Execution 列表", + "Export": "导出", + "Export this version as a JSON file.": "导出该版本到JSON文件", + "Fault": "错误", + "Finish": "完成", + "Finished": "已完成", + "Flow": "Flow", + "14 days": "14 天", + "A brief description of this output.": "输出的简要描述。", + "Add": "添加", + "A brief description of this input.": "输入的简要描述。", + "A brief description of the secret.": "secret的简要描述。", + "Activities": "活动列表", + "Add output": "添加输出", + "Apply": "应用", + "Auto-save": "自动保存", + "Category": "分类", + "Complete": "完成", + "Completed": "已完成", + "Definition ID": "工作流定义ID", + "For": "For", + "Definition version": "工作流定义版本", + "Delay": "延迟", + "Create Secret": "创建 Secret", + "Created": "已创建", + "Create workflow": "创建工作流", + "Details": "详情", + "Download File": "下载文件", + "Branching": "分支", + "Execution Settings": "Execution 设置", + "Duration": "持续时间", + "Email": "邮件", + "Events": "事件列表", + "Export the current workflow as a JSON file": "导出当前工作流的JSON文件", + "ID": "ID", + "Import": "导入", + "If": "If", + "FinishedAt": "完成于", + "Activation strategy": "激活策略", + "Agent": "代理", + "Agents": "代理列表", + "API Key": "API Key", + "Auto-update consuming workflows": "自动更新使用中的工作流", + "API Keys": "接口密钥", + "Connection Strings": "连接字符串", + "Create Agent": "创建代理", + "Enter a list of possible outcomes for this workflow.": "键入此工作流可能的结果列表。", + "HTTP File Response": "HTTP文件响应", + "The ID of the activity.": "此活动的ID。", + "Info": "信息", + "Information": "信息", + "Input Properties": "输入属性列表", + "Input/output": "输入/输出", + "Is After": "在此之后", + "Is Not": "不包含", + "Join": "连接", + "Latest version": "最新版本", + "Loading": "加载中", + "Log Persistence Strategy": "日志持久化策略", + "No": "否", + "No inputs": "输入列表为空", + "No outcomes associated with this execution.": "此执行过程没有任何结果。", + "No variables": "没有变量列表", + "object": "对象(Object)", + "Ok": "确认", + "Open File": "打开文件", + "Outcomes": "结果列表", + "Output": "输出", + "Output Properties": "输出属性", + "Outputs": "输出列表", + "Log Persistence": "日志持久化", + "Metadata": "元数据", + "Properties": "属性列表", + "Publish Event": "发布事件", + "Published": "已发布", + "Publish the current workflow": "发布当前工作流", + "Read Data from Configuration / Runtime": "从配置/运行时读取数据", + "Read Data from Database": "从数据库读取数据", + "Rollback to this version": "回退到此版本", + "Secret": "Secret", + "Send Email": "发送电子邮件", + "Services": "服务", + "Rows Per Page": "每页行数", + "Run": "运行", + "Run Asynchronously": "运行(异步)", + "Run JavaScript": "运行JavaScript", + "Run Python": "运行Python", + "Run workflow": "运行工作流", + "Save File": "保存文件", + "Scope": "范围", + "Settings": "设置列表", + "Start": "开始", + "Started": "已开始", + "State": "状态", + "Storage": "存储库", + "string": "字符串", + "Sub-Workflow Inputs": "子工作流输入列表", + "The name of the new agent.": "新代理的名称。", + "Sub-Workflow Outputs": "子工作流输出列表", + "SubStatus": "子状态", + "Switch": "选择器", + "Switch (flow)": "选择器(流模式)", + "Task": "任务", + "The description of the output variable.": "输出变量的描述。", + "This output is an array": "这个输出是一个数组", + "This input is an array": "这个输入是一个数组", + "This activity does not have any output properties.": "这个活动没有任何输出属性。", + "There are currently no variables defined.": "目前没有定义任何变量。", + "There are currently no inputs defined.": "目前没有定义任何输入。", + "There are currently no outputs defined.": "目前没有定义任何输出。", + "The type of the service.": "服务的类型。", + "Variables": "变量列表", + "Version history": "历史版本", + "View": "视图", + "When you publish": "当您发布一个新版本时,所有的被使用工作流将被更新,以指向该工作流的新版本。", + "Workflow Context": "工作流上下文", + "Workflow Contexts": "工作流上下文列表", + "Workflow definitions Editor": "工作流定义编辑器", + "Workflow instances": "工作流实例", + "The name of the workflow.": "工作流的名称。", + "You are running in read-only mode. Workflow operations are disabled": "您正在以只读模式运行。工作流操作被禁止", + "You can find documentation and examples here": "你可以在这里找到文档和示例", + "Version": "版本", + "Webhooks": "Webhooks", + "When checked, the workflow will be executed asynchronously.": "选中时,工作流将异步执行。", + "Whether to show the description in the designer.": "是否在设计器中显示描述。", + "Workflow Context Settings": "工作流上下文设置", + "Timer": "计时器", + "Time": "时间", + "This variable is an array": "这个变量是一个数组", + "This activity does not have any input properties.": "这个活动没有任何输入属性。", + "The value of the API key.": "API密钥的值。", + "The name of the agent.": "代理的名称。", + "The name of the function that the agent will execute.": "代理将执行的函数的名称。", + "Search": "搜索…", + "Value": "值", + "Write Line": "写行", + "Yes": "是", + "Search on name, id, definition id, definition version id, correlation id": "搜索 名称,id,定义id,定义版本id,相关id", + "Search on name, description, id, definition id": "搜索 名称,描述,id,定义id", + "Set Workflow Context Parameter": "设置工作流上下文参数", + "Set Output": "设置输出", + "Set Variable": "设置变量", + "Showing all events": "显示所有事件", + "Start at": "开始于", + "Status": "状态", + "Sub status": "子状态", + "Sub-Workflow": "子工作流", + "Temperature": "温度", + "The name of the activity. Named activities can be referenced by other activities.": "活动的名称。命名活动后可以被其他活动引用。", + "No output associated with this execution.": "此执行过程没有任何输出。", + "No outputs": "输出列表为空", + "Plugins": "插件列表", + "Publish": "发布", + "Published version": "已发布版本", + "Save": "保存", + "HTTP Endpoint": "HTTP 终节点", + "HTTP Request": "HTTP请求", + "HTTP Request (flow)": "HTTP请求(流模式)", + "HTTP Response": "HTTP响应", + "Input": "输入", + "Import a JSON file containing workflow data to override the current workflow\u0027s contents.": "导入一个包含工作流数据的JSON文件,以覆盖当前工作流的内容。", + "Inputs": "输入列表", + "Instances": "实例列表", + "int": "整型(Int)", + "Is array": "是数组", + "Is Before": "在此之前", + "A description about the role and purpose of this input variable.": "关于此输入变量的角色和目的的描述。", + "A custom activity category to assign this workflow-as-an-activity to.": "分配给此工作流作为活动的自定义活动类别。", + "A custom category by which to group this input.": "用于对此输入进行分组的自定义类别。", + "A custom category by which to group this output.": "用于对此输出进行分组的自定义类别。", + "A description about the secret.": "关于此密钥的描述。", + "A technical name for the function": "此代理执行的功能名称。例如:编写代码(WriteCode)", + "Acknowledge Datasource Event": "确认数据源事件", + "Check this option if the variable holds an array of the selected type.": "如果变量持有选定类型的数组,请勾选此选项。", + "Expires": "过期", + "Expires In": "过期于", + "Fork (flow)": "分支(流)", + "Frequency Penalty": "频率惩罚", + "Function": "函数", + "Function Name": "函数名称", + "General": "常规", + "HTTP": "HTTP", + "Has Incidents": "存在异常事件", + "IncidentCount": "异常事件数量", + "Incident Strategy": "异常事件策略", + "Incidents": "异常事件", + "Is": "是", + "Is on or After": "等于或在此之后", + "Is on or Before": "等于或在此之前", + "No agents found": "未找到任何代理", + "No API keys found": "未找到任何 API keys", + "No secrets found": "未找到任何 secrets", + "No services found": "未找到任何服务", + "No state associated with this execution.": "此执行过程没有任何状态。", + "No workflow instances found": "未找到任何工作流实例", + "No workflow definitions found": "未找到任何工作流定义", + "OpenAIChatCompletion": "OpenAI对话模型(OpenAIChatCompletion)", + "OpenAITextToImage": "OpenAI文本转图像模型(OpenAITextToImage)", + "Parser": "解析器", + "Looping": "循环控制", + "Manage all the things": "管理所有事项", + "Max Tokens": "最大 Tokens 数量", + "Never": "从不", + "Prompt Template": "提示词模版", + "Presence Penalty": "重复惩罚(Presence Penalty)", + "Run Task": "运行Task", + "Set Name": "设置名称", + "Scheduling": "计划控制", + "Secrets": "Secrets列表", + "Show accumulated time": "显示累计时长", + "Show incidents": "显示异常事件列表", + "Showing incidents": "正显示异常事件列表中", + "Start of workflow": "启动工作流", + "The lifetime of this secret": "此 secret 的生命周期", + "The .NET type of the input variable.": "此输入变量的.NET类型", + "The .NET type of the output variable.": "此输出变量的.NET类型", + "The maximum number": "可生成的最大tokens数量。留空时为可用的最大值。", + "The name of the new secret.": "新 secret 的名称。", + "The name of the new workflow.": "新工作流的名称。", + "The name of the variable.": "此变量的名称。", + "The penalty for repeating": "当Token重复时的惩罚。重复惩罚值越高,生成重复Token的可能性就越低,从而促进生成更多样化的文本。", + "The plugins that the agent can use.": "此代理可用的插件列表", + "The technical name of the service.": "此服务的专业名称。", + "The technical name of the input.": "此输入的专业名称。", + "The technical name of the API key.": "此 API key 的专业名称。", + "The technical name of the input variable": "此输入变量的专业名称。在提示模板中引用此变量时,请使用此名称。", + "Unable to make changes as the workflow or application is currently in read-only mode": "无法更改,因为当前工作流或应用处于只读模式。", + "UI Hint": "用户界面提示", + "Type": "类型", + "Trigger workflow": "触发工作流", + "TopP": "TopP", + "Top Probability determines": "TopP 决定了文本生成过程中 Token 选择的累积概率阈值。较低的值会导致多样性减少,使模型仅从最可能的 Token 中选择。较高的值将允许输出更加多样化,因为模型会考虑更广泛的 Token 范围。", + "This activity does not have any common properties.": "此活动没有任何公共属性。", + "The user-friendly name of this output.": "此输出的显示文本。", + "The user-friendly name of this input.": "此输入出的显示文本。", + "Used": "已使用", + "Use this version": "使用此版本", + "Use JSON Response": "启用 JSON Response", + "Usable as activity": "可用作活动", + "UpdatedAt": "更新于", + "Updated": "已更新", + "Unpublish the current workflow": "取消发布当前工作流。", + "Unpublish": "取消发布", + "Workflows": "工作流列表", + "Incident handling strategy": "异常事件处理策略", + "A description of this activity to be displayed in the designer.": "在设计器中显示的此活动的描述。", + "A description about the role and purpose of this agent.": "关于此代理的角色和目的的描述。", + "The services that the agent requires.": "此代理所需的服务列表", + "The secret value.": "此 secret 的值", + "Used to define the initial instructions": "初始指令或上下文,用于定义Semantic Kernel代理如何生成内容。此模板指导代理的行为,并帮助它理解需要扮演的角色或执行的任务。", + "The temperature of the model": "模型的温度。值越高,意味着模型将承担更大的风险。尝试0.1以获得与模型训练内容非常接近的结果。尝试0.9以获得极具创意的结果。", + "Journal": "日志", + "Primitives": "基本元素", + "Scripting": "脚本语言", + "Show events scoped to current container": "显示当前容器范围内的事件", + "Show relative time": "显示相对时间", + "The settings of the service in JSON format.": "服务的设置以JSON格式表示。", + "The system prompt template that the agent will use.": "系统提示模板,代理将使用该模板。", + "The penalty for repeating any one token": "重复任何一个token的惩罚。更高的频率惩罚将减少token重复的可能性,促进更多样化的文本生成。", + "The scope of the secret helps the UI to filter available options from dropdown lists.": "secret的范围可以从下拉列表中筛选可用选项。", + "A code editor": "代码编辑器", + "A dropdown list": "下拉列表", + "A JSON editor": "JSON编辑器", + "A list of checkboxes": "复选框列表", + "A list of radio buttons": "单选按钮列表", + "A single line of text input": "单行文本输入", + "A checkbox": "复选框", + "A variable with this name already exists in the current scope.": "当前作用域中已经存在这个名称的变量。", + "A workflow definition picker": "工作流定义选择器", + "A workflow output definition picker": "工作流输出定义选择器", + "An input with this name already exists.": "这个名称的输入已经存在。", + "Are you sure you want to delete the selected input?": "您确定要删除选定的输入吗?", + "Are you sure you want to delete the selected workflows?": "您确定要删除选定的工作流吗?", + "Are you sure you want to delete this version?": "您确定要删除此版本吗?", + "Are you sure you want to delete this workflow instance?": "您确定要删除此工作流实例吗?", + "Cancel running workflow instances?": "确定取消正在运行的工作流实例?", + "Cancel selected workflow instances?": "确定取消选定的工作流实例?", + "Checkbox": "复选框", + "Create variable": "创建变量", + "Default": "默认", + "Delete selected input?": "确定删除选定的输入?", + "Delete selected workflow instances?": "确认删除选定的工作流实例?", + "Delete selected workflows?": "确定删除选定的工作流?", + "Edit variable": "编辑变量", + "Enter the expression that will be used to determine the log persistence strategy for this property.": "输入将用于确定此属性的日志持久性策略的表达式。", + "Failed to import {0} workflow definitions. Errors: {1}": "导入 {0} 个工作流定义失败。错误:{1}", + "imported successfully.": "导入成功。", + "Inherit": "继承", + "Multi line": "多行", + "Multi text": "多文本", + "Not executed": "未执行", + "None": "无", + "Not usable as activity.": "不能作为活动使用。", + "One workflow consuming a published workflow has been updated": "一个使用已发布工作流的工作流已被更新", + "One workflow is already published": "已经发布了一个工作流", + "Output picker": "输出选择器", + "Please enter a name for the variable.": "请输入这个变量的名称。", + "Please enter a name for the workflow.": "请输入这个工作流的名称。", + "Single line": "单行", + "Successfully imported": "导入成功", + "The text to display in the designer.": "要在设计器中显示的文本。", + "Timestamp": "时间戳", + "When checked, this activity is the start of this workflow.": "当选中时,此活动是此工作流的开始。", + "Workflow definition picker": "工作流定义选择器", + "Workflow published": "工作流已发布", + "Workflow published with Not Found activities": "发布的工作流没有找到活动", + "workflows are unpublished": "工作流已撤销发布", + "workflows consuming published workflows have been updated": "已更新使用当前已发布工作流的其他工作流", + "Delete version {0}": "删除版本 {0}", + "{0} workflows are already published": "{0} 个工作流已发布", + "{0} workflows consuming published workflows have been updated": "已更新 {0} 个使用已发布工作流的工作流", + "{0} {1} imported successfully.": "{0} {1} 导入成功。", + "Are you sure you want to cancel this workflow instance?": "您确定要取消此工作流实例吗?", + "No workflows found to import.": "没有找到要导入的工作流。", + "Delete workflow?": "确定删除工作流?", + "No workflows were imported.": "没有导入任何工作流。", + "Multiple lines of text input": "多行文本输入", + "Successfully imported 1 workflow definition.": "成功导入 1 个工作流定义。", + "Delete version": "删除版本", + "Checklist": "复选框列表", + "A workflow with this name already exists.": "具有此名称的工作流已经存在。", + "Are you sure you want to delete the selected output?": "您确定要删除选定的输出吗?", + "The workflow cannot be started": "无法启动工作流", + "Create input": "创建输入", + "One workflow is published": "发布一个工作流", + "Delete selected versions": "删除选定版本", + "Publish selected workflows?": "发布选定的工作流?", + "Successfully started workflow": "启动工作流成功", + "Edit output": "编辑输出", + "Are you sure you want to cancel the selected workflow instances?": "您确定要取消选定的工作流实例吗?", + "Are you sure you want to delete this workflow?": "您确定要删除此工作流吗?", + "A variable picker": "变量选择器", + "Default Strategy": "默认策略", + "Code editor": "代码编辑器", + "Are you sure you want to cancel all running workflow instances of this workflow definition?": "确定要取消此工作流定义的所有正在运行的工作流实例吗?", + "Are you sure you want to publish the selected workflows?": "您确定要发布选定的工作流吗?", + "Are you sure you want to delete the selected variable?": "您确定要删除选定的变量吗?", + "Create output": "创建输出", + "Delete workflow instance?": "确定删除工作流实例?", + "An outcome picker": "结果选择器", + "Are you sure you want to delete the selected versions?": "您确定要删除选定的版本吗?", + "An input for multiple words, like a tagging input": "多个单词的输入,如标记输入", + "Are you sure you want to delete the selected workflow instances?": "您确定要删除选定的工作流实例吗?", + "Are you sure you want to unpublish the selected workflows?": "您确定要取消选定的工作流吗?", + "Delete selected variable?": "确认删除选定的变量?", + "Please enter a name for the output.": "请输入这个输出的名称。", + "Cancel workflow instance?": "取消工作流实例?", + "Edit Workflow Definition": "编辑工作流定义", + "New workflow": "新工作流", + "One workflow is not found": "没有找到一个工作流", + "Delete selected output?": "确定删除选定的输出?", + "{0} consuming workflow(s) updated": "{0} 个使用中的工作流已更新", + "Dropdown": "下拉", + "Radio list": "单选列表", + "Message": "消息", + "One workflow is unpublished": "撤销发布一个工作流", + "JSON editor": "JSON 编辑器", + "Please enter a name for the input.": "请输入这个输入的名称。", + "Workflow was already published": "工作流已经发布", + "Outcome picker": "结果选择器", + "One workflow is already unpublished": "一个工作流已经撤销发布", + "Readonly": "只读", + "Successfully imported one instance": "成功导入一个实例", + "workflow definitions.": "工作流定义。", + "Show description": "显示描述", + "Workflow retracted": "已撤销工作流", + "The commit strategy to use that controls when the workflow state should be committed.": "要使用的提交策略控制应何时该提交工作流状态。", + "The ID of the variable.": "变量的ID。", + "The read-only workflows will not be affected.": "只读工作流不会受到影响。", + "Upload JSON and/or ZIP files containing workflow data to import.": "上传包含工作流数据的JSON文件或包含工作流数据的JSON文件的压缩包(ZIP)导入。", + "Variable picker": "变量选择器", + "Unpublish selected workflows?": "确定取消发布选定的工作流?", + "When checked, this activity can trigger this workflow.": "当被选中时,此活动可以触发此工作流。", + "Workflow saved": "已保存工作流", + "workflows are not found": "未找到工作流", + "workflows are already published": "工作流已经发布", + "Successfully imported {0} instances": "成功导入 {0} 个实例", + "Workflow unpublished": "已取消发布工作流", + "workflows are already unpublished": "工作流已撤销发布", + "workflows are published": "工作流已发布", + "{0} workflows are published": "{0} 个工作流已发布", + "Failed to import {0} {1}.": "{0} {1} 导入失败。", + "Successfully imported {0} workflow definitions.": "成功导入 {0} 个工作流定义。", + "{0} workflows are unpublished": "{0} 个工作流已取消发布", + "{0} workflows are not found": "{0} 个工作流未找到", + "{0} workflows are already unpublished": "{0} 个工作流已取消发布", + "The join mode determines whether this activity should continue as soon as one inbound path comes in (Wait Any), or once all inbound paths have executed (Wait All).": "连接模式决定这个活动是否应该在一个入站路径进入时就继续(等待任意一个),还是在所有入站路径都执行完毕后才继续(等待全部)。", + "The data source to use for dispatching the workflows.": "用于分派工作流的数据源。", + "The default key to use for the input name when sending the current item to the dispatched workflow. Will not be used if the Items field contain a list of dictionaries": "在将当前项发送到被分派的工作流时用于输入名称的默认键。如果Items字段包含字典列表,则不会使用该键", + "Auto layout": "自动布局", + "Center": "中心", + "Zoom to fit the screen": "缩放适应屏幕", + "A function to compute the correlation ID to associate a dispatched workflow with.": "一个用来计算关联分发的工作流的相关性ID的函数。", + "A list of expected outcomes to handle.": "一个预期结果列表进行处理。", + "A list of expected status codes to handle.": "一个要处理的预期状态码列表。", + "A value that allows to add the Authorization header without validation.": "一个允许在不进行验证的情况下添加授权头的值。", + "Always allow the creation": "始终允许创建", + "Branches": "分支", + "Break out of a loop.": "跳出循环。", + "Cancelled": "已取消", + "Condition": "状态", + "Content Type": "内容形式", + "Correlation ID Function": "相关 ID 函数", + "Default Item Input Key": "默认项目输入键", + "Disable Authorization Header Validation": "禁用授权头验证", + "Executes javaScript code": "执行JavaScript代码", + "Executing": "执行中", + "Expected Status Codes": "预期的状态码", + "Method": "方法", + "Mode": "模式", + "Request Content.": "请求内容。", + "Result": "结果", + "Running": "运行中", + "Supported Methods": "建议方法", + "The Authorization header value to send with the request. For example: Bearer {some-access-token}": "和请求一起发送的授权头值。例如:Bearer {some - access - token}", + "The content to send with the request. Can be a string, an object, a byte array or a stream.": "随请求发送的内容。可以是字符串、对象、字节数组或者流。", + "The content type to use when sending the request.": "发送请求时使用的内容类型。", + "The definition ID of the workflows to dispatch.": "要分派的工作流的定义ID。", + "The HTTP method to use when sending the request.": "发送请求时使用的HTTP方法。", + "The HTTP methods to accept.": "要接受的HTTP方法。", + "The outcome or set of outcomes to complete this activity with.": "完成此活动的结果或结果集。", + "The path to associate with the workflow.": "与工作流关联的路径。", + "The switch mode determines whether the first match should be scheduled, or all matches.": "切换模式决定是应该安排第一个匹配项,还是安排所有匹配项。", + "The URL to download the file from.": "用于下载文件的URL。", + "Time Duration": "持续时间", + "Trigger execution at a specific time in the future.": "在未来特定的时间触发执行。", + "URL": "URL", + "Validation Result": "验证结果", + "Workflow Definition": "工作流定义", + "Add case": "添加示例", + "Data": "数据", + "Label": "标签", + "String7": "结果", + "Zoom to fit": "缩放匹配", + "Always allow the creation of a new workflow instance.": "总是允许创建新的工作流实例。", + "Assign the current value to the specified variable.": "将当前值赋给指定的变量。", + "Code": "代码", + "Controls whether the end step is upper/lowerbound inclusive or exclusive. True (inclusive) by default.": "控制结束步骤是包含还是排除上界/下界。默认为True(包含)。", + "Current Value": "现有赋值", + "Interval": "间隔", + "Outer Bound Inclusive": "包含外部边界", + "Payload": "负载", + "Step": "步", + "The end step.": "终止步骤。", + "The items to iterate through.": "要迭代的项目。", + "The set of values to iterate.": "要迭代的值集合。", + "The start step.": "初始化。", + "The step size. To count down, enter a negative number.": "步长。要倒计时,请输入一个负数。", + "Variable": "变量", + "Exclude": "排除", + "Include": "包括", + "Load": "加载", + "Configuration": "配置", + "Provider Type": "提供者类型", + "Pending": "待处理", + "min ago": "分钟前", + "Loaded": "已加载", + "Operational health": "运行健康", + "Available": "可用", + "Prioritized findings": "优先关注项", + "Unavailable": "不可用", + "h ago": "小时前", + "No hotspots in this range.": "此范围内没有热点数据。", + "Faults": "故障", + "Dashboard unavailable": "仪表盘不可用", + "Last hour": "最近 1 小时", + "Last 24 hours": "最近 24 小时", + "Current suspended instances": "当前挂起实例", + "Dashboard data is unavailable from the selected backend.": "无法从所选后端获取仪表盘数据。", + "Execution trend": "执行趋势", + "items": "项", + "Accepting work": "接收中", + "Avg duration": "平均耗时", + "Refreshed": "刷新于", + "Suspended": "已挂起", + "Backend": "后端", + "Draining": "排空中", + "Last 7 days": "最近 7 天", + "Selected backend": "所选后端", + "Not installed": "未安装", + "Paused": "已暂停", + "Needs attention": "需要注意", + "Refresh failed": "刷新失败", + "Not refreshed yet": "尚未刷新", + "No workflow activity in this range.": "此范围内没有工作流活动。", + "No access": "无权限", + "Current active instances": "当前活动实例", + "Workflow hotspots": "工作流热点", + "Interrupted": "已中断", + "Actions": "操作", + "Search on Id, Definition Id, Definition Version Id, Correlation Id": "按 ID、定义 ID、定义版本 ID、相关性 ID 搜索", + "Refresh dashboard": "刷新仪表盘", + "Search on Id, Name, Description, Definition Id": "按 ID、名称、描述、定义 ID 搜索", + "Backend disconnected": "后端连接断开", + "No workflow instances changed in this range.": "此范围内没有工作流实例发生变化。", + "Workflow": "工作流", + "d ago": "天前", + "item": "项", + "Open workflow instance": "打开工作流实例", + "Just now": "刚刚", + "Workflow / instance": "工作流 / 实例", + "Name": "名称", + "No active findings in the selected range.": "所选范围内没有活动发现项。", + "Faulted": "失败", + "Recent workflow activity": "最近的工作流活动", + "Logs": "日志", + "To": "至", + "discarded locally": "已在本地丢弃", + "Auto scroll": "自动滚动", + "Source": "来源", + "Reconnect": "重新连接", + "Auto": "自动", + "Copy selected": "复制所选", + "Structured Logs": "结构化日志", + "Wrap messages": "自动换行", + "The live structured log subscription is disconnected.": "实时结构化日志订阅已断开。", + "Workflow instance": "工作流实例", + "Open details": "打开详情", + "Minimum level": "最低级别", + "All sources": "所有来源", + "Compact rows": "紧凑行", + "Tenant": "租户", + "Compact": "紧凑", + "Span ID": "跨度 ID", + "From": "从", + "rows": "行", + "Text": "文本", + "Trace ID": "追踪 ID", + "Copy visible": "复制可见", + "Wrap": "换行" + } +} \ No newline at end of file diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext/LINGYUN.Abp.ElsaNext.csproj b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext/LINGYUN.Abp.ElsaNext.csproj index 523c4ec8e..fa07bc599 100644 --- a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext/LINGYUN.Abp.ElsaNext.csproj +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext/LINGYUN.Abp.ElsaNext.csproj @@ -11,17 +11,14 @@ false false - Enable - - diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext/LINGYUN/Abp/ElsaNext/AbpElsaNextAutoMapperProfile.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext/LINGYUN/Abp/ElsaNext/AbpElsaNextAutoMapperProfile.cs deleted file mode 100644 index f6cad9125..000000000 --- a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext/LINGYUN/Abp/ElsaNext/AbpElsaNextAutoMapperProfile.cs +++ /dev/null @@ -1,10 +0,0 @@ -using AutoMapper; - -namespace LINGYUN.Abp.ElsaNext; -public class AbpElsaNextAutoMapperProfile : Profile -{ - public AbpElsaNextAutoMapperProfile() - { - - } -} diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext/LINGYUN/Abp/ElsaNext/AbpElsaNextModule.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext/LINGYUN/Abp/ElsaNext/AbpElsaNextModule.cs index 193c7b789..072e9c653 100644 --- a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext/LINGYUN/Abp/ElsaNext/AbpElsaNextModule.cs +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext/LINGYUN/Abp/ElsaNext/AbpElsaNextModule.cs @@ -1,14 +1,12 @@ -using Elsa.Common.Features; -using Elsa.Common.Multitenancy; +using Elsa.Common.Multitenancy; using Elsa.Extensions; using Elsa.Features.Services; -using Elsa.Tenants.Extensions; +using Elsa.ShellFeatures; using Elsa.Workflows; using LINGYUN.Abp.ElsaNext.Localization; using LINGYUN.Abp.ElsaNext.Multitenancy; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using Volo.Abp.AutoMapper; using Volo.Abp.Features; using Volo.Abp.Json; using Volo.Abp.Localization; @@ -18,7 +16,6 @@ using Volo.Abp.Threading; namespace LINGYUN.Abp.ElsaNext; [DependsOn( - typeof(AbpAutoMapperModule), typeof(AbpFeaturesModule), typeof(AbpThreadingModule), typeof(AbpJsonModule))] @@ -34,19 +31,7 @@ public class AbpElsaNextModule : AbpModule { elsa .AddActivitiesFrom() - .AddWorkflowsFrom() - .UseTenants(tenants => - { - tenants.ConfigureMultitenancy(options => - { - options.TenantResolverPipelineBuilder.Append(); - }); - }); - - elsa.Configure(feature => - { - feature.UseTenantsProvider(); - }); + .AddWorkflowsFrom(); elsaModule.Configure(elsa); });