diff --git a/apps/docs/docs/integrations/docker-labels/index.mdx b/apps/docs/docs/integrations/docker-labels/index.mdx index 677e7a518..de726e2df 100644 --- a/apps/docs/docs/integrations/docker-labels/index.mdx +++ b/apps/docs/docs/integrations/docker-labels/index.mdx @@ -69,7 +69,8 @@ Homepage metadata: once `homarr.name` is present, use `homarr.*` labels for the ## What onboarding creates In **Connect**, label-discovered apps and integrations appear before image-based suggestions. You can import multiple -services in one pass and edit every suggested address. +services in one pass and edit every suggested address. For image-matched apps, Homarr shows the detected service type +and uses it when building host, subdomain, or reverse-proxy path suggestions. When setup completes, Homarr rechecks the selected container sources and then: diff --git a/packages/api/src/router/onboard/onboard-router.ts b/packages/api/src/router/onboard/onboard-router.ts index 1fc3972da..ef9756292 100644 --- a/packages/api/src/router/onboard/onboard-router.ts +++ b/packages/api/src/router/onboard/onboard-router.ts @@ -88,6 +88,7 @@ interface DiscoveredApp { suggestedUrl: string; publishedPort: number | null; iconUrl: string | null; + detectedType?: string; source: "label" | "docker"; group?: string; boardName?: string; @@ -207,8 +208,8 @@ export const onboardRouter = createTRPCRouter({ const normalized = imageName.toLowerCase().trim(); if (normalized.length < 3) return null; return ( - dbIcons.find((icon) => icon.name.toLowerCase() === normalized)?.url ?? - dbIcons.find((icon) => icon.name.toLowerCase().startsWith(normalized))?.url ?? + dbIcons.find((icon) => icon.name.toLowerCase() === normalized) ?? + dbIcons.find((icon) => icon.name.toLowerCase().startsWith(normalized)) ?? null ); }; @@ -257,7 +258,7 @@ export const onboardRouter = createTRPCRouter({ const imageName = extractContainerImageName(container.Image); const containerName = container.Names[0]?.split("/")[1] ?? "Unknown"; const dbIcon = strictIconMatch(imageName); - const iconUrl = dbIcon ?? cdnIconUrl(imageName.toLowerCase()); + const iconUrl = dbIcon?.url ?? cdnIconUrl(imageName.toLowerCase()); const { url: suggestedUrl, publishedPort } = buildSuggestedUrl(container.Ports, container.host); const kind = matchIntegrationKindFromContainer({ image: container.Image, name: containerName }); const sourceId = createDockerSourceId(container.host, container.Id); @@ -271,6 +272,7 @@ export const onboardRouter = createTRPCRouter({ suggestedUrl, publishedPort, iconUrl, + detectedType: dbIcon.name, source: "docker", host: container.host, }); diff --git a/packages/onboarding/src/onboarding-studio.module.css b/packages/onboarding/src/onboarding-studio.module.css index 31b57b19d..1a0fcda32 100644 --- a/packages/onboarding/src/onboarding-studio.module.css +++ b/packages/onboarding/src/onboarding-studio.module.css @@ -181,6 +181,11 @@ border: 1px solid color-mix(in srgb, var(--mantine-color-default-border) 76%, transparent); } +.discoveryChoice { + width: 100%; + min-height: 4.25rem; +} + .discoveryChoice[data-checked] { border-color: var(--mantine-primary-color-filled); background: color-mix(in srgb, var(--mantine-primary-color-light) 62%, var(--mantine-color-body)); diff --git a/packages/onboarding/src/setup-studio.tsx b/packages/onboarding/src/setup-studio.tsx index 878a5c2f9..253bdbf3d 100644 --- a/packages/onboarding/src/setup-studio.tsx +++ b/packages/onboarding/src/setup-studio.tsx @@ -269,7 +269,9 @@ export const SetupStudio = ({ environment, assistantConfiguration }: OnboardingS resolveDiscoveredAppUrl( appUrlOverrides[app.sourceId], app.suggestedUrl, - serverOrigin ? buildAppUrl(app.containerName, serverOrigin, urlMode, app.publishedPort ?? undefined) : null, + serverOrigin + ? buildAppUrl(app.detectedType ?? app.containerName, serverOrigin, urlMode, app.publishedPort ?? undefined) + : null, ), ]), ), @@ -1148,7 +1150,7 @@ const Connections = (props: StudioSectionProps) => { {integration.containerName} - {getIntegrationName(integration.kind)} ยท {integration.suggestedUrl || t("addressNeeded")} + {getIntegrationName(integration.kind)} @@ -1165,7 +1167,7 @@ const Connections = (props: StudioSectionProps) => { {props.discoveredApps.map((app) => { const selected = props.selectedAppIds.includes(app.sourceId); return ( - + { p="sm" radius="md" > - + {app.containerName.at(0)} - - - - {app.containerName} - - - {app.source === "label" ? t("dockerLabelSource") : t("dockerImageSource")} - - + + + {app.containerName} + - {props.discoveredAppUrls[app.sourceId] || t("addressNeeded")} + {app.detectedType ?? t("appType")} {selected ? ( props.setDiscoveredAppUrl(app.sourceId, event.currentTarget.value)} error={props.appErrorSourceId === app.sourceId ? props.appError : undefined} @@ -1218,6 +1215,12 @@ const Connections = (props: StudioSectionProps) => { ) : null} + {props.appError ? ( + + {props.appError} + + ) : null} +
{ />
- {props.appError ? ( - - {props.appError} - - ) : null} - - {props.drafts.length > 0 ? ( - draft.id)} - > - {props.drafts.map((draft) => ( - props.updateDraft(draft.id, patch)} - selectSecretOption={(option) => props.selectSecretOption(draft, option)} - /> - ))} - - ) : ( - } my="xs"> - {t("empty")} - - )} +
+ {props.drafts.length > 0 ? ( + draft.id)} + > + {props.drafts.map((draft) => ( + props.updateDraft(draft.id, patch)} + selectSecretOption={(option) => props.selectSecretOption(draft, option)} + /> + ))} + + ) : ( + }> + {t("empty")} + + )} +
); }; diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json index e9898c480..f2f1d65a6 100644 --- a/packages/translation/src/lang/en.json +++ b/packages/translation/src/lang/en.json @@ -112,11 +112,9 @@ "detectedIntegrationLabel": "Import {name}, detected as {kind}", "detectedAppLabel": "Import detected app {name}", "otherIntegrations": "Add another integration", + "configureIntegrations": "Configure integrations", "discoveredApps": "Detected apps ({count})", - "dockerLabelSource": "Docker label", - "dockerImageSource": "Docker image", - "addressNeeded": "Address needed", - "appUrl": "Address for {name}", + "appType": "App", "appAddressRequired": "Add an HTTP or HTTPS address for {name}, or remove it from the selection.", "empty": "No integrations selected. Homarr will still create a useful starter board with general widgets.", "dockerSource": "Docker",