@adcops/autocore-react 3.3.75 → 3.3.79

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.
Files changed (56) hide show
  1. package/dist/components/Indicator.d.ts +29 -52
  2. package/dist/components/Indicator.d.ts.map +1 -1
  3. package/dist/components/Indicator.js +1 -1
  4. package/dist/components/ams/AmsProvider.d.ts +7 -0
  5. package/dist/components/ams/AmsProvider.d.ts.map +1 -1
  6. package/dist/components/ams/AssetDetailView.d.ts.map +1 -1
  7. package/dist/components/ams/AssetDetailView.js +1 -1
  8. package/dist/components/ams/AssetRegistryTable.d.ts.map +1 -1
  9. package/dist/components/ams/AssetRegistryTable.js +1 -1
  10. package/dist/components/ams/CalibrationEntryDialog.d.ts.map +1 -1
  11. package/dist/components/ams/CalibrationEntryDialog.js +1 -1
  12. package/dist/components/ams/MissingAssetsBanner.d.ts +11 -0
  13. package/dist/components/ams/MissingAssetsBanner.d.ts.map +1 -0
  14. package/dist/components/ams/MissingAssetsBanner.js +1 -0
  15. package/dist/components/ams/PlaceholderHealthPanel.d.ts +3 -0
  16. package/dist/components/ams/PlaceholderHealthPanel.d.ts.map +1 -0
  17. package/dist/components/ams/PlaceholderHealthPanel.js +1 -0
  18. package/dist/components/ams/index.d.ts +2 -0
  19. package/dist/components/ams/index.d.ts.map +1 -1
  20. package/dist/components/ams/index.js +1 -1
  21. package/dist/components/index.d.ts +8 -0
  22. package/dist/components/index.d.ts.map +1 -1
  23. package/dist/components/index.js +1 -1
  24. package/dist/components/network/NetworkPanel.d.ts +8 -0
  25. package/dist/components/network/NetworkPanel.d.ts.map +1 -0
  26. package/dist/components/network/NetworkPanel.js +1 -0
  27. package/dist/components/network/NetworkProvider.d.ts +72 -0
  28. package/dist/components/network/NetworkProvider.d.ts.map +1 -0
  29. package/dist/components/network/NetworkProvider.js +1 -0
  30. package/dist/components/network/StagedChangeBanner.d.ts +8 -0
  31. package/dist/components/network/StagedChangeBanner.d.ts.map +1 -0
  32. package/dist/components/network/StagedChangeBanner.js +1 -0
  33. package/dist/components/network/index.d.ts +7 -0
  34. package/dist/components/network/index.d.ts.map +1 -0
  35. package/dist/components/network/index.js +1 -0
  36. package/dist/components/tis/ProjectManager.d.ts +7 -0
  37. package/dist/components/tis/ProjectManager.d.ts.map +1 -0
  38. package/dist/components/tis/ProjectManager.js +1 -0
  39. package/dist/components/tis/ResultHistoryTable.d.ts.map +1 -1
  40. package/dist/components/tis/ResultHistoryTable.js +1 -1
  41. package/package.json +1 -1
  42. package/src/components/Indicator.tsx +177 -162
  43. package/src/components/ams/AmsProvider.tsx +7 -0
  44. package/src/components/ams/AssetDetailView.tsx +287 -4
  45. package/src/components/ams/AssetRegistryTable.tsx +325 -21
  46. package/src/components/ams/CalibrationEntryDialog.tsx +163 -30
  47. package/src/components/ams/MissingAssetsBanner.tsx +124 -0
  48. package/src/components/ams/PlaceholderHealthPanel.tsx +188 -0
  49. package/src/components/ams/index.ts +2 -0
  50. package/src/components/index.ts +26 -0
  51. package/src/components/network/NetworkPanel.tsx +363 -0
  52. package/src/components/network/NetworkProvider.tsx +349 -0
  53. package/src/components/network/StagedChangeBanner.tsx +101 -0
  54. package/src/components/network/index.ts +17 -0
  55. package/src/components/tis/ProjectManager.tsx +393 -0
  56. package/src/components/tis/ResultHistoryTable.tsx +126 -188
@@ -0,0 +1,349 @@
1
+ /*
2
+ * Copyright (C) 2026 Automated Design Corp. All Rights Reserved.
3
+ *
4
+ * <NetworkProvider> — context for the network management panel.
5
+ *
6
+ * Owns:
7
+ * 1. A copy of the server-side `nw.status` snapshot (devices, saved
8
+ * connections, radio state) — refreshed on demand and on any
9
+ * `nw.*` mutation broadcast.
10
+ * 2. The latest pending stage (if any), so <StagedChangeBanner> can
11
+ * render its countdown without re-polling.
12
+ * 3. Action methods (scan / connect / confirm / cancel / forget /
13
+ * set_radio) that wrap `invoke` and refresh status afterwards.
14
+ *
15
+ * Drop once at the top of the HMI (or scope it to the network tab —
16
+ * the provider's WS subscriptions are cheap, so either works).
17
+ */
18
+
19
+ import React, {
20
+ createContext,
21
+ useCallback,
22
+ useContext,
23
+ useEffect,
24
+ useMemo,
25
+ useState,
26
+ type ReactNode,
27
+ } from 'react';
28
+ import { EventEmitterContext } from '../../core/EventEmitterContext';
29
+ import { MessageType } from '../../hub/CommandMessage';
30
+
31
+ // -------------------------------------------------------------------------
32
+ // Types
33
+ // -------------------------------------------------------------------------
34
+
35
+ export interface NetworkDeviceIp4 {
36
+ addresses: string[];
37
+ gateway: string;
38
+ dns: string[];
39
+ }
40
+
41
+ export interface NetworkInterface {
42
+ device: string;
43
+ type: string;
44
+ state: string;
45
+ connection: string;
46
+ ip4: NetworkDeviceIp4;
47
+ }
48
+
49
+ export interface NetworkConnection {
50
+ name: string;
51
+ uuid: string;
52
+ type: string;
53
+ device: string;
54
+ active: boolean;
55
+ }
56
+
57
+ export interface WifiAp {
58
+ ssid: string;
59
+ bssid: string;
60
+ signal: number;
61
+ security: string;
62
+ in_use: boolean;
63
+ }
64
+
65
+ export interface StagedChange {
66
+ staged_id: string;
67
+ device: string;
68
+ ssid: string;
69
+ prev_connection_uuid: string;
70
+ new_connection_uuid: string;
71
+ new_profile_created: boolean;
72
+ revert_in_seconds: number;
73
+ /**
74
+ * Absolute deadline (ms since epoch) computed when the staged
75
+ * change was received. The banner uses this to render a
76
+ * monotonically-decreasing countdown that doesn't depend on
77
+ * `revert_in_seconds` staying fresh in React state.
78
+ */
79
+ deadline_ms: number;
80
+ }
81
+
82
+ export interface NetworkStatus {
83
+ interfaces: NetworkInterface[];
84
+ connections: NetworkConnection[];
85
+ wifi_radio: boolean | null;
86
+ }
87
+
88
+ export interface NetworkContextValue {
89
+ status: NetworkStatus;
90
+ statusLoaded: boolean;
91
+ refreshStatus: () => Promise<void>;
92
+
93
+ aps: WifiAp[];
94
+ scanning: boolean;
95
+ scanWifi: (device?: string) => Promise<void>;
96
+
97
+ /** Latest pending stage, or null when none in flight. */
98
+ staged: StagedChange | null;
99
+
100
+ /** Connect to an SSID; returns the staged change record on success. */
101
+ wifiConnect: (ssid: string, password?: string, device?: string) => Promise<StagedChange | null>;
102
+ confirmConnection: (stagedId: string) => Promise<boolean>;
103
+ cancelStaged: (stagedId: string) => Promise<boolean>;
104
+ forgetConnection: (uuid: string) => Promise<boolean>;
105
+ setRadio: (enabled: boolean) => Promise<boolean>;
106
+
107
+ /** Most recent error from any network action, or empty string. */
108
+ lastError: string;
109
+ }
110
+
111
+ const EMPTY_STATUS: NetworkStatus = {
112
+ interfaces: [],
113
+ connections: [],
114
+ wifi_radio: null,
115
+ };
116
+
117
+ const NetworkContext = createContext<NetworkContextValue>({
118
+ status: EMPTY_STATUS,
119
+ statusLoaded: false,
120
+ refreshStatus: async () => {},
121
+ aps: [],
122
+ scanning: false,
123
+ scanWifi: async () => {},
124
+ staged: null,
125
+ wifiConnect: async () => null,
126
+ confirmConnection: async () => false,
127
+ cancelStaged: async () => false,
128
+ forgetConnection: async () => false,
129
+ setRadio: async () => false,
130
+ lastError: '',
131
+ });
132
+
133
+ // -------------------------------------------------------------------------
134
+ // Provider
135
+ // -------------------------------------------------------------------------
136
+
137
+ export interface NetworkProviderProps {
138
+ children: ReactNode;
139
+ }
140
+
141
+ export const NetworkProvider: React.FC<NetworkProviderProps> = ({ children }) => {
142
+ const { invoke, subscribe, unsubscribe } = useContext(EventEmitterContext);
143
+
144
+ const [status, setStatus] = useState<NetworkStatus>(EMPTY_STATUS);
145
+ const [statusLoaded, setStatusLoaded] = useState(false);
146
+ const [aps, setAps] = useState<WifiAp[]>([]);
147
+ const [scanning, setScanning] = useState(false);
148
+ const [staged, setStaged] = useState<StagedChange | null>(null);
149
+ const [lastError, setLastError] = useState('');
150
+
151
+ // -----------------------------------------------------------------
152
+ // Status fetch — merges nw.status + nw.list_interfaces so consumers
153
+ // get IP4 info alongside the connection list.
154
+ // -----------------------------------------------------------------
155
+ const refreshStatus = useCallback(async () => {
156
+ try {
157
+ const [ifResp, statResp] = await Promise.all([
158
+ invoke('nw.list_interfaces' as any, MessageType.Request, {} as any),
159
+ invoke('nw.status' as any, MessageType.Request, {} as any),
160
+ ]) as any[];
161
+ const interfaces = (ifResp?.success ? (ifResp.data?.interfaces ?? []) : []) as NetworkInterface[];
162
+ const connections = (statResp?.success ? (statResp.data?.connections ?? []) : []) as NetworkConnection[];
163
+ const wifi_radio = statResp?.success
164
+ ? (typeof statResp.data?.wifi_radio === 'boolean' ? statResp.data.wifi_radio : null)
165
+ : null;
166
+ setStatus({ interfaces, connections, wifi_radio });
167
+ setStatusLoaded(true);
168
+ if (!ifResp?.success) setLastError(ifResp?.error_message ?? '');
169
+ else if (!statResp?.success) setLastError(statResp?.error_message ?? '');
170
+ else setLastError('');
171
+ } catch (e) {
172
+ setLastError(e instanceof Error ? e.message : String(e));
173
+ }
174
+ }, [invoke]);
175
+
176
+ useEffect(() => { void refreshStatus(); }, [refreshStatus]);
177
+
178
+ // -----------------------------------------------------------------
179
+ // Subscriptions: refresh status on any nw.* mutation. Stage and
180
+ // revert broadcasts also drive the in-memory `staged` record so
181
+ // the banner survives across refresh-induced remounts.
182
+ // -----------------------------------------------------------------
183
+ useEffect(() => {
184
+ const onStaged = (payload: any) => {
185
+ if (!payload || typeof payload !== 'object') return;
186
+ const secs = Number(payload.revert_in_seconds ?? 0);
187
+ setStaged({
188
+ staged_id: String(payload.staged_id ?? ''),
189
+ device: String(payload.device ?? ''),
190
+ ssid: String(payload.ssid ?? ''),
191
+ prev_connection_uuid: String(payload.prev_connection_uuid ?? ''),
192
+ new_connection_uuid: String(payload.new_connection_uuid ?? ''),
193
+ new_profile_created: !!payload.new_profile_created,
194
+ revert_in_seconds: secs,
195
+ deadline_ms: Date.now() + secs * 1000,
196
+ });
197
+ };
198
+ const onConfirmed = (payload: any) => {
199
+ const id = String(payload?.staged_id ?? '');
200
+ setStaged(prev => prev && prev.staged_id === id ? null : prev);
201
+ void refreshStatus();
202
+ };
203
+ const onReverted = (payload: any) => {
204
+ const id = String(payload?.staged_id ?? '');
205
+ setStaged(prev => prev && prev.staged_id === id ? null : prev);
206
+ void refreshStatus();
207
+ };
208
+ const onDeleted = () => { void refreshStatus(); };
209
+
210
+ const subs = [
211
+ subscribe('nw.staged_change', onStaged),
212
+ subscribe('nw.confirmed', onConfirmed),
213
+ subscribe('nw.revert_fired', onReverted),
214
+ subscribe('nw.connection_deleted', onDeleted),
215
+ ];
216
+ return () => { subs.forEach(unsubscribe); };
217
+ }, [subscribe, unsubscribe, refreshStatus]);
218
+
219
+ // -----------------------------------------------------------------
220
+ // Actions
221
+ // -----------------------------------------------------------------
222
+ const scanWifi = useCallback(async (device?: string) => {
223
+ setScanning(true);
224
+ setLastError('');
225
+ try {
226
+ const payload: any = {};
227
+ if (device) payload.device = device;
228
+ const resp: any = await invoke('nw.scan_wifi' as any, MessageType.Request, payload);
229
+ if (resp?.success) {
230
+ setAps((resp.data?.access_points ?? []) as WifiAp[]);
231
+ } else {
232
+ setLastError(resp?.error_message ?? 'scan_wifi failed');
233
+ }
234
+ } catch (e) {
235
+ setLastError(e instanceof Error ? e.message : String(e));
236
+ } finally {
237
+ setScanning(false);
238
+ }
239
+ }, [invoke]);
240
+
241
+ const wifiConnect = useCallback(async (
242
+ ssid: string, password?: string, device?: string,
243
+ ): Promise<StagedChange | null> => {
244
+ setLastError('');
245
+ try {
246
+ const payload: any = { ssid };
247
+ if (password) payload.password = password;
248
+ if (device) payload.device = device;
249
+ const resp: any = await invoke('nw.wifi_connect' as any, MessageType.Request, payload);
250
+ if (!resp?.success) {
251
+ setLastError(resp?.error_message ?? 'wifi_connect failed');
252
+ return null;
253
+ }
254
+ // The broadcast handler above will also set `staged`, but we
255
+ // build a record here too in case the broadcast arrives after
256
+ // the resolver wakes (we'd otherwise return before the banner
257
+ // had a chance to render).
258
+ const secs = Number(resp.data?.revert_in_seconds ?? 0);
259
+ const change: StagedChange = {
260
+ staged_id: String(resp.data?.staged_id ?? ''),
261
+ device: String(resp.data?.device ?? ''),
262
+ ssid: String(resp.data?.ssid ?? ssid),
263
+ prev_connection_uuid: String(resp.data?.prev_connection_uuid ?? ''),
264
+ new_connection_uuid: String(resp.data?.new_connection_uuid ?? ''),
265
+ new_profile_created: !!resp.data?.new_profile_created,
266
+ revert_in_seconds: secs,
267
+ deadline_ms: Date.now() + secs * 1000,
268
+ };
269
+ setStaged(change);
270
+ return change;
271
+ } catch (e) {
272
+ setLastError(e instanceof Error ? e.message : String(e));
273
+ return null;
274
+ }
275
+ }, [invoke]);
276
+
277
+ const confirmConnection = useCallback(async (stagedId: string): Promise<boolean> => {
278
+ try {
279
+ const resp: any = await invoke('nw.confirm_connection' as any, MessageType.Request, { staged_id: stagedId } as any);
280
+ if (resp?.success) {
281
+ setStaged(prev => prev && prev.staged_id === stagedId ? null : prev);
282
+ return true;
283
+ }
284
+ setLastError(resp?.error_message ?? 'confirm_connection failed');
285
+ return false;
286
+ } catch (e) {
287
+ setLastError(e instanceof Error ? e.message : String(e));
288
+ return false;
289
+ }
290
+ }, [invoke]);
291
+
292
+ const cancelStaged = useCallback(async (stagedId: string): Promise<boolean> => {
293
+ try {
294
+ const resp: any = await invoke('nw.cancel_staged' as any, MessageType.Request, { staged_id: stagedId } as any);
295
+ return !!resp?.success;
296
+ } catch (e) {
297
+ setLastError(e instanceof Error ? e.message : String(e));
298
+ return false;
299
+ }
300
+ }, [invoke]);
301
+
302
+ const forgetConnection = useCallback(async (uuid: string): Promise<boolean> => {
303
+ try {
304
+ const resp: any = await invoke('nw.forget_connection' as any, MessageType.Request, { uuid } as any);
305
+ if (resp?.success) {
306
+ void refreshStatus();
307
+ return true;
308
+ }
309
+ setLastError(resp?.error_message ?? 'forget_connection failed');
310
+ return false;
311
+ } catch (e) {
312
+ setLastError(e instanceof Error ? e.message : String(e));
313
+ return false;
314
+ }
315
+ }, [invoke, refreshStatus]);
316
+
317
+ const setRadio = useCallback(async (enabled: boolean): Promise<boolean> => {
318
+ try {
319
+ const resp: any = await invoke('nw.set_radio' as any, MessageType.Request, { enabled } as any);
320
+ if (resp?.success) {
321
+ setStatus(s => ({ ...s, wifi_radio: enabled }));
322
+ return true;
323
+ }
324
+ setLastError(resp?.error_message ?? 'set_radio failed');
325
+ return false;
326
+ } catch (e) {
327
+ setLastError(e instanceof Error ? e.message : String(e));
328
+ return false;
329
+ }
330
+ }, [invoke]);
331
+
332
+ const value = useMemo<NetworkContextValue>(() => ({
333
+ status, statusLoaded, refreshStatus,
334
+ aps, scanning, scanWifi,
335
+ staged,
336
+ wifiConnect, confirmConnection, cancelStaged, forgetConnection, setRadio,
337
+ lastError,
338
+ }), [status, statusLoaded, refreshStatus, aps, scanning, scanWifi,
339
+ staged, wifiConnect, confirmConnection, cancelStaged, forgetConnection, setRadio,
340
+ lastError]);
341
+
342
+ return <NetworkContext.Provider value={value}>{children}</NetworkContext.Provider>;
343
+ };
344
+
345
+ // -------------------------------------------------------------------------
346
+ // Hook
347
+ // -------------------------------------------------------------------------
348
+
349
+ export const useNetwork = () => useContext(NetworkContext);
@@ -0,0 +1,101 @@
1
+ /*
2
+ * Copyright (C) 2026 Automated Design Corp. All Rights Reserved.
3
+ *
4
+ * <StagedChangeBanner> — sticky banner that appears whenever a WiFi
5
+ * connection has been staged via `nw.wifi_connect` and is awaiting
6
+ * confirmation. Shows a live countdown to the auto-revert deadline
7
+ * and offers Confirm / Cancel buttons.
8
+ *
9
+ * Drives itself off `useNetwork().staged`; renders nothing when no
10
+ * stage is in flight.
11
+ */
12
+
13
+ import React, { useEffect, useState } from 'react';
14
+ import { Button } from 'primereact/button';
15
+ import { useNetwork } from './NetworkProvider';
16
+
17
+ export interface StagedChangeBannerProps {
18
+ /** Override the banner placement. Defaults to a fixed top bar. */
19
+ style?: React.CSSProperties;
20
+ }
21
+
22
+ export const StagedChangeBanner: React.FC<StagedChangeBannerProps> = ({ style }) => {
23
+ const { staged, confirmConnection, cancelStaged } = useNetwork();
24
+ const [now, setNow] = useState<number>(() => Date.now());
25
+
26
+ // Tick once per second while a stage is active. The countdown reads
27
+ // from `staged.deadline_ms - now` so the display stays monotonic
28
+ // even if the broadcast arrives a beat late.
29
+ useEffect(() => {
30
+ if (!staged) return;
31
+ const id = window.setInterval(() => setNow(Date.now()), 250);
32
+ return () => window.clearInterval(id);
33
+ }, [staged]);
34
+
35
+ if (!staged) return null;
36
+
37
+ const remainingSec = Math.max(0, Math.ceil((staged.deadline_ms - now) / 1000));
38
+ const total = Math.max(1, staged.revert_in_seconds);
39
+ const pctRemaining = Math.max(0, Math.min(100, (remainingSec / total) * 100));
40
+
41
+ const containerStyle: React.CSSProperties = {
42
+ position: 'sticky',
43
+ top: 0,
44
+ zIndex: 1000,
45
+ padding: '0.75rem 1rem',
46
+ background: remainingSec <= 10 ? '#7f1d1d' : '#78350f',
47
+ color: 'white',
48
+ borderBottom: '1px solid #1f2937',
49
+ boxShadow: '0 2px 4px rgba(0, 0, 0, 0.3)',
50
+ ...style,
51
+ };
52
+
53
+ return (
54
+ <div style={containerStyle} role="alertdialog" aria-live="polite">
55
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '1rem', flexWrap: 'wrap' }}>
56
+ <div style={{ minWidth: 0 }}>
57
+ <strong>WiFi change pending:</strong>{' '}
58
+ connected to <code>{staged.ssid}</code> on <code>{staged.device}</code>
59
+ </div>
60
+ <div style={{ fontVariantNumeric: 'tabular-nums', fontSize: '1.1rem' }}>
61
+ Reverting in <strong>{remainingSec}s</strong>
62
+ </div>
63
+ <div style={{ display: 'flex', gap: '0.4rem' }}>
64
+ <Button
65
+ label="Confirm"
66
+ icon="pi pi-check"
67
+ size="small"
68
+ severity="success"
69
+ onClick={() => { void confirmConnection(staged.staged_id); }}
70
+ />
71
+ <Button
72
+ label="Cancel & Revert"
73
+ icon="pi pi-undo"
74
+ size="small"
75
+ severity="danger"
76
+ outlined
77
+ onClick={() => { void cancelStaged(staged.staged_id); }}
78
+ />
79
+ </div>
80
+ </div>
81
+ {/* Slim progress strip beneath the row — visual reinforcement
82
+ of the countdown. Width shrinks toward zero as time runs out. */}
83
+ <div style={{
84
+ marginTop: '0.5rem',
85
+ height: '0.25rem',
86
+ background: 'rgba(255, 255, 255, 0.2)',
87
+ borderRadius: 0,
88
+ overflow: 'hidden',
89
+ }}>
90
+ <div style={{
91
+ width: `${pctRemaining}%`,
92
+ height: '100%',
93
+ background: 'white',
94
+ transition: 'width 0.25s linear',
95
+ }} />
96
+ </div>
97
+ </div>
98
+ );
99
+ };
100
+
101
+ export default StagedChangeBanner;
@@ -0,0 +1,17 @@
1
+ export { NetworkProvider, useNetwork } from './NetworkProvider';
2
+ export type {
3
+ NetworkProviderProps,
4
+ NetworkContextValue,
5
+ NetworkStatus,
6
+ NetworkInterface,
7
+ NetworkConnection,
8
+ NetworkDeviceIp4,
9
+ WifiAp,
10
+ StagedChange,
11
+ } from './NetworkProvider';
12
+
13
+ export { NetworkPanel } from './NetworkPanel';
14
+ export type { NetworkPanelProps } from './NetworkPanel';
15
+
16
+ export { StagedChangeBanner } from './StagedChangeBanner';
17
+ export type { StagedChangeBannerProps } from './StagedChangeBanner';