diff --git a/src/ui/settings/host-feature-tabs.tsx b/src/ui/settings/host-feature-tabs.tsx index f61aed726..66e89a045 100644 --- a/src/ui/settings/host-feature-tabs.tsx +++ b/src/ui/settings/host-feature-tabs.tsx @@ -12,12 +12,13 @@ import { registerHostEditorSection, unregisterHostEditorSection, } from "@/sidebar/HostManagerTabs"; -import type { PluginSummary } from "@/api/plugins-api"; +import type { PluginSettingsField, PluginSummary } from "@/api/plugins-api"; import { HostPluginSections, type HostPluginSettings, } from "./HostPluginSections"; import { hasVisibleFields } from "./settings-fields-util"; +import { isDefaultableField } from "@/sidebar/host-defaults/catalog"; const TAB_PREFIX = "feature:"; @@ -29,6 +30,16 @@ interface HostFormLike { pluginSettings?: HostPluginSettings; } +function hasDefaultableFields(host: { + enableKey?: string; + fields: PluginSettingsField[]; +}): boolean { + return ( + !!host.enableKey || + host.fields.some((field) => !field.hidden && isDefaultableField(field)) + ); +} + /** Adds, updates and removes generated tabs to match the plugin list. */ export function syncHostFeatureTabs(plugins: PluginSummary[]): string[] { const existing = hostEditorSectionList(); @@ -36,6 +47,19 @@ export function syncHostFeatureTabs(plugins: PluginSummary[]): string[] { (section) => section.pluginId && !section.id.startsWith(TAB_PREFIX), ); const ownSections = new Set(own.map((section) => section.pluginId)); + // The defaults tab sits where the plugin's own section does on a host. + const ownPlacement = new Map< + string, + { group: "top" | "ssh"; order?: number } + >(); + for (const section of own) { + if (!ownPlacement.has(section.pluginId!)) { + ownPlacement.set(section.pluginId!, { + group: section.group, + order: section.order, + }); + } + } // A plugin whose own sections are all about one host still gets its // switches in the defaults editor, through a generated tab shown only there. const ownDefaultsSections = new Set( @@ -47,7 +71,10 @@ export function syncHostFeatureTabs(plugins: PluginSummary[]): string[] { const contributors = plugins.filter((plugin) => { if (!plugin.enabled || ownDefaultsSections.has(plugin.id)) return false; const host = plugin.contributes?.settings?.host; - return !!host && (hasVisibleFields(host.fields) || !!host.enableKey); + if (!host) return false; + // Its own section edits it per host, so only a default is left to offer. + if (ownSections.has(plugin.id)) return hasDefaultableFields(host); + return hasVisibleFields(host.fields) || !!host.enableKey; }); const wanted = new Set( @@ -62,11 +89,12 @@ export function syncHostFeatureTabs(plugins: PluginSummary[]): string[] { for (const plugin of contributors) { const host = plugin.contributes!.settings!.host!; const icon = plugin.icon; + const placement = ownPlacement.get(plugin.id); registerHostEditorSection({ id: hostFeatureTabId(plugin.id), pluginId: plugin.id, - group: host.editorGroup ?? "top", - order: host.editorOrder ?? 100, + group: placement?.group ?? host.editorGroup ?? "top", + order: placement?.order ?? host.editorOrder ?? 100, labelKey: plugin.name, label: plugin.name, // Nothing but the plugin's host settings, so it works for defaults too. diff --git a/src/ui/tests/settings/host-feature-tabs.test.tsx b/src/ui/tests/settings/host-feature-tabs.test.tsx index 2a637ffcf..6b080c196 100644 --- a/src/ui/tests/settings/host-feature-tabs.test.tsx +++ b/src/ui/tests/settings/host-feature-tabs.test.tsx @@ -121,6 +121,51 @@ describe("syncHostFeatureTabs", () => { ); }); + it("places the defaults tab where the plugin's own section sits", () => { + registerHostEditorSection({ + id: "docker-own", + pluginId: "docker", + group: "ssh", + order: 40, + labelKey: "k", + component: () => null, + }); + syncHostFeatureTabs([plugin()]); + const tab = getHostEditorSection(hostFeatureTabId("docker")); + expect(tab?.group).toBe("ssh"); + expect(tab?.order).toBe(40); + }); + + it("gives a plugin with its own section no tab when nothing can have a default", () => { + registerHostEditorSection({ + id: "proxmox-own", + pluginId: "proxmox", + group: "ssh", + labelKey: "k", + component: () => null, + }); + const proxmox = plugin({ + id: "proxmox", + name: "Proxmox", + contributes: { + settings: { + host: { + fields: [ + { + key: "enableProxmox", + type: "custom", + component: "proxmoxHostSettings", + defaultable: false, + }, + ], + }, + }, + }, + } as Partial); + expect(syncHostFeatureTabs([proxmox])).toEqual([]); + expect(getHostEditorSection(hostFeatureTabId("proxmox"))).toBeUndefined(); + }); + it("skips a plugin whose own section already covers the defaults editor", () => { registerHostEditorSection({ id: "docker-own",