117 changed files with 4814 additions and 300 deletions
@ -0,0 +1,64 @@ |
|||
@using System.Globalization |
|||
@using Microsoft.Extensions.Hosting |
|||
@using Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme.Bundling |
|||
@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Bundling |
|||
@using Volo.Abp.Localization |
|||
@inject IHostEnvironment Env |
|||
@{ |
|||
var rtl = CultureHelper.IsRtl ? "rtl" : string.Empty; |
|||
} |
|||
|
|||
<!DOCTYPE html> |
|||
<html lang="@CultureInfo.CurrentCulture.Name" dir="@rtl"> |
|||
|
|||
<head> |
|||
<meta charset="utf-8" /> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
|||
<title>Elsa Studio (Blazor Server)</title> |
|||
<base href="/" /> |
|||
|
|||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" /> |
|||
<AbpStyles BundleName="@BlazorMudBlazorBasicThemeBundles.Styles.Global" /> |
|||
|
|||
@* Elsa Studio UI styles (RCL static assets) + offline icon-font fallback. *@ |
|||
<link href="/_content/CodeBeam.MudBlazor.Extensions/MudExtensions.min.css" rel="stylesheet" /> |
|||
<link href="/_content/Radzen.Blazor/css/material-base.css" rel="stylesheet" /> |
|||
<link href="/_content/Elsa.Studio.Shell/css/shell.css" rel="stylesheet" /> |
|||
<link href="/_content/Elsa.Studio.Workflows.Designer/designer.css" rel="stylesheet" /> |
|||
<link href="/_content/Elsa.Studio.Workflows.Designer/designer.v1.css" rel="stylesheet" /> |
|||
<link href="/_content/Elsa.Studio.Workflows.Designer/designer.v2.css" rel="stylesheet" /> |
|||
<link href="/elsa-studio-icons.css" rel="stylesheet" /> |
|||
|
|||
<HeadOutlet @rendermode="InteractiveServer" /> |
|||
|
|||
</head> |
|||
<body> |
|||
|
|||
<Routes @rendermode="InteractiveServer" /> |
|||
|
|||
<AbpScripts BundleName="@BlazorMudBlazorBasicThemeBundles.Scripts.Global" /> |
|||
|
|||
@* Monaco + Blazor Server bootstrap (mirrors the official Elsa Studio Server host). *@ |
|||
<script src="/_content/MudBlazor/MudBlazor.min.js"></script> |
|||
<script src="/_content/CodeBeam.MudBlazor.Extensions/MudExtensions.min.js"></script> |
|||
<script src="/_content/Radzen.Blazor/Radzen.Blazor.js"></script> |
|||
<script src="/_content/BlazorMonaco/jsInterop.js"></script> |
|||
<script src="/_content/BlazorMonaco/lib/monaco-editor/min/vs/loader.js"></script> |
|||
<script src="/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.js"></script> |
|||
<script src="/_framework/blazor.web.js"></script> |
|||
|
|||
<div id="blazor-error-ui"> |
|||
@if (Env.IsDevelopment()) |
|||
{ |
|||
<text>An unhandled exception has occurred. See browser dev tools for details.</text> |
|||
} |
|||
else if (Env.IsStaging() || Env.IsProduction()) |
|||
{ |
|||
<text>An error has occurred. This application may no longer respond until reloaded.</text> |
|||
} |
|||
<a href="" class="reload">Reload</a> |
|||
<a class="dismiss">✖</a> |
|||
</div> |
|||
|
|||
</body> |
|||
</html> |
|||
@ -0,0 +1,137 @@ |
|||
@page "/" |
|||
@using Microsoft.AspNetCore.Components.Authorization |
|||
@inject AuthenticationStateProvider AuthenticationStateProvider |
|||
@inject NavigationManager Navigation |
|||
|
|||
<MudGrid Class="mb-3"> |
|||
<MudItem xs="12" md="6"> |
|||
<MudCard Style="height: 100%;"> |
|||
<MudCardContent> |
|||
@if (!IsAuthenticated) |
|||
{ |
|||
<MudText Typo="Typo.h5" Class="mb-2">欢迎使用 Elsa 工作室</MudText> |
|||
<MudText Typo="Typo.body1" Class="mud-text-secondary mb-2"> |
|||
以 Blazor Server 模式集成的 Elsa 工作流工作室:服务端渲染与交互,接口调用在服务端完成,凭据不出浏览器。 |
|||
</MudText> |
|||
<MudButton Href="/Account/Login" Variant="Variant.Filled" Color="Color.Primary" Class="mb-4" |
|||
StartIcon="@Icons.Material.Filled.Login">登录</MudButton> |
|||
} |
|||
else |
|||
{ |
|||
<MudText Typo="Typo.h5" Class="mb-2">欢迎回来</MudText> |
|||
<MudText Typo="Typo.body1" Class="mud-text-secondary mb-3"> |
|||
通过左侧菜单进入 <strong>Elsa 工作室</strong>:管理工作流定义与实例,打开设计器进行可视化编排。 |
|||
</MudText> |
|||
<MudButton StartIcon="@Icons.Material.Filled.AccountTree" Variant="Variant.Text" Color="Color.Primary" |
|||
OnClick="GoDefinitions">工作流定义</MudButton> |
|||
} |
|||
</MudCardContent> |
|||
</MudCard> |
|||
</MudItem> |
|||
<MudItem xs="12" md="6"> |
|||
<MudCard Style="height: 100%;"> |
|||
<MudCardContent> |
|||
<MudText Typo="Typo.h6" Color="Color.Primary" Class="mb-2"> |
|||
<MudIcon Icon="@Icons.Material.Filled.Info" Class="me-1" />关于 |
|||
</MudText> |
|||
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-3"> |
|||
服务端 Blazor Server(SignalR)承载 Elsa Studio;认证走外部 OIDC AuthServer 授权码 + PKCE; |
|||
Studio 页面以 ABP 子页面呈现(路由在菜单中定义)。 |
|||
</MudText> |
|||
<MudDivider Class="my-2" /> |
|||
<MudText Typo="Typo.body2" Class="mud-text-secondary"> |
|||
集成模块:LINGYUN.Abp.ElsaNext.Studio.Blazor |
|||
</MudText> |
|||
</MudCardContent> |
|||
</MudCard> |
|||
</MudItem> |
|||
</MudGrid> |
|||
|
|||
<MudGrid> |
|||
<MudItem xs="12" lg="4"> |
|||
<MudCard Style="height: 100%;"> |
|||
<MudCardHeader> |
|||
<MudText Typo="Typo.h6">More from ABP.IO</MudText> |
|||
</MudCardHeader> |
|||
<MudCardContent Class="pt-0"> |
|||
<MudList T="string" Dense="true"> |
|||
<MudListItem Icon="@Icons.Material.Filled.Check" IconColor="Color.Primary" |
|||
Href="https://github.com/abpframework/abp/releases" Target="_blank"> |
|||
Latest Release Logs |
|||
</MudListItem> |
|||
<MudListItem Icon="@Icons.Material.Filled.Check" IconColor="Color.Primary" |
|||
Href="https://abp.io/video-courses/essentials" Target="_blank"> |
|||
Video Courses |
|||
</MudListItem> |
|||
<MudListItem Icon="@Icons.Material.Filled.Check" IconColor="Color.Primary" |
|||
Href="https://docs.abp.io/en/abp/latest/Samples/Index" Target="_blank"> |
|||
Samples |
|||
</MudListItem> |
|||
<MudListItem Icon="@Icons.Material.Filled.Check" IconColor="Color.Primary" |
|||
Href="https://abp.io/books" Target="_blank"> |
|||
Books |
|||
</MudListItem> |
|||
<MudListItem Icon="@Icons.Material.Filled.Check" IconColor="Color.Primary" |
|||
Href="https://abp.io/faq" Target="_blank"> |
|||
FAQ |
|||
</MudListItem> |
|||
</MudList> |
|||
</MudCardContent> |
|||
</MudCard> |
|||
</MudItem> |
|||
<MudItem xs="12" lg="8"> |
|||
<MudCard Style="height: 100%;"> |
|||
<MudCardHeader> |
|||
<MudText Typo="Typo.h6">Follow us on Social Media</MudText> |
|||
</MudCardHeader> |
|||
<MudCardContent Class="pt-0"> |
|||
<MudGrid Spacing="0"> |
|||
<MudItem xs="12" sm="6"> |
|||
<MudList T="string" Dense="true"> |
|||
<MudListItem Href="https://twitter.com/abpframework" Target="_blank" |
|||
Icon="@Icons.Custom.Brands.Twitter" IconColor="Color.Primary"> |
|||
X.com (Twitter) |
|||
</MudListItem> |
|||
<MudListItem Href="https://community.abp.io/discord" Target="_blank" |
|||
Icon="@Icons.Custom.Brands.Discord" IconColor="Color.Primary"> |
|||
Discord |
|||
</MudListItem> |
|||
<MudListItem Href="https://stackoverflow.com/questions/tagged/abp" Target="_blank" |
|||
Icon="@Icons.Custom.Brands.StackOverflow" IconColor="Color.Primary"> |
|||
Stack Overflow |
|||
</MudListItem> |
|||
</MudList> |
|||
</MudItem> |
|||
<MudItem xs="12" sm="6"> |
|||
<MudList T="string" Dense="true"> |
|||
<MudListItem Href="@("https://www.youtube.com/@Volosoft")" Target="_blank" |
|||
Icon="@Icons.Custom.Brands.YouTube" IconColor="Color.Error"> |
|||
YouTube |
|||
</MudListItem> |
|||
<MudListItem Href="https://www.instagram.com/abpframework/" Target="_blank" |
|||
Icon="@Icons.Custom.Brands.Instagram" IconColor="Color.Secondary"> |
|||
Instagram |
|||
</MudListItem> |
|||
<MudListItem Href="https://github.com/abpframework/abp" Target="_blank" |
|||
Icon="@Icons.Custom.Brands.GitHub" IconColor="Color.Dark"> |
|||
GitHub |
|||
</MudListItem> |
|||
</MudList> |
|||
</MudItem> |
|||
</MudGrid> |
|||
</MudCardContent> |
|||
</MudCard> |
|||
</MudItem> |
|||
</MudGrid> |
|||
|
|||
@code { |
|||
private bool IsAuthenticated; |
|||
|
|||
protected override async Task OnInitializedAsync() |
|||
{ |
|||
var state = await AuthenticationStateProvider.GetAuthenticationStateAsync(); |
|||
IsAuthenticated = state.User.Identity?.IsAuthenticated == true; |
|||
} |
|||
|
|||
private void GoDefinitions() => Navigation.NavigateTo("/elsa/workflows/definitions"); |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
@inject NavigationManager Navigation |
|||
|
|||
@code |
|||
{ |
|||
protected override void OnInitialized() |
|||
{ |
|||
var returnUrl = Uri.EscapeDataString(Navigation.Uri); |
|||
Navigation.NavigateTo($"/Account/Login?returnUrl={returnUrl}", forceLoad: true); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
@using Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme.Themes.Basic |
|||
@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing |
|||
@using Microsoft.Extensions.Options |
|||
@inject IOptions<AbpRouterOptions> RouterOptions |
|||
<Router AppAssembly="typeof(Program).Assembly" AdditionalAssemblies="RouterOptions.Value.AdditionalAssemblies"> |
|||
<Found Context="routeData"> |
|||
<AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)"> |
|||
<NotAuthorized> |
|||
<LoginRedirect /> |
|||
</NotAuthorized> |
|||
</AuthorizeRouteView> |
|||
</Found> |
|||
</Router> |
|||
@ -0,0 +1,12 @@ |
|||
@using System.Net.Http |
|||
@using Microsoft.AspNetCore.Authorization |
|||
@using Microsoft.AspNetCore.Components.Authorization |
|||
@using Microsoft.AspNetCore.Components.Forms |
|||
@using Microsoft.AspNetCore.Components.Routing |
|||
@using Microsoft.AspNetCore.Components.Web |
|||
@using Microsoft.AspNetCore.Components.Web.Virtualization |
|||
@using static Microsoft.AspNetCore.Components.Web.RenderMode |
|||
@using Microsoft.JSInterop |
|||
@using MudBlazor |
|||
@using Volo.Abp.MudBlazorUI |
|||
@using Volo.Abp.MudBlazorUI.Components |
|||
@ -0,0 +1,42 @@ |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.Extensions.Options; |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.MicroService.WorkflowService; |
|||
|
|||
/// <summary>
|
|||
/// Root <see cref="IAuthorizationPolicyProvider"/> for Shells hosting. Elsa 3.8.0-rc2 endpoints
|
|||
/// declare FastEndpoints permission policies named "epPolicy:<EndpointType>" that CShells
|
|||
/// registers inside the shell's own container, so ASP.NET's root policy provider (ABP's) cannot
|
|||
/// resolve them. ABP authenticates /elsa/api with the AuthServer JWT and every validated token
|
|||
/// carries Elsa's "all permissions" claim (see <see cref="WorkflowManagementNextBlazorServerModule"/>),
|
|||
/// so epPolicy names are bridged to that grant; other policy names fall back to the defaults.
|
|||
/// </summary>
|
|||
public sealed class ElsaShellPermissionPolicyProvider : IAuthorizationPolicyProvider |
|||
{ |
|||
// Allow-all policy (always-true assertion; AuthorizationPolicy rejects zero requirements).
|
|||
private static readonly AuthorizationPolicy AllowAll = new AuthorizationPolicyBuilder().RequireAssertion(_ => true).Build(); |
|||
private readonly DefaultAuthorizationPolicyProvider _default = new(Options.Create(new AuthorizationOptions())); |
|||
|
|||
/// <inheritdoc />
|
|||
public Task<AuthorizationPolicy?> GetPolicyAsync(string policyName) |
|||
{ |
|||
if (!policyName.StartsWith("epPolicy:", StringComparison.Ordinal)) |
|||
return _default.GetPolicyAsync(policyName); |
|||
|
|||
var policy = new AuthorizationPolicyBuilder() |
|||
.RequireAuthenticatedUser() |
|||
.RequireAssertion(context => |
|||
context.User.HasClaim(Elsa.PermissionNames.ClaimType, Elsa.PermissionNames.All)) |
|||
.Build(); |
|||
|
|||
return Task.FromResult<AuthorizationPolicy?>(policy); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public Task<AuthorizationPolicy?> GetDefaultPolicyAsync() => Task.FromResult<AuthorizationPolicy?>(AllowAll); |
|||
|
|||
/// <inheritdoc />
|
|||
public Task<AuthorizationPolicy?> GetFallbackPolicyAsync() => Task.FromResult<AuthorizationPolicy?>(AllowAll); |
|||
} |
|||
@ -0,0 +1,130 @@ |
|||
using Consul; |
|||
using Elsa.Studio.Authentication.Abstractions.ComponentProviders; |
|||
using Elsa.Studio.Authentication.Abstractions.Contracts; |
|||
using Elsa.Studio.Authentication.Abstractions.Models; |
|||
using Elsa.Studio.Authentication.OpenIdConnect.BlazorServer.Components; |
|||
using Elsa.Studio.Authentication.OpenIdConnect.BlazorServer.Services; |
|||
using Elsa.Studio.Authentication.OpenIdConnect.Contracts; |
|||
using Elsa.Studio.Authentication.OpenIdConnect.Models; |
|||
using Elsa.Studio.Authentication.OpenIdConnect.Services; |
|||
using Elsa.Studio.Contracts; |
|||
using Microsoft.AspNetCore.Authentication.Cookies; |
|||
using Microsoft.AspNetCore.Authentication.JwtBearer; |
|||
using Microsoft.AspNetCore.Authentication.OpenIdConnect; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.DependencyInjection.Extensions; |
|||
using Polly; |
|||
using System; |
|||
using System.Linq; |
|||
using System.Net.Http; |
|||
|
|||
namespace LINGYUN.Abp.MicroService.WorkflowService.Extensions; |
|||
|
|||
public static class ElsaStudioServiceCollectionExtensions |
|||
{ |
|||
internal const string AnonymousHttpClientName = "Elsa.Studio.Authentication.OpenIdConnect.BlazorServer.Anonymous"; |
|||
|
|||
public static IServiceCollection AddOpenIdConnectAuth( |
|||
this IServiceCollection services, |
|||
Action<OidcOptions> configure, |
|||
Func<IAsyncPolicy<HttpResponseMessage>> configureRetryPolicy = null) |
|||
{ |
|||
var options = new OidcOptions(); |
|||
configure(options); |
|||
services.AddSingleton<StudioAuthenticationProviderRegistration>(new StudioAuthenticationProviderRegistration(StudioAuthenticationProvider.OpenIdConnect)); |
|||
// Ensure we always request the minimal identity scopes.
|
|||
var configuredScopes = options.AuthenticationScopes?.Where(s => !string.IsNullOrWhiteSpace(s)).Distinct(StringComparer.OrdinalIgnoreCase).ToArray() ?? Array.Empty<string>(); |
|||
if (configuredScopes.Length == 0) |
|||
configuredScopes = ["openid", "profile", "offline_access"]; |
|||
options.AuthenticationScopes = configuredScopes; |
|||
|
|||
// Set Blazor Server defaults for callback paths if not explicitly specified.
|
|||
options.CallbackPath ??= "/signin-oidc"; |
|||
options.SignedOutCallbackPath ??= "/signout-callback-oidc"; |
|||
|
|||
// Register core services
|
|||
services.AddHttpContextAccessor(); |
|||
services.AddSingleton(options); |
|||
services.AddScoped<ITokenProvider, ServerTokenProvider>(); |
|||
services.AddScoped<IHttpConnectionOptionsConfigurator, OidcHttpConnectionOptionsConfigurator>(); |
|||
|
|||
// Shared token refresh service used by both session refresh and backend API token acquisition
|
|||
services.AddScoped<TokenRefreshService>(); |
|||
|
|||
// Cookie authentication events for automatic session token refresh (standard ASP.NET Core pattern)
|
|||
services.AddScoped<AuthCookieEvents>(); |
|||
|
|||
// Configure ASP.NET Core authentication with cookie and OIDC
|
|||
services.AddAuthentication(authOptions => |
|||
{ |
|||
authOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; |
|||
authOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; |
|||
}) |
|||
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, cookieOptions => |
|||
{ |
|||
cookieOptions.Cookie.Name = "ElsaStudio.Auth"; |
|||
cookieOptions.Cookie.HttpOnly = true; |
|||
cookieOptions.Cookie.SecurePolicy = Microsoft.AspNetCore.Http.CookieSecurePolicy.Always; |
|||
cookieOptions.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax; |
|||
cookieOptions.ExpireTimeSpan = TimeSpan.FromHours(8); |
|||
cookieOptions.SlidingExpiration = true; |
|||
cookieOptions.LoginPath = "/Account/Login"; |
|||
cookieOptions.AccessDeniedPath = "/Account/Login"; |
|||
|
|||
// Use custom events for automatic token refresh on every request validation
|
|||
cookieOptions.EventsType = typeof(AuthCookieEvents); |
|||
}) |
|||
.AddAbpOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, oidcOptions => |
|||
{ |
|||
oidcOptions.Authority = options.Authority; |
|||
oidcOptions.ClientId = options.ClientId; |
|||
oidcOptions.ClientSecret = options.ClientSecret; |
|||
oidcOptions.ResponseType = options.ResponseType; |
|||
oidcOptions.UsePkce = options.UsePkce; |
|||
oidcOptions.SaveTokens = options.SaveTokens; |
|||
oidcOptions.CallbackPath = options.CallbackPath; |
|||
oidcOptions.SignedOutCallbackPath = options.SignedOutCallbackPath; |
|||
oidcOptions.RequireHttpsMetadata = options.RequireHttpsMetadata; |
|||
oidcOptions.GetClaimsFromUserInfoEndpoint = options.GetClaimsFromUserInfoEndpoint; |
|||
oidcOptions.PushedAuthorizationBehavior = PushedAuthorizationBehavior.Disable; |
|||
|
|||
// Configure scopes
|
|||
oidcOptions.Scope.Clear(); |
|||
foreach (var scope in options.AuthenticationScopes) |
|||
{ |
|||
oidcOptions.Scope.Add(scope); |
|||
} |
|||
|
|||
// Map token response properties to enable token refresh
|
|||
oidcOptions.MapInboundClaims = false; |
|||
|
|||
if (!string.IsNullOrWhiteSpace(options.MetadataAddress)) |
|||
{ |
|||
oidcOptions.MetadataAddress = options.MetadataAddress; |
|||
} |
|||
|
|||
// Configure token validation parameters
|
|||
oidcOptions.TokenValidationParameters = new() |
|||
{ |
|||
NameClaimType = options.NameClaimType, |
|||
RoleClaimType = options.RoleClaimType, |
|||
ValidateIssuer = true |
|||
}; |
|||
}); |
|||
|
|||
// Add authorization services
|
|||
services.AddAuthorizationCore(); |
|||
|
|||
// Use an OIDC-aware unauthorized component that initiates a challenge.
|
|||
services.AddScoped<IUnauthorizedComponentProvider, UnauthorizedComponentProvider<ChallengeToLogin>>(); |
|||
services.AddScoped<ILoginMethodCatalog, DirectOpenIdConnectLoginMethodCatalog>(); |
|||
services.AddScoped<ILoginMethodComponentProvider, DirectOpenIdConnectLoginMethodComponentProvider>(); |
|||
|
|||
// HTTP client for token refresh requests with retry policy
|
|||
var retryPolicy = configureRetryPolicy?.Invoke() ?? Elsa.Studio.Authentication.OpenIdConnect.BlazorServer.Extensions.ServiceCollectionExtensions.DefaultRetryPolicy; |
|||
services.AddHttpClient(AnonymousHttpClientName) |
|||
.AddPolicyHandler(retryPolicy); |
|||
|
|||
return services; |
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
{ |
|||
"culture": "en", |
|||
"texts": { |
|||
"Menu:Logout": "Logout" |
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
{ |
|||
"culture": "zh-Hans", |
|||
"texts": { |
|||
"Menu:Logout": "退出登录" |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using Localization.Resources.AbpUi; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.UI.Navigation; |
|||
|
|||
namespace LINGYUN.Abp.MicroService.WorkflowService.Navigation; |
|||
|
|||
public class UserMenuContributor : IMenuContributor |
|||
{ |
|||
public virtual Task ConfigureMenuAsync(MenuConfigurationContext context) |
|||
{ |
|||
if (context.Menu.Name != StandardMenus.User) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
var uiResource = context.GetLocalizer<AbpUiResource>(); |
|||
|
|||
context.Menu.AddItem(new ApplicationMenuItem("Auth.Logout", uiResource["Menu:Logout"], url: "~/Account/Logout", icon: "fa fa-power-off", order: int.MaxValue - 1000)); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
@page "/Account/Login" |
|||
@model LINGYUN.Abp.MicroService.WorkflowService.Pages.Auth.LoginModel |
|||
@{ |
|||
ViewData["Title"] = "Login"; |
|||
} |
|||
<p>Redirecting to the identity provider…</p> |
|||
@ -0,0 +1,21 @@ |
|||
using Microsoft.AspNetCore.Authentication; |
|||
using Microsoft.AspNetCore.Authentication.OpenIdConnect; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.Mvc.RazorPages; |
|||
|
|||
namespace LINGYUN.Abp.MicroService.WorkflowService.Pages.Auth; |
|||
|
|||
/// <summary>
|
|||
/// Triggers the standard OIDC authorization-code flow: signing in challenges the "oidc" scheme so
|
|||
/// the user is redirected to the external AuthServer, then back to this host's cookie.
|
|||
/// </summary>
|
|||
public class LoginModel : PageModel |
|||
{ |
|||
public IActionResult OnGet(string returnUrl = null) |
|||
{ |
|||
var redirectUri = string.IsNullOrWhiteSpace(returnUrl) ? "/" : returnUrl; |
|||
return Challenge( |
|||
new AuthenticationProperties { RedirectUri = redirectUri }, |
|||
OpenIdConnectDefaults.AuthenticationScheme); |
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
@page "/Account/Logout" |
|||
@model LINGYUN.Abp.MicroService.WorkflowService.Pages.Auth.LogoutModel |
|||
@{ |
|||
ViewData["Title"] = "Logout"; |
|||
} |
|||
<p>Signing out…</p> |
|||
@ -0,0 +1,22 @@ |
|||
using Microsoft.AspNetCore.Authentication; |
|||
using Microsoft.AspNetCore.Authentication.Cookies; |
|||
using Microsoft.AspNetCore.Authentication.OpenIdConnect; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.Mvc.RazorPages; |
|||
|
|||
namespace LINGYUN.Abp.MicroService.WorkflowService.Pages.Auth; |
|||
|
|||
/// <summary>
|
|||
/// Signs out the local cookie and ends the OIDC session at the external AuthServer.
|
|||
/// </summary>
|
|||
public class LogoutModel : PageModel |
|||
{ |
|||
public IActionResult OnGet(string returnUrl = null) |
|||
{ |
|||
var redirectUri = string.IsNullOrWhiteSpace(returnUrl) ? "/" : returnUrl; |
|||
return SignOut( |
|||
new AuthenticationProperties { RedirectUri = redirectUri }, |
|||
CookieAuthenticationDefaults.AuthenticationScheme, |
|||
OpenIdConnectDefaults.AuthenticationScheme); |
|||
} |
|||
} |
|||
@ -1,42 +0,0 @@ |
|||
@page "/" |
|||
@inject IConfiguration Configuration; |
|||
@{ |
|||
var serverUrl = Configuration["Elsa:Server:BaseUrl"]; |
|||
} |
|||
<!DOCTYPE html> |
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="utf-8"/> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> |
|||
<title>Elsa Workflows</title> |
|||
<link rel="icon" type="image/png" sizes="32x32" href="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/assets/images/favicon-32x32.png"> |
|||
<link rel="icon" type="image/png" sizes="16x16" href="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/assets/images/favicon-16x16.png"> |
|||
<link rel="stylesheet" href="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/assets/fonts/inter/inter.css"> |
|||
<link rel="stylesheet" href="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/elsa-workflows-studio.css"> |
|||
<script src="/_content/Elsa.Designer.Components.Web/monaco-editor/min/vs/loader.js"></script> |
|||
<script type="module" src="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/elsa-workflows-studio.esm.js"></script> |
|||
</head> |
|||
<body> |
|||
<elsa-studio-root server-url="@serverUrl" base-path="" monaco-lib-path="./_content/Elsa.Designer.Components.Web/monaco-editor/min"> |
|||
<elsa-studio-dashboard></elsa-studio-dashboard> |
|||
</elsa-studio-root> |
|||
</body> |
|||
|
|||
<script type="module"> |
|||
// Import publicly exposed services. |
|||
import { WebhooksPlugin, WorkflowSettingsPlugin } from "/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/index.esm.js"; |
|||
|
|||
// Get a handle to the elsa-studio-root element. |
|||
const elsaStudioRoot = document.querySelector('elsa-studio-root'); |
|||
|
|||
// Configure Elsa during the 'initializing' event. |
|||
elsaStudioRoot.addEventListener('initializing', e => { |
|||
const elsa = e.detail; |
|||
elsa.pluginManager.registerPlugins([ |
|||
WebhooksPlugin, |
|||
WorkflowSettingsPlugin |
|||
]); |
|||
}); |
|||
</script> |
|||
|
|||
</html> |
|||
@ -1,2 +1,3 @@ |
|||
@using Microsoft.Extensions.Configuration |
|||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers |
|||
@using Microsoft.AspNetCore.Authorization |
|||
@using Microsoft.AspNetCore.Http |
|||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers |
|||
|
|||
@ -0,0 +1,8 @@ |
|||
{ |
|||
"version": "10.6.0", |
|||
"name": "aspire-workflow-service", |
|||
"private": true, |
|||
"dependencies": { |
|||
"@abp/aspnetcore.components.server.basictheme": "10.6.0" |
|||
} |
|||
} |
|||
@ -0,0 +1,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<AbpRouterOptions>(options => |
|||
{ |
|||
options.AdditionalAssemblies.Add(typeof(Elsa.Studio.Agents.Feature).Assembly); |
|||
}); |
|||
|
|||
Configure<AbpNavigationOptions>(options => |
|||
{ |
|||
options.MenuContributors.Add(new ElsaStudioAgentsMenuContributor()); |
|||
}); |
|||
|
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<AbpElsaNextAgentsBlazorModule>("LINGYUN.Abp.ElsaNext.Agents.Blazor"); |
|||
}); |
|||
|
|||
Configure<AbpLocalizationOptions>(options => |
|||
{ |
|||
options.Resources |
|||
.Get<ElsaStudioResource>() |
|||
.AddVirtualJson("/Localization/Resources/AgentsBlazor"); |
|||
}); |
|||
|
|||
var elsaNextStudioBlazoeOptions = context.Services.ExecutePreConfiguredActions<AbpElsaNextStudioBlazorOptions>(); |
|||
|
|||
context.Services.AddAgentsModule(elsaNextStudioBlazoeOptions.BackendApiConfig); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,29 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk.Razor"> |
|||
|
|||
<Import Project="..\..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net10.0</TargetFramework> |
|||
<AssemblyName>LINGYUN.Abp.ElsaNext.Agents.Blazor</AssemblyName> |
|||
<PackageId>LINGYUN.Abp.ElsaNext.Agents.Blazor</PackageId> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<Content Remove="Localization\Resources\AgentsBlazor\*.json" /> |
|||
<EmbeddedResource Include="Localization\Resources\AgentsBlazor\*.json" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Elsa.Studio.Agents" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.ElsaNext.Agents\LINGYUN.Abp.ElsaNext.Agents.csproj" /> |
|||
<ProjectReference Include="..\LINGYUN.Abp.ElsaNext.Studio.Blazor\LINGYUN.Abp.ElsaNext.Studio.Blazor.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,7 @@ |
|||
{ |
|||
"culture": "en", |
|||
"texts": { |
|||
"Menu:Agents": "Agents", |
|||
"Agents": "Agents" |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
{ |
|||
"culture": "zh-Hans", |
|||
"texts": { |
|||
"Menu:Agents": "代理列表", |
|||
"Agents": "代理列表" |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
using LINGYUN.Abp.ElsaNext.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<ElsaStudioResource>(); |
|||
|
|||
var group = menu.GetMenuItemOrNull(ElsaStudioMenus.GroupName); |
|||
group?.AddItem(new ApplicationMenuItem( |
|||
ElsaStudioMenus.GroupName + ".Agents", |
|||
l["Menu:Agents"], |
|||
url: "/ai/agents", |
|||
icon: "fa fa-bots", |
|||
order: 5)); |
|||
} |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
@using Microsoft.AspNetCore.Authorization |
|||
@using Microsoft.AspNetCore.Components.Web |
|||
@using Elsa.Studio.Components |
|||
@using Elsa.Studio.Localization |
|||
@using MudBlazor |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,27 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net10.0</TargetFramework> |
|||
<AssemblyName>LINGYUN.Abp.ElsaNext.Agents</AssemblyName> |
|||
<PackageId>LINGYUN.Abp.ElsaNext.Agents</PackageId> |
|||
<ImplicitUsings>enable</ImplicitUsings> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Elsa.Agents" /> |
|||
<PackageReference Include="Elsa.Agents.Api" /> |
|||
<PackageReference Include="Elsa.Agents.OpenAI" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.ElsaNext\LINGYUN.Abp.ElsaNext.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,22 @@ |
|||
using Elsa.Agents; |
|||
using Elsa.Extensions; |
|||
using Elsa.Features.Services; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.ElsaNext.Agents; |
|||
|
|||
[DependsOn( |
|||
typeof(AbpElsaNextModule))] |
|||
public class AbpElsaNextAgentsModule : AbpModule |
|||
{ |
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
PreConfigure<IModule>(elsa => |
|||
{ |
|||
elsa.UseAgents(); |
|||
elsa.UseAgentActivities(); |
|||
|
|||
elsa.UseAgentsApi(); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,25 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net10.0</TargetFramework> |
|||
<AssemblyName>LINGYUN.Abp.ElsaNext.EntityFrameworkCore</AssemblyName> |
|||
<PackageId>LINGYUN.Abp.ElsaNext.EntityFrameworkCore</PackageId> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Elsa.Persistence.EFCore" VersionOverride="$(ElsaNextPackageVersion)" /> |
|||
<PackageReference Include="Volo.Abp.EntityFrameworkCore" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.ElsaNext\LINGYUN.Abp.ElsaNext.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,57 @@ |
|||
using Elsa.Alterations.Extensions; |
|||
using Elsa.Extensions; |
|||
using Elsa.Features.Services; |
|||
using Elsa.Persistence.EFCore.Modules.Alterations; |
|||
using Elsa.Persistence.EFCore.Modules.Identity; |
|||
using Elsa.Persistence.EFCore.Modules.Labels; |
|||
using Elsa.Persistence.EFCore.Modules.Management; |
|||
using Elsa.Persistence.EFCore.Modules.Runtime; |
|||
using Elsa.Persistence.EFCore.Modules.Tenants; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.ElsaNext.EntityFrameworkCore; |
|||
|
|||
[DependsOn(typeof(AbpElsaNextModule))] |
|||
public class AbpElsaNextEntityFrameworkCoreModule : AbpModule |
|||
{ |
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
var labelFeature = context.Services.GetPreConfigureActions<EFCoreLabelPersistenceFeature>(); |
|||
var alterationsFeature = context.Services.GetPreConfigureActions<EFCoreAlterationsPersistenceFeature>(); |
|||
var workflowManagementFeature = context.Services.GetPreConfigureActions<WorkflowManagementPersistenceFeature>(); |
|||
var workflowRuntimeFeature = context.Services.GetPreConfigureActions<EFCoreWorkflowRuntimePersistenceFeature>(); |
|||
|
|||
PreConfigure<IModule>(elsa => |
|||
{ |
|||
elsa.UseLabels(label => |
|||
{ |
|||
label.UseEntityFrameworkCore(efCore => |
|||
{ |
|||
labelFeature.Configure(efCore); |
|||
}); |
|||
}); |
|||
elsa.UseAlterations(alterations => |
|||
{ |
|||
alterations.UseEntityFrameworkCore(efCore => |
|||
{ |
|||
alterationsFeature.Configure(efCore); |
|||
}); |
|||
}); |
|||
elsa.UseWorkflowManagement(management => |
|||
{ |
|||
management.UseEntityFrameworkCore(efCore => |
|||
{ |
|||
workflowManagementFeature.Configure(efCore); |
|||
}); |
|||
}); |
|||
elsa.UseWorkflowRuntime(runtime => |
|||
{ |
|||
runtime.UseEntityFrameworkCore(efCore => |
|||
{ |
|||
workflowRuntimeFeature.Configure(efCore); |
|||
}); |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,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<AbpRouterOptions>(options => |
|||
{ |
|||
options.AdditionalAssemblies.Add(typeof(Elsa.Studio.Secrets.Feature).Assembly); |
|||
}); |
|||
|
|||
Configure<AbpNavigationOptions>(options => |
|||
{ |
|||
options.MenuContributors.Add(new ElsaStudioSecretsMenuContributor()); |
|||
}); |
|||
|
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<AbpElsaNextSecretsBlazorModule>("LINGYUN.Abp.ElsaNext.Secrets.Blazor"); |
|||
}); |
|||
|
|||
Configure<AbpLocalizationOptions>(options => |
|||
{ |
|||
options.Resources |
|||
.Get<ElsaStudioResource>() |
|||
.AddVirtualJson("/Localization/Resources/SecretsBlazor"); |
|||
}); |
|||
|
|||
var elsaNextStudioBlazoeOptions = context.Services.ExecutePreConfiguredActions<AbpElsaNextStudioBlazorOptions>(); |
|||
|
|||
context.Services.AddSecretsModule(elsaNextStudioBlazoeOptions.BackendApiConfig); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,29 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk.Razor"> |
|||
|
|||
<Import Project="..\..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net10.0</TargetFramework> |
|||
<AssemblyName>LINGYUN.Abp.ElsaNext.Secrets.Blazor</AssemblyName> |
|||
<PackageId>LINGYUN.Abp.ElsaNext.Secrets.Blazor</PackageId> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<Content Remove="Localization\Resources\SecretsBlazor\*.json" /> |
|||
<EmbeddedResource Include="Localization\Resources\SecretsBlazor\*.json" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Elsa.Studio.Secrets" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.ElsaNext.Secrets\LINGYUN.Abp.ElsaNext.Secrets.csproj" /> |
|||
<ProjectReference Include="..\LINGYUN.Abp.ElsaNext.Studio.Blazor\LINGYUN.Abp.ElsaNext.Studio.Blazor.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,7 @@ |
|||
{ |
|||
"culture": "en", |
|||
"texts": { |
|||
"Menu:Secrets": "Secrets", |
|||
"Secrets": "Secrets" |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
{ |
|||
"culture": "zh-Hans", |
|||
"texts": { |
|||
"Menu:Secrets": "机密列表", |
|||
"Secrets": "机密列表" |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
using LINGYUN.Abp.ElsaNext.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<ElsaStudioResource>(); |
|||
|
|||
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; |
|||
} |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
@using Microsoft.AspNetCore.Authorization |
|||
@using Microsoft.AspNetCore.Components.Web |
|||
@using Elsa.Studio.Components |
|||
@using Elsa.Studio.Localization |
|||
@using MudBlazor |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,26 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net10.0</TargetFramework> |
|||
<AssemblyName>LINGYUN.Abp.ElsaNext.Secrets</AssemblyName> |
|||
<PackageId>LINGYUN.Abp.ElsaNext.Secrets</PackageId> |
|||
<ImplicitUsings>enable</ImplicitUsings> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Elsa.Secrets" /> |
|||
<PackageReference Include="Elsa.Secrets.JavaScript" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.ElsaNext\LINGYUN.Abp.ElsaNext.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -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<IModule>(elsa => |
|||
{ |
|||
elsa.UseSecrets(secret => |
|||
{ |
|||
secret.ConfigureOptions = options => configuration.GetSection("Secrets").Bind(options); |
|||
}); |
|||
elsa.UseSecretsJavaScript(); |
|||
}); |
|||
} |
|||
} |
|||
@ -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<AbpElsaNextStudioBlazorOptions>(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<AbpNavigationOptions>(options => |
|||
{ |
|||
options.MenuContributors.Add(new ElsaStudioMenuContributor()); |
|||
}); |
|||
|
|||
Configure<AbpRouterOptions>(options => |
|||
{ |
|||
options.AdditionalAssemblies.Add(typeof(Elsa.Studio.Settings.Feature).Assembly); |
|||
options.AdditionalAssemblies.Add(typeof(Elsa.Studio.Workflows.Feature).Assembly); |
|||
}); |
|||
|
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<AbpElsaNextStudioBlazorModule>("LINGYUN.Abp.ElsaNext.Studio.Blazor"); |
|||
}); |
|||
|
|||
Configure<AbpLocalizationOptions>(options => |
|||
{ |
|||
options.Resources |
|||
.Get<ElsaStudioResource>() |
|||
.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<AbpElsaNextStudioBlazorOptions>(); |
|||
|
|||
context.Services.AddRemoteBackend(elsaNextStudioBlazoeOptions.BackendApiConfig); |
|||
|
|||
context.Services.AddWorkflowsModule(); |
|||
context.Services.AddSettingsModule(); |
|||
|
|||
Configure<DesignerOptions>(configuration.GetSection("DesignerOptions")); |
|||
|
|||
var abpLocalizationOptions = context.Services.ExecutePreConfiguredActions<AbpLocalizationOptions>(); |
|||
context.Services.AddLocalizationModule(new LocalizationConfig |
|||
{ |
|||
ConfigureLocalizationOptions = options => |
|||
{ |
|||
options.SupportedCultures = abpLocalizationOptions.Languages.Select(x => x.CultureName).ToArray(); |
|||
} |
|||
}); |
|||
context.Services.AddElsaStudioTranslations(); |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,43 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk.Razor"> |
|||
|
|||
<Import Project="..\..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net10.0</TargetFramework> |
|||
<Nullable>enable</Nullable> |
|||
<ImplicitUsings>enable</ImplicitUsings> |
|||
<AssemblyName>LINGYUN.Abp.ElsaNext.Studio.Blazor</AssemblyName> |
|||
<PackageId>LINGYUN.Abp.ElsaNext.Studio.Blazor</PackageId> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.Localization" /> |
|||
<PackageReference Include="Volo.Abp.UI.Navigation" /> |
|||
<PackageReference Include="Volo.Abp.VirtualFileSystem" /> |
|||
<PackageReference Include="Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.ElsaNext\LINGYUN.Abp.ElsaNext.csproj" /> |
|||
<!-- ABP-hosted Elsa Studio UI copy (en/zh-Hans) + Elsa ILocalizationProvider bridge. --> |
|||
<ProjectReference Include="..\LINGYUN.Abp.ElsaNext.Studio.Translations\LINGYUN.Abp.ElsaNext.Studio.Translations.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<!-- Elsa Studio, Blazor Server hosting pieces (Elsa.Studio 3.8.0-rc2). --> |
|||
<PackageReference Include="Elsa.Studio.Core.BlazorServer" /> |
|||
<PackageReference Include="Elsa.Studio.Settings" /> |
|||
<PackageReference Include="Elsa.Studio.Shell" /> |
|||
<PackageReference Include="Elsa.Studio.Workflows" /> |
|||
<PackageReference Include="Elsa.Studio.Workflows.Designer" /> |
|||
<PackageReference Include="Elsa.Api.Client" /> |
|||
<PackageReference Include="Elsa.Studio.Authentication.Abstractions" /> |
|||
<PackageReference Include="Elsa.Studio.Localization.BlazorServer" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<EmbeddedResource Include="Localization\**\*.json" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,9 @@ |
|||
{ |
|||
"culture": "en", |
|||
"texts": { |
|||
"Menu:ElsaStudio": "Elsa Studio", |
|||
"Menu:WorkflowDefinitions": "Workflow Definitions", |
|||
"Menu:WorkflowInstances": "Workflow Instances", |
|||
"Menu:Settings": "Settings" |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
{ |
|||
"culture": "zh-Hans", |
|||
"texts": { |
|||
"Menu:ElsaStudio": "Elsa 工作室", |
|||
"Menu:WorkflowDefinitions": "工作流定义", |
|||
"Menu:WorkflowInstances": "工作流实例", |
|||
"Menu:Settings": "设置" |
|||
} |
|||
} |
|||
@ -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<ElsaStudioResource>(); |
|||
|
|||
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); |
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
namespace LINGYUN.Abp.ElsaNext.Studio.Blazor.Navigation; |
|||
|
|||
public static class ElsaStudioMenus |
|||
{ |
|||
public const string GroupName = "ElsaStudio"; |
|||
} |
|||
@ -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" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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 |
|||
@ -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<IModule>(elsa => |
|||
{ |
|||
elsa.UseWorkflowRuntimeDashboard(); |
|||
}); |
|||
} |
|||
|
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
var configuration = context.Configuration.GetSection("Elsa:Studio"); |
|||
|
|||
Configure<AbpRouterOptions>(options => |
|||
{ |
|||
options.AdditionalAssemblies.Add(typeof(AbpElsaNextStudioDashboardBlazorModule).Assembly); |
|||
}); |
|||
|
|||
Configure<AbpNavigationOptions>(options => |
|||
{ |
|||
options.MenuContributors.Add(new ElsaStudioDashboardMenuContributor()); |
|||
}); |
|||
|
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<AbpElsaNextStudioDashboardBlazorModule>("LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor"); |
|||
}); |
|||
Configure<AbpLocalizationOptions>(options => |
|||
{ |
|||
options.Resources |
|||
.Get<ElsaStudioResource>() |
|||
.AddVirtualJson("/Localization/Resources/ElsaStudioDashboardBlazor"); |
|||
}); |
|||
|
|||
var elsaNextStudioBlazoeOptions = context.Services.ExecutePreConfiguredActions<AbpElsaNextStudioBlazorOptions>(); |
|||
|
|||
context.Services.AddDashboardModule(elsaNextStudioBlazoeOptions.BackendApiConfig); |
|||
context.Services.AddDashboardWidgets(); |
|||
|
|||
context.Services.AddScoped<ElsaStudioDashboardText>(); |
|||
} |
|||
} |
|||
@ -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<OperationalHealthWidget>("dashboard.workflow.metrics", DashboardWidgetZones.Metrics, 100, "Workflow metrics", payloadKind: "WorkflowInstances") |
|||
.AddDashboardWidget<NeedsAttentionWidget>("dashboard.needs-attention", DashboardWidgetZones.Findings, 100, "Needs attention") |
|||
.AddDashboardWidget<ExecutionTrendWidget>("dashboard.workflow.trend", DashboardWidgetZones.Trend, 100, "Workflow trends", payloadKind: "WorkflowTrends") |
|||
.AddDashboardWidget<RecentActivityWidget>("dashboard.workflow.recent-activity", DashboardWidgetZones.Activity, 100, "Recent activity", payloadKind: "RecentActivity") |
|||
.AddDashboardWidget<HotspotsWidget>("dashboard.workflow.hotspots", DashboardWidgetZones.SecondaryPanels, 100, "Workflow hotspots", payloadKind: "WorkflowHotspots"); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,31 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk.Razor"> |
|||
|
|||
<Import Project="..\..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net10.0</TargetFramework> |
|||
<Nullable>enable</Nullable> |
|||
<ImplicitUsings>enable</ImplicitUsings> |
|||
<AssemblyName>LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor</AssemblyName> |
|||
<PackageId>LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor</PackageId> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<!-- Elsa Studio Dashboard (official, rc2): page shell, zones, models and API client. The |
|||
workflow widgets are re-implemented in this module (Widgets/) as localized copies. --> |
|||
<PackageReference Include="Elsa.Studio.Dashboard" /> |
|||
<!-- 使用自定义仪表板组件 --> |
|||
<!--<PackageReference Include="Elsa.Studio.Workflows.Dashboard" />--> |
|||
<PackageReference Include="Elsa.Workflows.Runtime.Dashboard" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.ElsaNext.Studio.Blazor\LINGYUN.Abp.ElsaNext.Studio.Blazor.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<EmbeddedResource Include="Localization\**\*.json" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,6 @@ |
|||
{ |
|||
"culture": "en", |
|||
"texts": { |
|||
"Menu:Dashboard": "Dashboard" |
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
{ |
|||
"culture": "zh-Hans", |
|||
"texts": { |
|||
"Menu:Dashboard": "仪表盘" |
|||
} |
|||
} |
|||
@ -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; |
|||
|
|||
/// <summary>
|
|||
/// 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).
|
|||
/// </summary>
|
|||
public class ElsaStudioDashboardMenuContributor : IMenuContributor |
|||
{ |
|||
public Task ConfigureMenuAsync(MenuConfigurationContext context) |
|||
{ |
|||
if (context.Menu.Name == StandardMenus.Main) |
|||
{ |
|||
var menu = context.Menu; |
|||
var l = context.GetLocalizer<ElsaStudioResource>(); |
|||
|
|||
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; |
|||
} |
|||
} |
|||
@ -0,0 +1,92 @@ |
|||
@page "/dashboard" |
|||
@attribute [Authorize] |
|||
@inherits StudioComponentBase |
|||
@inject ILocalizer Localizer |
|||
|
|||
<HeadContent> |
|||
<link rel="stylesheet" href="_content/LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor/dashboard.css" /> |
|||
</HeadContent> |
|||
|
|||
<PageTitle>@Localizer["Dashboard"]</PageTitle> |
|||
|
|||
<MudContainer MaxWidth="MaxWidth.False" Class="dashboard-shell" Style="@DashboardStyles.Shell"> |
|||
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Class="dashboard-header" Spacing="3" Style="@DashboardStyles.Header"> |
|||
<MudStack Spacing="1"> |
|||
<MudText Typo="Typo.h4">@Localizer["Dashboard"]</MudText> |
|||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1" Class="flex-wrap"> |
|||
<MudText Typo="Typo.body2" Class="dashboard-muted">@BackendLabel</MudText> |
|||
@if (_snapshot != null) |
|||
{ |
|||
var runtime = _snapshot.Overview.Runtime.Status; |
|||
<MudChip T="string" Size="Size.Small" Variant="Variant.Filled" Color="@DashboardUiMapper.RuntimeColor(runtime)" Icon="@Icons.Material.Outlined.Circle">@Text.RuntimeStatusLabel(runtime)</MudChip> |
|||
} |
|||
else |
|||
{ |
|||
<MudChip T="string" Size="Size.Small" Variant="Variant.Filled" Color="Color.Error" Icon="@Icons.Material.Outlined.CloudOff">@StatusLabel</MudChip> |
|||
} |
|||
<MudText Typo="Typo.caption" Class="dashboard-muted">@LastRefreshedLabel</MudText> |
|||
</MudStack> |
|||
</MudStack> |
|||
|
|||
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.FlexEnd" Spacing="1" Class="flex-wrap"> |
|||
<MudToggleGroup T="string" Value="@_selectedRange" ValueChanged="OnRangeChangedAsync" Color="Color.Primary" SelectionMode="SelectionMode.SingleSelection" Size="Size.Small"> |
|||
@foreach (var option in DashboardRangeMapper.Options) |
|||
{ |
|||
<MudToggleItem T="string" Value="@option.Value">@option.Label</MudToggleItem> |
|||
} |
|||
</MudToggleGroup> |
|||
<MudTooltip Text="@Localizer["Refresh dashboard"]" Delay="400"> |
|||
<MudIconButton Icon="@Icons.Material.Outlined.Refresh" Color="Color.Primary" Disabled="@_loading" OnClick="RefreshAsync" aria-label="Refresh dashboard" /> |
|||
</MudTooltip> |
|||
</MudStack> |
|||
</MudStack> |
|||
|
|||
@if (!string.IsNullOrWhiteSpace(_message)) |
|||
{ |
|||
<MudAlert Severity="@AlertSeverity" Dense="true" Class="mb-4">@_message</MudAlert> |
|||
} |
|||
|
|||
@if (_loading && _snapshot == null) |
|||
{ |
|||
<MudGrid Spacing="2" Class="mb-4"> |
|||
@for (var i = 0; i < 6; i++) |
|||
{ |
|||
<MudItem xs="12" sm="6" md="4" xl="2"> |
|||
<MudSkeleton Height="112px" Animation="Animation.Wave" /> |
|||
</MudItem> |
|||
} |
|||
</MudGrid> |
|||
<MudSkeleton Height="360px" Animation="Animation.Wave" /> |
|||
} |
|||
else if (_snapshot == null) |
|||
{ |
|||
<MudPaper Outlined="true" Elevation="0" Class="dashboard-panel dashboard-empty-state" Style="@DashboardStyles.EmptyPanel"> |
|||
<MudIcon Icon="@Icons.Material.Outlined.DashboardCustomize" Color="Color.Default" /> |
|||
<MudText Typo="Typo.h6">@StatusLabel</MudText> |
|||
<MudText Typo="Typo.body2" Class="dashboard-muted">@(_message ?? @Localizer["Dashboard data is unavailable from the selected backend."])</MudText> |
|||
</MudPaper> |
|||
} |
|||
else |
|||
{ |
|||
<DashboardWidgetZone Widgets="@GetWidgets(DashboardWidgetZones.Metrics)" Context="@WidgetContext" /> |
|||
|
|||
<MudStack Spacing="2" Class="dashboard-main-stack"> |
|||
<DashboardWidgetZone Widgets="@GetWidgets(DashboardWidgetZones.Trend)" Context="@WidgetContext" /> |
|||
<DashboardWidgetZone Widgets="@GetWidgets(DashboardWidgetZones.PrimaryPanels)" Context="@WidgetContext" /> |
|||
|
|||
<MudGrid Spacing="2" Class="dashboard-activity-grid"> |
|||
<MudItem xs="12" lg="8"> |
|||
<DashboardWidgetZone Widgets="@GetWidgets(DashboardWidgetZones.Activity)" Context="@WidgetContext" /> |
|||
</MudItem> |
|||
<MudItem xs="12" lg="4"> |
|||
<MudStack Spacing="2"> |
|||
<DashboardWidgetZone Widgets="@GetWidgets(DashboardWidgetZones.Findings)" Context="@WidgetContext" /> |
|||
<DashboardWidgetZone Widgets="@GetWidgets(DashboardWidgetZones.SecondaryPanels)" Context="@WidgetContext" /> |
|||
</MudStack> |
|||
</MudItem> |
|||
</MudGrid> |
|||
|
|||
<DashboardWidgetZone Widgets="@GetWidgets(DashboardWidgetZones.DiagnosticsStatus)" Context="@WidgetContext" Class="dashboard-diagnostics-grid" ItemClass="dashboard-diagnostics-grid-item" /> |
|||
</MudStack> |
|||
} |
|||
</MudContainer> |
|||
@ -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; |
|||
|
|||
/// <summary>
|
|||
/// Elsa Studio dashboard page (official Elsa.Studio.Dashboard content hosted under
|
|||
/// <c>/elsa/workflows/dashboard</c>). The dashboard service reads the Elsa dashboard API with the
|
|||
/// circuit session token; the widgets (metrics/trend/activity/...) come from the official
|
|||
/// dashboard modules.
|
|||
/// </summary>
|
|||
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<DashboardWidgetDescriptor> 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<DashboardWidgetDescriptor> 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(); |
|||
} |
|||
} |
|||
@ -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; |
|||
|
|||
/// <summary>
|
|||
/// 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
|
|||
/// <see cref="ILocalizer"/> (which follows the ABP UI culture captured on the circuit) and format
|
|||
/// numbers with that same culture.
|
|||
/// </summary>
|
|||
public class ElsaStudioDashboardText |
|||
{ |
|||
private readonly ILocalizer _localizer; |
|||
|
|||
public ElsaStudioDashboardText(ILocalizer localizer) |
|||
{ |
|||
_localizer = localizer; |
|||
} |
|||
|
|||
/// <summary>The UI culture captured at prerender (fallback: current thread culture).</summary>
|
|||
public CultureInfo Culture => CultureInfo.CurrentUICulture; |
|||
|
|||
/// <summary>Localized text for the given key (falls back to the key itself).</summary>
|
|||
public string this[string key] => _localizer[key]; |
|||
|
|||
/// <summary>Localized capability label of the "needs attention" backend capability.</summary>
|
|||
public string CapabilityLabel(DashboardCapabilityStatus? capability) |
|||
{ |
|||
return capability?.Status switch |
|||
{ |
|||
"Available" => _localizer["Available"], |
|||
"Unauthorized" => _localizer["No access"], |
|||
"NotInstalled" => _localizer["Not installed"], |
|||
_ => _localizer["Unavailable"] |
|||
}; |
|||
} |
|||
|
|||
/// <summary>Localized label of the runtime status chip shown in the dashboard header.</summary>
|
|||
public string RuntimeStatusLabel(string? status) |
|||
{ |
|||
return status switch |
|||
{ |
|||
DashboardRuntimeStatusKeys.AcceptingWork => _localizer["Accepting work"], |
|||
DashboardRuntimeStatusKeys.Paused => _localizer["Paused"], |
|||
DashboardRuntimeStatusKeys.Draining => _localizer["Draining"], |
|||
_ => _localizer["Unavailable"] |
|||
}; |
|||
} |
|||
|
|||
/// <summary>Humanized caption for the selected dashboard range.</summary>
|
|||
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"]}"; |
|||
} |
|||
|
|||
/// <summary>Trend chart x-axis label for a bucket timestamp.</summary>
|
|||
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); |
|||
} |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
@using LINGYUN.Abp.ElsaNext.Studio.Dashboard.Blazor.Services |
|||
|
|||
@inject ElsaStudioDashboardText Text |
|||
|
|||
@if (Context.Snapshot != null) |
|||
{ |
|||
var response = Context.Snapshot.Trend; |
|||
|
|||
<MudPaper Outlined="true" Elevation="0" Class="dashboard-panel dashboard-section dashboard-chart-panel" Style="@DashboardStyles.ChartPanel"> |
|||
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Class="dashboard-section-header" Style="@DashboardStyles.SectionHeader"> |
|||
<MudText Typo="Typo.subtitle1">@Text["Execution trend"]</MudText> |
|||
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.FlexEnd" Spacing="1" Class="dashboard-trend-actions" Style="@DashboardStyles.TrendActions"> |
|||
<MudText Typo="Typo.caption" Class="dashboard-muted">@RangeCaption(response)</MudText> |
|||
</MudStack> |
|||
</MudStack> |
|||
|
|||
@if (Context.Loading) |
|||
{ |
|||
<MudSkeleton Height="220px" Animation="Animation.Wave" /> |
|||
} |
|||
else if (response.Buckets.Count == 0) |
|||
{ |
|||
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text">@Text["No workflow activity in this range."]</MudAlert> |
|||
} |
|||
else |
|||
{ |
|||
<div class="dashboard-trend-chart-frame"> |
|||
<MudChart T="double" |
|||
Class="dashboard-trend-chart" |
|||
ChartType="ChartType.Line" |
|||
ChartSeries="@Series(response)" |
|||
ChartLabels="@Labels(response)" |
|||
Width="100%" |
|||
Height="220px" |
|||
MatchBoundsToSize="true" /> |
|||
</div> |
|||
} |
|||
</MudPaper> |
|||
} |
|||
|
|||
@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<ChartSeries<double>> 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() } |
|||
]; |
|||
} |
|||
@ -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; |
|||
|
|||
<MudPaper Outlined="true" Elevation="0" Class="dashboard-panel dashboard-section" Style="@DashboardStyles.Panel"> |
|||
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Class="dashboard-section-header" Style="@DashboardStyles.SectionHeader"> |
|||
<MudText Typo="Typo.subtitle1">@Text["Workflow hotspots"]</MudText> |
|||
<MudChip T="string" Size="Size.Small" Variant="Variant.Outlined">@MetricLabel(response.Metric)</MudChip> |
|||
</MudStack> |
|||
|
|||
@if (response.Items.Count == 0) |
|||
{ |
|||
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text">@Text["No hotspots in this range."]</MudAlert> |
|||
} |
|||
else |
|||
{ |
|||
<MudTable T="DashboardHotspot" Items="@response.Items" Dense="true" Hover="true" Elevation="0" Breakpoint="Breakpoint.Sm"> |
|||
<HeaderContent> |
|||
<MudTh>@Text["Workflow"]</MudTh> |
|||
<MudTh>@Text["Value"]</MudTh> |
|||
<MudTh>@Text["Avg duration"]</MudTh> |
|||
</HeaderContent> |
|||
<RowTemplate> |
|||
<MudTd DataLabel="@Text["Workflow"]"> |
|||
<MudStack Spacing="0"> |
|||
<MudText Typo="Typo.body2">@DisplayName(context)</MudText> |
|||
<MudText Typo="Typo.caption" Class="dashboard-muted">@context.DefinitionId</MudText> |
|||
</MudStack> |
|||
</MudTd> |
|||
<MudTd DataLabel="@Text["Value"]">@Text.Count(context.Value)</MudTd> |
|||
<MudTd DataLabel="@Text["Avg duration"]">@Text.Duration(context.AverageDuration)</MudTd> |
|||
</RowTemplate> |
|||
</MudTable> |
|||
} |
|||
</MudPaper> |
|||
} |
|||
|
|||
@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!; |
|||
} |
|||
@ -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; |
|||
|
|||
<MudPaper Outlined="true" Elevation="0" Class="dashboard-panel dashboard-section" Style="@DashboardStyles.Panel"> |
|||
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Class="dashboard-section-header" Style="@DashboardStyles.SectionHeader"> |
|||
<MudText Typo="Typo.subtitle1">@Text["Needs attention"]</MudText> |
|||
<MudChip T="string" Size="Size.Small" Variant="Variant.Outlined" Color="@DashboardUiMapper.CapabilityColor(capability)">@Text.CapabilityLabel(capability)</MudChip> |
|||
</MudStack> |
|||
|
|||
@if (findings.Count == 0) |
|||
{ |
|||
<MudAlert Severity="Severity.Success" Dense="true" Variant="Variant.Text" Icon="@Icons.Material.Outlined.CheckCircle"> |
|||
@Text["No active findings in the selected range."] |
|||
</MudAlert> |
|||
} |
|||
else |
|||
{ |
|||
<MudList T="DashboardFinding" Dense="true" Class="dashboard-finding-list" Style="@DashboardStyles.FindingList"> |
|||
@foreach (var item in findings.OrderBy(x => x.Priority).Take(8)) |
|||
{ |
|||
<MudListItem T="DashboardFinding" Value="@item" Icon="@DashboardUiMapper.SeverityIcon(item.Severity)" IconColor="@DashboardUiMapper.SeverityColor(item.Severity)"> |
|||
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Spacing="2" Class="dashboard-finding-row" Style="@DashboardStyles.FindingRow"> |
|||
<MudText Typo="Typo.body2">@item.Message</MudText> |
|||
<MudChip T="string" Size="Size.Small" Variant="Variant.Text" Color="@DashboardUiMapper.SeverityColor(item.Severity)">@item.Severity</MudChip> |
|||
</MudStack> |
|||
</MudListItem> |
|||
} |
|||
</MudList> |
|||
} |
|||
</MudPaper> |
|||
} |
|||
|
|||
@code { |
|||
[Parameter] public DashboardWidgetContext Context { get; set; } = null!; |
|||
[Parameter] public DashboardWidgetDescriptor Descriptor { get; set; } = null!; |
|||
} |
|||
@ -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) |
|||
}; |
|||
|
|||
<div class="dashboard-operational-health"> |
|||
<MudText Typo="Typo.subtitle1" Class="dashboard-operational-health-heading">@Text["Operational health"]</MudText> |
|||
|
|||
<div class="dashboard-operational-health-metrics"> |
|||
@foreach (var metric in healthMetrics) |
|||
{ |
|||
<div class="dashboard-operational-health-metric"> |
|||
@MetricContent(metric) |
|||
</div> |
|||
} |
|||
</div> |
|||
</div> |
|||
} |
|||
|
|||
@code { |
|||
[Parameter] public DashboardWidgetContext Context { get; set; } = null!; |
|||
[Parameter] public DashboardWidgetDescriptor Descriptor { get; set; } = null!; |
|||
|
|||
private RenderFragment<HealthMetric> MetricContent => metric => @<text> |
|||
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Spacing="2"> |
|||
<MudText Typo="Typo.overline">@metric.Label</MudText> |
|||
<MudIcon Icon="@metric.Icon" Size="Size.Small" Color="@metric.Color" /> |
|||
</MudStack> |
|||
<MudText Typo="Typo.h5" Color="@metric.Color" Class="dashboard-operational-health-value">@metric.Value</MudText> |
|||
<MudText Typo="Typo.caption" Class="dashboard-operational-health-caption">@metric.Caption</MudText> |
|||
</text>; |
|||
|
|||
private sealed record HealthMetric(string Label, string Value, string Caption, string Icon, Color Color); |
|||
} |
|||
@ -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; |
|||
|
|||
<MudPaper Outlined="true" Elevation="0" Class="dashboard-panel dashboard-section" Style="@DashboardStyles.Panel"> |
|||
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Class="dashboard-section-header" Style="@DashboardStyles.SectionHeader"> |
|||
<MudText Typo="Typo.subtitle1">@Text["Recent workflow activity"]</MudText> |
|||
<MudText Typo="Typo.caption" Class="dashboard-muted">@items.Count @(items.Count == 1 ? Text["item"] : Text["items"])</MudText> |
|||
</MudStack> |
|||
|
|||
<MudTable T="DashboardRecentActivityItem" Items="@items" Dense="true" Hover="true" Elevation="0" Breakpoint="Breakpoint.Sm" RowClassFunc="GetRowClass" OnRowClick="OnRowClick"> |
|||
<HeaderContent> |
|||
<MudTh>@Text["Workflow / instance"]</MudTh> |
|||
<MudTh>@Text["Status"]</MudTh> |
|||
<MudTh>@Text["Sub status"]</MudTh> |
|||
<MudTh>@Text["Incidents"]</MudTh> |
|||
<MudTh>@Text["Duration"]</MudTh> |
|||
<MudTh>@Text["Updated"]</MudTh> |
|||
<MudTh></MudTh> |
|||
</HeaderContent> |
|||
<RowTemplate> |
|||
<MudTd DataLabel="@Text["Workflow / instance"]"> |
|||
<MudStack Spacing="0"> |
|||
<MudText Typo="Typo.body2">@DisplayName(context)</MudText> |
|||
<MudText Typo="Typo.caption" Class="dashboard-muted">@context.InstanceId</MudText> |
|||
</MudStack> |
|||
</MudTd> |
|||
<MudTd DataLabel="@Text["Status"]"> |
|||
<MudChip T="string" Size="Size.Small" Variant="Variant.Outlined" Color="@StatusColor(context.Status)">@LocalizedStatus(context.Status)</MudChip> |
|||
</MudTd> |
|||
<MudTd DataLabel="@Text["Sub status"]"> |
|||
<MudChip T="string" Size="Size.Small" Variant="Variant.Text" Color="@SubStatusColor(context.SubStatus)">@LocalizedStatus(context.SubStatus)</MudChip> |
|||
</MudTd> |
|||
<MudTd DataLabel="@Text["Incidents"]">@Text.Count(context.IncidentCount)</MudTd> |
|||
<MudTd DataLabel="@Text["Duration"]">@Text.Duration(context.Duration)</MudTd> |
|||
<MudTd DataLabel="@Text["Updated"]">@Text.RelativeTimestamp(context.UpdatedAt ?? context.FinishedAt ?? context.CreatedAt)</MudTd> |
|||
<MudTd> |
|||
<MudTooltip Text="@Text["Open workflow instance"]"> |
|||
<MudIconButton Icon="@Icons.Material.Outlined.OpenInNew" Size="Size.Small" Href="@InstanceUrl(context)" aria-label="@($"{Text["Open workflow instance"]} {context.InstanceId}")" /> |
|||
</MudTooltip> |
|||
</MudTd> |
|||
</RowTemplate> |
|||
<NoRecordsContent> |
|||
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text">@Text["No workflow instances changed in this range."]</MudAlert> |
|||
</NoRecordsContent> |
|||
</MudTable> |
|||
</MudPaper> |
|||
} |
|||
|
|||
@code { |
|||
[Parameter] public DashboardWidgetContext Context { get; set; } = null!; |
|||
[Parameter] public DashboardWidgetDescriptor Descriptor { get; set; } = null!; |
|||
|
|||
private void OnRowClick(TableRowClickEventArgs<DashboardRecentActivityItem> 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 |
|||
}; |
|||
} |
|||
} |
|||
|
|||
@ -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 |
|||
@ -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 <HeadContent>. |
|||
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; |
|||
} |
|||
} |
|||
@ -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<IModule>(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<AbpRouterOptions>(options => |
|||
{ |
|||
options.AdditionalAssemblies.Add(typeof(Elsa.Studio.Diagnostics.OpenTelemetry.Feature).Assembly); |
|||
}); |
|||
|
|||
Configure<AbpNavigationOptions>(options => |
|||
{ |
|||
options.MenuContributors.Add(new ElsaStudioOpenTelemetryMenuContributor()); |
|||
}); |
|||
|
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<AbpElsaNextStudioDiagnosticsOpenTelemetryBlazorModule>("LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor"); |
|||
}); |
|||
|
|||
Configure<AbpLocalizationOptions>(options => |
|||
{ |
|||
options.Resources |
|||
.Get<ElsaStudioResource>() |
|||
.AddVirtualJson("/Localization/Resources/ElsaStudioOpenTelemetryBlazor"); |
|||
}); |
|||
|
|||
var elsaNextStudioBlazoeOptions = context.Services.ExecutePreConfiguredActions<AbpElsaNextStudioBlazorOptions>(); |
|||
|
|||
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<AbpEndpointRouterOptions>(options => |
|||
{ |
|||
options.EndpointConfigureActions.Add((context) => |
|||
{ |
|||
context.Endpoints.MapOpenTelemetryHub(); |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
@ -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<OpenTelemetryDashboardWidget>( |
|||
"diagnostics.open-telemetry", |
|||
DashboardWidgetZones.DiagnosticsStatus, |
|||
300, |
|||
"OpenTelemetry", |
|||
requiredBackendCapability: "OpenTelemetry", |
|||
payloadKind: "OpenTelemetry.StorageDiagnostics"); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,34 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk.Razor"> |
|||
|
|||
<Import Project="..\..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net10.0</TargetFramework> |
|||
<Nullable>enable</Nullable> |
|||
<ImplicitUsings>enable</ImplicitUsings> |
|||
<AssemblyName>LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor</AssemblyName> |
|||
<PackageId>LINGYUN.Abp.ElsaNext.Studio.Diagnostics.OpenTelemetry.Blazor</PackageId> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.AspNetCore" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="OpenTelemetry.Extensions.Hosting" /> |
|||
<PackageReference Include="Elsa.OpenTelemetry" /> |
|||
<PackageReference Include="Elsa.Diagnostics.OpenTelemetry" /> |
|||
<PackageReference Include="Elsa.Studio.Diagnostics.OpenTelemetry.Dashboard" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.ElsaNext.Studio.Blazor\LINGYUN.Abp.ElsaNext.Studio.Blazor.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<Content Remove="Localization\**\*.json" /> |
|||
<EmbeddedResource Include="Localization\**\*.json" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,7 @@ |
|||
{ |
|||
"culture": "en", |
|||
"texts": { |
|||
"Menu:OpenTelemetry": "Open Telemetry", |
|||
"OpenTelemetry": "Open Telemetry" |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
{ |
|||
"culture": "zh-Hans", |
|||
"texts": { |
|||
"Menu:OpenTelemetry": "Open Telemetry", |
|||
"OpenTelemetry": "Open Telemetry" |
|||
} |
|||
} |
|||
@ -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<ElsaStudioResource>(); |
|||
|
|||
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; |
|||
} |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
@using Microsoft.AspNetCore.Authorization |
|||
@using Microsoft.AspNetCore.Components.Web |
|||
@using Elsa.Studio.Components |
|||
@using Elsa.Studio.Localization |
|||
@using MudBlazor |
|||
@ -0,0 +1,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<IModule>(elsa => |
|||
{ |
|||
elsa.UseStructuredLogs(); |
|||
}); |
|||
} |
|||
|
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
var configuration = context.Configuration.GetSection("Elsa:Studio"); |
|||
|
|||
Configure<AbpRouterOptions>(options => |
|||
{ |
|||
// options.AdditionalAssemblies.Add(typeof(Elsa.Studio.Diagnostics.StructuredLogs.Feature).Assembly);
|
|||
options.AdditionalAssemblies.Add(typeof(AbpElsaNextStudioDiagnosticsStructuredLogsBlazorModule).Assembly); |
|||
}); |
|||
|
|||
Configure<AbpNavigationOptions>(options => |
|||
{ |
|||
options.MenuContributors.Add(new ElsaStudioStructuredLogsMenuContributor()); |
|||
}); |
|||
|
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<AbpElsaNextStudioDiagnosticsStructuredLogsBlazorModule>("LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor"); |
|||
}); |
|||
Configure<AbpLocalizationOptions>(options => |
|||
{ |
|||
options.Resources |
|||
.Get<ElsaStudioResource>() |
|||
.AddVirtualJson("/Localization/Resources/ElsaStudioStructuredLogsBlazor"); |
|||
}); |
|||
|
|||
var elsaNextStudioBlazoeOptions = context.Services.ExecutePreConfiguredActions<AbpElsaNextStudioBlazorOptions>(); |
|||
|
|||
context.Services.AddStructuredLogsModule(elsaNextStudioBlazoeOptions.BackendApiConfig); |
|||
context.Services.AddStructuredLogsDashboardModule(); |
|||
context.Services.AddStructuredLogsDashboardWidget(); |
|||
|
|||
Configure<AbpEndpointRouterOptions>(options => |
|||
{ |
|||
options.EndpointConfigureActions.Add((context) => |
|||
{ |
|||
context.Endpoints.MapStructuredLogsHub(); |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
@ -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<StructuredLogsDashboardWidget>( |
|||
"diagnostics.structured-logs", |
|||
DashboardWidgetZones.DiagnosticsStatus, |
|||
100, |
|||
"Structured logs", |
|||
requiredBackendCapability: "StructuredLogs", |
|||
payloadKind: "Diagnostics.StructuredLogs"); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,33 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk.Razor"> |
|||
|
|||
<Import Project="..\..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net10.0</TargetFramework> |
|||
<Nullable>enable</Nullable> |
|||
<ImplicitUsings>enable</ImplicitUsings> |
|||
<AssemblyName>LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor</AssemblyName> |
|||
<PackageId>LINGYUN.Abp.ElsaNext.Studio.Diagnostics.StructuredLogs.Blazor</PackageId> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.AspNetCore" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<!-- Official Elsa Studio structured-logs module (rc2): services, SignalR observer, API client |
|||
and the viewer page component reused by this module's ABP page (see Pages/). --> |
|||
<PackageReference Include="Elsa.Diagnostics.StructuredLogs" /> |
|||
<PackageReference Include="Elsa.Studio.Diagnostics.StructuredLogs.Dashboard" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.ElsaNext.Studio.Blazor\LINGYUN.Abp.ElsaNext.Studio.Blazor.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<EmbeddedResource Include="Localization\**\*.json" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,7 @@ |
|||
{ |
|||
"culture": "en", |
|||
"texts": { |
|||
"Menu:StructuredLogs": "Structured logs", |
|||
"Logs": "Structured logs" |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue