From 631126a85cc9d2a3bd136cfb94c8f0c547d25765 Mon Sep 17 00:00:00 2001 From: Vitali Semianiaka Date: Tue, 10 Feb 2026 14:42:19 +0300 Subject: [PATCH] first version --- .../MyCompany.MyProject.BackendApi.csproj | 7 +- MyCompany.MyProject.BackendApi/Program.cs | 123 ++++++++++++++++++ QuasarFrontend/quasar.config.ts | 5 +- QuasarFrontend/src/components/s8n-runtime.ts | 84 ++++++++++-- .../components_s8n/ComponentCalculator.vue | 96 ++++++++++++++ QuasarFrontend/src/pages/IndexPage.vue | 38 +----- S8n.Components.Packages/Basics/Calculator.cs | 61 +++++++++ .../S8n.Components.Packages.csproj | 9 ++ S8n.MySolutionTemplate.sln | 14 ++ components | 2 +- 10 files changed, 390 insertions(+), 49 deletions(-) create mode 100644 QuasarFrontend/src/components_s8n/ComponentCalculator.vue create mode 100644 S8n.Components.Packages/Basics/Calculator.cs create mode 100644 S8n.Components.Packages/S8n.Components.Packages.csproj diff --git a/MyCompany.MyProject.BackendApi/MyCompany.MyProject.BackendApi.csproj b/MyCompany.MyProject.BackendApi/MyCompany.MyProject.BackendApi.csproj index e1f290f..55868cd 100644 --- a/MyCompany.MyProject.BackendApi/MyCompany.MyProject.BackendApi.csproj +++ b/MyCompany.MyProject.BackendApi/MyCompany.MyProject.BackendApi.csproj @@ -1,13 +1,18 @@ - + net10.0 enable enable + ff020750-eacc-49ce-880e-1ecbc30605ac + + + + diff --git a/MyCompany.MyProject.BackendApi/Program.cs b/MyCompany.MyProject.BackendApi/Program.cs index ee9d65d..083884d 100644 --- a/MyCompany.MyProject.BackendApi/Program.cs +++ b/MyCompany.MyProject.BackendApi/Program.cs @@ -1,3 +1,9 @@ +using System.Reflection; +using System.Text.Json; + +// Ensure S8n.Components.Packages assembly is loaded +_ = typeof(S8n.Components.Basics.Calculator).Assembly; + var builder = WebApplication.CreateBuilder(args); // Add services to the container. @@ -33,9 +39,126 @@ app.MapGet("/weatherforecast", () => }) .WithName("GetWeatherForecast"); +// Runtime execution endpoint +app.MapPost("/api/runtime/execute", (RuntimeRequest request) => +{ + try + { + // Get the type from the class name + var type = Type.GetType(request.ClassName); + if (type == null) + { + // Try to find in loaded assemblies + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + type = assembly.GetType(request.ClassName); + if (type != null) break; + } + } + + if (type == null) + { + return Results.BadRequest(new RuntimeResponse + { + Error = $"Class not found: {request.ClassName}" + }); + } + + // Create instance + var instance = Activator.CreateInstance(type); + if (instance == null) + { + return Results.BadRequest(new RuntimeResponse + { + Error = $"Failed to create instance of: {request.ClassName}" + }); + } + + // Get the method + var method = type.GetMethod(request.MethodName); + if (method == null) + { + return Results.BadRequest(new RuntimeResponse + { + Error = $"Method not found: {request.MethodName}" + }); + } + + // Parse inputs and invoke method + var inputs = request.Inputs as JsonElement?; + object? result; + + if (inputs.HasValue) + { + // Get method parameters + var parameters = method.GetParameters(); + var args = new List(); + + foreach (var param in parameters) + { + if (inputs.Value.TryGetProperty(param.Name!, out var property)) + { + var value = JsonSerializer.Deserialize(property.GetRawText(), param.ParameterType); + args.Add(value); + } + else if (param.IsOptional) + { + args.Add(param.DefaultValue); + } + else + { + return Results.BadRequest(new RuntimeResponse + { + Error = $"Missing required parameter: {param.Name}" + }); + } + } + + result = method.Invoke(instance, args.ToArray()); + } + else + { + result = method.Invoke(instance, null); + } + + return Results.Ok(new RuntimeResponse + { + Outputs = result + }); + } + catch (TargetInvocationException tie) when (tie.InnerException != null) + { + return Results.BadRequest(new RuntimeResponse + { + Error = tie.InnerException.Message + }); + } + catch (Exception ex) + { + return Results.BadRequest(new RuntimeResponse + { + Error = ex.Message + }); + } +}) +.WithName("ExecuteRuntime"); + app.Run(); record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) { public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); } + +record RuntimeRequest +{ + public string ClassName { get; set; } = string.Empty; + public string MethodName { get; set; } = string.Empty; + public object? Inputs { get; set; } +} + +record RuntimeResponse +{ + public object? Outputs { get; set; } + public string? Error { get; set; } +} diff --git a/QuasarFrontend/quasar.config.ts b/QuasarFrontend/quasar.config.ts index fa29981..5f78074 100644 --- a/QuasarFrontend/quasar.config.ts +++ b/QuasarFrontend/quasar.config.ts @@ -20,7 +20,7 @@ export default defineConfig((ctx) => { // https://github.com/quasarframework/quasar/tree/dev/extras extras: [ // 'ionicons-v4', - // 'mdi-v7', + 'mdi-v7', // 'fontawesome-v6', // 'eva-icons', // 'themify', @@ -99,6 +99,9 @@ export default defineConfig((ctx) => { devServer: { // https: true, open: true, // opens browser window automatically + proxy: { + '/api': 'http://localhost:5127', + }, }, // https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#framework diff --git a/QuasarFrontend/src/components/s8n-runtime.ts b/QuasarFrontend/src/components/s8n-runtime.ts index 51df4c9..8bc5475 100644 --- a/QuasarFrontend/src/components/s8n-runtime.ts +++ b/QuasarFrontend/src/components/s8n-runtime.ts @@ -1,19 +1,83 @@ -import { ref } from 'vue'; +import { type Ref, ref, shallowRef } from 'vue'; + +const API_BASE_URL = ''; // Backend API URL + +export interface RuntimeExecutor { + execute(this: void, method: string): Promise; + running: Ref; + inputs: Ref; + outputs: Ref; + error: Ref; + duration: Ref; +} export const runtime = { - createExecutor(className: string) { - const inputs = ref(); - const outputs = ref(); - return { - execute(method: string): Promise { - console.log('executing...', className, method); + createExecutor( + className: string, + initInputs: TIn, + silentErrors = true, + ): RuntimeExecutor { + const inputs = ref(initInputs) as Ref; + const outputs = ref(); + const duration = shallowRef(0); + const running = shallowRef(false); + const error = shallowRef(); - return new Promise((r) => { - setTimeout(() => r(null), 2500); + const execute = async (method: string): Promise => { + console.trace('executing...', className, method); + const startTime = performance.now(); + try { + error.value = undefined; + running.value = true; + duration.value = 0; + const response = await fetch(`${API_BASE_URL}/api/runtime/execute`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + className, + methodName: method, + inputs: inputs.value, + }), }); - }, + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || `HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + if (data.error) { + throw new Error(data.error); + } + + outputs.value = data.outputs || {}; + return outputs.value; + } catch (err) { + console.error('Runtime execution error:', err); + error.value = err instanceof Error ? err.message : 'An error occurred'; + + if (!silentErrors) { + throw err; + } + } finally { + running.value = false; + const endTime = performance.now(); + duration.value = Math.round((endTime - startTime) * 10) / 10; + } + return undefined; + }; + + return { + execute, + + running, + duration, inputs, outputs, + error, }; }, }; diff --git a/QuasarFrontend/src/components_s8n/ComponentCalculator.vue b/QuasarFrontend/src/components_s8n/ComponentCalculator.vue new file mode 100644 index 0000000..1578de6 --- /dev/null +++ b/QuasarFrontend/src/components_s8n/ComponentCalculator.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/QuasarFrontend/src/pages/IndexPage.vue b/QuasarFrontend/src/pages/IndexPage.vue index f114b5e..820e982 100644 --- a/QuasarFrontend/src/pages/IndexPage.vue +++ b/QuasarFrontend/src/pages/IndexPage.vue @@ -1,43 +1,9 @@ diff --git a/S8n.Components.Packages/Basics/Calculator.cs b/S8n.Components.Packages/Basics/Calculator.cs new file mode 100644 index 0000000..ea63e73 --- /dev/null +++ b/S8n.Components.Packages/Basics/Calculator.cs @@ -0,0 +1,61 @@ +using System.Text.Json; + +namespace S8n.Components.Basics; + +public class Calculator +{ + public object Calc(string @operator, object[] args) + { + if (args == null || args.Length < 2) + { + throw new ArgumentException("At least two arguments are required"); + } + + // Convert arguments to decimal for precision, handling JsonElement + var numbers = args.Select(a => + { + if (a is JsonElement jsonElement) + { + return jsonElement.GetDecimal(); + } + return Convert.ToDecimal(a); + }).ToArray(); + decimal result = 0; + + switch (@operator?.ToLower()) + { + case "add": + result = numbers.Sum(); + break; + case "subtract": + result = numbers[0]; + for (int i = 1; i < numbers.Length; i++) + { + result -= numbers[i]; + } + break; + case "multiply": + result = numbers[0]; + for (int i = 1; i < numbers.Length; i++) + { + result *= numbers[i]; + } + break; + case "divide": + result = numbers[0]; + for (int i = 1; i < numbers.Length; i++) + { + if (numbers[i] == 0) + { + throw new DivideByZeroException("Division by zero is not allowed"); + } + result /= numbers[i]; + } + break; + default: + throw new ArgumentException($"Unknown operator: {@operator}"); + } + + return new { Result = result }; + } +} diff --git a/S8n.Components.Packages/S8n.Components.Packages.csproj b/S8n.Components.Packages/S8n.Components.Packages.csproj new file mode 100644 index 0000000..0d2df0d --- /dev/null +++ b/S8n.Components.Packages/S8n.Components.Packages.csproj @@ -0,0 +1,9 @@ + + + + net9.0 + enable + enable + + + diff --git a/S8n.MySolutionTemplate.sln b/S8n.MySolutionTemplate.sln index 4262a1e..4111c9e 100644 --- a/S8n.MySolutionTemplate.sln +++ b/S8n.MySolutionTemplate.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyCompany.MyProject.Models" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyCompany.MyProject.Tests", "MyCompany.MyProject.Tests\MyCompany.MyProject.Tests.csproj", "{A4D2D9E2-8A84-4F3F-8A16-0F83FD8DDC87}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "S8n.Components.Packages", "S8n.Components.Packages\S8n.Components.Packages.csproj", "{B5C8D5E2-3A4B-4C6D-8E9F-0A1B2C3D4E5F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -55,6 +57,18 @@ Global {A4D2D9E2-8A84-4F3F-8A16-0F83FD8DDC87}.Release|x64.Build.0 = Release|Any CPU {A4D2D9E2-8A84-4F3F-8A16-0F83FD8DDC87}.Release|x86.ActiveCfg = Release|Any CPU {A4D2D9E2-8A84-4F3F-8A16-0F83FD8DDC87}.Release|x86.Build.0 = Release|Any CPU + {B5C8D5E2-3A4B-4C6D-8E9F-0A1B2C3D4E5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B5C8D5E2-3A4B-4C6D-8E9F-0A1B2C3D4E5F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B5C8D5E2-3A4B-4C6D-8E9F-0A1B2C3D4E5F}.Debug|x64.ActiveCfg = Debug|Any CPU + {B5C8D5E2-3A4B-4C6D-8E9F-0A1B2C3D4E5F}.Debug|x64.Build.0 = Debug|Any CPU + {B5C8D5E2-3A4B-4C6D-8E9F-0A1B2C3D4E5F}.Debug|x86.ActiveCfg = Debug|Any CPU + {B5C8D5E2-3A4B-4C6D-8E9F-0A1B2C3D4E5F}.Debug|x86.Build.0 = Debug|Any CPU + {B5C8D5E2-3A4B-4C6D-8E9F-0A1B2C3D4E5F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B5C8D5E2-3A4B-4C6D-8E9F-0A1B2C3D4E5F}.Release|Any CPU.Build.0 = Release|Any CPU + {B5C8D5E2-3A4B-4C6D-8E9F-0A1B2C3D4E5F}.Release|x64.ActiveCfg = Release|Any CPU + {B5C8D5E2-3A4B-4C6D-8E9F-0A1B2C3D4E5F}.Release|x64.Build.0 = Release|Any CPU + {B5C8D5E2-3A4B-4C6D-8E9F-0A1B2C3D4E5F}.Release|x86.ActiveCfg = Release|Any CPU + {B5C8D5E2-3A4B-4C6D-8E9F-0A1B2C3D4E5F}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/components b/components index db90a10..2ccd4e9 160000 --- a/components +++ b/components @@ -1 +1 @@ -Subproject commit db90a10bf6ad1368a20b9eb30918f86da89c72dd +Subproject commit 2ccd4e998bce75bb65f9dea4861114522db5ffa0