@attraccess/plugin-wago 0.1.0-alpha.0 → 0.1.0-nightly.1002.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/frontend/{__federation_expose_Plugin-BoHSTQq7.js → __federation_expose_Plugin-B8WKMfaS.js}
RENAMED
|
@@ -65,24 +65,24 @@ function createPluginApiClient(basePath = "") {
|
|
|
65
65
|
var RESOURCE_OVERVIEW_SLOT = "resource.overview";
|
|
66
66
|
//#endregion
|
|
67
67
|
//#region frontend/src/api.ts
|
|
68
|
-
var api$
|
|
68
|
+
var api$5 = createPluginApiClient("/api/wago");
|
|
69
69
|
var hostApi = createPluginApiClient("/api");
|
|
70
|
-
var listControllers = () => api$
|
|
71
|
-
var getSettings = () => api$
|
|
72
|
-
var setSettings = (defaultMqttServerId) => api$
|
|
70
|
+
var listControllers = () => api$5.request("/controllers");
|
|
71
|
+
var getSettings = () => api$5.request("/settings");
|
|
72
|
+
var setSettings = (defaultMqttServerId) => api$5.request("/settings", {
|
|
73
73
|
method: "POST",
|
|
74
74
|
body: { defaultMqttServerId }
|
|
75
75
|
});
|
|
76
76
|
var listMqttServers = () => hostApi.request("/mqtt/servers");
|
|
77
|
-
var claimController = (id, input) => api$
|
|
77
|
+
var claimController = (id, input) => api$5.request(`/controllers/${id}/claim`, {
|
|
78
78
|
method: "POST",
|
|
79
79
|
body: input
|
|
80
80
|
});
|
|
81
|
-
var createCommissioningSession = (input) => api$
|
|
81
|
+
var createCommissioningSession = (input) => api$5.request("/commissioning/sessions", {
|
|
82
82
|
method: "POST",
|
|
83
83
|
body: input
|
|
84
84
|
});
|
|
85
|
-
var confirmCommissioningHostKey = (id, hostKeyFingerprint, physicalIdentityConfirmed = false) => api$
|
|
85
|
+
var confirmCommissioningHostKey = (id, hostKeyFingerprint, physicalIdentityConfirmed = false) => api$5.request(`/commissioning/sessions/${id}/confirm-host-key`, {
|
|
86
86
|
method: "POST",
|
|
87
87
|
body: {
|
|
88
88
|
hostKeyFingerprint,
|
|
@@ -90,22 +90,22 @@ var confirmCommissioningHostKey = (id, hostKeyFingerprint, physicalIdentityConfi
|
|
|
90
90
|
trustMethod: physicalIdentityConfirmed ? "isolated_service_connection" : "trusted_inventory"
|
|
91
91
|
}
|
|
92
92
|
});
|
|
93
|
-
var listCommissioningSessions = (limit = 100, offset = 0) => api$
|
|
94
|
-
var getCommissioningVerification = (id) => api$
|
|
95
|
-
var getCommissioningSupport = () => api$
|
|
96
|
-
var deliverCommissioningSession = (id, input) => api$
|
|
93
|
+
var listCommissioningSessions = (limit = 100, offset = 0) => api$5.request(`/commissioning/sessions?limit=${limit}&offset=${offset}`);
|
|
94
|
+
var getCommissioningVerification = (id) => api$5.request(`/commissioning/sessions/${id}/verification`);
|
|
95
|
+
var getCommissioningSupport = () => api$5.request("/commissioning/support");
|
|
96
|
+
var deliverCommissioningSession = (id, input) => api$5.request(`/commissioning/sessions/${id}/deliver`, {
|
|
97
97
|
method: "POST",
|
|
98
98
|
body: input
|
|
99
99
|
});
|
|
100
|
-
var recoverCommissioningSession = (id, input) => api$
|
|
100
|
+
var recoverCommissioningSession = (id, input) => api$5.request(`/commissioning/sessions/${id}/recover`, {
|
|
101
101
|
method: "POST",
|
|
102
102
|
body: input
|
|
103
103
|
});
|
|
104
|
-
var removeCommissioningSession = (id) => api$
|
|
105
|
-
var removeController = (id) => api$
|
|
106
|
-
var getDraft = (id) => api$
|
|
107
|
-
var getConfigurationBaseline = (id) => api$
|
|
108
|
-
var saveDraft = (id, snapshot, metadata, expectedDraft) => api$
|
|
104
|
+
var removeCommissioningSession = (id) => api$5.request(`/commissioning/sessions/${id}`, { method: "DELETE" });
|
|
105
|
+
var removeController = (id) => api$5.request(`/controllers/${id}`, { method: "DELETE" });
|
|
106
|
+
var getDraft = (id) => api$5.request(`/controllers/${id}/configuration/draft`);
|
|
107
|
+
var getConfigurationBaseline = (id) => api$5.request(`/controllers/${id}/configuration/baseline`);
|
|
108
|
+
var saveDraft = (id, snapshot, metadata, expectedDraft) => api$5.request(`/controllers/${id}/configuration/draft`, {
|
|
109
109
|
method: "POST",
|
|
110
110
|
body: {
|
|
111
111
|
snapshot,
|
|
@@ -113,15 +113,15 @@ var saveDraft = (id, snapshot, metadata, expectedDraft) => api$6.request(`/contr
|
|
|
113
113
|
expectedDraft
|
|
114
114
|
}
|
|
115
115
|
});
|
|
116
|
-
var listPresets = () => api$
|
|
117
|
-
var previewPreset = (id, application, snapshot) => api$
|
|
116
|
+
var listPresets = () => api$5.request("/configuration/presets");
|
|
117
|
+
var previewPreset = (id, application, snapshot) => api$5.request(`/controllers/${id}/configuration/presets/preview`, {
|
|
118
118
|
method: "POST",
|
|
119
119
|
body: {
|
|
120
120
|
application,
|
|
121
121
|
snapshot
|
|
122
122
|
}
|
|
123
123
|
});
|
|
124
|
-
var applyPreset = (id, application, selectedPaths, previewedDraftHash, snapshot) => api$
|
|
124
|
+
var applyPreset = (id, application, selectedPaths, previewedDraftHash, snapshot) => api$5.request(`/controllers/${id}/configuration/presets/apply`, {
|
|
125
125
|
method: "POST",
|
|
126
126
|
body: {
|
|
127
127
|
application,
|
|
@@ -130,28 +130,28 @@ var applyPreset = (id, application, selectedPaths, previewedDraftHash, snapshot)
|
|
|
130
130
|
snapshot
|
|
131
131
|
}
|
|
132
132
|
});
|
|
133
|
-
var validateConfiguration = (id, snapshot) => api$
|
|
133
|
+
var validateConfiguration = (id, snapshot) => api$5.request(`/controllers/${id}/configuration/validate`, {
|
|
134
134
|
method: "POST",
|
|
135
135
|
body: { snapshot }
|
|
136
136
|
});
|
|
137
|
-
var reviewConfiguration = (id) => api$
|
|
138
|
-
var publishConfiguration = (id, force, reviewedHash) => api$
|
|
137
|
+
var reviewConfiguration = (id) => api$5.request(`/controllers/${id}/configuration/review`, { method: "POST" });
|
|
138
|
+
var publishConfiguration = (id, force, reviewedHash) => api$5.request(`/controllers/${id}/configuration/publish`, {
|
|
139
139
|
method: "POST",
|
|
140
140
|
body: {
|
|
141
141
|
force,
|
|
142
142
|
reviewedHash
|
|
143
143
|
}
|
|
144
144
|
});
|
|
145
|
-
var listConfigurationRevisions = (id, offset) => api$
|
|
146
|
-
var previewConfigurationRevision = (id, revision) => api$
|
|
147
|
-
var acknowledgeConfigurationRejection = (id, revision, contentHash, reportedAt) => api$
|
|
145
|
+
var listConfigurationRevisions = (id, offset) => api$5.request(`/controllers/${id}/configuration/revisions?offset=${offset}&limit=20`);
|
|
146
|
+
var previewConfigurationRevision = (id, revision) => api$5.request(`/controllers/${id}/configuration/revisions/${revision}/preview`);
|
|
147
|
+
var acknowledgeConfigurationRejection = (id, revision, contentHash, reportedAt) => api$5.request(`/controllers/${id}/configuration/revisions/${revision}/acknowledge-rejection`, {
|
|
148
148
|
method: "POST",
|
|
149
149
|
body: {
|
|
150
150
|
contentHash,
|
|
151
151
|
reportedAt
|
|
152
152
|
}
|
|
153
153
|
});
|
|
154
|
-
var rollbackConfiguration = (id, revision, force, sourceHash, currentHash, draftHash) => api$
|
|
154
|
+
var rollbackConfiguration = (id, revision, force, sourceHash, currentHash, draftHash) => api$5.request(`/controllers/${id}/configuration/rollback/${revision}`, {
|
|
155
155
|
method: "POST",
|
|
156
156
|
body: {
|
|
157
157
|
force,
|
|
@@ -369,7 +369,7 @@ function useConfigurationActions(controllerId) {
|
|
|
369
369
|
//#endregion
|
|
370
370
|
//#region frontend/src/ClaimControllerModal.tsx
|
|
371
371
|
var import_jsx_runtime = require_jsx_runtime();
|
|
372
|
-
var { Alert: Alert$
|
|
372
|
+
var { Alert: Alert$8, Button: Button$17, Form: Form$3, Input: Input$6, Label: Label$9, Modal: Modal$2, ModalBackdrop: ModalBackdrop$1, ModalBody: ModalBody$1, ModalContainer: ModalContainer$1, ModalDialog: ModalDialog$1, ModalFooter: ModalFooter$1, ModalHeader: ModalHeader$1, ModalHeading: ModalHeading$1, TextField: TextField$6 } = await importShared("@heroui/react");
|
|
373
373
|
var { useEffectEvent, useState: useState$15 } = await importShared("react");
|
|
374
374
|
function ClaimControllerModal({ controllerId, onOpenChange }) {
|
|
375
375
|
const claimMutation = useClaimControllerMutation();
|
|
@@ -412,31 +412,31 @@ function ClaimControllerModal({ controllerId, onOpenChange }) {
|
|
|
412
412
|
className: "wg:text-sm wg:text-muted",
|
|
413
413
|
children: "Verify physical access to this controller before it can receive commands."
|
|
414
414
|
}),
|
|
415
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TextField$
|
|
415
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TextField$6, {
|
|
416
416
|
isRequired: true,
|
|
417
417
|
name: "name",
|
|
418
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$
|
|
418
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$9, { children: "Controller name" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input$6, {
|
|
419
419
|
value: name,
|
|
420
420
|
onChange: (event) => setName(event.target.value)
|
|
421
421
|
})]
|
|
422
422
|
}),
|
|
423
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TextField$
|
|
423
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TextField$6, {
|
|
424
424
|
isRequired: true,
|
|
425
425
|
name: "verifier",
|
|
426
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$
|
|
426
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$9, { children: "Physical pairing code or fingerprint" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input$6, {
|
|
427
427
|
value: verifier,
|
|
428
428
|
onChange: (event) => setVerifier(event.target.value)
|
|
429
429
|
})]
|
|
430
430
|
}),
|
|
431
|
-
claimMutation.isError && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert$
|
|
431
|
+
claimMutation.isError && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert$8, {
|
|
432
432
|
status: "danger",
|
|
433
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$
|
|
433
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$8.Indicator, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$8.Content, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$8.Description, { children: getErrorMessage$1(claimMutation.error) }) })]
|
|
434
434
|
})
|
|
435
|
-
] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ModalFooter$1, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$
|
|
435
|
+
] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ModalFooter$1, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$17, {
|
|
436
436
|
variant: "secondary",
|
|
437
437
|
onPress: close,
|
|
438
438
|
children: "Cancel"
|
|
439
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$
|
|
439
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$17, {
|
|
440
440
|
type: "submit",
|
|
441
441
|
isPending: claimMutation.isPending,
|
|
442
442
|
children: "Verify and claim"
|
|
@@ -449,8 +449,42 @@ function getErrorMessage$1(error) {
|
|
|
449
449
|
return error instanceof Error ? error.message : "Please try again.";
|
|
450
450
|
}
|
|
451
451
|
//#endregion
|
|
452
|
+
//#region frontend/src/useCommissioningVerification.ts
|
|
453
|
+
var { useQuery: useQuery$3 } = await importShared("@tanstack/react-query");
|
|
454
|
+
var { useEffect: useEffect$12, useState: useState$14 } = await importShared("react");
|
|
455
|
+
/** Enrollment evidence is independent of configuration and physical qualification. */
|
|
456
|
+
function useCommissioningVerification(session) {
|
|
457
|
+
const enabled = ["awaiting_verification", "completed"].includes(session.state);
|
|
458
|
+
const query = useQuery$3({
|
|
459
|
+
queryKey: [
|
|
460
|
+
"wago",
|
|
461
|
+
"commissioning-verification",
|
|
462
|
+
session.id
|
|
463
|
+
],
|
|
464
|
+
queryFn: () => getCommissioningVerification(session.id),
|
|
465
|
+
enabled,
|
|
466
|
+
refetchInterval: 5e3
|
|
467
|
+
});
|
|
468
|
+
const [now, setNow] = useState$14(Date.now);
|
|
469
|
+
useEffect$12(() => {
|
|
470
|
+
if (!enabled) return;
|
|
471
|
+
const timer = setInterval(() => setNow(Date.now()), 1e3);
|
|
472
|
+
return () => clearInterval(timer);
|
|
473
|
+
}, [enabled]);
|
|
474
|
+
const stale = !!query.data && now - query.dataUpdatedAt > 15e3;
|
|
475
|
+
const unavailable = query.isError || stale;
|
|
476
|
+
const data = enabled && !unavailable ? query.data : void 0;
|
|
477
|
+
const enrollmentComplete = !!(data?.controllerId && data.permanentConnection && data.enrollmentRevoked);
|
|
478
|
+
return {
|
|
479
|
+
data,
|
|
480
|
+
unavailable,
|
|
481
|
+
enrollmentComplete,
|
|
482
|
+
runtimeVerified: enrollmentComplete && data?.configurationApplied === true && data.hardwareReadiness === "ready"
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
//#endregion
|
|
452
486
|
//#region frontend/src/ControllersTable.tsx
|
|
453
|
-
var { Button: Button$
|
|
487
|
+
var { Button: Button$16, Chip: Chip$2, Table, TableBody, TableCell, TableColumn, TableContent, TableHeader, TableRow, TableScrollContainer } = await importShared("@heroui/react");
|
|
454
488
|
function ControllersTable({ controllers, sessions, onClaim, onConfigure, onRemove, onResume }) {
|
|
455
489
|
const activeSessions = sessions.filter((session) => session.state !== "completed" && (session.state !== "revoked" || !!session.runtimeRecoveryAvailable || !!session.dockerProvisionState || !!session.managementControllerId));
|
|
456
490
|
const rows = [...controllers.map((controller) => ({
|
|
@@ -540,23 +574,23 @@ function ControllerRow({ row, onClaim, onConfigure, onRemove, onResume }) {
|
|
|
540
574
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
541
575
|
className: "wg:flex wg:justify-end wg:gap-2",
|
|
542
576
|
children: [
|
|
543
|
-
session && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$
|
|
577
|
+
session && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$16, {
|
|
544
578
|
size: "sm",
|
|
545
579
|
variant: "secondary",
|
|
546
580
|
onPress: () => onResume(session),
|
|
547
581
|
children: "View progress"
|
|
548
582
|
}),
|
|
549
|
-
controller.trustState === "untrusted" ? !session && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$
|
|
583
|
+
controller.trustState === "untrusted" ? !session && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$16, {
|
|
550
584
|
size: "sm",
|
|
551
585
|
onPress: () => onClaim(controller.id),
|
|
552
586
|
children: "Claim"
|
|
553
|
-
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$
|
|
587
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$16, {
|
|
554
588
|
size: "sm",
|
|
555
589
|
variant: "secondary",
|
|
556
590
|
onPress: () => onConfigure(controller.id),
|
|
557
591
|
children: "Configure"
|
|
558
592
|
}),
|
|
559
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$
|
|
593
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$16, {
|
|
560
594
|
size: "sm",
|
|
561
595
|
variant: "danger",
|
|
562
596
|
onPress: () => onRemove(controller),
|
|
@@ -611,7 +645,7 @@ function CommissioningRow({ row, onResume }) {
|
|
|
611
645
|
className: "wg:hidden wg:lg:table-cell",
|
|
612
646
|
children: ["Updated ", new Date(session.updatedAt).toLocaleString()]
|
|
613
647
|
}),
|
|
614
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$
|
|
648
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$16, {
|
|
615
649
|
size: "sm",
|
|
616
650
|
variant: isResumable(session.state) ? "primary" : "secondary",
|
|
617
651
|
onPress: () => onResume(session),
|
|
@@ -621,9 +655,11 @@ function CommissioningRow({ row, onResume }) {
|
|
|
621
655
|
}, row.key);
|
|
622
656
|
}
|
|
623
657
|
function CommissioningStatus({ session }) {
|
|
658
|
+
const verification = useCommissioningVerification(session);
|
|
659
|
+
const label = verification.enrollmentComplete ? verification.runtimeVerified ? "Enrollment complete · runtime verified" : "Enrollment complete · runtime setup pending" : verification.unavailable ? "Verification status unavailable" : commissioningLabel(session.state);
|
|
624
660
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
625
661
|
className: "wg:mt-1 wg:text-xs wg:text-primary",
|
|
626
|
-
children: [
|
|
662
|
+
children: [label, session.failureReason ? `: ${session.failureReason}` : ""]
|
|
627
663
|
});
|
|
628
664
|
}
|
|
629
665
|
function EmptyControllers() {
|
|
@@ -678,27 +714,27 @@ function formatHeartbeat(lastHeartbeatAt) {
|
|
|
678
714
|
}
|
|
679
715
|
//#endregion
|
|
680
716
|
//#region frontend/src/RuntimeArtifactImport.tsx
|
|
681
|
-
var { useEffect: useEffect$11, useRef: useRef$
|
|
682
|
-
var { Button: Button$
|
|
683
|
-
var api$
|
|
717
|
+
var { useEffect: useEffect$11, useRef: useRef$5, useState: useState$13 } = await importShared("react");
|
|
718
|
+
var { Button: Button$15 } = await importShared("@heroui/react");
|
|
719
|
+
var api$4 = createPluginApiClient("/api/wago/runtime-artifacts");
|
|
684
720
|
var maxBytes = 536870912;
|
|
685
721
|
/** Standalone admin panel; uses the host API origin and authenticated session automatically. */
|
|
686
722
|
function RuntimeArtifactImport({ onImported, onBusyChange, onSelectionChange, disabled = false }) {
|
|
687
|
-
const [current, setCurrent] = useState$
|
|
723
|
+
const [current, setCurrent] = useState$13(null);
|
|
688
724
|
useEffect$11(() => {
|
|
689
725
|
onSelectionChange?.(current);
|
|
690
726
|
}, [current, onSelectionChange]);
|
|
691
|
-
const [artifacts, setArtifacts] = useState$
|
|
692
|
-
const [files, setFiles] = useState$
|
|
693
|
-
const [busy, setBusy] = useState$
|
|
694
|
-
const [loading, setLoading] = useState$
|
|
695
|
-
const [loadFailed, setLoadFailed] = useState$
|
|
696
|
-
const [loadAttempt, setLoadAttempt] = useState$
|
|
697
|
-
const [error, setError] = useState$
|
|
698
|
-
const [status, setStatus] = useState$
|
|
699
|
-
const form = useRef$
|
|
700
|
-
const uploadAbort = useRef$
|
|
701
|
-
const busyCallback = useRef$
|
|
727
|
+
const [artifacts, setArtifacts] = useState$13([]);
|
|
728
|
+
const [files, setFiles] = useState$13({});
|
|
729
|
+
const [busy, setBusy] = useState$13(false);
|
|
730
|
+
const [loading, setLoading] = useState$13(true);
|
|
731
|
+
const [loadFailed, setLoadFailed] = useState$13(false);
|
|
732
|
+
const [loadAttempt, setLoadAttempt] = useState$13(0);
|
|
733
|
+
const [error, setError] = useState$13("");
|
|
734
|
+
const [status, setStatus] = useState$13("");
|
|
735
|
+
const form = useRef$5(null);
|
|
736
|
+
const uploadAbort = useRef$5(null);
|
|
737
|
+
const busyCallback = useRef$5(onBusyChange);
|
|
702
738
|
useEffect$11(() => {
|
|
703
739
|
busyCallback.current = onBusyChange;
|
|
704
740
|
}, [onBusyChange]);
|
|
@@ -715,7 +751,7 @@ function RuntimeArtifactImport({ onImported, onBusyChange, onSelectionChange, di
|
|
|
715
751
|
}, []);
|
|
716
752
|
useEffect$11(() => {
|
|
717
753
|
const abort = new AbortController();
|
|
718
|
-
Promise.all([api$
|
|
754
|
+
Promise.all([api$4.request("/current", { signal: abort.signal }), api$4.request("", { signal: abort.signal })]).then(([active, entries]) => {
|
|
719
755
|
if (abort.signal.aborted) return;
|
|
720
756
|
setCurrent(active);
|
|
721
757
|
setArtifacts(entries);
|
|
@@ -730,14 +766,14 @@ function RuntimeArtifactImport({ onImported, onBusyChange, onSelectionChange, di
|
|
|
730
766
|
return () => abort.abort();
|
|
731
767
|
}, [loadAttempt]);
|
|
732
768
|
async function importRelease() {
|
|
733
|
-
if (!files.bundle || !files.checksum ||
|
|
734
|
-
if (files.bundle.size > maxBytes || files.checksum.size > 4096
|
|
735
|
-
setError("The runtime tar must be at most 512 MiB
|
|
769
|
+
if (!files.bundle || !files.checksum || disabled || loading || loadFailed || uploadAbort.current) return;
|
|
770
|
+
if (files.bundle.size > maxBytes || files.checksum.size > 4096) {
|
|
771
|
+
setError("The runtime tar must be at most 512 MiB and checksum 4 KiB.");
|
|
736
772
|
return;
|
|
737
773
|
}
|
|
738
774
|
setBusy(true);
|
|
739
775
|
setError("");
|
|
740
|
-
setStatus("Uploading and
|
|
776
|
+
setStatus("Uploading and checking the runtime release…");
|
|
741
777
|
const abort = new AbortController();
|
|
742
778
|
uploadAbort.current = abort;
|
|
743
779
|
let failureMessage = "Import could not be completed. Check your connection and retry with the selected files.";
|
|
@@ -745,15 +781,14 @@ function RuntimeArtifactImport({ onImported, onBusyChange, onSelectionChange, di
|
|
|
745
781
|
const body = new FormData();
|
|
746
782
|
body.append("bundle", files.bundle);
|
|
747
783
|
body.append("checksum", files.checksum);
|
|
748
|
-
|
|
749
|
-
const response = await api$5.fetch("/import", {
|
|
784
|
+
const response = await api$4.fetch("/import", {
|
|
750
785
|
method: "POST",
|
|
751
786
|
body,
|
|
752
787
|
signal: abort.signal
|
|
753
788
|
});
|
|
754
789
|
if (abort.signal.aborted) return;
|
|
755
790
|
if (!response.ok) {
|
|
756
|
-
failureMessage = response.status === 403 ? "Administrator access is required." : response.status === 409 ? "Another upload is in progress. Try again shortly." : "Import failed. Check that
|
|
791
|
+
failureMessage = response.status === 403 ? "Administrator access is required." : response.status === 409 ? "Another upload is in progress. Try again shortly." : "Import failed. Check that the runtime tar and checksum belong together and try again.";
|
|
757
792
|
throw new Error("Import rejected");
|
|
758
793
|
}
|
|
759
794
|
const artifact = await response.json();
|
|
@@ -778,15 +813,15 @@ function RuntimeArtifactImport({ onImported, onBusyChange, onSelectionChange, di
|
|
|
778
813
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
|
|
779
814
|
className: "wg:space-y-3",
|
|
780
815
|
"aria-label": "CC100 runtime release",
|
|
781
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: "CC100 runtime release" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Download the
|
|
816
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: "CC100 runtime release" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Download the WAGO runtime bundle, extract its archive, then select the runtime tar and checksum files." })] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
782
817
|
className: "wg:space-y-3",
|
|
783
818
|
children: [
|
|
784
819
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", {
|
|
785
820
|
href: "https://github.com/Attraccess/Attraccess/actions/workflows/wago-cc100-runtime.yml",
|
|
786
821
|
target: "_blank",
|
|
787
822
|
rel: "noreferrer",
|
|
788
|
-
children: "Official
|
|
789
|
-
}), ". Your software distributor can also provide these files.
|
|
823
|
+
children: "Official runtime build artifacts"
|
|
824
|
+
}), ". Your software distributor can also provide these files. Check the source before importing."] }),
|
|
790
825
|
loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Loading releases…" }) : loadFailed ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Runtime releases are unavailable." }) : current ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
|
|
791
826
|
className: "wg:break-words",
|
|
792
827
|
children: [
|
|
@@ -826,24 +861,16 @@ function RuntimeArtifactImport({ onImported, onBusyChange, onSelectionChange, di
|
|
|
826
861
|
disabled: disabled || busy || loading || loadFailed,
|
|
827
862
|
className: "wg:flex wg:flex-col wg:gap-3",
|
|
828
863
|
children: [
|
|
829
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("legend", { children: "
|
|
830
|
-
[
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
".sha256"
|
|
840
|
-
],
|
|
841
|
-
[
|
|
842
|
-
"signature",
|
|
843
|
-
"Signature (.sig)",
|
|
844
|
-
".sig"
|
|
845
|
-
]
|
|
846
|
-
].map(([field, label, accept]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [label, /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
|
|
864
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("legend", { children: "Runtime release files" }),
|
|
865
|
+
[[
|
|
866
|
+
"bundle",
|
|
867
|
+
"Runtime bundle (.tar)",
|
|
868
|
+
".tar"
|
|
869
|
+
], [
|
|
870
|
+
"checksum",
|
|
871
|
+
"Checksum (.sha256)",
|
|
872
|
+
".sha256"
|
|
873
|
+
]].map(([field, label, accept]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [label, /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
|
|
847
874
|
type: "file",
|
|
848
875
|
accept,
|
|
849
876
|
required: true,
|
|
@@ -852,9 +879,9 @@ function RuntimeArtifactImport({ onImported, onBusyChange, onSelectionChange, di
|
|
|
852
879
|
[field]: event.target.files?.[0]
|
|
853
880
|
}))
|
|
854
881
|
})] }, field)),
|
|
855
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$
|
|
882
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$15, {
|
|
856
883
|
type: "submit",
|
|
857
|
-
isDisabled: disabled || busy || loading || loadFailed || !files.bundle || !files.checksum
|
|
884
|
+
isDisabled: disabled || busy || loading || loadFailed || !files.bundle || !files.checksum,
|
|
858
885
|
children: busy ? "Verifying release…" : "Import and select release"
|
|
859
886
|
})
|
|
860
887
|
]
|
|
@@ -869,7 +896,7 @@ function RuntimeArtifactImport({ onImported, onBusyChange, onSelectionChange, di
|
|
|
869
896
|
role: "alert",
|
|
870
897
|
children: error
|
|
871
898
|
}),
|
|
872
|
-
loadFailed && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$
|
|
899
|
+
loadFailed && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$15, {
|
|
873
900
|
variant: "secondary",
|
|
874
901
|
isDisabled: loading || busy,
|
|
875
902
|
onPress: () => {
|
|
@@ -895,18 +922,18 @@ function RuntimeArtifactImport({ onImported, onBusyChange, onSelectionChange, di
|
|
|
895
922
|
}
|
|
896
923
|
//#endregion
|
|
897
924
|
//#region frontend/src/ManagementSecurityStatus.tsx
|
|
898
|
-
var { Alert: Alert$
|
|
899
|
-
var { useEffect: useEffect$10, useRef: useRef$
|
|
925
|
+
var { Alert: Alert$7, Button: Button$14, Checkbox: Checkbox$5, Form: Form$2, Input: Input$5, Label: Label$8, TextField: TextField$5 } = await importShared("@heroui/react");
|
|
926
|
+
var { useEffect: useEffect$10, useRef: useRef$4, useState: useState$12 } = await importShared("react");
|
|
900
927
|
function ManagementSecurityStatus(props) {
|
|
901
|
-
const [status, setStatus] = useState$
|
|
902
|
-
const [mode, setMode] = useState$
|
|
903
|
-
const [exceptions, setExceptions] = useState$
|
|
904
|
-
const [confirmed, setConfirmed] = useState$
|
|
905
|
-
const [credentialGeneration, setCredentialGeneration] = useState$
|
|
906
|
-
const [pending, setPending] = useState$
|
|
907
|
-
const [failed, setFailed] = useState$
|
|
908
|
-
const form = useRef$
|
|
909
|
-
const generation = useRef$
|
|
928
|
+
const [status, setStatus] = useState$12(props.status);
|
|
929
|
+
const [mode, setMode] = useState$12("baseline");
|
|
930
|
+
const [exceptions, setExceptions] = useState$12([]);
|
|
931
|
+
const [confirmed, setConfirmed] = useState$12(false);
|
|
932
|
+
const [credentialGeneration, setCredentialGeneration] = useState$12(0);
|
|
933
|
+
const [pending, setPending] = useState$12(false);
|
|
934
|
+
const [failed, setFailed] = useState$12(false);
|
|
935
|
+
const form = useRef$4(null);
|
|
936
|
+
const generation = useRef$4(0);
|
|
910
937
|
useEffect$10(() => {
|
|
911
938
|
setStatus(props.status);
|
|
912
939
|
}, [props.status]);
|
|
@@ -979,34 +1006,34 @@ function ManagementSecurityStatus(props) {
|
|
|
979
1006
|
onSubmit: (event) => event.preventDefault(),
|
|
980
1007
|
"aria-label": "Management security actions",
|
|
981
1008
|
children: [
|
|
982
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TextField$
|
|
1009
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TextField$5, {
|
|
983
1010
|
name: "managementUsername",
|
|
984
1011
|
isRequired: true,
|
|
985
1012
|
isDisabled: pending,
|
|
986
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$
|
|
1013
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$8, { children: "Temporary SSH username" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input$5, {
|
|
987
1014
|
autoComplete: "off",
|
|
988
1015
|
maxLength: 32
|
|
989
1016
|
})]
|
|
990
1017
|
}, `username-${credentialGeneration}`),
|
|
991
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TextField$
|
|
1018
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TextField$5, {
|
|
992
1019
|
name: "managementPassword",
|
|
993
1020
|
isRequired: true,
|
|
994
1021
|
isDisabled: pending,
|
|
995
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$
|
|
1022
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$8, { children: "Temporary SSH password" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input$5, {
|
|
996
1023
|
type: "password",
|
|
997
1024
|
autoComplete: "off",
|
|
998
1025
|
maxLength: 4096
|
|
999
1026
|
})]
|
|
1000
1027
|
}, `password-${credentialGeneration}`),
|
|
1001
1028
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Credentials are cleared after each request. Apply and recovery require fresh credentials." }),
|
|
1002
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$
|
|
1029
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$14, {
|
|
1003
1030
|
type: "button",
|
|
1004
1031
|
variant: "secondary",
|
|
1005
1032
|
isDisabled: pending || !!recovery,
|
|
1006
1033
|
onPress: () => void run("inspect"),
|
|
1007
1034
|
children: "Inspect management"
|
|
1008
1035
|
}),
|
|
1009
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$
|
|
1036
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$14, {
|
|
1010
1037
|
type: "button",
|
|
1011
1038
|
variant: mode === "baseline" ? "primary" : "secondary",
|
|
1012
1039
|
isDisabled: pending || !!recovery,
|
|
@@ -1016,7 +1043,7 @@ function ManagementSecurityStatus(props) {
|
|
|
1016
1043
|
},
|
|
1017
1044
|
children: "Full baseline"
|
|
1018
1045
|
}),
|
|
1019
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$
|
|
1046
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$14, {
|
|
1020
1047
|
type: "button",
|
|
1021
1048
|
variant: mode === "key_only" ? "primary" : "secondary",
|
|
1022
1049
|
isDisabled: pending || !!recovery,
|
|
@@ -1039,16 +1066,16 @@ function ManagementSecurityStatus(props) {
|
|
|
1039
1066
|
id: "unqualified_privileges",
|
|
1040
1067
|
label: "Acknowledge unqualified account privileges (required for key enrollment)"
|
|
1041
1068
|
}
|
|
1042
|
-
].map(({ id, label }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Checkbox$
|
|
1069
|
+
].map(({ id, label }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Checkbox$5, {
|
|
1043
1070
|
isSelected: exceptions.includes(id),
|
|
1044
1071
|
isDisabled: pending || !!recovery,
|
|
1045
1072
|
onChange: (selected) => {
|
|
1046
1073
|
setExceptions((current) => selected ? [...current.filter((value) => value !== id), id] : current.filter((value) => value !== id));
|
|
1047
1074
|
setConfirmed(false);
|
|
1048
1075
|
},
|
|
1049
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox$
|
|
1076
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox$5.Control, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox$5.Indicator, {}) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox$5.Content, { children: label })]
|
|
1050
1077
|
}, id)),
|
|
1051
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$
|
|
1078
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$14, {
|
|
1052
1079
|
type: "button",
|
|
1053
1080
|
variant: "secondary",
|
|
1054
1081
|
isDisabled: pending || !status?.inspection || !!recovery,
|
|
@@ -1056,19 +1083,19 @@ function ManagementSecurityStatus(props) {
|
|
|
1056
1083
|
children: "Review changes"
|
|
1057
1084
|
}),
|
|
1058
1085
|
reviewed && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: mode === "key_only" ? "Review: snapshot authorized keys, arm rollback, add a unique key, verify a new pinned key connection, then retain the recovery snapshot. Existing access remains enabled." : "Review: snapshot access, arm reboot-safe rollback, add and verify a unique management key, restrict access, verify a new connection and the service baseline, then retain recovery." }),
|
|
1059
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Checkbox$
|
|
1086
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Checkbox$5, {
|
|
1060
1087
|
isSelected: confirmed,
|
|
1061
1088
|
isDisabled: pending,
|
|
1062
1089
|
onChange: setConfirmed,
|
|
1063
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox$
|
|
1090
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox$5.Control, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox$5.Indicator, {}) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox$5.Content, { children: "I confirm the reviewed change or explicit recovery for this controller." })]
|
|
1064
1091
|
}),
|
|
1065
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$
|
|
1092
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$14, {
|
|
1066
1093
|
type: "button",
|
|
1067
1094
|
isDisabled: pending || !confirmed || !reviewed || status?.support !== "supported",
|
|
1068
1095
|
onPress: () => void run("apply"),
|
|
1069
1096
|
children: "Apply reviewed change"
|
|
1070
1097
|
}),
|
|
1071
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$
|
|
1098
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$14, {
|
|
1072
1099
|
type: "button",
|
|
1073
1100
|
variant: "secondary",
|
|
1074
1101
|
isDisabled: pending || !confirmed || !recovery,
|
|
@@ -1098,9 +1125,9 @@ function ManagementSummary({ status, recovery }) {
|
|
|
1098
1125
|
status?.support ?? "qualification_required"
|
|
1099
1126
|
]
|
|
1100
1127
|
}),
|
|
1101
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert$
|
|
1128
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert$7, {
|
|
1102
1129
|
status: "warning",
|
|
1103
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$
|
|
1130
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$7.Indicator, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$7.Content, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$7.Description, { children: "Automatic FW31 management hardening is not implemented. Missing operations are a restricted management account, root/password restrictions, WBM and service restrictions, and recovery that survives reboot. The FW31 vendor procedure for applying, restoring and persisting these changes is still needed. Key enrollment supports an existing non-root OpenSSH account or a detected Dropbear 2025.88 account. The new key disables forwarding and PTY allocation; account privileges and existing access remain enabled. Exceptions never count as hardened. Physical hardware verification is separate." }) })]
|
|
1104
1131
|
}),
|
|
1105
1132
|
status?.inspection && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("dl", { children: [
|
|
1106
1133
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Firmware / SSH / service control" }),
|
|
@@ -1137,16 +1164,16 @@ function ManagementSummary({ status, recovery }) {
|
|
|
1137
1164
|
}
|
|
1138
1165
|
//#endregion
|
|
1139
1166
|
//#region frontend/src/CommissioningSecurityPanel.tsx
|
|
1140
|
-
var { useEffect: useEffect$9, useRef: useRef$
|
|
1141
|
-
var api$
|
|
1167
|
+
var { useEffect: useEffect$9, useRef: useRef$3, useState: useState$11 } = await importShared("react");
|
|
1168
|
+
var api$3 = createPluginApiClient("/api/wago/commissioning/sessions");
|
|
1142
1169
|
function CommissioningSecurityPanel({ sessionId, controllerId }) {
|
|
1143
|
-
const [status, setStatus] = useState$
|
|
1144
|
-
const [loadError, setLoadError] = useState$
|
|
1145
|
-
const generation = useRef$
|
|
1170
|
+
const [status, setStatus] = useState$11(null);
|
|
1171
|
+
const [loadError, setLoadError] = useState$11(false);
|
|
1172
|
+
const generation = useRef$3(0);
|
|
1146
1173
|
useEffect$9(() => {
|
|
1147
1174
|
const current = ++generation.current;
|
|
1148
1175
|
const abort = new AbortController();
|
|
1149
|
-
api$
|
|
1176
|
+
api$3.request(`/${sessionId}/management`, { signal: abort.signal }).then((value) => {
|
|
1150
1177
|
if (generation.current === current) setStatus(value);
|
|
1151
1178
|
}).catch(() => {
|
|
1152
1179
|
if (!abort.signal.aborted) setLoadError(true);
|
|
@@ -1158,7 +1185,7 @@ function CommissioningSecurityPanel({ sessionId, controllerId }) {
|
|
|
1158
1185
|
}, [sessionId]);
|
|
1159
1186
|
async function request(action, body) {
|
|
1160
1187
|
const current = ++generation.current;
|
|
1161
|
-
const value = await api$
|
|
1188
|
+
const value = await api$3.request(`/${sessionId}/management/${action}`, {
|
|
1162
1189
|
method: "POST",
|
|
1163
1190
|
body
|
|
1164
1191
|
});
|
|
@@ -1185,18 +1212,19 @@ function CommissioningSecurityPanel({ sessionId, controllerId }) {
|
|
|
1185
1212
|
}
|
|
1186
1213
|
//#endregion
|
|
1187
1214
|
//#region frontend/src/CommissioningPlatformPreflight.tsx
|
|
1188
|
-
var { Alert: Alert$
|
|
1189
|
-
var { useEffect: useEffect$8, useRef: useRef$
|
|
1215
|
+
var { Alert: Alert$6, Button: Button$13, Checkbox: Checkbox$4, Input: Input$4, Label: Label$7, TextField: TextField$4 } = await importShared("@heroui/react");
|
|
1216
|
+
var { useEffect: useEffect$8, useRef: useRef$2, useState: useState$10 } = await importShared("react");
|
|
1190
1217
|
var { useQueryClient: useQueryClient$1 } = await importShared("@tanstack/react-query");
|
|
1191
|
-
var api$
|
|
1218
|
+
var api$2 = createPluginApiClient("/api/wago/commissioning/sessions");
|
|
1192
1219
|
function CommissioningPlatformPreflight({ session }) {
|
|
1193
1220
|
const client = useQueryClient$1();
|
|
1194
|
-
const [updated, setUpdated] = useState$
|
|
1195
|
-
const [busy, setBusy] = useState$
|
|
1196
|
-
const [approved, setApproved] = useState$
|
|
1197
|
-
const [
|
|
1198
|
-
const
|
|
1199
|
-
const
|
|
1221
|
+
const [updated, setUpdated] = useState$10(null);
|
|
1222
|
+
const [busy, setBusy] = useState$10(false);
|
|
1223
|
+
const [approved, setApproved] = useState$10(false);
|
|
1224
|
+
const [customSsh, setCustomSsh] = useState$10(false);
|
|
1225
|
+
const [error, setError] = useState$10("");
|
|
1226
|
+
const form = useRef$2(null);
|
|
1227
|
+
const generation = useRef$2(0);
|
|
1200
1228
|
useEffect$8(() => () => {
|
|
1201
1229
|
generation.current++;
|
|
1202
1230
|
form.current?.reset();
|
|
@@ -1212,16 +1240,17 @@ function CommissioningPlatformPreflight({ session }) {
|
|
|
1212
1240
|
if (busy || !form.current?.reportValidity() || action !== "inspect" && !approved) return;
|
|
1213
1241
|
const values = new FormData(form.current);
|
|
1214
1242
|
const temporarySsh = {
|
|
1215
|
-
username: String(values.get("preflightUsername") ?? ""),
|
|
1216
|
-
password: String(values.get("preflightPassword") ?? "")
|
|
1243
|
+
username: customSsh ? String(values.get("preflightUsername") ?? "") : "root",
|
|
1244
|
+
password: customSsh ? String(values.get("preflightPassword") ?? "") : "wago"
|
|
1217
1245
|
};
|
|
1218
1246
|
form.current.reset();
|
|
1247
|
+
setCustomSsh(false);
|
|
1219
1248
|
setApproved(false);
|
|
1220
1249
|
setBusy(true);
|
|
1221
1250
|
setError("");
|
|
1222
1251
|
const request = generation.current;
|
|
1223
1252
|
try {
|
|
1224
|
-
const value = await api$
|
|
1253
|
+
const value = await api$2.request(`/${session.id}/platform/${action}`, {
|
|
1225
1254
|
method: "POST",
|
|
1226
1255
|
body: {
|
|
1227
1256
|
temporarySsh,
|
|
@@ -1233,7 +1262,7 @@ function CommissioningPlatformPreflight({ session }) {
|
|
|
1233
1262
|
client.setQueryData(["wago", "commissioning-sessions"], (entries) => entries?.map((entry) => entry.id === value.id ? value : entry));
|
|
1234
1263
|
}
|
|
1235
1264
|
} catch {
|
|
1236
|
-
if (request === generation.current) setError("
|
|
1265
|
+
if (request === generation.current) setError("Could not inspect or clean up the controller. Check SSH access and try again.");
|
|
1237
1266
|
} finally {
|
|
1238
1267
|
temporarySsh.password = "";
|
|
1239
1268
|
if (request === generation.current) setBusy(false);
|
|
@@ -1243,8 +1272,8 @@ function CommissioningPlatformPreflight({ session }) {
|
|
|
1243
1272
|
className: "wg:space-y-3",
|
|
1244
1273
|
"aria-label": "Controller installation preflight",
|
|
1245
1274
|
children: [
|
|
1246
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: "
|
|
1247
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "
|
|
1275
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: "Check the controller" }),
|
|
1276
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Optional: check firmware and installation prerequisites. This does not change the controller." }),
|
|
1248
1277
|
codesysDisabled && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
1249
1278
|
role: "status",
|
|
1250
1279
|
children: "Controller preparation verified CODESYS stopped and permanently disabled. This is a saved result, not a live controller status check."
|
|
@@ -1296,9 +1325,9 @@ function CommissioningPlatformPreflight({ session }) {
|
|
|
1296
1325
|
!codesysDisabled && report?.exclusivity === "codesys-active" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "CODESYS is active. Destructive installation will stop and permanently disable it. Existing PLC applications and data may be lost; Attraccess will not preserve, back up, or restore them. Installation fails if CODESYS cannot be verified stopped and disabled before I/O." }),
|
|
1297
1326
|
!codesysDisabled && report?.exclusivity === "codesys-boot-enabled" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "CODESYS is configured to start at boot. Destructive installation must disable that startup and verify CODESYS is stopped before I/O. A stopped process alone is insufficient. No separate PLC preservation or restoration approval is required." }),
|
|
1298
1327
|
report?.exclusivity === "output-container-conflict" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Another container can write the digital outputs. Resolve exclusive ownership before installing this runtime." }),
|
|
1299
|
-
report?.provision === "unsupported-fw31-package-activation" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert$
|
|
1328
|
+
report?.provision === "unsupported-fw31-package-activation" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert$6, {
|
|
1300
1329
|
status: "warning",
|
|
1301
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$
|
|
1330
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$6.Indicator, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$6.Content, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$6.Description, { children: "This report could not establish a supported Docker package installation path. Retry installation only after the reported package or compatibility issue is resolved. An old inspection does not authorize activation by itself." }) })]
|
|
1302
1331
|
}),
|
|
1303
1332
|
report?.provision === "unsupported-lifecycle-dependencies" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "This report could not verify the Docker lifecycle dependencies. Installation must validate a supported activation path before changing Docker. Vendor activation can change startup, routing and firewall settings; preexisting settings are not restored by commissioning." }),
|
|
1304
1333
|
current.dockerProvisionState && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
|
|
@@ -1317,34 +1346,40 @@ function CommissioningPlatformPreflight({ session }) {
|
|
|
1317
1346
|
ref: form,
|
|
1318
1347
|
onSubmit: (event) => event.preventDefault(),
|
|
1319
1348
|
children: [
|
|
1320
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1349
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { children: ["SSH login: ", customSsh ? "Custom credentials" : "Default root account"] }),
|
|
1350
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Checkbox$4, {
|
|
1351
|
+
isSelected: customSsh,
|
|
1352
|
+
onChange: setCustomSsh,
|
|
1353
|
+
isDisabled: busy,
|
|
1354
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox$4.Control, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox$4.Indicator, {}) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox$4.Content, { children: "Advanced: use different SSH credentials" })]
|
|
1355
|
+
}),
|
|
1356
|
+
customSsh && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TextField$4, {
|
|
1321
1357
|
name: "preflightUsername",
|
|
1322
1358
|
isRequired: true,
|
|
1323
1359
|
isDisabled: busy,
|
|
1324
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$
|
|
1325
|
-
}),
|
|
1326
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TextField$5, {
|
|
1360
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$7, { children: "Preflight SSH username" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input$4, { autoComplete: "off" })]
|
|
1361
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TextField$4, {
|
|
1327
1362
|
name: "preflightPassword",
|
|
1328
1363
|
isRequired: true,
|
|
1329
1364
|
isDisabled: busy,
|
|
1330
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$
|
|
1365
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$7, { children: "Preflight SSH password" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input$4, {
|
|
1331
1366
|
type: "password",
|
|
1332
1367
|
autoComplete: "off"
|
|
1333
1368
|
})]
|
|
1334
|
-
}),
|
|
1335
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$
|
|
1369
|
+
})] }),
|
|
1370
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$13, {
|
|
1336
1371
|
type: "button",
|
|
1337
1372
|
variant: "secondary",
|
|
1338
1373
|
isDisabled: busy,
|
|
1339
1374
|
onPress: () => void run("inspect"),
|
|
1340
1375
|
children: "Inspect installation prerequisites"
|
|
1341
1376
|
}),
|
|
1342
|
-
recovery && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Checkbox$
|
|
1377
|
+
recovery && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Checkbox$4, {
|
|
1343
1378
|
isSelected: approved,
|
|
1344
1379
|
onChange: setApproved,
|
|
1345
1380
|
isDisabled: busy,
|
|
1346
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox$
|
|
1347
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$
|
|
1381
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox$4.Control, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox$4.Indicator, {}) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox$4.Content, { children: "I approve cleaning up this controller preparation. Preexisting workloads and host settings will not be restored." })]
|
|
1382
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$13, {
|
|
1348
1383
|
type: "button",
|
|
1349
1384
|
isDisabled: busy || !approved,
|
|
1350
1385
|
onPress: () => void run("recover"),
|
|
@@ -1360,109 +1395,6 @@ function CommissioningPlatformPreflight({ session }) {
|
|
|
1360
1395
|
});
|
|
1361
1396
|
}
|
|
1362
1397
|
//#endregion
|
|
1363
|
-
//#region frontend/src/CommissioningOperationStatus.tsx
|
|
1364
|
-
var { useQuery: useQuery$3 } = await importShared("@tanstack/react-query");
|
|
1365
|
-
var { Alert: Alert$6, Button: Button$13, Checkbox: Checkbox$4, Input: Input$4, Label: Label$7, TextField: TextField$4 } = await importShared("@heroui/react");
|
|
1366
|
-
var { useRef: useRef$2, useState: useState$10 } = await importShared("react");
|
|
1367
|
-
var api$2 = createPluginApiClient("/api/wago/commissioning/sessions");
|
|
1368
|
-
function CommissioningOperationStatus({ sessionId }) {
|
|
1369
|
-
const query = useQuery$3({
|
|
1370
|
-
queryKey: [
|
|
1371
|
-
"wago",
|
|
1372
|
-
"commissioning-operation",
|
|
1373
|
-
sessionId
|
|
1374
|
-
],
|
|
1375
|
-
queryFn: () => api$2.request(`/${sessionId}/operation`),
|
|
1376
|
-
refetchInterval: 5e3
|
|
1377
|
-
});
|
|
1378
|
-
const [confirmed, setConfirmed] = useState$10(false);
|
|
1379
|
-
const [busy, setBusy] = useState$10(false);
|
|
1380
|
-
const [error, setError] = useState$10(false);
|
|
1381
|
-
const form = useRef$2(null);
|
|
1382
|
-
const status = query.data;
|
|
1383
|
-
async function recover() {
|
|
1384
|
-
if (!form.current?.reportValidity() || busy || !confirmed || status?.state !== "stale") return;
|
|
1385
|
-
const fields = new FormData(form.current);
|
|
1386
|
-
const temporarySsh = {
|
|
1387
|
-
username: String(fields.get("leaseUsername") ?? ""),
|
|
1388
|
-
password: String(fields.get("leasePassword") ?? "")
|
|
1389
|
-
};
|
|
1390
|
-
form.current.reset();
|
|
1391
|
-
setConfirmed(false);
|
|
1392
|
-
setBusy(true);
|
|
1393
|
-
setError(false);
|
|
1394
|
-
try {
|
|
1395
|
-
await api$2.request(`/${sessionId}/operation/recover`, {
|
|
1396
|
-
method: "POST",
|
|
1397
|
-
body: {
|
|
1398
|
-
temporarySsh,
|
|
1399
|
-
owner: status.owner,
|
|
1400
|
-
previousWorkerStopped: true
|
|
1401
|
-
}
|
|
1402
|
-
});
|
|
1403
|
-
await query.refetch();
|
|
1404
|
-
} catch {
|
|
1405
|
-
setError(true);
|
|
1406
|
-
} finally {
|
|
1407
|
-
temporarySsh.password = "";
|
|
1408
|
-
setBusy(false);
|
|
1409
|
-
}
|
|
1410
|
-
}
|
|
1411
|
-
if (!status || status.state === "available") return null;
|
|
1412
|
-
if (status.state !== "active" && status.state !== "stale") return null;
|
|
1413
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert$6, {
|
|
1414
|
-
status: "warning",
|
|
1415
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$6.Indicator, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert$6.Content, { children: [
|
|
1416
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$6.Title, { children: status.state === "active" ? "A commissioning operation owns this controller" : "Interrupted coordinator recovery required" }),
|
|
1417
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$6.Description, { children: "Another operation cannot change this controller until ownership is released. An application restart never silently takes over an interrupted operation." }),
|
|
1418
|
-
status.state === "stale" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", {
|
|
1419
|
-
ref: form,
|
|
1420
|
-
onSubmit: (event) => {
|
|
1421
|
-
event.preventDefault();
|
|
1422
|
-
recover();
|
|
1423
|
-
},
|
|
1424
|
-
children: [
|
|
1425
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { children: [
|
|
1426
|
-
"Recovery is available after ",
|
|
1427
|
-
new Date(status.recoveryAfter).toLocaleString(),
|
|
1428
|
-
". Stop the previous Attraccess commissioning instance first. The server then checks that both controller locks are idle. Runtime and management snapshots are not removed."
|
|
1429
|
-
] }),
|
|
1430
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TextField$4, {
|
|
1431
|
-
name: "leaseUsername",
|
|
1432
|
-
isRequired: true,
|
|
1433
|
-
isDisabled: busy,
|
|
1434
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$7, { children: "Coordinator recovery SSH username" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input$4, { autoComplete: "off" })]
|
|
1435
|
-
}),
|
|
1436
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TextField$4, {
|
|
1437
|
-
name: "leasePassword",
|
|
1438
|
-
isRequired: true,
|
|
1439
|
-
isDisabled: busy,
|
|
1440
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$7, { children: "Coordinator recovery SSH password" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input$4, {
|
|
1441
|
-
type: "password",
|
|
1442
|
-
autoComplete: "off"
|
|
1443
|
-
})]
|
|
1444
|
-
}),
|
|
1445
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Checkbox$4, {
|
|
1446
|
-
isSelected: confirmed,
|
|
1447
|
-
onChange: setConfirmed,
|
|
1448
|
-
isDisabled: busy,
|
|
1449
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox$4.Control, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox$4.Indicator, {}) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox$4.Content, { children: "The previous commissioning instance has stopped, not merely paused. I approve checking and releasing its expired operation." })]
|
|
1450
|
-
}),
|
|
1451
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$13, {
|
|
1452
|
-
type: "submit",
|
|
1453
|
-
isDisabled: busy || !confirmed,
|
|
1454
|
-
children: "Recover interrupted coordinator"
|
|
1455
|
-
})
|
|
1456
|
-
]
|
|
1457
|
-
}),
|
|
1458
|
-
error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
1459
|
-
role: "alert",
|
|
1460
|
-
children: "Recovery remains blocked. Check the safe timeout, previous instance and controller access, then retry."
|
|
1461
|
-
})
|
|
1462
|
-
] })]
|
|
1463
|
-
});
|
|
1464
|
-
}
|
|
1465
|
-
//#endregion
|
|
1466
1398
|
//#region frontend/src/drawer.tsx
|
|
1467
1399
|
var { DrawerBackdrop, DrawerContent, DrawerDialog } = await importShared("@heroui/react");
|
|
1468
1400
|
var DRAWER_DIALOG_CLASSNAME = "wg:md:mx-auto wg:md:max-w-2xl wg:bg-surface-secondary";
|
|
@@ -1488,6 +1420,10 @@ var { Alert: Alert$5, Button: Button$12, Checkbox: Checkbox$3, DrawerBody: Drawe
|
|
|
1488
1420
|
var { AlertCircleIcon, CheckCircle2Icon, CpuIcon: CpuIcon$1 } = await importShared("lucide-react");
|
|
1489
1421
|
var { useEffect: useEffect$7, useState: useState$9 } = await importShared("react");
|
|
1490
1422
|
var { useQuery: useQuery$2 } = await importShared("@tanstack/react-query");
|
|
1423
|
+
var DEFAULT_SSH = {
|
|
1424
|
+
username: "root",
|
|
1425
|
+
password: "wago"
|
|
1426
|
+
};
|
|
1491
1427
|
function useCommissioning({ isOpen, session: resumedSession, onOpenChange, onConfigure }) {
|
|
1492
1428
|
const createSessionMutation = useCreateCommissioningSessionMutation();
|
|
1493
1429
|
const confirmHostKeyMutation = useConfirmCommissioningHostKeyMutation();
|
|
@@ -1516,11 +1452,13 @@ function useCommissioning({ isOpen, session: resumedSession, onOpenChange, onCon
|
|
|
1516
1452
|
setIsolatedIdentity(false);
|
|
1517
1453
|
setHostKeyFingerprint("");
|
|
1518
1454
|
}, [resumedSession?.id, isOpen]);
|
|
1519
|
-
const [sshUsername, setSshUsername] = useState$9(
|
|
1520
|
-
const [sshPassword, setSshPassword] = useState$9(
|
|
1455
|
+
const [sshUsername, setSshUsername] = useState$9(DEFAULT_SSH.username);
|
|
1456
|
+
const [sshPassword, setSshPassword] = useState$9(DEFAULT_SSH.password);
|
|
1457
|
+
const [customSsh, setCustomSsh] = useState$9(false);
|
|
1521
1458
|
const [confirmInstall, setConfirmInstall] = useState$9(false);
|
|
1522
|
-
const [recoveryUsername, setRecoveryUsername] = useState$9(
|
|
1523
|
-
const [recoveryPassword, setRecoveryPassword] = useState$9(
|
|
1459
|
+
const [recoveryUsername, setRecoveryUsername] = useState$9(DEFAULT_SSH.username);
|
|
1460
|
+
const [recoveryPassword, setRecoveryPassword] = useState$9(DEFAULT_SSH.password);
|
|
1461
|
+
const [customRecoverySsh, setCustomRecoverySsh] = useState$9(false);
|
|
1524
1462
|
const [confirmRecovery, setConfirmRecovery] = useState$9(false);
|
|
1525
1463
|
const [isCancelConfirmationOpen, setCancelConfirmationOpen] = useState$9(false);
|
|
1526
1464
|
const attemptSession = recoverSessionMutation.submittedAt > deliverSessionMutation.submittedAt ? recoverSessionMutation.data : deliverSessionMutation.data;
|
|
@@ -1535,10 +1473,13 @@ function useCommissioning({ isOpen, session: resumedSession, onOpenChange, onCon
|
|
|
1535
1473
|
confirmHostKeyMutation
|
|
1536
1474
|
});
|
|
1537
1475
|
useEffect$7(() => {
|
|
1538
|
-
|
|
1476
|
+
setSshUsername(DEFAULT_SSH.username);
|
|
1477
|
+
setSshPassword(DEFAULT_SSH.password);
|
|
1478
|
+
setCustomSsh(false);
|
|
1539
1479
|
setConfirmInstall(false);
|
|
1540
|
-
setRecoveryUsername(
|
|
1541
|
-
setRecoveryPassword(
|
|
1480
|
+
setRecoveryUsername(DEFAULT_SSH.username);
|
|
1481
|
+
setRecoveryPassword(DEFAULT_SSH.password);
|
|
1482
|
+
setCustomRecoverySsh(false);
|
|
1542
1483
|
setConfirmRecovery(false);
|
|
1543
1484
|
setHostKeyFingerprint("");
|
|
1544
1485
|
setIsolatedIdentity(false);
|
|
@@ -1561,12 +1502,14 @@ function useCommissioning({ isOpen, session: resumedSession, onOpenChange, onCon
|
|
|
1561
1502
|
setControllerIp("");
|
|
1562
1503
|
setMqttServerId(null);
|
|
1563
1504
|
setHostKeyFingerprint("");
|
|
1564
|
-
setSshUsername(
|
|
1565
|
-
setSshPassword(
|
|
1505
|
+
setSshUsername(DEFAULT_SSH.username);
|
|
1506
|
+
setSshPassword(DEFAULT_SSH.password);
|
|
1507
|
+
setCustomSsh(false);
|
|
1566
1508
|
setConfirmInstall(false);
|
|
1567
1509
|
setCancelConfirmationOpen(false);
|
|
1568
|
-
setRecoveryUsername(
|
|
1569
|
-
setRecoveryPassword(
|
|
1510
|
+
setRecoveryUsername(DEFAULT_SSH.username);
|
|
1511
|
+
setRecoveryPassword(DEFAULT_SSH.password);
|
|
1512
|
+
setCustomRecoverySsh(false);
|
|
1570
1513
|
setConfirmRecovery(false);
|
|
1571
1514
|
createSessionMutation.reset();
|
|
1572
1515
|
confirmHostKeyMutation.reset();
|
|
@@ -1581,11 +1524,10 @@ function useCommissioning({ isOpen, session: resumedSession, onOpenChange, onCon
|
|
|
1581
1524
|
createSessionMutation.mutate({
|
|
1582
1525
|
name,
|
|
1583
1526
|
targetHost: controllerIp,
|
|
1584
|
-
mqttServerId: selectedMqttServerId
|
|
1585
|
-
runtimeArtifactDigest: selectedArtifact?.digest
|
|
1527
|
+
mqttServerId: selectedMqttServerId
|
|
1586
1528
|
}, { onSuccess: (created) => {
|
|
1587
1529
|
setCreatedSession(created);
|
|
1588
|
-
setStep(
|
|
1530
|
+
setStep(3);
|
|
1589
1531
|
} });
|
|
1590
1532
|
}
|
|
1591
1533
|
function deliverSession() {
|
|
@@ -1598,10 +1540,9 @@ function useCommissioning({ isOpen, session: resumedSession, onOpenChange, onCon
|
|
|
1598
1540
|
password: sshPassword
|
|
1599
1541
|
}
|
|
1600
1542
|
});
|
|
1601
|
-
setSshPassword("");
|
|
1543
|
+
if (customSsh) setSshPassword("");
|
|
1602
1544
|
setConfirmInstall(false);
|
|
1603
|
-
|
|
1604
|
-
setRecoveryPassword("");
|
|
1545
|
+
if (customRecoverySsh) setRecoveryPassword("");
|
|
1605
1546
|
setConfirmRecovery(false);
|
|
1606
1547
|
}
|
|
1607
1548
|
function recoverSession() {
|
|
@@ -1614,10 +1555,9 @@ function useCommissioning({ isOpen, session: resumedSession, onOpenChange, onCon
|
|
|
1614
1555
|
password: recoveryPassword
|
|
1615
1556
|
}
|
|
1616
1557
|
});
|
|
1617
|
-
|
|
1618
|
-
setRecoveryPassword("");
|
|
1558
|
+
if (customRecoverySsh) setRecoveryPassword("");
|
|
1619
1559
|
setConfirmRecovery(false);
|
|
1620
|
-
setSshPassword("");
|
|
1560
|
+
if (customSsh) setSshPassword("");
|
|
1621
1561
|
setConfirmInstall(false);
|
|
1622
1562
|
}
|
|
1623
1563
|
function confirmHostKey() {
|
|
@@ -1661,12 +1601,16 @@ function useCommissioning({ isOpen, session: resumedSession, onOpenChange, onCon
|
|
|
1661
1601
|
sshPassword,
|
|
1662
1602
|
setSshUsername,
|
|
1663
1603
|
setSshPassword,
|
|
1604
|
+
customSsh,
|
|
1605
|
+
setCustomSsh,
|
|
1664
1606
|
confirmInstall,
|
|
1665
1607
|
setConfirmInstall,
|
|
1666
1608
|
recoveryUsername,
|
|
1667
1609
|
recoveryPassword,
|
|
1668
1610
|
setRecoveryUsername,
|
|
1669
1611
|
setRecoveryPassword,
|
|
1612
|
+
customRecoverySsh,
|
|
1613
|
+
setCustomRecoverySsh,
|
|
1670
1614
|
confirmRecovery,
|
|
1671
1615
|
setConfirmRecovery,
|
|
1672
1616
|
createSessionMutation,
|
|
@@ -1701,7 +1645,7 @@ function CommissioningModal(props) {
|
|
|
1701
1645
|
});
|
|
1702
1646
|
}
|
|
1703
1647
|
function CommissioningContent({ model }) {
|
|
1704
|
-
const { onConfigure, session, activeStep, title, loadingStatus, hostKeyFingerprint, setHostKeyFingerprint, isolatedIdentity, setIsolatedIdentity, deliverSessionMutation, sshUsername, sshPassword, setSshUsername, setSshPassword, confirmInstall, setConfirmInstall, isCancelConfirmationOpen, configureController } = model;
|
|
1648
|
+
const { onConfigure, session, activeStep, title, loadingStatus, hostKeyFingerprint, setHostKeyFingerprint, isolatedIdentity, setIsolatedIdentity, deliverSessionMutation, sshUsername, sshPassword, setSshUsername, setSshPassword, customSsh, setCustomSsh, confirmInstall, setConfirmInstall, isCancelConfirmationOpen, configureController } = model;
|
|
1705
1649
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DrawerBody$1, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1706
1650
|
className: "wg:grid wg:min-w-0 wg:gap-5 wg:md:grid-cols-[13rem_minmax(0,1fr)]",
|
|
1707
1651
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DevicePassport, {
|
|
@@ -1716,16 +1660,7 @@ function CommissioningContent({ model }) {
|
|
|
1716
1660
|
title: loadingStatus[0],
|
|
1717
1661
|
description: loadingStatus[1]
|
|
1718
1662
|
}),
|
|
1719
|
-
session && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommissioningOperationStatus, { sessionId: session.id }, `operation-${session.id}`),
|
|
1720
1663
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConnectionFields, { model }),
|
|
1721
|
-
session?.runtimeArtifactDigest && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
|
|
1722
|
-
className: "wg:break-all wg:text-sm",
|
|
1723
|
-
children: [
|
|
1724
|
-
"Pinned signed release: ",
|
|
1725
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: session.runtimeArtifactDigest }),
|
|
1726
|
-
". Retries keep this release."
|
|
1727
|
-
]
|
|
1728
|
-
}),
|
|
1729
1664
|
session?.state === "awaiting_identity_confirmation" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HostKeyConfirmationStep, {
|
|
1730
1665
|
fingerprint: hostKeyFingerprint,
|
|
1731
1666
|
expectedFingerprint: session.hostKeyFingerprint,
|
|
@@ -1736,21 +1671,31 @@ function CommissioningContent({ model }) {
|
|
|
1736
1671
|
onChange: setIsolatedIdentity,
|
|
1737
1672
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox$3.Control, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox$3.Indicator, {}) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox$3.Content, { children: "Alternatively, I verified the physical 751-9301 label and connected this controller as the only device on an isolated service network. I accept first-key pinning on that connection, not independent cryptographic identity verification. Do not select this on a shared LAN." })]
|
|
1738
1673
|
}),
|
|
1739
|
-
session && activeStep ===
|
|
1674
|
+
session && activeStep === 4 && session.state !== "awaiting_identity_confirmation" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DeliveryStep, {
|
|
1740
1675
|
isDelivering: deliverSessionMutation.isPending,
|
|
1741
1676
|
session,
|
|
1742
1677
|
sshUsername,
|
|
1743
1678
|
sshPassword,
|
|
1744
1679
|
onSshUsernameChange: setSshUsername,
|
|
1745
1680
|
onSshPasswordChange: setSshPassword,
|
|
1681
|
+
customSsh,
|
|
1682
|
+
onCustomSshChange: (custom) => {
|
|
1683
|
+
setCustomSsh(custom);
|
|
1684
|
+
setSshUsername(custom ? "" : DEFAULT_SSH.username);
|
|
1685
|
+
setSshPassword(custom ? "" : DEFAULT_SSH.password);
|
|
1686
|
+
},
|
|
1746
1687
|
confirmInstall,
|
|
1747
1688
|
onConfirmInstallChange: setConfirmInstall
|
|
1748
1689
|
}),
|
|
1749
|
-
session && activeStep ===
|
|
1690
|
+
session && activeStep === 5 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProgressStep, {
|
|
1750
1691
|
name: title,
|
|
1751
1692
|
session
|
|
1752
1693
|
}),
|
|
1753
|
-
session &&
|
|
1694
|
+
session && canInstall(session) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("summary", {
|
|
1695
|
+
className: "wg:cursor-pointer",
|
|
1696
|
+
children: "Optional: inspect controller before installation"
|
|
1697
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommissioningPlatformPreflight, { session }, `preflight-${session.id}`)] }),
|
|
1698
|
+
session && !canInstall(session) && session.dockerProvisionState && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommissioningPlatformPreflight, { session }, `preflight-${session.id}`),
|
|
1754
1699
|
session && (["awaiting_verification", "completed"].includes(session.state) || session.managementControllerId) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VerificationStatus, {
|
|
1755
1700
|
session,
|
|
1756
1701
|
onConfigure: onConfigure ? configureController : void 0
|
|
@@ -1801,13 +1746,13 @@ function CommissioningActions({ model }) {
|
|
|
1801
1746
|
});
|
|
1802
1747
|
}
|
|
1803
1748
|
function RecoveryFields({ model }) {
|
|
1804
|
-
const { session, isLoading, recoveryUsername, recoveryPassword, setRecoveryUsername, setRecoveryPassword, confirmRecovery, setConfirmRecovery } = model;
|
|
1749
|
+
const { session, isLoading, recoveryUsername, recoveryPassword, setRecoveryUsername, setRecoveryPassword, customRecoverySsh, setCustomRecoverySsh, confirmRecovery, setConfirmRecovery } = model;
|
|
1805
1750
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: session && canRecover(session) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1806
1751
|
className: "wg:space-y-4",
|
|
1807
1752
|
children: [
|
|
1808
1753
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert$5, {
|
|
1809
1754
|
status: "warning",
|
|
1810
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$5.Indicator, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert$5.Content, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$5.Title, { children: "Clean up failed installation" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$5.Description, { children: "
|
|
1755
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$5.Indicator, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert$5.Content, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$5.Title, { children: "Clean up failed installation" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$5.Description, { children: "This stops and removes the failed Attraccess installation. It cannot restore previous applications, data, or CODESYS. You can retry after cleanup." })] })]
|
|
1811
1756
|
}),
|
|
1812
1757
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CredentialFields, {
|
|
1813
1758
|
intent: "recovery",
|
|
@@ -1815,7 +1760,13 @@ function RecoveryFields({ model }) {
|
|
|
1815
1760
|
username: recoveryUsername,
|
|
1816
1761
|
password: recoveryPassword,
|
|
1817
1762
|
onUsernameChange: setRecoveryUsername,
|
|
1818
|
-
onPasswordChange: setRecoveryPassword
|
|
1763
|
+
onPasswordChange: setRecoveryPassword,
|
|
1764
|
+
custom: customRecoverySsh,
|
|
1765
|
+
onCustomChange: (custom) => {
|
|
1766
|
+
setCustomRecoverySsh(custom);
|
|
1767
|
+
setRecoveryUsername(custom ? "" : DEFAULT_SSH.username);
|
|
1768
|
+
setRecoveryPassword(custom ? "" : DEFAULT_SSH.password);
|
|
1769
|
+
}
|
|
1819
1770
|
}),
|
|
1820
1771
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Checkbox$3, {
|
|
1821
1772
|
isRequired: true,
|
|
@@ -1824,10 +1775,6 @@ function RecoveryFields({ model }) {
|
|
|
1824
1775
|
onChange: setConfirmRecovery,
|
|
1825
1776
|
name: "confirm-recovery",
|
|
1826
1777
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox$3.Control, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox$3.Indicator, {}) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox$3.Content, { children: "I approve interrupting the Attraccess runtime and cleaning up this failed installation. This does not restore preexisting applications or data." })]
|
|
1827
|
-
}),
|
|
1828
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
1829
|
-
className: "wg:text-sm wg:text-muted",
|
|
1830
|
-
children: "Enter fresh SSH credentials and approve each recovery attempt separately. Credentials and recovery consent are cleared after submission or closing."
|
|
1831
1778
|
})
|
|
1832
1779
|
]
|
|
1833
1780
|
}) });
|
|
@@ -1846,7 +1793,7 @@ function ConnectionFields({ model }) {
|
|
|
1846
1793
|
onControllerIpChange: setControllerIp,
|
|
1847
1794
|
onMqttServerIdChange: setMqttServerId
|
|
1848
1795
|
}),
|
|
1849
|
-
!session && activeStep ===
|
|
1796
|
+
!session && activeStep === 2 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RuntimeArtifactImport, {
|
|
1850
1797
|
disabled: isLoading,
|
|
1851
1798
|
onBusyChange: setArtifactBusy,
|
|
1852
1799
|
onSelectionChange: setSelectedArtifact
|
|
@@ -1855,19 +1802,32 @@ function ConnectionFields({ model }) {
|
|
|
1855
1802
|
}
|
|
1856
1803
|
function CreateSessionActions({ model }) {
|
|
1857
1804
|
const { session, isLoading, activeStep, name, controllerIp, mqttServersQuery, setArtifactBusy, artifactBusy, artifactAvailable, selectedMqttServerId, setStep, createSession } = model;
|
|
1858
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1805
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1806
|
+
!session && activeStep === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$12, {
|
|
1807
|
+
isDisabled: !name.trim(),
|
|
1808
|
+
onPress: () => {
|
|
1809
|
+
setArtifactBusy(true);
|
|
1810
|
+
setStep(1);
|
|
1811
|
+
},
|
|
1812
|
+
children: "Continue"
|
|
1813
|
+
}),
|
|
1814
|
+
!session && activeStep === 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$12, {
|
|
1815
|
+
isDisabled: !controllerIp.trim() || selectedMqttServerId === null || mqttServersQuery.isPending || mqttServersQuery.isError,
|
|
1816
|
+
onPress: () => setStep(2),
|
|
1817
|
+
children: "Continue"
|
|
1818
|
+
}),
|
|
1819
|
+
!session && activeStep === 2 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$12, {
|
|
1820
|
+
variant: "secondary",
|
|
1821
|
+
onPress: () => setStep(1),
|
|
1822
|
+
children: "Back to connection"
|
|
1823
|
+
}),
|
|
1824
|
+
!session && activeStep === 2 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$12, {
|
|
1825
|
+
isPending: isLoading,
|
|
1826
|
+
isDisabled: artifactBusy || !artifactAvailable || !controllerIp.trim() || selectedMqttServerId === null || mqttServersQuery.isPending || mqttServersQuery.isError,
|
|
1827
|
+
onPress: createSession,
|
|
1828
|
+
children: isLoading ? "Preparing commissioning" : "Scan controller for review"
|
|
1829
|
+
})
|
|
1830
|
+
] });
|
|
1871
1831
|
}
|
|
1872
1832
|
function CancelSessionAction({ model }) {
|
|
1873
1833
|
const { session, isLoading, removeSessionMutation, isCancelConfirmationOpen, setCancelConfirmationOpen, close } = model;
|
|
@@ -1914,15 +1874,15 @@ function DevicePassport({ className, name, step }) {
|
|
|
1914
1874
|
children: [
|
|
1915
1875
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PassportRow, {
|
|
1916
1876
|
label: "Identity",
|
|
1917
|
-
value: step >=
|
|
1877
|
+
value: step >= 3 ? "See session status" : "Not scanned"
|
|
1918
1878
|
}),
|
|
1919
1879
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PassportRow, {
|
|
1920
1880
|
label: "Runtime",
|
|
1921
|
-
value: step >=
|
|
1881
|
+
value: step >= 4 ? "See session status" : "Not installed"
|
|
1922
1882
|
}),
|
|
1923
1883
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PassportRow, {
|
|
1924
1884
|
label: "Claim",
|
|
1925
|
-
value: step >=
|
|
1885
|
+
value: step >= 5 ? "See session status" : "Pending"
|
|
1926
1886
|
})
|
|
1927
1887
|
]
|
|
1928
1888
|
}),
|
|
@@ -1932,7 +1892,9 @@ function DevicePassport({ className, name, step }) {
|
|
|
1932
1892
|
0,
|
|
1933
1893
|
1,
|
|
1934
1894
|
2,
|
|
1935
|
-
3
|
|
1895
|
+
3,
|
|
1896
|
+
4,
|
|
1897
|
+
5
|
|
1936
1898
|
].map((index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `wg:h-1.5 wg:flex-1 wg:rounded-full ${index <= step ? "wg:bg-primary" : "wg:bg-default-300"}` }, index))
|
|
1937
1899
|
})
|
|
1938
1900
|
]
|
|
@@ -1949,10 +1911,12 @@ function PassportRow({ label, value }) {
|
|
|
1949
1911
|
}
|
|
1950
1912
|
function StepHeading({ step }) {
|
|
1951
1913
|
const content = [
|
|
1952
|
-
["
|
|
1953
|
-
["Connect the controller", "Enter its
|
|
1954
|
-
["
|
|
1955
|
-
["
|
|
1914
|
+
["Name the controller", "Choose a name you will recognize later."],
|
|
1915
|
+
["Connect the controller", "Enter its IP address and choose the MQTT server it will use."],
|
|
1916
|
+
["Choose a runtime release", "Select the release to install on this controller."],
|
|
1917
|
+
["Verify the controller", "Check the SSH fingerprint before installation."],
|
|
1918
|
+
["Install the runtime", "Review what installation changes, then approve it."],
|
|
1919
|
+
["Installation progress", "You can close this window and return to the saved session later."]
|
|
1956
1920
|
][step];
|
|
1957
1921
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
1958
1922
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
|
|
@@ -1960,7 +1924,7 @@ function StepHeading({ step }) {
|
|
|
1960
1924
|
children: [
|
|
1961
1925
|
"Step ",
|
|
1962
1926
|
step + 1,
|
|
1963
|
-
" of
|
|
1927
|
+
" of 6"
|
|
1964
1928
|
]
|
|
1965
1929
|
}),
|
|
1966
1930
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", {
|
|
@@ -2017,7 +1981,7 @@ function ConnectionStep({ controllerIp, mqttServerId, mqttServersQuery, onContro
|
|
|
2017
1981
|
children: [
|
|
2018
1982
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert$5, {
|
|
2019
1983
|
status: "accent",
|
|
2020
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$5.Indicator, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert$5.Content, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$5.Title, { children: "Prepare the CC100 (751-9301), firmware 31" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$5.Description, { children: "
|
|
1984
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$5.Indicator, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert$5.Content, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$5.Title, { children: "Prepare the CC100 (751-9301), firmware 31" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$5.Description, { children: "Power on the CC100 and connect it to the local network. Keep it connected during installation. Check the device label and firmware if you are unsure which controller this is." })] })]
|
|
2021
1985
|
}),
|
|
2022
1986
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TextField$3, {
|
|
2023
1987
|
isRequired: true,
|
|
@@ -2068,7 +2032,7 @@ function HostKeyConfirmationStep({ fingerprint, expectedFingerprint, onFingerpri
|
|
|
2068
2032
|
children: [
|
|
2069
2033
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert$5, {
|
|
2070
2034
|
status: "warning",
|
|
2071
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$5.Indicator, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert$5.Content, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$5.Title, { children: "Review the controller SSH key" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$5.Description, { children: "Compare
|
|
2035
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$5.Indicator, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert$5.Content, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$5.Title, { children: "Review the controller SSH key" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$5.Description, { children: "Compare this fingerprint with a trusted record. If you do not have one, connect only this controller to an isolated service network and confirm its physical label. Stop if you cannot identify the controller." })] })]
|
|
2072
2036
|
}),
|
|
2073
2037
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
|
|
2074
2038
|
className: "wg:break-all wg:text-sm",
|
|
@@ -2095,33 +2059,48 @@ function canInstall(session) {
|
|
|
2095
2059
|
function canRecover(session) {
|
|
2096
2060
|
return session.runtimeRecoveryAvailable === true;
|
|
2097
2061
|
}
|
|
2098
|
-
function CredentialFields({ intent = "installation", isDisabled, username, password, onUsernameChange, onPasswordChange }) {
|
|
2062
|
+
function CredentialFields({ intent = "installation", isDisabled, username, password, onUsernameChange, onPasswordChange, custom, onCustomChange }) {
|
|
2099
2063
|
const prefix = intent === "recovery" ? "Recovery SSH" : "Temporary SSH";
|
|
2100
2064
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
2101
|
-
className: "wg:
|
|
2102
|
-
children: [
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2065
|
+
className: "wg:space-y-3",
|
|
2066
|
+
children: [
|
|
2067
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
|
|
2068
|
+
className: "wg:text-sm",
|
|
2069
|
+
children: ["SSH login: ", custom ? "Custom credentials" : "Default root account"]
|
|
2070
|
+
}),
|
|
2071
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Checkbox$3, {
|
|
2072
|
+
isSelected: custom,
|
|
2073
|
+
isDisabled,
|
|
2074
|
+
onChange: onCustomChange,
|
|
2075
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox$3.Control, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox$3.Indicator, {}) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox$3.Content, { children: "Advanced: use different SSH credentials" })]
|
|
2076
|
+
}),
|
|
2077
|
+
custom && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
2078
|
+
className: "wg:grid wg:gap-4 wg:sm:grid-cols-2",
|
|
2079
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TextField$3, {
|
|
2080
|
+
isRequired: true,
|
|
2081
|
+
isDisabled,
|
|
2082
|
+
name: `${intent}-ssh-username`,
|
|
2083
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Label$6, { children: [prefix, " username"] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input$3, {
|
|
2084
|
+
autoComplete: "off",
|
|
2085
|
+
value: username,
|
|
2086
|
+
onChange: (event) => onUsernameChange(event.target.value)
|
|
2087
|
+
})]
|
|
2088
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TextField$3, {
|
|
2089
|
+
isRequired: true,
|
|
2090
|
+
isDisabled,
|
|
2091
|
+
name: `${intent}-ssh-password`,
|
|
2092
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Label$6, { children: [prefix, " password"] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input$3, {
|
|
2093
|
+
autoComplete: "off",
|
|
2094
|
+
type: "password",
|
|
2095
|
+
value: password,
|
|
2096
|
+
onChange: (event) => onPasswordChange(event.target.value)
|
|
2097
|
+
})]
|
|
2098
|
+
})]
|
|
2099
|
+
})
|
|
2100
|
+
]
|
|
2122
2101
|
});
|
|
2123
2102
|
}
|
|
2124
|
-
function DeliveryStep({ isDelivering, session, sshUsername, sshPassword, onSshUsernameChange, onSshPasswordChange, confirmInstall, onConfirmInstallChange }) {
|
|
2103
|
+
function DeliveryStep({ isDelivering, session, sshUsername, sshPassword, onSshUsernameChange, onSshPasswordChange, customSsh, onCustomSshChange, confirmInstall, onConfirmInstallChange }) {
|
|
2125
2104
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
2126
2105
|
className: "wg:space-y-4",
|
|
2127
2106
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommissioningStatusPanel, {
|
|
@@ -2131,9 +2110,10 @@ function DeliveryStep({ isDelivering, session, sshUsername, sshPassword, onSshUs
|
|
|
2131
2110
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert$5, {
|
|
2132
2111
|
status: "warning",
|
|
2133
2112
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$5.Indicator, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert$5.Content, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$5.Title, { children: "Destructive installation" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert$5.Description, { children: [
|
|
2134
|
-
"
|
|
2113
|
+
"Make connected equipment safe before continuing. Installation stops and disables CODESYS on",
|
|
2114
|
+
" ",
|
|
2135
2115
|
session.targetHost,
|
|
2136
|
-
"
|
|
2116
|
+
". Existing applications and data may be lost and cannot be restored by Attraccess."
|
|
2137
2117
|
] })] })]
|
|
2138
2118
|
}),
|
|
2139
2119
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CredentialFields, {
|
|
@@ -2141,7 +2121,9 @@ function DeliveryStep({ isDelivering, session, sshUsername, sshPassword, onSshUs
|
|
|
2141
2121
|
username: sshUsername,
|
|
2142
2122
|
password: sshPassword,
|
|
2143
2123
|
onUsernameChange: onSshUsernameChange,
|
|
2144
|
-
onPasswordChange: onSshPasswordChange
|
|
2124
|
+
onPasswordChange: onSshPasswordChange,
|
|
2125
|
+
custom: customSsh,
|
|
2126
|
+
onCustomChange: onCustomSshChange
|
|
2145
2127
|
}),
|
|
2146
2128
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Checkbox$3, {
|
|
2147
2129
|
isRequired: true,
|
|
@@ -2153,29 +2135,35 @@ function DeliveryStep({ isDelivering, session, sshUsername, sshPassword, onSshUs
|
|
|
2153
2135
|
}),
|
|
2154
2136
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
2155
2137
|
className: "wg:text-sm wg:text-muted",
|
|
2156
|
-
children: "
|
|
2138
|
+
children: "Each attempt needs your approval. Custom passwords are cleared after use."
|
|
2157
2139
|
})
|
|
2158
2140
|
] })]
|
|
2159
2141
|
});
|
|
2160
2142
|
}
|
|
2161
2143
|
function ProgressStep({ name, session }) {
|
|
2162
|
-
const
|
|
2144
|
+
const verification = useCommissioningVerification(session);
|
|
2145
|
+
const complete = verification.enrollmentComplete || [
|
|
2163
2146
|
"completed",
|
|
2164
2147
|
"revoked",
|
|
2165
2148
|
"claim_interrupted",
|
|
2166
2149
|
"recovery_revocation_pending"
|
|
2167
2150
|
].includes(session.state);
|
|
2151
|
+
const progress = verification.enrollmentComplete ? {
|
|
2152
|
+
...session,
|
|
2153
|
+
progressStep: "Runtime enrollment complete",
|
|
2154
|
+
progressDetail: verification.runtimeVerified ? "Permanent MQTT access, enrollment credential revocation, applied configuration and a fresh runtime probe are verified." : "Permanent MQTT access and enrollment credential revocation are verified. Configure inputs and outputs to finish runtime setup."
|
|
2155
|
+
} : session;
|
|
2168
2156
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
2169
2157
|
className: "wg:space-y-4",
|
|
2170
2158
|
children: [
|
|
2171
2159
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DevicePassport, {
|
|
2172
2160
|
className: "wg:md:hidden",
|
|
2173
2161
|
name,
|
|
2174
|
-
step:
|
|
2162
|
+
step: 5
|
|
2175
2163
|
}),
|
|
2176
2164
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommissioningStatusPanel, {
|
|
2177
2165
|
isActive: !complete,
|
|
2178
|
-
session
|
|
2166
|
+
session: progress
|
|
2179
2167
|
}),
|
|
2180
2168
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
2181
2169
|
className: "wg:rounded-large wg:border wg:border-default-200 wg:p-4 wg:text-sm",
|
|
@@ -2195,34 +2183,33 @@ function ProgressStep({ name, session }) {
|
|
|
2195
2183
|
});
|
|
2196
2184
|
}
|
|
2197
2185
|
function VerificationStatus({ session, onConfigure }) {
|
|
2198
|
-
const verification =
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
"commissioning-verification",
|
|
2202
|
-
session.id
|
|
2203
|
-
],
|
|
2204
|
-
queryFn: () => getCommissioningVerification(session.id),
|
|
2205
|
-
refetchInterval: 5e3
|
|
2206
|
-
});
|
|
2186
|
+
const verification = useCommissioningVerification(session);
|
|
2187
|
+
const controllerId = verification.data?.controllerId;
|
|
2188
|
+
const managementControllerId = controllerId ?? session.managementControllerId;
|
|
2207
2189
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
2208
2190
|
className: "wg:space-y-3",
|
|
2209
|
-
children: [
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: ["Permanent heartbeat: ", verification.data.permanentConnection ? "received" : "pending"] }),
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2191
|
+
children: [
|
|
2192
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert$5, {
|
|
2193
|
+
status: verification.enrollmentComplete ? "success" : "warning",
|
|
2194
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$5.Indicator, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert$5.Content, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$5.Title, { children: verification.enrollmentComplete ? "Enrollment complete" : "Verifying enrollment" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$5.Description, { children: verification.unavailable ? "Current verification is unavailable. Waiting for a fresh status check." : verification.data ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ul", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: ["Permanent heartbeat: ", verification.data.permanentConnection ? "received" : "pending"] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: ["Enrollment credential revoked: ", verification.data.enrollmentRevoked ? "verified" : "pending"] })] }) : "Checking commissioning evidence..." })] })]
|
|
2195
|
+
}),
|
|
2196
|
+
verification.data && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
|
|
2197
|
+
"aria-label": "Runtime setup and qualification",
|
|
2198
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: verification.runtimeVerified ? "Runtime configuration verified" : "Runtime setup checks" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ul", { children: [
|
|
2199
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: ["Desired/reported configuration: ", verification.data.configurationApplied ? "applied" : "pending"] }),
|
|
2200
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: ["Runtime hardware probe: ", verification.data.hardwareReadiness ?? "unverified"] }),
|
|
2201
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: ["Management hardening: ", verification.data.managementHardening] }),
|
|
2202
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: "Physical qualification: required before production use" })
|
|
2203
|
+
] })]
|
|
2204
|
+
}),
|
|
2205
|
+
managementControllerId && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [onConfigure && controllerId && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$12, {
|
|
2206
|
+
onPress: () => onConfigure(controllerId),
|
|
2207
|
+
children: "Configure inputs and outputs"
|
|
2208
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommissioningSecurityPanel, {
|
|
2209
|
+
sessionId: session.id,
|
|
2210
|
+
controllerId: managementControllerId
|
|
2211
|
+
}, session.id)] })
|
|
2212
|
+
]
|
|
2226
2213
|
});
|
|
2227
2214
|
}
|
|
2228
2215
|
function CommissioningStatusPanel({ isActive, session }) {
|
|
@@ -2230,7 +2217,7 @@ function CommissioningStatusPanel({ isActive, session }) {
|
|
|
2230
2217
|
const isQueued = session.state === "awaiting_delivery";
|
|
2231
2218
|
const hasFailure = !isActive && Boolean(session.failureReason);
|
|
2232
2219
|
const title = isQueued ? "Installation approval required" : session.progressStep ?? (isActive ? "Preparing commissioning" : commissioningLabel(session.state));
|
|
2233
|
-
const detail = isQueued ? "Review the installation
|
|
2220
|
+
const detail = isQueued ? "Review the installation and approve this attempt. Saved sessions do not start automatically." : session.progressDetail ?? "Waiting for the next commissioning operation.";
|
|
2234
2221
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
2235
2222
|
"aria-live": "polite",
|
|
2236
2223
|
className: "wg:rounded-large wg:border wg:border-primary/30 wg:bg-primary/5 wg:p-4",
|
|
@@ -2312,13 +2299,13 @@ function parseActivityLog(auditLog) {
|
|
|
2312
2299
|
}
|
|
2313
2300
|
function sessionStep(session) {
|
|
2314
2301
|
if (!session) return 0;
|
|
2302
|
+
if (session.state === "awaiting_identity_confirmation") return 3;
|
|
2315
2303
|
return [
|
|
2316
2304
|
"awaiting_delivery",
|
|
2317
2305
|
"delivering",
|
|
2318
|
-
"awaiting_identity_confirmation",
|
|
2319
2306
|
"awaiting_codesys_confirmation",
|
|
2320
2307
|
"delivery_failed"
|
|
2321
|
-
].includes(session.state) ?
|
|
2308
|
+
].includes(session.state) ? 4 : 5;
|
|
2322
2309
|
}
|
|
2323
2310
|
function ErrorAlert$1({ error }) {
|
|
2324
2311
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert$5, {
|
|
@@ -3568,7 +3555,7 @@ var base = {
|
|
|
3568
3555
|
pollIntervalMs: 5e3,
|
|
3569
3556
|
functionCode: 3
|
|
3570
3557
|
};
|
|
3571
|
-
var
|
|
3558
|
+
var legacyProfiles = ["879-3000", "879-1300"].map((model) => ({
|
|
3572
3559
|
id: `wago-${model}-unverified`,
|
|
3573
3560
|
name: `WAGO ${model} — UNQUALIFIED / map unverified`,
|
|
3574
3561
|
version: 1,
|
|
@@ -3599,6 +3586,40 @@ var BUILTIN_MODBUS_PROFILES = ["879-3000", "879-1300"].map((model) => ({
|
|
|
3599
3586
|
kind: "cumulative"
|
|
3600
3587
|
}))]
|
|
3601
3588
|
}));
|
|
3589
|
+
var BUILTIN_MODBUS_PROFILES = [{
|
|
3590
|
+
id: "wago-879-3000",
|
|
3591
|
+
name: "WAGO 879-3000 — Modbus RTU",
|
|
3592
|
+
version: 1,
|
|
3593
|
+
actions: [],
|
|
3594
|
+
measurements: [
|
|
3595
|
+
...legacyProfiles[0].measurements.map((measurement) => ({
|
|
3596
|
+
...measurement,
|
|
3597
|
+
decimalPlaces: 3
|
|
3598
|
+
})),
|
|
3599
|
+
{
|
|
3600
|
+
...base,
|
|
3601
|
+
id: "voltage-l1",
|
|
3602
|
+
name: "L1 voltage",
|
|
3603
|
+
address: 20482,
|
|
3604
|
+
dataType: "float32",
|
|
3605
|
+
scale: 1,
|
|
3606
|
+
unit: "volt",
|
|
3607
|
+
kind: "live",
|
|
3608
|
+
decimalPlaces: 3
|
|
3609
|
+
},
|
|
3610
|
+
{
|
|
3611
|
+
...base,
|
|
3612
|
+
id: "current-l1",
|
|
3613
|
+
name: "L1 current",
|
|
3614
|
+
address: 20492,
|
|
3615
|
+
dataType: "float32",
|
|
3616
|
+
scale: 1,
|
|
3617
|
+
unit: "ampere",
|
|
3618
|
+
kind: "live",
|
|
3619
|
+
decimalPlaces: 3
|
|
3620
|
+
}
|
|
3621
|
+
]
|
|
3622
|
+
}, ...legacyProfiles];
|
|
3602
3623
|
function freeze(value) {
|
|
3603
3624
|
Object.values(value).forEach((child) => {
|
|
3604
3625
|
if (child && typeof child === "object") freeze(child);
|
|
@@ -3746,7 +3767,8 @@ function validateModbus(value) {
|
|
|
3746
3767
|
"unit",
|
|
3747
3768
|
"kind",
|
|
3748
3769
|
"pollIntervalMs",
|
|
3749
|
-
"rollover"
|
|
3770
|
+
"rollover",
|
|
3771
|
+
"decimalPlaces"
|
|
3750
3772
|
] : ["onValue", "offValue"]
|
|
3751
3773
|
], `${path}.${f.id}`);
|
|
3752
3774
|
});
|
|
@@ -3757,7 +3779,7 @@ function validateModbus(value) {
|
|
|
3757
3779
|
"watt",
|
|
3758
3780
|
"watt-hour",
|
|
3759
3781
|
"percent"
|
|
3760
|
-
].includes(m.unit) || !["live", "cumulative"].includes(m.kind) || !integer(m.pollIntervalMs, 100, 36e5)) fail(path, "measurement requires FC03/04, physical unit, kind, poll interval 100..3600000ms");
|
|
3782
|
+
].includes(m.unit) || !["live", "cumulative"].includes(m.kind) || m.decimalPlaces !== void 0 && !integer(m.decimalPlaces, 0, 3) || !integer(m.pollIntervalMs, 100, 36e5)) fail(path, "measurement requires FC03/04, physical unit, kind, poll interval 100..3600000ms");
|
|
3761
3783
|
if (m?.rollover !== void 0 && (m.kind !== "cumulative" || !Number.isFinite(m.rollover) || m.rollover <= 0)) fail(path, "rollover must be an explicit positive raw modulus on cumulative measurements");
|
|
3762
3784
|
if (m?.kind === "cumulative" && m.scale <= 0) fail(path, "cumulative measurements require a positive scale");
|
|
3763
3785
|
});
|
|
@@ -4340,6 +4362,26 @@ function ModbusProfileForm({ value, onChange, isDisabled = false, showIdentifier
|
|
|
4340
4362
|
});
|
|
4341
4363
|
}
|
|
4342
4364
|
}),
|
|
4365
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Choice$1, {
|
|
4366
|
+
label: "Reading precision (physical units)",
|
|
4367
|
+
value: m.decimalPlaces ?? "exact",
|
|
4368
|
+
options: [
|
|
4369
|
+
"exact",
|
|
4370
|
+
0,
|
|
4371
|
+
1,
|
|
4372
|
+
2,
|
|
4373
|
+
3
|
|
4374
|
+
],
|
|
4375
|
+
labels: {
|
|
4376
|
+
exact: "Exact (no rounding)",
|
|
4377
|
+
0: "Whole units",
|
|
4378
|
+
1: "0.1 units",
|
|
4379
|
+
2: "0.01 units",
|
|
4380
|
+
3: "0.001 units"
|
|
4381
|
+
},
|
|
4382
|
+
disabled: readonly,
|
|
4383
|
+
onChange: (v) => update({ decimalPlaces: v === "exact" ? void 0 : Number(v) })
|
|
4384
|
+
}),
|
|
4343
4385
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, {
|
|
4344
4386
|
label: "Polling interval (ms)",
|
|
4345
4387
|
value: m.pollIntervalMs,
|
|
@@ -4563,7 +4605,7 @@ function ModbusConfigurationForm({ value, onChange, isDisabled = false, showIden
|
|
|
4563
4605
|
] }),
|
|
4564
4606
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
4565
4607
|
className: "wg:text-sm wg:text-muted",
|
|
4566
|
-
children: "
|
|
4608
|
+
children: "WAGO 879-3000 uses its documented read-only register map. Profiles marked unverified are retained for existing configurations."
|
|
4567
4609
|
}),
|
|
4568
4610
|
value.connections.map((c, index) => {
|
|
4569
4611
|
if (focused && (section !== "connections" || c.id !== selectedId)) return null;
|
|
@@ -4609,8 +4651,8 @@ function ModbusConfigurationForm({ value, onChange, isDisabled = false, showIden
|
|
|
4609
4651
|
} : {
|
|
4610
4652
|
transport: "rtu",
|
|
4611
4653
|
path: "/dev/serial",
|
|
4612
|
-
baudRate:
|
|
4613
|
-
parity: "
|
|
4654
|
+
baudRate: 9600,
|
|
4655
|
+
parity: "none",
|
|
4614
4656
|
stopBits: 1
|
|
4615
4657
|
}
|
|
4616
4658
|
} : item)
|
|
@@ -4628,6 +4670,10 @@ function ModbusConfigurationForm({ value, onChange, isDisabled = false, showIden
|
|
|
4628
4670
|
disabled: isDisabled,
|
|
4629
4671
|
onChange: (v) => update({ port: Number(v) })
|
|
4630
4672
|
})] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
4673
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
4674
|
+
className: "wg:text-sm wg:text-muted",
|
|
4675
|
+
children: "CC100 two-wire RS-485: /dev/serial. WAGO 879-3000 factory settings: unit 1, 9600 baud, 8N1."
|
|
4676
|
+
}),
|
|
4631
4677
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, {
|
|
4632
4678
|
label: "Serial device path",
|
|
4633
4679
|
value: c.path,
|