Add project files.

This commit is contained in:
Behzad Alizadeh
2025-07-02 16:25:46 +03:30
parent 7391bb6626
commit d58167d24a
137 changed files with 85333 additions and 0 deletions
@@ -0,0 +1,107 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageId>Caspian.WorkflowFramework</PackageId>
<Version>1.0.1-preview2</Version>
<Authors>Digital banking team</Authors>
<Company>Caspian</Company>
<Product>Caspian.WorkflowFramework</Product>
<Description>Caspian Workflow Framework</Description>
<PackageProjectUrl>https://caspco.ir</PackageProjectUrl>
<PackageIcon>package-icon.png</PackageIcon>
<PackageReadmeFile>readme.md</PackageReadmeFile>
<PackageTags>caspian, nextbank, workflow</PackageTags>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>caspian-pkg-key.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Caspian.Elsa" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Expressions" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Alterations" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Alterations.Core" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Alterations.MassTransit" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Api.Client" Version="1.0.2-preview1" />
<PackageReference Include="Caspian.Elsa.Api.Common" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Caching" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Caching.Distributed.ProtoActor" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Common" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Connections.Api" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Connections.Core" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Connections.Models" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.CSharp" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Dapper.Migrations" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.DropIns" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.DropIns.Core" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Dsl" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Email" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.EntityFrameworkCore" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.EntityFrameworkCore.Common" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.EntityFrameworkCore.MySql" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.EntityFrameworkCore.Oracle" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.EntityFrameworkCore.PostgreSql" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.EntityFrameworkCore.Sqlite" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.EntityFrameworkCore.SqlServer" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Environments" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Features" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Hangfire" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Hosting.Management" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Http" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Identity" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.JavaScript" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.JavaScript.Libraries" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Liquid" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Mediator" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.ProtoActor.Core" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Python" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Quartz" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Quartz.EntityFrameworkCore.MySql" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Quartz.EntityFrameworkCore.PostgreSql" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Quartz.EntityFrameworkCore.Sqlite" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Quartz.EntityFrameworkCore.SqlServer" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Retention" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.SasTokens" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Scheduling" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Sql.PostgreSql" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Sql.Sqlite" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Sql.SqlServer" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Tenants" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Tenants.AspNetCore" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Testing.Shared" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Testing.Shared.Component" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Testing.Shared.Integration" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Webhooks" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.WorkflowContexts" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.WorkflowProviders.BlobStorage" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Workflows.Api" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Workflows.Core" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Workflows.Management" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Workflows.Runtime" Version="1.0.1" />
<PackageReference Include="Caspian.Elsa.Workflows.Runtime.Distributed" Version="1.0.1" />
<PackageReference Include="JetBrains.Annotations" Version="2024.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.3" />
<PackageReference Include="MongoLogger" Version="1.9.2.8" />
</ItemGroup>
<ItemGroup>
<None Update="LICENSE.txt">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<None Update="package-icon.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<None Update="readme.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
</Project>
@@ -0,0 +1,130 @@
<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>Caspian.WorkflowFramework</id>
<version>1.0.0-preview34</version>
<authors>Digital Banking Team</authors>
<owners>Caspian</owners>
<description>Caspian workflow framework</description>
<projectUrl>https://caspco.ir</projectUrl>
<icon>package-icon.png</icon>
<readme>readme.md</readme>
<tags>caspian, nextbank, workflow</tags>
<license type="file">LICENSE.txt</license>
<dependencies>
<group targetFramework="net9.0">
<dependency id="Microsoft.EntityFrameworkCore" version="*" />
<dependency id="ConfigureAwait.Fody" version="*" />
<dependency id="Cronos" version="*" />
<dependency id="DistributedLock.Core" version="*" />
<dependency id="Fody" version="*" />
<dependency id="IronCompress" version="*" />
<dependency id="JetBrains.Annotations" version="*" />
<dependency id="LinqKit.Core" version="*" />
<dependency id="Microsoft.AspNetCore.Http.Abstractions" version="*" />
<dependency id="Microsoft.Extensions.Configuration" version="*" />
<dependency id="Microsoft.Extensions.Configuration.Json" version="*" />
<dependency id="Microsoft.Extensions.DependencyInjection.Abstractions" version="*" />
<dependency id="Microsoft.Extensions.Hosting" version="*" />
<dependency id="Microsoft.Extensions.Hosting.Abstractions" version="*" />
<dependency id="Microsoft.Extensions.Logging" version="*" />
<dependency id="Microsoft.SourceLink.GitHub" version="*" />
<dependency id="MongoLogger" version="*" />
<dependency id="Open.Linq.AsyncExtensions" version="*" />
<dependency id="Proto.Actor" version="*" />
<dependency id="Proto.Cluster" version="*" />
<dependency id="Proto.Cluster.TestProvider" version="*" />
<dependency id="Proto.OpenTelemetry" version="*" />
<dependency id="Proto.Persistence" version="*" />
<dependency id="Proto.Remote" version="*" />
<dependency id="FastEndpoints" version="*" />
<dependency id="FastEndpoints.Security" version="*" />
<dependency id="FastEndpoints.Swagger" version="*" />
<dependency id="Microsoft.Extensions.DependencyInjection" version="*" />
<dependency id="Microsoft.Extensions.Options" version="*" />
<dependency id="NuGet.Packaging" version="*" />
<dependency id="NuGet.Protocol" version="*" />
<dependency id="System.Formats.Asn1" version="*" />
<dependency id="ThrottleDebounce" version="*" />
<dependency id="Microsoft.Extensions.Options.ConfigurationExtensions" version="*" />
<dependency id="Microsoft.Extensions.Caching.Memory" version="*" />
<dependency id="Scrutor" version="*" />
<dependency id="Google.Protobuf" version="*" />
<dependency id="Grpc.Tools" version="*" />
<dependency id="Proto.Cluster.CodeGen" version="*" />
<dependency id="Microsoft.Data.Sqlite" version="*" />
<dependency id="Microsoft.EntityFrameworkCore.Sqlite" version="*" />
<!-- <dependency id="Microsoft.EntityFrameworkCore.Design" version="*" /> -->
<dependency id="MailKit" version="*" />
<dependency id="Microsoft.Extensions.Http" version="*" />
<dependency id="FluentStorage" version="*" />
<dependency id="Microsoft.Extensions.Http.Resilience" version="*" />
<dependency id="Microsoft.Extensions.Resilience" version="*" />
<dependency id="Microsoft.Extensions.Http.Polly" version="*" />
<dependency id="WebhooksCore" version="*" />
<dependency id="Azure.Identity" version="*" />
<dependency id="Dapper" version="*" />
<dependency id="FluentMigrator" version="*" />
<dependency id="FluentMigrator.Runner" version="*" />
<dependency id="Microsoft.Data.Sqlite.Core" version="*" />
<dependency id="Microsoft.Identity.Client" version="*" />
<!-- <dependency id="Npgsql" /> -->
<dependency id="System.Data.SqlClient" version="*" />
<dependency id="Microsoft.EntityFrameworkCore.Relational" version="*" />
<dependency id="System.CommandLine" version="*" />
<dependency id="DistributedLock.FileSystem" version="*" />
<!-- <dependency id="Microsoft.CodeAnalysis.CSharp.Scripting" version="*" /> -->
<dependency id="Antlr4.Runtime.Standard" version="*" />
<dependency id="System.Linq.Async" version="*" />
<dependency id="Jint" version="*" />
<dependency id="Fluid.Core" version="*" />
<dependency id="pythonnet" version="*" />
<dependency id="Microsoft.AspNetCore.DataProtection.Abstractions" version="*" />
<dependency id="AspNetCore.Authentication.ApiKey" version="*" />
<dependency id="Azure.Messaging.ServiceBus" version="*" />
<dependency id="Azure.ResourceManager.ServiceBus" version="*" />
<dependency id="Confluent.Kafka" version="*" />
<dependency id="Confluent.SchemaRegistry.Serdes.Avro" version="*" />
<dependency id="MassTransit" version="*" />
<dependency id="MassTransit.Azure.ServiceBus.Core" version="*" />
<dependency id="MassTransit.RabbitMQ" version="*" />
<dependency id="Hangfire" version="*" />
<dependency id="Hangfire.MemoryStorage" version="*" />
<dependency id="Hangfire.Storage.SQLite" version="*" />
<dependency id="Quartz.Extensions.Hosting" version="*" />
<dependency id="AppAny.Quartz.EntityFrameworkCore.Migrations.MySql" version="*" />
<dependency id="Quartz.Serialization.Json" version="*" />
<dependency id="AppAny.Quartz.EntityFrameworkCore.Migrations.SqlServer" version="*" />
<dependency id="Humanizer.Core" version="*" />
<dependency id="Newtonsoft.Json" version="*" />
<dependency id="ShortGuid" version="*" />
<dependency id="System.Linq.Dynamic.Core" version="*" />
<dependency id="PolySharp" version="*" />
</group>
</dependencies>
</metadata>
<files>
<!-- Include the favicon -->
<file src="favicon.ico" target="\" />
<!-- Include license, icon, and readme files -->
<file src="LICENSE.txt" target="\" />
<file src="package-icon.png" target="\" />
<file src="readme.md" target="\" />
<!-- Include all required DLLs in the lib folder -->
<file src="bin\release\net9.0\*.dll" target="lib\net9.0" />
<!-- <file src="..\..\common\Elsa.Features\bin\Release\net9.0\Elsa.Features.dll" target="lib\net9.0\Elsa.Features.dll" /> -->
<!-- <file src="..\..\modules\Elsa.CSharp\bin\Release\net9.0\Elsa.CSharp.dll" target="lib\net9.0\Elsa.CSharp.dll" /> -->
<!-- <file src="..\..\modules\Elsa.Email\bin\Release\net9.0\Elsa.Email.dll" target="lib\net9.0\Elsa.Email.dll" /> -->
<!-- <file src="..\..\modules\Elsa.EntityFrameworkCore.Common\bin\Release\net9.0\Elsa.EntityFrameworkCore.Common.dll" target="lib\net9.0\Elsa.EntityFrameworkCore.Common.dll" /> -->
<!-- <file src="..\..\modules\Elsa.EntityFrameworkCore.SqlServer\bin\Release\net9.0\Elsa.EntityFrameworkCore.SqlServer.dll" target="lib\net9.0\Elsa.EntityFrameworkCore.SqlServer.dll" /> -->
<!-- <file src="..\..\modules\Elsa.EntityFrameworkCore\bin\Release\net9.0\Elsa.EntityFrameworkCore.dll" target="lib\net9.0\Elsa.EntityFrameworkCore.dll" /> -->
<!-- <file src="..\..\modules\Elsa.JavaScript\bin\Release\net9.0\Elsa.JavaScript.dll" target="lib\net9.0\Elsa.JavaScript.dll" /> -->
<!-- <file src="..\..\modules\Elsa.Scheduling\bin\Release\net9.0\Elsa.Scheduling.dll" target="lib\net9.0\Elsa.Scheduling.dll" /> -->
<!-- <file src="..\..\modules\Elsa.Workflows.Api\bin\Release\net9.0\Elsa.Workflows.Api.dll" target="lib\net9.0\Elsa.Workflows.Api.dll" /> -->
<!-- <file src="..\..\modules\Elsa.Workflows.Management\bin\Release\net9.0\Elsa.Workflows.Management.dll" target="lib\net9.0\Elsa.Workflows.Management.dll" /> -->
<!-- <file src="..\..\modules\Elsa.Workflows.Runtime\bin\Release\net9.0\Elsa.Workflows.Runtime.dll" target="lib\net9.0\Elsa.Workflows.Runtime.dll" /> -->
<!-- <file src="..\..\modules\Elsa\bin\Release\net9.0\Elsa.dll" target="lib\net9.0\Elsa.dll" /> -->
</files>
</package>
@@ -0,0 +1,72 @@
using Caspian.WorkflowFramework.Contracts;
using Caspian.WorkflowFramework.Services;
using Elsa.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Elsa.EntityFrameworkCore.Extensions;
using Elsa.EntityFrameworkCore.Modules.Management;
using Elsa.EntityFrameworkCore.Modules.Runtime;
using Elsa.EntityFrameworkCore.Modules.Identity;
using Elsa.Email;
using MongoLogger;
using MongoLogger.Models;
namespace Caspian.WorkflowFramework;
public static class DependencyInjectionq
{
public static void AddWorkflowFramework(this IHostApplicationBuilder builder, string connectionString)
{
builder.Services.AddMongoLogger<LogModel>(builder.Configuration);
builder.Services.AddElsa(elsa =>
{
// Configure Management layer to use EF Core.
elsa.UseWorkflowManagement(management => management
.UseEntityFrameworkCore(ef => ef.UseSqlServer(connectionString)));
// Configure Runtime layer to use EF Core.
elsa.UseWorkflowRuntime(runtime => runtime
.UseEntityFrameworkCore(ef => ef.UseSqlServer(connectionString)));
// Expose Elsa API endpoints.
elsa.UseWorkflowsApi();
// Enable C# workflow expressions
elsa.UseCSharp();
// Enable JavaScript workflow expressions
elsa.UseJavaScript(options => options.AllowClrAccess = true);
});
//// Configure CORS to allow designer app hosted on a different origin to invoke the APIs.
//builder.Services.AddCors(cors => cors
// .AddDefaultPolicy(policy => policy
// .AllowAnyOrigin() // For demo purposes only. Use a specific origin instead.
// .AllowAnyHeader()
// .AllowAnyMethod()
// .WithExposedHeaders("x-elsa-workflow-instance-id"))); // Required for Elsa Studio in order to support running workflows from the designer. Alternatively, you can use the `*` wildcard to expose all headers.
// Add Health Checks.
builder.Services.AddHealthChecks();
builder.Services.AddScoped<IWorkflowService, WorkflowService>();
}
public static void UseWorkflowFramework(this IApplicationBuilder app)
{
try
{
app.UseMongoLogger<LogModel>();
}
catch (Exception ex)
{
var d = ex;
}
//app.UseWorkflowsApi(); // Use Elsa API endpoints.
app.UseWorkflows(); // Use Elsa middleware to handle HTTP requests mapped to HTTP Endpoint activities.
}
}
@@ -0,0 +1,29 @@
using Caspian.WorkflowFramework.Models;
namespace Caspian.WorkflowFramework.Contracts
{
public interface IWorkflowService
{
/// <summary>
/// Create and executes a new workflow instance.
/// </summary>
/// <param name="input"></param>
/// <returns>A new <see cref="WorkflowResponse<WorkflowModel>"/> object.</returns>
/// <remarks>Create a new workflow instance and executes it and waits for it to complete or reach a suspend point.</remarks>
Task<WorkflowResponse<WorkflowModel>> CreateAndExecuteAsync(CreateAndExecuteWorkflowModel input);
/// <summary>
/// Publishes the specified event.
/// </summary>
/// <param name="input"></param>
/// <returns>A new <see cref="WorkflowResponse<WorkflowModel>"/> object.</returns>
Task<WorkflowResponse<WorkflowModel>> PublishEventAsync(PublishEventModel input);
/// <summary>
/// Inquiry last state of workflow instance.
/// </summary>
/// <param name="input"></param>
/// <returns>A new <see cref="WorkflowResponse<WorkflowModel>"/> object.</returns>
Task<WorkflowResponse<WorkflowModel>> InquiryStateAsync(InquiryStateModel input);
}
}
@@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait />
</Weavers>
@@ -0,0 +1,3 @@
Terms and Conditions of Use, Reproduction and Distribution are permitted only for Caspian Company.
Copyright [2025] Caspian Company
@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Elsa.Common.Models;
using JetBrains.Annotations;
namespace Caspian.WorkflowFramework.Models
{
[PublicAPI]
public class CreateAndExecuteWorkflowModel
{
/// <summary>
/// Workflow name
/// </summary>
[Required]
public string? Name { get; set; }
/// <summary>
/// Workflow version
/// </summary>
[Required]
public VersionOptions Version { get; set; }
/// <summary>
/// Current user's id
/// </summary>
[Required]
public long? UserId { get; set; }
}
}
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Caspian.WorkflowFramework.Models
{
public class InquiryStateModel
{
[Required]
public string WorkflowInstanceId { get; set; }
}
}
@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Caspian.WorkflowFramework.Models
{
[PublicAPI]
public class PublishEventModel
{
[Required]
public string WorkflowInstanceId { get; set; }
[Required]
public string EventName { get; set; }
[Required]
public string? EventData { get; set; }
}
}
@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Caspian.WorkflowFramework.Models
{
[PublicAPI]
public class WorkflowModel
{
/// <summary>
/// شناسه گردش کار
/// </summary>
public string? InstanceId { get; set; }
/// <summary>
/// نام گردش کار
/// </summary>
public string? Name { get; set; }
/// <summary>
/// اتمام گردش کار؟
/// </summary>
public bool IsComplete { get; set; }
/// <summary>
/// برای کدام کاربر؟
/// </summary>
public long? UserId { get; set; }
/// <summary>
/// داده صفحه جاری
/// </summary>
public string? CurrentPageData { get; set; }
/// <summary>
/// داده صفحه بعد
/// <remark>
/// فعلا استفاده ای نداریم
/// </remark>
/// </summary>
public string? NextPageData { get; set; }
/// <summary>
/// آدرس صفحه بعدی
/// </summary>
public string? NextPageUrl { get; set; }
/// <summary>
/// رویدادی که باید پس از سابمیت صفحه بعدی منتشر شود
/// </summary>
public string? NextPageEventName { get; set; }
}
}
@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Caspian.WorkflowFramework.Models
{
[PublicAPI]
public class WorkflowResponse<T> : WorkflowResponseBase where T : class, new()
{
public WorkflowResponse()
{
Data = new T();
}
/// <summary>
/// پاسخ
/// </summary>
public T? Data { get; set; }
}
[PublicAPI]
public class WorkflowResponseBase
{
/// <summary>
/// وضعیت پاسخ
/// </summary>
public int? Status { get; set; }
/// <summary>
/// پیام پاسخ
/// </summary>
public string? Message { get; set; }
}
}
@@ -0,0 +1,251 @@
using Caspian.WorkflowFramework.Models;
using Caspian.WorkflowFramework.Contracts;
using Newtonsoft.Json;
using Elsa.Workflows.Runtime;
using Microsoft.Extensions.DependencyInjection;
using Elsa.Workflows.Management;
using Elsa.Workflows.Management.Filters;
using Elsa.Workflows.Runtime.Messages;
using Elsa.Workflows.Models;
using Elsa.Common.Models;
using Caspian.WorkflowFramework.Utility;
using MongoLogger;
using System.Diagnostics;
namespace Caspian.WorkflowFramework.Services
{
public class WorkflowService : IWorkflowService, ILoggable
{
#region Fields
private readonly IServiceProvider _serviceProvider;
private readonly IWorkflowRuntime _workflowRuntime;
private readonly IWorkflowDefinitionService _workflowDefinitionService;
private readonly IWorkflowInstanceVariableManager _workflowInstanceVariableManager;
private readonly IWorkflowInstanceManager _workflowInstanceManager;
private readonly IEventPublisher _eventPublisher;
private readonly IMdbLogger<WorkflowService> _logger;
#endregion
#region Ctor
public WorkflowService(IServiceProvider serviceProvider,
IMdbLogger<WorkflowService> logger)
{
_serviceProvider = serviceProvider;
_workflowRuntime = _serviceProvider.GetRequiredService<IWorkflowRuntime>();
_workflowDefinitionService = _serviceProvider.GetRequiredService<IWorkflowDefinitionService>();
_workflowInstanceVariableManager = _serviceProvider.GetRequiredService<IWorkflowInstanceVariableManager>();
_workflowInstanceManager = _serviceProvider.GetRequiredService<IWorkflowInstanceManager>();
_eventPublisher = _serviceProvider.GetRequiredService<IEventPublisher>();
_logger = logger;
}
#endregion
#region CreateAndExecuteAsync
/// <inheritdoc />
public async Task<WorkflowResponse<WorkflowModel>> CreateAndExecuteAsync(CreateAndExecuteWorkflowModel input)
{
try
{
_logger.Log("Start of CreateAndExecuteAsync", JsonConvert.SerializeObject(input));
WorkflowResponse<WorkflowModel> response;
var workflowClient = await _workflowRuntime.CreateClientAsync();
var definition = await _workflowDefinitionService
.FindWorkflowDefinitionAsync(new WorkflowDefinitionFilter()
{
Name = input.Name
});
if (definition == null)
{
var msg = $"Workflow '{input.Name}' defintion not found";
response = new WorkflowResponse<WorkflowModel>()
{
Status = 1,
Message = msg,
Data = null
};
_logger.Log("End of CreateAndExecuteAsync", WFInstanceId: response.Data?.InstanceId, JsonConvert.SerializeObject(response));
return response;
}
var instanceInput = new Dictionary<string, object> { ["UserId"] = input.UserId };
input.Version = VersionOptions.Published;
var newInstance = await workflowClient.CreateInstanceAsync(new CreateWorkflowInstanceRequest
{
WorkflowDefinitionHandle = WorkflowDefinitionHandle.ByDefinitionId(definition.DefinitionId, input.Version),
Input = instanceInput,
ExternalUserId = input.UserId
});
var result = await workflowClient.RunInstanceAsync(RunWorkflowInstanceRequest.Empty);
var variables = await _workflowInstanceVariableManager.GetVariablesAsync(result.WorkflowInstanceId);
// تبدیل به دیکشنری
var dictionary = variables.ToDictionary(a => a.Variable.Name, a => a.Value);
var workflowModel = dictionary.ConvertToWorkflowModel();
workflowModel.InstanceId = workflowClient.WorkflowInstanceId;
workflowModel.UserId = input.UserId;
response = new WorkflowResponse<WorkflowModel>()
{
Status = 0,
Message = "Success",
Data = workflowModel
};
_logger.Log("End of CreateAndExecuteAsync", WFInstanceId: response.Data?.InstanceId, JsonConvert.SerializeObject(response));
return response;
}
catch (Exception ex)
{
var response = new WorkflowResponse<WorkflowModel>()
{
Status = 100,
Message = ex.Message,
Data = null
};
_logger.Log("Exception occurs in CreateAndExecuteAsync", WFInstanceId: response.Data?.InstanceId, JsonConvert.SerializeObject(response), ex);
return response;
}
}
#endregion
#region PublishEventAsync
/// <inheritdoc />
public async Task<WorkflowResponse<WorkflowModel>> PublishEventAsync(PublishEventModel input)
{
try
{
_logger.Log("Start of PublishEventAsync", WFInstanceId: input.WorkflowInstanceId, JsonConvert.SerializeObject(input));
WorkflowResponse<WorkflowModel> response;
var instance = await _workflowInstanceManager.FindByIdAsync(input.WorkflowInstanceId);
if (instance == null)
{
var msg = $"Workflow instance with id '{input.WorkflowInstanceId}' not found";
response = new WorkflowResponse<WorkflowModel>()
{
Status = 1,
Message = msg,
Data = null
};
_logger.Log("End of PublishEventAsync", WFInstanceId: input.WorkflowInstanceId, JsonConvert.SerializeObject(response));
return response;
}
await _eventPublisher.PublishAsync(input.EventName, null, input.WorkflowInstanceId, null, input.EventData);
var variables = await _workflowInstanceVariableManager.GetVariablesAsync(input.WorkflowInstanceId);
// تبدیل به دیکشنری
var dictionary = variables.ToDictionary(a => a.Variable.Name, a => a.Value);
var workflowModel = dictionary.ConvertToWorkflowModel();
workflowModel.InstanceId = instance.Id;
workflowModel.UserId = instance.ExternalUserId;
response = new WorkflowResponse<WorkflowModel>()
{
Status = 0,
Message = "Success",
Data = workflowModel
};
_logger.Log("End of PublishEventAsync", WFInstanceId: input.WorkflowInstanceId, JsonConvert.SerializeObject(response));
return response;
}
catch (Exception ex)
{
var response = new WorkflowResponse<WorkflowModel>()
{
Status = 100,
Message = ex.Message,
Data = null
};
_logger.Log("Exception occurs in PublishEventAsync", WFInstanceId: input.WorkflowInstanceId, JsonConvert.SerializeObject(response), ex);
return response;
}
}
#endregion
#region InquiryStateAsync
/// <inheritdoc />
public async Task<WorkflowResponse<WorkflowModel>> InquiryStateAsync(InquiryStateModel input)
{
try
{
_logger.Log("Start of InquiryStateAsync", WFInstanceId: input.WorkflowInstanceId, JsonConvert.SerializeObject(input));
WorkflowResponse<WorkflowModel> response;
var instance = await _workflowInstanceManager.FindByIdAsync(input.WorkflowInstanceId);
if (instance == null)
{
var msg = $"Workflow instance with id '{input.WorkflowInstanceId}' not found";
response = new WorkflowResponse<WorkflowModel>()
{
Status = 1,
Message = msg,
Data = null
};
_logger.Log("End of InquiryStateAsync", WFInstanceId: input.WorkflowInstanceId, JsonConvert.SerializeObject(response));
return response;
}
var variables = await _workflowInstanceVariableManager.GetVariablesAsync(input.WorkflowInstanceId);
// تبدیل به دیکشنری
var dictionary = variables.ToDictionary(a => a.Variable.Name, a => a.Value);
var workflowModel = dictionary.ConvertToWorkflowModel();
workflowModel.InstanceId = instance.Id;
workflowModel.UserId = instance.ExternalUserId;
response = new WorkflowResponse<WorkflowModel>()
{
Status = 0,
Message = "Success",
Data = workflowModel
};
_logger.Log("End of InquiryStateAsync", WFInstanceId: input.WorkflowInstanceId, JsonConvert.SerializeObject(response));
return response;
}
catch (Exception ex)
{
var response = new WorkflowResponse<WorkflowModel>()
{
Status = 100,
Message = ex.Message,
Data = null
};
_logger.Log("Exception occurs in InquiryStateAsync", WFInstanceId: input.WorkflowInstanceId, JsonConvert.SerializeObject(response), ex);
return response;
}
}
#endregion
}
}
@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Caspian.WorkflowFramework.Models;
namespace Caspian.WorkflowFramework.Utility
{
public static class ConversionExtensions
{
public static WorkflowModel ConvertToWorkflowModel(this Dictionary<string, object?> dictionary)
{
var workflowModel = new WorkflowModel();
if (dictionary == null || dictionary.Count == 0)
return workflowModel;
//Map dictionary values to WorkflowModel properties
workflowModel.InstanceId = dictionary.ContainsKey(nameof(workflowModel.InstanceId))
? dictionary[nameof(workflowModel.InstanceId)]?.ToString()
: null;
workflowModel.Name = dictionary.ContainsKey(nameof(workflowModel.Name))
? dictionary[nameof(workflowModel.Name)]?.ToString()
: null;
workflowModel.IsComplete = dictionary.ContainsKey(nameof(workflowModel.IsComplete))
? bool.TryParse(dictionary[nameof(workflowModel.IsComplete)]?.ToString(), out var isComplete) && isComplete
: false;
workflowModel.UserId = dictionary.ContainsKey(nameof(workflowModel.UserId))
? long.TryParse(dictionary[nameof(workflowModel.UserId)]?.ToString(), out var userId) ? userId : (long?)null
: null;
//workflowModel.CurrentPageData = dictionary.ContainsKey(nameof(workflowModel.CurrentPageData))
// ? (object?)dictionary[nameof(workflowModel.CurrentPageData)]
// : null;
workflowModel.CurrentPageData = dictionary.ContainsKey(nameof(workflowModel.CurrentPageData))
? dictionary[nameof(workflowModel.CurrentPageData)]?.ToString()
: null;
//workflowModel.NextPageData = dictionary.ContainsKey(nameof(workflowModel.NextPageData))
// ? (object?)dictionary[nameof(workflowModel.NextPageData)]
// : null;
workflowModel.NextPageData = dictionary.ContainsKey(nameof(workflowModel.NextPageData))
? dictionary[nameof(workflowModel.NextPageData)]?.ToString()
: null;
workflowModel.NextPageUrl = dictionary.ContainsKey(nameof(workflowModel.NextPageUrl))
? dictionary[nameof(workflowModel.NextPageUrl)]?.ToString()
: null;
workflowModel.NextPageEventName = dictionary.ContainsKey(nameof(workflowModel.NextPageEventName))
? dictionary[nameof(workflowModel.NextPageEventName)]?.ToString()
: null;
return workflowModel;
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.
@@ -0,0 +1 @@
nuget pack Caspian.WorkflowFramework.nuspec -IncludeReferencedProjects
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@@ -0,0 +1,37 @@
# Caspian.WorkflowFramework
Caspian workflow framework
## Installation
```bash
install-package Caspian.WorkflowFramework
```
## Usage Example
In your Program.cs :
```csharp
//Workflow
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.AddWorkflowFramework(connectionString);
//Mongo
builder.Services.AddMongoLogger<LogModel>(builder.Configuration);
var app = builder.Build();
//Mongo
app.UseMongoLogger<LogModel>();
//Workflow
app.UseWorkflowFramework();
```
Then use service "WorkflowService"
## Features
- Start new instance of every workflow
- Publish event and get next step information
- Get last status of workflow instance
## Version History
### 1.0.0-alfa
- Initial prerelease
+31
View File
@@ -0,0 +1,31 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.13.35818.85
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Caspian.WorkflowFramework.Package", "Caspian.WorkflowFramework.Package\Caspian.WorkflowFramework.Package.csproj", "{36F9F180-CF48-46E9-8BA7-DD82BC4BFE61}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleWorkflowWeb", "SampleWorkflowWeb\SampleWorkflowWeb.csproj", "{CB39AFCA-747E-4329-BEEC-22CC25E9C2E1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{36F9F180-CF48-46E9-8BA7-DD82BC4BFE61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{36F9F180-CF48-46E9-8BA7-DD82BC4BFE61}.Debug|Any CPU.Build.0 = Debug|Any CPU
{36F9F180-CF48-46E9-8BA7-DD82BC4BFE61}.Release|Any CPU.ActiveCfg = Release|Any CPU
{36F9F180-CF48-46E9-8BA7-DD82BC4BFE61}.Release|Any CPU.Build.0 = Release|Any CPU
{CB39AFCA-747E-4329-BEEC-22CC25E9C2E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CB39AFCA-747E-4329-BEEC-22CC25E9C2E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CB39AFCA-747E-4329-BEEC-22CC25E9C2E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CB39AFCA-747E-4329-BEEC-22CC25E9C2E1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {24AE9A23-AC7E-49BF-9729-98A3C07B7646}
EndGlobalSection
EndGlobal
@@ -0,0 +1,219 @@
using Microsoft.AspNetCore.Mvc;
using SampleWorkflowWeb.Models;
using Newtonsoft.Json;
using SampleWorkflowWeb.Services;
using SampleWorkflowWeb.Dao;
using MongoLogger;
using Caspian.WorkflowFramework.Contracts;
using Elsa.Common.Models;
using PublishEventModel = Caspian.WorkflowFramework.Models.PublishEventModel;
using PackageNS = Caspian.WorkflowFramework.Models;
namespace SampleWorkflowWeb.Controllers;
public class HomeController : Controller, ILoggable
{
#region Fields
private readonly ILogger<HomeController> _logger;
private readonly ICardService _cardService;
private readonly IWorkflowService _workflowService;
#endregion
#region Ctor
public HomeController(ILogger<HomeController> logger,
ICardService cardService,
IWorkflowService workflowService)
{
_logger = logger;
_cardService = cardService;
_workflowService = workflowService;
}
#endregion
#region Index
public async Task<IActionResult> Index()
{
return View();
}
#endregion
#region Inquiry
public async Task<IActionResult> Inquiry()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Inquiry(InquiryViewModel input)
{
var response = await _workflowService.InquiryStateAsync(new PackageNS.InquiryStateModel()
{
WorkflowInstanceId = input.WorkflowId
});
if (response != null && response.Status == 0)
{
return View("InquiryResult", response);
}
else
{
return RedirectToAction("Error", response);
}
}
#endregion
#region CardToCard
public async Task<IActionResult> CardToCard()
{
var response = await _workflowService.CreateAndExecuteAsync(new PackageNS.CreateAndExecuteWorkflowModel()
{
Name = "CardToCardWorkflow",
UserId = 114,
Version = VersionOptions.Published
});
if (response != null && response.Status == 0)
{
var form100Input = new Form100ViewModel()
{
WorkflowId = response.Data?.InstanceId,
PageEventName = response.Data?.NextPageEventName
};
//return View(response.Data?.NextPageUrl, form100Input);
return RedirectToAction(response.Data?.NextPageUrl, form100Input);
}
else
{
return RedirectToAction("Error", response);
}
}
#endregion
#region Form100
public async Task<IActionResult> Form100(Form100ViewModel input, string WorkflowId)
{
ViewBag.SourceCards = _cardService.GetSourceCardsService();
ViewBag.DestinationCards = _cardService.GetDestinationCardsService();
return View(input);
}
[HttpPost]
public async Task<IActionResult> Form100(Form100ViewModel input)
{
if (!ModelState.IsValid)
{
ViewBag.SourceCards = _cardService.GetSourceCardsService();
ViewBag.DestinationCards = _cardService.GetDestinationCardsService();
return View(input);
}
var publishModel = new PublishEventModel()
{
EventName = input.PageEventName,
WorkflowInstanceId = input.WorkflowId,
EventData = JsonConvert.SerializeObject(input)
};
var response = await _workflowService.PublishEventAsync(publishModel);
if (response != null && response.Status == 0)
{
var form101Input = new Form101ViewModel()
{
From = input.From,
To = input.To,
Amount = input.Amount,
WorkflowId = response.Data?.InstanceId,
PageEventName = response.Data?.NextPageEventName
};
//return View(response.Data?.NextPageUrl, form101Input);
return RedirectToAction(response.Data?.NextPageUrl, form101Input);
}
else
{
return RedirectToAction("Error", response);
}
}
#endregion
#region Form101
public IActionResult Form101(Form101ViewModel input, string WorkflowId)
{
input.FromPan = _cardService.GetCardService(input.From)?.PAN;
input.FromFullName = _cardService.GetCardOwnerService(input.From);
input.ToPan = _cardService.GetCardService(input.To)?.PAN;
input.ToFullName = _cardService.GetCardOwnerService(input.To);
return View(input);
}
[HttpPost]
public async Task<IActionResult> Form101(Form101ViewModel input)
{
if (!ModelState.IsValid)
{
input.FromPan = _cardService.GetCardService(input.From)?.PAN;
input.FromFullName = _cardService.GetCardOwnerService(input.From);
input.ToPan = _cardService.GetCardService(input.To)?.PAN;
input.ToFullName = _cardService.GetCardOwnerService(input.To);
return View(input);
}
//انتقال
var result = await _cardService.TransferAsync(input.FromPan, input.ToPan, input.Amount);
var publishEventModel = new PublishEventModel()
{
EventName = input.PageEventName,
WorkflowInstanceId = input.WorkflowId,
EventData = JsonConvert.SerializeObject(input)
};
var response = await _workflowService.PublishEventAsync(publishEventModel);
if (response != null && response.Status == 0)
{
var completeInput = new CompleteViewModel()
{
From = input.From,
FromPan = input.FromPan,
FromFullName = input.FromFullName,
To = input.To,
ToPan = input.ToPan,
ToFullName = input.ToFullName,
Amount = input.Amount,
WorkflowId = response.Data?.InstanceId,
PageEventName = response.Data?.NextPageEventName
};
//return View(response.Data?.NextPageUrl, completeInput);
return RedirectToAction(response.Data?.NextPageUrl, completeInput);
}
else
{
return RedirectToAction("Error", response);
}
}
#endregion
#region Complete
public IActionResult Complete(CompleteViewModel input)
{
return View(input);
}
#endregion
#region Error
public IActionResult Error(WorkflowResponse<object> input)
{
return View(input);
}
#endregion
}
@@ -0,0 +1,310 @@
using SampleWorkflowWeb.Models;
using SampleWorkflowWeb.Models.Transfer;
using SampleWorkflowWeb.Services;
using Caspian.WorkflowFramework.Contracts;
using PackageNS = Caspian.WorkflowFramework.Models;
using Elsa.Common.Models;
using Microsoft.AspNetCore.Mvc;
using MongoLogger;
using Newtonsoft.Json;
using Microsoft.AspNetCore.Http;
namespace SampleWorkflowWeb.Controllers
{
public class TransferController : Controller, ILoggable
{
#region Fields
private readonly ILogger<HomeController> _logger;
private readonly ICardService _cardService;
private readonly IWorkflowService _workflowService;
#endregion
#region Ctor
public TransferController(ILogger<HomeController> logger,
ICardService cardService,
IWorkflowService workflowService)
{
_logger = logger;
_cardService = cardService;
_workflowService = workflowService;
}
#endregion
#region Index
public IActionResult Index()
{
return View();
}
#endregion
#region TransferAmount
[HttpGet]
public async Task<IActionResult> TransferAmount(long userId)
{
var response = await _workflowService.CreateAndExecuteAsync(new PackageNS.CreateAndExecuteWorkflowModel()
{
Name = "TransferWorkflow",
UserId = userId,
Version = VersionOptions.Published
});
if (response != null && response.Status == 0)
{
var input = new SelectionViewModel()
{
WorkflowId = response.Data?.InstanceId,
PageEventName = response.Data?.NextPageEventName
};
HttpContext.Session.SetObject("SelectionSession", input);
//return View(response.Data?.NextPageUrl, input);
return RedirectToAction(response.Data?.NextPageUrl);
}
else
{
return RedirectToAction("Error", response);
}
}
#endregion
#region Selection
public async Task<IActionResult> Selection()
{
// Retrieve from session
var model = HttpContext.Session.GetObject<SelectionViewModel>("SelectionSession");
HttpContext.Session.Remove("SelectionSession");
ViewBag.SourceCards = _cardService.GetSourceCardsService();
ViewBag.DestinationCards = _cardService.GetDestinationCardsService();
return View(model);
}
[HttpPost]
public async Task<IActionResult> Selection(SelectionViewModel input)
{
if (!ModelState.IsValid)
{
ViewBag.SourceCards = _cardService.GetSourceCardsService();
ViewBag.DestinationCards = _cardService.GetDestinationCardsService();
return View(input);
}
var publishModel = new PackageNS.PublishEventModel()
{
EventName = input.PageEventName,
WorkflowInstanceId = input.WorkflowId,
EventData = JsonConvert.SerializeObject(input)
};
var response = await _workflowService.PublishEventAsync(publishModel);
if (response != null && response.Status == 0)
{
if (response.Data?.NextPageUrl == "Confirm")
{
var fromPan = _cardService.GetCardService(input.From)?.PAN;
var fromFullName = _cardService.GetCardOwnerService(input.From);
var toPan = _cardService.GetCardService(input.To)?.PAN;
var toFullName = _cardService.GetCardOwnerService(input.To);
var confirmViewModel = new ConfirmViewModel()
{
From = input.From,
FromPan = fromPan,
FromFullName = fromFullName,
To = input.To,
ToPan = toPan,
ToFullName = toFullName,
Amount = input.Amount,
PersonType = input.PersonType,
WorkflowId = response.Data?.InstanceId,
PageEventName = response.Data?.NextPageEventName
};
HttpContext.Session.SetObject("ConfirmSession", confirmViewModel);
//return View(response.Data?.NextPageUrl, confirmViewModel);
return RedirectToAction(response.Data?.NextPageUrl);
}
else if (response.Data?.NextPageUrl == "Cartable")
{
var viewModel = new CartableViewModel()
{
From = input.From,
To = input.To,
Amount = input.Amount,
PersonType = input.PersonType,
WorkflowId = response.Data?.InstanceId,
PageEventName = response.Data?.NextPageEventName
};
HttpContext.Session.SetObject("CartableSession", viewModel);
return RedirectToAction(response.Data?.NextPageUrl);
}
else
{
ViewBag.SourceCards = _cardService.GetSourceCardsService();
ViewBag.DestinationCards = _cardService.GetDestinationCardsService();
return View(input);
}
}
else
{
return RedirectToAction("Error", response);
}
}
#endregion
#region Cartable
public async Task<IActionResult> Cartable()
{
// Retrieve from session
var model = HttpContext.Session.GetObject<CartableViewModel>("CartableSession");
HttpContext.Session.Remove("CartableSession");
return View(model);
}
[HttpPost]
public async Task<IActionResult> Cartable(CartableViewModel input)
{
if (!ModelState.IsValid)
{
//ViewBag.SourceCards = _cardService.GetSourceCardsService();
//ViewBag.DestinationCards = _cardService.GetDestinationCardsService();
return View(input);
}
var publishModel = new PackageNS.PublishEventModel()
{
EventName = input.PageEventName,
WorkflowInstanceId = input.WorkflowId,
EventData = JsonConvert.SerializeObject(input)
};
var response = await _workflowService.PublishEventAsync(publishModel);
if (response != null && response.Status == 0)
{
var fromPan = _cardService.GetCardService(input.From)?.PAN;
var fromFullName = _cardService.GetCardOwnerService(input.From);
var toPan = _cardService.GetCardService(input.To)?.PAN;
var toFullName = _cardService.GetCardOwnerService(input.To);
var viewModel = new ConfirmViewModel()
{
From = input.From,
FromPan = fromPan,
FromFullName = fromFullName,
To = input.To,
ToPan = toPan,
ToFullName = toFullName,
Amount = input.Amount,
PersonType = input.PersonType,
WorkflowId = response.Data?.InstanceId,
PageEventName = response.Data?.NextPageEventName
};
HttpContext.Session.SetObject("ConfirmSession", viewModel);
return RedirectToAction(response.Data?.NextPageUrl);
}
else
{
return RedirectToAction("Error", response);
}
}
#endregion
#region Confirm
public async Task<IActionResult> Confirm()
{
// Retrieve from session
var model = HttpContext.Session.GetObject<ConfirmViewModel>("ConfirmSession");
HttpContext.Session.Remove("ConfirmSession");
return View(model);
}
[HttpPost]
public async Task<IActionResult> Confirm(ConfirmViewModel input)
{
input.FromPan = _cardService.GetCardService(input.From)?.PAN;
input.FromFullName = _cardService.GetCardOwnerService(input.From);
input.ToPan = _cardService.GetCardService(input.To)?.PAN;
input.ToFullName = _cardService.GetCardOwnerService(input.To);
if (!ModelState.IsValid)
{
return View(input);
}
//انتقال
var result = await _cardService.TransferAsync(input.FromPan, input.ToPan, input.Amount);
var publishModel = new PackageNS.PublishEventModel()
{
EventName = input.PageEventName,
WorkflowInstanceId = input.WorkflowId,
EventData = JsonConvert.SerializeObject(input)
};
var response = await _workflowService.PublishEventAsync(publishModel);
if (response != null && response.Status == 0)
{
var receiptInput = new ReceiptViewModel()
{
From = input.From,
FromPan = input.FromPan,
FromFullName = input.FromFullName,
To = input.To,
ToPan = input.ToPan,
ToFullName = input.ToFullName,
Amount = input.Amount,
WorkflowId = response.Data?.InstanceId,
PageEventName = response.Data?.NextPageEventName
};
HttpContext.Session.SetObject("ReceiptSession", receiptInput);
return RedirectToAction(response.Data?.NextPageUrl);
}
else
{
return RedirectToAction("Error", response);
}
}
#endregion
#region Confirm
public async Task<IActionResult> Receipt()
{
// Retrieve from session
var model = HttpContext.Session.GetObject<ReceiptViewModel>("ReceiptSession");
HttpContext.Session.Remove("ReceiptSession");
return View(model);
}
#endregion
#region Error
public IActionResult Error(WorkflowResponse<object> input)
{
return View(input);
}
#endregion
}
}
+16
View File
@@ -0,0 +1,16 @@
using System.ComponentModel.DataAnnotations;
namespace SampleWorkflowWeb.Dao;
public class GetStateDao
{
/// <summary>
/// شناسه گردش کار
/// <remark>
/// Type: Guid
/// </remark>
/// </summary>
[Display(Name = "شناسه گردش کار")]
[Required(ErrorMessage = "{0} اجباری است")]
public string InstanceId { get; set; }
}
+19
View File
@@ -0,0 +1,19 @@
using System.ComponentModel.DataAnnotations;
namespace SampleWorkflowWeb.Dao;
public class PublishEventDao
{
[Display(Name = "نام رویداد")]
[Required(ErrorMessage = "{0} اجباری است")]
public string EventName { get; set; }
[Display(Name = "شناسه گردش کار")]
[Required(ErrorMessage = "{0} اجباری است")]
public string EventKey { get; set; }
[Display(Name = "داده های صفحه جاری")]
[Required(ErrorMessage = "{0} اجباری است")]
public string EventData { get; set; }
}
+18
View File
@@ -0,0 +1,18 @@
using System.ComponentModel.DataAnnotations;
namespace SampleWorkflowWeb.Dao;
public class StartWorkflowDao
{
[Display(Name = "نام گردش کار")]
[Required(ErrorMessage = "{0} اجباری است")]
public string Name { get; set; }
[Display(Name = "نسخه گردش کار")]
[Required(ErrorMessage = "{0} اجباری است")]
public int Version { get; set; }
[Display(Name = "شناسه کاربر جاری")]
[Required(ErrorMessage = "{0} اجباری است")]
public long UserId { get; set; }
}
+19
View File
@@ -0,0 +1,19 @@
using System.ComponentModel.DataAnnotations;
namespace SampleWorkflowWeb.Enums
{
public enum PersonType
{
/// <summary>
/// حقوقی
/// </summary>
[Display(Name = "حقوقی")]
Legal = 1,
/// <summary>
/// حقیقی
/// </summary>
[Display(Name = "حقیقی")]
Natural = 2
}
}
+16
View File
@@ -0,0 +1,16 @@
using System.ComponentModel.DataAnnotations;
namespace SampleWorkflowWeb.Enums
{
public enum PriorityType
{
[Display(Name ="بالا")]
High,
[Display(Name ="متوسط")]
Middle,
[Display(Name ="کم")]
Low
}
}
+3
View File
@@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait />
</Weavers>
@@ -0,0 +1,16 @@
using System.ComponentModel.DataAnnotations;
namespace SampleWorkflowWeb.Models
{
public class ViewModelBase
{
[Required]
public string WorkflowId { get; set; }
/// <summary>
/// رویدادی که باید پس از سابمیت صفحه بعدی منتشر شود
/// </summary>
[Required]
public string PageEventName { get; set; }
}
}
@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SampleWorkflowWeb.Models
{
public class WorkflowResponse<T> : WorkflowResponseBase where T : class, new()
{
public WorkflowResponse()
{
Data = new T();
}
/// <summary>
/// پاسخ
/// </summary>
public T? Data { get; set; }
}
public class WorkflowResponseBase
{
/// <summary>
/// وضعیت پاسخ
/// </summary>
public int? Status { get; set; }
/// <summary>
/// پیام پاسخ
/// </summary>
public string? Message { get; set; }
}
}
@@ -0,0 +1,13 @@
namespace SampleWorkflowWeb.Models
{
public class CardModel
{
public int Id { get; set; }
public string PAN { get; set; }
public string FullName { get; set; }
public string BankName { get; set; }
}
}
@@ -0,0 +1,15 @@
namespace SampleWorkflowWeb.Models
{
public class CompleteViewModel
{
public int From { get; set; }
public string? FromPan { get; set; }
public string? FromFullName { get; set; }
public int To { get; set; }
public string? ToPan { get; set; }
public string? ToFullName { get; set; }
public long? Amount { get; set; }
public string WorkflowId { get; set; }
public string PageEventName { get; set; }
}
}
@@ -0,0 +1,9 @@
namespace SampleWorkflowWeb.Models
{
public class ErrorViewModel
{
public string? RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
}
}
@@ -0,0 +1,15 @@
namespace SampleWorkflowWeb.Models
{
public class Form100ViewModel
{
public int From { get; set; }
public int To { get; set; }
public long? Amount { get; set; }
public string? WorkflowId { get; set; }
/// <summary>
/// رویدادی که باید پس از سابمیت صفحه بعدی منتشر شود
/// </summary>
public string? PageEventName { get; set; }
}
}
@@ -0,0 +1,15 @@
namespace SampleWorkflowWeb.Models
{
public class Form101ViewModel
{
public int From { get; set; }
public string? FromPan { get; set; }
public string? FromFullName { get; set; }
public int To { get; set; }
public string? ToPan { get; set; }
public string? ToFullName { get; set; }
public long? Amount { get; set; }
public string WorkflowId { get; set; }
public string PageEventName { get; set; }
}
}
@@ -0,0 +1,7 @@
namespace SampleWorkflowWeb.Models
{
public class InquiryViewModel
{
public string WorkflowId { get; set; }
}
}
@@ -0,0 +1,9 @@
namespace SampleWorkflowWeb.Models
{
public class PublishEventModel
{
public string EventName { get; set; }
public string EventKey { get; set; }
public object EventData { get; set; }
}
}
@@ -0,0 +1,10 @@
namespace SampleWorkflowWeb.Models
{
public class StartWorkflowModel
{
public string Name { get; set; }
public int? Version { get; set; }
public string Reference { get; set; }
public object WfModel { get; set; }
}
}
@@ -0,0 +1,56 @@
namespace SampleWorkflowWeb.Models
{
public class WorkflowModel
{
/// <summary>
/// شناسه گردش کار
/// <remark>
/// Type: Guid
/// </remark>
/// </summary>
public string? InstanceId { get; set; }
/// <summary>
/// نام گردش کار
/// </summary>
public string? Name { get; set; }
/// <summary>
/// اتمام گردش کار؟
/// </summary>
public bool IsComplete { get; set; }
/// <summary>
/// برای کدام کاربر؟
/// </summary>
public long? UserId { get; set; }
/// <summary>
/// داده صفحه جاری
/// </summary>
public string? CurrentPageData { get; set; }
/// <summary>
/// داده صفحه بعد
/// <remark>
/// فعلا استفاده ای نداریم
/// </remark>
/// </summary>
public string? NextPageData { get; set; }
/// <summary>
/// آدرس صفحه بعدی
/// </summary>
public string? NextPageUrl { get; set; }
/// <summary>
/// رویدادی که باید پس از سابمیت صفحه بعدی منتشر شود
/// </summary>
public string? NextPageEventName { get; set; }
/// <summary>
/// کلید رویداد
/// </summary>
public string? NextPageEventKey { get; set; }
}
}
@@ -0,0 +1,33 @@
using SampleWorkflowWeb.Enums;
using System.ComponentModel.DataAnnotations;
using System.Reflection.Metadata.Ecma335;
namespace SampleWorkflowWeb.Models.Transfer
{
public class CartableViewModel : ViewModelBase
{
[Required]
public int From { get; set; }
[Required]
public int To { get; set; }
[Required]
public long? Amount { get; set; }
[Required]
public PersonType PersonType { get; set; }
[Required]
public string ExpireDate { get; set; }
[Required]
public PriorityType PriorityType { get; set; }
[Required]
public string SenderNote { get; set; }
[Required]
public string ReceiverNote { get; set; }
}
}
@@ -0,0 +1,18 @@
using SampleWorkflowWeb.Enums;
namespace SampleWorkflowWeb.Models.Transfer
{
public class ConfirmViewModel
{
public int From { get; set; }
public string? FromPan { get; set; }
public string? FromFullName { get; set; }
public int To { get; set; }
public string? ToPan { get; set; }
public string? ToFullName { get; set; }
public long? Amount { get; set; }
public PersonType PersonType { get; set; }
public string WorkflowId { get; set; }
public string PageEventName { get; set; }
}
}
@@ -0,0 +1,15 @@
namespace SampleWorkflowWeb.Models
{
public class ReceiptViewModel
{
public int From { get; set; }
public string? FromPan { get; set; }
public string? FromFullName { get; set; }
public int To { get; set; }
public string? ToPan { get; set; }
public string? ToFullName { get; set; }
public long? Amount { get; set; }
public string WorkflowId { get; set; }
public string PageEventName { get; set; }
}
}
@@ -0,0 +1,19 @@
using SampleWorkflowWeb.Enums;
using System.ComponentModel.DataAnnotations;
namespace SampleWorkflowWeb.Models.Transfer
{
public class SelectionViewModel : ViewModelBase
{
[Required]
public int From { get; set; }
[Required]
public int To { get; set; }
[Required]
public long? Amount { get; set; }
public PersonType PersonType { get; set; }
}
}
+51
View File
@@ -0,0 +1,51 @@
using SampleWorkflowWeb.Services;
using Caspian.WorkflowFramework;
using MongoLogger;
using MongoLogger.Models;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
//Workflow
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.AddWorkflowFramework(connectionString);
builder.Services.AddMongoLogger<LogModel>(builder.Configuration);
builder.Services.AddSingleton<ICardService, CardService>();
builder.Services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(30); // Adjust as needed
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
var app = builder.Build();
app.UseMongoLogger<LogModel>();
//Workflow
app.UseWorkflowFramework();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
}
app.UseRouting();
app.UseAuthorization();
app.MapStaticAssets();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}")
.WithStaticAssets();
app.UseSession();
app.Run();
@@ -0,0 +1,14 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5294",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Caspian.WorkflowFramework.Package\Caspian.WorkflowFramework.Package.csproj" />
</ItemGroup>
</Project>
+88
View File
@@ -0,0 +1,88 @@
using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.AspNetCore.Mvc.Rendering;
using System;
using SampleWorkflowWeb.Models;
namespace SampleWorkflowWeb.Services
{
public class CardService : ICardService
{
#region Fields
private List<CardModel> _cards;
#endregion
#region Ctor
public CardService()
{
_cards = new List<CardModel>();
_cards.Add(new CardModel() { Id = 1, PAN = "6221-0612-2862-5721", BankName = "پارسیان", FullName = "بهزاد علیزاده" });
_cards.Add(new CardModel() { Id = 2, PAN = "6221-0612-2577-8317", BankName = "پارسیان", FullName = "بهزاد علیزاده" });
_cards.Add(new CardModel() { Id = 3, PAN = "6037-6915-6533-2053", BankName = "صادرات", FullName = "ذکربا تاجمیری" });
_cards.Add(new CardModel() { Id = 4, PAN = "6037-6915-6533-2053", BankName = "ملی", FullName = "مصطفی حسنی کلشتری" });
_cards.Add(new CardModel() { Id = 5, PAN = "6219-8619-9535-2407", BankName = "سامان", FullName = "علی محمدی" });
_cards.Add(new CardModel() { Id = 6, PAN = "5022-2910-9783-9243", BankName = "پاسارگاد", FullName = "اکبر وفایی مزید" });
_cards.Add(new CardModel() { Id = 7, PAN = "6037-6916-2463-8961", BankName = "صادرات", FullName = "رضا پازوکی" });
_cards.Add(new CardModel() { Id = 8, PAN = "5892-1016-2042-8678", BankName = "سپه", FullName = "حسن بهرامی" });
}
#endregion
#region GetSourceCardsService
public SelectList GetSourceCardsService()
{
var myCards = _cards
.Select(x => new { Id = x.Id, Title = $"{x.PAN}" })
.Take(2);
return new SelectList(myCards, "Id", "Title");
}
#endregion
#region GetDestinationCardsService
public SelectList GetDestinationCardsService()
{
var cards = _cards
.Select(x => new { Id = x.Id, Title = $"{x.PAN}" })
.Skip(2);
return new SelectList(cards, "Id", "Title");
}
#endregion
#region GetCardService
public CardModel? GetCardService(int id)
{
var card = _cards
.FirstOrDefault(x => x.Id == id);
return card;
}
#endregion
#region GetCardOwnerService
public string? GetCardOwnerService(int id)
{
var fullName = _cards
.FirstOrDefault(x => x.Id == id)?
.FullName;
return fullName;
}
#endregion
#region TransferAsync
public async Task<bool> TransferAsync(string sourcePan, string destinationPan, long? amount)
{
return true;
}
#endregion
}
public interface ICardService
{
SelectList GetSourceCardsService();
SelectList GetDestinationCardsService();
CardModel? GetCardService(int id);
string? GetCardOwnerService(int id);
Task<bool> TransferAsync(string sourcePan, string destinationPan, long? amount);
}
}
@@ -0,0 +1,22 @@
using Newtonsoft.Json;
namespace SampleWorkflowWeb
{
public static class SessionExtensions
{
public static void SetObject<T>(this ISession session, string key, T value)
{
var data = JsonConvert.SerializeObject(value);
session.SetString(key, data);
}
public static T GetObject<T>(this ISession session, string key)
{
var data = session.GetString(key);
if (string.IsNullOrEmpty(data))
return default(T);
return JsonConvert.DeserializeObject<T>(data);
}
}
}
@@ -0,0 +1 @@

@@ -0,0 +1,40 @@
@model SampleWorkflowWeb.Models.CompleteViewModel
@{
ViewData["Title"] = "Receipt";
}
<div class="container">
<div class="row">
<aside class="col-sm-4">
</aside>
<aside class="col-sm-4">
<div class="card">
<article class="card-body">
<h4 class="card-title mb-4 mt-1">@ViewData["Title"]</h4>
<form asp-action="Complete" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
@Html.HiddenFor(x => x.WorkflowId)
@Html.HiddenFor(x => x.PageEventName)
@Html.HiddenFor(x => x.From)
@Html.HiddenFor(x => x.To)
@Html.HiddenFor(x => x.Amount)
@Html.HiddenFor(x => x.WorkflowId)
<div class="form-group">
<label asp-for="From"></label>
<input asp-for="FromPan" class="form-control" disabled />
<input asp-for="FromFullName" class="form-control mt-2" disabled />
</div>
<div class="form-group">
<label asp-for="To"></label>
<input asp-for="ToPan" class="form-control" disabled />
<input asp-for="ToFullName" class="form-control mt-2" disabled />
</div>
<div class="form-group">
<label asp-for="Amount"></label>
<input asp-for="Amount" class="form-control" disabled />
</div>
</form>
</article>
</div>
</aside>
</div>
</div>
+10
View File
@@ -0,0 +1,10 @@
@using SampleWorkflowWeb.Models
@model WorkflowResponse<Object>
@{
ViewData["Title"] = "Error Page";
}
<div class="text-center">
<h4>خطای @Model.Status</h4>
<p>@Model.Message</p>
</div>
@@ -0,0 +1,49 @@
@model SampleWorkflowWeb.Models.Form100ViewModel
@{
ViewData["Title"] = "Card To Card";
}
<div class="container">
<div class="row">
<aside class="col-sm-4">
</aside>
<aside class="col-sm-4">
<div class="card">
<article class="card-body">
<h4 class="card-title mb-4 mt-1">@ViewData["Title"]</h4>
@*<form asp-action="Form100" method="post">*@
@using(Html.BeginForm("Form100", "Home", FormMethod.Post))
{
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
@Html.HiddenFor(x => x.WorkflowId)
@Html.HiddenFor(x => x.PageEventName)
<div class="form-group mt-2">
<label asp-for="From"></label>
<select asp-for="From" asp-items="@ViewBag.SourceCards as SelectList" class="form-control"></select>
<span asp-validation-for="From" class="text-danger"></span>
</div>
<div class="form-group mt-2">
<label asp-for="To"></label>
<select asp-for="To" asp-items="@ViewBag.DestinationCards as SelectList" class="form-control"></select>
<span asp-validation-for="To" class="text-danger"></span>
</div>
<div class="form-group mt-2">
<label asp-for="Amount"></label>
<input asp-for="Amount" class="form-control" />
<span asp-validation-for="Amount" class="text-danger"></span>
</div>
<div class="form-group mt-2">
<button type="submit" class="btn btn-primary btn-block">Submit</button>
</div>
}
@*</form>*@
</article>
</div>
</aside>
</div>
</div>
@section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
}
@@ -0,0 +1,50 @@
@model SampleWorkflowWeb.Models.Form101ViewModel
@{
ViewData["Title"] = "Confirm";
}
<div class="container">
<div class="row">
<aside class="col-sm-4">
</aside>
<aside class="col-sm-4">
<div class="card">
<article class="card-body">
<h4 class="card-title mb-4 mt-1">@ViewData["Title"]</h4>
<form asp-action="Form101" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
@Html.HiddenFor(x => x.WorkflowId)
@Html.HiddenFor(x => x.PageEventName)
@Html.HiddenFor(x => x.From)
@Html.HiddenFor(x => x.FromPan)
@Html.HiddenFor(x => x.FromFullName)
@Html.HiddenFor(x => x.To)
@Html.HiddenFor(x => x.ToPan)
@Html.HiddenFor(x => x.ToFullName)
@Html.HiddenFor(x => x.Amount)
@Html.HiddenFor(x => x.WorkflowId)
<div class="form-group mt-2">
<label asp-for="From"></label>
<input asp-for="FromPan" class="form-control" disabled />
<input asp-for="FromFullName" class="form-control mt-2" disabled />
</div>
<div class="form-group mt-2">
<label asp-for="To"></label>
<input asp-for="ToPan" class="form-control" disabled />
<input asp-for="ToFullName" class="form-control mt-2" disabled />
</div>
<div class="form-group mt-2">
<label asp-for="Amount"></label>
<input asp-for="Amount" class="form-control" disabled />
<span asp-validation-for="Amount" class="text-danger"></span>
</div>
<div class="form-group mt-2">
<button type="submit" class="btn btn-warning btn-block">Confirm</button>
</div>
</form>
</article>
</div>
</aside>
</div>
</div>
@@ -0,0 +1,7 @@
@{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
<a href="/Home/CardToCard" class="btn btn-success">کارت به کارت</a>
</div>
@@ -0,0 +1,34 @@
@model SampleWorkflowWeb.Models.InquiryViewModel
@{
ViewData["Title"] = "Inquiry";
}
<div class="container">
<div class="row">
<aside class="col-sm-4">
</aside>
<aside class="col-sm-4">
<div class="card">
<article class="card-body">
<h4 class="card-title mb-4 mt-1">@ViewData["Title"]</h4>
@using(Html.BeginForm("Inquiry", "Home", FormMethod.Post))
{
<div class="form-group">
<label asp-for="WorkflowId"></label>
<input asp-for="WorkflowId" class="form-control" />
<span asp-validation-for="WorkflowId" class="text-danger"></span>
</div>
<div class="form-group mt-2">
<button type="submit" class="btn btn-primary btn-block">Submit</button>
</div>
}
</article>
</div>
</aside>
</div>
</div>
@section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
}
@@ -0,0 +1,31 @@
@model Caspian.WorkflowFramework.Models.WorkflowResponse<Caspian.WorkflowFramework.Models.WorkflowModel>
@{
ViewData["Title"] = "Inquiry";
}
<div class="container">
<div class="row">
<aside class="col-sm-4">
</aside>
<aside class="col-sm-4">
<div class="card">
<article class="card-body">
<h4 class="card-title mb-4 mt-1">@ViewData["Title"]</h4>
<form asp-action="Complete" method="post">
@if (Model != null)
{
<ul class="list-group">
<li class="list-group-item">Status: <br /><strong>@Model.Status</strong></li>
<li class="list-group-item mb-2">Message: <br /><strong>@Model.Message</strong></li>
<li class="list-group-item">IsComplete: <br /><strong>@Model.Data?.IsComplete</strong></li>
<li class="list-group-item">InstanceId: <br /><strong>@Model.Data?.InstanceId</strong></li>
<li class="list-group-item">NextPageUrl: <br /><strong>@Model.Data?.NextPageUrl</strong></li>
<li class="list-group-item">NextPageEventName: <br /><strong>@Model.Data?.NextPageEventName</strong></li>
<li class="list-group-item">CurrentPageData: <br /><strong>@Model.Data?.CurrentPageData</strong></li>
</ul>
}
</form>
</article>
</div>
</aside>
</div>
</div>
@@ -0,0 +1,6 @@
@{
ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>
<p>Use this page to detail your site's privacy policy.</p>
@@ -0,0 +1,25 @@
@model ErrorViewModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
@@ -0,0 +1,53 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Caspian Workflowframework</title>
<script type="importmap"></script>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/SampleWorkflowWeb.styles.css" asp-append-version="true" />
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container-fluid">
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Caspian Workflowframework</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Card To Card</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Inquiry">Inquiry</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Transfer" asp-action="Index">Transfer Amount (New)</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<footer class="border-top footer text-muted">
<div class="container">
&copy; 2025 - Caspian Workflowframework
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
@@ -0,0 +1,48 @@
/* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
for details on configuring this project to bundle and minify static web assets. */
a.navbar-brand {
white-space: normal;
text-align: center;
word-break: break-all;
}
a {
color: #0077cc;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.border-top {
border-top: 1px solid #e5e5e5;
}
.border-bottom {
border-bottom: 1px solid #e5e5e5;
}
.box-shadow {
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
}
button.accept-policy {
font-size: 1rem;
line-height: inherit;
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
white-space: nowrap;
line-height: 60px;
}
@@ -0,0 +1,2 @@
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js"></script>
@@ -0,0 +1,52 @@
@using SampleWorkflowWeb.Enums
@model SampleWorkflowWeb.Models.Transfer.CartableViewModel
@{
ViewData["Title"] = "Cartable setting";
}
<div class="container">
<div class="row">
<aside class="col-sm-4">
</aside>
<aside class="col-sm-4">
<div class="card">
<article class="card-body">
<h4 class="card-title mb-4 mt-1">@ViewData["Title"]</h4>
<form asp-action="Cartable" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
@Html.HiddenFor(x => x.WorkflowId)
@Html.HiddenFor(x => x.PageEventName)
@Html.HiddenFor(x => x.From)
@Html.HiddenFor(x => x.To)
@Html.HiddenFor(x => x.Amount)
@Html.HiddenFor(x => x.PersonType)
<div class="form-group mt-2">
<label asp-for="ExpireDate"></label>
<input asp-for="ExpireDate" class="form-control" />
<span asp-validation-for="ExpireDate" class="text-danger"></span>
</div>
<div class="form-group mt-2">
<label asp-for="PriorityType"></label>
<select asp-for="PriorityType" asp-items="Html.GetEnumSelectList(typeof(PriorityType))" class="form-control"></select>
<span asp-validation-for="PriorityType" class="text-danger mt-2"></span>
</div>
<div class="form-group mt-2">
<label asp-for="SenderNote"></label>
<textarea asp-for="SenderNote" type="text" rows="5" class="form-control"></textarea>
<span asp-validation-for="SenderNote" class="text-danger mt-2"></span>
</div>
<div class="form-group mt-2">
<label asp-for="ReceiverNote"></label>
<textarea asp-for="ReceiverNote" type="text" rows="5" class="form-control"></textarea>
<span asp-validation-for="ReceiverNote" class="text-danger mt-2"></span>
</div>
<div class="form-group mt-2">
<button type="submit" class="btn btn-warning btn-block">Continue</button>
</div>
</form>
</article>
</div>
</aside>
</div>
</div>
@@ -0,0 +1,51 @@
@using SampleWorkflowWeb.Models.Transfer
@model ConfirmViewModel
@{
ViewData["Title"] = "Confirm";
}
<div class="container">
<div class="row">
<aside class="col-sm-4">
</aside>
<aside class="col-sm-4">
<div class="card">
<article class="card-body">
<h4 class="card-title mb-4 mt-1">@ViewData["Title"]</h4>
<form asp-action="Confirm" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
@Html.HiddenFor(x => x.WorkflowId)
@Html.HiddenFor(x => x.PageEventName)
@Html.HiddenFor(x => x.From)
@Html.HiddenFor(x => x.FromPan)
@Html.HiddenFor(x => x.FromFullName)
@Html.HiddenFor(x => x.To)
@Html.HiddenFor(x => x.ToPan)
@Html.HiddenFor(x => x.ToFullName)
@Html.HiddenFor(x => x.PersonType)
@Html.HiddenFor(x => x.Amount)
<div class="form-group mt-2">
<label asp-for="From"></label>
<input asp-for="FromPan" class="form-control" disabled />
<input asp-for="FromFullName" class="form-control mt-2" disabled />
</div>
<div class="form-group mt-2">
<label asp-for="To"></label>
<input asp-for="ToPan" class="form-control" disabled />
<input asp-for="ToFullName" class="form-control mt-2" disabled />
</div>
<div class="form-group mt-2">
<label asp-for="Amount"></label>
<input asp-for="Amount" class="form-control" disabled />
<span asp-validation-for="Amount" class="text-danger"></span>
</div>
<div class="form-group mt-2">
<button type="submit" class="btn btn-warning btn-block">Confirm</button>
</div>
</form>
</article>
</div>
</aside>
</div>
</div>
@@ -0,0 +1,10 @@
@using SampleWorkflowWeb.Models
@model WorkflowResponse<Object>
@{
ViewData["Title"] = "Error Page";
}
<div class="text-center">
<h4>خطای @Model.Status</h4>
<p>@Model.Message</p>
</div>
@@ -0,0 +1,40 @@
@{
ViewData["Title"] = "Transfer amount";
}
<div class="container">
<div class="row">
<aside class="col-sm-4">
</aside>
<aside class="col-sm-4">
<div class="card">
<article class="card-body">
<h4 class="card-title mb-4 mt-1">@ViewData["Title"]</h4>
<div class="form-group mt-2">
<label for="UserId">UserId</label>
<input name="UserId" id="UserId" class="form-control" />
<span id="ValidationMessage" class="text-danger"></span>
</div>
<div class="form-group mt-2">
<button type="button" onclick="goToTransfer()" class="btn btn-primary btn-block">Transfer amount</button>
</div>
</article>
</div>
</aside>
</div>
</div>
<script>
function goToTransfer(){
var userId = $("#UserId").val();
if(userId == "" || userId == null){
$("#ValidationMessage").html("UserId is required");
return;
}
$("#ValidationMessage").html("");
var url = `/Transfer/TransferAmount?userId=${userId}`;
window.location.href = url;
}
</script>
@@ -0,0 +1,40 @@
@model SampleWorkflowWeb.Models.ReceiptViewModel
@{
ViewData["Title"] = "Receipt";
}
<div class="container">
<div class="row">
<aside class="col-sm-4">
</aside>
<aside class="col-sm-4">
<div class="card">
<article class="card-body">
<h4 class="card-title mb-4 mt-1">@ViewData["Title"]</h4>
<form asp-action="Receipt" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
@Html.HiddenFor(x => x.WorkflowId)
@Html.HiddenFor(x => x.PageEventName)
@Html.HiddenFor(x => x.From)
@Html.HiddenFor(x => x.To)
@Html.HiddenFor(x => x.Amount)
@Html.HiddenFor(x => x.WorkflowId)
<div class="form-group">
<label asp-for="From"></label>
<input asp-for="FromPan" class="form-control" disabled />
<input asp-for="FromFullName" class="form-control mt-2" disabled />
</div>
<div class="form-group">
<label asp-for="To"></label>
<input asp-for="ToPan" class="form-control" disabled />
<input asp-for="ToFullName" class="form-control mt-2" disabled />
</div>
<div class="form-group">
<label asp-for="Amount"></label>
<input asp-for="Amount" class="form-control" disabled />
</div>
</form>
</article>
</div>
</aside>
</div>
</div>
@@ -0,0 +1,53 @@
@using SampleWorkflowWeb.Enums
@model SampleWorkflowWeb.Models.Transfer.SelectionViewModel
@{
ViewData["Title"] = "Selection";
}
<div class="container">
<div class="row">
<aside class="col-sm-4">
</aside>
<aside class="col-sm-4">
<div class="card">
<article class="card-body">
<h4 class="card-title mb-4 mt-1">@ViewData["Title"]</h4>
@using (Html.BeginForm("Selection", "Transfer", FormMethod.Post))
{
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
@Html.HiddenFor(x => x.WorkflowId)
@Html.HiddenFor(x => x.PageEventName)
<div class="form-group mt-2">
<label asp-for="PersonType"></label>
<select asp-for="PersonType" asp-items="Html.GetEnumSelectList(typeof(PersonType))" class="form-control"></select>
<span asp-validation-for="PersonType" class="text-danger"></span>
</div>
<div class="form-group mt-2">
<label asp-for="From"></label>
<select asp-for="From" asp-items="@ViewBag.SourceCards as SelectList" class="form-control"></select>
<span asp-validation-for="From" class="text-danger"></span>
</div>
<div class="form-group mt-2">
<label asp-for="To"></label>
<select asp-for="To" asp-items="@ViewBag.DestinationCards as SelectList" class="form-control"></select>
<span asp-validation-for="To" class="text-danger"></span>
</div>
<div class="form-group mt-2">
<label asp-for="Amount"></label>
<input asp-for="Amount" class="form-control" />
<span asp-validation-for="Amount" class="text-danger"></span>
</div>
<div class="form-group mt-2">
<button type="submit" class="btn btn-primary btn-block">Submit</button>
</div>
}
</article>
</div>
</aside>
</div>
</div>
@section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
}
@@ -0,0 +1,3 @@
@using SampleWorkflowWeb
@using SampleWorkflowWeb.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
+23
View File
@@ -0,0 +1,23 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultConnection": "Data Source=.;Initial Catalog=WorkflowFrameworkTest;User ID=sa;Password=123456;Integrated Security=false;TrustServerCertificate=True;"
},
"LogDatabaseSettings": {
"LogsCollectionName": "TestWorkflowCollection",
"ConnectionString": "mongodb://localhost",
"DatabaseName": "TestWorkflowDb",
"SqlConnectionString": "",
"SqlScriptString": "",
"ActiveLogFile": true,
"FileLogPath": "c:\\",
"RollingInterval": "Daily", //none, Minutely, Daily, Weekly, Monthly, Yearly, Hourly
"BatchSize": 1
}
}
+31
View File
@@ -0,0 +1,31 @@
html {
font-size: 14px;
}
@media (min-width: 768px) {
html {
font-size: 16px;
}
}
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}
html {
position: relative;
min-height: 100%;
}
body {
margin-bottom: 60px;
}
.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
color: var(--bs-secondary-color);
text-align: end;
}
.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
text-align: start;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

+4
View File
@@ -0,0 +1,4 @@
// Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
// for details on configuring this project to bundle and minify static web assets.
// Write your JavaScript code.
@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2011-2021 Twitter, Inc.
Copyright (c) 2011-2021 The Bootstrap Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,597 @@
/*!
* Bootstrap Reboot v5.3.3 (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
[data-bs-theme=light] {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-black: #000;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-primary-text-emphasis: #052c65;
--bs-secondary-text-emphasis: #2b2f32;
--bs-success-text-emphasis: #0a3622;
--bs-info-text-emphasis: #055160;
--bs-warning-text-emphasis: #664d03;
--bs-danger-text-emphasis: #58151c;
--bs-light-text-emphasis: #495057;
--bs-dark-text-emphasis: #495057;
--bs-primary-bg-subtle: #cfe2ff;
--bs-secondary-bg-subtle: #e2e3e5;
--bs-success-bg-subtle: #d1e7dd;
--bs-info-bg-subtle: #cff4fc;
--bs-warning-bg-subtle: #fff3cd;
--bs-danger-bg-subtle: #f8d7da;
--bs-light-bg-subtle: #fcfcfd;
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #9ec5fe;
--bs-secondary-border-subtle: #c4c8cb;
--bs-success-border-subtle: #a3cfbb;
--bs-info-border-subtle: #9eeaf9;
--bs-warning-border-subtle: #ffe69c;
--bs-danger-border-subtle: #f1aeb5;
--bs-light-border-subtle: #e9ecef;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg: #fff;
--bs-body-bg-rgb: 255, 255, 255;
--bs-emphasis-color: #000;
--bs-emphasis-color-rgb: 0, 0, 0;
--bs-secondary-color: rgba(33, 37, 41, 0.75);
--bs-secondary-color-rgb: 33, 37, 41;
--bs-secondary-bg: #e9ecef;
--bs-secondary-bg-rgb: 233, 236, 239;
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
--bs-tertiary-color-rgb: 33, 37, 41;
--bs-tertiary-bg: #f8f9fa;
--bs-tertiary-bg-rgb: 248, 249, 250;
--bs-heading-color: inherit;
--bs-link-color: #0d6efd;
--bs-link-color-rgb: 13, 110, 253;
--bs-link-decoration: underline;
--bs-link-hover-color: #0a58ca;
--bs-link-hover-color-rgb: 10, 88, 202;
--bs-code-color: #d63384;
--bs-highlight-color: #212529;
--bs-highlight-bg: #fff3cd;
--bs-border-width: 1px;
--bs-border-style: solid;
--bs-border-color: #dee2e6;
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
--bs-border-radius: 0.375rem;
--bs-border-radius-sm: 0.25rem;
--bs-border-radius-lg: 0.5rem;
--bs-border-radius-xl: 1rem;
--bs-border-radius-xxl: 2rem;
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
--bs-border-radius-pill: 50rem;
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
--bs-focus-ring-width: 0.25rem;
--bs-focus-ring-opacity: 0.25;
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
--bs-form-valid-color: #198754;
--bs-form-valid-border-color: #198754;
--bs-form-invalid-color: #dc3545;
--bs-form-invalid-border-color: #dc3545;
}
[data-bs-theme=dark] {
color-scheme: dark;
--bs-body-color: #dee2e6;
--bs-body-color-rgb: 222, 226, 230;
--bs-body-bg: #212529;
--bs-body-bg-rgb: 33, 37, 41;
--bs-emphasis-color: #fff;
--bs-emphasis-color-rgb: 255, 255, 255;
--bs-secondary-color: rgba(222, 226, 230, 0.75);
--bs-secondary-color-rgb: 222, 226, 230;
--bs-secondary-bg: #343a40;
--bs-secondary-bg-rgb: 52, 58, 64;
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #2b3035;
--bs-tertiary-bg-rgb: 43, 48, 53;
--bs-primary-text-emphasis: #6ea8fe;
--bs-secondary-text-emphasis: #a7acb1;
--bs-success-text-emphasis: #75b798;
--bs-info-text-emphasis: #6edff6;
--bs-warning-text-emphasis: #ffda6a;
--bs-danger-text-emphasis: #ea868f;
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #031633;
--bs-secondary-bg-subtle: #161719;
--bs-success-bg-subtle: #051b11;
--bs-info-bg-subtle: #032830;
--bs-warning-bg-subtle: #332701;
--bs-danger-bg-subtle: #2c0b0e;
--bs-light-bg-subtle: #343a40;
--bs-dark-bg-subtle: #1a1d20;
--bs-primary-border-subtle: #084298;
--bs-secondary-border-subtle: #41464b;
--bs-success-border-subtle: #0f5132;
--bs-info-border-subtle: #087990;
--bs-warning-border-subtle: #997404;
--bs-danger-border-subtle: #842029;
--bs-light-border-subtle: #495057;
--bs-dark-border-subtle: #343a40;
--bs-heading-color: inherit;
--bs-link-color: #6ea8fe;
--bs-link-hover-color: #8bb9fe;
--bs-link-color-rgb: 110, 168, 254;
--bs-link-hover-color-rgb: 139, 185, 254;
--bs-code-color: #e685b5;
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #664d03;
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #75b798;
--bs-form-valid-border-color: #75b798;
--bs-form-invalid-color: #ea868f;
--bs-form-invalid-border-color: #ea868f;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
border: 0;
border-top: var(--bs-border-width) solid;
opacity: 0.25;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
color: var(--bs-heading-color);
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-left: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.1875em;
color: var(--bs-highlight-color);
background-color: var(--bs-highlight-bg);
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
text-decoration: underline;
}
a:hover {
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: var(--bs-code-color);
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.1875rem 0.375rem;
font-size: 0.875em;
color: var(--bs-body-bg);
background-color: var(--bs-body-color);
border-radius: 0.25rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: var(--bs-secondary-color);
text-align: left;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
display: none !important;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: left;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: left;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
/* rtl:raw:
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
::file-selector-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,594 @@
/*!
* Bootstrap Reboot v5.3.3 (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
[data-bs-theme=light] {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-black: #000;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-primary-text-emphasis: #052c65;
--bs-secondary-text-emphasis: #2b2f32;
--bs-success-text-emphasis: #0a3622;
--bs-info-text-emphasis: #055160;
--bs-warning-text-emphasis: #664d03;
--bs-danger-text-emphasis: #58151c;
--bs-light-text-emphasis: #495057;
--bs-dark-text-emphasis: #495057;
--bs-primary-bg-subtle: #cfe2ff;
--bs-secondary-bg-subtle: #e2e3e5;
--bs-success-bg-subtle: #d1e7dd;
--bs-info-bg-subtle: #cff4fc;
--bs-warning-bg-subtle: #fff3cd;
--bs-danger-bg-subtle: #f8d7da;
--bs-light-bg-subtle: #fcfcfd;
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #9ec5fe;
--bs-secondary-border-subtle: #c4c8cb;
--bs-success-border-subtle: #a3cfbb;
--bs-info-border-subtle: #9eeaf9;
--bs-warning-border-subtle: #ffe69c;
--bs-danger-border-subtle: #f1aeb5;
--bs-light-border-subtle: #e9ecef;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg: #fff;
--bs-body-bg-rgb: 255, 255, 255;
--bs-emphasis-color: #000;
--bs-emphasis-color-rgb: 0, 0, 0;
--bs-secondary-color: rgba(33, 37, 41, 0.75);
--bs-secondary-color-rgb: 33, 37, 41;
--bs-secondary-bg: #e9ecef;
--bs-secondary-bg-rgb: 233, 236, 239;
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
--bs-tertiary-color-rgb: 33, 37, 41;
--bs-tertiary-bg: #f8f9fa;
--bs-tertiary-bg-rgb: 248, 249, 250;
--bs-heading-color: inherit;
--bs-link-color: #0d6efd;
--bs-link-color-rgb: 13, 110, 253;
--bs-link-decoration: underline;
--bs-link-hover-color: #0a58ca;
--bs-link-hover-color-rgb: 10, 88, 202;
--bs-code-color: #d63384;
--bs-highlight-color: #212529;
--bs-highlight-bg: #fff3cd;
--bs-border-width: 1px;
--bs-border-style: solid;
--bs-border-color: #dee2e6;
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
--bs-border-radius: 0.375rem;
--bs-border-radius-sm: 0.25rem;
--bs-border-radius-lg: 0.5rem;
--bs-border-radius-xl: 1rem;
--bs-border-radius-xxl: 2rem;
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
--bs-border-radius-pill: 50rem;
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
--bs-focus-ring-width: 0.25rem;
--bs-focus-ring-opacity: 0.25;
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
--bs-form-valid-color: #198754;
--bs-form-valid-border-color: #198754;
--bs-form-invalid-color: #dc3545;
--bs-form-invalid-border-color: #dc3545;
}
[data-bs-theme=dark] {
color-scheme: dark;
--bs-body-color: #dee2e6;
--bs-body-color-rgb: 222, 226, 230;
--bs-body-bg: #212529;
--bs-body-bg-rgb: 33, 37, 41;
--bs-emphasis-color: #fff;
--bs-emphasis-color-rgb: 255, 255, 255;
--bs-secondary-color: rgba(222, 226, 230, 0.75);
--bs-secondary-color-rgb: 222, 226, 230;
--bs-secondary-bg: #343a40;
--bs-secondary-bg-rgb: 52, 58, 64;
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #2b3035;
--bs-tertiary-bg-rgb: 43, 48, 53;
--bs-primary-text-emphasis: #6ea8fe;
--bs-secondary-text-emphasis: #a7acb1;
--bs-success-text-emphasis: #75b798;
--bs-info-text-emphasis: #6edff6;
--bs-warning-text-emphasis: #ffda6a;
--bs-danger-text-emphasis: #ea868f;
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #031633;
--bs-secondary-bg-subtle: #161719;
--bs-success-bg-subtle: #051b11;
--bs-info-bg-subtle: #032830;
--bs-warning-bg-subtle: #332701;
--bs-danger-bg-subtle: #2c0b0e;
--bs-light-bg-subtle: #343a40;
--bs-dark-bg-subtle: #1a1d20;
--bs-primary-border-subtle: #084298;
--bs-secondary-border-subtle: #41464b;
--bs-success-border-subtle: #0f5132;
--bs-info-border-subtle: #087990;
--bs-warning-border-subtle: #997404;
--bs-danger-border-subtle: #842029;
--bs-light-border-subtle: #495057;
--bs-dark-border-subtle: #343a40;
--bs-heading-color: inherit;
--bs-link-color: #6ea8fe;
--bs-link-hover-color: #8bb9fe;
--bs-link-color-rgb: 110, 168, 254;
--bs-link-hover-color-rgb: 139, 185, 254;
--bs-code-color: #e685b5;
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #664d03;
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #75b798;
--bs-form-valid-border-color: #75b798;
--bs-form-invalid-color: #ea868f;
--bs-form-invalid-border-color: #ea868f;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
border: 0;
border-top: var(--bs-border-width) solid;
opacity: 0.25;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
color: var(--bs-heading-color);
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-right: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-right: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.1875em;
color: var(--bs-highlight-color);
background-color: var(--bs-highlight-bg);
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
text-decoration: underline;
}
a:hover {
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: var(--bs-code-color);
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.1875rem 0.375rem;
font-size: 0.875em;
color: var(--bs-body-bg);
background-color: var(--bs-body-color);
border-radius: 0.25rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: var(--bs-secondary-color);
text-align: right;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
display: none !important;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: right;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: right;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
::file-selector-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More