mirror of
https://github.com/kremalicious/umami.git
synced 2025-02-14 21:10:34 +01:00
Merge branch 'dev' into analytics
This commit is contained in:
commit
ac43f55636
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "umami",
|
"name": "umami",
|
||||||
"version": "2.5.0",
|
"version": "2.6.0",
|
||||||
"description": "A simple, fast, privacy-focused alternative to Google Analytics.",
|
"description": "A simple, fast, privacy-focused alternative to Google Analytics.",
|
||||||
"author": "Mike Cao <mike@mikecao.com>",
|
"author": "Mike Cao <mike@mikecao.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -29,16 +29,6 @@ const aliasConfig = {
|
|||||||
customResolver,
|
customResolver,
|
||||||
};
|
};
|
||||||
|
|
||||||
const external = [
|
|
||||||
'react',
|
|
||||||
'react-dom',
|
|
||||||
'react/jsx-runtime',
|
|
||||||
'react-intl',
|
|
||||||
'react-basics',
|
|
||||||
'classnames',
|
|
||||||
'next',
|
|
||||||
];
|
|
||||||
|
|
||||||
const jsBundle = {
|
const jsBundle = {
|
||||||
input: 'src/index.ts',
|
input: 'src/index.ts',
|
||||||
output: [
|
output: [
|
||||||
|
@ -4,7 +4,7 @@ const path = require('path');
|
|||||||
const https = require('https');
|
const https = require('https');
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
|
|
||||||
const src = path.resolve(__dirname, '../lang');
|
const src = path.resolve(__dirname, '../src/lang');
|
||||||
const dest = path.resolve(__dirname, '../public/intl/country');
|
const dest = path.resolve(__dirname, '../public/intl/country');
|
||||||
const files = fs.readdirSync(src);
|
const files = fs.readdirSync(src);
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ const path = require('path');
|
|||||||
const https = require('https');
|
const https = require('https');
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
|
|
||||||
const src = path.resolve(__dirname, '../lang');
|
const src = path.resolve(__dirname, '../src/lang');
|
||||||
const dest = path.resolve(__dirname, '../public/intl/language');
|
const dest = path.resolve(__dirname, '../public/intl/language');
|
||||||
const files = fs.readdirSync(src);
|
const files = fs.readdirSync(src);
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ const path = require('path');
|
|||||||
const del = require('del');
|
const del = require('del');
|
||||||
const prettier = require('prettier');
|
const prettier = require('prettier');
|
||||||
|
|
||||||
const src = path.resolve(__dirname, '../lang');
|
const src = path.resolve(__dirname, '../src/lang');
|
||||||
const dest = path.resolve(__dirname, '../build/messages');
|
const dest = path.resolve(__dirname, '../build/messages');
|
||||||
const files = fs.readdirSync(src);
|
const files = fs.readdirSync(src);
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ async function run() {
|
|||||||
await fs.ensureDir(dest);
|
await fs.ensureDir(dest);
|
||||||
|
|
||||||
files.forEach(file => {
|
files.forEach(file => {
|
||||||
const lang = require(`../lang/${file}`);
|
const lang = require(`../src/lang/${file}`);
|
||||||
const keys = Object.keys(lang).sort();
|
const keys = Object.keys(lang).sort();
|
||||||
|
|
||||||
const formatted = keys.reduce((obj, key) => {
|
const formatted = keys.reduce((obj, key) => {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { createPortal } from 'react-dom';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
@ -28,10 +29,11 @@ export function MobileMenu({ items = [], onClose }) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return createPortal(
|
||||||
<div className={classNames(styles.menu)}>
|
<div className={classNames(styles.menu)}>
|
||||||
<Items items={items} />
|
<Items items={items} />
|
||||||
</div>
|
</div>,
|
||||||
|
document.body,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,9 +8,10 @@ import styles from './WebsiteDateFilter.module.css';
|
|||||||
export function WebsiteDateFilter({ websiteId }) {
|
export function WebsiteDateFilter({ websiteId }) {
|
||||||
const [dateRange, setDateRange] = useDateRange(websiteId);
|
const [dateRange, setDateRange] = useDateRange(websiteId);
|
||||||
const { value, startDate, endDate, selectedUnit } = dateRange;
|
const { value, startDate, endDate, selectedUnit } = dateRange;
|
||||||
|
|
||||||
const isFutureDate =
|
const isFutureDate =
|
||||||
value !== 'all' && isAfter(incrementDateRange(dateRange, -1).startDate, new Date());
|
value !== 'all' &&
|
||||||
|
selectedUnit &&
|
||||||
|
isAfter(incrementDateRange(dateRange, -1).startDate, new Date());
|
||||||
|
|
||||||
const handleChange = value => {
|
const handleChange = value => {
|
||||||
setDateRange(value);
|
setDateRange(value);
|
||||||
@ -21,7 +22,21 @@ export function WebsiteDateFilter({ websiteId }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Flexbox justifyContent="center" gap={10}>
|
||||||
|
{value !== 'all' && selectedUnit && (
|
||||||
|
<Flexbox justifyContent="center" className={styles.buttons}>
|
||||||
|
<Button onClick={() => handleIncrement(1)}>
|
||||||
|
<Icon rotate={90}>
|
||||||
|
<Icons.ChevronDown />
|
||||||
|
</Icon>
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => handleIncrement(-1)} disabled={isFutureDate}>
|
||||||
|
<Icon rotate={270}>
|
||||||
|
<Icons.ChevronDown />
|
||||||
|
</Icon>
|
||||||
|
</Button>
|
||||||
|
</Flexbox>
|
||||||
|
)}
|
||||||
<DateFilter
|
<DateFilter
|
||||||
className={styles.dropdown}
|
className={styles.dropdown}
|
||||||
value={value}
|
value={value}
|
||||||
@ -31,22 +46,7 @@ export function WebsiteDateFilter({ websiteId }) {
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
showAllTime={true}
|
showAllTime={true}
|
||||||
/>
|
/>
|
||||||
{value !== 'all' && (
|
</Flexbox>
|
||||||
<Flexbox justifyContent="center" gap={10} className={styles.container}>
|
|
||||||
<Button onClick={() => handleIncrement(1)}>
|
|
||||||
<Icon rotate={90}>
|
|
||||||
<Icons.ChevronDown />
|
|
||||||
</Icon>
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button onClick={() => handleIncrement(-1)} disabled={isFutureDate}>
|
|
||||||
<Icon rotate={270}>
|
|
||||||
<Icons.ChevronDown />
|
|
||||||
</Icon>
|
|
||||||
</Button>
|
|
||||||
</Flexbox>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,14 @@
|
|||||||
.dropdown {
|
.dropdown {
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.buttons button:first-child {
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons button:last-child {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-left: 1px solid var(--base400) !important;
|
||||||
|
}
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
width: 100vw;
|
width: 100vw;
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
grid-row: 1 / 2;
|
grid-row: 1 / 2;
|
||||||
z-index: 1;
|
z-index: var(--z-index-popup);
|
||||||
}
|
}
|
||||||
|
|
||||||
.body {
|
.body {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { safeDecodeURI } from 'next-basics';
|
import { safeDecodeURI } from 'next-basics';
|
||||||
import { Button, Icon, Icons, Text } from 'react-basics';
|
import { Button, Icon, Icons, Text } from 'react-basics';
|
||||||
import usePageQuery from 'components/hooks/usePageQuery';
|
import usePageQuery from 'components/hooks/usePageQuery';
|
||||||
import styles from './FilterTags.module.css';
|
|
||||||
import useMessages from 'components/hooks/useMessages';
|
import useMessages from 'components/hooks/useMessages';
|
||||||
|
import styles from './FilterTags.module.css';
|
||||||
|
|
||||||
export function FilterTags({ params }) {
|
export function FilterTags({ params }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
@ -26,6 +26,7 @@ export function FilterTags({ params }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.filters}>
|
<div className={styles.filters}>
|
||||||
|
<div className={styles.label}>{formatMessage(labels.filters)}</div>
|
||||||
{Object.keys(params).map(key => {
|
{Object.keys(params).map(key => {
|
||||||
if (!params[key]) {
|
if (!params[key]) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -4,19 +4,23 @@
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
.tag {
|
.tag {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
font-size: var(--font-size-sm);
|
font-size: var(--font-size-sm);
|
||||||
border: 1px solid var(--base600);
|
border: 1px solid var(--blue400);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
line-height: 30px;
|
padding: 8px 16px;
|
||||||
padding: 0 8px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
background: var(--blue100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag:hover {
|
.tag:hover {
|
||||||
background: var(--base75);
|
background: var(--blue200);
|
||||||
}
|
}
|
||||||
|
@ -22,12 +22,12 @@ export default function WebsiteDetailsPage({ websiteId }) {
|
|||||||
return (
|
return (
|
||||||
<Page loading={isLoading} error={error}>
|
<Page loading={isLoading} error={error}>
|
||||||
<WebsiteHeader websiteId={websiteId} showLinks={showLinks} />
|
<WebsiteHeader websiteId={websiteId} showLinks={showLinks} />
|
||||||
<WebsiteMetricsBar websiteId={websiteId} sticky={true} />
|
|
||||||
<WebsiteChart websiteId={websiteId} />
|
|
||||||
<FilterTags
|
<FilterTags
|
||||||
websiteId={websiteId}
|
websiteId={websiteId}
|
||||||
params={{ url, referrer, os, browser, device, country, region, city, title }}
|
params={{ url, referrer, os, browser, device, country, region, city, title }}
|
||||||
/>
|
/>
|
||||||
|
<WebsiteMetricsBar websiteId={websiteId} sticky={true} />
|
||||||
|
<WebsiteChart websiteId={websiteId} />
|
||||||
{!website && <Loading icon="dots" style={{ minHeight: 300 }} />}
|
{!website && <Loading icon="dots" style={{ minHeight: 300 }} />}
|
||||||
{website && (
|
{website && (
|
||||||
<>
|
<>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Flexbox } from 'react-basics';
|
import { Flexbox, Loading } from 'react-basics';
|
||||||
import EventDataTable from 'components/pages/event-data/EventDataTable';
|
import EventDataTable from 'components/pages/event-data/EventDataTable';
|
||||||
import EventDataValueTable from 'components/pages/event-data/EventDataValueTable';
|
import EventDataValueTable from 'components/pages/event-data/EventDataValueTable';
|
||||||
import { EventDataMetricsBar } from 'components/pages/event-data/EventDataMetricsBar';
|
import { EventDataMetricsBar } from 'components/pages/event-data/EventDataMetricsBar';
|
||||||
@ -28,13 +28,14 @@ export default function WebsiteEventData({ websiteId }) {
|
|||||||
const {
|
const {
|
||||||
query: { event },
|
query: { event },
|
||||||
} = usePageQuery();
|
} = usePageQuery();
|
||||||
const { data } = useData(websiteId, event);
|
const { data, isLoading } = useData(websiteId, event);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flexbox className={styles.container} direction="column" gap={20}>
|
<Flexbox className={styles.container} direction="column" gap={20}>
|
||||||
<EventDataMetricsBar websiteId={websiteId} />
|
<EventDataMetricsBar websiteId={websiteId} />
|
||||||
{!event && <EventDataTable data={data} />}
|
{!event && <EventDataTable data={data} />}
|
||||||
{event && <EventDataValueTable event={event} data={data} />}
|
{isLoading && <Loading position="page" />}
|
||||||
|
{event && data && <EventDataValueTable event={event} data={data} />}
|
||||||
</Flexbox>
|
</Flexbox>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,10 @@ import MetricsBar from 'components/metrics/MetricsBar';
|
|||||||
import FilterSelectForm from 'components/pages/reports/FilterSelectForm';
|
import FilterSelectForm from 'components/pages/reports/FilterSelectForm';
|
||||||
import PopupForm from 'components/pages/reports/PopupForm';
|
import PopupForm from 'components/pages/reports/PopupForm';
|
||||||
import { formatShortTime } from 'lib/format';
|
import { formatShortTime } from 'lib/format';
|
||||||
import { Button, Column, Icon, Icons, Popup, PopupTrigger, Row, TooltipPopup } from 'react-basics';
|
import { Button, Column, Icon, Icons, Popup, PopupTrigger, Row } from 'react-basics';
|
||||||
import styles from './WebsiteMetricsBar.module.css';
|
import styles from './WebsiteMetricsBar.module.css';
|
||||||
|
|
||||||
export function WebsiteMetricsBar({ websiteId, showFilter = true, sticky }) {
|
export function WebsiteMetricsBar({ websiteId, showFilter = true, showRefresh = true, sticky }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
@ -71,14 +71,12 @@ export function WebsiteMetricsBar({ websiteId, showFilter = true, sticky }) {
|
|||||||
const WebsiteFilterButton = () => {
|
const WebsiteFilterButton = () => {
|
||||||
return (
|
return (
|
||||||
<PopupTrigger>
|
<PopupTrigger>
|
||||||
<TooltipPopup label={formatMessage(labels.addFilter)} position="top">
|
<Button>
|
||||||
<Button>
|
<Icon>
|
||||||
<Icon>
|
<Icons.Plus />
|
||||||
<Icons.Plus />
|
</Icon>
|
||||||
</Icon>
|
{formatMessage(labels.filter)}
|
||||||
{formatMessage(labels.filter)}
|
</Button>
|
||||||
</Button>
|
|
||||||
</TooltipPopup>
|
|
||||||
<Popup position="bottom" alignment="start" className={styles.popup}>
|
<Popup position="bottom" alignment="start" className={styles.popup}>
|
||||||
{close => {
|
{close => {
|
||||||
return (
|
return (
|
||||||
@ -163,8 +161,8 @@ export function WebsiteMetricsBar({ websiteId, showFilter = true, sticky }) {
|
|||||||
<Column defaultSize={12} xl={4}>
|
<Column defaultSize={12} xl={4}>
|
||||||
<div className={styles.actions}>
|
<div className={styles.actions}>
|
||||||
{showFilter && <WebsiteFilterButton />}
|
{showFilter && <WebsiteFilterButton />}
|
||||||
|
{showRefresh && <RefreshButton websiteId={websiteId} />}
|
||||||
<WebsiteDateFilter websiteId={websiteId} />
|
<WebsiteDateFilter websiteId={websiteId} />
|
||||||
<RefreshButton websiteId={websiteId} />
|
|
||||||
</div>
|
</div>
|
||||||
</Column>
|
</Column>
|
||||||
</Row>
|
</Row>
|
||||||
|
Loading…
Reference in New Issue
Block a user