feat: add analytics (#528)
* feat: add analytics * feat: send data to umami * refactor: fix format * fix: click behavior of switches * refactor: format * chore: rerun ci --------- Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
"with-env": "dotenv -e ../../.env --"
|
||||
},
|
||||
"dependencies": {
|
||||
"@homarr/analytics": "workspace:^0.1.0",
|
||||
"@homarr/api": "workspace:^0.1.0",
|
||||
"@homarr/auth": "workspace:^0.1.0",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
|
||||
@@ -11,6 +11,7 @@ import { auth } from "@homarr/auth/next";
|
||||
import { ModalProvider } from "@homarr/modals";
|
||||
import { Notifications } from "@homarr/notifications";
|
||||
|
||||
import { Analytics } from "~/components/layout/analytics";
|
||||
import { JotaiProvider } from "./_client-providers/jotai";
|
||||
import { NextInternationalProvider } from "./_client-providers/next-international";
|
||||
import { AuthProvider } from "./_client-providers/session";
|
||||
@@ -76,6 +77,7 @@ export default function Layout(props: { children: React.ReactNode; params: { loc
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<head>
|
||||
<ColorSchemeScript defaultColorScheme={colorScheme} />
|
||||
<Analytics />
|
||||
</head>
|
||||
<body className={["font-sans", fontSans.variable].join(" ")}>
|
||||
<StackedProvider>
|
||||
|
||||
@@ -96,18 +96,23 @@ const SwitchSetting = ({
|
||||
title: string;
|
||||
text: ReactNode;
|
||||
}) => {
|
||||
const disabled = formKey !== "enableGeneral" && !form.values.enableGeneral;
|
||||
const handleClick = React.useCallback(() => {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
form.setFieldValue(formKey, !form.values[formKey]);
|
||||
}, [form, formKey]);
|
||||
}, [form, formKey, disabled]);
|
||||
|
||||
return (
|
||||
<UnstyledButton onClick={handleClick}>
|
||||
<Group ms={ms} justify="space-between" gap="lg" align="center" wrap="nowrap">
|
||||
<Group ms={ms} justify="space-between" gap="lg" align="center" wrap="nowrap">
|
||||
<UnstyledButton style={{ flexGrow: 1 }} onClick={handleClick}>
|
||||
<Stack gap={0}>
|
||||
<Text fw="bold">{title}</Text>
|
||||
<Text c="gray.5">{text}</Text>
|
||||
</Stack>
|
||||
<Switch {...form.getInputProps(formKey, { type: "checkbox" })} />
|
||||
</Group>
|
||||
</UnstyledButton>
|
||||
</UnstyledButton>
|
||||
<Switch disabled={disabled} onClick={handleClick} checked={form.values[formKey] && !disabled} />
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
14
apps/nextjs/src/components/layout/analytics.tsx
Normal file
14
apps/nextjs/src/components/layout/analytics.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import Script from "next/script";
|
||||
|
||||
import { UMAMI_WEBSITE_ID } from "@homarr/analytics";
|
||||
import { api } from "@homarr/api/server";
|
||||
|
||||
export const Analytics = async () => {
|
||||
const analytics = await api.serverSettings.getAnalytics();
|
||||
|
||||
if (analytics.enableGeneral) {
|
||||
return <Script src="https://umami.homarr.dev/script.js" data-website-id={UMAMI_WEBSITE_ID} defer />;
|
||||
}
|
||||
|
||||
return <></>;
|
||||
};
|
||||
@@ -27,6 +27,7 @@
|
||||
"@homarr/redis": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@homarr/analytics": "workspace:^0.1.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"node-cron": "^3.0.3",
|
||||
"superjson": "2.2.1"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { iconsUpdaterJob } from "~/jobs/icons-updater";
|
||||
import { analyticsJob } from "./jobs/analytics";
|
||||
import { queuesJob } from "./jobs/queue";
|
||||
import { createJobGroup } from "./lib/cron-job/group";
|
||||
|
||||
@@ -8,4 +9,5 @@ export const jobs = createJobGroup({
|
||||
// This job is used to process queues.
|
||||
queues: queuesJob,
|
||||
iconsUpdater: iconsUpdaterJob,
|
||||
analytics: analyticsJob,
|
||||
});
|
||||
|
||||
29
apps/tasks/src/jobs/analytics.ts
Normal file
29
apps/tasks/src/jobs/analytics.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import SuperJSON from "superjson";
|
||||
|
||||
import { sendServerAnalyticsAsync } from "@homarr/analytics";
|
||||
import { db, eq } from "@homarr/db";
|
||||
import { serverSettings } from "@homarr/db/schema/sqlite";
|
||||
|
||||
import { EVERY_WEEK } from "~/lib/cron-job/constants";
|
||||
import { createCronJob } from "~/lib/cron-job/creator";
|
||||
import type { defaultServerSettings } from "../../../../packages/server-settings";
|
||||
|
||||
export const analyticsJob = createCronJob(EVERY_WEEK, {
|
||||
runOnStart: true,
|
||||
}).withCallback(async () => {
|
||||
const analyticSetting = await db.query.serverSettings.findFirst({
|
||||
where: eq(serverSettings.settingKey, "analytics"),
|
||||
});
|
||||
|
||||
if (!analyticSetting) {
|
||||
return;
|
||||
}
|
||||
|
||||
const value = SuperJSON.parse<(typeof defaultServerSettings)["analytics"]>(analyticSetting.value);
|
||||
|
||||
if (!value.enableGeneral) {
|
||||
return;
|
||||
}
|
||||
|
||||
await sendServerAnalyticsAsync();
|
||||
});
|
||||
Reference in New Issue
Block a user