feat(scripts): realign Test Run with runtime API, add anonymous-object calls and instance binding
The Test Run sandbox and Monaco analysis modelled a script API that had drifted from the site runtime's ScriptGlobals, so real scripts failed to compile in Test Run. Realign both to the runtime surface (Instance/Scripts/ExternalSystem/Attributes/Children/Parent) and drop the duplicate ScriptHost stub so the two cannot diverge again. - Script calls (Scripts.CallShared, Instance.CallScript, Route.To().Call) accept an anonymous object instead of a hand-built dictionary, via a shared ScriptArgs normalizer; existing dictionary calls still compile. - Test Run can optionally bind to a deployed instance, so Instance/ Attributes/CallScript route to it cross-site; adds site-side RouteToGetAttributes/RouteToSetAttributes handlers. - Adds Test Run panels to the API method and template script editors. - Fixes the TestDatabaseQuery seed script, which queried a table that never existed. Also commits unrelated in-progress work already in the tree: the health monitoring report loop, site streaming changes, and the Admin/Design data-connection and SMTP page reorganization.
This commit is contained in:
195
infra/mssql/seed-config.sql
Normal file
195
infra/mssql/seed-config.sql
Normal file
@@ -0,0 +1,195 @@
|
||||
-- ScadaLink design-data seed.
|
||||
-- Auto-generated by infra/tools/dump_seed.py against ScadaLinkConfig.
|
||||
-- Replays the design-time configuration (templates, scripts,
|
||||
-- data connections, external systems). Idempotent: deletes
|
||||
-- existing rows in the covered tables before inserting.
|
||||
--
|
||||
-- Excluded: Sites (seed via docker/seed-sites.sh), Instances,
|
||||
-- InstanceConnectionBindings, notifications, SMTP, API keys,
|
||||
-- areas, LDAP mappings.
|
||||
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
SET QUOTED_IDENTIFIER ON;
|
||||
BEGIN TRAN;
|
||||
|
||||
-- Wipe existing design + dependent rows so the seed is idempotent.
|
||||
-- Order matters: dependents first.
|
||||
DELETE FROM DeployedConfigSnapshots;
|
||||
DELETE FROM DeploymentRecords;
|
||||
DELETE FROM InstanceAlarmOverrides;
|
||||
DELETE FROM InstanceAttributeOverrides;
|
||||
DELETE FROM InstanceConnectionBindings;
|
||||
DELETE FROM Instances;
|
||||
DELETE FROM ExternalSystemMethods;
|
||||
DELETE FROM ExternalSystemDefinitions;
|
||||
DELETE FROM DataConnections;
|
||||
DELETE FROM SharedScripts;
|
||||
DELETE FROM TemplateCompositions;
|
||||
UPDATE TemplateAlarms SET OnTriggerScriptId = NULL;
|
||||
DELETE FROM TemplateAlarms;
|
||||
DELETE FROM TemplateScripts;
|
||||
DELETE FROM TemplateAttributes;
|
||||
UPDATE Templates SET ParentTemplateId = NULL, OwnerCompositionId = NULL;
|
||||
DELETE FROM Templates;
|
||||
UPDATE TemplateFolders SET ParentFolderId = NULL;
|
||||
DELETE FROM TemplateFolders;
|
||||
|
||||
-- TemplateFolders (1 rows)
|
||||
SET IDENTITY_INSERT [TemplateFolders] ON;
|
||||
INSERT INTO [TemplateFolders] ([Id], [Name], [ParentFolderId], [SortOrder]) VALUES (1002, N'Test', NULL, 0);
|
||||
SET IDENTITY_INSERT [TemplateFolders] OFF;
|
||||
|
||||
-- Templates (18 rows)
|
||||
SET IDENTITY_INSERT [Templates] ON;
|
||||
INSERT INTO [Templates] ([Id], [Name], [Description], [ParentTemplateId], [FolderId], [IsDerived], [OwnerCompositionId]) VALUES (1, N'Base Device', N'Root template for all devices', NULL, NULL, 0, NULL);
|
||||
INSERT INTO [Templates] ([Id], [Name], [Description], [ParentTemplateId], [FolderId], [IsDerived], [OwnerCompositionId]) VALUES (2, N'Pump', N'Centrifugal pump template', 1, NULL, 0, NULL);
|
||||
INSERT INTO [Templates] ([Id], [Name], [Description], [ParentTemplateId], [FolderId], [IsDerived], [OwnerCompositionId]) VALUES (3, N'Sensor Module', N'Reusable sensor feature module', NULL, 1002, 0, NULL);
|
||||
INSERT INTO [Templates] ([Id], [Name], [Description], [ParentTemplateId], [FolderId], [IsDerived], [OwnerCompositionId]) VALUES (4, N'Motor Controller', N'Motor with OPC UA tags from test server', NULL, 1002, 0, NULL);
|
||||
INSERT INTO [Templates] ([Id], [Name], [Description], [ParentTemplateId], [FolderId], [IsDerived], [OwnerCompositionId]) VALUES (5, N'Variable Speed Motor', N'VFD motor extending Motor Controller with sensor composition', 4, NULL, 0, NULL);
|
||||
INSERT INTO [Templates] ([Id], [Name], [Description], [ParentTemplateId], [FolderId], [IsDerived], [OwnerCompositionId]) VALUES (1002, N'Tank Monitor', N'Tank level and temperature monitoring module', NULL, NULL, 0, NULL);
|
||||
INSERT INTO [Templates] ([Id], [Name], [Description], [ParentTemplateId], [FolderId], [IsDerived], [OwnerCompositionId]) VALUES (2003, N'Pump.TempSensor', N'Reusable sensor feature module', 3, NULL, 1, 1);
|
||||
INSERT INTO [Templates] ([Id], [Name], [Description], [ParentTemplateId], [FolderId], [IsDerived], [OwnerCompositionId]) VALUES (2004, N'Variable Speed Motor.TempSensor', N'Reusable sensor feature module', 3, NULL, 1, 2);
|
||||
INSERT INTO [Templates] ([Id], [Name], [Description], [ParentTemplateId], [FolderId], [IsDerived], [OwnerCompositionId]) VALUES (2005, N'Motor Controller.CoolingTank', N'Tank level and temperature monitoring module', 1002, NULL, 1, 1002);
|
||||
INSERT INTO [Templates] ([Id], [Name], [Description], [ParentTemplateId], [FolderId], [IsDerived], [OwnerCompositionId]) VALUES (2006, N'Motor Controller.CoolingTank2', N'Tank level and temperature monitoring module', 1002, NULL, 1, 1003);
|
||||
INSERT INTO [Templates] ([Id], [Name], [Description], [ParentTemplateId], [FolderId], [IsDerived], [OwnerCompositionId]) VALUES (2007, N'aaa', NULL, 3, NULL, 0, NULL);
|
||||
INSERT INTO [Templates] ([Id], [Name], [Description], [ParentTemplateId], [FolderId], [IsDerived], [OwnerCompositionId]) VALUES (2008, N'Pump.AlarmSensor', N'Reusable sensor feature module', 3, NULL, 1, 1004);
|
||||
INSERT INTO [Templates] ([Id], [Name], [Description], [ParentTemplateId], [FolderId], [IsDerived], [OwnerCompositionId]) VALUES (2012, N'Tank Monitor.DrivePump', N'Centrifugal pump template', 2, NULL, 1, 1008);
|
||||
INSERT INTO [Templates] ([Id], [Name], [Description], [ParentTemplateId], [FolderId], [IsDerived], [OwnerCompositionId]) VALUES (2013, N'Tank Monitor.DrivePump.TempSensor', N'Reusable sensor feature module', 2003, NULL, 1, 1009);
|
||||
INSERT INTO [Templates] ([Id], [Name], [Description], [ParentTemplateId], [FolderId], [IsDerived], [OwnerCompositionId]) VALUES (2014, N'Tank Monitor.DrivePump.AlarmSensor', N'Reusable sensor feature module', 2008, NULL, 1, 1010);
|
||||
INSERT INTO [Templates] ([Id], [Name], [Description], [ParentTemplateId], [FolderId], [IsDerived], [OwnerCompositionId]) VALUES (2018, N'Motor Controller.Pump', N'Centrifugal pump template', 2, NULL, 1, 1014);
|
||||
INSERT INTO [Templates] ([Id], [Name], [Description], [ParentTemplateId], [FolderId], [IsDerived], [OwnerCompositionId]) VALUES (2019, N'Motor Controller.Pump.TempSensor', N'Reusable sensor feature module', 2003, NULL, 1, 1015);
|
||||
INSERT INTO [Templates] ([Id], [Name], [Description], [ParentTemplateId], [FolderId], [IsDerived], [OwnerCompositionId]) VALUES (2020, N'Motor Controller.Pump.AlarmSensor', N'Reusable sensor feature module', 2008, NULL, 1, 1016);
|
||||
SET IDENTITY_INSERT [Templates] OFF;
|
||||
|
||||
-- TemplateAttributes (48 rows)
|
||||
SET IDENTITY_INSERT [TemplateAttributes] ON;
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (1, 1, N'Status', N'Offline', N'String', 0, NULL, NULL, 0, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (2, 1, N'Temperature', N'0.0', N'Double', 0, NULL, N'ns=3;s=Temperature', 0, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (9, 3, N'SensorReading', N'0', N'Double', 0, NULL, N'ns=3;s=Sensor.Reading', 0, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (10, 3, N'SensorUnit', N'Celsius', N'String', 0, NULL, NULL, 0, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (11, 5, N'MaxRPM', N'3600', N'Double', 0, NULL, NULL, 0, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (12, 5, N'MinRPM', N'0', N'Double', 0, NULL, NULL, 0, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (1002, 4, N'Weather', N'Unknown', N'String', 0, NULL, NULL, 0, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (1003, 4, N'Greeting', N'', N'String', 0, NULL, NULL, 0, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (1004, 4, N'Goodbye', N'', N'String', 0, NULL, NULL, 0, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (1005, 1002, N'Level', N'0', N'Float', 0, NULL, N'ns=3;s=Tank.Level', 0, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (1006, 1002, N'Temperature', N'0', N'Float', 0, NULL, N'ns=3;s=Tank.Temperature', 0, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (1007, 1002, N'HighLevel', N'false', N'Boolean', 0, NULL, N'ns=3;s=Tank.HighLevel', 0, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (1008, 1002, N'LowLevel', N'false', N'Boolean', 0, NULL, N'ns=3;s=Tank.LowLevel', 0, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (2009, 4, N'TestBool', NULL, N'Boolean', 0, NULL, N'ns=3;s=TestChildObject.TestBool', 0, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (2010, 4, N'TestInt', NULL, N'Int32', 0, NULL, N'ns=3;s=TestChildObject.TestInt', 0, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (2011, 4, N'TestFloat', NULL, N'Float', 0, NULL, N'ns=3;s=TestChildObject.TestFloat', 0, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (2012, 4, N'TestDouble', NULL, N'Double', 0, NULL, N'ns=3;s=TestChildObject.TestDouble', 0, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (2013, 4, N'TestString', NULL, N'String', 0, NULL, N'ns=3;s=TestChildObject.TestString', 0, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (2014, 4, N'TestDateTime', NULL, N'String', 0, NULL, N'ns=3;s=TestChildObject.TestDateTime', 0, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (2015, 4, N'TestBoolArray', NULL, N'String', 0, NULL, N'ns=3;s=TestChildObject.TestBoolArray', 0, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (2016, 4, N'TestDateTimeArray', NULL, N'String', 0, NULL, N'ns=3;s=TestChildObject.TestDateTimeArray', 0, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (2017, 4, N'TestDoubleArray', NULL, N'String', 0, NULL, N'ns=3;s=TestChildObject.TestDoubleArray', 0, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (2018, 4, N'TestFloatArray', NULL, N'String', 0, NULL, N'ns=3;s=TestChildObject.TestFloatArray', 0, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (2019, 4, N'TestIntArray', NULL, N'String', 0, NULL, N'ns=3;s=TestChildObject.TestIntArray', 0, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (2020, 4, N'TestStringArray', NULL, N'String', 0, NULL, N'ns=3;s=TestChildObject.TestStringArray', 0, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (2021, 4, N'ScanTime', NULL, N'String', 0, NULL, N'ns=3;s=DevAppEngine.Scheduler.ScanTime', 0, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (3009, 2003, N'SensorReading', N'0', N'Double', 0, NULL, N'ns=3;s=Sensor.Reading', 1, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (3010, 2003, N'SensorUnit', N'Celsius', N'String', 0, NULL, NULL, 1, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (3011, 2004, N'SensorReading', N'0', N'Double', 0, NULL, N'ns=3;s=Sensor.Reading', 1, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (3012, 2004, N'SensorUnit', N'Celsius', N'String', 0, NULL, NULL, 1, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (3013, 2005, N'Level', N'0', N'Float', 0, NULL, N'ns=3;s=Tank.Level', 1, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (3014, 2005, N'Temperature', N'0', N'Float', 0, NULL, N'ns=3;s=Tank.Temperature', 1, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (3015, 2005, N'HighLevel', N'false', N'Boolean', 0, NULL, N'ns=3;s=Tank.HighLevel', 1, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (3016, 2005, N'LowLevel', N'false', N'Boolean', 0, NULL, N'ns=3;s=Tank.LowLevel', 1, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (3017, 2006, N'Level', N'0', N'Float', 0, NULL, N'ns=3;s=Tank.Level', 1, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (3018, 2006, N'Temperature', N'0', N'Float', 0, NULL, N'ns=3;s=Tank.Temperature', 1, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (3019, 2006, N'HighLevel', N'false', N'Boolean', 0, NULL, N'ns=3;s=Tank.HighLevel', 1, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (3020, 2006, N'LowLevel', N'false', N'Boolean', 0, NULL, N'ns=3;s=Tank.LowLevel', 1, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (3021, 2008, N'SensorReading', N'0', N'Double', 0, NULL, N'ns=3;s=Sensor.Reading', 1, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (3022, 2008, N'SensorUnit', N'Celsius', N'String', 0, NULL, NULL, 1, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (3025, 2013, N'SensorReading', N'0', N'Double', 0, NULL, N'ns=3;s=Sensor.Reading', 1, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (3026, 2013, N'SensorUnit', N'Celsius', N'String', 0, NULL, NULL, 1, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (3027, 2014, N'SensorReading', N'0', N'Double', 0, NULL, N'ns=3;s=Sensor.Reading', 1, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (3028, 2014, N'SensorUnit', N'Celsius', N'String', 0, NULL, NULL, 1, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (3033, 2019, N'SensorReading', N'0', N'Double', 0, NULL, N'ns=3;s=Sensor.Reading', 1, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (3034, 2019, N'SensorUnit', N'Celsius', N'String', 0, NULL, NULL, 1, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (3035, 2020, N'SensorReading', N'0', N'Double', 0, NULL, N'ns=3;s=Sensor.Reading', 1, 0);
|
||||
INSERT INTO [TemplateAttributes] ([Id], [TemplateId], [Name], [Value], [DataType], [IsLocked], [Description], [DataSourceReference], [IsInherited], [LockedInDerived]) VALUES (3036, 2020, N'SensorUnit', N'Celsius', N'String', 0, NULL, NULL, 1, 0);
|
||||
SET IDENTITY_INSERT [TemplateAttributes] OFF;
|
||||
|
||||
-- TemplateScripts (12 rows)
|
||||
SET IDENTITY_INSERT [TemplateScripts] ON;
|
||||
INSERT INTO [TemplateScripts] ([Id], [TemplateId], [Name], [IsLocked], [Code], [TriggerType], [TriggerConfiguration], [ParameterDefinitions], [ReturnDefinition], [MinTimeBetweenRuns], [IsInherited], [LockedInDerived]) VALUES (1, 1, N'CheckTemp', 0, N'var temp = Instance.GetAttribute("Temperature");
|
||||
if (temp.Value > 90.0) {
|
||||
Instance.SetAttribute("Status", "HighTemp");
|
||||
}', N'ValueChange', NULL, NULL, NULL, NULL, 0, 0);
|
||||
INSERT INTO [TemplateScripts] ([Id], [TemplateId], [Name], [IsLocked], [Code], [TriggerType], [TriggerConfiguration], [ParameterDefinitions], [ReturnDefinition], [MinTimeBetweenRuns], [IsInherited], [LockedInDerived]) VALUES (1002, 4, N'TestExternalSystem', 0, N'var parms = new Dictionary<string, object?> { ["a"] = 2, ["b"] = 3 }; var result = await ExternalSystem.Call("Test REST API", "Add", parms); Instance.SetAttribute("Status", "API result: " + result.Response.result);', N'Interval', N'{"intervalMs":10000}', NULL, NULL, NULL, 0, 0);
|
||||
INSERT INTO [TemplateScripts] ([Id], [TemplateId], [Name], [IsLocked], [Code], [TriggerType], [TriggerConfiguration], [ParameterDefinitions], [ReturnDefinition], [MinTimeBetweenRuns], [IsInherited], [LockedInDerived]) VALUES (1003, 4, N'TestDatabaseQuery', 0, N'var conn = await Database.Connection("Machine Data DB"); var cmd = conn.CreateCommand(); cmd.CommandText = "SELECT COUNT(*) FROM TagHistory"; var count = await cmd.ExecuteScalarAsync(); conn.Dispose(); Instance.SetAttribute("Status", "DB: " + count + " rows");', N'Interval', N'{"intervalMs":60000}', NULL, NULL, NULL, 0, 0);
|
||||
INSERT INTO [TemplateScripts] ([Id], [TemplateId], [Name], [IsLocked], [Code], [TriggerType], [TriggerConfiguration], [ParameterDefinitions], [ReturnDefinition], [MinTimeBetweenRuns], [IsInherited], [LockedInDerived]) VALUES (1004, 4, N'UpdateWeather', 0, N'var weather = await Scripts.CallShared("GetWeather"); Instance.SetAttribute("Weather", weather?.ToString() ?? "Unknown");', N'Interval', N'{"intervalMs":10000}', NULL, NULL, NULL, 0, 0);
|
||||
INSERT INTO [TemplateScripts] ([Id], [TemplateId], [Name], [IsLocked], [Code], [TriggerType], [TriggerConfiguration], [ParameterDefinitions], [ReturnDefinition], [MinTimeBetweenRuns], [IsInherited], [LockedInDerived]) VALUES (1005, 4, N'UpdateGreeting', 0, N'var parms = new Dictionary<string, object?> { ["name"] = "BOB" }; var greeting = await Scripts.CallShared("Greet", parms); Instance.SetAttribute("Greeting", greeting?.ToString() ?? "");', N'Interval', N'{"intervalMs":10000}', NULL, NULL, NULL, 0, 0);
|
||||
INSERT INTO [TemplateScripts] ([Id], [TemplateId], [Name], [IsLocked], [Code], [TriggerType], [TriggerConfiguration], [ParameterDefinitions], [ReturnDefinition], [MinTimeBetweenRuns], [IsInherited], [LockedInDerived]) VALUES (1007, 4, N'SayGoodbye', 0, N'var name = (string)(Parameters?["Name"] ?? "World"); return $"Goodbye {name}! It is {DateTimeOffset.UtcNow:HH:mm:ss} UTC";', N'Call', N'{}', N'{"type":"object","properties":{"Name":{"type":"string"}},"required":["Name"]}', N'{"type":"string"}', NULL, 0, 0);
|
||||
INSERT INTO [TemplateScripts] ([Id], [TemplateId], [Name], [IsLocked], [Code], [TriggerType], [TriggerConfiguration], [ParameterDefinitions], [ReturnDefinition], [MinTimeBetweenRuns], [IsInherited], [LockedInDerived]) VALUES (1008, 4, N'UpdateGoodbye', 0, N'var parms = new Dictionary<string, object?> { ["Name"] = "Bob" }; var result = await Instance.CallScript("SayGoodbye", parms); Instance.SetAttribute("Goodbye", result?.ToString() ?? "");', N'Interval', N'{"intervalMs":10000}', NULL, NULL, NULL, 0, 0);
|
||||
INSERT INTO [TemplateScripts] ([Id], [TemplateId], [Name], [IsLocked], [Code], [TriggerType], [TriggerConfiguration], [ParameterDefinitions], [ReturnDefinition], [MinTimeBetweenRuns], [IsInherited], [LockedInDerived]) VALUES (1009, 4, N'Hello', 0, N'var name = (string)(Parameters?["Name"] ?? "World"); return $"Hello {name}! It is {DateTimeOffset.UtcNow:HH:mm:ss} UTC";', N'Call', N'{}', N'{"type":"object","properties":{"Name":{"type":"string"}},"required":["Name"]}', N'{"type":"string"}', NULL, 0, 0);
|
||||
INSERT INTO [TemplateScripts] ([Id], [TemplateId], [Name], [IsLocked], [Code], [TriggerType], [TriggerConfiguration], [ParameterDefinitions], [ReturnDefinition], [MinTimeBetweenRuns], [IsInherited], [LockedInDerived]) VALUES (1010, 4, N'SendEmailAlert', 0, N'await Notify.To("Engineering Alerts").Send("Motor Status Update", "Motor check-in at " + DateTimeOffset.UtcNow.ToString("HH:mm:ss") + " UTC");', N'Interval', N'{"intervalMs":10000}', NULL, NULL, NULL, 0, 0);
|
||||
INSERT INTO [TemplateScripts] ([Id], [TemplateId], [Name], [IsLocked], [Code], [TriggerType], [TriggerConfiguration], [ParameterDefinitions], [ReturnDefinition], [MinTimeBetweenRuns], [IsInherited], [LockedInDerived]) VALUES (1011, 1002, N'AddNumbers', 0, N'var a = Convert.ToDouble(Parameters?["a"] ?? 0); var b = Convert.ToDouble(Parameters?["b"] ?? 0); return a + b;', N'Call', N'{}', N'{"type":"object","properties":{"a":{"type":"number"},"b":{"type":"number"}},"required":["a","b"]}', N'{"type":"number"}', NULL, 0, 0);
|
||||
INSERT INTO [TemplateScripts] ([Id], [TemplateId], [Name], [IsLocked], [Code], [TriggerType], [TriggerConfiguration], [ParameterDefinitions], [ReturnDefinition], [MinTimeBetweenRuns], [IsInherited], [LockedInDerived]) VALUES (1012, 2005, N'AddNumbers', 0, N'var a = Convert.ToDouble(Parameters?["a"] ?? 0); var b = Convert.ToDouble(Parameters?["b"] ?? 0); return a + b;', N'Call', N'{}', N'{"type":"object","properties":{"a":{"type":"number"},"b":{"type":"number"}},"required":["a","b"]}', N'{"type":"number"}', NULL, 1, 0);
|
||||
INSERT INTO [TemplateScripts] ([Id], [TemplateId], [Name], [IsLocked], [Code], [TriggerType], [TriggerConfiguration], [ParameterDefinitions], [ReturnDefinition], [MinTimeBetweenRuns], [IsInherited], [LockedInDerived]) VALUES (1013, 2006, N'AddNumbers', 0, N'var a = Convert.ToDouble(Parameters?["a"] ?? 0); var b = Convert.ToDouble(Parameters?["b"] ?? 0); return a + b;', N'Call', N'{}', N'{"type":"object","properties":{"a":{"type":"number"},"b":{"type":"number"}},"required":["a","b"]}', N'{"type":"number"}', NULL, 1, 0);
|
||||
SET IDENTITY_INSERT [TemplateScripts] OFF;
|
||||
|
||||
-- TemplateAlarms (4 rows)
|
||||
SET IDENTITY_INSERT [TemplateAlarms] ON;
|
||||
INSERT INTO [TemplateAlarms] ([Id], [TemplateId], [Name], [Description], [PriorityLevel], [IsLocked], [TriggerType], [TriggerConfiguration], [OnTriggerScriptId]) VALUES (1, 1, N'HighTemp', NULL, 800, 0, N'RangeViolation', N'{"attribute":"Temperature","high":95.0}', NULL);
|
||||
INSERT INTO [TemplateAlarms] ([Id], [TemplateId], [Name], [Description], [PriorityLevel], [IsLocked], [TriggerType], [TriggerConfiguration], [OnTriggerScriptId]) VALUES (1002, 1002, N'HighLevel', NULL, 800, 0, N'RangeViolation', N'{"attribute":"Level","high":80}', NULL);
|
||||
INSERT INTO [TemplateAlarms] ([Id], [TemplateId], [Name], [Description], [PriorityLevel], [IsLocked], [TriggerType], [TriggerConfiguration], [OnTriggerScriptId]) VALUES (1003, 2, N'RatePump', NULL, 750, 0, N'RateOfChange', N'{"attributeName":"AlarmSensor.SensorReading","thresholdPerSecond":25,"windowSeconds":2,"direction":"falling"}', NULL);
|
||||
INSERT INTO [TemplateAlarms] ([Id], [TemplateId], [Name], [Description], [PriorityLevel], [IsLocked], [TriggerType], [TriggerConfiguration], [OnTriggerScriptId]) VALUES (1004, 2, N'TempLevels', NULL, 500, 0, N'HiLo', N'{"attributeName":"AlarmSensor.SensorReading","loLo":-10,"lo":5,"hi":80,"hiHi":100,"loLoPriority":900,"loPriority":600,"hiPriority":600,"hiHiPriority":900,"hiDeadband":3,"hiHiDeadband":5,"hiMessage":"Temperature high — investigate","hiHiMessage":"CRITICAL: shut down immediately"}', NULL);
|
||||
SET IDENTITY_INSERT [TemplateAlarms] OFF;
|
||||
|
||||
-- TemplateCompositions (11 rows)
|
||||
SET IDENTITY_INSERT [TemplateCompositions] ON;
|
||||
INSERT INTO [TemplateCompositions] ([Id], [TemplateId], [ComposedTemplateId], [InstanceName]) VALUES (1, 2, 2003, N'TempSensor');
|
||||
INSERT INTO [TemplateCompositions] ([Id], [TemplateId], [ComposedTemplateId], [InstanceName]) VALUES (2, 5, 2004, N'TempSensor');
|
||||
INSERT INTO [TemplateCompositions] ([Id], [TemplateId], [ComposedTemplateId], [InstanceName]) VALUES (1002, 4, 2005, N'CoolingTank');
|
||||
INSERT INTO [TemplateCompositions] ([Id], [TemplateId], [ComposedTemplateId], [InstanceName]) VALUES (1003, 4, 2006, N'CoolingTank2');
|
||||
INSERT INTO [TemplateCompositions] ([Id], [TemplateId], [ComposedTemplateId], [InstanceName]) VALUES (1004, 2, 2008, N'AlarmSensor');
|
||||
INSERT INTO [TemplateCompositions] ([Id], [TemplateId], [ComposedTemplateId], [InstanceName]) VALUES (1008, 1002, 2012, N'DrivePump');
|
||||
INSERT INTO [TemplateCompositions] ([Id], [TemplateId], [ComposedTemplateId], [InstanceName]) VALUES (1009, 2012, 2013, N'TempSensor');
|
||||
INSERT INTO [TemplateCompositions] ([Id], [TemplateId], [ComposedTemplateId], [InstanceName]) VALUES (1010, 2012, 2014, N'AlarmSensor');
|
||||
INSERT INTO [TemplateCompositions] ([Id], [TemplateId], [ComposedTemplateId], [InstanceName]) VALUES (1014, 4, 2018, N'Pump');
|
||||
INSERT INTO [TemplateCompositions] ([Id], [TemplateId], [ComposedTemplateId], [InstanceName]) VALUES (1015, 2018, 2019, N'TempSensor');
|
||||
INSERT INTO [TemplateCompositions] ([Id], [TemplateId], [ComposedTemplateId], [InstanceName]) VALUES (1016, 2018, 2020, N'AlarmSensor');
|
||||
SET IDENTITY_INSERT [TemplateCompositions] OFF;
|
||||
|
||||
-- SharedScripts (2 rows)
|
||||
SET IDENTITY_INSERT [SharedScripts] ON;
|
||||
INSERT INTO [SharedScripts] ([Id], [Name], [Code], [ParameterDefinitions], [ReturnDefinition]) VALUES (1, N'GetWeather', N'var conditions = new[]
|
||||
{
|
||||
"Sunny",
|
||||
"Cloudy",
|
||||
"Rainy",
|
||||
"Stormy",
|
||||
"Windy",
|
||||
"Foggy",
|
||||
"Snowy",
|
||||
"Clear"
|
||||
};
|
||||
var temps = new Random().Next(-10, 40);
|
||||
var condition = conditions[new Random().Next(conditions.Length)];
|
||||
return $"{condition}, {temps}°C";', NULL, N'{"type":"string"}');
|
||||
INSERT INTO [SharedScripts] ([Id], [Name], [Code], [ParameterDefinitions], [ReturnDefinition]) VALUES (2, N'Greet', N'var name = (string)(Parameters?["name"] ?? "World"); return $"Hello, {name}! It is {DateTimeOffset.UtcNow:HH:mm:ss} UTC";', N'{"type":"object","properties":{"name":{"type":"string"}},"required":["name"]}', N'{"type":"string"}');
|
||||
SET IDENTITY_INSERT [SharedScripts] OFF;
|
||||
|
||||
-- DataConnections (3 rows)
|
||||
SET IDENTITY_INSERT [DataConnections] ON;
|
||||
INSERT INTO [DataConnections] ([Id], [Name], [Protocol], [PrimaryConfiguration], [SiteId], [BackupConfiguration], [FailoverRetryCount]) VALUES (1, N'OPC PLC Simulator', N'OpcUa', N'{"endpointUrl":"opc.tcp://scadalink-opcua:50000","securityMode":"none","autoAcceptUntrustedCerts":true,"sessionTimeoutMs":60000,"operationTimeoutMs":15000,"publishingIntervalMs":1000,"samplingIntervalMs":1000,"queueSize":10,"keepAliveCount":10,"lifetimeCount":30,"maxNotificationsPerPublish":100,"discardOldest":true,"subscriptionPriority":0,"subscriptionDisplayName":"ScadaLink","timestampsToReturn":"source","deadband":null,"userIdentity":null,"heartbeat":null}', 1, NULL, 3);
|
||||
INSERT INTO [DataConnections] ([Id], [Name], [Protocol], [PrimaryConfiguration], [SiteId], [BackupConfiguration], [FailoverRetryCount]) VALUES (3014, N'OPC PLC Simulator', N'OpcUa', N'{"endpoint":"opc.tcp://scadalink-opcua:50000","securityMode":"None","publishInterval":1000}', 2, NULL, 3);
|
||||
INSERT INTO [DataConnections] ([Id], [Name], [Protocol], [PrimaryConfiguration], [SiteId], [BackupConfiguration], [FailoverRetryCount]) VALUES (3015, N'OPC PLC Simulator', N'OpcUa', N'{"endpoint":"opc.tcp://scadalink-opcua:50000","securityMode":"None","publishInterval":1000}', 3, NULL, 3);
|
||||
SET IDENTITY_INSERT [DataConnections] OFF;
|
||||
|
||||
-- ExternalSystemDefinitions (1 rows)
|
||||
SET IDENTITY_INSERT [ExternalSystemDefinitions] ON;
|
||||
INSERT INTO [ExternalSystemDefinitions] ([Id], [Name], [EndpointUrl], [AuthType], [AuthConfiguration], [MaxRetries], [RetryDelay]) VALUES (1, N'Test REST API', N'http://scadalink-restapi:5200', N'ApiKey', N'scadalink-test-key-1', 0, '00:00:00.000000');
|
||||
SET IDENTITY_INSERT [ExternalSystemDefinitions] OFF;
|
||||
|
||||
-- ExternalSystemMethods (1 rows)
|
||||
SET IDENTITY_INSERT [ExternalSystemMethods] ON;
|
||||
INSERT INTO [ExternalSystemMethods] ([Id], [ExternalSystemDefinitionId], [Name], [HttpMethod], [Path], [ParameterDefinitions], [ReturnDefinition]) VALUES (1, 1, N'Add', N'POST', N'/api/Add', N'{"a":"number","b":"number"}', N'{"result":"number"}');
|
||||
SET IDENTITY_INSERT [ExternalSystemMethods] OFF;
|
||||
|
||||
COMMIT;
|
||||
@@ -170,6 +170,158 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Folder": "DevAppEngine",
|
||||
"NodeList": [],
|
||||
"FolderList": [
|
||||
{
|
||||
"Folder": "Scheduler",
|
||||
"NodeList": [
|
||||
{
|
||||
"NodeId": "DevAppEngine.Scheduler.ScanTime",
|
||||
"Name": "ScanTime",
|
||||
"DataType": "DateTime",
|
||||
"ValueRank": -1,
|
||||
"AccessLevel": "CurrentReadOrWrite",
|
||||
"Description": "Current scan time for DevAppEngine"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Folder": "Sensor",
|
||||
"NodeList": [
|
||||
{
|
||||
"NodeId": "Sensor.Reading",
|
||||
"Name": "Reading",
|
||||
"DataType": "Double",
|
||||
"ValueRank": -1,
|
||||
"AccessLevel": "CurrentReadOrWrite",
|
||||
"Description": "Generic sensor reading"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Folder": "Misc",
|
||||
"NodeList": [
|
||||
{
|
||||
"NodeId": "Temperature",
|
||||
"Name": "Temperature",
|
||||
"DataType": "Double",
|
||||
"ValueRank": -1,
|
||||
"AccessLevel": "CurrentReadOrWrite",
|
||||
"Description": "Standalone Temperature tag (Base Device default)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Folder": "TestChildObject",
|
||||
"NodeList": [
|
||||
{
|
||||
"NodeId": "TestChildObject.TestBool",
|
||||
"Name": "TestBool",
|
||||
"DataType": "Boolean",
|
||||
"ValueRank": -1,
|
||||
"AccessLevel": "CurrentReadOrWrite",
|
||||
"Description": "Test scalar Boolean"
|
||||
},
|
||||
{
|
||||
"NodeId": "TestChildObject.TestBoolArray",
|
||||
"Name": "TestBoolArray",
|
||||
"DataType": "Boolean",
|
||||
"ValueRank": 1,
|
||||
"ArrayDimensions": [4],
|
||||
"AccessLevel": "CurrentReadOrWrite",
|
||||
"Description": "Test Boolean array"
|
||||
},
|
||||
{
|
||||
"NodeId": "TestChildObject.TestDateTime",
|
||||
"Name": "TestDateTime",
|
||||
"DataType": "DateTime",
|
||||
"ValueRank": -1,
|
||||
"AccessLevel": "CurrentReadOrWrite",
|
||||
"Description": "Test scalar DateTime"
|
||||
},
|
||||
{
|
||||
"NodeId": "TestChildObject.TestDateTimeArray",
|
||||
"Name": "TestDateTimeArray",
|
||||
"DataType": "DateTime",
|
||||
"ValueRank": 1,
|
||||
"ArrayDimensions": [4],
|
||||
"AccessLevel": "CurrentReadOrWrite",
|
||||
"Description": "Test DateTime array"
|
||||
},
|
||||
{
|
||||
"NodeId": "TestChildObject.TestDouble",
|
||||
"Name": "TestDouble",
|
||||
"DataType": "Double",
|
||||
"ValueRank": -1,
|
||||
"AccessLevel": "CurrentReadOrWrite",
|
||||
"Description": "Test scalar Double"
|
||||
},
|
||||
{
|
||||
"NodeId": "TestChildObject.TestDoubleArray",
|
||||
"Name": "TestDoubleArray",
|
||||
"DataType": "Double",
|
||||
"ValueRank": 1,
|
||||
"ArrayDimensions": [4],
|
||||
"AccessLevel": "CurrentReadOrWrite",
|
||||
"Description": "Test Double array"
|
||||
},
|
||||
{
|
||||
"NodeId": "TestChildObject.TestFloat",
|
||||
"Name": "TestFloat",
|
||||
"DataType": "Float",
|
||||
"ValueRank": -1,
|
||||
"AccessLevel": "CurrentReadOrWrite",
|
||||
"Description": "Test scalar Float"
|
||||
},
|
||||
{
|
||||
"NodeId": "TestChildObject.TestFloatArray",
|
||||
"Name": "TestFloatArray",
|
||||
"DataType": "Float",
|
||||
"ValueRank": 1,
|
||||
"ArrayDimensions": [4],
|
||||
"AccessLevel": "CurrentReadOrWrite",
|
||||
"Description": "Test Float array"
|
||||
},
|
||||
{
|
||||
"NodeId": "TestChildObject.TestInt",
|
||||
"Name": "TestInt",
|
||||
"DataType": "Int32",
|
||||
"ValueRank": -1,
|
||||
"AccessLevel": "CurrentReadOrWrite",
|
||||
"Description": "Test scalar Int32"
|
||||
},
|
||||
{
|
||||
"NodeId": "TestChildObject.TestIntArray",
|
||||
"Name": "TestIntArray",
|
||||
"DataType": "Int32",
|
||||
"ValueRank": 1,
|
||||
"ArrayDimensions": [4],
|
||||
"AccessLevel": "CurrentReadOrWrite",
|
||||
"Description": "Test Int32 array"
|
||||
},
|
||||
{
|
||||
"NodeId": "TestChildObject.TestString",
|
||||
"Name": "TestString",
|
||||
"DataType": "String",
|
||||
"ValueRank": -1,
|
||||
"AccessLevel": "CurrentReadOrWrite",
|
||||
"Description": "Test scalar String"
|
||||
},
|
||||
{
|
||||
"NodeId": "TestChildObject.TestStringArray",
|
||||
"Name": "TestStringArray",
|
||||
"DataType": "String",
|
||||
"ValueRank": 1,
|
||||
"ArrayDimensions": [4],
|
||||
"AccessLevel": "CurrentReadOrWrite",
|
||||
"Description": "Test String array"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
124
infra/reseed.sh
Executable file
124
infra/reseed.sh
Executable file
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env bash
|
||||
# Full reseed of the ScadaLink test cluster.
|
||||
#
|
||||
# Tears down infra + app containers, drops the MSSQL volume, brings
|
||||
# everything back, lets EF Core migrations create the schema, replays
|
||||
# infra/mssql/seed-config.sql for templates/scripts/data-connections, and
|
||||
# re-seeds sites via docker/seed-sites.sh.
|
||||
#
|
||||
# Usage:
|
||||
# infra/reseed.sh Full reseed (default seed file)
|
||||
# infra/reseed.sh --seed PATH Replay a different seed SQL
|
||||
# infra/reseed.sh --skip-teardown Replay seed against running stack
|
||||
#
|
||||
# Prerequisites:
|
||||
# - Docker / OrbStack running
|
||||
# - Python 3 with pymssql (used by infra/tools/mssql_tool.py + dump_seed.py)
|
||||
# - Built scadalink:latest image (docker/build.sh — deploy.sh runs it)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
SEED_FILE="$SCRIPT_DIR/mssql/seed-config.sql"
|
||||
SKIP_TEARDOWN=false
|
||||
MGMT_URL="http://localhost:9000"
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--seed)
|
||||
SEED_FILE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--skip-teardown)
|
||||
SKIP_TEARDOWN=true
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
sed -n '2,16p' "$0" | sed 's/^# \{0,1\}//'
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ ! -f "$SEED_FILE" ]; then
|
||||
echo "Seed file not found: $SEED_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== ScadaLink Reseed ==="
|
||||
echo "Seed file: $SEED_FILE"
|
||||
echo ""
|
||||
|
||||
if ! $SKIP_TEARDOWN; then
|
||||
echo "--- Stage 1/6: tear down application containers ---"
|
||||
"$PROJECT_ROOT/docker/teardown.sh"
|
||||
|
||||
echo ""
|
||||
echo "--- Stage 2/6: wipe site SQLite state ---"
|
||||
shopt -s nullglob
|
||||
for d in "$PROJECT_ROOT"/docker/site-*/data; do
|
||||
rm -rf "$d"/*
|
||||
echo " cleared $d"
|
||||
done
|
||||
shopt -u nullglob
|
||||
|
||||
echo ""
|
||||
echo "--- Stage 3/6: tear down infra (drops MSSQL volume) ---"
|
||||
(cd "$SCRIPT_DIR" && docker compose down -v)
|
||||
|
||||
echo ""
|
||||
echo "--- Stage 4/6: bring infra back up ---"
|
||||
(cd "$SCRIPT_DIR" && docker compose up -d)
|
||||
|
||||
echo " Waiting for MSSQL to accept connections..."
|
||||
until docker exec scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \
|
||||
-S localhost -U sa -P 'ScadaLink_Dev1#' -C -Q "SELECT 1" >/dev/null 2>&1; do
|
||||
sleep 2
|
||||
done
|
||||
echo " MSSQL ready."
|
||||
|
||||
echo " Waiting for setup.sql to create ScadaLinkConfig..."
|
||||
until docker exec scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \
|
||||
-S localhost -U sa -P 'ScadaLink_Dev1#' -C \
|
||||
-Q "IF DB_ID('ScadaLinkConfig') IS NULL THROW 50000, 'not ready', 1;" \
|
||||
>/dev/null 2>&1; do
|
||||
sleep 2
|
||||
done
|
||||
echo " ScadaLinkConfig present."
|
||||
|
||||
echo ""
|
||||
echo "--- Stage 5/6: deploy central + site nodes ---"
|
||||
"$PROJECT_ROOT/docker/deploy.sh"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "--- Stage 6a/6: wait for central cluster /health/ready ---"
|
||||
until curl -fs "$MGMT_URL/health/ready" >/dev/null 2>&1; do
|
||||
sleep 2
|
||||
done
|
||||
echo " Central cluster ready (EF Core migrations applied)."
|
||||
|
||||
echo ""
|
||||
echo "--- Stage 6b/6: seed sites (CLI) ---"
|
||||
# Sites must exist before the design seed: DataConnections.SiteId FKs to Sites.
|
||||
"$PROJECT_ROOT/docker/seed-sites.sh"
|
||||
|
||||
echo ""
|
||||
echo "--- Stage 6c/6: replay seed SQL ---"
|
||||
docker exec -i scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \
|
||||
-S localhost -U sa -P 'ScadaLink_Dev1#' -C -d ScadaLinkConfig -b < "$SEED_FILE"
|
||||
echo " Seed replayed."
|
||||
|
||||
echo ""
|
||||
echo "=== Reseed complete ==="
|
||||
echo ""
|
||||
echo "Verify:"
|
||||
echo " $PROJECT_ROOT/src/ScadaLink.CLI/bin/Debug/net*/ScadaLink.CLI --url $MGMT_URL --username multi-role --password password template list"
|
||||
echo ""
|
||||
echo "To refresh the seed file from the current DB state:"
|
||||
echo " python3 $SCRIPT_DIR/tools/dump_seed.py --output $SEED_FILE"
|
||||
@@ -1,6 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
# Tear down ScadaLink test infrastructure.
|
||||
#
|
||||
# Drops the MSSQL data volume by default, so the ScadaLinkConfig DB
|
||||
# (templates, scripts, data connections, etc.) is wiped. Use
|
||||
# infra/reseed.sh afterwards to restore the design state from
|
||||
# infra/mssql/seed-config.sql.
|
||||
#
|
||||
# Usage:
|
||||
# ./teardown.sh Stop containers and delete the SQL data volume
|
||||
# ./teardown.sh --images Also remove downloaded Docker images
|
||||
@@ -44,4 +49,9 @@ fi
|
||||
|
||||
echo ""
|
||||
echo "Teardown complete."
|
||||
echo "To start fresh: docker compose up -d && python tools/mssql_tool.py setup --script mssql/setup.sql"
|
||||
echo ""
|
||||
echo "To restore the full test cluster (infra + app + design seed + sites):"
|
||||
echo " infra/reseed.sh"
|
||||
echo ""
|
||||
echo "To start only infra (no app, no seed):"
|
||||
echo " cd infra && docker compose up -d"
|
||||
|
||||
220
infra/tools/dump_seed.py
Executable file
220
infra/tools/dump_seed.py
Executable file
@@ -0,0 +1,220 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Dump design tables from ScadaLinkConfig to a replayable SQL seed file.
|
||||
|
||||
Usage:
|
||||
python3 infra/tools/dump_seed.py --output infra/mssql/seed-config.sql
|
||||
|
||||
Tables covered (insert order; reverse for delete):
|
||||
TemplateFolders, Templates, TemplateAttributes, TemplateScripts,
|
||||
TemplateAlarms, TemplateCompositions, SharedScripts, DataConnections,
|
||||
ExternalSystemDefinitions, ExternalSystemMethods
|
||||
|
||||
Excluded by design (per-environment, not design-time): Sites (seeded via
|
||||
seed-sites.sh), Instances + InstanceConnectionBindings + InstanceOverrides,
|
||||
NotificationLists/Recipients, SmtpConfigurations, ApiKeys, Areas,
|
||||
SiteScopeRules, LdapGroupMappings, DataProtectionKeys, audit, deployment.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import datetime
|
||||
import sys
|
||||
|
||||
import pymssql
|
||||
|
||||
|
||||
DEFAULT_HOST = "localhost"
|
||||
DEFAULT_PORT = 1433
|
||||
DEFAULT_USER = "sa"
|
||||
DEFAULT_PASSWORD = "ScadaLink_Dev1#"
|
||||
DEFAULT_DATABASE = "ScadaLinkConfig"
|
||||
|
||||
INSERT_ORDER = [
|
||||
"TemplateFolders",
|
||||
"Templates",
|
||||
"TemplateAttributes",
|
||||
"TemplateScripts",
|
||||
"TemplateAlarms",
|
||||
"TemplateCompositions",
|
||||
"SharedScripts",
|
||||
"DataConnections",
|
||||
"ExternalSystemDefinitions",
|
||||
"ExternalSystemMethods",
|
||||
]
|
||||
|
||||
# Identity columns get IDENTITY_INSERT wrapped around inserts and are kept in
|
||||
# the column list. All listed tables happen to use Id as their identity.
|
||||
IDENTITY_TABLES = set(INSERT_ORDER)
|
||||
|
||||
# Templates has self-FK Templates.ParentTemplateId; emit a single batch that
|
||||
# inserts shallow rows first then deeper ones. pymssql returns rows in Id order
|
||||
# from our ORDER BY, which matches insertion order for this schema (parent Id
|
||||
# is always less than child Id in the live data).
|
||||
|
||||
|
||||
def quote(value):
|
||||
if value is None:
|
||||
return "NULL"
|
||||
if isinstance(value, bool):
|
||||
return "1" if value else "0"
|
||||
if isinstance(value, (int, float)):
|
||||
return str(value)
|
||||
if isinstance(value, (bytes, bytearray)):
|
||||
return "0x" + value.hex()
|
||||
if isinstance(value, datetime.datetime):
|
||||
return "'" + value.isoformat(sep=" ", timespec="microseconds") + "'"
|
||||
if isinstance(value, datetime.date):
|
||||
return "'" + value.isoformat() + "'"
|
||||
if isinstance(value, datetime.time):
|
||||
return "'" + value.isoformat(timespec="microseconds") + "'"
|
||||
if isinstance(value, datetime.timedelta):
|
||||
total = value.total_seconds()
|
||||
hours, rem = divmod(int(total), 3600)
|
||||
minutes, seconds = divmod(rem, 60)
|
||||
micros = value.microseconds
|
||||
return "'{:02d}:{:02d}:{:02d}.{:06d}'".format(hours, minutes, seconds, micros)
|
||||
text = str(value).replace("'", "''")
|
||||
return "N'" + text + "'"
|
||||
|
||||
|
||||
def get_columns(cursor, table):
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT COLUMN_NAME
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_NAME = %s
|
||||
ORDER BY ORDINAL_POSITION
|
||||
""",
|
||||
(table,),
|
||||
)
|
||||
return [row[0] for row in cursor.fetchall()]
|
||||
|
||||
|
||||
def dump(args):
|
||||
conn = pymssql.connect(
|
||||
server=args.host,
|
||||
port=args.port,
|
||||
user=args.user,
|
||||
password=args.password,
|
||||
database=args.database,
|
||||
)
|
||||
cursor = conn.cursor()
|
||||
|
||||
out = []
|
||||
out.append("-- ScadaLink design-data seed.")
|
||||
out.append("-- Auto-generated by infra/tools/dump_seed.py against " + args.database + ".")
|
||||
out.append("-- Replays the design-time configuration (templates, scripts,")
|
||||
out.append("-- data connections, external systems). Idempotent: deletes")
|
||||
out.append("-- existing rows in the covered tables before inserting.")
|
||||
out.append("--")
|
||||
out.append("-- Excluded: Sites (seed via docker/seed-sites.sh), Instances,")
|
||||
out.append("-- InstanceConnectionBindings, notifications, SMTP, API keys,")
|
||||
out.append("-- areas, LDAP mappings.")
|
||||
out.append("")
|
||||
out.append("SET NOCOUNT ON;")
|
||||
out.append("SET XACT_ABORT ON;")
|
||||
# sqlcmd defaults QUOTED_IDENTIFIER OFF; EF Core's filtered indexes
|
||||
# and computed columns require ON, so force it here.
|
||||
out.append("SET QUOTED_IDENTIFIER ON;")
|
||||
out.append("BEGIN TRAN;")
|
||||
out.append("")
|
||||
|
||||
# Wipe in reverse FK order. Beyond the design tables themselves, we also
|
||||
# clear instance + deployment rows because they FK to Templates and
|
||||
# DataConnections; without this, an idempotent replay against a populated
|
||||
# DB fails on the FK to DataConnections. On a fresh reseed (after
|
||||
# teardown.sh) these tables are already empty so the DELETEs are no-ops.
|
||||
out.append("-- Wipe existing design + dependent rows so the seed is idempotent.")
|
||||
out.append("-- Order matters: dependents first.")
|
||||
delete_order = [
|
||||
# Dependents on Instances / DataConnections / Sites.
|
||||
"DeployedConfigSnapshots",
|
||||
"DeploymentRecords",
|
||||
"InstanceAlarmOverrides",
|
||||
"InstanceAttributeOverrides",
|
||||
"InstanceConnectionBindings",
|
||||
"Instances",
|
||||
# Design tables themselves.
|
||||
"ExternalSystemMethods",
|
||||
"ExternalSystemDefinitions",
|
||||
"DataConnections",
|
||||
"SharedScripts",
|
||||
"TemplateCompositions",
|
||||
# Alarms reference scripts via OnTriggerScriptId; null it first so we
|
||||
# can delete scripts without FK violations.
|
||||
"UPDATE TemplateAlarms SET OnTriggerScriptId = NULL",
|
||||
"TemplateAlarms",
|
||||
"TemplateScripts",
|
||||
"TemplateAttributes",
|
||||
# Templates is self-referential and references TemplateCompositions
|
||||
# (OwnerCompositionId); null parent links first.
|
||||
"UPDATE Templates SET ParentTemplateId = NULL, OwnerCompositionId = NULL",
|
||||
"Templates",
|
||||
# Folders is self-referential too.
|
||||
"UPDATE TemplateFolders SET ParentFolderId = NULL",
|
||||
"TemplateFolders",
|
||||
]
|
||||
for step in delete_order:
|
||||
if step.startswith("UPDATE "):
|
||||
out.append(step + ";")
|
||||
else:
|
||||
out.append("DELETE FROM " + step + ";")
|
||||
out.append("")
|
||||
|
||||
for table in INSERT_ORDER:
|
||||
columns = get_columns(cursor, table)
|
||||
if not columns:
|
||||
print("Skipping {} (no columns found)".format(table), file=sys.stderr)
|
||||
continue
|
||||
|
||||
# Order by Id so self-referential rows insert in dependency order
|
||||
# (in the live data, parent Id < child Id by construction).
|
||||
order_clause = "ORDER BY Id" if "Id" in columns else ""
|
||||
cursor.execute(
|
||||
"SELECT [{}] FROM [{}] {}".format("], [".join(columns), table, order_clause)
|
||||
)
|
||||
rows = cursor.fetchall()
|
||||
|
||||
out.append("-- " + table + " (" + str(len(rows)) + " rows)")
|
||||
if not rows:
|
||||
continue
|
||||
|
||||
col_list = ", ".join("[" + c + "]" for c in columns)
|
||||
identity = table in IDENTITY_TABLES
|
||||
if identity:
|
||||
out.append("SET IDENTITY_INSERT [{}] ON;".format(table))
|
||||
for row in rows:
|
||||
values = ", ".join(quote(v) for v in row)
|
||||
out.append(
|
||||
"INSERT INTO [{}] ({}) VALUES ({});".format(table, col_list, values)
|
||||
)
|
||||
if identity:
|
||||
out.append("SET IDENTITY_INSERT [{}] OFF;".format(table))
|
||||
out.append("")
|
||||
|
||||
out.append("COMMIT;")
|
||||
out.append("")
|
||||
|
||||
sql = "\n".join(out)
|
||||
with open(args.output, "w") as f:
|
||||
f.write(sql)
|
||||
|
||||
print("Wrote " + args.output + " (" + str(sum(1 for line in out if line.startswith('INSERT'))) + " inserts).")
|
||||
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--host", default=DEFAULT_HOST)
|
||||
parser.add_argument("--port", type=int, default=DEFAULT_PORT)
|
||||
parser.add_argument("--user", default=DEFAULT_USER)
|
||||
parser.add_argument("--password", default=DEFAULT_PASSWORD)
|
||||
parser.add_argument("--database", default=DEFAULT_DATABASE)
|
||||
parser.add_argument("--output", required=True, help="Path to write seed SQL")
|
||||
args = parser.parse_args()
|
||||
dump(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user