mirror of
https://github.com/ajnart/homarr.git
synced 2025-10-26 08:06:12 +01:00
📈 Add umami analytics using homarr.dev
This commit is contained in:
20
.env.example
20
.env.example
@@ -1,22 +1,14 @@
|
|||||||
# Since the ".env" file is gitignored, you can use the ".env.example" file to
|
|
||||||
# build a new ".env" file when you clone the repo. Keep this file up-to-date
|
|
||||||
# when you add new variables to `.env`.
|
|
||||||
|
|
||||||
# This file will be committed to version control, so make sure not to have any
|
|
||||||
# secrets in it. If you are cloning this repo, create a copy of this file named
|
|
||||||
# ".env" and populate it with your secrets.
|
|
||||||
|
|
||||||
# When adding additional environment variables, the schema in "/src/env.js"
|
|
||||||
# should be updated accordingly.
|
|
||||||
|
|
||||||
# Database
|
|
||||||
DATABASE_URL="file:./database/db.sqlite"
|
DATABASE_URL="file:./database/db.sqlite"
|
||||||
|
|
||||||
# Next Auth
|
# Next Auth
|
||||||
# You can generate a new secret on the command line with:
|
# You can generate a new secret on the command line with:
|
||||||
# openssl rand -base64 32
|
# openssl rand -base64 32
|
||||||
# https://next-auth.js.org/configuration/options#secret
|
# https://next-auth.js.org/configuration/options#secret
|
||||||
# NEXTAUTH_SECRET=""
|
|
||||||
NEXTAUTH_URL="http://localhost:3000"
|
NEXTAUTH_URL="http://localhost:3000"
|
||||||
|
|
||||||
NEXTAUTH_SECRET=""
|
NEXTAUTH_SECRET="anything"
|
||||||
|
|
||||||
|
# Disable analytics
|
||||||
|
NEXT_PUBLIC_DISABLE_ANALYTICS="true"
|
||||||
|
|
||||||
|
DEFAULT_COLOR_SCHEME="light"
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
"@nivo/core": "^0.83.0",
|
"@nivo/core": "^0.83.0",
|
||||||
"@nivo/line": "^0.83.0",
|
"@nivo/line": "^0.83.0",
|
||||||
"@react-native-async-storage/async-storage": "^1.18.1",
|
"@react-native-async-storage/async-storage": "^1.18.1",
|
||||||
"@t3-oss/env-nextjs": "^0.6.0",
|
"@t3-oss/env-nextjs": "^0.7.1",
|
||||||
"@tabler/icons-react": "^2.20.0",
|
"@tabler/icons-react": "^2.20.0",
|
||||||
"@tanstack/query-async-storage-persister": "^4.27.1",
|
"@tanstack/query-async-storage-persister": "^4.27.1",
|
||||||
"@tanstack/query-sync-storage-persister": "^4.27.1",
|
"@tanstack/query-sync-storage-persister": "^4.27.1",
|
||||||
@@ -118,6 +118,7 @@
|
|||||||
"@types/node": "18.17.8",
|
"@types/node": "18.17.8",
|
||||||
"@types/prismjs": "^1.26.0",
|
"@types/prismjs": "^1.26.0",
|
||||||
"@types/react": "^18.2.11",
|
"@types/react": "^18.2.11",
|
||||||
|
"@types/umami": "^0.1.4",
|
||||||
"@types/uuid": "^9.0.0",
|
"@types/uuid": "^9.0.0",
|
||||||
"@types/video.js": "^7.3.51",
|
"@types/video.js": "^7.3.51",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { useStyles } from './styles';
|
|||||||
|
|
||||||
interface GenericAvailableElementTypeProps {
|
interface GenericAvailableElementTypeProps {
|
||||||
name: string;
|
name: string;
|
||||||
|
id: string;
|
||||||
handleAddition: () => Promise<void>;
|
handleAddition: () => Promise<void>;
|
||||||
description?: string;
|
description?: string;
|
||||||
image: string | Icon;
|
image: string | Icon;
|
||||||
@@ -16,6 +17,7 @@ interface GenericAvailableElementTypeProps {
|
|||||||
|
|
||||||
export const GenericAvailableElementType = ({
|
export const GenericAvailableElementType = ({
|
||||||
name,
|
name,
|
||||||
|
id,
|
||||||
description,
|
description,
|
||||||
image,
|
image,
|
||||||
disabled,
|
disabled,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Grid, Text } from '@mantine/core';
|
import { Container, Grid, Text } from '@mantine/core';
|
||||||
import { IconCursorText } from '@tabler/icons-react';
|
import { IconCursorText } from '@tabler/icons-react';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
|
||||||
@@ -21,12 +21,13 @@ export const AvailableStaticTypes = ({ onClickBack }: AvailableStaticTypesProps)
|
|||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Grid grow>
|
<Grid grow>
|
||||||
|
{/*
|
||||||
<GenericAvailableElementType
|
<GenericAvailableElementType
|
||||||
name="Static Text"
|
name="Static Text"
|
||||||
description="Display a fixed string on your dashboard"
|
description="Display a fixed string on your dashboard"
|
||||||
image={IconCursorText}
|
image={IconCursorText}
|
||||||
handleAddition={/* TODO: add something? */ async () => {}}
|
handleAddition={async () => {}}
|
||||||
/>
|
/> */}
|
||||||
</Grid>
|
</Grid>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import { showNotification } from '@mantine/notifications';
|
|||||||
import { Icon, IconChecks } from '@tabler/icons-react';
|
import { Icon, IconChecks } from '@tabler/icons-react';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
import { useConfigContext } from '~/config/provider';
|
import { useConfigContext } from '~/config/provider';
|
||||||
import { useConfigStore } from '~/config/store';
|
import { useConfigStore } from '~/config/store';
|
||||||
import { IWidget, IWidgetDefinition } from '~/widgets/widgets';
|
import { IWidget, IWidgetDefinition } from '~/widgets/widgets';
|
||||||
|
|
||||||
import { useEditModeStore } from '../../../../Views/useEditModeStore';
|
import { useEditModeStore } from '../../../../Views/useEditModeStore';
|
||||||
import { GenericAvailableElementType } from '../Shared/GenericElementType';
|
import { GenericAvailableElementType } from '../Shared/GenericElementType';
|
||||||
|
|
||||||
@@ -97,6 +97,7 @@ export const WidgetElementType = ({ id, image, disabled, widget }: WidgetElement
|
|||||||
icon: <IconChecks stroke={1.5} />,
|
icon: <IconChecks stroke={1.5} />,
|
||||||
color: 'teal',
|
color: 'teal',
|
||||||
});
|
});
|
||||||
|
umami.track('Add widget', { id: widget.id });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -104,6 +105,7 @@ export const WidgetElementType = ({ id, image, disabled, widget }: WidgetElement
|
|||||||
name={t('descriptor.name')}
|
name={t('descriptor.name')}
|
||||||
description={t('descriptor.description') ?? undefined}
|
description={t('descriptor.description') ?? undefined}
|
||||||
image={image}
|
image={image}
|
||||||
|
id={widget.id}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
handleAddition={handleAddition}
|
handleAddition={handleAddition}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -58,7 +58,9 @@ export const CreateBoardModal = ({ id }: ContextModalProps<{}>) => {
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={async () => {}}
|
onClick={async () => {
|
||||||
|
umami.track('Create new board')
|
||||||
|
}}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
variant="light"
|
variant="light"
|
||||||
color="green"
|
color="green"
|
||||||
|
|||||||
@@ -117,4 +117,5 @@ export const openDockerSelectBoardModal = (innerProps: InnerProps) => {
|
|||||||
),
|
),
|
||||||
innerProps,
|
innerProps,
|
||||||
});
|
});
|
||||||
|
umami.track('Add to homarr modal')
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ export const ReviewInputStep = ({ values, prevStep, nextStep }: ReviewInputStepP
|
|||||||
password: values.security.password,
|
password: values.security.password,
|
||||||
email: values.account.eMail === '' ? undefined : values.account.eMail,
|
email: values.account.eMail === '' ? undefined : values.account.eMail,
|
||||||
});
|
});
|
||||||
|
umami.track('Create user', { username: values.account.username});
|
||||||
}}
|
}}
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
rightIcon={<IconCheck size="1rem" />}
|
rightIcon={<IconCheck size="1rem" />}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ export const CreateAccountSecurityStep = ({
|
|||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const randomPassword = await mutateAsync();
|
const randomPassword = await mutateAsync();
|
||||||
form.setFieldValue('password', randomPassword);
|
form.setFieldValue('password', randomPassword);
|
||||||
|
umami.track('Generate random password');
|
||||||
}}
|
}}
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
variant="default"
|
variant="default"
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ export const StepCreateAccount = ({
|
|||||||
<Text>
|
<Text>
|
||||||
Your administrator account <b>must be secure</b>, that's why we have so many rules surrounding it.
|
Your administrator account <b>must be secure</b>, that's why we have so many rules surrounding it.
|
||||||
<br/>Try not to make it adminadmin this time...
|
<br/>Try not to make it adminadmin this time...
|
||||||
|
<br/>Note: these password requirements <b>are not forced</b>, they are just recommendations.
|
||||||
</Text>
|
</Text>
|
||||||
<form onSubmit={form.onSubmit(handleSubmit)}>
|
<form onSubmit={form.onSubmit(handleSubmit)}>
|
||||||
<Stack>
|
<Stack>
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { useScreenLargerThan } from '~/hooks/useScreenLargerThan';
|
|||||||
import { api } from '~/utils/api';
|
import { api } from '~/utils/api';
|
||||||
|
|
||||||
import { MainLayout } from './MainLayout';
|
import { MainLayout } from './MainLayout';
|
||||||
|
import { env } from 'process';
|
||||||
|
|
||||||
type BoardLayoutProps = {
|
type BoardLayoutProps = {
|
||||||
dockerEnabled: boolean;
|
dockerEnabled: boolean;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ const env = createEnv({
|
|||||||
*/
|
*/
|
||||||
client: {
|
client: {
|
||||||
// NEXT_PUBLIC_CLIENTVAR: z.string().min(1),
|
// NEXT_PUBLIC_CLIENTVAR: z.string().min(1),
|
||||||
|
NEXT_PUBLIC_DISABLE_ANALYTICS: z.string(),
|
||||||
NEXT_PUBLIC_PORT: portSchema,
|
NEXT_PUBLIC_PORT: portSchema,
|
||||||
NEXT_PUBLIC_NODE_ENV: envSchema,
|
NEXT_PUBLIC_NODE_ENV: envSchema,
|
||||||
NEXT_PUBLIC_DEFAULT_COLOR_SCHEME: z
|
NEXT_PUBLIC_DEFAULT_COLOR_SCHEME: z
|
||||||
@@ -46,7 +47,6 @@ const env = createEnv({
|
|||||||
.default('light'),
|
.default('light'),
|
||||||
NEXT_PUBLIC_DOCKER_HOST: z.string().optional(),
|
NEXT_PUBLIC_DOCKER_HOST: z.string().optional(),
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g.
|
* You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g.
|
||||||
* middlewares) or client-side so we need to destruct manually.
|
* middlewares) or client-side so we need to destruct manually.
|
||||||
@@ -55,6 +55,7 @@ const env = createEnv({
|
|||||||
DATABASE_URL: process.env.DATABASE_URL,
|
DATABASE_URL: process.env.DATABASE_URL,
|
||||||
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
|
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
|
||||||
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
|
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
|
||||||
|
NEXT_PUBLIC_DISABLE_ANALYTICS: process.env.NEXT_PUBLIC_DISABLE_ANALYTICS,
|
||||||
DOCKER_HOST: process.env.DOCKER_HOST,
|
DOCKER_HOST: process.env.DOCKER_HOST,
|
||||||
DOCKER_PORT: process.env.DOCKER_PORT,
|
DOCKER_PORT: process.env.DOCKER_PORT,
|
||||||
VERCEL_URL: process.env.VERCEL_URL,
|
VERCEL_URL: process.env.VERCEL_URL,
|
||||||
|
|||||||
@@ -13,27 +13,28 @@ import { Session } from 'next-auth';
|
|||||||
import { SessionProvider, getSession } from 'next-auth/react';
|
import { SessionProvider, getSession } from 'next-auth/react';
|
||||||
import { appWithTranslation } from 'next-i18next';
|
import { appWithTranslation } from 'next-i18next';
|
||||||
import { AppProps } from 'next/app';
|
import { AppProps } from 'next/app';
|
||||||
|
import Script from 'next/script';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import 'video.js/dist/video-js.css';
|
import 'video.js/dist/video-js.css';
|
||||||
import { CommonHead } from '~/components/layout/Meta/CommonHead';
|
import { CommonHead } from '~/components/layout/Meta/CommonHead';
|
||||||
|
import { ConfigProvider } from '~/config/provider';
|
||||||
import { env } from '~/env.js';
|
import { env } from '~/env.js';
|
||||||
import { ColorSchemeProvider } from '~/hooks/use-colorscheme';
|
import { ColorSchemeProvider } from '~/hooks/use-colorscheme';
|
||||||
import { modals } from '~/modals';
|
import { modals } from '~/modals';
|
||||||
|
import { ColorTheme } from '~/tools/color';
|
||||||
import { getLanguageByCode } from '~/tools/language';
|
import { getLanguageByCode } from '~/tools/language';
|
||||||
|
import {
|
||||||
|
ServerSidePackageAttributesType,
|
||||||
|
getServiceSidePackageAttributes,
|
||||||
|
} from '~/tools/server/getPackageVersion';
|
||||||
|
import { theme } from '~/tools/server/theme/theme';
|
||||||
import { ConfigType } from '~/types/config';
|
import { ConfigType } from '~/types/config';
|
||||||
import { api } from '~/utils/api';
|
import { api } from '~/utils/api';
|
||||||
import { colorSchemeParser } from '~/validations/user';
|
import { colorSchemeParser } from '~/validations/user';
|
||||||
|
|
||||||
import { COOKIE_COLOR_SCHEME_KEY, COOKIE_LOCALE_KEY } from '../../data/constants';
|
import { COOKIE_COLOR_SCHEME_KEY, COOKIE_LOCALE_KEY } from '../../data/constants';
|
||||||
import nextI18nextConfig from '../../next-i18next.config.js';
|
import nextI18nextConfig from '../../next-i18next.config.js';
|
||||||
import { ConfigProvider } from '~/config/provider';
|
|
||||||
import '../styles/global.scss';
|
import '../styles/global.scss';
|
||||||
import { ColorTheme } from '~/tools/color';
|
|
||||||
import {
|
|
||||||
ServerSidePackageAttributesType,
|
|
||||||
getServiceSidePackageAttributes,
|
|
||||||
} from '~/tools/server/getPackageVersion';
|
|
||||||
import { theme } from '~/tools/server/theme/theme';
|
|
||||||
|
|
||||||
dayjs.extend(locale);
|
dayjs.extend(locale);
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
@@ -92,6 +93,13 @@ function App(
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CommonHead />
|
<CommonHead />
|
||||||
|
{env.NEXT_PUBLIC_DISABLE_ANALYTICS !== 'true' && (
|
||||||
|
<Script
|
||||||
|
src="https://umami.homarr.dev/script.js"
|
||||||
|
data-website-id="f133f10c-30a7-4506-889c-3a803f328fa4"
|
||||||
|
strategy="lazyOnload"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<SessionProvider session={pageProps.session}>
|
<SessionProvider session={pageProps.session}>
|
||||||
<ColorSchemeProvider {...pageProps}>
|
<ColorSchemeProvider {...pageProps}>
|
||||||
{(colorScheme) => (
|
{(colorScheme) => (
|
||||||
|
|||||||
34
yarn.lock
34
yarn.lock
@@ -1919,25 +1919,31 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@t3-oss/env-core@npm:0.6.1":
|
"@t3-oss/env-core@npm:0.7.1":
|
||||||
version: 0.6.1
|
version: 0.7.1
|
||||||
resolution: "@t3-oss/env-core@npm:0.6.1"
|
resolution: "@t3-oss/env-core@npm:0.7.1"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: ">=4.7.2"
|
typescript: ">=4.7.2"
|
||||||
zod: ^3.0.0
|
zod: ^3.0.0
|
||||||
checksum: 6a1dd9d2f88643f6315a3add7eb6053a436a953a3c2b59b9743a2d8f28dbafcdaf4f6bccf0e7dfe424189834f75fbb53b9ac3891076a19b289468e4b406a340b
|
peerDependenciesMeta:
|
||||||
|
typescript:
|
||||||
|
optional: true
|
||||||
|
checksum: 1a8447f5009931eb24a2e653723acd817585539d882c6c97b1df590df605045dee0e9abb3e6af083ec1492a6c143469947c4e4fef8d6c4a7e64cdf61ee8748d0
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@t3-oss/env-nextjs@npm:^0.6.0":
|
"@t3-oss/env-nextjs@npm:^0.7.1":
|
||||||
version: 0.6.1
|
version: 0.7.1
|
||||||
resolution: "@t3-oss/env-nextjs@npm:0.6.1"
|
resolution: "@t3-oss/env-nextjs@npm:0.7.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@t3-oss/env-core": 0.6.1
|
"@t3-oss/env-core": 0.7.1
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: ">=4.7.2"
|
typescript: ">=4.7.2"
|
||||||
zod: ^3.0.0
|
zod: ^3.0.0
|
||||||
checksum: c103a501b186c2a1633ead602fbd4e2d3096f736dceecba2f88a8c7b016c6f982a42bb8eb948e8069df93bf8d51a8a973e49a0a497d3da9282933683ced665e5
|
peerDependenciesMeta:
|
||||||
|
typescript:
|
||||||
|
optional: true
|
||||||
|
checksum: a1183bca68de4c3670a6e2e61839a9e54ab6472fbe49d4fc34665e43f2f05b379ae9b1880a122eb1bc42f7d6bd1160a543bbd3727543022d8f1fdc5b26290984
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -3102,6 +3108,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@types/umami@npm:^0.1.4":
|
||||||
|
version: 0.1.4
|
||||||
|
resolution: "@types/umami@npm:0.1.4"
|
||||||
|
checksum: d3a970aec4553db91475fd67d8ca734ae18033c00b7ba9251e7a90050b6bd0f7961bf50ac1f576575a24ffa9b7ab78960cac14df4005a7b8429cb1682af013c2
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@types/uuid@npm:^9.0.0":
|
"@types/uuid@npm:^9.0.0":
|
||||||
version: 9.0.4
|
version: 9.0.4
|
||||||
resolution: "@types/uuid@npm:9.0.4"
|
resolution: "@types/uuid@npm:9.0.4"
|
||||||
@@ -7581,7 +7594,7 @@ __metadata:
|
|||||||
"@nivo/core": ^0.83.0
|
"@nivo/core": ^0.83.0
|
||||||
"@nivo/line": ^0.83.0
|
"@nivo/line": ^0.83.0
|
||||||
"@react-native-async-storage/async-storage": ^1.18.1
|
"@react-native-async-storage/async-storage": ^1.18.1
|
||||||
"@t3-oss/env-nextjs": ^0.6.0
|
"@t3-oss/env-nextjs": ^0.7.1
|
||||||
"@tabler/icons-react": ^2.20.0
|
"@tabler/icons-react": ^2.20.0
|
||||||
"@tanstack/query-async-storage-persister": ^4.27.1
|
"@tanstack/query-async-storage-persister": ^4.27.1
|
||||||
"@tanstack/query-sync-storage-persister": ^4.27.1
|
"@tanstack/query-sync-storage-persister": ^4.27.1
|
||||||
@@ -7606,6 +7619,7 @@ __metadata:
|
|||||||
"@types/node": 18.17.8
|
"@types/node": 18.17.8
|
||||||
"@types/prismjs": ^1.26.0
|
"@types/prismjs": ^1.26.0
|
||||||
"@types/react": ^18.2.11
|
"@types/react": ^18.2.11
|
||||||
|
"@types/umami": ^0.1.4
|
||||||
"@types/uuid": ^9.0.0
|
"@types/uuid": ^9.0.0
|
||||||
"@types/video.js": ^7.3.51
|
"@types/video.js": ^7.3.51
|
||||||
"@typescript-eslint/eslint-plugin": ^6.0.0
|
"@typescript-eslint/eslint-plugin": ^6.0.0
|
||||||
|
|||||||
Reference in New Issue
Block a user