mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 22:55:24 +00:00
feat: move terminal settings, look and ssh tools into ssh-terminal plugin
This commit is contained in:
1 parent
ffa872ebd3
commit
d9bba10612
242 files changed
+25071
-7207
No files matched your search
@@ -9,6 +9,7 @@ import {
|
||||
type TestDb,
|
||||
} from "@termix/plugin-sdk/testing";
|
||||
import type { PluginManifest } from "@termix/plugin-sdk/manifest";
|
||||
import type { PluginHostSummary } from "@termix/plugin-sdk/backend";
|
||||
import manifestJson from "../../manifest.json";
|
||||
|
||||
export const pluginDir = fileURLToPath(new URL("../..", import.meta.url));
|
||||
@@ -32,7 +33,11 @@ export interface TestServer {
|
||||
* the way core mounts it, with the acting user taken from a test header.
|
||||
*/
|
||||
export async function startServer(
|
||||
options: { permissions?: string[]; settings?: Record<string, unknown> } = {},
|
||||
options: {
|
||||
permissions?: string[];
|
||||
settings?: Record<string, unknown>;
|
||||
hosts?: PluginHostSummary[];
|
||||
} = {},
|
||||
): Promise<TestServer> {
|
||||
const db = await createTestDb(pluginDir);
|
||||
for (const user of ["user-1", "user-2"]) {
|
||||
@@ -51,6 +56,7 @@ export async function startServer(
|
||||
router: () => (router = express.Router()),
|
||||
permissions: options.permissions,
|
||||
settings: options.settings,
|
||||
hosts: options.hosts,
|
||||
});
|
||||
|
||||
const { activate } = await import("../../src/backend/index.js");
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
hostImportNormalizer,
|
||||
hostPayloadLegacy,
|
||||
} from "../../src/backend/host-import";
|
||||
|
||||
describe("hostImportNormalizer", () => {
|
||||
it("reads a 2.8 export's terminalConfig into the host settings", () => {
|
||||
expect(
|
||||
hostImportNormalizer({
|
||||
enableTerminal: false,
|
||||
terminalConfig: {
|
||||
fontSize: 16,
|
||||
autoTmux: true,
|
||||
keepaliveInterval: 30,
|
||||
startupSnippetId: 2,
|
||||
},
|
||||
}),
|
||||
).toEqual({
|
||||
enableTerminal: false,
|
||||
inheritAppearance: false,
|
||||
fontSize: 16,
|
||||
autoTmux: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("reads the raw JSON a SQLite export carries", () => {
|
||||
expect(
|
||||
hostImportNormalizer({
|
||||
terminalConfig: JSON.stringify({ localEcho: "on" }),
|
||||
}),
|
||||
).toEqual({ localEcho: "on" });
|
||||
});
|
||||
|
||||
it("leaves terminalConfig alone when the export carries the plugin's own values", () => {
|
||||
expect(
|
||||
hostImportNormalizer({
|
||||
pluginSettings: { "ssh-terminal": { autoTmux: false } },
|
||||
terminalConfig: { autoTmux: true },
|
||||
}),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("imports nothing from a row without terminal values", () => {
|
||||
expect(hostImportNormalizer({ name: "x" })).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("hostPayloadLegacy", () => {
|
||||
it("puts the settings back in the 2.8 terminalConfig shape", () => {
|
||||
const { terminalConfig } = hostPayloadLegacy({
|
||||
autoTmux: true,
|
||||
inheritAppearance: true,
|
||||
fontSize: 20,
|
||||
}) as { terminalConfig: Record<string, unknown> };
|
||||
expect(terminalConfig.autoTmux).toBe(true);
|
||||
expect(terminalConfig).not.toHaveProperty("fontSize");
|
||||
expect(terminalConfig).not.toHaveProperty("localEcho");
|
||||
});
|
||||
|
||||
it("includes the look of a host that has its own", () => {
|
||||
const { terminalConfig } = hostPayloadLegacy({
|
||||
inheritAppearance: false,
|
||||
fontSize: 20,
|
||||
}) as { terminalConfig: Record<string, unknown> };
|
||||
expect(terminalConfig.fontSize).toBe(20);
|
||||
expect(terminalConfig.theme).toBe("termix");
|
||||
});
|
||||
});
|
||||
@@ -132,3 +132,78 @@ describe("client settings and image storage", () => {
|
||||
expect(result.body.connected).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("terminal settings routes", () => {
|
||||
const host = (id: number, userId = "user-1") => ({
|
||||
id,
|
||||
userId,
|
||||
name: `h${id}`,
|
||||
ip: "10.0.0.1",
|
||||
port: 22,
|
||||
username: "root",
|
||||
tags: null,
|
||||
folder: null,
|
||||
authType: "password",
|
||||
});
|
||||
|
||||
it("hands every user their own terminal settings and the new-host defaults", async () => {
|
||||
server = await startServer({
|
||||
settings: { newHostFontSize: 20, newHostTheme: "nord" },
|
||||
});
|
||||
await server.mock.ctx.settings.setUser(
|
||||
"user-1",
|
||||
"commandAutocomplete",
|
||||
true,
|
||||
);
|
||||
await server.mock.ctx.settings.setUser("user-1", "terminalDefaults", {
|
||||
fontSize: 18,
|
||||
});
|
||||
const { body } = await server.request("GET", "/client-settings");
|
||||
expect(body.user).toMatchObject({
|
||||
commandAutocomplete: true,
|
||||
terminalDefaults: { fontSize: 18 },
|
||||
localEcho: "auto",
|
||||
linkClickBehavior: "confirm",
|
||||
});
|
||||
expect(body.newHostDefaults).toMatchObject({ fontSize: 20, theme: "nord" });
|
||||
const other = await server.request("GET", "/client-settings", {
|
||||
user: "user-2",
|
||||
});
|
||||
expect(other.body.user.commandAutocomplete).toBe(false);
|
||||
});
|
||||
|
||||
it("saves the browser preferences a user moves over, for that user only", async () => {
|
||||
server = await startServer();
|
||||
const saved = await server.request("PUT", "/user-settings", {
|
||||
body: { localEcho: "on", linkClickBehavior: "direct", theme: "x" },
|
||||
});
|
||||
expect(saved.status).toBe(200);
|
||||
expect(await server.mock.ctx.settings.getUser("user-1", "localEcho")).toBe(
|
||||
"on",
|
||||
);
|
||||
expect(
|
||||
await server.mock.ctx.settings.getUser("user-2", "localEcho"),
|
||||
).not.toBe("on");
|
||||
});
|
||||
|
||||
it("turns auto tmux on for a host the user can edit", async () => {
|
||||
server = await startServer({ hosts: [host(1)] });
|
||||
const saved = await server.request("PUT", "/hosts/1/auto-tmux", {
|
||||
body: { enabled: true },
|
||||
});
|
||||
expect(saved.status).toBe(200);
|
||||
expect(await server.mock.ctx.settings.getHost(1, "autoTmux")).toBe(true);
|
||||
});
|
||||
|
||||
it("refuses auto tmux on a host the user cannot reach, or a bad body", async () => {
|
||||
server = await startServer({ hosts: [host(1)] });
|
||||
const unknown = await server.request("PUT", "/hosts/2/auto-tmux", {
|
||||
body: { enabled: true },
|
||||
});
|
||||
expect(unknown.status).toBe(403);
|
||||
const bad = await server.request("PUT", "/hosts/1/auto-tmux", {
|
||||
body: { enabled: "yes" },
|
||||
});
|
||||
expect(bad.status).toBe(400);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
DEFAULT_HOST_TERMINAL_SETTINGS,
|
||||
hostSettingsFromTerminalConfig,
|
||||
readHostTerminalSettings,
|
||||
readUserSettings,
|
||||
resolveTerminalSettings,
|
||||
} from "../../src/shared/terminal-settings";
|
||||
|
||||
describe("hostSettingsFromTerminalConfig", () => {
|
||||
it("keeps behavior and leaves a look-less host following the user", () => {
|
||||
expect(
|
||||
hostSettingsFromTerminalConfig({
|
||||
autoMosh: true,
|
||||
fastScrollModifier: "ctrl",
|
||||
keepaliveInterval: 5,
|
||||
}),
|
||||
).toEqual({ autoMosh: true, fastScrollModifier: "ctrl" });
|
||||
});
|
||||
|
||||
it("drops values a select field would refuse", () => {
|
||||
expect(
|
||||
hostSettingsFromTerminalConfig({ bellStyle: "loud", cursorStyle: "bar" }),
|
||||
).toEqual({ inheritAppearance: false, cursorStyle: "bar" });
|
||||
});
|
||||
|
||||
it("reads nothing from garbage", () => {
|
||||
expect(hostSettingsFromTerminalConfig("{")).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveTerminalSettings", () => {
|
||||
it("uses the user's defaults while the host follows them", () => {
|
||||
const host = readHostTerminalSettings({ fontSize: 30, autoTmux: true });
|
||||
const resolved = resolveTerminalSettings(host, { fontSize: 18 });
|
||||
expect(resolved.fontSize).toBe(18);
|
||||
expect(resolved.autoTmux).toBe(true);
|
||||
});
|
||||
|
||||
it("uses the host's own look once it opts out", () => {
|
||||
const host = readHostTerminalSettings({
|
||||
inheritAppearance: false,
|
||||
fontSize: 30,
|
||||
});
|
||||
expect(resolveTerminalSettings(host, { fontSize: 18 }).fontSize).toBe(30);
|
||||
});
|
||||
|
||||
it("falls back to the built-in defaults", () => {
|
||||
expect(resolveTerminalSettings(null).theme).toBe(
|
||||
DEFAULT_HOST_TERMINAL_SETTINGS.theme,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("readUserSettings", () => {
|
||||
it("types what it reads and drops broken saved themes", () => {
|
||||
expect(
|
||||
readUserSettings({
|
||||
terminalDefaults: { fontSize: "16", junk: 1 },
|
||||
customThemes: [{ id: "a", name: "A", colors: {} }, { id: 1 }],
|
||||
localEcho: "sideways",
|
||||
linkClickBehavior: "direct",
|
||||
}),
|
||||
).toEqual({
|
||||
terminalDefaults: { fontSize: 16 },
|
||||
customThemes: [{ id: "a", name: "A", colors: {} }],
|
||||
commandAutocomplete: false,
|
||||
localEcho: "auto",
|
||||
linkClickBehavior: "direct",
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user