@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.
- package/dist/components/Indicator.d.ts +29 -52
- package/dist/components/Indicator.d.ts.map +1 -1
- package/dist/components/Indicator.js +1 -1
- package/dist/components/ams/AmsProvider.d.ts +7 -0
- package/dist/components/ams/AmsProvider.d.ts.map +1 -1
- package/dist/components/ams/AssetDetailView.d.ts.map +1 -1
- package/dist/components/ams/AssetDetailView.js +1 -1
- package/dist/components/ams/AssetRegistryTable.d.ts.map +1 -1
- package/dist/components/ams/AssetRegistryTable.js +1 -1
- package/dist/components/ams/CalibrationEntryDialog.d.ts.map +1 -1
- package/dist/components/ams/CalibrationEntryDialog.js +1 -1
- package/dist/components/ams/MissingAssetsBanner.d.ts +11 -0
- package/dist/components/ams/MissingAssetsBanner.d.ts.map +1 -0
- package/dist/components/ams/MissingAssetsBanner.js +1 -0
- package/dist/components/ams/PlaceholderHealthPanel.d.ts +3 -0
- package/dist/components/ams/PlaceholderHealthPanel.d.ts.map +1 -0
- package/dist/components/ams/PlaceholderHealthPanel.js +1 -0
- package/dist/components/ams/index.d.ts +2 -0
- package/dist/components/ams/index.d.ts.map +1 -1
- package/dist/components/ams/index.js +1 -1
- package/dist/components/index.d.ts +8 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -1
- package/dist/components/network/NetworkPanel.d.ts +8 -0
- package/dist/components/network/NetworkPanel.d.ts.map +1 -0
- package/dist/components/network/NetworkPanel.js +1 -0
- package/dist/components/network/NetworkProvider.d.ts +72 -0
- package/dist/components/network/NetworkProvider.d.ts.map +1 -0
- package/dist/components/network/NetworkProvider.js +1 -0
- package/dist/components/network/StagedChangeBanner.d.ts +8 -0
- package/dist/components/network/StagedChangeBanner.d.ts.map +1 -0
- package/dist/components/network/StagedChangeBanner.js +1 -0
- package/dist/components/network/index.d.ts +7 -0
- package/dist/components/network/index.d.ts.map +1 -0
- package/dist/components/network/index.js +1 -0
- package/dist/components/tis/ProjectManager.d.ts +7 -0
- package/dist/components/tis/ProjectManager.d.ts.map +1 -0
- package/dist/components/tis/ProjectManager.js +1 -0
- package/dist/components/tis/ResultHistoryTable.d.ts.map +1 -1
- package/dist/components/tis/ResultHistoryTable.js +1 -1
- package/package.json +1 -1
- package/src/components/Indicator.tsx +177 -162
- package/src/components/ams/AmsProvider.tsx +7 -0
- package/src/components/ams/AssetDetailView.tsx +287 -4
- package/src/components/ams/AssetRegistryTable.tsx +325 -21
- package/src/components/ams/CalibrationEntryDialog.tsx +163 -30
- package/src/components/ams/MissingAssetsBanner.tsx +124 -0
- package/src/components/ams/PlaceholderHealthPanel.tsx +188 -0
- package/src/components/ams/index.ts +2 -0
- package/src/components/index.ts +26 -0
- package/src/components/network/NetworkPanel.tsx +363 -0
- package/src/components/network/NetworkProvider.tsx +349 -0
- package/src/components/network/StagedChangeBanner.tsx +101 -0
- package/src/components/network/index.ts +17 -0
- package/src/components/tis/ProjectManager.tsx +393 -0
- package/src/components/tis/ResultHistoryTable.tsx +126 -188
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* <MissingAssetsBanner> — warning panel for unfilled asset roles.
|
|
3
|
+
*
|
|
4
|
+
* Lists every `by_location` asset_ref declared in project.json that
|
|
5
|
+
* has no active asset registered at its location. Each row gets a
|
|
6
|
+
* "Register" button that fires a custom DOM event the
|
|
7
|
+
* <AssetRegistryTable> picks up to open its Add dialog pre-populated
|
|
8
|
+
* with the asset_type + location.
|
|
9
|
+
*
|
|
10
|
+
* Zero-prop. Hides itself when zero missing. Drop above
|
|
11
|
+
* <AssetRegistryTable> in the AIS tab.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import React, { useMemo } from 'react';
|
|
15
|
+
import { Button } from 'primereact/button';
|
|
16
|
+
import { useAms, type AmsRole } from './AmsProvider';
|
|
17
|
+
|
|
18
|
+
/** Custom event the banner emits; <AssetRegistryTable> listens for it
|
|
19
|
+
* and opens its Add dialog pre-populated. Decouples the two so the
|
|
20
|
+
* banner can sit anywhere relative to the table in the layout. */
|
|
21
|
+
export const PREFILL_ADD_EVENT = 'ams:prefill-add';
|
|
22
|
+
export interface PrefillAddDetail {
|
|
23
|
+
assetType: string;
|
|
24
|
+
location: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const MissingAssetsBanner: React.FC = () => {
|
|
28
|
+
const { roles, assets, rolesLoaded } = useAms();
|
|
29
|
+
|
|
30
|
+
/** Flatten the role registry into (asset_type, AmsRole) tuples.
|
|
31
|
+
* Skip roles that have at least one active asset registered —
|
|
32
|
+
* those are filled and don't need attention. Also skip roles
|
|
33
|
+
* marked `by_id_field`-only (those have no fixed slot — they're
|
|
34
|
+
* picked per-test by the operator). */
|
|
35
|
+
const missing: Array<{ assetType: string; role: AmsRole }> = useMemo(() => {
|
|
36
|
+
const result: Array<{ assetType: string; role: AmsRole }> = [];
|
|
37
|
+
for (const [assetType, list] of Object.entries(roles)) {
|
|
38
|
+
for (const role of list) {
|
|
39
|
+
// Is there an active asset of this type at this location?
|
|
40
|
+
const filled = assets.some(a =>
|
|
41
|
+
a.asset_type === assetType
|
|
42
|
+
&& a.location === role.location
|
|
43
|
+
&& a.status === 'active'
|
|
44
|
+
);
|
|
45
|
+
if (!filled) {
|
|
46
|
+
result.push({ assetType, role });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}, [roles, assets]);
|
|
52
|
+
|
|
53
|
+
if (!rolesLoaded) return null;
|
|
54
|
+
if (missing.length === 0) return null;
|
|
55
|
+
|
|
56
|
+
const fireRegister = (assetType: string, location: string) => {
|
|
57
|
+
const detail: PrefillAddDetail = { assetType, location };
|
|
58
|
+
window.dispatchEvent(new CustomEvent(PREFILL_ADD_EVENT, { detail }));
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const oneMany = (n: number, sing: string, plur: string) =>
|
|
62
|
+
n === 1 ? sing : plur;
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div
|
|
66
|
+
role="alert"
|
|
67
|
+
style={{
|
|
68
|
+
borderLeft: '4px solid #f59e0b',
|
|
69
|
+
background: 'rgba(245, 158, 11, 0.08)',
|
|
70
|
+
padding: '0.75rem 1rem',
|
|
71
|
+
marginBottom: '1rem',
|
|
72
|
+
borderRadius: '4px',
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', marginBottom: '0.5rem' }}>
|
|
76
|
+
<i className="pi pi-exclamation-triangle" style={{ color: '#f59e0b' }} />
|
|
77
|
+
<strong>
|
|
78
|
+
{missing.length} {oneMany(missing.length, 'role has', 'roles have')} no active asset registered.
|
|
79
|
+
</strong>
|
|
80
|
+
<span style={{ color: 'var(--text-secondary-color)', fontSize: '0.875rem' }}>
|
|
81
|
+
Modules and tests depending on {oneMany(missing.length, 'it', 'them')} will refuse to start.
|
|
82
|
+
</span>
|
|
83
|
+
</div>
|
|
84
|
+
<ul style={{ margin: 0, paddingLeft: '1.5rem' }}>
|
|
85
|
+
{missing.map(({ assetType, role }) => {
|
|
86
|
+
const label = role.label ?? role.location;
|
|
87
|
+
const ref_summary = [
|
|
88
|
+
role.used_by.length > 0
|
|
89
|
+
? `methods: ${role.used_by.join(', ')}`
|
|
90
|
+
: null,
|
|
91
|
+
role.used_by_modules.length > 0
|
|
92
|
+
? `modules: ${role.used_by_modules.join(', ')}`
|
|
93
|
+
: null,
|
|
94
|
+
].filter(Boolean).join(' • ');
|
|
95
|
+
return (
|
|
96
|
+
<li key={`${assetType}/${role.location}`}
|
|
97
|
+
style={{ marginBottom: '0.25rem',
|
|
98
|
+
display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
|
99
|
+
<span>
|
|
100
|
+
<strong>{label}</strong>
|
|
101
|
+
<span style={{ color: 'var(--text-secondary-color)' }}>
|
|
102
|
+
{' '}({assetType} at <code>{role.location}</code>)
|
|
103
|
+
</span>
|
|
104
|
+
{ref_summary && (
|
|
105
|
+
<span style={{ color: 'var(--text-secondary-color)',
|
|
106
|
+
fontSize: '0.85em', marginLeft: '0.5rem' }}>
|
|
107
|
+
— {ref_summary}
|
|
108
|
+
</span>
|
|
109
|
+
)}
|
|
110
|
+
</span>
|
|
111
|
+
<Button
|
|
112
|
+
label="Register"
|
|
113
|
+
icon="pi pi-plus"
|
|
114
|
+
size="small"
|
|
115
|
+
outlined
|
|
116
|
+
onClick={() => fireRegister(assetType, role.location)}
|
|
117
|
+
/>
|
|
118
|
+
</li>
|
|
119
|
+
);
|
|
120
|
+
})}
|
|
121
|
+
</ul>
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
};
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* <PlaceholderHealthPanel> — pre-flight check for AMS placeholder
|
|
3
|
+
* resolution. Calls `ams.diagnose_placeholders` and lists every
|
|
4
|
+
* `${ams.by_location.*}` reference in every enabled module's config
|
|
5
|
+
* along with its resolution status. Green check = will resolve at
|
|
6
|
+
* module-spawn time. Red ✗ = will block the spawn, with the typed
|
|
7
|
+
* reason ("no active asset registered at location `tsdr_fx`") next to
|
|
8
|
+
* the offending config path.
|
|
9
|
+
*
|
|
10
|
+
* Drop into the AIS tab. Zero-prop: reads from <AmsProvider> context
|
|
11
|
+
* and the EventEmitter for IPC.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
|
15
|
+
import { Button } from 'primereact/button';
|
|
16
|
+
import { DataTable } from 'primereact/datatable';
|
|
17
|
+
import { Column } from 'primereact/column';
|
|
18
|
+
import { EventEmitterContext } from '../../core/EventEmitterContext';
|
|
19
|
+
import { MessageType } from '../../hub/CommandMessage';
|
|
20
|
+
import { useAms } from './AmsProvider';
|
|
21
|
+
|
|
22
|
+
interface PlaceholderRow {
|
|
23
|
+
module: string;
|
|
24
|
+
path: string;
|
|
25
|
+
placeholder: string;
|
|
26
|
+
location: string;
|
|
27
|
+
field_path: string[];
|
|
28
|
+
status: 'ok' | 'unresolved';
|
|
29
|
+
/** Present on `status: ok` rows. */
|
|
30
|
+
asset_id?: string;
|
|
31
|
+
asset_type?: string;
|
|
32
|
+
/** Present on `status: ok` rows — the rendered value the module
|
|
33
|
+
* will receive (number / string / bool depending on the field). */
|
|
34
|
+
resolved_value?: any;
|
|
35
|
+
/** Present on `status: unresolved` rows — human-readable cause. */
|
|
36
|
+
reason?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface Summary {
|
|
40
|
+
total: number;
|
|
41
|
+
ok: number;
|
|
42
|
+
unresolved: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const PlaceholderHealthPanel: React.FC = () => {
|
|
46
|
+
const { invoke } = useContext(EventEmitterContext);
|
|
47
|
+
// The panel watches AMS state mutations so refresh is automatic on
|
|
48
|
+
// create/update — the operator shouldn't have to click Refresh
|
|
49
|
+
// after registering the missing asset.
|
|
50
|
+
const { assets, refreshAssets } = useAms();
|
|
51
|
+
const [rows, setRows] = useState<PlaceholderRow[] | null>(null);
|
|
52
|
+
const [summary, setSummary] = useState<Summary | null>(null);
|
|
53
|
+
const [loading, setLoading] = useState(false);
|
|
54
|
+
const [error, setError] = useState<string | null>(null);
|
|
55
|
+
|
|
56
|
+
const refresh = useCallback(async () => {
|
|
57
|
+
setLoading(true);
|
|
58
|
+
setError(null);
|
|
59
|
+
try {
|
|
60
|
+
const resp: any = await invoke(
|
|
61
|
+
'ams.diagnose_placeholders' as any, MessageType.Request, {} as any,
|
|
62
|
+
);
|
|
63
|
+
if (resp?.success) {
|
|
64
|
+
setRows((resp.data?.placeholders ?? []) as PlaceholderRow[]);
|
|
65
|
+
setSummary(resp.data?.summary ?? null);
|
|
66
|
+
} else {
|
|
67
|
+
setError(resp?.error_message ?? 'diagnose_placeholders failed');
|
|
68
|
+
}
|
|
69
|
+
} catch (e: any) {
|
|
70
|
+
setError(String(e?.message ?? e));
|
|
71
|
+
} finally {
|
|
72
|
+
setLoading(false);
|
|
73
|
+
}
|
|
74
|
+
}, [invoke]);
|
|
75
|
+
|
|
76
|
+
// Initial load and re-load whenever the asset registry changes.
|
|
77
|
+
// `assets` is updated by AmsProvider in response to ams.* broadcasts,
|
|
78
|
+
// so registering a missing load_cell flips its row from red to
|
|
79
|
+
// green without manual interaction.
|
|
80
|
+
useEffect(() => { void refresh(); }, [refresh, assets]);
|
|
81
|
+
|
|
82
|
+
const renderStatus = (row: PlaceholderRow) => {
|
|
83
|
+
if (row.status === 'ok') {
|
|
84
|
+
return (
|
|
85
|
+
<span style={{ color: '#34d399', display: 'inline-flex', alignItems: 'center', gap: '0.25rem' }}>
|
|
86
|
+
<i className="pi pi-check-circle" />
|
|
87
|
+
</span>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return (
|
|
91
|
+
<span style={{ color: '#f59e0b', display: 'inline-flex', alignItems: 'center', gap: '0.25rem' }}>
|
|
92
|
+
<i className="pi pi-exclamation-triangle" />
|
|
93
|
+
</span>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const renderValueOrReason = (row: PlaceholderRow) => {
|
|
98
|
+
if (row.status === 'unresolved') {
|
|
99
|
+
return (
|
|
100
|
+
<span style={{ color: '#f59e0b' }}>{row.reason ?? 'unresolved'}</span>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
// Render the resolved value compactly. Numbers render bare,
|
|
104
|
+
// strings get quotes so the operator can tell "5000" (number)
|
|
105
|
+
// from "5000" (string) — relevant for EtherCAT SDO hex values
|
|
106
|
+
// which are intentionally strings.
|
|
107
|
+
const v = row.resolved_value;
|
|
108
|
+
let body: React.ReactNode;
|
|
109
|
+
if (typeof v === 'string') body = <code>"{v}"</code>;
|
|
110
|
+
else if (typeof v === 'number') body = <code>{v}</code>;
|
|
111
|
+
else if (v === null || v === undefined) body = <em style={{ color: '#9ca3af' }}>null</em>;
|
|
112
|
+
else body = <code>{JSON.stringify(v)}</code>;
|
|
113
|
+
return body;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const renderAsset = (row: PlaceholderRow) => {
|
|
117
|
+
if (row.status !== 'ok' || !row.asset_id) {
|
|
118
|
+
return <span style={{ color: '#9ca3af' }}>—</span>;
|
|
119
|
+
}
|
|
120
|
+
return <code style={{ fontSize: '0.85em' }}>{row.asset_id}</code>;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const summaryLine = summary && (
|
|
124
|
+
<span>
|
|
125
|
+
{summary.total === 0 ? (
|
|
126
|
+
<span style={{ color: '#9ca3af' }}>
|
|
127
|
+
No <code>{'${ams.*}'}</code> placeholders in any enabled module's config.
|
|
128
|
+
</span>
|
|
129
|
+
) : summary.unresolved === 0 ? (
|
|
130
|
+
<span style={{ color: '#34d399' }}>
|
|
131
|
+
✓ All {summary.total} placeholder{summary.total === 1 ? '' : 's'} resolved.
|
|
132
|
+
Modules will start cleanly.
|
|
133
|
+
</span>
|
|
134
|
+
) : (
|
|
135
|
+
<span style={{ color: '#f59e0b' }}>
|
|
136
|
+
{summary.unresolved} of {summary.total} placeholder
|
|
137
|
+
{summary.total === 1 ? '' : 's'} can't be resolved.
|
|
138
|
+
Register the missing asset{summary.unresolved === 1 ? '' : 's'} before
|
|
139
|
+
starting the affected module{summary.unresolved === 1 ? '' : 's'}.
|
|
140
|
+
</span>
|
|
141
|
+
)}
|
|
142
|
+
</span>
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
|
|
147
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
148
|
+
<h4 style={{ margin: 0 }}>Module Placeholder Health</h4>
|
|
149
|
+
<Button
|
|
150
|
+
icon="pi pi-refresh"
|
|
151
|
+
label="Refresh"
|
|
152
|
+
onClick={() => { void refreshAssets(); void refresh(); }}
|
|
153
|
+
loading={loading}
|
|
154
|
+
severity="secondary"
|
|
155
|
+
outlined
|
|
156
|
+
/>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
{error && (
|
|
160
|
+
<div style={{ color: '#ef4444', fontSize: '0.875rem' }}>
|
|
161
|
+
Couldn't fetch placeholder diagnostic: {error}
|
|
162
|
+
</div>
|
|
163
|
+
)}
|
|
164
|
+
|
|
165
|
+
<div style={{ fontSize: '0.875rem' }}>{summaryLine}</div>
|
|
166
|
+
|
|
167
|
+
{rows && rows.length > 0 && (
|
|
168
|
+
<DataTable
|
|
169
|
+
value={rows}
|
|
170
|
+
size="small"
|
|
171
|
+
stripedRows
|
|
172
|
+
sortField="status"
|
|
173
|
+
sortOrder={-1}
|
|
174
|
+
emptyMessage="No placeholders to report."
|
|
175
|
+
>
|
|
176
|
+
<Column header="" body={renderStatus} style={{ width: '2rem' }} />
|
|
177
|
+
<Column field="module" header="Module" style={{ width: '8rem' }} />
|
|
178
|
+
<Column field="location" header="Role" style={{ width: '10rem' }} />
|
|
179
|
+
<Column header="Value / Reason" body={renderValueOrReason} />
|
|
180
|
+
<Column header="Asset" body={renderAsset} style={{ width: '14rem' }} />
|
|
181
|
+
<Column field="path" header="Config path"
|
|
182
|
+
body={(r: PlaceholderRow) => <code style={{ fontSize: '0.8em' }}>{r.path}</code>}
|
|
183
|
+
/>
|
|
184
|
+
</DataTable>
|
|
185
|
+
)}
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
};
|
|
@@ -11,3 +11,5 @@ export { AssetRegistryTable } from './AssetRegistryTable';
|
|
|
11
11
|
export { AssetDetailView } from './AssetDetailView';
|
|
12
12
|
export { CalibrationEntryDialog } from './CalibrationEntryDialog';
|
|
13
13
|
export { SubLocationPicker } from './SubLocationPicker';
|
|
14
|
+
export { PlaceholderHealthPanel } from './PlaceholderHealthPanel';
|
|
15
|
+
export { MissingAssetsBanner } from './MissingAssetsBanner';
|
package/src/components/index.ts
CHANGED
|
@@ -48,6 +48,9 @@ export type { TestDataViewProps, ChartAxis, ChartSeries, ChartView, RawDataShape
|
|
|
48
48
|
export { TestRawDataView } from './tis/TestRawDataView';
|
|
49
49
|
export type { TestRawDataViewProps } from './tis/TestRawDataView';
|
|
50
50
|
|
|
51
|
+
export { ProjectManager } from './tis/ProjectManager';
|
|
52
|
+
export type { ProjectManagerProps } from './tis/ProjectManager';
|
|
53
|
+
|
|
51
54
|
// -----------------------------------------------------------------------
|
|
52
55
|
// Asset Management System — see autocore-server/doc/ams_product_plan.md
|
|
53
56
|
// Drop <AmsProvider> at the top of your HMI; the rest are zero-prop.
|
|
@@ -74,3 +77,26 @@ export { AssetDetailView } from './ams/AssetDetailView';
|
|
|
74
77
|
export { CalibrationEntryDialog } from './ams/CalibrationEntryDialog';
|
|
75
78
|
export type { CalibrationEntryDialogProps } from './ams/CalibrationEntryDialog';
|
|
76
79
|
export { SubLocationPicker } from './ams/SubLocationPicker';
|
|
80
|
+
|
|
81
|
+
// -----------------------------------------------------------------------
|
|
82
|
+
// Network management — wraps the autocore-server `nw.*` IPC servelet
|
|
83
|
+
// (nmcli/NetworkManager backend). Drop <NetworkProvider> at the top of
|
|
84
|
+
// your HMI, render <StagedChangeBanner> in a high-z slot so the
|
|
85
|
+
// revert countdown is visible on every tab, and mount <NetworkPanel>
|
|
86
|
+
// on the Network tab.
|
|
87
|
+
// -----------------------------------------------------------------------
|
|
88
|
+
export { NetworkProvider, useNetwork } from './network/NetworkProvider';
|
|
89
|
+
export type {
|
|
90
|
+
NetworkProviderProps,
|
|
91
|
+
NetworkContextValue,
|
|
92
|
+
NetworkStatus,
|
|
93
|
+
NetworkInterface,
|
|
94
|
+
NetworkConnection,
|
|
95
|
+
NetworkDeviceIp4,
|
|
96
|
+
WifiAp,
|
|
97
|
+
StagedChange,
|
|
98
|
+
} from './network/NetworkProvider';
|
|
99
|
+
export { NetworkPanel } from './network/NetworkPanel';
|
|
100
|
+
export type { NetworkPanelProps } from './network/NetworkPanel';
|
|
101
|
+
export { StagedChangeBanner } from './network/StagedChangeBanner';
|
|
102
|
+
export type { StagedChangeBannerProps } from './network/StagedChangeBanner';
|