feat: add onboarding with oldmarr import (#1606)

This commit is contained in:
Meier Lukas
2024-12-15 15:40:26 +01:00
committed by GitHub
parent 82ec77d2da
commit 6de74d9525
108 changed files with 6045 additions and 312 deletions

View File

@@ -0,0 +1,59 @@
import type { InferSelectModel } from "@homarr/db";
import type { apps } from "@homarr/db/schema/sqlite";
import type { OldmarrConfig } from "@homarr/old-schema";
import type { ValidAnalyseConfig } from "../analyse/types";
import { mapOldmarrApp, mapOldmarrBookmarkApp } from "../mappers/map-app";
import type { OldmarrBookmarkDefinition } from "../widgets/definitions/bookmark";
export type PreparedApp = Omit<InferSelectModel<typeof apps>, "id"> & { ids: string[]; existingId?: string };
export const prepareApps = (analyseConfigs: ValidAnalyseConfig[]) => {
const preparedApps: PreparedApp[] = [];
analyseConfigs.forEach(({ config }) => {
const appsFromConfig = extractAppsFromConfig(config).concat(extractBookmarkAppsFromConfig(config));
addAppsToPreparedApps(preparedApps, appsFromConfig);
});
return preparedApps;
};
const extractAppsFromConfig = (config: OldmarrConfig) => {
return config.apps.map(mapOldmarrApp);
};
const extractBookmarkAppsFromConfig = (config: OldmarrConfig) => {
const bookmarkWidgets = config.widgets.filter((widget) => widget.type === "bookmark");
return bookmarkWidgets.flatMap((widget) =>
(widget.properties as OldmarrBookmarkDefinition["options"]).items.map(mapOldmarrBookmarkApp),
);
};
const addAppsToPreparedApps = (preparedApps: PreparedApp[], configApps: InferSelectModel<typeof apps>[]) => {
configApps.forEach(({ id, ...app }) => {
const existingApp = preparedApps.find((preparedApp) => doAppsMatch(preparedApp, app));
if (existingApp) {
existingApp.ids.push(id);
return;
}
preparedApps.push({
...app,
ids: [id],
});
});
};
export const doAppsMatch = (
app1: Omit<InferSelectModel<typeof apps>, "id">,
app2: Omit<InferSelectModel<typeof apps>, "id">,
) => {
return (
app1.name === app2.name &&
app1.iconUrl === app2.iconUrl &&
app1.description === app2.description &&
app1.href === app2.href
);
};

View File

@@ -0,0 +1,34 @@
import { objectEntries } from "@homarr/common";
import type { BoardSize } from "@homarr/old-schema";
import type { ValidAnalyseConfig } from "../analyse/types";
import type { BoardSelectionMap } from "../components/initial/board-selection-card";
const boardSizeSuffix: Record<BoardSize, string> = {
lg: "large",
md: "medium",
sm: "small",
};
export const createBoardName = (fileName: string, boardSize: BoardSize) => {
return `${fileName.replace(".json", "")}-${boardSizeSuffix[boardSize]}`;
};
export const prepareBoards = (analyseConfigs: ValidAnalyseConfig[], selections: BoardSelectionMap) => {
return analyseConfigs.flatMap(({ name, config }) => {
const selectedSizes = selections.get(name);
if (!selectedSizes) return [];
return objectEntries(selectedSizes)
.map(([size, selected]) => {
if (!selected) return null;
return {
name: createBoardName(name, size),
size,
config,
};
})
.filter((board) => board !== null);
});
};

View File

@@ -0,0 +1,19 @@
import type { ValidAnalyseConfig } from "../analyse/types";
export type PreparedIntegration = ReturnType<typeof prepareIntegrations>[number];
export const prepareIntegrations = (analyseConfigs: ValidAnalyseConfig[]) => {
return analyseConfigs.flatMap(({ config }) => {
return config.apps
.map((app) =>
app.integration?.type
? {
...app.integration,
name: app.name,
url: app.url,
}
: null,
)
.filter((integration) => integration !== null);
});
};

View File

@@ -0,0 +1,14 @@
import type { BoardSize, OldmarrConfig } from "@homarr/old-schema";
import { mapApp, mapWidget } from "../mappers/map-item";
export const prepareItems = (
{ apps, widgets }: Pick<OldmarrConfig, "apps" | "widgets">,
boardSize: BoardSize,
appsMap: Map<string, { id: string }>,
sectionMap: Map<string, { id: string }>,
) =>
widgets
.map((widget) => mapWidget(widget, boardSize, appsMap, sectionMap))
.filter((widget) => widget !== null)
.concat(apps.map((app) => mapApp(app, boardSize, appsMap, sectionMap)));

View File

@@ -0,0 +1,25 @@
import type { AnalyseConfig, ValidAnalyseConfig } from "../analyse/types";
import type { BoardSelectionMap } from "../components/initial/board-selection-card";
import type { InitialOldmarrImportSettings } from "../settings";
import { prepareApps } from "./prepare-apps";
import { prepareBoards } from "./prepare-boards";
import { prepareIntegrations } from "./prepare-integrations";
export const prepareMultipleImports = (
analyseConfigs: AnalyseConfig[],
settings: InitialOldmarrImportSettings,
selections: BoardSelectionMap,
) => {
const invalidConfigs = analyseConfigs.filter((item) => item.config === null);
invalidConfigs.forEach(({ name }) => {
console.warn(`Skipping import of ${name} due to error in configuration. See logs of container for more details.`);
});
const filteredConfigs = analyseConfigs.filter((item): item is ValidAnalyseConfig => item.config !== null);
return {
preparedApps: prepareApps(filteredConfigs),
preparedBoards: settings.onlyImportApps ? [] : prepareBoards(filteredConfigs, selections),
preparedIntegrations: prepareIntegrations(filteredConfigs),
};
};

View File

@@ -0,0 +1,13 @@
import type { OldmarrConfig } from "@homarr/old-schema";
import { mapCategorySection, mapEmptySection } from "../mappers/map-section";
export const prepareSections = (
boardId: string,
{ categories, wrappers }: Pick<OldmarrConfig, "categories" | "wrappers">,
) =>
new Map(
categories
.map((category) => [category.id, mapCategorySection(boardId, category)] as const)
.concat(wrappers.map((wrapper) => [wrapper.id, mapEmptySection(boardId, wrapper)] as const)),
);

View File

@@ -0,0 +1,21 @@
import type { OldmarrConfig } from "@homarr/old-schema";
import type { OldmarrImportConfiguration } from "../settings";
import { prepareApps } from "./prepare-apps";
export const prepareSingleImport = (config: OldmarrConfig, settings: OldmarrImportConfiguration) => {
const validAnalyseConfigs = [{ name: settings.name, config, isError: false }];
return {
preparedApps: prepareApps(validAnalyseConfigs),
preparedBoards: settings.onlyImportApps
? []
: [
{
name: settings.name,
size: settings.screenSize,
config,
},
],
};
};