+
diff --git a/components/metrics/RealtimeViews.js b/components/metrics/RealtimeViews.js
new file mode 100644
index 00000000..f07facef
--- /dev/null
+++ b/components/metrics/RealtimeViews.js
@@ -0,0 +1,100 @@
+import React, { useMemo, useState, useCallback } from 'react';
+import { FormattedMessage } from 'react-intl';
+import firstBy from 'thenby';
+import { percentFilter } from 'lib/filters';
+import DataTable from './DataTable';
+import FilterButtons from 'components/common/FilterButtons';
+
+const FILTER_REFERRERS = 0;
+const FILTER_PAGES = 1;
+
+export default function RealtimeViews({ websiteId, data, websites }) {
+ const { pageviews } = data;
+ const [filter, setFilter] = useState(FILTER_REFERRERS);
+ const domains = useMemo(() => websites.map(({ domain }) => domain), [websites]);
+ const getDomain = useCallback(
+ id => websites.find(({ website_id }) => website_id === id)?.domain,
+ [websites],
+ );
+
+ const buttons = [
+ {
+ label:
,
+ value: FILTER_REFERRERS,
+ },
+ {
+ label:
,
+ value: FILTER_PAGES,
+ },
+ ];
+
+ const [referrers, pages] = useMemo(() => {
+ if (pageviews) {
+ const referrers = percentFilter(
+ pageviews
+ .reduce((arr, { referrer }) => {
+ if (referrer?.startsWith('http')) {
+ const hostname = new URL(referrer).hostname.replace(/^www\./, '');
+
+ if (hostname && !domains.includes(hostname)) {
+ const row = arr.find(({ x }) => x === hostname);
+
+ if (!row) {
+ arr.push({ x: hostname, y: 1 });
+ } else {
+ row.y += 1;
+ }
+ }
+ }
+ return arr;
+ }, [])
+ .sort(firstBy('y', -1)),
+ );
+
+ const pages = percentFilter(
+ pageviews
+ .reduce((arr, { url, website_id }) => {
+ if (url?.startsWith('/')) {
+ if (!websiteId) {
+ url = `${getDomain(website_id)}${url}`;
+ }
+ const row = arr.find(({ x }) => x === url);
+
+ if (!row) {
+ arr.push({ x: url, y: 1 });
+ } else {
+ row.y += 1;
+ }
+ }
+ return arr;
+ }, [])
+ .sort(firstBy('y', -1)),
+ );
+
+ return [referrers, pages];
+ }
+ return [];
+ }, [pageviews]);
+
+ return (
+ <>
+
+ {filter === FILTER_REFERRERS && (
+
}
+ metric={
}
+ data={referrers}
+ height={400}
+ />
+ )}
+ {filter === FILTER_PAGES && (
+
}
+ metric={
}
+ data={pages}
+ height={400}
+ />
+ )}
+ >
+ );
+}
diff --git a/components/metrics/ReferrersTable.js b/components/metrics/ReferrersTable.js
index 2e04b6dc..2d51ab74 100644
--- a/components/metrics/ReferrersTable.js
+++ b/components/metrics/ReferrersTable.js
@@ -1,11 +1,13 @@
import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl';
import MetricsTable from './MetricsTable';
-import ButtonGroup from 'components/common/ButtonGroup';
-import ButtonLayout from 'components/layout/ButtonLayout';
-import { FILTER_DOMAIN_ONLY, FILTER_COMBINED, FILTER_RAW } from 'lib/constants';
+import FilterButtons from 'components/common/FilterButtons';
import { refFilter } from 'lib/filters';
+export const FILTER_DOMAIN_ONLY = 0;
+export const FILTER_COMBINED = 1;
+export const FILTER_RAW = 2;
+
export default function ReferrersTable({ websiteId, websiteDomain, showFilters, ...props }) {
const [filter, setFilter] = useState(FILTER_COMBINED);
@@ -52,11 +54,3 @@ export default function ReferrersTable({ websiteId, websiteDomain, showFilters,
>
);
}
-
-const FilterButtons = ({ buttons, selected, onClick }) => {
- return (
-
-
-
- );
-};
diff --git a/components/pages/RealtimeDashboard.js b/components/pages/RealtimeDashboard.js
index 2dbf858c..1a2a3f54 100644
--- a/components/pages/RealtimeDashboard.js
+++ b/components/pages/RealtimeDashboard.js
@@ -9,16 +9,14 @@ import RealtimeLog from 'components/metrics/RealtimeLog';
import RealtimeHeader from 'components/metrics/RealtimeHeader';
import WorldMap from 'components/common/WorldMap';
import DataTable from 'components/metrics/DataTable';
+import RealtimeViews from 'components/metrics/RealtimeViews';
import useFetch from 'hooks/useFetch';
import useLocale from 'hooks/useLocale';
import useCountryNames from 'hooks/useCountryNames';
import { percentFilter } from 'lib/filters';
-import { TOKEN_HEADER } from 'lib/constants';
+import { TOKEN_HEADER, REALTIME_RANGE, REALTIME_INTERVAL } from 'lib/constants';
import styles from './RealtimeDashboard.module.css';
-const REALTIME_RANGE = 30;
-const REALTIME_INTERVAL = 3000;
-
function mergeData(state, data, time) {
const ids = state.map(({ __id }) => __id);
return state
@@ -43,7 +41,10 @@ export default function RealtimeDashboard() {
headers: { [TOKEN_HEADER]: init?.token },
});
- const renderCountryName = useCallback(({ x }) => countryNames[x], []);
+ const renderCountryName = useCallback(
+ ({ x }) =>
{countryNames[x]},
+ [countryNames],
+ );
const realtimeData = useMemo(() => {
if (data) {
@@ -83,38 +84,11 @@ export default function RealtimeDashboard() {
return [];
}, [realtimeData?.sessions]);
- const referrers = useMemo(() => {
- if (realtimeData?.pageviews) {
- return percentFilter(
- realtimeData.pageviews
- .reduce((arr, { referrer }) => {
- if (referrer?.startsWith('http')) {
- const { hostname } = new URL(referrer);
-
- if (!data.domains.includes(hostname)) {
- const row = arr.find(({ x }) => x === hostname);
-
- if (!row) {
- arr.push({ x: hostname, y: 1 });
- } else {
- row.y += 1;
- }
- }
- }
- return arr;
- }, [])
- .sort(firstBy('y', -1)),
- );
- }
- return [];
- }, [realtimeData?.pageviews]);
-
useEffect(() => {
if (init && !data) {
const { websites, data } = init;
- const domains = init.websites.map(({ domain }) => domain);
- setData({ websites, domains, ...data });
+ setData({ websites, ...data });
}
}, [init]);
@@ -158,12 +132,7 @@ export default function RealtimeDashboard() {
- }
- metric={}
- data={referrers}
- height={400}
- />
+
diff --git a/components/pages/WebsiteDetails.js b/components/pages/WebsiteDetails.js
index b808f44f..7385bf3f 100644
--- a/components/pages/WebsiteDetails.js
+++ b/components/pages/WebsiteDetails.js
@@ -173,7 +173,14 @@ export default function WebsiteDetails({ websiteId }) {
contentClassName={styles.content}
menu={menuOptions}
>
-
+
)}
diff --git a/lib/auth.js b/lib/auth.js
index 379f6777..acfbe422 100644
--- a/lib/auth.js
+++ b/lib/auth.js
@@ -33,7 +33,7 @@ export async function allowQuery(req, skipToken) {
const website = await getWebsiteById(websiteId);
if (website) {
- if (token && !skipToken) {
+ if (token && token !== 'undefined' && !skipToken) {
return isValidToken(token, { website_id: websiteId });
}
diff --git a/lib/constants.js b/lib/constants.js
index 72f7fce6..65702620 100644
--- a/lib/constants.js
+++ b/lib/constants.js
@@ -6,6 +6,15 @@ export const THEME_CONFIG = 'umami.theme';
export const VERSION_CHECK = 'umami.version-check';
export const TOKEN_HEADER = 'x-umami-token';
+export const DEFAULT_LOCALE = 'en-US';
+export const DEFAULT_THEME = 'light';
+export const DEFAUL_CHART_HEIGHT = 400;
+export const DEFAULT_ANIMATION_DURATION = 300;
+export const DEFAULT_DATE_RANGE = '24hour';
+
+export const REALTIME_RANGE = 30;
+export const REALTIME_INTERVAL = 3000;
+
export const THEME_COLORS = {
light: {
primary: '#2680eb',
@@ -52,12 +61,6 @@ export const EVENT_COLORS = [
'#ffec16',
];
-export const DEFAULT_LOCALE = 'en-US';
-export const DEFAULT_THEME = 'light';
-export const DEFAUL_CHART_HEIGHT = 400;
-export const DEFAULT_ANIMATION_DURATION = 300;
-export const DEFAULT_DATE_RANGE = '24hour';
-
export const POSTGRESQL = 'postgresql';
export const MYSQL = 'mysql';
@@ -77,10 +80,6 @@ export const POSTGRESQL_DATE_FORMATS = {
year: 'YYYY-01-01',
};
-export const FILTER_DOMAIN_ONLY = 0;
-export const FILTER_COMBINED = 1;
-export const FILTER_RAW = 2;
-
export const DOMAIN_REGEX = /localhost(:\d{1,5})?|((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}/;
export const DESKTOP_SCREEN_WIDTH = 1920;