diff --git a/web/packages/teleport/src/Assist/Chat/ChatItem/Action/RunAction.tsx b/web/packages/teleport/src/Assist/Chat/ChatItem/Action/RunAction.tsx
index f12a2fbaadd1d..d65a9babd04da 100644
--- a/web/packages/teleport/src/Assist/Chat/ChatItem/Action/RunAction.tsx
+++ b/web/packages/teleport/src/Assist/Chat/ChatItem/Action/RunAction.tsx
@@ -75,6 +75,10 @@ interface RawPayload {
payload: string;
}
+interface SessionData {
+ session: { server_id: string };
+}
+
class assistClient extends EventEmitterWebAuthnSender {
private readonly ws: WebSocket;
readonly proto: Protobuf = new Protobuf();
@@ -89,11 +93,32 @@ class assistClient extends EventEmitterWebAuthnSender {
this.ws = new WebSocket(url);
this.ws.binaryType = 'arraybuffer';
+ this.ws.onclose = () => {
+ setState(state => {
+ return state.map(n => ({
+ ...n,
+ stdout: n.stdout || '',
+ status: RunActionStatus.Finished,
+ }));
+ });
+ };
+
this.ws.onmessage = event => {
const uintArray = new Uint8Array(event.data);
const msg = this.proto.decode(uintArray);
switch (msg.type) {
+ case MessageTypeEnum.SESSION_DATA:
+ const sessionData = JSON.parse(msg.payload) as SessionData;
+ setState(state => {
+ state.push({
+ nodeId: sessionData.session.server_id,
+ status: RunActionStatus.Connecting,
+ });
+ return state;
+ });
+ break;
+
case MessageTypeEnum.RAW:
const data = JSON.parse(msg.payload) as RawPayload;
const payload = atob(data.payload);
@@ -245,11 +270,14 @@ function NodeOutput(props: NodeOutputProps) {
)}
- {props.state.stdout && (
- {props.state.stdout}
- {props.state.stdout}
+
Empty output.
) : ( )} diff --git a/web/packages/teleport/src/Assist/contexts/messages.tsx b/web/packages/teleport/src/Assist/contexts/messages.tsx index 4b1b05cc1fb59..064a721f50be2 100644 --- a/web/packages/teleport/src/Assist/contexts/messages.tsx +++ b/web/packages/teleport/src/Assist/contexts/messages.tsx @@ -37,6 +37,8 @@ import cfg from 'teleport/config'; import { ApiError } from 'teleport/services/api/parseError'; +import { EventType } from 'teleport/lib/term/enums'; + import { Author, ExecuteRemoteCommandContent, @@ -84,6 +86,12 @@ interface PartialMessagePayload { idx: number; } +interface ExecEvent { + event: EventType.EXEC; + exitError?: string; +} +type SessionEvent = ExecEvent | { event: string }; + const convertToQuery = (cmd: ExecuteRemoteCommandPayload): string => { let query = ''; @@ -161,6 +169,10 @@ export const remoteCommandToMessage = async ( } }; +function isExecEvent(e: SessionEvent): e is ExecEvent { + return e.event == EventType.EXEC; +} + async function convertServerMessage( message: ServerMessage, clusterId: string @@ -221,16 +233,29 @@ async function convertServerMessage( sid: payload.session_id, }); - // The offset here is set base on A/B test that was run between me, myself and I. - const resp = await api.fetch(sessionUrl + '/stream?offset=0&bytes=4096', { - Accept: 'text/plain', - 'Content-Type': 'text/plain; charset=utf-8', - }); + const eventsResp = await api.fetch(sessionUrl + '/events'); + const sessionExists = eventsResp.status === 200; + const eventsData = (await eventsResp.json()) as { + events: SessionEvent[]; + }; + const execEvent = eventsData.events.find(isExecEvent); let msg; let errorMsg; - if (resp.status === 200) { - msg = await resp.text(); + if (sessionExists) { + // The offset here is set base on A/B test that was run between me, myself and I. + const stream = await api.fetch( + sessionUrl + '/stream?offset=0&bytes=4096', + { + Accept: 'text/plain', + 'Content-Type': 'text/plain; charset=utf-8', + } + ); + if (stream.status === 200) { + msg = await stream.text(); + } else { + msg = execEvent?.exitError || ''; // empty output handled in