fix(onboarding): align detected app setup
This commit is contained in:
1 parent
ad0bb41fe4
commit
def3a3a6fa
5 files changed
+57
-53
No files matched your search
@@ -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:
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" truncate>
|
||||
{getIntegrationName(integration.kind)} · {integration.suggestedUrl || t("addressNeeded")}
|
||||
{getIntegrationName(integration.kind)}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
@@ -1165,7 +1167,7 @@ const Connections = (props: StudioSectionProps) => {
|
||||
{props.discoveredApps.map((app) => {
|
||||
const selected = props.selectedAppIds.includes(app.sourceId);
|
||||
return (
|
||||
<Stack key={app.sourceId} gap="xs">
|
||||
<Stack key={app.sourceId} gap="xs" h="100%">
|
||||
<Checkbox.Card
|
||||
className={classes.discoveryChoice}
|
||||
value={app.sourceId}
|
||||
@@ -1181,29 +1183,24 @@ const Connections = (props: StudioSectionProps) => {
|
||||
p="sm"
|
||||
radius="md"
|
||||
>
|
||||
<Group wrap="nowrap">
|
||||
<Group wrap="nowrap" w="100%">
|
||||
<Checkbox.Indicator />
|
||||
<Avatar src={app.iconUrl} size="sm" radius="sm">
|
||||
{app.containerName.at(0)}
|
||||
</Avatar>
|
||||
<Stack gap={0} miw={0}>
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
<Text size="sm" fw={600} truncate>
|
||||
{app.containerName}
|
||||
</Text>
|
||||
<Badge size="xs" variant="light" color={app.source === "label" ? "blue" : "gray"}>
|
||||
{app.source === "label" ? t("dockerLabelSource") : t("dockerImageSource")}
|
||||
</Badge>
|
||||
</Group>
|
||||
<Stack gap={0} miw={0} style={{ flex: 1 }}>
|
||||
<Text size="sm" fw={600} truncate>
|
||||
{app.containerName}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" truncate>
|
||||
{props.discoveredAppUrls[app.sourceId] || t("addressNeeded")}
|
||||
{app.detectedType ?? t("appType")}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
</Checkbox.Card>
|
||||
{selected ? (
|
||||
<TextInput
|
||||
label={t("appUrl", { name: app.containerName })}
|
||||
label={t("url")}
|
||||
value={props.discoveredAppUrls[app.sourceId] ?? ""}
|
||||
onChange={(event) => props.setDiscoveredAppUrl(app.sourceId, event.currentTarget.value)}
|
||||
error={props.appErrorSourceId === app.sourceId ? props.appError : undefined}
|
||||
@@ -1218,6 +1215,12 @@ const Connections = (props: StudioSectionProps) => {
|
||||
</Fieldset>
|
||||
) : null}
|
||||
|
||||
{props.appError ? (
|
||||
<Alert color="red" role="alert">
|
||||
{props.appError}
|
||||
</Alert>
|
||||
) : null}
|
||||
|
||||
<Fieldset legend={t("otherIntegrations")}>
|
||||
<IntegrationMultiSelectGrid
|
||||
selectedKinds={props.selectedKinds}
|
||||
@@ -1227,36 +1230,31 @@ const Connections = (props: StudioSectionProps) => {
|
||||
/>
|
||||
</Fieldset>
|
||||
|
||||
{props.appError ? (
|
||||
<Alert color="red" role="alert">
|
||||
{props.appError}
|
||||
</Alert>
|
||||
) : null}
|
||||
|
||||
{props.drafts.length > 0 ? (
|
||||
<Accordion
|
||||
className={classes.integrationAccordion}
|
||||
variant="separated"
|
||||
multiple
|
||||
transitionDuration={200}
|
||||
order={3}
|
||||
mb="md"
|
||||
defaultValue={props.drafts.slice(0, 1).map((draft) => draft.id)}
|
||||
>
|
||||
{props.drafts.map((draft) => (
|
||||
<IntegrationEditor
|
||||
key={draft.id}
|
||||
draft={draft}
|
||||
update={(patch) => props.updateDraft(draft.id, patch)}
|
||||
selectSecretOption={(option) => props.selectSecretOption(draft, option)}
|
||||
/>
|
||||
))}
|
||||
</Accordion>
|
||||
) : (
|
||||
<Alert variant="light" icon={<IconPlugConnected size={18} />} my="xs">
|
||||
{t("empty")}
|
||||
</Alert>
|
||||
)}
|
||||
<Fieldset legend={t("configureIntegrations")}>
|
||||
{props.drafts.length > 0 ? (
|
||||
<Accordion
|
||||
className={classes.integrationAccordion}
|
||||
variant="separated"
|
||||
multiple
|
||||
transitionDuration={200}
|
||||
order={3}
|
||||
defaultValue={props.drafts.slice(0, 1).map((draft) => draft.id)}
|
||||
>
|
||||
{props.drafts.map((draft) => (
|
||||
<IntegrationEditor
|
||||
key={draft.id}
|
||||
draft={draft}
|
||||
update={(patch) => props.updateDraft(draft.id, patch)}
|
||||
selectSecretOption={(option) => props.selectSecretOption(draft, option)}
|
||||
/>
|
||||
))}
|
||||
</Accordion>
|
||||
) : (
|
||||
<Alert variant="light" icon={<IconPlugConnected size={18} />}>
|
||||
{t("empty")}
|
||||
</Alert>
|
||||
)}
|
||||
</Fieldset>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user