feat(init): apply base URL template to discovered apps

Discovered Docker apps now use the same buildAppUrl logic as
integrations when a base host is set, instead of using the raw
Docker socket URL (e.g. http://container:5432).

Extracts shared buildUrl helper and adds buildAppUrl that derives
a slug from the container name. Also passes publishedPort through
the DiscoveredApp type so host:port mode works correctly.
This commit is contained in:
Thomas Camlong committed 2026-05-28 12:21:03 +02:00
1 parent bbb04b084f
commit 7c0653672a
4 files changed
+36 -18

No files matched your search

@@ -36,6 +36,7 @@ export interface DiscoveredApp {
containerId: string;
containerName: string;
suggestedUrl: string;
publishedPort: number | null;
iconUrl: string | null;
}
@@ -22,7 +22,7 @@ import { IconArrowRight, IconInfoCircle, IconSettings } from "@tabler/icons-reac
import { clientApi } from "@homarr/api/client";
import type { IntegrationKind, UrlTemplateMode } from "@homarr/definitions";
import { buildIntegrationUrl, getIntegrationName } from "@homarr/definitions";
import { buildAppUrl, buildIntegrationUrl, getIntegrationName } from "@homarr/definitions";
import { showErrorNotification } from "@homarr/notifications";
import { useScopedI18n } from "@homarr/translation/client";
import { IntegrationAvatar } from "@homarr/ui";
@@ -137,11 +137,16 @@ export const InitIntegrations = () => {
const appsToCreate = dockerApps.filter((app) => selectedAppIds.has(app.containerId));
if (appsToCreate.length > 0) {
await createApps(
appsToCreate.map((app) => ({
name: app.containerName,
href: app.suggestedUrl || null,
iconUrl: app.iconUrl,
})),
appsToCreate.map((app) => {
const href = baseHost
? buildAppUrl(app.containerName, baseHost, urlMode, app.publishedPort ?? undefined)
: app.suggestedUrl;
return {
name: app.containerName,
href: href || null,
iconUrl: app.iconUrl,
};
}),
);
}
await setupIntegrations();
@@ -157,6 +157,7 @@ export const onboardRouter = createTRPCRouter({
containerId: string;
containerName: string;
suggestedUrl: string;
publishedPort: number | null;
iconUrl: string | null;
}[],
};
@@ -228,6 +229,7 @@ export const onboardRouter = createTRPCRouter({
containerId: container.Id,
containerName,
suggestedUrl,
publishedPort,
iconUrl,
});
}
@@ -6,6 +6,14 @@ export type UrlTemplateMode = "hostPort" | "subdomain";
const getSlugForKind = (kind: IntegrationKind): string => integrationIconSlugs[kind];
const buildUrl = (slug: string, host: string, mode: UrlTemplateMode, port?: number): string => {
const modeBuilders: Record<UrlTemplateMode, () => string> = {
subdomain: () => `https://${slug}.${host}`,
hostPort: () => (port ? `http://${host}:${port}` : `http://${host}`),
};
return modeBuilders[mode]();
};
export const buildIntegrationUrl = (
kind: IntegrationKind,
baseHost: string,
@@ -14,16 +22,18 @@ export const buildIntegrationUrl = (
): string => {
const host = baseHost.trim().replace(/\/+$/, "");
if (!host) return "";
const slug = getSlugForKind(kind);
const modeBuilders: Record<UrlTemplateMode, () => string> = {
subdomain: () => `https://${slug}.${host}`,
hostPort: () => {
const port = dockerPort ?? getIntegrationDefaultPort(kind);
return port ? `http://${host}:${port}` : `http://${host}`;
},
};
return modeBuilders[mode]();
const port = dockerPort ?? getIntegrationDefaultPort(kind);
return buildUrl(getSlugForKind(kind), host, mode, port ?? undefined);
};
export const buildAppUrl = (
containerName: string,
baseHost: string,
mode: UrlTemplateMode,
dockerPort?: number,
): string => {
const host = baseHost.trim().replace(/\/+$/, "");
if (!host) return "";
const slug = containerName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
return buildUrl(slug, host, mode, dockerPort);
};