fix: load and save plugin host settings in the editor and tighten settings row spacing

This commit is contained in:
LukeGus committed 2026-09-29 22:40:01 -05:00
1 parent 1a6f9ffad6
commit 3028ab1176
6 files changed
+66 -10

No files matched your search

+2
View File
@@ -20,6 +20,8 @@
"webUiEndpointLabel": "Endpoint {{number}}",
"webUiIntegration": "Web UI",
"webUiEndpointsSection": "Endpoints",
"webUiNoEndpoints": "No web endpoints yet. Use Add endpoint to create one.",
"webUiEndpointsDisabled": "Turn on Web UI above to add endpoints.",
"webUiRequirementsText": "A direct endpoint is opened by your browser, so its port must be reachable from here. An SSH tunnel is opened by Termix over this host's SSH connection instead, so the port only has to be reachable from the host.",
"enableWebUi": "Enable web endpoints",
"enableWebUiDesc": "Open this host's web interfaces from the sidebar.",
@@ -220,9 +220,17 @@ export function HostEditorWebUiSection({
) : undefined
}
>
<div className="flex flex-col gap-4 py-3">
{enableWebUi && (
<div className="flex flex-col gap-3">
<div className="flex flex-col gap-3 py-3">
{!enableWebUi ? (
<p className="text-xs text-muted-foreground">
{t("hosts.webUiEndpointsDisabled")}
</p>
) : endpoints.length === 0 ? (
<p className="text-xs text-muted-foreground">
{t("hosts.webUiNoEndpoints")}
</p>
) : (
<>
{endpoints.map((endpoint, index) => {
const rowError = webEndpointRowError(endpoint);
return (
@@ -497,7 +505,7 @@ export function HostEditorWebUiSection({
);
})}
{!tunnelAvailable && endpoints.length > 0 && (
{!tunnelAvailable && (
// Shown whenever tunnelling is unavailable, not only once a row
// already asks for it: the Tunnel option renders disabled, and
// a disabled control with no stated reason reads as the
@@ -506,7 +514,7 @@ export function HostEditorWebUiSection({
{t("hosts.webUiAccessTunnelUnavailable")}
</p>
)}
</div>
</>
)}
</div>
</SectionCard>
+3 -1
View File
@@ -42,7 +42,9 @@ export function SettingRow({
children: React.ReactNode;
}) {
return (
<div className="flex items-center justify-between gap-3 py-3 border-b border-border last:border-0">
// In a gap-4 form stack the gap already spaces the row, so its own padding
// and divider would double up.
<div className="flex items-center justify-between gap-3 py-3 border-b border-border last:border-0 [.gap-4>&]:py-0 [.gap-4>&]:border-0">
<div className="flex flex-col gap-0.5 min-w-0 flex-1">
<div className="flex items-center gap-1.5 flex-wrap">
<span className="text-sm font-medium leading-snug">{label}</span>
+18 -4
View File
@@ -153,17 +153,23 @@ export function HostEditor({
const savePluginHostSettings = async (
hostId: number,
values: Record<string, Record<string, unknown>> | undefined,
) => {
if (!values || !Number.isInteger(hostId)) return;
): Promise<Record<string, Record<string, unknown>>> => {
const saved: Record<string, Record<string, unknown>> = {};
if (!values || !Number.isInteger(hostId)) return saved;
for (const [pluginId, fields] of Object.entries(values)) {
if (!fields || Object.keys(fields).length === 0) continue;
try {
await updatePluginHostSettings(pluginId, hostId, fields);
saved[pluginId] = await updatePluginHostSettings(
pluginId,
hostId,
fields,
);
} catch {
toast.error(t("settings.pluginSettingsSaveFailed"));
}
}
return saved;
};
const handleSave = async () => {
@@ -186,12 +192,20 @@ export function HostEditor({
// After the host: a new one has no id to scope settings to until it
// exists. A failure here must not claim the host itself failed to save.
const pluginValues = withProtocolSettings(form.pluginSettings, protocols);
await savePluginHostSettings(
const savedPluginValues = await savePluginHostSettings(
Number(saved.id),
host
? pluginValues
: withNewHostDefaults(pluginValues ?? {}, hostSettingPlugins),
);
// The host was read back before these were written.
saved = {
...saved,
pluginSettings: {
...(saved.pluginSettings ?? {}),
...savedPluginValues,
},
};
toast.success(host ? t("hosts.hostUpdated") : t("hosts.hostCreated"));
onSave(saved);
+1
View File
@@ -79,6 +79,7 @@ export function sshHostToHost(h: SSHHostWithStatus): Host {
h.sshPort ??
(h.connectionType === "ssh" || !h.connectionType ? h.port : 22),
protocolAuth: h.protocolAuth ?? {},
pluginSettings: h.pluginSettings ?? {},
jumpHosts: (parseJson<HostJumpHost[]>(h.jumpHosts) ?? []).map((j) => ({
hostId: String(j.hostId ?? j.hostid ?? j),
})),
@@ -32,4 +32,33 @@ describe("sshHostToHost", () => {
expect(host.id).toBe("-12");
expect(host.isShared).toBe(true);
});
it("keeps every plugin's host settings so the editor can load them", () => {
const pluginSettings = {
docker: { enableDocker: true, containerRuntime: "podman" },
"tmux-monitor": { enableTmuxMonitor: true },
};
const host = sshHostToHost({
id: 3,
name: "box",
ip: "10.0.0.3",
port: 22,
username: "root",
pluginSettings,
} as unknown as SSHHostWithStatus);
expect(host.pluginSettings).toEqual(pluginSettings);
});
it("defaults plugin settings to an empty map", () => {
const host = sshHostToHost({
id: 4,
name: "bare",
ip: "10.0.0.4",
port: 22,
username: "root",
} as SSHHostWithStatus);
expect(host.pluginSettings).toEqual({});
});
});