mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-09-19 17:12:47 +01:00
Merge branch 'master' into users/aisayas/sql-connectionstring-login
This commit is contained in:
Generated
+7
-7
@@ -13942,9 +13942,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/fast-uri": {
|
"node_modules/fast-uri": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz",
|
||||||
"integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
|
"integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -22533,9 +22533,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.15",
|
"version": "8.5.26",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
|
||||||
"integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
|
"integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -22553,7 +22553,7 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.12",
|
"nanoid": "^3.3.17",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
"source-map-js": "^1.2.1"
|
"source-map-js": "^1.2.1"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -74,8 +74,8 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Open Cassandra shell button", () => {
|
describe("Open Cassandra Shell button", () => {
|
||||||
const openCassandraShellBtnLabel = "Open Cassandra shell";
|
const openCassandraShellBtnLabel = "Open Cassandra Shell";
|
||||||
const selectedNodeState = useSelectedNode.getState();
|
const selectedNodeState = useSelectedNode.getState();
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
@@ -135,14 +135,14 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("Open Postgres and vCore Mongo buttons", () => {
|
describe("Open Postgres and vCore Mongo buttons", () => {
|
||||||
const openPostgresShellButtonLabel = "Open PSQL shell";
|
const openPostgresShellButtonLabel = "Open PSQL Shell";
|
||||||
const openVCoreMongoShellButtonLabel = "Open MongoDB (DocumentDB) shell";
|
const openVCoreMongoShellButtonLabel = "Open MongoDB (DocumentDB) Shell";
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
mockExplorer = {} as Explorer;
|
mockExplorer = {} as Explorer;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("creates Postgres shell button", () => {
|
it("creates Postgres Shell button", () => {
|
||||||
const buttons = CommandBarComponentButtonFactory.createPostgreButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createPostgreButtons(mockExplorer);
|
||||||
const openPostgresShellButton = buttons.find(
|
const openPostgresShellButton = buttons.find(
|
||||||
(button) => button.commandButtonLabel === openPostgresShellButtonLabel,
|
(button) => button.commandButtonLabel === openPostgresShellButtonLabel,
|
||||||
@@ -150,7 +150,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
expect(openPostgresShellButton).toBeDefined();
|
expect(openPostgresShellButton).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("creates vCore Mongo shell button", () => {
|
it("creates vCore Mongo Shell button", () => {
|
||||||
const buttons = CommandBarComponentButtonFactory.createVCoreMongoButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createVCoreMongoButtons(mockExplorer);
|
||||||
const openVCoreMongoShellButton = buttons.find(
|
const openVCoreMongoShellButton = buttons.find(
|
||||||
(button) => button.commandButtonLabel === openVCoreMongoShellButtonLabel,
|
(button) => button.commandButtonLabel === openVCoreMongoShellButtonLabel,
|
||||||
|
|||||||
@@ -513,7 +513,7 @@ function createOpenTerminalButtonByKind(
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const label = `Open ${terminalFriendlyName()} shell`;
|
const label = `Open ${terminalFriendlyName()} Shell`;
|
||||||
const tooltip =
|
const tooltip =
|
||||||
"This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks.";
|
"This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks.";
|
||||||
const isNativeAuthDisabled = terminalKind === ViewModels.TerminalKind.VCoreMongo && isVCoreMongoNativeAuthDisabled();
|
const isNativeAuthDisabled = terminalKind === ViewModels.TerminalKind.VCoreMongo && isVCoreMongoNativeAuthDisabled();
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
import { Terminal } from "xterm";
|
||||||
|
import { registerTerminalResizeHandler } from "./CloudShellTerminalCore";
|
||||||
|
import { resizeTerminal } from "./Data/CloudShellClient";
|
||||||
|
|
||||||
|
// Mock the CloudShell client so we can assert on backend resize calls without any network access.
|
||||||
|
jest.mock("./Data/CloudShellClient");
|
||||||
|
|
||||||
|
const mockResizeTerminal = resizeTerminal as jest.Mock;
|
||||||
|
|
||||||
|
const CONSOLE_URI = "https://shell.azure.com/console123";
|
||||||
|
const TERMINAL_ID = "terminal-id";
|
||||||
|
const DEBOUNCE_MS = 300;
|
||||||
|
|
||||||
|
type ResizeListener = (size: { cols: number; rows: number }) => void;
|
||||||
|
|
||||||
|
interface MockTerminal {
|
||||||
|
cols: number;
|
||||||
|
rows: number;
|
||||||
|
onResize: (listener: ResizeListener) => void;
|
||||||
|
emitResize: (size: { cols: number; rows: number }) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createMockTerminal = (cols: number, rows: number): MockTerminal => {
|
||||||
|
let listener: ResizeListener | undefined;
|
||||||
|
return {
|
||||||
|
cols,
|
||||||
|
rows,
|
||||||
|
onResize: (cb: ResizeListener) => {
|
||||||
|
listener = cb;
|
||||||
|
},
|
||||||
|
emitResize: (size: { cols: number; rows: number }) => listener?.(size),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const registerHandler = (terminal: MockTerminal): void =>
|
||||||
|
registerTerminalResizeHandler(terminal as unknown as Terminal, CONSOLE_URI, TERMINAL_ID);
|
||||||
|
|
||||||
|
describe("registerTerminalResizeHandler", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
mockResizeTerminal.mockReset();
|
||||||
|
mockResizeTerminal.mockResolvedValue(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllTimers();
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not notify the backend when the dimensions are unchanged", () => {
|
||||||
|
const terminal = createMockTerminal(80, 24);
|
||||||
|
registerHandler(terminal);
|
||||||
|
|
||||||
|
terminal.emitResize({ cols: 80, rows: 24 });
|
||||||
|
jest.advanceTimersByTime(DEBOUNCE_MS);
|
||||||
|
|
||||||
|
expect(mockResizeTerminal).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("notifies the backend with the new dimensions after the debounce interval", () => {
|
||||||
|
const terminal = createMockTerminal(80, 24);
|
||||||
|
registerHandler(terminal);
|
||||||
|
|
||||||
|
terminal.emitResize({ cols: 120, rows: 40 });
|
||||||
|
// Nothing should be sent until the debounce interval elapses.
|
||||||
|
expect(mockResizeTerminal).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
jest.advanceTimersByTime(DEBOUNCE_MS);
|
||||||
|
|
||||||
|
expect(mockResizeTerminal).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockResizeTerminal).toHaveBeenCalledWith(CONSOLE_URI, TERMINAL_ID, { cols: 120, rows: 40 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("debounces rapid resizes into a single call with the latest dimensions", () => {
|
||||||
|
const terminal = createMockTerminal(80, 24);
|
||||||
|
registerHandler(terminal);
|
||||||
|
|
||||||
|
terminal.emitResize({ cols: 100, rows: 30 });
|
||||||
|
terminal.emitResize({ cols: 110, rows: 35 });
|
||||||
|
terminal.emitResize({ cols: 120, rows: 40 });
|
||||||
|
|
||||||
|
jest.advanceTimersByTime(DEBOUNCE_MS);
|
||||||
|
|
||||||
|
expect(mockResizeTerminal).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockResizeTerminal).toHaveBeenCalledWith(CONSOLE_URI, TERMINAL_ID, { cols: 120, rows: 40 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sends a separate call for each resize that is spaced beyond the debounce interval", () => {
|
||||||
|
const terminal = createMockTerminal(80, 24);
|
||||||
|
registerHandler(terminal);
|
||||||
|
|
||||||
|
terminal.emitResize({ cols: 100, rows: 30 });
|
||||||
|
jest.advanceTimersByTime(DEBOUNCE_MS);
|
||||||
|
|
||||||
|
terminal.emitResize({ cols: 120, rows: 40 });
|
||||||
|
jest.advanceTimersByTime(DEBOUNCE_MS);
|
||||||
|
|
||||||
|
expect(mockResizeTerminal).toHaveBeenCalledTimes(2);
|
||||||
|
expect(mockResizeTerminal).toHaveBeenNthCalledWith(1, CONSOLE_URI, TERMINAL_ID, { cols: 100, rows: 30 });
|
||||||
|
expect(mockResizeTerminal).toHaveBeenNthCalledWith(2, CONSOLE_URI, TERMINAL_ID, { cols: 120, rows: 40 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("swallows backend resize errors and logs a warning", async () => {
|
||||||
|
const warnSpy = jest.spyOn(console, "warn").mockImplementation(() => undefined);
|
||||||
|
mockResizeTerminal.mockRejectedValueOnce(new Error("boom"));
|
||||||
|
const terminal = createMockTerminal(80, 24);
|
||||||
|
registerHandler(terminal);
|
||||||
|
|
||||||
|
terminal.emitResize({ cols: 120, rows: 40 });
|
||||||
|
jest.advanceTimersByTime(DEBOUNCE_MS);
|
||||||
|
// Flush the microtask queue so the rejected promise's .catch handler runs.
|
||||||
|
await Promise.resolve();
|
||||||
|
|
||||||
|
expect(mockResizeTerminal).toHaveBeenCalledTimes(1);
|
||||||
|
expect(warnSpy).toHaveBeenCalledWith("CloudShell: failed to resize backend terminal", expect.any(Error));
|
||||||
|
|
||||||
|
warnSpy.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
provisionConsole,
|
provisionConsole,
|
||||||
putEphemeralUserSettings,
|
putEphemeralUserSettings,
|
||||||
registerCloudShellProvider,
|
registerCloudShellProvider,
|
||||||
|
resizeTerminal,
|
||||||
verifyCloudShellProviderRegistration,
|
verifyCloudShellProviderRegistration,
|
||||||
} from "./Data/CloudShellClient";
|
} from "./Data/CloudShellClient";
|
||||||
import { CloudShellProviderInfo, ProvisionConsoleResponse } from "./Models/DataModels";
|
import { CloudShellProviderInfo, ProvisionConsoleResponse } from "./Models/DataModels";
|
||||||
@@ -26,6 +27,7 @@ const DEFAULT_FAIRFAX_CLOUDSHELL_REGION = "usgovvirginia";
|
|||||||
const POLLING_INTERVAL_MS = 2000;
|
const POLLING_INTERVAL_MS = 2000;
|
||||||
const MAX_RETRY_COUNT = 10;
|
const MAX_RETRY_COUNT = 10;
|
||||||
const MAX_PING_COUNT = 120 * 60; // 120 minutes (60 seconds/minute)
|
const MAX_PING_COUNT = 120 * 60; // 120 minutes (60 seconds/minute)
|
||||||
|
const TERMINAL_RESIZE_DEBOUNCE_MS = 300;
|
||||||
|
|
||||||
let pingCount = 0;
|
let pingCount = 0;
|
||||||
let keepAliveID: NodeJS.Timeout = null;
|
let keepAliveID: NodeJS.Timeout = null;
|
||||||
@@ -96,6 +98,7 @@ export const startCloudShellTerminal = async (terminal: Terminal, shellType: Ter
|
|||||||
socketUri?: string;
|
socketUri?: string;
|
||||||
provisionConsoleResponse?: ProvisionConsoleResponse;
|
provisionConsoleResponse?: ProvisionConsoleResponse;
|
||||||
targetUri?: string;
|
targetUri?: string;
|
||||||
|
terminalId?: string;
|
||||||
} = await provisionCloudShellSession(resolvedRegion, terminal);
|
} = await provisionCloudShellSession(resolvedRegion, terminal);
|
||||||
|
|
||||||
if (!sessionDetails.socketUri) {
|
if (!sessionDetails.socketUri) {
|
||||||
@@ -108,6 +111,14 @@ export const startCloudShellTerminal = async (terminal: Terminal, shellType: Ter
|
|||||||
// Configure WebSocket connection with shell-specific commands
|
// Configure WebSocket connection with shell-specific commands
|
||||||
const socket = await establishTerminalConnection(terminal, shellHandler, sessionDetails.socketUri);
|
const socket = await establishTerminalConnection(terminal, shellHandler, sessionDetails.socketUri);
|
||||||
|
|
||||||
|
// Keep the backend PTY size in sync with the frontend terminal. Without this, resizing the
|
||||||
|
// browser window only re-fits the local xterm while the remote shell keeps its original
|
||||||
|
// column count, causing typed input to wrap/break at the wrong column.
|
||||||
|
const consoleUri = sessionDetails.provisionConsoleResponse?.properties?.uri;
|
||||||
|
if (consoleUri && sessionDetails.terminalId) {
|
||||||
|
registerTerminalResizeHandler(terminal, consoleUri, sessionDetails.terminalId);
|
||||||
|
}
|
||||||
|
|
||||||
TelemetryProcessor.traceSuccess(
|
TelemetryProcessor.traceSuccess(
|
||||||
Action.CloudShellTerminalSession,
|
Action.CloudShellTerminalSession,
|
||||||
{
|
{
|
||||||
@@ -159,13 +170,46 @@ export const determineCloudShellRegion = (): string => {
|
|||||||
return getNormalizedRegion(userContext.databaseAccount?.location, defaultRegion);
|
return getNormalizedRegion(userContext.databaseAccount?.location, defaultRegion);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a debounced handler that notifies the CloudShell backend whenever the frontend
|
||||||
|
* terminal is resized (e.g. after a browser window resize triggers FitAddon.fit()), keeping the
|
||||||
|
* remote PTY column/row count in sync with what the user sees.
|
||||||
|
*/
|
||||||
|
export const registerTerminalResizeHandler = (terminal: Terminal, consoleUri: string, terminalId: string): void => {
|
||||||
|
let debounceTimer: NodeJS.Timeout | null = null;
|
||||||
|
let lastCols = terminal.cols;
|
||||||
|
let lastRows = terminal.rows;
|
||||||
|
|
||||||
|
terminal.onResize(({ cols, rows }) => {
|
||||||
|
if (cols === lastCols && rows === lastRows) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastCols = cols;
|
||||||
|
lastRows = rows;
|
||||||
|
|
||||||
|
if (debounceTimer) {
|
||||||
|
clearTimeout(debounceTimer);
|
||||||
|
}
|
||||||
|
debounceTimer = setTimeout(() => {
|
||||||
|
resizeTerminal(consoleUri, terminalId, { cols, rows }).catch((err) => {
|
||||||
|
console.warn("CloudShell: failed to resize backend terminal", err);
|
||||||
|
});
|
||||||
|
}, TERMINAL_RESIZE_DEBOUNCE_MS);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provisions a CloudShell session
|
* Provisions a CloudShell session
|
||||||
*/
|
*/
|
||||||
export const provisionCloudShellSession = async (
|
export const provisionCloudShellSession = async (
|
||||||
resolvedRegion: string,
|
resolvedRegion: string,
|
||||||
terminal: Terminal,
|
terminal: Terminal,
|
||||||
): Promise<{ socketUri?: string; provisionConsoleResponse?: ProvisionConsoleResponse; targetUri?: string }> => {
|
): Promise<{
|
||||||
|
socketUri?: string;
|
||||||
|
provisionConsoleResponse?: ProvisionConsoleResponse;
|
||||||
|
targetUri?: string;
|
||||||
|
terminalId?: string;
|
||||||
|
}> => {
|
||||||
// Apply user settings
|
// Apply user settings
|
||||||
await putEphemeralUserSettings(userContext.subscriptionId, resolvedRegion);
|
await putEphemeralUserSettings(userContext.subscriptionId, resolvedRegion);
|
||||||
|
|
||||||
@@ -217,7 +261,7 @@ export const provisionCloudShellSession = async (
|
|||||||
socketUri = `wss://${targetUriBodyArr[0]}/$hc/${targetUriBodyArr[1]}/terminals/${termId}`;
|
socketUri = `wss://${targetUriBodyArr[0]}/$hc/${targetUriBodyArr[1]}/terminals/${termId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { socketUri, provisionConsoleResponse, targetUri };
|
return { socketUri, provisionConsoleResponse, targetUri, terminalId: termId };
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
provisionConsole,
|
provisionConsole,
|
||||||
putEphemeralUserSettings,
|
putEphemeralUserSettings,
|
||||||
registerCloudShellProvider,
|
registerCloudShellProvider,
|
||||||
|
resizeTerminal,
|
||||||
verifyCloudShellProviderRegistration,
|
verifyCloudShellProviderRegistration,
|
||||||
} from "./CloudShellClient";
|
} from "./CloudShellClient";
|
||||||
|
|
||||||
@@ -340,4 +341,66 @@ describe("CloudShellClient", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("resizeTerminal", () => {
|
||||||
|
it("should call fetch with correct parameters", async () => {
|
||||||
|
const consoleUri = "https://shell.azure.com/console123";
|
||||||
|
const terminalId = "terminal-id";
|
||||||
|
const size = { rows: 40, cols: 120 };
|
||||||
|
|
||||||
|
global.fetch = jest.fn().mockImplementationOnce(() => {
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
json: jest.fn().mockResolvedValue({}),
|
||||||
|
text: jest.fn().mockResolvedValue(""),
|
||||||
|
headers: new Headers(),
|
||||||
|
} as unknown as Promise<Response>;
|
||||||
|
});
|
||||||
|
|
||||||
|
await resizeTerminal(consoleUri, terminalId, size);
|
||||||
|
|
||||||
|
expect(global.fetch).toHaveBeenCalledWith(
|
||||||
|
"https://shell.azure.com/console123/terminals/terminal-id/size?cols=120&rows=40&version=2019-01-01",
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Content-Length": "2",
|
||||||
|
Authorization: "Bearer mock-token",
|
||||||
|
"x-ms-client-request-id": "mocked-uuid",
|
||||||
|
"Accept-Language": "en-US",
|
||||||
|
},
|
||||||
|
body: "{}",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle errors when terminal resize fails", async () => {
|
||||||
|
const consoleUri = "https://shell.azure.com/console123";
|
||||||
|
const terminalId = "terminal-id";
|
||||||
|
const size = { rows: 40, cols: 120 };
|
||||||
|
|
||||||
|
global.fetch = jest.fn().mockImplementationOnce(() => {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
status: 500,
|
||||||
|
statusText: "Internal Server Error",
|
||||||
|
json: jest.fn().mockRejectedValue(new Error("Failed to parse JSON")),
|
||||||
|
text: jest.fn().mockResolvedValue("Server Error"),
|
||||||
|
headers: new Headers(),
|
||||||
|
} as unknown as Promise<Response>;
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(resizeTerminal(consoleUri, terminalId, size)).rejects.toThrow(
|
||||||
|
"Failed to resize terminal: 500 Internal Server Error",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(global.fetch).toHaveBeenCalledWith(
|
||||||
|
"https://shell.azure.com/console123/terminals/terminal-id/size?cols=120&rows=40&version=2019-01-01",
|
||||||
|
expect.any(Object),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -125,3 +125,27 @@ export const connectTerminal = async (
|
|||||||
|
|
||||||
return resp.json();
|
return resp.json();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const resizeTerminal = async (
|
||||||
|
consoleUri: string,
|
||||||
|
terminalId: string,
|
||||||
|
size: { rows: number; cols: number },
|
||||||
|
): Promise<void> => {
|
||||||
|
const targetUri = consoleUri + `/terminals/${terminalId}/size?cols=${size.cols}&rows=${size.rows}&version=2019-01-01`;
|
||||||
|
const resp = await fetch(targetUri, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Content-Length": "2",
|
||||||
|
Authorization: userContext.authorizationToken,
|
||||||
|
"x-ms-client-request-id": uuidv4(),
|
||||||
|
"Accept-Language": getLocale(),
|
||||||
|
},
|
||||||
|
body: "{}", // empty body is necessary
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!resp.ok) {
|
||||||
|
throw new Error(`Failed to resize terminal: ${resp.status} ${resp.statusText}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user