feat(onboarding): enhance integration setup with Docker discovery, URL templating, and API key helpers

Add automatic Docker container discovery during onboarding to pre-select
and pre-fill integrations. Introduce base URL templating with subdomain/port
modes, API key settings path links, and a curated onboarding list that
hides niche integrations (GitHub, npm, Docker Hub, etc.). Prevent duplicate
widget seeding and remove the timetable widget from defaults.
This commit is contained in:
Thomas Camlong committed 2026-05-27 14:36:04 +02:00
1 parent 1fcc149c91
commit 309ba0590d
18 files changed
+1056 -56

No files matched your search

@@ -1,6 +1,12 @@
import { objectEntries } from "@homarr/common";
import type { IntegrationKind, WidgetKind } from "@homarr/definitions";
import { featuredIntegrations, getIntegrationName, integrationDefs, integrationKinds } from "@homarr/definitions";
import {
featuredIntegrations,
getIntegrationName,
hiddenFromOnboarding,
integrationDefs,
integrationKinds,
} from "@homarr/definitions";
import { widgetImports } from "@homarr/widgets";
export const CARD_HEIGHT = 180;
@@ -62,9 +68,13 @@ export interface IntegrationGridItem {
widgets: WidgetKind[];
}
export const buildSortedIntegrations = (enableMockIntegration: boolean): IntegrationGridItem[] =>
export const buildSortedIntegrations = (options: { enableMockIntegration?: boolean; onboarding?: boolean } = {}): IntegrationGridItem[] =>
integrationKinds
.filter((kind) => enableMockIntegration || kind !== "mock")
.filter((kind) => {
if (options.onboarding && hiddenFromOnboarding.has(kind)) return false;
if (!options.enableMockIntegration && kind === "mock") return false;
return true;
})
.map((kind) => ({
kind,
name: getIntegrationName(kind),
@@ -16,18 +16,20 @@ import {
} from "./integration-grid-shared";
interface IntegrationMultiSelectGridProps {
selectedKinds: IntegrationKind[];
onSelectionChange: (kinds: IntegrationKind[]) => void;
initialSelection?: IntegrationKind[];
enableMockIntegration?: boolean;
onboarding?: boolean;
}
export const IntegrationMultiSelectGrid = ({
selectedKinds: selectedKindsArray,
onSelectionChange,
initialSelection = [],
enableMockIntegration = false,
onboarding = false,
}: IntegrationMultiSelectGridProps) => {
const [search, setSearch] = useState("");
const [selectedKinds, setSelectedKinds] = useState<Set<IntegrationKind>>(() => new Set(initialSelection));
const selectedKinds = useMemo(() => new Set(selectedKindsArray), [selectedKindsArray]);
const t = useI18n();
const toggleKind = useCallback(
@@ -38,13 +40,12 @@ export const IntegrationMultiSelectGrid = ({
} else {
next.add(kind);
}
setSelectedKinds(next);
onSelectionChange(Array.from(next));
},
[selectedKinds, onSelectionChange],
);
const integrations = useMemo(() => buildSortedIntegrations(enableMockIntegration), [enableMockIntegration]);
const integrations = useMemo(() => buildSortedIntegrations({ enableMockIntegration, onboarding }), [enableMockIntegration, onboarding]);
const filtered = useMemo(() => filterIntegrations(integrations, search), [integrations, search]);
return (
@@ -24,7 +24,7 @@ export const IntegrationSelectGrid = ({ onSelect, enableMockIntegration = false
const [search, setSearch] = useState("");
const t = useI18n();
const integrations = useMemo(() => buildSortedIntegrations(enableMockIntegration), [enableMockIntegration]);
const integrations = useMemo(() => buildSortedIntegrations({ enableMockIntegration }), [enableMockIntegration]);
const filtered = useMemo(() => filterIntegrations(integrations, search), [integrations, search]);
return (