* feat(analytics): consolidate server analytics into single Umami event - Replace multiple separate events with single 'server-analytics' event - Add comprehensive metrics: Homarr version, database type, Docker/K8s status - Add auth providers status (credentials, OIDC, LDAP) - Add counts for users, boards, groups, apps, widgets, sections, integrations - Add counts for search engines, icon repos, icons, API keys, invites, media - Add counts for layouts, item layouts, section layouts, cron job configs - Include per-integration-kind breakdown (integration_sonarr, etc.) - Add required dependencies: @homarr/auth, @homarr/common, @homarr/docker * refactor(ui): simplify analytics settings to single toggle - Remove individual toggles for widget/integration/user data - Keep only the main 'Enable analytics' toggle - All data is now collected when analytics are enabled - Update both init settings and manage settings pages * docs(i18n): update analytics description with collected data details - Explain all data points collected when analytics are enabled - List: version, DB type, Docker/K8s, auth methods, counts - Clarify that no personal info, URLs, or names are collected * chore(deps): update pnpm-lock.yaml for analytics package dependencies
110 lines
3.6 KiB
TypeScript
110 lines
3.6 KiB
TypeScript
import type { UmamiEventData } from "@umami/node";
|
|
import { Umami } from "@umami/node";
|
|
|
|
import { isProviderEnabled } from "@homarr/auth/server";
|
|
import { Stopwatch } from "@homarr/common";
|
|
import { createLogger } from "@homarr/core/infrastructure/logs";
|
|
import { count, db } from "@homarr/db";
|
|
import { isMysql, isPostgresql } from "@homarr/db/collection";
|
|
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
|
|
import {
|
|
apiKeys,
|
|
apps,
|
|
boards,
|
|
cronJobConfigurations,
|
|
groups,
|
|
iconRepositories,
|
|
icons,
|
|
integrations,
|
|
invites,
|
|
itemLayouts,
|
|
items,
|
|
layouts,
|
|
medias,
|
|
searchEngines,
|
|
sectionLayouts,
|
|
sections,
|
|
users,
|
|
} from "@homarr/db/schema";
|
|
import { env as dockerEnv } from "@homarr/docker/env";
|
|
|
|
import packageJson from "../../../package.json";
|
|
import { UMAMI_HOST_URL, UMAMI_WEBSITE_ID } from "./constants";
|
|
|
|
const logger = createLogger({ module: "analytics" });
|
|
|
|
const getDatabaseType = (): "mysql" | "postgresql" | "sqlite" => {
|
|
if (isMysql()) return "mysql";
|
|
if (isPostgresql()) return "postgresql";
|
|
return "sqlite";
|
|
};
|
|
|
|
const getEnabledAuthProviders = (): string[] => {
|
|
const providers: string[] = [];
|
|
if (isProviderEnabled("credentials")) providers.push("credentials");
|
|
if (isProviderEnabled("oidc")) providers.push("oidc");
|
|
if (isProviderEnabled("ldap")) providers.push("ldap");
|
|
return providers;
|
|
};
|
|
|
|
export const sendServerAnalyticsAsync = async () => {
|
|
const stopWatch = new Stopwatch();
|
|
const analyticsSettings = await getServerSettingByKeyAsync(db, "analytics");
|
|
|
|
if (!analyticsSettings.enableGeneral) {
|
|
logger.info("Analytics are disabled. No data will be sent. Enable analytics in the settings");
|
|
return;
|
|
}
|
|
|
|
const umamiInstance = new Umami();
|
|
umamiInstance.init({
|
|
hostUrl: UMAMI_HOST_URL,
|
|
websiteId: UMAMI_WEBSITE_ID,
|
|
});
|
|
|
|
const enabledAuthProviders = getEnabledAuthProviders();
|
|
|
|
const analyticsData: UmamiEventData = {
|
|
homarrVersion: packageJson.version,
|
|
databaseType: getDatabaseType(),
|
|
dockerEnabled: dockerEnv.ENABLE_DOCKER ? 1 : 0,
|
|
kubernetesEnabled: dockerEnv.ENABLE_KUBERNETES ? 1 : 0,
|
|
authCredentials: enabledAuthProviders.includes("credentials") ? 1 : 0,
|
|
authOidc: enabledAuthProviders.includes("oidc") ? 1 : 0,
|
|
authLdap: enabledAuthProviders.includes("ldap") ? 1 : 0,
|
|
countUsers: await db.$count(users),
|
|
countBoards: await db.$count(boards),
|
|
countGroups: await db.$count(groups),
|
|
countApps: await db.$count(apps),
|
|
countWidgets: await db.$count(items),
|
|
countSections: await db.$count(sections),
|
|
countIntegrations: await db.$count(integrations),
|
|
countSearchEngines: await db.$count(searchEngines),
|
|
countIconRepositories: await db.$count(iconRepositories),
|
|
countIcons: await db.$count(icons),
|
|
countApiKeys: await db.$count(apiKeys),
|
|
countInvites: await db.$count(invites),
|
|
countMedias: await db.$count(medias),
|
|
countLayouts: await db.$count(layouts),
|
|
countItemLayouts: await db.$count(itemLayouts),
|
|
countSectionLayouts: await db.$count(sectionLayouts),
|
|
countCronJobConfigs: await db.$count(cronJobConfigurations),
|
|
};
|
|
|
|
const integrationKinds = await db
|
|
.select({ kind: integrations.kind, count: count(integrations.id) })
|
|
.from(integrations)
|
|
.groupBy(integrations.kind);
|
|
|
|
integrationKinds.forEach((integrationKind) => {
|
|
analyticsData[`integration_${integrationKind.kind}`] = integrationKind.count;
|
|
});
|
|
|
|
const response = await umamiInstance.track("server-analytics-v2", analyticsData);
|
|
if (!response.ok) {
|
|
logger.warn("Unable to send analytics to Umami instance");
|
|
}
|
|
|
|
logger.info(`Sent analytics in ${stopWatch.getElapsedInHumanWords()}`);
|
|
};
|