mirror of
https://github.com/umami-software/umami.git
synced 2026-02-04 04:37:11 +01:00
Compare commits
6 commits
489c2712d1
...
1229663814
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1229663814 | ||
|
|
e57239de1e | ||
|
|
abfb78bb98 | ||
|
|
9b310dacef | ||
|
|
fd2e2047cd | ||
|
|
fbd0564133 |
6 changed files with 21 additions and 32 deletions
|
|
@ -42,7 +42,7 @@ export function MobileNav() {
|
||||||
{({ close }) => {
|
{({ close }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<NavMenu padding="3" onItemClick={close} border="bottom">
|
<NavMenu padding="3" onItemClick={close} border="bottom" width="100%">
|
||||||
<NavButton />
|
<NavButton />
|
||||||
{links.map(link => {
|
{links.map(link => {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,13 @@
|
||||||
import { Grid, Heading, Row, Tab, TabList, TabPanel, Tabs } from '@umami/react-zen';
|
import { Grid, Heading, Row, Tab, TabList, TabPanel, Tabs } from '@umami/react-zen';
|
||||||
import { GridRow } from '@/components/common/GridRow';
|
import { GridRow } from '@/components/common/GridRow';
|
||||||
import { Panel } from '@/components/common/Panel';
|
import { Panel } from '@/components/common/Panel';
|
||||||
import { useMessages, useNavigation } from '@/components/hooks';
|
import { useMessages } from '@/components/hooks';
|
||||||
import { EventsChart } from '@/components/metrics/EventsChart';
|
|
||||||
import { MetricsTable } from '@/components/metrics/MetricsTable';
|
import { MetricsTable } from '@/components/metrics/MetricsTable';
|
||||||
import { WeeklyTraffic } from '@/components/metrics/WeeklyTraffic';
|
import { WeeklyTraffic } from '@/components/metrics/WeeklyTraffic';
|
||||||
import { WorldMap } from '@/components/metrics/WorldMap';
|
import { WorldMap } from '@/components/metrics/WorldMap';
|
||||||
|
|
||||||
export function WebsitePanels({ websiteId }: { websiteId: string }) {
|
export function WebsitePanels({ websiteId }: { websiteId: string }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { pathname } = useNavigation();
|
|
||||||
const tableProps = {
|
const tableProps = {
|
||||||
websiteId,
|
websiteId,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
|
|
@ -18,7 +16,6 @@ export function WebsitePanels({ websiteId }: { websiteId: string }) {
|
||||||
metric: formatMessage(labels.visitors),
|
metric: formatMessage(labels.visitors),
|
||||||
};
|
};
|
||||||
const rowProps = { minHeight: '570px' };
|
const rowProps = { minHeight: '570px' };
|
||||||
const isSharePage = pathname.includes('/share/');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid gap="3">
|
<Grid gap="3">
|
||||||
|
|
@ -116,25 +113,6 @@ export function WebsitePanels({ websiteId }: { websiteId: string }) {
|
||||||
<WeeklyTraffic websiteId={websiteId} />
|
<WeeklyTraffic websiteId={websiteId} />
|
||||||
</Panel>
|
</Panel>
|
||||||
</GridRow>
|
</GridRow>
|
||||||
{isSharePage && (
|
|
||||||
<GridRow layout="two-one" {...rowProps}>
|
|
||||||
<Panel>
|
|
||||||
<Heading size="2">{formatMessage(labels.events)}</Heading>
|
|
||||||
<Row border="bottom" marginBottom="4" />
|
|
||||||
<MetricsTable
|
|
||||||
websiteId={websiteId}
|
|
||||||
type="event"
|
|
||||||
title={formatMessage(labels.event)}
|
|
||||||
metric={formatMessage(labels.count)}
|
|
||||||
limit={15}
|
|
||||||
filterLink={false}
|
|
||||||
/>
|
|
||||||
</Panel>
|
|
||||||
<Panel gridColumn={{ xs: 'span 1', md: 'span 2' }}>
|
|
||||||
<EventsChart websiteId={websiteId} />
|
|
||||||
</Panel>
|
|
||||||
</GridRow>
|
|
||||||
)}
|
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@ export function ShareNav({
|
||||||
const items = allItems
|
const items = allItems
|
||||||
.map(section => ({
|
.map(section => ({
|
||||||
label: section.label,
|
label: section.label,
|
||||||
items: section.items.filter(item => parameters[item.id] !== false),
|
items: section.items.filter(item => parameters[item.id] === true),
|
||||||
}))
|
}))
|
||||||
.filter(section => section.items.length > 0);
|
.filter(section => section.items.length > 0);
|
||||||
|
|
||||||
|
|
@ -144,12 +144,12 @@ export function ShareNav({
|
||||||
position={isMobile ? undefined : 'fixed'}
|
position={isMobile ? undefined : 'fixed'}
|
||||||
padding="3"
|
padding="3"
|
||||||
width={isMobile ? '100%' : collapsed ? '60px' : '240px'}
|
width={isMobile ? '100%' : collapsed ? '60px' : '240px'}
|
||||||
maxHeight="100vh"
|
maxHeight="100dvh"
|
||||||
height="100vh"
|
height="100dvh"
|
||||||
border={isMobile ? undefined : 'right'}
|
border={isMobile ? undefined : 'right'}
|
||||||
borderColor={isMobile ? undefined : '4'}
|
borderColor={isMobile ? undefined : '4'}
|
||||||
>
|
>
|
||||||
<Row as="header" gap alignItems="center" paddingY="3" justifyContent="space-between">
|
<Row as="header" gap alignItems="center" justifyContent="space-between">
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<a href={logoUrl} target="_blank" rel="noopener" style={{ marginLeft: 12 }}>
|
<a href={logoUrl} target="_blank" rel="noopener" style={{ marginLeft: 12 }}>
|
||||||
<Row alignItems="center" gap>
|
<Row alignItems="center" gap>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ export function MobileMenuButton(props: DialogProps) {
|
||||||
<Menu />
|
<Menu />
|
||||||
</Icon>
|
</Icon>
|
||||||
</Button>
|
</Button>
|
||||||
<Modal placement="left">
|
<Modal placement="left" offset="80px">
|
||||||
<Dialog variant="sheet" {...props} style={{ width: 'auto' }} />
|
<Dialog variant="sheet" {...props} style={{ width: 'auto' }} />
|
||||||
</Modal>
|
</Modal>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,16 @@ import { safeDecodeURIComponent } from '@/lib/url';
|
||||||
const MAXMIND = 'maxmind';
|
const MAXMIND = 'maxmind';
|
||||||
|
|
||||||
const PROVIDER_HEADERS = [
|
const PROVIDER_HEADERS = [
|
||||||
|
// Umami custom headers (cloud mode only)
|
||||||
|
...(process.env.CLOUD_MODE
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
countryHeader: 'x-umami-client-country',
|
||||||
|
regionHeader: 'x-umami-client-region',
|
||||||
|
cityHeader: 'x-umami-client-city',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
// Cloudflare headers
|
// Cloudflare headers
|
||||||
{
|
{
|
||||||
countryHeader: 'cf-ipcountry',
|
countryHeader: 'cf-ipcountry',
|
||||||
|
|
@ -66,13 +76,13 @@ function decodeHeader(s: string | undefined | null): string | undefined | null {
|
||||||
return Buffer.from(s, 'latin1').toString('utf-8');
|
return Buffer.from(s, 'latin1').toString('utf-8');
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getLocation(ip: string = '', headers: Headers, hasPayloadIP: boolean) {
|
export async function getLocation(ip: string = '', headers: Headers, skipHeaders: boolean) {
|
||||||
// Ignore local ips
|
// Ignore local ips
|
||||||
if (!ip || (await isLocalhost(ip))) {
|
if (!ip || (await isLocalhost(ip))) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasPayloadIP && !process.env.SKIP_LOCATION_HEADERS) {
|
if (!skipHeaders && !process.env.SKIP_LOCATION_HEADERS) {
|
||||||
for (const provider of PROVIDER_HEADERS) {
|
for (const provider of PROVIDER_HEADERS) {
|
||||||
const countryHeader = headers.get(provider.countryHeader);
|
const countryHeader = headers.get(provider.countryHeader);
|
||||||
if (countryHeader) {
|
if (countryHeader) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import ipaddr from 'ipaddr.js';
|
import ipaddr from 'ipaddr.js';
|
||||||
|
|
||||||
export const IP_ADDRESS_HEADERS = [
|
export const IP_ADDRESS_HEADERS = [
|
||||||
|
...(process.env.CLOUD_MODE ? ['x-umami-client-ip'] : []), // Umami custom header (cloud mode only)
|
||||||
'true-client-ip', // CDN
|
'true-client-ip', // CDN
|
||||||
'cf-connecting-ip', // Cloudflare
|
'cf-connecting-ip', // Cloudflare
|
||||||
'fastly-client-ip', // Fastly
|
'fastly-client-ip', // Fastly
|
||||||
|
|
@ -81,7 +82,7 @@ export function getIpAddress(headers: Headers) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header === 'forwarded') {
|
if (header === 'forwarded') {
|
||||||
const match = ip.match(/for=(\[?[0-9a-fA-F:.]+\]?)/);
|
const match = ip.match(/for=(\[?[0-9a-fA-F:.]+]?)/);
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
return resolveIp(match[1]);
|
return resolveIp(match[1]);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue