tassello 0.2.0 → 0.4.0

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/CHANGELOG.md +53 -0
  2. package/README.md +175 -8
  3. package/dist/server/behavior_tree_files.d.ts +0 -1
  4. package/dist/server/behavior_tree_files.js +2 -25
  5. package/dist/server/diagnostics.d.ts +16 -0
  6. package/dist/server/diagnostics.js +200 -0
  7. package/dist/server/diagnostics_state.d.ts +23 -0
  8. package/dist/server/diagnostics_state.js +146 -0
  9. package/dist/server/disks.d.ts +14 -0
  10. package/dist/server/disks.js +152 -0
  11. package/dist/server/file_manager.d.ts +25 -0
  12. package/dist/server/file_manager.js +717 -0
  13. package/dist/server/http.js +248 -5
  14. package/dist/server/netpbm.d.ts +3 -0
  15. package/dist/server/netpbm.js +69 -0
  16. package/dist/server/networking.d.ts +41 -0
  17. package/dist/server/networking.js +508 -0
  18. package/dist/server/networking_format.d.ts +38 -0
  19. package/dist/server/networking_format.js +651 -0
  20. package/dist/server/pty.d.ts +26 -0
  21. package/dist/server/pty.js +150 -0
  22. package/dist/server/server.js +13 -2
  23. package/dist/server/session.d.ts +10 -1
  24. package/dist/server/session.js +88 -1
  25. package/dist/server/terminal_output.d.ts +25 -0
  26. package/dist/server/terminal_output.js +108 -0
  27. package/dist/server/terminals.d.ts +32 -0
  28. package/dist/server/terminals.js +366 -0
  29. package/dist/server/util.d.ts +4 -0
  30. package/dist/server/util.js +45 -1
  31. package/dist/shared/file_manager.d.ts +130 -0
  32. package/dist/shared/file_manager.js +7 -0
  33. package/dist/shared/networking.d.ts +178 -0
  34. package/dist/shared/networking.js +248 -0
  35. package/dist/shared/protocol.d.ts +103 -2
  36. package/dist/shared/protocol.js +8 -0
  37. package/dist/types/client/api.d.ts +22 -0
  38. package/dist/types/client/connection.d.ts +30 -1
  39. package/dist/types/client/icons.d.ts +33 -0
  40. package/dist/types/client/sdk/index.d.ts +5 -3
  41. package/dist/types/shared/file_manager.d.ts +130 -0
  42. package/dist/types/shared/networking.d.ts +178 -0
  43. package/dist/types/shared/protocol.d.ts +103 -2
  44. package/dist/www/assets/Joystick.js +1 -1
  45. package/dist/www/assets/RobotModelView.js +2 -2
  46. package/dist/www/assets/TerminalView.css +1 -0
  47. package/dist/www/assets/TerminalView.js +17 -0
  48. package/dist/www/assets/dialogs.js +1 -0
  49. package/dist/www/assets/icons.js +1 -1
  50. package/dist/www/assets/index.css +1 -1
  51. package/dist/www/assets/index.js +59 -19
  52. package/dist/www/assets/keys.js +3 -0
  53. package/dist/www/assets/shared/tassello-sdk.js +1 -1
  54. package/dist/www/index.html +2 -0
  55. package/docs/plugins.md +22 -0
  56. package/package.json +6 -2
@@ -0,0 +1,178 @@
1
+ export type WifiSecurity = 'open' | 'owe' | 'wep' | 'wpa-psk' | 'sae' | 'wpa-eap';
2
+ export type ConnectionKind = 'wifi' | 'hotspot' | 'ethernet' | 'vpn' | 'other';
3
+ export type EditableKind = 'wifi' | 'hotspot' | 'ethernet';
4
+ export type Ipv4Method = 'auto' | 'manual' | 'shared' | 'disabled';
5
+ export declare const DEVICE_UNMANAGED = 10;
6
+ export declare const DEVICE_UNAVAILABLE = 20;
7
+ export declare const DEVICE_DISCONNECTED = 30;
8
+ export declare const DEVICE_ACTIVATED = 100;
9
+ export declare const DEVICE_DEACTIVATING = 110;
10
+ export declare const DEVICE_FAILED = 120;
11
+ export interface NetworkDevice {
12
+ name: string;
13
+ type: string;
14
+ state: number;
15
+ stateText: string;
16
+ reason: string;
17
+ connection: string | null;
18
+ connectionName: string;
19
+ external: boolean;
20
+ software: boolean;
21
+ hwaddr: string;
22
+ mtu: number | null;
23
+ carrier: boolean | null;
24
+ speed: string;
25
+ driver: string;
26
+ vendor: string;
27
+ product: string;
28
+ accessPoint: boolean;
29
+ band24: boolean;
30
+ band5: boolean;
31
+ ipv4: string[];
32
+ ipv6: string[];
33
+ gateway: string;
34
+ dns: string[];
35
+ }
36
+ export interface NetworkConnection {
37
+ uuid: string;
38
+ name: string;
39
+ type: string;
40
+ kind: ConnectionKind;
41
+ interface: string;
42
+ autoconnect: boolean;
43
+ priority: number;
44
+ lastUsed: number | null;
45
+ readOnly: boolean;
46
+ file: string;
47
+ device: string | null;
48
+ activeState: string | null;
49
+ external: boolean;
50
+ ssid: string;
51
+ security: WifiSecurity | null;
52
+ hidden: boolean;
53
+ band: '' | 'a' | 'bg';
54
+ channel: number;
55
+ identity: string;
56
+ vpnService: string;
57
+ ipv4Method: string;
58
+ addresses: string[];
59
+ gateway: string;
60
+ dns: string[];
61
+ }
62
+ export interface WifiNetwork {
63
+ ssid: string;
64
+ device: string;
65
+ bssid: string;
66
+ signal: number;
67
+ frequency: number;
68
+ channel: number;
69
+ rate: string;
70
+ securityText: string;
71
+ security: WifiSecurity;
72
+ mode: string;
73
+ active: boolean;
74
+ points: number;
75
+ }
76
+ export interface NetworkActivity {
77
+ id: number;
78
+ action: NetworkRequest['action'];
79
+ label: string;
80
+ uuid: string | null;
81
+ ssid: string | null;
82
+ device: string | null;
83
+ started: number;
84
+ finished: number | null;
85
+ error: string | null;
86
+ }
87
+ export interface NetworkPermissions {
88
+ control: boolean;
89
+ modify: boolean;
90
+ wifi: boolean;
91
+ scan: boolean;
92
+ share: boolean;
93
+ }
94
+ export interface NetworkState {
95
+ error: string | null;
96
+ version: string;
97
+ state: string;
98
+ connectivity: string;
99
+ wifi: boolean;
100
+ wifiHardware: boolean;
101
+ permissions: NetworkPermissions;
102
+ devices: NetworkDevice[];
103
+ connections: NetworkConnection[];
104
+ networks: WifiNetwork[];
105
+ scanning: boolean;
106
+ activity: NetworkActivity[];
107
+ }
108
+ export interface NetworkSettings {
109
+ kind: EditableKind;
110
+ name: string;
111
+ interface: string;
112
+ autoconnect: boolean;
113
+ priority: number;
114
+ ssid: string;
115
+ hidden: boolean;
116
+ security: WifiSecurity;
117
+ password: string | null;
118
+ identity: string;
119
+ band: '' | 'a' | 'bg';
120
+ channel: number;
121
+ ipv4: Ipv4Method;
122
+ addresses: string[];
123
+ gateway: string;
124
+ dns: string[];
125
+ }
126
+ export type NetworkRequest = {
127
+ action: 'connect';
128
+ uuid: string;
129
+ device: string | null;
130
+ password: string | null;
131
+ } | {
132
+ action: 'disconnect';
133
+ uuid: string;
134
+ } | {
135
+ action: 'create';
136
+ settings: NetworkSettings;
137
+ connect: boolean;
138
+ device: string | null;
139
+ } | {
140
+ action: 'modify';
141
+ uuid: string;
142
+ settings: NetworkSettings;
143
+ } | {
144
+ action: 'delete';
145
+ uuid: string;
146
+ } | {
147
+ action: 'wifi';
148
+ enabled: boolean;
149
+ } | {
150
+ action: 'rescan';
151
+ device: string | null;
152
+ } | {
153
+ action: 'secrets';
154
+ uuid: string;
155
+ };
156
+ export interface NetworkSecrets {
157
+ ssid: string;
158
+ security: WifiSecurity | null;
159
+ password: string;
160
+ hidden: boolean;
161
+ }
162
+ export interface NetworkResult {
163
+ uuid: string | null;
164
+ message: string | null;
165
+ }
166
+ export declare const WRONG_PASSWORD = "Wrong password, or none given";
167
+ export declare const SECURITY_LABELS: Record<WifiSecurity, string>;
168
+ export declare function needsPassword(security: WifiSecurity): boolean;
169
+ export declare function isInterfaceName(name: string): boolean;
170
+ export declare function isUuid(uuid: string): boolean;
171
+ export declare function isIpv4(address: string): boolean;
172
+ export declare function isIpv6(address: string): boolean;
173
+ export declare function cidrProblem(cidr: string): string | null;
174
+ export declare function splitList(text: string): string[];
175
+ export declare function passwordProblem(security: WifiSecurity, password: string, hotspot?: boolean): string | null;
176
+ export declare function settingsProblems(settings: NetworkSettings, creating: boolean): Partial<Record<keyof NetworkSettings, string>>;
177
+ export declare function settingsOf(connection: NetworkConnection): NetworkSettings | null;
178
+ export declare function defaultSettings(kind: EditableKind): NetworkSettings;
@@ -0,0 +1,248 @@
1
+ // The robot's networks, as NetworkManager has them: what the Networking
2
+ // widget shows and changes (see src/server/networking.ts), and the checks of
3
+ // the settings it sends, which the page makes as they are typed and the
4
+ // server again before it passes them to nmcli.
5
+ // NetworkManager's device states (NMDeviceState)
6
+ export const DEVICE_UNMANAGED = 10;
7
+ export const DEVICE_UNAVAILABLE = 20;
8
+ export const DEVICE_DISCONNECTED = 30;
9
+ export const DEVICE_ACTIVATED = 100;
10
+ export const DEVICE_DEACTIVATING = 110;
11
+ export const DEVICE_FAILED = 120;
12
+ // The reason a connection fails with when its password is wrong: the page
13
+ // asks for it again
14
+ export const WRONG_PASSWORD = 'Wrong password, or none given';
15
+ export const SECURITY_LABELS = {
16
+ open: 'None (open)',
17
+ owe: 'Enhanced Open (OWE)',
18
+ wep: 'WEP',
19
+ 'wpa-psk': 'WPA/WPA2 Personal',
20
+ sae: 'WPA3 Personal',
21
+ 'wpa-eap': 'WPA/WPA2 Enterprise',
22
+ };
23
+ export function needsPassword(security) {
24
+ return security !== 'open' && security !== 'owe';
25
+ }
26
+ // Linux's: at most 15 characters, no slashes, spaces or colons
27
+ const INTERFACE = /^[\w.@-]{1,15}$/;
28
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
29
+ // Of the 2.4 GHz band, and of the 5 GHz one (20 MHz channels)
30
+ const CHANNELS_24 = Array.from({ length: 14 }, (_, i) => i + 1);
31
+ const CHANNELS_5 = [
32
+ 36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144, 149, 153, 157, 161, 165,
33
+ 169, 173, 177,
34
+ ];
35
+ export function isInterfaceName(name) {
36
+ return INTERFACE.test(name);
37
+ }
38
+ export function isUuid(uuid) {
39
+ return UUID.test(uuid);
40
+ }
41
+ export function isIpv4(address) {
42
+ const parts = address.split('.');
43
+ return parts.length === 4 && parts.every((part) => /^(0|[1-9]\d{0,2})$/.test(part) && Number(part) <= 255);
44
+ }
45
+ export function isIpv6(address) {
46
+ if (!/^[0-9a-f:.]+$/i.test(address) || !address.includes(':')) {
47
+ return false;
48
+ }
49
+ const halves = address.split('::');
50
+ if (halves.length > 2) {
51
+ return false;
52
+ }
53
+ const groups = halves.flatMap((half) => (half ? half.split(':') : []));
54
+ // An IPv4 tail counts as two groups
55
+ const last = groups.at(-1) ?? '';
56
+ const ipv4Tail = last.includes('.');
57
+ if (ipv4Tail && !isIpv4(last)) {
58
+ return false;
59
+ }
60
+ const hex = ipv4Tail ? groups.slice(0, -1) : groups;
61
+ if (!hex.every((group) => /^[0-9a-f]{1,4}$/i.test(group))) {
62
+ return false;
63
+ }
64
+ const count = hex.length + (ipv4Tail ? 2 : 0);
65
+ return halves.length === 2 ? count < 8 : count === 8;
66
+ }
67
+ // 192.168.1.20/24: an address of a network, not the network itself
68
+ export function cidrProblem(cidr) {
69
+ const [address, prefix, ...rest] = cidr.trim().split('/');
70
+ if (rest.length > 0 || !isIpv4(address)) {
71
+ return `“${cidr.trim()}” is not an IPv4 address`;
72
+ }
73
+ if (prefix === undefined) {
74
+ return `Give its network's size: ${address}/24`;
75
+ }
76
+ if (!/^\d{1,2}$/.test(prefix) || Number(prefix) < 1 || Number(prefix) > 32) {
77
+ return `/${prefix} is not a network's size: /1 to /32 (/24: 255.255.255.0)`;
78
+ }
79
+ return null;
80
+ }
81
+ // Separated by commas, spaces or new lines
82
+ export function splitList(text) {
83
+ return text
84
+ .split(/[\s,;]+/)
85
+ .map((item) => item.trim())
86
+ .filter(Boolean);
87
+ }
88
+ function utf8Length(text) {
89
+ return new TextEncoder().encode(text).length;
90
+ }
91
+ // Of a Wi-Fi network or an access point, by its security; null if fine
92
+ export function passwordProblem(security, password, hotspot = false) {
93
+ switch (security) {
94
+ case 'open':
95
+ case 'owe':
96
+ return null;
97
+ case 'wpa-psk':
98
+ if (/^[0-9a-f]{64}$/i.test(password)) {
99
+ return null;
100
+ }
101
+ if (!/^[\x20-\x7e]*$/.test(password)) {
102
+ return 'Letters, digits and symbols of the keyboard only: no accents';
103
+ }
104
+ if (password.length < 8 || password.length > 63) {
105
+ return hotspot ? '8 to 63 characters' : 'WPA passwords have 8 to 63 characters';
106
+ }
107
+ return null;
108
+ case 'sae':
109
+ return password.length === 0 ? 'The network’s password' : password.length > 128 ? 'At most 128 characters' : null;
110
+ case 'wep':
111
+ return /^([\x20-\x7e]{5}|[\x20-\x7e]{13}|[0-9a-f]{10}|[0-9a-f]{26})$/i.test(password)
112
+ ? null
113
+ : 'WEP keys have 5 or 13 characters, or 10 or 26 hexadecimal digits';
114
+ case 'wpa-eap':
115
+ return password.length === 0 ? 'Your password on the network' : null;
116
+ }
117
+ }
118
+ // What is wrong with the settings, by field: empty if nothing. creating: a
119
+ // new connection, whose password is required.
120
+ export function settingsProblems(settings, creating) {
121
+ const problems = {};
122
+ const { kind } = settings;
123
+ const name = settings.name.trim();
124
+ if (!name) {
125
+ problems.name = 'A name for the connection';
126
+ }
127
+ else if (name.length > 255 || /[\x00-\x1f]/.test(name)) {
128
+ problems.name = 'At most 255 characters, on one line';
129
+ }
130
+ if (settings.interface && !isInterfaceName(settings.interface)) {
131
+ problems.interface = `“${settings.interface}” is not an interface's name`;
132
+ }
133
+ else if (kind === 'hotspot' && !settings.interface) {
134
+ problems.interface = 'The Wi-Fi interface of the access point';
135
+ }
136
+ if (!Number.isInteger(settings.priority) || settings.priority < -999 || settings.priority > 999) {
137
+ problems.priority = 'A whole number, from -999 to 999';
138
+ }
139
+ if (kind !== 'ethernet') {
140
+ const bytes = utf8Length(settings.ssid);
141
+ if (bytes === 0) {
142
+ problems.ssid = kind === 'hotspot' ? 'The name the access point shows' : 'The network’s name';
143
+ }
144
+ else if (bytes > 32) {
145
+ problems.ssid = 'At most 32 bytes (fewer with accents and symbols)';
146
+ }
147
+ else if (/[\x00-\x1f]/.test(settings.ssid)) {
148
+ problems.ssid = 'No control characters';
149
+ }
150
+ if (kind === 'hotspot' && settings.security !== 'open' && settings.security !== 'wpa-psk') {
151
+ problems.security = 'An access point is open, or secured by a WPA2 password';
152
+ }
153
+ if (settings.security === 'wpa-eap') {
154
+ if (!settings.identity.trim()) {
155
+ problems.identity = 'Your user name on the network';
156
+ }
157
+ else if (/[\x00-\x1f]/.test(settings.identity)) {
158
+ problems.identity = 'No control characters';
159
+ }
160
+ }
161
+ if (settings.password !== null) {
162
+ const problem = passwordProblem(settings.security, settings.password, kind === 'hotspot');
163
+ if (problem) {
164
+ problems.password = problem;
165
+ }
166
+ }
167
+ else if (creating && needsPassword(settings.security)) {
168
+ problems.password = 'The network’s password';
169
+ }
170
+ if (kind === 'hotspot') {
171
+ const channels = settings.band === 'a' ? CHANNELS_5 : settings.band === 'bg' ? CHANNELS_24 : [];
172
+ if (settings.channel !== 0 && !channels.includes(settings.channel)) {
173
+ problems.channel = settings.band
174
+ ? `Not a channel of ${settings.band === 'a' ? '5' : '2.4'} GHz`
175
+ : 'Choose the band of the channel';
176
+ }
177
+ }
178
+ }
179
+ const methods = kind === 'hotspot' ? ['shared'] : ['auto', 'manual', 'shared', 'disabled'];
180
+ if (!methods.includes(settings.ipv4)) {
181
+ problems.ipv4 = kind === 'hotspot' ? 'An access point gives the addresses (shared)' : 'Not a way to get an address';
182
+ }
183
+ const addressProblem = settings.addresses.map(cidrProblem).find((problem) => problem !== null);
184
+ if (addressProblem) {
185
+ problems.addresses = addressProblem;
186
+ }
187
+ else if (settings.ipv4 === 'manual' && settings.addresses.length === 0) {
188
+ problems.addresses = 'The robot’s address on this network: 192.168.1.20/24';
189
+ }
190
+ else if (settings.ipv4 === 'shared' && settings.addresses.length > 1) {
191
+ problems.addresses = 'One address: the robot’s, on the network it gives';
192
+ }
193
+ if (settings.gateway && !isIpv4(settings.gateway)) {
194
+ problems.gateway = `“${settings.gateway}” is not an IPv4 address`;
195
+ }
196
+ const badDns = settings.dns.find((server) => !isIpv4(server) && !isIpv6(server));
197
+ if (badDns !== undefined) {
198
+ problems.dns = `“${badDns}” is not an IP address`;
199
+ }
200
+ return problems;
201
+ }
202
+ // The settings of a saved connection, to edit them (its password unchanged)
203
+ export function settingsOf(connection) {
204
+ const kind = connection.kind;
205
+ if (kind !== 'wifi' && kind !== 'hotspot' && kind !== 'ethernet') {
206
+ return null;
207
+ }
208
+ const methods = ['auto', 'manual', 'shared', 'disabled'];
209
+ return {
210
+ kind,
211
+ name: connection.name,
212
+ interface: connection.interface,
213
+ autoconnect: connection.autoconnect,
214
+ priority: connection.priority,
215
+ ssid: connection.ssid,
216
+ hidden: connection.hidden,
217
+ security: connection.security ?? 'open',
218
+ password: null,
219
+ identity: connection.identity,
220
+ band: connection.band,
221
+ channel: connection.channel,
222
+ ipv4: methods.includes(connection.ipv4Method) ? connection.ipv4Method : 'auto',
223
+ addresses: connection.addresses,
224
+ gateway: connection.gateway,
225
+ dns: connection.dns,
226
+ };
227
+ }
228
+ // A new connection's, of a kind
229
+ export function defaultSettings(kind) {
230
+ return {
231
+ kind,
232
+ name: '',
233
+ interface: '',
234
+ autoconnect: true,
235
+ priority: 0,
236
+ ssid: '',
237
+ hidden: false,
238
+ security: kind === 'ethernet' ? 'open' : 'wpa-psk',
239
+ password: kind === 'ethernet' ? null : '',
240
+ identity: '',
241
+ band: '',
242
+ channel: 0,
243
+ ipv4: kind === 'hotspot' ? 'shared' : 'auto',
244
+ addresses: [],
245
+ gateway: '',
246
+ dns: [],
247
+ };
248
+ }
@@ -1,4 +1,5 @@
1
1
  import type { BtNodeModel } from './bt_nodes.ts';
2
+ import type { NetworkRequest, NetworkState } from './networking.ts';
2
3
  export type ImageTransport = 'auto' | 'raw' | 'compressed';
3
4
  export type GridStyle = 'map' | 'costmap';
4
5
  export interface TwistCommand {
@@ -192,7 +193,53 @@ export interface RosoutSubscribe {
192
193
  id: number;
193
194
  topic: string;
194
195
  }
195
- export type ClientMessage = TwistCommand | ImageSubscribe | GridSubscribe | TfSubscribe | PathSubscribe | TopicSubscribe | ServicesSubscribe | ServiceAction | LogsSubscribe | Unsubscribe | FrameAck | PosePublish | CommandRun | CommandStop | ArmSubscribe | ArmJog | RosPublish | RosCall | PluginRequest | PluginSubscribe | BehaviorTreeSubscribe | BehaviorTreeRecord | BehaviorTreePlaybackSubscribe | BehaviorTreePlaybackControl | BehaviorTreeFileSubscribe | BehaviorTreeFileSave | RosoutSubscribe;
196
+ export interface DiagnosticsSubscribe {
197
+ type: 'diagnostics.subscribe';
198
+ id: number;
199
+ topic: string;
200
+ }
201
+ export interface TerminalOpen {
202
+ type: 'terminal.open';
203
+ id: number;
204
+ terminal: string;
205
+ command: string;
206
+ folder: string;
207
+ cols: number;
208
+ rows: number;
209
+ started: number | null;
210
+ offset: number | null;
211
+ }
212
+ export interface TerminalInput {
213
+ type: 'terminal.input';
214
+ id: number;
215
+ data: string;
216
+ }
217
+ export interface TerminalResize {
218
+ type: 'terminal.resize';
219
+ id: number;
220
+ cols: number;
221
+ rows: number;
222
+ }
223
+ export interface TerminalAck {
224
+ type: 'terminal.ack';
225
+ id: number;
226
+ started: number;
227
+ offset: number;
228
+ }
229
+ export interface TerminalRestart {
230
+ type: 'terminal.restart';
231
+ id: number;
232
+ }
233
+ export interface NetworkSubscribe {
234
+ type: 'network.subscribe';
235
+ id: number;
236
+ }
237
+ export interface NetworkAction {
238
+ type: 'network.action';
239
+ id: number;
240
+ request: NetworkRequest;
241
+ }
242
+ export type ClientMessage = TwistCommand | ImageSubscribe | GridSubscribe | TfSubscribe | PathSubscribe | TopicSubscribe | ServicesSubscribe | ServiceAction | LogsSubscribe | Unsubscribe | FrameAck | PosePublish | CommandRun | CommandStop | ArmSubscribe | ArmJog | RosPublish | RosCall | PluginRequest | PluginSubscribe | BehaviorTreeSubscribe | BehaviorTreeRecord | BehaviorTreePlaybackSubscribe | BehaviorTreePlaybackControl | BehaviorTreeFileSubscribe | BehaviorTreeFileSave | RosoutSubscribe | DiagnosticsSubscribe | TerminalOpen | TerminalInput | TerminalResize | TerminalAck | TerminalRestart | NetworkSubscribe | NetworkAction;
196
243
  export interface Hello {
197
244
  type: 'hello';
198
245
  clientId: string;
@@ -429,7 +476,57 @@ export interface RosoutUpdate {
429
476
  skipped: number;
430
477
  nodes: string[] | null;
431
478
  }
432
- export type ServerMessage = Hello | CommandStates | CommandUpdate | StreamStatus | TfUpdate | PathUpdate | TopicMessage | ServicesUpdate | ServiceResult | LogsUpdate | ArmUpdate | Result | PluginData | BehaviorTreeInfo | BehaviorTreeUpdate | BehaviorTreeRecording | BehaviorTreePlaybackState | BehaviorTreeFile | BehaviorTreeModels | RosoutUpdate | PanelsChanged | ErrorMessage;
479
+ export interface DiagnosticEntry {
480
+ id: number;
481
+ name: string;
482
+ level: number;
483
+ message: string;
484
+ hardwareId: string;
485
+ values: {
486
+ key: string;
487
+ value: string;
488
+ }[];
489
+ stale: boolean;
490
+ updated: number;
491
+ since: number;
492
+ }
493
+ export interface DiagnosticChange {
494
+ seq: number;
495
+ time: number;
496
+ status: number;
497
+ level: number;
498
+ message: string;
499
+ }
500
+ export interface DiagnosticsUpdate {
501
+ type: 'diagnostics';
502
+ id: number;
503
+ reset: boolean;
504
+ started: number;
505
+ statuses: DiagnosticEntry[];
506
+ changes: DiagnosticChange[];
507
+ }
508
+ export interface TerminalOutput {
509
+ type: 'terminal.output';
510
+ id: number;
511
+ started: number;
512
+ offset: number;
513
+ reset: boolean;
514
+ data: string;
515
+ title: string | null;
516
+ }
517
+ export interface TerminalExit {
518
+ type: 'terminal.exit';
519
+ id: number;
520
+ started: number;
521
+ exitCode: number | null;
522
+ }
523
+ export interface NetworkUpdate extends NetworkState {
524
+ type: 'network';
525
+ id: number;
526
+ via: string | null;
527
+ canChange: boolean;
528
+ }
529
+ export type ServerMessage = Hello | CommandStates | CommandUpdate | StreamStatus | TfUpdate | PathUpdate | TopicMessage | ServicesUpdate | ServiceResult | LogsUpdate | ArmUpdate | Result | PluginData | BehaviorTreeInfo | BehaviorTreeUpdate | BehaviorTreeRecording | BehaviorTreePlaybackState | BehaviorTreeFile | BehaviorTreeModels | RosoutUpdate | DiagnosticsUpdate | TerminalOutput | TerminalExit | NetworkUpdate | PanelsChanged | ErrorMessage;
433
530
  export declare const FRAME_HEADER_BYTES = 11;
434
531
  export declare const IMAGE_FORMATS: readonly ['image/jpeg', 'image/png'];
435
532
  export interface GridInfo {
@@ -452,6 +549,10 @@ export declare const ARM_STATE_RATE = 10;
452
549
  export declare const BT_RATE = 10;
453
550
  export declare const ROSOUT_RATE = 10;
454
551
  export declare const ROSOUT_HISTORY = 10000;
552
+ export declare const DIAGNOSTICS_RATE = 4;
553
+ export declare const DIAGNOSTICS_CHANGES = 1000;
554
+ export declare const TERMINAL_SCROLLBACK = 2000;
555
+ export declare const TERMINAL_WINDOW: number;
455
556
  export declare const DASHBOARD_CLIENT_HEADER = "X-Dashboard-Client";
456
557
  export interface PluginInfo {
457
558
  name: string;
@@ -19,4 +19,12 @@ export const BT_RATE = 10;
19
19
  export const ROSOUT_RATE = 10;
20
20
  // Messages of the ROS log kept by the server, and by the page
21
21
  export const ROSOUT_HISTORY = 10_000;
22
+ // Diagnostics come about once a second
23
+ export const DIAGNOSTICS_RATE = 4;
24
+ // Changes of level kept by the server, and by the page
25
+ export const DIAGNOSTICS_CHANGES = 1000;
26
+ // Lines of a terminal kept above its screen, by the server and by the page
27
+ export const TERMINAL_SCROLLBACK = 2000;
28
+ // Characters of a terminal's output sent to a page and not yet written by it
29
+ export const TERMINAL_WINDOW = 256 * 1024;
22
30
  export const DASHBOARD_CLIENT_HEADER = 'X-Dashboard-Client';
@@ -1,5 +1,10 @@
1
+ import type { FsAnalysis, FsConflict, FsDetails, FsDisk, FsListing, FsOperation, FsPlaces, FsResult, FsSaved, FsText } from '../shared/file_manager.ts';
1
2
  import type { Panel, PanelSummary, TopicInfo } from '../shared/panel.ts';
2
3
  import { type PluginInfo } from '../shared/protocol.ts';
4
+ export declare class ApiError extends Error {
5
+ readonly status: number;
6
+ constructor(status: number, message: string);
7
+ }
3
8
  export type FileKind = 'image' | 'behavior-tree' | 'bag' | 'folder';
4
9
  export interface DirectoryListing {
5
10
  dir: string;
@@ -21,3 +26,20 @@ export declare const api: {
21
26
  listFiles: (dir: string, kind?: FileKind) => Promise<DirectoryListing>;
22
27
  uploadFile: (file: Blob) => Promise<string>;
23
28
  };
29
+ export interface Upload {
30
+ result: Promise<FsResult>;
31
+ abort: () => void;
32
+ }
33
+ export declare const fsApi: {
34
+ places: () => Promise<FsPlaces>;
35
+ list: (path: string, known?: string) => Promise<FsListing | undefined>;
36
+ details: (path: string) => Promise<FsDetails>;
37
+ disks: () => Promise<FsDisk[]>;
38
+ analyze: (path: string) => Promise<FsAnalysis>;
39
+ readText: (path: string, stamp?: string) => Promise<FsText | undefined>;
40
+ saveText: (path: string, text: string, base: string | null, create?: boolean) => Promise<FsSaved>;
41
+ operate: (operation: FsOperation) => Promise<FsResult>;
42
+ downloadUrl: (paths: string[]) => string;
43
+ viewUrl: (path: string, stamp?: string) => string;
44
+ upload(folder: string, name: string, file: Blob, conflict: FsConflict, onProgress: (sent: number) => void): Upload;
45
+ };
@@ -1,4 +1,5 @@
1
- import { type ArmSubscribe, type ArmUpdate, type BehaviorTreeFile, type BehaviorTreeFileSubscribe, type BehaviorTreeInfo, type BehaviorTreeModels, type BehaviorTreePlaybackControl, type BehaviorTreePlaybackState, type BehaviorTreePlaybackSubscribe, type BehaviorTreeRecording, type BehaviorTreeSaved, type BehaviorTreeSubscribe, type BehaviorTreeUpdate, type ClientMessage, type CommandState, type GridSubscribe, type ImageSubscribe, type LogsSubscribe, type LogsUpdate, type PathSubscribe, type PathUpdate, type RosoutSubscribe, type RosoutUpdate, type ServerMessage, type ServiceScope, type ServicesSubscribe, type ServicesUpdate, type ServiceVerb, type StreamStatus, type TfSubscribe, type TfUpdate, type TopicSubscribe } from '../shared/protocol.ts';
1
+ import type { NetworkRequest, NetworkResult, NetworkSecrets } from '../shared/networking.ts';
2
+ import { type ArmSubscribe, type ArmUpdate, type BehaviorTreeFile, type BehaviorTreeFileSubscribe, type BehaviorTreeInfo, type BehaviorTreeModels, type BehaviorTreePlaybackControl, type BehaviorTreePlaybackState, type BehaviorTreePlaybackSubscribe, type BehaviorTreeRecording, type BehaviorTreeSaved, type BehaviorTreeSubscribe, type BehaviorTreeUpdate, type ClientMessage, type CommandState, type DiagnosticsSubscribe, type DiagnosticsUpdate, type GridSubscribe, type ImageSubscribe, type LogsSubscribe, type LogsUpdate, type NetworkUpdate, type PathSubscribe, type PathUpdate, type RosoutSubscribe, type RosoutUpdate, type ServerMessage, type ServiceScope, type ServicesSubscribe, type ServicesUpdate, type ServiceVerb, type StreamStatus, type TerminalExit, type TerminalOpen, type TerminalOutput, type TfSubscribe, type TfUpdate, type TopicSubscribe } from '../shared/protocol.ts';
2
3
  export type ConnectionStatus = 'connecting' | 'open' | 'closed';
3
4
  export type ImageOptions = Omit<ImageSubscribe, 'type' | 'id'>;
4
5
  export type GridOptions = Omit<GridSubscribe, 'type' | 'id'>;
@@ -13,6 +14,8 @@ export type BehaviorTreePlaybackOptions = Omit<BehaviorTreePlaybackSubscribe, 't
13
14
  export type BehaviorTreePlaybackRequest = Omit<BehaviorTreePlaybackControl, 'type' | 'id'>;
14
15
  export type BehaviorTreeFileOptions = Omit<BehaviorTreeFileSubscribe, 'type' | 'id'>;
15
16
  export type RosoutOptions = Omit<RosoutSubscribe, 'type' | 'id'>;
17
+ export type DiagnosticsOptions = Omit<DiagnosticsSubscribe, 'type' | 'id'>;
18
+ export type TerminalOptions = Pick<TerminalOpen, 'terminal' | 'command' | 'folder'>;
16
19
  export interface TopicHandlers {
17
20
  status: (state: StreamStatus['state'], message: string) => void;
18
21
  message: (messageType: string, message: unknown) => void;
@@ -32,6 +35,16 @@ export interface RosoutHandlers {
32
35
  status: (state: StreamStatus['state'], message: string) => void;
33
36
  update: (update: RosoutUpdate) => void;
34
37
  }
38
+ export interface DiagnosticsHandlers {
39
+ status: (state: StreamStatus['state'], message: string) => void;
40
+ update: (update: DiagnosticsUpdate) => void;
41
+ }
42
+ export interface TerminalHandlers {
43
+ status: (state: StreamStatus['state'], message: string) => void;
44
+ output: (output: TerminalOutput) => void;
45
+ exit: (exit: TerminalExit) => void;
46
+ shown: () => Pick<TerminalOpen, 'cols' | 'rows' | 'started' | 'offset'>;
47
+ }
35
48
  export interface FrameInfo {
36
49
  width: number;
37
50
  height: number;
@@ -81,6 +94,22 @@ export declare class Connection {
81
94
  subscribeBehaviorTreeFile(options: BehaviorTreeFileOptions, handlers: BehaviorTreeFileHandlers): () => void;
82
95
  saveBehaviorTreeFile(path: string, text: string, base: string | null, create?: boolean): Promise<BehaviorTreeSaved>;
83
96
  subscribeRosout(options: RosoutOptions, handlers: RosoutHandlers): () => void;
97
+ subscribeDiagnostics(options: DiagnosticsOptions, handlers: DiagnosticsHandlers): () => void;
98
+ subscribeTerminal(options: TerminalOptions, handlers: TerminalHandlers): {
99
+ id: number;
100
+ unsubscribe: () => void;
101
+ };
102
+ terminalInput(id: number, data: string): boolean;
103
+ resizeTerminal(id: number, cols: number, rows: number): boolean;
104
+ ackTerminal(id: number, started: number, offset: number): boolean;
105
+ restartTerminal(id: number): boolean;
106
+ subscribeNetwork(callback: (update: NetworkUpdate) => void): () => void;
107
+ networkAction(request: Exclude<NetworkRequest, {
108
+ action: 'secrets';
109
+ }>): Promise<NetworkResult>;
110
+ networkAction(request: Extract<NetworkRequest, {
111
+ action: 'secrets';
112
+ }>): Promise<NetworkSecrets>;
84
113
  serviceAction(unit: string, scope: ServiceScope, verb: ServiceVerb): Promise<string | null>;
85
114
  publish(topic: string, messageType: string, message: unknown): boolean;
86
115
  callService<T = unknown>(service: string, serviceType: string, request?: unknown, timeoutMs?: number): Promise<T>;