diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Artifacts/ExternalSystemArtifact.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Artifacts/ExternalSystemArtifact.cs
index f93e6ebb..6e689145 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Artifacts/ExternalSystemArtifact.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Artifacts/ExternalSystemArtifact.cs
@@ -5,4 +5,5 @@ public record ExternalSystemArtifact(
string EndpointUrl,
string AuthType,
string? AuthConfiguration,
- string? MethodDefinitionsJson);
+ string? MethodDefinitionsJson,
+ int TimeoutSeconds = 0);
diff --git a/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/ArtifactDeploymentService.cs b/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/ArtifactDeploymentService.cs
index 8d3d310c..41f06bf8 100644
--- a/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/ArtifactDeploymentService.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/ArtifactDeploymentService.cs
@@ -192,7 +192,7 @@ public class ArtifactDeploymentService
}))
: null;
externalSystemArtifacts.Add(new ExternalSystemArtifact(
- es.Name, es.EndpointUrl, es.AuthType, es.AuthConfiguration, methodsJson));
+ es.Name, es.EndpointUrl, es.AuthType, es.AuthConfiguration, methodsJson, es.TimeoutSeconds));
}
// Map database connections
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/DeploymentManagerActor.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/DeploymentManagerActor.cs
index f4953b55..fbbcf723 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/DeploymentManagerActor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/DeploymentManagerActor.cs
@@ -1885,7 +1885,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
foreach (var es in command.ExternalSystems)
{
await _storage.StoreExternalSystemAsync(es.Name, es.EndpointUrl,
- es.AuthType, es.AuthConfiguration, es.MethodDefinitionsJson);
+ es.AuthType, es.AuthConfiguration, es.MethodDefinitionsJson, es.TimeoutSeconds);
}
}
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/SiteReplicationActor.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/SiteReplicationActor.cs
index fe8a3a64..f7d9d375 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/SiteReplicationActor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/SiteReplicationActor.cs
@@ -349,7 +349,7 @@ public class SiteReplicationActor : ReceiveActor
if (command.ExternalSystems != null)
foreach (var es in command.ExternalSystems)
- await _storage.StoreExternalSystemAsync(es.Name, es.EndpointUrl, es.AuthType, es.AuthConfiguration, es.MethodDefinitionsJson);
+ await _storage.StoreExternalSystemAsync(es.Name, es.EndpointUrl, es.AuthType, es.AuthConfiguration, es.MethodDefinitionsJson, es.TimeoutSeconds);
if (command.DatabaseConnections != null)
foreach (var db in command.DatabaseConnections)
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Persistence/SiteStorageService.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Persistence/SiteStorageService.cs
index a4c86003..60800328 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Persistence/SiteStorageService.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Persistence/SiteStorageService.cs
@@ -174,6 +174,10 @@ public class SiteStorageService
// Native-alarm display metadata (UA4) — restored on rehydration so a persisted condition
// renders fully (type/category/message/values) before the first source snapshot arrives.
await TryAddColumnAsync(connection, "native_alarm_state", "metadata_json", "TEXT");
+
+ // Per-external-system call timeout (ExternalSystemGateway Timeout) — carried through the
+ // artifact pipeline so site-side calls honor it; 0 = use the site default.
+ await TryAddColumnAsync(connection, "external_systems", "timeout_seconds", "INTEGER NOT NULL DEFAULT 0");
}
private async Task TryAddColumnAsync(SqliteConnection connection, string table, string column, string type)
@@ -716,22 +720,25 @@ public class SiteStorageService
/// The authentication type (e.g., 'ApiKey', 'BasicAuth').
/// Authentication configuration JSON, if applicable.
/// JSON representation of available method definitions, if any.
+ /// Per-system call timeout in seconds (0 = use the site default).
/// A task that completes when the external system definition has been stored or updated.
public async Task StoreExternalSystemAsync(
- string name, string endpointUrl, string authType, string? authConfig, string? methodDefs)
+ string name, string endpointUrl, string authType, string? authConfig, string? methodDefs,
+ int timeoutSeconds = 0)
{
await using var connection = new SqliteConnection(_connectionString);
await connection.OpenAsync();
await using var command = connection.CreateCommand();
command.CommandText = @"
- INSERT INTO external_systems (name, endpoint_url, auth_type, auth_configuration, method_definitions, updated_at)
- VALUES (@name, @url, @authType, @authConfig, @methodDefs, @updatedAt)
+ INSERT INTO external_systems (name, endpoint_url, auth_type, auth_configuration, method_definitions, timeout_seconds, updated_at)
+ VALUES (@name, @url, @authType, @authConfig, @methodDefs, @timeoutSeconds, @updatedAt)
ON CONFLICT(name) DO UPDATE SET
endpoint_url = excluded.endpoint_url,
auth_type = excluded.auth_type,
auth_configuration = excluded.auth_configuration,
method_definitions = excluded.method_definitions,
+ timeout_seconds = excluded.timeout_seconds,
updated_at = excluded.updated_at";
command.Parameters.AddWithValue("@name", name);
@@ -739,6 +746,7 @@ public class SiteStorageService
command.Parameters.AddWithValue("@authType", authType);
command.Parameters.AddWithValue("@authConfig", (object?)authConfig ?? DBNull.Value);
command.Parameters.AddWithValue("@methodDefs", (object?)methodDefs ?? DBNull.Value);
+ command.Parameters.AddWithValue("@timeoutSeconds", timeoutSeconds);
command.Parameters.AddWithValue("@updatedAt", DateTimeOffset.UtcNow.ToString("O"));
await command.ExecuteNonQueryAsync();
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Repositories/SiteExternalSystemRepository.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Repositories/SiteExternalSystemRepository.cs
index 8b378390..efb8a48e 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Repositories/SiteExternalSystemRepository.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Repositories/SiteExternalSystemRepository.cs
@@ -36,7 +36,7 @@ public class SiteExternalSystemRepository : IExternalSystemRepository
await using var command = connection.CreateCommand();
command.CommandText = @"
- SELECT name, endpoint_url, auth_type, auth_configuration
+ SELECT name, endpoint_url, auth_type, auth_configuration, timeout_seconds
FROM external_systems";
var results = new List();
@@ -68,7 +68,7 @@ public class SiteExternalSystemRepository : IExternalSystemRepository
await using var command = connection.CreateCommand();
command.CommandText = @"
- SELECT name, endpoint_url, auth_type, auth_configuration
+ SELECT name, endpoint_url, auth_type, auth_configuration, timeout_seconds
FROM external_systems
WHERE name = @name";
command.Parameters.AddWithValue("@name", name);
@@ -263,7 +263,8 @@ public class SiteExternalSystemRepository : IExternalSystemRepository
authType: reader.GetString(2))
{
Id = GenerateSyntheticId(name),
- AuthConfiguration = reader.IsDBNull(3) ? null : reader.GetString(3)
+ AuthConfiguration = reader.IsDBNull(3) ? null : reader.GetString(3),
+ TimeoutSeconds = reader.IsDBNull(4) ? 0 : reader.GetInt32(4)
};
}
diff --git a/tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Repositories/SiteRepositoryTests.cs b/tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Repositories/SiteRepositoryTests.cs
index c1a3e88e..14b1f08b 100644
--- a/tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Repositories/SiteRepositoryTests.cs
+++ b/tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Repositories/SiteRepositoryTests.cs
@@ -54,6 +54,26 @@ public class SiteRepositoryTests : IDisposable
Assert.Equal("https://api.example.com", all[0].EndpointUrl);
}
+ ///
+ /// ExternalSystemGateway (Timeout): the per-system TimeoutSeconds stored via
+ /// must round-trip through the
+ /// site SQLite store and back out via the repository, so site-side calls honor it.
+ ///
+ [Fact]
+ public async Task SiteStorage_ExternalSystem_TimeoutSeconds_RoundTrips()
+ {
+ var storage = NewStorage();
+ await storage.InitializeAsync();
+ await storage.StoreExternalSystemAsync(
+ "WeatherApi", "https://api.example.com", "ApiKey", "{\"key\":\"x\"}", null, 7);
+
+ var repo = new SiteExternalSystemRepository(storage);
+ var found = await repo.GetExternalSystemByNameAsync("WeatherApi");
+
+ Assert.NotNull(found);
+ Assert.Equal(7, found!.TimeoutSeconds);
+ }
+
///
/// SiteRuntime-007: the synthetic ID for an external system must be identical when
/// the storage service and repository are re-created (simulating a process restart).