fix(onboarding): address PR review feedback on docker label discovery
Fix app-only continue flow, handle stale label sync, per-layout yOffset, blank label normalization, updatedAt on source upsert, docs link, and forceDisableStatus for ping-enabled apps.
This commit is contained in:
6 files changed
+51
-20
No files matched your search
@@ -31,7 +31,7 @@ Homarr can read `homarr.*` labels on your containers to pre-populate apps during
|
||||
|
||||
## Homepage fallback
|
||||
|
||||
When `homarr.name` is absent, Homarr reads `homepage.name`, `homepage.group`, and `homepage.href` if homepage label reading is enabled (default: on).
|
||||
When `homarr.name` is absent, Homarr reads `homepage.name`, `homepage.group`, `homepage.href`, `homepage.icon`, and `homepage.description` if homepage label reading is enabled (default: on).
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ They are then available in the app list and can be used as normally added apps.
|
||||
|
||||
### Configuration
|
||||
|
||||
Homarr supports Docker labels for automatic app discovery during onboarding. See [Docker labels](./docker-labels/index.mdx) for the full reference.
|
||||
Homarr supports Docker labels for automatic app discovery during onboarding. See [Docker labels](../docker-labels/index.mdx) for the full reference.
|
||||
|
||||
Currently Homarr supports one label for hiding containers:
|
||||
|
||||
|
||||
@@ -159,14 +159,21 @@ export const InitIntegrations = () => {
|
||||
const imageApps = selectedApps.filter((app) => app.source !== "label");
|
||||
|
||||
if (labelApps.length > 0) {
|
||||
const labelAppsWithHost = labelApps.filter((app): app is typeof app & { host: string } => Boolean(app.host));
|
||||
const labelAppsWithHost = labelApps.filter((app): app is (typeof app & { host: string }) => Boolean(app.host));
|
||||
const missingHostCount = labelApps.length - labelAppsWithHost.length;
|
||||
if (missingHostCount > 0) {
|
||||
throw new Error("Some selected Docker label apps are missing host metadata");
|
||||
}
|
||||
if (labelAppsWithHost.length > 0) {
|
||||
await syncLabelApps(
|
||||
const syncResult = await syncLabelApps(
|
||||
labelAppsWithHost.map((app) => ({
|
||||
containerId: app.containerId,
|
||||
host: app.host,
|
||||
})),
|
||||
);
|
||||
if (syncResult.notFound > 0) {
|
||||
throw new Error("Some selected Docker label apps were not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,7 +391,13 @@ export const InitIntegrations = () => {
|
||||
{t("action.skip")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleContinueToConfig}
|
||||
onClick={() => {
|
||||
if (selectedKinds.length === 0) {
|
||||
void finishSetupAsync();
|
||||
return;
|
||||
}
|
||||
handleContinueToConfig();
|
||||
}}
|
||||
disabled={(selectedKinds.length === 0 && selectedAppCount === 0) || undefined}
|
||||
rightSection={<IconArrowRight size={16} stroke={1.5} />}
|
||||
suppressHydrationWarning
|
||||
|
||||
@@ -175,6 +175,7 @@ export type HomarrDocumentationPath =
|
||||
| "/docs/integrations/deluge"
|
||||
| "/docs/integrations/docker-hub"
|
||||
| "/docs/integrations/docker"
|
||||
| "/docs/integrations/docker-labels"
|
||||
| "/docs/integrations/emby"
|
||||
| "/docs/integrations/github-containerregistry"
|
||||
| "/docs/integrations/github"
|
||||
|
||||
@@ -34,7 +34,13 @@ const homepageLabelKeys = {
|
||||
const integrationKindSet = new Set<string>(integrationKinds);
|
||||
const widgetKindSet = new Set<string>(widgetKinds);
|
||||
|
||||
const readLabel = (labels: Record<string, string>, key: string) => labels[key]?.trim();
|
||||
const readLabel = (labels: Record<string, string>, key: string) => {
|
||||
const value = labels[key]?.trim();
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const resolveLabelValue = (
|
||||
labels: Record<string, string>,
|
||||
|
||||
@@ -133,21 +133,20 @@ export const syncDiscoveredServicesAsync = async (
|
||||
|
||||
if (!existingItem) {
|
||||
const newItemId = createId();
|
||||
const yOffset = getNextItemYOffset(
|
||||
board,
|
||||
categorySection.id,
|
||||
firstLayout.id,
|
||||
itemYOffsetBySection,
|
||||
pendingItemLayouts,
|
||||
itemHeight,
|
||||
);
|
||||
|
||||
const newItemLayouts = board.layouts.map((layout) => ({
|
||||
itemId: newItemId,
|
||||
sectionId: categorySection.id,
|
||||
layoutId: layout.id,
|
||||
xOffset: 0,
|
||||
yOffset,
|
||||
yOffset: getNextItemYOffset(
|
||||
board,
|
||||
categorySection.id,
|
||||
layout.id,
|
||||
itemYOffsetBySection,
|
||||
pendingItemLayouts,
|
||||
itemHeight,
|
||||
),
|
||||
width: itemWidth,
|
||||
height: itemHeight,
|
||||
}));
|
||||
@@ -232,7 +231,13 @@ export const syncDiscoveredServicesAsync = async (
|
||||
if (existing) {
|
||||
await innerDb
|
||||
.update(table)
|
||||
.set({ appId: source.appId, boardId: source.boardId, itemId: source.itemId, externalId: source.externalId })
|
||||
.set({
|
||||
appId: source.appId,
|
||||
boardId: source.boardId,
|
||||
itemId: source.itemId,
|
||||
externalId: source.externalId,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(and(eq(dockerAppSources.host, source.host), eq(dockerAppSources.containerId, source.containerId)));
|
||||
} else {
|
||||
await innerDb.insert(table).values(source as never);
|
||||
@@ -254,7 +259,13 @@ export const syncDiscoveredServicesAsync = async (
|
||||
} catch {
|
||||
innerDb
|
||||
.update(dockerAppSources)
|
||||
.set({ appId: source.appId, boardId: source.boardId, itemId: source.itemId, externalId: source.externalId })
|
||||
.set({
|
||||
appId: source.appId,
|
||||
boardId: source.boardId,
|
||||
itemId: source.itemId,
|
||||
externalId: source.externalId,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(and(eq(dockerAppSources.host, source.host), eq(dockerAppSources.containerId, source.containerId)))
|
||||
.run();
|
||||
}
|
||||
@@ -338,9 +349,9 @@ const buildAppItemOptions = (
|
||||
showTitle: true,
|
||||
descriptionDisplayMode: "hidden",
|
||||
layout: "column",
|
||||
pingEnabled: service.pingUrl
|
||||
? true
|
||||
: !(options.forceDisableStatus ?? false) && (options.enableStatusByDefault ?? true),
|
||||
pingEnabled:
|
||||
!(options.forceDisableStatus ?? false) &&
|
||||
(Boolean(service.pingUrl) || (options.enableStatusByDefault ?? true)),
|
||||
});
|
||||
|
||||
const findOrCreateCategorySection = (
|
||||
|
||||
Reference in new issue
Block a user