@opengeni/react 0.45.0 → 0.48.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.
- package/README.md +68 -12
- package/dist/{chunk-U3CSCMWA.js → chunk-FK6VG4LL.js} +9 -13
- package/dist/chunk-FK6VG4LL.js.map +1 -0
- package/dist/{chunk-HFO4ERGQ.js → chunk-FWXGSNXT.js} +2 -2
- package/dist/{chunk-SRFUT2ZU.js → chunk-I4MPFDRP.js} +119 -63
- package/dist/chunk-I4MPFDRP.js.map +1 -0
- package/dist/{chunk-EB67J347.js → chunk-KZ73RL76.js} +111 -31
- package/dist/chunk-KZ73RL76.js.map +1 -0
- package/dist/{chunk-LWR4MXSS.js → chunk-LHLYWG5R.js} +10 -1
- package/dist/chunk-LHLYWG5R.js.map +1 -0
- package/dist/{chunk-L3EWO3UK.js → chunk-NJ7M2B6Z.js} +445 -84
- package/dist/chunk-NJ7M2B6Z.js.map +1 -0
- package/dist/{chunk-VCBFUSUZ.js → chunk-O4VXOBGF.js} +3 -3
- package/dist/{chunk-VCBFUSUZ.js.map → chunk-O4VXOBGF.js.map} +1 -1
- package/dist/{chunk-6EDV6YFE.js → chunk-OL6QVXGF.js} +121 -29
- package/dist/chunk-OL6QVXGF.js.map +1 -0
- package/dist/chunk-VZTQ73XT.js +86 -0
- package/dist/chunk-VZTQ73XT.js.map +1 -0
- package/dist/{chunk-EHSYZ4KP.js → chunk-WYA65Y3F.js} +16 -16
- package/dist/chunk-WYA65Y3F.js.map +1 -0
- package/dist/{chunk-FD72GJMK.js → chunk-XCNTXWBB.js} +274 -125
- package/dist/chunk-XCNTXWBB.js.map +1 -0
- package/dist/client.d.ts +1 -1
- package/dist/components/chat-composer.d.ts +7 -2
- package/dist/components/composer-transcription-control.d.ts +2 -0
- package/dist/components/composer.d.ts +14 -0
- package/dist/components/machines/machine-detail.d.ts +3 -1
- package/dist/components/machines-dashboard.d.ts +3 -3
- package/dist/components/message-timeline.d.ts +12 -2
- package/dist/components/sandbox-files.d.ts +2 -2
- package/dist/components/sandbox-terminal.d.ts +29 -0
- package/dist/components/sandbox-workspace.d.ts +5 -1
- package/dist/components/workspace-dock.d.ts +3 -1
- package/dist/composer.d.ts +2 -2
- package/dist/composer.js +6 -6
- package/dist/hooks/internal.d.ts +6 -0
- package/dist/hooks/use-codex-accounts.d.ts +1 -1
- package/dist/hooks/use-environments.d.ts +10 -4
- package/dist/hooks/use-machines.d.ts +7 -0
- package/dist/hooks/use-sandbox-files.d.ts +19 -0
- package/dist/hooks/use-sandbox-git.d.ts +7 -0
- package/dist/hooks/use-transcription.d.ts +2 -2
- package/dist/hooks/use-voice-input.d.ts +13 -4
- package/dist/index.d.ts +6 -5
- package/dist/index.js +904 -538
- package/dist/index.js.map +1 -1
- package/dist/lib/composer-responsive-context.d.ts +9 -0
- package/dist/lib/use-portal-token-style.d.ts +14 -3
- package/dist/machines.js +2 -2
- package/dist/{queue-surface-implementation-J4XJFKRW.js → queue-surface-implementation-U5JFG7HT.js} +19 -18
- package/dist/queue-surface-implementation-U5JFG7HT.js.map +1 -0
- package/dist/realtime/realtime-control.d.ts +9 -0
- package/dist/realtime.js +29 -32
- package/dist/realtime.js.map +1 -1
- package/dist/session-ui.js +5 -5
- package/dist/session.js +4 -4
- package/dist/timeline/activity-rail.d.ts +3 -2
- package/dist/timeline/index.d.ts +2 -2
- package/dist/timeline/parsers.d.ts +3 -3
- package/dist/timeline/registry.d.ts +3 -0
- package/dist/timeline/screenshot-lightbox.d.ts +1 -1
- package/dist/timeline/types.d.ts +4 -0
- package/dist/voice-recording-store.d.ts +8 -2
- package/package.json +20 -4
- package/src/client.ts +3 -0
- package/src/components/chat-composer.tsx +12 -2
- package/src/components/code-editor.tsx +1 -1
- package/src/components/command-palette.tsx +1 -1
- package/src/components/composer-transcription-control.tsx +41 -16
- package/src/components/composer.tsx +52 -26
- package/src/components/copy-button.tsx +2 -2
- package/src/components/desktop-viewer.tsx +2 -2
- package/src/components/file-browser.tsx +39 -17
- package/src/components/human-input-form.tsx +2 -2
- package/src/components/machine-card.tsx +1 -1
- package/src/components/machines/machine-detail.tsx +39 -15
- package/src/components/machines-dashboard.tsx +7 -6
- package/src/components/message-timeline.tsx +44 -11
- package/src/components/model-picker.tsx +2 -2
- package/src/components/model-policy-picker.tsx +16 -15
- package/src/components/queue-surface-implementation.tsx +20 -16
- package/src/components/queue-surface-state.tsx +2 -2
- package/src/components/sandbox-files.tsx +140 -60
- package/src/components/sandbox-terminal.tsx +24 -5
- package/src/components/sandbox-workspace.tsx +180 -142
- package/src/components/session-chrome.tsx +14 -14
- package/src/components/tooltip.tsx +13 -16
- package/src/components/user-message-body.tsx +1 -1
- package/src/components/workbench-changes.tsx +4 -4
- package/src/components/workspace-dock.tsx +13 -5
- package/src/composer.ts +3 -0
- package/src/hooks/internal.ts +132 -38
- package/src/hooks/use-codex-accounts.ts +4 -4
- package/src/hooks/use-environments.ts +24 -5
- package/src/hooks/use-file-attachments.ts +70 -32
- package/src/hooks/use-goal.ts +17 -5
- package/src/hooks/use-machines.ts +47 -1
- package/src/hooks/use-sandbox-files.ts +310 -59
- package/src/hooks/use-sandbox-git.ts +148 -41
- package/src/hooks/use-session-capabilities.ts +35 -24
- package/src/hooks/use-session-events.ts +18 -4
- package/src/hooks/use-transcription.ts +4 -14
- package/src/hooks/use-turn-queue.ts +17 -5
- package/src/hooks/use-voice-input.ts +481 -36
- package/src/hooks/use-windowed-sections.ts +9 -7
- package/src/index.ts +11 -6
- package/src/lib/composer-responsive-context.ts +15 -0
- package/src/lib/use-portal-token-style.ts +60 -15
- package/src/provider.tsx +52 -7
- package/src/realtime/dropdown-menu.tsx +15 -19
- package/src/realtime/realtime-control.tsx +18 -7
- package/src/timeline/activity-rail.tsx +15 -6
- package/src/timeline/index.ts +1 -1
- package/src/timeline/parsers.ts +33 -20
- package/src/timeline/projection.ts +23 -0
- package/src/timeline/registry.ts +7 -0
- package/src/timeline/screenshot-lightbox.tsx +15 -4
- package/src/timeline/shared.tsx +8 -8
- package/src/timeline/tool-renderers.tsx +166 -22
- package/src/timeline/turn-summary.tsx +6 -6
- package/src/timeline/types.ts +4 -0
- package/src/voice-recording-store.ts +15 -0
- package/styles/compiled.css +5065 -0
- package/styles/compiled.d.ts +2 -0
- package/styles/effective-tokens.css +27 -0
- package/styles/index.css +3 -0
- package/styles/responsive.css +196 -0
- package/styles/responsive.d.ts +2 -0
- package/styles/tokens.css +50 -17
- package/dist/chunk-6EDV6YFE.js.map +0 -1
- package/dist/chunk-EB67J347.js.map +0 -1
- package/dist/chunk-EHSYZ4KP.js.map +0 -1
- package/dist/chunk-FD72GJMK.js.map +0 -1
- package/dist/chunk-JB5725BI.js +0 -48
- package/dist/chunk-JB5725BI.js.map +0 -1
- package/dist/chunk-L3EWO3UK.js.map +0 -1
- package/dist/chunk-LWR4MXSS.js.map +0 -1
- package/dist/chunk-SRFUT2ZU.js.map +0 -1
- package/dist/chunk-U3CSCMWA.js.map +0 -1
- package/dist/queue-surface-implementation-J4XJFKRW.js.map +0 -1
- /package/dist/{chunk-HFO4ERGQ.js.map → chunk-FWXGSNXT.js.map} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FileAsset, FileResourceRef } from "@opengeni/sdk";
|
|
2
|
-
import { useCallback, useRef, useState } from "react";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
3
|
import {
|
|
4
4
|
useEmbeddedFileAttachments,
|
|
5
5
|
type EmbeddedFileAttachmentClientOverride,
|
|
@@ -87,12 +87,64 @@ export function useFileAttachments(
|
|
|
87
87
|
// Keep the source File per attachment id so a failed upload can be retried
|
|
88
88
|
// in place. Cleared on remove/clear so it never outlives its attachment.
|
|
89
89
|
const sources = useRef<Map<string, File>>(new Map());
|
|
90
|
+
// Object URLs must be owned synchronously: React may discard an attachment
|
|
91
|
+
// state update when its component unmounts in the same batch that minted the
|
|
92
|
+
// preview. The registry remains available to cleanup even before a render.
|
|
93
|
+
const previewUrls = useRef<Map<string, string>>(new Map());
|
|
94
|
+
const revokePreview = useCallback((id: string) => {
|
|
95
|
+
const previewUrl = previewUrls.current.get(id);
|
|
96
|
+
if (!previewUrl) return;
|
|
97
|
+
previewUrls.current.delete(id);
|
|
98
|
+
URL.revokeObjectURL(previewUrl);
|
|
99
|
+
}, []);
|
|
100
|
+
const revokeAllPreviews = useCallback(() => {
|
|
101
|
+
const urls = [...previewUrls.current.values()];
|
|
102
|
+
previewUrls.current.clear();
|
|
103
|
+
for (const previewUrl of urls) URL.revokeObjectURL(previewUrl);
|
|
104
|
+
}, []);
|
|
105
|
+
// Ready-file reconciliation uses functional state updaters, which React may
|
|
106
|
+
// evaluate during a concurrent render that later suspends or is abandoned.
|
|
107
|
+
// Diff only committed attachment sets here so URL revocation cannot run from
|
|
108
|
+
// render-phase code while the previously committed DOM still uses a preview.
|
|
109
|
+
const committedAttachmentIds = useRef<Set<string>>(new Set());
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
const currentIds = new Set(attachments.map((attachment) => attachment.id));
|
|
112
|
+
for (const id of committedAttachmentIds.current) {
|
|
113
|
+
if (!currentIds.has(id)) revokePreview(id);
|
|
114
|
+
}
|
|
115
|
+
committedAttachmentIds.current = currentIds;
|
|
116
|
+
}, [attachments, revokePreview]);
|
|
117
|
+
// Upload promises are not cancellable at this layer. Fence their settlements
|
|
118
|
+
// when the hook changes client/workspace or unmounts so an old tenant/session
|
|
119
|
+
// cannot mutate the next attachment queue (or an already-unmounted component).
|
|
120
|
+
const scopeGeneration = useRef(0);
|
|
121
|
+
const previousScope = useRef({ client, workspaceId });
|
|
122
|
+
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
const previous = previousScope.current;
|
|
125
|
+
if (previous.client === client && previous.workspaceId === workspaceId) return;
|
|
126
|
+
previousScope.current = { client, workspaceId };
|
|
127
|
+
scopeGeneration.current += 1;
|
|
128
|
+
sources.current.clear();
|
|
129
|
+
revokeAllPreviews();
|
|
130
|
+
setAttachments([]);
|
|
131
|
+
}, [client, revokeAllPreviews, workspaceId]);
|
|
132
|
+
|
|
133
|
+
useEffect(
|
|
134
|
+
() => () => {
|
|
135
|
+
scopeGeneration.current += 1;
|
|
136
|
+
sources.current.clear();
|
|
137
|
+
revokeAllPreviews();
|
|
138
|
+
},
|
|
139
|
+
[revokeAllPreviews],
|
|
140
|
+
);
|
|
90
141
|
|
|
91
142
|
// Run (or re-run) the upload for one already-tracked attachment id. Sets it
|
|
92
143
|
// back to `uploading`, then resolves to `ready` (with the asset) or `failed`
|
|
93
144
|
// (with the error message).
|
|
94
145
|
const startUpload = useCallback(
|
|
95
146
|
(id: string, file: File) => {
|
|
147
|
+
const generation = scopeGeneration.current;
|
|
96
148
|
void client
|
|
97
149
|
.uploadFile(workspaceId, {
|
|
98
150
|
filename: file.name || "file",
|
|
@@ -100,6 +152,7 @@ export function useFileAttachments(
|
|
|
100
152
|
data: file,
|
|
101
153
|
})
|
|
102
154
|
.then((asset) => {
|
|
155
|
+
if (scopeGeneration.current !== generation) return;
|
|
103
156
|
// Retry bytes are useful only until durable finalization succeeds.
|
|
104
157
|
// Drop the source File immediately; restored/ready attachments must
|
|
105
158
|
// never retain browser-local byte authority.
|
|
@@ -121,6 +174,7 @@ export function useFileAttachments(
|
|
|
121
174
|
);
|
|
122
175
|
})
|
|
123
176
|
.catch((error: unknown) => {
|
|
177
|
+
if (scopeGeneration.current !== generation) return;
|
|
124
178
|
setAttachments((current) =>
|
|
125
179
|
current.map((attachment) =>
|
|
126
180
|
attachment.id === id
|
|
@@ -143,6 +197,7 @@ export function useFileAttachments(
|
|
|
143
197
|
const id = crypto.randomUUID();
|
|
144
198
|
sources.current.set(id, file);
|
|
145
199
|
const previewUrl = isImage(file) ? URL.createObjectURL(file) : undefined;
|
|
200
|
+
if (previewUrl) previewUrls.current.set(id, previewUrl);
|
|
146
201
|
setAttachments((current) => [
|
|
147
202
|
...current,
|
|
148
203
|
{
|
|
@@ -232,11 +287,6 @@ export function useFileAttachments(
|
|
|
232
287
|
// only authority restored across page/device boundaries.
|
|
233
288
|
};
|
|
234
289
|
});
|
|
235
|
-
for (const [fileId, attachment] of existingReady) {
|
|
236
|
-
if (!incoming.has(fileId) && attachment.previewUrl) {
|
|
237
|
-
URL.revokeObjectURL(attachment.previewUrl);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
290
|
// A server restoration is authoritative for finalized assets, but an
|
|
241
291
|
// upload that has not finalized still belongs to the local actor. Keep
|
|
242
292
|
// those unresolved entries while replacing the ready set exactly.
|
|
@@ -246,46 +296,34 @@ export function useFileAttachments(
|
|
|
246
296
|
[workspaceId],
|
|
247
297
|
);
|
|
248
298
|
|
|
249
|
-
const remove = useCallback(
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
});
|
|
258
|
-
}, []);
|
|
299
|
+
const remove = useCallback(
|
|
300
|
+
(id: string) => {
|
|
301
|
+
sources.current.delete(id);
|
|
302
|
+
revokePreview(id);
|
|
303
|
+
setAttachments((current) => current.filter((attachment) => attachment.id !== id));
|
|
304
|
+
},
|
|
305
|
+
[revokePreview],
|
|
306
|
+
);
|
|
259
307
|
|
|
260
308
|
const removeReadyFiles = useCallback((fileIds: Iterable<string>) => {
|
|
261
309
|
const accepted = new Set(fileIds);
|
|
262
310
|
if (accepted.size === 0) return;
|
|
263
311
|
setAttachments((current) =>
|
|
264
312
|
current.filter((attachment) => {
|
|
265
|
-
|
|
313
|
+
return !(
|
|
266
314
|
attachment.status === "ready" &&
|
|
267
315
|
attachment.file !== undefined &&
|
|
268
|
-
accepted.has(attachment.file.id)
|
|
269
|
-
|
|
270
|
-
sources.current.delete(attachment.id);
|
|
271
|
-
if (attachment.previewUrl) URL.revokeObjectURL(attachment.previewUrl);
|
|
272
|
-
}
|
|
273
|
-
return !removeAccepted;
|
|
316
|
+
accepted.has(attachment.file.id)
|
|
317
|
+
);
|
|
274
318
|
}),
|
|
275
319
|
);
|
|
276
320
|
}, []);
|
|
277
321
|
|
|
278
322
|
const clear = useCallback(() => {
|
|
279
323
|
sources.current.clear();
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
URL.revokeObjectURL(attachment.previewUrl);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
return [];
|
|
287
|
-
});
|
|
288
|
-
}, []);
|
|
324
|
+
revokeAllPreviews();
|
|
325
|
+
setAttachments([]);
|
|
326
|
+
}, [revokeAllPreviews]);
|
|
289
327
|
|
|
290
328
|
return {
|
|
291
329
|
attachments,
|
package/src/hooks/use-goal.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { useEmbeddedGoal, type EmbeddedGoalClientOverride } from "../session-con
|
|
|
4
4
|
import {
|
|
5
5
|
useDebouncedCallback,
|
|
6
6
|
useMutationRunner,
|
|
7
|
+
usePageLiveActivity,
|
|
7
8
|
useSessionEventTrigger,
|
|
8
9
|
type SessionEventFeedOptions,
|
|
9
10
|
} from "./internal";
|
|
@@ -75,6 +76,7 @@ export function useGoal(
|
|
|
75
76
|
const enabled = (options.enabled ?? true) && Boolean(sessionId);
|
|
76
77
|
const sharedEvents = options.events;
|
|
77
78
|
const sharedFeed = sharedEvents !== undefined;
|
|
79
|
+
const pageLive = usePageLiveActivity();
|
|
78
80
|
const [goal, setGoal] = useState<SessionGoal | null>(null);
|
|
79
81
|
const [loading, setLoading] = useState(enabled);
|
|
80
82
|
const [error, setError] = useState<Error | null>(null);
|
|
@@ -116,7 +118,7 @@ export function useGoal(
|
|
|
116
118
|
setGoal(null);
|
|
117
119
|
setError(null);
|
|
118
120
|
}
|
|
119
|
-
if (!enabled) {
|
|
121
|
+
if (!enabled || !pageLive) {
|
|
120
122
|
setLoading(false);
|
|
121
123
|
return;
|
|
122
124
|
}
|
|
@@ -127,19 +129,29 @@ export function useGoal(
|
|
|
127
129
|
};
|
|
128
130
|
}
|
|
129
131
|
setLoading(true);
|
|
130
|
-
void load();
|
|
131
132
|
const pollIntervalMs = options.pollIntervalMs;
|
|
132
133
|
if (pollIntervalMs === undefined || pollIntervalMs <= 0) {
|
|
134
|
+
void load();
|
|
133
135
|
return () => {
|
|
134
136
|
generation.current += 1;
|
|
135
137
|
};
|
|
136
138
|
}
|
|
137
|
-
|
|
139
|
+
let cancelled = false;
|
|
140
|
+
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
141
|
+
const schedule = () => {
|
|
142
|
+
if (cancelled) return;
|
|
143
|
+
timer = setTimeout(() => {
|
|
144
|
+
timer = null;
|
|
145
|
+
void load().finally(schedule);
|
|
146
|
+
}, pollIntervalMs);
|
|
147
|
+
};
|
|
148
|
+
void load().finally(schedule);
|
|
138
149
|
return () => {
|
|
139
|
-
|
|
150
|
+
cancelled = true;
|
|
151
|
+
if (timer !== null) clearTimeout(timer);
|
|
140
152
|
generation.current += 1;
|
|
141
153
|
};
|
|
142
|
-
}, [load, enabled, workspaceId, sessionId, options.pollIntervalMs, sharedFeed]);
|
|
154
|
+
}, [load, enabled, pageLive, workspaceId, sessionId, options.pollIntervalMs, sharedFeed]);
|
|
143
155
|
|
|
144
156
|
const scheduleRefresh = useDebouncedCallback(() => void load());
|
|
145
157
|
useSessionEventTrigger(client, workspaceId, sessionId, isGoalRefreshEvent, scheduleRefresh, {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useCallback, useRef, useState } from "react";
|
|
2
|
+
import type { RemoveEnrollmentRequest, RemoveEnrollmentResponse } from "@opengeni/sdk";
|
|
2
3
|
import { useOpenGeni, type ClientOverride } from "../provider";
|
|
3
4
|
import { useMutationRunner, usePolledValue } from "./internal";
|
|
4
5
|
import type { MachinesResponse, MachineView, MetricSample } from "../types/machines";
|
|
@@ -31,6 +32,12 @@ export type MachinesClientLike = {
|
|
|
31
32
|
enrollmentId: string,
|
|
32
33
|
options?: { window?: "15m" | "1h" | "6h" | "24h" },
|
|
33
34
|
) => Promise<MetricSample[]>;
|
|
35
|
+
/** POST .../enrollments/:enrollmentId/revoke — workspace-admin removal. */
|
|
36
|
+
removeEnrollment?: (
|
|
37
|
+
workspaceId: string,
|
|
38
|
+
enrollmentId: string,
|
|
39
|
+
request?: RemoveEnrollmentRequest,
|
|
40
|
+
) => Promise<RemoveEnrollmentResponse>;
|
|
34
41
|
/**
|
|
35
42
|
* POST .../sessions/:sessionId/active-sandbox — swap the session's active
|
|
36
43
|
* sandbox to a machine. The default swap path; the real SDK client provides it.
|
|
@@ -69,6 +76,13 @@ export type UseMachinesResult = {
|
|
|
69
76
|
refresh: () => Promise<void>;
|
|
70
77
|
/** Attach/swap the session's active sandbox to a machine (returns the new pointer). */
|
|
71
78
|
attach: (sandboxId: string) => Promise<boolean>;
|
|
79
|
+
/** Remove a self-hosted enrollment while retaining history. */
|
|
80
|
+
remove: (
|
|
81
|
+
enrollmentId: string,
|
|
82
|
+
request?: RemoveEnrollmentRequest,
|
|
83
|
+
) => Promise<RemoveEnrollmentResponse | null>;
|
|
84
|
+
canRemove: boolean;
|
|
85
|
+
removingEnrollmentId: string | null;
|
|
72
86
|
/** Whether the host wired an attach/swap path (drives the card affordance). */
|
|
73
87
|
canAttach: boolean;
|
|
74
88
|
/** Fetch a downsampled metric series for one enrolled machine. */
|
|
@@ -128,6 +142,12 @@ export function useMachines(options: UseMachinesOptions = {}): UseMachinesResult
|
|
|
128
142
|
sandboxId: string | null;
|
|
129
143
|
}>(() => ({ identity: identityKey, sandboxId: null }));
|
|
130
144
|
const attachingSandboxId = attachState.identity === identityKey ? attachState.sandboxId : null;
|
|
145
|
+
const [removeState, setRemoveState] = useState<{
|
|
146
|
+
identity: string;
|
|
147
|
+
enrollmentId: string | null;
|
|
148
|
+
}>(() => ({ identity: identityKey, enrollmentId: null }));
|
|
149
|
+
const removingEnrollmentId =
|
|
150
|
+
removeState.identity === identityKey ? removeState.enrollmentId : null;
|
|
131
151
|
|
|
132
152
|
const data = loadedData ?? EMPTY;
|
|
133
153
|
// The swap is session-scoped: a host adapter (`attachMachine`) wins; otherwise
|
|
@@ -137,6 +157,7 @@ export function useMachines(options: UseMachinesOptions = {}): UseMachinesResult
|
|
|
137
157
|
sessionId !== undefined &&
|
|
138
158
|
(typeof machinesClient.attachMachine === "function" ||
|
|
139
159
|
typeof machinesClient.swapActiveSandbox === "function");
|
|
160
|
+
const canRemove = typeof machinesClient.removeEnrollment === "function";
|
|
140
161
|
|
|
141
162
|
const attach = useCallback(
|
|
142
163
|
async (sandboxId: string): Promise<boolean> => {
|
|
@@ -173,6 +194,28 @@ export function useMachines(options: UseMachinesOptions = {}): UseMachinesResult
|
|
|
173
194
|
[machinesClient, workspaceId],
|
|
174
195
|
);
|
|
175
196
|
|
|
197
|
+
const remove = useCallback(
|
|
198
|
+
async (
|
|
199
|
+
enrollmentId: string,
|
|
200
|
+
request: RemoveEnrollmentRequest = {},
|
|
201
|
+
): Promise<RemoveEnrollmentResponse | null> => {
|
|
202
|
+
if (!machinesClient.removeEnrollment) return null;
|
|
203
|
+
const ownedIdentity = identityKey;
|
|
204
|
+
setRemoveState({ identity: ownedIdentity, enrollmentId });
|
|
205
|
+
const result = await run(() =>
|
|
206
|
+
machinesClient.removeEnrollment!(workspaceId, enrollmentId, request),
|
|
207
|
+
);
|
|
208
|
+
if (identityRef.current === ownedIdentity) {
|
|
209
|
+
setRemoveState({ identity: ownedIdentity, enrollmentId: null });
|
|
210
|
+
if (result?.outcome === "removed" || result?.outcome === "already_removed") {
|
|
211
|
+
await refresh();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return result;
|
|
215
|
+
},
|
|
216
|
+
[machinesClient, workspaceId, identityKey, run, refresh],
|
|
217
|
+
);
|
|
218
|
+
|
|
176
219
|
return {
|
|
177
220
|
machines: data.machines,
|
|
178
221
|
activeSandboxId: data.activeSandboxId,
|
|
@@ -183,8 +226,11 @@ export function useMachines(options: UseMachinesOptions = {}): UseMachinesResult
|
|
|
183
226
|
attach,
|
|
184
227
|
canAttach,
|
|
185
228
|
fetchSeries,
|
|
186
|
-
attaching: mutating,
|
|
229
|
+
attaching: mutating && attachingSandboxId !== null,
|
|
187
230
|
attachingSandboxId,
|
|
231
|
+
remove,
|
|
232
|
+
canRemove,
|
|
233
|
+
removingEnrollmentId,
|
|
188
234
|
mutationError,
|
|
189
235
|
clearMutationError,
|
|
190
236
|
};
|