@attraccess/plugin-wago 0.1.0-nightly.932.1 → 0.1.0-nightly.935.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +556 -74
- package/dist/migrations.js +22 -0
- package/dist/signing-public-key.pub +1 -0
- package/frontend/{__federation_expose_Plugin-ByLnlTDb.js → __federation_expose_Plugin-DdJzlkSB.js} +208 -190
- package/frontend/remoteEntry.js +1 -1
- package/frontend/style.css +0 -19
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -35,8 +35,8 @@ __export(plugin_exports, {
|
|
|
35
35
|
module.exports = __toCommonJS(plugin_exports);
|
|
36
36
|
|
|
37
37
|
// backend/wago.controller.ts
|
|
38
|
-
var
|
|
39
|
-
var
|
|
38
|
+
var import_common3 = require("@nestjs/common");
|
|
39
|
+
var import_plugins_backend_sdk8 = require("@attraccess/plugins-backend-sdk");
|
|
40
40
|
|
|
41
41
|
// backend/wago.service.ts
|
|
42
42
|
var import_node_crypto2 = require("node:crypto");
|
|
@@ -711,6 +711,12 @@ var PLUGIN_CONTEXT = /* @__PURE__ */ Symbol.for("attraccess.plugin.context");
|
|
|
711
711
|
var STALE_AFTER_MS = 9e4;
|
|
712
712
|
var MAX_PENDING_CONFIGURATION_REPORTS = 100;
|
|
713
713
|
var ENROLLMENT_RETRY_MS = 6e4;
|
|
714
|
+
var MqttSubscriptionError = class extends Error {
|
|
715
|
+
constructor(mqttError) {
|
|
716
|
+
super(String(mqttError));
|
|
717
|
+
__publicField(this, "mqttError", mqttError);
|
|
718
|
+
}
|
|
719
|
+
};
|
|
714
720
|
var WagoService = class {
|
|
715
721
|
constructor(context) {
|
|
716
722
|
__publicField(this, "context", context);
|
|
@@ -738,7 +744,13 @@ var WagoService = class {
|
|
|
738
744
|
this.revisions = this.context.getRepository(WagoConfigurationRevision);
|
|
739
745
|
const enrollments = await this.enrollments.createQueryBuilder("enrollment").where("enrollment.consumedAt IS NULL").getMany();
|
|
740
746
|
for (const enrollment of enrollments) this.scheduleEnrollmentExpiry(enrollment);
|
|
741
|
-
|
|
747
|
+
try {
|
|
748
|
+
await this.subscribeConfiguredServers();
|
|
749
|
+
} catch (error) {
|
|
750
|
+
if (!(error instanceof MqttSubscriptionError)) throw error;
|
|
751
|
+
this.context.logger.warn(`Could not establish WAGO MQTT subscriptions during startup: ${String(error.mqttError)}`);
|
|
752
|
+
this.scheduleSubscriptionRetry();
|
|
753
|
+
}
|
|
742
754
|
}
|
|
743
755
|
onModuleDestroy() {
|
|
744
756
|
this.destroyed = true;
|
|
@@ -803,7 +815,7 @@ var WagoService = class {
|
|
|
803
815
|
if (!await this.context.getMqttServerConfig(mqttServerId))
|
|
804
816
|
throw new import_common.NotFoundException(`MQTT server ${mqttServerId} not found`);
|
|
805
817
|
const providers = await this.context.getMqttCredentialProvisioning().availableProviders(mqttServerId);
|
|
806
|
-
return { automatic: providers.length
|
|
818
|
+
return { automatic: providers.length === 1 };
|
|
807
819
|
}
|
|
808
820
|
async getDraft(controllerId) {
|
|
809
821
|
await this.claimedController(controllerId);
|
|
@@ -1004,6 +1016,11 @@ var WagoService = class {
|
|
|
1004
1016
|
) : void 0
|
|
1005
1017
|
};
|
|
1006
1018
|
}
|
|
1019
|
+
/** Server-side commissioning calls this to revoke a bootstrap identity without exposing it to a browser. */
|
|
1020
|
+
async revokeEnrollmentForHardwareId(hardwareId) {
|
|
1021
|
+
const enrollment = (await this.enrollments.find({ where: { hardwareId } })).find((item) => this.isActiveEnrollment(item));
|
|
1022
|
+
if (enrollment) await this.revokeEnrollment(enrollment);
|
|
1023
|
+
}
|
|
1007
1024
|
async claim(id, name, verifier, mqttServerId) {
|
|
1008
1025
|
return this.withClaimLock(id, async () => {
|
|
1009
1026
|
const prepared = await this.withClaimConfigurationLock(() => this.prepareClaim(id, name, verifier, mqttServerId));
|
|
@@ -1165,7 +1182,7 @@ var WagoService = class {
|
|
|
1165
1182
|
try {
|
|
1166
1183
|
for (const serverId of serverIds) {
|
|
1167
1184
|
replacements.push(
|
|
1168
|
-
await this.
|
|
1185
|
+
await this.subscribeMqtt(serverId, `${DISCOVERY_ROOT}/+`, async (message) => {
|
|
1169
1186
|
if (!this.isActiveSubscriptionGeneration(generation)) return;
|
|
1170
1187
|
await this.onDiscovery(serverId, message.topic, message.payload);
|
|
1171
1188
|
})
|
|
@@ -1181,7 +1198,7 @@ var WagoService = class {
|
|
|
1181
1198
|
claimedControllers.map((controller) => [controller.hardwareId, controller])
|
|
1182
1199
|
);
|
|
1183
1200
|
replacements.push(
|
|
1184
|
-
await this.
|
|
1201
|
+
await this.subscribeMqtt(
|
|
1185
1202
|
serverId,
|
|
1186
1203
|
configurationReportedWildcardTopic(settings.operationalPrefix),
|
|
1187
1204
|
(message) => {
|
|
@@ -1198,7 +1215,7 @@ var WagoService = class {
|
|
|
1198
1215
|
}
|
|
1199
1216
|
for (const controller of claimedControllers) {
|
|
1200
1217
|
replacements.push(
|
|
1201
|
-
await this.
|
|
1218
|
+
await this.subscribeMqtt(
|
|
1202
1219
|
serverId,
|
|
1203
1220
|
heartbeatTopic(settings.operationalPrefix, controller.hardwareId),
|
|
1204
1221
|
async (message) => {
|
|
@@ -1228,6 +1245,13 @@ var WagoService = class {
|
|
|
1228
1245
|
unsubscribe() {
|
|
1229
1246
|
this.subscriptions.splice(0).forEach((subscription) => subscription.unsubscribe());
|
|
1230
1247
|
}
|
|
1248
|
+
async subscribeMqtt(...args) {
|
|
1249
|
+
try {
|
|
1250
|
+
return await this.context.mqtt.subscribe(...args);
|
|
1251
|
+
} catch (error) {
|
|
1252
|
+
throw new MqttSubscriptionError(error);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1231
1255
|
isActiveSubscriptionGeneration(generation) {
|
|
1232
1256
|
return !this.destroyed && generation === this.activeSubscriptionGeneration;
|
|
1233
1257
|
}
|
|
@@ -1549,31 +1573,452 @@ function parsePresetProvenance(value) {
|
|
|
1549
1573
|
}
|
|
1550
1574
|
}
|
|
1551
1575
|
function applySelectedChanges(snapshot, diff, selectedPaths) {
|
|
1552
|
-
|
|
1576
|
+
let merged = JSON.parse(JSON.stringify(snapshot));
|
|
1553
1577
|
const changes = new Map(diff.map((change) => [change.path, change]));
|
|
1554
1578
|
for (const path of selectedPaths) {
|
|
1555
1579
|
const change = changes.get(path);
|
|
1556
1580
|
if (!change) continue;
|
|
1557
1581
|
const segments = [...path.matchAll(/\.([^.[\]]+)|\[(\d+)\]/g)].map((match) => match[1] ?? Number(match[2]));
|
|
1558
|
-
if (!segments.length) continue;
|
|
1559
|
-
|
|
1560
|
-
for (const segment of segments.slice(0, -1)) {
|
|
1561
|
-
const next = target[segment];
|
|
1562
|
-
if (next === void 0) target[segment] = typeof segment === "number" ? [] : {};
|
|
1563
|
-
target = target[segment];
|
|
1564
|
-
}
|
|
1565
|
-
const last = segments[segments.length - 1];
|
|
1566
|
-
if (last === void 0) continue;
|
|
1567
|
-
if (change.current === void 0) delete target[last];
|
|
1568
|
-
else target[last] = change.current;
|
|
1582
|
+
if (!segments.length || segments.some((segment) => typeof segment === "string" && unsafePathSegment(segment))) continue;
|
|
1583
|
+
merged = replacePath(merged, segments, change.current);
|
|
1569
1584
|
}
|
|
1570
1585
|
return merged;
|
|
1571
1586
|
}
|
|
1587
|
+
function replacePath(value, [segment, ...remaining], replacement) {
|
|
1588
|
+
if (segment === void 0) return replacement;
|
|
1589
|
+
if (typeof segment === "number") {
|
|
1590
|
+
const next = Array.isArray(value) ? [...value] : [];
|
|
1591
|
+
if (remaining.length) next[segment] = replacePath(next[segment], remaining, replacement);
|
|
1592
|
+
else if (replacement === void 0) delete next[segment];
|
|
1593
|
+
else next[segment] = replacement;
|
|
1594
|
+
return next;
|
|
1595
|
+
}
|
|
1596
|
+
const entries = Object.entries(value ?? {}).filter(([key]) => key !== segment);
|
|
1597
|
+
if (remaining.length) entries.push([segment, replacePath(value?.[segment], remaining, replacement)]);
|
|
1598
|
+
else if (replacement !== void 0) entries.push([segment, replacement]);
|
|
1599
|
+
return Object.fromEntries(entries);
|
|
1600
|
+
}
|
|
1601
|
+
function unsafePathSegment(segment) {
|
|
1602
|
+
return segment === "__proto__" || segment === "constructor" || segment === "prototype";
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
// backend/wago-commissioning.service.ts
|
|
1606
|
+
var import_common2 = require("@nestjs/common");
|
|
1607
|
+
var import_node_child_process = require("node:child_process");
|
|
1608
|
+
var import_node_crypto3 = require("node:crypto");
|
|
1609
|
+
var import_promises = require("node:fs/promises");
|
|
1610
|
+
var import_node_os = require("node:os");
|
|
1611
|
+
var import_node_path = require("node:path");
|
|
1612
|
+
var import_plugins_backend_sdk7 = require("@attraccess/plugins-backend-sdk");
|
|
1613
|
+
|
|
1614
|
+
// backend/wago-commissioning-session.entity.ts
|
|
1615
|
+
var import_plugins_backend_sdk6 = require("@attraccess/plugins-backend-sdk");
|
|
1616
|
+
var WagoCommissioningSession = class {
|
|
1617
|
+
constructor() {
|
|
1618
|
+
__publicField(this, "id");
|
|
1619
|
+
__publicField(this, "hardwareId");
|
|
1620
|
+
__publicField(this, "mqttServerId");
|
|
1621
|
+
__publicField(this, "targetHost");
|
|
1622
|
+
__publicField(this, "hostKeyFingerprint");
|
|
1623
|
+
__publicField(this, "firmwareBaseline");
|
|
1624
|
+
__publicField(this, "state");
|
|
1625
|
+
__publicField(this, "enrollmentExpiresAt");
|
|
1626
|
+
__publicField(this, "codesysState");
|
|
1627
|
+
__publicField(this, "auditLog");
|
|
1628
|
+
__publicField(this, "failureReason");
|
|
1629
|
+
__publicField(this, "createdAt");
|
|
1630
|
+
__publicField(this, "updatedAt");
|
|
1631
|
+
}
|
|
1632
|
+
};
|
|
1633
|
+
__decorateClass([
|
|
1634
|
+
(0, import_plugins_backend_sdk6.PrimaryGeneratedColumn)({ type: "integer" })
|
|
1635
|
+
], WagoCommissioningSession.prototype, "id", 2);
|
|
1636
|
+
__decorateClass([
|
|
1637
|
+
(0, import_plugins_backend_sdk6.Column)({ type: "varchar", name: "hardware_id" })
|
|
1638
|
+
], WagoCommissioningSession.prototype, "hardwareId", 2);
|
|
1639
|
+
__decorateClass([
|
|
1640
|
+
(0, import_plugins_backend_sdk6.Column)({ type: "integer", name: "mqtt_server_id" })
|
|
1641
|
+
], WagoCommissioningSession.prototype, "mqttServerId", 2);
|
|
1642
|
+
__decorateClass([
|
|
1643
|
+
(0, import_plugins_backend_sdk6.Column)({ type: "varchar", name: "target_host" })
|
|
1644
|
+
], WagoCommissioningSession.prototype, "targetHost", 2);
|
|
1645
|
+
__decorateClass([
|
|
1646
|
+
(0, import_plugins_backend_sdk6.Column)({ type: "varchar", name: "host_key_fingerprint" })
|
|
1647
|
+
], WagoCommissioningSession.prototype, "hostKeyFingerprint", 2);
|
|
1648
|
+
__decorateClass([
|
|
1649
|
+
(0, import_plugins_backend_sdk6.Column)({ type: "varchar", name: "firmware_baseline" })
|
|
1650
|
+
], WagoCommissioningSession.prototype, "firmwareBaseline", 2);
|
|
1651
|
+
__decorateClass([
|
|
1652
|
+
(0, import_plugins_backend_sdk6.Column)({ type: "varchar" })
|
|
1653
|
+
], WagoCommissioningSession.prototype, "state", 2);
|
|
1654
|
+
__decorateClass([
|
|
1655
|
+
(0, import_plugins_backend_sdk6.Column)({ type: "varchar", name: "enrollment_expires_at", nullable: true })
|
|
1656
|
+
], WagoCommissioningSession.prototype, "enrollmentExpiresAt", 2);
|
|
1657
|
+
__decorateClass([
|
|
1658
|
+
(0, import_plugins_backend_sdk6.Column)({ type: "varchar", name: "codesys_state", nullable: true })
|
|
1659
|
+
], WagoCommissioningSession.prototype, "codesysState", 2);
|
|
1660
|
+
__decorateClass([
|
|
1661
|
+
(0, import_plugins_backend_sdk6.Column)({ type: "varchar", name: "audit_log" })
|
|
1662
|
+
], WagoCommissioningSession.prototype, "auditLog", 2);
|
|
1663
|
+
__decorateClass([
|
|
1664
|
+
(0, import_plugins_backend_sdk6.Column)({ type: "varchar", name: "failure_reason", nullable: true })
|
|
1665
|
+
], WagoCommissioningSession.prototype, "failureReason", 2);
|
|
1666
|
+
__decorateClass([
|
|
1667
|
+
(0, import_plugins_backend_sdk6.Column)({ type: "varchar", name: "created_at" })
|
|
1668
|
+
], WagoCommissioningSession.prototype, "createdAt", 2);
|
|
1669
|
+
__decorateClass([
|
|
1670
|
+
(0, import_plugins_backend_sdk6.Column)({ type: "varchar", name: "updated_at" })
|
|
1671
|
+
], WagoCommissioningSession.prototype, "updatedAt", 2);
|
|
1672
|
+
WagoCommissioningSession = __decorateClass([
|
|
1673
|
+
(0, import_plugins_backend_sdk6.Entity)({ name: "plugin_wago_commissioning_sessions" })
|
|
1674
|
+
], WagoCommissioningSession);
|
|
1675
|
+
|
|
1676
|
+
// backend/wago-commissioning.service.ts
|
|
1677
|
+
var SSH_TIMEOUT_MS = 2e4;
|
|
1678
|
+
var configuredFirmwareBaseline = process.env.WAGO_CC100_FIRMWARE_BASELINE?.trim() || "31";
|
|
1679
|
+
var configuredRuntimeImage = process.env.WAGO_CC100_RUNTIME_IMAGE?.trim() ?? "";
|
|
1680
|
+
var configuredRuntimeBundle = process.env.WAGO_CC100_RUNTIME_BUNDLE_PATH?.trim() ?? "";
|
|
1681
|
+
var configuredRuntimeBundleChecksum = process.env.WAGO_CC100_RUNTIME_BUNDLE_SHA256_PATH?.trim() ?? "";
|
|
1682
|
+
var configuredRuntimeBundleSignature = process.env.WAGO_CC100_RUNTIME_BUNDLE_SIGNATURE_PATH?.trim() ?? "";
|
|
1683
|
+
var SIGNING_NAMESPACE = "attraccess-wago-runtime";
|
|
1684
|
+
var SIGNING_IDENTITY = "attraccess-wago-runtime";
|
|
1685
|
+
var WagoCommissioningService = class {
|
|
1686
|
+
constructor(context, wago) {
|
|
1687
|
+
__publicField(this, "context", context);
|
|
1688
|
+
__publicField(this, "wago", wago);
|
|
1689
|
+
__publicField(this, "sessions");
|
|
1690
|
+
this.sessions = context.getRepository(WagoCommissioningSession);
|
|
1691
|
+
}
|
|
1692
|
+
support() {
|
|
1693
|
+
return {
|
|
1694
|
+
firmwareBaseline: configuredFirmwareBaseline || null,
|
|
1695
|
+
ready: Boolean(
|
|
1696
|
+
configuredFirmwareBaseline && isImmutableImage(configuredRuntimeImage) && configuredRuntimeBundle && configuredRuntimeBundleChecksum && configuredRuntimeBundleSignature
|
|
1697
|
+
)
|
|
1698
|
+
};
|
|
1699
|
+
}
|
|
1700
|
+
async create(input) {
|
|
1701
|
+
const hardwareId = input.hardwareId.trim();
|
|
1702
|
+
if (!/^[A-Za-z0-9._-]+$/.test(hardwareId)) throw new import_common2.ConflictException("a valid controller hardware ID is required");
|
|
1703
|
+
if (!isPrivateAddress(input.targetHost)) throw new import_common2.ConflictException("commissioning is limited to a private controller address");
|
|
1704
|
+
if (!configuredFirmwareBaseline)
|
|
1705
|
+
throw new import_common2.ConflictException("no CC100 firmware baseline is configured; commissioning is disabled until an exact supported baseline is set");
|
|
1706
|
+
if (!await this.context.getMqttServerConfig(input.mqttServerId)) throw new import_common2.NotFoundException("MQTT server not found");
|
|
1707
|
+
const hostKeyFingerprint = await scanHostKey(input.targetHost);
|
|
1708
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1709
|
+
return this.sessions.save(
|
|
1710
|
+
this.sessions.create({
|
|
1711
|
+
hardwareId,
|
|
1712
|
+
mqttServerId: input.mqttServerId,
|
|
1713
|
+
targetHost: input.targetHost,
|
|
1714
|
+
hostKeyFingerprint,
|
|
1715
|
+
firmwareBaseline: configuredFirmwareBaseline,
|
|
1716
|
+
state: "awaiting_identity_confirmation",
|
|
1717
|
+
enrollmentExpiresAt: null,
|
|
1718
|
+
codesysState: null,
|
|
1719
|
+
auditLog: JSON.stringify([{ at: now, event: "host_key_scanned" }]),
|
|
1720
|
+
failureReason: null,
|
|
1721
|
+
createdAt: now,
|
|
1722
|
+
updatedAt: now
|
|
1723
|
+
})
|
|
1724
|
+
);
|
|
1725
|
+
}
|
|
1726
|
+
async list() {
|
|
1727
|
+
return this.sessions.find({ order: { updatedAt: "DESC" } });
|
|
1728
|
+
}
|
|
1729
|
+
async deliver(id, input) {
|
|
1730
|
+
const session = await this.sessions.findOneBy({ id });
|
|
1731
|
+
if (!session) throw new import_common2.NotFoundException("commissioning session not found");
|
|
1732
|
+
if (session.state === "completed" || session.state === "revoked") throw new import_common2.ConflictException("commissioning session is no longer active");
|
|
1733
|
+
if (!input.physicalIdentityConfirmed) throw new import_common2.ConflictException("physical controller identity confirmation is required");
|
|
1734
|
+
if (input.hostKeyFingerprint !== session.hostKeyFingerprint) throw new import_common2.ConflictException("controller SSH host key does not match the scanned key");
|
|
1735
|
+
if (!input.temporarySsh.username.trim() || !input.temporarySsh.password)
|
|
1736
|
+
throw new import_common2.ConflictException("temporary SSH credentials are required for this session only");
|
|
1737
|
+
if (!isRuntimeArtifactConfigured())
|
|
1738
|
+
throw new import_common2.ConflictException("no immutable, signed CC100 runtime bundle is configured for commissioning");
|
|
1739
|
+
try {
|
|
1740
|
+
const inspection = await this.inspect(session.targetHost, session.hostKeyFingerprint, input.temporarySsh);
|
|
1741
|
+
session.codesysState = inspection.codesys;
|
|
1742
|
+
if (!isSupportedController(inspection.firmware, session.firmwareBaseline)) {
|
|
1743
|
+
throw new import_common2.ConflictException(`unsupported CC100 model or firmware; expected baseline ${session.firmwareBaseline}`);
|
|
1744
|
+
}
|
|
1745
|
+
if (inspection.codesys === "active" && !input.codesysStopConfirmed) {
|
|
1746
|
+
session.state = "awaiting_codesys_confirmation";
|
|
1747
|
+
session.failureReason = "CODESYS is active; explicit administrator confirmation is required before it is stopped.";
|
|
1748
|
+
return this.save(session, "codesys_confirmation_required");
|
|
1749
|
+
}
|
|
1750
|
+
const bundle = await verifyRuntimeBundle();
|
|
1751
|
+
const enrollment = await this.wago.createEnrollment(session.hardwareId, session.mqttServerId);
|
|
1752
|
+
if (!enrollment.password) throw new import_common2.ConflictException("automatic restricted MQTT credential provisioning is required for secure commissioning");
|
|
1753
|
+
const runtimeEnv = [
|
|
1754
|
+
`WAGO_HARDWARE_ID=${session.hardwareId}`,
|
|
1755
|
+
`WAGO_MQTT_URL=${enrollment.broker.useTls ? "mqtts" : "mqtt"}://${enrollment.broker.host}:${enrollment.broker.port}`,
|
|
1756
|
+
`WAGO_MQTT_USERNAME=${enrollment.username}`,
|
|
1757
|
+
`WAGO_MQTT_PASSWORD=${enrollment.password}`,
|
|
1758
|
+
`WAGO_ENROLLMENT_SECRET=${enrollment.claimSecret}`
|
|
1759
|
+
].join("\n");
|
|
1760
|
+
if (inspection.codesys === "active")
|
|
1761
|
+
await this.sudoRun(session.targetHost, session.hostKeyFingerprint, input.temporarySsh, "/etc/init.d/pp_codesys3 stop");
|
|
1762
|
+
await this.sudoRun(
|
|
1763
|
+
session.targetHost,
|
|
1764
|
+
session.hostKeyFingerprint,
|
|
1765
|
+
input.temporarySsh,
|
|
1766
|
+
"docker info >/dev/null 2>&1 || /etc/init.d/dockerd start; docker info >/dev/null"
|
|
1767
|
+
);
|
|
1768
|
+
await this.writeRuntimeEnvironment(session.targetHost, session.hostKeyFingerprint, input.temporarySsh, runtimeEnv);
|
|
1769
|
+
try {
|
|
1770
|
+
await this.copyTo(session.targetHost, session.hostKeyFingerprint, input.temporarySsh, bundle.path, "/tmp/attraccess-wago-runtime.tar");
|
|
1771
|
+
await this.sudoRun(
|
|
1772
|
+
session.targetHost,
|
|
1773
|
+
session.hostKeyFingerprint,
|
|
1774
|
+
input.temporarySsh,
|
|
1775
|
+
`docker load -i /tmp/attraccess-wago-runtime.tar && rm -f /tmp/attraccess-wago-runtime.tar && docker image inspect ${runtimeImageTag()} >/dev/null && (docker rm -f attraccess-wago >/dev/null 2>&1 || true) && docker run -d --name attraccess-wago --restart unless-stopped --env-file /etc/attraccess-wago/runtime.env -v /var/lib/attraccess-wago:/var/lib/attraccess-wago ${runtimeImageTag()}`
|
|
1776
|
+
);
|
|
1777
|
+
} finally {
|
|
1778
|
+
await (0, import_promises.rm)(bundle.directory, { recursive: true, force: true });
|
|
1779
|
+
}
|
|
1780
|
+
session.state = "awaiting_discovery";
|
|
1781
|
+
session.enrollmentExpiresAt = enrollment.expiresAt;
|
|
1782
|
+
session.failureReason = null;
|
|
1783
|
+
return this.save(session, "bootstrap_delivered");
|
|
1784
|
+
} catch (error) {
|
|
1785
|
+
session.state = "delivery_failed";
|
|
1786
|
+
session.failureReason = error instanceof Error ? redact(error.message) : "Secure delivery failed";
|
|
1787
|
+
return this.save(session, "delivery_failed");
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
async revoke(id) {
|
|
1791
|
+
const session = await this.sessions.findOneBy({ id });
|
|
1792
|
+
if (!session) throw new import_common2.NotFoundException("commissioning session not found");
|
|
1793
|
+
await this.wago.revokeEnrollmentForHardwareId(session.hardwareId);
|
|
1794
|
+
session.state = "revoked";
|
|
1795
|
+
session.failureReason = null;
|
|
1796
|
+
return this.save(session, "revoked");
|
|
1797
|
+
}
|
|
1798
|
+
async inspect(host, fingerprint, credential) {
|
|
1799
|
+
const output = await this.run(host, fingerprint, credential, "cat /etc/os-release; printf '\\nCODESYS='; ps");
|
|
1800
|
+
const [, codesys = "unknown"] = output.match(/CODESYS=([^\r\n]*)/) ?? [];
|
|
1801
|
+
return { firmware: output, codesys: /codesys/i.test(codesys) ? "active" : "inactive" };
|
|
1802
|
+
}
|
|
1803
|
+
async writeRuntimeEnvironment(host, fingerprint, credential, content) {
|
|
1804
|
+
await this.sudoRun(
|
|
1805
|
+
host,
|
|
1806
|
+
fingerprint,
|
|
1807
|
+
credential,
|
|
1808
|
+
"install -d -m 0700 /etc/attraccess-wago && umask 077 && base64 -d > /etc/attraccess-wago/runtime.env && chmod 0600 /etc/attraccess-wago/runtime.env",
|
|
1809
|
+
Buffer.from(content).toString("base64")
|
|
1810
|
+
);
|
|
1811
|
+
}
|
|
1812
|
+
async sudoRun(host, fingerprint, credential, command, input) {
|
|
1813
|
+
return this.run(host, fingerprint, credential, `sudo -S sh -c ${shellQuote(command)}`, `${credential.password}
|
|
1814
|
+
${input ?? ""}`);
|
|
1815
|
+
}
|
|
1816
|
+
async run(host, fingerprint, credential, command, input) {
|
|
1817
|
+
const dir = await (0, import_promises.mkdtemp)((0, import_node_path.join)((0, import_node_os.tmpdir)(), "attraccess-cc100-"));
|
|
1818
|
+
const knownHosts = (0, import_node_path.join)(dir, "known_hosts");
|
|
1819
|
+
const askPass = (0, import_node_path.join)(dir, "askpass");
|
|
1820
|
+
try {
|
|
1821
|
+
await (0, import_promises.writeFile)(knownHosts, await pinnedHostKey(host, fingerprint), { mode: 384 });
|
|
1822
|
+
await (0, import_promises.writeFile)(askPass, `#!/bin/sh
|
|
1823
|
+
printf '%s\\n' "$ATTRACCESS_SSH_PASSWORD"
|
|
1824
|
+
`, { mode: 448 });
|
|
1825
|
+
return await runProcess(
|
|
1826
|
+
"ssh",
|
|
1827
|
+
[
|
|
1828
|
+
"-o",
|
|
1829
|
+
"BatchMode=no",
|
|
1830
|
+
"-o",
|
|
1831
|
+
"NumberOfPasswordPrompts=1",
|
|
1832
|
+
"-o",
|
|
1833
|
+
"HostKeyAlgorithms=ssh-ed25519",
|
|
1834
|
+
"-o",
|
|
1835
|
+
"StrictHostKeyChecking=yes",
|
|
1836
|
+
"-o",
|
|
1837
|
+
`UserKnownHostsFile=${knownHosts}`,
|
|
1838
|
+
"-o",
|
|
1839
|
+
"ConnectTimeout=15",
|
|
1840
|
+
`${credential.username}@${host}`,
|
|
1841
|
+
"sh",
|
|
1842
|
+
"-c",
|
|
1843
|
+
command
|
|
1844
|
+
],
|
|
1845
|
+
input,
|
|
1846
|
+
{ SSH_ASKPASS: askPass, SSH_ASKPASS_REQUIRE: "force", DISPLAY: "attraccess", ATTRACCESS_SSH_PASSWORD: credential.password }
|
|
1847
|
+
);
|
|
1848
|
+
} finally {
|
|
1849
|
+
await (0, import_promises.rm)(dir, { recursive: true, force: true });
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
async copyTo(host, fingerprint, credential, source, destination) {
|
|
1853
|
+
const dir = await (0, import_promises.mkdtemp)((0, import_node_path.join)((0, import_node_os.tmpdir)(), "attraccess-cc100-"));
|
|
1854
|
+
const knownHosts = (0, import_node_path.join)(dir, "known_hosts");
|
|
1855
|
+
const askPass = (0, import_node_path.join)(dir, "askpass");
|
|
1856
|
+
try {
|
|
1857
|
+
await (0, import_promises.writeFile)(knownHosts, await pinnedHostKey(host, fingerprint), { mode: 384 });
|
|
1858
|
+
await (0, import_promises.writeFile)(askPass, `#!/bin/sh
|
|
1859
|
+
printf '%s\\n' "$ATTRACCESS_SSH_PASSWORD"
|
|
1860
|
+
`, { mode: 448 });
|
|
1861
|
+
await runProcess(
|
|
1862
|
+
"scp",
|
|
1863
|
+
[
|
|
1864
|
+
"-o",
|
|
1865
|
+
"BatchMode=no",
|
|
1866
|
+
"-o",
|
|
1867
|
+
"NumberOfPasswordPrompts=1",
|
|
1868
|
+
"-o",
|
|
1869
|
+
"HostKeyAlgorithms=ssh-ed25519",
|
|
1870
|
+
"-o",
|
|
1871
|
+
"StrictHostKeyChecking=yes",
|
|
1872
|
+
"-o",
|
|
1873
|
+
`UserKnownHostsFile=${knownHosts}`,
|
|
1874
|
+
"-o",
|
|
1875
|
+
"ConnectTimeout=15",
|
|
1876
|
+
source,
|
|
1877
|
+
`${credential.username}@${host}:${destination}`
|
|
1878
|
+
],
|
|
1879
|
+
void 0,
|
|
1880
|
+
{ SSH_ASKPASS: askPass, SSH_ASKPASS_REQUIRE: "force", DISPLAY: "attraccess", ATTRACCESS_SSH_PASSWORD: credential.password }
|
|
1881
|
+
);
|
|
1882
|
+
} finally {
|
|
1883
|
+
await (0, import_promises.rm)(dir, { recursive: true, force: true });
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
save(session, event) {
|
|
1887
|
+
const audit = JSON.parse(session.auditLog);
|
|
1888
|
+
audit.push({ at: (/* @__PURE__ */ new Date()).toISOString(), event });
|
|
1889
|
+
session.auditLog = JSON.stringify(audit.slice(-50));
|
|
1890
|
+
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1891
|
+
return this.sessions.save(session);
|
|
1892
|
+
}
|
|
1893
|
+
};
|
|
1894
|
+
WagoCommissioningService = __decorateClass([
|
|
1895
|
+
(0, import_common2.Injectable)(),
|
|
1896
|
+
__decorateParam(0, (0, import_common2.Inject)(import_plugins_backend_sdk7.PLUGIN_CONTEXT))
|
|
1897
|
+
], WagoCommissioningService);
|
|
1898
|
+
async function scanHostKey(host) {
|
|
1899
|
+
const dir = await (0, import_promises.mkdtemp)((0, import_node_path.join)((0, import_node_os.tmpdir)(), "attraccess-cc100-"));
|
|
1900
|
+
const knownHosts = (0, import_node_path.join)(dir, "known_hosts");
|
|
1901
|
+
try {
|
|
1902
|
+
await (0, import_promises.writeFile)(knownHosts, await scanHostKeys(host), { mode: 384 });
|
|
1903
|
+
return fingerprintFor(knownHosts);
|
|
1904
|
+
} finally {
|
|
1905
|
+
await (0, import_promises.rm)(dir, { recursive: true, force: true });
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
function scanHostKeys(host) {
|
|
1909
|
+
return runProcess("ssh-keyscan", ["-T", "15", "-t", "ed25519", host]);
|
|
1910
|
+
}
|
|
1911
|
+
async function pinnedHostKey(host, expectedFingerprint) {
|
|
1912
|
+
const key = (await scanHostKeys(host)).split("\n").find((line) => line.includes("ssh-ed25519"));
|
|
1913
|
+
if (!key) throw new import_common2.ConflictException("the controller did not provide an Ed25519 SSH host key");
|
|
1914
|
+
const dir = await (0, import_promises.mkdtemp)((0, import_node_path.join)((0, import_node_os.tmpdir)(), "attraccess-cc100-"));
|
|
1915
|
+
const knownHosts = (0, import_node_path.join)(dir, "known_hosts");
|
|
1916
|
+
try {
|
|
1917
|
+
await (0, import_promises.writeFile)(knownHosts, `${key}
|
|
1918
|
+
`, { mode: 384 });
|
|
1919
|
+
if (await fingerprintFor(knownHosts) !== expectedFingerprint) throw new import_common2.ConflictException("controller SSH host key changed after identity confirmation");
|
|
1920
|
+
return `${key}
|
|
1921
|
+
`;
|
|
1922
|
+
} finally {
|
|
1923
|
+
await (0, import_promises.rm)(dir, { recursive: true, force: true });
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
async function fingerprintFor(knownHosts) {
|
|
1927
|
+
const result = (await runProcess("ssh-keygen", ["-lf", knownHosts, "-E", "sha256"])).match(/(SHA256:[A-Za-z0-9+/=]+)/)?.[1];
|
|
1928
|
+
if (!result) throw new import_common2.ConflictException("the controller did not provide a supported SSH host key");
|
|
1929
|
+
return result;
|
|
1930
|
+
}
|
|
1931
|
+
function runProcess(command, args, input, environment) {
|
|
1932
|
+
return new Promise((resolve, reject) => {
|
|
1933
|
+
const child = (0, import_node_child_process.spawn)(command, args, { env: { ...process.env, ...environment }, stdio: ["pipe", "pipe", "pipe"] });
|
|
1934
|
+
let stdout = "";
|
|
1935
|
+
let stderr = "";
|
|
1936
|
+
const timer = setTimeout(() => child.kill(), SSH_TIMEOUT_MS);
|
|
1937
|
+
child.stdout.on("data", (chunk) => stdout += chunk);
|
|
1938
|
+
child.stderr.on("data", (chunk) => stderr += chunk);
|
|
1939
|
+
child.on("error", reject);
|
|
1940
|
+
child.on("close", (code) => {
|
|
1941
|
+
clearTimeout(timer);
|
|
1942
|
+
if (code === 0) resolve(stdout);
|
|
1943
|
+
else reject(new Error(stderr || `${command} exited with status ${code}`));
|
|
1944
|
+
});
|
|
1945
|
+
child.stdin.end(input);
|
|
1946
|
+
});
|
|
1947
|
+
}
|
|
1948
|
+
function isPrivateAddress(host) {
|
|
1949
|
+
const match = host.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
|
|
1950
|
+
if (!match) return false;
|
|
1951
|
+
const octets = match.slice(1).map(Number);
|
|
1952
|
+
if (octets.some((octet) => octet > 255)) return false;
|
|
1953
|
+
return octets[0] === 10 || octets[0] === 192 && octets[1] === 168 || octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31;
|
|
1954
|
+
}
|
|
1955
|
+
function redact(value) {
|
|
1956
|
+
return value.replace(/(?:password|secret|token)=?[^\s]*/gi, "[redacted]");
|
|
1957
|
+
}
|
|
1958
|
+
function isImmutableImage(image) {
|
|
1959
|
+
return /@sha256:[a-f0-9]{64}$/i.test(image);
|
|
1960
|
+
}
|
|
1961
|
+
function isRuntimeArtifactConfigured() {
|
|
1962
|
+
return Boolean(
|
|
1963
|
+
isImmutableImage(configuredRuntimeImage) && configuredRuntimeBundle && configuredRuntimeBundleChecksum && configuredRuntimeBundleSignature
|
|
1964
|
+
);
|
|
1965
|
+
}
|
|
1966
|
+
async function verifyRuntimeBundle() {
|
|
1967
|
+
const directory = await (0, import_promises.mkdtemp)((0, import_node_path.join)((0, import_node_os.tmpdir)(), "attraccess-cc100-runtime-"));
|
|
1968
|
+
const bundlePath = (0, import_node_path.join)(directory, "runtime.tar");
|
|
1969
|
+
const checksumPath = (0, import_node_path.join)(directory, "runtime.tar.sha256");
|
|
1970
|
+
const signaturePath = (0, import_node_path.join)(directory, "runtime.tar.sig");
|
|
1971
|
+
try {
|
|
1972
|
+
await Promise.all([
|
|
1973
|
+
(0, import_promises.copyFile)(configuredRuntimeBundle, bundlePath),
|
|
1974
|
+
(0, import_promises.copyFile)(configuredRuntimeBundleChecksum, checksumPath),
|
|
1975
|
+
(0, import_promises.copyFile)(configuredRuntimeBundleSignature, signaturePath)
|
|
1976
|
+
]);
|
|
1977
|
+
} catch (error) {
|
|
1978
|
+
await (0, import_promises.rm)(directory, { recursive: true, force: true });
|
|
1979
|
+
throw error;
|
|
1980
|
+
}
|
|
1981
|
+
const [bundle, checksum, signature] = await Promise.all([(0, import_promises.readFile)(bundlePath), (0, import_promises.readFile)(checksumPath, "utf8"), (0, import_promises.stat)(signaturePath)]);
|
|
1982
|
+
if (!signature.isFile()) throw new import_common2.ConflictException("CC100 runtime signature is not a file");
|
|
1983
|
+
const digest = (0, import_node_crypto3.createHash)("sha256").update(bundle).digest("hex");
|
|
1984
|
+
if (!new RegExp(`^${digest}\\s+\\*?${escapeRegExp(configuredRuntimeBundle.split("/").pop() ?? "")}\\s*$`, "m").test(checksum)) {
|
|
1985
|
+
throw new import_common2.ConflictException("CC100 runtime bundle checksum does not match");
|
|
1986
|
+
}
|
|
1987
|
+
try {
|
|
1988
|
+
const allowedSigners = (0, import_node_path.join)(directory, "allowed_signers");
|
|
1989
|
+
const publicKey = await (0, import_promises.readFile)((0, import_node_path.join)(__dirname, "signing-public-key.pub"), "utf8");
|
|
1990
|
+
await (0, import_promises.writeFile)(allowedSigners, `${SIGNING_IDENTITY} ${publicKey.trim()}
|
|
1991
|
+
`, { mode: 384 });
|
|
1992
|
+
await runProcess(
|
|
1993
|
+
"ssh-keygen",
|
|
1994
|
+
["-Y", "verify", "-f", allowedSigners, "-I", SIGNING_IDENTITY, "-n", SIGNING_NAMESPACE, "-s", signaturePath],
|
|
1995
|
+
bundle
|
|
1996
|
+
);
|
|
1997
|
+
} finally {
|
|
1998
|
+
await (0, import_promises.rm)((0, import_node_path.join)(directory, "allowed_signers"), { force: true });
|
|
1999
|
+
}
|
|
2000
|
+
return { directory, path: bundlePath };
|
|
2001
|
+
}
|
|
2002
|
+
function escapeRegExp(value) {
|
|
2003
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2004
|
+
}
|
|
2005
|
+
function runtimeImageTag() {
|
|
2006
|
+
return configuredRuntimeImage.split("@", 1)[0];
|
|
2007
|
+
}
|
|
2008
|
+
function isSupportedController(inspection, firmwareBaseline) {
|
|
2009
|
+
if (!inspection.includes('PTXDIST_PLATFORM_NAME="cc100"')) return false;
|
|
2010
|
+
const reportedFirmware = inspection.match(/firmware[^\r\n]*?(\d+(?:\.\d+)+|\d+)/i)?.[1];
|
|
2011
|
+
return !reportedFirmware || reportedFirmware.includes(firmwareBaseline);
|
|
2012
|
+
}
|
|
2013
|
+
function shellQuote(value) {
|
|
2014
|
+
return `'${value.replace(/'/g, `'\\"'\\"'`)}'`;
|
|
2015
|
+
}
|
|
1572
2016
|
|
|
1573
2017
|
// backend/wago.controller.ts
|
|
1574
2018
|
var WagoControllerApi = class {
|
|
1575
|
-
constructor(wago) {
|
|
2019
|
+
constructor(wago, commissioning) {
|
|
1576
2020
|
__publicField(this, "wago", wago);
|
|
2021
|
+
__publicField(this, "commissioning", commissioning);
|
|
1577
2022
|
}
|
|
1578
2023
|
list() {
|
|
1579
2024
|
return this.wago.list();
|
|
@@ -1584,12 +2029,30 @@ var WagoControllerApi = class {
|
|
|
1584
2029
|
setSettings(body) {
|
|
1585
2030
|
return this.wago.setSettings(body?.defaultMqttServerId, body?.operationalPrefix);
|
|
1586
2031
|
}
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
return this.wago.createEnrollment(body?.hardwareId ?? "", body?.mqttServerId, manualCredentials);
|
|
2032
|
+
commissioningSupport() {
|
|
2033
|
+
return this.commissioning.support();
|
|
1590
2034
|
}
|
|
1591
|
-
|
|
1592
|
-
return this.
|
|
2035
|
+
commissioningSessions() {
|
|
2036
|
+
return this.commissioning.list();
|
|
2037
|
+
}
|
|
2038
|
+
createCommissioningSession(body) {
|
|
2039
|
+
if (!body?.mqttServerId) throw new import_common3.BadRequestException("MQTT server is required");
|
|
2040
|
+
return this.commissioning.create({
|
|
2041
|
+
hardwareId: body.hardwareId ?? "",
|
|
2042
|
+
mqttServerId: body.mqttServerId,
|
|
2043
|
+
targetHost: body.targetHost ?? ""
|
|
2044
|
+
});
|
|
2045
|
+
}
|
|
2046
|
+
deliverCommissioningSession(id, body) {
|
|
2047
|
+
return this.commissioning.deliver(id, {
|
|
2048
|
+
hostKeyFingerprint: body?.hostKeyFingerprint ?? "",
|
|
2049
|
+
physicalIdentityConfirmed: body?.physicalIdentityConfirmed === true,
|
|
2050
|
+
codesysStopConfirmed: body?.codesysStopConfirmed === true,
|
|
2051
|
+
temporarySsh: { username: body?.temporarySsh?.username ?? "", password: body?.temporarySsh?.password ?? "" }
|
|
2052
|
+
});
|
|
2053
|
+
}
|
|
2054
|
+
revokeCommissioningSession(id) {
|
|
2055
|
+
return this.commissioning.revoke(id);
|
|
1593
2056
|
}
|
|
1594
2057
|
claim(id, body) {
|
|
1595
2058
|
return this.wago.claim(id, body?.name ?? "", body?.verifier ?? "", body?.mqttServerId);
|
|
@@ -1601,11 +2064,11 @@ var WagoControllerApi = class {
|
|
|
1601
2064
|
return this.wago.presets();
|
|
1602
2065
|
}
|
|
1603
2066
|
previewPreset(id, body) {
|
|
1604
|
-
if (!body?.application) throw new
|
|
2067
|
+
if (!body?.application) throw new import_common3.BadRequestException("application is required");
|
|
1605
2068
|
return this.wago.previewPreset(id, body.application);
|
|
1606
2069
|
}
|
|
1607
2070
|
applyPreset(id, body) {
|
|
1608
|
-
if (!body?.application) throw new
|
|
2071
|
+
if (!body?.application) throw new import_common3.BadRequestException("application is required");
|
|
1609
2072
|
return this.wago.applyPreset(id, body.application, body.selectedPaths ?? [], body.previewedDraftHash ?? "");
|
|
1610
2073
|
}
|
|
1611
2074
|
saveDraft(id, body) {
|
|
@@ -1631,95 +2094,114 @@ var WagoControllerApi = class {
|
|
|
1631
2094
|
}
|
|
1632
2095
|
};
|
|
1633
2096
|
__decorateClass([
|
|
1634
|
-
(0,
|
|
2097
|
+
(0, import_common3.Get)("controllers")
|
|
1635
2098
|
], WagoControllerApi.prototype, "list", 1);
|
|
1636
2099
|
__decorateClass([
|
|
1637
|
-
(0,
|
|
2100
|
+
(0, import_common3.Get)("settings")
|
|
1638
2101
|
], WagoControllerApi.prototype, "settings", 1);
|
|
1639
2102
|
__decorateClass([
|
|
1640
|
-
(0,
|
|
1641
|
-
__decorateParam(0, (0,
|
|
2103
|
+
(0, import_common3.Post)("settings"),
|
|
2104
|
+
__decorateParam(0, (0, import_common3.Body)())
|
|
1642
2105
|
], WagoControllerApi.prototype, "setSettings", 1);
|
|
1643
2106
|
__decorateClass([
|
|
1644
|
-
(0,
|
|
1645
|
-
|
|
1646
|
-
|
|
2107
|
+
(0, import_common3.Get)("commissioning/support")
|
|
2108
|
+
], WagoControllerApi.prototype, "commissioningSupport", 1);
|
|
2109
|
+
__decorateClass([
|
|
2110
|
+
(0, import_common3.Get)("commissioning/sessions")
|
|
2111
|
+
], WagoControllerApi.prototype, "commissioningSessions", 1);
|
|
2112
|
+
__decorateClass([
|
|
2113
|
+
(0, import_common3.Post)("commissioning/sessions"),
|
|
2114
|
+
__decorateParam(0, (0, import_common3.Body)())
|
|
2115
|
+
], WagoControllerApi.prototype, "createCommissioningSession", 1);
|
|
2116
|
+
__decorateClass([
|
|
2117
|
+
(0, import_common3.Post)("commissioning/sessions/:id/deliver"),
|
|
2118
|
+
__decorateParam(0, (0, import_common3.Param)("id", import_common3.ParseIntPipe)),
|
|
2119
|
+
__decorateParam(1, (0, import_common3.Body)())
|
|
2120
|
+
], WagoControllerApi.prototype, "deliverCommissioningSession", 1);
|
|
1647
2121
|
__decorateClass([
|
|
1648
|
-
(0,
|
|
1649
|
-
__decorateParam(0, (0,
|
|
1650
|
-
], WagoControllerApi.prototype, "
|
|
2122
|
+
(0, import_common3.Post)("commissioning/sessions/:id/revoke"),
|
|
2123
|
+
__decorateParam(0, (0, import_common3.Param)("id", import_common3.ParseIntPipe))
|
|
2124
|
+
], WagoControllerApi.prototype, "revokeCommissioningSession", 1);
|
|
1651
2125
|
__decorateClass([
|
|
1652
|
-
(0,
|
|
1653
|
-
__decorateParam(0, (0,
|
|
1654
|
-
__decorateParam(1, (0,
|
|
2126
|
+
(0, import_common3.Post)("controllers/:id/claim"),
|
|
2127
|
+
__decorateParam(0, (0, import_common3.Param)("id", import_common3.ParseIntPipe)),
|
|
2128
|
+
__decorateParam(1, (0, import_common3.Body)())
|
|
1655
2129
|
], WagoControllerApi.prototype, "claim", 1);
|
|
1656
2130
|
__decorateClass([
|
|
1657
|
-
(0,
|
|
1658
|
-
__decorateParam(0, (0,
|
|
2131
|
+
(0, import_common3.Get)("controllers/:id/configuration/draft"),
|
|
2132
|
+
__decorateParam(0, (0, import_common3.Param)("id", import_common3.ParseIntPipe))
|
|
1659
2133
|
], WagoControllerApi.prototype, "draft", 1);
|
|
1660
2134
|
__decorateClass([
|
|
1661
|
-
(0,
|
|
2135
|
+
(0, import_common3.Get)("configuration/presets")
|
|
1662
2136
|
], WagoControllerApi.prototype, "presets", 1);
|
|
1663
2137
|
__decorateClass([
|
|
1664
|
-
(0,
|
|
1665
|
-
__decorateParam(0, (0,
|
|
1666
|
-
__decorateParam(1, (0,
|
|
2138
|
+
(0, import_common3.Post)("controllers/:id/configuration/presets/preview"),
|
|
2139
|
+
__decorateParam(0, (0, import_common3.Param)("id", import_common3.ParseIntPipe)),
|
|
2140
|
+
__decorateParam(1, (0, import_common3.Body)())
|
|
1667
2141
|
], WagoControllerApi.prototype, "previewPreset", 1);
|
|
1668
2142
|
__decorateClass([
|
|
1669
|
-
(0,
|
|
1670
|
-
__decorateParam(0, (0,
|
|
1671
|
-
__decorateParam(1, (0,
|
|
2143
|
+
(0, import_common3.Post)("controllers/:id/configuration/presets/apply"),
|
|
2144
|
+
__decorateParam(0, (0, import_common3.Param)("id", import_common3.ParseIntPipe)),
|
|
2145
|
+
__decorateParam(1, (0, import_common3.Body)())
|
|
1672
2146
|
], WagoControllerApi.prototype, "applyPreset", 1);
|
|
1673
2147
|
__decorateClass([
|
|
1674
|
-
(0,
|
|
1675
|
-
__decorateParam(0, (0,
|
|
1676
|
-
__decorateParam(1, (0,
|
|
2148
|
+
(0, import_common3.Post)("controllers/:id/configuration/draft"),
|
|
2149
|
+
__decorateParam(0, (0, import_common3.Param)("id", import_common3.ParseIntPipe)),
|
|
2150
|
+
__decorateParam(1, (0, import_common3.Body)())
|
|
1677
2151
|
], WagoControllerApi.prototype, "saveDraft", 1);
|
|
1678
2152
|
__decorateClass([
|
|
1679
|
-
(0,
|
|
1680
|
-
__decorateParam(0, (0,
|
|
2153
|
+
(0, import_common3.Post)("controllers/:id/configuration/validate"),
|
|
2154
|
+
__decorateParam(0, (0, import_common3.Param)("id", import_common3.ParseIntPipe))
|
|
1681
2155
|
], WagoControllerApi.prototype, "validateDraft", 1);
|
|
1682
2156
|
__decorateClass([
|
|
1683
|
-
(0,
|
|
1684
|
-
__decorateParam(0, (0,
|
|
2157
|
+
(0, import_common3.Post)("controllers/:id/configuration/review"),
|
|
2158
|
+
__decorateParam(0, (0, import_common3.Param)("id", import_common3.ParseIntPipe))
|
|
1685
2159
|
], WagoControllerApi.prototype, "reviewDraft", 1);
|
|
1686
2160
|
__decorateClass([
|
|
1687
|
-
(0,
|
|
1688
|
-
__decorateParam(0, (0,
|
|
1689
|
-
__decorateParam(1, (0,
|
|
1690
|
-
__decorateParam(2, (0,
|
|
2161
|
+
(0, import_common3.Get)("controllers/:id/configuration/revisions"),
|
|
2162
|
+
__decorateParam(0, (0, import_common3.Param)("id", import_common3.ParseIntPipe)),
|
|
2163
|
+
__decorateParam(1, (0, import_common3.Query)("offset")),
|
|
2164
|
+
__decorateParam(2, (0, import_common3.Query)("limit"))
|
|
1691
2165
|
], WagoControllerApi.prototype, "revisions", 1);
|
|
1692
2166
|
__decorateClass([
|
|
1693
|
-
(0,
|
|
1694
|
-
__decorateParam(0, (0,
|
|
2167
|
+
(0, import_common3.Post)("controllers/:id/configuration/publish"),
|
|
2168
|
+
__decorateParam(0, (0, import_common3.Param)("id", import_common3.ParseIntPipe))
|
|
1695
2169
|
], WagoControllerApi.prototype, "publishDraft", 1);
|
|
1696
2170
|
__decorateClass([
|
|
1697
|
-
(0,
|
|
1698
|
-
__decorateParam(0, (0,
|
|
1699
|
-
__decorateParam(1, (0,
|
|
2171
|
+
(0, import_common3.Post)("controllers/:id/configuration/rollback/:revision"),
|
|
2172
|
+
__decorateParam(0, (0, import_common3.Param)("id", import_common3.ParseIntPipe)),
|
|
2173
|
+
__decorateParam(1, (0, import_common3.Param)("revision", import_common3.ParseIntPipe))
|
|
1700
2174
|
], WagoControllerApi.prototype, "rollback", 1);
|
|
1701
2175
|
__decorateClass([
|
|
1702
|
-
(0,
|
|
1703
|
-
__decorateParam(0, (0,
|
|
1704
|
-
__decorateParam(1, (0,
|
|
2176
|
+
(0, import_common3.Get)("controllers/:id/configuration/revisions/:revision/preview"),
|
|
2177
|
+
__decorateParam(0, (0, import_common3.Param)("id", import_common3.ParseIntPipe)),
|
|
2178
|
+
__decorateParam(1, (0, import_common3.Param)("revision", import_common3.ParseIntPipe))
|
|
1705
2179
|
], WagoControllerApi.prototype, "previewRevision", 1);
|
|
1706
2180
|
WagoControllerApi = __decorateClass([
|
|
1707
|
-
(0,
|
|
1708
|
-
(0,
|
|
1709
|
-
__decorateParam(0, (0,
|
|
2181
|
+
(0, import_plugins_backend_sdk8.Auth)("system.settings.manage"),
|
|
2182
|
+
(0, import_common3.Controller)("wago"),
|
|
2183
|
+
__decorateParam(0, (0, import_common3.Inject)(WagoService)),
|
|
2184
|
+
__decorateParam(1, (0, import_common3.Inject)(WagoCommissioningService))
|
|
1710
2185
|
], WagoControllerApi);
|
|
1711
2186
|
|
|
1712
2187
|
// backend/plugin.ts
|
|
1713
|
-
var
|
|
2188
|
+
var PLUGIN_CONTEXT3 = /* @__PURE__ */ Symbol.for("attraccess.plugin.context");
|
|
1714
2189
|
var WagoPluginModule = class {
|
|
1715
2190
|
};
|
|
1716
2191
|
var plugin = {
|
|
1717
|
-
entities: [
|
|
2192
|
+
entities: [
|
|
2193
|
+
WagoController,
|
|
2194
|
+
WagoSettings,
|
|
2195
|
+
WagoEnrollment,
|
|
2196
|
+
WagoConfigurationDraft,
|
|
2197
|
+
WagoConfigurationRevision,
|
|
2198
|
+
WagoCommissioningSession
|
|
2199
|
+
],
|
|
1718
2200
|
register(context) {
|
|
1719
2201
|
return {
|
|
1720
2202
|
module: WagoPluginModule,
|
|
1721
2203
|
controllers: [WagoControllerApi],
|
|
1722
|
-
providers: [{ provide:
|
|
2204
|
+
providers: [{ provide: PLUGIN_CONTEXT3, useValue: context }, WagoService, WagoCommissioningService]
|
|
1723
2205
|
};
|
|
1724
2206
|
}
|
|
1725
2207
|
};
|