refactor: rename ScadaLink → ZB.MOM.WW.ScadaBridge (code + projects + namespaces)
Solution + 23 src projects + 26 test projects renamed; folders, csproj, namespaces, and ScadaLinkDbContext/ScadaBridgeDbContext class updated. ActorSystem "scadalink" → "scadabridge", Akka seed-node URLs migrated. SQL roles/logins, LDAP domains, CLI command name, and CLI config dir (~/.scadalink → ~/.scadabridge) also renamed. Build green; 5 Host.Tests fail awaiting SQL login rename in next commit. Pre-existing StaleTagMonitor timing flakes unchanged. Rename script committed at tools/rename-to-scadabridge.sh.
This commit is contained in:
@@ -328,9 +328,9 @@ MS SQL for direct-write events). Unredacted secrets never persist.
|
||||
## Security & Tamper-Evidence
|
||||
|
||||
- **Append-only enforcement.** The application accesses `AuditLog` via a
|
||||
dedicated DB role `scadalink_audit_writer` granted `INSERT` + `SELECT` only —
|
||||
dedicated DB role `scadabridge_audit_writer` granted `INSERT` + `SELECT` only —
|
||||
no `UPDATE`, no `DELETE`. Purge runs under a separate role
|
||||
`scadalink_audit_purger` whose permissions are limited to the partition-switch
|
||||
`scadabridge_audit_purger` whose permissions are limited to the partition-switch
|
||||
operation; row-level `DELETE` is not granted even to purge.
|
||||
- **CI grep guard.** The build greps the data layer for any
|
||||
`UPDATE … AuditLog` or `DELETE … AuditLog` text and fails on a hit.
|
||||
@@ -342,9 +342,9 @@ MS SQL for direct-write events). Unredacted secrets never persist.
|
||||
secrets never persist; the safety net over-redacts on misconfiguration.
|
||||
- **Hash-chain tamper evidence — deferred to v1.x.** A future `RowHash` column,
|
||||
computed per partition as `SHA-256(prev.RowHash || canonical(row))`, will be
|
||||
verifiable offline via `scadalink audit verify-chain --month YYYY-MM`. Off by
|
||||
verifiable offline via `scadabridge audit verify-chain --month YYYY-MM`. Off by
|
||||
default in v1.
|
||||
- **Site SQLite security.** File permissions: read/write by the ScadaLink
|
||||
- **Site SQLite security.** File permissions: read/write by the ScadaBridge
|
||||
service account only. Not backed up off-machine — site SQLite is a buffer,
|
||||
not a record.
|
||||
|
||||
@@ -393,7 +393,7 @@ global value in v1; per-channel overrides are deferred to v1.x.
|
||||
`AuditStatus` enum types live here.
|
||||
- **[Configuration Database (#17)](Component-ConfigurationDatabase.md)** — hosts
|
||||
the `AuditLog` table schema, the monthly partition function and scheme, the
|
||||
`scadalink_audit_writer` / `scadalink_audit_purger` DB roles, and the EF
|
||||
`scadabridge_audit_writer` / `scadabridge_audit_purger` DB roles, and the EF
|
||||
migration. Distinct concern from `IAuditService` (config-change audit), which
|
||||
is unchanged.
|
||||
- **[Cluster Infrastructure (#13)](Component-ClusterInfrastructure.md)** —
|
||||
@@ -442,6 +442,6 @@ global value in v1; per-channel overrides are deferred to v1.x.
|
||||
tiles (Volume, Error rate, Backlog) plus new health metrics:
|
||||
`SiteAuditBacklog`, `SiteAuditWriteFailures`, `SiteAuditTelemetryStalled`,
|
||||
`CentralAuditWriteFailures`, `AuditRedactionFailure`.
|
||||
- **[CLI (#19)](Component-CLI.md)** — new `scadalink audit query`,
|
||||
`scadalink audit export`, and `scadalink audit verify-chain` commands; same
|
||||
- **[CLI (#19)](Component-CLI.md)** — new `scadabridge audit query`,
|
||||
`scadabridge audit export`, and `scadabridge audit verify-chain` commands; same
|
||||
permission requirements as the UI.
|
||||
|
||||
+111
-111
@@ -2,13 +2,13 @@
|
||||
|
||||
## Purpose
|
||||
|
||||
The CLI is a standalone command-line tool for scripting and automating administrative operations against the ScadaLink central cluster. It connects to the Central Host's HTTP Management API (`POST /management`), which dispatches commands to the ManagementActor. Authentication and role resolution are handled server-side — the CLI sends credentials via HTTP Basic Auth. The CLI provides the same administrative capabilities as the Central UI, enabling automation, batch operations, and integration with CI/CD pipelines.
|
||||
The CLI is a standalone command-line tool for scripting and automating administrative operations against the ScadaBridge central cluster. It connects to the Central Host's HTTP Management API (`POST /management`), which dispatches commands to the ManagementActor. Authentication and role resolution are handled server-side — the CLI sends credentials via HTTP Basic Auth. The CLI provides the same administrative capabilities as the Central UI, enabling automation, batch operations, and integration with CI/CD pipelines.
|
||||
|
||||
## Location
|
||||
|
||||
Standalone executable, not part of the Host binary. Deployed on any machine with HTTP access to a central node.
|
||||
|
||||
`src/ScadaLink.CLI/`
|
||||
`src/ZB.MOM.WW.ScadaBridge.CLI/`
|
||||
|
||||
## Responsibilities
|
||||
|
||||
@@ -45,52 +45,52 @@ The CLI connects to the Central Host via HTTP:
|
||||
The CLI uses a hierarchical subcommand structure mirroring the Management Service message groups:
|
||||
|
||||
```
|
||||
scadalink <group> <action> [options]
|
||||
scadabridge <group> <action> [options]
|
||||
```
|
||||
|
||||
All entities are identified by their integer **ID** (via `--id`, `--template-id`,
|
||||
`--site-id`, etc.), not by name. Create/update commands take individual flags — there
|
||||
is no `--file` option. The authoritative, always-current reference is the in-repo
|
||||
`src/ScadaLink.CLI/README.md`; the command lists below mirror the implemented command
|
||||
`src/ZB.MOM.WW.ScadaBridge.CLI/README.md`; the command lists below mirror the implemented command
|
||||
tree at the time of writing.
|
||||
|
||||
### Template Commands
|
||||
```
|
||||
scadalink template list
|
||||
scadalink template get --id <id>
|
||||
scadalink template create --name <name> [--description <desc>] [--parent-id <id>]
|
||||
scadalink template update --id <id> [--name <name>] [--description <desc>] [--parent-id <id>]
|
||||
scadalink template validate --id <id>
|
||||
scadalink template delete --id <id>
|
||||
scadalink template attribute add --template-id <id> --name <name> --data-type <type> [--value <value>] [--description <desc>] [--data-source <ref>] [--locked <bool>]
|
||||
scadalink template attribute update --id <id> [--name <name>] [--data-type <type>] [--value <value>] [--description <desc>] [--data-source <ref>] [--locked <bool>]
|
||||
scadalink template attribute delete --id <id>
|
||||
scadalink template alarm add --template-id <id> --name <name> --trigger-type <type> --priority <n> [--description <desc>] [--trigger-config <json>] [--locked <bool>]
|
||||
scadalink template alarm update --id <id> [--name <name>] [--trigger-type <type>] [--priority <n>] [--description <desc>] [--trigger-config <json>] [--locked <bool>]
|
||||
scadalink template alarm delete --id <id>
|
||||
scadalink template script add --template-id <id> --name <name> --code <code> --trigger-type <type> [--trigger-config <json>] [--locked <bool>] [--parameters <json>] [--return-def <json>]
|
||||
scadalink template script update --id <id> [--name <name>] [--code <code>] [--trigger-type <type>] [--trigger-config <json>] [--locked <bool>] [--parameters <json>] [--return-def <json>]
|
||||
scadalink template script delete --id <id>
|
||||
scadalink template composition add --template-id <id> --instance-name <name> --composed-template-id <id>
|
||||
scadalink template composition delete --template-id <id> --instance-name <name>
|
||||
scadabridge template list
|
||||
scadabridge template get --id <id>
|
||||
scadabridge template create --name <name> [--description <desc>] [--parent-id <id>]
|
||||
scadabridge template update --id <id> [--name <name>] [--description <desc>] [--parent-id <id>]
|
||||
scadabridge template validate --id <id>
|
||||
scadabridge template delete --id <id>
|
||||
scadabridge template attribute add --template-id <id> --name <name> --data-type <type> [--value <value>] [--description <desc>] [--data-source <ref>] [--locked <bool>]
|
||||
scadabridge template attribute update --id <id> [--name <name>] [--data-type <type>] [--value <value>] [--description <desc>] [--data-source <ref>] [--locked <bool>]
|
||||
scadabridge template attribute delete --id <id>
|
||||
scadabridge template alarm add --template-id <id> --name <name> --trigger-type <type> --priority <n> [--description <desc>] [--trigger-config <json>] [--locked <bool>]
|
||||
scadabridge template alarm update --id <id> [--name <name>] [--trigger-type <type>] [--priority <n>] [--description <desc>] [--trigger-config <json>] [--locked <bool>]
|
||||
scadabridge template alarm delete --id <id>
|
||||
scadabridge template script add --template-id <id> --name <name> --code <code> --trigger-type <type> [--trigger-config <json>] [--locked <bool>] [--parameters <json>] [--return-def <json>]
|
||||
scadabridge template script update --id <id> [--name <name>] [--code <code>] [--trigger-type <type>] [--trigger-config <json>] [--locked <bool>] [--parameters <json>] [--return-def <json>]
|
||||
scadabridge template script delete --id <id>
|
||||
scadabridge template composition add --template-id <id> --instance-name <name> --composed-template-id <id>
|
||||
scadabridge template composition delete --template-id <id> --instance-name <name>
|
||||
```
|
||||
|
||||
### Instance Commands
|
||||
```
|
||||
scadalink instance list [--site-id <id>] [--template-id <id>] [--search <term>]
|
||||
scadalink instance get --id <id>
|
||||
scadalink instance create --name <name> --template-id <id> --site-id <id> [--area-id <id>]
|
||||
scadalink instance set-bindings --id <id> --bindings <json>
|
||||
scadalink instance set-overrides --id <id> --overrides <json>
|
||||
scadalink instance alarm-override set --instance-id <id> --alarm <name> [--trigger-config <json>] [--priority <n>]
|
||||
scadalink instance alarm-override delete --instance-id <id> --alarm <name>
|
||||
scadalink instance alarm-override list --instance-id <id>
|
||||
scadalink instance set-area --id <id> [--area-id <id>]
|
||||
scadalink instance diff --id <id>
|
||||
scadalink instance deploy --id <id>
|
||||
scadalink instance enable --id <id>
|
||||
scadalink instance disable --id <id>
|
||||
scadalink instance delete --id <id>
|
||||
scadabridge instance list [--site-id <id>] [--template-id <id>] [--search <term>]
|
||||
scadabridge instance get --id <id>
|
||||
scadabridge instance create --name <name> --template-id <id> --site-id <id> [--area-id <id>]
|
||||
scadabridge instance set-bindings --id <id> --bindings <json>
|
||||
scadabridge instance set-overrides --id <id> --overrides <json>
|
||||
scadabridge instance alarm-override set --instance-id <id> --alarm <name> [--trigger-config <json>] [--priority <n>]
|
||||
scadabridge instance alarm-override delete --instance-id <id> --alarm <name>
|
||||
scadabridge instance alarm-override list --instance-id <id>
|
||||
scadabridge instance set-area --id <id> [--area-id <id>]
|
||||
scadabridge instance diff --id <id>
|
||||
scadabridge instance deploy --id <id>
|
||||
scadabridge instance enable --id <id>
|
||||
scadabridge instance disable --id <id>
|
||||
scadabridge instance delete --id <id>
|
||||
```
|
||||
|
||||
`--bindings` is a JSON array of `[attributeName, dataConnectionId]` pairs, e.g.
|
||||
@@ -99,86 +99,86 @@ value, e.g. `{"Speed": "100", "Mode": null}`.
|
||||
|
||||
### Site Commands
|
||||
```
|
||||
scadalink site list
|
||||
scadalink site get --id <id>
|
||||
scadalink site create --identifier <id> --name <name> [--description <desc>] [--node-a-address <addr>] [--node-b-address <addr>] [--grpc-node-a-address <addr>] [--grpc-node-b-address <addr>]
|
||||
scadalink site update --id <id> [--name <name>] [--description <desc>] [--node-a-address <addr>] [--node-b-address <addr>] [--grpc-node-a-address <addr>] [--grpc-node-b-address <addr>]
|
||||
scadalink site delete --id <id>
|
||||
scadalink site area list --site-id <id>
|
||||
scadalink site area create --site-id <id> --name <name> [--parent-id <id>]
|
||||
scadalink site area update --id <id> --name <name>
|
||||
scadalink site area delete --id <id>
|
||||
scadalink site deploy-artifacts [--site-id <id>]
|
||||
scadabridge site list
|
||||
scadabridge site get --id <id>
|
||||
scadabridge site create --identifier <id> --name <name> [--description <desc>] [--node-a-address <addr>] [--node-b-address <addr>] [--grpc-node-a-address <addr>] [--grpc-node-b-address <addr>]
|
||||
scadabridge site update --id <id> [--name <name>] [--description <desc>] [--node-a-address <addr>] [--node-b-address <addr>] [--grpc-node-a-address <addr>] [--grpc-node-b-address <addr>]
|
||||
scadabridge site delete --id <id>
|
||||
scadabridge site area list --site-id <id>
|
||||
scadabridge site area create --site-id <id> --name <name> [--parent-id <id>]
|
||||
scadabridge site area update --id <id> --name <name>
|
||||
scadabridge site area delete --id <id>
|
||||
scadabridge site deploy-artifacts [--site-id <id>]
|
||||
```
|
||||
|
||||
### Deployment Commands
|
||||
```
|
||||
scadalink deploy instance --id <id>
|
||||
scadalink deploy artifacts [--site-id <id>]
|
||||
scadalink deploy status [--instance-id <id>] [--status <status>] [--page <n>] [--page-size <n>]
|
||||
scadabridge deploy instance --id <id>
|
||||
scadabridge deploy artifacts [--site-id <id>]
|
||||
scadabridge deploy status [--instance-id <id>] [--status <status>] [--page <n>] [--page-size <n>]
|
||||
```
|
||||
|
||||
### Data Connection Commands
|
||||
```
|
||||
scadalink data-connection list [--site-id <id>]
|
||||
scadalink data-connection get --id <id>
|
||||
scadalink data-connection create --site-id <id> --name <name> --protocol <protocol> [--backup-config <json>] [--failover-retry-count <n>]
|
||||
scadalink data-connection update --id <id> [--name <name>] [--protocol <protocol>] [--backup-config <json>] [--failover-retry-count <n>]
|
||||
scadalink data-connection delete --id <id>
|
||||
scadabridge data-connection list [--site-id <id>]
|
||||
scadabridge data-connection get --id <id>
|
||||
scadabridge data-connection create --site-id <id> --name <name> --protocol <protocol> [--backup-config <json>] [--failover-retry-count <n>]
|
||||
scadabridge data-connection update --id <id> [--name <name>] [--protocol <protocol>] [--backup-config <json>] [--failover-retry-count <n>]
|
||||
scadabridge data-connection delete --id <id>
|
||||
```
|
||||
|
||||
### External System Commands
|
||||
```
|
||||
scadalink external-system list
|
||||
scadalink external-system get --id <id>
|
||||
scadalink external-system create --name <name> --endpoint-url <url> --auth-type <type> [--auth-config <json>]
|
||||
scadalink external-system update --id <id> [--name <name>] [--endpoint-url <url>] [--auth-type <type>] [--auth-config <json>]
|
||||
scadalink external-system delete --id <id>
|
||||
scadalink external-system method list --external-system-id <id>
|
||||
scadalink external-system method get --id <id>
|
||||
scadalink external-system method create --external-system-id <id> --name <name> --http-method <verb> --path <path> [--params <json>] [--return <json>]
|
||||
scadalink external-system method update --id <id> [--name <name>] [--http-method <verb>] [--path <path>] [--params <json>] [--return <json>]
|
||||
scadalink external-system method delete --id <id>
|
||||
scadabridge external-system list
|
||||
scadabridge external-system get --id <id>
|
||||
scadabridge external-system create --name <name> --endpoint-url <url> --auth-type <type> [--auth-config <json>]
|
||||
scadabridge external-system update --id <id> [--name <name>] [--endpoint-url <url>] [--auth-type <type>] [--auth-config <json>]
|
||||
scadabridge external-system delete --id <id>
|
||||
scadabridge external-system method list --external-system-id <id>
|
||||
scadabridge external-system method get --id <id>
|
||||
scadabridge external-system method create --external-system-id <id> --name <name> --http-method <verb> --path <path> [--params <json>] [--return <json>]
|
||||
scadabridge external-system method update --id <id> [--name <name>] [--http-method <verb>] [--path <path>] [--params <json>] [--return <json>]
|
||||
scadabridge external-system method delete --id <id>
|
||||
```
|
||||
|
||||
### Notification Commands
|
||||
```
|
||||
scadalink notification list
|
||||
scadalink notification get --id <id>
|
||||
scadalink notification create --name <name> --emails <comma-separated>
|
||||
scadalink notification update --id <id> [--name <name>] [--emails <comma-separated>]
|
||||
scadalink notification delete --id <id>
|
||||
scadalink notification smtp list
|
||||
scadalink notification smtp update --id <id> --server <host> --port <n> --auth-mode <mode> --from-address <email>
|
||||
scadabridge notification list
|
||||
scadabridge notification get --id <id>
|
||||
scadabridge notification create --name <name> --emails <comma-separated>
|
||||
scadabridge notification update --id <id> [--name <name>] [--emails <comma-separated>]
|
||||
scadabridge notification delete --id <id>
|
||||
scadabridge notification smtp list
|
||||
scadabridge notification smtp update --id <id> --server <host> --port <n> --auth-mode <mode> --from-address <email>
|
||||
```
|
||||
|
||||
### Security Commands
|
||||
```
|
||||
scadalink security api-key list
|
||||
scadalink security api-key create --name <name>
|
||||
scadalink security api-key update --id <id> --enabled <bool>
|
||||
scadalink security api-key delete --id <id>
|
||||
scadalink security role-mapping list
|
||||
scadalink security role-mapping create --ldap-group <group> --role <role>
|
||||
scadalink security role-mapping update --id <id> [--ldap-group <group>] [--role <role>]
|
||||
scadalink security role-mapping delete --id <id>
|
||||
scadalink security scope-rule list [--mapping-id <id>]
|
||||
scadalink security scope-rule add --mapping-id <id> --site-id <id>
|
||||
scadalink security scope-rule delete --id <id>
|
||||
scadabridge security api-key list
|
||||
scadabridge security api-key create --name <name>
|
||||
scadabridge security api-key update --id <id> --enabled <bool>
|
||||
scadabridge security api-key delete --id <id>
|
||||
scadabridge security role-mapping list
|
||||
scadabridge security role-mapping create --ldap-group <group> --role <role>
|
||||
scadabridge security role-mapping update --id <id> [--ldap-group <group>] [--role <role>]
|
||||
scadabridge security role-mapping delete --id <id>
|
||||
scadabridge security scope-rule list [--mapping-id <id>]
|
||||
scadabridge security scope-rule add --mapping-id <id> --site-id <id>
|
||||
scadabridge security scope-rule delete --id <id>
|
||||
```
|
||||
|
||||
### Audit Log Commands
|
||||
```
|
||||
scadalink audit-log query [--user <username>] [--entity-type <type>] [--action <action>] [--from <date>] [--to <date>] [--page <n>] [--page-size <n>]
|
||||
scadabridge audit-log query [--user <username>] [--entity-type <type>] [--action <action>] [--from <date>] [--to <date>] [--page <n>] [--page-size <n>]
|
||||
```
|
||||
|
||||
The legacy `audit-log query` above targets the original configuration-change audit
|
||||
(IAuditService) surface. The new centralized Audit Log component (#23) is exposed via
|
||||
the `scadalink audit` group below.
|
||||
the `scadabridge audit` group below.
|
||||
|
||||
### Centralized Audit Commands
|
||||
|
||||
The `scadalink audit` group targets the centralized Audit Log component (#23) and
|
||||
The `scadabridge audit` group targets the centralized Audit Log component (#23) and
|
||||
exposes the UI-equivalent operational audit surface. Permissions follow the same
|
||||
read-vs-export split the Central UI uses (see Component-AuditLog.md, Security &
|
||||
Tamper-Evidence, and Security & Auth #10): `audit query` and `audit verify-chain`
|
||||
@@ -187,9 +187,9 @@ require the `OperationalAudit` permission; `audit export` additionally requires
|
||||
exit code 2) on denial.
|
||||
|
||||
```
|
||||
scadalink audit query --since <t> [--until <t>] [--channel <c>] [--kind <k>] [--status <s>] [--site <s>] [--instance <i>] [--target <t>] [--actor <a>] [--correlation-id <id>] [--errors-only] [--page <n>] [--page-size <n>]
|
||||
scadalink audit export --since <t> --until <t> --format csv|jsonl|parquet --output <path> [--channel <c>] [--kind <k>] [--status <s>] [--site <s>] [--target <t>] [--actor <a>]
|
||||
scadalink audit verify-chain --month <YYYY-MM>
|
||||
scadabridge audit query --since <t> [--until <t>] [--channel <c>] [--kind <k>] [--status <s>] [--site <s>] [--instance <i>] [--target <t>] [--actor <a>] [--correlation-id <id>] [--errors-only] [--page <n>] [--page-size <n>]
|
||||
scadabridge audit export --since <t> --until <t> --format csv|jsonl|parquet --output <path> [--channel <c>] [--kind <k>] [--status <s>] [--site <s>] [--target <t>] [--actor <a>]
|
||||
scadabridge audit verify-chain --month <YYYY-MM>
|
||||
```
|
||||
|
||||
- `audit query` — filtered query against the central `AuditLog` table, matching the
|
||||
@@ -208,16 +208,16 @@ scadalink audit verify-chain --month <YYYY-MM>
|
||||
|
||||
### Health Commands
|
||||
```
|
||||
scadalink health summary
|
||||
scadalink health site --identifier <site-identifier>
|
||||
scadalink health event-log --site <site-identifier> [--event-type <type>] [--severity <level>] [--keyword <term>] [--from <date>] [--to <date>] [--page <n>] [--page-size <n>] [--instance-name <name>]
|
||||
scadalink health parked-messages --site <site-identifier> [--page <n>] [--page-size <n>]
|
||||
scadabridge health summary
|
||||
scadabridge health site --identifier <site-identifier>
|
||||
scadabridge health event-log --site <site-identifier> [--event-type <type>] [--severity <level>] [--keyword <term>] [--from <date>] [--to <date>] [--page <n>] [--page-size <n>] [--instance-name <name>]
|
||||
scadabridge health parked-messages --site <site-identifier> [--page <n>] [--page-size <n>]
|
||||
```
|
||||
|
||||
### Debug Commands
|
||||
```
|
||||
scadalink debug snapshot --id <id>
|
||||
scadalink debug stream --id <id>
|
||||
scadabridge debug snapshot --id <id>
|
||||
scadabridge debug stream --id <id>
|
||||
```
|
||||
|
||||
The `debug snapshot` command retrieves a point-in-time snapshot via the HTTP Management API.
|
||||
@@ -234,29 +234,29 @@ Unlike `debug snapshot` (which uses the HTTP Management API), `debug stream` use
|
||||
|
||||
### Shared Script Commands
|
||||
```
|
||||
scadalink shared-script list
|
||||
scadalink shared-script get --id <id>
|
||||
scadalink shared-script create --name <name> --code <code> [--parameters <json>] [--return-def <json>]
|
||||
scadalink shared-script update --id <id> [--name <name>] [--code <code>] [--parameters <json>] [--return-def <json>]
|
||||
scadalink shared-script delete --id <id>
|
||||
scadabridge shared-script list
|
||||
scadabridge shared-script get --id <id>
|
||||
scadabridge shared-script create --name <name> --code <code> [--parameters <json>] [--return-def <json>]
|
||||
scadabridge shared-script update --id <id> [--name <name>] [--code <code>] [--parameters <json>] [--return-def <json>]
|
||||
scadabridge shared-script delete --id <id>
|
||||
```
|
||||
|
||||
### Database Connection Commands
|
||||
```
|
||||
scadalink db-connection list
|
||||
scadalink db-connection get --id <id>
|
||||
scadalink db-connection create --name <name> --connection-string <string>
|
||||
scadalink db-connection update --id <id> [--name <name>] [--connection-string <string>]
|
||||
scadalink db-connection delete --id <id>
|
||||
scadabridge db-connection list
|
||||
scadabridge db-connection get --id <id>
|
||||
scadabridge db-connection create --name <name> --connection-string <string>
|
||||
scadabridge db-connection update --id <id> [--name <name>] [--connection-string <string>]
|
||||
scadabridge db-connection delete --id <id>
|
||||
```
|
||||
|
||||
### Inbound API Method Commands
|
||||
```
|
||||
scadalink api-method list
|
||||
scadalink api-method get --id <id>
|
||||
scadalink api-method create --name <name> --script <code> [--timeout <seconds>] [--parameters <json>] [--return-def <json>]
|
||||
scadalink api-method update --id <id> [--script <code>] [--timeout <seconds>] [--parameters <json>] [--return-def <json>]
|
||||
scadalink api-method delete --id <id>
|
||||
scadabridge api-method list
|
||||
scadabridge api-method get --id <id>
|
||||
scadabridge api-method create --name <name> --script <code> [--timeout <seconds>] [--parameters <json>] [--return-def <json>]
|
||||
scadabridge api-method update --id <id> [--script <code>] [--timeout <seconds>] [--parameters <json>] [--return-def <json>]
|
||||
scadabridge api-method delete --id <id>
|
||||
```
|
||||
|
||||
The `--format json|table` option is recursive and accepted on every command above.
|
||||
@@ -272,7 +272,7 @@ Configuration is resolved in the following priority order (highest wins):
|
||||
- `SCADALINK_USERNAME` / `SCADALINK_PASSWORD` — LDAP credentials. Preferred over
|
||||
`--password` on the command line, which is visible in process listings and shell
|
||||
history. Credentials are never read from the config file.
|
||||
3. **Configuration file**: `~/.scadalink/config.json` — Persistent defaults for management URL and output format only (never credentials).
|
||||
3. **Configuration file**: `~/.scadabridge/config.json` — Persistent defaults for management URL and output format only (never credentials).
|
||||
|
||||
### Configuration File Format
|
||||
|
||||
@@ -307,8 +307,8 @@ Configuration is resolved in the following priority order (highest wins):
|
||||
- **Commons**: Message contracts (`Messages/Management/`) for command type definitions and registry.
|
||||
- **System.CommandLine**: Command-line argument parsing.
|
||||
- **Microsoft.AspNetCore.SignalR.Client**: SignalR client for the `debug stream` command's WebSocket connection.
|
||||
- **Management Service (#18)**: The CLI hits the central cluster via the existing HTTP Management API (`POST /management`), which dispatches to the ManagementActor. The `scadalink audit` command group rides a parallel REST surface on the same Host (`GET /api/audit/query` and `GET /api/audit/export`), sharing HTTP Basic Auth with `/management` but bypassing the actor for read-only, keyset-paged / streaming workloads.
|
||||
- **Audit Log (#23)**: The `scadalink audit query` and `audit export` subcommands target the centralized Audit Log component's REST endpoints (`GET /api/audit/query`, `GET /api/audit/export`) on the Host's Management API surface; `audit verify-chain` rides `POST /management` until hash-chain verification ships. Permission checks (`OperationalAudit`, `AuditExport`) are enforced server-side by `AuditEndpoints`.
|
||||
- **Management Service (#18)**: The CLI hits the central cluster via the existing HTTP Management API (`POST /management`), which dispatches to the ManagementActor. The `scadabridge audit` command group rides a parallel REST surface on the same Host (`GET /api/audit/query` and `GET /api/audit/export`), sharing HTTP Basic Auth with `/management` but bypassing the actor for read-only, keyset-paged / streaming workloads.
|
||||
- **Audit Log (#23)**: The `scadabridge audit query` and `audit export` subcommands target the centralized Audit Log component's REST endpoints (`GET /api/audit/query`, `GET /api/audit/export`) on the Host's Management API surface; `audit verify-chain` rides `POST /management` until hash-chain verification ships. Permission checks (`OperationalAudit`, `AuditExport`) are enforced server-side by `AuditEndpoints`.
|
||||
|
||||
## Interactions
|
||||
|
||||
|
||||
@@ -24,10 +24,10 @@ This component is a **design responsibility**, not a single buildable project th
|
||||
contains all of the code. The cluster-infrastructure responsibilities above are
|
||||
realised across two projects:
|
||||
|
||||
- **`src/ScadaLink.ClusterInfrastructure`** owns the cluster **configuration model**:
|
||||
- **`src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure`** owns the cluster **configuration model**:
|
||||
the `ClusterOptions` POCO (seed nodes, roles, remoting/gRPC ports, failure-detection
|
||||
timings, split-brain settings) bound from `appsettings.json` via the Options pattern.
|
||||
- **`src/ScadaLink.Host`** owns the cluster **bootstrap and runtime wiring**: it
|
||||
- **`src/ZB.MOM.WW.ScadaBridge.Host`** owns the cluster **bootstrap and runtime wiring**: it
|
||||
builds the Akka.NET HOCON from `ClusterOptions`, starts the `ActorSystem`,
|
||||
configures the keep-oldest split-brain resolver (`down-if-alone = on`), wires
|
||||
`CoordinatedShutdown` into the service lifecycle, and provides active-node /
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Purpose
|
||||
|
||||
The Commons component provides the shared foundation of data types, interfaces, enums, message contracts, data transfer objects, and persistence-ignorant domain entity classes used across all other ScadaLink components. It ensures consistent type definitions for cross-component communication, data access, and eliminates duplication of common abstractions.
|
||||
The Commons component provides the shared foundation of data types, interfaces, enums, message contracts, data transfer objects, and persistence-ignorant domain entity classes used across all other ScadaBridge components. It ensures consistent type definitions for cross-component communication, data access, and eliminates duplication of common abstractions.
|
||||
|
||||
## Location
|
||||
|
||||
@@ -159,7 +159,7 @@ Since the system supports cross-site artifact version skew (sites may temporaril
|
||||
All types in Commons are organized by **category** and **domain area** using a consistent namespace and folder hierarchy:
|
||||
|
||||
```
|
||||
ScadaLink.Commons/
|
||||
ZB.MOM.WW.ScadaBridge.Commons/
|
||||
├── Types/ # REQ-COM-1: Shared data types
|
||||
│ ├── Result.cs
|
||||
│ ├── RetryPolicy.cs
|
||||
@@ -278,7 +278,7 @@ ScadaLink.Commons/
|
||||
```
|
||||
|
||||
**Naming rules**:
|
||||
- Namespaces mirror the folder structure: `ScadaLink.Commons.Entities.Templates`, `ScadaLink.Commons.Interfaces.Repositories`, etc.
|
||||
- Namespaces mirror the folder structure: `ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates`, `ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories`, etc.
|
||||
- Interface names use the `I` prefix: `ITemplateEngineRepository`, `IAuditService`, `IDataConnection`.
|
||||
- Entity classes are named after the domain concept (no suffixes like `Entity` or `Model`): `Template`, `Instance`, `Site`.
|
||||
- Message contracts are named as commands, events, or responses: `DeployInstanceCommand`, `DeploymentStatusResponse`, `AttributeValueChanged`.
|
||||
|
||||
@@ -80,7 +80,7 @@ Both central and site clusters. Each side has communication actors that handle m
|
||||
|
||||
#### gRPC Proto Definition
|
||||
|
||||
The streaming protocol is defined in `sitestream.proto` (`src/ScadaLink.Communication/Protos/sitestream.proto`):
|
||||
The streaming protocol is defined in `sitestream.proto` (`src/ZB.MOM.WW.ScadaBridge.Communication/Protos/sitestream.proto`):
|
||||
|
||||
- **Service**: `SiteStreamService` with a single RPC `SubscribeInstance(InstanceStreamRequest) returns (stream SiteStreamEvent)`.
|
||||
- **Messages**: `InstanceStreamRequest` (correlation_id, instance_unique_name), `SiteStreamEvent` (correlation_id, oneof event: `AttributeValueUpdate`, `AlarmStateUpdate`).
|
||||
@@ -164,7 +164,7 @@ Site Clusters
|
||||
|
||||
Central discovers site addresses through the **configuration database**, not runtime registration:
|
||||
|
||||
- Each site record in the Sites table includes optional **NodeAAddress** and **NodeBAddress** fields containing base Akka addresses of the site's cluster nodes (e.g., `akka.tcp://scadalink@host:port`), and optional **GrpcNodeAAddress** and **GrpcNodeBAddress** fields containing gRPC endpoints (e.g., `http://host:8083`).
|
||||
- Each site record in the Sites table includes optional **NodeAAddress** and **NodeBAddress** fields containing base Akka addresses of the site's cluster nodes (e.g., `akka.tcp://scadabridge@host:port`), and optional **GrpcNodeAAddress** and **GrpcNodeBAddress** fields containing gRPC endpoints (e.g., `http://host:8083`).
|
||||
- The **CentralCommunicationActor** loads all site addresses from the database at startup and creates one **ClusterClient per site**, configured with both NodeA and NodeB as contact points. The **SiteStreamGrpcClientFactory** uses `GrpcNodeAAddress` / `GrpcNodeBAddress` to create per-site gRPC channels for streaming.
|
||||
- The address cache is **refreshed every 60 seconds** and **on-demand** when site records are added, edited, or deleted via the Central UI or CLI. ClusterClient instances are recreated when contact points change.
|
||||
- When routing a message to a site, central sends via `ClusterClient.Send("/user/site-communication", msg)`. **ClusterClient handles failover between NodeA and NodeB internally** — there is no application-level NodeA preference/NodeB fallback logic.
|
||||
|
||||
@@ -84,7 +84,7 @@ The configuration database stores all central system data, organized by domain a
|
||||
|
||||
### DbContext
|
||||
|
||||
A single `ScadaLinkDbContext` (or a small number of bounded DbContexts if warranted) serves as the EF Core entry point. The DbContext:
|
||||
A single `ScadaBridgeDbContext` (or a small number of bounded DbContexts if warranted) serves as the EF Core entry point. The DbContext:
|
||||
|
||||
- Maps the POCO entity classes defined in Commons to the database using **Fluent API only** — no data annotations on the entity classes.
|
||||
- Configures relationships, indexes, constraints, and value conversions.
|
||||
@@ -234,8 +234,8 @@ Results are returned in reverse chronological order (most recent first) with pag
|
||||
|
||||
The configuration database defines dedicated SQL Server roles for the append-only `AuditLog` table so that the application can never accidentally mutate audit history:
|
||||
|
||||
- **`scadalink_audit_writer`** — the role used by application code that ingests audit events (the `AuditLogIngestActor`, central direct-write paths, and the Notification Outbox dispatcher). Granted `INSERT` and `SELECT` on `AuditLog` only — explicitly **no** `UPDATE` and **no** `DELETE`. Audit ingest is `INSERT … WHERE NOT EXISTS` keyed on `EventId`, which this grant set fully supports.
|
||||
- **`scadalink_audit_purger`** — the role used by the `AuditLogPurgeActor`. Granted only the permissions required to execute the monthly partition-switch operation (switch out a partition to a staging table and drop the staging table). Row-level `DELETE` on `AuditLog` is **not** granted even to the purge role; retention is a partition switch, never a row-by-row delete.
|
||||
- **`scadabridge_audit_writer`** — the role used by application code that ingests audit events (the `AuditLogIngestActor`, central direct-write paths, and the Notification Outbox dispatcher). Granted `INSERT` and `SELECT` on `AuditLog` only — explicitly **no** `UPDATE` and **no** `DELETE`. Audit ingest is `INSERT … WHERE NOT EXISTS` keyed on `EventId`, which this grant set fully supports.
|
||||
- **`scadabridge_audit_purger`** — the role used by the `AuditLogPurgeActor`. Granted only the permissions required to execute the monthly partition-switch operation (switch out a partition to a staging table and drop the staging table). Row-level `DELETE` on `AuditLog` is **not** granted even to the purge role; retention is a partition switch, never a row-by-row delete.
|
||||
|
||||
A CI grep guard fails the build on any occurrence of `UPDATE … AuditLog` or `DELETE … AuditLog` in the data-access layer source, backstopping the DB-grant enforcement at code-review time. See Component-AuditLog.md (Security & Tamper-Evidence) for the full enforcement contract.
|
||||
|
||||
@@ -248,7 +248,7 @@ A CI grep guard fails the build on any occurrence of `UPDATE … AuditLog` or `D
|
||||
- Schema changes are managed via EF Core Migrations (`dotnet ef migrations add`, `dotnet ef migrations script`).
|
||||
- Each migration is a versioned, incremental schema change.
|
||||
- New tables are introduced as their own migration — for example, the `Notifications` table for the Notification Outbox ships as a dedicated EF Core migration that creates the table, its `Type`/`Status` value conversions, and its dispatcher and KPI indexes.
|
||||
- The initial `AuditLog` migration creates the monthly partition function `pf_AuditLog_Month` and partition scheme `ps_AuditLog_Month`, then creates the `AuditLog` table aligned to that scheme on `OccurredAtUtc`, along with the indexes listed under Database Schema. The migration also creates the `scadalink_audit_writer` and `scadalink_audit_purger` DB roles with the grants described in Database Roles. The ongoing **partition-maintenance job** that rolls the scheme forward each month (creating the next month's partition ahead of time) and switches out expired partitions is owned by the **Audit Log component** (`AuditLogPurgeActor` and its monthly roll-forward step), not by the Configuration Database component — this component is responsible only for the initial schema, roles, and any EF migrations against the table going forward.
|
||||
- The initial `AuditLog` migration creates the monthly partition function `pf_AuditLog_Month` and partition scheme `ps_AuditLog_Month`, then creates the `AuditLog` table aligned to that scheme on `OccurredAtUtc`, along with the indexes listed under Database Schema. The migration also creates the `scadabridge_audit_writer` and `scadabridge_audit_purger` DB roles with the grants described in Database Roles. The ongoing **partition-maintenance job** that rolls the scheme forward each month (creating the next month's partition ahead of time) and switches out expired partitions is owned by the **Audit Log component** (`AuditLogPurgeActor` and its monthly roll-forward step), not by the Configuration Database component — this component is responsible only for the initial schema, roles, and any EF migrations against the table going forward.
|
||||
|
||||
### Development Environment
|
||||
- Migrations are **auto-applied** at application startup using `dbContext.Database.MigrateAsync()`.
|
||||
|
||||
@@ -42,7 +42,7 @@ The `Disconnected` event is raised by an adapter when it detects an unexpected c
|
||||
|
||||
All protocols produce the same value tuple consumed by Instance Actors. Before the first value update arrives from the DCL, data-sourced attributes are held at **uncertain** quality by the Instance Actor (see Site Runtime — Initialization):
|
||||
|
||||
| Concept | ScadaLink Design |
|
||||
| Concept | ScadaBridge Design |
|
||||
|---|---|
|
||||
| Value container | `TagValue(Value, Quality, Timestamp)` |
|
||||
| Quality | `QualityCode` enum: Good / Bad / Uncertain |
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Purpose
|
||||
|
||||
The Host component is the single deployable executable for the entire ScadaLink system. The same binary runs on every node — central and site alike. The node's role is determined entirely by configuration (`appsettings.json`), not by which binary is deployed. On central nodes the Host additionally bootstraps ASP.NET Core to serve the Central UI and Inbound API web endpoints.
|
||||
The Host component is the single deployable executable for the entire ScadaBridge system. The same binary runs on every node — central and site alike. The node's role is determined entirely by configuration (`appsettings.json`), not by which binary is deployed. On central nodes the Host additionally bootstraps ASP.NET Core to serve the Central UI and Inbound API web endpoints.
|
||||
|
||||
## Location
|
||||
|
||||
@@ -10,7 +10,7 @@ All nodes (central and site).
|
||||
|
||||
## Responsibilities
|
||||
|
||||
- Serve as the single entry point (`Program.cs`) for the ScadaLink process.
|
||||
- Serve as the single entry point (`Program.cs`) for the ScadaBridge process.
|
||||
- Read and validate node configuration at startup before any actor system is created.
|
||||
- Register the correct set of component services and actors based on the configured node role.
|
||||
- Bootstrap the Akka.NET actor system with Remoting, Clustering, and split-brain resolution via Akka.Hosting.
|
||||
@@ -39,37 +39,37 @@ Components not applicable to the current role must not be registered in the DI c
|
||||
|
||||
### REQ-HOST-3: Configuration Binding
|
||||
|
||||
The Host must bind configuration sections from `appsettings.json` to strongly-typed options classes using the .NET **Options pattern** (`IOptions<T>` / `IOptionsSnapshot<T>`). Each component has its own configuration section under `ScadaLink`, mapped to a dedicated configuration class owned by that component.
|
||||
The Host must bind configuration sections from `appsettings.json` to strongly-typed options classes using the .NET **Options pattern** (`IOptions<T>` / `IOptionsSnapshot<T>`). Each component has its own configuration section under `ScadaBridge`, mapped to a dedicated configuration class owned by that component.
|
||||
|
||||
#### Infrastructure Sections
|
||||
|
||||
| Section | Options Class | Owner | Contents |
|
||||
|---------|--------------|-------|----------|
|
||||
| `ScadaLink:Node` | `NodeOptions` | Host | Role, NodeHostname, SiteId, RemotingPort, GrpcPort (site only, default 8083) |
|
||||
| `ScadaLink:Cluster` | `ClusterOptions` | ClusterInfrastructure | SeedNodes, SplitBrainResolverStrategy, StableAfter, HeartbeatInterval, FailureDetectionThreshold, MinNrOfMembers |
|
||||
| `ScadaLink:Database` | `DatabaseOptions` | Host | Central: ConfigurationDb, MachineDataDb connection strings; Site: SQLite paths |
|
||||
| `ScadaBridge:Node` | `NodeOptions` | Host | Role, NodeHostname, SiteId, RemotingPort, GrpcPort (site only, default 8083) |
|
||||
| `ScadaBridge:Cluster` | `ClusterOptions` | ClusterInfrastructure | SeedNodes, SplitBrainResolverStrategy, StableAfter, HeartbeatInterval, FailureDetectionThreshold, MinNrOfMembers |
|
||||
| `ScadaBridge:Database` | `DatabaseOptions` | Host | Central: ConfigurationDb, MachineDataDb connection strings; Site: SQLite paths |
|
||||
|
||||
#### Per-Component Sections
|
||||
|
||||
| Section | Options Class | Owner | Contents |
|
||||
|---------|--------------|-------|----------|
|
||||
| `ScadaLink:DataConnection` | `DataConnectionOptions` | Data Connection Layer | ReconnectInterval, TagResolutionRetryInterval, WriteTimeout |
|
||||
| `ScadaLink:StoreAndForward` | `StoreAndForwardOptions` | Store-and-Forward | SqliteDbPath, ReplicationEnabled |
|
||||
| `ScadaLink:HealthMonitoring` | `HealthMonitoringOptions` | Health Monitoring | ReportInterval, OfflineTimeout |
|
||||
| `ScadaLink:SiteEventLog` | `SiteEventLogOptions` | Site Event Logging | RetentionDays, MaxStorageMb, PurgeScheduleCron |
|
||||
| `ScadaLink:Communication` | `CommunicationOptions` | Communication | DeploymentTimeout, LifecycleTimeout, QueryTimeout, TransportHeartbeatInterval, TransportFailureThreshold |
|
||||
| `ScadaLink:Security` | `SecurityOptions` | Security & Auth | LdapServer, LdapPort, LdapUseTls, JwtSigningKey, JwtExpiryMinutes, IdleTimeoutMinutes |
|
||||
| `ScadaLink:InboundApi` | `InboundApiOptions` | Inbound API | DefaultMethodTimeout |
|
||||
| `ScadaLink:Notification` | `NotificationOptions` | Notification Service | (SMTP config is stored in the central config DB, not in appsettings) |
|
||||
| `ScadaLink:NotificationOutbox` | `NotificationOutboxOptions` | Notification Outbox | Dispatcher poll interval, stuck-age threshold, retention window (delivery retry settings reuse the central SMTP configuration) |
|
||||
| `ScadaLink:SiteCallAudit` | `SiteCallAuditOptions` | Site Call Audit | Reconciliation pull interval, stuck-age threshold, retention window |
|
||||
| `ScadaLink:ManagementService` | `ManagementServiceOptions` | Management Service | (Reserved for future configuration) |
|
||||
| `ScadaLink:Logging` | `LoggingOptions` | Host | Serilog sink configuration, log level overrides |
|
||||
| `ScadaBridge:DataConnection` | `DataConnectionOptions` | Data Connection Layer | ReconnectInterval, TagResolutionRetryInterval, WriteTimeout |
|
||||
| `ScadaBridge:StoreAndForward` | `StoreAndForwardOptions` | Store-and-Forward | SqliteDbPath, ReplicationEnabled |
|
||||
| `ScadaBridge:HealthMonitoring` | `HealthMonitoringOptions` | Health Monitoring | ReportInterval, OfflineTimeout |
|
||||
| `ScadaBridge:SiteEventLog` | `SiteEventLogOptions` | Site Event Logging | RetentionDays, MaxStorageMb, PurgeScheduleCron |
|
||||
| `ScadaBridge:Communication` | `CommunicationOptions` | Communication | DeploymentTimeout, LifecycleTimeout, QueryTimeout, TransportHeartbeatInterval, TransportFailureThreshold |
|
||||
| `ScadaBridge:Security` | `SecurityOptions` | Security & Auth | LdapServer, LdapPort, LdapUseTls, JwtSigningKey, JwtExpiryMinutes, IdleTimeoutMinutes |
|
||||
| `ScadaBridge:InboundApi` | `InboundApiOptions` | Inbound API | DefaultMethodTimeout |
|
||||
| `ScadaBridge:Notification` | `NotificationOptions` | Notification Service | (SMTP config is stored in the central config DB, not in appsettings) |
|
||||
| `ScadaBridge:NotificationOutbox` | `NotificationOutboxOptions` | Notification Outbox | Dispatcher poll interval, stuck-age threshold, retention window (delivery retry settings reuse the central SMTP configuration) |
|
||||
| `ScadaBridge:SiteCallAudit` | `SiteCallAuditOptions` | Site Call Audit | Reconciliation pull interval, stuck-age threshold, retention window |
|
||||
| `ScadaBridge:ManagementService` | `ManagementServiceOptions` | Management Service | (Reserved for future configuration) |
|
||||
| `ScadaBridge:Logging` | `LoggingOptions` | Host | Serilog sink configuration, log level overrides |
|
||||
|
||||
#### Convention
|
||||
|
||||
- Each component defines its own options class (e.g., `DataConnectionOptions`) in its own project. The class is a plain POCO with properties matching the JSON section keys.
|
||||
- The Host binds each section during startup via `services.Configure<T>(configuration.GetSection("ScadaLink:<ComponentName>"))`.
|
||||
- The Host binds each section during startup via `services.Configure<T>(configuration.GetSection("ScadaBridge:<ComponentName>"))`.
|
||||
- Each component's `AddXxx()` extension method accepts `IServiceCollection` and reads its options via `IOptions<T>` — the component never reads `IConfiguration` directly.
|
||||
- Options classes live in the component project, not in Commons, because they are component-specific configuration — not shared contracts.
|
||||
- Startup validation (REQ-HOST-4) validates all required options before the actor system starts.
|
||||
@@ -111,7 +111,7 @@ The Host must configure the Akka.NET actor system using Akka.Hosting with:
|
||||
- **Split-Brain Resolver**: Configured with the strategy and stable-after duration from `ClusterConfiguration`.
|
||||
- **Actor registration**: Each component's actors registered via its `AddXxxActors()` extension method, conditional on the node's role.
|
||||
|
||||
> **Persistence note.** ScadaLink does not use Akka.Persistence. Durable state
|
||||
> **Persistence note.** ScadaBridge does not use Akka.Persistence. Durable state
|
||||
> (store-and-forward buffers, site event logs, static attribute writes,
|
||||
> deployment records, configuration) is owned by the individual components and
|
||||
> persisted through component-owned stores — SQLite at sites, MS SQL centrally —
|
||||
@@ -198,7 +198,7 @@ The Host's `Program.cs` calls these extension methods; the component libraries o
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **All 19 component libraries**: The Host references every component project to call their extension methods (excludes CLI, which is a separate executable). Audit Log (#23) ships its central+site code in `ScadaLink.AuditLog`; the Host calls `AddAuditLog()` on both roles, M2+ will add `AddAuditLogActors()`.
|
||||
- **All 19 component libraries**: The Host references every component project to call their extension methods (excludes CLI, which is a separate executable). Audit Log (#23) ships its central+site code in `ZB.MOM.WW.ScadaBridge.AuditLog`; the Host calls `AddAuditLog()` on both roles, M2+ will add `AddAuditLogActors()`.
|
||||
- **Akka.Hosting**: For `AddAkka()` and the hosting configuration builder.
|
||||
- **Akka.Remote.Hosting, Akka.Cluster.Hosting**: For Akka subsystem configuration. (No Akka.Persistence plugin — see the Persistence note under REQ-HOST-6.)
|
||||
- **Serilog.AspNetCore**: For structured logging integration.
|
||||
|
||||
@@ -163,7 +163,7 @@ Inbound API scripts **cannot** call shared scripts directly — shared scripts a
|
||||
|
||||
> **No direct database access.** Inbound API scripts are not given a raw database
|
||||
> client. Handing a script a raw `SqlConnection` is in direct tension with the
|
||||
> ScadaLink script trust model (scripts are forbidden `System.IO`, `Process`,
|
||||
> ScadaBridge script trust model (scripts are forbidden `System.IO`, `Process`,
|
||||
> `Threading`, `Reflection`, and raw network access; `ForbiddenApiChecker`
|
||||
> statically enforces this). Scripts interact with the system only through the
|
||||
> curated `Route` and `Parameters` surfaces above. If a method needs data from
|
||||
|
||||
@@ -8,7 +8,7 @@ The Management Service is an Akka.NET actor on the central cluster that provides
|
||||
|
||||
Central cluster only. The ManagementActor runs as a plain actor on **every** central node (not a cluster singleton). Because the actor is completely stateless — it holds no locks and no local state, delegating all work to repositories and services — running on all nodes improves availability without requiring coordination between instances. Either node can serve any request independently.
|
||||
|
||||
`src/ScadaLink.ManagementService/`
|
||||
`src/ZB.MOM.WW.ScadaBridge.ManagementService/`
|
||||
|
||||
## Responsibilities
|
||||
|
||||
@@ -221,7 +221,7 @@ The ManagementActor receives the following services and repositories via DI (inj
|
||||
|
||||
| Section | Options Class | Contents |
|
||||
|---------|--------------|----------|
|
||||
| `ScadaLink:ManagementService` | `ManagementServiceOptions` | `CommandTimeout` (`TimeSpan`, default 30 s) — Ask timeout the HTTP endpoint applies when forwarding to the `ManagementActor`. A non-positive configured value falls back to the 30 s default. |
|
||||
| `ScadaBridge:ManagementService` | `ManagementServiceOptions` | `CommandTimeout` (`TimeSpan`, default 30 s) — Ask timeout the HTTP endpoint applies when forwarding to the `ManagementActor`. A non-positive configured value falls back to the 30 s default. |
|
||||
|
||||
## Dependencies
|
||||
|
||||
|
||||
@@ -121,4 +121,4 @@ Central cluster. Sites do not have user-facing interfaces and do not perform ind
|
||||
- **Deployment Manager**: Deployment role enforcement with site scoping.
|
||||
- **All central components**: Role checks are a cross-cutting concern applied at the API layer.
|
||||
- **Management Service**: The ManagementActor enforces role-based authorization on every incoming command using the authenticated user identity carried in the message envelope. The CLI authenticates users via the same LDAP bind mechanism and passes the user's identity (username, roles, permitted sites) in every request message. The ManagementActor applies the same role and site-scoping rules as the Central UI — no separate authentication path exists on the server side.
|
||||
- **Transport (#24)**: Provides the `RequireDesign` policy (export) and `RequireAdmin` policy (import) enforced at both the Razor page layer and inside the `ScadaLink.Transport` service entrypoints.
|
||||
- **Transport (#24)**: Provides the `RequireDesign` policy (export) and `RequireAdmin` policy (import) enforced at both the Razor page layer and inside the `ZB.MOM.WW.ScadaBridge.Transport` service entrypoints.
|
||||
|
||||
@@ -77,9 +77,9 @@ There is **no maximum buffer size**. Messages accumulate in the buffer until del
|
||||
|
||||
> **StoreAndForward-021:** the operation tracking table is **not** owned by
|
||||
> this component. The `IOperationTrackingStore` interface lives in
|
||||
> `src/ScadaLink.Commons/Interfaces/Services/`, and the SQLite-backed
|
||||
> `src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Services/`, and the SQLite-backed
|
||||
> implementation (`OperationTrackingStore`, alongside `OperationTrackingOptions`)
|
||||
> lives in [`src/ScadaLink.SiteRuntime/Tracking/`](../../src/ScadaLink.SiteRuntime/Tracking/).
|
||||
> lives in [`src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Tracking/`](../../src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Tracking/).
|
||||
> See [`Component-SiteRuntime.md`](Component-SiteRuntime.md) for the table's
|
||||
> semantics, lifecycle, and central-mirror coordination — it is summarised here
|
||||
> only because the S&F retry loop carries the `TrackedOperationId` linking a
|
||||
|
||||
@@ -6,7 +6,7 @@ The Traefik Proxy is a reverse proxy and load balancer that sits in front of the
|
||||
|
||||
## Location
|
||||
|
||||
Runs as a Docker container (`scadalink-traefik`) in the cluster compose stack (`docker/docker-compose.yml`). Not part of the application codebase — it is a third-party infrastructure component with static configuration files.
|
||||
Runs as a Docker container (`scadabridge-traefik`) in the cluster compose stack (`docker/docker-compose.yml`). Not part of the application codebase — it is a third-party infrastructure component with static configuration files.
|
||||
|
||||
`docker/traefik/`
|
||||
|
||||
@@ -91,8 +91,8 @@ http:
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
servers:
|
||||
- url: "http://scadalink-central-a:5000"
|
||||
- url: "http://scadalink-central-b:5000"
|
||||
- url: "http://scadabridge-central-a:5000"
|
||||
- url: "http://scadabridge-central-b:5000"
|
||||
```
|
||||
|
||||
- **Router `central`**: Catches all requests and forwards to the `central` service.
|
||||
@@ -117,9 +117,9 @@ The central nodes expose three health endpoints:
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **Central cluster nodes**: The two backends (`scadalink-central-a`, `scadalink-central-b`) on the `scadalink-net` Docker network.
|
||||
- **ActiveNodeHealthCheck**: Health check implementation in `src/ScadaLink.Host/Health/ActiveNodeHealthCheck.cs` that determines cluster leader status.
|
||||
- **Docker network**: All containers must be on the shared `scadalink-net` bridge network.
|
||||
- **Central cluster nodes**: The two backends (`scadabridge-central-a`, `scadabridge-central-b`) on the `scadabridge-net` Docker network.
|
||||
- **ActiveNodeHealthCheck**: Health check implementation in `src/ZB.MOM.WW.ScadaBridge.Host/Health/ActiveNodeHealthCheck.cs` that determines cluster leader status.
|
||||
- **Docker network**: All containers must be on the shared `scadabridge-net` bridge network.
|
||||
|
||||
## Interactions
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
## Purpose
|
||||
|
||||
The Transport component provides a file-based, encrypted, environment-agnostic way to promote configuration artifacts from one ScadaLink cluster to another through the Central UI. A user with the Design role on the source cluster exports a selected set of templates and supporting artifacts to a `.scadabundle` file. A user with the Admin role on the target cluster uploads the bundle, reviews a diff, resolves conflicts per artifact, and applies it. Import is config-only: it updates the central configuration database; affected instances surface as stale on the existing Deployments page and the user redeploys via the standard flow. Transport does not touch site nodes, does not move runtime state, and does not move site-scoped artifacts.
|
||||
The Transport component provides a file-based, encrypted, environment-agnostic way to promote configuration artifacts from one ScadaBridge cluster to another through the Central UI. A user with the Design role on the source cluster exports a selected set of templates and supporting artifacts to a `.scadabundle` file. A user with the Admin role on the target cluster uploads the bundle, reviews a diff, resolves conflicts per artifact, and applies it. Import is config-only: it updates the central configuration database; affected instances surface as stale on the existing Deployments page and the user redeploys via the standard flow. Transport does not touch site nodes, does not move runtime state, and does not move site-scoped artifacts.
|
||||
|
||||
## Location
|
||||
|
||||
- New project: `src/ScadaLink.Transport/`
|
||||
- New tests: `tests/ScadaLink.Transport.Tests/`, `tests/ScadaLink.Transport.IntegrationTests/`
|
||||
- Central UI pages: `src/ScadaLink.CentralUI/Components/Pages/Design/TransportExport.razor`, `TransportImport.razor`
|
||||
- EF migration in `src/ScadaLink.ConfigurationDatabase/Migrations/` (adds `BundleImportId` column to `AuditLogEntries`)
|
||||
- New project: `src/ZB.MOM.WW.ScadaBridge.Transport/`
|
||||
- New tests: `tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/`, `tests/ZB.MOM.WW.ScadaBridge.Transport.IntegrationTests/`
|
||||
- Central UI pages: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TransportExport.razor`, `TransportImport.razor`
|
||||
- EF migration in `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Migrations/` (adds `BundleImportId` column to `AuditLogEntries`)
|
||||
- This design doc: `docs/requirements/Component-Transport.md`
|
||||
|
||||
## Responsibilities
|
||||
@@ -21,7 +21,7 @@ The Transport component provides a file-based, encrypted, environment-agnostic w
|
||||
- Validate `manifest.json` on upload: format version gating, SHA-256 content hash verification.
|
||||
- Manage in-memory `BundleSession` objects: 30-minute TTL, 3-strike passphrase lockout per session.
|
||||
- Compute a per-artifact diff between bundle contents and the target environment, classifying each artifact as Identical, Modified, New, or a Blocker.
|
||||
- Apply user-supplied conflict resolutions (Add, Overwrite, Skip, Rename) in a single EF transaction, running two-tier semantic validation before committing: a minimal name-resolution scan over the merged target (fails fast on unresolved SharedScript / ExternalSystem identifiers), then the full `SemanticValidator` from `ScadaLink.TemplateEngine` over each imported template's per-template `FlattenedConfiguration`.
|
||||
- Apply user-supplied conflict resolutions (Add, Overwrite, Skip, Rename) in a single EF transaction, running two-tier semantic validation before committing: a minimal name-resolution scan over the merged target (fails fast on unresolved SharedScript / ExternalSystem identifiers), then the full `SemanticValidator` from `ZB.MOM.WW.ScadaBridge.TemplateEngine` over each imported template's per-template `FlattenedConfiguration`.
|
||||
- Emit `BundleExported`, `BundleImported`, `BundleImportFailed`, `UnencryptedBundleExport`, `BundleImportUnlockFailed`, `BundleImportAlarmScriptUnresolved`, and `BundleImportCompositionUnresolved` audit events via `IAuditService`.
|
||||
- Thread a `BundleImportId` correlation GUID through every per-entity `AuditLogEntry` written during `ApplyAsync` via a scoped `IAuditCorrelationContext`.
|
||||
- Enforce `RequireDesign` on export and `RequireAdmin` on import both at the Razor page layer and inside the service entrypoints (defense in depth).
|
||||
@@ -93,7 +93,7 @@ The manifest is plaintext so the import wizard can preview bundle contents and s
|
||||
## Architecture
|
||||
|
||||
```
|
||||
ScadaLink.Transport
|
||||
ZB.MOM.WW.ScadaBridge.Transport
|
||||
├── IBundleExporter
|
||||
│ ExportAsync(ExportSelection, Passphrase?, ct) → Stream
|
||||
├── IBundleImporter
|
||||
@@ -107,7 +107,7 @@ ScadaLink.Transport
|
||||
└── ManifestValidator (schema/version gating, hash check)
|
||||
```
|
||||
|
||||
The component is central-only. It is registered in `ScadaLink.Host` for central roles only, never for site roles. All persistence flows through existing audited repository interfaces in `ScadaLink.ConfigurationDatabase` — the component does not call `DbContext.SaveChangesAsync` directly. `BundleSessionStore` is in-process on the active central node (matching Blazor Server circuit affinity): 30-minute TTL, eviction on expiry, 3-strike passphrase lockout per session.
|
||||
The component is central-only. It is registered in `ZB.MOM.WW.ScadaBridge.Host` for central roles only, never for site roles. All persistence flows through existing audited repository interfaces in `ZB.MOM.WW.ScadaBridge.ConfigurationDatabase` — the component does not call `DbContext.SaveChangesAsync` directly. `BundleSessionStore` is in-process on the active central node (matching Blazor Server circuit affinity): 30-minute TTL, eviction on expiry, 3-strike passphrase lockout per session.
|
||||
|
||||
## Export Flow
|
||||
|
||||
@@ -171,7 +171,7 @@ Bulk "Apply to all" at the top (Overwrite / Skip / Rename), overridable per row.
|
||||
|
||||
Bundle references that cannot be satisfied in either the bundle or the target DB (e.g., a template references a shared script that is neither in the bundle nor pre-existing) appear as **blocker rows** — Apply is disabled until they are resolved (typically by skipping the dependent artifact or re-exporting with dependencies).
|
||||
|
||||
**Blocker-scan heuristic boundaries.** The scanner walks `TemplateScript.Code`, `TemplateAttribute.Value`, and `ApiMethod.Script` looking for top-level `Identifier(` or `Identifier.` tokens. To keep the heuristic usable on real script bodies it (a) skips identifiers preceded by `.` (member access — `obj.Method()` does not flag `Method`); (b) does NOT scan `TemplateAttribute.DataSourceReference` (an OPC UA address path, never script source); and (c) filters out a small `KnownNonReferenceNames` denylist of .NET stdlib types (`Convert`, `DateTimeOffset`, `ToString`, `Dispose`, `UtcNow`, …), ScadaLink runtime API roots (`Notify`, `Database`, `ExternalSystem`, `Scripts`, `Instance`, `Parameters`, `Attributes`, `Route`, …), and common SQL keywords that appear inside string literals (`COUNT`, `SELECT`, `FROM`, …). Both the diff-step `DetectBlockersAsync` and the Apply-time `RunSemanticValidationAsync` Pass 1 share this filter, so the diff preview and the Apply gate agree.
|
||||
**Blocker-scan heuristic boundaries.** The scanner walks `TemplateScript.Code`, `TemplateAttribute.Value`, and `ApiMethod.Script` looking for top-level `Identifier(` or `Identifier.` tokens. To keep the heuristic usable on real script bodies it (a) skips identifiers preceded by `.` (member access — `obj.Method()` does not flag `Method`); (b) does NOT scan `TemplateAttribute.DataSourceReference` (an OPC UA address path, never script source); and (c) filters out a small `KnownNonReferenceNames` denylist of .NET stdlib types (`Convert`, `DateTimeOffset`, `ToString`, `Dispose`, `UtcNow`, …), ScadaBridge runtime API roots (`Notify`, `Database`, `ExternalSystem`, `Scripts`, `Instance`, `Parameters`, `Attributes`, `Route`, …), and common SQL keywords that appear inside string literals (`COUNT`, `SELECT`, `FROM`, …). Both the diff-step `DetectBlockersAsync` and the Apply-time `RunSemanticValidationAsync` Pass 1 share this filter, so the diff preview and the Apply gate agree.
|
||||
|
||||
**Step 4 — Confirm.** Final summary plus a "N instances will become stale" warning enumerating affected instances. User types the source environment name to confirm (typo-resistant gate at the prod boundary).
|
||||
|
||||
@@ -219,8 +219,8 @@ There is no explicit stale-mark write. Overwriting a template during import chan
|
||||
|
||||
| Where | Failure | Surfaced as |
|
||||
|---|---|---|
|
||||
| Upload | Not a zip / missing `manifest.json` | Step 1 error: "Not a valid ScadaLink bundle" |
|
||||
| Upload | `bundleFormatVersion` newer than supported | Step 1 error: "Bundle was created by ScadaLink v{x}; upgrade this cluster" |
|
||||
| Upload | Not a zip / missing `manifest.json` | Step 1 error: "Not a valid ScadaBridge bundle" |
|
||||
| Upload | `bundleFormatVersion` newer than supported | Step 1 error: "Bundle was created by ScadaBridge v{x}; upgrade this cluster" |
|
||||
| Upload | Content hash mismatch | Step 1 error: "Bundle integrity check failed — file may be corrupt" |
|
||||
| Unlock | Wrong passphrase | Step 2 error; 3rd wrong attempt invalidates session, audit `BundleImportUnlockFailed` |
|
||||
| Preview | Bundle references shared script not in bundle and not in target DB | Listed as a blocker row in Step 3; cannot Apply until resolved |
|
||||
@@ -238,7 +238,7 @@ Imports are all-or-nothing per bundle. A bundle either applies fully or not at a
|
||||
- **Bundle size cap** on upload (default 100 MB, configurable) to bound memory.
|
||||
- **In-transit:** existing HTTPS to the Central UI; no new channel.
|
||||
- **Audit trail is the chain of custody.** Every export, every import (including aborted ones at validation), and every unlock failure is audit-logged with source env, content hash, encrypted yes/no, and artifact summary.
|
||||
- **Defense in depth on authorization:** `RequireDesign` (export) and `RequireAdmin` (import) are enforced both on the Razor page and inside `ScadaLink.Transport` service entrypoints. The UI is not the only gate.
|
||||
- **Defense in depth on authorization:** `RequireDesign` (export) and `RequireAdmin` (import) are enforced both on the Razor page and inside `ZB.MOM.WW.ScadaBridge.Transport` service entrypoints. The UI is not the only gate.
|
||||
- **Bundles are not retained server-side** after download (export) or after `ApplyAsync` commits (import).
|
||||
|
||||
## Configuration Audit Trail
|
||||
@@ -271,15 +271,15 @@ Import flows through the same audited repository methods the UI and CLI use, so
|
||||
Three commands surface the same Transport operations as the Central UI wizards, designed for test automation. The bundle bytes travel as base64 inside the existing `/management` JSON envelope — no new HTTP endpoints — and the per-request body cap is raised to 200 MB to cover the 100 MB raw-bundle ceiling once base64-inflated.
|
||||
|
||||
```bash
|
||||
scadalink bundle export --output FILE --passphrase X [--all | --templates A,B ...] \
|
||||
scadabridge bundle export --output FILE --passphrase X [--all | --templates A,B ...] \
|
||||
[--shared-scripts ...] [--external-systems ...] [--db-connections ...] \
|
||||
[--notification-lists ...] [--smtp-configs ...] [--api-keys ...] \
|
||||
[--api-methods ...] [--include-dependencies] [--source-environment NAME]
|
||||
|
||||
scadalink bundle preview --input FILE --passphrase X
|
||||
scadabridge bundle preview --input FILE --passphrase X
|
||||
# prints PreviewBundleResult JSON: per-row items + add/modified/identical/blocker counts
|
||||
|
||||
scadalink bundle import --input FILE --passphrase X [--on-conflict skip|overwrite|rename]
|
||||
scadabridge bundle import --input FILE --passphrase X [--on-conflict skip|overwrite|rename]
|
||||
# one-shot load + preview + apply with a single global policy for Modified rows
|
||||
# Identical → Skip, New → Add, Blocker → abort
|
||||
```
|
||||
@@ -300,16 +300,16 @@ Exit codes follow the project convention: `0` = success, `1` = command failure (
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **`ScadaLink.Commons`** — Bundle manifest and content DTOs (`BundleManifest`, `ExportSelection`, `ImportPreview`, `ImportResolution`, `ImportResult`, `BundleSession`); transport interface definitions (`IBundleExporter`, `IBundleImporter`, `IBundleSessionStore`, `IAuditCorrelationContext`).
|
||||
- **`ScadaLink.ConfigurationDatabase`** — All repository implementations and `IAuditService` for persistence and per-entity audit emission; `IAuditCorrelationContext` implementation (`AuditCorrelationContext`) registered as a scoped service; EF migration for `BundleImportId`.
|
||||
- **`ScadaLink.TemplateEngine`** — Pre-deployment `SemanticValidator` invoked inside `ApplyAsync` before the transaction commits. The importer builds a single-template `FlattenedConfiguration` directly from each imported `TemplateDto` (no inheritance / composition resolution at design time — the deployment-time flatten revalidates against the full instance graph) and feeds it through the validator alongside a `ResolvedScript` catalog combining in-bundle + pre-existing target `SharedScript`s. Validator errors are aggregated per template and surfaced as a `SemanticValidationException` that rolls back the import transaction.
|
||||
- **`ZB.MOM.WW.ScadaBridge.Commons`** — Bundle manifest and content DTOs (`BundleManifest`, `ExportSelection`, `ImportPreview`, `ImportResolution`, `ImportResult`, `BundleSession`); transport interface definitions (`IBundleExporter`, `IBundleImporter`, `IBundleSessionStore`, `IAuditCorrelationContext`).
|
||||
- **`ZB.MOM.WW.ScadaBridge.ConfigurationDatabase`** — All repository implementations and `IAuditService` for persistence and per-entity audit emission; `IAuditCorrelationContext` implementation (`AuditCorrelationContext`) registered as a scoped service; EF migration for `BundleImportId`.
|
||||
- **`ZB.MOM.WW.ScadaBridge.TemplateEngine`** — Pre-deployment `SemanticValidator` invoked inside `ApplyAsync` before the transaction commits. The importer builds a single-template `FlattenedConfiguration` directly from each imported `TemplateDto` (no inheritance / composition resolution at design time — the deployment-time flatten revalidates against the full instance graph) and feeds it through the validator alongside a `ResolvedScript` catalog combining in-bundle + pre-existing target `SharedScript`s. Validator errors are aggregated per template and surfaced as a `SemanticValidationException` that rolls back the import transaction.
|
||||
|
||||
## Interactions
|
||||
|
||||
- **Central UI** — Hosts the Export Bundle (`/design/transport/export`) page under the Design nav group and the Import Bundle (`/design/transport/import`) page under the Admin nav group. The import result page links to the Deployments page and to the filtered Configuration Audit Log Viewer.
|
||||
- **Management Service / CLI** — `ManagementActor` registers three Transport command handlers (`ExportBundleCommand`, `PreviewBundleCommand`, `ImportBundleCommand`) and the CLI ships `bundle export` / `bundle preview` / `bundle import` subcommands. Bundle bytes ride the existing `/management` JSON envelope as base64.
|
||||
- **Deployment Manager** — Never directly invoked by Transport. Transport-driven template changes propagate to deployed instances through the existing revision-hash drift detection in `DeploymentService.CompareAsync`; the Deployments page surfaces affected instances as stale automatically.
|
||||
- **Security & Auth** — Provides `RequireDesign` and `RequireAdmin` policies from `ScadaLink.Security`, enforced at both the page and service layers.
|
||||
- **Security & Auth** — Provides `RequireDesign` and `RequireAdmin` policies from `ZB.MOM.WW.ScadaBridge.Security`, enforced at both the page and service layers.
|
||||
- **Audit Log (Configuration)** — Writes `BundleExported` / `BundleImported` / `BundleImportFailed` / `UnencryptedBundleExport` / `BundleImportUnlockFailed` rows via `IAuditService`, plus per-import name-resolution warnings `BundleImportAlarmScriptUnresolved` and `BundleImportCompositionUnresolved`; per-entity rows from audited repositories are correlated by `BundleImportId` via `IAuditCorrelationContext`.
|
||||
|
||||
---
|
||||
|
||||
@@ -6,7 +6,7 @@ A reusable, generic Blazor Server component that renders hierarchical data as an
|
||||
|
||||
## Location
|
||||
|
||||
`src/ScadaLink.CentralUI/Components/Shared/TreeView.razor`
|
||||
`src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/TreeView.razor`
|
||||
|
||||
## Primary Use Case: Instance Hierarchy
|
||||
|
||||
@@ -370,7 +370,7 @@ Drag-drop is **not** part of the TreeView component's intrinsic behavior — it
|
||||
|
||||
### V4 — Glyph & Icon System
|
||||
|
||||
**Distribution**: Bootstrap Icons ships as static files under `src/ScadaLink.CentralUI/wwwroot/lib/bootstrap-icons/` (`bootstrap-icons.css` + `fonts/*.woff2`). Referenced once from `MainLayout.razor`:
|
||||
**Distribution**: Bootstrap Icons ships as static files under `src/ZB.MOM.WW.ScadaBridge.CentralUI/wwwroot/lib/bootstrap-icons/` (`bootstrap-icons.css` + `fonts/*.woff2`). Referenced once from `MainLayout.razor`:
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="~/lib/bootstrap-icons/bootstrap-icons.css" />
|
||||
@@ -612,7 +612,7 @@ The component is generic enough for:
|
||||
|
||||
## Testing
|
||||
|
||||
Unit tests use the existing bUnit + xUnit + NSubstitute setup in `tests/ScadaLink.CentralUI.Tests/`. Tests live in a dedicated file: `TreeViewTests.cs`.
|
||||
Unit tests use the existing bUnit + xUnit + NSubstitute setup in `tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/`. Tests live in a dedicated file: `TreeViewTests.cs`.
|
||||
|
||||
All tests use a simple test model:
|
||||
|
||||
@@ -694,7 +694,7 @@ record TestNode(string Key, string Label, List<TestNode> Children);
|
||||
|
||||
### Test File Location
|
||||
|
||||
`tests/ScadaLink.CentralUI.Tests/TreeViewTests.cs`
|
||||
`tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/TreeViewTests.cs`
|
||||
|
||||
## Dependencies
|
||||
|
||||
@@ -738,14 +738,14 @@ The Topology page is the single home for Site → Area → Instance hierarchy ma
|
||||
**Top-of-page buttons:** `+ Area` (opens `CreateAreaDialog` with site picker), `+ Instance` (navigates to `/deployment/instances/create` with no preselection), `Refresh`, `Expand`, `Collapse`.
|
||||
|
||||
**Files added:**
|
||||
- `src/ScadaLink.CentralUI/Components/Pages/Deployment/Topology.razor`
|
||||
- `src/ScadaLink.CentralUI/Components/Pages/Deployment/MoveAreaDialog.razor`
|
||||
- `src/ScadaLink.CentralUI/Components/Pages/Deployment/MoveInstanceDialog.razor`
|
||||
- `src/ScadaLink.CentralUI/Components/Pages/Deployment/CreateAreaDialog.razor`
|
||||
- `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/Topology.razor`
|
||||
- `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/MoveAreaDialog.razor`
|
||||
- `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/MoveInstanceDialog.razor`
|
||||
- `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/CreateAreaDialog.razor`
|
||||
|
||||
**Files removed:**
|
||||
- `src/ScadaLink.CentralUI/Components/Pages/Deployment/Instances.razor`
|
||||
- `src/ScadaLink.CentralUI/Components/Pages/Admin/Areas.razor` (and AreaAdd / AreaEdit / AreaDelete)
|
||||
- `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/Instances.razor`
|
||||
- `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Admin/Areas.razor` (and AreaAdd / AreaEdit / AreaDelete)
|
||||
|
||||
**Backend addition:** `AreaService.MoveAreaAsync(int areaId, int? newParentAreaId, string user)` adds area re-parenting (cycle prevention, same-site, name collision at new parent). Pairs with the existing `InstanceService.AssignToAreaAsync`.
|
||||
|
||||
@@ -771,7 +771,7 @@ The Topology page is the single home for Site → Area → Instance hierarchy ma
|
||||
- **StorageKey:** `"data-connections-tree"`
|
||||
|
||||
**Files to modify:**
|
||||
- `src/ScadaLink.CentralUI/Components/Pages/Admin/DataConnections.razor` — replace table with TreeView, add tree model building, move actions to context menu.
|
||||
- `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Admin/DataConnections.razor` — replace table with TreeView, add tree model building, move actions to context menu.
|
||||
|
||||
**Removed code:**
|
||||
- `<table>` / `<thead>` / `<tbody>` structure
|
||||
|
||||
@@ -457,7 +457,7 @@ Sections 10.1–10.4 cover **configuration-database audit** (config-mutating use
|
||||
- **AL-9**: The site SQLite Audit Log is purged only when `ForwardState ∈ {Forwarded, Reconciled}` — i.e., a row must be either confirmed-forwarded *or* confirmed-reconciled before it can be removed. A central outage therefore **cannot cause audit loss at sites**.
|
||||
- **AL-10**: The Central UI exposes an **Audit Log page** with a cross-channel filter (by site, target, status, time range, correlation ID), plus **drill-ins from existing operational pages** (Site Calls, Notification Outbox, Inbound API).
|
||||
- **AL-11**: Append-only semantics are **enforced via DB roles** (no UPDATE/DELETE granted on the `AuditLog` table to application accounts); a **tamper-evidence hash chain is deferred to v1.x**.
|
||||
- **AL-12**: The CLI provides a `scadalink audit` command group for query, export, and hash-chain verification (verify-chain becomes operational once AL-11's hash chain ships) against the central Audit Log.
|
||||
- **AL-12**: The CLI provides a `scadabridge audit` command group for query, export, and hash-chain verification (verify-chain becomes operational once AL-11's hash chain ships) against the central Audit Log.
|
||||
|
||||
## 11. Health Monitoring
|
||||
|
||||
@@ -503,12 +503,12 @@ Sites log operational events locally, including:
|
||||
- The ManagementActor runs on **every central node** (stateless). For HTTP API access, any central node can handle any request without sticky sessions.
|
||||
|
||||
### 13.2 CLI
|
||||
- The system provides a standalone **command-line tool** (`scadalink`) for scripting and automating administrative operations.
|
||||
- The system provides a standalone **command-line tool** (`scadabridge`) for scripting and automating administrative operations.
|
||||
- The CLI connects to the Central Host's HTTP Management API (`POST /management`) — it sends commands as JSON with HTTP Basic Auth credentials. The server handles LDAP authentication, role resolution, and ManagementActor dispatch.
|
||||
- The CLI sends user credentials via HTTP Basic Auth. The server authenticates against **LDAP/AD** and resolves roles before dispatching commands to the ManagementActor.
|
||||
- CLI commands mirror all Management Service operations: templates, instances, sites, data connections, deployments, external systems, notifications, security (API keys and role mappings), audit log queries, and health status.
|
||||
- Output is **JSON by default** (machine-readable, suitable for scripting) with an optional `--format table` flag for human-readable tabular output.
|
||||
- Configuration is resolved from command-line options, **environment variables** (`SCADALINK_MANAGEMENT_URL`, `SCADALINK_FORMAT`), or a **configuration file** (`~/.scadalink/config.json`).
|
||||
- Configuration is resolved from command-line options, **environment variables** (`SCADALINK_MANAGEMENT_URL`, `SCADALINK_FORMAT`), or a **configuration file** (`~/.scadabridge/config.json`).
|
||||
- The CLI is a separate executable from the Host binary — it is deployed on any machine with HTTP access to a central node.
|
||||
|
||||
## 14. General Conventions
|
||||
|
||||
Reference in New Issue
Block a user