From a54765304783a6c51f88f32f781c091eeac3c0cd Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 8 Oct 2026 09:28:17 +0200 Subject: [PATCH 1/3] Remove unused correction validation assignment --- scripts/container_release.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/container_release.py b/scripts/container_release.py index 8038990..d760157 100644 --- a/scripts/container_release.py +++ b/scripts/container_release.py @@ -1358,7 +1358,7 @@ def load_superseded_publication( run_repository = (run.get("repository") or {}).get("full_name") old_commit = str(run.get("head_sha", "")) attempt = run.get("run_attempt") - prior_source_ref = prior_publication_source_ref(run, version) + prior_publication_source_ref(run, version) if ( str(run.get("id", "")) != run_id or run.get("status") != "completed" From 66cf5cef091944ce42ed81dfd4260a2d6109e628 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 8 Oct 2026 09:40:06 +0200 Subject: [PATCH 2/3] Verify labels by platform manifest digest --- scripts/container_release.py | 36 ++++++++++-- scripts/tests/test_container_release.py | 75 +++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 6 deletions(-) diff --git a/scripts/container_release.py b/scripts/container_release.py index d760157..3975261 100644 --- a/scripts/container_release.py +++ b/scripts/container_release.py @@ -423,6 +423,19 @@ def image_reference(repository: str, tag: str) -> str: return f"{repository}:{tag}" +def repository_from_reference(reference: str) -> str: + if not isinstance(reference, str) or not reference: + raise ReleaseError("Image reference must be a non-empty string") + name = reference.split("@", 1)[0] + last_slash = name.rfind("/") + last_colon = name.rfind(":") + if last_colon > last_slash: + name = name[:last_colon] + if not name or name.endswith("/"): + raise ReleaseError(f"Could not resolve repository from image reference {reference!r}") + return name + + def verify_manifest(reference: str, *, expected_digest: str | None = None) -> dict[str, Any]: manifest = inspect_manifest(reference) if manifest is None: @@ -442,24 +455,35 @@ def verify_manifest(reference: str, *, expected_digest: str | None = None) -> di def verify_labels(reference: str, expected: dict[str, str], platforms: Iterable[str]) -> None: - for platform in platforms: - run_command(["docker", "pull", "--platform", platform, reference]) + requested_platforms = tuple(platforms) + if len(requested_platforms) != len(REQUIRED_PLATFORMS) or set(requested_platforms) != set(REQUIRED_PLATFORMS): + raise ReleaseError(f"Label verification requires exactly these platforms: {list(REQUIRED_PLATFORMS)}") + + manifest = verify_manifest(reference) + repository = repository_from_reference(reference) + for platform in REQUIRED_PLATFORMS: + platform_reference = f"{repository}@{manifest['platforms'][platform]}" + run_command(["docker", "pull", "--platform", platform, platform_reference]) result = run_command( - ["docker", "image", "inspect", "--format", "{{json .Config.Labels}}", reference] + ["docker", "image", "inspect", "--format", "{{json .Config.Labels}}", platform_reference] ) try: actual = json.loads(result.stdout) except json.JSONDecodeError as error: - raise ReleaseError(f"Could not read image labels from {reference}: {error}") from error + raise ReleaseError(f"Could not read image labels from {platform_reference}: {error}") from error + if not isinstance(actual, dict): + raise ReleaseError(f"Could not read image labels from {platform_reference}: expected a JSON object") for key, value in expected.items(): if key == "org.opencontainers.image.ref.name": allowed_refs = {"refs/heads/main", f"refs/tags/{expected['org.opencontainers.image.version']}"} if actual.get(key) not in allowed_refs: - raise ReleaseError(f"{reference} has an unapproved publication ref label: {actual.get(key)!r}") + raise ReleaseError( + f"{platform_reference} has an unapproved publication ref label: {actual.get(key)!r}" + ) continue if actual.get(key) != value: raise ReleaseError( - f"{reference} label {key!r} is {actual.get(key)!r}; expected {value!r}" + f"{platform_reference} label {key!r} is {actual.get(key)!r}; expected {value!r}" ) diff --git a/scripts/tests/test_container_release.py b/scripts/tests/test_container_release.py index 009f0b4..8c368af 100644 --- a/scripts/tests/test_container_release.py +++ b/scripts/tests/test_container_release.py @@ -447,6 +447,81 @@ def test_correction_plan_ignores_the_existing_version_tag(self): self.assertEqual([call.args[0] for call in inspect.call_args_list], [candidate_ref, candidate_ref]) +class VerifyLabelsTests(unittest.TestCase): + def setUp(self): + self.packages = {family: "3.9.0" for family in ("core", "studio", "extensions")} + self.expected_labels = release.labels_for("3.9.0", COMMIT, "refs/heads/main", self.packages) + self.manifest = {"digest": ROOT_DIGEST, "platforms": PLATFORMS} + + def test_pulls_each_platform_child_digest_for_tag_and_root_digest_references(self): + for reference in ( + "registry.example:5443/team/elsa-server:3.9.0", + f"registry.example:5443/team/elsa-server@{ROOT_DIGEST}", + ): + with self.subTest(reference=reference): + commands = [] + + def run(command, **_kwargs): + commands.append(command) + return Mock(stdout=json.dumps(self.expected_labels)) + + with ( + patch.object(release, "verify_manifest", return_value=self.manifest), + patch.object(release, "run_command", side_effect=run), + ): + release.verify_labels(reference, self.expected_labels, release.REQUIRED_PLATFORMS) + + repository = "registry.example:5443/team/elsa-server" + expected_commands = [] + for platform in release.REQUIRED_PLATFORMS: + child_reference = f"{repository}@{PLATFORMS[platform]}" + expected_commands.extend(( + ["docker", "pull", "--platform", platform, child_reference], + ["docker", "image", "inspect", "--format", "{{json .Config.Labels}}", child_reference], + )) + self.assertEqual(commands, expected_commands) + + def test_rejects_a_label_mismatch_from_the_platform_child(self): + actual_labels = dict(self.expected_labels) + actual_labels["org.opencontainers.image.version"] = "3.8.4" + with ( + patch.object(release, "verify_manifest", return_value=self.manifest), + patch.object(release, "run_command", return_value=Mock(stdout=json.dumps(actual_labels))) as command, + ): + with self.assertRaisesRegex(release.ReleaseError, "org.opencontainers.image.version"): + release.verify_labels( + "elsaworkflows/elsa-server-app:3.9.0", + self.expected_labels, + release.REQUIRED_PLATFORMS, + ) + + self.assertIn(PLATFORMS["linux/amd64"], command.call_args_list[1].args[0][-1]) + + def test_rejects_incomplete_manifest_before_pulling_any_platform(self): + incomplete = {"digest": ROOT_DIGEST, "platforms": {"linux/amd64": PLATFORMS["linux/amd64"]}} + with ( + patch.object(release, "inspect_manifest", return_value=incomplete), + patch.object(release, "run_command") as command, + ): + with self.assertRaisesRegex(release.ReleaseError, "missing required platforms"): + release.verify_labels( + "elsaworkflows/elsa-server-app:3.9.0", + self.expected_labels, + release.REQUIRED_PLATFORMS, + ) + command.assert_not_called() + + def test_requires_the_complete_supported_platform_set(self): + with patch.object(release, "verify_manifest") as verify_manifest: + with self.assertRaisesRegex(release.ReleaseError, "requires exactly these platforms"): + release.verify_labels( + "elsaworkflows/elsa-server-app:3.9.0", + self.expected_labels, + ("linux/amd64",), + ) + verify_manifest.assert_not_called() + + class SupersededPublicationTests(unittest.TestCase): def assert_archive_rejected(self, run, artifact, archive, packages, message): with self.assertRaisesRegex(release.ReleaseError, message): From 10841d6ed80764f7566a65bd7304afbe4d53f62f Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 8 Oct 2026 09:53:24 +0200 Subject: [PATCH 3/3] Register workflow dashboard hosts and widgets --- Directory.Packages.props | 2 + scripts/container_release.py | 41 ++++++++++++++-- scripts/tests/test_container_release.py | 47 ++++++++++++++++++- .../Elsa.Server.Studio.BlazorServer.csproj | 2 + .../Program.cs | 3 ++ ...lsa.Server.Studio.BlazorWasm.Client.csproj | 1 + .../Program.cs | 2 + .../Elsa.Server.Studio.BlazorWasm.csproj | 1 + src/Elsa.Server.Studio.BlazorWasm/Program.cs | 1 + src/Elsa.Server/Elsa.Server.csproj | 1 + src/Elsa.Server/Program.cs | 1 + .../Bootstrap/Modules.cs | 6 ++- .../Elsa.Studio.BlazorServer.csproj | 1 + .../Bootstrap/Modules.cs | 6 ++- .../Elsa.Studio.BlazorWasm.Client.csproj | 1 + 15 files changed, 108 insertions(+), 8 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index fd14c62..f67d9bf 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -27,6 +27,7 @@ + @@ -40,6 +41,7 @@ + diff --git a/scripts/container_release.py b/scripts/container_release.py index 3975261..6c2ef96 100644 --- a/scripts/container_release.py +++ b/scripts/container_release.py @@ -574,11 +574,7 @@ def login_and_probe_api(url: str, username: str, password: str) -> dict[str, Any ) dashboard_api = probe_bearer_json_api(url, DASHBOARD_API_ENDPOINT, access_token) - dashboard = dashboard_api["body"] - if not isinstance(dashboard.get("runtime"), dict) or not isinstance(dashboard.get("workflowInstances"), dict): - raise ReleaseError( - f"Bearer-authenticated dashboard API request to {DASHBOARD_API_ENDPOINT} returned an unexpected JSON object" - ) + dashboard_evidence = validate_dashboard_overview(dashboard_api["body"]) return { "identityLogin": {"status": login_status, "endpoint": "/elsa/api/identity/login"}, @@ -591,10 +587,40 @@ def login_and_probe_api(url: str, username: str, password: str) -> dict[str, Any "status": dashboard_api["status"], "endpoint": DASHBOARD_API_ENDPOINT, "contentType": dashboard_api["contentType"], + **dashboard_evidence, }, } +def validate_dashboard_overview(body: dict[str, Any]) -> dict[str, Any]: + runtime = body.get("runtime") + workflow_instances = body.get("workflowInstances") + if not isinstance(runtime, dict) or not isinstance(workflow_instances, dict): + raise ReleaseError( + f"Bearer-authenticated dashboard API request to {DASHBOARD_API_ENDPOINT} returned an unexpected JSON object" + ) + + runtime_status = runtime.get("status") + is_accepting_work = runtime.get("isAcceptingWork") + metric_names = ("running", "completed", "faulted", "suspended", "interrupted", "incidentBearing") + metrics = {name: workflow_instances.get(name) for name in metric_names} + if ( + runtime_status != "AcceptingWork" + or is_accepting_work is not True + or any(type(value) is not int or value < 0 for value in metrics.values()) + ): + raise ReleaseError( + f"Bearer-authenticated dashboard API request to {DASHBOARD_API_ENDPOINT} did not report an accepting workflow runtime and metrics" + ) + + return { + "runtimeStatus": runtime_status, + "isAcceptingWork": is_accepting_work, + "workflowMetricsValid": True, + "running": metrics["running"], + } + + def probe_bearer_json_api(url: str, endpoint: str, access_token: str) -> dict[str, Any]: request = urllib.request.Request( f"{url.rstrip('/')}{endpoint}", @@ -913,6 +939,11 @@ def validate_fragment_evidence( or row.get("dashboardApi", {}).get("status") != 200 or row.get("dashboardApi", {}).get("endpoint") != DASHBOARD_API_ENDPOINT or row.get("dashboardApi", {}).get("contentType", "").split(";", 1)[0] != "application/json" + or row.get("dashboardApi", {}).get("runtimeStatus") != "AcceptingWork" + or row.get("dashboardApi", {}).get("isAcceptingWork") is not True + or row.get("dashboardApi", {}).get("workflowMetricsValid") is not True + or type(row.get("dashboardApi", {}).get("running")) is not int + or row.get("dashboardApi", {}).get("running", -1) < 0 ): raise ReleaseError(f"Image evidence for {image['name']} lacks successful authenticated API smoke") diff --git a/scripts/tests/test_container_release.py b/scripts/tests/test_container_release.py index 8c368af..4fdc931 100644 --- a/scripts/tests/test_container_release.py +++ b/scripts/tests/test_container_release.py @@ -796,6 +796,10 @@ def server_fragment(): "status": 200, "endpoint": release.DASHBOARD_API_ENDPOINT, "contentType": "application/json; charset=utf-8", + "runtimeStatus": "AcceptingWork", + "isAcceptingWork": True, + "workflowMetricsValid": True, + "running": 0, }, } for platform, digest in PLATFORMS.items() @@ -1056,7 +1060,9 @@ def read(self): def test_uses_bearer_protected_workflow_definitions_api_and_validates_paged_json(self): login = self.Response(b'{"accessToken":"synthetic-token"}') definitions = self.Response(b'{"items":[],"totalCount":0}') - dashboard = self.Response(b'{"runtime":{},"workflowInstances":{}}') + dashboard = self.Response( + b'{"runtime":{"status":"AcceptingWork","isAcceptingWork":true},"workflowInstances":{"running":0,"completed":0,"faulted":0,"suspended":0,"interrupted":0,"incidentBearing":0}}' + ) requests = [] def open_url(request, timeout): @@ -1112,6 +1118,45 @@ def test_rejects_dashboard_json_without_dashboard_overview_shape(self): with self.assertRaisesRegex(release.ReleaseError, "dashboard/overview.*unexpected JSON object"): release.login_and_probe_api("http://127.0.0.1:1234", "smoke", "secret") + def test_rejects_dashboard_without_runtime_contributor_or_metrics(self): + login = self.Response(b'{"accessToken":"synthetic-token"}') + definitions = self.Response(b'{"items":[],"totalCount":0}') + unavailable = self.Response( + b'{"runtime":{"status":"Unavailable","isAcceptingWork":false},"workflowInstances":{"running":0,"completed":0,"faulted":0,"suspended":0,"interrupted":0,"incidentBearing":0}}' + ) + with patch.object(release.urllib.request, "urlopen", side_effect=[login, definitions, unavailable]): + with self.assertRaisesRegex(release.ReleaseError, "did not report an accepting workflow runtime"): + release.login_and_probe_api("http://127.0.0.1:1234", "smoke", "secret") + + accepting_without_metrics = { + "runtime": {"status": "AcceptingWork", "isAcceptingWork": True}, + "workflowInstances": { + "running": 0, + "completed": 0, + "faulted": 0, + "suspended": 0, + "interrupted": 0, + "incidentBearing": "0", + }, + } + with self.assertRaisesRegex(release.ReleaseError, "did not report an accepting workflow runtime"): + release.validate_dashboard_overview(accepting_without_metrics) + + def test_dashboard_fragment_requires_runtime_contributor_evidence(self): + image = release.parse_image_selection("server")[0] + fragment = server_fragment() + row = fragment["smoke"]["platforms"][0] + row["dashboardApi"].pop("runtimeStatus") + with self.assertRaisesRegex(release.ReleaseError, "lacks successful authenticated API smoke"): + release.validate_fragment_evidence( + [fragment], + [image], + "3.9.0", + COMMIT, + {"core": "3.9.0", "studio": "3.9.0", "extensions": "3.9.0"}, + "published", + ) + def test_login_and_api_timeouts_name_the_failed_endpoint(self): with patch.object(release.urllib.request, "urlopen", side_effect=TimeoutError("timed out")): with self.assertRaisesRegex(release.ReleaseError, "identity/login failed"): diff --git a/src/Elsa.Server.Studio.BlazorServer/Elsa.Server.Studio.BlazorServer.csproj b/src/Elsa.Server.Studio.BlazorServer/Elsa.Server.Studio.BlazorServer.csproj index 76b24c6..c425d07 100644 --- a/src/Elsa.Server.Studio.BlazorServer/Elsa.Server.Studio.BlazorServer.csproj +++ b/src/Elsa.Server.Studio.BlazorServer/Elsa.Server.Studio.BlazorServer.csproj @@ -17,6 +17,7 @@ + @@ -30,6 +31,7 @@ + diff --git a/src/Elsa.Server.Studio.BlazorServer/Program.cs b/src/Elsa.Server.Studio.BlazorServer/Program.cs index e98f11e..30132f2 100644 --- a/src/Elsa.Server.Studio.BlazorServer/Program.cs +++ b/src/Elsa.Server.Studio.BlazorServer/Program.cs @@ -15,6 +15,7 @@ using Elsa.Studio.Models; using Elsa.Studio.Shell.Extensions; using Elsa.Studio.Http.Webhooks.Extensions; +using Elsa.Studio.Workflows.Dashboard.Extensions; using Elsa.Studio.Workflows.Designer.Extensions; using Elsa.Studio.Workflows.Extensions; using Elsa.Workflows.Runtime.Distributed.Extensions; @@ -41,6 +42,7 @@ runtime.UseEntityFrameworkCore(ef => ConfigureEntityFrameworkCore(ef, configuration)); runtime.UseDistributedRuntime(); }) + .UseWorkflowRuntimeDashboard() .UseScheduling() .UseJavaScript() .UseLiquid() @@ -87,6 +89,7 @@ services.AddRemoteBackend(backendApiConfig); services.AddLoginModule().UseElsaIdentity(); services.AddDashboardModule(backendApiConfig); +services.AddWorkflowsDashboardModule(); services.AddWorkflowsModule(); services.AddWebhooksModule(); services.AddAgentsModule(backendApiConfig); diff --git a/src/Elsa.Server.Studio.BlazorWasm.Client/Elsa.Server.Studio.BlazorWasm.Client.csproj b/src/Elsa.Server.Studio.BlazorWasm.Client/Elsa.Server.Studio.BlazorWasm.Client.csproj index 45bfeaa..e3fd1db 100644 --- a/src/Elsa.Server.Studio.BlazorWasm.Client/Elsa.Server.Studio.BlazorWasm.Client.csproj +++ b/src/Elsa.Server.Studio.BlazorWasm.Client/Elsa.Server.Studio.BlazorWasm.Client.csproj @@ -9,6 +9,7 @@ + diff --git a/src/Elsa.Server.Studio.BlazorWasm.Client/Program.cs b/src/Elsa.Server.Studio.BlazorWasm.Client/Program.cs index b42db46..994ee53 100644 --- a/src/Elsa.Server.Studio.BlazorWasm.Client/Program.cs +++ b/src/Elsa.Server.Studio.BlazorWasm.Client/Program.cs @@ -3,6 +3,7 @@ using Elsa.Studio.Core.BlazorWasm.Extensions; using Elsa.Studio.Dashboard.Extensions; using Elsa.Studio.Extensions; +using Elsa.Studio.Workflows.Dashboard.Extensions; using Elsa.Studio.Localization.Time; using Elsa.Studio.Localization.Time.Providers; using Elsa.Studio.Login.BlazorWasm.Extensions; @@ -41,6 +42,7 @@ builder.Services.AddRemoteBackend(backendApiConfig); builder.Services.AddLoginModule().UseElsaIdentity(); builder.Services.AddDashboardModule(backendApiConfig); +builder.Services.AddWorkflowsDashboardModule(); builder.Services.AddWorkflowsModule(); builder.Services.AddWorkflowContextsModule(); builder.Services.AddScoped(); diff --git a/src/Elsa.Server.Studio.BlazorWasm/Elsa.Server.Studio.BlazorWasm.csproj b/src/Elsa.Server.Studio.BlazorWasm/Elsa.Server.Studio.BlazorWasm.csproj index 21b6bab..b65e8ac 100644 --- a/src/Elsa.Server.Studio.BlazorWasm/Elsa.Server.Studio.BlazorWasm.csproj +++ b/src/Elsa.Server.Studio.BlazorWasm/Elsa.Server.Studio.BlazorWasm.csproj @@ -12,6 +12,7 @@ + diff --git a/src/Elsa.Server.Studio.BlazorWasm/Program.cs b/src/Elsa.Server.Studio.BlazorWasm/Program.cs index 131f19c..fa0356f 100644 --- a/src/Elsa.Server.Studio.BlazorWasm/Program.cs +++ b/src/Elsa.Server.Studio.BlazorWasm/Program.cs @@ -21,6 +21,7 @@ .UseDefaultAuthentication() .UseWorkflowManagement(management => management.UseEntityFrameworkCore(ef => ConfigureEntityFrameworkCore(ef, configuration))) .UseWorkflowRuntime(runtime => runtime.UseEntityFrameworkCore(ef => ConfigureEntityFrameworkCore(ef, configuration))) + .UseWorkflowRuntimeDashboard() .UseScheduling() .UseJavaScript() .UseLiquid() diff --git a/src/Elsa.Server/Elsa.Server.csproj b/src/Elsa.Server/Elsa.Server.csproj index 61eede5..9181a00 100644 --- a/src/Elsa.Server/Elsa.Server.csproj +++ b/src/Elsa.Server/Elsa.Server.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Elsa.Server/Program.cs b/src/Elsa.Server/Program.cs index 858c48e..1206610 100644 --- a/src/Elsa.Server/Program.cs +++ b/src/Elsa.Server/Program.cs @@ -35,6 +35,7 @@ runtime.UseDistributedRuntime(); runtime.WorkflowDispatcherOptions += options => configuration.Bind("Dispatcher", options); }) + .UseWorkflowRuntimeDashboard() .UseIdentity(identity => { identity.TokenOptions = options => configuration.Bind("Identity:Tokens", options); diff --git a/src/Elsa.Studio.BlazorServer/Bootstrap/Modules.cs b/src/Elsa.Studio.BlazorServer/Bootstrap/Modules.cs index d2a8880..3823d3e 100644 --- a/src/Elsa.Studio.BlazorServer/Bootstrap/Modules.cs +++ b/src/Elsa.Studio.BlazorServer/Bootstrap/Modules.cs @@ -3,6 +3,7 @@ using Elsa.Studio.Extensions; using Elsa.Studio.Http.Webhooks.Extensions; using Elsa.Studio.Models; +using Elsa.Studio.Workflows.Dashboard.Extensions; using Elsa.Studio.Workflows.Extensions; using Shared.Constants; using Shared.Options; @@ -18,8 +19,11 @@ public static IServiceCollection AddModules(this IServiceCollection services, IC if(modulesOptions.Features.Agents) services.AddAgentsModule(backendApiConfig); - if(modulesOptions.Features.Dashboard) + if (modulesOptions.Features.Dashboard) + { services.AddDashboardModule(backendApiConfig); + services.AddWorkflowsDashboardModule(); + } if (modulesOptions.Features.Webhooks) services.AddWebhooksModule(); diff --git a/src/Elsa.Studio.BlazorServer/Elsa.Studio.BlazorServer.csproj b/src/Elsa.Studio.BlazorServer/Elsa.Studio.BlazorServer.csproj index 13dd323..f2bb4f2 100644 --- a/src/Elsa.Studio.BlazorServer/Elsa.Studio.BlazorServer.csproj +++ b/src/Elsa.Studio.BlazorServer/Elsa.Studio.BlazorServer.csproj @@ -12,6 +12,7 @@ + diff --git a/src/Elsa.Studio.BlazorWasm.Client/Bootstrap/Modules.cs b/src/Elsa.Studio.BlazorWasm.Client/Bootstrap/Modules.cs index 3b5b319..a48bac3 100644 --- a/src/Elsa.Studio.BlazorWasm.Client/Bootstrap/Modules.cs +++ b/src/Elsa.Studio.BlazorWasm.Client/Bootstrap/Modules.cs @@ -3,6 +3,7 @@ using Elsa.Studio.Extensions; using Elsa.Studio.Http.Webhooks.Extensions; using Elsa.Studio.Models; +using Elsa.Studio.Workflows.Dashboard.Extensions; using Elsa.Studio.Workflows.Extensions; using Shared.Constants; using Shared.Options; @@ -18,8 +19,11 @@ public static IServiceCollection AddModules(this IServiceCollection services, IC if(modulesOptions.Features.Agents) services.AddAgentsModule(backendApiConfig); - if(modulesOptions.Features.Dashboard) + if (modulesOptions.Features.Dashboard) + { services.AddDashboardModule(backendApiConfig); + services.AddWorkflowsDashboardModule(); + } if (modulesOptions.Features.Webhooks) services.AddWebhooksModule(); diff --git a/src/Elsa.Studio.BlazorWasm.Client/Elsa.Studio.BlazorWasm.Client.csproj b/src/Elsa.Studio.BlazorWasm.Client/Elsa.Studio.BlazorWasm.Client.csproj index ea60faf..142a619 100644 --- a/src/Elsa.Studio.BlazorWasm.Client/Elsa.Studio.BlazorWasm.Client.csproj +++ b/src/Elsa.Studio.BlazorWasm.Client/Elsa.Studio.BlazorWasm.Client.csproj @@ -14,6 +14,7 @@ +