Skip to content

Commit 4b6af7f

Browse files
committed
feat: add confirmation dialog, split auto/recommended SSH defaults
1 parent ead3b59 commit 4b6af7f

3 files changed

Lines changed: 93 additions & 43 deletions

File tree

src/commands.ts

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ import { type Logger } from "./logging/logger";
2424
import { type LoginCoordinator } from "./login/loginCoordinator";
2525
import { withProgress } from "./progress";
2626
import { maybeAskAgent, maybeAskUrl } from "./promptUtils";
27-
import { RECOMMENDED_SSH_SETTINGS } from "./remote/userSettings";
27+
import {
28+
RECOMMENDED_SSH_SETTINGS,
29+
applySettingOverrides,
30+
} from "./remote/userSettings";
2831
import { escapeCommandArg, toRemoteAuthority, toSafeHost } from "./util";
2932
import { vscodeProposed } from "./vscodeProposed";
3033
import {
@@ -315,19 +318,41 @@ export class Commands {
315318
* Apply recommended SSH settings for reliable Coder workspace connections.
316319
*/
317320
public async applyRecommendedSettings(): Promise<void> {
318-
const config = vscode.workspace.getConfiguration();
319321
const entries = Object.entries(RECOMMENDED_SSH_SETTINGS);
320-
for (const [key, setting] of entries) {
321-
await config.update(
322-
key,
323-
setting.value,
324-
vscode.ConfigurationTarget.Global,
325-
);
322+
const summary = entries.map(([, s]) => s.label).join("\n");
323+
const confirm = await vscodeProposed.window.showWarningMessage(
324+
"Apply Recommended SSH Settings",
325+
{
326+
useCustom: true,
327+
modal: true,
328+
detail: summary,
329+
},
330+
"Apply",
331+
);
332+
if (confirm !== "Apply") {
333+
return;
326334
}
327-
const summary = entries.map(([, s]) => s.label).join(", ");
328-
vscode.window.showInformationMessage(
329-
`Applied recommended SSH settings: ${summary}`,
335+
336+
const overrides = entries.map(([key, setting]) => ({
337+
key,
338+
value: setting.value,
339+
}));
340+
await applySettingOverrides(
341+
this.pathResolver.getUserSettingsPath(),
342+
overrides,
343+
this.logger,
330344
);
345+
if (this.remoteWorkspaceClient) {
346+
const action = await vscode.window.showInformationMessage(
347+
"Applied recommended SSH settings. Reload the window for changes to take effect.",
348+
"Reload Window",
349+
);
350+
if (action === "Reload Window") {
351+
await vscode.commands.executeCommand("workbench.action.reloadWindow");
352+
}
353+
} else {
354+
vscode.window.showInformationMessage("Applied recommended SSH settings.");
355+
}
331356
}
332357

333358
/**

src/remote/userSettings.ts

Lines changed: 45 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { formatDuration, intervalToDuration } from "date-fns";
12
import * as jsonc from "jsonc-parser";
23
import * as fs from "node:fs/promises";
34

@@ -15,25 +16,45 @@ interface RecommendedSetting {
1516
readonly label: string;
1617
}
1718

19+
function recommended(
20+
shortName: string,
21+
value: number | null,
22+
): RecommendedSetting {
23+
if (value === null) {
24+
return { value, label: `${shortName}: max allowed` };
25+
}
26+
const humanized = formatDuration(
27+
intervalToDuration({ start: 0, end: value * 1000 }),
28+
);
29+
return { value, label: `${shortName}: ${humanized}` };
30+
}
31+
32+
/** Applied by the "Apply Recommended SSH Settings" command. */
1833
export const RECOMMENDED_SSH_SETTINGS = {
19-
"remote.SSH.connectTimeout": {
20-
value: 1800,
21-
label: "Connect Timeout: 1800s (30 min)",
22-
},
23-
"remote.SSH.reconnectionGraceTime": {
24-
value: 28800,
25-
label: "Reconnection Grace Time: 28800s (8 hours)",
26-
},
27-
"remote.SSH.serverShutdownTimeout": {
28-
value: 28800,
29-
label: "Server Shutdown Timeout: 28800s (8 hours)",
30-
},
31-
"remote.SSH.maxReconnectionAttempts": {
32-
value: null,
33-
label: "Max Reconnection Attempts: max allowed",
34-
},
34+
"remote.SSH.connectTimeout": recommended("Connect Timeout", 1800),
35+
"remote.SSH.reconnectionGraceTime": recommended(
36+
"Reconnection Grace Time",
37+
86400,
38+
),
39+
"remote.SSH.serverShutdownTimeout": recommended(
40+
"Server Shutdown Timeout",
41+
86400,
42+
),
43+
"remote.SSH.maxReconnectionAttempts": recommended(
44+
"Max Reconnection Attempts",
45+
null,
46+
),
3547
} as const satisfies Record<string, RecommendedSetting>;
3648

49+
type SshSettingKey = keyof typeof RECOMMENDED_SSH_SETTINGS;
50+
51+
/** Defaults set during connection when the user hasn't configured a value. */
52+
const AUTO_SETUP_DEFAULTS = {
53+
"remote.SSH.reconnectionGraceTime": 28800, // 8h
54+
"remote.SSH.serverShutdownTimeout": 28800, // 8h
55+
"remote.SSH.maxReconnectionAttempts": null, // max allowed
56+
} as const satisfies Partial<Record<SshSettingKey, number | null>>;
57+
3758
/**
3859
* Build the list of VS Code setting overrides needed for a remote SSH
3960
* connection to a Coder workspace.
@@ -58,25 +79,17 @@ export function buildSshOverrides(
5879
}
5980

6081
// Default 15s is too short for startup scripts; enforce a minimum.
61-
const minConnTimeout =
62-
RECOMMENDED_SSH_SETTINGS["remote.SSH.connectTimeout"].value;
63-
const connTimeout = config.get<number>("remote.SSH.connectTimeout");
64-
if (!connTimeout || connTimeout < minConnTimeout) {
65-
overrides.push({
66-
key: "remote.SSH.connectTimeout",
67-
value: minConnTimeout,
68-
});
82+
const connTimeoutKey: SshSettingKey = "remote.SSH.connectTimeout";
83+
const { value: minConnTimeout } = RECOMMENDED_SSH_SETTINGS[connTimeoutKey];
84+
const connTimeout = config.get<number>(connTimeoutKey);
85+
if (minConnTimeout && (!connTimeout || connTimeout < minConnTimeout)) {
86+
overrides.push({ key: connTimeoutKey, value: minConnTimeout });
6987
}
7088

71-
// Set recommended defaults for settings the user hasn't configured.
72-
const setIfUndefined = [
73-
"remote.SSH.reconnectionGraceTime",
74-
"remote.SSH.serverShutdownTimeout",
75-
"remote.SSH.maxReconnectionAttempts",
76-
] as const;
77-
for (const key of setIfUndefined) {
89+
// Set conservative defaults for settings the user hasn't configured.
90+
for (const [key, value] of Object.entries(AUTO_SETUP_DEFAULTS)) {
7891
if (config.get(key) === undefined) {
79-
overrides.push({ key, value: RECOMMENDED_SSH_SETTINGS[key].value });
92+
overrides.push({ key, value });
8093
}
8194
}
8295

test/unit/remote/userSettings.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,18 @@ describe("applySettingOverrides", () => {
229229
expect(raw).toContain('"remote.SSH.remotePlatform"');
230230
});
231231

232+
it("writes null values literally instead of deleting the key", async () => {
233+
const ok = await applySettingOverrides(
234+
settingsPath,
235+
[{ key: "remote.SSH.maxReconnectionAttempts", value: null }],
236+
logger,
237+
);
238+
239+
expect(ok).toBe(true);
240+
const raw = await fsPromises.readFile(settingsPath, "utf8");
241+
expect(raw).toContain('"remote.SSH.maxReconnectionAttempts": null');
242+
});
243+
232244
it("returns false and logs warning when write fails", async () => {
233245
vol.fromJSON({ [settingsPath]: "{}" });
234246
const writeSpy = vi

0 commit comments

Comments
 (0)