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 !== undefined && + (props.state.stdout === '' ? ( + 'Empty output.' + ) : ( + +
{props.state.stdout}
+
+ ))} ); } diff --git a/web/packages/teleport/src/Assist/Chat/ChatItem/ChatItem.tsx b/web/packages/teleport/src/Assist/Chat/ChatItem/ChatItem.tsx index 5aab8eeada780..f58f7daf8fcb7 100644 --- a/web/packages/teleport/src/Assist/Chat/ChatItem/ChatItem.tsx +++ b/web/packages/teleport/src/Assist/Chat/ChatItem/ChatItem.tsx @@ -251,6 +251,8 @@ export function ChatItem(props: ChatItemProps) { {props.message.content.errorMsg ? ( {props.message.content.errorMsg} + ) : props.message.content.payload === '' ? ( +

Empty output.

) : ( {props.message.content.payload} )} 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 + } } else { errorMsg = 'No session recording. The command execution failed.'; } diff --git a/web/packages/teleport/src/lib/term/enums.ts b/web/packages/teleport/src/lib/term/enums.ts index b1cd690816765..dd422f995627f 100644 --- a/web/packages/teleport/src/lib/term/enums.ts +++ b/web/packages/teleport/src/lib/term/enums.ts @@ -18,6 +18,7 @@ export enum EventType { START = 'session.start', JOIN = 'session.join', END = 'session.end', + EXEC = 'exec', PRINT = 'print', RESIZE = 'resize', FILE_TRANSFER_REQUEST = 'file_transfer_request',