@attraccess/plugin-wago 0.1.0-nightly.1089.1 → 0.1.0-nightly.1106.2
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
CHANGED
|
@@ -957,10 +957,10 @@ function sameRuntimeImage(left, right) {
|
|
|
957
957
|
const digest = left.image.split("@")[1];
|
|
958
958
|
return Boolean(digest) && digest === right.image.split("@")[1];
|
|
959
959
|
}
|
|
960
|
-
var
|
|
960
|
+
var import_common12, import_node_path, WagoBuildRuntimeCatalog;
|
|
961
961
|
var init_build = __esm({
|
|
962
962
|
"backend/runtime/artifacts/build.ts"() {
|
|
963
|
-
|
|
963
|
+
import_common12 = require("@nestjs/common");
|
|
964
964
|
import_node_path = require("node:path");
|
|
965
965
|
init_catalog();
|
|
966
966
|
init_verification();
|
|
@@ -976,7 +976,7 @@ var init_build = __esm({
|
|
|
976
976
|
}
|
|
977
977
|
async import(upload) {
|
|
978
978
|
for (const stream of Object.values(upload)) stream.destroy();
|
|
979
|
-
throw new
|
|
979
|
+
throw new import_common12.ConflictException("The deployed server build owns the CC100 runtime. Deploy a new build to change it.");
|
|
980
980
|
}
|
|
981
981
|
current() {
|
|
982
982
|
if (!this.build) {
|
|
@@ -992,7 +992,7 @@ var init_build = __esm({
|
|
|
992
992
|
}
|
|
993
993
|
async acquire(digest) {
|
|
994
994
|
const build = await this.current();
|
|
995
|
-
if (digest && digest !== build.digest) throw new
|
|
995
|
+
if (digest && digest !== build.digest) throw new import_common12.ConflictException("Runtime does not belong to this server build.");
|
|
996
996
|
return Object.freeze({ ...await super.acquire(build.digest), ...build });
|
|
997
997
|
}
|
|
998
998
|
async loadBuild() {
|
|
@@ -1097,7 +1097,7 @@ async function writeArtifactStream(path, source, limit) {
|
|
|
1097
1097
|
write(chunk, _encoding, callback) {
|
|
1098
1098
|
bytes += chunk.length;
|
|
1099
1099
|
if (bytes > limit) {
|
|
1100
|
-
callback(new
|
|
1100
|
+
callback(new import_common13.BadRequestException("Runtime artifact upload exceeds the size limit"));
|
|
1101
1101
|
return;
|
|
1102
1102
|
}
|
|
1103
1103
|
const write = async () => {
|
|
@@ -1115,10 +1115,10 @@ async function writeArtifactStream(path, source, limit) {
|
|
|
1115
1115
|
}
|
|
1116
1116
|
return bytes;
|
|
1117
1117
|
}
|
|
1118
|
-
var
|
|
1118
|
+
var import_common13, import_node_crypto9, import_node_fs, import_promises, import_node_os, import_node_path2, import_node_stream, import_promises2, digestPattern, hostId, processId, uuidPattern, temporaryPattern, WagoRuntimeArtifactCatalog, WagoRuntimeArtifactsService;
|
|
1119
1119
|
var init_catalog = __esm({
|
|
1120
1120
|
"backend/runtime/artifacts/catalog.ts"() {
|
|
1121
|
-
|
|
1121
|
+
import_common13 = require("@nestjs/common");
|
|
1122
1122
|
import_node_crypto9 = require("node:crypto");
|
|
1123
1123
|
import_node_fs = require("node:fs");
|
|
1124
1124
|
import_promises = __toESM(require("node:fs/promises"));
|
|
@@ -1147,7 +1147,7 @@ var init_catalog = __esm({
|
|
|
1147
1147
|
async acquire(digest) {
|
|
1148
1148
|
const selected = digest ?? (await this.current())?.digest;
|
|
1149
1149
|
if (!selected || !digestPattern.test(selected))
|
|
1150
|
-
throw new
|
|
1150
|
+
throw new import_common13.ConflictException("Import a verified runtime release first");
|
|
1151
1151
|
const root = await this.root();
|
|
1152
1152
|
await this.verifiedMetadata(root, selected);
|
|
1153
1153
|
const directory = await this.createTemporaryDirectory((0, import_node_path2.join)(root, "snapshots"), "delivery");
|
|
@@ -1191,7 +1191,7 @@ var init_catalog = __esm({
|
|
|
1191
1191
|
return result;
|
|
1192
1192
|
}
|
|
1193
1193
|
async get(digest) {
|
|
1194
|
-
if (!digestPattern.test(digest)) throw new
|
|
1194
|
+
if (!digestPattern.test(digest)) throw new import_common13.ConflictException("Select a verified runtime release.");
|
|
1195
1195
|
return this.verifiedMetadata(await this.root(), digest);
|
|
1196
1196
|
}
|
|
1197
1197
|
async current() {
|
|
@@ -1209,7 +1209,7 @@ var init_catalog = __esm({
|
|
|
1209
1209
|
async ingest(upload, publish) {
|
|
1210
1210
|
if (this.activeImports >= 2) {
|
|
1211
1211
|
for (const source of Object.values(upload)) source.destroy();
|
|
1212
|
-
throw new
|
|
1212
|
+
throw new import_common13.ConflictException("Another runtime import is in progress; retry shortly");
|
|
1213
1213
|
}
|
|
1214
1214
|
this.activeImports++;
|
|
1215
1215
|
let directory;
|
|
@@ -1266,8 +1266,8 @@ var init_catalog = __esm({
|
|
|
1266
1266
|
}
|
|
1267
1267
|
return metadata2;
|
|
1268
1268
|
} catch (error) {
|
|
1269
|
-
if (error instanceof
|
|
1270
|
-
throw new
|
|
1269
|
+
if (error instanceof import_common13.ConflictException) throw error;
|
|
1270
|
+
throw new import_common13.BadRequestException(
|
|
1271
1271
|
"Runtime import failed. Check the release files, size, and manifest compatibility."
|
|
1272
1272
|
);
|
|
1273
1273
|
} finally {
|
|
@@ -1456,16 +1456,16 @@ var init_catalog = __esm({
|
|
|
1456
1456
|
return current ? [current] : [];
|
|
1457
1457
|
}
|
|
1458
1458
|
async acquire(digest) {
|
|
1459
|
-
if (!await this.current()) throw new
|
|
1459
|
+
if (!await this.current()) throw new import_common13.ConflictException("Build and install the bundled CC100 runtime first.");
|
|
1460
1460
|
return (await this.buildCatalog()).acquire(digest);
|
|
1461
1461
|
}
|
|
1462
1462
|
async import(upload) {
|
|
1463
1463
|
for (const stream of Object.values(upload)) stream.destroy();
|
|
1464
|
-
throw new
|
|
1464
|
+
throw new import_common13.ConflictException("The server build owns the CC100 runtime. Custom runtime imports are not supported.");
|
|
1465
1465
|
}
|
|
1466
1466
|
};
|
|
1467
1467
|
WagoRuntimeArtifactsService = __decorateClass([
|
|
1468
|
-
(0,
|
|
1468
|
+
(0, import_common13.Injectable)()
|
|
1469
1469
|
], WagoRuntimeArtifactsService);
|
|
1470
1470
|
}
|
|
1471
1471
|
});
|
|
@@ -1479,7 +1479,7 @@ module.exports = __toCommonJS(plugin_exports);
|
|
|
1479
1479
|
|
|
1480
1480
|
// backend/controllers/controller.ts
|
|
1481
1481
|
var import_plugins_backend_sdk16 = require("@attraccess/plugins-backend-sdk");
|
|
1482
|
-
var
|
|
1482
|
+
var import_common31 = require("@nestjs/common");
|
|
1483
1483
|
|
|
1484
1484
|
// backend/audit/index.ts
|
|
1485
1485
|
var import_common = require("@nestjs/common");
|
|
@@ -1542,6 +1542,17 @@ function parseMeasurement(value) {
|
|
|
1542
1542
|
};
|
|
1543
1543
|
}
|
|
1544
1544
|
|
|
1545
|
+
// modbus/channel-enabled.ts
|
|
1546
|
+
var DISABLEABLE_CAPABILITIES = /* @__PURE__ */ new Set(["input", "measurement"]);
|
|
1547
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1548
|
+
function canDisableChannel(capabilities, point) {
|
|
1549
|
+
const list = [...capabilities];
|
|
1550
|
+
const binding = isRecord(point) && isRecord(point.modbus) ? point.modbus : void 0;
|
|
1551
|
+
return Boolean(
|
|
1552
|
+
isRecord(point) && point.hardwareProfile === "modbus" && binding && typeof binding.measurementId === "string" && binding.measurementId && binding.actionId === void 0 && list.length && list.every((capability) => typeof capability === "string" && DISABLEABLE_CAPABILITIES.has(capability))
|
|
1553
|
+
);
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1545
1556
|
// modbus/model.ts
|
|
1546
1557
|
var import_ipaddr = __toESM(require_ipaddr());
|
|
1547
1558
|
|
|
@@ -1695,6 +1706,19 @@ var base = {
|
|
|
1695
1706
|
};
|
|
1696
1707
|
var field = (id, name, address, unit, section, scale = 1, kind = "live") => ({ ...base, id, name, address, unit, section, scale, kind });
|
|
1697
1708
|
var information = (id, name, address, dataType = "uint16", extra = {}) => ({ ...field(id, name, address, "number", "information"), dataType, ...extra });
|
|
1709
|
+
var WAGO_879_3020_DEFAULT_ENABLED = Object.freeze([
|
|
1710
|
+
"active-power",
|
|
1711
|
+
"import-energy",
|
|
1712
|
+
"export-energy",
|
|
1713
|
+
"voltage-l1",
|
|
1714
|
+
"voltage-l2",
|
|
1715
|
+
"voltage-l3",
|
|
1716
|
+
"current-l1",
|
|
1717
|
+
"current-l2",
|
|
1718
|
+
"current-l3",
|
|
1719
|
+
"frequency",
|
|
1720
|
+
"current"
|
|
1721
|
+
]);
|
|
1698
1722
|
function wago8793020Measurements() {
|
|
1699
1723
|
const original = [
|
|
1700
1724
|
field("active-power", "Active power", 20498, "watt", "electrical", 1e3),
|
|
@@ -2132,18 +2156,40 @@ function isNullableString(value) {
|
|
|
2132
2156
|
function operationalWildcardTopic(prefix) {
|
|
2133
2157
|
return `${normalizeOperationalPrefix(prefix)}/v${CONFIGURATION_PROTOCOL_VERSION}/controllers/+/#`;
|
|
2134
2158
|
}
|
|
2135
|
-
|
|
2136
|
-
|
|
2159
|
+
var CONTROLLER_PAYLOAD_MAX_BYTES = 64 * 1024;
|
|
2160
|
+
var CONTROLLER_PAYLOAD_MAX_DEPTH = 8;
|
|
2161
|
+
function exceedsJsonDepth(text, maxDepth) {
|
|
2162
|
+
let depth = 0;
|
|
2163
|
+
let inString = false;
|
|
2164
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
2165
|
+
const char = text[index];
|
|
2166
|
+
if (inString) {
|
|
2167
|
+
if (char === "\\") index += 1;
|
|
2168
|
+
else if (char === '"') inString = false;
|
|
2169
|
+
} else if (char === '"') inString = true;
|
|
2170
|
+
else if (char === "{" || char === "[") {
|
|
2171
|
+
depth += 1;
|
|
2172
|
+
if (depth > maxDepth) return true;
|
|
2173
|
+
} else if (char === "}" || char === "]") depth -= 1;
|
|
2174
|
+
}
|
|
2175
|
+
return false;
|
|
2176
|
+
}
|
|
2177
|
+
function parseControllerJson(payload, label) {
|
|
2178
|
+
if (payload.byteLength > CONTROLLER_PAYLOAD_MAX_BYTES) throw new Error(`${label} is too large`);
|
|
2179
|
+
const text = payload.toString("utf8");
|
|
2180
|
+
if (exceedsJsonDepth(text, CONTROLLER_PAYLOAD_MAX_DEPTH)) throw new Error(`${label} is nested too deeply`);
|
|
2137
2181
|
try {
|
|
2138
|
-
|
|
2182
|
+
return JSON.parse(text);
|
|
2139
2183
|
} catch {
|
|
2140
2184
|
throw new Error(`${label} is not valid JSON`);
|
|
2141
2185
|
}
|
|
2186
|
+
}
|
|
2187
|
+
function parseObject(payload, label) {
|
|
2188
|
+
const value = parseControllerJson(payload, label);
|
|
2142
2189
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`);
|
|
2143
2190
|
return value;
|
|
2144
2191
|
}
|
|
2145
|
-
function
|
|
2146
|
-
const input = parseObject(payload, "announcement");
|
|
2192
|
+
function heartbeatFromObject(input) {
|
|
2147
2193
|
const required = ["hardwareId", "protocolVersion", "runtimeVersion"];
|
|
2148
2194
|
for (const key of required)
|
|
2149
2195
|
if (typeof input[key] !== "string" || !input[key].trim()) throw new Error(`announcement ${key} is required`);
|
|
@@ -2168,8 +2214,8 @@ function parseHeartbeat(payload) {
|
|
|
2168
2214
|
};
|
|
2169
2215
|
}
|
|
2170
2216
|
function parseAnnouncement(payload) {
|
|
2171
|
-
const
|
|
2172
|
-
const
|
|
2217
|
+
const input = parseObject(payload, "announcement");
|
|
2218
|
+
const heartbeat = heartbeatFromObject(input);
|
|
2173
2219
|
if (typeof input.pairingCode !== "string" || !input.pairingCode.trim())
|
|
2174
2220
|
throw new Error("announcement pairingCode is required");
|
|
2175
2221
|
return { ...heartbeat, pairingCode: input.pairingCode.trim() };
|
|
@@ -2367,6 +2413,7 @@ function validateFeedback(value, path, capabilities, currentChannelId, channelsB
|
|
|
2367
2413
|
code: "invalid_feedback_channel",
|
|
2368
2414
|
message: "feedback must reference a distinct input channel"
|
|
2369
2415
|
});
|
|
2416
|
+
if (feedbackChannel?.enabled === false) errors.push(disabledReferenceError(`${path}.channelId`));
|
|
2370
2417
|
if (!["match", "inverse"].includes(value.expected))
|
|
2371
2418
|
errors.push({ path: `${path}.expected`, code: "unsupported_value", message: "expected must be match or inverse" });
|
|
2372
2419
|
if (!Number.isSafeInteger(value.timeoutMs) || value.timeoutMs <= 0)
|
|
@@ -2378,17 +2425,35 @@ function validateFeedback(value, path, capabilities, currentChannelId, channelsB
|
|
|
2378
2425
|
if (!capabilities.has("feedback"))
|
|
2379
2426
|
errors.push({ path, code: "unsupported_field", message: "feedback requires feedback capability" });
|
|
2380
2427
|
}
|
|
2381
|
-
function validateGuard(value, path, capabilities, channelIds, errors) {
|
|
2428
|
+
function validateGuard(value, path, capabilities, channelIds, errors, disabledChannelIds = /* @__PURE__ */ new Set()) {
|
|
2382
2429
|
if (value === void 0) return;
|
|
2383
2430
|
if (!record(value, path, errors)) return;
|
|
2384
2431
|
exactKeys(value, path, ["channelId", "when"], errors);
|
|
2385
2432
|
if (typeof value.channelId !== "string" || !channelIds.has(value.channelId))
|
|
2386
2433
|
errors.push(referenceError(`${path}.channelId`, "logical channel", value.channelId));
|
|
2434
|
+
else if (disabledChannelIds.has(value.channelId)) errors.push(disabledReferenceError(`${path}.channelId`));
|
|
2387
2435
|
if (!["on", "off"].includes(value.when))
|
|
2388
2436
|
errors.push({ path: `${path}.when`, code: "unsupported_value", message: "when must be on or off" });
|
|
2389
2437
|
if (!capabilities.has("guard"))
|
|
2390
2438
|
errors.push({ path, code: "unsupported_field", message: "guard requires guard capability" });
|
|
2391
2439
|
}
|
|
2440
|
+
function disabledReferenceError(path) {
|
|
2441
|
+
return { path, code: "disabled_reference", message: "a guard or feedback cannot use a disabled channel" };
|
|
2442
|
+
}
|
|
2443
|
+
function validateEnabled(channel, path, capabilities, pointsById, errors) {
|
|
2444
|
+
if (channel.enabled === void 0) return;
|
|
2445
|
+
if (channel.enabled !== false) {
|
|
2446
|
+
errors.push({ path: `${path}.enabled`, code: "invalid_enabled", message: "enabled must be false or omitted" });
|
|
2447
|
+
return;
|
|
2448
|
+
}
|
|
2449
|
+
const point = typeof channel.physicalPointId === "string" ? pointsById.get(channel.physicalPointId) : void 0;
|
|
2450
|
+
if (!canDisableChannel(capabilities, point))
|
|
2451
|
+
errors.push({
|
|
2452
|
+
path: `${path}.enabled`,
|
|
2453
|
+
code: "invalid_enabled",
|
|
2454
|
+
message: "only Modbus measurement channels without an action can be disabled"
|
|
2455
|
+
});
|
|
2456
|
+
}
|
|
2392
2457
|
function validateMeasurement(value, path, capabilities, errors) {
|
|
2393
2458
|
if (value === void 0) return;
|
|
2394
2459
|
if (!record(value, path, errors)) return;
|
|
@@ -2453,7 +2518,7 @@ function collection(value, path, errors) {
|
|
|
2453
2518
|
function configurationHash(snapshot) {
|
|
2454
2519
|
return (0, import_node_crypto.createHash)("sha256").update(canonicalSnapshot(snapshot)).digest("hex");
|
|
2455
2520
|
}
|
|
2456
|
-
function
|
|
2521
|
+
function isRecord2(value) {
|
|
2457
2522
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2458
2523
|
}
|
|
2459
2524
|
function diffValue(path, previous, current, changes) {
|
|
@@ -2464,7 +2529,7 @@ function diffValue(path, previous, current, changes) {
|
|
|
2464
2529
|
diffValue(`${path}[${index}]`, previous[index], current[index], changes);
|
|
2465
2530
|
return;
|
|
2466
2531
|
}
|
|
2467
|
-
if (
|
|
2532
|
+
if (isRecord2(previous) && isRecord2(current)) {
|
|
2468
2533
|
const keys = /* @__PURE__ */ new Set([...Object.keys(previous), ...Object.keys(current)]);
|
|
2469
2534
|
for (const key of [...keys].sort()) diffValue(`${path}.${key}`, previous[key], current[key], changes);
|
|
2470
2535
|
return;
|
|
@@ -2494,7 +2559,7 @@ function exactKeys(value, path, allowed, errors, optional = []) {
|
|
|
2494
2559
|
);
|
|
2495
2560
|
}
|
|
2496
2561
|
function isConfigurationValidationError(value) {
|
|
2497
|
-
return
|
|
2562
|
+
return isRecord2(value) && typeof value.path === "string" && Boolean(value.path.trim()) && typeof value.code === "string" && Boolean(value.code.trim()) && typeof value.message === "string" && Boolean(value.message.trim());
|
|
2498
2563
|
}
|
|
2499
2564
|
function parseConfigurationReport(value) {
|
|
2500
2565
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
@@ -2554,11 +2619,14 @@ function validateSnapshot(snapshot) {
|
|
|
2554
2619
|
const pointIds = /* @__PURE__ */ new Set();
|
|
2555
2620
|
const channelIds = /* @__PURE__ */ new Set();
|
|
2556
2621
|
const channelsById = /* @__PURE__ */ new Map();
|
|
2622
|
+
const pointsById = /* @__PURE__ */ new Map();
|
|
2623
|
+
const disabledChannelIds = /* @__PURE__ */ new Set();
|
|
2557
2624
|
points.forEach((point, index) => {
|
|
2558
2625
|
const path = `physicalPoints[${index}]`;
|
|
2559
2626
|
if (!record(point, path, errors)) return;
|
|
2560
2627
|
exactKeys(point, path, ["id", "hardwareProfile", "channel", "modbus"], errors, ["modbus"]);
|
|
2561
2628
|
addId(point.id, `${path}.id`, pointIds, errors);
|
|
2629
|
+
if (typeof point.id === "string" && !pointsById.has(point.id)) pointsById.set(point.id, point);
|
|
2562
2630
|
enumValue(point.hardwareProfile, `${path}.hardwareProfile`, HARDWARE_PROFILES, errors);
|
|
2563
2631
|
if (typeof point.channel !== "number" || !Number.isSafeInteger(point.channel) || point.channel < 0)
|
|
2564
2632
|
errors.push({
|
|
@@ -2572,6 +2640,7 @@ function validateSnapshot(snapshot) {
|
|
|
2572
2640
|
const item = channel;
|
|
2573
2641
|
addId(item.id, `logicalChannels[${index}].id`, channelIds, errors);
|
|
2574
2642
|
if (typeof item.id === "string") channelsById.set(item.id, item);
|
|
2643
|
+
if (typeof item.id === "string" && item.enabled === false) disabledChannelIds.add(item.id);
|
|
2575
2644
|
}
|
|
2576
2645
|
});
|
|
2577
2646
|
channels.forEach((channel, index) => {
|
|
@@ -2585,6 +2654,7 @@ function validateSnapshot(snapshot) {
|
|
|
2585
2654
|
"physicalPointId",
|
|
2586
2655
|
"profile",
|
|
2587
2656
|
"capabilities",
|
|
2657
|
+
"enabled",
|
|
2588
2658
|
"invert",
|
|
2589
2659
|
"disconnectPolicy",
|
|
2590
2660
|
"range",
|
|
@@ -2593,7 +2663,8 @@ function validateSnapshot(snapshot) {
|
|
|
2593
2663
|
"feedback",
|
|
2594
2664
|
"measurement"
|
|
2595
2665
|
],
|
|
2596
|
-
errors
|
|
2666
|
+
errors,
|
|
2667
|
+
["enabled"]
|
|
2597
2668
|
);
|
|
2598
2669
|
if (typeof channel.physicalPointId !== "string" || !pointIds.has(channel.physicalPointId))
|
|
2599
2670
|
errors.push(referenceError(`${path}.physicalPointId`, "physical point", channel.physicalPointId));
|
|
@@ -2605,10 +2676,11 @@ function validateSnapshot(snapshot) {
|
|
|
2605
2676
|
code: "invalid_invert",
|
|
2606
2677
|
message: "invert requires a boolean and an input channel"
|
|
2607
2678
|
});
|
|
2679
|
+
validateEnabled(channel, path, capabilities, pointsById, errors);
|
|
2608
2680
|
validateDisconnectPolicy(channel.disconnectPolicy, `${path}.disconnectPolicy`, errors);
|
|
2609
2681
|
validateRange(channel.range, `${path}.range`, capabilities, errors);
|
|
2610
2682
|
validatePulse(channel.pulse, `${path}.pulse`, capabilities, errors);
|
|
2611
|
-
validateGuard(channel.guard, `${path}.guard`, capabilities, channelIds, errors);
|
|
2683
|
+
validateGuard(channel.guard, `${path}.guard`, capabilities, channelIds, errors, disabledChannelIds);
|
|
2612
2684
|
validateFeedback(channel.feedback, `${path}.feedback`, capabilities, channel.id, channelsById, errors);
|
|
2613
2685
|
validateMeasurement(channel.measurement, `${path}.measurement`, capabilities, errors);
|
|
2614
2686
|
});
|
|
@@ -2990,12 +3062,12 @@ function parseManagementInspection(output) {
|
|
|
2990
3062
|
}
|
|
2991
3063
|
|
|
2992
3064
|
// backend/commissioning/service.ts
|
|
2993
|
-
var
|
|
3065
|
+
var import_common29 = require("@nestjs/common");
|
|
2994
3066
|
var import_promises9 = require("node:fs/promises");
|
|
2995
3067
|
var import_plugins_backend_sdk14 = require("@attraccess/plugins-backend-sdk");
|
|
2996
3068
|
|
|
2997
3069
|
// backend/controllers/service.ts
|
|
2998
|
-
var
|
|
3070
|
+
var import_common10 = require("@nestjs/common");
|
|
2999
3071
|
|
|
3000
3072
|
// backend/controllers/model.ts
|
|
3001
3073
|
var import_common3 = require("@nestjs/common");
|
|
@@ -3046,7 +3118,7 @@ function safeEqual(left, right) {
|
|
|
3046
3118
|
}
|
|
3047
3119
|
function isClaimAcknowledgement(payload, token) {
|
|
3048
3120
|
try {
|
|
3049
|
-
const value =
|
|
3121
|
+
const value = parseObject(payload, "claim acknowledgement");
|
|
3050
3122
|
return typeof value.acknowledgementToken === "string" && safeEqual(value.acknowledgementToken, token);
|
|
3051
3123
|
} catch {
|
|
3052
3124
|
return false;
|
|
@@ -3073,6 +3145,11 @@ var MqttSubscriptionError = class extends Error {
|
|
|
3073
3145
|
var WagoCredentialOperationUncertainError = class extends import_common3.ConflictException {
|
|
3074
3146
|
};
|
|
3075
3147
|
|
|
3148
|
+
// shared/capabilities.ts
|
|
3149
|
+
var BOUNDED_MESSAGES_CAPABILITY = "bounded-messages-v1";
|
|
3150
|
+
var DROP_REASONS = ["oversized", "overload"];
|
|
3151
|
+
var CHANNEL_ENABLED_CAPABILITY = "channel-enabled-v1";
|
|
3152
|
+
|
|
3076
3153
|
// shared/clock.ts
|
|
3077
3154
|
var CONTROLLER_CLOCK_TOLERANCE_MS = 5e3;
|
|
3078
3155
|
|
|
@@ -3135,6 +3212,147 @@ function admitEnvelope(state, data, category, now) {
|
|
|
3135
3212
|
return restart ? "restart" : "accepted";
|
|
3136
3213
|
}
|
|
3137
3214
|
|
|
3215
|
+
// shared/configuration-limits.ts
|
|
3216
|
+
var CONFIGURATION_LIMIT_CODE = "configuration_limit_exceeded";
|
|
3217
|
+
var MAX_CONFIGURATION_BYTES = 1024 * 1024;
|
|
3218
|
+
var DESIRED_ENVELOPE_RESERVE_BYTES = 1024;
|
|
3219
|
+
var MAX_CONFIGURATION_DEPTH = 16;
|
|
3220
|
+
var MAX_IDENTIFIER_CHARS = 256;
|
|
3221
|
+
var MAX_CONFIGURATION_ITEMS = 2048;
|
|
3222
|
+
var MAX_ENABLED_CHANNELS = 256;
|
|
3223
|
+
var MAX_STATUS_REPORT_BYTES = 64 * 1024;
|
|
3224
|
+
var STATUS_ERROR_BUDGET_BYTES = 8 * 1024;
|
|
3225
|
+
var STATUS_FIXED_BYTES = 1024;
|
|
3226
|
+
var encoder = new TextEncoder();
|
|
3227
|
+
var CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f]/;
|
|
3228
|
+
var IDENTIFIER_KEY = /^(?:id|[A-Za-z]*Id)$/;
|
|
3229
|
+
var byteLength = (value) => encoder.encode(JSON.stringify(value)).byteLength;
|
|
3230
|
+
function isRecord3(value) {
|
|
3231
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
3232
|
+
}
|
|
3233
|
+
function depthExceeds(root, max) {
|
|
3234
|
+
const stack = [[root, 1]];
|
|
3235
|
+
while (stack.length) {
|
|
3236
|
+
const [value, depth] = stack.pop();
|
|
3237
|
+
if (!value || typeof value !== "object") continue;
|
|
3238
|
+
if (depth > max) return true;
|
|
3239
|
+
for (const child of Array.isArray(value) ? value : Object.values(value)) stack.push([child, depth + 1]);
|
|
3240
|
+
}
|
|
3241
|
+
return false;
|
|
3242
|
+
}
|
|
3243
|
+
function identifierViolation(root) {
|
|
3244
|
+
const stack = [[root, "$"]];
|
|
3245
|
+
while (stack.length) {
|
|
3246
|
+
const [value, path] = stack.pop();
|
|
3247
|
+
if (Array.isArray(value)) value.forEach((item, index) => stack.push([item, `${path}[${index}]`]));
|
|
3248
|
+
else if (isRecord3(value))
|
|
3249
|
+
for (const [key, item] of Object.entries(value)) {
|
|
3250
|
+
if (typeof item === "string" && IDENTIFIER_KEY.test(key)) {
|
|
3251
|
+
if (item.length > MAX_IDENTIFIER_CHARS)
|
|
3252
|
+
return { limit: "identifier", path: `${path}.${key}`, reason: "length", max: MAX_IDENTIFIER_CHARS };
|
|
3253
|
+
if (CONTROL_CHARACTERS.test(item))
|
|
3254
|
+
return { limit: "identifier", path: `${path}.${key}`, reason: "control", max: MAX_IDENTIFIER_CHARS };
|
|
3255
|
+
}
|
|
3256
|
+
stack.push([item, `${path}.${key}`]);
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
return void 0;
|
|
3260
|
+
}
|
|
3261
|
+
function channelEnabled(channel) {
|
|
3262
|
+
return !isRecord3(channel) || channel.enabled !== false;
|
|
3263
|
+
}
|
|
3264
|
+
var capabilitiesOf = (channel) => Array.isArray(channel.capabilities) ? channel.capabilities : [];
|
|
3265
|
+
function worstCaseStatusReportBytes(snapshot) {
|
|
3266
|
+
const pointIsModbus = /* @__PURE__ */ new Map();
|
|
3267
|
+
for (const point of Array.isArray(snapshot.physicalPoints) ? snapshot.physicalPoints : [])
|
|
3268
|
+
if (isRecord3(point) && typeof point.id === "string" && !pointIsModbus.has(point.id))
|
|
3269
|
+
pointIsModbus.set(point.id, Boolean(point.modbus));
|
|
3270
|
+
let total = STATUS_FIXED_BYTES + STATUS_ERROR_BUDGET_BYTES;
|
|
3271
|
+
for (const channel of Array.isArray(snapshot.logicalChannels) ? snapshot.logicalChannels : []) {
|
|
3272
|
+
if (!isRecord3(channel) || !channelEnabled(channel)) continue;
|
|
3273
|
+
const capabilities = capabilitiesOf(channel);
|
|
3274
|
+
const output = capabilities.includes("output");
|
|
3275
|
+
const digitalInput = !output && capabilities.includes("input") && !(typeof channel.physicalPointId === "string" && pointIsModbus.get(channel.physicalPointId));
|
|
3276
|
+
if (!output && !digitalInput) continue;
|
|
3277
|
+
const id = typeof channel.id === "string" ? channel.id : "";
|
|
3278
|
+
const idBytes = encoder.encode(JSON.stringify(id)).byteLength;
|
|
3279
|
+
total += output ? (idBytes + 7) * 2 + idBytes + 1 : idBytes + 7;
|
|
3280
|
+
}
|
|
3281
|
+
return total;
|
|
3282
|
+
}
|
|
3283
|
+
function configurationLimitViolations(snapshot) {
|
|
3284
|
+
const violations = [];
|
|
3285
|
+
const bytes = (() => {
|
|
3286
|
+
try {
|
|
3287
|
+
return byteLength(snapshot) + DESIRED_ENVELOPE_RESERVE_BYTES;
|
|
3288
|
+
} catch {
|
|
3289
|
+
return Infinity;
|
|
3290
|
+
}
|
|
3291
|
+
})();
|
|
3292
|
+
if (bytes > MAX_CONFIGURATION_BYTES) {
|
|
3293
|
+
violations.push({ limit: "bytes", actual: bytes, max: MAX_CONFIGURATION_BYTES });
|
|
3294
|
+
return violations;
|
|
3295
|
+
}
|
|
3296
|
+
if (depthExceeds(snapshot, MAX_CONFIGURATION_DEPTH)) {
|
|
3297
|
+
violations.push({ limit: "depth", max: MAX_CONFIGURATION_DEPTH });
|
|
3298
|
+
return violations;
|
|
3299
|
+
}
|
|
3300
|
+
const identifier3 = identifierViolation(snapshot);
|
|
3301
|
+
if (identifier3) violations.push(identifier3);
|
|
3302
|
+
if (isRecord3(snapshot)) {
|
|
3303
|
+
for (const [limit, collection3] of [
|
|
3304
|
+
["points", snapshot.physicalPoints],
|
|
3305
|
+
["channels", snapshot.logicalChannels]
|
|
3306
|
+
])
|
|
3307
|
+
if (Array.isArray(collection3) && collection3.length > MAX_CONFIGURATION_ITEMS)
|
|
3308
|
+
violations.push({ limit, actual: collection3.length, max: MAX_CONFIGURATION_ITEMS });
|
|
3309
|
+
if (Array.isArray(snapshot.logicalChannels)) {
|
|
3310
|
+
const enabled = snapshot.logicalChannels.filter(channelEnabled).length;
|
|
3311
|
+
if (enabled > MAX_ENABLED_CHANNELS)
|
|
3312
|
+
violations.push({ limit: "enabled-channels", actual: enabled, max: MAX_ENABLED_CHANNELS });
|
|
3313
|
+
}
|
|
3314
|
+
const report = worstCaseStatusReportBytes(snapshot);
|
|
3315
|
+
if (report > MAX_STATUS_REPORT_BYTES)
|
|
3316
|
+
violations.push({ limit: "status-report", actual: report, max: MAX_STATUS_REPORT_BYTES });
|
|
3317
|
+
}
|
|
3318
|
+
return violations;
|
|
3319
|
+
}
|
|
3320
|
+
var CONFIGURATION_LIMIT_MESSAGES = {
|
|
3321
|
+
bytes: "The configuration is too large to send to the controller (limit 1 MiB). Remove devices, channels or custom profiles.",
|
|
3322
|
+
depth: "The configuration is nested deeper than 16 levels. Simplify custom profiles or remove nested settings.",
|
|
3323
|
+
identifierLength: "An ID is longer than 256 characters. Shorten it.",
|
|
3324
|
+
identifierControl: "An ID contains control characters such as line breaks or tabs. Remove them.",
|
|
3325
|
+
points: "The configuration has more than 2048 physical points. Remove points or devices; each built-in 879-3020 meter adds 131.",
|
|
3326
|
+
channels: "The configuration has more than 2048 channels. Remove channels or devices; each built-in 879-3020 meter adds 131 channels.",
|
|
3327
|
+
enabledChannels: "More than 256 channels are enabled. Disable meter readings you do not need or remove channels; each built-in 879-3020 meter adds 131 channels, of which 11 are enabled by default.",
|
|
3328
|
+
statusReport: "The controller status report could exceed 64 KiB with these digital and output channel IDs. Shorten those IDs or remove channels."
|
|
3329
|
+
};
|
|
3330
|
+
function describeConfigurationLimit(violation) {
|
|
3331
|
+
switch (violation.limit) {
|
|
3332
|
+
case "bytes":
|
|
3333
|
+
return CONFIGURATION_LIMIT_MESSAGES.bytes;
|
|
3334
|
+
case "depth":
|
|
3335
|
+
return CONFIGURATION_LIMIT_MESSAGES.depth;
|
|
3336
|
+
case "identifier":
|
|
3337
|
+
return violation.reason === "length" ? CONFIGURATION_LIMIT_MESSAGES.identifierLength : CONFIGURATION_LIMIT_MESSAGES.identifierControl;
|
|
3338
|
+
case "points":
|
|
3339
|
+
return CONFIGURATION_LIMIT_MESSAGES.points;
|
|
3340
|
+
case "channels":
|
|
3341
|
+
return CONFIGURATION_LIMIT_MESSAGES.channels;
|
|
3342
|
+
case "enabled-channels":
|
|
3343
|
+
return CONFIGURATION_LIMIT_MESSAGES.enabledChannels;
|
|
3344
|
+
case "status-report":
|
|
3345
|
+
return CONFIGURATION_LIMIT_MESSAGES.statusReport;
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3348
|
+
function configurationLimitErrors(snapshot, rootPath = "snapshot") {
|
|
3349
|
+
return configurationLimitViolations(snapshot).map((violation) => ({
|
|
3350
|
+
path: violation.limit === "identifier" ? violation.path.replace(/^\$/, rootPath) : rootPath,
|
|
3351
|
+
code: CONFIGURATION_LIMIT_CODE,
|
|
3352
|
+
message: describeConfigurationLimit(violation)
|
|
3353
|
+
}));
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3138
3356
|
// backend/diagnostics/validation.ts
|
|
3139
3357
|
var fields = new Set(
|
|
3140
3358
|
"snapshot version physicalPoints logicalChannels id physicalPointId hardwareProfile channel profile capabilities disconnectPolicy mode timeoutMs range minimum maximum pulse durationMs guard channelId when feedback expected measurement unit scale offset".split(
|
|
@@ -3145,7 +3363,7 @@ var codes = new Set(
|
|
|
3145
3363
|
"invalid_snapshot unsupported_version invalid_channel invalid_collection invalid_object unknown_field required_field invalid_id duplicate_id unsupported_value invalid_capabilities unsupported_capability duplicate_capability unsupported_field invalid_range invalid_duration invalid_measurement invalid_feedback_channel invalid_timeout missing_capability unknown_reference invalid_reference content_hash_mismatch revision_mismatch hardware_unavailable configuration_commit_failed".split(
|
|
3146
3364
|
" "
|
|
3147
3365
|
)
|
|
3148
|
-
);
|
|
3366
|
+
).add(CONFIGURATION_LIMIT_CODE);
|
|
3149
3367
|
function safeValidationSummaries(value) {
|
|
3150
3368
|
if (!Array.isArray(value)) return [];
|
|
3151
3369
|
return value.slice(0, 50).map((item) => {
|
|
@@ -3161,6 +3379,18 @@ function safeValidationSummaries(value) {
|
|
|
3161
3379
|
}
|
|
3162
3380
|
|
|
3163
3381
|
// backend/diagnostics/store.ts
|
|
3382
|
+
function heartbeatDropCounts(data) {
|
|
3383
|
+
const counts = data.dropCounts;
|
|
3384
|
+
if (!Array.isArray(data.capabilities) || !data.capabilities.includes(BOUNDED_MESSAGES_CAPABILITY) || !counts || typeof counts !== "object" || Array.isArray(counts))
|
|
3385
|
+
return void 0;
|
|
3386
|
+
const result = {};
|
|
3387
|
+
for (const reason of DROP_REASONS) {
|
|
3388
|
+
const value = counts[reason];
|
|
3389
|
+
if (!Number.isSafeInteger(value) || value < 0) return void 0;
|
|
3390
|
+
result[reason] = value;
|
|
3391
|
+
}
|
|
3392
|
+
return result;
|
|
3393
|
+
}
|
|
3164
3394
|
var faultCodes = /* @__PURE__ */ new Set([
|
|
3165
3395
|
"measurement_read_failed",
|
|
3166
3396
|
"device_write_failed",
|
|
@@ -3220,8 +3450,8 @@ var WagoDiagnosticsStore = class {
|
|
|
3220
3450
|
applyEvents(id, state, kind, data, canonical, metadata2) {
|
|
3221
3451
|
const { receivedAt } = metadata2;
|
|
3222
3452
|
if (kind === "measurements" && identifier(data.channelId) && typeof data.value === "number" && Number.isFinite(data.value) && (canonical ? CANONICAL_UNITS : ["ampere", "volt", "watt", "percent"]).includes(data.unit)) {
|
|
3223
|
-
const
|
|
3224
|
-
|
|
3453
|
+
const collection3 = canonical && data.kind === "cumulative" ? state.cumulativeMeasurements : state.measurements;
|
|
3454
|
+
collection3[data.channelId] = {
|
|
3225
3455
|
kind: "measurement",
|
|
3226
3456
|
value: data.value,
|
|
3227
3457
|
unit: data.unit,
|
|
@@ -3334,10 +3564,9 @@ var WagoDiagnosticsStore = class {
|
|
|
3334
3564
|
}
|
|
3335
3565
|
ingest(id, kind, payload) {
|
|
3336
3566
|
this.prune();
|
|
3337
|
-
if (payload.length > 65536) return false;
|
|
3338
3567
|
let data;
|
|
3339
3568
|
try {
|
|
3340
|
-
data =
|
|
3569
|
+
data = parseControllerJson(payload, "diagnostic message");
|
|
3341
3570
|
} catch {
|
|
3342
3571
|
return false;
|
|
3343
3572
|
}
|
|
@@ -3350,7 +3579,7 @@ var WagoDiagnosticsStore = class {
|
|
|
3350
3579
|
if (!this.controllers.has(id) && this.controllers.size >= MAX_CONTROLLERS && oldest === void 0) return false;
|
|
3351
3580
|
if (!this.controllers.has(id) && this.controllers.size >= MAX_CONTROLLERS && oldest !== void 0)
|
|
3352
3581
|
this.controllers.delete(oldest);
|
|
3353
|
-
const state = this.
|
|
3582
|
+
const state = this.working(id);
|
|
3354
3583
|
if (!this.admitIncoming(id, state, kind, data, canonical)) return false;
|
|
3355
3584
|
state.touched = this.now();
|
|
3356
3585
|
const receivedAt = new Date(this.now()).toISOString();
|
|
@@ -3360,10 +3589,13 @@ var WagoDiagnosticsStore = class {
|
|
|
3360
3589
|
streamId: canonical ? state.activeStream : null,
|
|
3361
3590
|
sequence: canonical ? data.sequence : null
|
|
3362
3591
|
};
|
|
3363
|
-
if (kind === "heartbeat")
|
|
3592
|
+
if (kind === "heartbeat") {
|
|
3593
|
+
state.heartbeatAt = canonical ? data.timestamp : receivedAt;
|
|
3594
|
+
state.dropCounts = heartbeatDropCounts(data);
|
|
3595
|
+
}
|
|
3364
3596
|
if (kind === "state") this.applyState(state, data, canonical, metadata2);
|
|
3365
3597
|
this.applyEvents(id, state, kind, data, canonical, metadata2);
|
|
3366
|
-
for (const
|
|
3598
|
+
for (const collection3 of [
|
|
3367
3599
|
state.cumulativeMeasurements,
|
|
3368
3600
|
state.inputs,
|
|
3369
3601
|
state.outputs,
|
|
@@ -3371,7 +3603,7 @@ var WagoDiagnosticsStore = class {
|
|
|
3371
3603
|
state.faults,
|
|
3372
3604
|
state.acknowledgements
|
|
3373
3605
|
]) {
|
|
3374
|
-
while (Object.keys(
|
|
3606
|
+
while (Object.keys(collection3).length > MAX_CHANNELS) delete collection3[Object.keys(collection3)[0]];
|
|
3375
3607
|
}
|
|
3376
3608
|
state.events.push({ kind, receivedAt });
|
|
3377
3609
|
state.events = state.events.slice(-50);
|
|
@@ -3405,10 +3637,18 @@ var WagoDiagnosticsStore = class {
|
|
|
3405
3637
|
if (this.commands.size >= 1024 && oldest !== void 0) this.commands.delete(oldest);
|
|
3406
3638
|
this.commands.set(id, { controllerId, channelId: channelId2, at: this.now() });
|
|
3407
3639
|
}
|
|
3640
|
+
/** The last heartbeat time without copying the whole diagnostics state, as `read(id).heartbeatAt`. */
|
|
3641
|
+
heartbeatAt(id) {
|
|
3642
|
+
this.prune();
|
|
3643
|
+
return this.controllers.get(id)?.heartbeatAt;
|
|
3644
|
+
}
|
|
3408
3645
|
read(id) {
|
|
3409
3646
|
this.prune();
|
|
3410
3647
|
const state = this.controllers.get(id);
|
|
3411
|
-
|
|
3648
|
+
return this.retained(state ? JSON.parse(JSON.stringify(state)) : this.empty());
|
|
3649
|
+
}
|
|
3650
|
+
empty() {
|
|
3651
|
+
return {
|
|
3412
3652
|
touched: 0,
|
|
3413
3653
|
inputs: {},
|
|
3414
3654
|
outputs: {},
|
|
@@ -3419,6 +3659,20 @@ var WagoDiagnosticsStore = class {
|
|
|
3419
3659
|
events: [],
|
|
3420
3660
|
...emptyStream()
|
|
3421
3661
|
};
|
|
3662
|
+
}
|
|
3663
|
+
/**
|
|
3664
|
+
* The copy `ingest` mutates and stores only once the message is admitted. It shares the immutable
|
|
3665
|
+
* sample, fault and acknowledgement records with the stored state instead of deep-copying them on
|
|
3666
|
+
* every message: those records are only ever replaced or deleted, never changed in place. Only the
|
|
3667
|
+
* containers a message can change are copied, so a rejected message still leaves no trace.
|
|
3668
|
+
*/
|
|
3669
|
+
working(id) {
|
|
3670
|
+
const state = this.controllers.get(id);
|
|
3671
|
+
if (!state) return this.retained(this.empty());
|
|
3672
|
+
return this.retained({ ...state, retiredStreams: [...state.retiredStreams], watermarks: { ...state.watermarks } });
|
|
3673
|
+
}
|
|
3674
|
+
/** Copies the per-channel collections (shallowly) and drops everything past the retention window. */
|
|
3675
|
+
retained(copy) {
|
|
3422
3676
|
const cutoff = this.now() - RETENTION_MS;
|
|
3423
3677
|
for (const field3 of [
|
|
3424
3678
|
"inputs",
|
|
@@ -3430,7 +3684,7 @@ var WagoDiagnosticsStore = class {
|
|
|
3430
3684
|
]) {
|
|
3431
3685
|
copy[field3] = Object.assign(/* @__PURE__ */ Object.create(null), copy[field3]);
|
|
3432
3686
|
}
|
|
3433
|
-
for (const
|
|
3687
|
+
for (const collection3 of [
|
|
3434
3688
|
copy.cumulativeMeasurements,
|
|
3435
3689
|
copy.inputs,
|
|
3436
3690
|
copy.outputs,
|
|
@@ -3438,8 +3692,8 @@ var WagoDiagnosticsStore = class {
|
|
|
3438
3692
|
copy.faults,
|
|
3439
3693
|
copy.acknowledgements
|
|
3440
3694
|
]) {
|
|
3441
|
-
for (const key of Object.keys(
|
|
3442
|
-
if (Date.parse(
|
|
3695
|
+
for (const key of Object.keys(collection3))
|
|
3696
|
+
if (Date.parse(collection3[key].receivedAt) < cutoff) delete collection3[key];
|
|
3443
3697
|
}
|
|
3444
3698
|
copy.events = copy.events.filter((event) => Date.parse(event.receivedAt) >= cutoff);
|
|
3445
3699
|
if (copy.rejection && Date.parse(copy.rejection.receivedAt) < cutoff) delete copy.rejection;
|
|
@@ -3452,12 +3706,70 @@ var WagoDiagnosticsStore = class {
|
|
|
3452
3706
|
}
|
|
3453
3707
|
};
|
|
3454
3708
|
|
|
3709
|
+
// backend/configuration/channel-enabled.ts
|
|
3710
|
+
var import_common4 = require("@nestjs/common");
|
|
3711
|
+
var CHANNEL_ENABLED_UNSUPPORTED_CODE = "channel_enabled_unsupported";
|
|
3712
|
+
var CHANNEL_ENABLED_UNSUPPORTED_MESSAGE = "This configuration disables channels, but the CC100 runtime on this controller cannot disable channels. Update the CC100 runtime, or enable every channel before publishing.";
|
|
3713
|
+
var LEGACY_MAX_CONFIGURATION_ITEMS = 256;
|
|
3714
|
+
var LEGACY_RUNTIME_LIMIT_MESSAGES = {
|
|
3715
|
+
points: "The CC100 runtime on this controller is older and accepts at most 256 physical points. Update the CC100 runtime to use the higher limits, or remove points or devices.",
|
|
3716
|
+
channels: "The CC100 runtime on this controller is older and accepts at most 256 channels. Update the CC100 runtime to use the higher limits, or remove channels or devices.",
|
|
3717
|
+
statusReport: "The CC100 runtime on this controller is older and counts every channel ID towards its 64 KiB status report. Update the CC100 runtime to use the higher limits, or shorten channel IDs or remove channels."
|
|
3718
|
+
};
|
|
3719
|
+
function controllerCapabilities(capabilities) {
|
|
3720
|
+
if (Array.isArray(capabilities)) return capabilities;
|
|
3721
|
+
try {
|
|
3722
|
+
const parsed = JSON.parse(capabilities);
|
|
3723
|
+
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
|
|
3724
|
+
} catch {
|
|
3725
|
+
return [];
|
|
3726
|
+
}
|
|
3727
|
+
}
|
|
3728
|
+
var supportsChannelEnabled = (capabilities) => controllerCapabilities(capabilities).includes(CHANNEL_ENABLED_CAPABILITY);
|
|
3729
|
+
function collection2(snapshot, key) {
|
|
3730
|
+
const value = snapshot && typeof snapshot === "object" ? snapshot[key] : void 0;
|
|
3731
|
+
return Array.isArray(value) ? value : [];
|
|
3732
|
+
}
|
|
3733
|
+
function channelEnabledErrors(snapshot, capabilities) {
|
|
3734
|
+
if (collection2(snapshot, "logicalChannels").every(channelEnabled) || supportsChannelEnabled(capabilities)) return [];
|
|
3735
|
+
return [{ path: "$", code: CHANNEL_ENABLED_UNSUPPORTED_CODE, message: CHANNEL_ENABLED_UNSUPPORTED_MESSAGE }];
|
|
3736
|
+
}
|
|
3737
|
+
function legacyWorstCaseStatusReportBytes(snapshot) {
|
|
3738
|
+
const encoder2 = new TextEncoder();
|
|
3739
|
+
let total = STATUS_FIXED_BYTES + STATUS_ERROR_BUDGET_BYTES;
|
|
3740
|
+
for (const channel of collection2(snapshot, "logicalChannels")) {
|
|
3741
|
+
const id = channel && typeof channel === "object" && typeof channel.id === "string" ? channel.id : "";
|
|
3742
|
+
const idBytes = encoder2.encode(JSON.stringify(id)).byteLength;
|
|
3743
|
+
total += (idBytes + 7) * 2 + idBytes + 1;
|
|
3744
|
+
}
|
|
3745
|
+
return total;
|
|
3746
|
+
}
|
|
3747
|
+
function legacyRuntimeLimitErrors(snapshot, capabilities) {
|
|
3748
|
+
if (supportsChannelEnabled(capabilities)) return [];
|
|
3749
|
+
const error = (message) => ({ path: "$", code: CONFIGURATION_LIMIT_CODE, message });
|
|
3750
|
+
const errors = [];
|
|
3751
|
+
if (collection2(snapshot, "physicalPoints").length > LEGACY_MAX_CONFIGURATION_ITEMS)
|
|
3752
|
+
errors.push(error(LEGACY_RUNTIME_LIMIT_MESSAGES.points));
|
|
3753
|
+
if (collection2(snapshot, "logicalChannels").length > LEGACY_MAX_CONFIGURATION_ITEMS)
|
|
3754
|
+
errors.push(error(LEGACY_RUNTIME_LIMIT_MESSAGES.channels));
|
|
3755
|
+
if (legacyWorstCaseStatusReportBytes(snapshot) > MAX_STATUS_REPORT_BYTES)
|
|
3756
|
+
errors.push(error(LEGACY_RUNTIME_LIMIT_MESSAGES.statusReport));
|
|
3757
|
+
return errors;
|
|
3758
|
+
}
|
|
3759
|
+
function runtimeSupportErrors(snapshot, capabilities) {
|
|
3760
|
+
return [...channelEnabledErrors(snapshot, capabilities), ...legacyRuntimeLimitErrors(snapshot, capabilities)];
|
|
3761
|
+
}
|
|
3762
|
+
function requireRuntimeSupport(snapshot, capabilities) {
|
|
3763
|
+
const errors = runtimeSupportErrors(snapshot, capabilities);
|
|
3764
|
+
if (errors.length) throw new import_common4.ConflictException({ message: errors[0].message, code: errors[0].code, errors });
|
|
3765
|
+
}
|
|
3766
|
+
|
|
3455
3767
|
// backend/controllers/enrollment.ts
|
|
3456
|
-
var
|
|
3768
|
+
var import_common9 = require("@nestjs/common");
|
|
3457
3769
|
var import_node_crypto7 = require("node:crypto");
|
|
3458
3770
|
|
|
3459
3771
|
// backend/configuration/revisions.ts
|
|
3460
|
-
var
|
|
3772
|
+
var import_common8 = require("@nestjs/common");
|
|
3461
3773
|
|
|
3462
3774
|
// backend/configuration/flow-references.ts
|
|
3463
3775
|
var import_plugins_backend_sdk = require("@attraccess/plugins-backend-sdk");
|
|
@@ -3485,6 +3797,10 @@ function editorMetadata(value) {
|
|
|
3485
3797
|
return { names, presets };
|
|
3486
3798
|
}
|
|
3487
3799
|
function validateEditorSnapshot(snapshot) {
|
|
3800
|
+
const limits2 = configurationLimitErrors(snapshot, "$");
|
|
3801
|
+
return limits2.length ? limits2 : validateEditorModel(snapshot);
|
|
3802
|
+
}
|
|
3803
|
+
function validateEditorModel(snapshot) {
|
|
3488
3804
|
const errors = validateSnapshot(snapshot);
|
|
3489
3805
|
if (errors.length) return errors;
|
|
3490
3806
|
const value = snapshot;
|
|
@@ -3583,6 +3899,8 @@ function selectPresetChanges(snapshot, application, paths, expectedHash) {
|
|
|
3583
3899
|
if (errors.length) throw new Error(errors.map((error) => `${error.path}: ${error.message}`).join("; "));
|
|
3584
3900
|
return candidate;
|
|
3585
3901
|
}
|
|
3902
|
+
var CHANNEL_DISABLED_IMPACT_CODE = "channel_disabled";
|
|
3903
|
+
var CHANNEL_DISABLED_IMPACT_MESSAGE = "Channel disabled. Flows reading it will see it as unavailable.";
|
|
3586
3904
|
function configurationImpacts(previous, current) {
|
|
3587
3905
|
return (previous?.logicalChannels ?? []).flatMap((channel) => {
|
|
3588
3906
|
const next = current.logicalChannels.find((item) => item.id === channel.id);
|
|
@@ -3590,6 +3908,8 @@ function configurationImpacts(previous, current) {
|
|
|
3590
3908
|
const newPoint = current.physicalPoints.find((point) => point.id === next?.physicalPointId);
|
|
3591
3909
|
if (next && configurationHash({ channel, point: oldPoint }) === configurationHash({ channel: next, point: newPoint }))
|
|
3592
3910
|
return [];
|
|
3911
|
+
if (next && next.enabled === false && channel.enabled !== false)
|
|
3912
|
+
return [{ channelId: channel.id, code: CHANNEL_DISABLED_IMPACT_CODE, message: CHANNEL_DISABLED_IMPACT_MESSAGE }];
|
|
3593
3913
|
return [
|
|
3594
3914
|
{
|
|
3595
3915
|
channelId: channel.id,
|
|
@@ -3626,7 +3946,7 @@ async function configurationFlowImpacts(context, controllerId, previous, current
|
|
|
3626
3946
|
}
|
|
3627
3947
|
|
|
3628
3948
|
// backend/configuration/drafts.ts
|
|
3629
|
-
var
|
|
3949
|
+
var import_common7 = require("@nestjs/common");
|
|
3630
3950
|
|
|
3631
3951
|
// backend/controllers/settings.entity.ts
|
|
3632
3952
|
var import_plugins_backend_sdk2 = require("@attraccess/plugins-backend-sdk");
|
|
@@ -3651,7 +3971,7 @@ WagoSettings = __decorateClass([
|
|
|
3651
3971
|
], WagoSettings);
|
|
3652
3972
|
|
|
3653
3973
|
// backend/controllers/registry.ts
|
|
3654
|
-
var
|
|
3974
|
+
var import_common6 = require("@nestjs/common");
|
|
3655
3975
|
|
|
3656
3976
|
// backend/controllers/entity.ts
|
|
3657
3977
|
var import_plugins_backend_sdk3 = require("@attraccess/plugins-backend-sdk");
|
|
@@ -3829,6 +4149,7 @@ function channelLabel(config, channel, snapshot, names, text) {
|
|
|
3829
4149
|
const terminal = DIGITAL_TERMINALS.find((item) => item.channel === point.channel)?.label;
|
|
3830
4150
|
if (terminal && !channelName.includes(terminal)) channelName += ` \xB7 ${terminal}`;
|
|
3831
4151
|
}
|
|
4152
|
+
if (channel?.enabled === false) channelName += ` (${text("disabled, unavailable", "deaktiviert, nicht verf\xFCgbar")})`;
|
|
3832
4153
|
return channelName;
|
|
3833
4154
|
}
|
|
3834
4155
|
function commandAction(config, channel, text) {
|
|
@@ -3981,7 +4302,7 @@ var WagoCommandHandler = class {
|
|
|
3981
4302
|
acknowledge(controllerId, payload) {
|
|
3982
4303
|
let acknowledgement;
|
|
3983
4304
|
try {
|
|
3984
|
-
acknowledgement =
|
|
4305
|
+
acknowledgement = parseControllerJson(payload, "command acknowledgement");
|
|
3985
4306
|
} catch {
|
|
3986
4307
|
return;
|
|
3987
4308
|
}
|
|
@@ -4346,7 +4667,7 @@ WagoConfigurationRevision = __decorateClass([
|
|
|
4346
4667
|
], WagoConfigurationRevision);
|
|
4347
4668
|
|
|
4348
4669
|
// backend/controllers/state.ts
|
|
4349
|
-
var
|
|
4670
|
+
var import_common5 = require("@nestjs/common");
|
|
4350
4671
|
var WagoServiceState = class {
|
|
4351
4672
|
constructor(context) {
|
|
4352
4673
|
__publicField(this, "context", context);
|
|
@@ -4374,7 +4695,7 @@ var WagoServiceState = class {
|
|
|
4374
4695
|
claimedController: async (id) => {
|
|
4375
4696
|
const controller = await this.claimedController(id);
|
|
4376
4697
|
if (this.isRuntimeUpdateRequired(id))
|
|
4377
|
-
throw new
|
|
4698
|
+
throw new import_common5.ConflictException(
|
|
4378
4699
|
"Runtime update required; outputs are held in failsafe until the server runtime is installed"
|
|
4379
4700
|
);
|
|
4380
4701
|
return controller;
|
|
@@ -4392,7 +4713,7 @@ var WagoServiceState = class {
|
|
|
4392
4713
|
}
|
|
4393
4714
|
};
|
|
4394
4715
|
WagoServiceState = __decorateClass([
|
|
4395
|
-
__decorateParam(0, (0,
|
|
4716
|
+
__decorateParam(0, (0, import_common5.Inject)(PLUGIN_CONTEXT))
|
|
4396
4717
|
], WagoServiceState);
|
|
4397
4718
|
|
|
4398
4719
|
// backend/controllers/registry.ts
|
|
@@ -4402,7 +4723,7 @@ var WagoRegistry = class extends WagoServiceState {
|
|
|
4402
4723
|
}
|
|
4403
4724
|
async setSettings(serverId, operationalPrefix) {
|
|
4404
4725
|
if (serverId !== void 0 && serverId !== null && !await this.context.getMqttServerConfig(serverId))
|
|
4405
|
-
throw new
|
|
4726
|
+
throw new import_common6.NotFoundException(`MQTT server ${serverId} not found`);
|
|
4406
4727
|
const save = async () => {
|
|
4407
4728
|
const settings2 = await this.getSettings();
|
|
4408
4729
|
if (serverId !== void 0) settings2.defaultMqttServerId = serverId;
|
|
@@ -4411,7 +4732,7 @@ var WagoRegistry = class extends WagoServiceState {
|
|
|
4411
4732
|
if (normalizedPrefix !== settings2.operationalPrefix) {
|
|
4412
4733
|
const controllers = await this.controllers.find({ where: { trustState: "claimed" } });
|
|
4413
4734
|
if (controllers.length)
|
|
4414
|
-
throw new
|
|
4735
|
+
throw new import_common6.ConflictException("operational MQTT prefix cannot change after a controller has been claimed");
|
|
4415
4736
|
settings2.operationalPrefix = normalizedPrefix;
|
|
4416
4737
|
}
|
|
4417
4738
|
}
|
|
@@ -4444,7 +4765,13 @@ var WagoRegistry = class extends WagoServiceState {
|
|
|
4444
4765
|
{ qos: 1, retain: false }
|
|
4445
4766
|
);
|
|
4446
4767
|
const [revision2] = await this.revisions.find({ where: { controllerId }, order: { revision: "DESC" }, take: 1 });
|
|
4447
|
-
|
|
4768
|
+
const replay = revision2 && (revision2.state === "published" || revision2.state === "applied") ? revision2 : null;
|
|
4769
|
+
const unsupported = replay ? runtimeSupportErrors(JSON.parse(replay.snapshot), controller.capabilities) : [];
|
|
4770
|
+
if (replay && unsupported.length)
|
|
4771
|
+
this.context.logger.warn(
|
|
4772
|
+
`Not replaying WAGO configuration revision ${replay.revision} to controller ${controllerId}: ${unsupported[0].message}`
|
|
4773
|
+
);
|
|
4774
|
+
else if (replay)
|
|
4448
4775
|
await this.context.mqtt.publish(
|
|
4449
4776
|
controller.mqttServerId,
|
|
4450
4777
|
topic,
|
|
@@ -4452,9 +4779,9 @@ var WagoRegistry = class extends WagoServiceState {
|
|
|
4452
4779
|
protocolVersion: CONFIGURATION_PROTOCOL_VERSION,
|
|
4453
4780
|
runtimeImageId: desired,
|
|
4454
4781
|
runtimePolicyToken,
|
|
4455
|
-
revision:
|
|
4456
|
-
contentHash:
|
|
4457
|
-
snapshot: JSON.parse(
|
|
4782
|
+
revision: replay.revision,
|
|
4783
|
+
contentHash: replay.contentHash,
|
|
4784
|
+
snapshot: JSON.parse(replay.snapshot)
|
|
4458
4785
|
}),
|
|
4459
4786
|
{ qos: 1, retain: true }
|
|
4460
4787
|
);
|
|
@@ -4487,7 +4814,7 @@ var WagoRegistry = class extends WagoServiceState {
|
|
|
4487
4814
|
runtimeVersion: controller.runtimeVersion,
|
|
4488
4815
|
capabilities: controller.capabilities,
|
|
4489
4816
|
lastSequence: controller.lastSequence,
|
|
4490
|
-
lastHeartbeatAt: this.diagnostics.
|
|
4817
|
+
lastHeartbeatAt: this.diagnostics.heartbeatAt(controller.id) ?? controller.lastHeartbeatAt,
|
|
4491
4818
|
lastSeenAt: controller.lastSeenAt,
|
|
4492
4819
|
compatibilityError: controller.compatibilityError,
|
|
4493
4820
|
createdAt: controller.createdAt,
|
|
@@ -4504,12 +4831,12 @@ var WagoRegistry = class extends WagoServiceState {
|
|
|
4504
4831
|
async manualCommand(controllerId, input, principal) {
|
|
4505
4832
|
const keys = ["channelId", "action", "value", "expectedConfigurationRevision", "acknowledgementTimeoutSeconds"];
|
|
4506
4833
|
if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).some((key) => !keys.includes(key)))
|
|
4507
|
-
throw new
|
|
4834
|
+
throw new import_common6.BadRequestException("Invalid manual command");
|
|
4508
4835
|
if (typeof input.channelId !== "string" || !/^[A-Za-z0-9_-]{1,64}$/.test(input.channelId))
|
|
4509
|
-
throw new
|
|
4836
|
+
throw new import_common6.BadRequestException("Invalid Logical Channel");
|
|
4510
4837
|
const config = { ...input, controllerId, completionBehavior: "acknowledged" };
|
|
4511
4838
|
const errors = await this.commands.validate(config, /* @__PURE__ */ new Map(), true);
|
|
4512
|
-
if (errors.length) throw new
|
|
4839
|
+
if (errors.length) throw new import_common6.BadRequestException("Manual command does not match the applied configuration");
|
|
4513
4840
|
const commandId = (0, import_node_crypto6.randomUUID)();
|
|
4514
4841
|
const details = { commandId, channelId: input.channelId, operation: input.action };
|
|
4515
4842
|
const lifecycle = new WagoAudit(this.context).begin(principal, controllerId, "manual_command", details);
|
|
@@ -4572,7 +4899,7 @@ var WagoRegistry = class extends WagoServiceState {
|
|
|
4572
4899
|
async refreshNetworkConnection(controllerId) {
|
|
4573
4900
|
const controller = await this.claimedController(controllerId), settings = await this.getSettings();
|
|
4574
4901
|
const serverId = controller.mqttServerId;
|
|
4575
|
-
if (!serverId) throw new
|
|
4902
|
+
if (!serverId) throw new import_common6.ConflictException("Controller MQTT server is unavailable");
|
|
4576
4903
|
const subscriptions = [];
|
|
4577
4904
|
let retained;
|
|
4578
4905
|
const active2 = () => !this.destroyed && this.networkSubscriptions.get(controllerId) === subscriptions;
|
|
@@ -4656,12 +4983,12 @@ var WagoDrafts = class extends WagoRegistry {
|
|
|
4656
4983
|
const draft = await this.draftForPreset(controllerId);
|
|
4657
4984
|
const snapshot = JSON.parse(draft.snapshot);
|
|
4658
4985
|
if (previewedDraftHash !== configurationHash(snapshot))
|
|
4659
|
-
throw new
|
|
4986
|
+
throw new import_common7.ConflictException("selected preset changes no longer match the configuration draft");
|
|
4660
4987
|
const candidate = applyPreset(snapshot, application);
|
|
4661
4988
|
const diff = configurationDiff(snapshot, candidate);
|
|
4662
4989
|
const validPaths = new Set(diff.map((change) => change.path));
|
|
4663
4990
|
if (!Array.isArray(selectedPaths) || selectedPaths.some((path) => !validPaths.has(path)))
|
|
4664
|
-
throw new
|
|
4991
|
+
throw new import_common7.ConflictException("selected preset changes no longer match the configuration draft");
|
|
4665
4992
|
const updatedSnapshot = applySelectedChanges(snapshot, diff, selectedPaths);
|
|
4666
4993
|
if (configurationHash(updatedSnapshot) === configurationHash(snapshot)) return draft;
|
|
4667
4994
|
const provenance = parsePresetProvenance(draft.presetProvenance);
|
|
@@ -4716,13 +5043,13 @@ var WagoDrafts = class extends WagoRegistry {
|
|
|
4716
5043
|
async draftForPreset(controllerId) {
|
|
4717
5044
|
await this.claimedController(controllerId);
|
|
4718
5045
|
const draft = await this.drafts.findOneBy({ controllerId });
|
|
4719
|
-
if (!draft) throw new
|
|
5046
|
+
if (!draft) throw new import_common7.NotFoundException(`WAGO controller ${controllerId} has no configuration draft`);
|
|
4720
5047
|
return draft;
|
|
4721
5048
|
}
|
|
4722
5049
|
async validateDraft(controllerId, snapshot) {
|
|
4723
5050
|
const draft = await this.getDraft(controllerId);
|
|
4724
5051
|
if (!draft && snapshot === void 0)
|
|
4725
|
-
throw new
|
|
5052
|
+
throw new import_common7.NotFoundException(`WAGO controller ${controllerId} has no configuration draft`);
|
|
4726
5053
|
const errors = validateEditorSnapshot(snapshot === void 0 && draft ? JSON.parse(draft.snapshot) : snapshot);
|
|
4727
5054
|
return { valid: errors.length === 0, errors };
|
|
4728
5055
|
}
|
|
@@ -4732,7 +5059,7 @@ var WagoDrafts = class extends WagoRegistry {
|
|
|
4732
5059
|
try {
|
|
4733
5060
|
return previewConfigurationPreset(source, application);
|
|
4734
5061
|
} catch (error) {
|
|
4735
|
-
throw new
|
|
5062
|
+
throw new import_common7.BadRequestException(error instanceof Error ? error.message : "invalid preset");
|
|
4736
5063
|
}
|
|
4737
5064
|
}
|
|
4738
5065
|
presets() {
|
|
@@ -4742,7 +5069,7 @@ var WagoDrafts = class extends WagoRegistry {
|
|
|
4742
5069
|
return this.withConfigurationLock(controllerId, async () => {
|
|
4743
5070
|
const previous = await this.getDraft(controllerId);
|
|
4744
5071
|
if (expectedDraft !== void 0 && (expectedDraft === null ? previous !== null : !previous || expectedDraft.snapshot !== previous.snapshot || expectedDraft.updatedAt !== previous.updatedAt || expectedDraft.presetProvenance !== previous.presetProvenance)) {
|
|
4745
|
-
throw new
|
|
5072
|
+
throw new import_common7.ConflictException("Saved draft changed. Reload it before saving your edits.");
|
|
4746
5073
|
}
|
|
4747
5074
|
const validatedMetadata = metadata2 === void 0 ? void 0 : editorMetadata(metadata2);
|
|
4748
5075
|
const previousMetadata = this.metadataFromProvenance(previous?.presetProvenance);
|
|
@@ -4836,10 +5163,11 @@ var WagoRevisions = class extends WagoDrafts {
|
|
|
4836
5163
|
const controller = await this.claimedController(controllerId);
|
|
4837
5164
|
this.requireConfigurationCompatibility(controller);
|
|
4838
5165
|
const draft = preparedDraft ?? await this.drafts.findOneBy({ controllerId });
|
|
4839
|
-
if (!draft) throw new
|
|
5166
|
+
if (!draft) throw new import_common8.NotFoundException(`WAGO controller ${controllerId} has no configuration draft`);
|
|
4840
5167
|
const validation = validateEditorSnapshot(JSON.parse(draft.snapshot));
|
|
4841
5168
|
if (validation.length)
|
|
4842
|
-
throw new
|
|
5169
|
+
throw new import_common8.ConflictException({ message: "configuration draft is invalid", errors: validation });
|
|
5170
|
+
requireRuntimeSupport(JSON.parse(draft.snapshot), controller.capabilities);
|
|
4843
5171
|
const contentHash = configurationHash(JSON.parse(draft.snapshot));
|
|
4844
5172
|
const previous = await this.latestRevision(controllerId);
|
|
4845
5173
|
const impacts = await configurationFlowImpacts(
|
|
@@ -4850,11 +5178,11 @@ var WagoRevisions = class extends WagoDrafts {
|
|
|
4850
5178
|
);
|
|
4851
5179
|
const reviewIdentity = this.reviewIdentity(draft, previous, impacts);
|
|
4852
5180
|
if (reviewedHash !== void 0 && reviewedHash !== reviewIdentity)
|
|
4853
|
-
throw new
|
|
5181
|
+
throw new import_common8.ConflictException("draft changed since your review; review it again");
|
|
4854
5182
|
if (draft.reviewedHash !== reviewIdentity)
|
|
4855
|
-
throw new
|
|
5183
|
+
throw new import_common8.ConflictException("review the current configuration draft before publishing it");
|
|
4856
5184
|
if (impacts.length && !force)
|
|
4857
|
-
throw new
|
|
5185
|
+
throw new import_common8.ConflictException({ message: "acknowledge potential flow impacts before publishing", impacts });
|
|
4858
5186
|
let allocatedRevision;
|
|
4859
5187
|
const dispatch = (revision2) => {
|
|
4860
5188
|
allocatedRevision = revision2.revision;
|
|
@@ -4886,12 +5214,12 @@ var WagoRevisions = class extends WagoDrafts {
|
|
|
4886
5214
|
protocolVersion: controller.protocolVersion,
|
|
4887
5215
|
capabilities: JSON.parse(controller.capabilities)
|
|
4888
5216
|
});
|
|
4889
|
-
if (incompatibility) throw new
|
|
5217
|
+
if (incompatibility) throw new import_common8.ConflictException(`Cannot publish configuration: ${incompatibility}`);
|
|
4890
5218
|
}
|
|
4891
5219
|
async reviewDraftWhileLocked(controllerId) {
|
|
4892
5220
|
await this.claimedController(controllerId);
|
|
4893
5221
|
const draft = await this.drafts.findOneBy({ controllerId });
|
|
4894
|
-
if (!draft) throw new
|
|
5222
|
+
if (!draft) throw new import_common8.NotFoundException(`WAGO controller ${controllerId} has no configuration draft`);
|
|
4895
5223
|
const previous = await this.latestRevision(controllerId);
|
|
4896
5224
|
const impacts = await configurationFlowImpacts(
|
|
4897
5225
|
this.context,
|
|
@@ -4922,7 +5250,7 @@ var WagoRevisions = class extends WagoDrafts {
|
|
|
4922
5250
|
try {
|
|
4923
5251
|
provenance = JSON.stringify({ editor: editorMetadata(metadata2) });
|
|
4924
5252
|
} catch (error) {
|
|
4925
|
-
throw new
|
|
5253
|
+
throw new import_common8.BadRequestException(error instanceof Error ? error.message : "invalid editor metadata");
|
|
4926
5254
|
}
|
|
4927
5255
|
}
|
|
4928
5256
|
const serialized = canonicalSnapshot(snapshot);
|
|
@@ -4979,27 +5307,30 @@ var WagoRevisions = class extends WagoDrafts {
|
|
|
4979
5307
|
}
|
|
4980
5308
|
async previewRevision(controllerId, revision2) {
|
|
4981
5309
|
return this.withConfigurationLock(controllerId, async () => {
|
|
4982
|
-
await this.claimedController(controllerId);
|
|
5310
|
+
const controller = await this.claimedController(controllerId);
|
|
4983
5311
|
const selected = await this.revisions.findOneBy({ controllerId, revision: revision2 });
|
|
4984
|
-
if (!selected) throw new
|
|
5312
|
+
if (!selected) throw new import_common8.NotFoundException(`WAGO configuration revision ${revision2} not found`);
|
|
4985
5313
|
const [current] = await this.revisions.find({ where: { controllerId }, order: { revision: "DESC" }, take: 1 });
|
|
4986
5314
|
const draft = await this.drafts.findOneBy({ controllerId });
|
|
5315
|
+
const snapshot = JSON.parse(selected.snapshot);
|
|
4987
5316
|
const impacts = await configurationFlowImpacts(
|
|
4988
5317
|
this.context,
|
|
4989
5318
|
controllerId,
|
|
4990
5319
|
current ? JSON.parse(current.snapshot) : null,
|
|
4991
|
-
|
|
5320
|
+
snapshot
|
|
4992
5321
|
);
|
|
4993
5322
|
return {
|
|
4994
5323
|
draftHash: this.rollbackIdentity(draft, current ?? null, selected, impacts),
|
|
4995
5324
|
impacts,
|
|
4996
5325
|
revision: selected,
|
|
4997
5326
|
current: current ?? null,
|
|
4998
|
-
diff: configurationDiff(current ? JSON.parse(current.snapshot) : null,
|
|
5327
|
+
diff: configurationDiff(current ? JSON.parse(current.snapshot) : null, snapshot),
|
|
4999
5328
|
metadataDiff: configurationDiff(
|
|
5000
5329
|
this.metadataFromProvenance(current?.presetProvenance),
|
|
5001
5330
|
this.metadataFromProvenance(selected.presetProvenance)
|
|
5002
|
-
)
|
|
5331
|
+
),
|
|
5332
|
+
// The same checks `rollback` runs, so the editor can refuse the rollback before publishing.
|
|
5333
|
+
errors: [...validateEditorSnapshot(snapshot), ...runtimeSupportErrors(snapshot, controller.capabilities)]
|
|
5003
5334
|
};
|
|
5004
5335
|
});
|
|
5005
5336
|
}
|
|
@@ -5007,9 +5338,9 @@ var WagoRevisions = class extends WagoDrafts {
|
|
|
5007
5338
|
return this.withConfigurationLock(controllerId, async () => {
|
|
5008
5339
|
await this.claimedController(controllerId);
|
|
5009
5340
|
const rejected = await this.revisions.findOneBy({ controllerId, revision: revision2 });
|
|
5010
|
-
if (!rejected) throw new
|
|
5341
|
+
if (!rejected) throw new import_common8.NotFoundException(`WAGO configuration revision ${revision2} not found`);
|
|
5011
5342
|
if (rejected.state !== "rejected" || !rejected.reportedAt || expected.contentHash !== rejected.contentHash || expected.reportedAt !== rejected.reportedAt)
|
|
5012
|
-
throw new
|
|
5343
|
+
throw new import_common8.ConflictException("rejection changed; refresh and review it before acknowledging");
|
|
5013
5344
|
if (rejected.rejectionAcknowledgedAt) return rejected;
|
|
5014
5345
|
return new WagoAudit(this.context).run(
|
|
5015
5346
|
principal,
|
|
@@ -5031,12 +5362,13 @@ var WagoRevisions = class extends WagoDrafts {
|
|
|
5031
5362
|
const controller = await this.claimedController(controllerId);
|
|
5032
5363
|
this.requireConfigurationCompatibility(controller);
|
|
5033
5364
|
const source = await this.revisions.findOneBy({ controllerId, revision: revision2 });
|
|
5034
|
-
if (!source) throw new
|
|
5365
|
+
if (!source) throw new import_common8.NotFoundException(`WAGO configuration revision ${revision2} not found`);
|
|
5035
5366
|
const current = await this.latestRevision(controllerId);
|
|
5036
5367
|
const draft = await this.drafts.findOneBy({ controllerId });
|
|
5037
5368
|
const snapshot = JSON.parse(source.snapshot);
|
|
5038
5369
|
const errors = validateEditorSnapshot(snapshot);
|
|
5039
|
-
if (errors.length) throw new
|
|
5370
|
+
if (errors.length) throw new import_common8.ConflictException({ message: "rollback configuration is invalid", errors });
|
|
5371
|
+
requireRuntimeSupport(snapshot, controller.capabilities);
|
|
5040
5372
|
const approvedImpacts = await configurationFlowImpacts(
|
|
5041
5373
|
this.context,
|
|
5042
5374
|
controllerId,
|
|
@@ -5045,11 +5377,11 @@ var WagoRevisions = class extends WagoDrafts {
|
|
|
5045
5377
|
);
|
|
5046
5378
|
const assertPreview = (impacts) => {
|
|
5047
5379
|
if (draftHash !== this.rollbackIdentity(draft, current, source, impacts) || sourceHash !== source.contentHash || currentHash !== (current?.contentHash ?? null))
|
|
5048
|
-
throw new
|
|
5380
|
+
throw new import_common8.ConflictException("configuration changed since rollback preview; preview and confirm again");
|
|
5049
5381
|
};
|
|
5050
5382
|
assertPreview(approvedImpacts);
|
|
5051
5383
|
if (approvedImpacts.length && !force)
|
|
5052
|
-
throw new
|
|
5384
|
+
throw new import_common8.ConflictException({
|
|
5053
5385
|
message: "acknowledge potential flow impacts before publishing",
|
|
5054
5386
|
impacts: approvedImpacts
|
|
5055
5387
|
});
|
|
@@ -5109,7 +5441,7 @@ var WagoControllerEnrollment = class extends WagoRevisions {
|
|
|
5109
5441
|
}, assertOwned = async () => void 0) {
|
|
5110
5442
|
await assertOwned();
|
|
5111
5443
|
const manual = await this.context.getMqttCredentialProvisioning().revoke({ mqttServerId, identity, username: identity, vhost: "/" });
|
|
5112
|
-
if (manual) throw new
|
|
5444
|
+
if (manual) throw new import_common9.ConflictException("Manual permanent credential revocation is required.");
|
|
5113
5445
|
await assertOwned();
|
|
5114
5446
|
controller.credentialMqttServerId = null;
|
|
5115
5447
|
controller.credentialEpoch = null;
|
|
@@ -5141,32 +5473,32 @@ var WagoControllerEnrollment = class extends WagoRevisions {
|
|
|
5141
5473
|
}
|
|
5142
5474
|
async prepareClaim(id, name, verifier, mqttServerId, assertOwned = async () => void 0, manualCredentials) {
|
|
5143
5475
|
const controller = await this.controllers.findOneBy({ id });
|
|
5144
|
-
if (!controller) throw new
|
|
5145
|
-
if (controller.trustState === "claimed") throw new
|
|
5146
|
-
if (!name.trim()) throw new
|
|
5476
|
+
if (!controller) throw new import_common9.NotFoundException(`WAGO controller ${id} not found`);
|
|
5477
|
+
if (controller.trustState === "claimed") throw new import_common9.ConflictException("controller has already been claimed");
|
|
5478
|
+
if (!name.trim()) throw new import_common9.ConflictException("a controller name is required");
|
|
5147
5479
|
if (!this.matchesVerifier(controller, verifier))
|
|
5148
|
-
throw new
|
|
5149
|
-
if (controller.compatibilityError) throw new
|
|
5480
|
+
throw new import_common9.ConflictException("physical pairing code or fingerprint does not match the controller");
|
|
5481
|
+
if (controller.compatibilityError) throw new import_common9.ConflictException(controller.compatibilityError);
|
|
5150
5482
|
const selectedServerId = mqttServerId ?? controller.mqttServerId;
|
|
5151
|
-
if (!selectedServerId) throw new
|
|
5483
|
+
if (!selectedServerId) throw new import_common9.ConflictException("select an MQTT server before claiming this controller");
|
|
5152
5484
|
if (selectedServerId !== controller.mqttServerId)
|
|
5153
|
-
throw new
|
|
5485
|
+
throw new import_common9.ConflictException("claim the controller on the MQTT server used for its enrollment package");
|
|
5154
5486
|
if (!await this.context.getMqttServerConfig(selectedServerId))
|
|
5155
|
-
throw new
|
|
5487
|
+
throw new import_common9.NotFoundException(`MQTT server ${selectedServerId} not found`);
|
|
5156
5488
|
const enrollment = await this.activeEnrollment(controller.enrollmentId);
|
|
5157
5489
|
if (!enrollment)
|
|
5158
|
-
throw new
|
|
5490
|
+
throw new import_common9.ConflictException(
|
|
5159
5491
|
"the controller enrollment package has expired or was already consumed; create a new one"
|
|
5160
5492
|
);
|
|
5161
5493
|
const identity = `wago-controller-${controller.hardwareId}`;
|
|
5162
5494
|
const settings = await this.getSettings();
|
|
5163
5495
|
const namespace = normalizeOperationalPrefix(settings.operationalPrefix);
|
|
5164
5496
|
if (manualCredentials && (await this.context.getMqttCredentialProvisioning().availableProviders(selectedServerId)).length)
|
|
5165
|
-
throw new
|
|
5497
|
+
throw new import_common9.ConflictException(
|
|
5166
5498
|
"This broker uses automatic credential provisioning; use the standard claim operation"
|
|
5167
5499
|
);
|
|
5168
5500
|
if (controller.credentialMqttServerId && (!manualCredentials || controller.credentialMqttServerId !== selectedServerId))
|
|
5169
|
-
throw new
|
|
5501
|
+
throw new import_common9.ConflictException(
|
|
5170
5502
|
"Remove the controller registration to revoke its unfinished permanent credential before claiming again."
|
|
5171
5503
|
);
|
|
5172
5504
|
await assertOwned();
|
|
@@ -5190,7 +5522,7 @@ var WagoControllerEnrollment = class extends WagoRevisions {
|
|
|
5190
5522
|
});
|
|
5191
5523
|
await assertOwned();
|
|
5192
5524
|
const credential = "password" in provisioned ? provisioned : manualCredentials;
|
|
5193
|
-
if (!credential) throw new
|
|
5525
|
+
if (!credential) throw new import_common9.ConflictException("Manual credential provisioning is required");
|
|
5194
5526
|
const previousController = {
|
|
5195
5527
|
trustState: controller.trustState,
|
|
5196
5528
|
name: controller.name,
|
|
@@ -5285,19 +5617,19 @@ var WagoControllerEnrollment = class extends WagoRevisions {
|
|
|
5285
5617
|
}
|
|
5286
5618
|
async completeManualCredentials(id, input, principal, assertOwned = async () => void 0) {
|
|
5287
5619
|
const controller = await this.controllers.findOneBy({ id });
|
|
5288
|
-
if (!controller) throw new
|
|
5620
|
+
if (!controller) throw new import_common9.NotFoundException("controller not found");
|
|
5289
5621
|
if (input.username !== `wago-controller-${controller.hardwareId}` || !input.password || input.password.length > 4096)
|
|
5290
|
-
throw new
|
|
5622
|
+
throw new import_common9.BadRequestException("Supply the controller identity and provisioned password");
|
|
5291
5623
|
if (!JSON.parse(controller.capabilities).includes("claim-expiry-v1"))
|
|
5292
|
-
throw new
|
|
5624
|
+
throw new import_common9.ConflictException("Install a runtime supporting expiring claims before manual credential fallback");
|
|
5293
5625
|
return new WagoAudit(this.context).run(principal, id, "manual_credential_fallback", {}, async () => {
|
|
5294
5626
|
let active2 = true;
|
|
5295
5627
|
let dispatched = false;
|
|
5296
5628
|
const expiresAt = new Date(Date.now() + 3e4).toISOString();
|
|
5297
5629
|
const assertActive = async () => {
|
|
5298
|
-
if (!active2) throw new
|
|
5630
|
+
if (!active2) throw new import_common9.ConflictException("Controller credential operation ended");
|
|
5299
5631
|
await assertOwned();
|
|
5300
|
-
if (!active2) throw new
|
|
5632
|
+
if (!active2) throw new import_common9.ConflictException("Controller credential operation ended");
|
|
5301
5633
|
};
|
|
5302
5634
|
let acknowledged;
|
|
5303
5635
|
const receipt = new Promise((resolve2) => {
|
|
@@ -5308,7 +5640,7 @@ var WagoControllerEnrollment = class extends WagoRevisions {
|
|
|
5308
5640
|
timer = setTimeout(() => {
|
|
5309
5641
|
active2 = false;
|
|
5310
5642
|
reject(
|
|
5311
|
-
dispatched ? new WagoCredentialOperationUncertainError("Controller credential acknowledgement timed out") : new
|
|
5643
|
+
dispatched ? new WagoCredentialOperationUncertainError("Controller credential acknowledgement timed out") : new import_common9.ConflictException("Controller credential acknowledgement timed out")
|
|
5312
5644
|
);
|
|
5313
5645
|
}, 3e4);
|
|
5314
5646
|
});
|
|
@@ -5348,7 +5680,7 @@ var WagoControllerEnrollment = class extends WagoRevisions {
|
|
|
5348
5680
|
id,
|
|
5349
5681
|
() => this.withClaimConfigurationLock(async () => {
|
|
5350
5682
|
const controller = await this.controllers.findOneBy({ id });
|
|
5351
|
-
if (!controller) throw new
|
|
5683
|
+
if (!controller) throw new import_common9.NotFoundException(`WAGO controller ${id} not found`);
|
|
5352
5684
|
const credentialServerId = controller.credentialMqttServerId ?? (controller.trustState === "claimed" ? controller.mqttServerId : null);
|
|
5353
5685
|
if (credentialServerId) {
|
|
5354
5686
|
const identity = `wago-controller-${controller.hardwareId}`;
|
|
@@ -5360,7 +5692,7 @@ var WagoControllerEnrollment = class extends WagoRevisions {
|
|
|
5360
5692
|
vhost: "/"
|
|
5361
5693
|
});
|
|
5362
5694
|
if (manual)
|
|
5363
|
-
throw new
|
|
5695
|
+
throw new import_common9.ConflictException(`Manual credential revocation is required: ${manual.instructions.join(" ")}`);
|
|
5364
5696
|
}
|
|
5365
5697
|
if (controller.enrollmentId) await this.revokeEnrollmentById(controller.enrollmentId, assertOwned);
|
|
5366
5698
|
await assertOwned();
|
|
@@ -5390,11 +5722,11 @@ var WagoControllerEnrollment = class extends WagoRevisions {
|
|
|
5390
5722
|
async createEnrollment(hardwareId, mqttServerId, manualCredentials, assertOwned = async () => void 0) {
|
|
5391
5723
|
const normalizedHardwareId = hardwareId.trim();
|
|
5392
5724
|
if (!isValidHardwareId(normalizedHardwareId))
|
|
5393
|
-
throw new
|
|
5725
|
+
throw new import_common9.ConflictException("a valid hardware ID without MQTT separators or wildcards is required");
|
|
5394
5726
|
const selectedServerId = mqttServerId ?? (await this.getSettings()).defaultMqttServerId;
|
|
5395
|
-
if (!selectedServerId) throw new
|
|
5727
|
+
if (!selectedServerId) throw new import_common9.ConflictException("select an MQTT server before creating an enrollment package");
|
|
5396
5728
|
const server = await this.context.getMqttServerConfig(selectedServerId);
|
|
5397
|
-
if (!server) throw new
|
|
5729
|
+
if (!server) throw new import_common9.NotFoundException(`MQTT server ${selectedServerId} not found`);
|
|
5398
5730
|
const claimSecret = (0, import_node_crypto7.randomBytes)(24).toString("base64url");
|
|
5399
5731
|
const identity = `wago-enrollment-${(0, import_node_crypto7.randomBytes)(8).toString("hex")}`;
|
|
5400
5732
|
const expiresAt = new Date(Date.now() + 15 * 6e4).toISOString();
|
|
@@ -5427,13 +5759,13 @@ var WagoControllerEnrollment = class extends WagoRevisions {
|
|
|
5427
5759
|
if (timer) clearTimeout(timer);
|
|
5428
5760
|
this.enrollmentExpiryTimers.delete(enrollment.id);
|
|
5429
5761
|
await this.enrollments.delete(enrollment.id);
|
|
5430
|
-
throw new
|
|
5762
|
+
throw new import_common9.ConflictException(
|
|
5431
5763
|
`Manual discovery credentials are required: ${provisionedCredential.instructions.join(" ")}`
|
|
5432
5764
|
);
|
|
5433
5765
|
}
|
|
5434
5766
|
const credential = "password" in provisionedCredential ? provisionedCredential : manualCredentials;
|
|
5435
5767
|
if (!credential?.username.trim() || !credential.password)
|
|
5436
|
-
throw new
|
|
5768
|
+
throw new import_common9.ConflictException("a manual discovery username and password are required");
|
|
5437
5769
|
await assertOwned();
|
|
5438
5770
|
if (!("password" in provisionedCredential)) {
|
|
5439
5771
|
enrollment.identity = credential.username;
|
|
@@ -5471,7 +5803,7 @@ var WagoSubscriptions = class extends WagoControllerEnrollment {
|
|
|
5471
5803
|
const canTrackDiagnostics = this.diagnostics.canTrack(controller.id);
|
|
5472
5804
|
const admitted = this.diagnostics.ingest(controller.id, "heartbeat", payload);
|
|
5473
5805
|
if (canTrackDiagnostics && !admitted) return;
|
|
5474
|
-
const heartbeatAt = admitted ? this.diagnostics.
|
|
5806
|
+
const heartbeatAt = admitted ? this.diagnostics.heartbeatAt(controller.id) : typeof rawHeartbeat.timestamp === "string" ? rawHeartbeat.timestamp : void 0;
|
|
5475
5807
|
if (canonical && !acceptedCanonicalHeartbeat(rawHeartbeat, heartbeatAt, controller.lastHeartbeatAt)) return;
|
|
5476
5808
|
if (canonical && (admitted || !canTrackDiagnostics))
|
|
5477
5809
|
this.reportRuntimeStatus(controller.id, heartbeat, rawHeartbeat);
|
|
@@ -5481,9 +5813,10 @@ var WagoSubscriptions = class extends WagoControllerEnrollment {
|
|
|
5481
5813
|
}
|
|
5482
5814
|
/** Parse a heartbeat for the expected controller; malformed packets are ignored. */
|
|
5483
5815
|
parseHeartbeatPayload(hardwareId, payload) {
|
|
5484
|
-
let heartbeat;
|
|
5816
|
+
let heartbeat, rawHeartbeat;
|
|
5485
5817
|
try {
|
|
5486
|
-
|
|
5818
|
+
rawHeartbeat = parseObject(payload, "announcement");
|
|
5819
|
+
heartbeat = heartbeatFromObject(rawHeartbeat);
|
|
5487
5820
|
} catch (error) {
|
|
5488
5821
|
this.context.logger.warn(
|
|
5489
5822
|
`Ignoring invalid WAGO heartbeat: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -5491,11 +5824,7 @@ var WagoSubscriptions = class extends WagoControllerEnrollment {
|
|
|
5491
5824
|
return void 0;
|
|
5492
5825
|
}
|
|
5493
5826
|
if (heartbeat.hardwareId !== hardwareId) return void 0;
|
|
5494
|
-
|
|
5495
|
-
return { heartbeat, rawHeartbeat: JSON.parse(payload.toString("utf8")) };
|
|
5496
|
-
} catch {
|
|
5497
|
-
return void 0;
|
|
5498
|
-
}
|
|
5827
|
+
return { heartbeat, rawHeartbeat };
|
|
5499
5828
|
}
|
|
5500
5829
|
reportRuntimeStatus(controllerId, heartbeat, rawHeartbeat) {
|
|
5501
5830
|
if (typeof rawHeartbeat.streamId !== "string" || typeof rawHeartbeat.timestamp !== "string") return;
|
|
@@ -5790,8 +6119,8 @@ var WagoService = class extends WagoSubscriptions {
|
|
|
5790
6119
|
async claimedController(id) {
|
|
5791
6120
|
const controller = await this.controllers.findOneBy({ id });
|
|
5792
6121
|
if (!controller || controller.trustState !== "claimed")
|
|
5793
|
-
throw new
|
|
5794
|
-
if (!controller.mqttServerId) throw new
|
|
6122
|
+
throw new import_common10.NotFoundException(`claimed WAGO controller ${id} not found`);
|
|
6123
|
+
if (!controller.mqttServerId) throw new import_common10.ConflictException(`WAGO controller ${id} has no MQTT server`);
|
|
5795
6124
|
return controller;
|
|
5796
6125
|
}
|
|
5797
6126
|
async withClaimConfigurationLock(operation) {
|
|
@@ -5838,7 +6167,7 @@ var WagoService = class extends WagoSubscriptions {
|
|
|
5838
6167
|
vhost: "/"
|
|
5839
6168
|
});
|
|
5840
6169
|
if (manual)
|
|
5841
|
-
throw new
|
|
6170
|
+
throw new import_common10.ConflictException(`Manual credential revocation is required: ${manual.instructions.join(" ")}`);
|
|
5842
6171
|
await assertOwned();
|
|
5843
6172
|
enrollment.revokedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5844
6173
|
await this.enrollments.save(enrollment);
|
|
@@ -5904,7 +6233,7 @@ var WagoService = class extends WagoSubscriptions {
|
|
|
5904
6233
|
}
|
|
5905
6234
|
connectivity(controller) {
|
|
5906
6235
|
if (controller.trustState === "untrusted") return "untrusted";
|
|
5907
|
-
const heartbeatAt = this.diagnostics.
|
|
6236
|
+
const heartbeatAt = this.diagnostics.heartbeatAt(controller.id) ?? controller.lastHeartbeatAt;
|
|
5908
6237
|
if (freshness(heartbeatAt, Date.now(), STALE_AFTER_MS) !== "fresh") return "stale";
|
|
5909
6238
|
if (this.isRuntimeUpdateRequired(controller.id)) {
|
|
5910
6239
|
const policy = this.runtimePolicies.get(controller.id);
|
|
@@ -5913,12 +6242,13 @@ var WagoService = class extends WagoSubscriptions {
|
|
|
5913
6242
|
return "online";
|
|
5914
6243
|
}
|
|
5915
6244
|
async publishRevision(controller, revision2) {
|
|
5916
|
-
if (!controller.mqttServerId) throw new
|
|
6245
|
+
if (!controller.mqttServerId) throw new import_common10.ConflictException(`WAGO controller ${controller.id} has no MQTT server`);
|
|
5917
6246
|
const incompatibility = compatibilityError({
|
|
5918
6247
|
protocolVersion: controller.protocolVersion,
|
|
5919
6248
|
capabilities: JSON.parse(controller.capabilities)
|
|
5920
6249
|
});
|
|
5921
|
-
if (incompatibility) throw new
|
|
6250
|
+
if (incompatibility) throw new import_common10.ConflictException(`Cannot publish configuration: ${incompatibility}`);
|
|
6251
|
+
requireRuntimeSupport(JSON.parse(revision2.snapshot), controller.capabilities);
|
|
5922
6252
|
const settings = await this.getSettings();
|
|
5923
6253
|
const runtimePolicy = this.runtimePolicies.get(controller.id);
|
|
5924
6254
|
await this.context.mqtt.publish(
|
|
@@ -5939,13 +6269,11 @@ var WagoService = class extends WagoSubscriptions {
|
|
|
5939
6269
|
revision2.state = "published";
|
|
5940
6270
|
return this.revisions.save(revision2);
|
|
5941
6271
|
}
|
|
6272
|
+
/** Revision a queued report acknowledges; throws for a report outside the controller payload limits. */
|
|
5942
6273
|
configurationReportRevision(payload) {
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
} catch {
|
|
5947
|
-
return null;
|
|
5948
|
-
}
|
|
6274
|
+
const report = parseControllerJson(payload, "configuration report");
|
|
6275
|
+
const revision2 = report && typeof report === "object" ? report.revision : void 0;
|
|
6276
|
+
return Number.isSafeInteger(revision2) && revision2 >= 1 ? revision2 : null;
|
|
5949
6277
|
}
|
|
5950
6278
|
async processConfigurationReports(controllerId, payload, queue) {
|
|
5951
6279
|
let next = payload;
|
|
@@ -5970,7 +6298,15 @@ var WagoService = class extends WagoSubscriptions {
|
|
|
5970
6298
|
const queue = this.configurationReportQueues.get(controllerId) ?? { pending: /* @__PURE__ */ new Map(), processing: false };
|
|
5971
6299
|
this.configurationReportQueues.set(controllerId, queue);
|
|
5972
6300
|
if (queue.processing) {
|
|
5973
|
-
|
|
6301
|
+
let revision2;
|
|
6302
|
+
try {
|
|
6303
|
+
revision2 = this.configurationReportRevision(payload);
|
|
6304
|
+
} catch (error) {
|
|
6305
|
+
this.context.logger.warn(
|
|
6306
|
+
`Dropping invalid WAGO configuration report for controller ${controllerId}: ${error.message}`
|
|
6307
|
+
);
|
|
6308
|
+
return;
|
|
6309
|
+
}
|
|
5974
6310
|
const key = revision2 ?? Number.NaN;
|
|
5975
6311
|
if (queue.pending.has(key) || queue.pending.size < MAX_PENDING_CONFIGURATION_REPORTS)
|
|
5976
6312
|
queue.pending.set(key, payload);
|
|
@@ -5986,7 +6322,7 @@ var WagoService = class extends WagoSubscriptions {
|
|
|
5986
6322
|
async onConfigurationReported(controllerId, payload) {
|
|
5987
6323
|
let report;
|
|
5988
6324
|
try {
|
|
5989
|
-
report = parseConfigurationReport(
|
|
6325
|
+
report = parseConfigurationReport(parseControllerJson(payload, "configuration report"));
|
|
5990
6326
|
} catch {
|
|
5991
6327
|
this.context.logger.warn(`Ignoring invalid WAGO configuration report for controller ${controllerId}`);
|
|
5992
6328
|
return;
|
|
@@ -6040,32 +6376,32 @@ var WagoService = class extends WagoSubscriptions {
|
|
|
6040
6376
|
}
|
|
6041
6377
|
};
|
|
6042
6378
|
WagoService = __decorateClass([
|
|
6043
|
-
(0,
|
|
6044
|
-
__decorateParam(0, (0,
|
|
6379
|
+
(0, import_common10.Injectable)(),
|
|
6380
|
+
__decorateParam(0, (0, import_common10.Inject)(PLUGIN_CONTEXT))
|
|
6045
6381
|
], WagoService);
|
|
6046
6382
|
|
|
6047
6383
|
// backend/commissioning/delivery/preflight.ts
|
|
6048
|
-
var
|
|
6384
|
+
var import_common11 = require("@nestjs/common");
|
|
6049
6385
|
var import_node_crypto8 = require("node:crypto");
|
|
6050
6386
|
function assertCommissioningBroker(config, now = Date.now()) {
|
|
6051
6387
|
if (!/^[a-zA-Z0-9.-]+$/.test(config.host) || !Number.isInteger(config.port) || config.port < 1 || config.port > 65535)
|
|
6052
|
-
throw new
|
|
6388
|
+
throw new import_common11.ConflictException("Configure a valid MQTT hostname and port before commissioning.");
|
|
6053
6389
|
if (!config.useTls || config.tlsInsecure) return;
|
|
6054
6390
|
if (config.caCert == null) return;
|
|
6055
6391
|
const certificates = config.caCert.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
|
|
6056
6392
|
if (!certificates?.length || config.caCert.replace(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g, "").trim())
|
|
6057
|
-
throw new
|
|
6393
|
+
throw new import_common11.ConflictException("The MQTT private CA must contain PEM certificates only.");
|
|
6058
6394
|
for (const pem of certificates) {
|
|
6059
6395
|
let certificate;
|
|
6060
6396
|
try {
|
|
6061
6397
|
certificate = new import_node_crypto8.X509Certificate(pem);
|
|
6062
6398
|
} catch {
|
|
6063
|
-
throw new
|
|
6399
|
+
throw new import_common11.ConflictException("The MQTT private CA certificate is invalid. Import a valid PEM CA certificate.");
|
|
6064
6400
|
}
|
|
6065
6401
|
if (!certificate.ca)
|
|
6066
|
-
throw new
|
|
6402
|
+
throw new import_common11.ConflictException("The MQTT trust bundle must contain CA certificates, not server certificates.");
|
|
6067
6403
|
if (now < Date.parse(certificate.validFrom) || now > Date.parse(certificate.validTo))
|
|
6068
|
-
throw new
|
|
6404
|
+
throw new import_common11.ConflictException(
|
|
6069
6405
|
"The MQTT CA certificate is expired or not yet valid. Check the server clock and renew the CA."
|
|
6070
6406
|
);
|
|
6071
6407
|
}
|
|
@@ -6075,7 +6411,7 @@ function assertCommissioningBroker(config, now = Date.now()) {
|
|
|
6075
6411
|
init_catalog();
|
|
6076
6412
|
|
|
6077
6413
|
// backend/commissioning/readiness/readiness.ts
|
|
6078
|
-
var
|
|
6414
|
+
var import_common14 = require("@nestjs/common");
|
|
6079
6415
|
var import_plugins_backend_sdk8 = require("@attraccess/plugins-backend-sdk");
|
|
6080
6416
|
function isReadiness(value) {
|
|
6081
6417
|
return !!value && typeof value === "object" && "configurationAccepted" in value && typeof value.configurationAccepted === "boolean" && "hardwareAvailable" in value && typeof value.hardwareAvailable === "boolean" && "ready" in value && typeof value.ready === "boolean";
|
|
@@ -6107,10 +6443,6 @@ var WagoCommissioningReadiness = class {
|
|
|
6107
6443
|
const current = entry;
|
|
6108
6444
|
void this.context.mqtt.subscribe(serverId, topic, (message) => {
|
|
6109
6445
|
if (message.serverId !== serverId || message.topic !== topic) return;
|
|
6110
|
-
if (message.payload.length > 65536) {
|
|
6111
|
-
current.state = void 0;
|
|
6112
|
-
return;
|
|
6113
|
-
}
|
|
6114
6446
|
try {
|
|
6115
6447
|
const parsed = parseOperationalMessage(prefix, topic, message.payload);
|
|
6116
6448
|
if (!parsed || parsed.hardwareId !== hardwareId || parsed.message.category !== "state") {
|
|
@@ -6120,7 +6452,7 @@ var WagoCommissioningReadiness = class {
|
|
|
6120
6452
|
const event = parsed.message;
|
|
6121
6453
|
const timestamp = Date.parse(event.timestamp);
|
|
6122
6454
|
const now = Date.now();
|
|
6123
|
-
const value =
|
|
6455
|
+
const value = parseObject(message.payload, "readiness state");
|
|
6124
6456
|
const readiness = value.readiness;
|
|
6125
6457
|
if (timestamp > now + CONTROLLER_CLOCK_TOLERANCE_MS || event.contentHash !== null && !/^[a-f0-9]{64}$/i.test(event.contentHash) || !isReadiness(readiness)) {
|
|
6126
6458
|
current.state = void 0;
|
|
@@ -6163,12 +6495,12 @@ var WagoCommissioningReadiness = class {
|
|
|
6163
6495
|
}
|
|
6164
6496
|
};
|
|
6165
6497
|
WagoCommissioningReadiness = __decorateClass([
|
|
6166
|
-
(0,
|
|
6167
|
-
__decorateParam(0, (0,
|
|
6498
|
+
(0, import_common14.Injectable)(),
|
|
6499
|
+
__decorateParam(0, (0, import_common14.Inject)(import_plugins_backend_sdk8.PLUGIN_CONTEXT))
|
|
6168
6500
|
], WagoCommissioningReadiness);
|
|
6169
6501
|
|
|
6170
6502
|
// backend/runtime/managed/service.ts
|
|
6171
|
-
var
|
|
6503
|
+
var import_common19 = require("@nestjs/common");
|
|
6172
6504
|
var import_node_crypto17 = require("node:crypto");
|
|
6173
6505
|
|
|
6174
6506
|
// backend/host/shell/stat.ts
|
|
@@ -9672,7 +10004,7 @@ function managedSetupFailure(stage, error, timedOut) {
|
|
|
9672
10004
|
init_catalog();
|
|
9673
10005
|
|
|
9674
10006
|
// backend/runtime/managed/reconciliation.ts
|
|
9675
|
-
var
|
|
10007
|
+
var import_common18 = require("@nestjs/common");
|
|
9676
10008
|
|
|
9677
10009
|
// backend/commissioning/sessions/verification.ts
|
|
9678
10010
|
async function commissioningVerification(context, session, runtime) {
|
|
@@ -9927,11 +10259,11 @@ async function managedSsh(access, privateKey, header, signal, file) {
|
|
|
9927
10259
|
}
|
|
9928
10260
|
|
|
9929
10261
|
// backend/runtime/managed/access.ts
|
|
9930
|
-
var
|
|
10262
|
+
var import_common17 = require("@nestjs/common");
|
|
9931
10263
|
|
|
9932
10264
|
// backend/runtime/managed/enrollment.ts
|
|
9933
10265
|
var import_node_crypto15 = require("node:crypto");
|
|
9934
|
-
var
|
|
10266
|
+
var import_common16 = require("@nestjs/common");
|
|
9935
10267
|
|
|
9936
10268
|
// backend/network/entity.ts
|
|
9937
10269
|
var import_plugins_backend_sdk9 = require("@attraccess/plugins-backend-sdk");
|
|
@@ -10230,7 +10562,7 @@ var WagoDeviceOperations = class {
|
|
|
10230
10562
|
};
|
|
10231
10563
|
|
|
10232
10564
|
// backend/runtime/managed/state.ts
|
|
10233
|
-
var
|
|
10565
|
+
var import_common15 = require("@nestjs/common");
|
|
10234
10566
|
init_catalog();
|
|
10235
10567
|
var WagoManagedRuntimeServiceState = class {
|
|
10236
10568
|
constructor(context, wago, artifacts, readiness) {
|
|
@@ -10262,10 +10594,10 @@ var WagoManagedRuntimeServiceState = class {
|
|
|
10262
10594
|
}
|
|
10263
10595
|
};
|
|
10264
10596
|
WagoManagedRuntimeServiceState = __decorateClass([
|
|
10265
|
-
__decorateParam(0, (0,
|
|
10266
|
-
__decorateParam(1, (0,
|
|
10267
|
-
__decorateParam(2, (0,
|
|
10268
|
-
__decorateParam(3, (0,
|
|
10597
|
+
__decorateParam(0, (0, import_common15.Inject)(/* @__PURE__ */ Symbol.for("attraccess.plugin.context"))),
|
|
10598
|
+
__decorateParam(1, (0, import_common15.Inject)(WagoService)),
|
|
10599
|
+
__decorateParam(2, (0, import_common15.Inject)(WagoRuntimeArtifactsService)),
|
|
10600
|
+
__decorateParam(3, (0, import_common15.Inject)(WagoCommissioningReadiness))
|
|
10269
10601
|
], WagoManagedRuntimeServiceState);
|
|
10270
10602
|
|
|
10271
10603
|
// backend/runtime/managed/enrollment.ts
|
|
@@ -10328,21 +10660,21 @@ var WagoManagedRuntimeEnrollment = class extends WagoManagedRuntimeServiceState
|
|
|
10328
10660
|
if (controllerId === null && !fingerprint2) return;
|
|
10329
10661
|
const change = await this.context.getRepository(WagoNetworkChange).findOneBy(controllerId === null ? { fingerprint: fingerprint2 } : { controllerId });
|
|
10330
10662
|
if (change && change.phase !== "completed")
|
|
10331
|
-
throw new
|
|
10663
|
+
throw new import_common16.ConflictException("Finish the pending MQTT/address change before another controller operation.");
|
|
10332
10664
|
}
|
|
10333
10665
|
async assertUpdateSettled(controllerId) {
|
|
10334
10666
|
await this.assertNetworkSettled(controllerId);
|
|
10335
10667
|
const row = await this.updates.findOneBy({ controllerId });
|
|
10336
10668
|
const update = row?.metadata ? JSON.parse(row.metadata) : null;
|
|
10337
|
-
if (update?.token) throw new
|
|
10669
|
+
if (update?.token) throw new import_common16.ConflictException("Finish runtime update recovery before removing this controller");
|
|
10338
10670
|
}
|
|
10339
10671
|
async assertRemovable(controllerId) {
|
|
10340
10672
|
await this.assertUpdateSettled(controllerId);
|
|
10341
10673
|
if (await this.context.getRepository(WagoMqttCredentialRetirement).countBy({ controllerId }))
|
|
10342
|
-
throw new
|
|
10674
|
+
throw new import_common16.ConflictException("Retire previous broker credentials before removing this controller.");
|
|
10343
10675
|
const access = await this.access.findOne({ where: { controllerId }, order: { sessionId: "DESC" } });
|
|
10344
10676
|
if (access && access.state !== "retired")
|
|
10345
|
-
throw new
|
|
10677
|
+
throw new import_common16.ConflictException("Restore bootstrap SSH and retire managed access before removing this controller");
|
|
10346
10678
|
}
|
|
10347
10679
|
async retire(controllerId) {
|
|
10348
10680
|
await this.assertRemovable(controllerId);
|
|
@@ -10351,7 +10683,7 @@ var WagoManagedRuntimeEnrollment = class extends WagoManagedRuntimeServiceState
|
|
|
10351
10683
|
}
|
|
10352
10684
|
async recoverPassword(sessionId, principal) {
|
|
10353
10685
|
const access = await this.loadSession(sessionId);
|
|
10354
|
-
if (!access) throw new
|
|
10686
|
+
if (!access) throw new import_common16.NotFoundException("Managed recovery credential not found");
|
|
10355
10687
|
const operationId = (0, import_node_crypto15.randomUUID)();
|
|
10356
10688
|
await this.securityAudit(sessionId, "root_recovery", principal, operationId, "attempted");
|
|
10357
10689
|
try {
|
|
@@ -10438,7 +10770,7 @@ var WagoManagedRuntimeEnrollment = class extends WagoManagedRuntimeServiceState
|
|
|
10438
10770
|
const desired = await this.desired();
|
|
10439
10771
|
let access = await this.loadSession(session.id);
|
|
10440
10772
|
if (access && (access.fingerprint !== session.hostKeyFingerprint || access.host !== session.targetHost || ["retiring", "retired"].includes(access.state)))
|
|
10441
|
-
throw new
|
|
10773
|
+
throw new import_common16.ConflictException("Managed identity changed; create a fresh enrolment session.");
|
|
10442
10774
|
if (!access) {
|
|
10443
10775
|
const key = generateManagementKey();
|
|
10444
10776
|
const credentials2 = {
|
|
@@ -10454,7 +10786,7 @@ var WagoManagedRuntimeEnrollment = class extends WagoManagedRuntimeServiceState
|
|
|
10454
10786
|
const plaintext = JSON.stringify(credentials2);
|
|
10455
10787
|
const encryptedCredentials = this.context.secrets.encrypt(plaintext);
|
|
10456
10788
|
if (!encryptedCredentials || encryptedCredentials === plaintext || this.context.secrets.decrypt(encryptedCredentials) !== plaintext)
|
|
10457
|
-
throw new
|
|
10789
|
+
throw new import_common16.ConflictException("Managed credential encryption failed.");
|
|
10458
10790
|
access = await this.access.save(
|
|
10459
10791
|
this.access.create({
|
|
10460
10792
|
sessionId: session.id,
|
|
@@ -10469,11 +10801,11 @@ var WagoManagedRuntimeEnrollment = class extends WagoManagedRuntimeServiceState
|
|
|
10469
10801
|
);
|
|
10470
10802
|
key.privateKey = "";
|
|
10471
10803
|
access = await this.loadSession(session.id);
|
|
10472
|
-
if (!access) throw new
|
|
10804
|
+
if (!access) throw new import_common16.ConflictException("Managed credential storage verification failed.");
|
|
10473
10805
|
}
|
|
10474
10806
|
const credentials = this.credentials(access);
|
|
10475
10807
|
if (credentials.hardwareId !== session.hardwareId)
|
|
10476
|
-
throw new
|
|
10808
|
+
throw new import_common16.ConflictException("Managed credentials belong to another controller identity");
|
|
10477
10809
|
let stage = "filesystem";
|
|
10478
10810
|
try {
|
|
10479
10811
|
if (access.state !== "verified" && access.state !== "managed") {
|
|
@@ -10508,7 +10840,7 @@ var WagoManagedRuntimeEnrollment = class extends WagoManagedRuntimeServiceState
|
|
|
10508
10840
|
const access = await this.loadSession(sessionId);
|
|
10509
10841
|
if (!access) return;
|
|
10510
10842
|
if (access.state !== "verified" || access.controllerId !== null && access.controllerId !== controllerId)
|
|
10511
|
-
throw new
|
|
10843
|
+
throw new import_common16.ConflictException("Managed enrolment is not verified for this controller");
|
|
10512
10844
|
await this.access.update(sessionId, { controllerId });
|
|
10513
10845
|
}
|
|
10514
10846
|
registerRetirementProbe(probe) {
|
|
@@ -10612,7 +10944,7 @@ var WagoManagedRuntimeAccess = class extends WagoManagedRuntimeEnrollment {
|
|
|
10612
10944
|
installerPublicKey(value.installerPrivateKey);
|
|
10613
10945
|
return value;
|
|
10614
10946
|
} catch {
|
|
10615
|
-
throw new
|
|
10947
|
+
throw new import_common17.ConflictException("Managed credential envelope is unavailable or does not match this device");
|
|
10616
10948
|
}
|
|
10617
10949
|
}
|
|
10618
10950
|
async required(controllerId) {
|
|
@@ -10635,29 +10967,29 @@ var WagoManagedRuntimeAccess = class extends WagoManagedRuntimeEnrollment {
|
|
|
10635
10967
|
async retryRuntime(controllerId) {
|
|
10636
10968
|
await this.required(controllerId);
|
|
10637
10969
|
if (await this.coordinator.reconcile(controllerId, true) === "busy")
|
|
10638
|
-
throw new
|
|
10970
|
+
throw new import_common17.ConflictException("Controller is busy; retry after its current operation finishes");
|
|
10639
10971
|
}
|
|
10640
10972
|
async setActiveState(sessionId, state) {
|
|
10641
10973
|
const result = await this.access.createQueryBuilder().update().set({ state }).where("session_id = :sessionId AND state NOT IN (:...retired)", {
|
|
10642
10974
|
sessionId,
|
|
10643
10975
|
retired: ["retiring", "retired"]
|
|
10644
10976
|
}).execute();
|
|
10645
|
-
if (result.affected !== 1) throw new
|
|
10977
|
+
if (result.affected !== 1) throw new import_common17.ConflictException("Managed access is being retired");
|
|
10646
10978
|
}
|
|
10647
10979
|
async retryAccess(sessionId) {
|
|
10648
10980
|
let access = await this.loadSession(sessionId);
|
|
10649
10981
|
if (!access || ["retiring", "retired"].includes(access.state))
|
|
10650
|
-
throw new
|
|
10982
|
+
throw new import_common17.ConflictException("Managed access cannot be retried");
|
|
10651
10983
|
const fingerprint2 = access.fingerprint;
|
|
10652
10984
|
const owner = (0, import_node_crypto16.randomBytes)(16).toString("hex");
|
|
10653
10985
|
if (!await this.operations.acquire(access.fingerprint, owner, Date.now(), Date.now() + 6e4))
|
|
10654
|
-
throw new
|
|
10986
|
+
throw new import_common17.ConflictException("Controller is busy");
|
|
10655
10987
|
const operation = new AbortController();
|
|
10656
10988
|
const timer = setTimeout(() => operation.abort(), 5e4).unref();
|
|
10657
10989
|
try {
|
|
10658
10990
|
access = await this.loadSession(sessionId);
|
|
10659
10991
|
if (!access || ["retiring", "retired"].includes(access.state))
|
|
10660
|
-
throw new
|
|
10992
|
+
throw new import_common17.ConflictException("Managed access cannot be retried");
|
|
10661
10993
|
if (access.controllerId) await this.assertNetworkSettled(access.controllerId);
|
|
10662
10994
|
await this.prove(access, operation.signal);
|
|
10663
10995
|
const status = await this.connection(access, `access-status ${access.token}`, operation.signal);
|
|
@@ -10670,7 +11002,7 @@ var WagoManagedRuntimeAccess = class extends WagoManagedRuntimeEnrollment {
|
|
|
10670
11002
|
await this.setActiveState(sessionId, "verified");
|
|
10671
11003
|
await this.connection(access, `access-key-commit ${access.token}`, operation.signal);
|
|
10672
11004
|
await this.prove(access, operation.signal);
|
|
10673
|
-
} else throw new
|
|
11005
|
+
} else throw new import_common17.ConflictException("Wait for the SSH-policy watchdog to restore access before retrying");
|
|
10674
11006
|
} finally {
|
|
10675
11007
|
clearTimeout(timer);
|
|
10676
11008
|
operation.abort();
|
|
@@ -10681,11 +11013,11 @@ var WagoManagedRuntimeAccess = class extends WagoManagedRuntimeEnrollment {
|
|
|
10681
11013
|
async restoreAccess(sessionId, principal) {
|
|
10682
11014
|
const access = await this.loadSession(sessionId);
|
|
10683
11015
|
if (!access || !this.rootProbe || !this.retirementProbe)
|
|
10684
|
-
throw new
|
|
11016
|
+
throw new import_common17.NotFoundException("Managed recovery access not found");
|
|
10685
11017
|
if (access.controllerId) await this.assertUpdateSettled(access.controllerId);
|
|
10686
11018
|
const owner = (0, import_node_crypto16.randomBytes)(16).toString("hex");
|
|
10687
11019
|
if (!await this.operations.acquire(access.fingerprint, owner, Date.now(), Date.now() + 6e4))
|
|
10688
|
-
throw new
|
|
11020
|
+
throw new import_common17.ConflictException("Controller is busy");
|
|
10689
11021
|
const operation = new AbortController();
|
|
10690
11022
|
const timer = setTimeout(() => operation.abort(), 5e4).unref();
|
|
10691
11023
|
const operationId = (0, import_node_crypto16.randomUUID)();
|
|
@@ -10709,12 +11041,12 @@ var WagoManagedRuntimeAccess = class extends WagoManagedRuntimeEnrollment {
|
|
|
10709
11041
|
await new Promise((resolve2) => setTimeout(resolve2, 1e3));
|
|
10710
11042
|
}
|
|
10711
11043
|
if (!restored)
|
|
10712
|
-
throw new
|
|
11044
|
+
throw new import_common17.ConflictException("Bootstrap SSH restoration is unverified; retry administrator recovery");
|
|
10713
11045
|
await this.operations.assertOwned(access.fingerprint, owner);
|
|
10714
11046
|
await this.connection(access, `access-retire ${access.token}`, operation.signal).catch(() => void 0);
|
|
10715
11047
|
}
|
|
10716
11048
|
if (!await this.retirementProbe(access.host, access.fingerprint, password))
|
|
10717
|
-
throw new
|
|
11049
|
+
throw new import_common17.ConflictException("Managed key retirement is unverified; retry administrator recovery");
|
|
10718
11050
|
await this.operations.assertOwned(access.fingerprint, owner);
|
|
10719
11051
|
await this.access.update(sessionId, { state: "retired" });
|
|
10720
11052
|
await this.securityAudit(sessionId, "security_recover", principal, operationId, "succeeded");
|
|
@@ -10739,7 +11071,7 @@ var WagoManagedRuntimeAccess = class extends WagoManagedRuntimeEnrollment {
|
|
|
10739
11071
|
if (!access || !this.rootProbe || !["pending", "verified", "recovery_required"].includes(access.state)) return null;
|
|
10740
11072
|
const credentials = this.credentials(access);
|
|
10741
11073
|
if (access.host !== session.targetHost || access.fingerprint !== session.hostKeyFingerprint || credentials.hardwareId !== session.hardwareId)
|
|
10742
|
-
throw new
|
|
11074
|
+
throw new import_common17.ConflictException("Managed recovery identity changed; check the saved controller identity.");
|
|
10743
11075
|
return await this.rootProbe(access.host, access.fingerprint, credentials.recoveryPassword) ? credentials.recoveryPassword : null;
|
|
10744
11076
|
}
|
|
10745
11077
|
async hasAccess(sessionId) {
|
|
@@ -10759,7 +11091,7 @@ var WagoManagedRuntimeReconciliation = class extends WagoManagedRuntimeAccess {
|
|
|
10759
11091
|
details: {}
|
|
10760
11092
|
});
|
|
10761
11093
|
if (receipt.status !== "recorded")
|
|
10762
|
-
throw new
|
|
11094
|
+
throw new import_common18.ConflictException("Durable audit is required for managed SSH recovery and transitions");
|
|
10763
11095
|
}
|
|
10764
11096
|
async finishSession(id) {
|
|
10765
11097
|
await this.sessions.update(id, {
|
|
@@ -11252,21 +11584,21 @@ var WagoManagedRuntimeService = class extends WagoManagedRuntimeReconciliation {
|
|
|
11252
11584
|
}
|
|
11253
11585
|
};
|
|
11254
11586
|
WagoManagedRuntimeService = __decorateClass([
|
|
11255
|
-
(0,
|
|
11256
|
-
__decorateParam(0, (0,
|
|
11257
|
-
__decorateParam(1, (0,
|
|
11258
|
-
__decorateParam(2, (0,
|
|
11259
|
-
__decorateParam(3, (0,
|
|
11587
|
+
(0, import_common19.Injectable)(),
|
|
11588
|
+
__decorateParam(0, (0, import_common19.Inject)(/* @__PURE__ */ Symbol.for("attraccess.plugin.context"))),
|
|
11589
|
+
__decorateParam(1, (0, import_common19.Inject)(WagoService)),
|
|
11590
|
+
__decorateParam(2, (0, import_common19.Inject)(WagoRuntimeArtifactsService)),
|
|
11591
|
+
__decorateParam(3, (0, import_common19.Inject)(WagoCommissioningReadiness))
|
|
11260
11592
|
], WagoManagedRuntimeService);
|
|
11261
11593
|
|
|
11262
11594
|
// backend/commissioning/model.ts
|
|
11263
|
-
var
|
|
11595
|
+
var import_common20 = require("@nestjs/common");
|
|
11264
11596
|
function requireDeliveryCredentials(input) {
|
|
11265
11597
|
if (input?.confirmInstall !== true)
|
|
11266
|
-
throw new
|
|
11598
|
+
throw new import_common20.ConflictException("explicit installation confirmation is required for every delivery attempt");
|
|
11267
11599
|
const credential = input.temporarySsh;
|
|
11268
11600
|
if (!credential || typeof credential.username !== "string" || !/^[a-zA-Z_][a-zA-Z0-9_.-]{0,63}$/.test(credential.username) || typeof credential.password !== "string" || !credential.password.trim() || /[\r\n\0]/.test(credential.password))
|
|
11269
|
-
throw new
|
|
11601
|
+
throw new import_common20.ConflictException("explicit valid SSH username and password are required for every delivery attempt");
|
|
11270
11602
|
return credential;
|
|
11271
11603
|
}
|
|
11272
11604
|
var WagoStorageCapacityError = class extends Error {
|
|
@@ -11279,7 +11611,7 @@ var WagoControllerLockError = class extends Error {
|
|
|
11279
11611
|
super("The CC100 is busy with a runtime operation. Retry installation shortly; no preparation was started.");
|
|
11280
11612
|
}
|
|
11281
11613
|
};
|
|
11282
|
-
var RuntimeReleaseChangedError = class extends
|
|
11614
|
+
var RuntimeReleaseChangedError = class extends import_common20.ConflictException {
|
|
11283
11615
|
constructor() {
|
|
11284
11616
|
super(
|
|
11285
11617
|
"The runtime release changed during delivery. Recover any retained installation and retry with the current release."
|
|
@@ -11556,7 +11888,7 @@ timeout -s KILL 10 /etc/config-tools/config_clock type=utc "time=$time" "date=$d
|
|
|
11556
11888
|
}
|
|
11557
11889
|
|
|
11558
11890
|
// backend/commissioning/sessions/lifecycle.ts
|
|
11559
|
-
var
|
|
11891
|
+
var import_common28 = require("@nestjs/common");
|
|
11560
11892
|
|
|
11561
11893
|
// backend/commissioning/delivery/transport.ts
|
|
11562
11894
|
var import_promises8 = require("node:fs/promises");
|
|
@@ -11564,7 +11896,7 @@ var import_node_os5 = require("node:os");
|
|
|
11564
11896
|
var import_node_path6 = require("node:path");
|
|
11565
11897
|
|
|
11566
11898
|
// backend/commissioning/delivery/host-identity.ts
|
|
11567
|
-
var
|
|
11899
|
+
var import_common21 = require("@nestjs/common");
|
|
11568
11900
|
var import_promises4 = require("node:fs/promises");
|
|
11569
11901
|
var import_node_os3 = require("node:os");
|
|
11570
11902
|
var import_node_path4 = require("node:path");
|
|
@@ -11660,14 +11992,14 @@ function scanHostKeys(host) {
|
|
|
11660
11992
|
}
|
|
11661
11993
|
async function pinnedHostKey(host, expectedFingerprint) {
|
|
11662
11994
|
const key = (await scanHostKeys(host)).split("\n").find((line) => line.includes("ssh-ed25519"));
|
|
11663
|
-
if (!key) throw new
|
|
11995
|
+
if (!key) throw new import_common21.ConflictException("the controller did not provide an Ed25519 SSH host key");
|
|
11664
11996
|
const dir = await (0, import_promises4.mkdtemp)((0, import_node_path4.join)((0, import_node_os3.tmpdir)(), "attraccess-cc100-"));
|
|
11665
11997
|
const knownHosts = (0, import_node_path4.join)(dir, "known_hosts");
|
|
11666
11998
|
try {
|
|
11667
11999
|
await (0, import_promises4.writeFile)(knownHosts, `${key}
|
|
11668
12000
|
`, { mode: 384 });
|
|
11669
12001
|
if (await fingerprintFor(knownHosts) !== expectedFingerprint)
|
|
11670
|
-
throw new
|
|
12002
|
+
throw new import_common21.ConflictException("controller SSH host key changed after automatic identity verification");
|
|
11671
12003
|
return `${key}
|
|
11672
12004
|
`;
|
|
11673
12005
|
} finally {
|
|
@@ -11679,7 +12011,7 @@ async function fingerprintFor(knownHosts) {
|
|
|
11679
12011
|
timeoutMs: 5e3,
|
|
11680
12012
|
maxOutputBytes: 1024
|
|
11681
12013
|
})).match(/(SHA256:[A-Za-z0-9+/=]+)/)?.[1];
|
|
11682
|
-
if (!result) throw new
|
|
12014
|
+
if (!result) throw new import_common21.ConflictException("the controller did not provide a supported SSH host key");
|
|
11683
12015
|
return result;
|
|
11684
12016
|
}
|
|
11685
12017
|
|
|
@@ -11783,13 +12115,13 @@ var ManagementPeerVersion = class {
|
|
|
11783
12115
|
};
|
|
11784
12116
|
|
|
11785
12117
|
// backend/commissioning/delivery/transport.ts
|
|
11786
|
-
var
|
|
12118
|
+
var import_common27 = require("@nestjs/common");
|
|
11787
12119
|
|
|
11788
12120
|
// backend/commissioning/sessions/ownership.ts
|
|
11789
|
-
var
|
|
12121
|
+
var import_common26 = require("@nestjs/common");
|
|
11790
12122
|
|
|
11791
12123
|
// backend/commissioning/delivery/recovery.ts
|
|
11792
|
-
var
|
|
12124
|
+
var import_common25 = require("@nestjs/common");
|
|
11793
12125
|
var import_node_crypto22 = require("node:crypto");
|
|
11794
12126
|
init_hardware_profile();
|
|
11795
12127
|
var import_promises7 = require("node:fs/promises");
|
|
@@ -11798,7 +12130,7 @@ var import_promises7 = require("node:fs/promises");
|
|
|
11798
12130
|
var import_promises6 = require("node:fs/promises");
|
|
11799
12131
|
var import_node_os4 = require("node:os");
|
|
11800
12132
|
var import_node_path5 = require("node:path");
|
|
11801
|
-
var
|
|
12133
|
+
var import_common24 = require("@nestjs/common");
|
|
11802
12134
|
|
|
11803
12135
|
// backend/management/model.ts
|
|
11804
12136
|
var import_node_crypto19 = require("node:crypto");
|
|
@@ -12129,7 +12461,7 @@ init_hardware_profile();
|
|
|
12129
12461
|
var import_node_crypto21 = require("node:crypto");
|
|
12130
12462
|
|
|
12131
12463
|
// backend/commissioning/sessions/sessions.ts
|
|
12132
|
-
var
|
|
12464
|
+
var import_common23 = require("@nestjs/common");
|
|
12133
12465
|
var import_node_crypto20 = require("node:crypto");
|
|
12134
12466
|
|
|
12135
12467
|
// backend/management/entity.ts
|
|
@@ -12465,7 +12797,7 @@ function createWagoManagementService(context, ssh) {
|
|
|
12465
12797
|
}
|
|
12466
12798
|
|
|
12467
12799
|
// backend/commissioning/state.ts
|
|
12468
|
-
var
|
|
12800
|
+
var import_common22 = require("@nestjs/common");
|
|
12469
12801
|
var import_node_async_hooks = require("node:async_hooks");
|
|
12470
12802
|
var import_plugins_backend_sdk13 = require("@attraccess/plugins-backend-sdk");
|
|
12471
12803
|
init_catalog();
|
|
@@ -12486,21 +12818,21 @@ var WagoCommissioningServiceState = class {
|
|
|
12486
12818
|
}
|
|
12487
12819
|
};
|
|
12488
12820
|
WagoCommissioningServiceState = __decorateClass([
|
|
12489
|
-
__decorateParam(0, (0,
|
|
12490
|
-
__decorateParam(1, (0,
|
|
12491
|
-
__decorateParam(2, (0,
|
|
12492
|
-
__decorateParam(2, (0,
|
|
12493
|
-
__decorateParam(3, (0,
|
|
12494
|
-
__decorateParam(3, (0,
|
|
12495
|
-
__decorateParam(4, (0,
|
|
12496
|
-
__decorateParam(4, (0,
|
|
12821
|
+
__decorateParam(0, (0, import_common22.Inject)(import_plugins_backend_sdk13.PLUGIN_CONTEXT)),
|
|
12822
|
+
__decorateParam(1, (0, import_common22.Inject)(WagoService)),
|
|
12823
|
+
__decorateParam(2, (0, import_common22.Optional)()),
|
|
12824
|
+
__decorateParam(2, (0, import_common22.Inject)(WagoRuntimeArtifactsService)),
|
|
12825
|
+
__decorateParam(3, (0, import_common22.Optional)()),
|
|
12826
|
+
__decorateParam(3, (0, import_common22.Inject)(WagoCommissioningReadiness)),
|
|
12827
|
+
__decorateParam(4, (0, import_common22.Optional)()),
|
|
12828
|
+
__decorateParam(4, (0, import_common22.Inject)(WagoManagedRuntimeService))
|
|
12497
12829
|
], WagoCommissioningServiceState);
|
|
12498
12830
|
|
|
12499
12831
|
// backend/commissioning/sessions/sessions.ts
|
|
12500
12832
|
var WagoCommissioningSessions = class extends WagoCommissioningServiceState {
|
|
12501
12833
|
async verification(id) {
|
|
12502
12834
|
const session = await this.sessions.findOneBy({ id });
|
|
12503
|
-
if (!session) throw new
|
|
12835
|
+
if (!session) throw new import_common23.NotFoundException("commissioning session not found");
|
|
12504
12836
|
const settings = this.readiness ? await this.wago.getSettings() : null;
|
|
12505
12837
|
const runtime = this.readiness?.observe(session.mqttServerId, session.hardwareId, settings.operationalPrefix);
|
|
12506
12838
|
const verification = await commissioningVerification(this.context, session, runtime);
|
|
@@ -12519,14 +12851,14 @@ var WagoCommissioningSessions = class extends WagoCommissioningServiceState {
|
|
|
12519
12851
|
}
|
|
12520
12852
|
async confirmHostKey(id, hostKeyFingerprint, trustMethod = "trusted_inventory", physicalIdentityConfirmed = false) {
|
|
12521
12853
|
if (!["trusted_inventory", "isolated_service_connection"].includes(trustMethod) || trustMethod === "isolated_service_connection" && physicalIdentityConfirmed !== true)
|
|
12522
|
-
throw new
|
|
12854
|
+
throw new import_common23.ConflictException("Confirm the isolated service connection and physical controller identity.");
|
|
12523
12855
|
return this.withDeliveryLock(id, async () => {
|
|
12524
12856
|
const session = await this.sessions.findOneBy({ id });
|
|
12525
|
-
if (!session) throw new
|
|
12857
|
+
if (!session) throw new import_common23.NotFoundException("commissioning session not found");
|
|
12526
12858
|
if (session.state !== "awaiting_identity_confirmation")
|
|
12527
|
-
throw new
|
|
12859
|
+
throw new import_common23.ConflictException("commissioning session identity cannot be confirmed in its current state");
|
|
12528
12860
|
if (hostKeyFingerprint !== session.hostKeyFingerprint)
|
|
12529
|
-
throw new
|
|
12861
|
+
throw new import_common23.ConflictException(
|
|
12530
12862
|
"the supplied SSH host-key fingerprint does not match the scanned controller identity"
|
|
12531
12863
|
);
|
|
12532
12864
|
session.state = "awaiting_delivery";
|
|
@@ -12538,24 +12870,24 @@ var WagoCommissioningSessions = class extends WagoCommissioningServiceState {
|
|
|
12538
12870
|
}
|
|
12539
12871
|
async create(input, principal = null) {
|
|
12540
12872
|
if (!isPrivateAddress(input.targetHost))
|
|
12541
|
-
throw new
|
|
12542
|
-
if (!input.name.trim()) throw new
|
|
12873
|
+
throw new import_common23.ConflictException("commissioning is limited to a protected controller address");
|
|
12874
|
+
if (!input.name.trim()) throw new import_common23.ConflictException("a controller name is required");
|
|
12543
12875
|
if (!configuredFirmwareBaseline)
|
|
12544
|
-
throw new
|
|
12876
|
+
throw new import_common23.ConflictException(
|
|
12545
12877
|
"no CC100 firmware baseline is configured; commissioning is disabled until an exact supported baseline is set"
|
|
12546
12878
|
);
|
|
12547
12879
|
let brokerAvailable;
|
|
12548
12880
|
try {
|
|
12549
12881
|
brokerAvailable = Boolean(await this.context.getMqttServerConfig(input.mqttServerId));
|
|
12550
12882
|
} catch {
|
|
12551
|
-
throw new
|
|
12883
|
+
throw new import_common23.ConflictException("Commissioning MQTT configuration could not be resolved.");
|
|
12552
12884
|
}
|
|
12553
|
-
if (!brokerAvailable) throw new
|
|
12885
|
+
if (!brokerAvailable) throw new import_common23.NotFoundException("MQTT server not found");
|
|
12554
12886
|
let hostKeyFingerprint;
|
|
12555
12887
|
try {
|
|
12556
12888
|
hostKeyFingerprint = await scanHostKey(input.targetHost);
|
|
12557
12889
|
} catch {
|
|
12558
|
-
throw new
|
|
12890
|
+
throw new import_common23.ConflictException("Commissioning SSH host-key scan failed.");
|
|
12559
12891
|
}
|
|
12560
12892
|
const hardwareId = `cc100-${(0, import_node_crypto20.createHash)("sha256").update(hostKeyFingerprint).digest("hex").slice(0, 16)}`;
|
|
12561
12893
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -12617,7 +12949,7 @@ var WagoCommissioningSessions = class extends WagoCommissioningServiceState {
|
|
|
12617
12949
|
Buffer.from(script).toString("base64") + "\n",
|
|
12618
12950
|
{ timeoutMs: 12 * 6e4, maxOutputBytes: 1024, recoveryDiagnostic: true }
|
|
12619
12951
|
);
|
|
12620
|
-
if (result !== "OK\n") throw new
|
|
12952
|
+
if (result !== "OK\n") throw new import_common23.ConflictException("Commissioning acceptance could not be confirmed.");
|
|
12621
12953
|
await guard.assertOwned();
|
|
12622
12954
|
});
|
|
12623
12955
|
}
|
|
@@ -12741,12 +13073,12 @@ var WagoCommissioningPlatform = class extends WagoCommissioningSessions {
|
|
|
12741
13073
|
id,
|
|
12742
13074
|
() => this.withDeliveryLock(id, async () => {
|
|
12743
13075
|
const session = await this.sessions.findOneBy({ id });
|
|
12744
|
-
if (!session) throw new
|
|
13076
|
+
if (!session) throw new import_common24.NotFoundException("commissioning session not found");
|
|
12745
13077
|
if (["awaiting_identity_confirmation", "delivering"].includes(session.state) || session.state === "revoked" && action !== "recover")
|
|
12746
|
-
throw new
|
|
13078
|
+
throw new import_common24.ConflictException("Confirm controller identity and finish delivery before management changes.");
|
|
12747
13079
|
if (!session.managementControllerId) {
|
|
12748
13080
|
const controller = await this.context.getRepository(WagoController).findOneBy({ hardwareId: session.hardwareId, mqttServerId: session.mqttServerId });
|
|
12749
|
-
if (!controller) throw new
|
|
13081
|
+
if (!controller) throw new import_common24.ConflictException("Wait for controller enrollment before management inspection.");
|
|
12750
13082
|
session.managementControllerId = controller.id;
|
|
12751
13083
|
await this.save(session, "management_subject_bound");
|
|
12752
13084
|
}
|
|
@@ -12783,7 +13115,7 @@ var WagoCommissioningPlatform = class extends WagoCommissioningSessions {
|
|
|
12783
13115
|
this.operationContext.getStore()?.assertOwned
|
|
12784
13116
|
);
|
|
12785
13117
|
} catch (error) {
|
|
12786
|
-
throw new
|
|
13118
|
+
throw new import_common24.ConflictException(
|
|
12787
13119
|
error instanceof ManagementError ? `Management security: ${error.code}` : "Management security request failed."
|
|
12788
13120
|
);
|
|
12789
13121
|
}
|
|
@@ -12802,15 +13134,15 @@ var WagoCommissioningPlatform = class extends WagoCommissioningSessions {
|
|
|
12802
13134
|
}
|
|
12803
13135
|
async managementStatus(id) {
|
|
12804
13136
|
const session = await this.sessions.findOneBy({ id });
|
|
12805
|
-
if (!session) throw new
|
|
13137
|
+
if (!session) throw new import_common24.NotFoundException("commissioning session not found");
|
|
12806
13138
|
return session.managementControllerId ? this.management.status(session.managementControllerId) : null;
|
|
12807
13139
|
}
|
|
12808
13140
|
/** Persist ownership before destructive host changes so interruption is recoverable. */
|
|
12809
13141
|
async prepareController(session, credential, verifiedBundleBytes, hardwareProfile = CC100_DIGITAL_PROFILE_ID) {
|
|
12810
13142
|
if (session.deliveryToken)
|
|
12811
|
-
throw new
|
|
13143
|
+
throw new import_common24.ConflictException("Clean up the retained runtime installation before preparing the controller.");
|
|
12812
13144
|
if (session.dockerProvisionToken && session.dockerProvisionState !== "started")
|
|
12813
|
-
throw new
|
|
13145
|
+
throw new import_common24.ConflictException("Clean up the retained controller preparation before retrying.");
|
|
12814
13146
|
await this.sudoRunScript(
|
|
12815
13147
|
session.targetHost,
|
|
12816
13148
|
session.hostKeyFingerprint,
|
|
@@ -12880,7 +13212,7 @@ var WagoCommissioningPlatform = class extends WagoCommissioningSessions {
|
|
|
12880
13212
|
async platform(id, action, input, principal = null) {
|
|
12881
13213
|
const credential = requireDeliveryCredentials({ temporarySsh: input.temporarySsh, confirmInstall: true });
|
|
12882
13214
|
if (action !== "inspect" && input.reviewedDockerActivation !== true)
|
|
12883
|
-
throw new
|
|
13215
|
+
throw new import_common24.ConflictException("Explicit Docker activation or recovery approval is required.");
|
|
12884
13216
|
return auditCommissioning(
|
|
12885
13217
|
this.context,
|
|
12886
13218
|
principal,
|
|
@@ -12890,9 +13222,9 @@ var WagoCommissioningPlatform = class extends WagoCommissioningSessions {
|
|
|
12890
13222
|
id,
|
|
12891
13223
|
() => this.withDeliveryLock(id, async () => {
|
|
12892
13224
|
const session = await this.sessions.findOneBy({ id });
|
|
12893
|
-
if (!session) throw new
|
|
13225
|
+
if (!session) throw new import_common24.NotFoundException("commissioning session not found");
|
|
12894
13226
|
if (["awaiting_identity_confirmation", "delivering"].includes(session.state) || session.state === "revoked" && action !== "recover")
|
|
12895
|
-
throw new
|
|
13227
|
+
throw new import_common24.ConflictException("Finish identity confirmation and any active delivery first.");
|
|
12896
13228
|
try {
|
|
12897
13229
|
if (action === "inspect") {
|
|
12898
13230
|
session.platformReport = null;
|
|
@@ -12919,13 +13251,13 @@ var WagoCommissioningPlatform = class extends WagoCommissioningSessions {
|
|
|
12919
13251
|
}
|
|
12920
13252
|
} else {
|
|
12921
13253
|
if (!session.dockerProvisionToken)
|
|
12922
|
-
throw new
|
|
13254
|
+
throw new import_common24.ConflictException("No Docker provisioning attempt to recover.");
|
|
12923
13255
|
await this.cleanupControllerPreparation(session, credential);
|
|
12924
13256
|
}
|
|
12925
13257
|
session.failureReason = null;
|
|
12926
13258
|
return this.toResponse(await this.save(session, `platform_${action}_succeeded`));
|
|
12927
13259
|
} catch (error) {
|
|
12928
|
-
if (error instanceof
|
|
13260
|
+
if (error instanceof import_common24.ConflictException) throw error;
|
|
12929
13261
|
if (action !== "inspect" && session.dockerProvisionToken)
|
|
12930
13262
|
session.dockerProvisionState = "recovery_required";
|
|
12931
13263
|
session.failureReason = error instanceof WagoStorageCapacityError ? error.message : error instanceof WagoControllerLockError ? error.message : error instanceof WagoRecoveryError ? error.message : action === "inspect" ? "Controller preflight could not be read. Check the explicit SSH credential and supported firmware tools." : action === "activate" ? "Controller preparation failed. Check the runtime release, staging storage and required tools. CODESYS must be stopped and permanently disabled before IO or runtime startup. Clean up any retained preparation attempt before retrying." : "Controller preparation cleanup remains unverified. Clean up any runtime transaction first, then retry preparation cleanup. The recovery token is retained; previous workloads are not restored.";
|
|
@@ -12957,7 +13289,7 @@ var WagoCommissioningPlatform = class extends WagoCommissioningSessions {
|
|
|
12957
13289
|
var WagoCommissioningRecovery = class extends WagoCommissioningPlatform {
|
|
12958
13290
|
async withControllerLock(id, operation) {
|
|
12959
13291
|
const session = await this.sessions.findOneBy({ id });
|
|
12960
|
-
if (!session) throw new
|
|
13292
|
+
if (!session) throw new import_common25.NotFoundException("commissioning session not found");
|
|
12961
13293
|
const key = session.hostKeyFingerprint || session.targetHost || session.hardwareId;
|
|
12962
13294
|
const previous = this.controllerLocks.get(key) ?? Promise.resolve();
|
|
12963
13295
|
let release;
|
|
@@ -12973,7 +13305,7 @@ var WagoCommissioningRecovery = class extends WagoCommissioningPlatform {
|
|
|
12973
13305
|
deadline,
|
|
12974
13306
|
assertOwned: async () => {
|
|
12975
13307
|
if (finished || controller.signal.aborted || Date.now() >= deadline)
|
|
12976
|
-
throw new
|
|
13308
|
+
throw new import_common25.ConflictException("Controller operation has stopped. Retry the request.");
|
|
12977
13309
|
}
|
|
12978
13310
|
};
|
|
12979
13311
|
try {
|
|
@@ -12982,7 +13314,7 @@ var WagoCommissioningRecovery = class extends WagoCommissioningPlatform {
|
|
|
12982
13314
|
if (this.managedRuntime) {
|
|
12983
13315
|
deviceOperations = new WagoDeviceOperations(this.context.getRepository(WagoDeviceOperation));
|
|
12984
13316
|
if (!await deviceOperations.acquire(key, owner, Date.now(), deadline + 6e4))
|
|
12985
|
-
throw new
|
|
13317
|
+
throw new import_common25.ConflictException("Another controller operation is active. Retry after it finishes.");
|
|
12986
13318
|
}
|
|
12987
13319
|
const originalAssert = guard.assertOwned;
|
|
12988
13320
|
guard.assertOwned = async () => {
|
|
@@ -13011,7 +13343,7 @@ var WagoCommissioningRecovery = class extends WagoCommissioningPlatform {
|
|
|
13011
13343
|
id,
|
|
13012
13344
|
() => this.withDeliveryLock(id, async () => {
|
|
13013
13345
|
const session = await this.sessions.findOneBy({ id });
|
|
13014
|
-
if (!session) throw new
|
|
13346
|
+
if (!session) throw new import_common25.NotFoundException("commissioning session not found");
|
|
13015
13347
|
if (![
|
|
13016
13348
|
"delivery_failed",
|
|
13017
13349
|
"awaiting_discovery",
|
|
@@ -13022,10 +13354,10 @@ var WagoCommissioningRecovery = class extends WagoCommissioningPlatform {
|
|
|
13022
13354
|
"delivering",
|
|
13023
13355
|
"awaiting_codesys_confirmation"
|
|
13024
13356
|
].includes(session.state))
|
|
13025
|
-
throw new
|
|
13357
|
+
throw new import_common25.ConflictException("commissioning session cannot be recovered in its current state");
|
|
13026
13358
|
const requiresNewSession = !session.pairingCode || ["claim_interrupted", "awaiting_verification"].includes(session.state);
|
|
13027
13359
|
if (!session.deliveryToken)
|
|
13028
|
-
throw new
|
|
13360
|
+
throw new import_common25.ConflictException("commissioning session has no runtime recovery ownership token");
|
|
13029
13361
|
let restored = session.state === "recovery_revocation_pending";
|
|
13030
13362
|
try {
|
|
13031
13363
|
const recoveryPassword = await this.managedRuntime?.commissioningRecoveryPassword?.(session);
|
|
@@ -13085,7 +13417,7 @@ var WagoCommissioningRecovery = class extends WagoCommissioningPlatform {
|
|
|
13085
13417
|
}
|
|
13086
13418
|
async acquireRuntimeBundle(session) {
|
|
13087
13419
|
await this.requireRuntimeArtifact();
|
|
13088
|
-
if (!this.artifacts) throw new
|
|
13420
|
+
if (!this.artifacts) throw new import_common25.ConflictException("Bundled CC100 runtime is unavailable.");
|
|
13089
13421
|
const acquired = await this.artifacts.acquire();
|
|
13090
13422
|
const bundle = {
|
|
13091
13423
|
...acquired,
|
|
@@ -13104,13 +13436,13 @@ var WagoCommissioningRecovery = class extends WagoCommissioningPlatform {
|
|
|
13104
13436
|
}
|
|
13105
13437
|
async requireRuntimeArtifact() {
|
|
13106
13438
|
if (!await this.artifacts?.has())
|
|
13107
|
-
throw new
|
|
13439
|
+
throw new import_common25.ConflictException("Build and install the bundled CC100 runtime before installation.");
|
|
13108
13440
|
}
|
|
13109
13441
|
async loadDeliverableSession(id) {
|
|
13110
13442
|
const session = await this.sessions.findOneBy({ id });
|
|
13111
|
-
if (!session) throw new
|
|
13443
|
+
if (!session) throw new import_common25.NotFoundException("commissioning session not found");
|
|
13112
13444
|
if (!["awaiting_delivery", "delivering", "awaiting_codesys_confirmation", "delivery_failed"].includes(session.state))
|
|
13113
|
-
throw new
|
|
13445
|
+
throw new import_common25.ConflictException("commissioning session cannot be delivered in its current state");
|
|
13114
13446
|
return session;
|
|
13115
13447
|
}
|
|
13116
13448
|
async deliver(id, input = {}, principal = null) {
|
|
@@ -13207,7 +13539,7 @@ var WagoCommissioningOwnership = class extends WagoCommissioningRecovery {
|
|
|
13207
13539
|
/** The HTTP audit wrapper belongs inside remove(), so removal is audited once by ATT-983. */
|
|
13208
13540
|
async removeControllerSafely(id, remove) {
|
|
13209
13541
|
const controller = await this.context.getRepository(WagoController).findOneBy({ id });
|
|
13210
|
-
if (!controller) throw new
|
|
13542
|
+
if (!controller) throw new import_common26.NotFoundException("controller not found");
|
|
13211
13543
|
const sessions = await this.sessions.find({ where: { hardwareId: controller.hardwareId }, order: { id: "DESC" } });
|
|
13212
13544
|
if (!sessions.length) {
|
|
13213
13545
|
await remove(async () => void 0);
|
|
@@ -13215,7 +13547,7 @@ var WagoCommissioningOwnership = class extends WagoCommissioningRecovery {
|
|
|
13215
13547
|
}
|
|
13216
13548
|
await this.withControllerLock(sessions[0].id, async () => {
|
|
13217
13549
|
const guard = this.operationContext.getStore();
|
|
13218
|
-
if (!guard) throw new
|
|
13550
|
+
if (!guard) throw new import_common26.ConflictException("Controller operation ownership is unavailable.");
|
|
13219
13551
|
const assertOwned = guard.assertOwned;
|
|
13220
13552
|
await assertOwned();
|
|
13221
13553
|
await this.managedRuntime?.assertRemovable(id);
|
|
@@ -13247,15 +13579,15 @@ var WagoCommissioningOwnership = class extends WagoCommissioningRecovery {
|
|
|
13247
13579
|
}
|
|
13248
13580
|
async operateControllerSafely(id, operation, requireCommissioningSession = false) {
|
|
13249
13581
|
const controller = await this.context.getRepository(WagoController).findOneBy({ id });
|
|
13250
|
-
if (!controller) throw new
|
|
13582
|
+
if (!controller) throw new import_common26.NotFoundException("controller not found");
|
|
13251
13583
|
const sessions = await this.sessions.find({ where: { hardwareId: controller.hardwareId }, order: { id: "DESC" } });
|
|
13252
13584
|
if (!sessions.length) {
|
|
13253
13585
|
if (requireCommissioningSession)
|
|
13254
|
-
throw new
|
|
13586
|
+
throw new import_common26.ConflictException("A commissioning session must remain pinned while rotating credentials.");
|
|
13255
13587
|
const controller2 = new AbortController();
|
|
13256
13588
|
const guard = {
|
|
13257
13589
|
assertOwned: async () => {
|
|
13258
|
-
if (controller2.signal.aborted) throw new
|
|
13590
|
+
if (controller2.signal.aborted) throw new import_common26.ConflictException("Controller operation ended");
|
|
13259
13591
|
},
|
|
13260
13592
|
signal: controller2.signal,
|
|
13261
13593
|
deadline: Date.now() + 6e4
|
|
@@ -13268,7 +13600,7 @@ var WagoCommissioningOwnership = class extends WagoCommissioningRecovery {
|
|
|
13268
13600
|
}
|
|
13269
13601
|
return this.withControllerLock(sessions[0].id, async () => {
|
|
13270
13602
|
const guard = this.operationContext.getStore();
|
|
13271
|
-
if (!guard) throw new
|
|
13603
|
+
if (!guard) throw new import_common26.ConflictException("Controller operation ownership is unavailable.");
|
|
13272
13604
|
await guard.assertOwned();
|
|
13273
13605
|
return operation(guard.assertOwned, guard);
|
|
13274
13606
|
});
|
|
@@ -13282,26 +13614,26 @@ var WagoCommissioningOwnership = class extends WagoCommissioningRecovery {
|
|
|
13282
13614
|
id,
|
|
13283
13615
|
() => this.withDeliveryLock(id, async () => {
|
|
13284
13616
|
const session = await this.sessions.findOneBy({ id });
|
|
13285
|
-
if (!session) throw new
|
|
13617
|
+
if (!session) throw new import_common26.NotFoundException("commissioning session not found");
|
|
13286
13618
|
if (session.deliveryToken)
|
|
13287
|
-
throw new
|
|
13619
|
+
throw new import_common26.ConflictException("Clean up the retained runtime installation before deleting this session.");
|
|
13288
13620
|
if (session.dockerProvisionToken)
|
|
13289
|
-
throw new
|
|
13621
|
+
throw new import_common26.ConflictException("Clean up the retained controller preparation before deleting this session.");
|
|
13290
13622
|
if (await this.managedRuntime?.hasAccess(id))
|
|
13291
|
-
throw new
|
|
13623
|
+
throw new import_common26.ConflictException(
|
|
13292
13624
|
"Retain this session for audited managed SSH recovery. Re-enrolment creates a fresh management identity."
|
|
13293
13625
|
);
|
|
13294
13626
|
if (session.managementControllerId) {
|
|
13295
13627
|
const security = await this.management.status(session.managementControllerId);
|
|
13296
13628
|
if (security && (security.recoveryRequired || ["key_enrolled", "hardened"].includes(security.state)))
|
|
13297
|
-
throw new
|
|
13629
|
+
throw new import_common26.ConflictException("Recover the saved management access before deleting this session.");
|
|
13298
13630
|
}
|
|
13299
13631
|
if (session.enrollmentId !== null) {
|
|
13300
13632
|
try {
|
|
13301
13633
|
await this.wago.revokeEnrollmentById(session.enrollmentId, this.operationContext.getStore()?.assertOwned);
|
|
13302
13634
|
await this.wago.deleteEnrollmentById(session.enrollmentId, this.operationContext.getStore()?.assertOwned);
|
|
13303
13635
|
} catch {
|
|
13304
|
-
throw new
|
|
13636
|
+
throw new import_common26.ConflictException("Commissioning enrollment removal failed.");
|
|
13305
13637
|
}
|
|
13306
13638
|
}
|
|
13307
13639
|
await this.operationContext.getStore()?.assertOwned();
|
|
@@ -13314,7 +13646,7 @@ var WagoCommissioningOwnership = class extends WagoCommissioningRecovery {
|
|
|
13314
13646
|
id,
|
|
13315
13647
|
() => this.withDeliveryLock(id, async () => {
|
|
13316
13648
|
const session = await this.sessions.findOneBy({ id });
|
|
13317
|
-
if (!session) throw new
|
|
13649
|
+
if (!session) throw new import_common26.NotFoundException("commissioning session not found");
|
|
13318
13650
|
await this.revokeSessionEnrollment(session);
|
|
13319
13651
|
session.state = "revoked";
|
|
13320
13652
|
session.pairingCode = null;
|
|
@@ -13392,7 +13724,7 @@ printf '%s\\n' "$ATTRACCESS_SSH_PASSWORD"
|
|
|
13392
13724
|
pinnedKey = await pinnedHostKey(host, fingerprint2);
|
|
13393
13725
|
} catch (error) {
|
|
13394
13726
|
if (limits2?.recoveryDiagnostic)
|
|
13395
|
-
throw new WagoRecoveryError(error instanceof
|
|
13727
|
+
throw new WagoRecoveryError(error instanceof import_common27.ConflictException ? "identity" : "transport");
|
|
13396
13728
|
throw error;
|
|
13397
13729
|
}
|
|
13398
13730
|
await (0, import_promises8.writeFile)(knownHosts, pinnedKey, { mode: 384 });
|
|
@@ -13619,7 +13951,7 @@ var WagoCommissioningProgress = class extends WagoCommissioningTransport {
|
|
|
13619
13951
|
}
|
|
13620
13952
|
if (page.length < 100) break;
|
|
13621
13953
|
}
|
|
13622
|
-
if (recoveryFailed) throw new
|
|
13954
|
+
if (recoveryFailed) throw new import_common28.ConflictException("Commissioning recovery requires credential revocation.");
|
|
13623
13955
|
}
|
|
13624
13956
|
async invalidateVerifier(session) {
|
|
13625
13957
|
session.state = "revoked";
|
|
@@ -13638,7 +13970,7 @@ var WagoCommissioningProgress = class extends WagoCommissioningTransport {
|
|
|
13638
13970
|
try {
|
|
13639
13971
|
await this.wago.revokeEnrollmentById(session.enrollmentId, this.operationContext.getStore()?.assertOwned);
|
|
13640
13972
|
} catch {
|
|
13641
|
-
throw new
|
|
13973
|
+
throw new import_common28.ConflictException("Commissioning credential revocation requires attention.");
|
|
13642
13974
|
}
|
|
13643
13975
|
session.enrollmentId = null;
|
|
13644
13976
|
session.enrollmentExpiresAt = null;
|
|
@@ -13651,14 +13983,14 @@ var WagoCommissioningProgress = class extends WagoCommissioningTransport {
|
|
|
13651
13983
|
if (!/^[A-Za-z0-9_-]{43}$/.test(plaintext)) throw new Error();
|
|
13652
13984
|
return plaintext;
|
|
13653
13985
|
} catch {
|
|
13654
|
-
throw new
|
|
13986
|
+
throw new import_common28.ConflictException("Commissioning verifier is unavailable; create a new session.");
|
|
13655
13987
|
}
|
|
13656
13988
|
}
|
|
13657
13989
|
encryptVerifier(plaintext) {
|
|
13658
13990
|
try {
|
|
13659
13991
|
return VERIFIER_PREFIX + this.context.secrets.encrypt(plaintext);
|
|
13660
13992
|
} catch {
|
|
13661
|
-
throw new
|
|
13993
|
+
throw new import_common28.ConflictException("Commissioning verifier encryption failed.");
|
|
13662
13994
|
}
|
|
13663
13995
|
}
|
|
13664
13996
|
reportPreparationProgress(session, checkpoint) {
|
|
@@ -13748,11 +14080,11 @@ var WagoCommissioningService = class extends WagoCommissioningProgress {
|
|
|
13748
14080
|
};
|
|
13749
14081
|
try {
|
|
13750
14082
|
const broker = await this.context.getMqttServerConfig(session.mqttServerId);
|
|
13751
|
-
if (!broker) throw new
|
|
14083
|
+
if (!broker) throw new import_common29.ConflictException("MQTT server not found");
|
|
13752
14084
|
try {
|
|
13753
14085
|
assertCommissioningBroker(broker);
|
|
13754
14086
|
} catch (error) {
|
|
13755
|
-
if (error instanceof
|
|
14087
|
+
if (error instanceof import_common29.ConflictException) attempt.safeFailure = error.message;
|
|
13756
14088
|
throw error;
|
|
13757
14089
|
}
|
|
13758
14090
|
const providers = await this.context.getMqttCredentialProvisioning().availableProviders(session.mqttServerId);
|
|
@@ -13969,22 +14301,22 @@ var WagoCommissioningService = class extends WagoCommissioningProgress {
|
|
|
13969
14301
|
}
|
|
13970
14302
|
};
|
|
13971
14303
|
WagoCommissioningService = __decorateClass([
|
|
13972
|
-
(0,
|
|
13973
|
-
__decorateParam(0, (0,
|
|
13974
|
-
__decorateParam(1, (0,
|
|
13975
|
-
__decorateParam(2, (0,
|
|
13976
|
-
__decorateParam(2, (0,
|
|
13977
|
-
__decorateParam(3, (0,
|
|
13978
|
-
__decorateParam(3, (0,
|
|
13979
|
-
__decorateParam(4, (0,
|
|
13980
|
-
__decorateParam(4, (0,
|
|
14304
|
+
(0, import_common29.Injectable)(),
|
|
14305
|
+
__decorateParam(0, (0, import_common29.Inject)(import_plugins_backend_sdk14.PLUGIN_CONTEXT)),
|
|
14306
|
+
__decorateParam(1, (0, import_common29.Inject)(WagoService)),
|
|
14307
|
+
__decorateParam(2, (0, import_common29.Optional)()),
|
|
14308
|
+
__decorateParam(2, (0, import_common29.Inject)(WagoRuntimeArtifactsService)),
|
|
14309
|
+
__decorateParam(3, (0, import_common29.Optional)()),
|
|
14310
|
+
__decorateParam(3, (0, import_common29.Inject)(WagoCommissioningReadiness)),
|
|
14311
|
+
__decorateParam(4, (0, import_common29.Optional)()),
|
|
14312
|
+
__decorateParam(4, (0, import_common29.Inject)(WagoManagedRuntimeService))
|
|
13981
14313
|
], WagoCommissioningService);
|
|
13982
14314
|
|
|
13983
14315
|
// backend/credentials/service.ts
|
|
13984
14316
|
var import_plugins_backend_sdk15 = require("@attraccess/plugins-backend-sdk");
|
|
13985
|
-
var
|
|
14317
|
+
var import_common30 = require("@nestjs/common");
|
|
13986
14318
|
var import_node_crypto24 = require("node:crypto");
|
|
13987
|
-
var WagoCredentialRotationUncertainError = class extends
|
|
14319
|
+
var WagoCredentialRotationUncertainError = class extends import_common30.ConflictException {
|
|
13988
14320
|
constructor() {
|
|
13989
14321
|
super("Credential rotation is incomplete. Inspect its recovery state and retry the pending handoff.");
|
|
13990
14322
|
}
|
|
@@ -14032,9 +14364,9 @@ var CAPABILITY = "credential-rotation-v1";
|
|
|
14032
14364
|
var WAIT_MS = 3e4;
|
|
14033
14365
|
function assertRotationController(controller, retry) {
|
|
14034
14366
|
if (controller.trustState !== "claimed" || !controller.mqttServerId || controller.compatibilityError)
|
|
14035
|
-
throw new
|
|
14367
|
+
throw new import_common30.ConflictException("A compatible claimed controller is required.");
|
|
14036
14368
|
if ("credentialMqttServerId" in controller && controller.credentialMqttServerId != null && controller.credentialMqttServerId !== controller.mqttServerId)
|
|
14037
|
-
throw new
|
|
14369
|
+
throw new import_common30.ConflictException("Use the original credential broker before rotating credentials.");
|
|
14038
14370
|
let capabilities;
|
|
14039
14371
|
try {
|
|
14040
14372
|
capabilities = JSON.parse(controller.capabilities);
|
|
@@ -14042,22 +14374,22 @@ function assertRotationController(controller, retry) {
|
|
|
14042
14374
|
capabilities = [];
|
|
14043
14375
|
}
|
|
14044
14376
|
if (!Array.isArray(capabilities) || !capabilities.includes(CAPABILITY))
|
|
14045
|
-
throw new
|
|
14377
|
+
throw new import_common30.ConflictException("Install a runtime supporting credential-rotation-v1 before rotating credentials.");
|
|
14046
14378
|
const heartbeatAt = sourceTime(controller.lastHeartbeatAt);
|
|
14047
14379
|
const now = Date.now();
|
|
14048
14380
|
if (!retry && (heartbeatAt === null || heartbeatAt > now + CONTROLLER_CLOCK_TOLERANCE_MS || now - heartbeatAt >= 9e4))
|
|
14049
|
-
throw new
|
|
14381
|
+
throw new import_common30.ConflictException("Wait for a fresh permanent controller heartbeat before rotating credentials.");
|
|
14050
14382
|
}
|
|
14051
14383
|
function checkPendingRotation(previous, credentialEpoch, retry, mqttServerId, prefix) {
|
|
14052
14384
|
if (previous && previous.credentialEpoch !== credentialEpoch)
|
|
14053
|
-
throw new
|
|
14385
|
+
throw new import_common30.ConflictException("Recover the previous credential registration before rotating again.");
|
|
14054
14386
|
if (previous && previous.phase !== "completed" && !retry)
|
|
14055
|
-
throw new
|
|
14387
|
+
throw new import_common30.ConflictException("Retry the pending credential handoff instead of rotating again.");
|
|
14056
14388
|
if (retry && (!previous || previous.phase === "provisioning"))
|
|
14057
|
-
throw new
|
|
14389
|
+
throw new import_common30.ConflictException("Broker rotation completion is uncertain. Recover broker credentials before retrying.");
|
|
14058
14390
|
if (retry && previous?.phase === "completed") return { state: "completed", revision: previous.revision };
|
|
14059
14391
|
if (previous && (previous.mqttServerId !== mqttServerId || previous.prefix !== prefix))
|
|
14060
|
-
throw new
|
|
14392
|
+
throw new import_common30.ConflictException("Credential rotation must use its original broker and namespace.");
|
|
14061
14393
|
return void 0;
|
|
14062
14394
|
}
|
|
14063
14395
|
var WagoCredentialRotationService = class {
|
|
@@ -14085,10 +14417,10 @@ var WagoCredentialRotationService = class {
|
|
|
14085
14417
|
(async () => {
|
|
14086
14418
|
await guard.assertOwned();
|
|
14087
14419
|
const pending = await this.context.mqtt.subscribe(row.mqttServerId, `${topic}/ack`, (message) => {
|
|
14088
|
-
if (closed || guard.signal.aborted || message.serverId !== row.mqttServerId || message.topic !== `${topic}/ack`
|
|
14420
|
+
if (closed || guard.signal.aborted || message.serverId !== row.mqttServerId || message.topic !== `${topic}/ack`)
|
|
14089
14421
|
return;
|
|
14090
14422
|
try {
|
|
14091
|
-
const ack =
|
|
14423
|
+
const ack = parseControllerJson(message.payload, "rotation acknowledgement");
|
|
14092
14424
|
if (ack?.credentialEpoch === row.credentialEpoch && ack?.revision === row.revision && ack?.token === row.token && ack?.status === "reconnected")
|
|
14093
14425
|
acknowledge();
|
|
14094
14426
|
} catch {
|
|
@@ -14127,7 +14459,7 @@ var WagoCredentialRotationService = class {
|
|
|
14127
14459
|
async rotate(controllerId, prefix, principal, guard, retry = false) {
|
|
14128
14460
|
await guard.assertOwned();
|
|
14129
14461
|
const controller = await this.context.getRepository(WagoController).findOneBy({ id: controllerId });
|
|
14130
|
-
if (!controller) throw new
|
|
14462
|
+
if (!controller) throw new import_common30.NotFoundException("WAGO controller not found");
|
|
14131
14463
|
assertRotationController(controller, retry);
|
|
14132
14464
|
const [epochRow] = await this.repository.query(
|
|
14133
14465
|
"SELECT credential_epoch AS epoch FROM plugin_wago_controllers WHERE id = ?",
|
|
@@ -14135,7 +14467,7 @@ var WagoCredentialRotationService = class {
|
|
|
14135
14467
|
);
|
|
14136
14468
|
const credentialEpoch = epochRow?.epoch;
|
|
14137
14469
|
if (typeof credentialEpoch !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(credentialEpoch))
|
|
14138
|
-
throw new
|
|
14470
|
+
throw new import_common30.ConflictException("Re-enroll this controller with a credential epoch before rotating credentials.");
|
|
14139
14471
|
prefix = normalizeOperationalPrefix(prefix);
|
|
14140
14472
|
const identity = `wago-controller-${controller.hardwareId}`;
|
|
14141
14473
|
const previous = await this.repository.createQueryBuilder("rotation").addSelect("rotation.encryptedCredentials").where("rotation.controllerId = :controllerId", { controllerId }).getOne();
|
|
@@ -14148,7 +14480,7 @@ var WagoCredentialRotationService = class {
|
|
|
14148
14480
|
let row = previous;
|
|
14149
14481
|
if (!retry) {
|
|
14150
14482
|
const revision2 = (previous?.revision ?? 0) + 1;
|
|
14151
|
-
if (!Number.isSafeInteger(revision2)) throw new
|
|
14483
|
+
if (!Number.isSafeInteger(revision2)) throw new import_common30.ConflictException("Credential rotation revision exhausted.");
|
|
14152
14484
|
row = this.repository.create({
|
|
14153
14485
|
controllerId,
|
|
14154
14486
|
revision: revision2,
|
|
@@ -14179,23 +14511,23 @@ var WagoCredentialRotationService = class {
|
|
|
14179
14511
|
uncertain = false;
|
|
14180
14512
|
if (previous) await this.repository.save(previous);
|
|
14181
14513
|
else await this.repository.delete(controllerId);
|
|
14182
|
-
throw new
|
|
14514
|
+
throw new import_common30.ConflictException("Automatic broker credential rotation is unavailable.");
|
|
14183
14515
|
}
|
|
14184
14516
|
if (credential2.username !== identity || !credential2.password || credential2.password.length > 4096)
|
|
14185
|
-
throw new
|
|
14517
|
+
throw new import_common30.ConflictException("Broker credential rotation needs recovery.");
|
|
14186
14518
|
const plaintext = JSON.stringify({ username: identity, password: credential2.password });
|
|
14187
14519
|
const encrypted = this.context.secrets.encrypt(plaintext);
|
|
14188
14520
|
if (!encrypted || encrypted === plaintext || this.context.secrets.decrypt(encrypted) !== plaintext)
|
|
14189
|
-
throw new
|
|
14521
|
+
throw new import_common30.ConflictException("Credential recovery storage is unavailable.");
|
|
14190
14522
|
row.encryptedCredentials = encrypted;
|
|
14191
14523
|
row.phase = "pending";
|
|
14192
14524
|
await guard.assertOwned();
|
|
14193
14525
|
await this.repository.save(row);
|
|
14194
14526
|
}
|
|
14195
|
-
if (!row?.encryptedCredentials) throw new
|
|
14527
|
+
if (!row?.encryptedCredentials) throw new import_common30.ConflictException("Credential recovery storage is unavailable.");
|
|
14196
14528
|
const credential = JSON.parse(this.context.secrets.decrypt(row.encryptedCredentials));
|
|
14197
14529
|
if (credential.username !== identity || typeof credential.password !== "string" || !credential.password || credential.password.length > 4096)
|
|
14198
|
-
throw new
|
|
14530
|
+
throw new import_common30.ConflictException("Credential recovery storage is unavailable.");
|
|
14199
14531
|
await this.handoff(controller.hardwareId, row, credential, guard);
|
|
14200
14532
|
await guard.assertOwned();
|
|
14201
14533
|
row.phase = "completed";
|
|
@@ -14207,7 +14539,7 @@ var WagoCredentialRotationService = class {
|
|
|
14207
14539
|
} catch {
|
|
14208
14540
|
await lifecycle.finish("failed");
|
|
14209
14541
|
if (uncertain) throw new WagoCredentialRotationUncertainError();
|
|
14210
|
-
throw new
|
|
14542
|
+
throw new import_common30.ConflictException(
|
|
14211
14543
|
"Credential rotation is incomplete. Inspect its recovery state and retry the pending handoff."
|
|
14212
14544
|
);
|
|
14213
14545
|
}
|
|
@@ -14216,7 +14548,7 @@ var WagoCredentialRotationService = class {
|
|
|
14216
14548
|
async assertRemovalBroker(controllerId, mqttServerId) {
|
|
14217
14549
|
const row = await this.repository.findOneBy({ controllerId });
|
|
14218
14550
|
if (row && row.mqttServerId !== mqttServerId)
|
|
14219
|
-
throw new
|
|
14551
|
+
throw new import_common30.ConflictException("Revoke the original rotation broker identity before removing this controller.");
|
|
14220
14552
|
}
|
|
14221
14553
|
async status(controllerId) {
|
|
14222
14554
|
const row = await this.repository.findOne({ where: { controllerId }, select: { phase: true, revision: true } });
|
|
@@ -14227,15 +14559,15 @@ var WagoCredentialRotationService = class {
|
|
|
14227
14559
|
}
|
|
14228
14560
|
};
|
|
14229
14561
|
WagoCredentialRotationService = __decorateClass([
|
|
14230
|
-
(0,
|
|
14231
|
-
__decorateParam(0, (0,
|
|
14562
|
+
(0, import_common30.Injectable)(),
|
|
14563
|
+
__decorateParam(0, (0, import_common30.Inject)(import_plugins_backend_sdk15.PLUGIN_CONTEXT))
|
|
14232
14564
|
], WagoCredentialRotationService);
|
|
14233
14565
|
|
|
14234
14566
|
// backend/controllers/controller.ts
|
|
14235
14567
|
function validateCommissioningAttempt(body, intent) {
|
|
14236
|
-
if (body?.confirmInstall !== true) throw new
|
|
14568
|
+
if (body?.confirmInstall !== true) throw new import_common31.BadRequestException(`Explicit ${intent} consent is required`);
|
|
14237
14569
|
if (typeof body.temporarySsh?.username !== "string" || !body.temporarySsh.username.trim() || typeof body.temporarySsh.password !== "string" || !body.temporarySsh.password) {
|
|
14238
|
-
throw new
|
|
14570
|
+
throw new import_common31.BadRequestException("Temporary SSH username and password are required");
|
|
14239
14571
|
}
|
|
14240
14572
|
return {
|
|
14241
14573
|
confirmInstall: true,
|
|
@@ -14283,7 +14615,7 @@ var WagoControllerApi = class {
|
|
|
14283
14615
|
return this.wago.saveDraft(id, body?.snapshot, body?.metadata, wagoAuditPrincipal(request), body?.expectedDraft);
|
|
14284
14616
|
}
|
|
14285
14617
|
applyPreset(id, body, request) {
|
|
14286
|
-
if (!body?.application) throw new
|
|
14618
|
+
if (!body?.application) throw new import_common31.BadRequestException("application is required");
|
|
14287
14619
|
return this.wago.applyPreset(
|
|
14288
14620
|
id,
|
|
14289
14621
|
body.application,
|
|
@@ -14294,7 +14626,7 @@ var WagoControllerApi = class {
|
|
|
14294
14626
|
);
|
|
14295
14627
|
}
|
|
14296
14628
|
previewPreset(id, body) {
|
|
14297
|
-
if (!body?.application) throw new
|
|
14629
|
+
if (!body?.application) throw new import_common31.BadRequestException("application is required");
|
|
14298
14630
|
return this.wago.previewPreset(id, body.application, body.snapshot);
|
|
14299
14631
|
}
|
|
14300
14632
|
presets() {
|
|
@@ -14317,9 +14649,9 @@ var WagoControllerApi = class {
|
|
|
14317
14649
|
}
|
|
14318
14650
|
async rotateCredentials(id, body, request) {
|
|
14319
14651
|
if (!body || Object.keys(body).some((key) => key !== "confirm" && key !== "retry") || body.confirm !== true)
|
|
14320
|
-
throw new
|
|
14652
|
+
throw new import_common31.BadRequestException("Explicit credential rotation consent is required");
|
|
14321
14653
|
if (body.retry !== void 0 && typeof body.retry !== "boolean")
|
|
14322
|
-
throw new
|
|
14654
|
+
throw new import_common31.BadRequestException("Invalid rotation retry flag");
|
|
14323
14655
|
const settings = await this.wago.getSettings();
|
|
14324
14656
|
return this.commissioning.operateControllerSafely(
|
|
14325
14657
|
id,
|
|
@@ -14339,7 +14671,7 @@ var WagoControllerApi = class {
|
|
|
14339
14671
|
completeManualCredentials(id, body, request) {
|
|
14340
14672
|
const principal = wagoAuditPrincipal(request);
|
|
14341
14673
|
if (!body || Object.keys(body).some((key) => !["name", "verifier", "username", "password"].includes(key)) || typeof body.name !== "string" || !body.name || typeof body.verifier !== "string" || !body.verifier || typeof body.username !== "string" || !body.username || typeof body.password !== "string" || !body.password)
|
|
14342
|
-
throw new
|
|
14674
|
+
throw new import_common31.BadRequestException("Controller name, physical verifier and provisioned credentials are required");
|
|
14343
14675
|
const input = { name: body.name, verifier: body.verifier, username: body.username, password: body.password };
|
|
14344
14676
|
return this.commissioning.operateControllerSafely(
|
|
14345
14677
|
id,
|
|
@@ -14363,7 +14695,7 @@ var WagoControllerApi = class {
|
|
|
14363
14695
|
}
|
|
14364
14696
|
manageSecurity(id, action, body, request) {
|
|
14365
14697
|
if (!["inspect", "review", "apply", "recover"].includes(action))
|
|
14366
|
-
throw new
|
|
14698
|
+
throw new import_common31.BadRequestException("Unknown management action");
|
|
14367
14699
|
return this.commissioning.manageSecurity(
|
|
14368
14700
|
id,
|
|
14369
14701
|
action,
|
|
@@ -14372,7 +14704,7 @@ var WagoControllerApi = class {
|
|
|
14372
14704
|
);
|
|
14373
14705
|
}
|
|
14374
14706
|
platformAction(id, action, body, request) {
|
|
14375
|
-
if (!["inspect", "activate", "recover"].includes(action)) throw new
|
|
14707
|
+
if (!["inspect", "activate", "recover"].includes(action)) throw new import_common31.BadRequestException("Unknown platform action");
|
|
14376
14708
|
return this.commissioning.platform(
|
|
14377
14709
|
id,
|
|
14378
14710
|
action,
|
|
@@ -14401,7 +14733,7 @@ var WagoControllerApi = class {
|
|
|
14401
14733
|
);
|
|
14402
14734
|
}
|
|
14403
14735
|
confirmCommissioningHostKey(id, body) {
|
|
14404
|
-
if (!body?.hostKeyFingerprint) throw new
|
|
14736
|
+
if (!body?.hostKeyFingerprint) throw new import_common31.BadRequestException("SSH host-key fingerprint is required");
|
|
14405
14737
|
return this.commissioning.confirmHostKey(
|
|
14406
14738
|
id,
|
|
14407
14739
|
body.hostKeyFingerprint,
|
|
@@ -14410,8 +14742,8 @@ var WagoControllerApi = class {
|
|
|
14410
14742
|
);
|
|
14411
14743
|
}
|
|
14412
14744
|
createCommissioningSession(body, request) {
|
|
14413
|
-
if (!body?.mqttServerId) throw new
|
|
14414
|
-
if (!body.name?.trim()) throw new
|
|
14745
|
+
if (!body?.mqttServerId) throw new import_common31.BadRequestException("MQTT server is required");
|
|
14746
|
+
if (!body.name?.trim()) throw new import_common31.BadRequestException("controller name is required");
|
|
14415
14747
|
return this.commissioning.create(
|
|
14416
14748
|
{
|
|
14417
14749
|
mqttServerId: body.mqttServerId,
|
|
@@ -14438,208 +14770,208 @@ var WagoControllerApi = class {
|
|
|
14438
14770
|
}
|
|
14439
14771
|
};
|
|
14440
14772
|
__decorateClass([
|
|
14441
|
-
(0,
|
|
14442
|
-
__decorateParam(0, (0,
|
|
14443
|
-
__decorateParam(1, (0,
|
|
14773
|
+
(0, import_common31.Get)("controllers/:id/configuration/revisions/:revision/preview"),
|
|
14774
|
+
__decorateParam(0, (0, import_common31.Param)("id", import_common31.ParseIntPipe)),
|
|
14775
|
+
__decorateParam(1, (0, import_common31.Param)("revision", import_common31.ParseIntPipe))
|
|
14444
14776
|
], WagoControllerApi.prototype, "previewRevision", 1);
|
|
14445
14777
|
__decorateClass([
|
|
14446
|
-
(0,
|
|
14447
|
-
__decorateParam(0, (0,
|
|
14448
|
-
__decorateParam(1, (0,
|
|
14449
|
-
__decorateParam(2, (0,
|
|
14450
|
-
__decorateParam(3, (0,
|
|
14778
|
+
(0, import_common31.Post)("controllers/:id/configuration/revisions/:revision/acknowledge-rejection"),
|
|
14779
|
+
__decorateParam(0, (0, import_common31.Param)("id", import_common31.ParseIntPipe)),
|
|
14780
|
+
__decorateParam(1, (0, import_common31.Param)("revision", import_common31.ParseIntPipe)),
|
|
14781
|
+
__decorateParam(2, (0, import_common31.Req)()),
|
|
14782
|
+
__decorateParam(3, (0, import_common31.Body)())
|
|
14451
14783
|
], WagoControllerApi.prototype, "acknowledgeRejection", 1);
|
|
14452
14784
|
__decorateClass([
|
|
14453
|
-
(0,
|
|
14454
|
-
__decorateParam(0, (0,
|
|
14455
|
-
__decorateParam(1, (0,
|
|
14456
|
-
__decorateParam(2, (0,
|
|
14457
|
-
__decorateParam(3, (0,
|
|
14785
|
+
(0, import_common31.Post)("controllers/:id/configuration/rollback/:revision"),
|
|
14786
|
+
__decorateParam(0, (0, import_common31.Param)("id", import_common31.ParseIntPipe)),
|
|
14787
|
+
__decorateParam(1, (0, import_common31.Param)("revision", import_common31.ParseIntPipe)),
|
|
14788
|
+
__decorateParam(2, (0, import_common31.Req)()),
|
|
14789
|
+
__decorateParam(3, (0, import_common31.Body)())
|
|
14458
14790
|
], WagoControllerApi.prototype, "rollback", 1);
|
|
14459
14791
|
__decorateClass([
|
|
14460
|
-
(0,
|
|
14461
|
-
__decorateParam(0, (0,
|
|
14462
|
-
__decorateParam(1, (0,
|
|
14463
|
-
__decorateParam(2, (0,
|
|
14792
|
+
(0, import_common31.Post)("controllers/:id/configuration/publish"),
|
|
14793
|
+
__decorateParam(0, (0, import_common31.Param)("id", import_common31.ParseIntPipe)),
|
|
14794
|
+
__decorateParam(1, (0, import_common31.Req)()),
|
|
14795
|
+
__decorateParam(2, (0, import_common31.Body)())
|
|
14464
14796
|
], WagoControllerApi.prototype, "publishDraft", 1);
|
|
14465
14797
|
__decorateClass([
|
|
14466
|
-
(0,
|
|
14467
|
-
__decorateParam(0, (0,
|
|
14468
|
-
__decorateParam(1, (0,
|
|
14469
|
-
__decorateParam(2, (0,
|
|
14798
|
+
(0, import_common31.Get)("controllers/:id/configuration/revisions"),
|
|
14799
|
+
__decorateParam(0, (0, import_common31.Param)("id", import_common31.ParseIntPipe)),
|
|
14800
|
+
__decorateParam(1, (0, import_common31.Query)("offset")),
|
|
14801
|
+
__decorateParam(2, (0, import_common31.Query)("limit"))
|
|
14470
14802
|
], WagoControllerApi.prototype, "revisions", 1);
|
|
14471
14803
|
__decorateClass([
|
|
14472
|
-
(0,
|
|
14473
|
-
__decorateParam(0, (0,
|
|
14804
|
+
(0, import_common31.Post)("controllers/:id/configuration/review"),
|
|
14805
|
+
__decorateParam(0, (0, import_common31.Param)("id", import_common31.ParseIntPipe))
|
|
14474
14806
|
], WagoControllerApi.prototype, "reviewDraft", 1);
|
|
14475
14807
|
__decorateClass([
|
|
14476
|
-
(0,
|
|
14477
|
-
__decorateParam(0, (0,
|
|
14478
|
-
__decorateParam(1, (0,
|
|
14808
|
+
(0, import_common31.Post)("controllers/:id/configuration/validate"),
|
|
14809
|
+
__decorateParam(0, (0, import_common31.Param)("id", import_common31.ParseIntPipe)),
|
|
14810
|
+
__decorateParam(1, (0, import_common31.Body)())
|
|
14479
14811
|
], WagoControllerApi.prototype, "validateDraft", 1);
|
|
14480
14812
|
__decorateClass([
|
|
14481
|
-
(0,
|
|
14482
|
-
__decorateParam(0, (0,
|
|
14483
|
-
__decorateParam(1, (0,
|
|
14484
|
-
__decorateParam(2, (0,
|
|
14813
|
+
(0, import_common31.Post)("controllers/:id/configuration/draft"),
|
|
14814
|
+
__decorateParam(0, (0, import_common31.Param)("id", import_common31.ParseIntPipe)),
|
|
14815
|
+
__decorateParam(1, (0, import_common31.Body)()),
|
|
14816
|
+
__decorateParam(2, (0, import_common31.Req)())
|
|
14485
14817
|
], WagoControllerApi.prototype, "saveDraft", 1);
|
|
14486
14818
|
__decorateClass([
|
|
14487
|
-
(0,
|
|
14488
|
-
__decorateParam(0, (0,
|
|
14489
|
-
__decorateParam(1, (0,
|
|
14490
|
-
__decorateParam(2, (0,
|
|
14819
|
+
(0, import_common31.Post)("controllers/:id/configuration/presets/apply"),
|
|
14820
|
+
__decorateParam(0, (0, import_common31.Param)("id", import_common31.ParseIntPipe)),
|
|
14821
|
+
__decorateParam(1, (0, import_common31.Body)()),
|
|
14822
|
+
__decorateParam(2, (0, import_common31.Req)())
|
|
14491
14823
|
], WagoControllerApi.prototype, "applyPreset", 1);
|
|
14492
14824
|
__decorateClass([
|
|
14493
|
-
(0,
|
|
14494
|
-
__decorateParam(0, (0,
|
|
14495
|
-
__decorateParam(1, (0,
|
|
14825
|
+
(0, import_common31.Post)("controllers/:id/configuration/presets/preview"),
|
|
14826
|
+
__decorateParam(0, (0, import_common31.Param)("id", import_common31.ParseIntPipe)),
|
|
14827
|
+
__decorateParam(1, (0, import_common31.Body)())
|
|
14496
14828
|
], WagoControllerApi.prototype, "previewPreset", 1);
|
|
14497
14829
|
__decorateClass([
|
|
14498
|
-
(0,
|
|
14830
|
+
(0, import_common31.Get)("configuration/presets")
|
|
14499
14831
|
], WagoControllerApi.prototype, "presets", 1);
|
|
14500
14832
|
__decorateClass([
|
|
14501
|
-
(0,
|
|
14502
|
-
__decorateParam(0, (0,
|
|
14833
|
+
(0, import_common31.Get)("controllers/:id/configuration/baseline"),
|
|
14834
|
+
__decorateParam(0, (0, import_common31.Param)("id", import_common31.ParseIntPipe))
|
|
14503
14835
|
], WagoControllerApi.prototype, "baseline", 1);
|
|
14504
14836
|
__decorateClass([
|
|
14505
|
-
(0,
|
|
14506
|
-
__decorateParam(0, (0,
|
|
14837
|
+
(0, import_common31.Get)("controllers/:id/configuration/draft"),
|
|
14838
|
+
__decorateParam(0, (0, import_common31.Param)("id", import_common31.ParseIntPipe))
|
|
14507
14839
|
], WagoControllerApi.prototype, "draft", 1);
|
|
14508
14840
|
__decorateClass([
|
|
14509
|
-
(0,
|
|
14510
|
-
__decorateParam(0, (0,
|
|
14511
|
-
__decorateParam(1, (0,
|
|
14841
|
+
(0, import_common31.Delete)("controllers/:id"),
|
|
14842
|
+
__decorateParam(0, (0, import_common31.Param)("id", import_common31.ParseIntPipe)),
|
|
14843
|
+
__decorateParam(1, (0, import_common31.Req)())
|
|
14512
14844
|
], WagoControllerApi.prototype, "removeController", 1);
|
|
14513
14845
|
__decorateClass([
|
|
14514
14846
|
(0, import_plugins_backend_sdk16.Auth)("resources.update"),
|
|
14515
|
-
(0,
|
|
14516
|
-
__decorateParam(0, (0,
|
|
14517
|
-
__decorateParam(1, (0,
|
|
14518
|
-
__decorateParam(2, (0,
|
|
14847
|
+
(0, import_common31.Post)("controllers/:id/commands"),
|
|
14848
|
+
__decorateParam(0, (0, import_common31.Param)("id", import_common31.ParseIntPipe)),
|
|
14849
|
+
__decorateParam(1, (0, import_common31.Body)()),
|
|
14850
|
+
__decorateParam(2, (0, import_common31.Req)())
|
|
14519
14851
|
], WagoControllerApi.prototype, "manualCommand", 1);
|
|
14520
14852
|
__decorateClass([
|
|
14521
14853
|
(0, import_plugins_backend_sdk16.Auth)("system.settings.manage"),
|
|
14522
|
-
(0,
|
|
14523
|
-
__decorateParam(0, (0,
|
|
14524
|
-
__decorateParam(1, (0,
|
|
14525
|
-
__decorateParam(2, (0,
|
|
14854
|
+
(0, import_common31.Post)("controllers/:id/credentials/rotate"),
|
|
14855
|
+
__decorateParam(0, (0, import_common31.Param)("id", import_common31.ParseIntPipe)),
|
|
14856
|
+
__decorateParam(1, (0, import_common31.Body)()),
|
|
14857
|
+
__decorateParam(2, (0, import_common31.Req)())
|
|
14526
14858
|
], WagoControllerApi.prototype, "rotateCredentials", 1);
|
|
14527
14859
|
__decorateClass([
|
|
14528
14860
|
(0, import_plugins_backend_sdk16.Auth)("system.settings.manage"),
|
|
14529
|
-
(0,
|
|
14530
|
-
__decorateParam(0, (0,
|
|
14861
|
+
(0, import_common31.Get)("controllers/:id/credentials/rotation"),
|
|
14862
|
+
__decorateParam(0, (0, import_common31.Param)("id", import_common31.ParseIntPipe))
|
|
14531
14863
|
], WagoControllerApi.prototype, "credentialRotationStatus", 1);
|
|
14532
14864
|
__decorateClass([
|
|
14533
14865
|
(0, import_plugins_backend_sdk16.Auth)("system.settings.manage"),
|
|
14534
|
-
(0,
|
|
14535
|
-
__decorateParam(0, (0,
|
|
14536
|
-
__decorateParam(1, (0,
|
|
14537
|
-
__decorateParam(2, (0,
|
|
14866
|
+
(0, import_common31.Post)("controllers/:id/credentials/manual/complete"),
|
|
14867
|
+
__decorateParam(0, (0, import_common31.Param)("id", import_common31.ParseIntPipe)),
|
|
14868
|
+
__decorateParam(1, (0, import_common31.Body)()),
|
|
14869
|
+
__decorateParam(2, (0, import_common31.Req)())
|
|
14538
14870
|
], WagoControllerApi.prototype, "completeManualCredentials", 1);
|
|
14539
14871
|
__decorateClass([
|
|
14540
|
-
(0,
|
|
14541
|
-
__decorateParam(0, (0,
|
|
14542
|
-
__decorateParam(1, (0,
|
|
14543
|
-
__decorateParam(2, (0,
|
|
14872
|
+
(0, import_common31.Post)("controllers/:id/claim"),
|
|
14873
|
+
__decorateParam(0, (0, import_common31.Param)("id", import_common31.ParseIntPipe)),
|
|
14874
|
+
__decorateParam(1, (0, import_common31.Body)()),
|
|
14875
|
+
__decorateParam(2, (0, import_common31.Req)())
|
|
14544
14876
|
], WagoControllerApi.prototype, "claim", 1);
|
|
14545
14877
|
__decorateClass([
|
|
14546
14878
|
(0, import_plugins_backend_sdk16.Auth)("system.settings.manage"),
|
|
14547
|
-
(0,
|
|
14548
|
-
__decorateParam(0, (0,
|
|
14879
|
+
(0, import_common31.Delete)("commissioning/sessions/:id"),
|
|
14880
|
+
__decorateParam(0, (0, import_common31.Param)("id", import_common31.ParseIntPipe))
|
|
14549
14881
|
], WagoControllerApi.prototype, "removeCommissioningSession", 1);
|
|
14550
14882
|
__decorateClass([
|
|
14551
14883
|
(0, import_plugins_backend_sdk16.Auth)("system.settings.manage"),
|
|
14552
|
-
(0,
|
|
14553
|
-
__decorateParam(0, (0,
|
|
14884
|
+
(0, import_common31.Post)("commissioning/sessions/:id/revoke"),
|
|
14885
|
+
__decorateParam(0, (0, import_common31.Param)("id", import_common31.ParseIntPipe))
|
|
14554
14886
|
], WagoControllerApi.prototype, "revokeCommissioningSession", 1);
|
|
14555
14887
|
__decorateClass([
|
|
14556
14888
|
(0, import_plugins_backend_sdk16.Auth)("system.settings.manage"),
|
|
14557
|
-
(0,
|
|
14558
|
-
__decorateParam(0, (0,
|
|
14559
|
-
__decorateParam(1, (0,
|
|
14560
|
-
__decorateParam(2, (0,
|
|
14561
|
-
__decorateParam(3, (0,
|
|
14889
|
+
(0, import_common31.Post)("commissioning/sessions/:id/management/:action"),
|
|
14890
|
+
__decorateParam(0, (0, import_common31.Param)("id", import_common31.ParseIntPipe)),
|
|
14891
|
+
__decorateParam(1, (0, import_common31.Param)("action")),
|
|
14892
|
+
__decorateParam(2, (0, import_common31.Body)()),
|
|
14893
|
+
__decorateParam(3, (0, import_common31.Req)())
|
|
14562
14894
|
], WagoControllerApi.prototype, "manageSecurity", 1);
|
|
14563
14895
|
__decorateClass([
|
|
14564
14896
|
(0, import_plugins_backend_sdk16.Auth)("system.settings.manage"),
|
|
14565
|
-
(0,
|
|
14566
|
-
__decorateParam(0, (0,
|
|
14567
|
-
__decorateParam(1, (0,
|
|
14568
|
-
__decorateParam(2, (0,
|
|
14569
|
-
__decorateParam(3, (0,
|
|
14897
|
+
(0, import_common31.Post)("commissioning/sessions/:id/platform/:action"),
|
|
14898
|
+
__decorateParam(0, (0, import_common31.Param)("id", import_common31.ParseIntPipe)),
|
|
14899
|
+
__decorateParam(1, (0, import_common31.Param)("action")),
|
|
14900
|
+
__decorateParam(2, (0, import_common31.Body)()),
|
|
14901
|
+
__decorateParam(3, (0, import_common31.Req)())
|
|
14570
14902
|
], WagoControllerApi.prototype, "platformAction", 1);
|
|
14571
14903
|
__decorateClass([
|
|
14572
14904
|
(0, import_plugins_backend_sdk16.Auth)("system.settings.manage"),
|
|
14573
|
-
(0,
|
|
14574
|
-
__decorateParam(0, (0,
|
|
14905
|
+
(0, import_common31.Get)("commissioning/sessions/:id/management"),
|
|
14906
|
+
__decorateParam(0, (0, import_common31.Param)("id", import_common31.ParseIntPipe))
|
|
14575
14907
|
], WagoControllerApi.prototype, "managementStatus", 1);
|
|
14576
14908
|
__decorateClass([
|
|
14577
14909
|
(0, import_plugins_backend_sdk16.Auth)("system.settings.manage"),
|
|
14578
|
-
(0,
|
|
14579
|
-
__decorateParam(0, (0,
|
|
14910
|
+
(0, import_common31.Get)("commissioning/sessions/:id/verification"),
|
|
14911
|
+
__decorateParam(0, (0, import_common31.Param)("id", import_common31.ParseIntPipe))
|
|
14580
14912
|
], WagoControllerApi.prototype, "commissioningVerification", 1);
|
|
14581
14913
|
__decorateClass([
|
|
14582
14914
|
(0, import_plugins_backend_sdk16.Auth)("system.settings.manage"),
|
|
14583
|
-
(0,
|
|
14584
|
-
__decorateParam(0, (0,
|
|
14585
|
-
__decorateParam(1, (0,
|
|
14586
|
-
__decorateParam(2, (0,
|
|
14915
|
+
(0, import_common31.Post)("commissioning/sessions/:id/recover"),
|
|
14916
|
+
__decorateParam(0, (0, import_common31.Param)("id", import_common31.ParseIntPipe)),
|
|
14917
|
+
__decorateParam(1, (0, import_common31.Body)()),
|
|
14918
|
+
__decorateParam(2, (0, import_common31.Req)())
|
|
14587
14919
|
], WagoControllerApi.prototype, "recoverCommissioningSession", 1);
|
|
14588
14920
|
__decorateClass([
|
|
14589
14921
|
(0, import_plugins_backend_sdk16.Auth)("system.settings.manage"),
|
|
14590
|
-
(0,
|
|
14591
|
-
__decorateParam(0, (0,
|
|
14592
|
-
__decorateParam(1, (0,
|
|
14593
|
-
__decorateParam(2, (0,
|
|
14922
|
+
(0, import_common31.Post)("commissioning/sessions/:id/deliver"),
|
|
14923
|
+
__decorateParam(0, (0, import_common31.Param)("id", import_common31.ParseIntPipe)),
|
|
14924
|
+
__decorateParam(1, (0, import_common31.Body)()),
|
|
14925
|
+
__decorateParam(2, (0, import_common31.Req)())
|
|
14594
14926
|
], WagoControllerApi.prototype, "deliverCommissioningSession", 1);
|
|
14595
14927
|
__decorateClass([
|
|
14596
14928
|
(0, import_plugins_backend_sdk16.Auth)("system.settings.manage"),
|
|
14597
|
-
(0,
|
|
14598
|
-
__decorateParam(0, (0,
|
|
14599
|
-
__decorateParam(1, (0,
|
|
14929
|
+
(0, import_common31.Post)("commissioning/sessions/:id/confirm-host-key"),
|
|
14930
|
+
__decorateParam(0, (0, import_common31.Param)("id", import_common31.ParseIntPipe)),
|
|
14931
|
+
__decorateParam(1, (0, import_common31.Body)())
|
|
14600
14932
|
], WagoControllerApi.prototype, "confirmCommissioningHostKey", 1);
|
|
14601
14933
|
__decorateClass([
|
|
14602
14934
|
(0, import_plugins_backend_sdk16.Auth)("system.settings.manage"),
|
|
14603
|
-
(0,
|
|
14604
|
-
__decorateParam(0, (0,
|
|
14605
|
-
__decorateParam(1, (0,
|
|
14935
|
+
(0, import_common31.Post)("commissioning/sessions"),
|
|
14936
|
+
__decorateParam(0, (0, import_common31.Body)()),
|
|
14937
|
+
__decorateParam(1, (0, import_common31.Req)())
|
|
14606
14938
|
], WagoControllerApi.prototype, "createCommissioningSession", 1);
|
|
14607
14939
|
__decorateClass([
|
|
14608
14940
|
(0, import_plugins_backend_sdk16.Auth)("system.settings.manage"),
|
|
14609
|
-
(0,
|
|
14610
|
-
__decorateParam(0, (0,
|
|
14611
|
-
__decorateParam(1, (0,
|
|
14941
|
+
(0, import_common31.Get)("commissioning/sessions"),
|
|
14942
|
+
__decorateParam(0, (0, import_common31.Query)("limit")),
|
|
14943
|
+
__decorateParam(1, (0, import_common31.Query)("offset"))
|
|
14612
14944
|
], WagoControllerApi.prototype, "commissioningSessions", 1);
|
|
14613
14945
|
__decorateClass([
|
|
14614
14946
|
(0, import_plugins_backend_sdk16.Auth)("system.settings.manage"),
|
|
14615
|
-
(0,
|
|
14947
|
+
(0, import_common31.Get)("commissioning/support")
|
|
14616
14948
|
], WagoControllerApi.prototype, "commissioningSupport", 1);
|
|
14617
14949
|
__decorateClass([
|
|
14618
14950
|
(0, import_plugins_backend_sdk16.Auth)("system.settings.manage"),
|
|
14619
|
-
(0,
|
|
14620
|
-
__decorateParam(0, (0,
|
|
14951
|
+
(0, import_common31.Post)("settings"),
|
|
14952
|
+
__decorateParam(0, (0, import_common31.Body)())
|
|
14621
14953
|
], WagoControllerApi.prototype, "setSettings", 1);
|
|
14622
14954
|
__decorateClass([
|
|
14623
14955
|
(0, import_plugins_backend_sdk16.Auth)("system.settings.manage"),
|
|
14624
|
-
(0,
|
|
14956
|
+
(0, import_common31.Get)("settings")
|
|
14625
14957
|
], WagoControllerApi.prototype, "settings", 1);
|
|
14626
14958
|
__decorateClass([
|
|
14627
|
-
(0,
|
|
14959
|
+
(0, import_common31.Get)("controllers")
|
|
14628
14960
|
], WagoControllerApi.prototype, "list", 1);
|
|
14629
14961
|
WagoControllerApi = __decorateClass([
|
|
14630
14962
|
(0, import_plugins_backend_sdk16.Auth)("resources.update"),
|
|
14631
|
-
(0,
|
|
14632
|
-
__decorateParam(0, (0,
|
|
14633
|
-
__decorateParam(1, (0,
|
|
14634
|
-
__decorateParam(2, (0,
|
|
14635
|
-
__decorateParam(3, (0,
|
|
14963
|
+
(0, import_common31.Controller)("wago"),
|
|
14964
|
+
__decorateParam(0, (0, import_common31.Inject)(WagoService)),
|
|
14965
|
+
__decorateParam(1, (0, import_common31.Inject)(WagoCommissioningService)),
|
|
14966
|
+
__decorateParam(2, (0, import_common31.Inject)(WagoCredentialRotationService)),
|
|
14967
|
+
__decorateParam(3, (0, import_common31.Inject)(/* @__PURE__ */ Symbol.for("attraccess.plugin.context")))
|
|
14636
14968
|
], WagoControllerApi);
|
|
14637
14969
|
|
|
14638
14970
|
// backend/plugin.ts
|
|
14639
14971
|
init_catalog();
|
|
14640
14972
|
|
|
14641
14973
|
// backend/runtime/artifacts/controller.ts
|
|
14642
|
-
var
|
|
14974
|
+
var import_common32 = require("@nestjs/common");
|
|
14643
14975
|
var import_plugins_backend_sdk17 = require("@attraccess/plugins-backend-sdk");
|
|
14644
14976
|
init_catalog();
|
|
14645
14977
|
var WagoArtifactsController = class {
|
|
@@ -14650,32 +14982,32 @@ var WagoArtifactsController = class {
|
|
|
14650
14982
|
try {
|
|
14651
14983
|
return await this.artifacts.list();
|
|
14652
14984
|
} catch {
|
|
14653
|
-
throw new
|
|
14985
|
+
throw new import_common32.ServiceUnavailableException("Runtime releases could not be loaded. Retry shortly.");
|
|
14654
14986
|
}
|
|
14655
14987
|
}
|
|
14656
14988
|
async current() {
|
|
14657
14989
|
try {
|
|
14658
14990
|
return await this.artifacts.current();
|
|
14659
14991
|
} catch {
|
|
14660
|
-
throw new
|
|
14992
|
+
throw new import_common32.ServiceUnavailableException("The selected runtime release could not be loaded. Retry shortly.");
|
|
14661
14993
|
}
|
|
14662
14994
|
}
|
|
14663
14995
|
};
|
|
14664
14996
|
__decorateClass([
|
|
14665
|
-
(0,
|
|
14997
|
+
(0, import_common32.Get)()
|
|
14666
14998
|
], WagoArtifactsController.prototype, "list", 1);
|
|
14667
14999
|
__decorateClass([
|
|
14668
|
-
(0,
|
|
15000
|
+
(0, import_common32.Get)("current")
|
|
14669
15001
|
], WagoArtifactsController.prototype, "current", 1);
|
|
14670
15002
|
WagoArtifactsController = __decorateClass([
|
|
14671
15003
|
(0, import_plugins_backend_sdk17.Auth)("system.settings.manage"),
|
|
14672
|
-
(0,
|
|
14673
|
-
__decorateParam(0, (0,
|
|
15004
|
+
(0, import_common32.Controller)("wago/runtime-artifacts"),
|
|
15005
|
+
__decorateParam(0, (0, import_common32.Inject)(WagoRuntimeArtifactsService))
|
|
14674
15006
|
], WagoArtifactsController);
|
|
14675
15007
|
|
|
14676
15008
|
// backend/diagnostics/service.ts
|
|
14677
15009
|
var import_plugins_backend_sdk18 = require("@attraccess/plugins-backend-sdk");
|
|
14678
|
-
var
|
|
15010
|
+
var import_common33 = require("@nestjs/common");
|
|
14679
15011
|
function configurationRejections(latest, runtime) {
|
|
14680
15012
|
return latest?.rejectionErrors ? safeValidationSummaries(JSON.parse(latest.rejectionErrors)) : latest && runtime.rejection?.revision === latest.revision && runtime.rejection.contentHash === latest.contentHash ? runtime.rejection.errors : [];
|
|
14681
15013
|
}
|
|
@@ -14729,6 +15061,7 @@ function diagnosticReferences(nodes, channelIds, revision2, capabilities) {
|
|
|
14729
15061
|
function runtimeStreamSummary(runtime) {
|
|
14730
15062
|
return {
|
|
14731
15063
|
sequenceGaps: runtime.activeStream ? runtime.sequenceGaps : null,
|
|
15064
|
+
dropCounts: runtime.dropCounts ?? null,
|
|
14732
15065
|
activeStream: runtime.activeStream ?? null,
|
|
14733
15066
|
trackingExhausted: runtime.trackingExhausted,
|
|
14734
15067
|
stateConnected: runtime.connected ?? null,
|
|
@@ -14744,7 +15077,7 @@ var WagoDiagnosticsService = class {
|
|
|
14744
15077
|
}
|
|
14745
15078
|
async get(controllerId) {
|
|
14746
15079
|
const controller = await this.context.getRepository(WagoController).findOneBy({ id: controllerId });
|
|
14747
|
-
if (!controller) throw new
|
|
15080
|
+
if (!controller) throw new import_common33.NotFoundException("WAGO controller not found");
|
|
14748
15081
|
const [draft, latest, applied, nodes] = await Promise.all([
|
|
14749
15082
|
this.context.getRepository(WagoConfigurationDraft).findOneBy({ controllerId }),
|
|
14750
15083
|
this.context.getRepository(WagoConfigurationRevision).findOne({ where: { controllerId }, order: { revision: "DESC" } }),
|
|
@@ -14760,6 +15093,14 @@ var WagoDiagnosticsService = class {
|
|
|
14760
15093
|
const expected = latest?.state === "rejected" ? applied : latest;
|
|
14761
15094
|
const revisionMismatch = !!expected && (applied?.revision !== expected.revision || runtime.revision !== expected.revision || runtime.activeStream !== void 0 && runtime.contentHash !== expected.contentHash);
|
|
14762
15095
|
const connected = runtime.connected === false ? false : heartbeatFreshness === "fresh" || freshness(runtime.stateSourceAt) === "fresh";
|
|
15096
|
+
const configuredChannels = (appliedSnapshot ?? snapshot)?.logicalChannels ?? [];
|
|
15097
|
+
const enabledChannels = configuredChannels.filter(channelEnabled);
|
|
15098
|
+
const disabledChannels = configuredChannels.filter((channel) => !channelEnabled(channel)).slice(0, MAX_CONFIGURATION_ITEMS).map((channel) => ({
|
|
15099
|
+
id: channel.id,
|
|
15100
|
+
profile: channel.profile,
|
|
15101
|
+
capabilities: channel.capabilities,
|
|
15102
|
+
availability: "disabled"
|
|
15103
|
+
}));
|
|
14763
15104
|
const references = diagnosticReferences(
|
|
14764
15105
|
nodes.slice(0, 1e3),
|
|
14765
15106
|
appliedSnapshot?.logicalChannels.map((channel) => channel.id) ?? [],
|
|
@@ -14786,7 +15127,7 @@ var WagoDiagnosticsService = class {
|
|
|
14786
15127
|
) : [],
|
|
14787
15128
|
hardwareReadiness: "unknown",
|
|
14788
15129
|
hardwareReadinessReason: "Reported hardware availability is shown when supplied; it does not prove physical I/O readiness. Applied configuration and cached output state are not physical proof.",
|
|
14789
|
-
channels:
|
|
15130
|
+
channels: enabledChannels.slice(0, MAX_CHANNELS).map((channel) => {
|
|
14790
15131
|
const values = [
|
|
14791
15132
|
channel.capabilities.includes("input") ? own(runtime.inputs, channel.id) : void 0,
|
|
14792
15133
|
channel.capabilities.includes("output") ? own(runtime.outputs, channel.id) : void 0,
|
|
@@ -14810,13 +15151,14 @@ var WagoDiagnosticsService = class {
|
|
|
14810
15151
|
acknowledgement: own(runtime.acknowledgements, channel.id) ?? null
|
|
14811
15152
|
};
|
|
14812
15153
|
}),
|
|
15154
|
+
disabledChannels,
|
|
14813
15155
|
faults: Object.entries(runtime.faults).map(([channelId2, fault]) => ({ channelId: channelId2, ...fault })),
|
|
14814
15156
|
references,
|
|
14815
15157
|
referencesTruncated: nodes.length > 1e3,
|
|
14816
15158
|
events: runtime.events,
|
|
14817
15159
|
limitations: [
|
|
14818
15160
|
"Hardware readiness remains unknown. Legacy samples have no source time and are never current.",
|
|
14819
|
-
"Diagnostics retain at most 256 channels, 50 recent events and 15 minutes in this process.",
|
|
15161
|
+
"Diagnostics retain at most 256 enabled channels, 50 recent events and 15 minutes in this process. Disabled channels are listed without samples.",
|
|
14820
15162
|
"Flow links select the affected node. Diagnostics warnings do not block resource usage; required flow nodes determine gating.",
|
|
14821
15163
|
"The MQTT SDK exposes neither retained-message flags nor broker connection events. Source age is bounded to 90 seconds; immediate broker-disconnect detection is unavailable.",
|
|
14822
15164
|
"Stream tracking keeps at most 16 retired boot stream IDs per controller and six category counters, and fails closed when exhausted. Replayed source times never become receipt times."
|
|
@@ -14921,9 +15263,9 @@ var WagoDiagnosticsService = class {
|
|
|
14921
15263
|
}
|
|
14922
15264
|
};
|
|
14923
15265
|
WagoDiagnosticsService = __decorateClass([
|
|
14924
|
-
(0,
|
|
14925
|
-
__decorateParam(0, (0,
|
|
14926
|
-
__decorateParam(1, (0,
|
|
15266
|
+
(0, import_common33.Injectable)(),
|
|
15267
|
+
__decorateParam(0, (0, import_common33.Inject)(/* @__PURE__ */ Symbol.for("attraccess.plugin.context"))),
|
|
15268
|
+
__decorateParam(1, (0, import_common33.Inject)(WagoService))
|
|
14927
15269
|
], WagoDiagnosticsService);
|
|
14928
15270
|
var WagoDiagnosticsController = class {
|
|
14929
15271
|
constructor(diagnostics) {
|
|
@@ -14937,17 +15279,17 @@ var WagoDiagnosticsController = class {
|
|
|
14937
15279
|
}
|
|
14938
15280
|
};
|
|
14939
15281
|
__decorateClass([
|
|
14940
|
-
(0,
|
|
14941
|
-
__decorateParam(0, (0,
|
|
15282
|
+
(0, import_common33.Get)("controllers/:id/diagnostics"),
|
|
15283
|
+
__decorateParam(0, (0, import_common33.Param)("id", import_common33.ParseIntPipe))
|
|
14942
15284
|
], WagoDiagnosticsController.prototype, "get", 1);
|
|
14943
15285
|
__decorateClass([
|
|
14944
|
-
(0,
|
|
14945
|
-
__decorateParam(0, (0,
|
|
15286
|
+
(0, import_common33.Get)("resources/:id/diagnostics"),
|
|
15287
|
+
__decorateParam(0, (0, import_common33.Param)("id", import_common33.ParseIntPipe))
|
|
14946
15288
|
], WagoDiagnosticsController.prototype, "getResource", 1);
|
|
14947
15289
|
WagoDiagnosticsController = __decorateClass([
|
|
14948
15290
|
(0, import_plugins_backend_sdk18.Auth)("resources.update"),
|
|
14949
|
-
(0,
|
|
14950
|
-
__decorateParam(0, (0,
|
|
15291
|
+
(0, import_common33.Controller)("wago"),
|
|
15292
|
+
__decorateParam(0, (0, import_common33.Inject)(WagoDiagnosticsService))
|
|
14951
15293
|
], WagoDiagnosticsController);
|
|
14952
15294
|
|
|
14953
15295
|
// backend/flow/command-node.ts
|
|
@@ -14973,7 +15315,7 @@ function createWagoCommandNode(service) {
|
|
|
14973
15315
|
}
|
|
14974
15316
|
|
|
14975
15317
|
// backend/flow/service.ts
|
|
14976
|
-
var
|
|
15318
|
+
var import_common35 = require("@nestjs/common");
|
|
14977
15319
|
|
|
14978
15320
|
// backend/flow/model.ts
|
|
14979
15321
|
var MAX_CACHE_ENTRIES = 2e3;
|
|
@@ -15040,7 +15382,7 @@ function createWagoStateNodes(service) {
|
|
|
15040
15382
|
}
|
|
15041
15383
|
|
|
15042
15384
|
// backend/flow/state.ts
|
|
15043
|
-
var
|
|
15385
|
+
var import_common34 = require("@nestjs/common");
|
|
15044
15386
|
var WagoFlowServiceState = class {
|
|
15045
15387
|
constructor(context, wago) {
|
|
15046
15388
|
__publicField(this, "context", context);
|
|
@@ -15053,6 +15395,8 @@ var WagoFlowServiceState = class {
|
|
|
15053
15395
|
__publicField(this, "unavailableConfiguration", /* @__PURE__ */ new Set());
|
|
15054
15396
|
__publicField(this, "appliedConfigurations", /* @__PURE__ */ new Map());
|
|
15055
15397
|
__publicField(this, "channelCache", /* @__PURE__ */ new Map());
|
|
15398
|
+
/** IDs of the disabled channels in `channelCache`, kept in step with it so reads need no scan. */
|
|
15399
|
+
__publicField(this, "disabledChannelCache", /* @__PURE__ */ new Map());
|
|
15056
15400
|
__publicField(this, "waiters", /* @__PURE__ */ new Set());
|
|
15057
15401
|
__publicField(this, "waitersByKey", /* @__PURE__ */ new Map());
|
|
15058
15402
|
__publicField(this, "subscriptions", []);
|
|
@@ -15074,14 +15418,14 @@ var WagoFlowServiceState = class {
|
|
|
15074
15418
|
}
|
|
15075
15419
|
};
|
|
15076
15420
|
WagoFlowServiceState = __decorateClass([
|
|
15077
|
-
__decorateParam(0, (0,
|
|
15078
|
-
__decorateParam(1, (0,
|
|
15421
|
+
__decorateParam(0, (0, import_common34.Inject)(PLUGIN_CONTEXT6)),
|
|
15422
|
+
__decorateParam(1, (0, import_common34.Inject)(WagoService))
|
|
15079
15423
|
], WagoFlowServiceState);
|
|
15080
15424
|
|
|
15081
15425
|
// backend/flow/queries.ts
|
|
15082
15426
|
var WagoFlowQueries = class extends WagoFlowServiceState {
|
|
15083
15427
|
async wait(config) {
|
|
15084
|
-
if (typeof config.controllerId !== "number" || typeof config.channelId !== "string" || typeof config.category !== "string")
|
|
15428
|
+
if (typeof config.controllerId !== "number" || typeof config.channelId !== "string" || typeof config.category !== "string" || this.disabled(config.controllerId, config.channelId))
|
|
15085
15429
|
return null;
|
|
15086
15430
|
const current = this.read(config);
|
|
15087
15431
|
if (current && this.matchesCondition(current, config)) return current;
|
|
@@ -15110,8 +15454,13 @@ var WagoFlowQueries = class extends WagoFlowServiceState {
|
|
|
15110
15454
|
this.waitersByKey.set(waiterKey, waiters);
|
|
15111
15455
|
});
|
|
15112
15456
|
}
|
|
15457
|
+
/** True when the applied configuration disables this channel; its readings are then unavailable. */
|
|
15458
|
+
disabled(controllerId, channelId2) {
|
|
15459
|
+
return this.disabledChannelCache.get(controllerId)?.has(channelId2) ?? false;
|
|
15460
|
+
}
|
|
15113
15461
|
read(config) {
|
|
15114
|
-
if (typeof config.controllerId !== "number" || typeof config.channelId !== "string"
|
|
15462
|
+
if (typeof config.controllerId !== "number" || typeof config.channelId !== "string" || this.disabled(config.controllerId, config.channelId))
|
|
15463
|
+
return null;
|
|
15115
15464
|
if (typeof config.category === "string")
|
|
15116
15465
|
return this.cache.get(this.cacheKey(config.controllerId, config.channelId, config.category)) ?? null;
|
|
15117
15466
|
const entries = [...this.cache.values()].filter(
|
|
@@ -15196,10 +15545,10 @@ var WagoFlowQueries = class extends WagoFlowServiceState {
|
|
|
15196
15545
|
type: "string",
|
|
15197
15546
|
title: "Logical Channel",
|
|
15198
15547
|
refreshesSchema: true,
|
|
15199
|
-
oneOf: channels.map((channel2) =>
|
|
15200
|
-
const: channel2.id
|
|
15201
|
-
|
|
15202
|
-
})
|
|
15548
|
+
oneOf: channels.map((channel2) => {
|
|
15549
|
+
const title = typeof names[channel2.id] === "string" ? names[channel2.id] : channel2.id;
|
|
15550
|
+
return { const: channel2.id, title: channel2.enabled === false ? `${title} (disabled)` : title };
|
|
15551
|
+
})
|
|
15203
15552
|
}
|
|
15204
15553
|
};
|
|
15205
15554
|
if (kind === "event") {
|
|
@@ -15253,6 +15602,7 @@ var WagoFlowQueries = class extends WagoFlowServiceState {
|
|
|
15253
15602
|
if (!controllerIds.has(id)) this.appliedConfigurations.delete(id);
|
|
15254
15603
|
for (const id of this.streams.keys()) if (!controllerIds.has(id)) this.streams.delete(id);
|
|
15255
15604
|
this.channelCache.clear();
|
|
15605
|
+
this.disabledChannelCache.clear();
|
|
15256
15606
|
const revisions = await this.loadLatestAppliedRevisions(controllers.map((controller) => controller.id));
|
|
15257
15607
|
for (const revision2 of revisions) this.cacheChannels(revision2);
|
|
15258
15608
|
for (const controller of controllers)
|
|
@@ -15380,12 +15730,14 @@ var WagoFlowService = class extends WagoFlowQueries {
|
|
|
15380
15730
|
return true;
|
|
15381
15731
|
}
|
|
15382
15732
|
readCategories(channel) {
|
|
15733
|
+
if (channel?.enabled === false) return [];
|
|
15383
15734
|
return [
|
|
15384
15735
|
...channel?.capabilities.some((capability) => capability === "input" || capability === "output") ? ["state"] : [],
|
|
15385
15736
|
...channel?.capabilities.includes("measurement") ? ["measurement"] : []
|
|
15386
15737
|
];
|
|
15387
15738
|
}
|
|
15388
15739
|
categories(channel) {
|
|
15740
|
+
if (channel?.enabled === false) return [];
|
|
15389
15741
|
return [
|
|
15390
15742
|
...channel?.capabilities.some((capability) => capability === "input" || capability === "output") ? ["state"] : [],
|
|
15391
15743
|
...channel?.capabilities.includes("measurement") ? ["measurement"] : [],
|
|
@@ -15409,9 +15761,14 @@ var WagoFlowService = class extends WagoFlowQueries {
|
|
|
15409
15761
|
contentHash: revision2.contentHash
|
|
15410
15762
|
});
|
|
15411
15763
|
this.channelCache.set(revision2.controllerId, channels);
|
|
15764
|
+
const disabled = new Set(channels.filter((channel) => channel.enabled === false).map((channel) => channel.id));
|
|
15765
|
+
this.disabledChannelCache.set(revision2.controllerId, disabled);
|
|
15766
|
+
for (const [key, state] of this.cache)
|
|
15767
|
+
if (state.controllerId === revision2.controllerId && disabled.has(state.channelId)) this.cache.delete(key);
|
|
15412
15768
|
return channels;
|
|
15413
15769
|
} catch {
|
|
15414
15770
|
this.channelCache.set(revision2.controllerId, []);
|
|
15771
|
+
this.disabledChannelCache.delete(revision2.controllerId);
|
|
15415
15772
|
return [];
|
|
15416
15773
|
}
|
|
15417
15774
|
}
|
|
@@ -15433,6 +15790,7 @@ var WagoFlowService = class extends WagoFlowQueries {
|
|
|
15433
15790
|
});
|
|
15434
15791
|
if (!revision2) {
|
|
15435
15792
|
this.channelCache.set(controllerId, []);
|
|
15793
|
+
this.disabledChannelCache.delete(controllerId);
|
|
15436
15794
|
return [];
|
|
15437
15795
|
}
|
|
15438
15796
|
return this.cacheChannels(revision2);
|
|
@@ -15486,6 +15844,7 @@ var WagoFlowService = class extends WagoFlowQueries {
|
|
|
15486
15844
|
if (invalidateSamples || state.category === "state") state.invalidated = true;
|
|
15487
15845
|
}
|
|
15488
15846
|
for (const channel of channels) {
|
|
15847
|
+
if (channel.enabled === false) continue;
|
|
15489
15848
|
const value = channel.capabilities.includes("input") && Object.hasOwn(event.inputs ?? {}, channel.id) ? event.inputs[channel.id] : channel.capabilities.includes("output") && Object.hasOwn(event.outputs, channel.id) ? event.outputs[channel.id] : void 0;
|
|
15490
15849
|
if (typeof value === "boolean") this.store(controller, channel.id, event, value);
|
|
15491
15850
|
}
|
|
@@ -15556,7 +15915,8 @@ var WagoFlowService = class extends WagoFlowQueries {
|
|
|
15556
15915
|
this.applyState(controller, event, eventTime, stream, channels);
|
|
15557
15916
|
} else if ("channelId" in event) {
|
|
15558
15917
|
const channel = channels.find((channel2) => channel2.id === event.channelId);
|
|
15559
|
-
if (!channel || event.category === "measurement" && !channel.capabilities.includes("measurement"))
|
|
15918
|
+
if (!channel || channel.enabled === false || event.category === "measurement" && !channel.capabilities.includes("measurement"))
|
|
15919
|
+
return;
|
|
15560
15920
|
this.store(controller, event.channelId, event, event.category === "measurement" ? event.value : event);
|
|
15561
15921
|
}
|
|
15562
15922
|
}
|
|
@@ -15567,9 +15927,9 @@ var WagoFlowService = class extends WagoFlowQueries {
|
|
|
15567
15927
|
}
|
|
15568
15928
|
};
|
|
15569
15929
|
WagoFlowService = __decorateClass([
|
|
15570
|
-
(0,
|
|
15571
|
-
__decorateParam(0, (0,
|
|
15572
|
-
__decorateParam(1, (0,
|
|
15930
|
+
(0, import_common35.Injectable)(),
|
|
15931
|
+
__decorateParam(0, (0, import_common35.Inject)(PLUGIN_CONTEXT6)),
|
|
15932
|
+
__decorateParam(1, (0, import_common35.Inject)(WagoService))
|
|
15573
15933
|
], WagoFlowService);
|
|
15574
15934
|
|
|
15575
15935
|
// backend/audit/policy.ts
|
|
@@ -15708,14 +16068,14 @@ var WAGO_AUDIT_DOMAIN = {
|
|
|
15708
16068
|
|
|
15709
16069
|
// backend/runtime/update/controller.ts
|
|
15710
16070
|
var import_plugins_backend_sdk19 = require("@attraccess/plugins-backend-sdk");
|
|
15711
|
-
var
|
|
16071
|
+
var import_common38 = require("@nestjs/common");
|
|
15712
16072
|
|
|
15713
16073
|
// backend/network/service.ts
|
|
15714
|
-
var
|
|
16074
|
+
var import_common37 = require("@nestjs/common");
|
|
15715
16075
|
var import_node_crypto25 = require("node:crypto");
|
|
15716
16076
|
|
|
15717
16077
|
// backend/network/model.ts
|
|
15718
|
-
var
|
|
16078
|
+
var import_common36 = require("@nestjs/common");
|
|
15719
16079
|
var NetworkChangeError = class extends Error {
|
|
15720
16080
|
constructor(failure) {
|
|
15721
16081
|
super(failure);
|
|
@@ -15726,24 +16086,24 @@ var EVIDENCE_MS = 12e4;
|
|
|
15726
16086
|
var OPERATION_MS2 = 20 * 6e4;
|
|
15727
16087
|
function networkChangeInput(body) {
|
|
15728
16088
|
if (!body || typeof body !== "object" || Array.isArray(body))
|
|
15729
|
-
throw new
|
|
16089
|
+
throw new import_common36.BadRequestException("Invalid MQTT/address change");
|
|
15730
16090
|
const input = body;
|
|
15731
16091
|
if (Object.keys(input).some((k) => !["targetHost", "mqttServerId"].includes(k)) || typeof input.targetHost !== "string" || !/^(?:\d{1,3}\.){3}\d{1,3}$/.test(input.targetHost))
|
|
15732
|
-
throw new
|
|
16092
|
+
throw new import_common36.BadRequestException("Enter the CC100 private IPv4 address.");
|
|
15733
16093
|
const octets = input.targetHost.split(".").map(Number);
|
|
15734
16094
|
if (octets.some((n) => n > 255) || octets.join(".") !== input.targetHost || !(octets[0] === 10 || octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31 || octets[0] === 192 && octets[1] === 168))
|
|
15735
|
-
throw new
|
|
16095
|
+
throw new import_common36.BadRequestException("Enter the CC100 private IPv4 address.");
|
|
15736
16096
|
if (input.mqttServerId !== null && (!Number.isSafeInteger(input.mqttServerId) || input.mqttServerId <= 0))
|
|
15737
|
-
throw new
|
|
16097
|
+
throw new import_common36.BadRequestException("Select a configured MQTT server or update only the address.");
|
|
15738
16098
|
return { targetHost: input.targetHost, mqttServerId: input.mqttServerId };
|
|
15739
16099
|
}
|
|
15740
16100
|
function assertNoUnfinishedChange(row) {
|
|
15741
16101
|
if (row && row.phase !== "completed" && !(row.phase === "connecting" && row.failure))
|
|
15742
|
-
throw new
|
|
16102
|
+
throw new import_common36.ConflictException("Retry the pending MQTT/address change before starting another.");
|
|
15743
16103
|
}
|
|
15744
16104
|
function assertSameManagedIdentity(row, access) {
|
|
15745
16105
|
if (!row || row.sessionId !== access.sessionId || row.fingerprint !== access.fingerprint)
|
|
15746
|
-
throw new
|
|
16106
|
+
throw new import_common36.ConflictException("Pending change belongs to another managed identity.");
|
|
15747
16107
|
}
|
|
15748
16108
|
|
|
15749
16109
|
// backend/network/service.ts
|
|
@@ -15757,14 +16117,14 @@ var WagoNetworkChangeService = class {
|
|
|
15757
16117
|
__publicField(this, "destroyed", false);
|
|
15758
16118
|
}
|
|
15759
16119
|
async apply(controllerId, body, principal, retry = false) {
|
|
15760
|
-
if (this.destroyed) throw new
|
|
16120
|
+
if (this.destroyed) throw new import_common37.ConflictException("Controller operation has stopped. Retry the request.");
|
|
15761
16121
|
const input = retry ? null : networkChangeInput(body);
|
|
15762
16122
|
const access = await this.context.getRepository(WagoManagedAccess).findOne({ where: { controllerId, state: "managed" }, order: { sessionId: "DESC" } });
|
|
15763
|
-
if (!access) throw new
|
|
16123
|
+
if (!access) throw new import_common37.ConflictException("Automatic SSH management is required for this operation.");
|
|
15764
16124
|
const operations = new WagoDeviceOperations(this.context.getRepository(WagoDeviceOperation));
|
|
15765
16125
|
const owner = (0, import_node_crypto25.randomBytes)(16).toString("hex"), now = Date.now();
|
|
15766
16126
|
if (!await operations.acquire(access.fingerprint, owner, now, now + OPERATION_MS2 + 6e4))
|
|
15767
|
-
throw new
|
|
16127
|
+
throw new import_common37.ConflictException("Another controller operation is active. Retry after it finishes.");
|
|
15768
16128
|
const abort = new AbortController();
|
|
15769
16129
|
this.active.add(abort);
|
|
15770
16130
|
const timer = setTimeout(() => abort.abort(), OPERATION_MS2).unref();
|
|
@@ -15778,7 +16138,7 @@ var WagoNetworkChangeService = class {
|
|
|
15778
16138
|
try {
|
|
15779
16139
|
const controller = await this.context.getRepository(WagoController).findOneBy({ id: controllerId, trustState: "claimed" });
|
|
15780
16140
|
if (!controller?.mqttServerId || !controller.credentialEpoch)
|
|
15781
|
-
throw new
|
|
16141
|
+
throw new import_common37.ConflictException("Managed enrolled controller required.");
|
|
15782
16142
|
row = await this.repository.createQueryBuilder("change").addSelect("change.encryptedPayload").where("change.controllerId = :controllerId", { controllerId }).getOne();
|
|
15783
16143
|
if (retry && (!row || row.phase === "completed")) return this.status(controllerId);
|
|
15784
16144
|
if (!retry) assertNoUnfinishedChange(row);
|
|
@@ -15807,7 +16167,7 @@ var WagoNetworkChangeService = class {
|
|
|
15807
16167
|
} catch (error) {
|
|
15808
16168
|
if (row && row.phase !== "completed") await this.recordFailure(row, error, assertOwned);
|
|
15809
16169
|
if (audited) await lifecycle.finish("failed");
|
|
15810
|
-
throw new
|
|
16170
|
+
throw new import_common37.ConflictException("MQTT/address change is incomplete. Check its status and retry the saved operation.");
|
|
15811
16171
|
} finally {
|
|
15812
16172
|
clearTimeout(timer);
|
|
15813
16173
|
abort.abort();
|
|
@@ -15820,13 +16180,13 @@ var WagoNetworkChangeService = class {
|
|
|
15820
16180
|
const rotation = await this.context.getRepository(WagoCredentialRotationEntity).findOneBy({ controllerId });
|
|
15821
16181
|
const update = await this.context.getRepository(WagoRuntimeUpdateEntity).findOneBy({ controllerId });
|
|
15822
16182
|
if (rotation && rotation.phase !== "completed" || update?.metadata && JSON.parse(update.metadata).token)
|
|
15823
|
-
throw new
|
|
16183
|
+
throw new import_common37.ConflictException(
|
|
15824
16184
|
"Finish credential rotation or runtime update recovery before changing MQTT/address settings."
|
|
15825
16185
|
);
|
|
15826
16186
|
}
|
|
15827
16187
|
/** Durably record a new change intent before contacting the controller. */
|
|
15828
16188
|
async savePendingChange(controllerId, access, input, assertOwned) {
|
|
15829
|
-
if (!input) throw new
|
|
16189
|
+
if (!input) throw new import_common37.BadRequestException("Invalid MQTT/address change");
|
|
15830
16190
|
const row = this.repository.create({
|
|
15831
16191
|
controllerId,
|
|
15832
16192
|
sessionId: access.sessionId,
|
|
@@ -16048,7 +16408,7 @@ var WagoNetworkChangeService = class {
|
|
|
16048
16408
|
}
|
|
16049
16409
|
async status(controllerId) {
|
|
16050
16410
|
const controller = await this.context.getRepository(WagoController).findOneBy({ id: controllerId });
|
|
16051
|
-
if (!controller) throw new
|
|
16411
|
+
if (!controller) throw new import_common37.NotFoundException("WAGO controller not found");
|
|
16052
16412
|
const access = await this.context.getRepository(WagoManagedAccess).findOne({ where: { controllerId }, order: { sessionId: "DESC" } });
|
|
16053
16413
|
const row = await this.repository.findOneBy({ controllerId });
|
|
16054
16414
|
const lease = row && await this.context.getRepository(WagoDeviceOperation).findOneBy({ fingerprint: row.fingerprint });
|
|
@@ -16068,10 +16428,10 @@ var WagoNetworkChangeService = class {
|
|
|
16068
16428
|
}
|
|
16069
16429
|
async retirePreviousCredentials(controllerId, principal) {
|
|
16070
16430
|
const access = await this.context.getRepository(WagoManagedAccess).findOne({ where: { controllerId }, order: { sessionId: "DESC" } });
|
|
16071
|
-
if (!access) throw new
|
|
16431
|
+
if (!access) throw new import_common37.ConflictException("Controller management identity is unavailable.");
|
|
16072
16432
|
const operations = new WagoDeviceOperations(this.context.getRepository(WagoDeviceOperation)), owner = (0, import_node_crypto25.randomBytes)(16).toString("hex");
|
|
16073
16433
|
if (!await operations.acquire(access.fingerprint, owner, Date.now(), Date.now() + 12e4))
|
|
16074
|
-
throw new
|
|
16434
|
+
throw new import_common37.ConflictException("Controller is busy");
|
|
16075
16435
|
const lifecycle = new WagoAudit(this.context).begin(principal, controllerId, "network_credential_retirement");
|
|
16076
16436
|
let audited = false;
|
|
16077
16437
|
try {
|
|
@@ -16119,7 +16479,7 @@ var WagoNetworkChangeService = class {
|
|
|
16119
16479
|
return this.status(controllerId);
|
|
16120
16480
|
} catch {
|
|
16121
16481
|
if (audited) await lifecycle.finish("failed").catch(() => void 0);
|
|
16122
|
-
throw new
|
|
16482
|
+
throw new import_common37.ConflictException(
|
|
16123
16483
|
"Previous broker credentials could not be retired. Check the previous broker connection and retry."
|
|
16124
16484
|
);
|
|
16125
16485
|
} finally {
|
|
@@ -16135,10 +16495,10 @@ var WagoNetworkChangeService = class {
|
|
|
16135
16495
|
}
|
|
16136
16496
|
};
|
|
16137
16497
|
WagoNetworkChangeService = __decorateClass([
|
|
16138
|
-
(0,
|
|
16139
|
-
__decorateParam(0, (0,
|
|
16140
|
-
__decorateParam(1, (0,
|
|
16141
|
-
__decorateParam(2, (0,
|
|
16498
|
+
(0, import_common37.Injectable)(),
|
|
16499
|
+
__decorateParam(0, (0, import_common37.Inject)(/* @__PURE__ */ Symbol.for("attraccess.plugin.context"))),
|
|
16500
|
+
__decorateParam(1, (0, import_common37.Inject)(WagoManagedRuntimeService)),
|
|
16501
|
+
__decorateParam(2, (0, import_common37.Inject)(WagoService))
|
|
16142
16502
|
], WagoNetworkChangeService);
|
|
16143
16503
|
|
|
16144
16504
|
// backend/runtime/update/controller.ts
|
|
@@ -16170,7 +16530,7 @@ var WagoUpdatesController = class {
|
|
|
16170
16530
|
}
|
|
16171
16531
|
recovery(id, body, request) {
|
|
16172
16532
|
if (body?.confirm !== true || Object.keys(body).length !== 1)
|
|
16173
|
-
throw new
|
|
16533
|
+
throw new import_common38.BadRequestException("Confirm administrator recovery disclosure");
|
|
16174
16534
|
return this.managed.recoverPassword(id, wagoAuditPrincipal(request));
|
|
16175
16535
|
}
|
|
16176
16536
|
retry(id) {
|
|
@@ -16178,70 +16538,70 @@ var WagoUpdatesController = class {
|
|
|
16178
16538
|
}
|
|
16179
16539
|
restore(id, body, request) {
|
|
16180
16540
|
if (body?.confirm !== true || Object.keys(body).length !== 1)
|
|
16181
|
-
throw new
|
|
16541
|
+
throw new import_common38.BadRequestException("Confirm bootstrap SSH restoration");
|
|
16182
16542
|
return this.managed.restoreAccess(id, wagoAuditPrincipal(request));
|
|
16183
16543
|
}
|
|
16184
16544
|
};
|
|
16185
16545
|
__decorateClass([
|
|
16186
|
-
(0,
|
|
16187
|
-
__decorateParam(0, (0,
|
|
16546
|
+
(0, import_common38.Get)("controllers/:id/network-change"),
|
|
16547
|
+
__decorateParam(0, (0, import_common38.Param)("id", import_common38.ParseIntPipe))
|
|
16188
16548
|
], WagoUpdatesController.prototype, "networkStatus", 1);
|
|
16189
16549
|
__decorateClass([
|
|
16190
|
-
(0,
|
|
16191
|
-
(0,
|
|
16192
|
-
__decorateParam(0, (0,
|
|
16193
|
-
__decorateParam(1, (0,
|
|
16194
|
-
__decorateParam(2, (0,
|
|
16550
|
+
(0, import_common38.Post)("controllers/:id/network-change"),
|
|
16551
|
+
(0, import_common38.Header)("Cache-Control", "no-store"),
|
|
16552
|
+
__decorateParam(0, (0, import_common38.Param)("id", import_common38.ParseIntPipe)),
|
|
16553
|
+
__decorateParam(1, (0, import_common38.Body)()),
|
|
16554
|
+
__decorateParam(2, (0, import_common38.Req)())
|
|
16195
16555
|
], WagoUpdatesController.prototype, "changeNetwork", 1);
|
|
16196
16556
|
__decorateClass([
|
|
16197
|
-
(0,
|
|
16198
|
-
__decorateParam(0, (0,
|
|
16199
|
-
__decorateParam(1, (0,
|
|
16557
|
+
(0, import_common38.Post)("controllers/:id/network-change/retry"),
|
|
16558
|
+
__decorateParam(0, (0, import_common38.Param)("id", import_common38.ParseIntPipe)),
|
|
16559
|
+
__decorateParam(1, (0, import_common38.Req)())
|
|
16200
16560
|
], WagoUpdatesController.prototype, "retryNetwork", 1);
|
|
16201
16561
|
__decorateClass([
|
|
16202
|
-
(0,
|
|
16203
|
-
__decorateParam(0, (0,
|
|
16204
|
-
__decorateParam(1, (0,
|
|
16562
|
+
(0, import_common38.Post)("controllers/:id/network-change/retire-credentials"),
|
|
16563
|
+
__decorateParam(0, (0, import_common38.Param)("id", import_common38.ParseIntPipe)),
|
|
16564
|
+
__decorateParam(1, (0, import_common38.Req)())
|
|
16205
16565
|
], WagoUpdatesController.prototype, "retirePreviousCredentials", 1);
|
|
16206
16566
|
__decorateClass([
|
|
16207
|
-
(0,
|
|
16208
|
-
__decorateParam(0, (0,
|
|
16567
|
+
(0, import_common38.Get)("controllers/:id/runtime-update"),
|
|
16568
|
+
__decorateParam(0, (0, import_common38.Param)("id", import_common38.ParseIntPipe))
|
|
16209
16569
|
], WagoUpdatesController.prototype, "status", 1);
|
|
16210
16570
|
__decorateClass([
|
|
16211
|
-
(0,
|
|
16212
|
-
__decorateParam(0, (0,
|
|
16571
|
+
(0, import_common38.Get)("commissioning/sessions/:id/managed-access"),
|
|
16572
|
+
__decorateParam(0, (0, import_common38.Param)("id", import_common38.ParseIntPipe))
|
|
16213
16573
|
], WagoUpdatesController.prototype, "sessionStatus", 1);
|
|
16214
16574
|
__decorateClass([
|
|
16215
|
-
(0,
|
|
16216
|
-
__decorateParam(0, (0,
|
|
16575
|
+
(0, import_common38.Post)("controllers/:id/runtime-update/retry"),
|
|
16576
|
+
__decorateParam(0, (0, import_common38.Param)("id", import_common38.ParseIntPipe))
|
|
16217
16577
|
], WagoUpdatesController.prototype, "retryRuntime", 1);
|
|
16218
16578
|
__decorateClass([
|
|
16219
|
-
(0,
|
|
16220
|
-
(0,
|
|
16221
|
-
(0,
|
|
16222
|
-
__decorateParam(0, (0,
|
|
16223
|
-
__decorateParam(1, (0,
|
|
16224
|
-
__decorateParam(2, (0,
|
|
16579
|
+
(0, import_common38.Post)("commissioning/sessions/:id/root-recovery"),
|
|
16580
|
+
(0, import_common38.Header)("Cache-Control", "no-store"),
|
|
16581
|
+
(0, import_common38.Header)("Pragma", "no-cache"),
|
|
16582
|
+
__decorateParam(0, (0, import_common38.Param)("id", import_common38.ParseIntPipe)),
|
|
16583
|
+
__decorateParam(1, (0, import_common38.Body)()),
|
|
16584
|
+
__decorateParam(2, (0, import_common38.Req)())
|
|
16225
16585
|
], WagoUpdatesController.prototype, "recovery", 1);
|
|
16226
16586
|
__decorateClass([
|
|
16227
|
-
(0,
|
|
16228
|
-
__decorateParam(0, (0,
|
|
16587
|
+
(0, import_common38.Post)("commissioning/sessions/:id/managed-access/retry"),
|
|
16588
|
+
__decorateParam(0, (0, import_common38.Param)("id", import_common38.ParseIntPipe))
|
|
16229
16589
|
], WagoUpdatesController.prototype, "retry", 1);
|
|
16230
16590
|
__decorateClass([
|
|
16231
|
-
(0,
|
|
16232
|
-
__decorateParam(0, (0,
|
|
16233
|
-
__decorateParam(1, (0,
|
|
16234
|
-
__decorateParam(2, (0,
|
|
16591
|
+
(0, import_common38.Post)("commissioning/sessions/:id/managed-access/restore"),
|
|
16592
|
+
__decorateParam(0, (0, import_common38.Param)("id", import_common38.ParseIntPipe)),
|
|
16593
|
+
__decorateParam(1, (0, import_common38.Body)()),
|
|
16594
|
+
__decorateParam(2, (0, import_common38.Req)())
|
|
16235
16595
|
], WagoUpdatesController.prototype, "restore", 1);
|
|
16236
16596
|
WagoUpdatesController = __decorateClass([
|
|
16237
|
-
(0,
|
|
16597
|
+
(0, import_common38.Controller)("wago"),
|
|
16238
16598
|
(0, import_plugins_backend_sdk19.Auth)("system.settings.manage"),
|
|
16239
|
-
__decorateParam(0, (0,
|
|
16240
|
-
__decorateParam(1, (0,
|
|
16599
|
+
__decorateParam(0, (0, import_common38.Inject)(WagoManagedRuntimeService)),
|
|
16600
|
+
__decorateParam(1, (0, import_common38.Inject)(WagoNetworkChangeService))
|
|
16241
16601
|
], WagoUpdatesController);
|
|
16242
16602
|
|
|
16243
16603
|
// backend/wago-live-updates.service.ts
|
|
16244
|
-
var
|
|
16604
|
+
var import_common39 = require("@nestjs/common");
|
|
16245
16605
|
var import_plugins_backend_sdk20 = require("@attraccess/plugins-backend-sdk");
|
|
16246
16606
|
var WagoLiveUpdatesService = class {
|
|
16247
16607
|
constructor(context, wago, commissioning, diagnostics, managed, network) {
|
|
@@ -16261,14 +16621,14 @@ var WagoLiveUpdatesService = class {
|
|
|
16261
16621
|
topic,
|
|
16262
16622
|
identifier: kind === "none" ? "none" : "required",
|
|
16263
16623
|
authorize: async ({ identifier: identifier3 }, user) => {
|
|
16264
|
-
if (!user.effectivePermissions?.has(permission)) throw new
|
|
16624
|
+
if (!user.effectivePermissions?.has(permission)) throw new import_common39.ForbiddenException();
|
|
16265
16625
|
if (kind === "none") return;
|
|
16266
16626
|
const pattern = kind === "revisions" ? /^[1-9]\d*:(0|[1-9]\d*)$/ : /^[1-9]\d*$/;
|
|
16267
|
-
if (!pattern.test(identifier3 ?? "")) throw new
|
|
16627
|
+
if (!pattern.test(identifier3 ?? "")) throw new import_common39.NotFoundException();
|
|
16268
16628
|
const [id, offset = 0] = identifier3.split(":").map(Number);
|
|
16269
|
-
if (!Number.isSafeInteger(id) || !Number.isSafeInteger(offset)) throw new
|
|
16629
|
+
if (!Number.isSafeInteger(id) || !Number.isSafeInteger(offset)) throw new import_common39.NotFoundException();
|
|
16270
16630
|
const exists = kind === "session" ? await this.context.getRepository(WagoCommissioningSession).existsBy({ id }) : await this.context.getRepository(WagoController).existsBy({ id });
|
|
16271
|
-
if (!exists) throw new
|
|
16631
|
+
if (!exists) throw new import_common39.NotFoundException();
|
|
16272
16632
|
},
|
|
16273
16633
|
source: ({ identifier: identifier3 }) => this.sample(`${topic}:${identifier3 ?? ""}`, interval, () => {
|
|
16274
16634
|
const [id = 0, offset = 0] = (identifier3 ?? "").split(":").map(Number);
|
|
@@ -16306,13 +16666,13 @@ var WagoLiveUpdatesService = class {
|
|
|
16306
16666
|
}
|
|
16307
16667
|
};
|
|
16308
16668
|
WagoLiveUpdatesService = __decorateClass([
|
|
16309
|
-
(0,
|
|
16310
|
-
__decorateParam(0, (0,
|
|
16311
|
-
__decorateParam(1, (0,
|
|
16312
|
-
__decorateParam(2, (0,
|
|
16313
|
-
__decorateParam(3, (0,
|
|
16314
|
-
__decorateParam(4, (0,
|
|
16315
|
-
__decorateParam(5, (0,
|
|
16669
|
+
(0, import_common39.Injectable)(),
|
|
16670
|
+
__decorateParam(0, (0, import_common39.Inject)(/* @__PURE__ */ Symbol.for("attraccess.plugin.context"))),
|
|
16671
|
+
__decorateParam(1, (0, import_common39.Inject)(WagoService)),
|
|
16672
|
+
__decorateParam(2, (0, import_common39.Inject)(WagoCommissioningService)),
|
|
16673
|
+
__decorateParam(3, (0, import_common39.Inject)(WagoDiagnosticsService)),
|
|
16674
|
+
__decorateParam(4, (0, import_common39.Inject)(WagoManagedRuntimeService)),
|
|
16675
|
+
__decorateParam(5, (0, import_common39.Inject)(WagoNetworkChangeService))
|
|
16316
16676
|
], WagoLiveUpdatesService);
|
|
16317
16677
|
|
|
16318
16678
|
// backend/plugin.ts
|