perf: optimize dashboard loading and builds

This commit is contained in:
Thomas Camlong committed 2026-08-02 15:58:16 +02:00
1 parent d90e762e4b
commit de801f3f00
135 files changed
+8521 -1718

No files matched your search

@@ -5,27 +5,8 @@ import {
hiddenFromOnboarding,
integrationDefs,
integrationKinds,
getWidgetKindsForIntegration,
} from "@homarr/definitions";
import { loadAllWidgetDefinitions } from "@homarr/widgets/manifest";
const buildWidgetsByIntegration = async () => {
const map = Object.fromEntries(integrationKinds.map((kind) => [kind, [] as WidgetKind[]])) as Record<
IntegrationKind,
WidgetKind[]
>;
const definitions = await loadAllWidgetDefinitions();
for (const [widgetKind, definition] of definitions) {
const supported =
"supportedIntegrations" in definition ? (definition.supportedIntegrations as IntegrationKind[]) : [];
for (const kind of supported) {
if (kind in map) {
map[kind].push(widgetKind);
}
}
}
return map;
};
export const categoryTranslationKeys: Record<string, string> = {
dnsHole: "integration.category.dnsHole",
@@ -64,14 +45,13 @@ export interface IntegrationGridItem {
widgets: WidgetKind[];
}
export const buildSortedIntegrations = async (
export const buildSortedIntegrations = (
options: { enableMockIntegration?: boolean; onboarding?: boolean } = {},
): Promise<IntegrationGridItem[]> => {
const widgetsByIntegration = await buildWidgetsByIntegration();
): IntegrationGridItem[] => {
return integrationKinds
.filter((kind) => {
if (options.onboarding && hiddenFromOnboarding.has(kind)) return false;
if (options.onboarding && widgetsByIntegration[kind].length === 0) return false;
if (options.onboarding && getWidgetKindsForIntegration(kind).length === 0) return false;
if (!options.enableMockIntegration && kind === "mock") return false;
return true;
})
@@ -79,7 +59,7 @@ export const buildSortedIntegrations = async (
kind,
name: getIntegrationName(kind),
categories: [...new Set(integrationDefs[kind].category.flat())] as string[],
widgets: widgetsByIntegration[kind],
widgets: getWidgetKindsForIntegration(kind),
}))
.toSorted((left, right) => {
const leftIdx = featuredIntegrations.indexOf(left.kind);
@@ -1,9 +1,8 @@
"use client";
import { useCallback, useMemo, useState } from "react";
import { Badge, Card, Center, Group, Loader, Stack, Text, Tooltip } from "@mantine/core";
import { Badge, Card, Center, Group, Stack, Text, Tooltip } from "@mantine/core";
import { IconBrandDocker, IconCheck, IconPuzzle } from "@tabler/icons-react";
import { useQuery } from "@tanstack/react-query";
import type { IntegrationKind } from "@homarr/definitions";
import { useI18n } from "@homarr/translation/client";
@@ -43,14 +42,10 @@ export const IntegrationMultiSelectGrid = ({
[selectedKindsArray, onSelectionChange],
);
const {
data: integrations = [],
isPending,
isError,
} = useQuery({
queryKey: ["widget-integrations", { enableMockIntegration, onboarding }],
queryFn: () => buildSortedIntegrations({ enableMockIntegration, onboarding }),
});
const integrations = useMemo(
() => buildSortedIntegrations({ enableMockIntegration, onboarding }),
[enableMockIntegration, onboarding],
);
const sorted = useMemo(() => {
if (!detectedKinds || detectedKinds.size === 0) return integrations;
@@ -69,11 +64,6 @@ export const IntegrationMultiSelectGrid = ({
disableScroll={onboarding}
disableAutoFocus={onboarding}
>
{isPending && (
<Center p="xl">
<Loader size="sm" />
</Center>
)}
{filtered.map((integration) => {
const isSelected = selectedKinds.has(integration.kind);
const isDetected = detectedKinds?.has(integration.kind) ?? false;
@@ -140,14 +130,7 @@ export const IntegrationMultiSelectGrid = ({
);
})}
{isError && (
<Center p="xl">
<Text c="red" role="alert">
{t("common.error")}
</Text>
</Center>
)}
{!isPending && !isError && filtered.length === 0 && (
{filtered.length === 0 && (
<Center p="xl">
<Text c="dimmed">{t("common.noResults")}</Text>
</Center>
@@ -1,9 +1,8 @@
"use client";
import { useMemo, useState } from "react";
import { Badge, Card, Center, Group, Loader, Stack, Text, Tooltip } from "@mantine/core";
import { Badge, Card, Center, Group, Stack, Text, Tooltip } from "@mantine/core";
import { IconPuzzle } from "@tabler/icons-react";
import { useQuery } from "@tanstack/react-query";
import type { IntegrationKind } from "@homarr/definitions";
import { useI18n } from "@homarr/translation/client";
@@ -20,14 +19,7 @@ export const IntegrationSelectGrid = ({ onSelect, enableMockIntegration = false
const [search, setSearch] = useState("");
const t = useI18n();
const {
data: integrations = [],
isPending,
isError,
} = useQuery({
queryKey: ["widget-integrations", { enableMockIntegration }],
queryFn: () => buildSortedIntegrations({ enableMockIntegration }),
});
const integrations = useMemo(() => buildSortedIntegrations({ enableMockIntegration }), [enableMockIntegration]);
const filtered = useMemo(() => filterIntegrations(integrations, search), [integrations, search]);
return (
@@ -36,11 +28,6 @@ export const IntegrationSelectGrid = ({ onSelect, enableMockIntegration = false
onSearchChange={setSearch}
placeholder={`${t("integration.page.list.search")}...`}
>
{isPending && (
<Center p="xl">
<Loader size="sm" />
</Center>
)}
{filtered.map((integration) => (
<Card
key={integration.kind}
@@ -93,14 +80,7 @@ export const IntegrationSelectGrid = ({ onSelect, enableMockIntegration = false
</Card>
))}
{isError && (
<Center p="xl">
<Text c="red" role="alert">
{t("common.error")}
</Text>
</Center>
)}
{!isPending && !isError && filtered.length === 0 && (
{filtered.length === 0 && (
<Center p="xl">
<Text c="dimmed">{t("common.noResults")}</Text>
</Center>