mirror of
https://github.com/kremalicious/umami.git
synced 2025-02-14 21:10:34 +01:00
Merge branch 'dev' into master
This commit is contained in:
commit
afbef196c2
@ -4,14 +4,6 @@
|
|||||||
"es2020": true,
|
"es2020": true,
|
||||||
"node": true
|
"node": true
|
||||||
},
|
},
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaFeatures": {
|
|
||||||
"jsx": true
|
|
||||||
},
|
|
||||||
"ecmaVersion": 11,
|
|
||||||
"sourceType": "module"
|
|
||||||
},
|
|
||||||
"extends": [
|
"extends": [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"plugin:prettier/recommended",
|
"plugin:prettier/recommended",
|
||||||
@ -19,6 +11,14 @@
|
|||||||
"plugin:@typescript-eslint/recommended",
|
"plugin:@typescript-eslint/recommended",
|
||||||
"next"
|
"next"
|
||||||
],
|
],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"jsx": true
|
||||||
|
},
|
||||||
|
"ecmaVersion": 11,
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
"plugins": ["@typescript-eslint", "prettier"],
|
"plugins": ["@typescript-eslint", "prettier"],
|
||||||
"settings": {
|
"settings": {
|
||||||
"import/resolver": {
|
"import/resolver": {
|
||||||
|
@ -3,14 +3,22 @@ require('dotenv').config();
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const pkg = require('./package.json');
|
const pkg = require('./package.json');
|
||||||
|
|
||||||
const contentSecurityPolicy = `
|
const contentSecurityPolicy = [
|
||||||
default-src 'self';
|
`default-src 'self'`,
|
||||||
img-src *;
|
`img-src *`,
|
||||||
script-src 'self' 'unsafe-eval' 'unsafe-inline';
|
`script-src 'self' 'unsafe-eval' 'unsafe-inline'`,
|
||||||
style-src 'self' 'unsafe-inline';
|
`style-src 'self' 'unsafe-inline'`,
|
||||||
connect-src 'self' api.umami.is;
|
`connect-src 'self' api.umami.is`,
|
||||||
frame-ancestors 'self' ${process.env.ALLOWED_FRAME_URLS};
|
`frame-src *`,
|
||||||
`;
|
];
|
||||||
|
|
||||||
|
const cspHeader = (values = []) => ({
|
||||||
|
key: 'Content-Security-Policy',
|
||||||
|
value: values
|
||||||
|
.join(';')
|
||||||
|
.replace(/\s{2,}/g, ' ')
|
||||||
|
.trim(),
|
||||||
|
});
|
||||||
|
|
||||||
const headers = [
|
const headers = [
|
||||||
{
|
{
|
||||||
@ -21,10 +29,18 @@ const headers = [
|
|||||||
key: 'X-Frame-Options',
|
key: 'X-Frame-Options',
|
||||||
value: 'SAMEORIGIN',
|
value: 'SAMEORIGIN',
|
||||||
},
|
},
|
||||||
|
cspHeader(contentSecurityPolicy),
|
||||||
|
];
|
||||||
|
|
||||||
|
const shareHeaders = [
|
||||||
{
|
{
|
||||||
key: 'Content-Security-Policy',
|
key: 'X-DNS-Prefetch-Control',
|
||||||
value: contentSecurityPolicy.replace(/\s{2,}/g, ' ').trim(),
|
value: 'on',
|
||||||
},
|
},
|
||||||
|
cspHeader([
|
||||||
|
...contentSecurityPolicy,
|
||||||
|
`frame-ancestors 'self' ${process.env.ALLOWED_FRAME_URLS || ''}`,
|
||||||
|
]),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (process.env.FORCE_SSL) {
|
if (process.env.FORCE_SSL) {
|
||||||
@ -81,14 +97,13 @@ const config = {
|
|||||||
reactStrictMode: false,
|
reactStrictMode: false,
|
||||||
env: {
|
env: {
|
||||||
basePath: basePath || '',
|
basePath: basePath || '',
|
||||||
cloudMode: !!process.env.CLOUD_MODE,
|
cloudMode: process.env.CLOUD_MODE || '',
|
||||||
cloudUrl: process.env.CLOUD_URL,
|
cloudUrl: process.env.CLOUD_URL || '',
|
||||||
configUrl: '/config',
|
configUrl: '/config',
|
||||||
currentVersion: pkg.version,
|
currentVersion: pkg.version,
|
||||||
defaultLocale: process.env.DEFAULT_LOCALE,
|
defaultLocale: process.env.DEFAULT_LOCALE || '',
|
||||||
disableLogin: process.env.DISABLE_LOGIN,
|
disableLogin: process.env.DISABLE_LOGIN || '',
|
||||||
disableUI: process.env.DISABLE_UI,
|
disableUI: process.env.DISABLE_UI || '',
|
||||||
isProduction: process.env.NODE_ENV === 'production',
|
|
||||||
},
|
},
|
||||||
basePath,
|
basePath,
|
||||||
output: 'standalone',
|
output: 'standalone',
|
||||||
@ -127,6 +142,10 @@ const config = {
|
|||||||
source: '/:path*',
|
source: '/:path*',
|
||||||
headers,
|
headers,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
source: '/share/:path*',
|
||||||
|
headers: shareHeaders,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
async rewrites() {
|
async rewrites() {
|
||||||
|
21
package.json
21
package.json
@ -63,11 +63,12 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@clickhouse/client": "^0.2.2",
|
"@clickhouse/client": "^0.2.2",
|
||||||
"@fontsource/inter": "^4.5.15",
|
"@fontsource/inter": "^4.5.15",
|
||||||
"@prisma/client": "5.3.1",
|
"@prisma/client": "5.4.2",
|
||||||
|
"@prisma/extension-read-replicas": "^0.3.0",
|
||||||
"@react-spring/web": "^9.7.3",
|
"@react-spring/web": "^9.7.3",
|
||||||
"@tanstack/react-query": "^4.33.0",
|
"@tanstack/react-query": "^4.33.0",
|
||||||
"@umami/prisma-client": "^0.3.0",
|
"@umami/prisma-client": "^0.5.0",
|
||||||
"@umami/redis-client": "^0.16.0",
|
"@umami/redis-client": "^0.18.0",
|
||||||
"chalk": "^4.1.1",
|
"chalk": "^4.1.1",
|
||||||
"chart.js": "^4.2.1",
|
"chart.js": "^4.2.1",
|
||||||
"chartjs-adapter-date-fns": "^3.0.0",
|
"chartjs-adapter-date-fns": "^3.0.0",
|
||||||
@ -92,17 +93,17 @@
|
|||||||
"kafkajs": "^2.1.0",
|
"kafkajs": "^2.1.0",
|
||||||
"maxmind": "^4.3.6",
|
"maxmind": "^4.3.6",
|
||||||
"moment-timezone": "^0.5.35",
|
"moment-timezone": "^0.5.35",
|
||||||
"next": "^13.5.3",
|
"next": "13.5.6",
|
||||||
"next-basics": "^0.37.0",
|
"next-basics": "^0.37.0",
|
||||||
"node-fetch": "^3.2.8",
|
"node-fetch": "^3.2.8",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"prisma": "5.3.1",
|
"prisma": "5.4.2",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-basics": "^0.105.0",
|
"react-basics": "^0.107.0",
|
||||||
"react-beautiful-dnd": "^13.1.0",
|
"react-beautiful-dnd": "^13.1.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-error-boundary": "^4.0.4",
|
"react-error-boundary": "^4.0.4",
|
||||||
"react-intl": "^6.4.7",
|
"react-intl": "^6.5.5",
|
||||||
"react-simple-maps": "^2.3.0",
|
"react-simple-maps": "^2.3.0",
|
||||||
"react-use-measure": "^2.0.4",
|
"react-use-measure": "^2.0.4",
|
||||||
"react-window": "^1.8.6",
|
"react-window": "^1.8.6",
|
||||||
@ -125,9 +126,9 @@
|
|||||||
"@rollup/plugin-replace": "^5.0.2",
|
"@rollup/plugin-replace": "^5.0.2",
|
||||||
"@svgr/rollup": "^8.1.0",
|
"@svgr/rollup": "^8.1.0",
|
||||||
"@svgr/webpack": "^8.1.0",
|
"@svgr/webpack": "^8.1.0",
|
||||||
"@types/node": "^18.11.9",
|
"@types/node": "^20.9.0",
|
||||||
"@types/react": "^18.0.25",
|
"@types/react": "^18.2.37",
|
||||||
"@types/react-dom": "^18.0.8",
|
"@types/react-dom": "^18.2.15",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.7.3",
|
"@typescript-eslint/eslint-plugin": "^6.7.3",
|
||||||
"@typescript-eslint/parser": "^6.7.3",
|
"@typescript-eslint/parser": "^6.7.3",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
|
@ -1,14 +1,23 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
import { Loading } from 'react-basics';
|
||||||
import Script from 'next/script';
|
import Script from 'next/script';
|
||||||
import { usePathname } from 'next/navigation';
|
import { usePathname } from 'next/navigation';
|
||||||
import UpdateNotice from 'components/common/UpdateNotice';
|
import { useLogin, useConfig } from 'components/hooks';
|
||||||
import { useRequireLogin, useConfig } from 'components/hooks';
|
import UpdateNotice from './UpdateNotice';
|
||||||
|
|
||||||
export function Shell({ children }) {
|
export function App({ children }) {
|
||||||
const { user } = useRequireLogin();
|
const { user, isLoading, error } = useLogin();
|
||||||
const config = useConfig();
|
const config = useConfig();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <Loading />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
window.location.href = `${process.env.basePath || ''}/login`;
|
||||||
|
}
|
||||||
|
|
||||||
if (!user || !config) {
|
if (!user || !config) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -24,4 +33,4 @@ export function Shell({ children }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Shell;
|
export default App;
|
@ -14,6 +14,7 @@ import styles from './NavBar.module.css';
|
|||||||
export function NavBar() {
|
export function NavBar() {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
const cloudMode = Boolean(process.env.cloudMode);
|
||||||
|
|
||||||
const links = [
|
const links = [
|
||||||
{ label: formatMessage(labels.dashboard), url: '/dashboard' },
|
{ label: formatMessage(labels.dashboard), url: '/dashboard' },
|
||||||
@ -22,6 +23,40 @@ export function NavBar() {
|
|||||||
{ label: formatMessage(labels.settings), url: '/settings' },
|
{ label: formatMessage(labels.settings), url: '/settings' },
|
||||||
].filter(n => n);
|
].filter(n => n);
|
||||||
|
|
||||||
|
const menuItems = [
|
||||||
|
{
|
||||||
|
label: formatMessage(labels.dashboard),
|
||||||
|
url: '/dashboard',
|
||||||
|
},
|
||||||
|
!cloudMode && {
|
||||||
|
label: formatMessage(labels.settings),
|
||||||
|
url: '/settings',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: formatMessage(labels.websites),
|
||||||
|
url: '/settings/websites',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: formatMessage(labels.teams),
|
||||||
|
url: '/settings/teams',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: formatMessage(labels.users),
|
||||||
|
url: '/settings/users',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: formatMessage(labels.profile),
|
||||||
|
url: '/settings/profile',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
cloudMode && {
|
||||||
|
label: formatMessage(labels.profile),
|
||||||
|
url: '/settings/profile',
|
||||||
|
},
|
||||||
|
!cloudMode && { label: formatMessage(labels.logout), url: '/logout' },
|
||||||
|
].filter(n => n);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.navbar}>
|
<div className={styles.navbar}>
|
||||||
<div className={styles.logo}>
|
<div className={styles.logo}>
|
||||||
@ -49,7 +84,7 @@ export function NavBar() {
|
|||||||
<ProfileButton />
|
<ProfileButton />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.mobile}>
|
<div className={styles.mobile}>
|
||||||
<HamburgerButton />
|
<HamburgerButton menuItems={menuItems} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import Dashboard from 'app/(main)/dashboard/Dashboard';
|
import Dashboard from 'app/(main)/dashboard/Dashboard';
|
||||||
import { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
|
|
||||||
export default function DashboardPage() {
|
export default function () {
|
||||||
return <Dashboard />;
|
return <Dashboard />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import Shell from './Shell';
|
import App from './App';
|
||||||
import NavBar from './NavBar';
|
import NavBar from './NavBar';
|
||||||
import Page from 'components/layout/Page';
|
import Page from 'components/layout/Page';
|
||||||
import styles from './layout.module.css';
|
import styles from './layout.module.css';
|
||||||
|
|
||||||
export default function AppLayout({ children }) {
|
export default function ({ children }) {
|
||||||
return (
|
return (
|
||||||
<Shell>
|
<App>
|
||||||
<main className={styles.layout}>
|
<main className={styles.layout}>
|
||||||
<nav className={styles.nav}>
|
<nav className={styles.nav}>
|
||||||
<NavBar />
|
<NavBar />
|
||||||
@ -14,6 +14,6 @@ export default function AppLayout({ children }) {
|
|||||||
<Page>{children}</Page>
|
<Page>{children}</Page>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
</Shell>
|
</App>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,9 @@ import useFilterQuery from 'components/hooks/useFilterQuery';
|
|||||||
import DataTable from 'components/common/DataTable';
|
import DataTable from 'components/common/DataTable';
|
||||||
import useCache from 'store/cache';
|
import useCache from 'store/cache';
|
||||||
|
|
||||||
export default function ReportsDataTable({ websiteId }) {
|
export default function ReportsDataTable({ websiteId }: { websiteId?: string }) {
|
||||||
const { get } = useApi();
|
const { get } = useApi();
|
||||||
const modified = useCache(state => state?.reports);
|
const modified = useCache(state => (state as any)?.reports);
|
||||||
const queryResult = useFilterQuery(['reports', { websiteId, modified }], params =>
|
const queryResult = useFilterQuery(['reports', { websiteId, modified }], params =>
|
||||||
get(websiteId ? `/websites/${websiteId}/reports` : `/reports`, params),
|
get(websiteId ? `/websites/${websiteId}/reports` : `/reports`, params),
|
||||||
);
|
);
|
@ -1,7 +1,7 @@
|
|||||||
import ReportsHeader from './ReportsHeader';
|
import ReportsHeader from './ReportsHeader';
|
||||||
import ReportsDataTable from './ReportsDataTable';
|
import ReportsDataTable from './ReportsDataTable';
|
||||||
|
|
||||||
export default function ReportsPage() {
|
export default function () {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ReportsHeader />
|
<ReportsHeader />
|
||||||
|
@ -2,11 +2,13 @@ import useApi from 'components/hooks/useApi';
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Button, Form, FormButtons, GridColumn, Loading, SubmitButton, Toggle } from 'react-basics';
|
import { Button, Form, FormButtons, GridColumn, Loading, SubmitButton, Toggle } from 'react-basics';
|
||||||
import useMessages from 'components/hooks/useMessages';
|
import useMessages from 'components/hooks/useMessages';
|
||||||
import WebsitesDataTable from '../../websites/WebsitesDataTable';
|
import WebsitesDataTable from 'app/(main)/settings/websites/WebsitesDataTable';
|
||||||
import Empty from 'components/common/Empty';
|
import Empty from 'components/common/Empty';
|
||||||
import { setValue } from 'store/cache';
|
import { setValue } from 'store/cache';
|
||||||
|
import { useUser } from 'components/hooks';
|
||||||
|
|
||||||
export function TeamWebsiteAddForm({ teamId, onSave, onClose }) {
|
export function TeamWebsiteAddForm({ teamId, onSave, onClose }) {
|
||||||
|
const { user } = useUser();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { get, post, useQuery, useMutation } = useApi();
|
const { get, post, useQuery, useMutation } = useApi();
|
||||||
const { mutate, error } = useMutation(data => post(`/teams/${teamId}/websites`, data));
|
const { mutate, error } = useMutation(data => post(`/teams/${teamId}/websites`, data));
|
||||||
@ -37,7 +39,7 @@ export function TeamWebsiteAddForm({ teamId, onSave, onClose }) {
|
|||||||
{!isLoading && !hasData && <Empty />}
|
{!isLoading && !hasData && <Empty />}
|
||||||
{hasData && (
|
{hasData && (
|
||||||
<Form onSubmit={handleSubmit} error={error}>
|
<Form onSubmit={handleSubmit} error={error}>
|
||||||
<WebsitesDataTable showHeader={false} showActions={false}>
|
<WebsitesDataTable userId={user.id} showHeader={false} showActions={false}>
|
||||||
<GridColumn name="select" label={formatMessage(labels.selectWebsite)} alignment="end">
|
<GridColumn name="select" label={formatMessage(labels.selectWebsite)} alignment="end">
|
||||||
{row => (
|
{row => (
|
||||||
<Toggle
|
<Toggle
|
||||||
|
@ -11,22 +11,22 @@ import useApi from 'components/hooks/useApi';
|
|||||||
import { DOMAIN_REGEX } from 'lib/constants';
|
import { DOMAIN_REGEX } from 'lib/constants';
|
||||||
import useMessages from 'components/hooks/useMessages';
|
import useMessages from 'components/hooks/useMessages';
|
||||||
|
|
||||||
export function WebsiteAddForm({ onSave, onClose }) {
|
export function WebsiteAddForm({ onSave, onClose }: { onSave?: () => void; onClose?: () => void }) {
|
||||||
const { formatMessage, labels, messages } = useMessages();
|
const { formatMessage, labels, messages } = useMessages();
|
||||||
const { post, useMutation } = useApi();
|
const { post, useMutation } = useApi();
|
||||||
const { mutate, error, isLoading } = useMutation(data => post('/websites', data));
|
const { mutate, error, isLoading } = useMutation(data => post('/websites', data));
|
||||||
|
|
||||||
const handleSubmit = async data => {
|
const handleSubmit = async (data: any) => {
|
||||||
mutate(data, {
|
mutate(data, {
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
onSave();
|
onSave?.();
|
||||||
onClose();
|
onClose?.();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form onSubmit={handleSubmit} error={error}>
|
<Form onSubmit={handleSubmit} error={error as string}>
|
||||||
<FormRow label={formatMessage(labels.name)}>
|
<FormRow label={formatMessage(labels.name)}>
|
||||||
<FormInput name="name" rules={{ required: formatMessage(labels.required) }}>
|
<FormInput name="name" rules={{ required: formatMessage(labels.required) }}>
|
||||||
<TextField autoComplete="off" />
|
<TextField autoComplete="off" />
|
||||||
@ -47,9 +47,11 @@ export function WebsiteAddForm({ onSave, onClose }) {
|
|||||||
<SubmitButton variant="primary" disabled={false}>
|
<SubmitButton variant="primary" disabled={false}>
|
||||||
{formatMessage(labels.save)}
|
{formatMessage(labels.save)}
|
||||||
</SubmitButton>
|
</SubmitButton>
|
||||||
<Button disabled={isLoading} onClick={onClose}>
|
{onClose && (
|
||||||
{formatMessage(labels.cancel)}
|
<Button disabled={isLoading} onClick={onClose}>
|
||||||
</Button>
|
{formatMessage(labels.cancel)}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</FormButtons>
|
</FormButtons>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
15
src/app/(main)/settings/websites/Websites.tsx
Normal file
15
src/app/(main)/settings/websites/Websites.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
'use client';
|
||||||
|
import { useUser } from 'components/hooks';
|
||||||
|
import WebsitesDataTable from './WebsitesDataTable';
|
||||||
|
import WebsitesHeader from './WebsitesHeader';
|
||||||
|
|
||||||
|
export default function Websites() {
|
||||||
|
const { user } = useUser();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<WebsitesHeader />
|
||||||
|
<WebsitesDataTable userId={user.id} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -1,13 +1,13 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import WebsitesTable from 'app/(main)/settings/websites/WebsitesTable';
|
import WebsitesTable from 'app/(main)/settings/websites/WebsitesTable';
|
||||||
import useUser from 'components/hooks/useUser';
|
|
||||||
import useApi from 'components/hooks/useApi';
|
import useApi from 'components/hooks/useApi';
|
||||||
import DataTable from 'components/common/DataTable';
|
import DataTable from 'components/common/DataTable';
|
||||||
import useFilterQuery from 'components/hooks/useFilterQuery';
|
import useFilterQuery from 'components/hooks/useFilterQuery';
|
||||||
import useCache from 'store/cache';
|
import useCache from 'store/cache';
|
||||||
|
|
||||||
export interface WebsitesDataTableProps {
|
export interface WebsitesDataTableProps {
|
||||||
|
userId: string;
|
||||||
allowEdit?: boolean;
|
allowEdit?: boolean;
|
||||||
allowView?: boolean;
|
allowView?: boolean;
|
||||||
showActions?: boolean;
|
showActions?: boolean;
|
||||||
@ -17,25 +17,25 @@ export interface WebsitesDataTableProps {
|
|||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
function useWebsites({ includeTeams, onlyTeams }) {
|
function useWebsites(userId: string, { includeTeams, onlyTeams }) {
|
||||||
const { user } = useUser();
|
|
||||||
const { get } = useApi();
|
const { get } = useApi();
|
||||||
const modified = useCache((state: any) => state?.websites);
|
const modified = useCache((state: any) => state?.websites);
|
||||||
|
|
||||||
return useFilterQuery(
|
return useFilterQuery(
|
||||||
['websites', { includeTeams, onlyTeams, modified }],
|
['websites', { includeTeams, onlyTeams, modified }],
|
||||||
(params: any) => {
|
(params: any) => {
|
||||||
return get(`/users/${user?.id}/websites`, {
|
return get(`/users/${userId}/websites`, {
|
||||||
includeTeams,
|
includeTeams,
|
||||||
onlyTeams,
|
onlyTeams,
|
||||||
...params,
|
...params,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
{ enabled: !!user },
|
{ enabled: !!userId },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WebsitesDataTable({
|
export function WebsitesDataTable({
|
||||||
|
userId,
|
||||||
allowEdit = true,
|
allowEdit = true,
|
||||||
allowView = true,
|
allowView = true,
|
||||||
showActions = true,
|
showActions = true,
|
||||||
@ -44,7 +44,7 @@ export function WebsitesDataTable({
|
|||||||
onlyTeams,
|
onlyTeams,
|
||||||
children,
|
children,
|
||||||
}: WebsitesDataTableProps) {
|
}: WebsitesDataTableProps) {
|
||||||
const queryResult = useWebsites({ includeTeams, onlyTeams });
|
const queryResult = useWebsites(userId, { includeTeams, onlyTeams });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataTable queryResult={queryResult}>
|
<DataTable queryResult={queryResult}>
|
||||||
|
@ -1,14 +1,8 @@
|
|||||||
import WebsitesDataTable from './WebsitesDataTable';
|
|
||||||
import WebsitesHeader from './WebsitesHeader';
|
|
||||||
import { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
|
import Websites from './Websites';
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
return (
|
return <Websites />;
|
||||||
<>
|
|
||||||
<WebsitesHeader />
|
|
||||||
<WebsitesDataTable />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import WebsitesDataTable from '../settings/websites/WebsitesDataTable';
|
import WebsitesDataTable from '../settings/websites/WebsitesDataTable';
|
||||||
import { useMessages } from 'components/hooks';
|
import { useMessages, useUser } from 'components/hooks';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Item, Tabs } from 'react-basics';
|
import { Item, Tabs } from 'react-basics';
|
||||||
|
|
||||||
@ -10,6 +10,7 @@ const TABS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function WebsitesBrowse() {
|
export function WebsitesBrowse() {
|
||||||
|
const { user } = useUser();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const [tab, setTab] = useState(TABS.myWebsites);
|
const [tab, setTab] = useState(TABS.myWebsites);
|
||||||
const allowEdit = !process.env.cloudMode;
|
const allowEdit = !process.env.cloudMode;
|
||||||
@ -20,9 +21,14 @@ export function WebsitesBrowse() {
|
|||||||
<Item key={TABS.myWebsites}>{formatMessage(labels.myWebsites)}</Item>
|
<Item key={TABS.myWebsites}>{formatMessage(labels.myWebsites)}</Item>
|
||||||
<Item key={TABS.teamWebsites}>{formatMessage(labels.teamWebsites)}</Item>
|
<Item key={TABS.teamWebsites}>{formatMessage(labels.teamWebsites)}</Item>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
{tab === TABS.myWebsites && <WebsitesDataTable allowEdit={allowEdit} />}
|
{tab === TABS.myWebsites && <WebsitesDataTable userId={user.id} allowEdit={allowEdit} />}
|
||||||
{tab === TABS.teamWebsites && (
|
{tab === TABS.teamWebsites && (
|
||||||
<WebsitesDataTable showTeam={true} onlyTeams={true} allowEdit={allowEdit} />
|
<WebsitesDataTable
|
||||||
|
userId={user.id}
|
||||||
|
showTeam={true}
|
||||||
|
onlyTeams={true}
|
||||||
|
allowEdit={allowEdit}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -5,7 +5,7 @@ import ReferrersTable from 'components/metrics/ReferrersTable';
|
|||||||
import BrowsersTable from 'components/metrics/BrowsersTable';
|
import BrowsersTable from 'components/metrics/BrowsersTable';
|
||||||
import OSTable from 'components/metrics/OSTable';
|
import OSTable from 'components/metrics/OSTable';
|
||||||
import DevicesTable from 'components/metrics/DevicesTable';
|
import DevicesTable from 'components/metrics/DevicesTable';
|
||||||
import WorldMap from 'components/common/WorldMap';
|
import WorldMap from 'components/metrics/WorldMap';
|
||||||
import CountriesTable from 'components/metrics/CountriesTable';
|
import CountriesTable from 'components/metrics/CountriesTable';
|
||||||
import EventsTable from 'components/metrics/EventsTable';
|
import EventsTable from 'components/metrics/EventsTable';
|
||||||
import EventsChart from 'components/metrics/EventsChart';
|
import EventsChart from 'components/metrics/EventsChart';
|
||||||
|
@ -5,7 +5,7 @@ import firstBy from 'thenby';
|
|||||||
import { Grid, GridRow } from 'components/layout/Grid';
|
import { Grid, GridRow } from 'components/layout/Grid';
|
||||||
import Page from 'components/layout/Page';
|
import Page from 'components/layout/Page';
|
||||||
import RealtimeChart from 'components/metrics/RealtimeChart';
|
import RealtimeChart from 'components/metrics/RealtimeChart';
|
||||||
import WorldMap from 'components/common/WorldMap';
|
import WorldMap from 'components/metrics/WorldMap';
|
||||||
import RealtimeLog from './RealtimeLog';
|
import RealtimeLog from './RealtimeLog';
|
||||||
import RealtimeHeader from './RealtimeHeader';
|
import RealtimeHeader from './RealtimeHeader';
|
||||||
import RealtimeUrls from './RealtimeUrls';
|
import RealtimeUrls from './RealtimeUrls';
|
||||||
|
@ -8,16 +8,16 @@ import 'styles/locale.css';
|
|||||||
import 'styles/index.css';
|
import 'styles/index.css';
|
||||||
import 'styles/variables.css';
|
import 'styles/variables.css';
|
||||||
|
|
||||||
export default function RootLayout({ children }) {
|
export default function ({ children }) {
|
||||||
return (
|
return (
|
||||||
<html lang="en" data-scroll="0">
|
<html lang="en" data-scroll="0">
|
||||||
<head>
|
<head>
|
||||||
<link rel="icon" href={`/favicon.ico`} />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href={`/apple-touch-icon.png`} />
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href={`/favicon-32x32.png`} />
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href={`/favicon-16x16.png`} />
|
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||||
<link rel="manifest" href={`/site.webmanifest`} />
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
<link rel="mask-icon" href={`/safari-pinned-tab.svg`} color="#5bbad5" />
|
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
|
||||||
<meta name="msapplication-TileColor" content="#da532c" />
|
<meta name="msapplication-TileColor" content="#da532c" />
|
||||||
<meta name="theme-color" content="#fafafa" media="(prefers-color-scheme: light)" />
|
<meta name="theme-color" content="#fafafa" media="(prefers-color-scheme: light)" />
|
||||||
<meta name="theme-color" content="#2f2f2f" media="(prefers-color-scheme: dark)" />
|
<meta name="theme-color" content="#2f2f2f" media="(prefers-color-scheme: dark)" />
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import Logout from './Logout';
|
import Logout from './Logout';
|
||||||
|
import { Metadata } from 'next';
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
return <Logout />;
|
return <Logout />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Logout | umami',
|
||||||
|
};
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import Share from './Share';
|
import Share from './Share';
|
||||||
|
import { Metadata } from 'next';
|
||||||
|
|
||||||
export default function ({ params: { id } }) {
|
export default function ({ params: { id } }) {
|
||||||
return <Share shareId={id[0]} />;
|
return <Share shareId={id[0]} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'umami',
|
||||||
|
};
|
||||||
|
@ -2,7 +2,13 @@ import { useState } from 'react';
|
|||||||
import { Button, LoadingButton, Form, FormButtons } from 'react-basics';
|
import { Button, LoadingButton, Form, FormButtons } from 'react-basics';
|
||||||
import useMessages from 'components/hooks/useMessages';
|
import useMessages from 'components/hooks/useMessages';
|
||||||
|
|
||||||
export function ConfirmDeleteForm({ name, onConfirm, onClose }) {
|
export interface ConfirmDeleteFormProps {
|
||||||
|
name: string;
|
||||||
|
onConfirm?: () => void;
|
||||||
|
onClose?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ConfirmDeleteForm({ name, onConfirm, onClose }: ConfirmDeleteFormProps) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { formatMessage, labels, messages, FormattedMessage } = useMessages();
|
const { formatMessage, labels, messages, FormattedMessage } = useMessages();
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import styles from './Empty.module.css';
|
|
||||||
import useMessages from 'components/hooks/useMessages';
|
import useMessages from 'components/hooks/useMessages';
|
||||||
|
import styles from './Empty.module.css';
|
||||||
|
|
||||||
export interface EmptyProps {
|
export interface EmptyProps {
|
||||||
message?: string;
|
message?: string;
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
import { Icon, Text, Flexbox } from 'react-basics';
|
import { Icon, Text, Flexbox } from 'react-basics';
|
||||||
import Logo from 'assets/logo.svg';
|
import Logo from 'assets/logo.svg';
|
||||||
|
|
||||||
|
export interface EmptyPlaceholderProps {
|
||||||
|
message: string;
|
||||||
|
children?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
export function EmptyPlaceholder({ message, children }) {
|
export function EmptyPlaceholder({ message, children }) {
|
||||||
return (
|
return (
|
||||||
<Flexbox direction="column" alignItems="center" justifyContent="center" gap={60} height={600}>
|
<Flexbox direction="column" alignItems="center" justifyContent="center" gap={60} height={600}>
|
@ -1,14 +1,19 @@
|
|||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
|
import { ErrorInfo, ReactNode } from 'react';
|
||||||
import { ErrorBoundary as Boundary } from 'react-error-boundary';
|
import { ErrorBoundary as Boundary } from 'react-error-boundary';
|
||||||
import { Button } from 'react-basics';
|
import { Button } from 'react-basics';
|
||||||
import useMessages from 'components/hooks/useMessages';
|
import useMessages from 'components/hooks/useMessages';
|
||||||
import styles from './ErrorBoundry.module.css';
|
import styles from './ErrorBoundry.module.css';
|
||||||
|
|
||||||
const logError = (error, info) => {
|
const logError = (error: Error, info: ErrorInfo) => {
|
||||||
console.error(error, info.componentStack);
|
console.error(error, info.componentStack);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ErrorBoundary({ children }) {
|
export interface ErrorBoundaryProps {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ErrorBoundary({ children }: ErrorBoundaryProps) {
|
||||||
const { formatMessage, messages } = useMessages();
|
const { formatMessage, messages } = useMessages();
|
||||||
|
|
||||||
const fallbackRender = ({ error, resetErrorBoundary }) => {
|
const fallbackRender = ({ error, resetErrorBoundary }) => {
|
@ -1,6 +1,6 @@
|
|||||||
import styles from './Favicon.module.css';
|
import styles from './Favicon.module.css';
|
||||||
|
|
||||||
function getHostName(url) {
|
function getHostName(url: string) {
|
||||||
const match = url.match(/^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:/\n?=]+)/im);
|
const match = url.match(/^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:/\n?=]+)/im);
|
||||||
return match && match.length > 1 ? match[1] : null;
|
return match && match.length > 1 ? match[1] : null;
|
||||||
}
|
}
|
@ -1,13 +0,0 @@
|
|||||||
import { ButtonGroup, Button, Flexbox } from 'react-basics';
|
|
||||||
|
|
||||||
export function FilterButtons({ items, selectedKey, onSelect }) {
|
|
||||||
return (
|
|
||||||
<Flexbox justifyContent="center">
|
|
||||||
<ButtonGroup items={items} selectedKey={selectedKey} onSelect={onSelect}>
|
|
||||||
{({ key, label }) => <Button key={key}>{label}</Button>}
|
|
||||||
</ButtonGroup>
|
|
||||||
</Flexbox>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default FilterButtons;
|
|
20
src/components/common/FilterButtons.tsx
Normal file
20
src/components/common/FilterButtons.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Key } from 'react';
|
||||||
|
import { ButtonGroup, Button, Flexbox } from 'react-basics';
|
||||||
|
|
||||||
|
export interface FilterButtonsProps {
|
||||||
|
items: any[];
|
||||||
|
selectedKey?: Key;
|
||||||
|
onSelect: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FilterButtons({ items, selectedKey, onSelect }: FilterButtonsProps) {
|
||||||
|
return (
|
||||||
|
<Flexbox justifyContent="center">
|
||||||
|
<ButtonGroup items={items} selectedKey={selectedKey as any} onSelect={onSelect}>
|
||||||
|
{({ key, label }) => <Button key={key}>{label}</Button>}
|
||||||
|
</ButtonGroup>
|
||||||
|
</Flexbox>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FilterButtons;
|
@ -1,3 +1,4 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
import { Icon, Icons } from 'react-basics';
|
import { Icon, Icons } from 'react-basics';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
@ -6,7 +7,23 @@ import useNavigation from 'components/hooks/useNavigation';
|
|||||||
import useMessages from 'components/hooks/useMessages';
|
import useMessages from 'components/hooks/useMessages';
|
||||||
import styles from './FilterLink.module.css';
|
import styles from './FilterLink.module.css';
|
||||||
|
|
||||||
export function FilterLink({ id, value, label, externalUrl, children, className }) {
|
export interface FilterLinkProps {
|
||||||
|
id: string;
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
externalUrl: string;
|
||||||
|
className: string;
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FilterLink({
|
||||||
|
id,
|
||||||
|
value,
|
||||||
|
label,
|
||||||
|
externalUrl,
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
}: FilterLinkProps) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { makeUrl, query } = useNavigation();
|
const { makeUrl, query } = useNavigation();
|
||||||
const active = query[id] !== undefined;
|
const active = query[id] !== undefined;
|
@ -1,59 +0,0 @@
|
|||||||
import { Button, Icon } from 'react-basics';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import MobileMenu from './MobileMenu';
|
|
||||||
import Icons from 'components/icons';
|
|
||||||
import useMessages from 'components/hooks/useMessages';
|
|
||||||
|
|
||||||
export function HamburgerButton() {
|
|
||||||
const { formatMessage, labels } = useMessages();
|
|
||||||
const [active, setActive] = useState(false);
|
|
||||||
const cloudMode = Boolean(process.env.cloudMode);
|
|
||||||
|
|
||||||
const menuItems = [
|
|
||||||
{
|
|
||||||
label: formatMessage(labels.dashboard),
|
|
||||||
url: '/dashboard',
|
|
||||||
},
|
|
||||||
!cloudMode && {
|
|
||||||
label: formatMessage(labels.settings),
|
|
||||||
url: '/settings',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
label: formatMessage(labels.websites),
|
|
||||||
url: '/settings/websites',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: formatMessage(labels.teams),
|
|
||||||
url: '/settings/teams',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: formatMessage(labels.users),
|
|
||||||
url: '/settings/users',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: formatMessage(labels.profile),
|
|
||||||
url: '/settings/profile',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
cloudMode && {
|
|
||||||
label: formatMessage(labels.profile),
|
|
||||||
url: '/settings/profile',
|
|
||||||
},
|
|
||||||
!cloudMode && { label: formatMessage(labels.logout), url: '/logout' },
|
|
||||||
].filter(n => n);
|
|
||||||
|
|
||||||
const handleClick = () => setActive(state => !state);
|
|
||||||
const handleClose = () => setActive(false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Button variant="quiet" onClick={handleClick}>
|
|
||||||
<Icon>{active ? <Icons.Close /> : <Icons.Menu />}</Icon>
|
|
||||||
</Button>
|
|
||||||
{active && <MobileMenu items={menuItems} onClose={handleClose} />}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default HamburgerButton;
|
|
22
src/components/common/HamburgerButton.tsx
Normal file
22
src/components/common/HamburgerButton.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { Button, Icon } from 'react-basics';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import MobileMenu from './MobileMenu';
|
||||||
|
import Icons from 'components/icons';
|
||||||
|
|
||||||
|
export function HamburgerButton({ menuItems }: { menuItems: any[] }) {
|
||||||
|
const [active, setActive] = useState(false);
|
||||||
|
|
||||||
|
const handleClick = () => setActive(state => !state);
|
||||||
|
const handleClose = () => setActive(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button variant="quiet" onClick={handleClick}>
|
||||||
|
<Icon>{active ? <Icons.Close /> : <Icons.Menu />}</Icon>
|
||||||
|
</Button>
|
||||||
|
{active && <MobileMenu items={menuItems} onClose={handleClose} />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HamburgerButton;
|
@ -1,8 +1,8 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { ReactNode, useEffect, useState } from 'react';
|
||||||
import { Tooltip } from 'react-basics';
|
import { Tooltip } from 'react-basics';
|
||||||
import styles from './HoverTooltip.module.css';
|
import styles from './HoverTooltip.module.css';
|
||||||
|
|
||||||
export function HoverTooltip({ children }) {
|
export function HoverTooltip({ children }: { children: ReactNode }) {
|
||||||
const [position, setPosition] = useState({ x: -1000, y: -1000 });
|
const [position, setPosition] = useState({ x: -1000, y: -1000 });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
@ -2,8 +2,17 @@ import classNames from 'classnames';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useLocale } from 'components/hooks';
|
import { useLocale } from 'components/hooks';
|
||||||
import styles from './LinkButton.module.css';
|
import styles from './LinkButton.module.css';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
export function LinkButton({ href, className, variant, scroll = true, children }) {
|
export interface LinkButtonProps {
|
||||||
|
href: string;
|
||||||
|
className?: string;
|
||||||
|
variant?: string;
|
||||||
|
scroll?: boolean;
|
||||||
|
children?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LinkButton({ href, className, variant, scroll = true, children }: LinkButtonProps) {
|
||||||
const { dir } = useLocale();
|
const { dir } = useLocale();
|
||||||
|
|
||||||
return (
|
return (
|
@ -4,12 +4,19 @@ import { usePathname } from 'next/navigation';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import styles from './MobileMenu.module.css';
|
import styles from './MobileMenu.module.css';
|
||||||
|
|
||||||
export function MobileMenu({ items = [], onClose }) {
|
export function MobileMenu({
|
||||||
|
items = [],
|
||||||
|
onClose,
|
||||||
|
}: {
|
||||||
|
items: any[];
|
||||||
|
className?: string;
|
||||||
|
onClose: () => void;
|
||||||
|
}): any {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
|
||||||
const Items = ({ items, className }) => (
|
const Items = ({ items, className }: { items: any[]; className?: string }): any => (
|
||||||
<div className={classNames(styles.items, className)}>
|
<div className={classNames(styles.items, className)}>
|
||||||
{items.map(({ label, url, children }) => {
|
{items.map(({ label, url, children }: { label: string; url: string; children: any[] }) => {
|
||||||
const selected = pathname.startsWith(url);
|
const selected = pathname.startsWith(url);
|
||||||
|
|
||||||
return (
|
return (
|
@ -3,7 +3,15 @@ import { Button, Icon, Icons } from 'react-basics';
|
|||||||
import useMessages from 'components/hooks/useMessages';
|
import useMessages from 'components/hooks/useMessages';
|
||||||
import styles from './Pager.module.css';
|
import styles from './Pager.module.css';
|
||||||
|
|
||||||
export function Pager({ page, pageSize, count, onPageChange, className }) {
|
export interface PagerProps {
|
||||||
|
page: number;
|
||||||
|
pageSize: number;
|
||||||
|
count: number;
|
||||||
|
onPageChange: (nextPage: number) => void;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Pager({ page, pageSize, count, onPageChange, className }: PagerProps) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const maxPage = pageSize && count ? Math.ceil(count / pageSize) : 0;
|
const maxPage = pageSize && count ? Math.ceil(count / pageSize) : 0;
|
||||||
const lastPage = page === maxPage;
|
const lastPage = page === maxPage;
|
||||||
@ -13,7 +21,7 @@ export function Pager({ page, pageSize, count, onPageChange, className }) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlePageChange = value => {
|
const handlePageChange = (value: number) => {
|
||||||
const nextPage = page + value;
|
const nextPage = page + value;
|
||||||
if (nextPage > 0 && nextPage <= maxPage) {
|
if (nextPage > 0 && nextPage <= maxPage) {
|
||||||
onPageChange(nextPage);
|
onPageChange(nextPage);
|
1
src/components/declarations.d.ts
vendored
1
src/components/declarations.d.ts
vendored
@ -1,2 +1,3 @@
|
|||||||
declare module '*.css';
|
declare module '*.css';
|
||||||
declare module '*.svg';
|
declare module '*.svg';
|
||||||
|
declare module '*.json';
|
||||||
|
@ -13,7 +13,7 @@ export * from './useMessages';
|
|||||||
export * from './useNavigation';
|
export * from './useNavigation';
|
||||||
export * from './useReport';
|
export * from './useReport';
|
||||||
export * from './useReports';
|
export * from './useReports';
|
||||||
export * from './useRequireLogin';
|
export * from './useLogin';
|
||||||
export * from './useShareToken';
|
export * from './useShareToken';
|
||||||
export * from './useSticky';
|
export * from './useSticky';
|
||||||
export * from './useTheme';
|
export * from './useTheme';
|
||||||
|
@ -6,10 +6,10 @@ const countryNames = {
|
|||||||
'en-US': enUS,
|
'en-US': enUS,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useCountryNames(locale) {
|
export function useCountryNames(locale: string) {
|
||||||
const [list, setList] = useState(countryNames[locale] || enUS);
|
const [list, setList] = useState(countryNames[locale] || enUS);
|
||||||
|
|
||||||
async function loadData(locale) {
|
async function loadData(locale: string) {
|
||||||
const { data } = await httpGet(`${process.env.basePath}/intl/country/${locale}.json`);
|
const { data } = await httpGet(`${process.env.basePath}/intl/country/${locale}.json`);
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
@ -6,7 +6,7 @@ import websiteStore, { setWebsiteDateRange } from 'store/websites';
|
|||||||
import appStore, { setDateRange } from 'store/app';
|
import appStore, { setDateRange } from 'store/app';
|
||||||
import useApi from './useApi';
|
import useApi from './useApi';
|
||||||
|
|
||||||
export function useDateRange(websiteId) {
|
export function useDateRange(websiteId: string) {
|
||||||
const { get } = useApi();
|
const { get } = useApi();
|
||||||
const { locale } = useLocale();
|
const { locale } = useLocale();
|
||||||
const websiteConfig = websiteStore(state => state[websiteId]?.dateRange);
|
const websiteConfig = websiteStore(state => state[websiteId]?.dateRange);
|
||||||
@ -20,7 +20,7 @@ export function useDateRange(websiteId) {
|
|||||||
|
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
if (value === 'all') {
|
if (value === 'all') {
|
||||||
const result = await get(`/websites/${websiteId}/daterange`);
|
const result: any = await get(`/websites/${websiteId}/daterange`);
|
||||||
const { mindate, maxdate } = result;
|
const { mindate, maxdate } = result;
|
||||||
|
|
||||||
const startDate = new Date(mindate);
|
const startDate = new Date(mindate);
|
@ -1,6 +1,6 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
export function useDocumentClick(handler) {
|
export function useDocumentClick(handler: (event: MouseEvent) => any) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.addEventListener('click', handler);
|
document.addEventListener('click', handler);
|
||||||
|
|
@ -1,21 +0,0 @@
|
|||||||
import { useEffect, useCallback } from 'react';
|
|
||||||
|
|
||||||
export function useEscapeKey(handler) {
|
|
||||||
const escFunction = useCallback(event => {
|
|
||||||
if (event.keyCode === 27) {
|
|
||||||
handler(event);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
document.addEventListener('keydown', escFunction, false);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('keydown', escFunction, false);
|
|
||||||
};
|
|
||||||
}, [escFunction]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default useEscapeKey;
|
|
21
src/components/hooks/useEscapeKey.ts
Normal file
21
src/components/hooks/useEscapeKey.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { useEffect, useCallback, KeyboardEvent } from 'react';
|
||||||
|
|
||||||
|
export function useEscapeKey(handler: (event: KeyboardEvent) => void) {
|
||||||
|
const escFunction = useCallback((event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
handler(event);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener('keydown', escFunction as any, false);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('keydown', escFunction as any, false);
|
||||||
|
};
|
||||||
|
}, [escFunction]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useEscapeKey;
|
@ -9,23 +9,23 @@ export function useFormat() {
|
|||||||
const { locale } = useLocale();
|
const { locale } = useLocale();
|
||||||
const countryNames = useCountryNames(locale);
|
const countryNames = useCountryNames(locale);
|
||||||
|
|
||||||
const formatBrowser = value => {
|
const formatBrowser = (value: string) => {
|
||||||
return BROWSERS[value] || value;
|
return BROWSERS[value] || value;
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatCountry = value => {
|
const formatCountry = (value: string) => {
|
||||||
return countryNames[value] || value;
|
return countryNames[value] || value;
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatRegion = value => {
|
const formatRegion = (value: string) => {
|
||||||
return regions[value] ? regions[value] : value;
|
return regions[value] ? regions[value] : value;
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatDevice = value => {
|
const formatDevice = (value: string) => {
|
||||||
return formatMessage(labels[value] || labels.unknown);
|
return formatMessage(labels[value] || labels.unknown);
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatValue = (value, type) => {
|
const formatValue = (value: string, type: string) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'browser':
|
case 'browser':
|
||||||
return formatBrowser(value);
|
return formatBrowser(value);
|
22
src/components/hooks/useLogin.ts
Normal file
22
src/components/hooks/useLogin.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import useApi from 'components/hooks/useApi';
|
||||||
|
import useUser from 'components/hooks/useUser';
|
||||||
|
|
||||||
|
export function useLogin() {
|
||||||
|
const { get, useQuery } = useApi();
|
||||||
|
const { user, setUser } = useUser();
|
||||||
|
|
||||||
|
const query = useQuery({
|
||||||
|
queryKey: ['login'],
|
||||||
|
queryFn: async () => {
|
||||||
|
const data = await get('/auth/verify');
|
||||||
|
|
||||||
|
setUser(data);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return { user, ...query };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useLogin;
|
@ -1,20 +0,0 @@
|
|||||||
import { useIntl, FormattedMessage } from 'react-intl';
|
|
||||||
import { messages, labels } from 'components/messages';
|
|
||||||
|
|
||||||
export function useMessages() {
|
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
const getMessage = id => {
|
|
||||||
const message = Object.values(messages).find(value => value.id === id);
|
|
||||||
|
|
||||||
return message ? formatMessage(message) : id;
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatMessage = (descriptor, values, opts) => {
|
|
||||||
return descriptor ? intl.formatMessage(descriptor, values, opts) : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
return { formatMessage, FormattedMessage, messages, labels, getMessage };
|
|
||||||
}
|
|
||||||
|
|
||||||
export default useMessages;
|
|
30
src/components/hooks/useMessages.ts
Normal file
30
src/components/hooks/useMessages.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { useIntl, FormattedMessage, MessageDescriptor, PrimitiveType } from 'react-intl';
|
||||||
|
import { messages, labels } from 'components/messages';
|
||||||
|
import { FormatXMLElementFn, Options } from 'intl-messageformat';
|
||||||
|
|
||||||
|
export function useMessages(): any {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const getMessage = (id: string) => {
|
||||||
|
const message = Object.values(messages).find(value => value.id === id);
|
||||||
|
|
||||||
|
return message ? formatMessage(message) : id;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatMessage = (
|
||||||
|
descriptor:
|
||||||
|
| MessageDescriptor
|
||||||
|
| {
|
||||||
|
id: string;
|
||||||
|
defaultMessage: string;
|
||||||
|
},
|
||||||
|
values?: Record<string, PrimitiveType | FormatXMLElementFn<string, string>>,
|
||||||
|
opts?: Options,
|
||||||
|
) => {
|
||||||
|
return descriptor ? intl.formatMessage(descriptor, values, opts) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return { formatMessage, FormattedMessage, messages, labels, getMessage };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useMessages;
|
@ -17,7 +17,7 @@ export function useNavigation() {
|
|||||||
return obj;
|
return obj;
|
||||||
}, [params]);
|
}, [params]);
|
||||||
|
|
||||||
function makeUrl(params, reset) {
|
function makeUrl(params: any, reset?: boolean) {
|
||||||
return reset ? pathname : buildUrl(pathname, { ...query, ...params });
|
return reset ? pathname : buildUrl(pathname, { ...query, ...params });
|
||||||
}
|
}
|
||||||
|
|
@ -18,7 +18,7 @@ export function useReport(reportId, defaultParameters) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const loadReport = async id => {
|
const loadReport = async id => {
|
||||||
const data = await get(`/reports/${id}`);
|
const data: any = await get(`/reports/${id}`);
|
||||||
|
|
||||||
const { dateRange } = data?.parameters || {};
|
const { dateRange } = data?.parameters || {};
|
||||||
const { startDate, endDate } = dateRange || {};
|
const { startDate, endDate } = dateRange || {};
|
||||||
@ -40,7 +40,7 @@ export function useReport(reportId, defaultParameters) {
|
|||||||
const data = await post(`/reports/${type}`, { ...parameters, timezone });
|
const data = await post(`/reports/${type}`, { ...parameters, timezone });
|
||||||
|
|
||||||
setReport(
|
setReport(
|
||||||
produce(state => {
|
produce((state: any) => {
|
||||||
state.parameters = parameters;
|
state.parameters = parameters;
|
||||||
state.data = data;
|
state.data = data;
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ export function useReport(reportId, defaultParameters) {
|
|||||||
const updateReport = useCallback(
|
const updateReport = useCallback(
|
||||||
async data => {
|
async data => {
|
||||||
setReport(
|
setReport(
|
||||||
produce(state => {
|
produce((state: any) => {
|
||||||
const { parameters, ...rest } = data;
|
const { parameters, ...rest } = data;
|
||||||
|
|
||||||
if (parameters) {
|
if (parameters) {
|
@ -1,28 +0,0 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import useApi from 'components/hooks/useApi';
|
|
||||||
import useUser from 'components/hooks/useUser';
|
|
||||||
|
|
||||||
export function useRequireLogin(handler?: (data?: object) => void) {
|
|
||||||
const { get } = useApi();
|
|
||||||
const { user, setUser } = useUser();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
async function loadUser() {
|
|
||||||
try {
|
|
||||||
const data = await get('/auth/verify');
|
|
||||||
|
|
||||||
setUser(typeof handler === 'function' ? handler(data) : (data as any)?.user);
|
|
||||||
} catch {
|
|
||||||
location.href = `${process.env.basePath || ''}/login`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
loadUser();
|
|
||||||
}
|
|
||||||
}, [user]);
|
|
||||||
|
|
||||||
return { user };
|
|
||||||
}
|
|
||||||
|
|
||||||
export default useRequireLogin;
|
|
@ -1,9 +1,9 @@
|
|||||||
import useStore, { setShareToken } from 'store/app';
|
import useStore, { setShareToken } from 'store/app';
|
||||||
import useApi from './useApi';
|
import useApi from './useApi';
|
||||||
|
|
||||||
const selector = state => state.shareToken;
|
const selector = (state: { shareToken: string }) => state.shareToken;
|
||||||
|
|
||||||
export function useShareToken(shareId) {
|
export function useShareToken(shareId: string) {
|
||||||
const shareToken = useStore(selector);
|
const shareToken = useStore(selector);
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
const { isLoading, error } = useQuery(['share', shareId], async () => {
|
const { isLoading, error } = useQuery(['share', shareId], async () => {
|
@ -5,8 +5,9 @@ export function useSticky({ enabled = true, threshold = 1 }) {
|
|||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let observer;
|
let observer: IntersectionObserver | undefined;
|
||||||
const handler = ([entry]) => setIsSticky(entry.intersectionRatio < threshold);
|
const handler: IntersectionObserverCallback = ([entry]) =>
|
||||||
|
setIsSticky(entry.intersectionRatio < threshold);
|
||||||
|
|
||||||
if (enabled && ref.current) {
|
if (enabled && ref.current) {
|
||||||
observer = new IntersectionObserver(handler, { threshold: [threshold] });
|
observer = new IntersectionObserver(handler, { threshold: [threshold] });
|
@ -4,7 +4,7 @@ import { getItem, setItem } from 'next-basics';
|
|||||||
import { THEME_COLORS, THEME_CONFIG } from 'lib/constants';
|
import { THEME_COLORS, THEME_CONFIG } from 'lib/constants';
|
||||||
import { colord } from 'colord';
|
import { colord } from 'colord';
|
||||||
|
|
||||||
const selector = state => state.theme;
|
const selector = (state: { theme: string }) => state.theme;
|
||||||
|
|
||||||
export function useTheme() {
|
export function useTheme() {
|
||||||
const defaultTheme =
|
const defaultTheme =
|
@ -1,6 +1,6 @@
|
|||||||
import useApi from './useApi';
|
import useApi from './useApi';
|
||||||
|
|
||||||
export function useWebsite(websiteId) {
|
export function useWebsite(websiteId: string) {
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
return useQuery(['websites', websiteId], () => get(`/websites/${websiteId}`), {
|
return useQuery(['websites', websiteId], () => get(`/websites/${websiteId}`), {
|
||||||
enabled: !!websiteId,
|
enabled: !!websiteId,
|
2
src/declaration.d.ts
vendored
Normal file
2
src/declaration.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
declare module 'cors';
|
||||||
|
declare module 'debug';
|
25
src/index.ts
25
src/index.ts
@ -1,19 +1,16 @@
|
|||||||
export * from 'components/hooks/useApi';
|
export * from 'components/hooks/useApi';
|
||||||
export * from 'components/hooks/useConfig';
|
export * from 'components/hooks/useConfig';
|
||||||
export * from 'components/hooks/useCountryNames';
|
|
||||||
export * from 'components/hooks/useDateRange';
|
export * from 'components/hooks/useDateRange';
|
||||||
export * from 'components/hooks/useDocumentClick';
|
export * from 'components/hooks/useDocumentClick';
|
||||||
export * from 'components/hooks/useEscapeKey';
|
export * from 'components/hooks/useEscapeKey';
|
||||||
|
export * from 'components/hooks/useFilterQuery';
|
||||||
export * from 'components/hooks/useFilters';
|
export * from 'components/hooks/useFilters';
|
||||||
export * from 'components/hooks/useForceUpdate';
|
export * from 'components/hooks/useForceUpdate';
|
||||||
export * from 'components/hooks/useFormat';
|
export * from 'components/hooks/useFormat';
|
||||||
export * from 'components/hooks/useLanguageNames';
|
|
||||||
export * from 'components/hooks/useLocale';
|
export * from 'components/hooks/useLocale';
|
||||||
export * from 'components/hooks/useMessages';
|
export * from 'components/hooks/useMessages';
|
||||||
export * from 'components/hooks/useNavigation';
|
export * from 'components/hooks/useNavigation';
|
||||||
export * from 'components/hooks/useReport';
|
export * from 'components/hooks/useLogin';
|
||||||
export * from 'components/hooks/useReports';
|
|
||||||
export * from 'components/hooks/useRequireLogin';
|
|
||||||
export * from 'components/hooks/useShareToken';
|
export * from 'components/hooks/useShareToken';
|
||||||
export * from 'components/hooks/useSticky';
|
export * from 'components/hooks/useSticky';
|
||||||
export * from 'components/hooks/useTheme';
|
export * from 'components/hooks/useTheme';
|
||||||
@ -21,7 +18,7 @@ export * from 'components/hooks/useTimezone';
|
|||||||
export * from 'components/hooks/useUser';
|
export * from 'components/hooks/useUser';
|
||||||
export * from 'components/hooks/useWebsite';
|
export * from 'components/hooks/useWebsite';
|
||||||
|
|
||||||
export * from './app/(main)/settings/teams/[id]/TeamWebsiteAddForm';
|
export * from 'app/(main)/settings/teams/[id]/TeamWebsiteAddForm';
|
||||||
export * from 'app/(main)/settings/teams/[id]/TeamEditForm';
|
export * from 'app/(main)/settings/teams/[id]/TeamEditForm';
|
||||||
export * from 'app/(main)/settings/teams/[id]/TeamMemberRemoveButton';
|
export * from 'app/(main)/settings/teams/[id]/TeamMemberRemoveButton';
|
||||||
export * from 'app/(main)/settings/teams/[id]/TeamMembers';
|
export * from 'app/(main)/settings/teams/[id]/TeamMembers';
|
||||||
@ -44,8 +41,22 @@ export * from 'app/(main)/settings/websites/[id]/TrackingCode';
|
|||||||
export * from 'app/(main)/settings/websites/[id]/WebsiteDeleteForm';
|
export * from 'app/(main)/settings/websites/[id]/WebsiteDeleteForm';
|
||||||
export * from 'app/(main)/settings/websites/[id]/WebsiteEditForm';
|
export * from 'app/(main)/settings/websites/[id]/WebsiteEditForm';
|
||||||
export * from 'app/(main)/settings/websites/[id]/WebsiteResetForm';
|
export * from 'app/(main)/settings/websites/[id]/WebsiteResetForm';
|
||||||
|
|
||||||
export * from 'app/(main)/settings/websites/WebsiteAddForm';
|
export * from 'app/(main)/settings/websites/WebsiteAddForm';
|
||||||
export * from 'app/(main)/settings/websites/WebsitesHeader';
|
export * from 'app/(main)/settings/websites/WebsitesHeader';
|
||||||
export * from 'app/(main)/settings/websites/WebsiteSettings';
|
export * from 'app/(main)/settings/websites/WebsiteSettings';
|
||||||
export * from './app/(main)/settings/websites/WebsitesDataTable';
|
export * from 'app/(main)/settings/websites/WebsitesDataTable';
|
||||||
export * from 'app/(main)/settings/websites/WebsitesTable';
|
export * from 'app/(main)/settings/websites/WebsitesTable';
|
||||||
|
|
||||||
|
export * from 'components/common/ConfirmDeleteForm';
|
||||||
|
export * from 'components/common/DataTable';
|
||||||
|
export * from 'components/common/Empty';
|
||||||
|
export * from 'components/common/ErrorBoundary';
|
||||||
|
export * from 'components/common/Favicon';
|
||||||
|
export * from 'components/common/FilterButtons';
|
||||||
|
export * from 'components/common/FilterLink';
|
||||||
|
export * from 'components/common/HamburgerButton';
|
||||||
|
export * from 'components/common/HoverTooltip';
|
||||||
|
export * from 'components/common/LinkButton';
|
||||||
|
export * from 'components/common/MobileMenu';
|
||||||
|
export * from 'components/common/Pager';
|
||||||
|
@ -1,29 +1,30 @@
|
|||||||
import { Report } from '@prisma/client';
|
import { Report } from '@prisma/client';
|
||||||
import redis from '@umami/redis-client';
|
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
|
import redis from '@umami/redis-client';
|
||||||
import { PERMISSIONS, ROLE_PERMISSIONS, SHARE_TOKEN_HEADER } from 'lib/constants';
|
import { PERMISSIONS, ROLE_PERMISSIONS, SHARE_TOKEN_HEADER } from 'lib/constants';
|
||||||
import { secret } from 'lib/crypto';
|
import { secret } from 'lib/crypto';
|
||||||
import { createSecureToken, ensureArray, getRandomChars, parseToken } from 'next-basics';
|
import { createSecureToken, ensureArray, getRandomChars, parseToken } from 'next-basics';
|
||||||
import { findTeamWebsiteByUserId, getTeamUser, getTeamWebsite } from 'queries';
|
import { findTeamWebsiteByUserId, getTeamUser, getTeamWebsite } from 'queries';
|
||||||
import { loadWebsite } from './load';
|
import { loadWebsite } from './load';
|
||||||
import { Auth } from './types';
|
import { Auth } from './types';
|
||||||
|
import { NextApiRequest } from 'next';
|
||||||
|
|
||||||
const log = debug('umami:auth');
|
const log = debug('umami:auth');
|
||||||
const cloudMode = process.env.CLOUD_MODE;
|
const cloudMode = process.env.CLOUD_MODE;
|
||||||
|
|
||||||
export async function setAuthKey(user, expire = 0) {
|
export async function saveAuth(data: any, expire = 0) {
|
||||||
const authKey = `auth:${getRandomChars(32)}`;
|
const authKey = `auth:${getRandomChars(32)}`;
|
||||||
|
|
||||||
await redis.set(authKey, user);
|
await redis.client.set(authKey, data);
|
||||||
|
|
||||||
if (expire) {
|
if (expire) {
|
||||||
await redis.expire(authKey, expire);
|
await redis.client.expire(authKey, expire);
|
||||||
}
|
}
|
||||||
|
|
||||||
return createSecureToken({ authKey }, secret());
|
return createSecureToken({ authKey }, secret());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAuthToken(req) {
|
export function getAuthToken(req: NextApiRequest) {
|
||||||
try {
|
try {
|
||||||
return req.headers.authorization.split(' ')[1];
|
return req.headers.authorization.split(' ')[1];
|
||||||
} catch {
|
} catch {
|
||||||
@ -31,7 +32,7 @@ export function getAuthToken(req) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseShareToken(req) {
|
export function parseShareToken(req: Request) {
|
||||||
try {
|
try {
|
||||||
return parseToken(req.headers[SHARE_TOKEN_HEADER], secret());
|
return parseToken(req.headers[SHARE_TOKEN_HEADER], secret());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -60,11 +61,7 @@ export async function canViewWebsite({ user, shareToken }: Auth, websiteId: stri
|
|||||||
|
|
||||||
export async function canCreateWebsite({ user, grant }: Auth) {
|
export async function canCreateWebsite({ user, grant }: Auth) {
|
||||||
if (cloudMode) {
|
if (cloudMode) {
|
||||||
if (grant?.find(a => a === PERMISSIONS.websiteCreate)) {
|
return !!grant?.find(a => a === PERMISSIONS.websiteCreate);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.isAdmin) {
|
if (user.isAdmin) {
|
||||||
@ -120,11 +117,7 @@ export async function canDeleteReport(auth: Auth, report: Report) {
|
|||||||
|
|
||||||
export async function canCreateTeam({ user, grant }: Auth) {
|
export async function canCreateTeam({ user, grant }: Auth) {
|
||||||
if (cloudMode) {
|
if (cloudMode) {
|
||||||
if (grant?.find(a => a === PERMISSIONS.teamCreate)) {
|
return !!grant?.find(a => a === PERMISSIONS.teamCreate);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.isAdmin) {
|
if (user.isAdmin) {
|
||||||
|
@ -3,67 +3,71 @@ import redis from '@umami/redis-client';
|
|||||||
import { getSession, getUserById, getWebsiteById } from '../queries';
|
import { getSession, getUserById, getWebsiteById } from '../queries';
|
||||||
|
|
||||||
async function fetchWebsite(id): Promise<Website> {
|
async function fetchWebsite(id): Promise<Website> {
|
||||||
return redis.getCache(`website:${id}`, () => getWebsiteById(id), 86400);
|
return redis.client.getCache(`website:${id}`, () => getWebsiteById(id), 86400);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function storeWebsite(data) {
|
async function storeWebsite(data) {
|
||||||
const { id } = data;
|
const { id } = data;
|
||||||
const key = `website:${id}`;
|
const key = `website:${id}`;
|
||||||
|
|
||||||
const obj = await redis.setCache(key, data);
|
const obj = await redis.client.setCache(key, data);
|
||||||
await redis.expire(key, 86400);
|
await redis.client.expire(key, 86400);
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteWebsite(id) {
|
async function deleteWebsite(id) {
|
||||||
return redis.deleteCache(`website:${id}`);
|
return redis.client.deleteCache(`website:${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchUser(id): Promise<User> {
|
async function fetchUser(id): Promise<User> {
|
||||||
return redis.getCache(`user:${id}`, () => getUserById(id, { includePassword: true }), 86400);
|
return redis.client.getCache(
|
||||||
|
`user:${id}`,
|
||||||
|
() => getUserById(id, { includePassword: true }),
|
||||||
|
86400,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function storeUser(data) {
|
async function storeUser(data) {
|
||||||
const { id } = data;
|
const { id } = data;
|
||||||
const key = `user:${id}`;
|
const key = `user:${id}`;
|
||||||
|
|
||||||
const obj = await redis.setCache(key, data);
|
const obj = await redis.client.setCache(key, data);
|
||||||
await redis.expire(key, 86400);
|
await redis.client.expire(key, 86400);
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteUser(id) {
|
async function deleteUser(id) {
|
||||||
return redis.deleteCache(`user:${id}`);
|
return redis.client.deleteCache(`user:${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchSession(id) {
|
async function fetchSession(id) {
|
||||||
return redis.getCache(`session:${id}`, () => getSession(id), 86400);
|
return redis.client.getCache(`session:${id}`, () => getSession(id), 86400);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function storeSession(data) {
|
async function storeSession(data) {
|
||||||
const { id } = data;
|
const { id } = data;
|
||||||
const key = `session:${id}`;
|
const key = `session:${id}`;
|
||||||
|
|
||||||
const obj = await redis.setCache(key, data);
|
const obj = await redis.client.setCache(key, data);
|
||||||
await redis.expire(key, 86400);
|
await redis.client.expire(key, 86400);
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteSession(id) {
|
async function deleteSession(id) {
|
||||||
return redis.deleteCache(`session:${id}`);
|
return redis.client.deleteCache(`session:${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchUserBlock(userId: string) {
|
async function fetchUserBlock(userId: string) {
|
||||||
const key = `user:block:${userId}`;
|
const key = `user:block:${userId}`;
|
||||||
return redis.get(key);
|
return redis.client.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function incrementUserBlock(userId: string) {
|
async function incrementUserBlock(userId: string) {
|
||||||
const key = `user:block:${userId}`;
|
const key = `user:block:${userId}`;
|
||||||
return redis.incr(key);
|
return redis.client.incr(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -78,5 +82,5 @@ export default {
|
|||||||
deleteSession,
|
deleteSession,
|
||||||
fetchUserBlock,
|
fetchUserBlock,
|
||||||
incrementUserBlock,
|
incrementUserBlock,
|
||||||
enabled: !!redis,
|
enabled: !!redis.enabled,
|
||||||
};
|
};
|
||||||
|
@ -5,7 +5,7 @@ export function getClientAuthToken() {
|
|||||||
return getItem(AUTH_TOKEN);
|
return getItem(AUTH_TOKEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setClientAuthToken(token) {
|
export function setClientAuthToken(token: string) {
|
||||||
setItem(AUTH_TOKEN, token);
|
setItem(AUTH_TOKEN, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ export const REPO_URL = 'https://github.com/umami-software/umami';
|
|||||||
export const UPDATES_URL = 'https://api.umami.is/v1/updates';
|
export const UPDATES_URL = 'https://api.umami.is/v1/updates';
|
||||||
export const TELEMETRY_PIXEL = 'https://i.umami.is/a.png';
|
export const TELEMETRY_PIXEL = 'https://i.umami.is/a.png';
|
||||||
|
|
||||||
export const DEFAULT_LOCALE = process.env.defaultLocale ?? 'en-US';
|
export const DEFAULT_LOCALE = process.env.defaultLocale || 'en-US';
|
||||||
export const DEFAULT_THEME = 'light';
|
export const DEFAULT_THEME = 'light';
|
||||||
export const DEFAULT_ANIMATION_DURATION = 300;
|
export const DEFAULT_ANIMATION_DURATION = 300;
|
||||||
export const DEFAULT_DATE_RANGE = '24hour';
|
export const DEFAULT_DATE_RANGE = '24hour';
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import redis from '@umami/redis-client';
|
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
|
import redis from '@umami/redis-client';
|
||||||
import { getAuthToken, parseShareToken } from 'lib/auth';
|
import { getAuthToken, parseShareToken } from 'lib/auth';
|
||||||
import { ROLES } from 'lib/constants';
|
import { ROLES } from 'lib/constants';
|
||||||
import { isUuid, secret } from 'lib/crypto';
|
import { secret } from 'lib/crypto';
|
||||||
import { findSession } from 'lib/session';
|
import { findSession } from 'lib/session';
|
||||||
import {
|
import {
|
||||||
badRequest,
|
badRequest,
|
||||||
createMiddleware,
|
createMiddleware,
|
||||||
|
forbidden,
|
||||||
parseSecureToken,
|
parseSecureToken,
|
||||||
tooManyRequest,
|
tooManyRequest,
|
||||||
unauthorized,
|
unauthorized,
|
||||||
@ -38,6 +39,9 @@ export const useSession = createMiddleware(async (req, res, next) => {
|
|||||||
if (e.message === 'Usage Limit.') {
|
if (e.message === 'Usage Limit.') {
|
||||||
return tooManyRequest(res, e.message);
|
return tooManyRequest(res, e.message);
|
||||||
}
|
}
|
||||||
|
if (e.message.startsWith('Website not found:')) {
|
||||||
|
return forbidden(res, e.message);
|
||||||
|
}
|
||||||
return badRequest(res, e.message);
|
return badRequest(res, e.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,19 +51,21 @@ export const useSession = createMiddleware(async (req, res, next) => {
|
|||||||
export const useAuth = createMiddleware(async (req, res, next) => {
|
export const useAuth = createMiddleware(async (req, res, next) => {
|
||||||
const token = getAuthToken(req);
|
const token = getAuthToken(req);
|
||||||
const payload = parseSecureToken(token, secret());
|
const payload = parseSecureToken(token, secret());
|
||||||
const shareToken = await parseShareToken(req);
|
const shareToken = await parseShareToken(req as any);
|
||||||
|
|
||||||
let user = null;
|
let user = null;
|
||||||
const { userId, authKey, grant } = payload || {};
|
const { userId, authKey, grant } = payload || {};
|
||||||
|
|
||||||
if (isUuid(userId)) {
|
if (userId) {
|
||||||
user = await getUserById(userId);
|
user = await getUserById(userId);
|
||||||
} else if (redis && authKey) {
|
} else if (redis.enabled && authKey) {
|
||||||
user = await redis.get(authKey);
|
const key = await redis.client.get(authKey);
|
||||||
|
|
||||||
|
user = await getUserById(key?.userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
log({ token, shareToken, payload, user, grant });
|
log('useAuth:', { token, shareToken, payload, user, grant });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user?.id && !shareToken) {
|
if (!user?.id && !shareToken) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import redis from '@umami/redis-client';
|
import redis from '@umami/redis-client';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
import { setAuthKey } from 'lib/auth';
|
import { saveAuth } from 'lib/auth';
|
||||||
import { secret } from 'lib/crypto';
|
import { secret } from 'lib/crypto';
|
||||||
import { useValidate } from 'lib/middleware';
|
import { useValidate } from 'lib/middleware';
|
||||||
import { NextApiRequestQueryBody, User } from 'lib/types';
|
import { NextApiRequestQueryBody, User } from 'lib/types';
|
||||||
@ -52,8 +52,8 @@ export default async (
|
|||||||
const user = await getUserByUsername(username, { includePassword: true });
|
const user = await getUserByUsername(username, { includePassword: true });
|
||||||
|
|
||||||
if (user && checkPassword(password, user.password)) {
|
if (user && checkPassword(password, user.password)) {
|
||||||
if (redis) {
|
if (redis.enabled) {
|
||||||
const token = await setAuthKey(user);
|
const token = await saveAuth({ userId: user.id });
|
||||||
|
|
||||||
return ok(res, { token, user });
|
return ok(res, { token, user });
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,8 @@ export default async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
await useAuth(req, res);
|
await useAuth(req, res);
|
||||||
|
|
||||||
if (req.method === 'POST') {
|
if (req.method === 'POST') {
|
||||||
if (redis) {
|
if (redis.enabled) {
|
||||||
await redis.del(getAuthToken(req));
|
await redis.client.del(getAuthToken(req));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ok(res);
|
return ok(res);
|
||||||
|
@ -3,13 +3,13 @@ import { useAuth } from 'lib/middleware';
|
|||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
import { badRequest, ok } from 'next-basics';
|
import { badRequest, ok } from 'next-basics';
|
||||||
import redis from '@umami/redis-client';
|
import redis from '@umami/redis-client';
|
||||||
import { setAuthKey } from 'lib/auth';
|
import { saveAuth } from 'lib/auth';
|
||||||
|
|
||||||
export default async (req: NextApiRequestAuth, res: NextApiResponse) => {
|
export default async (req: NextApiRequestAuth, res: NextApiResponse) => {
|
||||||
await useAuth(req, res);
|
await useAuth(req, res);
|
||||||
|
|
||||||
if (redis && req.auth.user) {
|
if (redis.enabled && req.auth.user) {
|
||||||
const token = await setAuthKey(req.auth.user, 86400);
|
const token = await saveAuth({ userId: req.auth.user.id }, 86400);
|
||||||
|
|
||||||
return ok(res, { user: req.auth.user, token });
|
return ok(res, { user: req.auth.user, token });
|
||||||
}
|
}
|
||||||
|
@ -6,5 +6,5 @@ import { ok } from 'next-basics';
|
|||||||
export default async (req: NextApiRequestAuth, res: NextApiResponse) => {
|
export default async (req: NextApiRequestAuth, res: NextApiResponse) => {
|
||||||
await useAuth(req, res);
|
await useAuth(req, res);
|
||||||
|
|
||||||
return ok(res, req.auth);
|
return ok(res, req.auth.user);
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { Resolver } from 'dns/promises';
|
|
||||||
import ipaddr from 'ipaddr.js';
|
import ipaddr from 'ipaddr.js';
|
||||||
import isbot from 'isbot';
|
import isbot from 'isbot';
|
||||||
import { COLLECTION_TYPE, HOSTNAME_REGEX } from 'lib/constants';
|
import { COLLECTION_TYPE, HOSTNAME_REGEX } from 'lib/constants';
|
||||||
@ -74,18 +73,18 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => {
|
|||||||
await useCors(req, res);
|
await useCors(req, res);
|
||||||
|
|
||||||
if (req.method === 'POST') {
|
if (req.method === 'POST') {
|
||||||
if (isbot(req.headers['user-agent']) && !process.env.DISABLE_BOT_CHECK) {
|
if (!process.env.DISABLE_BOT_CHECK && isbot(req.headers['user-agent'])) {
|
||||||
return ok(res);
|
return ok(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { type, payload } = req.body;
|
|
||||||
|
|
||||||
await useValidate(schema, req, res);
|
await useValidate(schema, req, res);
|
||||||
|
|
||||||
if (await hasBlockedIp(req)) {
|
if (hasBlockedIp(req)) {
|
||||||
return forbidden(res);
|
return forbidden(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { type, payload } = req.body;
|
||||||
|
|
||||||
const { url, referrer, name: eventName, data: eventData, title: pageTitle } = payload;
|
const { url, referrer, name: eventName, data: eventData, title: pageTitle } = payload;
|
||||||
|
|
||||||
await useSession(req, res);
|
await useSession(req, res);
|
||||||
@ -143,28 +142,16 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => {
|
|||||||
return methodNotAllowed(res);
|
return methodNotAllowed(res);
|
||||||
};
|
};
|
||||||
|
|
||||||
async function hasBlockedIp(req: NextApiRequestCollect) {
|
function hasBlockedIp(req: NextApiRequestCollect) {
|
||||||
const ignoreIps = process.env.IGNORE_IP;
|
const ignoreIps = process.env.IGNORE_IP;
|
||||||
const ignoreHostnames = process.env.IGNORE_HOSTNAME;
|
|
||||||
|
|
||||||
if (ignoreIps || ignoreHostnames) {
|
if (ignoreIps) {
|
||||||
const ips = [];
|
const ips = [];
|
||||||
|
|
||||||
if (ignoreIps) {
|
if (ignoreIps) {
|
||||||
ips.push(...ignoreIps.split(',').map(n => n.trim()));
|
ips.push(...ignoreIps.split(',').map(n => n.trim()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ignoreHostnames) {
|
|
||||||
const resolver = new Resolver();
|
|
||||||
const promises = ignoreHostnames
|
|
||||||
.split(',')
|
|
||||||
.map(n => resolver.resolve4(n.trim()).catch(() => {}));
|
|
||||||
|
|
||||||
await Promise.all(promises).then(resolvedIps => {
|
|
||||||
ips.push(...resolvedIps.filter(n => n).flatMap(n => n as string[]));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const clientIp = getIpAddress(req);
|
const clientIp = getIpAddress(req);
|
||||||
|
|
||||||
return ips.find(ip => {
|
return ips.find(ip => {
|
||||||
@ -177,8 +164,8 @@ async function hasBlockedIp(req: NextApiRequestCollect) {
|
|||||||
|
|
||||||
if (addr.kind() === range[0].kind() && addr.match(range)) return true;
|
if (addr.kind() === range[0].kind() && addr.match(range)) return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -11,13 +11,17 @@ export interface GetUserOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getUser(
|
async function getUser(
|
||||||
where: Prisma.UserWhereInput | Prisma.UserWhereUniqueInput,
|
where: Prisma.UserWhereUniqueInput,
|
||||||
options: GetUserOptions = {},
|
options: GetUserOptions = {},
|
||||||
): Promise<User> {
|
): Promise<User> {
|
||||||
const { includePassword = false, showDeleted = false } = options;
|
const { includePassword = false, showDeleted = false } = options;
|
||||||
|
|
||||||
return prisma.client.user.findFirst({
|
if (showDeleted) {
|
||||||
where: { ...where, ...(showDeleted ? {} : { deletedAt: null }) },
|
where.deletedAt = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return prisma.client.user.findUnique({
|
||||||
|
where,
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
username: true,
|
username: true,
|
||||||
@ -28,8 +32,8 @@ async function getUser(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUserById(userId: string, options: GetUserOptions = {}) {
|
export async function getUserById(id: string, options: GetUserOptions = {}) {
|
||||||
return getUser({ id: userId }, options);
|
return getUser({ id }, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUserByUsername(username: string, options: GetUserOptions = {}) {
|
export async function getUserByUsername(username: string, options: GetUserOptions = {}) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"target": "es2021",
|
||||||
"outDir": "./build",
|
"outDir": "./build",
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
|
213
yarn.lock
213
yarn.lock
@ -1353,12 +1353,12 @@
|
|||||||
"@formatjs/intl-localematcher" "0.2.25"
|
"@formatjs/intl-localematcher" "0.2.25"
|
||||||
tslib "^2.1.0"
|
tslib "^2.1.0"
|
||||||
|
|
||||||
"@formatjs/ecma402-abstract@1.17.2":
|
"@formatjs/ecma402-abstract@1.18.0":
|
||||||
version "1.17.2"
|
version "1.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.17.2.tgz#d197c6e26b9fd96ff7ba3b3a0cc2f25f1f2dcac3"
|
resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.0.tgz#e2120e7101020140661b58430a7ff4262705a2f2"
|
||||||
integrity sha512-k2mTh0m+IV1HRdU0xXM617tSQTi53tVR2muvYOsBeYcUgEAyxV1FOC7Qj279th3fBVQ+Dj6muvNJZcHSPNdbKg==
|
integrity sha512-PEVLoa3zBevWSCZzPIM/lvPCi8P5l4G+NXQMc/CjEiaCWgyHieUoo0nM7Bs0n/NbuQ6JpXEolivQ9pKSBHaDlA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@formatjs/intl-localematcher" "0.4.2"
|
"@formatjs/intl-localematcher" "0.5.2"
|
||||||
tslib "^2.4.0"
|
tslib "^2.4.0"
|
||||||
|
|
||||||
"@formatjs/ecma402-abstract@1.4.0":
|
"@formatjs/ecma402-abstract@1.4.0":
|
||||||
@ -1391,13 +1391,13 @@
|
|||||||
"@formatjs/icu-skeleton-parser" "1.3.6"
|
"@formatjs/icu-skeleton-parser" "1.3.6"
|
||||||
tslib "^2.1.0"
|
tslib "^2.1.0"
|
||||||
|
|
||||||
"@formatjs/icu-messageformat-parser@2.7.0":
|
"@formatjs/icu-messageformat-parser@2.7.3":
|
||||||
version "2.7.0"
|
version "2.7.3"
|
||||||
resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.0.tgz#9b13f2710a3b4efddfeb544480f684f27a53483b"
|
resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.3.tgz#c8c95e7c9f8141bdb93bea0e92e4fcace19d3c9f"
|
||||||
integrity sha512-7uqC4C2RqOaBQtcjqXsSpGRYVn+ckjhNga5T/otFh6MgxRrCJQqvjfbrGLpX1Lcbxdm5WH3Z2WZqt1+Tm/cn/Q==
|
integrity sha512-X/jy10V9S/vW+qlplqhMUxR8wErQ0mmIYSq4mrjpjDl9mbuGcCILcI1SUYkL5nlM4PJqpc0KOS0bFkkJNPxYRw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@formatjs/ecma402-abstract" "1.17.2"
|
"@formatjs/ecma402-abstract" "1.18.0"
|
||||||
"@formatjs/icu-skeleton-parser" "1.6.2"
|
"@formatjs/icu-skeleton-parser" "1.7.0"
|
||||||
tslib "^2.4.0"
|
tslib "^2.4.0"
|
||||||
|
|
||||||
"@formatjs/icu-skeleton-parser@1.3.6":
|
"@formatjs/icu-skeleton-parser@1.3.6":
|
||||||
@ -1408,30 +1408,30 @@
|
|||||||
"@formatjs/ecma402-abstract" "1.11.4"
|
"@formatjs/ecma402-abstract" "1.11.4"
|
||||||
tslib "^2.1.0"
|
tslib "^2.1.0"
|
||||||
|
|
||||||
"@formatjs/icu-skeleton-parser@1.6.2":
|
"@formatjs/icu-skeleton-parser@1.7.0":
|
||||||
version "1.6.2"
|
version "1.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.6.2.tgz#00303034dc08583973c8aa67b96534c49c0bad8d"
|
resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.7.0.tgz#796938d6d0ba8fc75bb9edee038d1350bfee32cb"
|
||||||
integrity sha512-VtB9Slo4ZL6QgtDFJ8Injvscf0xiDd4bIV93SOJTBjUF4xe2nAWOoSjLEtqIG+hlIs1sNrVKAaFo3nuTI4r5ZA==
|
integrity sha512-Cfdo/fgbZzpN/jlN/ptQVe0lRHora+8ezrEeg2RfrNjyp+YStwBy7cqDY8k5/z2LzXg6O0AdzAV91XS0zIWv+A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@formatjs/ecma402-abstract" "1.17.2"
|
"@formatjs/ecma402-abstract" "1.18.0"
|
||||||
tslib "^2.4.0"
|
tslib "^2.4.0"
|
||||||
|
|
||||||
"@formatjs/intl-displaynames@6.6.0":
|
"@formatjs/intl-displaynames@6.6.4":
|
||||||
version "6.6.0"
|
version "6.6.4"
|
||||||
resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-6.6.0.tgz#6f590784b1bcdc1b96d4dba158bdce350f876804"
|
resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-6.6.4.tgz#dd9ca9bb2d1f4b140cc8814667bc830802621674"
|
||||||
integrity sha512-bskUou9boZOzTqI8JdNCNkDavXf8uWWz/6NG1og/XJKpn4zsfiLdQ9EYKhVe/CfbCjlSyieJYn7/NztdoprHjw==
|
integrity sha512-ET8KQ+L9Q0K8x1SnJQa4DNssUcbATlMopWqYvGGR8yAvw5qwAQc1fv+DshCoZNIE9pbcue0IGC4kWNAkWqlFag==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@formatjs/ecma402-abstract" "1.17.2"
|
"@formatjs/ecma402-abstract" "1.18.0"
|
||||||
"@formatjs/intl-localematcher" "0.4.2"
|
"@formatjs/intl-localematcher" "0.5.2"
|
||||||
tslib "^2.4.0"
|
tslib "^2.4.0"
|
||||||
|
|
||||||
"@formatjs/intl-listformat@7.5.0":
|
"@formatjs/intl-listformat@7.5.3":
|
||||||
version "7.5.0"
|
version "7.5.3"
|
||||||
resolved "https://registry.yarnpkg.com/@formatjs/intl-listformat/-/intl-listformat-7.5.0.tgz#dbccf2e0f07792aa1c273702796bdad061dc27ae"
|
resolved "https://registry.yarnpkg.com/@formatjs/intl-listformat/-/intl-listformat-7.5.3.tgz#c6f028471839cd1014760498f783fdfe011422d5"
|
||||||
integrity sha512-n9FsXGl1T2ZbX6wSyrzCDJHrbJR0YJ9ZNsAqUvHXfbY3nsOmGnSTf5+bkuIp1Xiywu7m1X1Pfm/Ngp/yK1H84A==
|
integrity sha512-l7EOr0Yh1m8KagytukB90yw81uyzrM7amKFrgxXqphz4KeSIL0KPa68lPsdtZ+JmQB73GaDQRwLOwUKFZ1VZPQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@formatjs/ecma402-abstract" "1.17.2"
|
"@formatjs/ecma402-abstract" "1.18.0"
|
||||||
"@formatjs/intl-localematcher" "0.4.2"
|
"@formatjs/intl-localematcher" "0.5.2"
|
||||||
tslib "^2.4.0"
|
tslib "^2.4.0"
|
||||||
|
|
||||||
"@formatjs/intl-localematcher@0.2.25":
|
"@formatjs/intl-localematcher@0.2.25":
|
||||||
@ -1441,10 +1441,10 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
tslib "^2.1.0"
|
tslib "^2.1.0"
|
||||||
|
|
||||||
"@formatjs/intl-localematcher@0.4.2":
|
"@formatjs/intl-localematcher@0.5.2":
|
||||||
version "0.4.2"
|
version "0.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.4.2.tgz#7e6e596dbaf2f0c5a7c22da5a01d5c55f4c37e9a"
|
resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.2.tgz#5fcf029fd218905575e5080fa33facdcb623d532"
|
||||||
integrity sha512-BGdtJFmaNJy5An/Zan4OId/yR9Ih1OojFjcduX/xOvq798OgWSyDtd6Qd5jqJXwJs1ipe4Fxu9+cshic5Ox2tA==
|
integrity sha512-txaaE2fiBMagLrR4jYhxzFO6wEdEG4TPMqrzBAcbr4HFUYzH/YC+lg6OIzKCHm8WgDdyQevxbAAV1OgcXctuGw==
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^2.4.0"
|
tslib "^2.4.0"
|
||||||
|
|
||||||
@ -1456,17 +1456,17 @@
|
|||||||
"@formatjs/ecma402-abstract" "1.4.0"
|
"@formatjs/ecma402-abstract" "1.4.0"
|
||||||
tslib "^2.0.1"
|
tslib "^2.0.1"
|
||||||
|
|
||||||
"@formatjs/intl@2.9.4":
|
"@formatjs/intl@2.9.9":
|
||||||
version "2.9.4"
|
version "2.9.9"
|
||||||
resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-2.9.4.tgz#6f97a8e6e282086c39c8e502face5b2839f47b6f"
|
resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-2.9.9.tgz#866629b565e20dd7490f9e77ee41a00748913e8f"
|
||||||
integrity sha512-hY0UlbDz8jY12RkQtkzxe3OfUmsIcUcsvVYyr1TFue6oTrUHqpkmYLdQ626V3BCSLc90EZDXdvmsPfMd3hTcYQ==
|
integrity sha512-JI3CNgL2Zdg5lv9ncT2sYKqbAj2RGrCbdzaCckIxMPxn4QuHuOVvYUGmBAXVusBmfG/0sxLmMrnwnBioz+QKdA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@formatjs/ecma402-abstract" "1.17.2"
|
"@formatjs/ecma402-abstract" "1.18.0"
|
||||||
"@formatjs/fast-memoize" "2.2.0"
|
"@formatjs/fast-memoize" "2.2.0"
|
||||||
"@formatjs/icu-messageformat-parser" "2.7.0"
|
"@formatjs/icu-messageformat-parser" "2.7.3"
|
||||||
"@formatjs/intl-displaynames" "6.6.0"
|
"@formatjs/intl-displaynames" "6.6.4"
|
||||||
"@formatjs/intl-listformat" "7.5.0"
|
"@formatjs/intl-listformat" "7.5.3"
|
||||||
intl-messageformat "10.5.4"
|
intl-messageformat "10.5.8"
|
||||||
tslib "^2.4.0"
|
tslib "^2.4.0"
|
||||||
|
|
||||||
"@formatjs/ts-transformer@3.9.4":
|
"@formatjs/ts-transformer@3.9.4":
|
||||||
@ -1941,22 +1941,27 @@
|
|||||||
"@parcel/watcher-win32-ia32" "2.3.0"
|
"@parcel/watcher-win32-ia32" "2.3.0"
|
||||||
"@parcel/watcher-win32-x64" "2.3.0"
|
"@parcel/watcher-win32-x64" "2.3.0"
|
||||||
|
|
||||||
"@prisma/client@5.3.1":
|
"@prisma/client@5.4.2":
|
||||||
version "5.3.1"
|
version "5.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.3.1.tgz#fc7fc2d91e814cc4fe18a4bc5e78bf851c26985e"
|
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.4.2.tgz#786f9c1d8f06d955933004ac638d14da4bf14025"
|
||||||
integrity sha512-ArOKjHwdFZIe1cGU56oIfy7wRuTn0FfZjGuU/AjgEBOQh+4rDkB6nF+AGHP8KaVpkBIiHGPQh3IpwQ3xDMdO0Q==
|
integrity sha512-2xsPaz4EaMKj1WS9iW6MlPhmbqtBsXAOeVttSePp8vTFTtvzh2hZbDgswwBdSCgPzmmwF+tLB259QzggvCmJqA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@prisma/engines-version" "5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59"
|
"@prisma/engines-version" "5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574"
|
||||||
|
|
||||||
"@prisma/engines-version@5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59":
|
"@prisma/engines-version@5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574":
|
||||||
version "5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59"
|
version "5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574"
|
||||||
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59.tgz#7eb6f5c6b7628b8b39df55c903f411528a6f761c"
|
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574.tgz#ff14f2926890edee47e8f1d08df7b4f392ee34bf"
|
||||||
integrity sha512-y5qbUi3ql2Xg7XraqcXEdMHh0MocBfnBzDn5GbV1xk23S3Mq8MGs+VjacTNiBh3dtEdUERCrUUG7Z3QaJ+h79w==
|
integrity sha512-wvupDL4AA1vf4TQNANg7kR7y98ITqPsk6aacfBxZKtrJKRIsWjURHkZCGcQliHdqCiW/hGreO6d6ZuSv9MhdAA==
|
||||||
|
|
||||||
"@prisma/engines@5.3.1":
|
"@prisma/engines@5.4.2":
|
||||||
version "5.3.1"
|
version "5.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.3.1.tgz#53cc72a5ed176dc27d22305fe5569c64cc78b381"
|
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.4.2.tgz#ba2b7faeb227c76e423e88f962afe6a031319f3f"
|
||||||
integrity sha512-6QkILNyfeeN67BNEPEtkgh3Xo2tm6D7V+UhrkBbRHqKw9CTaz/vvTP/ROwYSP/3JT2MtIutZm/EnhxUiuOPVDA==
|
integrity sha512-fqeucJ3LH0e1eyFdT0zRx+oETLancu5+n4lhiYECyEz6H2RDskPJHJYHkVc0LhkU4Uv7fuEnppKU3nVKNzMh8g==
|
||||||
|
|
||||||
|
"@prisma/extension-read-replicas@^0.3.0":
|
||||||
|
version "0.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@prisma/extension-read-replicas/-/extension-read-replicas-0.3.0.tgz#2842a7c928f957c1dd58a6256104797596d43426"
|
||||||
|
integrity sha512-F9+rSmYday6GT2qjhJtkZcBOpLO5LtpvFcMGqrBDHf+78LEdSuxfFjOxYlNuqk4B+th62yxpbhfpmB9/Mca14Q==
|
||||||
|
|
||||||
"@react-spring/animated@~9.7.3":
|
"@react-spring/animated@~9.7.3":
|
||||||
version "9.7.3"
|
version "9.7.3"
|
||||||
@ -2393,10 +2398,12 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190"
|
||||||
integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==
|
integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==
|
||||||
|
|
||||||
"@types/node@^18.11.9":
|
"@types/node@^20.9.0":
|
||||||
version "18.18.6"
|
version "20.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.18.6.tgz#26da694f75cdb057750f49d099da5e3f3824cb3e"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.9.0.tgz#bfcdc230583aeb891cf51e73cfdaacdd8deae298"
|
||||||
integrity sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==
|
integrity sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==
|
||||||
|
dependencies:
|
||||||
|
undici-types "~5.26.4"
|
||||||
|
|
||||||
"@types/normalize-package-data@^2.4.0":
|
"@types/normalize-package-data@^2.4.0":
|
||||||
version "2.4.3"
|
version "2.4.3"
|
||||||
@ -2408,10 +2415,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.9.tgz#b6f785caa7ea1fe4414d9df42ee0ab67f23d8a6d"
|
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.9.tgz#b6f785caa7ea1fe4414d9df42ee0ab67f23d8a6d"
|
||||||
integrity sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g==
|
integrity sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g==
|
||||||
|
|
||||||
"@types/react-dom@^18.0.8":
|
"@types/react-dom@^18.2.15":
|
||||||
version "18.2.14"
|
version "18.2.15"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.14.tgz#c01ba40e5bb57fc1dc41569bb3ccdb19eab1c539"
|
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.15.tgz#921af67f9ee023ac37ea84b1bc0cc40b898ea522"
|
||||||
integrity sha512-V835xgdSVmyQmI1KLV2BEIUgqEuinxp9O4G6g3FqO/SqLac049E53aysv0oEFD2kHfejeKU+ZqL2bcFWj9gLAQ==
|
integrity sha512-HWMdW+7r7MR5+PZqJF6YFNSCtjz1T0dsvo/f1BV6HkV+6erD/nA7wd9NM00KVG83zf2nJ7uATPO9ttdIPvi3gg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
@ -2425,7 +2432,7 @@
|
|||||||
hoist-non-react-statics "^3.3.0"
|
hoist-non-react-statics "^3.3.0"
|
||||||
redux "^4.0.0"
|
redux "^4.0.0"
|
||||||
|
|
||||||
"@types/react@*", "@types/react@16 || 17 || 18", "@types/react@^18.0.25":
|
"@types/react@*", "@types/react@16 || 17 || 18":
|
||||||
version "18.2.30"
|
version "18.2.30"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.30.tgz#b84f786864fc46f18545364a54d5e1316308e59b"
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.30.tgz#b84f786864fc46f18545364a54d5e1316308e59b"
|
||||||
integrity sha512-OfqdJnDsSo4UNw0bqAjFCuBpLYQM7wvZidz0hVxHRjrEkzRlvZL1pJVyOSY55HMiKvRNEo9DUBRuEl7FNlJ/Vg==
|
integrity sha512-OfqdJnDsSo4UNw0bqAjFCuBpLYQM7wvZidz0hVxHRjrEkzRlvZL1pJVyOSY55HMiKvRNEo9DUBRuEl7FNlJ/Vg==
|
||||||
@ -2434,6 +2441,15 @@
|
|||||||
"@types/scheduler" "*"
|
"@types/scheduler" "*"
|
||||||
csstype "^3.0.2"
|
csstype "^3.0.2"
|
||||||
|
|
||||||
|
"@types/react@^18.2.37":
|
||||||
|
version "18.2.37"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.37.tgz#0f03af69e463c0f19a356c2660dbca5d19c44cae"
|
||||||
|
integrity sha512-RGAYMi2bhRgEXT3f4B92WTohopH6bIXw05FuGlmJEnv/omEn190+QYEIYxIAuIBdKgboYYdVved2p1AxZVQnaw==
|
||||||
|
dependencies:
|
||||||
|
"@types/prop-types" "*"
|
||||||
|
"@types/scheduler" "*"
|
||||||
|
csstype "^3.0.2"
|
||||||
|
|
||||||
"@types/resolve@1.20.2":
|
"@types/resolve@1.20.2":
|
||||||
version "1.20.2"
|
version "1.20.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975"
|
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975"
|
||||||
@ -2585,18 +2601,18 @@
|
|||||||
"@typescript-eslint/types" "6.8.0"
|
"@typescript-eslint/types" "6.8.0"
|
||||||
eslint-visitor-keys "^3.4.1"
|
eslint-visitor-keys "^3.4.1"
|
||||||
|
|
||||||
"@umami/prisma-client@^0.3.0":
|
"@umami/prisma-client@^0.5.0":
|
||||||
version "0.3.0"
|
version "0.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/@umami/prisma-client/-/prisma-client-0.3.0.tgz#fea35a44c76af0e4ce58288107cda3ee76fc80ba"
|
resolved "https://registry.yarnpkg.com/@umami/prisma-client/-/prisma-client-0.5.0.tgz#e2287debbf21f9344c989b9e7192491df88513bf"
|
||||||
integrity sha512-88y/WJX2TEZaUfP+PTretGUL6YdwZCBbhaoeC87eTF3l1aG0Lv3TsmW0lJy5rbKpVqrFJ8zrtvCMP/vt7WeIjg==
|
integrity sha512-BkStMrvxYZQPwEIyy30JJPucTTsmQqb4jD8+ciSHxcBc7039cW0XyX3TL/u9ebZmANzIuNO0XiBArwjWulGIjg==
|
||||||
dependencies:
|
dependencies:
|
||||||
chalk "^4.1.2"
|
chalk "^4.1.2"
|
||||||
debug "^4.3.4"
|
debug "^4.3.4"
|
||||||
|
|
||||||
"@umami/redis-client@^0.16.0":
|
"@umami/redis-client@^0.18.0":
|
||||||
version "0.16.0"
|
version "0.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/@umami/redis-client/-/redis-client-0.16.0.tgz#0050d1f93338d88691c983f3c0cd4a62da20212b"
|
resolved "https://registry.yarnpkg.com/@umami/redis-client/-/redis-client-0.18.0.tgz#6a2315a878f2688dae162d93e88dfc4e097fc48e"
|
||||||
integrity sha512-fE08lkMvhXbkXSdSRpG0R/9a3xIiTvwD6f+hKERFZrpfvJJlH3Uf4Jod8Ahg/+TmD03ihSQPooUT3T9Ig3dfaQ==
|
integrity sha512-uDuX5w7ydlOZWrq0h6fADG3XWOhto9fAqrUVu85FUhdijWoGlv5f8adaL8FAah5jD+/Byw2VyGQaZO4VhboEZw==
|
||||||
dependencies:
|
dependencies:
|
||||||
debug "^4.3.4"
|
debug "^4.3.4"
|
||||||
redis "^4.5.1"
|
redis "^4.5.1"
|
||||||
@ -5163,14 +5179,14 @@ intl-messageformat-parser@^5.3.7:
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@formatjs/intl-numberformat" "^5.5.2"
|
"@formatjs/intl-numberformat" "^5.5.2"
|
||||||
|
|
||||||
intl-messageformat@10.5.4:
|
intl-messageformat@10.5.8:
|
||||||
version "10.5.4"
|
version "10.5.8"
|
||||||
resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.5.4.tgz#7b212b083f1b354d7e282518e78057e025134af9"
|
resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.5.8.tgz#7184da425f360a53a5483a6194e16d666b011fc0"
|
||||||
integrity sha512-z+hrFdiJ/heRYlzegrdFYqU1m/KOMOVMqNilIArj+PbsuU8TNE7v4TWdQgSoxlxbT4AcZH3Op3/Fu15QTp+W1w==
|
integrity sha512-NRf0jpBWV0vd671G5b06wNofAN8tp7WWDogMZyaU8GUAsmbouyvgwmFJI7zLjfAMpm3zK+vSwRP3jzaoIcMbaA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@formatjs/ecma402-abstract" "1.17.2"
|
"@formatjs/ecma402-abstract" "1.18.0"
|
||||||
"@formatjs/fast-memoize" "2.2.0"
|
"@formatjs/fast-memoize" "2.2.0"
|
||||||
"@formatjs/icu-messageformat-parser" "2.7.0"
|
"@formatjs/icu-messageformat-parser" "2.7.3"
|
||||||
tslib "^2.4.0"
|
tslib "^2.4.0"
|
||||||
|
|
||||||
ioredis@^5.3.2:
|
ioredis@^5.3.2:
|
||||||
@ -7361,12 +7377,12 @@ pretty-bytes@^5.6.0:
|
|||||||
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
|
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
|
||||||
integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==
|
integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==
|
||||||
|
|
||||||
prisma@5.3.1:
|
prisma@5.4.2:
|
||||||
version "5.3.1"
|
version "5.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.3.1.tgz#a0932c1c1a5ed4ff449d064b193d9c7e94e8bf77"
|
resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.4.2.tgz#7eac9276439ec7073ec697c6c0dfa259d96e955e"
|
||||||
integrity sha512-Wp2msQIlMPHe+5k5Od6xnsI/WNG7UJGgFUJgqv/ygc7kOECZapcSz/iU4NIEzISs3H1W9sFLjAPbg/gOqqtB7A==
|
integrity sha512-GDMZwZy7mysB2oXU+angQqJ90iaPFdD0rHaZNkn+dio5NRkGLmMqmXs31//tg/qXT3iB0cTQwnGGQNuirhSTZg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@prisma/engines" "5.3.1"
|
"@prisma/engines" "5.4.2"
|
||||||
|
|
||||||
promise.series@^0.2.0:
|
promise.series@^0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
@ -7460,10 +7476,10 @@ rc@^1.2.7:
|
|||||||
minimist "^1.2.0"
|
minimist "^1.2.0"
|
||||||
strip-json-comments "~2.0.1"
|
strip-json-comments "~2.0.1"
|
||||||
|
|
||||||
react-basics@^0.105.0:
|
react-basics@^0.107.0:
|
||||||
version "0.105.0"
|
version "0.107.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.105.0.tgz#94eda703b3c0728e817b6e9d086e5d1c6c68f25c"
|
resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.107.0.tgz#e5615792cbb3e4707ba5c8f438b29d6a88cf38b3"
|
||||||
integrity sha512-iKYtfB0A2vsmO+X4jaX64XdmHE836w8TG2jFQ0pi5Qp0ktL0lAL9/q0IrWUjNr86hi0apg46aeJWxY+qQO+T1g==
|
integrity sha512-jYnP1z2LTotxXWYwxOBvF26vXxSUBJB0x62YPKkEr1vmJGeg8iOLr8JGF8KE3R6E+NTqzRt6Bmdtt93mjaog4A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@react-spring/web" "^9.7.3"
|
"@react-spring/web" "^9.7.3"
|
||||||
classnames "^2.3.1"
|
classnames "^2.3.1"
|
||||||
@ -7504,20 +7520,20 @@ react-hook-form@^7.34.2:
|
|||||||
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.47.0.tgz#a42f07266bd297ddf1f914f08f4b5f9783262f31"
|
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.47.0.tgz#a42f07266bd297ddf1f914f08f4b5f9783262f31"
|
||||||
integrity sha512-F/TroLjTICipmHeFlMrLtNLceO2xr1jU3CyiNla5zdwsGUGu2UOxxR4UyJgLlhMwLW/Wzp4cpJ7CPfgJIeKdSg==
|
integrity sha512-F/TroLjTICipmHeFlMrLtNLceO2xr1jU3CyiNla5zdwsGUGu2UOxxR4UyJgLlhMwLW/Wzp4cpJ7CPfgJIeKdSg==
|
||||||
|
|
||||||
react-intl@^6.4.7:
|
react-intl@^6.5.5:
|
||||||
version "6.5.0"
|
version "6.5.5"
|
||||||
resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-6.5.0.tgz#0ff04170f91e1bcbcd3301dfb2ae39a258827ec7"
|
resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-6.5.5.tgz#d2de7bfd79718a7e3d8031e2599e94e0c8638377"
|
||||||
integrity sha512-ZnBYFlFUU1ivhvWBA87XJLAr9nR8yeC1/83e6AL7yiHbWH7xQE7tyMyIyw6or78EvU9Hx8Sh8LUDC4bGrNxXOA==
|
integrity sha512-cI5UKvBh4tc1zxLIziHBYGMX3dhYWDEFlvUDVN6NfT2i96zTXz/zH2AmM8+2waqgOhwkFUzd+7kK1G9q7fiC2g==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@formatjs/ecma402-abstract" "1.17.2"
|
"@formatjs/ecma402-abstract" "1.18.0"
|
||||||
"@formatjs/icu-messageformat-parser" "2.7.0"
|
"@formatjs/icu-messageformat-parser" "2.7.3"
|
||||||
"@formatjs/intl" "2.9.4"
|
"@formatjs/intl" "2.9.9"
|
||||||
"@formatjs/intl-displaynames" "6.6.0"
|
"@formatjs/intl-displaynames" "6.6.4"
|
||||||
"@formatjs/intl-listformat" "7.5.0"
|
"@formatjs/intl-listformat" "7.5.3"
|
||||||
"@types/hoist-non-react-statics" "^3.3.1"
|
"@types/hoist-non-react-statics" "^3.3.1"
|
||||||
"@types/react" "16 || 17 || 18"
|
"@types/react" "16 || 17 || 18"
|
||||||
hoist-non-react-statics "^3.3.2"
|
hoist-non-react-statics "^3.3.2"
|
||||||
intl-messageformat "10.5.4"
|
intl-messageformat "10.5.8"
|
||||||
tslib "^2.4.0"
|
tslib "^2.4.0"
|
||||||
|
|
||||||
react-is@^16.13.1, react-is@^16.7.0:
|
react-is@^16.13.1, react-is@^16.7.0:
|
||||||
@ -8898,6 +8914,11 @@ undici-types@~5.25.1:
|
|||||||
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.25.3.tgz#e044115914c85f0bcbb229f346ab739f064998c3"
|
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.25.3.tgz#e044115914c85f0bcbb229f346ab739f064998c3"
|
||||||
integrity sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==
|
integrity sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==
|
||||||
|
|
||||||
|
undici-types@~5.26.4:
|
||||||
|
version "5.26.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
|
||||||
|
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
|
||||||
|
|
||||||
unenv@^1.7.4:
|
unenv@^1.7.4:
|
||||||
version "1.7.4"
|
version "1.7.4"
|
||||||
resolved "https://registry.yarnpkg.com/unenv/-/unenv-1.7.4.tgz#a0e5a78de2c7c3c4563c06ba9763c96c59db3333"
|
resolved "https://registry.yarnpkg.com/unenv/-/unenv-1.7.4.tgz#a0e5a78de2c7c3c4563c06ba9763c96c59db3333"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user